From 82b945dbbef467ecdf532074424f2042b61e8c10 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Massart?= <fred@moodle.com>
Date: Fri, 23 Sep 2016 12:40:48 +0200
Subject: [PATCH] MDL-56095 output: Make the progress bar templatable

The order of inclusion of weblib and outputlib had to change
as the progress_bar (weblib) uses interfaces defined in outputlib.
---
 lib/javascript-static.js                      | 39 +++++------
 lib/outputrenderers.php                       | 14 ++++
 lib/setup.php                                 |  2 +-
 lib/templates/progress_bar.mustache           | 64 +++++++++++++++++++
 lib/weblib.php                                | 36 ++++++-----
 .../templates/core/progress_bar.mustache      | 62 ++++++++++++++++++
 6 files changed, 182 insertions(+), 35 deletions(-)
 create mode 100644 lib/templates/progress_bar.mustache
 create mode 100644 theme/boost/templates/core/progress_bar.mustache

diff --git a/lib/javascript-static.js b/lib/javascript-static.js
index e0438e69198..dc56fe35d18 100644
--- a/lib/javascript-static.js
+++ b/lib/javascript-static.js
@@ -1300,30 +1300,33 @@ function stripHTML(str) {
 }
 
 function updateProgressBar(id, percent, msg, estimate) {
-    var progressIndicator = Y.one('#' + id);
-    if (!progressIndicator) {
+    var event,
+        el = document.getElementById(id),
+        eventData = {};
+
+    if (!el) {
         return;
     }
 
-    var progressBar = progressIndicator.one('.bar'),
-        statusIndicator = progressIndicator.one('h2'),
-        estimateIndicator = progressIndicator.one('p');
+    eventData.message = msg;
+    eventData.percent = percent;
+    eventData.estimate = estimate;
 
-    statusIndicator.set('innerHTML', Y.Escape.html(msg));
-    progressBar.set('innerHTML', Y.Escape.html('' + percent + '%'));
-    if (percent === 100) {
-        progressIndicator.addClass('progress-success');
-        estimateIndicator.set('innerHTML', null);
-    } else {
-        if (estimate) {
-            estimateIndicator.set('innerHTML', Y.Escape.html(estimate));
-        } else {
-            estimateIndicator.set('innerHTML', null);
+    try {
+        event = new CustomEvent('update', {
+            bubbles: false,
+            cancelable: true,
+            detail: eventData
+        });
+    } catch (exception) {
+        if (!(exception instanceof TypeError)) {
+            throw exception;
         }
-        progressIndicator.removeClass('progress-success');
+        event = document.createEvent('Event');
+        event.initCustomEvent('update', false, true, eventData);
     }
-    progressBar.setAttribute('aria-valuenow', percent);
-    progressBar.setStyle('width', percent + '%');
+
+    el.dispatchEvent(event);
 }
 
 // ===== Deprecated core Javascript functions for Moodle ====
diff --git a/lib/outputrenderers.php b/lib/outputrenderers.php
index fc95c5d5eb2..01f6c2588c0 100644
--- a/lib/outputrenderers.php
+++ b/lib/outputrenderers.php
@@ -4333,6 +4333,20 @@ EOD;
             return false;
         }
     }
+
+    /**
+     * Renders a progress bar.
+     *
+     * Do not use $OUTPUT->render($bar), instead use progress_bar::create().
+     *
+     * @param  progress_bar $bar The bar.
+     * @return string HTML fragment
+     */
+    public function render_progress_bar(progress_bar $bar) {
+        global $PAGE;
+        $data = $bar->export_for_template($this);
+        return $this->render_from_template('core/progress_bar', $data);
+    }
 }
 
 /**
diff --git a/lib/setup.php b/lib/setup.php
index b5970239918..612f077728e 100644
--- a/lib/setup.php
+++ b/lib/setup.php
@@ -587,8 +587,8 @@ core_date::store_default_php_timezone();
 // Load up standard libraries
 require_once($CFG->libdir .'/filterlib.php');       // Functions for filtering test as it is output
 require_once($CFG->libdir .'/ajax/ajaxlib.php');    // Functions for managing our use of JavaScript and YUI
-require_once($CFG->libdir .'/weblib.php');          // Functions relating to HTTP and content
 require_once($CFG->libdir .'/outputlib.php');       // Functions for generating output
+require_once($CFG->libdir .'/weblib.php');          // Functions relating to HTTP and content
 require_once($CFG->libdir .'/navigationlib.php');   // Class for generating Navigation structure
 require_once($CFG->libdir .'/dmllib.php');          // Database access
 require_once($CFG->libdir .'/datalib.php');         // Legacy lib with a big-mix of functions.
diff --git a/lib/templates/progress_bar.mustache b/lib/templates/progress_bar.mustache
new file mode 100644
index 00000000000..7a2653e80eb
--- /dev/null
+++ b/lib/templates/progress_bar.mustache
@@ -0,0 +1,64 @@
+{{!
+    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/>.
+}}
+{{!
+    Progress bar.
+
+    Example context (json):
+    {
+        id: 'progressbar_test',
+        width: '500'
+    }
+}}
+<div class="progressbar_container" style="width: {{width}}px;" id="{{id}}">
+    <h2 id="{{id}}_status"></h2>
+    <div class="progress progress-striped active">
+        <div id="{{id}}_bar" class="bar" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">&nbsp;</div>
+    </div>
+    <p id="{{id}}_estimate"></p>
+</div>
+
+{{! We must not use the JS helper otherwise this gets executed too late. }}
+<script type="text/javascript">
+(function() {
+    var el = document.getElementById('{{id}}'),
+        progressBar = document.getElementById('{{id}}_bar'),
+        statusIndicator = document.getElementById('{{id}}_status'),
+        estimateIndicator = document.getElementById('{{id}}_estimate');
+
+    el.addEventListener('update', function(e) {
+        var msg = e.detail.message,
+            percent = e.detail.percent,
+            estimate = e.detail.estimate;
+
+        statusIndicator.textContent = msg;
+        progressBar.textContent = '' + percent + '%';
+        if (percent === 100) {
+            el.classList.add('progress-success');
+            estimateIndicator.textContent = '';
+        } else {
+            if (estimate) {
+                estimateIndicator.textContent = estimate;
+            } else {
+                estimateIndicator.textContent = '';
+            }
+            el.classList.remove('progress-success');
+        }
+        progressBar.setAttribute('aria-valuenow', percent);
+        progressBar.setAttribute('style', 'width: ' + percent + '%');
+    });
+})();
+</script>
diff --git a/lib/weblib.php b/lib/weblib.php
index 69160ab1003..db4d76e6c78 100644
--- a/lib/weblib.php
+++ b/lib/weblib.php
@@ -3182,7 +3182,7 @@ function is_in_popup() {
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  * @package core
  */
-class progress_bar {
+class progress_bar implements renderable, templatable {
     /** @var string html id */
     private $html_id;
     /** @var int total width */
@@ -3223,26 +3223,15 @@ class progress_bar {
      * @return void Echo's output
      */
     public function create() {
-        global $PAGE;
+        global $OUTPUT;
 
         $this->time_start = microtime(true);
         if (CLI_SCRIPT) {
             return; // Temporary solution for cli scripts.
         }
 
-        $PAGE->requires->string_for_js('secondsleft', 'moodle');
-
-        $htmlcode = <<<EOT
-        <div class="progressbar_container" style="width: {$this->width}px;" id="{$this->html_id}">
-            <h2></h2>
-            <div class="progress progress-striped active">
-                <div class="bar" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">&nbsp;</div>
-            </div>
-            <p></p>
-        </div>
-EOT;
         flush();
-        echo $htmlcode;
+        echo $OUTPUT->render($this);
         flush();
     }
 
@@ -3276,15 +3265,17 @@ EOT;
             // No significant change, no need to update anything.
             return;
         }
+
+        $estimatemsg = null;
         if (is_numeric($estimate)) {
-            $estimate = get_string('secondsleft', 'moodle', round($estimate, 2));
+            $estimatemsg = get_string('secondsleft', 'moodle', round($estimate, 2));
         }
 
         $this->percent = round($percent, 2);
         $this->lastupdate = microtime(true);
 
         echo html_writer::script(js_writer::function_call('updateProgressBar',
-            array($this->html_id, $this->percent, $msg, $estimate)));
+            array($this->html_id, $this->percent, $msg, $estimatemsg)));
         flush();
     }
 
@@ -3343,6 +3334,19 @@ EOT;
         $this->lastupdate = 0;
         $this->time_start = 0;
     }
+
+    /**
+     * Export for template.
+     *
+     * @param  renderer_base $output The renderer.
+     * @return array
+     */
+    public function export_for_template(renderer_base $output) {
+        return [
+            'id' => $this->html_id,
+            'width' => $this->width,
+        ];
+    }
 }
 
 /**
diff --git a/theme/boost/templates/core/progress_bar.mustache b/theme/boost/templates/core/progress_bar.mustache
new file mode 100644
index 00000000000..7a9d4f6193d
--- /dev/null
+++ b/theme/boost/templates/core/progress_bar.mustache
@@ -0,0 +1,62 @@
+{{!
+    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/>.
+}}
+{{!
+    Progress bar.
+
+    Example context (json):
+    {
+        id: 'progressbar_test',
+        width: '500'
+    }
+}}
+<div class="row" id="{{id}}" class="progressbar_container">
+    <div class="col-md-6 push-md-3">
+        <p id="{{id}}_status" class="text-xs-center"></p>
+        <progress id="{{id}}_bar" class="progress progress-striped progress-animated" value="0" max="100"></progress>
+        <p id="{{id}}_estimate" class="text-xs-center"></p>
+    </div>
+</div>
+
+{{! We must not use the JS helper otherwise this gets executed too late. }}
+<script type="text/javascript">
+(function() {
+    var el = document.getElementById('{{id}}'),
+        progressBar = document.getElementById('{{id}}_bar'),
+        statusIndicator = document.getElementById('{{id}}_status'),
+        estimateIndicator = document.getElementById('{{id}}_estimate');
+
+    el.addEventListener('update', function(e) {
+        var msg = e.detail.message,
+            percent = e.detail.percent,
+            estimate = e.detail.estimate;
+
+        statusIndicator.textContent = msg;
+        progressBar.setAttribute('value', Math.round(percent));
+        if (percent === 100) {
+            progressBar.classList.add('progress-success');
+            estimateIndicator.textContent = '100%';
+        } else {
+            if (estimate) {
+                estimateIndicator.textContent = estimate + ' - ' + percent + '%';
+            } else {
+                estimateIndicator.textContent = '' + percent + '%';
+            }
+            progressBar.classList.remove('progress-success');
+        }
+    });
+})();
+</script>