From https://developers.google.com/web/fundamentals/app-install-banners/#trigger-m68
let deferredPrompt;
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
// Stash the event so it can be triggered later.
deferredPrompt = e;
});
This code is fine, but I want to trigger the stashed event later, in a different place. To perform that, I need to store an event not just in a variable, but somewhere else.
The question: how can an event be stored with its methods?
I tried Local Storage with serialization/deserialization of an object:
> localStorage.setItem('stashed-event', JSON.stringify(e))
>
> JSON.parse(localStorage.getItem('stashed-event'))
But this approach doesn't work as expected, because it's storing only key-values and losing all event methods.
You cannot store an event in this manner. You want to store an object. Only serializable properties are storable for such an object. Functions are not serializable in JavaScript. Functions are not serializable in many languages.
Fundamentally this is basically because when you deserialize an object, its signature can change. If you have ever programmed in java, this is similar to a deserialization error when reading in a serialized object and attempting to reconstruct an object. Because the body of a method function of an object can change in between the time the object is written to some storage and then later read, methods are not serializable. This is because when you serialize an object, it does not serialize its interface definition where methods are defined. It just stores data.
Same reason when you serialize to a json string, it drops the functions.
Instead of storing an event, store the useful information from the event in an object (or let things be implicitly dropped by stringify and use the event directly).
Which method of storage you use just depends on things not mentioned in your question. Such as how long it should be stored, whether it should be available outside of your site's origin, how much data will typically be stored, whether there is more than one object to store, etc. Based on the limited information provided in your question, you are probably fine just using either localStorage or an in memory array.
If you find the need to store hundreds of objects then indexedDB would begin to be more appropriate. But just choosing a different storage medium will have no effect whatsoever on whether you can store functions. You cannot store functions.
There have been loads of talk around this as soon as I/O 2018 mentioned about handling of A2HS event being developer driven from now onwards. This is also captured in the official doc and inspired from it, there is a beautiful article explaining thoroughly how to achieve exactly this scenario. While I'd suggest to go through the complete article for proper understanding of the updated dynamics around the A2HS flow, feel free to jump onto the "The New Add To Homescreen Flow" section for your requirement.
In a nutshell, follow the following steps:
Create a variable outside the scope of the beforeinstallprompt event handler.
Save a reference to the beforeinstallprompt event object in the above handler.
Use this later to trigger the add to homescreen prompt on demand.
The article have the complete code snippets which you can refer/reuse.
Edit: I read your question once again and realized one important aspect you might be specifically looking for, viz., using it "somewhere else". If this means you are referring to using it on a different page, then my suggestion would be to go for storing the event object in:
IndexedDB which is a collection of "object stores" which you can just drop objects into. Disadvantage - Can have browser compatibility restrictions. Also, can result in large amount of nested callbacks.
Or you can choose to use the "in process cache" (heap memory of your application) which doesn't require serializing either. Disadvantage - This cannot be shared across multiple servers though.
Other than this, I cannot foresee a con free solution at the moment. But will try to figure it out and possibly update the thread.
After reading your question a few times, and the answers another few,
The question: how can any javascript Object be stored with its methods?
The answer: there is no how.
However,
Josh properly explained you can extract and store all the serializable properties, say data, from your event.
I will just add you can create an event with somehow that same data later anywhere, this new event will have all the methods any Event has, but by now probably none of use.
Obviously, even serialized in your data, properties like timeStamp, isTrusted, etc... will be overriden at creating the new event.
What you just miss / need is an EventTarget, the value of the event.target property,
the reference which is lost forever when document.body unloads forever, or when serializing the event Object.
But if it is still alive, or if you know what event.target should be, like a DOM Element or any Object you can reference, from wherever you recreate the event (where?), just dispatch your event to that object, if it listens to that event.type,
your brand new event should be at least heard.
Simple example from MDN EventTarget, or see EventTarget.dispatchEvent
As a comment over the extensive answer by cegfault: eval, and text source code... could be <script> text source code </script>... should your script produces a (String) script. If not you ´d probably better go further backwards to where did your script creates the unserializable things that appear in your event, and think about recreating those things, not the event.
TL;DR to accomplish what you are doing, you have three options:
Store a reference to the event in a global value (which is what most tutorials - like your referenced youtube video - will recommend you do). This requires the event to run in the same context (ie web page) as when you store the reference
When you store the reference to the event in localStorage (such as by name or a key/value look up), on the page/context where you want to execute the event, make sure the appropriate functions and libraries are loaded before executing the event
[strongly NOT recommended] Store the javascript source code in your storate and eval() it later [again, please don't do this]
As mentioned by #Josh and #SaurabhRajpal, what you are asking for, strictly speaking, is not possible in JavaScript. What you are doing with JSON.stringify(e) will probably return undefined or null, as the MDN documentation for JSON.stringify says:
If undefined, a Function, or a Symbol is encountered during conversion it is either omitted (when it is found in an object) or censored to null (when it is found in an array). JSON.stringify can also just return undefined when passing in "pure" values like JSON.stringify(function(){}) or JSON.stringify(undefined).
In short, there is no way to store a single function into localStorage (or any other offline storage). To explain why this is not possible, see this example:
function foo() {
console.log("a")
}
function bar() {
return foo()
}
How can you store bar() for later usage? In order to store bar, you would also have to store foo(). This becomes much more complicated when you consider referencing a function which is in, or uses, a large library (like jQuery, underscore, D3, charting libraries, etc). Keep in mind your computer has already parsed the source code down into binary, and as such won't easily know how to read the function for every possible if, for, and switch statements to ensure all possible correlated functions and libraries are saved.
If you really wanted to do this, you would have to write your own javascript parser, and you really don't want to do that!
So what are your options? First, do everything on the same page, and store the reference to the event in a global value (the youtube video you link to in a comment is using this method).
Your second option is to use a reference to the event (not the event itself), and make sure the source code for that reference is use later. For (html) example:
// on page #1
<script src="path/to/my/js/library.js"></script>
...
<script>
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault()
localStorage.setItem('stashed-event', 'before-install')
})
</script>
// later, on page #2:
<script src="path/to/my/js/library.js"></script>
...
<script>
var evt = localStorage.setItem('stashed-event', 'before-install')
if(evt == 'before-install') {
dosomething() // which would be a function in path/to/my/js/library.js
}
// another option here would be to define window listeners for all possible events
// in your library.js file, and then simply build and trigger the event here. for
// more about this, see: this link:
// https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events
</script>
Finally, you can store javascript source code and then eval() it later. Please, please, please do NOT do this. It's bad practice and can lead to very evil things. But, if you insist:
// on page #1
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault()
SomeAjaxFunction("path/to/my/js/library.js", function(responseText) {
localStorage.setItem('stashed-event', {
name: 'before-install',
call: 'beforeInstallFunction()',
src: responseText
})
})
})
// later, on page #2:
var evt = localStorage.setItem('stashed-event', 'before-install')
if(evt) {
console.log("triggering event " + evt.name)
eval(evt.src)
eval(evt.call)
}
Like I said, this is a really bad idea, but it's an option.
IMHO, I think you're trying to avoid including a library or source code in a later page/app/whatever, and javascript just does not work this way. It's best to pass around references in-memory, and only use key/value storage for names. Everything else is a type of coding gymnastics to avoid simply including your source code in the places it needs ot be included.
You can create a global constant and update it when ever event changes rather than serializing it and de-serializing which is a costly processes. SO this is how you can do it - You can create a window instance and clone the event in the window object so that it wont mutate.(Note this wont won't work across tabs)
window.addEventListener('click', (e) => {
e.preventDefault();
window.deferredPrompt = Object.assign(e);//Don't mutate
});
let someOtherMethod = ()=>{
console.log(window.deferredPrompt)
}
window.setInterval(someOtherMethod, 5000);
Try clicking after 5 seconds in the last window and check after 5 seconds
Here is a simple but successful solution.
The idea is to capture the event in a variable and only fire it when signaled by another window of the same origin (domain etc).
The solution uses localStorage methods as the signaling semaphore.
Here is the code I used. I have tested it successfully in Chrome, both mobile & desktop.
//In event handling window
//Register the ServiceWorker
if('serviceWorker' in navigator) {
navigator.serviceWorker.register('sw.js');
};
//Capture beforeInstall event
window.addEventListener('beforeinstallprompt', function(event){
event.preventDefault();
window.deferredPrompt = event;
return false;
})
//Wait for signal
window.onstorage = event => {
if (event.key === 'installprompt') {
//Fire the event when signaled.
window.deferredPrompt.prompt();
// Discard event
window.deferredPrompt = null;
//Discard storage item
localStorage.removeItem('installprompt');
}
}
//In a different window or tab from the same origin fire the event when ready.
localStorage.setItem('installprompt', 'whatever');
Please see if this helps.
Defining an event listener for 'beforeinstallprompt' event
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
//do all the stufff
console.log('Event triggered');
});
When you want to dispatch the event manually.
Create a new event variable.
var myevent = new Event('beforeinstallprompt');
window.dispatchEvent(myevent);
Outputs 'Event triggered' in the console.
Related
I'm working on replacing an Ext.data.Store load event handler.
The variable me is different every time within the code block but me.store is the same (obtained via StoreManager.lookup). I want the store event listener to update the various me references. Best way i could find was to add another listener (and delete the old one since i don't need it anymore)
I haven't been able to use un / removeListener i.e. it had not effect.
I've found that i could replace the it by accesing the me.store.events and popping the listener from the load event. However this feels hacky and it might make the code dependant on a specific ExtJS version (4.2) since i don't know if it's a private property or not.
Also me.store.hasListeners['load'] doesn't get notified so it only helps because it removes the actual listener but not in the intended manner. The docs don't mention it, but i'm wondering if it may be an inherited property which can be accessed freely.
Are there any alterntives to the working approach i've come to? Can i remove all event handlers for an event without having a reference to the handler? Or is there a simpler approach i'm missing?
var me = this; // an enriched Ext.form.FormPanel, different every time code runs
me.store //obtained via StoreManger.lookup - so the same every time
me.storeLoaded = function (store, records,successful, opts) {
// some code to select a record from records and use it
me.loadRecord(record);
}
};
if (!me.store.hasListener('load')) {
me.store.on('load', me.storeLoaded);
} else{
//tried this, but it doesn't remove it, probably because me.storeLoaded is different each time (parentForm is different)
me.store.un('load', me.storeLoaded);
//this feels hacky, i couldn't find out if events is a private property
if (me.store.events && me.store.events['load']){
me.store.events['load'].listeners.pop()
}
me.store.on('load', me.storeLoaded);
}
The easiest way to implement adding/removing listeners is using the destroyable parameter as described in the addListener function. That way, you can always be sure which one is removed.
Example:
setActive:function(cmp) {
cmp.myActiveListeners = cmp.eventStore.on({
destroyable: true,
load:cmp.refreshStores,
filterchange:cmp.refreshStores,
scope:cmp
});
},
setInactive:function(cmp) {
Ext.destroy(cmp.myActiveListeners);
},
I cannot recommend to blindly remove ALL listeners, since they may be added by other components (e.g. combobox) that you add later. To track down these bugs will grow you quite some gray hairs.
I was able to find an answer in this article ExtJS overwrite listener:
Sometimes you need to overwrite an event listener in ExtJS. Usually
listeners are registered like this myStore.on('load',
this.myFunction, this); then to remove our previously registered
listener, all we have to do is call un (which is an alias for
removeListener): myStore.un('load', this.myFunction, this);
But, what happens when you don't know what function is registered?
Sometimes you will not have a reference to the original function that
was registered. This situation may arise if there is code that exists
in a different flow or may even come as a package! If that is true,
the you may not be able to get a reference to the javascript function
or edit the existing code. In this case, we will have to look at all
of the functions that are registered for this event. We can then
remove the listeners just for a certain event by calling
clearListeners.
clearListeners was the method i was looking for.
It would seem he uses the events property so i assume it is a valid use. It could be translated in my case to:
me.store.events.load.clearListeners()
However since i will only be using the load event on this particular store, i will simply call on them all.
me.store.clearListeners()
Thanks to Alexander, by suggesting not to remove all listeners that actually helped me find the article. However i will stil go with his solution, even if it polutes the store object because i like it better than clearing all listeners on a store, even if only for a specific event.
jQuery holds references to DOM nodes in its internal cache until I explicitly call $.remove(). If I use a framework such as React which removes DOM nodes on its own (using native DOM element APIs), how do I clean up jQuery's mem cache?
I'm designing a fairly large app using React. For those unfamiliar, React will tear down the DOM and rebuild as needed based on its own "shadow" DOM representation. The part works great with no memory leaks.
Flash forward, we decided to use a jQuery plugin. After React runs through its render loop and builds the DOM, we initialize the plugin which causes jQuery to hold a reference to the corresponding DOM nodes. Later, the user changes tabs on the page and React removes those DOM elements. Unfortunately, because React doesn't use jQuery's $.remove() method, jQuery maintains the reference to those DOM elements and the garbage collector never clears them.
Is there a way I can tell jQuery to flush its cache, or better yet, to not cache at all? I would love to still be able to leverage jQuery for its plugins and cross-browser goodness.
jQuery keeps track of the events and other kind of data via the internal API jQuery._data() however due to this method is internal, it has no official support.
The internal method have the following signature:
jQuery._data( DOMElement, data)
Thus, for example we are going to retrieve all event handlers attached to an Element (via jQuery):
var allEvents = jQuery._data( document, 'events');
This returns and Object containing the event type as key, and an array of event handlers as the value.
Now if you want to get all event handlers of a specific type, we can write as follow:
var clickHandlers = (jQuery._data(document, 'events') || {}).click;
This returns an Array of the "click" event handlers or undefined if the specified event is not bound to the Element.
And why I speak about this method? Because it allow us tracking down the event delegation and the event listeners attached directly, so that we can find out if an event handler is bound several times to the same Element, resulting in memory leaks.
But if you also want a similar functionality without jQuery, you can achieve it with the method getEventHandlers
Take a look at this useful articles:
getEventHandlers
getEventListeners - chrome
getEventListeners - firebug
Debugging
We are going to write a simple function that prints the event handlers and its namespace (if it was specified)
function writeEventHandlers (dom, event) {
jQuery._data(dom, 'events')[event].forEach(function (item) {
console.info(new Array(40).join("-"));
console.log("%cnamespace: " + item.namespace, "color:orangered");
console.log(item.handler.toString());
});
}
Using this function is quite easy:
writeEventHandlers(window, "resize");
I wrote some utilities that allow us keep tracking of the events bound to DOM Elements
Gist: Get all event handlers of an Element
And if you care about performance, you will find useful the following links:
Leaking Memory in Single Page Apps
Writing Fast, Memory-Efficient JavaScript
JavaScript Memory Profiling
I encourage anybody who reads this post, to pay attention to memory allocation in our code, I learn the performance problems ocurrs because of three important things:
Memory
Memory
And yes, Memory.
Events: good practices
It is a good idea create named functions in order to bind and unbind event handlers from DOM elements.
If you are creating DOM elements dynamically, and for example, adding handlers to some events, you could consider using event delegation instead of keep bounding event listeners directly to each element, that way, a parent of dynamically added elements will handle the event. Also if you are using jQuery, you can namespace the events ;)
//the worse!
$(".my-elements").click(function(){});
//not good, anonymous function can not be unbinded
$(".my-element").on("click", function(){});
//better, named function can be unbinded
$(".my-element").on("click", onClickHandler);
$(".my-element").off("click", onClickHandler);
//delegate! it is bound just one time to a parent element
$("#wrapper").on("click.nsFeature", ".my-elements", onClickMyElement);
//ensure the event handler is not bound several times
$("#wrapper")
.off(".nsFeature1 .nsFeature2") //unbind event handlers by namespace
.on("click.nsFeature1", ".show-popup", onShowPopup)
.on("click.nsFeature2", ".show-tooltip", onShowTooltip);
Circular references
Although circular references are not a problem anymore for those browsers that implement the Mark-and-sweep algorithm in their Garbage Collector, it is not a wise practice using that kind of objects if we are interchanging data, because is not possible (for now) serialize to JSON, but in future releases, it will be possible due to a new algorithm that handles that kind of objects. Let's see an example:
var o1 = {};
o2 = {};
o1.a = o2; // o1 references o2
o2.a = o1; // o2 references o1
//now we try to serialize to JSON
var json = JSON.stringify(o1);
//we get:"Uncaught TypeError: Converting circular structure to JSON"
Now let's try with this other example
var freeman = {
name: "Gordon Freeman",
friends: ["Barney Calhoun"]
};
var david = {
name: "David Rivera",
friends: ["John Carmack"]
};
//we create a circular reference
freeman.friends.push(david); //freeman references david
david.friends.push(freeman); //david references freeman
//now we try to serialize to JSON
var json = JSON.stringify(freeman);
//we get:"Uncaught TypeError: Converting circular structure to JSON"
PD: This article is about Cloning Objects in JavaScript. Also this gist contain demos about cloning objects with circular references: clone.js
Reusing objects
Let's follow some of the programming principles, DRY (Don't Repeat Yourself) and instead of creating new objects with similar functionality, we can abstract them in a fancy way. In this example I will going to reuse an event handler (again with events)
//the usual way
function onShowContainer(e) {
$("#container").show();
}
function onHideContainer(e) {
$("#container").hide();
}
$("#btn1").on("click.btn1", onShowContainer);
$("#btn2").on("click.btn2", onHideContainer);
//the good way, passing data to events
function onToggleContainer(e) {
$("#container").toggle(e.data.show);
}
$("#btn1").on("click.btn1", { show: true }, onToggleContainer);
$("#btn2").on("click.btn2", { show: false }, onToggleContainer);
And there are a lot of ways to improve our code, having an impact on performance, and preventing memory leaks. In this post I spoke mainly about events, but there are other ways that can produce memory leaks. I suggest read the articles posted before.
Happy reading and happy coding!
If your plugin exposes a method to programatically destroy one of its instances (i.e. $(element).plugin('destroy')), you should be calling that in the componentWillUnmount lifecycle of your component.
componentWillUnmount is called right before your component is unmounted from the DOM, it's the right place to clean up all external references / event listeners / dom elements your component might have created during its lifetime.
var MyComponent = React.createClass({
componentDidMount() {
$(React.findDOMNode(this.refs.jqueryPluginContainer)).plugin();
},
componentWillUnmount() {
$(React.findDOMNode(this.refs.jqueryPluginContainer)).plugin('destroy');
},
render() {
return <div ref="jqueryPluginContainer" />;
},
});
If your plugin doesn't expose a way to clean up after itself, this article lists a few ways in which you can try to dereference a poorly thought out plugin.
However, if you are creating DOM elements with jQuery from within your React component, then you are doing something seriously wrong: you should almost never need jQuery when working with React, since it already abstracts away all the pain points of working with the DOM.
I'd also be wary of using refs. There are only few use cases where refs are really needed, and those usually involve integration with third-party libraries that manipulate/read from the DOM.
If your component conditionally renders the element affected by your jQuery plugin, you can use callback refs to listen to its mount/unmount events.
The previous code would become:
var MyComponent = React.createClass({
handlePluginContainerLifecycle(component) {
if (component) {
// plugin container mounted
this.pluginContainerNode = React.findDOMNode(component);
$(this.pluginContainerNode).plugin();
} else {
// plugin container unmounted
$(this.pluginContainerNode).plugin('destroy');
}
},
render() {
return (
<div>
{Math.random() > 0.5 &&
// conditionally render the element
<div ref={this.handlePluginContainerLifecycle} />
}
</div>
);
},
});
How about do this when the user exits the tab:
for (x in window) {
delete x;
}
This is much better to do, though:
for (i in $) {
delete i;
}
So here' s the piece of code. I'm very new to JavaScript so don't be afraid to explain the obvious
$(".my-css-class").on("click", function() {
($(this).attr("data-property-1"), $(this).attr("data-property-2"), this);
});
There's an element in the .jsp page that looks like this:
<i class="clickMe"></i>
I know the .jsp creates a link-icon, and that the above JavaScript is an event handler. I know that it passes these 3 values as arguments another JavaScript method:
function doStuff(prop1, prop2, obj) {
if (prop1 == 'foo') {
//do stuff with prop2
}
else{
// do stuff with obj
}
}
It all works fine. What I want to know is what exactly is going on to make it work? I can't find anything in the code that connects what the event-handler returns to the 'doStuff' java-script function.
The names are totally different, so it's not reflection, it can't be parameter matching because there's other functions with the same number and type of parameters in the file, it can't be convention based because it still works if I find/replace the name of the function to gibberish.
I guess basically I'm asking what this line is doing:
($(this).attr("data-property-1"), $(this).attr("data-property-2"), this);
tl;dr: I'm at a loss, I know how the properties get as far as the onClick event-handler's anonymous function - but how does JavaScript know to pass them as arguments the to the doStuff() function?
the onClick event is a standard event triggered on click of any clickable html element and is automatically raised by the DOM.
You are hooking in to this by listening on any matched ".my-css-class" elements for an onClick Event.
The jquery syntax ".on" has been simplified over time and allows you to hook into any number of events like "submit" - OnSubmit event , or "load" - onLoad Event
Wherever your on("click", myFunction) event hook is picked up, your myFunction will execute.
Looking at your second point...
because it still works if I find/replace the name of the function to gibberish.
The DoStuff function will be found and replaced across all files in your site? or page? or open tabs? , so therefore it must exist somewhere as "doStuff(" or "giberish(".
so when you do a global find/replace, do each one slowly, until you locate it.
Finally, when you do a view source in the browser, this should either explicitly show you the doStuff function, or at the very least give you a clue as to satelite files loaded at runtime, where you can go and investigate.
Use firebug in firefox to debug loaded resources; the ".net tab" to view external loaded resources and the html/javascript they might contain. (for example: your master page might be loading in an embeded resource that contains the doStuff method, becuase of a user or server control reference in that master page)
Also have a look at this:
http://www.developerfusion.com/article/139949/debugging-javascript-with-firebug/
You can step through the javascipt piece by peice until it hits the doStuff method.
Just remember to set at least 1 breakpoint ;-)
I need to call "MyOtherFunction" when "MyFunction"(which creates an element) completes, without MyFunction knowing what MyOtherFunction is.
The reason I need this is for extension of a jquery powered fileupload User Control that is used in several places with different functionality. A specific page shows a header and file count for it, and when the upload completes, I need to modify the file count according to how many files are displayed(by created elements) I thought :
$(UserControl).on(MyFunction, UploadElem, MyOtherFunction);
but this route is not accomplishing anything. The most I can alter the User Control is add in a function call, but without effecting the original user control functionality.
I'm not sure if because MyFunction isn't an event and doesn't bubble up or if it just isn't possible to use a defined function as a parameter of .on() is the reason I cannot get this code to work. Any suggestions?
Easiest way I can think of, is duck punching respectively hooking that method:
var _oldMyFunction = MyFunction;
MyFunction = function() {
_oldMyFunction.apply( this, arguments );
MyOtherFunction();
};
I managed to solve my own issue, but the context is important for the answer:
// Using a Global JavaScript object I created:
GlobalNameSpace.ExtensionFunction = function(oParam1, oParam2, oParam3)
{
/// <summary>All parameters are optional</summary>
return; // For instances when it is not being overwritten, simply return
}
//In the Code for the user control:
GlobalNameSpace.UploadControl.UploadComplete(oSender, oArgs)
{
///<summary>Handles the Upload process</summary>
// process the upload
GlobalNameSpace.ExtensionFunction(oSender, oArgs);
}
//and finally in the code to extend the functionality
GlobalNameSpace.Page.Init
{
///<summary>Initializes the page</summary>
// redefine the extension function
GlobalNameSpace.ExtensionFunction = function(oSender, oArgs)
{
GlobalNameSpace.Page.Function(oSender, oArgs);
}
}
This allows me to extend anything I need it to without polluting my objects, and having something generic already existing to call on to make my changes. This solution solves my problem of needing a onCreate function for the elements I create to represent my uploaded items to trigger the header displaying the number of files. Very useful
I need to be able to determine when an object is created (not a DOM element -- a JavaScript object).
An answer to this question has some very useful looking code for creating observable properties, so you can have a function fire when a property changes.
In my situation I need to do something when the object/property is created, not an existing property changed, and my limited understanding of such matters did not help me figure out if or how I could use that code to do this after much squinting.
The situation is: page loads a bunch of scripts. Some of the scripts create things that are needed by other scripts, e.g:
ThisStuff = (function () {
// blah blah
return self;
} ());
Some other code needs to initialize this ThisStuff, whenever it's available, which may be after the DOM is done loading. The user doesn't actually need ThisStuff right away, so it's fine for it to happen whenever the script is done loading. So I would like to do something along lines of:
$(document).ready(function() {
wheneverIsAvailable(window,'ThisStuff', function(object) {
object.init(args);
})
});
I realize there are other solutions to this problem (changing script order, or loading scripts on demand) but those are difficult because of the architecture. So I'm only interested in a way to do this versus other solutions. If jQuery offers some such functionality, that's fine also as I'm using it.
You could have a setInterval checking a number of times a second to watch the specific variable. You can check whether it is created using obj.hasOwnProperty(prop). When it is created, you invoke the function, and clear the interval.
It might be dirty but it might also just work fine for you.
Edit: I coded this for you: http://jsfiddle.net/jhXJ2/2/. It also supports passing additional arguments to the function.
window.__intervals = [];
function wheneverIsAvailable(obj, prop, func) {
var id = (Math.random()+"").substring(2);
var args = arguments;
window.__intervals[id] = window.setInterval(function() {
if(obj.hasOwnProperty(prop)) {
window.clearInterval(window.__intervals[id]);
func(Array.prototype.slice.call(args, 3));
// Call function with additional parameters passed
// after func (from index 3 and on)
}
}, 1000/ 50);
}
wheneverIsAvailable(window, 'test', function() {
alert(arguments[0]);
}, 'Woot!');
window.setTimeout('window.test = 123', 1000);
This is a bit far-fetched but it might work.
You would need to use knockoutjs, a javascript library. It's awesome but is built for a slightly different purpose.
Anyways it has a dependentObservable thing which allows to fire up an event whenever a certain value changes. Now I know you want on creation but you can check whether your variable holds any value (other than what you provided initially), if yes then consider it initialize.
Let me know if you think this sounds feasible.