Skip to content

Commit

Permalink
Unify mobile state in JS, drop resize
Browse files Browse the repository at this point in the history
In `gh-content-view-container` the visibility of another DOM node was
being used to detect if a given view was mobile or not. This means the
UI needed to have layout forced (and DOM rendered) before the content
view container would render a second time. This is slow interaction with
the DOM (forcing layout) and slow for Ember's renderer (it needs to
render the container once with a default, then again when the value
changes).

Additionally there were two ways resize was being observed. The
`Window.matchMedia` API was used for some styles and the `ember-resize`
addon used to detect other changes. Here I've unified around just the
`Window.matcheMedia` API but abstracted it behind a service.

Sizes are exposed as properties that can be bound to or used directly in
templates.
  • Loading branch information
mixonic committed Nov 25, 2015
1 parent 787554b commit 402b27c
Show file tree
Hide file tree
Showing 15 changed files with 95 additions and 120 deletions.
24 changes: 2 additions & 22 deletions core/client/app/components/gh-content-view-container.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,6 @@ export default Ember.Component.extend({
tagName: 'section',
classNames: ['gh-view', 'content-view-container'],

previewIsHidden: false,

resizeService: Ember.inject.service(),

_resizeListener: null,

calculatePreviewIsHidden: function () {
if (this.$('.content-preview').length) {
this.set('previewIsHidden', !this.$('.content-preview').is(':visible'));
}
},

didInsertElement: function () {
this._super(...arguments);
this._resizeListener = Ember.run.bind(this, this.calculatePreviewIsHidden);
this.get('resizeService').on('debouncedDidResize', this._resizeListener);
this.calculatePreviewIsHidden();
},

willDestroy: function () {
this.get('resizeService').off('debouncedDidResize', this._resizeListener);
}
mediaQueries: Ember.inject.service(),
previewIsHidden: Ember.computed.reads('mediaQueries.maxWidth900')
});
16 changes: 2 additions & 14 deletions core/client/app/components/gh-menu-toggle.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ closes the mobile menu
*/

import Ember from 'ember';
import mobileQuery from 'ghost/utils/mobile';

export default Ember.Component.extend({
classNames: ['gh-menu-toggle'],

isMobile: false,
mediaQueries: Ember.inject.service(),
isMobile: Ember.computed.reads('mediaQueries.isMobile'),
maximise: false,

iconClass: Ember.computed('maximise', 'isMobile', function () {
Expand All @@ -26,18 +26,6 @@ export default Ember.Component.extend({
}
}),

didInsertElement: function () {
this.set('isMobile', mobileQuery.matches);
this.set('mqListener', Ember.run.bind(this, function (mql) {
this.set('isMobile', mql.matches);
}));
mobileQuery.addListener(this.get('mqListener'));
},

willDestroyElement: function () {
mobileQuery.removeListener(this.get('mqListener'));
},

click: function () {
if (this.get('isMobile')) {
this.sendAction('mobileAction');
Expand Down
3 changes: 3 additions & 0 deletions core/client/app/components/gh-tag-settings-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ export default Ember.Component.extend({

config: Ember.inject.service(),

mediaQueries: Ember.inject.service(),
isMobile: Ember.computed.reads('mediaQueries.maxWidth600'),

title: Ember.computed('tag.isNew', function () {
if (this.get('tag.isNew')) {
return 'New Tag';
Expand Down
34 changes: 10 additions & 24 deletions core/client/app/components/gh-tags-management-container.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,18 @@ export default Ember.Component.extend({
classNames: ['view-container'],
classNameBindings: ['isMobile'],

mobileWidth: 600,
mediaQueries: Ember.inject.service(),

tags: null,
selectedTag: null,

isMobile: false,
isMobile: Ember.computed.reads('mediaQueries.maxWidth600'),
isEmpty: Ember.computed.equal('tags.length', 0),

resizeService: Ember.inject.service('resize-service'),

_resizeListener: null,
init: function () {
this._super(...arguments);
Ember.run.schedule('actions', this, this.fireMobileChangeActions);
},

displaySettingsPane: Ember.computed('isEmpty', 'selectedTag', 'isMobile', function () {
const isEmpty = this.get('isEmpty'),
Expand All @@ -36,25 +38,9 @@ export default Ember.Component.extend({
return true;
}),

toggleMobile: function () {
let width = Ember.$(window).width();

if (width < this.get('mobileWidth')) {
this.set('isMobile', true);
this.sendAction('enteredMobile');
} else {
this.set('isMobile', false);
fireMobileChangeActions: Ember.observer('isMobile', function () {
if (!this.get('isMobile')) {
this.sendAction('leftMobile');
}
},

didInitAttrs: function () {
this._resizeListener = Ember.run.bind(this, this.toggleMobile);
this.get('resizeService').on('debouncedDidResize', this._resizeListener);
this.toggleMobile();
},

willDestroyElement: function () {
this.get('resizeService').off('debouncedDidResize', this._resizeListener);
}
})
});
15 changes: 3 additions & 12 deletions core/client/app/controllers/settings/tags.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@ export default Ember.Controller.extend({

tagController: inject.controller('settings.tags.tag'),

// set at controller level because it's shared by routes and components
mobileWidth: 600,

isMobile: false,
selectedTag: alias('tagController.tag'),

tagListFocused: equal('keyboardFocus', 'tagList'),
Expand All @@ -31,17 +27,12 @@ export default Ember.Controller.extend({
}),

actions: {
enteredMobile: function () {
this.set('isMobile', true);
},

leftMobile: function () {
this.set('isMobile', false);

let firstTag = this.get('tags.firstObject');
// redirect to first tag if possible so that you're not left with
// tag settings blank slate when switching from portrait to landscape
if (this.get('tags.length') && !this.get('tagController.tag')) {
this.transitionToRoute('settings.tags.tag', this.get('tags.firstObject'));
if (firstTag && !this.get('tagController.tag')) {
this.transitionToRoute('settings.tags.tag', firstTag);
}
}
}
Expand Down
32 changes: 16 additions & 16 deletions core/client/app/routes/mobile-index-route.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
import Ember from 'ember';
import mobileQuery from 'ghost/utils/mobile';

// Routes that extend MobileIndexRoute need to implement
// desktopTransition, a function which is called when
// the user resizes to desktop levels.
export default Ember.Route.extend({
desktopTransition: Ember.K,
_callDesktopTransition: null,

activate: function attachDesktopTransition() {
this._super();
mobileQuery.addListener(this.desktopTransitionMQ);
},
mediaQueries: Ember.inject.service(),

deactivate: function removeDesktopTransition() {
this._super();
mobileQuery.removeListener(this.desktopTransitionMQ);
activate: function () {
this._callDesktopTransition = () => {
if (!this.get('mediaQueries.isMobile')) {
this.desktopTransition();
}
};
Ember.addObserver(this, 'mediaQueries.isMobile', this._callDesktopTransition);
},

setDesktopTransitionMQ: Ember.on('init', function () {
var self = this;
this.set('desktopTransitionMQ', function desktopTransitionMQ() {
if (!mobileQuery.matches) {
self.desktopTransition();
}
});
})
deactivate: function () {
if (this._callDesktopTransition) {
Ember.removeObserver(this, 'mediaQueries.isMobile', this._callDesktopTransition);
this._callDesktopTransition = null;
}
}

});
10 changes: 6 additions & 4 deletions core/client/app/routes/posts/index.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import Ember from 'ember';
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
import MobileIndexRoute from 'ghost/routes/mobile-index-route';
import mobileQuery from 'ghost/utils/mobile';

export default MobileIndexRoute.extend(AuthenticatedRouteMixin, {
noPosts: false,

mediaQueries: Ember.inject.service(),
isMobile: Ember.computed.reads('mediaQueries.isMobile'),

// Transition to a specific post if we're not on mobile
beforeModel: function () {
if (!mobileQuery.matches) {
if (!this.get('isMobile')) {
return this.goToPost();
}
},

setupController: function (controller, model) {
/*jshint unused:false*/
setupController: function (controller) {
controller.set('noPosts', this.get('noPosts'));
},

Expand Down
10 changes: 4 additions & 6 deletions core/client/app/routes/settings/tags/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@ import AuthenticatedRoute from 'ghost/routes/authenticated';

export default AuthenticatedRoute.extend({

// HACK: ugly way of changing behaviour when on mobile
beforeModel: function () {
const firstTag = this.modelFor('settings.tags').get('firstObject'),
mobileWidth = this.controllerFor('settings.tags').get('mobileWidth'),
viewportWidth = Ember.$(window).width();
mediaQueries: Ember.inject.service(),

if (firstTag && viewportWidth > mobileWidth) {
beforeModel: function () {
let firstTag = this.modelFor('settings.tags').get('firstObject');
if (firstTag && !this.get('mediaQueries.maxWidth600')) {
this.transitionTo('settings.tags.tag', firstTag);
}
}
Expand Down
46 changes: 46 additions & 0 deletions core/client/app/services/media-queries.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import Ember from 'ember';

const MEDIA_QUERIES = {
maxWidth600: '(max-width: 600px)',
isMobile: '(max-width: 800px)',
maxWidth900: '(max-width: 900px)',
maxWidth1000: '(max-width: 1000px)'
};

export default Ember.Service.extend({
init: function () {
this._super(...arguments);
this._handlers = [];
this.loadQueries(MEDIA_QUERIES);
},

loadQueries: function (queries) {
Object.keys(queries).forEach(key => {
this.loadQuery(key, queries[key]);
});
},

loadQuery: function (key, queryString) {
let query = window.matchMedia(queryString);

this.set(key, query.matches);

let handler = Ember.run.bind(this, () => {
let lastValue = this.get(key);
let newValue = query.matches;
if (lastValue !== newValue) {
this.set(key, query.matches);
}
});
query.addListener(handler);
this._handlers.push([query, handler]);
},

willDestroy: function () {
this._handlers.forEach(([query, handler]) => {
query.removeListener(handler);
});
this._super(...arguments);
}

});
2 changes: 1 addition & 1 deletion core/client/app/templates/settings/tags.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
</section>
</header>

{{#gh-tags-management-container mobileWidth=mobileWidth tags=tags selectedTag=selectedTag enteredMobile="enteredMobile" leftMobile="leftMobile" as |container|}}
{{#gh-tags-management-container tags=tags selectedTag=selectedTag enteredMobile="enteredMobile" leftMobile="leftMobile" as |container|}}
{{#gh-infinite-scroll
fetch="loadNextPage"
isLoading=isLoading
Expand Down
2 changes: 1 addition & 1 deletion core/client/app/templates/settings/tags/tag.hbs
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{{gh-tag-settings-form tag=tag setProperty=(action "setProperty") openModal="openModal" isMobile=isMobile}}
{{gh-tag-settings-form tag=tag setProperty=(action "setProperty") openModal="openModal"}}
1 change: 0 additions & 1 deletion core/client/app/utils/mobile.js

This file was deleted.

7 changes: 0 additions & 7 deletions core/client/config/environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,6 @@ module.exports = function (environment) {
authenticationRoute: 'signin',
routeAfterAuthentication: 'posts',
routeIfAlreadyAuthenticated: 'posts'
},

resizeServiceDefaults: {
debounceTimeout: 100,
heightSensitive: false,
widthSensitive: true,
injectionFactories: []
}
};

Expand Down
1 change: 0 additions & 1 deletion core/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
"ember-disable-proxy-controllers": "^1.0.0",
"ember-export-application-global": "^1.0.3",
"ember-myth": "0.1.1",
"ember-resize": "0.0.10",
"ember-simple-auth": "1.0.0",
"ember-sinon": "0.2.1",
"ember-watson": "^0.6.4",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,14 @@ import {
import hbs from 'htmlbars-inline-precompile';
import Ember from 'ember';

const resizeStub = Ember.Service.extend(Ember.Evented, {

});

describeComponent(
'gh-tags-management-container',
'Integration: Component: gh-tags-management-container',
{
integration: true
},
function () {
beforeEach(function () {
this.register('service:resize-service', resizeStub);
this.inject.service('resize-service', {as: 'resize-service'});
});

it('renders', function () {
this.set('mobileWidth', 600);
this.set('tags', []);
this.set('selectedTag', null);
this.on('enteredMobile', function () {
Expand All @@ -35,7 +25,7 @@ describeComponent(
});

this.render(hbs`
{{#gh-tags-management-container mobileWidth=mobileWidth tags=tags selectedTag=selectedTag enteredMobile="enteredMobile" leftMobile="leftMobile"}}{{/gh-tags-management-container}}
{{#gh-tags-management-container tags=tags selectedTag=selectedTag enteredMobile="enteredMobile" leftMobile="leftMobile"}}{{/gh-tags-management-container}}
`);
expect(this.$()).to.have.length(1);
});
Expand Down

0 comments on commit 402b27c

Please sign in to comment.