Skip to content

Instantly share code, notes, and snippets.

@volkovasystems
Created December 4, 2014 09:15
Show Gist options
  • Save volkovasystems/22b10d9ffd1fd157b75c to your computer and use it in GitHub Desktop.
Save volkovasystems/22b10d9ffd1fd157b75c to your computer and use it in GitHub Desktop.
Hack into Angular + React
<html ng-app="TextInput">
<head>
</head>
<body>
<text-input
style="height:100px;"
class="first-name-input"
name="firstName">
</text-input>
<script type="text/javascript" src="https://rawgit.com/broofa/node-uuid/master/uuid.js"></script>
<script type="text/javascript" src="https://cdn.rawgit.com/jprichardson/string.js/master/lib/string.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.27/angular.min.js"></script>
<script type="text/javascript" src="https://fb.me/react-0.12.1.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.min.js"></script>
<script type="text/javascript" src="./build/text-input.js"></script>
</body>
</html>
angular.module( "generateUUIDForDOMID", [ ] )
.factory( "generateUUIDForDOMID", [
function factory( ){
var generateUUIDForDOMID = function generateUUIDForDOMID( ){
return btoa( [ uuid.v1( ), uuid.v4( ) ].join( ":" ) )
};
return generateUUIDForDOMID;
}
] )
.directive( "id", [
"generateUUIDForDOMID",
function directive( generateUUIDForDOMID ){
return {
"restrict": "A",
"scope": true,
"priority": 4,
"link": function onLink( scope, container, propertySet ){
scope.id = generateUUIDForDOMID( );
}
}
}
] );
angular.module( "parseStyleSet", [ ] )
.factory( "parseStyleSet", [
function factory( ){
var parseStyleSet = function parseStyleSet( style ){
var styleList = ( style || "" ).split( ";" ) || [ ];
var styleSet = { };
_.each( styleList,
function onEachStyleString( styleString ){
var styleData = styleString.split( ":" );
var styleName = S( styleData[ 0 ] ).camelize( );
styleSet[ styleName ] = styleData[ 1 ];
} );
return styleSet;
};
return parseStyleSet;
}
] )
.directive( "style", [
"parseStyleSet",
function directive( parseStyleSet ){
return {
"restrict": "A",
"scope": true,
"priority": 4,
"link": function onLink( scope, container, propertySet ){
scope.styleSet = parseStyleSet( propertySet.style );
$( container ).removeAttr( "style" );
}
}
}
] );
angular.module( "parseClassList", [ ] )
.factory( "parseClassList", [
function factory( ){
var parseClassList = function parseClassList( classString ){
return ( classString || "" ).split( " " ) || [ ];
};
return parseClassList;
}
] )
.directive( "class", [
"parseClassList",
function directive( parseClassList ){
return {
"restrict": "A",
"scope": true,
"priority": 4,
"link": function onLink( scope, container, propertySet ){
scope.classList = parseClassList( propertySet.class );
$( container ).removeAttr( "class" );
}
}
}
] );
angular.module( "Box", [ ] )
//: This will become bigger but we need a single in-memory reference to all the boxes.
.constant( "BOX_CONTAINER_LIST", [ ] )
.factory( "Box", [
function factory( ){
var Box = function Box( elementClass ){
if( this instanceof Box ){
this.elementClass = elementClass;
this.instanceList = [ ];
}else{
return new Box( elementClass );
}
};
/*:
This will add essential methods to the element's class.
This will also gives the notion that we are wrapping a UI component as a box.
*/
Box.prototype.wrap = function wrap( ){
var self = this;
var element = this.elementClass;
element.attach = function attach( container, propertySet, optionSet ){
container = $( container )[ 0 ];
var elementInstance = React.createElement( element, propertySet );
//: This will let us track and manipulate the instances created by this box.
self.instanceList.push( {
"instance": elementInstance,
"container": container
} );
var renderedElement = React.render( elementInstance, container );
if( !_.isEmpty( optionSet ) &&
"initialize" in optionSet &&
typeof optionSet.initialize == "function" )
{
//: And yes this will override so be careful.
renderedElement.initialize = optionSet.initialize;
}
if( !_.isEmpty( optionSet ) &&
"configure" in optionSet &&
typeof optionSet.configure == "function" )
{
//: Same with the initialize method, this will override the default configure behaviour.
//: Note that I made it like this because this will change the entire component.
//: The previous initialize and configure method may conflict with the new ones.
renderedElement.configure = optionSet.configure;
}
if( typeof renderedElement.initialize == "function" &&
!_.isEmpty( propertySet ) )
{
//: Property set should be passed to the initialize method.
renderedElement.initialize.call( renderedElement, propertySet );
}
if( typeof renderedElement.configure == "function" &&
!_.isEmpty( optionSet ) )
{
//: Option set should be passed to the configure method.
//: Optionally, some components may want the initial values from the propertySet.
//: We will not provide a check for this.
//: This is left to the developers to ensure that propertySet contains something.
renderedElement.configure.call( renderedElement, optionSet, propertySet );
}
return renderedElement;
};
};
//: Destroy the box and its contents.
//: Note that this may destroy the entire components created using this box.
//: Providing a reference will selectively destroy that component.
Box.prototype.crush = function crush( reference ){
//: Now the reference may be a jQuery instance, a string reference, or a native DOM instance.
var instanceList = this.instanceList;
if( !_.isEmpty( reference ) ){
var container = _.find( instanceList,
function onEachInstanceList( instanceData ){
return instanceData.container === $( reference )[ 0 ];
} ).container;
React.unmountComponentAtNode( container );
}else{
_.each( instanceList,
function onEachInstanceList( instanceData ){
React.unmountComponentAtNode( instanceData.container );
} );
}
};
return Box;
}
] )
/*:
Boxify creates an instance around the element class.
And invoke the wrap method.
It will also push the new box to the container list for tracking.
*/
.factory( "boxify", [
"Box",
"BOX_CONTAINER_LIST",
function factory( Box, BOX_CONTAINER_LIST ){
var boxify = function boxify( elementClass ){
var newBox = Box( elementClass );
newBox.wrap( );
BOX_CONTAINER_LIST.push( newBox );
return newBox;
};
return boxify;
}
] );
angular
.module( "TextInput", [
"Box",
"parseStyleSet",
"parseClassList",
"generateUUIDForDOMID"
] )
/*:
This enable us to use either the default view OR the template view.
It is reasonable to add all template views to the template directory.
The template should be simple.
*/
.factory( "getTextInputView", [
function factory( ){
var getTextInputView = function getTextInputView( defaultView ){
var view; //: @template: template/text-input.html
return view || defaultView;
};
return getTextInputView;
}
] )
.factory( "TextInput", [
"boxify",
"getTextInputView",
"generateUUIDForDOMID",
function factory(
boxify,
getTextInputView,
generateUUIDForDOMID
){
var TextInput = React.createClass( {
"getInitialState": function getInitialState( ){
return {
"id": "",
"classList": null,
"styleSet": null,
"name": "",
"title": "",
"placeholder": "",
"inputValue": ""
};
},
"getDefaultProps": function getDefaultProps( ){
return {
"id": generateUUIDForDOMID( ),
"classList": [ ],
"styleSet": { },
"name": "inputText",
"title": "Text",
"placeholder": "Type something...",
"inputValue": ""
};
},
"getID": function getID( ){
return this.state.id || this.props.id;
},
"getClassList": function getClassList( ){
return this.state.classList || this.props.classList;
},
"getClassString": function getClassString( ){
return this.getClassList( ).join( " " );
},
"getStyleSet": function getStyleSet( ){
return this.state.styleSet || this.props.styleSet;
},
"getName": function getName( ){
return this.state.name || this.props.name;
},
"getTitle": function getTitle( ){
return this.state.title || this.props.title;
},
"getPlaceholder": function getPlaceholder( ){
return this.state.placeholder || this.props.placeholder;
},
"getInputValue": function getInputValue( ){
return this.state.inputValue || this.props.inputValue;
},
"onChangeTextInput": function onChangeTextInput( event ){
var newValue = event.target.value;
this.setState( {
"inputValue": newValue
} );
},
"onUpdateInputValue": function onUpdateInputValue( prevProps, prevState ){
if( !_.isEqual( prevState.inputValue, this.state.inputValue ) ){
if( typeof this.props.onChange == "function" ){
this.props.onChange( this.state.inputValue, prevState.inputValue );
}
}
},
"render": function onRender( ){
//: We revert to default if a custom template is not given.
return getTextInputView(
<input
id={ this.getID( ) }
className={ this.getClassString( ) }
style={ this.getStyleSet( ) }
name={ this.getName( ) }
title={ this.getTitle( ) }
placeholder={ this.getPlaceholder( ) }
type="text"
value={ this.getInputValue( ) }
onChange={ this.onChangeTextInput }/>
);
},
"componentDidUpdate": function componentDidUpdate( prevProps, prevState ){
this.onUpdateInputValue( prevProps, prevState );
}
} );
boxify( TextInput );
return TextInput;
}
] )
.factory( "attachTextInput", [
"$rootScope",
"TextInput",
function factory( $rootScope, TextInput ){
var attachTextInput = function attachTextInput( container, propertySet, optionSet ){
var scope = propertySet.scope || $rootScope;
scope = scope.$new( true );
TextInput.attach( container, propertySet, optionSet );
};
return attachTextInput;
}
] )
/*:
This will enable us to attach the component via Angular's directive approach.
*/
.directive( "textInput", [
"attachTextInput",
"parseStyleSet",
"parseClassList",
"generateUUIDForDOMID",
function directive(
attachTextInput,
parseStyleSet,
parseClassList,
generateUUIDForDOMID
){
return {
"restrict": "EA",
"scope": true,
"priority": 3,
"template": "<section></section>",
"replace": true,
"link": function onLink( scope, container, propertySet ){
attachTextInput( container,
//: This will be attached to the props.
//: This will also flow inside the initialize method if provided.
//: Most of the stuffs here is component specific.
{
"scope": scope,
"container": $( "section", container ),
"name": propertySet.name,
"title": propertySet.title,
"placeholder": propertySet.placeholder,
"inputValue": propertySet.inputValue,
"id": scope.id || propertySet.id || generateUUIDForDOMID( ),
"classList": scope.classList || parseClassList( propertySet.class ),
"styleSet": scope.styleSet || parseStyleSet( propertySet.style )
},
//: This will flow inside the configure method if provided.
{
"onChange": scope.onChange,
"initialize": scope.initialize,
"configure": scope.configure
} );
}
};
}
] );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment