[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
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 * @package tool_lp 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 /** @type {Object} The current competency. */ 52 RuleConfig.prototype._competency = null; 53 /** @type {Node} The node we attach the events to. */ 54 RuleConfig.prototype._eventNode = null; 55 /** @type {Array} Outcomes options. */ 56 RuleConfig.prototype._outcomesOption = null; 57 /** @type {Dialogue} The dialogue. */ 58 RuleConfig.prototype._popup = null; 59 /** @type {Promise} Resolved when the module is ready. */ 60 RuleConfig.prototype._ready = null; 61 /** @type {Array} The rules. */ 62 RuleConfig.prototype._rules = null; 63 /** @type {Array} The rules modules. */ 64 RuleConfig.prototype._rulesModules = null; 65 /** @type {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 * @param {Number} competencyId The competency ID of the competency to work on. 160 * @method display 161 * @return {Promise} 162 */ 163 RuleConfig.prototype.display = function() { 164 var self = this; 165 if (!self._competency) { 166 return false; 167 } 168 return self._render().then(function(html) { 169 return Str.get_string('competencyrule', 'tool_lp').then(function(title) { 170 self._popup = new Dialogue( 171 title, 172 html, 173 self._afterRender.bind(self) 174 ); 175 }); 176 }).fail(Notification.exception); 177 }; 178 179 /** 180 * Find a node in the dialogue. 181 * 182 * @param {String} selector 183 * @return {JQuery} 184 * @method _find 185 * @protected 186 */ 187 RuleConfig.prototype._find = function(selector) { 188 return $(this._popup.getContent()).find(selector); 189 }; 190 191 /** 192 * Get the applicable outcome options. 193 * 194 * @return {Array} 195 * @method _getApplicableOutcomesOptions 196 * @protected 197 */ 198 RuleConfig.prototype._getApplicableOutcomesOptions = function() { 199 var self = this, 200 options = []; 201 202 $.each(self._outcomesOption, function(index, outcome) { 203 options.push({ 204 code: outcome.code, 205 name: outcome.name, 206 selected: (outcome.code == self._competency.ruleoutcome) ? true : false, 207 }); 208 }); 209 210 return options; 211 }; 212 213 /** 214 * Get the applicable rules options. 215 * 216 * @return {Array} 217 * @method _getApplicableRulesOptions 218 * @protected 219 */ 220 RuleConfig.prototype._getApplicableRulesOptions = function() { 221 var self = this, 222 options = []; 223 224 $.each(self._rules, function(index, rule) { 225 if (!rule.canConfig()) { 226 return; 227 } 228 options.push({ 229 name: self._getRuleName(rule.getType()), 230 type: rule.getType(), 231 selected: (rule.getType() == self._competency.ruletype) ? true : false, 232 }); 233 }); 234 235 return options; 236 }; 237 238 /** 239 * Get the full config for the competency. 240 * 241 * @return {Object} Contains rule, ruleoutcome and ruleconfig. 242 * @method _getConfig 243 * @protected 244 */ 245 RuleConfig.prototype._getConfig = function() { 246 var rule = this._getRule(); 247 return { 248 ruletype: rule ? rule.getType() : null, 249 ruleconfig: rule ? rule.getConfig() : null, 250 ruleoutcome: this._getOutcome() 251 }; 252 }; 253 254 /** 255 * Get the selected outcome code. 256 * 257 * @return {String} 258 * @method _getOutcome 259 * @protected 260 */ 261 RuleConfig.prototype._getOutcome = function() { 262 return this._find('[name="outcome"]').val(); 263 }; 264 265 /** 266 * Get the selected rule. 267 * 268 * @return {null|Rule} 269 * @method _getRule 270 * @protected 271 */ 272 RuleConfig.prototype._getRule = function() { 273 var result, 274 type = this._find('[name="rule"]').val(); 275 276 $.each(this._rules, function(index, rule) { 277 if (rule.getType() == type) { 278 result = rule; 279 return; 280 } 281 }); 282 283 return result; 284 }; 285 286 /** 287 * Return the name of a rule. 288 * 289 * @param {String} type The type of a rule. 290 * @return {String} 291 * @method _getRuleName 292 * @protected 293 */ 294 RuleConfig.prototype._getRuleName = function(type) { 295 var self = this, 296 name; 297 $.each(self._rulesModules, function(index, modInfo) { 298 if (modInfo.type == type) { 299 name = modInfo.name; 300 return; 301 } 302 }); 303 return name; 304 }; 305 306 /** 307 * Initialise the outcomes. 308 * 309 * @return {Promise} 310 * @method _initOutcomes 311 * @protected 312 */ 313 RuleConfig.prototype._initOutcomes = function() { 314 var self = this; 315 316 return Outcomes.getAll().then(function(outcomes) { 317 self._outcomesOption = outcomes; 318 }); 319 }; 320 321 /** 322 * Initialise the rules. 323 * 324 * @return {Promise} 325 * @method _initRules 326 * @protected 327 */ 328 RuleConfig.prototype._initRules = function() { 329 var self = this, 330 promises = []; 331 332 $.each(self._rules, function(index, rule) { 333 var promise = rule.init().then(function() { 334 rule.setTargetCompetency(self._competency); 335 rule.on('change', self._afterRuleConfigChange.bind(self)); 336 }, function() { 337 // Upon failure remove the rule, and resolve the promise. 338 self._rules.splice(index, 1); 339 return $.when(); 340 }); 341 promises.push(promise); 342 }); 343 344 return $.when.apply($.when, promises); 345 }; 346 347 /** 348 * Whether or not the current config is valid. 349 * 350 * @return {Boolean} 351 * @method _isValid 352 * @protected 353 */ 354 RuleConfig.prototype._isValid = function() { 355 var outcome = this._getOutcome(), 356 rule = this._getRule(); 357 358 if (outcome == Outcomes.NONE) { 359 return true; 360 } else if (!rule) { 361 return false; 362 } 363 364 return rule.isValid(); 365 }; 366 367 /** 368 * Register an event listener. 369 * 370 * @param {String} type The event type. 371 * @param {Function} handler The event listener. 372 * @method on 373 */ 374 RuleConfig.prototype.on = function(type, handler) { 375 this._eventNode.on(type, handler); 376 }; 377 378 /** 379 * Hook to executed before render. 380 * 381 * @method _preRender 382 * @protected 383 * @return {Promise} 384 */ 385 RuleConfig.prototype._preRender = function() { 386 // We need to have all the information about the rule plugins first. 387 return this.ready(); 388 }; 389 390 /** 391 * Returns a promise that is resolved when the module is ready. 392 * 393 * @return {Promise} 394 * @method ready 395 * @protected 396 */ 397 RuleConfig.prototype.ready = function() { 398 return this._ready.promise(); 399 }; 400 401 /** 402 * Render the dialogue. 403 * 404 * @method _render 405 * @protected 406 * @return {Promise} 407 */ 408 RuleConfig.prototype._render = function() { 409 var self = this; 410 return this._preRender().then(function() { 411 var config; 412 413 if (!self.canBeConfigured()) { 414 config = false; 415 } else { 416 config = {}; 417 config.outcomes = self._getApplicableOutcomesOptions(); 418 config.rules = self._getApplicableRulesOptions(); 419 } 420 421 var context = { 422 competencyshortname: self._competency.shortname, 423 config: config 424 }; 425 426 return Templates.render('tool_lp/competency_rule_config', context); 427 }); 428 }; 429 430 /** 431 * Set the target competency. 432 * 433 * @param {Number} competencyId The target competency Id. 434 * @method setTargetCompetencyId 435 */ 436 RuleConfig.prototype.setTargetCompetencyId = function(competencyId) { 437 var self = this; 438 self._competency = self._tree.getCompetency(competencyId); 439 $.each(self._rules, function(index, rule) { 440 rule.setTargetCompetency(self._competency); 441 }); 442 }; 443 444 /** 445 * Set up the instance. 446 * 447 * @method _setUp 448 * @protected 449 */ 450 RuleConfig.prototype._setUp = function() { 451 var self = this, 452 promises = [], 453 modules = []; 454 455 self._ready = $.Deferred(); 456 self._rules = []; 457 458 $.each(self._rulesModules, function(index, rule) { 459 modules.push(rule.amd); 460 }); 461 462 // Load all the modules. 463 require(modules, function() { 464 $.each(arguments, function(index, Module) { 465 // Instantiate the rule and listen to it. 466 var rule = new Module(self._tree); 467 self._rules.push(rule); 468 }); 469 470 // Load all the option values. 471 promises.push(self._initRules()); 472 promises.push(self._initOutcomes()); 473 474 // Ready when everything is done. 475 $.when.apply($.when, promises).always(function() { 476 self._ready.resolve(); 477 }); 478 }); 479 }; 480 481 /** 482 * Called when the user switches outcome. 483 * 484 * @method _switchedOutcome 485 * @protected 486 */ 487 RuleConfig.prototype._switchedOutcome = function() { 488 var self = this, 489 type = self._getOutcome(); 490 491 if (type == Outcomes.NONE) { 492 // Reset to defaults. 493 self._find('[data-region="rule-type"]').hide() 494 .find('[name="rule"]').val(-1); 495 self._find('[data-region="rule-config"]').empty().hide(); 496 self._afterChange(); 497 return; 498 } 499 500 self._find('[data-region="rule-type"]').show(); 501 self._find('[data-region="rule-config"]').show(); 502 self._afterChange(); 503 }; 504 505 /** 506 * Called when the user switches rule. 507 * 508 * @method _switchedRule 509 * @protected 510 */ 511 RuleConfig.prototype._switchedRule = function() { 512 var self = this, 513 container = self._find('[data-region="rule-config"]'), 514 rule = self._getRule(); 515 516 if (!rule) { 517 container.empty().hide(); 518 self._afterChange(); 519 return; 520 } 521 522 rule.injectTemplate(container).then(function() { 523 container.show(); 524 }, function() { 525 container.empty().hide(); 526 }).always(function() { 527 self._afterChange(); 528 }); 529 }; 530 531 /** 532 * Trigger an event. 533 * 534 * @param {String} type The type of event. 535 * @param {Object} data The data to pass to the listeners. 536 * @method _trigger 537 * @protected 538 */ 539 RuleConfig.prototype._trigger = function(type, data) { 540 this._eventNode.trigger(type, [data]); 541 }; 542 543 return /** @alias module:tool_lp/competencyruleconfig */ RuleConfig; 544 545 });
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |