├── README.md ├── examples └── my-custom-tag-manager.jsx ├── tag-manager.css └── tag-manager.jsx /README.md: -------------------------------------------------------------------------------- 1 | # React Tag Manager 2 | React Tag Manager is a React plugin for implementing tagging system easily with React. 3 | 4 | # Features 5 | * Autocomplete 6 | * Dynamic creating/deleting 7 | 8 | # Demo 9 | http://howtomakeaturn.github.io/react-tag-manager/ 10 | 11 | # Getting Started 12 | To use the tag manager plugin, include the react library, the JSX transformer, and the react-tag-manager library inside the tag of your HTML document: 13 | 14 | ```html 15 | 16 | 17 | 18 | 19 | 20 | 21 | ``` 22 | 23 | Tell tag manager what's the initial tags and options for autocomplete: 24 | ```jsx 25 | 44 | ``` 45 | We done! The live demo is here: [Basic example](http://howtomakeaturn.github.io/react-tag-manager/) 46 | 47 | # Advanced 48 | 49 | Usually you need to send an ajax or do some other things after user creating/deleting tags. Tag manager support this with callback functions in composition components. 50 | 51 | Simply create your own component with callback functions for creating/deleting, and then render TagManager with [JSX spread attributes](http://facebook.github.io/react/docs/jsx-spread.html). 52 | 53 | ```jsx 54 | /** @jsx React.DOM */ 55 | 56 | var MyCustomTagManager = React.createClass({ 57 | 58 | addTagCallback: function(tagName, setStateCallback){ 59 | alert('Add a tag! Maybe you should send an ajax!'); 60 | setStateCallback({id: 99, name: tagName}); 61 | }, 62 | 63 | removeTagCallback: function(tag){ 64 | alert('Remove a tag! Maybe you should send an ajax!'); 65 | }, 66 | 67 | render: function(){ 68 | return( 69 | 70 | ) 71 | } 72 | 73 | }); 74 | 75 | ``` 76 | To render the customized tag manager, it's the same as the original one: 77 | ```jsx 78 | /** @jsx React.DOM */ 79 | 80 | var tags = [ 81 | { name: 'Jack', id: 1}, 82 | { name: 'Betty', id: 2}, 83 | { name: 'Kelly', id: 3}, 84 | ]; 85 | 86 | var tagOptions = [ 87 | 'apple', 88 | 'banana', 89 | 'cat' 90 | ]; 91 | 92 | React.renderComponent( 93 | , 94 | document.getElementById('advanced-tag-manager') 95 | ); 96 | ``` 97 | 98 | 99 | The live demo is here: [Custom tag manager example](http://howtomakeaturn.github.io/react-tag-manager/) 100 | -------------------------------------------------------------------------------- /examples/my-custom-tag-manager.jsx: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 3 | var MyCustomTagManager = React.createClass({ 4 | 5 | addTagCallback: function(tagName, setStateCallback){ 6 | alert('Add a tag! Maybe you should send an ajax!'); 7 | setStateCallback({id: 99, name: tagName}); 8 | /* 9 | $.ajax({ 10 | url: "/tag/add", 11 | method: 'post', 12 | data: { name: tagName }, 13 | success: function(res){ 14 | setStateCallback(res.tag); 15 | } 16 | }); 17 | */ 18 | }, 19 | 20 | removeTagCallback: function(tag){ 21 | alert('Remove a tag! Maybe you should send an ajax!'); 22 | /* 23 | $.ajax({ 24 | url: "/tag/remove", 25 | method: 'delete', 26 | data: { id: tag.id }, 27 | }); 28 | */ 29 | }, 30 | 31 | render: function(){ 32 | return( 33 | 34 | ) 35 | } 36 | 37 | }); 38 | -------------------------------------------------------------------------------- /tag-manager.css: -------------------------------------------------------------------------------- 1 | .react-tag-add-component{ 2 | display: inline-block; 3 | } 4 | .react-tag-add-input{ 5 | width: 210px; 6 | } 7 | .react-tag-option{ 8 | background-color: white; 9 | padding: 5px; 10 | margin: 0; 11 | width: 200px; 12 | cursor: pointer; 13 | } 14 | .react-tag-option:hover{ 15 | background-color: lightgrey; 16 | } 17 | .react-tag-option-container{ 18 | position: absolute; 19 | border: 1px solid grey; 20 | } 21 | .react-tag{ 22 | border-radius: 5px; 23 | padding: 5px; 24 | margin: 5px; 25 | background-color: #2EB8E6; 26 | color: white; 27 | cursor: pointer; 28 | } 29 | -------------------------------------------------------------------------------- /tag-manager.jsx: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 3 | var TagOption = React.createClass({ 4 | 5 | onClick: function(){ 6 | this.props.addTag(this.props.value); 7 | this.props.closeInput(); 8 | }, 9 | 10 | render: function(){ 11 | return ( 12 |

13 | {this.props.value} 14 |

15 | ); 16 | } 17 | }); 18 | 19 | var AddTagComponent = React.createClass({ 20 | 21 | componentDidMount: function(){ 22 | document.addEventListener("keydown", function (e) { 23 | if ( (this.props.showInput) && (e.keyCode === 27) ){ 24 | this.props.closeInput(); 25 | } 26 | }.bind(this)); 27 | 28 | document.addEventListener("click", function (e) { 29 | if( this.props.showInput && (e.target !== this.refs.input.getDOMNode()) ){ 30 | this.props.closeInput(); 31 | } 32 | }.bind(this)); 33 | 34 | }, 35 | 36 | componentDidUpdate: function(){ 37 | if (this.props.showInput){ 38 | this.refs.input.getDOMNode().focus(); 39 | } 40 | }, 41 | 42 | onKeyDown: function(e){ 43 | if (e.keyCode === 13){ 44 | if (this.refs.input.getDOMNode().value === ''){ 45 | 46 | } else { 47 | this.props.addTag(this.refs.input.getDOMNode().value); 48 | this.refs.input.getDOMNode().value = ''; 49 | this.props.closeInput(); 50 | } 51 | } 52 | }, 53 | 54 | onClickButton: function(e){ 55 | // Using stopImmediatePropagation rather than stopPropagation is kind of ugly. 56 | // But since the current button is disappearing at the same time, it's not easy 57 | // to figure out which element is the parent element. 58 | 59 | e.nativeEvent.stopImmediatePropagation(); 60 | this.props.openInput(); 61 | }, 62 | 63 | render: function(){ 64 | 65 | if (this.props.showInput){ 66 | var tagOptions = []; 67 | for(var i in this.props.tagOptions){ 68 | tagOptions.push(); 69 | } 70 | 71 | return ( 72 |
73 | 74 |
75 | {tagOptions} 76 |
77 |
78 | ); 79 | } else { 80 | return (); 81 | } 82 | 83 | } 84 | 85 | }); 86 | 87 | var Tag = React.createClass({ 88 | 89 | onClick: function(){ 90 | this.props.removeTag(this.props.index); 91 | }, 92 | 93 | render: function(){ 94 | return ( 95 | 96 | {this.props.name} 97 | 98 | ); 99 | } 100 | 101 | }); 102 | 103 | var TagList = React.createClass({ 104 | render: function(){ 105 | var tags= []; 106 | for(var i in this.props.tags){ 107 | tags.push( ); 108 | } 109 | 110 | return( 111 | 112 | {tags} 113 | 114 | ) 115 | } 116 | }); 117 | 118 | var TagManager = React.createClass({ 119 | 120 | getInitialState: function(){ 121 | if (this.props.tags){ 122 | return { showInput: false, currentInput: '', tags: this.props.tags }; 123 | } else { 124 | return { showInput: false, currentInput: '', tags: [] }; 125 | } 126 | }, 127 | 128 | getDefaultProps: function() { 129 | return { 130 | tagOptions: [] 131 | }; 132 | }, 133 | 134 | openInput: function(){ 135 | this.setState({ showInput: true }); 136 | }, 137 | 138 | closeInput: function(){ 139 | this.setState({ showInput: false }); 140 | }, 141 | 142 | addTag: function(name){ 143 | 144 | if (this.props.addTagCallback) { 145 | this.props.addTagCallback(name, function(tag){ 146 | var tags = this.state.tags; 147 | tags.push(tag); 148 | this.setState({tags: tags}); 149 | }.bind(this)); 150 | } else { 151 | var tags = this.state.tags; 152 | tags.push({name: name}); 153 | this.setState({tags: tags}); 154 | } 155 | }, 156 | 157 | removeTag: function(index){ 158 | var tags = this.state.tags; 159 | this.props.removeTagCallback && this.props.removeTagCallback(tags[index]); 160 | tags.splice(index, 1); 161 | this.setState({tags: tags}); 162 | }, 163 | 164 | changeInput: function(e){ 165 | this.setState({currentInput: e.target.value}); 166 | }, 167 | 168 | getTagOptions: function(){ 169 | var result = []; 170 | 171 | for (var j=0; j 181 | 182 | 190 | 191 | ) 192 | } 193 | }); 194 | --------------------------------------------------------------------------------