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
Related
I am working on an ASP.Net MVC page that uses a dropdown which currently uses the ng-repeat tag. I'm working to solve the problem where the dropdown does not correctly select the current model value when the page loads so I switched the dropdown to use ng-options.
My new dropdown looks like this:
<select id="one" ng-model="data.CommandProvider"
ng-options="item.ident as item.ProviderName for item in providers">
</select>
When the page loads my new select displays as a large empty rectangle. It's approximately the width and height to match the three items it should contain but it's not a dropdown. No options and no dropdown button.
However, when I follow the new dropdown with the old dropdown like so:
<select id="one" ng-model="data.CommandProvider"
ng-options="item.ident as item.ProviderName for item in providers">
</select>
<select id="two" ng-model="data.CommandProvider">
<option ng-repeat="opt in providers track by opt.ident"
value="{{opt.ident}}">
{{opt.ProviderName}}
</option>
</select>
BOTH dropdowns load their options correctly but NEITHER dropdown correctly displays the current value of the model.
If the page only contains the old dropdown based on ng-repeat that dropdown displays correctly.
I don't understand what could cause such behavior in ng-options and what would cause the dropdowns to never correctly represent the model on page load?
ADDED: So the previous author had mismatched HTML tags and that was causing the error with the new dropdown - why it didn't break the original I don't know. That being said the new dropdown STILL does not display the value of the model when the page is loaded.
So after working this problem for too long this is the solution that worked for me:
There are three http requests in play: one for each select input and one for the model data and whenever the model data returned before the select data one or both of the select would be out of sync with the model. My solution was to synchronize the data requests.
The select inputs:
<select ng-model="data.Connection">
<option ng-repeat="opt in connections track by opt.ident" value="{{opt.ident}}">{{opt.ConnectionName}}</option>
</select>
<select id="two" ng-model="data.CommandProvider">
<option ng-repeat="opt in providers track by opt.ident" value="{{opt.ident}}">{{opt.ProviderName}}</option>
</select>
The javascript:
// connection and provider data variables
$scope.providers;
$scope.connections;
// function to retrieve connection dropdown data
$scope.getConnections = function () {
$scope.getApiData('GetConnections',
{}, function (data) {
$scope.connections = data;
});
}
// function to retrieve the provider dropdown data
$scope.getProviders = function () {
$scope.getApiData('GetProviders',
{}, function (data) {
$scope.providers = data;
});
}
// retrieve the primary page data
$scope.getCommandData = function () {
$scope.getApiCommandDataV1('GetCommandById',
{Id: #ViewBag.ID},
function (data) {
$scope.data = data;
});
}
// retrieves data from the core api
$scope.getApiData = function (alias, params, successFn, errorFn = null) {
var qdata = { SqlAlias: alias, SqlParameters: params };
if (errorFn == null) {
$http.post('/api/request', qdata).success(successFn);
} else {
$http.post('/api/request', qdata).success(successFn).error(errorFn);
}
}
// function to request the data for the page
$scope.init = function () {
$scope.getConnections();
}
// set a watch on the connections variable to fire when the data
// returns from the server - this requests the Providers information.
$scope.$watch('connections', function (newValue, oldValue, scope) {
if (newValue == undefined || newValue == null)
return;
$scope.getProviders();
}, true);
// set a watch function on the providers variable to fire when the data
// returns from the server - this requests the primary data for the Command.
$scope.$watch('providers', function (newValue, oldValue, scope) {
if (newValue == undefined || newValue == null)
return;
$scope.getCommandData();
}, true);
// initialize the page logic and data
$scope.init();
As you can see my use of $scope.$watch forces the data requests to be synchronous rather than asynchronous and using this method insures the two select inputs are correct every time the web page loads.
Feel free to comment on my coding here as there may be better ways to address this problem - just keep in mind that I have only been working with JavaScript and Angular for about a month.
my html code is :
<select ng-model='refresh_rate' ng-change="onRefreshRateChange()">
<option value='10000'>10</option>
<option value='30000' selected>30</option>
<option value='60000'>60</option>
<option value='180000'>180</option>
<option value='300000'>300</option>
</select>
and in my angular controller is :
var intervaL = $interval(function(){
//[here]
//after run every times i want update delay to $scope.refresh_rate
}, $scope.refresh_rate);
see this line in top code :
//here i want update delay time to $scope.refresh_rate
now i want a code that put it Instead of this line
To change the $interval rate you need to stop one $interval and start another.
Without going into particulars of whether the last $interval call should fire or cancel immediately - I'll let you handle that on your own - you can do the following:
var p = $interval(doSomething, $scope.refresh_rate);
$scope.$watch("refresh", function(){
$interval.cancel(p);
p = $interval(doSomething, $scope.refresh_rate);
});
function doSomething(){
//...
}
plunker
EDIT:
You can also certainly do this in onRefreshRateChange instead of $scope.$watch. The difference is whether you care to change $scope.refresh_rate outside of your <select> - this is where you would use $watch vs. only in response to a change in <select>
Use a $timeout loop.
function loop(fn) {
$timeout(function() {
if (!$scope.stopBefore) {
fn();
$scope.stopAfter || loop(fn);
}
, $scope.refresh);
}
loop(doSomething);
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 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
I'm using angular-ui's select2 for a fairly simple dropdown. It's backed by a static array of data sitting on my controller's scope. In my controller I have a function that gets called on ng-change of the dropdown so that I can perform some actions when the value changes.
However, what I'm finding is that the ng-model's property gets set as a JSON string rather than an actual javascript object, which makes it impossible to use dot notation to grab properties off of that model.
Here's the function that handles the value of the dropdown getting changed:
$scope.roleTypeChanged = function() {
//fine:
console.log('selectedType is: ', $scope.adminModel.selectedType);
// this ends up being undefined because $scope.adminModel.selectedType is a
// JSON string, rather than a js object:
console.log('selectedType.typeCode is: ', $scope.adminModel.selectedType.typeCode);
}
Here's a plunker of my full example: http://plnkr.co/edit/G39iZC4f7QH05VctY8zG
I've never seen a property that's bound to ng-model do this before, however I'm also fairly new to Angular so it's likely that I'm just doing something wrong here. I can certainly do something like $.parseJSON() to convert the JSON string back to an object, but I'd rather not unless I have to.
Thanks for any help!
You need to use ng-options on your select if you want to have object values. Actually creating the options yourself using an ng-repeat will only allow you to have string values for the various options:
<select ui-select2
ng-model="adminModel.selectedType"
ng-change="roleTypeChanged()"
data-placeholder="Select Role Type" ng-options="type.displayName for type in adminModel.roleTypes">
<option value=""></option>
</select>
http://plnkr.co/edit/UydBai3Iy9GQg6KphhN5?p=preview
Thanks Karl!
I have struggled a day with this
as a note for others having similar problems as I did,
when using an ng-model not accessible and defined in the controller/directive I solved it like this.
//country.Model has Code and Name nodes
* HTML *
<select
name="country" data-ng-model="country.Model"
data-ui-select2=""
data-ng-change="countryChanged(country.Model)" <!--only for test, log to console-->
data-ng-options="country as CodeAndName(country) for country in countries"
data-placeholder="{{placeholderText(country.Model, '- - Select Country - -')}}" >
<option value=""></option>
</select>
* JS *
function controller($scope, $q, $location, $routeParams) {
$scope.countryChanged = function(item) { // for test
console.log('selectedType is: ', item);
};
//returns the item or the text if no item is selected
$scope.placeholderText = function (item, text){
if (item == undefined)
return text;
return $scope.CodeAndName(item);
};
// returns a string for code and name of the item, used to set the placeholder text
$scope.CodeAndName = function (item) {
if (item == undefined)
return '';
return item.Code + ' - ' + item.Name;
};