gocodebox/lifterlms

View on GitHub
assets/js/builder/Views/Quiz.js

Summary

Maintainability
C
1 day
Test Coverage
/**
 * Single Quiz View.
 *
 * @since 3.16.0
 * @version 5.4.0
 */
define( [
        'Models/Quiz',
        'Views/Popover',
        'Views/PostSearch',
        'Views/QuestionBank',
        'Views/QuestionList',
        'Views/SettingsFields',
        'Views/_Detachable',
        'Views/_Editable',
        'Views/_Subview',
        'Views/_Trashable'
    ], function(
        QuizModel,
        Popover,
        PostSearch,
        QuestionBank,
        QuestionList,
        SettingsFields,
        Detachable,
        Editable,
        Subview,
        Trashable
    ) {

    return Backbone.View.extend( _.defaults( {

        /**
         * Current view state.
         *
         * @type {String}
         */
        state: 'default',

        /**
         * Current Subviews.
         *
         * @type {Object}
         */
        views: {
            settings: {
                class: SettingsFields,
                instance: null,
                state: 'default',
            },
            bank: {
                class: QuestionBank,
                instance: null,
                state: 'default',
            },
            list: {
                class: QuestionList,
                instance: null,
                state: 'default',
            },
        },

        el: '#llms-editor-quiz',

        /**
         * Events.
         *
         * @type {Object}
         */
        events: _.defaults( {
            'click #llms-existing-quiz': 'add_existing_quiz_click',
            'click #llms-new-quiz': 'add_new_quiz',
            'click #llms-show-question-bank': 'show_tools',
            'click .bulk-toggle': 'bulk_toggle',
            // 'keyup #llms-question-bank-filter': 'filter_question_types',
            // 'search #llms-question-bank-filter': 'filter_question_types',
        }, Detachable.events, Editable.events, Trashable.events ),

        /**
         * Wrapper Tag name.
         *
         * @type {String}
         */
        tagName: 'div',

        /**
         * Get the underscore template
         *
         * @type {[type]}
         */
        template: wp.template( 'llms-quiz-template' ),

        /**
         * Initialization callback func (renders the element on screen).
         *
         * @since 3.16.0
         * @since 3.19.2 Unknown.
         *
         * @return {Void}
         */
        initialize: function( data ) {

            this.lesson = data.lesson;

            // Initialize the model if the quiz is enabled or it's disabled but we still have data for a quiz.
            if ( 'yes' === this.lesson.get( 'quiz_enabled' ) || ! _.isEmpty( this.lesson.get( 'quiz' ) ) ) {

                this.model = this.lesson.get( 'quiz' );

                /**
                 * @todo  this is a terrible terrible patch
                 *        I've spent nearly 3 days trying to figure out how to not use this line of code
                 *        ISSUE REPRODUCTION:
                 *        Open course builder
                 *        Open a lesson (A) and add a quiz
                 *        Switch to a new lesson (B)
                 *        Add a new quiz
                 *        Return to lesson A and the quizzes parent will be set to LESSON B
                 *        This will happen for *every* quiz in the builder...
                 *        Adding this set_parent on init guarantees that the quizzes correct parent is set
                 *        after adding new quizzes to other lessons
                 *        it's awful and it's gross...
                 *        I'm confused and tired and going to miss release dates again because of it
                 */
                this.model.set_parent( this.lesson );

                this.listenTo( this.model, 'change:_points', this.render_points );

            }

            this.on( 'model-trashed', this.on_trashed );

        },

        /**
         * Compiles the template and renders the view.
         *
         * @since 3.16.0
         * @since 3.19.2 Unknown.
         *
         * @return {Self} For chaining.
         */
        render: function() {

            this.$el.html( this.template( this.model ) );

            // Render the quiz builder.
            if ( this.model ) {

                // Don't allow interaction until questions are lazy loaded.
                LLMS.Spinner.start( this.$el );

                this.render_subview( 'settings', {
                    el: '#llms-quiz-settings-fields',
                    model: this.model,
                } );

                this.init_datepickers();
                this.init_selects();

                this.render_subview( 'bank', {
                    collection: window.llms_builder.questions,
                } );

                var last_group = null,
                    group = null;
                // Let all the question types reference the quiz for adding questions quickly.
                this.get_subview( 'bank' ).instance.viewManager.each( function( view ) {

                    view.quiz = this.model;

                    group = view.model.get( 'group' ).name;

                    if ( last_group !== group ) {
                        last_group = group;
                        view.$el.before( '<li class="llms-question-bank-header"><h4>' + group + '</h4></li>' );
                    }

                }, this );

                this.model.load_questions( _.bind( function( err ) {

                    if ( err ) {
                        alert( LLMS.l10n.translate( 'An error occurred while trying to load the questions. Please refresh the page and try again.' ) );
                        return this;
                    }

                    LLMS.Spinner.stop( this.$el );
                    this.render_subview( 'list', {
                        el: '#llms-quiz-questions',
                        collection: this.model.get( 'questions' ),
                    } );
                    var list = this.get_subview( 'list' ).instance;
                    list.quiz = this;
                    list.collection.on( 'add', function() {
                        list.collection.trigger( 'reorder' );
                    }, this );
                    list.on( 'sortStart', list.sortable_start );
                    list.on( 'sortStop', list.sortable_stop );

                }, this ) );

                this.model.on( 'new-question-added', function() {
                    var $questions = this.$el.find( '#llms-quiz-questions' );
                    $questions.animate( { scrollTop: $questions.prop( 'scrollHeight' ) }, 200 );
                }, this );

            }

            return this;

        },

        /**
         * On quiz points update, update the value of the Total Points area in the header.
         *
         * @since 3.17.6
         *
         * @param {Object} quiz   Instance of the quiz model.
         * @param {Int}    points Updated number of points.
         * @return {Void}
         */
        render_points: function( quiz, points ) {

            this.$el.find( '#llms-quiz-total-points' ).text( points );

        },

        /**
         * Bulk expand / collapse question buttons.
         *
         * @since 3.16.0
         *
         * @param {Object} Event JS event object.
         * @return {Void}
         */
        bulk_toggle: function( event ) {

            var expanded = ( 'expand' === $( event.target ).attr( 'data-action' ) );

            this.model.get( 'questions' ).each( function( question ) {
                question.set( '_expanded', expanded );
            } );

        },

        /**
         * Adds a new quiz to a lesson which currently has no quiz associated with it.
         *
         * @since 3.16.0
         *
         * @return {Void}
         */
        add_new_quiz: function() {

            var quiz = this.lesson.get( 'quiz' );
            if ( _.isEmpty( quiz ) ) {
                quiz = this.lesson.add_quiz();
            } else {
                this.lesson.set( 'quiz_enabled', 'yes' );
            }

            this.model = quiz;
            this.render();

        },


        /**
         * Add an existing quiz to a lesson.
         *
         * @since 3.16.0
         * @since 3.24.0 Unknown.
         * @since 5.4.0 Use author id instead of the quiz author object.
         *
         * @param {Object} event JS event object.
         * @return {Void}
         */
        add_existing_quiz: function( event ) {

            this.post_search_popover.hide();

            var quiz = event.data;

            if ( 'clone' === event.action ) {

                quiz = _.prepareQuizObjectForCloning( quiz );

            } else {

                // Use author id instead of the quiz author object.
                quiz = _.prepareExistingPostObjectDataForAddingOrCloning( quiz );
                quiz._forceSync = true;

            }

            delete quiz.lesson_id;

            this.lesson.add_quiz( quiz );
            this.model = this.lesson.get( 'quiz' );
            this.render();

        },

        /**
         * Open add existing quiz popover.
         *
         * @since 3.16.12
         *
         * @param {Object} event JS event object.
         * @return {Void}
         */
        add_existing_quiz_click: function( event ) {

            event.preventDefault();

            this.post_search_popover = new Popover( {
                el: '#llms-existing-quiz',
                args: {
                    backdrop: true,
                    closeable: true,
                    container: '.wrap.lifterlms.llms-builder',
                    dismissible: true,
                    placement: 'left',
                    width: 480,
                    title: LLMS.l10n.translate( 'Add Existing Quiz' ),
                    content: new PostSearch( {
                        post_type: 'llms_quiz',
                        searching_message: LLMS.l10n.translate( 'Search for existing quizzes...' ),
                    } ).render().$el,
                    onHide: function() {
                        Backbone.pubSub.off( 'quiz-search-select' );
                    },
                }
            } );

            this.post_search_popover.show();
            Backbone.pubSub.once( 'quiz-search-select', this.add_existing_quiz, this );

        },

        // filter_question_types: _.debounce( function( event ) {

        //     var term = $( event.target ).val();

        //     this.QuestionBankView.viewManager.each( function( view ) {
        //         if ( ! term ) {
        //             view.clear_filter();
        //         } else {
        //             view.filter( term );
        //         }
        //     } );


        // }, 300 ),

        /**
         * Callback function when the quiz has been deleted.
         *
         * @since 3.16.6
         *
         * @param {Oject} quiz Quiz Model.
         * @return {Void}
         */
        on_trashed: function( quiz ) {

            this.lesson.set( 'quiz_enabled', 'no' );
            this.lesson.set( 'quiz', '' );

            delete this.model;

            this.render();

        },

        /**
         * "Add Question" button click event.
         *
         * @since 3.16.0
         *
         * Creates a popover with question type list interface.
         *
         * @return {Void}
         */
        show_tools: function() {

            // Create popover,
            var pop = new Popover( {
                el: '#llms-show-question-bank',
                args: {
                    backdrop: true,
                    closeable: true,
                    container: '#llms-builder-sidebar',
                    dismissible: true,
                    placement: 'top-left',
                    width: 'calc( 100% - 40px )',
                    title: LLMS.l10n.translate( 'Add a Question' ),
                    url: '#llms-quiz-tools',
                }
            } );

            // Show it.
            pop.show();

            // If a question is added, hide the popover.
            this.model.on( 'new-question-added', function() {
                pop.hide();
            } );

        },

        get_question_list: function( options ) {
            return new QuestionList( options );
        }

    }, Detachable, Editable, Subview, Trashable, SettingsFields ) );

} );