I have an AngularJS, JS, JQ, HTML5 web app, which is capable of sending different HTTP methods to our project's RESTful Web Service and receiving responses in JSON.
It looks like this:
What I want is to create an AngularJS directive, which could accept JSON object and create an <li> for every JSON property it finds. If property itself is an object - the function should be called recursively.
Basically, I search a way to parse a JSON object to HTML elements in a such way that following JSON:
{
"title": "1",
"version": "1",
"prop" : {
"a" : "10",
"b" : "20",
"obj" : {
"nestedObj" : {
"c" : "30"
}
}
}
}
Would be transfrormed into following html:
<ul>
<li>title : 1</li>
<li>version : 1</li>
<li>
prop :
<ul>
<li>a: 10</li>
<li>b: 20</li>
<li>
obj :
<ul>
<li>
nestedObj :
<ul>
<li>c : 30</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
Does anyone know how to achieve this using AngularJS directives? Every useful answer is highly appreciated and evaluated.
Thank you.
I tried this by recursivly include a directive. But this seems be really ugly.
My solution is just like the plain old html generated out of a recursive method and append as element:
//recursivly generate the object output
scope.printObject = function (obj, content) {
content = "<ul>";
for (var i in obj) {
if (angular.isObject(obj[i])) {
content += "<li>"+i+""+scope.printObject(obj[i])+"</li>";
} else {
content += "<li>" + i + ":" + obj[i] + "</li>";
}
}
content+="</ul>";
return content;
};
Full working code here:
http://jsfiddle.net/zh5Vf/1/
It has little to do with Angular (it's plain old JS), but for the fun of it, here is a directive that does what you want:
(It is a bit more lengthy in order to properly format the HTML code (indent) and support custom initial indentation.)
app.directive('ulFromJson', function () {
var indentationStep = ' ';
function createUL(ulData, indentation) {
indentation = indentation || '';
var tmpl = ['', '<ul>'].join('\n' + indentation);
for (var key in ulData) {
tmpl += createLI(key, ulData[key], indentation + indentationStep);
}
tmpl = [tmpl, '</ul>'].join('\n' + indentation);
return tmpl;
}
function createLI(key, value, indentation) {
indentation = indentation || '';
var tmpl = '';
if (angular.isObject(value)) {
var newIndentation = indentation + indentationStep;
tmpl += '\n' + indentation + '<li>' +
'\n' + newIndentation + key + ' : ' +
createUL(value, newIndentation) +
'\n' + indentation + '</li>';
} else {
tmpl += '\n' + indentation + '<li>' + key +
' : ' + value + '</li>';
}
return tmpl;
}
return {
restrict: 'A',
scope: {
data: '='
},
link: function postLink(scope, elem, attrs) {
scope.$watch('data', function (newValue, oldValue) {
if (newValue === oldValue) { return; }
elem.html(createUL(scope.data));
});
elem.html(createUL(scope.data));
}
};
});
And then use it like this:
<div id="output" ul-from-json data="data"></div>
See, also, this short demo.
Related
How i can filter my JSON object with array.
FIDDLE
This is an sample of my json object and code, i want to filter final render HTML by selected checkbox.
Thanks for your help
function init(arr){
var li = '';
$.each(jsn, function (key, value) {
if (arr.length == 0) {
li += '<li>' + jsn[key].name + '</li>';
}else{
$(arr).each(function (i, v) {
// this section must be filter "pack's" but i can't writ correct query
li += '<li>' + jsn[key].name + '</li>';
});
};
$('#container').html(li);
})
}
var CheckArr = new Array();
init(CheckArr);
$('#btnFilter').click(function(){
var CheckArr = new Array();
$('input[type=checkbox]').each(function () {
if ($(this).is(':checked')) {
CheckArr.push($(this).attr('value'))
}
});
init(CheckArr);
First of all, you have to verify length of array outside of init function. (for case when function is called for first time).Then, you need to iterate your checkboxes array and search every item in your json array(called jsn) to verify condition you need.
Here is solution:
$(document).ready(function(){
var jsn = [
{
"name":"pack01",
"caplessthan100mb":"False",
"cap100to500mb":"True",
"cap500mbto2g":"False",
"cap2gto10g":"False"
},
{
"name":"pack02",
"caplessthan100mb":"True",
"cap100to500mb":"False",
"cap500mbto2g":"False",
"cap2gto10g":"False"
},
{
"name":"pack03",
"caplessthan100mb":"False",
"cap100to500mb":"False",
"cap500mbto2g":"False",
"cap2gto10g":"True"
},
{
"name":"pack04",
"caplessthan100mb":"False",
"cap100to500mb":"False",
"cap500mbto2g":"True",
"cap2gto10g":"False"
},
{
"name":"pack05",
"caplessthan100mb":"False",
"cap100to500mb":"False",
"cap500mbto2g":"False",
"cap2gto10g":"True"
},
{
"name":"pack06",
"caplessthan100mb":"True",
"cap100to500mb":"False",
"cap500mbto2g":"False",
"cap2gto10g":"False"
},
{
"name":"pack07",
"caplessthan100mb":"False",
"cap100to500mb":"False",
"cap500mbto2g":"False",
"cap2gto10g":"True"
}
];
function init(arr){
var li = '';
if(arr.length==0)
{
$.each(jsn, function (key, value) {
li+= '<li>' + jsn[key].name + '</li>';
});
}
else{
$(arr).each(function (i, v) {
$.each(jsn, function (key, value) {
if(jsn[key][v]=="True")
li+= '<li>' + jsn[key].name + '</li>';
});
});
}
$('#container').html(li);
}
var CheckArr = new Array();
init(CheckArr);
$('#btnFilter').click(function(){
var CheckArr = new Array();
$('input[type=checkbox]').each(function () {
if ($(this).is(':checked')) {
CheckArr.push($(this).attr('value'))
}
});
init(CheckArr);
})
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul>
<li><input type="checkbox" value="caplessthan100mb">caplessthan100mb</li>
<li><input type="checkbox" value="cap100to500mb">cap100to500mb</li>
<li><input type="checkbox" value="cap500mbto2g">cap500mbto2g</li>
<li><input type="checkbox" value="cap2gto10g">cap2gto10g</li>
<li><input type="button" id="btnFilter" value="Filter"></li>
</ul>
<br />
<ul id="container">
</ul>
There are quite a few things in your code that could use improvement so I've taken the liberty of largely rewriting it (see jsFiddle link below).
1) First thing is in your data (jsn) you are using "False" and "True" instead of false and true. That'll make it hard to write your filter condition because true != "True".
2) It's quite hard to debug your code because the variable names aren't very meaningful. I'd highly recommend putting some energy into improving your variable names especially when code isn't working.
For example:
packsData instead of jsn
checkedBoxes instead of arr
3) If you try to filter within the .each() below you'll run into trouble when it matches more than one filter condition (it'll be displayed more than once).
$(arr).each(function (i, v) {
// this section must be filter "pack's" but i can't writ correct query
li += '<li>' + jsn[key].name + '</li>';
});
Here is a working jsFiddle
Currently I'm adding data to my unordered list like this:
<ul id="mychats"></ul>
if (obj.request_type == 'client_information') {
$("#mychats").children().remove();
$.each(obj.clients_information, function (index, data) {
var li = "<li class='not_selected' id='" + data.chatid + "'>" + data.client_name;
if (data.status == 0) //Chat is unlocked
li += " <img class='chat_status' src='https://www.astro.ru.nl/lopes/_media/intra.png?w=&h=&cache=cache' />";
else
li += " <img class='chat_status' src='https://confluence.atlassian.com/download/attachments/686859081/page-restrictions-padlock-icon.png?api=v2' />";
li += "</li>";
$("#mychats").append(li);
});
}
This works as intended but I want to do it in a different way, I want to bind the ul with the data in obj.clients.informations directly.
Sample JSON data in obj:
{
"request_type": "client_information",
"clients_information": [{
"chatid": "0a9ef3c4-b34a-435f-b0c5-15bf415e1517",
"client_name": "awd",
"status": 0
},{
"chatid": "08a725c4-4fd1-471d-a507-4782f6bbc774",
"client_name": "awdaeqdaw",
"status": 0
}]
}
something like:
<ul id='mychats' data-bind='foreach: Clients' data-role='listview'>
<li data-binding='class: thisclass, id: chatid, text: client_name'></li>
</li>
With the specified code in jQuery to be able to bind it to the unordered list. I tried to search all over the net and I couldn't really find an example for jQuery.
Just this: http://jsfiddle.net/rniemeyer/4FdcY/ (i want something like this for my object)
Thanks all.
That example uses the knockoutjs library. The documentation on the website is spot on and is exactly what youre after.
Just parse your json in to an object and use knockout to bind the object to the markup.
I have an <OL> and a function that reads in json and loads in <li>'s. I then have another function that looks at another json and loads the final <li>. I want the first function to trigger first and then the second to append the final <li> after. However, 1 out of every 10 or so page loads the second function triggers first and the <li>s are out of order. BTW the use case is for dynamic breadcrumbs. I am using twitter bootstrap's breadcrumb class to style these elements.
First Trigger:
$.getJSON("/requirementdesc/{{ catalog }}/"+ c, function(d) {
$.each(d, function(k, v) {
$(".breadcrumb").append("<li><a href='/{{ catalog }}/"+ c +"'>"+ v.raw_requirement_desc +"</a></li>");
});
});
Second Trigger:
$.getJSON("/parentrequirement/{{ catalog }}/{{ block }}", function(data) {
$.each(data, function(key, value) {
$(".breadcrumb").append("<li class='active'>"+ value.raw_requirement_desc +"</li>");
});
});
I have tried using .promise(), but no luck.
Use jQuery's $.when(), which provides a way to execute callback functions based on one or more objects that represent asynchronous events. This code waits for both calls to complete, and then appends the lis to the ol in order.
var li_1,
li_2;
$.when(
$.getJSON("/requirementdesc/{{ catalog }}/" + c, function (d) {
$.each(d, function (k, v) {
li_1 += "<li><a href='/{{ catalog }}/" + c + "'>" +
v.raw_requirement_desc + "</a></li>";
})
}),
$.getJSON("/parentrequirement/{{ catalog }}/{{ block }}", function (data) {
$.each(data, function (key, value) {
li_2 += "<li class='active'>" + value.raw_requirement_desc + "</li>";
})
})
)
.then(function(){
if(typeof li_1 === "string" && typeof li_1 !== "undefined") {
$(".breadcrumb").append(li_1);
}
if(typeof li_2 === "string" && typeof li_2 !== "undefined") {
$(".breadcrumb").append(li_2);
}
});
Note: I didn't test this but it should theoretically work, given your code.
sometimes my javascript code is mixing with html and css. in this stuation, my code is beind unreadable. How do you separate the javascript and html side in javascript?
For example: (using javascript dojo toolkit)
addLayer: function (layer, index) {
var layerClass = layer.visible === true ? 'layer checked' : 'layer';
var html = '';
html += '<li class="' + layersClass + '">';
html += '<div class="cover"></div>';
html += '<span tabindex="0" class="info" title="MyTitle"></span>';
html += '<span tabindex="0" class="toggle box"></span>';
html += '<div class="clear"></div>';
html += '</li>';
var node = dom.byId('layersList');
if (node) {
domConstruct.place(html, node, "first");
HorizontalSlider({
name: "slider",
value: parseFloat(layer.opacity),
minimum: 0,
maximum: 1,
showButtons: false,
discreteValues: 20,
intermediateChanges: true,
style: "width:100px; display:inline-block; *display:inline; vertical-align:middle;",
onChange: function (value) {
layer.setOpacity(value);
}
}, "layerSlider" + index);
if (!this.layerInfoShowClickHandler) {
this.layerInfoShowClickHandler = on(query(".listMenu"), ".cBinfo:click, .cBinfo:keyup", this._onLayerInfoShowIconClicked);
}
}
}
In this stuation, my code is adding html to view side dynamically. Adding event handlers to created html code. Adding additional tools(HorizantalSlider) same time.
This workflow is binded one to another. This code is unreadable. Is there a way to solve this with clean code?
This answer uses Dojo to split your HTML + CSS from JavaScript.
HTML template
The recommended approach is by defining your HTML template in a seperate HTML file. For example:
<li class="{layersClass}">
<div class="cover"></div>
<span tabindex="0" class="info" title="MyTitle"></span>
<span tabindex="0" class="toggle box"></span>
<div class="clear"></div>
</li>
Also notice the replacement of layersClass by a placeholder.
Load the HTML template
Now, to load the template you use the dojo/text plugin. With this plugin you can load external templates, for example by using:
require(["dojo/text!./myTemplate.html"], function(template) {
// The "template" variable contains your HTML template
});
Converting the placeholders
To replace {layersClass}, you can use the replace() function of the dojo/_base/lang module. Your code would eventually look like:
require(["dojo/text!./myTemplate.html", "dojo/_base/lang"], function(myTemplate, lang) {
var html = lang.replace(myTemplate, {
layersClass: layersClass
});
});
This would return exactly the same as your html variable, but seperated the HTML from your JavaScript code.
Seperate CSS
To seperate the CSS style from your HorizontalSlider you could define an id property and just put your CSS in a seperate CSS file. Your HorizontalSlider would become:
HorizontalSlider({
name: "slider",
value: parseFloat(layer.opacity),
minimum: 0,
maximum: 1,
showButtons: false,
discreteValues: 20,
intermediateChanges: true,
id: "mySlider",
onChange: function (value) {
layer.setOpacity(value);
}
}, "layerSlider" + index);
Now you can use the following CSS:
#mySlider {
width:100px;
display:inline-block;
*display:inline;
vertical-align:middle;
}
You could store the html variable in a different place, let's say in a file called template.js. However, doing so you can't concatenate the HTML string immediately since you need to inject this layersClass variable. Here is a possible workaround :
// template.js
var template = function () {
return [
'<li class="', this.layersClass, '">',
'<div class="cover"></div>',
'<span tabindex="0" class="info" title="MyTitle"></span>',
'<span tabindex="0" class="toggle box"></span>',
'<div class="clear"></div>',
'</li>'
].join('');
};
// view.js
html = template.call({
layersClass: layerClass
});
Effective and easy to use. However, if you want to use a template in the form of a string rather than a function, you'll need a template parser. The following one will give the same kind of result as above (notice that regex capturing is not supported by IE7 split()) :
function compile(tpl) {
tpl = Array.prototype.join.call(tpl, '').split(/{{(.*?)}}/);
return Function('return [' + tpl.map(function (v, i) {
if (i % 2) return 'this["' + v + '"]';
return v && '"' + v.replace(/"/g, '\\"') + '"';
}).join(',') + '].join("");');
}
Usage example :
var template = '<b>{{text}}</b>';
var compiled = compile(template);
// compiled -> function () {
// return ["<b>",this["text"],"</b>"].join("");
// }
var html1 = compiled.call({ text: 'Some text.' });
var html2 = compiled.call({ text: 'Bold this.' });
// html1 -> "<b>Some text.</b>"
// html2 -> "<b>Bold this.</b>"
Now, let's see how you could use this tool to organize your files in a clean way.
// product.data.js
product.data = [{
name: 'Apple iPad mini',
preview: 'ipadmini.jpeg',
link: 'ipadmini.php',
price: 280
}, {
name: 'Google Nexus 7',
preview: 'nexus7.jpeg',
link: 'nexus7.php',
price: 160
}, {
name: 'Amazon Kindle Fire',
base64: 'kindlefire.jpeg',
link: 'kindlefire.php',
price: 230
}];
// product.tpl.js
product.tpl = [
'<div class="product">',
'<img src="{{preview}}" alt="{{name}}" />',
'<span>{{name}} - ${{price}}</span>',
'details',
'</div>'
];
// product.view.js
var html = [];
var compiled = compile(product.tpl);
for (var i = 0, l = product.data.length; i < l; i++) {
html.push(compiled.call(product.data[i]));
}
document.getElementById('products').innerHTML = html.join('');
Demo : http://jsfiddle.net/wared/EzG3p/.
More details here : https://stackoverflow.com/a/20886377/1636522.
This compile function might not be enough for your needs, I mean, you might quickly need something more powerful that includes conditional structures for example. In this case, you may take a look at Mustache, Handlebars, John Resig or Google "javascript templating engine".
Consider using templates for building dynamic view elements
E.g:
http://handlebarsjs.com/
http://underscorejs.org/#template
One way is to use HTML (not that cool if you don't like to split your logic):
<div style="display:none" id="jQ_addLayer">
<div class="cover"></div>
<span tabindex="0" class="info" title="MyTitle"></span>
<span tabindex="0" class="toggle box"></span>
<div class="clear"></div>
</div>
than in jQuery create the LI with the passed variable and insert your #jQ_addLayer content:
var html = '<li class="'+layersClass+'">'+ $("#jQ_addLayer").html() +'</li>';
Another way is to escape your string newlines:
var html = '\
<li class="' + layersClass + '">\
<div class="cover"></div>\
<span tabindex="0" class="info" title="MyTitle"></span>\
<span tabindex="0" class="toggle box"></span>\
<div class="clear"></div>\
</li>'; //don't forget to escape possible textual single-quotes in your string
Suggest me any good mustache doc. Also i want to know in a mushtach loop how do i get the count or the loop no. I mean how can i do a for loop in mustache.
In the below code i wish to change the id in every loop
<script src="http://github.com/janl/mustache.js/raw/master/mustache.js"></script>
<script>
var data, template, html;
data = {
name : "Some Tuts+ Sites",
big: ["Nettuts+", "Psdtuts+", "Mobiletuts+"],
url : function () {
return function (text, render) {
text = render(text);
var url = text.trim().toLowerCase().split('tuts+')[0] + '.tutsplus.com';
return '' + text + '';
}
}
};
template = '<h1> {{name}} </h1><ul> {{#big}}<li id="no"> {{#url}} {{.}} {{/url}} </li> {{/big}} </ul>';
html = Mustache.to_html(template, data);
document.write(html)
</script>
<body></body>
You can't get at the array index in Mustache, Mustache is deliberately simple and wants you to do all the work when you set up your data.
However, you can tweak your data to include the indices:
data = {
//...
big: [
{ i: 0, v: "Nettuts+" },
{ i: 1, v: "Psdtuts+" },
{ i: 2, v: "Mobiletuts+" }
],
//...
};
and then adjust your template to use {{i}} in the id attributes and {{v}} instead of {{.}} for the text:
template = '<h1> {{name}} </h1><ul> {{#big}}<li id="no-{{i}}"> {{#url}} {{v}} {{/url}} </li> {{/big}} </ul>';
And as an aside, you probably want to include a scheme in your url:
url : function () {
return function (text, render) {
text = render(text);
var url = text.trim().toLowerCase().split('tuts+')[0] + '.tutsplus.com';
return '' + text + '';
//---------------^^^^^^^
}
}
Demo: http://jsfiddle.net/ambiguous/SFXGG/
Expanding on #mu's answer, you could also keep an index in the data object and have the template refer to it and the function increment it. So you wouldn't need to add i to each item.
see demo : http://jsfiddle.net/5vsZ2/