Skip to content

Instantly share code, notes, and snippets.

@fragsalat
Forked from jdanyow/app.html
Last active August 21, 2018 10:22
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 fragsalat/0542bb772df1903cbec7f13fa18143b1 to your computer and use it in GitHub Desktop.
Save fragsalat/0542bb772df1903cbec7f13fa18143b1 to your computer and use it in GitHub Desktop.
Aurelia Gist
<template>
<require from="./children"></require>
<p>Below are groups which get's re-rendered every time you add a child or re-name one. You can see it because of the logged getter calls.</p>
<div repeat.for="group of groupedChildren">
<h3>${group.title}</h3>
<div repeat.for="child of group.children">
<children child.bind="child"></children>
</div>
</div>
<br/>
<br/>
<label>Add new child</label>
<input type="text" value.bind="newChildName" />
<button click.delegate="addChildren(newChildName)">Add</button>
</template>
import {computedFrom} from 'aurelia-framework';
import {Store} from './store';
import {ChildGroup} from './child-group';
export class App {
static inject = [Store]
/**
* Get object updates
* @param {Store} store
*/
constructor(store) {
this.store = store;
this.store.replaceState({
object: {
name: 'I\'m the only and real object',
children: [
{id: 1, name: 'Nami'},
{id: 2, name: 'Zoro'},
{id: 3, name: 'Luffy'},
{id: 4, name: 'Shopper'},
{id: 5, name: 'Sanji'},
{id: 6, name: 'Brook'}
]
}
});
store.subscribe(state => {
console.log('new state');
debugger;
this.object = state.clone().object;
})
}
/**
* Add a children with name
*/
addChildren(name) {
// Would normally be done by an action class
const state = this.store.getState().clone();
state.object.children.push({
id: state.object.children.length,
name
});
this.store.replaceState(state);
}
/**
* Group children based on their id
* @returns {Array}
*/
@computedFrom('object')
get groupedChildren() {
return ChildGroup.groupChildren(this.object.children);
}
}
export class ChildGroup {
/**
* @param {string} title
*/
constructor(title) {
this.title = title;
this.amount = 0;
this.children = [];
}
/**
* @param {Object} child
*/
addChild(child) {
this.children.push(child);
}
recalculateValues() {
this.amount = this.children.length + 0;
}
/**
* @param {Array<Object>} children
* @returns {Array<PositionGroup>}
*/
static groupChildren(children) {
const groups = children.reduce((groups, child) => {
const groupingKey = child.id % 2;
// Create new group
if (!groups.has(groupingKey)) {
groups.set(groupingKey, new ChildGroup('Group ' + groupingKey));
}
const group = groups.get(groupingKey);
group.addChild(child);
groups.set(groupingKey, group);
return groups;
}, new Map());
return Array.from(groups.values()).map(group => {
group.recalculateValues();
return group;
});
}
}
<template>
<input type="text" value="${childName}" change.delegate="changeName($event.target.value)" />
</template>
import {bindable, computedFrom, customElement} from 'aurelia-framework';
import {Store} from './store';
export class ChildrenCustomElement {
static inject = [Store];
@bindable child;
/**
* @param {Store} store
*/
constructor(store) {
this.store = store;
}
/**
* Change name of current child
*/
changeName(newName) {
// Would normally be done by an action class
const state = this.store.getState().clone();
state.object.children.find(child => child.id === this.child.id).name = newName;
this.store.replaceState(state);
}
/**
* Get child name an log for debugging
*/
@computedFrom('child.name')
get childName() {
console.log('Got name for ', this.child.id);
return this.child.name;
}
}
<!doctype html>
<html>
<head>
<title>Aurelia</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body aurelia-app>
<h1>Loading...</h1>
<script src="https://cdn.rawgit.com/jdanyow/aurelia-bundle/v1.0.3/jspm_packages/system.js"></script>
<script src="https://cdn.rawgit.com/jdanyow/aurelia-bundle/v1.0.3/config.js"></script>
<script>
System.import('aurelia-bootstrapper');
</script>
</body>
</html>
import {freeze} from './util';
export class Store {
/**
* Current application state
* @type {Object}
*/
_state:Object;
/**
* List of subscribers to notify on state change
* @type {Array}
*/
_callbacks:Array = [];
/**
* Initialize store
*/
constructor() {
this._state = freeze({});
}
/**
* Subscribe to state changes and get the initial state
* @param {Function} callback Will be initially called and when state changes. The state is passed as parameter.
*/
subscribe(callback: Function): void {
this._callbacks.push(callback);
// Call back with current state
callback(this.getState());
}
/**
* Replaces the current application state with a new one
* @param {State} newState
*/
replaceState(newState: Object): void {
const nextState = freeze(newState);
this._callbacks.forEach(callback =>
callback(nextState, this._state)
);
this._state = nextState;
}
/**
* Get a clone of the current state
* @returns {Object}
*/
getState(): Object {
return this._state;
}
/**
* Dispatch a action and update the application state
* @param {AbstractAction} action
*/
dispatch(action: AbstractAction): void {
this.replaceState(action.state);
}
}
/**
* Helper function to use splice to add an array entry or to just assign the property
* Splice is important because otherwise JS/Aurelia observers doesn't recognize changes
* @param {Object|Array} objOrArray
* @param {string} keyOrIndex
* @param {*} value
*/
function setOrSplice(objOrArray, keyOrIndex, value): void {
if (Array.isArray(objOrArray) && isFinite(keyOrIndex)) {
objOrArray.splice(keyOrIndex, 0, value);
} else {
objOrArray[keyOrIndex] = value;
}
}
/**
* Merge objects or arrays deep
* @param {Object|Array} destination
* @param {Array<Object|Array>} objects
* @returns {Object}
*/
export function deepMerge(destination: Object, ...objects: Array<Object>) {
// Iterate over objects object by object
for (let o = 0; o < objects.length; o++) {
// Iterate over object levels
const levels = [{target: destination, source: objects[o], path: []}];
for (let l = 0; l < levels.length; l++) {
// Iterate over level properties
const {target, source, path} = levels[l];
const keys = Object.keys(source);
for (let k = 0; k < keys.length; k++) {
const key = keys[k];
const value = source[key];
// If the value is an array concat with target
if (Array.isArray(value)) {
// Create new array (or array extending class) if not exist in target
if (Array.isArray(target[key])) {
target[key].splice(0, target[key].length);
} else {
setOrSplice(target, key, new value.constructor());
}
levels.push({target: target[key], source: value, path: path.concat([key])});
// If the value is an object add it as level
} else if (value && typeof value === 'object') {
// Ensure the target is an object we can merge into
if (typeof target[key] !== 'object') {
setOrSplice(target, key, new value.constructor());
}
levels.push({target: target[key], source: value, path: path.concat([key])});
} else {
setOrSplice(target, key, value);
}
}
}
}
return destination;
}
/**
* Freezes an object deep and prevent any modification
* @param {Object} obj
* @returns {Object}
*/
export function freeze(obj: Object): Object {
// Iterate over all nesting levels to freeze them
const levels = [obj];
for (let l = 0; l < levels.length; l++) {
const target = levels[l];
// Create clone function to create a unfrozen object
if (!Object.isFrozen(target)) {
Object.defineProperty(target, 'clone', {
enumerable: false,
writable: false,
value: function cloneFrozen() {
const clone = deepMerge({}, {target});
return clone.target;
}
});
Object.freeze(target);
}
const keys = Object.keys(target);
for (let k = 0; k < keys.length; k++) {
const key = keys[k];
if (target[key] && typeof target[key] === 'object') {
levels.push(target[key]);
}
}
}
return obj;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment