extend a javascript option to add functionality - javascript

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

Related

Overriding core JS commands?

I'm trying to modify/limit/prevent access to certain JS commands of my browser. For example commands like navigator.clipboard; However, I'm not sure how to approach this.
Is it possible to override these commands with user-defined javascript injected in the page, or do i have to edit the browser's javascript compiler and re-compile it from source for this?
I'm not really familiar with browsers and want to save time by knowing a general direction to follow. Thanks
First of all navigator.clipboard is not a function, but here is an example using the read function of navigator.clipboard:
navigator.clipboard.read = function (originalFunction) {
return function (yourParamsYouWantForThisFunction) {
// Do Stuff you wanna do before the real call. For example:
console.log(yourParamsYouWantForThisFunction);
// Call the original function
return originalFunction.call();
};
}(navigator.clipboard.read); // Pass the original function reference as a parameter
You may wonder, why there are two function statements:
The first one is there, so that we can pass the original function at runtime. If we would not do that, we would not be able to access the original navigator.clipboard.read function.
The second function is the actual function, that you will be using later, when you call navigator.clipboard.read().

How to store/stash JavaScript event and reuse it later?

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.

How to re-evaluate a script that doesn't expose any global in a declarative-style component

I have been writing a reusable script, let's call it a plugin although it's not jQuery, that can be initialised in a declarative way from the HTML. I have extremely simplified it to explain my question so let's say that if a user inserts a tag like:
<span data-color="green"></span>
the script will fire because the attribute data-color is found, changing the color accordingly.
This approach proved very handy because it avoids anyone using the plugin having to initialise it imperatively in their own scripts with something like:
var elem = document.getElementsByTagName('span')[0];
myPlugin.init(elem);
Moreover by going the declarative way I could get away without defining any global (in this case myPlugin), which seemed to be a nice side effect.
I simplified this situation in an example fiddle here, and as you can see a user can avoid writing any js, leaving the configuration to the HTML.
Current situation
The plugin is wrapped in a closure like so:
;(function(){
var changeColor = {
init : function(elem){
var bg = elem.getAttribute('data-color');
elem.style.background = bg;
}
};
// the plugin itslef looks for appropriate HTML elements
var elem = document.querySelectorAll('[data-color]')[0];
// it inits itself as soon as it is evaluated at page load
changeColor.init(elem);
})();
The page loads and the span gets the correct colour, so everything is fine.
The problem
What has come up lately, though, is the need to let the user re-evaluate/re-init the plugin when he needs to.
Let's say that in the first example the HTML is changed dynamically after the page is loaded, becoming:
<span data-color="purple"></span>
With the first fiddle there's no way to re-init the plugin, so I am now testing some solutions.
Possible solutions
Exposing a global
The most obvious is exposing a global. If we go this route the fiddle becomes
http://jsfiddle.net/gleezer/089om9z5/4/
where the only real difference is removing the selection of the element, leaving it to the user:
// we remove this line
// var elem = document.querySelectorAll('[data-color]')[0];
and adding something like (again, i am simplifying for the sake of the question):
window.changeColor = changeColor;
to the above code in order to expose the init method to be called from anywhere.
Although this works I am not satisfied with it. I am really looking for an alternative solution, as I don't want to lose the ease of use of the original approach and I don't want to force anyone using the script adding a new global to their projects.
Events
One solution I have found is leveraging events. By putting something like this in the plugin body:
elem.addEventListener('init', function() {
changeColor.init(elem);
}, false);
anybody will be able to just create an event an fire it accordingly. An example in this case:
var event = new CustomEvent('init', {});
span.dispatchEvent(event);
This would re-init the plugin whenever needed. A working fiddle is to be found here:
http://jsfiddle.net/gleezer/tgztjdzL/1/
The question (finally)
My question is: is there a cleaner/better way of handling this?
How can i let people using this plugin without the need of a global or having to initialise the script themselves the first time? Is event the best way or am I missing some more obvious/better solutions?
You can override Element.setAttribute to trigger your plugin:
var oldSetAttribute = Element.prototype.setAttribute;
Element.prototype.setAttribute = function(name, value) {
oldSetAttribute.call(this, name, value);
if (name === 'data-color') {
changeColor.init(this);
}
}
Pros:
User does not have to explicitly re-initialize the plugin. It will happen automatically when required.
Cons:
This will, of course, only work if the user changes data-color attributes using setAttribute, and not if they create new DOM elements using innerHTML or via some other approach.
Modifying host object prototypes is considered bad practice by many, and for good reasons. Use at your own risk.

Override javascript library function

Im trying to modify the behavior of the jquery UI spinner, and want to avoid hacking the original js file. Theres a function in the js file called _doSpin that if I modify I can achieve the effect I want (I want it to change the value by a percentage - not like the built in percentage method which presents a real percentage to the user, but I need normal ints to be incremented or decremented by a percentage without the bounds of 100% to 0%).
But I don't want to hack the original file, so I want to override the function from within my page (which uses ui.spinner.js). I cant work out how to get to the namespace of the library file, from within my html page. I've tried loads of tips from stackoverflow already but none seem to work in my case. The library file starts with (function($, undefined) but I can't understand how its defined a namespace for me to define in my html page, so I can 'break in' and override the function.
BTW the function is used internally in the library file, maybe its not possible to override it in this case? I don't want to use it locally in my html page, I call higher methods to do my business, I just need to change how the library works internally.
First off, are you entirely sure that you can't solve your problem by specifying options to the spinner object? It seems that with the min, max and step options you could make the step be any percentage of the range that you wanted at the time you created the spinner or set the range. Here's the doc for the step option.
If this is an internal function (not exposed in a public API or as a method on a public object), then it all depends upon how it is coded. If it is not put into a public namespace and it is defined within some other function, then you can't get at it to redefine it. The internals of a function are not something you can get to from outside the function in Javascript unless they are explicitly make accessible to the public somehow.
If it is in a public API somewhere, then you can just assign a new function to whatever symbol the API is on and that will replace the original function (usually you save the original function first so you can still use it if needed).
We can't really help you more specifically without you showing us the actual code you're wanting to override. FYI, I can't find a function called _doSpin in the jQueryUI source repository, so I don't really know which function in which code you're trying to override.
Edit: In a Google search, I found another version of a jQuery UI spinner that does have a _doSpin() method. In that version the _doSpin() method ends up as a method on the prototype for the spinner widget object (which is a jQuery UI object structure it uses for all its objects). I don't personally have much experience with jQueryUI, but from reading their doc on the widget factory, it sounds like you can inherit from an existing widget and replace methods on the prototype. Or, you could probably hack on the prototype directly - though it would take some doing to figure out how/where jQueryUI stores it or how to get to it.
This did a better job in the end.
<html>
<script type='text/javascript'>
var mainarray = [1,2,3,4,5];
function scrolltxtbox (e) {
var evt=window.event || e;
var delta=evt.detail ? evt.detail*(-120) : evt.wheelDelta;
var targ;
if (evt.target) targ = evt.target;
else if (evt.srcElement) targ = evt.srcElement;
(delta<=-120)?multiplier=0.95:multiplier=1.05;
changeweight(targ.id, multiplier);
if (evt.preventDefault)
evt.preventDefault()
else return false
}
function changeweight(targid, multiplier) {
var u = targid.match(/myspinner(.*)/);
mainarray[u[1]] = mainarray[u[1]]*multiplier;
document.getElementById(targid).value = mainarray[u[1]].toPrecision(3);
}
var mousewheelevt=(/Firefox/i.test(navigator.userAgent))? "DOMMouseScroll" : "mousewheel"
var index;
for (index = 0; index < mainarray.length; ++index) {
document.write('<input type="text" size=6 id="myspinner'+index+'" value='+mainarray[index]+' autocomplete="off"><br>');
if (window['myspinner'+index].attachEvent) {
window['myspinner'+index].attachEvent("on"+mousewheelevt, scrolltxtbox);
} else if (window['myspinner'+index].addEventListener) {
window['myspinner'+index].addEventListener(mousewheelevt, scrolltxtbox, false);
}
}
</script>
</html>
Its probably (definitely!) oververbose, but it works nice.

Watch for a property creation event?

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.

Categories