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('\ + \ + \ + \ + \ + '); + + // Copy the bootstrap style from the sample alerts to toaster.js via + // dynamic document style tag + + $('head').append('\ + \ + '); + + $.each(DjangoMessages, function (index, value) { + var options = {}; + + if (value.tags === 'error') { + // Error messages persist + options['timeOut'] = 0; + } + if (value.tags === 'warning') { + // Error messages persist + options['timeOut'] = 10000; + } + + toastr[value.tags](value.message, '', options); + }); + } + + initialize () { + this.setupAJAXperiodicWorkers(); + this.setupAutoSubmit(); + this.setupFullHeightResizing(); + this.setupItemsSelector(); + this.setupNavbarCollapse(); + this.setupNewWindowAnchor(); + partialNavigation.initialize(); + } + + setupAJAXperiodicWorkers () { + var app = this; + + $('a[data-apw-url]').each(function() { var $this = $(this); - formArray[$this.attr('name')] = $this.attr('value'); + + app.AJAXperiodicWorker({ + attributeName: $this.data('apw-attribute'), + APIURL: $this.data('apw-url'), + callback: $this.data('apw-callback'), + element: $this, + interval: $this.data('apw-interval'), + }); }); - - // Set the form data as the data to send - options.data = formArray; - } -} - -App.tagSelectionTemplate = function (tag, container) { - var $tag = $( - ' ' + tag.text + '' - ); - container[0].style.background = tag.element.dataset.color; - return $tag; -} - -App.tagResultTemplate = function (tag) { - if (!tag.element) { return ''; } - var $tag = $( - ' ' + tag.text + '' - ); - return $tag; -} - -// Instance methods - -App.prototype.AJAXperiodicWorker = function (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 - }); -} - -App.prototype.doToastrMessages = function () { - 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('\ - \ - \ - \ - \ - '); - - // Copy the bootstrap style from the sample alerts to toaster.js via - // dynamic document style tag - - $('head').append('\ - \ - '); - - $.each(DjangoMessages, function (index, value) { - var options = {}; - - if (value.tags === 'error') { - // Error messages persist - options['timeOut'] = 0; - } - if (value.tags === 'warning') { - // Error messages persist - options['timeOut'] = 10000; - } - - toastr[value.tags](value.message, '', options); - }); -} - -App.prototype.setupAJAXperiodicWorkers = function () { - var app = this; - - $('a[data-apw-url]').each(function() { - var $this = $(this); - - app.AJAXperiodicWorker({ - attributeName: $this.data('apw-attribute'), - APIURL: $this.data('apw-url'), - callback: $this.data('apw-callback'), - element: $this, - interval: $this.data('apw-interval'), + setupAutoSubmit () { + $('body').on('change', '.select-auto-submit', function () { + if ($(this).val()) { + $(this.form).trigger('submit'); + } }); - }); -} + } -App.prototype.setupAutoSubmit = function () { - $('body').on('change', '.select-auto-submit', function () { - if ($(this).val()) { - $(this.form).trigger('submit'); - } - }); -} + setupFullHeightResizing () { + var self = this; -App.prototype.setupNavbarCollapse = function () { - $(document).keyup(function(e) { - if (e.keyCode === 27) { - $('.navbar-collapse').collapse('hide'); - } - }); + this.resizeFullHeight(); - $('body').on('click', 'a', function (event) { - if (!$(this).hasAnyClass(['dropdown-toggle'])) { - $('.navbar-collapse').collapse('hide'); - } - }); -} + this.window.resize(function() { + self.resizeFullHeight(); + }); + } -App.prototype.setupNewWindowAnchor = function () { - $('body').on('click', 'a.new_window', function (event) { - event.preventDefault(); - var newWindow = window.open($(this).attr('href'), '_blank'); - newWindow.focus(); - }); -} + setupItemsSelector () { + var app = this; + app.lastChecked = null; -App.prototype.setupScrollView = function () { - $('.scrollable').scrollview(); -} - -App.prototype.setupItemsSelector = function () { - var app = this; - app.lastChecked = null; - - $('body').on('click', '.check-all', function (event) { - var checked = $(event.target).prop('checked'); - var $checkBoxes = $('.check-all-slave'); - - $checkBoxes.prop('checked', checked); - $checkBoxes.trigger('change'); - }); - - $('body').on('click', '.check-all-slave', function(e) { - if(!app.lastChecked) { - app.lastChecked = this; - return; - } - if(e.shiftKey) { + $('body').on('click', '.check-all', function (event) { + var checked = $(event.target).prop('checked'); var $checkBoxes = $('.check-all-slave'); - var start = $checkBoxes.index(this); - var end = $checkBoxes.index(app.lastChecked); + $checkBoxes.prop('checked', checked); + $checkBoxes.trigger('change'); + }); - $checkBoxes.slice( - Math.min(start,end), Math.max(start,end) + 1 - ).prop('checked', app.lastChecked.checked).trigger('change'); - } - app.lastChecked = this; - }) -} - -App.prototype.setupSelect2 = function () { - $('.select2').select2({ - dropdownAutoWidth: true, - width: '100%' - }); - - $('.select2-tags').select2({ - templateSelection: App.tagSelectionTemplate, - templateResult: App.tagResultTemplate, - width: '100%' - }); -} - -App.prototype.setupFullHeightResizing = function () { - var self = this; - - this.resizeFullHeight(); - - this.window.resize(function() { - self.resizeFullHeight(); - }); -} - -App.prototype.resizeFullHeight = function () { - $('.full-height').height(this.window.height() - $('.full-height').data('height-difference')); + $('body').on('click', '.check-all-slave', function(e) { + if(!app.lastChecked) { + app.lastChecked = this; + return; + } + if(e.shiftKey) { + var $checkBoxes = $('.check-all-slave'); + + var start = $checkBoxes.index(this); + var end = $checkBoxes.index(app.lastChecked); + + $checkBoxes.slice( + Math.min(start,end), Math.max(start,end) + 1 + ).prop('checked', app.lastChecked.checked).trigger('change'); + } + app.lastChecked = this; + }) + } + + setupNavbarCollapse () { + $(document).keyup(function(e) { + if (e.keyCode === 27) { + $('.navbar-collapse').collapse('hide'); + } + }); + + $('body').on('click', 'a', function (event) { + if (!$(this).hasAnyClass(['dropdown-toggle'])) { + $('.navbar-collapse').collapse('hide'); + } + }); + } + + setupNewWindowAnchor () { + $('body').on('click', 'a.new_window', function (event) { + event.preventDefault(); + var newWindow = window.open($(this).attr('href'), '_blank'); + newWindow.focus(); + }); + } + + setupScrollView () { + $('.scrollable').scrollview(); + } + + setupSelect2 () { + $('.select2').select2({ + dropdownAutoWidth: true, + width: '100%' + }); + + $('.select2-tags').select2({ + templateSelection: MayanApp.tagSelectionTemplate, + templateResult: MayanApp.tagResultTemplate, + width: '100%' + }); + } + + resizeFullHeight () { + $('.full-height').height(this.window.height() - $('.full-height').data('height-difference')); + } } diff --git a/mayan/apps/appearance/static/appearance/js/mayan_image.js b/mayan/apps/appearance/static/appearance/js/mayan_image.js index 333e1df518..d8db8a9290 100644 --- a/mayan/apps/appearance/static/appearance/js/mayan_image.js +++ b/mayan/apps/appearance/static/appearance/js/mayan_image.js @@ -1,79 +1,80 @@ 'use strict'; -var MayanImage = function (options) { - this.element = options.element; - this.load(); -} +class MayanImage { + constructor (options) { + this.element = options.element; + this.load(); + } -MayanImage.intialize = function () { - var app = this; + static intialize () { + var app = this; - this.fancybox = $().fancybox({ - animationDuration : 300, - buttons : [ - 'fullScreen', - 'close', - ], - selector: 'a.fancybox', - afterShow: function (instance, current) { - $('a.a-caption').on('click', function(event) { - instance.close(true); - }); - }, - infobar: true, + this.fancybox = $().fancybox({ + animationDuration : 300, + buttons : [ + 'fullScreen', + 'close', + ], + selector: 'a.fancybox', + afterShow: function (instance, current) { + $('a.a-caption').on('click', function(event) { + instance.close(true); + }); + }, + infobar: true, - }); + }); - $('img.lazy-load').lazyload({ - appear: function(elements_left, settings) { - new MayanImage({element: $(this)}); - }, - threshold: 400, - }); + $('img.lazy-load').lazyload({ + appear: function(elements_left, settings) { + new MayanImage({element: $(this)}); + }, + threshold: 400, + }); - $('img.lazy-load-carousel').lazyload({ - appear: function(elements_left, settings) { - new MayanImage({element: $(this)}); - }, - container: $('#carousel-container'), - threshold: 2000, - }); + $('img.lazy-load-carousel').lazyload({ + appear: function(elements_left, settings) { + new MayanImage({element: $(this)}); + }, + container: $('#carousel-container'), + threshold: 2000, + }); - $('.lazy-load').on('load', function() { - $(this).hide(); - $(this).fadeIn(300); - $(this).siblings('.spinner-container').remove(); - $(this).removeClass('lazy-load pull-left'); - }); + $('.lazy-load').on('load', function() { + $(this).hide(); + $(this).fadeIn(300); + $(this).siblings('.spinner-container').remove(); + $(this).removeClass('lazy-load pull-left'); + }); - $('.lazy-load-carousel').on('load', function() { - $(this).hide(); - $(this).fadeIn(300); - $(this).siblings('.spinner-container').remove(); - $(this).removeClass('lazy-load-carousel pull-left'); - }); + $('.lazy-load-carousel').on('load', function() { + $(this).hide(); + $(this).fadeIn(300); + $(this).siblings('.spinner-container').remove(); + $(this).removeClass('lazy-load-carousel pull-left'); + }); + } + + static timerFunction () { + $.fn.matchHeight._maintainScroll = true; + $.fn.matchHeight._update(); + } + + load () { + var self = this; + var container = this.element.parent().parent().parent(); + + this.element.on('error', (function(event) { + container.html(MayanImage.templateInvalidDocument); + })); + + this.element.attr('src', this.element.attr('data-url')); + $.fn.matchHeight._maintainScroll = true; + + clearTimeout(MayanImage.timer); + MayanImage.timer = setTimeout(MayanImage.timerFunction, 100); + }; } MayanImage.templateInvalidDocument = $('#template-invalid-document').html(); - MayanImage.timer = setTimeout(null); - -MayanImage.timerFunction = function () { - $.fn.matchHeight._maintainScroll = true; - $.fn.matchHeight._update(); -} - -MayanImage.prototype.load = function () { - var self = this; - var container = this.element.parent().parent().parent(); - - this.element.on('error', (function(event) { - container.html(MayanImage.templateInvalidDocument); - })); - - this.element.attr('src', this.element.attr('data-url')); - $.fn.matchHeight._maintainScroll = true; - - clearTimeout(MayanImage.timer); - MayanImage.timer = setTimeout(MayanImage.timerFunction, 100); -}; diff --git a/mayan/apps/appearance/static/appearance/js/partial_navigation.js b/mayan/apps/appearance/static/appearance/js/partial_navigation.js index b1590a0869..55e55811c9 100644 --- a/mayan/apps/appearance/static/appearance/js/partial_navigation.js +++ b/mayan/apps/appearance/static/appearance/js/partial_navigation.js @@ -13,274 +13,291 @@ $.fn.hasAnyClass = function() { return false; } -var PartialNavigation = function (parameters) { - parameters = parameters || {}; +class PartialNavigation { + constructor (parameters) { + parameters = parameters || {}; - // lastLocation - used as the AJAX referer - this.lastLocation = null; + // lastLocation - used as the AJAX referer + this.lastLocation = null; - // initialURL - the URL to send users when trying to access the / URL - this.initialURL = parameters.initialURL || null; + // initialURL - the URL to send users when trying to access the / URL + this.initialURL = parameters.initialURL || null; - // excludeAnchorClasses - Anchors with any of these classes will not be processes as AJAX anchors - this.excludeAnchorClasses = parameters.excludeAnchorClasses || []; + // disabledAnchorClasses - Anchors with any of these classes will not be + // processes as AJAX anchors and their events nulled + this.disabledAnchorClasses = parameters.disabledAnchorClasses || []; - // formBeforeSerializeCallbacks - Callbacks to execute before submitting an ajaxForm - this.formBeforeSerializeCallbacks = parameters.formBeforeSerializeCallbacks || []; + // excludeAnchorClasses - Anchors with any of these classes will not be + // processes as AJAX anchors + this.excludeAnchorClasses = parameters.excludeAnchorClasses || []; - if (!this.initialURL) { - alert('Need to setup initialURL'); - } -} + // formBeforeSerializeCallbacks - Callbacks to execute before submitting an ajaxForm + this.formBeforeSerializeCallbacks = parameters.formBeforeSerializeCallbacks || []; -PartialNavigation.prototype.initialize = function () { - this.setupAjaxAnchors(); - this.setupAjaxNavigation(); - this.setupAjaxForm(); -} - -PartialNavigation.prototype.filterLocation = function (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; -} - -PartialNavigation.prototype.loadAjaxContent = function (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', - }); -} - -PartialNavigation.prototype.onAnchorClick = function ($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; - } - - 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); - } -} - -PartialNavigation.prototype.processAjaxRequestError = function (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); - } - } -} - -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 @@ - - + {% endspaceless %} diff --git a/mayan/apps/events/links.py b/mayan/apps/events/links.py index 6551483324..2dd2e0eb36 100644 --- a/mayan/apps/events/links.py +++ b/mayan/apps/events/links.py @@ -80,7 +80,7 @@ link_user_notifications_list = Link( html_data={ 'apw-attribute': 'count', 'apw-interval': '5000', 'apw-url': '/api/notifications/?read=False', - 'apw-callback': 'App.mayanNotificationBadge' + 'apw-callback': 'MayanAppClass.mayanNotificationBadge' }, icon_class=icon_user_notifications_list, text='', view='events:user_notifications_list' ) diff --git a/mayan/apps/metadata/static/metadata/js/metadata_form.js b/mayan/apps/metadata/static/metadata/js/metadata_form.js index a39726fac8..8bbdc29912 100644 --- a/mayan/apps/metadata/static/metadata/js/metadata_form.js +++ b/mayan/apps/metadata/static/metadata/js/metadata_form.js @@ -1,11 +1,9 @@ 'use strict'; -waitForJQuery(function() { - jQuery(document).ready(function() { - $('.metadata-value').on('input', function(event) { - // Check the checkbox next to a metadata value input when there is - // data entry in the value's input. - $(event.target).parents('tr').find(':checkbox').prop('checked', true); - }); +jQuery(document).ready(function() { + $('.metadata-value').on('input', function(event) { + // Check the checkbox next to a metadata value input when there is + // data entry in the value's input. + $(event.target).parents('tr').find(':checkbox').prop('checked', true); }); });