Skip to content

Commit

Permalink
Issue #775 Drop Frameback
Browse files Browse the repository at this point in the history
  • Loading branch information
Jason Zhou committed Feb 14, 2017
1 parent 9611f41 commit 22a7b9f
Show file tree
Hide file tree
Showing 15 changed files with 31 additions and 600 deletions.
39 changes: 1 addition & 38 deletions docs/guides/client-transitions.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,41 +82,4 @@ that's shared between page (for example a tab-set or a sidebar menu). With
ordinary navigation, and by default with client transitions, the DOM is blown
away and recreated each time you navigate. With `reuseDom` portions of the
page that are the same as the previous page are _re-used_. This includes
`React` component state! This is single page app navigation done _right_.

### `frameback`

```javascript
<Link path={path} frameback={true}>...</Link>

navigateTo(path, {frameback: true});
```

This one addresses a very specific navigation pattern: A _list_ page that's
_expensive_ to render, which has links to _details_ pages, where the common
navigation is: Forward to a details page, back to the list page, forward to
_another_ details page, ...

What `frameback` does is make the navigation _back_ to the list page instant.
It does this by loading the _details_ page in an iframe that covers the
viewport and simply hiding the iframe on back-navigation. This _only_ makes
sense to use if the list page is expensive to render! For example if it has a
large map with many overlays representing the geographic location of items in
the list.

### `reuseFrame`

```javascript
<Link path={path} frameback={true} reuseFrame={true}>...</Link>

navigateTo(path, {frameback: true, reuseFrame: true});
```

This one only affects the behavior of `frameback`. Ordinarily each new
details page navigated to using `frameback` is loaded _from the server_ in a
new iframe. With `reuseFrame` navigation to a new details page may perform a
_client transition_ within the frame instead!

When `reuseFrame` is enabled the other client transition options
(`bundleData`, `reuseDom`) are also available for the navigation between
details pages within the frame.
`React` component state! This is single page app navigation done _right_.
2 changes: 1 addition & 1 deletion packages/react-server-examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ Some example apps for `react-server`, which use `react-server-cli` to compile th
Installation and running instructions are in the respective README.md files:

- [**hello-world**](./hello-world): A simple one-page app that shows off server rendering, a simple routes file, and client-side interactivity.
- [**bike-share**](./bike-share): An example project for react-server which demos server rendering, interactivity on the client side, frameback, ReactServerAgent, url parameters and logging.
- [**bike-share**](./bike-share): An example project for react-server which demos server rendering, interactivity on the client side, ReactServerAgent, url parameters and logging.
- [**redux-basic**](./redux-basic): An example of the official Redux Counter app powered by react-server.
- [**meteor-site**](./meteor-site): An example of react-server using Google Map, redux and transitions.
2 changes: 1 addition & 1 deletion packages/react-server-examples/bike-share/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# react-server-examples/bike-share

An example project for `react-server` which demos server rendering, interactivity
on the client side, frameback, ReactServerAgent, url parameters and logging.
on the client side, ReactServerAgent, url parameters and logging.
Uses [api.citybik.es](http://api.citybik.es/v2/) to get data about bike shares
and their availability around the world.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const logger = logging.getLogger(__LOGGER__);
const NetworkCard = ({id, name, location, company}) => {
logger.info(`rendering card for network ${name}`);
return (
<div><Link path={`/network?network=${id}`} frameback>{name}</Link> in {location.city}, {location.country}, run by {company}</div>
<div><Link path={`/network?network=${id}`}>{name}</Link> in {location.city}, {location.country}, run by {company}</div>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
.pick-pointer {
width: 16px;
cursor: pointer;
}

.not-available {
text-decoration: line-through;
}
}
33 changes: 0 additions & 33 deletions packages/react-server-test-pages/pages/navigation/playground.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,32 +82,6 @@ const CT = ({row}) => <Link path={LINK(row)}>CT</Link>
const RD = ({row}) => <Link reuseDom={true} path={LINK(row)}>CT/RD</Link>
const BD = ({row}) => <Link bundleData={true} path={LINK(row)}>CT/BD</Link>
const BDRD = ({row}) => <Link bundleData={true} reuseDom={true} path={LINK(row)}>CT/BD/RD</Link>
const FB = (props) => <FBL {...props}></FBL>
const FBCT = (props) => <FBL {...props} link={{reuseFrame:true}}>CT</FBL>
const FBCTBD = (props) => <FBL {...props} link={{reuseFrame:true, bundleData:true}}>CT/BD</FBL>
const FBCTRD = (props) => <FBL {...props} link={{reuseFrame:true, reuseDom:true}}>CT/RD</FBL>
const FBCTBDRD = (props) => <FBL {...props} link={{reuseFrame:true, bundleData:true, reuseDom:true}}>CT/BD/RD</FBL>

// Frameback Link.
class FBL extends React.Component {
constructor(props){
super(props);
this.state = {available: true};
}
componentDidMount() {
if (window.__reactServerIsFrame) {
this.setState({available: false});
}
}
render() {
return <Link path={LINK(this.props.row)} frameback={true} {...this.props.link}>
<span className={this.state.available?'available':'not-available'}>FB</span>{
this.props.children?['/',...React.Children.toArray(this.props.children)]:[]
}
</Link>
}
}


export default class NavigationPlaygroundPage {
handleRoute(next) {
Expand All @@ -131,8 +105,6 @@ export default class NavigationPlaygroundPage {
<li>CT: Client Transition</li>
<li>RD: Reuse DOM</li>
<li>BD: Bundle Data</li>
<li>FB: Frameback</li>
<li><span className='not-available'>FB</span>: Frameback disabled (already in a frame)</li>
</ul>
</RootContainer>,
...this.data.map(promise => <RootContainer when={promise} className="row">
Expand All @@ -146,11 +118,6 @@ export default class NavigationPlaygroundPage {
<RD />
<BD />
<BDRD />
<FB />
<FBCT />
<FBCTBD />
<FBCTRD />
<FBCTBDRD />
</RootContainer>),
]
}
Expand Down
132 changes: 22 additions & 110 deletions packages/react-server/core/ClientController.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ var React = require('react'),
History = require('./components/History'),
PageUtil = require("./util/PageUtil"),
ReactServerAgent = require('./ReactServerAgent'),
FramebackController = require('./FramebackController'),
{getRootElementAttributes} = require('./components/RootElement'),
{PAGE_LINK_NODE_ID, PAGE_CONTAINER_NODE_ID} = require('./constants');

Expand Down Expand Up @@ -106,82 +105,18 @@ class ClientController extends EventEmitter {
:new Date; // There's no naviagation. We're it.

const url = request.getUrl();
const FC = this.context.framebackController;
const isPush = type === History.events.PUSHSTATE;
const shouldEnterFrame = request.getFrameback() && (
// A push to a frame, or a pop _from_ a previous push.
isPush || ((this._lastState||{}).reactServerFrame||{})._framebackExit
);

// This is who we're going to listen to regarding when navigation is
// complete for timing purposes. By default it's our navigator, but
// if we're going to do a frameback navigation we'll listen to the
// frameback controller instead.

// This is the navigator we're going to listen to regarding when navigation
// is complete for timing purposes.
let navigationTimingAuthority = this.context.navigator;

this._reuseDom = request.getReuseDom();

// If we're _entering_ a frame or we're _already in_ a frame
// then we'll delegate navigation to the frameback controller,
// which will tell the client controller within the frame what
// to do.
if (shouldEnterFrame || FC.isActive()) {

// Tell the navigator we got this one.
this.context.navigator.ignoreCurrentNavigation();

// This only happens on a popstate.
if (request.getOpts()._framebackExit) {

// That was fun!
FC.navigateBack();
setTimeout(() => {

// Need to do this in a new time slice
// to get order of events right in
// external subscribers.
this.context.navigator.finishRoute();
});

} else {

// We're going to let the navigator unlock navigation (via
// back button) as soon as our frame starts loading. This is
// nice from an interactivity perspective. But we still want
// to know how long it actually took to load the content in
// the frame. For that we'll listen to the frameback
// controller.
navigationTimingAuthority = FC;

// Here we go...
FC.navigate(request).then(() => {
this.context.navigator.finishRoute();
});
}

} else if (this._previouslyRendered) {

// If we're supposed to exit a frame, and we don't
// have one open, then we need to do a full browser
// navigation. There's no provision for client
// transitions between the outer page and the frame.
if (request.getOpts()._framebackExit) {

// This is just so the navigator doesn't try
// to proceed with an ordinary navigation.
// This whole window is toast.
this.context.navigator.ignoreCurrentNavigation();

// Start from scratch with current URL (we've
// just popped).
document.location.reload();
if (this._previouslyRendered) {

// That's all, folks.
return;
}

// If this is a secondary request (client transition)
// within a session, then we'll get a fresh
// This is a secondary request (client transition)
// within a session, so we'll get a fresh
// RequestLocalStorage container.
RequestLocalStorage.startRequest();

Expand All @@ -197,15 +132,14 @@ class ClientController extends EventEmitter {
}

// If this is a History.events.PUSHSTATE navigation,
// and we have control of the navigation bar (we're
// not in a frameback frame) we should change the URL
// in the location bar before rendering.
// and we have control of the navigation bar we should
// change the URL in the location bar before rendering.
//
// Note that for browsers that do not have pushState,
// this will result in a window.location change and
// full browser load.
//
if (this._history && this._history.hasControl()) {
if (this._history) {

if (isPush) {

Expand All @@ -231,20 +165,10 @@ class ClientController extends EventEmitter {
}

this._setHistoryRequestOpts({

// If we're entering a frame, then
// when we get back here we need to
// exit.
_framebackExit: request.getFrameback(),

// If we're reusing the DOM on the way
// forward, then we can also reuse on
// the way back.
reuseDom: request.getReuseDom(),

// The same reasoning as for
// `reuseDom` also applies here.
reuseFrame: request.getReuseFrame(),
});

this._history.pushState(
Expand Down Expand Up @@ -283,34 +207,24 @@ class ClientController extends EventEmitter {
reuseDom: true,
});
}
} else if (this._history) {

// We're in a frameback frame, but we want to make sure that the
// frame's `document.location` stays up to date.
window.history.replaceState(null, null, url);
}

// If we've got control of the URL bar we'll also take responsibility
// for logging how long the request took in a variety of ways:
// logging how long the request took in a variety of ways:
// - Request type (pageload, pushstate, popstate)
// - Request options (reuseDom, bundleData, etc)
if (!window.__reactServerIsFrame) {
navigationTimingAuthority.once('loadComplete', () => {
const bas = `handleRequest`;
const typ = `type.${type||'PAGELOAD'}`;
logTimingData(`${bas}.all`, t0);
logTimingData(`${bas}.${typ}.all`, t0);
_.forEach(request.getOpts(), (val, key) => {
if (val) {
const opt = `opt.${key}`;
logTimingData(`${bas}.${opt}`, t0);
logTimingData(`${bas}.${typ}.${opt}`, t0);
}
});
navigationTimingAuthority.once('loadComplete', () => {
const bas = `handleRequest`;
const typ = `type.${type||'PAGELOAD'}`;
logTimingData(`${bas}.all`, t0);
logTimingData(`${bas}.${typ}.all`, t0);
_.forEach(request.getOpts(), (val, key) => {
if (val) {
const opt = `opt.${key}`;
logTimingData(`${bas}.${opt}`, t0);
logTimingData(`${bas}.${typ}.${opt}`, t0);
}
});
}


});

this._lastState = history.state;
}
Expand Down Expand Up @@ -937,8 +851,6 @@ function buildContext(routes) {

context.setMobileDetect(new MobileDetect(navigator.userAgent));

context.setFramebackController(new FramebackController());

return context;
}

Expand Down
19 changes: 0 additions & 19 deletions packages/react-server/core/ClientRequest.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,12 @@ class ClientRequest {

constructor(url, {
bundleData,
frameback,
reuseDom,
reuseFrame,

// These are for internal statekeeping. Don't use them yourself.
_framebackExit,
_fromOuterFrame,
}={}) {
this._url = url;
this._opts = {
bundleData,
frameback,
reuseDom,
reuseFrame,
_framebackExit,
_fromOuterFrame,
}
}

Expand All @@ -39,18 +29,10 @@ class ClientRequest {
return this._opts;
}

getFrameback() {
return this._opts.frameback;
}

getReuseDom() {
return this._opts.reuseDom;
}

getReuseFrame() {
return this._opts.reuseFrame;
}

getBundleData() {
return this._opts.bundleData;
}
Expand Down Expand Up @@ -120,7 +102,6 @@ class ClientRequest {
console.error("ClientRequest.getBody not implemented.");
}


}

module.exports = ClientRequest;
Loading

0 comments on commit 22a7b9f

Please sign in to comment.