When using function like .add, .remove, etc. on a jquery object, the jQuery object itself isn't modified, the result get caught by the returned new jQuery object.
Is there a way to modify the object itself?
I'd need that so I can write a function with a jQuery passed by reference, something like this :
function DoThings(refJQuery) {
refJQuery.find(...).remove(...);
if (refJQuery.length > 0) {
DoThings(refJQuery);
}
}
IMPORTANT: this sample of code is just a reminder for purpose/usage of "value passed by reference".
No, jQuery collections are designed as immutable (you could change their properties but you're not supposed to, they're part of the implementation, not the API)
Composition is usually the way to go when you want to pass a immutable object to a function and have it modified (assuming taking a return value isn't feasible).
Normally, you should use a custom object. But you can also devise a generic decorator if you want:
function MutableJQueryObject(jqo){
this.jqo = jqo;
}
['add','find','filter',].forEach(function(k){
MutableJQueryObject.prototype[k] = function(){
this.jqo = $.fn[k].apply(this.jqo, arguments);
}
});
['html','remove'].forEach(function(k){
MutableJQueryObject.prototype[k] = function(){
return this.jqo[k].apply(this.jqo, arguments);
}
});
$.fn.mutable = function(){
return new MutableJQueryObject(this);
}
So you would build a mutable object :
var $e = $('body').mutable();
console.log($e.html()); // html of the body
(function($c){
$c.find('div'); // change the object
})($e);
console.log($e.html()); // html of the div
Related
I am write a compatibility layer over a legacy library function whose internal signature looks like —
function legacyLibraryFunction(context) {
context.foo.bar = "data"
return context
}
The new system however, doesn't recommend assigning custom properties directly to context and instead recommends using the context.set() method.
How do I pass context from the new system to the old one so that context.foo="data" ends up called context.set('foo', data) instead?
I'm guessing I can use Object.defineProperty for this, but I don't really understand how.
Using a setter:
You can use a setter for this:
var wrappedContext = {
set foo(val) {
context.set('foo',val);
}
}
Note: I'm assuming context.foo instead of context.foo.bar because the second part of your question does not quite match the example code.
This will create an object (wrappedContext) that will have a foo property that will call context.set() when you assign a value to it. So then you can do:
legacyLibraryFunction(wrappedContext);
Using a proxy:
Since you're using node 6.6 you can also use a proxy:
var wrappedContext = new Proxy(context,{
set: function(obj, prop, value) {
obj.set(prop,value);
}
});
EDIT:
Everything is working as I expected. It was just an error calling the template method. I mistyped a () so I was trying template.method instead of template().method;
Anyway, if somebody would like to explain me if this is a valid design pattern or if I should go in a different way I will be definitively very grateful.
I read about the module pattern and I'm trying to implement it in some of my projects. The problem is that, in my opinion, I'm twisting it too much.
I'm inspired by the google apps script style where many objects returns other objects with methods and so on and they pass arguments.
something like
object.method(var).otherMethod();
What I want to achieve is a method that receives a parameter, sets an internal variable to that parameter and then returns an object with methods that uses that variable. Here is a minified version of the code that does not work:
var H_UI =(function (window) {
var selectedTemplate,
compileTemplate = function(){},
parseTemplateFields = function(){};
//template subModule. Collect: collects the template fields and returns a JSON representation.
var template = function(templateString){
if(templateString) selectedTemplate = templateString;
return {
getHtml:function(){ return compileTemplate( parseTemplateFields( selectedTemplate ) ) } ,
collect:function(){
.. operating over selectedTemplate ...
return JSON.stringify(result)}
} };
return {
template:template
};
})(window);
If I remove the line :
if(templateString) selectedTemplate = templateString;
and replace selectedTemplate with the parameter templateString in the methods of the returned object it works as expected. I know that I cant create a set() method in the returned object and use it like this
H_UI.template().set(var)
But I find it ugly. Anyway I think that I'm messing things up.
What is the best way to construct this?
If you want H_UI.template() creates a new object every time you call template() on it, your solution does not work. Because the variable selectedTemplate is created only once when the immediate function is called.
However if your intent is this your solution works fine. (variable selectedTemplate is shared for all calls to template()).
But if you want to every call to template creates a new object. Please tell me to write my idea
Is this a valid design pattern or if I should go in a different way
Yes, enabling chaining is definitely a valid design pattern.
However, if your template() method returns a new object, that object and its methods should only depend on itself (including the local variables and parameters of the template call), but not on anything else like the parent object that template was called on.
So either remove that "global" selectedTemplate thing:
var H_UI = (function () {
function compileTemplate(){}
function parseTemplateFields(){}
// make a template
function template(templateString) {
return {
getHtml: function(){
return compileTemplate(parseTemplateFields(templateString));
},
collect: function(){
// .. operating over templateString ...
return JSON.stringify(result)
}
}
}
return {template:template};
})();
or make only one module with with a global selectedTemplate, a setter for it, and global methods:
var H_UI = (function () {
var selectedTemplate;
function compileTemplate(){}
function parseTemplateFields(){}
return {
template: function(templateString){
if (templateString)
selectedTemplate = templateString;
return this; // for chaining
},
getHtml: function(){
return compileTemplate(parseTemplateFields(selectedTemplate));
},
collect: function(){
// .. operating over selectedTemplate ...
return JSON.stringify(result)}
}
};
})();
The difference is striking when we make two templates with that method:
var templ1 = H_UI.template("a"),
templ2 = H_UI.template("b");
What would you expect them to do? In a functional design, templ1 must not use "b". With the first snippet we have this, and templ1 != templ2. However, if .template() is a mere setter, and every call affects the whole instance (like in the second snippet), we have templ1 == H_UI and templ2 == H_UI.
While this issue occurred to me specifically with KnockoutJS, my question is more like a general javascript question.
It is good to understand however that ko.observable() and ko.observableArray() return a method so when assigning a value to them, you need to call the target as method instead of simply assigning a value to them. The code that I'm working with should also support plain objects and arrays, which I why I need to resolve to a method to call to assign a value to the target.
Think of these 2 examples:
Non-working one (this context changed in called method):
// Assigning value to the target object
var target;
// target can be of any of thr following types
target = ko.observableArray(); // knockout observable array (function)
// target = ko.observable(); // knockout observable (function)
// target = {}; // generic object
// target = []; // generic array
//#region resolve method to call
var method;
if (isObservable(target)) {
// if it is a knockout observable array, we need to call the target's push method
// if it is a konckout observable, we need to call the target as a method
method = target.push || target;
} else {
// if target is a generic array, we need to use the array's push prototype
// if target is a generic object, we need to wrap a function to assign the value
method = target.push || function(item){ target = item; };
}
//#endregion
// call resolved method
method(entity);
Working one (this context is fine):
if (isObservable(target)) {
if (target.push) {
target.push(entity);
} else {
target(entity);
};
} else {
if (target.push) {
target.push(entity);
} else {
target = entity;
};
}
Now, to the actual question:
In the first approach, later in the execution chain when using a knockout observable knockout refers to this context within itself, trying to access the observable itself (namely this.t() in case someone is wondering). In this particular case due to the way of callin, this has changed to window object instead of pointing to the original observable.
In the latter case, knockout's this context is just normal.
Can any of you javascript gurus tell me how on earth my way of calling can change the 'this' context of the function being called?
Ok, I know someone wants a fiddle so here goes :)
Method 1 (Uncaught TypeError: Object [object global] has no method 'peek')
Method 2 (Works fine)
P.S. I'm not trying to fix the code, I'm trying to understand why my code changes the this context.
UPDATE:
Thanks for the quick answers! I must say I hate it when I don't know why (and especially how) something is happening. From your answers I fiddled up this quick fiddle to repro the situation and I think I got it now :)
// So having an object like Foo
function Foo() {
this.dirThis = function () {
console.dir(this);
};
};
// Instantiating a new Foo
var foo = new Foo();
// Foo.dirThis() has it's original context
foo.dirThis(); // First log in console (Foo)
// The calling code is in Window context
console.dir(this); // Second log in console (Window)
// Passing a reference to the target function from another context
// changes the target function's context
var anotherFoo = foo.dirThis;
// So, when being called through anotherFoo,
// Window object gets logged
// instead of Foo's original context
anotherFoo(); // 3rd log
// So, to elaborate, if I create a type AnotherFoo
function AnotherFoo(dirThis){
this.dirThis = dirThis;
}
// And and instantiate it
var newFoo = new AnotherFoo(foo.dirThis);
newFoo.dirThis(); // Should dir AnotherFoo (4th in log)
If you're after a way to choose the 'this' that will get used at the time of call,
you should use bind, that's exactly done for that.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
So if SomeObject has a push method, then storing it like this won't work :
var thePushMethod = someObject.push;
since you loose the context of the function when writing this.
Now if you do :
var thePushMethod = someObject.push.bind(someObject);
the context is now stored inside thePushMethod, that you just call with
thePushMethod();
Notice that you can bind also the arguments, so for instance you might write :
var pushOneLater = someObject.push.bind(someObject, 1 );
// then, later :
pushOneLater(); // will push one into someObject
Consider this example,
function Person () {
this.fname = "Welcome";
this.myFunc = function() {
return this.fname;
}
};
var a = new Person();
console.log(a.myFunc());
var b = a.myFunc;
console.log(b());
Output
Welcome
undefined
When you make a call to a.myFunc(), the current object (this) is set as a. So, the first example works fine.
But in the second case, var b = a.myFunc; you are getting only the reference to the function and when you are calling it, you are not invoking on any specific object, so the window object is assigned. Thats why it prints undefined.
To fix this problem, you can explicitly pass the this argument with call function, like this
console.log(b.call(a));
So, for your case, you might have to do this
method.call(target, entity);
Check the fixed fiddle
It seems that kendo's unobtrusive-javascript style event calls break this in my method context.
Say I have an object Foo, instantiated as bar = new Foo()
function Foo(){};
Foo.prototype.name = "Herring";
Foo.prototype.doSomething = function(e) {
alert(this.name);
};
bar = new Foo();
And attach the event using data-click for example
<a data-role="button" data-click="bar.doSomething">Click Me</a>
Object context to bar is replaced (not sure why, since we have the convenient element container.) and so this.name is undefined.
I've tried the old var self = this; in the object constructor, but it's not working, does anyone know what the best way to solve this is?
Update : Hacky Workaround
Since I really don't want to lose the benefits of wrapping up my modules as classes, I've created event call functions wrappers, which then call the methods on the appropriate object.
For example, connect the markup to a wrapper-function.
<a data-role="button" data-click="doSomething">Click Me</a>
and the wrapper function just calls the object.method.
function doSomething(e){ bar.doSomething(e) };
Now, this achieves the intended result, but it's quite horrible, each and every event called from markup must have a proxy function like the one above. So just imagine a scenario where you have 300 events... and you'll instantly see why this is horrible.
If there's no other solution, and I dearly hope there is. I'll post this workaround as an answer, but as far as I'm concerned, it's far from desirable.
Footnote
I'll be completely honest this seems like major architectural flaw in Kendo, since this method of calling events from markup is "the kendo way." Obviously it can't be patched out, because there's probably a fair bit of code already dealing with this as a reference to the html element.
Being able to override it, or being able to route these event calls through a generic handler which can pass the call on, essentially a generic proxy function, are possible ways this could be dealt with. It could also be a simple configurable value on the kendo. object.
Theoretical Solution
I'll post follow-up if this works, in theory it's possible to throw events at a generic proxy, and have it call the properly scoped function.
Say we use the event attribute to call the proxy and then create a separate attribute to convey the object/method call. For example.
<a data-role="button" data-click="prox" data-prox="o.eventHandler">Click Me</a>
The proxy function would pull prox from the attribute dataset:
method - using eval
Not because I'm evil, but needs must.
// sitting in global namespace
function prox(e){
var p = e.sender.element.data['prox'];
// make sure our delegate is a function.
if("function" == eval("typeof "+p)) {
eval(p + "(e)");
}
}
Obviously I'd like a better way to do this but, at least it's DRY.
(I'll cook a non-eval method in a moment...)
Begone Eval...
let's use the window context to locate the object / method.
function prox(e) {
var p = e.sender.element.data['prox'];
if(p.indexOf(".") == -1){
// global function : where *this* is window.
// check you've got the function if not ditch it.
if("function" == typeof window[p]) window[p](e);
} else {
// object/method (one level deep only.)
var s = p.split(".");
var o = s[0], m = s[1];
// check the object/method is a function before executing it.
if("function" == typeof window[o][p]) window[o][p](e);
}
}
Of course for global (window) scoped functions, this as the element is probably more useful, but in that case, you have a choice, I'd leave out the
version in use.
// dynamic proxy for retaining object context on methods called by
// data- attributes in Kendo.
//
// e.g.
//
// data-click="o.method"
//
// Would lose context with `o` - context would be set in the same
// way as JQuery handlers, which is an inconvenience.
//
// Alternatively we use the prox method
//
// data-click="prox"
//
// We'd then set `data-prox` on the same element, to the
// object.method pair.
//
// data-prox="o.method"
//
// This is read by prox, transformed into a method call, type
// checked and executed if it's a valid method.
//
// A `data-prox` value in any form other than `object.method` will
// be ignored, for example, `object.child.method` will fail. If
// you're doing that sort of thing, feel free to hack it.
//
// There's a backup eval() to locate the object if window doesn't
// own it. It should be possible to remove it under most
// circumstances, it's here for compatability with
// JSFiddle. (JSBin works without it.)
function prox(e) {
var p = this.element.data().prox;
if(p.indexOf(".") > -1){
var s = p.split("."); if(s.length > 2) return;
var o = s[0], m = s[1];
if("object" == typeof window[o]) {
o = window[o];
}
if("function" == typeof o[m]) o[m](e);
// comment this out in production:
l( "prox called " + s[0] + "::" + s[1] );
}
}
function l(s) { console.log(s); }
Caveats
If you have multiple handlers on the same element, prox() is unsuitable, for example, if you have data-init, data-show, etc. prox cannot differentiate, and will fail.
I'll probably update this, especially if this becomes a prevalent use-case for me.
I temporarily tried a third method, with a non-generic technique, which works like this.
Pseudo code:
MyObject {
method : function(e) {
if (this instanceof MyObject) {
// Do something with this
} else {
myInstance.method(e); // otherwise re-call the method to set this properly.
}
}
}
myInstance = new MyObject();
Not as flexible as the prox method, but suitable for my use case, and at least doesn't require a separate function proxy away from the method we want to use. We could make this more terse by doing the type check & re-call up front.
e.g.
MyObject = {
method : function(e) {
if (! this instanceof MyObject) myInstance.method(e); // re-call
// Method body...
}
}
myInstance = new MyObject();
It also meant I didn't need custom data- attributes in my markup.
Note: this method is problematic for objects which will have multiple instances, however, the objects I was applying to were single instances.
If you have handlers which need to be instance specific (which is the main reason I raised this question) the prox method is a much better fit than this, which is just a neater way of doing one-per-event proxy functions.
You may use jQuery Proxy (http://api.jquery.com/jQuery.proxy/).
function Foo(){};
Foo.prototype.name = "Herring";
Foo.prototype.doSomething = function(e) {
alert(this.name);
};
bar = new Foo();
$("btn").click($.proxy(bar.doSomething), bar);
or for inside using
$("btn").click($.proxy(this.doSomething), this);
I developed a proxy method using the JS Proxy Polyfill that simplify calling custom logic via parameters in a custon html data-* attribute.
Include https://raw.githubusercontent.com/GoogleChrome/proxy-polyfill/master/proxy.js
function makeGridTemplateEventProxy(o) {
return new Proxy(o, {
get(target, eventName) {
return function (options) {
return templateEventProxy(options, eventName);
}
}
});
}
templateEventProxy: function (options, attribute) {
if (!options.sender.element.attr('data-proxy-' + attribute)) {
throw new Error('Cannot find attribute data-proxy-' + attribute + ' on ' + options.sender.name + ' widget');
}
var proxyParams = JSON.parse(options.sender.element.attr('data-proxy-' + attribute));
method = $("#" + proxyParams.id).data(proxyParams.widget).element.data(proxyParams.method);
if (method && typeof method == 'function') {
return $.proxy(method, this)(options);
}
return null;
}
var eventproxy = makeGridTemplateEventProxy({});
for example for upload component
<input type=file ...
data-success="eventproxy.customsuccesshandler"
data-proxy-customsuccesshandler='{widget:"kendoGrid",method:"<myJqueryDataDefinedMethod>",id:"<gridId>"}'
....
/>
substitute myJqueryDataDefinedMethod and gridId with your parameters
as you see you can define in data-success an eventproxy with dynamic name
data-success="eventproxy.CUSTOMKEY"
and after define a custom attribute
data-proxy-CUSTOMKEY
data-proxy-CUSTOMKEY contains parameters ( JSON encoded ) you can use to implement a custom logic,
I suggested custom logic which can retrieve JS method stored on kendo widget grid via $.data
$("#" + proxyParams.id).data(proxyParams.widget).element.data(proxyParams.method)
You can bind method to grid for example with this
$('#my-grid-id').data("kendoGrid").element.data('methodName',function(e){
// my implementation
});
This is my current code:
var PermissionsChecker = {};
PermissionsChecker.check = function(id) {
PermissionsChecker.getPermissions(id);
}
PermissionsChecker.getPermissions = function(id) {
// do stuff
}
Two questions:
Is this the right way to construct node.js functions?
Is that line in .check the correct way to refer to a sibling function?
Thanks!
It's perfectly fine. Some notes:
Sibling function isn't really any standard term for methods of the same object. Minor note, but could cause confusion.
When a function is called as a method on some object, then the value of this inside that function refers to the object on which it was called. That is, calling check like this:
PermissionsChecker.check()
...allows you to write the function like this:
PermissionsChecker.check = function(id) {
this.getPermissions(id);
}
...which is more succinct and probably more common.
Nothing about your question is specific to node.js. This applies to JavaScript in the browser (or anywhere else), too.
You could save some typing by rewriting your example like this:
var PermissionsChecker = {
check: function(id) {
this.getPermissions(id);
},
getPermissions: function(id) {
// do stuff
}
};
So long as the function is called with PermissionsChecker.check(), you can refer to the object with this.
CodePad.
What you've done above is called an object literal, but you could choose the prototypal way also (when you need to instantiate objects - OOP stuff).
You can call this inside to refer to another object property:
PermissionsChecker.check = function(id) {
this.getPermissions(id);
}