Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nightire/b959ece0dd60c6a23c78565bd27feaf4 to your computer and use it in GitHub Desktop.
Save nightire/b959ece0dd60c6a23c78565bd27feaf4 to your computer and use it in GitHub Desktop.
SkeletonScreen Example
import Ember from 'ember';
const { Component, computed } = Ember;
const BlogPostCardComponent = Component.extend({
classNames: ['blog-post-card', 'card'],
classNameBindings: ['disabled::clickable'],
click() {
if (!this.get('disabled')) {
this.get('viewPost')(this.get('post'));
}
},
humanDate: computed('post.createdAt', {
get() {
let createdAt = this.get('post.createdAt');
let day = createdAt.getDate();
let month = createdAt.getMonth() + 1;
let year = createdAt.getFullYear();
return `${month}/${day}/${year}`;
}
}),
isoDate: computed('post.createdAt', {
get() {
return this.get('post.createdAt').toISOString();
}
})
});
BlogPostCardComponent.reopenClass({
positionalParams: ['post']
});
export default BlogPostCardComponent;
import Ember from 'ember';
const { Component } = Ember;
export default Component.extend({
classNames: ['loading-card', 'card'],
});
import Ember from 'ember';
const { Component, computed, computed: { reads } } = Ember;
const { min, max } = Math;
const PaginationButtonsComponent = Component.extend({
tagName: 'p',
classNames: ['pagination-buttons', 'grid'],
page: reads('metadata.page'),
pages: reads('metadata.pages'),
total: reads('metadata.total'),
previousPage: computed('{page,pages}', {
get() {
let lastPage = this.get('pages');
let previousPage = this.get('page') - 1;
return (previousPage > 0)
? min(previousPage, lastPage)
: null;
}
}),
nextPage: computed('{page,pages}', {
get() {
let pages = this.get('pages');
let nextPage = this.get('page') + 1;
return (nextPage <= pages)
? max(nextPage, 1)
: null;
}
})
});
PaginationButtonsComponent.reopenClass({
positionalParams: ['metadata']
});
export default PaginationButtonsComponent;
import Ember from 'ember';
const { Controller, computed: { and, empty, not, or, reads } } = Ember;
export default Controller.extend({
queryParams: ['page', 'fail'],
page: 1,
fail: false,
posts: or('model.{currentWrapper,previousWrapper}.content'),
metadata: reads('posts.meta'),
isLoading: reads('model.currentWrapper.isPending'),
error: reads('model.currentWrapper.reason'),
hasNoPosts: empty('posts'),
hasPosts: not('hasNoPosts'),
showLoadingMessage: and('{hasPosts,isLoading}'),
showSkeleton: and('{hasNoPosts,isLoading}'),
actions: {
viewPost(post) {
this.transitionToRoute('post', post);
}
}
});
import Ember from 'ember';
const { Controller, computed, computed: { reads } } = Ember;
export default Controller.extend({
post: reads('model'),
isoDate: computed('post.createdAt', {
get() {
return this.get('post.createdAt').toISOString();
}
})
});
import Response from 'ember-cli-mirage/response';
const { random } = Math;
export default function() {
this.resource('blog-posts', { except: ['index'] });
this.get('blog-posts', function(schema, request) {
console.log(request.queryParams);
return request.queryParams.fail === "true"
? new Response(500, { 'Content-Type': 'text/html' }, 'Fake Server Error')
: this.serialize(schema.blogPosts.all());
}, { timing: 2000 });
}
import { Factory, association, faker } from 'ember-cli-mirage';
export default Factory.extend({
title: faker.hacker.phrase,
createdAt: faker.date.past,
author: faker.name.findName
});
export default function(server) {
server.createList('blog-post', 10, {author: 'Zelda'});
server.createList('blog-post', 8, {author: 'Link'});
server.createList('blog-post', 9, {author: 'Gannondorf'});
}
import ApplicationSerializer from './application';
const { ceil } = Math;
export default ApplicationSerializer.extend({
serialize(response, req) {
let json = ApplicationSerializer.prototype.serialize.apply(this, arguments);
let posts = json.data;
let total = posts.length;
let page = parseInt(req.queryParams.page, 10) || 1;
let size = parseInt(req.queryParams.size, 10) || 10;
let start = (page - 1) * size;
let end = start + size;
if (start < 0) {
json.data = [];
} else {
json.data = posts
.sortBy('attributes.created-at')
.reverse()
.slice(start, end);
}
return this.attachMetadata(json, { page, size, total });
},
attachMetadata(json, { page, size, total }) {
let pages = ceil(total / size);
json.meta = { page, pages, total };
return json;
}
});
import Model from "ember-data/model";
import attr from "ember-data/attr";
import { belongsTo, hasMany } from "ember-data/relationships";
export default Model.extend({
title: attr('string'),
createdAt: attr('date'),
author: attr('string')
});
import Ember from 'ember';
import config from './config/environment';
const Router = Ember.Router.extend({
location: 'none',
rootURL: config.rootURL
});
Router.map(function() {
this.route('post', { path: '/post/:id' });
});
export default Router;
import Ember from 'ember';
const { Route, Object: EmObject, PromiseProxyMixin } = Ember;
const Wrapper = EmObject.extend(PromiseProxyMixin);
export default Route.extend({
queryParams: {
page: { refreshModel: true },
fail: { refreshModel: true, replace: true }
},
model({ page, fail }) {
let promise = this.store.query('blog-post', { fail, page, size: 5 });
let currentWrapper = Wrapper.create({ promise });
let previousWrapper = this.get('previousWrapper');
currentWrapper.then(() => this.set('previousWrapper', currentWrapper));
currentWrapper.catch(() => {});
return { currentWrapper, previousWrapper };
}
});
import Ember from 'ember';
const { Route } = Ember;
export default Route.extend({
model(params) {
return this.store.findRecord('blog-post', params.id);
}
});
num {
font-family: monospace;
}
.clickable {
cursor: pointer;
}
.post-list {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: top;
}
.blog-post-card.clickable:hover {
background-color: #eee;
}
.blog-post-card, .loading-card {
width: 200px;
margin: 25px;
min-height: 200px;
}
.loading-card .card-content {
text-align: center;
}
<div class="hack container">
<h1>Example of a Skeleton Screen with Route/Controller seperation</h1>
{{outlet}}
</div>
<header class="card-header">
{{post.author}} - <time datetime={{isoDate}}>{{humanDate}}</time>
</header>
<div class="card-content">
<div class="inner">
{{post.title}}
</div>
</div>
<header class="card-header">
Loading&hellip;
</header>
<div class="card-content">
<div class="inner">
<div class="loading"></div>
</div>
</div>
<div class="cell -4of12">
{{#if previousPage}}
<button
class="btn btn-default btn-ghost"
disabled={{disabled}}
{{action (action updatePage previousPage)}}>
&lt;-- Previous Page
</button>
{{/if}}
</div>
<div class="cell -4of12">
Page {{page}} of {{pages}}, Total Posts: {{total}}
</div>
<div class="cell -4of12">
{{#if nextPage}}
<button
class="btn btn-default btn-ghost"
disabled={{disabled}}
{{action (action updatePage nextPage)}}>
Next Page --&gt;
</button>
{{/if}}
</div>
<div class="btn-group">
{{#link-to "index" (query-params fail=true) tagName="button" class="btn btn-error btn-ghost"}}
Experience Controller managed error
{{/link-to}}
{{#link-to "post" 0 tagName="button" class="btn btn-error btn-ghost"}}
Experience Ember managed routing error
{{/link-to}}
</div>
{{#if showLoadingMessage}}
<p>
<div class="alert alert-info">
<div class="loading"></div>
Loading&hellip;
</div>
</p>
{{/if}}
{{#if error}}
<div class="alert alert-error">
<div>There was an error loading the posts.</div>
<br>
<div>{{error}}</div>
<br>
{{#link-to "index" (query-params fail=false) tagName="button" class="btn btn-success btn-ghost"}}
Try Again
{{/link-to}}
</div>
{{/if}}
{{#if metadata}}
{{pagination-buttons metadata
disabled=isLoading
updatePage=(action (mut page))}}
{{/if}}
<div class="post-list">
{{#each posts as |post|}}
{{blog-post-card post
disabled=isLoading
viewPost=(action "viewPost")}}
{{else if showSkeleton}}
{{loading-card}}
{{loading-card}}
{{loading-card}}
{{loading-card}}
{{loading-card}}
{{else unless error}}
<div class="alert alert-warning">
No posts available
</div>
{{/each}}
</div>
{{#if metadata}}
{{pagination-buttons metadata
disabled=isLoading
updatePage=(action (mut page))}}
{{/if}}
<p>
{{#link-to "index" tagName="button" class="btn btn-default btn-ghost"}}
&lt;-- Back
{{/link-to}}
</p>
<div class="alert alert-error">
{{model}}
</div>
<div class="alert alert-info">
<div class="loading"></div>
Loading&hellip;
</div>
{{#link-to "index" tagName="button" class="btn btn-default btn-ghost"}}
&lt;-- Back
{{/link-to}}
<p>
Post <num>{{post.id}}</num> By {{post.author}} on
<time datetime={{isoDate}}>{{post.createdAt}}</time>
</p>
<p>{{post.title}}</p>
{
"version": "0.13.0",
"ENV": {
"ember-cli-mirage": {
"enabled": true
}
},
"EmberENV": {
"FEATURES": {}
},
"options": {
"use_pods": false,
"enable-testing": false
},
"dependencies": {
"hack_css": "https://cdnjs.cloudflare.com/ajax/libs/hack/0.8.0/hack.css",
"jquery": "https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.3/jquery.js",
"ember": "2.16.2",
"ember-template-compiler": "2.16.2",
"ember-testing": "2.16.2"
},
"addons": {
"ember-data": "2.16.3",
"ember-cli-mirage": "0.4.1"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment