Underscore 'after' function - backbone.js

I have code similar to this
(function(exports, $, Backbone, document){
"use strict";
var modals = {
"Alerts.alert_delete" : (function() {
var self = this;
return {
template : {
count : this.collection.length,
models : this.collection.models
},
events : {
"click .confirm" : function(event) {
event.preventDefault();
var modal = this,
finished = _.after(modal.collection.length, self.reDraw);
// Models are succesfully delete, but finished is not completed
this.collection.each(function(model) {
modal.collection.sync('delete', model, { success : finished });
});
}
}
};
})
};
exports.app.modal = function(name, context) {
return modals[name].call(context);
};
}(this, jQuery, Backbone, document));
Please ignore the abstraction, this is to allow me to use one generic view for all my modals whilst keeping unique logic abstracted. I am at a loss as to why the _.after function is not completing when it has been called the correct number of times. self in this instance is a reference to the parent view.
Can someone shed some light on this for me?

Related

Implementing notification alerts in angularjs

I was wondering how an error alert would be implemented using angularjs.
Required functionality:
An alertQueue consists of all the alerts to be displayed to the user. These alerts are deleted from the queue after a span of 3 seconds. The user himself can close the alert by clicking the close button.
This AlertService must be the core service. Alerts are rendered in the view as <alert-list></alert-list>i.e using a component alertList.
Should be able to update alerts from other controllers like: AlertService.alert("my alert").
so far what I have done?
angular.
module('core').
factory('AlertService', [function() {
var alertQueue = [];
var addAlert = function(message, type){
message = {message: message, type: type};
alertQueue.push(message)
};
var deleteAlert = function(alert){
alertQueue.splice(alertQueue.indexOf(alert), 1);
};
return{
warning: function(msg){
addAlert(msg, "warning");
},
success: function(msg){
addAlert(msg, "success");
},
removeAlert: function(alert){
deleteAlert(alert);
},
getAlerts: function(){
return alertQueue;
}
}
}]);
angular.
module('alertApp').
component('alertList', {
templateUrl: '/static/js/app/aurora-alert/aurora-alert.template.html',
controller: ['$routeParams','$scope', 'Aurora',
function AlertController($routeParams, $scope, AlertService) {
var self = this;
self.alertQueue = AlertService.alertQueue;
self.alert = function(){
var message = arguments[0];
AlertService.warning(message);
};
self.removeAlert = function(alert) {
AlertService.removeAlert(alert);
};
}
]
});
I know that I'm doing something wrong in the above code and in its logic. I said above that I require the <alert-list></alert-list> component. So the alertService is injected as a dependency into alertController. But how am I going to raise the alert from other controllers? I know we can use $scope.$broadcast but that doesn't feel right.
Please explain how to achieve this? No third party libraries are to be used.
I think you are going about it only slightly incorrectly. Your alert-list should be responsible only for displaying and removing alerts, not for creating them. Leave the creation of alerts to your controllers
So for example, if you run into an error with an ApiSerivce:
DemoCtrl(AlertService, ApiService) {
ApiService.submitForm({some:data}).then(function() {
//something successfull happened
}).catch(function(error) {
AlertService.warning("Something bad happened calling the API serivce");
});
}
Then you can change your AlertService to broadcast an event when a new alert is created that the alert-list can listen to:
factory('AlertService', ["$rootScope", function($rootScope) {
var alertQueue = [];
var addAlert = function(message, type){
message = {message: message, type: type};
alertQueue.push(message)
$rootScope.$broadcast("new-alert"); //notify the list that there are new alerts
};
This is how you would listen to it in your alert-list:
$scope.$on("new-alert", function() {
self.alertQueue = AlertService.alertQueue;
});
This way, as soon as an alert is created, the alert-list is instantly updated with the latest queue of alerts.
You would probably want to do the same thing for alert deletion.

calling a function when AngularUI Bootstrap modal has been dismissed and animation has finished executing

I'm using the Angular UI bootstrap modal and I ran into a bit of a problem.
I want to call a function when the bootstrap modal dismiss animation is finished. The code block below will call the cancel() function as soon as the modal starts to be dismissed - and NOT when the modal dismiss animation has finished.
Angular UI does not use events, so there is no 'hidden.bs.modal' event being fired (at least, not to my knowledge).
var instance = $modal.open({...});
instance.result.then(function(data) {
return success(data);
}, function() {
return cancel();
})
The cancel() block immediately runs when the modal starts to close. I need code to execute when the closing animation for the Bootstrap modal finishes.
How can I achieve this with angular UI?
Component for reference:
https://angular-ui.github.io/bootstrap/#/modal
Thanks!
A little late but hope it still helps! You can hijack the uib-modal-window directive and check when its scope gets destroyed (it is an isolated scope directive). The scope is destroyed when the modal is finally removed from the document. I would also use a service to encapsulate the functionality:
Service
app.service('Modals', function ($uibModal, $q) {
var service = this,
// Unique class prefix
WINDOW_CLASS_PREFIX = 'modal-window-interceptor-',
// Map to save created modal instances (key is unique class)
openedWindows = {};
this.open = function (options) {
// create unique class
var windowClass = _.uniqueId(WINDOW_CLASS_PREFIX);
// check if we already have a defined class
if (options.windowClass) {
options.windowClass += ' ' + windowClass;
} else {
options.windowClass = windowClass;
}
// create new modal instance
var instance = $uibModal.open(options);
// attach a new promise which will be resolved when the modal is removed
var removedDeferred = $q.defer();
instance.removed = removedDeferred.promise;
// remember instance in internal map
openedWindows[windowClass] = {
instance: instance,
removedDeferred: removedDeferred
};
return instance;
};
this.afterRemove = function (modalElement) {
// get the unique window class assigned to the modal
var windowClass = _.find(_.keys(openedWindows), function (windowClass) {
return modalElement.hasClass(windowClass);
});
// check if we have found a valid class
if (!windowClass || !openedWindows[windowClass]) {
return;
}
// get the deferred object, resolve and clean up
var removedDeferred = openedWindows[windowClass].removedDeferred;
removedDeferred.resolve();
delete openedWindows[windowClass];
};
return this;
});
Directive
app.directive('uibModalWindow', function (Modals) {
return {
link: function (scope, element) {
scope.$on('$destroy', function () {
Modals.afterRemove(element);
});
}
}
});
And use it in your controller as follows:
app.controller('MainCtrl', function ($scope, Modals) {
$scope.openModal = function () {
var instance = Modals.open({
template: '<div class="modal-body">Close Me</div>' +
'<div class="modal-footer"><a class="btn btn-default" ng-click="$close()">Close</a></div>'
});
instance.result.finally(function () {
alert('result');
});
instance.removed.then(function () {
alert('closed');
});
};
});
I also wrote a blog post about it here.

Proper place for data-saving logic in AngularJS

App design question. I have a project which has a very large number of highly customized inputs. Each input is implemented as a directive (and Angular has made this an absolute joy to develop).
The inputs save their data upon blur, so there's no form to submit. That's been working great.
Each input has an attribute called "saveable" which drives another directive which is shared by all these input types. the Saveable directive uses a $resource to post data back to the API.
My question is, should this logic be in a directive at all? I initially put it there because I thought I would need the saving logic in multiple controllers, but it turns out they're really happening in the same one. Also, I read somewhere (lost the reference) that the directive is a bad place to put API logic.
Additionally, I need to introduce unit testing for this saving logic soon, and testing controllers seems much more straightforward than testing directives.
Thanks in advance; Angular's documentation may be… iffy… but the folks in the community are mega-rad.
[edit] a non-functional, simplified look at what I'm doing:
<input ng-model="question.value" some-input-type-directive saveable ng-blur="saveModel(question)">
.directive('saveable', ['savingService', function(savingService) {
return {
restrict: 'A',
link: function(scope) {
scope.saveModel = function(question) {
savingService.somethingOrOther.save(
{id: question.id, answer: question.value},
function(response, getResponseHeaders) {
// a bunch of post-processing
}
);
}
}
}
}])
No, I don't think the directive should be calling $http. I would create a service (using the factory in Angular) OR (preferably) a model. When it is in a model, I prefer to use the $resource service to define my model "classes". Then, I abstract the $http/REST code into a nice, active model.
The typical answer for this is that you should use a service for this purpose. Here's some general information about this: http://docs.angularjs.org/guide/dev_guide.services.understanding_services
Here is a plunk with code modeled after your own starting example:
Example code:
var app = angular.module('savingServiceDemo', []);
app.service('savingService', function() {
return {
somethingOrOther: {
save: function(obj, callback) {
console.log('Saved:');
console.dir(obj);
callback(obj, {});
}
}
};
});
app.directive('saveable', ['savingService', function(savingService) {
return {
restrict: 'A',
link: function(scope) {
scope.saveModel = function(question) {
savingService.somethingOrOther.save(
{
id: question.id,
answer: question.value
},
function(response, getResponseHeaders) {
// a bunch of post-processing
}
);
}
}
};
}]);
app.controller('questionController', ['$scope', function($scope) {
$scope.question = {
question: 'What kind of AngularJS object should you create to contain data access or network communication logic?',
id: 1,
value: ''
};
}]);
The relevant HTML markup:
<body ng-controller="questionController">
<h3>Question<h3>
<h4>{{question.question}}</h4>
Your answer: <input ng-model="question.value" saveable ng-blur="saveModel(question)" />
</body>
An alternative using only factory and the existing ngResource service:
However, you could also utilize factory and ngResource in a way that would let you reuse some of the common "saving logic", while still giving you the ability to provide variation for distinct types of objects / data that you wish to save or query. And, this way still results in just a single instantiation of the saver for your specific object type.
Example using MongoLab collections
I've done something like this to make it easier to use MongoLab collections.
Here's a plunk.
The gist of the idea is this snippet:
var dbUrl = "https://api.mongolab.com/api/1/databases/YOURDB/collections";
var apiKey = "YOUR API KEY";
var collections = [
"user",
"question",
"like"
];
for(var i = 0; i < collections.length; i++) {
var collectionName = collections[i];
app.factory(collectionName, ['$resource', function($resource) {
var resourceConstructor = createResource($resource, dbUrl, collectionName, apiKey);
var svc = new resourceConstructor();
// modify behavior if you want to override defaults
return svc;
}]);
}
Notes:
dbUrl and apiKey would be, of course, specific to your own MongoLab info
The array in this case is a group of distinct collections that you want individual ngResource-derived instances of
There is a createResource function defined (which you can see in the plunk and in the code below) that actually handles creating a constructor with an ngResource prototype.
If you wanted, you could modify the svc instance to vary its behavior by collection type
When you blur the input field, this will invoke the dummy consoleLog function and just write some debug info to the console for illustration purposes.
This also prints the number of times the createResource function itself was called, as a way to demonstrate that, even though there are actually two controllers, questionController and questionController2 asking for the same injections, the factories get called only 3 times in total.
Note: updateSafe is a function I like to use with MongoLab that allows you to apply a partial update, basically a PATCH. Otherwise, if you only send a few properties, the entire document will get overwritten with ONLY those properties! No good!
Full code:
HTML:
<body>
<div ng-controller="questionController">
<h3>Question<h3>
<h4>{{question.question}}</h4>
Your answer: <input ng-model="question.value" saveable ng-blur="save(question)" />
</div>
<div ng-controller="questionController2">
<h3>Question<h3>
<h4>{{question.question}}</h4>
Your answer: <input ng-model="question.value" saveable ng-blur="save(question)" />
</div>
</body>
JavaScript:
(function() {
var app = angular.module('savingServiceDemo', ['ngResource']);
var numberOfTimesCreateResourceGetsInvokedShouldStopAt3 = 0;
function createResource(resourceService, resourcePath, resourceName, apiKey) {
numberOfTimesCreateResourceGetsInvokedShouldStopAt3++;
var resource = resourceService(resourcePath + '/' + resourceName + '/:id',
{
apiKey: apiKey
},
{
update:
{
method: 'PUT'
}
}
);
resource.prototype.consoleLog = function (val, cb) {
console.log("The numberOfTimesCreateResourceGetsInvokedShouldStopAt3 counter is at: " + numberOfTimesCreateResourceGetsInvokedShouldStopAt3);
console.log('Logging:');
console.log(val);
console.log('this =');
console.log(this);
if (cb) {
cb();
}
};
resource.prototype.update = function (cb) {
return resource.update({
id: this._id.$oid
},
angular.extend({}, this, {
_id: undefined
}), cb);
};
resource.prototype.updateSafe = function (patch, cb) {
resource.get({id:this._id.$oid}, function(obj) {
for(var prop in patch) {
obj[prop] = patch[prop];
}
obj.update(cb);
});
};
resource.prototype.destroy = function (cb) {
return resource.remove({
id: this._id.$oid
}, cb);
};
return resource;
}
var dbUrl = "https://api.mongolab.com/api/1/databases/YOURDB/collections";
var apiKey = "YOUR API KEY";
var collections = [
"user",
"question",
"like"
];
for(var i = 0; i < collections.length; i++) {
var collectionName = collections[i];
app.factory(collectionName, ['$resource', function($resource) {
var resourceConstructor = createResource($resource, dbUrl, collectionName, apiKey);
var svc = new resourceConstructor();
// modify behavior if you want to override defaults
return svc;
}]);
}
app.controller('questionController', ['$scope', 'user', 'question', 'like',
function($scope, user, question, like) {
$scope.question = {
question: 'What kind of AngularJS object should you create to contain data access or network communication logic?',
id: 1,
value: ''
};
$scope.save = function(obj) {
question.consoleLog(obj, function() {
console.log('And, I got called back');
});
};
}]);
app.controller('questionController2', ['$scope', 'user', 'question', 'like',
function($scope, user, question, like) {
$scope.question = {
question: 'What is the coolest JS framework of them all?',
id: 1,
value: ''
};
$scope.save = function(obj) {
question.consoleLog(obj, function() {
console.log('You better have said AngularJS');
});
};
}]);
})();
In general, things related to the UI belong in a directive, things related to the binding of input and output (either from the user or from the server) belong in a controller, and things related to the business/application logic belong in a service (of some variety). I've found this separation leads to very clean code for my part.

How to dynamically update a Marionette CollectionView when the underlying model changes

Seems like this should be obvious, but there seem to be so many different examples out there, most of which cause errors for me, making me think they are out of date. The basic situation is that I have a MessageModel linked to a MessageView which extends ItemView, MessageCollection linked to a MessageCollectionView (itemView: MessageView). I have a slightly unusual scenario in that the MessageCollection is populated asynchronously, so when the page first renders, it is empty and a "Loading" icon would be displayed. Maybe I have things structured incorrectly (see here for the history), but right now, I've encapsulated the code that makes the initial request to the server and receives the initial list of messages in the MessageCollection object such that it updates itself. However, I'm not clear, given this, how to trigger displaying the view. Obviously, the model shouldn't tell the view to render, but none of my attempts to instantiate a view and have it listen for modelChange events and call "render" have worked.
I have tried:
No loading element, just display the CollectionView with no elements on load, but then it doesn't refresh after the underlying Collection is refreshed.
Adding modelEvents { 'change': 'render' } to the view --> Uncaught TypeError: Object function () { return parent.apply(this, arguments); } has no method 'on'
I also tried this.bindTo(this.collection..) but "this" did not nave a bindTo method
Finally, I tried, in the view.initialize: _.bindAll(this); this.model.on('change': this.render); --> Uncaught TypeError: Object function () { [native code] } has no method 'on'
Here is the code
Entities.MessageCollection = Backbone.Collection.extend({
defaults: {
questionId: null
},
model: Entities.Message,
initialize: function (models, options) {
options || (options = {});
if (options.title) {
this.title = options.title;
}
if (options.id) {
this.questionId = options.id;
}
},
subscribe: function () {
var self = this; //needed for proper scope
QaApp.Lightstreamer.Do('subscribeUpdate', {
adapterName: 'QaAdapter',
parameterValue: this.questionId,
otherStuff: 'otherstuff',
onUpdate: function (data, options) {
console.log("calling sync");
var obj = JSON.parse(data.jsonString);
self.set(obj.Messages, options);
self.trigger('sync', self, obj.Messages, options);
}
});
},
});
Views.MessageCollectionView = Backbone.Marionette.CollectionView.extend({
itemView: Views.MessageView,
tagName: 'ul',
// modelEvents: {
// 'change': 'render'
// },
onAfterItemAdded: function (itemView) {
this.$el.append(itemView.el);
}
});
var Api = {
subscribe: function (id) {
var question = new QaApp.Entities.Question(null, { id: id });
question.subscribe();
var questionView = new QaApp.Views.QuestionView(question);
QaApp.page.show(questionView);
}
};
I am very grateful for all the help I've received already and thanks in advance for looking.
Try this:
var questionView = new QaApp.Views.QuestionView({
collection: question
});

backbone extension file loads, some helpers work, one doesn't

I have a backbone-extend.js file that I load in the require define in app.js. It has a Backbone.View extender class defining a couple helper methods. Two of the methods work just fine in my views, one always errors with Uncaught TypeError: Object [object global] has no method 'gotoUrl'. Why would just this one method be not defined but the other two are working fine? Do you see any issue in this code...
// Filename: backbone-extend.js
define([
'jquery',
'underscore',
'backbone'
], function($, _, Backbone) {
var helpers = {
eventSyncError: function(model,response,options) {
console.log('Sync error='+response.statusText);
$('#server-message').css({'color':'red', 'font-weight':'bold'}).text(response.statusText);
},
gotoUrl: function(url,delay) {
var to = setTimeout(function() { Backbone.history.navigate(url, true); }, delay);
},
getFormData: function(form) {
var unindexed_array = form.serializeArray();
var indexed_array = {};
$.map(unindexed_array, function(n, i) {
indexed_array[n['name']] = n['value'];
});
return indexed_array;
}
}
_.extend(Backbone.View.prototype, helpers);
});
Here is the code in view that calls it...
eventSyncMemberSaved: function(model,response,options) {
console.log("Member saved!");
$('#server-message').css({'color':'green', 'font-weight':'bold'}).text("Member saved!");
this.gotoUrl('members',2000);
//setTimeout(function() { Backbone.history.navigate('members', true); }, 2000);
},
saveMember: function() {
var data = this.getFormData($('#member-form'));
this.member.save(data, { success: this.eventSyncMemberSaved });
},
Thanks in advance for your help. I'm stuck.
The context of this is different in the success callback.
It no longer points to the view as it points to the xhr object
So it throws an error as that method is not available on the xhr object
To resolve it you need to bind the context of this to the success handler so that it points to the right object.
So in the initialize of the view add this code
initialize: function() {
_.bindAll(this, 'eventSyncMemberSaved');
}

Resources