HTML structure in treeview with jQuery - javascript

I need to display a HTML structure in a treeview. I found some jQuery treeview plugins but they generally require a list.
Let's take a simple HTML (nodes may have different tags, not only 'div'):
<div id="node1">
<div id="node2">
<div id="node3"></div>
<div id="node4"></div>
</div>
<div>
I want to display it like this:
node1
node2
node3
node4
For now, i'm using this jQuery plugin: treeview
So I need to convert the HTML to an unordered list like this:
<ul>
<li>node1
<ul>
<li>node2
<ul>
<li>node3</li>
<li>node4</li>
</ul>
</li>
</ul>
</li>
</ul>
How can I do this using jQuery? If you think a different approach would be better, please let me know.

Here you go...that was fun. Demo - Updated to not be specific to div tags, and return an element, that could be turned into a html string.
$.fn.convertToUL = function(isTop) {
if(isTop==null) isTop=true;
var ul=$("<ul>");
var li=$("<li>");
li.append($(this).attr("id"));
var children=0;
$(this).children().each(function(){
ul.append($(this).convertToUL(false));
children++;
});
if(children>0)
li.append(ul);
if(li.is(':empty'))
return "";
if(isTop)
return $("<ul>").append(li);
return li;
}
//to get text of elements use .wrap('<div/>').parent().html()
var ulElement=$("#node1").convertToUL();
alert(ulElement.wrap('<div/>').parent().html());
$("#node1").replaceWith(ulElement);

Related

How can I add the same XML tags multiple times, with different content?

I have some problems with my code. I want to create an XML Document with JQuery / JavaScript. I am now at the point, where I want to create a few Tags and populate them each with the same tags but different content inside the tags.
Here is the code for better understand
function setItems(xmlDoc, channelTag){
const itemList = [];
const itemTitle = xmlDoc.createElement("title");
const itemLink = xmlDoc.createElement("link");
const itemGuid = xmlDoc.createElement("guid");
const itemMediaContent = xmlDoc.createElement("media:content");
const itemMediaDescription = xmlDoc.createElement("media:description");
itemList.push(itemTitle, itemLink, itemGuid, itemMediaContent, itemMediaDescription);
for (var i = 0; i < jsonObj.length; i++){
var item = xmlDoc.createElement("item");
channelTag.appendChild(item);
//Populate the <item> with the tags from "itemList" and content from "jsonObj"
$.each(itemList, function(index) {
$(channelTag).children('item')[i].appendChild(itemList[index]).textContent = jsonObj[0].title;
})
}
}
The Output of the code looks like this:
<item></item>
<item></item>
<item>
<title>Something</title>
<guid>Something</guid>
<link>Something</link>
<media:content>Something</media:description>
<media:description>Something</media:description>
</item>
It always populates the last item-Tag but not the ones above. What I want is that every item-Tag has the same child-Tags (e.g. title, link, guid and so on). Is there something i am missing some unique tags or something like that?
Edited:
Here is some minimal HTML and XML. The values for the function "xmlDoc" and "channelTag" just contains some Document Elements, where my items should be appended, like so:
<rss>
<channel>
<title>SomeTitle</title>
<atom:link href="Link" rel="self" type="application/rss+xml"/>
<link>SomeLink</link>
<description>SomeDesc</description>
<item></item>
<item></item>
<item></item>
</channel>
</rss>
<div class="col-5 col-sm-5 col-lg-3 order-2 count">
<a class="guid1"><img class="card-img image1"></a>
</div>
<div class="col-7 col-sm-7 col-lg-5 order-2">
<div class="card-body">
<a class="guid1">
<h5 class="card-title title1 overflow-title"></h5>
</a>
<p class="card-text body1 text-body overflow-body"></p>
<div class="card-body subtitle">
</div>
</div>
</div>
There are several issues with your code but the area we mostly want to focus on is this:
for (var i = 0; i < jsonObj.length; i++){
var item = xmlDoc.createElement("item");
channelTag.appendChild(item); // you're adding a node here
$.each(itemList, function(index) {
$(channelTag).children('item')[i].appendChild(... // and here
})
}
Instead of appending nodes multiple times per iteration, you should create and populate your node before add it it to channelTag.
Here's a way your could do it:
// use a "$" sign as a variable name prefix, so you know it's a Document Element and not a regular javascript variable
var $item = xmlDoc.createElement("item");
// you don't need jQuery for this iteration
itemList.forEach(function (item, index) {
$item.appendChild(itemList[index]).textContent = jsonObj[0].title;
});
// if "channelTag" is a Document Element, rename it "$channelTag"
$channelTag.appendChild(item);
Couple things about the code above:
you don't need jQuery, use forEach instead
there is no way telling what type is channelTag. If it is a selector (of type string), use $(selector), but you are using the appendChild() method before, suggesting it's actually a Document Element. In that case you don't need to wrap it with $()
I don't have the context needed to test this code, so no guarantee it'll work out of the box. But try and re-read your code and go through it top-to-bottom. For each variable, describe its type and value. I found that to be helpful when I'm lost in code.

Create a function that shows text of a selected attribute and value based on function parameters in jQuery

This is the problem I am working on:
An HTML div element contains lists of endangered species grouped by continent and the species population status.
<div>
<ul data-continent="North America">
<li data-species="California condor">Critically Endangered</li>
<li data-species="American bison">Near Threatened</li>
</ul>
<ul data-continent="Europe">
<li data-species="Cave bear">Extinct</li>
</ul>
</div>
Write a function that returns how endangered a species is on a particular continent. For example endangeredSpecies('North America', 'American bison') would return 'Near Threatened'.
Now, I did the following;
return $('ul[data-continent="+continent+"]').find('li[data-species="+species+"]').html()
Why does this not work? I thought .find would traverse the selected element (in this case traverse the child elements of ul elements with data-continent = continent) and then apply .html() to it?
Your string interpolation should look like this
return $('ul[data-continent="' + continent + '"]').find('li[data-species="' + species + '"]').html()
Take advantage of jquery's tag find feature.
The best code:
function endangeredSpecies(continent, species) {
return $("ul[data-continent='"+continent+"']>li[data-species='"+species+"']").text();
}

RivetsJS - Dynamically bind an input to a list item

I'm using RivetsJS to create a dynamic list which will be editable via an input box, and use the two-way data binding to update the element...
The list code is:
<ul id="todo">
<li rv-each-todo="list.todos">
<input type="checkbox" rv-idx="todo.idx" rv-checked="todo.done">
<span>{ todo.summary }</span>
</li>
<ul>
And the RivetsJS binding:
<script type="text/javascript">
var list = {
"todos": [
{"idx":133, "done":1,"summary":"Eat"},
{"idx":25, "done":0,"summary":"Code"},
{"idx":33, "done":1,"summary":"Sleep"},
{"idx":24, "done":0,"summary":"Repeat"}
]
}
rivets.bind($('#todo'), {list: list})
</script>
Now I want to create an element which can edit the items in the list in real time.. something like
<input rv-editing-idx="133"></input>
So that when I change the input data, element 133 on the list would change.. if I change the rv-editing-idx="133" attribute on the input, then another element would be edited..
Any ideas on how I can acheive that?
I hope you figured it out by yourself, if not, here's a possible solution: https://jsfiddle.net/nohvtLhs/1/
You can bind the input element, with the selected element from the array.
How you select the element of the array its up to you, i used in the example some radio input elements. After that, you have a simple binding, like you do it usually.
HTML:
<ul id="todo">
<li rv-each-todo="list.todos">
<input type="radio" rv-idx="todo.idx" rv-checked="todo.done" name="todo" rv-on-change="list.change">
<span>{ todo.summary }</span>
</li>
<ul>
<input id="changeele" rv-value="summary"></input>
JS:
var ele = document.getElementById('changeele');
var eleBinding = rivets.bind(ele, {});
var list = {
"todos": [
{"idx":133, "done":true,"summary":"Eat"},
{"idx":25, "done":false,"summary":"Code"},
{"idx":33, "done":false,"summary":"Sleep"},
{"idx":24, "done":false,"summary":"Repeat"}
],
"change": function(event, scope) {
eleBinding.unbind();
eleBinding = rivets.bind(ele, scope.list.todos[scope.index]);
}
}
rivets.bind(document.getElementById('todo'), {list: list})

Mustache (or Handlebars) iterating over two lists

I have two arrays:
var content = {
"girls": ["Maria", "Angela", "Bianca"],
"digits": ["21.143.191.2", "123.456.78.90", "971.6.17.18.1"]
};
and a template:
<script id="template" type="text/template">
<ul>
<li>{{girls}}</li>
</ul>
</script>
I'd like the end result to be:
<ul>
<li>Maria</li>
<li>Angela</li>
<li>Bianca</li>
</ul>
I've tried block mustaches like {{#girls}} {{.}} {{/girls}} and {{#digits}} {{.}} {{/digits}} but no matter which way I nest them I seem to get repeats instead of interlacing.
Any Ideas?
PS: Obviously in the future we'll be asking for IP addresses, not phone numbers.
PPS: None of those are intended to be real IPs, please don't try to contact those girls!
You need to rearrange your content so that you can iterate over just one thing. If you combine those two arrays with something like this:
var data = { girls: [ ] };
for(var i = 0; i < content.girls.length; ++i)
data.girls.push({
name: content.girls[i],
number: content.digits[i]
});
Then a template like this:
<script id="template" type="text/template">
<ul>
{{#girls}}
<li>{{name}}</li>
{{/girls}}
</ul>
</script>
should work.
With "mu is too short"'s advice. And a few crazy ideas I came up with an interesting approach to complex templating. *It almost works!
So let's say I have this content (or data or view) which I want to put into a template:
var content = {
title: "Black Book",
girls: ["blonde", "brunette", "redhead"],
digits: ['123', '456', '789'],
calc: function () {
return 2 + 4;
}
};
And I have a template like this:
<script type="text/template" id="template">
<h1>{{title}}</h1>
<h3>{{calc}}</h3>
<ul>
<li>{{hair}}</li>
</ul>
</script>
And the end result I want is this:
<h1>Black Book</h1>
<h3>6</h3>
<ul>
<li>blonde</li>
<li>brunette</li>
<li>redhead</li>
</ul>
The problem we'll encounter is that we have arrays (or lists) nested in our original content, and if we tried to Mustache.render(html, content) we'd end up with only one li item or a whole array within one href="" attribute. So sad...
So he's the approach, pass through the template multiple times. The first time, pass through and replace the top level items, and adjust the template for the next pass through. The second time, adjust one of the lists, and adjust the template for the third pass through, etc for how ever many layers of content you have. Here's the new starting template:
<script type="text/template" id="template">
<h1>{{title}}</h1>
<h3>{{calc}}</h3>
<ul>
{{#hair}}
{{#digits}}
<li>{{hair}}</li>
{{/digits}}
{{/hair}}
</ul>
</script>
On the first pass through fill in the top level stuff, and change {{digits}} to {{.}}
$.each(content, function (index, value) {
template2 = template.replace(/{{title}}/ig, content.title)
.replace(/{{calc}}/ig, content.calc)
.replace(/{{digits}}/ig, '{{.}}');
});
Now you have this:
<h1>Black Book</h1>
<h3>6</h3>
<ul>
{{#hair}}
{{#digits}}
<li>{{hair}}</li>
{{/digits}}
{{/hair}}
</ul>
On the next pass through we'll just call Mustache.render(template2, content.digits); and that should give us:
<h1>Black Book</h1>
<h3>6</h3>
<ul>
{{#hair}}
<li>{{hair}}</li>
<li>{{hair}}</li>
<li>{{hair}}</li>
{{/hair}}
</ul>
And that's where my logic dies, haha. Any help thinking this through would rock! I'm thinking I could take out the {{hair}} block elements and just do a $.each pass through content.girls and stack .replace three times. Or I could try to start with lowest level of content, and work my way up. I dunno.
All this basically leaves me wondering if there's some "logic-less" way for this kind of nesting or multiple pass throughs to be put into mustache does handlebars do it?

Group javascript xml output by letters or other attributes

I have an xml document that looks something like this
<?xml version="1.0" encoding="UTF-8"?>
<mapPoints>
<annotation cat="A" title="123" type="pointer"/>
<annotation cat="A" title="333" type="pointer"/>
<annotation cat="B" title="555" type="pointer"/>
</mapPoints>
I am parsing the XML into my javascript document.
Now, I want to group the output of my xml file in different divs base on the cat in the xml so that the markup would look something like this
<div class="A">
<ul>
<li>123 cat A</li>
<li>333 cat A</li>
</ul>
</div>
<div class="B">
<ul>
<li>555 cat B</li>
</ul>
</div>
My parsing is working properly but I don't know how I would group the entries and then output them into different divs. The solution needs to be dynamic since it could be a lot of categories.
Using jQuery and assuming you have your xml in a variable xml, try this:
$(xml).find('mapPoints annotation').each(function() {
var class = $(this).attr('cat');
if ($('div.' + class).length == 0) {
$('#annotations').append('<div class="' + class + '"><ul></ul></div>');
}
$('div.' + class + ' ul').append(getLiFromXml(this));
});
getLiFromXml is a function that returns <li>123 cat A</li> for <annotation cat="A" title="123" type="pointer"/> - hopefully, you can easily write it.
UPDATE
Updated the code to add a div first. I assumed that you store all your divs in a container (div, td, whatever...) with id="annotations"

Categories

Resources