javascript send object's attributes to function - javascript

i have a situation i have been thinking for a while and cant seem to find logic to solve it. hope you can.
i have a series of buttons that i would like to track, an example of one is this:
when a button is clicked a function is called, sending some strings and an object with a series of attributes. different buttons have different sets of attributes.
$(".btn").on('click', function(ev){
trackFunction("Purchase","apply_promo", { product_code: "product1", last_page: "home", refer: "facebook", promo: "12345" });
});
The track function will then receive this and send it to my analytics software
but i need it to be sent in this format
dataToSend = { event_type : eventType, event_value: eventValue, data };
where data is each and every attribute from the object the button is sending.
in that example it woud be something like this
dataToSend = { event_type : eventType, event_value: eventValue, data.product_code, data.last_page, data.refer, data.promo };`
here is my tracking function :
function trackFunction(eventType, eventValue, data ) {
dataToSend = { event_type : eventType, event_value: eventValue, data };
analyze(dataToSend);
return true;
}
the problem here is that not every button is sending the same attributes inside the object so i cant hardcode the output.
hope i made myself clear.
and thank you so much.

Either you add the common properties to the object each time. The downside is that you're altering a function parameter which is generally a bad idea because it might cause weird bugs:
function track(a, b, data) {
data.a = a;
data.b = b;
sendData(data);
}
Or you turn it around by iterating over the object's properties:
function track(a, b, data) {
var send = {a: a, b: b}, k;
for (k in data) {
if (data.hasOwnProperty(k)) {
send[k] = data[k];
}
}
sendData(send);
}
If you use lodash, underscore, or jQuery (or probably every other JS framework out there) you usually have an extend function at your disposal, which does pretty much what you want (and internally the same as the second example).
// with underscore:
function track(a, b, data) {
sendData(_.extend({a: a, b: b}, data));
}

Related

Passing metadata between functions

I created an API with Node.js, and I don't want the API to change nor do I want to add extra parameters to a function. However, the internal code in the library needs to now send some metadata between an internal API method and an external facing method.
Is there a way to pass (meta) data between functions somehow in JS that does not involve parameters/arguments?
TL;DR, it would be really useful to pass metadata between functions for the purposes of JS APIs, that should not change signatures.
(One trick is if the function is created everytime it is called, you can assign data onto the function object itself, but that is not true in this case (function is not being created everytime it is called).)
The trick I am currently using - and it is not a good one - there is an options {} object being used in the API. I am passing a hidden property in that objects object "__preParsed". The user will use that objects object as they normally would, behind the scenes I use it for some bookkeeping stuff that they don't need to know about.
Ok here is the code:
//public API
beforeEach.cb = function (desc, opts, fn) {
const _args = pragmatik.parse(arguments, rules.hookSignature);
_args[ 1 ].cb = true;
return beforeEach.apply(ctx, _args);
};
beforeEach = function (desc, opts, aBeforeEach) {
handleSetupComplete(zuite);
const _args = pragmatik.parse(arguments, rules.hookSignature);
const obj = { //have to do this until destructuring works
desc: _args[ 0 ],
opts: _args[ 1 ],
fn: _args[ 2 ]
};
handleBadOptionsForEachHook(obj.opts, zuite);
return 'there is more code but I omitted it';
};
as you can see the first method calls the second, or the second can be called directly, both are public APIs.
We need to parse the arguments in both calls, but as an optimization, we shouldn't have to parse them a second time if the second method was called by the first instead of directly.
The solution I will use for the moment is:
beforeEach.cb = function (desc, opts, fn) {
const _args = pragmatik.parse(arguments, rules.hookSignature);
_args[ 1 ].cb = true;
_args[ 1 ].__preParsed = true;
return beforeEach.apply(ctx, _args);
};
the opts options object is public, but the user won't know about the __preParsed property. The internal API will.
The problem with this is that the user can call the public API directly without an options object, and since the signature is very much varargs, then I really don't know until I have parsed it with my parse engine, which arg if any is the objects object!
You could abuse the this object to carry non-argument metadata in as follows by invoking your function using Function.prototype.call:
function inner (arg1, arg2) {
console.log('inner called with', arg1, arg2)
console.log('inner metadata', this._meta_count)
}
inner.call({_meta_count: 17}, 'ARG ONE', 'ARG TWO')
inner.call({_meta_count: 18}, 'ARG ONE B', 'ARG TWO B')
You could just add a new undocumented parameter to the end. JavaScript won't care and previous calls will still work, why is that a problem for you?
If you are checking parameter count and throwing errors, you could expect the hidden parameter to be an object with a magic property, if it's not, throw the error.
function go(a, b, c, _internal) {
if (_internal && ! _internal.hasOwnProperty('_magic')) {
throw new Error('invalid internal parameter passed');
}
}
You can get a little more paranoid and store the magic property as a Symbol, then the caller couldn't pass it by accident, they would have to be acting nefariously.
function go(a, b, c, _internal) {
if (_internal && !_internal.hasOwnProperty(go.internalParamProp)) {
throw new Error('invalid internal parameter passed');
}
console.log("Internal param", _internal && _internal[go.internalParamProp])
}
// Symbol for the magic property name to avoid accidental passing of internal param
go.internalParamProp = Symbol('');
// Passing the internal param
// Uses JS syntax that is not yet supported in some browsers
// If it's a concern, use or var obj={}; obj[go.internalParamProp] = 45
go(1, 2, 3, {
[go.internalParamProp]: 45
})
// Regular call
go(1, 2, 3)
// Invalid call
go(1, 2, 3, 4)

Passing parameters on JQuery .trigger

I am using JQuery trigger but am not sure what the correct syntax is to pass parameters in my situation. Here is where I am making the call :
$('#'+controlName).trigger(event);
Here is where I am doing the event binding :
$(window).on('onPartialRendered', onPartialRendered);
And here is my event handler :
var onPartialRendered = function () {
.....
};
Everything works fine until I try to pass parameters. What would be the correct way to do it as per my example?
First parameter is always string with event name and next parameters are additional data:
.trigger('foo', [1, 2]);
.on('foo', function(event, one, two) { ... });
Special thanks for Rocket Hazmat
Example:
var controller = {
listen: function (event, json, string) {}
};
$('body').trigger('this_works', [{data: [1, 2, 3]}, 'something']);
$('body').on('this_works', function (event, json, string) {
controller.listen(event, json, string);
});
Remote partial:
Please do not use this way. There is many article about this problem in network. This take much time and generate unnecessary traffic in network. Please use this way:
var template = $('#templates #example_template').detach();
var clone = template.clone();
clone.find('.some_field').val('new_data');
clone.attr('id', null);
$('table tbody').append(clone);

Javascript: How change function call params on the fly?

I'm receiving some 'body' content from a jquery's json call, where I can get the unique javascript element returned by doing:
script_element = $(data.body)[1]
This equals to:
<script type=​"text/​javascript">​
updater('foo', 'bar', {}, '0', constant='');
</script>​
So, typeof script_element returns "object"
And, if I run script_element.innerText, I can get:
updater('foo', 'bar', {}, '0', constant='');
After receiving this script, what I'm doing right now is just run an eval on it, but searching around I couldn't get a way to run eval changing function call params.
What I'm trying to do is change the third param of the call, in this case the {}, that can change depending on the return of the json call, so I can't just search for {}.
I could also do script_element.text.split(',')[2] for example, and change this text on the fly, but I was thinking there should be a better way to do this.
I don't know if javascript can recognize and treat a "future method call", but still think there should be a better way.
Any idea?
What you could do is shadowing the function so as to be able to alter the third argument. You ought to define that shadowing function before fetching the JSON.
var originalUpdater = updater; // keep old function to call
// overwrite (shadowing)
updater = function(a, b, c, d, e) {
// change c appropriately here
originalUpdater(a, b, c, d, e);
}
Then you can still just eval it (which is not very safe, but that's not your point if I'm not mistaking), and it will call the shadow function.
A more generic shadowing method would be along the lines of:
var originalUpdater = updater; // keep old function to call
// overwrite (shadowing)
updater = function() {
// change arguments[2] appropriately here
originalUpdater.apply(this, arguments);
}
Fiddle: http://jsfiddle.net/n7dLX/
Change the server. Rather than returning
<script type=​"text/​javascript">​
updater('foo', 'bar', {}, '0', constant='');
</script>​
Return
{
"method": "updater",
"params": [
"foo", "bar", {}, "0", ''
]
}
Assuming that you cannot change what is being sent over from the server, you can simply run through the innerText with a regular expression and pass update the HTML before you insert it.
var replacer = /\w+\(([^()]+)\)/gi;
script_element.innerText.replace(replacer, function(matched_text, func_params){
var orig_func_params = func_params;
// Make changes to func_params here.
return matched_text.replace(orig_func_params, func_params);
});
This can be functionized by doing the following:
var replacer = /\w+\(([^()]+)\)/gi;
function replace_arg(script_element, arg_index, replacement_value) {
script_element.innerHTML = script_element.innerHTML.replace(replacer,
function(matched_text, func_params){
var orig_func_params = func_params;
func_params = func_params.split(",");
if (arg_index >= func_params.length) {
throw new RangeError(arg_index + " is out of range. Total args in function:" + func_params.length);
}
func_params[arg_index] = JSON.stringify(replacement_value);
return matched_text.replace(orig_func_params, func_params.join(","));
});
return script_element;
}
This can be called in this way:
script_element = replace_arg(script_element, 3, {"new":"arg"});
I don't understand what you are doing, but in general if you don't want to rely on the order of parameters make the function take one parameter that is an object whose properties are the parameters:
function add(params) {
var a = params.hasOwnProperty("paramA") ? params.paramA : 0;
var b = params.hasOwnProperty("paramB") ? params.paramB : 0;
return a + b;
}
add({paramA: 1, paramB: 2});
In this case you should use hasOwnProperty to check if the function was passed the parameter you are looking for before trying to access it.

JQuery/JavaScript : refactoring nested functions

I have this interesting jQuery function. It basically adds a click handler to link, and when that is clicked, it will load a form to allow the user to edit the content. and the form is submitted by AJAX, and will display a success message when it's done.
The outline is below; needless to say, this is messy. I could have each of the callback as a class method. What other ways are there to refactor nested functions? I am also interested to see if there are ways that variables declare in a parent function still retain its value down to the nested function after refactoring
$('a.edit').click( function() {
// ..snipped..
// get form
$.ajax({
success: function() {
// add form
// submit handler for form
$(new_form).submit(function() {
// submit via ajax
$.ajax({
success: function(data) {
// display message
}
})
})
}}
)
}
I guess the interesting part of your question is how to refactor without loosing access to the closure variables. Here is my suggestion:
Version one: nested, with closures and variable access:
var a;
$('a.edit').click( function() {
var b;
$.ajax({
success: function() {
var c;
$(new_form).submit(function() {
var d;
$.ajax({
success: function(data) {
// a,b,c,d are all visible here.
// note that a references the same object for all calls of the success function, whereas d is a different variable for each call of submit.
// this behaviour is called closure: the 'enclosed' function has access to the outer var
}
})
})
}
})
})
Version two: less nested, but without closures and without variable access:
var a;
$('a.edit').click(onEdit);
var onEdit = function() {
var b;
$.ajax({success: onEditSuccess});
};
var onEditSuccess = function() {
var c;
$(new_form).submit(onSubmit);
};
var onSubmit = function() {
var d;
$.ajax({success: onSubmitSuccess});
}
var onSubmitSuccess = function(data) {
// a is visible (global var)
// b,c,d NOT visible here.
};
Version three: less nested and with unnamed functions and parameters to get access to the closure variables:
var a;
$('a.edit').click(function(){onEdit(a)});
var onEdit = function(a) {
var b;
$.ajax({success: function(){onEditSuccess(a,b)}});
};
var onEditSuccess = function(a,b) {
var c;
$(new_form).submit(function(){onSubmit(a,b,c)});
};
var onSubmit = function(a,b,c) {
var d;
$.ajax({success: function(data){onSubmitSuccess(data,a,b,c,d)}});
}
var onSubmitSuccess = function(data,a,b,c,d) {
// a,b,c,d are visible again
// nice side effect: people not familiar with closures see that the vars are available as they are function parameters
};
You can easily refactor this to make it much more readable. The key concept to grasp is that you can refer to named functions in callbacks as well as anonymous ones. So, for instance:
function clickHandler() {
alert("Link clicked");
}
$('a').click(clickHandler);
My preference is always to give the functions names according to what they do (e.g. loadImage, rather than the event that you intend to trigger them (e.g. clickLink. This makes your code clearer and makes later changes much easier. In this case, I would structure my code like this:
$(document).ready(function(){
$('a.edit').click(loadFormStart);
function loadFormStart() { // get form
$.ajax({
success: loadFormEnd
});
}
function loadFormEnd(data) { // add form & handler
$('new_form').submit(handleFormStart);
}
function handleFormStart() { // submit form
$.ajax({
success: handleFormEnd
});
}
function handleFormEnd(data) { // receive form data
//display message
}
});
I'd also advise you to read Code Organization on jqfundamentals which gives a similar approach to this using an object literal.
Interesting question. Personally I don't mind the above. Commenting is key, so you could consider qualifying the closing braces with some:
} //success: function(data)
}) //$.ajax({
}) //$(new_form).submit(
...etc
I would also look at aligning the brackets correctly (at first clance, your }} is a little mystifying).
If it comes to 'generic' nesting strategies, the only other suggestion I have is to move code out other functions. The of course means that you have the function decalred in memory, but may make it more readable.
You could also consider a specific strategy that relates to this code. For example, rather than manually binding a submit to new_form can you use the live function in some way to ensure that it is done automatically?
On a completely unrelated note, you should probably add some ; at the end of each of the bracketed lines!

Creating methods on the fly

Hi I'm trying to author a jQuery plugin and I need to have methods accessible to elements after they are initialized as that kind of object, e.g.:
$('.list').list({some options}); //This initializes .list as a list
//now I want it to have certain methods like:
$('.list').find('List item'); //does some logic that I need
I tried with
$.fn.list = function (options) {
return this.each(function() {
// some code here
this.find = function(test) {
//function logic
}
}
}
and several other different attempts, I just can't figure out how to do it.
EDIT:
I'll try to explain this better.
I'm trying to turn a table into a list, basically like a list on a computer with column headers and sortable items and everything inbetween. You initiate the table with a command like
$(this).list({
data: [{id: 1, name:'My First List Item', date:'2010/06/26'}, {id:2, name:'Second', date:'2010/05/20'}]
});
.list will make the <tbody> sortable and do a few other initial tasks, then add the following methods to the element:
.findItem(condition) will allow you to find a certain item by a condition (like findItem('name == "Second"')
.list(condition) will list all items that match a given condition
.sort(key) will sort all items by a given key
etc.
What's the best way to go about doing this?
If you want these methods to be available on any jQuery object, you will have to add each one of them to jQuery's prototype. The reason is every time you call $(".list") a fresh new object is created, and any methods you attached to a previous such object will get lost.
Assign each method to jQuery's prototype as:
jQuery.fn.extend({
list: function() { .. },
findItem: function() { .. },
sort: function() { .. }
});
The list method here is special as it can be invoked on two occasions. First, when initializing the list, and second when finding particular items by a condition. You would have to differentiate between these two cases somehow - either by argument type, or some other parameter.
You can also use the data API to throw an exception if these methods are called for an object that has not been initialized with the list plugin. When ('xyz').list({ .. }) is first called, store some state variable in the data cache for that object. When any of the other methods - "list", "findItem", or "sort" are later invoked, check if the object contains that state variable in its data cache.
A better approach would be to namespace your plugin so that list() will return the extended object. The three extended methods can be called on its return value. The interface would be like:
$('selector').list({ ... });
$('selector').list().findOne(..);
$('selector').list().findAll(..);
$('selector').list().sort();
Or save a reference to the returned object the first time, and call methods on it directly.
var myList = $('selector').list({ ... });
myList.findOne(..);
myList.findAll(..);
myList.sort();
I found this solution here:
http://www.virgentech.com/blog/2009/10/building-object-oriented-jquery-plugin.html
This seems to do exactly what I need.
(function($) {
var TaskList = function(element, options)
{
var $elem = $(element);
var options = $.extend({
tasks: [],
folders: []
}, options || {});
this.changed = false;
this.selected = {};
$elem.sortable({
revert: true,
opacity: 0.5
});
this.findTask = function(test, look) {
var results = [];
for (var i = 0,l = options.tasks.length; i < l; i++)
{
var t = options['tasks'][i];
if (eval(test))
{
results.push(options.tasks[i]);
}
}
return results;
}
var debug = function(msg) {
if (window.console) {
console.log(msg);
}
}
}
$.fn.taskList = function(options)
{
return this.each(function() {
var element = $(this);
if (element.data('taskList')) { return; }
var taskList = new TaskList(this, options);
element.data('taskList', taskList);
});
}
})(jQuery);
Then I have
$('.task-list-table').taskList({
tasks: eval('(<?php echo mysql_real_escape_string(json_encode($tasks)); ?>)'),
folders: eval('(<?php echo mysql_real_escape_string(json_encode($folders)); ?>)')
});
var taskList = $('.task-list-table').data('taskList');
and I can use taskList.findTask(condition);
And since the constructor has $elem I can also edit the jQuery instance for methods like list(condition) etc. This works perfectly.
this.each isn't needed. This should do:
$.fn.list = function (options) {
this.find = function(test) {
//function logic
};
return this;
};
Note that you'd be overwriting jQuery's native find method, and doing so isn't recommended.
Also, for what it's worth, I don't think this is a good idea. jQuery instances are assumed to only have methods inherited from jQuery's prototype object, and as such I feel what you want to do would not be consistent with the generally accepted jQuery-plugin behaviour -- i.e. return the this object (the jQuery instance) unchanged.

Categories

Resources