Merge branch 'cbe' of git://github.com/FMCorz/moodle

This commit is contained in:
David Monllao
2016-04-19 12:37:11 +08:00
404 changed files with 67678 additions and 15 deletions
+50
View File
@@ -0,0 +1,50 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* File.
*
* @package core_competency
* @copyright 2016 Frédéric Massart - FMCorz.net
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
// Save processing when the user will not be able to access anything.
if (has_capability('moodle/site:config', $systemcontext)) {
$parentname = 'competencies';
// Settings page.
$settings = new admin_settingpage('competencysettings', new lang_string('competenciessettings', 'core_competency'),
'moodle/site:config', false);
$ADMIN->add($parentname, $settings);
// Load the full tree of settings.
if ($ADMIN->fulltree) {
$setting = new admin_setting_configcheckbox('core_competency/enabled',
new lang_string('enablecompetencies', 'core_competency'),
new lang_string('enablecompetencies_desc', 'core_competency'), 1);
$settings->add($setting);
$setting = new admin_setting_configcheckbox('core_competency/pushcourseratingstouserplans',
new lang_string('pushcourseratingstouserplans', 'core_competency'),
new lang_string('pushcourseratingstouserplans_desc', 'core_competency'), 1);
$settings->add($setting);
}
}
+1
View File
@@ -30,6 +30,7 @@ if ($hassiteconfig) {
$ADMIN->add('root', new admin_category('users', new lang_string('users','admin')));
$ADMIN->add('root', new admin_category('courses', new lang_string('courses','admin')));
$ADMIN->add('root', new admin_category('grades', new lang_string('grades')));
$ADMIN->add('root', new admin_category('competencies', new lang_string('competencies', 'core_competency')));
$ADMIN->add('root', new admin_category('badges', new lang_string('badges'), empty($CFG->enablebadges)));
$ADMIN->add('root', new admin_category('location', new lang_string('location','admin')));
$ADMIN->add('root', new admin_category('language', new lang_string('language')));
+228
View File
@@ -0,0 +1,228 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Class exposing the api for the cohortroles tool.
*
* @package tool_cohortroles
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_cohortroles;
use stdClass;
use context_system;
use core_competency\invalid_persistent_exception;
/**
* Class for doing things with cohort roles.
*
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class api {
/**
* Create a cohort role assignment from a record containing all the data for the class.
*
* Requires moodle/role:manage capability at the system context.
*
* @param stdClass $record Record containing all the data for an instance of the class.
* @return competency
*/
public static function create_cohort_role_assignment(stdClass $record) {
$cohortroleassignment = new cohort_role_assignment(0, $record);
$context = context_system::instance();
// First we do a permissions check.
require_capability('moodle/role:manage', $context);
// Validate before we check for existing records.
if (!$cohortroleassignment->is_valid()) {
throw new invalid_persistent_exception($cohortroleassignment->get_errors());
}
$existing = cohort_role_assignment::get_record((array) $record);
if (!empty($existing)) {
return false;
} else {
// OK - all set.
$cohortroleassignment->create();
}
return $cohortroleassignment;
}
/**
* Delete a cohort role assignment by id.
*
* Requires moodle/role:manage capability at the system context.
*
* @param int $id The record to delete. This will also remove this role from the user for all users in the system.
* @return boolean
*/
public static function delete_cohort_role_assignment($id) {
$cohortroleassignment = new cohort_role_assignment($id);
$context = context_system::instance();
// First we do a permissions check.
require_capability('moodle/role:manage', $context);
// OK - all set.
return $cohortroleassignment->delete();
}
/**
* Perform a search based on the provided filters and return a paginated list of records.
*
* Requires moodle/role:manage capability at the system context.
*
* @param string $sort The column to sort on
* @param string $order ('ASC' or 'DESC')
* @param int $skip Number of records to skip (pagination)
* @param int $limit Max of records to return (pagination)
* @return array of cohort_role_assignment
*/
public static function list_cohort_role_assignments($sort = '', $order = 'ASC', $skip = 0, $limit = 0) {
$context = context_system::instance();
// First we do a permissions check.
require_capability('moodle/role:manage', $context);
// OK - all set.
return cohort_role_assignment::get_records(array(), $sort, $order, $skip, $limit);
}
/**
* Perform a search based on the provided filters and return a paginated list of records.
*
* Requires moodle/role:manage capability at system context.
*
* @return int
*/
public static function count_cohort_role_assignments() {
$context = context_system::instance();
// First we do a permissions check.
require_capability('moodle/role:manage', $context);
// OK - all set.
return cohort_role_assignment::count_records();
}
/**
* Sync all roles - adding and deleting role assignments as required.
*
* Slow. Should only be called from a background task.
*
* Requires moodle/role:manage capability at the system context.
*
* @return array('rolesadded' => array of (useridassignedto, useridassignedover, roleid),
* 'rolesremoved' => array of (useridassignedto, useridassignedover, roleid))
*/
public static function sync_all_cohort_roles() {
global $DB;
$context = context_system::instance();
// First we do a permissions check.
require_capability('moodle/role:manage', $context);
// Ok ready to go.
$rolesadded = array();
$rolesremoved = array();
// Get all cohort role assignments and group them by user and role.
$all = cohort_role_assignment::get_records(array(), 'userid, roleid');
// We build an better structure to loop on.
$info = array();
foreach ($all as $cra) {
if (!isset($info[$cra->get_userid()])) {
$info[$cra->get_userid()] = array();
}
if (!isset($info[$cra->get_userid()][$cra->get_roleid()])) {
$info[$cra->get_userid()][$cra->get_roleid()] = array();
}
array_push($info[$cra->get_userid()][$cra->get_roleid()], $cra->get_cohortid());
}
// Then for each user+role combo - find user context in the cohort without a role assigned.
foreach ($info as $userid => $roles) {
foreach ($roles as $roleid => $cohorts) {
list($cohortsql, $params) = $DB->get_in_or_equal($cohorts, SQL_PARAMS_NAMED);
$params['usercontext'] = CONTEXT_USER;
$params['roleid'] = $roleid;
$params['userid'] = $userid;
$sql = 'SELECT u.id AS userid, ra.id, ctx.id AS contextid
FROM {user} u
JOIN {cohort_members} cm ON u.id = cm.userid
JOIN {context} ctx ON u.id = ctx.instanceid AND ctx.contextlevel = :usercontext
LEFT JOIN {role_assignments} ra ON ra.contextid = ctx.id
AND ra.roleid = :roleid
AND ra.userid = :userid
WHERE cm.cohortid ' . $cohortsql . '
AND ra.id IS NULL';
$toadd = $DB->get_records_sql($sql, $params);
foreach ($toadd as $add) {
role_assign($roleid, $userid, $add->contextid, 'tool_cohortroles');
$rolesadded[] = array(
'useridassignedto' => $userid,
'useridassignedover' => $add->userid,
'roleid' => $roleid
);
}
}
}
// And for each user+role combo - find user context not in the cohort with a role assigned.
// If the role was assigned by this component, unassign the role.
foreach ($info as $userid => $roles) {
foreach ($roles as $roleid => $cohorts) {
// Now we are looking for entries NOT in the cohort.
list($cohortsql, $params) = $DB->get_in_or_equal($cohorts, SQL_PARAMS_NAMED);
$params['usercontext'] = CONTEXT_USER;
$params['roleid'] = $roleid;
$params['userid'] = $userid;
$params['component'] = 'tool_cohortroles';
$sql = 'SELECT u.id as userid, ra.id, ctx.id AS contextid
FROM {user} u
JOIN {context} ctx ON u.id = ctx.instanceid AND ctx.contextlevel = :usercontext
JOIN {role_assignments} ra ON ra.contextid = ctx.id AND ra.roleid = :roleid AND ra.userid = :userid
LEFT JOIN {cohort_members} cm ON u.id = cm.userid
AND cm.cohortid ' . $cohortsql . '
WHERE ra.component = :component AND cm.cohortid IS NULL';
$toremove = $DB->get_records_sql($sql, $params);
foreach ($toremove as $remove) {
role_unassign($roleid, $userid, $remove->contextid, 'tool_cohortroles');
$rolesremoved[] = array(
'useridassignedto' => $userid,
'useridassignedover' => $remove->userid,
'roleid' => $roleid
);
}
}
}
return array('rolesadded' => $rolesadded, 'rolesremoved' => $rolesremoved);
}
}
@@ -0,0 +1,107 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Class for cohort_role_assignment persistence.
*
* @package tool_cohortroles
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_cohortroles;
use lang_string;
use core_competency\persistent;
/**
* Class for loading/storing cohort_role_assignments from the DB.
*
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cohort_role_assignment extends persistent {
/** Table name for user_competency persistency */
const TABLE = 'tool_cohortroles';
/**
* Return the definition of the properties of this model.
*
* @return array
*/
protected static function define_properties() {
return array(
'userid' => array(
'type' => PARAM_INT,
),
'roleid' => array(
'type' => PARAM_INT,
),
'cohortid' => array(
'type' => PARAM_INT,
)
);
}
/**
* Validate the user ID.
*
* @param int $value The value.
* @return true|lang_string
*/
protected function validate_userid($value) {
global $DB;
if (!$DB->record_exists('user', array('id' => $value))) {
return new lang_string('invaliduserid', 'error');
}
return true;
}
/**
* Validate the role ID.
*
* @param int $value The value.
* @return true|lang_string
*/
protected function validate_roleid($value) {
global $DB;
if (!$DB->record_exists('role', array('id' => $value))) {
return new lang_string('invalidroleid', 'error');
}
return true;
}
/**
* Validate the cohort ID.
*
* @param int $value The value.
* @return true|lang_string
*/
protected function validate_cohortid($value) {
global $DB;
if (!$DB->record_exists('cohort', array('id' => $value))) {
return new lang_string('invalidcohortid', 'error');
}
return true;
}
}
@@ -0,0 +1,86 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Assign role to cohort form.
*
* @package tool_cohortroles
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_cohortroles\form;
defined('MOODLE_INTERNAL') || die();
use moodleform;
use context_system;
require_once($CFG->libdir . '/formslib.php');
/**
* Assign role to cohort form.
*
* @package tool_cohortroles
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class assign_role_cohort extends moodleform {
/**
* Form definition.
*/
public function definition() {
global $PAGE;
$mform = $this->_form;
$roles = get_roles_for_contextlevels(CONTEXT_USER);
if (empty($roles)) {
$output = $PAGE->get_renderer('tool_cohortroles');
$warning = $output->notify_problem(get_string('noassignableroles', 'tool_cohortroles'));
$mform->addElement('html', $warning);
return;
}
$options = array(
'ajax' => 'tool_lp/form-user-selector',
'multiple' => true
);
$mform->addElement('autocomplete', 'userids', get_string('selectusers', 'tool_cohortroles'), array(), $options);
$mform->addRule('userids', null, 'required');
$names = role_get_names();
$options = array();
foreach ($roles as $idx => $roleid) {
$options[$roleid] = $names[$roleid]->localname;
}
$mform->addElement('select', 'roleid', get_string('selectrole', 'tool_cohortroles'), $options);
$mform->addRule('roleid', null, 'required');
$context = context_system::instance();
$options = array(
'ajax' => 'tool_lp/form-cohort-selector',
'multiple' => true,
'data-contextid' => $context->id,
'data-includes' => 'all'
);
$mform->addElement('autocomplete', 'cohortids', get_string('selectcohorts', 'tool_cohortroles'), array(), $options);
$mform->addRule('cohortids', null, 'required');
$mform->addElement('submit', 'submit', get_string('assign', 'tool_cohortroles'));
}
}
@@ -0,0 +1,231 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Cohort role assignments table
*
* @package tool_cohortroles
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_cohortroles\output;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/tablelib.php');
use context_helper;
use context_system;
use html_writer;
use moodle_url;
use table_sql;
/**
* Cohort role assignments table.
*
* @package tool_cohortroles
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cohort_role_assignments_table extends table_sql {
/**
* Sets up the table.
*
* @param string $uniqueid Unique id of table.
* @param moodle_url $url The base URL.
*/
public function __construct($uniqueid, $url) {
global $CFG;
parent::__construct($uniqueid);
$context = context_system::instance();
$this->context = $context;
$this->rolenames = role_get_names();
// This object should not be used without the right permissions.
require_capability('moodle/role:manage', $context);
$this->useridfield = 'userid';
// Define columns in the table.
$this->define_table_columns();
$this->define_baseurl($url);
// Define configs.
$this->define_table_configs();
}
/**
* Role name column.
*
* @param array $data Row data.
* @return string
*/
protected function col_rolename($data) {
return $this->rolenames[$data->roleid]->localname;
}
/**
* Cohort name column.
*
* @param array $data Row data.
* @return string
*/
protected function col_cohortname($data) {
global $OUTPUT;
$record = (object) array(
'id' => $data->cohortid,
'idnumber' => $data->cohortidnumber,
'description' => $data->cohortdescription,
'visible' => $data->cohortvisible,
'name' => $data->cohortname
);
$context = context_helper::instance_by_id($data->cohortcontextid);
$exporter = new \tool_lp\external\cohort_summary_exporter($record, array('context' => $context));
$cohort = $exporter->export($OUTPUT);
$html = $OUTPUT->render_from_template('tool_cohortroles/cohort-in-list', $cohort);
return $html;
}
/**
* Actions column.
*
* @param array $data Row data.
* @return string
*/
protected function col_actions($data) {
global $OUTPUT;
$action = new \confirm_action(get_string('removecohortroleassignmentconfirm', 'tool_cohortroles'));
$url = new moodle_url($this->baseurl);
$url->params(array('removecohortroleassignment' => $data->id, 'sesskey' => sesskey()));
$pix = new \pix_icon('t/delete', get_string('removecohortroleassignment', 'tool_cohortroles'));
return $OUTPUT->action_link($url, '', $action, null, $pix);
}
/**
* Setup the headers for the table.
*/
protected function define_table_columns() {
$extrafields = get_extra_user_fields($this->context);
// Define headers and columns.
$cols = array(
'cohortname' => get_string('cohort', 'cohort'),
'rolename' => get_string('role'),
'fullname' => get_string('name'),
);
// Add headers for extra user fields.
foreach ($extrafields as $field) {
if (get_string_manager()->string_exists($field, 'moodle')) {
$cols[$field] = get_string($field);
} else {
$cols[$field] = $field;
}
}
// Add remaining headers.
$cols = array_merge($cols, array('actions' => get_string('actions')));
$this->define_columns(array_keys($cols));
$this->define_headers(array_values($cols));
}
/**
* Define table configs.
*/
protected function define_table_configs() {
$this->collapsible(false);
$this->sortable(true, 'lastname', SORT_ASC);
$this->pageable(true);
$this->no_sorting('actions');
}
/**
* Builds the SQL query.
*
* @param bool $count When true, return the count SQL.
* @return array containing sql to use and an array of params.
*/
protected function get_sql_and_params($count = false) {
$fields = 'uca.id, uca.cohortid, uca.userid, uca.roleid, ';
$fields .= 'c.name as cohortname, c.idnumber as cohortidnumber, c.contextid as cohortcontextid, ';
$fields .= 'c.visible as cohortvisible, c.description as cohortdescription, ';
// Add extra user fields that we need for the graded user.
$extrafields = get_extra_user_fields($this->context);
foreach ($extrafields as $field) {
$fields .= 'u.' . $field . ', ';
}
$fields .= get_all_user_name_fields(true, 'u');
if ($count) {
$select = "COUNT(1)";
} else {
$select = "$fields";
}
$sql = "SELECT $select
FROM {tool_cohortroles} uca
JOIN {user} u ON u.id = uca.userid
JOIN {cohort} c ON c.id = uca.cohortid";
$params = array();
// Add order by if needed.
if (!$count && $sqlsort = $this->get_sql_sort()) {
$sql .= " ORDER BY " . $sqlsort;
}
return array($sql, $params);
}
/**
* Override the default implementation to set a decent heading level.
*/
public function print_nothing_to_display() {
global $OUTPUT;
echo $this->render_reset_button();
$this->print_initials_bar();
echo $OUTPUT->heading(get_string('nothingtodisplay'), 4);
}
/**
* Query the DB.
*
* @param int $pagesize size of page for paginated displayed table.
* @param bool $useinitialsbar do you want to use the initials bar.
*/
public function query_db($pagesize, $useinitialsbar = true) {
global $DB;
list($countsql, $countparams) = $this->get_sql_and_params(true);
list($sql, $params) = $this->get_sql_and_params();
$total = $DB->count_records_sql($countsql, $countparams);
$this->pagesize($pagesize, $total);
$this->rawdata = $DB->get_records_sql($sql, $params, $this->get_page_start(), $this->get_page_size());
// Set initial bars.
if ($useinitialsbar) {
$this->initialbars($total > $pagesize);
}
}
}
@@ -0,0 +1,76 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Renderer class for cohort roles
*
* @package tool_cohortroles
* @copyright 2016 Issam Taboubi <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_cohortroles\output;
defined('MOODLE_INTERNAL') || die();
use plugin_renderer_base;
use renderable;
/**
* Renderer class for cohort roles
*
* @package tool_cohortroles
* @copyright 2016 Issam Taboubi <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class renderer extends plugin_renderer_base {
/**
* Output a nofication.
*
* @param string $message the message to print out
* @return string HTML fragment.
* @see \core\output\notification
*/
public function notify_message($message) {
$n = new \core\output\notification($message, \core\output\notification::NOTIFY_INFO);
return $this->render($n);
}
/**
* Output an error notification.
*
* @param string $message the message to print out
* @return string HTML fragment.
* @see \core\output\notification
*/
public function notify_problem($message) {
$n = new \core\output\notification($message, \core\output\notification::NOTIFY_ERROR);
return $this->render($n);
}
/**
* Output a success notification.
*
* @param string $message the message to print out
* @return string HTML fragment.
* @see \core\output\notification
*/
public function notify_success($message) {
$n = new \core\output\notification($message, \core\output\notification::NOTIFY_SUCCESS);
return $this->render($n);
}
}
@@ -0,0 +1,56 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Scheduled task for syncing cohort roles.
*
* @package tool_cohortroles
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_cohortroles\task;
use core\task\scheduled_task;
use tool_cohortroles\api;
/**
* Scheduled task for syncing cohort roles.
*
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cohort_role_sync extends scheduled_task {
/**
* Get name.
* @return string
*/
public function get_name() {
// Shown in admin screens.
return get_string('taskname', 'tool_cohortroles');
}
/**
* Execute.
*/
public function execute() {
mtrace('Sync cohort roles...');
$result = api::sync_all_cohort_roles();
mtrace('Added ' . count($result['rolesadded']));
mtrace('Removed ' . count($result['rolesremoved']));
}
}
+25
View File
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="admin/tool/cohortroles/db" VERSION="20151201" COMMENT="XMLDB file for Moodle admin/tool/cohortroles"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../../lib/xmldb/xmldb.xsd"
>
<TABLES>
<TABLE NAME="tool_cohortroles" COMMENT="Mapping of users to cohort role assignments.">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="cohortid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The cohort to sync"/>
<FIELD NAME="roleid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The role to assign"/>
<FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The user to sync"/>
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The time this record was created"/>
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The time this record was modified."/>
<FIELD NAME="usermodified" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Who last modified this record?"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
</KEYS>
<INDEXES>
<INDEX NAME="cohortuserrole" UNIQUE="true" FIELDS="cohortid, roleid, userid"/>
</INDEXES>
</TABLE>
</TABLES>
</XMLDB>
+37
View File
@@ -0,0 +1,37 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Tasks definitions.
*
* @package tool_cohortroles
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$tasks = array(
array(
'classname' => 'tool_cohortroles\task\cohort_role_sync',
'blocking' => 0,
'minute' => 'R',
'hour' => '*',
'day' => '*',
'dayofweek' => '*',
'month' => '*'
),
);
+98
View File
@@ -0,0 +1,98 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Assign roles for a user.
*
* @package tool_cohortroles
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(dirname(__FILE__) . '/../../../config.php');
require_once($CFG->libdir.'/adminlib.php');
require_once($CFG->libdir.'/tablelib.php');
$removeid = optional_param('removecohortroleassignment', 0, PARAM_INT);
admin_externalpage_setup('toolcohortroles');
$context = context_system::instance();
$pageurl = new moodle_url('/admin/tool/cohortroles/index.php');
$output = $PAGE->get_renderer('tool_cohortroles');
echo $output->header();
$title = get_string('assignroletocohort', 'tool_cohortroles');
echo $output->heading($title);
$form = new tool_cohortroles\form\assign_role_cohort();
if ($removeid) {
require_sesskey();
$result = \tool_cohortroles\api::delete_cohort_role_assignment($removeid);
if ($result) {
$notification = get_string('cohortroleassignmentremoved', 'tool_cohortroles');
echo $output->notify_success($notification);
} else {
$notification = get_string('cohortroleassignmentnotremoved', 'tool_cohortroles');
echo $output->notify_problem($notification);
}
echo $output->continue_button(new moodle_url($pageurl));
} else if ($data = $form->get_data()) {
require_sesskey();
// We must create them all or none.
$saved = 0;
foreach ($data->userids as $userid) {
if (empty($data->cohortids)) {
$data->cohortids = array();
}
foreach ($data->cohortids as $cohortid) {
$params = (object) array('userid' => $userid, 'cohortid' => $cohortid, 'roleid' => $data->roleid);
$result = \tool_cohortroles\api::create_cohort_role_assignment($params);
if ($result) {
$saved++;
}
}
}
if ($saved == 0) {
$notification = get_string('nocohortroleassignmentssaved', 'tool_cohortroles');
echo $output->notify_problem($notification);
} else if ($saved == 1) {
$notification = get_string('onecohortroleassignmentsaved', 'tool_cohortroles');
echo $output->notify_success($notification);
} else {
$notification = get_string('acohortroleassignmentssaved', 'tool_cohortroles', $saved);
echo $output->notify_success($notification);
}
echo $output->continue_button(new moodle_url($pageurl));
} else {
$form->display();
$title = get_string('existingcohortroles', 'tool_cohortroles');
echo $output->heading($title);
$url = new moodle_url('/admin/tool/cohortroles/index.php');
$table = new tool_cohortroles\output\cohort_role_assignments_table(uniqid(), $url);
echo $table->out(50, true);
echo $output->spacer();
echo $output->notify_message(get_string('backgroundsync', 'tool_cohortroles'));
}
echo $output->footer();
@@ -0,0 +1,44 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Strings for component 'tool_userroles', language 'en'
*
* @package tool_cohortroles
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['acohortroleassignmentssaved'] = '{$a} cohort role assignments were saved.';
$string['assign'] = 'Assign';
$string['assignroletocohort'] = 'Assign role to users in the context of all members of these cohorts';
$string['backgroundsync'] = 'Note: Roles assigned on this page will not take affect immediately, the changes to role assignments will be made by a background task.';
$string['cohortroleassignmentremoved'] = 'The cohort role assignment was removed.';
$string['cohortroleassignmentnotremoved'] = 'The cohort role assignment was not removed.';
$string['cohortroles'] = 'Cohort roles';
$string['existingcohortroles'] = 'Existing cohort role assignments';
$string['managecohortroles'] = 'Assign user roles for entire cohort';
$string['noassignableroles'] = 'No roles can be assigned at a user context level. <a href="../../roles/manage.php">Manage roles</a>';
$string['nocohortroleassignmentssaved'] = 'No cohort role assignments were saved.';
$string['onecohortroleassignmentsaved'] = 'One cohort role assignment was saved.';
$string['pluginname'] = 'Cohort roles management';
$string['removecohortroleassignment'] = 'Remove cohort role assignment';
$string['removecohortroleassignmentconfirm'] = 'Are you sure you want to remove this cohort role assignment? This role will be removed for this user in all other user contexts.';
$string['selectcohorts'] = 'Select cohorts';
$string['selectrole'] = 'Select role';
$string['selectusers'] = 'Select users to assign role';
$string['taskname'] = 'Sync cohort role assignments';
$string['thisuserroles'] = 'Roles assigned relative to this user';
+27
View File
@@ -0,0 +1,27 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Link to user roles management.
*
* @package tool_cohortroles
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
$str = get_string('managecohortroles', 'tool_cohortroles');
$ADMIN->add('roles', new admin_externalpage('toolcohortroles', $str, '/admin/tool/cohortroles/index.php', 'moodle/role:manage'));
@@ -0,0 +1,43 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template tool_cohortroles/cohort-in-list
Moodle template for displaying a cohort in a list.
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* id cohort id field
* name cohort name field
* idnumber cohort idnumber field
* description cohort description field
* visible cohort visible field
Example context (json):
{ "id": "1",
"name": "Cohort 1",
"visible": true,
"idnumber": "014",
"description": "Some users"
}
}}
{{> tool_lp/form-cohort-selector-suggestion }}
+243
View File
@@ -0,0 +1,243 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* API tests.
*
* @package tool_cohortroles
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
use tool_cohortroles\api;
/**
* API tests.
*
* @package tool_cohortroles
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tool_cohortroles_api_testcase extends advanced_testcase {
/** @var stdClass $cohort */
protected $cohort = null;
/** @var stdClass $userassignto */
protected $userassignto = null;
/** @var stdClass $userassignover */
protected $userassignover = null;
/** @var stdClass $role */
protected $role = null;
/**
* Setup function- we will create a course and add an assign instance to it.
*/
protected function setUp() {
global $DB;
$this->resetAfterTest(true);
// Create some users.
$this->cohort = $this->getDataGenerator()->create_cohort();
$this->userassignto = $this->getDataGenerator()->create_user();
$this->userassignover = $this->getDataGenerator()->create_user();
$this->roleid = create_role('Sausage Roll', 'sausageroll', 'mmmm');
cohort_add_member($this->cohort->id, $this->userassignover->id);
}
public function test_create_cohort_role_assignment_without_permission() {
$this->setExpectedException('required_capability_exception');
$this->setUser($this->userassignto);
$params = (object) array(
'userid' => $this->userassignto->id,
'roleid' => $this->roleid,
'cohortid' => $this->cohort->id
);
api::create_cohort_role_assignment($params);
}
public function test_create_cohort_role_assignment_with_invalid_data() {
$this->setExpectedException('core_competency\invalid_persistent_exception');
$this->setAdminUser();
$params = (object) array(
'userid' => $this->userassignto->id,
'roleid' => -8,
'cohortid' => $this->cohort->id
);
api::create_cohort_role_assignment($params);
}
public function test_create_cohort_role_assignment() {
$this->setAdminUser();
$params = (object) array(
'userid' => $this->userassignto->id,
'roleid' => $this->roleid,
'cohortid' => $this->cohort->id
);
$result = api::create_cohort_role_assignment($params);
$this->assertNotEmpty($result->get_id());
$this->assertEquals($result->get_userid(), $this->userassignto->id);
$this->assertEquals($result->get_roleid(), $this->roleid);
$this->assertEquals($result->get_cohortid(), $this->cohort->id);
}
public function test_delete_cohort_role_assignment_without_permission() {
$this->setAdminUser();
$params = (object) array(
'userid' => $this->userassignto->id,
'roleid' => $this->roleid,
'cohortid' => $this->cohort->id
);
$result = api::create_cohort_role_assignment($params);
$this->setExpectedException('required_capability_exception');
$this->setUser($this->userassignto);
api::delete_cohort_role_assignment($result->get_id());
}
public function test_delete_cohort_role_assignment_with_invalid_data() {
$this->setAdminUser();
$params = (object) array(
'userid' => $this->userassignto->id,
'roleid' => $this->roleid,
'cohortid' => $this->cohort->id
);
$result = api::create_cohort_role_assignment($params);
$this->setExpectedException('dml_missing_record_exception');
api::delete_cohort_role_assignment($result->get_id() + 1);
}
public function test_delete_cohort_role_assignment() {
$this->setAdminUser();
$params = (object) array(
'userid' => $this->userassignto->id,
'roleid' => $this->roleid,
'cohortid' => $this->cohort->id
);
$result = api::create_cohort_role_assignment($params);
$worked = api::delete_cohort_role_assignment($result->get_id());
$this->assertTrue($worked);
}
public function test_list_cohort_role_assignments() {
$this->setAdminUser();
$params = (object) array(
'userid' => $this->userassignto->id,
'roleid' => $this->roleid,
'cohortid' => $this->cohort->id
);
$result = api::create_cohort_role_assignment($params);
$list = api::list_cohort_role_assignments();
$list[0]->is_valid();
$this->assertEquals($list[0], $result);
}
public function test_count_cohort_role_assignments() {
$this->setAdminUser();
$params = (object) array(
'userid' => $this->userassignto->id,
'roleid' => $this->roleid,
'cohortid' => $this->cohort->id
);
$result = api::create_cohort_role_assignment($params);
$count = api::count_cohort_role_assignments();
$this->assertEquals($count, 1);
}
public function test_sync_all_cohort_roles() {
$this->setAdminUser();
$params = (object) array(
'userid' => $this->userassignto->id,
'roleid' => $this->roleid,
'cohortid' => $this->cohort->id
);
$result = api::create_cohort_role_assignment($params);
// Verify roles are assigned when users enter the cohort.
$sync = api::sync_all_cohort_roles();
$rolesadded = array(array(
'useridassignedto' => $this->userassignto->id,
'useridassignedover' => $this->userassignover->id,
'roleid' => $this->roleid
));
$rolesremoved = array();
$expected = array('rolesadded' => $rolesadded,
'rolesremoved' => $rolesremoved);
$this->assertEquals($sync, $expected);
// Verify roles are removed when users leave the cohort.
cohort_remove_member($this->cohort->id, $this->userassignover->id);
$sync = api::sync_all_cohort_roles();
$rolesadded = array();
$rolesremoved = array(array(
'useridassignedto' => $this->userassignto->id,
'useridassignedover' => $this->userassignover->id,
'roleid' => $this->roleid
));
$expected = array('rolesadded' => $rolesadded,
'rolesremoved' => $rolesremoved);
$this->assertEquals($sync, $expected);
// Verify roles assigned by any other component are not removed.
$usercontext = context_user::instance($this->userassignover->id);
role_assign($this->roleid, $this->userassignto->id, $usercontext->id);
$sync = api::sync_all_cohort_roles();
$rolesadded = array();
$rolesremoved = array();
$expected = array('rolesadded' => $rolesadded,
'rolesremoved' => $rolesremoved);
$this->assertEquals($sync, $expected);
// Remove manual role assignment.
role_unassign($this->roleid, $this->userassignto->id, $usercontext->id);
// Add someone to the cohort again...
cohort_add_member($this->cohort->id, $this->userassignover->id);
$sync = api::sync_all_cohort_roles();
$rolesadded = array(array(
'useridassignedto' => $this->userassignto->id,
'useridassignedover' => $this->userassignover->id,
'roleid' => $this->roleid
));
$rolesremoved = array();
$expected = array('rolesadded' => $rolesadded,
'rolesremoved' => $rolesremoved);
$this->assertEquals($sync, $expected);
// Verify no fatal errors when a cohort is deleted.
cohort_delete_cohort($this->cohort);
$sync = api::sync_all_cohort_roles();
$rolesadded = array();
$rolesremoved = array(array(
'useridassignedto' => $this->userassignto->id,
'useridassignedover' => $this->userassignover->id,
'roleid' => $this->roleid
));
$expected = array('rolesadded' => $rolesadded,
'rolesremoved' => $rolesremoved);
$this->assertEquals($sync, $expected);
}
}
+34
View File
@@ -0,0 +1,34 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Plugin version info
*
* @package tool_cohortroles
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2015111018; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2014110400; // Requires this Moodle version.
$plugin->component = 'tool_cohortroles'; // Full name of the plugin (used for diagnostics).
$plugin->dependencies = array(
'tool_lp' => ANY_VERSION
);
+1
View File
@@ -0,0 +1 @@
define(["jquery","core/notification","core/ajax","core/templates","tool_lp/dialogue","tool_lp/event_base"],function(a,b,c,d,e,f){var g=function(a,b,c,d,e){var g=this;f.prototype.constructor.apply(this,[]),g._title=a,g._message=b,g._actions=c,g._confirm=d,g._cancel=e,g._selectedValue=null,g._reset()};return g.prototype=Object.create(f.prototype),g.prototype._selectedValue=null,g.prototype._popup=null,g.prototype._title=null,g.prototype._message=null,g.prototype._actions=null,g.prototype._confirm=null,g.prototype._cancel=null,g.prototype._afterRender=function(){var b=this;b._find('[data-action="action-selector-confirm"]').attr("disabled","disabled"),b._find('[data-region="action-selector-radio-buttons"]').change(function(){b._selectedValue=a("input[type='radio']:checked").val(),b._find('[data-action="action-selector-confirm"]').removeAttr("disabled"),b._refresh.bind(b)}.bind(b)),b._find('[data-action="action-selector-cancel"]').click(function(a){a.preventDefault(),b.close()}.bind(b)),b._find('[data-action="action-selector-confirm"]').click(function(a){a.preventDefault(),b._selectedValue.length&&(b._trigger("save",{action:b._selectedValue}),b.close())}.bind(b))},g.prototype.close=function(){var a=this;a._popup.close(),a._reset()},g.prototype.display=function(){var a=this;return a._render().then(function(b){a._popup=new e(a._title,b,a._afterRender.bind(a))}.bind(a)).fail(b.exception)},g.prototype._find=function(b){return a(this._popup.getContent()).find(b)},g.prototype._refresh=function(){var a=this;return a._render().then(function(b){a._find('[data-region="action-selector"]').replaceWith(b),a._afterRender()}.bind(a))},g.prototype._render=function(){var a=this,b=[];for(var c in a._actions)b.push(a._actions[c]);var e={message:a._message,choices:b,confirm:a._confirm,cancel:a._cancel};return d.render("tool_lp/action_selector",e)},g.prototype._reset=function(){this._popup=null,this._selectedValue=""},g});
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
define(["jquery","core/str"],function(a,b){var c=0,d=1,e=2,f=3;return{NONE:c,EVIDENCE:d,COMPLETE:e,RECOMMEND:f,getAll:function(){var a=this;return b.get_strings([{key:"competencyoutcome_none",component:"tool_lp"},{key:"competencyoutcome_evidence",component:"tool_lp"},{key:"competencyoutcome_recommend",component:"tool_lp"},{key:"competencyoutcome_complete",component:"tool_lp"}]).then(function(b){var c={};return c[a.NONE]={code:a.NONE,name:b[0]},c[a.EVIDENCE]={code:a.EVIDENCE,name:b[1]},c[a.RECOMMEND]={code:a.RECOMMEND,name:b[2]},c[a.COMPLETE]={code:a.COMPLETE,name:b[3]},c})},getString:function(b){var c=this,d=c.getAll();return d.then(function(c){return"undefined"==typeof c[b]?a.Deferred().reject().promise():c[b].name})}}});
+1
View File
@@ -0,0 +1 @@
define(["jquery"],function(a){var b=function(b){this._eventNode=a("<div>"),this._ready=a.Deferred(),this._tree=b};return b.prototype._competency=null,b.prototype._eventNode=null,b.prototype._ready=null,b.prototype._tree=null,b.prototype.canConfig=function(){return this._tree.hasChildren(this._competency.id)},b.prototype.getConfig=function(){return null},b.prototype.getType=function(){throw new Error("Not implemented")},b.prototype.init=function(){return this._load()},b.prototype.injectTemplate=function(){return a.Deferred().reject().promise()},b.prototype.isValid=function(){return!1},b.prototype._load=function(){return a.when()},b.prototype.on=function(a,b){this._eventNode.on(a,b)},b.prototype.setTargetCompetency=function(a){this._competency=a},b.prototype._trigger=function(a,b){this._eventNode.trigger(a,[b])},b.prototype._triggerChange=function(){this._trigger("change",this)},b});
+1
View File
@@ -0,0 +1 @@
define(["jquery","core/str","tool_lp/competency_rule"],function(a,b,c){var d=function(){c.apply(this,arguments)};return d.prototype=Object.create(c.prototype),d.prototype.getType=function(){return"core_competency\\competency_rule_all"},d.prototype.isValid=function(){return!0},d});
+1
View File
@@ -0,0 +1 @@
define(["jquery","core/str","core/templates","tool_lp/competency_rule"],function(a,b,c,d){var e=function(){d.apply(this,arguments)};return e.prototype=Object.create(d.prototype),e.prototype._container=null,e.prototype._templateLoaded=!1,e.prototype.getConfig=function(){return JSON.stringify({base:{points:this._getRequiredPoints()},competencies:this._getCompetenciesConfig()})},e.prototype._getCompetenciesConfig=function(){var b=[];return this._container.find("[data-competency]").each(function(){var c=a(this),d=c.data("competency"),e=parseInt(c.find('[name="points"]').val(),10),f=c.find('[name="required"]').prop("checked");b.push({id:d,points:e,required:f?1:0})}),b},e.prototype._getRequiredPoints=function(){return parseInt(this._container.find('[name="requiredpoints"]').val()||1,10)},e.prototype.getType=function(){return"core_competency\\competency_rule_points"},e.prototype.injectTemplate=function(b){var d,e=this,f=this._tree.getChildren(this._competency.id),g={base:{points:2},competencies:[]};if(this._templateLoaded=!1,e._competency.ruletype==e.getType())try{g=JSON.parse(e._competency.ruleconfig)}catch(h){}return d={requiredpoints:g&&g.base?g.base.points:2,competency:e._competency,children:[]},a.each(f,function(b,c){var e={id:c.id,shortname:c.shortname,required:!1,points:0};g&&a.each(g.competencies,function(a,b){b.id==e.id&&(e.required=b.required?!0:!1,e.points=b.points)}),d.children.push(e)}),c.render("tool_lp/competency_rule_points",d).then(function(a){e._container=b,b.html(a),b.find("input").change(function(){e._triggerChange()}),e._templateLoaded=!0,e._triggerChange()})},e.prototype.isValid=function(){if(!this._templateLoaded)return!1;var b=this._getRequiredPoints(),c=0,d=!0;return a.each(this._getCompetenciesConfig(),function(a,b){b.points<0&&(d=!1),c+=b.points}),d=d&&c>=b},e});
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
define(["jquery","core/notification","core/ajax","core/templates","core/str","tool_lp/dialogue"],function(a,b,c,d,e,f){var g=function(b){this.options={includerelated:!1,includecourses:!1},a.extend(this.options,b)};return g.prototype.triggerCompetencyViewedEvent=function(a){c.call([{methodname:"core_competency_competency_viewed",args:{id:a}}])},g.prototype.enhanceDialogue=function(a){var b=new g({includerelated:!1});b.watch(a.getContent())},g.prototype.showDialogue=function(a){var c=this.getCompetencyDataPromise(a),e=this;c.done(function(c){d.render("tool_lp/competency_summary",c).done(function(b){e.triggerCompetencyViewedEvent(a),new f(c.competency.shortname,b,e.enhanceDialogue)}).fail(b.exception)}).fail(b.exception)},g.prototype.showDialogueFromData=function(a){var c=this;d.render("tool_lp/competency_summary",a).done(function(b){c.triggerCompetencyViewedEvent(a.id),new f(a.shortname,b,c.enhanceDialogue)}).fail(b.exception)},g.prototype.clickEventHandler=function(b){var c=b.data.compdialogue,d=a(b.currentTarget).data("id");c.showDialogue(d),b.preventDefault()},g.prototype.getCompetencyDataPromise=function(a){var d=c.call([{methodname:"tool_lp_data_for_competency_summary",args:{competencyid:a,includerelated:this.options.includerelated,includecourses:this.options.includecourses}}]);return d[0].then(function(a){return a}).fail(b.exception)},g.prototype.watch=function(b){a(b).off("click",'[data-action="competency-dialogue"]',this.clickEventHandler),a(b).on("click",'[data-action="competency-dialogue"]',{compdialogue:this},this.clickEventHandler)},g});
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
define(["jquery","core/notification","core/ajax","core/templates","core/str","tool_lp/tree","tool_lp/competencypicker"],function(a,b,c,d,e,f,g){var h=function(a,b,c){g.prototype.constructor.apply(this,[1,!1,"self",c]),this._userId=a,this._plans=[],b&&(this._planId=b,this._singlePlan=!0)};return h.prototype=Object.create(g.prototype),h.prototype._plans=null,h.prototype._planId=null,h.prototype._singlePlan=!1,h.prototype._userId=null,h.prototype._afterRender=function(){var b=this;g.prototype._afterRender.apply(b,arguments),b._singlePlan||b._find('[data-action="chooseplan"]').change(function(c){b._planId=a(c.target).val(),b._loadCompetencies().then(b._refresh.bind(b))}.bind(b))},h.prototype._fetchCompetencies=function(a,d){var e=this;return c.call([{methodname:"core_competency_list_plan_competencies",args:{id:a}}])[0].done(function(a){var b,c,f=[];for(b=0;b<a.length;b++)c=a[b].competency,c.shortname.toLowerCase().indexOf(d.toLowerCase())<0||(c.children=[],c.haschildren=0,f.push(c));e._competencies=f}).fail(b.exception)},h.prototype._getPlan=function(b){var c;return a.each(this._plans,function(a,d){return d.id==b?(c=d,!1):void 0}),c},h.prototype._loadCompetencies=function(){return this._fetchCompetencies(this._planId,this._searchText)},h.prototype._loadPlans=function(){var d,e=this;return e._plans.length>0?a.when():(d=e._singlePlan?c.call([{methodname:"core_competency_read_plan",args:{id:this._planId}}])[0].then(function(a){return[a]}):c.call([{methodname:"core_competency_list_user_plans",args:{userid:e._userId}}])[0],d.done(function(a){e._plans=a}).fail(b.exception))},h.prototype._preRender=function(){var b=this;return b._loadPlans().then(function(){return!b._planId&&b._plans.length>0&&(b._planId=b._plans[0].id),b._planId?b._loadCompetencies():(b._plans=[],a.when())}.bind(b))},h.prototype._render=function(){var b=this;return b._preRender().then(function(){b._singlePlan||a.each(b._plans,function(a,c){c.id==b._planId?c.selected=!0:c.selected=!1});var c={competencies:b._competencies,plan:b._getPlan(b._planId),plans:b._plans,search:b._searchText,singlePlan:b._singlePlan};return d.render("tool_lp/competency_picker_user_plans",c)}.bind(b))},h});
+1
View File
@@ -0,0 +1 @@
define(["jquery","core/notification","core/templates","tool_lp/dialogue","tool_lp/competency_outcomes","core/str"],function(a,b,c,d,e,f){var g=function(b,c){this._eventNode=a("<div></div>"),this._tree=b,this._rulesModules=c,this._setUp()};return g.prototype._competency=null,g.prototype._eventNode=null,g.prototype._outcomesOption=null,g.prototype._popup=null,g.prototype._ready=null,g.prototype._rules=null,g.prototype._rulesModules=null,g.prototype._tree=null,g.prototype._afterChange=function(){this._isValid()?this._find('[data-action="save"]').prop("disabled",!1):this._find('[data-action="save"]').prop("disabled",!0)},g.prototype._afterRuleConfigChange=function(a,b){b==this._getRule()&&this._afterChange()},g.prototype._afterRender=function(){var a=this;a._find('[name="outcome"]').on("change",function(){a._switchedOutcome()}).trigger("change"),a._find('[name="rule"]').on("change",function(){a._switchedRule()}).trigger("change"),a._find('[data-action="save"]').on("click",function(){a._trigger("save",a._getConfig()),a.close()}),a._find('[data-action="cancel"]').on("click",function(){a.close()})},g.prototype.canBeConfigured=function(){var b=!1;return a.each(this._rules,function(a,c){return c.canConfig()?(b=!0,!1):void 0}),b},g.prototype.close=function(){this._popup.close(),this._popup=null},g.prototype.display=function(){var a=this;if(a._competency)return a._render().then(function(b){return f.get_string("competencyrule","tool_lp").then(function(c){a._popup=new d(c,b,a._afterRender.bind(a))})}).fail(b.exception)},g.prototype._find=function(b){return a(this._popup.getContent()).find(b)},g.prototype._getApplicableOutcomesOptions=function(){var b=this,c=[];return a.each(b._outcomesOption,function(a,d){c.push({code:d.code,name:d.name,selected:d.code==b._competency.ruleoutcome?!0:!1})}),c},g.prototype._getApplicableRulesOptions=function(){var b=this,c=[];return a.each(b._rules,function(a,d){d.canConfig()&&c.push({name:b._getRuleName(d.getType()),type:d.getType(),selected:d.getType()==b._competency.ruletype?!0:!1})}),c},g.prototype._getConfig=function(){var a=this._getRule();return{ruletype:a?a.getType():null,ruleconfig:a?a.getConfig():null,ruleoutcome:this._getOutcome()}},g.prototype._getOutcome=function(){return this._find('[name="outcome"]').val()},g.prototype._getRule=function(){var b,c=this._find('[name="rule"]').val();return a.each(this._rules,function(a,d){return d.getType()==c?(b=d,!1):void 0}),b},g.prototype._getRuleName=function(b){var c,d=this;return a.each(d._rulesModules,function(a,d){return d.type==b?(c=d.name,!1):void 0}),c},g.prototype._initOutcomes=function(){var a=this;return e.getAll().then(function(b){a._outcomesOption=b})},g.prototype._initRules=function(){var b=this,c=[];return a.each(b._rules,function(d,e){var f=e.init().then(function(){e.setTargetCompetency(b._competency),e.on("change",b._afterRuleConfigChange.bind(b))},function(){return b._rules.splice(d,1),a.when()});c.push(f)}),a.when.apply(a.when,c)},g.prototype._isValid=function(){var a=this._getOutcome(),b=this._getRule();return a==e.NONE?!0:b?b.isValid():!1},g.prototype.on=function(a,b){this._eventNode.on(a,b)},g.prototype._preRender=function(){return this.ready()},g.prototype.ready=function(){return this._ready.promise()},g.prototype._render=function(){var a=this;return this._preRender().then(function(){var b;a.canBeConfigured()?(b={},b.outcomes=a._getApplicableOutcomesOptions(),b.rules=a._getApplicableRulesOptions()):b=!1;var d={competencyshortname:a._competency.shortname,config:b};return c.render("tool_lp/competency_rule_config",d)})},g.prototype.setTargetCompetencyId=function(b){var c=this;c._competency=c._tree.getCompetency(b),a.each(c._rules,function(a,b){b.setTargetCompetency(c._competency)})},g.prototype._setUp=function(){var b=this,c=[],d=[];b._ready=a.Deferred(),b._rules=[],a.each(b._rulesModules,function(a,b){d.push(b.amd)}),require(d,function(){a.each(arguments,function(a,c){var d=new c(b._tree);b._rules.push(d)}),c.push(b._initRules()),c.push(b._initOutcomes()),a.when.apply(a.when,c).always(function(){b._ready.resolve()})})},g.prototype._switchedOutcome=function(){var a=this,b=a._getOutcome();return b==e.NONE?(a._find('[data-region="rule-type"]').hide().find('[name="rule"]').val(-1),a._find('[data-region="rule-config"]').empty().hide(),void a._afterChange()):(a._find('[data-region="rule-type"]').show(),a._find('[data-region="rule-config"]').show(),void a._afterChange())},g.prototype._switchedRule=function(){var a=this,b=a._find('[data-region="rule-config"]'),c=a._getRule();return c?void c.injectTemplate(b).then(function(){b.show()},function(){b.empty().hide()}).always(function(){a._afterChange()}):(b.empty().hide(),void a._afterChange())},g.prototype._trigger=function(a,b){this._eventNode.trigger(a,[b])},g});
+1
View File
@@ -0,0 +1 @@
define(["core/ajax","core/notification","core/templates","tool_lp/tree","tool_lp/competency_outcomes","jquery"],function(a,b,c,d,e,f){var g={},h=0,i="",j="",k="",l=!1,m=function(a,b){var c=0,d=!1;for(a.haschildren=!1,a.children=[],c=0;c<b.length;c++)d=b[c],d.parentid==a.id&&(a.haschildren=!0,a.children.push(d),m(d,b))},n=function(b){var e=f.Deferred();return c.render("tool_lp/loading",{}).done(function(n,o){c.replaceNodeContents(f(j),n,o);var p=a.call([{methodname:"core_competency_search_competencies",args:{searchtext:b,competencyframeworkid:h}}]);p[0].done(function(a){g={};var b=0;for(b=0;b<a.length;b++)g[a[b].id]=a[b];var h=[],n=!1;for(b=0;b<a.length;b++)n=a[b],0===parseInt(n.parentid,10)&&(h.push(n),m(n,a));var o={shortname:i,canmanage:l,competencies:h};c.render("tool_lp/competencies_tree_root",o).done(function(a,b){c.replaceNodeContents(f(j),f(a).html(),b);var h=new d(j,!1);if(k){var i=f(j).find("[data-id="+k+"]");i.length&&(h.selectItem(i),h.updateFocus(i))}e.resolve(g)}).fail(e.reject)}).fail(e.reject)}),e.promise()},o=function(a,b){var c=b.selected;k=c.attr("data-id")};return{init:function(a,c,d,e,f){h=a,i=c,l=f,j=e,n(d).fail(b.exception),this.on("selectionchanged",o)},on:function(a,b){f(j).on(a,b)},getChildren:function(a){var b=[];return f.each(g,function(c,d){d.parentid==a&&b.push(d)}),b},getCompetencyFrameworkId:function(){return h},getCompetency:function(a){return g[a]},getCompetencyLevel:function(a){var b=this.getCompetency(a),c=b.path.replace(/^\/|\/$/g,"").split("/").length;return c},hasChildren:function(a){return this.getChildren(a).length>0},hasRule:function(a){var b=this.getCompetency(a);return b?b.ruleoutcome!=e.OUTCOME_NONE&&b.ruletype:!1},reloadCompetencies:function(){return n("").fail(b.exception)},listCompetencies:function(){return g}}});
@@ -0,0 +1 @@
define(["jquery","core/notification","tool_lp/dialogue","core/str","core/ajax","core/templates"],function(a,b,c,d,e,f){var g=function(b){a(b).on("click",this.configureSettings.bind(this))};return g.prototype._dialogue=null,g.prototype.configureSettings=function(e){var g=a(e.target).closest("a").data("courseid"),h=a(e.target).closest("a").data("pushratingstouserplans"),i={courseid:g,settings:{pushratingstouserplans:h}};e.preventDefault(),f.render("tool_lp/course_competency_settings",i).done(function(a){d.get_string("configurecoursecompetencysettings","tool_lp").done(function(b){this._dialogue=new c(b,a,this.addListeners.bind(this))}.bind(this)).fail(b.exception)}.bind(this)).fail(b.exception)},g.prototype.addListeners=function(){var a=this._find('[data-action="save"]');a.on("click",this.saveSettings.bind(this));var b=this._find('[data-action="cancel"]');b.on("click",this.cancelChanges.bind(this))},g.prototype.cancelChanges=function(a){a.preventDefault(),this._dialogue.close()},g.prototype._find=function(b){return a('[data-region="coursecompetencysettings"]').find(b)},g.prototype.saveSettings=function(a){a.preventDefault();var c=this._find('input[name="pushratingstouserplans"]:checked').val(),d=this._find('input[name="courseid"]').val(),f={pushratingstouserplans:c};e.call([{methodname:"core_competency_update_course_competency_settings",args:{courseid:d,settings:f}}])[0].done(function(){this.refreshCourseCompetenciesPage()}.bind(this)).fail(b.exception)},g.prototype.refreshCourseCompetenciesPage=function(){var c=this._find('input[name="courseid"]').val();e.call([{methodname:"tool_lp_data_for_course_competencies_page",args:{courseid:c}}])[0].done(function(c){f.render("tool_lp/course_competencies_page",c).done(function(b,c){a('[data-region="coursecompetenciespage"]').replaceWith(b),f.runTemplateJS(c),this._dialogue.close()}.bind(this)).fail(b.exception)}.bind(this)).fail(b.exception)},g});
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
define(["core/yui"],function(a){var b=function(b,c,d,e,f){this.yuiDialogue=null;var g=this;"undefined"==typeof f&&(f=!1),a.use("moodle-core-notification","timers",function(){var h="480px";f&&(h="800px"),g.yuiDialogue=new M.core.dialogue({headerContent:b,bodyContent:c,draggable:!0,visible:!1,center:!0,modal:!0,width:h}),g.yuiDialogue.after("visibleChange",function(b){b.newVal?"undefined"!=typeof d&&a.soon(function(){d(g),g.yuiDialogue.centerDialogue()}):"undefined"!=typeof e&&a.soon(function(){e(g)})}),g.yuiDialogue.show()})};return b.prototype.close=function(){this.yuiDialogue.hide(),this.yuiDialogue.destroy()},b.prototype.getContent=function(){return this.yuiDialogue.bodyNode.getDOMNode()},b});
+1
View File
@@ -0,0 +1 @@
define(["core/str","core/yui"],function(a,b){var c=function(a){var b=a.drag.get("node"),c=a.drop.get("node");this.callback(b.getDOMNode(),c.getDOMNode())};return{dragdrop:function(d,e,f,g,h,i,j,k){a.get_strings([{key:"emptydragdropregion",component:"moodle"},{key:"movecontent",component:"moodle"},{key:"tocontent",component:"moodle"}]).done(function(){b.use("moodle-tool_lp-dragdrop-reorder",function(){var a={callback:k};M.tool_lp.dragdrop_reorder({group:d,dragHandleText:e,sameNodeText:f,parentNodeText:g,sameNodeClass:h,parentNodeClass:i,dragHandleInsertClass:j,callback:b.bind(c,a)})})})}}});
+1
View File
@@ -0,0 +1 @@
define(["jquery"],function(a){var b=function(){this._eventNode=a("<div></div>")};return b.prototype._eventNode=null,b.prototype.on=function(a,b){this._eventNode.on(a,b)},b.prototype._trigger=function(a,b){this._eventNode.trigger(a,[b])},b});
+1
View File
@@ -0,0 +1 @@
define(["jquery","core/notification","core/ajax","core/str","core/log"],function(a,b,c,d,e){var f={},g=function(g,h){"undefined"==typeof f[g]&&(f[g]=a("body").delegate(g,"click",function(f){var g=a(f.currentTarget).parents(h);if(!g.length||g.length>1)return void e.error("None or too many evidence container were found.");var i=g.data("id");return i?(f.preventDefault(),f.stopPropagation(),void d.get_strings([{key:"confirm",component:"moodle"},{key:"areyousure",component:"moodle"},{key:"delete",component:"moodle"},{key:"cancel",component:"moodle"}]).done(function(a){b.confirm(a[0],a[1],a[2],a[3],function(){var a=c.call([{methodname:"core_competency_delete_evidence",args:{id:i}}]);a[0].then(function(){g.remove()}).fail(b.exception)})}).fail(b.exception)):void e.error("Evidence ID was not found.")}))};return{register:g}});
+1
View File
@@ -0,0 +1 @@
define(["jquery","core/ajax","core/templates"],function(a,b,c){return{processResults:function(b,c){var d=[];return a.each(c,function(a,b){d.push({value:b.id,label:b._label})}),d},transport:function(d,e,f,g){var h,i=parseInt(a(d).data("contextid"),10),j=a(d).data("includes");h=b.call([{methodname:"tool_lp_search_cohorts",args:{query:e,context:{contextid:i},includes:j}}]),h[0].then(function(b){var d=[],e=0;return a.each(b.cohorts,function(a,b){d.push(c.render("tool_lp/form-cohort-selector-suggestion",b))}),a.when.apply(a.when,d).then(function(){var c=arguments;a.each(b.cohorts,function(a,b){b._label=c[e],e++}),f(b.cohorts)})},g)}}});
+1
View File
@@ -0,0 +1 @@
define(["jquery","core/ajax","core/templates"],function(a,b,c){return{processResults:function(b,c){var d=[];return a.each(c,function(a,b){d.push({value:b.id,label:b._label})}),d},transport:function(d,e,f,g){var h,i=a(d).data("capability");"undefined"==typeof i&&(i=""),h=b.call([{methodname:"tool_lp_search_users",args:{query:e,capability:i}}]),h[0].then(function(b){var d=[],e=0;return a.each(b.users,function(b,e){var f=e,g=[];a.each(["idnumber","email","phone1","phone2","department","institution"],function(a,b){"undefined"!=typeof e[b]&&""!==e[b]&&(f.hasidentity=!0,g.push(e[b]))}),f.identity=g.join(", "),d.push(c.render("tool_lp/form-user-selector-suggestion",f))}),a.when.apply(a.when,d).then(function(){var c=arguments;a.each(b.users,function(a,b){b._label=c[e],e++}),f(b.users)})},g)}}});
+1
View File
@@ -0,0 +1 @@
define(["jquery","core/templates","core/ajax","core/notification","core/str"],function(a,b,c,d,e){var f=0,g=0,h=function(c,d){a('[data-region="managecompetencies"]').replaceWith(c),b.runTemplateJS(d)},i=function(a){b.render("tool_lp/manage_competency_frameworks_page",a).done(h).fail(d.exception)},j=function(b){b.preventDefault(),g=a(this).attr("data-frameworkid");var e=c.call([{methodname:"core_competency_duplicate_competency_framework",args:{id:g}},{methodname:"tool_lp_data_for_competency_frameworks_manage_page",args:{pagecontext:{contextid:f}}}]);e[1].done(i).fail(d.exception)},k=function(){var a=c.call([{methodname:"core_competency_delete_competency_framework",args:{id:g}},{methodname:"tool_lp_data_for_competency_frameworks_manage_page",args:{pagecontext:{contextid:f}}}]);a[0].done(function(a){if(a===!1){var b=c.call([{methodname:"core_competency_read_competency_framework",args:{id:g}}]);b[0].done(function(a){e.get_strings([{key:"frameworkcannotbedeleted",component:"tool_lp",param:a.shortname},{key:"cancel",component:"moodle"}]).done(function(a){d.alert(null,a[0])}).fail(d.exception)})}}).fail(d.exception),a[1].done(i).fail(d.exception)},l=function(b){b.preventDefault();var f=a(this).attr("data-frameworkid");g=f;var h=c.call([{methodname:"core_competency_read_competency_framework",args:{id:g}}]);h[0].done(function(a){e.get_strings([{key:"confirm",component:"moodle"},{key:"deletecompetencyframework",component:"tool_lp",param:a.shortname},{key:"delete",component:"moodle"},{key:"cancel",component:"moodle"}]).done(function(a){d.confirm(a[0],a[1],a[2],a[3],k)}).fail(d.exception)}).fail(d.exception)};return{deleteHandler:l,duplicateHandler:j,init:function(a){f=a}}});
+1
View File
@@ -0,0 +1 @@
define(["jquery","core/templates","core/ajax","core/notification","core/str"],function(a,b,c,d,e){var f=0,g=0,h=function(c,d){a('[data-region="managecompetencies"]').replaceWith(c),b.runTemplateJS(d)},i=function(a){b.render("tool_lp/manage_competency_frameworks_page",a).done(h).fail(d.exception)},j=function(){var a=c.call([{methodname:"core_competency_delete_competency_framework",args:{id:g}},{methodname:"tool_lp_data_for_competency_frameworks_manage_page",args:{pagecontext:{contextid:f}}}]);a[1].done(i).fail(d.exception)},k=function(b){b.preventDefault();var f=a(this).attr("data-frameworkid");g=f;var h=c.call([{methodname:"core_competency_read_competency_framework",args:{id:g}}]);h[0].done(function(a){e.get_strings([{key:"confirm",component:"moodle"},{key:"deletecompetencyframework",component:"tool_lp",param:a.shortname},{key:"delete",component:"moodle"},{key:"cancel",component:"moodle"}]).done(function(a){d.confirm(a[0],a[1],a[2],a[3],j)}).fail(d.exception)}).fail(d.exception)};return{deleteHandler:k,init:function(a){f=a}}});
+1
View File
@@ -0,0 +1 @@
define(["jquery","core/ajax","core/notification"],function(a,b,c){return{list:function(d,e){var f,g={context:{contextid:d}};return a.extend(g,"undefined"==typeof e?{}:e),f=b.call([{methodname:"core_competency_list_competency_frameworks",args:g}])[0],f.fail(c.exception)},processResults:function(b,c){var d=[];return a.each(c,function(a,b){d.push({value:b.id,label:b.shortname+" "+b.idnumber})}),d},transport:function(b,c,d){var e=a(b),f=e.data("contextid"),g=e.data("onlyvisible");if(!f)throw new Error("The attribute data-contextid is required on "+b);this.list(f,{query:c,onlyvisible:g}).then(d)}}});
+1
View File
@@ -0,0 +1 @@
define(["jquery","core/notification","core/templates","tool_lp/dialogue","tool_lp/event_base","core/str"],function(a,b,c,d,e,f){var g=function(a){e.prototype.constructor.apply(this,[]),this._ratingOptions=a};return g.prototype=Object.create(e.prototype),g.prototype._popup=null,g.prototype._ratingOptions=null,g.prototype._afterRender=function(){var b=this._find('[data-action="rate"]'),c=this._find('[name="rating"]'),d=this._find('[name="comment"]');this._find('[data-action="cancel"]').click(function(a){a.preventDefault(),this._trigger("cancelled"),this.close()}.bind(this)),c.change(function(){var c=a(this);c.val()?b.prop("disabled",!1):b.prop("disabled",!0)}).change(),b.click(function(a){a.preventDefault();var b=c.val();b&&(this._trigger("rated",{rating:b,note:d.val()}),this.close())}.bind(this))},g.prototype.close=function(){this._popup.close(),this._popup=null},g.prototype.display=function(){return this._render().then(function(a){return f.get_string("rate","tool_lp").then(function(b){this._popup=new d(b,a,this._afterRender.bind(this))}.bind(this))}.bind(this)).fail(b.exception)},g.prototype._find=function(b){return a(this._popup.getContent()).find(b)},g.prototype._render=function(){var a={cangrade:this._canGrade,ratings:this._ratingOptions};return c.render("tool_lp/competency_grader",a)},g});
@@ -0,0 +1 @@
define(["jquery","core/notification","core/ajax","core/log","tool_lp/grade_dialogue","tool_lp/event_base","tool_lp/scalevalues"],function(a,b,c,d,e,f,g){var h=function(b,c,d,e,g,h,i){f.prototype.constructor.apply(this,[]);var j=a(b);if(!j.length)throw new Error("Could not find the trigger");this._scaleId=c,this._competencyId=d,this._userId=e,this._planId=g,this._courseId=h,this._chooseStr=i,this._setUp(),j.click(function(a){a.preventDefault(),this._dialogue.display()}.bind(this)),this._planId?(this._methodName="core_competency_grade_competency_in_plan",this._args={competencyid:this._competencyId,planid:this._planId}):this._courseId?(this._methodName="core_competency_grade_competency_in_course",this._args={competencyid:this._competencyId,courseid:this._courseId,userid:this._userId}):(this._methodName="core_competency_grade_competency",this._args={userid:this._userId,competencyid:this._competencyId})};return h.prototype=Object.create(f.prototype),h.prototype._setUp=function(){var a=[],d=this,f=g.get_values(d._scaleId);f.done(function(f){a.push({value:"",name:d._chooseStr});for(var g=0;g<f.length;g++){var h=f[g];a.push({value:h.id,name:h.name})}d._dialogue=new e(a),d._dialogue.on("rated",function(a,e){var f=d._args;f.grade=e.rating,f.note=e.note,c.call([{methodname:d._methodName,args:f,done:function(a){d._trigger("competencyupdated",{args:f,evidence:a})}.bind(d),fail:b.exception}])}.bind(d))}).fail(b.exception)},h.prototype._scaleId=null,h.prototype._competencyId=null,h.prototype._userId=null,h.prototype._planId=null,h.prototype._courseId=null,h.prototype._chooseStr=null,h.prototype._dialogue=null,h});
+1
View File
@@ -0,0 +1 @@
define(["jquery","core/templates","core/notification","core/yui"],function(a,b,c,d){var e=function(b){var c=a(b);c.find("li a").each(function(b,c){a(c).addClass("menu-action"),a(c).attr("role","menuitem")}),a(this).replaceWith(c),d.use("moodle-core-actionmenu",function(){if(null===M.core.actionmenu.instance)M.core.actionmenu.init();else{var a=d.one(c.get(0));M.core.actionmenu.newDOMNode(a.ancestor())}})};return{menu:function(d,f){a(f).each(function(f,g){var h=[];a(g).find("li").each(function(b,c){h.push(a(c).html().trim())});var i={triggerMessage:d,links:h},j=a.proxy(e,g);b.render("core/menu",i).done(j).fail(c.exception)})}}});
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
define(["jquery","core/ajax","core/str","tool_lp/competencypicker","core/templates","core/notification"],function(a,b,c,d,e,f){var g=function(a,b,c,d,e,f){this.buttonSelector=a,this.inputHiddenSelector=b,this.staticElementSelector=c,this.frameworkId=d,this.frameworkMaxLevel=e,this.pageContextId=f,this.registerEvents()};return g.prototype.buttonSelector=null,g.prototype.inputHiddenSelector=null,g.prototype.staticElementSelector=null,g.prototype.frameworkId=null,g.prototype.frameworkMaxLevel=null,g.prototype.pageContextId=null,g.prototype.setParent=function(d){var e=this;0!==d.competencyId?b.call([{methodname:"core_competency_read_competency",args:{id:d.competencyId}}])[0].done(function(b){a(e.staticElementSelector).html(b.shortname),a(e.inputHiddenSelector).val(b.id)}).fail(f.exception):c.get_string("competencyframeworkroot","tool_lp").then(function(b){a(e.staticElementSelector).html(b),a(e.inputHiddenSelector).val(d.competencyId)}).fail(f.exception)},g.prototype.registerEvents=function(){var c=this;a(c.buttonSelector).on("click",function(a){a.preventDefault();var g=new d(c.pageContextId,c.frameworkId,"self",!1),h=c.frameworkMaxLevel;g._fetchCompetencies=function(a,c){var d=this;return b.call([{methodname:"core_competency_search_competencies",args:{searchtext:c,competencyframeworkid:a}}])[0].done(function(a){function b(a,c){for(var d=0;d<c.length;d++){var e=String(c[d].path),g=e.split("/").length-2;g>=h&&"0"!==c[d].id&&f.push(c[d].id),c[d].parentid==a.id&&(a.haschildren=!0,c[d].children=[],c[d].haschildren=!1,a.children[a.children.length]=c[d],b(c[d],c))}}var c,e,f=[],g=[];for(c=0;c<a.length;c++)e=a[c],"0"==e.parentid&&(e.children=[],e.haschildren=0,g[g.length]=e,b(e,a));d._competencies=g,d.setDisallowedCompetencyIDs(f)}.bind(d)).fail(f.exception)},g._render=function(){var a=this;return a._preRender().then(function(){var b={competencies:a._competencies,framework:a._getFramework(a._frameworkId),frameworks:a._frameworks,search:a._searchText,singleFramework:a._singleFramework};return e.render("tool_lp/competency_picker_competencyform",b)}.bind(a))},g.on("save",function(a,b){c.setParent(b)}.bind(c)),g.display()})},{init:function(a,b,c,d,e,f){new g(a,b,c,d,e,f)}}});
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
define(["jquery","core/templates","core/ajax","core/notification","core/str"],function(a,b,c,d,e){var f=1,g=2,h=0,i=0,j=null,k=function(c,d){a('[data-region="plans"]').replaceWith(c),b.runTemplateJS(d)},l=function(a){b.render("tool_lp/plans_page",a).done(k).fail(d.exception)},m=function(){var a=c.call([{methodname:"core_competency_delete_plan",args:{id:h}},{methodname:"tool_lp_data_for_plans_page",args:{userid:i}}]);a[1].done(l).fail(d.exception)},n=function(b){b.preventDefault(),h=a(this).attr("data-planid"),i=a(this).attr("data-userid");var f=c.call([{methodname:"core_competency_read_plan",args:{id:h}}]);f[0].done(function(a){e.get_strings([{key:"confirm",component:"moodle"},{key:"deleteplan",component:"tool_lp",param:a.name},{key:"delete",component:"moodle"},{key:"cancel",component:"moodle"}]).done(function(a){d.confirm(a[0],a[1],a[2],a[3],m)}).fail(d.exception)}).fail(d.exception)},o=function(){var a=c.call([{methodname:"core_competency_update_plan_status",args:{planid:h,status:j}},{methodname:"tool_lp_data_for_plans_page",args:{userid:i}}]);a[1].done(l).fail(d.exception)},p=function(b){b.preventDefault(),h=a(this).attr("data-planid"),i=a(this).attr("data-userid"),j=parseInt(a(this).attr("data-status"));var k="";j===f&&(k="reopenplan"),j===g&&(k="completeplan");var l=c.call([{methodname:"core_competency_read_plan",args:{id:h}}]);l[0].done(function(a){e.get_strings([{key:"confirm",component:"moodle"},{key:k+"confirm",component:"tool_lp",param:a.name},{key:k,component:"tool_lp"},{key:"cancel",component:"moodle"}]).done(function(a){d.confirm(a[0],a[1],a[2],a[3],o)}).fail(d.exception)}).fail(d.exception)};return{deleteHandler:n,updateStatusHandler:p}});
+1
View File
@@ -0,0 +1 @@
define(["jquery","core/notification","core/templates","core/ajax","tool_lp/dialogue","tool_lp/scalevalues"],function(a,b,c,d,e,f){var g=function(b,c,d){this.selectSelector=b,this.inputSelector=c,this.triggerSelector=d,this.originalscaleid=a(b).val(),a(b).on("change",this.scaleChangeHandler.bind(this)).change(),a(d).click(this.showConfig.bind(this))};return g.prototype.selectSelector=null,g.prototype.inputSelector=null,g.prototype.triggerSelector=null,g.prototype.scalevalues=null,g.prototype.originalscaleid=0,g.prototype.scaleid=0,g.prototype.popup=null,g.prototype.showConfig=function(){var d=this;if(this.scaleid=a(this.selectSelector).val(),!(this.scaleid<=0)){var f=a(this.selectSelector).find("option:selected").text();this.getScaleValues(this.scaleid).done(function(){var a={scalename:f,scales:d.scalevalues};c.render("tool_lp/scale_configuration_page",a).done(function(a){new e(f,a,d.initScaleConfig.bind(d))}).fail(b.exception)}).fail(b.exception)}},g.prototype.retrieveOriginalScaleConfig=function(){var b=a(this.inputSelector).val();if(""!==b){var c=a.parseJSON(b),d=c.shift();if(d.scaleid===this.originalscaleid)return c}return""},g.prototype.initScaleConfig=function(b){this.popup=b;var c=a(b.getContent());if(this.originalscaleid===this.scaleid){var d=this.retrieveOriginalScaleConfig();""!==d&&d.forEach(function(a){1===a.scaledefault&&c.find('[data-field="tool_lp_scale_default_'+a.id+'"]').attr("checked",!0),1===a.proficient&&c.find('[data-field="tool_lp_scale_proficient_'+a.id+'"]').attr("checked",!0)})}c.on("click",'[data-action="close"]',function(){this.setScaleConfig(),b.close()}.bind(this)),c.on("click",'[data-action="cancel"]',function(){b.close()}.bind(this))},g.prototype.setScaleConfig=function(){var b=a(this.popup.getContent()),c=[{scaleid:this.scaleid}];this.scalevalues.forEach(function(a){var d=0,e=0;b.find('[data-field="tool_lp_scale_default_'+a.id+'"]').is(":checked")&&(d=1),b.find('[data-field="tool_lp_scale_proficient_'+a.id+'"]').is(":checked")&&(e=1),(d||e)&&c.push({id:a.id,scaledefault:d,proficient:e})});var d=JSON.stringify(c);a(this.inputSelector).val(d),this.originalscaleid=this.scaleid},g.prototype.getScaleValues=function(a){return f.get_values(a).then(function(a){return this.scalevalues=a,a}.bind(this))},g.prototype.scaleChangeHandler=function(b){a(b.target).val()<=0?a(this.triggerSelector).prop("disabled",!0):a(this.triggerSelector).prop("disabled",!1)},{init:function(a,b,c){return new g(a,b,c)}}});
+1
View File
@@ -0,0 +1 @@
define(["jquery","core/ajax"],function(a,b){var c=[];return{get_values:function(d){var e=a.Deferred();return"undefined"==typeof c[d]?b.call([{methodname:"core_competency_get_scale_values",args:{scaleid:d},done:function(a){c[d]=a,e.resolve(a)},fail:e.reject}]):e.resolve(c[d]),e.promise()}}});
+1
View File
@@ -0,0 +1 @@
define(["jquery","core/templates","core/ajax","core/notification","core/str","tool_lp/actionselector"],function(a,b,c,d,e,f){var g=0,h=0,i=!0,j=function(c,d){a('[data-region="managetemplates"]').replaceWith(c),b.runTemplateJS(d)},k=function(a){b.render("tool_lp/manage_templates_page",a).done(j).fail(d.exception)},l=function(){var a=c.call([{methodname:"core_competency_delete_template",args:{id:h,deleteplans:i}},{methodname:"tool_lp_data_for_templates_manage_page",args:{pagecontext:{contextid:g}}}]);a[1].done(k).fail(d.exception)},m=function(b){b.preventDefault(),h=a(this).attr("data-templateid");var e=c.call([{methodname:"core_competency_duplicate_template",args:{id:h}},{methodname:"tool_lp_data_for_templates_manage_page",args:{pagecontext:{contextid:g}}}]);e[1].done(k).fail(d.exception)},n=function(b){b.preventDefault();var g=a(this).attr("data-templateid");h=g,i=!0;var j=c.call([{methodname:"core_competency_read_template",args:{id:h}},{methodname:"core_competency_template_has_related_data",args:{id:h}}]);j[0].done(function(a){j[1].done(function(b){b?e.get_strings([{key:"deletetemplate",component:"tool_lp",param:a.shortname},{key:"deletetemplatewithplans",component:"tool_lp"},{key:"deleteplans",component:"tool_lp"},{key:"unlinkplanstemplate",component:"tool_lp"},{key:"confirm",component:"moodle"},{key:"cancel",component:"moodle"}]).done(function(a){var b=[{text:a[2],value:"delete"},{text:a[3],value:"unlink"}],c=new f(a[0],a[1],b,a[4],a[5]);c.display(),c.on("save",function(a,b){"delete"!=b.action&&(i=!1),l()})}).fail(d.exception):e.get_strings([{key:"confirm",component:"moodle"},{key:"deletetemplate",component:"tool_lp",param:a.shortname},{key:"delete",component:"moodle"},{key:"cancel",component:"moodle"}]).done(function(a){d.confirm(a[0],a[1],a[2],a[3],l)}).fail(d.exception)}).fail(d.exception)}).fail(d.exception)};return{deleteHandler:n,duplicateHandler:m,init:function(a){g=a}}});
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
define(["jquery"],function(a){var b=function(b,c,d,e,f,g){this._baseUrl=d,this._userId=e+"",this._competencyId=f+"",this._courseId=g,this._ignoreFirstUser=!0,this._ignoreFirstCompetency=!0,a(b).on("change",this._userChanged.bind(this)),a(c).on("change",this._competencyChanged.bind(this))};return b.prototype._userChanged=function(b){if(this._ignoreFirstUser)return void(this._ignoreFirstUser=!1);var c=a(b.target).val(),d="?userid="+c+"&courseid="+this._courseId+"&competencyid="+this._competencyId;document.location=this._baseUrl+d},b.prototype._competencyChanged=function(b){if(this._ignoreFirstCompetency)return void(this._ignoreFirstCompetency=!1);var c=a(b.target).val(),d="?userid="+this._userId+"&courseid="+this._courseId+"&competencyid="+c;document.location=this._baseUrl+d},b.prototype._competencyId=null,b.prototype._userId=null,b.prototype._courseId=null,b.prototype._baseUrl=null,b.prototype._ignoreFirstUser=null,b.prototype._ignoreFirstCompetency=null,b});
+1
View File
@@ -0,0 +1 @@
define(["jquery","core/notification","core/ajax","core/templates"],function(a,b,c,d){var e=function(a,b,c,d,e,f){this._rootElement=a,this._competencyId=b,this._userId=c,this._planId=d,this._courseId=e,this._valid=!0,this._displayuser="undefined"!=typeof f?f:!1,this._planId?(this._methodName="tool_lp_data_for_user_competency_summary_in_plan",this._args={competencyid:this._competencyId,planid:this._planId},this._templateName="tool_lp/user_competency_summary_in_plan"):this._courseId?(this._methodName="tool_lp_data_for_user_competency_summary_in_course",this._args={userid:this._userId,competencyid:this._competencyId,courseid:this._courseId},this._templateName="tool_lp/user_competency_summary_in_course"):(this._methodName="tool_lp_data_for_user_competency_summary",this._args={userid:this._userId,competencyid:this._competencyId},this._templateName="tool_lp/user_competency_summary")};return e.prototype.reload=function(){var a=this,e=[];this._valid&&(e=c.call([{methodname:this._methodName,args:this._args}]),e[0].done(function(c){a._displayuser&&(c.displayuser=!0),d.render(a._templateName,c).done(function(b,c){d.replaceNode(a._rootElement,b,c)}).fail(b.exception)}).fail(b.exception))},e.prototype._rootElement=null,e.prototype._courseId=null,e.prototype._valid=null,e.prototype._planId=null,e.prototype._competencyId=null,e.prototype._userId=null,e.prototype._methodName=null,e.prototype._args=null,e.prototype._templateName=null,e.prototype._displayuser=!1,e});
@@ -0,0 +1 @@
define(["jquery","core/templates","core/ajax","core/notification","core/str","tool_lp/menubar","tool_lp/event_base"],function(a,b,c,d,e,f,g){var h=function(){g.prototype.constructor.apply(this,[])};return h.prototype=Object.create(g.prototype),h.prototype._nodeSelector='[data-node="user-competency"]',h.prototype._cancelReviewRequest=function(a){var b={methodname:"core_competency_user_competency_cancel_review_request",args:{userid:a.userid,competencyid:a.competencyid}};c.call([b])[0].then(function(){this._trigger("review-request-cancelled",a),this._trigger("status-changed",a)}.bind(this),function(){this._trigger("error-occured",a)}.bind(this))},h.prototype.cancelReviewRequest=function(a){this._cancelReviewRequest(a)},h.prototype._cancelReviewRequestHandler=function(b){b.preventDefault();var c=this._findUserCompetencyData(a(b.target));this.cancelReviewRequest(c)},h.prototype._requestReview=function(a){var b={methodname:"core_competency_user_competency_request_review",args:{userid:a.userid,competencyid:a.competencyid}};c.call([b])[0].then(function(){this._trigger("review-requested",a),this._trigger("status-changed",a)}.bind(this),function(){this._trigger("error-occured",a)}.bind(this))},h.prototype.requestReview=function(a){this._requestReview(a)},h.prototype._requestReviewHandler=function(b){b.preventDefault();var c=this._findUserCompetencyData(a(b.target));this.requestReview(c)},h.prototype._startReview=function(a){var b={methodname:"core_competency_user_competency_start_review",args:{userid:a.userid,competencyid:a.competencyid}};c.call([b])[0].then(function(){this._trigger("review-started",a),this._trigger("status-changed",a)}.bind(this),function(){this._trigger("error-occured",a)}.bind(this))},h.prototype.startReview=function(a){this._startReview(a)},h.prototype._startReviewHandler=function(b){b.preventDefault();var c=this._findUserCompetencyData(a(b.target));this.startReview(c)},h.prototype._stopReview=function(a){var b={methodname:"core_competency_user_competency_stop_review",args:{userid:a.userid,competencyid:a.competencyid}};c.call([b])[0].then(function(){this._trigger("review-stopped",a),this._trigger("status-changed",a)}.bind(this),function(){this._trigger("error-occured",a)}.bind(this))},h.prototype.stopReview=function(a){this._stopReview(a)},h.prototype._stopReviewHandler=function(b){b.preventDefault();var c=this._findUserCompetencyData(a(b.target));this.stopReview(c)},h.prototype.enhanceMenubar=function(a){f.enhance(a,{'[data-action="request-review"]':this._requestReviewHandler.bind(this),'[data-action="cancel-review-request"]':this._cancelReviewRequestHandler.bind(this)})},h.prototype._findUserCompetencyData=function(a){var b,c=a.parents(this._nodeSelector);if(1!=c.length)throw new Error("The evidence node was not located.");if(b=c.data(),"undefined"==typeof b||"undefined"==typeof b.userid||"undefined"==typeof b.competencyid)throw new Error("User competency data could not be found.");return b},h.prototype.enhanceMenubar=function(a){f.enhance(a,{'[data-action="request-review"]':this._requestReviewHandler.bind(this),'[data-action="cancel-review-request"]':this._cancelReviewRequestHandler.bind(this),'[data-action="start-review"]':this._startReviewHandler.bind(this),'[data-action="stop-review"]':this._stopReviewHandler.bind(this)})},h.prototype.registerEvents=function(b){var c=a(b);c.find('[data-action="request-review"]').click(this._requestReviewHandler.bind(this)),c.find('[data-action="cancel-review-request"]').click(this._cancelReviewRequestHandler.bind(this)),c.find('[data-action="start-review"]').click(this._startReviewHandler.bind(this)),c.find('[data-action="stop-review"]').click(this._stopReviewHandler.bind(this))},h});
File diff suppressed because one or more lines are too long
+193
View File
@@ -0,0 +1,193 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Action selector.
*
* To handle 'save' events use: actionselector.on('save')
* This will receive the information to display in popup.
* The actions have the format [{'text': sometext, 'value' : somevalue}].
*
* @package tool_lp
* @copyright 2016 Serge Gauthier - <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery',
'core/notification',
'core/ajax',
'core/templates',
'tool_lp/dialogue',
'tool_lp/event_base'],
function($, Notification, Ajax, Templates, Dialogue, EventBase) {
/**
* Action selector class.
* @param {String} title The title of popup.
* @param {String} message The message to display.
* @param {object} actions The actions that can be selected.
* @param {String} confirm Text for confirm button.
* @param {String} cancel Text for cancel button.
*/
var ActionSelector = function(title, message, actions, confirm, cancel) {
var self = this;
EventBase.prototype.constructor.apply(this, []);
self._title = title;
self._message = message;
self._actions = actions;
self._confirm = confirm;
self._cancel = cancel;
self._selectedValue = null;
self._reset();
};
ActionSelector.prototype = Object.create(EventBase.prototype);
/** @type {String} The value that was selected. */
ActionSelector.prototype._selectedValue = null;
/** @type {Dialogue} The reference to the dialogue. */
ActionSelector.prototype._popup = null;
/** @type {String} The title of popup. */
ActionSelector.prototype._title = null;
/** @type {String} The message in popup. */
ActionSelector.prototype._message = null;
/** @type {object} The information for radion buttons. */
ActionSelector.prototype._actions = null;
/** @type {String} The text for confirm button. */
ActionSelector.prototype._confirm = null;
/** @type {String} The text for cancel button. */
ActionSelector.prototype._cancel = null;
/**
* Hook to executed after the view is rendered.
*
* @method _afterRender
*/
ActionSelector.prototype._afterRender = function() {
var self = this;
// Confirm button is disabled until a choice is done.
self._find('[data-action="action-selector-confirm"]').attr('disabled', 'disabled');
// Add listener for radio buttons change.
self._find('[data-region="action-selector-radio-buttons"]').change(function() {
self._selectedValue = $("input[type='radio']:checked").val();
self._find('[data-action="action-selector-confirm"]').removeAttr('disabled');
self._refresh.bind(self);
}.bind(self));
// Add listener for cancel.
self._find('[data-action="action-selector-cancel"]').click(function(e) {
e.preventDefault();
self.close();
}.bind(self));
// Add listener for confirm.
self._find('[data-action="action-selector-confirm"]').click(function(e) {
e.preventDefault();
if (!self._selectedValue.length) {
return;
}
self._trigger('save', { action: self._selectedValue });
self.close();
}.bind(self));
};
/**
* Close the dialogue.
*
* @method close
*/
ActionSelector.prototype.close = function() {
var self = this;
self._popup.close();
self._reset();
};
/**
* Opens the action selector.
*
* @method display
* @return {Promise}
*/
ActionSelector.prototype.display = function() {
var self = this;
return self._render().then(function(html) {
self._popup = new Dialogue(
self._title,
html,
self._afterRender.bind(self)
);
}.bind(self)).fail(Notification.exception);
};
/**
* Find a node in the dialogue.
*
* @param {String} selector
* @method _find
*/
ActionSelector.prototype._find = function(selector) {
return $(this._popup.getContent()).find(selector);
};
/**
* Refresh the view.
*
* @method _refresh
* @return {Promise}
*/
ActionSelector.prototype._refresh = function() {
var self = this;
return self._render().then(function(html) {
self._find('[data-region="action-selector"]').replaceWith(html);
self._afterRender();
}.bind(self));
};
/**
* Render the dialogue.
*
* @method _render
* @return {Promise}
*/
ActionSelector.prototype._render = function() {
var self = this;
var choices = [];
for (var i in self._actions) {
choices.push(self._actions[i]);
}
var content = {'message': self._message, 'choices' : choices,
'confirm' : self._confirm, 'cancel' : self._cancel};
return Templates.render('tool_lp/action_selector', content);
};
/**
* Reset the dialogue properties.
*
* This does not reset everything, just enough to reset the UI.
*
* @method _reset
*/
ActionSelector.prototype._reset = function() {
this._popup = null;
this._selectedValue = '';
};
return /** @alias module:tool_lp/actionselector */ ActionSelector;
});
+338
View File
@@ -0,0 +1,338 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Handle add/remove competency links.
*
* @module tool_lp/competencies
* @package tool_lp
* @copyright 2015 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery',
'core/notification',
'core/ajax',
'core/templates',
'core/str',
'tool_lp/competencypicker',
'tool_lp/dragdrop-reorder'],
function($, notification, ajax, templates, str, Picker, dragdrop) {
/**
* Constructor
*
* @param {Number} itemid
* @param {String} itemtype
*/
var competencies = function(itemid, itemtype, pagectxid) {
this.itemid = itemid;
this.itemtype = itemtype;
this.pageContextId = pagectxid;
this.pickerInstance = null;
$('[data-region="actions"] button').prop('disabled', false);
this.registerEvents();
this.registerDragDrop();
};
/**
* Initialise the drag/drop code.
* @method registerDragDrop
*/
competencies.prototype.registerDragDrop = function() {
var localthis = this;
// Init this module.
str.get_string('movecompetency', 'tool_lp').done(
function(movestring) {
dragdrop.dragdrop('movecompetency',
movestring,
{ identifier: 'movecompetency', component: 'tool_lp'},
{ identifier: 'movecompetencyafter', component: 'tool_lp'},
'drag-samenode',
'drag-parentnode',
'drag-handlecontainer',
function(drag, drop) {
localthis.handleDrop.call(localthis, drag, drop);
});
}
).fail(notification.exception);
};
/**
* Handle a drop from a drag/drop operation.
*
* @method handleDrop
* @param {DOMNode} drag The dragged node.
* @param {DOMNode} drop The dropped on node.
*/
competencies.prototype.handleDrop = function(drag, drop) {
var fromid = $(drag).data('id');
var toid = $(drop).data('id');
var localthis = this;
var requests = [];
if (localthis.itemtype == 'course') {
requests = ajax.call([
{
methodname: 'core_competency_reorder_course_competency',
args: { courseid: localthis.itemid, competencyidfrom: fromid, competencyidto: toid }
}
]);
} else if (localthis.itemtype == 'template') {
requests = ajax.call([
{
methodname: 'core_competency_reorder_template_competency',
args: { templateid: localthis.itemid, competencyidfrom: fromid, competencyidto: toid }
}
]);
} else if (localthis.itemtype == 'plan') {
requests = ajax.call([
{
methodname: 'core_competency_reorder_plan_competency',
args: { planid: localthis.itemid, competencyidfrom: fromid, competencyidto: toid }
}
]);
} else {
return null;
}
requests[0].fail(notification.exception);
};
/**
* Pick a competency
*
* @method pickCompetency
*/
competencies.prototype.pickCompetency = function() {
var self = this;
var requests;
var pagerender;
var pageregion;
var pageContextIncludes;
if (!self.pickerInstance) {
if (self.itemtype === 'template' || self.itemtype === 'course') {
pageContextIncludes = 'parents';
}
self.pickerInstance = new Picker(self.pageContextId, false, pageContextIncludes);
self.pickerInstance.on('save', function(e, data) {
var compIds = data.competencyIds;
if (self.itemtype === "course") {
requests = [];
$.each(compIds, function(index, compId) {
requests.push({
methodname: 'core_competency_add_competency_to_course',
args: { courseid: self.itemid, competencyid: compId }
});
});
requests.push({
methodname: 'tool_lp_data_for_course_competencies_page',
args: { courseid: self.itemid }
});
pagerender = 'tool_lp/course_competencies_page';
pageregion = 'coursecompetenciespage';
} else if (self.itemtype === "template") {
requests = [];
$.each(compIds, function(index, compId) {
requests.push({
methodname: 'core_competency_add_competency_to_template',
args: { templateid: self.itemid, competencyid: compId }
});
});
requests.push({
methodname: 'tool_lp_data_for_template_competencies_page',
args: { templateid: self.itemid, pagecontext: { contextid: self.pageContextId }}
});
pagerender = 'tool_lp/template_competencies_page';
pageregion = 'templatecompetenciespage';
} else if (self.itemtype === "plan") {
requests = [];
$.each(compIds, function(index, compId) {
requests.push({
methodname: 'core_competency_add_competency_to_plan',
args: { planid: self.itemid, competencyid: compId }
});
});
requests.push({
methodname: 'tool_lp_data_for_plan_page',
args: { planid: self.itemid}
});
pagerender = 'tool_lp/plan_page';
pageregion = 'plan-page';
}
ajax.call(requests)[requests.length - 1].then(function(context) {
return templates.render(pagerender, context).done(function(html, js) {
$('[data-region="' + pageregion + '"]').replaceWith(html);
templates.runTemplateJS(js);
});
}, notification.exception);
});
}
self.pickerInstance.display();
};
/**
* Delete the link between competency and course, template or plan. Reload the page.
*
* @method doDelete
* @param {int} deleteid The id of record to delete.
*/
competencies.prototype.doDelete = function(deleteid) {
var localthis = this;
var requests = [],
pagerender = '',
pageregion = '';
// Delete the link and reload the page template.
if (localthis.itemtype == 'course') {
requests = ajax.call([
{ methodname: 'core_competency_remove_competency_from_course',
args: { courseid: localthis.itemid, competencyid: deleteid } },
{ methodname: 'tool_lp_data_for_course_competencies_page',
args: { courseid: localthis.itemid } }
]);
pagerender = 'tool_lp/course_competencies_page';
pageregion = 'coursecompetenciespage';
} else if (localthis.itemtype == 'template') {
requests = ajax.call([
{ methodname: 'core_competency_remove_competency_from_template',
args: { templateid: localthis.itemid, competencyid: deleteid } },
{ methodname: 'tool_lp_data_for_template_competencies_page',
args: { templateid: localthis.itemid, pagecontext: { contextid: localthis.pageContextId } } }
]);
pagerender = 'tool_lp/template_competencies_page';
pageregion = 'templatecompetenciespage';
} else if (localthis.itemtype == 'plan') {
requests = ajax.call([
{ methodname: 'core_competency_remove_competency_from_plan',
args: { planid: localthis.itemid, competencyid: deleteid } },
{ methodname: 'tool_lp_data_for_plan_page',
args: { planid: localthis.itemid } }
]);
pagerender = 'tool_lp/plan_page';
pageregion = 'plan-page';
}
requests[1].done(function(context) {
templates.render(pagerender, context).done(function(html, js) {
$('[data-region="' + pageregion + '"]').replaceWith(html);
templates.runTemplateJS(js);
}).fail(notification.exception);
}).fail(notification.exception);
};
/**
* Show a confirm dialogue before deleting a competency.
*
* @method deleteHandler
* @param {int} deleteid The id of record to delete.
*/
competencies.prototype.deleteHandler = function(deleteid) {
var localthis = this;
var requests = [];
var message;
if (localthis.itemtype == 'course') {
message = 'unlinkcompetencycourse';
} else if (localthis.itemtype == 'template') {
message = 'unlinkcompetencytemplate';
} else if (localthis.itemtype == 'plan') {
message = 'unlinkcompetencyplan';
} else {
return;
}
requests = ajax.call([{
methodname: 'core_competency_read_competency',
args: { id: deleteid }
}]);
requests[0].done(function(competency) {
str.get_strings([
{ key: 'confirm', component: 'moodle' },
{ key: message, component: 'tool_lp', param: competency.shortname },
{ key: 'confirm', component: 'moodle' },
{ key: 'cancel', component: 'moodle' }
]).done(function (strings) {
notification.confirm(
strings[0], // Confirm.
strings[1], // Unlink the competency X from the course?
strings[2], // Confirm.
strings[3], // Cancel.
function() {
localthis.doDelete(deleteid);
}
);
}).fail(notification.exception);
}).fail(notification.exception);
};
/**
* Register the javascript event handlers for this page.
*
* @method registerEvents
*/
competencies.prototype.registerEvents = function() {
var localthis = this;
if (localthis.itemtype == 'course') {
// Course completion rule handling.
$('[data-region="coursecompetenciespage"]').on('change', 'select[data-field="ruleoutcome"]', function(e){
var requests = [];
var pagerender = 'tool_lp/course_competencies_page';
var pageregion = 'coursecompetenciespage';
var coursecompetencyid = $(e.target).data('id');
var ruleoutcome = $(e.target).val();
requests = ajax.call([
{ methodname: 'core_competency_set_course_competency_ruleoutcome',
args: { coursecompetencyid: coursecompetencyid, ruleoutcome: ruleoutcome } },
{ methodname: 'tool_lp_data_for_course_competencies_page',
args: { courseid: localthis.itemid } }
]);
requests[1].done(function(context) {
templates.render(pagerender, context).done(function(html, js) {
$('[data-region="' + pageregion + '"]').replaceWith(html);
templates.runTemplateJS(js);
}).fail(notification.exception);
}).fail(notification.exception);
});
}
$('[data-region="actions"] button').click(function(e) {
e.preventDefault();
localthis.pickCompetency();
});
$('[data-action="delete-competency-link"]').click(function(e) {
e.preventDefault();
var deleteid = $(e.target).closest('[data-id]').data('id');
localthis.deleteHandler(deleteid);
});
};
return /** @alias module:tool_lp/competencies */ competencies;
});
@@ -0,0 +1,83 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Competency rule config.
*
* @package tool_lp
* @copyright 2015 Frédéric Massart - FMCorz.net
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery',
'core/str'],
function($, Str) {
var OUTCOME_NONE = 0,
OUTCOME_EVIDENCE = 1,
OUTCOME_COMPLETE = 2,
OUTCOME_RECOMMEND = 3;
return /** @alias module:tool_lp/competency_outcomes */ {
NONE: OUTCOME_NONE,
EVIDENCE: OUTCOME_EVIDENCE,
COMPLETE: OUTCOME_COMPLETE,
RECOMMEND: OUTCOME_RECOMMEND,
/**
* Get all the outcomes.
*
* @return {Object} Indexed by outcome code, contains code and name.
* @method getAll
*/
getAll: function() {
var self = this;
return Str.get_strings([
{ key: 'competencyoutcome_none', component: 'tool_lp' },
{ key: 'competencyoutcome_evidence', component: 'tool_lp' },
{ key: 'competencyoutcome_recommend', component: 'tool_lp' },
{ key: 'competencyoutcome_complete', component: 'tool_lp' },
]).then(function(strings) {
var outcomes = {};
outcomes[self.NONE] = { code: self.NONE, name: strings[0] };
outcomes[self.EVIDENCE] = { code: self.EVIDENCE, name: strings[1] };
outcomes[self.RECOMMEND] = { code: self.RECOMMEND, name: strings[2] };
outcomes[self.COMPLETE] = { code: self.COMPLETE, name: strings[3] };
return outcomes;
});
},
/**
* Get the string for an outcome.
*
* @param {Number} id The outcome code.
* @return {Promise Resolved with the string.
* @method getString
*/
getString: function(id) {
var self = this,
all = self.getAll();
return all.then(function(outcomes) {
if (typeof outcomes[id] === 'undefined') {
return $.Deferred().reject().promise();
}
return outcomes[id].name;
});
}
};
});
+175
View File
@@ -0,0 +1,175 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Competency rule base module.
*
* @package tool_lp
* @copyright 2015 Frédéric Massart - FMCorz.net
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery'], function($) {
/**
* Competency rule abstract class.
*
* Any competency rule should extend this object. The event 'change' should be
* triggered on the instance when the configuration has changed. This will allow
* the components using the rule to gather the config, or check its validity.
*
* this._triggerChange();
*
* @param {Tree} tree The competency tree.
*/
var Rule = function(tree) {
this._eventNode = $('<div>');
this._ready = $.Deferred();
this._tree = tree;
};
/** @type {Object} The current competency. */
Rule.prototype._competency = null;
/** @type {Node} The node we attach the events to. */
Rule.prototype._eventNode = null;
/** @type {Promise} Resolved when the object is ready. */
Rule.prototype._ready = null;
/** @type {Tree} The competency tree. */
Rule.prototype._tree = null;
/**
* Whether or not the current competency can be configured using this rule.
*
* @return {Boolean}
* @method canConfig
*/
Rule.prototype.canConfig = function() {
return this._tree.hasChildren(this._competency.id);
};
/**
* The config established by this rule.
*
* To override in subclasses when relevant.
*
* @return {String|null}
* @method getConfig
*/
Rule.prototype.getConfig = function() {
return null;
};
/**
* Return the type of the module.
*
* @return {String}
* @method getType
*/
Rule.prototype.getType = function() {
throw new Error('Not implemented');
};
/**
* The init process.
*
* Do not override this, instead override _load.
*
* @return {Promise} Revoled when the plugin is initialised.
* @method init
*/
Rule.prototype.init = function() {
return this._load();
};
/**
* Callback to inject the template.
*
* @param {Node} container Node to inject in.
* @return {Promise} Resolved when done.
* @method injectTemplate
*/
Rule.prototype.injectTemplate = function() {
return $.Deferred().reject().promise();
};
/**
* Whether or not the current config is valid.
*
* Plugins should override this.
*
* @return {Boolean}
* @method _isValid
*/
Rule.prototype.isValid = function() {
return false;
};
/**
* Load the class.
*
* @return {Promise}
* @method _load
* @protected
*/
Rule.prototype._load = function() {
return $.when();
};
/**
* Register an event listener.
*
* @param {String} type The event type.
* @param {Function} handler The event listener.
* @method on
*/
Rule.prototype.on = function(type, handler) {
this._eventNode.on(type, handler);
};
/**
* Sets the current competency.
*
* @param {Competency} competency
* @method setTargetCompetency
*/
Rule.prototype.setTargetCompetency = function(competency) {
this._competency = competency;
};
/**
* Trigger an event.
*
* @param {String} type The type of event.
* @param {Object} The data to pass to the listeners.
* @method _trigger
* @protected
*/
Rule.prototype._trigger = function(type, data) {
this._eventNode.trigger(type, [data]);
};
/**
* Trigger the change event.
*
* @method _triggerChange
* @protected
*/
Rule.prototype._triggerChange = function() {
this._trigger('change', this);
};
return /** @alias module:tool_lp/competency_rule */ Rule;
});
@@ -0,0 +1,60 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Competency rule all module.
*
* @package tool_lp
* @copyright 2015 Frédéric Massart - FMCorz.net
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery',
'core/str',
'tool_lp/competency_rule',
],
function($, Str, RuleBase) {
/**
* Competency rule all class.
*/
var Rule = function() {
RuleBase.apply(this, arguments);
};
Rule.prototype = Object.create(RuleBase.prototype);
/**
* Return the type of the module.
*
* @return {String}
* @method getType
*/
Rule.prototype.getType = function() {
return 'core_competency\\competency_rule_all';
};
/**
* Whether or not the current config is valid.
*
* @return {Boolean}
* @method isValid
*/
Rule.prototype.isValid = function() {
return true;
};
return /** @alias module:tool_lp/competency_rule_all */ Rule;
});
@@ -0,0 +1,199 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Competency rule points module.
*
* @package tool_lp
* @copyright 2015 Frédéric Massart - FMCorz.net
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery',
'core/str',
'core/templates',
'tool_lp/competency_rule',
],
function($, Str, Templates, RuleBase) {
/**
* Competency rule points class.
*/
var Rule = function() {
RuleBase.apply(this, arguments);
};
Rule.prototype = Object.create(RuleBase.prototype);
/** @type {Node} Reference to the container in which the template was included. */
Rule.prototype._container = null;
/** @type {Boolean} Whether or not the template was included. */
Rule.prototype._templateLoaded = false;
/**
* The config established by this rule.
*
* @return {String}
* @method getConfig
*/
Rule.prototype.getConfig = function() {
return JSON.stringify({
base: {
points: this._getRequiredPoints(),
},
competencies: this._getCompetenciesConfig()
});
};
/**
* Gathers the input provided by the user for competencies.
*
* @return {Array} Containing id, points and required.
* @method _getCompetenciesConfig
* @protected
*/
Rule.prototype._getCompetenciesConfig = function() {
var competencies = [];
this._container.find('[data-competency]').each(function() {
var node = $(this),
id = node.data('competency'),
points = parseInt(node.find('[name="points"]').val(), 10),
required = node.find('[name="required"]').prop('checked');
competencies.push({
id: id,
points: points,
required: required ? 1 : 0
});
});
return competencies;
};
/**
* Fetches the required points set by the user.
*
* @return {Number}
* @method _getRequiredPoints
* @protected
*/
Rule.prototype._getRequiredPoints = function() {
return parseInt(this._container.find('[name="requiredpoints"]').val() || 1, 10);
};
/**
* Return the type of the module.
*
* @return {String}
* @method getType
*/
Rule.prototype.getType = function() {
return 'core_competency\\competency_rule_points';
};
/**
* Callback to inject the template.
*
* @param {Node} container Node to inject in.
* @return {Promise} Resolved when done.
* @method injectTemplate
*/
Rule.prototype.injectTemplate = function(container) {
var self = this,
children = this._tree.getChildren(this._competency.id),
context,
config = {
base: { points: 2 },
competencies: []
};
this._templateLoaded = false;
// Only pre-load the configuration when the competency is using this rule.
if (self._competency.ruletype == self.getType()) {
try {
config = JSON.parse(self._competency.ruleconfig);
} catch (e) {
}
}
context = {
requiredpoints: (config && config.base) ? config.base.points : 2,
competency: self._competency,
children: []
};
$.each(children, function(index, child) {
var competency = {
id: child.id,
shortname: child.shortname,
required: false,
points: 0
};
if (config) {
$.each(config.competencies, function(index, comp) {
if (comp.id == competency.id) {
competency.required = comp.required ? true : false;
competency.points = comp.points;
}
});
}
context.children.push(competency);
});
return Templates.render('tool_lp/competency_rule_points', context).then(function(html) {
self._container = container;
container.html(html);
container.find('input').change(function() {
self._triggerChange();
});
// We're done, let's trigger a change.
self._templateLoaded = true;
self._triggerChange();
});
};
/**
* Whether or not the current config is valid.
*
* @return {Boolean}
* @method isValid
*/
Rule.prototype.isValid = function() {
if (!this._templateLoaded) {
return false;
}
var required = this._getRequiredPoints(),
max = 0,
valid = true;
$.each(this._getCompetenciesConfig(), function(index, competency) {
if (competency.points < 0) {
valid = false;
}
max += competency.points;
});
valid = valid && max >= required;
return valid;
};
return /** @alias module:tool_lp/competency_rule_all */ Rule;
});
+861
View File
@@ -0,0 +1,861 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Handle selection changes and actions on the competency tree.
*
* @module tool_lp/competencyactions
* @package tool_lp
* @copyright 2015 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery',
'core/url',
'core/templates',
'core/notification',
'core/str',
'core/ajax',
'tool_lp/dragdrop-reorder',
'tool_lp/tree',
'tool_lp/dialogue',
'tool_lp/menubar',
'tool_lp/competencypicker',
'tool_lp/competency_outcomes',
'tool_lp/competencyruleconfig'],
function($, url, templates, notification, str, ajax, dragdrop, Ariatree, Dialogue, menubar, Picker, Outcomes, RuleConfig) {
// Private variables and functions.
/** @var {Object} treeModel - This is an object representing the nodes in the tree. */
var treeModel = null;
/** @var {Node} moveSource - The start of a drag operation */
var moveSource = null;
/** @var {Node} moveTarget - The end of a drag operation */
var moveTarget = null;
/** @var {Number} pageContextId The page context ID. */
var pageContextId;
/** @type {Object} Picker instance. */
var pickerInstance;
/** @type {Object} Rule config instance. */
var ruleConfigInstance;
/** @type {Object} The competency we're picking a relation to. */
var relatedTarget;
/** @type {Object} Taxonomy constants indexed per level. */
var taxonomiesConstants;
/** @type {Array} The rules modules. Values are object containing type, namd and amd. */
var rulesModules;
/** @type {Number} the selected competency ID. */
var selectedCompetencyId = null;
/**
* Respond to choosing the "Add" menu item for the selected node in the tree.
* @method addHandler
*/
var addHandler = function() {
var parent = $('[data-region="competencyactions"]').data('competency');
var params = {
competencyframeworkid : treeModel.getCompetencyFrameworkId(),
pagecontextid: pageContextId
};
if (parent !== null) {
// We are adding at a sub node.
params.parentid = parent.id;
}
var relocate = function() {
var queryparams = $.param(params);
window.location = url.relativeUrl('/admin/tool/lp/editcompetency.php?' + queryparams);
};
if (parent !== null && treeModel.hasRule(parent.id)) {
str.get_strings([
{ key: 'confirm', component: 'moodle' },
{ key: 'addingcompetencywillresetparentrule', component: 'tool_lp', param: parent.shortname },
{ key: 'yes', component: 'core' },
{ key: 'no', component: 'core' }
]).done(function (strings) {
notification.confirm(
strings[0],
strings[1],
strings[2],
strings[3],
relocate
);
}).fail(notification.exception);
} else {
relocate();
}
};
/**
* A source and destination has been chosen - so time to complete a move.
* @method doMove
*/
var doMove = function() {
var frameworkid = $('[data-region="filtercompetencies"]').data('frameworkid');
var requests = ajax.call([{
methodname: 'core_competency_set_parent_competency',
args: { competencyid: moveSource, parentid: moveTarget }
}, {
methodname: 'tool_lp_data_for_competencies_manage_page',
args: { competencyframeworkid: frameworkid,
search: $('[data-region="filtercompetencies"] input').val() }
}]);
requests[1].done(reloadPage).fail(notification.exception);
};
/**
* Confirms a competency move.
*
* @method confirmMove
*/
var confirmMove = function() {
moveTarget = typeof moveTarget === "undefined" ? 0 : moveTarget;
if (moveTarget == moveSource) {
// No move to do.
return;
}
var targetComp = treeModel.getCompetency(moveTarget) || {},
sourceComp = treeModel.getCompetency(moveSource) || {},
confirmMessage = 'movecompetencywillresetrules',
showConfirm = false;
// We shouldn't be moving the competency to the same parent.
if (sourceComp.parentid == moveTarget) {
return;
}
// If we are moving to a child of self.
if (targetComp.path && targetComp.path.indexOf('/' + sourceComp.id + '/') >= 0) {
confirmMessage = 'movecompetencytochildofselfwillresetrules';
// Show a confirmation if self has rules, as they'll disappear.
showConfirm = showConfirm || treeModel.hasRule(sourceComp.id);
}
// Show a confirmation if the current parent, or the destination have rules.
showConfirm = showConfirm || (treeModel.hasRule(targetComp.id) || treeModel.hasRule(sourceComp.parentid));
// Show confirm, and/or do the things.
if (showConfirm) {
str.get_strings([
{ key: 'confirm', component: 'moodle' },
{ key: confirmMessage, component: 'tool_lp' },
{ key: 'yes', component: 'moodle' },
{ key: 'no', component: 'moodle' }
]).done(function (strings) {
notification.confirm(
strings[0], // Confirm.
strings[1], // Delete competency X?
strings[2], // Delete.
strings[3], // Cancel.
doMove
);
}).fail(notification.exception);
} else {
doMove();
}
};
/**
* A move competency popup was opened - initialise the aria tree in it.
* @method initMovePopup
* @param {dialogue} popup The tool_lp/dialogue that was created.
*/
var initMovePopup = function(popup) {
var body = $(popup.getContent());
var treeRoot = body.find('[data-enhance=movetree]');
var tree = new Ariatree(treeRoot, false);
tree.on('selectionchanged', function(evt, params) {
var target = params.selected;
moveTarget = $(target).data('id');
});
treeRoot.show();
body.on('click', '[data-action="move"]', function() { popup.close(); confirmMove(); });
body.on('click', '[data-action="cancel"]', function() { popup.close(); });
};
/**
* Turn a flat list of competencies into a tree structure (recursive).
* @method addCompetencyChildren
* @param {Object} parent The current parent node in the tree
* @param {Object[]} competencies The flat list of competencies
*/
var addCompetencyChildren = function(parent, competencies) {
var i;
for (i = 0; i < competencies.length; i++) {
if (competencies[i].parentid == parent.id) {
parent.haschildren = true;
competencies[i].children = [];
competencies[i].haschildren = false;
parent.children[parent.children.length] = competencies[i];
addCompetencyChildren(competencies[i], competencies);
}
}
};
/**
* A node was chosen and "Move" was selected from the menu. Open a popup to select the target.
* @method moveHandler
*/
var moveHandler = function(e) {
e.preventDefault();
var competency = $('[data-region="competencyactions"]').data('competency');
// Remember what we are moving.
moveSource = competency.id;
// Load data for the template.
var requests = ajax.call([
{
methodname: 'core_competency_search_competencies',
args: {
competencyframeworkid: competency.competencyframeworkid,
searchtext: ''
}
},{
methodname: 'core_competency_read_competency_framework',
args: {
id: competency.competencyframeworkid
}
}
]);
// When all data has arrived, continue.
$.when.apply(null, requests).done(function(competencies, framework) {
// Expand the list of competencies into a tree.
var i, competenciestree = [];
for (i = 0; i < competencies.length; i++) {
var onecompetency = competencies[i];
if (onecompetency.parentid == "0") {
onecompetency.children = [];
onecompetency.haschildren = 0;
competenciestree[competenciestree.length] = onecompetency;
addCompetencyChildren(onecompetency, competencies);
}
}
str.get_strings([
{ key: 'movecompetency', component: 'tool_lp', param: competency.shortname },
{ key: 'move', component: 'tool_lp' },
{ key: 'cancel', component: 'moodle' }
]).done(function (strings) {
var context = {
framework: framework,
competencies: competenciestree
};
templates.render('tool_lp/competencies_move_tree', context)
.done(function(tree) {
new Dialogue(
strings[0], // Move competency x.
tree, // The move tree.
initMovePopup
);
}).fail(notification.exception);
}).fail(notification.exception);
}).fail(notification.exception);
};
/**
* Edit the selected competency.
* @method editHandler
*/
var editHandler = function() {
var competency = $('[data-region="competencyactions"]').data('competency');
var params = {
competencyframeworkid : treeModel.getCompetencyFrameworkId(),
id : competency.id,
parentid: competency.parentid,
pagecontextid: pageContextId
};
var queryparams = $.param(params);
window.location = url.relativeUrl('/admin/tool/lp/editcompetency.php?' + queryparams);
};
/**
* Re-render the page with the latest data.
* @method reloadPage
*/
var reloadPage = function(context) {
templates.render('tool_lp/manage_competencies_page', context)
.done(function(newhtml, newjs) {
$('[data-region="managecompetencies"]').replaceWith(newhtml);
templates.runTemplateJS(newjs);
})
.fail(notification.exception);
};
/**
* Perform a search and render the page with the new search results.
* @method updateSearchHandler
*/
var updateSearchHandler = function(e) {
e.preventDefault();
var frameworkid = $('[data-region="filtercompetencies"]').data('frameworkid');
var requests = ajax.call([{
methodname: 'tool_lp_data_for_competencies_manage_page',
args: { competencyframeworkid: frameworkid,
search: $('[data-region="filtercompetencies"] input').val() }
}]);
requests[0].done(reloadPage).fail(notification.exception);
};
/**
* Move a competency "up". This only affects the sort order within the same branch of the tree.
* @method moveUpHandler
*/
var moveUpHandler = function() {
// We are chaining ajax requests here.
var competency = $('[data-region="competencyactions"]').data('competency');
var requests = ajax.call([{
methodname: 'core_competency_move_up_competency',
args: { id: competency.id }
}, {
methodname: 'tool_lp_data_for_competencies_manage_page',
args: { competencyframeworkid: competency.competencyframeworkid,
search: $('[data-region="filtercompetencies"] input').val() }
}]);
requests[1].done(reloadPage).fail(notification.exception);
};
/**
* Move a competency "down". This only affects the sort order within the same branch of the tree.
* @method moveDownHandler
*/
var moveDownHandler = function() {
// We are chaining ajax requests here.
var competency = $('[data-region="competencyactions"]').data('competency');
var requests = ajax.call([{
methodname: 'core_competency_move_down_competency',
args: { id: competency.id }
}, {
methodname: 'tool_lp_data_for_competencies_manage_page',
args: { competencyframeworkid: competency.competencyframeworkid,
search: $('[data-region="filtercompetencies"] input').val() }
}]);
requests[1].done(reloadPage).fail(notification.exception);
};
/**
* Open a dialogue to show all the courses using the selected competency.
* @method seeCoursesHandler
*/
var seeCoursesHandler = function() {
var competency = $('[data-region="competencyactions"]').data('competency');
var requests = ajax.call([{
methodname: 'tool_lp_list_courses_using_competency',
args: { id: competency.id }
}]);
requests[0].done(function(courses) {
var context = {
courses: courses
};
templates.render('tool_lp/linked_courses_summary', context).done(function(html) {
str.get_string('linkedcourses', 'tool_lp').done(function (linkedcourses) {
new Dialogue(
linkedcourses, // Title.
html, // The linked courses.
initMovePopup
);
}).fail(notification.exception);
}).fail(notification.exception);
}).fail(notification.exception);
};
/**
* Open a competencies popup to relate competencies.
*
* @method relateCompetenciesHandler
*/
var relateCompetenciesHandler = function() {
relatedTarget = $('[data-region="competencyactions"]').data('competency');
if (!pickerInstance) {
pickerInstance = new Picker(pageContextId, relatedTarget.competencyframeworkid);
pickerInstance.on('save', function(e, data) {
var compIds = data.competencyIds;
var calls = [];
$.each(compIds, function(index, value) {
calls.push({
methodname: 'core_competency_add_related_competency',
args: { competencyid: value, relatedcompetencyid: relatedTarget.id }
});
});
calls.push( {
methodname: 'tool_lp_data_for_related_competencies_section',
args: { competencyid: relatedTarget.id }
});
var promises = ajax.call(calls);
promises[calls.length - 1].then(function(context) {
return templates.render('tool_lp/related_competencies', context).done(function(html, js) {
$('[data-region="relatedcompetencies"]').replaceWith(html);
templates.runTemplateJS(js);
updatedRelatedCompetencies();
});
}, notification.exception);
});
}
pickerInstance.setDisallowedCompetencyIDs([relatedTarget.id]);
pickerInstance.display();
};
var ruleConfigHandler = function(e) {
e.preventDefault();
relatedTarget = $('[data-region="competencyactions"]').data('competency');
ruleConfigInstance.setTargetCompetencyId(relatedTarget.id);
ruleConfigInstance.display();
};
var ruleConfigSaveHandler = function(e, config) {
var update = {
id: relatedTarget.id,
shortname: relatedTarget.shortname,
idnumber: relatedTarget.idnumber,
description: relatedTarget.description,
descriptionformat: relatedTarget.descriptionformat,
ruletype: config.ruletype,
ruleoutcome: config.ruleoutcome,
ruleconfig: config.ruleconfig
};
var promise = ajax.call([{
methodname: 'core_competency_update_competency',
args: { competency: update }
}]);
promise[0].then(function(result) {
if (result) {
relatedTarget.ruletype = config.ruletype;
relatedTarget.ruleoutcome = config.ruleoutcome;
relatedTarget.ruleconfig = config.ruleconfig;
renderCompetencySummary(relatedTarget);
}
}, notification.exception);
};
/**
* Delete a competency.
* @method doDelete
*/
var doDelete = function() {
// We are chaining ajax requests here.
var competency = $('[data-region="competencyactions"]').data('competency');
var requests = ajax.call([{
methodname: 'core_competency_delete_competency',
args: { id: competency.id }
}, {
methodname: 'tool_lp_data_for_competencies_manage_page',
args: { competencyframeworkid: competency.competencyframeworkid,
search: $('[data-region="filtercompetencies"] input').val() }
}]);
requests[0].done(function(success) {
if (success === false) {
str.get_strings([
{ key: 'competencycannotbedeleted', component: 'tool_lp', param: competency.shortname },
{ key: 'cancel', component: 'moodle' }
]).done(function (strings) {
notification.alert(
null,
strings[0]
);
}).fail(notification.exception);
}
}).fail(notification.exception);
requests[1].done(reloadPage).fail(notification.exception);
};
/**
* Show a confirm dialogue before deleting a competency.
* @method deleteCompetencyHandler
*/
var deleteCompetencyHandler = function() {
var competency = $('[data-region="competencyactions"]').data('competency'),
confirmMessage = 'deletecompetency';
if (treeModel.hasRule(competency.parentid)) {
confirmMessage = 'deletecompetencyparenthasrule';
}
str.get_strings([
{ key: 'confirm', component: 'moodle' },
{ key: confirmMessage, component: 'tool_lp', param: competency.shortname },
{ key: 'delete', component: 'moodle' },
{ key: 'cancel', component: 'moodle' }
]).done(function (strings) {
notification.confirm(
strings[0], // Confirm.
strings[1], // Delete competency X?
strings[2], // Delete.
strings[3], // Cancel.
doDelete
);
}).fail(notification.exception);
};
/**
* HTML5 implementation of drag/drop (there is an accesible alternative in the menus).
* @method dragStart
*/
var dragStart = function(e) {
e.originalEvent.dataTransfer.setData('text', $(e.target).parent().data('id'));
};
/**
* HTML5 implementation of drag/drop (there is an accesible alternative in the menus).
* @method allowDrop
*/
var allowDrop = function(e) {
e.originalEvent.dataTransfer.dropEffect = 'move';
e.preventDefault();
};
/**
* HTML5 implementation of drag/drop (there is an accesible alternative in the menus).
* @method dragEnter
*/
var dragEnter = function(e) {
e.preventDefault();
$(this).addClass('currentdragtarget');
};
/**
* HTML5 implementation of drag/drop (there is an accesible alternative in the menus).
* @method dragLeave
*/
var dragLeave = function(e) {
e.preventDefault();
$(this).removeClass('currentdragtarget');
};
/**
* HTML5 implementation of drag/drop (there is an accesible alternative in the menus).
* @method dropOver
*/
var dropOver = function(e) {
e.preventDefault();
moveSource = e.originalEvent.dataTransfer.getData('text');
moveTarget = $(e.target).parent().data('id');
$(this).removeClass('currentdragtarget');
confirmMove();
};
/**
* Deletes a related competency without confirmation.
*
* @param {Event} e The event that triggered the action.
* @method deleteRelatedHandler
*/
var deleteRelatedHandler = function(e) {
e.preventDefault();
var relatedid = this.id.substr(11);
var competency = $('[data-region="competencyactions"]').data('competency');
var removeRelated = ajax.call([
{ methodname: 'core_competency_remove_related_competency',
args: { relatedcompetencyid: relatedid, competencyid: competency.id } },
{ methodname: 'tool_lp_data_for_related_competencies_section',
args: { competencyid: competency.id } }
]);
removeRelated[1].done(function(context) {
templates.render('tool_lp/related_competencies', context).done(function(html) {
$('[data-region="relatedcompetencies"]').replaceWith(html);
updatedRelatedCompetencies();
}.bind(this)).fail(notification.exception);
}.bind(this)).fail(notification.exception);
};
/**
* Updates the competencies list (with relations) and add listeners.
*
* @method updatedRelatedCompetencies
*/
var updatedRelatedCompetencies = function() {
// Listeners to newly loaded related competencies.
$('[data-action="deleterelation"]').on('click', deleteRelatedHandler);
};
/**
* Log the competency viewed event.
*
* @param {Object} The competency.
* @method triggerCompetencyViewedEvent
*/
var triggerCompetencyViewedEvent = function(competency) {
if (competency.id !== selectedCompetencyId) {
// Set the selected competency id.
selectedCompetencyId = competency.id;
ajax.call([{
methodname: 'core_competency_competency_viewed',
args: { id: competency.id }
}]);
}
};
/**
* Return if the level has a sub level.
*
* @param {Number} level The level.
* @return {Boolean}
* @function hasSubLevel
*/
var hasSubLevel = function(level) {
return typeof taxonomiesConstants[level + 1] !== 'undefined';
};
/**
* Return the taxonomy constant for a level.
*
* @param {Number} level The level.
* @return {String}
* @function getTaxonomyAtLevel
*/
var getTaxonomyAtLevel = function(level) {
var constant = taxonomiesConstants[level];
if (!constant) {
constant = 'competency';
}
return constant;
};
/**
* Render the competency summary.
*
* @param {Object} competency The competency.
*/
var renderCompetencySummary = function(competency) {
var promise = $.Deferred().resolve().promise(),
context = {};
context.competency = competency;
context.showdeleterelatedaction = true;
context.showrelatedcompetencies = true;
context.showrule = false;
if (competency.ruleoutcome != Outcomes.NONE) {
// Get the outcome and rule name.
promise = Outcomes.getString(competency.ruleoutcome).then(function(str) {
var name;
$.each(rulesModules, function(index, modInfo) {
if (modInfo.type == competency.ruletype) {
name = modInfo.name;
}
});
return [str, name];
});
}
promise.then(function(strs) {
if (typeof strs !== 'undefined') {
context.showrule = true;
context.rule = {
outcome: strs[0],
type: strs[1]
};
}
}).then(function() {
return templates.render('tool_lp/competency_summary', context).then(function(html) {
$('[data-region="competencyinfo"]').html(html);
$('[data-action="deleterelation"]').on('click', deleteRelatedHandler);
});
}).then(function() {
return templates.render('tool_lp/loading', {});
}).then(function(html, js) {
templates.replaceNodeContents('[data-region="relatedcompetencies"]', html, js);
}).done(function() {
ajax.call([{
methodname: 'tool_lp_data_for_related_competencies_section',
args: { competencyid: competency.id },
done: function(context) {
return templates.render('tool_lp/related_competencies', context).done(function(html, js) {
$('[data-region="relatedcompetencies"]').replaceWith(html);
templates.runTemplateJS(js);
updatedRelatedCompetencies();
});
}
}]);
}).fail(notification.exception);
};
/**
* Return the string "Add <taxonomy>".
*
* @param {Number} level The level.
* @return {String}
* @function strAddTaxonomy
*/
var strAddTaxonomy = function(level) {
return str.get_string('taxonomy_add_' + getTaxonomyAtLevel(level), 'tool_lp');
};
/**
* Return the string "Selected <taxonomy>".
*
* @param {Number} level The level.
* @return {String}
* @function strSelectedTaxonomy
*/
var strSelectedTaxonomy = function(level) {
return str.get_string('taxonomy_selected_' + getTaxonomyAtLevel(level), 'tool_lp');
};
/**
* Handler when a node in the aria tree is selected.
* @method selectionChanged
* @param {Event} evt The event that triggered the selection change.
* @param {Object} params The parameters for the event. Contains a list of selected nodes.
*/
var selectionChanged = function(evt, params) {
var node = params.selected,
id = $(node).data('id'),
btn = $('[data-region="competencyactions"] [data-action="add"]'),
actionMenu = $('[data-region="competencyactionsmenu"]'),
selectedTitle = $('[data-region="selected-competency"]'),
level = 0,
sublevel = 1;
menubar.closeAll();
if (typeof id === "undefined") {
// Assume this is the root of the tree.
// Here we are only getting the text from the top of the tree, to do it we clone the tree,
// remove all children and then call text on the result.
$('[data-region="competencyinfo"]').html(node.clone().children().remove().end().text());
$('[data-region="competencyactions"]').data('competency', null);
actionMenu.hide();
} else {
var competency = treeModel.getCompetency(id);
level = treeModel.getCompetencyLevel(id);
if (!hasSubLevel(level)) {
sublevel = false;
} else {
sublevel = level + 1;
}
actionMenu.show();
$('[data-region="competencyactions"]').data('competency', competency);
renderCompetencySummary(competency);
// Log Competency viewed event.
triggerCompetencyViewedEvent(competency);
}
strSelectedTaxonomy(level).then(function(str) {
selectedTitle.text(str);
});
if (!sublevel) {
btn.hide();
} else {
strAddTaxonomy(sublevel).then(function(str) {
btn.show()
.find('[data-region="term"]')
.text(str);
});
}
// We handled this event so consume it.
evt.preventDefault();
return false;
};
/**
* Return the string "Selected <taxonomy>".
*
* @function parseTaxonomies
* @param {String} Comma separated list of taxonomies.
* @return {Array} of level => taxonomystr
*/
var parseTaxonomies = function(taxonomiesstr) {
var all = taxonomiesstr.split(',');
all.unshift("");
delete all[0];
// Note we don't need to fill holes, because other functions check for empty anyway.
return all;
};
return {
/**
* Initialise this page (attach event handlers etc).
*
* @method init
* @param {Object} model The tree model provides some useful functions for loading and searching competencies.
* @param {Number} pagectxid The page context ID.
* @param {Object} taxonomies Constants indexed by level.
* @param {Object} rulesMods The modules of the rules.
*/
init: function(model, pagectxid, taxonomies, rulesMods) {
treeModel = model;
pageContextId = pagectxid;
taxonomiesConstants = parseTaxonomies(taxonomies);
rulesModules = rulesMods;
$('[data-region="competencyactions"] [data-action="add"]').on('click', addHandler);
menubar.enhance('.competencyactionsmenu', {
'[data-action="edit"]': editHandler,
'[data-action="delete"]': deleteCompetencyHandler,
'[data-action="move"]': moveHandler,
'[data-action="moveup"]': moveUpHandler,
'[data-action="movedown"]': moveDownHandler,
'[data-action="linkedcourses"]': seeCoursesHandler,
'[data-action="relatedcompetencies"]': relateCompetenciesHandler.bind(this),
'[data-action="competencyrules"]': ruleConfigHandler.bind(this)
});
$('[data-region="competencyactionsmenu"]').hide();
$('[data-region="competencyactions"] [data-action="add"]').hide();
$('[data-region="filtercompetencies"]').on('submit', updateSearchHandler);
// Simple html5 drag drop because we already added an accessible alternative.
var top = $('[data-region="managecompetencies"] [data-enhance="tree"]');
top.on('dragstart', 'li>span', dragStart)
.on('dragover', 'li>span', allowDrop)
.on('dragenter', 'li>span', dragEnter)
.on('dragleave', 'li>span', dragLeave)
.on('drop', 'li>span', dropOver);
model.on('selectionchanged', selectionChanged);
// Prepare the configuration tool.
ruleConfigInstance = new RuleConfig(treeModel, rulesModules);
ruleConfigInstance.on('save', ruleConfigSaveHandler.bind(this));
}
};
});
+175
View File
@@ -0,0 +1,175 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Display Competency in dialogue box.
*
* @module tool_lp/Competencydialogue
* @package tool_lp
* @copyright 2015 Issam Taboubi <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery',
'core/notification',
'core/ajax',
'core/templates',
'core/str',
'tool_lp/dialogue'],
function($, notification, ajax, templates, str, Dialogue) {
/**
* Constructor for CompetencyDialogue.
*
* @param {Object} options
*
*/
var Competencydialogue = function(options) {
this.options = {
includerelated: false,
includecourses: false
};
$.extend(this.options, options);
};
/**
* Log the competency viewed event.
*
* @param {Number} The competency ID.
* @method triggerCompetencyViewedEvent
*/
Competencydialogue.prototype.triggerCompetencyViewedEvent = function(competencyId) {
ajax.call([{
methodname: 'core_competency_competency_viewed',
args: { id: competencyId }
}]);
};
/**
* Callback on dialogue display, it apply enhance on competencies dialogue.
*
* @param {Dialogue} dialogue
* @method enhanceDialogue
*/
Competencydialogue.prototype.enhanceDialogue = function(dialogue) {
//Apply watch on the related competencies and competencies in the dialogue.
var comprelated = new Competencydialogue({includerelated : false});
comprelated.watch(dialogue.getContent());
};
/**
* Display a dialogue box by competencyid.
*
* @param {Number} the competency id
* @param {Object} Options for tool_lp_data_for_competency_summary service
* @param {Object} dataSource data to be used to display dialogue box
* @method showDialogue
*/
Competencydialogue.prototype.showDialogue = function(competencyid) {
var datapromise = this.getCompetencyDataPromise(competencyid);
var localthis = this;
datapromise.done(function(data) {
// Inner Html in the dialogue content.
templates.render('tool_lp/competency_summary', data)
.done(function(html) {
// Log competency viewed event.
localthis.triggerCompetencyViewedEvent(competencyid);
// Show the dialogue.
new Dialogue(
data.competency.shortname,
html,
localthis.enhanceDialogue
);
}).fail(notification.exception);
}).fail(notification.exception);
};
/**
* Display a dialogue box from data.
*
* @param {Object} dataSource data to be used to display dialogue box
* @method showDialogueFromData
*/
Competencydialogue.prototype.showDialogueFromData = function(dataSource) {
var localthis = this;
// Inner Html in the dialogue content.
templates.render('tool_lp/competency_summary', dataSource)
.done(function(html) {
// Log competency viewed event.
localthis.triggerCompetencyViewedEvent(dataSource.id);
// Show the dialogue.
new Dialogue(
dataSource.shortname,
html,
localthis.enhanceDialogue
);
}).fail(notification.exception);
};
/**
* The action on the click event.
*
* @param {Event} event click
* @method clickEventHandler
*/
Competencydialogue.prototype.clickEventHandler = function(e) {
var compdialogue = e.data.compdialogue;
var competencyid = $(e.currentTarget).data('id');
// Show the dialogue box.
compdialogue.showDialogue(competencyid);
e.preventDefault();
};
/**
* Get a promise on data competency.
*
* @param {Number} competencyid
* @return {Promise} return promise on data request
* @method getCompetencyDataPromise
*/
Competencydialogue.prototype.getCompetencyDataPromise = function(competencyid) {
var requests = ajax.call([
{ methodname: 'tool_lp_data_for_competency_summary',
args: { competencyid: competencyid,
includerelated: this.options.includerelated,
includecourses: this.options.includecourses
}
}
]);
return requests[0].then(function(context) {
return context;
}).fail(notification.exception);
};
/**
* Watch the competencies links in container.
*
* @param {String} container selector of node containing competencies links
* @method watch
*/
Competencydialogue.prototype.watch = function(containerSelector) {
$(containerSelector).off('click', '[data-action="competency-dialogue"]', this.clickEventHandler);
$(containerSelector).on('click', '[data-action="competency-dialogue"]', { compdialogue: this }, this.clickEventHandler);
};
return Competencydialogue;
});
+460
View File
@@ -0,0 +1,460 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Competency picker.
*
* To handle 'save' events use: picker.on('save')
* This will receive a object with either a single 'competencyId', or an array in 'competencyIds'
* depending on the value of multiSelect.
*
* @package tool_lp
* @copyright 2015 Frédéric Massart - FMCorz.net
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery',
'core/notification',
'core/ajax',
'core/templates',
'tool_lp/dialogue',
'core/str',
'tool_lp/tree'],
function($, Notification, Ajax, Templates, Dialogue, Str, Tree) {
/**
* Competency picker class.
* @param {Number} pageContextId The page context ID.
* @param {Number|false} singleFramework The ID of the framework when limited to one.
* @param {String} pageContextIncludes One of 'children', 'parents', 'self'.
* @param {Boolean} multiSelect Support multi-select in the tree.
*/
var Picker = function(pageContextId, singleFramework, pageContextIncludes, multiSelect) {
var self = this;
self._eventNode = $('<div></div>');
self._frameworks = [];
self._reset();
self._pageContextId = pageContextId;
self._pageContextIncludes = pageContextIncludes || 'children';
self._multiSelect = (typeof multiSelect === 'undefined' || multiSelect === true);
if (singleFramework) {
self._frameworkId = singleFramework;
self._singleFramework = true;
}
};
/** @type {Array} The competencies fetched. */
Picker.prototype._competencies = null;
/** @type {Array} The competencies that cannot be picked. */
Picker.prototype._disallowedCompetencyIDs = null;
/** @type {Node} The node we attach the events to. */
Picker.prototype._eventNode = null;
/** @type {Array} The list of frameworks fetched. */
Picker.prototype._frameworks = null;
/** @type {Number} The current framework ID. */
Picker.prototype._frameworkId = null;
/** @type {Number} The page context ID. */
Picker.prototype._pageContextId = null;
/** @type {Number} Relevant contexts inclusion. */
Picker.prototype._pageContextIncludes = null;
/** @type {Dialogue} The reference to the dialogue. */
Picker.prototype._popup = null;
/** @type {String} The string we filter the competencies with. */
Picker.prototype._searchText = '';
/** @type {Object} The competency that was selected. */
Picker.prototype._selectedCompetencies = null;
/** @type {Boolean} Whether we can browse frameworks or not. */
Picker.prototype._singleFramework = false;
/** @type {Boolean} Do we allow multi select? */
Picker.prototype._multiSelect = true;
/** @type {Boolean} Do we allow to display hidden framework? */
Picker.prototype._onlyVisible = true;
/**
* Hook to executed after the view is rendered.
*
* @method _afterRender
*/
Picker.prototype._afterRender = function() {
var self = this;
// Initialise the tree.
var tree = new Tree(self._find('[data-enhance=linktree]'), self._multiSelect);
// To prevent jiggling we only show the tree after it is enhanced.
self._find('[data-enhance=linktree]').show();
tree.on('selectionchanged', function(evt, params) {
var selected = params.selected;
evt.preventDefault();
var validIds = [];
$.each(selected, function(index, item) {
var compId = $(item).data('id'),
valid = true;
if (typeof compId === 'undefined') {
// Do not allow picking nodes with no id.
valid = false;
} else {
$.each(self._disallowedCompetencyIDs, function(i, id) {
if (id == compId) {
valid = false;
}
});
}
if (valid) {
validIds.push(compId);
}
}.bind(self));
self._selectedCompetencies = validIds;
// TODO Implement disabling of nodes in the tree module somehow.
if (!self._selectedCompetencies.length) {
self._find('[data-region="competencylinktree"] [data-action="add"]').attr('disabled', 'disabled');
} else {
self._find('[data-region="competencylinktree"] [data-action="add"]').removeAttr('disabled');
}
}.bind(self));
// Add listener for framework change.
if (!self._singleFramework) {
self._find('[data-action="chooseframework"]').change(function(e) {
self._frameworkId = $(e.target).val();
self._loadCompetencies().then(self._refresh.bind(self));
}.bind(self));
}
// Add listener for search.
self._find('[data-region="filtercompetencies"] button').click(function(e) {
e.preventDefault();
$(e.target).attr('disabled', 'disabled');
self._searchText = self._find('[data-region="filtercompetencies"] input').val() || '';
return self._refresh().always(function() {
$(e.target).removeAttr('disabled');
});
}.bind(self));
// Add listener for cancel.
self._find('[data-region="competencylinktree"] [data-action="cancel"]').click(function(e) {
e.preventDefault();
self.close();
}.bind(self));
// Add listener for add.
self._find('[data-region="competencylinktree"] [data-action="add"]').click(function(e) {
e.preventDefault();
if (!self._selectedCompetencies.length) {
return;
}
if (self._multiSelect) {
self._trigger('save', { competencyIds: self._selectedCompetencies });
} else {
// We checked above that the array has at least one value.
self._trigger('save', { competencyId: self._selectedCompetencies[0] });
}
self.close();
}.bind(self));
// The list of selected competencies will be modified while looping (because of the listeners above).
var currentItems = self._selectedCompetencies.slice(0);
$.each(currentItems, function(index, id) {
var node = self._find('[data-id=' + id + ']');
if (node.length) {
tree.toggleItem(node);
tree.updateFocus(node);
}
}.bind(self));
};
/**
* Close the dialogue.
*
* @method close
*/
Picker.prototype.close = function() {
var self = this;
self._popup.close();
self._reset();
};
/**
* Opens the picker.
*
* @method display
* @return {Promise}
*/
Picker.prototype.display = function() {
var self = this;
return self._render().then(function(html) {
return Str.get_string('competencypicker', 'tool_lp').then(function(title) {
self._popup = new Dialogue(
title,
html,
self._afterRender.bind(self)
);
}.bind(self));
}.bind(self)).fail(Notification.exception);
};
/**
* Fetch the competencies.
*
* @param {Number} frameworkId The frameworkId.
* @param {String} searchText Limit the competencies to those matching the text.
* @method _fetchCompetencies
* @return {Promise}
*/
Picker.prototype._fetchCompetencies = function(frameworkId, searchText) {
var self = this;
return Ajax.call([
{ methodname: 'core_competency_search_competencies', args: {
searchtext: searchText,
competencyframeworkid: frameworkId
}}
])[0].done(function(competencies) {
function addCompetencyChildren(parent, competencies) {
for (var i = 0; i < competencies.length; i++) {
if (competencies[i].parentid == parent.id) {
parent.haschildren = true;
competencies[i].children = [];
competencies[i].haschildren = false;
parent.children[parent.children.length] = competencies[i];
addCompetencyChildren(competencies[i], competencies);
}
}
}
// Expand the list of competencies into a tree.
var i, tree = [], comp;
for (i = 0; i < competencies.length; i++) {
comp = competencies[i];
if (comp.parentid == "0") { // Loose check for now, because WS returns a string.
comp.children = [];
comp.haschildren = 0;
tree[tree.length] = comp;
addCompetencyChildren(comp, competencies);
}
}
self._competencies = tree;
}.bind(self)).fail(Notification.exception);
};
/**
* Find a node in the dialogue.
*
* @param {String} selector
* @method _find
*/
Picker.prototype._find = function(selector) {
return $(this._popup.getContent()).find(selector);
};
/**
* Convenience method to get a framework object.
*
* @param {Number} fid The framework ID.
* @method _getFramework
*/
Picker.prototype._getFramework = function(fid) {
var frm;
$.each(this._frameworks, function(i, f) {
if (f.id == fid) {
frm = f;
return false;
}
});
return frm;
};
/**
* Load the competencies.
*
* @method _loadCompetencies
* @return {Promise}
*/
Picker.prototype._loadCompetencies = function() {
return this._fetchCompetencies(this._frameworkId, this._searchText);
};
/**
* Load the frameworks.
*
* @method _loadFrameworks
* @return {Promise}
*/
Picker.prototype._loadFrameworks = function() {
var promise,
self = this;
// Quit early because we already have the data.
if (self._frameworks.length > 0) {
return $.when();
}
if (self._singleFramework) {
promise = Ajax.call([
{ methodname: 'core_competency_read_competency_framework', args: {
id: this._frameworkId
}}
])[0].then(function(framework) {
return [framework];
});
} else {
promise = Ajax.call([
{ methodname: 'core_competency_list_competency_frameworks', args: {
sort: 'shortname',
context: { contextid: self._pageContextId },
includes: self._pageContextIncludes,
onlyvisible: self._onlyVisible
}}
])[0];
}
return promise.done(function(frameworks) {
self._frameworks = frameworks;
}).fail(Notification.exception);
};
/**
* Register an event listener.
*
* @param {String} type The event type.
* @param {Function} handler The event listener.
* @method on
*/
Picker.prototype.on = function(type, handler) {
this._eventNode.on(type, handler);
};
/**
* Hook to executed before render.
*
* @method _preRender
* @return {Promise}
*/
Picker.prototype._preRender = function() {
var self = this;
return self._loadFrameworks().then(function() {
if (!self._frameworkId && self._frameworks.length > 0) {
self._frameworkId = self._frameworks[0].id;
}
// We could not set a framework ID, that probably means there are no frameworks accessible.
if (!self._frameworkId) {
self._frameworks = [];
return $.when();
}
return self._loadCompetencies();
}.bind(self));
};
/**
* Refresh the view.
*
* @method _refresh
* @return {Promise}
*/
Picker.prototype._refresh = function() {
var self = this;
return self._render().then(function(html) {
self._find('[data-region="competencylinktree"]').replaceWith(html);
self._afterRender();
}.bind(self));
};
/**
* Render the dialogue.
*
* @method _render
* @return {Promise}
*/
Picker.prototype._render = function() {
var self = this;
return self._preRender().then(function() {
if (!self._singleFramework) {
$.each(self._frameworks, function(i, framework) {
if (framework.id == self._frameworkId) {
framework.selected = true;
} else {
framework.selected = false;
}
});
}
var context = {
competencies: self._competencies,
framework: self._getFramework(self._frameworkId),
frameworks: self._frameworks,
search: self._searchText,
singleFramework: self._singleFramework,
};
return Templates.render('tool_lp/competency_picker', context);
}.bind(self));
};
/**
* Reset the dialogue properties.
*
* This does not reset everything, just enough to reset the UI.
*
* @method _reset
*/
Picker.prototype._reset = function() {
this._competencies = [];
this._disallowedCompetencyIDs = [];
this._popup = null;
this._searchText = '';
this._selectedCompetencies = [];
};
/**
* Set what competencies cannot be picked.
*
* This needs to be set after reset/close.
*
* @params {Number[]} The IDs.
* @method _setDisallowedCompetencyIDs
*/
Picker.prototype.setDisallowedCompetencyIDs = function(ids) {
this._disallowedCompetencyIDs = ids;
};
/**
* Trigger an event.
*
* @param {String} type The type of event.
* @param {Object} The data to pass to the listeners.
* @method _reset
*/
Picker.prototype._trigger = function(type, data) {
this._eventNode.trigger(type, [data]);
};
return /** @alias module:tool_lp/competencypicker */ Picker;
});
@@ -0,0 +1,240 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Competency picker from user plans.
*
* To handle 'save' events use: picker.on('save').
*
* This will receive a object with either a single 'competencyId', or an array in 'competencyIds'
* depending on the value of multiSelect.
*
* @package tool_lp
* @copyright 2015 Frédéric Massart - FMCorz.net
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery',
'core/notification',
'core/ajax',
'core/templates',
'core/str',
'tool_lp/tree',
'tool_lp/competencypicker'
],
function($, Notification, Ajax, Templates, Str, Tree, PickerBase) {
/**
* Competency picker in plan class.
*
* @param {Number|false} singlePlan The ID of the plan when limited to one.
* @param {String} pageContextIncludes One of 'children', 'parents', 'self'.
* @param {Boolean} multiSelect Support multi-select in the tree.
*/
var Picker = function(userId, singlePlan, multiSelect) {
PickerBase.prototype.constructor.apply(this, [1, false, 'self', multiSelect]);
this._userId = userId;
this._plans = [];
if (singlePlan) {
this._planId = singlePlan;
this._singlePlan = true;
}
};
Picker.prototype = Object.create(PickerBase.prototype);
/** @type {Array} The list of plans fetched. */
Picker.prototype._plans = null;
/** @type {Number} The current plan ID. */
Picker.prototype._planId = null;
/** @type {Boolean} Whether we can browse plans or not. */
Picker.prototype._singlePlan = false;
/** @type {Number} The user the plans belongs to. */
Picker.prototype._userId = null;
/**
* Hook to executed after the view is rendered.
*
* @method _afterRender
*/
Picker.prototype._afterRender = function() {
var self = this;
PickerBase.prototype._afterRender.apply(self, arguments);
// Add listener for framework change.
if (!self._singlePlan) {
self._find('[data-action="chooseplan"]').change(function(e) {
self._planId = $(e.target).val();
self._loadCompetencies().then(self._refresh.bind(self));
}.bind(self));
}
};
/**
* Fetch the competencies.
*
* @param {Number} planId The planId.
* @param {String} searchText Limit the competencies to those matching the text.
* @method _fetchCompetencies
* @return {Promise}
*/
Picker.prototype._fetchCompetencies = function(planId, searchText) {
var self = this;
return Ajax.call([
{ methodname: 'core_competency_list_plan_competencies', args: {
id: planId
}}
])[0].done(function(competencies) {
// Expand the list of competencies into a fake tree.
var i, tree = [], comp;
for (i = 0; i < competencies.length; i++) {
comp = competencies[i].competency;
if (comp.shortname.toLowerCase().indexOf(searchText.toLowerCase()) < 0) {
continue;
}
comp.children = [];
comp.haschildren = 0;
tree.push(comp);
}
self._competencies = tree;
}).fail(Notification.exception);
};
/**
* Convenience method to get a plan object.
*
* @param {Number} id The plan ID.
* @return {Object|undefined} The plan.
* @method _getPlan
*/
Picker.prototype._getPlan = function(id) {
var plan;
$.each(this._plans, function(i, f) {
if (f.id == id) {
plan = f;
return false;
}
});
return plan;
};
/**
* Load the competencies.
*
* @method _loadCompetencies
* @return {Promise}
*/
Picker.prototype._loadCompetencies = function() {
return this._fetchCompetencies(this._planId, this._searchText);
};
/**
* Load the plans.
*
* @method _loadPlans
* @return {Promise}
*/
Picker.prototype._loadPlans = function() {
var promise,
self = this;
// Quit early because we already have the data.
if (self._plans.length > 0) {
return $.when();
}
if (self._singlePlan) {
promise = Ajax.call([
{ methodname: 'core_competency_read_plan', args: {
id: this._planId
}}
])[0].then(function(plan) {
return [plan];
});
} else {
promise = Ajax.call([
{ methodname: 'core_competency_list_user_plans', args: {
userid: self._userId
}}
])[0];
}
return promise.done(function(plans) {
self._plans = plans;
}).fail(Notification.exception);
};
/**
* Hook to executed before render.
*
* @method _preRender
* @return {Promise}
*/
Picker.prototype._preRender = function() {
var self = this;
return self._loadPlans().then(function() {
if (!self._planId && self._plans.length > 0) {
self._planId = self._plans[0].id;
}
// We could not set a framework ID, that probably means there are no frameworks accessible.
if (!self._planId) {
self._plans = [];
return $.when();
}
return self._loadCompetencies();
}.bind(self));
};
/**
* Render the dialogue.
*
* @method _render
* @return {Promise}
*/
Picker.prototype._render = function() {
var self = this;
return self._preRender().then(function() {
if (!self._singlePlan) {
$.each(self._plans, function(i, plan) {
if (plan.id == self._planId) {
plan.selected = true;
} else {
plan.selected = false;
}
});
}
var context = {
competencies: self._competencies,
plan: self._getPlan(self._planId),
plans: self._plans,
search: self._searchText,
singlePlan: self._singlePlan,
};
return Templates.render('tool_lp/competency_picker_user_plans', context);
}.bind(self));
};
return /** @alias module:tool_lp/competencypicker_user_plans */ Picker;
});
@@ -0,0 +1,548 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Competency rule config.
*
* @package tool_lp
* @copyright 2015 Frédéric Massart - FMCorz.net
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery',
'core/notification',
'core/templates',
'tool_lp/dialogue',
'tool_lp/competency_outcomes',
'core/str'],
function($, Notification, Templates, Dialogue, Outcomes, Str) {
/**
* Competency rule class.
*
* When implementing this you should attach a listener to the event 'save'
* on the instance. E.g.
*
* var config = new RuleConfig(tree, modules);
* config.on('save', function(e, config) { ... });
*
* @param {competencytree} tree The competency tree.
* @param {Array} rulesModules The modules containing the rules: [{ typeName: { amd: amdModule, name: ruleName }}].
*/
var RuleConfig = function(tree, rulesModules) {
this._eventNode = $('<div></div>');
this._tree = tree;
this._rulesModules = rulesModules;
this._setUp();
};
/** @type {Object} The current competency. */
RuleConfig.prototype._competency = null;
/** @type {Node} The node we attach the events to. */
RuleConfig.prototype._eventNode = null;
/** @type {Array} Outcomes options. */
RuleConfig.prototype._outcomesOption = null;
/** @type {Dialogue} The dialogue. */
RuleConfig.prototype._popup = null;
/** @type {Promise} Resolved when the module is ready. */
RuleConfig.prototype._ready = null;
/** @type {Array} The rules. */
RuleConfig.prototype._rules = null;
/** @type {Array} The rules modules. */
RuleConfig.prototype._rulesModules = null;
/** @type {competencytree} The competency tree. */
RuleConfig.prototype._tree = null;
/**
* After change.
*
* Triggered when a change occured.
*
* @return {Void}
* @method _afterChange
* @protected
*/
RuleConfig.prototype._afterChange = function() {
if (!this._isValid()) {
this._find('[data-action="save"]').prop('disabled', true);
} else {
this._find('[data-action="save"]').prop('disabled', false);
}
};
/**
* After change in rule's config.
*
* Triggered when a change occured in a specific rule config.
*
* @return {Void}
* @method _afterRuleConfigChange
* @protected
*/
RuleConfig.prototype._afterRuleConfigChange = function(e, rule) {
if (rule != this._getRule()) {
// This rule is not the current one any more, we can ignore.
return;
}
this._afterChange();
};
/**
* After render hook.
*
* @return {Promise}
* @method _afterRender
* @protected
*/
RuleConfig.prototype._afterRender = function() {
var self = this;
self._find('[name="outcome"]').on('change', function() {
self._switchedOutcome();
}).trigger('change');
self._find('[name="rule"]').on('change', function() {
self._switchedRule();
}).trigger('change');
self._find('[data-action="save"]').on('click', function() {
self._trigger('save', self._getConfig());
self.close();
});
self._find('[data-action="cancel"]').on('click', function() {
self.close();
});
};
/**
* Whether the current competency can be configured.
*
* @return {Boolean}
* @method canBeConfigured
*/
RuleConfig.prototype.canBeConfigured = function() {
var can = false;
$.each(this._rules, function(index, rule) {
if (rule.canConfig()) {
can = true;
return false;
}
});
return can;
};
/**
* Close the dialogue.
*
* @method close
*/
RuleConfig.prototype.close = function() {
this._popup.close();
this._popup = null;
};
/**
* Opens the picker.
*
* @param {Number} competencyId The competency ID of the competency to work on.
* @method display
* @return {Promise}
*/
RuleConfig.prototype.display = function() {
var self = this;
if (!self._competency) {
return;
}
return self._render().then(function(html) {
return Str.get_string('competencyrule', 'tool_lp').then(function(title) {
self._popup = new Dialogue(
title,
html,
self._afterRender.bind(self)
);
});
}).fail(Notification.exception);
};
/**
* Find a node in the dialogue.
*
* @param {String} selector
* @method _find
* @protected
*/
RuleConfig.prototype._find = function(selector) {
return $(this._popup.getContent()).find(selector);
};
/**
* Get the applicable outcome options.
*
* @return {Array}
* @method _getApplicableOutcomesOptions
* @protected
*/
RuleConfig.prototype._getApplicableOutcomesOptions = function() {
var self = this,
options = [];
$.each(self._outcomesOption, function(index, outcome) {
options.push({
code: outcome.code,
name: outcome.name,
selected: (outcome.code == self._competency.ruleoutcome) ? true : false,
});
});
return options;
};
/**
* Get the applicable rules options.
*
* @return {Array}
* @method _getApplicableRulesOptions
* @protected
*/
RuleConfig.prototype._getApplicableRulesOptions = function() {
var self = this,
options = [];
$.each(self._rules, function(index, rule) {
if (!rule.canConfig()) {
return;
}
options.push({
name: self._getRuleName(rule.getType()),
type: rule.getType(),
selected: (rule.getType() == self._competency.ruletype) ? true : false,
});
});
return options;
};
/**
* Get the full config for the competency.
*
* @return {Object} Contains rule, ruleoutcome and ruleconfig.
* @method _getConfig
* @protected
*/
RuleConfig.prototype._getConfig = function() {
var rule = this._getRule();
return {
ruletype: rule ? rule.getType() : null,
ruleconfig: rule ? rule.getConfig() : null,
ruleoutcome: this._getOutcome()
};
};
/**
* Get the selected outcome code.
*
* @return {String}
* @method _getOutcome
* @protected
*/
RuleConfig.prototype._getOutcome = function() {
return this._find('[name="outcome"]').val();
};
/**
* Get the selected rule.
*
* @return {null|Rule}
* @method _getRule
* @protected
*/
RuleConfig.prototype._getRule = function() {
var result,
type = this._find('[name="rule"]').val();
$.each(this._rules, function(index, rule) {
if (rule.getType() == type) {
result = rule;
return false;
}
});
return result;
};
/**
* Return the name of a rule.
*
* @param {String} type The type of a rule.
* @return {String}
* @method _getRuleName
* @protected
*/
RuleConfig.prototype._getRuleName = function(type) {
var self = this,
name;
$.each(self._rulesModules, function(index, modInfo) {
if (modInfo.type == type) {
name = modInfo.name;
return false;
}
});
return name;
};
/**
* Initialise the outcomes.
*
* @return {Promise}
* @method _initOutcomes
* @protected
*/
RuleConfig.prototype._initOutcomes = function() {
var self = this;
return Outcomes.getAll().then(function(outcomes) {
self._outcomesOption = outcomes;
});
};
/**
* Initialise the rules.
*
* @return {Promise}
* @method _initRules
* @protected
*/
RuleConfig.prototype._initRules = function() {
var self = this,
promises = [];
$.each(self._rules, function(index, rule) {
var promise = rule.init().then(function() {
rule.setTargetCompetency(self._competency);
rule.on('change', self._afterRuleConfigChange.bind(self));
}, function() {
// Upon failure remove the rule, and resolve the promise.
self._rules.splice(index, 1);
return $.when();
});
promises.push(promise);
});
return $.when.apply($.when, promises);
};
/**
* Whether or not the current config is valid.
*
* @return {Boolean}
* @method _isValid
* @protected
*/
RuleConfig.prototype._isValid = function() {
var outcome = this._getOutcome(),
rule = this._getRule();
if (outcome == Outcomes.NONE) {
return true;
} else if (!rule) {
return false;
}
return rule.isValid();
};
/**
* Register an event listener.
*
* @param {String} type The event type.
* @param {Function} handler The event listener.
* @method on
*/
RuleConfig.prototype.on = function(type, handler) {
this._eventNode.on(type, handler);
};
/**
* Hook to executed before render.
*
* @method _preRender
* @protected
* @return {Promise}
*/
RuleConfig.prototype._preRender = function() {
// We need to have all the information about the rule plugins first.
return this.ready();
};
/**
* Returns a promise that is resolved when the module is ready.
*
* @return {Promise}
* @method ready
* @protected
*/
RuleConfig.prototype.ready = function() {
return this._ready.promise();
};
/**
* Render the dialogue.
*
* @method _render
* @protected
* @return {Promise}
*/
RuleConfig.prototype._render = function() {
var self = this;
return this._preRender().then(function() {
var config;
if (!self.canBeConfigured()) {
config = false;
} else {
config = {};
config.outcomes = self._getApplicableOutcomesOptions();
config.rules = self._getApplicableRulesOptions();
}
var context = {
competencyshortname: self._competency.shortname,
config: config
};
return Templates.render('tool_lp/competency_rule_config', context);
});
};
/**
* Set the target competency.
*
* @param {Number} competencyId The target competency Id.
* @method setTargetCompetencyId
*/
RuleConfig.prototype.setTargetCompetencyId = function(competencyId) {
var self = this;
self._competency = self._tree.getCompetency(competencyId);
$.each(self._rules, function(index, rule) {
rule.setTargetCompetency(self._competency);
});
};
/**
* Set up the instance.
*
* @return {Void}
* @method _setUp
* @protected
*/
RuleConfig.prototype._setUp = function() {
var self = this,
promises = [],
modules = [];
self._ready = $.Deferred();
self._rules = [];
$.each(self._rulesModules, function(index, rule) {
modules.push(rule.amd);
});
// Load all the modules.
require(modules, function() {
$.each(arguments, function(index, Module) {
// Instantiate the rule and listen to it.
var rule = new Module(self._tree);
self._rules.push(rule);
});
// Load all the option values.
promises.push(self._initRules());
promises.push(self._initOutcomes());
// Ready when everything is done.
$.when.apply($.when, promises).always(function() {
self._ready.resolve();
});
});
};
/**
* Called when the user switches outcome.
*
* @return {Void}
* @method _switchedOutcome
* @protected
*/
RuleConfig.prototype._switchedOutcome = function() {
var self = this,
type = self._getOutcome();
if (type == Outcomes.NONE) {
// Reset to defaults.
self._find('[data-region="rule-type"]').hide()
.find('[name="rule"]').val(-1);
self._find('[data-region="rule-config"]').empty().hide();
self._afterChange();
return;
}
self._find('[data-region="rule-type"]').show();
self._find('[data-region="rule-config"]').show();
self._afterChange();
};
/**
* Called when the user switches rule.
*
* @return {Void}
* @method _switchedRule
* @protected
*/
RuleConfig.prototype._switchedRule = function() {
var self = this,
container = self._find('[data-region="rule-config"]'),
rule = self._getRule();
if (!rule) {
container.empty().hide();
self._afterChange();
return;
}
rule.injectTemplate(container).then(function() {
container.show();
}, function() {
container.empty().hide();
}).always(function() {
self._afterChange();
});
};
/**
* Trigger an event.
*
* @param {String} type The type of event.
* @param {Object} The data to pass to the listeners.
* @method _trigger
* @protected
*/
RuleConfig.prototype._trigger = function(type, data) {
this._eventNode.trigger(type, [data]);
};
return /** @alias module:tool_lp/competencyruleconfig */ RuleConfig;
});
+262
View File
@@ -0,0 +1,262 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Handle selection changes on the competency tree.
*
* @module tool_lp/competencyselect
* @package tool_lp
* @copyright 2015 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['core/ajax', 'core/notification', 'core/templates', 'tool_lp/tree', 'tool_lp/competency_outcomes', 'jquery'],
function(ajax, notification, templates, Ariatree, CompOutcomes, $) {
// Private variables and functions.
/** @var {Object[]} competencies - Cached list of competencies */
var competencies = {};
/** @var {Number} competencyFrameworkId - The current framework id */
var competencyFrameworkId = 0;
/** @var {String} competencyFrameworkShortName - The current framework short name */
var competencyFrameworkShortName = '';
/** @var {String} treeSelector - The selector for the root of the tree. */
var treeSelector = '';
/** @var {String} currentNodeId - The data-id of the current node in the tree. */
var currentNodeId = '';
/** @var {Boolean} competencyFramworkCanManage - Can manage the competencies framework */
var competencyFramworkCanManage = false;
/**
* Build a tree from the flat list of competencies.
* @param {Object} parent The parent competency.
* @param {Array} all The list of all competencies.
*/
var addChildren = function(parent, all) {
var i = 0;
var current = false;
parent.haschildren = false;
parent.children = [];
for (i = 0; i < all.length; i++) {
current = all[i];
if (current.parentid == parent.id) {
parent.haschildren = true;
parent.children.push(current);
addChildren(current, all);
}
}
};
/**
* Load the list of competencies via ajax. Competencies are filtered by the searchtext.
* @param {String} searchtext The text to filter on.
* @return {promise}
*/
var loadCompetencies = function(searchtext) {
var deferred = $.Deferred();
templates.render('tool_lp/loading', {}).done(function(loadinghtml, loadingjs) {
templates.replaceNodeContents($(treeSelector), loadinghtml, loadingjs);
var promises = ajax.call([{
methodname: 'core_competency_search_competencies',
args: {
searchtext: searchtext,
competencyframeworkid: competencyFrameworkId
}
}]);
promises[0].done(function(result) {
competencies = {};
var i = 0;
for (i = 0; i < result.length; i++) {
competencies[result[i].id] = result[i];
}
var children = [];
var competency = false;
for (i = 0; i < result.length; i++) {
competency = result[i];
if (parseInt(competency.parentid, 10) === 0) {
children.push(competency);
addChildren(competency, result);
}
}
var context = {
shortname: competencyFrameworkShortName,
canmanage: competencyFramworkCanManage,
competencies: children
};
templates.render('tool_lp/competencies_tree_root', context).done(function(html, js) {
templates.replaceNodeContents($(treeSelector), $(html).html(), js);
var tree = new Ariatree(treeSelector, false);
if (currentNodeId) {
var node = $(treeSelector).find('[data-id=' + currentNodeId + ']');
if (node.length) {
tree.selectItem(node);
tree.updateFocus(node);
}
}
deferred.resolve(competencies);
}).fail(deferred.reject);
}).fail(deferred.reject);
});
return deferred.promise();
};
/**
* Whenever the current item in the tree is changed - remember the "id".
* @param {Event} evt
* @param {Object} params The parameters for the event (This is the selected node).
*/
var rememberCurrent = function(evt, params) {
var node = params.selected;
currentNodeId = node.attr('data-id');
};
return /** @alias module:tool_lp/competencytree */ {
// Public variables and functions.
/**
* Initialise the tree.
*
* @param {Number} id The competency id.
* @param {String} shortname The framework shortname
* @param {String} search The current search string
* @param {String} selector The selector for the tree div
* @param {Boolean} canmanage Can manage the competencies
*/
init: function(id, shortname, search, selector, canmanage) {
competencyFrameworkId = id;
competencyFrameworkShortName = shortname;
competencyFramworkCanManage = canmanage;
treeSelector = selector;
loadCompetencies(search).fail(notification.exception);
this.on('selectionchanged', rememberCurrent);
},
/**
* Add an event handler for custom events emitted by the tree.
*
* @param {String} eventname The name of the event - only "selectionchanged" for now
* @param {Function} handler The handler for the event.
*/
on: function(eventname, handler) {
// We can't use the tree on function directly
// because the tree gets rebuilt whenever the search string changes,
// instead we attach the listner to the root node of the tree which never
// gets destroyed (same as "on()" code in the tree.js).
$(treeSelector).on(eventname, handler);
},
/**
* Get the children of a competency.
*
* @param {Number} id The competency ID.
* @return {Array}
* @method getChildren
*/
getChildren: function(id) {
var children = [];
$.each(competencies, function(index, competency) {
if (competency.parentid == id) {
children.push(competency);
}
});
return children;
},
/**
* Get the competency framework id this model was initiliased with.
*
* @return {Number}
*/
getCompetencyFrameworkId: function() {
return competencyFrameworkId;
},
/**
* Get a competency by id
*
* @param {Number} id The competency id
* @return {Object}
*/
getCompetency: function(id) {
return competencies[id];
},
/**
* Get the competency level.
*
* @param {Number} id The competency ID.
* @return {Number}
*/
getCompetencyLevel: function(id) {
var competency = this.getCompetency(id),
level = competency.path.replace(/^\/|\/$/g, '').split('/').length;
return level;
},
/**
* Whether a competency has children.
*
* @param {Number} id The competency ID.
* @return {Boolean}
* @method hasChildren
*/
hasChildren: function(id) {
return this.getChildren(id).length > 0;
},
/**
* Does the competency have a rule?
*
* @param {Number} id The competency ID.
* @return {Boolean}
*/
hasRule: function(id) {
var comp = this.getCompetency(id);
if (comp) {
return comp.ruleoutcome != CompOutcomes.OUTCOME_NONE
&& comp.ruletype;
}
return false;
},
/**
* Reload all the page competencies framework competencies.
* @method reloadCompetencies
* @return {Promise}
*/
reloadCompetencies: function() {
return loadCompetencies('').fail(notification.exception);
},
/**
* Get all competencies for this framework.
*
* @return {Object[]}
*/
listCompetencies: function() {
return competencies;
},
};
});
@@ -0,0 +1,149 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Change the course competency settings in a popup.
*
* @module tool_lp/configurecoursecompetencysettings
* @package tool_lp
* @copyright 2015 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery',
'core/notification',
'tool_lp/dialogue',
'core/str',
'core/ajax',
'core/templates'],
function($, notification, Dialogue, str, ajax, templates) {
/**
* Constructor
*
* @param {String} selector - selector for the links to open the dialogue.
*/
var settingsMod = function(selector) {
$(selector).on('click', this.configureSettings.bind(this));
};
/** @type {Dialogue} Reference to the dialogue that we opened. */
settingsMod.prototype._dialogue = null;
/**
* Open the configure settings dialogue.
*
* @param {Event} e
* @method configureSettings
*/
settingsMod.prototype.configureSettings = function(e) {
var courseid = $(e.target).closest('a').data('courseid');
var currentValue = $(e.target).closest('a').data('pushratingstouserplans');
var context = {
courseid: courseid,
settings: { pushratingstouserplans: currentValue }
};
e.preventDefault();
templates.render('tool_lp/course_competency_settings', context).done(function(html) {
str.get_string('configurecoursecompetencysettings', 'tool_lp').done(function (title) {
this._dialogue = new Dialogue(
title,
html,
this.addListeners.bind(this)
);
}.bind(this)).fail(notification.exception);
}.bind(this)).fail(notification.exception);
};
/**
* Add the save listener to the form.
*
* @method addSaveListener
*/
settingsMod.prototype.addListeners = function() {
var save = this._find('[data-action="save"]');
save.on('click', this.saveSettings.bind(this));
var cancel = this._find('[data-action="cancel"]');
cancel.on('click', this.cancelChanges.bind(this));
};
/**
* Cancel the changes.
*
* @param {Event} e
* @method cancelChanges
*/
settingsMod.prototype.cancelChanges = function(e) {
e.preventDefault();
this._dialogue.close();
};
/**
* Cancel the changes.
*
* @param {String} selector
* @return {JQuery}
*/
settingsMod.prototype._find = function(selector) {
return $('[data-region="coursecompetencysettings"]').find(selector);
};
/**
* Save the settings.
*
* @param {Event} e
* @method saveSettings
*/
settingsMod.prototype.saveSettings = function(e) {
e.preventDefault();
var newValue = this._find('input[name="pushratingstouserplans"]:checked').val();
var courseId = this._find('input[name="courseid"]').val();
var settings = { pushratingstouserplans: newValue };
ajax.call([
{ methodname: 'core_competency_update_course_competency_settings',
args: { courseid: courseId, settings: settings } }
])[0].done(function() {
this.refreshCourseCompetenciesPage();
}.bind(this)).fail(notification.exception);
};
/**
* Refresh the course competencies page.
*
* @param {Event} e
* @method saveSettings
*/
settingsMod.prototype.refreshCourseCompetenciesPage = function() {
var courseId = this._find('input[name="courseid"]').val();
ajax.call([
{ methodname: 'tool_lp_data_for_course_competencies_page',
args: { courseid: courseId } }
])[0].done(function(context) {
templates.render('tool_lp/course_competencies_page', context).done(function(html, js) {
$('[data-region="coursecompetenciespage"]').replaceWith(html);
templates.runTemplateJS(js);
this._dialogue.close();
}.bind(this)).fail(notification.exception);
}.bind(this)).fail(notification.exception);
};
return /** @alias module:tool_lp/configurecoursecompetencysettings */ settingsMod;
});
+101
View File
@@ -0,0 +1,101 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Wrapper for the YUI M.core.notification class. Allows us to
* use the YUI version in AMD code until it is replaced.
*
* @module tool_lp/dialogue
* @package tool_lp
* @copyright 2015 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['core/yui'], function(Y) {
// Private variables and functions.
/**
* Constructor
*
* @param {String} title Title for the window.
* @param {String} content The content for the window.
* @param {function} afterShow Callback executed after the window is opened.
* @param {function} afterHide Callback executed after the window is closed.
* @param {Boolean} wide Specify we want an extra wide dialogue (the size is standard, but wider than the default).
*/
var dialogue = function(title, content, afterShow, afterHide, wide) {
this.yuiDialogue = null;
var parent = this;
// Default for wide is false.
if (typeof wide == 'undefined') {
wide = false;
}
Y.use('moodle-core-notification', 'timers', function () {
var width = '480px';
if (wide) {
width = '800px';
}
parent.yuiDialogue = new M.core.dialogue({
headerContent: title,
bodyContent: content,
draggable: true,
visible: false,
center: true,
modal: true,
width: width
});
parent.yuiDialogue.after('visibleChange', function(e) {
if (e.newVal) {
// Delay the callback call to the next tick, otherwise it can happen that it is
// executed before the dialogue constructor returns.
if ((typeof afterShow !== 'undefined')) {
Y.soon(function() {
afterShow(parent);
parent.yuiDialogue.centerDialogue();
});
}
} else {
if ((typeof afterHide !== 'undefined')) {
Y.soon(function() {
afterHide(parent);
});
}
}
});
parent.yuiDialogue.show();
});
};
/**
* Close this window.
*/
dialogue.prototype.close = function() {
this.yuiDialogue.hide();
this.yuiDialogue.destroy();
};
/**
* Get content.
*/
dialogue.prototype.getContent = function() {
return this.yuiDialogue.bodyNode.getDOMNode();
};
return /** @alias module:tool_lp/dialogue */ dialogue;
});
+88
View File
@@ -0,0 +1,88 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Drag and drop reorder via HTML5.
*
* @module tool_lp/dragdrop-reorder
* @package tool_lp
* @copyright 2015 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['core/str', 'core/yui'], function(str, Y) {
// Private variables and functions.
/**
* Translate the drophit event from YUI
* into simple drag and drop nodes.
* @param {Y.Event} e The yui drop event.
*/
var proxyCallback = function(e) {
var dragNode = e.drag.get('node');
var dropNode = e.drop.get('node');
this.callback(dragNode.getDOMNode(), dropNode.getDOMNode());
};
return /** @alias module:tool_lp/dragdrop-reorder */ {
// Public variables and functions.
/**
* Create an instance of M.tool_lp.dragdrop
*
* @param {String} group Unique string to identify this interaction.
* @param {String} dragHandleText Alt text for the drag handle.
* @param {String} sameNodeText Used in keyboard drag drop for the list of items target.
* @param {String} parentNodeText Used in keyboard drag drop for the parent target.
* @param {String} sameNodeClass class used to find the each of the list of items.
* @param {String} parentNodeClass class used to find the container for the list of items.
* @param {String} dragHandleInsertClass class used to find the location to insert the drag handles.
* @param {function} callback Drop hit handler.
*/
dragdrop: function(group,
dragHandleText,
sameNodeText,
parentNodeText,
sameNodeClass,
parentNodeClass,
dragHandleInsertClass,
callback) {
// Here we are wrapping YUI. This allows us to start transitioning, but
// wait for a good alternative without having inconsistent UIs.
str.get_strings([
{ key: 'emptydragdropregion', component: 'moodle' },
{ key: 'movecontent', component: 'moodle' },
{ key: 'tocontent', component: 'moodle' },
]).done( function () {
Y.use('moodle-tool_lp-dragdrop-reorder', function () {
var context = {
callback: callback
};
M.tool_lp.dragdrop_reorder({
group: group,
dragHandleText: dragHandleText,
sameNodeText: sameNodeText,
parentNodeText: parentNodeText,
sameNodeClass: sameNodeClass,
parentNodeClass: parentNodeClass,
dragHandleInsertClass: dragHandleInsertClass,
callback: Y.bind(proxyCallback, context)
});
});
});
}
};
});
+59
View File
@@ -0,0 +1,59 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Event base javascript module.
*
* @module tool_lp/event_base
* @package tool_lp
* @copyright 2015 Frédéric Massart - FMCorz.net
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery'], function($) {
/**
* Base class.
*/
var Base = function() {
this._eventNode = $('<div></div>');
};
/** @type {Node} The node we attach the events to. */
Base.prototype._eventNode = null;
/**
* Register an event listener.
*
* @param {String} type The event type.
* @param {Function} handler The event listener.
* @method on
*/
Base.prototype.on = function(type, handler) {
this._eventNode.on(type, handler);
};
/**
* Trigger an event.
*
* @param {String} type The type of event.
* @param {Object} The data to pass to the listeners.
* @method _trigger
*/
Base.prototype._trigger = function(type, data) {
this._eventNode.trigger(type, [data]);
};
return /** @alias module:tool_lp/event_base */ Base;
});
+101
View File
@@ -0,0 +1,101 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Evidence delete.
*
* @package tool_lp
* @copyright 2016 Frédéric Massart - FMCorz.net
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery',
'core/notification',
'core/ajax',
'core/str',
'core/log'],
function($, Notification, Ajax, Str, Log) {
var selectors = {};
/**
* Register an event listener.
*
* @param {String} triggerSelector The node on which the click will happen.
* @param {String} containerSelector The parent node that will be removed and contains the evidence ID.
* @return {Void}
*/
var register = function(triggerSelector, containerSelector) {
if (typeof selectors[triggerSelector] !== 'undefined') {
return;
}
selectors[triggerSelector] = $('body').delegate(triggerSelector, 'click', function(e) {
var parent = $(e.currentTarget).parents(containerSelector);
if (!parent.length || parent.length > 1) {
Log.error('None or too many evidence container were found.');
return;
}
var evidenceId = parent.data('id');
if (!evidenceId) {
Log.error('Evidence ID was not found.');
return;
}
e.preventDefault();
e.stopPropagation();
Str.get_strings([
{ key: 'confirm', component: 'moodle' },
{ key: 'areyousure', component: 'moodle' },
{ key: 'delete', component: 'moodle' },
{ key: 'cancel', component: 'moodle' }
]).done(function (strings) {
Notification.confirm(
strings[0], // Confirm.
strings[1], // Are you sure?
strings[2], // Delete.
strings[3], // Cancel.
function() {
var promise = Ajax.call([{
methodname: 'core_competency_delete_evidence',
args: {
id: evidenceId
}
}]);
promise[0].then(function() {
parent.remove();
}).fail(Notification.exception);
}
);
}).fail(Notification.exception);
});
};
return /** @alias module:tool_lp/evidence_delete */ {
/**
* Register an event listener.
*
* @param {String} triggerSelector The node on which the click will happen.
* @param {String} containerSelector The parent node that will be removed and contains the evidence ID.
* @return {Void}
*/
register: register
};
});
@@ -0,0 +1,79 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Cohort selector module.
*
* @module tool_lp/form-cohort-selector
* @class form-cohort-selector
* @package tool_lp
* @copyright 2015 Frédéric Massart - FMCorz.net
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/ajax', 'core/templates'], function($, Ajax, Templates) {
return /** @alias module:tool_lp/form-cohort-selector */ {
processResults: function(selector, results) {
var cohorts = [];
$.each(results, function(index, cohort) {
cohorts.push({
value: cohort.id,
label: cohort._label
});
});
return cohorts;
},
transport: function(selector, query, success, failure) {
var promise,
contextid = parseInt($(selector).data('contextid'), 10),
includes = $(selector).data('includes');
promise = Ajax.call([{
methodname: 'tool_lp_search_cohorts',
args: {
query: query,
context: { contextid: contextid },
includes: includes
}
}]);
promise[0].then(function(results) {
var promises = [],
i = 0;
// Render the label.
$.each(results.cohorts, function(index, cohort) {
promises.push(Templates.render('tool_lp/form-cohort-selector-suggestion', cohort));
});
// Apply the label to the results.
return $.when.apply($.when, promises).then(function() {
var args = arguments;
$.each(results.cohorts, function(index, cohort) {
cohort._label = args[i];
i++;
});
success(results.cohorts);
});
}, failure);
}
};
});
@@ -0,0 +1,89 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* User selector module.
*
* @module tool_lp/form-user-selector
* @class form-user-selector
* @package tool_lp
* @copyright 2015 Frédéric Massart - FMCorz.net
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/ajax', 'core/templates'], function($, Ajax, Templates) {
return /** @alias module:tool_lp/form-user-selector */ {
processResults: function(selector, results) {
var users = [];
$.each(results, function(index, user) {
users.push({
value: user.id,
label: user._label
});
});
return users;
},
transport: function(selector, query, success, failure) {
var promise;
var capability = $(selector).data('capability');
if (typeof capability === "undefined") {
capability = '';
}
promise = Ajax.call([{
methodname: 'tool_lp_search_users',
args: {
query: query,
capability: capability
}
}]);
promise[0].then(function(results) {
var promises = [],
i = 0;
// Render the label.
$.each(results.users, function(index, user) {
var ctx = user,
identity = [];
$.each(['idnumber', 'email', 'phone1', 'phone2', 'department', 'institution'], function(i, k) {
if (typeof user[k] !== 'undefined' && user[k] !== '') {
ctx.hasidentity = true;
identity.push(user[k]);
}
});
ctx.identity = identity.join(', ');
promises.push(Templates.render('tool_lp/form-user-selector-suggestion', ctx));
});
// Apply the label to the results.
return $.when.apply($.when, promises).then(function() {
var args = arguments;
$.each(results.users, function(index, user) {
user._label = args[i];
i++;
});
success(results.users);
});
}, failure);
}
};
});
+179
View File
@@ -0,0 +1,179 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Competency frameworks actions via ajax.
*
* @module tool_lp/frameworkactions
* @package tool_lp
* @copyright 2015 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/templates', 'core/ajax', 'core/notification', 'core/str'], function($, templates, ajax, notification, str) {
// Private variables and functions.
/** @var {Number} pagecontextid The id of the context */
var pagecontextid = 0;
/** @var {Number} frameworkid The id of the framework */
var frameworkid = 0;
/**
* Callback to replace the dom element with the rendered template.
*
* @param {String} newhtml The new html to insert.
* @param {String} newjs The new js to run.
*/
var updatePage = function(newhtml, newjs) {
$('[data-region="managecompetencies"]').replaceWith(newhtml);
templates.runTemplateJS(newjs);
};
/**
* Callback to render the page template again and update the page.
*
* @param {Object} context The context for the template.
*/
var reloadList = function(context) {
templates.render('tool_lp/manage_competency_frameworks_page', context)
.done(updatePage)
.fail(notification.exception);
};
/**
* Duplicate a framework and reload the page.
* @method doDuplicate
* @param {Event} e
*/
var doDuplicate = function(e) {
e.preventDefault();
frameworkid = $(this).attr('data-frameworkid');
// We are chaining ajax requests here.
var requests = ajax.call([{
methodname: 'core_competency_duplicate_competency_framework',
args: { id: frameworkid }
}, {
methodname: 'tool_lp_data_for_competency_frameworks_manage_page',
args: {
pagecontext: {
contextid: pagecontextid
}
}
}]);
requests[1].done(reloadList).fail(notification.exception);
};
/**
* Delete a framework and reload the page.
*/
var doDelete = function() {
// We are chaining ajax requests here.
var requests = ajax.call([{
methodname: 'core_competency_delete_competency_framework',
args: { id: frameworkid }
}, {
methodname: 'tool_lp_data_for_competency_frameworks_manage_page',
args: {
pagecontext: {
contextid: pagecontextid
}
}
}]);
requests[0].done(function (success) {
if (success === false) {
var req = ajax.call([{
methodname: 'core_competency_read_competency_framework',
args: { id: frameworkid }
}]);
req[0].done(function (framework) {
str.get_strings([
{ key: 'frameworkcannotbedeleted', component: 'tool_lp', param: framework.shortname },
{ key: 'cancel', component: 'moodle' }
]).done(function (strings) {
notification.alert(
null,
strings[0]
);
}).fail(notification.exception);
});
}
}).fail(notification.exception);
requests[1].done(reloadList).fail(notification.exception);
};
/**
* Handler for "Delete competency framework" actions.
* @param {Event} e
*/
var confirmDelete = function(e) {
e.preventDefault();
var id = $(this).attr('data-frameworkid');
frameworkid = id;
var requests = ajax.call([{
methodname: 'core_competency_read_competency_framework',
args: { id: frameworkid }
}]);
requests[0].done(function(framework) {
str.get_strings([
{ key: 'confirm', component: 'moodle' },
{ key: 'deletecompetencyframework', component: 'tool_lp', param: framework.shortname },
{ key: 'delete', component: 'moodle' },
{ key: 'cancel', component: 'moodle' }
]).done(function (strings) {
notification.confirm(
strings[0], // Confirm.
strings[1], // Delete competency framework X?
strings[2], // Delete.
strings[3], // Cancel.
doDelete
);
}).fail(notification.exception);
}).fail(notification.exception);
};
return /** @alias module:tool_lp/frameworkactions */ {
// Public variables and functions.
/**
* Expose the event handler for delete.
* @method deleteHandler
* @param {Event} e
*/
deleteHandler: confirmDelete,
/**
* Expose the event handler for duplicate.
* @method duplicateHandler
* @param {Event} e
*/
duplicateHandler: doDuplicate,
/**
* Initialise the module.
* @method init
* @param {Number} contextid The context id of the page.
*/
init: function(contextid) {
pagecontextid = contextid;
}
};
});
@@ -0,0 +1,96 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Frameworks datasource.
*
* This module is compatible with core/form-autocomplete.
*
* @package tool_lpmigrate
* @copyright 2016 Frédéric Massart - FMCorz.net
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/ajax', 'core/notification'], function($, Ajax, Notification) {
return /** @alias module:tool_lpmigrate/frameworks_datasource */ {
/**
* List frameworks.
*
* @param {Number} contextId The context ID.
* @param {Object} options Additional parameters to pass to the external function.
* @return {Promise}
*/
list: function(contextId, options) {
var promise,
args = {
context: {
contextid: contextId
}
};
$.extend(args, typeof options === 'undefined' ? {} : options);
promise = Ajax.call([{
methodname: 'core_competency_list_competency_frameworks',
args: args
}])[0];
return promise.fail(Notification.exception);
},
/**
* Process the results for auto complete elements.
*
* @param {String} selector The selector of the auto complete element.
* @param {Array} results An array or results.
* @return {Array} New array of results.
*/
processResults: function(selector, results) {
var options = [];
$.each(results, function(index, data) {
options.push({
value: data.id,
label: data.shortname + ' ' + data.idnumber
});
});
return options;
},
/**
* Source of data for Ajax element.
*
* @param {String} selector The selector of the auto complete element.
* @param {String} query The query string.
* @param {Function} callback A callback function receiving an array of results.
* @return {Void}
*/
transport: function(selector, query, callback) {
var el = $(selector),
contextId = el.data('contextid'),
onlyVisible = el.data('onlyvisible');
if (!contextId) {
throw new Error('The attribute data-contextid is required on ' + selector);
}
this.list(contextId, {
query: query,
onlyvisible: onlyVisible,
}).then(callback);
}
};
});
+144
View File
@@ -0,0 +1,144 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Grade dialogue.
*
* @package tool_lp
* @copyright 2016 Frédéric Massart - FMCorz.net
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery',
'core/notification',
'core/templates',
'tool_lp/dialogue',
'tool_lp/event_base',
'core/str'],
function($, Notification, Templates, Dialogue, EventBase, Str) {
/**
* Grade dialogue class.
*/
var Grade = function(ratingOptions) {
EventBase.prototype.constructor.apply(this, []);
this._ratingOptions = ratingOptions;
};
Grade.prototype = Object.create(EventBase.prototype);
/** @type {Dialogue} The dialogue. */
Grade.prototype._popup = null;
/** @type {Array} Array of objects containing, 'value', 'name' and optionally 'selected'. */
Grade.prototype._ratingOptions = null;
/**
* After render hook.
*
* @return {Promise}
* @method _afterRender
* @protected
*/
Grade.prototype._afterRender = function() {
var btnRate = this._find('[data-action="rate"]'),
lstRating = this._find('[name="rating"]'),
txtComment = this._find('[name="comment"]');
this._find('[data-action="cancel"]').click(function(e) {
e.preventDefault();
this._trigger('cancelled');
this.close();
}.bind(this));
lstRating.change(function() {
var node = $(this);
if (!node.val()) {
btnRate.prop('disabled', true);
} else {
btnRate.prop('disabled', false);
}
}).change();
btnRate.click(function(e) {
e.preventDefault();
var val = lstRating.val();
if (!val) {
return;
}
this._trigger('rated', {
'rating': val,
'note': txtComment.val()
});
this.close();
}.bind(this));
};
/**
* Close the dialogue.
*
* @method close
*/
Grade.prototype.close = function() {
this._popup.close();
this._popup = null;
};
/**
* Opens the picker.
*
* @param {Number} competencyId The competency ID of the competency to work on.
* @method display
* @return {Promise}
*/
Grade.prototype.display = function() {
return this._render().then(function(html) {
return Str.get_string('rate', 'tool_lp').then(function(title) {
this._popup = new Dialogue(
title,
html,
this._afterRender.bind(this)
);
}.bind(this));
}.bind(this)).fail(Notification.exception);
};
/**
* Find a node in the dialogue.
*
* @param {String} selector
* @method _find
* @protected
*/
Grade.prototype._find = function(selector) {
return $(this._popup.getContent()).find(selector);
};
/**
* Render the dialogue.
*
* @method _render
* @protected
* @return {Promise}
*/
Grade.prototype._render = function() {
var context = {
cangrade: this._canGrade,
ratings: this._ratingOptions
};
return Templates.render('tool_lp/competency_grader', context);
};
return /** @alias module:tool_lp/grade_dialogue */ Grade;
});
@@ -0,0 +1,146 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Module to enable inline editing of a comptency grade.
*
* @package tool_lp
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery',
'core/notification',
'core/ajax',
'core/log',
'tool_lp/grade_dialogue',
'tool_lp/event_base',
'tool_lp/scalevalues',
], function($, notification, ajax, log, GradeDialogue, EventBase, ScaleValues) {
/**
* InlineEditor
*
* @param {String} selector The selector to trigger the grading.
* @param {Number} The id of the scale for this competency.
* @param {Number} The id of the competency.
* @param {Number} The id of the user.
* @param {Number} The id of the plan.
* @param {Number} The id of the course.
* @param {String} Language string for choose a rating.
*/
var InlineEditor = function(selector, scaleId, competencyId, userId, planId, courseId, chooseStr) {
EventBase.prototype.constructor.apply(this, []);
var trigger = $(selector);
if (!trigger.length) {
throw new Error('Could not find the trigger');
}
this._scaleId = scaleId;
this._competencyId = competencyId;
this._userId = userId;
this._planId = planId;
this._courseId = courseId;
this._chooseStr = chooseStr;
this._setUp();
trigger.click(function(e) {
e.preventDefault();
this._dialogue.display();
}.bind(this));
if (this._planId) {
this._methodName = 'core_competency_grade_competency_in_plan';
this._args = {
competencyid: this._competencyId,
planid: this._planId
};
} else if (this._courseId) {
this._methodName = 'core_competency_grade_competency_in_course';
this._args = {
competencyid: this._competencyId,
courseid: this._courseId,
userid: this._userId
};
} else {
this._methodName = 'core_competency_grade_competency';
this._args = {
userid: this._userId,
competencyid: this._competencyId
};
}
};
InlineEditor.prototype = Object.create(EventBase.prototype);
/**
* Setup.
*
* @method _setUp
*/
InlineEditor.prototype._setUp = function() {
var options = [],
self = this;
var promise = ScaleValues.get_values(self._scaleId);
promise.done(function(scalevalues) {
options.push({
value: '',
name: self._chooseStr
});
for (var i = 0; i < scalevalues.length; i++) {
var optionConfig = scalevalues[i];
options.push({
value: optionConfig.id,
name: optionConfig.name
});
}
self._dialogue = new GradeDialogue(options);
self._dialogue.on('rated', function(e, data) {
var args = self._args;
args.grade = data.rating;
args.note = data.note;
ajax.call([{
methodname: self._methodName,
args: args,
done: function(evidence) {
self._trigger('competencyupdated', { args: args, evidence: evidence });
}.bind(self),
fail: notification.exception
}]);
}.bind(self));
}).fail(notification.exception);
};
/** @type {Number} The scale id for this competency. */
InlineEditor.prototype._scaleId = null;
/** @type {Number} The id of the competency. */
InlineEditor.prototype._competencyId = null;
/** @type {Number} The id of the user. */
InlineEditor.prototype._userId = null;
/** @type {Number} The id of the plan. */
InlineEditor.prototype._planId = null;
/** @type {Number} The id of the course. */
InlineEditor.prototype._courseId = null;
/** @type {String} The text for Choose rating. */
InlineEditor.prototype._chooseStr = null;
/** @type {GradeDialogue} The grading dialogue. */
InlineEditor.prototype._dialogue = null;
return /** @alias module:tool_lp/grade_user_competency_inline */ InlineEditor;
});
+817
View File
@@ -0,0 +1,817 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Aria menubar functionality. Enhances a simple nested list structure into a full aria widget.
* Based on the open ajax example: http://oaa-accessibility.org/example/26/
*
* @module tool_lp/menubar
* @package tool_lp
* @copyright 2015 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery'], function($) {
/** @property {boolean} Flag to indicate if we have already registered a click event handler for the document. */
var documentClickHandlerRegistered = false;
/** @property {boolean} Flag to indicate whether there's an active, open menu. */
var menuActive = false;
/**
* Close all open submenus anywhere in the page (there should only ever be one open at a time).
*
* @method closeAllSubMenus
*/
var closeAllSubMenus = function() {
$('.tool-lp-menu .tool-lp-sub-menu').attr('aria-hidden', 'true');
// Every menu's closed at this point, so set the menu active flag to false.
menuActive = false;
};
/**
* Constructor
*
* @param {$} Jquery collection matching the root of the menu.
* @param {Function[]} handlers, called when a menu item is chosen.
*/
var Menubar = function(menuRoot, handlers) {
// Setup private class variables.
this.menuRoot = menuRoot;
this.handlers = handlers;
this.rootMenus = this.menuRoot.children('li');
this.subMenus = this.rootMenus.children('ul');
this.subMenuItems = this.subMenus.children('li');
this.allItems = this.rootMenus.add(this.subMenuItems);
this.activeItem = null;
this.isChildOpen = false;
this.keys = {
tab: 9,
enter: 13,
esc: 27,
space: 32,
left: 37,
up: 38,
right: 39,
down: 40
};
this.addAriaAttributes();
// Add the event listeners.
this.addEventListeners();
};
/**
* Open a submenu, first it closes all other sub-menus and sets the open direction.
* @method openSubMenu
* @param {Node} menu
*/
Menubar.prototype.openSubMenu = function(menu) {
this.setOpenDirection();
closeAllSubMenus();
menu.attr('aria-hidden', 'false');
// Set menu active flag to true when a menu is opened.
menuActive = true;
};
/**
* Bind the event listeners to the DOM
* @method addEventListeners
*/
Menubar.prototype.addEventListeners = function() {
var currentThis = this;
// When clicking outside the menubar.
if (documentClickHandlerRegistered === false) {
$(document).click(function() {
// Check if a menu is opened.
if (menuActive) {
// Close menu.
closeAllSubMenus();
}
});
// Set this flag to true so that we won't need to add a document click handler for the other Menubar instances.
documentClickHandlerRegistered = true;
}
// Hovers.
this.subMenuItems.mouseenter(function() {
$(this).addClass('menu-hover');
return true;
});
this.subMenuItems.mouseout(function() {
$(this).removeClass('menu-hover');
return true;
});
// Mouse listeners.
this.allItems.click(function(e) {
return currentThis.handleClick($(this), e);
});
// Key listeners.
this.allItems.keydown(function(e) {
return currentThis.handleKeyDown($(this), e);
});
this.allItems.focus(function() {
return currentThis.handleFocus($(this));
});
this.allItems.blur(function() {
return currentThis.handleBlur($(this));
});
};
/**
* Process click events for the top menus.
*
* @method handleClick
* @param {Object} item is the jquery object of the item firing the event
* @param {Event} e is the associated event object
* @return boolean Returns false
*/
Menubar.prototype.handleClick = function(item, e) {
e.stopPropagation();
var parentUL = item.parent();
if (parentUL.is('.tool-lp-menu')) {
// Toggle the child menu open/closed.
if (item.children('ul').first().attr('aria-hidden') == 'true') {
this.openSubMenu(item.children('ul').first());
} else {
item.children('ul').first().attr('aria-hidden', 'true');
}
} else {
// Remove hover and focus styling.
this.allItems.removeClass('menu-hover menu-focus');
// Clear the active item.
this.activeItem = null;
// Close the menu.
this.menuRoot.find('ul').not('.root-level').attr('aria-hidden','true');
// Follow any link, or call the click handlers.
var anchor = item.find('a').first();
var clickEvent = new $.Event('click');
clickEvent.target = anchor;
var eventHandled = false;
if (this.handlers) {
$.each(this.handlers, function(selector, handler) {
if (eventHandled) {
return false;
}
if (item.find(selector).length > 0) {
var callable = $.proxy(handler, anchor);
// False means stop propogatting events.
eventHandled = (callable(clickEvent) === false) || clickEvent.isDefaultPrevented();
}
});
}
// If we didn't find a handler, and the HREF is # that probably means that
// we are handling it from somewhere else. Let's just do nothing in that case.
if (!eventHandled && anchor.attr('href') !== '#') {
window.location.href = anchor.attr('href');
}
}
return false;
};
/*
* Process focus events for the menu.
*
* @method handleFocus
* @param {Object} item is the jquery object of the item firing the event
* @return boolean Returns false
*/
Menubar.prototype.handleFocus = function(item) {
// If activeItem is null, we are getting focus from outside the menu. Store
// the item that triggered the event.
if (this.activeItem === null) {
this.activeItem = item;
} else if (item[0] != this.activeItem[0]) {
return true;
}
// Get the set of jquery objects for all the parent items of the active item.
var parentItems = this.activeItem.parentsUntil('ul.tool-lp-menu').filter('li');
// Remove focus styling from all other menu items.
this.allItems.removeClass('menu-focus');
// Add focus styling to the active item.
this.activeItem.addClass('menu-focus');
// Add focus styling to all parent items.
parentItems.addClass('menu-focus');
// If the bChildOpen flag has been set, open the active item's child menu (if applicable).
if (this.isChildOpen === true) {
var itemUL = item.parent();
// If the itemUL is a root-level menu and item is a parent item,
// show the child menu.
if (itemUL.is('.tool-lp-menu') && (item.attr('aria-haspopup') == 'true')) {
this.openSubMenu(item.children('ul').first());
}
}
return true;
};
/*
* Process blur events for the menu.
*
* @method handleBlur
* @param {Object} item is the jquery object of the item firing the event
* @return boolean Returns false
*/
Menubar.prototype.handleBlur = function(item) {
item.removeClass('menu-focus');
return true;
};
/*
* Determine if the menu should open to the left, or the right,
* based on the screen size and menu position.
* @method setOpenDirection
*/
Menubar.prototype.setOpenDirection = function() {
var pos = this.menuRoot.offset();
var isRTL = $(document.body).hasClass('dir-rtl');
var menuMinWidth = 160;
var openLeft = false;
if (isRTL) {
if (pos.left - menuMinWidth < 0) {
openLeft = true;
}
} else {
if (pos.left + menuMinWidth > $( window ).width()) {
openLeft = true;
}
}
if (openLeft) {
this.menuRoot.addClass('tool-lp-menu-open-left');
} else {
this.menuRoot.removeClass('tool-lp-menu-open-left');
}
};
/*
* Process keyDown events for the menu.
*
* @method handleKeyDown
* @param {Object} item is the jquery object of the item firing the event
* @param {Event} e is the associated event object
* @return boolean Returns false if consuming the event
*/
Menubar.prototype.handleKeyDown = function(item, e) {
if (e.altKey || e.ctrlKey) {
// Modifier key pressed: Do not process.
return true;
}
switch(e.keyCode) {
case this.keys.tab: {
// Hide all menu items and update their aria attributes.
this.menuRoot.find('ul').attr('aria-hidden', 'true');
// Remove focus styling from all menu items.
this.allItems.removeClass('menu-focus');
this.activeItem = null;
this.isChildOpen = false;
break;
}
case this.keys.esc: {
var itemUL = item.parent();
if (itemUL.is('.tool-lp-menu')) {
// Hide the child menu and update the aria attributes.
item.children('ul').first().attr('aria-hidden', 'true');
} else {
// Move up one level.
this.activeItem = itemUL.parent();
// Reset the isChildOpen flag.
this.isChildOpen = false;
// Set focus on the new item.
this.activeItem.focus();
// Hide the active menu and update the aria attributes.
itemUL.attr('aria-hidden', 'true');
}
e.stopPropagation();
return false;
}
case this.keys.enter:
case this.keys.space: {
// Trigger click handler.
return this.handleClick(item, e);
}
case this.keys.left: {
this.activeItem = this.moveToPrevious(item);
this.activeItem.focus();
e.stopPropagation();
return false;
}
case this.keys.right: {
this.activeItem = this.moveToNext(item);
this.activeItem.focus();
e.stopPropagation();
return false;
}
case this.keys.up: {
this.activeItem = this.moveUp(item);
this.activeItem.focus();
e.stopPropagation();
return false;
}
case this.keys.down: {
this.activeItem = this.moveDown(item);
this.activeItem.focus();
e.stopPropagation();
return false;
}
}
return true;
};
/**
* Move to the next menu level.
* This will be either the next root-level menu or the child of a menu parent. If
* at the root level and the active item is the last in the menu, this function will loop
* to the first menu item.
*
* If the menu is a horizontal menu, the first child element of the newly selected menu will
* be selected
*
* @method moveToNext
* @param {Object} item is the active menu item
* @return {Object} Returns the item to move to. Returns item is no move is possible
*/
Menubar.prototype.moveToNext = function(item) {
// Item's containing menu.
var itemUL = item.parent();
// The items in the currently active menu.
var menuItems = itemUL.children('li');
// The number of items in the active menu.
var menuNum = menuItems.length;
// The items index in its menu.
var menuIndex = menuItems.index(item);
var newItem = null;
var childMenu = null;
if (itemUL.is('.tool-lp-menu')) {
// This is the root level move to next sibling. This will require closing
// the current child menu and opening the new one.
if (menuIndex < menuNum-1) {
// Not the last root menu.
newItem = item.next();
} else { // Wrap to first item.
newItem = menuItems.first();
}
// Close the current child menu (if applicable).
if (item.attr('aria-haspopup') == 'true') {
childMenu = item.children('ul').first();
if (childMenu.attr('aria-hidden') == 'false') {
// Update the child menu's aria-hidden attribute.
childMenu.attr('aria-hidden', 'true');
this.isChildOpen = true;
}
}
// Remove the focus styling from the current menu.
item.removeClass('menu-focus');
// Open the new child menu (if applicable).
if ((newItem.attr('aria-haspopup') === 'true') && (this.isChildOpen === true)) {
childMenu = newItem.children('ul').first();
// Update the child's aria-hidden attribute.
this.openSubMenu(childMenu);
}
} else {
// This is not the root level. If there is a child menu to be moved into, do that;
// otherwise, move to the next root-level menu if there is one.
if (item.attr('aria-haspopup') == 'true') {
childMenu = item.children('ul').first();
newItem = childMenu.children('li').first();
// Show the child menu and update its aria attributes.
this.openSubMenu(childMenu);
} else {
// At deepest level, move to the next root-level menu.
var parentMenus = null;
var rootItem = null;
// Get list of all parent menus for item, up to the root level.
parentMenus = item.parentsUntil('ul.tool-lp-menu').filter('ul').not('.tool-lp-menu');
// Hide the current menu and update its aria attributes accordingly.
parentMenus.attr('aria-hidden', 'true');
// Remove the focus styling from the active menu.
parentMenus.find('li').removeClass('menu-focus');
parentMenus.last().parent().removeClass('menu-focus');
// The containing root for the menu.
rootItem = parentMenus.last().parent();
menuIndex = this.rootMenus.index(rootItem);
// If this is not the last root menu item, move to the next one.
if (menuIndex < this.rootMenus.length-1) {
newItem = rootItem.next();
} else {
// Loop.
newItem = this.rootMenus.first();
}
// Add the focus styling to the new menu.
newItem.addClass('menu-focus');
if (newItem.attr('aria-haspopup') == 'true') {
childMenu = newItem.children('ul').first();
newItem = childMenu.children('li').first();
// Show the child menu and update it's aria attributes.
this.openSubMenu(childMenu);
this.isChildOpen = true;
}
}
}
return newItem;
};
/**
* Member function to move to the previous menu level.
* This will be either the previous root-level menu or the child of a menu parent. If
* at the root level and the active item is the first in the menu, this function will loop
* to the last menu item.
*
* If the menu is a horizontal menu, the first child element of the newly selected menu will
* be selected
*
* @method moveToPrevious
* @param {Object} item is the active menu item
* @return {Object} Returns the item to move to. Returns item is no move is possible
*/
Menubar.prototype.moveToPrevious = function(item) {
// Item's containing menu.
var itemUL = item.parent();
// The items in the currently active menu.
var menuItems = itemUL.children('li');
// The items index in its menu.
var menuIndex = menuItems.index(item);
var newItem = null;
var childMenu = null;
if (itemUL.is('.tool-lp-menu')) {
// This is the root level move to previous sibling. This will require closing
// the current child menu and opening the new one.
if (menuIndex > 0) {
// Not the first root menu.
newItem = item.prev();
} else {
// Wrap to last item.
newItem = menuItems.last();
}
// Close the current child menu (if applicable).
if (item.attr('aria-haspopup') == 'true') {
childMenu = item.children('ul').first();
if (childMenu.attr('aria-hidden') == 'false') {
// Update the child menu's aria-hidden attribute.
childMenu.attr('aria-hidden', 'true');
this.isChildOpen = true;
}
}
// Remove the focus styling from the current menu.
item.removeClass('menu-focus');
// Open the new child menu (if applicable).
if ((newItem.attr('aria-haspopup') === 'true') && (this.isChildOpen === true)) {
childMenu = newItem.children('ul').first();
// Update the child's aria-hidden attribute.
this.openSubMenu(childMenu);
}
} else {
// This is not the root level. If there is a parent menu that is not the
// root menu, move up one level; otherwise, move to first item of the previous
// root menu.
var parentLI = itemUL.parent();
var parentUL = parentLI.parent();
// If this is a vertical menu or is not the first child menu
// of the root-level menu, move up one level.
if (!parentUL.is('.tool-lp-menu')) {
newItem = itemUL.parent();
// Hide the active menu and update aria-hidden.
itemUL.attr('aria-hidden', 'true');
// Remove the focus highlight from the item.
item.removeClass('menu-focus');
} else {
// Move to previous root-level menu.
// Hide the current menu and update the aria attributes accordingly.
itemUL.attr('aria-hidden', 'true');
// Remove the focus styling from the active menu.
item.removeClass('menu-focus');
parentLI.removeClass('menu-focus');
menuIndex = this.rootMenus.index(parentLI);
if (menuIndex > 0) {
// Move to the previous root-level menu.
newItem = parentLI.prev();
} else {
// Loop to last root-level menu.
newItem = this.rootMenus.last();
}
// Add the focus styling to the new menu.
newItem.addClass('menu-focus');
if (newItem.attr('aria-haspopup') == 'true') {
childMenu = newItem.children('ul').first();
// Show the child menu and update it's aria attributes.
this.openSubMenu(childMenu);
this.isChildOpen = true;
newItem = childMenu.children('li').first();
}
}
}
return newItem;
};
/**
* Member function to select the next item in a menu.
* If the active item is the last in the menu, this function will loop to the
* first menu item.
*
* @method moveDown
* @param {Object} item is the active menu item
* @param {String} startChr is the character to attempt to match against the beginning of the
* menu item titles. If found, focus moves to the next menu item beginning with that character.
* @return {Object} Returns the item to move to. Returns item is no move is possible
*/
Menubar.prototype.moveDown = function(item, startChr) {
// Item's containing menu.
var itemUL = item.parent();
// The items in the currently active menu.
var menuItems = itemUL.children('li').not('.separator');
// The number of items in the active menu.
var menuNum = menuItems.length;
// The items index in its menu.
var menuIndex = menuItems.index(item);
var newItem = null;
var newItemUL = null;
if (itemUL.is('.tool-lp-menu')) {
// This is the root level menu.
if (item.attr('aria-haspopup') != 'true') {
// No child menu to move to.
return item;
}
// Move to the first item in the child menu.
newItemUL = item.children('ul').first();
newItem = newItemUL.children('li').first();
// Make sure the child menu is visible.
this.openSubMenu(newItemUL);
return newItem;
}
// If $item is not the last item in its menu, move to the next item. If startChr is specified, move
// to the next item with a title that begins with that character.
if (startChr) {
var match = false;
var curNdx = menuIndex+1;
// Check if the active item was the last one on the list.
if (curNdx == menuNum) {
curNdx = 0;
}
// Iterate through the menu items (starting from the current item and wrapping) until a match is found
// or the loop returns to the current menu item.
while (curNdx != menuIndex) {
var titleChr = menuItems.eq(curNdx).html().charAt(0);
if (titleChr.toLowerCase() == startChr) {
match = true;
break;
}
curNdx = curNdx+1;
if (curNdx == menuNum) {
// Reached the end of the list, start again at the beginning.
curNdx = 0;
}
}
if (match === true) {
newItem = menuItems.eq(curNdx);
// Remove the focus styling from the current item.
item.removeClass('menu-focus');
return newItem;
} else {
return item;
}
} else {
if (menuIndex < menuNum-1) {
newItem = menuItems.eq(menuIndex+1);
} else {
newItem = menuItems.first();
}
}
// Remove the focus styling from the current item.
item.removeClass('menu-focus');
return newItem;
};
/**
* Function moveUp() is a member function to select the previous item in a menu.
* If the active item is the first in the menu, this function will loop to the
* last menu item.
*
* @method moveUp
* @param {Object} item is the active menu item
* @return {Object} Returns the item to move to. Returns item is no move is possible
*/
Menubar.prototype.moveUp = function(item) {
// Item's containing menu.
var itemUL = item.parent();
// The items in the currently active menu.
var menuItems = itemUL.children('li').not('.separator');
// The items index in its menu.
var menuIndex = menuItems.index(item);
var newItem = null;
if (itemUL.is('.tool-lp-menu')) {
// This is the root level menu.
// Nothing to do.
return item;
}
// If item is not the first item in its menu, move to the previous item.
if (menuIndex > 0) {
newItem = menuItems.eq(menuIndex-1);
} else {
// Loop to top of menu.
newItem = menuItems.last();
}
// Remove the focus styling from the current item.
item.removeClass('menu-focus');
return newItem;
};
/**
* Enhance the dom with aria attributes.
* @method addAriaAttributes
*/
Menubar.prototype.addAriaAttributes = function() {
this.menuRoot.attr('role', 'menubar');
this.rootMenus.attr('role', 'menuitem');
this.rootMenus.attr('tabindex', '0');
this.rootMenus.attr('aria-haspopup', 'true');
this.subMenus.attr('role', 'menu');
this.subMenus.attr('aria-hidden', 'true');
this.subMenuItems.attr('role', 'menuitem');
this.subMenuItems.attr('tabindex', '-1');
// For CSS styling and effects.
this.menuRoot.addClass('tool-lp-menu');
this.allItems.addClass('tool-lp-menu-item');
this.rootMenus.addClass('tool-lp-root-menu');
this.subMenus.addClass('tool-lp-sub-menu');
};
return /** @alias module:tool_lp/menubar */ {
/**
* Create a menu bar object for every node matching the selector.
*
* The expected DOM structure is shown below.
* <ul> <- This is the target of the selector parameter.
* <li> <- This is repeated for each top level menu.
* Text <- This is the text for the top level menu.
* <ul> <- This is a list of the entries in this top level menu.
* <li> <- This is repeated for each menu entry.
* <a href="someurl">Choice 1</a> <- The anchor for the menu.
* </li>
* </ul>
* </li>
* </ul>
*
* @method enhance
* @param {String} selector - The selector for the outer most menu node.
* @param {Function} handler - Javascript handler for when a menu item was chosen. If the
* handler returns true (or does not exist), the
* menu will look for an anchor with a link to follow.
* For example, if the menu entry has a "data-action" attribute
* and we want to call a javascript function when that entry is chosen,
* we could pass a list of handlers like this:
* { "[data-action='add']" : callAddFunction }
*/
enhance: function(selector, handler) {
$(selector).each(function (index, element) {
var menuRoot = $(element);
// Don't enhance the same menu twice.
if (menuRoot.data("menubarEnhanced") !== true) {
(new Menubar(menuRoot, handler));
menuRoot.data("menubarEnhanced", true);
}
});
},
/**
* Handy function to close all open menus anywhere on the page.
* @method closeAll
*/
closeAll: closeAllSubMenus
};
});
@@ -0,0 +1,207 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Handle selecting parent competency in competency form.
*
* @module tool_lp/parentcompetency_form
* @package tool_lp
* @copyright 2015 Issam Taboubi <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/ajax', 'core/str', 'tool_lp/competencypicker', 'core/templates', 'core/notification'],
function($, ajax, Str, Picker, Templates, Notification) {
/**
* Parent Competency Form object.
* @param {String} buttonSelector The parent competency button selector.
* @param {String} inputHiddenSelector The hidden input field selector.
* @param {String} staticElementSelector The static element displaying the parent competency.
* @param {Number} frameworkId The competency framework ID.
* @param {Number} frameworkMaxLevel The framework max level.
* @param {Number} pageContextId The page context ID.
*/
var ParentCompetencyForm = function(buttonSelector,
inputHiddenSelector,
staticElementSelector,
frameworkId,
frameworkMaxLevel,
pageContextId) {
this.buttonSelector = buttonSelector;
this.inputHiddenSelector = inputHiddenSelector;
this.staticElementSelector = staticElementSelector;
this.frameworkId = frameworkId;
this.frameworkMaxLevel = frameworkMaxLevel;
this.pageContextId = pageContextId;
// Register the events.
this.registerEvents();
};
/** @var {String} The parent competency button selector. */
ParentCompetencyForm.prototype.buttonSelector = null;
/** @var {String} The hidden input field selector. */
ParentCompetencyForm.prototype.inputHiddenSelector = null;
/** @var {String} The static element displaying the parent competency. */
ParentCompetencyForm.prototype.staticElementSelector = null;
/** @var {Number} The competency framework ID. */
ParentCompetencyForm.prototype.frameworkId = null;
/** @var {Number} The framework max level. */
ParentCompetencyForm.prototype.frameworkMaxLevel = null;
/** @var {Number} The page context ID. */
ParentCompetencyForm.prototype.pageContextId = null;
/**
* Set the parent competency in the competency form.
*
* @param {Object} Data containing selected cmpetency.
* @method setParent
*/
ParentCompetencyForm.prototype.setParent = function(data) {
var self = this;
if (data.competencyId !== 0) {
ajax.call([
{ methodname: 'core_competency_read_competency', args: {
id: data.competencyId
}}
])[0].done(function(competency) {
$(self.staticElementSelector).html(competency.shortname);
$(self.inputHiddenSelector).val(competency.id);
}).fail(Notification.exception);
} else {
// Root of competency framework selected.
Str.get_string('competencyframeworkroot', 'tool_lp').then(function(rootframework) {
$(self.staticElementSelector).html(rootframework);
$(self.inputHiddenSelector).val(data.competencyId);
}).fail(Notification.exception);
}
};
/**
* Register the events of parent competency button click.
*
* @method registerEvents
*/
ParentCompetencyForm.prototype.registerEvents = function() {
var self = this;
// Event on edit parent button.
$(self.buttonSelector).on('click', function(e) {
e.preventDefault();
var picker = new Picker(self.pageContextId, self.frameworkId, 'self', false);
var maxlevel = self.frameworkMaxLevel;
// Override the fetchcompetencies method to filter by max level.
picker._fetchCompetencies = function(frameworkId, searchText) {
var self = this;
return ajax.call([
{ methodname: 'core_competency_search_competencies', args: {
searchtext: searchText,
competencyframeworkid: frameworkId
}}
])[0].done(function(competencies) {
var disabledcompetencies = [];
function addCompetencyChildren(parent, competencies) {
for (var i = 0; i < competencies.length; i++) {
// Check if competency does not exceed the framework max level.
var path = String(competencies[i].path),
level = path.split('/').length - 2;
if (level >= maxlevel && competencies[i].id !== "0") {
disabledcompetencies.push(competencies[i].id);
}
if (competencies[i].parentid == parent.id) {
parent.haschildren = true;
competencies[i].children = [];
competencies[i].haschildren = false;
parent.children[parent.children.length] = competencies[i];
addCompetencyChildren(competencies[i], competencies);
}
}
}
// Expand the list of competencies into a tree.
var i, tree = [], comp;
for (i = 0; i < competencies.length; i++) {
comp = competencies[i];
if (comp.parentid == "0") { // Loose check for now, because WS returns a string.
comp.children = [];
comp.haschildren = 0;
tree[tree.length] = comp;
addCompetencyChildren(comp, competencies);
}
}
self._competencies = tree;
self.setDisallowedCompetencyIDs(disabledcompetencies);
}.bind(self)).fail(Notification.exception);
};
// Override the render method to make framework selectable.
picker._render = function() {
var self = this;
return self._preRender().then(function() {
var context = {
competencies: self._competencies,
framework: self._getFramework(self._frameworkId),
frameworks: self._frameworks,
search: self._searchText,
singleFramework: self._singleFramework,
};
return Templates.render('tool_lp/competency_picker_competencyform', context);
}.bind(self));
};
// On selected competency.
picker.on('save', function(e, data) {
self.setParent(data);
}.bind(self));
picker.display();
});
};
return {
/**
* Main initialisation.
* @param {String} buttonSelector The parent competency button selector.
* @param {String} inputHiddenSelector The hidden input field selector.
* @param {String} staticElementSelector The static element displaying the parent competency.
* @param {Number} frameworkId The competency framework ID.
* @param {Number} frameworkMaxLevel The framework max level.
* @param {Number} pageContextId The page context ID.
* @method init
*/
init: function(buttonSelector,
inputSelector,
staticElementSelector,
frameworkId,
frameworkMaxLevel,
pageContextId) {
// Create instance.
new ParentCompetencyForm(buttonSelector,
inputSelector,
staticElementSelector,
frameworkId,
frameworkMaxLevel,
pageContextId);
}
};
});
+600
View File
@@ -0,0 +1,600 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Plan actions via ajax.
*
* @module tool_lp/planactions
* @package tool_lp
* @copyright 2015 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery',
'core/templates',
'core/ajax',
'core/notification',
'core/str',
'tool_lp/menubar',
'tool_lp/dialogue'],
function($, templates, ajax, notification, str, Menubar, Dialogue) {
/**
* PlanActions class.
*
* Note that presently this cannot be instantiated more than once per page.
*
* @param {String} type The type of page we're in.
*/
var PlanActions = function(type) {
this._type = type;
if (type === 'plan') {
// This is the page to view one plan.
this._region = '[data-region="plan-page"]';
this._planNode = '[data-region="plan-page"]';
this._template = 'tool_lp/plan_page';
this._contextMethod = 'tool_lp_data_for_plan_page';
} else if (type === 'plans') {
// This is the page to view a list of plans.
this._region = '[data-region="plans"]';
this._planNode = '[data-region="plan-node"]';
this._template = 'tool_lp/plans_page';
this._contextMethod = 'tool_lp_data_for_plans_page';
} else {
throw new TypeError('Unexpected type.');
}
};
/** @type {String} Ajax method to fetch the page data from. */
PlanActions.prototype._contextMethod = null;
/** @type {String} Selector to find the node describing the plan. */
PlanActions.prototype._planNode = null;
/** @type {String} Selector mapping to the region to update. Usually similar to wrapper. */
PlanActions.prototype._region = null;
/** @type {String} Name of the template used to render the region. */
PlanActions.prototype._template = null;
/** @type {String} Type of page/region we're in. */
PlanActions.prototype._type = null;
/**
* Resolve the arguments to refresh the region.
*
* @param {Object} planData Plan data from plan node.
* @return {Object} List of arguments.
*/
PlanActions.prototype._getContextArgs = function(planData) {
var self = this,
args = {};
if (self._type === 'plan') {
args = {
planid: planData.id
};
} else if (self._type === 'plans') {
args = {
userid: planData.userid
};
}
return args;
};
/**
* Refresh the plan view.
*
* This is useful when you only want to refresh the view.
*
* @param {String} selector The node to search the plan data from.
*/
PlanActions.prototype.refresh = function(selector) {
var planData = this._findPlanData($(selector));
this._callAndRefresh([], planData);
};
/**
* Callback to render the region template.
*
* @param {Object} context The context for the template.
*/
PlanActions.prototype._renderView = function(context) {
var self = this;
templates.render(self._template, context)
.done(function(newhtml, newjs) {
$(self._region).replaceWith(newhtml);
templates.runTemplateJS(newjs);
}.bind(self))
.fail(notification.exception);
};
/**
* Call multiple ajax methods, and refresh.
*
* @param {Array} calls List of Ajax calls.
* @param {Object} planData Plan data from plan node.
* @return {Promise}
*/
PlanActions.prototype._callAndRefresh = function(calls, planData) {
var self = this;
calls.push({
methodname: self._contextMethod,
args: self._getContextArgs(planData)
});
// Apply all the promises, and refresh when the last one is resolved.
return $.when.apply($.when, ajax.call(calls))
.then(function() {
self._renderView.call(self, arguments[arguments.length - 1]);
})
.fail(notification.exception);
};
/**
* Delete a plan and reload the region.
*
* @param {Object} planData Plan data from plan node.
*/
PlanActions.prototype._doDelete = function(planData) {
var self = this,
calls = [{
methodname: 'core_competency_delete_plan',
args: { id: planData.id }
}];
self._callAndRefresh(calls, planData);
};
/**
* Delete a plan.
*
* @param {Object} planData Plan data from plan node.
*/
PlanActions.prototype.deletePlan = function(planData) {
var self = this,
requests;
requests = ajax.call([{
methodname: 'core_competency_read_plan',
args: { id: planData.id }
}]);
requests[0].done(function(plan) {
str.get_strings([
{ key: 'confirm', component: 'moodle' },
{ key: 'deleteplan', component: 'tool_lp', param: plan.name },
{ key: 'delete', component: 'moodle' },
{ key: 'cancel', component: 'moodle' }
]).done(function (strings) {
notification.confirm(
strings[0], // Confirm.
strings[1], // Delete plan X?
strings[2], // Delete.
strings[3], // Cancel.
function() {
self._doDelete(planData);
}.bind(self)
);
}).fail(notification.exception);
}).fail(notification.exception);
};
/**
* Reopen plan and reload the region.
*
* @param {Object} planData Plan data from plan node.
*/
PlanActions.prototype._doReopenPlan = function(planData) {
var self = this,
calls = [{
methodname: 'core_competency_reopen_plan',
args: { planid: planData.id}
}];
self._callAndRefresh(calls, planData);
};
/**
* Reopen a plan.
*
* @param {Object} planData Plan data from plan node.
*/
PlanActions.prototype.reopenPlan = function(planData) {
var self = this,
requests = ajax.call([{
methodname: 'core_competency_read_plan',
args: { id: planData.id }
}]);
requests[0].done(function(plan) {
str.get_strings([
{ key: 'confirm', component: 'moodle' },
{ key: 'reopenplanconfirm', component: 'tool_lp', param: plan.name },
{ key: 'reopenplan', component: 'tool_lp' },
{ key: 'cancel', component: 'moodle' }
]).done(function (strings) {
notification.confirm(
strings[0], // Confirm.
strings[1], // Reopen plan X?
strings[2], // reopen.
strings[3], // Cancel.
function() {
self._doReopenPlan(planData);
}.bind(self)
);
}).fail(notification.exception);
}).fail(notification.exception);
};
/**
* Complete plan and reload the region.
*
* @param {Object} planData Plan data from plan node.
*/
PlanActions.prototype._doCompletePlan = function(planData) {
var self = this,
calls = [{
methodname: 'core_competency_complete_plan',
args: { planid: planData.id}
}];
self._callAndRefresh(calls, planData);
};
/**
* Complete a plan process.
*
* @param {Object} planData Plan data from plan node.
*/
PlanActions.prototype.completePlan = function(planData) {
var self = this,
requests = ajax.call([{
methodname: 'core_competency_read_plan',
args: { id: planData.id }
}]);
requests[0].done(function(plan) {
str.get_strings([
{ key: 'confirm', component: 'moodle' },
{ key: 'completeplanconfirm', component: 'tool_lp', param: plan.name },
{ key: 'completeplan', component: 'tool_lp' },
{ key: 'cancel', component: 'moodle' }
]).done(function (strings) {
notification.confirm(
strings[0], // Confirm.
strings[1], // Complete plan X?
strings[2], // Complete.
strings[3], // Cancel.
function() {
self._doCompletePlan(planData);
}.bind(self)
);
}).fail(notification.exception);
}).fail(notification.exception);
};
/**
* Unlink plan and reload the region.
*
* @param {Object} planData Plan data from plan node.
*/
PlanActions.prototype._doUnlinkPlan = function(planData) {
var self = this,
calls = [{
methodname: 'core_competency_unlink_plan_from_template',
args: { planid: planData.id}
}];
self._callAndRefresh(calls, planData);
};
/**
* Unlink a plan process.
*
* @param {Object} planData Plan data from plan node.
*/
PlanActions.prototype.unlinkPlan = function(planData) {
var self = this,
requests = ajax.call([{
methodname: 'core_competency_read_plan',
args: { id: planData.id }
}]);
requests[0].done(function(plan) {
str.get_strings([
{ key: 'confirm', component: 'moodle' },
{ key: 'unlinkplantemplateconfirm', component: 'tool_lp', param: plan.name },
{ key: 'unlinkplantemplate', component: 'tool_lp' },
{ key: 'cancel', component: 'moodle' }
]).done(function (strings) {
notification.confirm(
strings[0], // Confirm.
strings[1], // Unlink plan X?
strings[2], // Unlink.
strings[3], // Cancel.
function() {
self._doUnlinkPlan(planData);
}.bind(self)
);
}).fail(notification.exception);
}).fail(notification.exception);
};
/**
* Request review of a plan.
*
* @param {Object} planData Plan data from plan node.
* @method _doRequestReview
*/
PlanActions.prototype._doRequestReview = function(planData) {
var calls = [{
methodname: 'core_competency_plan_request_review',
args: {
id: planData.id
}
}];
this._callAndRefresh(calls, planData);
};
/**
* Request review of a plan.
*
* @param {Object} planData Plan data from plan node.
* @method requestReview
*/
PlanActions.prototype.requestReview = function(planData) {
this._doRequestReview(planData);
};
/**
* Cancel review request of a plan.
*
* @param {Object} planData Plan data from plan node.
* @method _doCancelReviewRequest
*/
PlanActions.prototype._doCancelReviewRequest = function(planData) {
var calls = [{
methodname: 'core_competency_plan_cancel_review_request',
args: {
id: planData.id
}
}];
this._callAndRefresh(calls, planData);
};
/**
* Cancel review request of a plan.
*
* @param {Object} planData Plan data from plan node.
* @method cancelReviewRequest
*/
PlanActions.prototype.cancelReviewRequest = function(planData) {
this._doCancelReviewRequest(planData);
};
/**
* Start review of a plan.
*
* @param {Object} planData Plan data from plan node.
* @method _doStartReview
*/
PlanActions.prototype._doStartReview = function(planData) {
var calls = [{
methodname: 'core_competency_plan_start_review',
args: {
id: planData.id
}
}];
this._callAndRefresh(calls, planData);
};
/**
* Start review of a plan.
*
* @param {Object} planData Plan data from plan node.
* @method startReview
*/
PlanActions.prototype.startReview = function(planData) {
this._doStartReview(planData);
};
/**
* Stop review of a plan.
*
* @param {Object} planData Plan data from plan node.
* @method _doStopReview
*/
PlanActions.prototype._doStopReview = function(planData) {
var calls = [{
methodname: 'core_competency_plan_stop_review',
args: {
id: planData.id
}
}];
this._callAndRefresh(calls, planData);
};
/**
* Stop review of a plan.
*
* @param {Object} planData Plan data from plan node.
* @method stopReview
*/
PlanActions.prototype.stopReview = function(planData) {
this._doStopReview(planData);
};
/**
* Approve a plan.
*
* @param {Object} planData Plan data from plan node.
* @method _doApprove
*/
PlanActions.prototype._doApprove = function(planData) {
var calls = [{
methodname: 'core_competency_approve_plan',
args: {
id: planData.id
}
}];
this._callAndRefresh(calls, planData);
};
/**
* Approve a plan.
*
* @param {Object} planData Plan data from plan node.
* @method approve
*/
PlanActions.prototype.approve = function(planData) {
this._doApprove(planData);
};
/**
* Unapprove a plan.
*
* @param {Object} planData Plan data from plan node.
* @method _doUnapprove
*/
PlanActions.prototype._doUnapprove = function(planData) {
var calls = [{
methodname: 'core_competency_unapprove_plan',
args: {
id: planData.id
}
}];
this._callAndRefresh(calls, planData);
};
/**
* Unapprove a plan.
*
* @param {Object} planData Plan data from plan node.
* @method unapprove
*/
PlanActions.prototype.unapprove = function(planData) {
this._doUnapprove(planData);
};
/**
* Display list of linked courses on a modal dialogue.
*
* @param {Event} e The event.
*/
PlanActions.prototype._showLinkedCoursesHandler = function(e) {
e.preventDefault();
var competencyid = $(e.target).data('id');
var requests = ajax.call([{
methodname: 'tool_lp_list_courses_using_competency',
args: { id: competencyid }
}]);
requests[0].done(function(courses) {
var context = {
courses: courses
};
templates.render('tool_lp/linked_courses_summary', context).done(function(html) {
str.get_string('linkedcourses', 'tool_lp').done(function (linkedcourses) {
new Dialogue(
linkedcourses, // Title.
html // The linked courses.
);
}).fail(notification.exception);
}).fail(notification.exception);
}).fail(notification.exception);
};
/**
* Plan event handler.
*
* @param {String} method The method to call.
* @param {Event} e The event.
* @method _eventHandler
*/
PlanActions.prototype._eventHandler = function(method, e) {
e.preventDefault();
var data = this._findPlanData($(e.target));
this[method](data);
};
/**
* Find the plan data from the plan node.
*
* @param {Node} node The node to search from.
* @return {Object} Plan data.
*/
PlanActions.prototype._findPlanData = function(node) {
var parent = node.parentsUntil($(this._region).parent(), this._planNode),
data;
if (parent.length != 1) {
throw new Error('The plan node was not located.');
}
data = parent.data();
if (typeof data === 'undefined' || typeof data.id === 'undefined') {
throw new Error('Plan data could not be found.');
}
return data;
};
/**
* Enhance a menu bar.
*
* @param {String} selector Menubar selector.
*/
PlanActions.prototype.enhanceMenubar = function(selector) {
Menubar.enhance(selector, {
'[data-action="plan-delete"]': this._eventHandler.bind(this, 'deletePlan'),
'[data-action="plan-complete"]': this._eventHandler.bind(this, 'completePlan'),
'[data-action="plan-reopen"]': this._eventHandler.bind(this, 'reopenPlan'),
'[data-action="plan-unlink"]': this._eventHandler.bind(this, 'unlinkPlan'),
'[data-action="plan-request-review"]': this._eventHandler.bind(this, 'requestReview'),
'[data-action="plan-cancel-review-request"]': this._eventHandler.bind(this, 'cancelReviewRequest'),
'[data-action="plan-start-review"]': this._eventHandler.bind(this, 'startReview'),
'[data-action="plan-stop-review"]': this._eventHandler.bind(this, 'stopReview'),
'[data-action="plan-approve"]': this._eventHandler.bind(this, 'approve'),
'[data-action="plan-unapprove"]': this._eventHandler.bind(this, 'unapprove'),
});
};
/**
* Register the events in the region.
*
* At this stage this cannot be used with enhanceMenubar or multiple handlers
* will be added to the same node.
*/
PlanActions.prototype.registerEvents = function() {
var wrapper = $(this._region);
wrapper.find('[data-action="plan-delete"]').click(this._eventHandler.bind(this, 'deletePlan'));
wrapper.find('[data-action="plan-complete"]').click(this._eventHandler.bind(this, 'completePlan'));
wrapper.find('[data-action="plan-reopen"]').click(this._eventHandler.bind(this, 'reopenPlan'));
wrapper.find('[data-action="plan-unlink"]').click(this._eventHandler.bind(this, 'unlinkPlan'));
wrapper.find('[data-action="plan-request-review"]').click(this._eventHandler.bind(this, 'requestReview'));
wrapper.find('[data-action="plan-cancel-review-request"]').click(this._eventHandler.bind(this, 'cancelReviewRequest'));
wrapper.find('[data-action="plan-start-review"]').click(this._eventHandler.bind(this, 'startReview'));
wrapper.find('[data-action="plan-stop-review"]').click(this._eventHandler.bind(this, 'stopReview'));
wrapper.find('[data-action="plan-approve"]').click(this._eventHandler.bind(this, 'approve'));
wrapper.find('[data-action="plan-unapprove"]').click(this._eventHandler.bind(this, 'unapprove'));
wrapper.find('[data-action="find-courses-link"]').click(this._showLinkedCoursesHandler.bind(this));
};
return PlanActions;
});
+228
View File
@@ -0,0 +1,228 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Handle opening a dialogue to configure scale data.
*
* @module tool_lp/scaleconfig
* @package tool_lp
* @copyright 2015 Adrian Greeve <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/notification', 'core/templates', 'core/ajax', 'tool_lp/dialogue', 'tool_lp/scalevalues'],
function($, notification, templates, ajax, Dialogue, ModScaleValues) {
/**
* Scale config object.
* @param {String} selectSelector The select box selector.
* @param {String} inputSelector The hidden input field selector.
* @param {String} triggerSelector The trigger selector.
*/
var ScaleConfig = function(selectSelector, inputSelector, triggerSelector) {
this.selectSelector = selectSelector;
this.inputSelector = inputSelector;
this.triggerSelector = triggerSelector;
// Get the current scale ID.
this.originalscaleid = $(selectSelector).val();
$(selectSelector).on('change', this.scaleChangeHandler.bind(this)).change();
$(triggerSelector).click(this.showConfig.bind(this));
};
/** @var {String} The select box selector. */
ScaleConfig.prototype.selectSelector = null;
/** @var {String} The hidden field selector. */
ScaleConfig.prototype.inputSelector = null;
/** @var {String} The trigger selector. */
ScaleConfig.prototype.triggerSelector = null;
/** @var {Array} scalevalues ID and name of the scales. */
ScaleConfig.prototype.scalevalues = null;
/** @var {Number) originalscaleid Original scale ID when the page loads. */
ScaleConfig.prototype.originalscaleid = 0;
/** @var {Number} scaleid Current scale ID. */
ScaleConfig.prototype.scaleid = 0;
/** @var {Dialogue} Reference to the popup. */
ScaleConfig.prototype.popup = null;
/**
* Displays the scale configuration dialogue.
*
* @method showConfig
*/
ScaleConfig.prototype.showConfig = function() {
var self = this;
this.scaleid = $(this.selectSelector).val();
if (this.scaleid <= 0) {
// This should not happen.
return;
}
var scalename = $(this.selectSelector).find("option:selected").text();
this.getScaleValues(this.scaleid).done(function() {
var context = {
scalename: scalename,
scales: self.scalevalues
};
// Dish up the form.
templates.render('tool_lp/scale_configuration_page', context)
.done(function(html) {
new Dialogue(
scalename,
html,
self.initScaleConfig.bind(self)
);
}).fail(notification.exception);
}).fail(notification.exception);
};
/**
* Gets the original scale configuration if it was set.
*
* @method retrieveOriginalScaleConfig
* @return {Object|String} scale configuration or empty string.
*/
ScaleConfig.prototype.retrieveOriginalScaleConfig = function() {
var jsonstring = $(this.inputSelector).val();
if (jsonstring !== '') {
var scaleconfiguration = $.parseJSON(jsonstring);
// The first object should contain the scale ID for the configuration.
var scaledetail = scaleconfiguration.shift();
// Check that this scale id matches the one from the page before returning the configuration.
if (scaledetail.scaleid === this.originalscaleid) {
return scaleconfiguration;
}
}
return '';
};
/**
* Initialises the scale configuration dialogue.
*
* @method initScaleConfig
* @param {Dialogue} popup Dialogue object to initialise.
*/
ScaleConfig.prototype.initScaleConfig = function(popup) {
this.popup = popup;
var body = $(popup.getContent());
if (this.originalscaleid === this.scaleid) {
// Set up the popup to show the current configuration.
var currentconfig = this.retrieveOriginalScaleConfig();
// Set up the form only if there is configuration settings to set.
if (currentconfig !== '') {
currentconfig.forEach(function(value) {
if (value.scaledefault === 1) {
body.find('[data-field="tool_lp_scale_default_' + value.id + '"]').attr('checked', true);
}
if (value.proficient === 1) {
body.find('[data-field="tool_lp_scale_proficient_' + value.id + '"]').attr('checked', true);
}
});
}
}
body.on('click', '[data-action="close"]', function() {
this.setScaleConfig();
popup.close();
}.bind(this));
body.on('click', '[data-action="cancel"]', function() {
popup.close();
}.bind(this));
};
/**
* Set the scale configuration back into a JSON string in the hidden element.
*
* @method setScaleConfig
*/
ScaleConfig.prototype.setScaleConfig = function() {
var body = $(this.popup.getContent());
// Get the data.
var data = [{ scaleid: this.scaleid}];
this.scalevalues.forEach(function(value) {
var scaledefault = 0;
var proficient = 0;
if (body.find('[data-field="tool_lp_scale_default_' + value.id + '"]').is(':checked')) {
scaledefault = 1;
}
if (body.find('[data-field="tool_lp_scale_proficient_' + value.id + '"]').is(':checked')) {
proficient = 1;
}
if (!scaledefault && !proficient) {
return;
}
data.push({
id: value.id,
scaledefault: scaledefault,
proficient: proficient
});
});
var datastring = JSON.stringify(data);
// Send to the hidden field on the form.
$(this.inputSelector).val(datastring);
// Once the configuration has been saved then the original scale ID is set to the current scale ID.
this.originalscaleid = this.scaleid;
};
/**
* Get the scale values for the selected scale.
*
* @method getScaleValues
* @param {Number} scaleid The scale ID of the selected scale.
* @return {Promise} A deffered object with the scale values.
*/
ScaleConfig.prototype.getScaleValues = function(scaleid) {
return ModScaleValues.get_values(scaleid).then(function(values) {
this.scalevalues = values;
return values;
}.bind(this));
};
/**
* Triggered when a scale is selected.
*
* @name scaleChangeHandler
* @param {Event} e
* @return {Void}
* @function
*/
ScaleConfig.prototype.scaleChangeHandler = function(e) {
if ($(e.target).val() <= 0) {
$(this.triggerSelector).prop('disabled', true);
} else {
$(this.triggerSelector).prop('disabled', false);
}
};
return {
/**
* Main initialisation.
*
* @param {String} selectSelector The select box selector.
* @param {String} inputSelector The hidden input field selector.
* @param {String} triggerSelector The trigger selector.
* @return {ScaleConfig} A new instance of ScaleConfig.
* @method init
*/
init: function(selectSelector, inputSelector, triggerSelector) {
return new ScaleConfig(selectSelector, inputSelector, triggerSelector);
}
};
});
+57
View File
@@ -0,0 +1,57 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Module to get the scale values.
*
* @package tool_lp
* @copyright 2016 Serge Gauthier
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/ajax'], function($, ajax) {
var localCache = [];
return /** @alias module:tool_lp/scalevalues */ {
/**
* Return a promise object that will be resolved into a string eventually (maybe immediately).
*
* @method get_values
* @param {Number} scaleid The scale id
* @return [] {Promise}
*/
get_values: function(scaleid) {
var deferred = $.Deferred();
if (typeof localCache[scaleid] === 'undefined') {
ajax.call([{
methodname: 'core_competency_get_scale_values',
args: {scaleid : scaleid},
done: function(scaleinfo) {
localCache[scaleid] = scaleinfo;
deferred.resolve(scaleinfo);
},
fail: (deferred.reject)
}]);
} else {
deferred.resolve(localCache[scaleid]);
}
return deferred.promise();
}
};
});
+202
View File
@@ -0,0 +1,202 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Handle actions on learning plan templates via ajax.
*
* @module tool_lp/templateactions
* @package tool_lp
* @copyright 2015 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/templates', 'core/ajax', 'core/notification', 'core/str','tool_lp/actionselector'],
function($, templates, ajax, notification, str, Actionselector) {
// Private variables and functions.
/** @var {Number} pagecontextid The id of the context */
var pagecontextid = 0;
/** @var {Number} templateid The id of the template */
var templateid = 0;
/** @var {Boolean} Action to apply to plans when deleting a template */
var deleteplans = true;
/**
* Callback to replace the dom element with the rendered template.
*
* @method updatePage
* @param {String} newhtml The new html to insert.
* @param {String} newjs The new js to run.
*/
var updatePage = function(newhtml, newjs) {
$('[data-region="managetemplates"]').replaceWith(newhtml);
templates.runTemplateJS(newjs);
};
/**
* Callback to render the page template again and update the page.
*
* @method reloadList
* @param {Object} context The context for the template.
*/
var reloadList = function(context) {
templates.render('tool_lp/manage_templates_page', context)
.done(updatePage)
.fail(notification.exception);
};
/**
* Delete a template and reload the page.
* @method doDelete
*/
var doDelete = function() {
// We are chaining ajax requests here.
var requests = ajax.call([{
methodname: 'core_competency_delete_template',
args: { id: templateid,
deleteplans: deleteplans }
}, {
methodname: 'tool_lp_data_for_templates_manage_page',
args: {
pagecontext: {
contextid: pagecontextid
}
}
}]);
requests[1].done(reloadList).fail(notification.exception);
};
/**
* Duplicate a template and reload the page.
* @method doDuplicate
* @param {Event} e
*/
var doDuplicate = function(e) {
e.preventDefault();
templateid = $(this).attr('data-templateid');
// We are chaining ajax requests here.
var requests = ajax.call([{
methodname: 'core_competency_duplicate_template',
args: { id: templateid }
}, {
methodname: 'tool_lp_data_for_templates_manage_page',
args: {
pagecontext: {
contextid: pagecontextid
}
}
}]);
requests[1].done(reloadList).fail(notification.exception);
};
/**
* Handler for "Delete learning plan template" actions.
* @method confirmDelete
* @param {Event} e
*/
var confirmDelete = function(e) {
e.preventDefault();
var id = $(this).attr('data-templateid');
templateid = id;
deleteplans = true;
var requests = ajax.call([{
methodname: 'core_competency_read_template',
args: { id: templateid }
}, {
methodname: 'core_competency_template_has_related_data',
args: { id: templateid }
}]);
requests[0].done(function(template) {
requests[1].done(function(templatehasrelateddata) {
if (templatehasrelateddata) {
str.get_strings([
{ key: 'deletetemplate', component: 'tool_lp', param: template.shortname },
{ key: 'deletetemplatewithplans', component: 'tool_lp' },
{ key: 'deleteplans', component: 'tool_lp' },
{ key: 'unlinkplanstemplate', component: 'tool_lp' },
{ key: 'confirm', component: 'moodle' },
{ key: 'cancel', component: 'moodle' }
]).done(function (strings) {
var actions = [{'text': strings[2], 'value' : 'delete'},
{'text': strings[3], 'value' : 'unlink'}];
var actionselector = new Actionselector(
strings[0], // Title.
strings[1], // Message
actions, // Radio button options.
strings[4], // Confirm.
strings[5]); // Cancel.
actionselector.display();
actionselector.on('save', function(e, data) {
if (data.action != 'delete') {
deleteplans = false;
}
doDelete();
});
}).fail(notification.exception);
}
else {
str.get_strings([
{ key: 'confirm', component: 'moodle' },
{ key: 'deletetemplate', component: 'tool_lp', param: template.shortname },
{ key: 'delete', component: 'moodle' },
{ key: 'cancel', component: 'moodle' }
]).done(function (strings) {
notification.confirm(
strings[0], // Confirm.
strings[1], // Delete learning plan template X?
strings[2], // Delete.
strings[3], // Cancel.
doDelete
);
}).fail(notification.exception);
}
}).fail(notification.exception);
}).fail(notification.exception);
};
return /** @alias module:tool_lp/templateactions */ {
// Public variables and functions.
/**
* Expose the event handler for the delete.
* @method deleteHandler
* @param {Event} e
*/
deleteHandler: confirmDelete,
/**
* Expose the event handler for the duplicate.
* @method duplicateHandler
* @param {Event} e
*/
duplicateHandler: doDuplicate,
/**
* Initialise the module.
* @method init
* @param {Number} contextid The context id of the page.
*/
init: function(contextid) {
pagecontextid = contextid;
}
};
});
+624
View File
@@ -0,0 +1,624 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Implement an accessible aria tree widget, from a nested unordered list.
* Based on http://oaa-accessibility.org/example/41/
*
* To respond to selection changed events - use tree.on("selectionchanged", handler).
* The handler will receive an array of nodes, which are the list items that are currently
* selected. (Or a single node if multiselect is disabled).
*
* @module tool_lp/tree
* @package core
* @copyright 2015 Damyon Wiese <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/url', 'core/log'], function($, url, log) {
// Private variables and functions.
/** @var {String} expandedImage The html for an expanded tree node twistie. */
var expandedImage = $('<img alt="" src="' + url.imageUrl('t/expanded') + '"/>');
/** @var {String} collapsedImage The html for a collapsed tree node twistie. */
var collapsedImage = $('<img alt="" src="' + url.imageUrl('t/collapsed') + '"/>');
/**
* Constructor
*
* @param {String} selector
* @param {Boolean} multiSelect
*/
var Tree = function(selector, multiSelect) {
this.treeRoot = $(selector);
this.multiSelect = (typeof multiSelect === 'undefined' || multiSelect === true);
this.items = this.treeRoot.find('li');
this.expandAll = this.items.length < 20;
this.parents = this.treeRoot.find('li:has(ul)');
if (multiSelect) {
this.treeRoot.attr('aria-multiselectable', 'true');
}
this.items.attr('aria-selected', 'false');
this.visibleItems = null;
this.activeItem = null;
this.lastActiveItem = null;
this.keys = {
tab: 9,
enter: 13,
space: 32,
pageup: 33,
pagedown: 34,
end: 35,
home: 36,
left: 37,
up: 38,
right: 39,
down: 40,
asterisk: 106
};
this.init();
this.bindEventHandlers();
};
// Public variables and functions.
/**
* Init this tree
* @method init
*/
Tree.prototype.init = function() {
this.parents.attr('aria-expanded', 'true');
this.parents.prepend(expandedImage.clone());
this.items.attr('role', 'tree-item');
this.items.attr('tabindex', '-1');
this.parents.attr('role', 'group');
this.treeRoot.attr('role', 'tree');
this.visibleItems = this.treeRoot.find('li');
var thisObj = this;
if (!this.expandAll) {
this.parents.each(function() {
thisObj.collapseGroup($(this));
});
this.expandGroup(this.parents.first());
}
};
/**
* Expand a collapsed group.
*
* @method expandGroup
* @param {Object} item is the jquery id of the parent item of the group
*/
Tree.prototype.expandGroup = function(item) {
// Find the first child ul node.
var group = item.children('ul');
// Expand the group.
group.show().attr('aria-hidden', 'false');
item.attr('aria-expanded', 'true');
item.children('img').attr('src', expandedImage.attr('src'));
// Update the list of visible items.
this.visibleItems = this.treeRoot.find('li:visible');
};
/**
* Collapse an expanded group.
*
* @method collapseGroup
* @param {Object} item is the jquery id of the parent item of the group
*/
Tree.prototype.collapseGroup = function(item) {
var group = item.children('ul');
// Collapse the group.
group.hide().attr('aria-hidden', 'true');
item.attr('aria-expanded', 'false');
item.children('img').attr('src', collapsedImage.attr('src'));
// Update the list of visible items.
this.visibleItems = this.treeRoot.find('li:visible');
};
/**
* Expand or collapse a group.
*
* @method toggleGroup
* @param {Object} item is the jquery id of the parent item of the group
*/
Tree.prototype.toggleGroup = function(item) {
if (item.attr('aria-expanded') == 'true') {
this.collapseGroup(item);
} else {
this.expandGroup(item);
}
};
/**
* Whenever the currently selected node has changed, trigger an event using this function.
*
* @method triggerChange
*/
Tree.prototype.triggerChange = function() {
var allSelected = this.items.filter('[aria-selected=true]');
if (!this.multiSelect) {
allSelected = allSelected.first();
}
this.treeRoot.trigger('selectionchanged', { selected: allSelected });
};
/**
* Select all the items between the last focused item and this currently focused item.
*
* @method multiSelectItem
* @param {Object} item is the jquery id of the newly selected item.
*/
Tree.prototype.multiSelectItem = function(item) {
if (!this.multiSelect) {
this.items.attr('aria-selected', 'false');
} else if (this.lastActiveItem !== null) {
var lastIndex = this.visibleItems.index(this.lastActiveItem);
var currentIndex = this.visibleItems.index(this.activeItem);
var oneItem = null;
while (lastIndex < currentIndex) {
oneItem = $(this.visibleItems.get(lastIndex));
oneItem.attr('aria-selected', 'true');
lastIndex++;
}
while (lastIndex > currentIndex) {
oneItem = $(this.visibleItems.get(lastIndex));
oneItem.attr('aria-selected', 'true');
lastIndex--;
}
}
item.attr('aria-selected', 'true');
this.triggerChange();
};
/**
* Select a single item. Make sure all the parents are expanded. De-select all other items.
*
* @method selectItem
* @param {Object} item is the jquery id of the newly selected item.
*/
Tree.prototype.selectItem = function(item) {
// Expand all nodes up the tree.
var walk = item.parent();
while (walk.attr('role') != 'tree') {
walk = walk.parent();
if (walk.attr('aria-expanded') == 'false') {
this.expandGroup(walk);
}
walk = walk.parent();
}
this.items.attr('aria-selected', 'false');
item.attr('aria-selected', 'true');
this.triggerChange();
};
/**
* Toggle the selected state for an item back and forth.
*
* @method toggleItem
* @param {Object} item is the jquery id of the item to toggle.
*/
Tree.prototype.toggleItem = function(item) {
if (!this.multiSelect) {
return this.selectItem(item);
}
var current = item.attr('aria-selected');
if (current === 'true') {
current = 'false';
} else {
current = 'true';
}
item.attr('aria-selected', current);
this.triggerChange();
};
/**
* Set the focus to this item.
*
* @method updateFocus
* @param {Object} item is the jquery id of the parent item of the group
*/
Tree.prototype.updateFocus = function(item) {
this.lastActiveItem = this.activeItem;
this.activeItem = item;
// Expand all nodes up the tree.
var walk = item.parent();
while (walk.attr('role') != 'tree') {
walk = walk.parent();
if (walk.attr('aria-expanded') == 'false') {
this.expandGroup(walk);
}
walk = walk.parent();
}
this.items.attr('tabindex', '-1');
item.attr('tabindex', 0);
};
/**
* Handle a key down event - ie navigate the tree.
*
* @method handleKeyDown
* @param {Object} item is the jquery id of the parent item of the group
* @param {Event} e The event.
*/
Tree.prototype.handleKeyDown = function(item, e) {
var currentIndex = this.visibleItems.index(item);
var newItem = null;
switch (e.keyCode) {
case this.keys.home: {
// Jump to first item in tree.
newItem = this.parents.first();
newItem.focus();
if (e.shiftKey) {
this.multiSelectItem(newItem);
} else if (!e.ctrlKey) {
this.selectItem(newItem);
}
e.stopPropagation();
return false;
}
case this.keys.end: {
// Jump to last visible item.
newItem = this.visibleItems.last();
newItem.focus();
if (e.shiftKey) {
this.multiSelectItem(newItem);
} else if (!e.ctrlKey) {
this.selectItem(newItem);
}
e.stopPropagation();
return false;
}
case this.keys.enter:
case this.keys.space: {
if (e.shiftKey) {
this.multiSelectItem(item);
} else if (e.ctrlKey) {
this.toggleItem(item);
} else {
this.selectItem(item);
}
e.stopPropagation();
return false;
}
case this.keys.left: {
if (item.has('ul') && item.attr('aria-expanded') == 'true') {
this.collapseGroup(item);
} else {
// Move up to the parent.
var itemUL = item.parent();
var itemParent = itemUL.parent();
if (itemParent.is('li')) {
itemParent.focus();
if (e.shiftKey) {
this.multiSelectItem(itemParent);
} else if (!e.ctrlKey) {
this.selectItem(itemParent);
}
}
}
e.stopPropagation();
return false;
}
case this.keys.right: {
if (item.has('ul') && item.attr('aria-expanded') == 'false') {
this.expandGroup(item);
} else {
// Move to the first item in the child group.
newItem = item.children('ul').children('li').first();
if (newItem.length > 0) {
newItem.focus();
if (e.shiftKey) {
this.multiSelectItem(newItem);
} else if (!e.ctrlKey) {
this.selectItem(newItem);
}
}
}
e.stopPropagation();
return false;
}
case this.keys.up: {
if (currentIndex > 0) {
var prev = this.visibleItems.eq(currentIndex - 1);
prev.focus();
if (e.shiftKey) {
this.multiSelectItem(prev);
} else if (!e.ctrlKey) {
this.selectItem(prev);
}
}
e.stopPropagation();
return false;
}
case this.keys.down: {
if (currentIndex < this.visibleItems.length - 1) {
var next = this.visibleItems.eq(currentIndex + 1);
next.focus();
if (e.shiftKey) {
this.multiSelectItem(next);
} else if (!e.ctrlKey) {
this.selectItem(next);
}
}
e.stopPropagation();
return false;
}
case this.keys.asterisk: {
// Expand all groups.
var thisObj = this;
this.parents.each(function() {
thisObj.expandGroup($(this));
});
e.stopPropagation();
return false;
}
}
return true;
};
/**
* Handle a key press event - ie navigate the tree.
*
* @method handleKeyPress
* @param {Object} item is the jquery id of the parent item of the group
* @param {Event} e The event.
*/
Tree.prototype.handleKeyPress = function(item, e) {
if (e.altKey || e.ctrlKey || e.shiftKey) {
// Do nothing.
return true;
}
switch (e.keyCode) {
case this.keys.tab: {
return true;
}
case this.keys.enter:
case this.keys.home:
case this.keys.end:
case this.keys.left:
case this.keys.right:
case this.keys.up:
case this.keys.down: {
e.stopPropagation();
return false;
}
default : {
var chr = String.fromCharCode(e.which);
var match = false;
var itemIndex = this.visibleItems.index(item);
var itemCount = this.visibleItems.length;
var currentIndex = itemIndex + 1;
// Check if the active item was the last one on the list.
if (currentIndex == itemCount) {
currentIndex = 0;
}
// Iterate through the menu items (starting from the current item and wrapping) until a match is found
// or the loop returns to the current menu item.
while (currentIndex != itemIndex) {
var currentItem = this.visibleItems.eq(currentIndex);
var titleChr = currentItem.text().charAt(0);
if (currentItem.has('ul')) {
titleChr = currentItem.find('span').text().charAt(0);
}
if (titleChr.toLowerCase() == chr) {
match = true;
break;
}
currentIndex = currentIndex+1;
if (currentIndex == itemCount) {
// Reached the end of the list, start again at the beginning.
currentIndex = 0;
}
}
if (match === true) {
this.updateFocus(this.visibleItems.eq(currentIndex));
}
e.stopPropagation();
return false;
}
}
return true;
};
/**
* Attach an event listener to the tree.
*
* @method on
* @param {String} eventname This is the name of the event to listen for. Only 'selectionchanged' is supported for now.
* @param {Function} handler The function to call when the event is triggered.
*/
Tree.prototype.on = function(eventname, handler) {
if (eventname !== 'selectionchanged') {
log.warning('Invalid custom event name for tree. Only "selectionchanged" is supported.');
} else {
this.treeRoot.on(eventname, handler);
}
};
/**
* Handle a double click (expand/collapse).
*
* @method handleDblClick
* @param {Object} item is the jquery id of the parent item of the group
* @param {Event} e The event.
*/
Tree.prototype.handleDblClick = function(item, e) {
if (e.altKey || e.ctrlKey || e.shiftKey) {
// Do nothing.
return true;
}
// Apply the focus markup.
this.updateFocus(item);
// Expand or collapse the group.
this.toggleGroup(item);
e.stopPropagation();
return false;
};
/**
* Handle a click (select).
*
* @method handleExpandCollapseClick
* @param {Object} item is the jquery id of the parent item of the group
* @param {Event} e The event.
*/
Tree.prototype.handleExpandCollapseClick = function(item, e) {
// Do not shift the focus.
this.toggleGroup(item);
e.stopPropagation();
return false;
};
/**
* Handle a click (select).
*
* @method handleClick
* @param {Object} item is the jquery id of the parent item of the group
* @param {Event} e The event.
*/
Tree.prototype.handleClick = function(item, e) {
if (e.shiftKey) {
this.multiSelectItem(item);
} else if (e.ctrlKey) {
this.toggleItem(item);
} else {
this.selectItem(item);
}
this.updateFocus(item);
e.stopPropagation();
return false;
};
/**
* Handle a blur event
*
* @method handleBlur
* @param {Object} item item is the jquery id of the parent item of the group
* @param {Event} e The event.
*/
Tree.prototype.handleBlur = function() {
return true;
};
/**
* Handle a focus event
*
* @method handleFocus
* @param {Object} item item is the jquery id of the parent item of the group
* @param {Event} e The event.
*/
Tree.prototype.handleFocus = function(item) {
this.updateFocus(item);
return true;
};
/**
* Bind the event listeners we require.
*
* @method bindEventHandlers
*/
Tree.prototype.bindEventHandlers = function() {
var thisObj = this;
// Bind a dblclick handler to the parent items.
this.parents.dblclick(function(e) {
return thisObj.handleDblClick($(this), e);
});
// Bind a click handler.
this.items.click(function(e) {
return thisObj.handleClick($(this), e);
});
// Bind a toggle handler to the expand/collapse icons.
this.items.children('img').click(function(e) {
return thisObj.handleExpandCollapseClick($(this).parent(), e);
});
// Bind a keydown handler.
this.items.keydown(function(e) {
return thisObj.handleKeyDown($(this), e);
});
// Bind a keypress handler.
this.items.keypress(function(e) {
return thisObj.handleKeyPress($(this), e);
});
// Bind a focus handler.
this.items.focus(function(e) {
return thisObj.handleFocus($(this), e);
});
// Bind a blur handler.
this.items.blur(function(e) {
return thisObj.handleBlur($(this), e);
});
};
return /** @alias module:tool_lp/tree */ Tree;
});
@@ -0,0 +1,96 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Module to enable inline editing of a comptency grade.
*
* @package tool_lp
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery'], function($) {
/**
* UserCompetencyCourseNavigation
*
* @param {String} The selector of the user element.
* @param {String} The selector of the competency element.
* @param {String} The base url for the page (no params).
* @param {Number} The course id
* @param {Number} The user id
* @param {Number} The competency id
*/
var UserCompetencyCourseNavigation = function(userSelector, competencySelector, baseUrl, userId, competencyId, courseId) {
this._baseUrl = baseUrl;
this._userId = userId + '';
this._competencyId = competencyId + '';
this._courseId = courseId;
this._ignoreFirstUser = true;
this._ignoreFirstCompetency = true;
$(userSelector).on('change', this._userChanged.bind(this));
$(competencySelector).on('change', this._competencyChanged.bind(this));
};
/**
* The user was changed in the select list.
*
* @method _userChanged
* @param {Event} e
*/
UserCompetencyCourseNavigation.prototype._userChanged = function(e) {
if (this._ignoreFirstUser) {
this._ignoreFirstUser = false;
return;
}
var newUserId = $(e.target).val();
var queryStr = '?userid=' + newUserId + '&courseid=' + this._courseId + '&competencyid=' + this._competencyId;
document.location = this._baseUrl + queryStr;
};
/**
* The competency was changed in the select list.
*
* @method _competencyChanged
* @param {Event} e
*/
UserCompetencyCourseNavigation.prototype._competencyChanged = function(e) {
if (this._ignoreFirstCompetency) {
this._ignoreFirstCompetency = false;
return;
}
var newCompetencyId = $(e.target).val();
var queryStr = '?userid=' + this._userId + '&courseid=' + this._courseId + '&competencyid=' + newCompetencyId;
document.location = this._baseUrl + queryStr;
};
/** @type {Number} The id of the competency. */
UserCompetencyCourseNavigation.prototype._competencyId = null;
/** @type {Number} The id of the user. */
UserCompetencyCourseNavigation.prototype._userId = null;
/** @type {Number} The id of the course. */
UserCompetencyCourseNavigation.prototype._courseId = null;
/** @type {String} Plugin base url. */
UserCompetencyCourseNavigation.prototype._baseUrl = null;
/** @type {Boolean} Ignore the first change event for users. */
UserCompetencyCourseNavigation.prototype._ignoreFirstUser = null;
/** @type {Boolean} Ignore the first change event for competencies. */
UserCompetencyCourseNavigation.prototype._ignoreFirstCompetency = null;
return /** @alias module:tool_lp/user_competency_course_navigation */ UserCompetencyCourseNavigation;
});
@@ -0,0 +1,112 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Module to refresh a user competency summary in a page.
*
* @package tool_lp
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/notification', 'core/ajax', 'core/templates'], function($, notification, ajax, templates) {
/**
* Info
*
* @param {JQuery} Selector to replace when the information needs updating.
* @param {Number} The id of the competency.
* @param {Number} The id of the user.
* @param {Number} The id of the plan.
* @param {Number} The id of the course.
* @param {Boolean} If we should display the user info.
*/
var Info = function(rootElement, competencyId, userId, planId, courseId, displayuser) {
this._rootElement = rootElement;
this._competencyId = competencyId;
this._userId = userId;
this._planId = planId;
this._courseId = courseId;
this._valid = true;
this._displayuser = (typeof displayuser !== 'undefined') ? displayuser : false;
if (this._planId) {
this._methodName = 'tool_lp_data_for_user_competency_summary_in_plan';
this._args = { competencyid: this._competencyId, planid: this._planId };
this._templateName = 'tool_lp/user_competency_summary_in_plan';
} else if (this._courseId) {
this._methodName = 'tool_lp_data_for_user_competency_summary_in_course';
this._args = { userid: this._userId, competencyid: this._competencyId, courseid: this._courseId };
this._templateName = 'tool_lp/user_competency_summary_in_course';
} else {
this._methodName = 'tool_lp_data_for_user_competency_summary';
this._args = { userid: this._userId, competencyid: this._competencyId };
this._templateName = 'tool_lp/user_competency_summary';
}
};
/**
* Reload the info for this user competency.
*
* @method reload
*/
Info.prototype.reload = function() {
var self = this,
promises = [];
if (!this._valid) {
return;
}
promises = ajax.call([{
methodname: this._methodName,
args: this._args
}]);
promises[0].done(function(context) {
// Check if we should also the user info.
if (self._displayuser) {
context.displayuser = true;
}
templates.render(self._templateName, context).done(function(html, js) {
templates.replaceNode(self._rootElement, html, js);
}).fail(notification.exception);
}).fail(notification.exception);
};
/** @type {JQuery} The root element to replace in the DOM. */
Info.prototype._rootElement = null;
/** @type {Number} The id of the course. */
Info.prototype._courseId = null;
/** @type {Boolean} Is this module valid? */
Info.prototype._valid = null;
/** @type {Number} The id of the plan. */
Info.prototype._planId = null;
/** @type {Number} The id of the competency. */
Info.prototype._competencyId = null;
/** @type {Number} The id of the user. */
Info.prototype._userId = null;
/** @type {String} The method name to load the data. */
Info.prototype._methodName = null;
/** @type {Object} The arguments to load the data. */
Info.prototype._args = null;
/** @type {String} The template to reload the fragment. */
Info.prototype._templateName = null;
/** @type {Boolean} If we should display the user info? */
Info.prototype._displayuser = false;
return /** @alias module:tool_lp/user_competency_info */ Info;
});
@@ -0,0 +1,301 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* User competency workflow.
*
* @module tool_lp/user_competency_workflow
* @package tool_lp
* @copyright 2015 Frédéric Massart - FMCorz.net
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery',
'core/templates',
'core/ajax',
'core/notification',
'core/str',
'tool_lp/menubar',
'tool_lp/event_base'],
function($, Templates, Ajax, Notification, Str, Menubar, EventBase) {
/**
* UserCompetencyWorkflow class.
*
* @param {String} selector The node containing the buttons to switch mode.
*/
var UserCompetencyWorkflow = function() {
EventBase.prototype.constructor.apply(this, []);
};
UserCompetencyWorkflow.prototype = Object.create(EventBase.prototype);
/** @type {String} The selector to find the user competency data. */
UserCompetencyWorkflow.prototype._nodeSelector = '[data-node="user-competency"]';
/**
* Cancel a review request and refresh the view.
*
* @param {Object} data The user competency data.
* @return {Void}
* @method _cancelReviewRequest
*/
UserCompetencyWorkflow.prototype._cancelReviewRequest = function(data) {
var call = {
methodname: 'core_competency_user_competency_cancel_review_request',
args: {
userid: data.userid,
competencyid: data.competencyid
}
};
Ajax.call([call])[0].then(function() {
this._trigger('review-request-cancelled', data);
this._trigger('status-changed', data);
}.bind(this), function() {
this._trigger('error-occured', data);
}.bind(this));
};
/**
* Cancel a review request an refresh the view.
*
* @param {Object} data The user competency data.
* @return {Void}
* @method cancelReviewRequest
*/
UserCompetencyWorkflow.prototype.cancelReviewRequest = function(data) {
this._cancelReviewRequest(data);
};
/**
* Cancel a review request handler.
*
* @param {Event} e The event.
* @return {Void}
* @method _cancelReviewRequestHandler
*/
UserCompetencyWorkflow.prototype._cancelReviewRequestHandler = function(e) {
e.preventDefault();
var data = this._findUserCompetencyData($(e.target));
this.cancelReviewRequest(data);
};
/**
* Request a review and refresh the view.
*
* @param {Object} data The user competency data.
* @return {Void}
* @method _requestReview
*/
UserCompetencyWorkflow.prototype._requestReview = function(data) {
var call = {
methodname: 'core_competency_user_competency_request_review',
args: {
userid: data.userid,
competencyid: data.competencyid
}
};
Ajax.call([call])[0].then(function() {
this._trigger('review-requested', data);
this._trigger('status-changed', data);
}.bind(this), function() {
this._trigger('error-occured', data);
}.bind(this));
};
/**
* Request a review.
*
* @param {Object} data The user competency data.
* @return {Void}
* @method requestReview
*/
UserCompetencyWorkflow.prototype.requestReview = function(data) {
this._requestReview(data);
};
/**
* Request a review handler.
*
* @param {Event} e The event.
* @return {Void}
* @method _requestReviewHandler
*/
UserCompetencyWorkflow.prototype._requestReviewHandler = function(e) {
e.preventDefault();
var data = this._findUserCompetencyData($(e.target));
this.requestReview(data);
};
/**
* Start a review and refresh the view.
*
* @param {Object} data The user competency data.
* @return {Void}
* @method _startReview
*/
UserCompetencyWorkflow.prototype._startReview = function(data) {
var call = {
methodname: 'core_competency_user_competency_start_review',
args: {
userid: data.userid,
competencyid: data.competencyid
}
};
Ajax.call([call])[0].then(function() {
this._trigger('review-started', data);
this._trigger('status-changed', data);
}.bind(this), function() {
this._trigger('error-occured', data);
}.bind(this));
};
/**
* Start a review.
*
* @param {Object} data The user competency data.
* @return {Void}
* @method startReview
*/
UserCompetencyWorkflow.prototype.startReview = function(data) {
this._startReview(data);
};
/**
* Start a review handler.
*
* @param {Event} e The event.
* @return {Void}
* @method _startReviewHandler
*/
UserCompetencyWorkflow.prototype._startReviewHandler = function(e) {
e.preventDefault();
var data = this._findUserCompetencyData($(e.target));
this.startReview(data);
};
/**
* Stop a review and refresh the view.
*
* @param {Object} data The user competency data.
* @return {Void}
* @method _stopReview
*/
UserCompetencyWorkflow.prototype._stopReview = function(data) {
var call = {
methodname: 'core_competency_user_competency_stop_review',
args: {
userid: data.userid,
competencyid: data.competencyid
}
};
Ajax.call([call])[0].then(function() {
this._trigger('review-stopped', data);
this._trigger('status-changed', data);
}.bind(this), function() {
this._trigger('error-occured', data);
}.bind(this));
};
/**
* Stop a review.
*
* @param {Object} data The user competency data.
* @return {Void}
* @method stopReview
*/
UserCompetencyWorkflow.prototype.stopReview = function(data) {
this._stopReview(data);
};
/**
* Stop a review handler.
*
* @param {Event} e The event.
* @return {Void}
* @method _stopReviewHandler
*/
UserCompetencyWorkflow.prototype._stopReviewHandler = function(e) {
e.preventDefault();
var data = this._findUserCompetencyData($(e.target));
this.stopReview(data);
};
/**
* Enhance a menu bar.
*
* @param {String} selector Menubar selector.
*/
UserCompetencyWorkflow.prototype.enhanceMenubar = function(selector) {
Menubar.enhance(selector, {
'[data-action="request-review"]': this._requestReviewHandler.bind(this),
'[data-action="cancel-review-request"]': this._cancelReviewRequestHandler.bind(this),
});
};
/**
* Find the user competency data from a node.
*
* @param {Node} node The node to search from.
* @return {Object} User competency data.
*/
UserCompetencyWorkflow.prototype._findUserCompetencyData = function(node) {
var parent = node.parents(this._nodeSelector),
data;
if (parent.length != 1) {
throw new Error('The evidence node was not located.');
}
data = parent.data();
if (typeof data === 'undefined' || typeof data.userid === 'undefined' || typeof data.competencyid === 'undefined') {
throw new Error('User competency data could not be found.');
}
return data;
};
/**
* Enhance a menu bar.
*
* @param {String} selector Menubar selector.
*/
UserCompetencyWorkflow.prototype.enhanceMenubar = function(selector) {
Menubar.enhance(selector, {
'[data-action="request-review"]': this._requestReviewHandler.bind(this),
'[data-action="cancel-review-request"]': this._cancelReviewRequestHandler.bind(this),
'[data-action="start-review"]': this._startReviewHandler.bind(this),
'[data-action="stop-review"]': this._stopReviewHandler.bind(this),
});
};
/**
* Register the events in the region.
*
* @param {String} selector The base selector to search nodes in and attach events.
*/
UserCompetencyWorkflow.prototype.registerEvents = function(selector) {
var wrapper = $(selector);
wrapper.find('[data-action="request-review"]').click(this._requestReviewHandler.bind(this));
wrapper.find('[data-action="cancel-review-request"]').click(this._cancelReviewRequestHandler.bind(this));
wrapper.find('[data-action="start-review"]').click(this._startReviewHandler.bind(this));
wrapper.find('[data-action="stop-review"]').click(this._stopReviewHandler.bind(this));
};
return /** @alias module:tool_lp/user_competency_actions */ UserCompetencyWorkflow;
});
@@ -0,0 +1,402 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* User evidence actions.
*
* @module tool_lp/user_evidence_actions
* @package tool_lp
* @copyright 2015 Frédéric Massart - FMCorz.net
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery',
'core/templates',
'core/ajax',
'core/notification',
'core/str',
'tool_lp/menubar',
'tool_lp/competencypicker_user_plans'],
function($, templates, ajax, notification, str, Menubar, PickerUserPlans) {
/**
* UserEvidenceActions class.
*
* Note that presently this cannot be instantiated more than once per page.
*
* @param {String} type The type of page we're in.
*/
var UserEvidenceActions = function(type) {
this._type = type;
if (type === 'evidence') {
// This is the page to view one evidence.
this._region = '[data-region="user-evidence-page"]';
this._evidenceNode = '[data-region="user-evidence-page"]';
this._template = 'tool_lp/user_evidence_page';
this._contextMethod = 'tool_lp_data_for_user_evidence_page';
} else if (type === 'list') {
// This is the page to view a list of evidence.
this._region = '[data-region="user-evidence-list"]';
this._evidenceNode = '[data-region="user-evidence-node"]';
this._template = 'tool_lp/user_evidence_list_page';
this._contextMethod = 'tool_lp_data_for_user_evidence_list_page';
} else {
throw new TypeError('Unexpected type.');
}
};
/** @type {String} Ajax method to fetch the page data from. */
UserEvidenceActions.prototype._contextMethod = null;
/** @type {String} Selector to find the node describing the evidence. */
UserEvidenceActions.prototype._evidenceNode = null;
/** @type {String} Selector mapping to the region to update. Usually similar to wrapper. */
UserEvidenceActions.prototype._region = null;
/** @type {String} Name of the template used to render the region. */
UserEvidenceActions.prototype._template = null;
/** @type {String} Type of page/region we're in. */
UserEvidenceActions.prototype._type = null;
/**
* Resolve the arguments to refresh the region.
*
* @param {Object} evidenceData Evidence data from evidence node.
* @return {Object} List of arguments.
*/
UserEvidenceActions.prototype._getContextArgs = function(evidenceData) {
var self = this,
args = {};
if (self._type === 'evidence') {
args = {
id: evidenceData.id
};
} else if (self._type === 'list') {
args = {
userid: evidenceData.userid
};
}
return args;
};
/**
* Callback to render the region template.
*
* @param {Object} context The context for the template.
*/
UserEvidenceActions.prototype._renderView = function(context) {
var self = this;
templates.render(self._template, context)
.done(function(newhtml, newjs) {
templates.replaceNode($(self._region), newhtml, newjs);
}.bind(self))
.fail(notification.exception);
};
/**
* Call multiple ajax methods, and refresh.
*
* @param {Array} calls List of Ajax calls.
* @param {Object} evidenceData Evidence data from evidence node.
* @return {Promise}
*/
UserEvidenceActions.prototype._callAndRefresh = function(calls, evidenceData) {
var self = this;
calls.push({
methodname: self._contextMethod,
args: self._getContextArgs(evidenceData)
});
// Apply all the promises, and refresh when the last one is resolved.
return $.when.apply($.when, ajax.call(calls))
.then(function() {
self._renderView.call(self, arguments[arguments.length - 1]);
})
.fail(notification.exception);
};
/**
* Delete a plan and reload the region.
*
* @param {Object} evidenceData Evidence data from evidence node.
*/
UserEvidenceActions.prototype._doDelete = function(evidenceData) {
var self = this,
calls = [{
methodname: 'core_competency_delete_user_evidence',
args: { id: evidenceData.id }
}];
self._callAndRefresh(calls, evidenceData);
};
/**
* Delete a plan.
*
* @param {Object} evidenceData Evidence data from evidence node.
*/
UserEvidenceActions.prototype.deleteEvidence = function(evidenceData) {
var self = this,
requests;
requests = ajax.call([{
methodname: 'core_competency_read_user_evidence',
args: { id: evidenceData.id }
}]);
requests[0].done(function(evidence) {
str.get_strings([
{ key: 'confirm', component: 'moodle' },
{ key: 'deleteuserevidence', component: 'tool_lp', param: evidence.name },
{ key: 'delete', component: 'moodle' },
{ key: 'cancel', component: 'moodle' }
]).done(function (strings) {
notification.confirm(
strings[0], // Confirm.
strings[1], // Delete evidence X?
strings[2], // Delete.
strings[3], // Cancel.
function() {
self._doDelete(evidenceData);
}.bind(self)
);
}).fail(notification.exception);
}).fail(notification.exception);
};
/**
* Delete evidence handler.
*
* @param {Event} e The event.
*/
UserEvidenceActions.prototype._deleteEvidenceHandler = function(e) {
e.preventDefault();
var data = this._findEvidenceData($(e.target));
this.deleteEvidence(data);
};
/**
* Link a competency and reload.
*
* @param {Object} evidenceData Evidence data from evidence node.
* @param {Number} competencyIds The competency IDs.
* @param {Boolean} requestReview Send competencies to review.
*/
UserEvidenceActions.prototype._doCreateUserEvidenceCompetency = function(evidenceData, competencyIds) {
var self = this,
calls = [];
$.each(competencyIds, function(index, competencyId) {
calls.push({
methodname: 'core_competency_create_user_evidence_competency',
args: {
userevidenceid: evidenceData.id,
competencyid: competencyId,
}
});
});
self._callAndRefresh(calls, evidenceData);
};
/**
* Create a user evidence competency.
*
* @param {Object} evidenceData Evidence data from evidence node.
*/
UserEvidenceActions.prototype.createUserEvidenceCompetency = function(evidenceData) {
var self = this,
picker = new PickerUserPlans(evidenceData.userid);
picker.on('save', function(e, data) {
var competencyIds = data.competencyIds;
self._doCreateUserEvidenceCompetency(evidenceData, competencyIds, data.requestReview);
}.bind(self));
picker.display();
};
/**
* Create user evidence competency handler.
*
* @param {Event} e The event.
*/
UserEvidenceActions.prototype._createUserEvidenceCompetencyHandler = function(e) {
e.preventDefault();
var data = this._findEvidenceData($(e.target));
this.createUserEvidenceCompetency(data);
};
/**
* Remove a linked competency and reload.
*
* @param {Object} evidenceData Evidence data from evidence node.
* @param {Number} competencyId The competency ID.
*/
UserEvidenceActions.prototype._doDeleteUserEvidenceCompetency = function(evidenceData, competencyId) {
var self = this,
calls = [];
calls.push({
methodname: 'core_competency_delete_user_evidence_competency',
args: {
userevidenceid: evidenceData.id,
competencyid: competencyId,
}
});
self._callAndRefresh(calls, evidenceData);
};
/**
* Delete a user evidence competency.
*
* @param {Object} evidenceData Evidence data from evidence node.
* @param {Number} competencyId The competency ID.
*/
UserEvidenceActions.prototype.deleteUserEvidenceCompetency = function(evidenceData, competencyId) {
this._doDeleteUserEvidenceCompetency(evidenceData, competencyId);
};
/**
* Delete user evidence competency handler.
*
* @param {Event} e The event.
*/
UserEvidenceActions.prototype._deleteUserEvidenceCompetencyHandler = function(e) {
var data = this._findEvidenceData($(e.currentTarget)),
competencyId = $(e.currentTarget).data('id');
e.preventDefault();
this.deleteUserEvidenceCompetency(data, competencyId);
};
/**
* Send request review for user evidence competencies and reload the region.
*
* @param {Object} evidenceData Evidence data from evidence node.
*/
UserEvidenceActions.prototype._doReviewUserEvidenceCompetencies = function(evidenceData) {
var self = this,
calls = [{
methodname: 'core_competency_request_review_of_user_evidence_linked_competencies',
args: { id: evidenceData.id }
}];
self._callAndRefresh(calls, evidenceData);
};
/**
* Send request review for user evidence competencies.
*
* @param {Object} evidenceData Evidence data from evidence node.
*/
UserEvidenceActions.prototype.reviewUserEvidenceCompetencies = function(evidenceData) {
var self = this,
requests;
requests = ajax.call([{
methodname: 'core_competency_read_user_evidence',
args: { id: evidenceData.id }
}]);
requests[0].done(function(evidence) {
str.get_strings([
{ key: 'confirm', component: 'moodle' },
{ key: 'sendallcompetenciestoreview', component: 'tool_lp', param: evidence.name },
{ key: 'confirm', component: 'moodle' },
{ key: 'cancel', component: 'moodle' }
]).done(function (strings) {
notification.confirm(
strings[0], // Confirm.
strings[1], // Send all competencies in review for X?
strings[2], // Confirm.
strings[3], // Cancel.
function() {
self._doReviewUserEvidenceCompetencies(evidenceData);
}.bind(self)
);
}).fail(notification.exception);
}).fail(notification.exception);
};
/**
* Send request review for user evidence competencies handler.
*
* @param {Event} e The event.
*/
UserEvidenceActions.prototype._reviewUserEvidenceCompetenciesHandler = function(e) {
e.preventDefault();
var data = this._findEvidenceData($(e.target));
this.reviewUserEvidenceCompetencies(data);
};
/**
* Find the evidence data from the evidence node.
*
* @param {Node} node The node to search from.
* @return {Object} Evidence data.
*/
UserEvidenceActions.prototype._findEvidenceData = function(node) {
var parent = node.parentsUntil($(this._region).parent(), this._evidenceNode),
data;
if (parent.length != 1) {
throw new Error('The evidence node was not located.');
}
data = parent.data();
if (typeof data === 'undefined' || typeof data.id === 'undefined') {
throw new Error('Evidence data could not be found.');
}
return data;
};
/**
* Enhance a menu bar.
*
* @param {String} selector Menubar selector.
*/
UserEvidenceActions.prototype.enhanceMenubar = function(selector) {
var self = this;
Menubar.enhance(selector, {
'[data-action="user-evidence-delete"]': self._deleteEvidenceHandler.bind(self),
'[data-action="link-competency"]': self._createUserEvidenceCompetencyHandler.bind(self),
'[data-action="send-competencies-review"]': self._reviewUserEvidenceCompetenciesHandler.bind(self),
});
};
/**
* Register the events in the region.
*
* At this stage this cannot be used with enhanceMenubar or multiple handlers
* will be added to the same node.
*/
UserEvidenceActions.prototype.registerEvents = function() {
var wrapper = $(this._region),
self = this;
wrapper.find('[data-action="user-evidence-delete"]').click(self._deleteEvidenceHandler.bind(self));
wrapper.find('[data-action="link-competency"]').click(self._createUserEvidenceCompetencyHandler.bind(self));
wrapper.find('[data-action="delete-competency-link"]').click(self._deleteUserEvidenceCompetencyHandler.bind(self));
wrapper.find('[data-action="send-competencies-review"]').click(self._reviewUserEvidenceCompetenciesHandler.bind(self));
};
return /** @alias module:tool_lp/user_evidence_actions */ UserEvidenceActions;
});
@@ -0,0 +1,87 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Course competencies element.
*
* @package tool_lp
* @copyright 2016 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
use core_competency\api;
use core_competency\external\competency_exporter;
require_once($CFG->libdir . '/form/autocomplete.php');
/**
* Course competencies element.
*
* @package tool_lp
* @copyright 2016 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tool_lp_course_competencies_form_element extends MoodleQuickForm_autocomplete {
/**
* Constructor
*
* @param string $elementName Element name
* @param mixed $elementLabel Label(s) for an element
* @param array $options Options to control the element's display
* @param mixed $attributes Either a typical HTML attribute string or an associative array.
*/
public function __construct($elementName=null, $elementLabel=null, $options=array(), $attributes=null) {
global $OUTPUT;
if ($elementName == null) {
// This is broken quickforms messing with the constructors.
return;
}
if (!isset($options['courseid'])) {
throw new coding_exception('Course id is required for the course_competencies form element');
}
$courseid = $options['courseid'];
if (!empty($options['cmid'])) {
$current = \core_competency\api::list_course_module_competencies_in_course_module($options['cmid']);
$ids = array();
foreach ($current as $coursemodulecompetency) {
array_push($ids, $coursemodulecompetency->get_competencyid());
}
$this->setValue($ids);
}
$competencies = api::list_course_competencies($courseid);
$validoptions = array();
$context = context_course::instance($courseid);
foreach ($competencies as $competency) {
$exporter = new competency_exporter($competency['competency'], array('context' => $context));
$templatecontext = array('competency' => $exporter->export($OUTPUT));
$id = $competency['competency']->get_id();
$validoptions[$id] = $OUTPUT->render_from_template('tool_lp/competency_summary', $templatecontext);
}
$attributes['tags'] = false;
$attributes['multiple'] = 'multiple';
parent::__construct($elementName, $elementLabel, $validoptions, $attributes);
}
}
@@ -0,0 +1,74 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Course competency rule element.
*
* @package tool_lp
* @copyright 2016 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
use core_competency\api;
use core_competency\external\competency_exporter;
use core_competency\course_module_competency;
require_once($CFG->libdir . '/form/select.php');
/**
* Course competency rule element.
*
* @package tool_lp
* @copyright 2016 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tool_lp_course_competency_rule_form_element extends MoodleQuickForm_select {
/**
* Constructor
*
* @param string $elementName Element name
* @param mixed $elementLabel Label(s) for an element
* @param array $options Options to control the element's display
* @param mixed $attributes Either a typical HTML attribute string or an associative array.
*/
public function __construct($elementName=null, $elementLabel=null, $options=array(), $attributes=null) {
if ($elementName == null) {
// This is broken quickforms messing with the constructors.
return;
}
if (!empty($options['cmid'])) {
$cmid = $options['cmid'];
$current = \core_competency\api::list_course_module_competencies_in_course_module($cmid);
// Note: We just pick the outcome set on the first course_module_competency - because in our UI are are
// forcing them to be all the same for each activity.
if (!empty($current)) {
$one = array_pop($current);
$this->setValue($one->get_ruleoutcome());
}
}
$validoptions = course_module_competency::get_ruleoutcome_list();
parent::__construct($elementName, $elementLabel, $validoptions, $attributes);
}
}
@@ -0,0 +1,60 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Course competency statistics class
*
* @package tool_lp
* @copyright 2016 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_lp;
defined('MOODLE_INTERNAL') || die();
use core_competency\api;
/**
* Course competency statistics class.
*
* @package tool_lp
* @copyright 2016 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_competency_statistics {
/** @var $competencycount The number of competencies in the course */
public $competencycount = 0;
/** @var $proficientcompetencycount The number of proficient competencies for the current user */
public $proficientcompetencycount = 0;
/** @var $leastproficientcompetencies The competencies in this course that were proficient the least times */
public $leastproficientcompetencies = array();
/**
* Return the custom definition of the properties of this model.
*
* @param int $courseid The course we want to generate statistics for.
*/
public function __construct($courseid) {
global $USER;
$this->competencycount = api::count_competencies_in_course($courseid);
$this->proficientcompetencycount = api::count_proficient_competencies_in_course_for_user($courseid, $USER->id);
$this->leastproficientcompetencies = api::get_least_proficient_competencies_for_course($courseid, 0, 3);
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,74 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Class for exporting a cohort summary from an stdClass.
*
* @package tool_lp
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_lp\external;
defined('MOODLE_INTERNAL') || die();
use renderer_base;
use moodle_url;
/**
* Class for exporting a cohort summary from an stdClass.
*
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cohort_summary_exporter extends \core_competency\external\exporter {
protected static function define_related() {
// Cohorts can exist on a category context.
return array('context' => '\\context');
}
public static function define_properties() {
return array(
'id' => array(
'type' => PARAM_INT,
),
'name' => array(
'type' => PARAM_TEXT,
),
'idnumber' => array(
'type' => PARAM_RAW, // ID numbers are plain text.
'default' => ''
),
'visible' => array(
'type' => PARAM_BOOL,
)
);
}
public static function define_other_properties() {
return array(
'contextname' => array(
'type' => PARAM_TEXT
),
);
}
protected function get_other_values(renderer_base $output) {
return array(
'contextname' => $this->related['context']->get_context_name()
);
}
}
@@ -0,0 +1,124 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Class for exporting competency_path data.
*
* @package tool_lp
* @copyright 2016 Issam Taboubi <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_lp\external;
defined('MOODLE_INTERNAL') || die();
use renderer_base;
use moodle_url;
/**
* Class for exporting competency_path data.
*
* @copyright 2016 Issam Taboubi <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class competency_path_exporter extends \core_competency\external\exporter {
/**
* Constructor.
*
* @param array $related - related objects.
*/
public function __construct($related) {
parent::__construct([], $related);
}
/**
* Return the list of properties.
*
* @return array
*/
protected static function define_related() {
return [
'ancestors' => 'core_competency\\competency[]',
'framework' => 'core_competency\\competency_framework',
'context' => 'context'
];
}
/**
* Return the list of additional properties used only for display.
*
* @return array - Keys with their types.
*/
protected static function define_other_properties() {
return [
'ancestors' => [
'type' => path_node_exporter::read_properties_definition(),
'multiple' => true,
],
'framework' => [
'type' => path_node_exporter::read_properties_definition()
],
'pluginbaseurl' => [
'type' => PARAM_TEXT
],
'pagecontextid' => [
'type' => PARAM_INT
]
];
}
/**
* Get the additional values to inject while exporting.
*
* @param renderer_base $output The renderer.
* @return array Keys are the property names, values are their values.
*/
protected function get_other_values(renderer_base $output) {
$result = new \stdClass();
$ancestors = [];
$nodescount = count($this->related['ancestors']);
$i = 1;
foreach ($this->related['ancestors'] as $competency) {
$exporter = new path_node_exporter([
'id' => $competency->get_id(),
'name' => $competency->get_idnumber(),
'position' => $i,
'first' => $i == 1,
'last' => $i == $nodescount
], [
'context' => $this->related['context'],
]
);
$ancestors[] = $exporter->export($output);
$i++;
}
$result->ancestors = $ancestors;
$exporter = new path_node_exporter([
'id' => $this->related['framework']->get_id(),
'name' => $this->related['framework']->get_shortname(),
'first' => 0,
'last' => 0,
'position' => -1
], [
'context' => $this->related['context']
]
);
$result->framework = $exporter->export($output);
$result->pluginbaseurl = (new moodle_url('/admin/tool/lp'))->out(true);
$result->pagecontextid = $this->related['context']->id;
return (array) $result;
}
}
@@ -0,0 +1,141 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Class for exporting competency data with the set of linked courses.
*
* @package tool_lp
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_lp\external;
defined('MOODLE_INTERNAL') || die();
use context_course;
use renderer_base;
use stdClass;
use core_competency\competency_framework;
use core_competency\external\competency_exporter;
use core_competency\external\competency_framework_exporter;
/**
* Class for exporting competency data with additional related data.
*
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class competency_summary_exporter extends \core_competency\external\exporter {
protected static function define_related() {
// We cache the context so it does not need to be retrieved from the framework every time.
return array('context' => '\\context',
'competency' => '\\core_competency\\competency',
'framework' => '\\core_competency\\competency_framework',
'linkedcourses' => '\\stdClass[]',
'relatedcompetencies' => '\\core_competency\\competency[]');
}
protected static function define_other_properties() {
return array(
'linkedcourses' => array(
'type' => course_summary_exporter::read_properties_definition(),
'multiple' => true
),
'relatedcompetencies' => array(
'type' => competency_exporter::read_properties_definition(),
'multiple' => true
),
'competency' => array(
'type' => competency_exporter::read_properties_definition()
),
'framework' => array(
'type' => competency_framework_exporter::read_properties_definition()
),
'hascourses' => array(
'type' => PARAM_BOOL
),
'hasrelatedcompetencies' => array(
'type' => PARAM_BOOL
),
'scaleid' => array(
'type' => PARAM_INT
),
'scaleconfiguration' => array(
'type' => PARAM_RAW
),
'taxonomyterm' => array(
'type' => PARAM_TEXT
),
'comppath' => array(
'type' => competency_path_exporter::read_properties_definition(),
)
);
}
protected function get_other_values(renderer_base $output) {
$result = new stdClass();
$context = $this->related['context'];
$courses = $this->related['linkedcourses'];
$linkedcourses = array();
foreach ($courses as $course) {
$context = context_course::instance($course->id);
$exporter = new course_summary_exporter($course, array('context' => $context));
$courseexport = $exporter->export($output);
array_push($linkedcourses, $courseexport);
}
$result->linkedcourses = $linkedcourses;
$result->hascourses = count($linkedcourses) > 0;
$relatedcompetencies = array();
foreach ($this->related['relatedcompetencies'] as $competency) {
$exporter = new competency_exporter($competency, array('context' => $context));
$competencyexport = $exporter->export($output);
array_push($relatedcompetencies, $competencyexport);
}
$result->relatedcompetencies = $relatedcompetencies;
$result->hasrelatedcompetencies = count($relatedcompetencies) > 0;
$competency = $this->related['competency'];
$exporter = new competency_exporter($competency, array('context' => $context));
$result->competency = $exporter->export($output);
$exporter = new competency_framework_exporter($this->related['framework']);
$result->framework = $exporter->export($output);
$scaleconfiguration = $this->related['framework']->get_scaleconfiguration();
$scaleid = $this->related['framework']->get_scaleid();
if ($competency->get_scaleid()) {
$scaleconfiguration = $competency->get_scaleconfiguration();
$scaleid = $competency->get_scaleid();
}
$result->scaleconfiguration = $scaleconfiguration;
$result->scaleid = $scaleid;
$level = $competency->get_level();
$taxonomy = $this->related['framework']->get_taxonomy($level);
$result->taxonomyterm = (string) (competency_framework::get_taxonomies_list()[$taxonomy]);
// Competency path.
$exporter = new competency_path_exporter([
'ancestors' => $competency->get_ancestors(),
'framework' => $this->related['framework'],
'context' => $context
]);
$result->comppath = $exporter->export($output);
return (array) $result;
}
}
@@ -0,0 +1,105 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Class for exporting a course competency statistics summary.
*
* @package tool_lp
* @copyright 2016 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_lp\external;
defined('MOODLE_INTERNAL') || die();
use renderer_base;
use moodle_url;
use core_competency\external\competency_exporter;
/**
* Class for exporting a course competency statistics summary.
*
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_competency_statistics_exporter extends \core_competency\external\exporter {
public static function define_properties() {
return array(
'competencycount' => array(
'type' => PARAM_INT,
),
'proficientcompetencycount' => array(
'type' => PARAM_INT,
),
);
}
public static function define_other_properties() {
return array(
'proficientcompetencypercentage' => array(
'type' => PARAM_FLOAT
),
'proficientcompetencypercentageformatted' => array(
'type' => PARAM_RAW
),
'leastproficient' => array(
'type' => competency_exporter::read_properties_definition(),
'multiple' => true
),
'leastproficientcount' => array(
'type' => PARAM_INT
),
'canbegradedincourse' => array(
'type' => PARAM_BOOL
),
'canmanagecoursecompetencies' => array(
'type' => PARAM_BOOL
),
);
}
protected static function define_related() {
return array('context' => 'context');
}
protected function get_other_values(renderer_base $output) {
$proficientcompetencypercentage = 0;
$proficientcompetencypercentageformatted = '';
if ($this->data->competencycount > 0) {
$proficientcompetencypercentage = ((float) $this->data->proficientcompetencycount
/ (float) $this->data->competencycount) * 100.0;
$proficientcompetencypercentageformatted = format_float($proficientcompetencypercentage);
}
$competencies = array();
$contextcache = array();
foreach ($this->data->leastproficientcompetencies as $competency) {
if (!isset($contextcache[$competency->get_competencyframeworkid()])) {
$contextcache[$competency->get_competencyframeworkid()] = $competency->get_context();
}
$context = $contextcache[$competency->get_competencyframeworkid()];
$exporter = new competency_exporter($competency, array('context' => $context));
$competencies[] = $exporter->export($output);
}
return array(
'proficientcompetencypercentage' => $proficientcompetencypercentage,
'proficientcompetencypercentageformatted' => $proficientcompetencypercentageformatted,
'leastproficient' => $competencies,
'leastproficientcount' => count($competencies),
'canbegradedincourse' => has_capability('moodle/competency:coursecompetencygradable', $this->related['context']),
'canmanagecoursecompetencies' => has_capability('moodle/competency:coursecompetencymanage', $this->related['context'])
);
}
}
@@ -0,0 +1,70 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Class for exporting a course module summary from an stdClass.
*
* @package tool_lp
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_lp\external;
defined('MOODLE_INTERNAL') || die();
use renderer_base;
/**
* Class for exporting a course module summary from a cm_info class.
*
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_module_summary_exporter extends \core_competency\external\exporter {
protected static function define_related() {
return array('cm' => 'cm_info');
}
protected function get_other_values(renderer_base $output) {
$context = $this->related['cm']->context;
return array(
'id' => $this->related['cm']->id,
'name' => external_format_string($this->related['cm']->name, $context->id),
'url' => $this->related['cm']->url->out(),
'iconurl' => $this->related['cm']->get_icon_url()->out()
);
}
public static function define_other_properties() {
return array(
'id' => array(
'type' => PARAM_INT,
),
'name' => array(
'type' => PARAM_TEXT
),
'url' => array(
'type' => PARAM_URL
),
'iconurl' => array(
'type' => PARAM_URL
)
);
}
}
@@ -0,0 +1,73 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Class for exporting a course summary from an stdClass.
*
* @package tool_lp
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_lp\external;
defined('MOODLE_INTERNAL') || die();
use renderer_base;
use moodle_url;
/**
* Class for exporting a course summary from an stdClass.
*
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_summary_exporter extends \core_competency\external\exporter {
protected static function define_related() {
// We cache the context so it does not need to be retrieved from the course.
return array('context' => '\\context');
}
protected function get_other_values(renderer_base $output) {
return array(
'viewurl' => (new moodle_url('/course/view.php', array('id' => $this->data->id)))->out(false)
);
}
public static function define_properties() {
return array(
'id' => array(
'type' => PARAM_INT,
),
'fullname' => array(
'type' => PARAM_TEXT,
),
'shortname' => array(
'type' => PARAM_TEXT,
),
'idnumber' => array(
'type' => PARAM_TEXT,
)
);
}
public static function define_other_properties() {
return array(
'viewurl' => array(
'type' => PARAM_URL,
)
);
}
}
+61
View File
@@ -0,0 +1,61 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Class for exporting path_node data.
*
* @package tool_lp
* @copyright 2016 Issam Taboubi <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_lp\external;
defined('MOODLE_INTERNAL') || die();
/**
* Class for exporting path_node data.
*
* @copyright 2016 Issam Taboubi <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class path_node_exporter extends \core_competency\external\exporter {
/**
* Return the list of properties.
*
* @return array
*/
protected static function define_properties() {
return [
'id' => [
'type' => PARAM_INT,
'null' => NULL_ALLOWED
],
'name' => [
'type' => PARAM_TEXT
],
'first' => [
'type' => PARAM_BOOL
],
'last' => [
'type' => PARAM_BOOL
],
'position' => [
'type' => PARAM_INT
]
];
}
}
@@ -0,0 +1,142 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Class for exporting a template statistics summary.
*
* @package tool_lp
* @copyright 2016 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_lp\external;
defined('MOODLE_INTERNAL') || die();
use renderer_base;
use moodle_url;
use core_competency\external\competency_exporter;
/**
* Class for exporting a cohort summary from an stdClass.
*
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class template_statistics_exporter extends \core_competency\external\exporter {
public static function define_properties() {
return array(
'competencycount' => array(
'type' => PARAM_INT,
),
'unlinkedcompetencycount' => array(
'type' => PARAM_INT,
),
'plancount' => array(
'type' => PARAM_INT,
),
'completedplancount' => array(
'type' => PARAM_INT,
),
'usercompetencyplancount' => array(
'type' => PARAM_INT,
),
'proficientusercompetencyplancount' => array(
'type' => PARAM_INT,
)
);
}
public static function define_other_properties() {
return array(
'linkedcompetencypercentage' => array(
'type' => PARAM_FLOAT
),
'linkedcompetencypercentageformatted' => array(
'type' => PARAM_RAW
),
'linkedcompetencycount' => array(
'type' => PARAM_INT
),
'completedplanpercentage' => array(
'type' => PARAM_FLOAT
),
'completedplanpercentageformatted' => array(
'type' => PARAM_RAW
),
'proficientusercompetencyplanpercentage' => array(
'type' => PARAM_FLOAT
),
'proficientusercompetencyplanpercentageformatted' => array(
'type' => PARAM_RAW
),
'leastproficient' => array(
'type' => competency_exporter::read_properties_definition(),
'multiple' => true
),
'leastproficientcount' => array(
'type' => PARAM_INT
),
);
}
protected function get_other_values(renderer_base $output) {
$linkedcompetencycount = $this->data->competencycount - $this->data->unlinkedcompetencycount;
if ($linkedcompetencycount < 0) {
// Should never happen.
$linkedcompetencycount = 0;
}
$linkedcompetencypercentage = 0;
$linkedcompetencypercentageformatted = '';
if ($this->data->competencycount > 0) {
$linkedcompetencypercentage = ((float) $linkedcompetencycount / (float) $this->data->competencycount) * 100.0;
$linkedcompetencypercentageformatted = format_float($linkedcompetencypercentage);
}
$completedplanpercentage = 0;
$completedplanpercentageformatted = '';
if ($this->data->plancount > 0) {
$completedplanpercentage = ((float) $this->data->completedplancount / (float) $this->data->plancount) * 100.0;
$completedplanpercentageformatted = format_float($completedplanpercentage);
}
$proficientusercompetencyplanpercentage = 0;
$proficientusercompetencyplanpercentageformatted = '';
if ($this->data->usercompetencyplancount > 0) {
$proficientusercompetencyplanpercentage = ((float) $this->data->proficientusercompetencyplancount
/ (float) $this->data->usercompetencyplancount) * 100.0;
$proficientusercompetencyplanpercentageformatted = format_float($proficientusercompetencyplanpercentage);
}
$competencies = array();
$contextcache = array();
foreach ($this->data->leastproficientcompetencies as $competency) {
if (!isset($contextcache[$competency->get_competencyframeworkid()])) {
$contextcache[$competency->get_competencyframeworkid()] = $competency->get_context();
}
$context = $contextcache[$competency->get_competencyframeworkid()];
$exporter = new competency_exporter($competency, array('context' => $context));
$competencies[] = $exporter->export($output);
}
return array(
'linkedcompetencycount' => $linkedcompetencycount,
'linkedcompetencypercentage' => $linkedcompetencypercentage,
'linkedcompetencypercentageformatted' => $linkedcompetencypercentageformatted,
'completedplanpercentage' => $completedplanpercentage,
'completedplanpercentageformatted' => $completedplanpercentageformatted,
'proficientusercompetencyplanpercentage' => $proficientusercompetencyplanpercentage,
'proficientusercompetencyplanpercentageformatted' => $proficientusercompetencyplanpercentageformatted,
'leastproficient' => $competencies,
'leastproficientcount' => count($competencies)
);
}
}

Some files were not shown because too many files have changed in this diff Show More