autosaving in chrome app (packaged app) - javascript

I'm developing a Chrome App (a.k.a. packaged app) where most of the work occurs in a textarea element, and I want to ensure that the user's work is automatically saved.
One impractical approach is to save the work in a file on every keystroke and other change to the textarea, with something like this:
$('#text').on("keypress paste cut change", function () {
// ... save to file
});
But that's way too much overhead, so instead I just set a dirty flag, and now the question is to effect the save at appropriate times if the dirty flag is set. These are some appropriate times I've come up with, all of which are easily implemented:
Every minute, which is the smallest interval that chrome.alarms.onAlarm will allow.
When the user exits the app with the Quit item on the File menu.
When the user explicitly chooses the Save item on the File menu. (This might go away in the final version, but it's very handy during development.)
The problem is that there is no event available when the user closes the app or the whole browser. These events do exist in JavaScript, but they are specifically disallowed in Chrome Apps. The rationale, as I understand it, is that the Chrome developers don't want to provide this crutch, and instead want to encourage app developers to implement apps that save work continuously. Fine! I'm all for it.
Any ideas how to implement autosaving in Chrome Apps?
Update: I just took a look at Gliffy Diagrams, perhaps the most ambitious of the Chrome Apps so far, and it doesn't do any saving when it's closed via the app window or from the dock. It only does it when Close or Save is chosen from its file menu. That's essentially what my app does.

You can use an normal JavaScript timer (say, 5 seconds) in the function where you're currently setting your dirty flag. Make the timer reset with each keypress, so that the autosave kicks in after 5 seconds of inactivity:
var inactiveTimer;
$('#text').on("keypress paste cut change", function () {
clearTimeout(inactiveTimer);
// save after 5 seconds, unless another keypress resets this timer
inactiveTimer = setTimeout(saveText, 5000);
}
Thus, you only save when the user hasn't done anything for 5 seconds, instead of saving after every single keystroke. Obviously, this doesn't handle the case that the user closes the app 4 seconds after he finishes typing, but it's better than your option #1.

Related

How to update a web page javascript counter live when the browser doesn't have focus?

I am making a browser game in html, css, and javascript, written in perl. Health and stamina are kept in the server and I use javascript to show the user a live updated count of these stats while the current page is loaded. This works fine, however if the user switches tabs or switches away from the browser and leaves it running in the background, the count value you see when you return does not keep up properly. So when you switch back to the browser, your counter might say 50/100 stamina when you actually have 100/100. So when you do something in the game (loads a new page) the server updates the counter to the true amount because the javascript is just keeping time to show the user a "live" rolling view in the browser.
Is there a way to ensure the javascript counter will continue to function even if the page/tab isn't active or on the forefront? Aside from completely re-writing my game to include continuous live server pushes in what is displayed on the browser to the user?
Say you are playing the game. You see your health and stamina regenerating. You switch to another program for a minute, then return to the game in the browser. You notice your health and stamina have not updated while you were away. But when you perform an action in the game, this value is updated to what it should be because it is tracked internally on the server. This is what I would like to fix. Hope that makes sense!
I have not tried anything to fix this issue yet besides searching the web and ending up on this site without a really "good" answer in sight, so I decided to ask the question.
Continuous server pushes wouldn't work either. Anything in the main event loop like a timer, or events happening when it's out of focus, gets slowed down by the browser to conserve resources. Some mobile browsers will stop it together.
The answer to the question is to change how your app keeps track of these stats.
Now some will say to use WebWorkers to run the timer in a separate thread but this won't solve all your issues. You'd still have a different version of the issue, like if someone restored your webpage from sleep or something along those lines. No background task can survive that.
You mention that you track these stats also on the server. That's convenient, so the most obvious thing you should do is detect when the tab comes back into focus using the Window focus event. You would then make all the calls to the server to fetch the most up-to-date stats and reset the timers based on that fresh data. To stop it from showing stale data while the request is in flight, you might choose to show a loading spinner or something during that period.
Another common way of fixing this is you keep around on each timer increment a var which says when the data last came back (a timestamp). When you leave focus, you detect this with the blur event and store that last timestamp somewhere. Then they come back into focus, you handle the focus event and calculate the difference between the current time and the last recorded time before defocus (blur). You may be able to recalculate from this period what the values should be.
But if your server has this info, it'd be far less error-prone and easy to just ask the server when they refocus.

Detect change to element id=ASIN when Ajax is making changes

This question is more of a general coding practice question. I am writing an extension for Google Chrome that gathers the ASIN number from Amazon.com when viewing an item page.
Design requirements:
When viewing a page that contains an element with id=ASIN, capture the ASIN. (E.g. B004FYJFNA from http://www.amazon.com/gp/product/B004FYJFNA/?tag=justinreinhart-20 )
When user changes platforms (e.g. from Playstation 3 to Xbox 360), detect that a change has occurred and capture the new ASIN value.
I have a "content script", injection.js, that is injected on every amazon page and can successfully perform requirement #1.
The issue I have is with #2. I am not able to efficiently detect a change to the ASIN value. Either the code doesn't fire OR I pick an element that fires ~100 times. I am at a loss how to do this efficiently.
Failure examples:
// (A) This fires ~100 times when user changes platforms,
// and also fires during mouseover events.
// Unacceptable but it does work.
$("#handleBuy").bind('DOMNodeRemoved', OnProductChange);
// (B) This doesn't fire at all. I have a few guesses why but no certainties.
$("#ASIN").on('change', OnProductChange);
Blathering:
Switching product platforms when the user clicks seems to tear the Amazon page apart and it destroys any event binding that I attempt. (I believe the element is removed and reinserted--not just changed.) I do not know javascript well enough to skillfully take these massive DOM changes into account.
Your help and knowledge is appreciated.

Update storage schema on extension update in an event page

I have a method to change format of localStorage strings when the extension updates.
The question is more on where (in the event page) to execute the method.
The method MUST run at update or the whole extension will not work anymore.
Currently I put it here:
chrome.runtime.onInstalled.addListener(function(runInfo) {
if (runInfo.reason=="install") {
}
if (runInfo.reason=="update") {
//HERE!!
}
});
It executes fine on extension update, that's what I want.
But it seems like if the extension is disabled when the update happens it will not run at all.
Then when the extension is enabled again, the user will deem it to be a bug.
I was hoping there is a chrome.runtime.onEnabled, but there is no such event.
There is chrome.management.onEnabled, but I don't want the event page to wake up for every other extension's enabling/disabling.
Thought of putting the method to run every time the event page wakes, by putting it in window.addEventListener("load", HERE);, but I'm afraid running it every time might slow down the real work that the event page was triggered to do.
Any suggestions?
Any devs have any better way they are using now?

How do I replace my angular location without a controller refresh?

Suppose I have an Angular app for editing eCards. Creating a new eCard uses a path like #/ecard/create and editing an existing eCard uses a path like #/ecard/:id. A tabbing system lets us have multiple eCards open for editing at a time.
We'd like an autosave feature like what users would expect from e.g. modern webmail or wiki software (or StackOverflow itself). We don't want to save an eCard draft the moment the user opens the Create form, which would give us a lot of drafts of blank eCards, so we start autosaving once the user starts typing.
I'd like to write code like this in our controller (this is simplified to not include e.g. error handling or stopping the autosave when the tab is closed, etc):
$scope.autosave = function () {
ECardService.autosave($scope.eCard).then(function (response) {
$location.path('/ecard/' + response.id).replace();
$timeout($scope.autosave, AUTOSAVE_INTERVAL);
});
};
$timeout($scope.autosave, AUTOSAVE_INTERVAL);
The above code works great, except for one thing: when the location changes, our controller reloads and the view re-renders. So if the user is in the middle of typing when the autosave completes, there's a brief flicker, and they lose their place.
I've considered several approaches to mitigate this problem:
1) Change the path to use the search path and set reloadOnSearch to false in the ngRoute configuration. So the path would change from #/ecard?id=create to e.g. #/ecard/id=123 and thus not force a reload. The problem is that I might have multiple eCards open and I do want changing from e.g. #/ecard/id=123 to #/ecard/id=321 to trigger a route change and reload the controller. So this isn't really feasible.
2) Don't bother editing the URL and deal with the back button giving a weird behavior in this case. This is tempting, but if a user opens their list of existing eCards and tries to open the specific eCard that has been saved, we want the tabbing system to recognize that it should just display the currently existing tab rather than open a new tab.
We could theoretically address this by updating our tabbing system to be smarter; instead of just checking the path, it could check both the path and the persistent id, which we could store somewhere. This would make the tabbing system significantly more complex, and that seems like overkill for this feature.
3) Only change the URL when the user is not actively editing, e.g. write a $scope.userIsIdle() function which returns true if it's been at least 10 seconds since the user made any edits, then update the path based on that. A simplified version of this would look something like:
$scope.updatePathWhenSafe = function (path) {
if ($scope.userIsIdle()) {
$location.path(path).replace();
} else {
$timeout(function () {
$scope.updatePathWhenSafe(path);
}, 1000);
}
};
I ended up going with option #3; it was significantly simpler than option #2, but a lot more complicated to implement and test than I'd like, especially once I account for edge cases such as "what if the tab is no longer the active tab when this timeout fires?" I'd love for option #4 to be possible.
4) Go outside Angular to edit the current location and history, assuming this is necessary and possible. This would be my preferred solution, but my research indicates it's not safe/advisable to try to go around the $location service for changing your path or editing history. Is there some safe way to do this? It would make things so much simpler if I could just say, "Change the current path but don't reload the controller."
Is option #4 possible/feasible? If not, then is there a better way? Maybe some magical "Do it the angular way but somehow don't refresh the controller"?
This is not angular way, but it can be useful. After receiving data you can check whether there is an focused element (user is typing). If so, then you need to define a function that is performed once when element lose focus. If no focused element, the change url immediately.
Like this:
ECardService.autosave($scope.eCard).then(function (response) {
if($(':focus').length){ //if there is focused element
$(':focus').one('blur', function(){ //
$location.path('/ecard/' + response.id).replace(); //perform once
});
}
else{
$location.path('/ecard/' + response.id).replace();
}
});
Of course this is not the most elegant solution, but it seems to solve your problem.
If you have code that needs to run across multiple view controllers AngularJS provides a root scope for such instances. You can find the documentation here.
However I would recommend against having a tabbing system that is actually multiple views. Having multiple items open means to have them all in your work space.
You might want to consider a single view with Angular directives for your e-cards. That way they could each have their own scope and would be available at an instance without re-rendering the page.
They would also be able to share the functions defined in the controller's $scope, without the need for an app wide root scope. Note that scope has to be enabled on directives. scope: true
Check out the AngularJS site for tutorial and documentation on this.
It seems that the best solution for the problem you're describing would be to use a state machine like ui-router.
With a library like that one, you can have a single page app that has multiple states (that you can also make part of the url), so whenever the state changes, you can save your e-card and you'll never have any visible reloads because you're working on a single page application.
So I understand the path wants to reflect the id of the latest version, so in that case you would need to refresh every save.
But, what about if the path was something like ecard/latest as a alias for the latest version. That way you wouldn't have to refresh your view since you don't have to change your path, and just implement something in the back-end directs the param latest to the id of the latest version.
It turns out there's a way to do exactly what I want, although it's not officially blessed by Angular. Someone opened an Angular ticket for this exact use case: https://github.com/angular/angular.js/issues/1699
The proposed change was submitted as a pull request and rejected: https://github.com/angular/angular.js/pull/2398
Based on the comments in the original ticket, I implemented a workaround that looks like this:
app.factory('patchLocationWithSkipReload', function ($location, $route, $rootScope) {
$location.skipReload = function () {
var prevRoute = $route.current;
var unregister = $rootScope.$on('$locationChangeSuccess', function () {
$route.current = prevRoute;
unregister();
});
return $location;
};
});
I'm then able to basically (error handling omitted for brevity) say
ECardService.autosave($scope.eCard).then(function (response) {
$location.skipReload().path('/ecard/' + response.id).replace();
$scope.resetAutosaveTimeout();
});
Basic testing shows this works great!

javascript set interval session management

I am in the midst of working on a project that is session-based. I was thinking that similar to a bank, I'd create a check and offer users the ability to thwart a session time-out.
I was thinking about creating a setInterval that would check a blank page that requires auth. If the timer is getting close to end-times, it'd throw up a dialogue advising the user that session is near end. A 'stay logged in' button would reset the timer, and they'd also be presented with an option to log off. If the set interval gets a '401' from the checker page, the current page would be refreshed to the login screen.
Does this make sense? Would the setInterval bog down the browser?
As an aside: what is the easiest way to clearInterval based on user interaction? Would I need to check for every keypress, and/or mouse event? Or is there a broader way of checking if the user is interacting with the page (like a focus check or something)?
Tanka.
So, I had some problems with the framework I'm using.. The session handling is not very good, therefore there seemed to be a problem with updating the session timout instead of having it timeout always based on the time since login, as opposed to since last activity. Anyway, got that handled. The issue I'm wondering about now is that by checking to see if the session is still authenticated via setInterval, the session will be updated via the check, therefore the session will never timeout. Or, am I missing something?
I decided to handle it just with javascript. Set the timeout to never in the framework config, and am handling timeouts with setTimeout exclusively.
function alerter(msg){
//warn user session about to expire; give opportunity to save
}
function killSess(){
window.location = '/logout';
}
function sessTimer(time){
timerID = window.setTimeout('killSess();',time);
}
function observe(div){
Event.observe(div, 'click', function(){
clearTimeout(timerID);
sessTimer(30000);
});
Event.observe('bodyDiv', 'keydown', function(e){
clearTimeout(timerID);
sessTimer(30000);
});
}
Does this make sense? Would the
setInterval bog down the browser?
This should work fine. So long as the interval is fairly large (a few seconds to a minute) and does not increase the size of a global data structure with each iteration, I don't anticipate it bogging down the browser.
As an aside: what is the easiest way
to clearInterval based on user
interaction? Would I need to check for
every keypress, and/or mouse event? Or
is there a broader way of checking if
the user is interacting with the page
(like a focus check or something)?
Maybe adding a few event handlers to a top-level page element such as a container div might be helpful.
It makes perfect sense, and setInterval wouldn't bog down the browser, as long as you make sure not to register it once more when it's already registered.
You only want to run clearInterval on the click of one specific button ("no, don't poll the browser", or "log out"), so i don't quite see the problem in your last paragraph...?
Other than that, I'll just add that upon 401, you shouldn't refresh to login screen. Just notify the user that the session seems to have been lost, so that the user can decide on his own to save anything he might be working with, or perhaps to log in again in a new tab.

Categories

Resources