Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
// This file is part of Moodle - http://moodle.org/
2
//
3
// Moodle is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, either version 3 of the License, or
6
// (at your option) any later version.
7
//
8
// Moodle is distributed in the hope that it will be useful,
9
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
// GNU General Public License for more details.
12
//
13
// You should have received a copy of the GNU General Public License
14
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
15
 
16
/**
17
 * Competency rule config.
18
 *
19
 * @module     tool_lp/competencyruleconfig
20
 * @copyright  2015 Frédéric Massart - FMCorz.net
21
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22
 */
23
 
24
define(['jquery',
25
        'core/notification',
26
        'core/templates',
27
        'tool_lp/dialogue',
28
        'tool_lp/competency_outcomes',
29
        'core/str'],
30
        function($, Notification, Templates, Dialogue, Outcomes, Str) {
31
 
32
    /**
33
     * Competency rule class.
34
     *
35
     * When implementing this you should attach a listener to the event 'save'
36
     * on the instance. E.g.
37
     *
38
     * var config = new RuleConfig(tree, modules);
39
     * config.on('save', function(e, config) { ... });
40
     *
41
     * @param {competencytree} tree The competency tree.
42
     * @param {Array} rulesModules The modules containing the rules: [{ typeName: { amd: amdModule, name: ruleName }}].
43
     */
44
    var RuleConfig = function(tree, rulesModules) {
45
        this._eventNode = $('<div></div>');
46
        this._tree = tree;
47
        this._rulesModules = rulesModules;
48
        this._setUp();
49
    };
50
 
51
    /** @property {Object} The current competency. */
52
    RuleConfig.prototype._competency = null;
53
    /** @property {Node} The node we attach the events to. */
54
    RuleConfig.prototype._eventNode = null;
55
    /** @property {Array} Outcomes options. */
56
    RuleConfig.prototype._outcomesOption = null;
57
    /** @property {Dialogue} The dialogue. */
58
    RuleConfig.prototype._popup = null;
59
    /** @property {Promise} Resolved when the module is ready. */
60
    RuleConfig.prototype._ready = null;
61
    /** @property {Array} The rules. */
62
    RuleConfig.prototype._rules = null;
63
    /** @property {Array} The rules modules. */
64
    RuleConfig.prototype._rulesModules = null;
65
    /** @property {competencytree} The competency tree. */
66
    RuleConfig.prototype._tree = null;
67
 
68
    /**
69
     * After change.
70
     *
71
     * Triggered when a change occured.
72
     *
73
     * @method _afterChange
74
     * @protected
75
     */
76
    RuleConfig.prototype._afterChange = function() {
77
        if (!this._isValid()) {
78
            this._find('[data-action="save"]').prop('disabled', true);
79
        } else {
80
            this._find('[data-action="save"]').prop('disabled', false);
81
        }
82
    };
83
 
84
    /**
85
     * After change in rule's config.
86
     *
87
     * Triggered when a change occured in a specific rule config.
88
     *
89
     * @method _afterRuleConfigChange
90
     * @protected
91
     * @param {Event} e
92
     * @param {Rule} rule
93
     */
94
    RuleConfig.prototype._afterRuleConfigChange = function(e, rule) {
95
        if (rule != this._getRule()) {
96
            // This rule is not the current one any more, we can ignore.
97
            return;
98
        }
99
        this._afterChange();
100
    };
101
 
102
    /**
103
     * After render hook.
104
     *
105
     * @method _afterRender
106
     * @protected
107
     */
108
    RuleConfig.prototype._afterRender = function() {
109
        var self = this;
110
 
111
        self._find('[name="outcome"]').on('change', function() {
112
            self._switchedOutcome();
113
        }).trigger('change');
114
 
115
        self._find('[name="rule"]').on('change', function() {
116
            self._switchedRule();
117
        }).trigger('change');
118
 
119
        self._find('[data-action="save"]').on('click', function() {
120
            self._trigger('save', self._getConfig());
121
            self.close();
122
        });
123
 
124
        self._find('[data-action="cancel"]').on('click', function() {
125
            self.close();
126
        });
127
    };
128
 
129
    /**
130
     * Whether the current competency can be configured.
131
     *
132
     * @return {Boolean}
133
     * @method canBeConfigured
134
     */
135
    RuleConfig.prototype.canBeConfigured = function() {
136
        var can = false;
137
        $.each(this._rules, function(index, rule) {
138
            if (rule.canConfig()) {
139
                can = true;
140
                return;
141
            }
142
        });
143
        return can;
144
    };
145
 
146
    /**
147
     * Close the dialogue.
148
     *
149
     * @method close
150
     */
151
    RuleConfig.prototype.close = function() {
152
        this._popup.close();
153
        this._popup = null;
154
    };
155
 
156
    /**
157
     * Opens the picker.
158
     *
159
     * @method display
160
     * @returns {Promise}
161
     */
162
    RuleConfig.prototype.display = function() {
163
        var self = this;
164
        if (!self._competency) {
165
            return false;
166
        }
167
        return $.when(Str.get_string('competencyrule', 'tool_lp'), self._render())
168
        .then(function(title, render) {
169
            self._popup = new Dialogue(
170
                title,
171
                render[0],
172
                self._afterRender.bind(self),
173
                null,
174
                false,
175
                '515px'
176
            );
177
            return;
178
        }).fail(Notification.exception);
179
    };
180
 
181
    /**
182
     * Find a node in the dialogue.
183
     *
184
     * @param {String} selector
185
     * @return {JQuery}
186
     * @method _find
187
     * @protected
188
     */
189
    RuleConfig.prototype._find = function(selector) {
190
        return $(this._popup.getContent()).find(selector);
191
    };
192
 
193
    /**
194
     * Get the applicable outcome options.
195
     *
196
     * @return {Array}
197
     * @method _getApplicableOutcomesOptions
198
     * @protected
199
     */
200
    RuleConfig.prototype._getApplicableOutcomesOptions = function() {
201
        var self = this,
202
            options = [];
203
 
204
        $.each(self._outcomesOption, function(index, outcome) {
205
            options.push({
206
                code: outcome.code,
207
                name: outcome.name,
208
                selected: (outcome.code == self._competency.ruleoutcome) ? true : false,
209
            });
210
        });
211
 
212
        return options;
213
    };
214
 
215
    /**
216
     * Get the applicable rules options.
217
     *
218
     * @return {Array}
219
     * @method _getApplicableRulesOptions
220
     * @protected
221
     */
222
    RuleConfig.prototype._getApplicableRulesOptions = function() {
223
        var self = this,
224
            options = [];
225
 
226
        $.each(self._rules, function(index, rule) {
227
            if (!rule.canConfig()) {
228
                return;
229
            }
230
            options.push({
231
                name: self._getRuleName(rule.getType()),
232
                type: rule.getType(),
233
                selected: (rule.getType() == self._competency.ruletype) ? true : false,
234
            });
235
        });
236
 
237
        return options;
238
    };
239
 
240
    /**
241
     * Get the full config for the competency.
242
     *
243
     * @return {Object} Contains rule, ruleoutcome and ruleconfig.
244
     * @method _getConfig
245
     * @protected
246
     */
247
    RuleConfig.prototype._getConfig = function() {
248
        var rule = this._getRule();
249
        return {
250
            ruletype: rule ? rule.getType() : null,
251
            ruleconfig: rule ? rule.getConfig() : null,
252
            ruleoutcome: this._getOutcome()
253
        };
254
    };
255
 
256
    /**
257
     * Get the selected outcome code.
258
     *
259
     * @return {String}
260
     * @method _getOutcome
261
     * @protected
262
     */
263
    RuleConfig.prototype._getOutcome = function() {
264
        return this._find('[name="outcome"]').val();
265
    };
266
 
267
    /**
268
     * Get the selected rule.
269
     *
270
     * @return {null|Rule}
271
     * @method _getRule
272
     * @protected
273
     */
274
    RuleConfig.prototype._getRule = function() {
275
        var result,
276
            type = this._find('[name="rule"]').val();
277
 
278
        $.each(this._rules, function(index, rule) {
279
            if (rule.getType() == type) {
280
                result = rule;
281
                return;
282
            }
283
        });
284
 
285
        return result;
286
    };
287
 
288
    /**
289
     * Return the name of a rule.
290
     *
291
     * @param  {String} type The type of a rule.
292
     * @return {String}
293
     * @method _getRuleName
294
     * @protected
295
     */
296
    RuleConfig.prototype._getRuleName = function(type) {
297
        var self = this,
298
            name;
299
        $.each(self._rulesModules, function(index, modInfo) {
300
            if (modInfo.type == type) {
301
                name = modInfo.name;
302
                return;
303
            }
304
        });
305
        return name;
306
    };
307
 
308
    /**
309
     * Initialise the outcomes.
310
     *
311
     * @return {Promise}
312
     * @method _initOutcomes
313
     * @protected
314
     */
315
    RuleConfig.prototype._initOutcomes = function() {
316
        var self = this;
317
        return Outcomes.getAll().then(function(outcomes) {
318
            self._outcomesOption = outcomes;
319
            return;
320
        });
321
    };
322
 
323
    /**
324
     * Initialise the rules.
325
     *
326
     * @return {Promise}
327
     * @method _initRules
328
     * @protected
329
     */
330
    RuleConfig.prototype._initRules = function() {
331
        var self = this,
332
            promises = [];
333
        $.each(self._rules, function(index, rule) {
334
            var promise = rule.init().then(function() {
335
                rule.setTargetCompetency(self._competency);
336
                rule.on('change', self._afterRuleConfigChange.bind(self));
337
                return;
338
            }, function() {
339
                // Upon failure remove the rule, and resolve the promise.
340
                self._rules.splice(index, 1);
341
                return $.when();
342
            });
343
            promises.push(promise);
344
        });
345
 
346
        return $.when.apply($.when, promises);
347
    };
348
 
349
    /**
350
     * Whether or not the current config is valid.
351
     *
352
     * @return {Boolean}
353
     * @method _isValid
354
     * @protected
355
     */
356
    RuleConfig.prototype._isValid = function() {
357
        var outcome = this._getOutcome(),
358
            rule = this._getRule();
359
 
360
        if (outcome == Outcomes.NONE) {
361
            return true;
362
        } else if (!rule) {
363
            return false;
364
        }
365
 
366
        return rule.isValid();
367
    };
368
 
369
    /**
370
     * Register an event listener.
371
     *
372
     * @param {String} type The event type.
373
     * @param {Function} handler The event listener.
374
     * @method on
375
     */
376
    RuleConfig.prototype.on = function(type, handler) {
377
        this._eventNode.on(type, handler);
378
    };
379
 
380
    /**
381
     * Hook to executed before render.
382
     *
383
     * @method _preRender
384
     * @protected
385
     * @return {Promise}
386
     */
387
    RuleConfig.prototype._preRender = function() {
388
        // We need to have all the information about the rule plugins first.
389
        return this.ready();
390
    };
391
 
392
    /**
393
     * Returns a promise that is resolved when the module is ready.
394
     *
395
     * @return {Promise}
396
     * @method ready
397
     * @protected
398
     */
399
    RuleConfig.prototype.ready = function() {
400
        return this._ready.promise();
401
    };
402
 
403
    /**
404
     * Render the dialogue.
405
     *
406
     * @method _render
407
     * @protected
408
     * @return {Promise}
409
     */
410
    RuleConfig.prototype._render = function() {
411
        var self = this;
412
        return this._preRender().then(function() {
413
            var config;
414
 
415
            if (!self.canBeConfigured()) {
416
                config = false;
417
            } else {
418
                config = {};
419
                config.outcomes = self._getApplicableOutcomesOptions();
420
                config.rules = self._getApplicableRulesOptions();
421
            }
422
 
423
            var context = {
424
                competencyshortname: self._competency.shortname,
425
                config: config
426
            };
427
 
428
            return Templates.render('tool_lp/competency_rule_config', context);
429
        });
430
    };
431
 
432
    /**
433
     * Set the target competency.
434
     *
435
     * @param {Number} competencyId The target competency Id.
436
     * @method setTargetCompetencyId
437
     */
438
    RuleConfig.prototype.setTargetCompetencyId = function(competencyId) {
439
        var self = this;
440
        self._competency = self._tree.getCompetency(competencyId);
441
        $.each(self._rules, function(index, rule) {
442
            rule.setTargetCompetency(self._competency);
443
        });
444
    };
445
 
446
    /**
447
     * Set up the instance.
448
     *
449
     * @method _setUp
450
     * @protected
451
     */
452
    RuleConfig.prototype._setUp = function() {
453
        var self = this,
454
            promises = [],
455
            modules = [];
456
 
457
        self._ready = $.Deferred();
458
        self._rules = [];
459
 
460
        $.each(self._rulesModules, function(index, rule) {
461
            modules.push(rule.amd);
462
        });
463
 
464
        // Load all the modules.
465
        require(modules, function() {
466
            $.each(arguments, function(index, Module) {
467
                // Instantiate the rule and listen to it.
468
                var rule = new Module(self._tree);
469
                self._rules.push(rule);
470
            });
471
 
472
            // Load all the option values.
473
            promises.push(self._initRules());
474
            promises.push(self._initOutcomes());
475
 
476
            // Ready when everything is done.
477
            $.when.apply($.when, promises).always(function() {
478
                self._ready.resolve();
479
            });
480
        });
481
    };
482
 
483
    /**
484
     * Called when the user switches outcome.
485
     *
486
     * @method _switchedOutcome
487
     * @protected
488
     */
489
    RuleConfig.prototype._switchedOutcome = function() {
490
        var self = this,
491
            type = self._getOutcome();
492
 
493
        if (type == Outcomes.NONE) {
494
            // Reset to defaults.
495
            self._find('[data-region="rule-type"]').hide()
496
                .find('[name="rule"]').val(-1);
497
            self._find('[data-region="rule-config"]').empty().hide();
498
            self._afterChange();
499
            return;
500
        }
501
 
502
        self._find('[data-region="rule-type"]').show();
503
        self._find('[data-region="rule-config"]').show();
504
        self._afterChange();
505
    };
506
 
507
    /**
508
     * Called when the user switches rule.
509
     *
510
     * @method _switchedRule
511
     * @protected
512
     */
513
    RuleConfig.prototype._switchedRule = function() {
514
        var self = this,
515
            container = self._find('[data-region="rule-config"]'),
516
            rule = self._getRule();
517
 
518
        if (!rule) {
519
            container.empty().hide();
520
            self._afterChange();
521
            return;
522
        }
523
        rule.injectTemplate(container).then(function() {
524
            container.show();
525
            return;
526
        }).always(function() {
527
            self._afterChange();
528
        }).catch(function() {
529
            container.empty().hide();
530
        });
531
    };
532
 
533
    /**
534
     * Trigger an event.
535
     *
536
     * @param {String} type The type of event.
537
     * @param {Object} data The data to pass to the listeners.
538
     * @method _trigger
539
     * @protected
540
     */
541
    RuleConfig.prototype._trigger = function(type, data) {
542
        this._eventNode.trigger(type, [data]);
543
    };
544
 
545
    return /** @alias module:tool_lp/competencyruleconfig */ RuleConfig;
546
 
547
});