Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
/**
2
 * A collection of utility classes for use with slots.
3
 *
4
 * @module moodle-mod_quiz-util
5
 * @submodule moodle-mod_quiz-util-slot
6
 */
7
 
8
Y.namespace('Moodle.mod_quiz.util.slot');
9
 
10
/**
11
 * A collection of utility classes for use with slots.
12
 *
13
 * @class Moodle.mod_quiz.util.slot
14
 * @static
15
 */
16
Y.Moodle.mod_quiz.util.slot = {
17
    CSS: {
18
        SLOT: 'slot',
19
        QUESTIONTYPEDESCRIPTION: 'qtype_description',
20
        CANNOT_DEPEND: 'question_dependency_cannot_depend'
21
    },
22
    CONSTANTS: {
23
        SLOTIDPREFIX: 'slot-',
24
        QUESTION: M.util.get_string('question', 'moodle')
25
    },
26
    SELECTORS: {
27
        SLOT: 'li.slot',
28
        INSTANCENAME: '.instancename',
29
        NUMBER: 'span.slotnumber',
30
        PAGECONTENT: 'div#page-content',
31
        PAGEBREAK: 'span.page_split_join_wrapper',
32
        ICON: '.icon',
33
        QUESTIONTYPEDESCRIPTION: '.qtype_description',
34
        SECTIONUL: 'ul.section',
35
        DEPENDENCY_WRAPPER: '.question_dependency_wrapper',
36
        DEPENDENCY_LINK: '.question_dependency_wrapper .cm-edit-action',
37
        DEPENDENCY_ICON: '.question_dependency_wrapper .icon'
38
    },
39
 
40
    /**
41
     * Retrieve the slot item from one of it's child Nodes.
42
     *
43
     * @method getSlotFromComponent
44
     * @param slotcomponent {Node} The component Node.
45
     * @return {Node|null} The Slot Node.
46
     */
47
    getSlotFromComponent: function(slotcomponent) {
48
        return Y.one(slotcomponent).ancestor(this.SELECTORS.SLOT, true);
49
    },
50
 
51
    /**
52
     * Determines the slot ID for the provided slot.
53
     *
54
     * @method getId
55
     * @param slot {Node} The slot to find an ID for.
56
     * @return {Number|false} The ID of the slot in question or false if no ID was found.
57
     */
58
    getId: function(slot) {
59
        // We perform a simple substitution operation to get the ID.
60
        var id = slot.get('id').replace(
61
                this.CONSTANTS.SLOTIDPREFIX, '');
62
 
63
        // Attempt to validate the ID.
64
        id = parseInt(id, 10);
65
        if (typeof id === 'number' && isFinite(id)) {
66
            return id;
67
        }
68
        return false;
69
    },
70
 
71
    /**
72
     * Determines the slot name for the provided slot.
73
     *
74
     * @method getName
75
     * @param slot {Node} The slot to find a name for.
76
     * @return {string|false} The name of the slot in question or false if no ID was found.
77
     */
78
    getName: function(slot) {
79
        var instance = slot.one(this.SELECTORS.INSTANCENAME);
80
        if (instance) {
81
            return instance.get('firstChild').get('data');
82
        }
83
        return null;
84
    },
85
 
86
    /**
87
     * Determines the slot number for the provided slot.
88
     *
89
     * @method getNumber
90
     * @param slot {Node} The slot to find the number for.
91
     * @return {Number|false} The number of the slot in question or false if no number was found.
92
     */
93
    getNumber: function(slot) {
94
        if (!slot) {
95
            return false;
96
        }
97
        // We perform a simple substitution operation to get the number.
98
        var number = slot.one(this.SELECTORS.NUMBER).get('text').replace(
99
                        this.CONSTANTS.QUESTION, '');
100
        // Attempt to validate the ID.
101
        number = parseInt(number, 10);
102
        if (typeof number === 'number' && isFinite(number)) {
103
            return number;
104
        }
105
        return false;
106
    },
107
 
108
    /**
109
     * Updates the slot number for the provided slot.
110
     *
111
     * @method setNumber
112
     * @param slot {Node} The slot to update the number for.
113
     * @return void
114
     */
115
    setNumber: function(slot, number) {
116
        var numbernode = slot.one(this.SELECTORS.NUMBER);
117
        numbernode.setHTML('<span class="accesshide">' + this.CONSTANTS.QUESTION + '</span> ' + number);
118
    },
119
 
120
    /**
121
     * Returns a list of all slot elements on the page.
122
     *
123
     * @method getSlots
124
     * @return {node[]} An array containing slot nodes.
125
     */
126
    getSlots: function() {
127
        return Y.all(this.SELECTORS.PAGECONTENT + ' ' + this.SELECTORS.SECTIONUL + ' ' + this.SELECTORS.SLOT);
128
    },
129
 
130
    /**
131
     * Returns a list of all slot elements on the page that have numbers. Excudes description questions.
132
     *
133
     * @method getSlots
134
     * @return {node[]} An array containing slot nodes.
135
     */
136
    getNumberedSlots: function() {
137
        var selector = this.SELECTORS.PAGECONTENT + ' ' + this.SELECTORS.SECTIONUL;
138
            selector += ' ' + this.SELECTORS.SLOT + ':not(' + this.SELECTORS.QUESTIONTYPEDESCRIPTION + ')';
139
        return Y.all(selector);
140
    },
141
 
142
    /**
143
     * Returns the previous slot to the given slot.
144
     *
145
     * @method getPrevious
146
     * @param slot Slot node
147
     * @return {node|false} The previous slot node or false.
148
     */
149
    getPrevious: function(slot) {
150
        return slot.previous(this.SELECTORS.SLOT);
151
    },
152
 
153
    /**
154
     * Returns the previous numbered slot to the given slot.
155
     *
156
     * Ignores slots containing description question types.
157
     *
158
     * @method getPrevious
159
     * @param slot Slot node
160
     * @return {node|false} The previous slot node or false.
161
     */
162
    getPreviousNumbered: function(slot) {
163
        var previous = slot.previous(this.SELECTORS.SLOT + ':not(' + this.SELECTORS.QUESTIONTYPEDESCRIPTION + ')');
164
        if (previous) {
165
            return previous;
166
        }
167
 
168
        var section = slot.ancestor('li.section').previous('li.section');
169
        while (section) {
170
            var questions = section.all(this.SELECTORS.SLOT + ':not(' + this.SELECTORS.QUESTIONTYPEDESCRIPTION + ')');
171
            if (questions.size() > 0) {
172
                return questions.item(questions.size() - 1);
173
            }
174
            section = section.previous('li.section');
175
        }
176
        return false;
177
    },
178
 
179
    /**
180
     * Reset the order of the numbers given to each slot.
181
     *
182
     * @method reorderSlots
183
     * @return void
184
     */
185
    reorderSlots: function() {
186
        // Get list of slot nodes.
187
        var slots = this.getSlots();
188
        // Loop through slots incrementing the number each time.
189
        slots.each(function(slot) {
190
 
191
            if (!Y.Moodle.mod_quiz.util.page.getPageFromSlot(slot)) {
192
                // Move the next page to the front.
193
                var nextpage = slot.next(Y.Moodle.mod_quiz.util.page.SELECTORS.PAGE);
194
                slot.swap(nextpage);
195
            }
196
 
197
            var previousSlot = this.getPreviousNumbered(slot),
198
                previousslotnumber = 0;
199
            if (slot.hasClass(this.CSS.QUESTIONTYPEDESCRIPTION)) {
200
                return;
201
            }
202
 
203
            if (previousSlot) {
204
                previousslotnumber = this.getNumber(previousSlot);
205
            }
206
 
207
            // Set slot number.
208
            this.setNumber(slot, previousslotnumber + 1);
209
        }, this);
210
    },
211
 
212
    /**
213
     * Add class only-has-one-slot to those sections that need it.
214
     *
215
     * @method updateOneSlotSections
216
     * @return void
217
     */
218
    updateOneSlotSections: function() {
219
        Y.all('.mod-quiz-edit-content ul.slots li.section').each(function(section) {
220
            if (section.all(this.SELECTORS.SLOT).size() > 1) {
221
                section.removeClass('only-has-one-slot');
222
            } else {
223
                section.addClass('only-has-one-slot');
224
            }
225
        }, this);
226
    },
227
 
228
    /**
229
     * Remove a slot and related elements from the list of slots.
230
     *
231
     * @method remove
232
     * @param slot Slot node
233
     * @return void
234
     */
235
    remove: function(slot) {
236
        var page = Y.Moodle.mod_quiz.util.page.getPageFromSlot(slot);
237
        slot.remove();
238
        // Is the page empty.
239
        if (!Y.Moodle.mod_quiz.util.page.isEmpty(page)) {
240
            return;
241
        }
242
        // If so remove it. Including add menu and page break.
243
        Y.Moodle.mod_quiz.util.page.remove(page);
244
    },
245
 
246
    /**
247
     * Returns a list of all page break elements on the page.
248
     *
249
     * @method getPageBreaks
250
     * @return {node[]} An array containing page break nodes.
251
     */
252
    getPageBreaks: function() {
253
        var selector = this.SELECTORS.PAGECONTENT + ' ' + this.SELECTORS.SECTIONUL;
254
            selector += ' ' + this.SELECTORS.SLOT + this.SELECTORS.PAGEBREAK;
255
        return Y.all(selector);
256
    },
257
 
258
    /**
259
     * Retrieve the page break element item from the given slot.
260
     *
261
     * @method getPageBreak
262
     * @param slot Slot node
263
     * @return {Node|null} The Page Break Node.
264
     */
265
    getPageBreak: function(slot) {
266
        return Y.one(slot).one(this.SELECTORS.PAGEBREAK);
267
    },
268
 
269
    /**
270
     * Add a page break and related elements to the list of slots.
271
     *
272
     * @method addPageBreak
273
     * @param beforenode Int | Node | HTMLElement | String to add
274
     * @return pagebreak PageBreak node
275
     */
276
    addPageBreak: function(slot) {
277
        var nodetext = M.mod_quiz.resource_toolbox.get('config').addpageiconhtml;
278
        nodetext = nodetext.replace('%%SLOT%%', this.getNumber(slot));
279
        var pagebreak = Y.Node.create(nodetext);
280
        slot.one('div').insert(pagebreak, 'after');
281
        return pagebreak;
282
    },
283
 
284
    /**
285
     * Remove a pagebreak from the given slot.
286
     *
287
     * @method removePageBreak
288
     * @param slot Slot node
289
     * @return boolean
290
     */
291
    removePageBreak: function(slot) {
292
        var pagebreak = this.getPageBreak(slot);
293
        if (!pagebreak) {
294
            return false;
295
        }
296
        pagebreak.remove();
297
        return true;
298
    },
299
 
300
    /**
301
     * Reorder each pagebreak by iterating through each related slot.
302
     *
303
     * @method reorderPageBreaks
304
     * @return void
305
     */
306
    reorderPageBreaks: function() {
307
        // Get list of slot nodes.
308
        var slots = this.getSlots();
309
        var slotnumber = 0;
310
        // Loop through slots incrementing the number each time.
311
        slots.each(function(slot, key) {
312
            slotnumber++;
313
            var pagebreak = this.getPageBreak(slot);
314
            var nextitem = slot.next('li.activity');
315
            if (!nextitem) {
316
                // Last slot in a section. Should not have an icon.
317
                return;
318
            }
319
 
320
            // No pagebreak and not last slot. Add one.
321
            if (!pagebreak) {
322
                pagebreak = this.addPageBreak(slot);
323
            }
324
 
325
            // Remove last page break if there is one.
326
            if (pagebreak && key === slots.size() - 1) {
327
                this.removePageBreak(slot);
328
            }
329
 
330
            // Get page break anchor element.
331
            var pagebreaklink = pagebreak.get('childNodes').item(0);
332
 
333
            // Get the correct title.
334
            var action = '';
335
            var iconname = '';
336
            if (Y.Moodle.mod_quiz.util.page.isPage(nextitem)) {
337
                action = 'removepagebreak';
338
                iconname = 'e/remove_page_break';
339
            } else {
340
                action = 'addpagebreak';
341
                iconname = 'e/insert_page_break';
342
            }
343
 
344
            // Update the link and image titles
345
            pagebreaklink.set('title', M.util.get_string(action, 'quiz'));
346
            pagebreaklink.setData('action', action);
347
            // Update the image title.
348
            var icon = pagebreaklink.one(this.SELECTORS.ICON);
349
            icon.set('title', M.util.get_string(action, 'quiz'));
350
            icon.set('alt', M.util.get_string(action, 'quiz'));
351
 
352
            // Update the image src.
353
            icon.set('src', M.util.image_url(iconname));
354
 
355
            // Get anchor url parameters as an associative array.
356
            var params = Y.QueryString.parse(pagebreaklink.get('href'));
357
            // Update slot number.
358
            params.slot = slotnumber;
359
            // Create the new url.
360
            var newurl = '';
361
            for (var index in params) {
362
                if (newurl.length) {
363
                    newurl += "&";
364
                }
365
                newurl += index + "=" + params[index];
366
            }
367
            // Update the anchor.
368
            pagebreaklink.set('href', newurl);
369
        }, this);
370
    },
371
 
372
    /**
373
     * Update the dependency icons.
374
     *
375
     * @method updateAllDependencyIcons
376
     * @return void
377
     */
378
    updateAllDependencyIcons: function() {
379
        // Get list of slot nodes.
380
        var slots = this.getSlots(),
381
            slotnumber = 0,
382
            previousslot = null;
383
        // Loop through slots incrementing the number each time.
384
        slots.each(function(slot) {
385
            slotnumber++;
386
 
387
            if (slotnumber == 1 || previousslot.getData('canfinish') === '0') {
388
                slot.one(this.SELECTORS.DEPENDENCY_WRAPPER).addClass(this.CSS.CANNOT_DEPEND);
389
            } else {
390
                slot.one(this.SELECTORS.DEPENDENCY_WRAPPER).removeClass(this.CSS.CANNOT_DEPEND);
391
            }
392
            this.updateDependencyIcon(slot, null);
393
 
394
            previousslot = slot;
395
        }, this);
396
    },
397
 
398
    /**
399
     * Update the slot icon to indicate the new requiresprevious state.
400
     *
401
     * @method slot Slot node
402
     * @method requiresprevious Whether this node now requires the previous one.
403
     * @return void
404
     */
405
    updateDependencyIcon: function(slot, requiresprevious) {
406
        var link = slot.one(this.SELECTORS.DEPENDENCY_LINK);
407
        var icon = slot.one(this.SELECTORS.DEPENDENCY_ICON);
408
        var previousSlot = this.getPrevious(slot);
409
        var a = {thisq: this.getNumber(slot)};
410
        if (previousSlot) {
411
            a.previousq = this.getNumber(previousSlot);
412
        }
413
 
414
        if (requiresprevious === null) {
415
            requiresprevious = link.getData('action') === 'removedependency';
416
        }
417
 
418
        if (requiresprevious) {
419
            link.set('title', M.util.get_string('questiondependencyremove', 'quiz', a));
420
            link.setData('action', 'removedependency');
421
            window.require(['core/templates'], function(Templates) {
422
                Templates.renderPix('t/locked', 'core', M.util.get_string('questiondependsonprevious', 'quiz')).then(
423
                    function(html) {
424
                        icon.replace(html);
425
                    }
426
                );
427
            });
428
        } else {
429
            link.set('title', M.util.get_string('questiondependencyadd', 'quiz', a));
430
            link.setData('action', 'adddependency');
431
            window.require(['core/templates'], function(Templates) {
432
                Templates.renderPix('t/unlocked', 'core', M.util.get_string('questiondependencyfree', 'quiz')).then(
433
                    function(html) {
434
                        icon.replace(html);
435
                    }
436
                );
437
            });
438
        }
439
    }
440
};