assets/js/builder/Views/Quiz.js
/**
* 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 ) );
} );