ng-repeat with dynamic array : view not updated - javascript

I have some checkboxes inside blocks like this :
<div ng-show="searchIn.IS" class="filterContent">
<div ng-click="toggleFilter('IS')">
<span class="{{filters.IS}}FilterIcon"></span>
Information System :
</div>
<div ng-show="filters.IS">
<md-checkbox ng-repeat="IS in ISList" ng-click="setISSearch(IS)" ng-checked="isInISSearch(IS)">
{IS}}
</md-checkbox>
</div>
</div>
<br />
<div ng-show="searchIn.area" class="filterContent">
<div ng-click="toggleFilter('area')">
<span class="{{filters.area}}FilterIcon"></span>
Area :
</div>
<div ng-show="filters.area">
<md-checkbox ng-repeat="area in filterAreaList" ng-click="setAreaSearch(area)" ng-checked="isInAreaSearch(area)" ng-show="isInCurrentAreas(area)">
{{area}}
</md-checkbox>
</div>
</div>
So the thing is that filterAreaList is changed each time I check or uncheck a checkbox from the ISList block. But the view is not updated, all the area are still displayed.
I also tried ng-if instead of ng-show.
$scope.filterAreaList = [];
$scope.getAreaFilters = function () {
$scope.searchedIS = $scope.ISList.slice();
for (var i = $scope.searchedIS.length; i >= 0; i--) {
if ($scope.searched.IS.indexOf($scope.searchedIS[i]) === -1)
$scope.searchedIS.splice(i, 1);
}
for (var i = 0; i < $scope.areaList.length; i++) {
for (var j = 0; j < $scope.searchedIS.length; j++)
if ($scope.hasArea($scope.searchedIS[j], $scope.areaList[i]))
$scope.filterAreaList.push($scope.areaList[i]);
}
};
$scope.isInCurrentAreas = function (area) {
if ($scope.filterAreaList.indexOf(area) === -1)
return false;
return true;
};
Some other topics pointed to this : http://www.bennadel.com/blog/2443-rendering-dom-elements-with-ngrepeat-in-angularjs.htm
But I don't really see how to apply this to my case.

Well, I actually missed a few things :
In order to make it work, I added : $scope.filterAreaList = []; in my getAreaFilters() function, and I call getAreaFilters() in my setAreaSearch(area)
And that's it.

Related

Get ID From Selected Checkbox on ng-change

I'm attempting to extract the ID of checkbox when it is selected, but I can't seem to find a way that fits what I'm trying to do.
First I have the HTML / Angular for the check boxes. The check boxes are generated by three tiers. First there's a service level, then the day of the week and then the service itself (which are what the check boxes are). The service level makes an accordion, the days of the week are loaded into tabs and the check boxes themselves come in as normal.
<div class="delivery-rules">
<div class="panel-group" id="accordion">
<div class="panel panel-default" ng-repeat="level in settings.serviceLevels">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#accordion" href="#{{level.LevelTmsCode}}">{{level.LevelName}}</a>
</h4>
</div>
<div id="{{level.LevelTmsCode}}" class="panel-collapse collapse in">
<div class="panel-body">
<ul class="nav nav-tabs">
<li id="{{day.Day}}-{{level.LevelTmsCode}}-tab" ng-repeat="day in settings.serviceDays">
<a id="{{day.Day}}-{{level.LevelTmsCode}}" href="#tabContent-{{day.Day}}-{{level.LevelTmsCode}}" ng-click="settings.changeTab(day, level, $event)">{{day.Day}}</a>
</li>
</ul>
<div class="tabContent" id="tabContent-{{day.Day}}-{{level.LevelTmsCode}}" ng-repeat="day in settings.serviceDays">
<h4>{{day.Day}}</h4>
<div class="time-check" ng-repeat="service in settings.services">
<input type="checkbox" value="None" ng-change="settings.showChecked(settings.rules, $event)" ng-model="settings.selected[$index]" class="time-check-input" id="{{level.LevelTmsCode}}-{{day.Day}}-{{service.TimeValidation}}" name="check"/>
<label for="{{level.LevelTmsCode}}-{{day.Day}}-{{service.TimeValidation}}" class="time-check-input"></label> <span>{{service.TimeValidation}}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
The arrays that build those check boxes, tabs and accordion are loaded with data from a standard http post request. Then once that is complete I place all the possible combinations of all three arrays into one big array and set their checked attribute to false.
// Get Service Levels to Build Delivery Rules Accordion
settings.getDeliveryServices = function() {
$http.get(resourceBase + "api/service/levels").success(function(data) {
settings.serviceLevels = data;
// Get Service Days
$http.get(resourceBase + "api/service/days").success(function(days) {
settings.serviceDays = days;
// Build the Accordion
setTimeout(() => settings.triggerClick(settings.serviceLevels), 500);
$http.get(resourceBase + "api/service/services").success(function (services) {
settings.services = services;
// Build a collection of all possible rules
for (var a = 0; a < settings.serviceLevels.length; a++) {
settings.rulesTmsCode.push(settings.serviceLevels[a].LevelTmsCode + "-");
}
for (var b = 0; b < settings.serviceDays.length; b++) {
settings.rulesDay.push(settings.serviceDays[b].Day + "-");
}
for (var c = 0; c < settings.services.length; c++) {
settings.rulesTime.push(settings.services[c].TimeValidation);
}
var allArrays = [settings.rulesTmsCode, settings.rulesDay, settings.rulesTime];
function allPossibleCases(arr) {
if (arr.length === 1) {
return arr[0];
} else {
var result = [];
var allCasesOfRest = allPossibleCases(arr.slice(1));
for (var i = 0; i < allCasesOfRest.length; i++) {
for (var j = 0; j < arr[0].length; j++) {
result.push(arr[0][j] + allCasesOfRest[i]);
}
}
return result;
}
}
var uncheckedRules = allPossibleCases(allArrays);
for (var i = 0; i < uncheckedRules.length; i++) {
settings.rules.push({
id: uncheckedRules[i],
checked: false
});
}
});
});
});
}
When each box is checked I'm trying to manipulate the combination array so that the selected combination is set to true.
// Check and Filter Rules to send
settings.showChecked = function (object, $event) {
for (var i = 0; i < settings.rules.length; i++) {
if (settings.rules.hasOwnProperty(i)) {
if (typeof settings.rules[i].id == settings.selected[i]) {
settings.showChecked(settings.rules[i], settings.selected[i]);
}
if (settings.rules[i].id === settings.selected[i]) {
settings.rules[i].checked = true;
}
}
}
console.clear();
console.log(settings.rules);
}
Currently, nothing is set to true as I can't seem to be able to get the ID from the checkbox to compare it with the string stored in the ID value of the combination array. So basically I need the ID of the checkbox that was selected and I need to pass that through to the ng-change event.
Try Some thing like this..
<input id={{emp.name}} type=checkbox value="{{emp.name}}" ng-change="settings.showChecked(settings.rules, $event)>
settings.showChecked=function(object,$event)
{
var el = event.target.id
}
the other way is you can pass id value in place event like below
<select id="hairColorComponent" ng-model="hairColor"
ng-options="option.name for option in hairColorData"
ng-change="updateUserData('hairColorComponent')">
$scope.updateUserData = function (id) {
var element = jQuery('#'+id);
};

Looping through function causes error and fails on first change

I have here the following script which is causing me some errors:
var sections = ["#general_info", "#address_records", "#employment_history", "#driver_experience", "#military_experience", "#eeo_survey", "#psp_notice", "#eva"];
for(var i = 0; i < sections.length; i++){
$(sections[i]).find('input, select').each(function(){
$(this).change(function(){
validate();
$(this).closest('.placement').find('.module-heading').removeClass('module-heading-bad');
$(this).closest('.placement').find('.glyphicon').addClass('glyphicon-ok text-success');
$(this).closest('.placement').find('.glyphicon').removeClass('glyphicon-warning-sign text-danger');
$(sections[i]).find('input, select').each(function(){
if($(this).closest('div').hasClass('has-error')){
$(this).closest('.placement').find('.module-heading').addClass('module-heading-bad');
$(this).closest('.placement').find('.glyphicon').addClass('glyphicon-warning-sign text-danger');
$(this).closest('.placement').find('.glyphicon').removeClass('glyphicon-ok text-success');
return false;
}
});
});
});
}
function validate(){
var driving_qs = ['driving_exp_qa', 'driving_exp_qb', 'driving_exp_qc', 'driving_exp_qd'];
for( var i = 0; i < driving_qs.length; i++){
if($('input[name='+driving_qs[i]+']:checked').val()){
$('input[name='+driving_qs[i]+']').closest('.form-group').removeClass('has-error');
$('input[name='+driving_qs[i]+']').closest('.form-group').addClass('has-success');
}else{
$('input[name='+driving_qs[i]+']').closest('.form-group').addClass('has-error');
$('input[name='+driving_qs[i]+']').closest('.form-group').removeClass('has-success');
}
}
var fields = [{
selector: $('.not-empty'),
validations: [ isNotEmpty]
},{
selector: $('.email'),
validations: [ isNotEmpty, isEmail]
},{
selector: $('.number'),
validations: [ isNotEmpty, isNumber]
},{
selector: $('.number-noreq'),
validations: [isNumberNotRequired]
}];
$('.form-control').closest('div').removeClass('has-error');
var i = 0, k = 0, z = 0, j = fields.length, item, selector, fn, info;
for(; i < j; i++){
item = fields[i];
for(k = 0; k < item.validations.length; k++){
fn = item.validations[k];
for( z = 0; z < item.selector.length; z++){
selector = $(item.selector[z]);
info = selector.closest('div');
if(info)
var result = fn(selector.val());
if(result){
info.removeClass("has-error");
info.addClass('has-success');
}else{
info.removeClass('has-success');
info.addClass("has-error")
}
}
}
}
}
The script works perfectly fine if I am running it without the for loop in front of it. Here is a quick step by step of what my code does (note: this is without the for loop):
Locate the section in code and find each input an select field
Assign the change event to each target input and select field
On change find closest span of class placement, and fine the first module heading and perform all the adding and removing of classes, just to refresh the heading to a success heading if no errors exist below.
Find all the inputs and selects and check for errors, if they exists return false, and add the error classes back on everything
This script will work all the way to the end of each section like it is supposed to do. However after I tried to do this with the for loop, it created a success scenario after only one input. Why is this happening, and is it even possible to have this function inside a loop like I am currently doing?
Also below I have included samples of the html mark-up
<!-- this tag serves no purpose other than being a bookmark for scripting -->
<span class='placement'>
<!-- Section 1: General Information -->
<div id='general-heading' class='row module-heading module-heading-bad general' data-toggle='#general_info'>
<div class='form-group'>
<div class='col-md-12'>
<h4 class='text-info '>General Information<div id='general-confirmation' class='glyphicon glyphicon-warning-sign pull-right text-danger'></div></h4>
</div>
</div>
</div>
<div id='general_info' class='app-section'>
<div class='form-group'>
<div class='col-xs-12'>
<div class='form-group row'>
<div class='col-sm-6 col-xs-12'>
<label class='control-label'>First Name<span class='req'> *</span></label><br />
<input type='text' class='form-control not-empty' id='first_name' value="<?=$first_name?>"/>
</div>
<div class='col-sm-6 col-xs-12'>
<label class='control-label'>Middle Name</label><br />
<input type='text' class='form-control' id='middle_name' value="<?=$middle_name?>"/>
</div>
</div>
</div>
</div>
</span>
The problem in this block of code:
for(var i = 0; i < sections.length; i++){
$(sections[i]).find('input, select').each(function(){
$(this).change(function(){
...
$(sections[i]).find('input, select').each(function(){
...
}
});
});
});
}
Is that it uses the variable i, which will have changed when the function() inside change is run.
In your case, the simplest way to fix it would be by using the forEach function instead of a for loop, and not using the index at all:
sections.forEach(function(section){
$(section).find('input, select').each(function(){
$(this).change(function(){
...
$(section).find('input, select').each(function(){
...
}
});
});
});
})
This will ensure that the i you mean is different each time.

add bootstrap rows during ng-repeat

I have a situation where I have a list of data to be displayed in individual panels, Using Bootstrap's grid system, I'd like to take advantage of a wide screen and display several panels horizontally, but on narrow screens have them stack. I'm currently laying things out on the server side with ejs like this, with columns being passed in as a query parameter, typically set to 2 or 3, so each colClass is either col-sm-6 or col-sm-4.
<% var colWidth = 12/columns; var colClass = "col-sm-" + colWidth; %>
<% for(var i=0; i<contestData.classData.length; i++) {%>
<% if ((classCount % columns) == 0) { %>
<div class="row">
<% } %>
<div class="<%= colClass %>">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title"> <%= contestData.classData[i].name %> </h3>
</div>
<div>...</div>
</div>
</div>
<% classCount++ %>
<% if ((classCount % columns) == 0) { %>
</div>
<% } %>
<% } %>
This works, but doing this level of layout on the server side offends me, I'd really rather do this with Angular but I can't figure out how to wrap the appropriate number of panels in a div with class=row while doing ng-repeat or even ng-repeat-start="classData in contestData.classData"
Thanks!
Here a simple solution with just HTML, 3 ROWS
<div class="row" >
<div class="col-md-4" ng-repeat-start="item in data">
I'M A ROW
</div>
<div class="clearfix" ng-if="($index+1)%3==0"></div>
<div ng-repeat-end=""></div>
</div>
If you start by chunking your data into smaller parts, based on the number of columns, it will be easy to use nested ng-repeats to create your layout:
$scope.getRows = function(array, columns) {
var rows = [];
//https://stackoverflow.com/questions/8495687/split-array-into-chunks
var i,j,temparray, chunk = columns;
for (i=0,j=array.length; i<j; i+=chunk) {
temparray = array.slice(i, i+chunk);
rows.push(temparray);
}
return rows;
};
$scope.rows = $scope.getRows($scope.contestData, $scope.columns);
Then your markup is simply:
<div ng-repeat="row in rows">
<div class="row">
<div ng-class="{'col-xs-4': columns == 3, 'col-xs-3': columns == 4}" ng-repeat="contest in row">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">{{contest}}</div>
</div>
</div>
</div>
</div>
</div>
Notice that ng-class is doing the work of deciding which type of class to add based on the number of columns. This example is handing 3 and 4, but you could extend it to handle others.
Here is a working demo: http://plnkr.co/edit/B3VAXlq9dkzO3hQkbkN3?p=preview
Update:
Plunker's full screen mode seems to interfere with the column width style, so I changed the link to display in preview mode.
Answering my own question here, similar to the answer from j.wittwer, I created a filter to chunk my data appropriately by row, etc.:
angular.module('myApp.filters').
filter('rowfilter', function () {
return function (data, columnCount) {
var rows = [];
var colCount = columnCount || 2;
var columns = [];
for (var i = 0; i< data.length; i++) {
columns.push(data[i]);
if (columns.length == colCount) {
rows.push(columns);
columns = [];
}
}
if (columns.length > 0) {
rows.push(columns);
}
return rows;
};
});
And then I use the filter (jade shown here):
.row(ng-repeat="row in contestData.classData | rowfilter")
.col-sm-6(ng-repeat="column in row")
Works very nicely, still wrapping my head around Angular!
I have this decision, seems to be working for 3 col
<div ng-repeat="r in data">
<div class="row" ng-if="$index%3==0">
<div class="col-md-4" ng-if="$index<data.length">
{{data[$index]}}
rrr
</div>
<div class="col-md-4" ng-if="$index+1<data.length">
{{data[$index+1]}}
rrr
</div>
<div class="col-md-4" ng-if="$index+2<data.length">
{{data[$index+2]}}
rrr
</div>
</div>
</div>
and data is
$scope.data = ['1','2','3','4','5','6','7'];
You can add something like this, first in your controller, do a function dad gets an integer "breakpoint" that is the number of columns you want to wrapped by a row, and the data you want inside each column like so:
function getRows(breakpoint,data) {
var len = data.length; var i = 0;
var rows = []; var temp = [];
for (; i < len; i++) {
if (i % breakpoint == 0 && i != 0) {
rows.push(temp);
temp = [];
}
temp.push(data[i]);
}
var len2 = rows.length * breakpoint;
if (len > len2) {
//var leftOvers = len - len2;
i = len2; temp = [];
for (; i < len; i++) {
temp.push(data[i]);
}
rows.push(temp);
}
return rows;
}
then whenever you recive the data yo simply do:
$scope.rows = getRows(3,data); // in case you want 3 cols.
then in your html:
<div class="row" ng-repeat="row in rows">
<div class="col-lg-4" ng-repeat="data in row">
{{data.whatever}}
</div>
</div>
</div>
and that`s it, it should work for u.

auto select first ng-repeat element in angularjs?

any idea on how to do this?
i basically have 1 controller set up and if you click on an element, an animation appears above, however, the first element should be active/clicked as soon as the page loads...
some code:
Animation Appears here:
<div class="animation">
<div ng-repeat="theme in themes" ng-show="isActive($index);" ng-if="isActive($index);">
<img id="id_{{ theme.animation_id }}" ng-src="{{ theme.animation_gif_url }}" class="active" />
</div>
</div>
Animation selection here:
<div class="theme-select animated fadeInUp">
<div class="theme-container" ng-repeat="theme in themes" ng-click="showAnimation($index);">
<div id="theme_{{ theme.animation_id }}" class="theme">
<img ng-src="{{ theme.icon_url }}" />
<div class="name">
{{ theme.name }}
</div>
</div>
</div>
</div>
Here's my controller:
themeFunction(function(themeData) {
name = [];
themeID = [];
animationURL = [];
var themes = $scope.themes = themeData.themes;
for (var t = 0; t < themes.length; t++) {
animationURL = themes[t].animation_url;
themeID = themes[t].animation_id;
iconURL = themes[t].icon_url;
name = themes[t].name;
animationGifUrl = themes[t].animation_gif_url;
$scope.themeID = themeID;
if ($scope.themes[t].animation_gif_url == 'NULL' || $scope.themes[t].animation_gif_url == null || $scope.themes[t].animation_gif_url == '.gif') {
// console.log(name + " are null or weird");
$scope.themes[t].animation_gif_url = iconURL;
} else {
$scope.themes[t].animation_gif_url = animationGifUrl;
}
}
$scope._Index = 0;
$scope.isActive = function(index) {
return $scope._Index === index;
};
$scope.showAnimation = function(index) {
selectedTheme = $(this).attr('theme');
console.log('Selected ' + selectedTheme.animation_id);
$scope._Index = index;
};
});
In your controller, after your list loads up, call the showAnimation on the first index
...list loads up
$scope.themes = data;
if (data.length) $scope.showAnimation(0);
Because:
[] == false
So, you can do if simply like this:
if (themes) $scope.showAnimation(0);
If themes is a empty array, not thing will happened.
If themes is a array has at least one theme, will trigger showAnimation function

$scope not filtering with other parts of controller in AngularJS

I have a controller that is pretty much an exact copy of the demo from AngularJS. I wanted to add a function to grab some items from an array within the items.
The issue I am running into is that the results are not filtering along with the main part of the controller.
Here is the original part of the controller.
function EmployerListCtrl($scope, Employer) {
$scope.employers = Employer.query();
$scope.orderProp = 'age';
$scope.getEmployerCount = function () {
return $scope.employers.length;
};
I added this function.
$scope.getSkillsList = function () {
var employerArray = this.employers;
var skillsArray = new Array();
for (var i = 0; i < employerArray.length; i++) {
if (employerArray[i].software != undefined || employerArray[i].software != null) {
for (var j = 0; j < employerArray[i].software.length; j++) {
skillsArray.push(employerArray[i].software[j]);
}
}
}
return skillsArray;
};
I also tried to use the same $scope as the main part of the controller, however it also shows just the initial results.
$scope.getSkillsList2 = function () {
var employerArray = $scope.employers;
var skillsArray = new Array();
for (var i = 0; i < employerArray.length; i++) {
if (employerArray[i].software != undefined || employerArray[i].software != null) {
for (var j = 0; j < employerArray[i].software.length; j++) {
skillsArray.push(employerArray[i].software[j]);
}
}
}
return skillsArray;
};
Here is the contents of the HTML Page. The ng-model="query" is what I am using to filter the ng-repeat.
<div class="container-fluid">
<p>Set One: {{getSkillsList()}}</p>
<p>Set Two: {{getSkillsList2()}}</p>
<div class="row-fluid">
<div class="span2">
<!--Sidebar content-->
Search: <input ng-model="query">
Sort by:
<select ng-model="orderProp">
<option value="name">Alphabetical</option>
<option value="age">Newest</option>
</select>
<p>Number of employers in list: {{getEmployerCount()}}</p>
</div>
<div class="span10">
<!--Body content-->
<ul class="employers">
<li ng-repeat="employer in employers | filter:query | orderBy:orderProp" class="thumbnail">
<img ng-src="{{employer.imageUrl}}">
{{employer.name}}
<p>{{employer.snippet}}</p>
</li>
</ul>
</div>
</div>
</div>
I think you are hoping to have the skillsArray automatically shrink or grow based on the ng-repeat filter. That won't work. In this line:
<li ng-repeat="employer in employers | filter:query | orderBy:orderProp" ...>
the results of the filter are not seen by the parent scope, but only by the ng-repeat scopes. In other words, the filter does not alter $scope.employers, so your skills functions will continue to operate on the full employers array received from the server.
One way to accomplish what you want is to define your own filter function, then chain it with the query filter one:
angular.module('myApp', []).
filter('skills', function() {
return function(filteredEmployers) {
var skillsArray = [];
... do your filtering here ...
return skillsArray;
}
});
Then in your HTML:
<li ng-repeat="skills in employers | filter:query | skills">
{{skill}}
</li>
See also Creating Angular Filters.

Categories

Resources