Backbone routes doesn't work with Laravel - javascript

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.

Related

Override method of an existing instance

I'm trying to extend an existing Durandal router plugin instance already created with the help of RequireJS.
The method map() should be overriden to add extra mapping parameters.
How should I access the original method from the modified one?
index.js
define([ 'durandal/app', 'app/childRouter'], function(
app, childRouter) {
childRouter.map([ {
route : 'details/:id',
moduleId : 'details/index',
}, {
route : 'details/tabs/base',
moduleId : 'details/tabs/base',
} ]);
childRouter.buildNavigationModel();
return {
router : childRouter
};
});
childRouter.js
define([ 'durandal/app', 'plugins/router'], function(
app, router) {
var childRouter = router.createChildRouter();
childRouter._map = childRouter.map;
childRouter.map = function(data) {
data.unshift({
route : [ '', 'grid' ],
moduleId : 'grid/index',
});
childRouter._map(data);//CALLS THE OVERRIDEN METHOD AGAIN INSTEAD OF ORIGINAL
};
return childRouter;
});
If you want to still call the original "map" function, you'll need to store it before you overwrite it. It's a bit "hacky" to replace functions in this way, but if you REALLY had to do it, this will work:
var childRouter = router.createChildRouter();
childRouter.originalMap = childRouter.map; // Save original
childRouter.map = function(data) {
data.unshift({
route : [ '', 'grid' ],
moduleId : 'grid/index',
});
childRouter.originalMap(data); // Call original
};

How to get the type of route path in Ember

If I have in the router map:
this.resource('detail', { path: '/detail/:type' }, function() {
...
});
And I retrive the currentPanth in my Ember Application code:
currentPath: '',
ApplicationController : Ember.Controller.extend({
updateCurrentPath: function() {
App.set('currentPath', this.get('currentPath'));
console.log('currentPath',App.currentPath);
}.observes('currentPath')
}),
When I navigate in my app, I get the route names by console, but when It is "detail" I get "detail.index". How can I get the type?
you only have access to the params in the route, ie. when you are defining your model:
App.Router.map(function() {
this.resource('photo', { path: '/photos/:photo_id' });
});
App.PhotoRoute = Ember.Route.extend({
model: function(params) {
return Ember.$.getJSON('/photos/'+params.photo_id);
}
});
Or you can also use paramsFor, also in the route only.
Depending on what you are trying to acomplish maybe query params suit better

pass parameters to backbone fetch url to deal with a non-standard api

I am trying to manipulate backbone's fetch method to to deal with a bit of a non-standard api. The way the api works is as follows:
api/products/[page]?param1=val&param2=val
ex:
api/products/2?budget=low&categories=all
would be equivalent to getting the second page of results for which the budget is low and all categories are included.
I can pass the parameters after the query string just fine through the format:
self.productsItemsCollection.fetch({ success : onDataHandler, dataType: "json", data: { budget: 'low', categories: 'all' } });
but I'm not sure what to do about the pagination, since it comes before the ? question mark.
Here is how the collection is set up:
define([
'underscore',
'backbone',
'models/products/ProductsItemsModel'
], function(_, Backbone, ProductsItemsModel){
var ProductsItemsCollection = Backbone.Collection.extend({
model: ProductsItemsModel,
initialize : function(models, options) {}, //MH - need to pass filters to this function
url : function() {
return '/api/products/'; //MH - need to pass page number to be appended to this url
},
parse : function(data) {
debugger;
return data.items;
}
});
return ProductsItemsCollection;
});
How do I include the pagination in backbone's fetch command given this api URL structure?
You're on the right track in that Backbone can use the return value of a function as its 'url' value. What I personally would do, is set a page property on the collection (referenced through something like this.page), and include that in the output of the url function.
initialize: function() {
this.page = 1; // Or whatever the default should be
},
url: function() {
return '/api/products/ + this.page;
}
The problem then becomes updating the page property, which can be as simple as 'ProductsItemsCollection.page = 2;'. Personally, I would also add a second fetch method to wrap the page update and fetch into a single method call.
fetch2: function(page, options) {
if (page) {
this.page = page;
}
return this.fetch(options);
}
Just few notes to your code. I think you don't need to define page number into your Collection. According to MVC pattern it's more suitable for Controller. Collection just should get parameter and return some data according to it. Meanwhile Backbone doesn't provide classic MVC Controller, but you can use for this purpose Backbone.View. So structure of your application could looks something like this:
// Collection
define([
'backbone',
'models/products/ProductsItemsModel'
], function(Backbone, ProductsItemsModel){
return Backbone.Collection.extend({
// I don't know what exactly your Model does, but if you don't override Backbone.Model with your own methods you don't really need to define it into your collection.
model: ProductsItemsModel,
initialize : function(models, options) {}, //MH - need to pass filters to this function
url : function(page) {
return '/api/products/' + page;
},
parse : function(data) {
return data.items;
}
});
});
And then in your View you can fetch needed page and render it:
define([
'jquery',
'backbone',
'ProductsItemsCollection'
], function($, Backbone, ProductsItemsCollection){
return Backbone.View.extend({
events: {
// Your logic to get page number from your pagination.
'click .pagination': 'getPageNumber'
}
collection: new ProductsItemsCollection(),
initialize : function() {
this.listenTo(this.collection, 'reset', this.render);
// initial loading collection
this.load(1); // load page #1
},
render: function () {
// your render code
}
// Example function to show how you could get page number.
getPageNumber: function(e) {
var pageNumber = $(e.currentTarget).data('pageNumber');
load(pageNumber);
},
load: function(page) {
url: this.collection.url(page),
data: {
budget: 'low',
categories: 'all'
}
}
});
});
Something like that. So in this View you just make initialization of your Collection and make initial loading. Then all you should make is passing page number to your load function.
I read these answers, i guess they make sense but this is what i went with. just really simple:
app.WorkOrder = Backbone.Collection.extend({
model: app.WorkOrderDetail,
urlRoot: '/m2/api/w/',
getWorkOrder: function(workorder_id, options) {
this.url = this.urlRoot + workorder_id;
return this.fetch(options);
}
});
Then in the view i do this:
app.AppView = Backbone.View.extend({
el: '#workorderapp',
initialize: function () {
app.workOrder.getWorkOrder(workorder_id, {
success:function(data) {
//...do something with data
}
});
},
});

Backbone router.navigate how to pass dynamic ID

Using backbone marionette I need to navigate to the following route:
'page/:id': 'page'
This is what I have tried so far:
success: function (page) {
id = page.get('id')
router.navigate('page', {trigger: true});
}
But I have two problems with above.
1) Router is undefined in my view
2) I cannot find a reference to how I pass the ID
How do I resolve this or does marionette have any build in methods?
You can pass the id just putting it in the url:
success: function (page) {
id = page.get('id')
router.navigate('page/' + id, {trigger: true});
}
Reference
Regarding the router you need to create it:
var MyRouter = Backbone.Router.extend({
routes: {
'page/:id': 'page'
},
page: function(id) {
...
}
});
var router = new MyRouter();

backbone.js, handlebars error : this._input.match is not a function

I'm new to backbone.js and handlebars and I'm having a problem getting my template to render out the data.
Here is my collection and model data from tagfeed.js module:
// Create a new module.
var Tagfeed = app.module();
// Default model.
Tagfeed.Model = Backbone.Model.extend({
defaults : {
name : '',
image : ''
}
});
// Default collection.
Tagfeed.Collection = Backbone.Collection.extend({
model : Tagfeed.Model,
url : Api_get('api/call')
});
Tagfeed.TagView = Backbone.LayoutView.extend({
template: "tagfeed/feed",
initialize: function() {
this.model.bind("change", this.render, this);
},
render: function(template, context) {
return Handlebars.compile(template)(context);
}
});
Then in my router I have:
define([
// Application.
"app",
// Attach some modules
"modules/tagfeed"
],
function(app, Tagfeed) {
// Defining the application router, you can attach sub routers here.
var Router = Backbone.Router.extend({
routes: {
"index.html": "index"
},
index: function() {
var collection = new Tagfeed.Collection();
app.useLayout('main', {
views: {
".feed": new Tagfeed.TagView({
collection: collection,
model: Tagfeed.Model,
render: function(template, context) {
return Handlebars.compile(template)(context);
}
})
}
});
}
});
return Router;
});
THis successfully makes a call to the api, makes a call to get my main template, and makes the call to get the feed template HTML. If I don't include that render(template, context) function, then it renders on the page as the straight up HTML that I have in the feed template with the {{ name }} still included. however when its included, I get the error
TypeError: this._input.match is not a function
[Break On This Error]
match = this._input.match(this.rules[rules[i]]);
and if I examine the variables that get passed into the appLayout views render function for feed, I see that the template var is a function, and the context var is undefined, then it throws that error.
Any ideas what I'm doing wrong? I know I have at least one problem here, probably more.
Since you're using requirejs, you can use the text module to externalise your templates or better still pre-compile them and include them in your view. Check out http://berzniz.com/post/24743062344/handling-handlebars-js-like-a-pro
E.g. using pre-compiled templates
// router.js
define(['views/tag_feed', 'templates/feed'], function(TagFeedView) {
var AppRouter = Backbone.Router.extend({
// ...
});
})
// tag_feed.js
define(['collections/tag_feed'], function() {
return Backbone.View.extend({
// ...
render: function() {
this.$el.html(
Handlebars.templates.feed({
name: '...'
})
);
}
});
})
For reference I've created simple boilerplate for a backbone/require/handlebars setup https://github.com/nec286/backbone-requirejs-handlebars

Categories

Resources