Skip to content

Commit

Permalink
Allow non-pjax page updates.
Browse files Browse the repository at this point in the history
Extract transition functionality from push.js into a new file, transitions.js.
Expose a global function, similar to PUSH named TRANSITION used to transition
from one page/view to another without dictating how the new markup is sourced.

This allows for Ratchet style page transitions when rendering client side.
  • Loading branch information
ndelage committed Oct 1, 2014
1 parent 8d30d97 commit 370464c
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 150 deletions.
158 changes: 8 additions & 150 deletions js/push.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,6 @@
fade : 'fade'
};

var bars = {
bartab : '.bar-tab',
barnav : '.bar-nav',
barfooter : '.bar-footer',
barheadersecondary : '.bar-header-secondary'
};

var cacheReplace = function (data, updates) {
PUSH.id = data.id;
if (updates) {
Expand Down Expand Up @@ -174,27 +167,11 @@
});
}

if (transitionFromObj.transition) {
activeObj = extendWithDom(activeObj, '.content', activeDom.cloneNode(true));
for (key in bars) {
if (bars.hasOwnProperty(key)) {
barElement = document.querySelector(bars[key]);
if (activeObj[key]) {
swapContent(activeObj[key], barElement);
} else if (barElement) {
barElement.parentNode.removeChild(barElement);
}
}
}
}

swapContent(
(activeObj.contents || activeDom).cloneNode(true),
document.querySelector('.content'),
transition, function() {
triggerStateChange();
}
);
TRANSITION(activeDom.cloneNode(true),
transitionFromObj.transition,
function() {
triggerStateChange();
});

PUSH.id = id;

Expand All @@ -209,14 +186,6 @@
var key;
var xhr = PUSH.xhr;

options.container = options.container || options.transition ? document.querySelector('.content') : document.body;

for (key in bars) {
if (bars.hasOwnProperty(key)) {
options[key] = options[key] || document.querySelector(bars[key]);
}
}

if (xhr && xhr.readyState < 4) {
xhr.onreadystatechange = noop;
xhr.abort();
Expand Down Expand Up @@ -279,20 +248,7 @@
document.title = data.title;
}

if (options.transition) {
for (key in bars) {
if (bars.hasOwnProperty(key)) {
barElement = document.querySelector(bars[key]);
if (data[key]) {
swapContent(data[key], barElement);
} else if (barElement) {
barElement.parentNode.removeChild(barElement);
}
}
}
}

swapContent(data.contents, options.container, options.transition, function () {
TRANSITION(data.contents, options.transition, function () {
cacheReplace({
id : options.id || +new Date(),
url : data.url,
Expand All @@ -303,6 +259,7 @@
triggerStateChange();
});


if (!options.ignorePush && window._gaq) {
_gaq.push(['_trackPageview']); // google analytics
}
Expand All @@ -319,78 +276,6 @@
// PUSH helpers
// ============

var swapContent = function (swap, container, transition, complete) {
var enter;
var containerDirection;
var swapDirection;

if (!transition) {
if (container) {
container.innerHTML = swap.innerHTML;
} else if (swap.classList.contains('content')) {
document.body.appendChild(swap);
} else {
document.body.insertBefore(swap, document.querySelector('.content'));
}
} else {
enter = /in$/.test(transition);

if (transition === 'fade') {
container.classList.add('in');
container.classList.add('fade');
swap.classList.add('fade');
}

if (/slide/.test(transition)) {
swap.classList.add('sliding-in', enter ? 'right' : 'left');
swap.classList.add('sliding');
container.classList.add('sliding');
}

container.parentNode.insertBefore(swap, container);
}

if (!transition) {
complete && complete();
}

if (transition === 'fade') {
container.offsetWidth; // force reflow
container.classList.remove('in');
var fadeContainerEnd = function () {
container.removeEventListener('webkitTransitionEnd', fadeContainerEnd);
swap.classList.add('in');
swap.addEventListener('webkitTransitionEnd', fadeSwapEnd);
};
var fadeSwapEnd = function () {
swap.removeEventListener('webkitTransitionEnd', fadeSwapEnd);
container.parentNode.removeChild(container);
swap.classList.remove('fade');
swap.classList.remove('in');
complete && complete();
};
container.addEventListener('webkitTransitionEnd', fadeContainerEnd);

}

if (/slide/.test(transition)) {
var slideEnd = function () {
swap.removeEventListener('webkitTransitionEnd', slideEnd);
swap.classList.remove('sliding', 'sliding-in');
swap.classList.remove(swapDirection);
container.parentNode.removeChild(container);
complete && complete();
};

container.offsetWidth; // force reflow
swapDirection = enter ? 'right' : 'left';
containerDirection = enter ? 'left' : 'right';
container.classList.add(containerDirection);
swap.classList.remove(swapDirection);
swap.addEventListener('webkitTransitionEnd', slideEnd);
}
};

var triggerStateChange = function () {
var e = new CustomEvent('push', {
detail: { state: getCached(PUSH.id) },
Expand Down Expand Up @@ -419,29 +304,6 @@
window.location.replace(url);
};

var extendWithDom = function (obj, fragment, dom) {
var i;
var result = {};

for (i in obj) {
if (obj.hasOwnProperty(i)) {
result[i] = obj[i];
}
}

Object.keys(bars).forEach(function (key) {
var el = dom.querySelector(bars[key]);
if (el) {
el.parentNode.removeChild(el);
}
result[key] = el;
});

result.contents = dom.querySelector(fragment);

return result;
};

var parseXHR = function (xhr, options) {
var head;
var body;
Expand All @@ -468,11 +330,7 @@
var text = 'innerText' in data.title ? 'innerText' : 'textContent';
data.title = data.title && data.title[text].trim();

if (options.transition) {
data = extendWithDom(data, '.content', body);
} else {
data.contents = body;
}
data.contents = body;

return data;
};
Expand Down
125 changes: 125 additions & 0 deletions js/transitions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
!(function () {
'use strict';

// Ratchet's layout includes fixed position headers & footers that should always
// appear before the main .content div within <body>
//
// These fixed bars will have new content swapped in, ignoring any
// transitions (slide-in, slide-out & fade).
//
// These following selectors define which elements are transitioned with
// simple DOM replacement and are always immediate children of <body>
var barSelectors = [
'.bar-tab',
'.bar-nav',
'.bar-footer',
'.bar-header-secondary'
];

// Other than any fixed bars, '.content' should be the only other child of body
var contentSelector = '.content';

// For any bar elements in `newMarkup`, either:
// * replace an existing bar elements with new content
// * add new bar elements when an existing one isn't present
// * remove any bar elements not found in `newMarkup`
var updateBars = function (newMarkup) {
for (var i=0; i < barSelectors.length; i++ ) {
var selector = barSelectors[i];
var newBar = newMarkup.querySelector(selector);
var existingBar = document.querySelector(selector);

if (newBar) {
displayBar(newBar, existingBar);
} else if (existingBar) {
existingBar.parentNode.removeChild(existingBar);
}
}
};

var displayBar = function (bar, container) {
if (container) {
container.innerHTML = bar.innerHTML;
} else {
// per Ratchet's CSS, bar elements must be the first thing in <body>
// here we assume `.content` is an immediate child of <body>
document.body.insertBefore(bar, document.querySelector(contentSelector));
}
};

var transitionContent = function (swap, container, transition, complete) {
var enter;
var containerDirection;
var swapDirection;

enter = /in$/.test(transition);

if (transition === 'fade') {
container.classList.add('in');
container.classList.add('fade');
swap.classList.add('fade');
}

if (/slide/.test(transition)) {
swap.classList.add('sliding-in', enter ? 'right' : 'left');
swap.classList.add('sliding');
container.classList.add('sliding');
}

container.parentNode.insertBefore(swap, container);
complete && complete();

if (transition === 'fade') {
container.offsetWidth; // force reflow
container.classList.remove('in');
var fadeContainerEnd = function () {
container.removeEventListener('webkitTransitionEnd', fadeContainerEnd);
swap.classList.add('in');
swap.addEventListener('webkitTransitionEnd', fadeSwapEnd);
};
var fadeSwapEnd = function () {
swap.removeEventListener('webkitTransitionEnd', fadeSwapEnd);
container.parentNode.removeChild(container);
swap.classList.remove('fade');
swap.classList.remove('in');
complete && complete();
};
container.addEventListener('webkitTransitionEnd', fadeContainerEnd);
}

if (/slide/.test(transition)) {
var slideEnd = function () {
swap.removeEventListener('webkitTransitionEnd', slideEnd);
swap.classList.remove('sliding', 'sliding-in');
swap.classList.remove(swapDirection);
container.parentNode.removeChild(container);
complete && complete();
};

container.offsetWidth; // force reflow
swapDirection = enter ? 'right' : 'left';
containerDirection = enter ? 'left' : 'right';
container.classList.add(containerDirection);
swap.classList.remove(swapDirection);
swap.addEventListener('webkitTransitionEnd', slideEnd);
}
};

// `contents` must include an element with the class 'content' and can
// optionally include a number of Ratchet bar elements (see `barSelectors`)
var TRANSITION = function (contents, transition, complete) {
if (transition) {
updateBars(contents);

var existingContentDiv = document.querySelector(contentSelector);
var newContentDiv = contents.querySelector(contentSelector);
transitionContent(newContentDiv, existingContentDiv,
transition, complete);
} else {
document.body.innerHTML = contents.innerHTML;
complete && complete();
}
};

window.TRANSITION = TRANSITION;
}());

0 comments on commit 370464c

Please sign in to comment.