web-apps/vendor/framework7/src/js/views.js
2016-11-11 16:24:21 +03:00

638 lines
26 KiB
JavaScript

/*======================================================
************ Views ************
======================================================*/
app.views = [];
var View = function (selector, params) {
var defaults = {
dynamicNavbar: false,
domCache: false,
linksView: undefined,
reloadPages: false,
uniqueHistory: app.params.uniqueHistory,
uniqueHistoryIgnoreGetParameters: app.params.uniqueHistoryIgnoreGetParameters,
allowDuplicateUrls: app.params.allowDuplicateUrls,
swipeBackPage: app.params.swipeBackPage,
swipeBackPageAnimateShadow: app.params.swipeBackPageAnimateShadow,
swipeBackPageAnimateOpacity: app.params.swipeBackPageAnimateOpacity,
swipeBackPageActiveArea: app.params.swipeBackPageActiveArea,
swipeBackPageThreshold: app.params.swipeBackPageThreshold,
animatePages: app.params.animatePages,
preloadPreviousPage: app.params.preloadPreviousPage
};
var i;
// Params
params = params || {};
// Disable dynamic navbar for material theme
if (params.dynamicNavbar && app.params.material) params.dynamicNavbar = false;
// Extend params with defaults
for (var def in defaults) {
if (typeof params[def] === 'undefined') {
params[def] = defaults[def];
}
}
// View
var view = this;
view.params = params;
// Selector
view.selector = selector;
// Container
var container = $(selector);
view.container = container[0];
// Fix Selector
if (typeof selector !== 'string') {
// Supposed to be HTMLElement or Dom7
selector = (container.attr('id') ? '#' + container.attr('id') : '') + (container.attr('class') ? '.' + container.attr('class').replace(/ /g, '.').replace('.active', '') : '');
view.selector = selector;
}
// Is main
view.main = container.hasClass(app.params.viewMainClass);
// Content cache
view.contentCache = {};
// Context cache
view.contextCache = {};
// Pages cache
view.pagesCache = {};
view.pageElementsCache = {};
// Store View in element for easy access
container[0].f7View = view;
// Pages
view.pagesContainer = container.find('.pages')[0];
view.initialPages = [];
view.initialPagesUrl = [];
view.initialNavbars = [];
if (view.params.domCache) {
var initialPages = container.find('.page');
for (i = 0; i < initialPages.length; i++) {
view.initialPages.push(initialPages[i]);
view.initialPagesUrl.push('#' + initialPages.eq(i).attr('data-page'));
}
if (view.params.dynamicNavbar) {
var initialNavbars = container.find('.navbar-inner');
for (i = 0; i < initialNavbars.length; i++) {
view.initialNavbars.push(initialNavbars[i]);
}
}
}
view.allowPageChange = true;
// Location
var docLocation = document.location.href;
// History
view.history = [];
var viewURL = docLocation;
var pushStateSeparator = app.params.pushStateSeparator;
var pushStateRoot = app.params.pushStateRoot;
if (app.params.pushState && view.main) {
if (pushStateRoot) {
viewURL = pushStateRoot;
}
else {
if (pushStateSeparator && viewURL.indexOf(pushStateSeparator) >= 0 && viewURL.indexOf(pushStateSeparator + '#') < 0) viewURL = viewURL.split(pushStateSeparator)[0];
}
}
// Active Page
var currentPage, currentPageData;
if (!view.activePage) {
currentPage = $(view.pagesContainer).find('.page-on-center');
if (currentPage.length === 0) {
currentPage = $(view.pagesContainer).find('.page:not(.cached)');
currentPage = currentPage.eq(currentPage.length - 1);
}
if (currentPage.length > 0) {
currentPageData = currentPage[0].f7PageData;
}
}
// View startup URL
if (view.params.domCache && currentPage) {
view.url = container.attr('data-url') || view.params.url || '#' + currentPage.attr('data-page');
view.pagesCache[view.url] = currentPage.attr('data-page');
}
else view.url = container.attr('data-url') || view.params.url || viewURL;
// Update current page Data
if (currentPageData) {
currentPageData.view = view;
currentPageData.url = view.url;
if (view.params.domCache && view.params.dynamicNavbar && !currentPageData.navbarInnerContainer) {
currentPageData.navbarInnerContainer = view.initialNavbars[view.initialPages.indexOf(currentPageData.container)];
}
view.activePage = currentPageData;
currentPage[0].f7PageData = currentPageData;
}
// Store to history main view's url
if (view.url) {
view.history.push(view.url);
}
// Touch events
var isTouched = false,
isMoved = false,
touchesStart = {},
isScrolling,
activePage = [],
previousPage = [],
viewContainerWidth,
touchesDiff,
allowViewTouchMove = true,
touchStartTime,
activeNavbar = [],
previousNavbar = [],
activeNavElements,
previousNavElements,
activeNavBackIcon,
previousNavBackIcon,
dynamicNavbar,
pageShadow,
el;
view.handleTouchStart = function (e) {
if (!allowViewTouchMove || !view.params.swipeBackPage || isTouched || app.swipeoutOpenedEl || !view.allowPageChange) return;
isMoved = false;
isTouched = true;
isScrolling = undefined;
touchesStart.x = e.type === 'touchstart' ? e.targetTouches[0].pageX : e.pageX;
touchesStart.y = e.type === 'touchstart' ? e.targetTouches[0].pageY : e.pageY;
touchStartTime = (new Date()).getTime();
dynamicNavbar = view.params.dynamicNavbar && container.find('.navbar-inner').length > 1;
};
view.handleTouchMove = function (e) {
if (!isTouched) return;
var pageX = e.type === 'touchmove' ? e.targetTouches[0].pageX : e.pageX;
var pageY = e.type === 'touchmove' ? e.targetTouches[0].pageY : e.pageY;
if (typeof isScrolling === 'undefined') {
isScrolling = !!(isScrolling || Math.abs(pageY - touchesStart.y) > Math.abs(pageX - touchesStart.x));
}
if (isScrolling || e.f7PreventSwipeBack || app.preventSwipeBack) {
isTouched = false;
return;
}
if (!isMoved) {
var cancel = false;
// Calc values during first move fired
viewContainerWidth = container.width();
var target = $(e.target);
var swipeout = target.hasClass('swipeout') ? target : target.parents('.swipeout');
if (swipeout.length > 0) {
if (!app.rtl && swipeout.find('.swipeout-actions-left').length > 0) cancel = true;
if (app.rtl && swipeout.find('.swipeout-actions-right').length > 0) cancel = true;
}
activePage = target.is('.page') ? target : target.parents('.page');
if (activePage.hasClass('no-swipeback')) cancel = true;
previousPage = container.find('.page-on-left:not(.cached)');
var notFromBorder = touchesStart.x - container.offset().left > view.params.swipeBackPageActiveArea;
if (app.rtl) {
notFromBorder = touchesStart.x < container.offset().left - container[0].scrollLeft + viewContainerWidth - view.params.swipeBackPageActiveArea;
}
else {
notFromBorder = touchesStart.x - container.offset().left > view.params.swipeBackPageActiveArea;
}
if (notFromBorder) cancel = true;
if (previousPage.length === 0 || activePage.length === 0) cancel = true;
if (cancel) {
isTouched = false;
return;
}
if (view.params.swipeBackPageAnimateShadow && !app.device.android) {
pageShadow = activePage.find('.swipeback-page-shadow');
if (pageShadow.length === 0) {
pageShadow = $('<div class="swipeback-page-shadow"></div>');
activePage.append(pageShadow);
}
}
if (dynamicNavbar) {
activeNavbar = container.find('.navbar-on-center:not(.cached)');
previousNavbar = container.find('.navbar-on-left:not(.cached)');
activeNavElements = activeNavbar.find('.left, .center, .right, .subnavbar, .fading');
previousNavElements = previousNavbar.find('.left, .center, .right, .subnavbar, .fading');
if (app.params.animateNavBackIcon) {
activeNavBackIcon = activeNavbar.find('.left.sliding .back .icon');
previousNavBackIcon = previousNavbar.find('.left.sliding .back .icon');
}
}
// Close/Hide Any Picker
if ($('.picker-modal.modal-in').length > 0) {
app.closeModal($('.picker-modal.modal-in'));
}
}
e.f7PreventPanelSwipe = true;
isMoved = true;
e.preventDefault();
// RTL inverter
var inverter = app.rtl ? -1 : 1;
// Touches diff
touchesDiff = (pageX - touchesStart.x - view.params.swipeBackPageThreshold) * inverter;
if (touchesDiff < 0) touchesDiff = 0;
var percentage = touchesDiff / viewContainerWidth;
// Swipe Back Callback
var callbackData = {
percentage: percentage,
activePage: activePage[0],
previousPage: previousPage[0],
activeNavbar: activeNavbar[0],
previousNavbar: previousNavbar[0]
};
if (view.params.onSwipeBackMove) {
view.params.onSwipeBackMove(callbackData);
}
container.trigger('swipeBackMove', callbackData);
// Transform pages
var activePageTranslate = touchesDiff * inverter;
var previousPageTranslate = (touchesDiff / 5 - viewContainerWidth / 5) * inverter;
if (app.device.pixelRatio === 1) {
activePageTranslate = Math.round(activePageTranslate);
previousPageTranslate = Math.round(previousPageTranslate);
}
activePage.transform('translate3d(' + activePageTranslate + 'px,0,0)');
if (view.params.swipeBackPageAnimateShadow && !app.device.android) pageShadow[0].style.opacity = 1 - 1 * percentage;
previousPage.transform('translate3d(' + previousPageTranslate + 'px,0,0)');
if (view.params.swipeBackPageAnimateOpacity) previousPage[0].style.opacity = 0.9 + 0.1 * percentage;
// Dynamic Navbars Animation
if (dynamicNavbar) {
var i;
for (i = 0; i < activeNavElements.length; i++) {
el = $(activeNavElements[i]);
if (!el.is('.subnavbar.sliding')) el[0].style.opacity = (1 - percentage * 1.3);
if (el[0].className.indexOf('sliding') >= 0) {
var activeNavTranslate = percentage * el[0].f7NavbarRightOffset;
if (app.device.pixelRatio === 1) activeNavTranslate = Math.round(activeNavTranslate);
el.transform('translate3d(' + activeNavTranslate + 'px,0,0)');
if (app.params.animateNavBackIcon) {
if (el[0].className.indexOf('left') >= 0 && activeNavBackIcon.length > 0) {
activeNavBackIcon.transform('translate3d(' + -activeNavTranslate + 'px,0,0)');
}
}
}
}
for (i = 0; i < previousNavElements.length; i++) {
el = $(previousNavElements[i]);
if (!el.is('.subnavbar.sliding')) el[0].style.opacity = percentage * 1.3 - 0.3;
if (el[0].className.indexOf('sliding') >= 0) {
var previousNavTranslate = el[0].f7NavbarLeftOffset * (1 - percentage);
if (app.device.pixelRatio === 1) previousNavTranslate = Math.round(previousNavTranslate);
el.transform('translate3d(' + previousNavTranslate + 'px,0,0)');
if (app.params.animateNavBackIcon) {
if (el[0].className.indexOf('left') >= 0 && previousNavBackIcon.length > 0) {
previousNavBackIcon.transform('translate3d(' + -previousNavTranslate + 'px,0,0)');
}
}
}
}
}
};
view.handleTouchEnd = function (e) {
if (!isTouched || !isMoved) {
isTouched = false;
isMoved = false;
return;
}
isTouched = false;
isMoved = false;
if (touchesDiff === 0) {
$([activePage[0], previousPage[0]]).transform('').css({opacity: '', boxShadow: ''});
if (dynamicNavbar) {
activeNavElements.transform('').css({opacity: ''});
previousNavElements.transform('').css({opacity: ''});
if (activeNavBackIcon && activeNavBackIcon.length > 0) activeNavBackIcon.transform('');
if (previousNavBackIcon && activeNavBackIcon.length > 0) previousNavBackIcon.transform('');
}
return;
}
var timeDiff = (new Date()).getTime() - touchStartTime;
var pageChanged = false;
// Swipe back to previous page
if (
timeDiff < 300 && touchesDiff > 10 ||
timeDiff >= 300 && touchesDiff > viewContainerWidth / 2
) {
activePage.removeClass('page-on-center').addClass('page-on-right');
previousPage.removeClass('page-on-left').addClass('page-on-center');
if (dynamicNavbar) {
activeNavbar.removeClass('navbar-on-center').addClass('navbar-on-right');
previousNavbar.removeClass('navbar-on-left').addClass('navbar-on-center');
}
pageChanged = true;
}
// Reset custom styles
// Add transitioning class for transition-duration
$([activePage[0], previousPage[0]]).transform('').css({opacity: '', boxShadow: ''}).addClass('page-transitioning');
if (dynamicNavbar) {
activeNavElements.css({opacity: ''})
.each(function () {
var translate = pageChanged ? this.f7NavbarRightOffset : 0;
var sliding = $(this);
sliding.transform('translate3d(' + translate + 'px,0,0)');
if (app.params.animateNavBackIcon) {
if (sliding.hasClass('left') && activeNavBackIcon.length > 0) {
activeNavBackIcon.addClass('page-transitioning').transform('translate3d(' + -translate + 'px,0,0)');
}
}
}).addClass('page-transitioning');
previousNavElements.transform('').css({opacity: ''}).each(function () {
var translate = pageChanged ? 0 : this.f7NavbarLeftOffset;
var sliding = $(this);
sliding.transform('translate3d(' + translate + 'px,0,0)');
if (app.params.animateNavBackIcon) {
if (sliding.hasClass('left') && previousNavBackIcon.length > 0) {
previousNavBackIcon.addClass('page-transitioning').transform('translate3d(' + -translate + 'px,0,0)');
}
}
}).addClass('page-transitioning');
}
allowViewTouchMove = false;
view.allowPageChange = false;
// Swipe Back Callback
var callbackData = {
activePage: activePage[0],
previousPage: previousPage[0],
activeNavbar: activeNavbar[0],
previousNavbar: previousNavbar[0]
};
if (pageChanged) {
// Update View's URL
var url = view.history[view.history.length - 2];
view.url = url;
// Page before animation callback
app.pageBackCallback('before', view, {pageContainer: activePage[0], url: url, position: 'center', newPage: previousPage, oldPage: activePage, swipeBack: true});
app.pageAnimCallback('before', view, {pageContainer: previousPage[0], url: url, position: 'left', newPage: previousPage, oldPage: activePage, swipeBack: true});
if (view.params.onSwipeBackBeforeChange) {
view.params.onSwipeBackBeforeChange(callbackData);
}
container.trigger('swipeBackBeforeChange', callbackData);
}
else {
if (view.params.onSwipeBackBeforeReset) {
view.params.onSwipeBackBeforeReset(callbackData);
}
container.trigger('swipeBackBeforeReset', callbackData);
}
activePage.transitionEnd(function () {
$([activePage[0], previousPage[0]]).removeClass('page-transitioning');
if (dynamicNavbar) {
activeNavElements.removeClass('page-transitioning').css({opacity: ''});
previousNavElements.removeClass('page-transitioning').css({opacity: ''});
if (activeNavBackIcon && activeNavBackIcon.length > 0) activeNavBackIcon.removeClass('page-transitioning');
if (previousNavBackIcon && previousNavBackIcon.length > 0) previousNavBackIcon.removeClass('page-transitioning');
}
allowViewTouchMove = true;
view.allowPageChange = true;
if (pageChanged) {
if (app.params.pushState && view.main) history.back();
// Page after animation callback
app.pageBackCallback('after', view, {pageContainer: activePage[0], url: url, position: 'center', newPage: previousPage, oldPage: activePage, swipeBack: true});
app.pageAnimCallback('after', view, {pageContainer: previousPage[0], url: url, position: 'left', newPage: previousPage, oldPage: activePage, swipeBack: true});
app.router.afterBack(view, activePage, previousPage);
if (view.params.onSwipeBackAfterChange) {
view.params.onSwipeBackAfterChange(callbackData);
}
container.trigger('swipeBackAfterChange', callbackData);
}
else {
if (view.params.onSwipeBackAfterReset) {
view.params.onSwipeBackAfterReset(callbackData);
}
container.trigger('swipeBackAfterReset', callbackData);
}
if (pageShadow && pageShadow.length > 0) pageShadow.remove();
});
};
view.attachEvents = function (detach) {
var action = detach ? 'off' : 'on';
var passiveListener = app.touchEvents.start === 'touchstart' && app.support.passiveListener ? {passive: true, capture: false} : false;
container[action](app.touchEvents.start, view.handleTouchStart, passiveListener);
container[action](app.touchEvents.move, view.handleTouchMove);
container[action](app.touchEvents.end, view.handleTouchEnd, passiveListener);
};
view.detachEvents = function () {
view.attachEvents(true);
};
// Init
if (view.params.swipeBackPage && !app.params.material) {
view.attachEvents();
}
// Add view to app
app.views.push(view);
if (view.main) app.mainView = view;
// Router
view.router = {
load: function (options) {
return app.router.load(view, options);
},
back: function (options) {
return app.router.back(view, options);
},
// Shortcuts
loadPage: function (options) {
options = options || {};
if (typeof options === 'string') {
var url = options;
options = {};
if (url && url.indexOf('#') === 0 && view.params.domCache) {
options.pageName = url.split('#')[1];
}
else options.url = url;
}
return app.router.load(view, options);
},
loadContent: function (content) {
return app.router.load(view, {content: content});
},
reloadPage: function (url) {
return app.router.load(view, {url: url, reload: true});
},
reloadContent: function (content) {
return app.router.load(view, {content: content, reload: true});
},
reloadPreviousPage: function (url) {
return app.router.load(view, {url: url, reloadPrevious: true, reload: true});
},
reloadPreviousContent: function (content) {
return app.router.load(view, {content: content, reloadPrevious: true, reload: true});
},
refreshPage: function () {
var options = {
url: view.url,
reload: true,
ignoreCache: true
};
if (options.url && options.url.indexOf('#') === 0) {
if (view.params.domCache && view.pagesCache[options.url]) {
options.pageName = view.pagesCache[options.url];
options.url = undefined;
delete options.url;
}
else if (view.contentCache[options.url]) {
options.content = view.contentCache[options.url];
options.url = undefined;
delete options.url;
}
}
return app.router.load(view, options);
},
refreshPreviousPage: function () {
var options = {
url: view.history[view.history.length - 2],
reload: true,
reloadPrevious: true,
ignoreCache: true
};
if (options.url && options.url.indexOf('#') === 0 && view.params.domCache && view.pagesCache[options.url]) {
options.pageName = view.pagesCache[options.url];
options.url = undefined;
delete options.url;
}
return app.router.load(view, options);
}
};
// Aliases for temporary backward compatibility
view.loadPage = view.router.loadPage;
view.loadContent = view.router.loadContent;
view.reloadPage = view.router.reloadPage;
view.reloadContent = view.router.reloadContent;
view.reloadPreviousPage = view.router.reloadPreviousPage;
view.reloadPreviousContent = view.router.reloadPreviousContent;
view.refreshPage = view.router.refreshPage;
view.refreshPreviousPage = view.router.refreshPreviousPage;
view.back = view.router.back;
// Bars methods
view.hideNavbar = function () {
return app.hideNavbar(container.find('.navbar'));
};
view.showNavbar = function () {
return app.showNavbar(container.find('.navbar'));
};
view.hideToolbar = function () {
return app.hideToolbar(container.find('.toolbar'));
};
view.showToolbar = function () {
return app.showToolbar(container.find('.toolbar'));
};
// Push State on load
if (app.params.pushState && app.params.pushStateOnLoad && view.main) {
var pushStateUrl;
var pushStateUrlSplit = docLocation.split(pushStateSeparator)[1];
if (pushStateRoot) {
pushStateUrl = docLocation.split(app.params.pushStateRoot + pushStateSeparator)[1];
}
else if (pushStateSeparator && docLocation.indexOf(pushStateSeparator) >= 0 && docLocation.indexOf(pushStateSeparator + '#') < 0) {
pushStateUrl = pushStateUrlSplit;
}
var pushStateAnimatePages = app.params.pushStateNoAnimation ? false : undefined;
var historyState = history.state;
if (pushStateUrl) {
if (pushStateUrl.indexOf('#') >= 0 && view.params.domCache && historyState && historyState.pageName && 'viewIndex' in historyState) {
app.router.load(view, {pageName: historyState.pageName, url: historyState.url, animatePages: pushStateAnimatePages, pushState: false});
}
else if (pushStateUrl.indexOf('#') >= 0 && view.params.domCache && view.initialPagesUrl.indexOf(pushStateUrl) >= 0) {
app.router.load(view, {pageName: pushStateUrl.replace('#',''), animatePages: pushStateAnimatePages, pushState: false});
}
else app.router.load(view, {url: pushStateUrl, animatePages: pushStateAnimatePages, pushState: false});
}
else if (view.params.domCache && docLocation.indexOf(pushStateSeparator + '#') >= 0) {
if (historyState && historyState.pageName && 'viewIndex' in historyState) {
app.router.load(view, {pageName: historyState.pageName, url: historyState.url, animatePages: pushStateAnimatePages, pushState: false});
}
else if (pushStateSeparator && pushStateUrlSplit.indexOf('#') === 0) {
if (view.initialPagesUrl.indexOf(pushStateUrlSplit)) {
app.router.load(view, {pageName: pushStateUrlSplit.replace('#', ''), animatePages: pushStateAnimatePages, pushState: false});
}
}
}
}
// Destroy
view.destroy = function () {
view.detachEvents();
view = undefined;
};
// Plugin hook
app.pluginHook('addView', view);
// Return view
return view;
};
app.addView = function (selector, params) {
return new View(selector, params);
};
app.getCurrentView = function (index) {
var popoverView = $('.popover.modal-in .view');
var popupView = $('.popup.modal-in .view');
var panelView = $('.panel.active .view');
var appViews = $('.views');
// Find active view as tab
var appView = appViews.children('.view');
// Propably in tabs or split view
if (appView.length > 1) {
if (appView.hasClass('tab')) {
// Tabs
appView = appViews.children('.view.active');
}
else {
// Split View, leave appView intact
}
}
if (popoverView.length > 0 && popoverView[0].f7View) return popoverView[0].f7View;
if (popupView.length > 0 && popupView[0].f7View) return popupView[0].f7View;
if (panelView.length > 0 && panelView[0].f7View) return panelView[0].f7View;
if (appView.length > 0) {
if (appView.length === 1 && appView[0].f7View) return appView[0].f7View;
if (appView.length > 1) {
var currentViews = [];
for (var i = 0; i < appView.length; i++) {
if (appView[i].f7View) currentViews.push(appView[i].f7View);
}
if (currentViews.length > 0 && typeof index !== 'undefined') return currentViews[index];
if (currentViews.length > 1) return currentViews;
if (currentViews.length === 1) return currentViews[0];
return undefined;
}
}
return undefined;
};