I'm quite new to angular (1), and have hit a problem. I need a custom year picker in my app, that appears in several places. The original code was
html
<select id="yearpicker" ng-model="vm.film.release" required="true" class="form-control">
<option disabled="" selected="" value="">Choose one... </option>
<option disabled="">___________________</option>
<option value="Unknown">Release Year Unknown</option>
<option disabled="">___________________ </option>
</select>
JS
<script>
for (i = new Date().getFullYear(); i > 1900; i--)
{
$('#yearpicker').append($('<option />').val(i).html(i));
};
</script>
Which gave me a nice dropdown with all the years from 1900 to present and a field for years where the year was unknown. But, the code was on the page, which isn't very nice, it meant that I had to load jquery before angular which was kicking out lots of errors, and also it meant that the code was repeated in several places. I pulled the code out, and put it in a directive, so I didn't have to repeat it, and so I could load angular first and get rid of the errors. Which I did like so
Directive JS
angular
.module('myapp')
.directive('customYearpicker', customYearpicker);
function customYearpicker () {
return {
restrict: 'AE',
scope: {
bindModel: '=ngModel'
},
controller: function () {
for (i = new Date().getFullYear(); i >= 1900; i--) {
$('#yearpicker').append($('<option />').val(i).html(i));
};
},
templateUrl: '/common/directives/customYearpicker/customYearpicker.template.html'
};
};
html template
<select id="yearpicker" ng-model="bindModel" class="form-control">
<option value="">Choose one... </option>
<option disabled="">___________________</option>
<option value="Unknown">Release Year Unknown</option>
<option disabled="">___________________ </option>
</select>
and I'm calling it like so
<custom-yearpicker name="release" ng-model="vm.record.release" required="true"></custom-yearpicker>
This works fine everywhere, except where I want to edit the record, and now the select is no longer populated by the value in 'vm.record.release' and is just blank. 'vm.record.release' has the correct value, it is just not displayed in my drop down, but neither is "Choose one... ", as with all other instances. Apart from this the yearpicker works fine, but this needs fixing.
If anyone can point out the (most likely) glaringly obvious blunder I've made, or point me in the right direction if I'm doing it all wrong, it would be much appreciated.
Thanks.
UPDATE
So after being advised to use ng-options, I have now updated my directive to this:
angular
.module('myapp')
.directive('customYearpicker', customYearpicker);
function customYearpicker () {
return {
restrict: 'AE',
scope: {
bindModel: '=ngModel'
},
controller: function ($scope) {
$scope.selectOptions = [
{ name: '', text: 'Choose one... ' },
{ name: '', text: '___________________' },
{ name: 'Unknown', text: 'Release Year Unknown' },
{ name: '', text: '___________________' },
];
for (i = new Date().getFullYear(); i >= 1900; i--) {
// $('#yearpicker').append($('<option />').val(i).html(i));
var newOpt = { name: i, text: i};
$scope.selectOptions.push(newOpt);
};
console.log($scope.selectOptions);
return $scope.selectOptions;
},
templateUrl: "/common/directives/customYearpicker/customYearpicker.template.html"
};
};
and the html template to this:
<select id="yearpicker" ng-model="bindModel" ng-options="option.name as option.text for option in selectOptions" class="form-control"> </select>
and I am calling it like this:
<custom-yearpicker name="release" ng-model="vm.record.release" required="true"></custom-yearpicker>
The problem is that even though it works much as before, my disabled options are no longer disabled, and the yearpicker is still not showing the value of vm.record.release. If I pick a value from the selector it is picked up by the edit controller with no problems, it just isn't selected in the selector for some reason. Still looking for suggestions please.
When bootstrapping your app, you need to declare the dependencies. If there are none, just use an empty array.
var app = angular.module('myapp', []);
app.directive('customYearpicker', customYearpicker);
function customYearpicker () {
return {
restrict: 'AE',
scope: {
bindModel: '=ngModel'
},
controller: function () {
for (i = new Date().getFullYear(); i >= 1900; i--) {
$('#yearpicker').append($('<option />').val(i).html(i));
};
},
template: `<select id="yearpicker" ng-model="bindModel" class="form-control"><option value="">Choose one... </option><option disabled="">___________________</option><option value="Unknown">Release Year Unknown</option><option disabled="">___________________ </option></select>`
};
};
Notice I used template instead of templateUrl for expediency on my end, but your templateUrl should work.
Also, I should mention that this is not the Angular way of creating selects. Please look into ngOptions.
You shouldn't use jQuery for such things.
directive code:
function customYearSelect(){
return {
restrict: 'E',
scope: {
model: '=ngModel',
name: '#',
yearFrom: '#',
yearTo: '#'
},
templateUrl: 'custom-year-select.template.html',
link: function (scope,element,attr) {
var yearFrom = attr['yearFrom'] ? attr['yearFrom'] : 1990;
var yearTo = attr['yearTo'] ? attr['yearTo'] : new Date().getFullYear();
scope.yearArray = [];
for(var i = yearTo; i >= yearFrom; i--){
scope.yearArray.push(i);
}
}
}
}
template:
<select name="custom-year-selet" ng-model="model" ng-options="name + ' ' + year for year in yearArray">
<option name="selectYear" value="" style="display:none">-- select year --</option>
</select>
HTML:
<custom-year-select name="Release year:" ng-model="selectedYear2"
year-from="2000" year-to="2005"></custom-year-select>
Here's a working jsfiddle: http://jsfiddle.net/kvda7Lc7/
Ok, I figured it out. In the end it was just me being a bit dopey, the fix was:
for (i = new Date().getFullYear(); i >= 1900; i--) {
// $('#yearpicker').append($('<option />').val(i).html(i));
var newOpt = { name: String(i), text: String(i) };
$scope.selectOptions.push(newOpt);
};
I just needed to parse the dates, doh!
Well thanks for the help anyway, I now have a directive doing it the angular way, instead of some bodgy jquery hack, and it all works .
Thanks for setting me on the right track Jorge.
Related
I am trying to perform a case-insensitive bind of an ng-model to a dynamic select drop-down using AngularJS.
by going through various other relavent answers from stack over flow, i have come up with something like below on the view , here caseinsensitive-options is an directive which i have come up with referencing the following solution
AngularJS case-insensitive binding to a static select drop-down
HTML:
<select id="dcName" caseinsensitive-options="" ng-model="DC.name" class="form-control">
<option value="">--- Please Select ---</option>
<option ng-repeat="dataCenter in inventoryDataCenters" value="{{dataCenter}}">{{dataCenter}}</option>
</select>
js directive code :
app.directive('caseinsensitiveOptions', function() {
return {
restrict: 'A',
require: ['ngModel', 'select'],
link: function(scope, el, attrs, ctrls) {
var ngModel = ctrls[0];
ngModel.$formatters.push(function(value) {
var option = [].filter.call(el.children(), function(option) {
return option.value.toUpperCase() === value.toUpperCase()
})[0];
return option ? option.value : value
});
}
}
});
The expected result is
when i pass something like this for
$scope.inventoryDataCenters = ["TEST1","test2",teST3]; and ng-model for DC.name has value TesT1.
The drop down should show TEST1 by doing case insensitive binding. That doesnt happen now. The above solution works perfect when we have static drop down.
things to be considered is that the select is inside a div which ng-repeat as shown below
ng-repeat="DC in workflowData.project_data.service_info.variables.service_data['data-center']"
and ng-model for select DC.name is derived from the above array DC.
check this URL for binding based on case insensitive value
https://jsfiddle.net/dwd2du17/6/
<div ng-app="module" ng-controller="controller as ctrl">
<select id="animal" ng-model="ctrl.animal">
<option value="">--- Select ---</option>
<option value="CaT">Cat</option>
<option value="DOg">Dog</option>
</select>
{{ctrl.animal}}
</div>
var appForm = angular.module('module', [])
.controller('controller', function($scope) {
});
I create customdirective and want to ng-model update before ng-change fire. Currently ng-change fire before udpate ng-mdoel value below is my code.
Main issue is coming when i change page number in dropdown list. It alert with previous value. I think ng-mdoel update after ng-change fire. but i want ng-model fire before ng-change.
app.directive('bottomPagination', function () {
var directive = {
templateUrl: '/App/Common/directives/bottomPagination.html',
restrict: 'A',
replace: true,
scope: {
currentPage: '=',
changePageSize: '&'
}
};
return directive;
});
//here is html directive (/App/Common/directives/bottomPagination.html)
<select id="pagesizeddl" ng-model="pageSize" ng-change="changePageSize()">
<option>5</option>
<option>10</option>
<option>20</option>
<option>30</option>
</select>
// here is my html page where i used directive
<div data-ng-controller="ProductsList as vm">
<div data-bottom-pagination
data-page-size="vm.paging.pageSize"
data-change-page-size="vm.changePageSize()"
>
</div>
</div>
// This is contrller
(function () {
// Start Products List function
var ListControllerId = 'ProductsList';
angular.module('app').controller(ListControllerId,
['$scope', ListController]);
function ListController($scope) {
var vm = this;
vm.paging = {
pageSize: 10
};
vm.changePageSize = changePageSize;
function changePageSize() {
alert(vm.paging.pageSize);
}
}
})();
Hi I recently had the same problem.
My best practice solution is to add a parameter in the ng-change function in your directive template. You need to add the parameter as an JSON Object!
<select id="pagesizeddl" ng-model="pageSize" ng-change="changePageSize({pageSize:pageSize})">
<option>5</option>
<option>10</option>
<option>20</option>
<option>30</option>
</select>
The property "pageSize" (of the JSON Object) will be matched with the parameter of your directive function vm.changePageSize(pageSize). So it is necessary that both have the same name!
<div data-ng-controller="ProductsList as vm">
<div data-bottom-pagination
data-page-size="vm.paging.pageSize"
data-change-page-size="vm.changePageSize(pageSize)"
>
</div>
Now you only have to alter your controller function like this:
function changePageSize(pageSize) {
alert(pageSize);
}
Here's a general idea of what I am doing:
<html ng-app="myapp">
<head>...</head>
<body>
<select class="form-control" ng-model="change_group" change-group>
<!-- options loaded in from Symfony/twig -->
<option value='0'>lala</option>
<option value='1'>bla</option>
</select>
<div ng-view></div>
</body>
</html>
When that all loads, the directive seems to work, and I can get it to trigger whenever the select is changed; but it's creating an empty option, and I can't seem to get it to pre-select an existing option, instead of it generating an empty one and having that be default.
myapp.directive('changeGroup', function() {
return {
restrict: 'A',
require: "ngModel",
scope: { },
controller: function($scope) {
$scope.change_group = 0;
},
link: function(scope, element, attrs, ngModel) {
ngModel.$viewChangeListeners.push(function() {
console.log('changed');
});
}
};
});
When I load the page, the desired option (0) flickers for a split second and then goes back to the empty option.
This is what the select looks like, after angular inserts that empty option:
<select class="form-control ng-pristine ng-valid ng-isolate-scope ng-touched" ng-model="change_group" change-group="">
<option value="? undefined:undefined ?"></option>
<option value="0">Public</option>
<option value="1">xil3</option>
</select>
Initialize to a string; not a number.
Also use no scope; not isolate scope.
angular.module("app",[]).directive('changeGroup', function() {
return {
restrict: 'A',
require: "ngModel",
//Do not use isolate scope
//scope: {},
controller: function($scope,$element) {
//Initialize to string
$scope.change_group = '1';
//Not to a number
//$scope.change_group = 1;
},
link: function(scope, element, attrs, ngModel) {
ngModel.$viewChangeListeners.push(function() {
console.log('changed');
});
}
};
});
The DEMO on JSFiddle.
Remove the scope: {} as that creates a new isolate scope and I think you are losing your change_group scope variable.
I have hundreds of cases like this (so the fix would have to be global and not tied to just this particular example)
There is a lot of select boxes like this:
<select ng-model="selectedItem">
<option ng-repeat="item in items | filter:attributes" value="{{item.id}}">{{item.name}}</option>
</select>
The selectedItem variable is null (and it will always initialize as null, which can't be changed in the controller in this particular situation).
What I'm trying to figure out is a way to globally watch all <select> elements in a view, see if the ng-model variable for that <select> box is null and if it is null set it to the first valid option in that select box, anytime the scope changes it will need to check if the ng-model is null and auto-select the first valid option.
The key thing to realise with this is that you can define more than one Angular directive with the same name, and all of them will be run for matching elements.
This is very powerful, as it enables you to extend functionality of built-in directives, or third party ones, etc.
Using this, I was able to create a select directive that would select the first valid option in the list whenever the model value is null.
One thing it doesn't do, however, is cope if you remove the selected item from the list (it goes back to being blank). But hopefully it is enough to get you started.
var app = angular.module('stackoverflow', []);
app.controller('MainCtrl', function($scope) {
$scope.selectedItem = null;
$scope.items = [1, 2, 3, 4].map(function(id) {
return {
id: id,
visible: true,
text: 'Item ' + id
};
});
});
app.directive('select', function() {
return {
restrict: 'E',
require: '?ngModel',
link: function($scope, $elem, $attrs, ngModel) {
// don't do anything for selects without ng-model attribute
if (!ngModel) return;
// also allow specifying a special "no-default" attribute to opt out of this behaviour
if ($attrs.hasOwnProperty('noDefault')) return;
// watch the model value for null
var deregWatch = $scope.$watch(function() {
return ngModel.$modelValue;
}, function(modelValue) {
if (modelValue === null) {
// delay to allow the expressions to be interpolated correctly
setTimeout(function() {
// find the first option with valid text
var $options = $elem.find('option'),
$firstValidOption, optionText;
for (var i = 0, len = $options.length; i < len; i++) {
optionText = $options.eq(i).text();
if (optionText !== '' && !optionText.match(/^(\?|{)/)) {
$firstValidOption = $options.eq(i);
break;
}
}
if ($firstValidOption) {
$firstValidOption.prop('selected', true);
ngModel.$setViewValue($firstValidOption.attr('value'));
// trigger a digest so Angular sees the change
$scope.$evalAsync();
}
}, 0);
}
});
// clean up in destroy method to prevent any memory leaks
var deregDestroy = $scope.$on('$destroy', function() {
deregWatch();
deregDestroy();
});
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<div ng-app="stackoverflow">
<div ng-controller="MainCtrl">
<select ng-model="selectedItem">
<option ng-repeat="item in items | filter:{visible:true} track by item.id" value="{{item.id}}">{{item.text}}</option>
</select>
<p>Visible items:</p>
<ul>
<li ng-repeat="item in items track by item.id">
<label>
<input type="checkbox" ng-model="item.visible">{{item.text}}
</label>
</li>
</ul>
</div>
</div>
In my code behind, I fill up a DropDownList as follows (using VB)
ddlCountries.DataSource = dt ' dt is DataTable with columns "CountryCode" and "CountryName"
ddlCountries.DataTextField = "CountryName"
ddlCountries.DataValueField = "CountryCode"
ddlCountries.DataBind()
ddlCountries.Attributes.Add("ng-model", "CountryName")
Then client side, I have a select tag, which is filled with options from server side as below:
<select ng-model="CountryName">
<option value="IN">India</option>
<option value="US">United States</option>
<option value="ML">Malaysia</option>
<!-- and other 200+ records -->
</select>
I can't use ng-options here! Now, whenever I change the value from select, I get CountryName as IN, US, ML etc.. Moreover, I can't edit values of options because they're used in server side code.
Here I want CountryName as India, United States or Malaysia instead! What can I do?
What data is the server sending you?
Is it some array of objects like this?
var countries = [
{ name: 'India', code: 'IN'},
{ name: 'United States', code: 'US'},
{ name: 'Malaysia', code: 'ML'}
];
If yes, then you can easily use ng-options (even though you dont have to, its just better).
<select ng-model="CountryName"
ng-options="c.code as c.name for c in countries">
</select>
Or in the case that you actually are working with server side generated html page and no javascript objects, then its a little bit tricker:
(Supposing you can use JQuery):
view:
<select id="countryNameSelect" ng-change="setCountryName()">
<option value="IN">India</option>
<option value="US">United States</option>
<option value="ML">Malaysia</option>
<!-- and other 200+ records -->
</select>
controller:
$scope.countryName = '';
$scope.setCountryName = function() {
$scope.countryName = $("#countryNameSelect option:selected").html();
};
In PHP BackEnd array to Json Out: < ? php echo json_encode($arrayDemo); ? >
// Init Load
$(function () {
'use strict';
angular
.module('ngjsapp', [])
.controller('FormCtrl', ['$scope', function ($scope) {
var myArraySelect = <?php echo json_encode($arrayDemo); ?>
$scope.changeSenasaProvinciasId = function (id) {
console.log(myArraySelect[id]);
}
}]);
});
Only HTML Stact + jQuery
// Option 1
$(function () {
'use strict';
angular
.module('ngjsapp', [])
.controller('FormCtrl', ['$scope', function ($scope) {
$scope.changeSenasaProvinciasId = function () {
console.log($("#mySelectID option:selected").text());
}
}]);
});
Only HTML Stact + jQuery BEST
// Option 2
$(function () {
'use strict';
angular
.module('ngjsapp', [])
.controller('FormCtrl', ['$scope', function ($scope) {
var myArraySelect = $('#mySelectID option').each( function() {
myArraySelect.push({$(this).val() : $(this).text() });
});
$scope.changeSenasaProvinciasId = function (id) {
console.log(myArraySelect[id]);
}
}]);
});