// generates a new Universally unique identify (UUID) // the UUID is used to identify each of the tasks public static uuid() : string { /*jshint bitwise:false */ var i, random; var uuid = '';
for (i = 0; i < 32; i++) { random = Math.random() * 16 | 0; if (i === 8 || i === 12 || i === 16 || i === 20) { uuid += '-'; } uuid += (i === 12 ? 4 : (i === 16 ? (random & 3 | 8) : random)) .toString(16); }
return uuid; }
// adds 's' to the end of a given world when count > 1 public static pluralize(count, word) { return count === 1 ? word : word + 's'; }
// stores data using the localStorage API public static store(namespace, data?) { if (data) { return localStorage.setItem(namespace, JSON.stringify(data)); }
var store = localStorage.getItem(namespace); return (store && JSON.parse(store)) || []; }
// just a helper for inheritance public static extend(...objs : any[]) : any { var newObj = {}; for (var i = 0; i < objs.length; i++) { var obj = objs[i]; for (var key in obj) { if (obj.hasOwnProperty(key)) { newObj[key] = obj[key]; } } } return newObj; }
public toggleAll(checked) { // Note: it's usually better to use immutable // data structures since they're easier to // reason about and React works very // well with them. That's why we use // map() and filter() everywhere instead of // mutating the array or todo items themselves. this.todos = this.todos.map<ITodo>((todo : ITodo) => { return app.miscelanious.Utils.extend( {}, todo, {completed: checked} ); });
this.inform(); }
public toggle(todoToToggle) { this.todos = this.todos.map<ITodo>((todo : ITodo) => { return todo !== todoToToggle ? todo : app.miscelanious.Utils.extend( {}, todo, {completed: !todo.completed} ); });
export class TodoItem extends React.Component<ITodoItemProps, ITodoItemState> {
constructor(props : ITodoItemProps){ super(props); // set initial state this.state = { editText: this.props.todo.title }; }
public handleSubmit(event) { var val = this.state.editText.trim(); if (val) { this.props.onSave(val); this.setState({editText: val}); } else { this.props.onDestroy(); } }
public handleEdit() { this.props.onEdit(); this.setState({editText: this.props.todo.title}); }
public handleKeyDown(event) { if (event.which === app.constants.ESCAPE_KEY) { this.setState({editText: this.props.todo.title}); this.props.onCancel(event); } else if (event.which === app.constants.ENTER_KEY) { this.handleSubmit(event); } }
public handleChange(event) { this.setState({editText: event.target.value}); }
// This is a completely optional performance enhancement // that you can implement on any React component. If you // were to delete this method the app would still work // correctly (and still be very performant!), we just use it // as an example of how little code it takes to get an order // of magnitude performance improvement. public shouldComponentUpdate(nextProps, nextState) { return ( nextProps.todo !== this.props.todo || nextProps.editing !== this.props.editing || nextState.editText !== this.state.editText ); }
// Safely manipulate the DOM after updating the state // when invoking this.props.onEdit() in the handleEdit // method above. public componentDidUpdate(prevProps) { if (!prevProps.editing && this.props.editing) { var node = React.findDOMNode<HTMLInputElement>(this.refs["editField"]); node.focus(); node.setSelectionRange(node.value.length, node.value.length); } }
// We should have installed a type declaration file but // for the director npm package but it is not available // so we will use this declaration to avoid compilation // errors for now. declare var Router : any;
var TodoModel = app.models.TodoModel; var TodoFooter = app.components.TodoFooter; var TodoItem = app.components.TodoItem;
namespace app.components {
export class TodoApp extends React.Component<IAppProps, IAppState> {
public componentDidMount() { var setState = this.setState; // we will configure the Router here // our router is provided by the // director npm module // the router observes changes in the URL and // triggers some component's event accordingly var router = Router({ '/': setState.bind(this, {nowShowing: app.constants.ALL_TODOS}), '/active': setState.bind(this, {nowShowing: app.constants.ACTIVE_TODOS}), '/completed': setState.bind(this, {nowShowing: app.constants.COMPLETED_TODOS}) }); router.init('/'); }
public handleNewTodoKeyDown(event) { if (event.keyCode !== app.constants.ENTER_KEY) { return; }
event.preventDefault();
var val = React.findDOMNode<HTMLInputElement>(this.refs["newField"]).value.trim();
if (val) { this.props.model.addTodo(val); React.findDOMNode<HTMLInputElement>(this.refs["newField"]).value = ''; } }
public toggleAll(event) { var checked = event.target.checked; this.props.model.toggleAll(checked); }
public toggle(todoToToggle) { this.props.model.toggle(todoToToggle); }
public destroy(todo) { this.props.model.destroy(todo); }
public edit(todo) { this.setState({editing: todo.id}); }
public save(todoToSave, text) { this.props.model.save(todoToSave, text); this.setState({editing: null}); }
public cancel() { this.setState({editing: null}); }
public clearCompleted() { this.props.model.clearCompleted(); }
// the JSX syntax is quite intuitive but check out // https://facebook.github.io/react/docs/jsx-in-depth.html // if you need additional help public render() { var footer; var main; var todos = this.props.model.todos;
var shownTodos = todos.filter(function (todo) { switch (this.state.nowShowing) { case app.constants.ACTIVE_TODOS: return !todo.completed; case app.constants.COMPLETED_TODOS: return todo.completed; default: return true; } }, this);