I build this directives for AngularJS+Symfony2 project:
app.directive('country', ['$http', function($http) {
return {
restrict: "C",
link: function(scope, element, attrs) {
$http.get(Routing.generate('countries')).success(function(data) {
if (data.message) {
scope.message = data.message;
} else {
scope.countries = data.entities;
}
}).error(function(data, status, headers, config) {
if (status == '500') {
scope.message = "There is not connection with server";
}
});
}
};
}]);
app.directive('state', ['$http', '$parse', function($http, $parse) {
return {
restrict: "C",
link: function(scope, element, attrs) {
scope.$watch(attrs.trigger, function() {
state = $parse(attrs.trigger)(scope);
iso = state != undefined && state.iso_country != undefined ? state.iso_country : state;
if (iso !== undefined && iso !=='') {
$http.get(Routing.generate('states') + '/' + iso).success(function(data) {
if (data.message) {
scope.message = data.message;
} else {
scope.states = data.entities;
}
}).error(function(data, status, headers, config) {
if (status == '500') {
scope.message = "There is not connection with server";
}
});
}
});
}
};
}]);
And this is how I use in my template file:
<select class="country"
ng-model = "country.standard_address"
ng-options = "country.name for country in countries">
<option value="-1">{{ "Select country" | trans }}</option>
</select>
<select class="state"
ng-model = "state.standard_address"
ng-disabled = "!states"
ng-options = "state.name for state in states"
trigger = "country.standard_address">
<option value="-1">{{ "Select state" | trans }}</option>
</select>
I need to add some way to validate this fields in case user didn't change it and remain with value equal -1. I could do this on click event for submit button but I not enabled the button until the form has no errors. This is the code to handle this part:
<input type="button" class="button {% verbatim %}{{ step1Form.$valid && 'active' || 'gray'}}{% endverbatim %}" value="Continuar" ng-disabled="!step1Form.$valid" ng-click="nextStep(2)" />
how I can do this?
Quoting the docs on select (with ngOptions):
Optionally, a single hard-coded element, with the value set to an empty string, can be nested into the element. This element will then represent the null or "not selected" option.
You need to set the value of the hard-coded option to an empty string and add the required attribute to the select elements. This way, the step1Form will not be valid unless an option (other than the null option) is selected.
UPDATE:
Seems like I misunderstood yur requirements. In order to be able to display a message on blur, you can use ng-focus and ng-blur to set the value of a variable. Then have an element that is shown/hidden based on the value of that variable (also taking into account the validity of the select).
The code for the "country" select could look like this:
<select name="countrySelect" ng-model="country.standard_address"
ng-options = "country.name for country in countries"
ng-focus="countryFocused=true" ng-blur="countryFocused=false"
required>
<option value="">Select country</option>
</select>
<div ng-hide="countryFocused || step1Form.countrySelect.$valid">
ERROR !!! Select a country ASAP !
</div>
See, also, this (updated) short demo.
Related
I'm having some issues with setting up the validation for a select. the code reads like
HTML
<form name="customerForm" novalidate="novalidate" data-ng-submit="submit()">
<li class="has-error" data-ng-if="customerForm.country.$error.required">
{{ 'CountryRequired' | translate }}
</li>
<label for="ddlCountries">{{ 'Country' | translate }}</label>
<select id="ddlCountries" name="country" class="form-control"
data-ng-model="selectedCountry"
data-ng-options="option.text for option in countries track by option.id"
data-ng-change="countryChange()" required="required">
<option value="" selected="selected">{{ 'SelectCountry' | translate }}</option>
</select>
</form>
JS Controller
$scope.countries = [];
countryService.getCountries().then(function (results) {
$scope.countries = results.data;
}, function (error) {
console.log(error.data.message);
});
$scope.$watch('customer.country', function (id) {
// Select the value on the dropdown list
$scope.selectedCountry = { id: id };
});
$scope.countryChange = function () {
$scope.customer.country = $scope.selectedCountry.id;
};
$scope.submit = function () {
if ($scope.customerForm.$valid) {
customerService.postCustomerForm($scope.customer).success(
function (data, status, headers, config) {
/*success callback*/
}).error(function (data, status, headers, config) {
alert("Submitting form failed!");
});
} else {
console.log("Invalid fields");
}
};
I've tried different things like setting selected="selected" on the select but didn't work. Also tried required and ng-required without luck.
Am I missing something or doing it wrong?
The problem is that you reset select model so original one you defined is replaced with a new one. Look at this piece of code:
$scope.$watch('customer.country', function(id) {
$scope.selectedCountry = {id: id};
});
In this code you overwrite $scope.selectedCountry with totally new object, so the model which has been used for setting up form validation is destroyed and new validation is never build.
In your case you can update selectedCountry model like this:
$scope.$watch('customer.country', function(id) {
if (id) {
$scope.selectedCountry.id = id;
}
});
But better, remove wather all together, you don't need it since you have ngChange directive, where you can update selectedCountry.id.
Demo: http://plnkr.co/edit/CXDRdRYxZn38FnanOqid?p=preview
My question is very similar to this one : AngularJS - Value attribute on an input text box is ignored when there is a ng-model used?
Considering that answer : https://stackoverflow.com/a/20522955/1598891
I would like to know if there's a way to do the same treatment for a <select> ?
If you set a ng-model to an <input type="text">, the value of the input is reset to null (because of Angular)
There's a similar behavior for drop down lists. If you set a "selected option" in a <select>, it will be ignored if you also had the ng-model attribute.
I'm new to angular, but I found this really annoying.
Since I'm working with ASP MVC, MVC already defines all the values if I do :
#Html.DropDownListFor(model => model.MyModel,
new SelectList(/*...*/),
"Select an options",
new { #class = "form-control",
ng_model = "MyModelProperty"})
I know that I could do something like :
ng_model = #String.Format("myModel = {0}", Model.MyModelProperty)
but it would be kind of annoying to repeat that information since MVC should already do it properly.
Yes, the behavior is the same as the input, this is how "two-way data binding" works.
Anyway, you can force the selection using ng-selected, for example:
In the your controller:
$scope.selected = null;
And in the html:
<select ng-model="model.selected">
<option value="a">a</option>
<option value="b" ng-selected="true">b</option>
<option value="c">c</option>
</select>
But ATTENTION, this will only force the selection on the html, the value of the model don't is updated
Check this on plunk: http://plnkr.co/edit/uV3md09CYX0mUVfIy4b2
To know more how the data binding works see this: ng-selected not working in select element
I ended up using this Angular.JS directive :
https://github.com/glaucocustodio/angular-initial-value
You can find more informatino in that blog :
http://blog.glaucocustodio.com/2014/10/20/init-ng-model-from-form-fields-attributes/
Just in case the links dies you need to put this code before the declaration of your Angular application:
var initialValueModule = angular.module('initialValue', [])
.directive('initialValue', function() {
return{
restrict: 'A',
controller: ['$scope', '$element', '$attrs', '$parse', function($scope, $element, $attrs, $parse){
var getter, setter, val, tag;
tag = $element[0].tagName.toLowerCase();
val = $attrs.initialValue || $element.val();
if(tag === 'input'){
if($element.attr('type') === 'checkbox'){
val = $element[0].checked ? true : undefined;
} else if($element.attr('type') === 'radio'){
val = ($element[0].checked || $element.attr('selected') !== undefined) ? $element.val() : undefined;
}
}
if($attrs.ngModel){
getter = $parse($attrs.ngModel);
setter = getter.assign;
setter($scope, val);
}
}]
};
});
/* commonjs package manager support (eg componentjs) */
if (typeof module !== "undefined" && typeof exports !== "undefined" && module.exports === exports){
module.exports = initialValueModule;
}
Then you can do :
<script>
var app = angular.module('myApp', ['initialValue']); //Pass the directive to the app
</script>
<body ng-app="myApp">
<!-- Add the "initial-value" directive to the html input -->
<input type="text" value="John" name="text" initial-value ng-model="text" id="text"/>
</body>
I'm trying to create an AngularJS Directive to manage the promps of a <select> input. The 3 different modes are as follows:
Don't allow a blank option
This is the default behavior
Allow a blank option with no text
Achieved by setting allow-blank="true" on the directive
Allow a blank option with prompt text
Achieved by setting allow-blank to any value other than "true"
However, the blank options are never available.
Demo on Plunker
Template
<select ng-options="tag.id as tag.name for tag in myTags">
<option ng-if="allowBlank === 'true'" value=""></option>
<option ng-if="allowBlank && allowBlank !== 'true'" value="">{{allowBlank}}</option>
</select>
Directive
myApp.directive('mySelector', ['$http', function($http) {
return {
templateUrl:'my-selector-template.html',
restrict: 'E',
scope: {
allowBlank: '#?'
},
replace: true,
controller: function ($scope) {
$scope.myTags = [
{"name":"aliquam in","id":1},
{"name":"massa velit","id":2},
{"name":"nec vestibulum","id":3}
];
}
};
}]);
The problem is that the content of the select is transcluded (see below). If you inspect the elements that are output you don't even see the option tags you defined inside.
As you're just using a string you should use a pre-linking function where you add the correct option in via javascript. The following code should do the trick (Forked Plunk).
link: {
pre: function(scope, element, attrs){
if(attrs.allowBlank === 'true') element.append('<option value=""></option>');
else if(attrs.allowBlank && attrs.allowBlank !== '') element.append('<option value="">' + attrs.allowBlank + '</option>');
}
}
EDIT The select does not 'transclude' it's contents, as #lightswitch05 pointed out the select directive grabs the first child with value="" as the empty node (source ref). Then later it clears out the contents of the select and dynamically adds in the relevant <option> tags (source ref)
I am creating dropdown in a directive as follows:
<select ng-model="selectedSite">
<option value="new">Add New Site</option>
<option value="line" disabled>------------------------</option>
<option ng-repeat="site in defaultSites"
value="{{$index}}">
{{site.name}}
</option>
</select>
Directive is:
app.directive('siteForm', function() {
return{
restrict: 'E',
replace: true,
scope: {
site: '=',
defaultSites: '=',
selectedSite: '=',
},
templateUrl: '/views/templates/site_form.html',
link: function($scope, element, attrs) {
$scope.$watch('selectedSite', function(newValue, oldValue) {
console.log('Site selected:', newValue);
if (newValue !== undefined && newValue !== null) {
if (newValue !== "new") {
var value = $scope.defaultSites[parseInt(newValue)];
$scope.site.name = value.name;
} else {
$scope.site.name = "";
}
}
});
}
};
});
However when I provide the initial index in selectedSite, it does not work and always shows first option. E.g. If we provide the selectedSite as "1" then option with value "1" should get selected which is not happening.
Everything else works fine including $watch and selectedSite gets populated when I select option from dropdown.
I am using AngularJS v1.2.10.
Try to use ng-selected in following way
<option ng-repeat="site in defaultSites" value="{{$index}}" ng-selected="{{$index == selectedSite}}">
{{site.name}}
</option>
Try calling $scope.$apply() at the end of your $watch handler.
See the AngularJS docs on Scope.
I have a select box with a blank option and some other options. The options are not generated with ngOptions directive, because they are generated on server side (with Symfony forms).
In some cases I want to unselect the selected value of the select box. So that the blank option is selected. But I cannot get this to work. The select box does not get updated.
I have created a jsfiddle to show the problem.
html
<div ng-controller="MyCtrl">
<form name="form">
<select ng-model="foo.hero" name="foo[hero]" my-directive="">
<option value=""></option>
<option value="1">Batman</option>
<option value="2">Superman</option>
<option value="3">Spiderman</option>
</select>
</form>
<p>Selected: {{ foo.hero }}</p>
</div>
javascript
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
$scope.foo = {hero: '2'};
}
myApp.directive('myDirective', function($timeout) {
return {
require: ['ngModel', '?form'],
link: function(scope, element, attrs, ctlrs) {
$timeout(function() {
var modelController = ctlrs[0];
modelController.$setViewValue('');
});
}
};
});
Use modelController.$render() after you call $setViewValue. This will update the DOM element(s).
myApp.directive('myDirective', function($timeout) {
return {
require: ['ngModel', '?form'],
link: function(scope, element, attrs, ctlrs) {
$timeout(function() {
var modelController = ctlrs[0];
modelController.$setViewValue('');
modelController.$render();
});
}
};
});
Updated fiddle here.
Do this:
$timeout(function() {
scope.foo.hero = '';
});
since the model of your select is foo.hero whenever you change it then the select will change the selected.
Not sure why you need to muck around with modelController? Can't you just update the model to the default value?
myApp.directive('myDirective', function($timeout) {
return {
scope: {
model: '=?ngModel'
},
link: function(scope, element, attrs, ctlrs) {
$timeout(function() {
scope.model = '';
});
}
};
});
Updated fiddle here.