(function (window) { var pagerJsModule = function ($, ko) { "use strict"; /** * @module pager * @readme README.md */ /** * * @param fn * @param scope * @return {Function} */ var makeComputed = function (fn, scope) { return function () { var args = arguments; return ko.computed(function () { return fn.apply(scope, args); }); }; }; var pager = {}; pager.page = null; pager.now = function () { if (!Date.now) { return (new Date()).valueOf(); } else { return Date.now(); } }; /** * @method extendWithPage * @static * @param {Observable} viewModel */ pager.extendWithPage = function (viewModel) { var page = new pager.Page(); viewModel['$__page__'] = page; pager.page = page; // initialize computed observables that depend on pager.page pager.activePage$ = makeComputed(pager.getActivePage, pager)(); }; var fire = function (scope, name, options) { options = options || {}; options.page = scope; // first fire the global method pager[name].fire(options); // then call the local callback if (scope.val(name)) { scope.val(name)(options); } }; // Add 9 callbacks on pager $.each(['onBindingError', 'onSourceError', 'onNoMatch', 'onMatch', 'beforeRemove', 'afterRemove', 'beforeHide', 'afterHide', 'beforeShow', 'afterShow'], function (i, n) { pager[n] = $.Callbacks(); }); /** * * @param {String[]} route */ pager.showChild = function (route) { // trim empty array var trimmedRoute = (route && route.length === 1 && route[0] === '') ? [] : route; pager.page.showPage(trimmedRoute); }; pager.getParentPage = function (bindingContext) { // search this context/$data until either root is accessed or no page is found while (bindingContext) { // get first parent page, but exclude pages with urlToggle: none if (bindingContext.$page && bindingContext.$page.val('urlToggle') !== 'none') { return bindingContext.$page; } else if (bindingContext.$data && bindingContext.$data.$__page__) { return bindingContext.$data.$__page__; } bindingContext = bindingContext.$parentContext; } return null; }; // set this to a random value in order to verify that the navigation should happen // Is cleaned after every goTo. var goToKey = null; var currentAsyncDeferred = null; /** * * Takes a complete, working, path as parameter. *Not* a route, relative route or Page-object. * * @param {String} path */ var goTo = function (path) { // reject any async navigation in progress if (currentAsyncDeferred) { currentAsyncDeferred.reject({cancel: true}); } goToKey = null; // strip # (or #!/) if (path.substring(0, pager.Href.hash.length) === pager.Href.hash) { path = path.slice(pager.Href.hash.length); } // split on '/' and decode var hashRoute = parseHash(path); // trigger navigation pager.showChild(hashRoute); }; pager.goTo = goTo; /** * * navigate takes a complete, working, path as parameter. *NOT* a route, object or pager.Page-object. * * @param {String} path */ pager.navigate = function (path) { if (pager.useHTML5history) { pager.Href5.history.pushState(null, null, path); } else { location.hash = path; } }; var parseHash = function (hash) { return $.map(hash.replace(/\+/g, ' ').split('/'), decodeURIComponent); }; // common KnockoutJS helpers var _ko = {}; _ko.value = ko.utils.unwrapObservable; _ko.arrayValue = function (arr) { return $.map(arr, function (e) { return _ko.value(e); }); }; var parseStringAsParameters = function (query) { var match, urlParams = {}, search = /([^&=]+)=?([^&]*)/g; while (match = search.exec(query)) { if (/\[\]$/.test(match[1])) { // handle array parameters var name = match[1].replace(/\[\]$/, ''); if (!(name in urlParams)) { urlParams[name] = []; } urlParams[name].push(match[2]); } else { // single (non-array) parameters urlParams[match[1]] = match[2]; } } return urlParams; }; var splitRoutePartIntoNameAndParameters = function (routePart) { if (!routePart) { return {name: null, params: {}}; } var routeSplit = routePart.split('?'); var name = routeSplit[0]; var paramsString = routeSplit[1]; var params = {}; if (paramsString) { params = parseStringAsParameters(paramsString); } return { name: name, params: params }; }; /** * @class pager.ChildManager * * @param {pager.Page[]} children * @param {pager.Page} page */ pager.ChildManager = function (children, page) { this.currentChildO = ko.observable(null); var me = this; this.page = page; // Used by showChild to find out if the navigation is still current. // In needed since the navigation is asynchronous and another navigation might happen in between. this.timeStamp = pager.now(); /** * @method pager.ChildManager#hideChild */ this.hideChild = function () { var currentChild = me.currentChildO(); if (currentChild) { currentChild.hidePage(function () { }); me.currentChildO(null); } }; /** * Show the sub-page in this ChildManager's page that matches the route. * * @method pager.ChildManager#showChild * @param {String[]} route route to match a sub-page to. Can be on the form `['foo','bar?x=22&y=11']`. */ this.showChild = function (route) { var showOnlyStart = route.length === 0; this.timeStamp = pager.now(); var timeStamp = this.timeStamp; var oldCurrentChild = me.currentChildO(); var currentChild = null; var match = false; var currentRoutePair = splitRoutePartIntoNameAndParameters(route[0]); var currentRoute = currentRoutePair.name; var wildcard = null; $.each(children(), function (childIndex, child) { if (!match) { var id = child.getId(); if (((pager.ignoreRouteCase && ((id || "").toLowerCase() === (currentRoute || "").toLowerCase())) || id === currentRoute) || ((currentRoute === '' || currentRoute == null) && child.isStartPage())) { match = true; currentChild = child; } if (id === '?') { wildcard = child; } } }); // find modals in parent - but only if 1) no match is found, 2) this page got a parent and 3) this page is not a modal! var isModal = false; var currentChildManager = me; var findMatchModalOrWildCard = function (childIndex, child) { if (!match) { var id = child.getId(); var modal = child.getValue().modal; if (modal) { if (id === currentRoute || ((currentRoute === '' || currentRoute == null) && child.isStartPage())) { match = true; currentChild = child; isModal = true; } if (id === '?' && !wildcard) { wildcard = child; isModal = true; } } } }; while (!currentChild && currentChildManager.page.parentPage && !currentChildManager.page.getValue().modal) { var parentChildren = currentChildManager.page.parentPage.children; $.each(parentChildren(), findMatchModalOrWildCard); if (!currentChild) { currentChildManager = currentChildManager.page.parentPage.childManager; } } if (!currentChild && wildcard && !showOnlyStart) { currentChild = wildcard; //me.currentChild.currentId = currentRoute; } me.currentChildO(currentChild); if (currentChild) { if (isModal) { currentChild.currentParentPage(me.page); } else { currentChild.currentParentPage(null); } } var onFailed = function () { fire(me.page, 'onNoMatch', {route: route}); }; var showCurrentChild = function() { fire(me.page, 'onMatch', { route: route }); var fired = 0; var guard = _ko.value(currentChild.getValue().guard); // If there is more than one guard for the route. if (guard && $.isArray(guard)) { $.each(guard, function(_, guard) { guard(currentChild, route, function() { fired++; // Allow route change only if all callbacks were fired. if (fired === guard.length - 1) { if (me.timeStamp === timeStamp) { currentChild.showPage(route.slice(1), currentRoutePair, route[0]); } } }, oldCurrentChild); }); } // There is only one guard. else if (guard) { guard(currentChild, route, function() { if (me.timeStamp === timeStamp) { currentChild.showPage(route.slice(1), currentRoutePair, route[0]); } }, oldCurrentChild); } else { currentChild.showPage(route.slice(1), currentRoutePair, route[0]); } }; if (oldCurrentChild && oldCurrentChild === currentChild) { showCurrentChild(); } else if (oldCurrentChild) { oldCurrentChild.hidePage(function () { if (currentChild) { showCurrentChild(); } else { onFailed(); } }); } else if (currentChild) { showCurrentChild(); } else { onFailed(); } }; }; /** * * @class pager.Page * * @constructor * * @param {Node} element * @param {Object} valueAccessor * @param {String} valueAccessor.id * @param {Observable} valueAccessor.with * @param {Function} valueAccessor.withOnShow * @param {String/Function} valueAccessor.source * @param {String/Function} valueAccessor.sourceLoaded * @param {Number/Boolean} valueAccessor.sourceCache * @param {String} valueAccessor.frame * @param {Boolean} valueAccessor.modal * @param {Boolean} valueAccessor.deep * @param {Function} valueAccessor.beforeHide * @param {Function} valueAccessor.beforeShow * @param {Function} valueAccessor.afterHide * @param {Function} valueAccessor.hideElement * @param {Function} valueAccessor.showElement * @param {Function} valueAccessor.loader * @param {Function} valueAccessor.onNoMatch * @param {Function} valueAccessor.guard * @param {Object} valueAccessor.params * @param {Object} valueAccessor.vars * @param {String} valueAccessor.fx * @param {String} valueAccessor.urlToggle can be either null (default), "none" or "show" * @param allBindingsAccessor * @param {Observable} viewModel * @param bindingContext */ pager.Page = function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { /** * * @type {Node} */ this.element = element; /** * * @type {Observable} */ this.valueAccessor = valueAccessor; /** * * @type {*} */ this.allBindingsAccessor = allBindingsAccessor; /** * * @type {Observable} */ this.viewModel = viewModel; /** * * @type {*} */ this.bindingContext = bindingContext; /** * * @type {ObservableArray} */ this.children = ko.observableArray([]); /** * * @type {pager.ChildManager} */ this.childManager = new pager.ChildManager(this.children, this); /** * * @type {pager.Page} */ this.parentPage = null; /** * * @type {String} */ this.currentId = null; this.getCurrentId = ko.observable(); /** * * * @type {Observable} */ this.ctx = null; /** * * @type {Observable/pager.Page} */ this.currentParentPage = ko.observable(null); /** * * @type {Observable} */ this.isVisible = ko.observable(false); this.originalRoute = ko.observable(null); this.route = null; }; var p = pager.Page.prototype; /** * @method pager.Page#val * * @param {String} key * @return {Object} an un-boxed configuration property */ p.val = function (key) { return _ko.value(this.getValue()[key]); }; /** * @method pager.Page#currentChildPage * * Returns an observable to the child page. * * @returns {Observable} */ p.currentChildPage = function () { return this.childManager.currentChildO; }; /** * * @param {String} key relative (to this page) or absolute page path * @return {Observable} page */ p.find = function (key) { var k = _ko.value(key); var currentRoot = this; if (k.substring(0, 1) === '/') { currentRoot = pager.page; k = k.slice(1); } else { while (k.substring(0, 3) === '../') { currentRoot = (currentRoot.currentParentPage && currentRoot.currentParentPage()) ? currentRoot.currentParentPage() : currentRoot.parentPage; k = k.slice(3); } } var route = parseHash(k); $.each(route, function (_, r) { currentRoot = currentRoot.child(r)(); }); return currentRoot; }; p.find$ = function (key) { return makeComputed(this.find, this)(key); }; var absolutePathToRealPath = function (path) { if (pager.useHTML5history) { return $('base').attr('href') + path; } else { return pager.Href.hash + path; } }; /** * * Utility method to generate a complete (computed observable) path relative to the current Page. * * @param {String/pager.Page/Object} path can either be relative path, a Page-object or a {path: params:}-object. * @return {String} */ p.path = function (path) { var me = this; var p = _ko.value(path); if (p && typeof(p) === 'object' && p.path && p.params && !(p instanceof pager.Page)) { var objectPath = p.path; var params = p.params; return me.path(objectPath) + '?' + $.param(params); } else { var page; if (p == null || p === '') { page = me; } else if (p instanceof pager.Page) { page = p; } else { // if string if (p.substring(0, 1) === '/') { var pagePath = pager.page.getFullRoute()().join('/') + p.substring(1); return absolutePathToRealPath(pagePath); } var parentsToTrim = 0; while (p.substring(0, 3) === '../') { parentsToTrim++; p = p.slice(3); } var fullRoute = me.getFullRoute()(); var parentPath = fullRoute.slice(0, fullRoute.length - parentsToTrim).join('/'); var fullPathWithoutHash = (parentPath === '' ? '' : parentPath + '/') + p; return absolutePathToRealPath(fullPathWithoutHash); } return absolutePathToRealPath(page.getFullRoute()().join('/')); } }; p.path$ = function (path) { return makeComputed(this.path, this)(path); }; /** * * @param {Function} fn should return a $.Deferred (NOT a promise since async should be able to reject it). * @param {String/Object} ok route (e.g. '/some/path' or '../some/path'). Should not contain '#!/'. * @param {String/Object} notOk route (e.g. '/some/path' or '../some/path'). Should not contain '#!/'. * @param {Function} [state] * @return {Function} */ p.async = function (fn, ok, notOk, state) { var me = this; return function () { if (currentAsyncDeferred) { currentAsyncDeferred.reject({cancel: true}); } var result = fn(); currentAsyncDeferred = result; if (state) { state(result.state()); } var key = Math.random(); goToKey = key; result.done(function () { if (state) { state(result.state()); } if (key === goToKey) { pager.navigate(me.path(ok)); } }); result.fail(function (data) { if (state) { state(result.state()); } var cancel = data && data.cancel; if (key === goToKey) { if (!cancel && notOk) { pager.navigate(me.path(notOk)); } } }); }; }; /** * @method showPage * @member pager.Page * * @param route * @param [pageRoute] * @param [originalRoute] */ p.showPage = function (route, pageRoute, originalRoute) { var m = this, currentId = m.currentId, params = m.pageRoute ? m.pageRoute.params : null, isVisible = m.isVisible(); m.currentId = pageRoute ? (pageRoute.name || '') : ''; m.getCurrentId(m.currentId); m.isVisible(true); if (originalRoute) { m.originalRoute(originalRoute); } m.route = route; m.pageRoute = pageRoute; // show if not already visible if (!isVisible) { m.setParams(); m.show(); } else { // show if wildcard got new ID if (m.getId() === '?' && currentId !== m.currentId) { m.show(); } // update params if they are updated if (pageRoute && params !== pageRoute.params) { m.setParams(); } } m.childManager.showChild(route); }; /** * @method setParams * @member pager.Page * */ p.setParams = function () { if (this.pageRoute && this.pageRoute.params) { var params = this.pageRoute.params; // get view model var vm = this.ctx; var userParams = this.val('params') || {}; // for each param for URL if ($.isArray(userParams)) { $.each(userParams, function (index, key) { var value = params[key]; if (vm[key]) { // set observable ... vm[key](value); } else { // ... or create observable vm[key] = ko.observable(value); } }); } else { $.each(userParams, function (key, defaultValue) { var value = params[key]; var runtimeValue; if (value == null) { runtimeValue = _ko.value(defaultValue); } else { runtimeValue = value; } if (vm[key]) { vm[key](runtimeValue); } else { vm[key] = ko.observable(runtimeValue); } }); } } if (this.pageRoute) { var nameParam = this.getValue()['nameParam']; if (nameParam) { if (typeof nameParam === 'string') { if (this.ctx[nameParam]) { // set observable ... this.ctx[nameParam](this.currentId); } else { // ... or create observable this.ctx[nameParam] = ko.observable(this.currentId); } } else { // is Observable nameParam(this.currentId); } } } }; /** * @method hidePage * @member pager.Page * * @param {Function} callback */ p.hidePage = function (callback) { var m = this; if ('show' !== m.val('urlToggle')) { m.hideElementWrapper(callback); m.childManager.hideChild(); } else { if (callback) { callback(); } } }; var applyBindingsToDescendants = function (page) { try { ko.applyBindingsToDescendants(page.childBindingContext, page.element); } catch (e) { if(!pager.onBindingError.has()) { if(window.console && window.console.error) { window.console.error(e); } } fire(page, 'onBindingError', {error: e}); } }; /** * @method init * @member pager.Page * * @return {Object} */ p.init = function () { var m = this; m.cleanElement = m.element.innerHTML; var urlToggle = m.val('urlToggle'); var id = m.val('id'); if (id !== '?') { m.getCurrentId(id); } var existingPage = ko.utils.domData.get(m.element, '__ko_pagerjsBindingData'); if (existingPage) { return { controlsDescendantBindings: true}; } else { ko.utils.domData.set(m.element, '__ko_pagerjsBindingData', m); } // listen to when the element is removed ko.utils.domNodeDisposal.addDisposeCallback(m.element, function () { // then remove this Page-instance fire(m, 'beforeRemove'); if (m.parentPage) { m.parentPage.children.remove(m); } fire(m, 'afterRemove'); }); var value = m.getValue(); if (urlToggle !== 'none') { m.parentPage = m.getParentPage(); m.parentPage.children.push(this); m.hideElement(); } // Fetch source if (m.val('source')) { m.loadSource(m.val('source')); } m.ctx = null; if (value.withOnShow) { m.ctx = {}; m.childBindingContext = m.bindingContext.createChildContext(m.ctx); ko.utils.extend(m.childBindingContext, { $page: this }); } else { var context = value['with'] || m.viewModel; m.ctx = _ko.value(context); m.augmentContext(); if (ko.isObservable(context)) { var dataInContext = ko.observable(m.ctx); m.childBindingContext = m.bindingContext.createChildContext(dataInContext); ko.utils.extend(m.childBindingContext, { $page: this }); applyBindingsToDescendants(m); context.subscribe(function () { dataInContext(_ko.value(context)); }); } else { m.childBindingContext = m.bindingContext.createChildContext(m.ctx); ko.utils.extend(m.childBindingContext, { $page: this, $observableData: undefined }); applyBindingsToDescendants(m); } } if (urlToggle !== 'none') { // check if this page should trigger showChild at parent var parent = m.parentPage; if (parent.route && (parent.route[0] === m.getId() || (parent.route.length && m.getId() === '?') )) { // call once the current event loop is finished. setTimeout(function () { parent.showPage(parent.route); }, 0); } } else { // urlToggle === 'none' // when the page is rendered var display = function () { // if the page is visible if ($(m.element).is(':visible')) { // trigger showPage with empty route-array m.showPage([]); } }; setTimeout(display, 0); m.getParentPage().isVisible.subscribe(function (x) { if (x) { setTimeout(display, 0); } }); } // Bind the page to the config property `bind` if it exists var bind = m.getValue()['bind']; if (ko.isObservable(bind)) { bind(m); } return { controlsDescendantBindings: true }; }; p.augmentContext = function () { var m = this; var params = m.val('params'); if (params) { if ($.isArray(params)) { $.each(params, function (index, param) { if (!m.ctx[param]) { m.ctx[param] = ko.observable(); } }); } else { // is object $.each(params, function (key, value) { if (!m.ctx[key]) { if (ko.isObservable(value)) { m.ctx[key] = value; } else if (value === null) { params[key] = ko.observable(null); m.ctx[key] = ko.observable(null); } else { m.ctx[key] = ko.observable(value); } } }); } } if (this.val('vars')) { $.each(this.val('vars'), function (key, value) { if (ko.isObservable(value)) { m.ctx[key] = value; } else { m.ctx[key] = ko.observable(value); } }); } var nameParam = this.getValue()['nameParam']; if (nameParam && typeof nameParam === 'string' && !m.ctx[nameParam]) { m.ctx[nameParam] = ko.observable(null); } this.setParams(); }; /** * @method pager.Page#getValue * @returns {Object} value */ p.getValue = function () { if (this.valueAccessor) { return _ko.value(this.valueAccessor()); } else { return {}; } }; /** * @method pager.Page#getParentPage * @return {pager.Page} */ p.getParentPage = function () { return pager.getParentPage(this.bindingContext); }; /** * @method pager.Page#getId * @return String */ p.getId = function () { return this.val('id'); }; p.id = function () { var currentId = this.getCurrentId(); if (currentId == null || currentId === '') { return this.getId(); } else { return currentId; } }; /** * @method pager.Page#sourceUrl * * @param {Observable/String} source * @return {Observable} */ p.sourceUrl = function (source) { var me = this; if (this.getId() === '?') { return ko.computed(function () { // TODO: maybe make currentId an ko.observable? var path; if (me.val('deep')) { path = [me.currentId].concat(me.route).join('/'); } else { path = me.currentId; } return _ko.value(source).replace('{1}', path); }); } else { return ko.computed(function () { return _ko.value(source); }); } }; p.loadWithOnShow = function (showCallback) { var me = this; if (!me.withOnShowLoaded || me.val('sourceCache') !== true) { me.val('withOnShow')(function (vm) { if (!me.val('sourceOnShow') && me.withOnShowLoaded) { ko.cleanNode($(me.element)); $(me.element).empty(); me.element.innerHTML = me.cleanElement; } var childBindingContext = me.bindingContext.createChildContext(vm); me.ctx = vm; // replace the childBindingContext me.childBindingContext = childBindingContext; me.augmentContext(); ko.utils.extend(childBindingContext, {$page: me}); applyBindingsToDescendants(me); me.showElementWrapper(showCallback); // what is signaling if a page is active or not? if (me.route) { me.childManager.showChild(me.route); } }, me); me.withOnShowLoaded = true; } else { me.showElementWrapper(showCallback); } }; /** * @method pager.Page#loadSource * @param source */ p.loadSource = function (source, showCallback) { var value = this.getValue(); var me = this; var element = this.element; var loader = null; var loaderMethod = value.loader || pager.loader; if (!me.val('withOnShow')) { me.showElementWrapper(showCallback); } if (value.frame === 'iframe') { var iframe = $('iframe', $(element)); if (iframe.length === 0) { iframe = $(''); $(element).append(iframe); } if (loaderMethod) { loader = _ko.value(loaderMethod)(me, iframe); loader.load(); } iframe.one('load', function () { if (loader) { loader.unload(); } if (value.sourceLoaded) { value.sourceLoaded(me); } }); // TODO: remove src binding and add this binding ko.applyBindingsToNode(iframe[0], { attr: { src: this.sourceUrl(source) } }); } else { if (loaderMethod) { loader = _ko.value(loaderMethod)(me, me.element); loader.load(); } // TODO: remove all children and add sourceUrl(source) var onLoad = function () { // remove load if (loader) { loader.unload(); } // apply bindings // TODO: call abstraction that either applies binding or loads view-model if (!me.val('withOnShow')) { applyBindingsToDescendants(me); } else if (me.val('withOnShow')) { me.loadWithOnShow(showCallback); } // trigger event if (value.sourceLoaded) { value.sourceLoaded(me); } // possibly continue routing if (me.route) { me.childManager.showChild(me.route); } }; if (typeof _ko.value(source) === 'string') { var s = _ko.value(this.sourceUrl(source)); koLoad(element, s, function () { onLoad(); }, me); } else { // should be a method var childrenToRemove = $(element).children(); _ko.value(source)(this, function () { $.each(childrenToRemove, function (i, c) { ko.utils.domNodeDisposal.removeNode(c); }); onLoad(); }); } } }; var rscript = /)<[^<]*)*<\/script>/gi; // a modified version of jQUery.fn.load, where the element is executing removeNode // before adding the new node. var koLoad = function (element, url, callback, page) { var selector, response, self = $(element), off = url.indexOf(" "); if (off >= 0) { selector = url.slice(off, url.length); url = url.slice(0, off); } // Request the remote document var loadPromise = jQuery.ajax({ url: url, type: 'GET', dataType: "html", complete: function (jqXHR, status) { if (callback) { self.each(callback, response || [ jqXHR.responseText, status, jqXHR ]); } } }).done(function (responseText) { // Save response for use in complete callback response = arguments; // clean up the element $.each(self.children(), function (i, c) { ko.utils.domNodeDisposal.removeNode(c); }); // See if a selector was specified self.html(selector ? // Create a dummy div to hold the results jQuery("
") // inject the contents of the document in, removing the scripts // to avoid any 'Permission Denied' errors in IE .append(responseText.replace(rscript, "")) // Locate the specified elements .find(selector) : // If not, just inject the full result responseText); }); loadPromise.fail(function () { fire(page, 'onSourceError', {url: url, xhrPromise: loadPromise}); }); return self; }; /** * @method pager.Page#show * @param {Function} [callback] */ p.show = function (callback) { var element = this.element; var me = this; //me.showElementWrapper(callback); //var value = me.getValue(); if (me.val('title')) { window.document.title = me.val('title'); } // Fetch source if (me.val('sourceOnShow')) { if (!me.val('sourceCache') || !element.__pagerLoaded__ || (typeof(me.val('sourceCache')) === 'number' && element.__pagerLoaded__ + me.val('sourceCache') * 1000 < pager.now())) { element.__pagerLoaded__ = pager.now(); me.loadSource(me.val('sourceOnShow'), callback); } else { me.showElementWrapper(callback); } } else if (me.val('withOnShow')) { me.loadWithOnShow(callback); } else { me.showElementWrapper(callback); } }; p.titleOrId = function () { return this.val('title') || this.id(); }; /** * @method pager.Page#showElementWrapper * @param {Function} callback */ p.showElementWrapper = function (callback) { var me = this; fire(me, 'beforeShow'); me.showElement(callback); if (me.val('scrollToTop')) { me.element.scrollIntoView(); } fire(me, 'afterShow'); }; /** * @method pager.Page#showElement * @param {Function} callback */ p.showElement = function (callback) { if (this.val('showElement')) { this.val('showElement')(this, callback); } else if (this.val('fx')) { pager.fx[this.val('fx')].showElement(this, callback); } else if (pager.showElement) { pager.showElement(this, callback); } else { $(this.element).show(callback); } }; /** * * @method pager.Page#hideElementWrapper * @param {Function} callback */ p.hideElementWrapper = function (callback) { this.isVisible(false); fire(this, 'beforeHide'); this.hideElement(callback); fire(this, 'afterHide'); }; /** * @method pager.Page#hideElement * @param {Function} [callback] */ p.hideElement = function (callback) { if (this.val('hideElement')) { this.val('hideElement')(this, callback); } else if (this.val('fx')) { pager.fx[this.val('fx')].hideElement(this, callback); } else if (pager.hideElement) { pager.hideElement(this, callback); } else { $(this.element).hide(); if (callback) { callback(); } } }; /** * * @return {Observable} */ p.getFullRoute = function () { // either return an already created computed observable if (this._fullRoute) { return this._fullRoute; } else { // or create a computed observable.. this._fullRoute = ko.computed(function () { var res = null; if (this.currentParentPage && this.currentParentPage()) { res = this.currentParentPage().getFullRoute()().slice(0); res.push((this.originalRoute() || this.getId())); return res; } else if (this.parentPage) { res = this.parentPage.getFullRoute()().slice(0); res.push((this.originalRoute() || this.getId())); return res; } else { // is root page return []; } }, this); // ... and return it return this._fullRoute; } }; /** * Return the role of the page (either `next` or `start`). * *
* * Specifying role `start` gives the page the same behaviour as if the page * had `{id: 'start'}`. * * @return {String} */ p.getRole = function () { return this.val('role') || 'next'; }; /** * @method pager.Page#isStartPage * * Returns true if id is start or role is start. * * @returns {boolean} */ p.isStartPage = function () { if (pager.ignoreRouteCase) { return (this.getId() || "").toLowerCase() === 'start' || (this.getRole() || "").toLowerCase() === 'start'; } else { return this.getId() === 'start' || this.getRole() === 'start'; } }; p.nullObject = new pager.Page(); p.nullObject.children = ko.observableArray([]); /** * Get the child page, by name, of the current child as a * computed observable. * * // get the child page of somePage, with the id admin * var adminObservable = somePage.child('admin'); * // run () to get the object, getId is a method on pager.Page * var id = adminObservable().getId(); * // id is admin * console.log(id === 'admin'); * * @param {String} key * @return {Observable} */ p.child = function (key) { var me = this; if (me._child == null) { me._child = {}; } if (!me._child[key]) { me._child[key] = ko.computed(function () { var child = $.grep(this.children(), function (c) { return c.id() === key; })[0]; return child || this.nullObject; }, this); } return me._child[key]; }; pager.getActivePage = function () { var active = pager.page; while (active.currentChildPage()() != null) { active = active.currentChildPage()(); } return active; }; ko.bindingHandlers.page = { init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { var page = null; if (_ko.value(valueAccessor()) instanceof pager.Page) { page = _ko.value(valueAccessor()); page.element = element; if (page.allBindingsAccessor == null) { page.allBindingsAccessor = allBindingsAccessor; } if (page.viewModel == null) { page.viewModel = viewModel; } if (page.bindingContext == null) { page.bindingContext = bindingContext; } } else { page = new pager.Page(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext); } return page.init(); } }; /** * * @type {string} */ var _dataAttribute = 'page-href'; /** * * @type {Boolean} */ pager.useHTML5history = false; /** * * @type {String} */ pager.rootURI = '/'; /** * * @type {Boolean} */ pager.ignoreRouteCase = false; /** * * @type {String} */ pager.dataAttribute = ko.computed({ read: function () { return _dataAttribute; }, write: function (value) { if(!value){ var err = new Error(); err.message = 'Data Attribute cannot be blank'; throw err; } if(ko.bindingHandlers[_dataAttribute]){ delete ko.bindingHandlers[_dataAttribute]; } _dataAttribute = value; ko.bindingHandlers[_dataAttribute] = koBindingHandlerMethod; } }); pager.Href = function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { this.element = element; this.bindingContext = bindingContext; this.path = ko.observable(); this.pageOrRelativePath = ko.observable(valueAccessor); }; var hp = pager.Href.prototype; hp.getParentPage = function () { return pager.getParentPage(this.bindingContext); }; hp.init = function () { var me = this; var page = me.getParentPage(); me.path = ko.computed(function () { var value = _ko.value(me.pageOrRelativePath()()); return page.path(value); }); }; pager.Href.hash = '#'; hp.bind = function () { ko.applyBindingsToNode(this.element, { attr: { 'href': this.path } }); }; hp.update = function (valueAccessor) { this.pageOrRelativePath(valueAccessor); }; pager.Href5 = function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { pager.Href.apply(this, arguments); }; pager.Href5.prototype = new pager.Href(); pager.Href5.history = window.History; pager.Href5.prototype.bind = function () { var self = this; ko.applyBindingsToNode(self.element, { attr: { 'href': self.path }, click: function (data, e) { var path = self.path(); if (path === '' || path === '/') { path = $('base').attr('href'); } if (e.shiftKey || e.ctrlKey || e.metaKey) { window.open(path, '_blank'); return; } pager.Href5.history.pushState(null, null, path); } }); }; var koBindingHandlerMethod = { init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { var Cls = pager.useHTML5history ? pager.Href5 : pager.Href; var href = new Cls(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext); href.init(); href.bind(); element.__ko__page = href; }, update: function (element, valueAccessor) { element.__ko__page.update(valueAccessor); } }; ko.bindingHandlers[pager.dataAttribute()] = koBindingHandlerMethod; pager.fx = {}; pager.fx.cssAsync = function (css) { return { showElement: function (page, callback) { var $e = $(page.element); $e.addClass(css); $e.show(); var i = setInterval(function () { clearInterval(i); $e.addClass(css + '-in'); }, 10); var i2 = setInterval(function () { clearInterval(i2); if (callback) { callback(); } }, 300); }, hideElement: function (page, callback) { var $e = $(page.element); if (!page.pageHiddenOnce) { page.pageHiddenOnce = true; $e.hide(); } else { $e.removeClass(css + '-in'); var i = setInterval(function () { clearInterval(i); if (callback) { callback(); } $e.hide(); }, 300); } } }; }; pager.fx.zoom = pager.fx.cssAsync('pagerjs-fx-zoom'); pager.fx.flip = pager.fx.cssAsync('pagerjs-fx-flip'); pager.fx.popout = pager.fx.cssAsync('pagerjs-fx-popout-modal'); pager.fx.jQuerySync = function (show, hide) { return { showElement: function (page, callback) { show.call($(page.element), 300, callback); }, hideElement: function (page, callback) { hide.call($(page.element), 300, function () { $(page.element).hide(); }); if (callback) { callback(); } } }; }; pager.fx.slide = pager.fx.jQuerySync($.fn.slideDown, $.fn.slideUp); pager.fx.fade = pager.fx.jQuerySync($.fn.fadeIn, $.fn.fadeOut); /** * * @param {String/Object} options */ pager.startHistoryJs = function (options) { var id = typeof options === 'string' ? options : null; if (id) { pager.Href5.history.pushState(null, null, id); } // Bind to StateChange Event pager.Href5.history.Adapter.bind(window, 'statechange', function () { var relativeUrl = pager.Href5.history.getState().url.replace(pager.Href5.history.getBaseUrl(), ''); goTo(relativeUrl); }); pager.Href5.history.Adapter.bind(window, 'anchorchange', function () { var hash = window.location.href.split('#')[1]; goTo(hash ? '#' + hash : ''); }); if (!options || !options.noGo) { goTo(pager.Href5.history.getState().url.replace(pager.Href5.history.getBaseUrl(), '')); } }; /** * This is the hash-based start-method. * * You should only use this method if you do not want HTML5 history support and * do not want IE6/7 support. * * @method start * @param {String/Object} options * @static */ pager.start = function (options) { var id = typeof options === 'string' ? options : null; if (id) { window.location.hash = pager.Href.hash + id; } var onHashChange = function () { var hash = window.location.href.split('#')[1]; goTo(hash ? '#' + hash : ''); }; $(window).bind('hashchange', onHashChange); if (!options || !options.noGo) { onHashChange(); } }; return pager; }; /*--------------------------------------------------------------------------*/ var define = window.define; // expose Pager.js // This code is a modified version of the AMD-fallback code found in Lo-Dash // (https://raw.github.com/bestiejs/lodash/master/lodash.js) // some AMD build optimizers, like r.js, check for specific condition patterns like the following: if (typeof define === 'function' && typeof define.amd === 'object' && define.amd) { // define as an anonymous module so, through path mapping, it can be // referenced as any module define('pager', ['knockout', 'jquery'], function (ko, $) { return pagerJsModule($, ko); }); } else { // without AMD window.pager = pagerJsModule($, ko); } }(window));