I would like to create a directive with a dropdown template. I try to give the value for ng-options as a parameter of the directive but the dropdown only contains the default value.
Here is a Fiddle and my code in this post :
http://jsfiddle.net/wXV6Z/6/
var app = angular.module('app', []);
function Ctrl($scope){
$scope.countries = [
algeria={name:'ALGERIA', phoneCode:'213'},
andorra={name:'ANDORRA', phoneCode:'376'},
angola={name:'ANGOLA', phoneCode:'244'}
];
}
app.directive('skiTest', function() {
return {
'replace': true,
'restrict': 'E',
'scope': {
'data': '=',
},
link: function (scope, element, attrs) {
scope.options = attrs.selectOptions;
},
'template':'<div><select name="testSelect" ng-model="ngModel" ng-options="
{{options}} in data"><option value="">Code</option></select></div>'
}
});
Here is the HTML :
<div ng-controller="Ctrl">
<div>
<h2>Without directive</h2>
<select ng-model="code" ng-options="country.phoneCode as country.name for country in countries">
<option value="">Code</option>
</select>
</div>
<div>
<h2>With directives</h2>
<ski-test data="countries" select-options="country.phoneCode as country.name for country"></ski-test>
</div>
</div>
Thank you very much if somebody can point my mistake !
The template in the directive just needs to be this:
'template':'<div><select name="testSelect" ng-model="data" ng-options="c.name for c in data"><option value="">Code</option></select></div>'
I forked your fiddle with the change: http://jsfiddle.net/W5Ex3/
Related
Brief intro to my problem
I have a directive that dynamically shows a list of checkboxes. It has a parameter called options that should be an array like the following, in order to show the list of checkboxes correctly. For example:
var options = [
{
id: 1,
label: 'option #1'
},
{
id: 2,
label: 'option #2'
},
{
id: 3,
label: 'option #3'
}
];
So, by passing this array to my directive, a group of three checkboxes would be shown.
Also, the directive requires ngModel that will have the result of checking/unchecking the checkboxes (this object is always passed initialized). For example:
var result = {
"1": true,
"2": true,
"3": false
};
This case means that the first and second checkboxes (options with id=1 and id=2) are checked and the third (option with id=3) is unchecked.
My directive
template.html
<div ng-repeat="option in options track by $index">
<div class="checkbox">
<label>
<input type="checkbox"
ng-model="result[option.id]">
{{ ::option.label }}
</label>
</div>
</div>
directive.js
angular
.module('myApp')
.directive('myDirective', myDirective);
function myDirective() {
var directive = {
templateUrl: 'template.html',
restrict: 'E',
require: 'ngModel',
scope: {
options: '='
},
link: linkFunc
};
return directive;
function linkFunc(scope, element, attrs, ngModel) {
scope.result;
ngModel.$render = setResult;
function setResult() {
scope.result = ngModel.$viewValue;
};
};
};
What I want to achieve
Wherever I use my directive, I want to be able to trigger a function whenever the ngModel changes. Of course, I would like to achieve this using ngChange. So far I have the following:
<my-directive
name="myName"
options="ctrlVM.options"
ng-model="ctrlVM.result"
ng-change="ctrlVM.selectionChanged()">
</my-directive>
but the .selectionChanged() function is not triggered whenever the model changes. Anyone has any idea why this is not working as I am expecting it to work?
First thing first, please try to provide jsfiddle, codepen etc code snippet link so that it will be easy for others to answer your question.
The problem in your case is that you are never updating the ctrlVM.result object as you are passing the object's reference and that reference never change even if you manually update the model by calling ngModel.$setViewValue().
To solve the problem, just update the model by manually calling ngModel.$setViewValue() and pass in the new Object so that the reference changes and that will trigger the ngChange directives logic.
I've added the logic to do that and it will successfully trigger the change. Look at the code below:
angular
.module('myApp', [])
.directive('myDirective', myDirective)
.controller('MyController', function($timeout) {
var vm = this;
vm.options = [{
id: 1,
label: 'option #1'
}, {
id: 2,
label: 'option #2'
}, {
id: 3,
label: 'option #3'
}];
vm.result = {
"1": true,
"2": true,
"3": false
};
vm.selectionChanged = function() {
vm.isChanged = true;
$timeout(function() {
vm.isChanged = false;
}, 500)
}
});
function myDirective() {
var directive = {
templateUrl: 'template.html',
restrict: 'E',
require: 'ngModel',
scope: {
options: '='
},
link: linkFunc
};
return directive;
function linkFunc(scope, element, attrs, ngModel) {
scope.result;
ngModel.$render = setResult;
function setResult() {
scope.result = ngModel.$viewValue;
};
scope.updateValue = function(val) {
ngModel.$setViewValue(Object.assign({}, val))
}
};
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.min.js"></script>
<div ng-app="myApp">
<script type="text/ng-template" id="template.html">
<div ng-repeat="option in options track by $index">
<div class="checkbox">
<label>
<input type="checkbox"
ng-model="result[option.id]" ng-click="updateValue(result)">
{{ ::option.label }}
</label>
</div>
</div>
</script>
<div ng-controller="MyController as ctrlVM">
<my-directive name="myName" options="ctrlVM.options" ng-model="ctrlVM.result" ng-change="ctrlVM.selectionChanged()">
</my-directive>
<div> Data: {{ctrlVM.result}} </div>
<div> isChanged: {{ctrlVM.isChanged}} </div>
</div>
</div>
#Gaurav correctly identified the problem (ng-change is never called because the object reference does not change). Here is a simpler solution that doesn't require manually cloning into the controller's model:
Add a binding for the ng-change attribute:
scope: {
options: '=',
ngChange: '&' // Add this, creates binding to `ctrlVM.selectionChanged()`
}
Add an ng-change to your checkbox template:
<input type="checkbox"
ng-model="result[option.id]" ng-change="ngChange()">
Now, when any checkbox changes it will automatically call the outer ng-change function without the intermediate step of cloning into the model.
HTML:
<select class="form-control" ng-model="sortBy">
<option value="createdAt"><strong>Date</strong></option>
<option value="ProgressStateNumber"><strong>Progress</strong></option>
<option value="adminUsername"><strong>adminUsername</strong></option>
</select>
<tbody>
<tr
reportrowgroup
ng-repeat="report in reportTree track by $index | orderBy: sortBy"
report="report"
>
Directive:
.directive('reportrowgroup', function () {
return {
restrict: 'EA',
template:
'<td>{{report.createdAt | date:"yyyy-MM-dd HH:mm"}}</td>'+
'MORE HTML'
scope: {
report: '='
},
controller: function() {
},
link: function(scope,elem,attr,ctrl) {
}
}
}
})
Everything works, except the table doesn't sort by createdAt when I select createdAt in select.
What could be the problem?
Your reportrowgroup directive is creating a new scope and therefore missing the reference to the same sortBy variable. Try wrapping the sortyBy in an object.
<!-- markup -->
<select class="form-control" ng-model="input.sortBy">
// Code
angular.module('myApp').controller('MyController', function($scope) {
$scope.input = {
sortBy: 'default-value-goes-here'
};
});
See this plunker: http://plnkr.co/edit/MmjuaLfvVnOQb0Ngz0dI?p=preview
How can I fill an input text field with a dropdown-menu ?
Text Input:
<input type="text" ng-model="storagePlaceModel" lms-dropdown class="form-control lms-input-text-disable lms-dropdown-toggle" id="storagePlace" placeholder="Standort" autocomplete="off" readonly>
Own written dropdown:
<div class="lms-dropdown">
<div class="lms-dropdown-scrollbar">
<li ng-repeat="data in data_input_fields.input_cat_music_book_storage_place">
<span>{{data.input_value}}</span>
<i ng-show="storagePlaceModel == data.input_value" class="glyphicon glyphicon-ok"></i>
</li>
</div>
</div>
If I select an li element I want to update the storagePlaceModel with the li value.
Updated question:
Because I have more than one of these text inputs with dropdowns I need a solution where the conroller/directive does not know the model name exactly.
Directive could look like:
lmsApp.directive('updateInputField', function() {
return {
restrict: 'A',
link: function(scope, elem, attrs) {
$(elem).click(function(e) {
// Read out the model name of the html text input field
});
}
};
});
Thank you for your help! I would appreciate every answer.
I've edited the entire question to create a directive to wrap your desired structure. You'll pass to the directive the model you want, and that way, each model will be independent on different directive usages:
myApp.directive('myDirective', function() {
return {
restrict: "E",
scope: {
model: "=",
datas: "="
},
templateUrl: "directive.html",
link: function(scope, element, attr) {
scope.updateValue = function(val) {
scope.model.storagePlaceModel = val;
}
}
}
});
The directive.html contains your text input and the dropdown.
Controller:
function MyCtrl($scope) {
$scope.wrapper = {};
$scope.wrapper2 = {};
$scope.wrapper3 = {};
$scope.datas = [
{ "input_value" : "1" },
{ "input_value" : "2" },
{ "input_value" : "3" },
{ "input_value" : "4" }
];
}
HTML usage:
<div ng-app="myApp" ng-controller="MyCtrl">
<my-directive model="wrapper" datas="datas"></my-directive>
<my-directive model="wrapper2" datas="datas"></my-directive>
<my-directive model="wrapper3" datas="datas"></my-directive>
</div>
Working Fiddle
I want to change the source (e.g source variable) that is managed by input's ngModel from my directive.
I want to clone the object that is editable by a form and make this process in the directive.
Probably this jsfiddle will explain you better.
What I do is:
<div ng-app="myApp">
<div ng-controller="SimpleController">
<form change-original-model>
<input ng-model="myModel.firstname" /><br/>
<input ng-model="myModel.secondname" /><br/>
I want <b>myModel</b> to be not changed:<br/>
{{ myModel.firstname }}<br/>
I want <b>newModel</b> to be cloned and changed by input:<br/>
{{ newModel.firstname }}<br/>
</form>
</div>
angular.module('directives', []);
angular.module('directives').controller('SimpleController', function($scope) {
$scope.myModel = { firstname: 'Sady', secondname: 'Sherozi' };
});
angular.module('directives').directive('changeOriginalModel',
function($parse) {
return {
require: '?ngModel',
link: function(scope, element, attrs, controller) {
var ngModel = $parse(attrs.ngModel)(scope);
scope.newModel = angular.copy(ngModel);
$(element).find("input").each(function(){
form_elements = $(this).attr("ng-model");
if (form_elements) {
var replace_input_data = form_elements.replace(attrs.ngModel + '.', "newModel." );
$(this).attr("ng-model", replace_input_data);
}
});
}
};
}
);
angular.module('myApp', ['directives']);
As an example of what I want, consider the following example
<select ng-options="option.text for option in options"></select>
In my directive I want to use something similar to ngOptions, because I need to create a list
For example, assume I have a directive barFoo, called as follows:
<bar-foo options="options"></bar-foo>
with a template/html as follows:
<ol>
<li ng-repeat="option in options" ng-bind="option.text"></li>
</ol>
What is needed to change all this into a call like
<bar-foo options="option.text for option in options"></bar-foo>
The main reason I need this is because I don't know the property name holding the label text (in this case it is text)
I provided a fiddle and see whether this helps. Instead of passing in "options.text for option in options", I set it up such that you pass the "options" array and then the field you want. I assumed the field will be set up as a variable; if it hard-coded, then you can just do field='someFieldName' instead.
http://jsfiddle.net/y376K/1/
HTML
<body ng-app='testApp'>
<div ng-controller='TestCtrl'>
<bar-foo options='options' field='{{optionsField}}'></bar-foo>
</div>
</body>
JS
angular.module('testApp', [])
.controller('TestCtrl', function($scope) {
$scope.options = [
{
text: 'Node.js rocks my socks',
language: 'Node.js',
},
{
text: 'Angular is hot',
language: 'Angular.js',
},
{
text: 'Backbone.js is mmmm',
language: 'Backbone.js',
}
];
$scope.optionsField = 'text';
})
.directive('barFoo', function() {
return {
restrict: 'E',
scope: {
options: '=',
field: '#'
},
template: '<ol><li ng-repeat="option in options" ng-bind="option[field]"></li>'
};
})
You can do this by parsing the attribute. The other solution would be to pass it as two attributes (see the other answer)
You should probably use a regexp for this, but I coded this quickly:
app.directive('barFoo',function($parse) {
return {
restrict: 'E',
scope: {},
templateUrl: "template.html",
link: function(scope,element,attrs) {
var splitOptions = attrs.options.split(' for ');
scope.fieldName = splitOptions[0].split('.')[1];
var repeatExp = splitOptions[1];
scope.valueName = repeatExp.split(' in ')[0];
var collectionName = repeatExp.split(' in ')[1];
scope.values = $parse(collectionName)(scope.$parent);
}
};
});
See this plnkr