Print obj array in JQ - javascript
First to say that I'm new to JQ and JS and I need a little help.
How can I print this array
var menu = [
{
name: 'Item1',
submenuName: 'submenu-1',
subMenu:[
{
name: 'Item1_1',
submenuName: 'submenu-1-1',
subMenu:[
{
name: 'Item1_1_1',
submenuName: 'submenu-1-1-1',
subMenu:[
{
name: 'Item1_1_1_1',
submenuName: 'submenu-1-1-1-1',
subMenu:[
{
name: 'Item1_1_1_1_1',
//submenuName: 'submenu-1-1-2',
},
]
},
{
name: 'Item1_1_1_2',
//submenuName: 'submenu-1-1-2',
}
]
},
{
name: 'Item1_1_2',
//submenuName: 'submenu-1-1-2',
},
]
},
{
name: 'Item1_2',
//submenuName: 'submenu-1-2',
},
{
name: 'Item1_3',
//submenuName: 'submenu-1-3',
}
]
},
{
name: 'Item2',
//submenuName: 'submenu-2',
}
];
to look in the html like that
<ul data-menu="main" class="menu__level">
<li class="menu__item"><a class="menu__link" data-submenu="submenu-1" href="#">Item1</a></li>
<ul data-menu="submenu-1" class="menu__level">
<li class="menu__item"><a class="menu__link" data-submenu="submenu-1-1" href="#">Item1_1</a></li>
<ul data-menu="submenu-1-1" class="menu__level">
<li class="menu__item"><a class="menu__link" data-submenu="submenu-1-1-1" href="#">Item1_1_1</a></li>
<ul data-menu="submenu-1-1-1" class="menu__level">
<li class="menu__item"><a class="menu__link" data-submenu="submenu-1-1-1-1" href="#">Item1_1_1_1</a></li>
<ul data-menu="submenu-1-1-1-1" class="menu__level">
<li class="menu__item"><a class="menu__link" href="#">Item1_1_1_1_1</a></li>
</ul>
<li class="menu__item"><a class="menu__link" href="#">Item1_1_1_2</a></li>
</ul>
<li class="menu__item"><a class="menu__link" href="#">Item1_1_2</a></li>
</ul>
<li class="menu__item"><a class="menu__link" href="#">Item1_2</a></li>
<li class="menu__item"><a class="menu__link" href="#">Item1_3</a></li>
</ul>
<li class="menu__item"><a class="menu__link" href="#">Item2</a></li>
</ul>
I know that my question is stupid, but I can't figure it out how to make it. This is going to be an drop-down menu.
With recursion:
function getMenuHTML(arr, name) {
var result = '<ul data-menu="' + (name || 'main') + '" class="menu__level">';
for (var i = 0; i < arr.length; i++) {
result += '<li class="menu__item"><a class="menu__link"' + (arr[i].submenuName ? 'data-submenu="' + arr[i].submenuName + '"' : '') + ' href="#">' + arr[i].name + '</a></li>';
if (arr[i].subMenu && arr[i].subMenu.length) {
result += getMenuHTML(arr[i].subMenu, arr[i].submenuName);
}
}
result += "</ul>";
return result;
}
var menu=[{name:'Item1',submenuName:'submenu-1',subMenu:[{name:'Item1_1',submenuName:'submenu-1-1',subMenu:[{name:'Item1_1_1',submenuName:'submenu-1-1-1',subMenu:[{name:'Item1_1_1_1',submenuName:'submenu-1-1-1-1',subMenu:[{name:'Item1_1_1_1_1'},]},{name:'Item1_1_1_2'}]},{name:'Item1_1_2'},]},{name:'Item1_2'},{name:'Item1_3'}]},{name:'Item2'}];
var html = getMenuHTML(menu);
document.getElementById('resultplain').value = html;
document.getElementById('result').insertAdjacentHTML('beforeend', html);
<nav id="result"></nav>
<textarea id="resultplain" style="height: 250px;width:100%"></textarea>
This function will constract that menu:
// createMenu takes an array of objects m and a menuname to be set as data-menu attribute
function createMenu(m, menuname){
// create the ul
var ul = document.createElement("ul");
// if we are at depth 0 then the data-menu should be 'main', else it should be 'submenu-???' depending on the depth
ul.setAttribute("data-menu", menuname);
ul.className = "menu__level";
// now for every object in the menu (m array), create an li element
m.forEach(function(e){
// create the li
var li = document.createElement("li");
li.className = "menu__item";
// create the link
var a = document.createElement("a");
// set its data-submen attribute and href and text content as well
/******* ONLY IF IT HAS A SUB-MENU *************/
if(e.subMenu)
a.setAttribute("data-submenu", e.submenuName);
a.setAttribute("href", "#");
a.textContent = e.name;
// append the link to the li
li.appendChild(a);
// append the li to the ul
ul.appendChild(li);
// if this object contains a subMenu the create it using createMenu with e.submenuName as menuname
if(e.subMenu)
// if you want it to be appended to the li instead of its parent change 'ul.ap...' to 'li.ap...'
ul.appendChild(createMenu(e.subMenu, e.submenuName));
});
// return the ul element
return ul;
}
Note: It should be called like this createMenu(menu, "main"); where the first parameter is your array of objects, and the second ("main") is the data-menu of the first ul.
Note2: createMenu returns an ul element, so you can append it to another element (container) like this:
// create the menu
var myMenuUl = createMenu(menu, "main");
// append it to an element (the body for example)
document.body.appendChild(myMenuUl);
This is an alternative solution using two Javascript template engines and template composition (a form of recursion): jsRender and Handlebars. jsRender detects jQuery and loads as a jQuery plugin (they both can be used also without jQuery).
The data-submenu attribute is added to menu__link and the has-submenu class is added to menu__item only when appropriate.
Note: I have modified the original, target HTML not to include the element ul as child of another element ul (see the first <li class="menu__item"> for example), that would be invalid HTML (check the W3C validator).
// Get compiled jsRender template.
var tmpl = $.templates("#entryPointTemplate");
// Get compiled Handlebars template.
var tmplHandlebarsEntryPoint = $("#entryPointTemplate-handlebars").html()
var tmplHandlebarsRenderFunction = Handlebars.compile(tmplHandlebarsEntryPoint);
// Register the subMenuTemplate Handlebars partial.
Handlebars.registerPartial("subMenuTemplate", $("#subMenuTemplate-handlebars").html());
var menu = [ // Source data.
{
name: 'Item1',
submenuName: 'submenu-1',
subMenu: [{
name: 'Item1_1',
submenuName: 'submenu-1-1',
subMenu: [{
name: 'Item1_1_1',
submenuName: 'submenu-1-1-1',
subMenu: [{
name: 'Item1_1_1_1',
submenuName: 'submenu-1-1-1-1',
subMenu: [{
name: 'Item1_1_1_1_1',
//submenuName: 'submenu-1-1-2',
}, ]
}, {
name: 'Item1_1_1_2',
//submenuName: 'submenu-1-1-2',
}]
}, {
name: 'Item1_1_2',
//submenuName: 'submenu-1-1-2',
}, ]
}, {
name: 'Item1_2',
//submenuName: 'submenu-1-2',
}, {
name: 'Item1_3',
//submenuName: 'submenu-1-3',
}]
}, {
name: 'Item2',
//submenuName: 'submenu-2',
}
];
// Wrap the data in this way for convenience with the template engines, see: `{{for data}}` and `{{#each data}}`.
var data = {
data: menu
}
// Render the jsRender template using data.
var htmlJsrender = tmpl.render(data);
// Render the Handlebars template using data.
var htmlHandlebars = tmplHandlebarsRenderFunction(data);
// Insert the rendered (by jsRender) HTML into the DOM.
$("#result-jsrender").html(htmlJsrender);
// Insert the rendered (by Handlebars) HTML into the DOM.
$("#result-handlebars").html(htmlHandlebars);
<!-- Load jQuery -->
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js" integrity="sha256-/SIrNqv8h6QGKDuNoLGA4iret+kyesCkHGzVUUV0shc=" crossorigin="anonymous"></script>
<!-- Load JsRender -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jsrender/0.9.83/jsrender.min.js"></script>
<!-- Load Handlebars -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.6/handlebars.min.js"></script>
<!-- JSRENDER ************************************************************ -->
<!-- Provide container for rendered jsRender template output: -->
<p><strong>jsRender output:</strong>
</p>
<div id="result-jsrender"></div>
<!-- Declare a JsRender template, in a script block: -->
<script id="subMenuTemplate" type="text/x-jsrender">
<li class="menu__item{{if subMenu}} has-submenu{{/if}}">
<a class="menu__link" {{if submenuName && subMenu}}data-submenu="{{:submenuName}}" {{/if}} href="#">{{:name}}</a>
{{if subMenu}}
<ul data-menu="{{:submenuName}}" class="menu__level">
{{for subMenu tmpl='#subMenuTemplate'/}}
</ul>
{{/if}}
</li>
</script>
<!-- Declare a JsRender template, in a script block: -->
<script id="entryPointTemplate" type="text/x-jsrender">
<ul data-menu="main" class="menu__level">
{{for data tmpl='#subMenuTemplate'/}}
</ul>
</script>
<!-- HANDLEBARS ********************************************************** -->
<!-- Provide container for rendered Handlebars template output: -->
<p><strong>Handlebars output:</strong>
</p>
<div id="result-handlebars"></div>
<!-- Declare an Handlebars template, in a script block: -->
<script id="subMenuTemplate-handlebars" type="text/x-jsrender">
<li class="menu__item{{#if subMenu}} has-submenu{{/if}}">
<a class="menu__link" {{#if subMenu}}data-submenu="{{submenuName}}" {{/if}} href="#">{{name}}</a>
{{#if subMenu}}
<ul data-menu="{{submenuName}}" class="menu__level">
{{#each subMenu}} {{> subMenuTemplate}} {{/each}}
</ul>
{{/if}}
</li>
</script>
<!-- Declare an Handlebars template, in a script block: -->
<script id="entryPointTemplate-handlebars" type="text/x-handlebars-template">
<ul data-menu="main" class="menu__level">
{{#each data}} {{> subMenuTemplate}} {{/each}}
</ul>
</script>
Related
KO visible not working with template
Goal: use KO to show/hide folder, sub-folder, and files, as recursive UL LI list. When a user click on the folders, the child items under that folder will toggle hide/show. Problem: The recursive part is ok. But it does not do toggle. console.log says error that 'show' is undefined. Any idea what went wrong ? Code <script type="text/javascript"> $(function() { ko.applyBindings(viewModel,document.getElementById('resources-panel')); }); var viewModel = { treeRoot: ko.observableArray() }; var FileElement = function(ppp_name, ppp_type, ppp_children) { var self = this; self.ppp_children = ko.observableArray(ppp_children); self.ppp_name = ko.observable(ppp_name); self.ppp_type = ko.observable(ppp_type); self.show = ko.observable(false); self.toggle=function() { self.show(!self.show()); } } var tree = [ new FileElement("IT Dept", "folder",[ new FileElement("IT Overview.docx", "file",[]), new FileElement("IT Server1", "folder",[ new FileElement("IT Server1 Configuration Part 1.docx", "file", []), new FileElement("IT Server1 Configuration Part 2.docx", "file", []), ]), new FileElement("IT Server2", "folder",[]) ]), new FileElement("HR Dept", "folder", []) ]; viewModel.treeRoot(tree); </script> <script id="FileElement" type="text/html"> <ul> <li> <a href="#" data-bind="click: toggle" class="action-link"><br/> <span data-bind="text: ppp_name"></span> </a> <ul data-bind="template: { name: 'FileElement', slideVisible: show, foreach: ppp_children }" ></ul> </li> </ul> </script> <div id="resources-panel" data-bind="template: { name: 'FileElement', slideVisible: show, foreach: $data.treeRoot }"></div>
Your top level binding context is the treeRoot, and treeRoot doesn't have a "show" property it's just a simple array so you probably want to remove that first show binding altogether <div id="resources-panel" data-bind="template: { name: 'FileElement', foreach: $data.treeRoot }"></div> Then within the FileElement template you'll want to move the show binding to the outside of the template binding like f_martinez suggested <ul data-bind="slideVisible: show, template: { name: 'FileElement', foreach: ppp_children }" ></ul> Here's an example jsFiddle
AngularJS Select from Json (similiar to MySQL)
I have a json: {"sectionTitle":"Account Information","sectionItems":[{"itemTitle":"Balance","url":"/account/balance","selected":true},{"itemTitle":"Account Statement","url":"/account/statementsearch","selected":false},{"itemTitle":"Deposit","url":"/account/deposit","selected":false},{"itemTitle":"Withdrawal","url":"/account/withdraw","selected":false},{"itemTitle":"Edit Profile","url":"/account/editprofile","selected":false},{"itemTitle":"Change Password","url":"/account/changepassword","selected":false}]} Now I just want to check if there is an item (child) inside sectionTitle where selected is true. Something like this in SQL SELECT * FROM sectionItems WHERE selected=true Can I do something similar in angular js, so I can check if the the parents has children? I hope you understood my question. This is my html <nav class="sidebar-nav"> <ul class="nav metismenu" id="side-menu-help"> <li ng-repeat="menuItem in accountCtrl.menuStructure"> <a class="{{ (menuItem.sectionItems.length > 0) ? 'metisHasChildren' : '' }}" href="/en/help-area/poker-help/poker-rules/"> <span ng-if="menuItem.sectionItems.length > 0" class="fa arrow fa fa-angle-double-down"></span> {{ ::menuItem.sectionTitle }} {{ ::menuItem }} </a> <ul class="nav nav-second-level collapse in"> <li ng-repeat="subMenuItem in menuItem.sectionItems" ng-click="accountCtrl.changePage(subMenuItem.url)"> <a ng-class="(subMenuItem.selected) ? 'page-active' : ''">{{ ::subMenuItem.itemTitle }}</a> </li> </ul> </li> </ul> </nav>
You do not need to anything fancy to get this working. Simply convert the json to an object and access the property you want using dot notation. So for example: var json = JSON.parse(json); var selectedItems = []; angular.forEach(json.sectionItems, function(sectionItem) { if (sectionItem.selected) { selectedItems.push(sectionItem); } }); Would convert the json string to an object and then loop over each sectionItem child, check for selected is true and create an array of matching items.
You can use a forEach loop. This example will return an array of all sectionItems where selected equals true, but you can return whatever you'd like. $scope.items =[]; angular.forEach(sectionItems, function(item){ if (item.selected === true){ $scope.items.push(item); } }) UPDATE Here's a working plunk To make this work inline with ng-repeat you will use it in a filter, like this: app.filter('menuFilter', function() { return function(menuItems) { var filtered = []; angular.forEach(menuItems, function(menuItem) { angular.forEach(menuItem.sectionItems, function(item) { if (item.selected === true) { filtered.push(menuItem); } }); }); return filtered; } }); And change your markup, like this: ng-repeat="menuItem in accountCtrl.menuStructure | menuFilter "
First, you don't need to do any custom filter or anything like that, just use the standard filter, as below: <li ng-repeat="menuItem in accountCtrl.menuStructure | filter: { sectionItems: { selected: true } }"> {{ menuItem.sectionTitle }} Working demo: angular.module('app', []) .controller('accountController', function() { var vm = this; vm.menuStructure = [ { "sectionTitle":"Account Information", "sectionItems":[ { "itemTitle":"Balance", "url":"/account/balance", "selected":true }, { "itemTitle":"Account Statement", "url":"/account/statementsearch", "selected":false }, { "itemTitle":"Deposit", "url":"/account/deposit", "selected":false }, { "itemTitle":"Withdrawal", "url":"/account/withdraw", "selected":false }, { "itemTitle":"Edit Profile", "url":"/account/editprofile", "selected":false }, { "itemTitle":"Change Password", "url":"/account/changepassword", "selected":false } ] }, { "sectionTitle":"Account Information 2", "sectionItems":[ { "itemTitle":"Balance", "url":"/account/balance", "selected":false }, { "itemTitle":"Account Statement", "url":"/account/statementsearch", "selected":false }, { "itemTitle":"Deposit", "url":"/account/deposit", "selected":false }, { "itemTitle":"Withdrawal", "url":"/account/withdraw", "selected":false }, { "itemTitle":"Edit Profile", "url":"/account/editprofile", "selected":false }, { "itemTitle":"Change Password", "url":"/account/changepassword", "selected":false } ] } ]; }); <!DOCTYPE html> <html ng-app="app"> <head> <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.7/angular.min.js"></script> </head> <body ng-controller="accountController as accountCtrl"> <ul> <li ng-repeat="menuItem in accountCtrl.menuStructure | filter: { sectionItems: { selected: true } }"> {{ menuItem.sectionTitle }} <ul class="nav nav-second-level collapse in"> <li ng-repeat="subMenuItem in menuItem.sectionItems" ng-click="accountCtrl.changePage(subMenuItem.url)"> <a ng-class="{ 'page-active': subMenuItem.selected }" ng-bind="::subMenuItem.itemTitle"></a> </li> </ul> </li> </ul> </body> </html> Tips: Instead of using ngClass with ternary operator, you can simply use this way: ng-class="{ 'page-active': subMenuItem.selected }" Even if works in this way that you're using, I'd recommend you to take a look on the special-repeats, it fits really well in your situation. I hope it helps!!
var JSONStr=[{"sectionTitle":"Account Information","sectionItems":[{"itemTitle":"Balance","url":"/account/balance","selected":true},{"itemTitle":"Account Statement","url":"/account/statementsearch","selected":false},{"itemTitle":"Deposit","url":"/account/deposit","selected":false},{"itemTitle":"Withdrawal","url":"/account/withdraw","selected":false},{"itemTitle":"Edit Profile","url":"/account/editprofile","selected":false},{"itemTitle":"Change Password","url":"/account/changepassword","selected":false}]}]; var result = JSONStr.where({ selected: true });
Using Iron Router to set a query variable
I am trying to use Iron Router to call a page and filter results by the route. Effectively, the application allows you to create items. Items include an array that can have 0 to many tags: Item: { _id: <assigned by application>, itemTags: [] } The dropdown list on the navbar collects all tags from the items, does some cleaning, and then drops them into the dropdown menu: // HTML <template name="header"> <nav class="navbar navbar-default" role="navigation"> <div class="container-fluid"> <div class="navbar-header"> <ul class="nav navbar-nav"> <li class="dropdown"> Select List to View <span class="caret"></span> <ul class="dropdown-menu" role="menu"> <li>All List Items</li> <li class="divider"></li> {{#each uniqueTags}} <li>{{this}}</li> {{/each}} </ul> </li> </ul> </div> </div> </nav> </template> // JS Template.header.helpers({ uniqueTags: function() { var allItems = Items.find({checked: false}); var uniqueTags = allItems.map((function(allItems) {return allItems.itemTags;})); uniqueTags = _.flatten(uniqueTags); uniqueTags = _.compact(uniqueTags); uniqueTags = _.uniq(uniqueTags); uniqueTags = _.sortBy(uniqueTags, function (name) {return name;}); return uniqueTags; } }); The router then sends you to "/list/". Everything up to this point works great. But then it falls apart. My problem is twofold: 1) How do I appropriately grab the data context of just the items that have the tag listed somewhere in the array? 2) How do I display those items on the "list" page? I have zero idea what should be in list.js to handle the returned data context My current code is below, but it's hacked up and clearly not working :) // JS - router.js Router.route('/list/:tagName', { name: 'list', data: function() { return Items.find({$and: [ {checked: false}, {listItem: true}, {itemTags: {$in: ['this.params.tagName']}} ]}); } }); // HTML - list.html <template name="list"> <h3><span class="label label-default view-list">List</span></h3> {{#each listItems}} {{> item}} {{/each}} </template> // JS - list.js Template.list.helpers({ listItems: function() { <somehow use the returned data context> } }); Thanks for your help!
This would work Router.route('/list/:tagName', { name: 'list', data: function() { return { 'listItems' : Items.find({$and: [ {checked: false}, {listItem: true}, {itemTags: {$in: [this.params.tagName]}} ]}) }; } }); // HTML - list.html <template name="list"> <h3><span class="label label-default view-list">List</span></h3> {{#each listItems}} {{> item}} {{/each}} </template> // You have to write a item template // JS - list.js Template.list.helpers({ //listItems: function() { // this helper is not required // you could access the data by this.data.listItems //<somehow use the returned data context> //} });
Nested ng-repeat undefined errors in AngularJS
I'm having an issue with a very simple nested ng-repeat with AngularJS 1.2.0-rc3. I have a JSON object inside my controller representing a menu structure, like this: $scope.menus = [ { title: "menu1", action: "#", menus: [ { title: "submenu1_1", action: "stuff" }, { title: "submenu1_2", action: "moreStuff" } ] }, { title: "menu2", action: "#", menus: [ { title: "submenu2_1", action: "awesomeStuff" }, { title: "submenu2_2", action: "moreAwesomeStuff" } ] } ] My view uses this information to create a Bootstrap nav menu structure, like this: <div class="navbar"> <ul class="nav navbar-nav"> <li ng-repeat="menu in menus" ng-class="{dropdown: menu.menus}"> {{menu.title}} <ul ng-if="menu.menus" class="dropdown-menu"> <li ng-repeat="submenu in menu.menus"> {{submenu.title}} </li> </ul> </li> </ul> </div> This should work fine, and it does... 50% of the time. Sometimes, almost randomly, the menu won't show up at all and I'll get this error in the console: Error: 'undefined' is not an object (evaluating 'node.childNodes') Is there something wrong with my markup? The error appears sometimes on page load, and always when I click on a submenu (in my code the actions are replaced simply by an "#").
Handlebars.js using ../ to reference parent Context
Lets say I have the following JSON and handlebars.js template : JSON { rootPath: '/some/path/', items:[ { title: 'Hello World', href: 'hello-world' }, { title: 'About', href: 'about' }, { title: 'Latest News', href: 'latest-news' } } Template <script id="test-template" type="text/x-handlebars-template"> <ul class="nav"> {{#each items}} <li>{{title}}</li> {{/each}} </ul> </script> The template above works, until I want to filter items - lets say to have 2 lists one odd and the other even, here's a simple template for odd : <script id="test-template" type="text/x-handlebars-template"> <ul class="nav"> {{#each items}} {{#isOdd #index}} <li>{{title}}</li> {{/isOdd}} {{/each}} </ul> </script> And the registered helper : // isOdd, helper to identify Odd items Handlebars.registerHelper('isOdd', function (rawValue, options) { if (+rawValue % 2) { return options.fn(this); } else { return options.inverse(this); } }); The helpers work as expected and only the Odd items are rendered, however the reference to the parent context becomes lost, so the {{../rootPath}} directive ~~fails to render~~ renders an empty value. Is there a way to pass the Parent context through the block Helper?
Change this: <a href="{{../rootPath}}{{href}}"> to this: <a href="{{../../rootPath}}{{href}}"> Why? because the if statement is in an inner context so first you need to go up a level and that's why you have to add ../ See more details in: https://github.com/wycats/handlebars.js/issues/196