Jquery.tmpl with WebSql Result set - javascript

I am using JQM and building a large lists of contacts from a webSQL database. Currently the process is painfully slow so I am trying to use a template to see how this affects performance.
I cannot figure out how to use a Jquery template with a WebSQL ResulSet.
This is the code so far:
function (tx, result)
{
var markup = '<li>${result.Name}</li>';
$.template("contactTemplate", markup);
$.tmpl("contactTemplate", /* What goes here? */).appendTo($list);
}

Ok, Jquery template expects an array. Therefore the following works:
var contacts = [];
for (i = 0; i < result.rows.length; i++)
{
contacts.push(result.rows.item(i));
}
var markup = '<li>${Name}</li>';
$.template("contactTemplate", markup);
$.tmpl("contactTemplate", contacts).appendTo($list);
This approach has very little impact on performance. Building the list is equally slow.

Related

Framework7 virtual list searchAll function not being used

I've got a PhoneGap application in development where I'm trying to use framework7's search bar to filter out my virtual list of products.
Current functionality is that the list works fine, but the search bar only searches through the rendered elements rather than the whole virtual list.
I've gone through framework7's documentation on getting their virtual list and searchbars to work together, but as far as I can tell the searchbar in my code completely ignores the virtual lists searchAll function which I put in. I can have searchAll() return anything and it makes no difference to the current functionality.
var listObject = {
items: selectProd,
template: '<li class="item-content"><div class="item-inner"><div data-value="{{model_id}}" class="item-title list-title">{{internal_descriptn}}</div></div></li></script>',
searchAll: function (query, items) {
var foundItems = [];
for (var i = 0; i < items.length; i++) {
// Check if title contains query string
if (items[i].title.indexOf(query.trim()) >= 0) foundItems.push(i);
}
// Return array with indexes of matched items
return foundItems;
}
};
console.log(listObject);
var virtualList = myApp.virtualList('#product-list', listObject);
var mySearchbar = myApp.searchbar('.searchbar', {
searchList: '.list-block-search',
searchIn: '.list-title'
});
I feel like the only thing I could be missing is some way to put the virtualList into the searchbar as an attribute or similar to link them, it seems strange for me to expect them to just work together like magic. Yet that seems to be what the documentation suggests it does (not in my case apparently or it would work). Thanks for any help.
Solved it by looking at an example on their github. At first glance everything is the same, so I copy it over and slowly change it back to include my data to see where the problem occurs. For some godamn reason, you need to use the class virtual-list to identify the parent containing your list. Then specify that class for your virtual list and for your search bar. Using a different name instead won't work. Very frustrating that this isn't mentioned anywhere at all in documentation.

Get account ID from Google Analytics Tracking code on page

I am performing an audit using a custom web crawler of mine and was trying to garner the accountID for the legacy implementations of Google Analytics, but I cannot seem to get any of the JavaScript functions in _gaq to return the accountId in use. Does anybody know how to do this? All the documentation I've read really only mentions how to set variables, not how to get variables out once set for the purpose of auditing an implementation.
Thanks in advance
UPDATE
Thanks everyone! I wish I could check off all of your responses as good answers.
After a bit of testing, I have come up with the following function that handles pretty much every use case.
function getAccount() {
try {
if (_gaq) {
for (i = 0; i < _gaq.length; i++) {
if (_gaq[i][0] == "_setAccount") {
return _gaq[i][1]
}
}
}
if (_gat) {
if (_gat.fb) {
return _gat.fb
}
}
if (ga) {
return ga.getAll()[0].a.data.values[':trackingId']
}
} catch (e) { }
return ""
}
Joshua, this isn't a standard feature / get function that would be available.
You can however manually access ga object created by the tracking library.
If you for example open console for this webpage and type in:
ga.getAll()[0].a.data.values[':trackingId']
You will receive UA-5620270-1 which is probably the main Analytics Account for Stack Overflow. Similar approach should work in your case as well - and also any other attribute that's accessible:
Screen: http://fii.cz/sbdqevk
If you are prepared to accept a less than elegant solution you could use
function getAccount(_gaq) {
for (i = 0; i < _gaq.length; i++) {
if (_gaq[i][0] == "_setAccount") {
return _gaq[i][1];
}
}
}
_gaq is an array of arrays, the function simply loops through until it finds a subarray where the first element is _setAccount and returns the second element, which is the account number (_gaq.push(['_setAccount', 'UA-XXXXX-X']);).
As you are talking of legacy implementations, there are even older versions of the code so you might need more checks. The oldest version I could find on an active page was:
<script type="text/javascript">
_uacct = "UA-XXXXXXXX-X";
urchinTracker();
</script>
It's quite easy to get the Account Id from there. There is also the synchronous version of the code (sorry, can't find an example right now).
And for the current version look at Petrs answer.
According to documentation first you get all the trackers set on page
// returns an array with all the trackers
const trackers = ga.getAll();
And then, for each tracker, you can obtain the tracking id for each one getting the property
// returns the 'trackingId' propperty
const trackingId = tracker.get('trackingId'));
If you want to shorten it up, you could do
const trackingIds = ga.getAll().map(tracker => tracker.get('trackingId'));
Warning:
Remember to run this always after the ga ready.
Don't — use ga object methods outside a readyCallback as the methods
may not be available yet.
var trackers = ga.getAll();
Do — use ga object methods inside a
readyCallback as they're guaranteed to be available.
ga(function() {
var trackers = ga.getAll();
});
I just did it using an alternative method, as follows:
var _gaq = _gaq || [];
_gaq.push(function() {
var trackers, i;
trackers = _gat._getTrackers();
for (i = trackers.length - 1; i >= 0; i--) {
var account = trackers[i]._getAccount();
console.log("tracker account", account);
}
});

Get prefixes of css filters with Modernizr

I'm trying to use the Modernizr.prefixed() function to avoid writing all the vendors code in my JS but it doesn't seems to work.
I would like to use it for css filters, here is my code:
Modernizr.prefixed('filter') // returns 'filter' on Chrome which actually needs '-webkit-filter'
I already looked at the source code of Modernizr but it didn't helped (https://github.com/Modernizr/Modernizr/blob/master/feature-detects/css/filters.js).
Notice that the feature detection for filters is present in my Modernizr build.
Thank you!
Unfortunately, this is really a chromium bug.
The fine young gentlemen stucox provides a work around for this specific case, though
// This could be written more efficiently, but shows the technique at least
function getFilterPrefixed () {
var elem = document.createElement('div');
var testValue = 'grayscale(1)';
var prop;
var i;
// `Modernizr._prefixes` is a list of known (common) vendor prefixes
for (i = 0; i < Modernizr._prefixes.length; i++) {
prop = Modernizr._prefixes[i] + 'filter';
// Set-and-check: if the property holds a valid value, it's the one
elem.style[prop] = testValue;
if (elem.style[prop] == testValue) {
return prop;
}
}
}

AngularJS ng-repeat not showing values

I have a simple ng-repeat block that looks like this:
<div ng-repeat = "Service in getServices()">
{{Service}}
</div>
The corresponding controller defines getServices()
$scope.getServices = function(){
try{
var services = [];
for (var i = 0; i < $rootScope.Store.Services.length; i++){
services.push($rootScope.Store.Services[i].ServiceID);
}
console.log('services: ' + JSON.stringify(services))
return services;
}
catch (err){
console.log('no services')
return [];
}
};
For some reason, however, I am getting no Services in my view. I know I can just repeat directly over the $rootScope and that's how I had it before but this was an effort to debug. I am getting Services in my console:
services: ["ATM","DELI","DIESEL"]
Also, when I change the getServices() function to something like this:
$scope.getservices = function(){
var test = ["a", "b", "c"];
return test;
}
It does update the view correctly. Not sure what's going on! Note that I have many other perfectly working ng-repeats in the app.
Solved
I have no idea why but after removing iscroll from this particular page, it works fine -_-
Since you mention that iScroll was the problem, have you checked there aren't any global variable collisions?
Maybe you should implement iScroll via Angular: http://ngmodules.org/modules/ng-iScroll

Knockout.js incredibly slow under semi-large datasets

I'm just getting started with Knockout.js (always wanted to try it out, but now I finally have an excuse!) - However, I'm running into some really bad performance problems when binding a table to a relatively small set of data (around 400 rows or so).
In my model, I have the following code:
this.projects = ko.observableArray( [] ); //Bind to empty array at startup
this.loadData = function (data) //Called when AJAX method returns
{
for(var i = 0; i < data.length; i++)
{
this.projects.push(new ResultRow(data[i])); //<-- Bottleneck!
}
};
The issue is the for loop above takes about 30 seconds or so with around 400 rows. However, if I change the code to:
this.loadData = function (data)
{
var testArray = []; //<-- Plain ol' Javascript array
for(var i = 0; i < data.length; i++)
{
testArray.push(new ResultRow(data[i]));
}
};
Then the for loop completes in the blink of an eye. In other words, the push method of Knockout's observableArray object is incredibly slow.
Here is my template:
<tbody data-bind="foreach: projects">
<tr>
<td data-bind="text: code"></td>
<td><a data-bind="projlink: key, text: projname"></td>
<td data-bind="text: request"></td>
<td data-bind="text: stage"></td>
<td data-bind="text: type"></td>
<td data-bind="text: launch"></td>
<td><a data-bind="mailto: ownerEmail, text: owner"></a></td>
</tr>
</tbody>
My Questions:
Is this the right way to bind my data (which comes from an AJAX method) to an observable collection?
I expect push is doing some heavy re-calc every time I call it, such as maybe rebuilding bound DOM objects. Is there a way to either delay this recalc, or perhaps push in all my items at once?
I can add more code if needed, but I'm pretty sure this is what's relevant. For the most part I was just following Knockout tutorials from the site.
UPDATE:
Per the advice below, I've updated my code:
this.loadData = function (data)
{
var mappedData = $.map(data, function (item) { return new ResultRow(item) });
this.projects(mappedData);
};
However, this.projects() still takes about 10 seconds for 400 rows. I do admit I'm not sure how fast this would be without Knockout (just adding rows through the DOM), but I have a feeling it would be much faster than 10 seconds.
UPDATE 2:
Per other advice below, I gave jQuery.tmpl a shot (which is natively supported by KnockOut), and this templating engine will draw around 400 rows in just over 3 seconds. This seems like the best approach, short of a solution that would dynamically load in more data as you scroll.
Please see: Knockout.js Performance Gotcha #2 - Manipulating observableArrays
A better pattern is to get a reference to our underlying array, push to it, then call .valueHasMutated(). Now, our subscribers will only receive one notification indicating that the array has changed.
As suggested in the comments.
Knockout has it's own native template engine associated with the (foreach, with) bindings. It also supports other template engines, namely jquery.tmpl. Read here for more details. I haven't done any benchmarking with different engines so don't know if it will help. Reading your previous comment, in IE7 you may struggle to get the performance that you are after.
As an aside, KO supports any js templating engine, if someone has written the adapter for it that is. You may want to try others out there as jquery tmpl is due to be replaced by JsRender.
Use pagination with KO in addition to using $.map.
I had the same problem with a large datasets of 1400 records until I used paging with knockout. Using $.map to load the records did make a huge difference but the DOM render time was still hideous. Then I tried using pagination and that made my dataset lighting fast as-well-as more user friendly. A page size of 50 made the dataset much less overwhelming and reduced the number of DOM elements dramatically.
Its very easy to do with KO:
http://jsfiddle.net/rniemeyer/5Xr2X/
KnockoutJS has some great tutorials, particularly the one about loading and saving data
In their case, they pull data using getJSON() which is extremely fast. From their example:
function TaskListViewModel() {
// ... leave the existing code unchanged ...
// Load initial state from server, convert it to Task instances, then populate self.tasks
$.getJSON("/tasks", function(allData) {
var mappedTasks = $.map(allData, function(item) { return new Task(item) });
self.tasks(mappedTasks);
});
}
Give KoGrid a look. It intelligently manages your row rendering so that it's more performant.
If you you're trying to bind 400 rows to a table using a foreach binding, you're going to have trouble pushing that much through KO into the DOM.
KO does some very interesting things using the foreach binding, most of which are very good operations, but they do start to break down on perf as the size of your array grows.
I've been down the long dark road of trying to bind large data-sets to tables/grids, and you end up needing to break apart/page the data locally.
KoGrid does this all. Its been built to only render the rows that the viewer can see on the page, and then virtualize the other rows until they are needed. I think you'll find its perf on 400 items to be much better than you're experiencing.
A solution to avoid locking up the browser when rendering a very large array is to 'throttle' the array such that only a few elements get added at a time, with a sleep in between. Here's a function which will do just that:
function throttledArray(getData) {
var showingDataO = ko.observableArray(),
showingData = [],
sourceData = [];
ko.computed(function () {
var data = getData();
if ( Math.abs(sourceData.length - data.length) / sourceData.length > 0.5 ) {
showingData = [];
sourceData = data;
(function load() {
if ( data == sourceData && showingData.length != data.length ) {
showingData = showingData.concat( data.slice(showingData.length, showingData.length + 20) );
showingDataO(showingData);
setTimeout(load, 500);
}
})();
} else {
showingDataO(showingData = sourceData = data);
}
});
return showingDataO;
}
Depending on your use case, this could result in massive UX improvement, as the user might only see the first batch of rows before having to scroll.
Taking advantage of push() accepting variable arguments gave the best performance in my case.
1300 rows were loading for 5973ms (~ 6 sec.). With this optimization the load time was down to 914ms (< 1 sec.)
That's 84.7 % improvement!
More info at Pushing items to an observableArray
this.projects = ko.observableArray( [] ); //Bind to empty array at startup
this.loadData = function (data) //Called when AJAX method returns
{
var arrMappedData = ko.utils.arrayMap(data, function (item) {
return new ResultRow(item);
});
//take advantage of push accepting variable arguments
this.projects.push.apply(this.projects, arrMappedData);
};
I been dealing with such huge volumes of data coming in for me valueHasMutated worked like a charm .
View Model :
this.projects([]); //make observableArray empty --(1)
var mutatedArray = this.projects(); -- (2)
this.loadData = function (data) //Called when AJAX method returns
{
ko.utils.arrayForEach(data,function(item){
mutatedArray.push(new ResultRow(item)); -- (3) // push to the array(normal array)
});
};
this.projects.valueHasMutated(); -- (4)
After calling (4) array data will be loaded into required observableArray which is this.projects automatically .
if you got time have a look at this and just in-case any trouble let me know
Trick here : By doing like this , if in case of any dependencies (computed,subscribes etc) can be avoided at push level and we can make them execute at one go after calling (4).
A possible work-around, in combination with using jQuery.tmpl, is to push items on at a time to the observable array in an asynchronous manner, using setTimeout;
var self = this,
remaining = data.length;
add(); // Start adding items
function add() {
self.projects.push(data[data.length - remaining]);
remaining -= 1;
if (remaining > 0) {
setTimeout(add, 10); // Schedule adding any remaining items
}
}
This way, when you only add a single item at a time, the browser / knockout.js can take its time to manipulate the DOM accordingly, without the browser being completely blocked for several seconds, so that the user may scroll the list simultaneously.
I've been experimenting with performance, and have two contributions that I hope might be useful.
My experiments focus on the DOM manipulation time. So before going into this, it is definitely worth following the points above about pushing into a JS array before creating an observable array, etc.
But if DOM manipulation time is still getting in your way, then this might help:
1: A pattern to wrap a loading spinner around the slow render, then hide it using afterRender
http://jsfiddle.net/HBYyL/1/
This isn't really a fix for the performance problem, but shows that a delay is probably inevitable if you loop over thousands of items and it uses a pattern where you can ensure you have a loading spinner appear before the long KO operation, then hide it afterwards. So it improves the UX, at least.
Ensure you can load a spinner:
// Show the spinner immediately...
$("#spinner").show();
// ... by using a timeout around the operation that causes the slow render.
window.setTimeout(function() {
ko.applyBindings(vm)
}, 1)
Hide the spinner:
<div data-bind="template: {afterRender: hide}">
which triggers:
hide = function() {
$("#spinner").hide()
}
2: Using the html binding as a hack
I remembered an old technique back from when I was working on a set top box with Opera, building UI using DOM manipulation. It was appalling slow, so the solution was to store large chunks of HTML as strings, and load the strings by setting the innerHTML property.
Something similar can be achieved by using the html binding and a computed that derives the HTML for the table as a big chunk of text, then applies it in one go. This does fix the performance problem, but the massive downside is that it severely limits what you can do with binding inside each table row.
Here's a fiddle that shows this approach, together with a function that can be called from inside the table rows to delete an item in a vaguely-KO-like way. Obviously this isn't as good as proper KO, but if you really need blazing(ish) performance, this is a possible workaround.
http://jsfiddle.net/9ZF3g/5/
If using IE, try closing the dev tools.
Having the developer tools open in IE significantly slows this operation down. I'm adding ~1000 elements to an array. When having the dev tools open, this takes around 10 seconds and IE freezes over while it is happening. When i close the dev tools, the operation is instant and i see no slow down in IE.
I also noticed that Knockout js template engine works slower in IE, I replaced it with underscore.js, works way faster.

Categories

Resources