Ember: How do I get simple form data without a model? - ember.js

I'm trying to do get a single string from a form input and log it to the console. Is there a way to do this without using an Ember Data model?
Here's the form component in Handlebars:
// my-form.hbs
<form {{action "grabNewEmail" on="submit"}}>
{{input value="Your Email Address"}}
<button class="btn btn-primary">Get Early Access</button>
</form>
And here's the current code from the component:
// my-form.js
import Ember from 'ember';
export default Ember.Component.extend({
actions: {
grabNewEmail() {
let email = /* I want to get this string from the input on submit */
console.log(email);
}
}
});
Is it possible to simply grab the input string, or am I required to create a model?
Thanks!

Instead of using a model, you can use a component property to store and retrieve the data.
In your hbs:
{{input value=newEmailAddress}}
And in your component you can do:
export default Ember.component.extend({
newEmailAddress: "", // Not really needed, just to be explicit that this variable exists in the component.
actions: {
grabNewEmail () {
let email = this.get('newEmailAddress');
}
}
});

Related

Ember component call an action in a route or controller

I have a component the main purpose of which is to display a row of items.
Every row has a delete button to make it possible to delete a row. How is possible to pass an action from a template to the component which will trigger an action in a router ?
Here is the template using the component:
#templates/holiday-hours.hbs
{{#each model as |holidayHour|}}
{{holiday-hour holiday=holidayHour shouldDisplayDeleteIcon=true}}
{{/each}}
Here is the component template:
# templates/components/holiday-hour.hbs
...
div class="col-sm-1">
{{#if shouldDisplayDeleteIcon}}
<button type="button" class="btn btn-danger btn-sm mt-1" {{action 'deleteHoliday' holiday}}>
<span class="oi oi-trash"></span>
</button>
{{/if}}
</div>
I'm using the same component to display a row and to create a new item (holiday-hour).
I'm using ember 3.1.2
Thank you
You have to send the actions up from the component to the route. The main way to do this is by adding actions to your component that "send" the action to the parent. Once the action is sent you have to tell the component what action on the route to trigger by passing in the action as a parameter. Below is an example of how to do this.
Component js
# components/holiday-hour.js
...
actions: {
deleteHoliday(){
this.sendAction('deleteHoliday');
}
}
Template for route
#templates/holiday-hours.hbs
...
{{#each model as |holidayHour|}}
{{holiday-hour holiday=holidayHour shouldDisplayDeleteIcon=true deleteHoliday='deleteHoliday'}}
{{/each}}
Route js
#routes/holiday-hours.js
...
actions: {
deleteHoliday(){
//code to delete holiday
}
}
I will try to give a general answer because your question is not giving enough/all info regarding the route actions etc. Long answer short, using closure functions. Assuming this is your route js file routes/holiday-hours.js
import Route from '#ember/routing/route';
export default Route.extend({
model(){ /*... some code */ },
setupController(controller){
this._super(controller);
controller.set('actions', {
passToComponent: function(param) { //.... function logic }
})
}
});
Note: in the above snippet, I'm using setupController to create actions. Alternatively, you can put the actions inside a controller file otherwise actions directly inside the route will throw an error.
So I want the action passToComponent to be called from the component. This is what you do to make it accessible inside the component.
{{#each model as |holidayHour|}} {{holiday-hour holiday=holidayHour shouldDisplayDeleteIcon=true callAction=(action 'passToComponent')} {{/each}}
Now we have passed the action to the component and here's how to call it from the component. Note: I have added a param just to show that it can take a param when called within the component.
import Component from '#ember/component';
export default Component.extend({
actions: {
deleteHoliday: ()=> {
this.get('callAction')() /*Pass in any params in the brackets*/
}
}
});
You will also see demonstrations using sendAction which is rather old and acts more of an event bus that is not very efficient. Read more from this article

Ember: globally-available search component (and action)?

I want to have a search field inside a component that can be placed anywhere in the app. It can appear on any template, or nested in components. The search form would accept user input (search term) and submit would trigger a search action which transitions to a results template.
Seems simple enough, but I can't figure out how to make an action globally available. And if I could, how do you pass the inputted term to the action in the first place? There's surprisingly little info on how to handle form submits with Ember CLI.
Thus far I've just been submitting a regular form with action='/results'. But that's obviously reloading the app.
I've been messing with creating an action in the index controller like this:
export default Ember.Controller.extend(defaultParams, {
term: '',
actions: {
keywordSearch() {
this.transitionToRoute('results', { queryParams: { q: this.get('term') }});
}
}
});
Then passing a closure action down to my search component, which is nested 2 deep from the index template.
index.hbs:
{{index-search keywordSearch=(action "keywordSearch")}}
index-search.hbs (component):
{{search-field keywordSearch=keywordSearch }}
search-field.hbs (nested component):
<form {{ action (action keywordSearch) on='submit' }}>
{{ input value=term }}
<button type="submit">Search</button>
</form>
And that will run the action, but the term is not supplied. How do you supply term to the closure action?
And...do I really need to pass the action down to every single place the search field is going to appear in the app, or is there an easier way to do it?
Instead of writing actions in all components and routes, you can create a service for search. Inject the service into the component and handle the route transition from service method. Check the sample code below,
Search-component.hbs
<form {{ action (action search) on='submit' }}>
{{ input value=keyword }}
<button type="submit">Search</button>
</form>
Search-component.js
export default Ember.Component.extend({
globalSearch: Ember.inject.service('search'),
actions: {
search() {
const { keyword } = this.getProperties('keyword');
this.get('globalSearch').showResults(keyword).then(() => {
alert('Success');
}, (err) => {
alert('Error while searching: ' + err.responseText);
});
}
}
});
Service - app/services/search.js
import Ember from 'ember';
export default Ember.Service.extend({
init() {
this._super(...arguments);
},
showResults(keyword) {
// write code for transition to search results route here
}
});

ember - combining data for two models into single result for power sort

I'm getting data from two models in one of my routes using RSVP hash, and then trying to combine those results in my controller so that they can be used in a select for power sort. However something doesn't seem to be working.
My route looks like this:
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return Ember.RSVP.hash({
newBook : this.store.createRecord('book'),
authors : this.store.findAll('author'),
publishing_houses : this.store.findAll('publishing-house')
});
},
setupController(controller, model) {
this._super(...arguments);
Ember.set(controller, 'authors', model.authors);
Ember.set(controller, 'publishing_houses', model.publishing_houses);
},
actions: {
save() {
this.modelFor(this.routeName).get('newBook').save();
}
}
});
My template looks like this:
<form {{action "save" on="submit"}}>
{{input value=model.newBook.title placeholder="Title"}}<br>
{{input value=model.newBook.price placeholder="Price"}}<br>
{{#power-select class="select"
selected=model.newBook.author
options=model.authors
onchange=(action (mut model.newBook.author)) as |author|}}
{{author.name}}
{{/power-select}}
{{#power-select class="select"
selected=model.newBook.publisher
options=model.publishers
onchange=(action (mut model.newBook.publisher)) as |publisher|}}
{{publisher.name}}
{{/power-select}}
<input type="submit" value="Save">
</form>
and my controller, which I think is the problem looks like this:
import Ember from 'ember';
export default Ember.Controller.extend({
publishers: function() {
var authors = this.get("authors");
var publishingHouses = this.get("publishing_houses");
return authors.concat(publishingHouses);
}
});
I'm still figuring out how to use controllers. Am I accessing the model data correctly in the controller? Also is this the proper way to create a property to be used in a template?
In setupController hook, you don't need to explicitly set authors and publishing_houses since it will be set by default through super function call.
In your controller, you can try accessing it like this.get("model.authors") and in the same way for other properties publishing_houses, newBook
To access RSVP return model from route, you should access it directly without get function.
`this.modelFor(this.routeName).newBook.save()
For concatenation and other stuff you refer http://emberjs.com/api/classes/Ember.Enumerable.html
reduce might suite your needs.

ember autofocus component after insertion into DOM

I want to display an input field, and immediately autofocus it upon clicking a button. Im still new to Ember so i am not sure this is the correct approach, but I tried to wrap as an ember component
template
{{#if showCalendarForm}}
{{new-calendar focus-out='hideNewCalendar' insert-newline='createCalendar'}}
{{else}}
<button class="btn btn-sm btn-primary" {{action "showNewCalendar"}}>New</button>
{{/if}}
new-calendar component handlebars:
<div class="input-group">
{{input
class = 'form-control'
id = 'newCalendar'
type = 'text'
placeholder = 'New calendar'
value = calendarName
action = 'createCalendar'
}}
</div>
new-calendar component js
import Ember from 'ember';
export default Ember.Component.extend({
didInsertElement: function() {
this.$().focus();
}
});
When I click the button, the text field is displayed, but autofocus and hitting enter doesnt work
The way the jQuery is written, you are trying to set focus on the <div class="input-group">, try this instead:
didInsertElement: function() {
this.$('input').focus();
}
Another way to do this would be to extend the Ember.TextField:
export default Ember.TextField.extend({
becomeFocused: function() {
this.$().focus();
}.on('didInsertElement')
});
Then, in your new-calendar template, use this component:
{{focus-input
class = 'form-control'
id = 'newCalendar'
type = 'text'
placeholder = 'New calendar'
value = calendarName
action = 'createCalendar'
}}
This way you can reuse the focus-input component wherever you need to.
As for hitting enter to create the calendar, I think you want to listen for the keyPress event, check to see if it's the enter key, and then send the action rather than trying to use insert-newline='createCalendar'.
//in FocusInputComponent
keyPress: function(e) {
// Return key.
if (e.keyCode === 13) {
this.sendAction();
}
}
Try wrapping your focus call in an Ember.run and schedule it to be run in the after render queue like this:
didInsertElement: function()
{
Ember.run.scheduleOnce('afterRender', this, function() {
this.$().focus();
});
}
this blog post has helped me a lot in understanding ember's lifecycle hooks:
http://madhatted.com/2013/6/8/lifecycle-hooks-in-ember-js-views

How to build a form with access to text input values on submit with ember.js

I'm using the latest pre 1.0 of ember.js and wanted to get away from using the deprecated button for simple forms.
I have something that works but I don't feel like this is the correct way to wire up a view that has both a text input and a button that needs access to that text.
Here is the basic view
{{#view PersonApp.AddPersonView}}
{{view Ember.TextField valueBinding="username"}}
{{#with this as username}}
<input type="submit" value="add" {{action addPerson username}}/>
{{/with}}
{{/view}}
Here is the view
PersonApp.AddPersonView = Ember.View.extend({
username: null,
addPerson: function(event) {
var username = event.context.username;
if (username) {
PersonApp.personController.addPerson(username);
this.set('username', ''); //this does not currently work
}
}
});
The only other issue I'm having is that I don't have access to username the usual way. ie - this.get('username') but in addition I can't clear the textbox value (even though it's shown above).
I'm looking to build a modern version of this gist (previous version of ember) https://gist.github.com/1477225
I see three issues here (perhaps there are more). First, username will not be a field in the event.context, but will actually be the event context. Secondly, I believe you need to specify view.username in the valueBinding, otherwise the controller is the default home of the property (I believe). Then, to set it to initial state you need to set it to null. Third, the target of your action will be the router, so you need to specify the view as the target.
This should work:
{{#view PersonApp.AddPersonView}}
{{view Ember.TextField valueBinding="view.username"}}
{{#with this as username}}
<input type="submit" value="add" {{action addPerson username target="this"}}/>
{{/with}}
{{/view}}
PersonApp.AddPersonView = Ember.View.extend({
username: null
addPerson: function(event) {
var username = event.context;
if (username) {
this.get('controller').addPerson(username);
this.set('username', null);
}
}
});
Also, a better way of creating a new person would be to create a blank person model, bind the controller and view to that, and then save the record, afterwards setting the binding back to null.
You can do the validation and then pass data right now, even with Gidrius' code. The only thing you need to do is write the validation code in the submit handling method. Or, 'cause we`re talking client-side validation anyway, you can do it on field value change or blur, which will give the user almost instant feedback on what he is doing.
I still couldn't get something like this.get('username') to work but I ended up with the following
{{#view PersonApp.AddPersonForm}}
{{view Ember.TextField valueBinding="username"}}
<input type="submit" value="add" {{action addPerson this}}/>
{{/view}}
PersonApp.AddPersonForm = Ember.View.extend({
addPerson: function(event) {
var username = event.context.username;
if (username) {
PersonApp.personController.addPerson(username);
event.context.set('username', '');
}
}
});
probably a bit too late, but might be helpful to someone else.
Usually form field value will be bind to controller or model, so all you need is to have is a submit function in the controller so whenever function will be called you will have access to the fields via bindings.
Here is how it all could look like, assuming you are using latest pre.4 ember
Updated
// DOM part
<form {{action submitForm on="submit"}}>
{{view Ember.TextField valueBinding="username"}}
<button type="submit">add</button>
</form>
And here is a controller
PersonApp.PersonController = Ember.ArrayController({
username: '',
submitForm: function() {
var u = this.get('username'); // saving value to variable
this.set('username',''); // sets username to ''
console.log(u); // will output saved username
}
});