Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save rtablada/427e6fe6ff81c7991b61835f5f3faa50 to your computer and use it in GitHub Desktop.
Save rtablada/427e6fe6ff81c7991b61835f5f3faa50 to your computer and use it in GitHub Desktop.
ES6 wrapped in Ember
import Component from '@ember/component';
import { set } from '@ember/object';
import { action, computed } from '@ember-decorators/object';
import { layout, classNames } from '@ember-decorators/component';
import hbs from 'htmlbars-inline-precompile';
const WORDS_PER_SEC = 200 / 60; // Assumption: 200 words per minute
export class CountingTextareaManager {
constructor(str) {
this.isDetailShown = true;
this.updateStr(str);
}
@computed('str')
get wordCount() {
return this.str.split(/\s+/g).length - 1;
}
@computed('str')
get characterCount() {
return this.str.length;
}
@computed('str')
get sentenceCount() {
return this.str === '' ? 0 : this.str.split('. ').length;
}
@computed('wordCount')
get readingTime() {
const seconds = Math.round(this.wordCount / WORDS_PER_SEC);
const partialMinuteInSeconds = seconds % 60;
const fullMins = seconds - (partialMinuteInSeconds);
if (fullMins === 0) return `${partialMinuteInSeconds} seconds`;
return `${fullMins} minutes, ${partialMinuteInSeconds} seconds`;
}
updateStr(newStr) {
set(this, 'str', newStr);
}
}
const TEMPLATE = hbs`
<textarea
value={{manager.str}}
onInput={{action 'onStrUpdated' value='target.value'}} />
<p>
{{#if manager.isDetailShown}}
<button onClick={{action (mut manager.isDetailShown) false}}>Hide Details</button>
<div>
<div><b>Characters: </b> {{manager.charCount}} </div>
<div><b>Words: </b> {{manager.wordCount}} </div>
<div><b>Sentences: </b> {{manager.sentenceCount}} </div>
<div><b>Reading Time: </b> {{manager.readingTime}} </div>
</div>
{{else}}
<button onClick={{action (mut manager.isDetailShown) true}}>Show Details</button>
{{/if}}
</p>
<pre style='background: #eee; padding: 5px; white-space: pre-line'>{{manager.str}}</pre>`;
@layout(TEMPLATE)
@classNames('counting-textarea')
export default class CountingTextareaComponent extends Component {
init() {
super.init(...arguments);
this.manager = new CountingTextareaManager(this.text);
}
@action
onStrUpdated(newStr) {
this.manager.updateStr(newStr);
if (this.onChange) {
this.onChange(newStr);
}
}
}
import Ember from 'ember';
export default Ember.Controller.extend({
appName: 'an example of plain JS wrapped in a thin Ember layer',
message: `This is an example of how we can define most of a component's interesting behavior in an ES6 class and pure JS functions, and wrap it in a thin "Ember.Component" shell.
Why would we want to do this? The interesting part of our component is very easy to manage and unit test, as it's not coupled with Ember at all. Additionally, you can imagine that this pattern may be applied multiple times (i.e., a component has instances of many regular ES6 classes), allowing us to apply the single responsibility pattern (define simple objects and functions that are responsible for one thing only) instead of feeling like we must lump everything into Ember.Component subclasses
`
});
body {
margin: 12px 16px;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-size: 12pt;
}
.counting-textarea textarea {
width: 100%;
height: 200px;
}
<h1>Welcome to {{appName}}</h1>
<br>
<code>
From application.hbs's point of view, the string has {{message.length}} characters
</code>
<br>
<CountingTextarea
@text={{message}}
@onChange={{action (mut message)}} />
{{outlet}}
<br>
<br>
{
"version": "0.15.0",
"EmberENV": {
"FEATURES": {}
},
"options": {
"use_pods": false,
"enable-testing": false
},
"dependencies": {
"jquery": "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.js",
"ember": "3.2.2",
"ember-template-compiler": "3.2.2",
"ember-testing": "3.2.2"
},
"addons": {
"ember-data": "3.1.1",
"ember-decorators": "2.4.1",
"ember-angle-bracket-invocation-polyfill": "1.2.2"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment