-
Notifications
You must be signed in to change notification settings - Fork 399
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
Param Navigation #3
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
{ | ||
"name": "reactboiler", | ||
"version": "1.0.0", | ||
"description": "", | ||
"scripts": { | ||
"postinstall": "tsd reinstall --overwrite --save", | ||
"start": "webpack-dev-server" | ||
}, | ||
"author": "Braulio Diez", | ||
"license": "ISC", | ||
"dependencies": { | ||
"bootstrap": "^3.3.5", | ||
"jquery": "^2.1.4", | ||
"lodash": "^4.5.1", | ||
"object-assign": "^4.0.1", | ||
"react": "~0.14.7", | ||
"react-dom": "^0.14.7", | ||
"react-router": "^2.0.0", | ||
"toastr": "^2.1.2" | ||
}, | ||
"devDependencies": { | ||
"css-loader": "^0.23.1", | ||
"extract-text-webpack-plugin": "^1.0.1", | ||
"file-loader": "^0.8.5", | ||
"html-webpack-plugin": "^2.9.0", | ||
"style-loader": "^0.13.0", | ||
"ts-loader": "^0.8.1", | ||
"typescript": "~1.7.5", | ||
"url-loader": "^0.5.7", | ||
"webpack": "^1.12.13", | ||
"webpack-dev-server": "~1.10.1" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import MemberEntity from './memberEntity' | ||
import MembersMockData from './memberMockData' | ||
import * as _ from 'lodash' | ||
// Sync mock data API, inspired from: | ||
// https://gist.github.com/coryhouse/fd6232f95f9d601158e4 | ||
export default class MemberAPI { | ||
private _idSeed : number; | ||
|
||
public constructor() { | ||
this._idSeed = 20; | ||
} | ||
|
||
//This would be performed on the server in a real app. Just stubbing in. | ||
private _clone (item) { | ||
return JSON.parse(JSON.stringify(item)); //return cloned copy so that the item is passed by value instead of by reference | ||
}; | ||
|
||
//This would be performed on the server in a real app. Just stubbing in. | ||
_generateId() : number { | ||
return this._idSeed++; | ||
}; | ||
|
||
|
||
// Just return a copy of the mock data | ||
getAllMembers() : Array<MemberEntity> { | ||
return this._clone(MembersMockData); | ||
} | ||
|
||
getMemberById(id : number) : MemberEntity { | ||
var member = _.find(MembersMockData, {id: id}); | ||
return this._clone(member); | ||
} | ||
|
||
saveAuthor(member: MemberEntity) { | ||
//pretend an ajax call to web api is made here | ||
console.log('Pretend this just saved the author to the DB via AJAX call...'); | ||
|
||
if (member.id != -1) { | ||
var existingAuthorIndex = _.indexOf(MembersMockData, _.find(MembersMockData, {id: member.id})); | ||
MembersMockData.splice(existingAuthorIndex, 1, member); | ||
} else { | ||
//Just simulating creation here. | ||
//The server would generate ids for new authors in a real app. | ||
//Cloning so copy returned is passed by value rather than by reference. | ||
member.id = this._generateId(); | ||
MembersMockData.push(this._clone(member)); | ||
} | ||
|
||
return member; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
|
||
export default class MemberEntity { | ||
id: number; | ||
login: string; | ||
avatar_url: string; | ||
|
||
constructor() { | ||
this.id = -1; | ||
this.login = ""; | ||
this.avatar_url = ""; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import MemberEntity from './memberEntity' | ||
|
||
var MembersMockData : Array<MemberEntity> = | ||
[ | ||
{ | ||
id: 1457912, | ||
login: "brauliodiez", | ||
avatar_url: "https://avatars.githubusercontent.com/u/1457912?v=3" | ||
}, | ||
{ | ||
id: 4374977, | ||
login: "Nasdan", | ||
avatar_url: "https://avatars.githubusercontent.com/u/4374977?v=3" | ||
} | ||
]; | ||
|
||
export default MembersMockData; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import * as React from 'react'; | ||
import header from '../common/header' | ||
import {Link} from 'react-router'; | ||
|
||
interface Props { | ||
} | ||
|
||
// Nice tsx guide: https://github.com/Microsoft/TypeScript/wiki/JSX | ||
export default class About extends React.Component<Props, {}> { | ||
public render() { | ||
return ( | ||
<div className="row"> | ||
<h2> About Page</h2> | ||
<Link to="/members">Members</Link> | ||
</div> | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import * as React from 'react'; | ||
import * as ReactDOM from 'react-dom'; | ||
import { Router, Route, IndexRoute, Link, IndexLink, browserHistory, hashHistory } from 'react-router' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unneeded import There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
|
||
import Header from './common/header' | ||
import aboutPage from './about/aboutPage'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unneeded import There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
import membersPage from './members/membersPage'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unneeded import There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
|
||
|
||
interface Props { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can extends from React.Props that it has children, key and ref properties
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. cool ! |
||
children : any; | ||
} | ||
|
||
// Nice tsx guide: https://github.com/Microsoft/TypeScript/wiki/JSX | ||
export default class App extends React.Component<Props, {}> { | ||
public render() { | ||
return ( | ||
<div className="container-fluid"> | ||
<Header/> | ||
{this.props.children} | ||
</div> | ||
|
||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import * as React from 'react'; | ||
import {Link} from 'react-router'; | ||
|
||
interface Props { | ||
} | ||
|
||
// Nice tsx guide: https://github.com/Microsoft/TypeScript/wiki/JSX | ||
export default class Header extends React.Component<Props, {}> { | ||
public render() { | ||
return ( | ||
<div className="row"> | ||
<nav className="navbar navbar-default"> | ||
<div className="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> | ||
<ul className="nav navbar-nav"> | ||
<li><Link to="/about">About</Link></li> | ||
<li><Link to="/members">Members</Link></li> | ||
</ul> | ||
</div> | ||
</nav> | ||
</div> | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import * as React from 'react'; | ||
|
||
interface Props { | ||
name : string; | ||
label : string; | ||
onChange : any; | ||
placeholder? : string; | ||
value: string; | ||
error : string; | ||
} | ||
|
||
// This components just contains a wrapper to avoid adding repetitive | ||
// code to each input (label indicating error, onChange callback, ...) | ||
// this is a port to typescript from Cory House sample | ||
// for more advanced scenario, rather check | ||
// https://github.com/christianalfoni/formsy-react | ||
export default class Input extends React.Component<Props, {}> { | ||
constructor(props : Props){ | ||
super(props); | ||
} | ||
|
||
|
||
public render() { | ||
var wrapperClass : string = 'form-group'; | ||
if (this.props.error && this.props.error.length > 0) { | ||
wrapperClass += " " + 'has-error'; | ||
} | ||
return ( | ||
<div className={wrapperClass}> | ||
<label htmlFor={this.props.name}>{this.props.label}</label> | ||
<div className="field"> | ||
<input type="text" | ||
name={this.props.name} | ||
className="form-control" | ||
placeholder={this.props.placeholder} | ||
ref={this.props.name} | ||
value={this.props.value} | ||
onChange={this.props.onChange} /> | ||
<div className="input">{this.props.error}</div> | ||
</div> | ||
</div> | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import * as React from 'react'; | ||
import MemberEntity from './../../api/memberEntity' | ||
|
||
import Input from './../common/textInput' | ||
|
||
interface Props extends React.Props<memberForm> { | ||
member : MemberEntity | ||
onChange : (event:any) => any; | ||
onSave : (event:any) => any; | ||
errors: any; | ||
} | ||
|
||
interface State { | ||
} | ||
|
||
export default class memberForm extends React.Component<Props, State> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. rename to MemberForm There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
constructor(props : Props){ | ||
super(props); | ||
} | ||
|
||
|
||
public render() { | ||
return ( | ||
<form> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indentation There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
<h1> Manage member </h1> | ||
|
||
<Input | ||
name="login" | ||
label="Login" | ||
value={this.props.member.login} | ||
onChange={this.props.onChange} | ||
error={this.props.errors.login} /> | ||
|
||
<Input | ||
name="avatar_url" | ||
label="Avatar Url" | ||
value={this.props.member.avatar_url} | ||
onChange={this.props.onChange} | ||
error={this.props.errors.avatar_rul} /> | ||
|
||
<input type="submit" value="Save" className="btn btn-default" onClick={this.props.onSave} /> | ||
</form> | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import * as React from 'react'; | ||
import { hashHistory } from 'react-router' | ||
import * as toastr from 'toastr'; | ||
import MemberEntity from './../../api/memberEntity' | ||
import MemberForm from './memberForm'; | ||
import MemberAPI from '../../api/memberAPI'; | ||
//import * as ObjectAssign from 'object-assign'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove comment There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
import objectAssign = require('object-assign'); | ||
|
||
interface Props extends React.Props<memberPage> { | ||
params : any | ||
} | ||
|
||
interface State { | ||
member : MemberEntity | ||
,errors: any | ||
,dirty : boolean | ||
} | ||
|
||
// Nice tsx guide: https://github.com/Microsoft/TypeScript/wiki/JSX | ||
export default class memberPage extends React.Component<Props, State> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. rename to MemberPage There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
|
||
constructor(props : Props){ | ||
super(props); | ||
// set initial state | ||
this.state = { | ||
member: new MemberEntity() | ||
,errors : {} | ||
,dirty : false | ||
}; | ||
} | ||
|
||
componentWillMount() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. componentWillMount() or componentDidMount()?
https://facebook.github.io/react/docs/component-specs.html I think the key is in
|
||
var memberId = this.props.params.id; | ||
|
||
if(memberId) { | ||
var memberAPI : MemberAPI = new MemberAPI(); | ||
var memberIdNumber : number = parseInt(memberId); | ||
var newState : State = objectAssign({}, this.state, {dirty: false, member: memberAPI.getMemberById(memberIdNumber)}); | ||
return this.setState(newState); | ||
|
||
} | ||
} | ||
|
||
// on any update on the form this function will be called | ||
setMemberState(event) { | ||
// https://www.npmjs.com/package/object-assign | ||
//var newState : State = objectAssign({}, this.state, {dirty: true}); | ||
//this.setState(newState); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove comments There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
|
||
var field = event.target.name; | ||
var value = event.target.value; | ||
this.state.member[field] = value; | ||
|
||
var newState : State = objectAssign({}, this.state, {dirty: true, member: this.state.member}); | ||
return this.setState(newState); | ||
} | ||
|
||
// We could extract all this logic to a separate class and add | ||
// unit test cases, on the other hand we could implement some | ||
// method to just check the current field that is being changed | ||
// validity | ||
memberFormIsValid() { | ||
var formIsValid = true; | ||
this.state.errors = {}; //clear any previous errors. | ||
|
||
// TODO: Pending extract this to class and add unit testing | ||
|
||
if (this.state.member.login.length < 3) { | ||
this.state.errors.login = 'Login must be at least 3 characters.'; | ||
formIsValid = false; | ||
} | ||
|
||
// TODO: Pending adding url validation on avatar, use this simple lib | ||
// https://github.com/chriso/validator.js | ||
var newState : State = objectAssign({}, this.state, {errors: this.state.errors}); | ||
this.setState(newState); | ||
|
||
return formIsValid; | ||
} | ||
|
||
public saveMember(event) { | ||
event.preventDefault(); | ||
|
||
if(!this.memberFormIsValid()) { | ||
return; | ||
} | ||
|
||
var memberAPI : MemberAPI = new MemberAPI(); | ||
memberAPI.saveAuthor(this.state.member); | ||
|
||
var newState : State = objectAssign({}, this.state, {dirty: true}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why you have to set dirty? |
||
this.setState(newState); | ||
|
||
toastr.success('Author saved.'); | ||
|
||
// using hashHistory, TODO: proper configure browserHistory on app and here | ||
hashHistory.push('/members') | ||
|
||
} | ||
|
||
public render() { | ||
return ( | ||
<MemberForm | ||
member={this.state.member} | ||
errors={this.state.errors} | ||
onChange={this.setMemberState.bind(this)} | ||
onSave={this.saveMember.bind(this)} | ||
/> | ||
); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unneeded import
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done