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')
);
Related
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
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
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);
},
...
I want to map JSON having hierarchical structure onto Model. I can map the data at a top hierarchy onto Model. However, I can't map it onto Model which nested the element which I nested.
JSON
{
"attr1":"data1",
"chi1": {
"attr1":"chi1_data"
},
"list1":[
{"name":"name1"},
{"name":"name2"}
]
}
JavaScript
var Child2 = Backbone.Model.extend({
fun1:function() {
alert("this is Child2");
}
});
var List1 = Backbone.Collection.extend({
url: "list1",
model: Child2,
fun1:function() {
alert("this is List1");
}
});
var Child1 = Backbone.Model.extend({
});
var Root1 = Backbone.Model.extend({
url: "sample.json",
defaults : {
list1 : new List1,
chi1 : new Child1,
}
});
var View1 = Backbone.View.extend({
el: "#friends",
events: {
"click button": "sample"
},
initialize: function() {
this.root1 = new Root1();
},
sample: function() {
this.root1.fetch({
success: function(model) {
// this is success
alert(model.get("attr1"));
// this is error
alert(model.get("list1").fun1());
// this is error too.
model.get("list1").each(function(attr) {
alert(attr.fun1());
});
},
error: function(model, res) {
alert("error: " + res.status);
}
});
},
});
You might want to take a look at this plugin.
http://documentup.com/afeld/backbone-nested/
Might not be exactly what you want, but it could at least point you in the right direction.
The other thing you can do is override the parse method on your model...
parse: function(resp){
// And setup the model using the raw resp
// The resp data is your json from the server and will
// be used to setup the model. So overriding parse, you can
// setup the model exactly they way you want.
return resp;
}
thank you jcreamer.
backbone-nested plugin seems to be different from what I want to do.
I can realize the nest of the model. In using parse function.
// it is able to get "chi1_data"
new Child2(JSON.parse(JSON.stringify(resp["chi1"]))).get("attr1")
// it is able to get "name2"
new Child2(JSON.parse(JSON.stringify(new List1(JSON.parse(JSON.stringify(resp["list1"]))).get(2)))).get("name")
I found Backbone-relational plug in. I will try this
https://github.com/PaulUithol/Backbone-relational
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) {
// ...