Angular custom filter with promise inside - javascript

What I am trying to achieve is using a filter that will return success or error from the ret() function. With the code below it returns {}, which is probably its promise.
.filter('postcode', ['$cordovaSQLite', '$q',
function($cordovaSQLite, $q) {
return function(PostCodeID) {
function ret() {
var def = $q.defer();
ionic.Platform.ready(function() {
if (window.cordova) {
var db = $cordovaSQLite.openDB({
name: "msddocapp.db"
});
} else {
var db = window.openDatabase("msddocapp.db", "1", "ES Database", 5 * 1024 * 1024);
}
var query = "select * from PostCode where ServerID = ?";
$cordovaSQLite.execute(db, query, [PostCodeID]).then(function(s) {
if (s.rows.length > 0) {
def.resolve(s.rows.item(0).Title);
}
}, function(e) {
console.log(e);
def.reject(PostCodeID);
})
});
return def.promise;
}
return ret().then(function(s) {
return s;
}, function(e) {
return e;
});
}
}]);
This filter is used for only one ng-repeat, so maybe I can bind a function to ng-repeat like:
HTML
{{getPostName(item.id)}}
Angular.js
function getPostName(id) {
return post[id].name;
}

Based on your comment
I got ID of PostCode in DB need to Get Value of it and put in place of ID like id = 1 then value is 00-000
You need to use a directive in order to make the call to the database and perform the DOM manipulation.
http://www.sitepoint.com/practical-guide-angularjs-directives/
Directive:
angular.directive('postcode', ['$cordovaSQLite', '$q', function($cordovaSQLite, $q){
return {
template: '{{getPostName(item.id)}}',
link: function(scope, elem, attrs) {
scope.getPostName = function(PostCodeID) {
var def = $q.defer();
ionic.Platform.ready(function() {
if (window.cordova) {
var db = $cordovaSQLite.openDB({
name: "msddocapp.db"
});
} else {
var db = window.openDatabase("msddocapp.db", "1", "ES Database", 5 * 1024 * 1024);
}
var query = "select * from PostCode where ServerID = ?";
$cordovaSQLite.execute(db, query, [PostCodeID]).then(function(s) {
if (s.rows.length > 0) {
def.resolve(s.rows.item(0).Title);
}
}, function(e) {
console.log(e);
def.reject(PostCodeID);
})
});
return def.promise.then(function(s) {
return s;
}, function(e) {
return e;
});
};
}
};
}]);
HTML:
<div data-postcode></div>
EDIT:
Since this particular directive is sharing the scope with its parent you just need to edit the template to use whatever you are passing in, i in this case:
HTML
<tr ng-repeat="i in data.contacts">
<td>
<div data-postcode></div>
</td>
</tr>
Directive
template: '{{getPostName(i.id)}}'

Related

Binding data after promise

I have a table that contains a button that activates/deactivates users. When I click that button it makes a API call to change the status of the user. What I am having trouble with is performing a live update with the view after the API call. Currently, the only way I see the text switch is when I refresh the page.
Here is my Admin.html where the text on the button should switch between 'Active' and 'Inactive' when the button is clicked.
<tr ng-repeat="account in $ctrl.account">
<td>{{account.FirstName}}</td>
<td>{{account.LastName}}</td>
<td class="text-center">
<button class="btn btn-sm btn-active-status" ng-class="account.Active === true ? 'btn-info' : 'btn-danger'" ng-bind="account.Active === true ? 'Active' : 'Inactive'" ng-click="$ctrl.UpdateActiveStatus(account.Id, account.Active)"></button>
</td>
</tr>
Here is my AdminController.js
app.component('admin', {
templateUrl: 'Content/app/components/admin/Admin.html',
controller: AdminController,
bindings: {
accounts: '='
}
})
function AdminController(AccountService) {
this.$onInit = function () {
this.account = this.accounts;
}
this.UpdateActiveStatus = function (id, status) {
var data = {
Id: id,
Active: !status
};
AccountService.UpdateActiveStatus(data).then(function (response) {
AccountService.GetAccounts().then(function (data) {
this.account = data;
});
});
};
}
Here is the fix to my problem. Please let me know if there is a better way than this.
function AdminController(AccountService) {
var controller = this;
this.$onInit = function () {
controller.account = this.accounts;
}
this.UpdateActiveStatus = function (id, status) {
var data = {
Id: id,
Active: !status
};
AccountService.UpdateActiveStatus(data).then(function (data) {
for (var i = 0; i < controller.account.length; i++) {
if (controller.account[i].Id === data.Id) {
controller.account[i].Active = data.Active;
}
}
});
};
}

Returning data from service dependancy using promises causes $digest iteration error

I have two services where one needs to query the other by an id for example.
This is what I currently have but I'm getting $digest iteration limits.
"Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!
Watchers fired in the last 5 iterations: []
Controller
angular.module('exampleApp')
.controller('ExampleCtrl', function ($scope, PeopleService, PlanService) {
$scope.plan = new Plan();
// or
$scope.plan = Plan.get({ id: 1 });
// Used to populate a <select> menu in the view
PeopleService.all().then(function(data) {
$scope.people = data;
});
});
PlanService
angular.module('exampleApp')
.factory('PlanService', function($resource, PeopleService){
// Resource example
// { id: 1, title: 'Example', assignee_id: 1 }
var Plan = $resource('/plans/:id', { id: '#id' }, {
'query': { method:'GET', isArray: true },
'get': { method:'GET', isArray: false },
'update': { method:'PUT' }
});
// Return the matching person from PeopleService
Plan.prototype.assignee = function() {
if(this.assignee_id) {
return PeopleService.find_by_id(this.assignee_id)
} else {
return null;
}
}
return Plan;
});
PeopleService
angular.module('exampleApp')
.service('PeopleService', function($http, $q){
var people_array = $q.defer();
// This list is reasonably static hence why I feel no need to make it it's own resource
// Contains an array of people objects for example
// { id: 1, name: 'Paul Smith', hourly_rate: 5.0, max_hours: 8 }
$http.get('/people/json').then(function(result) {
people_array.resolve(result.data);
});
this.all = function() {
return people_array.promise;
};
this.find_by_id = function(id) {
var def = $q.defer();
people_array.promise.then(function(data) {
// underscore.js find
var person = _.find(data, function(person) {
return parseInt(person.id) === parseInt(id);
})
def.resolve(person);
});
return def.promise;
};
});
View
<select name="person_id" class="form-control" ng-model="plan.assignee_id" ng-options="assignee.id as assignee.name for person in people" required>
</select>
{{ plan.assignee().name }}
Now the select populates no problem, however the plan.assignee.name causes the digest errors when using the select menu to select an option. I'm sure this is a misunderstanding I have with how promises work so I'd appreciate a nudge in the right direction.

display only the elements with checked = true

I have a list of items with the option to checked or unchecked.
<ion-item ng-repeat="sport in sports"
ng-click="toggleSportSelection(sport)">
{{:: sport.name}}
</ion-item>
if those items are unchecked you are unable to see them here
<div ng-show="sport.checked" ng-repeat="sport in sports">
{{sport.name}}
</div>
those items has been saved in a DB every time you unchecked them.
The reason why I am here, is because the default behavior of the items is checked = true so it doesn't matter if they are saved in a DB, if you refresh the page, all the items are set up to checked = true again.
So what can I do in order to avoid that behavior and that the app recognizes once the items are unchecked or checked ?
this is part of the controller
.controller('SportsController', function($scope, SportsFactory,
AuthFactory) {
SportsFactory.getSportChecked(customer).then(function(sportChecked) {
_.each(sports, function(sport) {
var intersectedSports = _.intersection(sport.id, sportChecked),
checkedSportObjects = _.filter(sport, function(sportObj) {
return _.includes(intersectedSports, sportObj);
});
_.each(checkedSportObjects, function(sport) {
$scope.sports.push(sport);
});
});
//here is the part where the default behavior is checked = true
if (sports.length) {
$scope.sports = _.map(sports, function(sport) {
sport.checked = true;
return sport;
});
}
$scope.toggleSportSelection = function(sport) {
var params = {};
params.user = $scope.customer.customer;
params.sport = sport.id;
sport.checked = !sport.checked;
SportsFactory.setSportChecked(params);
};
});
UPDATE
service.js
setSportChecked: function(params) {
var defer = $q.defer();
$http.post(CONSTANT_VARS.BACKEND_URL + '/sports/checked', params)
.success(function(sportChecked) {
LocalForageFactory.remove(CONSTANT_VARS.LOCALFORAGE_SPORTS_CHECKED, params);
defer.resolve(sportChecked);
})
.error(function(err) {
console.log(err);
defer.reject(err);
});
return defer.promise;
},
and the NODEJS part
sportChecked: function(params) {
var Promise = require('bluebird');
return new Promise(function(fullfill, reject) {
console.time('sportChecked_findOne');
SportSelection.findOne({
user: params.user
}).exec(function(err, sportChecked) {
console.timeEnd('sportChecked_findOne');
var newSport;
if (err) {
reject(new Error('Error finding user'));
console.error(err);
}else if (sportChecked) {
newSport = sportChecked.sport || [];
console.log(newSport);
console.time('sportChecked_update');
if (_.includes(sportChecked.sport, params.sport)) {
console.log('Sport already exists');
console.log(sportChecked.sport);
sportChecked.sport = _.pull(newSport, params.sport);
// sportChecked.sport = _.difference(newSport, params.sport);
console.log(sportChecked.sport);
}else {
newSport.push(params.sport);
sportChecked.sport = newSport;
}
SportSelection.update({
user: params.user
},
{
sport: newSport
}).exec(function(err, sportCheckedUpdated) {
console.timeEnd('sportChecked_update');
if (err) {
reject(new Error('Error on sportChecked'));
}else {
fullfill(sportCheckedUpdated);
}
});
if (sportChecked.sport) {
sportChecked.sport.push(params.sport);
console.log('New sport added');
}else {
sportChecked.sport = [params.sport];
}
}else {
console.time('sportChecked_create');
SportSelection.create({
sport: [params.sport],
user: params.user
}).exec(function(err, created) {
console.timeEnd('sportChecked_create');
if (err) {
reject(new Error('Error on sportChecked'));
}else {
fullfill(created);
}
});
}
});
});
},
I am using lodash so I will appreciate if you can assist me with that.
My issue itself is: it doesn't matter if the items are unchecked, once you refresh the page, all the items will be set up to checked = true again.
Someone says that I can use _.difference, but how ? or what can I do? I am here to read your suggestions.
Something like this?
.controller('SportsController', function($scope, SportsFactory) {
// get a list of all sports, with default value false
SportsFactory.getAllSports().then(function(sports){
$scope.sports = sports;
// set all items to unchecked
angular.each($scope.sports, function(sport) {
sport.checked = false;
});
// get a list of checked sports for customer
SportsFactory.getCheckedSports(customer).then(function(checkedSports)
{
// set the sports in your list as checked
angular.each(checkedSports, function(checkedSport){
var sport = _.findWhere($scope.sports, {id: checkedSport.id});
sport.checked = true;
});
});
$scope.toggleSportSelection = function(sport) {
// do your toggle magic here
};
});
In your view use a filter:
<div ng-repeat="sport in sports | filter:{checked:true}">
{{sport.name}}
</div>

EmberJS: Refreshing a model?

Hello again everyone.
EDIT: I want to emphasize that I can find no docs on the solution for this.
I am using a route to perform a search query to my server. The server does all the data logic and such and returns a list of objects that match the keywords given. I am taking those results and feeding them to the model so that I can use the {{#each}} helper to iterate over each result.
The problem I am having is that the model does not want to refresh when the searchText (search input) changes. I've tried several things. I'm not worried about creating too many ajax requests as my server performs the search query in 2ms. Here's what I have now.
App.SearchView = Ember.View.extend({...
EDIT:
Thank you for the answer.
App.SearchView = Ember.View.extend({
didInsertElement: function () {
this._super();
Ember.run.scheduleOnce('afterRender', this, this.focusSearch);
},
focusSearch: function () {
$(".searchInput").focus().val(this.get("controller").get('searchTextI'));
}
});
App.SearchRoute = Ember.Route.extend({
model: function () {
return this.controllerFor('search').processSearch();
}
});
App.SearchController = Ember.ArrayController.extend({
searchTextI: null,
timeoutid: null,
processid: null,
updateSearch: function () {
if(this.get('timeoutid')) {clearTimeout(this.get('timeoutid')); }
var i = this.get('searchTextI');
var sc = this;
clearTimeout(this.get('processid'));
this.controllerFor('index').set('searchText', i); //set the search text on transition
if(i.length < 3) {
this.set('timeoutid', setTimeout(function () {
sc.controllerFor('index').set("transitioningFromSearch", true);
sc.transitionToRoute('index');
}, 1500));
} else {
var self = this;
this.set('processid', setTimeout(function() {
self.processSearch().then(function(result) {
self.set('content', result);
});
}, 1000));
}
}.observes('searchTextI'),
processSearch: function () {
return $.getJSON('http://api.*********/search', { 'token': guestToken, 'search_query': this.get('searchTextI') }).then(function(data) { if(data == "No Results Found.") { return []; } else { return data; } }).fail(function() { return ["ERROR."]; });
}
});
Don't observe anything within a route and don't define any computed properties. Routes are not the place for these. Apart from that, the model doesn't fire because controller is undefined.
One way to achieve what you want:
App.SearchRoute = Ember.Route.extend({
model: function () {
this.controllerFor('search').searchQuery();
}.observes('controller.searchText') //not triggering an ajax request...
});
App.SearchController = Ember.ArrayController.extend({
searchQuery: function() {
return $.getJSON('http://api.**************/search', { 'token': guestToken, 'search_query': t }).fail(function() {
return null; //prevent error substate.
});
}
onSearchTextChange: function() {
var controller = this;
this.searchQuery().then(function(result) {
controller.set('content', result);
});
}.observes('searchText')
});
Putting an observes on the model hook is not going to do anything. You should simply do what you were thinking of doing and say
processSearch: function () {
this.set('content', $.getJSON....);
}

angular-ui/bootstrap: Testing a controller that uses a dialog

I've a controller that uses a Dialog from angular-ui/bootstrap:
function ClientFeatureController($dialog, $scope, ClientFeature, Country, FeatureService) {
//Get list of client features for selected client (that is set in ClientController)
$scope.clientFeatures = ClientFeature.query({clientId: $scope.selected.client.id}, function () {
console.log('getting clientfeatures for clientid: ' + $scope.selected.client.id);
console.log($scope.clientFeatures);
});
//Selected ClientFeature
$scope.selectedClientFeature = {};
/**
* Edit selected clientFeature.
* #param clientFeature
*/
$scope.editClientFeature = function (clientFeature) {
//set selectedClientFeature for data binding
$scope.selectedClientFeature = clientFeature;
var dialogOpts = {
templateUrl: 'partials/clients/dialogs/clientfeature-edit.html',
controller: 'EditClientFeatureController',
resolve: {selectedClientFeature: function () {
return clientFeature;
} }
};
//open dialog box
$dialog.dialog(dialogOpts).open().then(function (result) {
if (result) {
$scope.selectedClientFeature = result;
$scope.selectedClientFeature.$save({clientId: $scope.selectedClientFeature.client.id}, function (data, headers) {
console.log('saved.');
}, null);
}
});
};
});
I'm almost completely new to testing, and figured that maybe I need to test two things:
That a dialog opens when $scope.editClientFeature() is called
That $save is called successfully after a dialog is closed and returns a 'result'
My really messed up test now looks like this:
describe('ClientFeatureController', function () {
var scope, $dialog, provider;
beforeEach(function () {
inject(function ($controller, $httpBackend, $rootScope, _$dialog_) {
scope = $rootScope;
$dialog = _$dialog_;
//mock client
scope.selected = {};
scope.selected.client = {
id: 23805
};
$httpBackend.whenGET('http://localhost:3001/client/' + scope.selected.client.id + '/clientfeatures').respond(mockClientFeatures);
$controller('ClientFeatureController', {$scope: scope});
$httpBackend.flush();
});
});
it('should inject dialog service from angular-ui-bootstrap module', function () {
expect($dialog).toBeDefined();
console.log($dialog); //{}
});
var dialog;
var createDialog = function (opts) {
dialog = $dialog.dialog(opts);
};
describe('when editing a clientfeature', function () {
createDialog({});
console.log(dialog); //undefined
// var res;
// var d;
// beforeEach(function () {
// var dialogOpts = {
// template: '<div>dummy template</div>'
// };
// console.log(dialog);
// d = $dialog.dialog(dialogOpts);
// d.open();
// });
//
// it('should open a dialog when editing a client feature', function () {
// expect(d.isOpen()).toBe(true);
// });
});
});
The immediate problem now is that I'm unable to create/open a dialog. I get the following error:
Chrome 25.0 (Mac) ClientFeatureController when editing a clientfeature encountered a declaration exception FAILED
TypeError: Cannot call method 'dialog' of undefined
It would be great if someone has already written a test for a similar use case and can provide me with an example as I'm pretty lost.
Thanks,
Shaun
I was struggling with the same problem until right now, after trolling the the github repo i found the dialog tests and used that as a starting point :
var $dialog,$scope,$httpBackend;
beforeEach(module('ui.bootstrap.dialog'));
beforeEach(function(){
inject(function (_$dialog_, _$httpBackend_, $controller){
$dialog = _$dialog_;
$httpBackend = _$httpBackend_;
$httpBackend.expectGET('/appServer/list')
.respond([{
id:1,
name:'test1'
},
{
id:2,
name:'test2'
},
{
id:3,
name:'test3'
}]);
//setup controller scope
scope = {};
ServerCtrl = $controller('ServerCtrl', {
$scope: scope,
$dialog:$dialog
});
});
});
I also prefer a proper mock. When it is not available, i patch the service
To test this:
$dialog.messageBox(title, msg, btns)
.open()
.then(function (result) {
if (result == 'ok') {
// block executed if user click OK
}
});
You can patch $dialog like this:
$dialog.messageBox = function (title, msg, btns) {
return {
open: function () {
return {
then: function (callback) {
callback('ok'); // 'ok' will be set to param result
}
}
}
}
};
Personally I try to mock all services out. If the ui-bootstrap project does not provide a $dialog mock, you should open a bug ticket there and ask them for one. However creating one is as easy.
The mock service should have fake methods that do nothing but return promises. It should also give you a method to flush all asynchronous methods to make it easier to do synchronous testing.
I find it clearest to write my own mock of the dialog. Here's an example of mocking out a dialog to simulate "yes" being chosen.
Code under test
.controller('AdminListingCtrl', function AdminListingController($scope, $dialog, houseRepository) {
$scope.houses = houseRepository.query();
$scope.remove = function (house) {
var dlg = $dialog.messageBox('Delete house', 'Are you sure?', [
{label: 'Yep', result: 'yes'},
{label: 'Nope', result: 'no'}
]);
dlg.open().then(function (result) {
if (result == 'yes') {
houseRepository.remove(house.id);
$scope.houses = houseRepository.query();
}
});
};
}
Tests
describe('when deleting a house', function () {
var fakeDialog = {
open: function()
{
return {
then: function(callback) {
callback("yes");
}
};
}
};
beforeEach(inject(function($dialog) {
spyOn($dialog, 'messageBox').andReturn(fakeDialog);
}));
it('should call the remove method on the houseRepository', function () {
scope.remove({id: 99});
expect(houseRepository.remove).toHaveBeenCalledWith(99);
});
// etc
});

Categories

Resources