I'm quite new to AngularJS and I'm trying to define 4 seperate select elements usign element directives.
app.directive('playerselect', function() {
return {
restrict: 'E',
replace: true,
templateUrl: 'templates/directives/player-select.html',
}
});
<select ng-options="player.full_name for player in players">
<option value="" selected disabled>Kies een speler</option>
</select>
When a user selects an option in one of the select elements I want the other select elements to be updated, so that the selected option gets disabled in all other select elements. Basically, I want to make sure that each option can get chosen only once.
Any ideas on how to do this?
I would do it like this:
directive
app.directive('playerSelect', function() {
return {
templateUrl: ...
scope: {
players: '=',
selectedPlayer: '=',
selectedPlayers: '=',
},
controller: function($scope) {
$scope.isDisabled = function(player) {
return $scope.selectedPlayers.indexOf(player) > -1 &&
player !== $scope.selectedPlayer)
};
},
}
});
directive template
<select ng-model="selectedPlayer">
<option ng-repeat="player in players" ng-disabled="isDisabled(player)">{{player}}</option>
</select>
controller
app.controller('PlayerController', function($scope) {
$scope.players = ...
$scope.selectedPlayers = [
$scope.player1,
$scope.player2,
$scope.player3,
$scope.player4,
];
});
controller template
<div ng-controller="PlayerController">
<player-select players="players" selected-players="selectedPlayers" selected-player="player1"></player-select>
<player-select players="players" selected-players="selectedPlayers" selected-player="player2"></player-select>
<player-select players="players" selected-players="selectedPlayers" selected-player="player3"></player-select>
<player-select players="players" selected-players="selectedPlayers" selected-player="player4"></player-select>
</div>
Check this jsFiddle for see it in action.
I would recommend you an use of directive two way data binding and ngModelController.
So in your html :
<player-select players="players" ng-model="select1"></player-select>
<player-select players="players" ng-model="select2"></player-select>
<player-select players="players" ng-model="select3"></player-select>
In your controller :
$scope.players = [{
name: 'foo'
}, {
name: 'bar'
}, {
name: 'baz'
}];
And the directive, using scope.$watch for update each list :
angular.module('fiddleApp').directive('playerSelect', function ($rootScope) {
return {
restrict: 'E',
require: 'ngModel',
scope: {
players: '='
},
template: '<select ng-model="selected" ng-options="player.name for player in playersCopy"></select>',
link: function (scope, el, attrs, ngModel) {
function update() {
scope.playersCopy = angular.copy(scope.players);
if (scope.selected) {
scope.playersCopy.push(scope.selected);
}
}
scope.$watch('selected', function (newValue, oldValue) {
if (newValue) {
scope.players.splice(scope.players.map(function (a) {
return a.name
}).indexOf(newValue.name), 1);
if (oldValue) { scope.players.push(oldValue); }
ngModel.$setViewValue(scope.selected);
$rootScope.$broadcast('playersUpdate');
}
});
scope.$on('playersUpdate', function () {
update();
});
update();
}
};
});
Related
How do I make a directive run a function like the other built in directives in angular?
In example:
<div ng-repeat="someId in myList" my-directive="runFunctionInScope(someId, divHtmlElement)" />
myApp.directive('myDirective', function ()
{
return function (scope, element, attrs)
{
//??
}
}
You can try something like the below code snippet. Also please check this plunker for working example of your given scenario.
Template:
<div ng-repeat="someId in myList" my-method='theMethodToBeCalled' my-id='someId.id' />
Controller:
app.controller('MainCtrl', function($scope) {
$scope.myList=[{
id: 1,
value: 'One'
}, {
id: 2,
value: 'Two'
}];
$scope.theMethodToBeCalled = function(id) {
alert(id);
};
});
Directive:
app.directive("myMethod",function($parse) {
var directiveDefinitionObject = {
restrict: 'A',
scope: {
method:'&myMethod',
id: '=myId'
},
link: function(scope,element,attrs) {
var expressionHandler = scope.method();
expressionHandler(scope.id);
}
};
return directiveDefinitionObject;
});
I am using bootstrap select widget in my angularjs app. I have a repeated pattern where I need to have the same select element in multiple forms. So I thought why not create that as a directive.
The code for the directive is below.
(function () {
var controller = function ($scope) { }
controller.$inject = ['$scope'];
var yesNoDecline = function ($timeout) {
return {
restrict: 'A',
scope: {},
template: '',
controller: controller,
link: function ($scope, element, attrs) {
//Only a select
if ($(element).is('select')) {
//Add options
$(element).append($('<option />', {
value: 'No',
text: 'No'
}));
$(element).append($('<option />', {
value: 'Yes',
text: 'Yes'
}));
$(element).append($('<option />', {
value: 'Decline to answer',
text: 'Decline To Answer'
}));
$timeout(function () {
$(element).selectpicker('refresh');
}, 0);
//Add some attributes
$(element).attr('ui-jq', 'selectpicker');
$(element).attr('ui-options', '{ iconBase: "fa", tickIcon: "fa fa-check" }');
}
}
}
};
yesNoDecline.$inject = ['$timeout'];
appModule.directive('yesNoDecline', yesNoDecline);
})();
Problems with the above code:
Additional empty item is added at the top although the code above didn't add it.
When I select "No" as an option, "Yes" is shown on the select. The same is true to other options, I select an option, another option appears selected.
In the controller, I am setting the selected value to No on the $scope. However, nothing is selected on the select using this directive.
Here is another version of the code using pure Angular:
I've converted this directive to a more angular-like. I get an error though multiple directives requesting same ngModel, which I don't get it frankly.
(function () {
var yesNoDecline = function ($timeout) {
return {
restrict: 'E',
replace: 'true',
require: 'ngModel',
scope: {
id: '#',
name: '#',
ngModel: '=ngModel'
},
template: '<select class="form-control bs-select" id="{{ ::id }}" name="{{ ::name }}" ng-model="ngModel.$viewValue" ' +
' ui-jq="selectpicker" ui-options=\'{ iconBase: "fa", tickIcon: "fa fa-check" }\'>' +
' <option ng-repeat="obj in data" value="{{ ::obj.value }}">{{ ::obj.text }}</option>' +
'</select>',
link: function (scope, element, attrs, ngModelCtrl) {
//Populate the scope
scope.data =
[{
value: 'No', text: 'No'
}, {
value: 'Yes', text: 'Yes'
}, {
value: 'Decline to answer', text: 'Decline To Answer'
}];
scope.$watch(function () {
return ngModelCtrl.$viewValue;
}, function (newValue) {
ngModelCtrl.$setViewValue(newValue);
ngModelCtrl.$render();
});
$timeout(function () {
$(element).selectpicker('refresh');
}, 0);
}
}
};
yesNoDecline.$inject = ['$timeout'];
appModule.directive('yesNoDecline', yesNoDecline);
})();
That's how I use it:
<yes-no-decline id="x" name="x" ng-model="vm.y"></yes-no-decline>
I appreciate your assistance.
Thanks
I created a select directive and am using this directive twice. I need to see the selected items of both. What should I do?
HTML
<div select-list="items"></div>
<div select-list="items2"></div>
Controller
var myApp = angular.module('myApp',[]);
myApp.controller('mainController', function($scope) {
$scope.items = [
{
name: "1"
},
{
name: "2"
}
];
$scope.items2 = [
{
name: "3"
},
{
name:"4"
}
];
$scope.selectedValues = [];
});
Select directive
myApp.directive("selectList", function() {
return {
restrict: "EACM",
template: '<select ng-model="selectedValues" ng-options="item.name for item in data"></select>',
scope: {
data: '=selectList'
}
}
});
I need to add selected items of both "selects" into $scope.selectedValues.
I tried through ng-change, but it didn't work.
Your directive use isolated scope, so you can't access from the controller to the directive or from the directive to the controller.
You have to create a new entry.
I let you a fiddle that is working :
https://jsfiddle.net/Lv1q2sh2/1/
// Code goes here
var myApp = angular.module('app', []);
angular.module('app')
.directive("selectList", function(){
return {
restrict: "EACM",
require: 'ngModel',
template: '<select ng-model="selected" ng-change="onSelectedValue()" ng-options="item.name for item in data"></select>',
scope: {
data: '=selectList'
},
link: function (scope, element, attr, ngModel) {
scope.onSelectedValue = function () {
ngModel.$setViewValue(scope.selected);
}
}
}
})
.controller('mainController', function($scope) {
$scope.items = [
{name: "1"},
{name: "2"}
];
$scope.items2 = [
{name:"3"},
{name:"4"}
];
$scope.selectedValues = [];
});
Directive needs to be created properly:
Have a controller for your directive
If you are using isolated scope, make sure to pass selectedValue to the scope.
ex:
Directive:
myApp.directive("selectList", function(){
return{
restrict: "EACM",
template: '<select ng-model="selectedValues" ng-options="item.name for item in data"></select>',
scope: {
data: '=selectList',
ngModel: '='
}
//Add link function here, crate watcher on ngModel and update it back on select dropdown selection.
})};
HTML:
<div select-list="items" ng-model="selectedValue1" ></div>
<div select-list="items2" ng-model="selectedValue2"></div>
Add link function to directive and put a watcher on ngModel, once user makes change in selection, update parent ng-model.
So I'm beginner to angularjs and firebase and I'm trying to develop an app which adds values(numerical) on an input. So far I have this:
app.js:
var app = angular.module("app", ['firebase']);
app.directive('addOne', function() {
return {
link: function(scope,element) {
element.bind('click', function() {
console.log(element.parent().find('input'))
element.parent().find('input')[1].value++;
});
}
}
});
and my view:
<section class="form-group">
<label for="">$</label> <input type="button" value="+" add-one>
<input ng-model="user.level" type="text" class="form-control" />
</section>
and my controller:
app.controller('mController', ['$scope', 'User',
function($scope, backHome, User, adicionar){
$scope.user = User(1);
User(1).$bindTo($scope, "user");
}
]);
the thing is that after I click the button with the directive add-one the value of the input changes but the $bindTo is not working...
So why does the bindTo doesn't work when I make a change directly in the DOM?
AngularJS doesn't care what the value of an input is set to, it only cares about what's in the ng-model. Try this...
app.directive('addOne', function() {
return {
link: function(scope,element) {
element.on('click', function() {
scope.$apply(function(){
scope.user.level++
});
});
}
}
});
As pointed out by #PankajParkar, you also need to use scope.$apply when you want to update a binding from event.
angular.module('demo', [])
.controller('DemoController', function($scope){
$scope.user={level: 1};
})
.directive('addOne', function() {
return {
link: function(scope, element, attrs) {
element.on('click', function() {
scope.$apply(scope.user.level++);
});
}
}
})
.directive('unaryInput', function(){
return {
restrict: 'EA',
scope: {
model: "=",
txt: '#buttonText'
},
template: '<input type="text" ng-model="model" /><button>{{txt}}</button>',
link: function(scope, element, attrs) {
if(angular.isDefined(attrs.initialVal)) {
scope.model = attrs.initialVal;
}
element.on('click', function() {
if (attrs.direction === 'decrement') {
scope.$apply(scope.model--);
} else {
scope.$apply(scope.model++);
}
});
}
};
});
<script src="https://code.angularjs.org/1.3.15/angular.min.js"></script>
<div ng-app="demo" ng-controller="DemoController">
<input type="text" ng-model="user.level">
<input type="button" value="+" add-one>
<hr>
<unary-input button-text="Add one" model="user.level" direction="increment"></unary-input>
<unary-input button-text="-" model="user.level" direction="decrement"></unary-input>
<hr>
<unary-input button-text="-" model="user.val" direction="decrement" initial-val="10"></unary-input>
</div>
In AngularJS, you want to change the view by changing the model that it's based on, versus doing it imperatively like you might with a traditional jQuery approach for example (traversing the DOM and incrementing the value).
UPDATE
Okay, so here's a nice reusable version of the (please check the snippet to see it in action).
The template includes both the button and the input. It accepts 4 values that you set as attributes:
button-text: The text you want to show on the button.
model: The model value for the input.
initial-val: The initial value for the input if you don't want to initialize on your controller.
direction: Whether to increment or decrement the values. This one currently accepts a string "decrement" to subtract. If you have no direction set or any other value set in the attribute, it will increment.
So, you would use it like this:
<unary-input button-text="Subtract One" model="user.val" direction="decrement" initial-val="10"></unary-input>
And the directive itself looks like this:
.directive('unaryInput', function(){
return {
restrict: 'EA',
scope: {
model: "=",
txt: '#buttonText'
},
template: '<input type="text" ng-model="model" /><button>{{txt}}</button>',
link: function(scope, element, attrs) {
if(angular.isDefined(attrs.initialVal)) {
scope.model = attrs.initialVal;
}
element.on('click', function() {
if (attrs.direction === 'decrement') {
scope.$apply(scope.model--);
} else {
scope.$apply(scope.model++);
}
});
}
};
});
Browsing around I could find a solution doing the way you said in the comments (two buttons one incrementing and another decrementing) thanks a lot for the help! and here's the final version.
app.directive('unaryInput', function(){
return {
restrict: 'EA',
scope: {
model: "="
},
template: '<input type="text" ng-model="model" /><button ng-click="decrement()">-</button><button ng-click="increment()">+</button>',
link: function(scope, element) {
scope.increment = function() {
scope.model++;
}
scope.decrement = function() {
scope.model--;
}
}
};
});
I'm writting a custom directive to bind a html select element to an unordered list.
It works just fine with static select elements.
However, I couldn't make it work with dynamic selects. Is there a way to my directive retrieve the options generated by ng-options or ng-repeat directives?
I have an example here: http://jsfiddle.net/fjbyq/3/
angular.module('myApp', [])
.directive('taskFilterChoices', function() {
return {
transclude: true,
replace: true,
template: '<div class="listToggleBox listType" data-ng-transclude><ul class="choicefilterList"><li data-ng-repeat="option in options"> {{ option.text }} </li> </ul> </div>',
scope: {},
link: function(scope, element, attrs) {
var _select = element.find('select'),
_selectOptions = [];
_select.hide();
_select.find('option').each(function(i, option) {
var _optionObj = {
value: option.value,
text: option.innerText
};
_selectOptions.push(_optionObj);
});
scope.options = _selectOptions;
}
};
})
.directive('taskFilterLink', function() {
return {
restrict: 'C',
link: function(scope, element) {
$(element).on('click', function(e){
e.preventDefault();
var _select = $(this).closest('.listToggleBox').find('select');
_select.val($(this).data('value')).triggerHandler('change');
});
}
};
});
Thanks