Backbone.js - Liking a post, code refactoring - backbone.js

Right now I'm using this code to Like a post. I'm Using jQuery methods to change the Like to Unlike and to change the Like count
View
Streaming.Views.StreamsIndex = Backbone.View.extend({
events: {
'click .like-icon': 'post_liked',
},
initialize: function(){
this.model = new Streaming.Models.StreamsIndex();
this.model.bind('post_likeSuccess', this.post_likeSuccess);
},
post_liked: function(event) {
event.preventDefault();
event.stopPropagation();
current_target = $(event.currentTarget);
liked_id = current_target.attr("id");
href = current_target.attr('href');
this.model.like(href, liked_id); // calls model to send API call for Like
},
post_likeSuccess: function(data, liked_id) {
$("#" + liked_id).attr({
"href": data.unlike,
"title": "Unlike",
"rel": "Unlike",
"class": "likehead-ico_active" // changing like icon
});
//changing like count
$("#"+ liked_id+"_count").text(parseInt($("#"+ liked_id+"_count").text()) + 1);
}
});
Model:
Streaming.Models.StreamsIndex = Backbone.Model.extend({
like: function(href, liked_id) {
var self = this;
$.ajax({
url: href,
type: "POST",
dataType: "json",
async: false,
success: function (data) {
self.trigger('post_likeSuccess', data, liked_id);
},
error: function (data) {
self.trigger('post_likeFail', data, liked_id);
alert("This action was not performed");
}
});
}
});
Is there a better way I can do this?
After liking a post can I change the Like text to unLike, Change the like count in a better way without using jquery?

There are several issues with this code. I'll try address them one by one.
Looks like your Streaming.Views.StreamsIndex has several posts in it. It should be broken down into a component views, that are rendered through a collection, so that each model in the collection is bound to a view. You could, maybe call it Streaming.Views.StreamPost
Your initialize method would have:
this.collection = this.model.posts(); // Or something to this effect
Your render method would have:
// addPost is a function
// that takes 'post' as a parameter
// build the corresponding view object
// and appends it to the posts container
this.collection.each(this.addPost, this)
// example of how addPost looks
var view = new Streaming.Views.StreamPost({model: post});
this.$('#posts-container').append(view.render().el);
The event listener 'click .like-icon': 'post_liked' should on the new component view Streaming.Views.StreamsIndex, instantiated in the addPost above. With this, you don't have to use the ugly current_target = $(event.currentTarget) hack. You always do this.model.get('id') to get the id of the post. The thumb rule he is to not use jQuery or any other form of raw DOM manipulation when using Backbone. That is what views & templates are for! Adjust your template by putting a little logic (as little as possible) to show something if post is liked, and show something else if post is not liked yet. The job of deciding whether a post is liked or not, is to be done by the Post model. I usually write wrapper methods in views that call relevant methods on the model.
Instead of using custom events like post_likeSuccess, update the state of your model and re-render your view. If you updated your templates like I mentioned above, then re-render would take care of all the DOM manipulation you are doing.

Related

How to pass Backbone.js model data to Bootbox with Handlebars.js?

I have a marionette view that have a method to create a new model from a bootbox. Now i need to be able to edit the model from the bootbox, how can i I pass the current model data to the box?
This is some of my current code:
Module.Views.Chaptersx = Marionette.CompositeView.extend({
template: Module.Templates['documents/create/course/chapter/index'],
childView: Module.Views.ChapterItemx,
childViewContainer: "#chaptersCollection",
events: {
'click .chapters-create': 'create',
//'click #uploadFilesChapters': 'startUpload'
},
create: function (evt) {
console.log('create');
evt.preventDefault();
var me = this;
var box = bootbox.dialog({
show: false,
title: "Nueva Seccion",
message: Module.Templates['documents/create/course/chapter/chapterModal'],
buttons: {
success: {
label: "Guardar",
className: "btn-success",
callback: function () {
var chapterNo = $('#cn').val();
var chapterName = $('#chapterName').val();
var chapter = new Module.Models.Chapter({
chapterNo: chapterNo,
chapterName: chapterName,
});
me.collection.add(chapter);
}
}
}
});
box.on("show.bs.modal", function () {
console.log('numbers');
var number = (me.collection.size() + 1);
$('#cn').val(number);
});
box.modal('show');
},
TL;DR - use model's custom events or an event bus to pass the data.
You can reference this.model in the view, which is somewhat of a compromise (you're tying the view and the model).
You could pass the data via the event object's data property, but for that you're gonna have to extend some methods and get into backbone's nitty gritty.
Use a data- attribute on the element:
<div class="chapters-create" data-cats></div>
create: function (evt) {
var cats = $(evt.currentTarget).data('cats');
// ...
}
… which is considered bad habit by the way - you're still tying data to the DOM (or model to view, MVC speaking).
Well, I don't like either of the above, as they tend to have high coupling - I'd do it with custom events on a shared model resides at a higher level.
I don't know where the data comes from, but bottom line - shoot it in a custom event, or, better yet, use an event bus, like the one offered by marionette.js.
You need to create another view, call it EditView or something, render it, and provide the view.el as a message option to bootbox. However, the whole thing feels like a hack to me, and I think that it's better to implement a modalRegion and manage the modals yourself.

Backbone - user case

Say a user is going down a page and checking off and selecting items.
I have a Backbone model object, and each time the user selects something I want to update the object.
I have this in a separate JavaScript file that I source in my HTML:
var app = {};
var newLineup = null;
var team = document.getElementsByName('team');
app.Lineup = Backbone.Model.extend({
defaults: {
team: team,
completed: false
},
idAttribute: "ID",
initialize: function () {
console.log('Book has been intialized');
this.on("invalid", function (model, error) {
console.log("Houston, we have a problem: " + error)
});
},
constructor: function (attributes, options) {
console.log('document',document);
console.log('Book\'s constructor had been called');
Backbone.Model.apply(this, arguments);
},
validate: function (attr) {
if (attr.ID <= 0) {
return "Invalid value for ID supplied."
}
},
urlRoot: 'http://localhost:3000/api/lineups'
});
function createNewLineupInDatabase(){
newLineup = new app.Lineup({team: team, completed: false});
newLineup.save({}, {
success: function (model, respose, options) {
},
error: function (model, xhr, options) {
}
});
}
When the user first accesses the page, I will create a new lineup object by calling the above function. But how do I update that object as the user interacts with the page? Is there a better way to do this other than putting the Backbone model object at the top of my JavaScript file?
The Backbone pattern was designed to answer your question. As other respondents said, wire up a View, which takes your model as a parameter and lets you bind DOM events to the model.
That said, you don't have to use the rest of the framework. I guess you can use all the functionality Backbone provides models by handling the model yourself.
You need to worry about a couple of things.
Give you model a little encapsulation.
Set up a listener (or listeners) for your checkbox items.
Scope the model to your app
Backbone provides neat encapsulation for your model inside a View, but if you can live with it, just use your app variable which is within scope of the JavaScript file you posted.
When you're ready to instantiate your model, make it a property of app:
app.newLineup = new app.Lineup({team: team, completed: false});
It may look weird to have the instance and the constructor in the same object, but there aren't other options until you pull out the rest of Backbone.
The listener
So you have N number of checkboxes you care about. Say you give them a class, say, .options. Your listener will look like
$( ".options" ).change(function() {
if(this.checked) {
//Do stuff with your model
//You can access it from app.newLineup
} else {
}
});
Voila! Now your page is ready to talk to your model.
If there is frontend ui / any user interaction within your code it is extremely useful to create a backbone view which makes use of an events object where you can set up your event handler.
You can also link a view to a model to allow your model / your object to be updated without scope issues.

Routing & events - backboneJS

How should I be handling routing in BackboneJS? When routing, after new-upping my view, should I be triggering an event, or rendering the view directly?
Here are the two scenarios:
Trigger Event:
routes: {
'orders/view/:orderId' : 'viewOrder'
},
viewOrder: function (orderId) {
var viewOrderView = new ViewOrderView();
vent.trigger('order:show', orderId);
}
In my view, I have:
var ViewOrderView = Backbone.View.extend({
el: "#page",
initialize: function () {
vent.on('order:show', this.show, this);
},
show: function (id) {
this.id = id;
this.render();
},
render: function () {
var template = viewOrderTemplate({ id: this.id });
this.$el.html(template);
return this;
}
});
OR, should I go this route:
routes: {
'orders/view/:orderId' : 'viewOrder'
},
viewOrder: function (orderId) {
var viewOrderView = new ViewOrderView({id : orderId });
viewOrderView.render();
}
In my view, I have:
var ViewOrderView = Backbone.View.extend({
el: "#page",
initialize: function () {
//init code here
},
render: function () {
var template = viewOrderTemplate({ id : this.id});
this.$el.html(template);
return this;
}
});
I think it's the first scenario - given that backbone is event driven, but the 2nd obviously has less code.
Also, I suppose a third scenario would be to keep the view code in the first scenario, but grab the router scenario of the second... rendering the view on navigation, but exposing an event in case I want to trigger that elsewhere.
Thoughts?
So all backbone questions usually end up with many plausible answers. In this case, I believe your second example is a more canonical/typical backbone pattern. Putting aside the tricky issue of handling loading spinners and updating after data loads, the simplified basic pattern in your router would be:
routes: {
'orders/view/:orderId' : 'viewOrder'
},
viewOrder: function (orderId) {
//Use models to represent your data
var orderModel = new Order({id: orderId});
//models know how to fetch data for themselves given an ID
orderModel.fetch();
//Views should take model instances, not scalar model IDs
var orderView = new OrderView({model: orderModel});
orderView.render();
//Exactly how you display the view in the DOM is up to you
//document.body might be $('#main-container') or whatever
$(document.body).html(orderView.el);
}
I think that's the textbook pattern. Again, the issue of who triggers the fetching of data and rerendering after it arrives is tricky. I think it's best if the view knows how to render a "loading" version of itself until the model has fetched data, and then when the model fires a change event after fetch completes, the view rerenders itself with the loaded model data. However, some people might put that logic elsewhere. This article on building the next soundcloud I think represents many very good "state of the art" backbone patterns, including how they handle unfetched models.
In general, you can code things with callbacks or events as you prefer. However, a good rule of thumb is to ask yourself some questions:
Is more than one independent logical piece of work going to respond to this event?
Do I need to decouple the source of this event from the things that happen in response to it?
If both of those are "yes", then events should be a good fit. If both are "no", than straightforward function logic is a better fit. In the case of "navigating to this URL triggers this view", generally the answer to both questions is "no", so you can just code that logic into the router's route handler method and be done with it.
I'd use second scenario. Don't see any benefits of using first approach. It would make more sence this way (but still arguable):
/* ... */
routes: {
'orders/view/:orderId' : 'viewOrder'
},
viewOrder: function (orderId) {
vent.trigger('order:show', orderId);
}
/* ... */
vent.on('order:show', function(orderId) {
var viewOrderView = new ViewOrderView();
viewOrderView.render();
});
var ViewOrderView = Backbone.View.extend({
el: "#page",
initialize: function (options) {
this.orderId = options.orderId;
},
render: function () {
var template = viewOrderTemplate({
id: this.orderId
});
this.$el.html(template);
return this;
}
});
This way at least you'd be able to trigger route action without updating a url. But same effect might be achieved using Backbone.router.viewOrder(1) probably. Events are pretty powerful, but i wouldn't use them if i don't really need.

Query Database with Backbone Collection

I need to query the database using a backbone collection. I have no idea how to do this. I assume that I need to set a url somewhere, but I don't know where that is. I apologize that this must be a very basic question, but I took a backbone course on CodeSchool.com and I still don't know where to begin.
This is the code that I have for the collection:
var NewCollection = Backbone.Collection.extend({
//INITIALIZE
initialize: function(){
_.bindAll(this);
// Bind global events
global_event_hub.bind('refresh_collection', this.on_request_refresh_collection);
}
// On refresh collection event
on_request_refresh_collection: function(query_args){
// This is where I am lost. I do not know how to take the "query_args"
// and use them to query the server and refresh the collection <------
}
})
The simple answer is you would define a URL property or function to your Backbone.Collection like so:
initialize: function() {
// Code
},
on_request_refresh_collection: function() {
// Code
},
url: 'myURL/whateverItIs'
OR
url: function() {
return 'moreComplex/' + whateverID + '/orWhatever/' + youWant;
}
After your URL function is defined all you would have to do is run a fetch() on that collection instance and it will use whatever you set your URL to.
EDIT ------- Making Collection Queries
So once you set the URL you can easily make queries using the native fetch() method.
fetch() takes an option called data:{} where you can send to the server your query arguments like so:
userCollection.fetch({
data: {
queryTerms: arrayOfTerms[], // Or whatever you want to send
page: userCollection.page, // Pagination data
length: userCollection.length // How many per page data
// The above are all just examples. You can make up your own data.properties
},
success: function() {
},
error: function() {
}
});
Then on your sever end you'd just want to make sure to get the parameters of your request and voila.

trigger loading view when collection or model fetched

I've been using Marionette for a week now and it really made my life easier!
Right now I need to be able to notify a user when a collection or model is being fetched, because some views take a significant amount of time to render/fetch. To give an example I've made a small mockup:
When a user clicks on a category, a collection of all items within that category need to be loaded. Before The collection is fetched I want to display a loading view as seen in the image (view 1). What would be an elegant solution to implement this. I've found the following post where a user enables a fetch trigger: http://tbranyen.com/post/how-to-indicate-backbone-fetch-progress . This seems to work but not really like I wanted to. This is something I came up with:
var ItemThumbCollectionView = Backbone.Marionette.CollectionView.extend({
collection: new ItemsCollection(userId),
itemView: ItemThumbView,
initialize: function(){
this.collection.on("fetch", function() {
//show loading view
}, this);
this.collection.on("reset", function() {
//show final view
}, this);
this.collection.fetch();
Backbone.history.navigate('user/'+identifier);
this.bindTo(this.collection, "reset", this.render, this)
}
});
It would be nice though If I could have an optional attribute called 'LoadItemView' for example. Which would override the itemView during a fetch. Would this be a good practice in your opinion?
A few days ago, Derick Bailey posted a possible solution in the Marionette Wiki: https://github.com/marionettejs/backbone.marionette/wiki/Displaying-A-%22loading-...%22-Message-For-A-Collection-Or-Composite-View
var myCollection = Backbone.Marionette.CollectionView.extend({
initialize: function(){
this.collection.on('request', function() {
//show loading here
})
this.collection.on('reset', function() {
//hide loading here
})
this.collection.fetch({reset: true})
}
})
It's better now, I think.
Use Backbone sync method
/* over riding of sync application every request come hear except direct ajax */
Backbone._sync = Backbone.sync;
Backbone.sync = function(method, model, options) {
// Clone the all options
var params = _.clone(options);
params.success = function(model) {
// Write code to hide the loading symbol
//$("#loading").hide();
if (options.success)
options.success(model);
};
params.failure = function(model) {
// Write code to hide the loading symbol
//$("#loading").hide();
if (options.failure)
options.failure(model);
};
params.error = function(xhr, errText) {
// Write code to hide the loading symbol
//$("#loading").hide();
if (options.error)
options.error(xhr, errText);
};
// Write code to show the loading symbol
//$("#loading").show();
Backbone._sync(method, model, params);
};
In general, I'd suggest loading a preloader while fetching data, then showing the collection. Something like:
region.show(myPreloader);
collection.fetch().done(function() {
region.show(new MyCollectionView({ collection: collection });
});

Resources