jquery-page
Version:
jQuery Page Transitions for HTML5 Single-Page-Apps
378 lines (346 loc) • 16.5 kB
JavaScript
/*!
** jQuery Page -- jQuery Page Transitions for HTML5 Single-Page-Apps
** Copyright (c) 2016-2021 Dr. Ralf S. Engelschall <rse@engelschall.com>
**
** Permission is hereby granted, free of charge, to any person obtaining
** a copy of this software and associated documentation files (the
** "Software"), to deal in the Software without restriction, including
** without limitation the rights to use, copy, modify, merge, publish,
** distribute, sublicense, and/or sell copies of the Software, and to
** permit persons to whom the Software is furnished to do so, subject to
** the following conditions:
**
** The above copyright notice and this permission notice shall be included
** in all copies or substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
** SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/* global jQuery: false */
(function ($) {
/* internal API class */
var Page = function (root) {
this.root = root;
};
Page.prototype = {
/* API method: insert new page element */
insert: function (pageId, el) {
var self = this;
/* sanity check arguments */
if (arguments.length !== 2)
throw new Error("invalid number of arguments");
if (typeof pageId !== "string")
throw new Error("invalid page id argument (string expected)");
/* append element */
$("> .jquery-page-container", self.root).append(
$(el)
.attr("data-jquery-page-name", pageId)
.addClass("jquery-page-disabled")
);
return this;
},
/* API method: remove existing page element */
remove: function (pageId) {
var self = this;
/* sanity check arguments */
if (arguments.length !== 1)
throw new Error("invalid number of arguments");
if (typeof pageId !== "string")
throw new Error("invalid page id argument (string expected)");
/* remove element */
var page = $("> .jquery-page-container > *", self.root).filter(function (idx, el) {
return $(el).attr("data-jquery-page-name") === pageId;
});
if (page.length === 0)
throw new Error("no such page \"" + pageId + "\" found");
if ($(page).hasClass("jquery-page-active")) {
var others = self.existing().filter(function (id) {
return id !== pageId;
});
if (others.length > 0)
self.transition(others[0], "none");
}
$(page).remove();
return this;
},
/* API method: fetch element of particular page */
fetch: function (pageId) {
var self = this;
/* sanity check arguments */
if (arguments.length === 0)
throw new Error("missing page id");
else if (arguments.length === 1 && typeof pageId !== "string")
throw new Error("invalid page id argument (string expected)");
/* get page element */
var pageTo = $("> .jquery-page-container > *", self.root).filter(function (idx, el) {
return $(el).attr("data-jquery-page-name") === pageId;
});
if (pageTo.length > 1)
throw new Error("more than one page with id \"" + pageId + "\" found");
if (pageTo.length === 0)
return null;
return pageTo.get(0);
},
/* API method: fetch ids of all existing page elements */
existing: function () {
var self = this;
/* sanity check arguments */
if (arguments.length !== 0)
throw new Error("invalid number of arguments");
/* find all pages */
var pages = [];
$("> .jquery-page-container > *", self.root).each(function () {
pages.push($(this).attr("data-jquery-page-name"));
});
return pages;
},
/* API method: get id of currently active element */
active: function () {
var self = this;
/* sanity check arguments */
if (arguments.length !== 0)
throw new Error("invalid number of arguments");
/* get id of currently active page element */
var pageActive = $("> .jquery-page-container > .jquery-page-active", self.root);
if (pageActive.length === 0)
throw new Error("internal error: no active page found");
if (pageActive.length > 1)
throw new Error("internal error: more than one active page found");
return pageActive.attr("data-jquery-page-name");
},
/* API method: shake current page element */
shake: function (complete) {
var self = this;
/* sanity check arguments */
if (arguments.length < 0 || arguments.length > 1)
throw new Error("invalid number of arguments (0 or 1 expected)");
else if (arguments.length === 1 && typeof complete !== "function")
throw new Error("invalid complete argument (function expected)");
/* apply effect */
var pageCo = $("> .jquery-page-container", self.root);
var pageWidth = $(self.root).width();
var handler = function (ev) {
if (ev.target !== this)
return;
$(pageCo)
.css("width", "")
.removeClass("jquery-page-shake");
if (typeof complete === "function")
complete.call(this);
$(pageCo).off("animationend", handler);
};
$(pageCo)
.width(pageWidth)
.addClass("jquery-page-shake")
.on("animationend", handler);
return this;
},
/* API method: transition to a particular page element */
transition: function (pageId, transition, complete) {
var self = this;
/* sanity check arguments */
if (arguments.length < 2 || arguments.length > 3)
throw new Error("invalid number of arguments (2 or 3 expected)");
else if (typeof pageId !== "string")
throw new Error("invalid page id argument (string expected)");
else if (typeof transition !== "string")
throw new Error("invalid transition type argument (string expected)");
else if (arguments.length === 3 && typeof complete !== "function")
throw new Error("invalid complete argument (function expected)");
/* get page container */
var pageCo = $("> .jquery-page-container", self.root);
/* get from/to pages */
var pageFr = $("> .jquery-page-container > .jquery-page-active", self.root);
if (transition !== "none" && pageFr.length === 0)
throw new Error("internal error: no active page found");
if (transition !== "none" && pageFr.length > 1)
throw new Error("internal error: more than one active page found");
var pageTo = $("> .jquery-page-container > *", self.root).filter(function (idx, el) {
return $(el).attr("data-jquery-page-name") === pageId;
});
if (pageTo.length === 0)
throw new Error("no such page \"" + pageId + "\" found");
if (pageTo.length > 1)
throw new Error("more than one page with id \"" + pageId + "\" found");
/* determine page dimensions */
var pageWidth = $(self.root).width();
var pageHeight = $(self.root).height();
/* dispatch according to transition type */
var m, to, handler;
if (transition === "none") {
/* TRANSITION: none at all (just switch instantly) */
$(pageFr)
.removeClass("jquery-page-active")
.addClass("jquery-page-disabled");
$(pageTo)
.removeClass("jquery-page-disabled")
.addClass("jquery-page-active");
if (typeof complete === "function")
complete.call(this, pageId);
}
else if ((m = transition.match(/^slide-in-from-(left|right)$/)) !== null) {
/* TRANSITION: slide in from left/right */
to = m[1];
$(pageCo)
.width(pageWidth * 2)
.css("left", to === "left" ? -pageWidth : 0)
.addClass("jquery-page-horizontal");
$(pageFr)
.width(pageWidth)
.addClass(to === "left" ? "jquery-page-right" : "jquery-page-left");
$(pageTo)
.width(pageWidth)
.addClass(to === "left" ? "jquery-page-left" : "jquery-page-right")
.removeClass("jquery-page-disabled");
handler = function (ev) {
if (ev.target !== this)
return;
$(pageFr)
.addClass("jquery-page-disabled")
.removeClass(to === "left" ? "jquery-page-right" : "jquery-page-left")
.removeClass("jquery-page-active")
.css("width", "");
$(pageTo)
.removeClass(to === "left" ? "jquery-page-left" : "jquery-page-right")
.addClass("jquery-page-active")
.css("width", "");
$(pageCo)
.css("width", "")
.css("transform", "")
.css("left", "")
.removeClass("jquery-page-horizontal")
.removeClass("jquery-page-slide");
if (typeof complete === "function")
complete.call(this, pageId);
$(pageCo).off("transitionend", handler);
};
$(pageCo)
.addClass("jquery-page-slide")
.css("transform", "translate(" + (to === "left" ? "" : "-") + pageWidth + "px,0)")
.on("transitionend", handler);
}
else if ((m = transition.match(/^slide-in-from-(top|bottom)$/)) !== null) {
/* TRANSITION: slide in from top/bottom */
to = m[1];
$(pageCo)
.height(pageHeight * 2)
.css("top", to === "top" ? -pageHeight : 0)
.addClass("jquery-page-vertical");
$(pageFr)
.addClass(to === "top" ? "jquery-page-bottom" : "jquery-page-top")
.height(pageHeight);
$(pageTo)
.addClass(to === "top" ? "jquery-page-top" : "jquery-page-bottom")
.removeClass("jquery-page-disabled")
.height(pageHeight);
handler = function (ev) {
if (ev.target !== this)
return;
$(pageFr)
.addClass("jquery-page-disabled")
.removeClass(to === "top" ? "jquery-page-bottom" : "jquery-page-top")
.removeClass("jquery-page-active")
.css("height", "");
$(pageTo)
.removeClass(to === "top" ? "jquery-page-top" : "jquery-page-bottom")
.addClass("jquery-page-active")
.css("height", "");
$(pageCo)
.css("height", "")
.removeClass("jquery-page-vertical")
.removeClass("jquery-page-slide")
.css("transform", "")
.css("top", 0);
if (typeof complete === "function")
complete.call(this, pageId);
$(pageCo).off("transitionend", handler);
};
$(pageCo)
.addClass("jquery-page-slide")
.css("transform", "translate(0," + (to === "top" ? "" : "-") + pageHeight + "px)")
.on("transitionend", handler);
}
else if ((m = transition.match(/^flip-towards-(left|right)$/)) !== null) {
/* TRANSITION: flip towards left/right */
to = m[1];
$(pageCo)
.addClass("jquery-page-stacked")
.width(pageWidth);
$(pageFr)
.addClass("jquery-page-front")
.width(pageWidth);
$(pageTo)
.addClass("jquery-page-back")
.removeClass("jquery-page-disabled")
.width(pageWidth);
handler = function (ev) {
if (ev.target !== this)
return;
$(pageFr)
.addClass("jquery-page-disabled")
.removeClass("jquery-page-front")
.removeClass("jquery-page-active")
.css("width", "");
$(pageTo)
.removeClass("jquery-page-back")
.addClass("jquery-page-active")
.css("width", "");
$(pageCo)
.css("width", "")
.removeClass("jquery-page-stacked")
.removeClass("jquery-page-flip-" + to);
if (typeof complete === "function")
complete.call(this, pageId);
$(pageCo).off("transitionend", handler);
};
$(pageCo)
.addClass("jquery-page-flip-" + to)
.on("transitionend", handler);
}
else
throw new Error("invalid transition type");
return this;
}
};
/* hook into jQuery (locally) */
$.fn.extend({
/* API method */
page: function () {
var result = null;
this.each(function () {
/* determine attached API */
var api = $(this).data("jquery-page-api");
if (!api) {
/* create new attached API and prepare root element */
api = new Page(this);
$(this)
.data("jquery-page-api", api)
.addClass("jquery-page");
/* sanity check and prepare container element */
var container = $("> *", this);
if (container.length === 0) {
container = $("<div></div>");
$(this).append(container);
}
else if (container.length !== 1)
throw new Error("require a single container element under jQuery Page root element");
$(container)
.addClass("jquery-page-container");
/* prepare already existing page elements */
$("> *", container)
.addClass("jquery-page-disabled");
$("> *:first", container)
.removeClass("jquery-page-disabled")
.addClass("jquery-page-active");
}
result = api;
});
return result;
}
});
})(jQuery);