I'm working on a dynamic form that appends groups of input fields to the page based on user input. Because of some additional functionality that needs to be attached to some of these elements, I need to create them as JSON objects. When I test this method with only one input element, the element is appended to the page, no problem. However, when I try to incorporate a second element into the process, I get [object Object] [object Object] appended to the page instead.
Here's the gist...
//THIS IS THE PROBLEM FUNCTION, WHICH IS TRIGGERED BY THE CHANGE FUNCTION BELOW
function generateInput(siteNumber, x){
select = $("<select/>", {
id: 'select'+siteNumber+''+x+'',
name: 'select'+siteNumber+'['+x+']'
}).append(list);
notes = $("<input/>", {
type: 'text',
id: 'notes'+siteNumber+''+x+'',
name: 'notes'+siteNumber+'['+x+']'
});
//IF I RETURN ONE OR THE OTHER, NO PROBLEM
return select + notes;
};
$(document).on("change", ".number_select", function(){
siteNumber = $(this).parent().attr('data-site_number');
numberFound = $(this).val();
for(x = 1; x <= numberFound; x++){
this['inputArray' + siteNumber].push(generateInput(siteNumber, x));
};
$(this).parent().append(this['inputArray' + siteNumber]);
});
I imagine that the problem is with the way that I am returning the elements at the end of generateInput, but I'm not sure of the proper way to handle this. Basically, what I am aiming to get is the select element with the text element sitting next to it.
Thanks very much!
The + operator will call the toString() method of one of the terms if the other is a string.
This is not what you want, what you want instead is to merge the jQuery objects into one
Thus
return select + notes;
Could become (see jquery doc for add):
return select.add(notes);
If you have two objects and add them, they are cast to string using .toString() method, which returns string "[object Object]". It means this is an object, which is instance of Object. If you want to return both object, you can return them for example as array:
return [species, notes];
Then you can pass the result to the .push() method using spread operator:
this['inputArray' + siteNumber].push(...generateInput(siteNumber, x));
However, since the .push() method accepts many arguments, you don't even need to modify this part of code.
Related
I want to add data variables to an element before causing a specific behavior, but this may require adding more than one data parameter. How can I accomplish this?
$("#dlg_box").data("r_redirect","index.php").dialog("open");
You can do it like this:
var data = $("#dlg_box").data();
data.r_redirect = "index.php";
data.foo = "bar";
$("#dlg_box").dialog("open");
This was taken from here.
To retrieve your values:
$("#dlg_box").data("r_redirect");
$("#dlg_box").data("foo");
JQuery's data() method also takes an JS Object as a parameter. So you might think of passing {"r_redirect": "index.php", "whatEver": "youWant" ...} etc to pass multiple values match your requirement.
Ultimately, the data() method converts your parameters into an Object. So whether you pass an Object or Key and Value separately should not matter
There are different ways to attach data to a jQuery dialog. If you need to attach multiple Data, I recomend using .data("myData", { /* OBJECT */ }, however you can also use inline string and array data. As far as why yours won't work, with so little code to go on, it could be numerous things. However, I've attached a working example of a Dialog with "params" or data for you to take example from. If you post more of your header code tho, I have a feeling we might find a syntax error or a lack of "doc ready" included. Just some thoughts. Anyway, my example:
jsFiddle
$(function() {
// Set the dialog to not open on load and clear all changes made when closed
$("#dlg").dialog({
autoOpen: false,
modal: true,
close: function(e) {
$(this).children("input").nextAll("p").remove();
}
}) // next i call for my first inner button which will show you how to get "attached" data
.children("#attached").on("click", function(e) {
var dlgData = $("#dlg").data("myData");
$(this).after($("<p />").text(dlgData.data1 + " " + dlgData.data2));
}) // finally, the button that will get the string data that was added in the HTML
.next("#inline").on("click", function(e) {
var dlgData = $("#dlg").data("inline");
$(this).after($("<p />").text(dlgData));
});
// simply open our dialog
$("button").on("click", function(e) {
// HERE data is ATTCHED to our dialog just before opening
$("#dlg").data("myData", { data1: "Hello", data2: "world" }).dialog("open")
});
});
$('#Dialog').data('data1', data1).data('data2', data2).dialog('open');
While Initializing the dialog get the values following:
var data1 = $(this).data('data1');
var data2 = $(this).data('data2');
There are some rules you should be aware of before using this!
ADDING
Adding variables using the object returned from $('.selector').data() works because the data object passes by reference, so anywhere you add a property, it gets added. If you call data() on another element, it gets changed. It is what it is what it is...
Adding an object places a object inside of the data object, as well as "extends the data previously stored with that element." - http://api.jquery.com/data/#entry-longdesc
That means that adding an obj to dataObj becomes
dataObj === { /*previous data*/, obj : { } }
Adding an array does not extend the data previously stored, but doesn't behave the same as a simple value either...
USING
If you have simple values stored, you can place them into variables and do what you want with them without changing the data object.
however
if you are using an object or array to store data on an element, beware!
Just because you store it to a variable does not mean you are not changing data value.
Just because you pass it to a function does not mean you are not changing data values!
It is what it is what it is.. unless it's simple.. then it's just a copy. :p
var data = $("#id").data(); // Get a reference to the data object
data.r_redirect = "index.php"; // Add a string value
data.num = 0; // Add a integer value
data.arr = [0,1,2]; // Add an array
data.obj = { a : "b" }; // Add an object
// but here is where the fun starts!
var r_redirectString = data.r_redirect; // returns "index.php", as expected.. cool
r_redirectString = "changed" // change the value and the compare :
data.r_redirect == r_redirectString // returns false, the values are different
var oArr = data.arr; // Now lets copy this array
oArr.push(3); // and modify it.
data.arr == oArr // should be false? Nope. returns true.
// arrays are passed by reference.
// but..
var oObj = data.obj // what about objects?
oObj["key"] = "value"; // modify the variable and
data.obj["key"] == oObj["key"] // it returns true, too!
So, resources..
What's the best way to store multiple values for jQuery's $.data()?
https://stackoverflow.com/a/5759883/1257652
I'm building a phonegap project using jQuery mobile.
I have a javascript object that I'm iterating through.
Currently the problem is this:
Below is a method in my model object. It is self recursing, and once called, will recurse through itself to the next level every time a user clicks on a list item generated by the previous level of the object.
What I am battling with is passing the iterated segment, b, into the method itself as an object. For some reason this is returned as a string called [Object], and not the object itself.
This function does work as it's displaying the first level, but something about the "firstString" string I am creating for each child seems to be turning my object into a string named object. I have removed the quotes, placed the object in braces, to no avail.
Would anyone have any idea why this is happening, I'm obviously missing something important regarding passing objects into methods whose call is generated as a string...
My code is below, and line causing the issue is firstString+="model.recurseAppTree('"+b+"');";
recurseAppTree: function(AppTree)
{
$.each(AppTree, function(a,b)
{
var firstString='<li data-role="list-divider" role="heading" data-theme="b">'+b.DisplayValue+'</li>';
if(b.Children != null)
{
$.each(b.Children, function(c,d)
{
firstString+="<li data-theme='c'><a data-transition='slide' id='id-"+d.IdValue+"' href='javascript:void(0);'>"+d.DisplayValue+"</a></li>";
firstString+="<script>";
firstString+="$('#id-"+d.IdValue+"').click(function(){";
firstString+="model.recurseAppTree('"+b+"');";
firstString+="});";
firstString+="</script>";
});
}
$("#selectview").html(firstString);
$("#selectview").listview('refresh', true);
});
},
It's just normal.
You use an object in a string context by the concatenation with +. This tells JS to implicitely cast the object to a string.
b = {}
alert(typeof b) // object
alert(typeof (''+b)) // string
You should use event delegation for your gui
1- Add a (common) class to your '' tags, e.g. unrollLink :
var firstString='<li ...><a class="unrollLink" ...></a></li>"
2- Choose a node in your html, which is a parent of all your "tree" nodes, and will always be present in your html. Delegate the click handler to this node :
$('#selectview').on('click', '.unrollLink', function(){
//this === clicked link - write a function which returns the node you want based on the "id" you set
var myNode = getNode( this.id );
model.recurseAppTree( myNode );
});
3- change your function to produce the adequate html. You don't need to add code for the click events :
recurseAppTree: function(AppTree)
{
$.each(AppTree, function(a,b)
{
var firstString='<li data-role="list-divider" role="heading" data-theme="b">'+b.DisplayValue+'</li>';
if(b.Children != null)
{
$.each(b.Children, function(c,d)
{
// add the class you chose to the clickable items :
firstString+='<li data-theme="c"><a class="unrollLink" data-transition="slide" id="id-'+d.IdValue+'" href="javascript:void(0);">'+d.DisplayValue+'</a></li>';
});
}
$("#selectview").html(firstString);
$("#selectview").listview('refresh', true);
});
},
I have a code:
var data = $('<span onclick="someFunction(' + element + ');">Element information</span>');
where element is a object.
Also, I have a function defined:
someFunction: function(element) {
console.log(element);
.... // some code
}
But when span element tries to call that function, I get error:
SyntaxError: missing ] after element list
someFunction([object Object]);
When I debug it in Firebug, I see
<span onclick="someFunction([object Object]);">
Element information
</span>
How can I normally call that function with concrete element as an argument?
You will not be able to pass the element as it is converted to string in your concatenation. When an object is converted to string it outputs: [Object object]. This is what you are seeing in your debug.
My suggestion:
You may add the element as data to the span like:
$('span).data('element', element);
And in someFunction retrieve it like:
var element = $(this).data('element');
Another option is to bind to click in Javascript at the place where your element is initialized. Like this
function anotherFunction() {
var element = {};
// Initialize element
...
// I have already got the element initialized, now I can bind the click
$('span').click(function() {
// For your debug and validation
console.log(JSON.stringify(element));
});
}
If you try to build some DOM elements with jQuery, you should do this:
// Wrong: $('<span onclick="someFunction(' + element + ');">Element information</span>');
// Right: build up the element step by step
var data = $('<span/>')
.append(
// build up the child <a> element here
$('<a href="#"/>')
.text("Element information")
//.attr('href',"#") Don't really need this
)
.click(
// Here is a real inline function
function(){
someFunction(element);
}
);
Note: .click in jQuery can be used to assign an event handler for the Click event.
In your original code, you are trying to concatenate a string with an object, which will result in applying toString to that object, converting it to a string:
console.log((new Object()).toString()); // [object Object]
console.log("blah" + (new Object())); // blah[object Object]
In your code, it seems that your object is in fact a jQuery object, but it won't make any differences.
So the resulting "code" used to form onclick is invalid:
someFunction([object Object]);
[ and ] is used to construct an Array in JavaScript, like [1, 2] is an Array with two elements. However [object Object] is an invalid JavaScript syntax so you get the error.
Anyway, this is not a correct way to build up DOM element with events, even with jQuery. The above shown the correct way.
You could also try:
var data = $('<span onclick="someFunction(' + JSON.stringify(element) + ');">Element information</span>');
The function:
someFunction: function(element) {
console.log(element);
.... // some code
}
Should return a JSON Object when called.
I have made an interesting observation. When trying to update an array that is stored in the Meteor session storage, the following code will not propagate the changes:
var tags = Session.get("Tags");
tags.push("a");
Session.set("Tags", tags);
But if I change the first line to use Session.get("Tags").slice(), everything depending on the session will update accordingly. I guess this is due to the fact that Meteor tests some references for equality and therefore does not update anything.
Is there a better way to manage lists stored in the meteor session store?
If I now try to remove an element from the collection (using array.remove() from here), the behavior turns out to be a bit ... of ... I am doing this inside a Meteor template event, the code looks like this:
"click .taglist li" : function(e) {
var tags = Session.get("Tags").slice();
var index = cardTags.indexOf(this);
Meteor._debug(Session.get("Tags").slice().indexOf("a"));
Meteor._debug("Removing tag \"" + this + "\", index: " + index, ", typeof(this) = " + typeof(this).toString());
tags.remove(index);
Session.set("Tags", tags);
}
This outputs:
1
Removing tag "a", index: -1, typeof(this) = string
So somehow, the cardTags.indexOf(this); statement seems to return -1 for almost any case. I guess I am doing something fundamentally wrong, as I am quite now to javascript, but somehow I can not figure out whats going on here.
Why will those two calls to indexOf() behave different?
I believe this is the same as this situation in Backbone.js. In order for the change event to be triggered, Meteor needs to have a new reference for the array, not just an updated copy of the old one.
In brief, in order to have the 'correct' behaviour, you'll need to clone the array, make the changes you want, and then do Session.set('foo', myCopiedArray).
In short: Use var index = cardTags.indexOf(this.toString()); instead.
Long version:
When using strings in JavaScript, those are strings, whereas typeof 'test' returns string.
Let's take a look at the following code in order to get find out another way to represent strings in JavaScript:
var func = function () {
return this;
};
console.log(func.call('test'));
The console (at least FireBug) won't show us "test", but instead it shows String {0="t", 1="e", 2="s", 3="t" }. typeof would return "object".
The content of the this statement seems to need to be an object. In order to convert a string into a "String" object we can do console.log(new String('test'));, which is the same as the previously logged value.
To convert a string object into a string (data type), just use its prototype toString.
In short, I want to use an object literal to allow me to pass a unknown amount of variables in any order to a function. Whilst this is not big deal in theory, in my code, this object literal is passed to a second function called on_change.
on_change works by comparing an element's innerHTML to a string; If it is the same, it sets a timeout to call the function again. If the element's innerHTML is different from the string, then the third parameter is executed, this will either be a function or a string. either way it will execute. I have tested this function plenty and used it for a while now.
However, I cannot seem to get the object literal to flow through the function calls...
var params = { xpos:'false'};
on_change('window_3_cont_buffer','','
if(Window_manager.windows[3].window_cont_buffer.getElementsByTagName(\'content\')[0].getElementsByTagName(\'p\')[0].innerHTML == \'ERROR\'){
alert(Window_manager.windows[3].window_cont_buffer.getElementsByTagName(\'content\')[0].getElementsByTagName(\'p\')[1].innerHTML);
return false;
} else {
Window_manager.windows[3].load_xml(\'location/view.php?location_ID=3\', \'\', ' + params + ' ); }
');
I call this as part of the form submission. After this line, I then call a function to load some content via ajax, which works fine and will trigger the code from the on_change function.
I have tested the load_xml function, it is able to call alert(param.xpos) and get the correct response. I can even added in a check for being undefined so that rest of the times I call load_xml I don't get swamped with alerts.
The load_xml function first sets up the on_change function, then calls the function to load the content to a hidden div. Once the AJAX request has updated that DIV, the on_change function should now call the parse_xml function. This pulls out the information from the xml file. However... The idea of this object literal param is that it can tell this parse_xml function to ignore certain things.
on_change("window_" + this.id + "_cont_buffer", "", "Window_manager.windows[" + this.id + "].parse_xml('" + param + "')");
This is part of load_xml, it works perfectly fine, even with the param bit in there. except, parse_xml does not seem to be able to use that parameter.
I have been able to get it to a point where parse_xml can alert(param) and give [object object] which I would of thought meant that the object literal had been passed through, but when I try and call alert(param.xpos) I get undefined.
I know this is a pig of a problem, and I could get around it by just having the function take a zillion boolean parameters, but its just not a very nice solution.
In effect, what you have is this:
var params = {param: "value"};
foo("bar('one', 'two', 'three');");
...where foo uses eval on that string, something like:
function foo(codestring) {
eval(codestring);
}
...and you're looking for how to embed params in that.
You could do this by serializing the object literal as a string so that when you combine it with the other string, and the total string is evaluated, it gets evaluated. Browsers are slowly getting JSON serialization built in, but for now you want to use jQuery, Prototype, or (if you just want this part) json2.js from Crockford, which offers JSON.stringify for turning objects that can be turned into JSON strings, into JSON strings. So:
var params = {param: "value"};
foo("bar(" + JSON.stringify(params) + ");");
But what you really want to do is refactor so that all of that logic is expressed as code, not code within a string. Then you could pass the literal directly, plus a whole raft of other benefits, including modularization, debugging, etc.
var params = {param: "value"};
function callBar() {
bar(params);
}
foo(callBar);
...changing foo so that it calls a function rather than evaling a string. (eval is to be avoided whenever possible, and to paraphrase the Dalai Lama, it's [almost] always possible.) My sample foo changes to:
function foo(func) {
func();
}
If foo needs to include additional information for bar (and if callBar is set up to handle those extra arguments), it can use Function#call or Function#apply to do that. (Those links are to MDC, but don't worry, they're not Firefox-specific, they've been in the ECMA spec for years and are nearly universally supported.)
You can't put an object inside a string. You would have to serialise the object, add it into the string, then parse it back into a structured object on the other side. The simplest way to do that would be to use JSON (via JSON.stringify or a library fallback for older browsers that don't have it), since JSON evaluates as simple JavaScript.
Note that you wouldn't get the exact same object back, but a new one with the same attributes and properties, and it only works for simple types, so you can't include a function in the object or anything.
However, in any case, passing JavaScript code around in strings is an anti-pattern to be strenuously avoided. Instead use inline functions, and you don't have to worry about what you can and can't put in a string, and you can get rid of all that unreadable wrapping and \-escaping:
var params = {xpos: 'false'};
on_change('window_3_cont_buffer', '', function() {
var w= Window_manager.windows[3];
var ps= w.window_cont_buffer.getElementsByTagName('content')[0].getElementsByTagName('p');
if (ps[0].innerHTML==='ERROR') {
alert(ps[1].innerHTML);
return false;
} else {
w.load_xml('location/view.php?location_ID=3', '', params);
}
});
Some general techniques which may be helpful for you:
// Example of calling function objects given as arguments:
function on_change(foo, callback1, callback2) {
if (foo)
callback1();
else
callback2.call(available_as_this);
}
on_change(foo, function() { your code }, function() { another code });
// Example of function taking arbitrary number of arguments:
function any_params() {
console.log('I got ' + arguments.length + 'arguments!');
console.log('First argument: ' + arguments[0]);
}
any_params('one', 'two', 'three', [], null, {});
See arguments variable and call().
I want to use object literal to allow me to pass a random amount of variables in any order to a function.
Why oh why don't you just create an object which contains the parameters and functions, and pass that around? The receiving function can just test to see if a property of the object is set before trying to use it.