knockout array binding not working - javascript

monkeyStuff does what i want, it updates the span content if i write in the input field.
But why doesn't it work with the voteStuff?
See it in Action: Fiddle
<body>
<div id="monkeyStuff">
<input type="text" data-bind="value:monkey" />
<span data-bind="text:monkey"></span>
</div>
<hr>
<div id="voteStuff">
<div data-bind="text: test"></div>
<ul data-bind="foreach: voters">
<li>
<input type="text" data-bind="value:name" />
<span data-bind="text:name"></span>
</li>
</ul>
</div>
<script>
var vm = {
monkey: ko.observable()
};
vm.monkey("Quak");
ko.applyBindings(vm, document.getElementById('monkeyStuff'));
var model = {
test: 'Test address text',
voters: ko.observableArray([
{ name: 'First Voter' },
{ name: 'Second Voter' }
])
};
ko.applyBindings(model, document.getElementById('voteStuff') );
</script>
</body>
EDIT: OK it works like this:
voters: ko.observableArray([
{ name: ko.observable('First Voter') },
{ name: ko.observable('Second Voter') }
])
But is there a way to do it automatic for each property in the voters array?

You need to make the name property of the elements in your voters ko.observableArray also observable, which would thus allow you to alter these properties with the bindings you have implemented:
voters: ko.observableArray([
{ name: ko.observable('First Voter') },
{ name: ko.observable('Second Voter') }
])
Working example: http://jsfiddle.net/he2zoa3d/2/

Related

Angularjs how to make checkbox checked?

Hi I am developing web application in angularjs. I have one form. I am binding values to multi select dropdown.
<li ng-repeat="p in locations">
<input type="checkbox" ng-checked="master" ng-model="isTrue" ng-change="getIndex(p.Location,isTrue )" ng-name="location" required/>
<span>{{p.Location}}</span>
</li>
I am binding array to locations.
My array look likes
0: id: 1 Location:"ABC"
1: id: 2 Location:"DEF"
2: id: 3 Location:"IJK"
Now my requirement is to make checked some values. Suppose if i have var locations="ABC,DEF" then i want to make only those values checked. May i know if this can be done. Any help would be appreciated. Thank you.
Basically, if our input is a string with the locations that should be selected (i.e) var locations = 'ABC,DEF'; we can split this string by the , character and get an array with the locations to match:
var app = angular.module('myApp', []);
app.controller("locationsController", ["$scope",
function ($scope) {
// vars
var locations = 'ABC,DEF';
// functions
function init () {
var locals = locations.split(',');
angular.forEach($scope.locations, function (item) {
if (locations.indexOf(item.Location) > -1) {
item.checked = true;
}
});
}
// $scope
$scope.locations = [
{ id: 1, Location: "ABC" },
{ id: 1, Location: "DEF" },
{ id: 1, Location: "IJK" }
];
// init
init();
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="locationsController">
<li ng-repeat="p in locations">
<input ng-checked="p.checked" type="checkbox" ng-model="p.checked" required/>
<span>{{ p.Location }}</span>
</li>
</div>
</div>
Try this. Define for each checkbox separate model.
var app = angular.module('myApp', []);
app.controller("Controller", ["$scope",
function($scope) {
$scope.locations = [{
"id": 1,
Location: "ABC"
}, {
"id": 1,
Location: "DEF"
}, {
"id": 1,
Location: "IJK"
}]
var checked = ['ABC','DEF'];
function init() {
angular.forEach($scope.locations,function(location){
if(checked.indexOf(location.Location) != -1){
location.checked = true;
}
})
}
init();
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="Controller">
<li ng-repeat="p in locations">
<input type="checkbox" ng-model="p.checked" name="location" required/>
<span>{{p.Location}}</span>
</li>
</div>
</div>
It should work:-
var app = angular.module("myApp", []);
app.controller("myCtrl", function($scope,$filter) {
$scope.selectedValue = 'ABC,IJK';
$scope.selectedValue = $scope.selectedValue.split(',');
$scope.options = [{
id: 0,
name: 'ABC'
}, {
id: 1,
name: 'DEF'
}, {
id: 2,
name: 'IJK'
}];
$scope.selected = [];
angular.forEach($scope.selectedValue,function(val,key){
var r = $filter('filter')( $scope.options, {name: val})[0].id;
if(r != undefined){
$scope.selected[r]=true;
}else{
$scope.selected[r]=false;
}
});
});
<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<body>
<div ng-app="myApp" ng-controller="myCtrl">
<li ng-repeat="p in options">
<input type="checkbox" ng-model="selected[p.id]" ng-change="getIndex(p.Location,isTrue )" />
<span>{{p.name}}</span>
</li>
Selected : {{selected}}
</div>
Try this, Since you need different ngModel for each check box, you need to put them inside the location object itself.
HTML:
<li ng-repeat="p in locations">
<input type="checkbox" ng-checked="master" ng-model="p.isTrue" ng-change="getIndex(p.Location, p.isTrue)" ng-name="location" required/>
<span>{{p.Location}}</span>
</li>
In Javascript:
$scope.locations = [
{id: 1 Location:"ABC"},
{id: 2 Location:"DEF"},
{id: 3 Location:"GHI"}
];
var selectedLocations="ABC,DEF";
selectedLocations = locations.split(",");
angular.forEach($scope.locations, function(loc){
loc.isTrue = selectedLocations.indexOf(loc.Location) > -1;
});
Try this:
<li ng-repeat="p in locations">
<input type="checkbox" ng-checked="p.Location == 'ABC' || p.Location == 'DEF'? true : false" ng-model="p.master" ng-change="getIndex(p.Location,isTrue )" ng-name="location" required/>
<span>{{p.Location}}</span>
</li>
Add one more field checked:"true" with your locations like this
var app = angular.module('myApp', []);
app.controller("Controller", ["$scope",
function($scope) {
$scope.locations = [{id:1,Location:"ABC",checked:"false"},{id:1,Location:"DEF",checked:"true"},{id:1,Location:"IJK",checked:"true"}]
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="Controller">
<li ng-repeat="p in locations">
<input ng-checked="{{p.checked}}" type="checkbox" ng-model="p.id" name="location" required/>
<span>{{p.Location}}</span>
</li>
</div>
</div>
Add another variable to your array. and set the value true/false in it
0: id: 1 Location:"ABC" flag : true
1: id: 2 Location:"DEF" flag : false
<li ng-repeat="p in locations">
<input type="checkbox" ng-checked="p.flag" ng-model="isTrue" ng-
change="getIndex(p.Location,isTrue )" ng-name="location" required/>
<span>{{p.Location}}</span>
</li>
Use that flag to check unchek ur checkbox/

unsure why ng-click isn't working

Hey there I'm new to js Angular and can't figure out what I've done wrong, how do I put an object method inside the ng-click function:
<div class="container" ng-repeat="exercise in exercises">
<div class="row">
<div class="exercise-icon col-xs-2">
<img ng-src="{{ exercise.icon }}">
</div>
<div class="col-xs-6">
<p class="exercise-name"> {{ exercise.name }} </p>
</div>
<div class="col-xs-4 counters">
<span class="decrease">-</span><span class="count"> {{ exercise.count }} </span>
<span class="increase" ng-click="{{exercise.increase($index)}}">+</span>
</div>
</div>
This is the controller script:
app.controller('MainController', ['$scope', function($scope) {
$scope.exercises = [
{
icon: 'img/pushup.jpg',
name: 'Pushups',
count: 20
},
{
icon: 'img/squat.jpg',
name: 'Squats',
count: 15
},
{
icon: 'img/pullup.jpg',
name: 'Pullups',
count: 10
},
{
icon: 'img/row.jpg',
name: 'Rows',
count: 10
},
{
icon: 'img/lunge.jpg',
name: 'Lunges',
count: 10
},
{
icon: 'img/stepup.jpg',
name: 'Step Ups',
count: 10
},
{
icon: 'img/situp.jpg',
name: 'Sit Ups',
count: 15
}
];
$scope.increase = function($index) {
$index.count++;
};
}]);
The exercise icon, name and count are all showing however the click function is not working for some reason, is the syntax correct for inserting a object method into a ng-click? I couldn't find any applicable answers online.
The functionality I would expect is count to increase everytime + is pressed.
There are 2 points wrong here, you're calling your function wrongly:
{{exercise.increase($index)}}
should be
increase($index)
and you're treating $index as an object, it should be like so:
$scope.increase = function($index) {
$scope.exercises[$index].count++;
};
Change:
<span class="increase" ng-click="{{exercise.increase($index)}}">+</span>
to:
<span class="increase" ng-click="exercise.increase($index)">+</span>
The braces aren't needed in Angular.
You can achieve your result without calling a function and doing stuff just inside ng-click itself.
<span class="increase" ng-click="exercises.count =exercises.count+1 ">+</span>
try this one
<span class="increase" ng-click="increase($index)">+</span>
and you controller
$index = $index + 1;

Angular - can I use a single function on multiple objects within a nested ng-repeat that triggers the ng-show of just that object?

SUMMARYI have a list of brands and a list of products. I am using an ng-repeat to show the list of brands, and an ng-repeat with a filter to show the list of products within their respective brands. I want each brand and each product to have a button that shows more about that brand/product. All of these buttons should use the same function on the controller.
PROBLEMThe button that shows more about the brand also shows more about each of that brand's products, UNLESS (this is the weird part to me) I click the button of a product within that brand first, in which case it will work correctly.
CODEPlease see the Plunker here, and note that when you click on 'show type' on a brand, it also shows all the types of the products within that brand: http://plnkr.co/edit/gFnq3O3f0YYmBAB6dcwe?p=preview
HTML
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css">
<script src="script.js"></script>
</head>
<body>
<div ng-app="myApp">
<div ng-controller="MyController as vm">
<div ng-repeat="brand in brands">
<h1>
{{brand.name}}
</h1>
<button ng-click="showType(brand)">
Show Brand Type
</button>
<div ng-show="show">
{{brand.type}}
</div>
<div ng-repeat="product in products
| filter:filterProducts(brand.name)">
<h2>
{{product.name}}
</h2>
<button ng-click="showType(product)">
Show Product Type
</button>
<div ng-show="show">
{{product.type}}
</div>
</div>
</div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.3/angular.min.js"></script>
<script src="script.js"></script>
</body>
</html>
JAVASCRIPT
var app = angular.module('myApp', []);
app.controller('MyController', function($scope) {
$scope.brands = [{
name: 'Kewl',
type: 'Cereal'
}, {
name: 'Joku',
type: 'Toy'
}, {
name: 'Loko',
type: 'Couch'
}]
$scope.products = [{
name: 'Kewlio',
type: 'Sugar Cereal',
brand: 'Kewl'
}, {
name: 'Kewliano',
type: 'Healthy Cereal',
brand: 'Kewl'
}, {
name: 'Jokurino',
type: 'Rattle',
brand: 'Joku'
}, {
name: 'Lokonoko',
type: 'Recliner',
brand: 'Loko'
}, {
name: 'Lokoboko',
type: 'Love Seat',
brand: 'Loko'
}]
$scope.showType = function(item) {
this.show = !this.show;
}
$scope.filterProducts = function(brand) {
return function(value) {
if(brand) {
return value.brand === brand;
} else {
return true;
}
}
}
});
IMPORTANT NOTE: I realize I could add an attribute to the object (brand.show) and pass the object into the function, then change that attribute to true/false, but I don't want to do this because in my actual application, the button will show a form that edits the brand/product and submits the info to Firebase, and I don't want the object to have a 'show' attribute on it. I would rather not have to delete the 'show' attribute every time I want to edit the info in Firebase.
ng-repeat directive create own scope, when you do
this.show = !this.show
you create/change show property in current scope, if click brand button - for brand scope, that global for product, and when click in product button - for scope concrete product.
To avoid this, you should create this property before clicking button, for example with ng-init, like
ng-init="show=false;"
on element with `ng-repeat" directive
Sample
var app = angular.module('myApp', []);
app.controller('MyController', function($scope) {
$scope.brands = [{
name: 'Kewl',
type: 'Cereal'
}, {
name: 'Joku',
type: 'Toy'
}, {
name: 'Loko',
type: 'Couch'
}]
$scope.products = [{
name: 'Kewlio',
type: 'Sugar Cereal',
brand: 'Kewl'
}, {
name: 'Kewliano',
type: 'Healthy Cereal',
brand: 'Kewl'
}, {
name: 'Jokurino',
type: 'Rattle',
brand: 'Joku'
}, {
name: 'Lokonoko',
type: 'Recliner',
brand: 'Loko'
}, {
name: 'Lokoboko',
type: 'Love Seat',
brand: 'Loko'
}]
$scope.showType = function(item) {
this.show = !this.show;
}
$scope.filterProducts = function(brand) {
return function(value) {
if (brand) {
return value.brand === brand;
} else {
return true;
}
}
}
});
/* Styles go here */
h1 {
font-family: impact;
}
h2 {
font-family: arial;
color: blue;
margin-bottom: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.js"></script>
<div ng-app="myApp">
<div ng-controller="MyController as vm">
<div ng-repeat="brand in brands" ng-init="show=false">
<h1>
{{brand.name}}
</h1>
<button ng-click="showType(brand)">
Show Brand Type
</button>
<div ng-show="show">
{{brand.type}}
</div>
<div ng-repeat="product in products
| filter:filterProducts(brand.name)" ng-init="show=false">
<h2>
{{product.name}}
</h2>
<button ng-click="showType(product)">
Show Product Type
</button>
<div ng-show="show">
{{product.type}}
</div>
</div>
</div>
</div>
</div>
The easiest fix for this, if you don't mind putting temporary properties in your data is the following changes:
<div ng-show="product.show">
{{product.type}}
</div>
and
<div ng-show="brand.show">
{{brand.type}}
</div>
and then in your controller
$scope.showType = function(item) {
item.show = !item.show;
}
Alternatively, if you don't want to touch the object properties, you can create an $scope.shownTypes array and have your click either push the object into or remove the object from the shown array. THen you can check for the object's existence in the array and show or not show the type appropriately. Let me know if you need a sample of that.
Your show boolean attribute same for whole tree (is in same scope). Using angular directive with child scope scope:true in ng-repeat helps to isolate each show property. I have forked your plunker code:
http://plnkr.co/edit/cMSvyfeCQOnTKG8F4l55?p=preview

KnockoutJS not binding

I cant seem to get the binding to work on my KnockoutJS app.
JSFIDDLE -> http://jsfiddle.net/maylortaylor/pfqnkj17/
Here is the format of my JSON (generated by using <pre data-bind="text: ko.toJSON($root.forms,null,2)"></pre>)
[
{
"formTitle": "formTitle",
"formDescription": "formDesc",
"fieldTemplates": [
{
"fieldId": "text1",
"title": "title",
"description": "description fieldTemplate",
"isReq": true
},
{
"fieldId": "text2",
"title": "ttitle22",
"description": "description fieldTemplate 2",
"isReq": false
}
]
}
]
And here is how i am trying to call it in the page
<div id="MiddleColumn">
<input data-bind="textInput: $root.formTitle" type="text" placeholder="Title" class="span8 hideOffFocus input-full large-type">
<input data-bind="textInput: formDescription" type="text" placeholder="Description" class="hideOffFocus input-full">
</div
neither of those bindings work.
I create the forms object here
var FormModel = function (forms) {
var self = this;
self.forms = ko.observableArray(ko.utils.arrayMap(forms, function (form) {
return {
formTitle: form.formTitle, formDescription: form.formDescription,
fieldTemplates: ko.observableArray(form.fieldTemplates) };
}));
};
ko.applyBindings(new FormModel(initialData));
i hope your are expecting something like this
Working fiddle here
Now if you change something in textboxes in preview you can see automatic updates i.e mapping does make things back to ko way .
View Model :
var mapping = {
'fieldTemplates': {
create: function (options) {
return new FormModel(options.data);
}
}
}
function FormModel(forms) {
var self = this;
self.forms = ko.observableArray();
ko.mapping.fromJS(forms, mapping, self);
// other stuff
}
View :
<div id="MiddleColumn">
<input data-bind="textInput: $root.formTitle" type="text" />
<input data-bind="textInput: $root.formDescription" type="text"/><br/>
<div data-bind="foreach:$root.fieldTemplates">
<span data-bind="text:fieldId"></span>
<span data-bind="text:title"></span>
<span data-bind="text:description"></span>
<span data-bind="text:isReq"></span>
<br/>
</div>
</div>

why din't computed don't track changes in this simple knockout app?

The menu is what I want, when mouse over the left, the right should changes but doesn't.
Here is my simplified viewmodel:
var currentSelectIndex = 0;
var AppModel = {
CurrentIndex: ko.observable(currentSelectedIndex),
OnMouseOver: function (data, event) {
// change currentIndex or currentSelectedIndex here
// CurrentSubCategory didn't updated
},
CurrentSubCategory: ko.computed({
read: function() {
return AppModel.Menu[AppModel.CurrentIndex()].subcategory;
},
deferEvaluation: true
}),
Menu: [
{
subcategory: [
{ name: '1', id: 50000436 },
{ name: '2', id: 50010402 },
{ name: '3', id: 50010159 }
],
}
};
And my html:
<div class="categories" id="categories">
<div class="first-category" id="first-category">
<ul data-bind="foreach:Menu">
<li data-bind="text:name,attr:{id:id,class:className},event{ mouseover: $root.myfunction}"></li>
</ul>
</div>
<div class="sub-category" id="sub-category">
<ul data-bind="foreach:CurrentSubCategory()">
<li><a data-bind="text:name,attr:{href:getListUrl(id)}"></a></li>
</ul>
<div class="clear">
</div>
</div>
<div class="clear">
</div>
</div>
Sorry, can't post images due to less than 10 reputation.
Thanks for any help!
There were several syntax errors in your code which I imagine are a result of your making it simpler to post.
I have posted a working jsFiddle here: http://jsfiddle.net/Gy6Gv/2/
I changed Menu to be an observable array only because knockout provides the helper method .indexOf to make it easier to get the index of the menu from the mouseover. Other than that there was no problem with the computed. I imagine there is some other syntactical error in your actual code.

Categories

Resources