I have a form with an ng-repeat that loads a set of scheduled days a user has added. Lets say the user has added the first day to the collection and selected Monday from the dropdown. When the user adds a second day to the collection I would like the dropdown to filter out Monday because its already used, however not filter out Monday for the day that Monday was selected for. Does anyone have an example on how to accomplish this?
Edit:
$scope.schedule = {days: []};
Updated from Zacks example jsfiddle
* Updated with the edit to the question *
Here every time a choice is selected, it calls updateAvailability()
<div ng-repeat="schedDay in schedule.days">
<select ng-model="schedDay.day" ng-change="updateAvailability()">
<option ng-repeat="day in days|orderBy:value" ng-disabled="!day.selectable" value="{{day.day}}">{{day.display}}</option>
</select>
</div>
Which goes through and checks if it is already used elsewhere.
$scope.updateAvailability = function() {
var used = $scope.schedule.days.map(function(day) {
return parseInt(day.day || '-1');
});
angular.forEach($scope.days, function(day) {
day.selectable = used.indexOf(day.day) === -1 ? true : false;
});
}
Check out the demo JSFiddle
Related
I followed a description from another answer in order to cancel ng-change on a select dropdown. The code should stop changing year if the user has filled out some numbers in week 53, if the year he switches to does not contain a week 53. This code works, but only the first time. Can anyone explain why? My guess is that it has something to do with changing the wrong scope, but since I cannot pass the scope as a parameter to ng-change I'm really not sure...
This is HTML:
<select data-ng-change="updateYear('{{Year}}');" data-ng-model="Year">
<option value="2012">2012</option>
<option value="2013">2013</option>
...
</select>
This is the controller:
$scope.updateYear = function (oldYear) {
hasValuesInW53($scope.selectedProgram.Uid, function (hasW53Values) {
var has54weeks = ['2015', '2020', '2026', '2032', '2037'];
if ($.inArray(oldYear, has54weeks) > -1 && //old year has 53 weeks
$.inArray($scope.Year, has54weeks) == -1 && //new year does not have 53 weeks
hasW53Values //has values in W53
) {
$scope.Year = oldYear;
}
else {
$scope.updateProgram();
}
});
This is what I do:
Switch 2020 to 2019 -> Works ok, the cancel code is triggered and GUI
is changed back. Swotch 2020 to 2019 again -> Does not work. Cancel
code is still triggered, but it's not reflected in GUI).
I found a dirty workaround in order to solve the problem. As suspected, in the original solution, the wrong scope is referenced, and I could not get the correct one since ng-change does not support passing $event where I can reference the correct scope.
My workaround is adding ng-focus, storing the event, and use this to access the correct scope. Dirty, but works.
HTML:
<select data-ng-model="Year" data-ng-focus="yearFocusCallback($event)" data-ng-change="updateYear('{{Year}}');">
<option value="2012">2012</option>
<option value="2013">2013</option>
...
</select>
CONTROLLER:
$scope.yearChangeEvent = null;
$scope.yearFocusCallback = function ($event) {
$scope.yearChangeEvent = $event;
};
$scope.updateYear = function (oldYear) {
hasValuesInW53($scope.selectedProgram.Uid, function (hasW53Values) {
var has54weeks = ['2015', '2020', '2026', '2032', '2037'];
if ($.inArray(oldYear, has54weeks) > -1 && //old year has 53 weeks
$.inArray($scope.Year, has54weeks) == -1 && //new year does not have 53 weeks
hasW53Values //has values in W53
) {
angular.element($scope.yearChangeEvent.target).scope().Year = oldYear;
}
else {
$scope.updateProgram();
}
});
}
My guess is, its because you are mixing angular and jquery and be ready to get unexpected results whenever you are doing that because angular is data driven and jquery is deals with the DOM elements directly. Try using angular.forEach or array.indexOf instead of $.inArray
I have 3 fields.
1st - option field / ng-repeat over available dates
2nd - option field / based on the date chosen by user, I do ng-repeat over quantity. I have tried all different ways but I can't make it depend on the first option field or other things don't work. Any help would be great! Thanks !!
html:
<div class="containerDiv">
<div>
<select ng-model='date'>
<option ng-repeat="availableDateProduct in product " value='{{i}}'>{{availableDateProduct.dateOfActivity}}
</option>
</select>
</div>
<div ng-repeat="availableDateProduct in product ">
<select>
<option ng-repeat='i in quantityLister(availableDateProduct.quantity)' value='{{i}}'>
{{i}}
</option>
</select>
</div>
<div>
<button>Book</button>
</div>
</div>
js:
app.controller('ProductCtrl', function($scope, ProductsFactory, $stateParams) {
ProductsFactory.fetchByTitle($stateParams.title)
.then(function(product) {
$scope.product = product;
})
$scope.quantityLister = function(num) {
var array = [];
for (var i = 1; i <= num; i++) {
array.push(i)
}
return array;
}
})
data:
var products = [
{
title:'Bowling',
description:'Its fun!',
photoUrl:'https://www.1.jpg',
quantity:12,
price:9,
dateOfActivity: '2017-13-07'
},
...
]
Thanks!!
Angular has a directive built specifically to accomplish this task; ng-options.
First, we define an object in the controller that will hold the values selected from the dropdown:
$scope.reservation = {};
Next, we use ng-options on our dropdowns, and use the ng-model property to accept the value selected. In the first dropdown, we take the array of products, display the dateOfActivity for each product, and save the product object to ng-model when selected. (work from right to left in the ng-options definition).
ng-model="reservation.selectedProduct"
ng-options="product as product.dateOfActivity for product in products"
In our second dropdown, you have defined a function to take a number and spread it into an array. We call this function from reservation.selectedProduct.quantity, and then use this array as the basis for the ng-options:
ng-model="reservation.selectedQuantity"
ng-options="num for num in quantityLister(reservation.selectedProduct.quantity)"
Now we have an object which has the selected values for both dropdowns, we just need to change the quantity in the original array on button press. we also want to clear the selections afterwords, to ensure that the user can't accidentally make a duplicate reservation.
$scope.reserve = function (){
$scope.reservation.selectedProduct.quantity -= $scope.reservation.selectedQuantity;
$scope.reservation = {};
};
Here we use the shorthand -= to subtract the selectedQuantity from the selectedProduct.quantity. since selectedProduct is two way bound, the change to selectedProduct is reflected in the original object in the product array as well. However, The quantityLister function isn't dynamic; if we don't reset $scope.reservation, the second dropdown would hold a now invalid number of available reservations.
I'm working on an app using AngularJS and Bootstrap UI. I've been fumbling my way through using the Typeahead control in Bootstrap UI.
Here's my Plunker
My challenge is I want the user to have the option of choosing an item, but not required to do so. For instance, right now, if you type Test in the text field and press "Enter", Test will be replaced with Alpha. However, I really want to use Test. The only time I want the text to be replaced is when someone chooses the item from the drop down list. My markup looks like the following:
<input type="text" class="form-control" placeholder="Search..."
ng-model="query"
typeahead="result as result.name for result in getResults($viewValue)"
typeahead-template-url="result.html" />
How do I give the user the option of choosing an item, but allow them to still enter their own text?
The issue is that both Enter and Tab confirm the selection of the currently highlighted item and Typeahead automatically selects an item as soon as you start to type.
If you want, you can click off the control to lose focus or hit Esc to exit out of typeahead, but those might be difficult to communicate to your users.
There's an open request in Bootstrap Ui to not auto select / highlight the first item
One solution is to populate the first item with the contents of the query thus far, so tabbing or entering will only confirm selection of the current query:
JavaScript:
angular.module('plunker', ['ui.bootstrap'])
.filter('concat', function() {
return function(input, viewValue) {
if (input && input.length) {
if (input.indexOf(viewValue) === -1) {
input.unshift(viewValue);
}
return input;
} else {
return [];
}};})
HTML:
<input type="text"
ng-model="selected"
typeahead="state for state in states | filter:$viewValue | limitTo:8 | concat:$viewValue"
class="form-control">
Demo in Plunker
I came across this same situation and found no good answers so I implemented it myself in ui-bootstrap Here is the relevant answer. This is probably not the best route to take, but it does get the job done. It makes the first result in the typeahead to be what you're currently typing, so if you tab or enter off of it, it's selected -- you must arrow-down or select another option to get it.
Here is the modified ui-bootstrap-tpls.js file
I added a mustMouseDownToMatch property/attribute to the directive, like:
<input type="text" ng-model="selected" typeahead="item for item in typeaheadOptions | filter:$viewValue" typeahead-arrow-down-to-match="true">
And the javascript:
var mustArrowDownToMatch = originalScope.$eval(attrs.typeaheadArrowDownToMatch) ? originalScope.$eval(attrs.typeaheadArrowDownToMatch) : false;
I also added this function which will put the current text into the first item of the typeahead list, and make it the selected item:
var setFirstResultToViewValue = function (inputValue) {
scope.matches.splice(0, 0, {
id: 0,
label: inputValue,
model: inputValue
});
}
And that is called in the getMatchesAsync call in the typeahead directive:
var getMatchesAsync = function(inputValue) {
// do stuff
$q.when(parserResult.source(originalScope, locals)).then(function(matches) {
// do stuff
if (matches.length > 0) {
// do stuff
}
if (mustArrowDownToMatch) {
setFirstResultToViewValue(inputValue);
scope.activeIdx = 0;
setTypeaheadPosition();
}
// do stuff
};
I'm having an issue using a dropdown that is populated with ng-repeat option values or even when using ng-options.
Basically I'm pulling a list of subsidiaries from the database. I then have a dropdown to choose a company, which in turn should populate the subsidiary dropdown with subsidiaries of the chosen company. Since many of the subsidiaries are of the same company, if I try and pull the the company name in ng-repeat, I get the same company several times. So I have created a custom filter that filters out the companyName and companyID of each company listed only once.
Everything works in the theory that when I change the value of the company dropdown, the correct subsidiaries are listed. However the value shown in the company box is stuck on the first option listed and will not change. If I remove the custom filter and allow it to list all the repeat names, the box displays correctly.
My first thought is to make a separate HTTP call that would just get companies from my companies table, but I would think I want to limit HTTP calls to as few as possible. Plus it would seem that I should be able to accomplish this.
What concept am I not grasping that prevents this from displaying correctly when I use my filter and what should I do to fix this?
thanks
HTML:
<div class="col-sm-5">
<select ng-model ="parentCompany" name="company">
<option ng-repeat="company in companies | uniqueCompanies:'companyName'" value="{{company.id}}" >{{company.name}}</option>
</select>
</div>
<div class="col-sm-5">
<select name="subsidiary">
<option ng-repeat="subsidary in companies" value="{{subsidary.subID}}" ng-hide="$parent.parentCompany !== subsidary.companyID">{{subsidary.subName}}</option>
</select>
</div>
Controller:
getCompanies();
function getCompanies(){
$http.get("get.php?table=getcompanies").success(function(data) {
$scope.companies = data;
});
}
Filter:
.filter("uniqueCompanies", function() {
return function(data, propertyName) {
if (angular.isArray(data) && angular.isString(propertyName)) {
var results = [];
var keys = {};
for (var i = 0; i < data.length; i++) {
var val = data[i][propertyName];
var val2 = data[i]['companyID'];
if (angular.isUndefined(keys[val])) {
keys[val] = true;
results.push({'name':val, 'id':val2});
}
}
return results;
} else {
return data;
}
};
});
Sample Data :
[{"subID":null,"subName":null,"companyID":"1","companyName":"DWG"},
{"subID":null,"subName":null,"companyID":"2","companyName":"Vista"},
{"subID":"1008","subName":"Data Services","companyID":"3","companyName":"Medcare"},
{"subID":"1009","subName":"Companion","companyID":"3","companyName":"Medcare"},
{"subID":"1010","subName":"GBA","companyID":"3","companyName":"Medcare"},
{"subID":"1011","subName":"PGBA","companyID":"3","companyName":"Medcare"},
{"subID":"1013","subName":"Health Plan","companyID":"3","companyName":"Medcare"},
{"subID":"1014","subName":"PAISC","companyID":"3","companyName":"Medcare"},
{"subID":"1015","subName":"CGS","companyID":"3","companyName":"Medcare"}]
You are creating new objects in your filter with different properties so they will be different every time. You can you track by as mentioned by others. Since filters are executed every digest cycle you may want to set up a $watch and only create a new list of unique companies when your companies change. I actually get the 10 $digest() iterations reached error without doing this.
$scope.$watchCollection('companies', function(newValue) {
$scope.filteredCompanies = $filter('uniqueCompanies')($scope.companies,
'companyName');
});
You could also set a watch on parentCompany and create the list of subsidiaries only when it changes, as well as clear out the value you have for subsidiaryCompany:
$scope.$watch('parentCompany', function(newValue) {
$scope.subsidiaries = [];
for (var i = 0; i < $scope.companies.length; i++) {
var c = $scope.companies[i];
if (c.companyID === newValue) {
$scope.subsidiaries.push(c);
}
}
$scope.subsidiaryCompany = undefined;
});
I may not be fully understanding you're issue here, but it looks like you could filter the data when you get it. Such as ...
function getCompanies(){
$http.get("get.php?table=getcompanies").success(function(data) {
$scope.companies = data.reduce(function (prev, cur) {
// some code for skipping duplicates goes here
}, []);
});
}
Array.reduce may not be the best way to get a new array without duplicates, but that's the general idea, anyway.
I have two drop downs. Initially both has values like "First","Second","Third",...
When 1st dropdown has value "First" the second should not offer a value "First" that is second should have other than what is selected from 1st one. I want to do this using angularjs. Values in 2nd dropdown should be changed dynamically.
<select class="form-control" ng-model="Data.firstType">
<option>First</option>
<option>Second</option>
<option>Third</option>
</select>
<select class="form-control" ng-model="Data.SecondType">
<option>First</option>
<option>Second</option>
<option>Third</option>
</select>
Can anyone help on this?
Thanks in advance.
If you're items in the drop downs are static you can do this fiddle
ng-if="Data.FirstType !== First"
If they are dynamic then you can filter the items in the second list based upon the item selected in the first drop down
Here is a fiddle to dynamically filter the second list if the lists are being populated dynamically.
function MyCtrl($scope){
$scope.Data = {};
$scope.items = [{id:1, name:'First'}, {id:2, name:'Second'}, {id:3, name:'Third'}];
$scope.exclude = function(x){
return x !== $scope.Data.firstType;
}
}
Just use a $watch:
$scope.$watch('Data.firstType', function(val) {
//Get index of type in Data.yourArrayData
var idx = $scope.Data.yourArrayData.indexOf(val);
$scope.Data.yourArrayData.splice(idx, 1);
});
This assumes you are binding your second select to yourArrayData using an ng-repeat. I haven't tested this code, but it should be what you need.