Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restricted Textbox Component #15

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions client/app.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<!DOCTYPE html>
<html>
<head>
<title>Example App</title>
</head>
<body></body>
<script src="/build/bundle.js" type="text/javascript" async></script>
</html>
<head>
<title>Example App</title>
</head>
<body></body>
<script src="/build/bundle.js" type="text/javascript" async></script>
</html>
19 changes: 0 additions & 19 deletions client/app/app.js

This file was deleted.

28 changes: 28 additions & 0 deletions client/app/components/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/** @jsx React.DOM */

var React = require("react");

var MainHeader = require('./main_header');

var App = React.createClass({
componentDidMount: function () {
//componentDidMount only gets called from client side - not on server rendering
console.log("Mounted");
},
componentWillMount: function () {
//componentWillMount is called from client side and server rendering
console.log("Going to Mount");
},
render: function () {
return (
<div className='app'>
<MainHeader currentUri='/'/>
<div className='main-content'>
{this.props.children}
</div>
</div>
);
}
});

module.exports = App;
22 changes: 22 additions & 0 deletions client/app/components/main_header.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/** @jsx React.DOM */

var React = require("react");

var MainNav = require('./main_nav');

var MainHeader = React.createClass({
propTypes: {
currentUri: React.PropTypes.string.isRequired
},

render:function(){
return (
<div className='main-header'>
<h1 className='logo'>SurveyBuilder</h1>
<MainNav currentUri={this.props.currentUri} />
</div>
);
}
});

module.exports = MainHeader;
36 changes: 36 additions & 0 deletions client/app/components/main_nav.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/** @jsx React.DOM */

var React = require("react/addons");
var classSet = React.addons.classSet;

var NAV_ITEMS = {
'All Surveys': '/',
'Add Survey': '/add_survey'
};

var MainNav = React.createClass({
propTypes: {
currentUri: React.PropTypes.string.isRequired
},

render: function () {
var currentUri = this.props.currentUri;

var items = Object.keys(NAV_ITEMS).map(function (key, i) {
var uri = NAV_ITEMS[key];
var className = classSet({
'current': uri === currentUri
});

return <a key={i} href={uri} className={className}>{key}</a>;
});

return (
<nav className='main-nav'>
{items}
</nav>
);
}
});

module.exports = MainNav;
6 changes: 3 additions & 3 deletions client/client.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/** @jsx React.DOM */

//client entry point - all components should be in separate files,
//client entry point - all components should be in separate files,
//this will allow webpack to use hotmodule update later
//and allow for server side to include just the app

Expand All @@ -9,9 +9,9 @@ require('es5-shim/es5-shim');
require('es5-shim/es5-sham');

var React = require('react');
var App = require('./app/app');
var App = require('./app/components/app');

React.renderComponent(<App/>, document.body);

//allow react dev tools work
window.React = React;
window.React = React;
75 changes: 75 additions & 0 deletions client/components/restricted_text_box.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/** @jsx React.DOM */
var React = require('react');

var Remaining = React.createClass({
render: function(){
var restrictions = this.props.restrictions;
var className = this.props.valid ? 'text-success' : 'text-danger';

var wording = <span>{restrictions.length}/{restrictions.maxLength} {pluralize('character', restrictions.length)}</span>;

if (restrictions.extraRequired > 0) {
wording = <span>need {restrictions.extraRequired} more {pluralize('character', restrictions.extraRequired)}</span>
}

return (
<div className={className}>
{wording}
</div>
);
}
})

var RestrictedTextBox = React.createClass({
render: function(){
var validClassName = this.validate(this.props.value) ? 'has-success' : 'has-error';

return (
<div>
<label style={{display: 'block'}} >
<div>{this.props.children}</div>
<textarea
className="form-control"
rows={5}
value={this.props.value}
onChange={this.props.onChange}
className={"form-control " + validClassName} />
{<Remaining restrictions={this.getRestrictions()} valid={this.validate(this.props.value)}/>}
</label>

</div>
)
},

// derive some data from our props
getRestrictions: function(value){
var props = this.props;

// if a .length prop is passed allow it to overried .value.length
var length = props.length != null ? props.length : props.value.length;
var minLength = props.minLength != null ? props.minLength : 0;
var maxLength = props.maxLength != null ? props.maxLength : Infinity;

return {
length: length,
maxLength: maxLength,
minLength: minLength,
extraAllowed: maxLength - length,
extraRequired: minLength - length
};
},


validate: function(value){
var restrictions = this.getRestrictions(value);
return restrictions.extraAllowed >= 0 && restrictions.extraRequired <= 0;
}
});

function pluralize(str, n){
return n === 1 ? str : str + 's';
}

module.exports = RestrictedTextBox;


1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"main": "server/server.js",
"scripts": {
"prestart": "browserify -t reactify client/client.js > public/build/bundle.js --debug",
"build": "browserify -t reactify client/client.js > public/build/bundle.js --debug",
"start": "node server/server.js",
"test": "karma start karma.conf.js"
},
Expand Down
6 changes: 3 additions & 3 deletions server/render/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ require('node-jsx').install({harmony: true});

var fs = require("fs");
var React = require("react");
var App = require("../../client/app/app");
var App = require("../../client/app/components/app");
var router = require('express').Router({caseSensitive: true, strict: true});

//only read on startup
Expand All @@ -30,7 +30,7 @@ function renderToHtml(route, callback){

//merge body into template
var html = template.replace(/<\/body>/, body + "</body>");

process.nextTick(function(){
callback(null, html);
});
Expand All @@ -43,4 +43,4 @@ router.get('*', function(req, res) {
});
});

module.exports = router;
module.exports = router;
32 changes: 32 additions & 0 deletions test/client/app/components/main_header_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/** @jsx React.DOM */

var React = require("react/addons");
var TestUtils = React.addons.TestUtils;

var MainHeader = require('../../../../client/app/components/main_header');
var MainNav = require('../../../../client/app/components/main_nav');

// verifying karma-jasmine is working
describe("components/main_nav", function (){
var subject;

beforeEach(function () {
subject = TestUtils.renderIntoDocument(
<MainHeader currentUri='/add_survey' />
);
});

describe('#render', function () {
it('has a logo', function () {
var logo = TestUtils.findRenderedDOMComponentWithClass(subject, 'logo');
expect( logo ).not.toBe( null );
});

it('has a <MainNav /> with currentUri', function () {
var mainNav = TestUtils.findRenderedComponentWithType(subject, MainNav);

expect( mainNav ).not.toBe( null );
expect( mainNav.props.currentUri ).toBe( '/add_survey' );
});
});
});
63 changes: 63 additions & 0 deletions test/client/app/components/main_nav_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/** @jsx React.DOM */

var React = require("react/addons");
var TestUtils = React.addons.TestUtils;

var MainNav = require('../../../../client/app/components/main_nav');

// verifying karma-jasmine is working
describe("components/main_nav", function (){
var subject;

beforeEach(function () {
subject = TestUtils.renderIntoDocument(
<MainNav currentUri='/add_survey' />
);
});

describe('#render', function () {
it('has a "All Surveys" nav item', function () {
var item = TestUtils.scryRenderedDOMComponentsWithTag(subject, 'a')[0];
expect( item.props.href ).toBe( '/' );
expect( item.props.children ).toBe( 'All Surveys' );
});

it('has a "Add Surveys" nav item', function () {
var item = TestUtils.scryRenderedDOMComponentsWithTag(subject, 'a')[1];
expect( item.props.href ).toBe( '/add_survey' );
expect( item.props.children ).toBe( 'Add Survey' );
});

describe('when currentUri is "/"', function () {
beforeEach(function () {
subject.setProps({ currentUri: '/' });
});

it('marks "All Surveys" as current', function () {
var currentItem = TestUtils.findRenderedDOMComponentWithClass(
subject,
'current'
);

expect( currentItem.props.href ).toBe( '/' );
expect( currentItem.props.children ).toBe( 'All Surveys' );
});
});

describe('when currentUri is "/add_survey"', function () {
beforeEach(function () {
subject.setProps({ currentUri: '/add_survey' });
});

it('marks "Add Survey" as current', function () {
var currentItem = TestUtils.findRenderedDOMComponentWithClass(
subject,
'current'
);

expect( currentItem.props.href ).toBe( '/add_survey' );
expect( currentItem.props.children ).toBe( 'Add Survey' );
});
});
});
});