Making dropdown menu with multidimensional array - javascript

I have a multidimensional object array like this.
var categories = [{
text: "engine",
children: [1,2,3, {
text: "piston",
children: [4,5,6]
}]
}, {
text: "tire",
children: [7,8,9]
}];
Everytime the index of the categories array equals an array there must be a dropdown menu with the contents of the array in it and it can be endless. If the index of the array is not equal to an array it should just be an a tag.
I am trying to achieve something like this:
https://www.w3schools.com/bootstrap/tryit.asp?filename=trybs_ref_js_dropdown_multilevel_css&stacked=h
I have tried altering this code below to make it suit my needs I did this in combination with a self made dropdown menu in html and css, but at this point it does not make sense anymore.
function menuToElement(menu) {
const ul = document.createElement("ul");
for (const item of menu) {
const li = document.createElement("li");
if (Object(item) === item) {
li.textContent = item.text;
li.appendChild(menuToElement(item.children));
} else {
li.textContent = item;
}
ul.appendChild(li);
}
return ul;
}
const ul = menuToElement(categories);
document.getElementById("menu").appendChild(ul);
The result I was getting is that I was only able to show the names engine and tire, but couldn't get a dropdown with the children in it to work. What I want to ask is if someone can explain what the code above does exactly and whether I approach this in the right way.
This is how I tried making the dropdown menu it is not fully working anymore because I was trying to make it append using the other code.
<div class="popup" onclick="togglePopup2()">
<div class="sel popuptext" id="myPopup2">
<div class='txt'>Select</div>
<div id="Select" class="options hide">
<div>Option 1</div>
<div>Option 2</div>
<div>Option 3</div>
</div>
</div>
<img src="~/images/circle.png" class="category" id="1">
</div>
var txt = $('.txt'),
options = $('.options');
txt.click(function (e) {
e.stopPropagation();
var content = $(e.target).text();
console.log(content);
$('#'+ content).show();
});
$('body').click(function (e) {
options.hide();
});
}

Your code seems to work ok.
At first it creates an unordered list (<ul>). Then goes through all elements of the input parameter and for each item create a list item element (<li>). If an element is an object then set its text to .text property of the item and recursively calls the same function with children of this item (.children) as parameters and append the result to previously created <li> element. Otherwise it just renders list item element with text being the same as the item.
Here is one simple fiddle using your code and some simple css (you should do it with click instead of hover): https://jsfiddle.net/zyuhqo3k/1/
function menuToElement(menu) {
const ul = document.createElement("ul");
for (const item of menu) {
const li = document.createElement("li");
if (Object(item) === item) {
li.textContent = item.text + ' \u25BD';
li.appendChild(menuToElement(item.children));
} else {
li.textContent = item;
}
ul.appendChild(li);
}
return ul;
}
var categories = [{
text: "engine",
children: [1,2,3, {
text: "piston",
children: [4,5,6]
}]
}, {
text: "tire",
children: [7,8,9]
}];
const ul = menuToElement(categories);
document.getElementById("menu").appendChild(ul);
li > ul {
display: none;
}
li:hover > ul {
display: block;
}
<div id="menu">
</div>

Adapted Answer JS/jQuery based on code provided
HTML
<div id='myMenu' class="dropdown">
</div>
CSS
.dropdown-submenu {
position: relative;
}
.dropdown-submenu .dropdown-menu {
top: 0;
left: 100%;
margin-top: -1px;
}
jQuery
var categories = [{
text: "engine",
children: [1, 2, 3, {
text: "piston",
children: [4, 5, 6]
}]
}, {
text: "tire",
children: [7, 8, 9]
}];
function menuToElement(menu) {
const ul = document.createElement("ul");
ul.className = 'dropdown-menu';
for (const item of menu) {
const li = document.createElement("li");
if (Object(item) === item) {
li.textContent = item.text;
li.appendChild(menuToElement(item.children));
li.className = "dropdown-submenu";
} else {
li.textContent = item;
}
ul.className = 'dropdown-menu';
ul.appendChild(li);
}
return ul;
}
$(document).ready(function() {
var menu = menuToElement(categories);
document.getElementById('myMenu').innerHTML = "<button class='btn btn-default dropdown-toggle' type='button' data-toggle='dropdown'>Categories<span class='caret'></span></button>";
document.getElementById('myMenu').appendChild(menu);
liList = document.getElementsByTagName('li');
for (var i = 0; i < liList.length; i++) {
items = liList[i].childNodes;
if (items.length > 1)
{
liList[i].innerHTML = "<a class='test' tabindex='-1' href='#'>" + items[0].textContent+ "<span class='caret'></span></a>" + "<ul class='dropdown-menu'>" + items[1].innerHTML + "</ul>";
}
else if (items.length == 1){
liList[i].innerHTML = "<a class='test' tabindex='-1' href='#'>" + items[0].textContent + "</a>";
}
}
$('#myMenu a').on("click", function(e) {
$(this).next('ul').toggle();
e.stopPropagation();
e.preventDefault();
});
});
Working JS Fiddle
https://jsfiddle.net/2h8adqut/10/

Related

How to display dynamically generated <select> in a row on a same line?

I need to dynamically display the list of ingredients in a row of 3 columns, but I've got the problem that my output isn't aligned on a row. Can anyone help with the solution, It may look a little complicated, but I am getting desperate already.
function display(){
var lght = jsn[0].ingredient.length;
var old_val = new Array(lght);
var ell = "";
var ul = document.getElementById("comp_tag");
for(let a=0; a < lght; a++){
var li = document.createElement('li');
var divr = document.createElement('div');
li.setAttribute("id","lineid_"+a);
divr.setAttribute("class","row align-middle");
ul.appendChild(li);
li.appendChild(divr);
var col_one = document.createElement('div');
col_one.setAttribute("class","col-6 text-left");
col_one.setAttribute("id","col1"+a);
divr.appendChild(col_one);
col_one.appendChild(document.createTextNode("value 1"));
var col_two = document.createElement('div');
col_two.setAttribute("class","col-3 text-right");
col_two.setAttribute("id","col2"+a);
divr.appendChild(col_two);
col_two.appendChild(document.createTextNode("value 2"));
var col_three = document.createElement('div');
col_three.setAttribute("class","col-3 text-left");
col_three.setAttribute("id","col3"+a);
divr.appendChild(col_three);
var pb = init_workspace(jsn[0].ingredient[a].type);
var sel = document.createElement("select");
sel.setAttribute("class","custom-select");
sel.setAttribute("id","c_select"+a);
col_three.appendChild(sel);
for(var t=0; t<pb.length; t++){
var optn = document.createElement("option");
if(jsn[0].ingredient[a].unit == pb[t].name){
optn.setAttribute("selected","")
};
optn.setAttribute("value",pb[t].name);
sel.appendChild(optn);
optn.appendChild(document.createTextNode(pb[t].name));
}
And my HTML part is:
<div class="col-md text-right" id="divid">
<p class="text-uppercase"><b>ingredients</b></p>
<ul id="comp_tag">
</ul>
</div>
With out the jsn object to recreate this example as you would see it, I am supplying a static example below...
Make the parent element of your list items a flex container. This will place the block level items in a row. To illustrate this I have placed static list items within a UL and targeted it using CSS display: flex;.
#divid #comp_tag {
display: flex;
}
Following is a very simple snippit that recreates an object and places the objects items in a row using display:flex on the parent UL element.
Hope this helps...
let obj = {
0: {
'name': 'Cajun Delight',
'ingredients': {
'type': {
'salt': '1 teaspoon',
'garlic': '2 cloves',
'pepper': '1 pinch'
}
}
},
1: {
'name': 'Midwest Bland',
'ingredients': {
'type': {
'salt': '1 teaspoon',
'garlic': '2 cloves',
'pepper': '1 pinch'
}
}
},
2: {
'name': 'Asian Stir Fry',
'ingredients': {
'type': {
'salt': '1 teaspoon',
'garlic': '3 cloves',
'pepper': '1 pinch',
'soy_sauce': '1/4 cup'
}
}
}
}
function displayInfo() {
let compTag = document.getElementById("comp_tag");
for (let values in obj) {
let li = document.createElement("LI");
li.textContent = obj[values].name;
compTag.append(li);
compTag.style.justifyContent = "space-around";
if (obj[values].ingredients.type) {
for (let type in obj[values].ingredients.type) {
let subUl = document.createElement("UL");
let subLi = document.createElement("LI");
li.append(subUl);
subUl.append(subLi);
subLi.textContent = `${obj[values].ingredients.type[type]}`;
}
}
}
}
displayInfo();
#divid #comp_tag {
display: flex;
list-style-type: none;
}
<div class="col-md text-right" id="divid">
<p class="text-uppercase"><b>ingredients</b></p>
<ul id="comp_tag">
</ul>
</div>

Parse JSON foreach with JS, shows HTML list

I am currently trying to parse a JSON with JavaScript. My issue is that I'd like the output to look like this:
<li>AppName1</li>
<li>AppName2</li>
<!-- and so on... -->
However it just does not work and I don't know how to achieve that. This is the object deserialized from the JSON response:
{
"data": [{
"AppId": 1,
"AppName": "AppName1",
"AppSize": "2.1"
}, {
"AppId": 2,
"AppName": "AppName2",
"AppSize": ""
}]
}
This is my .js file:
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var myObj = JSON.parse(this.responseText);
document.getElementById("test").innerHTML = myObj.AppName;
}
};
xmlhttp.open("GET", "json.json", true);
xmlhttp.send();
This is in my HTML file
<p id="test"></p>
Any help would be appreciated as I really cannot seem to understand this a single bit. Thank you so much!
Firstly note that you can only have li elements as children of <ul> or <ol>, so the p element needs to be changed.
The AppName property is part of the objects within data, so you will need to either loop through them:
myObj.data.forEach(function(o) {
document.getElementById("test").innerHTML += '<li>' + o.AppName + '</li>';
}
Or access them, individually by index:
document.getElementById("test").innerHTML = '<li>' + myObj.data[0].AppName + '</li>'; // first item only
var myObj = {
"data": [{
"AppId": 3,
"AppName": "AnimojiStudio",
"AppSlug": "animojistudio",
"AppIcon": "https:\/\/img.lmdinteractive.pro\/icons\/animojistudio.png",
"AppUrl": "https:\/\/ipa.lmdinteractive.pro\/ipa\/appstore\/animojistudio.ipa",
"AppVersion": "1.2.2",
"AppSize": "2.1"
}, {
"AppId": 2,
"AppName": "Cute Cut Pro",
"AppSlug": "cute-cut-pro",
"AppIcon": "http:\/\/is2.mzstatic.com\/image\/thumb\/Purple118\/v4\/03\/70\/69\/03706968-2399-a1d8-e7c4-12897394ead9\/source\/512x512bb.jpg",
"AppUrl": "https:\/\/ipa.lmdinteractive.pro\/ipa\/appstore\/cutecutpro.ipa",
"AppVersion": "",
"AppSize": ""
}]
}
document.getElementById("test").innerHTML = '<li>' + myObj.data[0].AppName + '</li>';
<ul id="test"><li>
If you just want a list of the AppName properties, you could do something like the below with jQuery. See the comments in the code for details:
// Below is the JSON string from the OP's link
let json = '{"data":[{"AppId":3,"AppName":"AnimojiStudio","AppSlug":"animojistudio","AppIcon":"https:\/\/img.lmdinteractive.pro\/icons\/animojistudio.png","AppUrl":"https:\/\/ipa.lmdinteractive.pro\/ipa\/appstore\/animojistudio.ipa","AppVersion":"1.2.2","AppSize":"2.1"},{"AppId":2,"AppName":"Cute Cut Pro","AppSlug":"cute-cut-pro","AppIcon":"http:\/\/is2.mzstatic.com\/image\/thumb\/Purple118\/v4\/03\/70\/69\/03706968-2399-a1d8-e7c4-12897394ead9\/source\/512x512bb.jpg","AppUrl":"https:\/\/ipa.lmdinteractive.pro\/ipa\/appstore\/cutecutpro.ipa","AppVersion":"","AppSize":""}]}';
// Parse the JSON string into a JS object
json = JSON.parse(json);
let html = "";
// Loop over the object and append a list item for each AppName property.
$.each(json.data, function (index, item) {
html += "<li>" + item.AppName + "</li>";
});
// Append the list to the div.
$("#container").append(html);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.3/jquery.min.js"></script>
<div id="container"></div>
Using forEach loop and append. Inserting li inside a p tag is not a good idea even though it works. Convert the p into a ul/ol
var data = {
"data": [{
"AppId": 3,
"AppName": "AnimojiStudio",
"AppSlug": "animojistudio",
"AppIcon": "https:\/\/img.lmdinteractive.pro\/icons\/animojistudio.png",
"AppUrl": "https:\/\/ipa.lmdinteractive.pro\/ipa\/appstore\/animojistudio.ipa",
"AppVersion": "1.2.2",
"AppSize": "2.1"
}, {
"AppId": 2,
"AppName": "Cute Cut Pro",
"AppSlug": "cute-cut-pro",
"AppIcon": "http:\/\/is2.mzstatic.com\/image\/thumb\/Purple118\/v4\/03\/70\/69\/03706968-2399-a1d8-e7c4-12897394ead9\/source\/512x512bb.jpg",
"AppUrl": "https:\/\/ipa.lmdinteractive.pro\/ipa\/appstore\/cutecutpro.ipa",
"AppVersion": "",
"AppSize": ""
}]
}
data.data.forEach(e =>$('#test').append('<li>' + e.AppName + '</li>' + "<br>"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<ul id="test"></ul>
You can use map() since you have an array inside myObj. What you want to do is returning a li with AppName value
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var myObj = JSON.parse(this.responseText);
var ul = document.getElementById("myUl");
var li = document.createElement('li');
var data = myObj.data;
data.map(app => {
li.textContent = app.AppName;
ul.appendChild(li);
})
}
};
xmlhttp.open("GET", "json.json", true);
xmlhttp.send();
You have your object, and it is parsed so let's concentrate on doing something with that object:
var myObj = {
"data": [{
"AppId": 1,
"AppName": "AppName1",
"AppSize": "2.1"
}, {
"AppId": 2,
"AppName": "AppName2",
"AppSize": ""
}]
};
Now we have that, let's use it in different ways. myObj contains an array called data here. That array is an array of JavaScript objects, each with properties like "AppId", "AppName" etc. which we can access either directly or through an index. So, let's put up some examples of how to do that. Comments in the code
var myObj = {
"data": [{
"AppId": 1,
"AppName": "AppName1",
"AppSize": "2.1"
}, {
"AppId": 2,
"AppName": "AppName2",
"AppSize": ""
}]
};
// Here I create a Bootstrap tab and contents
// call to create a new element on the DOM
function additem(item) {
let lt = $('#list-tab');
let ltc = $('#debug-tabContent');
let thing = item.name;
let thingId = "list-" + thing;
let thingTabId = thingId + "-list";
let ttab = $('<a />')
.addClass('list-group-item list-group-item-action')
.data('toggle', "list")
.prop("id", thingTabId)
.attr('role', 'tab')
.prop('href', '#' + thingId)
.html(item.name);
ttab.appendTo(lt);
let lc = $('<div />')
.addClass('tab-pane fade')
.prop("id", thingId)
.attr('role', 'tabpanel')
.text(JSON.stringify(item.obj));
// .text("test");
lc.appendTo(ltc);
}
// * cheat, put the objects in a bootstrap tab content list
additem({
name: "myObj",
obj: myObj
});
additem({
name: "myObjW",
obj: window["myObj"]
});
additem({
name: "data",
obj: myObj.data
});
additem({
name: "data0",
obj: myObj.data[0]
});
additem({
name: "AppName",
obj: myObj.data[0].AppName
});
// pure JS walk
// Here I create a LI list as a Bootstrap list group
let len = myObj.data.length;
let myP = document.getElementById("test");
let myReg = document.getElementById("mylist-reg");
let newUl = document.createElement("ul");
newUl.classList.add('list-group');
newUl.classList.add('list-group-primary');
for (var i = 0; i < len; i++) {
let newLi = document.createElement("li");
let newContent = document.createTextNode(myObj.data[i].AppName);
newLi.appendChild(newContent);
newLi.setAttribute("id", "app-" + myObj.data[i].AppId); //has to be unique
newLi.setAttribute("class", "list-group-item");
newUl.appendChild(newLi);
}
// put the list after the paragraph
document.body.insertBefore(newUl, myP);
let myLast = document.getElementById("app-2");
myLast.classList.add("active");
//activate the bootstrap tab clicks
$('#list-tab').on('click', 'a', function(e) {
e.preventDefault();
$(this).tab('show');
});
// just do it as strings
let html = "";
for (var i = 0; i < len; i++) {
let textel = "<li id='app-js-" + myObj.data[i].AppId + "'>" + myObj.data[i].AppName + "</li>";
html = html + textel;
}
myReg.innerHTML = html;
// jQuery, similar to prior
$.each(myObj.data, function(index, el) {
let textel = "<li id='app-jq-" + el.AppId + "'>" + index + ":" + el.AppName + "</li>";
$('#mylist-jq').append(textel);
});
// jQuery, similar to prior
$.each(myObj.data, function(index, el) {
let elid = 'app-jq2-' + el.AppId;
$("<li />").prop("id", elid).text(el.AppName)
.appendTo('#mylist-jq2');
});
.list-group-item {
border: 1px lime solid
}
.list-item-last {
color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.2.1/css/bootstrap.min.css" />
<ul id="mylist-reg"></ul>
<ul id="mylist-jq"></ul>
<ul id="mylist-jq2"></ul>
<p id="test" class="row">put stuff after here</p>
<div class="row">
<div class="col-4">
<div class="list-group" id="list-tab" role="tablist">
</div>
</div>
<div class="col-8">
<div class="tab-content" id="debug-tabContent">
<div class="tab-pane fade show active" id="list-home" role="tabpanel" aria-labelledby="list-home-list">Click a tab to see one.</div>
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.2.1/js/bootstrap.bundle.min.js"></script>

Append text by matching object array's attribute and element id's digit

I have a list of object array with id and value as its properties. Basically what I want is, the num.items[i].value should go in each div as pair. One: one and so on.
If num.items[i].id doesn't have the digit (like the array doesn't include id 3) then the id="digit_3" should be left blank.
HTML
<ul>
<li>One: <div id="digit_1">s</div></li>
<li>Two: <div id="digit_2">sd</div></li>
<li>Three: <div id="digit_3">sdf</div></li>
<li>Four: <div id="digit_4">sdf</div></li>
</ul>
Javascript
var num = {
items: [
{ id:4, value:"four"},
{ id:1, value:"one"},
{ id:2, value:"two"},
]
};
for(var i=0; i<num.items.length; i++){
document.getElementById("digit_"+i+1).innerHTML = i+1;
console.log(i+1)
}
Required output
One: one
Two: two
Three:
Four: four
I know we cannot compare the id digit but any modification in HTML is greatly appreciated.
it's really simple - you just have to understand arrays and objects:
var num = {
items: [{
id: 4,
value: "four"
},
{
id: 1,
value: "one"
},
{
id: 2,
value: "two"
},
]
};
var cleanableElements = document.querySelectorAll("ul li div");
for (var i = 0; i < cleanableElements.length; i++) {
cleanableElements[i].innerHTML = '';
}
var index;
for (var i = 0; i < num.items.length; i++) {
index = num.items[i].id;
document.getElementById("digit_" + index).innerHTML = num.items[i].value;
}
<ul>
<li>One:
<div id="digit_1"></div>
</li>
<li>Two:
<div id="digit_2"></div>
</li>
<li>Three:
<div id="digit_3"></div>
</li>
<li>Four:
<div id="digit_4"></div>
</li>
</ul>
Best idea would be to select all elements with querySelectorAll and setting empty before next step. You can't really detect all #digit_X id's so you can't just check for unchanged DIVs as you can't reliably detect them all.
You should loop ul li div then check id whether is in num.items.
Assuming your id format is digit_*.
var num = {
items: [
{ id:4, value:"four"},
{ id:1, value:"one"},
{ id:2, value:"two"},
]
}
function checkItems(num){
items = document.querySelectorAll('#target_div li div')
indexes = num.items.reduce( (pre, cur) => {
pre[cur.id] = cur.value
return pre
}, {}) // loop num.items then create one dict with key=id.
items.forEach(function(item){ //loop ul li div, then check whether id in dict=indexes.
let ids = item.id.split('_')
if(ids[1] in indexes){
item.innerHTML += ' #Found:'+item.id
} else {
item.innerHTML = ''
}
})
}
checkItems(num)
<ul id="target_div">
<li>One: <div id="digit_1">s</div></li>
<li>Two: <div id="digit_2">sd</div></li>
<li>Three: <div id="digit_3">sdf</div></li>
<li>Four: <div id="digit_4">sdf</div></li>
</ul>
I know I did something awkward but if div have already some value then above example will not work expect #sphinx answer I guess
var num = {
items: [{
id: 4,
value: "four"
},
{
id: 1,
value: "one"
},
{
id: 2,
value: "two"
},
]
};
var idsArray = [];
var valuesArray = [];
for (var value of num.items) {
idsArray.push(value.id);
valuesArray.push(value.value);
}
var maxId = Math.max(...idsArray);
for (var i = 1; i <= maxId; i++) {
if (idsArray.indexOf(i) !== -1) {
document.getElementById("digit_" + i).innerHTML = valuesArray[idsArray.indexOf(i)];
} else {
document.getElementById("digit_" + i).innerHTML = "";
}
}
div {
display: inline
}
<ul>
<li>One: <div id="digit_1">s</div></li>
<li>Two: <div id="digit_2">sd</div></li>
<li>Three: <div id="digit_3">sdf</div></li>
<li>Four: <div id="digit_4">sdf</div></li>
</ul>

Sort the divs by content

I have a problem.
.titel
{
display: inline-block;
padding:5px 0 ;
}
#sort div div
{
display: inline-block;
padding:5px 0 ;
}
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<div>
<div class="titel achternaam" >Achternaam</div>
<div class="titel voornaam" >Voornaam</div>
<div class="titel kantoor" >Kantoor</div>
</div>
<div class="spann">
<span class="ui-icon ui-icon-circle-triangle-n"></span>
<span class="ui-icon ui-icon-circle-triangle-s"></span>
<span class="ui-icon ui-icon-circle-triangle-n"></span>
<span class="ui-icon ui-icon-circle-triangle-s"></span>
<span class="ui-icon ui-icon-circle-triangle-n"></span>
<span class="ui-icon ui-icon-circle-triangle-s"></span>
</div>
<div id="sort">
<div class="someaspcode" onClick="someaspcodethatifyouclickitwilgotothepage">
<div class="achternaam">bill</div>
<div class="voornaam">gates</div>
<div class="kantoor">123</div>
</div>
<div class="someaspcode" onClick="someaspcodethatifyouclickitwilgotothepage">
<div class="achternaam">jhonny</div>
<div class="voornaam">depp</div>
<div class="kantoor">43321</div>
</div>
The data from div with id sort comes from a database (thats the reason ,that I show it like this)
What I whant to do is :
If I click on the first icon it shows the list sorted by voornaam(asc)
If I click on the second icon it shows the list sorted by voornaam(desc)
If I click on the third icon it shows the list sorted by achternaam (asc)
and so further
I have tried everything that I found on stackoverflow and google but none of it worked.
Can someone give me a good piece of advice.
what i whant is something like this
http://jsfiddle.net/7sgw21hn/1/
but it must read the content
things i tried
jQuery - Sorting div contents
https://www.sitepoint.com/community/t/sort-div-order-alphabetically-based-on-contents/39955/2
and many more (can't find it right now)
this is before i click
and this is after
can we do something about this
Here's the demo: http://output.jsbin.com/gojopuh
As mentioned, the first two buttons sort asc and desc on first name.
The second two buttons sort asc and desc on last name.
My code uses bubble sort and takes advantage of replaceChild for performance benefits.
Also with the code below, adding more controls for this data is now trivial.
Code below, any questions just ask.
var controls = document.querySelectorAll('.spann > span');
var dataContainer = document.querySelector('#sort');
var data = document.querySelectorAll('#sort > div');
// select controls
var ascAchternaam = controls[0];
var descAchternaam = controls[1];
var ascVoornaam = controls[2];
var descVoornaam = controls[3];
var ascKantoor = controls[4];
var descKantoor = controls[5];
var ascVerjaardag = controls[6];
var descVerjaardag = controls[7];
// define a user type
function User(achternaam, voornaam, kantoor, verjaardag, elem) {
this.achternaam = achternaam;
this.voornaam = voornaam;
this.kantoor = kantoor;
this.verjaardag = verjaardag;
this.elem = elem;
}
function bubbleSort(order, data, prop) {
// copy data array
var sortingArr = Array.prototype.slice.call(data);
for (var i = sortingArr.length - 1; i >= 0; i--) {
for (var j = 1; j <= i; j++) {
var birthdayA = sortingArr[j-1][prop].split('-');
var birthdayB = sortingArr[j][prop].split('-');
if (order == 'asc') {
if (birthdayA.length > 1) {
if (parseFloat(birthdayA[1], 10) > parseFloat(birthdayB[1], 10) || parseFloat(birthdayA[0], 10) > parseFloat(birthdayB[0], 10)) {
var temp = sortingArr[j-1];
sortingArr[j-1] = sortingArr[j];
sortingArr[j] = temp;
}
} else {
if (sortingArr[j-1][prop] > sortingArr[j][prop]) {
var temp = sortingArr[j-1];
sortingArr[j-1] = sortingArr[j];
sortingArr[j] = temp;
}
}
} else {
if (birthdayA.length > 1) {
if (parseFloat(birthdayA[1], 10) < parseFloat(birthdayB[1], 10) || parseFloat(birthdayA[0], 10) < parseFloat(birthdayB[0], 10)) {
var temp = sortingArr[j-1];
sortingArr[j-1] = sortingArr[j];
sortingArr[j] = temp;
}
} else {
if (sortingArr[j-1][prop] < sortingArr[j][prop]) {
var temp = sortingArr[j-1];
sortingArr[j-1] = sortingArr[j];
sortingArr[j] = temp;
}
}
}
}
}
return sortingArr;
}
// event action
function sortOnClick(order, data, prop) {
var sorted = bubbleSort(order, data, prop);
for (var i = 0; i < sorted.length; i++) {
var user = sorted[i];
var wrapper = user.elem.cloneNode(true);
dataContainer.replaceChild(wrapper, dataContainer.children[i]);
}
return sorted;
}
// used to make the data into a format we need
function formatUsers(data) {
var userData = [];
for (var i = 0; i < data.length; i++) {
var userElem = data[i];
var fname = userElem.querySelector('.achternaam').textContent;
var lname = userElem.querySelector('.voornaam').textContent;
var office = userElem.querySelector('.kantoor').textContent;
var birthday = userElem.querySelector('.verjaardag').textContent;
userData.push(new User(fname, lname, office, birthday, userElem));
}
return userData;
}
// sorter
function initSorter(data) {
// reshape our data
var userData = formatUsers(data);
// add event listeners to controls
ascAchternaam.addEventListener('click', function() {
sortOnClick('asc', userData, 'achternaam');
});
descAchternaam.addEventListener('click', function() {
sortOnClick('desc', userData, 'achternaam');
});
ascVoornaam.addEventListener('click', function() {
sortOnClick('asc', userData, 'voornaam');
});
descVoornaam.addEventListener('click', function() {
sortOnClick('desc', userData, 'voornaam');
});
ascKantoor.addEventListener('click', function() {
sortOnClick('asc', userData, 'kantoor');
});
descKantoor.addEventListener('click', function() {
sortOnClick('desc', userData, 'kantoor');
});
ascVerjaardag.addEventListener('click', function() {
sortOnClick('asc', userData, 'verjaardag');
});
descVerjaardag.addEventListener('click', function() {
sortOnClick('desc', userData, 'verjaardag');
});
}
// init our sorter
initSorter(data);
Let's give this a try then.
You do have to edit your HTML structure so that each 'record' of first name, last name and office has a seperate container. If you also have to go counting the amout of divs that make up one record, the code grows even larger.
I opted for a list as the wrappers, as it's more or less the standard way.
Also added a data-sort attribute to each of the icons so I don't have to go through the hassle of reading the sort type from the header.
<!DOCTYPE html>
<html lang="en">
<head>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.wrap-3, .wrap-6 {
border: 1px solid black;
width: 50%;
}
.wrap-3 > * {
display: inline-block;
width: 32%;
}
.wrap-6 > * {
display: inline-block;
width: 16%;
}
ul {
border: 1px solid black;
list-style: none;
width: 50%;
}
li {
display: block;
width: 100%;
}
li > * {
display: inline-block;
width: 32%;
}
</style>
</head>
<body>
<div class="wrap-3">
<span class="titel achternaam" >Achternaam</span>
<span class="titel voornaam" >Voornaam</span>
<span class="titel kantoor" >Kantoor</span>
</div>
<div id="icons-sort" class="wrap-6">
<span class="ui-icon ui-icon-circle-triangle-n" data-sort="achternaam-asc">up</span>
<span class="ui-icon ui-icon-circle-triangle-s" data-sort="achternaam-desc">down</span>
<span class="ui-icon ui-icon-circle-triangle-n" data-sort="voornaam-asc">up</span>
<span class="ui-icon ui-icon-circle-triangle-s" data-sort="voornaam-desc">down</span>
<span class="ui-icon ui-icon-circle-triangle-n" data-sort="kantoor-asc">up</span>
<span class="ui-icon ui-icon-circle-triangle-s" data-sort="kantoor-desc">down</span>
</div>
<ul>
<li>
<span class="achternaam">Gates</span>
<span class="voornaam">Bill</span>
<span class="kantoor">123</span>
</li>
<li>
<span class="achternaam">Zuckerberg</span>
<span class="voornaam">Mark</span>
<span class="kantoor">456</span>
</li>
<li>
<span class="achternaam">Resig</span>
<span class="voornaam">John</span>
<span class="kantoor">789</span>
</li>
</ul>
<script>
var clear = function clear( node ) {
while (node.firstChild) {
node.removeChild(node.firstChild);
}
return node;
};
document.querySelector('#icons-sort').addEventListener('click', function( event ) {
var list, records, fragment, sortType, field, order;
if (event.target && event.target.hasAttribute('data-sort')) {
list = document.querySelector('ul'),
records = Array.prototype.slice.call(list.querySelectorAll('li')),
fragment = document.createDocumentFragment(),
sortType = event.target.getAttribute('data-sort').split('-'),
field = '.' + sortType[0],
order = sortType[1];
records = records.sort(function( first, second ) {
var firstVal = first.querySelector(field).innerHTML,
secondVal = second.querySelector(field).innerHTML;
if (firstVal < secondVal) return -1;
else if (firstVal > secondVal) return 1;
});
if (order === 'desc') records.reverse();
records.forEach(function( listItem ) {
fragment.appendChild(listItem);
});
clear(list).appendChild(fragment);
}
});
</script>
</body>
</html>

Limit checkbox selections and bind to an array in AngularJS

I am trying to achieve two things:
Bind an array to a list of checkboxes (just a string array), and
Limit the number of selections the user can make to a number between
1 and the number of available items less 1.
I can get (2) to work until the user clicks the last item, at which point it loses track and the items remain selected.
The interactive code is up here: http://codepen.io/adamcodegarden/pen/bdbQqe (forked from a similar example)
My HTML/Angular code:
<p ng-repeat="item in allOptions" class="item" id="{{item}}">
{{item}} <input type="checkbox" ng-change="sync(bool, item)" ng-model="bool" ng-checked="isChecked(item)"> Click this to sync this item with the target array. {{item}} Selected: {{bool}}
and the JS:
var myApp = angular.module("myApp", []);
var maxItems = 1;
myApp.controller('myController', function($scope) {
$scope.isChecked = function(item){
var match = false;
for(var i=0 ; i < $scope.data.length; i++) {
if($scope.data[i] === item) {
match = true;
}
}
return match;
};
$scope.allOptions = [
'one', 'two', 'three', 'four'
];
$scope.data = [
];
$scope.sync = function(bool, item){
if (bool) {
// add item
$scope.data.push(item);
// if we have gone over maxItems:
if ($scope.data.length > maxItems) {
//remove oldest item
$scope.data.splice(0,1);
}
} else {
// remove item
for (var i=0 ; i < $scope.data.length; i++) {
if ($scope.data[i] === item){
$scope.data.splice(i,1);
}
}
}
};
});
I like plunker more than codepen. So I created this plunker
http://plnkr.co/edit/l8gxQHXBQdFeKIuwf3f0?p=preview
The main idea is that I format the original array as:
$scope.allOptions = [
{key: 'one'}, {key: 'two'}, {key: 'three'}, {key:'four'}
];
And slight change to the sync logic as well:
$scope.sync = function(bool, item){
if (bool) {
// add item
$scope.data.push(item);
// if we have gone over maxItems:
if ($scope.data.length > maxItems) {
//remove first item
$scope.data[0].checked = false;
$scope.data.splice(0,1);
}
} else {
// remove item
for (var i=0 ; i < $scope.data.length; i++) {
if ($scope.data[i] === item) {
$scope.data.splice(i,1);
}
}
}
};
Also change html portion:
<p ng-repeat="item in allOptions" class="item" id="{{item.key}}">
{{item.key}} <input type="checkbox" ng-change="sync(item.checked, item)" ng-model="item.checked"> Click this to sync this item with the target array. {{item.key}} Selected: {{bool}}

Categories

Resources