Limit re-rendering of page through two-way binding - ember.js

I'm writing an editor for the blog-posts on my homepage.
I'm using Ember's textarea-helper to bind an attribute of my model to the editor. In this textarea I type a post in markdown. Below will the output be automatically rendered (like the Stackoverflow-editor does).
My markdown also contains media-embeds from pages like YouTube or Soundcloud which makes the re-rendering very slow.
How can I limit the re-rendering of the page to like only once every five seconds?

You can use Ember.run.throttle to avoid updating render markup too often.
Example (see also JsBin):
App.IndexController = Ember.Controller.extend({
markdown: 'this is markdown',
rendered: '',
markdownChanged: function() {
Ember.run.throttle(this, this.renderOutput, 5000);
}.observes('markdown').on('init'),
renderOutput: function(){
this.set('rendered', 'I RENDERED THIS: ' + this.get('markdown'));
}
});

Related

displaying & editing datetime / datetime-tz in ember

what is the best way to define and use dates , datetime and datetime-tz in emberjs ?
I can put
App.Customer = DS.Model.extend({
testDate: DS.attr('date', {defaultValue: '2013-09-13'),
testDateTime: DS.attr('date', {defaultValue: '2013-09-13T20:47:20+01:00'),
into the model, but it seems to me that I can only put
{{input type="datetime-local"
into the template. this allows me to update the field according to the local time (which may not be what I want), but has a nice drop-down calendar and allows me to enter the time
Any other type (datetime-tz for example) seems to only show as a text input.
thanks for your time
datetime-tz is not a valid input type. http://www.w3.org/TR/html-markup/input.html
The way that datetime-local is styled is not part of the spec and is left up to the browser.
You might want to use something like the jQueryUI datepicker to ensure that things work the same across browsers. In that case you could extend the Ember.TextField view to handle setting up and tearing down the datepicker widget.
App.DatePicker = Ember.TextField.extend({
didInsertElement : function(){
this.$().datepicker();
},
willDestroyElement : function(){
this.$().datepicker('destroy');
}
});
{{view App.DatePicker valueBinding="date"}}
Here's a JSBin : http://jsbin.com/ucanam/1054/edit

Ember.js how to design different representations of Data (with TodoMVC as an example)?

I would like to know what's the best way of designing the display of different representations of the same data model in Ember.js. To ask my question, I'll use the TodoMVC of Ember.JS, which has 3 representations of todo-data:
any todo, i.e. the entire todo list (TodosIndexRoute)
todos that are still active and incomplete (TodosActiveRoute)
todos that have been completed (TodosCompletedRoute)
Currently, you can see each of the 3 by clicking on the words at the bottom of the list, directing to a different URL each time. Since currently each representation has a route, it makes sense that each representation gets its unique URL. The main page displays the entire todo list (1.).
A. What is the best ember.js design to make the main page display all 3 representations (i.e. under just one URL)?
B. How about the best design that displays all 3 on the main page as well as on separate pages?
Currently I only figured out a clumsy way and made this modified TodoMVC app that shows incomplete and completed lists at the bottom of the page.
In index.html, I added new named lists:
{{#each todosactive itemController="todo"}}
{{ title }},
{{/each}}
In the js router, I copied TodosActiveRoute and TodosCompletedRoute into TodoIndexRoute, which is code duplication, very bad.
Todos.TodosIndexRoute = Ember.Route.extend({
setupController: function () {
var todos = Todos.Todo.find();
this.controllerFor('todos').set('filteredTodos', todos);
var todos_active = Todos.Todo.filter(function (todo) {
if (!todo.get('isCompleted')) {
return true;
}
});
this.controllerFor('todos').set('todosactive', todos_active);
...
});
I feel like I'm missing an elegant way of doing this, but my current ember.js knowledge is very limited. Should I use {{partial}}, {{render}}, render, or something else?
I tried {{ partial }} and {{ render }}, but I can't get them to display any data .
Thanks for helping out.
A) Ember tries to work really closely with urls. This is a good thing since if you want to share a url, the view should be consistent. The url is a powerful tool and each unique url should link to the same unique page. Having one url that links to multiple views isn't great, and certainly not shareable. If you have some time listen to some talks by Tom Dale and Yehuda Katz for an interesting overview of ember and what they're trying to do.
B) You can include different views on one page. Have a look at the guides, most notably on rendering templates and using helpers for more information on including different views under one url.
A) To display all 3 representations in one view, we actually just need the basic model in the single route. The key is for the controller to give you flavors of that model. The other important thing is to use data binding in the handlebars template. You can see the running version here.
In the IndexRoute, add a model that gets the plain todos list:
Todos.TodosIndexRoute = Ember.Route.extend({
model: function(params) {
return Todos.Todo.find();
},
...
Make a TodosListView that doesn't need to have anything:
Todos.TodoListView = Ember.View.extend();
In the controller, add 2 computed properties that returns the desired arrays:
Todos.TodosController = Ember.ArrayController.extend({
...
todosActive: function() {
return this.filterProperty('isCompleted', false);
}.property('#each.isCompleted'),
todosCompleted: function() {
return this.filterProperty('isCompleted', true);
}.property('#each.isCompleted'),
...
Finally, in the HTML template:
<script type="text/x-handlebars" data-template-name="todos">
Active:
{{#view Todos.TodoListView lalaBinding="todosActive"}}
{{#each view.lala}}
{{title}},
{{/each}}
{{/view}}
Completed:
{{#view Todos.TodoListView dataBinding="todosCompleted"}}
{{#each view.data}}
{{title}},
{{/each}}
{{/view}}
</script>
note the dataBinding.
Thanks to the folks on #emberjs irc for helping out.

Delaying sub-view rendering till associated model is fetched, when using computed properties?

I'm trying to make this ember app, where one controller depends on another like so:
App.BooksController = Ember.ArrayController.extend({
itemController: 'book'
});
App.BookController = Ember.ObjectController.extend({
needs: 'books',
formattedPrice: function(){
return "$"+this.get('price').toFixed(2);
}.property('price'),
imageUrl: function(){
var img = this.get('image');
return "http://localhost/book-store-restful-api/images/book_covers/"+img;
}.property('image')
});
And there is 2 routes: #/books and #/books/book_id
When I navigate to #/books I get a list with all the books, along with a link pointing to each individual book controller. When I click it, it works as expected some individual book information is shown. But if I would browse to 1 #/books/book_id (it renders inside books template) then the list of the books get rendered, but it looks like the subtemplate get rendered before the associated model is fetched, therefore when navigating directly, all the individual book properties are undefined. If I click on corresponding link after it is loaded, the data becomes available.
So in a nutshell, is there anyway to tell the BookController to say that it only renders the associated single book sub-view after the model data was fetched?

Generating forms and handling submit properly with Ember.js rc1

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.

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.