Created
December 4, 2014 09:15
-
-
Save volkovasystems/22b10d9ffd1fd157b75c to your computer and use it in GitHub Desktop.
Hack into Angular + React
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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