We have a page containing a table with 26 rows. Each row will contain either an <input> or <select> element, depending on the data we're binding to. When binding to elements that contain between 5-30 options, it takes a page about 5 seconds to render. If I remove the binding, the page renders in under a second.
Is there a known performance issue when binding to Ember.Select views? Or, could I be doing it incorrectly? I'm using Firefox 22. IE9 is about twice as slow. The CPU is not pegged during this time. I'm using ember 1.0rc6.
Template snippet:
{{#if pa.isPickList}}
{{view Ember.Select viewName="select" contentBinding="pa.options" selectionBinding="pa.selected"}}
{{else}}
{{input valueBinding="pa.selected"}}
{{/if}}
I worry that the async nature of how I'm fetching the model could be causing inefficiencies. Perhaps the binding and async events are interacting inefficiently.
Salesforce.com is the backend. From what little I know about promises, I'm wondering if I need to fetch the server data in a promise. I'm not sure how to do this.
Here's how I'm currently fetching the data in my Route:
App.IndexRoute = Ember.Route.extend({
model: function(params){
var otherController = this.controllerFor('selectedProducts');
var ar = Ember.A(); //create an array
var arg = '0067000000PNWrV';
Visualforce.remoting.Manager.invokeAction(
'{!$RemoteAction.ProductPricingLookupController.loadOpportunityProducts}',
arg,
function myHandler(result, event) {
console.info('got results!!! ' + result.length);
for(var i = 0; i < result.length; i++)
{
var p = result[i];
var sfProd = App.ProductResult.create({content: p});
ar.pushObject(sfProd);
}
},
{escape: false} //some of the names have ampersands!!
);
return ar;
}
}
Thanks in advance for helping a newbie learn the ways of javascript and Ember.
Update
Here is working example of this issue. I have 5 picklists each with 60 options. This take 2-3 seconds to render on my machine. I realize these are decently large numbers but hopefully not unreasonable. Increase the number of picklist elements or options and you can easily hit 5 seconds.
Also, moving my server-model-fetching to a promise did not affect performance.
Andrew
It's a little hard to guess at performance problems like this without looking at it in a profiler. You can try creating a profile in Chrome dev tools to see what method is taking the most amount of time. Or create a jsbin which has the same issue.
One potential issue is that the array that you bind to is being built at the same time when the bindings are connected. This shouldn't be an issue with rc.6. What version of Ember are you on?
Regards to promises, your model hook should return a promise that wraps your async call, like so.
model: function(params) {
var promise = Ember.Deferred.create();
var myHandler = function(result, event) {
var model = Ember.A();
// populate the model
promise.resolve(model)
}
var arg = '0067000000PNWrV';
Visualforce.remoting.Manager.invokeAction(..., myHandler);
return promise;
}
If the bindings were firing too early/often, loading the model in a promise like this would help.
Finally try setting Ember.STRUCTURED_PROFILE to true. This will show you exactly how long the templates are taking to render.
Edit: After the the jsfiddle
I dug into this a little more. This is a known issue with Ember.Select. The default implementation creates SelectOption views for each option inside the select to allow databinding of the option items itself. Creating those many views is what takes the time.
However the typical usage will rarely need binding to the option items only to the whole list itself. And this appears to be the common way to bridge the performance gap.
I found a gist that uses option tags instead of SelectOption views.
Here's your updated jsfiddle. I upped the lists to 10 with 100 items each. The list renders in about 150ms for me now.
Related
I'm working on a module in a CMS' backend. I'm trying to 'hook in' to their knockout bindings and run code when they are finished rendering. So far I've had no luck.
I have however, attached to the different data-components and obtained knockout data.
I've had many failed attempts, but so far, I have this which is returning a binding context.
var bindingContext = ko.contextFor(jQuery('div[data-component="customer_form.areas"]').get(0));
Does anyone know of a way I can use this to somehow attach an observer to watch for the rendering to finish? I'll admin, I'm new to knockout. I'm not creating the view models, nor the templates. I can't add the afterRender to the template like I think should be done.
Like you said, this should be done using afterRender. All other methods feel hacky, because you'll never know when knockout will re-render (parts of) the ui.
I can't think of a reason why you'd need such a work around, but who am I to judge..
The only approach I can think off, is to use the MutationObserver. Here's an example:
var bindingContextElement = document.querySelector("ul");
var renderThrottle = 300;
var renderCycle = null;
var onRenderComplete = function() {
var pre = document.createElement("pre");
var msg = new Date().toLocaleString() + ": finished rendering";
pre.appendChild(document.createTextNode(msg));
document.body.appendChild(pre);
}
// Observe mutations to element, call onRenderComplete after 300ms of no mutations
var observer = new MutationObserver(function(mutations) {
clearTimeout(renderCycle);
renderCycle = setTimeout(onRenderComplete, renderThrottle);
});
var config = {
childList: true
};
observer.observe(bindingContextElement, config);
ko.applyBindings({
items: ko.observableArray([1, 2, 3])
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<ul data-bind="foreach: items">
<li data-bind="text: $data"></li>
</ul>
<button data-bind="click: function() { items.push(items().length + 1)}">add</button>
This code listens to any mutations in the <ul> element, which is controlled by knockout. Once changes start happening to the element or its children, it attempts to log a "Rendered" message. It's only allowed to log this message if there are no further changes for 300ms.
Have a look at the docs to determine your config object and which elements to watch... And please keep in mind that things might get out of hand if stuff gets more complicated than this example...
I have the following controller
var myApp = angular.module("MyApp", ["firebase"]);
function MyController($scope, $firebase) {
var rootRef = new Firebase("vivid-fire-447.firebaseio.com/products");
// Automatically syncs everywhere in realtime
$scope.products = $firebase(rootRef);
$scope.products.$on('loaded', function(){
var index = $scope.products.$getIndex();
$scope.total = function(){
var total = 0;
index.forEach(function(i){
total += $scope.products.$child(i).price;
});
return total;
};
});
}
and the following html
<div ng-app="MyApp">
<div ng-controller="MyController">
<ul>
<li ng-repeat="product in products | orderByPriority">{{product.code}}</li>
</ul>
<div>Total = {{total()}}</div>
</div>
</div>
The problem is that when I show the total price of products Chrome cpu gets pegged at 100% and memory usage starts climbing rapidly and eventually the tab hangs.
I noted using AngularJS Batarang that total method gets called continuosly?
How can I get the total price of all products in an efficient way?
JsFiddle
I tried your example and it ran without issue; it loaded in less than 1 second. I don't think your code/data shown above accurately captured the problem. However, I did note a couple potential issues you would start with.
The definition for $scope.getTotal is inside the loaded event for $scope.products which means that function may exist the first time Angular tries to compile this page. You can correct that by giving it an empty array initially, and moving it out of the loaded callback:
$scope.products = $firebase(rootRef);
var index = [];
$scope.products.$on('loaded', function(){
index = $scope.products.$getIndex();
});
$scope.total = function(){ /*...*/ }
The code creates a new synchronized connection to Firebase for each record by calling $child. There is no need for this since all the data is already present in the parent object. In general, you don't need to use $child as it's for some specialized use cases:
total += $scope.products[key].price; // far more efficient!
Here's a fiddle demonstrating the changes. On my box it loads in less than half a second, even with the overhead of fiddle's containing code.
http://jsfiddle.net/katowulf/Q6VPx/
I'm having some trouble integrating the appear-plugin for jQuery with my Knockout viewmodel. My first attempt only involved a single appear-item and a single global action but now I have multiple number of items to appear on and multiple action to trigger. I was thinking of making a binding-handler for this purpuse which could be used like this:
<div class="loadMoreTrigger" data-bind="appear: loadMore" data-appear-top-offset="200" />
My skills with custom bindinghandlers is very limited, and maybee there is existing code for this already? Haven't found any...
Would this be possible at all? And the right way of doing it? (I'm trying to lazy load items)
This is what I have now:
ko.bindingHandlers.appear = {
init: function (element, valueAccessor) {
$(element).appear();
var action = valueAccessor();
$(element).on("appear", action);
}
};
But it doesn't work...
How to prevent jQuery $('body').load('something.php'); from changing any DOM till all the content from something.php (including images,js) is fully loaded
-Lets say some actual content is:
Hello world
And something.php content is:
image that loads for 10 seconds
20 js plugins
After firing .load() function nothing should happen, till images an js files are fully loaded, and THEN instantly change the content.
some preloader may appear, but its not subject of question.
[edit]----------------------------------------------------------------------
My solution for that was css code (css is loaded always before dom is build) that has cross-browser opacity 0.
.transparent{
-moz-opacity: 0.00;
opacity: 0.00;
-ms-filter:"progid:DXImageTransform.Microsoft.Alpha"(Opacity=0);
filter: progid:DXImageTransform.Microsoft.Alpha(opacity=0);
filter:alpha(opacity=0);
}
And it prevent from bad flickr of content usually but not always. Now its possible to detect somehow that content is loaded by jQuery ajax and apply some show timeout and so on. But in fact question is still open.
To make now a little more simple example for that question:
How to begin changing DOM after $('body').load('something.php') with 3000ms delay after clicking the link that fire .load('something.php') function? (Browser should start downloading instantly, but DOM changing has to be initiated later)
Use .get instead and assign the contents in the success callback:
$.get('something.php', function(result) {
$('body').html(result);
});
There are some implementation details you may have to solve yourself, but here's a rough solution:
Don't use .load() directly. It can't be changed to wait for all images to load.
Use $.get() to fetch the HTML into a variable, let's call it frag.
Use $(frag).find('img').each(fn) to find all images and dump each this.src inside a preloader.
var images = [],
$frag = $(frag),
loaded = 0;
function imageLoaded()
{
++loaded;
// reference images array here to keep it alive
if (images.ready && loaded >= images.length) {
// add $frag to the DOM
$frag.appendTo('#container');
}
}
$frag.find('img').each(function() {
var i = new Image();
i.onload = i.onerror = imageLoaded;
i.src = this.src;
images[images.length] = i;
});
// signal that images contains all image objects that we wish to monitor
images.ready = true;
Demo
Once all images are loaded, append the earlier frag to the DOM using $frag.appendTo('#container').
Here is a quick proof of concept that loads relevant images before inserting an HTML fragment into the DOM: http://jsfiddle.net/B8B6u/5/
You can preload the images using the onload handler to trigger iterations:
var images = $(frag).find('img'),
loader = $('<img/>');
function iterate(i, callback) {
if (i > 0) {
i--;
loader.unbind("load");
loader.load(function() {
iterate(i, callback);
});
loader.attr('src', images[i].src);
}else{
callback();
}
}
iterate(images.length,function(){
$('#container').html(frag);
});
This should work, since each image is loaded after the previous one has finished loading.
Have you tried this?
$(function(){$('body').load('something.php')});
Edit: I just realized you are actually wanting to wait for the stuff to load before it get's placed in the body.
Here are three links to similar questions.
Preloading images with jQuery
Is it possible to preload page contents with ajax/jquery technique?
Preloading images using PHP and jQuery - Comma seperated array?
You can probably adapt those to scripts too.
This might work too.
$.ajax({
'url': 'content.php',
'dataType': 'text',
'success': function(data){
var docfrag = document.createDocumentFragment();
var tmp = document.createElement('div'), child;
//get str from data here like: str data.str
tmp.innerHTML = str;
while(child = tmp.firstChild){
docfrag.appendChild(child);
}
$('body').append(docfrag);
}
});
It's a longer way of doing what Shadow Wizard suggests, but it will probably work.
Hm. Never mind. Jack's answer looks the best. I'll wait a while and if no one likes my answer I'll delete it.
Edit: It looks like appending to documentfragments can do http requests.
Any script using createDocumentFrament may benefit from preloading.
In this question they want no http requests even though that's what createDocumentFragment is doing:
Using documentFragment to parse HTML without sending HTTP requests.
I can't be sure if this is true for all browsers or just when the console.log is run, but it could be a good option for preloading if this behavior is universal.
How do you keep track of your UI elements in Titanium? Say you have a window with a TableView that has some Switches (on/off) in it and you'd like to reference the changed switch onchange with a generic event listener. There's the property event.source, but you still don't really know what field of a form was just toggled, you just have a reference to the element. Is there a way to give the element an ID, as you would with a radiobutton in JavaScript?
Up to now, registered each form UI element in a dictionary, and saved all the values at once, looping through the dictionary and getting each object value. But now I'd like to do this onchange, and I can't find any other way to do it than create a specific callback function for each element (which I'd really rather not).
just assign and id to the element... all of these other solution CAN work, but they seem to be over kill for what you are asking for.
// create switch with id
var switcher0 = Ti.Ui.createSwitch({id:"switch1"});
then inside your event listener
myform.addEventListener('click', function(e){
var obj = e.source;
if ( obj.id == "switch1" ) {
// do some magic!!
}
});
A simple solution is to use a framework that helps you keep track of all your elements, which speeds up development quite a bit, as the project and app grows. I've built a framework of my own called Adamantium.js, which lets you use a syntax like jQuery to deal with your elements, based on ID and type selectors. In a coming release, it will also support for something like classes, that can be arbitrarily added or removed from an element, tracking of master/slave relationships and basic filter methods, to help you narrow your query. Most methods are chainable, so building apps with rich interaction is quick and simple.
A quick demo:
// Type selector, selects all switches
$(':Switch')
// Bind a callback to the change event on all switches
// This callback is also inherited by all new switch elements
$(':Switch').bind('change', function (e) {
alert(e.type + ' fired on ' + e.source.id + ', value = ' + e.value);
});
// Select by ID and trigger an event
$('#MyCustomSwitch').trigger('change', {
foo: 'bar'
});
Then there's a lot of other cool methods in the framework, that are all designed to speed up development and modeled after the familiar ways of jQuery, more about that in the original blog post.
I completely understand not wanting to write a listener to each one because that is very time consuming. I had the same problem that you did and solved it like so.
var switches = [];
function createSwitch(i) {
switches[i] = Ti.UI.createSwitch();
switches[i].addEventListener('change', function(e) {
Ti.API.info('switch '+i+' = '+e.value);
});
return switches[i];
}
for(i=0;i<rows.length;i++) {
row = Ti.UI.createTableViewRow();
row.add(createSwitch(i));
}
However keep in mind that this solution may not fit your needs as it did mine. For me it was good because each time I created a switch it added a listener to it dynamically then I could simply get the e.source.parent of the switch to interact with whatever I needed.
module Id just for the hold it's ID. When we have use id the call any another space just use . and use easily.
Try This
var but1 = Ti.Ui.createButton({title : 'Button', id:"1"});
window.addEventListener('click', function(e){
var obj = e.source;
if ( obj.id == "1" ) {
// do some magic!!
}
});
window.add(but1);
I, think this is supported for you.
how do you create your tableview and your switcher? usually i would define a eventListener function while creating the switcher.
// first switch
var switcher0 = Ti.Ui.createSwitch();
switch0.addEventListener('change',function(e){});
myTableViewRow.add(switch0);
myTableView.add(myTableViewRow);
// second switch
var switch1 = ..
so no generic event listener is needed.