I'm working on implementing an Angular factory into a project I'm working on.
I've gotten routing working: ArtLogMain.js
var ArtLog = angular.module('ArtLog', ['ngGrid', 'ui.bootstrap']);
ArtLog.config(function ($locationProvider, $routeProvider) {
$locationProvider.html5Mode(true);
$routeProvider.when("/ArtLog", {
controller: "ArtLogCtrl",
templateUrl: "/Templates/ArtLog/Index.html"
});
$routeProvider.when("/ArtLog/:Id", {
controller: "ArtLogEditCtrl",
templateUrl: "/Templates/ArtLog/Edit.html"
});
$routeProvider.when("/ArtLog/Dashboard", {
controller: "ArtLogDashBoardCtrl",
templateUrl: "/Templates/ArtLog/Dashboard.html"
});
$routeProvider.otherwise("/");
});
Next I setup the Factory: ArtLogDataService
ArtLog.factory("ArtLogDataService", function ($q) {
breeze.config.initializeAdapterInstance("modelLibrary", "backingStore", true);
var _artLogView = [];
var _artLogSingle = [];
var _getArtLogById = function (Id) {
var deferred = $q.defer();
var manager = new breeze.EntityManager('breeze/BreezeData');
var query = new breeze.EntityQuery().from('Project').where("Id", "Equals", Id);
manager.executeQuery(query).then(function (data) {
angular.copy(data, _artLogSingle);
deferred.resolve();
}).fail(function () {
deferred.reject();
});
return deferred.promise;
};
var _getArtLogView = function () {
var deferred = $q.defer();
var manager = new breeze.EntityManager('breeze/BreezeData');
var query = new breeze.EntityQuery().from('ArtLogView');
manager.executeQuery(query).then(function (data) {
//angular.copy(data.results, _artLogView);
_artLogView = data.results;
deferred.resolve();
}).fail(function () {
deferred.reject();
});
return deferred.promise;
};
return {
artLogView: _artLogView,
artLogSingle: _artLogSingle,
getArtLogView: _getArtLogView,
getArtLogById: _getArtLogById
};
})
The the Controller: ArtLogController.js
function ArtLogCtrl($scope, ArtLogDataService) {
$scope.ArtLogData = ArtLogDataService;
$scope.editableInPopup = '<button id="editBtn" type="button" class="btn btn-primary" ng-click="edit(row)" >Edit</button>';
ArtLogDataService.getArtLogView();
$scope.edit = function (row) {
window.location.href = '/ArtLog/' + row.entity.Id;
};
$scope.gridOptions = {
data: ArtLogDataService.artLogView,
showGroupPanel: true,
enablePinning: true,
showFilter: true,
multiSelect: false,
columnDefs: [
{ displayName: 'Edit', cellTemplate: $scope.editableInPopup, width: 80, pinned: true, groupable: false, sortable: false },
{ field: 'ArtNum', displayName: 'Art Number', resizable: true, pinned: true, groupable: false, width: '100px' },
{ field: 'CreateDate', displayName: 'Date Created', cellFilter: "date:'MM-dd-yyyy'", pinnable: false, width: '110px' },
{ field: 'ArtCompletionDue', displayName: 'Art Comp Due Date', cellFilter: "date:'MM-dd-yyyy'", pinnable: false, width: '160px', enableCellEdit: true },
{ field: 'DaysLeft', displayName: 'Days Left', pinnable: false, width: '90px' },
{ field: 'RevisionNum', displayName: 'Rev Number', pinnable: false, width: '100px' },
{ field: 'Status', displayName: 'Status', pinnable: false, width: '80px' },
{ field: 'Template', displayName: 'Template', pinnable: false, width: '190px' },
{ field: 'Driver', displayName: 'Driver', pinnable: false, width: '160px' },
{ field: 'AssignedTo', displayName: 'Assigned To', pinnable: false, width: '160px' },
{ field: 'BuddyArtist', displayName: 'Buddy Artist', pinnable: false, width: '160px' }
],
filterOptions: {
filterText: "",
useExternalFilter: false
}
};
}
I set a breakpoint on ArtLogDataService.getArtLogData and I see the call fire. I also see the query run and data is returned, but When I look at the ArtLogDataService object returned from the factory it always shows Array[0]. The data never seems to bind to artLogView.
What am I doing wrong?
Thanks!
The problem is that your network callback from Breeze is not part of the Angular update loop. Angular doesn't know that your data changed, so the watcher on the view binding never gets updated.
You need to wire in a $scope.$apply() call when your data comes back. This will cause the bindings to notice the change in the data and update.
Perhaps something like this:
ArtLogDataService.getArtLogView().then(function() {
$scope.$apply();
});
If you do everything from within Angular, you never need to call $scope.$apply, because anything that can mutate data (events, network responses, timeouts, etc) will get handled by Angular (via $http and $timeout, etc) and $apply will automatically get called. It is in these situations where the data gets changed by an event from outside of Angular that $scope.$apply is necessary.
Hope this does it for you!
You do not ... and should not ... use $q.deferred in your query callbacks. The Breeze EntityManager methods already return promises ... $q promises when you use the Breeze.Angular module as explained in the documentation and demonstrated in samples such as "Todo-Angular".
Get rid of your hand-rolled promises and you won't need $apply either.
Should be something like this:
// Create or acquire the manager ONCE for the lifetime of your data service
// Todo: more testable to use a separate "entityManagerFactory" service
// to get your manager.
var manager = new breeze.EntityManager('breeze/BreezeData');
var _getArtLogView = function () {
return breeze.EntityQuery.from('ArtLogView')
.using(manager).execute()
.then(success)
.catch(fail); // only if you have something useful to do here when it fails
function success(data) { return data.results; }
function fail(error) {
// some kind of logging or useful error handling;
// otherwise don't bother with fail here
return $q.reject(error); // so caller sees it
}
};
Related
I have a web application, started by a previous company, written in Angular.JS.
The application exposes a request towards the back-end (written in Node.JS+Express) to gather some data required to fill a table.
Specifically, this is the request that the application sends everytime the user enters in the page that holds the table (The config variable holds the access token).
define(['app'], function (app) {
app.factory('AdvService', AdvService);
AdvService.$inject = ['BasicService'];
function AdvService(BasicService) {
var service = angular.extend(BasicService, {});
var $http = service.$http;
var API = service.API
var handleSuccess = service.handleSuccess;
var handleError = service.handleError;
var config = {
params: {
'token': JSON.parse(window.sessionStorage.getItem('session')).token
}
};
service.searchByCriteria = function (criteria) {
debugger;
config.params.pageNumber = criteria.pageNumber;
config.params.pageSize = criteria.pageSize;
return $http.get(API + '/api/v2/admin/ads/getAdsByCriteria', config).then(handleSuccess, handleError);
};
service.createAd = function (ad) {
debugger;
return $http.post(API + '/api/v2/admin/ads/insertNewAd', ad, config).then(handleSuccess, handleError);
};
return service;
}
});
handleSuccess and handleError are so defined
define(['app'], function (app) {
app.factory('BasicService', BasicService);
BasicService.$inject = ['CONF', '$http', '$window', '$q'];
function BasicService(CONF, $http, $window, $q) {
$http.defaults.headers.common['x-access-token'] = JSON.parse($window.sessionStorage.getItem('session')).token;
return {
$http: $http,
API: CONF.API_URL,
handleSuccess: function (res) {
debugger;
var deferred = $q.defer();
res.data.success ? deferred.resolve(res.data) : deferred.reject(res.data.message);
return res.data;
},
handleError: function (error) {
debugger;
return {
success: false,
message: error
};
}
}
}
})
and this is the only point of the application that calls that service
($scope.search = function () {
debugger;
AdvService.searchByCriteria($scope.searchCriteria).then(function (res) {
debugger;
$scope.searchRes = res.data.docs;
//$scope.gridOptions.totalItems = res.data.total;
}, function (err) {
console.log(err);
});
})();
Being a CORS request (the front-end is at port 8010 and the back-end in another one), I see from Chrome's Network Monitoring System that the $http.get part gets executed twice, but here's my problem: even before starting handling on the back-end the first call, the front-end generates the error
angular.js:14961 TypeError: Cannot read property 'data' of undefined
at Object.<anonymous> (ui-grid.js:3291)
at Object.invoke (angular.js:5117)
at $controllerInit (angular.js:11139)
at nodeLinkFn (angular.js:10002)
at angular.js:10410
at processQueue (angular.js:17330)
at angular.js:17378
at Scope.$digest (angular.js:18515)
at Scope.scopePrototype.$digest (hint.js:1495)
at Scope.$apply (angular.js:18903)
and even tough the request does return data, having crashed, Angular cannot correctly render everything.
The only thing I tried was to use the Await/Async mechanism to try to wait and see what could have happened but this always resolves in the above error.
Does anybody have any clue of what's going on? I'm 100% positive that the code that I've posted here is the only one that gets called during this process and I can't honestly understand why should the process fail if both requests return data
So, as correctly pointed out by Alon Eitan in the comments, the error was somehow related with ui-grid and that, probably, it might have been that the gridOptions were not correctly called like the HTML was excepting.
IN FACT ...
HTML
<div class="alerts container-fluid" ng-controller="AdsController">
<div class="row">
WORK IN PROGRESS!
<!-- <span>PubblicitĂ attive: <b>{{activeAdsCounter}}</b> - </span>-->
<!-- <span>PubblicitĂ totali: <b>{{totalAdsCounter}}</b> - </span>-->
</div>
<button ng-click="openCreateNewAdModal()"><i class="material-icons">library_add</i></button>
<div class="row">
<div class="col-md-12">
<h3>PubblicitĂ Nel Sistema</h3>
<div class="grid" ui-grid="adsGridOptions" ui-grid-pagination
ui-grid-auto-resize></div>
</div>
</div>
</div>
Controller
define(['app', 'moment'], function (app, moment) {
app.controller('AdsController', AdsController);
AdsController.$inject = ['$scope', '$mdDialog', 'AdvService'];
function AdsController($scope, $mdDialog, AdvService) {
debugger;
$scope.rowHeight = 30;
$scope.gridOptions = {
data: 'searchRes',
paginationPageSizes: [25, 50, 75],
paginationPageSize: 25,
enableSorting: true,
enablePaginationControls: true,
enableColumnMenus: false,
useExternalPagination: true,
rowHeight: $scope.rowHeight,
columnDefs: [
{
name: 'Cliente',
field: 'customerName',
}, {
name: 'Durata',
field: 'duration',
}, {
name: 'Euro Spesi',
field: 'spentEuros',
}, {
name: 'Data inserimento',
field: 'createdAt',
type: 'date',
width: '130',
cellFilter: "date:'dd-MM-yyyy'",
}, {
name: 'Email Referente',
field: 'referralEmail',
}, {
name: 'Nome Referente',
field: 'referralPerson',
}, {
name: 'Numero Referente',
field: 'referralNumber',
},
],
onRegisterApi: function (gridApi) {
$scope.gridApi = gridApi;
$scope.gridApi.core.on.sortChanged($scope, function (grid, sortColumns) {
if (sortColumns[0]) {
console.log(sortColumns[0].sort);
$scope.searchCriteria.sort = {};
$scope.searchCriteria.sort[sortColumns[0].field] = sortColumns[0].sort.direction;
}
});
$scope.gridApi.pagination.on.paginationChanged($scope, function (pageNum, pageSize) {
$scope.searchCriteria.pageNumber = pageNum;
$scope.searchCriteria.pageSize = pageSize;
$scope.search();
});
}
};
The controller had the options called as "gridOptions", while the HTML had them called "adsGridOptions". Changing the name to "gridOptions" to both sides solved the issue.
Thank you, Alon Eitan!
Below is my code snippet and I wish to call a function "insertBarchart()" on loading the ionic popup. However I am not able to call without the use of "onTap" (which requires clicking the 'Show' button).
Note: The barchart is shown at the place of "container" which is defined inside popup so function call must be made within the scope.
$ionicPopup = Apperyio.get("$ionicPopup");
var alertPopup = $ionicPopup.show({
scope: $scope,
template: '<div id="container" style="min-width: 200px; height: 200px; margin: 0 auto" ></div>',
buttons: [{
text : 'OK',
type: 'button-positive'
},
{
text : 'Show',
type: 'button-positive',
onTap : function(e) {
$scope.insertBarchart();
e.preventDefault();
}
}]
});
In the same situation I tried in other way please check this code once
angular.module('ionic.example', ['ionic'])
.controller('PopupCtrl', function($scope, $timeout, $q, $ionicPopup) {
$scope.cmnt={};
$scope.showToast=function (message) {
if (window.plugins && window.plugins.toast) {
window.plugins.toast.showLongCenter(message);
}
else $ionicLoading.show({ template: message, noBackdrop: true, duration: 2000 });
}
$scope.showPopup = function () {
$scope.data = {};
var pop=$ionicPopup.show({
template: '<div class="list" dir="rtl"><label class="item item-input"><input ng-model="cmnt.name" type="text" placeholder="name"></label><label class="item item-input"><textarea ng-model="cmnt.body" rows="4" placeholder=" comment..."></textarea></label></div>',
title: 'write your comment',
subTitle: '',
scope: $scope,
buttons: [
{
text: 'cancel',
type: 'button-small'
},
{
text: '<b>save</b>',
type: 'button-energized',
onTap: function (e) {
return $scope.cmnt;
}
}
]
}).then(function (res) {
$scope.showToast(res.name);
});
}
});
Instead of $ionicPopup you can create a modal page in Appery that will have its own init() function and can be customized more easily
This is what I have:
$http.get("http://localhost/app/api/Suppliers").success(function(response) {
$scope.dataSource = response;
console.log($scope.dataSource);
$scope.safeApply(function() {
$scope.settings.columns[3] = $scope.dataSource;
});
});
$scope.settings = {
colHeaders: ["Code", "Comments"],
contextMenu : ["row_above","row_below","remove_row"],
colWidths: [100, 100],
columns : [
{ type: 'dropdown',
source: ['Not Started', 'In Progress', 'Completed']
},
{},
{},
{ type: 'dropdown',
source: $scope.dataSource,
}
]
};
Problem is $scope.dataSource is undefined, it's not displaying the data. What should be the solution to this?
UPDATE:
This displays the data in my $http call. But in the settings source when I call source: $scope.dataSource is undefined
When you are making a request to the server, the rest of the controller will be compiled then you will enter the success function
you are declaring your array inside the success of your request, it's better be declared outside like this
$scope.dataSource = []
$http.get("http://localhost/app/api/Suppliers").success(function(response) {
$scope.dataSource = response;
console.log($scope.dataSource);
$scope.safeApply(function() {
$scope.settings.columns[3] = $scope.dataSource;
});
});
UPDATE
try this
$http.get("http://localhost/app/api/Suppliers").success(function(response) {
$scope.dataSource = response;
console.log($scope.dataSource);
getSetting();
$scope.safeApply(function() {
$scope.settings.columns[3] = $scope.dataSource;
});
});
var getSetting = function() {
$scope.settings = {
colHeaders: ["Code", "Comments"],
contextMenu: ["row_above", "row_below", "remove_row"],
colWidths: [100, 100],
columns: [{
type: 'dropdown',
source: ['Not Started', 'In Progress', 'Completed']
}, {}, {}, {
type: 'dropdown',
source: $scope.dataSource,
}]
};
}
So first you need to be aware that when you declare $scope.settings = {...} the $http call will not be over yet, so instead of writting
{
type: 'dropdown',
source: $scope.dataSource,
}
You may as well simply write
{
type: 'dropdown',
source: null,
}
Then when the $http call finishes, I assume you want to set this source property. However in your code you are overriding the whole $scope.settings.columns[3] instead of just its source property.
Try this instead:
$http.get("http://localhost/app/api/Suppliers").success(function(response) {
$scope.dataSource = response;
console.log($scope.dataSource);
$scope.settings.columns[3].source = $scope.dataSource;
});
Note that I have removed the safeApply which is an anti-pattern. Here the $http call will take care of the digest cycle.
Found the answer!
$scope.suppliers = [];
$http.get("http://localhost/i95/api/Suppliers").success(function(data, status, headers, config) {
for(var i = 0; i < data.length; i++) {
$scope.suppliers.push(data[i].company);
}
console.log($scope.suppliers);
});
So creating a global variable and adding push method fixed the issue.
I have a modal that I want to display pre-selected rows in. However, I keep getting a 'cannot read 'grid' of undefined' error. The UI Grids are defined within the modal, and I declare two Grid Apis with two different names. Here is my code:
This launches the modal:
//edit user group
$scope.editSelectedGroup = function(){
//get the selected row
var row = $scope.gridApiInfo.selection.getSelectedRows();
var modalInstance = $modal.open({
animation: true,
templateUrl: 'userGroupModal.html',
controller: 'UserGroupModalController',
resolve: {
modalTitle: function () {
return "Edit User Group"
},
allDepartments: function () {
return $scope.allDepartments;
},
allRegions: function() {
return $scope.allRegions;
},
isEdit: function(){
return true;
},
row: function(){
return row[0];
}
}
});
This is the modal controller:
.controller('UserGroupModalController',function($scope,$modalInstance,$modal,$window,modalTitle,allDepartments,allRegions,isEdit,row,referenceDataService){
$scope.modalTitle = modalTitle;
$scope.isEdit = isEdit;
$scope.allDepartments= allDepartments;
$scope.allRegions = allRegions;
$scope.form= {
value: "",
description: "",
departments: [],
regions: []
};
$scope.departmentsGrid = {
enableRowSelection: true,
multiSelect: true,
enableRowHeaderSelection: false,
onRegisterApi: function(gridApi) {
$scope.deptGridApi= gridApi;
},
columnDefs: [
{name: 'Name', field: 'name'}
],
data: $scope.allDepartments
};
$scope.regionsGrid = {
enableRowSelection: true,
multiSelect: true,
enableRowHeaderSelection: false,
onRegisterApi: function(gridApi) {
$scope.gridApiRegions = gridApi;
},
columnDefs: [
{name: 'Name', field: 'name'}
],
data: $scope.allRegions
};
if ($scope.isEdit){
$scope.form.value = row.value;
$scope.form.description = row.description;
//pushing selected depts
angular.forEach(row.departments, function(department) {
var deptElementPos=angular.findIndexOf($scope.allDepartments, department.id);
$scope.form.departments.push($scope.allDepartments[deptElementPos]);
});
//pushing selected regions
angular.forEach(row.regions, function(region) {
var regionElementPos=angular.findIndexOf($scope.allRegions, region.id);
$scope.form.regions.push($scope.allRegions[regionElementPos]);
});
//setting pre-selected rows
angular.forEach($scope.form.departments, function(department) {
$scope.deptGridApi.grid.rows.map(function (row) {
if (row.entity.id == department.id) {
row.setSelected(true);
$log.log("row selected: " + row.entity.id);
}
});
});
angular.forEach($scope.form.regions, function(region) {
$scope.gridApiRegions.grid.rows.map(function (row) {
if (row.entity.id == region.id) {
row.setSelected(true);
$log.log("row selected region: " + row.entity.id);
}
});
});
$scope.form.id = row.id;
}
$scope.close = function () {
$modalInstance.dismiss();
};
})
When I click the button to launch the modal, no modal shows up - instead I get a console error saying that it 'Cannot read property 'grid' of undefined' at the line with $scope.deptGridApi.grid.rows.map. Anyone have any suggestions for how to fix this? Thanks in advance!
EDIT: Getting selected rows using deptGridApi and gridApiRegions work - I wrote a test function activated by clicking a button in the modal, shown below:
$scope.getDeptandRegions= function(form){
$log.log($scope.gridApiRegions.selection.getSelectedRows());
$log.log($scope.deptGridApi.selection.getSelectedRows())
};
These log the selected rows fine, even though this also uses the grid APIs. Is it this is only fired after I press a button?
Cannot read property 'grid' of undefined' at the line with $scope.deptGridApi.grid.rows.map
means that :
$scope.deptGridApi === undefined
Looking at your code it is because onRegisterApi is either
Never called
Called with "undefined" as paramener
If you can provide a working code snippet I may be able to help you better
I am using dgrid and i am attempting to set the dataStore externally. When the page loads i call aliasTicket.load() to create the grid. At the time the grid is loading the datasource is null. When a query is executed the setAliasSource(aliasData); is set.
There are no errors however the grid is still empty. The aliasStore is being updated with data however it isn't being reflected on the grid even after the grid is refreshed. How can i get the data reflected in the grid after the query?
Javascript Object
var aliasTicket = (function (){
var aliasData = [];
require([ "dojo/store/Observable", "dojo/store/Memory"]);
var aliasStore = new dojo.store.Observable(new dojo.store.Memory({
data: aliasData,
idProperty: "id"
}));
return{
load:function(){
require([
........
], function(declare, Memory, OnDemandGrid, ColumnSet, Selection,
selector, Keyboard, DijitRegistry, editor, ColumnHider,
registry, Observable,lang) {
aliasData = this.aliasData;
var Store = this.aliasStore = new dojo.store.Observable(new dojo.store.Memory({
data: aliasData,
idProperty: "id"
}));
console.log(Store);
var CustomAliasNameGrid = declare([OnDemandGrid, selector, Selection, Keyboard, editor, DijitRegistry, ColumnHider]);
var aliasNameGrid = new CustomAliasNameGrid({
store: Store,
columns: {
id: {
label: "Id",
field: "id",
hidden: true,
autoSizeColumn: true
},
employeeTicketId: {
label: "Employee Ticket Id",
field: "employeeTicketId",
hidden: true,
autoSizeColumn: true
},
chkBox: selector({}),
aliasName: {
label: "Alias Names",
field: "aliasTicketName",
autoSizeColumn: true,
formatter: function(str) {
return str.replace(/\w\S*/g, function(txt) {
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
});
}
}
},
selectionMode: "none",
loadingMessage: "Loading data...",
noDataMessage: "No results found....",
allowSelectAll: true
}, "aliasNameGrid");
aliasNameGrid.refresh()
});
},
setAliasSource: function (data){
console.log(data);
this.aliasSource = data;
},
setAliasData: function (data){
this.aliasData = data;
},
getAliasSource: function (){
return this.aliasSource;
}
};
})();
Setting Data Store Data
aliasData = [{.....},
{.....},
{......];
require(["dijit/dijit"]);
aliasTicket.setAliasSource(aliasData);
dijit.byId('aliasNameGrid').refresh();
You are setting 'this.Store' to an object array, not a real 'dojo store' object. Following your code I can not see where you actually use 'this.Store'. Inside the grid code I do see a local variable named 'Store'.
So I'm not sure if I'm following your code example here but, you should 'set' the store of the grid and then refresh it. Something like this.
setAliasSource: function (data){
console.log(data);
this.Store = data;
dijit.byId('aliasNameGrid').set("store",new dojo.store.Observable(new dojo.store.Memory({ data: data,idProperty: "id"}));
dijit.byId('aliasNameGrid').refresh();
},