Marionette building re-usable sub-apps and modules - javascript

I am currently building a complex marionette application and have been follow a great book as a reference. However, I am a sucker for reusable code and like to keep things flexible as possible.
My application has various screens and modules, one of which is a dashboard. The dashboard has its own set of responsibilities, so I made the conclusion that it should be a sub-app.
The dashboard is split 50/50 and there are two components inside. I have identified that each of these components (lets say compA and compB) each have their own set of responsibilities aswell and should be their own sub apps. However, I will have the use case where compA will be used in another area of the application.
My first thought with re-use in mind was to simply re-use the views and create a new module where ever this particular view was needed. However, the unique events and actions that come with this view are stored in the controller and API to interact with the module.
So I have ended up with the following structure:
application.js
apps
--dashboard
--compA
--compB
and I have implemented something like the following to reuse functionality from compA
Controller = {
getView: function () {
return new Show.MyView();
}
}
API = {
getMyView: function () {
return Controller.getView();
}
}
App.reqres.setHandler('compa:get:view', function () {
return API.getMyView();
});
Doing this allows me to request a new instance of the view to display and keep the same action logic. However, this means that there is no separation between each section (compA in the dashboard, and compa in another section of the app). So if I were to stop the compa module it would not have the desired results.
Is there a better approach to re-usable modules with minimal duplication of code?
My thought was to extract the Controller logic into a controller object that I can extend, and then creating a new 'sub app' when I would like to re-use the features.
application.js
apps
--dashboard
--compA-dashboard // new instance of Controller
--compA-somewhereelse // new instance of Controller
--compB
It seems as though I may be over-complicating my design pattern.

Allow me to rename your "component" as "widget" at first. In my opinion "component" is better for things more general than the widgets in your case.
I would arrange those widgets as following:
app.js
/app
/dashboard
/show
show_controller.js
dashboard_app.js
/other
/components
/widgets
/widgetA
widgetController.js
widgetView.js
/widgetB
Since widgetA is dependent from dashboard and is supposed to be use elsewhere, it should be fully decoupled from dashboard.
The 'show' View of Dashboard should have a Layout to define where to show widgetA and widgetB.
Then, in your DashBoardApp, set an appRoute to respond to some route, and then call the controller.
The controller will initialize the Layout.
The Layout will ask for show of RegionA and RegionB.
Listen to 'show' events of RegionA and RegionB, to ask for an App wide request
this.listenTo(regionA, 'show', function(){
App.request 'widget:a:show'
});
Then in Widget module, respond to the App events and deliver the view
App.reqres.setHandler('widget:a:show, function(){
API.getWidgetAView();
});
The later part of my answer is a bit vague with less code. The basic idea is, DashBoardApp should finish his job by sending App request. And then it's components job to deliver the view upon App request, which is fully decoupled.

Related

Getting initial state using HTML5 history api

Everything I've been searching for is just a tutorial how to use pushState, replaceState, history.state, etc. Those concepts are simple but one thing I'm wondering how people solve is how to know what the initial state is.
Say you SPA is hosted at https://example.com/en-us/myapp/. Go there and your home page of the app is loaded, click around and it does a pushState to see you to https://example.com/en-us/myapp/get/users. Great, now you see a list of users and thanks to the history api, it wasn't an actual page load.
But now let's pretend a user had that https://example.com/en-us/myapp/get/users state bookmarked and the started the app off at this URL. Ok, so your server listens to that and serves up the app. My question is, how do you know that get/users is the current state and you need to show the associated view? Do you just know that your app is hosted at https://example.com/en-us/myapp/ and so you get whatever is after that to know?
Something like this:
function getState (uri) {
return uri.match(/^https:\/{2}(?:w{3}\.)?example.com\/en-us\/myapp\/?(.*)/i)[1];
}
var state = getState(location.href);
and if state is falsey then load the initial view, otherwise handle the state and show the list of users when state === 'get/users'?
Yes, that is quite right. However, you could try using location.pathname to fetch the state, so that your regex does not need to include the domain name.
For example:
function getState (uri){
var path = uri.split("myapp", 2)[1]; // This will split the pathname after 'myapp'
console.log(path) // Just for debugging purposes
// Now we can decide what to do with the path (i.e. "/get/users")
// For example, we can use a switch or a simple if statement
if (path === '/get/users'){
return true
} else {
return false
}
}
var state = getState(location.pathname);
That is just a simple example of a router. You can now try building your very own router for your SPA. Also, there are many libraries out there for you to use if you would like a different approach. You can take a look at these ones if you would like.
navigo
router.js
Also, if you are using a framework to build your SPA, they often have their own routing ability built in. These are just some of the many frameworks that have routers built in. (Sorry, I've <10 reputation so I'm not allowed more than two links).
Vue.js — vuejs.org/v2/guide/routing.html
Mithril.js — mithril.js.org/#routing
Ember.js — guides.emberjs.com/v2.13.0/routing/
Of course, it is ultimately your choice which to use. You could expand upon the example I've provided, by simply implementing a switch for different links/pages in your SPA. I wish you the best with your app!

Angular JS Service Architecture

EDIT
The short version:
Say I have application data is many different services. How do I get around needing to inject all of those services into every controller that displays application state?
EDIT
I am building my first Angular application. The basic design is I have a home page that shows the value of about 5 different variables (which are each pretty complicated). While on this page the app is collecting and analyzing data from bluetooth. Occasionally, the these 5 variables and some bluetooth data are saved to a REST back end and also saved to the device. There are pages for each of these 5 variables to change their value.
I have done my best to follow best practices. I have very thin controllers. I use services for all my data. I really only use $scope for binding data between views and controllers.
My issue now is that I started with a global "State" service to keep track of those 5 variables. I inject into any controller that needs to display state, and bind the html to it. Any time I want to change any state, I call a method of that State service to do it. This worked well, but now that State service is getting huge.
I have tried to break functions out to other services, but I run into the issue of needing to read data from the State service, then writing back to other properties of the State service. If I inject the other service into State, I can't inject State into the other service too.
I have thought about how I could have many smaller services, but I keep coming back to when I save the data to the server. When I do that I need to gather up data from every corner of the application to send up. If all this information is stored in different services, I am left with injecting all of them into a single service once again.
As I write this, I am pretty sure I am missing a big concept with using $scope across an application.
Any pointers would be appreciated,
Thanks,
Scott
Could you divide things into sub-services, and then make the State service an aggregator for these sub-services, then instead of injecting State into the sub-services, you inject the specific sub-service that you need? E.g.:
var app = angular.module('services', []);
app.service('sub1', function(){
return {
// ...
}
});
app.service('sub2', function(sub1){
var data = sub1.getData();
data.prop = 'new_value';
sub1.setData(data);
return {
// ...
}
});
app.service('State', function(sub1, sub2){
var data = sub1.getData();
data.prop = 'new_value';
sub1.setData(data);
var data = sub2.getData();
data.prop = 'new_value';
sub2.setData(data);
return {
// ...
}
});
Looks like you need Redux to help you manage your application state
https://github.com/wbuchwalter/ng-redux

What is the most straight-forward way to use Ember.js for just one part of a Rails app?

I'd like to develop a Rails app with a full admin suite, authentication, authorization etc. that is pure Rails, but I would like there to be one part of the app that is 'real-time' and where the view is bound to the data, and I'd like to use Ember for this.
For example, I have a Company model, which has_many Parties, and Party has_many Guests. I would like a User to be able to log in, navigate to a Company page that they are authorized to access, and click a link to Company/:id/Party/:id/live, which would be a page featuring an Ember.js app that offers details of all of the Guests in the party. It would query the database every X seconds, and the Ember.js app would automatically reflect the updated data (e.g. one section of the page only shows Guests where Guest.status == in_attendance or something like this).
So my question is: Is there any standardized or proven way of incorporating Ember into just a portion of a Rails app in this manner?
Thanks!
To embed Ember application, you should do two things:
Change root element from body to something else
Disable URL management
Code samples:
App = Ember.Application.create({
rootElement: '#app'
});
App.Router = Ember.Router.extend({
location: 'none'
});
I took this information from Ember Guides: Embedding Applications

Syncronizing Session data with other angular directives and controllers

In my AngularJS application, I have a Session service object that contains stuff like the current user, their preferences, the current company they belong to, and the current theme that they are using. Many places in my application refer to the Session service when they need to get at this data.
Because these variables are in a service, I cannot use scope watches to detect changes. So instead, I've decided to use the observer pattern. Various places in the application, such as other services, directives, controllers, etc. will register themselves with the Session service and provide a callback to be executed whenever the Session changes.
For example, if the user changes their theme, the <style> element in index.html that uses a custom directive will be notified, and it will recreate all of the overriding css rules for the new colors.
For another example, whenever the user's avatar is updated, the main menu bar controller will be notified to refresh and redraw the avatar. Stuff like this.
Obviously the data in Session has to be refreshed at least once before the various controllers, directives, etc. use it. The natural place to ask the Session service to get its session data was in a run block for the application-level module. This works pretty well, but I don't think it's the best place either.
One problem I have noticed is that when Firebug is open, the asynchronous nature of things loading causes ordering issues. For example, the directive that runs on the <style> element will run AFTER the Session service has refreshed in the application's run block... which means the theme will not get updated after pressing F5 because the callback is registered after the initialization of the data occured. I would have to call a manual refresh here to keep it in sync, but if I did that, it may execute twice in the times where the order is different! This is a big problem. I don't think this issue is just related to Firebug... it could happen under any circumstance, but Firebug seems to cause it somewhat consistently, and this is bad.
To recap... This asynchronous ordering is good:
Theme Directive registers callback to Session
Menu Bar application controller registers callback to Session
Session.refresh() is called in .run block.
This asynchronous ordering is bad:
Menu Bar application controller registers callback to Session
Session.refresh() is called in .run block.
Theme Directive registers callback to Session, but callback does not get executed since Session.refresh() was already executed.
So rather than use the observer pattern, or refresh the Session state via a run block, what the best way to design the services, etc. so that the session data will ALWAYS get refreshed after (or maybe before) the various other parts of the application require it? Is there some kind of event I can hook into that gets executed before directives and controllers are executed instead of the run block?
If my approach is generally sound, what can I add to it to really make it work the way it should?
Thanks!
In angular.js you have 2 way of using global variables:
use a $rootScope
use a service
Using $rootScope is very easy as you can simply inject it into any controller and change values in this scope. All global variables have problems!
Services is a singletons(What you need)!
I think in your case you can use
$rootScope
And
$scope.$watch
Great answer
Is there a reason you can't access the variables directly like this:
app.factory('SessionService', function() {
var items = {
"avatar": "some url"
};
return items;
});
var MainController = [$scope, 'SessionService', function($scope, SessionService){
$scope.session = SessionService;
$scope.modifyAvatar = function(url){
$scope.session.avatar = "some new url";
};
}];
var HeaderController = [$scope, 'SessionService', function($scope, SessionService){
$scope.session = SessionService;
// You probably wouldn't do this, you would just bind
// to {{session.avatar}} in your template
$scope.getAvatar = function(){
return $scope.session.avatar;
};
}];

Multiple routers vs single router in BackboneJs

All examples on Backbone I've seen use one router for the whole application, but wouldn't it make sense to have a router for each single part of your app (header, footer, stage, sidebar)? Has anyone built apps with more than one router and what are your experiences?
Let's think about a complex app with nested views: Wouldn't it be better when a view has its own router that handles the display of the subviews, than having one big router that has to inform the main view to change its subviews?
The background of this question: I've see a lot of parallels of the router in backbone and the ActivityMapper in GWT. The ActivityMapper is only responsible to get the right presenter for a given route and a given container in the DOM.
i wrote an app (still writing) with multiple routers in it.
however it is not like you think, it is more module based and not a router per view or anything like that.
for example,
say i got two big modules in my app, 1 handling all books, and 1 for the users.
books have multiple views (as do users), a list view, detail view, edit view, etc etc...
so each module has its own router,
which stands for its own set of urls:
// user module...
var userRouter = Backbone.Router.extend({
routes: {
"users": "loadUsers",
"users/add": "addUser",
"user/:id": "loadUser",
"user/:id/edit": "editUser"
}
// ... rest dropped to save the trees (you never know if someone prints this out)
});
// book module
var bookRouter = Backbone.Router.extend({
routes: {
"books": "loadBooks",
"books/add": "addBook",
"book/:name": "loadBook",
"book/:name/edit": "editBook"
}
// ... rest dropped to save the trees (you never know if someone prints this out)
});
so, it is not like my two routers are competing for the same route, they each handle their own set of routes.
edit
now that I had more info via Elf Sternberg, I know it isn't possible by default to have multiple routers match on the same route. without a workaround like overriding the backbone history or using namespaces in routes and regexes to match these routes.
more info here: multiple matching routes
thanks Elf Sternberg for the link.
I just wrote a blog post on Module-Specific Subroutes in Backbone, which allow a "subroute" to be defined which pays attention to everything after the prefix for that route.
Check out the blog entry for more explanation: http://www.geekdave.com/?p=13
This means you don't have to redundantly define the same prefix over and over, and you can also lazy-load subroutes as modules are accessed. Feedback is most welcome!
There is a limited but important case when it makes sense to use multiple Routers. If you need to expose only a subset of your application's routes & views based on data collected at runtime (perhaps login credentials - e.g., manager vs. staff can see & navigate between different sets of views) you could instantiate only the appropriate Router & View classes. This is significant because routes can be bookmarked and sent from user to user. Of course, you still need checks on the server to ensure that an unauthorized user isn't issuing requests after navigating to a view they arrived at via a bookmark sent by an authorized user. But it's better to design the application so the unauthorized view is just not generated.

Categories

Resources