I have this XML response: http://jsfiddle.net/ZeeHv/
I'm trying to create something like this using the information from the dump:
<UL>
<li>Academic
<ul>
<li>BM</li>
<li>CMTTE</LI>
<li>DM</li>
<li>PM</li>
</ul>
</li>
</ul>
<ul>
<li>ARCHIVE</li>
</UL>
<ul>
<LI>ASSOCIATIONS
<ul>
<li>BM</li>
<li>DM</LI>
<li>PM</li>
</ul>
</LI>
</ul>
In the end the XML can give me a list of all my sites and subsits:
https://hosted.demo.ca
https://hosted.demo.ca/academic
https://hosted.demo.ca/academic/bm
https://hosted.demo.ca/academic/cmtte
https://hosted.demo.ca/academic/dm
https://hosted.demo.ca/academic/pm
https://hosted.demo.ca/archive
https://hosted.demo.ca/associations
https://hosted.demo.ca/associations/bm
https://hosted.demo.ca/associations/dm
https://hosted.demo.ca/associations/pm
How can I go through this information and append ul and li tags to create a site navigation menu?
JS used to get XML:
function getAllSites(){
$().SPServices({
operation: "GetAllSubWebCollection",
async: true,
completefunc: function(xData, Status){
$(xData.responseXML).find("Web").each(function(){
console.log($(this).attr("Url"));
});
}
});
}
A simple solution would be to build a map of indexes based on the depth of the links, the depth is determined by the number of / in the url.
var map = {}; //init the map
for (var i = 0, l = webs.length; i < l; i++) {
//we create a index for our links based on the depth of them by `/`
var m = webs[i].attributes['Url'].value.substring(23, webs[i].attributes['Url'].value.length).split('/').length;
map[m] = map[m] || []; //make sure we leave alone the old values if there is none init with new array
map[m].push(webs[i].attributes['Url'].value); //push new value to node
}
console.log(map);
console.log(map); will output an object similar to this:
{
"1": ["https://hosted.demo.ca", "https://hosted.demo.ca/academic", "https://hosted.demo.ca/archive", ...],
"2": ["https://hosted.demo.ca/academic/bm", "https://hosted.demo.ca/academic/cmtte", ...],
}
From this you can create your list of elements.
Related
<ul id='parent_of_all'>
<li>
<span class='operator'>&&</span>
<ul>
<li>
<span class='operator'>||</span>
<ul>
<li>
<span class='operator'>&&</span>
<ul>
<li>
<span class='condition'>1 == 1</span>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<ul>
<li>
<span class='condition'>1 != 0</span>
</li>
</ul>
</li>
</ul>
to
{"&&":[{'||':[ {'&&':[ {"lhs": "1", "comparator": "==", "rhs":"1"} ]} ] } , {"lhs": "1", "comparator": "!=", "rhs":"0"}]}
As of now, I know the basics of jQuery, JavaScript. I need to know where to start thinking in order to accomplish the above conversion.
And the html tree could be more complex with more children.
You can do this with each and map
var obj = {}
var span = $('li > span').not('ul li span').text();
$('ul li span').each(function() {
var text = $(this).text().split(' ');
obj[span] = (obj[span]||[]).concat({lhs: text[0], comparator: text[1], rhs: text[2]});
});
console.log(obj)
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<li>
<span>&&</span>
<ul>
<li>
<span>1 == 1</span>
</li>
</ul>
<ul>
<li>
<span>1 != 0</span>
</li>
</ul>
</li>
You will need a way to select the first level of li, I assumed you have a parent element with an id such as list. I wrote the following code using basic jquery so you can understand it.
var result = {};
var $all_li = $('#list').children('li'); // selecting the first level of li
for(var i in $all_li){ // iterating all_li using for (you may use forEach )
var $current_li = $( $all_li[i] ); // getting operator from first span
var operator = $current_li.children('span').html(); // the text of the operator
var $inner_spans = $current_li.find('>ul >li >span'); // getting list of children spans (from path $list>li>ul>li>span)
var li_spans = []; // an array where we will put the inner span objects
for(var j in $inner_spans){ // iterating the inner spans
var text = $($inner_spans[j]).html().split(" "); // splitting the html
li_spans.push({
lhs: text[0],
comparator: text[1],
rhs: text[2]
}); // adding the splitted html to an object. Note: error if text didn't have 2 white spaces
}
result[operator] = li_spans; // adding the operator key and li_spans value to the result json
}
This code will parse the html and construct the result json, it should work for the html format you provided. Keep in mind that it does not handle errors (such as bad tree format).
simmiar html formats.
Thanks #Alexandru and #Nenad for giving a start. I have been able to complete this on my own.
Below is the function that generates json.
function prepare_json(current_node){
var object = {}
var span = $(current_node).children('span')
if (span.hasClass('condition')){
var text = span.html().split(" ");
object = {lhs: text[0], comparator: text[1], rhs: text[2]}
}
else if(span.hasClass('operator')){
var operator = span.text()
object[operator] = (object[operator] || [])
var children = $(current_node).children('ul').children('li')
for(var i = 0; i < children.length; i++){
var child_pql = prepare_json([children[i]])
object[operator].push(child_pql)
}
}
return object
}
Below is the code that calls that function:
var parent_node = $('#parent_of_all').children('li')
var json = JSON.stringify(prepare_pql_json(parent_node), null, 2)
Inside html dom I have following structure
<div id="myTitles">
<ul>
<li>
Title:<title>A</title><br>
Second title: <secTitle>B</secTitle><br>
Third title: <thirdTitle>3</thirdTitle>
</li>
<hr>
<li>
Title:<title>B</title><br>
Second title: <secTitle>C</secTitle><br>
Third title: <thirdTitle>9</thirdTitle>
</li>
</ul>
</div>
Number of list elements inside ul of myTitles div is unknown on runtime. So I'm taking var nrOfTitles = $('#myTitles ul li').length; to determine how many is there.
Now, I want to create some simple javascript object which will hold those values (stored inside this semantic tags (title, secTitle, thirdTitle)) and store them inside js array.
var items = $("#myTitles > ul > li");
var arr = [];
items.each(function(){
arr.push([$(this).find("title").text(),$(this).find("secTitle").text(),$(this).find("thirdTitle").text()])
})
With your current combination of <li>s it would contain:
[["A","B","3"],["B","C","9"]]
Or you can store the properties as objects:
var items = $("#myTitles > ul > li");
var arr = [];
items.each(function(){
arr.push({title:$(this).find("title").text(),secTitle:$(this).find("secTitle").text(),thirdTitle:$(this).find("thirdTitle").text()})
})
Then you would get
[{title:"A",secTitle:"B",thirdTitle:"3"},{title:"B",secTitle:"C",thirdTitle:"9"}]
You can use $.fn.map method to conveniently create an array of objects:
var obj = $('#myTitles ul li').map(function() {
return {
title: $(this).find('title').text(),
secTitle: $(this).find('secTitle').text(),
thirdTitle: $(this).find('thirdTitle').text()
};
}).get();
alert(JSON.stringify(obj, null, 4));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="myTitles">
<ul>
<li>
Title:<title>A</title><br>
Second title: <secTitle>B</secTitle><br>
Third title: <thirdTitle>3</thirdTitle>
</li>
<hr>
<li>
Title:<title>B</title><br>
Second title: <secTitle>C</secTitle><br>
Third title: <thirdTitle>9</thirdTitle>
</li>
</ul>
</div>
But since you are using custom tags in HTML, remember to create them for IE8, if you plan so support this browser, like document.createElement('title');.
I have a db table with users and I am creating a hierarchy system.
Currently the top users (with a 1 hierarchy) are shown, and then when clicked I gather all the related users via ajax and display below.
These users have 4 or 5 tiers for their hierarchy. 1 being the highest level.
I want to be able to make a new line of users each time the hierarchy changes.
I query the db and fetch all the users with an order by of the hierarchy number, I then put them into an array, return with JSON and loop through in jQuery:
for(var i=0; i<data.userArray.length; i++){
var user = data.userArray[i];
$appendedItems.append(SHOW ALL USERS HERE);
}
Lets say the array is:
hierarchy => 2,
hierarchy => 2,
hierarchy => 2,
hierarchy => 3,
hierarchy => 3,
hierarchy => 4,
Each time the hierarchy changes, I would like to show a line break or something just to put the users on a new line to denote a lower level.
Thanks.
how I'd do it, obviously your case will be different but you can get the idea:
markup:
<ul id="#users">
<li data-user-id="3">david</li>
<li data-user-id="4">frank</li>
</ul>
jQuery:
$('#users').on('click', 'li:not(.exploded)', function() {
var $user = $(this).addClass('exploded'),
userId = $user.data('userId'),
url = '/getUsersChildres?user=' + userId;
$.getJSON(url).then(function(data) {
$('<ul>', {
html: $.map(data.userArray, function(i, user) {
return $('<li>', {
text: user.name,
data: {
'userId': user.id
}
});
})
}).appendTo($user);
}, function() {
$user.removeClass('exploded');
});
return false;
});
should end up with:
<ul id="#users">
<li data-user-id="3" class="exploded">david
<ul>
<li data-user-id="13">jack</li>
<li data-user-id="14">paul</li>
</ul>
</li>
<li data-user-id="4">frank</li>
</ul>
Summary:
Child categories nested inside of Parent Categories are not getting rendered in a Meteor template.
Details:
Consider a data model for 'Category' as such:
// Model Schema
Category {
idCategory : 20, (id of the category itself)
idCategoryParent : 0, (idCategory of our parent category)
defaultLabel : "Movies" (our label)
}
There are parent categories and child categories. Parent categories have an idCategoryParent property value of 0. Child categories store the idCategory of their parents as their idCategoryParent property. I'm trying to loop through a collection of these Categories and render them in the following way:
<b>Movies</b> // parent category is in bold
<ul> // child categories are rendered as an unordered list
<li>Horror</li>
<li>Comedy</li>
<li>Action</li>
<li>Drama</li>
</ul>
<b>Music</b>
<ul>
<li>Rock</li>
<li>Classical</li>
<li>Ambient</li>
</ul>
However, this is what I actually get:
<b>Movies</b>
<ul> // empty...
</ul>
<b>Music</b>
<ul>
</ul>
Source Code:
// How we get the 'categories_parents' data
Template.content.categories_parents = function (){
/*
* Get all parent categories (categories with an idCategoryParent of 0)
*/
var parents = Categories.find({idCategoryParent:0});
var pCount = parents.count();
for (var i = 0; i < pCount; i++){
var pId = parents.db_objects[i]['idCategory'];
/*
* Get all child categories of the parent (categories with
* an idCategoryParent equal to value of parent category's idCategory).
*/
var children = Categories.find({idCategoryParent:pId});
var cCount = children.count();
/*
* Assign the child categories array as a property of the parent category
* so that we can access it easily in the template #each expression
*/
parents.db_objects[i]['children'] = children;
}
return parents;
}
// This is our template
<template name="content">
<h1>Categories</h1>
{{#each categories_parents}}
<b>{{defaultLabel}}</b><br />
<ul>
{{#each children}}
<li>{{defaultLabel}}</li>
{{/each}}
</ul>
{{/each}}
</template>
Other template configurations I have tried in troubleshooting:
{{#each children}}
<li>A Child Exists Here</li> // Even this never rendered... no children?
{{/each}}
Any clues as to why this is happening would be appreciated.
Your model is kind of iffy... Consider
{name:"Category name", parent:"_id of parent category"}
Okay, that's a lot simpler. To create a category.
var moviesId = Categories.insert({name:"Movies"});
Categories.insert({name:"Horror",parent:moviesId});
That was easy enough. Now, to render in a way that {{#each}} works:
Template.categories.categories = function(parent) {
if (parent) {
return Categories.find({parent:parent}).fetch();
} else {
return Categories.find({parent:{$exists:false}});
}
}
You might be seeing where this is going...
<template name="categories">
{{#each categories}}
<ul>{{name}}
{{#each categories _id}}
<li>{{name}}</li>
{{/each}}
</ul>
{{/each}}
</template>
Now I'm not sure if the {{#each}} block helper can take a function argument when it calls another helper. If it doesn't...
Template.categories.categories = function() {
return Categories.find({parent:{$exists:false}}).map(function(parentCategory) {
return _.extend(parentCategory,
{children:Categories.find({parent:parentCategory._id}).fetch()});
});
}
That's a real doozy. It returns parent categories with a new "children" list property, that contains all the children categories. Now you can do:
<template name="categories">
{{#each categories}}
<ul>{{name}}
{{#each children}}
<li>{{name}}</li>
{{/each}}
</ul>
{{/each}}
</template>
Clever, eh?
I don't know about db_objects, when I try and access that property on a cursor (which is what find() returns), it's null.
You could fetch the items that matches your query instead, and then do your iteration:
Template.content.categories_parents = function (){
var parents = Categories.find({idCategoryParent:0}).fetch(); // Returns an array.
for (var i = 0; i < parents.length; i++){
var pId = parents[i]['idCategory'];
var children = Categories.find({idCategoryParent:pId});
// No need for array here, cursor is fine.
parents.db_objects[i]['children'] = children;
}
return parents;
}
I'm new at this myself, so maybe there's a more efficient way of doing it, but I don't know it currently.
Update after Eric's comment.
The js file looks like this:
Categories = new Meteor.Collection("categories");
if (Meteor.isClient) {
Template.categories.categories = function () {
var parents = Categories.find({idCategoryParent:0}).fetch();
for (var i = 0; i < parents.length; i += 1) {
var pId = parents[i]['idCategory'];
var children = Categories.find({idCategoryParent:pId});
parents[i]['children'] = children;
}
return parents;
};
}
if (Meteor.isServer) {
Meteor.startup(function () {
Categories.remove({});
var data = [
{idCategoryParent: 0, idCategory: 1, label: "Movies"},
{idCategoryParent: 1, idCategory: 0, label: "Science Fiction"},
{idCategoryParent: 1, idCategory: 0, label: "Drama"},
{idCategoryParent: 0, idCategory: 2, label: "Music"},
{idCategoryParent: 2, idCategory: 0, label: "Jazz"},
{idCategoryParent: 2, idCategory: 0, label: "Piano"}
];
for (var i = 0; i < data.length; i += 1) {
Categories.insert(data[i]);
}
});
}
The html file looks like this:
<head>
<title>nested_template</title>
</head>
<body>
{{> categories}}
</body>
<template name="categories">
<h1>Categories</h1>
{{#each categories}}
<b>{{label}}</b>
<ul>
{{#each children}}
<li>{{label}}</li>
{{/each}}
</ul>
{{/each}}
</template>
It works just fine for me.
Solved.
My solution was to remove the {{#each}} logic from the template and replace it with a single handlebars helper expression, and pass back the needed html all at once. The html is generated from data in the cursor of the Categories collection.
Not so sure about having all this html in my logic though -- is this bad design? If so I'll defer to a better answer.
// This is the new template
<template name="content">
<h1>Categories</h1>
{{listCategories}}
</template>
// Handlebars helper
Handlebars.registerHelper('listCategories', function() {
var parents = Categories.find({idCategoryParent:0});
var countParents = parents.count();
var string = '';
// iterate over each parent category
for(m = 0; m < countParents; m++){
// Get the parents category id
var pId = parents.db_objects[m].idCategory;
var children = Categories.find({idCategoryParent:pId});
var count = children.count();
/*
* Build the Parent Category html
* Example: <b>Movies</b><ul>
*/
string = string + '<b>' + parents.db_objects[m].defaultLabel + '</b><ul>';
// iterate over each child category
for(var i = 0; i < count; i++){
/*
* Build the child category html
* Example: <li>Horror</li>
*/
string = string + '<li>' + children.db_objects[i]['defaultLabel'] + '</li>';
}
// Close up the unordered list
string = string + '</ul>';
}
// Return the string as raw html
return new Handlebars.SafeString(string);
});
// Rendered out the result correctly like so:
<b>Movies</b>
<ul>
<li>Horror</li>
<li>Comedy</li>
<li>Action</li>
<li>Drama</li>
</ul>
<b>Music</b>
<ul>
<li>Rock</li>
<li>Classical</li>
<li>Ambient</li>
</ul>
Given the following structure:
<ul>
<li data-conference="Conference1" >Spain</li>
<li data-conference="Conference1" >France</li>
<li data-conference="Conference1" >Germany</li>
<li data-conference="Conference1" >Italy</li>
<li data-conference="Conference2" >Austria</li>
<li data-conference="Conference2" >Poland</li>
<li data-conference="Conference3" >Russia</li>
<li data-conference="Conference3" >USA</li>
<li data-conference="Conference3" >China</li>
</ul>
what is the best way (with jQuery), considering performance, to rearrange this into this:
<ul>
<li>Spain</li>
<li>France</li>
<li>Germany</li>
<li>Italy</li>
</ul>
<ul>
<li>Austria</li>
<li>Poland</li>
</ul>
<ul>
<li>Russia</li>
<li>USA</li>
<li>China</li>
</ul>
Thanks!
I think the overall question (group elements by attribute) is good, you just should have put more effort into trying to solve it yourself.
Anyways, grouping elements by an attribute is quite simple. You can create an attribute value -> [element, ...] map, which can be done with an object:
var groups = {};
$('li[data-city]').each(function() {
var attr = $(this).attr('data-city'),
group = groups[attr];
if(!group) {
group = groups[attr] = [];
}
group.push(this);
});
Now you have a collection of lists of DOM elements. You can iterate over the collection and create the HTML lists accordingly.
For example:
for(var group in groups) {
var $list = $('<ul />');
$list.append(groups[group]);
// now append $list somewhere
}
Have a look at Working with Objects [MDN] to get more information about how to process objects.
It's also trivial to do this without jQuery, as long as you have references to the elements, for example as a NodeList. Instead of using .each you can then use a "normal" for loop to iterate that list.
Unless you have a insane amount of cities in those lists I wouldn't worry about performance. The only performance consideration I would take is to avoid repaint / reflows by minimizing writing to the DOM. I think code clarity is much more important in this use case.
That being said I'd implement this with something like this - http://jsfiddle.net/XWufy/.
Here you go:
(function () {
var $list = $( '#list' );
var lists = {};
var $newLists = $();
$list.children().each( function () {
var city = $( this ).data( 'city' );
if ( !lists[ city ] ) lists[ city ] = [];
lists[ city ].push( this );
});
$.each( lists, function ( city, items ) {
var $newList = $( '<ul />' ).append( items );
$newLists = $newLists.add( $newList );
});
$list.replaceWith( $newLists );
}());
Live demo: http://jsfiddle.net/rjt9W/6/
Btw, the code assumes that the list has an ID of "list". Replace the selector in this line
var $list = $( ... );
so that it properly selects your UL element.
Use the data attribute as an object property to sort them, then loop over them to construct the new html. this should get you started:
var list = {};
// for each item
list[item.data('city')] = item.text();
// for each property of list
var ul = $('<ul>');
// for each listItem in current list
var li = $('<li>').text(listItem);
ul.append(li);
try this:
<ul id="first"></ul>// you can create the ul tags by using JavaScript
$("li").each(function(){
data = $(this).attr("data");
if (data == "Conference1") {
txt = $(this).text();
$("<li>" + txt + "</li>").appendTo("ul#first");
}
})
Try this:
var list = [];
var $div = $('#my_container_div');
$('li[data-city]').each(function() {
var $this = $(this), data = $this.attr('data-city');
list[ data ] = list[ data ] || [];
list[ data ].push( $this.text() );
});
for(var data in list) {
var $ul = $div.append('<ul/>');
for(var li in list[data]) {
$ul.append('<li>' + list[data][li] + '</li>');
}
}