Skip to content

Instantly share code, notes, and snippets.

@foxnewsnetwork
Last active May 17, 2018 20:42
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 foxnewsnetwork/d4cbfe4f8e688e5338669bbc0cbb6358 to your computer and use it in GitHub Desktop.
Save foxnewsnetwork/d4cbfe4f8e688e5338669bbc0cbb6358 to your computer and use it in GitHub Desktop.
Async Attrs Component Demo (with cache + error handling)
import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'canvas',
attributeBindings: ['width', 'height'],
width: 200,
height: 274,
canvasContext: Ember.computed('element', {
get() {
return this.element.getContext('2d');
}
}).readOnly()
});
import Ember from 'ember';
const DURATION = 1000;
const linear = (start, end, percent) => {
return start * (1 - percent) + end * percent;
};
class BadImageError extends Error {
constructor(image) {
super(`You gave me a bad image "${image}"`);
this.name = 'BadImageError';
}
};
const interpolateOpacity = (x) => {
return x;
}
export default Ember.Component.extend({
isFinished: Ember.computed.or('isDestroying', 'isAnimeDead'),
finishTime: Ember.computed('startTime', {
get() {
return this.get('startTime') + DURATION;
}
}).readOnly(),
isAnimating: Ember.computed('startTime', 'currentTime', 'finishTime', {
get() {
const start = this.get('startTime')
const now = this.get('currentTime')
const finish = this.get('finishTime')
return (start <= now) && (now <= finish);
}
}).readOnly(),
isAnimeDead: Ember.computed.not('isAnimating'),
animeLoop(tick) {
this.set('currentTime', tick);
this.drawFn(tick);
if (this.get('isFinished')) {
this.didFinishDrawing();
} else {
window.requestAnimationFrame((tick) => this.animeLoop(tick))
}
},
drawFn(currentTime) {
const startTime = this.get('startTime');
const finishTime = this.get('finishTime');
const percent = (currentTime - startTime) / (finishTime - startTime)
const opacity = percent
const image = this.get('image');
const canvas = this.get('canvas');
canvas.globalAlpha = opacity;
canvas.drawImage(image, 0, 0, image.width, image.height);
},
actions: {
start(image) {
this.set('image', image);
window.requestAnimationFrame((tick) => {
this.set('startTime', tick);
this.set('currentTime', tick);
this.animeLoop(tick)
})
}
}
});
{{call (action 'start') image}}
{{yield
(hash isAnimating=isAnimating)
}}
import Ember from 'ember';
export default Ember.Component.extend({
tagName: '',
value: Ember.computed('key', 'cache', 'hasKey', {
get() {
if (this.get('hasKey')) {
const cache = this.get('cache');
const key = this.get('key');
return cache.get(key);
}
}
}).readOnly(),
hasKey: Ember.computed('key', 'cache', {
get() {
const cache = this.get('cache');
const key = this.get('key');
return Ember.isPresent(cache) && Ember.isPresent(key) && cache.has(key);
}
}).readOnly(),
didInsertElement() {
this.set('cache', new Map());
},
willDestroyElement() {
this.get('cache').forEach((image) => {
image.src = null;
image.onload = null;
image.onerror = null;
});
this.set('cache', null);
},
actions: {
store(key, val) {
this.get('cache').set(key, val);
this.notifyPropertyChange('cache');
}
}
});
{{yield
(hash
hasKey=hasKey
image=value
)
(hash
store=(action 'store')
)
}}
import Ember from 'ember';
export default Ember.Component.extend({
tagName: '',
width: 200,
height: 274,
imageEl: Ember.computed('width', 'height', {
get() {
return new Image(this.get('width'), this.get('height'));
}
}).readOnly(),
didInsertElement() {
const image = this.get('imageEl');
image.onload = () => this.didLoadImage(image);
image.onerror = () =>
Ember.tryInvoke(this, 'loadingError', [this.get('src')])
},
actions: {
start(image, src) {
image.src = src;
}
}
});
{{#if src}}
{{call (action 'start') imageEl src}}
{{/if}}
import Ember from 'ember';
export default Ember.Component.extend({
tagName: '',
}).reopenClass({
positionalParams: ['canvas']
});
{{#canvas-image-cache key=src as |state actions|}}
{{#if state.hasKey}}
{{canvas-image-animator
canvas=canvas
image=state.image
didFinishDrawing=(action didDrawImage state.image)
}}
{{else}}
{{canvas-image-loader
width=width
height=height
src=src
loadingError=loadingError
didLoadImage=(action actions.store src)
}}
{{/if}}
{{/canvas-image-cache}}
import Ember from 'ember';
import { XBOX_MIXER_URLS } from '../fixtures';
export default Ember.Controller.extend({
appName: 'Async Component Attrs Demo',
XBOX_MIXER_URLS
});
import Ember from 'ember';
export default Ember.Component.extend({
after: 1000,
willDestroyElement() {
Ember.run.cancel(this.laterTimer);
},
actions: {
out(...args) {
Ember.run.cancel(this.laterTimer);
this.laterTimer = Ember.run.later(() => this.get('in')(...args), this.get('after'));
}
}
}).reopenClass({
positionalParams: ['in']
});
{{yield (action 'out')}}
import Ember from 'ember';
export default Ember.Component.extend({
tagName: '',
index: 0,
outdex: Ember.computed('items.length', 'index', {
get() {
return this.get('index') % this.get('items.length');
}
}).readOnly(),
actions: {
next() {
this.incrementProperty('index');
}
}
}).reopenClass({
positionalParams: ['items']
});
{{yield
(get items outdex)
(hash next=(action 'next'))
}}
export const XBOX_MIXER_URLS = [
'https://images-eds-ssl.xboxlive.com/image?url=8Oaj9Ryq1G1_p3lLnXlsaZgGzAie6Mnu24_PawYuDYIoH77pJ.X5Z.MqQPibUVTcureBBtsDmpX_Mtk9oGbcVP6wkXDk.al_QXiMXkmWwcsVsEJdYN8_Ewv4yW2Q2mX39MmA6cYmf9OZfUi.9LwT.hutpBHr0OJsynb.Oftm0GUD_oadzsIc.PgOwkDV31mkY1GHUFYz6gWgR6GkPQzGAESYdhMzyq78aFjSUzYhilY-&w=200&format=jpg',
'https://images-eds-ssl.xboxlive.com/image?url=8Oaj9Ryq1G1_p3lLnXlsaZgGzAie6Mnu24_PawYuDYIoH77pJ.X5Z.MqQPibUVTcTXsf4U1Veg7.CmjGt.BCDU13MTKnsc4lHPfWLJ7tAay_ButBFB6Waop39RK5SNdPFm0.kRWbSh9fSHmhGf3Q4EKDtbRezyAHToLgwAq4m1hjpUkobypqi4KcVdwjSYGAul_F_d2fE9gEBavfYLOPDQDal2ABRdxOxIGaVzSahwM-&w=200&format=jpg',
'https://images-eds-ssl.xboxlive.com/image?url=8Oaj9Ryq1G1_p3lLnXlsaZgGzAie6Mnu24_PawYuDYIoH77pJ.X5Z.MqQPibUVTcKd71eYDzF.YpJ.E5oVytLvDU9fUoOB7i3yhNBdYhlL8zGO43vc.WX9iqQvNtnM7fraah6gJkKW0fmbW_Ow7teCX7YJA35gtQf558cUON_qtWFL5.OOTqsigItbLzkZwP8BavmwmIGhEQnczE8vkDPmJwx9AuAK.707w_1KXYukA-&w=200&format=jpg',
'https://images-eds-ssl.xboxlive.com/image?url=8Oaj9Ryq1G1_p3lLnXlsaZgGzAie6Mnu24_PawYuDYIoH77pJ.X5Z.MqQPibUVTc_kqy0_kBG34KRbLGXrjyKMp8W8SEFBgMRZ8NZQZcjM8zUo0CCtkNf8F8A92W8spjmAK_E2PnvAp_4MJK5bM9sTgaq4zGxK.4omrzzbHBkunsxLuqz_U1kJ6hv_A9TBWRdUT9tpibOZt4uib1CsV96WDmzcOOIDttCTjnDXoWz3g-&w=200&format=jpg',
'https://images-eds-ssl.xboxlive.com/image?url=8Oaj9Ryq1G1_p3lLnXlsaZgGzAie6Mnu24_PawYuDYIoH77pJ.X5Z.MqQPibUVTcneHk..mNYPn1yeBZiagJ1MTcsofV_OzSl0Z3pwBoXPPYYZS.y2HGLgQhAiN.m8K_ndN2kv9JQDVEbZ99iy.RGUumZeLzzRzGiK.ZO0Rpxh_diq9nJ0y69UGF0PfUPkXoYoE.bsw0Yj3NKy0sEMpX0tfzc_TQi6JDl0w_PB8Xzn4-&w=200&format=jpg',
'https://images-eds-ssl.xboxlive.com/image?url=8Oaj9Ryq1G1_p3lLnXlsaZgGzAie6Mnu24_PawYuDYIoH77pJ.X5Z.MqQPibUVTc7DwWz_gkbIz5MbNyCXUG4teniBvkMsFLZiNfNZAtaDI0YHJmAVqzoTIcSOSyprIxf_VBseOZqxActrQu7XZjbagfO9fj4xnpr6_ZvLSJpwb_RJYf1ouKH3ZUvpoyK.KhjgXqt_JTvZiHE4pMT6eD5GK8zsb9exeU5.UwBQQ3J0w-&w=200&format=jpg',
'https://images-eds-ssl.xboxlive.com/image?url=8Oaj9Ryq1G1_p3lLnXlsaZgGzAie6Mnu24_PawYuDYIoH77pJ.X5Z.MqQPibUVTcxgs2ynGf2GOLUJwsreYTGpfZwbUb2ezDmhzUq7vVqeAqSkBQvgeBfAe.ivnkIeghhNY9wyBBIm5p5IZU5w6pvHCeheD7MzMXsZQ4q.xBkBRCyo2uoPhpr8S4Y5OdN.Q8sXVmaef.xXsrnJNHtL6vb8csfToaz0FHPe9J5JNqsHY-&w=200&format=jpg',
'https://images-eds-ssl.xboxlive.com/image?url=8Oaj9Ryq1G1_p3lLnXlsaZgGzAie6Mnu24_PawYuDYIoH77pJ.X5Z.MqQPibUVTcrPDJCIMaG9C19JqbmZiC0Wi5A26JzXIUBtIZ5I1Ixc_M_rYe9csE2rMpfsR6aOm1qMSHgoTBRPqpMwrjj_C_rztmRxGavwRN1aThHcJNY8G79.rSLujAvo2EdTCIyr53WrvR.L3xeo1txnig8b5FkcWAniy7Lclqkuxav4iFzfE-&w=200&format=jpg',
'https://images-eds-ssl.xboxlive.com/image?url=8Oaj9Ryq1G1_p3lLnXlsaZgGzAie6Mnu24_PawYuDYIoH77pJ.X5Z.MqQPibUVTcIQ94VyHoy9XfqHjvxqcadmq6PX7stS4T1mkLrQhGBdhdVxSeEKJ4CRpljoqotKn1uDFG8irSne96.z7offBqNV45pGfRBKvE5SnSWI24VQjgA0OT5KZutRmFzCuyeqY45uRMDVrJ9Vs0MFAULDs.trzyE7dox1ZS3IgGCgz0Q2E-&w=200&format=jpg'
]
import Ember from 'ember';
/**
* Executes an action, this is _exactly_ the same
* thing as `compute` from ember-composable-helpers
*/
export function call(params/*, hash*/) {
const [action, ...args] = params;
return action(...args);
}
export default Ember.Helper.helper(call);
<h1>Welcome to {{appName}}</h1>
<p>Finding yourself relying on <code>didReceiveAttrs</code> for asynchronous behavior in Ember? Check out this twiddle for a concurrency-safe component approach!</p>
{{#canvas-container as |canvas|}}
{{#each-one-by-one XBOX_MIXER_URLS as |url actions|}}
{{#debounce-action actions.next after=3000 as |next|}}
{{canvas-image canvas
src=url
didDrawImage=next
loadingError=actions.next
}}
{{/debounce-action}}
{{/each-one-by-one}}
{{/canvas-container}}
{
"version": "0.13.1",
"EmberENV": {
"FEATURES": {}
},
"options": {
"use_pods": true,
"enable-testing": false
},
"dependencies": {
"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"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment