Model not updating in Angular.js directive for bootstrap-multiselect - angularjs

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

Related

angular 1.3.0 and input[type=string]

We recently updated our application to use the latest angular version, from a version to before 1.3.0 to 1.5.0. Apparently we now bump into a breaking change introduced in 1.3.0:
https://github.com/angular/angular.js/issues/9218
We had a custom directive that enabled us to use the pikaday date picker:
module.directive('pikaday', function () {
return {
require: 'ngModel',
link: function preLink(scope, element, attrs, controller) {
var $element = $(element),
momentFormat = 'DD/MM/YYYY',
year = new Date().getFullYear();
// init datepicker
var picker = new Pikaday(
{
field: document.getElementById(attrs.id),
firstDay: 1,
format: momentFormat,
minDate: new Date((year - 1) + '-01-01'),
maxDate: new Date((year + 1) + '-12-31'),
yearRange: [year - 1, year + 1],
onSelect: function (date) {
controller.$setViewValue(date);
},
defaultDate: scope.$eval(attrs.ngModel), // require: 'ngModel'
setDefaultDate: true
});
// format model values to view
controller.$formatters.unshift(function (modelValue) {
var formatted = (modelValue)
? moment(modelValue).format(momentFormat)
: modelValue;
return formatted;
});
// parse view values to model
controller.$parsers.unshift(function (viewValue) {
if (viewValue instanceof Date) {
return viewValue;
}
else {
return moment(viewValue, momentFormat).toDate();
}
});
}
};
})
This used to work fine, but now after binding a form that has this control, my scope value is suddenly changed from a Date object to a string (without ever interacting with the control!) The funny thing is that this happens without the formatter or parsers ever being called. So it looks like angular just decides to change the scope value just because it is being bound a an input of type "text", even if the value in the input is never touched.
I dont want to use input[type=text] because i dont want the browser to force its own handling of dates.
If my formatter/parser would be called i would know how to work around this, but this has me puzzled.
I could just display a date in a span and have a button the user could click to spawn the pikaday plugin, but i would prefer the behavior to stay as is ...
Have you seen this workaround from https://github.com/angular-ui/bootstrap/issues/2659 ?
All you have to do is add a directive:
directive('datepickerPopup', function (){
return {
restrict: 'EAC',
require: 'ngModel',
link: function(scope, element, attr, controller) {
//remove the default formatter from the input directive to prevent conflict
controller.$formatters.shift();
}
}
})
Work around removes the not so nice working formatter introduced by the 1.3.0 version and thus resolves the issue with it.
This fix should make it as it is widely thanked in the github thread.

AngularJS SmartTable persistent state not held with custom search (scope issue?)

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.

In angularjs ui-bootstrap, how can I set initial option in bootstrap-formhelpers-states, and avoid '? undefined:undefined ?' option?

I'm trying to resolve an old angularjs 'issue' which is actually expected behavior: 'Adding ng-model to predefined Select element adds undefined option' (https://github.com/angular/angular.js/issues/1019).
I'm working with ng 1.4.9, ui-bootstrap, ui-router and am trying to use 'bootstrap-formhelpers-states' for a State select list. I do have jQuery in the project in order to support FullCalendar, which I have an ng directive for, but I'm trying not to use it for anything else.
I can get the States list to work and without having to include any bootstrap js, but I'm not getting the binding to Model to work correctly on init, which is something many people have trouble with.
From this question (Why angularJS´s ui-view breaks Bootstrap Formhelper) I got a directive for Countries and adapted it for States:
angular.module("myApp").directive('statepicker', function ($parse) {
return {
restrict: 'A', // It is an attribute
require: '?ngModel', // It uses ng-model binding
scope: {
ngModel: '='
},
link: function (scope, elem, attrs, ctrl) {
// Add the required classes
elem.addClass('bfh-states');
// First we initialize the selec with the desired options
elem.select({
filter: (elem.attr('data-filter') == 'true') ? true : false
}).on('change', function() {
// Listen for the change event and update the bound model
return scope.$apply(function () {
return scope.ngModel = elem.val();
});
});
scope.$watch('ngModel', function (newVal, oldVal) {
if (newVal != oldVal) {
elem.val(newVal);
});
// Initialize the states with the desired options
return elem.bfhstates({
flags: (elem.attr('data-flags') == 'true') ? true : false,
country: 'US',
state: scope.ngModel || 'CA',
blank: false
});
}
};
});
So if i don't add 'ng-model' the the 'select' element, it initializes OK with the value from the directive options (CA), but with the model binding, it doesn't; something to do with it not being a valid option in the list (but it is). The problem is when I'm editing an exiting address, I want the list to be set from a variable model value, but when set via the initial directive options, isn't changeable.
HTML:
<select statepicker id="stateList" name="state" class="form-control" ng-model="state" ng-init="initState()"></select>
<button ng-click="setState('NY')">Set State</button>
With the model binding, I can use a button and a watcher to set the value after things load, but trying to do so via 'ng-init' just doesn't work.
Controller functions:
$scope.state = 'IL';
$scope.initState = function() {
$scope.setState('AZ');
};
$scope.setState = function(val) {
$scope.state = val;
};

Combining ng-grid and ui-select2: unable to select a value

In the editableCellTemplate of an ng-grid, I wanted to use a more user-friendly dropdown, and I found ui-select2 to be a good one.
The thing is however, that any click on this component, when it is used inside the ng-grid, results in the cell juming back to non-editable mode.
This way I can't select another value using the mouse (I can using the arrows on my keyboard).
When I put the dropdown in the cellTemplate, it works, but it should also work in the *editable*CellTemplate.
Some code to see what I mean can be found here.
http://plnkr.co/edit/taPmlwLxZrF10jwwb1FX?p=preview
Does anyone know why that happens, and what could be done to work around it?
I updated the plunker with the solution (last grid shown).
http://plnkr.co/edit/taPmlwLxZrF10jwwb1FX?p=preview
The idea is that you should 'talk to ng-grid' exactly like the other editablCellTemplates do. These 'rules' aren't well defined in the documentation, but they could be deduced by looking at ng-grid's source code for the ng-input directive.
Basically, your own component should respond to the ngGridEventStartCellEdit event by focusing your editor element, and the most important thing is: your component MUST emit the ngGridEventEndCellEdit event only when the cell loses focus (or when you want the editor to disappear, like maybe when pressing enter or something).
So for this specific case I created my own directive that adds the necessary behaviour to a ui-select2 element, but I imagine you could understand what you'd have to do for your own specific case.
So, as an example, here is my ui-select2 specific directive:
app.directive(
'uiSelect2EditableCellTemplate',
[
function() {
return {
restrict: "A",
link: function ( scope, elm, attrs ) {
//make sure the id is set, so we can focus the ui-select2 by ID later on (because its ID will be generated from our id if we have one)
elm.attr( "id", "input-" + scope.col.index + "-" + scope.row.rowIndex );
elm.on( 'click', function( evt ) {
evt.stopPropagation();
} );
elm.on( 'mousedown', function( evt ) {
evt.stopPropagation();
} );
//select2 has its own blur event !
elm.on( 'select2-blur',
function ( event ) {
scope.$emit( 'ngGridEventEndCellEdit' );
}
);
scope.$on( 'ngGridEventStartCellEdit', function () {
//Event is fired BEFORE the new elements are part of the DOM, so try to set the focus after a timeout
setTimeout( function () {
$( "#s2id_" + elm[0].id ).select2( 'open' );
}, 10 );
} );
}
};
}
]
);
and my own editableCellTemplate would have to look something like this:
$scope.cellTemplateDropdownUiSelect3 =
'<select ui-select2-editable-cell-template ui-select2 ng-model="COL_FIELD" style="width: 90%" >' +
'<option ng-repeat="(fkid, fkrow) in fkvalues_country" value="{{fkid}}" ng-selected="COL_FIELD == fkid" >{{fkrow}} ({{fkid}})</option>' +
'</select>' ;
A little bit of 'official' information can be found here: https://github.com/angular-ui/ng-grid/blob/master/CHANGELOG.md#editing-cells
the template for datepicker
<input type="text" datepicker ng-model="COL_FIELD"/>
and the angular directive would look like this.
app.directive('datepicker',
function () {
return {
restrict: 'A',
require: '?ngModel',
scope: {},
link: function (scope, element, attrs, ngModel) {
if (!ngModel) return;
var optionsObj = {};
optionsObj.dateFormat = 'dd/mm/yy';
optionsObj.onClose = function(){
scope.$emit( 'ngGridEventEndCellEdit' );
}
var updateModel = function (date) {
scope.$apply(function () {
ngModel.$setViewValue(date);
});
};
optionsObj.onSelect = function (date, picker) {
updateModel(date);
};
ngModel.$render = function () {
element.datepicker('setDate', ngModel.$viewValue || '');
};
scope.$on('ngGridEventStartCellEdit', function (obj) {
element.datepicker( "show" );
});
scope.$on('ngGridEventStartCellEdit', function (obj) {
element.datepicker( "show" );
});
element.datepicker(optionsObj);
}
};
});

checkbox set model value unchanging

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.

Resources