Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drop Frameback #880

Merged
merged 1 commit into from
Mar 1, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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