symphonycms/symphony-2

View on GitHub
symphony/assets/js/src/symphony.js

Summary

Maintainability
D
1 day
Test Coverage
/*!
 * Symphony CMS, https://www.getsymphony.com, MIT license
 */

/**
 * @package assets
 */

/**
 * The Symphony object provides language, message and context management.
 *
 * @class
 */
var Symphony = (function($, crossroads) {
    'use strict';

    // Internal Symphony storage
    var Storage = {
        Context: {},
        Dictionary: {},
        Support: {}
    };

/*-------------------------------------------------------------------------
    Functions
-------------------------------------------------------------------------*/

    // Replace variables in string
    function replaceVariables(string, inserts) {
        if($.type(string) === 'string' && $.type(inserts) === 'object') {
            $.each(inserts, function(index, value) {
                string = string.replace('{$' + index + '}', value);
            });
        }
        return string;
    }

    // Get localised strings
    function translate(strings) {
        var env = Symphony.Context.get('env');
        if (!env) {
            // no env, can't continue
            return;
        }
        var namespace = $.trim(env['page-namespace']),
            data = {
                'strings': strings
            };

        // Validate and set namespace
        if($.type(namespace) === 'string' && namespace !== '') {
            data.namespace = namespace;
        }

        // Request translations
        $.ajax({
            async: false,
            type: 'GET',
            url: Symphony.Context.get('symphony') + '/ajax/translate/',
            data: data,
            dataType: 'json',

            // Add localised strings
            success: function(result) {
                $.extend(true, Storage.Dictionary, result);
            },

            // Use English strings on error
            error: function() {
                $.extend(true, Storage.Dictionary, strings);
            }
        });
    }

    // request animation frame
    var raf = window.requestAnimationFrame || window.mozRequestAnimationFrame ||  
        window.webkitRequestAnimationFrame || window.msRequestAnimationFrame ||
        window.oRequestAnimationFrame || function (f) { return window.setTimeout(f, 1000/16); };
    var craf = window.cancelAnimationFrame || window.webkitCancelRequestAnimationFrame ||
        window.mozCancelRequestAnimationFrame || window.oCancelRequestAnimationFrame ||
        window.msCancelRequestAnimationFrame  || function (t) { window.clearTimeout(t); };
    

/*-----------------------------------------------------------------------*/

    // Set browser support information
    try {
        Storage.Support.localStorage = !!localStorage.getItem;
    } catch(e) {
        Storage.Support.localStorage = false;
    }

    // Deep copy jQuery.support
    $.extend(true, Storage.Support, $.support);

    // Turn off migrate trace
    jQuery.migrateTrace = undefined;

/*-------------------------------------------------------------------------
    Symphony API
-------------------------------------------------------------------------*/

    return {

        /**
         * Symphony backend view using Crossroads
         *
         * @since Symphony 2.4
         */
        View: {

            /**
             * Add function to view
             *
             * @param {String} pattern
             *  Expression to match the view, using the Symphony URL as base
             * @param {Function} handler
             *  Function that should be applied to a view
             * @param {Integer} priority
             *  Priority of the function
             * @param {Boolean} greedy
             *  If set to `false`, only executes the first matched view, defaults to `true`
             * @return {Route}
             *  Returns a route object
             */
            add: function addRoute(pattern, handler, priority, greedy) {
                var route;

                pattern = Symphony.Context.get('path') + pattern;
                route = crossroads.addRoute(pattern, handler, priority);

                if(greedy !== false) {
                    route.greedy = true;
                }

                return route;
            },

            /**
             * Render given URL, defaults to the current backend URL
             *
             * @param {String} url
             *  The URL of the view that should be rendered, optional
             * @param {Boolean} greedy
             *  Determines, if only the first or all matching views are rendered,
             *  defaults to `true (all)
             */
            render: function renderRoute(url, greedy) {

                if(!url) {

                    url = Symphony.Context.get('path') + Symphony.Context.get('route');
                }

                if(greedy === false) {
                    crossroads.greedyEnabled = false;
                }

                crossroads.parse(url);
            }

        },

        /**
         * Storage for the main Symphony elements`.
         * Symphony automatically adds all main UI areas.
         *
         * @since Symphony 2.4
         */
        Elements: {
            window: null,
            html: null,
            body: null,
            wrapper: null,
            header: null,
            nav: null,
            session: null,
            context: null,
            contents: null
        },

        /**
         * The Context object contains general information about the system,
         * the backend, the current user. It includes an add and a get function.
         * This is a private object and can only be accessed via add and get.
         *
         * @class
         */
        Context: {

            /**
             * Add data to the Context object
             *
             * @param {String} group
             *  Name of the data group
             * @param {String|Object} values
             *  Object or string to be stored
             */
            add: function addContext(group, values) {

                // Add multiple groups
                if(!group && $.type(values) === 'object') {
                    $.extend(Storage.Context, values);
                }

                // Add to existing group
                else if(Storage.Context[group] && $.type(values) !== 'string') {
                    $.extend(Storage.Context[group], values);
                }

                // Add new group
                else {
                    Storage.Context[group] = values;
                }

                // Always return
                return true;
            },

            /**
             * Get data from the Context object
             *
             * @param {String} group
             *  Name of the group to be returned
             */
            get: function getContext(group) {

                // Return full context, if no group is set
                if(!group) {
                    return Storage.Context;
                }

                // Return false if group does not exist in Storage
                if(typeof Storage.Context[group] === 'undefined') {
                    return false;
                }

                // Default: Return context group
                return Storage.Context[group];
            }
        },

        /**
         * The Language object stores the dictionary with all needed translations.
         * It offers public functions to add strings and get their translation and
         * it offers private functions to handle variables and get the translations via
         * an synchronous AJAX request.
         * Since Symphony 2.3, it is also possible to define different translations
         * for the same string, by using page namespaces.
         * This is a private object
         *
         * @class
         */
        Language: {

            /**
             * Add strings to the Dictionary
             *
             * @param {Object} strings
             *  Object with English string as key, value should be false
             */
            add: function addStrings(strings) {

                // English system
                if(Symphony.Context.get('lang') === 'en') {
                    $.extend(true, Storage.Dictionary, strings);
                }

                // Localised system
                else {

                    // Check if strings have already been translated
                    $.each(strings, function checkStrings(index, key) {
                        if(key in Storage.Dictionary) {
                            delete strings[key];
                        }
                    });

                    // Translate strings
                    if(!$.isEmptyObject(strings)) {
                        translate(strings);
                    }
                }
            },

            /**
             * Get translated string from the Dictionary.
             * The function replaces variables like {$name} with the a specified value if
             * an object of inserts is passed in the function call.
             *
             * @param {String} string
             *  English string to be translated
             * @param {Object} inserts
             *  Object with variable name and value pairs
             * @return {String}
             *  Returns the translated string
             */
            get: function getString(string, inserts) {
                var translation = Storage.Dictionary[string];

                // Validate and set translation
                if($.type(translation) === 'string') {
                    string = translation;
                }

                // Insert variables
                string = replaceVariables(string, inserts);

                // Return translated string
                return string;
            }
        },

        /**
         * A collection of properties that represent the presence of
         * different browser features and also contains the test results
         * from jQuery.support.
         *
         * @class
         */
        Support: Storage.Support,

        /**
         * A namespace for core interface components
         *
         * @since Symphony 2.6
         */
        Interface: {},

        /**
         * A namespace for extension to store global functions
         *
         * @since Symphony 2.3
         */
        Extensions: {},

        /**
         * Helper functions
         *
         * @since Symphony 2.4
         */
        Utilities: {

            /**
             * Get a jQuery object of all elements within the current viewport
             *
             * @since Symphony 2.4
             */
            inSight: function inSight(elements) {
                var windowHeight = window.innerHeight,
                    visibles = $();

                elements.each(function() {
                    var context = this.getBoundingClientRect();

                    if(
                        (context.top >= 0 && context.top <= windowHeight) || // Element top in sight
                        (context.bottom >= 0 && context.bottom <= windowHeight) || // Element bottom in sight
                        (context.top <= 0 && context.bottom >= windowHeight) // Element overflowing viewport
                    ) {
                        visibles = visibles.add(this);
                    }
                    else if (visibles.length > 0) {
                        return false;
                    }
                });

                return visibles;
            },

            /**
             * Returns the XSRF token for the backend
             *
             * @since Symphony 2.4
             * @param boolean $serialised
             *  If passed as true, this function will return the string as a serialised
             *  form elements, ie. field=value. If omitted, or false, this function
             *  will just return the XSRF token.
             */
            getXSRF: function(serialised) {
                var xsrf = Symphony.Elements.contents.find('input[name=xsrf]').val();

                if(serialised === true) {
                    return 'xsrf=' + encodeURIComponent(xsrf);
                }
                else {
                    return xsrf;
                }
            },

            /**
             * Cross browser wrapper around requestFrameAnimation
             *
             * @since Symphony 2.5
             * @param function $func
             *  The callback to schedule for frame animation
             */
            requestAnimationFrame: function (func) {
                return raf.call(window, func);
            },

            /**
             * Cross browser wrapper around cancelAnimationFrame
             *
             * @since Symphony 2.5
             * @param Integer $t
             *  The request id
             */
            cancelAnimationFrame: function (t) {
                return craf.call(window, t);
            }
        }
    };
}(window.jQuery, window.crossroads));