Index: lams_central/web/includes/javascript/jinplace-1.0.1.js =================================================================== diff -u --- lams_central/web/includes/javascript/jinplace-1.0.1.js (revision 0) +++ lams_central/web/includes/javascript/jinplace-1.0.1.js (revision 9f9c86b114e598873485e958c645757b89b9897b) @@ -0,0 +1,726 @@ +/** @preserve Copyright © 2013, Itinken Limited. + * MIT Licence */ +/* + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * See (http://jquery.com/). + * @name jQuery + * @class + * See the jQuery Library (http://jquery.com/) for full details. This just + * documents the function and classes that are added to jQuery by this plug-in. + */ + +/** + * See (http://jquery.com/) + * @name fn + * @class + * See the jQuery Library (http://jquery.com/) for full details. This just + * documents the function and classes that are added to jQuery by this plug-in. + * @memberOf jQuery + */ + +//noinspection JSUnnecessarySemicolon +; +//noinspection JSUnusedLocalSymbols +(function ($, window, document, undefined) { + 'use strict'; + var pluginName = "jinplace"; + + /** + * @typedef {object} Options + * @class Options + * @property {!string} type - The type of field. Defaults to 'input' + * @property {string} url - The url to submit to. Defaults to same page + * @property {string} data - Text or JSON data as initial editing text + * @property {string} loadurl - The URL to load content for editing + * @property {string} elementId - The ID of the element + * @property {string} object - A name to pass back on submit + * @property {string} attribute - Another name to pass back on submit + * @property {string} okButton - Create a submit button with this name + * @property {string} cancelButton - Create a cancel button with this name + * @property {string} inputClass - A css class that is added to the input field + * @property {jQuery|string} activator - Object (or css selector) for object to activate editing. Defaults to the element itself. + * @property {boolean} textOnly - When true (the default) text returned from server is displayed literally and not as html. + * @property {string} placeholder - Text to display in empty elements. + * @property {submitFunction} submitFunction - Function that is called to submit the new value. + * @property {loadFunction} loadFunction - Function that is called to load the editing data + */ + + var option_list = ['type', + 'url', + 'data', + 'loadurl', + 'elementId', + 'object', + 'attribute', + 'okButton', + 'cancelButton', + 'checkboxLabel', + 'inputClass', + 'activator', + 'textOnly', + 'placeholder', + 'submitFunction' + ]; + + /** + * The actual constructor of the JinPlace object. + * + * @class jinplace + * @memberOf jQuery.fn + * @constructor + * + * @property {jQuery} element - The element containing plain text to be edited. + * @property {Options} opts - The final set of options. + */ + function JinPlace(element, options) { + var $el = this.element = $(element); // The editable element (often a span or div). + + var elementOptions = this.elementOptions($el); + + var act = elementOptions.activator || element; + elementOptions.activator = $(act); + + // So we have 1) options defined in defaults, 2) passed into the plugin, 3) set + // on the element. Combine all these together. + var opts = $.extend({}, + $.fn[pluginName].defaults, + options, + elementOptions); + + this.opts = opts; + + this.bindElement(opts); + } + + JinPlace.prototype = { + + /** + * Get the options that are set on the editable element with the data-* attributes. + * + * @param {jQuery} $el The element that is being made editable. + */ + elementOptions: function ($el) { + var opts = {}; + function upperToHyphenLower(match) { + return '-' + match.toLowerCase(); + } + + function make_attr_name(value) { + return "data-" + value.replace(/[A-Z]/g, upperToHyphenLower); + } + + $.each(option_list, function(index, value) { + opts[value] = $el.attr(make_attr_name(value)); + }); + + opts.elementId = $el.attr('id'); + + if (opts.textOnly) + opts.textOnly = opts.textOnly !== 'false'; + + return opts; + }, + + /** + * Prepare the activator element to receive click events. + * + * This involves setting placeholder text if the element is empty. + * + * @param {Options} opts - The editor options. + */ + bindElement: function(opts) { + // Remove any existing handler we set and bind to the activation click handler. + opts.activator + .off('click.jip') + .on('click.jip', $.proxy(this.clickHandler, this)); + + // If there is no content, then we replace it with the empty indicator. + var $el = this.element; + if ($.trim($el.html()) == "") { + $el.html(opts.placeholder); + + // In IE<9 the html is made uppercase which means it no longer matches what think the text is. + // So we retrieve the html. + opts.placeholder = $el.html(); + } + }, + + /** + * Handle a click that is activating the element. This click can be on any element + * so is not directly useful. Things are always set up so that 'this' is this object + * and not the element that the click occurred on. + * + * @this {JinPlace} + * @param ev The event. + */ + clickHandler: function(ev) { + ev.preventDefault(); + ev.stopPropagation(); + + // Turn off the activation handler, and disable any effect in case the activator + // was a button that might submit. + $(ev.currentTarget) + .off('click.jip') + .on('click.jip', function(ev) { + //*LAMS* customized + if ((ev.target.id == "notify-learner") || (ev.target.id == "notify-learner-label")) { + return; + } + ev.preventDefault(); + }); + + var self = this, + opts = self.opts; + + /** A new editor is created for every activation. So it is OK to keep instance + * data on it. + * @type {editorBase} + */ + var editor = $.extend({}, editorBase, $.fn[pluginName].editors[opts.type]); + + // Save original for use when cancelling. + self.origValue = self.element.html(); + + self.fetchData(opts).done(function(data) { + + var field = editor.makeField(self.element, data); + if (!editor.inputField) + editor.inputField = field; + field.addClass(opts.inputClass); + + var form = createForm(opts, field, editor.buttonsAllowed); + + // Add the form to the element to be edited + self.element.html(form); + + // Now we can setup handlers and focus or otherwise activate the field. + + form + .on("jip:submit submit", function(ev) { + //*LAMS* modified the next 2 lines + opts.isNotifyLearner = $(this).find('#notify-learner').prop("checked"); + self.submit(editor, opts); + return false; + }) + .on("jip:cancel", function(ev) { + self.cancel(editor); + return false; + }) + .on("keyup", function(ev) { + if (ev.keyCode == 27) { + self.cancel(editor); + } + }); + + editor.activate(form, field); + + // The action to take on blur can be set on the editor. If not, and there + // are automatically added buttons, then the blur action is set according to + // which ones exist. By default nothing happens on blur. + var act = editor.blurAction || ( + (!opts.okButton)? 'submit': + (!opts.cancelButton)? 'jip:cancel': + undefined); + editor.blurEvent(field, form, act); + }); + }, + + /** + * Fetch the data that will be placed into the editing control. The data is + * obtained from the following sources in this order: + * 1. data-data (or options.data) + * 2. data-loadurl (or options.loadurl) a request is made to the given url and the + * resulting data is used. + * 3. The existing contents of 'element'. + * + * @param {Options} opts + */ + fetchData: function(opts) { + var data; + if (opts.data) { + data = opts.data; + + } else if (opts.loadurl) { + data = opts.loadFunction(opts); + } else { + data = $.trim(this.element.html()); + } + + var placeholderFilter = function (data) { + if (data == opts.placeholder) + return ''; + return data; + }; + + var when = $.when(data); + if (when.pipe) { + return when.pipe(placeholderFilter); + } else { + return when.then(placeholderFilter); + } + }, + + /** + * Throw away any edits and return the element to its original text. + * + * @param {editorBase} editor The element editor. + * @return {void} + */ + cancel: function(editor) { + var self = this; + self.element.html(self.origValue); + + editor.finish(); + + // Rebind the element for the next time + self.bindElement(self.opts); + }, + + /** + * + * Called to submit the changed data to the server. + * + * This method is always called with 'this' set to this object. + * + * @this {JinPlace} + * @param {editorBase} editor + * @param {Options} opts + */ + submit: function (editor, opts) { + var self = this; + var rval; + var rejected = $.Deferred().reject(); + + // Since the function is user defined protect against exceptions and + // returning nothing. Either problem causes the edit to be cancelled. + // Of course it is possible that some action has been taken depending + // on why the exception was thrown, but there is no way to know that. + try { + + opts.submitFunction.call(undefined, opts, editor.value()); + } catch (e) { + } + + //*LAMS* modified this part + self.onUpdate(editor, opts, editor.value()); + }, + + /** + * The server has received our data and replied successfully and the new data to + * be displayed is available. + * + * @param {editorBase} editor The element editor. + * @param {Options} opts The element options. + * @param {string} data The data to display from the server. + */ + onUpdate: function(editor, opts, data) { + var self = this; + self.setContent(data); + editor.finish(); + self.element.trigger('jinplace:done', [data]); + self.bindElement(opts); + }, + + /** + * Set the content of the element. Called to update the value from the value + * returned by the server. + * + * @param data The data to be displayed, it has been converted to the display format. + */ + setContent: function(data) { + var element = this.element; + + if (!data) { + element.html(this.opts.placeholder); + return; + } + + if (this.opts.textOnly) { + element.text(data); + } else { + element.html(data); + } + } + + }; + + /** + * Get the parameters that will be sent in the ajax call to the server. + * Called for both the url and loadurl cases. + * + * @param {Options} opts The options from the element and config settings. + * @param {*=} [value] The value of the control as returned by editor.value(). + * @returns {object} + */ + var requestParams = function (opts, value) { + var params = { + "id": opts.elementId, + "object": opts.object, + attribute: opts.attribute, + //*LAMS* added the next line + "isNotifyLearner":opts.isNotifyLearner + }; + + if ($.isPlainObject(value)) { + $.extend(params, value); + } else if (value !== undefined) { + params.value = value; + } + + return params; + }; + + // A really lightweight plugin wrapper around the constructor, + // preventing against multiple instantiations + $.fn[pluginName] = function (options) { + return this.each(function () { + if (!$.data(this, "plugin_" + pluginName)) { + $.data(this, "plugin_" + pluginName, new JinPlace(this, options)); + } + }); + }; + + /** These are the plugin defaults. You can override these if required. + * @type {Options} + */ + $.fn[pluginName].defaults = { + url: document.location.pathname, + type: "input", + textOnly: 2, + placeholder: '[ --- ]', + + /** + * + * @name Options.submitFunction + * + * The function to call when an editor form is submitted. This can be supplied as an + * option to completely change the default action. + * + * @callback submitFunction + * @param {Options} opts The options for this element. + * @param {string} value The value that was submitted. + * @returns {string|object} Returns a string which will be used to populate the element text or + * a promise that will resolve to a string. + */ + submitFunction: function(opts, value) { + return $.ajax(opts.url, { + type: "post", + data: requestParams(opts, value), + dataType: 'text', + + // iOS 6 has a dreadful bug where POST requests are not sent to the + // server if they are in the cache. + headers: {'Cache-Control': 'no-cache'} // Apple! + }); + }, + + /** + * @name Options.loadFunction + * + * @callback loadFunction + * @param {Options} opts + * @returns {string} + */ + loadFunction: function(opts) { + return $.ajax(opts.loadurl, { + data: requestParams(opts) + }); + } + }; + + /** + * Create a form for the editing area. The input element is added and if buttons + * are required then they are added. Event handlers are set up. + * + * @param {Options} opts The options for this editor. + * @param {jQuery} inputField The newly created input field. + * @param {boolean} [buttons] True if buttons can be added. Whether buttons really are added + * depends on the options and data-* attributes. + * @returns {jQuery} The newly created form element. + */ + var createForm = function (opts, inputField, buttons) { + var form = $("