I have an select box which contains list of 350-400 <option> element. The element data is loaded via ajax. Which loads fine.
But when I ember tries to create the list. The browser hangs with high RAM and CPU uses finally unresponsive script error is shown in browser. Given enough time it renders.
It also seems that ember doesn't really caches the rendered element because every time I comeback to the same page the problem repeats.
So, Is there any way I can improve the performance of the view select?
The sample code for the select view:
{{view "select" prompt="-- select --" content=controllers.application.companies
optionValuePath="content.id" optionLabelPath="content.name_abbr"
classNames="form-control" value=dailyId}}
I know one of the alternatives will be using a typeahead script using a text box. But I want to save it as a plan B for now.
Warning:
This solution is due to be deprecated as of Ember 2.0 according to this blog entry:
REMOVALS
....
Manually rendering a string into the buffer in a view
I don't think I have an answer for you, and I didn't come back to this 'cause I couldn't find the full implementation I've made a while back. It was essentially a copy of the current select component, except, like I said, I've implemented the render method. Something like this:
App.CustomSelectComponent = Em.Component.extend({
tagName: 'select',
optionValuePath: '',
optionLabelPath: '',
content: [],
render: function(buffer) {
var selfie = this,
options = this.get('content'),
idPath = this.get('optionValuePath'),
valPath = this.get('optionLabelPath');
options.forEach(function(option) {
buffer.push('<option value=\'' + option[idPath.replace(/^content\.?/, '')] + '\'>');
buffer.push(option[valPath.replace(/^content\.?/, '')]);
buffer.push('</option>');
});
}
});
This is NOT the implementation I had before, and if you spend a few seconds looking at it you will see some problems with this solution, however, the render implementation is what you might want to look into.
With the implementation as is, I can get a better performance already. On the jsbin I've added a small set of records for both Ember.Select and the sample custom-select:
But again, this implementation is lacking several features and is probably not enough to answer this question. I just did it a few minutes ago to sort of show were you'd start implementing the render and how, within your component.
Sorry if this isn't the answer you wanted.
Related
I seem to often end up in a situation where I am rendering a view, but the Model on which that view depends is not yet loaded. Most often, I have just the model's ID taken from the URL, e.g. for a hypothetical market application, a user lands on the app with that URL:
http://example.org/#/products/product0
In my ProductView, I create a ProductModel and set its id, product0 and then I fetch(). I render once with placeholders, and when the fetch completes, I re-render. But I'm hoping there's a better way.
Waiting for the model to load before rendering anything feels unresponsive. Re-rendering causes flickering, and adding "loading... please wait" or spinners everywhere makes the view templates very complicated (esp. if the model fetch fails because the model doesn't exist, or the user isn't authorized to view the page).
So, what is the proper way to render a view when you don't yet have the model?
Do I need to step away
from hashtag-views and use pushState? Can the server give me a push? I'm all ears.
Loading from an already-loaded page:
I feel there's more you can do when there's already a page loaded as opposed to landing straight on the Product page.
If the app renders a link to a Product page, say by rendering a ProductOrder collection, is there something more that can be done?
<ul id="product-order-list">
<li>Ordered 5 days ago. Product 0 (see details)</li>
<li>Ordered 1 month ago. Product 1 (see details)</li>
</ul>
My natural way to handle this link-to-details-page pattern is to define a route which does something along these lines:
routes: {
'products/:productid': 'showProduct'
...
}
showProduct: function (productid) {
var model = new Product({_id: productid});
var view = new ProductView({model: model});
//just jam it in there -- for brevity
$("#main").html(view.render().el);
}
I tend to then call fetch() inside the view's initialize function, and call this.render() from an this.listenTo('change', ...) event listener. This leads to complicated render() cases, and objects appearing and disappearing from view. For instance, my view template for a Product might reserve some screen real-estate for user comments, but if and only if comments are present/enabled on the product -- and that is generally not known before the model is completely fetched from the server.
Now, where/when is it best to do the fetch?
If I load the model before the page transition, it leads to straightforward view code, but introduces delays perceptible to the user. The user would click on an item in the list, and would have to wait (without the page changing) for the model to be returned. Response times are important, and I haven't done a usability study on this, but I think users are used to see pages change immediately as soon as they click a link.
If I load the model inside the ProductView's initialize, with this.model.fetch() and listen for model events, I am forced to render twice, -- once before with empty placeholders (because otherwise you have to stare at a white page), and once after. If an error occurs during loading, then I have to wipe the view (which appears flickery/glitchy) and show some error.
Is there another option I am not seeing, perhaps involving a transitional loading page that can be reused between views? Or is good practice to always make the first call to render() display some spinners/loading indicators?
Edit: Loading via collection.fetch()
One may suggest that because the items are already part of the collection listed (the collection used to render the list of links), they could be fetched before the link is clicked, with collection.fetch(). If the collection was indeed a collection of Product, then it would be easy to render the product view.
The Collection used to generate the list may not be a ProductCollection however. It may be a ProductOrderCollection or something else that simply has a reference to a product id (or some sufficient amount of product information to render a link to it).
Fetching all Product via a collection.fetch() may also be prohibitive if the Product model is big, esp. in the off-chance that one of the product links gets clicked.
The chicken or the egg? The collection.fetch() approach also doesn't really solve the problem for users that navigate directly to a product page... in this case we still need to render a ProductView page that requires a Product model to be fetched from just an id (or whatever's in the product page URL).
Alright, so in my opinion there's a lot of ways that you can fix this. I'll list all that I've thought of and hopefully one will work with you or at the very minimum it will inspire you to find your optimal solution.
I'm not entirely opposed to T J's answer. If you just go ahead and do a collection.fetch() on all the products when the website is loading (users generally expect there to be some load time involved) then you have all of your data and you can just pass that data round like he mentioned. The only difference between what he's suggesting and what I normally do is that I usually have a reference to app in all my views. So, for example in my initialize function in app.js I'll do something like this.
initialize: function() {
var options = {app: this}
var views = {
productsView: new ProductsView(options)
};
this.collections = {
products: new Products()
}
// This session model is just a sandbox object that I use
// to store information about the user's session. I would
// typically store things like currentlySelectedProductId
// or lastViewedProduct or things like that. Then, I just
// hang it off the app for ease of access.
this.models = {
session: new Session()
}
}
Then in my productsView.js initialize function I would do this:
initialize: function(options) {
this.app = options.app;
this.views = {
headerView: new HeaderView(options),
productsListView: new ProductsListView(options),
footerView: new FooterView(options)
};
}
The subviews that I create in the initialize in productsView.js are arbitrary. I was mostly just trying to demonstrate that I continue to pass that options object to subviews of views as well.
What this does is allows every view, whether it be a top level view or deeply nested subview, every view knows about every other view, and every single view has reference to the application data.
These two code samples also introduce the concept of scoping your functionality as precise as you possibly can. Don't try to have a view that does everything. Pass functionality off to other views so that each view has one specific purpose. This will promote reuse of views as well. Especially complex modals.
Now to get back to the actual topic at hand. If you were going to go ahead and load all of the products up front where should you fetch them? Because like you said you don't want a blank page just sitting there in front of your user. So, my advice would be to trick them. Load as much of your page as you possibly can and only block the part that needs the data from loading. That way to the user the page looks like it's loading while you're actually doing work behind the scenes. If you can trick the user into thinking the page is steadily loading then they are much less likely to get impatient with the page load.
So, referencing the initialize from productsView.js, you could go ahead and let the headerView and footerView render. Then, you could do your fetch in the render of the productsListView.
Now, I know what you're thinking. Have I lost my mind. If you do a fetch in the render function then there's no way that the call will have time to return before we hit the line that actually renders the productsViewList template. Well, luckily there's a couple of ways around that. One way would be to use Promises. However, the way I typically do it is to just use the render function as its own callback. Let me show you.
render: function(everythingLoaded) {
var _this = this;
if(!!everythingLoaded) {
this.$el.html(_.template(this.template(this)));
}
else {
// load a spinner template here if you want a spinner
this.app.collection.products.fetch()
.done(function(data) {
_this.render(true);
})
.fail(function(data) {
console.warn('Error: ' + data.status);
});
}
return this;
}
Now, by structuring our render this way the actual template won't load until the data has fully loaded.
While we have a render function here I want to introduce another concept that I use every where. I call it postRender. This is a function where I execute any code that depends on DOM elements being in place once the template has finished loading. If you were just coding a plain .html page then this is code that traditionally goes in the $(document).ready(function() {});. It may be worth noting that I don't use .html files for my templates. I use embedded javascript files (.ejs). Continuing on, the postRender function is a function that I have basically added to my boiler plate code. So, any time I call render for a view in the code base, I immediately chain postRender onto it. I also use postRender as a call back for itself like I did with the render. So, essentially the previous code example would look something like this in my code base.
render: function(everythingLoaded) {
var _this = this;
if(!!everythingLoaded) {
this.$el.html(_.template(this.template(this)));
}
else {
// load a spinner template here if you want a spinner
this.app.collection.products.fetch()
.done(function(data) {
_this.render(true).postRender(true);
})
.fail(function(data) {
console.warn('Error: ' + data.status);
});
}
return this;
},
postRender: function(everythingLoaded) {
if(!!everythingLoaded) {
// do any DOM manipulation to the products list after
// it's loaded and rendered
}
else {
// put code to start spinner
}
return this;
}
By chaining these functions like this we guarantee that they'll run sequentially.
=========================================================================
So, that's one way to tackle the problem. However, you mentioned that you don't want to necessarily load all of the products up front for fear that the request could take too long.
Side Note: You should really consider taking out any information related to the products call that could cause the call to take a considerable amount of time, and make the larger pieces of information a separate request. I have a feeling that users will be more forgiving about data taking a while to load if you can get them the core information really fast and if the thumbnails related to each product takes a little longer to load it shouldn't be then end of the world. That's just my opinion.
The other way to solve this problem is if you just want to go to a specific product page then just implement the render/postRender pattern that I outlined above on the individual productView. However note that your productView.js will probably have to look something like this:
initialize: function(options) {
this.app = options.app;
this.productId = options.productId;
this.views = {
headerView: new HeaderView(options),
productsListView: new ProductsListView(options),
footerView: new FooterView(options)
};
}
render: function(everythingLoaded) {
var _this = this;
if(!!everythingLoaded) {
this.$el.html(_.template(this.template(this)));
}
else {
// load a spinner template here if you want a spinner
this.app.collection.products.get(this.productId).fetch()
.done(function(data) {
_this.render(true).postRender(true);
})
.fail(function(data) {
console.warn('Error: ' + data.status);
});
}
return this;
},
postRender: function(everythingLoaded) {
if(!!everythingLoaded) {
// do any DOM manipulation to the product after it's
// loaded and rendered
}
else {
// put code to start spinner
}
return this;
}
The only difference here is that the productId was passed along in the options object to the initialize and then that's pulled out and used in the .fetch in the render function.
=========================================================================
In conclusion, I hope this helps. I'm not sure I've answered all of your questions, but I think I made a pretty good pass at them. For the sake of this getting too long I'm going to stop here for now and let you digest this and ask any questions that you have. I imagine I'll probably have to do at least 1 update to this post to further flush it out.
You started saying:
I have a listing of items in one Collection view
So what does a collection have..? Models..!
When you do collection.fetch() you retrieve all the models.
When the user selects an item, just pass the corresponding model to the item view, something like:
this.currentView = new ItemView({
model: this.collection.find(id); // where this points to collection view
// and id is the id of clicked model
});
This way, there there won't be any delay/ improper rendering.
What if your collections end point returns huge volume of data..?
Then implement common practices like pagination, lazy loading etc.
I construct a Product model with the given ID
To me that sounds wrong. If you have a collection of products, you shouldn't be constructing such models manually.
Have the collection fetch your models before rendering the list view. This way all the problem you mentioned can be avoided.
It may be a very dumb question... I am using Meteor-ui-progress-circle and I want redrawing the template when the percentage (wich is store in a reactive collection Progress) is changed (currently, when I click on a "play" button).
I think I have to use Blaze.render but I don't really understand how it work.
Here a part of my main template (in Jade) :
div.panel-body
div.col-md-9.col-sm-8
p Lorem ipsum...
div.col-md-3.col-sm-4#progress-circle
+progressCircle progress="0" radius="100" class="green"
And my JavaScript :
Template.controlBar.events(
{
"click .play-button": function ()
{
var tmp = Progress.findOne({});
if (!tmp)
{
Meteor.call('createProgress');
tmp = Progress.findOne({});
}
var val = tmp.progressValue;
val += 10;
if (val > 100)
return;
Meteor.call('updateProgess', tmp._id, val);
Template.progressCircle.progress = tmp.progressValue;
Blaze.render(Template.progressCircle, $("#progress-circle")[0]);
},
Doing this... I have several template that are displaying each time I click on the play button. I don't understand how to specify that I don't want a new template but just re-render the one I already have.
Not sure I quite understand your question, but I'll try to help by giving my best understanding of templating and how I have come to use them. If someone sees any incorrect information here, please speak up so I can get a better understanding myself and correct this answer.
First, the Template.XXX.events handlers. In your event handler, you are using a function with no arguments. You can actually accept 2 arguments for these event handler functions: the event and the template. So, you can do something like thus:
Template.controlBar.events({
'click .play_button': function(event, tmpl) {
tmpl.$('div#progress-circle').doSomething();
}
});
Notice the tmpl.$() call? That says to use jQuery to find the specified selector, but ONLY in the current template. This is a wonderful way to use classes to generalize your components, but then be able to filter the selection to only those within the same template...
...Which brings me to my next bit of advice: Use child templates excessively. Any component that I can identify as an "autonomous component" on my page I will consider as a separate template. For instance, I was recently working on a custom reporting page that had a table and some D3 graphs representing some real-time data. In this report page, I had one main template defined for the "page", then each of the D3 graphs where defined as a separate template, and the table was another separate template. This allows several advantages:
Compartmentalization of the "components" of the page, allowing code reuse (I can now put the same graph on ANY page, since it's now an autonomous "component"
The advantage of using the Template.XXX.events trick above to "narrow" the scope of my element searches to elements within that template
Prevents total page refreshes as Meteor is smart enough to only refresh templates that need to be refreshed, which also speeds the responsiveness of the page itself
As a result, I try to apply my Templates liberally. In your case, it would sound to me that if I were to have multiply progress bars on the page that I might turn those into separate templates. I might even do it if I had a single progress bar if it made sense to separate it out for ease of data handling.
Finally, inter-communications between Templates. This can be tricky at times, but the best, most efficient way to do this I have found is through Session variables. The pattern I typically use is to have my data for my template be returned by a Template .helper, which does something like this:
Template.controlBar.helpers({
progressData: function() {
if (!Session.equals('playId', null)) {
return Progress.findOne({_play_id: Session.get('playId')});
}
}
});
Because Helpers are reactive, and Sessions is reactive, the template is re-rendered anytime the 'playId' is altered in the Session. The corresponding Session variable can be set from anywhere in the client code. Again, this tends to work best when you narrow the scope of your templates to the individual components. It is important to note here that the Session object in Meteor is NOT the same as "sessions" in other languages like Java and such, which typically use cookies and a session token/id. Meteor sessions work considerably different, and do not survive page reloads or closing of browsers.
This is fairly complex, so please bear with me.
Let me first say that I am very new to MVC. My experience in even the basics are minimal, but my overall development experience with .NET/C# is extensive. Point being, the solution may be simple and I just do not know it lol.
Now.. We have a massive app that uses "EditorTemplates" (Partial views) via #Html.EditorFor() rendering in order to render customized UI elements based on database content as needed.
Now... I was tasked with creating one of these elements, and due to the complexity of our system and how long it takes to load it just to test, I decided to first develop my UI/Logic in its own standalone view/controller.
The view consisted of 3 cascading Kendo dropdownlists which pull data from a method in the controller (which returns a list of class objects).
I got this working beautifully in a standalone view/controller.
Now... I ported the HTML and logic from the controller in to the partial view files in our "EditorTemplates" folder, and ran our app for the first time.
Immediately I get a javascript error
"filterRabu2" is not defined.
Here is the associated code:
<label for="rabu2">Rabu2:</label>
#(Html.Kendo().DropDownList()
.Name("rabu2")
.HtmlAttributes(new { style = "width:300px" })
.OptionLabel("Select ...")
.DataTextField("Name")
.DataValueField("ID")
.DataSource(source =>
{
source.Read(read =>
{
read.Action("GetRabu2", "Datasets")
.Data("filterRabu2");
})
.ServerFiltering(true);
})
//.Enable(false)
.AutoBind(false)
.CascadeFrom("rabu1")
)
<script>
function filterRabu2() {
return {
rabu1: $("#rabu1").val()
};
}
</script>
Now... I got rid of that error by moving that function to a .js file that was already being included in the page.
But... when the dropdownlists render, the first one (which should be enabled and with data present), it appears to be disabled and is not clickable. I confirmed that our data access routine is being hit and returning a valid and populated list of data, just as it did (working) in the standalone view.
That's where I'm at... I can't get the DDLs to function, and the existence of that JS error leads me to theorize that JS is not being rendered or allowed to execute properly? I have no idea.
This is being rendered via the #Html.EditorFor() method from another partial view inside a loop which renders templates for data based on that data... it's quite complex... but I could really use a hand at figuring out whats going on.
It could be that the dom is rendering before the the javascript has executed. Try wapping the calling method up in a
$(document).ready(
function(){
//initial method call here
});
This will ensure that the javascript executes after the dom has loaded.
I have a long running template helper. It relies on three separate collections and performs a lot of looping to pivot some data for daily reports. The users are okay with it being long running, but I need to give them feedback that the client is busy calculating what will be rendered to the UI. The problem for me is that using the waitOn hook only gets me part way there and the rendered callback doesn't work unless I am adding a new row to the template (which is almost never). In fact, I wonder if the Meteor team realize this. It seems like a feature that would be nice to have. I have a table with a the same number of rows and columns, but the values in the cells change. How can I show the users some feedback while the JS to calculate those cells runs?
The Meteor way would be to use a reactive variable:
HTML
<template name="busy">
{{#if processing}}
spinner
{{else}}
Done, showing results: ...
{{/if}}
</template>
JS
var data = new ReactiveDict();
Template.busy.rendered = function() {
data.set('processing', true);
};
Template.busy.processing = function() {
return data.get('processing');
};
var processing = function() {
...
// Looooong calculations
...
// Or even async
...
data.set('processing', false);
};
I have had a similar issue. I had to show an indication when a table is updated. I observed the query (check Collection.observeChanges) and ran a jQuery-powered flash on the row.
In your case, it seems like you are doing the compuation on your own and supply it to the Template as a variable. Put a spinner in the original template and at the end of your computation hide it with jQuery.
OK, so the key was twofold. I needed to purposely delay the "Loading" feedback I was providing. I presume that is so that it was called after the updating elements were actually finished rendering. I used the underscore delay function like so:
_.delay removeUpdating, 500
removeUpdating just removes a class on each table cell 'updating'.
And then at the beginning of this same helper method I add those classes to all the cells.
The delay coupled with a nice CSS3 background animation gives the user a nice cue that the fields are updating when they navigate!
I'd prefer not to be adding a delay manually, nor using jQuery to add and remove classes, so any better suggestions are appreciated!
I'm trying to figure out if Backbone.js is the right framework for my current project: a visualization app.
I have a number of questions:
1) State / Routing?
As this is not your typical RESTful app, but rather a visualization application with various chart types and settings for these charts, how do i maintain state in the URL?
Let's say my areaChart model has a number of defaults like this:
AreaChartModel = Backbone.Model.extend({
defaults: {
selectedCountries: [],
year: 1970,
stacked: false
},
initialize: function(){
[...]
}
});
On an update to the model I'd like to serialize some of these attributes so that I can bookmark the specific state: chartApp.html#!year=1970&stacked=false etc.
And vice-versa, when initing the app with this state, how do I "deparam" the url state and set the model? Can I use Backbone's intrinsic routing?
2) Controller and coupling?
It seems as Backbone has a pretty tight view-model coupling?
Is this really how I should bind for example my areaChartView to the model?
AreaChartView = Backbone.View.extend({
initialize: function(){
areaChartModel.bind("change:year", this.render);
}
});
Isn't this normally the role of the controller?
3) Continuation: Model vs. Controller?
Given this scenario:
A change in the "Sidebar" should trigger a sequence of functions:
1) "New data for the current selection should be loaded"
2) "Based on this data, the scales in the Visualization view should be updated"
3) "The visualization view should be rendered"
Where should I place these functions and how can I create an event in the model that I trigger when the state is stable? (i.e. when all the functions have been invoked and it's time to set the view states?)
1) I would use Backbone.js native routing as much as possible using “:params” and “*splats” , read more. You could fit all your queries into the Backbone.js routing but I would personally sacrifice certain things in favor of intuitive UI buttons
e.g. I would have the default as a line bar and you can't preset this with the URL but to change to a stacked graph would be a simple click of a button.
I would probably stray from ever using ? and & in my URL's. I might come back to this point later as it is interesting.
2) Your example is fine and you just need to remember Backbone.js MVC terminology doesn't correlate to traditional MVC.
Backbone Views are essentially the Controller in traditional MVC.
Backbone Controllers are simply a way of routing inside a framework.
The templating engine you use with Backbone.js is the traditional MVC view.
3) Still writing
Regarding question #3, I would create a Model and a View for the slider.
Then I would associate the triggering of the change event on the model to some function in the view that updates the graph's view (like changing the scales). Something like:
var Slider = Backbone.Model.extend({})
var SliderView = Backbone.View.extend({
initialize: function() {
this.model.bind('change', this.render);
}
render: function() {
// load data, change scales, etc.
}
});
var slider = new Slider();
var slider_view = new SliderView({ model: slider });
Maybe a good idea would be to put the bindings in a parent view, that would then dispatch to sub-views, coordinating their work.
Do sit down for a while and consider if maintaining the entire state is at all a good idea ? The key motivations for having url-based state management is being able to support browser based navigation buttons and being able to bookmark a page. In a visualization app, your data would probably change every moment. This is not something you want to persist in your app-url. Do you really want that when a user bookmarks your app and comes back to it three days later - he sees the visualization for three days old data ? For your scenario, assuming I have not misunderstood your requirements, I would recommend to keep the data state in your model itself.
Also regarding synchronization of views with model data, Yes you can code all the binding logic on your own. In that case your View class will take care of setting up the bindings on the first render. And upon subsequent calls to render, which can be invoked in response to any change event in the model, will refresh the DOM/canvas where the visualization is present.
Probably you should be look forward to a plugin for data-synchronization that takes care of much of boilerplate for you. This page lists some of the data-binding extensions available. Orchestrator is another solution that I have been working on, which might be helpful in this regard.