* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.1
*/
-define(['jquery', 'core/ajax'], function($, ajax) {
+define(['jquery', 'core/ajax', 'core/notification'], function($, ajax, notification) {
/**
* Loads an HTML fragment through a callback.
*
- * @method load_fragment
+ * @method loadFragment
* @param {string} component Component where callback is located.
* @param {string} callback Callback function name.
+ * @param {integer} contextid Context ID of the fragment.
* @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) {
+ var loadFragment = function(component, callback, contextid, params) {
// Change params into required webservice format.
var formattedparams = [];
for (var index in params) {
args:{
component: component,
callback: callback,
+ contextid: contextid,
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) {
return deferred.promise();
};
+ /**
+ * Removes and cleans children of a node. This includes event handlers and listeners that may be
+ * attached to the nodes for both jquery and yui.
+ *
+ * @method recursiveCleanup
+ * @param {object} DOM node to be cleaned.
+ * @return {void}
+ */
var recursiveCleanup = function(node) {
node.children().each(function(index, el) {
var child = $(el);
if (yuinode.get('childNodes')) {
yuinode.empty();
}
- yuinode.remove();
+ yuinode.remove(true);
};
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.
+ * Callbacks called by this AMD module are responsible for doing the appropriate security checks
+ * to access the information that is returned. This only does minimal validation on the context.
*
- * @method fragment_append
+ * @method fragmentAppend
* @param {string} component Component where callback is located.
* @param {string} callback Callback function name.
+ * @param {integer} contextid Context ID of the fragment.
* @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.
- recursiveCleanup($('#fragment-html'));
- recursiveCleanup($('#ajax-import-scripts'));
-
- $.when(load_fragment('mod_assign', "fragment", params)).then(function(data) {
+ fragmentAppend: function(component, callback, contextid, params, htmlnodeidentifier, javascriptnodeidentifier) {
+ $.when(loadFragment(component, callback, contextid, params)).then(function(data) {
+ // Clean up previous code if found first.
+ recursiveCleanup($('#fragment-html'));
+ recursiveCleanup($('#fragment-scripts'));
// Attach new HTML and JavaScript.
- $(htmlnodeidentifier).append("<div id=\"fragment-html\">");
- $(htmlnodeidentifier).append("</div>");
- $('#fragment-html').append(data.html);
+ $(htmlnodeidentifier).append('<div id="fragment-html">' + data.html + '</div>');
+ $(javascriptnodeidentifier).append('<div id="fragment-scripts">' + data.javascript + '</div>');
- $(javascriptnodeidentifier).append("<div id=\"ajax-import-scripts\">");
- $(javascriptnodeidentifier).append("</div>");
- $('#ajax-import-scripts').append(data.javascript);
+ }).fail(function(ex) {
+ notification.exception(ex);
});
}
};
'classpath' => 'lib/external/externallib.php',
'description' => 'Return a fragment for inclusion, such as a JavaScript page.',
'type' => 'read',
- 'loginrequired' => false,
'ajax' => true,
),
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'),
+ 'component' => new external_value(PARAM_COMPONENT, 'Component for the callback e.g. mod_assign'),
+ 'callback' => new external_value(PARAM_ALPHANUMEXT, 'Name of the callback to execute'),
+ 'contextid' => new external_value(PARAM_INT, 'Context ID that the fragment is from'),
'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')
+ 'value' => new external_value(PARAM_RAW, '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.
+ * This web service is designed to be called only via AJAX and not directly.
+ * Callbacks that are called by this web service are responsible for doing the appropriate security checks
+ * to access the information returned. This only does minimal validation on the context.
*
* @param string $component Name of the component.
* @param string $callback Function callback name.
+ * @param int $contextid Context ID this fragment is in.
* @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) {
+ public static function get_fragment($component, $callback, $contextid, $args = null) {
global $OUTPUT, $PAGE;
$params = self::validate_parameters(self::get_fragment_parameters(),
array(
'component' => $component,
'callback' => $callback,
+ 'contextid' => $contextid,
'args' => $args
)
);
$arguments[$paramargument['name']] = $paramargument['value'];
}
- // Remove warning about context not being set.
- $PAGE->set_context(context_system::instance());
+ $context = context::instance_by_id($contextid);
+ self::validate_context($context);
// Hack alert: Forcing bootstrap_renderer to initiate moodle page.
$OUTPUT->header();
// Overwriting page_requirements_manager with the fragment one so only JS included from
// this point is returned to the user.
- $PAGE->set_requirements_for_fragments();
- $data = component_callback($params['component'], $params['callback'], $arguments);
+ $PAGE->start_collecting_javascript_requirements();
+ $data = component_callback($params['component'], 'output_fragment_' . $params['callback'], $arguments);
$jsfooter = $PAGE->requires->get_end_code();
$output = array('html' => $data, 'javascript' => $jsfooter);
return $output;
/**
* 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
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.
+ * This requirements manager captures the appropriate html for creating a fragment to
+ * be inserted elsewhere.
*
* @copyright 2016 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
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
+ * Page fragment constructor.
*/
- 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;
+ public function __construct() {
+ parent::__construct();
+ // As this is a fragment the header should already be done.
+ $this->headdone = true;
}
-
/**
* 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() {
/**
* 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() {
// 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) {
}
}
- // Add all needed strings.
- $strings = array();
- foreach ($this->stringsforjs as $component => $v) {
- foreach ($v as $indentifier => $langstring) {
- $strings[$component][$indentifier] = $langstring->out();
+ if (!empty($this->stringsforjs)) {
+ // 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('require(["jquery"], function($) {
+ M.str = $.extend(true, M.str, ' . json_encode($strings) . ');
+ });');
}
- // Append don't overwrite.
- $output .= html_writer::script('require(["jquery"], function($) {
- M.str = $.extend(true, M.str, ' . json_encode($strings) . ');
-});');
// Add variables.
if ($this->jsinitvariables['footer']) {
$output .= html_writer::script($js);
- // return 'bottom stuff';
return $output;
-
}
}
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.
}
/**
- * Changes the requirements manager over to receive fragments.
+ * Switches from the regular requirements manager to the fragment requirements manager to
+ * capture all necessary JavaScript to display a chunk of HTML such as an mform. This is for use
+ * by the get_fragment() web service and not for use elsewhere.
*/
- public function set_requirements_for_fragments() {
- $this->_requires = new fragment_requirements_manager();
+ public function start_collecting_javascript_requirements() {
+ global $CFG;
+ require_once($CFG->libdir.'/outputfragmentrequirementslib.php');
+
+ // Check that the requirements manager has not already been switched.
+ if (get_class($this->_requires) == 'fragment_requirements_manager') {
+ throw new coding_exception('JavaScript collection has already been started.');
+ }
+ // The header needs to have been called to flush out the generic JavaScript for the page. This allows only
+ // JavaScript for the fragment to be collected. _wherethemewasinitialised is set when header() is called.
+ if (!empty($this->_wherethemewasinitialised)) {
+ // Change the current requirements manager over to the fragment manager to capture JS.
+ $this->_requires = new fragment_requirements_manager();
+ } else {
+ throw new coding_exception('$OUTPUT->header() needs to be called before collecting JavaScript requirements.');
+ }
}
/**
defined('MOODLE_INTERNAL') || die();
-$version = 2016012900.00; // YYYYMMDD = weekly release date of this DEV branch.
+$version = 2016012900.01; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.