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

Param Navigation #3

Merged
merged 2 commits into from
Mar 3, 2016
Merged
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
33 changes: 33 additions & 0 deletions 08 ParamNavigation/package.json
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"
}
}
52 changes: 52 additions & 0 deletions 08 ParamNavigation/src/api/memberAPI.ts
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;
}

}
12 changes: 12 additions & 0 deletions 08 ParamNavigation/src/api/memberEntity.ts
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 = "";
}
}
17 changes: 17 additions & 0 deletions 08 ParamNavigation/src/api/memberMockData.ts
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;
18 changes: 18 additions & 0 deletions 08 ParamNavigation/src/components/about/aboutPage.tsx
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>
);
}
}
25 changes: 25 additions & 0 deletions 08 ParamNavigation/src/components/app.tsx
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';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unneeded import

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

import { Router, Route, IndexRoute, Link, IndexLink, browserHistory, hashHistory } from 'react-router'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unneeded import

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


import Header from './common/header'
import aboutPage from './about/aboutPage';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unneeded import

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

import membersPage from './members/membersPage';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unneeded import

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done



interface Props {
Copy link
Member

Choose a reason for hiding this comment

The 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

interface Props extends React.Props<App>{

}

Copy link
Member Author

Choose a reason for hiding this comment

The 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>

);
}
}
23 changes: 23 additions & 0 deletions 08 ParamNavigation/src/components/common/header.tsx
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>
);
}
}
44 changes: 44 additions & 0 deletions 08 ParamNavigation/src/components/common/textInput.tsx
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>
);
}
}
45 changes: 45 additions & 0 deletions 08 ParamNavigation/src/components/member/memberForm.tsx
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> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename to MemberForm

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

constructor(props : Props){
super(props);
}


public render() {
return (
<form>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation

Copy link
Member Author

Choose a reason for hiding this comment

The 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>
);
}
}
112 changes: 112 additions & 0 deletions 08 ParamNavigation/src/components/member/memberPage.tsx
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';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove comment

Copy link
Member Author

Choose a reason for hiding this comment

The 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> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename to MemberPage

Copy link
Member Author

Choose a reason for hiding this comment

The 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() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

componentWillMount() or componentDidMount()?

Mounting: componentWillMount
void componentWillMount()
Invoked once, both on the client and server, immediately before the initial rendering occurs. If you call setState within this method, render() will see the updated state and will be executed only once despite the state change.

Mounting: componentDidMount #
void componentDidMount()
Invoked once, only on the client (not on the server), immediately after the initial rendering occurs. At this point in the lifecycle, you can access any refs to your children (e.g., to access the underlying DOM representation). The componentDidMount() method of child components is invoked before that of parent components.

If you want to integrate with other JavaScript frameworks, set timers using setTimeout or setInterval, or send AJAX requests, perform those operations in this method.

https://facebook.github.io/react/docs/component-specs.html

I think the key is in

or send AJAX requests

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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove comments

Copy link
Member Author

Choose a reason for hiding this comment

The 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});
Copy link
Member

Choose a reason for hiding this comment

The 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)}
/>
);
}
}
Loading