How can I map one object usage to route to 2 objects? - javascript

I use totango for some usage tracking. Now, while we try to rework the way we track, I want to send trackings into 2 different totango accounts, for a transition period.
I've managed to split the objects into window.totango_old and window.totango_beta.
Now instead of replacing all the old usages of window.totango, I was wondering whether I can make window.totango simply apply any arbitrary method I use on it onto the 2 different objects specified above.
I've tried figuring out the usage with .apply(), but I can't fully grasp how it would work in my case.
I want to avoid doing this:
window.totango = function() {
return {
track: function(event, value) {
window.totango_old.track(event, value);
window.totango_beta.track(event, value);
}
}
}
Because it means I have to map the usable functions one by one. Is there a "catch-all" way that would pass any method I call on an object, and let me get its name and arguments, to pass on to different objects dynamically?
I tried running a test, like so:
window.test2 = function() {
return {
testFunc: function(a, b) {
console.log([a, b]);
}
}
};
window.test = function() {
this.apply(window.test2, arguments)
// also tried: window.test2.apply(window.test2, arguments)
};
window.test.testFunc("1", "2");
But I received the following exception:
Uncaught TypeError: undefined is not a function

You could use something like this:
window.callFunction = function( method, args ) {
var res = { };
//Set the default values
method = method || 'track';
args = args || [ ];
//in case we want the result of the functions
res.old = window.totango_old[ method ].apply( window.totango_old, args );
res.beta = window.totango_beta[ method ].apply( window.totango_beta, args );
return res;
}
Because totango_old is an object, you can use the name of the method as the index, then call apply on the returned function and pass your arguments. The first parameter of apply is the value of "this" in that context. Depending on how the module is set up it is important to have the right value in the first parameter. The second parameter passed to apply is the arguments to pass to the function.
You could possibly do something like this
function TotangoFill () {
}
TotangoFill.prototype = {
callFunction: function ( method, args ) {
var res = { };
//Set the default values
args = args || [ ];
//in case we want the result of the functions
res.old = window.totango_old[ method ].apply( window.totango_old, args );
res.beta = window.totango_beta[ method ].apply( window.totango_beta, args );
return res;
},
track: function( event, value ) {
// arguments is an array of all the arguments passed to this function
return this.callFunction( 'track', arguments );
},
anotherMethod: function( ) {
//You don't need to define the parameters, just pass them on
return this.callFunction( 'anotherMethod', arguments );
},
customEvent: function( value ) {
//Add another layer of abstraction
return this.track( 'customEvent', value );
}
}
window.totango = new TotangoFill( )

You have a problem with your attempt to wrap totango which explains the error in your test, and can be easily resolved without changing your invocations.
Specifically, you need to actually invoke the function that you assigned to window.totango such that totango contains the returned object, and not the function itself, i.e.:
window.totango = (function() {
return {
track: function(event, value) {
window.totango_old.track(event, value);
return window.totango_beta.track(event, value);
}
}
})(); // invocation

Related

Rx.Observable.bindCallback with scope in rxjs

It seems in rxjs 4.x, Rx.Observable.fromCallback accept scope as the second parameter, but in 5.0, this method is changed to Rx.Observable.bindCallback and doesn't accept scope parameter. How to add scope parameter in bindCallback. For example in ES6.
class Test {
constructor(input) {
this.input = input;
}
callback(cb) {
return cb(this.input);
}
rx() {
// this works on rx 4.x
// var observable = Rx.Observable.fromCallback(this.callback, this)();
// this doesn't work, because this.callback function doesn't use original this, so cannot get this.input
var observable = Rx.Observable.bindCallback(this.callback)();
// Work around: Rx.Observable.bindCallback(this.callback)();
// var me = this;
// var observable = Rx.Observable.bindCallback((cb) => {me.callback(cb);})();
observable.subscribe(
input => console.log('get data => ' + input),
err => console.log('get error =>' + err),
() => console.log('complete')
);
}
}
new Test(100).rx();
There is an example at http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#static-method-bindCallback which shows how to do this.
Use bindCallback on object method
const boundMethod = Rx.Observable.bindCallback(someObject.methodWithCallback);
boundMethod.call(someObject) // make sure methodWithCallback has access to someObject
.subscribe(subscriber);
You can call it immediately without declaring a variable, and also pass args like this:
Rx.Observable.bindCallback(someObject.callback).call(someObject,<args>)
So to bind to this you can simply call
Rx.Observable.bindCallback(this.callback).call(this,<args>)
It works for me, when I add this to the constructor
constructor(input) {
this.input = input;
this.callback = this.callback.bind(this)
}

Split an object into arrays

I'm currently writing a library using jQuery, and I'm facing a wall I can't pass alone.
First, here is some code :
(function($) {
// default options
var defaults = {
option1: "what ever",
option2: "what ever"
};
// available methods
var methods = {
method1: function(param1) {
console.log(param1);
/* ... */
},
method2: function(param1, param2) {
console.log(param1);
console.log(param2); // gives undefined
/* ... */
}
};
// Where magic happens
$.fn.pskChart = function(params) {
if (methods[params] != undefined) {
if (this.length > 0) {
return $(this).each(function(i) {
return methods[params].apply($(this), Array.prototype.slice.call(arguments, 1));
});
}
} else {
$.error("Method " + params + " doesn't exist for pskChart");
}
}
})(jQuery);
$("#sample").pskChart("method1", "param1"); // will work
$("#sample").pskChart("method2", "param1", "param2"); // won't work
This code is working in case I provide only two parameters (method and another parameter), but won't work if I have more parameters
I understand that Array.prototype.slice.call(arguments, 1) returns only one object containing all the remaining arguments.
In the second example, method2 will be called with only one parameter that contains ["param1","param2"]
I would like to "split" this object in order to provide as many parameters I have.
I feel like methods[params].apply($(this), Array.prototype.slice.call(arguments, 1)); but I have no clue how to fix it.
Note that I could have an infinite (or at least big) number of parameters (in addition to the method, that will always be first).
Playing with Array.slice would not be a great (nor proper) solution.
I don't see why it would work even with just one parameter, as you're using the wrong arguments object.
Instead (see comments):
// Where magic happens
$.fn.pskChart = function(params) {
var args; // **Change** We'll use this below
if (methods[params] != undefined) {
if (this.length > 0) {
// **Change** Grab the arguments here, in the `pskChart` function
args = Array.prototype.slice.call(arguments, 1);
return $(this).each(function(i) {
// **Change** Use them here; you can't use `arguments` here
// because, it will be the arguments to this anonymouus
// function, not the ones to `pskChart`
return methods[params].apply($(this), args);
});
}
} else {
$.error("Method " + params + " doesn't exist for pskChart");
}
}
Side note: params is a very strange name for an argument you're using as a method name. Perhaps methodName?
Side note 2: Rather than repeatedly looking up methods[params], consider using a variable. Large sets may benefit from the reduced work.
Side note 3: This line inside your each loop is almost certainly a mistake:
return methods[params].apply($(this), args);
That will return the return value of the method to jQuery's each code. jQuery's each code will completely ignore that vallue unless it's === false, in which case it will stop the each loop. Not at all likely to be what you meant that to do.
Side note 4: Your pskChart function, being a jQuery plugin, should return this unless it has a good reason for returning something else. Right now, the return value is chaotic: If the set has no elements, you return undefined (implicitly); if the set has objects, you return a new jQuery object, not this, because of this line:
return $(this).each(function(i) {
There's no reason for using $() there, this is already a jQuery set (which is why the previous line using this.length works).
Side note 5: You're missing a semicolon from the assignment statement (e.g., there should be one after the closing } on the function expression).
Recommendation:
// Where magic happens
$.fn.pskChart = function(methodName) {
var args, method = methods[methodName];
if (!method) {
// Throws
$.error("Method " + params + " doesn't exist for pskChart");
}
if (this.length > 0) {
args = Array.prototype.slice.call(arguments, 1);
this.each(function() {
method.apply($(this), args);
});
}
return this;
};

Rivets.js event handlers with custom arguments

I've just started with Rivets.js, which looks promising as simple data-binding framework.
I've arrived at the point that I don't know how to pass "custom arguments" to the rv-on-click binder, so I tried to take the idea from this: https://github.com/mikeric/rivets/pull/34
My code:
rivets.binders["on-click-args"] = {
bind: function(el) {
model = this.model;
keypath = this.keypath;
if(model && keypath)
{
var args = keypath.split(' ');
var modelFunction = args.shift();
args.splice(0, 0, model);
var fn = model[modelFunction];
if(typeof(fn) == "function")
{
this.callback = function(e) {
//copy by value
var params = args.slice();
params.splice(0, 0, e);
fn.apply(model, params);
}
$(el).on('click', this.callback);
}
}
},
unbind: function(el) {
$(el).off('click', this.callback);
},
routine: function(el, value) {
}
}
This code is working, my question is: is this the correct way?
If you want to pass a custom argument to the event handler then this code might be simpler:
rivets.configure({
// extracted from: https://github.com/mikeric/rivets/issues/258#issuecomment-52489864
// This configuration allows for on- handlers to receive arguments
// so that you can onclick="steps.go" data-on-click="share"
handler: function (target, event, binding) {
var eventType = binding.args[0];
var arg = target.getAttribute('data-on-' + eventType);
if (arg) {
this.call(binding.model, arg);
} else {
// that's rivets' default behavior afaik
this.call(binding.model, event, binding);
}
}
});
With this configuration enabled, the first and only argument sent to the rv-on-click handler is the value specified by data-on-click.
<a rv-on-click="steps.go" data-on-click="homePage">home</a>
This is not my code (I found it here), but it does work with Rivets 0.8.1.
There is also a way to pass the current context to the event handler. Basically, when an event fires, the first argument passed to the handler is the event itself (click, etc), and the second argument is the model context.
So lets say that you are dealing with a model object that is the product of a rv-each loop...
<div rv-each-group="menu.groups">
<input rv-input="group.name"><button rv-on-click="vm.addItem">Add item</button>
___ more code here ___
</div>
Then you can access the current "group" object in the event handler like this:
var viewModel = {
addItem: function(ev, view) {
var group = view.group;
}
};
More details on this technique can he found here https://github.com/mikeric/rivets/pull/162
I hope this helps.
There is another answer here:
https://github.com/mikeric/rivets/issues/682
by Namek:
You could define args formatter:
rivets.formatters.args = function(fn) {
let args = Array.prototype.slice.call(arguments, 1)
return () => fn.apply(null, args)
}
and then:
rv-on-click="on_click | args 1"
Please note that args is not a special id, you can define anything instead of args.
To pass multiple arguments use space: "on_click | args 1 2 3 'str'"

jQuery .data() and plugins

I am trying to use jQuery's .data() tool in a jQuery plugin but I'm getting some odd results.
Here is a cut down version of my plugin with the relevant code:
(function( $ ){
var methods = {
init : function( options ) {
// Do some stuff
this.data('name', 'New Component');
return this;
},
getStateData : function () {
// Function returns all data for save
var state = new Object;
state.name = this.data('name');
// snip ... Add some other bits to state
// ...
return state;
},
setStateData: function (args) {
var key = args[0];
var value = args[1];
// snip
this.data(key, value);
}
};
$.fn.component7Segment = function(method) {
if ( methods[method] ) {
return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || ! method ) {
return methods.init.apply( this, arguments );
} else {
$.error( 'Method ' + method + ' does not exist on jQuery.designComponent' );
}
};
})( jQuery );
If I call $(instance-of-plugin).component7Segment('getStateData'); the plugin returns the correct 'name' however if I call setStateData to change the name it has not changed the 'name' value when I next call getStateData.
Doing console.log(this); in each function looks slightly different so I started trying:
$.data($('#object-id'), 'name', value);
This still doesn't have the desired effect.
Can anybody see what I am missing?
Assuming you're calling setStateData like this:
$(instance).component7Segment("setStateData", "name", "Foo");
the setStateData function should be changed to look like this:
setStateData: function () {
var key = arguments[0];
var value = arguments[1];
// snip
this.data(key, value);
}
Note the removal of the args parameter and the use of the special arguments array-like object instead.
Example: http://jsfiddle.net/andrewwhitaker/ShFHC/
Your original problem was most likely that you were calling setStateData with parameters name and "foo". args[0] and args[1] was actually accessing the characters in name at that position instead of the arguments passed to the function.

Trying to learn jQuery plugin development

So I'm trying to learn how to implement method collection for a plugin based on this example: http://docs.jquery.com/Plugins/Authoring
What I cannot understand is how options that are extended with defaults for the plugin get sent to the individual methods.
I'm pretty sure any original options get sent to the method here:
return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
So how can you extend these arguments with defaults? The example doesn't really define how to do this...
var methods = {
init : function( options ) {
return this.each(function(){
$(window).bind('resize.tooltip', methods.reposition);
});
}
}
Also, here is the code from the example plugin authoring page:
(function( $ ){
var methods = {
init : function( options ) {
return this.each(function(){
$(window).bind('resize.tooltip', methods.reposition);
});
},
destroy : function( ) {
return this.each(function(){
$(window).unbind('.tooltip');
})
},
reposition : function( ) { // ... },
show : function( ) { // ... },
hide : function( ) { // ... },
update : function( content ) { // ...}
};
$.fn.tooltip = function( method ) {
if ( methods[method] ) {
return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || ! method ) {
return methods.init.apply( this, arguments );
} else {
$.error( 'Method ' + method + ' does not exist on jQuery.tooltip' );
}
};
})( jQuery );
Looks like my previous answer was closer to the mark than I previously thought.
Yes, this line is passing on the arguments:
return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
Using .apply(), you can call a method, change its context (this value), and give it a collection of arguments instead of individual ones. Handy if you don't know how may arguments need to be passed on.
So you can break the above line down like this:
// reference the arguments passed to the plugin
var args = arguments;
// eliminate the first one, since we know that's just the name of the method
args = Array.prototype.slice.call( arguments, 1 )
// call the method that was passed,
// passing along the Array of the arguments (minus the first one),
return methods[method].apply( this, args);
// Note that it is being called from the context of "this" which is the jQuery object
So if the plugin was called like this:
$('.someElem').tooltip('destroy', 'someArg', 'anotherArg' );
The code will locate the "destroy" method, slice "destroy" off of the Arguments object, and call "destroy" passing along the Array of remaining arguments.
you use $.extend(existingObject || {} (new object), oneObject, secondObject, nthObject)
var mydefaults = { myDefaultArg : "foo" }
var inputOptions = { myDefaultArg : "Somethin else" }
var options = $.extend({}, mydefaults, inputOptions);
options.myDefaultArg == "Somethin else";
To access data, or to save them,you can use the data method
so if you are in the plugin, "this" is the jquery element element, you can now save data into it.
$(this).data("pluginname.somedataname", data);
and retriev it
var data = $(this).data("pluginname.somedataname");

Categories