Lazy loading in Knockout JS - javascript

I'm trying to load and parse json data from an external source into a table via Knockout JS. So far, everything has been successful through the following code:
// Snippet
var self = this;
self.notices = ko.observableArray([]);
self.currentTab = ko.observable(5);
ko.computed(function() {
$.getJSON('http://json.source.here.com/tab/'+ko.toJS(self.currentTab), function(threads) {
if (threads !== null) {
self.notices(threads);
} else {
self.notices([]);
}
});
}, self.notices);
When a user clicks on a certain tab it would load the json data (forum threads) based on the selected tab value (self.currentTab) onto the table in the form of rows (self.notices).
Everything works as expected however, I noticed while browsing around other pages that do not have the above bindings, the json is still being loaded ($.getJSON is fired). I'm concerned that this may have some detrimental effects on the performance of my website as it is loading the json source even though it is not needed.
EDIT: I figured this out through Google Chrome's developer console.
I currently have my view model in a JavaScript file that is being used by every other pages as well. It consists of bindings for all the pages.
My question is, how can I load the json data on a specific page or only when the bindings are present - lazy loading? Preferably, I would like to keep all the bindings in a single JavaScript file, I do not want to separate them out and load them on a per page basis.

Here is an article that I wrote on a simliar topic a little while back: http://www.knockmeout.net/2011/06/lazy-loading-observable-in-knockoutjs.html
In your case, I think that you really want to add some guards around the $.getJSON call to ensure that it is only making AJAX requests when you are in the appropriate state (on the appropriate tab).
Along with that, the blog post describes using the deferEvaluation flag on a computed observable to ensure that the logic does not run until someone binds against the computed observable (in your case, you have an anonymous computed observable, but you could add it to your view model as a property and bind against it in your view. Without this flag, the evaluation code will run when you create the computed observable, which is not desirable in your case.

Related

How can I dynamically generate content of a Blockly dropdown menu?

I am writing an application using python and flask.
In the webinterface, the user is able to make small customized programs using blockly.
Some of the already existing blocks have a dropdown menu.
All that works fine.
But now I want to have a block with a dropdown menu where the options are certain files on a usb stick the python backend found by walking through the usb stick directory.
I would like to fill the dropdown menu with these files now. I cannot, however, know them in advance and that is why they have to be dynamically generated - but by python, not by javascript or that like.
I already found this here: https://developers.google.com/blockly/guides/create-custom-blocks/dropdown-menus (at "Dynamic menu" at the end), but that does not help in my case, since I want the information to come from the python backend and not from javascript.
Does anyone know a way to do this?
Thank you a lot in advance!
This is a pretty old question, so I'm not sure if the OP still needs an answer, but I'm answering on the assumption that this is a situation where the OP is using an asynchronous JavaScript call to the backend and can't just return the results because of that.
We have a not dissimilar situation in our application (albeit using a nodejs backend) where we're populating dropdowns based on options retrieved from Google Cloud Datastore. We have a React App, so our solution was to have a Flux store attached to the window which our asynchronous call populates upon completing, and then have the dropdown generation code access that store. Obviously, a full Flux store may not be appropriate for all situations, but my general advice here would be to pass in an option generator function and have it read from a store or other variable on the window, and then have your call to the backend run on page load (or when otherwise appropriate) and populate the store on the window. If your app inits before the call is done, you can have it set up to show that it's loading options based on a flag on the store.
As a very simplified case, you might have:
// on page load
window.store = {done: false};
myBackendCall()
.then(function(results){
window.store = {
results: results,
done: true
};
})
.catch(function(error){
console.error(error);
window.store = {results: [], done: true};
});
Then in your selector, you might go with something like:
function generateOptions(){
if(!window.store.done) {
return [['Loading...', 'loading']];
} else {
return [['Select a file', '']].concat(window.store.results);
}
}
this.appendDummyInput()
.appendField(new Blockly.FieldDropdown(generateOptions), fieldName);
Obviously, this is a gross simplification, but I think it's enough to show the technique. We actually made our own custom Blockly fields for this for other reasons, but this should still work with a vanilla Blockly dropdown. Swap out Promise chaining for callbacks if that's how your setup is. You could also make the call inside the block and set a variable on the block itself (this.store or whatever), but that would result in multiple calls being made for multiple instances of the block, so that may not be appropriate for you.

Caching URL view/state with parameters

I'm making a mobile app using Cordova and AngularJS. Currently I have installed ui-router for routing but I'm open to any other alternative for routing.
My desire: I want to cache certain views bound with parameters. In other words I want to cache paths (or pages).
Example situation: let's say that we see some dashboard page, click on some book cover which redirects to the path book/2. This path is being loaded for the first time into app. Router redirects from HomeController to BooksController (whatever the name). Now the BooksController loads data for given $stateParams (book id = 2) and creates view filled with info about chosen book.
What I want in this situation:
I go back to the dashboard page - it is already loaded (cached?)
I choose book #2 again
Controller or router notices that data about this book is already loaded
The view isn't being recreated, instead it's being fetched from cache
Actually, it would be best to cache everything what I visit based on path. Preloading would be cool too.
Reason: performance. When I open some list of books then I want it to show fast. When view is being created every time, then animation of page change looks awful (it's not smooth).
Any help would be appreciated.
EDIT:
First of all, since I believe it's a common problem for many mobile HTML app programmers, I'd like to precise some information:
I'm not looking for hacks but a clear solution if possible.
Data in the views uses AngularJS, so YES, there are things like ng-bind, ng-repeat and so on.
Caching is needed for both data and DOM elements. As far as I know, browser's layout operation is not as expensive as recreating whole DOM tree. And repaint is not what we can omit.
Having separate controllers is a natural thing. Since I could leave without it I cannot imagine how it would work anyway.
I've got some semi-solutions but I'm gonna be strict about my desire.
Solution 1.
Put all views into one file (I may do it using gulp builder) and use ng-show. That's the simplest solution and I don't believe that anyone knowing AngularJS would not think about it.
A nice trick (from #DmitriZaitsev) is to create a helper function to show/hide element based on current location path.
Advantages:
It's easy.
KIND OF preload feature.
Disadvantages:
all views have to be in a single file. Don't ask why it's not convenient.
Since it's all about mobile devices, sometimes I'd like to "clear" memory. The only way I can think of is to remove those children from DOM. Dirty but ok.
I cannot easily cache /book/2 and /book/3 at the same time. I would have to dynamically create DOM children on top of some templates for each view bound with parameters.
Solution 2.
Use Sticky States AND Future States from ui-router-extras which is awesome.
Advantages:
Separated views.
Very clear usage, very simple since it's just a plugin for ui-router.
Can create dynamic substates. So it would be possible to cache book1, book2 but I'm not sure about book/1 and book/2
Disadvantages:
Again, I'm not sure but I didn't found an example with caching a pair/tuple (view, parameters). Other than that it looks cool.
This is precisely the problem I had to solve for my site 33hotels.com. You can check it and play with the tabs "Filter" and "Filter List" (corresponding to different Routes), and see that the View is updated instantly without any delay!
How did I do it? The idea is surprisingly simple - get rid of the Router!
Why? Because the way the Router works is it re-compiles the View upon every single Route change. Yes, Angular does cache the Template but not the compiled View populated with data. Even if data do not change! As the result, when I used the Router in the past, the switch always felt sluggish and non-reactive. Every time I could notice annoying delay, it was a fraction of second but still noticeable.
Now the solution I used? Don't re-compile your Views! Keep them inside your DOM at all times! Then use ng-hide/ng-show to hide/show them depending on the routes:
<div ng-show="routeIs('/dashboard')">
<-- Your template for Dashboard -->
</div>
<div ng-show="routeIs('/book')">
<-- Your template for Book -->
</div>
Then create a function routeIs(string) inside your Controller to test if $location.path() matches string, or begins with string as I am using it. That way I still get my View for all pathes like /book/2. Here is the function I am using:
$scope.routeBegins = function () {
return _.some(arguments, function (string) {
return 0 === $location.path().indexOf(string);
});
};
So no need to be smart with caching - just keep it in the DOM. It will cache your Views for you!
And the best part is - whenever your data is changed, Angular will instantly update all the Views inside your DOM, even the hidden ones!
Why is this awesome? Because, as user, I don't have to wait for all the parsing and compiling at the moment I want to see the result. I want to click the tab and see my results immediately! Why should the site wait for me to click it and then begin all the re-compiling as I am waiting? Especially when this could be easily done before, during the time my computer is idle.
Is there any downside? The only real one I can think of is loading memory with more DOM elements. However, this actual byte size of my views is negligible, comparing e.g. with all JS, CSS and images.
Another possible but avoidable downside is the re-compilation cost of the hidden views. This is where you can get smart and avoid computation-heavy parts depending on the current routes.
Also, you are not re-compiling the whole View, just the parts affected by data changes, which also lowers computational cost.
I find it quite remarkable that everyone is using Routes and seems to be completely unaware (or ignorant) of this problem.
1) About static pages in the app (views), angular takes care of loading them.
for example: for your dashboard page you need not worry about caching the page, as angular will take care of it. Angular will only load the dashboard view once and on all next requests for the dashboard view, angular will just show you the view(not load the file for view), if it is all a static view without any data loaded by ajax calls.
2) if your dashboard is itself loading the book list(or similar data) via ajax, then you can tell your controller to only load the data once and store it to localstorage and on subsequent requests to the dashboard page can only load the data from the localStorage.
3) similar approach can be used when your BooksController loads the data into a view. You can check in your BooksController if the request for a particular book is been previously made and if not your can store it to localstorage or database. and if the data was previously requested then you can load the same data from the storage without making a request to server.
Example situation:
say user makes request for book1, then
your controller i.e BooksController check whether the same data was requested before,
if not, you can load the data via the ajax call from server and also save it to local storage.
if it was loaded before you will load the data stored in the localstorage or in the database.
If you're using ui.router, then you should take a look at ui.router extras, specifically the sticky states module. This allows you to cache the state 'tree' (including rendered views) so they don't have to be compiled or re-rendered on state changes.
http://christopherthielen.github.io/ui-router-extras/
Here's a demo:
http://christopherthielen.github.io/ui-router-extras/example/sticky/#/

Meteor Rendering Template Dynamically

I have a webapp that consists of several pages and a main page. When developing this webapp in Meteor, I segregated the pages into templates. The webapp shall display different pages for a period of time, meaning that Page A shall be for 60 seconds, then proceed with Page B for maybe 75 seconds and so on.
In meteor I construct a main page that consists of header and footer and a changeable templateholder. So basically the main page looks like this:
<template name='main'>
{{>header}}
{{>templateHolder}}
{{>footer}}
</template>
and the Pages are translated into templates, i.e. templateA etc. These templates shall replace the templateHolder based on an update on Session object, which also based on some timing (in seconds) that will be executed by using setTimeout JS function.
Everything works, however I noticed that the timing of each Page goes haywire. When I tested the template individually, they work fine. I suspect that the asynch call of setTimeout somehow conflicting each other.
Here is the JS code that changes the template periodically.
Template.main.templateHolder = function(){
var appIndex = Session.get('currentAppIndex');
switch(appIndex){
case 'A':
return Template['templateA'];
} //....and so on... with other templates
}
Template.main.created = function() {
//Query each pages display time and load it into sessions
// etc...........
Session.set('currentAppIndex',0); //Initialize the first page
setTimeout(nextPage,0);
}
function nextPage() {
//Bunch of line of codes that retrieve time param from settings and calculate
//Also some simple alogrithm to get next templates.. etc
//Update the session index object so that it reactively updates the templateHolder
setTimeout(nextPage,currPageDisplayTime); //currPageDisplayTime is the pages time of display in milliseconds.
}
I am not sure if my way is correct but it managed to display the templates and change them. The only concerns is the timing does not work properly. What is the best way to change template dynamically? Is this prone to any bug in the future?
I have found the reason of this buggy behavior. How do I troubleshoot them?
I put JS alert whenever timeout occurs in templates.
Also, alert whenever a session object is updated.
The flow of the templates rendering should be like this template A (60 secs) --> template B (75secs)--> template A (which is a looping). I noticed that during displaying template B, the alert that I put in template A fires! That means that eventhough only one template is being shown at a time, the created template continues to live healthy behind the scene. It still continues the process whatever it is designated to do.
This made me realize that if both of the templates exist at the same time, I cannot use the same session objects as they will both updating the same things. Turned out that I standardized the use of the session objects across the templates; for example i use Session.set('itemIndex',index) to iterate through the collection in each template, but since the templates are accidentally using the same object, they both update the data at the same time.
So the JS setTimeout works as intended. The problem is the shared session objects.
TL;DR templates don't get destroyed when it is not displayed/changed. This can be avoided by destroying it manually or using different objects.
P/S: Still looking on how to destroy template after created.

initial data loading in angularjs

When building a web app where every page depends on many data sources, what's the best way to fetch the initial bits of data? When I look at twitter, I see the tweets that are visible on page load are in the HTML source, and more tweets are loaded in using AJAX as you scroll down. But there's no convenient way to get data that's already in the DOM to be inserted into the model.
Making a request for the initial data, immediately after page load seams stupid, because you've just made a lot of roundtrips to the server to fetch css, html and javascript. Would it be a bad idea to insert the data into a javascript tag on the page, so a javascript function can add the initial data?
I'm specifically asking for angularjs, but if there's an general technique, please let me know as well.
You'll be referencing your controller anyway on page load, so you won't have to have an inline script tag.
You can either set a default model and use the attribute ng-bind on initial load, or call a function to pass back data.
It's pretty typical to fetch data on load in angularjs.
Would it be best to couple Angularjs with an HTTP Client in the backend like Zend_Http_Client or Guzzle to let the server fetch the data. Then, pass the data as json to javascript upon render.
I know Angularjs is designed for Single Page applications. That's why it makes sense that it lazy loads the data.
However, if we're going to move to the approach where we still render the page dynamically and still delegate the task of organizing the content to Angularjs. What framework will be suitable to contain the AngularJS views. Right now, project templates like angular-seed are all static..
That is, the idea is the server serves a page with the embedded json object. Then angular, takes over in the client side, Fetching additional content where needed.
So instead of just a single page of contact (e.g: index.html), we would have several pages like profiles.html, products.html. The help of the backend would be particularly helpful say you have a section which doesn't change often like your username on the top right side of the page. For me, I just think it's better to have these data preloaded in your page and not have to ask the server after the page has been loaded.
As bigblind have noticed, this seems to be the way sites like facebook, gmail, twitter does it. They contain the data embedded on page load. Then, load additional content via services afterwards.
The idea is something like below:
Webservice <---------- Backend------------> Frontend
<------------------------------------------
Backend delegates the task of querying the webservice to provide initial data in the rendered page to the client. Then client, can directly connect to webservice to fetch additional content.
Using the above setup.. What is the ideal development stack?
One way to do it is to create a directive that handles the initialization before binding happens.
For example:
app.directive('initdata', function() {
return {
restrict: 'A',
link: function($scope, element, attrs) {
if ( attrs.ngBind !== undefined)
{
$scope[attrs.ngBind] = attrs.initdata ? attrs.initdata : element.text();
}
}
};
});
This directive takes either the attribute value as initial value for the bound $scope property, or the textvalue of the element.
Example usage:
<div initdata="Foo Bar" ng-bind="test1"></div>
<div initdata ng-bind="test2">Lorem Ipsem</div>
Working example on http://jsfiddle.net/6PNG8/
There's numerous way to elaborate on this; for example parsing the initdata as json and merging it with the scope, and making it work for more complicated binds, like $root.someprop. But the basis is remarkably simple.
According to the answers on this question, a JSON object in a script tag on the page seems to be the way to go. If ayone comes up with a better idea, I'll accept your answer.

Best practice to send javascript code (e.g. a function, not a complete file) to the browser?

Assuming a JavaScript-based single-page application is returned by the server on the initial request. Besides some initialization code, which is common for every application, just the portion of the application needed to show the requested page (e.g. index page) is returned by the server on the initial request (then cached and rendered).
As the user clicks through the application, other portions of the application should be asynchronously loaded ('fetched', 'requested' or however you wanna call it) from the server. By "portions" a mean javascript code, images, css, etc. required to render the page. Let's focus on the javascript code part in this discussion.
It's important to notice that the javascript code to be returned to the browser is not contained in separate (static) files (which would be easy then and might be the case in the future due to e.g. performance reasons), but rather in one file, so it's not a 1:1 assiociation (request : file).
E.g. we could have a single app defined like this:
var LayoutPresenter = App.Presenter.extend('LayoutPresenter', {
__view: '<div>{{link "Author" "/author"}} - {{link "Book" "/book"}}</div>'
});
var AuthorPresenter = App.Presenter.extend('AuthorPresenter', {
__view: '<div><h1>{{name}}</h1></div>',
__parent: LayoutPresenter,
__context: { name: "Steve" }
});
var BookPresenter = App.Presenter.extend('BookPresenter', {
__view: '<div><h1>{{title}}</h1></div>',
__parent: LayoutPresenter,
__context: { title: "Advanced JavaScript" }
});
App.Presenter is part of the library I am writing and is available in the browser (or on any other client).
So assuming the user is browsing to the Book page which hasn't be loaded before (neither initially nor cached in the browser), the BookPresenter code, which is a function, should be returned by the server (assuming the LayoutPresenter code is already available in the browser and App.Presenter is available anyway because it's part of the library). I am running node.js on the server side.
How would you recommend to address this problem?
There is the eval function, so one could send javascript as a string and bring it back to live using eval(), but such an approach seems to be bad practice.
Never use eval - it's evil. The better option would be use jQuery ajax and set the dataType as script. This will evaluate your js, and also provide you with a call back once the script is loaded.
Refer to Ajax dataTypes and jQuery getScript shorthand. This is of course assuming that you can separate your code into logical modules
You might also consider it worth your time to check this question (How can I share code between Node.js and the browser?)
dNode is an option that is described in the question above and it looks quite exciting in terms of possibilities. You could create a list of function required for the Book page, then call them right off the server itself. That would eliminate the need to maintain separate js modules for each section of your page. Kudos to #Caolan for suggesting it.
As interesting as it is, take care to properly scope your functions; you don't want random users playing around on your server.

Categories