Hook changes to lineWidth on HTML5 canvas context - javascript

I want to enforce a miterLimit in actual pixels rather than as a ratio of the lineWidth. To do this, I'd like to hook any changes to lineWidth, and set the miterLimit simultaneously and automatically. I've used custom setters on objects before, but if I replace the lineWidth setter, I don't know of any way to actually pass the value to set on through to the actual canvas context.
Is there some way (compatible on IE9+) that I can listen to changes to a given key on an object without changing the behavior of setting that value?

Your getter/setter idea is a good one...
How about just adding a property definition to your context object?
Add a myLineWidth property to your context object and then set the linewidth using context.myLineWidth instead of context.lineWidth.
Some example code:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
Object.defineProperty(ctx, 'myLineWidth', {
get: function() {
return(this.lineWidth);
},
set: function(newWidth) {
this.lineWidth=newWidth;
console.log("Executed myLineWidth setter: ",this.lineWidth);
}
});
ctx.myLineWidth=5;
ctx.strokeRect(100,100,50,50);
Alternate Method using Encapsulation:
JavaScript does have true inheritance so it's not possible to inherit & override lineWidth.
The next best thing would be encapsulating the context object. Then all coders can use the encapsulated version of the context using the standard property and method syntax (no need for myLineWidth). If needed, here's a how-to: http://aboutcode.net/2011/10/04/efficient-encapsulation-of-javascript-objects.html.
I did a similar encapsulation in order to log the context drawings. Below, I've tried to snip the encapsulation code from one of my projects. You can ignore my special handling of drawImage and gradients as I needed to grab values from these that you won't need to grab--just add those methods to the returnMethods[] array.
Some example code for you to start with:
// Log all context drawings
// Creates a proxy class wrapping canvas context
function LoggedContext(canvas,context) {
var self = this;
this.canvas=canvas;
this.context=context;
this.imageURLs=[];
this.log=[];
this.gradients=[];
this.patterns=[];
this.init(self);
}
// maintain urls of images used
LoggedContext.prototype.imageIndex=function(url){
var i=a.indexOf(url);
// found
if(i>-1){ return(i); }
// not found -- added
a.push(url);
return(a.length-1);
}
///////////////////////////////////////////
// These methods require special handling
// (drawImage:need image.src, gradients:need gradDefs & colors)
//
LoggedContext.prototype.drawImage=function(){
this.context.drawImage.apply(this.context,arguments);
var args = Array.prototype.slice.call(arguments);
args[0]=arguments[0].src;
args.unshift(2,"drawImage");
var sArgs=JSON.stringify(args);
this.log.push(sArgs);
return(this);
}
//
LoggedContext.prototype.createLinearGradient =function(x1,y1,x2,y2){
var gradient=this.context.createLinearGradient(x1,y1,x2,y2);
gradient.context=this;
gradient.gradientID=this.gradients.length;
this.gradients.push({line:{x1:x1,y1:y1,x2:x2,y2:y2},stops:[]});
gradient.baseAddColorStop=gradient.addColorStop;
gradient.addColorStop=function(stop,color){
this.context.gradients[this.gradientID].stops.push({stop:stop,color:color});
this.baseAddColorStop(stop,color);
}
return(gradient);
}
//
LoggedContext.prototype.createPattern =function(i,r){
var pattern=this.context.createPattern(i,r);
pattern.patternID=this.patterns.length;
this.patterns.push({src:i.src,repeat:r});
return(pattern);
}
//
LoggedContext.prototype.createRadialGradient =function(sx,sy,sr,ex,ey,er){
var gradient=this.context.createRadialGradient(sx,sy,sr,ex,ey,er);
gradient.context=this;
gradient.gradientID=this.gradients.length;
this.gradients.push({circles:{sx:sx,sy:sy,sr:sr,ex:ex,ey:ey,er:er},stops:[]});
gradient.baseAddColorStop=gradient.addColorStop;
gradient.addColorStop=function(stop,color){
this.context.gradients[this.gradientID].stops.push({stop:stop,color:color});
this.baseAddColorStop(stop,color);
}
return(gradient);
}
// load the proxy object with all properties & methods of the context
LoggedContext.prototype.init=function(self){
// define public context properties
var properties={
//
fillStyle:"black",
strokeStyle:"black",
lineWidth:1,
font:"10px sans-serif",
//
globalAlpha:1.00,
globalCompositeOperation:"source-over",
//
shadowColor:"black",
shadowBlur:0,
shadowOffsetX:0,
shadowOffsetY:0,
//
lineCap:"butt", // butt,round,square
lineJoin:"miter", // miter,round,miter
miterLimit:10,
//
textAlign:"start",
textBaseLine:"alphabetic",
};
// encapsulate public properties
for (var i in properties) {
(function(i) {
if(!(i=="fillStyle")){
Object.defineProperty(self, i, {
get: function () {
return properties[i];
},
set: function (val) {
this.log.push(JSON.stringify([1,i,val]));
properties[i] = val;
this.context[i]=val;
}
})
}else{
Object.defineProperty(self, i, {
get: function () {
return properties[i];
},
set: function (val) {
if(typeof val ==="object"){
if(val.gradientID>=0){
this.log.push(JSON.stringify([1,i,"gradient",val.gradientID]));
}else if(val.patternID>=0){
this.log.push(JSON.stringify([1,i,"pattern",val.patternID]));
}
}else{
this.log.push(JSON.stringify([1,i,val]));
}
properties[i] = val;
this.context[i]=val;
}
})
}
})(i);
}
// define public context methods
var methods = ['arc','beginPath','bezierCurveTo','clearRect','clip',
'closePath','fill','fillRect','fillText','lineTo','moveTo',
'quadraticCurveTo','rect','restore','rotate','save','scale','setTransform',
'stroke','strokeRect','strokeText','transform','translate','putImageData'];
// encapsulate public methods
for (var i=0;i<methods.length;i++){
var m = methods[i];
this[m] = (function(m){
return function () {
this.context[m].apply(this.context, arguments);
// "arguments" is not a real array--so convert it
var args = Array.prototype.slice.call(arguments);
args.unshift(2,m);
var sArgs=JSON.stringify(args);
this.log.push(sArgs);
return(this);
};}(m));
}
// define context methods that return values
var returnMethods = ['measureText','getImageData','toDataURL',
'isPointInPath','isPointInStroke'];
// encapsulate return methods
for (var i=0;i<returnMethods.length;i++){
var m = returnMethods[i];
this[m] = (function(m){
return function () {
return(this.context[m].apply(this.context, arguments));
};}(m));
}
} // end init()

Related

Something like MutationObserver for observing changes to object [duplicate]

Is it possible to have an event in JS that fires when the value of a certain variable changes? JQuery is accepted.
This question was originally posted in 2009 and most of the existing answers are either outdated, ineffective, or require the inclusion of large bloated libraries:
Object.watch and Object.observe are both deprecated and should not be used.
onPropertyChange is a DOM element event handler that only works in some versions of IE.
Object.defineProperty allows you to make an object property immutable, which would allow you to detect attempted changes, but it would also block any changes.
Defining setters and getters works, but it requires a lot of setup code and it does not work well when you need to delete or create new properties.
As of 2018, you can now use the Proxy object to monitor (and intercept) changes made to an object. It is purpose built for what the OP is trying to do. Here's a basic example:
var targetObj = {};
var targetProxy = new Proxy(targetObj, {
set: function (target, key, value) {
console.log(`${key} set to ${value}`);
target[key] = value;
return true;
}
});
targetProxy.hello_world = "test"; // console: 'hello_world set to test'
The only drawbacks of the Proxy object are:
The Proxy object is not available in older browsers (such as IE11) and the polyfill cannot fully replicate Proxy functionality.
Proxy objects do not always behave as expected with special objects (e.g., Date) -- the Proxy object is best paired with plain Objects or Arrays.
If you need to observe changes made to a nested object, then you need to use a specialized library such as Observable Slim (which I have published). It works like this:
var test = {testing:{}};
var p = ObservableSlim.create(test, true, function(changes) {
console.log(JSON.stringify(changes));
});
p.testing.blah = 42; // console: [{"type":"add","target":{"blah":42},"property":"blah","newValue":42,"currentPath":"testing.blah",jsonPointer:"/testing/blah","proxy":{"blah":42}}]
Yes, this is now completely possible!
I know this is an old thread but now this effect is possible using accessors (getters and setters): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects#Defining_getters_and_setters
You can define an object like this, in which aInternal represents the field a:
x = {
aInternal: 10,
aListener: function(val) {},
set a(val) {
this.aInternal = val;
this.aListener(val);
},
get a() {
return this.aInternal;
},
registerListener: function(listener) {
this.aListener = listener;
}
}
Then you can register a listener using the following:
x.registerListener(function(val) {
alert("Someone changed the value of x.a to " + val);
});
So whenever anything changes the value of x.a, the listener function will be fired. Running the following line will bring the alert popup:
x.a = 42;
See an example here: https://jsfiddle.net/5o1wf1bn/1/
You can also user an array of listeners instead of a single listener slot, but I wanted to give you the simplest possible example.
Using Prototype: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
// Console
function print(t) {
var c = document.getElementById('console');
c.innerHTML = c.innerHTML + '<br />' + t;
}
// Demo
var myVar = 123;
Object.defineProperty(this, 'varWatch', {
get: function () { return myVar; },
set: function (v) {
myVar = v;
print('Value changed! New value: ' + v);
}
});
print(varWatch);
varWatch = 456;
print(varWatch);
<pre id="console">
</pre>
Other example
// Console
function print(t) {
var c = document.getElementById('console');
c.innerHTML = c.innerHTML + '<br />' + t;
}
// Demo
var varw = (function (context) {
/**
* Declare a new variable.
*
* #param {string} Variable name.
* #param {any | undefined} varValue Default/Initial value.
* You can use an object reference for example.
*/
return function (varName, varValue) {
var value = varValue;
Object.defineProperty(context, varName, {
get: function () { return value; },
set: function (v) {
value = v;
print('Value changed! New value: ' + value);
}
});
};
})(window);
varw('varWatch'); // Declare without initial value
print(varWatch);
varWatch = 456;
print(varWatch);
print('---');
varw('otherVarWatch', 123); // Declare with initial value
print(otherVarWatch);
otherVarWatch = 789;
print(otherVarWatch);
<pre id="console">
</pre>
No.
But, if it's really that important, you have 2 options (first is tested, second isn't):
First, use setters and getters, like so:
var myobj = {a : 1};
function create_gets_sets(obj) { // make this a framework/global function
var proxy = {}
for ( var i in obj ) {
if (obj.hasOwnProperty(i)) {
var k = i;
proxy["set_"+i] = function (val) { this[k] = val; };
proxy["get_"+i] = function () { return this[k]; };
}
}
for (var i in proxy) {
if (proxy.hasOwnProperty(i)) {
obj[i] = proxy[i];
}
}
}
create_gets_sets(myobj);
then you can do something like:
function listen_to(obj, prop, handler) {
var current_setter = obj["set_" + prop];
var old_val = obj["get_" + prop]();
obj["set_" + prop] = function(val) { current_setter.apply(obj, [old_val, val]); handler(val));
}
then set the listener like:
listen_to(myobj, "a", function(oldval, newval) {
alert("old : " + oldval + " new : " + newval);
}
Second, you could put a watch on the value:
Given myobj above, with 'a' on it:
function watch(obj, prop, handler) { // make this a framework/global function
var currval = obj[prop];
function callback() {
if (obj[prop] != currval) {
var temp = currval;
currval = obj[prop];
handler(temp, currval);
}
}
return callback;
}
var myhandler = function (oldval, newval) {
//do something
};
var intervalH = setInterval(watch(myobj, "a", myhandler), 100);
myobj.set_a(2);
Sorry to bring up an old thread, but here is a little manual for those who (like me!) don't see how Eli Grey's example works:
var test = new Object();
test.watch("elem", function(prop,oldval,newval){
//Your code
return newval;
});
Hope this can help someone
As Luke Schafer's answer (note: this refers to his original post; but the whole point here remains valid after the edit), I would also suggest a pair of Get/Set methods to access your value.
However I would suggest some modifications (and that's why I'm posting...).
A problem with that code is that the field a of the object myobj is directly accessible, so it's possible to access it / change its value without triggering the listeners:
var myobj = { a : 5, get_a : function() { return this.a;}, set_a : function(val) { this.a = val; }}
/* add listeners ... */
myobj.a = 10; // no listeners called!
Encapsulation
So, to guarantee that the listeners are actually called, we would have to prohibit that direct access to the field a. How to do so? Use a closure!
var myobj = (function() { // Anonymous function to create scope.
var a = 5; // 'a' is local to this function
// and cannot be directly accessed from outside
// this anonymous function's scope
return {
get_a : function() { return a; }, // These functions are closures:
set_a : function(val) { a = val; } // they keep reference to
// something ('a') that was on scope
// where they were defined
};
})();
Now you can use the same method to create and add the listeners as Luke proposed, but you can rest assured that there's no possible way to read from or write to a going unnoticed!
Adding encapsulated fields programmatically
Still on Luke's track, I propose now a simple way to add encapsulated fields and the respective getters/setters to objects by the means of a simple function call.
Note that this will only work properly with value types. For this to work with reference types, some kind of deep copy would have to be implemented (see this one, for instance).
function addProperty(obj, name, initial) {
var field = initial;
obj["get_" + name] = function() { return field; }
obj["set_" + name] = function(val) { field = val; }
}
This works the same as before: we create a local variable on a function, and then we create a closure.
How to use it? Simple:
var myobj = {};
addProperty(myobj, "total", 0);
window.alert(myobj.get_total() == 0);
myobj.set_total(10);
window.alert(myobj.get_total() == 10);
Recently found myself with the same issue. Wanted to listen for on change of a variable and do some stuff when the variable changed.
Someone suggested a simple solution of setting the value using a setter.
Declaring a simple object that keeps the value of my variable here:
var variableObject = {
value: false,
set: function (value) {
this.value = value;
this.getOnChange();
}
}
The object contains a set method via which I can change the value. But it also calls a getOnChange() method in there. Will define it now.
variableObject.getOnChange = function() {
if(this.value) {
// do some stuff
}
}
Now whenever I do variableObject.set(true), the getOnChange method fires, and if the value was set as desired (in my case: true), the if block also executes.
This is the simplest way I found to do this stuff.
If you're using jQuery {UI} (which everyone should be using :-) ), you can use .change() with a hidden <input/> element.
AngularJS (I know this is not JQuery, but that might help. [Pure JS is good in theory only]):
$scope.$watch('data', function(newValue) { ..
where "data" is name of your variable in the scope.
There is a link to doc.
For those tuning in a couple years later:
A solution for most browsers (and IE6+) is available that uses the onpropertychange event and the newer spec defineProperty. The slight catch is that you'll need to make your variable a dom object.
Full details:
http://johndyer.name/native-browser-get-set-properties-in-javascript/
Easiest way I have found, starting from this answer:
// variable holding your data
const state = {
count: null,
update() {
console.log(`this gets called and your value is ${this.pageNumber}`);
},
get pageNumber() {
return this.count;
},
set pageNumber(pageNumber) {
this.count = pageNumber;
// here you call the code you need
this.update(this.count);
}
};
And then:
state.pageNumber = 0;
// watch the console
state.pageNumber = 15;
// watch the console
The functionality you're looking for can be achieved through the use of the "defineProperty()" method--which is only available to modern browsers:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
I've written a jQuery extension that has some similar functionality if you need more cross browser support:
https://github.com/jarederaj/jQueue
A small jQuery extension that handles queuing callbacks to the
existence of a variable, object, or key. You can assign any number of
callbacks to any number of data points that might be affected by
processes running in the background. jQueue listens and waits for
these data you specify to come into existence and then fires off the
correct callback with its arguments.
Not directly: you need a pair getter/setter with an "addListener/removeListener" interface of some sort... or an NPAPI plugin (but that's another story altogether).
A rather simple and simplistic solution is to just use a function call to set the value of the global variable, and never set its value directly. This way you have total control:
var globalVar;
function setGlobalVar(value) {
globalVar = value;
console.log("Value of globalVar set to: " + globalVar);
//Whatever else
}
There is no way to enforce this, it just requires programming discipline... though you can use grep (or something similar) to check that nowhere does your code directly set the value of globalVar.
Or you could encapsulate it in an object and user getter and setter methods... just a thought.
With the help of getter and setter, you can define a JavaScript class that does such a thing.
First, we define our class called MonitoredVariable:
class MonitoredVariable {
constructor(initialValue) {
this._innerValue = initialValue;
this.beforeSet = (newValue, oldValue) => {};
this.beforeChange = (newValue, oldValue) => {};
this.afterChange = (newValue, oldValue) => {};
this.afterSet = (newValue, oldValue) => {};
}
set val(newValue) {
const oldValue = this._innerValue;
// newValue, oldValue may be the same
this.beforeSet(newValue, oldValue);
if (oldValue !== newValue) {
this.beforeChange(newValue, oldValue);
this._innerValue = newValue;
this.afterChange(newValue, oldValue);
}
// newValue, oldValue may be the same
this.afterSet(newValue, oldValue);
}
get val() {
return this._innerValue;
}
}
Assume that we want to listen for money changes, let's create an instance of MonitoredVariable with initial money 0:
const money = new MonitoredVariable(0);
Then we could get or set its value using money.val:
console.log(money.val); // Get its value
money.val = 2; // Set its value
Since we have not defined any listeners for it, nothing special happens after money.val changes to 2.
Now let's define some listeners. We have four listeners available: beforeSet, beforeChange, afterChange, afterSet.
The following will happen sequentially when you use money.val = newValue to change variable's value:
money.beforeSet(newValue, oldValue);
money.beforeChange(newValue, oldValue); (Will be skipped if its value not changed)
money.val = newValue;
money.afterChange(newValue, oldValue); (Will be skipped if its value not changed)
money.afterSet(newValue, oldValue);
Now we define afterChange listener which be triggered only after money.val has changed (while afterSet will be triggered even if the new value is the same as the old one):
money.afterChange = (newValue, oldValue) => {
console.log(`Money has been changed from ${oldValue} to ${newValue}`);
};
Now set a new value 3 and see what happens:
money.val = 3;
You will see the following in the console:
Money has been changed from 2 to 3
For full code, see https://gist.github.com/yusanshi/65745acd23c8587236c50e54f25731ab.
In my case, I was trying to find out if any library I was including in my project was redefining my window.player. So, at the begining of my code, I just did:
Object.defineProperty(window, 'player', {
get: () => this._player,
set: v => {
console.log('window.player has been redefined!');
this._player = v;
}
});
Based On akira's answer I added that you can manipulate the dom through the listerner.
https://jsfiddle.net/2zcr0Lnh/2/
javascript:
x = {
aInternal: 10,
aListener: function(val) {},
set a(val) {
this.aInternal = val;
this.aListener(val);
},
get a() {
return this.aInternal;
},
registerListener: function(listener) {
this.aListener = listener;
}
}
x.registerListener(function(val) {
document.getElementById('showNumber').innerHTML = val;
});
x.a = 50;
function onClick(){
x.a = x.a + 1;
}
html:
<div id="showNumber">
</div>
<button onclick="onClick()">
click me to rerender
</button>
The registerListener method is fired when the variable x.a changes.
//ex:
/*
var x1 = {currentStatus:undefined};
your need is x1.currentStatus value is change trigger event ?
below the code is use try it.
*/
function statusChange(){
console.log("x1.currentStatus_value_is_changed"+x1.eventCurrentStatus);
};
var x1 = {
eventCurrentStatus:undefined,
get currentStatus(){
return this.eventCurrentStatus;
},
set currentStatus(val){
this.eventCurrentStatus=val;
//your function();
}
};
or
/* var x1 = {
eventCurrentStatus:undefined,
currentStatus : {
get : function(){
return Events.eventCurrentStatus
},
set : function(status){
Events.eventCurrentStatus=status;
},
}*/
console.log("eventCurrentStatus = "+ x1.eventCurrentStatus);
x1.currentStatus="create"
console.log("eventCurrentStatus = "+ x1.eventCurrentStatus);
x1.currentStatus="edit"
console.log("eventCurrentStatus = "+ x1.eventCurrentStatus);
console.log("currentStatus = "+ x1.currentStatus);
or
/* global variable ku*/
var jsVarEvents={};
Object.defineProperty(window, "globalvar1", {//no i18n
get: function() { return window.jsVarEvents.globalvarTemp},
set: function(value) { window.window.jsVarEvents.globalvarTemp = value; }
});
console.log(globalvar1);
globalvar1=1;
console.log(globalvar1);
Please guys remember the initial question was for VARIABLES, not for OBJECTS ;)
in addition to all answers above, I created a tiny lib called forTheWatch.js,
that use the same way to catch and callback for changes in normal global variables in javascript.
Compatible with JQUERY variables, no need to use OBJECTS, and you can pass directly an ARRAY of several variables if needed.
If it can be helpful... :
https://bitbucket.org/esabora/forthewatch Basically you just have to call the function :
watchIt("theVariableToWatch", "varChangedFunctionCallback");
And sorry by advance if not relevant.
The question is about variables, not object properties! So my approach is to take advantage of the window object, with its custom getters/setters, and then use/change the variable like a "normal" variable (not like an object property).
The simplest way is that of #José Antonio Postigo in his answer (i voted that answer). What I'd like to do here, is to reduce that to an even simpler "creator" function (so even someone that does not understand object getters/setters can easily use it).
A live example is here: https://codepen.io/dimvai/pen/LYzzbpz
This is the general "creator" function you must have as is:
let createWatchedVariable = (variableName,initialValue,callbackFunction) => {
// set default callback=console.log if missing
callbackFunction ??= function(){console.log(variableName+" changed to " + window[variableName])};
// the actual useful code:
Object.defineProperty(window, variableName, {
set: function(value) {window["_"+variableName] = value; callbackFunction()},
get: function() {return window["_"+variableName]}
});
window[variableName]=initialValue??null;
};
Then, instead of declaring the variable using var or let, use this:
// 1st approach - default callback//
createWatchedVariable ('myFirstVariable',12);
// instead of: let myFirstVariable = 12;
Or, in order to use your custom callback (instead of the default console.log) use:
// 2nd approach - set a custom callback//
var myCallback = ()=>{/*your custom code...*/}
// now use callback function as the third optional argument
createWatchedVariable('mySecondVariable',0,myCallback);
That's it! Now, you can change it like a "normal" variable:
myFirstVariable = 15; // logs to console
myFirstVariable++; // logs to console
mySecondVariable = 1001; // executes your custom code
mySecondVariable++; // executes your custom code
The solution of #akira and #mo-d-genesis can be further simplified because the DOM manipulation does not depend on state in this example:
CodePen
const render = (val) => {
document.getElementById("numberDiv").innerHTML = val;
};
state = {
_value_internal: undefined,
set value(val) {
// 1. set state value
this._value_internal = val;
// 2. render user interface
render(val);
},
get value() {
return this._value_internal;
},
};
const onClick = () => {
state.value = state.value + 1; // state change leads to re-render!
};
// set default value
state.value = 0;
The corresponding html:
<div id="numberDiv"></div>
<button onclick="onClick()">
Click to rerender
</button>
Remarks:
I renamed variables and functions to better reflect their semantics.
FYI: Svelte offers a very similar reactive behavior by changing variables
It's not directly possible.
However, this can be done using CustomEvent: https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent
The below method accepts an array of variable names as an input and adds event listener for each variable and triggers the event for any changes to the value of the variables.
The Method uses polling to detect the change in the value. You can increase the value for timeout in milliseconds.
function watchVariable(varsToWatch) {
let timeout = 1000;
let localCopyForVars = {};
let pollForChange = function () {
for (let varToWatch of varsToWatch) {
if (localCopyForVars[varToWatch] !== window[varToWatch]) {
let event = new CustomEvent('onVar_' + varToWatch + 'Change', {
detail: {
name: varToWatch,
oldValue: localCopyForVars[varToWatch],
newValue: window[varToWatch]
}
});
document.dispatchEvent(event);
localCopyForVars[varToWatch] = window[varToWatch];
}
}
setTimeout(pollForChange, timeout);
};
let respondToNewValue = function (varData) {
console.log("The value of the variable " + varData.name + " has been Changed from " + varData.oldValue + " to " + varData.newValue + "!!!");
}
for (let varToWatch of varsToWatch) {
localCopyForVars[varToWatch] = window[varToWatch];
document.addEventListener('onVar_' + varToWatch + 'Change', function (e) {
respondToNewValue(e.detail);
});
}
setTimeout(pollForChange, timeout);
}
By calling the Method:
watchVariables(['username', 'userid']);
It will detect the changes to variables username and userid.
This is what I did: Call JSON.stringify twice and compare the two strings...
Drawbacks:
You can only know whether the whole object changes
You have to detect changes manually
You better have only primitive fields in the object(no properties, no functions...)
This is NOT a production ideal answer, but what it is doing is setting an interval in JavaScript for every 100 milliseconds and checking to see if the variable is changed and when it is, it does something (anything intended by the OP) and then clears the interval, so it sort of simulates what the OP is asking.
let myvar = "myvar";
const checkChange = setInterval(() => {
if (myvar !== "myvar") {
console.log("My precious var has been changed!");
clearInterval(checkChange);
}
}, 100);
Now if myvar gets changed to something else then this program will say "My precious var has been changed!" :)
This is an old great question, has more than 12 years. Also, there are many ways to solve it. However, most of then are complicated or using old JS concepts we are in 2022 and we can use ES6 to improve our code.
I will implemented two main solutions that I constantly use.
Simple variable
If we have a simple variable and we don't care about reutilization then we can declare our variable as an object. We define a set and get methods and a listener attribute to handle the "change" event.
const $countBtn = document.getElementById('counter')
const $output = document.getElementById('output')
const counter = {
v: 0,
listener: undefined,
set value(v) {
this.v = v
if (this.listener) this.listener(v)
},
get value() { return this.v },
count() { this.value++ },
registerListener(callback) {
this.listener = callback
},
}
const countOnClick = () => { counter.count() }
$countBtn.onclick = countOnClick
counter.registerListener(v => {
$output.textContent = v
})
counter.value = 50
#output {
display: block;
font-size: 2em;
margin-top: 0.67em;
margin-bottom: 0.67em;
margin-left: 0;
margin-right: 0;
font-weight: bold;
}
<button id="counter">Count</button>
<div id="output"></div>
Advanced Class for reusability
If we will have multiple variables and we need to monitor them, we can create a class and then apply it to our variables. I recommend to add two listeners one beforeChange and afterChange this will give you flexibility to use the variable in different process.
class ObservableObject {
constructor(v) {
this.v = v ?? 0
this.on = {
beforeChange(newValue, oldValue) {},
afterChange(newValue, oldValue) {},
}
}
set value(newValue) {
const oldValue = this.v
// newValue, oldValue are the same
if (oldValue === newValue) return
this.on.beforeChange(newValue, oldValue)
this.v = newValue
this.on.afterChange(newValue, oldValue)
}
get value() { return this.v }
}
const $countABtn = document.getElementById('counter-a')
const $countBBtn = document.getElementById('counter-b')
const $outputA = document.getElementById('output-a')
const $outputB = document.getElementById('output-b')
const counterA = new ObservableObject()
const counterB = new ObservableObject()
const countOnClick = counter => { counter.value++ }
const onChange = (v, output) => { output.textContent = v }
$countABtn.onclick = () => { countOnClick(counterA) }
$countBBtn.onclick = () => { countOnClick(counterB) }
counterA.on.afterChange = v => { onChange(v, $outputA) }
counterB.on.afterChange = v => { onChange(v, $outputB) }
counterA.value = 50
counterB.value = 20
.wrapper {
display: flex;
flex-flow: row wrap;
justify-content: center;
align-items: center;
width: 100vw
}
.item {
width: 50%
}
.output {
display: block;
font-size: 2em;
margin-top: 0.67em;
margin-bottom: 0.67em;
margin-left: 0;
margin-right: 0;
font-weight: bold;
}
<div class="wrapper">
<div class="item">
<button id="counter-a">Count A</button>
<div id="output-a" class="output"></div>
</div>
<div class="item">
<button id="counter-b">Count B</button>
<div id="output-b" class="output"></div>
</div>
</div>
This is an old thread but I stumbled onto second highest answer (custom listeners) while looking for a solution using Angular. While the solution works, angular has a better built in way to resolve this using #Output and event emitters. Going off of the example in custom listener answer:
ChildComponent.html
<button (click)="increment(1)">Increment</button>
ChildComponent.ts
import {EventEmitter, Output } from '#angular/core';
#Output() myEmitter: EventEmitter<number> = new EventEmitter<number>();
private myValue: number = 0;
public increment(n: number){
this.myValue += n;
// Send a change event to the emitter
this.myEmitter.emit(this.myValue);
}
ParentComponent.html
<child-component (myEmitter)="monitorChanges($event)"></child-component>
<br/>
<label>{{n}}</label>
ParentComponent.ts
public n: number = 0;
public monitorChanges(n: number){
this.n = n;
console.log(n);
}
This will now update non parent each time the child button is clicked. Working stackblitz
I came here looking for same answer for node js. So here it is
const events = require('events');
const eventEmitter = new events.EventEmitter();
// Createing state to watch and trigger on change
let x = 10 // x is being watched for changes in do while loops below
do {
eventEmitter.emit('back to normal');
}
while (x !== 10);
do {
eventEmitter.emit('something changed');
}
while (x === 10);
What I am doing is setting some event emitters when values are changed and using do while loops to detect it.
I searched for JavaScript two-way data binding library and came across this one.
I did not succeed to make it work in DOM to variable direction, but in variable to DOM direction it works and that is what we need here.
I have rewritten it slightly, as the original code is very hard to read (for me). It uses
Object.defineProperty, so the second most upvoted answer by Eliot B. at least partially wrong.
<!DOCTYPE html>
<html>
<head>
<title>TODO supply a title</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script>
const dataBind = (function () {
const getElementValue = function (selector) {
let element = document.querySelectorAll(selector)[0];
return 'value' in element ? element.value : element.innerHTML;
};
const setElementValue = function (selector, newValue) {
let elementArray = document.querySelectorAll(selector);
for (let i = 0; i < elementArray.length; i++) {
let element = elementArray[i];
if ('value' in element) {
element.value = newValue;
if (element.tagName.toLowerCase() === 'select'){
let options = element.querySelectorAll('option');
for (let option in options){
if (option.value === newValue){
option.selected = true;
break;
}
}
}
} else {
element.innerHTML = newValue;
}
}
};
const bindModelToView = function (selector, object, property, enumerable) {
Object.defineProperty(object, property, {
get: function () {
return getElementValue(selector);
},
set: function (newValue) {
setElementValue(selector, newValue);
},
configurable: true,
enumerable: (enumerable)
});
};
return {
bindModelToView
};
})();
</script>
</head>
<body>
<div style="padding: 20%;">
<input type="text" id="text" style="width: 40px;"/>
</div>
<script>
let x = {a: 1, b: 2};
dataBind.bindModelToView('#text', x, 'a'); //data to dom
setInterval(function () {
x.a++;
}, 1000);
</script>
</body>
</html>
JSFiddle.
JSFiddle with original code.
In the provided example a property of object x updated by the setInterval and value of text input automatically updated as well. If it is not enough and event is what you looking for, you can add onchange listener to the above input. Input also can be made hidden if needed.
Utils = {
eventRegister_globalVariable : function(variableName,handlers){
eventRegister_JsonVariable(this,variableName,handlers);
},
eventRegister_jsonVariable : function(jsonObj,variableName,handlers){
if(jsonObj.eventRegisteredVariable === undefined) {
jsonObj.eventRegisteredVariable={};//this Object is used for trigger event in javascript variable value changes ku
}
Object.defineProperty(jsonObj, variableName , {
get: function() {
return jsonObj.eventRegisteredVariable[variableName] },
set: function(value) {
jsonObj.eventRegisteredVariable[variableName] = value; handlers(jsonObj.eventRegisteredVariable[variableName]);}
});
}

how to define global namespace in javascript

is there a way to define global namespace, so that i can call function from this namespace from all my page?
e.g
// in one file i define below code
DefineNameSpace("my.namespace.api", {
addObject: function(obj) {
// store obj into indexDB
},
readAllObject: function() {
// return array of object from indexdb
}
})
// so that in another javascript file i can do
my.namespace.api.addObject({name: "foo", desc: "bar"});
is there a way to implement "DefineNameSpace" method?
Thanks
one way to do it, which is very simple, is this:
my = {
namespace: {
api : {}
}
}
my.namespace.api.addObject = function (obj) { }
you're actually creating objects but in this way it will function as a namespace just as well :)
hm it's not the method you're implementing. But building a namespace with a method would require the function to be called before the script files are loaded where the namespace is used like that, otherwise those lines of code are called before the DefineNamespace method is called and you will run into parts of namespaces that are undefined at that point. With above solution that won't be the case, although it is not dynamic unfortunately.
building a namespace dynamically can be done in the following way:
// the root of the namespace would still be handy to have declared here
var my = {};
function defineNamespace(namespaceStr) {
var namespaceSegments = namespaceStr.split(".");
var namespaceSoFar = null;
// iterate through namespace parts
for (var i = 0; i < namespaceSegments.length; i++) {
var segment = namespaceSegments[i];
if (i == 0) {
// if namespace starts with my, use that
if (segment == "my") {
// set pointer to my
namespaceSoFar = my;
}
else {
// create new root namespace (not tested this, but think this should work)
var otherNamespace = eval(segment);
if (typeof otherNamespace == "undefined") {
eval(segment + " = {};");
}
// set pointer to created root namespace
namespaceSoFar = eval(segment);
}
}
else {
// further build the namespace
if (typeof namespaceSoFar[segment] == "undefined") {
namespaceSoFar[segment] = {};
}
// update the pointer (my -> my.namespace) for use in the next iteration
namespaceSoFar = namespaceSoFar[segment];
}
}
}

Javascript, possible to pass undeclared method parameters without eval?

Ok, difficult to understand from the title only. Here is an example. I want a function to refer to a variable that is "injected" automagically, ie:
function abc() {
console.log(myVariable);
}
I have tried with:
with({myVariable: "value"}) { abc() }
but this doesn't work unless abc is declared within the with block, ie:
with({myVariable: "value"}) {
function abc() {
console.log(myVariable);
}
abc(); // This will work
}
So the last piece will work, but is it possible to fake the with statement, or do I have to force the developers to declare their function calls in a with statement?
Basically the call I want to do is:
doSomething({myVariable: "value"}, function() {
console.log(myVariable);
});
Ofcourse, I am aware I could pass this is a one parameter object, but that is not what I am trying to do:
doSomething({myVariable: "value"}, function(M) {
console.log(M.myVariable);
});
Further more, I am trying to avoid using eval:
with({myVariable: "value"}) {
eval(abc.toString())(); // Will also work
}
Is this not supported at at all beyond eval in Javascript?
JavaScript does not provide any straightforward way to achieve the syntax you're looking for. The only way to inject a variable into a Lexical Environment is by using eval (or the very similar Function constructor). Some of the answers to this question suggest this. Some other answers suggest using global variables as a workaround. Each of those solutions have their own caveats, though.
Other than that, your only option is to use a different syntax. The closest you can get to your original syntax is passing a parameter from doSomething to the callback, as Aadit M Shah suggested. Yes, I am aware you said you don't want to do that, but it's either that or an ugly hack...
Original answer (written when I didn't fully understand the question)
Maybe what you're looking for is a closure? Something like this:
var myVariable = "value";
function doSomething() {
console.log(myVariable);
};
doSomething(); // logs "value"
Or maybe this?
function createClosure(myVariable) {
return function() {
console.log(myVariable);
};
}
var closure = createClosure("value");
closure(); // logs "value"
Or even:
var closure = function(myVariable) {
return function() {
console.log(myVariable);
};
}("value");
closure(); // logs "value"
I asked a similar question a long time ago: Is it possible to achieve dynamic scoping in JavaScript without resorting to eval?
The short answer is no, you can't achieve dynamic scoping without resorting to eval. The long answer is, you don't need to.
JavaScript doesn't support dynamic scoping, but that's not an issue because you can make your free variables parameters of the function that they belong to.
In my humble opinion this is the best solution:
function doSomething(context, callback) {
callback(context);
}
doSomething({myVariable: "value"}, function(M) {
console.log(M.myVariable);
});
However since you don't want to write a formal parameter, the next best thing is to use this instead:
function doSomething(context, callback) {
callback.call(context);
}
doSomething({myVariable: "value"}, function() {
console.log(this.myVariable);
});
Another option would be to manipulate the formal parameter list of the program as follows:
function inject(func, properties) {
var args = [], params = [];
for (var property in properties) {
if (properties.hasOwnProperty(property)) {
args.push(properties[property]);
params.push(property);
}
}
return Function.apply(null, params.concat("return " + func.toString()))
.apply(null, args);
}
Now we can use this inject method to inject properties into a function as follows:
function doSomething(context, callback) {
var func = inject(callback, context);
func();
}
doSomething({myVariable: "value"}, function() {
console.log(myVariable);
});
See the demo: http://jsfiddle.net/sDKga/1/
Note: The inject function will create an entirely new function which will not have the same lexical scope as the original function. Hence functions with free variables and partially applied functions will not work as expected. Only use inject with normal functions.
The Function constructor is kind of like eval but it's much safer. Of course I would advise you to simply use a formal parameter or this instead. However the design decision is your choice.
Try:
function doSomething(vars, fun) {
for (var key in vars) { // set the variables in vars
window[key] = vars[key];
}
fun.call(); // call function
for (var key in vars) { // remove the variables again. this will allow only the function to use it
delete window[key];
}
}
Set global variables that can then be received inside of fun
The JSFiddle: http://jsfiddle.net/shawn31313/MbAMQ/
Warning: disgusting code ahead
function callWithContext(func, context, args) {
var oldProperties = {};
for(var n in context) {
if(context.hasOwnProperty(n)) {
var oldProperty = Object.getOwnPropertyDescriptor(self, n);
oldProperties[n] = oldProperty;
(function(n) {
Object.defineProperty(self, n, {
get: function() {
if(arguments.callee.caller === func) {
return context[n];
}
if(!oldProperty) {
return;
}
if(oldProperty.get) {
return oldProperty.get.apply(this, arguments);
}
return oldProperty.value;
},
set: function(value) {
if(arguments.callee.caller === func) {
context[n] = value;
}
if(!oldProperty) {
return;
}
if(oldProperty.set) {
return oldProperty.get.apply(this, arguments);
} else if(!oldProperty.writable) {
var fakeObject = {};
Object.defineProperty(fakeObject, n, {value: null, writable: false});
fakeObject[n] = value; // Kind of stupid, but…
return;
}
oldProperty.value = value;
}
});
})(n);
}
}
func.apply(this, args);
for(var n in context) {
if(context.hasOwnProperty(n)) {
if(oldProperties[n]) {
Object.defineProperty(self, n, oldProperties[n]);
} else {
delete self[n];
}
}
}
}
This is vomitously horrendous, by the way; don’t use it. But ew, it actually works.
i don't see why you can't just pass the info in or define a single global, but i think that would be best.
that said, i am working on a Module maker/runner that allows sloppy/dangerous code to execute without interference to the host environment. that provides the opportunity to re-define variables, which can be passed as an object.
this does use eval (Function() technically) but it can run in "use strict", so it's not too crazy/clever.
it doesn't leave behind artifacts.
it also won't let globals get hurt.
it's still a work in progress, and i need to iron out a couple minor details before i vouch for security, so don't use it for fort knox or anything, but it's working and stable enough to perform the operation asked for.
tested in ch28, FF22, IE10:
function Module(strCode, blnPreventExtensions, objWhitelist, objExtend) {
var __proto__=self.__proto__, pbu=self.__proto__, str=strCode, om=[].map, wasFN=false,
params = {Object:1}, fnScrubber, natives= [ Object, Array, RegExp, String, Boolean, Date] ,
nativeSlots = [],
preamble = "'use strict';" ,
inherited="__defineGetter__,__defineSetter__,__proto__,valueOf,constructor,__lookupGetter__,__lookupSetter__",
late = inherited +
Object.getOwnPropertyNames(__proto__||{}) + Object.getOwnPropertyNames(window);
late.split(",").sort().map(function(a) {
this[a] = 1;
}, params);
preamble+=";var "+inherited+";";
//turn functions into strings, but note that a function was passed
if(str.call){wasFN=true; str=String(str); delete params.Object; }
objExtend=objExtend||{};
var vals=Object.keys(objExtend).map(function(k){ return objExtend[k]; })
// build a usable clone of Object for all the new OOP methods it provides:
var fakeOb=Object.bind();
(Object.getOwnPropertyNames(Object)||Object.keys(Object)).map(function(a){
if(Object[a] && Object[a].bind){this[a]=Object[a].bind(Object); } return this;
},fakeOb)[0];
//allow "eval" and "arguments" since strict throws if you formalize them and eval is now presumed safe.
delete params.eval;
delete params.arguments;
params.hasOwnProperty=undefined;
params.toString=undefined;
params['__proto__']={};
__proto__=null;
Object.keys(objWhitelist||{}).map(function ripper(a,b){
b=this[a];
if(typeof b!=='object'){
delete this[a];
}
}, params);
// var ok=Object.keys.bind(Object);
// prevent new prototype methods from being added to native constructors:
if (blnPreventExtensions) {
natives.forEach(function(con, i) {
var proto=con.prototype;
Object.getOwnPropertyNames(proto).map(function(prop){
if(proto[prop] && proto[prop].bind ){ this[prop]=proto[prop];}
}, nativeSlots[i] = {});
delete con.constructor;
delete con.prototype.constructor;
}); //end con map()
} /* end if(blnPreventExtensions) */
//white-list harmless math utils and prevent hijacking:
delete params.Math;
if(blnPreventExtensions){Object.freeze(Math);}
//prevent literal constructors from getting Function ref (eg: [].constructor.constructor, /./.constructor.constructor, etc...):
Function.prototype.constructor = null;
try {
//generate a private wrapper function to evaluate code:
var response = Function(
Object.keys(objExtend) + (vals.length?",":"") +
Object.keys(params).filter(/./.test, /^[\w\$]+$/), // localize most globals
preamble + " return " + str.trim() // cram code into a function body with global-blocking formal parameters
);
// call it with a blank this object and only user-supplied arguments:
if (blnPreventExtensions) { //( user-land code must run inside here to be secure)
response = response.apply({}, vals.concat(fakeOb)).apply({}, [].slice.call(arguments,4) );
}else{
response = response.apply({}, vals.concat(fakeOb));
}
} catch (y) {
response = y + "!!";
} /* end try/catch */
if (blnPreventExtensions) {
om.call(natives, function(con, i) {
var pro=con.prototype;
//remove all proto methods for this con to censor any additions made by unsafe code:
Object.getOwnPropertyNames(pro).map(function(a){ try{delete pro[a];}catch(y){}});
//restore all original props from the backup:
var bu = nativeSlots[i];
om.call(Object.keys(bu), function(prop){ con.prototype[prop]=bu[prop]; }, bu);
}); //end con map()
} /* end if(blnPreventExtensions) */
//restore hidden Function constructor property:
Function.prototype.constructor = Function;
return response;
} /* end Module() */
/////////////////////////////////////////////////////////////
function doSomething(context, fn){
console.log(myVariable);
return myVariable;
}
//use 1:
alert( Module(doSomething, true, {console:1}, {myVariable: "value123"} ) );// immed
//use2:
var fn=Module(doSomething, false, {console:1}, {myVariable: "value123"} );// as function
alert(fn);
alert(fn());
again, i think OP would be best off not doing things later than need be, but for the sake of comprehensiveness and inspiration i'm putting this out there in good faith.
You need to use call() to construct a context, as in:
var f=function(){
console.log(this.foo);
};
f.call({foo:'bar'})
will print "bar"
You can avoid using eval() in calling the function, if you are willing to use it in doSomething():
function abc() {
console.log(myVariable);
}
// Prints "value"
callWith({ myVariable: "value" }, abc);
function callWith(context, func) {
for(var i in context) eval('var ' + i + ' = context[i];');
eval('(' + func.toString() + ')')();
}
Have a look at this post.
Have a look at goog.partial, scroll a little bit up to see the description of what it does:
Here is an implementation of it:
var b = goog.partial(alert, 'Hello world!');
b();//alerts "Hello world!"
In the example it passes the function alert with parameter "Hello world!" but you can pass it your own function with multiple parameters.
This allows you to create a variable that points to a function that is always called with a certain paramater. To use parameters in a function that are not named you can use arguments:
function test(){
console.log(arguments);//["hello","world"]
}
test("hello","world");

Incorporate window.onresize into OO JS class

I'm just trying to structure my Javascript better and wondering how to incorporate window.onresize into the returned object, like so:
var baseline = function(){
var tall, newHeight, target, imgl, cur, images = [];
return {
init: function(selector, target){
this.images = document.querySelectorAll(selector);
this.target = target;
this.setbase(this.images);
window.onresize = this.setbase(this.images);
},
setbase: function(imgs){
this.imgl = imgs.length;
if(this.imgl !== 0){
while(this.imgl--){
this.cur = imgs[this.imgl];
this.cur.removeAttribute("style");
this.tall = this.cur.offsetHeight;
this.newHeight = Math.floor(this.tall / this.target) * this.target;
this.cur.style.maxHeight = this.newHeight + 'px';
}
} else {
return false;
}
}
}
}();
Is this the way that people would do it, is this going to work? Thanks
EDIT:
Invoked like so:
window.onload = function(){
baseline.init('img', '24');
};
I would like it so that when the window is resized, baseline.init is called with the same params as the initial init function call...
Here's the main error
init: function(selector, target){
this.images = document.querySelectorAll(selector);
this.target = target;
this.setbase(this.images);
// This line says call setbase now and assign the result of that
// as the onresize handler
window.onresize = this.setbase(this.images);
},
Your this.images does not point to the var images = [] you've created. This is for when you're using protoype style objects. You should just use images in your functions.
Some of your variables look like they're only used in setBase, they should be local
Looking at your object, it's very hard to tell what it's supposed to do, sounds like you're wrapping code in an object just for the sake of wrapping it into an object. What does baseline mean?
Here's a better version of your code, you should read and understand http://www.joezimjs.com/javascript/javascript-closures-and-the-module-pattern/ and http://js-bits.blogspot.com/2010/08/javascript-inheritance-done-right.html so you can decide what pattern you want to use and how they actually work. You are mixing both patterns, even though you didn't intend to. The trick is that with the way you're writing it (module pattern) there's no need to use this in the code, they're actually local variables held be the module
var baseline = function(){
// Don't use "this.tall", just "tall" gets you the variable
// Class variables, are you sure you need them throughout the class
var tall, newHeight, target, imgl, cur, images = [];
// Different name for the parameter so it doesn't get confused with
// the class variables
function init(selector, pTarget) {
images = document.querySelectorAll(selector);
target = pTarget;
setBase();
// Since we're not using this, you
// can just reference the function itself
window.onresize = setBase
}
// Most JS developers name methods using camelCase
function setBase() {
imgl = imgs.length;
if(imgl !== 0){
while(imgl--){
cur = imgs[imgl];
cur.removeAttribute("style");
tall = cur.offsetHeight;
newHeight = Math.floor(tall / target) * target;
cur.style.maxHeight = newHeight + 'px';
}
// should you return true here? what does returning
// something even mean here?
} else {
return false;
}
}
// Return just the public interface
return {
init: init
setBase: setBase
};
}();

jQuery plugin template - best practice, convention, performance and memory impact

I've started to write few jQuery plugins and figured it'd be nice to setup my IDE with a jQuery plugin template.
I have been reading some articles and posts on this site related to plugin convention, design, etc.. and thought I'd try and consolidate all of that.
Below is my template, I am looking to use it frequently so was keen to ensure it generally conforms to jQuery plugin design convention and whether the idea of having multiple internal methods (or even its general design) would impact performance and be prone to memory issues.
(function($)
{
var PLUGIN_NAME = "myPlugin"; // TODO: Plugin name goes here.
var DEFAULT_OPTIONS =
{
// TODO: Default options for plugin.
};
var pluginInstanceIdCount = 0;
var I = function(/*HTMLElement*/ element)
{
return new Internal(element);
};
var Internal = function(/*HTMLElement*/ element)
{
this.$elem = $(element);
this.elem = element;
this.data = this.getData();
// Shorthand accessors to data entries:
this.id = this.data.id;
this.options = this.data.options;
};
/**
* Initialises the plugin.
*/
Internal.prototype.init = function(/*Object*/ customOptions)
{
var data = this.getData();
if (!data.initialised)
{
data.initialised = true;
data.options = $.extend(DEFAULT_OPTIONS, customOptions);
// TODO: Set default data plugin variables.
// TODO: Call custom internal methods to intialise your plugin.
}
};
/**
* Returns the data for relevant for this plugin
* while also setting the ID for this plugin instance
* if this is a new instance.
*/
Internal.prototype.getData = function()
{
if (!this.$elem.data(PLUGIN_NAME))
{
this.$elem.data(PLUGIN_NAME, {
id : pluginInstanceIdCount++,
initialised : false
});
}
return this.$elem.data(PLUGIN_NAME);
};
// TODO: Add additional internal methods here, e.g. Internal.prototype.<myPrivMethod> = function(){...}
/**
* Returns the event namespace for this widget.
* The returned namespace is unique for this widget
* since it could bind listeners to other elements
* on the page or the window.
*/
Internal.prototype.getEventNs = function(/*boolean*/ includeDot)
{
return (includeDot !== false ? "." : "") + PLUGIN_NAME + "_" + this.id;
};
/**
* Removes all event listeners, data and
* HTML elements automatically created.
*/
Internal.prototype.destroy = function()
{
this.$elem.unbind(this.getEventNs());
this.$elem.removeData(PLUGIN_NAME);
// TODO: Unbind listeners attached to other elements of the page and window.
};
var publicMethods =
{
init : function(/*Object*/ customOptions)
{
return this.each(function()
{
I(this).init(customOptions);
});
},
destroy : function()
{
return this.each(function()
{
I(this).destroy();
});
}
// TODO: Add additional public methods here.
};
$.fn[PLUGIN_NAME] = function(/*String|Object*/ methodOrOptions)
{
if (!methodOrOptions || typeof methodOrOptions == "object")
{
return publicMethods.init.call(this, methodOrOptions);
}
else if (publicMethods[methodOrOptions])
{
var args = Array.prototype.slice.call(arguments, 1);
return publicMethods[methodOrOptions].apply(this, args);
}
else
{
$.error("Method '" + methodOrOptions + "' doesn't exist for " + PLUGIN_NAME + " plugin");
}
};
})(jQuery);
Thanks in advance.
A while back I've build a plugin generator based on a blog article I have read: http://jsfiddle.net/KeesCBakker/QkPBF/. It might be of use. It is fairly basic and straight forward. Any comments would be very welcome.
You can fork your own generator and change it to your needs.
Ps. This is the generated body:
(function($){
//My description
function MyPluginClassName(el, options) {
//Defaults:
this.defaults = {
defaultStringSetting: 'Hello World',
defaultIntSetting: 1
};
//Extending options:
this.opts = $.extend({}, this.defaults, options);
//Privates:
this.$el = $(el);
}
// Separate functionality from object creation
MyPluginClassName.prototype = {
init: function() {
var _this = this;
},
//My method description
myMethod: function() {
var _this = this;
}
};
// The actual plugin
$.fn.myPluginClassName = function(options) {
if(this.length) {
this.each(function() {
var rev = new MyPluginClassName(this, options);
rev.init();
$(this).data('myPluginClassName', rev);
});
}
};
})(jQuery);
[Edit] 7 months later
Quoting from the github project
jQuery is no good, and jQuery plugins is not how do modular code.
Seriously "jQuery plugins" are not a sound architecture strategy. Writing code with a hard dependency on jQuery is also silly.
[Original]
Since I gave critique about this template I will propose an alternative.
To make live easier this relies on jQuery 1.6+ and ES5 (use the ES5 Shim).
I've spend some time re-designing the plugin template you've given and rolled out my own.
Links:
Github
Documentation
Unit tests Confirmed to pass in FF4, Chrome and IE9 (IE8 & OP11 dies. known bug).
Annotated Source Code
The PlaceKitten example plugin
Comparison:
I've refactored the template so that it's split into boilerplate (85%) and scaffolding code (15%). The intention is that you only have to edit the scaffolding code and you can keep leave boilerplate code untouched. To achieve this I've used
inheritance var self = Object.create(Base) Rather then editing the Internal class you have directly you should be editing a sub class. All your template / default functionality should be in a base class (called Base in my code).
convention self[PLUGIN_NAME] = main; By convention the plugin defined on jQuery will call the method define on self[PLUGIN_NAME] by default. This is considered the main plugin method and has a seperate external method for clarity.
monkey patching $.fn.bind = function _bind ... Use of monkey patching means that the event namespacing is done automatically for you under the hood. This functionality is free and does not come at the cost of readability (calling getEventNS all the time).
OO Techniques
It's better to stick to proper JavaScript OO rather then classical OO emulation. To achieve this you should use Object.create. (which ES5 just use the shim to upgrade old browsers).
var Base = (function _Base() {
var self = Object.create({});
/* ... */
return self;
})();
var Wrap = (function _Wrap() {
var self = Object.create(Base);
/* ... */
return self;
})();
var w = Object.create(Wrap);
This is different from the standard new and .prototype based OO people are used to. This approach is preferred because it re-inforces the concept that there are only Objects in JavaScript and it's a prototypical OO approach.
[getEventNs]
As mentioned this method has been refactored away by overriding .bind and .unbind to automatically inject namespaces. These methods are overwritten on the private version of jQuery $.sub(). The overwritten methods behave the same way as your namespacing does. It namespaces events uniquely based on plugin and instance of a plugin wrapper around a HTMLElement (Using .ns.
[getData]
This method has been replaced with a .data method that has the same API as jQuery.fn.data. The fact that it's the same API makes it easier to use, its basically a thin wrapper around jQuery.fn.data with namespacing. This allows you to set key/value pair data that is immediatley stored for that plugin only. Multiple plugins can use this method in parallel without any conflicts.
[publicMethods]
The publicMethods object has been replaced by any method being defined on Wrap being automatically public. You can call any method on a Wrapped object directly but you do not actually have access to the wrapped object.
[$.fn[PLUGIN_NAME]]
This has been refactored so it exposes a more standardized API. This api is
$(selector).PLUGIN_NAME("methodName", {/* object hash */}); // OR
$(selector).PLUGIN_NAME({/* object hash */}); // methodName defaults to PLUGIN_NAME
the elements in the selector are automatically wrapped in the Wrap object, the method is called or each selected element from the selector and the return value is always a $.Deferred element.
This standardizes the API and the return type. You can then call .then on the returned deferred to get out the actual data you care about. The use of deferred here is very powerful for abstraction away whether the plugin is synchronous or asynchronous.
_create
A caching create function has been added. This is called to turn a HTMLElement into a Wrapped element and each HTMLElement will only be wrapped once. This caching gives you a solid reduction in memory.
$.PLUGIN_NAME
Added another public method for the plugin (A total of two!).
$.PLUGIN_NAME(elem, "methodName", {/* options */});
$.PLUGIN_NAME([elem, elem2, ...], "methodName", {/* options */});
$.PLUGIN_NAME("methodName", {
elem: elem, /* [elem, elem2, ...] */
cb: function() { /* success callback */ }
/* further options */
});
All parameters are optional. elem defaults to <body>, "methodName" defaults to "PLUGIN_NAME" and {/* options */} defaults to {}.
This API is very flexible (with 14 method overloads!) and standard enough to get used to the syntnax for every method your plugin will expose.
Public exposure
The Wrap, create and $ objects are exposed globally. This will allow advanced plugin users maximum flexibility with your plugin. They can use create and the modified subbed $ in their development and they can also monkey patch Wrap. This allows for i.e. hooking into your plugin methods. All three of these are marked with a _ in front of their name so they are internal and using them breaks the garantuee that your plugin works.
The internal defaults object is also exposed as $.PLUGIN_NAME.global. This allows users to override your defaults and set plugin global defaults. In this plugin setup all hashes past into methods as objects are merged with the defaults, so this allows users to set global defaults for all your methods.
Actual Code
(function($, jQuery, window, document, undefined) {
var PLUGIN_NAME = "Identity";
// default options hash.
var defaults = {
// TODO: Add defaults
};
// -------------------------------
// -------- BOILERPLATE ----------
// -------------------------------
var toString = Object.prototype.toString,
// uid for elements
uuid = 0,
Wrap, Base, create, main;
(function _boilerplate() {
// over-ride bind so it uses a namespace by default
// namespace is PLUGIN_NAME_<uid>
$.fn.bind = function _bind(type, data, fn, nsKey) {
if (typeof type === "object") {
for (var key in type) {
nsKey = key + this.data(PLUGIN_NAME)._ns;
this.bind(nsKey, data, type[key], fn);
}
return this;
}
nsKey = type + this.data(PLUGIN_NAME)._ns;
return jQuery.fn.bind.call(this, nsKey, data, fn);
};
// override unbind so it uses a namespace by default.
// add new override. .unbind() with 0 arguments unbinds all methods
// for that element for this plugin. i.e. calls .unbind(_ns)
$.fn.unbind = function _unbind(type, fn, nsKey) {
// Handle object literals
if ( typeof type === "object" && !type.preventDefault ) {
for ( var key in type ) {
nsKey = key + this.data(PLUGIN_NAME)._ns;
this.unbind(nsKey, type[key]);
}
} else if (arguments.length === 0) {
return jQuery.fn.unbind.call(this, this.data(PLUGIN_NAME)._ns);
} else {
nsKey = type + this.data(PLUGIN_NAME)._ns;
return jQuery.fn.unbind.call(this, nsKey, fn);
}
return this;
};
// Creates a new Wrapped element. This is cached. One wrapped element
// per HTMLElement. Uses data-PLUGIN_NAME-cache as key and
// creates one if not exists.
create = (function _cache_create() {
function _factory(elem) {
return Object.create(Wrap, {
"elem": {value: elem},
"$elem": {value: $(elem)},
"uid": {value: ++uuid}
});
}
var uid = 0;
var cache = {};
return function _cache(elem) {
var key = "";
for (var k in cache) {
if (cache[k].elem == elem) {
key = k;
break;
}
}
if (key === "") {
cache[PLUGIN_NAME + "_" + ++uid] = _factory(elem);
key = PLUGIN_NAME + "_" + uid;
}
return cache[key]._init();
};
}());
// Base object which every Wrap inherits from
Base = (function _Base() {
var self = Object.create({});
// destroy method. unbinds, removes data
self.destroy = function _destroy() {
if (this._alive) {
this.$elem.unbind();
this.$elem.removeData(PLUGIN_NAME);
this._alive = false;
}
};
// initializes the namespace and stores it on the elem.
self._init = function _init() {
if (!this._alive) {
this._ns = "." + PLUGIN_NAME + "_" + this.uid;
this.data("_ns", this._ns);
this._alive = true;
}
return this;
};
// returns data thats stored on the elem under the plugin.
self.data = function _data(name, value) {
var $elem = this.$elem, data;
if (name === undefined) {
return $elem.data(PLUGIN_NAME);
} else if (typeof name === "object") {
data = $elem.data(PLUGIN_NAME) || {};
for (var k in name) {
data[k] = name[k];
}
$elem.data(PLUGIN_NAME, data);
} else if (arguments.length === 1) {
return ($elem.data(PLUGIN_NAME) || {})[name];
} else {
data = $elem.data(PLUGIN_NAME) || {};
data[name] = value;
$elem.data(PLUGIN_NAME, data);
}
};
return self;
})();
// Call methods directly. $.PLUGIN_NAME(elem, "method", option_hash)
var methods = jQuery[PLUGIN_NAME] = function _methods(elem, op, hash) {
if (typeof elem === "string") {
hash = op || {};
op = elem;
elem = hash.elem;
} else if ((elem && elem.nodeType) || Array.isArray(elem)) {
if (typeof op !== "string") {
hash = op;
op = null;
}
} else {
hash = elem || {};
elem = hash.elem;
}
hash = hash || {}
op = op || PLUGIN_NAME;
elem = elem || document.body;
if (Array.isArray(elem)) {
var defs = elem.map(function(val) {
return create(val)[op](hash);
});
} else {
var defs = [create(elem)[op](hash)];
}
return $.when.apply($, defs).then(hash.cb);
};
// expose publicly.
Object.defineProperties(methods, {
"_Wrap": {
"get": function() { return Wrap; },
"set": function(v) { Wrap = v; }
},
"_create":{
value: create
},
"_$": {
value: $
},
"global": {
"get": function() { return defaults; },
"set": function(v) { defaults = v; }
}
});
// main plugin. $(selector).PLUGIN_NAME("method", option_hash)
jQuery.fn[PLUGIN_NAME] = function _main(op, hash) {
if (typeof op === "object" || !op) {
hash = op;
op = null;
}
op = op || PLUGIN_NAME;
hash = hash || {};
// map the elements to deferreds.
var defs = this.map(function _map() {
return create(this)[op](hash);
}).toArray();
// call the cb when were done and return the deffered.
return $.when.apply($, defs).then(hash.cb);
};
}());
// -------------------------------
// --------- YOUR CODE -----------
// -------------------------------
main = function _main(options) {
this.options = options = $.extend(true, defaults, options);
var def = $.Deferred();
// Identity returns this & the $elem.
// TODO: Replace with custom logic
def.resolve([this, this.elem]);
return def;
}
Wrap = (function() {
var self = Object.create(Base);
var $destroy = self.destroy;
self.destroy = function _destroy() {
delete this.options;
// custom destruction logic
// remove elements and other events / data not stored on .$elem
$destroy.apply(this, arguments);
};
// set the main PLUGIN_NAME method to be main.
self[PLUGIN_NAME] = main;
// TODO: Add custom logic for public methods
return self;
}());
})(jQuery.sub(), jQuery, this, document);
As can be seen the code your supposed to edit is below the YOUR CODE line. The Wrap object acts similarly to your Internal object.
The function main is the main function called with $.PLUGIN_NAME() or $(selector).PLUGIN_NAME() and should contain your main logic.
How about something like this ? It's much clearer but again it would be nice to hear from you if you can improve it without overcomplicating its simplicity.
// jQuery plugin Template
(function($){
$.myPlugin = function(options) { //or use "$.fn.myPlugin" or "$.myPlugin" to call it globaly directly from $.myPlugin();
var defaults = {
target: ".box",
buttons: "li a"
};
options = $.extend(defaults, options);
function logic(){
// ... code goes here
}
//DEFINE WHEN TO RUN THIS PLUGIN
$(window).on('load resize', function () { // Load and resize as example ... use whatever you like
logic();
});
// RETURN OBJECT FOR CHAINING
// return this;
// OR FOR FOR MULTIPLE OBJECTS
// return this.each(function() {
// // Your code ...
// });
};
})(jQuery);
// USE EXAMPLE with default settings
$.myPlugin(); // or run plugin with default settings like so.
// USE EXAMPLE with overwriten settings
var options = {
target: "div.box", // define custom options
buttons: ".something li a" // define custom options
}
$.myPlugin(options); //or run plugin with overwriten default settings
I've been googling and landed here so, I have to post some ideas: first I agree with #Raynos.
The most code out there that tries to build a jQuery plugin actually...is not a plugin! It's just an object stored in memory which is refered by the data property of a node/element. That's because jQuery should be seen and used as a tool side by side with a class library (to remedy js inconsistencies from OO architecture) to build better code and yes this is not bad at all!
If you don't like classical OO behaviour stick to a prototypal library like clone.
So what our options really?
use JQueryUI/Widget or a similar library that hides technicalities and
provides abstraction
don't use them because of complexities, learning curve and god knows future changes
don't use them becuase you want to insist on modular design, build small-increase later
don't use them because you might want porting/connecting your code with different libraries.
Suppose the issues addressed by the following scenario (see the complexities from this question: Which jQuery plugin design pattern should I use?):
we have nodes A, B and C that store an object reference into their data property
some of them store info in public and private accessible internal objects,
some classes of these objects are connected with inheritance,
all of these nodes also need some private and public singletons to work best.
What would we do? See the drawning:
classes : | A B C
------------------case 1----------
members | | | |
of | v v v
an object | var a=new A, b=new B, c=new C
at | B extends A
node X : | a, b, c : private
------------------case 2---------
members | | | |
of | v v v
an object | var aa=new A, bb=new B, cc=new C
at | BB extends AA
node Y : | aa, bb, cc : public
-------------------case 3--------
members | | | |
of | v v v
an object | var d= D.getInstance() (private),
at | e= E.getInstance() (public)
node Z : | D, E : Singletons
as you can see every node refers to an object - a jQuery approach - but these objects change wildely; they contain object-properties with different data stored in or, even singletons that should be...single in memory like the prototype functions of the objects. We don't want every object's function belonging to class A to be repeatedly duplicated in memory in every node's object!
Before my answer see a common approach I've seen in jQuery plugins - some of them very popular but I don't say names:
(function($, window, document, undefined){
var x = '...', y = '...', z = '...',
container, $container, options;
var myPlugin = (function(){ //<----the game is lost!
var defaults = {
};
function init(elem, options) {
container = elem;
$container = $(elem);
options = $.extend({}, defaults, options);
}
return {
pluginName: 'superPlugin',
init: function(elem, options) {
init(elem, options);
}
};
})();
//extend jquery
$.fn.superPlugin = function(options) {
return this.each(function() {
var obj = Object.create(myPlugin); //<---lose, lose, lose!
obj.init(this, options);
$(this).data(obj.pluginName, obj);
});
};
}(jQuery, window, document));
I was watching some slides at: http://www.slideshare.net/benalman/jquery-plugin-creation from Ben Alman where he refers at slide 13 to object literals as singletons and that just knock me over: this is what the above plugin does, it creates one singleton with no chance whatsover to alter it's internal state!!!
Furthermore, at the jQuery part it stores a common reference to every single node!
My solution uses a factory to keep internal state and return an object plus it can be expanded with a class library and split in different files:
;(function($, window, document, undefined){
var myPluginFactory = function(elem, options){
........
var modelState = {
options: null //collects data from user + default
};
........
function modeler(elem){
modelState.options.a = new $$.A(elem.href);
modelState.options.b = $$.B.getInstance();
};
........
return {
pluginName: 'myPlugin',
init: function(elem, options) {
init(elem, options);
},
get_a: function(){return modelState.options.a.href;},
get_b: function(){return modelState.options.b.toString();}
};
};
//extend jquery
$.fn.myPlugin = function(options) {
return this.each(function() {
var plugin = myPluginFactory(this, options);
$(this).data(plugin.pluginName, plugin);
});
};
}(jQuery, window, document));
My project: https://github.com/centurianii/jsplugin
See: http://jsfiddle.net/centurianii/s4J2H/1/

Categories