I'm at my wits end! In angular I've got a controller and a view.
There are 2 dropdowns on the page which need to reset to default once the restart button has been clicked.
I can set the value of the boxes as they render by pushing a "select" option into the collection inside the controller. However, when the reset button is pressed, which runs the init() method again, the dropdowns should be set back to the first value. This doesn't occur, the values for $scope.selectedYear and $scope.selectedReport remain as they did before the reset button was pressed.
This is the full code for the controller
function TreeReportsCHLController($scope, $q, $routeParams, reportsDashboardResource, navigationService, notificationsService, dialogService, entriesManageDashboardResource, $timeout) {
// Methods
var generalError = function () {
notificationsService.error("Ooops", "There was an error fetching the data");
$scope.actionInProgress = false;
}
// Scope
$scope.selectedYear = "";
$scope.init = function () {
$scope.hasCompleted = false;
$scope.actionInProgress = false;
$scope.yearSelected = false;
$scope.reportTypes = ["Choose", "Entered", "ShortListed", "Winner", "Recieved"];
$scope.selectedReport = "";
$scope.entryYears = new Array();
$scope.entryYears.push("Choose a Year");
entriesManageDashboardResource.getEntryYears().then(function (response) {
angular.forEach(response, function (value, key) {
$scope.entryYears.push(value);
});
});
$scope.selectedYear = $scope.entryYears[0];
$scope.selectedReport = $scope.reportTypes[0];
};
$scope.yearHasSelected = function(selectedYear) {
$scope.yearSelected = true;
$scope.selectedYear = selectedYear;
};
$scope.generateFile = function (selectedReport) {
$scope.actionInProgress = true;
var reportDetail = {
entryYear: $scope.selectedYear,
chosenEntryStatus: selectedReport
};
reportsDashboardResource.generateEntriesReportDownloadLink(reportDetail).then(function (response) {
if (response.Successful) {
$scope.hasCompleted = true;
} else {
notificationsService.error("Ooops", response.ErrorMessage);
}
$scope.actionInProgress = false;
}, generalError);
};
$scope.restart = function () {
$scope.init();
}
// Initialise Page
$scope.init();
}
angular.module("umbraco").controller("ReportsDashboardController", TreeReportsCHLController)
this is the code with the dropdowns in it;
<table>
<tr>
<td>Get a report for year: {{selectedYear}}</td>
<td><select ng-model="selectedYear" ng-change="yearHasSelected(selectedYear)" ng-options="year for year in entryYears" no-dirty-check></select></td>
</tr>
<tr ng-show="!hasCompleted && yearSelected">
<td>
Get Report Type:
</td>
<td>
<select ng-model="selectedReport" ng-change="generateFile(selectedReport)" ng-options="status for status in reportTypes" no-dirty-check ng-disabled="actionInProgress"></select>
</td>
</tr>
</table>
I've also done a further test where I simply set $scope.selectedYear to $scope.entryYears[0] within the reset method. When I console.log $scope.selectedYear here, the value confirms it has been changed, but strangely where I've outputted the $scope.selectedYear / {{selectedYear}} to the page for testing, this does not update. It's almost as though the binding between the controller and the view isn't occuring.
Any help?
Thank-you.
Here's a working plunk that is somewhat stripped down since I didn't have access to of the services that your are injecting into your controller. The changes I made in the controller are:
First,
$scope.entryYears = new Array();
becomes
$scope.entryYears = [];
as this is the preferred way to declare an array in js.
Second, I removed $scope.apply() that was wrapping
$scope.selectedYear = $scope.entryYears[0];
$scope.selectedReport = $scope.reportTypes[0];
as this was causing infinite digest cycles.
Related
I try to learn SAPUI5 with Samples frpm Demo kit Input - Checked. I get an error message: oInput.getBinding is not a function
I have a simple input field xml:
<Label text="Name" required="false" width="60%" visible="true"/>
<Input id="nameInput" type="Text" enabled="true" visible="true" valueHelpOnly="false" required="true" width="60%" valueStateText="Name must not be empty." maxLength="0" value="{previewModel>/name}" change= "onChange"/>
and my controller:
_validateInput: function(oInput) {
var oView = this.getView().byId("nameInput");
oView.setModel(this.getView().getModel("previewModel"));
var oBinding = oInput.getBinding("value");
var sValueState = "None";
var bValidationError = false;
try {
oBinding.getType().validateValue(oInput.getValue());
} catch (oException) {
sValueState = "Error";
bValidationError = true;
}
oInput.setValueState(sValueState);
return bValidationError;
},
/**
* Event handler for the continue button
*/
onContinue : function () {
// collect input controls
var that = this;
var oView = this.getView();
var aInputs =oView.byId("nameInput");
var bValidationError = false;
// check that inputs are not empty
// this does not happen during data binding as this is only triggered by changes
jQuery.each(aInputs, function (i, oInput) {
bValidationError = that._validateInput(oInput) || bValidationError;
});
// output result
if (!bValidationError) {
MessageToast.show("The input is validated. You could now continue to the next screen");
} else {
MessageBox.alert("A validation error has occured. Complete your input first");
}
},
// onChange update valueState of input
onChange: function(oEvent) {
var oInput = oEvent.getSource();
this._validateInput(oInput);
},
Can someone explain to me how I can set the Model?
Your model is fine and correctly binded.
The problem in your code is here, in the onContinue function
jQuery.each(aInputs, function (i, oInput) {
bValidationError = that._validateInput(oInput) || bValidationError;
});
aInput is not an array, so your code is not iterating on an array element.
To quickly fix this, you can put parentheses around the declaration like this:
var aInputs = [
oView.byId("nameInput")
];
Also, you could remove the first two lines of the _validateInput method since they are useless...
Usually, we set the model once the view is loaded, not when the value is changed. For example, if you would like to set a JSONModel with the name "previewModel", you can do as mentioned below.
Note that onInit is called when the controller is initialized. If you bind the model properly as follows, then the oEvent.getSource().getBinding("value") will return the expected value.
onInit: function(){
var oView = this.getView().byId("nameInput");
oView.setModel(new sap.ui.model.json.JSONModel({
name : "HELLO"
}), "previewModel");
},
onChange: function(oEvent) {
var oInput = oEvent.getSource();
this._validateInput(oInput);
},
...
Also, for validating the input text, you can do the following:
_validateInput: function(oInput) {
var oBinding = oInput.getBinding("value");
var sValueState = "None";
var sValueStateText = "";
var bValidationError = false;
if(oBinding.getValue().length === 0){
sValueState = "Error";
sValueStateText = "Custom Error"
}
oInput.setValueState(sValueState);
if(sValueState === "Error"){
oInput.setValueStateText(sValueStateText);
}
return bValidationError;
},
Please note that the code above is not high quality and production ready as it's a quick response to this post :)
I have this mini app that allows the user to enter the make and model of a car, then adds it to the table below. I have added a function that will check for duplicates and alert if there are any already existing in the table. If there are no existing records that match, it will push the new "make" and "model" to the table. I changed the code to use _.isEqual instead of what I had prior and now it gets caught at the Alert every time I run it. Anyone know what is going on?
<div>Make: <input type="text" ng-model="make"></div>
<div>Model:<input type="text" ng-model="model"></div>
<button ng-click="add()">Add</button>
<tr>
<th>Make</th>
<th>Model</th>
</tr>
<tr ng-repeat="car in cars" ng-click="rowClick(car)">
<td>{{car.make}}</td>
<td>{{car.model}}</td>
</tr>
var carsApp = angular.module('carsApp', []);
carsApp.controller('carController', function ($scope){
$scope.cars = [];
$scope.add = function () {
var newCar = {
make: $scope.make,
model: $scope.model
};
function hasDuplicates(){
angular.forEach($scope.cars, function(car, key){
_.isEqual(car, newCar);
});
}
if (hasDuplicates) {
alert("Car already exists");
} else {
$scope.cars.push(newCar);
}
}
$scope.rowClick = function(car){
$scope.make= car.make;
$scope.model= car.model;
};
$scope.make = null;
$scope.model = null;
});
Ended up going with a simpler option and used angular.equals instead. isEqual was too much of a pain to implement properly. Thanks for all the help!
function hasDuplicates(newCar){
var returnVal = false;
angular.forEach($scope.cars, function(car, key){
if (angular.equals(car, newCar))
{
returnVal = true;
}
});
return returnVal;
};
As the title suggests I want to be able to refresh a ng-repeat list from the server every 30 seconds or so. More data can be added on the backend, so I want my list to reflect that. Right now I have the regular $http.get( ) working fine which is here:
$scope.select = function() {
$scope.searchText = '';
$scope.selectedItem = null;
var url = 'http:xxxxxxxxxxxx.com';
url += $scope.selectModel.name;
console.debug("GOING TO: " + url);
$http.get(url).success(function(data2) {
$scope.records = [];
data2.forEach(function(r) {
$scope.records.push(r);
});
});
};
and the portion of the web page it supplies is:
<div style="margin: 1em">
<h4>Search</h4>
<div role="form">
<!-- start dropdown -->
<div class="form-group">
<select class="form-control" ng-options="model as model.name for model in allModels" ng-model="selectModel" ng-change="select()">
<option value="">Choose Model</option>
</select>
</div>
<!-- /end dropdown-->
<div class="form-group">
<input id="start_date" type="text" class="form-control" placeholder="Threat Date">
</div>
</div>
<div>
<table class="table table-hover table-striped" ng-show="records">
<thead>
<th>#</th>
<th>Name</th>
<th>Score</th>
</thead>
<tr data-ng-repeat=" item in records | orderBy : '-score' | limitTo : 10 " ng-click="moreInfo(item)">
<td>{{$index+1}}</td>
<td>{{item.name.slice(5)}}</td>
<td>{{item.score.toFixed(3)}}</td>
</tr>
</table>
</div>
</div>
Is there a way to choose a time # which the list will refresh? And it has to be without hitting a refresh button or something like that. Thanks in advance.
EDIT This is the error I get when I try and use $interval as suggested:
ReferenceError: $interval is not defined
at Scope.$scope.select (http:xxxxxxxxxx.com/UserQuery/js/script.js:24:7)
at fn (eval at <anonymous> (https://code.angularjs.org/1.4.0-rc.0/angular.js:12822:15), <anonymous>:2:209)
at Scope.$get.Scope.$eval (https://code.angularjs.org/1.4.0-rc.0/angular.js:15465:28)
at https://code.angularjs.org/1.4.0-rc.0/angular.js:21825:13
at https://code.angularjs.org/1.4.0-rc.0/angular.js:24485:9
at forEach (https://code.angularjs.org/1.4.0-rc.0/angular.js:332:20)
at NgModelController.$$writeModelToScope (https://code.angularjs.org/1.4.0-rc.0/angular.js:24483:5)
at writeToModelIfNeeded (https://code.angularjs.org/1.4.0-rc.0/angular.js:24476:14)
at https://code.angularjs.org/1.4.0-rc.0/angular.js:24470:9
at validationDone (https://code.angularjs.org/1.4.0-rc.0/angular.js:24398:9)
SOLUTION With combined efforts from this and another question, I came to a solution. First off, like many on this question mentioned, the key here is the use of $interval. There are a few important things to not about using it though.
It must be included in the dependencies for the controller as
#mcpDESIGNS mentioned.
In my case, where there is a dropdown therefor multiple things I want
to $interval over, it is important to close one when you open a new
one.
$scope.select = function() {
$scope.searchText = '';
$scope.selectedItem = null;
$interval.cancel(mainInterval);
$scope.url = '';
url = 'http:xxxxxxxxxxxxxx.com';
url += $scope.selectModel.name;
console.debug("GOING TO: " + url);
$http.get(url).success(function(data2) {
$scope.records = [];
data2.forEach(function(r) {
$scope.records.push(r);
});
});
mainInterval = $interval(function() {
console.debug("UPDATING....");
console.debug("GETTING NEW FROM " + url);
$http.get(url).success(function(data2) {
$scope.records = [];
data2.forEach(function(r) {
$scope.records.push(r);
});
});
}, 5000);
};
Have a look at this:
https://docs.angularjs.org/api/ng/service/$interval
It wraps JavaScript's native setInterval function. You can set it to do the poll every 30 seconds.
It also returns a promise so you can cancel the interval when required.
However, please bear this in mind:
"Intervals created by this service must be explicitly destroyed when you are finished with them. In particular they are not automatically destroyed when a controller's scope or a directive's element are destroyed. You should take this into consideration and make sure to always cancel the interval at the appropriate moment. See the example below for more details on how and when to do this."
EDIT
Taking your code:
$scope.select = function() {
$scope.searchText = '';
$scope.selectedItem = null;
var url = 'http:xxxxxxxxxxxx.com';
url += $scope.selectModel.name;
console.debug("GOING TO: " + url);
$http.get(url).success(function(data2) {
$scope.records = [];
data2.forEach(function(r) {
$scope.records.push(r);
});
});
};
Try changing to this:
$scope.select = function() {
$scope.searchText = '';
$scope.selectedItem = null;
var url = 'http:xxxxxxxxxxxx.com';
url += $scope.selectModel.name;
console.debug("GOING TO: " + url);
$http.get(url).success(function(data2) {
$scope.records = [];
data2.forEach(function(r) {
$scope.records.push(r);
});
});
$interval(function() {
$http.get(url).success(function(data2) {
$scope.records = [];
data2.forEach(function(r) {
$scope.records.push(r);
});
});
}, 30000);
};
If that works you can then refactor the actual $http.get out into a named function to remove the code smell.
Just use a $interval() around your $http to make it refresh every 30 seconds.
$interval(function () {
$http({
/* run your AJAX and update your $scope / etc */
});
}, 30000); // in milliseconds
Note: $interval must be dependency injected into your controller / service / etc to work!
// for examples sake
.controller('MyController', ['$interval', function ($interval) { }]);
Use ng-table instead.
Have a look http://bazalt-cms.com/ng-table/
And u can call its reload propery, which will refresh your table,
The reload you can call inside $timeout service provided by angular.
$timeout(function(){
tablename.reload();
},3000);
or
Just call the select function inside the timeout
$timeout(function(){
$scope.select();
},3000);
Try this:
Ok, I think the plan is each time select is fired, the existing interval needs to be cancelled and another one started. Have a look at this:
var intervalObj;
$scope.select = function() {
if (intervalObj !== null) {
$interval.cancel(intervalObj);
intervalObj = null;
}
$scope.searchText = '';
$scope.selectedItem = null;
var url = 'http:xxxxxxxxxxxx.com';
url += $scope.selectModel.name;
console.debug("GOING TO: " + url);
$http.get(url).success(function(data2) {
$scope.records = [];
data2.forEach(function(r) {
$scope.records.push(r);
});
});
intervalObj = $interval(function() {
$http.get(url).success(function(data2) {
$scope.records = [];
data2.forEach(function(r) {
$scope.records.push(r);
});
});
}, 30000);
};
I have not been able to fully test this, but the principle is sound.
I'm watching two values in my angular project like that: $scope.$watchGroup( ['currentPage', 'newStatus'], $scope.setPage ).
And when both values are changing the function $scope.setPage executes two times accordingly, but it should once. How to implement this?
My code:
JS
app.controller('CategoryListCtrl', ['$scope', '$http', '$location', '$route', function($scope, $http, $location, $route) {
$scope.numPerPage = 5;
$scope.currentPage = 1;
$scope.newStatus = -1;
var currentPageChanged = false;
var newStatusChanged = false;
function setPage() {
// function's code based on requests to db
};
setPage();
// pagination based on db requests
$scope.$watchGroup( ['currentPage', 'newStatus'], function(newValues, oldValues) {
if ((newValues[0] != oldValues[0]) && !newStatusChanged) {
console.log(1);
// currentPage has changed
setPage();
currentPageChanged = true;
} else if ((newValues[1] != oldValues[1]) && !currentPageChanged) {
console.log(2);
// newStatus has changed
newStatusChanged = true;
} else {
console.log(3);
newStatusChanged = false;
currentPageChanged = false;
}
});
...
HTML
...
<th>
<select class="form-control"
ng-model="isPublic"
ng-options="isPublic.name for isPublic in isPublicScope"
ng-change="newStatus = isPublic.status; currentPage = 1;">
<option value="">Show all</option>
</select>
</th>
<tr ng-repeat="category in categories | filter:search">
</tr>
...
You can add a flag, which changes when both the values change for the first time (only once). After which you can disable the watch.
Here is an example: jsbin example
can also be written like this: editted
In this example, two textboxes are given, only when both the textbox values are changed for the first time, the watch increased the count to 1. After that, the watch is disabled and hence the count does not increase even when changes occur in the textboxes.
With a slight change in the code, you may be able to get what you want from this example.
This should probably do the trick (not tested):
var w = $scope.$watchGroup( ['currentPage', 'newStatus'],
function(newValues, oldValues) {
if ((newValues[0] != oldValues[0]) || (newValues[1] != oldValues[1])) {
$scope.setPage;
// clear $watchGroup
w();
}
}
)
I have a page running angularJS. Sometimes, when I open the document, the data that needs to appear only sometimes shows up. When I keep trying to refresh the page, it's pretty much random: sometimes the content appears, sometimes it doesn't.
The section of the code that runs this looks like this:
<div class="row">
<div class="col-md-12" ng-repeat="(observer,hosts2) in bugDuration">
{{observer}}
<div class="row">
<div class="col-md-3" ng-repeat="(host, bugs2) in hosts2"> {{host}}
<div ng-repeat="(bug, duration) in bugs2">
{{bug}} for {{duration}} seconds.
</div>
</div>
</div>
</div>
</div>
As you can see, it is using ng-repeat, and my best guess is that when this code is running, the ng-repeat objects, such as bugDuration are empty, so none of it runs.
My script that initializes all of these variables is located after, in my document. Is there something I should do in the controller or whatever so the variables can be refreshed and the content can be shown everytime?
Edit
Here is the code where bugDuration is initialized:
bugDuration = {};
bugTracker = {};
$.getJSON('../java_output/bugs.json', function (data) {
for ( var observer in data ) {
bugDuration[observer] = {};
for (var host in data[observer]) {
bugDuration[observer][host] = {};
for (var bug in data[observer][host]) {
bugDuration[observer][host][bug] = data[observer][host][bug].duration;
}
}
}
console.log (bugDuration);
});
$.getJSON('../java_output/bug_summary.json', function (data) {
var numObservers = data.numObservers;
delete data['numObservers'];
JSONbugsList = data;
var bugTracker = {};
for (var observer = 1; observer <= numObservers; observer++) {
observers.push(observer);
observerKeys = Object.keys(data);
// observerKeys.splice(observerKeys.indexOf('numObservers'));
for (var host in data["observer" + observer]) {
if (hosts.indexOf(host) == -1) {
hosts.push(host);
}
hostKeys = Object.keys(data["observer" + observer]);
for (var bug in data["observer" + observer][host]) {
if (bugs.indexOf(bug) == -1) {
bugs.push(bug);
}
for (var i in data["observer" + observer][host][bug]) {
bugTracker[bug] = true;
var dateVar = data["observer" + observer][host][bug][i];
var intoList = {"observer":observer, "host":host, "bug":bug, "start":(new Date(1000*dateVar.start)), "end":(dateVar.end==null?' the end.':(new Date(1000*dateVar.end)))}
}
}
}
}
// Removed unimportant stuff here//
$scope.$apply();
$scope.hostsS = hosts;
$scope.bugsS = bugs;
$scope.observersS = observers;
$scope.JSONbugsList = JSONbugsList;
$scope.hostKeys = hostKeys;
$scope.observerKeys = observerKeys;
$scope.start = 'start';
$scope.end = 'end';
$scope.bugDuration = bugDuration;
$scope.$apply();
The biggest problem among others is that $scope.$apply() needs to happen after the data gets set on the $scope. Since $.getJSON is asynchronous, by the time the callback gets triggered, the $scope.$apply() lines at the bottom will have already been fired.
$.getJSON('../java_output/bug_summary.json', function (data) {
/*do stuff outside of angular context when the ASYNC callback fires*/
$scope.stuff = data;
/*then call $scope.$apply()*/
$scope.$apply();
});