Angularjs - set limit on user choice in a select field - javascript

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()" ...

Related

Isotope v2 Grid - Multiple Filters - Hide empty filters

My current isotope grid has two dropdown filters that sort the grid items. The first filter is menu type, and the second filter is drink type. Each menu type however does not contain all drink types so when some filter configurations are chosen, no results are showed, which is correct. But i would like to stop this from happening by when the user selects the first filter, the second filter hides the empty types.
Working Codepen of current filters: https://codepen.io/whitinggg/pen/zYGEaNb
This was my old filter code:
// Select Filters
jQuery(function ($) {
var $grid = $('.grid');
var $selects = $('div#filterGroup select').change(function() {
var selector = $selects.get().map(function(el) { // map the select elements ...
return $(el).val(); // ... to an array of values
}).join('') || '*'; // if joined array is empty-string, then default to a single '*'
$grid.isotope({
'filter': selector
});
return false;
});
$grid.imagesLoaded().progress( function() {
$grid.isotope('layout');
});
});
I have tried to change my code to the below from other links around the internet on this topic but havent had any luck getting it to work.
// Select Filters
jQuery(function ($) {
var $grid = $('.grid');
var $selects = $('div#filterGroup select').change(function() {
var selector = $selects.get().map(function(el) { // map the select elements ...
return $(el).val(); // ... to an array of values
}).join('') || '*'; // if joined array is empty-string, then default to a single '*'
$grid.isotope({
'filter': selector
});
return false;
});
//Hide Empty Filters
var DROP = $('div#filterGroup select option:not([data-filter=""])');
// list of all class in html
var strall = ''; $('.grid-item').each(function(el){ strall += $(this).attr('class') });
// remove select if not in strall.. TODO : needs improvement, this is kind a hack
DROP.each(function(el){
var nowfilter = $(this).attr('data-filter').replace('.', ''); // co_kenya
if( strall.indexOf( nowfilter ) == -1 ){
console.log( 'this one is missing ' + nowfilter );
$(this).remove();
}
});
$grid.imagesLoaded().progress( function() {
$grid.isotope('layout');
});
});
Is this possible? Any help much appreciated!
Working codepen
First, add an ID to each drop-down so that we can distinguish them.
<select id="menu-selector" class="filter option-set" data-filter-group="menu">
[...]
<select id="type-selector" class="filter option-set" data-filter-group="categories">
Then, for each drop-down, add a change listener. We'll look at the code for the menu drop-down change listener.
First, get the class filter from the selected drop-down:
$('#menu-selector').change(function() {
var selectedClass = $('#menu-selector option:selected').attr('value');
Then we're going to select all of the grid items matching that type, to see what other classes they have. These other classes will be the available types
var availableTypes = $(`.grid-item${selectedClass}`)
.toArray()
.flatMap(div => Array.from(div.classList.values())) //get all of the classes
.filter(i => !['grid-item', selectedClass.substring(1)].includes(i)); //eliminate useless ones
Last, toggle the disabled property on the other drop-down, enabling only those that are available.
$('#type-selector option')
.each( (i,el) => $(el).prop('disabled', el.value != "" && !availableTypes.includes(el.value.substring(1))));
That should do it. The change handler for the type drop-down is the same but references the opposite drop-down. Check the codepen for details.

How to pass an argument from select options to trigger and filter array observables using foreach binding

In the markup below, I am binding unique data containing a list of Continents: I am also subscribing to the selected value and triggering an event with the continent selected by the user.
<div id="country-collection">
<select data-bind="options: UniqueContinent,
value: SelectedContinent"></select>
</div>
code:
self.CountryData = ko.observableArray([]);
self.SelectedContinent = ko.observable('');
self.UniqueContinent = ko.dependentObservable(function() {
var continent = ko.utils.arrayMap(self.CountryData(),
function(item){
return item.Continent
})
return ko.utils.arrayGetDistinctValues(continent).sort();
});
The below function is fired each time a selection is made:
self.SelectedContinent.subscribe(function (selectedValue) {
// alert(selectedValue);
});
Using the code above, I need to populate the following list with all of the countries based on the default Continent onload or a selected Continent: So if Asia is selected, the only countries displayed are countries in Asia and their respective details.
<div id="country-list">
<ul data-bind= "foreach: CountryData">
<li><span data-bind="text: Country"></span></li>
// More list stuff here (removed for brevity)
</ul>
</div>
I tried this but it only worked if the value is hard coded. I need the Countries to load based on the default value or selected value of the select options:
self.SelectedContinent.subscribe(function (selectedValue) {
// Call this function when changes are made
self.FilteredEntries = ko.computed(function() {
return ko.utils.arrayFilter(self.CountryData(), function(item) {
// I need to use the selected value
return item.Continent === 'SOUTH AMERICA';
});
});
});
You can remove the subscribe function:
// Call this function when changes are made
self.FilteredEntries = ko.computed(function() {
return ko.utils.arrayFilter(self.CountryData(), function(item) {
// I need to use the selected value
return item.Continent === self.SelectedContinent();
});
});
With subscribe you are creating a new computed observable each time the selection changes and the reassigned computed observables are never bound to the DOM.
You can check out the working demo in this fiddle.
You can use the awsome Knockout Projections plugin, available here; knockout-projections.
Defining the filtered array observable is very simple, and the implementation is highly efficient:
var FilteredCountries = self.CountryData().filter(
function(c) { return c.Continent === self.SelectedContinent();
});

How to filter the NG-Grid by dropdown values

I have a drop down menu which get populated from Nggrid Column. The dropdown is a separate control.
I want to filter the NG-grid based on the value selected from drop down value. How do I do this?
you could add an ng-change to your select like this:
select(ng-model="search", ng-options="data in datas", ng-change='myfilter()')
and in your controller:
$scope.myfilter = function() {
$scope.datas = $filter('filter')($scope.datas, {data.YourField: $scope.search});
// or:
// $scope.datas = $scope.datas.filter(function(data) {
// return data.YourField == search || data.YourOther == search;
// }
};

How to prevent property change on Angular JS

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!

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