http://plnkr.co/edit/nEfBE33AJen3mz9YvjeC?p=preview
I have a list of tag buttons, by default after all the items are loaded, I set the first 3 items to have the selected class.
Now I also need tags to gain or lose the selected class on ng-click.
How would you combine the code in the markup or controller?
.controller('PageCtrl',
['$scope',
function($scope) {
var vs = $scope;
vs.message = "1st, 2nd and 3rd item should be selected by default:";
// Gives the 1st 3 items the selected class
vs.toggleTags = { item: [0, 1, 2] };
// the ng-click to individually toggle the selected class:
vs.selectTag = function(term) {
alert(term +' in tag #'+ vs.toggleTags.item);
};
vs.tags = [
{name: 'aaa'},
{name: 'bbb'},
{name: 'ccc'},
{name: 'ddd'},
{name: 'eee'},
{name: 'fff'},
{name: 'ggg'}
];
}]);
HTML
<ul>
<li ng-repeat="(k, m) in tags"
ng-class="{'selected':toggleTags.item.indexOf(k) > -1}"
ng-click="toggleTags.item = $index; selectTag(m.name)">
<div class="tag">{{m.name}}</div>
</li>
</ul>
Basically I'd like to combine something like: {'selected' : toggle.state} into the ng-class line. Right now my code deselects everything.
Any ideas?
If u dont want to change data you get from server - you can create selection model separately:
<li ng-repeat="(k, m) in tags"
ng-class="{'selected':selectedModel[$index]}"
ng-click="selectedModel[$index] = !selectedModel[$index]">
http://plnkr.co/edit/QpoAs9XFknKug8mpNkkO?p=preview
Ofc, better to use ids not indexes, if u have any.
http://plnkr.co/edit/bqXi4GHxJjbD03ilJ4Me?p=preview
New plnkr (for toggling):
http://plnkr.co/edit/bqXi4GHxJjbD03ilJ4Me?p=preview
You can do something like this. Using an extra variable inside your tags
vs.tags = [
{name: 'aaa', active: true},
{name: 'bbb', active: true},
{name: 'ccc', active: true},
{name: 'ddd', active: false},
{name: 'eee', active: false},
{name: 'fff', active: false},
{name: 'ggg', active: false}
];
You can achieve this by updaing your controller function as :
vs.selectTag = function(term,k) {
if(vs.toggleTags.item.indexOf(k) == -1){
vs.toggleTags.item.push(k)
}
else {
vs.toggleTags.item.splice(vs.toggleTags.item.indexOf(k),1);
}
alert(term +' in tag #'+ vs.toggleTags.item);
};
And update html to:
<li ng-repeat="(k, m) in tags"
ng-class="{'selected':toggleTags.item.indexOf(k) > -1}"
ng-click="selectTag(m.name,k)">
<div class="tag">{{m.name}}</div>
</li>
Every button have an object so instead of using another array to check if the button is checked you can add it to the existing object (m object in you code).
That way you can do {'selected': m.state} which is pretty clean.
As maddog showed, you just need to add the active property to the tags object and check there if it's selected and not in a different array, there is no reason for this (at least not one you have shown us).
Related
I have the following binding in js fiddle.
<div class="container body-content">
<div>Name : <span data-bind="text: Name"></span>
</div>The select control should be below
<select multiple data-bind="selectPicker: teamID, optionsText: 'text', optionsValue : 'id', selectPickerOptions: { optionsArray: teamItems, disabledOption: IsDisabled }"></select>
<div>Selected Value(s)
<div data-bind="text: teamID"></div>
</div>
</div>
I am thinking of doing this disabledOption: IsDisabled and then adding
this.teamItems = ko.observableArray([{
text: 'Chris',
id: 1,
IsDisabled: false
}, {
text: 'Peter',
id: 2,
IsDisabled: false
}, {
text: 'John',
id: 3,
IsDisabled: false
}]);
I would like to know how to disable an option in the select.
In the knockout docs, there's an example that shows how you can disable an item using an optionsAfterRender method.
About the method you can pass to it:
It has to be in your viewmodel, not in your items
It takes in the option HTML node, and the item it's bound to
So step one is to find a place to store which items are disabled. The easiest option would be to store it inside your teamItems' objects:
{
text: 'Chris',
id: 1,
disable: ko.observable(true)
}
Now, we need to add a method that takes in an item and creates a binding. We can take this straight from the example:
this.setOptionDisable = function(option, item) {
ko.applyBindingsToNode(option, {
disable: item.disable
}, item);
}
Finally, we need to tell knockout to call this method:
<select multiple data-bind="optionsAfterRender: setOptionDisable, ...
Note that after changing a disable property in one of your items, you'll have to call teamItems.valueHasMutated manually.
Here's an updated fiddle:
http://jsfiddle.net/nq56p9fz/
I am working with angular and semantic ui. I am trying to make a selection of Y and N through a select option. Basically i just want the first item was selected when the page show up. Tried many ways but i couldn't make it works.
Please take a look at this plunker.
angular.module('myapp', [])
.controller('testctrl', function ($scope){
$scope.add = {};
$scope.Consigns = [{value: 'N',label: 'N'}, {value: 'Y',label: 'Y'}];
$scope.add.consign = $scope.Consigns[0].label;
})
.controller('testctrl1', function ($scope){
$scope.add = {};
$scope.Consigns1 = [{value: 'N',label: 'N'}, {value: 'Y',label: 'Y'}];
$scope.add.consign1 = $scope.Consigns1[0].label;
});
https://plnkr.co/edit/cHcLd14xKFxLMS4uy0BM?p=preview
Print the model value in default placeholder. Rather than sending the label value in $scope.add.consign you could send the whole object and print whats required.
Working plunker: https://plnkr.co/edit/XeuiS7p3K1OOx5nHL9c5?p=preview
javascript:
$scope.ListOrder =
[
{ Name: "price", Id: 1 },
{ Name: "exist", Id: 2 },
{ Name: "Sale", Id: 3 },
{ Name: "New", Id: 4 }
];
$scope.Order = { Name: "exist", Id: 1 };
HTML:
<select ng-model="Order" ng-options="obj.Name for obj in ListOrder"></select>
Remove
<script>
$('.ui.dropdown').dropdown();
</script>
and also change select tag to this
<select ng-model="add.consign" class="ui fluid dropdown" ng-options="x.label as x.label for x in Consigns">
<option value=""></option>
</select>
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'm new to angular. If this is a duplicate, please post a link.
Ok, so I in javascript I have a list of items, lets say this:
[{
name: 'Bob',
age: 24,
},
{
name: 'Smith',
age: 56,
},
{
name: 'Lisa',
age: 12,
}]
All the name properties are printed out in a list at the left of the page, like this:
<li data-ng-repeat="person in persons">{{tournament.name}}</li>
All this works, but here is the thing.
When I click a person in the list, I want to display more detailed information to the right of the list about that person.
If I click on Bob in the list, it should display both name and age to the right of the list.
I can't figure this out in angular. Can anyone explain how I update a part of the page with that information?
You can do that with a simple click on your li like that :
<ul data-ng-repeat="person in persons">
<li ng-click="detail($index)">{{person.name}}</li>
</ul>
The $index is the index of the ng-repeat really useful to mange with arrays !
You add a div where you want to see the person details :
<div>
{{personDetail.name}} {{personDetail.age}}
</div>
In your controller implement the detail function like that :
var app = angular.module('MyApp', []);
app.controller('MyCtrl', function($scope){
$scope.persons = [{
name: 'Bob',
age: 24,
},
{
name: 'Smith',
age: 56,
},
{
name: 'Lisa',
age: 12,
}];
$scope.detail = function(index){
$scope.personDetail = $scope.persons[index];
};
});
And voila !
working plnkr here : http://plnkr.co/edit/Wg4UD6?p=preview
<!-- left -->
<li data-ng-repeat="person in persons" ng-click="obj.selected=$index">
{{person.name}}
</li>
<!-- right -->
<div>
{{persons[obj.selected]["name"]}}
{{persons[obj.selected]["age"]}}
</div>
Controller:
$scope.obj = {
selected:-1
};
HTML
<li data-ng-repeat="person in persons" ng-click="clicked(person)">
controller
$scope.selectedNode = "";
...
$scope.clicked = function(info) {
$scope.selectedNode = info;
};
now create right side:
<div>
<pre>{{selectedNode | json}}</pre>
</div>
http://jsfiddle.net/WcJbu/
When I select a person, I want the favoriteThing selector to display their current selection.
<div ng-controller='MyController'>
<select ng-model='data.selectedPerson' ng-options='person.name for person in data.people'></select>
<span ...> likes </span>
<select ... ng-model='data.favoriteThing' ng-options='thing.name for thing in data.things'></select>
</div>
$scope.data.people = [{
name: 'Tom',
id: 1,
favorite_thing_id: 1
}, {
name: 'Jill',
id: 2,
favorite_thing_id: 3
}];
$scope.data.things = [{
name: 'Snails',
id: 1
}, {
name: 'Puppies',
id: 2
}, {
name: 'Flowers',
id: 3
}];
Do I need to set up a service and add watches, or is there a [good] way to use the favorite_thing_id directly in the select?
Change the second select to this:
<select ng-show='data.selectedPerson' ng-model='data.selectedPerson.favorite_thing_id'
ng-options='thing.id as thing.name for thing in data.things'></select>
Adding the thing.id as to the ng-options will allow you to select the data.things entries based on their id's instead of their references. Changing the ng-model to data.selectedPerson.favorite_thing_id will make angular automatically change to the correct option based on selectedPerson.favorite_thing_id.
jsfiddle: http://jsfiddle.net/bmleite/4Qf63/
http://jsfiddle.net/4Qf63/2/ does what I want - but it's pretty unsatisfying.
$scope.$watch(function() {
return $scope.data.selectedPerson;
}, function(newValue) {
if (newValue) {
$scope.data.thing = $filter('filter')($scope.data.things, {id: newValue.favorite_thing_id})[0];
}
})
I'd like to see all of that be possible from within the select statement.
Maybe I'll try to write a directive.
association = {key: matchValue}
So that I can do
<select ... ng-model='data.thing' ng-options='t.name for t in data.things' association='{id: "data.selectedPerson.favorite_thing_id"}'></select>