I need to store a Javascript object into a div (or in production: many divs). It really needs to go into the data-object="" attribute – i don't want to add it later via $('div').data('object').
The below code only returns "{"... I feel like I have tried every combination of stringify and parse and whatnot.
Does anybody have a clue how to retrieve my object?
var Module = {
div: function() {
var object = {
name: 'one',
type: 'two'
};
var html = '<div data-object="' + JSON.stringify(object) + '"></div>';
var div = $(html).appendTo('body');
// This just returns "{" instead of my object
console.log(div.data('object'));
}
}
$(document).click(function() {
Module.div();
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
This is because JSON gets encoded using double quotes ", and you also encapsulate it between double quotes. The problem with this is that it produces :
<div data-object="{" name":"one","type":"two"}"></div>
so, when you read data-object, its value is effectively "{".
Try encapsulating it into simple quotes :
var Module = {
div: function() {
var object = {
name: 'one',
type: 'two'
};
var html = "<div data-object='" + JSON.stringify(object) + "'></div>";
var div = $(html).appendTo('body');
// This just returns "{" instead of my object
console.log(div.data('object'));
}
}
$(document).click(function() {
Module.div();
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Now the output is valid :
<div data-object='{"name":"one","type":"two"}' ></div>
You can also set the attribute programmatically with jQuery :
var Module = {
div: function() {
var object = {
name: 'one',
type: 'two'
};
var div = $("<div>")
.attr("data-object", JSON.stringify(object))
.appendTo('body');
console.log(div.data('object'));
}
}
$(document).click(function() {
Module.div();
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Replace var html = '<div data-object="' + JSON.stringify(object) + '"></div>';
With var html = '<div data-object=' + JSON.stringify(object) + '></div>';
No need to add the additional double quotes
var Module = {
div: function() {
var object = {
name: 'one',
type: 'two'
};
var html = '<div data-object=' + JSON.stringify(object) + '></div>';
var div = $(html).appendTo('body');
// This just returns "{" instead of my object
console.log(div.data('object'));
}
}
$(document).click(function() {
Module.div();
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body>
</body>
Related
I have a website with a list of json objects arranged something like this:
[
{
"a": true or false,
"b": "information",
"c": "information",
"d": "information",
"e": "information"
},
...
]
The idea of this code is to print out all the objects on a table and have a checkbox which filters out the false objects out when needed. The site is supposed to just have the the table with unfiltered object on there, but after I added the checkbox event listener the full table list disappeared. When I check the checkbox I get the filtered objects and it keeps adding more and more of the same filtered content on the bottom of the table if I keep re-clicking it.
What am I doing wrong here? Here is the code I have:
var stuff = document.getElementById("stuff-info");
var ourRequest = new XMLHttpRequest();
ourRequest.open('GET', 'url');
ourRequest.onload = function() {
var ourData = JSON.parse(ourRequest.responseText);
renderHTML(ourData);
};
ourRequest.send();
function renderHTML(data) {
var htmlString = "";
var filteredData = data.filter(function(element) {
return element.a
});
var checkbox = document.querySelector("input[name=hide]");
checkbox.addEventListener('change', function() {
if (this.checked) {
for (i = 0; i < filteredData.length; i++) {
htmlString += "<table><tr><td>" + filteredData[i].b + "</td><td>" + filteredData[i].c + "</td><td>" + filteredData[i].d + "</td><td>" + filteredData[i].e + "</td></tr>"
}
} else {
for (i = 0; i < data.length; i++) {
htmlString += "<table><tr><td>" + data[i].b + "</td><td>" + data[i].c + "</td><td>" + data[i].d + "</td><td>" + data[i].e + "</td></tr>"
}
}
stuff.insertAdjacentHTML('beforeend', htmlString);
});
}
Might be easier to filter with CSS selector:
#filter:checked ~ table .filter { display: none }
<input type=checkbox id=filter> Filter
<table border=1>
<tr class=filter><td>1</td><td>a</td></tr>
<tr><td>2</td><td>b</td></tr>
<tr class=filter><td>3</td><td>c</td></tr>
<tr><td>4</td><td>d</td></tr>
</table>
after I added the checkbox event listener the full table list disappeared.
All of your logic for deciding what to render is trapped inside your onchange event, so nothing will be drawn until a checkbox is changed.
When I check the checkbox I get the filtered objects and it keeps adding more and more of the same filtered.
All of your html strings are generated with += against the original htmlString variable trapped in the closure. So yeah, it will just keep adding more and more rows. You are also inserting the udated strings into the dom without removing the old table(s), so this will be exponential growth.
I think there is a great case here for higher order functions instead of for loops, you can use the map array method to transform each item in the array into a string, instead of manually iterating. This is cleaner and more maintainable.
Notice that now that the rendering logic is not mixed together with the event logic, it would be much easier to reuse the render function with some different data or different events. It's also somewhat trivial to add more transformations or filters.
const ourRequest = new XMLHttpRequest();
ourRequest.onload = function() {
const ourData = JSON.parse(ourRequest.responseText);
initialRender(ourData);
};
ourRequest.open('GET', 'url');
ourRequest.send();
function filterAll() { return true; }
function filterA() { return element.a; }
function toRowString(item) {
return `
<tr>
<td>${item.a}</td>
<td>${item.b}</td>
<td>${item.c}</td>
<td>${item.d}</td>
<td>${item.e}</td>
</tr>`;
}
function renderTable(predicate, parentElement, data){
const rows = data
.filter(predicate)
.map(toRowString);
parentElement.innerHTML = `<table>${rows}</table>`;
}
function initialRender(data) {
const stuff = document.getElementById("stuff-info");
const checkbox = document.querySelector("input[name=hide]");
renderTable(filterAll, stuff, data);
checkbox.addEventListener('change', function(event) {
renderTable(
event.target.checked ? filterA : filterAll,
stuff,
data
);
}
}
I'm trying to dynamically create elements and add an Event Listener to them. Here's some code:
var marks = {
list:[{
selected: "content",
url: "http://youtube.com/feed/subscriptions",
trait: "id"
},{
selected: "js-streams streams items",
url: "http://twitch.tv/directory/following",
trait: "class"
}]
};
var l = marks.list.length;
var web = "<webview src='https://youtube.com/feed/subscriptions' preload='preload.js' disablewebsecurity></webview>";
for(var i = 0; i < l; i++){
var webadd = "<webview id=web" + i + " src=" + marks.list[i].url + " preload='preload.js' disablewebsecurity></webview>";
$('#canvas1').append(webadd);
document.getElementById("web" + (i)).addEventListener("dom-ready", function() {
console.log("nice" + (i)); // <-- This line most likely needs changing
});
}
Right now, I'm able to get my elements and an event listener on each, but when it comes to console log, it prints out "nice2" twice. Is there a way to get that line to get var i's value as an integer instead of i itself? Ideally, I want it to be printing out nice0 and nice1. Any ideas? Thanks.
You better use a forEach loop. This way you can have a closure and keep you context. also reads better.
mark.list.forEach(function(item, i){
var webadd = "<webview id=web" + i + " src=" + item.url + " preload='preload.js' disablewebsecurity></webview>";
$('#canvas1').append(webadd);
document.getElementById("web" + i).addEventListener("dom-ready", function() {
console.log("nice" + i); // <--
});
})
You need an extra function that creates your event listener and binds the current i to it:
document.getElementById("web" + (i)).addEventListener("dom-ready", (function(n) {
return function() {
console.log("nice" + (n));
}
})(i));
Fiddle Example
The following is an example where several buttons are rendered via a loop. I was wondering if it is possible to bind events to each button as well during the loop before the buttons are appended to a container. My example doesn't work.
Jquery
function render(){
var input = '',
array = [{'name':'Confirm','title':'This'},{'name':'Cancel','title':'That'}]
$.each(array,function(k,obj){
var name = obj.name;
input += '<h3>'+obj.title+'</h3>';
input += '<input type="submit" name="'+name+'" value="'+name+'"/>';
$(input).find('[name="'+name+'"]').click(function(){
alert(name)
/*** do some ajax things etc ***/
})
})
return input;
}
$('#box').append(render())
Yes but I wouldn't do it the way you are:
function render(target){
var array = [{'name':'Confirm','title':'This'},{'name':'Cancel','title':'That'}]
$.each(array,function(k,obj){
var name = obj.name;
var h3 = $('<h3/>').text(obj.title);
var input = $('<input/>')
.attr('type', 'submit')
.attr('name',name)
.val(name);
input.click(function() {alert('test');});
target.append(h3);
target.append(input);
})
}
$(document).ready(function(){
render($('#box'));
});
So create jquery objects that will be rendered, then attach the event to these objects. Then once the object is built ask jquery to render them.
This way jquery can keep track of the DOM elements, in your example your stringfying everything. Jquery hasn't built the DOM element at the point where your attempting to bind to them.
Fiddle
You need to use filter() to find the element by the name as there is no parent selector to find() within:
$(input).filter('[name="' + name + '"]').click(function(){
alert(this.name)
/*** do some ajax things etc ***/
})
No, you can't bind event handlers to strings. You will need to create HTML elements first. I would recommend to bind single delegated event handler after your HTML string is appended, it's also going to be much better in terms of performance:
function render() {
var input = '',
array = [{'name': 'Confirm','title': 'This'}, {'name': 'Cancel','title': 'That'}]
$.each(array, function (k, obj) {
var name = obj.name;
input += '<h3>' + obj.title + '</h3>';
input += '<input type="submit" name="' + name + '" value="' + name + '"/>';
});
return input;
}
$('#box').append(render()).on('click', 'input[name]', function() {
alert(this.name);
/** do some ajax things etc **/
});
Demo: http://jsfiddle.net/KHeZY/200/
This can be done properly by using event-delegation, But since you concerned, I just written a solution by using .add() and .filter()
function render() {
var input = '',
array = [{
'name': 'Confirm',
'title': 'This'
}, {
'name': 'Cancel',
'title': 'That'
}],
elem = $();
$.each(array, function (k, obj) {
var name = obj.name;
input += '<h3>' + obj.title + '</h3>';
input += '<input type="submit" name="' + name + '" value="' + name + '"/>';
elem = elem.add($(input));
input = "";
});
elem.filter("[name]").click(function () {
alert(this.name);
})
return elem;
}
$('#box').append(render())
DEMO
I am going to do the best I can explaining what I am trying to do here. Its a bit complex(for me at least)
I currently have an object that I loop through and populate an html form with inputs dynamically. This code places the object values in the inputs and allows me to edit/save them back to the object.
What I need to do however, is actually map the object fields to a set of fields that are defined in another object. The reason for this is that the original Object sometimes is missing data and I would like to add it from my form.
For example: My object has a property 'Button'. And the object has k,v for lets say width, height, and position. But in my other Object which defines available fields for 'Button' has width, height, position, color, and transition. What I am trying to do is, when I click on 'Button' label(see my demo) it shows me all the fields in my 'Controls' object and fills in all the values from my other Object which contains the values. I can then add values into the inputs that are empty and it will save them back to my Object.
Here is my Demo(click on the labels to see the form inputs): http://jsfiddle.net/bGxFC/6/
Here is my code:
var controls = {
"Controls": [{
"Button":[{"Transition": "","BackgroundImage": "","Position": "","Width": "","Height": ""}],
"Image":[{"BackgroundImage": "","Position": "","Width": "","Height": "", "Type": ""}],
"Label":[{"Position": "","Width": "","Height": "","Text": "","FontSize":"","Color": "", "FontType": ""}]
}]
};
var str = 'View\n{\n Image\n {\n BackgroundImage: Image.gif;\n Position: 0, 0;\n Width: 320;\n Height: 480;\n }\n\n Button\n {\n BackgroundImage: Button.gif;\n Transition: View2;\n Position: 49, 80;\n Width: 216;\n Height: 71;\n }\n\n Button\n {\n BackgroundImage: Button2.gif;\n Position: 65, 217;\n Width: 188;\n Height: 134;\n }\n\n Label\n {\n Position: 106, 91;\n Width: 96;\n Height: 34;\n Text: "Button";\n FontSize: 32;\n Color: 0.12549, 0.298039, 0.364706, 1;\n }\n \n\n}';
str = str.replace(/(\w+)\s*\{/g, "$1:{"); // add in colon after each named object
str = str.replace(/\}(\s*\w)/g, "},$1"); // add comma before each new named object
str = str.replace(/;/g, ","); // swap out semicolons with commas
str = str.replace(/,(\s+\})/g, "$1"); // get rid of trailing commas
str = str.replace(/([\d\.]+(, [\d\.]+)+)/g, "[$1]"); // create number arrays
str = str.replace(/"/g, ""); // get rid of all double quotes
str = str.replace(/:\s+([^\[\d\{][^,]+)/g, ':"$1"'); // create strings
$("#parseList").html(str);
var objStr;
eval("objStr={" + str + "};");
//End Parse String
$(document).ready(function () {
var $objectList = $('<div id="main" />').appendTo($('#main'));
$.each(objStr.View, function(k, v) {
$('<div/>').append(k).appendTo($objectList).on('click', function(){
var $wrapper = $('#form .wrapper').empty();
if(typeof v === 'string') {
$('<div class="item" />').append('<span class="key">' + k + '</span>' + '<input value="' + v + '"/>').appendTo($wrapper);
}
else {//object
$('<h3 class="formHeading" />').append(k).appendTo($wrapper);
$.each(v, function(key, val) {
$('<div class="item" />').append('<span class="key">' + key + '</span>' + '<input value="' + val + '"/>').appendTo($wrapper);
});
}
$("<button>Save</button>").appendTo($wrapper).on('click', function() {
if(typeof v === 'string') {
v = $(this).closest(".wrapper").find("input").val();
}
else {//object
$(this).closest(".wrapper").find(".item").each(function(i, div) {
var $div = $(div),
key = $div.find(".key").text(),
val = $div.find("input").val();
v[key] = val;
});
}
});
});
});
});
How would I solve this problem?
You need the input elements in html to contain the object keys they associate too.
Something like:
<div class="wrapper">
<h3 class="formHeading" data-item="Button_2">Button_2</h3>
<div class="item"><span class="key">Type</span><input value="Button" data-property="Type"></div>
<div class="item"><span class="key">BackgroundImage</span><input value="Button2.gif" data-property="BackgroundImage"></div>
<div class="item"><span class="key">Position</span><input value="65,217"></div><button class="save">Save</button>
</div>
JS
$('button.save').click(function(){
var $form=$(this).closest( '.wrapper'), itemName=$form.find('.formHeading').data('item')
var objectItem= storedObject[ itemName ];
$('.item').each(function(){
var $input=$(this).find(':input');
objectItem[ $input.data('property')]= $input.val();
})
})
If you can get rid of all of the string parser and use a proper javascript object in a demo, and add these html modifications I will be happy to help debug from there
How do I get the options from another set of options.
JS Fiddle Example
at the moment this is outputting the name of each option in opt.social. Instead I want it to fetch the actual HTML related to each option name.
Thus the idea is that in the future when a new social media site is built, this can easily be added via the plugin options without the need to edit the plugin.
Example:
$.each(opt.social, function(index, value) {
html += "<li>" + value.name + "</li>";
});
I have tried
opt[value.name];
opt.value.name;
opt(value.name);
Full example:
(function ($) {
$.fn.socialMedia = function (options) {
// default configuration properties
var defaults = {
social: [
{ name: "facebook.like_large"},
{ name: "twitter.large"},
{ name: "googlePlus.large"}
],
facebook: {
like_large: '<div class="fb-like" data-href="{url}" data-send="false" data-layout="box_count" data-width="120" data-show-faces="false"></div>',
like_small: '<div class="fb-like" data-href="{url}" data-send="false" data-layout="button_count" data-width="120" data-show-faces="false" data-colorscheme="dark"></div>',
share: '<a name="fb_share" type="box_count" share_url="{url}" href="http://www.facebook.com/sharer.php?u={url}&t={title}">Share</a>'
},
twitter: {
large: 'Tweet',
small: 'Tweet'
},
googlePlus: {
large: '<div class="g-plusone" data-size="tall" data-href="{url}"></div>',
small: '<div class="g-plusone" data-size="medium" data-annotation="inline" data-width="120" data-href="{url}"></div>'
}
};
var opt = jQuery.extend(defaults, options);
// Generate HTML
$(this).append(generateHtml());
function generateHtml() {
var html = '<ul>';
$.each(opt.social, function(index, value) {
html += "<li>" + value.name + "</li>";
});
html += '</ul>';
return html;
}
}
$("body").socialMedia();
})(jQuery);
In that code, opt.social is an array of objects, each with a "name" property.
Thus,
var firstOptName = opt.social[0].name;
And so on. The opt.social array should be indexed numerically. Now, opt.googlePlus is just an object with (in this case) two properties, so there's no array indexing involved:
var googleLarge = opt.googlePlus.large;
edit — if you want to just alter that loop to show the HTML:
$.each(opt.social, function(index, value) {
var parts = value.name.split('.'), partVal = opts;
for (var i = 0; i < parts.length; ++i)
partVal = partVal[parts[i]];
html += "<li>" + partVal + "</li>";
});
The trick is that those "name" properties are in the form of dotted "paths" through an object graph, and JavaScript does not have a built-in way of interpreting those. The code I wrote above walks through the object part by part (parts are separated by "." characters), starting from the outer "opts" object.
I've updated your fiddle, according to what I understood you wanted.
each social entry can contain the name of the social service and a default widget to use.
so your defaults can be like this:
social: [
{ name: 'facebook', widget : 'like_large'},
{ name: 'twitter', widget : 'large'},
{ name: 'googlePlus', widget : 'large'}
]
and your generateHTML() can be:
$.each(opt.social, function(index, value) {
var default_widget = opt[value.name][value.widget];
html += "<li>" + default_widget + "</li>";
// or maybe:
html += $('<li></li>').html(default_widget);
});