Giter VIP home page Giter VIP logo

focus-starter-kit's People

Contributors

bernardstanislas avatar ephrame avatar hartorn avatar johnnp avatar pierr avatar plepaisant avatar tomgallon avatar tommass avatar tsj- avatar

Stargazers

 avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

focus-starter-kit's Issues

Vertigo compatibility check

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.

Application structure : models directory

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 :

  • The model is mandatory for the view
  • Generally speaking, the model is used by a single view

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).

Wrong path for the entityDefinition

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.

Add a NotImplementedView

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());
    }

Add a default view for unmapped route

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>

Global error manager outside the SPA

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 :

capture

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>&nbsp;<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;
    }

Add Focus views extensions in the project

(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 :

  • to encapsule project-specific patterns implementation
  • to reduce code duplication in the views
  • to have project-specific well named views to extend
  • to gain robustness when a new Focus release is shipped with breaking changes

This strategy could be encouraged on the other Focus projets for the previous reasons.

We store the views extensions at the application level :

pagetemplates

Log implementation and configuration

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 :

  • to use log4javascript loggers instead,
  • to use an injection mecanism that would allow to configure the named loggers (appender / level) outside Focus in the application.

Here is how it's done on CI-T :

Setup

Logger directory

In the application root, I added a loggers directory :

  • app
    • loggers
      • index.js
      • Spa.Application.js
      • Spa.View.js

Declaration

Each logger is declared in its own module as a singleton :

var log = log4javascript.getLogger("Spa.Application");

module.exports = log;

Configuration

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);

Initialization

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;

Usage

var log = require('./loggers/Spa.Application');
log.info("Démarrage SPA...");

Result in Chrome console :
capture

Warning 404 on bootstrap.css.map

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.

Add DEFAULT_ROLE in initializer

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');

[Skeleton] Add log injection

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

[Features] Theme management

Requirement

Some application need to manage different color themes for different menu items.

Each group of menus is assigned a color theme.

  • The menu item uses a theme color
  • The pages in this menu use the theme color.

For instance, a green theme color and a blue theme color :
modulevert

modulebleu

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.

Implementation

On my Travaux project, we implemented this behaviour from outside Focus.

Components

  • A ThemeManager object centralizes the theme logic
    • The manager is initialized with the theme configuraiton during application initialization
  • A handlerbars helper generate the CSS color class for the menus
    • The helper delegates the logic to the manager.
  • The layout publishes an event when the menu is changed.
    • The manager listens to the layout event to change the content container CSS class accordingly.

ThemeManager

(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;
})();

Configuration

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();
            });

Layout

     /**
     * 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});
    }

Handlebars helper

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)

Menus

The handlebars helper is called on each menu item.

        <ul class="nav nav-pills">
            {{#each headerItems}}
                <li id='{{cssId}}' class="{{cssClass}} {{menu_css name}} {{#if isActive}}active{{/if}}" {{dataattributes}}>
                    <a href="{{route}}">{{t name prefix="header." suffix=".title"}}</a>
                </li>
            {{/each}}
        </ul>

Focus or not Focus ?

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.

Use a layout variable in routers

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');

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.