I have a router that does the site navigation nicely and also works when clicking the browsers back / forward button. However, when entering directly an URL I get 404.
Here is my router:
define(function(require) {
var $ = require('jquery'),
_ = require('underscore'),
Backbone = require('backbone');
var AppRouter = Backbone.Router.extend( {
routes: {
'home' : 'homeHandler',
'webdesign' : 'webHandler',
'mobile' : 'mobileHandler',
'javascript' : 'javascriptHandler',
'hosting' : 'hostingHandler',
'contact' : 'contactHandler'
},
initialize: function() {
this._bindRoutes();
$('.link').click(function(e){
e.preventDefault();
Backbone.history.navigate($(this).attr("href"),true);
});
if(history && history.pushState) {
Backbone.history.start({pushState : true});
console.log("has pushstate");
}
else {
Backbone.history.start();
console.log("no pushstate");
}
console.log("Router init with routes:",this.routes);
},
homeHandler: function(e) {
require(['../views/home-content-view', '../views/home-sidebar-view'],
function(HomeContent, HomeSidebar) {
var homeContent = new HomeContent();
homeContent.render();
var homeSidebar = new HomeSidebar();
homeSidebar.render();
});
},
webHandler: function(e) {
require(['../views/web-content-view', '../views/web-sidebar-view'],
function(WebContent, WebSidebar) {
var webContent = new WebContent();
webContent.render();
var webSidebar = new WebSidebar();
webSidebar.render();
});
},
...
});
return AppRouter;
});
Obviously, I'm missing something.
Any clarification would be greatly appreciated.
Thanks,
Stephan
Backbone operates on a web-page (that has already been loaded in the browser). When you enter a URL in the browser directly, you're making a HTTP-request for that URL to the server. The server is not managed by Backbone. You have to define on the server the behavior when such HTTP-requests are encountered.
Related
I am trying to fire a backbone event when the user inputs the URL (whatever.com)/questions/new.
my router looks like the following:
define(['backbone'],function(BackBone){
var routeExtend = Backbone.Router.extend({
routes: {
"": "home",
"questions/new": 'newQuestion'
},
});
var initialize = function(){
var Router = new routeExtend();
Router.on('route:newQuestion', function() {
console.log('new question');
});
BackBone.history.start();
return Router;
}
return {initialize:initialize};
});
And if I call router.navigate within my app, the event fires fine.
However, I want to be able to fire the "on" event if "questions/new" is the initial URL given to the web browser. Is there any way to do this?
you could define the function instead of router.on event
var routeExtend = Backbone.Router.extend({
routes: {
"": "home",
"questions/new": 'newQuestion'
},
newQuestion: function() {
console.log('new question');
}
});
I dont't know exactly where the error/s is/are.
I'm doing a Single Page App, this is the context:
I have a resource controller in Laravel that watch this route "domain.dev/v1/"
Laravel serves the first page/view "/public/views/layouts/application.blade.php"
Mustache views are stored under "public/views/" and they are loaded synchronously when they are called by the Backbone Router (I've modified the "app/config/view.php" file to serve the views from bakcbone)
In backbone, the Router controls every URI change, even pushstate and the respective Mustache views. Everything seems to work fine, but if you type the direct URI for a user o list or users...you only see JSON returned by the server, not the corresponding Backbone View, in other words, I dont know Which is not doing the correct work, the Laravel Router or the Backbone Router. Or is it a Laravel configuration?
This is my code so far:
// app/routes.php
Route::group(['prefix' => 'v1'],function (){
Route::resource('users','V1\UsersController');
Route::get('/', function()
{
return View::make('layouts.application')->nest('content', 'app');
});
});
// app/controllers/V1/UsersController.php
namespace V1;
//import classes that are not in this new namespace
use BaseController;
use User;
use View;
use Input;
use Request;
class UsersController extends \BaseController {
public function index()
{
return $users = User::all(['id','email','name']) ;
}
public function show($id)
{
$user = User::find($id,['id','email','name']);
if(is_null($user)){
return array(
'id' => 0,
'email' => 'fake email'
);
}
return $user;
}
// public/js/namespaces.js
(function(){
window.App = {
Models : {},
Collections : {},
Views : {},
Router : {}
};
window.Templator = function (mustacheView){
return $.ajax({
url: '/views/'+mustacheView,
async : false,
type: 'GET',
}).responseText;
};
window.Vent = _.extend({},Backbone.Events);
})();
// public/js/backbone/router.js
App.Router = Backbone.Router.extend({
routes : {
'' : 'home',
'users' : 'showAll',
'users/:id' : 'showUser',
'login' : 'showLoginForm'
},
home: function (){
Vent.trigger('home:index');
},
showAll : function (){
Vent.trigger('users:showAll');
},
showUser: function (id){
Vent.trigger('users:show',id);
},
showLoginForm : function (){
Vent.trigger('login:form');
}
});
// public/js/app.js
$(function() {
$(document).on("click", "a", function(e){
var href = $(this).attr("href");
if (href != '#') {
e.preventDefault();
Backbone.history.navigate(href,{trigger:true});
}
});
new App.Views.AllUsers;
new App.Views.Index;
new App.Views.Login;
new App.Router;
Backbone.history.start({
pushState: true,
silent: true,
root: '/v1'
});
});
So, if I type this URI "domain.dev/v1/users" on the nav bar, shows list of users in JSON and the view associated in backbone is not displayed.
Any suggestions?
Have a look at my answer I just gave in a similar question about Angular: Angular and Laravel
In the text, just mentally replace Angular with Backbone.
How to pass the route from Laravel to Backbone:
The base view, that is returned from Laravel for the '/' route needs to have something like this somewhere in its <head> before backbone.js is included:
<script>
var myRoute = "{{ $route }}";
</script>
This blade template creates a Javascript variable. Then, after you declared your routes in Backbone, add this:
Backbone.history.start(); // You should already have this line, so just add the next one
App.nav.navigate(myRoute);
This should do the trick.
I have to make the one-page application in Backbone.js with a dozen of internal pages.
I decided to do it such way: every page is matched with appropriate function in Router object and consists of several Views, that representing special elements of page.
As for now, I made only two pages, but already have a total mess in a code - this pages and even internal views, that are almost completely different, have many common elements such as events handlers and another internal functions.
So, how it looks like now (briefly):
var AppRouter = Backbone.Router.extend({
routes: {
'': 'main',
'user/:username': 'account'
},
main: function() {
new MainView();
},
account: function(username) {
new AccountTopView();
new AccountMiddleView();
}
});
var LinkEvents = {
'click a': 'navigate'
};
var navigate = function(event) {
event.preventDefault();
var link = event.currentTarget;
var url = link.href;
url = RemoveBaseUrl(url);
app.navigate(url, {trigger: true});
}
var AccountMiddleView = ModalView.extend({
template: _.template($('#tpl-account-middle').html()),
events: _.extend(
{},
LinkEvents
),
render: function() {
this.$el.html(
this.template(
)
);
return this;
},
navigate: navigate
});
var AccountTopView = ModalHatView.extend({
template: _.template($('#tpl-account-top').html()),
events: _.extend(
{},
LinkEvents
),
render: function() {
this.$el.html(
this.template(
)
);
return this;
},
navigate: navigate
});
The real code is much larger, because there are already many events. Are there any ways to optimize such structure? And maybe any other advices?
I am very new to ember and trying to implement authentication via facebook
I am using ember-facebook.js library to connect with facebook. Once the authentication is successful, I want to transition to some other route e.g. '/index'. This library creates a App.FBUser object in mixin which is populated from the facebook response. The blog say following:
Whenever the user changes (login, logout, app authorization, etc) the method updateFBUser is called, updating the App.FBUser object on your application. You can do whatever you want with this binding, observe it, put it in the DOM, whatever.
Ember.Facebook = Ember.Mixin.create({
FBUser: void 0,
appId: void 0,
fetchPicture: true,
init: function() {
this._super();
return window.FBApp = this;
},
appIdChanged: (function() {
var _this = this;
this.removeObserver('appId');
window.fbAsyncInit = function() {
return _this.fbAsyncInit();
};
return $(function() {
var js;
js = document.createElement('script');
$(js).attr({
id: 'facebook-jssdk',
async: true,
src: "//connect.facebook.net/en_US/all.js"
});
return $('head').append(js);
});
}).observes('appId'),
fbAsyncInit: function() {
var _this = this;
FB.init({
appId: this.get('appId'),
status: true,
cookie: true,
xfbml: true
});
this.set('FBloading', true);
FB.Event.subscribe('auth.authResponseChange', function(response) {
return _this.updateFBUser(response);
});
return FB.getLoginStatus(function(response) {
return _this.updateFBUser(response);
});
},
updateFBUser: function(response) {
console.log("Facebook.updateFBUser: Start");
var _this = this;
if (response.status === 'connected') {
//console.log(_this);
return FB.api('/me', function(user) {
var FBUser;
FBUser = user;
FBUser.accessToken = response.authResponse.accessToken;
if (_this.get('fetchPicture')) {
return FB.api('/me/picture', function(path) {
FBUser.picture = path;
_this.set('FBUser', FBUser);
return _this.set('FBloading', false);
});
} else {
_this.set('FBUser', FBUser);
return _this.set('FBloading', false);
}
});
} else {
this.set('FBUser', false);
return this.set('FBloading', false);
}
}//updateFBUser
});
Update :
Adding following observer in my LoginController, I am able to capture the App.FBUser update event(it is update after getting response from FB; as indicated by the blog).
From this observer method, when I try to 'transitionTo' my index route I get following error
Uncaught TypeError: Object data-size has no method 'transitionTo'. Following is the code
App.LoginController = Ember.Controller.extend({
onSuccess: (function(){
var self = this;
/*
//tried all these method to redirect but error is the same
var attemptedTransition = this.get('attemptedTransition');
attemptedTransition.retry();
*/
/*
//tried all these method to redirect but error is the same
var router = this.get('target.router');
router.transitionTo('index');
*/
//tried all these method to redirect but error is the same
this.transitionToRoute('index');
}).observes('App.FBUser')
});
Index Route
App.AuthenticatedRoute = Ember.Route.extend({
beforeModel: function(transition){
var self = this;
if(!App.FBUser){
self.redirectToLogin(transition);
}
},
redirectToLogin: function(transition){
var loginController = this.controllerFor('login');
loginController.set('attemptedTransition', transition);
this.transitionTo('login');
}
});
I am not able to get my head around it.
Any help is greatly appreciated. Thanks
How can I access this object in my Route.beforeModel() hook.
Depending on what route's beforModel hook you are talking about, this is how you could do it:
App.SomeRoute = Ember.Route.extend({
beforeModel: function(transition) {
if (!Ember.isNone(App.FBUser)) {
// calling 'transitionTo' aborts the transition, redirects to 'index'
this.transitionTo('index');
}
}
});
Update in response to your last comment
The addon you are using is slightly outdated and the proposed implementation method for the mixin in your application will not work with the current version of ember:
App = Ember.Application.create(Ember.Facebook)
App.set('appId', 'yourfacebookappid');
starting from version 1.0.0-rc3 of ember you should rather do it like this:
App = Ember.Application.creatWithMixins(Ember.Facebook);
App.set('appId', 'yourfacebookappid');
After that you should be able to have access to the App.FBUser object as mentioned above.
Update 2
If you want to be able to be notified when some events happend, like login, logout etc. you should (as the Author of the addon states on it's blog post) override the updateFBUser method and do in there your transitions.
Since the addon is trough the mixin available in our App namespace you should be able to do the following:
App = Ember.Application.creatWithMixins(Ember.Facebook, {
updateFBUser: function() {
this._super();
// we are calling super to let the addon
// do it's work but at the same time we get
// notified that something happened, so do at this
// point your transition
}
});
Hope it helps.
As per Issue 1 adding
attributeBindings: [],
to:
return Ember.FacebookView = Ember.View.extend({
solved the issue.
In the Backbone.js documentation, in the entry for the Router.routes method, it is stated
When the visitor presses the back button, or enters a URL, and a particular route is matched,
the name of the action will be fired as an event, so that other objects can listen to the router,
and be notified.
I have attempted to implement this in this relatively simple example:
The relevant JS:
$(document).ready(function(){
// Thing model
window.Thing = Backbone.Model.extend({
defaults: {
text: 'THIS IS A THING'
}
});
// An individual Thing's View
window.ThingView = Backbone.View.extend({
el: '#thing',
initialize: function() {
this.on('route:showThing', this.anything);
},
anything: function() {
console.log("THIS DOESN'T WORK! WHY?");
},
render: function() {
$(this.el).html(_.template($('#thing-template').html(), {
text: this.model.get('text')
}));
return this;
}
});
// The Router for our App
window.ThingRouter = Backbone.Router.extend({
routes: {
"thing": "showThing"
},
showThing: function() {
console.log('THIS WORKS!');
}
});
// Modified from the code here (from Tim Branyen's boilerplate)
// http://stackoverflow.com/questions/9328513/backbone-js-and-pushstate
window.initializeRouter = function (router, root) {
Backbone.history.start({ pushState: true, root: root });
$(document).on('click', 'a:not([data-bypass])', function (evt) {
var href = $(this).attr('href');
var protocol = this.protocol + '//';
if (href.slice(protocol.length) !== protocol) {
evt.preventDefault();
router.navigate(href, true);
}
});
return router;
}
var myThingView = new ThingView({ model: new Thing() });
myThingView.render();
var myRouter = window.initializeRouter(new ThingRouter(), '/my/path/');
});
The relevant HTML:
<div id="thing"></div>
<!-- Thing Template -->
<script type="text/template" id="thing-template">
<a class='task' href="thing"><%= text %></a>
</script>
However, the router event referenced in the View's initialize function does not seem to get picked up (everything else works--I'm successfully calling the "showThing" method defined in the Router).
I believe I must have some misconception about what the documentation intended by this statement. Therefore, what I'm looking for in a response is: I'd love to have someone revise my code so that it works via a Router event getting picked up by the View, or, clearly explain what the Router documentation I listed above intends us to do, ideally with an alternative code sample (or using mine, modified).
Many thanks in advance for any assistance you can provide!
This is beacuse you are binding a listener to the wrong object. Try this in your View :
window.ThingView = Backbone.View.extend({
initialize: function() {
myRouter.on('route:showThing', this.anything);
},
...