I'm working on a medium sized application that is going to include a lot of D3 charts / interactions in it. I'm wondering if anyone has tried to use Backbone, Angular, or Ember with D3, and if so, which one seems like the best fit for a front end MV* framework. The application won't be doing a whole lot of CRUD operations, mainly interactive charts and widgets to manipulate them.
Any comments appreciated!
We used d3 with Backbone pretty extensively on a project that consisted of multiple "scenes". Each scene contained a set of different charts, and a user has the ability navigate from one scene to another. These scenes and their content all needed to be highly configurable (e.g. label colors and formatting, or indicating which data params should be plotted on a given axis).
D3 (rightfully) doesn't provide a view management system, which is where Backbone took over. Backbone Views served as wrappers for d3 charts. Predictably, Backbone Models served as the carriers of the d3-plotted data. But more interestingly, we also found that they served well as a means of controlling the appearance and behavior of the d3 code contained within the Backbone Views; essentially they served as view models. Since d3 promotes passing functions as arguments into other functions, these Backbone Models-as-view-models ended up holding many functions in them.
The following is a simplistic example, but picture doing this with dozens of properties. Using coffeescript here, because it's shorter (and better).
First, there's the model, which we instantiate inside (for example) a router's event handler. We populate this model with functions that will be applied to d3 selectors.
barChartModel = new Backbone.Model
barColor: (d, i) -> if d.profits < 0 then "red" else "green"
barLengthVal: (d, i) -> return bar.profits #// profits will be the prop we graph
onClick: (d, i) ->
console.log "We are", if d.profits <= 0 then "losing" else "making", "money"
data: someJsonWeLoaded
We pass this model into a new view:
barChartView = new BarChartView
el: "#the_bar_chart"
model: barChartModel
A view might be implemented like this:
class BarChartView extends Backbone.View
render: ->
bars = d3.select(#el)
.selectAll('.bar')
.data(#model.get 'data') # <---- THIS
bars.enter()
.attr('class', 'bar')
.attr('fill', #model.get 'barColor') # <---- THIS
.attr('height', (d, i) ->
#barLengthScale #model.get('barLengthVal')(d, i) # <---- AND THIS
)
.on('click', #model.get 'onClick') # <---- INTERACTIVITY TOO
initialize: ->
#barLengthScale = d3.scale.linear()
.domain([-100, 100]) # <---- THIS COULD ALSO COME FROM MODEL
.range([0, #$el.height()])
#render()
I have used D3 with Angular on a few dashboards, and it worked very well. I have never really used Backbone, and not with D3, so I cannot compare the two. I chose Angular to complement D3 because it appeared to me that lately the D3 community has been using D3 with Angular the most of the three options you mentioned, so there were great resources available. There has recently been an entire book dedicated to using D3 and Angular together. I had also used Angular a bit before, and was aware of directives. Directives (in Angular it is a way to extend html tags) are great for meshing with D3. Each chart can become a directive, and then makes it extremely easy to reuse charts, changing only the $scope data. These are some resources I found helpful when combining the two:
https://www.youtube.com/watch?v=aqHBLS_6gF8
https://leanpub.com/d3angularjs
http://plnkr.co/edit/WnoCtNPV9azj0oPwv9kM?p=preview
http://vicapow.github.io/angular-d3-talk/slides/demos/a-donut-chart-editor/index.html#/
I recently gave a talk on this very topic, here are some links: Video · Code · Slides
I've done some smaller projects using similar methods to meetamit, but have recently started exploring Ember + D3. I haven't done much yet, but I think Ember has a lot to offer that could simplify building these types of app. Some things that come to mind:
Computed properties: you'll often be displaying aggregates, so slicing your data using computed properties means you just have to call your chart's update function whenever the data changes, and you're good to go. No more worrying about sending off an event to every view that will change when one specific part of your data changes. Plus, these will probably be properties on your controllers, instead of being calculated within a specific chart or view, which will make reuse much easier.
Storing state: I had a tough time figuring out the best way to store state in Backbone. I started out trying to coordinate everything via events, but in the end landed on having a separate State model which acted as the brains of the whole system.
I didn't get around to using Backbone's router much, but Ember's router + focus on state has made this design challenge easier for me so far. If you build within the system, you can click around your filters and controls, and everything just works. It may be possible to do exactly the same thing in Backbone, but there's something to be said for seriously reducing your cognitive load. You can also explicitly use a StateManager object - there may be some really interesting solutions here, though I haven't explored them yet.
Again, my experience with this combo is shallow, but if my instinct is right there are going to be many gains from building visualizations within Ember's conventions.
If you haven't already come across this, Square put up an article briefly covering their experience building an interactive dashboard with Ember + D3.
Keep us up to date on your progress + any insights you come across, and good luck!
My group has used both angular and backbone with d3, and we like both for different reasons.
Backbone
Backbone is a bit less opinionated about the way you construct your application, which is nice if you need to customize the way data is getting handled for performance. You generally integrate d3 with a backbone view.
One challenge of working with Backbone is memory management for complex views, but using marionette helps with that. Also Marionette's event aggregator (and specifically using request-response) is a good fit for centralized data sources for coordinated views if you want to use something like crossfilter or lunr.
Angular
Angular is more structured, and it is allows you to build up cool features very quickly. It has a steep learning curve, but I've found now that I'm understanding angular (having using it to develop an application for the past ~4 weeks), I've found that I can accomplish many of the same things I can in backbone without resorting to anything too hackish.
Like the request-response object in backbone marionette, using angular services allows you to build up complex views quickly. You'll need to avoid using angular's dirty checking on $scope data for complex data visualizations to keep your application from bogging down, so the code you write for working with your data in angular is going to end up looking a lot like the code you would write in backbone.
I had resisted angular's "magic" for a while, but I'm starting to get won over by the speed of development you can achieve because of all the built in directives, scope checking, and other goodies. Angular still allows you to poke around in its internals to speed up your code when that's required. This "digging" may take more time than in backbone (because the code base is more complex), but I've found that time lost in this phase is usually recouped by time saved avoiding common bugs like memory leaks in view code and writing boilerplate code like view rendering and data binding.
In summary
Backbone is a good choice if you need extensive control and customization
Angular is excellent if you really like data binding
Ember is probably fine too, if for no other reason than Square uses (used?) it, and Mike Bostocks worked for Square.
In any framework the "hard" data intensive parts of your app are probably going to look similar if you write them well (i.e. get the data transformations into services and put a clean simple interface around your view code)
D3 on Angular
I use D3 on Angular since one year and I love the combination of both. Angular uses directives to create new and reusable HTML elements. When you have this knowlegde, you can encapsule a D3 visualization in an Angular directive without having D3 in your controllers or somewhere else. After that you can reuse the directive everywhere in your application to visualize your data. When working in a team of multiple Angular developers, the rest of your team doesn't need to know anything about D3, because your D3 code exists only in the directive.
Directives in Angular give you a flexible way of dealing with data. You can choose whether your data persists in your directive (then you will have a static reusable visualizaiton) or you make a data binding to a controller in your Angular application (then you will have a dynamic reusable visualization). The latter can be achieved by setting a scope in your directive
scope: { data: '=' /* bi-directional binding */ },
setting the data in your controller
$scope.data = [23,44,22];
and join both in your HTML element instance
<div your-new-d3-element data="data"></div>.
Thats it!
Furthermore you can use watcher in your directive to watch changing data in your controller. A nice example of reusable d3 directives in Angular gives this example: http://bl.ocks.org/biovisualize/5372077
Moreover you can find an easy D3 on Angular setup in this article: http://goo.gl/hNS8k1
On this site you find further introductions how to use d3-plugins like the fisheye plugin in your Angular application or how to implement more complex d3 visualizations in Angular.
I have worked with Backbone+d3 and with Angular+d3 a little bit.
Backbone suffer with memory leaks problem and if you use backbone you should be accurate with garbage collection. However as mentioned before marionet or chaplin can easily solve this problem. If you want to have light and fast application I recommend you to use react engine for views and backbone models for other needs.
As to Angular it has more strict structure. However, I believe that it will be almost the same logic as Backbone. Angular takes care about architecture and everything what is needed just write code by refs and it will be maintable. Unlike backbone, where structure is your option.
I recommend you to compare routing, bindings, models to select right framework, but not views.
I always choose backbone + smths, often React. Because it is flexible and easy to manage application views. F.e. when we found react we moved from backbone to react to fix our mremory leak problems. However applicarion had about 50 views with different data visualisation tools with many controls. And we moved views step by step in two month.
It is difficult to do same thing with same efforts in angular.
D3 on Angular.js
I think these types of questions don't really have a right answer IMO, it's like asking someone whether they should choose a Subaru or a Toyota, when all they need to do is get from a to b :) Anyway, using AngularJS and D3 at the moment, so far so good :) There is a nice AngularJS directive for D3 and NVD3, if you do choose to go with AngularJS.
Also, check out this excellent post on combining D3 with AngularJS.
Happy exploring!
Related
When I started working on my current project I was given quite an arduous task - to build something that in essence suppose to replace big spreadsheet people use internally in my company.
That's why we I thought a paginated table would never work, and quite honestly I think pagination is stupid. Displaying dynamically changing data on a paginated table is lame. Say an item on page #2 with next data update can land on page whatever.
So we needed to build a grid with nice infinite scroll. Don't get me wrong, I've tried many different solutions. First, I built vanilla ng-repeat thing and tried using ng-infinite-scroll, and then ng-scroll from UI.Utils. That quickly get me to the point where scrolling became painfully slow, and I haven't even had used any crazy stuff like complicated cell templates, ng-ifs or filters. Very soon performance became my biggest pain. When I started adding stuff like resizable columns and custom cell templates, no browser could handle all those bindings anymore.
Then I tried ng-grid, and at first I kinda liked it - easy to use, it has a few nice features I needed, but soon I realized - ng-grid is awful. Current version stuffed with bugs, all contributors stopped fixing those and switched to work on a next version. And only God knows when that will be ready to use. ng-grid turned out to be pretty much worse than even vanilla ng-repeat.
I kept trying to find something better. trNgGrid looked good, but way too simplistic and doesn't offer features I was looking for out of the box.
ng-table didn't look much different from ng-grid, probably it would've caused me same performance issues.
And of course I needed to find a way to optimize bindings. Tried bind-once - wasn't satisfied, grid was still laggy. (upd: angular 1.3 offers {{::foo}} syntax for one-time binding)
Then I tried React. Initial experiment looked promising, but in order to build something more complicated I need to learn React specifics, besides that thing feels kinda non-anguleresque and who knows how to test directives built with angular+react. All my efforts to build nice automated testing failed - I couldn't find a way to make React and PhanthomJS to like each other (which is probably more Phantom's problem. is there better headless browser) Also React doesn't solve "appending to DOM" problem - when you push new elements into the data array, for a few milliseconds browser blocks the UI thread. That of course is completely different type of problem.
My colleague (who's working on server-side of things) after seeing my struggles, grumbled to me that I already spent too much, trying to solve performance problems. He made me to try SlickGrid, telling me stories how this is freakin zee best grid widget. I honestly tried it, and quickly wanted to burn my computer. That thing completely depends on jQuery and bunch of jQueryUI plugins and I refuse to suddenly drop to medieval times of web-development and lose all angular goodness. No, thank you.
Then I came by ux-angularjs-datagrid, and I really, really, really liked it. It uses some smart bad-ass algorithm to keep things very responsive. Project is young, yet looks very promising. I was able to build some basic grid with lots of rows (I mean huge number of rows) without straying too much from the way of angular zen and scrolling still smooth. Unfortunately it's not a complete grid widget solution - you won't have resizable columns and other things out of the box, documentation is somewhat lacking, etc.
Also I found this article, and had mixed feelings about it, these guys applied a few non-documented hacks to angular and most probably those would breaks with feature versions of angular.
Of course there are at least couple of paid options like Wijmo and Kendo UI. Those are compatible with angular, however examples shown are quite simple paginated tables and I'm not sure if it is worth even trying them. I might end-up having same performance issues. Also you can't selectively pay just for the grid widget, you have to buy entire suite - full of shit I probably never use.
So, finally to my question - is there good, guaranteed, less painful way to have nice grid with infinite scrolling? Can someone point to good examples, projects or web-pages? Is it safe to use ux-angularjs-datagrid or better to build my own thing using angular and react? Anybody ever tried Kendo or Wijmo grids?
Please! Don't vote for closing this question, I know there are a lot of similar questions on stackoverflow, and I read through almost every single one of them, yet the question remains open.
Maybe the problem is not with the existing widgets but more with the way you use it.
You have to understand that over 2000 bindings angular digest cycles can take too long for the UI to render smoothly. In the same idea the more html nodes you have on your page, the more memory you will use and you might reach the browser capacity to render so many nodes in a smooth way. This is one of the reason why people use this "lame" pagination.
At the end what you need to achieve to get something "smooth" is to limit the amount of displayed data on the page. To make it transparent you can do pagination on scroll.
This plunker shows you the idea, with smart-table. When scrolling down, the next page is loaded (you will have to implement the previous page when scrolling up). And at any time the maximum amount of rows is 40.
function getData(tableState) {
//here you could create a query string from tableState
//fake ajax call
$scope.isLoading = true;
$timeout(function () {
//if we reset (like after a search or an order)
if (tableState.pagination.start === 0) {
$scope.rowCollection = getAPage();
} else {
//we load more
$scope.rowCollection = $scope.rowCollection.concat(getAPage());
//remove first nodes if needed
if (lastStart < tableState.pagination.start && $scope.rowCollection.length > maxNodes) {
//remove the first nodes
$scope.rowCollection.splice(0, 20);
}
}
lastStart = tableState.pagination.start;
$scope.isLoading = false;
}, 1000);
}
This function is called whenever the user scroll down and reach a threshold (with throttle of course for performance reason)
but the important part is where you remove the first entries in the model if you have loaded more than a given amount of data.
I'd like to bring your attention towards Angular Grid. I had the exactly same problems as you said, so ended up writing (and sharing) my own grid widget. It can handle very large datasets and it has excellent scrolling.
Got a webapp I'm building in Angular.
This app walks a user to authorizing accounts, presenting specific instructions based on the users choices.
I've implemented this as HTML that is shown or hidden based on values in the model, so for 3 different choices, I have 3 different sets of HTML sections that are similar but with different texts.
In the spirit of DRY, I should instead have one set of HTML sections, and instead switch the text based on the values of the model. This means putting text data inside the model, including small snippets of markup, like anchor and strong tags.
Does putting presentation data into the controller violate the principals of Angular?
There are quite a number of options to avoid repeating code depending on what you are looking to do. The following ideas are things I would consider and use when they make sense (I placed these from simple to complex, so you probably can skip the first few):
ng-bind -- Put it on a span/div. Simple & works to bind the model to the display
ng-switch, ng-if, ng-hide, ng-show -- Work to conditionally show an element
custom directive -- use this when you want to alter the behavior of an element or if you want to alter the dom based on a template. If you use "ng-transclude" the contents of the element you template will be included in the result. This can be very elegant but it works best when you have a single format. I can provide examples but angular's documentation also has excellent examples.
service -- I generally use this just to provide data only. This could be via a restful api and $resource or via $http calls. Either way, I wouldn't recommend doing much more than load/save data here.
$scope method -- In other words:
$scope.myMethod = function(x,y,z) { /* code making decisions based on the model */ }
Then you can call this method from one of the previous either via a prebuilt directive (ng-show, etc) or via a custom directive that manipulates the dom for how you expect it to be.
ng-bind-html -- Last option I know to suggest is to use this directive combined with the $sce service to bind whatever you want to the DOM. If you are binding something with angular code in it - make sure to use the $compile service as well. I generally don't favor this approach except as a last resort because it makes it harder to find where elements in the DOM are coming from and it can make debugging + testing a real pain. That said, these tools wouldn't exist if people didn't need them.
I'm sure that this isn't complete and maybe others have suggestions but that is where I would start. Best of luck!
I would put the text data in a separate angular service. This article gives an example: http://joelhooks.com/blog/2013/04/24/modeling-data-and-state-in-your-angularjs-application/
Then if you decided at some point to move it to some other storage, your service would still be the single access point for the rest of the app.
I'm trying to create a directive to allow the user to navigate the page with arrow keys by section. But I also want to be able to have those sections be scattered around the dom, and to have this not break when stuff gets added and removed. I can think of several ways to do this, but none of them are satisfactory:
Create a directive with a controller that lets other directives register themselves (and unregister on $destroy). But this will be out of order if I add something in the middle later. Also, I've tried writing it this way, and it seems like way more code than necessary.
Whenever the user hits an arrow key, make an empty array, and $broadcast an event, with a callback for directives to register themselves on that list. Then, once that list is full, advance or go backwards on it. They (should?) come back in the order they're in on the DOM, but I'm not sure since this way seems crazy and hackish.
Mark things that are 'tabbable' with css, and write this the simple way in jquery, something like this: On a new click event, var all = $('.tabbable'), and then do the obvious with that. But I really don't want to do it that way, because it's not 'the angular' way. Not out of some sense of purity, but because I'm building this as part of a larger library of widgets, and I want this functionality to be accessibly to them.
So, is there any way for me to get the scopes of all directives of a certain type, without resorting to weird hacks, or spreading the logic out all over the place?
This is a good question. +1
First, finding all directives or nodes by type goes against the Angular way. The View is the official record in AngularJS, so directives should say what they do and do what they say. Coding some process somewhere to scan for DOM nodes and act accordingly is problematic for several reasons, not the least of which are separation of concerns and testability.
I'm glad to see you're looking at other options, but I agree that the other options you provided are sub-optimal for the very reasons you mentioned. But I have one more. This is one that I've used for a different application, but that required knowledge of scattered DOM nodes.
First, we create a service to manage the state of this component. It's simple. Let's call it SectionsService. Next, we create a directive to register sections. Let's call that section for simplicity. The section directive registers the DOM node's ID (maybe created programmatically to ensure uniqueness) with the SectionsService during its linking phase. Since the DOM is processed (mostly) in order, the nodes added to the SectionsService will also be in order. So the DOM looks something like this (irrelevant stuff omitted):
<div section>...</div>
<!-- other stuff -->
<div section>...</div>
<!-- other stuff -->
<!-- etc. -->
(Though out of scope here, it would not be very difficult to program it in such a way that the order wouldn't matter, but it'd be based on specifics of your app that I don't know.)
Next, you create your triggers, like an arrow key handler. On these events, you simply tell the SectionService to go to the previous/next node in the list. AngularJS comes with a service called $anchorScroll that can be used to emulate the browser's hash-based positioning we're familiar with. You could obviously also use a jQuery plugin to animate the scrolling if you wanted to.
And that's it! A very simply directive, a fairly simple service, and whatever trigger(s) you need. All told, I'd guess less than 100 lines of code including tests. All components are decoupled and easily testable, but still really quite simple. The view remains The Truth. The Angular Way is preserved.
And there was much rejoicing.
I hope this sets you on the right direction, but of course feel free to ask a follow-up question. We can also talk code specifics too if you'd like; as I said, they wouldn't be very complicated.
AngularJS services are singletons and can be required via dependency injection. You could have your directives require a state manager service and call incrementers/decrementers.
Alternatively, a little easier but more brittle, you could keep an array in $rootScope. It's more idiomatic "angular" (but not by much) than a jquery selector global, but probably not the best route if you're building a widget library.
I've done a fair bit of introductory tutorial reading (including Absolute Beginners, Learn it Completely, and large parts of the 2 Code School Backbone courses) and I can completely see the potential that Backbone offers. But the comprehension light bulb hasn't quite turned on yet...I think the vast majority of examples being simple todo apps makes it seem a lot more trivial than it actually is when working on more complex projects.
My experiments have gone ok to a point - but I think getting answers or feedback about the following series of scattershot questions now might save me a lot of frustration and move me up the learning curve towards where I'd like to be. I've tried to include relevant snippets for examples that are of a detailed nature.
1) Routers are awesome, but are / are not the place for any heavy lifting?
The following router works to move user from initial page load into a specific route (search) which has its SearchBoxView instantiated in the last js loaded similar to app.js in the TodoMVC example. However trying to setup the views in router as shown for SummaryResultsView generates an 'is not a constructor' error.
var Workspace = Backbone.Router.extend({
routes: {
'': 'default',
'search': 'searchBox',
'summary': 'summary',
'detail': 'detail',
},
default: function() {
console.log("Router: Default");
track.Router.navigate("#search", {
trigger: true,
replace: true
});
},
searchBox: function () {
console.log("Router: Search");
},
summary: function () {
console.log("Router: Summary");
new track.SummaryResultsView({ el: $("#summary #results")});
},
I JUST found https://github.com/thomasdavis/backbonetutorials/blob/gh-pages/examples/modular-backbone/js/router.js which seems to have the router initialize behaving in a similar fashion to what I'd expect. Will try that approach next.
2) Do you have to a main page view that builds a lot of state logic in render?
What I was aiming for with the router above is that each route will have a number of views that show / hide or change their presentation based on results in the collection. Setting up what views to render on state change seemed like a good place. The TodoMVC example has the app.js view render function does a lot of the equivalent logic.
3) Underscore Templates in external files w/o require.js
I'm pretty sure I'll get to including require.js eventually - but to simplify part of the learning curve wanted to start without it. Also b/c the templates will be pulling field titles from a separate CMS and not (yet) sure how nice it will play with AMD.
4) Can it help reduce plugin dependencies like colorbox and datatables?
One of the proof of concept items I'm working towards with Backbone is a smedium-sized app that has a fair amount of presentation code written explicitly to work with these jQuery plugins. It isn't bad persay, but it seems like writing similar functionality in backbone structure would be a bit more maintainable or at least legible to understand what it is doing. Mind you, I found backbone tablesorter (out of links for post) in my travels and can't (yet) tell if it would result in any more or less tightly coupled code w.r.t plugin.
Thanks!
Routers
Sure they can be used for heavy lifting - I am sure you've heard it before, but Backbone just provides the bare-bones to built on-top of as you choose.
I would set the SummaryResultsView to a variable on the Workspace router. Otherwise whenever you call Workspace.summary() you will have ghost views hanging around.
I am not sure what you are doing with track.Router.navigate as it looks like it shares the same route #search as that defined in your Workspace router, which would cause both routes to be called.
You could always create multiple routers to help you divide your code up between different sections of your application. Starting with a main router and having child routers is usually what I try and aim for.
Main Page Views
Again, some people like doing it this way and others like to kick things off from a router. If you find you have a massive main view, try splitting it into smaller views so that you Don't Repeat Yourself.
Caching views can be useful - so on a main view that is only created once (on your application start) you could do:
var MyView = Backbone.View.extend({
childView: null,
toggleChildView: function() {
if (this.childView) {
this.childView.toggle(); //Toggle show/hide
} else {
this.childView = new ChildView({model: someModel});
}
}
});
Underscore Templates
I actually found that using require.js helped me learn. It certainly helps when the application starts to grow in size - there is a require.js implementation of the TodoMVC app - if you didn't already know.
You won't be able to use external templates without the require.js (text plugin) as it uses an AJAX call to grab the template files. Unless of course you design your own AJAX call to pull in the template files but it seems like a rather long-winded way to go about it.
Template files are static - I don't fully understand how you mean pulling from a separate CMS?
Just for a side note - if you use the require.js optimization it actually in-lines your templates so they are included in one fat JS file.
Porting code
If you figure out a very generic Backbone model that works with the jQuery plugins (I have one for a jQuery UI date picker) you can pretty easily port it between applications without too much fuss. This is sped up if using require.js as its already in a separate file (copy and paste FTW).
Hope this helps!
I'm trying to wrap my head around backbone.js but I'm finding it hard due to the lack of (IMO) good examples.
First of all, what is the best way of getting a link to an object.
If I want to get the edit url of an Album model I could do album.url() + '/edit', is this really the best way?
Also, I'm trying to make my application work 100% without javascript so I don't want my URLs/links to say /albums/#1/edit, I want it to be /albums/1/edit and override this in JS.
I'm thinking I create normal URLs and use jQuery.live to call router.navigate in backbone.js
I never got this to work however, when I call router.navigate('/albums/2', true) the URL changes but my show action is never called. If I refresh it's called so the route is matched.
What am I missing?
The basic answer, which is kind of frustrating, is "there is no preferred way!". Backbone.js doesn't tell you how to set up links, you can do it any way you like. I found this flexibility just as annoying as you do, at least at first.
So here's the way I'm approaching this on my current project, with the (big) caveat that this is just one of many ways to do things in Backbone:
For the most part, I don't use actual links. There's no explicit reason not to, but it means you have to keep track of a bunch of URL strings that have to be consistent. I would rather stick all the URL formatting in my routers and not deal with it elsewhere.
To open a new "top-level" view, like an editing screen, I set something that fires an event. In the application I'm currently working on, I have a global State model, and to open a new view I call state.set({ topview: MyTopView }). This causes the state object to trigger change:topview.
Any piece of the UI that needs to change when the top-level view changes has an update method bound to change:topview. When the event fires, they look at state.get('topview') and update as necessary.
I treat my routers as only marginally specialized parts of the UI - they're essentially views that render in the browser address bar, rather than the window. Like other views, they update the state object on UI events (i.e. a new URL), and like other views, they listen to the state object for changes that cause them to update. The logic that the editing screen has the URL albums/<albumid>/edit is fully encapsulated in the router, and I don't refer to it anywhere else.
This works well for me, but it adds an entirely new pattern, the global State object, to the Backbone structure, so I can hardly call this the "preferred" approach.
Update: Also note that .url(), in the Backbone idiom, refers to the model's URL in the back-end API, not the front-end URL (it's not like Django's get_absolute_url). There is no method in the default Backbone setup that gives you a user-facing URL for your model - you'd have to write this yourself.
Also, I'm trying to make my application work 100% without javascript; so I don't want my URLs/links to say /albums/#1/edit, I want it to be /albums/1/edit and override this in JS.
you can do exactly this w/ pushState. just enable it in your Backbone.history.start call:
Backbone.history.start({pushState: true})
this tells Backbone to use the HTML5 History API (a.k.a. "PushState"), which uses full URLs exactly like you're wanting.
read up on the history api here: http://diveintohtml5.ep.io/history.html
and I wrote up a 2 part series on using pushstate w/ the second part focusing on progressive enhancement in backbone, to do what you're needing:
http://lostechies.com/derickbailey/2011/09/26/seo-and-accessibility-with-html5-pushstate-part-1-introducing-pushstate/
and
http://lostechies.com/derickbailey/2011/09/26/seo-and-accessibility-with-html5-pushstate-part-2-progressive-enhancement-with-backbone-js/
hope that helps :)