APIs

Show:
/**
 StoryLineView module manages the Timeline > Channels UI while displaying the visual length over time for each block on the selected channel
 @class StorylineView
 @constructor
 @param {String}
 @return {Object} instantiated StorylineView
 **/
define(['jquery', 'backbone', 'text', 'text!_templates/_storyboard.html'], function ($, Backbone, text, storylineTemplate) {

    /**
     Custom event fired when a block is selected on the storyline
     @event STORYLINE_BLOCK_SELECTED
     @param {This} caller
     @param {Self} context caller
     @param {Event}
     @static
     @final
     **/
    BB.EVENTS.STORYLINE_BLOCK_SELECTED = 'STORYLINE_BLOCK_SELECTED';

    BB.SERVICES.STORYLINE = 'StoryLine';

    var StorylineView = BB.View.extend({

        /**
         Constructor
         @method initialize
         **/
        initialize: function () {
            var self = this;
            self.m_storyWidth = 0;
            self.m_owner = self;
            self.m_selectedTimelineID = undefined;
            self.m_selectedBlockID = undefined;
            self.m_selectedChannel = undefined;
            self.m_blockZindex = 3; // future drag support
            BB.comBroker.setService(BB.SERVICES.STORYLINE, self);
            BB.comBroker.listen(BB.EVENTS.SIDE_PANEL_SIZED, $.proxy(self._updateWidth, self));
            self._listenReset();
            self._listenTimelineSelected();
            self._listenTimelineChanged();
            self._listenBlockSelection();
            self._listenTimelineBlockRemoved();
            self._listenStackViewSelected();
            self._listenToggleStorylineCollapsible();
            self._listenAppResized();
            self._listenContextMenu();
            self._listenExitPreview();
            self._updateWidth();

            //setTimeout(function(){
            //    self.collapseStoryLine()
            //},8000)
        },

        /**
         Draw a fresh storyline for current timeline
         @method _render
         **/
        _render: function () {
            var self = this;
            if (_.isUndefined(self.m_render)) {
                self.m_render = _.debounce(function () {
                    $(Elements.STORYLINE_ELEM).empty();
                    self.m_storylineContainerSnippet = $(storylineTemplate).find(Elements.STORYLINE_CONTAINER).parent();
                    self.m_TableSnippet = $(storylineTemplate).find('table').parent();
                    self.m_ChannelSnippet = $(storylineTemplate).find(Elements.CLASS_STORYLINE_CHANNEL).parent();
                    self._populateScala();
                    self._populateChannels();
                    self._listenSelections();
                    self._addBlockSelection(self.m_selectedBlockID);
                    self._addChannelSelection(self.m_selectedChannel);
                }, 100);
            }
            self.m_render();
        },

        /**
         Listen to reset of when switching to different campaign so we forget current state
         @method _listenReset
         **/
        _listenReset: function () {
            var self = this;
            BB.comBroker.listen(BB.EVENTS.CAMPAIGN_RESET, function () {
                self.m_storyWidth = 0;
                self.m_selectedTimelineID = undefined;
                self.m_selectedBlockID = undefined;
                self.m_selectedChannel = undefined;
            });
        },

        /**
         Anytime the containing StackView is selected, re-render the Storyline as resources or scenes could have been
         removed while we were gone
         @method _listenStackViewSelected
         **/
        _listenStackViewSelected: function () {
            var self = this;
            var appContentFaderView = BB.comBroker.getService(BB.SERVICES['APP_CONTENT_FADER_VIEW']);
            var campaignSliderStackView = BB.comBroker.getService(BB.SERVICES['CAMPAIGN_SLIDER_STACK_VIEW']);

            campaignSliderStackView.on(BB.EVENTS.SELECTED_STACK_VIEW, function (e) {
                if (e == BB.comBroker.getService(BB.SERVICES['CAMPAIGN_VIEW'])) {
                    self._delayedRender();
                }
            });
            appContentFaderView.on(BB.EVENTS.SELECTED_STACK_VIEW, function (e) {
                if (e == BB.comBroker.getService(BB.SERVICES['CAMPAIGN_MANAGER_VIEW'])) {
                    self._delayedRender();
                }
            });
        },

        /**
         Listen for block selection
         @method _listenBlockSelection
         **/
        _listenBlockSelection: function () {
            var self = this;
            BB.comBroker.listen(BB.EVENTS.BLOCK_SELECTED, function (e) {
                var blockID = e.edata;
                if (!_.isNumber(blockID)) // ignore scene blocks
                    return;
                self._addBlockSelection(blockID);
            });
        },

        /**
         Listen to when the app is resized so we can re-render
         @method _listenAppResized
         **/
        _listenAppResized: function () {
            var self = this;
            BB.comBroker.listen(BB.EVENTS.APP_SIZED, function (e) {
                self._delayedRender();
            });
        },

        /**
         Add block selection by marking it on the storyline and remembering selection
         @method _addBlockSelection
         @param {Number} i_blockID
         **/
        _addBlockSelection: function (i_blockID) {
            var self = this;
            if (_.isUndefined(i_blockID))
                return;
            self._removeBlockSelection();
            self.m_selectedBlockID = i_blockID;
            var blockElem = $(Elements.STORYLINE_CONTAINER).find('[data-timeline_channel_block_id="' + i_blockID + '"]');
            $(blockElem).addClass(BB.lib.unclass(Elements.CLASS_TIMELINE_BLOCK_SELECTED));
        },

        /**
         Add channel selection by marking it on the storyline and remembering selection
         @method _addChannelSelection
         @param {Number} i_selectedChannel
         **/
        _addChannelSelection: function (i_selectedChannel) {
            var self = this;
            if (_.isUndefined(i_selectedChannel))
                return;
            self._removeChannelSelection();
            self.m_selectedChannel = i_selectedChannel;
            var blockElem = $(Elements.STORYLINE_CONTAINER).find('[data-timeline_channel_id="' + i_selectedChannel + '"]');
            blockElem = $(blockElem).filter('.channelHead');
            $(blockElem).addClass(BB.lib.unclass(Elements.CLASS_CHANNEL_HEAD_SELECTED));
        },

        /**
         Remove currently selected channel by removing selection as well forgetting it
         @method _removeChannelSelection
         **/
        _removeChannelSelection: function () {
            var self = this;
            self.m_selectedChannel = undefined;
            $(Elements.CLASS_CHANNEL_HEAD_SELECTED, Elements.STORYLINE_CONTAINER).removeClass(BB.lib.unclass(Elements.CLASS_CHANNEL_HEAD_SELECTED));
        },

        /**
         Remove currently selected block by removing selection as well forgetting it
         @method _removeBlockSelection
         **/
        _removeBlockSelection: function () {
            var self = this;
            self.m_selectedBlockID = undefined;
            $(Elements.CLASS_TIMELINE_BLOCK, Elements.STORYLINE_CONTAINER).removeClass(BB.lib.unclass(Elements.CLASS_TIMELINE_BLOCK_SELECTED));
        },

        /**
         Build the UI for the top seconds / minutes scala of the storyline
         @method _populateScala
         **/
        _populateScala: function () {
            var self = this, i;
            var ticks = [];
            var format = 's';
            var totalDuration = parseInt(pepper.getTimelineTotalDuration(self.m_selectedTimelineID));
            if (totalDuration > 420) {
                totalDuration = totalDuration / 60;
                format = 'm';
            }

            var tick = totalDuration / 4;
            for (i = 1; i < 5; i++) {
                tick = BB.lib.parseToFloatDouble(tick);
                ticks.push(tick * i);
            }

            ticks.unshift(0);
            ticks[ticks.length - 1] = totalDuration;
            var l = String((ticks[ticks.length - 1]).toFixed(2)).length;
            var lastTick = '';
            var scalaRuler = $(self.m_TableSnippet).find(Elements.CLASS_SCALA_RULER);
            for (i = 0; i < ticks.length; i++) {
                if (i == ticks.length - 1)
                    lastTick = 'width="1%"'
                var value = BB.lib.padZeros(BB.lib.parseToFloatDouble(ticks[i]), l) + format; // log(value);
                $(scalaRuler).append('<td class="scalaNum"' + lastTick + ' >' + value + '</td>');
            }
            $(Elements.STORYLINE_ELEM).append(self.m_TableSnippet);
        },

        /**
         Populate UI channels
         @method _populateChannels
         **/
        _populateChannels: function () {
            var self = this;
            var channelsIDs = pepper.getChannelsOfTimeline(self.m_selectedTimelineID);
            for (var n = 0; n < channelsIDs.length; n++) {
                var channelID = channelsIDs[n];
                var channelSnippet = _.template(_.unescape(self.m_ChannelSnippet.html()), {value: n + 1});
                var viewerID = pepper.getAssignedViewerIdFromChannelId(channelID);
                $(self.m_storylineContainerSnippet).find('section').append(channelSnippet);
                var channelHead = $(self.m_storylineContainerSnippet).find(Elements.CLASS_CHANNEL_HEAD + ':last');
                var channelBody = $(self.m_storylineContainerSnippet).find(Elements.CLASS_CHANNEL_BODY + ':last');
                $(channelHead).attr('data-timeline_channel_id', channelID);
                $(channelBody).attr('data-timeline_channel_id', channelID);
                $(channelHead).attr('data-campaign_timeline_board_viewer_id', viewerID);
                $(channelBody).attr('data-campaign_timeline_board_viewer_id', viewerID);
                self._populateBlocks(channelID);
            }
            $(Elements.STORYLINE_ELEM).append(self.m_storylineContainerSnippet);
            self._updateWidth();
            setTimeout(function () {
                self._updateWidth();
            }, 5);
        },

        /**
         Populate UI blocks
         @method _populateBlocks
         @params {Number} i_campaign_timeline_chanel_id
         **/
        _populateBlocks: function (i_campaign_timeline_chanel_id) {
            var self = this;
            var timeline = BB.comBroker.getService(BB.SERVICES['CAMPAIGN_VIEW']).getTimelineInstance(self.m_selectedTimelineID);
            var channel = timeline.getChannelInstance(i_campaign_timeline_chanel_id);
            var blocks = channel.getBlocks();
            var snippet, totalPercent = 0;
            for (var block in blocks) {
                var blockData = blocks[block].getBlockData();
                var blockID = blockData.blockID;
                var fontAwesome = blocks[block].getBlockData().blockFontAwesome;
                var totalDuration = parseInt(pepper.getTimelineTotalDuration(self.m_selectedTimelineID));
                var blockDuration = pepper.getBlockTimelineChannelBlockLength(blockID).totalInSeconds;
                var percent = (parseFloat(blockDuration) / parseFloat(totalDuration) * 100);
                totalPercent += percent;

                var blockWidth = (self.m_storyWidth * percent) / 100;
                if (blockWidth < 1)
                    continue;
                if (blockWidth < 25) {
                    snippet = '<div class="timelineBlock" data-timeline_channel_block_id="' + blockID + '" style="width: ' + percent + '%;"></div>';
                } else {
                    snippet = '<div class="timelineBlock" data-timeline_channel_block_id="' + blockID + '" style="width: ' + percent + '%;"><i style="font-size: 14px"  class="fa ' + fontAwesome + '"></i></div>';

                    /* future support draggable */
                    // snippet = '<div class="draggable ui-widget-content ui-draggable ui-draggable-handle timelineBlock" data-timeline_channel_block_id="' + blockID + '" style="width: ' + percent + '%;"><i style="font-size: 14px"  class="fa ' + fontAwesome + '"></i></div>';

                }
                /* future support draggable */
                // setTimeout(function(){
                //    $(".timelineBlock").draggable({
                //        axis: "x",
                //        start: function(event, ui) { $(this).css("z-index", self.m_blockZindex++); }
                //    });
                //},700);

                $(self.m_storylineContainerSnippet).find('.channelBody:last').append(snippet);
            }
        },

        /**
         Compute the storyline UI width total width
         @method _updateWidth
         **/
        _updateWidth: function () {
            var self = this;
            self.m_storyWidth = parseInt($(Elements.STORYLINE_CONTAINER).width()) - 25;
            $(Elements.CLASS_CHANNEL_BODY_CONTAINER).width(self.m_storyWidth);
        },

        /**
         Listen to changes in the timeline (channel, block length etc) so we can re-render the storyline
         @method _listenTimelineChanged
         **/
        _listenTimelineChanged: function () {
            var self = this;
            pepper.listen(Pepper.BLOCK_LENGTH_CHANGED, $.proxy(self._render, self));
            BB.comBroker.listen(BB.EVENTS.CAMPAIGN_TIMELINE_CHANGED, function () {
                self._render();
            })
        },

        /**
         Listen to a new timeline selection so we can re-render the storyline
         @method _listenTimelineSelected
         **/
        _listenTimelineSelected: function () {
            var self = this;
            BB.comBroker.listen(BB.EVENTS.CAMPAIGN_TIMELINE_SELECTED, function (e) {
                self._deselection();
                self.m_selectedTimelineID = e.edata;
                self._render();
            });
        },

        /**
         Forget all current selections
         @method _deselection
         **/
        _deselection: function () {
            var self = this;
            self.m_selectedTimelineID = undefined;
            self.m_selectedBlockID = undefined;
            self.m_selectedChannel = undefined;
        },

        /**
         Listen to channel selection so we can re-render storyline
         @method _listenSelections
         **/
        _listenSelections: function () {
            var self = this;
            $(Elements.CLASS_CHANNEL_HEAD).off('click');
            $(Elements.CLASS_CHANNEL_HEAD).on('click', function (e) {
                $.proxy(self._blockChannelSelected(e), self);
                BB.comBroker.fire(BB.EVENTS.CAMPAIGN_TIMELINE_CHANNEL_SELECTED, this, null, self.m_selectedChannel);
            });
            $(Elements.CLASS_STORYLINE_CHANNEL).off('click');
            $(Elements.CLASS_STORYLINE_CHANNEL).on('click', function (e) {
                $.proxy(self._blockChannelSelected(e), self);
                BB.comBroker.fire(BB.EVENTS.CAMPAIGN_TIMELINE_CHANNEL_SELECTED, this, null, self.m_selectedChannel);
            });
            $(Elements.CLASS_TIMELINE_BLOCK).off('click contextmenu');
            $(Elements.CLASS_TIMELINE_BLOCK).on('click contextmenu', function (e) {
                $.proxy(self._blockSelected(e), self);

                /* future support draggable */
                // $(this).addClass('top').removeClass('bottom');
                // $(this).siblings().removeClass('top').addClass('bottom');
                // $(this).css("z-index", self.m_blockZindex++);
            });
        },

        /**
         When a block is selected within a channel, get the resource element so we can select it and fire
         the BLOCK_SELECTED event
         @method _blockSelected
         @param {Event} e
         **/
        _blockChannelSelected: function (e) {
            var self = this;
            if (e.button == 0) {
                e.stopImmediatePropagation();
                $(Elements.STORYLINE_CONTEXT_MENU).hide();
            }

            var blockElem = $(e.target);

            if (_.isUndefined($(blockElem).attr('class')))
                return true;

            if ($(blockElem).hasClass(BB.lib.unclass(Elements.CLASS_STORYLINE_CHANNEL)))
                blockElem = $(blockElem).find(Elements.CLASS_CHANNEL_HEAD);

            if ($(blockElem).hasClass(BB.lib.unclass(Elements.CLASS_TIMELINE_BLOCK)))
                blockElem = $(blockElem).closest(Elements.CLASS_CHANNEL_BODY);

            var timeline_channel_id = $(blockElem).data('timeline_channel_id');
            var campaign_timeline_board_viewer_id = $(blockElem).data('campaign_timeline_board_viewer_id');

            //if (_.isUndefined(timeline_channel_id) || _.isUndefined(campaign_timeline_board_viewer_id))
            //    return false;

            if (self.m_selectedChannel == timeline_channel_id)
                return true;

            self.m_selectedChannel = timeline_channel_id;
            var screenData = {
                m_owner: self,
                campaign_timeline_id: self.m_selectedTimelineID,
                campaign_timeline_board_viewer_id: campaign_timeline_board_viewer_id
            };
            self._removeBlockSelection();
            self._addChannelSelection(self.m_selectedChannel);
            var sequencer = BB.comBroker.getService(BB.SERVICES['SEQUENCER_VIEW']);
            sequencer.selectViewer(screenData.campaign_timeline_id, screenData.campaign_timeline_board_viewer_id);
            BB.comBroker.fire(BB.EVENTS.ON_VIEWER_SELECTED, this, screenData);
            return true;
        },

        /**
         When a block is selected within a channel, get the resource element so we can select it and fire
         the BLOCK_SELECTED event
         @method _blockSelected
         @param {Event} e
         **/
        _blockSelected: function (e) {
            var self = this;
            //e.stopImmediatePropagation();
            var blockElem = $(e.target);
            self.selected_block_id = $(blockElem).data('timeline_channel_block_id');
            // if label was selected
            if (_.isUndefined(self.selected_block_id)) {
                blockElem = $(e.target).parent();
                self.selected_block_id = $(blockElem).data('timeline_channel_block_id');
            }
            e['target'] = blockElem[0];
            self._blockChannelSelected(e);
            BB.comBroker.fire(BB.EVENTS.STORYLINE_BLOCK_SELECTED, this, null, self.selected_block_id);
            $(blockElem).addClass(BB.lib.unclass(Elements.CLASS_TIMELINE_BLOCK_SELECTED));
            //return false;
        },

        /**
         Toggle the arrow of the collapsible storyline UI widget
         @method _listenToggleStorylineCollapsible
         **/
        _listenToggleStorylineCollapsible: function () {
            var self = this;
            $(Elements.TOGGLE_STORYLINE_COLLAPSIBLE).on('click', function () {
                var toggle = $(this).find('span')[0];
                if ($(toggle).hasClass('glyphicon-chevron-down')) {
                    $(toggle).removeClass('glyphicon-chevron-down').addClass('glyphicon-chevron-right')
                } else {
                    $(toggle).removeClass('glyphicon-chevron-right').addClass('glyphicon-chevron-down')
                }
                $(Elements.STORYLINE_ELEM).fadeIn(500).queue(function () {
                    self._render();
                }).dequeue().delay(500).queue(function () {
                    self._render();
                }).dequeue();
            });
        },

        /**
         Listen to any canvas right click
         @method _listenContextMenu
         **/
        _listenContextMenu: function () {
            var self = this;
            $(Elements.STORYLINE_ELEM).contextmenu({
                target: Elements.STORYLINE_CONTEXT_MENU,
                before: function (e, element, target) {
                    e.preventDefault();
                    //self.m_mouseX = e.offsetX;
                    //self.m_mouseY = e.offsetY;
                    if (_.isUndefined(self.m_selectedBlockID))
                        return false;
                    return true;
                },
                onItem: function (context, e) {
                    self._onContentMenuSelection($(e.target).attr('name'))
                }
            });
        },

        /**
         Re-render the storyboard upon live preview exit
         @method _listenExitPreview
         **/
        _listenExitPreview: function () {
            var self = this;
            BB.comBroker.listen(BB.EVENTS.PREVIEW_EXIT, function (e) {
                self._delayedRender();
            });
        },

        /**
         Delayed render of stoeyboard
         @method _delayedRender
         **/
        _delayedRender: function () {
            var self = this;
            setTimeout(function () {
                self._updateWidth();
                self._render();
            }, 500);
        },

        /**
         On Scene right click context menu selection command
         @method _onContentMenuSelection
         @param {String} i_command
         **/
        _onContentMenuSelection: function (i_command) {
            var self = this;
            var campaign_timeline_id = BB.comBroker.getService(BB.SERVICES.CAMPAIGN_VIEW).getSelectedTimeline();
            if (campaign_timeline_id == -1 || _.isUndefined(campaign_timeline_id))
                return;

            switch (i_command) {
                case 'nextChannel':
                {
                    self.selectNextChannel();
                    break;
                }
                case 'addContent':
                {
                    $(Elements.ADD_BLOCK_BUTTON).trigger('click');
                    break;
                }
                case 'removeContent':
                {
                    $(Elements.REMOVE_BLOCK_BUTTON).trigger('click');
                    break;
                }
                case 'first':
                {
                    BB.comBroker.getService(BB.SERVICES.CHANNEL_LIST_VIEW).moveBlockFirst();
                    break;
                }
                case 'last':
                {
                    BB.comBroker.getService(BB.SERVICES.CHANNEL_LIST_VIEW).moveBlockLast();
                    break;
                }
            }
            return true;
        },

        /**
         Listen to when a timeline block is removed
         @method _listenTimelineBlockRemoved
         @param {Object} e
         **/
        _listenTimelineBlockRemoved: function () {
            var self = this;
            pepper.listen(Pepper.REMOVE_TIMELINE_CHANNEL_BLOCK, function (e) {
                self.m_selectedBlockID = undefined;
                self._removeBlockSelection();
            });
        },

        /**
         Collapse the storyline bootstrap panel and title
         @method collapseStoryLine
         **/
        collapseStoryLine: function () {
            var self = this;
            if (!$(Elements.STORYLINE_CONTAINER_COLLAPSE).hasClass('in'))
                return;
            $('.panel-collapse', Elements.STORYLINE_COLLAPSIBLE).collapse('hide');
            $('.panel-title', Elements.STORYLINE_COLLAPSIBLE).attr('data-toggle', 'collapse');
            var coll = $(Elements.STORYLINE_COLLAPSIBLE);
            var toggle = $(coll).find('span')[0];
            $(toggle).removeClass('glyphicon-chevron-down').addClass('glyphicon-chevron-right')
        },

        /**
         Select next channel
         @method selectNextChannel
         **/
        selectNextChannel: function () {
            var self = this;
            var timeline_channel_id, campaign_timeline_board_viewer_id;
            var channelsIDs = pepper.getChannelsOfTimeline(self.m_selectedTimelineID);
            if (_.isUndefined(self.m_selectedChannel)) {
                timeline_channel_id = channelsIDs[0];
            } else {
                for (var ch in channelsIDs) {
                    if (channelsIDs[ch] == self.m_selectedChannel) {
                        if (_.isUndefined(channelsIDs[parseInt(ch) + 1])) {
                            timeline_channel_id = channelsIDs[0];
                        } else {
                            timeline_channel_id = channelsIDs[parseInt(ch) + 1];
                        }
                    }
                }
            }
            campaign_timeline_board_viewer_id = pepper.getAssignedViewerIdFromChannelId(timeline_channel_id);
            // note: workaround for when viewer is unassigned, need to investigate
            if (_.isUndefined(campaign_timeline_board_viewer_id))
                return;
            var screenData = {
                m_owner: self,
                campaign_timeline_id: self.m_selectedTimelineID,
                campaign_timeline_board_viewer_id: campaign_timeline_board_viewer_id
            };
            self._removeBlockSelection();
            self._addChannelSelection(timeline_channel_id);
            BB.comBroker.getService(BB.SERVICES['SEQUENCER_VIEW']).selectViewer(screenData.campaign_timeline_id, screenData.campaign_timeline_board_viewer_id);
            BB.comBroker.fire(BB.EVENTS.ON_VIEWER_SELECTED, this, screenData);
            BB.comBroker.fire(BB.EVENTS.CAMPAIGN_TIMELINE_CHANNEL_SELECTED, this, null, self.m_selectedChannel);
        }
    });

    return StorylineView;

});