Skip to content

Instantly share code, notes, and snippets.

@mjgartendev
Created April 13, 2019 20:53
Show Gist options
  • Save mjgartendev/b7a68910e32f5b2639593439c0117cbb to your computer and use it in GitHub Desktop.
Save mjgartendev/b7a68910e32f5b2639593439c0117cbb to your computer and use it in GitHub Desktop.
JS Bin // source https://jsbin.com/dujatu
<!DOCTYPE html>
<html>
<head>
<script src="http://fb.me/react-with-addons-0.12.2.js"></script>
<meta charset="utf-8">
<title>JS Bin</title>
<script src="http://arqex.github.io/freezer/freezer.js"></script>
<style id="jsbin-css">
.docEditor > .objectAttr {
float: left;
}
.modified {
font-weight: bold;
}
.attrChildren {
margin-left: 20px;
display: none;
font-weight: normal;
border-left: 1px dotted #ddd;
}
.compoundAttr.open > .attrChildren {
display: block;
}
.hashToggle:before {
content: '▸';
color: #333;
line-height: 1em;
margin-right: 3px;
}
.open > .hashToggle:before {
content: '▾';
}
.attrName {
font-style: italic;
}
.attrValue {
margin-left: 5px;
}
.attrRemove {
visibility: hidden;
margin-right: 5px;
text-decoration: none;
}
.hashAttribute:hover > .attrRemove {
visibility: visible;
}
pre {
background: #eee;
border: 1px solid #ddd;
padding: 5px;
float: left;
width: 180px;
margin: 0 10px 0 0;
}
.hashToggle, .stringAttr{
cursor: pointer;
}
</style>
</head>
<body>
<script id="jsbin-javascript">
/****************
JSON data to edit
*****************/
var json = {
hola: 'amigo',
adios:'enemigo',
obj: { hi: 'man', bye: 'dude' },
arr: ['a', 'b', {c: 1}, 'd']
};
// Create a Freezer store
var frozen = new Freezer( { json: json });
/****************
Helper functions
*****************/
// Guess the type given a value to create the proper attribute
var guessType = function( value ){
var type = typeof value;
if( type != 'object' )
return type;
if( value instanceof Array )
return 'array';
if( value instanceof Date)
return 'date';
return 'object';
};
// Default values to initialize attributes
var typeDefaultValues = {
string: '',
object: {},
array: []
}
/**
* Creates an specific attribute component depending on
* the value given
* @param {Mixed} value The value for the attribute
* @param {Mixed} original The value of the attribute on the original json
* @param {FreezerNode} parent The parent node is needed to let the attribute update
* @param {String} key The key for the attribute.
* @return {ReactComponent} A react component to edit the attribute.
*/
var createAttribute = function( value, original, parent, key ){
var type = guessType( value );
className = StringAttribute
;
if( type == 'object' )
className = ObjectAttribute;
else if( type == 'array' )
className = ArrayAttribute;
if( typeof original == 'undefined' )
original = typeDefaultValues[ type ];
return React.createElement( className, {
value: value,
attrkey: typeof key != 'undefined' ? key : '',
parent: parent,
original: original
});
};
/****************
JSX components
*****************/
/**
* The main component. It will refresh the props when the store changes.
*
* @param {FreezerNode} store Freezer node that contains a json property with the data
* @param {FreezerNode} original Freezer node to compare with the current data
*/
var DocEditor = React.createClass({displayName: 'DocEditor',
render: function(){
var store = this.props.store;
return (
React.createElement("div", {className: "docEditor"},
React.createElement("pre", null, JSON.stringify( this.props.store.json, null, ' ')),
React.createElement(ObjectAttribute, {value: this.props.store.json, original: this.props.original.json})
)
);
},
componentDidMount: function(){
var me = this,
// Let's create a listener to update the store on change
listener = this.props.store.getListener()
;
// We are going to update the props every time the store changes
listener.on('update', function( updated ){
me.setProps({ store: updated });
});
}
});
/**
* Attribute component that represent each Array element or Object property.
* @param {string} attrkey The key of the attribute in the parent.
* @param {Mixed} value The value of the attribute.
* @param {Mixed} original The value of the attibute in the original json to highlight the changes.
* @param {FreezerNode} parent The parent node to notify attribute updates.
*/
var Attribute = React.createClass({displayName: 'Attribute',
render: function(){
var typeAttribute = createAttribute( this.props.value, this.props.original, this.props.parent, this.props.attrkey ),
modifiedClass = this.props.value == this.props.original ? '' : ' modified',
className = 'hashAttribute' + modifiedClass
;
return (
React.createElement("div", {className: className},
React.createElement("a", {href: "#", className: "attrRemove", onClick: this.handleRemove}, "[x]"),
React.createElement("span", {className: "attrName"}, this.props.attrkey, ":"),
React.createElement("span", {className: "attrValue"}, typeAttribute )
)
);
},
handleRemove: function( e ){
e.preventDefault();
if( this.props.parent.constructor == Array )
this.props.parent.splice( this.props.attrkey, 1 );
else
this.props.parent.remove( this.props.attrkey );
},
shouldComponentUpdate: function( nextProps, nextState ){
return nextProps.value != this.props.value ||
nextProps.parent != this.props.parent
;
}
});
/**
* Component for editing a hash.
* @param {FreezerNode} value The value of the object.
* @param {Mixed} original The value of the component it the original json.
*/
var ObjectAttribute = React.createClass({displayName: 'ObjectAttribute',
getInitialState: function(){
return { editing: false };
},
render: function(){
var keys = Object.keys( this.props.value ),
className = this.state.editing ? 'open objectAttr compoundAttr' : 'objectAttr compoundAttr',
openHash = ''
;
var attrs = [];
for( var attr in this.props.value ){
attrs.push(
React.createElement(Attribute, {
parent: this.props.value,
value: this.props.value[attr],
original: this.props.original[attr],
key: attr,
attrkey: attr }
)
);
}
openHash = (React.createElement("div", {className: "attrChildren"},
attrs,
React.createElement(AttributeCreator, {type: "attribute", parent: this.props.value})
));
return (React.createElement("span", {className: className },
React.createElement("span", {onClick: this.toggleEditing, className: "hashToggle"}, "Map [", keys.length, "]"),
openHash
))
;
},
toggleEditing: function(){
this.setState({ editing: !this.state.editing });
}
});
/**
* Component for editing an array.
* @param {FreezerNode} value The value of the array.
* @param {Mixed} original The value of the component it the original json.
*/
var ArrayAttribute = React.createClass({displayName: 'ArrayAttribute',
getInitialState: function(){
return { editing: false };
},
render: function(){
var keys = Object.keys( this.props.value ),
className = this.state.editing ? 'open arrayAttr compoundAttr' : 'arrayAttr compoundAttr',
openArray = ''
;
var attrs = [];
for (var i = 0; i < this.props.value.length; i++) {
attrs.push(
React.createElement(Attribute, {
parent: this.props.value,
value: this.props.value[i],
original: this.props.original[i],
key: i,
attrkey: i }
)
);
}
openArray = (React.createElement("div", {className: "attrChildren"},
attrs,
React.createElement(AttributeCreator, {type: "element", parent: this.props.value, attrkey: keys.length})
)
);
return (React.createElement("span", {className: className },
React.createElement("span", {onClick: this.toggleEditing, className: "hashToggle"}, "List [", keys.length, "]"),
openArray
))
;
},
toggleEditing: function(){
this.setState({editing: !this.state.editing});
}
});
/**
* Component for editing a string.
* @param {string} value The value of the string.
* @param {Mixed} original The value of the component it the original json.
* @param {FreezerNode} parent The parent node to let the string component update its value.
*/
var StringAttribute = React.createClass({displayName: 'StringAttribute',
getInitialState: function(){
return {
editing: !this.props.value,
value: this.props.value,
modified: false
};
},
render: function(){
var className = 'stringAttr';
if( this.state.modified )
className = ' modified';
if( !this.state.editing )
return React.createElement("span", {onClick: this.setEditMode, className: className }, this.props.value);
return React.createElement("input", {value: this.state.value, onChange: this.updateValue, onBlur: this.setValue, ref: "input", onKeyDown: this.handleKeyDown});
},
componentDidUpdate: function( prevProps, prevState ){
if( this.state.editing && ! prevState.editing ){
var node = this.refs.input.getDOMNode();
node.focus();
node.value = node.value;
}
},
componentDidMount: function(){
if( this.state.editing ){
var node = this.refs.input.getDOMNode();
node.focus();
node.value = node.value;
}
},
setEditMode: function(){
this.setState({editing: true});
},
setValue: function(){
if( this.state.modified )
this.props.parent.set( this.props.attrkey, this.state.value );
this.setState({editing: false});
},
updateValue: function( e ){
this.setState({value: e.target.value, modified: e.target.value != this.props.value });
},
handleKeyDown: function( e ){
if( e.which == 13 )
this.setValue();
},
toggleEditing: function(){
this.setState({ editing: !this.state.editing });
}
});
/**
* Component to add attributes to a Hash or Array.
* @param {FreezerNode} root The parent to add the attribute.
* @param {string} attrkey Optional. If provided, the attribute added will have that key (arrays).
* Otherwise an input will be shown to let the user define the key.
*/
var AttributeCreator = React.createClass({displayName: 'AttributeCreator',
getInitialState: function(){
return {
creating: false,
attrkey: this.props.attrkey,
type: 'string'
};
},
render: function(){
if( !this.state.creating )
return React.createElement("a", {href: "#", onClick: this.handleCreate}, "+ Add ", this.props.type);
var attrName;
if( typeof this.props.attrkey != 'undefined' )
attrName = React.createElement("span", {className: "attrName"}, this.props.attrkey, ":");
else {
attrName = [
React.createElement("input", {ref: "keyInput", type: "text", value: this.state.value, onChange: this.changeKey}),
React.createElement("span", null, ":")
];
}
return (React.createElement("div", {className: "hashAttribute"},
attrName,
React.createElement("select", {value: this.state.type, onChange: this.changeType, ref: "typeSelector"},
React.createElement("option", {value: "string"}, "String"),
React.createElement("option", {value: "array"}, "List"),
React.createElement("option", {value: "object"}, "Map")
),
React.createElement("button", {onClick: this.createAttribute}, "OK"), ",",
React.createElement("a", {href: "#", className: "cancelAttr", onClick: this.handleCancel}, "Cancel")
));
},
componentDidUpdate: function( prevProps, prevState){
if( !prevState.creating && this.state.creating ){
if( this.refs.keyInput )
this.refs.keyInput.getDOMNode().focus();
else
this.refs.typeSelector.getDOMNode().focus();
}
},
componentWillReceiveProps: function( newProps ){
this.setState({attrkey: newProps.attrkey});
},
handleCreate: function( e ){
e.preventDefault();
this.setState({creating: true});
},
handleCancel: function( e ){
e.preventDefault();
this.setState({creating: false});
},
changeType: function( e ){
this.setState({type: e.target.value});
},
changeKey: function( e ){
this.setState({attrkey: e.target.value});
},
createAttribute: function(){
this.setState({creating: false});
var parent = this.props.parent,
value = typeDefaultValues[ this.state.type ]
;
if( parent.constructor == Array )
parent.push( value )
else
parent.set(this.state.attrkey, value );
}
});
/****************
Start the UI
*****************/
React.render(React.createElement(DocEditor, {store: frozen.get(), original: frozen.get() }), document.body);
</script>
<script id="jsbin-source-html" type="text/html"><!DOCTYPE html>
<html>
<head>
<script src="//fb.me/react-with-addons-0.12.2.js"><\/script>
<meta charset="utf-8">
<title>JS Bin</title>
<script src="http://arqex.github.io/freezer/freezer.js"><\/script>
</head>
<body>
</body>
</html></script>
<script id="jsbin-source-css" type="text/css">.docEditor > .objectAttr {
float: left;
}
.modified {
font-weight: bold;
}
.attrChildren {
margin-left: 20px;
display: none;
font-weight: normal;
border-left: 1px dotted #ddd;
}
.compoundAttr.open > .attrChildren {
display: block;
}
.hashToggle:before {
content: '▸';
color: #333;
line-height: 1em;
margin-right: 3px;
}
.open > .hashToggle:before {
content: '▾';
}
.attrName {
font-style: italic;
}
.attrValue {
margin-left: 5px;
}
.attrRemove {
visibility: hidden;
margin-right: 5px;
text-decoration: none;
}
.hashAttribute:hover > .attrRemove {
visibility: visible;
}
pre {
background: #eee;
border: 1px solid #ddd;
padding: 5px;
float: left;
width: 180px;
margin: 0 10px 0 0;
}
.hashToggle, .stringAttr{
cursor: pointer;
}</script>
<script id="jsbin-source-javascript" type="text/javascript">/****************
JSON data to edit
*****************/
var json = {
hola: 'amigo',
adios:'enemigo',
obj: { hi: 'man', bye: 'dude' },
arr: ['a', 'b', {c: 1}, 'd']
};
// Create a Freezer store
var frozen = new Freezer( { json: json });
/****************
Helper functions
*****************/
// Guess the type given a value to create the proper attribute
var guessType = function( value ){
var type = typeof value;
if( type != 'object' )
return type;
if( value instanceof Array )
return 'array';
if( value instanceof Date)
return 'date';
return 'object';
};
// Default values to initialize attributes
var typeDefaultValues = {
string: '',
object: {},
array: []
}
/**
* Creates an specific attribute component depending on
* the value given
* @param {Mixed} value The value for the attribute
* @param {Mixed} original The value of the attribute on the original json
* @param {FreezerNode} parent The parent node is needed to let the attribute update
* @param {String} key The key for the attribute.
* @return {ReactComponent} A react component to edit the attribute.
*/
var createAttribute = function( value, original, parent, key ){
var type = guessType( value );
className = StringAttribute
;
if( type == 'object' )
className = ObjectAttribute;
else if( type == 'array' )
className = ArrayAttribute;
if( typeof original == 'undefined' )
original = typeDefaultValues[ type ];
return React.createElement( className, {
value: value,
attrkey: typeof key != 'undefined' ? key : '',
parent: parent,
original: original
});
};
/****************
JSX components
*****************/
/**
* The main component. It will refresh the props when the store changes.
*
* @param {FreezerNode} store Freezer node that contains a json property with the data
* @param {FreezerNode} original Freezer node to compare with the current data
*/
var DocEditor = React.createClass({
render: function(){
var store = this.props.store;
return (
<div className="docEditor">
<pre>{ JSON.stringify( this.props.store.json, null, ' ')}</pre>
<ObjectAttribute value={ this.props.store.json } original={ this.props.original.json }/>
</div>
);
},
componentDidMount: function(){
var me = this,
// Let's create a listener to update the store on change
listener = this.props.store.getListener()
;
// We are going to update the props every time the store changes
listener.on('update', function( updated ){
me.setProps({ store: updated });
});
}
});
/**
* Attribute component that represent each Array element or Object property.
* @param {string} attrkey The key of the attribute in the parent.
* @param {Mixed} value The value of the attribute.
* @param {Mixed} original The value of the attibute in the original json to highlight the changes.
* @param {FreezerNode} parent The parent node to notify attribute updates.
*/
var Attribute = React.createClass({
render: function(){
var typeAttribute = createAttribute( this.props.value, this.props.original, this.props.parent, this.props.attrkey ),
modifiedClass = this.props.value == this.props.original ? '' : ' modified',
className = 'hashAttribute' + modifiedClass
;
return (
<div className={className}>
<a href="#" className="attrRemove" onClick={ this.handleRemove }>[x]</a>
<span className="attrName">{this.props.attrkey }:</span>
<span className="attrValue">{ typeAttribute }</span>
</div>
);
},
handleRemove: function( e ){
e.preventDefault();
if( this.props.parent.constructor == Array )
this.props.parent.splice( this.props.attrkey, 1 );
else
this.props.parent.remove( this.props.attrkey );
},
shouldComponentUpdate: function( nextProps, nextState ){
return nextProps.value != this.props.value ||
nextProps.parent != this.props.parent
;
}
});
/**
* Component for editing a hash.
* @param {FreezerNode} value The value of the object.
* @param {Mixed} original The value of the component it the original json.
*/
var ObjectAttribute = React.createClass({
getInitialState: function(){
return { editing: false };
},
render: function(){
var keys = Object.keys( this.props.value ),
className = this.state.editing ? 'open objectAttr compoundAttr' : 'objectAttr compoundAttr',
openHash = ''
;
var attrs = [];
for( var attr in this.props.value ){
attrs.push(
<Attribute
parent={ this.props.value }
value={this.props.value[attr]}
original={this.props.original[attr]}
key={ attr }
attrkey={ attr }
/>
);
}
openHash = (<div className="attrChildren">
{ attrs }
<AttributeCreator type="attribute" parent={ this.props.value } />
</div>);
return (<span className={ className }>
<span onClick={ this.toggleEditing } className="hashToggle">Map [{ keys.length }]</span>
{openHash}
</span>)
;
},
toggleEditing: function(){
this.setState({ editing: !this.state.editing });
}
});
/**
* Component for editing an array.
* @param {FreezerNode} value The value of the array.
* @param {Mixed} original The value of the component it the original json.
*/
var ArrayAttribute = React.createClass({
getInitialState: function(){
return { editing: false };
},
render: function(){
var keys = Object.keys( this.props.value ),
className = this.state.editing ? 'open arrayAttr compoundAttr' : 'arrayAttr compoundAttr',
openArray = ''
;
var attrs = [];
for (var i = 0; i < this.props.value.length; i++) {
attrs.push(
<Attribute
parent={ this.props.value }
value={this.props.value[i]}
original={this.props.original[i]}
key={ i }
attrkey={ i }
/>
);
}
openArray = (<div className="attrChildren">
{ attrs }
<AttributeCreator type="element" parent={ this.props.value } attrkey={ keys.length }/>
</div>
);
return (<span className={ className }>
<span onClick={this.toggleEditing} className="hashToggle">List [{keys.length}]</span>
{openArray}
</span>)
;
},
toggleEditing: function(){
this.setState({editing: !this.state.editing});
}
});
/**
* Component for editing a string.
* @param {string} value The value of the string.
* @param {Mixed} original The value of the component it the original json.
* @param {FreezerNode} parent The parent node to let the string component update its value.
*/
var StringAttribute = React.createClass({
getInitialState: function(){
return {
editing: !this.props.value,
value: this.props.value,
modified: false
};
},
render: function(){
var className = 'stringAttr';
if( this.state.modified )
className = ' modified';
if( !this.state.editing )
return <span onClick={ this.setEditMode } className={ className }>{ this.props.value }</span>;
return <input value={ this.state.value } onChange={ this.updateValue } onBlur={ this.setValue } ref="input" onKeyDown={this.handleKeyDown} />;
},
componentDidUpdate: function( prevProps, prevState ){
if( this.state.editing && ! prevState.editing ){
var node = this.refs.input.getDOMNode();
node.focus();
node.value = node.value;
}
},
componentDidMount: function(){
if( this.state.editing ){
var node = this.refs.input.getDOMNode();
node.focus();
node.value = node.value;
}
},
setEditMode: function(){
this.setState({editing: true});
},
setValue: function(){
if( this.state.modified )
this.props.parent.set( this.props.attrkey, this.state.value );
this.setState({editing: false});
},
updateValue: function( e ){
this.setState({value: e.target.value, modified: e.target.value != this.props.value });
},
handleKeyDown: function( e ){
if( e.which == 13 )
this.setValue();
},
toggleEditing: function(){
this.setState({ editing: !this.state.editing });
}
});
/**
* Component to add attributes to a Hash or Array.
* @param {FreezerNode} root The parent to add the attribute.
* @param {string} attrkey Optional. If provided, the attribute added will have that key (arrays).
* Otherwise an input will be shown to let the user define the key.
*/
var AttributeCreator = React.createClass({
getInitialState: function(){
return {
creating: false,
attrkey: this.props.attrkey,
type: 'string'
};
},
render: function(){
if( !this.state.creating )
return <a href="#" onClick={this.handleCreate}>+ Add {this.props.type}</a>;
var attrName;
if( typeof this.props.attrkey != 'undefined' )
attrName = <span className="attrName">{this.props.attrkey}:</span>;
else {
attrName = [
<input ref="keyInput" type="text" value={this.state.value} onChange={this.changeKey}/>,
<span>:</span>
];
}
return (<div className="hashAttribute">
{ attrName }
<select value={this.state.type} onChange={ this.changeType } ref="typeSelector">
<option value="string">String</option>
<option value="array">List</option>
<option value="object">Map</option>
</select>
<button onClick={ this.createAttribute }>OK</button>,
<a href="#" className="cancelAttr" onClick={ this.handleCancel }>Cancel</a>
</div>);
},
componentDidUpdate: function( prevProps, prevState){
if( !prevState.creating && this.state.creating ){
if( this.refs.keyInput )
this.refs.keyInput.getDOMNode().focus();
else
this.refs.typeSelector.getDOMNode().focus();
}
},
componentWillReceiveProps: function( newProps ){
this.setState({attrkey: newProps.attrkey});
},
handleCreate: function( e ){
e.preventDefault();
this.setState({creating: true});
},
handleCancel: function( e ){
e.preventDefault();
this.setState({creating: false});
},
changeType: function( e ){
this.setState({type: e.target.value});
},
changeKey: function( e ){
this.setState({attrkey: e.target.value});
},
createAttribute: function(){
this.setState({creating: false});
var parent = this.props.parent,
value = typeDefaultValues[ this.state.type ]
;
if( parent.constructor == Array )
parent.push( value )
else
parent.set(this.state.attrkey, value );
}
});
/****************
Start the UI
*****************/
React.render(<DocEditor store={ frozen.get() } original={ frozen.get() } />, document.body);</script></body>
</html>
.docEditor > .objectAttr {
float: left;
}
.modified {
font-weight: bold;
}
.attrChildren {
margin-left: 20px;
display: none;
font-weight: normal;
border-left: 1px dotted #ddd;
}
.compoundAttr.open > .attrChildren {
display: block;
}
.hashToggle:before {
content: '▸';
color: #333;
line-height: 1em;
margin-right: 3px;
}
.open > .hashToggle:before {
content: '▾';
}
.attrName {
font-style: italic;
}
.attrValue {
margin-left: 5px;
}
.attrRemove {
visibility: hidden;
margin-right: 5px;
text-decoration: none;
}
.hashAttribute:hover > .attrRemove {
visibility: visible;
}
pre {
background: #eee;
border: 1px solid #ddd;
padding: 5px;
float: left;
width: 180px;
margin: 0 10px 0 0;
}
.hashToggle, .stringAttr{
cursor: pointer;
}
/****************
JSON data to edit
*****************/
var json = {
hola: 'amigo',
adios:'enemigo',
obj: { hi: 'man', bye: 'dude' },
arr: ['a', 'b', {c: 1}, 'd']
};
// Create a Freezer store
var frozen = new Freezer( { json: json });
/****************
Helper functions
*****************/
// Guess the type given a value to create the proper attribute
var guessType = function( value ){
var type = typeof value;
if( type != 'object' )
return type;
if( value instanceof Array )
return 'array';
if( value instanceof Date)
return 'date';
return 'object';
};
// Default values to initialize attributes
var typeDefaultValues = {
string: '',
object: {},
array: []
}
/**
* Creates an specific attribute component depending on
* the value given
* @param {Mixed} value The value for the attribute
* @param {Mixed} original The value of the attribute on the original json
* @param {FreezerNode} parent The parent node is needed to let the attribute update
* @param {String} key The key for the attribute.
* @return {ReactComponent} A react component to edit the attribute.
*/
var createAttribute = function( value, original, parent, key ){
var type = guessType( value );
className = StringAttribute
;
if( type == 'object' )
className = ObjectAttribute;
else if( type == 'array' )
className = ArrayAttribute;
if( typeof original == 'undefined' )
original = typeDefaultValues[ type ];
return React.createElement( className, {
value: value,
attrkey: typeof key != 'undefined' ? key : '',
parent: parent,
original: original
});
};
/****************
JSX components
*****************/
/**
* The main component. It will refresh the props when the store changes.
*
* @param {FreezerNode} store Freezer node that contains a json property with the data
* @param {FreezerNode} original Freezer node to compare with the current data
*/
var DocEditor = React.createClass({displayName: 'DocEditor',
render: function(){
var store = this.props.store;
return (
React.createElement("div", {className: "docEditor"},
React.createElement("pre", null, JSON.stringify( this.props.store.json, null, ' ')),
React.createElement(ObjectAttribute, {value: this.props.store.json, original: this.props.original.json})
)
);
},
componentDidMount: function(){
var me = this,
// Let's create a listener to update the store on change
listener = this.props.store.getListener()
;
// We are going to update the props every time the store changes
listener.on('update', function( updated ){
me.setProps({ store: updated });
});
}
});
/**
* Attribute component that represent each Array element or Object property.
* @param {string} attrkey The key of the attribute in the parent.
* @param {Mixed} value The value of the attribute.
* @param {Mixed} original The value of the attibute in the original json to highlight the changes.
* @param {FreezerNode} parent The parent node to notify attribute updates.
*/
var Attribute = React.createClass({displayName: 'Attribute',
render: function(){
var typeAttribute = createAttribute( this.props.value, this.props.original, this.props.parent, this.props.attrkey ),
modifiedClass = this.props.value == this.props.original ? '' : ' modified',
className = 'hashAttribute' + modifiedClass
;
return (
React.createElement("div", {className: className},
React.createElement("a", {href: "#", className: "attrRemove", onClick: this.handleRemove}, "[x]"),
React.createElement("span", {className: "attrName"}, this.props.attrkey, ":"),
React.createElement("span", {className: "attrValue"}, typeAttribute )
)
);
},
handleRemove: function( e ){
e.preventDefault();
if( this.props.parent.constructor == Array )
this.props.parent.splice( this.props.attrkey, 1 );
else
this.props.parent.remove( this.props.attrkey );
},
shouldComponentUpdate: function( nextProps, nextState ){
return nextProps.value != this.props.value ||
nextProps.parent != this.props.parent
;
}
});
/**
* Component for editing a hash.
* @param {FreezerNode} value The value of the object.
* @param {Mixed} original The value of the component it the original json.
*/
var ObjectAttribute = React.createClass({displayName: 'ObjectAttribute',
getInitialState: function(){
return { editing: false };
},
render: function(){
var keys = Object.keys( this.props.value ),
className = this.state.editing ? 'open objectAttr compoundAttr' : 'objectAttr compoundAttr',
openHash = ''
;
var attrs = [];
for( var attr in this.props.value ){
attrs.push(
React.createElement(Attribute, {
parent: this.props.value,
value: this.props.value[attr],
original: this.props.original[attr],
key: attr,
attrkey: attr }
)
);
}
openHash = (React.createElement("div", {className: "attrChildren"},
attrs,
React.createElement(AttributeCreator, {type: "attribute", parent: this.props.value})
));
return (React.createElement("span", {className: className },
React.createElement("span", {onClick: this.toggleEditing, className: "hashToggle"}, "Map [", keys.length, "]"),
openHash
))
;
},
toggleEditing: function(){
this.setState({ editing: !this.state.editing });
}
});
/**
* Component for editing an array.
* @param {FreezerNode} value The value of the array.
* @param {Mixed} original The value of the component it the original json.
*/
var ArrayAttribute = React.createClass({displayName: 'ArrayAttribute',
getInitialState: function(){
return { editing: false };
},
render: function(){
var keys = Object.keys( this.props.value ),
className = this.state.editing ? 'open arrayAttr compoundAttr' : 'arrayAttr compoundAttr',
openArray = ''
;
var attrs = [];
for (var i = 0; i < this.props.value.length; i++) {
attrs.push(
React.createElement(Attribute, {
parent: this.props.value,
value: this.props.value[i],
original: this.props.original[i],
key: i,
attrkey: i }
)
);
}
openArray = (React.createElement("div", {className: "attrChildren"},
attrs,
React.createElement(AttributeCreator, {type: "element", parent: this.props.value, attrkey: keys.length})
)
);
return (React.createElement("span", {className: className },
React.createElement("span", {onClick: this.toggleEditing, className: "hashToggle"}, "List [", keys.length, "]"),
openArray
))
;
},
toggleEditing: function(){
this.setState({editing: !this.state.editing});
}
});
/**
* Component for editing a string.
* @param {string} value The value of the string.
* @param {Mixed} original The value of the component it the original json.
* @param {FreezerNode} parent The parent node to let the string component update its value.
*/
var StringAttribute = React.createClass({displayName: 'StringAttribute',
getInitialState: function(){
return {
editing: !this.props.value,
value: this.props.value,
modified: false
};
},
render: function(){
var className = 'stringAttr';
if( this.state.modified )
className = ' modified';
if( !this.state.editing )
return React.createElement("span", {onClick: this.setEditMode, className: className }, this.props.value);
return React.createElement("input", {value: this.state.value, onChange: this.updateValue, onBlur: this.setValue, ref: "input", onKeyDown: this.handleKeyDown});
},
componentDidUpdate: function( prevProps, prevState ){
if( this.state.editing && ! prevState.editing ){
var node = this.refs.input.getDOMNode();
node.focus();
node.value = node.value;
}
},
componentDidMount: function(){
if( this.state.editing ){
var node = this.refs.input.getDOMNode();
node.focus();
node.value = node.value;
}
},
setEditMode: function(){
this.setState({editing: true});
},
setValue: function(){
if( this.state.modified )
this.props.parent.set( this.props.attrkey, this.state.value );
this.setState({editing: false});
},
updateValue: function( e ){
this.setState({value: e.target.value, modified: e.target.value != this.props.value });
},
handleKeyDown: function( e ){
if( e.which == 13 )
this.setValue();
},
toggleEditing: function(){
this.setState({ editing: !this.state.editing });
}
});
/**
* Component to add attributes to a Hash or Array.
* @param {FreezerNode} root The parent to add the attribute.
* @param {string} attrkey Optional. If provided, the attribute added will have that key (arrays).
* Otherwise an input will be shown to let the user define the key.
*/
var AttributeCreator = React.createClass({displayName: 'AttributeCreator',
getInitialState: function(){
return {
creating: false,
attrkey: this.props.attrkey,
type: 'string'
};
},
render: function(){
if( !this.state.creating )
return React.createElement("a", {href: "#", onClick: this.handleCreate}, "+ Add ", this.props.type);
var attrName;
if( typeof this.props.attrkey != 'undefined' )
attrName = React.createElement("span", {className: "attrName"}, this.props.attrkey, ":");
else {
attrName = [
React.createElement("input", {ref: "keyInput", type: "text", value: this.state.value, onChange: this.changeKey}),
React.createElement("span", null, ":")
];
}
return (React.createElement("div", {className: "hashAttribute"},
attrName,
React.createElement("select", {value: this.state.type, onChange: this.changeType, ref: "typeSelector"},
React.createElement("option", {value: "string"}, "String"),
React.createElement("option", {value: "array"}, "List"),
React.createElement("option", {value: "object"}, "Map")
),
React.createElement("button", {onClick: this.createAttribute}, "OK"), ",",
React.createElement("a", {href: "#", className: "cancelAttr", onClick: this.handleCancel}, "Cancel")
));
},
componentDidUpdate: function( prevProps, prevState){
if( !prevState.creating && this.state.creating ){
if( this.refs.keyInput )
this.refs.keyInput.getDOMNode().focus();
else
this.refs.typeSelector.getDOMNode().focus();
}
},
componentWillReceiveProps: function( newProps ){
this.setState({attrkey: newProps.attrkey});
},
handleCreate: function( e ){
e.preventDefault();
this.setState({creating: true});
},
handleCancel: function( e ){
e.preventDefault();
this.setState({creating: false});
},
changeType: function( e ){
this.setState({type: e.target.value});
},
changeKey: function( e ){
this.setState({attrkey: e.target.value});
},
createAttribute: function(){
this.setState({creating: false});
var parent = this.props.parent,
value = typeDefaultValues[ this.state.type ]
;
if( parent.constructor == Array )
parent.push( value )
else
parent.set(this.state.attrkey, value );
}
});
/****************
Start the UI
*****************/
React.render(React.createElement(DocEditor, {store: frozen.get(), original: frozen.get() }), document.body);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment