I have a an array in js it's something like this
menu = [
{
name: 'Item1',
//submenuName: 'submenu-1',
},
{
name: 'Item2',
submenuName: 'submenu-2',
sub: [
{
name: 'Item2_1',
//submenuName: '',
},
{
name: 'Item2_2',
//submenuName: '',
},
{
name: 'Item2_3',
//submenuName: '',
}
]
},
{
name: 'Item3',
//submenuName: 'submenu-3',
}
]
And i need to list them in ul tag, but every level has to be closed before the other.
<ul data-menu="main">
<li data-submenu>Item1</li>
<li data-submenu='submenu-2'>Item2</li>
<li data-submenu>Item3</li>
</ul>
<ul data-menu="submenu-2">
<li data-submenu>Item2_1</li>
<li data-submenu>Item2_2</li>
<li data-submenu>Item2_3</li>
</ul>
and so on. I've mange to print them but only the first level. Cannot print the sub level.
If the menus need to be listed one after another and not nested, then maintain an array of menus to print and fill that array with submenus while printing parent menus.
Since you mentioned, that you're using jQuery, here is an example using this library.
function generateMenu (menu, container) {
var menus = [{name: 'main', entries: menu}];
while (menus.length) {
var current = menus.shift();
var ul = $("<ul />").attr('data-menu', current.name);
$.each(current.entries, function (index, menuItem) {
var li = $('<li />')
.attr('data-submenu', menuItem.submenuName || '')
.text(menuItem.name);
if ($.isArray(menuItem.sub)) {
menus.push({name: menuItem.submenuName, entries: menuItem.sub});
}
li.appendTo(ul);
});
ul.appendTo(container);
}
}
generateMenu(menu, $('body'));
JSFiddle example
Related
I am trying to add a class to an object within an ng-repeat if a property of that object (name) exists within another array.
Basically, a user can flag an object in the items ng-repeat as correct or incorrect which creates a "judgement" object within the judgements array. Onload of the page, I want to be able to compare the two arrays to add/remove a class based on if the most recent judgement of the object is incorrect or correct.
Based on my fiddle below, item1 and item3 should have a class of "incorrect". How could I accomplish this?
I tried using inArray (see http://jsfiddle.net/arunpjohny/wnnWu/) but could not figure out how to get it to work with specific properties rather than the entire array.
fiddle: http://jsfiddle.net/bTyAa/2/
function itemCtrl($scope) {
$scope.items = [{
itemname: "item1"
}, {
itemname: "item2"
}, {
itemname: "item3"
}];
$scope.judgements = [{
judgementResult: "incorrect",
date: "2016-02-01T11:03:16-0500",
item: {
itemname: "item1"
}
}, {
judgementResult: "correct",
date: "2016-01-06T11:03:16-0500",
item: {
itemname: "item1"
}
}, {
judgementResult: "incorrect",
date: "2016-01-04T11:03:16-0500",
item: {
itemname: "item3"
}
}]
}
<div ng-app>
<div ng-controller="itemCtrl">
<ul>
<li ng-repeat="item in items" class="item"> <span>{{item.itemname}}</span>
</li>
</ul>
</div>
</div>
Try to define new function to get proper class for your item
http://jsfiddle.net/bTyAa/8/
$scope.getJudgementsClass = function(itemName) {
var matched = $scope.judgements.filter(function(el) {
return el.item.itemname === itemName;
}).sort(function(a,b){
// Turn your strings into dates, and then subtract them
// to get a value that is either negative, positive, or zero.
return new Date(b.date) - new Date(a.date);
});
if (matched.length == 0)
{
return "";
}
console.log(itemName);
return matched[0].judgementResult;
}
why not doing the NG-repeat on the second array since you already have the information in the first array as item in the second array.
and just use the judgement Result as the class ?
function itemCtrl($scope) {
$scope.items = [{
itemname: "item1"
}, {
itemname: "item2"
}, {
itemname: "item3"
}];
$scope.judgements = [{
judgementResult: "incorrect",
date: "2016-02-01T11:03:16-0500",
item: {
itemname: "item1"
}
}, {
judgementResult: "correct",
date: "2016-01-06T11:03:16-0500",
item: {
itemname: "item1"
}
}, {
judgementResult: "incorrect",
date: "2016-01-04T11:03:16-0500",
item: {
itemname: "item3"
}
}]
}
<div ng-app>
<div ng-controller="itemCtrl">
<ul>
<li ng-repeat="obj in judgements" ng-class="obj.judgementResult"> <span>{{obj.item.itemname}}</span>
</li>
</ul>
</div>
</div>
You cant iterate through $scope.judgements array on each ng-repeat iteration. Something like:
$scope.isItemInCorrect = function(itemname){
console.log($scope.judgements);
for(var i = $scope.judgements.length; i--;) {
if ($scope.judgements[i].item.itemname == itemname
&& $scope.judgements[i].judgementResult == 'incorrect') {
return true;
}
}
}
<li ng-repeat="item in items" ng-class="{'incorrect' : isItemInCorrect(item.itemname)}" class="item"> <span>{{item.itemname}}</span>
http://jsfiddle.net/n0eb82j3/7/
I have two arrays, Users and Employments like so:
Users = [{id:1, name: "ryan"}, {id:2, name:"Julie"}]
Employments = [{user_id: 1, title: "manager"}, {user_id: 2, title: "Professor"}]
I'd like to display the Employments array in an ng-repeat like so:
<li ng-repeat="employment in Employments">
{{employment.user.name}}
</li>
How do I map the Users array to the Employments array?
If you want the employee name to get displayed based on id, the simplest way is just pass that id to a function and return the name, like as shown below
Working Demo
html
<div ng-app='myApp' ng-controller="ArrayController">
<li ng-repeat="employment in Employments">{{getEmployeeName(employment.user_id)}}
</li>
</div>
script
var app = angular.module('myApp', []);
app.controller('ArrayController', function ($scope) {
$scope.Users = [{
id: 1,
name: "ryan"
}, {
id: 2,
name: "Julie"
}];
$scope.Employments = [{
user_id: 1,
title: "manager"
}, {
user_id: 2,
title: "Professor"
}];
$scope.getEmployeeName = function (empId) {
for (var i = 0; i < $scope.Users.length; i++) {
if ($scope.Users[i].id === empId) {
return $scope.Users[i].name;
}
};
};
});
UPDATE 2
If you want to embed the User array in the Employments array, try the following stuff
$scope.Users = [{id: 1, name: "ryan"}, {id: 2, name: "Julie"}];
$scope.Employments = [{user_id: 1, title: "manager"},
{user_id: 2, title: "Professor"}
];
code for flattening Employments array by adding User properties
angular.forEach($scope.Users, function (user, userIndex) {
angular.forEach($scope.Employments, function (employee, employeeIndex) {
if (employee.user_id === user.id) {
employee.name = user.name;
}
});
});
Output
$scope.Employments = [ { user_id: 1, title: "manager", name: "ryan" },
{ user_id: 2, title: "Professor", name: "Julie" }
]
Working Demo
UPDATE 3
Code for making a nested employee structure like as shown below from $scope.Users and $scope.Employments
$scope.employees = [];
angular.forEach($scope.Employments, function (employee, employeeIndex) {
var employeeObject = {};
employeeObject.title = employee.title;
angular.forEach($scope.Users, function (user, userIndex) {
if (employee.user_id === user.id) {
employeeObject.user = user;
}
});
$scope.employees.push(employeeObject);
});
Output
[ { title: "manager", user: { "id": 1, "name": "ryan" } },
{ title: "Professor", user: { "id": 2, "name": "Julie" } }
]
Working Demo
If you wanted to match up the two following arrays purely with a template you could take the following arrays
Users = [{id:1, name: "ryan"}, {id:2, name:"Julie"}]
Employments = [{user_id: 1, title: "manager"}, {user_id: 2, title: "Professor"}]
And nest a repeat like:
<li ng-repeat="employment in Employments">
<div ng-repeat="user in Users" ng-if="user.id === employment.user_id" >
{{user.name}}:{{employment.title}}
</div>
</li>
Two more nice little thing to do to avoid any risk of getting those brackets showing on a slow page load is to use the ng-bind and prefix the attributes with data so its with the html spec
<li data-ng-repeat="employment in Employments">
<div data-ng-repeat="user in Users" data-ng-if="user.id === employment.user_id" >
<span data-ng-bind="user.name"></span>:<span data-ng-bind="employment.title"></span>
</div>
</li>
I know you didn't have the need for anything but the name, but figured a quick example of using the outer loop in the inner still could be helpful. Also this would be the case for ng-init if you needed to reference the the $index of the outer ng-repeat from the inner, but that might be more than you're looking for here.
Plunker
This sorts the users names into the employments array:
var sortUsers = function() {
var i = 0;
for (i; i < $scope.users.length; i++) {
console.log($scope.users[i].id)
for(var z = 0; z < $scope.employments.length; z++) {
if($scope.employments[z].user_id === $scope.users[i].id) {
$scope.employments[z].name = $scope.users[i].name;
}
}
}
}
HTML:
<ul>
<li ng-repeat="employment in employments">
{{employment.name}}
</li>
</ul>
I dealt similar problem yesterday. If you want to use js, have to loop twice.
I recommend to use the best way is to select in one query by join table if data come from single database.
You select User by one query, and Employment for another query in database. Then, twice ng-repeat to re-arrange. Here is my solution.
select users.*, employments.title from `users` inner join `employments` where users.id = employments.user_id;
Hope be be helpful.
I have a ul containing li's which contain names of different recipe ingredients for a recipe page. I'm trying to get those ingredients and store them into a JavaScript array within an object. I already know the title of the recipe so I put that right into the object property title, but I don't know how many ingredients there will be for each recipe. Here is what I have:
var recipeobj = {
title: $('h3.title').val(),
ingredients: [
ingredient,
optional
]
}
$.each($('ul.ingredients > li > h4'), function (index, ingredient) {
recipeobj.ingredients[index].ingredient = $(ingredient).html();
recipeobj.ingredients[index].optional = false;
})
If I try to do console.log(recipeobj.ingredients) I just get the error Uncaught ReferenceError: ingredient is not defined
No doubt this is simple, I just rarely need to use arrays in JavaScript so have little experience with them.
Open your console and run it
var recipeobj = {
title: $('h3.title').html(),
// ingredients is empty for now
ingredients: []
};
$.each($('ul.ingredients > li > h4'), function(index, ingredient) {
// Get the name
var name = $(ingredient).html(),
// Find out if it is 'optional'(using a class here)
optional = $(ingredient).hasClass('optional');
// Push a new ingredient into the array
recipeobj.ingredients.push({ name: name, optional: optional });
});
console.log(recipeobj);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h3 class="title">Pork and beans</h3>
<ul class="ingredients">
<li>
<h4>Pork</h4>
</li>
<li>
<h4>Beans</h4>
</li>
<li>
<h4 class="optional">Salt*</h4>
</li>
</ul>
This should output:
{
"title": "Pork and beans",
"ingredients": [
{ name : "Pork", optional : false },
{ name : "Beans", optional : false },
{ name : "Salt*", optional : true}
]
}
var rObj = {
title: $('h3.title').val(),
ingredients: [
'source cream',
'cheese',
'chopped meat'
],
optional: true
};
accessing
var rItem = rObj.ingredients[1];
or you want
var rObj = {
title: $('h3.title').val(),
ingredients: {
ingredient_list: ['one','two','three'],
optional: true
}
};
accessing
var rItem = rObj.ingredients.ingredient_list[1];
The structure you are attempting to use looks like the structure should be like
var rObj = {
title: $('h3.title').val(),
things: [{
ingredient: 'source cream',
optional: true
},
{
ingredient: 'cheese',
optional: false
}]
};
accessing
var ingred = rObj.things[1].ingredient;
var rObj = {
title: $('h3.title').val(),
ingredients : []
};
you can add ingredients:
$.each($('ul.ingredients > li > h4'), function (index, ingredient) {
rObj.ingredients.push({ingredient: $(ingredient).html(), optional :false})
})
I have a nested model which is used to construct the tree view.
Here, I have two action handlers which is used to delete the folders based on the passed "id" param.
So when I pass the first level folder "id", I can remove the elements from the model using filterBy().
But when I pass the second/third level folder "id", and use filterBy to filter through the model using "id", it returns an empty value.
Here, how can I can filter through the sub level model using id?
In Template:
<script type="text/x-handlebars" data-template-name="index">
<button {{action deleteFolder_1 '1'}}>Delete Folder_1</button>
<br><br>
<button {{action deleteFolder_11 '11'}}>Delete Folder_11</button>
<ul>
{{#each model}}
{{partial "tree"}}
{{/each}}
</ul>
</script>
<script type="text/x-handlebars" data-template-name="_tree">
<li>{{foldername}}</li>
<ul>
{{#each children}}
{{partial "tree"}}
{{/each}}
</ul>
</script>
In app.js
App.IndexRoute = Ember.Route.extend({
model: function() {
return [
{
id: '1',
foldername: 'Folder 1',
children:[{
id: '11',
foldername: 'Sub Folder 11',
children: [{
id: '111',
foldername: 'Sub Folder 111',
children: []
}]
}]
},
{
id: '2',
foldername: 'Folder 2',
children: []
},
{
id: '3',
foldername: 'Folder 3',
children:[]
}];
},
actions: {
deleteFolder_1: function (id) {
var obj = this.controller.content.filterBy('id',id);
this.controller.content.removeObjects(obj);
},
deleteFolder_11: function (id) {
var obj = this.controller.content.filterBy('id',id);
this.controller.content.removeObjects(obj);
// How can I delete the subfolder here? How can I find the sub id? Is there any way to find id recursively.
}
}
});
JSBIN DEMO: LINK
You will need to recursively traverse the tree. Here is a function that will delete the entry based on the id you pass. This function will traverse the whole tree to find a match.
deleteFolderById: function (id) {
function recursiveFilter(item, index, array) {
item = Em.Object.create(item);
if (item.id === id) {
return null;
} else {
item.set('children', item.children.map(recursiveFilter).compact());
return item;
}
}
var filtered = this.get('controller.content').map(recursiveFilter).compact();
this.set('controller.content', filtered);
}
Here is the working demo.
I would like to select a specific location by Continent / Country / State / etc.
I get a JSON and I managed to display/select the Continent, but I cannot get the list of Countries to populate.
I am rather new to knockout and JS, so I am probably doing something wrong (I managed to do this based on the live examples from knockout, but those were using select, and I cannot seem to get it to work on ul)
The JSON (composed by hand, so might have errors)
var Continent = [
{ Name: "Europe", Countries: [{ Name: "England", ID: 1 }, { Name: "Wales", ID: 2 }] },
{ Name: "America", Countries: [{ Name: "US", ID: 3 }, { Name: "Canada", ID: 4 }] },
{ Name: "Asia", Countries: [{ Name: "India", ID: 5 }, { Name: "China", ID: 6 }] }
];
The javascript:
var locationVM = function () {
var self = this;
self.selectedContinent = ko.observable("none");
self.selectedCountry = ko.observable();
self.selectedContinent.subscribe(function () {
self.selectedCountry(undefined);
});
self.onClickContinent = function (data) {
self.selectedContinent(data.Name);
};
};
ko.applyBindings(new locationVM());
The HTML
<div>
<ul data-bind="foreach: Continent">
<li data-bind="text: Name, click: $parent.onClickContinent" />
</ul>
</div>Selected Continent : <span data-bind="text: selectedContinent">text</span>
<div data-bind="with: selectedContinent">
<ul data-bind="foreach: Countries">
<li data-bind="text: Name" />
</ul>
</div>
and here is the fiddle http://jsfiddle.net/norbert/WB46V/
Also, if there is anything I missed, or is not needed, please point it out, or provide link for further studies ;)
You're trying to set the continent value to a string and then later trying to iterate thru the string. What you need to do is set the continent to the data.
Something like this:
self.selectedContinent(data);
Working fiddle here.
http://jsfiddle.net/WB46V/6/
Just to add, I haven't used knockout before, so my solution in the fiddle might not be the best one.