Skip to content

Commit

Permalink
Try: Framework-agnostic block interoperability ("Vanilla", Vue)
Browse files Browse the repository at this point in the history
  • Loading branch information
aduth committed Aug 18, 2017
1 parent de51e24 commit 9a5326e
Show file tree
Hide file tree
Showing 8 changed files with 312 additions and 12 deletions.
17 changes: 12 additions & 5 deletions blocks/api/serializer.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import classnames from 'classnames';
/**
* WordPress dependencies
*/
import { Component, createElement, renderToString, cloneElement, Children } from '@wordpress/element';
import { Component, createElement, buildVTree, renderToString, cloneElement, Children } from '@wordpress/element';

/**
* Internal dependencies
Expand Down Expand Up @@ -41,14 +41,21 @@ export function getSaveContent( blockType, attributes ) {
if ( save.prototype instanceof Component ) {
rawContent = createElement( save, { attributes } );
} else {
rawContent = save( { attributes } );
const target = document.createElement( 'div' );
rawContent = save( { attributes, target } );

// Special-case function render implementation to allow raw HTML return
if ( 'string' === typeof rawContent ) {
return rawContent;
switch ( typeof rawContent ) {
// Special-case function render implementation for raw HTML return
case 'string':
return rawContent;

case 'undefined':
return target.innerHTML;
}
}

rawContent = buildVTree( rawContent );

// Adding a generic classname
const addClassnameToElement = ( element ) => {
if ( ! element || ! isObject( element ) || ! className ) {
Expand Down
2 changes: 2 additions & 0 deletions blocks/library/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,5 @@ import './text-columns';
import './verse';
import './video';
import './audio';
import './vanilla-banner';
import './vue-banner';
47 changes: 47 additions & 0 deletions blocks/library/vanilla-banner/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import { registerBlockType, source } from '../../api';

const { text } = source;

registerBlockType( 'core/vanilla-banner', {
title: __( 'Vanilla Banner' ),

icon: 'marker',

category: 'widgets',

attributes: {
text: {
type: 'string',
source: text( 'h1' ),
default: 'Hello World',
},
},

className: false,

edit( { attributes, setAttributes } ) {
return [ 'div',
[ 'input', {
value: attributes.text,
onChange( event ) {
setAttributes( {
text: event.target.value,
} );
},
} ],
[ 'h1', attributes.text ],
];
},

save( { attributes } ) {
return [ 'h1', attributes.text ];
},
} );
78 changes: 78 additions & 0 deletions blocks/library/vue-banner/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
* External dependencies
*/
import Vue from 'vue/dist/vue';

/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import { registerBlockType, source } from '../../api';

const { text } = source;

registerBlockType( 'core/vue-banner', {
title: __( 'Vue Banner' ),

icon: 'marker',

category: 'widgets',

attributes: {
text: {
type: 'string',
source: text( 'h1' ),
default: 'Hello World',
},
},

className: false,

edit( { attributes, setAttributes, target } ) {
if ( target.firstChild ) {
Object.assign( target.firstChild.__vue__, attributes );
return;
}

const child = document.createElement( 'div' );
target.appendChild( child );

new Vue( {
el: target.firstChild,

data: () => ( { ...attributes } ),

template: `
<div>
<input :value="text" @input="setText( $event.target.value )">
<h1>{{ text }}</h1>
</div>
`,

methods: {
setText( nextText ) {
setAttributes( { text: nextText } );
},
},
} );
},

save( { attributes, target } ) {
const child = document.createElement( 'div' );
target.appendChild( child );

new Vue( {
el: target.firstChild,

data: () => ( { ...attributes } ),

template: `
<h1>{{ text }}</h1>
`,
} );
},
} );
33 changes: 33 additions & 0 deletions editor/modes/visual-editor/block-render-context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* WordPress dependencies
*/
import { buildVTree, renderSubtreeIntoContainer, Component } from '@wordpress/element';

class BlockRenderContext extends Component {
componentDidMount() {
this.delegatedRender();
}

componentDidUpdate() {
this.delegatedRender();
}

delegatedRender() {
const { render, ...props } = this.props;
const result = render( { ...props, target: this.node } );

if ( result ) {
renderSubtreeIntoContainer(
this,
buildVTree( result ),
this.node
);
}
}

render() {
return <div ref={ ( node ) => this.node = node } />;
}
}

export default BlockRenderContext;
8 changes: 5 additions & 3 deletions editor/modes/visual-editor/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { __, sprintf } from '@wordpress/i18n';
import InvalidBlockWarning from './invalid-block-warning';
import BlockCrashWarning from './block-crash-warning';
import BlockCrashBoundary from './block-crash-boundary';
import BlockRenderContext from './block-render-context';
import BlockMover from '../../block-mover';
import BlockRightMenu from '../../block-settings-menu';
import BlockSwitcher from '../../block-switcher';
Expand Down Expand Up @@ -430,7 +431,8 @@ class VisualEditorBlock extends Component {
>
{ isValid && ! error && (
<BlockCrashBoundary onError={ this.onBlockError }>
<BlockEdit
<BlockRenderContext
render={ BlockEdit }
focus={ focus }
attributes={ block.attributes }
setAttributes={ this.setAttributes }
Expand All @@ -443,12 +445,12 @@ class VisualEditorBlock extends Component {
/>
</BlockCrashBoundary>
) }
{ ! isValid && (
{ /* TODO: Re-enable */ /* ! isValid && (
blockType.save( {
attributes: block.attributes,
className,
} )
) }
) */ }
</div>
{ !! error && <BlockCrashWarning /> }
{ ! isValid && <InvalidBlockWarning block={ block } /> }
Expand Down
136 changes: 133 additions & 3 deletions element/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,138 @@
/**
* External dependencies
*/
import { createElement, Component, cloneElement, Children } from 'react';
import { render, findDOMNode, unstable_createPortal } from 'react-dom'; // eslint-disable-line camelcase
import { createElement, Component, cloneElement, Children, isValidElement } from 'react';
import {
render,
findDOMNode,
unstable_createPortal, // eslint-disable-line camelcase
unstable_renderSubtreeIntoContainer, // eslint-disable-line camelcase
} from 'react-dom';
import { renderToStaticMarkup } from 'react-dom/server';
import { isString } from 'lodash';
import { isString, castArray } from 'lodash';

const interops = [
// {
// isHandled: ( element ) => (
// element.$$typeof === Symbol.for( 'react.element' )
// ),
// render( element, target ) {
// render( element, target );
// },
// },
// {
// isHandled: ( [ tagName ] ) => (
// tagName && tagName.prototype instanceof Component
// ),
// render( [ tagName, props, ...children ], target ) {
// render(
// createElement( tagName, props, children ),
// target
// );
// },
// },
// {
// isHandled: ( [ VueComponent ] ) => VueComponent.prototype instanceof Vue,
// render( [ VueComponent, props, ...children ], target ) {
// if ( ! target.firstChild ) {
// const child = document.createElement( 'div' );
// target.appendChild( child );
// }

// new Vue( {
// el: target.firstChild,

// functional: true,

// render( h ) {
// return h( VueComponent, { props }, children );
// },
// } );
// },
// },
];

class InteropRenderer extends Component {
componentDidMount() {
this.props.handler.render( this.props.element, this.node );
}

componentWillReceiveProps( nextProps ) {
nextProps.handler.render( nextProps.element, this.node );
}

shouldComponentUpdate() {
return false;
}

render() {
return createElement( 'div', { ref: ( node ) => this.node = node } );
}
}

export function buildVTree( element ) {
if ( null === element || undefined === element ) {
return null;
}

if ( isValidElement( element ) ) {
return element;
}

// Defer to interoperability handler
const handler = interops.find( ( interop ) => interop.isHandled( element ) );
if ( handler ) {
return createElement( InteropRenderer, {
handler,
element,
} );
}

const [ type ] = element;

// Handle React 16.x array render returns
const isNodeType = 'function' === typeof type || 'string' === typeof type;
if ( ! isNodeType ) {
return castArray( element ).map( buildVTree );
}

// Handle case where children starts at second argument
let [ , attributes, ...children ] = element;
if ( attributes && attributes.constructor !== Object ) {
children.unshift( attributes );
attributes = null;
}

children = castArray( children ).map( ( child ) => {
if ( 'boolean' === typeof child ) {
child = null;
}

if ( null === child || undefined === child ) {
child = '';
} else if ( 'number' === typeof child ) {
child = String( child );
}

if ( 'string' === typeof child ) {
return child;
}

return buildVTree( child );
} );

switch ( typeof type ) {
case 'string':
return createElement( type, attributes, ...children );

case 'function':
return buildVTree( type( { ...attributes, children } ) );
}
}

export function wpRender( element, target ) {
render( buildVTree( element ), null, target );
}

/**
* Returns a new element of given type. Type can be either a string tag name or
Expand Down Expand Up @@ -61,6 +189,8 @@ export { Children };
*/
export { unstable_createPortal as createPortal }; // eslint-disable-line camelcase

export { unstable_renderSubtreeIntoContainer as renderSubtreeIntoContainer }; // eslint-disable-line camelcase

/**
* Renders a given element into a string
*
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@
"refx": "^2.0.0",
"rememo": "^2.3.0",
"simple-html-tokenizer": "^0.4.1",
"uuid": "^3.0.1"
"uuid": "^3.0.1",
"vue": "^2.4.2"
},
"devDependencies": {
"autoprefixer": "^6.7.7",
Expand Down

0 comments on commit 9a5326e

Please sign in to comment.