APIs

Show:
/**
 The timeline instance is created for each timeline in a campaign. It creates its UI, listens to timeline
 event selections, and holds a reference to its own timeline_id.
 The timeline instance also creates channel instances for all the channels it hosts and hold references to these
 channels via m_channels member.
 @class Timeline
 @constructor
 @return {Object} instantiated Timeline
 **/
define(['jquery', 'backbone', 'Channel', 'ScreenTemplateFactory', 'datepicker', 'XDate'], function ($, Backbone, Channel, ScreenTemplateFactory, datepicker, Xdate) {

    /**
     Custom event fired when a timeline is selected. If a timeline is not of the one selected,
     it ignores the event.
     @event Timeline.CAMPAIGN_TIMELINE_SELECTED
     @param {This} caller
     @param {Self} context caller
     @param {Event} timelineID of the timeline selected
     @static
     @final
     **/
    BB.EVENTS.CAMPAIGN_TIMELINE_SELECTED = 'CAMPAIGN_TIMELINE_SELECTED';

    var Timeline = BB.Controller.extend({

        /**
         Constructor
         @method initialize
         **/
        initialize: function () {
            var self = this;
            self.m_ONCE = '0';
            self.m_DAILY = '1';
            self.m_WEEKLY = '2';
            self.m_PRIORITY_LOW = 2;
            self.m_PRIORITY_MEDIUM = 1;
            self.m_PRIORITY_HIGH = 0;
            self.m_WEEKDAYS = [1, 2, 4, 8, 16, 32, 64];
            self.m_channels = {}; // hold references to all created channel instances
            self.m_screenTemplate = undefined;
            self.m_campaign_timeline_id = self.options.campaignTimelineID;
            self.m_timing = 'sequencer';
            self.m_stackViewID = undefined;
            self.m_property = BB.comBroker.getService(BB.SERVICES['PROPERTIES_VIEW']);
            self.m_sequences = BB.comBroker.getService(BB.SERVICES['SEQUENCER_VIEW']);
            self.m_selected = false;
            self._populateChannels();
            self.populateTimeline();
            self._listenInputChange();
            self._listenReset();
            self._listenViewerRemoved();
            self._onTimelineSelected();
            pepper.listenWithNamespace(Pepper.TEMPLATE_VIEWER_EDITED, self, $.proxy(self._templateViewerEdited, self));
            pepper.listenWithNamespace(Pepper.NEW_CHANNEL_ADDED, self, $.proxy(self._channelAdded, self));

            var campaignPlayMode = pepper.getCampaignPlayModeFromTimeline(self.m_campaign_timeline_id);
            if (campaignPlayMode == BB.CONSTS.SCHEDULER_MODE) {
                self._listenSchedDurationChange();
                self._listenSchedPriorityChange();
                self._listenSchedStartTimeChange();
                self._listenSchedRepeatChange();
                self._listenDatePicker();
                self._listenWeekdayChange();
            }
        },

        /**
         Listen to reset of when switching to different campaign so we forget current state
         @method _listenReset
         **/
        _listenReset: function () {
            var self = this;
            BB.comBroker.listenWithNamespace(BB.EVENTS.CAMPAIGN_RESET, self, function () {
                self._reset();
            });
        },

        /**
         Listen to timeline selection events and populate the properties panel accordingly.
         @method _onTimelineSelected
         @return none
         **/
        _onTimelineSelected: function () {
            var self = this;
            self.m_campaignTimelineSelectedHandler = function (e) {
                var timelineID = e.edata;
                if (self.m_campaign_timeline_id != timelineID) {
                    self.m_selected = false;
                    return;
                }
                self.m_selected = true;
                self._propLoadTimeline();
                // log('timeline selected ' + self.m_campaign_timeline_id);
            };
            BB.comBroker.listenWithNamespace(BB.EVENTS.CAMPAIGN_TIMELINE_SELECTED, self, self.m_campaignTimelineSelectedHandler);
        },

        /**
         Update msdb when the timeline title has changed.
         @method _listenInputChange
         @return none
         **/
        _listenInputChange: function () {
            var self = this;
            self.m_inputChangeHandler = _.debounce(function (e) {
                if (!self.m_selected)
                    return;
                var text = $(Elements.TIME_LINE_PROP_TITLE_ID).val();
                if (BB.lib.isEmpty(text))
                    return;
                text = BB.lib.cleanProbCharacters(text, 1);
                pepper.setCampaignTimelineRecord(self.m_campaign_timeline_id, 'timeline_name', text);
            }, 150, false);
            $(Elements.TIME_LINE_PROP_TITLE_ID).on("input", self.m_inputChangeHandler);
        },


        /**
         Listen to changes in timeline duration changes with respect to the scheduler
         @method _listenDatePicker
         @return none
         **/
        _listenDatePicker: function () {
            var self = this;
            self.m_listenDatePickerHandler = function (e) {
                if (!self.m_selected)
                    return;
                if (_.isUndefined(e.date))
                    return;
                var field = $(e.target).attr('name');
                var xd = new XDate(e.date);
                var date = xd.toString('MM/dd/yyyy');
                pepper.setCampaignsSchedule(self.m_campaign_timeline_id, field, date);
            };
            $(Elements.CLASS_TIME_PICKER_SCHEDULER).datepicker().on("hide", self.m_listenDatePickerHandler);

        },

        /**
         Listen weekdays change in scheduler
         @method _listenWeekdayChange
         @param {Number} i_playerData
         @return {Number} Unique clientId.
         **/
        _listenWeekdayChange: function () {
            var self = this;
            self.m_schedWeekdayHandler = function (e) {
                if (!self.m_selected)
                    return;
                var weekBits = 0;
                // bitwize operator
                $(Elements.SCHEDUALED_DAYS).find('input').each(function (i, el) {
                    if ($(el).prop('checked'))
                        weekBits = weekBits + self.m_WEEKDAYS[i];
                });
                pepper.setCampaignsSchedule(self.m_campaign_timeline_id, 'week_days', weekBits);
            };
            $(Elements.CLASS_SCEDULE_DAY).on("change", self.m_schedWeekdayHandler);
        },

        /**
         Listen to changes in timeline duration changes with respect to the scheduler
         @method _listenSchedDurationChange
         @return none
         **/
        _listenSchedDurationChange: function () {
            var self = this;
            self.m_schedChangeDurationHandler = function (e) {
                if (!self.m_selected)
                    return;
                var totalSeconds = pepper.formatObjectToSeconds({
                    hours: e.time.hours,
                    minutes: e.time.minutes,
                    seconds: e.time.seconds
                });
                pepper.setCampaignsSchedule(self.m_campaign_timeline_id, 'duration', totalSeconds);
            };
            $(Elements.TIME_PICKER_DURATION_INPUT).on("hide.timepicker", self.m_schedChangeDurationHandler);
        },

        /**
         Listen to when sched repeat on the carousel changed
         @method _listenSchedRepeatChange
         **/
        _listenSchedRepeatChange: function(){
            var self = this;
            self.m_schedChangeRepeatHandler = _.debounce(function (e) {
                if (!self.m_selected)
                    return;
                var carouselIndex = $(Elements.SCHEDULER_REPEAT_MODE + ' .active').index(Elements.SCHEDULER_REPEAT_MODE + ' .item');
                pepper.setCampaignsSchedule(self.m_campaign_timeline_id, 'repeat_type', carouselIndex);
            },500, false);
            $(Elements.SCHEDULER_REPEAT_MODE).on('slid.bs.carousel', self.m_schedChangeRepeatHandler);
        },

        /**
         Listen to changes in scheduler start time playback values
         @method _listenSchedStartTimeChange
         **/
        _listenSchedStartTimeChange: function () {
            var self = this;
            self.m_schedChangeStartTimeHandler = function (e) {
                if (!self.m_selected)
                    return;
                var totalSeconds = pepper.formatObjectToSeconds({
                    hours: e.time.hours,
                    minutes: e.time.minutes,
                    seconds: e.time.seconds
                });
                pepper.setCampaignsSchedule(self.m_campaign_timeline_id, 'start_time', totalSeconds);
            };
            $(Elements.TIME_PICKER_TIME_INPUT).on('hide.timepicker', self.m_schedChangeStartTimeHandler);
        },

        /**
         Listen to changes in scheduler priority values
         @method _listenSchedPriorityChange
         **/
        _listenSchedPriorityChange: function () {
            var self = this;
            self.m_schedChangePriorityHandler = function (e) {
                if (!self.m_selected)
                    return;
                var priority = $(e.target).attr('name');
                pepper.setCampaignsSchedule(self.m_campaign_timeline_id, 'priorty', priority);
                if (Number(priority) == self.m_PRIORITY_LOW) {
                    $(Elements.SCHEDULE_PRIORITY).find('img').eq(1).fadeTo('fast', 0.5).end().eq(2).fadeTo('fast', 0.5);
                } else if (Number(priority) == self.m_PRIORITY_MEDIUM) {
                    $(Elements.SCHEDULE_PRIORITY).find('img').eq(1).fadeTo('fast', 1).end().eq(2).fadeTo('fast', 0.5);
                } else {
                    $(Elements.SCHEDULE_PRIORITY).find('img').eq(1).fadeTo('fast', 1).end().eq(2).fadeTo('fast', 1);
                }
            };
            $(Elements.CLASS_SCHEDULE_PRIORITIES).on('click', self.m_schedChangePriorityHandler);
        },

        /**
         When a campaign_timeline_board_template is edited, modify its related UI (inside sequencer)
         @method campaign_timeline_board_template_id
         @param {event} e template viewer ids
         **/
        _templateViewerEdited: function (e) {
            var self = this;
            if (!self.m_selected)
                return;
            var campaign_timeline_board_template_id = e.edata;
            self._populateBoardTemplate(campaign_timeline_board_template_id);
        },

        /**
         New channel was added to an existing timeline (most likely through the addition of a viewer (screen division) template editor)
         @method _channelAdded
         @param {event} e
         **/
        _channelAdded: function (e) {
            var self = this;
            if (!self.m_selected)
                return;
            self.m_channels[e.edata.chanel] = new Channel({campaignTimelineChanelID: e.edata.chanel});
        },

        /**
         Populate the timeline property
         @method _listenInputChange
         **/
        _propLoadTimeline: function () {
            var self = this;
            self.m_property.viewPanel(Elements.TIMELINE_PROPERTIES);
            var recTimeline = pepper.getCampaignTimelineRecord(self.m_campaign_timeline_id);
            $(Elements.TIME_LINE_PROP_TITLE_ID).val(recTimeline['timeline_name']);
            self._populateTimelineLength();
            self._populateTimelinePlayMode();
        },

        /**
         Populate the Scheduler UI
         @method _populateScheduler
         @params {Number} i_timeline_id
         **/
        _populateScheduler: function () {
            var self = this;
            if ($(Elements.TIME_PICKER_DURATION_INPUT).timepicker == undefined)
                return;
            var recSchedule = pepper.getCampaignsSchedule(self.m_campaign_timeline_id);
            $(Elements.SCHEDULER_REPEAT_MODE).carousel(Number(recSchedule.repeat_type));
            var duration = pepper.formatSecondsToObject(recSchedule.duration);
            var startTime = pepper.formatSecondsToObject(recSchedule.start_time);
            $(Elements.TIME_PICKER_DURATION_INPUT).timepicker('setTime', duration.hours + ':' + duration.minutes + ':' + duration.seconds);
            $(Elements.TIME_PICKER_TIME_INPUT).timepicker('setTime', startTime.hours + ':' + startTime.minutes + ':' + startTime.seconds);

            if (recSchedule.priorty == self.m_PRIORITY_LOW) {
                $(Elements.SCHEDULE_PRIORITY).find('img').eq(1).fadeTo('fast', 0.5).end().eq(2).fadeTo('fast', 0.5);
            } else if (recSchedule.priorty == self.m_PRIORITY_MEDIUM) {
                $(Elements.SCHEDULE_PRIORITY).find('img').eq(1).fadeTo('fast', 1).end().eq(2).fadeTo('fast', 0.5);
            } else {
                $(Elements.SCHEDULE_PRIORITY).find('img').eq(1).fadeTo('fast', 1).end().eq(2).fadeTo('fast', 1);
            }

            $(Elements.SCHEDULE_PRIORITY)
            switch (String(recSchedule.repeat_type)) {
                case self.m_ONCE:
                {
                    var date = recSchedule.start_date.split(' ')[0];
                    $(Elements.DATE_PICKER_SCHEDULER_ONCE).datepicker('setDate', date);
                    break;
                }
                case self.m_DAILY:
                {
                    var startDate = recSchedule.start_date.split(' ')[0];
                    var endDate = recSchedule.end_date.split(' ')[0];
                    $(Elements.DATE_PICKER_SCHEDULER_DAILY_START).datepicker('setDate', startDate);
                    $(Elements.DATE_PICKER_SCHEDULER_DAILY_END).datepicker('setDate', endDate);
                    break;
                }
                case self.m_WEEKLY:
                {
                    var startDate = recSchedule.start_date.split(' ')[0];
                    var endDate = recSchedule.end_date.split(' ')[0];
                    var weekDays = recSchedule.week_days;
                    var elDays = $(Elements.SCHEDUALED_DAYS);
                    // use bitwise (bitwize) operator << >> to compute days selected
                    self.m_WEEKDAYS.forEach(function (v, i) {
                        var n = weekDays & v;
                        if (n == v) {
                            $(elDays).find('input').eq(i).prop('checked', true);
                        } else {
                            $(elDays).find('input').eq(i).prop('checked', false);
                        }
                    });
                    $(Elements.DATE_PICKER_SCHEDULER_WEEK_START).datepicker('setDate', startDate);
                    $(Elements.DATE_PICKER_SCHEDULER_WEEK_END).datepicker('setDate', endDate);
                    break;
                }
            }
        },

        /**
         Populate the timeline depending if running with sequencer or scheduler
         @method _populateTimelinePlayMode
         **/
        _populateTimelinePlayMode: function () {
            var self = this;
            var campaignMode = pepper.getCampaignPlayModeFromTimeline(self.m_campaign_timeline_id);
            switch (campaignMode) {
                case BB.CONSTS.SEQUENCER_MODE:
                {
                    $(Elements.TIMELINE_WRAP).show();
                    $(Elements.TIMELINE_PLAYMODE_LABEL).find('aside').eq(0).show().end().eq(1).hide();
                    $(Elements.CLASS_SCHEDULER_CLASS).hide();
                    $(Elements.CLASS_SEQUENCE_CLASS).show();
                    break;
                }
                case BB.CONSTS.SCHEDULER_MODE:
                {
                    $(Elements.TIMELINE_WRAP).hide();
                    $(Elements.TIMELINE_PLAYMODE_LABEL).find('aside').eq(1).show().end().eq(0).hide();
                    $(Elements.CLASS_SCHEDULER_CLASS).show();
                    $(Elements.CLASS_SEQUENCE_CLASS).hide();
                    self._populateScheduler();
                    break;
                }
            }
        },

        /**
         Populate the timeline length in its properties box
         @method _populateTimelineLength
         **/
        _populateTimelineLength: function () {
            var self = this;
            self.m_xdate = BB.comBroker.getService('XDATE');
            var totalDuration = parseInt(pepper.getTimelineTotalDuration(self.m_campaign_timeline_id));
            totalDuration = self.m_xdate.clearTime().addSeconds(totalDuration).toString('HH:mm:ss');
            $(Elements.TIMELINE_LENGTH).text(totalDuration);
        },

        /**
         Create a channel instance for every channel this timeline hosts
         @method _populateChannels
         @return none
         **/
        _populateChannels: function () {
            var self = this;
            var channelIDs = pepper.getChannelsOfTimeline(self.m_campaign_timeline_id);
            for (var i = 0; i < channelIDs.length; i++) {
                self.m_channels[channelIDs[i]] = new Channel({campaignTimelineChanelID: channelIDs[i]});
            }
        },

        /**
         Load up the board template (screen divisions) for this timeline instance.
         In case sequencer is used, we push it to the sequencer, thus creating the thumbnail template
         inside the sequencer so this timeline can be selected.
         Scheduler future support.
         @method _populateBoardTemplate
         @param {Number} i_campaign_timeline_board_template_id
         @return none
         **/
        _populateBoardTemplate: function (i_campaign_timeline_board_template_id) {
            var self = this;
            var recBoard = pepper.getGlobalBoardRecFromTemplate(i_campaign_timeline_board_template_id);
            var width = parseInt(recBoard['board_pixel_width']);
            var height = parseInt(recBoard['board_pixel_height']);

            BB.comBroker.getService(BB.SERVICES.RESOLUTION_SELECTOR_VIEW).setResolution(width + 'x' + height);
            if (width > height) {
                BB.comBroker.getService(BB.SERVICES.ORIENTATION_SELECTOR_VIEW).setOrientation(BB.CONSTS.HORIZONTAL);
            } else {
                BB.comBroker.getService(BB.SERVICES.ORIENTATION_SELECTOR_VIEW).setOrientation(BB.CONSTS.VERTICAL);
            }
            var screenProps = pepper.getTemplateViewersScreenProps(self.m_campaign_timeline_id, i_campaign_timeline_board_template_id)
            self.m_sequences.createTimelineThumbnailUI(screenProps);
        },

        /**
         Listen when a screen division / viewer inside a screen layout was deleted and if the channel
         is equal to my channel, dispose of self
         @method _listenViewerRemoved
         **/
        _listenViewerRemoved: function () {
            var self = this;
            BB.comBroker.listenWithNamespace(BB.EVENTS.VIEWER_REMOVED, self, function (e) {
                for (var channel in self.m_channels) {
                    if (e.edata.campaign_timeline_chanel_id == channel) {
                        self.m_channels[channel].deleteChannel();
                        delete self.m_channels[channel];
                        break;
                    }
                }
            });
        },

        /**
         Create the actual UI for this timeline instance. We use the ScreenTemplateFactory for SVG creation
         and insert the snippet onto timelineViewStack so the timeline UI can be presented when selected.
         @method _createTimelineUI
         @param {Object} i_screenProps template properties object
         @return none
         _createTimelineUI: function (i_screenProps) {
            var self = this;

            var screenTemplateData = {
                orientation: BB.comBroker.getService(BB.SERVICES.ORIENTATION_SELECTOR_VIEW).getOrientation(),
                resolution: BB.comBroker.getService(BB.SERVICES.RESOLUTION_SELECTOR_VIEW).getResolution(),
                screenProps: i_screenProps,
                scale: '7'
            };

            var screenTemplate = new ScreenTemplateFactory({
                i_screenTemplateData: screenTemplateData,
                i_type: ScreenTemplateFactory.VIEWER_SELECTABLE,
                i_owner: this});

            var snippet = screenTemplate.create();
            // var elemID = $(snippet).attr('id');
            var divID1 = 'selectableScreenCollections' + _.uniqueId();
            var divID2 = 'selectableScreenCollections' + _.uniqueId();

            var snippetWrapper = '<div id="' + divID1 + '" style="display: none">' +
                '<div align="center" >' +
                '<div id="' + divID2 + '" align="center">' +
                '</div>' +
                '</div>' +
                '</div>';

            $(Elements.SELECTED_TIMELINE).append(snippetWrapper);

            var timelineViewStack = BB.comBroker.getService(BB.SERVICES.CAMPAIGN_VIEW).getTimelineViewStack();
            $('#' + divID2).append($(snippet));
            screenTemplate.selectablelDivision();
            var view = new BB.View({el: '#' + divID1});

            // if we are updating layout from ScreenLayoutEditorView (but actually creating a new Template layout)
            // we remove the previous Template Layout from DOM as well as its matching ScreenTemplateFactory instance
            if (self.m_stackViewID) {
                $('#' + self.m_stackViewID).remove();
                self.m_screenTemplate.destroy();
            }
            ;

            self.m_screenTemplate = screenTemplate;
            self.m_stackViewID = timelineViewStack.addView(view);
            screenTemplate.activate();
        },
         **/

        /**
         Return the view stack index this timeline occupies in the timelineViewStack manager.
         @method getStackViewID
         @return {Number} m_stackViewID
         getStackViewID: function () {
            var self = this;
            return self.m_stackViewID;
        },
         **/

        /**
         Reset current state
         @method _reset
         **/
        _reset: function () {
            var self = this;
            pepper.stopListenWithNamespace(Pepper.TEMPLATE_VIEWER_EDITED, self);
            pepper.stopListenWithNamespace(Pepper.NEW_CHANNEL_ADDED, self);
            BB.comBroker.stopListenWithNamespace(BB.EVENTS.CAMPAIGN_RESET, self);
            BB.comBroker.stopListenWithNamespace(BB.EVENTS.CAMPAIGN_TIMELINE_SELECTED, self);
            $(Elements.TIME_LINE_PROP_TITLE_ID).off("input", self.m_inputChangeHandler);
            $(Elements.TIME_PICKER_DURATION_INPUT).off("hide.timepicker", self.m_schedChangeDurationHandler);
            $(Elements.TIME_PICKER_TIME_INPUT).off('hide.timepicker', self.m_schedChangeStartTimeHandler);
            $(Elements.CLASS_SCHEDULE_PRIORITIES).off('click', self.m_schedChangePriorityHandler);
            $(Elements.CLASS_SCEDULE_DAY).off("change", self.m_schedWeekdayHandler);
            $(Elements.SCHEDULER_REPEAT_MODE).off('slid.bs.carousel', self.m_schedChangeRepeatHandler);
            $(Elements.CLASS_TIME_PICKER_SCHEDULER).datepicker().off("hide", self.m_listenDatePickerHandler);

            $.each(self, function (k) {
                self[k] = undefined;
            });
        },

        /**
         Create the timeline and load up its template (screen divisions) UI
         @method populateTimeline
         @return none
         **/
        populateTimeline: function () {
            var self = this;
            var boardTemplateIDs = pepper.getTemplatesOfTimeline(self.m_campaign_timeline_id);
            for (var i = 0; i < boardTemplateIDs.length; i++) {
                self._populateBoardTemplate(boardTemplateIDs[i]);
            }
        },

        /**
         The timeline hold references to all of the channels it creates that exist within it.
         The getChannelInstance returns a specific channel instance for a channel_id.
         @method getChannelInstance
         @param {Number} i_campaign_timeline_chanel_id
         @return {Object} Channel
         **/
        getChannelInstance: function (i_campaign_timeline_chanel_id) {
            var self = this;
            return self.m_channels[i_campaign_timeline_chanel_id];
        },

        /**
         Delete this timeline thus also need to delete all of its related channels
         @method deleteTimeline
         @return none
         **/
        deleteTimeline: function () {
            var self = this;
            var boardTemplateID = pepper.getGlobalTemplateIdOfTimeline(self.m_campaign_timeline_id);
            pepper.removeTimelineFromCampaign(self.m_campaign_timeline_id);
            pepper.removeSchedulerFromTime(self.m_campaign_timeline_id);
            var campaignTimelineBoardTemplateID = pepper.removeBoardTemplateFromTimeline(self.m_campaign_timeline_id);
            pepper.removeBoardTemplate(boardTemplateID);
            pepper.removeTimelineBoardViewerChannels(campaignTimelineBoardTemplateID);
            BB.comBroker.stopListenWithNamespace(BB.EVENTS.VIEWER_REMOVED, self);
            pepper.removeBoardTemplateViewers(boardTemplateID);
            for (var channel in self.m_channels) {
                self.m_channels[channel].deleteChannel();
                delete self.m_channels[channel];
            }
            self._reset();
        }
    });

    return Timeline;
});