Skip to content

Commit

Permalink
feat(core): Zippers support @@girders-elements/children (#57, #51)
Browse files Browse the repository at this point in the history
An element can include a property  `@@girders-elements/children` which denotes *which of it's properties contain children elements*. 

This is an optional property, and can contain:
- a *string* denoting a single property name, (e.g. `'@@girders-elements/children': 'children'`) 
- an *array of strings* denoting which properties contain child elements

*A property containing child elements* can either:
- contain a single element 
- contain an array of elements 

E.g.

```javascript
{
   kind: ['container', 'two-column'],
  '@@girders-elements/children': ['main', 'aside'],

  main: [
    { 
       kind: 'tile',
       content: [ ],
    },
    // ...
  ],
  aside: {
    kind: 'weather',
    content: [],
  }
}
```

This feature enhances the `zip.elementZipper`
  • Loading branch information
ognen authored Aug 18, 2017
1 parent 0ab0ba2 commit 39f00e7
Show file tree
Hide file tree
Showing 14 changed files with 1,536 additions and 123 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
node_modules/
dist/
coverage/
packages/core/src/vendor
3 changes: 1 addition & 2 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@
"omniscient": "^4.1.1",
"ramda": "^0.22.1",
"redux-saga": "^0.12.0",
"uuid": "^3.0.1",
"zippa": "^0.3.1"
"uuid": "^3.0.1"
},
"peerDependencies": {
"react": ">= 15.0.0",
Expand Down
155 changes: 112 additions & 43 deletions packages/core/src/__tests__/elementZipper.js
Original file line number Diff line number Diff line change
@@ -1,113 +1,182 @@
'use strict';
'use strict'

import { fromJS } from 'immutable';
import { zip } from '..';
import { fromJS } from 'immutable'
import { zip, data } from '..'

describe('Zipper', () => {
const childCollectionKind = '@@girders-elements/child-collection'

describe('Zipper', () => {
const singleChild = {
kind: 'parent',
children: [
{
kind: 'lvl1',
children: [
{
kind: 'lvl2'
}
]
}
]
kind: 'lvl2',
},
],
},
],
}

it('zipper should correctly navigate up and down', () => {
const zipper = zip.elementZipper(fromJS(singleChild), 'children')
expect(zipper.value().get('kind')).toEqual('parent')
expect(zipper.down().value().size).toEqual(1) // the children collection level
expect(data.isOfKind(childCollectionKind, zipper.down().value())).toBe(true)
expect(zipper.down().down().value().get('kind')).toEqual('lvl1')
expect(zipper.down().down().down().value().size).toEqual(1) // the children collection level
expect(zipper.down().down().down().down().value().get('kind')).toEqual('lvl2')
expect(
data.isOfKind(childCollectionKind, zipper.down().down().down().value())
).toBe(true)
expect(zipper.down().down().down().down().value().get('kind')).toEqual(
'lvl2'
)
expect(zipper.down().down().down().down().down()).toBeNull()
expect(zipper.down().up().value().get('kind')).toEqual('parent')
expect(zipper.down().down().down().up().value().get('kind')).toEqual('lvl1')
});
})

const multipleChildren = {
id: 1,
kind: 't',
children: [
{
id: 2,
kind: 't',
children: [
{
id: 3
id: 3,
kind: 't',
},
{
id: 4
}
]
kind: 't',
id: 4,
},
],
},
{
id: 5,
kind: 't',
children: [
{
id: 6
kind: 't',
id: 6,
},
{
id: 7
}
]
kind: 't',
id: 7,
},
],
},
{
id: 8,
kind: 't',
children: [
{
id: 9
kind: 't',
id: 9,
},
{
id: 10
}
]
}
]
kind: 't',
id: 10,
},
],
},
],
}

it('zipper should correctly navigate up down left and right', () => {
const zipper = zip.elementZipper(fromJS(multipleChildren), 'children')

expect(zipper.value().get('id')).toEqual(1)
expect(zipper.down().value().size).toEqual(3) // children collection level
expect(data.isOfKind(childCollectionKind, zipper.down().value())).toBe(true)
expect(zipper.down().down().value().get('id')).toEqual(2)
expect(zipper.down().down().right().value().get('id')).toEqual(5)
expect(zipper.down().down().right().right().value().get('id')).toEqual(8)
expect(zipper.down().down().right().right().left().value().get('id')).toEqual(5)
expect(zipper.down().down().right().right().left().up().value().size).toEqual(3)
expect(zipper.down().down().right().right().left().up().up().value().get('id')).toEqual(1)
});
expect(
zipper.down().down().right().right().left().value().get('id')
).toEqual(5)
expect(
data.isOfKind(
childCollectionKind,
zipper.down().down().right().right().left().up().value()
)
).toBe(true)
expect(
zipper.down().down().right().right().left().up().up().value().get('id')
).toEqual(1)
})

const multipleChildrenElements = {
id: 1,
kind: 't',
left: [
{
id: 2
}
kind: 't',
id: 2,
},
],
right: [
{
id: 3
kind: 't',
id: 3,
},
{
id: 4
}
]
kind: 't',
id: 4,
},
],
}

it('zipper multiple children elements', () => {
const zipper = zip.elementZipper(fromJS(multipleChildrenElements), ['left', 'right'])
const zipper = zip.elementZipper(fromJS(multipleChildrenElements), [
'left',
'right',
])

expect(zipper.value().get('id')).toEqual(1)
expect(zipper.down().value().size).toEqual(1) // children collection node for left
expect(zipper.down().right().value().size).toEqual(2) // children collection node for right
expect(zipper.down().value().get('propertyName')).toEqual('left')

expect(zipper.down().right().value().get('propertyName')).toEqual('right')

expect(zipper.down().down().value().get('id')).toEqual(2)
expect(zipper.down().right().down().value().get('id')).toEqual(3)
expect(zipper.down().right().down().right().value().get('id')).toEqual(4)
})

const withChildrenPositions = {
id: 1,
kind: 'tX',
'@@girders-elements/children': ['left', 'right'],

left: [
{
kind: 't',
id: 2,
},
],
right: [
{
kind: 't',
id: 3,
},
{
kind: 't',
id: 4,
},
],
}

it('supports the @@girders-elements/children hint for child positions', () => {
const zipper = zip.elementZipper(fromJS(withChildrenPositions))

expect(zipper.value().get('id')).toEqual(1)
expect(zipper.down().value().get('propertyName')).toEqual('left')

expect(zipper.down().right().value().get('propertyName')).toEqual('right')

expect(zipper.down().down().value().get('id')).toEqual(2)
expect(zipper.down().right().down().value().get('id')).toEqual(3)
expect(zipper.down().right().down().right().value().get('id')).toEqual(4)
});
});
})
})
5 changes: 3 additions & 2 deletions packages/core/src/data/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
'use strict';

import * as element from './element';
import * as element from './element'
export * from './element'

export {
element
};
}
8 changes: 2 additions & 6 deletions packages/core/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import * as _ui from './ui';
import read from './read';
import * as _transform from './transform';
import * as data from './data';

import elementZipper from './zip/elementZipper'
import * as zip from './zip';

import update from './update';
import Engine from './engine/engine';


const ui = {
register: _ui.register,
reset: _ui.reset,
Expand All @@ -24,10 +24,6 @@ const transform = {
apply: _transform.apply
}

const zip = {
elementZipper
}

export {
ui,
read,
Expand Down
34 changes: 16 additions & 18 deletions packages/core/src/transform/transformRegistry.js
Original file line number Diff line number Diff line change
@@ -1,39 +1,36 @@
'use strict';
'use strict'

import invariant from 'invariant';
import invariant from 'invariant'

import Registry from '../common/MultivalueRegistry';
import { isElementRef } from '../data/element';
import Registry from '../common/MultivalueRegistry'
import { isElementRef } from '../data/element'

import { Iterable } from 'immutable';
import { Iterable } from 'immutable'

import R from 'ramda';
import R from 'ramda'

import { postWalk } from 'zippa'
import elementZipper from '../zip/elementZipper'

const transformerRegistry = new Registry();
import { postWalk, elementZipper } from '../zip'

const transformerRegistry = new Registry()

export function register(kind, transformer) {
invariant(
isElementRef(kind),
"You must provide a valid element reference to register");
'You must provide a valid element reference to register'
)
invariant(
transformer != null && typeof transformer === 'function',
"You must provide a transformer function"
);
'You must provide a transformer function'
)

transformerRegistry.register(kind, transformer);
transformerRegistry.register(kind, transformer)
}

export function get(kind) {
return transformerRegistry.get(kind)
}


function transform(element) {

// this is a safety check for the case when some of the children elements also matches a field with a scalar value
if (!Iterable.isIndexed(element) && !Iterable.isAssociative(element)) {
return element
Expand All @@ -47,7 +44,8 @@ function transform(element) {
return element
}

const transformFn = transformers.length === 1 ? transformers : R.pipe(...transformers)
const transformFn =
transformers.length === 1 ? transformers : R.pipe(...transformers)
return transformFn(element)
}

Expand All @@ -62,5 +60,5 @@ export function apply(element, childrenElements = 'content') {
}

export function reset() {
transformerRegistry.reset();
transformerRegistry.reset()
}
21 changes: 21 additions & 0 deletions packages/core/src/vendor/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2016 Tommi Kaikkonen

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
18 changes: 18 additions & 0 deletions packages/core/src/vendor/zippa/array_zipper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { makeZipper } from './zipper';

/**
* Zipper for nested Arrays.
*
* Don't use with new keyword - use the function plainly
* or with `ArrayZipper.from([1, 2, 3])`.
*
* @param {Array} arr - the data structure to make a zipper for
* @return {Zipper}
*/
export const ArrayZipper = makeZipper(
arr => !!arr.length,
arr => arr,
(_, children) => children,
);

export default ArrayZipper;
Loading

0 comments on commit 39f00e7

Please sign in to comment.