Skip to content

Instantly share code, notes, and snippets.

@andybluntish
Last active January 18, 2018 20:49
Show Gist options
  • Save andybluntish/ed61f26922250a7313b473de8ed310b9 to your computer and use it in GitHub Desktop.
Save andybluntish/ed61f26922250a7313b473de8ed310b9 to your computer and use it in GitHub Desktop.
Expandable Table Rows
import Ember from 'ember'
const {
Component,
set,
} = Ember
export default Component.extend({
tagName: '',
current: null,
actions: {
select(current) {
set(this, 'current', current)
},
},
})
import Ember from 'ember'
const {
Component,
String: { htmlSafe },
computed,
get,
guidFor,
set,
} = Ember
export default Component.extend({
tagName: '',
// unique ID for this item within the group
key: null,
// the ID of the currently selected item in the group
current: null,
// select this item (de-selects other in the group)
select: null,
// the Component to render when this item is active
contentComponent: null,
active: computed('current', {
get() {
return get(this, 'current') === get(this, 'key')
}
}),
actions: {
open() {
get(this, 'select')(get(this, 'key'))
},
close() {
get(this, 'select')(null)
},
toggle() {
if (get(this, 'active')) {
this.send('close')
} else {
this.send('open')
}
},
},
})
import Ember from 'ember'
const {
Component,
computed: { alias },
get,
run,
set,
} = Ember
export default Component.extend({
tagName: 'tr',
classNames: ['user-editor'],
user: alias('data'),
years: [1, 2, 3, 4, 5, 6, 7],
passwordFieldType: 'password',
saving: false,
saveModel() {
const user = get(this, 'data')
set(this, 'saving', true)
this.get('save')(user).then((updatedUser) => {
run(() => {
if (!this.isDestroyed) set(this, 'saving', false)
})
}).catch((e) => {
console.error(`Error saving ${get(e, 'fullName')}`, e)
})
},
actions: {
showPassword(...args) {
set(this, 'passwordFieldType', 'text')
},
hidePassword(...args) {
set(this, 'passwordFieldType', 'password')
},
save() {
run.debounce(this, this.saveModel, 2000)
},
}
})
import Ember from 'ember'
const {
A,
Controller,
RSVP: { Promise },
computed,
computed: {
alias,
sort,
},
get,
getWithDefault,
getProperties,
set,
setProperties,
} = Ember
export default Controller.extend({
appName: 'Manage Students',
queryParams: {
sortCol: 'col',
sortDir: 'dir',
currentPage: 'page',
},
sortCol: null,
sortDir: null,
currentPage: 1,
recordsPerPage: 10,
dataSorting: computed('sortCol', 'sortDir', {
get() {
const col = get(this, 'sortCol') || 'id'
let dir = get(this, 'sortDir')
if (dir !== 'desc') {
dir = null
set(this, 'sortDir', dir)
}
return [A([col, dir]).compact().join(':')]
},
}),
sortedModels: sort('model', 'dataSorting'),
pagedSortedModels: computed('sortedModels', 'currentPage', 'recordsPerPage', {
get() {
const {
sortedModels,
currentPage,
recordsPerPage,
} = getProperties(this, 'sortedModels', 'currentPage', 'recordsPerPage')
const page = currentPage || 1
const start = (page * recordsPerPage) - recordsPerPage
const end = start + recordsPerPage
return sortedModels.slice(start, end)
}
}),
pageLinks: computed('model', 'recordsPerPage', {
get() {
const total = get(this, 'model.length')
const recordsPerPage = get(this, 'recordsPerPage')
const length = Math.ceil(total/recordsPerPage)
return Array.from({ length }).map((item, index) => index + 1)
},
}),
users: alias('pagedSortedModels'),
actions: {
sort(newColumn) {
const { sortCol, sortDir } = getProperties(this, 'sortCol', 'sortDir')
if (newColumn === sortCol && !sortDir) {
setProperties(this, { sortDir: 'desc' })
} else {
setProperties(this, {
sortCol: newColumn,
sortDir: null
})
}
},
saveUser(user) {
return new Promise((resolve) => {
console.log('-------------------------------')
console.log('Saving:', get(user, 'fullName'))
window.setTimeout(() => {
console.table(user.toJSON())
console.log('done')
console.log('-------------------------------')
resolve(user)
}, 5000)
})
},
},
})
import Ember from 'ember';
import Model from "ember-data/model";
import attr from "ember-data/attr";
import { belongsTo, hasMany } from "ember-data/relationships";
const {
A,
computed,
get,
} = Ember
export default Model.extend({
firstName: attr(),
lastName: attr(),
externalId: attr(),
login: attr(),
password: attr(),
year: attr('number'),
className: attr(),
reProgressReading: attr('number', { defaultValue: 0 }),
reProgressSpelling: attr('number', { defaultValue: 0 }),
reProgressClinker: attr('number', { defaultValue: 0 }),
rexProgressMyLessons: attr('number', { defaultValue: 0 }),
rexProgressEnglishSkillsSpelling: attr('number', { defaultValue: 0 }),
games: attr('boolean', { defaultValue: true }),
playroom: attr('boolean', { defaultValue: true }),
fullName: computed('firstName', 'lastName', {
get() {
return A([
get(this, 'firstName'),
get(this, 'lastName'),
]).compact().join(' ').trim()
}
}),
})
import Ember from 'ember'
import faker from 'faker'
const {
Route,
get,
} = Ember
export default Route.extend({
beforeModel() {
Array.from({ length: 103 }).map((item, index) => ({
id: index + 1000,
firstName: faker.name.firstName(),
lastName: faker.name.lastName(),
externalId: faker.random.number(),
login: faker.internet.userName(),
password: faker.internet.password(),
year: (faker.random.number(6) + 1),
className: `Class ${faker.random.number(2) + 1}`
})).map((user) => {
return get(this, 'store').createRecord('user', user)
})
},
model() {
return get(this, 'store').peekAll('user')
},
})
/**
* General
*/
body {
margin: 12px 16px;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-size: 12pt;
color: hsla(0, 0%, 0%, 0.75);
}
h1, h2, h3, h4, h5, h6, th {
color: hsla(0, 0%, 0%, 0.85);
}
h3, h4, h5, h6 {
font-weight: 500;
}
/**
* Expandable Table
*/
.expandable-table {
width: 100%;
border-collapse: collapse;
}
.expandable-table th,
.expandable-table td {
padding: 0.75rem 1.25rem;
border-bottom: 1px solid hsla(0, 0%, 0%, 0.1);
text-align: left;
}
.expandable-table th {
background: hsla(0, 0%, 0%, 0.05);
}
.expandable-table tr.active {
font-weight: 500;
background: #FBE4B2;
border-bottom: none;
}
.expandable-table tr.active + tr {
background: #FEF3D7;
}
/**
* User Editor
*/
.user-editor h3 {
margin: 0;
}
.user-editor [type="number"] {
width: 3em;
}
.user-editor .row {
display: flex;
}
.user-editor .col {
flex: 1;
}
.user-editor .col.edit-details {
flex: 2;
}
/**
* Pagination
*/
.pagination {
text-align: center;
}
.pagination a {
display: inline-block;
padding: 0.3rem 0.5rem;
text-decoration: none;
color: inherit;
}
.pagination a.active,
.pagination a:hover,
.pagination a.focus {
text-decoration: underline;
color: inherit;
}
<h1>{{appName}}</h1>
<table class="expandable-table">
<thead>
<tr>
<th {{action "sort" "id"}}>ID</th>
<th {{action "sort" "firstName"}}>First name</th>
<th {{action "sort" "lastName"}}>Last name</th>
<th {{action "sort" "login"}}>Username</th>
<th {{action "sort" "className"}}>Class</th>
<th {{action "sort" "year"}}>Year</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{{!--
Tagless wrapper for managing accordion items
- yields the accordion item component, with attributes/actions to control state already bound
--}}
{{#accordion-group as |accordion|}}
{{#each users as |user|}}
{{!--
Tagless wrapper for each accordion item
- the block is the content
- yields the active state of itself
- yields open, close, toggle actions that operate on itself
- renders the passed in contentComponent after the block content, if the items is active
--}}
{{#accordion.item key=user.id contentComponent=(component "user-editor-row" data=user columns=7 save=(action "saveUser")) as |item|}}
<tr class={{if item.active "active" ""}}>
<td>{{user.id}}</td>
<td>{{user.firstName}}</td>
<td>{{user.lastName}}</td>
<td>{{user.login}}</td>
<td>{{user.className}}</td>
<td>{{user.year}}</td>
<td>
<button {{action item.toggle}}>{{if item.active "Close" "Edit"}}</button>
</td>
</tr>
{{/accordion.item}}
{{/each}}
{{/accordion-group}}
</tbody>
</table>
<p class="pagination">
{{#each pageLinks as |page|}}
{{link-to page "index" (query-params page=page)}}
{{/each}}
</p>
{{yield (hash
item=(component "accordion-item" current=current select=(action "select"))
)}}
{{yield (hash
active=active
open=(action "open")
close=(action "close")
toggle=(action "toggle")
)}}
{{#if (and active contentComponent)}}
{{component contentComponent}}
{{/if}}
<td colspan={{columns}}>
<form class="edit-form">
<div class="row">
<div class="col edit-details">
<h3>Edit details</h3>
<div class="row">
<div class="col">
<p>
<label>
First name
{{input value=(mut user.firstName) change=(action "save")}}
</label>
</p>
<p>
<label>
Last name
{{input value=(mut user.lastName) change=(action "save")}}
</label>
</p>
<p>
<label>
External ID
{{input value=(mut user.externalId) change=(action "save")}}
</label>
</p>
<p>
<label>
Games
{{input type="checkbox" name="games" checked=(mut user.games) change=(action "save")}}
</label>
<label>
Playroom
{{input type="checkbox" name="playroom" checked=(mut user.playroom) change=(action "save")}}
</label>
</p>
</div>
<div class="col">
<h3></h3>
<p>
<label>
Login
{{input value=(mut user.login) change=(action "save")}}
</label>
</p>
<p>
<label>
Password
{{input
type=passwordFieldType
focus-in="showPassword"
focus-out="hidePassword"
change=(action "save")
value=(mut user.password)
}}
</label>
</p>
<p>
<label>
Year
<select onchange={{action (mut user.year) value="target.value"}} {{action "save" on="change"}}>
{{#each years as |year|}}
<option value={{year}} selected={{eq user.year year}}>{{year}}</option>
{{/each}}
</select>
</label>
</p>
</div>
</div>
</div>
<div class="col re-progress">
<h3>Reading Eggs Progress</h3>
<p>
<label>
Reading
{{input type="number" min=0 value=(mut user.reProgressReading) change=(action "save")}}
</label>
</p>
<p>
<label>
Spelling
{{input type="number" min=0 value=(mut user.reProgressSpelling) change=(action "save")}}
</label>
</p>
<p>
<label>
Clinker Castle
{{input type="number" min=0 value=(mut user.reProgressClinker) change=(action "save")}}
</label>
</p>
</div>
<div class="col rex-progress">
<h3>Reading Eggspress Progress</h3>
<p>
<label>
My Lessons
{{input type="number" min=0 value=(mut user.rexProgressMyLessons) change=(action "save")}}
</label>
</p>
<p>
<label>
English Skills Spelling
{{input type="number" min=0 value=(mut user.rexProgressEnglishSkillsSpelling) change=(action "save")}}
</label>
</p>
</div>
</div>
<div class="row">
<i>{{if saving "Saving..." "Saved"}}</i>
</div>
</form>
</td>
{
"version": "0.12.1",
"EmberENV": {
"FEATURES": {}
},
"options": {
"use_pods": false,
"enable-testing": false
},
"dependencies": {
"jquery": "https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.3/jquery.js",
"ember": "2.12.0",
"ember-template-compiler": "2.12.0",
"ember-testing": "2.12.0"
},
"addons": {
"ember-data": "2.12.1",
"ember-faker": "~1.2.1",
"ember-truth-helpers": "~2.0.0"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment