Generating forms and handling submit properly with Ember.js rc1 - ember.js

I'm having trouble figuring out how to properly populate and accept an update from an Ember form under RC1. I've boiled it down to the bare essentials in this jsfiddle. I've made it far enough to display the form for a particular entity (user with first and last name) and the current values populate in the fields. However, as the user types, the fields actually update with each keystroke, and clicking the back button reveals that the data has already been changed without clicking the update button. I'd prefer to keep some logic in between the updates and only confirm an update after the user clicks the update button.
{{#view App.PersonFormView}}
First name: {{view Ember.TextField valueBinding="firstName"}}
Last name: {{view Ember.TextField valueBinding="lastName"}}
<button {{action "updatePerson"}}>Update</button>
{{/view}}
In the form template, I was trying to follow one of the Ember.js examples, but doing so resulted in a long delay and a monstrous deprecation warning using RC1. I think the examples are still being updated. I'd prefer a more handlebars-elegant way of coding the form if it existed.
The second problem is that I cannot capture the submit event itself, either on the form view or the controller. I don't know where this event is going.
App.PersonFormController = Ember.ObjectController.extend({
updatePerson: function(params){
// this doesn't get triggered as I would have expected
console.log('controller updatePerson: '+params);
}
});
App.PersonFormView = Ember.View.extend({
tagName: 'form',
updatePerson: function(params){
// this doesn't get triggered either!
console.log('updatePerson params: '+params);
}
});
In summary, I need to:
populate the input fields with the values without having them linked directly back to the model's data while the user is typing
catch the submit button's (or other control would be fine) clicked event along with the fields - and the entity's id - so that I can set them back on the model's data manually

There are several things:
I cannot capture the submit event itself
Events are fired in the controller and the route, not the view. The reason why your controller PersonFormController wasn't catching the event, is because the name is wrong. The controller should be named after the route: EditPersonController.
It's generally good to pass the model along with the action:
<button {{action "updatePerson" content}}>Update</button>
Here is an updated version that catches the event: http://jsfiddle.net/teddyzeenny/L9HMm/5/
populate the input fields with the values without having them linked directly back to the model's data
It's generally good practice to bind the fields directly to the model, to avoid code duplication.
Your problem is not that the fields are bound directly to the model, it's that you have no control over what is happening (saved, not saved, left the route...)
To have solid control, it's best to put your updating logic in your route. That way you can act accordingly when the user enters/leaves the route.
To catch your events in the route:
App.EditPersonRoute = Ember.Route.extend({
events: {
updatePerson: function(record) {
record.one('didUpdate', this, function() {
this.transitionTo('index');
});
record.get('transaction').commit();
}
}
});
To rollback changes if the user doesn't click on Update, use the deactivate callback in the route:
App.EditPersonRoute = Ember.Route.extend({
deactivate: function() {
this.modelFor('editPerson').get('transaction').rollback();
},
events: {
updatePerson: function(record) {
record.one('didUpdate', this, function() {
this.transitionTo('index');
});
record.get('transaction').commit();
}
}
});
Now these won't work in the fiddle since you are not using ember-data models.

Related

Right way for conditional transition depending on where view is rendered in emberjs

Hi I have a view call AutocompleteView its job is to put google places autocomplete on landing page.There is no map rendered on this page just a textbox.
what I am try to achieve is user should just use autocomplete textbox on landing page. On entering some place of choice. I will transition the user to a specific route where the same AutocompleteView will be rendered with map so tha user can change his choice of place.
When user chang the location on the page where map is rendered no transition is needed here
the approch I am trying is I check the parent view where
{{view AutocompleteView}}
has been rendered using this.get('ParentView") and based on the parent view
say if ParentView is Application(landing page) I will transition or else no transition will occur.
I have no good experienve of javascript mvc my doubt
Is it right to rely on ParentView for just transtions or is there a better way??
App.AutocompleteAddressView = Ember.View.extend({
tagName: 'input',
didInsertElement: function() {
var options = {
componentRestrictions: {country: "xx"}
};
var autocomplete = new google.maps.places.Autocomplete(this.$()[0], options);
console.log(this.get('parentView'));
}
});
It sounds like what you want is to handle the action of "entering text in the search box" differently based on which route is currently active -- the landing page or the results page.
The Ember way to do this is to trigger an action from your view using this.get('controller').send(#{actionName}). That action will bubble up from the view's controller to the current route. Then you can handle that action differently depending on which route is active, e.g.:
App.IndexRoute = Em.Route.extend({
...
actions: {
search: function() {
# transition to results page
}
}
});
App.ResultsRoute = Em.Route.extend({
...
actions: {
search: function() {
# update visible results
}
}
});
To make this easier, you might want to use a subclass of Ember.TextField for your AutocompleteAddressView. Then you can define an action property on your view that will automatically get triggered when the user types enter/return.

Modifying template based on record saving

I have a form and handle the submit code in my router. My template has this:
{{#if isSaving}}
Saving...
{{else}}
<form>
inputs...
</form>
{{/if}}
My code for saving within the router is this (ctrl is controller):
events: {
saveCompany: function(record, ctrl){
if (!record.get('isDirty')) return;
ctrl.set('isSaving', true);
record.one('didUpdate', this, function() {
ctrl.set('isSaving', false);
});
record.get('transaction').commit();
},
When user clicks submit, "Saving..." is properly shown and hidden when update is done. But I already handle didCreate, DidUpdate etc. in my model definition - by showing some popups, but I still want to be able to prevent the user from fiddling with my form while data is being saved, that's why I use "record.one(...)". What would be best way to do so in a more "generic" way than the way I did it?
If your controller is an Ember.ObjectController you already have access, transparently, to the properties on the content. If the content is the record you're observing, you can just observe isSaving on the controller without any additional code, bindings, or what have you.

How do I refresh a model in ember.js after I have added a record with createRecord()?

UPDATE: This is outdated, my blog is not Ember-based anymore. Basically, my question is simple. I added a record with createRecord(). I can see the didCreate event fired, but I don't know how to make ember load and display what I have just created. This case is about adding a comment to an post -- that is what I want to see instantly.
UPDATE: I have no jsfiddle, but I can show off the live app/site I am talking about is my own blog, here: http://eduardmoldovan.com/
The templates are at the bottom of the page, the javascript is here: http://eduardmoldovan.com/static/eduardmoldovan.com/javascripts/ngin.js
You can manually add your new record to array of records from the didCreate hook:
var newRecord = transaction.createRecord(Ngin.Comment, {
articleUrl: articleUrl,
name: name,
url: url,
email: email,
body: body,
secret: secret
});
newRecord.one('didCreate', this, function () {
this.get('comments').pushObject(newRecord);
});
transaction.commit();
Or, if you want to reload from server, use the reload method:
controller.get("comments").reload();
Edit
After examining sourcecode I found an update method in class RecordArray. It seems to be the right one.
Assuming you have used commit() to save the comment, something like this should work:
{{#each comment in post.comments}}
{{comment.text}}
{{/each}}
The following is neither efficient nor the best method but it will notify the view. And will try to find a better method. Im sure theres an update method and one that notifies the view. Cannot find it immediately.
template
<script type="text/x-handlebars" id="posts">
<button {{action 'newObject'}} ></button>
controller
App.PostsController = Ember.ArrayController.extend
actions: {
newObject: function() {
this.store.createRecord('post', {id: xxxx, title: 'Default title'});
var all = this.store.filter('post', function(post) {
return post; // return each object
}); //This filter will include the new post record
this.set('model', all); // set will notify the view of a mutated array
}
},

binding context to action in ember textfield

I've got an ember application that needs to manage multiple chat windows. A window for each active chat is created within an {{#each}} loop. This is straightforward enough. The place that I'm having trouble is sending the chat message when the user presses enter.
The window looks like this
{{#each chats}}
... stuff to display already existing chats...
{{view Ember.TextField valueBinding="text" action="sendChat"}}
<button {{action sendChat this}}> Send </button>
{{/each}}
This works fine for the button, since I can pass this to it. By default the function defined in the textfield view action just gets the text within that textfield, which is not enough in this case. Since there can be multiple chat windows open, I need to know which window the message was typed into. Is it possible to pass this to the textfield action function? (or can you suggest a different way to solve this problem?)
Add contentBinding="this" to the definition of the view, like:
{{view Ember.TextField valueBinding="text" action=sendChat contentBinding="this"}}
EDIT
Ember master already has this change, but the official downloadable verstion still don't.. so you will need to subclass the Ember.TextField and change its insertNewline to achieve required functionality:
App.ActionTextField = Em.TextField.extend({
insertNewline: function(event) {
var controller = this.get('controller'),
action = this.get('action');
if (action) {
controller.send(action, this.get('value'), this);
if (!this.get('bubbles')) {
event.stopPropagation();
}
}
}
});
After that, the action handler will receive additional argument, the view:
{{view App.ActionTextField valueBinding="text" action=sendChat myfieldBinding="this"}}
and in controller:
sendChat: function (text, view) {
var myField = view.get('myfield');
//do stuff with my field
}
You may use ember master instead of subclassing Ember.TextField..
I hope the ember guys will release the next version soon..
I know this question has been answered but I said let me add some information that may help out someone in the situation of actions and TextField. One word "Component". TextField in Ember is a Component so if you think of TextField from that perspective it may help when it comes to sending actions and using TextField in an application.
So when you say App.SomeTextField = Ember.TexField.extend({...});App.SomeTextField is subclassing Ember.TextField (remember which is a component). You could add your logic inside and that works and you could access it from your template such as {{view App.SomeTextField}}
You may be thinking I see the word 'view' this guy sucks, TextField is a View. Well, it is sort of a View because Ember Components are a subclass of Ember.View so they have all that Views have. But there are some important things to keep in mind Components un-like Views do not absorb their surrounding context(information/data), they lock out everything and if you want to send something from the outside surrounding context you must explicitly do so.
So to pass things into App.SomeTextField in your template where you have it you would do something like {{view App.SomeTextField value=foo action="sendChat"}} where you are passing in two things value, and action in this case. You may be able to ride the fine line between View/Component for a bit but things come crashing why is your action not sending?
Now this is where things get a little trippy. Remember TextField is a Component which is subclassed from View but a View is not a Component. Since Components are their own encapsulated element when you are trying to do this.get('controller').send('someAction', someParam), "this" is referring to the Component its self, and the controller is once again the component its self in regards to this code. The action that you are hoping will go to the outside surrounding context and your application will not.
In order to fix this you have to follow the protocol for sending actions from a Component. It would be something like
App.SomeTextField = Ember.TextField.extend({
//this will fire when enter is pressed
insertNewline: function() {
//this is how you send actions from components
//we passed sendChat action in
//Your logic......then send...
this.sendAction('sendChat');
}
});
Now in the controller that is associated with where your SomeTextField component/view element is you would do
App.SomeController = Ember.Controller.extend({
//In actions hash capture action sent from SomeTextField component/view element
actions: {
sendChat: function() {
//Your logic well go here...
}
}
});
Now I said to think of TextField as a Component but I have been riding the tail of the view and declaring {{view AppSomeTextField...}}. Lets do it like a component.
So you would have in your template where you want to use it
//inside some template
`{{some-text-field}}`
Then you get a specfic template for the component with the name:
//template associated with component
<script type="text/x-handlebars" data-template-name="components/some-text-field">
Add what you want
</script>
In your JS declare your component:
//important word 'Component' must be at end
App.SomeTextFieldComponent = Ember.TextField.extend({
//same stuff as above example
});
Since we on a role you could probably get the same functionality using Ember input helpers. They are pretty powerful.
{{input action="sendChat" onEvent="enter"}}
Welp hopefully this information will help someone if they get stuck wondering why is my action not sending from this textField.
This jsBin is a sandBox for Components/Views sending actions etc....Nothing too fancy but it may help someone..
http://emberjs.jsbin.com/suwaqobo/3/
Peace, Im off this...

infinite scroll with ember.js (lazy loading)

I have a view where there can be a large number of items for the user to scroll through and I'd like to implement infinite scrolling to enable progressive loading of the content.
It looks like some folks have done pagination but Google doesn't bring up anyone discussing how they've done infinite lists with Ember/Ember Data. Anyone already worked through this and have a blog post/example code to share?
I've implemented an infinite scroll mechanism at the GitHub Dashboard project, I'm currently developing. The feature is added in commit 68d1728.
The basic idea is to have a LoadMoreView which invokes the loadMore method on the controller every time the view is visible on the current viewport. I'm using the jQuery plugin inview for this. It allows you to register for an inview event, which is fired when the element of the specified selector is visible on screen and when it disappears.
The controller also has properties which indicate whether there are more items to load and if there are currently items fetched. These properties are called canLoadMore and isLoading.
The LoadMoreView basically looks like this:
App.LoadMoreView = Ember.View.extend({
templateName: 'loadMore',
didInsertElement: function() {
var view = this;
this.$().bind('inview', function(event, isInView, visiblePartX, visiblePartY) {
if (isInView) Ember.tryInvoke(view.get('controller'), 'loadMore');
});
}
});
where the loadMore template is defined as follows:
{{#if isLoading}}
fetching some more stuff <img width="10" src="img/ajax-loader.gif" >
{{else}}
{{#if canLoadMore}}
<a {{action "loadMore" target="controller" }}>click to load more items</a>
{{else}}
<i>no more items</i>
{{/if}}
{{/if}}
The controller which handles the fetching of more items is then implemented as follows. Note that in the loadMore method a query on the store is performed, which loads a specific page of of entries for a model.
App.EventsController = Ember.ArrayController.extend({
currentPage: 1,
canLoadMore: function() {
// can we load more entries? In this example only 10 pages are possible to fetch ...
return this.get('currentPage') < 10;
}.property('currentPage'),
loadMore: function() {
if (this.get('canLoadMore')) {
this.set('isLoading', true);
var page = this.incrementProperty('currentPage');
// findQuery triggers somehing like /events?page=6 and this
// will load more models of type App.Event into the store
this.get('store').findQuery(App.Event, { page: page });
} else {
this.set('isLoading', false);
}
}
});
The only thing left is to initially set the content of the controller to the result of a filter function, so the content is updated when new models are loaded into the store (which happens due to the findQuery method in the loadMore of the controller). Also, a query hash is added when the filter is invoked. This ensures that an initial query to the server is made.
App.eventsController = App.EventsController.create({
content: []
});
var events = App.store.filter(App.Event, { page: 1 }, function(data) {
// show all events; return false if a specific model - for example a specific
// type of event - shall not be included
return true;
});
Were you aware of the newly released Ember.ListView component?
https://github.com/emberjs/list-view
It was announced at the February San Francisco Ember Meetup. Here's a slidedeck from Erik Bryn, one of the Ember Core developers about using it:
http://talks.erikbryn.com/ember-list-view/
I'm writing an infinite pagination plugin for Ember based on #pangratz's work.
Please fire any issues on there if you have questions or improvements that you'd like.
I would recommend using Ember Infinity addon. It supports Ember 1.10 through to 2.0+. It's relatively easy to setup. You only need to modify your route and template.
Route (Product is example model):
import InfinityRoute from 'ember-infinity/mixins/route';
export default Ember.Route.extend(InfinityRoute, {
model() {
/* Load pages of the Product Model, starting from page 1, in groups of 12. */
return this.infinityModel('product', { perPage: 12, startingPage: 1 });
}
});
Template:
{{#each model as |product|}}
...
{{/each}}
{{infinity-loader infinityModel=model}}
When {{infinity-loader}} component becomes visible it sends an action to your route, so it knows to update model array with new (fetched) records.
First request will be sent to:
/products?per_page=12&page=1
So you also need to prepare your backend API to handle these query params. It's obviously customizable, take a look at Advanced Usage section of Readme.
Note:
Both using ListView (#commadelimited's answer) and views with ArrayController (#pangratz's answer) is deprecated/removed as of Ember 2.0 being stable version.