In my backbone application, I am saving objects into local storage and only want to retrieve them once they are saved.
I tried using callback function (which fires after the function which saves data) but there is a bit of latency I observed and it returns undefined.
However, when I delay the function call (which retrieves the data) by 200 ms using setTimeout, it works just fine.
Is there an elegant way of doing it?
function delayed(){
// this callback function retrieves the data
callback.call(self);
}
window.setTimeout(delayed, 200);
So you can make a custom wrapper for this purpose:
(function() {
var Store = function() {
};
Store.prototype.set = function(key, value) {
localStorage.setItem(key, value);
return this.get(key);
};
Store.prototype.get = function(key) {
return localStorage.getItem(key);
};
var store = new Store();
console.log(store.set('foo', 'bar'));
})();
Fiddle
You could keep a duplicate outside of localStorage, in memory. You wouldn't need to rely localStorage's timing. Just write to localStorage often, and only load from it upon page load.
Just an idea! Without more specifics, it's hard to give a more concrete answer.
At first I thought of using the storage-event, but as you can see of this question - and this question, and this demonstration at html5demos.com, the use of the storage event is intended to track changes of the localstorage between windows / tabs, not inside the document itself.
But you can create your own event, firing when setItem is called by overriding setItem :
//create an "onstoragechange" custom event
var storageEvent = document.createEvent('Event');
storageEvent.initEvent('onstoragechanged', true, true);
document.addEventListener('onstoragechanged', function (e) {
alert('value added to localstorage');
//or
alert(localStorage.getItem('test'));
//call the code here, as you above would do after setTimeout
//"callback.call(self);" or whatever
}, false);
//override localStorage.setItem
var oldSetItem = Storage.prototype.setItem;
Storage.prototype.setItem = function() {
oldSetItem.apply(this, arguments);
document.dispatchEvent(storageEvent);
}
//test
localStorage.setItem('test', 'value');
demo / jsfiddle : http://jsfiddle.net/cYLHT/
Now you have an event that is dispatched each time you save anything to localStorage, and the written value actually is present. Expand this with events that helps out your application - like a special event if a certain important key is updated / stored. The above seems maybe as an "off topic" answer, or overkill, but I think it is a far better approach than spreading setTimeouts around the code.
Related
I am extending mxgraph delete control example to add delete like controls to nodes which are generated dynamically in my graph. The source code for the example is available here
The problem is in this part of the code -
// Overridden to add an additional control to the state at creation time
mxCellRendererCreateControl = mxCellRenderer.prototype.createControl;
mxCellRenderer.prototype.createControl = function(state)
{
mxCellRendererCreateControl.apply(this, arguments);
var graph = state.view.graph;
if (graph.getModel().isVertex(state.cell))
{
if (state.deleteControl == null)
mxCellRendererCreateControl.apply inside the overridden call back of createControl seems to work as intended (calls the original function before creating additional controls) with the initial state of the graph on load. But, once I add nodes dynamically to the graph and the callback is invoked by mxgraph's validate/redraw, the control goes into an infinite loop, where 'apply' function basically keeps calling itself (i.e, the callback).
I am a bit clueless because when I debug, the context(this) looks fine, but I can't figure out why instead of invoking the prototype method, it just keeps invoking the overridden function in a loop. What am I doing wrong?
It looks like you are not cloning your original function the right way, please try the following :
Function.prototype.clone = function() {
var that = this;
return function theClone() {
return that.apply(this, arguments);
};
};
Add that new method somewhere in your main code so it will available in the whole application, now you can change your code to :
// Overridden to add an additional control to the state at creation time
let mxCellRendererCreateControl = mxCellRenderer.prototype.createControl.clone();
mxCellRenderer.prototype.createControl = function(state) {
mxCellRendererCreateControl(state);
var graph = state.view.graph;
if (graph.getModel().isVertex(state.cell)) {
if (state.deleteControl == null) {
// ...
}
}
// ...
};
This should work if I understood your problem correctly, if it does not, please change the old function call back to the apply. Otherwise let me know if something different happened after the Function prototype change.
It seems that your overriding code is being called multiple times (adding a simple console.log before your overriding code should be enough to test this)
Try to ensure that the code that overrides the function only gets called once, or validate whether the prototype function is the original or yours.
Here is an example of how you can check if the function is yours or not
if (!mxCellRenderer.prototype.createControl.isOverridenByMe) {
let mxCellRendererCreateControl = mxCellRenderer.prototype.createControl;
mxCellRenderer.prototype.createControl = function(state) { /* ... */ };
mxCellRenderer.prototype.createControl.isOverridenByMe = true;
}
There are other ways, like using a global variable to check if you have overriden the method or not.
If this doesn't fix your issue, please post more about the rest of your code (how is this code being loaded/called would help a lot)
I'm a fairly experienced knockout user, so I understand quite a bit of the under the hood stuff, I have however been battling now for a few days trying to figure out how to achieve a given scenario.
I have to create a system that allows observable's within a given knockout component to be able to translate themselves to different languages.
to facilitate this, I've created a custom binding, which is applied to a given element in the following way.
<p data-bind="translatedText: {observable: translatedStringFour, translationToken: 'testUiTransFour'}"></p>
This is in turn attached to a property in my knockout component with a simple standard observable
private translatedStringFour: KnockoutObservable<string> = ko.observable<string>("I'm an untranslated string four....");
(YES, I am using typescript for the project, but TS/JS either I can work with.....)
With my custom binding I can still do 'translatedStringFour("foo")' and it will still update in exactly the same way as the normal text binding.
Where storing the translations in the HTML5 localStorage key/value store, and right at the beginning when our app is launched, there is another component that's responsible, for taking a list of translation ID's and requesting the translated strings from our app, based on the users chosen language.
These strings are then stored in localStorage using the translationToken (seen in the binding) as the key.
This means that when the page loads, and our custom bind fires, we can grab the translationToken off the binding, and interrogate localStorage to ask for the value to replace the untranslated string with, the code for our custom binding follows:
ko.bindingHandlers.translatedText = {
init: (element: HTMLElement, valueAccessor: Function, allBindings: KnockoutAllBindingsAccessor, viewModel: any, bindingContext: KnockoutBindingContext) => {
// Get our custom binding values
var value = valueAccessor();
var associatedObservable = value.observable;
var translationToken = value.translationToken;
},
update: (element: HTMLElement, valueAccessor: Function, allBindings: KnockoutAllBindingsAccessor, viewModel: any, bindingContext: KnockoutBindingContext) => {
// Get our custom binding values
var value = valueAccessor();
var associatedObservable = value.observable;
var translationToken = value.translationToken;
// Ask local storage if we have a token by that name
var translatedText = sessionStorage[translationToken];
// Check if our translated text is defined, if it's not then substitute it for a fixed string that will
// be seen in the UI (We should really not change this but this is for dev purposes so we can see whats missing)
if (undefined === translatedText) {
translatedText = "No Translation ID";
}
associatedObservable(translatedText);
ko.utils.setTextContent(element, associatedObservable());
}
}
Now, thus far this works brilliantly, as long as the full cache of translations has been loaded into localStorage, the observables will self translate with the correct strings as needed.
HOWEVER......
Because this translation loader may take more than a few seconds, and the initial page that it's loading on also needs to have some elements translated, the first time the page is loaded it is very possible that the translations the UI is asking for have not yet been loaded into into localStorage, or may be in the process of still loading.
Handling this is not a big deal, I'm performing the load using a promise, so the load takes place, my then clause fires, and I do something like
window.postMessage(...);
or
someElement.dispatchEvent(...);
or even (my favorite)
ko.postbox.publish(...)
The point here is I have no shortage of ways to raise an event/message of some description to notify the page and/or it's components that the translations have finished loading, and you are free to retry requesting them if you so wish.
HERE IN.... Lies my problem.
I need the event/message handler that receives this message to live inside the binding handler, so that the very act of me "binding" using our custom binding, will add the ability for this element to receive this event/message, and be able to retry.
This is not a problem for other pages in the application, because by the time the user has logged in, and all that jazz the translations will have loaded and be safely stored in local storage.
I'm more than happy to use post box (Absolutely awesome job by the way Ryan -- if your reading this.... it's an amazingly useful plugin, and should be built into the core IMHO) but, I intend to wrap this binding in a stand alone class which I'll then just load with requireJs as needed, by those components that need it. I cannot however guarantee that postbox will be loaded before or even at the same instant the binding is loaded.
Every other approach i've tried to get an event listener working in the binding have just gotten ignored, no errors or anything, they just don't fire.
I've tried using the postmessage api, I've tried using a custom event, I've even tried abusing JQuery, and all to no avail.
I've scoured the KO source code, specifically the event binding, and the closest I've come to attaching an event in the init handler is as follows:
init: (element: HTMLElement, valueAccessor: Function, allBindings: KnockoutAllBindingsAccessor, viewModel: any, bindingContext: KnockoutBindingContext) => {
// Get our custom binding values
var value = valueAccessor();
var associatedObservable = value.observable;
var translationToken = value.translationToken;
// Set up an event handler that will respond to events on session storage, by doing this
// the custom binding will instantly update when a key matching it's translation ID is loaded into the
// local session store
//ko.utils.registerEventHandler(element, 'storage', (event) => {
// console.log("Storage event");
// console.log(event);
//});
ko.utils.registerEventHandler(element, 'customEvent', (event) => {
console.log("HTML5 custom event recieved in the binding handler.");
console.log(event);
});
},
None of this has worked, so folks of the Knockout community.....
How do I add an event handler inside of a custom binding, that I can then trigger from outside that binding, but without depending on anything other than Knockout core and my binding being loaded.
Shawty
Update (About an hour later)
I wanted to add this part, beacuse it's not 100% clear why Regis's answer solves my problem.
Effectively, I was using exactly the same method, BUT (and this is the crucial part) I was targeting the "element" that came in as part of the binding.
This is my mind was the correct approach, as I wanted the event to stick specifically with the element the binding was applied too, as it was said element that I wanted to re-try it's translation once it knew it had the go-ahead.
However, after looking at Regis's code, and comparing it to mine, I noticed he was attaching his event handlers to the "Window" object, and not the "Element".
Following up on this, I too changed my code to use the window object, and everything I'd been attempting started to work.
More's the point, the element specific targeting works too, so I get the actual event, on the actual element, in the actual binding that needs to re-try it's translation.
[EDIT: trying to better answer the question]
I don't really get the whole point of the question, since I don't see how sessionStorage load can be asynchronous.
I supposed therefore sessionStorage is populated from som asynchronous functions like an ajax call to a translation API.
But I don't see what blocks you here, since you already have all the code in your question:
var sessionStorageMock = { // mandatory to mock in code snippets: initially empty
};
var counter = 0;
var attemptTranslation = function() {
setInterval(function() { // let's say it performs some AJAX calls which result is cached in the sessionStorage
var token = "token"; // that should be a collection
sessionStorageMock[token] = "after translation " + (counter++); // we're done, notifying event handlers
window.dispatchEvent(new Event("translation-" + token));
}, 500);
};
ko.bindingHandlers.translated = {
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
var val = valueAccessor();
var token = val.token;
console.log("init");
window.addEventListener("translation-" + token, function() {
if (token && sessionStorageMock[token]) {
val.observable(sessionStorageMock[token]);
}
});
}
};
var vm = function() {
this.aftertranslation = ko.observable("before translation");
};
ko.applyBindings(new vm());
attemptTranslation();
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div data-bind="translated: { observable: aftertranslation, token: 'token' }, text: aftertranslation" />
I am wondering if there is a way to execute a JavaScript function once only on the first ever page load and then not execute on any subsequent reloads.
Is there a way I can go about doing this?
The code below will execute once the onload event fires. The statement checks if the onetime function has NOT been executed before by making use of a flag (hasCodeRunBefore), which is then stored in localStorage.
window.onload = function () {
if (localStorage.getItem("hasCodeRunBefore") === null) {
/** Your code here. **/
localStorage.setItem("hasCodeRunBefore", true);
}
}
Note: If the user clears their browsers' localStorage by any means, then the function will run again because the flag (hasCodeRunBefore) will have been removed.
Good news...
Using localStorage can be tedious because of operators and long winded function names. I created a basic module to simplify this, so the above code would be replaced with:
window.onload = function () {
if (!ls.exists('has_code_run_before')) {
/** Your code here... **/
ls.set.single('has_code_run_before', true);
/** or... you can use a callback. **/
ls.set.single('has_code_run_before', true, function () {
/** Your code here... **/
});
}
};
Update #1
Per #PatrickRoberts comment, you can use the in operator to see if a variable key exists in localStorage, so
if (localStorage.getItem('hasCodeRunBefore') === null)
becomes
if (!('hasCodeRunBefore' in localStorage))
and is less verbose and a lot cleaner.
Secondly, you can set values as you would an object (localStorage.hasCodeRunBefore = true) though it will be stored as a string, not as boolean (or whatever data type your value is).
function toBeExecutedOnFirstLoad(){
// ...
}
if(localStorage.getItem('first') === null){
toBeExecutedOnFirstLoad();
localStorage.setItem('first','nope!');
}
All JavaScript must execute every time a page loads. If the script is on the page, it will execute.
The logic that is executed within the JavaScript included on the page may execute in a different manner depending on the page state, input provided, and any other signals it receives, be it from the server or the client.
If you're using a server side language, you might choose to render a script conditionally, such as the first time a user logs in.
If you need to include the javascript irrespective of context, then you need to listen to other signals.
The simple modern solution is to make use of localStorage. localStorage can be used to store custom string values on custom key values for any given domain.
The code to make use of this would look like:
if (localStorage['...my key here...'] === '...my expected value here...') {
// The page has been visited before
} else {
// The page has not been visited before
// OR
// The user or script has cleared the localStorage value
}
localStorage['...my key here...'] = '...my expected value here...';
That's all well and good if you just need things to work on the client alone. Sometimes you might need the server to know whether or not the page has been visited before.
The (less)simple solution is to use document.cookie:
if (/(?:^|;\s*)...my key here...=...my expected value here...(?:;|$)/.test(document.cookie)) {
// the page has been visited before
} else {
// The page has not been visited before
// OR
// The user or script has cleared the cookie
}
document.cookie = '...my key here...=...my expected value here...';
If you need to defer the execution until the page has finished loading, then simply include the script in an onload callback, either by assigning the event to the window:
window.onload = function () {
// do stuff when the page has loaded
//this will override any other scripts that may have been assigned to .onload
};
or by binding the event handler to the window:
window.addEventListener('load', function () {
// do stuff when the page has loaded
}, false);
It depends on what first page load means to you. It's subjective.
If you want the function to fire once the DOM has been parsed, but only the HTML and no other external resources, bind it to the DOMContentLoaded event.
document.addEventListener('DOMContentLoaded', fn);
Otherwise, if you want to wait for external resources to be loaded and then fire the event, you should bind it to the window object's load event like so:
window.addEventListener('load', fn);
Here are some links from the Mozilla Developer Network that explain the what I just said in more detail:
https://developer.mozilla.org/en-US/docs/Web/Events/load
https://developer.mozilla.org/en-US/docs/Web/Events/DOMContentLoaded
Good luck!
I was facing something similar, the difference in my case was, I wanted to run a code whenever a new instance was being created I needed a certain code to execute, and then later for the rest of the reloads that code should not execute.
For that similar to localStorage solutions above use session storage instead:
fun_RunOnlyOnFirstPageLoad(){}
if(!$window.sessionStorage.getItem(hasRunBefore)){
fun_RunOnlyOnFirstPageLoad();
$window.sessionStorage.setItem(hasRunBefore, true);
}
using the window.sessionStorage instead stores the value only for that session.
this way once the tab is closed (session is over, that value is wiped out) and on every new instantiation, the code is executed.
I have a problem. I am trying to get the source of an audio element as soon as it changes and without making it connect to the server. Let me elaborate: The server allows only one request (randomly generated url for content), which I am trying to get and obviously I don't want the audio element to reload using a new source. I am currently using a mutation observer, but with no luck of beating the onclick's speed (yes, there's complicated js involved in order to not allow anybody to get the source directly). Here's the code:
document.getElementsByTagName('audio')[0].setAttribute("autoplay", false);
//not sure if ^^ makes a difference
audioObserver = new MutationObserver(function (mutations) {
window.open(mutations[0].target.src);
//I only listen for one mutation and want to open the url in a new window
audioObserver.disconnect();
//don't listen in order to not cause infinite loop
document.getElementsByTagName('audio')[0].parentNode.removeChild(document.getElementsByTagName('audio')[0]);
//remove the element in order to avoid reloading
});
observerConfig = {
attributes: true,
childList: false,
characterData: false
};
audioObserver.observe(document.getElementsByTagName('audio')[0], observerConfig);
I would be happy with any implementation i.e. not necessarily MutationObserver. Is there anyway to literally hijack the attribute change?
You're right that MutationObserver can't do what you need. The way those work, browsers collect mutation records while a script runs, then passes the records to the observers all at once when the script yields. It's pretty much asynchronous. This is what makes mutation observers perform better than mutation events, which are dispatched synchronously. By the time the observer runs, the audio element will have received the URL and have started loading it.
Supposing that this onclick handler you're talking about sets the element's .src property, you'll want to define a custom setter for that, so that you can intercept the value before the audio element's implementation handles it.
You can define a setter like this in JavaScript: http://jsfiddle.net/omqdx8d1/
var el = document.getElementsByTagName('audio')[0];
Object.defineProperty(el, 'src', {
set: function (newSrc) {
console.log('they set src to ' + newSrc);
}
});
In Chrome, this will make it impossible to call the original setter afterward, but it sounds like you're not worried about that.
Another way to modify an element's attributes is to use the setAttribute method. If you want to intercept this, you can redefine the element's setAttribute method.
Here's another example that uses setAttribute: http://jsfiddle.net/5mLysc9n/1/
var el = document.getElementsByTagName('audio')[0];
var origSetAttribute = el.setAttribute;
el.setAttribute = function (name, value) {
console.log('they set ' + name + ' to ' + value);
// origSetAttribute.call(el, name, value);
};
When you monkey patch a method, you can save the original method function, which can be useful.
Let's say I have a page that loads pages dynamically. As each page loads into the DOM, events for the elements in that page are added.
If the user loads another page, the elements loaded previously will be removed from the DOM. Naturally, because the elements themselves no longer exist, any events mapped to those elements cease to function.
However, are they also removed? Or are they sitting in the user's memory, taking up space?
Follow-up:
Were a function defined as such:
var event = $('foobar').addEvent('click', function() {
alert(1);
});
One could easily remove the event with event = null (or so I'd assume!)...
but what if the event were not saved to a local variable?
$('foobar').addEvent('click', function() {
alert(1);
});
Thanks!
first of all. what? this makes no sense:
var event = $('foobar').addEvent('click', function() {
alert(1);
});
it does not save the event into a local variable as you seem to think. it saves a reference to the foobar element object into the event variable - most mootools element methods will return this for chaining, which is the element itself and not the result of the method (unless it's a getter like '.getStyle').
it then depends on how you get rid of the element what happens next. first off, element.destroy, found here: https://github.com/mootools/mootools-core/blob/master/Source/Element/Element.js#L728
it will remove the element from the dom and from memory, and empty it in a safe way. it will be reliant on the browser's GC to clean up once it's gone, mootools won't do any spectacular GC for you for the element itself but it does run the special clean function on the child nodes as well: var children = clean(this).getElementsByTagName('*');.
the clean method also gets rid of any event handlers and storage attached to the child elements of the div.
THEN. events added by mootools go into element storage. Element storage is in an object behind a closure which the element proto uses. To test it, we will re-implement it and make it puncturable (a global object called storage) so we can check what happens to the reference after the parent is gone:
http://jsfiddle.net/dimitar/DQ8JU/
(function() {
var storage = this.storage = {}; // make it puncturable
var get = function(uid){
return (storage[uid] || (storage[uid] = {}));
};
Element.implement({
retrieve: function(property, dflt){
var storage = get($uid(this)), prop = storage[property];
if (dflt != null && prop == null) prop = storage[property] = dflt;
return prop != null ? prop : null;
},
store: function(property, value){
var storage = get($uid(this));
storage[property] = value;
return this;
},
eliminate: function(property){
var storage = get($uid(this));
delete storage[property];
return this;
}
});
})();
// read it.
var link = document.getElement("a");
var uid = link.uid; // will reference the mootools unique id for it
// add an event handler
link.addEvent("click", function(e) {
console.log("hi");
this.destroy();
// see what's left in storage for that element.
console.log(storage[uid]);
// storage should be empty.
console.log(storage);
});
link.getFirst().addEvent("mouseenter", function() {
console.log("over");
});
// check to see if it is there via element storage API.
console.log(link.retrieve("events").click);
// check to see if it's there via our puncture
console.log(this.storage[uid]);
// see all events in storage, inc child element:
console.info(this.storage);
what all this proves is, mootools cleans up all you need cleaned. as long as you don't use any inline onclick= stuff on elements you work with, you're going to be fine. Between mootools' garbage collection and the browser, you are well covered. just be aware you can stack up multiple events on a single element if the callbacks are anonymous.
Interesting question... have a read of this: https://developer.mozilla.org/en/DOM/element.addEventListener#Memory_issues
To remove the event listener using jQuery, see http://api.jquery.com/unbind/
I have found that older versions of IE seems to have issues with adding and removing lots of elements with events binded to them. The main cause is circular references that cannot be garbage collected. You can find more information here: http://msdn.microsoft.com/en-us/library/Bb250448
Setting to null will not remove the event, it would just remove the reference to the event. You need to use both element.removeEventListener and element.detachEvent (depending on browser), or if you are using jquery unbind should work.
Also, there are tools available to detect leaks, this one works well (according to coworker): http://blogs.msdn.com/b/gpde/archive/2009/08/03/javascript-memory-leak-detector-v2.aspx