I am working on a web applicaiton that interacts with a RESTful api. The client is built in the laravel and backbone, what I am struggling to do is come up with an intelligent way to load in the correct, models, collections and view based on the current URL.
I have a blade template that gives me universal branding, and then are templates for each of the sections of the site, that load in appropriate underscore templates, scripts, data etc.
For example the users page is accessed at http://domain.local/users and this loads in the following scripts,
User.Collection.js
(function( Users, app_arg ){
'use strict';
Users.Collection = app.UserCollection
}(POPS.module('users'), POPS ));
User.Model.js
(function( Users, app ) {
'use strict';
Users.Model = Backbone.Model.extend({
});
})(POPS.module('users', POPS));
User.Views.Master.js
(function( Users, app_arg ) {
'use strict';
Users.Views.Master = Backbone.View.extend({
el: '#app',
template: _.template( $('#tpl-user').html() ),
events: {
"click .js-add-new-user" : "launchModal",
},
initialize: function() {
this.listenTo( this.collection, 'reset', this.render );
this.listenTo( this.collection, 'add', this.render );
// app.dashProjectCollection = this.collection;
},
render: function() {
this.$el.html( this.template() );
new app.UsersView({ collection: this.collection });
new app.userModal({ model: app.User, collection: this.collection });
//this.filterView = new Dashboard.Views.Filter();
//this.projectView = new Dashboard.Views.Projects({ collection: this.collection });
//this.CollaboratorView = new Dashboard.Views.Collaborators();
return this;
},
launchModal: function(e) {
e.preventDefault();
$("#newUser").modal();
}
});
}( POPS.module('users'), POPS ));
Everything gets fired from app.js
// Main object for the entire app
window.POPS = {
config: {
api: {
base:'http://pops.local/api/v1/',
}
},
// Create this closure to contain the cached modules
module: function() {
// Internal module cache.
var modules = {};
// Create a new module reference scaffold or load an
// existing module.
return function(name) {
// If this module has already been created, return it.
if(modules[name]) {
return modules[name];
}
// Create a module and save it under this name
modules[name] = { Views: {} };
return modules[name];
};
}(),
init: function() {
// :: app start :: //
var app = POPS;
var Module = app.module( $( '#popsapp' ).data('route') );
// Creates a Master object in the global namespace so data can be passed in from the DOM.
// This would be replaced with a master Router if we weren't using actual pages
app.Initialiser = function( initialCollection ) {
this.start = function() {
//don't cache ajax calls
$.ajaxSetup({ cache: false });
if(Module.Collection !== undefined) {
this.collection = new Module.Collection();
this.view = new Module.Views.Master({ collection: this.collection });
} else {
this.view = new Module.Views.Master();
}
if(this.collection !== undefined) {
this.collection.reset( initialCollection );
}
//moved this here so script runs after the DOM has loaded.
//but script.js still needs completely removing.
};
};
}
};
// Entry point into the application
POPS.init();
I cannot fathom why app is undefined!
in your first block you have
(function( Users, app_arg ){
'use strict';
Users.Collection = app.UserCollection
}(POPS.module('users'), POPS ));
you define app_arg in the function arguments but use app.UserCollection within the function. app here will be undefined.
This would also be the same for User.Views.Master.js
Related
I'm new to ReactJS and forgive me for now. We have an existing Marionette BackboneJS application for our hospital. However, the code below is an example working BackboneJS Marionette where I want to replace Marionette with ReactJS view. This will help me tremendously on how I'll be able to migrate to ReactJS.
It would also be awesome if we can retrieve contentPlacement: "here" using an GET method call(xhr/ajax).
<header>
<h1>An Example BackboneJS Marionette</h1>
</header>
<article id="main">
</article>
<script type="text/html" id="sample-template">
put some content <%= contentPlacement %>.
</script>
And below is the javascript code
// Define the app and a region to show content
// -------------------------------------------
var App = new Marionette.Application();
App.addRegions({
"mainRegion": "#main"
});
// Create a module to contain some functionality
// ---------------------------------------------
App.module("SampleModule", function(Mod, App, Backbone, Marionette, $, _){
// Define a view to show
// ---------------------
var MainView = Marionette.ItemView.extend({
template: "#sample-template"
});
// Define a controller to run this module
// --------------------------------------
var Controller = Marionette.Controller.extend({
initialize: function(options){
this.region = options.region
},
show: function(){
var model = new Backbone.Model({
contentPlacement: "here"
});
var view = new MainView({
model: model
});
this.region.show(view);
}
});
// Initialize this module when the app starts
// ------------------------------------------
Mod.addInitializer(function(){
Mod.controller = new Controller({
region: App.mainRegion
});
Mod.controller.show();
});
});
// Start the app
// -------------
App.start();
Here is the jsfiddle link - http://jsfiddle.net/Lvnwj2dp/1/
Can someone please guide me on how I will replace the Marionette with ReactJS for the view? A new code would really awesome!
UPDATE:
Here is my new jsfiddle. It's doing the REST api call but it's not updating the DOM. http://jsfiddle.net/6df6a2zv/10/
var url = 'http://jsonplaceholder.typicode.com';
var responseText = '';
console.log('executing the request ......');
$.ajax({
url: url + '/posts/1',
method: 'GET'
}).then(function(data) {
responseText = data;
});
var CommentBox = React.createClass({displayName: 'CommentBox',
render: function() {
return (
React.createElement('div', {className: "commentBox"},
"REST response:" + responseText
)
);
}
});
ReactDOM.render(
React.createElement(CommentBox, null),
document.getElementById('main')
);
// Define the app and a region to show content
// -------------------------------------------
// var App = new Marionette.Application();
// App.addRegions({
// "mainRegion": "#main"
// });
// Create a module to contain some functionality
// ---------------------------------------------
// App.module("SampleModule", function(Mod, App, Backbone, Marionette, $, _){
// Define a view to show
// ---------------------
// var MainView = Marionette.ItemView.extend({
// template: "#sample-template"
// });
// Define a controller to run this module
// --------------------------------------
// var Controller = Marionette.Controller.extend({
// initialize: function(options){
// this.region = options.region
// },
// show: function(){
// var model = new Backbone.Model({
// contentPlacement: "here"
// });
// var view = new MainView({
// model: model
// });
// this.region.show(view);
// }
// });
// Initialize this module when the app starts
// ------------------------------------------
// Mod.addInitializer(function(){
// Mod.controller = new Controller({
// region: App.mainRegion
// });
// Mod.controller.show();
// });
// });
// Start the app
// -------------
// App.start();
Typescript CodePen Example
I am going to do a better write up soon but here is something close to what you are looking for. I am using Typescript and left the jsx out, because one I am not a big fan of it and two it adds another thing to learn and react can be a mental leap enough at times. See the codepen link below
Typescript is essentially ES6 with a good typing system, I like to think the types are actually quite helpful when trying to learn new code.
Remember react is just the view layer you will need something like Flux to drive it with data and a router, I recommend using react-router.
here are the types from the example, this should be the only really non-js looking piece of code.
interface ViewProps {
children:any;
id:string;
headerTitle:string;
bgColor?:string;
className?:string;
}
interface ViewState {
bgColor:string;
}
http://codepen.io/Thecavepeanut/pen/mVMVEx
Cheers,
Jake
NOTE: you can look at compiled JS on the codepen
Finally figured it out
var url = 'http://jsonplaceholder.typicode.com';
var responseText = '';
var CommentBox = React.createClass({
displayName: 'CommentBox',
getInitialState: function() {
return {
response: ''
}
},
componentDidMount: function() {
var _this = this;
$.ajax({
url: url + '/posts/1',
method: 'GET'
}).then(function(data) {
_this.setState({
response: data
});
});
},
render: function() {
return (
React.createElement('div', {
className: "commentBox"
},
"REST response:" + this.state.response.title
)
);
}
});
ReactDOM.render(
React.createElement(CommentBox, null),
document.getElementById('main')
);
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 relatively new in the backbone library. I'm trying to build a mobile application based on backbone + requirejs + jquery-mobile. I can fill my collection with existing json local file. (in the future may come from a remote server).
Now I'm trying to get this collection to be called only once and then storing it in localStorage for read. for this I am trying to use this adapter (https://github.com/jeromegn/Backbone.localStorage) but I do not understand how.
Sample code
// models
define([
'underscore',
'backbone'
], function(_, Backbone) {
var AzModel = Backbone.Model.extend({
defaults: {
item: '',
img:"img/gi.jpg"
},
initialize: function(){
}
});
return AzModel;
});
// Collection
define(['jquery', 'underscore', 'backbone', 'models/az'], function($, _, Backbone, AzModel) {
var AzCollection = Backbone.Collection.extend({
localStorage: new Backbone.LocalStorage("AzStore"), // Unique name within your app.
url : "json/azlist.json",
model : AzModel
parse : function(response) {
return response;
}
});
return AzCollection;
});
define(['jquery', 'underscore', 'backbone', 'collections/azlist', 'text!templates/karate/az.html'], function($, _, Backbone, AzList, AzViewTemplate) {
var AzView = Backbone.View.extend({
id:"az",
initialize: function() {
this.collection = new AzList();
var self = this;
this.collection.fetch().done(function() {
//alert("done")
self.render();
});
},
render : function() {
var data = this.collection;
if (data.length == 0) {
// Show's the jQuery Mobile loading icon
$.mobile.loading("show");
} else {
$.mobile.loading("hide");
console.log(data.toJSON());
this.$el.html(_.template(AzViewTemplate, {data:data.toJSON()}));
// create jqueryui
$(document).trigger("create");
}
return this;
}
});
return AzView;
});
Does someone can point me the way.
The Backbone local storage adapter overrides Collection.sync, the function which is used when you fetch the collection, or save models within the collection. If you set the Collection.localStorage property, it redirects the calls to the local storage instead of the server. This means you can have either or -- read and write to local storage or server -- but not both at the same time.
This leaves you two options:
Do the initial fetch, which populates the data from the server, and only then set the localStorage property:
var self = this;
self.collection.fetch().done(function() {
self.collection.localStorage = new Backbone.LocalStorage("AzStore");
self.render();
});
Set the Collection.localStorage property as you do now, and fetch the initial dataset manually using Backbone.ajaxSync, which is the alias given to Backbone.sync by the localstorage adapter:
Backbone.ajaxSync('read', self.collection).done(function() {
self.render();
}
The latter option might be preferrable, because it doesn't prevent you from loading the data from the server later on, if required.
You could quite neatly wrap the functionality as a method on the collection:
var AzCollection = Backbone.Collection.extend({
localStorage: new Backbone.LocalStorage('AzStore'),
refreshFromServer: function() {
return Backbone.ajaxSync('read', this);
}
});
When you want to load data from the server, you can call that method:
collection.refreshFromServer().done(function() { ... });
And when you want to use the local storage, you can use the native fetch:
collection.fetch().done(function() { ... });
Edited to correct mistake in sample code for the benefit of drive-by googlers.
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
I believe my problem relates to scope somehow, as I'm a js newbie. I have a tiny backbone.js example where all I am trying to do is print out a list of items fetched from the server.
$(function(){
// = Models =
// Video
window.Video = Backbone.Model.extend({
defaults: function() {
return {
title: 'No title',
description: 'No description'
};
},
urlRoot: 'api/v1/video/'
});
// VideoList Collection
// To be extended for Asset Manager and Search later...
window.VideoList = Backbone.Collection.extend({
model: Video,
url: 'api/v1/video/'
});
// = Views =
window.VideoListView = Backbone.View.extend({
tagName: 'ul',
render: function(eventName) {
$(this.el).html("");
_.each(this.model.models, function(video) {
$(this.el).append(new VideoListRowView({model:video}).render().el);
}, this);
return this;
}
});
// VideoRow
window.VideoListRowView = Backbone.View.extend({
tagName: "li",
template: _.template("id: <%= id %>; title: <%= title %>"),
className: "asset-video-row",
render: function() {
$(this.el).html(this.template(this.model.toJSON()));
return this;
}
});
// Router
var AppRouter = Backbone.Router.extend({
routes:{
"":"assetManager"
},
assetManager:function() {
this.assetList = new VideoList();
this.assetListView = new VideoListView({model:this.assetList});
this.assetList.fetch();
$('#content').html(this.assetListView.render().el);
}
});
var app = new AppRouter();
Backbone.history.start();
// The following works fine:
window.mylist = new VideoList();
window.mylistview = new VideoListView({model:window.mylist});
});
If I access mylist.fetch(); mylist.toJSON() from the console, mylist populates fine. I can tell that this.assetList.fetch() is accurately fetching the data from the backend, but it doesn't appear to be adding the objects to this.assetList.
The fetch method on Backbone collections is asynchronous:
Fetch the default set of models for this collection from the server, resetting the collection when they arrive. [...] Delegates to Backbone.sync under the covers, for custom persistence strategies.
And Backbone.sync says:
Backbone.sync is the function that Backbone calls every time it attempts to read or save a model to the server. By default, it uses (jQuery/Zepto).ajax to make a RESTful JSON request.
So fetch involves an (asynchronous) AJAX call and that means that you're trying to use the collection before fetch has retrieved the data from the server. Note that fetch supports success and error callbacks so you can do this instead:
var self = this;
this.assetList.fetch({
success: function(collection, response) {
$('#content').html(self.assetListView.render().el);
}
});
Or you could bind a callback to the collection's reset event as fetch will reset the collection. Then render your assetListView when the collection's reset event is triggered.
Also, your assetList is a collection so you should be doing:
this.assetListView = new VideoListView({collection: this.assetList});
and:
window.VideoListView = Backbone.View.extend({
tagName: 'ul',
render: function(eventName) {
$(this.el).html("");
_.each(this.collection.models, function(video) {
// ...