Windows 8 Javascript app activation/launch deferral - javascript

So currently in a windows 8 WinJS app I'm coding, I am trying to get the loading of an xml file to take place in the app startup sequence, while the splash screen is still showing, as this xmldoc element is needed for when the home page loads, and loading of the home page will fail without it.
This is my initiation sequence in default.js:
(function () {
"use strict";
var activation = Windows.ApplicationModel.Activation;
var app = WinJS.Application;
var nav = WinJS.Navigation;
var sched = WinJS.Utilities.Scheduler;
var ui = WinJS.UI;
app.addEventListener("activated", function (args) {
if (args.detail.kind === activation.ActivationKind.launch) {
if (args.detail.previousExecutionState !== activation.ApplicationExecutionState.terminated) {
// TODO: This application has been newly launched. Initialize
// your application here.
console.log("Newly Launched!");
var localSettings = Windows.Storage.ApplicationData.current.localSettings;
WinJS.Namespace.define("MyGlobals", { localSettings: localSettings });
// APP RUN TYPE CHECK AND SEQUENCE (FIRST RUN / NOT FIRST RUN):
if (MyGlobals.localSettings.values['firstRunCompleted']) {
console.log("NOT FIRST RUN!");
// CACHE VERSION CHECK. IF APP HAS BEEN UPDATED, INITIATE NEWLY ADDED CACHE VALUES HERE:
} else {
console.log("FIRST RUN!")
MyGlobals.localSettings.values['firstRunCompleted'] = true;
};
//loadXML(); have tried many things with this. doesn't work.
} else {
// TODO: This application has been reactivated from suspension.
// Restore application state here.
var currentVolume = app.sessionState.currentVolume;
if (currentVolume) {
console.log("RESTORE FROM SUSPENSION");
console.log(currentVolume);
};
}
nav.history = app.sessionState.history || {};
nav.history.current.initialPlaceholder = true;
// Optimize the load of the application and while the splash screen is shown, execute high priority scheduled work.
ui.disableAnimations();
var p = ui.processAll().then(function () {
return nav.navigate(nav.location || Application.navigator.home, nav.state);
}).then(function () {
return sched.requestDrain(sched.Priority.aboveNormal + 1);
}).then(function () {
ui.enableAnimations();
});
args.setPromise(p);
args.setPromise(WinJS.UI.processAll().then(function completed() {
loadSavedColour();
// Populate Settings pane and tie commands to Settings flyouts.
WinJS.Application.onsettings = function (e) {
e.detail.applicationcommands = {
"helpDiv": { href: "html/Help.html", title: WinJS.Resources.getString("settings_help").value },
"aboutDiv": { href: "html/About.html", title: WinJS.Resources.getString("settings_about").value },
"settingsDiv": { href: "html/Settings.html", title: WinJS.Resources.getString("settings_settings").value },
};
WinJS.UI.SettingsFlyout.populateSettings(e);
}
As you can see where I have the commented line of "loadXML()", that is where I need the loadXML() function to take place.
Here is my loadXML() function:
function loadXML() {
Windows.ApplicationModel.Package.current.installedLocation.getFolderAsync("foldername").then(function (externalDtdFolder) {
externalDtdFolder.getFileAsync(MyGlobals.localSettings.values['currentBook']).done(function (file) {
Windows.Data.Xml.Dom.XmlDocument.loadFromFileAsync(file).then(function (doc) {
WinJS.Namespace.define("MyGlobals", {
xmlDoc: doc,
});
})
})
});
};
(loadXML is a working function and works in other scenarios)
However, the issue is that before the loadXML function finishes, the app splash screen goes away, and the next home.html home page loads, which starts the accompanying home.js, which has a function that requires the MyGlobals.xmlDoc object that loadXML should have made. This immediately crashes the app, as MyGlobals.xmlDoc is undefined/null.
I used to have this app working by running loadXML in home.js for the home.html page directly, but in that scenario the XML document is reloaded every time navigation is made to the page, wasting time and resources. As such, I'm trying to move the xmldocument loading into the app startup/initialization.
Thanks a lot!

loadXML has async functionality and you need to handle that.
You shouldn't expect that the loadFromFileAsync (or any of the other async functions) have completed before it returns to the caller. If your code doesn't wait, you'll find that the MyGlobals.xmlDoc value won't be set when you need it.
I've renamed it below to be more accurate as to its behavior. The big change is that it returns a Promise that can be used by the caller to properly wait for the Xml doc to be loaded. This Promise could be used with other Promises to wait on multiple conditions if you'd like (or in the case, other async work).
function loadXMLAsync() {
return new WinJS.Promise(function (complete, error, progress) {
var localSettings = MyGlobals.localSettings.values;
var installedLocation = Windows.ApplicationModel.Package.current.installedLocation;
installedLocation.getFolderAsync("foldername").then(function (externalDtdFolder) {
externalDtdFolder.getFileAsync(values['currentBook']).done(function (file) {
Windows.Data.Xml.Dom.XmlDocument.loadFromFileAsync(file).then(function (doc) {
complete(doc);
});
});
});
});
};
Then, in use:
loadXmlAsync().then(function(doc) {
WinJS.Namespace.define("MyGlobals", {
xmlDoc: doc,
});
// and any other code that should wait until this has completed
});
The code above does not handle errors.

I believe what you need to do is to extend the splash screen to give your app more time to initialize UI. For your scenario, it's loading the xml.
I will suggest you read How to extend the splash screen (HTML). The main idea is to display an extended splash screen(you can use the same image as the default one) in the activated event, then call your loadXML.

In addition to other comments, what you really need to do is include the promise from loadXml with what you pass to args.setPromise. As you know, setPromise is a way to tell the app loader to wait until that promise is fulfilled before removing the splash screen. However, in your code you're calling setPromise multiple times. What you should be doing is joining all the promises you care about (animations, loadXml, and setting loading) with WinJS.Promise.join, so that you get a single promise that's then waiting on all the other three, and when that one is fulfilled, then remove the splash screen.
Alan's suggestion for an extended splash screen is helpful if that whole loading process ends up taking too long, as doing an extended splash gives you total control over what's happening and when the transition happens to your main page.

Related

Code Splitting / Preloading Content while user is browsing?

Using tools like Webpack we can enable code splitting and only
load our application code asynchronously when required.
Example in the context of a react application with react-router.
Load initial page.
-> go to new route
---> webpack loads in the component file required asynchronous.
Webpack waits until the code is required in order to initiate the request.
My question is, once the base application code load, can we start loading the rest of the code, even before the user initiates the transition to the new route?
My view is that will prevent the user from waiting for the webpack chunk to download.
-> Load initial page
--> user sitting idle or browsing on home page
----> Start loading application code for rest of the application
---> user goes to new route (faster UX because code has already download in the background)
I hope this makes sense
Yes you can achieve this. I will show one of the possible solutions.
Firstly let's create backgroundLoader for queuing required chunks:
const queue = [];
const delay = 1000;
let isWaiting = false;
function requestLoad() {
if (isWaiting) {
return;
}
if (!queue.length) {
return;
}
const loader = queue.pop();
isWaiting = true;
loader(() => {
setTimeout(() => {
isWaiting = false;
requestLoad();
}, delay)
});
}
export default (loader) => {
queue.push(loader);
requestLoad();
}
This function will load your chunks in background with 1 second delay (you can tweak it - for example start popping queue after for example 5 second or shuffle array of chunks).
Next you must register your require.ensure in queuing function backgroundLoader:
import render from './render'; // not relevant in this example
import backgroundLoader from './backgroundLoader';
let lightTheme = (cb) => {
require.ensure([], () => {
cb(require('./themeA.css'));
}, 'light');
}
let darkTheme = (cb) => {
require.ensure([], () => {
cb(require('./themeB.css'));
}, 'dark');
}
let pinkTheme = (cb) => {
require.ensure([], () => {
cb(require('./themeC.css'));
}, 'pink');
}
backgroundLoader(lightTheme);
backgroundLoader(darkTheme);
backgroundLoader(pinkTheme);
export default (themeName) => { // router simulation
switch(themeName) {
case 'light':
lightTheme(render);
break;
case 'dark':
darkTheme(render);
break;
case 'pink':
pinkTheme(render);
break;
}
};
Once you require your chunk in switch statement you pass render function containing resolve function. In backgroundLoader this function will be empty resulting only loading chunk to head of your app.
Full code for this example you can see on WebpackBin (you can check network to see how chunks are loaded in background)

Authorization interceptor with Infinity-scroll in AngularJS

I'm using angular-http-auth for intercepting 401 response in order to display login dialogue and when the user is authorized, to retry failed request.
Since I'm using infinity-scroll I'm increasing an offset value, with every additional upload:
var upload = function () {
dataResource.query($scope.model).then(function (result) {
angular.forEach(result.items, function (value) {
$scope.items.push(value);
});
});
}
$scope.uploadMore = function () {
$scope.model.Offset = $scope.model.Offset + 10;
upload();
};
upload();
When my page loads up it immediately sends 2 request to server upload(), invoked from this directive, and uploadMore() by infinity-scroll.
However, after user has logged in, the page does not display the first 10 entries, instead it displays 11-20 items 2 times in a row.
When I tried to debug it, I noticed that when angular-http-auth retries requests it uses increased by 10 Offset value for both queries($scope.module argument).
Functions upload() and uploadMore() are running for 2 times before angular-http-auth, so I guess that is why interceptor uses updated argument for both queries.
Could somebody please help me with this problem?
So you can resolve this problem prevent execute request until previous will finish.
The faster way to do that is :
var pending = false;
var upload = function () {
if(!pending) {
pending = true;
dataResource.query($scope.model).then(function (result) {
pending = false;
angular.forEach(result.items, function (value) {
$scope.items.push(value);
});
});
}
}

Fire an event when an NFC card is presented

I am attempting to build a webapp on a Chromebook, I need it to read RFID card serial numbers with an ACR122U NFC. I am using chrome-nfc.
I am reading cards happily, but I do not know how to fire an event when a card is presented.
Are there any events in chrome-nfc I can use to know when a card has been presented to the reader?
EDIT: I have been trying to use chrome.nfc.wait_for_tag, but it does not behave as I would expect.
// With a card on the reader
chrome.nfc.wait_for_tag(device, 10000, function(tag_type, tag_id){
var CSN = new Uint32Array(tag_id)[0];
console.log ( "CSN: " + CSN );
});
[DEBUG] acr122_set_timeout(round up to 1275 secs)
DEBUG: InListPassiveTarget SENS_REQ(ATQA)=0x4, SEL_RES(SAK)=0x8
DEBUG: tag_id: B6CA9B6B
DEBUG: found Mifare Classic 1K (106k type A)
[DEBUG] nfc.wait_for_passive_target: mifare_classic with ID: B6CA9B6B
CSN: 1805372086
// with no card on the reader
chrome.nfc.wait_for_tag(device, 10000, function(tag_type, tag_id){
var CSN = new Uint32Array(tag_id)[0];
console.log ( "CSN: " + CSN );
});
[DEBUG] acr122_set_timeout(round up to 1275 secs)
DEBUG: found 0 target, tg=144
Both return the results as above immediately, it does not seem to matter what number I use for a timeout...
If I call the function with no card on the reader, and then immediately put the card on the reader after function call, I get no output in the console.
I'm not familiar with chrome-nfc, but taking a shot in the dark by reverse engineering the source, it looks like you would want to use the wait_for_tag method, like:
chrome.nfc.wait_for_tag(device, 3000, function(tag_type, tag_id) {
// Do your magic here.
});
...Where device is your reader, 3000 is the maximum time to wait (in ms), and replacing // Do your magic here. with your desired logic. If it times out, both tag_type and tag_id will be null.
If you wanted to wait indefinitely, you could just recursively call a function with the above code. Example:
function waitAllDay(device) {
chrome.nfc.wait_for_tag(device, 1000, function(tag_type, tag_id) {
if(tag_type !== null && tag_id !== null)
{
// Do your magic here.
}
waitAllDay(device);
});
}
That's assuming you want it to continue waiting even after a tag has been presented. Wrap the waitAllDay(device); in an else if you want it to stop once a tag is read.
UPDATE: It seems the wait_for_tag method does not work as intended, so I'm proposing a second solution. I'm leaving the existing solution in place in case the method is fixed by the developers of chrome-nfc.
Another thing to try is to use chrome.nfc.read, passing in a timeout option, inside a window.setInterval.
var timer = window.setInterval(function () {
chrome.nfc.read(device, { timeout: 1000 }, function(type, ndef) {
if(!!type && !!ndef) {
// Do your magic here.
// Uncomment the next line if you want it to stop once found.
// window.clearInterval(timer);
}
});
}, 1000);
Be sure and call window.clearInterval(timer) whenever you want it to stop watching for tags.
While I do not consider this a proper solution; here is a workaround I am using for the time being.
function listen_for_tag(callback, listen_timeout){
var poll_delay = 400; //ms
var listen_loop = null;
if(!listen_timeout){
listen_timeout = 99999999;
}
function check_for_tag(){
if(listen_timeout < 0) {
clearInterval(listen_loop);
console.log("we didnt find a tag. finished");
}
chrome.nfc.wait_for_tag(dev_manager.devs[0].clients[0], 10, function(tag_type, tag_id){
console.log ( "FOUND A TAG!!" );
clearInterval(listen_loop);
// handle the callback (call it now)
var C = callback;
if (C) {
callback = null;
window.setTimeout(function() {
C(tag_type, tag_id);
}, 0);
}
});
listen_timeout -= poll_delay;
}
listen_loop = setInterval(check_for_tag, poll_delay);
}

Fast way to redirect tab when new page is loading

I'm trying to redirect a tab to a new page when the URL matches my pattern before it's done loading. The method I came up with does the redirection after a good part of the page is done loading yet.
var tabs = require("sdk/tabs");
var tab_utils = require("sdk/tabs/utils");
function logShow(tab) {
console.log(tab.url + " is loaded; " + pattern.test(tab.url));
if (pattern.test(tab.url)) {
var lowLevelTab = viewFor(tab);
console.log(tab_utils.setTabURL (lowLevelTab, newURL(tab.url)));
// also simply replacing this bit with
// tab.url = "foo" doesn't speed things up
}
}
tabs.on('load', logShow);
Is there a good way of calling setTabURL (...) earlier?
I finally found the best way to do it:
function listener(event) {
var channel = event.subject.QueryInterface(Ci.nsIHttpChannel);
var url = event.subject.URI.spec;
// Here you should evaluate the url and decide if make a redirect or not.
if (pattern.test(url)) {
// If you want to redirect to another url,
// you have to abort current request, see: [1] (links below)
channel.cancel(Cr.NS_BINDING_ABORTED);
// Set the current gbrowser object (since
// the user may have several windows/tabs)
var goodies = loadContextGoodies(channel);
var domWin = goodies.aDOMWindow; // method suggested by
var gBrowser = goodies.gBrowser; // Noitidart [2] (links below)
var browser = goodies.browser; // for reference see comment below
var htmlWindow = goodies.contentWindow;
// and load the fixed URI
browser.loadURI(newUrl(url));
} else {
// do nothing, let Firefox keep going on the normal flow
}
}
exports.main = function() {
events.on("http-on-modify-request", listener);
}
credit where credit is due: answer by matagus (on question asked by Andrew)
[1]: Link: Intercepting Page Loads
[2]: Noitidart: 'from topics: How can I change the User Agent in just one tab of Firefox? and Is it possible to know the target DOMWindow for an HTTPRequest?'
Never used sdk/tabs before, but you could load your content hidden.
Once your page has loaded your logShow function will run.
Then build into this function some "reveal body" functionality.

Global variables for a single web page application using Durandal

My application has three things that are "global": a user location with lat and lng values, if the user is logged in or not, and the activity that they have chosen (think of it like a category).
What I'd like to happen is for these values to be used across the application. Each screen will use some set of this date, so loading it per page isn't the right answer. The information also will not change unless the user creates an event: different location, activity, or signs in/out.
How should I set this up?
My thinking is that there should be an initial load upon startup, in my main.js file I load each and then app.start like so:
siteService.js
define([...], function (...) {
return {
init: function () {
return $.when(
activityService.getStoredActivity(),
locationService.getStoredLocation(),
userService.getUsername()
);
}
}
});
main.js
define([..., 'modules/siteService'], function (..., site) {
...
site.init().then(function () {
app.start().then(function () {
...
app.setRoot('viewmodels/shell', 'entrance');
});
});
});
This does make sure that the data is loaded. From there in each of my services I store the information into local storage so I don't have to keep firing off AJAX calls. If local storage isn't there it will.
The problem comes later when I need this data in other pages. I'm ending up calling my site.init().then() within each page's activate method. Which is pretty sloppy.
shell.js
self.activate = function () {
...
siteService.init().then(function (activity, location, username) {
self.activity(activity);
self.location(location);
setUsername(username);
});
return router.activate();
};
and then again in:
welcome.js
this.activate = function () {
site.init().then(function(activity, location) {
loc = location;
act = activity;
load();
});
});
I'd like to set the values initially, each page initially loading from those values, and then use the pub/sub of Durandal to react to any changes after the fact.
I was thinking of solely using the pub/sub, but then felt that I would run into chicken and the egg issues when the data is loaded in relation to the page loads (IE If my AJAX loaded first, and my page wasn't loaded, the event fires, but the page never gets the update).
Is it possible to setup something like a static class in javascript to pull the data initially and then share it throughout the viewmodels? Or, is there a way to guarantee that the value will be there when I initially load a page, like a app.on('value', { initialize: true })?
When a require.js module returns an object, that object is a singleton, so you can have something like
settings.js
define(function() {
return {
location: {
lat: 0,
long: 0
},
loggedin: false,
activity: ""
}
});
Then any time you name settings as a dependency, you'll always get a reference to the same object and you can get/set its fields as you wish.
Changed siteService.js to:
define([...], function (...) {
var module = function () { };
module.prototype.activity = {};
module.prototype.location = {};
module.prototype.username = {};
module.prototype.init = function () {
return $.when(
activityService.getStoredActivity(),
locationService.getStoredLocation(),
userService.getUsername()
).then(function (activity, location, username) {
module.prototype.activity = activity;
module.prototype.location = location;
module.prototype.username = username;
});
};
return module;
});
main.js to
define([..., 'modules/siteService'], function(..., Site) {
site = new Site();
site.init().then(function () {
app.start().then(function () {
...
app.setRoot('viewmodels/shell', 'entrance');
});
});
and shell.js and welcome.js now just use:
var site = new Site();
self.activity(site.activity);
self.location(site.location);
setUsername(site.username);
and
var site = new Site();
loc = site.location;
act = site.activity;

Categories