Пример #1
0
define(function (require) {
    var ComponentView = require('coreViews/componentView');
    var Adapt = require('coreJS/adapt');

    var Character = ComponentView.extend({

        preRender: function () {
            //this.listenTo(Adapt, 'device:changed', this.printChart);
        },

        postRender: function () {
            this.setReadyStatus();
            //this.setCompletionStatus();
            this.setCharacter();
        },

        setCharacter: function () {
            var characterId = this.model.get('characterId');
            var speech = this.model.get('speech');
            this.log('characterId = ' + characterId);
            this.log('speech = ' + speech);
        },
		
		log: function (message) {
			console.log('adapt-character:log:' + message);
		}
    });

    Adapt.register('character', Character);
    return Character;
});
define(function(require) {

    var ComponentView = require("coreViews/componentView");
    var Adapt = require("coreJS/adapt");

    var ResponsiveIframe = ComponentView.extend({

        events: {
            'inview': 'inview'
        },

        preRender: function() {
            this.listenTo(Adapt, 'device:changed', this.resizeControl);

            this.checkIfResetOnRevisit();

            // Set the title of the IFRAME
            var iframeTitle = this.model.get('displayTitle') || this.model.get('title');
            this.model.set("iframeTitle", iframeTitle);
        },

        checkIfResetOnRevisit: function() {
            var isResetOnRevisit = this.model.get('_isResetOnRevisit');

            // If reset is enabled set defaults
            if (isResetOnRevisit) {
                this.model.reset(isResetOnRevisit);
            }
        },

        postRender: function() {
            var that = this;
            this.$('.responsiveIframe-iframe').ready(function() {
                that.resizeControl(Adapt.device.screenSize);
                that.setReadyStatus();
            });
        },

        inview: function(event, visible) {
            if (visible) {
                this.setCompletionStatus();
            }
        },

        resizeControl: function(size) {
            var width = this.$('.responsiveIframe-iframe').attr('data-width-' + size);
            var height = this.$('.responsiveIframe-iframe').attr('data-height-' + size);
            this.$('.responsiveIframe-iframe').width(width);
            this.$('.responsiveIframe-iframe').height(height);
        }

    });

    Adapt.register("responsiveIframe", ResponsiveIframe);

});
define(function(require) {

    var ComponentView = require('coreViews/componentView');
    var Adapt = require('coreJS/adapt');

    var Blank = ComponentView.extend({


        preRender: function() {
            this.$el.addClass("no-state");
            // Checks to see if the blank should be reset on revisit
            this.checkIfResetOnRevisit();
        },

        postRender: function() {
            this.setReadyStatus();
            this.$('.component-inner').on('inview', _.bind(this.inview, this));
        },

        // Used to check if the blank should reset on revisit
        checkIfResetOnRevisit: function() {
            var isResetOnRevisit = this.model.get('_isResetOnRevisit');

            // If reset is enabled set defaults
            if (isResetOnRevisit) {
                this.model.reset(isResetOnRevisit);
            }
        },

        inview: function(event, visible, visiblePartX, visiblePartY) {
            if (visible) {
                if (visiblePartY === 'top') {
                    this._isVisibleTop = true;
                } else if (visiblePartY === 'bottom') {
                    this._isVisibleBottom = true;
                } else {
                    this._isVisibleTop = true;
                    this._isVisibleBottom = true;
                }

                if (this._isVisibleTop && this._isVisibleBottom) {
                    this.$('.component-inner').off('inview');
                    this.setCompletionStatus();
                }

            }
        }

    });

    Adapt.register('blank', Blank);

    return Blank;

});
Пример #4
0
define(function(require) {

    var ComponentView = require("coreViews/componentView");
    var Adapt = require("coreJS/adapt");
    var fired = false;


    var GameIframe = ComponentView.extend({
        events: {
            'inview':'inview'
        },

        preRender: function() {
        },

        postRender: function() {
            var that = this;
            /*this.$('.gameIframe-iframe').ready(function() {
                that.setReadyStatus();
            });*/
            this.$('.gameIframe-object').ready(function() {
                that.setReadyStatus();
            });
        },
        /* This function only allows the data or src of the game frame to be set once */
        once: function(fired) {
          if (!fired) {
            /* Options included to allow for using either the iframe or the object tag */
            this.$('.gameIframe-object').attr('data', this.model.get('_source'));
            // this.$('.gameIframe-iframe').attr('src', this.model.get('_source'));
          } else {
            return;
          }
        },

        gameComplete: function(e) {
          if (e.origin === this.model.get('_originURL')) {
            this.setCompletionStatus();
          }
        },

        inview: function(event, visible) {
            if (visible) {
              this.once(fired);
              fired = true;
              window.addEventListener('message', this.gameComplete.bind(this), false);
            }
        }

    });

    Adapt.register("game-frame", GameIframe);

});
define(function(require) {

    var ComponentView = require("coreViews/componentView");
    var Adapt = require("coreJS/adapt");

    var Graphic = ComponentView.extend({

        preRender: function() {
            this.listenTo(Adapt, 'device:changed', this.resizeImage);
        },

        postRender: function() {
            this.resizeImage(Adapt.device.screenSize);
            this.$('.component-widget').on('inview', _.bind(this.inview, this));
        },

        inview: function(event, visible, visiblePartX, visiblePartY) {
            if (visible) {
                if (visiblePartY === 'top') {
                    this._isVisibleTop = true;
                } else if (visiblePartY === 'bottom') {
                    this._isVisibleBottom = true;
                } else {
                    this._isVisibleTop = true;
                    this._isVisibleBottom = true;
                }

                if (this._isVisibleBottom) {
                    //this.$('.component-widget').off('inview');
                    this.setCompletionStatus();
                }
                
            }
        },
        
        resizeImage: function(width) {
            var src = this.$('.graphic-widget img').attr('data-' + width);
            this.$('.graphic-widget img').attr('src', src);

            this.$('.graphic-widget').imageready(_.bind(function() {
                this.setReadyStatus();
            }, this));
        }
    });

    Adapt.register("graphic", Graphic);
});
define(function(require) {

    var ComponentView = require('coreViews/componentView');
    var Adapt = require('coreJS/adapt');

    var Text = ComponentView.extend({
        
        postRender: function() {
            this.setReadyStatus();

            // Check if instruction or body is set, otherwise force completion
            var cssSelector = this.$('.component-instruction').length > 0 ? '.component-instruction' 
                : (this.$('.component-body').length > 0 ? '.component-body' : null);

            if (!cssSelector) {
                this.setCompletionStatus();
            } else {
                this.model.set('cssSelector', cssSelector);
                this.$(cssSelector).on('inview', _.bind(this.inview, this));
            }
        },


        inview: function(event, visible, visiblePartX, visiblePartY) {
            if (visible) {
                if (visiblePartY === 'top') {
                    this._isVisibleTop = true;
                } else if (visiblePartY === 'bottom') {
                    this._isVisibleBottom = true;
                } else {
                    this._isVisibleTop = true;
                    this._isVisibleBottom = true;
                }

                if (this._isVisibleTop && this._isVisibleBottom) {                   
                    this.$(this.model.get('cssSelector')).off('inview');
                    this.setCompletionStatus();
                }
            }
        }
        
    });
    
    Adapt.register("text", Text);
    
});
Пример #7
0
define(function(require) {
  var ComponentView = require('coreViews/componentView');
  var Adapt = require('coreJS/adapt');

  var Blank = ComponentView.extend({

    postRender: function() {
      this.setReadyStatus();
      this.listenTo(this.model, 'change:_isComplete', this.removeInviewListener);
      this.$('.component-inner').on('inview', _.bind(this.inview, this));
    },

    inview: function(event, visible, visiblePartX, visiblePartY) {
        if (visible) {
            if (visiblePartY === 'top') {
                this._isVisibleTop = true;
            } else if (visiblePartY === 'bottom') {
                this._isVisibleBottom = true;
            } else {
                this._isVisibleTop = true;
                this._isVisibleBottom = true;
            }

            if (this._isVisibleTop && this._isVisibleBottom) {
                this.setCompletionStatus();
            }
            
        }
    },

    removeInviewListener: function(model, changeAttribute) {
      if (changeAttribute) {
        this.$('.component-inner').off('inview');
      }
    },

    remove: function() {
      this.$('.component-inner').off('inview');
      Backbone.View.prototype.remove.apply(this, arguments);
    }

  });

  Adapt.register("blank", Blank);

});
Пример #8
0
define(function(require) {

    var ComponentView = require('coreViews/componentView');
    var Adapt = require('coreJS/adapt');

    var Notepad = ComponentView.extend({
        events: {
            'click button': 'onClearButtonClicked',
            'blur textarea': 'storeUserAnswer'
        },

        postRender: function() {
            this.setReadyStatus();
            if (this.model.get('userInput')) this.resetUserAnswer();
            this.$('.notepad-widget').on('inview', _.bind(this.inview, this));
        },

        storeUserAnswer: function() {
            this.model.set('userInput', this.$('textarea').val());
        },

        resetUserAnswer: function() {
            this.$('textarea').val(this.model.get('userInput'));
            this.forceFixedPositionFakeScroll();
        },

        forceFixedPositionFakeScroll: function() {
            if (Modernizr.touch) {
                _.defer(function() {
                    window.scrollTo(document.body.scrollLeft, document.body.scrollTop);
                });
            }
        },

        onClearButtonClicked: function() {
            this.$('.textinput-item-textbox').val('');
            this.storeUserAnswer();
        },

        inview: function(event, visible, visiblePartX, visiblePartY) {
            if (visible) {
                if (visiblePartY === 'top') {
                    this._isVisibleTop = true;
                } else if (visiblePartY === 'bottom') {
                    this._isVisibleBottom = true;
                } else {
                    this._isVisibleTop = true;
                    this._isVisibleBottom = true;
                }

                if (this._isVisibleTop && this._isVisibleBottom) {
                    this.$(this.model.get('.notepad-widget')).off('inview');
                    this.setCompletionStatus();
                }
            }
        }

    });

    Adapt.register('notepad', Notepad);

    return Notepad;

});
Пример #9
0
define(function(require) {

    var ComponentView = require('coreViews/componentView');
    var Adapt = require('coreJS/adapt');

    var Text = ComponentView.extend({

        preRender: function() {
            // Checks to see if the text should be reset on revisit
            this.checkIfResetOnRevisit();
        },

        postRender: function() {
            this.setReadyStatus();

            // Check if instruction or title or body is set, otherwise force completion
            var cssSelector = this.$('.component-instruction').length > 0
                ? '.component-instruction'
                : (this.$('.component-title').length > 0 
                ? '.component-title' 
                : (this.$('.component-body').length > 0 
                ? '.component-body' 
                : null));

            if (!cssSelector) {
                this.setCompletionStatus();
            } else {
                this.model.set('cssSelector', cssSelector);
                this.$(cssSelector).on('inview', _.bind(this.inview, this));
            }
        },

        // Used to check if the text should reset on revisit
        checkIfResetOnRevisit: function() {
            var isResetOnRevisit = this.model.get('_isResetOnRevisit');

            // If reset is enabled set defaults
            if (isResetOnRevisit) {
                this.model.reset(isResetOnRevisit);
            }
        },

        inview: function(event, visible, visiblePartX, visiblePartY) {
            if (visible) {
                if (visiblePartY === 'top') {
                    this._isVisibleTop = true;
                } else if (visiblePartY === 'bottom') {
                    this._isVisibleBottom = true;
                } else {
                    this._isVisibleTop = true;
                    this._isVisibleBottom = true;
                }

                if (this._isVisibleTop && this._isVisibleBottom) {
                    this.$(this.model.get('cssSelector')).off('inview');
                    this.setCompletionStatus();
                }
            }
        }

    });

    Adapt.register('text', Text);

    return Text;

});
define(function(require) {

    var ComponentView = require("coreViews/componentView");
    var Adapt = require("coreJS/adapt");

    var Narrative = ComponentView.extend({
        
        events: function () {
            return Adapt.device.touch == true ? {
                'touchstart .narrative-slider' : 'navigateTouch',
                'touchstart .narrative-popup-open' : 'openNarrative',
                'click .narrative-popup-close' : 'closeNarrative',
                'click .narrative-controls' : 'navigateClick',
            }:{
                'click .narrative-controls' : 'navigateClick',
                'click .narrative-popup-open' : 'openNarrative',
                'click .narrative-popup-close' : 'closeNarrative'
            }
        },

        preRender: function () {
            this.listenTo(Adapt, 'pageView:ready', this.setupNarrative, this);
            this.listenTo(Adapt, 'device:changed', this.reRender, this);
            this.listenTo(Adapt, 'device:resize', this.resizeControl, this);
            this.setDeviceSize();
        },

        setDeviceSize: function() {
            if (Adapt.device.screenSize === 'large') {
                this.$el.addClass('desktop').removeClass('mobile');
                this.model.set('_isDesktop', true);
            } else {
                this.$el.addClass('mobile').removeClass('desktop');
                this.model.set('_isDesktop', false)
            }
        },

        postRender: function() {
            this.$('.narrative-slider').imageready(_.bind(function(){
                this.setReadyStatus();
            }, this));
        }, 

        setupNarrative: function() {
            this.setDeviceSize();
            var slideCount = this.$('.narrative-slider-graphic', this.$el).length;
            this.model.set('_itemCount', slideCount);
            this.calculateWidths();

            this.model.set('_active', true);

            if (this.model.get('_stage')) {
                this.setStage(this.model.get('_stage'));
            } else {
                this.setStage(0);
            }
        },

        calculateWidths: function() {
            var slideWidth = this.$('.narrative-slide-container').width();
            var slideCount = this.model.get('_itemCount');
            var extraMargin = parseInt(this.$('.narrative-slider-graphic').css('margin-right'));

            this.$('.narrative-slider-graphic').width(slideWidth)
            this.$('.narrative-slider').width((slideWidth + extraMargin) * slideCount);

            var stage = this.model.get('_stage');
            var margin = -(stage * slideWidth);

            this.$('.narrative-slider').css('margin-left', margin);
        },

        resizeControl: function() {
            this.setDeviceSize();
            this.calculateWidths();
            this.evaluateNavigation();
        },

        reRender: function() {
            if (this.model.get('_wasHotgraphic') && Adapt.device.screenSize == 'large') {
                this.replaceWithHotgraphic();
            }
        },

        replaceWithHotgraphic: function () {
            var Hotgraphic = require('components/adapt-contrib-hotgraphic/js/adapt-contrib-hotgraphic');
            var model = this.prepareHotgraphicModel();
            var newHotgraphic = new Hotgraphic({model:model, $parent: this.options.$parent});
            this.options.$parent.append(newHotgraphic.$el);
            Adapt.trigger('device:resize');
            this.remove();
        },

        prepareHotgraphicModel: function() {
          var model = this.model;
          model.set('_component', 'hotgraphic');
          model.set('body', model.get('originalBody'));
          return model;
        },

        navigateClick: function (event) {
            event.preventDefault();
            if (!this.model.get('_active')) return;

            var extraMargin = parseInt(this.$('.narrative-slider-graphic').css('margin-right'));
            var movementSize = this.$('.narrative-slide-container').width() + extraMargin;

            var stage = this.model.get('_stage');
            var itemCount = this.model.get('_itemCount');

            if ($(event.currentTarget).hasClass('narrative-control-right')) {
                this.navigateRight(stage, itemCount, movementSize);
            }
            if ($(event.currentTarget).hasClass('narrative-control-left')) {
                this.navigateLeft(stage, movementSize);
            }
        },

        navigateRight: function(stage, itemCount, movementSize) {
            if (stage < itemCount - 1) {
                stage++;
                this.$('.narrative-slider').stop().animate({'margin-left': - (movementSize * stage)});
                if (this.model.get('_isDesktop')) {
                    this.$('.narrative-slider-graphic').eq(stage).addClass('visited');
                }

                this.setStage(stage);
            }            
        },

        navigateLeft: function(stage, movementSize) {
            if (stage > 0) {
                stage--;
                this.$('.narrative-slider').stop().animate({'margin-left': - (movementSize * stage)});

                this.setStage(stage);
            }            
        },

        setStage: function(stage) {
            this.model.set('_stage', stage);

            // Set the visited attribute
            var currentItem = this.model.get('items')[stage];
            currentItem.visited = true;

            this.$('.narrative-progress').removeClass('selected').eq(stage).addClass('selected');
            this.$('.narrative-slider-graphic').children('.controls').attr('tabindex', -1);
            this.$('.narrative-slider-graphic').eq(stage).children('.controls').attr('tabindex', 0);
            this.$('.narrative-content-item').addClass('narrative-hidden').eq(stage).removeClass('narrative-hidden');
            this.$('.narrative-strapline-title').addClass('narrative-hidden').eq(stage).removeClass('narrative-hidden');

            this.evaluateNavigation();
            this.evaluateCompletion();
        },

        navigateSwipe: function(el, stage) {
            var extraMargin = parseInt(this.$('.narrative-slider-graphic').css('margin-right'));
            var strapLineSize = this.$('.narrative-strapline-title').width();
            var movementSize = this.$('.narrative-slide-container').width() + extraMargin;

            $('.narrative-slider', el).animate({'margin-left': - (movementSize * stage)});

            if (this.model.get('_isDesktop')) {
                this.$('.narrative-slider-graphic').eq(stage).addClass('visited');
            }

            this.setStage(stage);
        },

        navigateTouch: function(event) {
            event.preventDefault();
            if (!this.model.get('_active')) return;

            var that = this;
            var xOrigPos = event.originalEvent.touches[0]['pageX'];
            var startPos = parseInt(this.$('.narrative-slider').css('margin-left'));
            var stage = this.model.get('_stage');
            var narrativeSize = this.model.get('_itemCount');
            var move;
            var xPos;
            var onFirst = (stage == 0) ? true : false;
            var onLast = (stage == narrativeSize - 1) ? true : false;
            var swipeLeft = false;
            var swipeRight = false;

            this.$('.narrative-slider').on('touchmove', _.bind(function(event) {
                event.preventDefault();
                xPos = event.originalEvent.touches[0]['pageX'];
                swipeLeft = (xOrigPos > xPos) ? true : false;
                swipeRight = (xOrigPos < xPos) ? true : false;

                // Ensure the user does not scroll beyond the bounds
                if (onFirst && swipeRight || onLast && swipeLeft) return;
                
                if (swipeRight && !onLast || swipeLeft && !onFirst) {
                    move = (xPos + startPos) - xOrigPos;  
                }
                else {
                    move = (xPos - xOrigPos)/4 + (startPos);
                }

                this.$('.narrative-slider').css('margin-left', move);
            }, this));

            this.$('.narrative-slider').one('touchend', _.bind(function (event) {
                $('.narrative-slider', that.$el).unbind('touchmove');

                if (swipeRight || swipeLeft) {
                    if (swipeLeft && !onLast) stage++;
                    if (swipeRight && !onFirst) stage--;
                } else {
                    return;
                }
                
                that.navigateSwipe(that.$el, stage);
            }, this));
        },

        evaluateNavigation: function() {
            var currentStage = this.model.get('_stage');
            var itemCount = this.model.get('_itemCount');

            if (currentStage == 0) {
                this.$('.narrative-control-left').addClass('narrative-hidden');

                if (itemCount > 1) {
                    this.$('.narrative-control-right').removeClass('narrative-hidden');
                }
            } else {
                this.$('.narrative-control-left').removeClass('narrative-hidden');

                if (currentStage == itemCount - 1) {
                    this.$('.narrative-control-right').addClass('narrative-hidden');
                } else {
                    this.$('.narrative-control-right').removeClass('narrative-hidden');
                }
            }

        },

        getVisitedItems: function() {
          return _.filter(this.model.get('items'), function(item) {
            return item.visited;
          });
        },

        evaluateCompletion: function() {
            if (this.getVisitedItems().length == this.model.get('items').length) {
                this.setCompletionStatus();
            }
        },

        openNarrative: function (event) {
            event.preventDefault();
            this.model.set('_active', false);

            var outerMargin = parseFloat(this.$('.narrative-popup-inner').css('margin'));
            var innerPadding = parseFloat(this.$('.narrative-popup-inner').css('padding'));

            this.$('.narrative-slider-graphic').eq(this.model.get('_stage')).addClass('visited');
            this.$('.narrative-popup-toolbar-title').addClass('narrative-hidden').eq(this.model.get('_stage')).removeClass('narrative-hidden');
            this.$('.narrative-popup-content').addClass('narrative-hidden').eq(this.model.get('_stage')).removeClass('narrative-hidden');
            this.$('.narrative-popup-inner').css('height', $(window).height() - (outerMargin * 2) - (innerPadding * 2));
            this.$('.narrative-popup').removeClass('narrative-hidden');

            var toolBarHeight = this.$('.narrative-toolbar').height();
            
            this.$('.narrative-popup-content').css('height', (this.$('.narrative-popup-inner').height() - toolBarHeight));
        },

        closeNarrative: function (event) {
            event.preventDefault();
            this.model.set('_active', true);

            this.$('.narrative-popup-close').blur();
            this.$('.narrative-popup').addClass('narrative-hidden');
            
            this.evaluateCompletion();
        }
    });
    
    Adapt.register("narrative", Narrative);
    
    return Narrative;

});
define(function(require) {

    var ComponentView = require("coreViews/componentView");
    var Adapt = require("coreJS/adapt");

    var Reveal = ComponentView.extend({
        
        events: function () {
            return Adapt.device.touch == true ? {
                'touchstart .reveal-widget-control':'clickReveal',
                'inview' : 'inview'
            }:{
                'click .reveal-widget-control':'clickReveal',
                'inview' : 'inview'
            }
        },

        preRender: function() {
            this.listenTo(Adapt, 'pageView:ready', this.setupReveal, this);
            this.listenTo(Adapt, 'device:resize', this.resizeControl, this);

            this.setDeviceSize();
        },

        setupReveal: function() {
            var direction = !this.model.get('_direction') ? "left" : this.model.get('_direction');

            // Initialise the directional arrows
            this.$('.reveal-widget-item').addClass('reveal-' + this.model.get('_direction'));
            this.$('.reveal-widget-control').addClass('reveal-' + direction);
            this.$('.reveal-widget-icon').addClass('icon-arrow-' + this.getOppositeDirection(direction));

            this.model.set('_direction', direction);
            this.model.set('_active', true);
            this.model.set('_revealed', false);

            this.setControlText(false);

            this.calculateWidths();
        },

        setControlText: function(isRevealed) {
            if (this.model.get('_control')) {
                if (!isRevealed && this.model.get('control').showText) {
                    this.$('.reveal-widget-control').attr('title', this.model.get('control').showText);
                }

                if (isRevealed && this.model.get('control').hideText) {
                    this.$('.reveal-widget-control').attr('title', this.model.get('control').hideText);
                }
            }
        },

        calculateWidths: function() {
            var direction = this.model.get('_direction');
            var imageWidth = this.$('.reveal-widget').width();
            var controlWidth = this.$('.reveal-widget-control').width();
            var margin = -imageWidth; 

            this.$('.reveal-widget-slider').css('width', 2 * imageWidth);
            
            if (this.model.get('_revealed')) {
                this.$('.reveal-widget-control').css(this.model.get('_direction'), imageWidth - controlWidth)
            }

            this.$('.reveal-widget-slider').css('margin-' + direction, margin);

            this.model.set('_scrollWidth', imageWidth);
            this.model.set('_controlWidth', controlWidth);
        },

        setDeviceSize: function() {
            if (Adapt.device.screenSize === 'large') {
                this.$el.addClass('desktop').removeClass('mobile');
                this.model.set('_isDesktop', true);
            } else {
                this.$el.addClass('mobile').removeClass('desktop');
                this.model.set('_isDesktop', false)
            }
        },

        resizeControl: function() {           
            var imageWidth = this.$('.reveal-widget').width();
            var controlWidth = this.$('.reveal-widget-control').width();
            var direction = this.model.get('_direction');


            if (this.model.get('_revealed')) {
                this.$('.reveal-widget-control').css(direction, imageWidth - controlWidth);
                this.$('.reveal-widget-slider').css('margin-left', (direction == 'left') ? 0 : - imageWidth);
            } else {
                this.$('.reveal-widget-slider').css('margin-left', (direction == 'left') ? imageWidth : 0);
            }

            this.$('.reveal-widget-slider').css('width', 2 * imageWidth);
            this.$('.reveal-widget-slider').css('margin-' + direction, -imageWidth);            

            this.model.set('_scrollWidth', imageWidth);
            this.model.set('_controlWidth', controlWidth);
        },

        postRender: function () {
            this.$('.reveal-widget').imageready(_.bind(function() {
                this.setReadyStatus();
            }, this));
        },
        
        getOppositeDirection: function(direction) {
            switch(direction) {
                case 'left':
                    oppositeDirection = 'right';
                    break;
                case 'right':
                    oppositeDirection = 'left';
                    break;
            }

            return oppositeDirection;
        },

        clickReveal: function (event) {
            event.preventDefault();

            var direction = this.model.get('_direction');
            var scrollWidth = this.model.get('_scrollWidth');
            var controlWidth = this.model.get('_controlWidth');
            var controlMovement = (!this.model.get('_revealed')) ? scrollWidth - controlWidth : scrollWidth; 
            var operator = !this.model.get('_revealed') ? '+=' : '-=';
            var controlAnimation = {}, sliderAnimation = {};
            
            // Define the animations and new icon styles
            if (!this.model.get('_revealed')) {
                this.model.set('_revealed', true);
                this.$('.reveal-widget').addClass('reveal-showing');

                controlAnimation[direction] = operator + controlMovement;
                classToAdd = 'icon-arrow-' + direction; 
                classToRemove = 'icon-arrow-' + this.getOppositeDirection(direction);

                sliderAnimation['margin-left'] = (direction == 'left') ? 0 : -scrollWidth;

                this.setCompletionStatus();
            } else {
                this.model.set('_revealed', false);
                this.$('.reveal-widget').removeClass('reveal-showing');

                controlAnimation[direction] = 0;
                classToAdd = 'icon-arrow-' + this.getOppositeDirection(direction);
                classToRemove = 'icon-arrow-' + direction;

                sliderAnimation['margin-left'] = (direction == 'left') ? controlMovement : 0
            }

            // Change the UI to handle the new state
            this.$('.reveal-widget-slider').animate(sliderAnimation);
            this.$('.reveal-widget-control').animate(controlAnimation);
            this.$('.reveal-widget-icon').removeClass(classToRemove).addClass(classToAdd);

            this.setControlText(this.model.get('_revealed'));
        }
    });
    
    Adapt.register("reveal", Reveal);
    
    return Reveal;
});
Пример #12
0
    ], function(require, StructureType, utils) {
    var ComponentView = require('coreViews/componentView');
    var Adapt = require('coreJS/adapt');


    var STrickleButton = ComponentView.extend({

        events: {
            "click .strickle-button-inner > *": "onClick",
            "inview": "onInview"
        },

        initialize: function() {
            this.$el.addClass("no-state");
            if (this.model.get("_strickle")._componentClasses)
                this.$el.addClass(this.model.get("_strickle")._componentClasses);
            ComponentView.prototype.initialize.apply(this);
            this.model.set("_isLocked", false);
            this.model.set("_isEnabled", false);
            switch (this.model.get("_strickle")._buttonType) {
            case "trickle":
                this.model.set("_isVisible", false, { pluginName: "blank" });
                break;
            }
            this.listenTo(this.model, "change:_isEnabled", this.onEnabledChange);
        },

        postRender: function() {
            switch (this.model.get("_strickle")._buttonType) {
            case "jump":
                this.model.set("_isLocked", false);
                this.model.set("_isEnabled", true);
                break;
            case "jump-lock":
                this.model.set("_isLocked", true);
                this.model.set("_isEnabled", true);
                break;
            case "inline-disable":
                var _isComplete = this.model.get("_isComplete");
                this.toggleDisabled(!_isComplete);
                break;
            default:
                var _isComplete = this.model.get("_isComplete");
                this.toggleDisabled(_isComplete);
            }
            
            this.setReadyStatus();
            this.setupEventListeners();
        },

        setupEventListeners: function() {
            this.listenTo(Adapt, "remove", this.onRemove);
            this.listenTo(this.model, "change:_isVisible", this.onVisibilityChange);
        },

        onClick: function() {
            if (!this.model.get("_isLocked")) {
                this.completeJump();
            } else {
                this.completeLock();
            }
        },

        completeJump: function() {
            switch (this.model.get("_strickle")._buttonType) {
            case "inline-jump": case "jump": case "jump-lock":
                this.scrollTo();
                break;
            }
        },

        completeLock: function() {
            this.toggleLock(false);

            this.model.get("_strickle")._isInteractionComplete = true;

            this.setCompletionStatus();

            switch (this.model.get("_strickle")._buttonType) {
            case "trickle":
                this.model.set("_isEnabled", false);
                this.model.set("_isVisible", false, { pluginName: "blank" });
                this.stopListening();
                break;
            case "inline-jump": case "jump": case  "jump-lock":
                this.model.set("_isEnabled", true);
                break;
            case "inline-disable":
                this.model.set("_isEnabled", false);
                this.stopListening();
                break;
            case "inline-hide":
                this.model.set("_isEnabled", false);
                this.model.set("_isVisible", false, { pluginName: "blank" });
                this.stopListening();
                break;
            }

            this.scrollTo();
        },

        onEnabledChange: function(model, value) {
            this.toggleDisabled(value);
        },

        toggleDisabled: function(bool) {
            if (bool === false) {
                this.$el.find(".strickle-button-inner > *").addClass("disabled").attr("disabled","disabled");
            } else {
                this.$el.find(".strickle-button-inner > *").removeClass("disabled").removeAttr("disabled");
            }
        },

        onInview: function(event, isInview) {
            if (this.model.get("_isComplete")) return;
            if (!this.model.get("_isEnabled") || !this.model.get("_isVisible")) return;

            this.toggleLock(isInview);
        },

        onVisibilityChange: function(model, value) {
            this.toggleLock(value);
        },

        toggleLock: function(bool) {
            if (this.model.get("_isLocked") === bool) return;
            switch(this.model.get("_strickle")._buttonType) {
            case "jump-lock": bool = true;
            }
            if (bool) {
                this.$el.find('.component-inner').addClass("locked");
                this.model.set("_isLocked", true);
                Adapt.trigger("strickle-button:locked", this);
            } else {
                this.$el.find('.component-inner').removeClass("locked");
                this.model.set("_isLocked", false);
                Adapt.trigger("strickle-button:unlocked", this);
            }
        },

        scrollTo: function() {

            if (this.model.get("_strickle")._autoScroll === false) return;
            var scrollTo = this.model.get("_strickle")._scrollTo;
            var duration = this.model.get("_strickle")._duration || 500;
            if (scrollTo === undefined) scrollTo = "@component +1";
            if (scrollTo.substr(0,1) == "@") {
                var descendantType = StructureType.fromString(this.model.get("_parentType"));
                var type = scrollTo.substr(0, _.indexOf(scrollTo, " "));
                var by = parseInt(scrollTo.substr(type.length));
                type = type.substr(1);

                var flatPageDescendantsJSON = this.model.get("_flatPageDescendantsJSON");
                var currentIndex = this.model.get("_index");

                var typeCount = {};
                for (var i = descendantType._level - 1, l = 0; i > l; i--) {
                    typeCount[StructureType.fromInt(i)._id] = -1;
                }

                for (var i = currentIndex +1, l = flatPageDescendantsJSON.length; i < l; i++) {
                    var item = flatPageDescendantsJSON[i];
                    if (!typeCount[item._type]) typeCount[item._type] = 0;
                    typeCount[item._type]++;
                    if (typeCount[type] >= by) {
                        if (!$("."+item._id).is(":visible")) {
                            by++;
                            continue;
                        }
                        return this.navigateToElement("." + item._id, duration);
                    }
                }
            } else if (scrollTo.substr(0,1) == ".") {
                this.navigateToElement(scrollTo, duration);
            } else {
                this.navigateToElement("." + scrollTo, duration);
            }

        },

        navigateToElement: function(to, duration) {
            _.defer(function() {
                Adapt.navigateToElement(to, {
                    duration: duration,
                    offset: {
                        top: -$('.navigation').height()
                    }
                }, false);
                if ($.fn.a11y_focus) $(to).a11y_focus();
            });
        }

    });

    Adapt.register("strickle-button", STrickleButton);

    return STrickleButton;
});
define(function(require) {

    var ComponentView = require("coreViews/componentView");
    var Adapt = require("coreJS/adapt");
    var Backbone = require('backbone');
    
    var Navigate = ComponentView.extend({
        events: {
            'click .navigate-icon':'navigateTo',
        },
        preRender: function() {
            this.listenTo(Adapt, 'device:changed', this.resizeImage);
        },

        postRender: function() {
            this.resizeImage(Adapt.device.screenSize);
            this.$('.component-widget').on('inview', _.bind(this.inview, this));
        },

        inview: function(event, visible, visiblePartX, visiblePartY) {
            if (visible) {
                if (visiblePartY === 'top') {
                    this._isVisibleTop = true;
                } else if (visiblePartY === 'bottom') {
                    this._isVisibleBottom = true;
                } else {
                    this._isVisibleTop = true;
                    this._isVisibleBottom = true;
                }

                if (this._isVisibleTop && this._isVisibleBottom) {
                    this.$('.component-widget').off('inview');
                    this.setCompletionStatus();
                }
                
            }
        },
        
        resizeImage: function(width) {
            //var src = this.$('.navigate-widget .navigate-icon img').attr('data-' + width);
            //this.$('.navigate-widget .navigate-icon img').attr('src', src);
            this.$('.navigate-icon').each(function( index ) {
                var src = $(this).find('img').attr('data-' + width);
                $(this).find('img').attr('src', src);
            }); 
            
            
            this.$('.navigate-widget .navigate-icon').imageready(_.bind(function() {
                this.setReadyStatus();
            }, this));
        },
        
        navigateTo: function(event) {
            event.preventDefault();
            var contentobject = $(event.currentTarget).data('to');
            Backbone.history.navigate('#/id/' + contentobject, true);
        }
    });

    Adapt.register("navigate", Navigate);
});
Пример #14
0
define(function(require) {

    var ComponentView = require('coreViews/componentView');
    var Adapt = require('coreJS/adapt');

    var TextPlus = ComponentView.extend({

        events: function () {
            return Adapt.device.touch == true ? {
                'touchstart .textPlus-popup-open' : 'openPopup',
                'click .textPlus-popup-open' : 'openPopup',
                'touchstart .textPlus-popup-close' : 'closePopup',
                'click .textPlus-popup-close' : 'closePopup'
            }:{
                'click .textPlus-popup-open' : 'openPopup',
                'click .textPlus-popup-close' : 'closePopup'
            }
        },

        preRender: function () {
            this.listenTo(Adapt, 'device:changed', this.setDeviceSize, this);
            this.listenTo(Adapt, 'device:changed', this.resizeImage);
            this.listenTo(Adapt, 'device:resize', this.setDeviceSize, this);
            this.checkIfResetOnRevisit();
            this.setDeviceSize();
        },

        // Used to check if the text should reset on revisit
        checkIfResetOnRevisit: function() {
            var isResetOnRevisit = this.model.get('_isResetOnRevisit');
            // If reset is enabled then state and model attributes should be reset to default.
            if (isResetOnRevisit) {
                this.model.set({
                    _isEnabled: true,
                    _isComplete: false
                });
            }
        },

        setDeviceSize: function() {
            if (Adapt.device.screenSize === 'small') {
                var charLimit = 100;
                var firstCharLimit = this.model.get('_maxBodyCharacters') || charLimit;
                var hasPopup = this.model.get('body') && this.model.get('body').length > firstCharLimit;
                if (hasPopup) {
                    this.model.set('body-short', this.model.get('body').substring(0, firstCharLimit) + '...');
                }
                this.model.set('_displayShortText', hasPopup);
                if (hasPopup) {
                    this.$('.textPlus-short').removeClass('textPlus-hidden');
                    this.$('.textPlus-long').addClass('textPlus-hidden');
                }
                return;
            }
            this.model.set('_displayShortText', false);
            this.$('.textPlus-short').addClass('textPlus-hidden');
            this.$('.textPlus-long').removeClass('textPlus-hidden');

        },

        postRender: function() {
            if(this.model.has('_graphic')){
                this.resizeImage(Adapt.device.screenSize);
            } else {
                this.setReadyStatus();
            }

            // If the screenSize is forcing popups, then completion will be set by
            // opening the popup. Otherwise...
            if (this.model.get('_displayShortText') === false) {
                // Check if instruction or body is set, otherwise force completion
                var cssSelector = this.$('.component-instruction').length > 0
                    ? '.component-instruction'
                    : (this.$('.component-body').length > 0 ? '.component-body' : null);

                if (!cssSelector) {
                    this.setCompletionStatus();
                } else {
                    this.model.set('cssSelector', cssSelector);
                    this.$(cssSelector).on('inview', _.bind(this.inview, this));
                    this.listenTo(this.model, 'change:_isComplete', this.removeInviewListener);
                }
            }
        },

        resizeImage: function(width) {
            var src = this.$('.textPlus-graphic-inner img').attr('data-' + width);
            this.$('.textPlus-graphic-inner img').attr('src', src);
            this.$('.textPlus-graphic-inner').imageready(_.bind(function() {
                this.setReadyStatus();
            }, this));
        },

        openPopup: function (event) {
            event.preventDefault();
            var outerMargin = parseFloat(this.$('.textPlus-popup-inner').css('margin'));
            var innerPadding = parseFloat(this.$('.textPlus-popup-inner').css('padding'));
            var toolBarHeight = this.$('.textPlus-toolbar').height();
            this.$('.textPlus-popup-content').removeClass('textPlus-hidden');
            this.$('.textPlus-popup-inner').css('height', $(window).height() - (outerMargin * 2) - (innerPadding * 2));
            this.$('.textPlus-popup').removeClass('textPlus-hidden');
            this.$('.textPlus-popup-content').css('height', (this.$('.textPlus-popup-inner').height() - toolBarHeight));
            this.setCompletionStatus();
            Adapt.trigger('popup:opened');
        },

        closePopup: function (event) {
            event.preventDefault();
            this.$('.textPlus-popup-close').blur();
            this.$('.textPlus-popup-content').addClass('textPlus-hidden');
            this.$('.textPlus-popup').addClass('textPlus-hidden');
            Adapt.trigger('popup:closed');
        },

        inview: function(event, visible, visiblePartX, visiblePartY) {
            if (visible) {
                if (visiblePartY === 'top') {
                    this._isVisibleTop = true;
                } else if (visiblePartY === 'bottom') {
                    this._isVisibleBottom = true;
                } else {
                    this._isVisibleTop = true;
                    this._isVisibleBottom = true;
                }
                if (this._isVisibleTop && this._isVisibleBottom) {
                    this.$(this.model.get('cssSelector')).off('inview');
                    this.setCompletionStatus();
                }
            }
        },

        removeInviewListener: function(model, changeAttribute) {
            if (changeAttribute) {
                this.$(this.model.get('cssSelector')).off('inview');
            }
        },

        remove: function() {
            this.$(this.model.get('cssSelector')).off('inview');
            Backbone.View.prototype.remove.apply(this, arguments);
        }

    });

    Adapt.register('textPlus', TextPlus);

    return TextPlus;

});
define(function(require) {

    var mep = require('components/adapt-contrib-media/js/mediaelement-and-player');
    require('components/adapt-contrib-media/js/mediaelement-and-player-accessible-captions');
    
    var ComponentView = require('coreViews/componentView');
    var Adapt = require('coreJS/adapt');

    var froogaloopAdded = false;

    var Media = ComponentView.extend({

        events: {
            "click .media-inline-transcript-button": "onToggleInlineTranscript"
        },

        preRender: function() {
            this.listenTo(Adapt, 'device:resize', this.onScreenSizeChanged);
            this.listenTo(Adapt, 'device:changed', this.onDeviceChanged);
            this.listenTo(Adapt, 'accessibility:toggle', this.onAccessibilityToggle);

            this.checkIfResetOnRevisit();
        },

        postRender: function() {
            this.setupPlayer();
        },


        setupPlayer: function() {
            if (!this.model.get('_playerOptions')) this.model.set('_playerOptions', {});

            var modelOptions = this.model.get('_playerOptions');

            if (modelOptions.pluginPath === undefined) modelOptions.pluginPath = 'assets/';
            if(modelOptions.features === undefined) {
                modelOptions.features = ['playpause','progress','current','duration'];
                if (this.model.get('_useClosedCaptions')) {
                    modelOptions.features.unshift('tracks');
                }
                if (this.model.get("_allowFullScreen") && !$("html").is(".ie9")) {
                    modelOptions.features.push('fullscreen');
                }
            }

            modelOptions.success = _.bind(this.onPlayerReady, this);

            if (this.model.get('_useClosedCaptions')) {
                modelOptions.startLanguage = this.model.get('_startLanguage') === undefined ? 'en' : this.model.get('_startLanguage');
            }

            var hasAccessibility = Adapt.config.has('_accessibility') && Adapt.config.get('_accessibility')._isActive
                ? true
                : false;

            if (hasAccessibility) {
                modelOptions.alwaysShowControls = true;
                modelOptions.hideVideoControlsOnLoad = false;
            }
            
            if (modelOptions.alwaysShowControls === undefined) {
                modelOptions.alwaysShowControls = false;
            }
            if (modelOptions.hideVideoControlsOnLoad === undefined) {
                modelOptions.hideVideoControlsOnLoad = true;
            }

            this.addMediaTypeClass();

            this.addThirdPartyFixes(modelOptions, _.bind(function createPlayer() {
                // create the player
                this.$('audio, video').mediaelementplayer(modelOptions);

                // We're streaming - set ready now, as success won't be called above
                try {
                    if (this.model.get('_media').source) {
                        this.$('.media-widget').addClass('external-source');
                    }
                } catch (e) {
                    console.log("ERROR! No _media property found in components.json for component " + this.model.get('_id'));
                } finally {
                    this.setReadyStatus();
                }
            }, this));
        },

        addMediaTypeClass: function() {
            var media = this.model.get("_media");
            if (media && media.type) {
                var typeClass = media.type.replace(/\//, "-");
                this.$(".media-widget").addClass(typeClass);
            }
        },

        addThirdPartyFixes: function(modelOptions, callback) {
            var media = this.model.get("_media");
            if (!media) return callback();
            
            switch (media.type) {
                case "video/vimeo":
                    modelOptions.alwaysShowControls = false;
                    modelOptions.hideVideoControlsOnLoad = true;
                    modelOptions.features = [];
                    if (froogaloopAdded) return callback();
                    Modernizr.load({
                        load: "assets/froogaloop.js", 
                        complete: function() {
                            froogaloopAdded = true;
                            callback();
                        }
                    }); 
                    break;
                default:
                    callback();
            }
        },

        setupEventListeners: function() {
            this.completionEvent = (!this.model.get('_setCompletionOn')) ? 'play' : this.model.get('_setCompletionOn');

            if (this.completionEvent !== 'inview') {
                this.mediaElement.addEventListener(this.completionEvent, _.bind(this.onCompletion, this));
            } else {
                this.$('.component-widget').on('inview', _.bind(this.inview, this));
            }
        },

        // Overrides the default play/pause functionality to stop accidental playing on touch devices
        setupPlayPauseToggle: function() {
            // bit sneaky, but we don't have a this.mediaElement.player ref on iOS devices
            var player = this.mediaElement.player;

            if (!player) {
                console.log("Media.setupPlayPauseToggle: OOPS! there's no player reference.");
                return;
            }

            // stop the player dealing with this, we'll do it ourselves
            player.options.clickToPlayPause = false;

            // play on 'big button' click
            $('.mejs-overlay-button',this.$el).click(_.bind(function(event) {
                player.play();
            }, this));

            // pause on player click
            $('.mejs-mediaelement',this.$el).click(_.bind(function(event) {
                var isPaused = player.media.paused;
                if(!isPaused) player.pause();
            }, this));
        },

        checkIfResetOnRevisit: function() {
            var isResetOnRevisit = this.model.get('_isResetOnRevisit');

            // If reset is enabled set defaults
            if (isResetOnRevisit) {
                this.model.reset(isResetOnRevisit);
            }
        },

        inview: function(event, visible, visiblePartX, visiblePartY) {
            if (visible) {
                if (visiblePartY === 'top') {
                    this._isVisibleTop = true;
                } else if (visiblePartY === 'bottom') {
                    this._isVisibleBottom = true;
                } else {
                    this._isVisibleTop = true;
                    this._isVisibleBottom = true;
                }

                if (this._isVisibleTop && this._isVisibleBottom) {
                    this.$('.component-inner').off('inview');
                    this.setCompletionStatus();
                }
            }
        },

        remove: function() {
            if ($("html").is(".ie8")) {
                var obj = this.$("object")[0];
                if (obj) {
                    obj.style.display = "none";
                }
            }
            if (this.mediaElement) {
                $(this.mediaElement.pluginElement).remove();
                delete this.mediaElement;
            }
            ComponentView.prototype.remove.call(this);
        },

        onCompletion: function() {
            this.setCompletionStatus();

            // removeEventListener needs to pass in the method to remove the event in firefox and IE10
            this.mediaElement.removeEventListener(this.completionEvent, this.onCompletion);
        },

        onDeviceChanged: function() {
            if (this.model.get('_media').source) {
                this.$('.mejs-container').width(this.$('.component-widget').width());
            }
        },

        onPlayerReady: function (mediaElement, domObject) {
            this.mediaElement = mediaElement;

            if (!this.mediaElement.player) {
                this.mediaElement.player =  mejs.players[this.$('.mejs-container').attr('id')];
            }

            var hasTouch = mejs.MediaFeatures.hasTouch;
            if (hasTouch) {
                this.setupPlayPauseToggle();
            }

            this.setReadyStatus();
            this.setupEventListeners();
        },

        onScreenSizeChanged: function() {
            this.$('audio, video').width(this.$('.component-widget').width());
        },

        onAccessibilityToggle: function() {
           this.showControls();
        },

        onToggleInlineTranscript: function(event) {
            if (event) event.preventDefault();
            var $transcriptBodyContainer = this.$(".media-inline-transcript-body-container");
            var $button = this.$(".media-inline-transcript-button");

            if ($transcriptBodyContainer.hasClass("inline-transcript-open")) {
                $transcriptBodyContainer.slideUp(function() {
                    $(window).resize();
                });
                $transcriptBodyContainer.removeClass("inline-transcript-open");
                $button.html(this.model.get("_transcript").inlineTranscriptButton);
            } else {
                $transcriptBodyContainer.slideDown(function() {
                    $(window).resize();
                }).a11y_focus();
                $transcriptBodyContainer.addClass("inline-transcript-open");
                $button.html(this.model.get("_transcript").inlineTranscriptCloseButton);
                if (this.model.get('_transcript')._setCompletionOnView !== false) {
                    this.setCompletionStatus();
                }
            }
        },

        showControls: function() {
            var hasAccessibility = Adapt.config.has('_accessibility') && Adapt.config.get('_accessibility')._isActive
                ? true
                : false;

            if (hasAccessibility) {
                if (!this.mediaElement.player) return;

                var player = this.mediaElement.player;

                player.options.alwaysShowControls = true;
                player.options.hideVideoControlsOnLoad = false;
                player.enableControls();
                player.showControls();

                this.$('.mejs-playpause-button button').attr({
                    "role": "button"
                });
                var screenReaderVideoTagFix = $("<div role='region' aria-label='.'>");
                this.$('.mejs-playpause-button').prepend(screenReaderVideoTagFix);

                this.$('.mejs-time, .mejs-time-rail').attr({
                    "aria-hidden": "true"
                });
            }
        }
    });

    Adapt.register('media', Media);

    return Media;

});
define(function(require) {

  var ComponentView = require("coreViews/componentView");
  var Adapt = require('coreJS/adapt');

  // sliding tile object
  function SlidingTile (options) {
    return _.extend({
        className: 'slidingPuzzle-tile',
        src: null, // the image to use in the source
        el: null,
        x: 0, // actual x position on puzzle
        y: 0, // actual y position on puzzle
        srcX: 0, // x position on image of this tile
        srcY: 0, // y position on image of this tile
        width: 0,
        height: 0,
        column: -1, // correct column of this tile
        row: -1, // correct row of this tile
        visible: true, // if true, draw this tile
        target: {},

        /**
         * returns a comma separated rect spec suitable for
         * use in a css clip style
         */
        getRect: function () {
          return [
            this.srcY + 'px',
            this.srcX + this.width + 'px',
            this.srcY + this.height + 'px',
            this.srcX + 'px'
          ].join(',');
        },

        /**
         * builds the img element for this tile and returns it
         * ready for use in $.append
         */
        renderElement: function (container) {
          // remove img if already rendered
          if (this.el) {
            this.el.remove();
          }

          // create image element
          var img = $('<img>');
          img.attr('src', this.src);
          img.css('clip', 'rect(' + this.getRect() + ')');
          img.css('left', -this.srcX + 'px');
          img.css('top', -this.srcY + 'px');

          // create element (the tile)
          var el = $('<div>');
          el.attr('class', this.className);
          el.css('left', this.x);
          el.css('top', this.y);
          this.el = el;

          this.el.append(img);
          this.setVisible(this.visible);

          container.append(this.el);

          return this.el;
        },

        /**
         * toggles display of the tile
         */
        setVisible: function (visible) {
          this.visible = visible;
          if (this.el) {
            this.el.css('opacity', (this.visible ? '100' : '0'));
          }
        },

        /**
         * sets the target x and y for this tile
         *
         */
        setTarget: function (target) {
          this.target.x = target.x;
          this.target.y = target.y;

          // immediately set to position
          if (this.el) {
            this.el.css('left', this.target.x);
            this.el.css('top', this.target.y);
          }
        }
      }, options);
  }

  var SlidingPuzzle = ComponentView.extend({

    events: {
      'click .slidingPuzzle-puzzle': 'attemptMove',
      'click .slidingPuzzle-widget .button.reset': 'onResetClicked',
      'click .slidingPuzzle-widget .button.model': 'onShowSolutionClicked'
    },

    _columns: 1,

    _rows: 1,

    _tiles: [],

    img: false,

    context: false,

    _debounceTime: 250,

    _debouncing: false,

    preRender: function () {
      this.listenTo(Adapt, 'device:changed', this.resizePuzzle);
    },

    postRender: function () {
      this.resizePuzzle(Adapt.device.screenSize);
    },

    resizePuzzle: function (width) {
      var img = this.$('.slidingPuzzle-widget img');
      img.attr('src', img.attr('data-' + width));

      this.$('.slidingPuzzle-widget').imageready(_.bind(function (imgEl) {
        this.resetPuzzle(imgEl);
      }, this, img.get(0)));
    },

    resetPuzzle: function (img) {
      var graphic = this.model.get('graphic');
      var puzzle = this.$('.slidingPuzzle-puzzle');
      puzzle.html('');
      this.img = img;

      // set up the puzzle board
      puzzle.css('width', this.img.width + 'px');
      puzzle.css('height', this.img.height + 'px');
      this._columns = this.model.get('dimension') || this._columns;
      this._rows = this.model.get('dimension') || this._rows;
      this._tiles = this.fetchTiles(this.img, this._columns, this._rows);

      // show/hide buttons
      this.$('.slidingPuzzle-widget .button.reset').hide();
      if (this.model.get('allowSkip')) {
        this.$('.slidingPuzzle-widget .button.model').show();
      }

      this.renderTiles(puzzle);

      // get debounce time from transition time
      var transTime = parseFloat(this.$('.slidingPuzzle-tile').first().css('transition-duration'), 10);
      this._debounceTime = transTime ? transTime * 1000 : this._debounceTime;


      this.setReadyStatus();
    },

    showAll: function () {
      for (var row = 0; row < this._tiles.length; ++row) {
        for (var col = 0; col < this._tiles[row].length; ++col) {
          var tile = this._tiles[row][col];
          tile.setTarget({x:tile.srcX, y:tile.srcY});
          tile.setVisible(true);
        }
      }
    },

    renderTiles: function (el) {
      for (var row = 0; row < this._tiles.length; ++row) {
        for (var col = 0; col < this._tiles[row].length; ++col) {
          this._tiles[col][row].renderElement(el);
        }
      }
    },

    fetchTiles: function (img, dimensionX, dimensionY) {
      var tiles = [];
      var tileWidth = Math.floor(img.width / (dimensionX || 1));
      var tileHeight = Math.floor(img.height / (dimensionY || 1));
      var col = 0;
      var row = 0;
      for (col = 0; col < dimensionX; ++col) {
        for (row = 0; row < dimensionY; ++row) {
          tiles.push(new SlidingTile({ src: img.src, srcX: col*tileWidth, srcY: row*tileHeight, width: tileWidth, height: tileHeight, column: col, row: row }));
        }
      }

      var randomTiles = [];
      var index = 0;
      while (tiles.length > 0) {
        if (index % dimensionX === 0) {
          randomTiles.push([]);
        }
        var t = tiles.splice(Math.floor(Math.random()*tiles.length), 1)[0];
        col = index % dimensionX;
        row = Math.floor(index / dimensionY);
        if (tiles.length !== 0) {
          t.x = col * t.width;
          t.y = row * t.height;
          t.setVisible(true);
        } else {
          t.setVisible(false); // make the last tile invisible
        }
        randomTiles[row].push(t);
        ++index;
      }

      return randomTiles;
    },

    attemptMove: function (e) {
      if (this._debouncing) {
        return;
      }
      var puzzlePos = this.$('.slidingPuzzle-puzzle').offset();
      var mouseX = e.pageX - Math.round(puzzlePos.left);
      var mouseY = e.pageY - Math.round(puzzlePos.top);
      var cellX = Math.floor(mouseX/this.img.width * this._columns);
      var cellY = Math.floor(mouseY/this.img.height * this._rows);
      if (this._tiles[cellY][cellX]) {
        var freeCell = false;
        // check if we can move to any postion
        // up or down?
        for (var row = 0; row < this._rows; ++row) {
          if (row === cellY) { // ignore self
            continue;
          }

          if (!this._tiles[row][cellX].visible) {
            // boom, found a free cell
            freeCell = {col: cellX, row: row};
            break
          }
        }

        if (!freeCell) {
          // check left and right
          for (var col = 0; col < this._columns; ++col) {
            if (col === cellX) { // ignore self
              continue;
            }

            if (!this._tiles[cellY][col].visible) {
              // boom, found a free cell
              freeCell = {col: col, row: cellY};
              break;
            }
          }
        }

        if (freeCell) {
          var tile = false;
          // move multiple tiles if we can
          if (freeCell.col === cellX) { // same column
            var direction = freeCell.row > cellY ? -1 : 1;
            for (; freeCell.row >= 0 && freeCell.row != this._rows; freeCell.row += direction) {
              var currentRow = freeCell.row + direction;
              tile = this._tiles[currentRow][cellX];
              tile.setTarget({x: cellX * tile.width, y: freeCell.row * tile.height});
              // swap tiles
              this.swapTiles(currentRow, cellX, freeCell.row, cellX);
              // stop when we reach the clicked cell
              if (currentRow === cellY) {
                break;
              }
            }
          } else { // same row
            var direction = freeCell.col > cellX ? -1 : 1;
            for (; freeCell.col >= 0 && freeCell.col != this._columns; freeCell.col += direction) {
              var currentCol = freeCell.col + direction;
              tile = this._tiles[cellY][currentCol];
              tile.setTarget({x: freeCell.col * tile.width, y: cellY * tile.height});
              // swap tiles
              this.swapTiles(cellY, currentCol, cellY, freeCell.col);
              // stop when we reach the clicked cell
              if ((freeCell.col + direction) === cellX) {
                break;
              }
            }
          }
          this.debounce();
        }

        // assess completion!
        if (this.checkPuzzle()) {
          // w00t! player got skillz
          this.solve();
        }
      }
    },

    debounce: function () {
      this._debouncing = true;
      setTimeout(_.bind(function () { this._debouncing = false; }, this), this._debounceTime);
    },

    solve: function () {
      this.$('.slidingPuzzle-widget .button.model').hide();
      this.$('.slidingPuzzle-widget .button.reset').show();
      this.showAll();
      this.puzzleComplete();
    },

    onResetClicked: function (e) {
      e.preventDefault();
      this.resetPuzzle(this.img);
    },

    onShowSolutionClicked: function (e) {
      e.preventDefault();
      this.solve();
    },

    swapTiles: function (row1, col1, row2, col2) {
      var temp = this._tiles[row1][col1];
      this._tiles[row1][col1] = this._tiles[row2][col2];
      this._tiles[row2][col2] = temp;
    },

    checkPuzzle: function () {
      for (var row = 0; row < this._tiles.length; ++row) {
        for (var col = 0; col < this._tiles[row].length; ++col) {
          var tile = this._tiles[row][col];
          if (tile.column !== col || tile.row !== row) {
            // not in order
            return false;
          }
        }
      }
      return true;
    },

    puzzleComplete: function () {
      this.setCompletionStatus();
    }

  });

  Adapt.register('slidingPuzzle', SlidingPuzzle);

  return SlidingPuzzle;

});
Пример #17
0
define(function(require) {

	var ComponentView = require('coreViews/componentView');
	var Adapt = require('coreJS/adapt');

	var Tabs = ComponentView.extend({

		events: {
			'click .tab-item': 'onTabItemClicked'
		},
		
		preRender: function() {
		},

		postRender: function() {
			this.setReadyStatus();
			this.setLayout();
			this.listenTo(Adapt, 'device:resize', this.setLayout);
			this.showContentItemAtIndex(0);
			this.setTabSelectedAtIndex(0);
		},

		setLayout: function() {
			this.$el.removeClass("tab-layout-left tab-layout-top");
			if (Adapt.device.screenSize == 'large') {
				var tabLayout = this.model.get('_tabLayout');
				this.$el.addClass("tab-layout-" + tabLayout);
				if (tabLayout === 'top') {
					this.setTabLayoutTop();
				} else if (tabLayout === 'left') {
					//this.setTabLayoutLeft();
				}                
			} else {
				//this.$el.addClass("tab-layout-left");
				//this.setTabLayoutLeft();
			}        	
		},

		setTabLayoutTop: function() {
			var itemsLength = this.model.get('_items').length;
			var itemWidth = 100 / itemsLength;
			this.$('.tab-item').css({
				width: itemWidth + '%'
			});
			this.$('.tabs-content-items').css({
				height: 'auto'
			});
		},

		setTabLayoutLeft: function() {
			var height = this.$('.tabs-tab-items').height();
			this.$('.tabs-content-items').css({
				height: height + 'px'
			});
			this.$('.tab-item').css({
				width: 'auto'
			});
		},

		onTabItemClicked: function(event) {
			event.preventDefault();
			var index = $(event.currentTarget).index();
			this.showContentItemAtIndex(index);
			this.setTabSelectedAtIndex(index);
			this.setVisited($(event.currentTarget).index());
		},

		showContentItemAtIndex: function(index) {
			this.$('.tab-content').velocity({
				opacity: 0,
				translateY: '20px'
			}, {
				duration: 200,
				display: 'none'
			});
			this.$('.tab-content-item-title-image').velocity({
				scaleX: 0.9,
				scaleY: 0.9
			}, {
				duration: 200
			});

			var contentItem = this.$('.tab-content').eq(index);
			var $contentItem = $(contentItem);
			$contentItem.velocity({
				opacity: 1,
				translateY: '0'
			}, {
				duration: 400,
				display: 'block'
			});
			$contentItem.find('.tab-content-item-title-image').velocity({
				scaleX: 1,
				scaleY: 1
			}, {
				duration: 800
			});
		},

		setTabSelectedAtIndex: function(index) {
			this.$('.tab-item').removeClass('selected');
			this.$('.tab-item').eq(index).addClass('selected visited');
			this.setVisited($(event.currentTarget).index());
		},

		setVisited: function(index) {
			var item = this.model.get('_items')[index];
			item._isVisited = true;
			this.checkCompletionStatus();
		},

		getVisitedItems: function() {
			return _.filter(this.model.get('_items'), function(item) {
				return item._isVisited;
			});
		},

		checkCompletionStatus: function() {
			if (this.getVisitedItems().length == this.model.get('_items').length) {
				this.setCompletionStatus();
			}
		}
		
	});
	
	Adapt.register("tabs", Tabs);

	return Tabs;
	
});
define(function(require) {

    var ComponentView = require("coreViews/componentView");
    var Adapt = require("coreJS/adapt");

    var Reveal = ComponentView.extend({

        events: function () {
            return Adapt.device.touch == true ? {
                'touchstart .reveal-widget-control':  'clickReveal',
                'click .reveal-widget-control':       'clickReveal',
                'inview':                             'inview',
                'touchstart .reveal-popup-open':      'openPopup'
            } : {
                'click .reveal-widget-control':       'clickReveal',
                'inview':                             'inview',
                'click .reveal-popup-open' :          'openPopup'
            }
        },

        preRender: function() {
            this.listenTo(Adapt, 'pageView:ready', this.setupReveal, this);
            this.listenTo(Adapt, 'device:resize', this.resizeControl, this);
            this.listenTo(Adapt, 'device:changed', this.setDeviceSize, this);

            this.setDeviceSize();
        },

        setupReveal: function() {
            var direction = !this.model.get('_direction') ? "left" : this.model.get('_direction');

            // Initialise the directional arrows
            this.$('.reveal-widget-item').addClass('reveal-' + this.model.get('_direction'));
            this.$('.reveal-widget-control').addClass('reveal-' + direction);
            this.$('.reveal-image').addClass('reveal-' + direction);
            this.$('div.reveal-widget-item-text').addClass('reveal-' + direction);

            this.$('div.reveal-widget-item-text-body').addClass('reveal-' + direction);
            this.$('.reveal-widget-icon').addClass('icon-controls-' + this.getOppositeDirection(direction));

            this.model.set('_direction', direction);
            this.model.set('_active', true);
            this.model.set('_revealed', false);

            this.setControlText(false);

            this.calculateWidths();
        },

        setControlText: function(isRevealed) {
            if (this.model.get('_control')) {
                if (!isRevealed && this.model.get('control').showText) {
                    this.$('.reveal-widget-control').attr('title', this.model.get('control').showText);
                }

                if (isRevealed && this.model.get('control').hideText) {
                    this.$('.reveal-widget-control').attr('title', this.model.get('control').hideText);
                }
            }
        },

        calculateWidths: function() {
            var direction = this.model.get('_direction');
            var imageWidth = this.$('.reveal-widget').width();
            var controlWidth = this.$('.reveal-widget-control').width();
            var margin = -imageWidth;

            this.$('.reveal-widget-slider').css('width', 2 * imageWidth);

            if (this.model.get('_revealed')) {
                this.$('.reveal-widget-control').css(this.model.get('_direction'), imageWidth - controlWidth)
            }

            this.$('.reveal-widget-slider').css('margin-' + direction, margin);
            // Ensure the text doesn't overflow the image
            this.$('div.reveal-widget-item-text').css('width', ($('img.reveal-image').width() - 80));
            this.model.set('_scrollWidth', imageWidth);
            this.model.set('_controlWidth', controlWidth);
        },

        setDeviceSize: function() {
            if (Adapt.device.screenSize === 'large') {
                this.$el.addClass('desktop').removeClass('mobile');
                this.model.set('_isDesktop', true);
            } else {
                this.$el.addClass('mobile').removeClass('desktop');
                this.model.set('_isDesktop', false);
            }

            // On mobile, Check for items with long text. We'll provide a popup for these
            var CHAR_LIMIT = 50;
            var first = this.model.get('first');
            var second = this.model.get('second');

            if (typeof first === 'undefined' || typeof second === 'undefined') {
                return false;
            }

            var firstCharLimit = first._maxCharacters || CHAR_LIMIT;
            var secondCharLimit = second._maxCharacters || CHAR_LIMIT;
            var firstHasPopup = first.body && first.body.length > firstCharLimit;
            var secondHasPopup = second.body && second.body.length > secondCharLimit;

            if (firstHasPopup) {
                if (first.body) {
                    this.model.set('_firstShortText', $(first.body).text().substring(0, firstCharLimit) + '...');
                }
            }
            if (secondHasPopup) {
                if (second.body) {
                    this.model.set('_secondShortText', $(second.body).text().substring(0, secondCharLimit) + '...');
                }
            }
            if (Adapt.device.screenSize === 'small') {
                this.model.set('_displayFirstShortText', firstHasPopup);
                this.model.set('_displaySecondShortText', secondHasPopup);
                if (firstHasPopup) {
                    this.$('.reveal-first-short').removeClass('reveal-hidden');
                    this.$('.reveal-first-long').addClass('reveal-hidden');
                }
                if (secondHasPopup) {
                    this.$('.reveal-second-short').removeClass('reveal-hidden');
                    this.$('.reveal-second-long').addClass('reveal-hidden');
                }
            } else {
                this.model.set('_displayFirstShortText', false);
                this.model.set('_displaySecondShortText', false);
                this.$('.reveal-first-short').addClass('reveal-hidden');
                this.$('.reveal-first-long').removeClass('reveal-hidden');
                this.$('.reveal-second-short').addClass('reveal-hidden');
                this.$('.reveal-second-long').removeClass('reveal-hidden');
            }
        },

        resizeControl: function() {
            var imageWidth = this.$('.reveal-widget').width();
            var controlWidth = this.$('.reveal-widget-control').width();
            var direction = this.model.get('_direction');
            var sliderAnimation = {};

            if (this.model.get('_revealed')) {
                this.$('.reveal-widget-slider').css('margin-left', (direction == 'left') ? -imageWidth : 0);
                sliderAnimation['margin-left'] = (direction == 'left') ? 0 :  -imageWidth
                this.$('.reveal-widget-slider').animate(sliderAnimation);
            } else {
                this.$('.reveal-widget-slider').css('margin-left', (direction == 'left') ? imageWidth : 0);
            }

            this.$('.reveal-widget-slider').css('width', 2 * imageWidth);
            this.$('.reveal-widget-slider').css('margin-' + direction, -imageWidth);
            this.model.set('_scrollWidth', imageWidth);
            this.model.set('_controlWidth', controlWidth);
        },

        postRender: function () {
            this.$('.reveal-widget').imageready(_.bind(function() {
                this.setReadyStatus();
            }, this));
        },

        getOppositeDirection: function(direction) {
          return (direction == 'left') ? 'right' : 'left';
        },

        clickReveal: function (event) {
            event.preventDefault();

            var direction = this.model.get('_direction');
            var scrollWidth = this.model.get('_scrollWidth');
            var controlWidth = this.model.get('_controlWidth');
            var controlMovement = (!this.model.get('_revealed')) ? scrollWidth - controlWidth : scrollWidth;
            var operator = !this.model.get('_revealed') ? '+=' : '-=';
            var controlAnimation = {}, sliderAnimation = {};
            var classToAdd;
            var classToRemove;

            // Define the animations and new icon styles
            if (!this.model.get('_revealed')) {
                // reveal second
                this.model.set('_revealed', true);
                this.$('.reveal-widget').addClass('reveal-showing');

                controlAnimation[direction] = operator + controlMovement;
                classToAdd = 'icon-controls-' + direction;
                classToRemove = 'icon-controls-' + this.getOppositeDirection(direction);

                sliderAnimation['margin-left'] = (direction == 'left') ? 0 : -scrollWidth;

                this.setCompletionStatus();
            } else {
                //show first
                this.model.set('_revealed', false);
                this.$('.reveal-widget').removeClass('reveal-showing');

                controlAnimation[direction] = 0;
                classToAdd = 'icon-controls-' + this.getOppositeDirection(direction);
                classToRemove = 'icon-controls-' + direction;

                sliderAnimation['margin-left'] = (direction == 'left') ? operator + controlMovement : 0
            }
            // Change the UI to handle the new state
            this.$('.reveal-widget-slider').animate(sliderAnimation);
            this.$('.reveal-widget-icon').removeClass(classToRemove).addClass(classToAdd);

            this.setControlText(this.model.get('_revealed'));
        },

        openPopup: function (event) {
            event.preventDefault();

            this.model.set('_active', false);

            var bodyText = this.model.get('_revealed')
              ? this.model.get('second').body
              : this.model.get('first').body;

            var popupObject = {
                title: '',
                body: bodyText
            };

            Adapt.trigger('notify:popup', popupObject);
      }
    });

    Adapt.register("reveal", Reveal);

    return Reveal;
});
define(function(require) {

    var ComponentView = require('coreViews/componentView');
    var Adapt = require('coreJS/adapt');

    var AssessmentResultsTotal = ComponentView.extend({

        events: {
            'inview': 'onInview'
        },

        preRender: function () {
            this.setupEventListeners();
            this.setupModelResetEvent();
            this.checkIfVisible();
        },

        checkIfVisible: function() {

            var wasVisible = this.model.get("_isVisible");
            var isVisibleBeforeCompletion = this.model.get("_isVisibleBeforeCompletion") || false;

            var isVisible = wasVisible && isVisibleBeforeCompletion;

            var assessmentArticleModels = Adapt.assessment.get();
            if (assessmentArticleModels.length === 0) return;

            var isComplete = this.isComplete();

            if (!isVisibleBeforeCompletion) isVisible = isVisible || isComplete;

            this.model.set('_isVisible', isVisible);

            // if assessment(s) already complete then render
            if (isComplete) this.onAssessmentComplete(Adapt.assessment.getState());
        },
        
        isComplete: function() {
            var isComplete = false;

            var assessmentArticleModels = Adapt.assessment.get();
            if (assessmentArticleModels.length === 0) return;

            for (var i = 0, l = assessmentArticleModels.length; i < l; i++) {
                var articleModel = assessmentArticleModels[i];
                var assessmentState = articleModel.getState();
                isComplete = assessmentState.isComplete;
                if (!isComplete) break;
            }

            if (!isComplete) {
                this.model.reset("hard", true);
            }
            
            return isComplete;
        },

        setupModelResetEvent: function() {
            if (this.model.onAssessmentsReset) return;
            this.model.onAssessmentsReset = function(state) {
                this.reset('hard', true);
            };
            this.model.listenTo(Adapt, 'assessments:reset', this.model.onAssessmentsReset);
        },

        postRender: function() {
            this.setReadyStatus();
        },
        setupEventListeners: function() {
            this.listenTo(Adapt, 'assessment:complete', this.onAssessmentComplete);
            this.listenToOnce(Adapt, 'remove', this.onRemove);
        },

        removeEventListeners: function() {;
            this.stopListening(Adapt, 'assessment:complete', this.onAssessmentComplete);
            this.stopListening(Adapt, 'remove', this.onRemove);
        },

        onAssessmentComplete: function(state) {
            this.model.set("_state", state);
            this.setFeedback();

            //show feedback component
            this.render();
            if(!this.model.get('_isVisible')) this.model.set('_isVisible', true);
            
        },

        onInview: function(event, visible, visiblePartX, visiblePartY) {
            if (visible) {
                if (visiblePartY === 'top') {
                    this._isVisibleTop = true;
                } else if (visiblePartY === 'bottom') {
                    this._isVisibleBottom = true;
                } else {
                    this._isVisibleTop = true;
                    this._isVisibleBottom = true;
                }
                
                if (this._isVisibleTop || this._isVisibleBottom) {
                    this.setCompletionStatus();
                    this.$el.off("inview");
                }
            }
        },

        setFeedback: function() {

            var completionBody = this.model.get("_completionBody");
            var feedbackBand = this.getFeedbackBand();

            var state = this.model.get("_state");
            state.feedbackBand = feedbackBand;
            state.feedback = feedbackBand.feedback;

            completionBody = this.stringReplace(completionBody, state);

            this.model.set("body", completionBody);

        },

        getFeedbackBand: function() {
            var state = this.model.get("_state");

            var bands = this.model.get("_bands");
            var scoreAsPercent = state.scoreAsPercent;
            
            for (var i = (bands.length - 1); i >= 0; i--) {
                if (scoreAsPercent >= bands[i]._score) {
                    return bands[i];
                }
            }

            return "";
        },

        stringReplace: function(string, context) {
            //use handlebars style escaping for string replacement
            //only supports unescaped {{{ attributeName }}} and html escaped {{ attributeName }}
            //will string replace recursively until no changes have occured

            var changed = true;
            while (changed) {
                changed = false;
                for (var k in context) {
                    var contextValue = context[k];

                    switch (typeof contextValue) {
                    case "object":
                        continue;
                    case "number":
                        contextValue = Math.floor(contextValue);
                        break;
                    }

                    var regExNoEscaping = new RegExp("((\\{\\{\\{){1}[\\ ]*"+k+"[\\ ]*(\\}\\}\\}){1})","g");
                    var regExEscaped = new RegExp("((\\{\\{){1}[\\ ]*"+k+"[\\ ]*(\\}\\}){1})","g");

                    var preString = string;

                    string = string.replace(regExNoEscaping, contextValue);
                    var escapedText = $("<p>").text(contextValue).html();
                    string = string.replace(regExEscaped, escapedText);

                    if (string != preString) changed = true;

                }
            }

            return string;
        },

        onRemove: function() {
            this.removeEventListeners();
        }
        
    });
    
    Adapt.register("assessmentResultsTotal", AssessmentResultsTotal);
    
});
define(function(require) {

    var ComponentView = require('coreViews/componentView');
    var Adapt = require('coreJS/adapt');

    var OpenTextInput = ComponentView.extend({

        events: {
            'click .openTextInput-save-button'  : 'onSaveClicked',
            'click .openTextInput-clear-button' : 'onClearClicked',
            'click .openTextInput-action-button': 'onActionClicked',
            'keyup .openTextInput-item-textbox' : 'onKeyUpTextarea'
        },

        preRender: function() {
            this.listenTo(this.model, 'change:_isSaved', this.onSaveChanged);
            this.listenTo(this.model, 'change:_userAnswer', this.onUserAnswerChanged);
            this.listenToOnce(Adapt, 'navigation:backButton', this.handleBackNavigation);
            this.listenToOnce(Adapt, 'navigation:homeButton', this.handleHomeNavigation);

            // Intercept the routing so that text entered can be saved.
            Adapt.router.set('_canNavigate', false, {pluginName:'_openTextInput'});
            
            if (!this.model.get('_userAnswer')) {
                var userAnswer = this.getUserAnswer();
                if (userAnswer) {
                    this.model.set('_userAnswer', userAnswer);
                }
            }
        },

        handleBackNavigation: function() {
            this.checkForChanges('navigation:backButton');
        },

        handleHomeNavigation: function() {
            this.checkForChanges('navigation:homeButton');
        },

        checkForChanges: function(eventToTrigger) {
            var userAnswer = this.model.get("_userAnswer") || '';
            
            if (userAnswer === this.$textbox.val()) {
                Adapt.router.set('_canNavigate', true, {pluginName:'_openTextInput'});
                Adapt.trigger(eventToTrigger);
            }
            else {
                Adapt.router.set('_canNavigate', false, {pluginName:'_openTextInput'});
                this.unsavedChangesNotification(eventToTrigger);
            }
         },

         unsavedChangesNotification: function(eventToTrigger) {
            var promptObject = {
                title: this.model.get('unsavedChangesNotificationTitle'),
                body: this.model.get('unsavedChangesNotificationBody'),
                _prompts: [{
                     promptText: 'Yes',
                     _callbackEvent: '_openTextInput:save',
                }, {
                     promptText: 'No',
                     _callbackEvent: '_openTextInput:doNotSave'
                }],
                _showIcon: true
            };

            Adapt.once('_openTextInput:save', function() {
                this.storeUserAnswer();
                Adapt.router.set('_canNavigate', true, {pluginName:'_openTextInput'});
                Adapt.trigger(eventToTrigger);
            }, this);

            Adapt.once('_openTextInput:doNotSave', function() {
               Adapt.router.set('_canNavigate', true, {pluginName:'_openTextInput'});
               Adapt.trigger(eventToTrigger);
             }, this);

            Adapt.trigger('notify:prompt', promptObject);
        },

        postRender: function() {
            //set component to ready
            this.$textbox = this.$('.openTextInput-item-textbox');
            this.countCharacter();
            this.setReadyStatus();

            if (this.model.get('_isComplete')) {
                this.disableButtons();
                this.disableTextarea();
                if (!this.model.get('modelAnswer')) {
                    this.$('.openTextInput-action-button')
                        .prop('disabled', true)
                } else {
                    this.showUserAnswer();
                }
            }
        },

        getUserAnswer: function() {
            var identifier = this.model.get('_id') + '-OpenTextInput-UserAnswer';
            var userAnswer = '';

            if (this.supportsHtml5Storage()) {
                userAnswer = localStorage.getItem(identifier);
                if (userAnswer) {
                    return userAnswer;
                }
            }

            return false;
        },

        supportsHtml5Storage: function() {
            // check for html5 local storage support
            try {
                return 'localStorage' in window && typeof window['localStorage'] !== 'undefined';
            } catch (e) {
                return false;
            }
        },

        countCharacter: function() {
            var charLengthOfTextarea = this.$textbox.val().length;
            var allowedCharacters = this.model.get('_allowedCharacters');
            if (allowedCharacters != null) {
                var charactersLeft = allowedCharacters - charLengthOfTextarea;
                this.$('.openTextInput-count-amount').html(charactersLeft);
            } else {
                this.$('.openTextInput-count-amount').html(charLengthOfTextarea);
            }
        },

        onKeyUpTextarea: function() {
            this.model.set('_isSaved', false);
            this.onUserAnswerChanged(null, this.$textbox.val());
            this.limitCharacters();
            this.countCharacter();
        },

        limitCharacters: function() {
            var allowedCharacters = this.model.get('_allowedCharacters');
            if (allowedCharacters != null && this.$textbox.val().length > allowedCharacters) {
                var substringValue = this.$textbox.val().substring(0, allowedCharacters);
                this.$textbox.val(substringValue);
            }
        },

        onSaveClicked: function(event) {
            event.preventDefault();
            this.storeUserAnswer();
            this.notifyUserAnswerIsSaved();
        },

        storeUserAnswer: function() {
            // use unique identifier to avoid collisions with other components
            var identifier = this.model.get('_id') + '-OpenTextInput-UserAnswer';

            if (this.supportsHtml5Storage()) {
                localStorage.setItem(identifier, this.$textbox.val());
            }

            this.model.set('_userAnswer', this.$textbox.val());
            this.model.set('_isSaved', true);
        },

        notifyUserAnswerIsSaved: function() {
            var pushObject = {
                title: '',
                body: this.model.get('savedMessage'),
                _timeout: 2000,
                _callbackEvent: '_openTextInput'
            };
            Adapt.trigger('notify:push', pushObject);
        },

        onSaveChanged: function(model, changedValue) {
            this.$('.openTextInput-save-button').prop('disabled', changedValue);
        },

        onClearClicked: function(event) {
            event.preventDefault();

            var promptObject = {
                title: this.model.get('clearNotificationTitle'),
                body: this.model.get('clearNotificationBody'),
                _prompts: [{
                    promptText: 'Yes',
                    _callbackEvent: '_openTextInput:clearText',
                }, {
                    promptText: 'No',
                    _callbackEvent: '_openTextInput:keepText'
                }],
                _showIcon: true
            };

            Adapt.once('_openTextInput:clearText', function() {
                this.clearTextarea();
                this.onUserAnswerChanged(null, this.$textbox.val());
                this.countCharacter();
            }, this);
            Adapt.trigger('notify:prompt', promptObject);
        },

        clearTextarea: function(event) {
            this.$textbox.val('');
            this.model.set('_isSaved', false);
        },

        onUserAnswerChanged: function(model, changedValue) {
            if (changedValue) {
                this.$('.openTextInput-clear-button, .openTextInput-action-button')
                    .prop('disabled', false);
            } else {
                this.$('.openTextInput-clear-button, .openTextInput-action-button')
                    .prop('disabled', true);
            }
        },

        onActionClicked: function(event) {
            if (this.model.get('_isComplete')) {
                if (this.model.get('_buttonState') == 'model') {
                    this.showUserAnswer();
                } else {
                    this.showModelAnswer();
                }
            } else {
                this.submitAnswer();
            }
        },

        submitAnswer: function() {
            this.storeUserAnswer();
            this.disableButtons();
            this.disableTextarea();
            if (!this.model.get('modelAnswer')) {
                this.$('.openTextInput-action-button')
                    .prop('disabled', true);
            } else {
                this.showUserAnswer();
            }

            this.setCompletionStatus();

            var pushObject = {
                title: '',
                body: this.model.get('submittedMessage'),
                _timeout: 2000,
                _callbackEvent: '_openTextInput:submitted'
            };

            Adapt.trigger('notify:push', pushObject);
        },

        disableTextarea: function() {
            this.$textbox.prop('disabled', true);
        },

        disableButtons: function() {
            this.$('.openTextInput-clear-button, .openTextInput-save-button')
                .prop('disabled', true)
                .addClass('disabled');
        },

        updateActionButton: function(buttonText) {
            this.$('.openTextInput-action-button')
                .html(buttonText);
        },

        showModelAnswer: function() {
            this.model.set('_buttonState', 'model');
            this.updateActionButton(this.model.get('_buttons').showUserAnswer);
            var modelAnswer = this.model.get('modelAnswer');
            modelAnswer = modelAnswer.replace(/\\n|&#10;/g, "\n");
            this.$textbox.val(modelAnswer);
        },

        showUserAnswer: function() {
            this.model.set('_buttonState', 'user');
            this.updateActionButton(this.model.get('_buttons').showModelAnswer);
            this.$textbox.val(this.model.get('_userAnswer'));
        }
    });

    Adapt.register('openTextInput', OpenTextInput);

});
define(function(require) {

    var ComponentView = require("coreViews/componentView");
    var Adapt = require("coreJS/adapt");

    var Narrative = ComponentView.extend({
 
        events: {
            'touchstart .narrative-slider':'onTouchNavigationStarted',
            'click .narrative-popup-open':'openPopup',
            'click .notify-popup-icon-close':'closePopup',
            'click .narrative-controls':'onNavigationClicked'
        },
        
        preRender: function () {
            this.listenTo(Adapt, 'device:changed', this.reRender, this);
            this.listenTo(Adapt, 'device:resize', this.resizeControl, this);
            this.setDeviceSize();
        },

        setDeviceSize: function() {
            if (Adapt.device.screenSize === 'large') {
                this.$el.addClass('desktop').removeClass('mobile');
                this.model.set('_isDesktop', true);
            } else {
                this.$el.addClass('mobile').removeClass('desktop');
                this.model.set('_isDesktop', false)
            }
        },

        postRender: function() {
            this.$('.narrative-slider').imageready(_.bind(function(){
                this.setReadyStatus();
            }, this));
            this.setupNarrative();
        }, 

        setupNarrative: function() {
            _.bindAll(this, 'onTouchMove', 'onTouchEnd');
            this.setDeviceSize();
            this.model.set('_itemCount', this.model.get('_items').length);

            this.model.set('_active', true);

            if (this.model.get('_stage')) {
                this.setStage(this.model.get('_stage'));
            } else {
                this.setStage(0);
            }
            this.calculateWidths();
        },

        calculateWidths: function() {
            var slideWidth = this.$('.narrative-slide-container').width();
            var slideCount = this.model.get('_itemCount');
            var marginRight = this.$('.narrative-slider-graphic').css('margin-right');
            var extraMargin = marginRight === "" ? 0 : parseInt(marginRight);
            var fullSlideWidth = (slideWidth + extraMargin) * slideCount;
            var iconWidth = this.$('.narrative-popup-open').outerWidth();

            this.$('.narrative-slider-graphic').width(slideWidth)
            this.$('.narrative-strapline-header').width(slideWidth);
            this.$('.narrative-strapline-title').width(slideWidth);
            this.$('.narrative-strapline-title-inner').width(slideWidth - iconWidth);

            this.$('.narrative-slider').width(fullSlideWidth);
            this.$('.narrative-strapline-header-inner').width(fullSlideWidth);

            var stage = this.model.get('_stage');
            var margin = -(stage * slideWidth);

            this.$('.narrative-slider').css('margin-left', margin);
            this.$('.narrative-strapline-header-inner').css('margin-left', margin);

            this.model.set('_finalItemLeft', fullSlideWidth - slideWidth);
        },

        resizeControl: function() {
            this.setDeviceSize();
            this.calculateWidths();
            this.evaluateNavigation();
        },

        reRender: function() {
            if (this.model.get('_wasHotgraphic') && Adapt.device.screenSize == 'large') {
                this.replaceWithHotgraphic();
            }
        },

        replaceWithHotgraphic: function () {
            var Hotgraphic = require('components/adapt-contrib-hotgraphic/js/adapt-contrib-hotgraphic');
            var model = this.prepareHotgraphicModel();
            var newHotgraphic = new Hotgraphic({model:model, $parent: this.options.$parent});
            this.options.$parent.append(newHotgraphic.$el);
            this.remove();
            _.defer(function(){
                Adapt.trigger('device:resize');
            });
        },

        prepareHotgraphicModel: function() {
          var model = this.model;
          model.set('_component', 'hotgraphic');
          model.set('body', model.get('originalBody'));
          return model;
        },

        animateSliderToIndex: function(itemIndex) {
            var extraMargin = parseInt(this.$('.narrative-slider-graphic').css('margin-right')),
                movementSize = this.$('.narrative-slide-container').width()+extraMargin;
            
            this.$('.narrative-slider').stop().animate({'margin-left': -(movementSize * itemIndex)});
            this.$('.narrative-strapline-header-inner').stop(true, true).animate({'margin-left': -(movementSize * itemIndex)});
        },

        closePopup: function (event) {
            event.preventDefault();
            Adapt.trigger('popup:closed');
            /*this.model.set('_active', true);

            this.$('.narrative-popup-close').blur();
            this.$('.narrative-popup').addClass('narrative-hidden');
            
            this.evaluateCompletion();*/
        },


        setStage: function(stage) {
            this.model.set('_stage', stage);

            // Set the visited attribute
            var currentItem = this.getCurrentItem(stage);
            currentItem.visited = true;

            this.$('.narrative-progress').removeClass('selected').eq(stage).addClass('selected');
            this.$('.narrative-slider-graphic').children('.controls').attr('tabindex', -1);
            this.$('.narrative-slider-graphic').eq(stage).children('.controls').attr('tabindex', 0);
            this.$('.narrative-content-item').addClass('narrative-hidden').eq(stage).removeClass('narrative-hidden');

            this.evaluateNavigation();
            this.evaluateCompletion();

            this.animateSliderToIndex(stage);
        },

        
        constrainStage: function(stage) {
            if (stage > this.model.get('_items').length - 1) {
                stage = this.model.get('_items').length - 1;
            } else if (stage < 0) {
                stage = 0;
            }
            return stage;
        },
        
        constrainXPosition: function(previousLeft, newLeft, deltaX) {
            if (newLeft > 0 && deltaX > 0) {
                newLeft = previousLeft + (deltaX / (newLeft * 0.1));
            }
            var finalItemLeft = this.model.get('_finalItemLeft'); 
            if (newLeft < -finalItemLeft && deltaX < 0) {
                var distance = Math.abs(newLeft + finalItemLeft);
                newLeft = previousLeft + (deltaX / (distance * 0.1));
            }
            return newLeft;
        },

        evaluateNavigation: function() {
            var currentStage = this.model.get('_stage');
            var itemCount = this.model.get('_itemCount');
            if (currentStage == 0) {
                this.$('.narrative-control-left').addClass('narrative-hidden');

                if (itemCount > 1) {
                    this.$('.narrative-control-right').removeClass('narrative-hidden');
                }
            } else {
                this.$('.narrative-control-left').removeClass('narrative-hidden');

                if (currentStage == itemCount - 1) {
                    this.$('.narrative-control-right').addClass('narrative-hidden');
                } else {
                    this.$('.narrative-control-right').removeClass('narrative-hidden');
                }
            }

        },

        getNearestItemIndex: function() {
            var currentPosition = parseInt(this.$('.narrative-slider').css('margin-left')),
                graphicWidth = this.$('.narrative-slider-graphic').width(),
                absolutePosition = currentPosition / graphicWidth,
                stage = this.model.get('_stage'),
                relativePosition = stage - Math.abs(absolutePosition);
            
            if(relativePosition < -0.3) {
                stage++;
            } else if (relativePosition > 0.3) {
                stage--;
            }
            
            return this.constrainStage(stage);
        },

        getCurrentItem: function(index) {
            return this.model.get('_items')[index];
        },
        
        getVisitedItems: function() {
          return _.filter(this.model.get('_items'), function(item) {
            return item.visited;
          });
        },

        evaluateCompletion: function() {
            if (this.getVisitedItems().length == this.model.get('_items').length) {
                this.setCompletionStatus();
            }
        },

        moveElement: function($element, deltaX) {
            var previousLeft = parseInt($element.css('margin-left')),
                newLeft = previousLeft + deltaX;
            
            newLeft = this.constrainXPosition(previousLeft, newLeft, deltaX);

            $element.css('margin-left', newLeft + 'px');
        },
        
        openPopup: function (event) {
            event.preventDefault();
            var currentItem = this.getCurrentItem(this.model.get('_stage')),
                popupObject = {
                    title: currentItem.title,
                    body: currentItem.body
                };

            Adapt.trigger('notify:popup', popupObject);
            Adapt.trigger('popup:opened');
        },

        onNavigationClicked: function(event) {
            event.preventDefault();
            
            if (!this.model.get('_active')) return;
            
            var stage = this.model.get('_stage'),
                numberOfItems = this.model.get('_itemCount');
            
            if ($(event.currentTarget).hasClass('narrative-control-right')) {
                stage++;
                if (stage == numberOfItems-1) {
                    this.$('.narrative-control-left').focus();
                }
            } else if ($(event.currentTarget).hasClass('narrative-control-left')) {
                stage--;
                if (stage == 0) {
                    this.$('.narrative-control-right').focus();
                }
            }
            stage = (stage + numberOfItems) % numberOfItems;
            this.setStage(stage);
        },

        onTouchNavigationStarted: function(event) {
            //event.preventDefault();
            //if (!this.model.get('_active')) return;
            
            /*this.$('.narrative-slider').stop();
            this.$('.narrative-strapline-header-inner').stop();
            
            this.model.set('_currentX', event.originalEvent.touches[0]['pageX']);
            this.model.set('_touchStartPosition', parseInt(this.$('.narrative-slider').css('margin-left')));
            
            this.$('.narrative-slider').on('touchmove', this.onTouchMove);
            this.$('.narrative-slider').one('touchend', this.onTouchEnd);*/
        },

        onTouchEnd: function(event) {
            var nextItemIndex = this.getNearestItemIndex();
            this.setStage(nextItemIndex);
            
            this.$('.narrative-slider').off('touchmove', this.onTouchMove);
        },

        onTouchMove: function(event) {
            var currentX = event.originalEvent.touches[0]['pageX'],
                previousX = this.model.get('_currentX'),
                deltaX = currentX - previousX;
            
            this.moveElement(this.$('.narrative-slider'), deltaX);
            this.moveElement(this.$('.narrative-strapline-header-inner'), deltaX);
            
            this.model.set('_currentX', currentX);
            Adapt.trigger('popup:closed');
        }
        
    });
    
    Adapt.register("narrative", Narrative);
    
    return Narrative;

});
Пример #22
0
define(function(require) {

    var ComponentView = require("coreViews/componentView");
    var Adapt = require("coreJS/adapt");

    var Reveal = ComponentView.extend({

        events: function () {
            return Adapt.device.touch == true ? {
                'touchstart .reveal-widget-control':'clickReveal',
		'click .reveal-widget-control':'clickReveal',
                'inview' : 'inview',
                'touchstart .reveal-popup-open' : 'openPopup',
                'click .reveal-popup-close' : 'closePopup'
            }:{
                'click .reveal-widget-control':'clickReveal',
                'inview' : 'inview',
                'click .reveal-popup-open' : 'openPopup',
                'click .reveal-popup-close' : 'closePopup'
            }
        },

        preRender: function() {
            this.listenTo(Adapt, 'pageView:ready', this.setupReveal, this);
            this.listenTo(Adapt, 'device:resize', this.resizeControl, this);
            this.listenTo(Adapt, 'device:changed', this.setDeviceSize, this);

            this.setDeviceSize();
        },

        setupReveal: function() {
            var direction = !this.model.get('_direction') ? "left" : this.model.get('_direction');

            // Initialise the directional arrows
            this.$('.reveal-widget-item').addClass('reveal-' + this.model.get('_direction'));
            this.$('.reveal-widget-control').addClass('reveal-' + direction);
            this.$('.reveal-image').addClass('reveal-' + direction);
            this.$('.reveal-widget-item-text').addClass('reveal-' + direction);

            this.$('.reveal-widget-item-text span').addClass('reveal-' + direction);
            this.$('.reveal-widget-icon').addClass('icon-arrow-' + this.getOppositeDirection(direction));

            this.model.set('_direction', direction);
            this.model.set('_active', true);
            this.model.set('_revealed', false);

            this.setControlText(false);

            this.calculateWidths();
        },

        setControlText: function(isRevealed) {
            if (this.model.get('_control')) {
                if (!isRevealed && this.model.get('control').showText) {
                    this.$('.reveal-widget-control').attr('title', this.model.get('control').showText);
                }

                if (isRevealed && this.model.get('control').hideText) {
                    this.$('.reveal-widget-control').attr('title', this.model.get('control').hideText);
                }
            }
        },

        calculateWidths: function() {
            var direction = this.model.get('_direction');
            var imageWidth = this.$('.reveal-widget').width();
            var controlWidth = this.$('.reveal-widget-control').width();
            var margin = -imageWidth;

            this.$('.reveal-widget-slider').css('width', 2 * imageWidth);

            if (this.model.get('_revealed')) {
                this.$('.reveal-widget-control').css(this.model.get('_direction'), imageWidth - controlWidth)
            }

            this.$('.reveal-widget-slider').css('margin-' + direction, margin);

            this.model.set('_scrollWidth', imageWidth);
            this.model.set('_controlWidth', controlWidth);
        },

        setDeviceSize: function() {
            if (Adapt.device.screenSize === 'large') {
                this.$el.addClass('desktop').removeClass('mobile');
                this.model.set('_isDesktop', true);
            } else {
                this.$el.addClass('mobile').removeClass('desktop');
                this.model.set('_isDesktop', false);
            }

            // On mobile, Check for items with long text. We'll provide a popup for these
            var charLimit = 50;
            var first = this.model.get('first');
            var second = this.model.get('second');
            var firstCharLimit = first._maxCharacters || charLimit;
            var secondCharLimit = second._maxCharacters || charLimit;
            var firstHasPopup = first.body && first.body.length > firstCharLimit;
            var secondHasPopup = second.body && second.body.length > secondCharLimit;

            if (firstHasPopup) {
                this.model.set('_firstShortText', first.body.substring(0, firstCharLimit) + '...');
            }
            if (secondHasPopup) {
                this.model.set('_secondShortText', second.body.substring(0, secondCharLimit) + '...');
            }
            if (Adapt.device.screenSize === 'small') {
                this.model.set('_displayFirstShortText', firstHasPopup);
                this.model.set('_displaySecondShortText', secondHasPopup);
                if (firstHasPopup) {
                    this.$('.reveal-first-short').removeClass('reveal-hidden');
                    this.$('.reveal-first-long').addClass('reveal-hidden');
                }
                if (secondHasPopup) {
                    this.$('.reveal-second-short').removeClass('reveal-hidden');
                    this.$('.reveal-second-long').addClass('reveal-hidden');
                }
            } else {
                this.model.set('_displayFirstShortText', false);
                this.model.set('_displaySecondShortText', false);
                this.$('.reveal-first-short').addClass('reveal-hidden');
                this.$('.reveal-first-long').removeClass('reveal-hidden');
                this.$('.reveal-second-short').addClass('reveal-hidden');
                this.$('.reveal-second-long').removeClass('reveal-hidden');
            }
        },

        resizeControl: function() {
            var imageWidth = this.$('.reveal-widget').width();
            var controlWidth = this.$('.reveal-widget-control').width();
            var direction = this.model.get('_direction');
            var scrollWidth = this.model.get('_scrollWidth');
            var sliderAnimation = {};

            if (this.model.get('_revealed')) {
                this.$('.reveal-widget-slider').css('margin-left', (direction == 'left') ? -imageWidth : 0);
                sliderAnimation['margin-left'] = (direction == 'left') ? 0 :  -imageWidth
                this.$('.reveal-widget-slider').animate(sliderAnimation);
            } else {
                this.$('.reveal-widget-slider').css('margin-left', (direction == 'left') ? imageWidth : 0);
            }

            this.$('.reveal-widget-slider').css('width', 2 * imageWidth);
            this.$('.reveal-widget-slider').css('margin-' + direction, -imageWidth);
            this.model.set('_scrollWidth', imageWidth);
            this.model.set('_controlWidth', controlWidth);
        },

        postRender: function () {
            this.$('.reveal-widget').imageready(_.bind(function() {
                this.setReadyStatus();
            }, this));
        },

        getOppositeDirection: function(direction) {
            switch(direction) {
                case 'left':
                    oppositeDirection = 'right';
                    break;
                case 'right':
                    oppositeDirection = 'left';
                    break;
            }
            return oppositeDirection;
        },

        clickReveal: function (event) {
            event.preventDefault();

            var direction = this.model.get('_direction');
            var scrollWidth = this.model.get('_scrollWidth');
            var controlWidth = this.model.get('_controlWidth');
            var controlMovement = (!this.model.get('_revealed')) ? scrollWidth - controlWidth : scrollWidth;
            var operator = !this.model.get('_revealed') ? '+=' : '-=';
            var controlAnimation = {}, sliderAnimation = {};

            // Define the animations and new icon styles
            if (!this.model.get('_revealed')) {
                // reveal second
                this.model.set('_revealed', true);
                this.$('.reveal-widget').addClass('reveal-showing');

                controlAnimation[direction] = operator + controlMovement;
                classToAdd = 'icon-arrow-' + direction;
                classToRemove = 'icon-arrow-' + this.getOppositeDirection(direction);

                sliderAnimation['margin-left'] = (direction == 'left') ? 0 : -scrollWidth;

                this.setCompletionStatus();
            } else {
                //show first
                this.model.set('_revealed', false);
                this.$('.reveal-widget').removeClass('reveal-showing');

                controlAnimation[direction] = 0;
                classToAdd = 'icon-arrow-' + this.getOppositeDirection(direction);
                classToRemove = 'icon-arrow-' + direction;

                sliderAnimation['margin-left'] = (direction == 'left') ? operator + controlMovement : 0
            }
            // Change the UI to handle the new state
            this.$('.reveal-widget-slider').animate(sliderAnimation);
            this.$('.reveal-widget-icon').removeClass(classToRemove).addClass(classToAdd);

            this.setControlText(this.model.get('_revealed'));
        },

        openPopup: function (event) {
            event.preventDefault();
            this.model.set('_active', false);

            var outerMargin = parseFloat(this.$('.reveal-popup-inner').css('margin'));
            var innerPadding = parseFloat(this.$('.reveal-popup-inner').css('padding'));
            var toolBarHeight = this.$('.reveal-toolbar').height();

            this.$('.reveal-popup-content').addClass('reveal-hidden').eq(this.model.get('_revealed') ? 1 : 0).removeClass('reveal-hidden');
            this.$('.reveal-popup-inner').css('height', $(window).height() - (outerMargin * 2) - (innerPadding * 2));
            this.$('.reveal-popup').removeClass('reveal-hidden');
            this.$('.reveal-popup-content').css('height', (this.$('.reveal-popup-inner').height() - toolBarHeight));
        },

        closePopup: function (event) {
            event.preventDefault();
            this.model.set('_active', true);
            this.$('.reveal-popup-close').blur();
            this.$('.reveal-popup').addClass('reveal-hidden');
        }
    });

    Adapt.register("reveal", Reveal);

    return Reveal;
});
define(function(require) {

    var ComponentView = require('coreViews/componentView');
    var Adapt = require('coreJS/adapt');

    var Narrative = ComponentView.extend({

        events: {
            'click .narrative-strapline-title': 'openPopup',
            'click .narrative-controls': 'onNavigationClicked'
        },

        preRender: function() {
            this.listenTo(Adapt, 'device:changed', this.reRender, this);
            this.listenTo(Adapt, 'device:resize', this.resizeControl, this);
            this.listenTo(Adapt, 'notify:closed', this.closeNotify, this);
            this.setDeviceSize();

            // Checks to see if the narrative should be reset on revisit
            this.checkIfResetOnRevisit();
        },

        setDeviceSize: function() {
            if (Adapt.device.screenSize === 'large') {
                this.$el.addClass('desktop').removeClass('mobile');
                this.model.set('_isDesktop', true);
            } else {
                this.$el.addClass('mobile').removeClass('desktop');
                this.model.set('_isDesktop', false)
            }
        },

        postRender: function() {
            this.renderState();
            this.$('.narrative-slider').imageready(_.bind(function() {
                this.setReadyStatus();
            }, this));
            this.setupNarrative();
        },

        // Used to check if the narrative should reset on revisit
        checkIfResetOnRevisit: function() {
            var isResetOnRevisit = this.model.get('_isResetOnRevisit');

            // If reset is enabled set defaults
            if (isResetOnRevisit) {
                this.model.reset(isResetOnRevisit);
                this.model.set({_stage: 0});

                _.each(this.model.get('_items'), function(item) {
                    item.visited = false;
                });
            }
        },

        setupNarrative: function() {
            this.setDeviceSize();
            this.model.set('_marginDir', 'left');
            if (Adapt.config.get('_defaultDirection') == 'rtl') {
                this.model.set('_marginDir', 'right');
            }
            this.model.set('_itemCount', this.model.get('_items').length);

            this.model.set('_active', true);

            if (this.model.get('_stage')) {
                this.setStage(this.model.get('_stage'), true);
            } else {
                this.setStage(0, true);
            }
            this.calculateWidths();

            if (Adapt.device.screenSize !== 'large' && !this.model.get('_wasHotgraphic')) {
                this.replaceInstructions();
            }
            this.setupEventListeners();
            
            // if hasNavigationInTextArea set margin left 
            var hasNavigationInTextArea = this.model.get('_hasNavigationInTextArea');
            if (hasNavigationInTextArea == true) {
                var indicatorWidth = this.$('.narrative-indicators').width();
                var marginLeft = indicatorWidth / 2;
                
                this.$('.narrative-indicators').css({
                    marginLeft: '-' + marginLeft + 'px'
                });
            }
        },

        calculateWidths: function() {
            var slideWidth = this.$('.narrative-slide-container').width();
            var slideCount = this.model.get('_itemCount');
            var marginRight = this.$('.narrative-slider-graphic').css('margin-right');
            var extraMargin = marginRight === '' ? 0 : parseInt(marginRight);
            var fullSlideWidth = (slideWidth + extraMargin) * slideCount;
            var iconWidth = this.$('.narrative-popup-open').outerWidth();

            this.$('.narrative-slider-graphic').width(slideWidth);
            this.$('.narrative-strapline-header').width(slideWidth);
            this.$('.narrative-strapline-title').width(slideWidth);

            this.$('.narrative-slider').width(fullSlideWidth);
            this.$('.narrative-strapline-header-inner').width(fullSlideWidth);

            var stage = this.model.get('_stage');
            var margin = -(stage * slideWidth);

            this.$('.narrative-slider').css(('margin-' + this.model.get('_marginDir')), margin);
            this.$('.narrative-strapline-header-inner').css(('margin-' + this.model.get('_marginDir')), margin);

            this.model.set('_finalItemLeft', fullSlideWidth - slideWidth);
        },

        resizeControl: function() {
            this.setDeviceSize();
            this.replaceInstructions();
            this.calculateWidths();
            this.evaluateNavigation();
        },

        reRender: function() {
            if (this.model.get('_wasHotgraphic') && Adapt.device.screenSize == 'large') {
                this.replaceWithHotgraphic();
            } else {
                this.resizeControl();
            }
        },

        closeNotify: function() {
            this.evaluateCompletion()
        },

        replaceInstructions: function() {
            if (Adapt.device.screenSize === 'large') {
                this.$('.narrative-instruction-inner').html(this.model.get('instruction'));
            } else if (this.model.get('mobileInstruction') && !this.model.get('_wasHotgraphic')) {
                this.$('.narrative-instruction-inner').html(this.model.get('mobileInstruction'));
            }
        },

        replaceWithHotgraphic: function() {
            if (!Adapt.componentStore.hotgraphic) throw "Hotgraphic not included in build";
            var Hotgraphic = Adapt.componentStore.hotgraphic;
            
            var model = this.prepareHotgraphicModel();
            var newHotgraphic = new Hotgraphic({ model: model });
            var $container = $(".component-container", $("." + this.model.get("_parentId")));

            $container.append(newHotgraphic.$el);
            this.remove();
            _.defer(function() {
                Adapt.trigger('device:resize');
            });
        },

        prepareHotgraphicModel: function() {
            var model = this.model;
            model.set('_component', 'hotgraphic');
            model.set('body', model.get('originalBody'));
            model.set('instruction', model.get('originalInstruction'));
            return model;
        },

        moveSliderToIndex: function(itemIndex, animate, callback) {
            var extraMargin = parseInt(this.$('.narrative-slider-graphic').css('margin-right'));
            var movementSize = this.$('.narrative-slide-container').width() + extraMargin;
            var marginDir = {};
            if (animate) {
                marginDir['margin-' + this.model.get('_marginDir')] = -(movementSize * itemIndex);
                this.$('.narrative-slider').velocity("stop", true).velocity(marginDir);
                this.$('.narrative-strapline-header-inner').velocity("stop", true).velocity(marginDir, {complete:callback});
            } else {
                marginDir['margin-' + this.model.get('_marginDir')] = -(movementSize * itemIndex);
                this.$('.narrative-slider').css(marginDir);
                this.$('.narrative-strapline-header-inner').css(marginDir);
                callback();
            }
        },

        setStage: function(stage, initial) {
            this.model.set('_stage', stage);

            if (this.model.get('_isDesktop')) {
                // Set the visited attribute for large screen devices
                var currentItem = this.getCurrentItem(stage);
                currentItem.visited = true;
            }

            this.$('.narrative-progress:visible').removeClass('selected').eq(stage).addClass('selected');
            this.$('.narrative-slider-graphic').children('.controls').a11y_cntrl_enabled(false);
            this.$('.narrative-slider-graphic').eq(stage).children('.controls').a11y_cntrl_enabled(true);
            this.$('.narrative-content-item').addClass('narrative-hidden').a11y_on(false).eq(stage).removeClass('narrative-hidden').a11y_on(true);
            this.$('.narrative-strapline-title').a11y_cntrl_enabled(false).eq(stage).a11y_cntrl_enabled(true);

            this.evaluateNavigation();
            this.evaluateCompletion();

            this.moveSliderToIndex(stage, !initial, _.bind(function() {
                if (this.model.get('_isDesktop')) {
                    if (!initial) this.$('.narrative-content-item').eq(stage).a11y_focus();
                } else {
                    if (!initial) this.$('.narrative-popup-open').a11y_focus();
                }
            }, this));
        },

        constrainStage: function(stage) {
            if (stage > this.model.get('_items').length - 1) {
                stage = this.model.get('_items').length - 1;
            } else if (stage < 0) {
                stage = 0;
            }
            return stage;
        },

        constrainXPosition: function(previousLeft, newLeft, deltaX) {
            if (newLeft > 0 && deltaX > 0) {
                newLeft = previousLeft + (deltaX / (newLeft * 0.1));
            }
            var finalItemLeft = this.model.get('_finalItemLeft');
            if (newLeft < -finalItemLeft && deltaX < 0) {
                var distance = Math.abs(newLeft + finalItemLeft);
                newLeft = previousLeft + (deltaX / (distance * 0.1));
            }
            return newLeft;
        },

        evaluateNavigation: function() {
            var currentStage = this.model.get('_stage');
            var itemCount = this.model.get('_itemCount');
            if (currentStage == 0) {
                this.$('.narrative-control-left').addClass('narrative-hidden');

                if (itemCount > 1) {
                    this.$('.narrative-control-right').removeClass('narrative-hidden');
                }
            } else {
                this.$('.narrative-control-left').removeClass('narrative-hidden');

                if (currentStage == itemCount - 1) {
                    this.$('.narrative-control-right').addClass('narrative-hidden');
                } else {
                    this.$('.narrative-control-right').removeClass('narrative-hidden');
                }
            }

        },

        getNearestItemIndex: function() {
            var currentPosition = parseInt(this.$('.narrative-slider').css('margin-left'));
            var graphicWidth = this.$('.narrative-slider-graphic').width();
            var absolutePosition = currentPosition / graphicWidth;
            var stage = this.model.get('_stage');
            var relativePosition = stage - Math.abs(absolutePosition);

            if (relativePosition < -0.3) {
                stage++;
            } else if (relativePosition > 0.3) {
                stage--;
            }

            return this.constrainStage(stage);
        },

        getCurrentItem: function(index) {
            return this.model.get('_items')[index];
        },

        getVisitedItems: function() {
            return _.filter(this.model.get('_items'), function(item) {
                return item.visited;
            });
        },

        evaluateCompletion: function() {
            if (this.getVisitedItems().length === this.model.get('_items').length) {
                this.trigger('allItems');
            } 
        },

        moveElement: function($element, deltaX) {
            var previousLeft = parseInt($element.css('margin-left'));
            var newLeft = previousLeft + deltaX;

            newLeft = this.constrainXPosition(previousLeft, newLeft, deltaX);
            $element.css(('margin-' + this.model.get('_marginDir')), newLeft + 'px');
        },

        openPopup: function(event) {
            event.preventDefault();
            var currentItem = this.getCurrentItem(this.model.get('_stage'));
            var popupObject = {
                title: currentItem.title,
                body: currentItem.body
            };

            // Set the visited attribute for small and medium screen devices
            currentItem.visited = true;

            Adapt.trigger('notify:popup', popupObject);
        },

        onNavigationClicked: function(event) {
            event.preventDefault();

            if (!this.model.get('_active')) return;

            var stage = this.model.get('_stage');
            var numberOfItems = this.model.get('_itemCount');

            if ($(event.currentTarget).hasClass('narrative-control-right')) {
                stage++;
            } else if ($(event.currentTarget).hasClass('narrative-control-left')) {
                stage--;
            }
            stage = (stage + numberOfItems) % numberOfItems;
            this.setStage(stage);
        },

        inview: function(event, visible, visiblePartX, visiblePartY) {
            if (visible) {
                if (visiblePartY === 'top') {
                    this._isVisibleTop = true;
                } else if (visiblePartY === 'bottom') {
                    this._isVisibleBottom = true;
                } else {
                    this._isVisibleTop = true;
                    this._isVisibleBottom = true;
                }

                if (this._isVisibleTop && this._isVisibleBottom) {
                    this.$('.component-inner').off('inview');
                    this.setCompletionStatus();
                }
            }
        },

        onCompletion: function() {
            this.setCompletionStatus();
            if (this.completionEvent && this.completionEvent != 'inview') {
                this.off(this.completionEvent, this);
            }
        },

        setupEventListeners: function() {
            this.completionEvent = (!this.model.get('_setCompletionOn')) ? 'allItems' : this.model.get('_setCompletionOn');
            if (this.completionEvent !== 'inview') {
                this.on(this.completionEvent, _.bind(this.onCompletion, this));
            } else {
                this.$('.component-widget').on('inview', _.bind(this.inview, this));
            }
        }

    });

    Adapt.register('narrative', Narrative);

    return Narrative;

});
define(function(require) {

    var Handlebars = require('handlebars');
    var ComponentView = require('coreViews/componentView');
    var Adapt = require('coreJS/adapt');
    var ButtonsView = require('coreViews/buttonsView');

    var QuestionView = ComponentView.extend({
    
        className: function() {
            return "component "
            + "question-component " 
            + this.model.get('_component')
            + "-component " + this.model.get('_id') 
            + " " + this.model.get('_classes')
            + " " + this.setVisibility()
            + " component-" + this.model.get('_layout')
            + " nth-child-" + this.options.nthChild;
        },

        //////
        // Setup question types
        ////

        preRender: function() {
            // Setup listener for _isEnabled
            this.listenTo(this.model, 'change:_isEnabled', this.onEnabledChanged);
            // Checks to see if the question should be reset on revisit
            this.checkIfResetOnRevisit();
            // This method helps setup defualt settings on the model
            this.setupDefaultSettings();
            // Blank method for setting up questions before rendering
            this.setupQuestion();
            console.log(this.model.get("instruction"))
        },

        // Used in the question view to disabled the question when _isEnabled has been set to false
        onEnabledChanged: function(model, changedAttribute) {

            // If isEnabled == false add disabled class
            // else remove disabled class
            if (!changedAttribute) {
                this.$('.component-widget').addClass('disabled');
                this.disableQuestion();
            } else {
                this.$('.component-widget').removeClass('disabled');
                this.enableQuestion();
            }

        },

        // Used by the question to disable the question during submit and complete stages
        disableQuestion: function() {},

        // Used by the question to enable the question during interactions
        enableQuestion: function() {},

        // Used to check if the question should reset on revisit
        checkIfResetOnRevisit: function() {

            var isResetOnRevisit = this.model.get('_isResetOnRevisit');

            // If reset is enabled set defaults
            // Call blank method for question to handle
            if (isResetOnRevisit) {
                var attempts = this.model.get('_attempts');
                this.model.set({
                    _isEnabled: true,
                    _attemptsLeft: attempts,
                    _isCorrect: false,
                    _isSubmitted: false,
                    _buttonState: 'submit'
                });
                // Defer is added to allow the component to render
                _.defer(_.bind(function() {
                    this.resetQuestionOnRevisit();
                }, this));

            } else {

                // If complete - display users answer
                // or reset the question if not complete
                var isComplete = this.model.get('_isComplete');

                if (isComplete) {
                    this.model.set('_buttonState', 'hideCorrectAnswer');
                    // Defer is added to allow the component to render
                    _.defer(_.bind(function() {
                        this.onHideCorrectAnswerClicked(); 
                    }, this));
                    
                } else {
                    this.model.set('_buttonState', 'submit');
                    // Defer is added to allow the component to render
                    _.defer(_.bind(function() {
                        this.onResetClicked();
                    }, this));
                }

            }

        },

        // Used by the question to reset the question when revisiting the component
        resetQuestionOnRevisit: function() {},

        // Calls default methods to setup on questions
        setupDefaultSettings: function() {
            this.setupButtonSettings();
            this.setupWeightSettings();
        },

        // Used to setup either global or local button text
        setupButtonSettings: function() {
            // Checks if no buttons object is on the model
            // Sets button either from global object or component
            if (!this.model.has("_buttons")) {
                this.model.set("_buttons", Adapt.course.get("_buttons"));
            } else {
                for(var key in Adapt.course.get("_buttons")) {
                    var value=this.model.get("_buttons")[key];
                    if (!value) {
                        this.model.get("_buttons")[key] = Adapt.course.get("_buttons")[key];
                    }
                }
            }

        },

        // Used to setup either global or local question weight/score
        setupWeightSettings: function() {
            // Checks if questionWeight exists and if not use global
            if (!this.model.has("_questionWeight")) {
                this.model.set("_questionWeight", Adapt.config.get("_questionWeight"));
            }

        },

        // Left blank for question setup - should be used instead of preRender
        setupQuestion: function() {},

        // Calls default methods to setup after the question is rendered
        postRender: function() {
            this.addButtonsView();
            this.onQuestionRendered();
        },

        // Used to setup buttonsView and sets up the internal events for the question
        addButtonsView: function() {
            this.buttonsView = new ButtonsView({model: this.model, el: this.$('.buttons')});
            this.listenTo(this.buttonsView, 'buttons:submit', this.onSubmitClicked);
            this.listenTo(this.buttonsView, 'buttons:reset', this.onResetClicked);
            this.listenTo(this.buttonsView, 'buttons:showCorrectAnswer', this.onShowCorrectAnswerClicked);
            this.listenTo(this.buttonsView, 'buttons:hideCorrectAnswer', this.onHideCorrectAnswerClicked);
            this.listenTo(this.buttonsView, 'buttons:showFeedback', this.showFeedback);
        },

        // Blank method used just like postRender is for presentational components
        onQuestionRendered: function() {},

        //////
        // Submit process
        ////

        // Triggered when the submit button is clicked
        onSubmitClicked: function() {

            // canSubmit is setup in questions and should return a boolean
            // If the question stops the user form submitting - show instruction error
            // and give a blank method, onCannotSubmit to the question
            if(!this.canSubmit()) {
                this.showInstructionError();
                this.onCannotSubmit();
                return;
            }

            // Used to update the amount of attempts the question has
            this.updateAttempts();
            
            // Used to set attributes on the model after being submitted
            // Also adds a class of submitted
            this.setQuestionAsSubmitted();
            
            // Used to remove instruction error that is set when
            // the user has interacted in the wrong way
            this.removeInstructionError();
            
            // Used to store the users answer for later
            // This is a blank method given to the question
            this.storeUserAnswer();

            // Used to set question as correct:true/false
            // Calls isCorrect which is blank for the question 
            // to fill out and return a boolean
            this.markQuestion();
            
            // Used by the question to set the score on the model
            this.setScore();
            
            // Used by the question to display markings on the component
            this.showMarking();
            
            // Used to check if the question is complete
            // Triggers setCompletionStatus and adds class to widget
            this.checkQuestionCompletion();
            
            // Used to update buttonsView based upon question state
            this.updateButtons();

            // Used to setup the feedback by checking against 
            // question isCorrect or isPartlyCorrect
            this.setupFeedback();

            // Used to trigger an event so plugins can display feedback
            this.showFeedback();

        },

        // Use to check if the user is allowed to submit the question
        // Maybe the user has to select an item?
        canSubmit: function() {},

        // Adds a validation error class when the canSubmit returns false
        showInstructionError: function() {
            this.$(".component-instruction-inner").addClass("validation-error");
        },

        // Blank method for question to fill out when the question cannot be submitted
        onCannotSubmit: function() {},

        // Used to update the amount of attempts the user has left
        updateAttempts: function() {
            if (!this.model.get('_attemptsLeft')) {
                this.model.set("_attemptsLeft", this.model.get('_attempts'));
            }
            this.model.set("_attemptsLeft", this.model.get('_attemptsLeft') - 1);
        },

        // Used to set _isEnabled and _isSubmitted on the model
        // Also adds a 'submitted' class to the widget
        setQuestionAsSubmitted: function() {
            this.model.set({
                _isEnabled: false,
                _isSubmitted: true
            });
            this.$(".component-widget").addClass("submitted");
        },

        // Removes validation error class when the user canSubmit
        removeInstructionError: function() {
            this.$(".component-instruction-inner").removeClass("validation-error");
        },

        // This is important for returning or showing the users answer
        // This should preserve the state of the users answers
        storeUserAnswer: function() {},

        // Sets _isCorrect:true/false based upon isCorrect method below
        markQuestion: function() {

            if (this.isCorrect()) {
                this.model.set('_isCorrect', true);
            } else {
                this.model.set('_isCorrect', false);
            }

        },

        // Should return a boolean based upon whether to question is correct or not
        isCorrect: function() {},

        // Used to set the score based upon the _questionWeight
        setScore: function() {},

        // This is important and should give the user feedback on how they answered the question
        // Normally done through ticks and crosses by adding classes
        showMarking: function() {},

        // Checks if the question should be set to complete
        // Calls setCompletionStatus and adds complete classes
        checkQuestionCompletion: function() {

            var isComplete = false;
            
            if (this.model.get('_isCorrect') || (this.model.get('_attemptsLeft') === 0) ) isComplete = true;

            if (isComplete) {
                this.setCompletionStatus();
                this.$('.component-widget').addClass('complete' + (this.model.get('_hideUserAnswer') ? '' : ' show-user-answer' ) );
                // GM - added this line for Kineo
                Adapt.trigger('questionView:complete', this);
            }

        },

        // Updates buttons based upon question state by setting
        // _buttonState on the model which buttonsView listens to
        updateButtons: function() {

            var isComplete = this.model.get('_isComplete');
            var isCorrect = this.model.get('_isCorrect');
            var isEnabled = this.model.get('_isEnabled');
            var buttonState = this.model.get('_buttonState');

            if (isComplete) {
                if (isCorrect) {
                    this.model.set('_buttonState', 'correct');
                } else if (buttonState === 'submit' || buttonState === 'hideCorrectAnswer'){
                    this.model.set('_buttonState', 'showCorrectAnswer');
                } else {
                    this.model.set('_buttonState', 'hideCorrectAnswer');
                }
            } else {
                if (isEnabled) {
                    this.model.set('_buttonState', 'submit');
                } else {
                    this.model.set('_buttonState', 'reset');
                }
            }

        },

        // Used to setup the correct, incorrect and partly correct feedback
        setupFeedback: function() {

            if (this.model.get('_isCorrect')) {
                this.setupCorrectFeedback();
            } else if (this.isPartlyCorrect()) {
                this.setupPartlyCorrectFeedback();
            } else {
                this.setupIncorrectFeedback();
            }

        },

        // Used by the question to determine if the question is incorrect or partly correct
        // Should return a boolean
        isPartlyCorrect: function() {},

        setupCorrectFeedback: function() {

            this.model.set({
                feedbackTitle: this.model.get('title'), 
                feedbackMessage: this.model.get("_feedback").correct
            });

        },

        setupPartlyCorrectFeedback: function() {

            if (this.model.get('_attemptsLeft') === 0 || !this.model.get('_feedback')._partlyCorrect.notFinal) {
                this.model.set({
                    feedbackTitle: this.model.get('title'),
                    feedbackMessage: this.model.get('_feedback')._partlyCorrect.final
                });
            } else {
                this.model.set({
                    feedbackTitle: this.model.get('title'),
                    feedbackMessage: this.model.get('_feedback')._partlyCorrect.notFinal
                });
            }

        },

        setupIncorrectFeedback: function() {

            if (this.model.get('_attemptsLeft') === 0 || !this.model.get('_feedback')._incorrect.notFinal) {
                this.model.set({
                    feedbackTitle: this.model.get('title'),
                    feedbackMessage: this.model.get('_feedback')._incorrect.final
                });
            } else {
                this.model.set({
                    feedbackTitle: this.model.get('title'),
                    feedbackMessage: this.model.get('_feedback')._incorrect.notFinal
                });
            }

        },
        
        // Used to show feedback based upon whether _canShowFeedback is true
        showFeedback: function() {
                
            if (this.model.get('_canShowFeedback')) {
                Adapt.trigger('questionView:showFeedback', this);
            } else {
                Adapt.trigger('questionView:disabledFeedback', this);
            }

        },

        onResetClicked: function() {
            this.setQuestionAsReset();
            this.updateButtons();
            this.resetUserAnswer();
            this.resetQuestion();
        },

        setQuestionAsReset: function() {
            this.model.set({
                _isEnabled: true,
                _isSubmitted: false
            });
            this.$(".component-widget").removeClass("submitted");
        },

        // Used by the question view to reset the stored user answer
        resetUserAnswer: function() {},

        // Used by the question view to reset the look and feel of the component.
        // This could also include resetting item data
        // This is triggered when the reset button is clicked so it shouldn't
        // be a full reset
        resetQuestion: function() {},

        onShowCorrectAnswerClicked: function() {
            this.setQuestionAsShowCorrect();
            this.updateButtons();
            this.showCorrectAnswer();
        },

        setQuestionAsShowCorrect: function() {
            this.$(".component-widget")
                .addClass("submitted show-correct-answer")
                .removeClass("show-user-answer");
        },

        // Used by the question to display the correct answer to the user
        showCorrectAnswer: function() {},

        onHideCorrectAnswerClicked: function() {
            this.setQuestionAsHideCorrect();
            this.updateButtons();
            this.hideCorrectAnswer();
        },

        setQuestionAsHideCorrect: function() {
            this.$(".component-widget")
                .addClass("submitted" + (this.model.get('_hideUserAnswer') ? '' : ' show-user-answer' ) )
                .removeClass("show-correct-answer");
        },

        // Used by the question to display the users answer and
        // hide the correct answer
        // Should use the values stored in storeUserAnswer
        hideCorrectAnswer: function() {}
        
    }, {
        _isQuestionType: true
    });
    
    return QuestionView;

});
Пример #25
0
define(function(require) {

    var Handlebars = require('handlebars');
    var ComponentView = require('coreViews/componentView');
    var Adapt = require('coreJS/adapt');

    var QuestionView = ComponentView.extend({
    
        className: function() {
            return "component "
            + "question-component " 
            + this.model.get('_component')
            + "-component " + this.model.get('_id') 
            + " " + this.model.get('_classes')
            + " " + this.setVisibility()
            + " component-" + this.model.get('_layout')
            + " nth-child-" + this.options.nthChild;
        },
        
        preRender: function() {
            this.setupDefaultSettings();
            this.resetQuestion({resetAttempts:true, initialisingScreen:true});
            this.listenTo(this.model, 'change:_isEnabled', this.onEnabledChanged);
        },
        
        isCorrect: function() {
            return !!Math.floor(this.model.get('_numberOfCorrectAnswers') / this.model.get('_items').length);
        },
        
        isPartlyCorrect: function() {
            return !this.isCorrect() && this.model.get('_isAtLeastOneCorrectSelection');
        },
        
        getNumberOfCorrectAnswers: function() {
            var numberOfCorrectAnswers = 0;
            this.forEachAnswer(function(correct) {
                if(correct) numberOfCorrectAnswers++;
            });
            return numberOfCorrectAnswers;
        },
        
        getOptionSpecificFeedback: function() {
            // Check if option specific feedback has been set
            var selectedItem = this.getSelectedItems();
            if (selectedItem.hasOwnProperty('_feedback')) {
                return selectedItem._feedback;
            } else {
                if (this.isCorrect()) {
                    return this.model.get('_feedback').correct;
                } else if (this.isPartlyCorrect()) {
                    return this.model.get('_feedback')._partlyCorrect.final;
                } else {
                    return this.model.get('_feedback')._incorrect.final;
                }
            }
        },
    
        getOptionSpecificAudio: function() {
            return this.getSelectedItems().audio;
        },
        
        getSelectedItems: function() {
            var selectedItems = this.model.get('_selectedItems');
            // if no second item exists, return just the first, else return the array
            return !selectedItems[1] ? selectedItems[0] : selectedItems;
        },
        
        markQuestion: function() {
            var correctCount = this.getNumberOfCorrectAnswers();
            var score = this.model.get("_questionWeight") * correctCount / this.model.get('_items').length;

            this.model.set({
                "_numberOfCorrectAnswers": correctCount,
                "_score": score
            });
            this.isCorrect() ? this.onQuestionCorrect() : this.onQuestionIncorrect();
        },
        
        resetQuestion: function(properties) {
            if(!!properties.initialisingScreen && this.model.get('_isComplete')) {
                Adapt.trigger('questionView:reset', this);
            }
            this.model.set({"_isEnabled": this.model.get('_isComplete') ? this.model.get("_isEnabledOnRevisit") : true});
            
            if(this.model.get('_isEnabled')) {
                _.each(this.model.get('_selectedItems'), function(item) {item.selected = false}, this);
                this.model.set({
                    _isSubmitted: false,
                    _selectedItems: [],
                    _userAnswer: []
                });
                if(properties.resetAttempts === true) this.model.set("_attemptsLeft", this.model.get('_attempts'));
                if(properties.resetCorrect === true) {
                    this.model.set({
                        _isCorrect: false,
                        _isAtLeastOneCorrectSelection: false
                    });
                }
            }
        },
        
        setupDefaultSettings: function() {
            if (!this.model.has("_questionWeight")) {
                this.model.set("_questionWeight", Adapt.config.get("_questionWeight"));
            }
            if (!this.model.has("_buttons")) {
                this.model.set("_buttons", Adapt.course.get("_buttons"));
            } else {
                for(var key in this.model.get("_buttons")) {
                    var value=this.model.get("_buttons")[key];
                    if (!value) {
                        this.model.get("_buttons")[key] = Adapt.course.get("_buttons")[key];
                    }
                }
            }
        },
    
        showFeedback: function() {
            
            if(this.model.get('_selectable') === 1) {
                if(this.getOptionSpecificFeedback()) {
                    this.model.set('feedbackMessage', this.getOptionSpecificFeedback());
                }
            }

            if (this.model.get('_canShowFeedback')) {
                Adapt.trigger('questionView:showFeedback', this);
            } else {
                Adapt.trigger('questionView:disabledFeedback', this);
            }

        },
        
        showMarking: function() {
            _.each(this.model.get('_items'), function(item, i) {
                var $item = this.$('.component-item').eq(i);
                $item.addClass(item.correct ? 'correct' : 'incorrect');
            }, this);
        },
        
        showModelAnswer: function () {
            this.$(".component-widget").removeClass("user").addClass("model");
            this.onModelAnswerShown();
        },
        
        showUserAnswer: function() {
            this.$(".component-widget").removeClass("model").addClass("user");
            this.onUserAnswerShown();
        },
        
        onComplete: function(parameters) {
            this.model.set({
                _isComplete: true,
                _isEnabled: false,
                _isCorrect: !!parameters.correct
            });
            this.$(".component-widget").addClass("disabled");
            if(parameters.correct) this.$(".component-widget").addClass("correct");
            this.showMarking();
            this.showUserAnswer();
            Adapt.trigger('questionView:complete', this);
        },
    
        onModelAnswerClicked: function(event) {
            if(event) event.preventDefault();
            this.showModelAnswer();
        },
        
        onQuestionCorrect: function() {
            this.onComplete({correct: true});
            this.model.getParent("article").attributes.score ++;
            this.model.set({"feedbackTitle": this.model.get('title'), "feedbackMessage": this.model.get("_feedback").correct});
        },
        
        onQuestionIncorrect: function() {
            if (this.isPartlyCorrect()) {
                if (this.model.get('_attemptsLeft') === 0 || !this.model.get('_feedback')._partlyCorrect.notFinal) {
                    this.model.set({
                        "feedbackTitle": this.model.get('title'),
                        "feedbackMessage": this.model.get('_feedback')._partlyCorrect.final
                    });
                } else {
                    this.model.set({
                        "feedbackTitle": this.model.get('title'),
                        "feedbackMessage": this.model.get('_feedback')._partlyCorrect.notFinal
                    });
                }
            } else {
                if (this.model.get('_attemptsLeft') === 0 || !this.model.get('_feedback')._incorrect.notFinal) {
                    this.model.set({
                        "feedbackTitle": this.model.get('title'),
                        "feedbackMessage": this.model.get('_feedback')._incorrect.final
                    });
                } else {
                    this.model.set({
                        "feedbackTitle": this.model.get('title'),
                        "feedbackMessage": this.model.get('_feedback')._incorrect.notFinal
                    });
                }
            }

            if (Math.ceil(this.model.get("_attemptsLeft")/this.model.get("_attempts")) === 0) {
                this.onComplete({correct: false});
            }
        },
        
        onResetClicked: function(event) {
            if(event) event.preventDefault(); 
            this.resetQuestion({resetAttempts:false, resetCorrect:true});
            this.$(".component-widget").removeClass("submitted");
            this.resetItems();
        },
    
        onSubmitClicked: function(event) {
            event.preventDefault();
            if(!this.canSubmit()) {
                this.showInstructionError();
                this.onCannotSubmit();
                return;
            } 

            Adapt.tabHistory = $(event.currentTarget).parent('.inner');
        
            var attemptsLeft = this.model.get("_attemptsLeft") - 1;
            this.model.set({
                _isEnabled: false,
                _isSubmitted: true,
                _attemptsLeft: attemptsLeft
            });
            this.$(".component-widget").addClass("submitted");
            this.$(".component-instruction-inner").removeClass("validation-error");
            
            this.storeUserAnswer();
            this.markQuestion();
            this.showFeedback(); 
        },

        showInstructionError: function() {
            this.$(".component-instruction-inner").addClass("validation-error");
        },
    
        onUserAnswerClicked: function(event) {
            if(event) event.preventDefault();
            this.showUserAnswer();
        },

        onEnabledChanged: function () {},

        postRender: function() {
            ComponentView.prototype.postRender.apply(this);
            if(this.model.get('_isEnabled') == false) {
                this.showUserAnswer();
            }
        },
        
        /**
        * to be implemented by subclass
        */
        // compulsory methods
        canSubmit: function() {},
        forEachAnswer: function() {},
        // optional methods
        resetItems: function(){},
        onModelAnswerShown: function() {},
        onUserAnswerShown: function() {},
        storeUserAnswer: function() {},
        onCannotSubmit: function() {}
    }, {
        _isQuestionType: true
    });
    
    return QuestionView;

});
define(function(require) {

    var ComponentView = require('coreViews/componentView');
    var Adapt = require('coreJS/adapt');

    var AssessmentResultsTotalAudio = ComponentView.extend({

        events: {
            'inview': 'onInview',
            'click .audio-toggle': 'toggleAudio'
        },

        preRender: function () {

            this.audioIsEnabled = false;

            if(Adapt.course.get('_audio') && Adapt.course.get('_audio')._isEnabled && this.model.has('_audioAssessment') && this.model.get('_audioAssessment')._isEnabled) {
              this.setupAudio();
              this.audioIsEnabled = true;
            }

            this.setupEventListeners();
            this.setupModelResetEvent();
            this.checkIfVisible();
        },

        setupAudio: function() {
          // Set vars
          this.audioChannel = this.model.get("_audioAssessment")._channel;
          this.elementId = this.model.get("_id");
          this.audioFile = this.model.get("_audioAssessment")._media.src;

          this.onscreenTriggered = false;

          // Autoplay
          if(Adapt.audio.autoPlayGlobal || this.model.get("_audioAssessment")._autoplay){
              this.canAutoplay = true;
          } else {
              this.canAutoplay = false;
          }

          // Autoplay once
          if(Adapt.audio.autoPlayOnceGlobal == false){
              this.autoplayOnce = false;
          } else if(Adapt.audio.autoPlayOnceGlobal || this.model.get("_audioAssessment")._autoPlayOnce){
              this.autoplayOnce = true;
          } else {
            this.autoplayOnce = false;
          }

          // Hide controls if set in JSON or if audio is turned off
          if(this.model.get('_audioAssessment')._showControls==false || Adapt.audio.audioClip[this.audioChannel].status==0){
              this.$('.audio-inner button').hide();
          }
          this.$el.on("onscreen", _.bind(this.onscreen, this));
        },

        checkIfVisible: function() {

            var wasVisible = this.model.get("_isVisible");
            var isVisibleBeforeCompletion = this.model.get("_isVisibleBeforeCompletion") || false;

            var isVisible = wasVisible && isVisibleBeforeCompletion;

            var assessmentArticleModels = Adapt.assessment.get();
            if (assessmentArticleModels.length === 0) return;

            var isComplete = this.isComplete();

            if (!isVisibleBeforeCompletion) isVisible = isVisible || isComplete;

            this.model.set('_isVisible', true, {pluginName: "assessmentResultsTotalAudio"});

            // if assessment(s) already complete then render
            if (isComplete) this.onAssessmentComplete(Adapt.assessment.getState());
        },

        isComplete: function() {
            var isComplete = false;

            var assessmentArticleModels = Adapt.assessment.get();
            if (assessmentArticleModels.length === 0) return;

            for (var i = 0, l = assessmentArticleModels.length; i < l; i++) {
                var articleModel = assessmentArticleModels[i];
                var assessmentState = articleModel.getState();
                isComplete = assessmentState.isComplete;
                if (!isComplete) break;
            }

            if (!isComplete) {
                this.model.reset("hard", true);
            }

            return isComplete;
        },

        setupModelResetEvent: function() {
            if (this.model.onAssessmentsReset) return;
            this.model.onAssessmentsReset = function(state) {
                this.reset('hard', true);
            };
            this.model.listenTo(Adapt, 'assessments:reset', this.model.onAssessmentsReset);
        },

        postRender: function() {
            this.setReadyStatus();
        },
        setupEventListeners: function() {
            this.listenTo(Adapt, 'assessment:complete', this.onAssessmentComplete);
            this.listenToOnce(Adapt, 'remove', this.onRemove);
        },

        removeEventListeners: function() {;
            this.stopListening(Adapt, 'assessment:complete', this.onAssessmentComplete);
            this.stopListening(Adapt, 'remove', this.onRemove);
            this.$el.off("inview");
            this.$el.off('onscreen');
        },

        onAssessmentComplete: function(state) {
            this.model.set("_state", state);
            this.setFeedback();

            //show feedback component
            this.render();
            this.setFeedback();
        },

        onInview: function(event, visible, visiblePartX, visiblePartY) {
            if (visible) {
                if (visiblePartY === 'top') {
                    this._isVisibleTop = true;
                } else if (visiblePartY === 'bottom') {
                    this._isVisibleBottom = true;
                } else {
                    this._isVisibleTop = true;
                    this._isVisibleBottom = true;
                }

                if (this._isVisibleTop || this._isVisibleBottom) {
                    this.setCompletionStatus();
                }
            }
        },

        onscreen: function(event, measurements) {
            var visible = this.model.get('_isVisible');
            var isOnscreenY = measurements.percentFromTop < Adapt.audio.triggerPosition && measurements.percentFromTop > 0;
            var isOnscreenX = measurements.percentInviewHorizontal == 100;
            var isOnscreen = measurements.onscreen;

            // Check for element coming on screen
            if (visible && isOnscreenY && isOnscreenX && this.canAutoplay && this.onscreenTriggered == false) {
              // Check if audio is set to on
              if (Adapt.audio.audioClip[this.audioChannel].status == 1) {
                Adapt.trigger('audio:playAudio', this.audioFile, this.elementId, this.audioChannel);
              }
              // Set to false to stop autoplay when onscreen again
              if (this.autoplayOnce) {
                this.canAutoplay = false;
              }
              // Set to true to stop onscreen looping
              this.onscreenTriggered = true;
            }
            // Check when element is off screen
            if (visible && isOnscreen == false) {
              this.onscreenTriggered = false;
              Adapt.trigger('audio:onscreenOff', this.elementId, this.audioChannel);
            }
        },

        toggleAudio: function(event) {
            if (event) event.preventDefault();
            Adapt.audio.audioClip[this.audioChannel].onscreenID = "";
            if ($(event.currentTarget).hasClass('playing')) {
                Adapt.trigger('audio:pauseAudio', this.audioChannel);
            } else {
                Adapt.trigger('audio:playAudio', this.audioFile, this.elementId, this.audioChannel);
            }
        },

        setFeedback: function() {

            var completionBody = this.model.get("_completionBody");
            var feedbackBand = this.getFeedbackBand();

            var state = this.model.get("_state");
            state.feedbackBand = feedbackBand;
            state.feedback = feedbackBand.feedback;

            completionBody = this.stringReplace(completionBody, state);

            this.model.set("body", completionBody);

            this.model.set("instruction", state.feedbackBand.instruction);

            ///// Audio /////
            if (this.audioIsEnabled) {
                this.audioFile = state.feedbackBand._audio.src;
            }
            ///// End of Audio /////

        },

        getFeedbackBand: function() {
            var state = this.model.get("_state");

            var bands = this.model.get("_bands");
            var scoreAsPercent = state.scoreAsPercent;

            for (var i = (bands.length - 1); i >= 0; i--) {
                if (scoreAsPercent >= bands[i]._score) {
                    return bands[i];
                }
            }

            return "";
        },

        stringReplace: function(string, context) {
            //use handlebars style escaping for string replacement
            //only supports unescaped {{{ attributeName }}} and html escaped {{ attributeName }}
            //will string replace recursively until no changes have occured

            var changed = true;
            while (changed) {
                changed = false;
                for (var k in context) {
                    var contextValue = context[k];

                    switch (typeof contextValue) {
                    case "object":
                        continue;
                    case "number":
                        contextValue = Math.floor(contextValue);
                        break;
                    }

                    var regExNoEscaping = new RegExp("((\\{\\{\\{){1}[\\ ]*"+k+"[\\ ]*(\\}\\}\\}){1})","g");
                    var regExEscaped = new RegExp("((\\{\\{){1}[\\ ]*"+k+"[\\ ]*(\\}\\}){1})","g");

                    var preString = string;

                    string = string.replace(regExNoEscaping, contextValue);
                    var escapedText = $("<p>").text(contextValue).html();
                    string = string.replace(regExEscaped, escapedText);

                    if (string != preString) changed = true;

                }
            }

            return string;
        },

        onRemove: function() {
            this.removeEventListeners();
        }

    });

    Adapt.register("assessmentResultsTotalAudio", AssessmentResultsTotalAudio);

});