MDL-60489 javascript: add animations to modal setBody
authorRyan Wyllie <ryan@moodle.com>
Tue, 17 Oct 2017 08:14:53 +0000 (08:14 +0000)
committerRyan Wyllie <ryan@moodle.com>
Mon, 23 Oct 2017 07:57:03 +0000 (07:57 +0000)
lib/amd/build/modal.min.js
lib/amd/src/modal.js
theme/boost/scss/moodle.scss
theme/boost/scss/moodle/modal.scss [new file with mode: 0644]
theme/bootstrapbase/less/moodle/modal.less
theme/bootstrapbase/style/moodle.css

index 3560618..c01e2ab 100644 (file)
Binary files a/lib/amd/build/modal.min.js and b/lib/amd/build/modal.min.js differ
index a49fb8f..60db5fd 100644 (file)
@@ -50,6 +50,12 @@ define(['jquery', 'core/templates', 'core/notification', 'core/key_codes',
      */
     var backdropPromise;
 
+    /**
+     * A counter that gets incremented for each modal created. This can be
+     * used to generate unique values for the modals.
+     */
+    var modalCounter = 0;
+
     /**
      * Constructor for the Modal.
      *
@@ -66,6 +72,7 @@ define(['jquery', 'core/templates', 'core/notification', 'core/key_codes',
         this.isAttached = false;
         this.bodyJS = null;
         this.footerJS = null;
+        this.modalCount = modalCounter++;
 
         if (!this.root.is(SELECTORS.CONTAINER)) {
             Notification.exception({message: 'Element is not a modal container'});
@@ -212,6 +219,16 @@ define(['jquery', 'core/templates', 'core/notification', 'core/key_codes',
         return this.footer;
     };
 
+    /**
+     * Get the unique modal count.
+     *
+     * @method getModalCount
+     * @return {int}
+     */
+    Modal.prototype.getModalCount = function() {
+        return this.modalCount;
+    };
+
     /**
      * Set the modal title element.
      *
@@ -245,27 +262,97 @@ define(['jquery', 'core/templates', 'core/notification', 'core/key_codes',
             Event.notifyFilterContentUpdated(body);
             this.getRoot().trigger(ModalEvents.bodyRendered, this);
         } else {
+            var jsPendingId = 'amd-modal-js-pending-id-' + this.getModalCount();
+            M.util.js_pending(jsPendingId);
             // Otherwise we assume it's a promise to be resolved with
             // html and javascript.
-            Templates.render(TEMPLATES.LOADING, {}).done(function(html) {
-                body.html(html);
+            var contentPromise = null;
+            body.css('overflow', 'hidden');
+
+            if (value.state() == 'pending') {
+                // We're still waiting for the body promise to resolve so
+                // let's show a loading icon.
+                body.animate({height: '100px'}, 150);
+
+                body.html('');
+                contentPromise = Templates.render(TEMPLATES.LOADING, {})
+                    .then(function(html) {
+                        var loadingIcon = $(html).hide();
+                        body.html(loadingIcon);
+                        loadingIcon.fadeIn(150);
+
+                        // We only want the loading icon to fade out
+                        // when the content for the body has finished
+                        // loading.
+                        return $.when(loadingIcon.promise(), value);
+                    })
+                    .then(function(loadingIcon) {
+                        // Once the content has finished loading and
+                        // the loading icon has been shown then we can
+                        // fade the icon away to reveal the content.
+                        return loadingIcon.fadeOut(100).promise();
+                    })
+                    .then(function() {
+                        return value;
+                    });
+            } else {
+                // The content is already loaded so let's just display
+                // it to the user. No need for a loading icon.
+                contentPromise = value;
+            }
 
-                value.done(function(html, js) {
+            // Now we can actually display the content.
+            contentPromise.then(function(html, js) {
+                var result = null;
+
+                if (this.isVisible()) {
+                    // If the modal is visible then we should display
+                    // the content gracefully for the user.
+                    body.css('opacity', 0);
+                    var currentHeight = body.innerHeight();
                     body.html(html);
+                    // We need to clear any height values we've set here
+                    // in order to measure the height of the content being
+                    // added. This then allows us to animate the height
+                    // transition.
+                    body.css('height', '');
+                    var newHeight = body.innerHeight();
+                    body.css('height', currentHeight + 'px');
+                    result = body.animate(
+                        {height: newHeight + 'px', opacity: 1},
+                        {duration: 150, queue: false}
+                    ).promise();
+                } else {
+                    // Since the modal isn't visible we can just immediately
+                    // set the content. No need to animate it.
+                    body.html(html);
+                }
 
-                    if (js) {
-                        if (this.isAttached) {
-                            // If we're in the DOM then run the JS immediately.
-                            Templates.runTemplateJS(js);
-                        } else {
-                            // Otherwise cache it to be run when we're attached.
-                            this.bodyJS = js;
-                        }
+                if (js) {
+                    if (this.isAttached) {
+                        // If we're in the DOM then run the JS immediately.
+                        Templates.runTemplateJS(js);
+                    } else {
+                        // Otherwise cache it to be run when we're attached.
+                        this.bodyJS = js;
                     }
-                    Event.notifyFilterContentUpdated(body);
-                    this.getRoot().trigger(ModalEvents.bodyRendered, this);
-                }.bind(this));
-            }.bind(this));
+                }
+                Event.notifyFilterContentUpdated(body);
+                this.getRoot().trigger(ModalEvents.bodyRendered, this);
+
+                return result;
+            }.bind(this))
+            .fail(Notification.exception)
+            .always(function() {
+                // When we're done displaying all of the content we need
+                // to clear the custom values we've set here.
+                body.css('height', '');
+                body.css('overflow', '');
+                body.css('opacity', '');
+                M.util.js_complete(jsPendingId);
+
+                return;
+            });
         }
     };
 
index 6b2b93b..19c89c3 100644 (file)
@@ -46,3 +46,4 @@ $breadcrumb-divider-rtl: "◀" !default;
 @import "moodle/responsive-tabs";
 @import "moodle/bs2-compat";
 @import "moodle/print";
+@import "moodle/modal";
diff --git a/theme/boost/scss/moodle/modal.scss b/theme/boost/scss/moodle/modal.scss
new file mode 100644 (file)
index 0000000..7399bfa
--- /dev/null
@@ -0,0 +1,16 @@
+.modal-body {
+    & > .loading-icon {
+        display: block;
+        position: relative;
+        width: 100%;
+        height: 100%;
+
+        .icon {
+            position: absolute;
+            top: 50%;
+            /*rtl:ignore*/
+            left: 50%;
+            transform: translate(-50%, -50%);
+        }
+    }
+}
index f1e1153..748d8af 100644 (file)
@@ -91,6 +91,21 @@ body {
 
         .modal-body {
             max-height: none;
+
+            & > .loading-icon {
+                display: block;
+                position: relative;
+                width: 100%;
+                height: 100%;
+
+                .icon {
+                    position: absolute;
+                    top: 50%;
+                    /*rtl:ignore*/
+                    left: 50%;
+                    transform: translate(-50%, -50%);
+                }
+            }
         }
 
         .modal-footer {
index 7565756..1d61174 100644 (file)
@@ -17177,6 +17177,19 @@ body.modal-open {
 .modal-container .modal .modal-body {
   max-height: none;
 }
+.modal-container .modal .modal-body > .loading-icon {
+  display: block;
+  position: relative;
+  width: 100%;
+  height: 100%;
+}
+.modal-container .modal .modal-body > .loading-icon .icon {
+  position: absolute;
+  top: 50%;
+  /*rtl:ignore*/
+  left: 50%;
+  transform: translate(-50%, -50%);
+}
 .modal-container .modal .modal-footer {
   border-top: 1px solid #bbb;
   text-align: center;