/** * Base logic for all jQuery Mobile applications. * * @author Michael J. Rubinsky * @author Jan Schneider * @author Michael Slusarz * @copyright 2010-2015 Horde LLC * @license LGPL-2.1 (http://www.horde.org/licenses/lgpl21) */ var HordeMobile = { notify_handler: function(m) { return HordeMobile.showNotifications(m); }, // page_init: false, // regenerate_sid: false, serverError: 0, loading: 0, /** * Common URLs. * * Set by Horde_PageOutput: * - ajax_url: AJAX endpoint. * - logout_url: Logout URL. * - token: AJAX session token. */ conf: {}, /** * Debug. */ debug: function(label, e) { if (!HordeMobile.is_logout && window.console && window.console.error) { window.console.error(label, navigator.userAgent.indexOf("Gecko") > -1 ? e : jQuery.makeArray(e)); } }, /** * Perform an Ajax action * * @param string action The AJAX request method. * @param object params The parameter hash for the AJAX request. * @param function callback A callback function for successful request. * @param object opts Additional options for jQuery.ajax(). Also: * - submit: (jQuery object) Send action via a submit action (since * 2.5.0). Value is the form element. The 'form.js' plugin * must be loaded before this can be called. * * @return jqXHR jQuery XHR object. */ doAction: function(action, params, callback, opts) { opts = opts || {}; params = params || {}; params.token = HordeMobile.conf.token; if (HordeMobile.conf.sid) { $.extend(params, HordeMobile.conf.sid.toQueryParams()); } if (HordeMobile.regenerate_sid) { params.regenerate_sid = 1; opts.async = false; delete HordeMobile.regenerate_sid; } HordeMobile.loading++; $.mobile.loading('show'); opts = $.extend({ data: params, error: $.noop, success: function(d) { HordeMobile.doActionComplete(action, d, callback); }, type: 'post', url: HordeMobile.conf.ajax_url + action }, opts); if (opts.submit) { var tmp = opts.submit, tmp2; delete opts.submit; tmp2 = tmp.ajaxSubmit(opts); return tmp2.data('jqxhr'); } else { return $.ajax(opts); } }, doActionComplete: function(action, d, callback) { var r = d.response; HordeMobile.inAjaxCallback = true; if (d.reload) { if (d.reload === true) { window.location.reload(); } else { window.location.assign(d.reload); } return; } HordeMobile.notify_handler(d.msgs || []); if (d.tasks) { $(document).trigger('HordeMobile:runTasks', d.tasks); } if (r) { $(document).trigger('HordeMobile:doActionCompleteBefore', { action: action, response: r }); if ($.isFunction(callback)) { try { callback(r); } catch (e) { HordeMobile.debug('doActionComplete', e); } } $(document).trigger('HordeMobile:doActionCompleteAfter', { action: action, response: r }); } HordeMobile.inAjaxCallback = false; if (!(--HordeMobile.loading)) { $.mobile.loading('hide'); } }, /** * Output a notification. * * @param object msgs */ showNotifications: function(msgs) { if (!msgs.length || HordeMobile.is_logout) { return; } if (!$.mobile.pageContainer) { window.setTimeout(function() { HordeMobile.showNotifications(msgs); }, 100); return; } $.each(msgs, function(key, m) { switch (m.type) { case 'horde.ajaxtimeout': HordeMobile.logout(m.message); return false; case 'horde.error': case 'horde.message': case 'horde.success': case 'horde.warning': $('#horde-notification').growler('notify', m.message, m.type, { raw: (m.flags && $.inArray('content.raw', m.flags) != -1), sticky: (m.type == 'horde.error') }); break; } }); }, /** * Run tasks. */ runTasks: function(e, d) { var v; if (d['horde:regenerate_sid']) { HordeMobile.regenerate_sid = true; } if ((v = d['horde:sid'])) { HordeMobile.conf.sid = v; } }, /** * Logout. * * @param string url Use this URL instead of the default. */ logout: function(url) { HordeMobile.is_logout = true; window.location = (url || HordeMobile.conf.logout_url); }, /** * Wrapper around $.mobile.changePage() to do Horde framework specifc * tasks. * * @param string|object page The page to navigate to. * @param object data The request data object. * @param object opts Options to pass to $.mobile.changePage. */ changePage: function(page, data, opts) { opts = opts || {}; if (data) { opts.dataUrl = data.toPage; } opts.changeHash = false; $.mobile.changePage($('#' + page), opts); }, /** * Returns the current page ID. * * @return string The page ID, or null if no page is loaded. */ currentPage: function(page) { return $.mobile.activePage ? $.mobile.activePage.attr('id') : null; }, /** * Create a URL to a smartmobile page. * * @param string page The page name. * @param object params URL parameters. */ createUrl: function(page, params) { var url = '#' + page, tmp = []; params = params || {}; if (!$.isEmptyObject(params)) { $.each(params, function(k, v) { tmp.push(k + '=' + (typeof(v) == 'undefined' ? '' : v)); }); url += '?' + tmp.join('&'); } return url; }, /** * Manually update hash: jqm exits too early if calling changePage() with * the same page but different hash parameters. * * @param object url A parsed URL object. */ updateHash: function(url) { $.mobile.urlHistory.ignoreNextHashChange = true; $.mobile.path.set(url.parsed.hash); }, /** * Commands to run when changing a page. */ onPageBeforeChange: function(e, data) { /* This code is needed for deep hash linking with parameters, since * the jquery mobile code will consider these hashes invalid and will * load the first page instead. */ if (!this.page_init && !$.mobile.activePage && typeof data.toPage !== 'string') { data.toPage = location.href; } /* Add view/parameter data to dataUrl: * - params: (object) List of URL parameters. * - parsed: (object) Parsed URL object. * - view: (string) The current view (URL hash value). */ if (typeof data.toPage === 'string') { var parsed = $.mobile.path.parseUrl(data.toPage), match = /^#([^?]*)/.exec(parsed.hash); /* Sanity checking - make sure view exists. */ if (!this.page_init && match && !$(match[0]).size()) { data.toPage = parsed.hrefNoHash; parsed = $.mobile.path.parseUrl(data.toPage); match = undefined; } data.options.parsedUrl = { params: $.extend({}, parsed.search.toQueryParams(), parsed.hash.toQueryParams()), parsed: parsed, view: match ? match[1] : ($.mobile.firstPage ? $.mobile.firstPage.attr('id') : undefined) }; } else { data.options.parsedUrl = {}; if (typeof(data.toPage) != 'undefined' && data.toPage.attr('data-role') == 'dialog') { data.options.changeHash = false; } } this.page_init = true; }, onPageChange: function(e, data) { $.mobile.navigate(data.options.dataUrl || ('#' + HordeMobile.currentPage()), {}, true); }, /** * Commands to run when the DOM is ready. */ onDocumentReady: function() { // Global ajax options. $.ajaxSetup({ dataFilter: function(data) { // Remove json security token filter = /^\/\*-secure-([\s\S]*)\*\/s*$/; return data.replace(filter, "$1"); } }); // Setup notifications $('#horde-notification').growler(); // Fix swipe threshold. $.event.special.swipe.horizontalDistanceThreshold = 50; } }; $(HordeMobile.onDocumentReady); $(document).on('pagebeforechange', HordeMobile.onPageBeforeChange) .on('pagechange', HordeMobile.onPageChange) .on('HordeMobile:runTasks', HordeMobile.runTasks);