DomReady with backbone.js - backbone.js

I am using backbone.js to create a single page app. I am new to backbone, so please forgive any wrong semantics.
My Problem is when rendering the views.
Initially, I have a javascript in my index.html that executes the some dom manipulation(image slider).
The JS is wrapped in $(window).load() so all is fine on initiation.
The code obviously doesn't execute unless the page is loaded from url. the code will not run from backbone views or router. So the page loads without the dom manipulation.
I have tried to insert my code into the render and initialize function in the view, but to no avail. Should I add this code to the router? that seems to be a bit of a hack.
Where should I include the "dom ready" code?
and / or is there a better way to manage views and their dom elements on load in backbone?
the code:
home.js
window.HomeView = Backbone.View.extend({
initialize:function () {
this.render();
},
render:function () {
$(this.el).html(this.template());
this.startOrbits();
return this;
},
startOrbits:function(){
$(window).load(function() {
$('#orbit-main').orbit({ fluid: '16x6', swipe:true });
$('#orbit-news').orbit({ fluid: '8x6', bullets: true, directionalNav:false, captions:true, advanceSpeed: 9000});
});
},
});

But when I go to another view, then back, the code obviously doesn't
excite
I'm not quite sure what that means. Leaving the "excite" part aside, you don't "go to" views; views are just ways of adding elements to the page, or adding logic to existing elements.
If I had to guess though, I'd imagine that you're using the Backbone router to move between virtual "pages" (and you use views to make those pages). If that's the case, you need to look at the Backbone router events:
http://documentcloud.github.com/backbone/#Router
http://documentcloud.github.com/backbone/#FAQ-events
Specifically, I think you want to bind an event handler (on your router) to "route:nameOfYourRoute", or just :route" (if you want to trigger your logic on every virtual page load).
Hope that helps, and if my guesses are wrong please edit your question to clarify.

I was able to find a solution.
After commenting out the if statement in my router function, things went smoothly.
home: function () {
// if (!this.homeView) {
this.homeView = new HomeView();
// }
$('#main-content').html(this.homeView.el);
this.homeView.startOrbits();
this.headerView.selectMenuItem('home');
},
I do realize that this means I create a new view on every rout trigger.
Please feel free to offer more optimal solutions.

Related

Google maps not always fully rendering in Ionic

Having trouble with always rendering google maps in my Ionic app. When I first land on a view from a list of items on the previous view, the map always renders in its complete state. However, if I go back to the previous view and tap a different business, or even the same one, it appears as if the map is only rendering 25% of the complete map. I'm having this issue on both the emulator and on my iPhone.
Example
Code
getData.getBusinesses()
.then(function(data) {
// get businesses data from getData factory
})
.then(function(data) {
// get businesses photo from getData factory
})
.then(function(data) {
// get some other business stuff
})
.then(function() {
// get reviews for current business from separate async call in reviews factory
})
.then(function() {
// instantiate our map
var map = new GoogleMap($scope.business.name, $scope.business.addr1, $scope.business.city, $scope.business.state, $scope.business.zip, $scope.business.lat, $scope.business.long);
map.initialize();
})
.then(function() {
// okay, hide loading icon and show view now
},
function(err) {
// log an error if something goes wrong
});
What doesn't make sense to me is that I'm using this exact code for a website equivalent of the app, yet the maps fully load in the browser every time. The maps also fully load when I do an ionic serve and test the app in Chrome. I did also try returning the map and initializing it in a following promise, but to no avail.
I've also tried using angular google maps, but the same issue is occurring. I think I might want to refactor my gmaps.js (where I'm creating the Google Maps function) into a directive, but I don't know if that will actually fix anything (seeing as angular google maps had the same rendering issue).
I don't think the full code is necessary, but if you need to see more let me know.
EDIT
It seems that wrapping my map call in a setTimeout for 100ms always renders the map now. So I guess the new question is, what's the angular way of doing this?
I'm seeing similar issues with ng-map in Ionic. I have a map inside of a tab view and upon switching tabs away from the map view and back again, I would often see the poorly rendered and greyed out map as you describe above. Two things that I did that may help fix your issue:
Try using $state.go('yourStateHere', {}, {reload: true}); to get back to your view. The reload: true seemed to help re-render the map properly when the map was within the tab's template.
After wrapping the map in my own directive, I found the same thing happening again and wasn't able to fix it with the first suggestion. To fix it this time, I started with #Fernando's suggestion and added his suggested $ionicView.enter event to my directive's controller. When that didn't work, I instead added a simple ng-if="vm.displayMap" directive to the <ng-map> directive and added the following code to add it to the DOM on controller activation and remove it from the DOM right before leaving the view.
function controller($scope) {
vm.displayMap = true;
$scope.$on('$ionicView.beforeLeave', function(){
vm.displayMap = false;
});
}
Hope that helps.
don't use setTimeout on this!
You need to understand that the map is conflicting with the container size or something (example: map is loading while ionic animation is running, like swiping).
Once you understand this, you need to set map after view is completely rendered.
Try this on your controller:
$scope.$on('$ionicView.enter', function(){
var map = new GoogleMap($scope.business.name,
$scope.business.addr1, $scope.business.city,
$scope.business.state, $scope.business.zip,
$scope.business.lat, $scope.business.long);
map.initialize();
});

the role of backbone router

This is a quote from Derick Baileys blog ( creator of Backbone.Marionette):
But a router should never instantiate a view and manipulate the DOM
directly with jQuery or placing the view in to the DOM
But I keep seeing code like this:
var Router = Backbone.Router.extend({
routes: {
"":home
},
home: function(){
// instance of a view object in a router
var homeView = new HomeView({model:model});
// do something with homeView
})
Can this be considered anti-pattern, although it is widely used ?
From my perspective, it is a good idea to separate definiton from instantiation of objects (calling new HomeView() later). But does this is to be found in router ?
Someone else asked the same question in the comments of the article you are referring to. Here is the answer Derick gave to which I agree, I am copying it in order to keep things centralized.
These two examples are functionally the same:
example 1
Backbone.Router.extend({
routes: {
"foo": "showFoo"
},
showFoo: function(){
var fooView = new FooView();
fooView.render();
$("#someEl").html(fooView.el);
}
});
example 2
Backbone.Router.extend({
routes: {
"foo": "showFoo"
},
showFoo: function(){
FooApp.show();
}
});
FooApp = {
show: function(){
var fooView = new FooView();
fooView.render();
$("#someEl").html(fooView.el);
}
}
The difference between the two is coupling, cohesion, encapsulation, separation of concerns and the single responsibility principle. If I want to change how the FooApp shows the application's views and make things work, I shouldn't have to change the Router. These two concerns should be separated. I should have "one reason to change" for the router and for the FooApp (high level application object).
For a small demo app, #1 is ok. Anything more than 1 or 2 views, though, and the code becomes unmaintainable quickly. Having to sift through all of the cruft of doing the actual display so that you can see what the router is doing as a whole is a really bad idea in my experience.
For a larger example of this at play, check out my BBCloneMail sample project: http://github.com/derickbailey... - look at the BBCloneMail.Router.js file.
I prefer initialize all view only once in router.initialize() and then just render them in routing methods. But I don't think it make such a big difference.

Backbone.js change url without reloading the page

I have a site that has a user page. On that page, there are several links that let you explore the user's profile. I'd like to make it so that, when one of those links is clicked on, the url changes, but the top third of the page containing the user's banner doesn't reload.
I'm using Backbone.js
I have a feeling that I'm in one of those situation where I have such a poor understanding of the problem I'm dealing with that I'm asking the wrong question, so please let me know if that appears to be the case
My mistake was assuming that there was a special, built-in way of doing this in backbone. There isn't.
Simply running the following line of code
window.history.pushState('object or string', 'Title', '/new-url');
will cause your browser's URL to change without reloading the page. You can open up the javascript console in your browser right now and try it with this page. This article explains how it works in more detail (as noted in this SO post).
Now I've just bound the following event to the document object (I'm running a single page site):
bindEvents: () ->
$(document).on('click', 'a', #pushstateClick)
pushstateClick: (e) ->
href = e.target.href || $(e.target).parents('a')[0].href
if MyApp.isOutsideLink(href) == false
if e.metaKey
#don't do anything if the user is holding down ctrl or cmd;
#let the link open up in a new tab
else
e.preventDefault()
window.history.pushState('', '', href);
Backbone.history.checkUrl()
See this post for more info.
Note that you CAN pass the option pushstate: true to your call to Backbone.history.start(), but this merely makes it so that navigating directly to a certain page (e.g. example.com/exampleuser/followers) will trigger a backbone route rather than simply leading to nowhere.
Routers are your friend in this situation. Basically, create a router that has several different routes. Your routes will call different views. These views will just affect the portions of the page that you define. I'm not sure if this video will help, but it may give you some idea of how routers interact with the page: http://www.youtube.com/watch?v=T4iPnh-qago
Here's a rudimentary example:
myapp.Router = Backbone.Router.extend({
routes: {
'link1': 'dosomething1',
'link2': 'dosomething2',
'link3': 'dosomething3'
},
dosomething1: function() {
new myapp.MyView();
},
dosomething2: function() {
new myapp.MyView2();
},
dosomething3: function() {
new myapp.MyView3();
}
});
Then your url will look like this: www.mydomain.com/#link1.
Also, because <a href=''></a> tags will automatically call a page refresh, make sure you are calling .preventDefault(); on them if you don't want the page to refresh.

Correctly configuring Routers for Parent Child Relationship in backbone Marionette

Trying to create a page that allows users to add edit and view a parent child combined.
UI has 3 columns
Parent : List of Parents Children : Child
I want to configure the controllers(s) so that users can come back to right where they were but see no need to have it so both Parent and child can be editable.
// Getting closer using backbone marionette but still having some small issues
MyRouter = Backbone.Marionette.AppRouter.extend({
appRoutes: {
'': 'AddClient',
'View/:clientid': 'ViewClient',
'Edit/:clientid': 'EditClient',
'View/:clientid/Add': 'PolicyAdd',
'View/:clientid/View/:policyid': 'PolicyView',
'View/:clientid/Edit/:policyid': 'PolicyEdit'
}
});
someController = {
AddClient: function () {
var someView = new ClientAdd();
MyApp.clientPane.show(someView);
},
ViewClient: function (clientid) {
var someView = new ClientView();
MyApp.clientPane.show(someView);
},
EditClient: function (clientid) {
var someView = new ClientEdit();
MyApp.clientPane.show(someView);
},
PolicyAdd: function (clientid) {
this.ViewClient(clientid);
var someView = new PolicyAdd();
MyApp.policyPane.show(someView);
},
PolicyView: function (clientid, policyid) {
this.ViewClient(clientid);
var someView = new PolicyView();
MyApp.policyPane.show(someView);
},
PolicyEdit: function (clientid, policyid) {
this.ViewClient(clientid);
var someView = new PolicyEdit();
MyApp.policyPane.show(someView);
}
};
Having the "this.ViewClient" feels hacky and also doesn't work.
Multi-part answer, here...
"this.ViewClient is not a function"
this is a bug in Marionette. the controller method is called in the context of the router instead of the controller, so the call to this.ViewClient is trying to find it on the router.
oops.
bug logged. will fix asap. https://github.com/derickbailey/backbone.marionette/issues/38
--
UPDATE: this bug is now fixed in v0.5.1 of Backbone.Marionette https://github.com/derickbailey/backbone.marionette
--
to work around this issue for now, you can do this:
PolicyEdit: {
someController.ViewClient();
// ...
}
If that doesn't work, you may need to use Underscore.js' bind or bindAll methods to ensure the correct binding on your controller functions.
These workaround won't be necessary once i get the bug fixed... hopefully later today / tonight.
is basically calling other routes the best way to manipulate multiple regions?
The direct answer to this question is no.
But, you're not calling a route in this case. You're calling a method on your controller. That's perfectly fine - and actually, I would encourage this. It's a proper use of your object, and is one of the things that I think should be done instead of calling another route / router handler.
Routers And Controllers
A router is a feature, not an architectural requirement. Your app should work without a router, and a router should only add the ability to use bookmarks and the browser's forward/backward button.
With that philosophy in mind (which I know is controversial), using a controller like you have and calling multiple methods on your controller in order to get your application to the correct state, is one of the right approaches to take.
Look at it this way: if you removed the router from your app, you would be forced to call methods on your controller directly. To prevent duplication of code, you'll want to create many small methods on your controller that can do one thing very well, and then compose larger methods out of those smaller methods.
Hope that helps. :)

multiple matching routes

I've got a backbone.js application that defines two controllers, and the controllers both define route patterns which match the location.hash. I'm having trouble getting both of them to fire - e.g.
ManagerController = Backbone.Controller.extend({
routes: {
":name": "doStuff"
},
doStuff : function(name) {
console.log("doStuff called...");
}
});
Component1Controller = Backbone.Controller.extend({
routes: {
"xyz123": "doMoreStuff"
},
doMoreStuff : function() {
console.log("doMoreStuff called...");
}
});
so if the url is "http://mysite.com/#xyz123", then I am seeing 'doStuff()' called, or if I comment out that route, then 'doMoreStuff()' is called. But not both.
I'm using this architecture because my page is highly component oriented, and each component defines its own Controller. A 'component manager' also defines a Controller which does some house keeping on all routes.
Should I be able to configure two controllers that both respond to the same route? Cheers,
Colin
Short answer: No, you can't do that. One Controller per page.
Long answer: When you instantiate a new Controller, it adds its routes to the History singleton. The History singleton is monitoring the hash component of the URL, and when the hash changes, it scans the routes for the first expression that matches its needs. It then fires the function associated with that route (that function has been bound to the controller in which it was declared). It will only fire once, and if there is a conflict the order in which it fires is formally indeterminate. (In practice it's probably deterministic.)
Philosophical answer: The controller is a "view" object which affects the presentation of the whole page based on the hash component of the URL. Its purpose is to provide bookmark-capable URLs that the user can reach in the future, so that when he goes to a URL he can start from a pre-selected view among many. From your description, it sounds like you're manipulating this publicly exposed, manually addressable item to manipulate different parts of your viewport, while leaving others alone. That's not how it works.
One of the nice things about Backbone is that if you pass it a route that's already a regular expression, it will use it as-is. So if you're trying to use the controller to create a bookmarkable description of the layout (component 1 in the upper right hand corner in display mode "A", component 2 in the upper left corner in display mode "B", etc) I can suggest a number of alternatives-- allocate each one a namespace in the hash part of the URL, and create routes that ignore the rest, i.e.
routes: {
new RegExp('^([^\/]*)/.*$'): 'doComponent1stuff',
new RegExp('^[^\/]*/([^\/]*)\/.*$': 'doComponent2stuff',
}
See how the first uses only items after the first slash, the second after the second slash, etc. You can encode your magic entirely how you want.
I suggest, though, that if you're going to be doing something with the look and feel of the components, and you want that to be reasonably persistent, that you look into the views getting and setting their cookies from some local store; if they're small enough, cookies will be enough.
I have a very similar issue. At present, backbone stops after the first matching route. I have a dirty workaround where I am overriding the loadUrl method of Backbone History. Here I am iterating through all of the registered routes and triggering callback for all of the matching routes .
_.extend(Backbone.History.prototype, {
loadUrl : function() {
var fragment = this.fragment = this.getFragment();
var matched = false;
_.each(this.handlers, function(handler) {
if (handler.route.test(fragment)) {
handler.callback(fragment);
matched = true;
}
});
return matched;
}
})
Philosophically, I am fine with having single controller per page. However, in a component based view framework, it will be nice to have multiple views per route rendering different parts of a view state.
Comments are welcome.
I've used namespacing to deal with a similar problem. Each module comes with it's own module controller, but is restricted to handle routes that start with /moduleName/ this way modules can be developed independently.
I haven't fully tested this yet, if you take a look at the Backbone.js source, you can see this at line 1449:
// Attempt to load the current URL fragment. If a route succeeds with a
// match, returns `true`. If no defined routes matches the fragment,
// returns `false`.
loadUrl: function(fragment) {
fragment = this.fragment = this.getFragment(fragment);
return _.any(this.handlers, function(handler) {
if (handler.route.test(fragment)) {
handler.callback(fragment);
return true;
}
});
}
The any method will stop as soon as it matches a handler route (with the "return true"), just comment the return and the short-circuit will never happend, and all the handlers will be tested. Tested this with a marionette app with two modules, each one having it's own router and controller, listening same routes anb both fired up.
I think this is the simplest way of resolving it
routes: {
'': 'userGrid',
'users': 'userGrid',
}

Resources