MDL-35819 AJAX Rewrite Moodle popup help to be more friendly and more efficient
authorAndrew Robert Nicols <andrew.nicols@luns.net.uk>
Wed, 10 Oct 2012 10:03:00 +0000 (11:03 +0100)
committerAndrew Robert Nicols <andrew.nicols@luns.net.uk>
Sun, 10 Feb 2013 11:50:56 +0000 (11:50 +0000)
help.php
lang/en/moodle.php
lib/javascript-static.js
lib/outputrenderers.php
lib/upgrade.txt
lib/yui/popuphelp/popuphelp.js [new file with mode: 0644]
lib/yui/tooltip/tooltip.js [new file with mode: 0644]
theme/base/style/core.css
theme/canvas/style/core.css
theme/formal_white/style/formal_white.css
theme/splash/style/core.css

index 6afdd97..4323461 100644 (file)
--- a/help.php
+++ b/help.php
@@ -48,15 +48,15 @@ $PAGE->set_context(context_system::instance());
 
 if ($ajax) {
     @header('Content-Type: text/plain; charset=utf-8');
-} else {
-    echo $OUTPUT->header();
 }
 
 if (!$sm->string_exists($identifier.'_help', $component)) {
-    // strings on-diskc cache may be dirty - try to rebuild it and check again
+    // strings on disk-cache may be dirty - try to rebuild it and check again
     $sm->load_component_strings($component, current_language(), true);
 }
 
+$data = new stdClass();
+
 if ($sm->string_exists($identifier.'_help', $component)) {
     $options = new stdClass();
     $options->trusted = false;
@@ -67,26 +67,38 @@ if ($sm->string_exists($identifier.'_help', $component)) {
     $options->newlines = false;
     $options->overflowdiv = !$ajax;
 
-    if ($ajax) {
-        // When using AJAX, the header should be H2 as it is in the same DOM as the calling page.
-        echo $OUTPUT->heading(format_string(get_string($identifier, $component)), 2, 'helpheading');
-    } else {
-        // When not using AJAX, the header should be H1 as it is in it's own window.
-        echo $OUTPUT->heading(format_string(get_string($identifier, $component)), 1, 'helpheading');
-    }
+    $data->heading = format_string(get_string($identifier, $component));
     // Should be simple wiki only MDL-21695
-    echo format_text(get_string($identifier.'_help', $component), FORMAT_MARKDOWN, $options);
+    $data->text =  format_text(get_string($identifier.'_help', $component), FORMAT_MARKDOWN, $options);
 
-    if ($sm->string_exists($identifier.'_link', $component)) {  // Link to further info in Moodle docs
-        $link = get_string($identifier.'_link', $component);
+    $helplink = $identifier . '_link';
+    if ($sm->string_exists($helplink, $component)) {  // Link to further info in Moodle docs
+        $link = get_string($helplink, $component);
         $linktext = get_string('morehelp');
-        echo '<div class="helpdoclink">'.$OUTPUT->doc_link($link, $linktext).'</div>';
-    }
 
+        $data->doclink = new stdClass();
+        $url = new moodle_url(get_docs_url($link));
+        $data->doclink->link = $url->out();
+        $data->doclink->linktext = $linktext;
+        $data->doclink->class = ($CFG->doctonewwindow) ? 'helplinkpopup' : '';
+
+        $completedoclink = html_writer::tag('div', $OUTPUT->doc_link($link, $linktext), array('class' => 'helpdoclink'));
+    }
 } else {
-    echo "<p><strong>TODO</strong>: missing help string [{$identifier}_help, $component]</p>";
+    $data->text = html_writer::tag('p',
+            html_writer::tag('strong', 'TODO') . ": missing help string [{$identifier}_help, {$component}]");
 }
 
-if (!$ajax) {
+if ($ajax) {
+    echo json_encode($data);
+} else {
+    echo $OUTPUT->header();
+    if (isset($data->heading)) {
+        echo $OUTPUT->heading($data->heading, 1, 'helpheading');
+    }
+    echo $data->text;
+    if (isset($completedoclink)) {
+        echo $completedoclink;
+    }
     echo $OUTPUT->footer();
 }
index 1543b76..71ad4f1 100644 (file)
@@ -924,6 +924,7 @@ $string['list'] = 'List';
 $string['listfiles'] = 'List of files in {$a}';
 $string['listofallpeople'] = 'List of all people';
 $string['listofcourses'] = 'List of courses';
+$string['loadinghelp'] = 'Loading...';
 $string['local'] = 'Local';
 $string['localplugindeleteconfirm'] = 'You are about to completely delete the local plugin \'{$a}\'. This will completely delete everything in the database associated with this plugin. Are you SURE you want to continue?';
 $string['localplugins'] = 'Local plugins';
index d709396..b1e2ed4 100644 (file)
@@ -1480,131 +1480,27 @@ M.util.help_popups = {
     }
 }
 
+/**
+ * This code bas been deprecated and will be removed from Moodle 2.7
+ *
+ * Please see lib/yui/popuphelp/popuphelp.js for its replacement
+ */
 M.util.help_icon = {
-    Y : null,
-    instance : null,
     initialised : false,
-    setup : function(Y) {
-        if (this.initialised) {
-            // Exit early if we have already completed setup
-            return;
-        }
-        this.Y = Y;
-        Y.one('body').delegate('click', this.display, 'span.helplink a.tooltip', this);
-        this.initialised = true;
-    },
-    add : function(Y, properties) {
-        this.setup(Y);
+    setup : function(Y, properties) {
+        this.add(Y, properties);
     },
-    display : function(event) {
-        event.preventDefault();
-        if (M.util.help_icon.instance === null) {
-            var Y = M.util.help_icon.Y;
-            Y.use('overlay', 'io-base', 'event-mouseenter', 'node', 'event-key', 'escape', function(Y) {
-                var help_content_overlay = {
-                    helplink : null,
-                    overlay : null,
-                    init : function() {
-
-                        var strclose = Y.Escape.html(M.str.form.close);
-                        var footerbtn = Y.Node.create('<button class="closebtn">'+strclose+'</button>');
-                        // Create an overlay from markup
-                        this.overlay = new Y.Overlay({
-                            footerContent: footerbtn,
-                            bodyContent: '',
-                            id: 'helppopupbox',
-                            width:'400px',
-                            visible : false,
-                            constrain : true
-                        });
-                        this.overlay.render(Y.one(document.body));
-
-                        footerbtn.on('click', this.close, this);
-
-                        var boundingBox = this.overlay.get("boundingBox");
-
-                        //  Hide the menu if the user clicks outside of its content
-                        boundingBox.get("ownerDocument").on("mousedown", function (event) {
-                            var oTarget = event.target;
-                            var menuButton = this.helplink;
-
-                            if (!oTarget.compareTo(menuButton) &&
-                                !menuButton.contains(oTarget) &&
-                                !oTarget.compareTo(boundingBox) &&
-                                !boundingBox.contains(oTarget)) {
-                                this.overlay.hide();
-                            }
-                        }, this);
-                    },
-
-                    close : function(e) {
-                        e.preventDefault();
-                        this.helplink.focus();
-                        this.overlay.hide();
-                    },
-
-                    display : function(event) {
-                        var overlayPosition;
-                        this.helplink = event.target.ancestor('span.helplink a', true);
-                        if (Y.one('html').get('dir') === 'rtl') {
-                            overlayPosition = [Y.WidgetPositionAlign.TR, Y.WidgetPositionAlign.LC];
-                        } else {
-                            overlayPosition = [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.RC];
-                        }
-
-                        this.overlay.set('bodyContent', Y.Node.create('<img src="'+M.cfg.loadingicon+'" class="spinner" />'));
-                        this.overlay.set("align", {node:this.helplink, points: overlayPosition});
-
-                        var cfg = {
-                            method: 'get',
-                            context : this,
-                            data : {
-                                ajax : 1
-                            },
-                            on: {
-                                success: function(id, o, node) {
-                                    this.display_callback(o.responseText);
-                                },
-                                failure: function(id, o, node) {
-                                    var debuginfo = o.statusText;
-                                    if (M.cfg.developerdebug) {
-                                        o.statusText += ' (' + ajaxurl + ')';
-                                    }
-                                    this.display_callback('bodyContent',debuginfo);
-                                }
-                            }
-                        };
-
-                        Y.io(this.helplink.get('href'), cfg);
-                        this.overlay.show();
-                    },
-
-                    display_callback : function(content) {
-                        var contentnode, heading;
-                        contentnode = Y.Node.create('<div role="alert">' + content + '</div>');
-                        this.overlay.set('bodyContent', contentnode);
-                        heading = contentnode.one('h2');
-                        if (heading) {
-                            heading.set('tabIndex', 0);
-                            heading.focus();
-                        }
-                    },
-
-                    hideContent : function() {
-                        help = this;
-                        help.overlay.hide();
-                    }
-                };
-                help_content_overlay.init();
-                M.util.help_icon.instance = help_content_overlay;
-                M.util.help_icon.instance.display(event);
+    add : function(Y) {
+        if (M.cfg.developerdebug) {
+            Y.log("You are using a deprecated function call (M.util.help_icon.add). " +
+                    "Please look at rewriting your call to support lib/yui/popuphelp/popuphelp.js");
+        }
+        if (!this.initialised) {
+            YUI().use('moodle-core-popuphelp', function() {
+                M.core.init_popuphelp([]);
             });
-        } else {
-            M.util.help_icon.instance.display(event);
         }
-    },
-    init : function(Y) {
-        this.Y = Y;
+        this.initialised = true;
     }
 };
 
index fb4808c..4e0545b 100644 (file)
@@ -364,9 +364,16 @@ class core_renderer extends renderer_base {
         // flow player embedding support
         $this->page->requires->js_function_call('M.util.load_flowplayer');
 
-        // Set up help link popups for all links with the helplinkpopup class
+        // Set up help link popups for all links with the helptooltip class
         $this->page->requires->js_init_call('M.util.help_popups.setup');
 
+        // Setup help icon overlays.
+        $this->page->requires->yui_module('moodle-core-popuphelp', 'M.core.init_popuphelp');
+        $this->page->requires->strings_for_js(array(
+            'morehelp',
+            'loadinghelp',
+        ), 'moodle');
+
         $this->page->requires->js_function_call('setTimeout', array('fix_column_widths()', 20));
 
         $focus = $this->page->focuscontrol;
@@ -1932,7 +1939,7 @@ class core_renderer extends renderer_base {
         $this->page->requires->string_for_js('close', 'form');
 
         // and finally span
-        return html_writer::tag('span', $output, array('class' => 'helplink'));
+        return html_writer::tag('span', $output, array('class' => 'helptooltip'));
     }
 
     /**
@@ -1989,14 +1996,11 @@ class core_renderer extends renderer_base {
         // note: this title is displayed only if JS is disabled, otherwise the link will have the new ajax tooltip
         $title = get_string('helpprefix2', '', trim($title, ". \t"));
 
-        $attributes = array('href'=>$url, 'title'=>$title, 'aria-haspopup' => 'true', 'class' => 'tooltip');
+        $attributes = array('href' => $url, 'title' => $title, 'aria-haspopup' => 'true');
         $output = html_writer::tag('a', $output, $attributes);
 
-        $this->page->requires->js_init_call('M.util.help_icon.setup');
-        $this->page->requires->string_for_js('close', 'form');
-
         // and finally span
-        return html_writer::tag('span', $output, array('class' => 'helplink'));
+        return html_writer::tag('span', $output, array('class' => 'helptooltip'));
     }
 
     /**
index ff30d90..b41714d 100644 (file)
@@ -15,6 +15,11 @@ information provided here is intended especially for developers.
   perform the whole deletion process. The function course_delete_module now takes care
   of the whole process.
 
+YUI changes:
+* M.util.help_icon has been deprecated. Code should be updated to use moodle-core-popuphelp
+  instead. To do so, remove any existing JS calls to M.util.help_icon from your PHP and ensure
+  that your help link is placed in a span which has the class 'helplink'.
+
 === 2.4 ===
 
 * Pagelib: Numerous deprecated functions were removed as classes page_base, page_course
diff --git a/lib/yui/popuphelp/popuphelp.js b/lib/yui/popuphelp/popuphelp.js
new file mode 100644 (file)
index 0000000..6ef2710
--- /dev/null
@@ -0,0 +1,86 @@
+YUI.add('moodle-core-popuphelp', function(Y) {
+    function POPUPHELP() {
+        POPUPHELP.superclass.constructor.apply(this, arguments);
+    }
+
+    var SELECTORS = {
+            CLICKABLELINKS: 'span.helptooltip > a',
+            FOOTER: 'div.moodle-dialogue-ft'
+        },
+
+        CSS = {
+            ICON: 'icon',
+            ICONPRE: 'icon-pre'
+        },
+        ATTRS = {};
+
+    // Set the modules base properties.
+    POPUPHELP.NAME = 'moodle-core-popuphelp';
+    POPUPHELP.ATTRS = ATTRS;
+
+    Y.extend(POPUPHELP, Y.Base, {
+        panel: null,
+
+        initializer: function() {
+            Y.one('body').delegate('click', this.display_panel, SELECTORS.CLICKABLELINKS, this);
+        },
+
+        display_panel: function(e) {
+            if (!this.panel) {
+                this.panel = new M.core.tooltip({
+                    bodyhandler: this.set_body_content,
+                    footerhandler: this.set_footer,
+                    initialheadertext: M.util.get_string('loadinghelp', 'moodle'),
+                    initialfootertext: ''
+                });
+            }
+
+            // Call the tooltip setup.
+            this.panel.display_panel(e);
+        },
+
+        /**
+         * Override the footer handler to add a 'More help' link where relevant.
+         *
+         * @param {Object} helpobject The object returned from the AJAX call.
+         */
+        set_footer: function(helpobject) {
+            // Check for an optional link to documentation on moodle.org.
+            if (helpobject.doclink) {
+                // Wrap a help icon and the morehelp text in an anchor. The class of the anchor should
+                // determine whether it's opened in a new window or not.
+                doclink = Y.Node.create('<a />')
+                    .setAttrs({
+                        'href': helpobject.doclink.link
+                    })
+                    .addClass(helpobject.doclink['class']);
+                helpicon = Y.Node.create('<img />')
+                    .setAttrs({
+                        'src': M.util.image_url('docs', 'core')
+                    })
+                    .addClass(CSS.ICON)
+                    .addClass(CSS.ICONPRE);
+                doclink.appendChild(helpicon);
+                doclink.appendChild(helpobject.doclink.linktext);
+
+                // Set the footerContent to the contents of the doclink.
+                this.set('footerContent', doclink);
+                this.bb.one(SELECTORS.FOOTER).show();
+            } else {
+                this.bb.one(SELECTORS.FOOTER).hide();
+            }
+        }
+    });
+    M.core = M.core || {};
+    M.core.popuphelp = M.core.popuphelp || null;
+    M.core.init_popuphelp = M.core.init_popuphelp || function(config) {
+        // Only set up a single instance of the popuphelp.
+        if (!M.core.popuphelp) {
+            M.core.popuphelp = new POPUPHELP(config);
+        }
+        return M.core.popuphelp;
+    };
+},
+'@VERSION@', {
+    requires: ['moodle-core-tooltip']
+});
diff --git a/lib/yui/tooltip/tooltip.js b/lib/yui/tooltip/tooltip.js
new file mode 100644 (file)
index 0000000..e2680e5
--- /dev/null
@@ -0,0 +1,434 @@
+YUI.add('moodle-core-tooltip', function(Y) {
+    /**
+     * Provides the base tooltip class.
+     *
+     * @module moodle-core-tooltip
+     */
+
+    /**
+     * A base class for a tooltip.
+     *
+     * @param {Object} config Object literal specifying tooltip configuration properties.
+     * @class M.core.tooltip
+     * @constructor
+     * @extends M.core.dialogue
+     */
+    function TOOLTIP(config) {
+        if (!config) {
+            config = {};
+        }
+
+        // Override the default options provided by the parent class.
+        if (typeof config.draggable === 'undefined') {
+            config.draggable = true;
+        }
+
+        if (typeof config.constrain === 'undefined') {
+            config.constrain = true;
+        }
+
+        if (typeof config.lightbox === 'undefined') {
+            config.lightbox = false;
+        }
+
+        TOOLTIP.superclass.constructor.apply(this, [config]);
+    }
+
+    var SELECTORS = {
+            CLOSEBUTTON: '.closebutton'
+        },
+
+        CSS = {
+            PANELTEXT: 'tooltiptext'
+        },
+        RESOURCES = {
+            WAITICON: {
+                pix: 'i/loading_small',
+                component: 'moodle'
+            }
+        },
+        ATTRS = {};
+
+    /**
+     * Static property provides a string to identify the JavaScript class.
+     *
+     * @property NAME
+     * @type String
+     * @static
+     */
+    TOOLTIP.NAME = 'moodle-core-tooltip';
+
+    /**
+     * Static property used to define the CSS prefix applied to tooltip dialogues.
+     *
+     * @property CSS_PREFIX
+     * @type String
+     * @static
+     */
+    TOOLTIP.CSS_PREFIX = 'moodle-dialogue';
+
+    /**
+     * Static property used to define the default attribute configuration for the Tooltip.
+     *
+     * @property ATTRS
+     * @type String
+     * @static
+     */
+    TOOLTIP.ATTRS = ATTRS;
+
+    /**
+     * The initial value of the header region before the content finishes loading.
+     *
+     * @attribute initialheadertext
+     * @type String
+     * @default ''
+     * @writeOnce
+     */
+    ATTRS.initialheadertext = {
+        value: ''
+    };
+
+    /**
+      * The initial value of the body region before the content finishes loading.
+      *
+      * The supplid string will be wrapped in a div with the CSS.PANELTEXT class and a standard Moodle spinner
+      * appended.
+      *
+      * @attribute initialbodytext
+      * @type String
+      * @default ''
+      * @writeOnce
+      */
+    ATTRS.initialbodytext = {
+        value: '',
+        setter: function(content) {
+            var parentnode,
+                spinner;
+            parentnode = Y.Node.create('<div />')
+                .addClass(CSS.PANELTEXT);
+
+            spinner = Y.Node.create('<img />')
+                .setAttribute('src', M.util.image_url(RESOURCES.WAITICON.pix, RESOURCES.WAITICON.component))
+                .addClass('spinner');
+
+            if (content) {
+                // If we have been provided with content, add it to the parent and make
+                // the spinner appear correctly inline
+                parentnode.set('text', content);
+                spinner.addClass('iconsmall');
+            } else {
+                // If there is no loading message, just make the parent node a lightbox
+                parentnode.addClass('content-lightbox');
+            }
+
+            parentnode.append(spinner);
+            return parentnode;
+        }
+    };
+
+    /**
+     * The initial value of the footer region before the content finishes loading.
+     *
+     * If a value is supplied, it will be wrapped in a <div> first.
+     *
+     * @attribute initialfootertext
+     * @type String
+     * @default ''
+     * @writeOnce
+     */
+    ATTRS.initialfootertext = {
+        value: null,
+        setter: function(content) {
+            if (content) {
+                return Y.Node.create('<div />')
+                    .set('text', content);
+            }
+        }
+    };
+
+    /**
+     * The function which handles setting the content of the title region.
+     * The specified function will be called with a context of the tooltip instance.
+     *
+     * The default function will simply set the value of the title to object.heading as returned by the AJAX call.
+     *
+     * @attribute headerhandler
+     * @type Function|String|null
+     * @default set_header_content
+     */
+    ATTRS.headerhandler = {
+        value: 'set_header_content'
+    };
+
+    /**
+     * The function which handles setting the content of the body region.
+     * The specified function will be called with a context of the tooltip instance.
+     *
+     * The default function will simply set the value of the body area to a div containing object.text as returned
+     * by the AJAX call.
+     *
+     * @attribute bodyhandler
+     * @type Function|String|null
+     * @default set_body_content
+     */
+    ATTRS.bodyhandler = {
+        value: 'set_body_content'
+    };
+
+    /**
+     * The function which handles setting the content of the footer region.
+     * The specified function will be called with a context of the tooltip instance.
+     *
+     * By default, the footer is not set.
+     *
+     * @attribute footerhandler
+     * @type Function|String|null
+     * @default null
+     */
+    ATTRS.footerhandler = {
+        value: null
+    };
+
+    /**
+     * Set the Y.Cache object to use.
+     *
+     * By default a new Y.Cache object will be created for each instance of the tooltip.
+     *
+     * In certain situations, where multiple tooltips may share the same cache, it may be preferable to
+     * seed this cache from the calling method.
+     *
+     * @attribute textcache
+     * @type Y.Cache|null
+     * @default null
+     */
+    ATTRS.textcache = {
+        value: null
+    };
+
+    /**
+     * Set the default size of the Y.Cache object.
+     *
+     * This is only used if no textcache is specified.
+     *
+     * @attribute textcachesize
+     * @type Number
+     * @default 10
+     */
+    ATTRS.textcachesize = {
+        value: 10
+    };
+
+    Y.extend(TOOLTIP, M.core.dialogue, {
+        // The bounding box.
+        bb: null,
+
+        // Any event listeners we may need to cancel later.
+        listenevents: [],
+
+        // Cache of objects we've already retrieved.
+        textcache: null,
+
+        // The align position. This differs for RTL languages so we calculate once and store.
+        alignpoints: [
+            Y.WidgetPositionAlign.TL,
+            Y.WidgetPositionAlign.RC
+        ],
+
+        initializer: function() {
+            // Set the initial values for the handlers.
+            // These cannot be set in the attributes section as context isn't present at that time.
+            if (!this.get('headerhandler')) {
+                this.set('headerhandler', this.set_header_content);
+            }
+            if (!this.get('bodyhandler')) {
+                this.set('bodyhandler', this.set_body_content);
+            }
+            if (!this.get('footerhandler')) {
+                this.set('footerhandler', function() {});
+            }
+
+            // Set up the dialogue with initial content.
+            this.setAttrs({
+                headerContent: this.get('initialheadertext'),
+                bodyContent: this.get('initialbodytext'),
+                footerContent: this.get('initialfootertext'),
+                zIndex: 150
+            });
+
+            // Hide and then render the dialogue.
+            this.hide();
+            this.render();
+
+            // Hook into a few useful areas.
+            this.bb = this.get('boundingBox');
+
+            // Change the alignment if this is an RTL language.
+            if (right_to_left()) {
+                this.alignpoints = [
+                    Y.WidgetPositionAlign.TR,
+                    Y.WidgetPositionAlign.LC
+                ];
+            }
+
+            // Set up the text cache if it's not set up already.
+            if (!this.get('textcache')) {
+                this.set('textcache', new Y.Cache({
+                    // Set a reasonable maximum cache size to prevent memory growth.
+                    max: this.get('textcachesize')
+                }));
+            }
+
+            // Disable the textcache when in developerdebug.
+            if (M.cfg.developerdebug) {
+                this.get('textcache').set('max', 0);
+            }
+
+            return this;
+        },
+
+        /**
+         * Display the tooltip for the clicked link.
+         *
+         * The anchor for the clicked link is used, additionally appending ajax=1 to the parameters.
+         *
+         * @method display_panel
+         * @param {EventFacade} e The event from the clicked link. This is used to determine the clicked URL.
+         */
+        display_panel: function(e) {
+            var clickedlink, thisevent, ajaxurl, config, cacheentry;
+
+            // Prevent the default click action and prevent the event triggering anything else.
+            e.preventDefault();
+            e.stopPropagation();
+
+            // Cancel any existing listeners and close the panel if it's already open.
+            this.cancel_events();
+
+            // Grab the clickedlink - this contains the URL we fetch and we align the panel to it.
+            clickedlink = e.target.ancestor('a', true);
+
+            // Align with the link that was clicked.
+            this.align(clickedlink, this.alignpoints);
+
+            // Reset the initial text to a spinner while we retrieve the text.
+            this.setAttrs({
+                headerContent: this.get('initialheadertext'),
+                bodyContent: this.get('initialbodytext'),
+                footerContent: this.get('initialfootertext')
+            });
+
+            // Now that initial setup has begun, show the panel.
+            this.show();
+
+            // Add some listen events to close on.
+            thisevent = this.bb.delegate('click', this.close_panel, SELECTORS.CLOSEBUTTON, this);
+            this.listenevents.push(thisevent);
+
+            thisevent = Y.one('body').on('key', this.close_panel, 'esc', this);
+            this.listenevents.push(thisevent);
+
+            // Listen for mousedownoutside events - clickoutside is broken on IE.
+            thisevent = this.bb.on('mousedownoutside', this.close_panel, this);
+            this.listenevents.push(thisevent);
+
+            ajaxurl = clickedlink.get('href');
+
+            cacheentry = this.get('textcache').retrieve(ajaxurl);
+            if (cacheentry) {
+                // The data from this help call was already cached so use that and avoid an AJAX call.
+                this._set_panel_contents(cacheentry.response);
+            } else {
+                // Retrieve the actual help text we should use.
+                config = {
+                    method: 'get',
+                    context: this,
+                    sync: false,
+                    data: {
+                        // We use a slightly different AJAX URL to the one on the anchor to allow non-JS fallback.
+                        ajax: 1
+                    },
+                    on: {
+                        complete: function(tid, response) {
+                            this._set_panel_contents(response.responseText, ajaxurl);
+                        }
+                    }
+                };
+
+                Y.io(clickedlink.get('href'), config);
+            }
+        },
+
+        _set_panel_contents: function(response, ajaxurl) {
+            var responseobject;
+
+            // Attempt to parse the response into an object.
+            try {
+                responseobject = Y.JSON.parse(response);
+                if (responseobject.error) {
+                    this.close_panel();
+                    return new M.core.ajaxException(responseobject);
+                }
+            } catch (error) {
+                this.close_panel();
+                return new M.core.exception({
+                    name: error.name,
+                    message: "Unable to retrieve the requested content. The following error was returned: " + error.message
+                });
+            }
+
+            // Set the contents using various handlers.
+            // We must use Y.bind to ensure that the correct context is used when the default handlers are overridden.
+            Y.bind(this.get('headerhandler'), this, responseobject)();
+            Y.bind(this.get('bodyhandler'), this, responseobject)();
+            Y.bind(this.get('footerhandler'), this, responseobject)();
+
+            if (ajaxurl) {
+                // Ensure that this data is added to the cache.
+                this.get('textcache').add(ajaxurl, response);
+            }
+
+            this.get('buttons').header[0].focus();
+        },
+
+        set_header_content: function(responseobject) {
+            this.set('headerContent', responseobject.heading);
+        },
+
+        set_body_content: function(responseobject) {
+            var bodycontent = Y.Node.create('<div />')
+                .set('innerHTML', responseobject.text)
+                .setAttribute('role', 'alert')
+                .addClass(CSS.PANELTEXT);
+            this.set('bodyContent', bodycontent);
+        },
+
+        close_panel: function(e) {
+            // Hide the panel first.
+            this.hide();
+
+            // Cancel the listeners that we added in display_panel.
+            this.cancel_events();
+
+            // Prevent any default click that the close button may have.
+            if (e) {
+                e.preventDefault();
+            }
+        },
+
+        cancel_events: function() {
+            // Detach all listen events to prevent duplicate triggers.
+            var thisevent;
+            while (this.listenevents.length) {
+                thisevent = this.listenevents.shift();
+                thisevent.detach();
+            }
+        }
+    });
+    M.core = M.core || {};
+    M.core.tooltip = M.core.tooltip = TOOLTIP;
+},
+'@VERSION@', {
+    requires: ['base', 'io-base', 'moodle-core-notification', 'json-parse',
+            'widget-position', 'widget-position-align', 'event-outside', 'cache']
+}
+);
index 819f38c..bc80df8 100644 (file)
@@ -515,18 +515,6 @@ body.tag .managelink {padding: 5px;}
  */
 #webservice-doc-generator td {text-align: left;border: 0px solid black;}
 
-/**
- * Help Content (pop-up)
- */
-#helppopupbox {background-color: #eee; border: 1px solid #848484;z-index: 10000 !important;}
-#helppopupbox .yui3-widget-hd {float:right;margin:3px 3px 0 0;}
-#helppopupbox .yui3-widget-bd {margin:0 1em 1em 1em;border-top:1px solid #eee;}
-#helppopupbox .yui3-widget-ft {text-align: center;}
-#helppopupbox .yui3-widget-ft .closebtn {margin:0 1em 1em 1em;}
-#helppopupbox .helpheading {font-size: 1em;}
-#helppopupbox .spinner {margin:1em;}
-.dir-rtl #helppopupbox .yui3-widget-hd {float:left;margin:3px 0 0 3px;}
-
 /**
  * Custom menu
  */
@@ -1019,6 +1007,22 @@ sup {vertical-align: super;}
     padding-bottom:4px;
 }
 
+.moodle-dialogue .moodle-dialogue-bd .content-lightbox {
+    opacity: .75;
+    width: 100%;
+    height: 100%;
+    top: 0;
+    left: 0;
+    background-color: white;
+    text-align: center;
+    padding: 10% 0;
+}
+
+/* Apply a default max-height on tooltip text */
+.moodle-dialogue .tooltiptext {
+    max-height: 300px;
+}
+
 
 /* Question Bank - Question Chooser "Close" button */
 #page-question-edit.dir-rtl a.container-close {right:auto;left:6px;}
index 4d2af4e..51248ec 100644 (file)
@@ -445,13 +445,6 @@ input[type="radio"] {
     padding: 0 0 1em;
 }
 
-/* YUI overlays
-------------------------*/
-#helppopupbox {
-    z-index: 99999 !important;
-}
-
-
 /* Embedded Pages
 ------------------------*/
 
index 17b2be3..ec66538 100644 (file)
@@ -180,9 +180,6 @@ table td.cell p {margin:0;}
 #page-footer .logininfo {padding:0.3em 0 0.7em 0;}
 #page-footer .moodledocs {text-align:center;background-color:#EFEFEF;padding:0.7em 0 0.8em 0;}
 
-/* js help messages */
-#helppopupbox .helpheading {margin-top:1em;}
-
 /* pre, code, tt */
 pre, code, tt {
     font: 1em/1.3 monospace;
index dd9f2f8..1a99a6e 100644 (file)
@@ -286,18 +286,6 @@ textarea#id_message {width:420px;}
 .helplink img {
     margin-left:5px;
 }
-#helppopupbox {
-    padding:10px 0;
-}
-#helppopupbox p {
-    padding:0 0 5px;
-    margin:0;
-    line-height:1.3em;
-}
-#helppopupbox .helpheading {
-    font-size:1.2em;
-    padding-bottom:10px;
-}
 /* Icons
 -------------------------*/
 img.icon {