├── .bowerrc ├── index.js ├── bundles └── index │ ├── index.bemdecl.js │ └── index.html ├── .gitignore ├── blocks ├── react │ └── react.js ├── commentBox │ ├── commentBox.css │ └── commentBox.jsx ├── document │ ├── document.css │ ├── document.jsx │ └── document.node.js ├── showdown │ └── showdown.js ├── comment │ ├── comment.css │ └── comment.jsx ├── commentList │ └── commentList.jsx └── commentForm │ └── commentForm.jsx ├── bower.json ├── README.md ├── .bem ├── techs │ └── jsx.js └── enb-make.js └── package.json /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "libs" 3 | } 4 | 5 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('./bundles/index/index.node.js'); -------------------------------------------------------------------------------- /bundles/index/index.bemdecl.js: -------------------------------------------------------------------------------- 1 | exports.deps = [ 2 | { block: 'document' } 3 | ] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | node_modules 3 | .DS_Store 4 | .idea 5 | 6 | .bem/tmp 7 | libs 8 | 9 | bundles/*/*.* 10 | !bundles/*/*.html 11 | !bundles/*/*.bemdecl.js 12 | -------------------------------------------------------------------------------- /blocks/react/react.js: -------------------------------------------------------------------------------- 1 | modules.define('react', ['loader_type_js'], function(provide, loader) { 2 | 3 | loader('http://fb.me/react-0.10.0.min.js', function() { 4 | provide(React); 5 | }); 6 | 7 | }); -------------------------------------------------------------------------------- /blocks/commentBox/commentBox.css: -------------------------------------------------------------------------------- 1 | .commentBox__title 2 | { 3 | border-bottom: 1px solid #ddd; 4 | font-size: 2.5em; 5 | font-weight: bold; 6 | margin: 0 0 15px; 7 | padding: 0; 8 | } 9 | -------------------------------------------------------------------------------- /blocks/document/document.css: -------------------------------------------------------------------------------- 1 | body 2 | { 3 | background: #fff; 4 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;; 5 | font-size: 15px; 6 | line-height: 1.7; 7 | margin: 0; 8 | padding: 30px; 9 | } -------------------------------------------------------------------------------- /blocks/showdown/showdown.js: -------------------------------------------------------------------------------- 1 | modules.define('showdown', ['loader_type_js'], function(provide, loader) { 2 | 3 | loader('http://cdnjs.cloudflare.com/ajax/libs/showdown/0.3.1/showdown.min.js', function() { 4 | provide(Showdown); 5 | }); 6 | 7 | }); -------------------------------------------------------------------------------- /blocks/document/document.jsx: -------------------------------------------------------------------------------- 1 | modules.require(['react', 'commentBox'], function(React, CommentBox) { 2 | 3 | React.renderComponent( 4 | , 5 | document.getElementById('content') 6 | ); 7 | 8 | }); 9 | -------------------------------------------------------------------------------- /blocks/comment/comment.css: -------------------------------------------------------------------------------- 1 | .comment__author 2 | { 3 | font-weight: bold; 4 | margin: 0 0 15px; 5 | padding: 0; 6 | border-bottom: 1px solid #eee; 7 | font-size: 2em; 8 | } 9 | 10 | .comment__text p 11 | { 12 | margin: 15px 0; 13 | } -------------------------------------------------------------------------------- /bundles/index/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hello React 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-tutorial", 3 | "version": "0.0.0", 4 | "authors": [ 5 | "Vyacheslav Aristov " 6 | ], 7 | "dependencies": { 8 | "bem-core": "git://github.com/bem/bem-core.git" 9 | }, 10 | "ignore": [ 11 | "node_modules", 12 | "libs", 13 | "bundles" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React comment box example with BEM! 2 | 3 | This is the React comment box example from [the React tutorial](http://facebook.github.io/react/docs/tutorial.html). 4 | It's built on [BEM](http://bem.info) with React integration. 5 | 6 | ## To use 7 | 8 | ``` 9 | npm install 10 | ./node_modules/.bin/bower install 11 | ./node_modules/.bin/enb make 12 | node . 13 | ``` 14 | 15 | And visit http://localhost:3000/. 16 | -------------------------------------------------------------------------------- /.bem/techs/jsx.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'), 2 | reactTools = require('react-tools'); 3 | 4 | module.exports = require('enb/lib/build-flow').create() 5 | .name('jsx') 6 | .target('target', '?.js') 7 | .useFileList(['js', 'jsx']) 8 | .builder(function (jsxFiles) { 9 | var jsx = jsxFiles.map(function(file) { 10 | return [ 11 | '/* ' + file.name + ' (begin) */', 12 | fs.readFileSync(file.fullname, 'utf-8'), 13 | '/* ' + file.name + ' (end) */' 14 | ].join('\n'); 15 | }); 16 | return reactTools.transform(['/** @jsx React.DOM */'].concat(jsx).join('\n')); 17 | }) 18 | .createTech(); -------------------------------------------------------------------------------- /blocks/comment/comment.jsx: -------------------------------------------------------------------------------- 1 | modules.define('comment', ['react', 'showdown'], function(provide, React, Showdown) { 2 | 3 | var converter = new Showdown.converter(); 4 | 5 | var Comment = React.createClass({ 6 | render: function() { 7 | var rawMarkup = converter.makeHtml(this.props.children.toString()); 8 | return ( 9 |
10 |

11 | {this.props.author} 12 |

13 | 14 |
15 | ); 16 | } 17 | }); 18 | 19 | provide(Comment); 20 | 21 | }); 22 | -------------------------------------------------------------------------------- /blocks/document/document.node.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var bodyParser = require('body-parser'); 3 | var app = express(); 4 | 5 | var comments = [{author: 'Pete Hunt', text: 'Hey there!'}]; 6 | 7 | app.use('/', express.static(__dirname)); 8 | app.use(bodyParser.json()); 9 | app.use(bodyParser.urlencoded({extended: true})); 10 | 11 | app.get('/comments.json', function(req, res) { 12 | res.setHeader('Content-Type', 'application/json'); 13 | res.send(JSON.stringify(comments)); 14 | }); 15 | 16 | app.post('/comments.json', function(req, res) { 17 | comments.push(req.body); 18 | res.setHeader('Content-Type', 'application/json'); 19 | res.send(JSON.stringify(comments)); 20 | }); 21 | 22 | app.listen(3000); 23 | 24 | console.log('Server started: http://localhost:3000/'); 25 | -------------------------------------------------------------------------------- /blocks/commentList/commentList.jsx: -------------------------------------------------------------------------------- 1 | modules.define('commentList', ['react', 'comment'], function(provide, React, Comment) { 2 | 3 | var CommentList = React.createClass({ 4 | render: function() { 5 | var commentNodes = this.props.data.map(function(comment, index) { 6 | return ( 7 | // `key` is a React-specific concept and is not mandatory for the 8 | // purpose of this tutorial. if you're curious, see more here: 9 | // http://facebook.github.io/react/docs/multiple-components.html#dynamic-children 10 | 11 | {comment.text} 12 | 13 | ); 14 | }); 15 | return ( 16 |
17 | {commentNodes} 18 |
19 | ); 20 | } 21 | }); 22 | 23 | provide(CommentList); 24 | 25 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-tutorial", 3 | "version": "0.0.0", 4 | "description": "Code from the React tutorial.", 5 | "main": "server.js", 6 | "dependencies": { 7 | "body-parser": "^1.4.3", 8 | "express": "^4.4.5", 9 | "enb": "~0.13.7", 10 | "enb-diverse-js": "~0.1.0", 11 | "react-tools": "~0.11.1", 12 | "ym": "~0.1.0", 13 | "enb-modules": "~0.2.0", 14 | "bower": "~1.3.9" 15 | }, 16 | "devDependencies": {}, 17 | "scripts": { 18 | "test": "echo \"Error: no test specified\" && exit 1", 19 | "start": "node server.js" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/reactjs/react-tutorial.git" 24 | }, 25 | "keywords": [ 26 | "react", 27 | "tutorial", 28 | "comment", 29 | "example" 30 | ], 31 | "author": "petehunt", 32 | "license": "MIT", 33 | "bugs": { 34 | "url": "https://github.com/reactjs/react-tutorial/issues" 35 | }, 36 | "homepage": "https://github.com/reactjs/react-tutorial" 37 | } 38 | -------------------------------------------------------------------------------- /blocks/commentForm/commentForm.jsx: -------------------------------------------------------------------------------- 1 | modules.define('commentForm', ['react'], function(provide, React) { 2 | 3 | var CommentForm = React.createClass({ 4 | handleSubmit: function(e) { 5 | e.preventDefault(); 6 | var author = this.refs.author.getDOMNode().value.trim(); 7 | var text = this.refs.text.getDOMNode().value.trim(); 8 | if (!text || !author) { 9 | return; 10 | } 11 | this.props.onCommentSubmit({author: author, text: text}); 12 | this.refs.author.getDOMNode().value = ''; 13 | this.refs.text.getDOMNode().value = ''; 14 | return; 15 | }, 16 | render: function() { 17 | return ( 18 |
19 | 20 | 21 | 22 |
23 | ); 24 | } 25 | }); 26 | 27 | provide(CommentForm); 28 | 29 | }); -------------------------------------------------------------------------------- /.bem/enb-make.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | config.mode("development", function() { 3 | config.node("bundles/index", function(nodeConfig) { 4 | nodeConfig.addTechs([ 5 | [ require("enb/techs/file-copy"), { sourceTarget: "?.css", destTarget: "_?.css" } ], 6 | [ require("enb/techs/file-copy"), { sourceTarget: "?.js", destTarget: "_?.js" } ] 7 | ]); 8 | }); 9 | }); 10 | config.mode("production", function() { 11 | config.node("bundles/index", function(nodeConfig) { 12 | nodeConfig.addTechs([ 13 | [ require("enb/techs/borschik"), { sourceTarget: "?.css", destTarget: "_?.css", minify: true, freeze: false } ], 14 | [ require("enb/techs/borschik"), { sourceTarget: "?.js", destTarget: "_?.js", minify: true, freeze: false } ] 15 | ]); 16 | }); 17 | }); 18 | 19 | config.node("bundles/index", function(nodeConfig) { 20 | nodeConfig.addTechs([ 21 | [ require("enb/techs/levels"), { levels: getLevels() } ], 22 | [ require("enb/techs/file-provider"), { target: "?.bemdecl.js" } ], 23 | [ require("enb-modules/techs/deps-with-modules"), { sourceSuffixes: ['js', 'jsx'] } ], 24 | require("enb/techs/files"), 25 | require("enb/techs/css"), 26 | [ require("./techs/jsx"), { target: '?.pre.js' } ], 27 | [ require("enb-modules/techs/prepend-modules"), { source: '?.pre.js', target: '?.js' } ], 28 | [ require("enb/techs/js"), { sourceSuffixes: 'node.js', target: '?.node.js' } ] 29 | ]); 30 | nodeConfig.addTargets(["_?.css", "_?.js", "?.node.js"]); 31 | 32 | function getLevels() { 33 | return [ 34 | {"path":"libs/bem-core/common.blocks","check":false}, 35 | {"path":"blocks","check":true} 36 | ].map(function(l) { return config.resolvePath(l); }); 37 | } 38 | }); 39 | } -------------------------------------------------------------------------------- /blocks/commentBox/commentBox.jsx: -------------------------------------------------------------------------------- 1 | modules.define('commentBox', [ 2 | 'react', 3 | 'commentList', 4 | 'commentForm', 5 | 'jquery' 6 | ], function(provide, React, CommentList, CommentForm, $) { 7 | 8 | var CommentBox = React.createClass({ 9 | loadCommentsFromServer: function() { 10 | $.ajax({ 11 | url: this.props.url, 12 | dataType: 'json', 13 | success: function(data) { 14 | this.setState({data: data}); 15 | }.bind(this), 16 | error: function(xhr, status, err) { 17 | console.error(this.props.url, status, err.toString()); 18 | }.bind(this) 19 | }); 20 | }, 21 | handleCommentSubmit: function(comment) { 22 | var comments = this.state.data; 23 | comments.push(comment); 24 | this.setState({data: comments}, function() { 25 | // `setState` accepts a callback. To avoid (improbable) race condition, 26 | // `we'll send the ajax request right after we optimistically set the new 27 | // `state. 28 | $.ajax({ 29 | url: this.props.url, 30 | dataType: 'json', 31 | type: 'POST', 32 | data: comment, 33 | success: function(data) { 34 | this.setState({data: data}); 35 | }.bind(this), 36 | error: function(xhr, status, err) { 37 | console.error(this.props.url, status, err.toString()); 38 | }.bind(this) 39 | }); 40 | }); 41 | }, 42 | getInitialState: function() { 43 | return {data: []}; 44 | }, 45 | componentDidMount: function() { 46 | this.loadCommentsFromServer(); 47 | setInterval(this.loadCommentsFromServer, this.props.pollInterval); 48 | }, 49 | render: function() { 50 | return ( 51 |
52 |

Comments

53 | 54 | 55 |
56 | ); 57 | } 58 | }); 59 | 60 | provide(CommentBox); 61 | 62 | }); 63 | --------------------------------------------------------------------------------