Roughly implement routes and data preloading

Only preloading data for basic requests w/o query params, at least for
the moment - if we have to preload for something like
/?q=test&sort=newest, we end up having to duplicate a whole lot of
logic between JS/PHP.
This commit is contained in:
Toby Zerner 2015-06-18 17:41:37 +09:30
parent c2da4a946f
commit 822a216cc9
11 changed files with 126 additions and 26 deletions

View File

@ -78,11 +78,17 @@ export default class DiscussionList extends Component {
} }
loadResults(offset) { loadResults(offset) {
if (app.preload.response) {
var discussions = app.store.pushPayload(app.preload.response);
app.preload.response = null;
return m.deferred().resolve(discussions).promise;
} else {
var params = this.params(); var params = this.params();
params.page = {offset}; params.page = {offset};
params.include = params.include.join(','); params.include = params.include.join(',');
return app.store.find('discussions', params); return app.store.find('discussions', params);
} }
}
loadMore() { loadMore() {
var self = this; var self = this;

View File

@ -44,7 +44,21 @@ export default class DiscussionPage extends mixin(Component, evented) {
var params = this.params(); var params = this.params();
params.include = params.include.join(','); params.include = params.include.join(',');
var discussion;
if (app.preload.response) {
// We must wrap this in a setTimeout because if we are mounting this
// component for the first time on page load, then any calls to m.redraw
// will be ineffective and thus any configs (scroll code) will be run
// before stuff is drawn to the page.
setTimeout(() => {
var discussion = app.store.pushPayload(app.preload.response);
app.preload.response = null;
this.setupDiscussion(discussion);
});
} else {
app.store.find('discussions', m.route.param('id'), params).then(this.setupDiscussion.bind(this)); app.store.find('discussions', m.route.param('id'), params).then(this.setupDiscussion.bind(this));
}
// Trigger a redraw only if we're not already in a computation (e.g. route change) // Trigger a redraw only if we're not already in a computation (e.g. route change)
m.startComputation(); m.startComputation();
@ -77,12 +91,19 @@ export default class DiscussionPage extends mixin(Component, evented) {
app.setTitle(discussion.title()); app.setTitle(discussion.title());
var includedPosts = []; var includedPosts = [];
discussion.payload.included && discussion.payload.included.forEach(record => { if (discussion.payload && discussion.payload.included) {
discussion.payload.included.forEach(record => {
if (record.type === 'posts' && (record.contentType !== 'comment' || record.contentHtml)) { if (record.type === 'posts' && (record.contentType !== 'comment' || record.contentHtml)) {
includedPosts.push(app.store.getById('posts', record.id)); includedPosts.push(app.store.getById('posts', record.id));
} }
}); });
includedPosts.sort((a, b) => a.id() - b.id()); includedPosts.sort((a, b) => a.id() - b.id());
} else {
var posts = discussion.posts();
if (posts) {
includedPosts = posts.filter(post => post);
}
}
this.stream = new PostStream({ discussion, includedPosts }); this.stream = new PostStream({ discussion, includedPosts });
this.stream.on('positionChanged', this.positionChanged.bind(this)); this.stream.on('positionChanged', this.positionChanged.bind(this));

View File

@ -199,9 +199,8 @@ export default class PostScrubber extends Component {
if (isInitialized) { return; } if (isInitialized) { return; }
this.renderScrollbar();
context.onunload = this.ondestroy.bind(this); context.onunload = this.ondestroy.bind(this);
this.scrollListener.start(); this.scrollListener.start();
// Whenever the window is resized, adjust the height of the scrollbar // Whenever the window is resized, adjust the height of the scrollbar

View File

@ -23,7 +23,7 @@ export default class SearchBox extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.value = m.prop(this.getCurrentSearch() || ''); this.value = m.prop();
this.hasFocus = m.prop(false); this.hasFocus = m.prop(false);
this.sources = this.sourceItems().toArray(); this.sources = this.sourceItems().toArray();
@ -40,10 +40,16 @@ export default class SearchBox extends Component {
} }
getCurrentSearch() { getCurrentSearch() {
return typeof app.current.searching === 'function' && app.current.searching(); return app.current && typeof app.current.searching === 'function' && app.current.searching();
} }
view() { view() {
// Initialize value in the view rather than the constructor so that we have
// access to app.current.
if (typeof this.value() === 'undefined') {
this.value(this.getCurrentSearch() || '');
}
var currentSearch = this.getCurrentSearch(); var currentSearch = this.getCurrentSearch();
return m('div.search-box.dropdown', { return m('div.search-box.dropdown', {
@ -104,7 +110,7 @@ export default class SearchBox extends Component {
case 13: // Return case 13: // Return
this.$('input').blur(); this.$('input').blur();
this.getItem(this.index()).find('a')[0].dispatchEvent(new Event('click')); m.route(this.getItem(this.index()).find('a').attr('href'));
break; break;
case 27: // Escape case 27: // Escape

View File

@ -18,8 +18,11 @@ export default function(app) {
app.history = new History(); app.history = new History();
app.pane = new Pane(id('page')); app.pane = new Pane(id('page'));
app.search = new SearchBox();
app.cache = {}; app.cache = {};
m.startComputation();
m.mount(id('back-control'), BackButton.component({ className: 'back-control', drawer: true })); m.mount(id('back-control'), BackButton.component({ className: 'back-control', drawer: true }));
m.mount(id('back-button'), BackButton.component()); m.mount(id('back-button'), BackButton.component());
@ -39,10 +42,12 @@ export default function(app) {
app.modal = m.mount(id('modal'), Modal.component()); app.modal = m.mount(id('modal'), Modal.component());
app.alerts = m.mount(id('alerts'), Alerts.component()); app.alerts = m.mount(id('alerts'), Alerts.component());
m.route.mode = 'hash'; m.route.mode = 'pathname';
m.route(id('content'), '/', mapRoutes(app.routes)); m.route(id('content'), '/', mapRoutes(app.routes));
app.search = new SearchBox(); m.endComputation();
new ScrollListener(top => $('body').toggleClass('scrolled', top > 0)).start(); new ScrollListener(top => $('body').toggleClass('scrolled', top > 0)).start();
app.booted = true;
} }

View File

@ -100,7 +100,7 @@ class IndexAction extends SerializeCollectionAction
static::addPaginationLinks( static::addPaginationLinks(
$document, $document,
$request, $request,
$this->url->toRoute('flarum.api.discussions.index'), $request->http ? $this->url->toRoute('flarum.api.discussions.index') : '',
$results->areMoreResults() $results->areMoreResults()
); );

View File

@ -32,8 +32,8 @@ class ShowAction extends SerializeResourceAction
public static $include = [ public static $include = [
'startUser' => false, 'startUser' => false,
'lastUser' => false, 'lastUser' => false,
'startPost' => true, 'startPost' => false,
'lastPost' => true, 'lastPost' => false,
'posts' => true, 'posts' => true,
'posts.user' => true, 'posts.user' => true,
'posts.user.groups' => true, 'posts.user.groups' => true,

View File

@ -0,0 +1,18 @@
<?php namespace Flarum\Forum\Actions;
class DiscussionAction extends IndexAction
{
protected function getDetails($request, $params)
{
$response = $this->apiClient->send('Flarum\Api\Actions\Discussions\ShowAction', [
'id' => $params['id'],
'near' => $params['near']
]);
// TODO: return an object instead of an array?
return [
'title' => $response->data->title,
'response' => $response
];
}
}

View File

@ -6,6 +6,7 @@ use Flarum\Assets\JsCompiler;
use Flarum\Assets\LessCompiler; use Flarum\Assets\LessCompiler;
use Flarum\Core; use Flarum\Core;
use Flarum\Forum\Events\RenderView; use Flarum\Forum\Events\RenderView;
use Flarum\Forum\Loaders\LoaderInterface;
use Flarum\Locale\JsCompiler as LocaleJsCompiler; use Flarum\Locale\JsCompiler as LocaleJsCompiler;
use Flarum\Support\Actor; use Flarum\Support\Actor;
use Flarum\Support\HtmlAction; use Flarum\Support\HtmlAction;
@ -66,11 +67,18 @@ class IndexAction extends HtmlAction
} }
} }
$details = $this->getDetails($request, $params);
$data = array_merge($data, array_get($details, 'data', []));
$response = array_get($details, 'response');
$title = array_get($details, 'title');
$view = view('flarum.forum::index') $view = view('flarum.forum::index')
->with('title', Core::config('forum_title')) ->with('title', ($title ? $title.' - ' : '').Core::config('forum_title'))
->with('config', $config) ->with('config', $config)
->with('layout', 'flarum.forum::forum') ->with('layout', 'flarum.forum::forum')
->with('data', $data) ->with('data', $data)
->with('response', $response)
->with('session', $session) ->with('session', $session)
->with('alert', $alert); ->with('alert', $alert);
@ -112,6 +120,23 @@ class IndexAction extends HtmlAction
->with('scripts', [$assets->getJsFile(), $localeCompiler->getFile()]); ->with('scripts', [$assets->getJsFile(), $localeCompiler->getFile()]);
} }
protected function getDetails($request, $params)
{
$queryParams = $request->getQueryParams();
// Only preload data if we're viewing the default index with no filters,
// otherwise we have to do all kinds of crazy stuff
if (!count($queryParams) && $request->getUri()->getPath() === '/') {
$response = $this->apiClient->send('Flarum\Api\Actions\Discussions\IndexAction');
return [
'response' => $response
];
}
return [];
}
protected static function filterTranslations($translations) protected static function filterTranslations($translations)
{ {
$filtered = []; $filtered = [];

View File

@ -2,7 +2,6 @@
use Flarum\Http\RouteCollection; use Flarum\Http\RouteCollection;
use Flarum\Http\UrlGenerator; use Flarum\Http\UrlGenerator;
use Flarum\Support\AssetManager;
use Flarum\Support\ServiceProvider; use Flarum\Support\ServiceProvider;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
@ -49,16 +48,36 @@ class ForumServiceProvider extends ServiceProvider
{ {
$this->app->instance('flarum.forum.routes', $routes = new RouteCollection); $this->app->instance('flarum.forum.routes', $routes = new RouteCollection);
/**
* Route::group(['middleware' => 'Flarum\Forum\Middleware\LoginWithCookie'], function () use ($action) {
* For the two below
*/
$routes->get( $routes->get(
'/', '/',
'flarum.forum.index', 'flarum.forum.index',
$this->action('Flarum\Forum\Actions\IndexAction') $this->action('Flarum\Forum\Actions\IndexAction')
); );
$routes->get(
'/d/{id:\d+}/{slug}',
'flarum.forum.discussion',
$this->action('Flarum\Forum\Actions\DiscussionAction')
);
$routes->get(
'/d/{id:\d+}/{slug}/{near}',
'flarum.forum.discussion.near',
$this->action('Flarum\Forum\Actions\DiscussionAction')
);
$routes->get(
'/u/{username}',
'flarum.forum.user',
$this->action('Flarum\Forum\Actions\IndexAction')
);
$routes->get(
'/settings',
'flarum.forum.settings',
$this->action('Flarum\Forum\Actions\IndexAction')
);
$routes->get( $routes->get(
'/logout', '/logout',
'flarum.forum.logout', 'flarum.forum.logout',

View File

@ -25,6 +25,7 @@
app.config = {!! json_encode($config) !!}; app.config = {!! json_encode($config) !!};
app.preload = { app.preload = {
data: {!! json_encode($data) !!}, data: {!! json_encode($data) !!},
response: {!! json_encode($response) !!},
session: {!! json_encode($session) !!} session: {!! json_encode($session) !!}
}; };
app.boot(); app.boot();