How to prevent property change on Angular JS - javascript

I'm using AngularJs on my project and i've a property on my viewModel that is connected to a dropdown (< select >)
that dropdown have a empty value witch is selected by default, what i want is to prevent user to select that empty value after he select some other value.
ive started to look to $watch, but i dont know if there is some way to cancel the "changing oof that property", some thing like this:
$scope.$watch('myProp', function (newVal, oldVal, scope) {
if (newVal) { scope.preventDefault(); }
}
any idea, this is the base idea, on a more advanced development i need to ask users for a confirmation.
any ideas?

what i want is to prevent user to select that empty value after he select some other value
This should happen automatically for you, as long as you don't assign the ng-model property a value initially. So using the <select> shown below, don't initialize $scope.selected_year in your controller:
<select ng-model="selected_year" ng-options="year for year in years"></select>
When the list displays initially, Angular will have added an option like this to the HTML, since $scope.selected_year is not currently set to a valid option/value:
<option value="?" selected="selected"></option>
After selecting a valid choice, that option will magically disappear, so the user will not be able to select it again. Try it in this fiddle.
If the ng-model property already has a valid value assigned when the select list is first displayed, then you can assign a controller function to the undocumented ng-change parameter:
<select ... ng-change="preventUserFromDoingXzy()">
Inside function preventUserFromDoingXzy() you can do what you need to do to control what the user can select, or modify the model.

You can just add ng-required to the select.
If there is no initial value to the model then an empty option will be added and on change to a valid value it will remove the empty option
EDITED jsFiddle to revert to previous value and to include the ng-change directive.
From the docs:
The expression is not evaluated when the value change is coming from the model.
This is useful in not interfering with change listeners and creating an infinite loop when reverting the old value in the $apply function
Controller
$scope.options = [{value: 'abc'},{value: 'def'}];
var confirmDialog = function(newVal, yes, no) {
// obviously not a good way to ask for the user to confirm
// replace this with a non blocking dialog
//the timeout is only for the confirm since it's blocking the angular $digest
setTimeout(function() {
c = confirm('Is it ok? [' + newVal.value + ']');
if(c) {
yes();
}
else {
no();
}
}, 0);
};
//Asking for confirmation example
function Ctrl($scope) {
$scope.options = [{value: 'abc'},{value: 'def'}];
$scope.select = undefined;
var oldSelect = undefined;
$scope.confirmChange = function(select) {
if(oldSelect) {
confirmDialog(select,
function() {
oldSelect = select;
},
function() {
$scope.$apply(function() {$scope.select = oldSelect;});
});
}
else {
oldSelect = select;
}
}
}
Template
<div ng-controller="Ctrl">
<select ng-model="select" ng-options="o.value for o in options"
ng-required ng-change="confirmChange(select)">
</select>
</div>

Probably the easiest, cleanest thing to do would be adding an initial option and setting disabled on it:
<option value="?" selected="selected" disabled></option>

Actually, it is easier to remove the empty value. Suppose you have a list of options:
$scope.options = [{value: ''}, {value: 'abc'},{value: 'def'}];
and a select:
<select ng-model="select" ng-options="o.value for o in options"></select>
Then $watch the model:
$scope.$watch('select', function(value) {
if (value && value !== '') {
if ($scope.options[0].value === '') {
$scope.options = $scope.options.slice(1);
}
}
}, true);
See it in action here.
PS Don't forget the objectEquality parameter in the $watch or it won't work!

Related

How does ngModelChange works without providing model name in ngModel directive

How does ngModelChange() works?
(ngModelChange) is the #Output of ngModel directive. It fires when the model changes. You cannot use this event without ngModel directive
but I am not sure, how does(ngModelChange) it works, if I am use ngModelChange() event, even i am not providing model name to ngModel.
<input #gb type="text" pInputText class="ui-widget ui-text" **ngModel**
(ngModelChange)="functionName($event)">
Yes, ngModelChange() work without providing model name to ngModel.
cause of this happen, (ngModelChange) is the #Output of ngModel directive.
when insert some value in input that time emitEvent is become true which is by default false (so it not fire page load on initial time).
_this.updateValueAndValidity({ emitEvent: false });
you can find at \#angular\forms\esm5\forms.js ► line no 3850
If emitEvent is true, this
change will cause a valueChanges event on the FormControl to be emitted. This defaults
to true (as it falls through to updateValueAndValidity).
If emitViewToModelChange is true, an ngModelChange event will be fired to update the
model. This is the default behavior if emitViewToModelChange is not specified.
If emitModelToViewChange is true, the view will be notified about the new value
via an onChange event.
now question is that why get same value in $event which is inserted in input instead of ture, that cause
FormControl.prototype.setValue = /**
function (value, options) {
var _this = this;
if (options === void 0) { options = {}; }
(/** #type {?} */ (this)).value = this._pendingValue = value;
if (this._onChange.length && options.emitModelToViewChange !== false) {
this._onChange.forEach(function (changeFn) { return changeFn(_this.value, options.emitViewToModelChange !== false); });
}
this.updateValueAndValidity(options);
};
same file line no 3911 to 3919
In the Source code ngModelChange is just an event emitter.
#Output('ngModelChange') update = new EventEmitter();
It fires when the viewToModelUpdate function is executed.
viewToModelUpdate(newValue: any): void {
this.viewModel = newValue;
this.update.emit(newValue);
}
ngModel can be anything and does not have a direct link to anything else. In the code it is declared and it is only used in a function called ngOnChanges
#Input('ngModel') model: any;
ngOnChanges(changes: SimpleChanges) {
this._checkForErrors();
if (!this._registered) this._setUpControl();
if ('isDisabled' in changes) {
this._updateDisabled(changes);
}
if (isPropertyUpdated(changes, this.viewModel)) {
this._updateValue(this.model);
this.viewModel = this.model;
}
}
I could be wrong here but it looks to me that ngModel is not the single source of truth but this.viewModel seems to be, because of this ngModel does not need a value for ngModelChange to work as it opporates seporatetly from the ngModel value.
Hope this helps.
you try it without ngModel
<select (change)="changed($event)">
<option *ngFor="let data of allData" [value]="data.id">
{{data.name}}
</option>
</select>
changed(e){
//event comes as parameter and then find data manually
//by using e.target.data
}
OR BY ID
<inputtype="text" #byid (change)="onChange(byid.value)" />
onChange(title:string){
alert(title);
}
You can try by passing id into input

Angularjs - set limit on user choice in a select field

I'm trying to build a method to limit the user choice in a couple of select fields. Below is a sample and here is a jsfiddle - http://jsfiddle.net/HB7LU/18414/
HTML:
<div ng-controller="MyCtrl">
<select size="5" id="selectpicker1" multiple ng-model="params.monthly" ng-change="selectionChanged(params.monthly)">
<option ng-repeat="month in newMonthlyArray">{{month}}</option>
</select>
</div>
Controller:
var myApp = angular.module('myApp', []);
//myApp.directive('myDirective', function() {});
//myApp.factory('myService', function() {});
function MyCtrl($scope) {
$scope.name = 'Superhero';
//console.log('YES');
$scope.newMonthlyArray = ["2015-Jan", "2015-Feb", "2015-Mar", "2015-Apr"];
$scope.selectionChanged = function (data) {
if (data.length > 2) {
alert('You can choose 2 months');
}
}
}
The part I'm having issues with is to remove the last chosen option and update the $scope variable. I can do with JQuery but didn't want to create a hybrid solution.
Any ideas will be greatly appreciated!
You don't really have to do that much to remove the extra selected elements:
if(data.length > 2){
data.splice(2);
//alert('You can choose 2 months');
}
Basically, that's all you need: data is your ng-model="params.monthly"
and since this model is bound to the select element, Angular will do the rest to update it:
http://jsfiddle.net/HB7LU/18420/
EDIT: As #Metallica suggested, not in all cases the select element is updated by Angular.
The only way to provide visual feedback, is to manually update the DOM:
if(data.length > 2) {
data.splice(2);
var selected = select.querySelectorAll('option:checked');
angular.forEach(selected, function(s) {
if(data.indexOf(s.value) < 0) s.selected = false;
});
}
In the updated example select is $element[0].querySelector('select')
the select element in this controller.
The additional code is to:
get all selected option elements and
iterate over the elements and check if they are present in the model data and update the selected property accordingly
Updated fiddle:
http://jsfiddle.net/HB7LU/18422/
You can get a reference to the extra selected items in your select element and deselect those items. In your selectionChanged method, iterate over options in your select element, deselect invalid selections, and update your model (data which is a reference to params.monthly).
angular.forEach(document.getElementById("selectpicker1").options, function(item) {
if (data.length > 2 && item.selected) {
data.splice(2);
item.selected = false;
}
});
You can use $watch service to make things easier and delete your own watch method (selectionChanged):
$scope.$watch(function() {
// In this function, return whatever object/value you would like to observe.
// This function is invoked whenever there is a change to the specified value/obj
return document.getElementById("selectpicker1").options;
}, function (items) {
// The argument of this function holds the new value of what you return in the
// first function. Feel free to name it whatever you wish
angular.forEach(items, function(item) {
var data = $scope.params.monthly;
if (data.length > 2 && item.selected) {
data.splice(2);
item.selected = false;
alert('You can choose 2 months');
}
});
});
Usually you need to clean/unregister your watch function, but in this case, you don't need to, because no other method/function relies on the value of your select element.
Use ng-options. See my fork
<select (...) ng-options="month for month in newMonthlyArray">
And in the controller, remove the last selected (using lodash):
if(data.length > 2){
alert('You can choose 2 months');
data = _.pull(data, _.last(data))
}
Add this to your controller:
$scope.onSelect = function() {
var selected = this.month;
$scope.newMonthlyArray.splice($scope.newMonthlyArray.indexOf(selected), 1);
};
and this to your tag:
<option ng-click="onSelect()" ...

CasperJS fails to set select list values

I've got a select list that looks like this:
<select id="ListingType" name="Criteria/ListingTypeID" onchange="Search.toggleListingType(this);">
<option selected="selected" value="1">RH1</option>
<option value="2">BC4</option>
<option value="3">RR3</option>
<option value="4">RH2</option>
<option value="5">RE0</option>
</select>
I'm trying to set the value to 3. I've unsuccessfully tried 3 ways to do this.
1.
this.evaluate(function(value) {
document.querySelector('select#ListingType').value = value;
return true;
}, '3');
// insure the onChange JS is run
this.evaluate(function() {
var element = document.querySelector('select#ListingType');
var evt = document.createEvent('HTMLEvents');
evt.initEvent('change', false, true);
element.dispatchEvent(evt);
});
This appears to work (throws no errors), but the value doesn't get changed.
2.
this.fillSelectors('select#ListingType', {
'select[name="Criteria/ListingTypeID"]' : "3"
}, true);
This throws the error message: ``CasperError: Errors encountered while filling form: no field matching css selector "select[name="Criteria/ListingTypeID"]" in form''
What's wrong with my selector?
3.
// In desperation...
this.sendKeys('select#ListingType', 'RR3');
This also seems to work (no errors), but again the value is unchanged.
Just for completeness, I've tried sending the "change" event after each of the variations. I also dump out the attributes of the selected element after each, and it never shows the value as being set.
Note the page that this selector is on has a ton of JS code on it, and this <select> element is not wrapped in a <form> element.
I'm sure I'm making some really stupid mistake, but after hacking on this code for the last several days, I've not made any progress. What's a working way to do this?
You can use slimerJS (it opens firefox) to easily check a change in value.
this.fillSelectors('form#id_form', {
'select[name="Criteria/ListingTypeID"]' : "3"
}, false);
this.wait(6000,function(){
this.echo("i saw my new value");
});
And if you want to fill the option by its text and not value (i find it easier): use this function :
casper.fillSelectOptionByText = function (selectSelector,text){
this.evaluate(function(sel,setByText) {
$(sel + " > option").each(function() {
if($(this).text() === setByText) {
$(this).attr('selected', 'selected');
}
});
},selectSelector,text);
};
And in your test :
this.fillSelectOptionByText("select[name='year of birth']","1991");
The page has to have jquery of course, or you inject it.

Select2, when no option matches, "other" should appear

With select2 dropdown, how do I get a default option to appear if no options match the user's typed input?
$("something").select2({
formatNoMatches: function(term) {
//return a search choice
}
});
I haven't been able to find anything that really matches this desired functionality within the select2 documentation or Stack Overflow.
Edit
I'm getting closer with this
$("something").select2({
formatNoMatches: function(term) {
return "<div class='select2-result-label'><span class='select2-match'></span>Other</div>"
}
});
But this is pretty hacky off the bat, and also isn't clickable.
To complement on #vararis's answer:
Select2 attached to a <select> element does not allow for custom createSearchChoice nor query functions, hence we will need to manually add an option element (I'll add it as the last option of the element so we can easily .pop it out of the results set):
<select>
...
<option value="0">Other</option>
</select>
Then pass a custom matcher function so that this "Other" option is always matched.
NOTE: Select2 3.4+ uses a different default matcher than the one currently shown in the documentation, this new one has a call to the stripDiacritics function so that a matches á for instance. Therefore I'll apply the default matcher that comes with the Select2 version included in the page to apply its default matching algorithm to any option that's not the "Other" option:
matcher: function(term, text) {
return text==='Other' || $.fn.select2.defaults.matcher.apply(this, arguments);
},
Finally, we need to remove the "Other" option from the results set when there's any result besides the "Other" result (which is initially always in the results set):
sortResults: function(results) {
if (results.length > 1) results.pop();
return results;
}
Demo
I solved it by changing the matcher.
$('#activity_log_industry_name').select2
matcher: (term, text) ->
if text=="Other"
return true
text.toUpperCase().indexOf(term.toUpperCase())>=0
(this is in coffeescript.) The only problem is that "Other" will pop up for any search -- but this can be easily solved by modifying the sortResults function.
In version 4+ of Select2, the matcher should be passed like this:
$("something").select2({
matcher: function(params, data) {
if (data.id === "0") { // <-- option value of "Other", always appears in results
return data;
} else {
return $.fn.select2.defaults.defaults.matcher.apply(this, arguments);
}
},
});
the "Other" option should be appended to the option list like this:
<select>
...
<option value="0">Other</option>
</select>
To customize formatNoMatches use following code in your view
ui-select2="{formatNoMatches:'No Results Found'}"
Try this answer on stackoverflow
createSearchChoice: function (term) {
return { id: term, text: term };
}
Use following code to display the message as 'Other' when no matches found
$("select").select2({
formatNoMatches : function(term) {
return "Other";
}
});
try it, this work in newer version ( Select2 4.0.3 )
without add a new variable.
>>>> Fiddle here <<<<
first you need to download javascript name "github/alasql"
to search like query in data
<select id="something">
...
<option value="other">Other</option> <!-- or put this in "dataItemsList" for dynamic option -->
</select>
then in your javascript
// other than this array we will give "other" option
var ajax_search_array = ['your', 'item', 'for', 'search'];
$("#something").select2({
placeholder: "Choose your something",
//data: dataItemsList, //your dataItemsList for dynamic option
//...
//...
tags: true,
createTag: function (tag) {
// here we add %search% to search like in mysql
var name_search = "%"+tag.term.toString().toLowerCase()+"%";
// alasql search
var result = alasql('SELECT COLUMN * FROM [?] WHERE [0] LIKE ?',[ajax_search_array, name_search]);
// if no result found then show "other"
// and prevent other to appear when type "other"
if(result==false && tag.term != "other"){
return {
id: "other",
text: "Other"
};
}
});
hope this work.

Using JavaScript or jQuery, how do I check if select box matches original value?

Just wondering if there is any way to check if the value of a select box drop-down matches the original value at the time of page load (when the value was set using selected = "yes") ?
I guess I could use PHP to create the original values as JavaScript variables and check against them, but there are a few select boxes and I'm trying to keep the code as concise as possible!
That's not too hard at all. This will keep track of the value for each select on the page:
$(document).ready(function() {
$("select").each(function() {
var originalValue = $(this).val();
$(this).change(function() {
if ($(this).val() != originalValue)
$(this).addClass('value-has-changed-since-page-loaded');
else
$(this).removeClass('value-has-changed-since-page-loaded');
});
});
});
This will apply a new class value-has-changed-since-page-loaded (which presumably you'd rename to something more relevant) to any select box whose value is different than it was when the page loaded.
You can exploit that class whenever it is you're interested in seeing that the value has changed.
$(document).ready(function() {
var initialSelectValue = $('#yourselect').val();
// call this function when you want to check the value
// returns true if value match, false otherwise
function checkSelectValue() {
return $('#yourselect').val() === initialSelectValue;
}
});
PS. You should use selected="selected" not selected="yes".
On page load, create an array with the initial value of each select box indexed by name:
var select_values = [];
$(document).ready(function() {
$("select").each(function() {
select_values[$(this).attr('name')] = $(this).val();
});
});
later when you need to check if a value has changed:
function has_select_changed(name) {
return $("select[name="+name+"]").val() != select_values[name];
}
First, a snippet:
$('select').each(
function(){
if( this.options[ this.selectedIndex ].getAttribute('selected') === null ){
alert( this.name +' has changed!')
}
});
Now the explanation:
Assuming selectElement is a reference to a <select /> elementYou can check which option is selected using
selectElement.selectedIndex
To get the <option /> element which is currently selected, use
selectElement.options[ selectElement.selectedIndex ]
Now when you know which option element is selected you can find out if this element has the selected='selected' attribute (as in the source code, it doesn't change - this is not the same as .selected propery of a DOM node, which is true for the currently selected option element and changes when the selection is changed)

Categories

Resources