How can I create custom GET urls with params using Backbone? - backbone.js

I've noticed that some web sites offer Ajax-ian search that refreshes the URL and displays the GET params used, for example:
someapp.com/search/Tokyo?price_min=80&price_max=300
As a result of an Ajax GET request.
I want to know how can I accomplish this by using Backbone.js, I understand that by using backbone's push state this may be possible, am I right?
How could I define a route like that (let's say the same case, scoped to /search) for a Place model for example?
Where would I do this? in a Router or in a Model?
I appreciate all the answers regarding this topic. And I apologize in advance for not providing any code, I usually do, but this exercise will be a proof of concept I'd like to make, and I hope backbone is the right tool for the job.
Thank you!

This is a working example of the solution - jsfiddle.net/avrelian/dGr8Y/, except that jsFiddle does not allow Backbone.history.navigate method to function properly.
Suppose, we have a button
<input class="fetch-button" type="button" value="Fetch" />​
and a handler
$('.fetch-button').click(function() {
Backbone.history.navigate('posts/?author=martin', true);
});
This is our collection of posts
var Posts = Backbone.Collection.extend({
url: 'api/posts'
});
This is our Router with a custom parameter extractor
var Router = Backbone.Router.extend({
routes: {
'posts/?:filters': 'filterPosts'
},
filterPosts: function(filters){
posts.fetch({data: $.param(filters)});
},
_extractParameters: function(route, fragment) {
var result = route.exec(fragment).slice(1);
result.unshift(deparam(result[result.length-1]));
return result.slice(0,-1);
}
});
It is simplified $.deparam analog. You could use your own instead.
var deparam = function(paramString){
var result = {};
if( ! paramString){
return result;
}
$.each(paramString.split('&'), function(index, value){
if(value){
var param = value.split('=');
result[param[0]] = param[1];
}
});
return result;
};
And finally, instantiation of posts collection and router object
var posts = new Posts;
var router = new Router;
Backbone.history.start();
When a user clicks on the button address bar changes to myapp.com/s/#posts?author=martin. Please, note the sign #. We use a hash query string. Of course, you can use push state, but it is not widespread yet.

Related

How can I save data in forEach loop?

I create a forEach loop. I can access data from Firebase with this loop and I can change variable. But I can not save these changes on Firebase. How can I save these changes? Here's my code:
var posts = $firebaseArray(ref);
posts.$loaded().then(function(item){
item.forEach(function(childSnapshot){
var num = childSnapshot.point-1;
childSnapshot.lastPoint = num;
});
});
You're using the AngularFire framework, which builds UI bindings on top of Firebase's regular JavaScript SDK. You should only be using it for things that you're binding to the Angular UI elements. For everything else, you're likely better off using Firebase's regular JavaScript SDK.
If I understand your requirement correctly, you're trying to loop over all child nodes in a database location once and modify a property. If so:
ref.once('value', function(snapshot) {
snapshot.forEach(function(childSnapshot) {
var child = childSnapshot.val();
childSnapshot.ref().update({ lastPoint: child.point - 1 });
});
});
The relevant sections of the Firebase documentation are on reading data once and updating data.
Since AngularFire is built on top of Firebase's JavaScript SDK, they work perfectly together. So if elsewhere you bind the posts to the scope ($scope.posts = $firebaseArray(ref)), they will be updated automatically when you update the last point with the above snippet.
You can create a service that contains a getter and a setter. It would look something like this;
angular.module('app').factory("DataHandler",
function() {
var data;
return {
get: function() {
return data;
},
set: function(something) {
data = something;
}
}
}
);
Then in your code you would call:
var posts = $firebaseArray(ref);
posts.$loaded().then(function(item){
item.forEach(function(childSnapshot){
var num = childSnapshot.point-1;
childSnapshot.lastPoint = num;
DataHandler.set(childSnapshot);
});
});
Then wherever/whenever you need to get that data just call:
Datahandler.get();

Fetching models from the server

I have a RESTful service which have being written using Node.js. If I open http://localhost:5000/links in the browser I will get the collection of links:
[{"_id":"5597f5d3e9a768531c07468a","uri":"http://google.com","title":"google","__v":0,"tags":["one","two"]}]
I need to get this collection from backbone application:
(function () {
var Link = Backbone.Model.extend({});
var LinkCollection = Backbone.Collection.extend({
model: Link,
url: 'http://localhost:5000/links'
});
var links = new LinkCollection();
links.fetch();
console.log(links);
console.log(links.length);
})();
Here is a console:
I can see my object the right side of the console (c3.attributes). But why the length of collection is zero? And how can I get this object?
Classic backbone issue.
Try this:
links.fetch({
success:function(response) {
//here are your results
}
});
Edit: Also I think a collection return a promise so an alternative solution could be:
links.fetch().then(function(response) {
});
haven't use it myself but I think it should work. Hope it helps.

Setting URL in Backbone.Router for QUnit testing

I have a Backbone.Router based myRouter, that calls certain set() methods based on the URL:
var MyRouter = Backbone.Router.extend({
routes: {
"doSomething/:value": "doSetterOnModel",
},
doSetterOnModel: function(value) {
// does some set(value) calls on my Model
},
});
I would like to test now if with the model gets updated (set()) correctly using a QUnit test:
test( "update Model via URL change", function() {
var value = "newValue";
var url = "http://localhost/#/doSomething/" + value;
//does not work as it moves page away from QUnit's tests.html page
window.location = url;
// does also not work because it moves away from QUnit's tests.html page
myRouter.navigate(url);
deepEqual( myModel.get(key), value, "Value on Model was not updated by URL change");
});
How can I set the URL in Qunit in order to test if my Router performs the right action for a certain URL?
When I want to test my routers I usually bypass the document navigation altogether and simply call Backbone.history.loadUrl, which is responsible for matching URL fragments to a router methods:
Backbone.history.loadUrl("#/doSomething");
The additional benefit here is that you don't need to call Backbone.history.start in your test code. Note also the use or a relative url. Specifying the http://localhost part is not necessary.
Tiny demo here.

Tracking Site Activity (Google Analytics) using Backbone

I am looking the best way to track the Site Activity in Google Analytics for a web app made with Backbone and Requires.
Looking At Google's Page, I found this drop-in plugin - Backbone.Analytics.
My questions are:
1) using Backbone.Analytics, should I change backbone.analytics.js in order to add _gaq.push(['_setAccount', 'UA-XXXXX-X']);?
2) Are there other possible solutions/plugins?
I prefer "do it yourself" style :) It's really simple:
var Router = Backbone.Router.extend({
initialize: function()
{
//track every route change as a page view in google analytics
this.bind('route', this.trackPageview);
},
trackPageview: function ()
{
var url = Backbone.history.getFragment();
//prepend slash
if (!/^\//.test(url) && url != "")
{
url = "/" + url;
}
_gaq.push(['_trackPageview', url]);
}
}
And you add google analytics script to your page as usual.
You shouldn't have to change anything. Just add your Google Analytics code snippet, like normal, and include Backbone.Analytics as you would any other Javascript library.
Just figured i'd share how i'm doing it. This might not work for larger apps but I like manually telling GA when to track page views or other events. I tried binding to "all" or "route" but couldn't quite get it to record all the actions that I need automajically.
App.Router = BB.Router.extend({
//...
track: function(){
var url = Backbone.history.getFragment();
// Add a slash if neccesary
if (!/^\//.test(url)) url = '/' + url;
// Record page view
ga('send', {
'hitType': 'pageview',
'page': url
});
}
});
So i just call App.Router.Main.track(); after I navigate or do anything i want to track.
Do note that I use the new Analytics.js tracking snippet which is currently in public beta but has an API so intuitive that it eliminates the need for a plugin to abstract any complexity what so ever. For example: I keep track of how many people scroll to end of an infinite scroll view like this:
onEnd: function(){
ga('send', 'event', 'scrollEvents', 'Scrolled to end');
}
Good luck.
I wrote a small post on this, hope it helps someone:
http://sizeableidea.com/adding-google-analytics-to-your-backbone-js-app/
var appRouter = Backbone.Router.extend({
initialize: function() {
this.bind('route', this.pageView);
},
routes: {
'dashboard': 'dashboardPageHandler'
},
dashboardPageHandler: function() {
// add your page-level logic here...
},
pageView : function(){
var url = Backbone.history.getFragment();
if (!/^\//.test(url) && url != ""){
url = "/" + url;
}
if(! _.isUndefined(_gaq)){
_gaq.push(['_trackPageview', url]);
}
}
});
var router = new appRouter();
Backbone.history.start();
Regarding other possible solutions/plugins, I've used https://github.com/aterris/backbone.analytics in a few projects and it works quite well as well. It also has options for a few more things like event tracking which can be handy at some point in your analytics integration.
If you use the new universal analytics.js, you can do that like this:
var Router = Backbone.Router.extend({
routes: {
"*path": "page",
},
initialize: function(){
// Track every route and call trackPage
this.bind('route', this.trackPage);
},
trackPage: function(){
var url = Backbone.history.getFragment();
// Add a slash if neccesary
if (!/^\//.test(url)) url = '/' + url;
// Analytics.js code to track pageview
ga('send', {
'hitType': 'pageview',
'page': url
});
},
// If you have a method that render pages in your application and
// call navigate to change the url, you can call trackPage after
// this.navigate()
pageview: function(path){
this.navigate(path);
pageView = new PageView;
pageView.render();
// It's better call trackPage after render because by default
// analytics.js passes the meta tag title to Google Analytics
this.trackPage();
}
}
All answers seem to be almost good, but out-of-date (Sept. 2015). Following this Google devs guide: https://developers.google.com/analytics/devguides/collection/analyticsjs/single-page-applications
Here's my version of the solution (I've added the suggested call to ga('set'...) ):
MyRouter = Backbone.Router.extend
...
initialize: () ->
# Track every route and call trackPage
#bind 'route', #trackPage
trackPage: () ->
url = Backbone.history.getFragment()
# Add a slash if neccesary
if not /^\//.test(url) then url = '/' + url
# Analytics.js code to track pageview
global.ga('set', {page: url})
global.ga('send', 'pageview')
...
Just posting an update to this question as it seems to be one I get a lot from backbone.js developers I know or work with who seem to fall at the last hurdle.
The Javascript:
App.trackPage = function() {
var url;
if (typeof ga !== "undefined" && ga !== null) {
url = Backbone.history.getFragment();
return ga('send', 'pageview', '/' + url);
}
};
Backbone.history.on("route", function() {
return App.trackPage();
});
The Tracking Snippet:
<head>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||
function(){(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();
a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;
a.src=g;m.parentNode.insertBefore(a,m)})(window,document,'script',
'//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-XXXXXXXX-X', 'auto');
</script>
</head>
The Tracking Snippet should be available on any page you wish to track activity. This could be your index.html where all your content is injected, but some sites may have multiple static pages or a mix. You can include the ga('send') function if you wish, but it will only fire on a page load.
I wrote a blog post that goes in to more detail, explaining rather than showing, the full process which you can find here: http://v9solutions.co.uk/tech/2016/02/15/how-to-add-google-analytics-to-backbone.js.html

using underscore variables with Backbone Boilerplate fetchTemplate function

I am building an application using the Backbone Boilerplate, and am having some trouble getting underscore template variables to work. I have a resource named Goal. My Goal View's render function looks like this:
render: function(done) {
var view = this;
namespace.fetchTemplate(this.template, function(tmpl) {
view.el.innerHTML = tmpl();
done(view.el);
});
}
I'm calling it inside of another view, like so:
var Goal = namespace.module("goal");
App.View = Backbone.View.extend({
addGoal: function(done) {
var view = new Goal.Views.GoalList({model: Goal.Model});
view.render(function(el) {
$('#goal-list').append(el);
});
}
});
I'm using local storage to save my data, and it's being added just fine. I can see it in the browser, but for some reason, when I load up the app, and try to fetch existing data, i get this error:
ReferenceError: Can't find variable: title
Where title is the only key I'm storing. It is a direct result of calling:
tmpl();
Any thoughts are greatly appreciated.
Your template is looking for a variable title, probably like this <%- title %>. You need to pass it an object like this tmpl({ title: 'Some title' })
Turns out, I wasn't passing in the model when i created the view, which was making it impossible to get the models data. Once I passed in the model correctly, I could then pass the data to tmpl, as correctly stated by #abraham.
render: function(done) {
var
view = this,
data = this.model.toJSON();
clam.fetchTemplate(this.template, function(tmpl) {
view.el.innerHTML = tmpl(data);
done(view.el);
});
},

Resources