MDL-52715 lib: Addition of new requirements class.

This adds a fragment requirements class to allow plugins
to register JavaScript to be used in an mform.
This commit is contained in:
Adrian Greeve 2016-01-11 15:54:43 +08:00
parent c9d91bb734
commit cc73ea075e
7 changed files with 401 additions and 0 deletions

1
lib/amd/build/fragment.min.js vendored Normal file
View File

@ -0,0 +1 @@
define(["jquery","core/ajax"],function(a,b){var c=function(c,d,e){var f=[];for(var g in e)f.push({name:g,value:e[g]});var h=a.Deferred(),i=b.call([{methodname:"core_get_fragment",args:{component:c,callback:d,args:f}}],!1);return i[0].done(function(a){h.resolve(a)}).fail(function(a){h.reject(a)}),h.promise()};return{fragment_load:function(a,b,d){return c(a,b,d)},fragment_append:function(b,d,e,f,g){a("#fragment-html").empty(),Y.on("#fragment-html").detach(),a("#fragment-html").remove(),a("#ajax-import-scripts").empty(),Y.on("#ajax-import-scripts").detach(),a("#ajax-import-scripts").remove(),a.when(c("mod_assign","fragment",e)).then(function(b){a(f).append('<div id="fragment-html">'),a(f).append("</div>"),a("#fragment-html").append(b.html),a(g).append('<div id="ajax-import-scripts">'),a(g).append("</div>"),a("#ajax-import-scripts").append(b.javascript)})}}});

116
lib/amd/src/fragment.js Normal file
View File

@ -0,0 +1,116 @@
// 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/>.
/**
* A way to call HTML fragments to be inserted as required via JavaScript.
*
* @module core/fragment
* @class fragment
* @package core
* @copyright 2016 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.1
*/
define(['jquery', 'core/ajax'], function($, ajax) {
/**
* Loads an HTML fragment through a callback.
*
* @method load_fragment
* @param {string} component Component where callback is located.
* @param {string} callback Callback function name.
* @param {object} params Parameters for the callback.
* @return {Promise} JQuery promise object resolved when the fragment has been loaded.
*/
var load_fragment = function(component, callback, params) {
// Change params into required webservice format.
var formattedparams = [];
for (var index in params) {
formattedparams.push({name: index, value: params[index]});
}
// Ajax stuff.
var deferred = $.Deferred();
var promises = ajax.call([{
methodname: 'core_get_fragment',
args:{
component: component,
callback: callback,
args: formattedparams
}
}], false);
// Worth noting somewhere that the assign module seems to require userid, rownum etc. to be passed via POST / GET.
promises[0].done(function(data) {
deferred.resolve(data);
}).fail(function(ex) {
deferred.reject(ex);
});
return deferred.promise();
};
return /** @alias module:core/fragment */{
/**
* Loads an HTML fragment through a callback.
*
* @method fragment_load
* @param {string} component Component where callback is located.
* @param {string} callback Callback function name.
* @param {object} params Parameters for the callback.
* @return {Promise} JQuery promise object resolved when the fragment has been loaded.
*/
fragment_load: function(component, callback, params) {
return load_fragment(component, callback, params);
},
/**
* Appends HTML and JavaScript fragments to specified nodes.
*
* @method fragment_append
* @param {string} component Component where callback is located.
* @param {string} callback Callback function name.
* @param {object} params Parameters for the callback.
* @param {string} htmlnodeidentifier The 'class' or 'id' to attach the HTML.
* @param {string} javascriptnodeidentifier The 'class' or 'id' to attach the JavaScript.
* @return {void}
*/
fragment_append: function(component, callback, params, htmlnodeidentifier, javascriptnodeidentifier) {
// Clean up previous code if found first.
$('#fragment-html').empty();
Y.on('#fragment-html').detach();
$('#fragment-html').remove();
$('#ajax-import-scripts').empty();
Y.on('#ajax-import-scripts').detach();
$('#ajax-import-scripts').remove();
// $(".moodle-dialogue-base").empty();
// $(".moodle-dialogue-base").remove();
$.when(load_fragment('mod_assign', "fragment", params)).then(function(data) {
// Attach new HTML and JavaScript.
$(htmlnodeidentifier).append("<div id=\"fragment-html\">");
$(htmlnodeidentifier).append("</div>");
$('#fragment-html').append(data.html);
$(javascriptnodeidentifier).append("<div id=\"ajax-import-scripts\">");
$(javascriptnodeidentifier).append("</div>");
$('#ajax-import-scripts').append(data.javascript);
});
}
};
});

View File

@ -1051,6 +1051,16 @@ $functions = array(
'ajax' => true,
),
'core_get_fragment' => array(
'classname' => 'core_external',
'methodname' => 'get_fragment',
'classpath' => 'lib/external/externallib.php',
'description' => 'Return a fragment for inclusion, such as a JavaScript page.',
'type' => 'read',
'loginrequired' => false,
'ajax' => true,
),
// === Calendar related functions ===

View File

@ -260,4 +260,79 @@ class core_external extends external_api {
'string' => new external_value(PARAM_RAW, 'translated string'))
));
}
/**
* Returns description of get_fragment parameters
*
* @return external_function_parameters
* @since Moodle 3.1
*/
public static function get_fragment_parameters() {
return new external_function_parameters(
array(
'component' => new external_value(PARAM_RAW, 'Component for the callback e.g. mod_asign'),
'callback' => new external_value(PARAM_RAW, 'Name of the callback to execute'),
'args' => new external_multiple_structure(
new external_single_structure(
array(
'name' => new external_value(PARAM_ALPHANUMEXT, 'param name'),
'value' => new external_value(PARAM_TEXT, 'param value')
)
), 'args for the callback are optional', VALUE_OPTIONAL
)
)
);
}
/**
* Get a HTML fragment for inserting into something. Initial use is for inserting mforms into
* a page using AJAX.
*
* @param string $component Name of the component.
* @param string $callback Function callback name.
* @param array $args optional arguments for the callback.
* @return array HTML and JavaScript fragments for insertion into stuff.
* @since Moodle 3.1
*/
public static function get_fragment($component, $callback, $args = null) {
global $PAGE;
$params = self::validate_parameters(self::get_fragment_parameters(),
array(
'component' => $component,
'callback' => $callback,
'args' => $args
)
);
// Reformat arguments into something less unwieldy.
$arguments = array();
foreach ($params['args'] as $paramargument) {
$arguments[$paramargument['name']] = $paramargument['value'];
}
// Remove warning about context not being set.
$PAGE->set_context(context_system::instance());
$PAGE->set_requirements_for_fragments();
$data = component_callback($params['component'], $params['callback'], $arguments);
$jsfooter = $PAGE->requires->get_end_code();
$output = array('html' => $data, 'javascript' => $jsfooter);
return $output;
}
/**
* Returns description of get_fragment() result value
*
* @return array
* @since Moodle 3.1
*/
public static function get_fragment_returns() {
return new external_single_structure(
array(
'html' => new external_value(PARAM_RAW, 'HTML fragment.'),
'javascript' => new external_value(PARAM_RAW, 'JavaScript fragment')
)
);
}
}

View File

@ -0,0 +1,191 @@
<?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/>.
/**
* Library functions to facilitate the use of JavaScript in Moodle.
*
* Note: you can find history of this file in lib/ajax/ajaxlib.php
*
* @copyright 2016 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @package core
* @category output
*/
defined('MOODLE_INTERNAL') || die();
/**
* This class tracks all the things that are needed by the current page.
*
* Normally, the only instance of this class you will need to work with is the
* one accessible via $PAGE->requires.
*
* Typical usage would be
* <pre>
* $PAGE->requires->js_call_amd('mod_forum/view', 'init');
* </pre>
*
* It also supports obsoleted coding style with/without YUI3 modules.
* <pre>
* $PAGE->requires->js_init_call('M.mod_forum.init_view');
* $PAGE->requires->css('/mod/mymod/userstyles.php?id='.$id); // not overridable via themes!
* $PAGE->requires->js('/mod/mymod/script.js');
* $PAGE->requires->js('/mod/mymod/small_but_urgent.js', true);
* $PAGE->requires->js_function_call('init_mymod', array($data), true);
* </pre>
*
* There are some natural restrictions on some methods. For example, {@link css()}
* can only be called before the <head> tag is output. See the comments on the
* individual methods for details.
*
* @copyright 2016 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 3.1
* @package core
* @category output
*/
class fragment_requirements_manager extends page_requirements_manager {
/**
* Append YUI3 module to default YUI3 JS loader.
* The structure of module array is described at {@link http://developer.yahoo.com/yui/3/yui/}
*
* @param string|array $module name of module (details are autodetected), or full module specification as array
* @return void
*/
public function js_module($module) {
global $CFG;
if (empty($module)) {
throw new coding_exception('Missing YUI3 module name or full description.');
}
if (is_string($module)) {
$module = $this->find_module($module);
}
if (empty($module) or empty($module['name']) or empty($module['fullpath'])) {
throw new coding_exception('Missing YUI3 module details.');
}
$module['fullpath'] = $this->js_fix_url($module['fullpath'])->out(false);
// Add all needed strings.
if (!empty($module['strings'])) {
foreach ($module['strings'] as $string) {
$identifier = $string[0];
$component = isset($string[1]) ? $string[1] : 'moodle';
$a = isset($string[2]) ? $string[2] : null;
$this->string_for_js($identifier, $component, $a);
}
}
unset($module['strings']);
// Process module requirements and attempt to load each. This allows
// moodle modules to require each other.
if (!empty($module['requires'])) {
foreach ($module['requires'] as $requirement) {
$rmodule = $this->find_module($requirement);
if (is_array($rmodule)) {
$this->js_module($rmodule);
}
}
}
$this->extramodules[$module['name']] = $module;
}
/**
* Returns js code to load amd module loader, then insert inline script tags
* that contain require() calls using RequireJS.
* @return string
*/
protected function get_amd_footercode() {
global $CFG;
$output = '';
// First include must be to a module with no dependencies, this prevents multiple requests.
$prefix = "require(['core/first'], function() {\n";
$suffix = "\n});";
$output .= html_writer::script($prefix . implode(";\n", $this->amdjscode) . $suffix);
return $output;
}
/**
* Generate any HTML that needs to go at the end of the page.
*
* Normally, this method is called automatically by the code that prints the
* page footer. You should not normally need to call it in your own code.
*
* @return string the HTML code to to at the end of the page.
*/
public function get_end_code() {
global $CFG;
$output = '';
// Call amd init functions.
$output .= $this->get_amd_footercode();
// Add other requested modules.
$output .= $this->get_extra_modules_code();
$this->js_init_code('M.util.js_complete("init");', true);
// All the other linked scripts - there should be as few as possible.
if ($this->jsincludes['footer']) {
foreach ($this->jsincludes['footer'] as $url) {
$output .= html_writer::script('', $url);
}
}
// Add all needed strings.
$strings = array();
foreach ($this->stringsforjs as $component => $v) {
foreach ($v as $indentifier => $langstring) {
$strings[$component][$indentifier] = $langstring->out();
}
}
// Append don't overwrite.
$output .= html_writer::script(js_writer::set_variable('M.str', $strings));
// Add variables.
if ($this->jsinitvariables['footer']) {
$js = '';
foreach ($this->jsinitvariables['footer'] as $data) {
list($var, $value) = $data;
$js .= js_writer::set_variable($var, $value, true);
}
$output .= html_writer::script($js);
}
$inyuijs = $this->get_javascript_code(false);
$ondomreadyjs = $this->get_javascript_code(true);
// See if this is still needed when we get to the ajax page.
$jsinit = $this->get_javascript_init_code();
$handlersjs = $this->get_event_handler_code();
// There is a global Y, make sure it is available in your scope.
$js = "(function() {{$inyuijs}{$ondomreadyjs}{$jsinit}{$handlersjs}})();";
$output .= html_writer::script($js);
// return 'bottom stuff';
return $output;
}
}

View File

@ -33,6 +33,7 @@ require_once($CFG->libdir.'/outputactions.php');
require_once($CFG->libdir.'/outputfactories.php');
require_once($CFG->libdir.'/outputrenderers.php');
require_once($CFG->libdir.'/outputrequirementslib.php');
require_once($CFG->libdir.'/outputfragmentrequirementslib.php');
/**
* Invalidate all server and client side caches.

View File

@ -824,6 +824,13 @@ class moodle_page {
return $this->_navbar->has_items();
}
/**
* Changes the requirements manager over to receive fragments.
*/
public function set_requirements_for_fragments() {
$this->_requires = new fragment_requirements_manager();
}
/**
* Should the current user see this page in editing mode.
* That is, are they allowed to edit this page, and are they currently in