AngularJS ng-repeat list built dynamically,$index is always zero - javascript

I'm using AngularJS ng-repeat to create a list of cities. The list is being built dynamically client-side by the user. The user selects city from a list on the left side of the page, clicks the "Add City" button, and the city is added to a list on the right. The ng-repeat is in both lists, but when a city is added to the list on the right, the $index value for the list items does not update. The $index values are all zero.
Here's the ng-repeat list where the cities are added dynamically:
<div ng-repeat="(key, value) in filter.selectedCities | groupBy:'country'" class="text-left">
<div ng-repeat="(key2, value2) in value | groupBy:'state'" class="text-left">
<span class="label label-default">{{key}} </span> <span class="label label-danger" ng-show="key2.length">{{key2}} </span>
<ul class="nav nav-pills">
<li class="selectedpill" ng-repeat="city in value2">
<div class="pull-left">
<span class="indent8">{{ city.label | characters:24 :true}}</span>
</div>
<div class="pull-right">
<i class="fa fa-heart pointer" ng-click="addToCityFavs($index);" ng-class="{'maroonzheimer' : checkFavCity(city)}" ng-hide="savingCityFav" title="Add To Favorites"></i>
<i class="fa fa-spinner fa-spin" ng-show="savingCityFav"></i>
<i class="fa fa-times fa-lg pointer" ng-click="removeCity(city)" title="Remove City"></i>
</div>
</li>
</ul>
</div>
</div>
Here are the JavaScript functions used to add a city dynamically to the list:
$scope.addSelectedCity = function () {
if ($scope.selectedCity && $scope.selectedCity.value) {
$scope.addCity($scope.selectedCity);
$scope.cityValidationError = false;
} else { $scope.cityValidationError = true; }
$scope.tempSelectedCity = null;
$scope.selectedCity = null;
}
$scope.addCity = function (city) {
if (city && city.city && !$scope.checkCityExists(city)) {
$scope.filter.selectedCities.push(city);
}
}
$scope.addToCityFavs = function (index) {
var city = $scope.filter.selectedCities[index];
var exists = false;
for (var i = 0; i < $scope.filter.favs.CityList.length; i++) {
if ($scope.filter.favs.CityList[i].city.value == city.value) {
exists = true;
}
}
if (!exists) {
$scope.savingCityFav = true;
var fav = { id: 0, active: true, ruser_id: 0, city: city };
Do I need to add something to the HTML or JavaScript functions to get the ng-repeat list to update the $index value after a city has been added to the list?
I'll appreciate any help anyone can give.

Try to add track by $index to ng-repeat.
See this link for an example: https://egghead.io/lessons/angularjs-index-event-log

So it seems that the index is only needed to get the city from the array filter.selectedCities. But you don't need the index, because you can use the city directly:
ng-click="addToCityFavs(city);"
$scope.addToCityFavs = function (city) {
//no longer needed -> var city = $scope.filter.selectedCities[index];
var exists = false;

Related

When the cursor leaves the input box #select-or-enter-category, this code checks whether the given input exists in the dropdown or not

I have this code I use for showing a warning message:
<div class="row clearfix">
<div class="col-sm-8 col-sm-offset-2">
<div>
<div class="form-line focus">
<span class="category-status" style="color: red;">
This entered category is not found in the category list.<br>
Press button on the right to save this new category.
</span>
</div>
</div>
</div>
</div>
Then, I have another code use for typeahead dropdown:
<div class="row clearfix">
<div class="col-sm-7 col-sm-offset-2">
<div class="form-group form-float">
<div class="form-line focus">
<input type="hidden" id="cat-id" name="category_id" value="">
<input type="text" id="select-or-enter-category" name="category" data-provide="typeahead"
placeholder="Enter/Select a category" value="" required="required" class="form-control">
</div>
</div>
</div>
<div class="col-sm-1">
<button type="button" id="save-category" data-toggle="tooltip" data-placement="top" title=""
class="btn btn-warning" data-original-title="Save this input as New Category">
<i aria-hidden="true" class="fa fa-bookmark-o" style="display: inline;"></i>
</button>
</div>
</div>
And this is my javascript/jquery code:
When the user types keyword in an input box #select-or-enter-category,
this javascript code will give a dropdown as typeahead for the given keyword.
/**
* Autocomplete for category - ADD TASK MODAL
* #return {[type]} [description]
*/
$(document).ready(function(){
axios_get('/axiosCategory', function(data) {
var cat = [];
var $input = $("#select-or-enter-category");
let temp;
data.forEach(function(item) {
temp = {
id: item.id,
name: item.categoryName
}
cat.push(temp);
});
$input.typeahead({
source: cat,
autoSelect: true
});
$input.change(function() {
// console.log($input);
var current = $input.typeahead("getActive");
if (current) {
$('#cat-id').val(current.id);
}
});
});
});
When the cursor leaves the input box #select-or-enter-category, this code checks whether the given input exists in the dropdown or not. If not, the warning message will show up that will ask the user to save the input as a new category.
/**
* Display the message asking the user to save the
* new category
*
* #return void
*/
$('#select-or-enter-category').focusout(function() {
let val = $(this).val();
axios_get('/axiosCategory', function(data) {
let search = false;
data.forEach(function(item) {
if (val == item.categoryName) {
search = true;
}
});
if (search == false) {
$('.category-status').removeAttr('hidden');
} else {
$('.category-status').attr('hidden', true);
}
});
});
Then problem is that when the user clicks an item from the dropdown using the mouse, the error message shows up which is not what I want to happen.
I want the error message to show up only when the cursor actually leaves the input box #select-or-enter-category.
But if the user only uses keyboard for choosing an item from the dropdown and enter it, there is no problem.
Do you have any suggestions?
Try this one
$(document).ready(function(){
axios_get('/axiosCategory', function(data) {
dataGlobal = data;
var cat = [];
var $input = $("#select-or-enter-category");
let temp;
data.forEach(function(item) {
temp = {
id: item.id,
name: item.categoryName
}
cat.push(temp);
});
$input.typeahead({
source: cat,
autoSelect: true
});
$input.change(function() {
var current = $input.typeahead("getActive");
if (current) {
$('#cat-id').val(current.id);
}
});
$input.focusout(function() {
let val = $('#select-or-enter-category').val();
let current = $input.typeahead("getActive");
let search = false;
let str = current.name.substring(0,val.length);
if (str == val) {
val = current.name;
}
dataGlobal.forEach(function(item) {
if (val == item.categoryName) {
search = true;
}
});
if (search == false) {
$('#category-status').removeAttr('hidden');
} else {
$('#category-status').attr('hidden', 'hidden');
}
});
});
});

Filtering function: use KO struggling

Try to convert this code:
JS:
self.filter = function() {
var s = $('#searchField').val();
console.log(s.toLowerCase().replace(/\b[a-z]/g,"KC"));
s = s.toLowerCase().replace(/\b[a-z]/g, function(self) {
console.log(self.toUpperCase());
return self.toUpperCase();
});
$(".locationList > li").each(function() {
console.log(this);
$(this).text().search(s) > -1 ? $(this).show() : $(this).hide();
});
for(var i = 0; i < self.placeList().length; i++) {
console.log(self.map);
self.placeList()[i].marker.setMap(self.placeList()[i].marker.title.search(s) > -1 ? map : null);
}
};
};
HTML:
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
<input id="searchField" data-bind="event: {keyup: filter}" type="text" placeholder='search by name or city' value="">
<hr>
<ul class="locationList" data-bind="foreach: placeList">
<li>
...
to something like this:
Javascript
self.filterText = ko.observable("");
self.filteredList = ko.computed(function(){
var filter = self.filterText().toLowerCase();
return // your filter function. make sure you return an array of what you want!
}, this);
You should use the visible-binding on the li-tag in your repeater. Then it is straight forward to hide items not meeting the criteria. Something like this:
<ul data-bind="foreach:placeList">
<li data-bind="text:$data, visible: filter"></li>
</ul>

Angularjs check and compare two input field values

I have 3 sections of input fields separated with different heading(Laser Pass, The Giggsy, The set up) generated from a JSON array. Here is what it looks like:
I want to compare two fields Score and Attempts and show an error message if the value of Score is larger then Attempts. Something like this:
But some section like, The Giggsy have a different type of input fields and no need to compare/check those fields. Only where it has SCORE and ATTEMPTS should compare.
When the section is filled up Show success message like this:
What I can do to make those things in angular way. Here is what I've done so far: PLUNKER
HTML:
<div class="row" ng-repeat="all in options">
<h4> {{ all.name}} </h4>
<div class="col-sm-5ths" ng-repeat="measurement in all.measurements">
<div class="form-group no-margin form-oneline">
<label style="width: 100%">{{ measurement.name }}</label>
<input ng-model="measurement.value" type="{{ measurement.type }}" min="{{ measurement.min }}" max="{{ measurement.max }}" class="form-control display-inline" required>
<label style="width: 100%">{{ measurement.scale }}</label>
</div>
</div>
<span style="color:red;" ng-show="testDataFieldWarning(options.measurements)">
Score can't be larger then Attempts
</span>
<span style="color:Green;" >
Done!!
</span>
</div>
<button type="submit" style="margin-top:50px;" ng-disable="">Submit</button>
JS
$scope.testDataFieldWarning = function (measurements) {
var score = 0 , attempts = 0;
angular.forEach(measurements, function(measurement) {
if((measurement.name) == 'Score'){
score = measurement.value;
}
if((measurement.name) == 'Attempts'){
attempts = measurement.value;
}
});
return attempts < score;
}
$scope.testDataFieldValidate = function (measurement) {
var isInvalid = false;
angular.forEach(measurement, function(v) {
if(typeof (v.value) == 'undefined'){
isInvalid = true;
}
});
return (isInvalid);
}
Sorry for bad English and explanation.
I forked your plunker and added some additional validating functions...
function isScoreField(measurements) {
if (measurements[1].name === 'Score' && measurements[2].name ==='Attempts') {
return true;
} else {
return false;
}
}
$scope.testDataFieldInvalid = function (measurements) {
if (isScoreField(measurements) && parseInt(measurements[2].value) < parseInt(measurements[1].value)) {
return true;
} else {
return false;
}
};
$scope.testDataFieldsEntered = function (measurements) {
if (measurements[1].value && measurements[2].value) {
return true;
} else {
return false;
}
};
... that will conditionally show/hide the done/error messages.
<span style="color:red;" ng-show="testDataFieldInvalid(all.measurements)">
Score can't be larger than Attempts
</span>
<span style="color:Green;" ng-show="testDataFieldsEntered(all.measurements) && !testDataFieldInvalid(all.measurements)">
Done!!
</span>
Hope this helps!

How to register order of checkboxes in AngularJS?

I currently have a list of checkboxes in my webapp. I want to show the order in which the checkboxes have been checked. So I wrote the code below.
$scope.updateNumbers = function(id, checked, inputs) {
if (checked === true) {
$scope.count += 1;
var span = angular.element('#'+id+'-'+id);
span.html($scope.count);
} else {
if ($scope.count != 0) {
$scope.count -= 1;
}
for (index = 0; index < inputs.length; ++index) {
var input = inputs[index];
var span = angular.element('#'+test.id+'-'+test.id);
if (span.html() > $scope.count || span.html() == $scope.count) {
span.html(span.html()-1);
}
}
var span = angular.element('#'+id+'-'+id);
span.html($scope.count);
}
}
And this HTML
<div class="list-view__body__row" ng-repeat="restaurant in restaurants">
<div class="list-view__body__cell">
<input type="checkbox" id="<% restaurant.id %>" value=""
class="input-field__checkbox--input"
ng-model="restaurant.checked"
ng-change="updateNumbers(restaurant.id, restaurant.checked, restaurants)">
<label for="<% restaurant.id %>"
class="input-field__checkbox--label"
ng-bind-html="restaurant.name|html"></label>
</div>
<div class="list-view__body__cell">
<div class="order__wrapper" ng-show="restaurant.checked">
<span id="<% restaurant.id %>-<% restaurant.id %>">0</span>
</div>
</div>
</div>
In the current implementation, though, sometimes the number will go down to 0 or numbers will appear twice. It's not working correctly, so how can I improve on this code?
With angular you always want your data model to drive the view. Note that the following solution requires no dom manipulation code and angular manages the dom based on the data. It also requires very little code.
All you need for this is an array of the checked restaurants in your data model.
Then the order is determined by the index within this array and the count is the length of the array.
Controller:
// array to store selections
$scope.checkedItems=[];
$scope.updateSelections = function(rest){
if(rest.checked){
// add to array if item is checked
$scope.checkedItems.push(rest)
}else{
// remove from array if not checked
$scope.checkedItems.splice($scope.checkedItems.indexOf(rest),1)
}
}
View:
<div ng-repeat="restaurant in restaurants">
<div>
<input type="checkbox" ng-model="restaurant.checked"
ng-change="updateSelections(restaurant)">
<label ng-bind="restaurant.name"></label>
</div>
<div ng-show="restaurant.checked">
<!-- Use index to display order -->
Order: <span>{{checkedItems.indexOf(restaurant) +1}}</span>
</div>
</div>
<h3>Count of checked items: {{checkedItems.length}}</h3>
DEMO

AngularJS ng-checked not updating with model

On my page I have brand A and Brand B. By clicking on one, I display a filtered list of address types, and clicking on one of those then filters locations.
When I click on brand A, I save an object of the checked items and overwrite with brand B's list. When I click back to Brand A, the location list is filtered correctly, but the address types are not being checked, even though they exists in the array.
FYI, md-checkbox is an Angular Material checkbox.
Here is my HTML:
<ul >
<li id="brandA" ng-click="setBrand(0)" ng-class="{'brandSelected': brand == 0}">
</span>Brand A</span>
</li>
<li id="brandB" ng-click="setBrand(1)" ng-class="{'brandSelected': brand == 1}">
</span>Brand B</span>
</li>
</ul>
<div ng-repeat="item in addressTypes | filter : filterAddressTypeByBrand">
<md-checkbox class="md-primary" ng-checked="exists(item)" ng-click="toggle(item)" ng-model="item.selected">
{{ item.desc }}
</md-checkbox>
</div>
Here is an extract of my controller:
$scope.addressTypesSelected = [];
$scope.addressTypesSelectedBrandA = [];
$scope.addressTypesSelectedBrandB = [];
$scope.toggle = function (item) {
var idx = $scope.addressTypesSelected.indexOf(item);
if (idx > -1) $scope.addressTypesSelected.splice(idx, 1);
else $scope.addressTypesSelected.push(item);
};
$scope.exists = function (item) {
return $scope.addressTypesSelected.indexOf(item) > -1;
};
$scope.setBrand = function (val) {
$scope.brand = val;
$scope.addressTypesSelected = [];
if ($scope.brand == 1) {
$scope.addressTypesSelectedBrandB = [];
$scope.addressTypesSelectedBrandB = angular.copy($scope.addressTypesSelected);
$scope.addressTypesSelected = angular.copy($scope.addressTypesSelectedBrandA);
} else {
$scope.addressTypesSelectedBrandA = [];
$scope.addressTypesSelectedBrandA = angular.copy($scope.addressTypesSelected);
$scope.addressTypesSelected = angular.copy($scope.addressTypesSelectedBrandB);
}
$scope.filterDealers($scope.dealers);
}
What needs to be done to get the checkboxes checked when switching between brands?
UPDATE 1.
I've added ng-model="item.selected" to md-checkbox. This seemed to do the trick. However, if I check a box on Brand A, then click on Brand B, then click Brand A, the checkbox remains checked. If I continue and click on Brand B then click again on Brand A, the checkbox is unchecked, yet the value in the model is set to true.

Categories