klee-contrib / focus-starter-kit Goto Github PK
View Code? Open in Web Editor NEWApplication starter kit using focus.
License: MIT License
Application starter kit using focus.
License: MIT License
When a major change happens in some key objects in Vertigo and Focus, updating one and not the other might result in the application not working and some very tedious debugging to find the origin of the problem.
I suggest some sort of compatibility list in Focus that would call a Vertigo-embedded webservice to check Vertigo version number and see if it matches.
Maintaining the list would be a hassle, but it would be a nice feature.
I'm wondering if the models
directory should be at the root of the application.
When developping a page, it's a bit boring to switch between the page-specific directory in the views
directory and the models
directory.
If we consider that :
Wouldn't it be pragmatic to have a specific models
directory in each page directory, like we do for templates
?
- views
- administration
- utilisateur
- templates
+ utilisateurDetailConsult.hbs
+ utilisateurDetailEdit.hbs
- models
+ utilisateurDetail.js
+ utilisateurDetail.js
If shared models are needed (which is the exception), we can add a models directory at the right level (for instance, in a module directory to share it across all pages of the same module).
In the file initializer/definition-initializer the entityDefinition is loaded with :
Focus.definition.entity.container.setEntityConfiguration(require('../config/entity-definition'));
//Display domaines utilisés
let entityDef = require('../config/entity-definition');
Instead of :
Focus.definition.entity.container.setEntityConfiguration(require('../config/entityDefinition'));
//Display domaines utilisés
let entityDef = require('../config/entityDefinition');
The path used is not correct, so the definition of entities is not loaded. This file set correctly on the focus-demo but not on the starter-kit.
It could be useful to have a "NotImplementedView" in the starter kit.
This way, we can init all the route callbacks of the navigation links (menu, etc.), which allows to navigate in the application smoothly without navigation inconsistency (active menu not matching current page) or javascript error.
var Router = Fmk.Helpers.Router;
var layout = require('../views/layout');
var NotImplementedStatic = require('../views/technique/notImplemented/notImplementedStatic');
module.exports = Router.extend({
routes: {
'affaire': 'affaireRecherche'
},
affaireRecherche: function affaireRechercheRoute() {
layout.setActiveMenu('affaire');
layout.content.show(new NotImplementedStatic());
}
It could be useful to have a default view for unmapped route, using a default route.
The idea is to have an error page for not mapped routes. This way, the application informs the user that he clicked a dead link, instead of just silently updating the hash in the browser.
Default router :
var layout = require('../views/layout');
module.exports = Fmk.Helpers.Router.extend({
routes: {
'*path': 'defaultRoute'
},
defaultRoute: function () {
var View = require('../views/technique/routeNotFound/routeNotFoundStatic');
var model = new Backbone.Model({route: Backbone.history.fragment});
var view = new View({model: model});
layout.content.show(view);
}
});
We use the *splat
joker to select all unmapped routes.
Beware, if there is several routers, the default router must be the first one to be initialized (and not the last one as one might think).
//Dependencies.
var DefaultRouter = require('./defaultRouter');
var HomeRouter = require("./homeRouter");
var AffaireRouter = require('./affaireRouter');
/*Router instanciation.*/
module.exports = {
routerList: [
new DefaultRouter(),
new HomeRouter(),
new AffaireRouter()
]
};
The view simply displayed the unmapped route :
<div class="techError">
<h4><i class="fa fa-exclamation-triangle"></i> Route inexistante : {{route}}</h4>
</div>
To prevent silent javascript errors, we could add a global error manager that display the javascript error directly in the DOM.
During developement, it seems essential that developpers have the most direct feedback possible when unmanaged javascript errors occur.
I added the handler directly in the html page that contains the SPA, using only native javascript.
This way, even application initialization code errors will be catched and we don't depend on any library.
This behaviour is configurable via a server parameter if we want to disable it on production environments.
Screenshot :
In the HTML page head tag:
<head>
<!-- ... -->
<title>Travaux 0.1.0</title>
@if (ViewBag.DisplayJavascriptError) {
<link rel="stylesheet" href="@Url.Content("~/Content/container.css")">
<script src="@Url.Content("~/Scripts/container.init.js")"></script>
}
<link rel="stylesheet" href="@Url.Content("~/spa/stylesheets/app.css")">
<script src="@Url.Content("~/spa/javascripts/vendor.js")"></script>
<script src="@Url.Content("~/spa/javascripts/app.js")"></script>
<!-- ... -->
</head>
container.init.js (the error is displayed in a div added at the begining of the body) :
(function () {
// Gestion globale des erreurs non traitée par la SPA.
window.onerror = function (event, source, line, column, error) {
// Construction d'un rapport d'erreur.
var report = '';
report += '<h1>Erreur javascript non gérée</h1>';
report += '<hr/>';
report += '<h2>' + event + '</h2>';
if (error && error.stack) {
var stack = error.stack;
report += '<p><strong>Stacktrace : </strong><pre>' + stack + '</pre></p>';
}
report += '<p><strong>Source : </strong><code>' + source + '</code> <strong>Ligne : </strong><code>' + line + '</code></p>';
// Affichage au début du body.
var debug = window.document.getElementById('debug');
if (!debug) {
debug = window.document.createElement('div');
debug.id = 'debug';
var body = window.document.body;
body.insertBefore(debug, body.firstChild);
}
debug.innerHTML = report;
};
})();
container.css :
#debug {
background-color: #fbfbfb;
border: 2px solid #000;
padding: 10px;
margin: 5px;
}
#debug hr {
margin: 0;
}
#debug > h1 {
color: #F00;
font-size: 20px;
margin-top: 5px;
}
#debug > h2 {
color: #800000;
font-style: italic;
font-size: 20px;
margin-top: 10px;
}
#debug pre {
background-color: #FFFFCC;
font-family: "Consolas","Lucida Console",Monospace;
}
(Following discussion from klee-contrib/focus-core#58)
On our project, we added our own extensions of Focus views.
An extension matches with a projet-level defined page pattern like Composite detail with global load and save.
This allows :
This strategy could be encouraged on the other Focus projets for the previous reasons.
We store the views extensions at the application level :
Backbone mediator has been use on two project. It could be a good idea to add it as a dependency of the skeleton.
On CI-T, I added log4javascript for client logging :
It uses log4j-like configuration and API with named loggers, appenders, level, layout, etc.
Available appenders include : console appender, DOM appender, popup appender, Server API appender. Adding new appender (for instance a localStorage appender) seems uncomplicated.
Currently, Focus is logging using a hard-coded reference to a generic unnamed hand-made unconfigurable logger, or uses directly the console object.
It would be nice :
Here is how it's done on CI-T :
In the application root, I added a loggers directory :
Each logger is declared in its own module as a singleton :
var log = log4javascript.getLogger("Spa.Application");
module.exports = log;
The main index.js script sets up the configuration of the loggers.
/* Chargement des singletons de logger */
var logSpaApplication = require('./Spa.Application');
var logSpaView = require('./Spa.View');
/* Définition des appenders */
var consoleAppender = new log4javascript.BrowserConsoleAppender();
consoleAppender.setLayout(new log4javascript.PatternLayout("%d{HH:mm:ss,SSS} %c %p %m"));
/* Configuration des loggers */
logSpaApplication.addAppender(consoleAppender);
logSpaView.addAppender(consoleAppender);
In the application intialize script, the first thing to do is to call the loggers main script :
var initialize = {
init: function init(params) {
return $(function () {
/* Init loggers. */
require('./loggers/index');
/* ... */
/* Initialize application. */
application.initialize();
});
}
};
module.exports = initialize;
var log = require('./loggers/Spa.Application');
log.info("Démarrage SPA...");
Since the upgrde to focus-core 0.11.0 and focus-components 0.7.0, package.json has been deeply changed, and jquery removed. But it is still in the script/install.js
In the skeleton application, when starting the SPA, there is an HTTP error in the browser :
GET http://localhost:10003/spa/stylesheets/bootstrap.css.map 404 (Not Found)
I needed to add the file manually to the expected folder to avoid the warning.
The file 'user-initializer.js' is not loaded at startup. This behavior prevents the user to have the default role. An 'application.noRights' notification is then displayed.
Please add the folowing line in 'app/initializer/index.js'
require('./user-initializer');
To inject automatic log in the SPA, we can use AOP.
A POC is documented on this wiki page.
For instance, it can automatically log calls to initialize and render methods of Backbone.View.
08:14:35:531 Spa.Application DEBUG initialize views/message/messageRecherche/messageResults 0
08:14:35:539 Spa.Application DEBUG initialize views/message/messageRecherche/messageCriteria 10
08:14:35:555 Spa.Application DEBUG render views/message/messageRecherche/messageResults 1
08:14:35:557 Spa.Application DEBUG render views/message/messageRecherche/messageCriteria 14
08:14:35:565 Spa.Application DEBUG render views/message/messageRecherche/messageResults 1
08:14:35:566 Spa.Application DEBUG render views/message/messageRecherche/messageCriteria 5
08:14:35:578 Spa.Application DEBUG render views/message/messageRecherche/messageResults 4
Some application need to manage different color themes for different menu items.
Each group of menus is assigned a color theme.
For instance, a green theme color and a blue theme color :
Basically, the requirement is to set a CSS color class on the menu items and on the content container, according to a theme color configuration.
On my Travaux project, we implemented this behaviour from outside Focus.
ThemeManager
object centralizes the theme logic
(function () {
/**
* Manager de thème.
* Un thème représente un jeu de couleurs spécifique pour le rendu graphique.
* Le manager permet d'associer un thème à chaque menu de l'application (de niveau 1).
* Un thème est associé à une classe CSS, qui est appliquée :
* - à l'item du menu de niveau 1 dans le bandeau,
* - aux menus de niveau supérieur ou égale à 2,
* - à la région de contenu principal du layout de l'application.
*/
var themeManager = _.extend({}, Backbone.Events, {
/**
* Référence du layout de l'application.
*/
layout: undefined,
/**
* Thème courant de l'application.
*/
currentThemeName: undefined,
/**
* Thème par défaut.
*/
defaultThemeName: undefined,
/**
* Map nom de menu => nom de thème.
*/
menuMap: {},
/**
* Map nom de thème => classe CSS du contenu.
*/
cssMap: {},
/**
* Initialise le manager de thème.
* @param config {{defaultThemeName:string, menuMap:{object}, cssMap:{object}}} Configuration des thèmes.
*/
init: function (config) {
/* Met à jour le manager avec la configuration. */
_.extend(this, config);
},
/**
* Démarre l'écoute.
*/
start: function () {
/* Stocke une référence du layout. */
this.layout = require('../views/layout');
/* Set le thème par défaut. */
this.setCurrentTheme(this.defaultThemeName);
/* Se met à l'écoute des changements de menus. */
this.listenTo(this.layout, 'menuChange', this.menuChangeHandler);
},
/**
* Handler du changement de menu.
* @param event {{menuName:string}} Evénement avec le nom du menu.
*/
menuChangeHandler: function (event) {
var menuName = event.menuName;
var newThemeName = this.getThemeNameByMenuName(menuName);
this.setCurrentTheme(newThemeName);
},
/**
* Obtient le nom de la class CSS du thème pour un menu donné.
* @param menuName {string} Nom du menu.
* @returns {string} Nom de la classe CSS du thème du menu.
*/
getCssClassByMenuName: function (menuName) {
return this.cssMap[this.getThemeNameByMenuName(menuName)];
},
/**
* Obtient le nom du thème pour un nom de menu donné.
* @param menuName {string} Nom du menu.
* @returns {string} Nom du thème.
*/
getThemeNameByMenuName: function (menuName) {
if (!menuName) {
return this.defaultThemeName;
}
/* Extrait le nom du menu de niveau 1 du nom du menu. */
var menu1Name = menuName.split('.')[0];
/* Retourne le nom du thème ou le thème par défaut. */
return this.menuMap[menu1Name] || this.defaultThemeName;
},
/**
* Modifie le thème courant.
* @param newThemeName {string} Nom du nouveau thème.
*/
setCurrentTheme: function (newThemeName) {
/* Prépare les classes CSS. */
var oldCssClass = this.cssMap[this.currentThemeName];
var newCssClass = this.cssMap[newThemeName];
/* Modifie le CSS de la région de contenu principal. */
var contentRegion = $(this.layout.content.el);
contentRegion.removeClass(oldCssClass);
contentRegion.addClass(newCssClass);
/* Met à jour le thème courant. */
this.currentThemeName = newThemeName;
}
});
module.exports = themeManager;
})();
The manager must be initialized before the handlebars helper registration and before the layout creation.
/* Initialise les thèmes de couleur par module applicatif. */
var themeManager = require('../../lib/themeManager');
themeManager.init({
/**
* Thème par défaut.
*/
defaultThemeName: "orange",
/**
* Map nom du menu niveau 1 => nom du thème.
*/
menuMap: {
"affaire": "orange",
"devis": "orange",
"facture": "orange",
"partenaire": "vert",
"codeTravaux": "vert",
"reporting": "bleu",
"administration": "bleu"
},
/**
* Map nom du thème => nom de la classe CSS.
*/
cssMap: {
"orange": "orangeTheme",
"vert": "vertTheme",
"bleu": "bleuTheme"
}
});
The manager must be started after the layout creation :
this.addInitializer(
function (options) {
this.layout = require('./views/layout');
this.layout.render();
this.layout.displayHeader();
var themeManager = require('./lib/themeManager');
themeManager.start();
});
/**
* Définit le menu actif.
* @param name {string} Nom du menu.
*/
setActiveMenu: function (name) {
headerView.processActive(name);
/* Lance un événement de changement de menu (écouté par le manager de thèmes). */
this.trigger('menuChange', {menuName: name});
}
themeManager = require '../../lib/themeManager'
# Rend la classe CSS correspondant au thème d'un menu.
# Utilisé dans les headers de menu.
Handlebars.registerHelper 'menu_css', (menuName)->
return themeManager.getCssClassByMenuName(menuName)
The handlebars helper is called on each menu item.
This implementation works outside Focus.
Possibly, a more elegant implementation could be done inside Focus.
For instance, the theme could be associated to menus directly into the site description tree.
In routers, we use a variable containing the application
that we use to access the layout
property:
var application = require('application');
userDetail: function (id) {
application.layout.setActiveMenu('administration.security.user');
var UserCompositeView = require('../views/administration/usrCompositeView');
application.layout.content.show(new UserCompositeView({ id: id }));
}
In fact, in the whole router, we only use the layout
property of application
.
Using a layout variable instead of an application variable can save lots of code :
var layout = require('application').layout;
userDetail: function (id) {
layout.setActiveMenu('administration.security.user');
var UserCompositeView = require('../views/administration/usrCompositeView');
layout.content.show(new UserCompositeView({ id: id }));
}
The layout could even be stored in his own singleton module :
module.exports = require('../application').layout;
And be called directly when needed :
var layout = require('views/layout');
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.