I'm currently writing a Backbone Marionette app which ultimately amounts to about 6 different "screens" or pages which will often times share content and I am unsure of how to best structure and access Regions.
I am using the app/module setup described here: StackOverflow question 11070408: How to define/use several routings using backbone and require.js. This will be an application which will have new functionality and content added to it over time and need to be scalable (and obviously as re-usable as possible)
The Single Page App I'm building has 4 primary sections on every screen: Header, Primary Content, Secondary Content, Footer.
The footer will be consistent across all pages, the header will be the same on 3 of the pages, and slightly modified (using about 80% of the same elements/content) on the remaining 3 pages. The "morecontent" region will be re-usable across various pages.
In my app.js file I'm defining my regions like so:
define(['views/LandingScreen', 'views/Header', 'router'], function(LandingScreen, Header, Router) {
"use strict";
var App = new Backbone.Marionette.Application();
App.addRegions({
header: '#mainHeader',
maincontent: '#mainContent',
morecontent: '#moreContent',
footer: '#mainFooter'
});
App.addInitializer(function (options) {
});
App.on("initialize:after", function () {
if (!Backbone.History.started) Backbone.history.start();
});
return App;
});
Now, referring back to the app setup in the aforementioned post, what would be the best way to handle the Regions. Would I independently re-declare each region in each sub-app? That seems to be the best way to keep modules as independent as possible. If I go that route, what would be the best way to open/close or hide/show those regions between the sub-apps?
Or, do I keep the Regions declared in app.js? If so, how would I then best alter and orchestrate events those regions from sub-apps? Having the Regions defined in the app.js file seems to be counter-intuitive to keeping what modules and the core app know about each other to a minimum. Plus, every example I see has the appRegions method in the main app file. What then is the best practice for accessing and changing those regions from the sub-app?
Thanks in advance!
I actually have a root app that takes care of starting up sub-applications, and it passes in the region in which they should display. I also use a custom component based off of Backbone.SubRoute that enables relative routing for sub-applications.
check out this gist: https://gist.github.com/4185418
You could easily adapt it to send a "config" object for addRegions that defines multiple regions, instead of the region value I'm sending to the sub-applications' start method
Keep in mind that whenever you call someRegion.show(view) in Marionette, it's going to first close whatever view is currently being shown in it. If you have two different regions, each defined in its own app, but both of which bind to the same DOM element, the only thing that matters is which region had show called most recently. That's messy, though, because you're not getting the advantages of closing the previous view - unbinding Event Binders, for example.
That's why, if I have a sub-app that "inherits" a region from some kind of root app, I usually just pass in the actual region instance from that root app to the sub-app, and save a reference to that region as a property of the sub-app. That way I can still call subApp.regionName.show(view) and it works perfectly - the only thing that might screw up is your event chain if you're trying to bubble events up from your region to your application (as the region will belong to the root app, rather than the sub-app). I get around this issue by almost always using a separate instance of Marionette.EventAggregator to manage events, rather than relying on the built-in capabilities of regions/views/controllers/etc.
That said, you can get the best of both worlds - you can pass the region instance into your sub-app, save a reference to it just so you can call "close", then use its regionInstance.el property to define your own region instance pointing to the same element.
for(var reg in regions) if regions.hasOwnProperty(reg) {
var regionManager = Marionette.Region.buildRegion(regions[reg].el,
Marionette.Region);
thisApp[reg] = regionManager;
}
It all depends on what your priorities are.
I personally prefer to use the modules in my Marionette application. I feel it removes the complexity that require.js adds to your application. In an app that I am currently working on, I've created one app.js file that defines my backbone application but I am using a controller module that loads my routes, fills my collections and populates my regions.
app.js ->
var app = new Backbone.Marionette.Application();
app.addRegions({
region1: "#region1",
region2: "#region2",
region3: "#region3",
region4: "#region4"
});
app.mainapp.js ->
app.module('MainApp', function(MainApp, App, Backbone, Marionette, $, _) {
// AppObjects is an object that holds a collection for each region,
// this makes it accessible to other parts of the application
// by calling app.MainApp.AppObjects.CollectionName....
MainApp.AppObjects = new App.AppObjects.Core();
MainApp.Controller = new Backbone.Marionette.Controller.extend({
start: function() {
// place some code here you want to run when the controller starts
} //, you can place other methods inside your controller
});
// This code is ran by Marionette when the modules are loaded
MainApp.addInitializer(function() {
var controller = new MainApp.Controller();
controller.start();
});
});
You would then place your routes inside another module that will be accessed in the controller.
Then in the web page, you would start everything by calling.
$(function () {
app.start();
});
Marionette will automatically run and load all of your modules.
I hope this gets you started in some direction. Sorry I couldn't copy and past the entire application code to give you better examples. Once this project has been completed, I am going to recreate a demo app that I can push to the web.
Related
I'm currently make a html page by using angularjs.
I have 1 html page, with 1 sidebar, 1 navigation bar and 1 content area.
Like this : AdminLTE
I've follow this instruction : this
And successfully, my app works ok.
But I don't know how to apply multilingual function to my every app.
For example : Navigationbar is 1 app, sidebar is 1 app and main content is 1 app.
How can I apply 1 translationService to 'em without downloading json again and again ?
Can anyone help me please ? Thank you.
I still think it would be better to have one application for the whole page, but have separate controllers for the nav, sidebar, and main content. That way they all work separately, but you don't have the awkwardness of dealing with separate apps. The only reason I can think of to have separate apps is if you want to make sure that services are NEVER shared between the different parts. However, in your case, it looks like you WANT to share the translate service, so I think it makes sense to use one app.
If you really want to have multiple apps, it is still possible. You can load the translations asynchronously, then when this is done, you call angular.module() for each app and inject the translations as a constant. Then, when you configure the translate provider, you can inject your translation constant just like you would inject any service.
I have done this in an application before, but I don't have access to the code right now. I did it for a single application, but you can easily extend it to multiple applications. I believe it looked similar to this:
var $http = angular.injector().get('$http');
var $q = angular.injector().get('$q');
var promises = [
$http.get('path/to/translations/en.json'),
$http.get('path/to/translations/fr.json'),
];
$q.all(promises)
.then(function(translations) {
angular.module('app', [])
.constant('translations_en', translations[0])
.constant('translations_fr', translations[1])
.config(['$translateProvider', 'translations_en', 'translations_fr',
function($translateProvider, translations_en, translations_fr) {
$translateProvider.translations('en', translations_en);
$translateProvider.translations('fr', translations_fr);
}]);
angular.bootstrap(element, ['app']);
});
For multiple apps, you would need to run the angular.module block once for each app.
Alternatively, you could define separate modules for each part, then define a parent module that depends on the other mini-modules, i.e.
angular.module('navigation', []);
angular.module('sidebar', []);
angular.module('mainPage', []);
angular.module('app', ['navigation', 'sidebar', 'mainPage']);
angular.bootstrap(element, ['app']);
I believe that all modules would share the same translate service in this case.
Sorry for a not very specific question by someone new to node webkit, new to Angular, new to about everything in web development:
My app is based on a JSON file that I load at the init of my node webkit app and which is at the center of a bunch of calculations.
In the app, one can open a file dialog to create a new JSON file. Now, of course, I would like the app to recalculate everything based on the new JSON. It works when I press the "refresh" button of node webkit, but I couldn't get it running by using either
require('nw.gui').Window.open('index.html');
nor
require('nw.gui').Window.get().reload(3);.
I am also wondering if handling this on the node level is the good way to do it. Shouldn't it rather be done by Angular? But I couldn't really connect to the content of my controller from an "outside" javascript.
Grateful for any hint...
Having logic on the page loading is always tricky and as you mentioned - requires page reloading what is not very elegant and modern applications avoid this.
In your case, I suggest that if your JSON file is not very big - store it in variable and modify it as needed. The elegant way will be to create Angular service, which can act as a "model".
angular.service('JsonService', function() {
var json = {
// content
};
return {
getJson: function () {
return json;
},
setJson: function (newJson) {
json = newJson;
}
};
});
Then, whenever you need to update JSON invoke setJson(newJson) method and modify your controllers to use the service getJson() method.
You can also add the loading/saving to file functions to this service. The loading function can be invoked in your main controller connected to your dashboard page. Then before the first page will be visible, the JSON file will be already loaded and you preserve desired behavior.
I have multiple backbone views that make up a screen, currently I am doing it like this:
render : function() {
this.$el.html(this.template());
this.view2 = new View2();
this.view2.render();
this.$el.append(this.view2.$el);
... more views added here as necessary
}
Is this the correct way to handle multiple views or is there a better way to attach views to the current view?
I want to be able to destory and recreate a view with the relevant HTML getting removed/added from the parent view without having to manually do it.
To be honest, i really recommend you to checkout Marionette features. I had an experience building large scale app with backbone, and can say it real magic wand in developments. It helps you avoid memory leaks working with views and make you code more compact and clear decreasing boilerplate code.
So in your case you can get Layout View, create regions and render views in regions.
var Layout = Marionette.Layout.extend({
regions: {
head: "#head",
main: "#main"
}
});
var layout = new Layout();
layout.render();
layout.main.show(new SomeView());
I'm reading up in choosing the correct client-side framework to segment/modularize my frontend code in Widgets.
Basically what I have/want is:
a complex website with multiple pagetypes, so no single-page application.
all pages are able to render a complete page WITHOUT the use of javascript. IOW: javascript is used as enrichment only.
Lots of pages have a very dynamic way in which widgets can be shown on screen. To overcome complexity at the server-side I've modularized my code into widgets (composite pattern), where each widget is responsible for it's own:
server-side controller code
server-side templating (using hogan/mustache)
routing endpoints, should it need to be called from the client
structural css (css converning the structure of the widget as opposed to the look&feel)
a server-side RegionManager ultimately decides which widgets are rendered and where they are rendered on screen. Endresults is that the RegionManager spits out the entire html (server-generated) as the composite of the rendering of all of it's widgets.
Now, some of these widgets DO have client-side logic and need rerendering on the client. Take a searchpage for instance, which needs to be able to update through ajax. (I've described this process, which uses DRY templating on client and server, here)
What I ultimately want is that, given I already use the composite pattern on the server, to extend this to the client somehow so that a Widget (1 particular logic block on the screen) contains all mentioned server-side code, plus all needed client-side code.
I hope this makes sense.
Would Marionette be suited to be used as a client side framework in this scenario? I'm asking since I'm not 100% sure if the concept of a Marionette Module is what I describe as being a Widget in above scenario. (I'm mentioning Twitter Flight in my question, since I believe this would be a fit, but it currently is so new that I'm hesitant to go with it at the moment_
I think basically what I'm asking is if anybody has some experience doing something along these lines.
I think just using Backbone.js is perfect for this type of application you are describing. You have probably already read this, but most of the backbone literature is focused around your views having associated server generated JSON models and collections, then using the View's render function to generate (on the client) the HTML UI that represents the model/collection.
However it doesn't have to be used this way. In fact there is nothing stopping you attaching views to existing elements that contain content already, which gives you all of the benefits of Backbone's modularity, events system and so on. I often use views that have no model or collection, purely because I like the conformity of style. I have also used an approach like I describe below in the cases where I have had to work with older, existing applications that have not yet got, or never will have a nice REST API, but they do provide content in HTML.
Firstly, lets assume the following HTML represents one of your widgets:
<div id="widget">
<div class="widget-title"></div>
<div class="widget-body">
<!-- assume lots more html is in here -->
Do something!
</div>
</div>
In this case, you could use backbone with a single Widget Model. This would be a very simple model, like this:
App.WidgetModel = Backbone.Model.extend({
intialize: function () {
this.url = this.options.url;
}
});
Take note of the fact the Widget receives a URL as a parameter to its constructor/initialize function. This widget model would represent many of your widgets (and of course you could adopt this general approach with more complicated models and pluck different data from the rendered HTML). So next for your views. As you probably know, normally you pass most views a model or collection when you instantiate them. However in this case, you could create the Widget model in your View's initialize Function and pass it a URL from the pre-rendered HTML as follows:
App.WidgetView = App.View.ComboboxView = Backbone.View.extend({
initialize: function () {
this.model = new App.WidgetModel({}, { url: this.$("a").attr("href") });
}
// rest of the view code
});
So instantiating the view would be something like:
new App.WidgetView({el: $("#widget")})'
By doing all of the above you can do pretty much everything else that backbone offers you and its modular and encapsulated nicely, which is what you are after.
The end result of this whole approach is:
You have rendered the Widget UI as pure HTML which (I assume) is functional without JavaScript.
You attach a View to the existing HTML.
You pass into the View as options, content by extracted (such as a URL) from the rendered HTML with jQuery.
The View is responsible for instantiating the Model passing on the relevant options the model needs (such as a URL).
This means all dynamic server side content is intially contained in the rendered HTML and your View is a modular JavaScript component that can do stuff to it, which I think is the end result you're after.
So you mentioned that you would like to have AJAX functionality for your widgets and that fine with this approach too. Using this approach, you can now use the standard Backbone fetch and save functions on the Widget model to get new content. In this example it is from the URL retrieved from the rendered HTML. When you get the response, you can use the view's, render function, or other finer grained functions to update the HTML on the page as required.
A few points:
The only thing to look out for is that you'll need to change the content type of the fetch and save functions to "text/html" if that's what the server is providing. For example:
this.model.fetch({
type: "POST",
contentType: "text/html"
});
Lastly, the model I have proposed is instantiated with no content. However if your ajax calls are a content type of "text/html", you may need to play around with you model so it can store this content in its attributes collection properly. See this answer for more information.
Of course the answer is "Use it when you want to show the app". Ok that is fair enough. But what about subviews? I am using Ted's example: https://github.com/t2k/backbone.marionette-RequireJS. That example only has one controller setup. I have six controllers which I copied the libraryController in Ted's example. Each example has the following code:
var _initializeLayout = function() {
console.log('initialize Start Layout...');
Controller.layout = new Layout();
Controller.layout.on("show", function() {
vent.trigger("startLayout:rendered");
});
vent.trigger('app:show', Controller.layout); <!-- is this needed for each?
};
So I have that code in each of my controllers. The StartLayout has two regions that have their own views that are the entry points to the InspectorController and the PlayerController.
Each of those controllers has:
vent.trigger('app:show', Controller.layout);
So it would seem to me that I may be calling 'app:show' more than needed. Once for every Controller that needs initializing.
Is this necessary? I can understand perhaps calling that when I'm dealing with direct child views of the app but if I'm deep into PlayerController and the app view isn't visible it seems like overkill.
Thanks,
Andrew
Try not to think of "calling" app:show. It's not a function, it's an event. An event can have an arbitrary number of subscriptions listening for it. In the case of this application, there is only one listener on that event:
vent.on('app:show', function(appView) {
app.content.show(appView);
});
In this case, it's telling the content region to display whatever view is included in the event as appView. So, if you want to replace the content region with your own view, you should trigger app:show with a parameter of whatever view you want the content region to display.
content is bound to a DOM element, and whenever you call content.show(someView), the contents of that DOM element will be replaced by whatever is generated by someView.render().el.
I would suggest reading up on Layouts, Regions, and Events.
Hope this helps.