├── .gitignore
├── example01
└── index.html
├── example02
└── index.html
├── example03
├── index.html
└── src
│ ├── app.js
│ ├── components
│ └── Items.js
│ └── core
│ └── Component.js
├── example04
├── index.html
└── src
│ ├── app.js
│ ├── components
│ └── Items.js
│ └── core
│ └── Component.js
├── example05
├── index.html
└── src
│ ├── app.js
│ ├── components
│ └── Items.js
│ └── core
│ └── Component.js
├── example06
├── index.html
└── src
│ ├── app.js
│ ├── components
│ └── Items.js
│ └── core
│ └── Component.js
├── example07
├── index.html
└── src
│ ├── app.js
│ ├── components
│ └── Items.js
│ └── core
│ └── Component.js
└── example08
├── index.html
└── src
├── App.js
├── components
├── ItemAppender.js
├── ItemFilter.js
└── Items.js
├── core
└── Component.js
└── main.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .DS_Store
3 | *.iml
--------------------------------------------------------------------------------
/example01/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Simple Component 1
6 |
7 |
8 | Example #1
9 |
10 |
37 |
38 |
--------------------------------------------------------------------------------
/example02/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Simple Component 2
6 |
7 |
8 | Example #2
9 |
10 |
56 |
57 |
--------------------------------------------------------------------------------
/example03/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Simple Component 3
6 |
7 |
8 | Example #3
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/example03/src/app.js:
--------------------------------------------------------------------------------
1 | import Items from "./components/Items.js";
2 |
3 | class App {
4 | constructor() {
5 | const $app = document.querySelector('#app');
6 | new Items($app);
7 | }
8 | }
9 |
10 | new App();
11 |
--------------------------------------------------------------------------------
/example03/src/components/Items.js:
--------------------------------------------------------------------------------
1 | import Component from "../core/Component.js";
2 |
3 | export default class Items extends Component {
4 | setup () {
5 | this.$state = { items: ['item1', 'item2'] };
6 | }
7 | template () {
8 | const { items } = this.$state;
9 | return `
10 |
11 | ${items.map(item => `- ${item}
`).join('')}
12 |
13 |
14 | `
15 | }
16 |
17 | setEvent () {
18 | this.$target.querySelector('button').addEventListener('click', () => {
19 | const { items } = this.$state;
20 | this.setState({ items: [ ...items, `item${items.length + 1}` ] });
21 | });
22 | }
23 | }
24 |
25 |
--------------------------------------------------------------------------------
/example03/src/core/Component.js:
--------------------------------------------------------------------------------
1 | export default class Component {
2 | $target;
3 | $state;
4 | constructor ($target) {
5 | this.$target = $target;
6 | this.setup();
7 | this.render();
8 | }
9 | setup () {};
10 | template () { return ''; }
11 | render () {
12 | this.$target.innerHTML = this.template();
13 | this.setEvent();
14 | }
15 | setEvent () {}
16 | setState (newState) {
17 | this.$state = { ...this.$state, ...newState };
18 | this.render();
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/example04/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Simple Component 4
6 |
7 |
8 | Example #4
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/example04/src/app.js:
--------------------------------------------------------------------------------
1 | import Items from "./components/Items.js";
2 |
3 | class App {
4 | constructor() {
5 | const $app = document.querySelector('#app');
6 | new Items($app);
7 | }
8 | }
9 |
10 | new App();
11 |
--------------------------------------------------------------------------------
/example04/src/components/Items.js:
--------------------------------------------------------------------------------
1 | import Component from "../core/Component.js";
2 |
3 | export default class Items extends Component {
4 | setup () {
5 | this.$state = { items: ['item1', 'item2'] };
6 | }
7 | template () {
8 | const { items } = this.$state;
9 | return `
10 |
11 | ${items.map((item, key) => `
12 | -
13 | ${item}
14 |
15 |
16 | `).join('')}
17 |
18 |
19 | `
20 | }
21 |
22 | setEvent () {
23 | this.$target.querySelector('.addBtn').addEventListener('click', () => {
24 | const { items } = this.$state;
25 | this.setState({ items: [ ...items, `item${items.length + 1}` ] });
26 | });
27 |
28 | this.$target.querySelectorAll('.deleteBtn').forEach(deleteBtn =>
29 | deleteBtn.addEventListener('click', ({ target }) => {
30 | const items = [ ...this.$state.items ];
31 | items.splice(target.dataset.index, 1);
32 | this.setState({ items });
33 | }))
34 | }
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/example04/src/core/Component.js:
--------------------------------------------------------------------------------
1 | export default class Component {
2 | $target;
3 | $state;
4 | constructor ($target) {
5 | this.$target = $target;
6 | this.setup();
7 | this.render();
8 | }
9 | setup () {};
10 | template () { return ''; }
11 | render () {
12 | this.$target.innerHTML = this.template();
13 | this.setEvent();
14 | }
15 | setEvent () {}
16 | setState (newState) {
17 | this.$state = { ...this.$state, ...newState };
18 | this.render();
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/example05/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Simple Component 5
6 |
7 |
8 | Example #5
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/example05/src/app.js:
--------------------------------------------------------------------------------
1 | import Items from "./components/Items.js";
2 |
3 | class App {
4 | constructor() {
5 | const $app = document.querySelector('#app');
6 | new Items($app);
7 | }
8 | }
9 |
10 | new App();
11 |
--------------------------------------------------------------------------------
/example05/src/components/Items.js:
--------------------------------------------------------------------------------
1 | import Component from "../core/Component.js";
2 |
3 | export default class Items extends Component {
4 | setup () {
5 | this.$state = { items: ['item1', 'item2'] };
6 | }
7 | template () {
8 | const { items } = this.$state;
9 | return `
10 |
11 | ${items.map((item, key) => `
12 | -
13 | ${item}
14 |
15 |
16 | `).join('')}
17 |
18 |
19 | `
20 | }
21 |
22 | setEvent () {
23 | this.$target.addEventListener('click', ({ target }) => {
24 | const items = [ ...this.$state.items ];
25 |
26 | if (target.classList.contains('addBtn')) {
27 | this.setState({ items: [ ...items, `item${items.length + 1}` ] });
28 | }
29 |
30 | if (target.classList.contains('deleteBtn')) {
31 | items.splice(target.dataset.index, 1);
32 | this.setState({ items });
33 | }
34 |
35 | });
36 | }
37 | }
38 |
39 |
--------------------------------------------------------------------------------
/example05/src/core/Component.js:
--------------------------------------------------------------------------------
1 | export default class Component {
2 | $target;
3 | $state;
4 | constructor ($target) {
5 | this.$target = $target;
6 | this.setup();
7 | this.setEvent();
8 | this.render();
9 | }
10 | setup () {};
11 | template () { return ''; }
12 | render () {
13 | this.$target.innerHTML = this.template();
14 | }
15 | setEvent () {}
16 | setState (newState) {
17 | this.$state = { ...this.$state, ...newState };
18 | this.render();
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/example06/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Simple Component 6
6 |
7 |
8 | Example #6
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/example06/src/app.js:
--------------------------------------------------------------------------------
1 | import Items from "./components/Items.js";
2 |
3 | class App {
4 | constructor() {
5 | const $app = document.querySelector('#app');
6 | new Items($app);
7 | }
8 | }
9 |
10 | new App();
11 |
--------------------------------------------------------------------------------
/example06/src/components/Items.js:
--------------------------------------------------------------------------------
1 | import Component from "../core/Component.js";
2 |
3 | export default class Items extends Component {
4 | setup () {
5 | this.$state = { items: ['item1', 'item2'] };
6 | }
7 | template () {
8 | const { items } = this.$state;
9 | return `
10 |
11 | ${items.map((item, key) => `
12 | -
13 | ${item}
14 |
15 |
16 | `).join('')}
17 |
18 |
19 | `
20 | }
21 |
22 | setEvent () {
23 | this.addEvent('click', '.addBtn', ({ target }) => {
24 | const { items } = this.$state;
25 | this.setState({ items: [ ...items, `item${items.length + 1}` ] });
26 | });
27 |
28 | this.addEvent('click', '.deleteBtn', ({ target }) => {
29 | const items = [ ...this.$state.items ];
30 | items.splice(target.dataset.index, 1);
31 | this.setState({ items });
32 | });
33 | }
34 | }
35 |
36 |
--------------------------------------------------------------------------------
/example06/src/core/Component.js:
--------------------------------------------------------------------------------
1 | export default class Component {
2 | $target;
3 | $state;
4 | constructor ($target) {
5 | this.$target = $target;
6 | this.setup();
7 | this.setEvent();
8 | this.render();
9 | }
10 | setup () {};
11 | template () { return ''; }
12 | render () {
13 | this.$target.innerHTML = this.template();
14 | }
15 | setEvent () {}
16 | setState (newState) {
17 | this.$state = { ...this.$state, ...newState };
18 | this.render();
19 | }
20 | addEvent (eventType, selector, callback) {
21 | this.$target.addEventListener(eventType, event => {
22 | if (!event.target.closest(selector)) return false;
23 | callback(event);
24 | })
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/example07/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Simple Component 7
6 |
7 |
8 | Example #7
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/example07/src/app.js:
--------------------------------------------------------------------------------
1 | import Items from "./components/Items.js";
2 |
3 | class App {
4 | constructor() {
5 | const $app = document.querySelector('#app');
6 | new Items($app);
7 | }
8 | }
9 |
10 | new App();
11 |
--------------------------------------------------------------------------------
/example07/src/components/Items.js:
--------------------------------------------------------------------------------
1 | import Component from "../core/Component.js";
2 |
3 | export default class Items extends Component {
4 | get filteredItems () {
5 | const { isFilter, items } = this.$state;
6 | return items.filter(({ active }) => (isFilter === 1 && active) ||
7 | (isFilter === 2 && !active) ||
8 | isFilter === 0);
9 | }
10 |
11 | setup() {
12 | this.$state = {
13 | isFilter: 0,
14 | items: [
15 | {
16 | seq: 1,
17 | contents: 'item1',
18 | active: false,
19 | },
20 | {
21 | seq: 2,
22 | contents: 'item2',
23 | active: true,
24 | }
25 | ]
26 | };
27 | }
28 |
29 | template() {
30 | return `
31 |
34 |
35 |
36 | ${this.filteredItems.map(({contents, active, seq}) => `
37 | -
38 | ${contents}
39 |
42 |
43 |
44 | `).join('')}
45 |
46 |
47 |
52 | `
53 | }
54 |
55 | setEvent() {
56 | this.addEvent('keyup', '.appender', ({ key, target }) => {
57 | if (key !== 'Enter') return;
58 | const {items} = this.$state;
59 | const seq = Math.max(0, ...items.map(v => v.seq)) + 1;
60 | const contents = target.value;
61 | const active = false;
62 | this.setState({
63 | items: [
64 | ...items,
65 | {seq, contents, active}
66 | ]
67 | });
68 | });
69 |
70 | this.addEvent('click', '.deleteBtn', ({target}) => {
71 | const items = [ ...this.$state.items ];;
72 | const seq = Number(target.closest('[data-seq]').dataset.seq);
73 | items.splice(items.findIndex(v => v.seq === seq), 1);
74 | this.setState({items});
75 | });
76 |
77 | this.addEvent('click', '.toggleBtn', ({target}) => {
78 | const items = [ ...this.$state.items ];
79 | const seq = Number(target.closest('[data-seq]').dataset.seq);
80 | const index = items.findIndex(v => v.seq === seq);
81 | items[index].active = !items[index].active;
82 | this.setState({items});
83 | });
84 |
85 | this.addEvent('click', '.filterBtn', ({ target }) => {
86 | this.setState({ isFilter: Number(target.dataset.isFilter) });
87 | });
88 | }
89 | }
90 |
91 |
--------------------------------------------------------------------------------
/example07/src/core/Component.js:
--------------------------------------------------------------------------------
1 | export default class Component {
2 | $target;
3 | $state;
4 | constructor ($target) {
5 | this.$target = $target;
6 | this.setup();
7 | this.setEvent();
8 | this.render();
9 | }
10 | setup () {};
11 | template () { return ''; }
12 | render () {
13 | this.$target.innerHTML = this.template();
14 | }
15 | setEvent () {}
16 | setState (newState) {
17 | this.$state = { ...this.$state, ...newState };
18 | this.render();
19 | }
20 | addEvent (eventType, selector, callback) {
21 | this.$target.addEventListener(eventType, event => {
22 | if (!event.target.closest(selector)) return false;
23 | callback(event);
24 | })
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/example08/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Simple Component 8
6 |
7 |
8 | Example #8
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/example08/src/App.js:
--------------------------------------------------------------------------------
1 | import Component from "./core/Component.js";
2 | import Items from "./components/Items.js";
3 | import ItemAppender from "./components/ItemAppender.js";
4 | import ItemFilter from "./components/ItemFilter.js";
5 |
6 | export default class App extends Component {
7 |
8 | setup () {
9 | this.$state = {
10 | isFilter: 0,
11 | items: [
12 | {
13 | seq: 1,
14 | contents: 'item1',
15 | active: false,
16 | },
17 | {
18 | seq: 2,
19 | contents: 'item2',
20 | active: true,
21 | }
22 | ]
23 | };
24 | }
25 |
26 | template () {
27 | return `
28 |
29 |
30 |
31 | `;
32 | }
33 |
34 | mounted () {
35 | const { filteredItems, addItem, deleteItem, toggleItem, filterItem } = this;
36 | const $itemAppender = this.$target.querySelector('[data-component="item-appender"]');
37 | const $items = this.$target.querySelector('[data-component="items"]');
38 | const $itemFilter = this.$target.querySelector('[data-component="item-filter"]');
39 |
40 | new ItemAppender($itemAppender, {
41 | addItem: addItem.bind(this)
42 | });
43 | new Items($items, {
44 | filteredItems,
45 | deleteItem: deleteItem.bind(this),
46 | toggleItem: toggleItem.bind(this),
47 | });
48 | new ItemFilter($itemFilter, {
49 | filterItem: filterItem.bind(this)
50 | });
51 | }
52 |
53 | get filteredItems () {
54 | const { isFilter, items } = this.$state;
55 | return items.filter(({ active }) => (isFilter === 1 && active) ||
56 | (isFilter === 2 && !active) ||
57 | isFilter === 0);
58 | }
59 |
60 | addItem (contents) {
61 | const {items} = this.$state;
62 | const seq = Math.max(0, ...items.map(v => v.seq)) + 1;
63 | const active = false;
64 | this.setState({
65 | items: [
66 | ...items,
67 | {seq, contents, active}
68 | ]
69 | });
70 | }
71 |
72 | deleteItem (seq) {
73 | const items = [ ...this.$state.items ];
74 | items.splice(items.findIndex(v => v.seq === seq), 1);
75 | this.setState({items});
76 | }
77 |
78 | toggleItem (seq) {
79 | const items = [ ...this.$state.items ];
80 | const index = items.findIndex(v => v.seq === seq);
81 | items[index].active = !items[index].active;
82 | this.setState({items});
83 | }
84 |
85 | filterItem (isFilter) {
86 | this.setState({ isFilter });
87 | }
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/example08/src/components/ItemAppender.js:
--------------------------------------------------------------------------------
1 | import Component from "../core/Component.js";
2 |
3 | export default class ItemAppender extends Component {
4 |
5 | template() {
6 | return ``;
7 | }
8 |
9 | setEvent() {
10 | const { addItem } = this.$props;
11 | this.addEvent('keyup', '.appender', ({ key, target }) => {
12 | if (key !== 'Enter') return;
13 | addItem(target.value);
14 | });
15 | }
16 | }
17 |
18 |
--------------------------------------------------------------------------------
/example08/src/components/ItemFilter.js:
--------------------------------------------------------------------------------
1 | import Component from "../core/Component.js";
2 |
3 | export default class ItemFilter extends Component {
4 |
5 | template() {
6 | return `
7 |
8 |
9 |
10 | `
11 | }
12 |
13 | setEvent() {
14 | const { filterItem } = this.$props;
15 | this.addEvent('click', '.filterBtn', ({ target }) => {
16 | filterItem(Number(target.dataset.isFilter));
17 | });
18 | }
19 | }
20 |
21 |
--------------------------------------------------------------------------------
/example08/src/components/Items.js:
--------------------------------------------------------------------------------
1 | import Component from "../core/Component.js";
2 |
3 | export default class Items extends Component {
4 |
5 | template() {
6 | const { filteredItems } = this.$props;
7 | return `
8 |
9 | ${filteredItems.map(({contents, active, seq}) => `
10 | -
11 | ${contents}
12 |
15 |
16 |
17 | `).join('')}
18 |
19 | `
20 | }
21 |
22 | setEvent() {
23 | const { deleteItem, toggleItem } = this.$props;
24 |
25 | this.addEvent('click', '.deleteBtn', ({target}) => {
26 | deleteItem(Number(target.closest('[data-seq]').dataset.seq));
27 | });
28 |
29 | this.addEvent('click', '.toggleBtn', ({target}) => {
30 | toggleItem(Number(target.closest('[data-seq]').dataset.seq));
31 | });
32 |
33 | }
34 |
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/example08/src/core/Component.js:
--------------------------------------------------------------------------------
1 | export default class Component {
2 | $target;
3 | $props;
4 | $state;
5 | constructor ($target, $props) {
6 | this.$target = $target;
7 | this.$props = $props;
8 | this.setup();
9 | this.setEvent();
10 | this.render();
11 | }
12 | setup () {};
13 | mounted () {};
14 | template () { return ''; }
15 | render () {
16 | this.$target.innerHTML = this.template();
17 | this.mounted();
18 | }
19 | setEvent () {}
20 | setState (newState) {
21 | this.$state = { ...this.$state, ...newState };
22 | this.render();
23 | }
24 | addEvent (eventType, selector, callback) {
25 | this.$target.addEventListener(eventType, event => {
26 | if (!event.target.closest(selector)) return false;
27 | callback(event);
28 | })
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/example08/src/main.js:
--------------------------------------------------------------------------------
1 | import App from './App.js';
2 |
3 | new App(document.querySelector('#app'));
4 |
--------------------------------------------------------------------------------