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

This commit is contained in:
Ken
2019-02-13 08:59:20 -08:00
30 changed files with 616 additions and 8 deletions

View File

@@ -1 +1,13 @@
<div style="display: inline-flex; flex-direction: column">
<a href="/step01/">Step01</a>
<a href="/step02/">Step02</a>
<a href="/step03/">Step03</a>
<a href="/step04/">Step04</a>
<a href="/step05/">Step05</a>
<a href="/step06/">Step06</a>
<a href="/step07/">Step07</a>
<a href="/step08/">Step08</a>
<a href="/step09/">Step09</a>
<a href="/playground/">Playground</a>
</div>

View File

@@ -1,6 +1,23 @@
<!DOCTYPE html>
<html>
<body>
<div id="app"></div>
<h1>todos</h1>
<div>
<input/><button>Add</button>
</div>
<div>
<button>all</button>
<button>active</button>
<button>completed</button>
</div>
<ul>
<li><input type="checkbox" /> Todo 1</li>
<li><input type="checkbox" /> Todo 2</li>
<li><input type="checkbox" /> Todo 3</li>
<li><input type="checkbox" /> Todo 4</li>
</ul>
<div>
<span>4 items left</span> <button>Clear Completed</button>
</div>
</body>
</html>

View File

@@ -1,4 +0,0 @@
document.addEventListener('DOMContentLoaded', function() {
const app = document.getElementById('app');
app.innerHTML = 'hello world blah';
});

View File

@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<link rel="stylesheet" href="./style.css" />
<body>
<h1>todos</h1>
<input class="textfield" /><button class="button add">Add</button>
<div class="filter">
<button class="active">all</button>
<button>active</button>
<button>completed</button>
</div>
<ul class="todos">
<li class="todo">
<label><input type="checkbox" /> Todo 1</label>
</li>
<li class="todo">
<label><input type="checkbox" /> Todo 2</label>
</li>
<li class="todo">
<label><input type="checkbox" /> Todo 3</label>
</li>
<li class="todo">
<label><input type="checkbox" /> Todo 4</label>
</li>
</ul>
<footer><span>4 items left</span> <button class="button">Clear Completed</button></footer>
</body>
</html>

46
step02/style.css Normal file
View File

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

91
step03/index.html Normal file
View File

@@ -0,0 +1,91 @@
<!DOCTYPE html>
<html>
<link rel="stylesheet" href="./style.css" />
<body>
<h1>todos</h1>
<input class="textfield" />
<button onclick="addTodo()" class="button add">
Add
</button>
<div class="filter">
<button onclick="filter('all', this)" class="active">all</button>
<button onclick="filter('active', this)">active</button>
<button onclick="filter('completed', this)">completed</button>
</div>
<ul class="todos">
<li class="todo">
<label><input type="checkbox" /> Todo 1</label>
</li>
<li class="todo">
<label><input type="checkbox" /> Todo 2</label>
</li>
<li class="todo">
<label><input type="checkbox" /> Todo 3</label>
</li>
<li class="todo">
<label><input type="checkbox" /> Todo 4</label>
</li>
</ul>
<footer>
<span><span class="remaining">4</span> items left</span>
<button onclick="clearCompleted()" class="button">Clear Completed</button>
</footer>
</body>
<script type="text/javascript">
function getValue(selector) {
const inputValue = document.querySelector(selector).value;
return inputValue;
}
function clearInput(selector) {
document.querySelector(selector).value = "";
}
function updateRemaining() {
const remaining = document.querySelector(".remaining");
const todos = document.querySelectorAll(".todo").length;
remaining.innerText = todos;
}
function addTodo() {
const todo = document.querySelector(".todo");
const newTodo = todo.cloneNode();
newTodo.innerHTML = `<label><input type="checkbox" /> ${getValue(
".textfield"
)}</label>`;
todo.parentElement.insertBefore(newTodo, todo);
clearInput(".textfield");
updateRemaining();
}
function clearCompleted() {
const todos = document.querySelectorAll(".todo");
for (let todo of todos) {
if (todo.querySelector("input").checked == true) {
todo.remove();
}
}
updateRemaining();
}
function filter(scope, button) {
document.querySelector('.active').classList.remove('active');
button.classList.add('active');
for (let todo of document.querySelectorAll(".todo")) {
const checked = todo.querySelector("input").checked == true;
if (scope == 'all') {
todo.hidden = false;
}
else if ( scope == 'active' ){
todo.hidden = checked;
}
else if ( scope == 'completed' ){
todo.hidden = !checked;
}
}
}
</script>
</html>

0
step03/index.js Normal file
View File

50
step03/style.css Normal file
View File

@@ -0,0 +1,50 @@
body {
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
width: 400px;
margin: 20px auto;
}
h1 {
text-align: center;
}
.textfield {
width: 80%;
}
.add {
margin-left: 5%;
}
.button {
border: none;
padding: 5px 10px;
}
.filter {
margin: 10px 0 0;
}
.filter button {
background: transparent;
border: none;
}
.filter .active {
border-bottom: 2px solid blue;
}
.todos {
list-style: none;
padding: 0;
}
footer {
display: flex;
}
footer span {
flex-grow: 1;
}
.hidden {
display: none;
}

8
step04/index.html Normal file
View File

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

13
step04/src/App.tsx Normal file
View File

@@ -0,0 +1,13 @@
import React from 'react';
export class App extends React.Component {
render() {
return (
<div>
</div>
);
}
}

4
step04/src/index.tsx Normal file
View File

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

9
step05/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>

18
step05/src/App.tsx Normal file
View File

@@ -0,0 +1,18 @@
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,13 @@
import React from "react";
export const TodoFooter = (props: any) => {
return (
<footer>
<span>
<span className="remaining">4</span> items left
</span>
<button className="button">Clear Completed</button>
</footer>
);
};

View File

@@ -0,0 +1,23 @@
import React from 'react';
export class TodoHeader extends React.Component {
render() {
return (
<div>
<h1>todos</h1>
<input className="textfield" />
<button className="button add">
Add
</button>
<div className="filter">
<button className="active">
all
</button>
<button >active</button>
<button>completed</button>
</div>
</div>
);
}
}

View File

@@ -0,0 +1,40 @@
import React from 'react';
import { TodoListItem } from './TodoListItem';
export class TodoList extends React.Component<any, any> {
render() {
const { filter, todos } = this.props;
let filteredTodos: typeof todos = {};
switch (filter) {
case 'completed':
Object.keys(todos).forEach(id => {
if (todos[id].completed) {
filteredTodos[id] = todos[id];
}
});
break;
case 'active':
Object.keys(todos).forEach(id => {
if (!todos[id].completed) {
filteredTodos[id] = todos[id];
}
});
break;
default:
filteredTodos = todos;
break;
}
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>
);
}
}

4
step05/src/index.tsx Normal file
View File

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

50
step05/src/style.css Normal file
View File

@@ -0,0 +1,50 @@
body {
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
width: 400px;
margin: 20px auto;
}
h1 {
text-align: center;
}
.textfield {
width: 80%;
}
.add {
margin-left: 5%;
}
.button {
border: none;
padding: 5px 10px;
}
.filter {
margin: 10px 0 0;
}
.filter button {
background: transparent;
border: none;
}
.filter .active {
border-bottom: 2px solid blue;
}
.todos {
list-style: none;
padding: 0;
}
footer {
display: flex;
}
footer span {
flex-grow: 1;
}
.hidden {
display: none;
}

9
step06/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>

30
step06/src/App.tsx Normal file
View File

@@ -0,0 +1,30 @@
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: [
{key: 1, text: 'Todo 1', completed: true},
{key: 2, text: 'Todo 2'},
{key: 3, text: 'Todo 3'},
{key: 4, text: 'Todo 4'},
],
filter: 'all'
}
}
render() {
const {filter, todos} = this.state;
return (
<div>
<TodoHeader filter={filter} />
<TodoList todos={todos} filter={filter} />
<TodoFooter todos={todos} />
</div>
);
}
}

View File

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

View File

@@ -0,0 +1,21 @@
import React from "react";
export class TodoHeader extends React.Component<any, any> {
render() {
const { filter } = this.props;
return (
<div>
<h1>todos</h1>
<input className="textfield" />
<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>
</div>
</div>
);
}
}

View File

@@ -0,0 +1,28 @@
import React from 'react';
import { TodoListItem } from './TodoListItem';
export class TodoList extends React.Component<any, any> {
render() {
const { filter, todos } = this.props;
let filteredTodos: typeof todos = {};
filteredTodos = todos.filter(todo => {
const matchesActive = filter == 'active' && !todo.completed;
const matchesCompleted = filter == 'completed' && todo.completed;
return filter == 'all' || matchesActive || matchesCompleted;
})
const TodoListItems = filteredTodos.map(todo => {
return (
<TodoListItem {...todo} />
);
})
return (
<ul className="todos">
{TodoListItems}
</ul>
);
}
}

View File

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

4
step06/src/index.tsx Normal file
View File

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

50
step06/src/style.css Normal file
View File

@@ -0,0 +1,50 @@
body {
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
width: 400px;
margin: 20px auto;
}
h1 {
text-align: center;
}
.textfield {
width: 80%;
}
.add {
margin-left: 5%;
}
.button {
border: none;
padding: 5px 10px;
}
.filter {
margin: 10px 0 0;
}
.filter button {
background: transparent;
border: none;
}
.filter .active {
border-bottom: 2px solid blue;
}
.todos {
list-style: none;
padding: 0;
}
footer {
display: flex;
}
footer span {
flex-grow: 1;
}
.hidden {
display: none;
}

View File

@@ -1,7 +1,7 @@
{
"compilerOptions": {
"outDir": "./dist/",
"noImplicitAny": true,
"noImplicitAny": false,
"module": "esnext",
"moduleResolution": "node",
"esModuleInterop": true,

View File

@@ -2,8 +2,9 @@ const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const entries = {
step01: './step01/index',
step02: './step02/src/index',
step04: './step04/src/index',
step05: './step05/src/index',
step06: './step06/src/index',
playground: './playground/src/index'
};
@@ -32,6 +33,10 @@ module.exports = Object.keys(entries).map(entryPoint => {
output: {
filename: '[name].js',
path: path.resolve(__dirname, entryPoint, 'dist')
},
devServer: {
contentBase: path.resolve(__dirname),
watchContentBase: true
}
};
});