芝麻web文件管理V1.00
编辑当前文件:/home2/sdektunc/.trash/cepali/grade/report/history/yui/src/userselector/js/userselector.js
// 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
. /** * The User Selector for the grade history report. * * @module moodle-gradereport_history-userselector * @package gradereport_history * @copyright 2013 NetSpot Pty Ltd (https://www.netspot.com.au) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @main moodle-gradereport_history-userselector */ /** * @module moodle-gradereport_history-userselector */ var COMPONENT = 'gradereport_history'; var USP = { AJAXURL: 'ajaxurl', BASE: 'base', CHECKBOX_NAME_PREFIX: 'usp-u', COURSEID: 'courseid', DIALOGUE_PREFIX: 'moodle-dialogue', NAME: 'gradereport_history_usp', PAGE: 'page', PARAMS: 'params', PERPAGE: 'perPage', SEARCH: 'search', SEARCHBTN: 'searchbtn', SELECTEDUSERS: 'selectedUsers', URL: 'url', USERCOUNT: 'userCount' }; var CSS = { ACCESSHIDE: 'accesshide', AJAXCONTENT: 'usp-ajax-content', CHECKBOX: 'usp-checkbox', CLOSE: 'close', CLOSEBTN: 'usp-finish', CONTENT: 'usp-content', DETAILS: 'details', EXTRAFIELDS: 'extrafields', FIRSTADDED: 'usp-first-added', FULLNAME: 'fullname', HEADER: 'usp-header', HIDDEN: 'hidden', LIGHTBOX: 'usp-loading-lightbox', LOADINGICON: 'loading-icon', MORERESULTS: 'usp-more-results', OPTIONS: 'options', PICTURE: 'usp-picture', RESULTSCOUNT: 'usp-results-count', SEARCH: 'usp-search', SEARCHBTN: 'usp-search-btn', SEARCHFIELD: 'usp-search-field', SEARCHRESULTS: 'usp-search-results', SELECTED: 'selected', USER: 'usp-user', USERS: 'usp-users', WRAP: 'usp-wrap' }; var SELECTORS = { AJAXCONTENT: '.' + CSS.AJAXCONTENT, FINISHBTN: '.' + CSS.CLOSEBTN + ' input', FIRSTADDED: '.' + CSS.FIRSTADDED, FULLNAME: '.' + CSS.FULLNAME + ' label', LIGHTBOX: '.' + CSS.LIGHTBOX, MORERESULTS: '.' + CSS.MORERESULTS, OPTIONS: '.' + CSS.OPTIONS, PICTURE: '.' + CSS.USER + ' .userpicture', RESULTSCOUNT: '.' + CSS.RESULTSCOUNT, RESULTSUSERS: '.' + CSS.SEARCHRESULTS + ' .' + CSS.USERS, SEARCHBTN: '.' + CSS.SEARCHBTN, SEARCHFIELD: '.' + CSS.SEARCHFIELD, SELECTEDNAMES: '.felement .selectednames', TRIGGER: '.gradereport_history_plugin input.selectortrigger', USER: '.' + CSS.USER, USERFULLNAMES: 'input[name="userfullnames"]', USERIDS: 'input[name="userids"]', USERSELECT: '.' + CSS.CHECKBOX + ' input[type=checkbox]' }; /** * User Selector. * * @namespace M.gradereport_history * @class UserSelector * @constructor */ var USERSELECTOR = function() { USERSELECTOR.superclass.constructor.apply(this, arguments); }; Y.namespace('M.gradereport_history').UserSelector = Y.extend(USERSELECTOR, M.core.dialogue, { /** * Whether or not this is the first time the user displays the dialogue within that request. * * @property _firstDisplay * @type Boolean * @private */ _firstDisplay: true, /** * The list of all the users selected while the dialogue is open. * * @type Object * @property _usersBufferList * @private */ _usersBufferList: null, /** * The Node on which the focus is set. * * @property _userTabFocus * @type Node * @private */ _userTabFocus: null, /** * Compiled template function for a user node. * * @property _userTemplate * @type Function * @private */ _userTemplate: null, initializer: function() { var bb = this.get('boundingBox'), content, params, tpl; tpl = Y.Handlebars.compile( '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
{{get_string "loading" "admin"}}
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
'); content = Y.Node.create( tpl({ COMPONENT: COMPONENT, CSS: CSS, loadingIcon: M.util.image_url('i/loading', 'moodle') }) ); // Set the title and content. this.getStdModNode(Y.WidgetStdMod.HEADER).prepend(Y.Node.create('
' + this.get('title') + '
')); this.setStdModContent(Y.WidgetStdMod.BODY, content, Y.WidgetStdMod.REPLACE); // Use standard dialogue class name. This removes the default styling of the footer. this.get('boundingBox').one('.moodle-dialogue-wrap').addClass('moodle-dialogue-content'); // Add the event on the button that opens the dialogue. Y.one(SELECTORS.TRIGGER).on('click', this.show, this); // The button to finalize the selection. bb.one(SELECTORS.FINISHBTN).on('click', this.finishSelectingUsers, this); // Delegate the keyboard navigation in the users list. bb.delegate('key', this.userKeyboardNavigation, 'down:38,40', SELECTORS.AJAXCONTENT, this); // Delegate the action to select a user. Y.delegate('click', this.selectUser, SELECTORS.AJAXCONTENT, SELECTORS.USERSELECT, this); Y.delegate('click', this.selectUser, SELECTORS.AJAXCONTENT, SELECTORS.PICTURE, this); params = this.get(USP.PARAMS); params.id = this.get(USP.COURSEID); this.set(USP.PARAMS, params); bb.one(SELECTORS.SEARCHBTN).on('click', this.search, this, false); }, /** * Display the dialogue. * * @method show */ show: function(e) { var bb; this._usersBufferList = Y.clone(this.get(USP.SELECTEDUSERS)); if (this._firstDisplay) { // Load the default list of users when the dialogue is loaded for the first time. this._firstDisplay = false; this.search(e, false); } else { // Leave the content as is, but reset the selection. bb = this.get('boundingBox'); // Remove all the selected users. bb.all(SELECTORS.USER).each(function(node) { this.markUserNode(node, false); }, this); // Select the users. Y.Object.each(this._usersBufferList, function(v, k) { var user = bb.one(SELECTORS.USER + '[data-userid="' + k + '"]'); if (user) { this.markUserNode(user, true); } }, this); // Reset the tab focus. this.setUserTabFocus(bb.one(SELECTORS.USER)); } return Y.namespace('M.gradereport_history.UserSelector').superclass.show.call(this); }, /** * Search for users. * * @method search * @param {EventFacade} e The event. * @param {Boolean} append Whether we want to append the results to the current results or not. */ search: function(e, append) { if (e) { e.preventDefault(); } var params; if (append) { this.set(USP.PAGE, this.get(USP.PAGE) + 1); } else { this.set(USP.USERCOUNT, 0); this.set(USP.PAGE, 0); } params = this.get(USP.PARAMS); params.sesskey = M.cfg.sesskey; params.action = 'searchusers'; params.search = this.get('boundingBox').one(SELECTORS.SEARCHFIELD).get('value'); params.page = this.get(USP.PAGE); params.perpage = this.get(USP.PERPAGE); Y.io(M.cfg.wwwroot + this.get(USP.AJAXURL), { method: 'POST', data: window.build_querystring(params), on: { start: this.preSearch, complete: this.processSearchResults, end: this.postSearch }, context: this, "arguments": { // Quoted because this is a reserved keyword. append: append } }); }, /** * Pre search callback. * * @method preSearch * @param {String} transactionId The transaction ID. * @param {Object} args The arguments passed from YUI.io() */ preSearch: function(unused, args) { var bb = this.get('boundingBox'); // Display the lightbox. bb.one(SELECTORS.LIGHTBOX).removeClass(CSS.HIDDEN); // Set the number of results to 'loading...'. if (!args.append) { bb.one(SELECTORS.RESULTSCOUNT).setHTML(M.util.get_string('loading', 'admin')); } }, /** * Post search callback. * * @method postSearch * @param {String} transactionId The transaction ID. * @param {Object} args The arguments passed from YUI.io() */ postSearch: function(transactionId, args) { var bb = this.get('boundingBox'), firstAdded = bb.one(SELECTORS.FIRSTADDED), firstUser; // Hide the lightbox. bb.one(SELECTORS.LIGHTBOX).addClass(CSS.HIDDEN); if (args.append && firstAdded) { // Sets the focus on the newly added user if we are appending results. this.setUserTabFocus(firstAdded); firstAdded.one(SELECTORS.USERSELECT).focus(); } else { // New search result, set the tab focus on the first user returned. firstUser = bb.one(SELECTORS.USER); if (firstUser) { this.setUserTabFocus(firstUser); } } }, /** * Process and display the search results. * * @method processSearchResults * @param {String} tid The transaction ID. * @param {Object} outcome The response object. * @param {Object} args The arguments passed from YUI.io(). */ processSearchResults: function(tid, outcome, args) { var result = false, error = false, bb = this.get('boundingBox'), users, userTemplate, count, selected, i, firstAdded = true, node, content, fetchmore, totalUsers; // Decodes the result. try { result = Y.JSON.parse(outcome.responseText); if (!result.success || result.error) { error = true; } } catch (e) { error = true; } // There was an error. if (error) { this.setContent(''); bb.one(SELECTORS.RESULTSCOUNT).setHTML(M.util.get_string('errajaxsearch', COMPONENT)); return; } // Create the div containing the users when it is a fresh search. if (!args.append) { users = Y.Node.create('
'); } else { users = bb.one(SELECTORS.RESULTSUSERS); } // Compile the template for each user node. if (!this._userTemplate) { this._userTemplate = Y.Handlebars.compile( '
' + '
' + '
' + '
' + '
{{{picture}}}
' + '
' + '
' + '
{{fullname}}
' + '
' + '
{{extrafields}}
' + '
' + '
' ); } userTemplate = this._userTemplate; // Append the users one by one. count = this.get(USP.USERCOUNT); selected = ''; var user; for (i in result.response.users) { count++; user = result.response.users[i]; // If already selected. if (Y.Object.hasKey(this._usersBufferList, user.userid)) { selected = true; } else { selected = false; } node = Y.Node.create(userTemplate({ checkboxId: Y.guid(), COMPONENT: COMPONENT, count: count, CSS: CSS, extrafields: user.extrafields, extraFieldsId: Y.guid(), fullname: user.fullname, picture: user.picture, userId: user.userid, USP: USP })); this.markUserNode(node, selected); // Noting the first user that was when adding more results. if (args.append && firstAdded) { users.all(SELECTORS.FIRSTADDED).removeClass(CSS.FIRSTADDED); node.addClass(CSS.FIRSTADDED); firstAdded = false; } users.append(node); } this.set(USP.USERCOUNT, count); // Update the count of users, and add a button to load more if need be. totalUsers = parseInt(result.response.totalusers, 10); if (!args.append) { if (totalUsers === 0) { bb.one(SELECTORS.RESULTSCOUNT).setHTML(M.util.get_string('noresults', 'moodle')); content = ''; } else { if (totalUsers === 1) { bb.one(SELECTORS.RESULTSCOUNT).setHTML(M.util.get_string('foundoneuser', COMPONENT)); } else { bb.one(SELECTORS.RESULTSCOUNT).setHTML(M.util.get_string('foundnusers', COMPONENT, totalUsers)); } content = Y.Node.create('
') .append(users); if (result.response.totalusers > (this.get(USP.PAGE) + 1) * this.get(USP.PERPAGE)) { fetchmore = Y.Node.create('
' + '
' + M.util.get_string('loadmoreusers', COMPONENT) + '
'); fetchmore.one('a').on('click', this.search, this, true); fetchmore.one('a').on('key', this.search, 'space', this, true); content.append(fetchmore); } } this.setContent(content); } else { if (totalUsers <= (this.get(USP.PAGE) + 1) * this.get(USP.PERPAGE)) { bb.one(SELECTORS.MORERESULTS).remove(); } } }, /** * When the user has finished selecting users. * * @method finishSelectingUsers * @param {EventFacade} e The event. */ finishSelectingUsers: function(e) { e.preventDefault(); this.applySelection(); this.hide(); }, /** * Apply the selection made. * * @method applySelection * @param {EventFacade} e The event. */ applySelection: function() { var userIds = Y.Object.keys(this._usersBufferList); this.set(USP.SELECTEDUSERS, Y.clone(this._usersBufferList)) .setNameDisplay(); Y.one(SELECTORS.USERIDS).set('value', userIds.join()); }, /** * Select a user. * * @method SelectUser * @param {EventFacade} e The event. */ selectUser: function(e) { var user = e.currentTarget.ancestor(SELECTORS.USER), checkbox = user.one(SELECTORS.USERSELECT), fullname = user.one(SELECTORS.FULLNAME).get('innerHTML'), checked = checkbox.get('checked'), userId = user.getData('userid'); if (e.currentTarget !== checkbox) { // We triggered the selection from another node, so we need to change the checkbox value. checked = !checked; } if (checked) { // Selecting the user. this._usersBufferList[userId] = fullname; } else { // De-selecting the user. delete this._usersBufferList[userId]; delete this._usersBufferList[parseInt(userId, 10)]; // Also remove numbered keys. } this.markUserNode(user, checked); }, /** * Mark a user node as selected or not. * * This only takes care of the DOM side of things, not the internal mechanism * storing what users have been selected or not. * * @param {Node} node The user node. * @param {Boolean} selected True to mark as selected. * @chainable */ markUserNode: function(node, selected) { if (selected) { node.addClass(CSS.SELECTED) .set('aria-selected', true) .one(SELECTORS.USERSELECT) .set('checked', true); } else { node.removeClass(CSS.SELECTED) .set('aria-selected', false) .one(SELECTORS.USERSELECT) .set('checked', false); } return this; }, /** * Set the content of the dialogue. * * @method setContent * @param {String} content The content. * @chainable */ setContent: function(content) { this.get('boundingBox').one(SELECTORS.AJAXCONTENT).setHTML(content); return this; }, /** * Display the names of the selected users in the form. * * @method setNameDisplay */ setNameDisplay: function() { var namelist = Y.Object.values(this.get(USP.SELECTEDUSERS)); Y.one(SELECTORS.SELECTEDNAMES).set('innerHTML', namelist.join(', ')); Y.one(SELECTORS.USERFULLNAMES).set('value', namelist.join()); }, /** * User keyboard navigation. * * @method userKeyboardNavigation */ userKeyboardNavigation: function(e) { var bb = this.get('boundingBox'), users = bb.all(SELECTORS.USER), direction = 1, user, current = e.target.ancestor(SELECTORS.USER, true); if (e.keyCode === 38) { direction = -1; } user = this.findFocusableUser(users, current, direction); if (user) { e.preventDefault(); user.one(SELECTORS.USERSELECT).focus(); this.setUserTabFocus(user); } }, /** * Find the next or previous focusable node. * * @param {NodeList} users The list of users. * @param {Node} user The user to start with. * @param {Number} direction The direction in which to go. * @return {Node|null} A user node, or null if not found. * @method findFocusableUser */ findFocusableUser: function(users, user, direction) { var index = users.indexOf(user); if (users.size() < 1) { Y.log('The users list is empty', 'debug', COMPONENT); return null; } if (index < 0) { Y.log('Unable to find the user in the list of users', 'debug', COMPONENT); return users.item(0); } index += direction; // Wrap the navigation when reaching the top of the bottom. if (index < 0) { index = users.size() - 1; } else if (index >= users.size()) { index = 0; } return users.item(index); }, /** * Set the user tab focus. * * @param {Node} user The user node. * @method setUserTabFocus */ setUserTabFocus: function(user) { if (this._userTabFocus) { this._userTabFocus.setAttribute('tabindex', '-1'); } if (!user) { // We were not passed a user, there is apparently none in the dialogue. Nothing to do here \\\o/. return; } this._userTabFocus = user.one(SELECTORS.USERSELECT); this._userTabFocus.setAttribute('tabindex', '0'); this.get('boundingBox').one(SELECTORS.RESULTSUSERS).setAttribute('aria-activedescendant', this._userTabFocus.generateID()); } }, { NAME: USP.NAME, CSS_PREFIX: USP.CSS_PREFIX, ATTRS: { /** * The header. * * @attribute title * @default selectusers language string. * @type String */ title: { validator: Y.Lang.isString, valueFn: function() { return M.util.get_string('selectusers', COMPONENT); } }, /** * The current page URL. * * @attribute url * @default null * @type String */ url: { validator: Y.Lang.isString, value: null }, /** * The URL to the Ajax file. * * @attribute ajaxurl * @default null * @type String */ ajaxurl: { validator: Y.Lang.isString, value: null }, /** * The names of the selected users. * * The keys are the user IDs, the values are their fullname. * * @attribute selectedUsers * @default null * @type Object */ selectedUsers: { validator: Y.Lang.isObject, value: null, getter: function(v) { if (v === null) { return {}; } return v; } }, /** * The course ID. * * @attribute courseid * @default null * @type Number */ courseid: { value: null }, /** * Array of parameters. * * @attribute params * @default [] * @type Array */ params: { validator: Y.Lang.isArray, value: [] }, /** * The page we are on. * * @attribute page * @default 0 * @type Number */ page: { validator: Y.Lang.isNumber, value: 0 }, /** * The number of users displayed. * * @attribute userCount * @default 0 * @type Number */ userCount: { value: 0, validator: Y.Lang.isNumber }, /** * The number of results per page. * * @attribute perPage * @default 25 * @type Number */ perPage: { value: 25, Validator: Y.Lang.isNumber } } }); Y.Base.modifyAttrs(Y.namespace('M.gradereport_history.UserSelector'), { /** * List of extra classes. * * @attribute extraClasses * @default ['gradereport_history_usp'] * @type Array */ extraClasses: { value: [ 'gradereport_history_usp' ] }, /** * Whether to focus on the target that caused the Widget to be shown. * * @attribute focusOnPreviousTargetAfterHide * @default true * @type Node */ focusOnPreviousTargetAfterHide: { value: true }, /** * * Width. * * @attribute width * @default '500px' * @type String|Number */ width: { value: '500px' }, /** * Boolean indicating whether or not the Widget is visible. * * @attribute visible * @default false * @type Boolean */ visible: { value: false }, /** * Whether the widget should be modal or not. * * @attribute modal * @type Boolean * @default true */ modal: { value: true }, /** * Whether the widget should be draggable or not. * * @attribute draggable * @type Boolean * @default true */ draggable: { value: true } }); Y.namespace('M.gradereport_history.UserSelector').init = function(cfg) { return new USERSELECTOR(cfg); };