ng-repeat with checkboxes and capture value - angularjs

Still new to angular and js. I have a list:
<ul ng-repeat="rate in resources">
<li> <input type="checkbox" ng-model="isSelected" ng-change="appendList(rate)"> </li>
</ul>
When a user clicks on them, i'd like the value to be pushed to a list:
$scope.rateValues = {
'rates': {
'fastrates': {
'value': '',
}
}
};
But I'd like to append a key, value pair to the fastrates like this:
$scope.rateValues = {
'rates': {
'fastrates': {
'value': '100',
'value': '200',
'value': '300',
'value': '400',
}
}
};
I guess i'm stuck on my ng-change function in my controller to handle this. I know this is far from complete but at least the value changes. I need to make it check if it has been added and if so then remove it from the key, value from the object. If it's unchecked and they check it. It needs to create a new 'value': rate pair in the fastrates obj.
$scope.appendList = function(rate){
$scope.rateValues.rates.fastrates.value = rate
}
Here's a plnkr I started http://plnkr.co/edit/MhqEp7skAdejdBRpXx2n?p=info. Any scripting advice on how to accomplish this?

You can't use same key multiple times. You can use array of object.
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.resources = {value:['100','200','300', '400']}
$scope.rateValues = {
'rates': {
'fastrates': []
}
};
$scope.appendList = function(rate){
$scope.rateValues.rates.fastrates.push({ value: rate });
}
});
Don't forget to remove value when you uncheck the checkbox.

http://plnkr.co/edit/MhqEp7skAdejdBRpXx2n?p=preview removing value from array when you uncheck checkbox
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.resources = {
value: ['100', '200', '300', '400']
}
$scope.rateValues = {
'rates': {
'fastrates': {
'value': [],
}
}
};
$scope.appendList = function(rate) {
var index = $scope.rateValues.rates.fastrates.value.indexOf(rate);
if (index < 0) {
$scope.rateValues.rates.fastrates.value.push(rate);
} else {
$scope.rateValues.rates.fastrates.value.splice(index, 1);
}
}
});

Related

how to test inner controller which loads data for select control in angular-formly

I have a ui-select field
{
key: 'data_id',
type: 'ui-select',
templateOptions: {
required: true,
label: 'Select label',
options: [],
valueProp: 'id',
labelProp: 'name'
},
controller: function($scope, DataService) {
DataService.getSelectData().then(function(response) {
$scope.to.options = response.data;
});
}
}
How can I access that inner controller in my unit tests and check that data loading for the select field actually works ?
UPDATE:
An example of a test could be as such:
var initializePageController = function() {
return $controller('PageCtrl', {
'$state': $state,
'$stateParams': $stateParams
});
};
var initializeSelectController = function(selectElement) {
return $controller(selectElement.controller, {
'$scope': $scope
});
};
Then test case looks like:
it('should be able to get list of data....', function() {
$scope.to = {};
var vm = initializePageController();
$httpBackend.expectGET(/\/api\/v1\/data...../).respond([
{id: 1, name: 'Data 1'},
{id: 2, name: 'Data 2'}
]);
initializeSelectController(vm.fields[1]);
$httpBackend.flush();
expect($scope.to.options.length).to.equal(2);
});
You could do it a few ways. One option would be to test the controller that contains this configuration. So, if you have the field configuration set to $scope.fields like so:
$scope.fields = [ { /* your field config you have above */ } ];
Then in your test you could do something like:
$controller($scope.fields[0].controller, { mockScope, mockDataService });
Then do your assertions.
I recently wrote some test for a type that uses ui-select. I actually create a formly-form and then run the tests there. I use the following helpers
function compileFormlyForm(){
var html = '<formly-form model="model" fields="fields"></formly-form>';
var element = compile(html)(scope, function (clonedElement) {
sandboxEl.html(clonedElement);
});
scope.$digest();
timeout.flush();
return element;
}
function getSelectController(fieldElement){
return fieldElement.find('.ui-select-container').controller('uiSelect');
}
function getSelectMultipleController(fieldElement){
return fieldElement.find('.ui-select-container').scope().$selectMultiple;
}
function triggerEntry(selectController, inputStr) {
selectController.search = inputStr;
scope.$digest();
try {
timeout.flush();
} catch(exception){
// there is no way to flush and not throw errors if there is nothing to flush.
}
}
// accepts either an element or a select controller
function triggerShowOptions(select){
var selectController = select;
if(angular.isElement(select)){
selectController = getSelectController(select);
}
selectController.activate();
scope.$digest();
}
An example of one of the tests
it('should call typeaheadMethod when the input value changes', function(){
scope.fields = [
{
key: 'selectOneThing',
type: 'singleSelect'
},
{
key: 'selectManyThings',
type: 'multipleSelect'
}
];
scope.model = {};
var formlyForm = compileFormlyForm();
var selects = formlyForm.find('.formly-field');
var singleSelectCtrl = getSelectController(selects.eq(0));
triggerEntry(singleSelectCtrl, 'woo');
expect(selectResourceManagerMock.searchAll.calls.count()).toEqual(1);
var multiSelectCtrl = getSelectController(selects.eq(1));
triggerEntry(multiSelectCtrl, 'woo');
expect(selectResourceManagerMock.searchAll.calls.count()).toEqual(2);
});

How to manually add a datasource to Angularjs-select2 when select2 is set up as <input/> to load remote data?

I am using the Select2 as a typeahead control. The code below works very well when the user types in the search term.
However, when loading data into the page, I need to be able to manually set the value of the search box.
Ideally something like: $scope.selectedProducerId = {id:1, text:"Existing producer}
However, since no data has been retrieved the Select2 data source is empty.
So what I really need to be able to do is to add a new array of data to the datasource and then set the $scope.selectedProducerId, something like: $scope.producersLookupsSelectOptions.addNewData({id:1, text:"Existing producer}) and then
$scope.selectedProducerId = 1;
Researching this I have seen various suggestions to use initSelection(), but I can't see how to get this to work.
I have also tried to set createSearchChoice(term), but the term is not appearing in the input box.
I would be most grateful for any assistance.
Thanks
This is the html
<div class="col-sm-4">
<input type="text" ui-select2="producersLookupsSelectOptions" ng- model="selectedProducerId" class="form-control" placeholder="[Produtor]" ng-change="selectedProducerIdChanged()"/>
</div>
This is the controller
angular.module("home").controller("TestLookupsCtrl", [
"$scope", "$routeParams", "AddressBookService",
function($scope, $routeParams, AddressBookService) {
$scope.producersLookupsSelectOptions = AddressBookService.producersLookupsSelectOptions();
}
]);
This is the service:
angular.module("addressBook").service("AddressBookService", [
"$http", "$q", function($http, $q) {
var routePrefix = "/api/apiAddressBook/";
//var fetchProducers = function(queryParams) {
// return $http.get(routePrefix + "GetClientsLookup/" + queryParams.data.query).then(queryParams.success);
//};
var _getSelectLookupOptions = function(url, minimumInputLength, idField, textField) {
var _dataSource = [];
var _queryParams;
return {
allowClear: true,
minimumInputLength: minimumInputLength || 3,
ajax: {
data: function(term, page) {
return {
query: term
};
},
quietMillis: 500,
transport: function(queryParams) {
_queryParams = queryParams;
return $http.get(url + queryParams.data.query).success(queryParams.success);
},
results: function(data, page) {
var firstItem = data[0];
if (firstItem) {
if (!firstItem[idField]) {
throw "[id] " + idField + " does not exist in datasource";
}
if (!firstItem[textField]) {
throw "[text] " + textField + " field does not exist in datasource";
}
}
var arr = [];
_.each(data, function(returnedData) {
arr.push({
id: returnedData[idField],
text: returnedData[textField],
data: returnedData
});
});
_dataSource = arr;
return { results: arr };
}
},
dataSource: function() {
return _dataSource;
},
getText: function (id) {
if (_dataSource.length === 0) {
throw ("AddressBookService.getText(): Since the control was not automatically loaded the dataSource has no content");
}
return _.find(_dataSource, { id: id }).text;
}
//initSelection: function(element, callback) {
// callback($(element).data('$ngModelController').$modelValue);
//},
//createSearchChoice:function(term) {
// return term;
//},
addNewData:function(data) {
this.ajax.results(data,1);
};
};
return {
producersLookupsSelectOptions: function() {
var url = routePrefix + "GetClientsLookup/";
return _getSelectLookupOptions(url, 2, "Id", "Name");
},
}
}
]);

Single Controller for multiple html section and data from ajax request angularjs

I'm trying to show two section of my html page with same json data, i don't want to wrap both in same controller as it is positioned in different areas. I have implemented that concept successfully by using local json data in "angular service" see the demo
<div ng-app="testApp">
<div ng-controller="nameCtrl">
Add New
Remove First
<ul id="first" class="navigation">
<li ng-repeat="myname in mynames">{{myname.name}}</li>
</ul>
</div>
<div>
Lot of things in between
</div>
<ul id="second" class="popup" ng-controller="nameCtrl">
<li ng-repeat="myname in mynames">{{myname.name}}</li>
</ul>
JS
var testApp = angular.module('testApp', []);
testApp.service('nameService', function($http) {
var me = this;
me.mynames = [
{
"name": "Funny1"
},
{
"name": "Funny2"
},
{
"name": "Funny3"
},
{
"name": "Funny4"
}
];
//How to do
/*this.getNavTools = function(){
return $http.get('http://localhost/data/name.json').then(function(result) {
me.mynames = result.mynames;
return result.data;
});
};*/
this.addName = function() {
me.mynames.push({
"name": "New Name"
});
};
this.removeName = function() {
me.mynames.pop();
};
});
testApp.controller('nameCtrl', function ($scope, nameService) {
$scope.mynames = nameService.mynames;
$scope.$watch(
function(){ return nameService },
function(newVal) {
$scope.mynames = newVal.mynames;
}
)
$scope.addName = function() {
nameService.addName();
}
$scope.removeName = function() {
nameService.removeName();
}
});
jsfiddle
Next thing i want to do is to make a http request to json file and load my two section with data, and if i add or remove it should reflect in both areas.
Any pointers or exisisitng demo will be much helpful.
Thanks
The reason why only one ngRepeat is updating is because they are bound to two different arrays.
How could it happen? It's because that you have called getNavTools() twice, and in each call, you have replaced mynames with a new array! Eventually, the addName() and removeName() are working on the last assigned array of mynames, so you're seeing the problem.
I have the fix for you:
testApp.service('nameService', function($http) {
var me = this;
me.mynames = []; // me.mynames should not be replaced by new result
this.getNavTools = function(){
return $http.post('/echo/json/', { data: data }).then(function(result) {
var myname_json = JSON.parse(result.config.data.data.json);
angular.copy(myname_json, me.mynames); // update mynames, not replace it
return me.mynames;
});
};
this.addName = function() {
me.mynames.push({
"name": "New Name"
});
};
this.removeName = function() {
me.mynames.pop();
};
});
testApp.controller('nameCtrl', function ($scope, nameService) {
// $scope.mynames = nameService.mynames; // remove, not needed
nameService.getNavTools().then(function() {
$scope.mynames = nameService.mynames;
});
/* Remove, not needed
$scope.$watch(
function(){ return nameService },
function(newVal) {
$scope.mynames = newVal.mynames;
}
);
*/
$scope.addName = function() {
nameService.addName();
};
$scope.removeName = function() {
nameService.removeName();
};
});
http://jsfiddle.net/z6fEf/9/
What you can do is to put the data in a parent scope (maybe in $rootScope) it will trigger the both views ,And you don't need to $watch here..
$rootScope.mynames = nameService.mynames;
See the jsFiddle

Angularjs: $filter in controller

Having issues with getting this filter to work.
$scope.imgCollection.then(function (images) {
$scope.images = images.thisGal_images;
if ($scope.images[0].order == '0') {
console.log('orgName');
$scope.images = $filter('orderBy')($scope.images, 'orgName');
} else {
console.log('sort order');
$scope.images = $filter('orderBy')($scope.images, 'sortOrder');
console.log($scope.images);
}
});
$scope.images returns a list of images from the database. On the initial upload, the sortOrder column is populated with '0' as they can be sorted via ui:sortable. So on the initial view I base the sort order on the file name. After the initial view the DB is written and the first image is given the sortOrder of 1 and increments from there.
This could be my misunderstanding of $filter, but $scope.images = $filter('orderBy')($scope.images,'sortOrder'); is not ordering my $scope.images based on sortOrder.
Thanks
I created a demo for you and hopefully you can compare the code and figure out the issue.
I guess you might forget to inject $filter module. Please take a look a the demo.
<div ng-app="myApp" ng-controller="ctrl">
<div ng-repeat="image in images">{{image}}</div>
<button ng-click="order('0')">By orgName</button>
<button ng-click="order('1')">By sortOrder</button>
</div>
var app = angular.module('myApp', []);
function ctrl($scope, $filter) {
$scope.images = [{
orgName: 'B',
sortOrder: 111
}, {
orgName: 'A',
sortOrder: 12
}, {
orgName: 'D',
sortOrder: 13
}, {
orgName: 'C',
sortOrder: 14
}];
$scope.order = function (order) {
if (order == '0') {
$scope.images = $filter('orderBy')($scope.images, 'orgName');
} else {
$scope.images = $filter('orderBy')($scope.images, 'sortOrder');
}
}
}
Working Demo

Scope property which depends on another scope property?

I'm just starting out with Angular and could use a hand re: binding to scopes.
I'm trying to load in data which populates a property.
I then want to update other properties based off this property, is there any way to trigger this easily?
At the moment I'm just doing:
Load JSON data
Set mythings
Trigger calculation of mythings_processed
I feel like I should be able to trigger (3) with (2)?
Example
Javascript
app.controller('MyCtrl', function($scope, $http) {
$scope.loadData = function () {
$http.get('/api/orgs/gocardless/issues.json').success(function(data) {
$scope.mythings = data.things;
$scope.generateCalculatedItems() // Surely this isn't necessary?
});
};
$scope.generateCalculatedItems = function () {
mythings_processed = {}; // Generated using mythings
$scope.mythings_processed = mythings_processed;
};
};
HTML
<div ng-controller="IssuesCtrl" ng-init="loadData()">
<ul>
<li ng-repeat="thing in things">{{thing.id}}</li>
</ul>
<ul>
<li ng-repeat="processed_thing in mythings_processed">{{processed_thing.id}}</li>
</ul>
</div>
I'm sure I'm doing something horrible to Angular here so I apologise!
I did feel like I should be able to do:
$scope.mythingsProcessed = function () {
mythings_processed = {}; // Generated using mythings
return mythings_processed;
};
<ul>
<li ng-repeat="processed_thing in mythingsProcessed()">{{processed_thing.id}}</li>
</ul>
But when I did, it gave me the error: 10 $digest() iterations reached. Aborting!
Any ideas?
UPDATE: Example data/use case
I have a list of items fetched via ajax:
{
items: [
{name: 'pete', value: 1},
{name: 'john', value: 2},
{name: 'pete', value: 3},
{name: 'steve', value: 2},
{name: 'john', value: 1}
]
}
I want to display all of these, but I also want to process (sum) them into:
{
processed_items: [
{ name: 'pete', value: 4 }
{ name: 'john', value: 3 }
{ name: 'steve', value: 2 }
}
and then display these in a list as well.
I am not 100% sure what you are trying to acheive. But to trigger (3) with (2) you could use the watch function of the $scope object:
app.controller('MyCtrl', function($scope, $http) {
$scope.loadData = function () {
$http.get('/api/orgs/gocardless/issues.json').success(function(data) {
$scope.mythings = data.things;
});
};
scope.$watch('mythings', function(newValue, oldValue) {
$scope.generateCalculatedItems();
}
$scope.generateCalculatedItems = function () {
mythings_processed = {}; // Generated using mythings
$scope.mythings_processed = mythings_processed;
};
};
To bind mythings to mythings_processed you have to do the binding manually via the $watch-function in AngularJS.
With your updated example I think the focus of your question shifted from the problem with the binding to how you could process the incoming data. Is that correct?
I made a plunker with a possible solution:
http://plnkr.co/edit/BMNmE5
I would process the data as soon as the success method is called. And not do a watch in this case.
$scope.loadData = function() {
$http.get('data.json').success(function(data) {
$scope.mythings = data;
$scope.mythingsProcessed = processThings(data);
});
};
Then the function processThings makes the sum as you want them (it uses the underscore.js library):
var processThings = function(things) {
var result = _.reduce(
things,
function(result, obj) {
var storedObject = _.findWhere(result, {'name': obj.name});
if(storedObject) {
storedObject.value += obj.value;
return result;
}
else {
return _.union(result, _.clone(obj));
}
},
[]);
return result;
};

Resources