symphonycms/symphony-2

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

Summary

Maintainability
C
1 day
Test Coverage
/**
 * @package assets
 */

(function($, Symphony) {
    'use strict';

    /**
     * Notify combines multiple system messages to an interface that focusses
     * on a single message at a time and offers a navigation to move between message.
     *
     * @name $.symphonyNotify
     * @class
     *
     * @param {Object} options An object specifying containing the attributes specified below
     * @param {String} [options.items='p.notice'] Selector to find messages
     * @param {String} [options.storage='symphony.notify.root'] Namespace used for local storage
     *
     * @example

            $('#messages').symphonyNotify();
     */
    $.fn.symphonyNotify = function(options) {
        var objects = this,
            settings = {
                items: 'p.notice',
                storage: 'symphony.notify.' + Symphony.Context.get('root').replace('http://', '')
            };

        $.extend(settings, options);

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

        Symphony.Language.add({
            'Ignore?': false,
            'next': false,
            'at': false
        });

    /*-------------------------------------------------------------------------
        Events
    -------------------------------------------------------------------------*/

        // Attach message
        objects.on('attach.notify', function attachMessage(event, message, type) {
            var object = $(this),
                notifier = object.find('div.notifier'),
                items = notifier.find(settings.items),
                item, storage;

            notifier.trigger('attachstart.notify');

            // Create item
            item = $('<p />', {
                'class': type
            }).html(message.replace(Symphony.Language.get('at') + ' ', '')).addClass('notice active').symphonyTimeAgo();

            // Add ignore link to notices)
            if(!item.is('.error') && !item.is('.success') && !item.is('.protected')) {
                item.html(item.html() + ' <a class="ignore">' + Symphony.Language.get('Ignore?') + '</a>');
            }

            // Add navigator
            $('<nav />', {
                text: Symphony.Language.get('next')
            }).hide().appendTo(item);

            // Load exclusion rules
            if(Symphony.Support.localStorage === true) {
                storage = (window.localStorage[settings.storage]) ? $.parseJSON(window.localStorage[settings.storage]) : [];
            }

            // Dimmed success transition
            if (!!type && !!~type.indexOf('success')) {
                setTimeout(function () {
                    item.addClass('dimmed');
                }, 10000);
            }

            // Prepend item
            if($.inArray(item.text(), storage) == -1) {
                items.removeClass('active');
                item.addClass('active').prependTo(notifier);
                notifier.scrollTop(0);

                notifier.trigger('attachstop.notify', [item]);
            }
            else {
                notifier.trigger('attachcancel.notify', [item]);
            }
        });

        // Detach message
        objects.on('detach.notify', settings.items, function detachMessage() {
            var item = $(this),
                notifier = item.parents('div.notifier');

            notifier.trigger('detachstart.notify', [item]);

            // Prepare item removal
            notifier.one('movestop.notify', function() {
                var notifier = $(this),
                    offset = notifier.scrollTop();

                // Adjust offset
                if(offset > 0) {
                    notifier.scrollTop(offset - item.outerHeight());
                }

                // Remove item
                item.remove();

                notifier.trigger('detachstop.notify', [item]);
            });

            // Fade item
            item.animate({
                opacity: 0
            }, 'normal', function removeItem() {

                // No other items
                if(item.siblings().length == 0) {
                    notifier.trigger('resize.notify', [$('<div />')]);
                }

                // More item
                else {
                    notifier.trigger('move.notify');
                }

                // Remove item
                item.remove();
                notifier.trigger('detachstop.notify', [item]);
            });
        });

        // Resize notifier
        objects.on('resize.notify attachstop.notify', 'div.notifier', function resizeNotifer(event, item) {
            var notifier = $(this);

            // Adjust height
            if(!notifier.hasClass('constructing')) {
                var active = item || notifier.find('.active:not(:animated)');

                notifier.show().animate({
                    height: active.innerHeight() || 0
                }, 100);
            }
        });

        // Count messages
        objects.on('attachstop.notify detachstop.notify', 'div.notifier', function toggleNavigator() {
            var notifier = $(this),
                items = notifier.find(settings.items);

            // Hide navigator
            if(items.length == 1) {
                items.find('nav').hide();
            }

            // Show navigator
            else {
                items.find('nav').show();
            }
        });

        // Next message
        objects.on('click', 'nav', function switchMessage() {
            var notifier = $(this).closest('div.notifier');

            // Move messages
            notifier.trigger('move.notify');
        });

        // Move messages
        objects.on('move.notify', 'div.notifier', function moveMessage() {
            var notifier = $(this),
                current = notifier.find('.active'),
                next = current.next(settings.items),
                from = current.outerHeight(),
                offset;

            notifier.trigger('movestart.notify');

            // Deactivate current message
            current.removeClass('active');

            // Activate next message and get offset
            if(next.length > 0) {
                next.addClass('active');
                offset = notifier.scrollTop() + from;
            }
            else {
                next = notifier.find(settings.items).first().addClass('active');
                offset = 0;
            }

            // If next's height is not the same, resize first
            if(next.outerHeight() !== from) {
                notifier.trigger('resize.notify');
            }

            // Move to next message
            notifier.animate({
                scrollTop: offset
            }, 'fast', function stopMovingMessage() {
                notifier.trigger('movestop.notify');
            });
        });

        // Ignore message
        objects.on('click', 'a.ignore', function ignoreMessage() {
            var ignore = $(this),
                item = ignore.parents(settings.items),
                text = item.text(),
                storage;

            // Store exclusion rule
            if(Symphony.Support.localStorage === true) {
                // Put in a try/catch incase we exceed storage space
                try {
                    storage = (window.localStorage[settings.storage]) ? $.parseJSON(window.localStorage[settings.storage]) : [];
                    storage.push(text);
                    window.localStorage[settings.storage] = JSON.stringify(storage);
                }
                catch(e) {
                    window.onerror(e.message);
                }
            }

            // Remove item
            item.trigger('detach.notify');
        });

    /*-------------------------------------------------------------------------
        Initialisation
    -------------------------------------------------------------------------*/

        // Build interface
        objects.each(function initNotify() {
            var object = $(this),
                notifier = $('<div class="notifier" />').hide().prependTo(object),
                items = $(object.find(settings.items).get().reverse());

            // Construct notifier
            notifier.addClass('constructing');
            notifier.height(items.last().innerHeight());
            items.each(function buildMessages() {
                var item = $(this).remove(),
                    message = item.html(),
                    type = item.attr('class');

                object.trigger('attach.notify', [message, type]);
            });

            // Resize notifier
            if(notifier.find(settings.items).length > 0) {
                notifier.removeClass('constructing').trigger('resize.notify');
            }

            notifier.removeClass('constructing');

            // Update relative times in system messages
            setInterval(function updateRelativeTimes() {
                $('header p.notice').symphonyTimeAgo();
            }, 60000);
        });

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

        return objects;
    };

})(window.jQuery, window.Symphony);