Merge branch 'master' of github.com:kenotron/bootcamp

This commit is contained in:
Ken
2019-02-22 14:12:31 -08:00
91 changed files with 1055 additions and 160 deletions

View File

@@ -5,7 +5,7 @@
<h1>todos</h1>
<input class="textfield" placeholder="add todo" /><button class="button add">Add</button>
<div class="filter">
<button class="active">all</button>
<button class="selected">all</button>
<button>active</button>
<button>completed</button>
</div>

View File

@@ -1,5 +1,5 @@
body {
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
width: 400px;
margin: 20px auto;
}
@@ -28,7 +28,7 @@ h1 {
background: transparent;
border: none;
}
.filter .active {
.filter .selected {
border-bottom: 2px solid blue;
}

View File

@@ -8,7 +8,7 @@
Add
</button>
<div class="filter">
<button onclick="filter('all', this)" class="active">all</button>
<button onclick="filter('all', this)" class="selected">all</button>
<button onclick="filter('active', this)">active</button>
<button onclick="filter('completed', this)">completed</button>
</div>

View File

@@ -1,5 +1,5 @@
body {
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
width: 400px;
margin: 20px auto;
}
@@ -28,7 +28,7 @@ h1 {
background: transparent;
border: none;
}
.filter .active {
.filter .selected {
border-bottom: 2px solid blue;
}

View File

@@ -8,7 +8,7 @@ export class TodoHeader extends React.Component {
<input className="textfield" placeholder="add todo" />
<button className="button add">Add</button>
<div className="filter">
<button className="active">all</button>
<button className="completed">all</button>
<button>active</button>
<button>completed</button>
</div>

View File

@@ -1,5 +1,5 @@
body {
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
width: 400px;
margin: 20px auto;
}
@@ -28,7 +28,7 @@ h1 {
background: transparent;
border: none;
}
.filter .active {
.filter .selected {
border-bottom: 2px solid blue;
}

View File

@@ -9,9 +9,9 @@ export class TodoHeader extends React.Component<any, any> {
<input className="textfield" placeholder="add todo" />
<button className="button add">Add</button>
<div className="filter">
<button className={filter == 'all' ? 'active' : ''}>all</button>
<button className={filter == 'active' ? 'active' : ''}>active</button>
<button className={filter == 'completed' ? 'active' : ''}>completed</button>
<button className={filter == 'all' ? 'completed' : ''}>all</button>
<button className={filter == 'active' ? 'completed' : ''}>active</button>
<button className={filter == 'completed' ? 'completed' : ''}>completed</button>
</div>
</div>
);

View File

@@ -1,5 +1,5 @@
body {
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
width: 400px;
margin: 20px auto;
}
@@ -28,7 +28,7 @@ h1 {
background: transparent;
border: none;
}
.filter .active {
.filter .selected {
border-bottom: 2px solid blue;
}

View File

@@ -23,13 +23,13 @@ export class TodoHeader extends React.Component<TodoHeaderProps, any> {
Add
</button>
<div className="filter">
<button onClick={() => setFilter('all')} className={filter == 'all' ? 'active' : ''}>
<button onClick={() => setFilter('all')} className={filter == 'all' ? 'completed' : ''}>
all
</button>
<button onClick={() => setFilter('active')} className={filter == 'active' ? 'active' : ''}>
<button onClick={() => setFilter('active')} className={filter == 'active' ? 'completed' : ''}>
active
</button>
<button onClick={() => setFilter('completed')} className={filter == 'completed' ? 'active' : ''}>
<button onClick={() => setFilter('completed')} className={filter == 'completed' ? 'completed' : ''}>
completed
</button>
</div>

View File

@@ -1,5 +1,5 @@
body {
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
width: 400px;
margin: 20px auto;
}
@@ -28,7 +28,7 @@ h1 {
background: transparent;
border: none;
}
.filter .active {
.filter .selected {
border-bottom: 2px solid blue;
}

View File

@@ -1,13 +1,95 @@
## HTML and CSS
Every website, application, form or component starts with markup. The HTML will change over time as you develop, but a first pass helps you understand the UI you are trying to build.
## Demo
Write html tag down to ul > li, footer
add CSS link
Add active styles, describe specificity
In this exercise we will scaffold out some HTML for out Todo app, and add some basic styling to it.
### Page scaffold
```html
<!DOCTYPE html>
<html>
<head></head>
<body></body>
</html>
```
1. The DOCTYPE tells the browser that this file is written in modern HTML.
2. The HTML tag wraps the entire page, and is the page root. Nothing is placed outside of those tags. Attributes can be set on HTML
3. Head will contain all of the page's meta data, in this case a link to our css file
4. Body is where all of the visible content should be placed.
### Content Sectioning
As we saw in the previous demo, HTML elements can be used to describe different content sections of the applications. Let's add `header`, `main` and `footer`, as well as populate the header with an `h1`, addTodo div, and `nav` for our filters.
```html
<body>
<header>
<h1></h1>
<div class="addTodo"></div>
<nav></nav>
<header>
<main class="filter"></main>
<footer></footer>
</body>
```
> Note that a `form` element would have been more semantic than a `div`, but we aren't using this form to POST to a server, so for this example a div is easier to use.
### Updating the header
The header of our page is where most of the action is going to happen. First, lets give our page a title, adding 'TODO' to our `h1`. Then we can add an input and button to our `addTodo` div.
```html
<input class="textfield" placeholder="add todo" /> <button class="submit">Add</button>
```
#### Navigation
The navigation for this application is quite simple. We want users to be able to switch between three filtered states. Since we need to track which state is currently selected, we'll add that as a class on the first item.
```html
<nav class="filter">
<button class="selected">all</button>
<button>active</button>
<button>completed</button>
</nav>
```
### Adding styles
Now that we've got the top of our application scaffolded, we can add some our styles in the head.
```html
<head>
<link rel="stylesheet" href="./style.css" />
</head>
```
### Updating styles
It looks like the selected button isn't getting any special styles. Let's dig in and see why that is.
Open up the browser inspector and target our 'all' button. You'll notice that the blue style is present on the list, but it is being overriden by the `border: none` above it. This is a situation where specificity is winning out over the cascade.
> **Cascade** states that if two selectors are equal, the lowest one on the page wins
> **Specificity** states that regardless of cascade, the selector with the highest specificity wins
To fix this problem we need to either reduce the specificity of our button styles, or increase the specificity of the selected style. In this situation we will add `.filter` in front of the `.selected` selector, because the selected style only applies to the filter anyway.
## Exercise
Update list item to match (already styled)
duplicate 3 more times
Add and style footer content
1. Add an unordered list with class `todos` to the main section
2. Add 4 list items with class `todo` inside of that list with the following content
`<label><input type="checkbox" /> <span class="title"> Todo </span> </label>`
3. Add a span and a button to your footer
4. Span content should be `4 items left` and button should say `Clear Completed` and have a class of `submit`
5. Go into the CSS file and add `display: flex` to the footer. Also add `flex-grow:1` to the span inside of the footer
> Hint: Look back at the CSS demo to see the various ways you can use selectors to target existing HTML
> There are many strategies for creating and organizing class names in a large application. This lesson is focused on using CSS selectors, not the optimized way to scale your CSS.

View File

@@ -1 +0,0 @@
<!DOCTYPE html>

View File

@@ -31,6 +31,10 @@ h1 {
border: none;
}
.selected {
border-bottom: 2px solid blue;
}
.todos {
list-style: none;
padding: 0;

View File

@@ -11,15 +11,12 @@
<button class="submit">Add</button>
</div>
<nav class="filter">
<button class="active">all</button>
<button class="selected">all</button>
<button>active</button>
<button>completed</button>
</nav>
</header>
<main>
<ul class="todos">
<li>Todo 1</li>
</ul>
</main>
<main></main>
<footer></footer>
</body>
</html>

View File

@@ -31,7 +31,7 @@ h1 {
border: none;
}
.filter .active {
.filter .selected {
border-bottom: 2px solid blue;
}

View File

@@ -11,7 +11,7 @@
<button class="submit">Add</button>
</div>
<nav class="filter">
<button class="active">all</button>
<button class="selected">all</button>
<button>active</button>
<button>completed</button>
</nav>

View File

@@ -31,7 +31,7 @@ h1 {
border: none;
}
.filter .active {
.filter .selected {
border-bottom: 2px solid blue;
}

View File

@@ -1,11 +1,25 @@
Demo
## Javascript Demo
already done
Now that we a UI that looks like a todo app, we need to add functionality to make it **function** like a todo app. In this example we are going to use raw Javascript explicitly modify our application as we interact with it. This will be in stark contrast to the implicit approach we will take when we do this with React in the next exercise.
- addTodo
- filter
- filter()
- getTodoText
> Keep an eye on how often user actions directly modify the HTML on the page. You'll see this number drop to zero when we start using React.
### Demo
This demo starts off with a few elements already in place. Let's walk through what's already here.
- **getTodoText()** - This is a quick helper function that returns the value inside of our textfield. Notice how some functions return values and how you can set that return to a variable. Other functions return nothing, but rather have side effects.
- **addTodo()** - This is the primary logic of our todo app. Here's how the lines break down.
1. `todo` is set to equal the first todo item
2. `newTodo` is a clone of todo. Passing true means it is a deep clone, so we get the todo's children as well. Cloning does not duplicate the DOM node. We'll need to insert it in step 4
3. We set the innerText of the `<span class='title'>` to the value returned from getTodoText
> Note that if we left off the `()` we'd actully be assiging innerText to the 'function' instead of the function return
4. Insert our new todo into the todo's parent (the `ul`), before our reference todo. [insertBefore](https://developer.mozilla.org/en-US/docs/Web/API/Node/insertBefore)
- **filter()** - This function takes in a `filterName` string, and a `button` which is a reference to the clicked button.
1. Remove any `selected` class names
2. Add `selected` to the clicked button
3. Get all of the todos with [querySelectAll](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll), and then loop through them.
4. Set the `hidden` property of each todo based on the filter/state combination
Walk through 'addTodo'
attach addTodo to button

View File

@@ -8,10 +8,10 @@
<h1>todos</h1>
<div class="addTodo">
<input class="textfield" placeholder="add todo" />
<button class="submit add">Add</button>
<button class="submit">Add</button>
</div>
<nav class="filter">
<button class="active">all</button>
<button class="selected">all</button>
<button>active</button>
<button>completed</button>
</nav>
@@ -52,16 +52,16 @@
// updateRemaining();
}
function filter(scope, button) {
document.querySelector('.active').classList.remove('active');
button.classList.add('active');
function filter(filterName, button) {
document.querySelector('.selected').classList.remove('selected');
button.classList.add('selected');
for (let todo of document.querySelectorAll('.todo')) {
const checked = todo.querySelector('input').checked == true;
if (scope == 'all') {
if (filterName == 'all') {
todo.hidden = false;
} else if (scope == 'active') {
} else if (filterName == 'active') {
todo.hidden = checked;
} else if (scope == 'completed') {
} else if (filterName == 'completed') {
todo.hidden = !checked;
}
}

View File

@@ -31,7 +31,7 @@ h1 {
border: none;
}
.filter .active {
.filter .selected {
border-bottom: 2px solid blue;
}

View File

@@ -8,10 +8,10 @@
<h1>todos</h1>
<div class="addTodo">
<input class="textfield" placeholder="add todo" />
<button onclick="addTodo()" class="submit add">Add</button>
<button onclick="addTodo()" class="submit">Add</button>
</div>
<nav class="filter">
<button class="active">all</button>
<button class="selected">all</button>
<button>active</button>
<button>completed</button>
</nav>
@@ -64,16 +64,16 @@
// clearCompleted
function filter(scope, button) {
document.querySelector('.active').classList.remove('active');
button.classList.add('active');
function filter(filterName, button) {
document.querySelector('.selected').classList.remove('selected');
button.classList.add('selected');
for (let todo of document.querySelectorAll('.todo')) {
const checked = todo.querySelector('input').checked == true;
if (scope == 'all') {
if (filterName == 'all') {
todo.hidden = false;
} else if (scope == 'active') {
} else if (filterName == 'active') {
todo.hidden = checked;
} else if (scope == 'completed') {
} else if (filterName == 'completed') {
todo.hidden = !checked;
}
}

View File

@@ -31,7 +31,7 @@ h1 {
border: none;
}
.filter .active {
.filter .selected {
border-bottom: 2px solid blue;
}

View File

@@ -8,10 +8,10 @@
<h1>todos</h1>
<div class="addTodo">
<input class="textfield" placeholder="add todo" />
<button onclick="addTodo()" class="submit add">Add</button>
<button onclick="addTodo()" class="submit">Add</button>
</div>
<nav class="filter">
<button onclick="filter('all', this)" class="active">all</button>
<button onclick="filter('all', this)" class="selected">all</button>
<button onclick="filter('active', this)">active</button>
<button onclick="filter('completed', this)">completed</button>
</nav>
@@ -72,16 +72,16 @@
updateRemaining();
}
function filter(scope, button) {
document.querySelector('.active').classList.remove('active');
button.classList.add('active');
function filter(filterName, button) {
document.querySelector('.selected').classList.remove('selected');
button.classList.add('selected');
for (let todo of document.querySelectorAll('.todo')) {
const checked = todo.querySelector('input').checked == true;
if (scope == 'all') {
if (filterName == 'all') {
todo.hidden = false;
} else if (scope == 'active') {
} else if (filterName == 'active') {
todo.hidden = checked;
} else if (scope == 'completed') {
} else if (filterName == 'completed') {
todo.hidden = !checked;
}
}

View File

@@ -31,7 +31,7 @@ h1 {
border: none;
}
.filter .active {
.filter .selected {
border-bottom: 2px solid blue;
}

View File

@@ -9,4 +9,4 @@ write index.html
demo 'hello world'
Write Counter Component with button
Demo Button, import into Component
Demo Button, import into Counter

View File

@@ -1,6 +0,0 @@
<!DOCTYPE html>
<html>
<body>
<div id="app"></div>
</body>
</html>

View File

@@ -1,14 +0,0 @@
import React from 'react';
import { Counter } from './components/Counter';
export class App extends React.Component {
render() {
return (
<div>
<h2>My App</h2>
<Counter text="Chickens" />
<Counter text="Ducks" />
</div>
);
}
}

View File

@@ -1,27 +1,11 @@
import React from 'react';
import { Button } from './Button';
export class Counter extends React.Component<{ text: string }, { counter: number }> {
constructor(props) {
super(props);
this.state = {
counter: 0
};
}
export class Counter extends React.Component<any, any> {
render() {
const { counter } = this.state;
const { text } = this.props;
return (
<div>
{text}: {counter}
<Button
onClick={() => {
this.setState({ counter: counter + 1 });
}}
>
Click
</Button>
</div>
);
}
}

View File

@@ -1,4 +0,0 @@
import React from "react";
import ReactDOM from "react-dom";
import { App } from "./App";
ReactDOM.render(<App />, document.getElementById("app"));

View File

@@ -31,7 +31,7 @@ h1 {
border: none;
}
.filter .active {
.filter .selected {
border-bottom: 2px solid blue;
}

View File

@@ -10,7 +10,7 @@ export class TodoHeader extends React.Component {
<button className="submit">Add</button>
</div>
<nav className="filter">
<button className="active">all</button>
<button className="completed">all</button>
<button>active</button>
<button>completed</button>
</nav>

View File

@@ -31,7 +31,7 @@ h1 {
border: none;
}
.filter .active {
.filter .selected {
border-bottom: 2px solid blue;
}

View File

@@ -10,7 +10,7 @@ export class TodoHeader extends React.Component {
<button className="submit">Add</button>
</div>
<nav className="filter">
<button className="active">all</button>
<button className="completed">all</button>
<button>active</button>
<button>completed</button>
</nav>

View File

@@ -1,4 +1,4 @@
import React from "react";
import ReactDOM from "react-dom";
import { TodoApp } from "./App";
ReactDOM.render(<TodoApp />, document.getElementById("app"));
import React from 'react';
import ReactDOM from 'react-dom';
import { TodoApp } from './TodoApp';
ReactDOM.render(<TodoApp />, document.getElementById('app'));

View File

@@ -31,7 +31,7 @@ h1 {
border: none;
}
.filter .active {
.filter .selected {
border-bottom: 2px solid blue;
}

View File

@@ -1,13 +1,20 @@
already done
itemCount filtering
## demo
demo
add state
add state to AppTodo
pass to header and list
add filter class stuff
controlled components (header) with consolelog
exercise
### header
- pull filter from props
- update buttons with classNames for active
- Add value/onChange to input
### TodoList
- write map to loop through items, pass key and todos
## exercise
update footer to include todos
add item count and item(s)
update ListItem, pull in props, use label/completed already passed in

9
step1-06/demo/index.html Normal file
View File

@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<link rel="stylesheet" href="./src/style.css" />
<body>
<div id="app"></div>
</body>
</html>

View File

@@ -0,0 +1,16 @@
import React from 'react';
import { TodoFooter } from './components/TodoFooter';
import { TodoHeader } from './components/TodoHeader';
import { TodoList } from './components/TodoList';
export class TodoApp extends React.Component {
render() {
return (
<div>
<TodoHeader />
<TodoList />
<TodoFooter />
</div>
);
}
}

View File

@@ -0,0 +1,11 @@
import React from 'react';
export const TodoFooter = (props: any) => {
const itemCount = Object.keys(props.todos).filter(id => !props.todos[id].completed).length;
return (
<footer>
<span>4 items left</span>
<button className="submit">Clear Completed</button>
</footer>
);
};

View File

@@ -0,0 +1,29 @@
import React from 'react';
export class TodoHeader extends React.Component<any, any> {
constructor(props) {
super(props);
this.state = { labelInput: '' };
}
render() {
return (
<header>
<h1>todos</h1>
<div className="addTodo">
<input className="textfield" placeholder="add todo" />
<button className="submit">Add</button>
</div>
<nav className="filter">
<button className="completed">all</button>
<button>active</button>
<button>completed</button>
</nav>
</header>
);
}
_onChange = evt => {
this.setState({ labelInput: evt.target.value });
};
}

View File

@@ -0,0 +1,20 @@
import React from 'react';
import { TodoListItem } from './TodoListItem';
export class TodoList extends React.Component<any, any> {
render() {
const { filter, todos } = this.props;
const filteredTodos = Object.keys(todos).filter(id => {
return filter === 'all' || (filter === 'completed' && todos[id].completed) || (filter === 'active' && !todos[id].completed);
});
return (
<ul className="todos">
<TodoListItem />
<TodoListItem />
<TodoListItem />
<TodoListItem />
</ul>
);
}
}

View File

@@ -0,0 +1,13 @@
import React from "react";
export class TodoListItem extends React.Component {
render() {
return (
<li className="todo">
<label>
<input type="checkbox" /> Todo 1
</label>
</li>
);
}
}

View File

@@ -31,7 +31,7 @@ h1 {
border: none;
}
.filter .active {
.filter .selected {
border-bottom: 2px solid blue;
}

View File

@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<link rel="stylesheet" href="./src/style.css" />
<body>
<div id="app"></div>
</body>
</html>

View File

@@ -0,0 +1,11 @@
import React from 'react';
export const TodoFooter = (props: any) => {
const itemCount = Object.keys(props.todos).filter(id => !props.todos[id].completed).length;
return (
<footer>
<span>4 items left</span>
<button className="submit">Clear Completed</button>
</footer>
);
};

View File

@@ -0,0 +1,30 @@
import React from 'react';
export class TodoHeader extends React.Component<any, any> {
constructor(props) {
super(props);
this.state = { labelInput: '' };
}
render() {
const { filter } = this.props;
return (
<header>
<h1>todos</h1>
<div className="addTodo">
<input value={this.state.labelInput} onChange={this._onChange} className="textfield" placeholder="add todo" />
<button className="submit">Add</button>
</div>
<nav className="filter">
<button className={filter == 'all' ? 'completed' : ''}>all</button>
<button className={filter == 'active' ? 'completed' : ''}>active</button>
<button className={filter == 'completed' ? 'completed' : ''}>completed</button>
</nav>
</header>
);
}
_onChange = evt => {
this.setState({ labelInput: evt.target.value });
};
}

View File

@@ -0,0 +1,13 @@
import React from 'react';
export class TodoListItem extends React.Component<any, any> {
render() {
return (
<li className="todo">
<label>
<input type="checkbox" /> Todo 1
</label>
</li>
);
}
}

View File

@@ -31,7 +31,7 @@ h1 {
border: none;
}
.filter .active {
.filter .selected {
border-bottom: 2px solid blue;
}

View File

@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<link rel="stylesheet" href="./src/style.css" />
<body>
<div id="app"></div>
</body>
</html>

View File

@@ -0,0 +1,41 @@
import React from 'react';
import { TodoFooter } from './components/TodoFooter';
import { TodoHeader } from './components/TodoHeader';
import { TodoList } from './components/TodoList';
export class TodoApp extends React.Component<any, any> {
constructor(props) {
super(props);
this.state = {
todos: {
'04': {
label: 'Todo 4',
completed: true
},
'03': {
label: 'Todo 3',
completed: false
},
'02': {
label: 'Todo 2',
completed: false
},
'01': {
label: 'Todo 1',
completed: false
}
},
filter: 'all'
};
}
render() {
const { filter, todos } = this.state;
return (
<div>
<TodoHeader filter={filter} />
<TodoList todos={todos} filter={filter} />
<TodoFooter todos={todos} />
</div>
);
}
}

View File

@@ -12,9 +12,9 @@ export class TodoHeader extends React.Component<any, any> {
<button className="submit">Add</button>
</div>
<nav className="filter">
<button className={filter == 'all' ? 'active' : ''}>all</button>
<button className={filter == 'active' ? 'active' : ''}>active</button>
<button className={filter == 'completed' ? 'active' : ''}>completed</button>
<button className={filter == 'all' ? 'completed' : ''}>all</button>
<button className={filter == 'active' ? 'completed' : ''}>active</button>
<button className={filter == 'completed' ? 'completed' : ''}>completed</button>
</nav>
</header>
);

View File

@@ -0,0 +1,19 @@
import React from 'react';
import { TodoListItem } from './TodoListItem';
export class TodoList extends React.Component<any, any> {
render() {
const { filter, todos } = this.props;
const filteredTodos = Object.keys(todos).filter(id => {
return filter === 'all' || (filter === 'completed' && todos[id].completed) || (filter === 'active' && !todos[id].completed);
});
return (
<ul className="todos">
{filteredTodos.map(id => (
<TodoListItem key={id} {...todos[id]} />
))}
</ul>
);
}
}

View File

@@ -0,0 +1,4 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { TodoApp } from './TodoApp';
ReactDOM.render(<TodoApp />, document.getElementById('app'));

View File

@@ -0,0 +1,49 @@
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
width: 400px;
margin: 20px auto;
}
h1 {
text-align: center;
}
.addTodo {
display: flex;
}
.textfield {
flex-grow: 1;
margin-right: 10px;
}
.submit {
border: none;
padding: 5px 10px;
}
.filter {
margin: 10px 0 0;
}
.filter button {
background: transparent;
border: none;
}
.filter .selected {
border-bottom: 2px solid blue;
}
.todos {
list-style: none;
padding: 0;
}
footer {
display: flex;
}
footer span {
flex-grow: 1;
}

View File

@@ -1,9 +1,15 @@
<!DOCTYPE html>
<html>
<link rel="stylesheet" href="./src/style.css" />
<body>
<div id="app"></div>
<head>
<link rel="stylesheet" href="../assets/shared.css" />
<link rel="stylesheet" href="https://static2.sharepointonline.com/files/fabric/office-ui-fabric-core/9.6.1/css/fabric.min.css" />
</head>
<body class="ms-Fabric">
<div class="Container">
<ul class="Tiles">
<li class="Tile"><a href="./demo/index.html" class="Tile-link">Demo Start</a></li>
<li class="Tile"><a href="./final/index.html" class="Tile-link">Final</a></li>
</ul>
</div>
</body>
</html>

View File

@@ -5,14 +5,33 @@ TodoApp methods
filteredTodos in List
demo
## app
Add Types to TodoApp
change 'filter' value
Types in List
pass complete to List - show types, change complete to boolean/, filter
change 'filter' state value to demonstrate
## List (open list next to app)
Demo TodoApp.types
Add Types in List
## App
Back to App, add complete={this.\_complete} to Todolist - show types, change complete to 'false', filter
## List
add complete, pass to item (prop drilling)
## List Item (move List Item into App window)
TodoListItemProps, extend, id, complete (possible abstraction)
Demo how you can't add random things to TodoListItem or this.props now
add complete to List item
add props, add complete to List item
## List
Demo how you can't add random things to TodoListItem or item's this.props now
exercise
@@ -20,4 +39,5 @@ Add types to footer
Add onClick to button
Add types to header
Add setFilter to filter buttons
add 'addTodo' to onAdd function
write onAdd function
place onAdd to submit button

7
step1-07/demo/index.html Normal file
View File

@@ -0,0 +1,7 @@
<!DOCTYPE html>
<html>
<link rel="stylesheet" href="./src/style.css" />
<body>
<div id="app"></div>
</body>
</html>

View File

@@ -0,0 +1,69 @@
import React from 'react';
import { TodoFooter } from './components/TodoFooter';
import { TodoHeader } from './components/TodoHeader';
import { TodoList } from './components/TodoList';
import { Todos, FilterTypes } from './TodoApp.types';
let index = 0;
export class TodoApp extends React.Component<any, any> {
constructor(props) {
super(props);
this.state = {
todos: {},
filter: 'all'
};
}
render() {
const { filter, todos } = this.state;
return (
<div>
<TodoHeader addTodo={this._addTodo} setFilter={this._setFilter} filter={filter} />
<TodoList complete={this._complete} todos={todos} filter={filter} />
<TodoFooter clear={this._clear} todos={todos} />
</div>
);
}
// business logic
private _addTodo = label => {
const { todos } = this.state;
const id = index++;
this.setState({
todos: { ...todos, [id]: { label, completed: false } }
});
};
private _complete = id => {
const newTodos = { ...this.state.todos };
newTodos[id].completed = !newTodos[id].completed;
this.setState({
todos: newTodos
});
};
private _clear = () => {
const { todos } = this.state;
const newTodos = {};
Object.keys(this.state.todos).forEach(id => {
if (!todos[id].completed) {
newTodos[id] = todos[id];
}
});
this.setState({
todos: newTodos
});
};
private _setFilter = filter => {
this.setState({
filter: filter
});
};
}

View File

@@ -0,0 +1,14 @@
import React from 'react';
import { Todos } from '../TodoApp.types';
export const TodoFooter = (props: any) => {
const itemCount = Object.keys(props.todos).filter(id => !props.todos[id].completed).length;
return (
<footer>
<span>
{itemCount} item{itemCount > 1 ? 's' : ''} left
</span>
<button className="submit">Clear Completed</button>
</footer>
);
};

View File

@@ -0,0 +1,31 @@
import React from 'react';
import { FilterTypes } from '../TodoApp.types';
export class TodoHeader extends React.Component<any, any> {
constructor(props) {
super(props);
this.state = { labelInput: '' };
}
render() {
const { filter } = this.props;
return (
<header>
<h1>todos</h1>
<div className="addTodo">
<input value={this.state.labelInput} onChange={this._onChange} className="textfield" placeholder="add todo" />
<button className="submit">Add</button>
</div>
<nav className="filter">
<button className={filter == 'all' ? 'completed' : ''}>all</button>
<button className={filter == 'active' ? 'completed' : ''}>active</button>
<button className={filter == 'completed' ? 'completed' : ''}>completed</button>
</nav>
</header>
);
}
_onChange = evt => {
this.setState({ labelInput: evt.target.value });
};
}

View File

@@ -0,0 +1,21 @@
import React from 'react';
import { TodoListItem } from './TodoListItem';
import { FilterTypes, Todos } from '../TodoApp.types';
export class TodoList extends React.Component<any, any> {
render() {
const { filter, todos, complete } = this.props;
const filteredTodos = Object.keys(todos).filter(id => {
return filter === 'all' || (filter === 'completed' && todos[id].completed) || (filter === 'active' && !todos[id].completed);
});
return (
<ul className="todos">
{filteredTodos.map(id => (
<TodoListItem key={id} id={id} complete={complete} {...todos[id]} />
))}
</ul>
);
}
}

View File

@@ -0,0 +1,16 @@
import React from 'react';
import { TodoItem } from '../TodoApp.types';
export class TodoListItem extends React.Component<any, any> {
render() {
const { label, completed } = this.props;
return (
<li className="todo">
<label>
<input type="checkbox" checked={completed} /> {label}
</label>
</li>
);
}
}

View File

@@ -0,0 +1,4 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { TodoApp } from './TodoApp';
ReactDOM.render(<TodoApp />, document.getElementById('app'));

View File

@@ -0,0 +1,49 @@
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
width: 400px;
margin: 20px auto;
}
h1 {
text-align: center;
}
.addTodo {
display: flex;
}
.textfield {
flex-grow: 1;
margin-right: 10px;
}
.submit {
border: none;
padding: 5px 10px;
}
.filter {
margin: 10px 0 0;
}
.filter button {
background: transparent;
border: none;
}
.filter .selected {
border-bottom: 2px solid blue;
}
.todos {
list-style: none;
padding: 0;
}
footer {
display: flex;
}
footer span {
flex-grow: 1;
}

View File

@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<link rel="stylesheet" href="./src/style.css" />
<body>
<div id="app"></div>
</body>
</html>

View File

@@ -0,0 +1,10 @@
export type FilterTypes = 'all' | 'active' | 'completed';
export interface TodoItem {
label: string;
completed: boolean;
}
export interface Todos {
[id: string]: TodoItem;
}

View File

@@ -0,0 +1,14 @@
import React from 'react';
import { Todos } from '../TodoApp.types';
export const TodoFooter = props => {
const itemCount = Object.keys(props.todos).filter(id => !props.todos[id].completed).length;
return (
<footer>
<span>
{itemCount} item{itemCount > 1 ? 's' : ''} left
</span>
<button className="submit">Clear Completed</button>
</footer>
);
};

View File

@@ -0,0 +1,31 @@
import React from 'react';
import { FilterTypes } from '../TodoApp.types';
export class TodoHeader extends React.Component<any, any> {
constructor(props) {
super(props);
this.state = { labelInput: '' };
}
render() {
const { filter } = this.props;
return (
<header>
<h1>todos</h1>
<div className="addTodo">
<input value={this.state.labelInput} onChange={this._onChange} className="textfield" placeholder="add todo" />
<button className="submit">Add</button>
</div>
<nav className="filter">
<button className={filter == 'all' ? 'completed' : ''}>all</button>
<button className={filter == 'active' ? 'completed' : ''}>active</button>
<button className={filter == 'completed' ? 'completed' : ''}>completed</button>
</nav>
</header>
);
}
_onChange = evt => {
this.setState({ labelInput: evt.target.value });
};
}

View File

@@ -0,0 +1,4 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { TodoApp } from './TodoApp';
ReactDOM.render(<TodoApp />, document.getElementById('app'));

View File

@@ -0,0 +1,49 @@
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
width: 400px;
margin: 20px auto;
}
h1 {
text-align: center;
}
.addTodo {
display: flex;
}
.textfield {
flex-grow: 1;
margin-right: 10px;
}
.submit {
border: none;
padding: 5px 10px;
}
.filter {
margin: 10px 0 0;
}
.filter button {
background: transparent;
border: none;
}
.filter .selected {
border-bottom: 2px solid blue;
}
.todos {
list-style: none;
padding: 0;
}
footer {
display: flex;
}
footer span {
flex-grow: 1;
}

View File

@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<link rel="stylesheet" href="./src/style.css" />
<body>
<div id="app"></div>
</body>
</html>

View File

@@ -0,0 +1,69 @@
import React from 'react';
import { TodoFooter } from './components/TodoFooter';
import { TodoHeader } from './components/TodoHeader';
import { TodoList } from './components/TodoList';
import { Todos, FilterTypes } from './TodoApp.types';
let index = 0;
export class TodoApp extends React.Component<any, { todos: Todos; filter: FilterTypes }> {
constructor(props) {
super(props);
this.state = {
todos: {},
filter: 'all'
};
}
render() {
const { filter, todos } = this.state;
return (
<div>
<TodoHeader addTodo={this._addTodo} setFilter={this._setFilter} filter={filter} />
<TodoList complete={this._complete} todos={todos} filter={filter} />
<TodoFooter clear={this._clear} todos={todos} />
</div>
);
}
// business logic
private _addTodo = label => {
const { todos } = this.state;
const id = index++;
this.setState({
todos: { ...todos, [id]: { label, completed: false } }
});
};
private _complete = id => {
const newTodos = { ...this.state.todos };
newTodos[id].completed = !newTodos[id].completed;
this.setState({
todos: newTodos
});
};
private _clear = () => {
const { todos } = this.state;
const newTodos = {};
Object.keys(this.state.todos).forEach(id => {
if (!todos[id].completed) {
newTodos[id] = todos[id];
}
});
this.setState({
todos: newTodos
});
};
private _setFilter = filter => {
this.setState({
filter: filter
});
};
}

View File

@@ -0,0 +1,10 @@
export type FilterTypes = 'all' | 'active' | 'completed';
export interface TodoItem {
label: string;
completed: boolean;
}
export interface Todos {
[id: string]: TodoItem;
}

View File

@@ -25,13 +25,13 @@ export class TodoHeader extends React.Component<TodoHeaderProps, any> {
</button>
</div>
<nav className="filter">
<button onClick={() => setFilter('all')} className={filter == 'all' ? 'active' : ''}>
<button onClick={() => setFilter('all')} className={filter == 'all' ? 'completed' : ''}>
all
</button>
<button onClick={() => setFilter('active')} className={filter == 'active' ? 'active' : ''}>
<button onClick={() => setFilter('active')} className={filter == 'active' ? 'completed' : ''}>
active
</button>
<button onClick={() => setFilter('completed')} className={filter == 'completed' ? 'active' : ''}>
<button onClick={() => setFilter('completed')} className={filter == 'completed' ? 'completed' : ''}>
completed
</button>
</nav>

View File

@@ -0,0 +1,27 @@
import React from 'react';
import { TodoListItem } from './TodoListItem';
import { FilterTypes, Todos } from '../TodoApp.types';
interface TodoListProps {
complete: (id: string) => void;
todos: Todos;
filter: FilterTypes;
}
export class TodoList extends React.Component<TodoListProps, any> {
render() {
const { filter, todos, complete } = this.props;
const filteredTodos = Object.keys(todos).filter(id => {
return filter === 'all' || (filter === 'completed' && todos[id].completed) || (filter === 'active' && !todos[id].completed);
});
return (
<ul className="todos">
{filteredTodos.map(id => (
<TodoListItem key={id} id={id} complete={complete} {...todos[id]} />
))}
</ul>
);
}
}

View File

@@ -0,0 +1,21 @@
import React from 'react';
import { TodoItem } from '../TodoApp.types';
interface TodoListItemProps extends TodoItem {
id: string;
complete: (id: string) => void;
}
export class TodoListItem extends React.Component<TodoListItemProps, any> {
render() {
const { label, completed, complete, id } = this.props;
return (
<li className="todo">
<label>
<input type="checkbox" checked={completed} onChange={() => complete(id)} /> {label}
</label>
</li>
);
}
}

View File

@@ -0,0 +1,4 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { TodoApp } from './TodoApp';
ReactDOM.render(<TodoApp />, document.getElementById('app'));

View File

@@ -0,0 +1,49 @@
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
width: 400px;
margin: 20px auto;
}
h1 {
text-align: center;
}
.addTodo {
display: flex;
}
.textfield {
flex-grow: 1;
margin-right: 10px;
}
.submit {
border: none;
padding: 5px 10px;
}
.filter {
margin: 10px 0 0;
}
.filter button {
background: transparent;
border: none;
}
.filter .selected {
border-bottom: 2px solid blue;
}
.todos {
list-style: none;
padding: 0;
}
footer {
display: flex;
}
footer span {
flex-grow: 1;
}

View File

@@ -1,9 +1,15 @@
<!DOCTYPE html>
<html>
<link rel="stylesheet" href="./src/style.css" />
<body>
<div id="app"></div>
<head>
<link rel="stylesheet" href="../assets/shared.css" />
<link rel="stylesheet" href="https://static2.sharepointonline.com/files/fabric/office-ui-fabric-core/9.6.1/css/fabric.min.css" />
</head>
<body class="ms-Fabric">
<div class="Container">
<ul class="Tiles">
<li class="Tile"><a href="./demo/index.html" class="Tile-link">Demo Start</a></li>
<li class="Tile"><a href="./final/index.html" class="Tile-link">Final</a></li>
</ul>
</div>
</body>
</html>