I am using Angularjs with select2. I have a multiselect box that gets populated based off the values selected in another dropdown. I use an click event on a button for fire a method that adds a list of ids to the ngModel object assigned to the multiselect box. I can see the values getting added properly after I trigger a change event on that form element because even after reading articles and changing the ngModel value to be a object and not an array, the values still aren't binding when I add them programmatically. After I trigger the change event on the form element, I am able to see some of the values but not all of them. Some of the values were removed. Below is my code.
addRecord.vm
<div class="col-sm-4">
<select ui-select2="{ allowClear: false, minimumResultsForSearch: 10, theme: bootstrap }"
name="recordTemplate" ng-model="addRecordCtrl.selectedTemplate">
<option class="selectPlaceholder" value="" disabled selected hidden>- Chose Record Template -</option>
<option ng-repeat="template in addRecordCtrl.recordTemplates" value="{{record.name}}">{{record.name}}</option>
</select>
</div>
<div class="col-sm-9">
<button id="addButton" class="btn btn-primary btn-lg" ng-click="addRecordCtrl.addRecords()" ng-disabled="addRecordCtrl.isLoading
|| addRecordCtrl.isEdit">Add</button>
</div>
<div class="col-lg-9 col-xs-9" id="recordContainer" name="recordContainer">
<select ui-select2="{ allowClear: false, minimumResultsForSearch: Infinity}"
name="records" id="records" class="medium"
multiple ng-model="addRecordCtrl.records.id"
ng-required="true" ng-cloak>
<option ng-repeat="record in addRecordCtrl.availableRecords | notInArray:addRecordCtrl.selectedRecordIds.ids:'recordId'"
value="{{record.recordId}}">{{record.name}}</option>
</select>
<input type="hidden" ng-model="addRecordCtrl.selectedRecordName" value="">
</div>
app.js
recordApp.controller('RecordController', ['$http', '$window', '$modal', '$log','$filter', '$timeout', function ($http, $window, $modal, $log, $filter, $timeout) {
var self = this;
self.availableRecords = [{
recordId: "1",
name: "Love and Happiness"
},{
recordId: "2",
name: "Feel the Burn"
},{
recordId: "3",
name: "Juicy Baby"
}];
self.recordTemplates = null;
self.selectedRecordIds = {};
self.addRecords = function () {
//add record ids from the selected records.
self.recordTemplates.recordId.forEach(function (id) {
self.selectedRecordIds.ids.push(id.recordId);
});
self.recordAdded = true;
};
}]);
//filter to filter out ids that have already been selected.
recordsApp.filter('notInArray', ['$filter', function($filter){
return function(list, arrayFilter, element){
if(arrayFilter){
return $filter("filter")(list, function(listItem){
for (var i = 0; i < arrayFilter.length; i++) {
if (arrayFilter[i][element] == listItem[element])
return false;
}
return true;
});
}
};
}]);
The reason some of my values were getting deleted from the selectedRecordIds array after adding them programmatically was because they are not in the availableRecords array. Once I made sure those values where in the availableRecords array all was well.
Related
I didn't found some code that can help me among all answers about the subject.
My level in AngularJS is 0 so I don't understand the code too ^^
It's not my code : I have to debug/correct it :
I have a ng-repeat and a button to add a div as many times I want.
The initial div selected by default the first "Act" and the additionnal div has the first choice
<option value="">Select</option> selected and that's what I want.
<div ng-repeat="item in vm.ListActs track by $index">
<fieldset>
<legend>Act {{$index + 1}}<i class="icon-close" ng-click="vm.DeleteAct($index)" ng-hide="$index === 0"></i></legend>
<br />
<div class="field selectField">
<label for="selectgroupAct{{$index}}">
TO <span class="obligatoire">*</span>
</label>
<select id="selectgroupAct{{$index}}"
name="selectgroupAct{{$index}}"
ng-options="act.Code as acte.Code for acte in vm.ListActsTO.Acts"
ng-disabled="vm.ListActsTO.Acts.length === 1"
ng-model="item.Code"
ng-change="vm.ActIndexChange($index, item.Code)"
required >
<option value="">Select</option>
</select>
</div>
</fieldset>
</div>
Do you know why the initial select doesn't want to have the <option value="">Select</option> as the selected option ?
In the js I have this code:
(function () {
'use strict';
angular
.module('SpaApp')
.controller('myController', myController);
myController.$inject = ['firstService', 'secondService', '$localStorage', '$location'];
function myController(firstService, secondService, $localStorage, $location) {
var $ctrl = this;
angular.extend($ctrl, {
...methods declared
});
init();
return $ctrl;
function init() {
angular.extend($ctrl, {
ListActsTO: {},
ListActs : []
});
getListActsTO();
$ctrl.ListActs [0] = {
Code: ""
};
}
...
I tried to add property selected in my option <option value="">Select</option> but it didn't work. I tried with https://docs.angularjs.org/api/ng/directive/select but I'm not good enough and after applying one, the other functionnalities I have didn't work anymore.
Thank you for your help
The ng-model is the key here. It says 'item.Code is the variable being tracked in this form element'.
<select id="selectgroupAct{{$index}}"
...
ng-model="item.Code"
...
required >
That said, in your controller, you can simply initialize this variable to match the value, in this case an empty string.
function myController(firstService, secondService, $localStorage, $location) {
var $ctrl = this;
$ctrl.item = {Code: ""}
.....
(assuming you don't have other empty string values in your vm.ListActsTO.Acts array)
I am using ng-options to display drop-down with options. Suppose I have three options in it for example option1, option2, option3.By default option1 is selected, now if a user selects option2, then $pristine becomes False and again if he selects option1 then from angularjs's prospective $pristine should be false but according to user he has not changed the option.So I was looking for a way to detect this change
Here is the Js fiddle demo
HTML
<div ng-app="myApp">
<div ng-controller="ctrl">
<form name="myForm">
{{myForm.$pristine}}
<select ng-model='list' ng-options='item.name for item in data'>
</select>
</form>
</div>
</div>
JS code
var app = angular.module('myApp', []);
app.controller('ctrl', function($scope) {
$scope.data = [{
id: 1,
name: 'test'
}, {
id: 2,
name: 'test1'
}];
$scope.list = $scope.data[0];
$scope.$watch('list', function(o, n) {
if ($scope.list == $scope.data[0])
$scope.myForm.$pristine = true;
})
});
You have add watch on your list model then this can be achieved
That is exactly what ng-change is for.
Usage would be like this (added $index to show you another option):
HTML
<div ng-app="myApp">
<div ng-controller="listController">
<form name="myForm">
<select ng-model="currOption"
ng-options='item.name for item in data'
ng-change="optionChanged(currOption, $index)">
</select>
</form>
</div>
</div>
Controller
angular.module('myApp')
.controller('listController', function($scope) {
$scope.data = [{
id: 1,
name: 'option1'
}, {
id: 2,
name: 'option2'
}, {
id: 3,
name: 'option3'
}];
$scope.optionChanged(option, index) {
// option = the selection the user just made.
// index = the index of the selection in your data. I.E. $scope.data[index]
};
});
Why is $scope.Templt_Kind_ID not changing after the bootstrap popup is closed?
Note: I do not close the popup until I retrieve the dropdown value from the popup.
I call a bootstrap popup with edit controls.
After a user changes a dropdown value, it calls
the $scope.onSelectChangeTemplate_kind = function ()
In the function below,
var ddlID = $scope.selectedCountry; contains the correct value.
$scope.Templt_Kind_ID = ddlID; // << $scope.Templt_Kind_ID = 34 as it should be
Upon closing the popup, I expected $scope.Templt_Kind_ID = 34
but it contains -1 which is what it was first initialized to.
Why? $scope.Templt_Kind_ID should = 34
JavaScript
app.controller('APIController', function ($scope, $window, $element, $log, $http, APIService) {
$scope.Templt_Kind_ID = -1; // << Initial value
// Bootstrap popup. After dropdown in bootstrap is changed, this is called.
// I tried a number of things including $scope.onSelectChangeTemplate_kind = function ($scope)
$scope.onSelectChangeTemplate_kind = function () {
var ddlID = $scope.selectedCountry; // << contains correct value
$scope.Templt_Kind_ID = ddlID; // << $scope.Templt_Kind_ID = 34 as it should be
}
// Bootstrap popup is closed.
// Why is $scope.Templt_Kind_ID=-1 although it shuold be 34 ?????
// Why is $scope.Templt_Kind_ID=-1 although it shuold be 34 ?????
$scope.hide = function () {
console.log('model one hidden Templt_Kind_ID=' +
$scope.Templt_Kind_ID); // <<<<<<
// << $scope.Templt_Kind_ID is still -1 although it shuold be 34
$scope.showModal1 = false;
}
})
<html>
<modal-body>
<div ng-controller="APIController">
<select id="ddlSelectedCountry" ng-model="selectedCountry" ng-change="onSelectChangeTemplate_kind()">
#*3-SP Insert*#
<option value="">Select Account</option>
<option
ng-model="selectedCountry"
ng-repeat="item in list_Template_kind" value="{{item.Templt_Kind_ID}}"
>
{{item.Templt_Kind_ID}}-{{item.Templt_Kind_Name}}
</option>
</select>
</div>
</modal-body>
</html>
As I recommended in the comments, you can use the native angular module.
Here you open the modal and select the template, when you click OK the modal return the selected template back to the controller:
var app = angular.module('app', ['ui.bootstrap']);
app.controller('ctrl', ['$scope', '$uibModal', '$log', function($scope, $uibModal, $log) {
// function to open the Modal from the view, the function accept "selection" argument that will be sent to the model's controller
$scope.openModal = function (selection) {
var modalInstance = $uibModal.open({
templateUrl: 'modal.html',
resolve: {
// Pass a pre selection template to the Model's controller (pass null if you don't want to pre select)
selection: function() { return selection; }
},
controller: function($scope, $uibModalInstance, selection) {
// save the preselected template and display it on the model's select box
$scope.selectedTemplate = selection;
// Mock of the actual list of templates
$scope.list_Template_kind = [
{ Templt_Kind_ID: 1, Templt_Kind_Name: 'Template 1' },
{ Templt_Kind_ID: 2, Templt_Kind_Name: 'Template 2' },
{ Templt_Kind_ID: 3, Templt_Kind_Name: 'Template 3' },
{ Templt_Kind_ID: 4, Templt_Kind_Name: 'Template 4' }
];
// OK button was clicked
$scope.ok = function () {
// Send the selected template back to the main controller
$uibModalInstance.close($scope.selectedTemplate);
};
// CANCEL button was clicked
$scope.cancel = function () {
$uibModalInstance.dismiss('cancel');
};
}
});
// The "$uibModal.open()" returns a promise that resolves when called "$uibModalInstance.close()" from the model's controller
// or rejected when you call "$uibModalInstance.dismiss()".
// You can pass any value to those promise functions from the Model controller and use it in the resolve/reject callbacks
modalInstance.result.then(function (selectedItem) {
// Get the selected template sent back from the modal ($uibModalInstance.close($scope.selectedTemplate);)
$scope.selected = selectedItem;
}, function () {
// The user clicked on the "cancel" button
$log.info('modal-component dismissed at: ' + new Date());
});
}
}]);
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/2.5.0/ui-bootstrap-tpls.min.js"></script>
<link href="//netdna.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<div ng-app="app" ng-controller="ctrl">
<script type="text/ng-template" id="modal.html">
<div class="modal-header">
<h3 class="modal-title">The modal!</h3>
</div>
<div class="modal-body">
<select id="selectedTemplate" ng-model="selectedTemplate" ng-change="onSelectChangeTemplate_kind()">
<option value="">Select Account</option>
<option ng-repeat="item in list_Template_kind" value="{{item.Templt_Kind_ID}}">
{{item.Templt_Kind_ID}}-{{item.Templt_Kind_Name}}
</option>
</select>
</div>
<div class="modal-footer">
<button class="btn btn-primary" type="button" ng-click="ok()">OK</button>
<button class="btn btn-warning" type="button" ng-click="cancel()">Cancel</button>
</div>
</script>
<! -- Call "openModal" and pass "null" so no pre selection is displayed in the modal -->
<button ng-click="openModal(null)">Open</button>
<! -- Call "openModal" and pass "2" so "Template 2" will be preselected in the modal -->
<button ng-click="openModal('2')">Open (Preselect 2)</button>
<! -- Call "openModal" and pass the {{ selected }} property so the last template selected since you last closed the modal will be shown in the modal -->
<button ng-click="openModal(selected )">Open (Preselect last selected)</button>
<! -- Show the last selected template in the view (For debugging purposes) -->
<span ng-if="selected">{{ 'selected - ' + selected }}</span>
</div>
I cannot set the default value for my selected Item knowing that I'm supposed to get the default value from a json () use that selected value to show another form.
In my controller I have set the selected this way:
.controller('attributeFacetCtrl', function ($scope, tabsService, $location, contentService, attribute) {
$scope.attribute = attribute;
$scope.types = [{
val: 'terms'
}, {
val: 'continuous'
}];
$scope.selected = $scope.attribute.facet.data.type;
$scope.isTerms = false;
$scope.validateFrom = function() {
var chk = $scope.selected.val;
if (chk === 'terms') {
$scope.isTerms = true;
} else {
$scope.isTerms = false;
}
};
})
the result of $scope.attribute.facet.data.type="terms"
And in my view I have this
<div class="form-group">
<label class="wk-field-label">
<i class="icon-asterisk wk-prefield wk-mandatory"></i>
Type
</span>
</label>
<select class="wk-field-input" ng-model="selected"
ng-options="typeValue as typeValue.val for typeValue in types"
ng-change="validateFrom()"
required></select>
</div>
<div ng-if="isTerms" ng-repeat="orders in attribute.facet.data.order">
<div>
<label class="wk-field-label">
<i class="icon-asterisk wk-prefield wk-mandatory"></i>
Order
</span>
</label>
</div>
</div>
The problem is that you're setting $scope.selected to a string ("terms"), but when you're loading up ng-options it's loading it with objects that have a val property.
If you don't necessarily need $scope.selected to be an object, then you can change your ng-options to the following:
ng-options = "typeValue.val as typeValue.val for typeValue in types"
If you require $scope.selected to be an object with a val property then you will need to set its initial value appropriately and not set it to a string in order to set the default selection.
Since you want to set the element by string, change your select element to the following:
<select
class="wk-field-input"
ng-model="selected"
ng-options="typeValue as typeValue.val for typeValue in types"
ng-change="validateFrom()"
required
>
</select>
You can read more about how to use as here: https://docs.angularjs.org/api/ng/directive/ngOptions
Hope that helps!
I have a couple of input fields in a ng-repeat block, and the user can add and remove fields with a button. A directive is used to mark invalid fields on blur.
My problem is this:
When I add new fields to the model in the controller, everything works fine. The view is updated, and the directive is called again to handle errors on the input fields.
But when I remove Fields from the model, the view is updated but the directive does not run. When the ng-repeat is rendered, a function is bound to the blur event of the fields. (inputNgEl.bind). When the user removes fields from the list, say, the 2nd field of 4, the rest of the fields get new names, so the function has to be bound again. Thats why I need the directive to run again after a person has been removed.
My demo code: http://plnkr.co/edit/EBggptFsMFld2fMfzS83?p=preview
You can see in the console how the directive is called when you add new fields, but not when you remove them.
What I would need is a way to trigger the directive manually in the removePersonFields function of the controller, or another, more elegant solution. Is this possible?
Here is the HTML:
<form novalidate name="form">
<div ng-repeat="person in persons" >
<div class="form-group" show-errors errorfieldindex="{{$index}}">
<label class="control-label">Name ({{'name_'+$index}})</label>
<input ng-model="person.name" name="name_{{$index}}" required class="form-control">
<span ng-if="form['name_'+$index].$error.required" class="help-block">Name invalid.</span>
</div>
<div class="form-group" show-errors errorfieldindex="{{$index}}">
<label class="control-label">Mail ({{'email_'+$index}})</label>
<input type="email" ng-model="person.email" name="email_{{$index}}" required class="form-control">
<span ng-if="form['email_'+$index].$error.required" class="help-block">Email required.</span>
<span ng-if="form['email_'+$index].$error.email" class="help-block">EMail invalid.</span>
</div>
<div class="btn-group">
<button type="button" class="btn btn-default" ng-click="removePersonFields($index)" ng-show="persons.length > 1">remove</button>
<button type="button" class="btn btn-default" ng-click="addPersonFields()">add</button>
</div>
</div>
</form>
The corresponding controller:
app.controller('MainCtrl', function($scope) {
$scope.init = function() {
$scope.persons = [{name: 'User One', email: 'aaa.bbb#ccc.org'},
{name: 'User Two', email: 'ddd.eee#fff.org'}];
}
$scope.addPersonFields = function() {
console.log("add person row");
$scope.persons.push( {name: '', email: ''});
}
$scope.removePersonFields = function(index) {
console.log("remove person row");
$scope.persons.splice(index,1);
// the view is updated after the splice, but the directive does not run.
}
$scope.init();
});
Every input field has a name with the current index of the loop. I use this name in the directive 'show-errors' to assign and remove a css-class when the field looses focus. The class is only there to hide the error messages until the user leaves the input field.
The directive 'showErrors':
app.directive('showErrors', function() {
return {
restrict: 'A',
require: '^form',
scope: {
errorfieldindex : '#'
},
link: function (scope, el, attrs, formCtrl) {
console.log("showErrors - link!");
// find the text box element, which has the 'name' attribute
var inputEl = el[0].querySelector("[name]");
// convert the native text box element to an angular element
var inputNgEl = angular.element(inputEl);
// get the name on the text box so we know the property to check
// on the form controller
var inputName = inputNgEl.attr('name');
if(typeof inputName != 'undefined') {
console.log("inputName: " + inputName);
var errorFieldIndex = scope.errorfieldindex;
if(typeof errorFieldIndex != 'undefined') {
console.log("errorFieldIndex: " + errorFieldIndex);
if(inputName.indexOf("{{$index}}") > 0) {
console.log("replace {{$index}} with " + errorFieldIndex);
inputName = inputName.replace("{{$index}}", errorFieldIndex);
}
}
// only apply the has-error class after the user leaves the text box
inputNgEl.bind('blur', function() {
console.log("bind Blur-event on element: " + inputName);
el.toggleClass('has-error', formCtrl[inputName].$invalid);
});
}
}
}
});