Backbone without Hashes? - ruby-on-rails

I think I'm trying to use Backbone in an unintended way, and I couldn't really find much on it. Basically I have a Rails app that is serving up the views. I want to keep the regular navigation (as in page reloading), but let backbone see the route and setup certain parts of the templates on that page, handle the models, and all of that good stuff. So basically I'm using Backbone to handle all of my complicated javascript without making it a "single page app". Would enabling PushState break my absolute paths in older browsers? eg: "http://localhost:3000/projects" matching the route "projects".

PushState will not work in old browsers like IE6, but you could use different technique, for example you could use jQuery selectors and check whether you're on the particular page:
if ($('#login-page').length > 0) {
// we're on the login page
// ..initialize login page related backbone collections and views
}
..or you could store action/controller name somewhere in the html using data attribute: <body data-action="edit" data-controller="post"> and check it in javascript va4 $body = $('body'); if ($body.data('action') == 'edit' && $body.data('controller') == 'posts') {} etc.
..or you could have separate js file for each action/controller pair and include it on demand.

Related

How to accomplish this MVC layout

Being relatively new to MVC I have been struggling for the past several weeks getting my layout to work.
I have managed to get myself really twisted into knots. So instead of trying to explain and unravel my mess perhaps instead someone could explain how I would accomplish the following at a high level.
_Layout this would have all the css js etc. It would also have basic structure.
Of course HTML tags not allowed in code block....each render is in a div.
#RenderPartial(Header)</div>
#RenderBody()</div>
#RenderPartial(Footer)</div>
RenderBody is Index.cshtml and it would be broken into three pieces
#
#Html.Partial(NavMenu, model)</div>
#Html.Partial(SubNavMenu, model)</div>
#Html.Partial(MainContent, model)</div>
I have this basic layout and it looks fine until you click one of the menu items.
The menu items render as:
<a class="k-link" href="/stuffroute">Stuff</a>
That route goes to a controller that returns a view and that navigates away from the above arrangement in Index.cshtml. So I end up with the header, footer, and subdash nav....
So the question is...
How do I route / orchestrate my layout to not lose the differing pieces?
Partials don't do anything for you here. You're essentially asking about how to create SPA (single page application), although in this case your application will have other pages, it's just that the index view will act like a SPA.
That requires JavaScript, specifically AJAX, to make requests to endpoints that will return HTML fragments you can use to replace portions of the DOM with. For example, clicking "Stuff 1" causes an AJAX request to be made to the URL that routes to FooController.GetSubNav([stuff identifier]). That action then would use what was passed to it to retrieve the correct sub-nav and return a partial view that renders that sub-nav. Your AJAX callback will then take this response, select a portion of the DOM (specifically the parent of the sub-nav) and insert the new HTML as its innerHTML.
If you're going to be doing a lot of this, you'll want to make use of some client-side MVC-style JavaScript library, like Angular for example. These make it trivial to wire everything up.

How to disable Backbone code to call an underscore template in non-essential pages?

I have an app that uses Backbone on the client side and Rails on the back end.
My Backbone code calls an underscore template in this manner:
var todo = Backbone.View.extend({
.
template : _.template( $('#todo_rows').html() ),
.
}
However, there's only one page on the site which would need to call this template, and the Backbone js loads on every page. That means that there will be a JS error on the other pages which don't have the "#todo_rows" underscore template defined.
One solution would be to create a blank "#todo_rows" template section for all the other pages, but this strikes me as a major hack and not the best way to deal with this problem.
What is the best way to disable the code to call a template in Backbone? It needs to be disabled if you are not on the home page OR if you are not logged in.
Underscore's template is happy with receiving an empty string as argument. As $('#todo_rows').html() returns null if the #todo_rows element does not exist, you can easily avoid the JS error by using the or-operator idiom:
template : _.template( $('#todo_rows').html() || '' ),
which means "use an empty string as dummy template if the real one is not available".
Another, Rails-specific workaround for this problem (and a neat approach in general) is to add the ejs gem to your project's Gemfile, which allows you to put each template in a separate .jst.ejs file in app/assets and have the Asset Pipleline compile it to a JST object that can be accessed by your view to get the template content:
templates/todo_rows.jst.ejs
<% // Your template (content of former "#todo_rows") here %>
todo_rows_view.js
//= require templates/todo_rows
var todo = Backbone.View.extend({
template : _.template(JST['templates/todo_rows']),
// ...
}
This avoids the roundtrip to the DOM and makes it possible to nicely organize the templates into multiple files, but has the drawback that the template string will be sent as part of the JavaScript code for every page including it.

Backbone.js with Rails

I'm currently trying Backbone.js along with a rails app. My problem is, that I don't know how to implement the Backbone controllers and views with my rails app. I've read a lot of tutorials, but they are always using just one controller in backbone.js.
For example, I have two controllers in rails.
Activities Controller
Includes two views, a google map and a search field. The google map is inserted with a backbone view, the searchfield is in HTML and gets its functionality through a backbone view.
The search field should fetch data from my rails model and display markers inside the map.
And the other one is
Users Controller
Here the users profile is viewed, and I want to add some ajax functionality like updating values and other things
In my application.js I start the app using
var App = {
Views: {},
Controllers: {},
Collections: {},
init: function() {
new App.Controllers.Activities();
new App.Controllers.Users();
Backbone.history.start();
}
};
$(function() {
App.init();
});
The problem is, that I don't need the Activities controller in my User Profile and the Users controller in the Rails Activities controller. How could I solve this? Should I try reading the current URL within javascript and then decide which controller is used?
Or should I put the JavaScript file into the application.html.erb and then decide here which controller should be used?
Or is this the wrong way to use backbone.js controllers?
Am I getting sth wrong with the structure of backbone.js? Or am I using the Controllers in a wrong way?
Another question is, how to add little JavaScript, in particular jQuery, functionality through Backbone.js? For example, I want to remove the label inside a field, when the user clicks into the field. Or I want to do some tab-functionality and just toggle the visibility of some elements.
Should I create for each element that is using javascript a Backbone view? Or is this overload?
Hope I made myself clear and anybody can help,
thx!
Why not make use of the routes feature Backbone provides to decide which method to call? The activities controller would contain only routes use for activities, the user controller only for the user handling, and so forth.
Like this you can instantiate the controller just as you do and the routing will decide what happens based on the current location's hash.
If you can't use links with hashes (or there are no such links on your page), I'd simply name my view containers specific enough to attach events only for the current view when needed.
jQuery plugins etc. belong into views IMO. Same goes for your tabs and input hint toggle.
Update
On a general level (and I would not necessarily recommend doing it this way): If you have two methods:
// should be only called for the 'Foo' controller
function foo() {
alert("FOO");
};
// should be only called for the 'Bar' controller
function bar() {
alert("BAR");
};
and want to call only one of them depending on the current Rails controller, create a small helper:
e.g. in you *helpers/application_helper.rb*
def body_class
controller.controller_name
end
then call this method in your layout file (or header partial):
<body class="<%= body_class %>">
…
and use e.g. jQuery to "split" your JS execution:
if ($('body').hasClass('foo')) {
foo();
} else if ($('body').hasClass('bar')) {
bar();
}
I personally use jammit (from the same author of backbone). You can group stylesheets and javascripts files by module and use them on your different pages. So you create a module for your activities view and another for your user view each requiring the necessary JavaScript file. Two nice things about this:
you have only code used on the page loaded on the page
jammit comprsses everything for you
This is the way to go when not creating a single page web app where you relies on # routes to navigate from one controller to another. In your case you have multiple single page apps inside a main rails app.
Im a Backbone.js newb, so please someone correct me if Im wrong, but I think youre confusing what Backbone controllers are used for.
Backbone.js controllers basically consists of hashbang routes(similar to Rails routes) and actions that these routes call. For instance if you have a rails route that will render a view at http://mysite.com/backbone_test and you have a following backbone route:
var MyController = Backbone.Controller.extend({
routes: {
"foo/:bar": "myFirstFunction"
},
myFirstFunction: function(bar){
console.log(bar);
});
then if you go to http://mysite.com/backbone_test#foo/THIS-IS-AMAZING then MyController.MyFirstFunction gets executed and "THIS-IS-AMAZING" gets logged in JS console.
Unless you have a direct need for this sort of hashbang-related functionality, such as saving a state of your JavaScript application via an url (for example: http://my.app.com/#key=value&key=value&ad-infinitum=true ) Id advise against using Backbone controllers. You can get all of the functionality u described via Models Collections and Views.
Nice thing about Controllers and Backbone in general is that its modular and different parts can be used independently.
I guess the summary of this answer is that if you dont have a single page javascript application, do not use Backbone Controllers, rely on your rails controllers instead.

Rails: generating URLs for actions in JSON response

In a view I am generating an HTML canvas of figures based on model data in an app. In the view I am preloading JSON model data in the page like this (to avoid an initial request back):
<script type="text/javascript" charset="utf-8">
<% ActiveRecord::Base.include_root_in_json = false -%>
var objects = <%= #objects.to_json(:include => :other_objects) %>;
...
Based on mouse (or touch) interaction I want to redirect to other parts of my app that are controller specific (such as view, edit, delete, etc.).
Rather than hard code the URLs in my JavaScript I want to generate them from Rails (which means it always adapts the latest routes).
It seems like I have one of three options:
Add an empty attr to the model that the controller fills in with the appropriate URL (we don't want to use routes in the model) before the JSON is generated
Generate custom JSON where I add the different URLs manually
Generate the URL as a template from Rails and replace the IDs in JavaScript as appropriate
I am starting to lean towards #1 for ease of implementation and maintainability.
Are there any other options that I am missing? Is #1 not the best?
Thanks!
Chris
I wrote a bit about this on my blog: Rails Dilemma: HATEOAS in XML/JSON Responses.
I came to similar conclusions. There's no incredibly clean way to do it as far as I know, because by default the model is responsible for creating a JSON representation of itself, but generating URLs is strictly a controller/view responsibility.
Feel free to look over my thoughts/conclusions and add comments here or there.

ASP.NET MVC editor template javascript location

We have an editor template that contains approx 40 lines of jquery. I tried dropping this script into a <asp:Content> block to keep all of the javascript in one location within the page. However, I get the following error message content controls have to be top-level controls in a content page.
Is there any way to get this working so we don't have script dotted around our final output pages or could someone recommend the best practice for storing javascript used within ASP.NET MVC templates? At the moment I'm thinking of pulling the code into a separate file and referencing it within the master page but this means it gets pulled into every page which isn't really ideal.
Thanks in advance.
It would be easier for later maintenance, if you keep the javascript into a separate file and reference it where ever it is needed. Also, if you feel that placing all script into a single file will increase unnecessary loading of scripts, where not needed, then break the scripts into separate files based on functionality/modules in which it is useful etc. strategy and then reference them instead.
Also, it was said that always, keep/reference the scripts at the bottom of the page so that page loading will be faster.
as siva says, bottom of the page is the 'ideal'. however, as for seperate files. this is only going to be practical as long as you don't reference asp.net elements from the page - ie:
<asp:Content ContentPlaceHolderID="jsCode" ID="jsCode1" runat="server">
<script type="text/javascript">
$(document).ready(function() {
getPoundsData();
});
function getPoundsData() {
var id = $("#ID").val();
var URL = '<%=Url.Action("GetPounds", "FundShareholder")%>';
var params = { id: id };
if (id != null)
SendAjaxCache("#" + $("#ShareholderID option:selected").text() + " RSP#", URL, params, null, ListDataShareholderPounds);
}
function ListDataShareholderPounds(data) {
if (data.length != 0) {
$('#shareholderPounds').html("");
$('#shareholderPounds').show();
$('#shareholderPounds').html(data);
}
};
</script>
</asp:Content>
notice the:
var URL = '<%=Url.Action("GetPounds", "FundShareholder")%>';
part in the js. what 'we' do is to add a content section to the master page at the very bottom to hold our js stuff. however, this only works inside the ViewPage (aspx) object. the ascx pages are 'ignorant' of any master page content sections.
We are currently working on systemizing the process whereby we save 'partial' js files with asp.net references inside them and then inject them into the page-flow via a filterattribute. we're at an early stage with this but the nice thing about this approach is that the partial js is treated as a file and is therefore cached for future visits to that page.
anyway, that's our current approach, would be interested to discover if peeps are using any similar mechanisms to inject js that contains asp.net object references.
cheers...
[edit] - here's a heads up on the approach i'm talking about (this wasn't our original inspiration, but is quite similar, tho is webforms, rather than mvc) - http://www.west-wind.com/WebLog/posts/252178.aspx or this one which IS mvc: http://poundingcode.blogspot.com/2009/12/injecting-javasript-into-aspnetmvc-with.html.
Finally found the article that inspired our 'search' in this: ASP.NET MVC routing and paths is js files plus http://codepaste.net/p2s3po

Resources