diff --git a/lib/amd/build/fragment.min.js b/lib/amd/build/fragment.min.js
new file mode 100644
index 00000000000..15ce5ec2ff4
--- /dev/null
+++ b/lib/amd/build/fragment.min.js
@@ -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('
'),a(f).append("
"),a("#fragment-html").append(b.html),a(g).append(''),a(g).append("
"),a("#ajax-import-scripts").append(b.javascript)})}}});
\ No newline at end of file
diff --git a/lib/amd/src/fragment.js b/lib/amd/src/fragment.js
new file mode 100644
index 00000000000..bcefdcf2bdc
--- /dev/null
+++ b/lib/amd/src/fragment.js
@@ -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 .
+
+/**
+ * A way to call HTML fragments to be inserted as required via JavaScript.
+ *
+ * @module core/fragment
+ * @class fragment
+ * @package core
+ * @copyright 2016 Adrian Greeve
+ * @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("");
+ $(htmlnodeidentifier).append("
");
+ $('#fragment-html').append(data.html);
+
+ $(javascriptnodeidentifier).append("");
+ $(javascriptnodeidentifier).append("
");
+ $('#ajax-import-scripts').append(data.javascript);
+ });
+ }
+ };
+});
\ No newline at end of file
diff --git a/lib/db/services.php b/lib/db/services.php
index bc3230f3f4d..6707877aec8 100644
--- a/lib/db/services.php
+++ b/lib/db/services.php
@@ -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 ===
diff --git a/lib/external/externallib.php b/lib/external/externallib.php
index a0526c53379..325f36027ac 100644
--- a/lib/external/externallib.php
+++ b/lib/external/externallib.php
@@ -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')
+ )
+ );
+ }
}
diff --git a/lib/outputfragmentrequirementslib.php b/lib/outputfragmentrequirementslib.php
new file mode 100644
index 00000000000..235092be41b
--- /dev/null
+++ b/lib/outputfragmentrequirementslib.php
@@ -0,0 +1,191 @@
+.
+
+/**
+ * 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
+ * @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
+ *
+ * $PAGE->requires->js_call_amd('mod_forum/view', 'init');
+ *
+ *
+ * It also supports obsoleted coding style with/without YUI3 modules.
+ *
+ * $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);
+ *
+ *
+ * There are some natural restrictions on some methods. For example, {@link css()}
+ * can only be called before the tag is output. See the comments on the
+ * individual methods for details.
+ *
+ * @copyright 2016 Adrian Greeve
+ * @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;
+
+ }
+}
diff --git a/lib/outputlib.php b/lib/outputlib.php
index 64e02997da4..e438f718017 100644
--- a/lib/outputlib.php
+++ b/lib/outputlib.php
@@ -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.
diff --git a/lib/pagelib.php b/lib/pagelib.php
index d46b77d8f54..3fcf08e6875 100644
--- a/lib/pagelib.php
+++ b/lib/pagelib.php
@@ -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