Skip to content

Commit

Permalink
Enable animation based on navigation transitions.
Browse files Browse the repository at this point in the history
Responses can now send a `name` value which will be used to construct "from"
and "to" CSS classes that will be applied during updates.  These two classes
will be applied with the following patterns:

* `config['animation-class'] + '-from-' + _previous_name_`
* `config['animation-class'] + '-to-' + _current_name_`

This will allow transitions to be applied based on state of the page (i.e.
navigating from page A to page B) instead of based on the action (i.e.
navigating back in the history stack).

Currently, a `name` must be sent with each part of a multipart response for it
to be used when processing that part.  The last `name` value will be saved
for upcoming "from" classes.

In order to initialize a "from" class before the first SPF navigation, pages
can set the `data-spf-name` attribute on the `<body>` tag:

```html
<body data-spf-name="home" ...>
```

Progress on youtube#299.
  • Loading branch information
nicksay committed Mar 10, 2015
1 parent 608f0f9 commit 5c02113
Show file tree
Hide file tree
Showing 14 changed files with 77 additions and 12 deletions.
9 changes: 6 additions & 3 deletions src/client/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -193,19 +193,21 @@ spf.ResponseFragment;
/**
* Type definition for a single SPF response object.
* - attr: Map of Element IDs to maps of attibute names to attribute values
* to set on the Elements.
* to set on the Elements.
* - body: Map of Element IDs to HTML strings containing content with which
* to update the Elements.
* to update the Elements.
* - cacheKey: Key used to cache this response.
* - cacheType: String of the type of caching to use for this response.
* - foot: HTML string containing <script> tags of JS to execute.
* - head: HTML string containing <link> and <style> tags of CSS to install.
* - name: String of the general name of this type of response. This will be
* used to generate "from" and "to" CSS classes for animation.
* - redirect: String of a URL to request instead.
* - reload: Boolean to indicate the page should be reloaded.
* - timing: Map of timing attributes to timestamp numbers.
* - title: String of the new Document title.
* - url: String of the correct URL for the current request. This will replace
* the current URL in history.
* the current URL in history.
*
* @typedef {{
* attr: (Object.<string, Object.<string, string>>|undefined),
Expand All @@ -214,6 +216,7 @@ spf.ResponseFragment;
* cacheType: (string|undefined),
* foot: (spf.ResponseFragment|undefined),
* head: (spf.ResponseFragment|undefined),
* name: (string|undefined),
* redirect: (string|undefined),
* reload: (boolean|undefined),
* timing: (Object.<string, number>|undefined),
Expand Down
10 changes: 10 additions & 0 deletions src/client/nav/nav.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ goog.require('spf.config');
goog.require('spf.debug');
goog.require('spf.dom');
goog.require('spf.dom.classlist');
goog.require('spf.dom.dataset');
goog.require('spf.history');
goog.require('spf.nav.request');
goog.require('spf.nav.response');
Expand Down Expand Up @@ -750,6 +751,15 @@ spf.nav.handleNavigateSuccess_ = function(options, info, url, response) {
// queued after existing ones from any ongoing part prcoessing.
var r = /** @type {spf.SingleResponse} */ (multipart ? {} : response);
spf.nav.response.process(url, r, info, function() {
// After processing is complete, save the name for future use.
var name = response['name'] || '';
if (multipart) {
var parts = response['parts'];
for (var i = 0; i < parts.length; i++) {
name = parts[i]['name'] || name;
}
}
spf.dom.dataset.set(document.body, 'spfName', name);
// If this navigation was from history, attempt to scroll to the previous
// position after all processing is complete. This should not be done
// earlier because the prevous position might rely on page width/height
Expand Down
17 changes: 16 additions & 1 deletion src/client/nav/response.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ goog.require('spf.config');
goog.require('spf.debug');
goog.require('spf.dom');
goog.require('spf.dom.classlist');
goog.require('spf.dom.dataset');
goog.require('spf.history');
goog.require('spf.net.connect');
goog.require('spf.net.script');
Expand Down Expand Up @@ -137,6 +138,8 @@ spf.nav.response.process = function(url, response, opt_info, opt_callback) {
var hasPosition = opt_info && !!opt_info.position;
var hasScrolled = opt_info && opt_info.scrolled;

var name = response['name'] || '';

// Convert the URL to absolute, to be used for finding the task queue.
var key = 'process ' + spf.url.absolute(url);
var sync = !spf.config.get('experimental-process-async');
Expand Down Expand Up @@ -272,6 +275,7 @@ spf.nav.response.process = function(url, response, opt_info, opt_callback) {
el,
extracted.html,
animationClass,
name,
parseInt(spf.config.get('animation-duration'), 10),
!!isReverse);
// Suspend main queue while the animation is running.
Expand Down Expand Up @@ -445,6 +449,8 @@ spf.nav.response.preprocess = function(url, response, opt_info, opt_callback) {
spf.nav.response.prepareAnimation_ = function(data) {
// Add the start class to put elements in their beginning states.
spf.dom.classlist.add(data.element, data.dirClass);
spf.dom.classlist.add(data.element, data.fromClass);
spf.dom.classlist.add(data.element, data.toClass);
spf.dom.classlist.add(data.element, data.startClass);
spf.dom.classlist.add(data.element, data.startClassDeprecated);
// Pack the existing content into a temporary container.
Expand Down Expand Up @@ -489,6 +495,8 @@ spf.nav.response.completeAnimation_ = function(data) {
// Remove the end class to put elements back in normal state.
spf.dom.classlist.remove(data.element, data.endClass);
spf.dom.classlist.remove(data.element, data.endClassDeprecated);
spf.dom.classlist.remove(data.element, data.fromClass);
spf.dom.classlist.remove(data.element, data.toClass);
spf.dom.classlist.remove(data.element, data.dirClass);
spf.debug.debug(' process done complete animation', data.element.id);
};
Expand Down Expand Up @@ -796,13 +804,14 @@ spf.nav.response.getCurrentUrl_ = function() {
* @param {!Element} el The element being updated.
* @param {string} html The new content for the element.
* @param {string} cls The animation class name.
* @param {string} name The page name.
* @param {number} duration The animation duration.
* @param {boolean} reverse Whether this is a "back" animation.
* @constructor
* @struct
* @private
*/
spf.nav.response.Animation_ = function(el, html, cls, duration, reverse) {
spf.nav.response.Animation_ = function(el, html, cls, name, duration, reverse) {
/** @type {!Element} */
this.element = el;
/** @type {string} */
Expand All @@ -812,8 +821,14 @@ spf.nav.response.Animation_ = function(el, html, cls, duration, reverse) {
/** @type {boolean} */
this.reverse = reverse;

var prevName = spf.dom.dataset.get(document.body, 'spfName') || '';

/** @type {string} */
this.key = spf.tasks.key(el);
/** @type {string} */
this.fromClass = prevName && (cls + '-from-' + prevName);
/** @type {string} */
this.toClass = name && (cls + '-to-' + name);
/** @type {Element} */
this.oldEl = null;
/** @type {string} */
Expand Down
6 changes: 6 additions & 0 deletions src/server/demo/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ def create_spf_response(self, content, fragments=None):
title = str(getattr(content, 'title', ''))
if title:
response[spf.ResponseKey.TITLE] = title
name = str(getattr(content, 'name', ''))
if name:
response[spf.ResponseKey.NAME] = name
attr = json.loads(str(getattr(content, 'attributes', '{}')))
if attr:
response[spf.ResponseKey.ATTR] = attr
Expand Down Expand Up @@ -167,6 +170,9 @@ def chunked_render_spf(self, content, fragments=None, truncate=False):
early[spf.ResponseKey.HEAD] = resp.pop(spf.ResponseKey.HEAD)
if spf.ResponseKey.TITLE in resp:
early[spf.ResponseKey.TITLE] = resp.pop(spf.ResponseKey.TITLE)
if spf.ResponseKey.NAME in resp:
# Don't pop the name so that it is present in both parts.
early[spf.ResponseKey.NAME] = resp[spf.ResponseKey.NAME]
# Begin the multipart response.
yield spf.MultipartToken.BEGIN
# Send part 1.
Expand Down
37 changes: 30 additions & 7 deletions src/server/demo/static/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -109,31 +109,54 @@ html, body {
left: 0;
width: 100%;
height: 100%;
-webkit-transition: -webkit-transform 400ms ease, opacity 400ms linear;
transition: transform 400ms ease, opacity 400ms linear;
-webkit-transition: -webkit-transform 400ms ease, opacity 200ms linear;
transition: transform 400ms ease, opacity 200ms linear;
will-change: transform, opacity;
}
/* Transitions fade-out old and fade-in new */
.spf-animate-start .spf-animate-old,
.spf-animate-end .spf-animate-new {
opacity: 1;
-webkit-transition-delay: 200ms;
transition-delay: 200ms;
}
.spf-animate-start .spf-animate-new,
.spf-animate-end .spf-animate-old {
opacity: 0;
}
/* transitions slide-out old and slide-in new */
/* By default, no movement, just fade */
.spf-animate-start .spf-animate-old,
.spf-animate-end .spf-animate-new {
-webkit-transform: translate(0%, 0%);
transform: translate(0%, 0%);
}
.spf-animate-start.spf-animate-forward .spf-animate-new,
.spf-animate-end.spf-animate-reverse .spf-animate-old {
/* Home/Spec -> Demo, slide in from right */
.spf-animate-from-home.spf-animate-to-demo.spf-animate-start .spf-animate-new,
.spf-animate-from-spec.spf-animate-to-demo.spf-animate-start .spf-animate-new {
-webkit-transform: translate(150%, 0%);
transform: translate(150%, 0%);
-webkit-transition-delay: 0ms;
transition-delay: 0ms;
}
.spf-animate-end.spf-animate-forward .spf-animate-old,
.spf-animate-start.spf-animate-reverse .spf-animate-new {
.spf-animate-from-home.spf-animate-to-demo.spf-animate-end .spf-animate-old,
.spf-animate-from-spec.spf-animate-to-demo.spf-animate-end .spf-animate-old {
-webkit-transform: translate(-150%, 0%);
transform: translate(-150%, 0%);
-webkit-transition-delay: 0ms;
transition-delay: 0ms;
}
/* Demo -> Home/Spec, slide in from left */
.spf-animate-from-demo.spf-animate-to-home.spf-animate-start .spf-animate-new,
.spf-animate-from-demo.spf-animate-to-spec.spf-animate-start .spf-animate-new {
-webkit-transform: translate(-150%, 0%);
transform: translate(-150%, 0%);
-webkit-transition-delay: 0ms;
transition-delay: 0ms;
}
.spf-animate-from-demo.spf-animate-to-home.spf-animate-end .spf-animate-old,
.spf-animate-from-demo.spf-animate-to-spec.spf-animate-end .spf-animate-old {
-webkit-transform: translate(150%, 0%);
transform: translate(150%, 0%);
-webkit-transition-delay: 0ms;
transition-delay: 0ms;
}
2 changes: 1 addition & 1 deletion src/server/demo/templates/base.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ See the LICENSE file for details.
<link rel="stylesheet" href="/static/app.css" name="app">
$:content.stylesheet
</head>
<body>
<body data-spf-name="$content.name">
<div id="masthead">
<strong>S</strong>tructured <strong>P</strong>age <strong>F</strong>ragments
<span class="info">
Expand Down
1 change: 1 addition & 0 deletions src/server/demo/templates/chunked.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ $def _javascript():


$var title: Chunked Test
$var name: chunked
$var stylesheet: $:_stylesheet()
$var javascript: $:_javascript()
$var attributes: $:_attributes()
Expand Down
1 change: 1 addition & 0 deletions src/server/demo/templates/demo.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ $def _attributes():
{ "nav": { "class": "demo$page_num" } }

$var title: Demo Page $page_num
$var name: demo
$var stylesheet: $:_stylesheet()
$var javascript:
$var attributes: $:_attributes()
Expand Down
1 change: 1 addition & 0 deletions src/server/demo/templates/index.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ $def _attributes():
{ "nav": { "class": "home" } }

$var title: Home
$var name: home
$var stylesheet: $:_stylesheet()
$var javascript: $:_javascript()
$var attributes: $:_attributes()
Expand Down
1 change: 1 addition & 0 deletions src/server/demo/templates/missing.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ $def _attributes():
{ "nav": { "class": "missing" } }

$var title: Missing
$var name: missing
$var stylesheet:
$var javascript:
$var attributes: $:_attributes()
Expand Down
1 change: 1 addition & 0 deletions src/server/demo/templates/other.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ $def _attributes():
{ "nav": { "class": "other" } }

$var title: Other
$var name: other
$var stylesheet:
$var javascript:
$var attributes: $:_attributes()
Expand Down
1 change: 1 addition & 0 deletions src/server/demo/templates/spec.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ $def _attributes():
{ "nav": { "class": "spec" } }

$var title: Spec
$var name: spec
$var stylesheet: $:_stylesheet()
$var javascript: $:_javascript()
$var attributes: $:_attributes()
Expand Down
1 change: 1 addition & 0 deletions src/server/demo/templates/truncated.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ $def _attributes():
{ "nav": { "class": "truncated" } }

$var title: Truncated
$var name: truncated
$var stylesheet:
$var javascript:
$var attributes: $:_attributes()
Expand Down
1 change: 1 addition & 0 deletions src/server/python/spf.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class ResponseKey(object):
FOOT = 'foot'
ATTR = 'attr'
BODY = 'body'
NAME = 'name'
REDIRECT = 'redirect'


Expand Down

0 comments on commit 5c02113

Please sign in to comment.