diff --git a/HISTORY.rst b/HISTORY.rst index 080125badc..3e92dbfe52 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -25,7 +25,8 @@ - Include querystring when force reload of a bare template view. - Speed up document image fade in reveal. - Use reseteable timer to ensure more document panels heights are matched. - +- Rewrote Mayan's Javascript suite MayanApp into ECMAScript2015. +- Remove use is waitForJQuery. 3.0.1 (2018-07-08) ================= diff --git a/mayan/apps/appearance/static/appearance/js/base.js b/mayan/apps/appearance/static/appearance/js/base.js index 72f0978ecc..244ac0298a 100644 --- a/mayan/apps/appearance/static/appearance/js/base.js +++ b/mayan/apps/appearance/static/appearance/js/base.js @@ -1,26 +1,12 @@ 'use strict'; -var app = new App(); +// Make it globally available. Used by event.links + +var MayanAppClass = MayanApp; + var partialNavigation = new PartialNavigation({ initialURL: initialURL, + disabledAnchorClasses: ['disabled'], excludeAnchorClasses: ['fancybox', 'new_window', 'non-ajax'], - formBeforeSerializeCallbacks: [App.MultiObjectFormProcess], + formBeforeSerializeCallbacks: [MayanApp.MultiObjectFormProcess], }); - -jQuery(document).ready(function() { - app.setupAutoSubmit(); - app.setupFullHeightResizing(); - app.setupItemsSelector(); - app.setupNavbarCollapse(); - app.setupNewWindowAnchor(); - app.setupAJAXperiodicWorkers(); - partialNavigation.initialize(); -}); - -var afterBaseLoad = function () { - MayanImage.intialize(); - app.doToastrMessages(); - app.resizeFullHeight(); - app.setupSelect2(); - app.setupScrollView(); -} diff --git a/mayan/apps/appearance/static/appearance/js/mayan_app.js b/mayan/apps/appearance/static/appearance/js/mayan_app.js index efcdd0dc38..7dd052e46e 100644 --- a/mayan/apps/appearance/static/appearance/js/mayan_app.js +++ b/mayan/apps/appearance/static/appearance/js/mayan_app.js @@ -1,284 +1,296 @@ 'use strict'; -var App = function (parameters) { - var self = this; +class MayanApp { + constructor (parameters) { + var self = this; - parameters = parameters || {} + parameters = parameters || {} - this.window = $(window); -} + this.window = $(window); + } -// Class methods and variables + // Class methods and variables -App.mayanNotificationBadge = function (options, data) { - // Callback to add the notifications count inside a badge markup - var notifications = data[options.attributeName]; + static mayanNotificationBadge (options, data) { + // Callback to add the notifications count inside a badge markup + var notifications = data[options.attributeName]; - if (notifications > 0) { - // Save the original link text before adding the initial badge markup - if (!options.element.data('mn-saved-text')) { - options.element.data('mn-saved-text', options.element.html()); - } + if (notifications > 0) { + // Save the original link text before adding the initial badge markup + if (!options.element.data('mn-saved-text')) { + options.element.data('mn-saved-text', options.element.html()); + } - options.element.html( - options.element.data('mn-saved-text') + ' ' + notifications + '' - ); - } else { - if (options.element.data('mn-saved-text')) { - // If there is a saved original link text, restore it options.element.html( - options.element.data('mn-saved-text') + options.element.data('mn-saved-text') + ' ' + notifications + '' ); + } else { + if (options.element.data('mn-saved-text')) { + // If there is a saved original link text, restore it + options.element.html( + options.element.data('mn-saved-text') + ); + } } } -} -App.MultiObjectFormProcess = function ($form, options) { - /* - * ajaxForm callback to add the external item checkboxes to the - * submitted form - */ + static MultiObjectFormProcess ($form, options) { + /* + * ajaxForm callback to add the external item checkboxes to the + * submitted form + */ - if ($form.hasClass('form-multi-object-action')) { - // Turn form data into an object - var formArray = $form.serializeArray().reduce(function (obj, item) { - obj[item.name] = item.value; - return obj; - }, {}); + if ($form.hasClass('form-multi-object-action')) { + // Turn form data into an object + var formArray = $form.serializeArray().reduce(function (obj, item) { + obj[item.name] = item.value; + return obj; + }, {}); - // Add all checked checkboxes to the form data - $('.form-multi-object-action-checkbox:checked').each(function() { + // Add all checked checkboxes to the form data + $('.form-multi-object-action-checkbox:checked').each(function() { + var $this = $(this); + formArray[$this.attr('name')] = $this.attr('value'); + }); + + // Set the form data as the data to send + options.data = formArray; + } + } + + static tagSelectionTemplate (tag, container) { + var $tag = $( + ' ' + tag.text + '' + ); + container[0].style.background = tag.element.dataset.color; + return $tag; + } + + static tagResultTemplate (tag) { + if (!tag.element) { return ''; } + var $tag = $( + ' ' + tag.text + '' + ); + return $tag; + } + + // Instance methods + + AJAXperiodicWorker (options) { + var app = this; + + $.ajax({ + complete: function() { + if (!options.app) { + // Preserve the app reference between consecutive calls + options.app = app; + } + setTimeout(options.app.AJAXperiodicWorker, options.interval, options); + }, + success: function(data) { + if (options.callback) { + // Conver the callback string to an actual function + var callbackFunction = window; + + $.each(options.callback.split('.'), function (index, value) { + callbackFunction = callbackFunction[value] + }); + + callbackFunction(options, data); + } else { + options.element.text(data[options.attributeName]); + } + }, + url: options.APIURL + }); + } + + doToastrMessages () { + toastr.options = { + 'closeButton': true, + 'debug': false, + 'newestOnTop': true, + 'positionClass': 'toast-top-right', + 'preventDuplicates': false, + 'onclick': null, + 'showDuration': '300', + 'hideDuration': '1000', + 'timeOut': '5000', + 'extendedTimeOut': '1000', + 'showEasing': 'swing', + 'hideEasing': 'linear', + 'showMethod': 'fadeIn', + 'hideMethod': 'fadeOut' + } + + // Add invisible bootstrap messages to copy the styles to toastr.js + + $('body').append('\ +
text
\ +text
\ +text
\ +text
\ +text
\ -text
\ -text
\ -text
\ -' + jqXHR.responseText + '');
- } else {
- if (jqXHR.status == 0) {
- $('#modal-server-error .modal-body').html($('#template-error').html());
- $('#modal-server-error').modal('show')
- } else {
- $('#ajax-content').html(jqXHR.responseText);
- }
- }
-}
-
-PartialNavigation.prototype.setLocation = function (newLocation, pushState) {
- /*
- * Method to update the browsers history and trigger a page update.
- */
-
- // Validate the new location first.
- newLocation = this.filterLocation(newLocation);
-
- if (typeof pushState === 'undefined') {
- // Check if we should just load the content or load the content
- // and update the history.
- pushState = true;
- }
-
- var currentLocation = new URI(location);
- currentLocation.fragment(newLocation);
-
- if (pushState) {
- history.pushState({}, '', currentLocation);
- }
- this.loadAjaxContent(newLocation);
-}
-
-PartialNavigation.prototype.setupAjaxAnchors = function () {
- /*
- * Setup the new click event handler.
- */
- var app = this;
- $('body').on('click', 'a', function (event) {
- app.onAnchorClick($(this), event);
- });
-}
-
-PartialNavigation.prototype.setupAjaxForm = function () {
- /*
- * Method to setup the handling of form in an AJAX way.
- */
- var app = this;
- var lastAjaxFormData = {};
-
- $('form').ajaxForm({
- async: true,
- beforeSerialize: function($form, options) {
- // Manage any callback registered to preprocess the form.
- $.each(app.formBeforeSerializeCallbacks, function (index, value) {
- value($form, options);
- });
- },
- beforeSubmit: function(arr, $form, options) {
- var uri = new URI(location);
- var uriFragment = uri.fragment();
- var url = $form.attr('action') || uriFragment;
-
- options.url = url;
- lastAjaxFormData.url = url + '?' + decodeURIComponent($form.serialize());
-
- if ($form.attr('target') == '_blank') {
- // If the form has a target attribute we emulate it by
- // opening a new window and passing the form serialized
- // data as the query.
- window.open(
- $form.attr('action') + '?' + decodeURIComponent($form.serialize())
- );
-
- return false;
- }
- },
- dataType: 'html',
- delegation: true,
- error: function(jqXHR, textStatus, errorThrown){
- app.processAjaxRequestError(jqXHR);
- },
- mimeType: 'text/html; charset=utf-8', // ! Need set mimeType only when run from local file
- success: function(data, textStatus, request){
- if (request.status == 278) {
- // Handle redirects after submitting the form
- var newLocation = request.getResponseHeader('Location');
- var uri = new URI(newLocation);
- var uriFragment = uri.fragment();
- var currentUri = new URI(window.location.hash);
- var currentUriFragment = currentUri.fragment();
- var url = uriFragment || currentUriFragment;
-
- app.setLocation(newLocation);
- } else {
- var currentUri = new URI(window.location.hash);
- currentUri.fragment(lastAjaxFormData.url);
- history.pushState({}, '', currentUri);
- $('#ajax-content').html(data);
- }
+ if (!this.initialURL) {
+ alert('Need to setup initialURL');
}
- });
-}
+ }
-PartialNavigation.prototype.setupAjaxNavigation = function () {
- /*
- * Setup the navigation method using the hash of the location.
- * Also handles the back button event and loads via AJAX any
- * URL in the location when the app first launches. Registers
- * a callback to send an emulated HTTP_REFERER so that the backends
- * code will still work without change.
- */
- var app = this;
+ initialize () {
+ this.setupAjaxAnchors();
+ this.setupAjaxNavigation();
+ this.setupAjaxForm();
+ }
- // Load ajax content when the hash changes
- if (window.history && window.history.pushState) {
- $(window).on('popstate', function() {
- var uri = new URI(location);
- var uriFragment = uri.fragment();
- app.setLocation(uriFragment, false);
+ filterLocation (newLocation) {
+ /*
+ * Method to validate new locations
+ */
+ var uri = new URI(newLocation);
+ var currentLocation = new URI(location);
+
+ if (uri.path() === '') {
+ // href with no path remain in the same location
+ // We strip the same location query and use the new href's one
+ uri.path(
+ new URI(currentLocation.fragment()).path()
+ )
+ return uri.toString();
+ }
+
+ if (uri.path() === '/') {
+ // Root URL is not allowed
+ return this.initialURL;
+ }
+
+ return newLocation;
+ }
+
+ loadAjaxContent (url) {
+ /*
+ * Method to load and display partial backend views to the main
+ * view port.
+ */
+ var app = this;
+
+ url = this.filterLocation(url);
+ $.ajax({
+ async: true,
+ mimeType: 'text/html; charset=utf-8', // ! Need set mimeType only when run from local file
+ url: url,
+ type: 'GET',
+ success: function (data, textStatus, response){
+ if (response.status == 278) {
+ // Handle redirects
+ var newLocation = response.getResponseHeader('Location');
+
+ app.setLocation(newLocation);
+ app.lastLocation = newLocation;
+ } else {
+ app.lastLocation = url;
+ if (response.getResponseHeader('Content-Disposition')) {
+ window.location = this.url;
+ } else {
+ $('#ajax-content').html(data);
+ }
+ }
+ },
+ error: function (jqXHR, textStatus, errorThrown){
+ app.processAjaxRequestError(jqXHR);
+ },
+ dataType: 'html',
});
}
- // Load any initial address in the URL of the browser
- if (window.location.hash) {
- var uri = new URI(window.location.hash);
- var uriFragment = uri.fragment();
- this.setLocation(uriFragment);
- } else {
- this.setLocation('/');
+ onAnchorClick ($this, event) {
+ /*
+ * Anchor click event manager. We intercept all click events and
+ * route them to load the content via AJAX instead.
+ */
+ var url;
+
+ if ($this.hasAnyClass(this.excludeAnchorClasses)) {
+ return true;
+ }
+
+ if ($this.hasAnyClass(this.disabledAnchorClasses)) {
+ event.preventDefault();
+ return false;
+ }
+
+ if ($this.parents().hasAnyClass(this.disabledAnchorClasses)) {
+ event.preventDefault();
+ return false;
+ }
+
+ url = $this.attr('href');
+ if (url === undefined) {
+ return true;
+ }
+
+ if (url.indexOf('javascript:;') > -1) {
+ // Ignore links meant to execute javascript on click.
+ return true;
+ }
+
+ if (url === '#') {
+ // Ignore links with hash at the.
+ return true;
+ }
+
+ event.preventDefault();
+
+ if (event.ctrlKey) {
+ window.open(url);
+ return false;
+ }
+
+ if (!($this.hasClass('disabled') || $this.parent().hasClass('disabled'))) {
+ this.setLocation(url);
+ }
}
- $.ajaxSetup({
- beforeSend: function (jqXHR, settings) {
- // Emulate the HTTP_REFERER.
- jqXHR.setRequestHeader('X-Alt-Referer', app.lastLocation);
- },
- });
+ processAjaxRequestError (jqXHR) {
+ /*
+ * Method to process an AJAX request and make it presentable to the
+ * user.
+ */
+
+ if (djangoDEBUG) {
+ $('#ajax-content').html('' + jqXHR.responseText + '');
+ } else {
+ if (jqXHR.status == 0) {
+ $('#modal-server-error .modal-body').html($('#template-error').html());
+ $('#modal-server-error').modal('show')
+ } else {
+ $('#ajax-content').html(jqXHR.responseText);
+ }
+ }
+ }
+
+ setLocation (newLocation, pushState) {
+ /*
+ * Method to update the browsers history and trigger a page update.
+ */
+
+ // Validate the new location first.
+ newLocation = this.filterLocation(newLocation);
+
+ if (typeof pushState === 'undefined') {
+ // Check if we should just load the content or load the content
+ // and update the history.
+ pushState = true;
+ }
+
+ var currentLocation = new URI(location);
+ currentLocation.fragment(newLocation);
+
+ if (pushState) {
+ history.pushState({}, '', currentLocation);
+ }
+ this.loadAjaxContent(newLocation);
+ }
+
+ setupAjaxAnchors () {
+ /*
+ * Setup the new click event handler.
+ */
+ var app = this;
+ $('body').on('click', 'a', function (event) {
+ app.onAnchorClick($(this), event);
+ });
+ }
+
+ setupAjaxForm () {
+ /*
+ * Method to setup the handling of form in an AJAX way.
+ */
+ var app = this;
+ var lastAjaxFormData = {};
+
+ $('form').ajaxForm({
+ async: true,
+ beforeSerialize: function($form, options) {
+ // Manage any callback registered to preprocess the form.
+ $.each(app.formBeforeSerializeCallbacks, function (index, value) {
+ value($form, options);
+ });
+ },
+ beforeSubmit: function(arr, $form, options) {
+ var uri = new URI(location);
+ var uriFragment = uri.fragment();
+ var url = $form.attr('action') || uriFragment;
+
+ options.url = url;
+ lastAjaxFormData.url = url + '?' + decodeURIComponent($form.serialize());
+
+ if ($form.attr('target') == '_blank') {
+ // If the form has a target attribute we emulate it by
+ // opening a new window and passing the form serialized
+ // data as the query.
+ window.open(
+ $form.attr('action') + '?' + decodeURIComponent($form.serialize())
+ );
+
+ return false;
+ }
+ },
+ dataType: 'html',
+ delegation: true,
+ error: function(jqXHR, textStatus, errorThrown){
+ app.processAjaxRequestError(jqXHR);
+ },
+ mimeType: 'text/html; charset=utf-8', // ! Need set mimeType only when run from local file
+ success: function(data, textStatus, request){
+ if (request.status == 278) {
+ // Handle redirects after submitting the form
+ var newLocation = request.getResponseHeader('Location');
+ var uri = new URI(newLocation);
+ var uriFragment = uri.fragment();
+ var currentUri = new URI(window.location.hash);
+ var currentUriFragment = currentUri.fragment();
+ var url = uriFragment || currentUriFragment;
+
+ app.setLocation(newLocation);
+ } else {
+ var currentUri = new URI(window.location.hash);
+ currentUri.fragment(lastAjaxFormData.url);
+ history.pushState({}, '', currentUri);
+ $('#ajax-content').html(data);
+ }
+ }
+ });
+ }
+
+ setupAjaxNavigation () {
+ /*
+ * Setup the navigation method using the hash of the location.
+ * Also handles the back button event and loads via AJAX any
+ * URL in the location when the app first launches. Registers
+ * a callback to send an emulated HTTP_REFERER so that the backends
+ * code will still work without change.
+ */
+ var app = this;
+
+ // Load ajax content when the hash changes
+ if (window.history && window.history.pushState) {
+ $(window).on('popstate', function() {
+ var uri = new URI(location);
+ var uriFragment = uri.fragment();
+ app.setLocation(uriFragment, false);
+ });
+ }
+
+ // Load any initial address in the URL of the browser
+ if (window.location.hash) {
+ var uri = new URI(window.location.hash);
+ var uriFragment = uri.fragment();
+ this.setLocation(uriFragment);
+ } else {
+ this.setLocation('/');
+ }
+
+ $.ajaxSetup({
+ beforeSend: function (jqXHR, settings) {
+ // Emulate the HTTP_REFERER.
+ jqXHR.setRequestHeader('X-Alt-Referer', app.lastLocation);
+ },
+ });
+ }
}
diff --git a/mayan/apps/appearance/templates/appearance/base_plain.html b/mayan/apps/appearance/templates/appearance/base_plain.html
index 6c33c4fd4a..a2a9a8b82f 100644
--- a/mayan/apps/appearance/templates/appearance/base_plain.html
+++ b/mayan/apps/appearance/templates/appearance/base_plain.html
@@ -31,16 +31,6 @@
if (currentHash.length) {
window.location = currentHash.substring(1);
}
-
- function waitForJQuery(func) {
- if (window.jQuery) {
- func();
- } else {
- setTimeout(function() {
- waitForJQuery(func)
- }, 50);
- }
- }
diff --git a/mayan/apps/appearance/templates/appearance/root.html b/mayan/apps/appearance/templates/appearance/root.html
index 8eff3b1735..f94aa88285 100644
--- a/mayan/apps/appearance/templates/appearance/root.html
+++ b/mayan/apps/appearance/templates/appearance/root.html
@@ -27,17 +27,6 @@
{% block stylesheets %}{% endblock %}
-
{% if appearance_type == 'plain' %}
@@ -152,15 +141,28 @@
-
-
+