I am very new to knockout.js (just picked it up yesterday), but I was suggested it for what I'm trying to do.
My dilemma is this:
i set the following "initialData" as a json array:
var initialData = [
{
id: 0,
pcName: "Test1"
},
{
id: 1,
pcName: "Test2"
},
{
id: 2,
pcName: "Test3"
},
{
id: 3,
pcName: "Test4"
},
{
id: 4,
pcName: "Test5"
}
];
followed by the following (simple) model:
var PCModel = function (pcs) {
var self = this;
self.pcsList = ko.observableArray(ko.utils.arrayMap(pcs, function (pc) {
return { id: pc.id, pcName: pc.pcName };
}));
and apply my bindings as such:
ko.applyBindings(new PCModel(initialData));
i then try to loop through my (what should be) pcsList:
<ul class="nav nav-tabs" id="sortable" data-bind="foreach: pcsList">
<li>
<a data-bind="attr: {href: '#' + id}, text: pcName"></a>
</li>
</ul>
And yet, nothing seems to happen. I cant seem to figure out why.
Please help.
For anyone that may stumble around here, my problem was simply not calling applyBindings AFTER the document was ready. wrapping it in $("document").ready(... solved the problem.
Related
Now before you mark this question as duplicate, hear me out.
i have a json response in reactjs that goes like
organisationUnits: [
{
name: "0.Mzondo",
id: "nW4j6JDVFGn",
parent: {
id: "Ppx2evDIOFG"
}
},
{
name: "1 Chipho",
id: "eE4p4gXpR4p",
parent: {
id: "JKNTgsOVMOo"
}
}, {}, {}, ....
}]
now I have searched the net for a list-to-tree solution i've a lot of code from people but it doesn't seem to work.
Ive also tried https://github.com/yi-ge/js-tree-list and https://www.npmjs.com/package/list-to-tree and also https://www.npmjs.com/package/array-to-tree
but nothing works, i assume its because my parent id is trapped in the parenthesis. So nothing online works. If anyone has a solution to this, it'd be greatly appreciated.
Okay for all those that may get stuck in the future, here's how i solved the issue.
surprisingly I'm not very bright, so wrong career choice.... here goes
var arrayToTree = require('array-to-tree');
var array = [
{
name: "0.Mzondo",
id: "nW4j6JDVFGn",
parent: {
id: "Ppx2evDIOFG"
}
},
{
name: "1 Chipho",
id: "eE4p4gXpR4p",
parent: {
id: "JKNTgsOVMOo"
}
}, {}, {}, ....
}
array.map((item) => {
//
if(item.parent != null){
//console.log(item.parent.id)
item.parent = item.parent.id
} else {
item.parent = undefined
}
});
var tree = arrayToTree(array, {
parentProperty: 'parent',
customID: 'id'
});
console.log( tree );
this.setState({
orgUnits : tree
});
done.
Thank you so much to all those that helped. like really
Hi I was developing a multilevel tree dropdown component. But am kind of confused on how to develop the component, since the data might have so many levels and sub levels. Currently am stuck with a better approach or idea
I know how to hardcode the data and create the menu like below with pure HTML and css. How can I do with Angular with backend dynamic data? Thanks in advance.
HTML:
<ul class="top-level-menu">
<li>About</li>
<li>Services</li>
<li>
Offices
<ul class="second-level-menu">
<li>Chicago</li>
<li>Los Angeles</li>
<li>
New York
<ul class="third-level-menu">
<li>Information</li>
<li>Book a Meeting</li>
<li>Testimonials</li>
<li>Jobs</li>
</ul>
</li>
<li>Seattle</li>
</ul>
</li>
<li>Contact</li>
</ul>
Sample data:
data = [
{
id: 1,
name: 'root1',
children: [
{ id: 2, name: 'child1' },
{ id: 3, name: 'child2' }
]
},
{
id: 4,
name: 'root2',
children: [
{ id: 5, name: 'child2.1' },
{
id: 6,
name: 'child2.2',
children: [
{ id: 7, name: 'subsub' }
]
}
]
}
];
I don't think you can make your component completely dynamic in this case but you can have a array of objects in typescript which has all your options in the menu and use *ngFor to iterate.
In .ts
let menu = [{
'About': '#link'
},
{
'Services': '#link'
}, {
'offices': [{
'Chicago': '#link'
}, {
'Los Angeles': '#link'
}, {
'New York': [{
'Information': '#link'
}, {
'Book a Meeting': "#link"
}]
}]
}
]
and iterate through these nested objects in your html. If you do not have too much things to add remove frequently from your menu it's better to go with hard coding.
I don't know if you are still looking for the answer. If yes and for new viewer, then I have created a plugin for Angular which creates a Multilevel menu.
Here is a NPM Link and Github Link, the documentation is very sweet and simple.
Just in case, if you wanna see more examples, I have a demo for you and this is the Github Repo of the demo.
If something doesn't work for you, and you still wanna use this plugin, then create an issue here I'll help you out.
P.S. it works in IE11 as well.
Thanks
First of all i am very new to React JS. So that i am writing this question. I am trying this for three days.
What I have to do, make a list of category, like-
Category1
->Sub-Category1
->Sub-Category2
Categroy2
Category3
.
.
.
CategoryN
And I have this json data to make the listing
[
{
Id: 1,
Name: "Category1",
ParentId: 0,
},
{
Id: 5,
Name: "Sub-Category1",
ParentId: 1,
},
{
Id: 23,
Name: "Sub-Category2",
ParentId: 1,
},
{
Id: 50,
Name: "Category2",
ParentId: 0,
},
{
Id: 54,
Name: "Category3",
ParentId: 0,
},
];
I have tried many open source examples, but their json data format is not like mine. so that that are not useful for me. I have build something but that is not like my expected result. Here is my jsfiddle link what i have done.
https://jsfiddle.net/mrahman_cse/6wwan1fn/
Note: Every subcategory will goes under a category depend on "ParentId",If any one have "ParentId":0 then, it is actually a category, not subcategory. please see the JSON
Thanks in advance.
You can use this code jsfiddle
This example allows to add new nested categories, and do nested searching.
code with comments:
var SearchExample = React.createClass({
getInitialState: function() {
return {
searchString: ''
};
},
handleChange: function(e) {
this.setState({
searchString: e.target.value.trim().toLowerCase()
});
},
isMatch(e,searchString){
return e.Name.toLowerCase().match(searchString)
},
nestingSerch(e,searchString){
//recursive searching nesting
return this.isMatch(e,searchString) || (e.subcats.length && e.subcats.some(e=>this.nestingSerch(e,searchString)));
},
renderCat(cat){
//recursive rendering
return (
<li key={cat.Id}> {cat.Name}
{(cat.subcats && cat.subcats.length) ? <ul>{cat.subcats.map(this.renderCat)}</ul>:""}
</li>);
},
render() {
let {items} = this.props;
let {searchString} = this.state;
//filtering cattegories
if (searchString.length) {
items = items.filter(e=>this.nestingSerch(e,searchString))
console.log(items);
};
//nesting, adding to cattegories their subcatigories
items.forEach(e=>e.subcats=items.filter(el=>el.ParentId==e.Id));
//filter root categories
items=items.filter(e=>e.ParentId==0);
//filter root categories
return (
<div>
<input onChange={this.handleChange} placeholder="Type here" type="text" value={this.state.searchString}/>
<ul>{items.map(this.renderCat)}</ul>
</div>
);
}
});
Description
I have a small product order system, where a user can add order lines, and on each order line add one or more products. (I realise it's quite unusual for more than one product to be on the same order line, but that's another issue).
The products that can be selected on each line is based on a hierarchy of products. For example:
Example product display
T-Shirts
V-neck
Round-neck
String vest
JSON data
$scope.products = [
{
id: 1,
name: 'T Shirts',
children: [
{ id: 4, name: 'Round-neck', children: [] },
{ id: 5, name: 'V-neck', children: [] },
{ id: 6, name: 'String vest (exclude)', children: [] }
]
},
{
id: 2,
name: 'Jackets',
children: [
{ id: 7, name: 'Denim jacket', children: [] },
{ id: 8, name: 'Glitter jacket', children: [] }
]
},
{
id: 3,
name: 'Shoes',
children: [
{ id: 9, name: 'Oxfords', children: [] },
{ id: 10, name: 'Brogues', children: [] },
{ id: 11, name: 'Trainers (exclude)', children: []}
]
}
];
T-Shirts isn't selectable, but the 3 child products are.
What I'm trying to achieve
What I'd like to be able to do, is have a 'select all' button which automatically adds the three products to the order line.
A secondary requirement, is that when the 'select all' button is pressed, it excludes certain products based on the ID of the product. I've created an 'exclusion' array for this.
I've set up a Plunker to illustrate the shopping cart, and what I'm trying to do.
So far it can:
Add / remove order lines
Add / remove products
Add a 'check' for all products in a section, excluding any that are in the 'exclusions' array
The problem
However, although it adds the check in the input, it doesn't trigger the ng-change on the input:
<table class="striped table">
<thead>
<tr>
<td class="col-md-3"></td>
<td class="col-md-6"></td>
<td class="col-md-3"><a ng-click="addLine()" class="btn btn-success">+ Add order line</a></td>
</tr>
</thead>
<tbody>
<tr ng-repeat="line in orderHeader.lines">
<td class="col-md-3">
<ul>
<li ng-repeat="product in products" id="line_{{ line.no }}_product_{{ product.id }}">
{{ product.name }} <a ng-click="selectAll(product.id, line.no)" class="btn btn-primary">Select all</a>
<ul>
<li ng-repeat="child in product.children">
<input type="checkbox"
ng-change="sync(bool, child, line)"
ng-model="bool"
data-category="{{child.id}}"
id="check_{{ line.no }}_product_{{ child.id }}"
ng-checked="isChecked(child.id, line)">
{{ child.name }}
</li>
</ul>
</li>
</ul>
</td>
<td class="col-md-6">
<pre style="max-width: 400px">{{ line }}</pre>
</td>
<td class="col-md-3">
<a ng-click="removeLine(line)" class="btn btn-warning">Remove line</a>
</td>
</tr>
</tbody>
</table>
Javascript
$scope.selectAll = function(product_id, line){
target = document.getElementById('line_'+line+'_product_'+product_id);
checkboxes = target.getElementsByTagName('input');
for (var i = 0; i < checkboxes.length; i++) {
if (checkboxes[i].type == 'checkbox') {
category = checkboxes[i].dataset.category;
if($scope.excluded.indexOf(parseInt(category)) == -1)
{
checkboxes[i].checked = true;
// TODO: Check the checkbox, and set its bool parameter to TRUE
}
}
}
}
Update with full solution
There were a couple of issues with the above code. Firstly, I was trying to solve the problem by manipulating the DOM which is very much against what Angular tries to achieve.
So the solution was to add a 'checked' property on the products so that I can track if they are contained on the order line, and then the view is updated automatically.
One drawback of this method is that the payload would be significantly larger (unless it is filtered before being sent to the back-end API) as each order line now has data for ALL products, even if they aren't selected.
Also, one point that tripped me up was forgetting that Javascript passes references of objects / arrays, not a new copy.
The solution
Javascript
var myApp = angular.module('myApp', []);
myApp.controller('CartForm', ['$scope', function($scope) {
var inventory = [
{
id: 1,
name: 'T Shirts',
checked: false,
children: [
{ id: 4, name: 'Round-neck', checked: false, children: [] },
{ id: 5, name: 'V-neck', checked: false, children: [] },
{ id: 6, name: 'String vest (exclude)', checked: false, children: [] }
]
},
{
id: 2,
name: 'Jackets',
checked: false,
children: [
{ id: 7, name: 'Denim jacket', checked: false, children: [] },
{ id: 8, name: 'Glitter jacket', checked: false, children: [] }
]
},
{
id: 3,
name: 'Shoes',
checked: false,
children: [
{ id: 9, name: 'Oxfords', checked: false, children: [] },
{ id: 10, name: 'Brogues', checked: false, children: [] },
{ id: 11, name: 'Trainers (exclude)', checked: false, children: []}
]
}
];
$scope.debug_mode = false;
var products = angular.copy(inventory);
$scope.orderHeader = {
order_no: 1,
total: 0,
lines: [
{
no: 1,
products: products,
total: 0,
quantity: 0
}
]
};
$scope.excluded = [6, 11];
$scope.addLine = function() {
var products = angular.copy(inventory);
$scope.orderHeader.lines.push({
no: $scope.orderHeader.lines.length + 1,
products: products,
quantity: 1,
total: 0
});
$scope.loading = false;
}
$scope.removeLine = function(index) {
$scope.orderHeader.lines.splice(index, 1);
}
$scope.selectAll = function(product){
angular.forEach(product.children, function(item){
if($scope.excluded.indexOf(parseInt(item.id)) == -1) {
item.checked=true;
}
});
}
$scope.removeAll = function(product){
angular.forEach(product.children, function(item){
item.checked=false;
});
}
$scope.toggleDebugMode = function(){
$scope.debug_mode = ($scope.debug_mode ? false : true);
}
}]);
Click here to see the Plunker
You are really over complicating things first by not taking advantage of passing objects and arrays into your controller functions and also by using the DOM and not your data models to try to update states
Consider this simplification that adds a checked property to each product via ng-model
<!-- checkboxes -->
<li ng-repeat="child in product.children">
<input ng-model="child.checked" >
</li>
If it's not practical to add properties to the items themselves, you can always keep another array for the checked properties that would have matching indexes with the child arrays. Use $index in ng-repeat for that
And passing whole objects into selectAll()
<a ng-click="selectAll(product,line)">
Which allows in controller to do:
$scope.selectAll = function(product, line){
angular.forEach(product.children, function(item){
item.checked=true;
});
line.products=product.children;
}
With angular you need to always think of manipulating your data models first, and let angular manage the DOM
Strongly suggest reading : "Thinking in AngularJS" if I have a jQuery background?
DEMO
Why ng-change isn't fired when the checkbox is checked programatically?
It happens because
if($scope.excluded.indexOf(parseInt(category)) == -1)
{
checkboxes[i].checked = true;
// TODO: Check the checkbox, and set its bool parameter to TRUE
}
only affects the view (DOM). ng-change works alongside ngModel, which can't be aware that the checkbox really changed visually.
I suggest you to refer to the solution I provided at How can I get angular.js checkboxes with select/unselect all functionality and indeterminate values?, works with any model structure you have (some may call this the Angular way).
I have a list of json objects which i want to map to knockout viewmodel list using ko mapping plugin. The below is my code (just to explain my problem, no need to take this code seriously):
var itemsList = [
{ Id: 1, Name: 'A' },
{ Id: 2, Name: 'B' },
{ Id: 3, Name: 'C' }
];
var item = function(data) {
ko.mapping.fromJS(data, {}, this);
}
var listOfItems = ko.observableArray();
var listOfItems = ko.mapping.fromJS(itemsList, {
create: function(options) {
return new item(options.data);
}
});
Now my listOfItems are always empty, why ??
You could really simplify this code and it is become working well.
var itemsList = [{ Id: 1, Name: 'A' }, { Id: 2, Name: 'B' }, { Id: 3, Name: 'C' }];
var viewModel = {
listOfItems: ko.mapping.fromJS(itemsList)
}
ko.applyBindings(viewModel);
Now you have viewModel with observableArray listOfItems, and all items in this array have Id and Name properties.
You can test it with folowing code:
<ul data-bind="foreach: listOfItems">
<li>
<span data-bind="text: Id"></span>
<span data-bind="text: Name"></span>
</li>
</ul>