I have a multiple select box with a model attached to it. Here is a JSFiddle of my basic setup.
As you can see, everything works as it should - values that are in my tags dataset automatically appear in my multiple select box (which has been made into a 'tag box' using jQuery Chosen).
The problem arises when I change the source of my tags data from being hardcoded into the JS, to being retrieved via an API call.
If I replace everything inside myController with the below code, the correct data is still send to the view, but it no longer automatically fills/selects the items in the multiple select.
var path = 'http://example.com/api/tags';
$http.get(path).success(function(data) {
$scope.tags = data;
$scope.createForm = {};
$scope.createForm.tags = [];
for(var i = 0; i < $scope.tags.length; i++)
{
$scope.createForm.tags[i] = $scope.tags[i].id;
}
});
The data I am retrieving from the API call is in the exact same format as I had for the hardcoded value. What could the problem be? I want the behaviour to be the same whether the tags data is being hardcoded or loaded form an external source.
Once chosen is initialised, you have to trigger the update whenever the model is changed:
$(elem).trigger("chosen:updated");
And watch for any changes to your model, inside the directive:
myApp.directive("ngChosen", function ($timeout) {
return {
require: 'ngModel',
restrict: 'A',
link: function (scope, elem, attrs, ngModel) {
scope.$watch(function () {
return ngModel.$modelValue;
}, function (newValue) {
$timeout(function () {
// TRIGGER CHOSEN UPDATE
$(elem).trigger("chosen:updated");
});
}, true);
// INIT CHOSEN
$timeout(function () {
$(elem).chosen({
width: "240px"
});
});
}
};
});
DEMO
Related
Is there anyway to trigger opening the match results on a typeahead input text box from the controller?
use case:
user goes to https://example.com/search/searchText
controller of page sets the input text to "searchText" (ng-model) on initialization
trigger showing the typeahead results from the controller
I can only seem to get the typeahead results, obviously, while typing in the input text box.
I got it to work in a couple ways, but both require changes to ui-bootstrap. I suppose I could create a pull request but not sure if my particular use case is a common one.
1) Custom directive and calling UibTypeaheadController.scheduleSearchWithTimeout method on focus of input element.
Directive:
.directive("showSearchResultsOnFocus", function($stateParams) {
return {
require: ['uibTypeahead', 'ngModel'],
link: function (scope, element, attr, ctrls) {
var typeaheadCtrl = ctrls[0];
var modelCtrl = ctrls[1];
element.bind('focus', function () {
if (!$stateParams.search || !modelCtrl.$viewValue) return;
typeaheadCtrl.exportScheduleSearchWithTimeout(modelCtrl.$viewValue);
});
}
}
Update to ui-bootstrap:
this.exportScheduleSearchWithTimeout = function(inputValue) {
return scheduleSearchWithTimeout(inputValue);
};
Bad: Requires making the method public on controller. Only method available is the init method and scope is isolated. Not meant to call from outside controller.
2) Add new typeahead attribute to allow setting default value and show results on focus:
Update to ui-bootstrap:
var isAllowedDefaultOnFocus = originalScope.$eval(attrs.typeaheadAllowDefaultOnFocus) !== false;
originalScope.$watch(attrs.typeaheadAllowedDefaultOnFocus, function (newVal) {
isAllowedDefaultOnFocus = newVal !== false;
});
element.bind('focus', function (evt) {
hasFocus = true;
// this was line before: if (minLength === 0 && !modelCtrl.$viewValue) {
if ((minLength === 0 && !modelCtrl.$viewValue) || isAllowedDefaultOnFocus) {
$timeout(function() {
getMatchesAsync(modelCtrl.$viewValue, evt);
}, 0);
}
});
Bad: Pull Request to ui-bootstrap but change perhaps not a common use feature. Submitted a PR here: https://github.com/angular-ui/bootstrap/pull/6353 Not sure if will be merged or not but using fork until then.
Any other suggestions?
Versions
Angular: 1.5.8, UIBS: 2.2.0, Bootstrap: 3.3.7
Is it feasible to create a directive (canUpdate) which will enable me to enable/disable elements of my angular/web api application depending on results from a service that will test users permissions for a given user group or comma separated list of groups maybe.
My thinking is:
<div><button can-update="customerMgmt">Edit customer detail</button></div>
and my directive can perform the call to check this user is part of customerMgmt group and enable/disable appropriately.
However I am struggling to visualize/understand what my directives' template would look like.
If you was to write a directive that would perform this type of operation what would your directives' html template look like, as I'd want this to be applicable to any element, text input, button, anchor, label etc... i'd basically be saying if the user isn't in the group(s) specified on the attribute then disable/don't allow text entry/clicking etc...
So I've wrote the following that "appears" to be performing as I expect for now, need to test more next week but it seems to be enabling/disabling dependent on what my permissionsService.permissionsForObject method returns(which goes off to webApi controller).... Does this make sense to you?
(function () {
'use strict';
angular.module('blocks.permissions').directive('canWrite', canWriteDirective);
function canWriteDirective() {
return {
//scope: {},
restict: "A",
controller: CanWriteController,
controllerAs: "vm",
bindToController: false,
link: function (scope, iElement, iAttrs, controller) {
controller.canWrite(iAttrs.canWrite);
}
}
};
CanWriteController.$inject = ["permissionsService"];
function CanWriteController(permissionService) {
var vm = this;
vm.canWrite = canWrite;
vm.canUpdate = false;
function canWrite(group) {
permissionService.permissionsForObject(group).then(function (result) {
vm.canUpdate = true;
}).catch(function (result) {
vm.canUpdate = false;
});
}
}
})();
Using AngularJS and SmartTable...
I have a Persistent State which works fine when filters are applied within the table.
I then added a custom search field which searches all columns.
This also works.
However the filter this applied is not held by my persistent state directive.
I believe its a scope issue but cannot seem to line the two up to meet.
Here is a Plunker
http://plnkr.co/edit/2qt7f6NxKH2blJ5GudNl?p=preview
Is my scope written incorrectly?
Here's my directive for the custom search
// Text Search for all columns in stTable
// USAGE: <input type="text" ng-model="queryAllColumns"/>
// There HAS TO BE a way to implement this into the stPersist
.directive('searchWatchModel',function(){
return {
require:'^stTable',
scope:{
searchWatchModel:'='
},
link:function(scope, ele, attr, ctrl){
var table=ctrl;
scope.$watch('searchWatchModel',function(val){
ctrl.search(val);
});
}
};
})
And here is my Persistent Table directive
// Create a Persistant Table Display
// Allows you to close the browser and return with filters still applied
// USAGE: on the st-able ADD: st-persist="myTable" <--myTable can be anything
.directive('stPersist', function () {
return {
require: '^stTable',
link: function (scope, element, attr, ctrl) {
var nameSpace = attr.stPersist;
//save the table state every time it changes
scope.$watch(function () {
return ctrl.tableState();
}, function (newValue, oldValue) {
if (newValue !== oldValue) {
localStorage.setItem(nameSpace, JSON.stringify(newValue));
}
}, true);
//fetch the table state when the directive is loaded
if (localStorage.getItem(nameSpace)) {
var savedState = JSON.parse(localStorage.getItem(nameSpace));
var tableState = ctrl.tableState();
angular.extend(tableState, savedState);
ctrl.pipe();
}
}
};
})
I believe there is already a "global search" available with smart-table.
<input st-search placeholder="global search" class="input-sm form-control" type="search"/>
so why do you have to create your own custom search ? maybe i'm missing something.
By the way, you should add a throttle / delay to your stPersist Directive to avoid unnecessary setItem on localStorage.
I have the following directive:
app.directive('scMultiselect', [function() {
return {
link: function(scope, element, attrs) {
element = $(element[0]);
element.multiselect({
enableFiltering: true,
// Replicate the native functionality on the elements so
// that Angular can handle the changes for us
onChange: function(optionElement, checked) {
optionElement.prop('selected', false);
if (checked)
optionElement.prop('selected', true);
element.change();
}
});
scope.$watch(function () {
return element[0].length;
}, function () {
element.multiselect('rebuild');
});
scope.$watch(attrs.ngModel, function() {
element.multiselect('refresh');
});
}
};
}]);
And the following element in my partial:
<select
id="level_teachers"
class="multiselect col-sm-10"
multiple="multiple"
ng-model="level.teachers"
ng-options="teacher.id as teacher.name for teacher in teachers"
sc-multiselect>
</select>
The bootstrap-multiselect control initializes and displays correctly, however when I select entries in it, my model (level.teachers) remains empty.
Had same problem and this worked for me :
First you add ngModel as 4th parameter of link function. Its very useful - more about it here: https://docs.angularjs.org/api/ng/type/ngModel.NgModelController
Then you basically have to add/delete 'by hand' the option in onChange method to/from your ngModel.
ngModel.$setViewValue() updates the value, ngModel.$render and scope.$apply() are making it visible and spread new model further :)
If you have only single selection then its much easier - less code because of no array control - just use $setViewValue(), $render() and $apply().
app.directive('scMultiselect', function() {
return {
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
element = $(element[0]);
element.multiselect({
enableFiltering: true,
onChange: function(optionElement, checked) {
optionElement.prop('selected', false);
var modelValue = ngModel.$modelValue; // current model value - array of selected items
var optionText = optionElement[0].text; // text of current option
var optionIndex = modelValue.indexOf(optionText);
if (checked) {
if ( optionIndex == -1) { // current option value is not in model - add it
modelValue.push(optionText)
}
optionElement.prop('selected', true);
} else if ( optionIndex > -1 ) { // if it is - delete it
modelValue.splice(optionIndex,1);
}
ngModel.$setViewValue(modelValue);
ngModel.$render();
scope.$apply();
}
});
scope.$watch(element[0].length, function () {
element.multiselect('rebuild');
});
}
};
});
Hope it will work for you too :)
IT's probably because Bootstrap components aren't built in a way that allow them to be used by Angularjs. They don't really have a way to update themselves after you instantiate them, and more importantly they don't participate in Angularjs's update process. Anyway the good news is someone has taken the initiative to rewrite those bootstrap components in Angularjs so we can use them.
http://angular-ui.github.io/bootstrap/
If you are using Bootstrap 3.x then get the latest. If you're stuck on 2.3.x v0.8 is the last version that supported 2.3.x
I am newbie with AngularJs and ,
I am tring to make a form project with $resource ,
I don't have anyideas to get multiple checkbox value and set it
to model , but there is an error show for my checkbox's value is empty from
service ?
here is my code
<dd ng-repeat="user in User.user" >
<input name="userName[user.userId][]" type="checkbox" ng-model="newProgram.program.managerId"
value="{{user.userId}}" check="model" />
{{user.userUsername}}
</dd>
angular.module('elnApp')
.directive('check', function () {
return {
require: 'ngModel',
scope: { model: '=check', value: '#' },
link: function(scope, elm, attr, ngModelCtrl, $filter){
elm.bind('click', function() {
scope.$watch('newProgram.program.managerId', function (value){
if(value == true){
var checkValue = scope.value;
var brands = [];
brands.push(checkValue);
ngModelCtrl.$setViewValue(brands);
scope.$apply();
}
});
});
}
}
});
I am so confused about that , anyone ideas ?
There may be other problems with your code than the one I'm going to point out, but one mistake I noticed is that you shouldn't be using elm.bind('click', ...) in your link function. The way your code is written, the watch won't be set up until after the element is clicked, but you really want the watch set up right from the beginning.
Just run scope.$watch from inside your link function, with no elm.bind, and you should see some improvement. In other words, your link function should look like:
link: function(scope, elm, attr, ngModelCtrl, $filter) {
scope.$watch('newProgram.program.managerId', function (value) {
if (value == true) {
var checkValue = scope.value;
var brands = [];
brands.push(checkValue);
ngModelCtrl.$setViewValue(brands);
scope.$apply();
}
});
});
I don't fully understand the rest of what you want to do ("get form value to service" really isn't very clear to me -- seeing an example on jsfiddle or plunker would be much easier to understand), so this is all the help I can give you. I hope it's useful.