Using objects/arrays as checkbox values and sharing across controllers - angularjs

I've got a two-part form, where one page lets a user select via checkbox from several lists. Having made those selections, the user can see a summary of selections, on another page. The user can then return to the checkbox list and check/uncheck as needed.
I can get it to work so the user's selections persist, but then I can't get the output to show up properly on the main page (the summary list). Or, if I get the output to show up nicely with item.name, then I can't get the selections to persist when the user returns to the secondary page's list of checkboxes. Sharing across controllers is NOT the problem, really. The problem here is getting the checkboxes to retain their checked-state when the user returns to the checkbox list. When I output {{checkedCity}}, for instance, I can see everything's in there. I just can't get the checkboxes to reflect this.
This seems to revolve around how the data is entered in the factory: if I use value="item.name" in the checkbox input, then the name string is all that's added to the factory. But I need the id, as well, since that's what I'll be actually sending the api. So if I change to value="item", then I can get the output to look nice, and I get the id -- but then I don't get the persistence if the user revisits the page.
My instinct (which has gotten me nowhere, sadly) is the factory can't see that its contained objects are the same as what's being spit out in the checkbox ng-repeat. I've tried ng-click, ng-checked, and now I'm trying using the webStorage plugin, instead. I've also attempted to cycle through the factory and compare it to the checkbox list.
Here the plunk: http://plnkr.co/edit/akvFJrPP7YtOoNBDS0Hx?p=preview
What's the best way to tackle this? Regardless of how I store the info, the underlying issue remains that I can't seem to get the checkbox list to reconcile with what's being stored, to know that something is checked.
Many many thanks in advance for any help!

The real solution is to make a directive like ngTrueValue that supports expressions so that ngModel can register with an object or array when the checkbox is checked. Unfortunately, it currently only supports strings. It is very likely that ngTrueValue will be updated in the near future, so using ngTrueValue will be the correct approach soon.
Currently, you can make an additional object/array to act as the model for the checkboxes and use that set of data to determine what you want to display.
Basic and Generic Example:
Live Demo (click).
Markup:
<div ng-controller="myCtrl1">
<div ng-repeat="item in shared.data track by $index">
<input type="checkbox" ng-model="shared.checks[$index]"/>
<span>{{item.name}}</span>
</div>
</div>
<div ng-controller="myCtrl2">
<h2>Checked:</h2>
<div ng-repeat="item in shared.checks track by $index" ng-show="item===true">{{shared.data[$index].name}}</div>
</div>
JavaScript:
app.controller('myCtrl1', function($scope, myService) {
$scope.shared = myService;
});
app.controller('myCtrl2', function($scope, myService) {
$scope.shared = myService;
});
app.factory('myService', function() {
var myService = {
data: [
{name:'Foo'},
{name:'Bar'},
{name:'Baz'},
{name:'Qux'}
],
checks: []
};
return myService;
});
Your code adapted:
Live demo (click).
Note that I have changed checkboxFactory to checkboxService. Factories return (create) services, so calling the service itself a factory is odd. Also, I got rid of a lot of stuff that either isn't needed or doesn't seem relevant.
JavaScript:
app.controller('Ctrl', function($scope, checkboxService) {
$scope.items = ['city', 'state'];
$scope.selection = $scope.items[0];
$scope.shared = checkboxService;
});
app.controller('AllCtrl', function($scope, checkboxService) {
$scope.shared = checkboxService;
});
app.factory('checkboxService', function() {
var checkboxService = {
city: [
{"id" : 1, "name" : "Firstplace"},
{"id" : 2, "name" : "Second place"},
{"id" : 3, "name" : "Thirdplace"},
{"id" : 4, "name" : "Fourthplace"},
{"id" : 5, "name" : "Fifth place"}
],
state: [
{"id" : 6, "name" : "yellow dog"},
{"id" : 7, "name" : "bluedog"},
{"id" : 8, "name" : "cobalt dog"},
{"id" : 9, "name" : "purple dog"},
{"id" : 10, "name" : "greendog"}
],
checks: {
city: [],
state: []
}
};
return checkboxService;
});
Markup:
<div ng-controller="Ctrl">
FIRST PAGE: make choices<br/>
<select ng-model="selection" ng-options="item for item in items">
</select>
<hr/>
<div ng-switch="" on="selection">
<div ng-switch-when="city">
<input ng-model="searchText">
<div ng-repeat="item in shared.city | filter:searchText track by $index">
<div class="checkbox-box">
<label>
<input type="checkbox" ng-model="shared.checks.city[$index]"/>
<span class="labelText">{{item.name}} {{item.id}}</span>
</label>
</div>
</div>
</div>
<div ng-switch-when="state">
<input ng-model="searchText">
<div ng-repeat="item in shared.state | filter:searchText track by $index">
<div class="checkbox-box">
<label>
<input type="checkbox" ng-model="shared.checks.state[$index]"/>
<span class="labelText">{{item.name}}</span>
</label>
</div>
</div>
</div>
<div ng-switch-default></div>
</div>
</div>
<div ng-controller="AllCtrl">
<hr/>
SECOND PAGE: show choices made<br/>
<div ng-repeat="item in shared.checks.city track by $index" ng-show="item===true">
<div class="selected-box1">{{shared.city[$index].name}} {{item.id}}<i class="fa fa-times-circle fa-lg pull-right"></i></div>
</div>
<div ng-repeat="item in shared.checks.state track by $index" ng-show="item===true">
<div class="selected-box2">{{shared.state[$index].name}}<i class="fa fa-times-circle fa-lg pull-right"></i></div>
</div>
</div>

Related

Adding button functionality individually in angularjs template

I have a template. In the template, there are three boxes. Each box has add button, which will add an input field in that box. I have tried the following codes.
HTML
<div class="row">
<div ng-repeat="item in items2">
<div class="theBox">
<button class="btn btn-success" ng-click="addItem()">add</button>
<h6>{{item.key}}</h6>
<input ng-model="item.value">
</div>
</div>
</div>
AngularJs
$scope.items2 = [
{
"key" : "a",
"value" : []
},
{
"key" : "b",
"value" : []
},
{
"key" : "c",
"value" : []
}
];
$scope.addItem = function(){
console.log("clicked ")
angular.forEach($scope.items2, function(mm){
mm.value.push({})
})
}
The Problem: If I click on add, it's creating an object inside each value, maybe because I have used the forEach-loop for each value. How can I add it individually?
Also The input tag is not added as well, how can I fix that ?
Your question it's not exactly clear.
If you are going to
add an input field in that box
every time you click on the add button, then you will need another ng-repeat in your HTML.
Something like:
<div class="row">
<div ng-repeat="item in items2">
<div class="theBox">
<button class="btn btn-success" ng-click="addItem(item)">add</button>
<h6>{{item.key}}</h6>
<input ng-repeat="itemValue in item.value" ng-model="itemValue.text">
</div>
</div>
and your javascript to:
$scope.addItem = function(item){
item.value.push({text:""});
}
which will add an input field in that box.
If I understand you right you want to add <input>s to specific box.
I would write something that:
<div class="row">
<div ng-repeat="item in items2">
<div class="theBox">
<button class="btn btn-success" ng-click="addItem(item)">add</button>
<h6>{{item.key}}</h6>
<div ng-repeat="value in item.values">
<input ng-model="value.text">
</div>
</div>
</div>
</div>
and method will be:
$scope.addItem = function(selectedItem){
selectedItem.values.push({text:""});
}
Demo Plunker
Tips:
If you have list og inputs, the good practice to name it values and
not value in {
"key" : "a",
"value" : []
},
input ng-model is based on string and not object so will be better to set {text:""} and <input ng-model="value.text">

ng-repeat with chips in angular material

I'm trying to use md-chips to collect input from user with auto-complete. For ex: price,available,Y,N. where each component will be rendered in chips.There will be multiple inputs from user per line. When i submit the form i need all the chips per line entered by user. This is where i'm facing the problem.
<div ng-repeat ="rule in rules">
<md-chips ng-model="selectedHeaders">
<md-chip-template>
{{$chip}}
</md-chip-template>
</md-chips>
</div>
the above code works as my model is just selectheader and in js it's
$scope.selectedHeaders = [];
how should i use it to for rule.selectheader??. If i change my model to rule.selectheader , it throws this below error
Cannot set property 'selectedHeaders' of undefined.
Any pointers to solve this issue will be much apppreciated. If issue is not clear, please ask
Have you defined rules values?
In js, at least the value of rules must be something like this
var rules = [{
selectedHeaders: ['Apple', 'Orange']
}, {
selectedHeaders: ['Banana']
}];
you can do like below
Js code
var app = angular.module('myApp', []);
app.controller('ctrl1', function($scope) {
$scope.rules = [{
name: 'rule1',
id: 1
}, {
name: 'rule2',
id: 2
}];
$scope.data = {};
});
HTML
<div ng-app='myApp'>
<div ng-controller='ctrl1'>
<div ng-repeat="rule in rules">
<md-chips ng-model="data.selectedHeaders">
<md-chip-template>
{{$chip}}
<!-- for testing-->{{rule}}
</md-chip-template>
</md-chips>
</div>
</div>
</div>
Here is the link Jsfiddle demo

Filter data on click function in AngularJS

I have data on some bikes in my HTML page. I have to filter that data via an on click function. I have used a filter in the text box area, but I want the same functionality via an on click function.
So how can I bind the filter with the click function?
http://jsfiddle.net/3G7Kd/114/
<div ng-app='app' class="filters_ct">
<ul class="nav" ng-controller="selectFilter">
<li ng-repeat="filter in filters" ng-click="select($index)" ng-class="{sel: $index == selected}">
<span class="filters_ct_status"></span>
{{filter.name}}
<ul class="subul" ng-if=filter.lists.length>
<li ng-repeat="list in filter.lists" ng-click=" $event.stopPropagation()">
<input type="checkbox"> {{list}}
</li>
</ul>
</li>
</ul>
<input type="text" ng-model="search">
<div ng-controller="listctrl">
<div class="list" ng-repeat="list in lists | filter:{brand:search}">
{{list.brand}}
{{list.year}}
</div>
</div>
</div>
Angular
var app = angular.module('app', []);
app.controller('selectFilter', function($scope) {
$scope.filters = [
{
"name": "brand",
'lists': ['yamaha','ducati','KTM','honda']
},
{
'name': "year",
'lists': [2012,2014,2015]
}
];
$scope.selected = 0;
$scope.select= function(index) {
if ($scope.selected === index)
$scope.selected = null
else
$scope.selected = index;
};
});
app.controller('listctrl', function($scope) {
$scope.lists = [
{
"brand": "ducati",
'year': 2012
},
{
'brand': "honda",
'year': 2014
},
{
'brand': "yamaha",
'year': 2015
},
{
'brand': "KTM",
'year': 2012
}
];
});
You already knew how to use the filter when given an object within the partial. I moved one of your controllers so that you have an outer and an inner controller.
<div ng-app='app'ng-controller="MainCtrl as mainCtrl">
<div ng-controller="listCtrl">
<!-- your filter object is now accessible here -->
</div>
</div>
I added a scope variable to the outer controller $scope.activeFilters (filling this you should be able to do on your own, see the plunker for one possible solution.
This object is now changed when clicking on the checkboxes. As $scope.activeFilters is now accessible from the inner controller we can pass it to the filter as before:
<div class="list" ng-repeat="list in lists | filter:activeFilters">
{{list.brand}}
{{list.year}}
</div>
Note that there are probably nicer solutions (using the checkbox with a model among other things).
Working plunker:
http://jsfiddle.net/ywfrbgoq/

Assign value to anchor tag - angularjs

User sees a list of options (eg: 1.Apple ; 2.Banana ; 3.Mango). There is a textbox where user types in the desired option and clicks send to proceed further.
Existing Setup:
HTML:
<p ng-repeat="opt in objHistory.OptionItems track by $index">
{{ opt.SortOrder }}. {{ opt.OptionName }}
</p>
<textarea ng-model="userInput"></textarea>
Send
JS:
$scope.GetNextItem = function () {
alert($scope.userInput);
//some code
}
The above code is working good. But now I have changed the options to anchor tags, so user can click on them, instead of typing and the same flow follows.
New HTML:
<p ng-repeat="opt in objHistory.OptionItems track by $index">
<a href="javascript:;"
ng-model = "userInput"
ng-init="userInput=opt.SortOrder"
ng-click="GetNextItem()">{{ opt.SortOrder }}. {{ opt.OptionName }}
</a>
</p>
I get undefined in the alert now. Where am I going wrong? Can the same ng-model variable name be used multiple times (I'm using it for the textbox and also the anchor tags)?
ng-model cannot be used on anchor tags unless you have an input field or a custom directive. According to docs:
The ngModel directive binds an input,select, textarea (or custom form control) to a property on the scope using NgModelController, which is created and exposed by this directive.
Why are you using ngModel on anchor tags?
Sorry not enough rep to comment
You are trying to read the set value before Angular is done assigning.
HTML:
<p ng-repeat="opt in objHistory.OptionItems track by $index">
<a href="javascript:;"
ng-model = "userInput"
ng-init="init(opt)"
ng-click="GetNextItem()">{{ opt.SortOrder }}. {{ opt.OptionName }}
</a>
</p>
<textarea ng-model="userInput"></textarea>
Controller:
angular.module("app",[]).controller("MainController", function($scope) {
$scope.GetNextItem = function () {
alert($scope.userInput);
//some code
};
$scope.init = function(opt) {
$scope.userInput = opt.SortOrder; // set it in the init method
};
$scope.objHistory = {
OptionItems: [{
SortOrder : "1",
OptionName: "Hi"
}, {
SortOrder : "2",
OptionName: "Hi"
}]
}
});
Working DEMO
Update based on comment:
$scope.GetNextItem = function (opt) {
$scope.userInput = opt.SortOrder;
//some code
};
Now in your HTML:
<p ng-repeat="opt in objHistory.OptionItems track by $index">
<a href="javascript:;"
ng-click="GetNextItem(opt)">{{ opt.SortOrder }}. {{ opt.OptionName }}
</a>
</p>
Move ng-model to the parent <p>

Multiple select boxes with different selected values in angularJS

I have multiple select boxes and I'm using angular JS. Each select box needs to have a different selected value. So far everything I've seen has elements that share the same selected value. In order to achieve this a scope is used. Since I can potentially have hundreds of drop downs... actually thousands... what is the correct approach here? Create a scope for each one? Try to have one scope that mutates with each select box?Here is an example with what I have jsfiddle.Any help is much appreciated.
Thanks
function MyCntrl($scope) {
$scope.colors = [
{name:'black'},
{name:'red'},
{yellow:'yellow'}
]
$scope.isSel = $scope.colors[1];
}
You need to bind each select box to its own scope. You can do it manually, binding each one to a new object instead of the same isSel, or you can use a ng-repeat like so:
http://jsfiddle.net/zmU8R/9/
html:
<div ng-app="">
<div ng-controller="MyCntrl">
<div ng-repeat="control in controls">
<select ng-model="control.isSel" ng-options="c.name for c in colors"></select><br />
</div>
<hr />
<div ng-repeat="control in controls">
{{control.id}}: {{control.isSel}}
</div>
</div>
</div>
script:
function MyCntrl($scope) {
$scope.controls = [
{id: 1, isSel:null},
{id: 2, isSel:null},
{id: 3, isSel:null}
];
$scope.colors = [
{name:'black'},
{name:'red'},
{name:'yellow'}
];
}
Not sure I've figured out what you exactly want. As far as I understand, you need each selectbox to have different value. So, you need to bind each selectbox to a different variable.
<div ng-app="myApp">
<div ng-controller="myCtrl">
<hr/>
<div ng-repeat="n in [] | range: selectionsCount">
<select ng-model="selectedValues[$index]" ng-options="c.name for c in colors"></select>
</div>
{{ selectedValues }}
</div>
</div>
For a much clearer example, I made selectboxes count variable here.
angular.module('myApp', [])
.controller('myCtrl', function ($scope) {
$scope.colors = [
{name: 'black'},
{name: 'red'},
{name: 'yellow'}
];
$scope.selectedValues = [];
$scope.selectionsCount = 5;
})
.filter('range', function () {
return function(input, total) {
total = parseInt(total);
for (var i=0; i<total; i++)
input.push(i);
return input;
};
});
You can test it here: http://jsfiddle.net/zmU8R/7/
If I misunderstood your question, feel free to correct me.

Resources