SpontaneousCMS/spontaneous

View on GitHub
application/js/top_bar.js

Summary

Maintainability
F
1 wk
Test Coverage
// console.log("Loading TopBar...")

Spontaneous.TopBar = (function($, S) {
    var dom = S.Dom, Ajax = S.Ajax;

    var disableParent = function(el) {
        el.hover(function() {
            $(this).parent().addClass('disabled');
        }, function() {
            $(this).parent().removeClass('disabled');
        });
        return el;
    };

    var slugComparator = function(a, b) {
        var at = a.slug, bt = b.slug;
        if (at > bt) { return 1; }
        if (at < bt) { return -1; }
        return 0;
    };

    var RootBrowser = new JS.Class(Spontaneous.PopoverView, {
        initialize: function(roots) {
            this.roots = roots;
        },
        width: function() { return 300; },
        title: function() { return 'Choose Root'; },
        view: function() {
            var self = this
            , w = dom.div('#navigation-page-browser.pop-root-browser')
            , list = dom.div('.pages')
            , roots = this.roots.roots;
            var click = function(page_id) {
                return function() { self.close(); S.Location.load_id(page_id); };
            };
            for (var key in roots) {
                if (roots.hasOwnProperty(key)) {
                    var r = dom.div('.page').text(key).click(click(roots[key]));
                    list.append(r);
                }
            }
            w.append(list);
            this.wrapper = w;
            return w;
        }
    });

    var PageBrowser = new JS.Class(Spontaneous.PopoverView, {
        initialize: function(origin, pages) {
            this.origin = origin;
            this.pages = pages;
        },
        width: function() { return 300; },
        title: function() { return 'Go to Page'; },
        view: function() {
            var self = this, w = dom.div('#navigation-page-browser.pop-root-browser')
            , list = dom.div('.pages')
            , frame = dom.div('.frame')
            , searchArea = dom.div('.search')
            , searchInput = dom.input({type: 'search', placeholder:'Search'});
            searchArea.append(searchInput);
            searchInput.bind('change keyup search', self.updateSearch.bind(self, searchInput));
            self.wrapper = w;
            self.list = list;
            frame.append(list);
            w.append(searchArea, frame);
            this.loadPages();
            return w;
        },
        updateSearch: function(input) {
            if (!this.pages) { return; }

            var self = this, boxes = {}, query = input.val(), search = new RegExp(query, 'i');
            // need to bubble up the event if the query is identical as this event
            // is also triggered on input blur when a page is clicked.
            if (query === this.query) {
                return true;
            }

            this.query = query;
            if (query === '') {
                self.listPages(this.pages);
                return;
            }
            $.each(self.pages, function(boxname, pages) {
                var box = [];
                for (var i = 0, ii = pages.length; i < ii; i++) {
                    var page = pages[i];
                    if (search.test(page.slug) || search.test(page.title)) {
                        box.push(page);
                    }
                }
                if (box.length > 0) {
                    boxes[boxname] = box;
                }
            });
            self.listPages(boxes, query);
        },
        clearList: function() {
            this.list.empty();
        },
        loadPages: function() {
            if (this.pages) {
                this.listPages(this.pages);
            } else {
                var path = ['/map', this.origin.id].join('/');
                S.Location.retrieve(path, this.pagesLoaded.bind(this));
            }
        },
        pagesLoaded: function(pages) {
            this.pages = pages.generation;
            this.listPages(pages.generation);
        },
        listPages: function(generation, filter) {
            var self = this, origin = self.origin, load_page = function(p) {
                return function() {
                    self.close();
                    S.Location.load_id(p.id);
                };
            };
            self.clearList();
            if (Object.keys(generation).length === 0) {
                var msg = filter ? 'No matches for ‘'+filter+'’' : 'No pages';
                self.list.append(dom.h3().text(msg));
                return;
            }
            $.each(generation, function(boxname, pages) {
                pages.sort(slugComparator);
                var box = dom.div('.box').append(dom.h4().text(boxname));
                for (var i = 0, ii = pages.length; i < ii; i++) {
                    var p = pages[i], page = dom.div('.page').text(p.slug).data('page', p);
                    if (origin && p.id === origin.id) {
                        page.addClass('current');
                    }
                    page.click(load_page(p));
                    box.append(page);
                }
                self.list.append(box);
            });
        }
    });

    var RootNode = function(page, roots) {
        this.page = page;
        this.roots = roots;
        this.id = page.id;
        this.url = page.url;
        this.title = this.title(); //page.title; //S.site_domain;
    };
    RootNode.prototype = {
        title: function() {
            var roots = this.roots.roots;

            for (var key in roots) {
                if (roots.hasOwnProperty(key)) {
                    if (roots[key] === this.id) { return key; }
                }
            }
            return S.site_domain;
        },
        element: function() {
            var self = this, li = dom.li('.root-node');
            var link = dom.a({'href': this.url}).text(this.title).data('page', this.page);
            if (Object.keys(this.roots.roots).length === 1) {
                li.addClass('singluar');
            } else {
                li.click(function(event) {
                    var browser = new RootBrowser(self.roots);
                    Spontaneous.Popover.open(event, browser);
                });
            }
            var loaded = S.Location.get('location')
            , active = (loaded.id == this.page.id) // active == true represents the state where the currently shown page is this node
            ;
            if (!active) {
                link.click(function() {
                    var page = $(this).data('page');
                    S.Location.load_id(page.id);
                    return false;
                });
            }
            disableParent(link);
            li.append(link);
            this.link = link;
            return li;
        },
        set_title: function(new_title) {
            this.link.text(new_title);
        }
    };

    var AncestorNode = function(page) {
        this.page = page;
        this.id = page.id;
        this.title = page.slug;
        this.path = page.path;
    };
    AncestorNode.prototype = {
        element: function() {
            var page = this.page, li = dom.li('.ancestor-node'), link = $('<a/>').data('page', page).click(function() {
                var page = $(this).data('page');
                S.Location.load_id(page.id);
                return false;
            }).text(this.title);

            disableParent(link);
            li.append(link);

            li.click(function(e) {
                var browser = new PageBrowser(page);
                Spontaneous.Popover.open(e, browser);
                e.preventDefault();
                return false;
            });

            return li;
        }
    };
    var CurrentNode = function(page) {
        var self = this;
        self.page = page;
        self.id = page.id;
        self.path = page.path;
        self.title = page.slug;
        self.pages = page.generation;
    };

    CurrentNode.prototype = {
        element: function() {
            var self = this
, li = dom.li('.current-node')
            , link = dom.a().text(self.title);
            li.click(function(event) {
                var browser = new PageBrowser(self.page, self.pages);
                Spontaneous.Popover.open(event, browser);
                return false;
            });
            li.append(link);
            this.title_element = link;
            return li;
        },
        set_title: function(new_title) {
            this.title_element.text(new_title);
        }
    };

    var ChildrenNode = function(origin) {
        this.origin = origin;
    };

    ChildrenNode.prototype = {
        element: function() {
            var self = this, li = dom.li('.children'), link = dom.a('.unselected'), children = this.children;
            this.li = li;
            this.link = link.text(this.status_text());
            li.append(link);
            li.click(function(event) {
                var browser = new PageBrowser(self.origin, self.children());
                Spontaneous.Popover.open(event, browser);
                return false;
            });
            return li;
        }.cache(),

        each: function(block) {
            var self = this, children = this.origin.children;
            for (var boxname in children) {
                if (children.hasOwnProperty(boxname)) {
                    for (var i = 0, box = children[boxname], ii = box.length; i < ii; ++i) {
                        block(box[i]);
                    }
                }
            }
        },
        children: function() {
            return this.origin.children;
        },

        status_text: function() {
            var children = this.origin.children, count = 0;
            for (var boxname in children) {
                if (children.hasOwnProperty(boxname)) { count += children[boxname].length; }
            }
            if (count === 0) {
                this.li.hide();
            } else {
                this.li.show();
            }
            return '('+(count)+' page'+(count === 1 ? '' : 's')+')';
        },
        update_status: function() {
            this.link.text(this.status_text());
            return this.link;
        },
        add_page: function(page, position) {
            var self = this
, container = page.container
, name = container.name()
            , children = self.origin.children
, box = children[name];

            if (!box) {
                box = [];
                self.origin.children[name] = box;
            }
            // make the new entry as much like the originals as possible
            var entry = {
                id: page.id(),
                children: 0,
                slug: page.slug(),
                title: page.title(),
                type_id: page.content.type_id
            };
            box.push(entry);
            self.update_status();
        },

        remove_page: function(page) {
            var self = this
, children = self.origin.children
, container = page.container.name()
            , page_id = page.id()
            , index = (function(children) {
                for (var boxname in children) {
                    if (children.hasOwnProperty(boxname)) {
                        var cc = children[boxname];
                        for (var i = 0, ii = cc.length; i < ii; i++) {
                            if (cc[i].id === page_id) { return i; }
                        }
                    }
                }
            }(children));
            children[container].splice(index, 1);
            self.update_status();
        }
    };

    var __lastEventID = -1;

    var PublishButton = new JS.Class({
        rapid_check: 2000,
        normal_check: 10000,
        initialize: function() {
            this.status = false;
            this.disabled = true;
            this.set_interval(this.normal_check);
            var update_status = this.update_status.bind(this);
            S.EventSource.addEventListener('publish_progress', function(event) {
                if (event.lastEventId !== __lastEventID) {
                    __lastEventID = event.lastEventId;
                    update_status($.parseJSON(event.data));
                }
            });
        },
        user_loaded: function(user) {
            if (user.can_publish()) {
                this.disabled = false;
                this.button().removeClass('disabled').velocity('fadeIn');
            }
        },
        update_status: function(status) {
            if (status === null || status === '') { return; }
            var state = status.state, progress = status.progress;
            // if (this.in_progress && (state == this.current_action && progress == this.current_progress)) { return; }
            this.current_action = state;
            this.current_progress = progress;
            if (state === 'complete' || state === 'error') {
                // if (this.in_progress) {
                    this.in_progress = false;
                    this.progress().stop();
                    // this.set_interval(this.normal_check);
                    this.set_label('Publish');
                    this.button().switchClass('progress', '');
                    this.current_action = this.current_progress = null;
                // }
                if (state === 'error') {
                    alert('There was a problem publishing: ' + progress);
                }
            } else {
                this.publishing_state();
                // don't turn off intermediate and replace it with an empty progress dial
                // by making sure our progress is > 1 before switching to progress view
                if ((progress === '*') || (progress < 1.0)) {
                    this.progress().indeterminate();
                } else {
                    this.progress().update(progress);
                }
            }
        },
        publishing_started: function() {
            this.publishing_state();
            this.progress().indeterminate();
        },
        publishing_state: function() {
            // this.set_interval(this.rapid_check);
            this.set_label('Publishing');
            var b = this.button();
            this.current_action = this.current_progress = null;
            if (!b.hasClass('progress')) { b.switchClass('', 'progress'); }
            this.in_progress = true;
        },
        set_label: function(label) {
            if (label !== this._label_text) {
                this._label_text = label;
                this._label.text(label);
            }
        },
        button: function() {
            if (!this._button) {
                this._progress_container = dom.span('#publish-progress');
                this._label = dom.span();
                this._button = dom.a('#open-publish.disabled').append(this._progress_container).append(this._label).hide();
                this.set_label('Publish');
                this._button.click(function() {
                    if (!this.disabled && !this.in_progress) {
                        S.Publishing.open_dialogue();
                    }
                }.bind(this));
            }
            return this._button;
        },
        progress: function() {
            if (!this._progress) {
                this._progress = Spontaneous.Progress('publish-progress', 15, {
                    spinner_fg_color: '#ccc',
                    progress_fg_color: '#fff'
                });
                this._progress.init();
            }
            return this._progress;
        },
        set_interval: function(milliseconds) {
            this.timer_interval = milliseconds;
        }
    });
    var LocationChildProxy = new JS.Class({
        initialize: function(child) {
            this.child = child;
        },
        id: function() { return this.child.id; },
        title: function() { return this.child.title; },
        slug: function() { return this.child.slug; }
    });

    var CMSNavigationView = new JS.Class({
        initialize: function(topBar) {
            this.topBar = topBar;
        },
        panel: function() {
            var self = this;
            if (!self.wrap) {
                self.wrap = dom.div('#cms-navigation-view');
                self.location = dom.ul('#navigation');
                self.mode_switch = dom.a('#switch-mode').
                    text(S.TopBar.opposite_mode(S.ContentArea.mode)).
                    click(function() {
                        S.TopBar.toggle_modes(self.previewModeDisabled);
                    });

                self.publish_button = new PublishButton();
                self.wrap.append(self.location);
                self.wrap.append(self.publish_button.button());
                self.wrap.append(self.mode_switch);
                S.User.watch('user', function(user) { self.publish_button.user_loaded(user); });
            }
            return self.wrap;
        },
        roots: function(roots) {
            this.roots = roots;
        },
        location_loaded: function(location) {
            var self = this;
            var children_node = new ChildrenNode(location);
            self.location.append(children_node.element());
            self.children_node = children_node;
        },
        page_loaded: function(page) {
            var self = this, children_node = self.children_node;

            page.bind('entry_added', function(entry, position) {
                if (entry.is_page()) {
                    children_node.add_page(entry, position);
                }
            });

            page.bind('removed_entry', function(entry) {
                if (entry.is_page()) {
                    children_node.remove_page(entry);
                }
            });

            if (!page.is_root()) {
                page.watch('slug', function(title) {
                    self.navigation_current.set_title(title);
                });
            }

            page.title_field().watch('value', function(title) {
                Spontaneous.set_browser_title(title);
            });
        },
        update_navigation: function(location) {
            var nodes = [];
            // var location = this.get('location');
            var $location_bar = this.location;
            if (typeof location.ancestors === 'undefined') {
                console.warn('Invalid location', location);
                return;
            }
            var ancestors = location.ancestors.slice(0);
            var i, node, root, is_root = false, root_node, children_node, current_node;
            if (ancestors.length === 0) {
                root = location;
                is_root = true;
            } else {
                root = ancestors.shift();
            }
            root_node = new RootNode(root, this.roots);
            nodes.push(root_node);
            for (i=0, ii=ancestors.length; i < ii; i++) {
                var page = ancestors[i];
                node = new AncestorNode(page);
                nodes.push(node);
            }
            if (!is_root) {
                current_node = new CurrentNode(location);
                nodes.push(current_node);
            } else {
                current_node = root_node;
            }
            $('li', $location_bar).remove();

            if (location.children.length > 0) {
                //  children_node = new ChildrenNode(location.children);
                // $location_bar.append(children_node.element())
            }
            for (i = 0, ii = nodes.length; i < ii; i++) {
                node = nodes[i];
                $location_bar.append(node.element());
            }
            this.navigation_current = current_node;
        },
        publishing_started: function() {
            this.publish_button.publishing_started();
        },
        mode_set: function(mode) {
            this.mode_switch.text(S.TopBar.opposite_mode(mode));
        },
        hide: function() {
            this.wrap.hide();
        },
        show: function() {
            this.wrap.show();
        }
    });

    var ServiceNavigationView = new JS.Class({
        initialize: function(service) {
            this.service = service;
        },
        panel: function() {
            var self = this;
            this.wrap = dom.div('#service-navigation');
            var title = dom.h2().text(this.service.title);
            var close = dom.a('.button').text('Close').click(function() {
                S.Services.close();
            });
            this.wrap.append(title, close);
            return this.wrap;
        }
    });
    var TopBar = new JS.Singleton({
        include: Spontaneous.Properties,
        location: '/',
        panel: function() {
            this.wrap = dom.div('#top');
            // this.icon = dom.div('#spontaneous-root');
            this.icon = this.rootMenu();
            this.holder = dom.div('#service-outer');
            this.navigationView = new CMSNavigationView(this);
            this.serviceStation = dom.div('#service-inner');
            this.holder.append(this.navigationView.panel(), this.serviceStation);
            this.wrap.append(this.icon, this.holder);
            return this.wrap;
        },
        rootMenu: function() {
            var li = dom.div('#spontaneous-root').click(function(event) {
                $(this).addClass('active'); // no easy way to remove this
                Spontaneous.Popover.open(event, new Spontaneous.RootMenuView(function() {
                    li.removeClass('active');
                }));
            });
            return li.append(dom.span());
        },
        init: function(metadata) {
            if (!this.get('mode')) {
                this.set('mode', S.ContentArea.mode);
            }
            this.navigationView.roots(metadata.roots);
            S.Editing.watch('page', this.page_loaded.bind(this));
            S.Location.watch('location', this.location_loaded.bind(this));
        },
        location_loaded: function(location) {
            // clear the loaded page so that it forces a reload of the nav when we switch back to edit mode
            this.set('page', undefined);
            this.navigationView.location_loaded(location);
        },
        page_loaded: function(page) {
            var loaded_page = this.get('page'), loaded_id = (loaded_page ? loaded_page.id() : undefined);
            if (page && (page.id() !== loaded_id)) {
                this.set('page', page);
                this.navigationView.page_loaded(page);
            }
        },
        publishing_started: function() {
            this.navigationView.publishing_started();
        },
        location_changed: function(new_location) {
            Spontaneous.set_browser_title(new_location.title);
            this.set('location', new_location);
            this.navigationView.update_navigation(new_location);
            this.page_loaded = function(page) {
            };
        },
        toggle_modes: function(previewModeDisabled) {
            var newMode = this.opposite_mode(this.get('mode'));
            if (previewModeDisabled && newMode === 'preview') {
                return;
            }
            this.set_mode(newMode);
        },
        opposite_mode: function(to_mode) {
            if (to_mode === 'preview') {
                return 'edit';
            } else if (to_mode === 'edit') {
                return 'preview';
            }
        },
        set_mode: function(mode) {
            this.set('mode', mode);
            this.navigationView.mode_set(mode);
        },
        showService: function(service) {
            this.navigationView.hide();
            this.serviceView = new ServiceNavigationView(service);
            this.serviceStation.empty().append(this.serviceView.panel());
        },
        showNavigationView: function() {
            this.serviceStation.empty();
            this.navigationView.show();
        }
    });
    return TopBar;
}(jQuery, Spontaneous));