MVC模式的本质

总述

MVC模式的本质

参考文章:
Writing a Simple MVC App in Plain JavaScript
用纯Javascript撸一个MVC程序

上代码:

<!--html部分-->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />

    <title>Todo App</title>

    <link rel="stylesheet" href="style.css" />
  </head>

  <body>
    <div id="root"></div>

    <script src="script.js"></script>
  </body>
</html>

样式:直接复制就行了不用看

*,
*::before,
*::after {
  box-sizing: border-box
}

html {
  font-family: sans-serif;
  font-size: 1rem;
  color: #444;
}

#root {
  max-width: 450px;
  margin: 2rem auto;
  padding: 0 1rem;
}

form {
  display: flex;
  margin-bottom: 2rem;
}

[type="text"],
button {
  display: inline-block;
  -webkit-appearance: none;
  padding: .5rem 1rem;
  font-size: 1rem;
  border: 2px solid #ccc;
  border-radius: 4px;
}

button {
  cursor: pointer;
  background: #007bff;
  color: white;
  border: 2px solid #007bff;
  margin: 0 .5rem;
}

[type="text"] {
  width: 100%;
}

[type="text"]:active,
[type="text"]:focus {
  outline: 0;
  border: 2px solid #007bff;
}

[type="checkbox"] {
  margin-right: 1rem;
  font-size: 2rem;
}

h1 {
  color: #222;
}

ul {
  padding: 0;
}

li {
  display: flex;
  align-items: center;
  padding: 1rem;
  margin-bottom: 1rem;
  background: #f4f4f4;
  border-radius: 4px;
}

li span {
  display: inline-block;
  padding: .5rem;
  width: 250px;
  border-radius: 4px;
  border: 2px solid transparent;
}

li span:hover {
  background: rgba(179, 215, 255, 0.52);
}

li span:focus {
  outline: 0;
  border: 2px solid #007bff;
  background: rgba(179, 207, 255, 0.52)
}

核心代码:

/**
 * @Class Model
 *
 * 管理应用的数据
 */
class Model {
    constructor() {
        //从 localStorage 里面取值
        this.todos = JSON.parse(localStorage.getItem('todos')) || [];
    }

    bindTodoListChanged(callback) {
        this.onTodoListChanged = callback;
    }

    //保存在本地
    _commit(todos) {
        this.onTodoListChanged(todos);
        localStorage.setItem('todos', JSON.stringify(todos))
    }

    addTodo(todoText) {
        const todo = {
            id: this.todos.length > 0 ? this.todos[this.todos.length - 1].id + 1 : 1,
            text: todoText,
            complete: false,
        };

        this.todos.push(todo);

        this._commit(this.todos);
    }

    editTodo(id, updatedText) {
        this.todos = this.todos.map(todo =>
            todo.id === id
                ? {id: todo.id, text: updatedText, complete: todo.complete}
                : todo
        );
        this._commit(this.todos)
    }

    deleteTodo(id) {
        this.todos = this.todos.filter(todo => todo.id !== id);

        this._commit(this.todos)
    }

    toggleTodo(id) {
        this.todos = this.todos.map(todo =>
            todo.id === id
                ? {id: todo.id, text: todo.text, complete: !todo.complete}
                : todo
        );

        this._commit(this.todos)
    }
}

/**
 * @class View
 * model层的可视化展示
 */
class View {
    constructor() {
        //创造DOM树需要展示的各种节点
        //DOM树根节点
        this.app = this.getElement("#root");

        //heading
        this.title = this.createElement("h1");
        this.title.textContent = "Todos";

        //form
        this.form = this.createElement("form");
        //form content
        this.input = this.createElement("input");
        this.input.type = "text";
        this.input.placeholder = "Add todo";
        this.input.name = "todo";
        //add button
        this.submitButton = this.createElement("button");
        this.submitButton.textContent = "Submit";
        this.form.append(this.input, this.submitButton);
        //todo things List
        this.todoList = this.createElement("ul", "todo-list");
        this.app.append(this.title, this.form, this.todoList);

        this._temporaryTodoText = '';
        this._initLocalListeners()
    }

    //获得输入的input 的 value
    get _todoText() {
        return this.input.value;
    }

    //重置
    _resetInput() {
        this.input.value = "";
    }

    //重载createElement 方法
    createElement(tag, className) {
        const element = document.createElement(tag);
        if (className) {
            element.classList.add(className);
        }
        return element;
    }

    //根据selector 获得 节点
    getElement = selector => document.querySelector(selector);

    //Todo 列表的展示
    displayTodos(todos) {
        //先删除所有的子节点元素
        while (this.todoList.firstChild) {
            this.todoList.removeChild(this.todoList.firstChild);
        }
        //展示默认的消息
        if (todos.length === 0) {
            const p = this.createElement("p");
            p.textContent = "Nothing to do ! Add a task?";
            this.todoList.append(p);
        } else {
            //创建节点
            todos.forEach(todo => {
                const li = this.createElement("li");
                li.id = todo.id;

                //每个代办项都有一个checkbox
                const checkbox = this.createElement("input");
                checkbox.type = "checkbox";
                checkbox.complete = todo.complete;

                const span = this.createElement("span");
                span.contentEditable = true;
                span.classList.add("editable");

                //如果已经完成,添加一条删除线 表示已经完成
                if (todo.complete) {
                    const strike = this.createElement("s");
                    strike.textContent = todo.text;
                    span.append(strike);
                } else {
                    //直接展示文字
                    span.textContent = todo.text;
                }
                //删除按钮
                const deleteButton = this.createElement("button", "delete");
                deleteButton.textContent = "Delete";
                li.append(checkbox, span, deleteButton);

                //将li 节点 加入到 todoList
                this.todoList.append(li);
            });
        }
        //调试
        console.log(todos)
    }

    _initLocalListeners() {
        this.todoList.addEventListener('input', event => {
            if (event.target.className === 'editable') {
                this._temporaryTodoText = event.target.innerText
            }
        })
    }

    bindAddTodo(handler) {
        this.form.addEventListener('submit', event => {
            event.preventDefault();

            if (this._todoText) {
                handler(this._todoText);
                this._resetInput()
            }
        });
    }

    bindDeleteTodo(handler) {
        this.todoList.addEventListener('click', event => {
            if (event.target.className === 'delete') {
                const id = parseInt(event.target.parentElement.id);

                handler(id)
            }
        })
    }

    bindEditTodo(handler) {
        this.todoList.addEventListener('focusout', event => {
            if (this._temporaryTodoText) {
                const id = parseInt(event.target.parentElement.id);

                handler(id, this._temporaryTodoText);
                this._temporaryTodoText = ''
            }
        })
    }

    bindToggleTodo(handler) {
        this.todoList.addEventListener('change', event => {
            if (event.target.type === 'checkbox') {
                const id = parseInt(event.target.parentElement.id);

                handler(id);
            }
        })
    }
}

/**
 * @class Controller
 *
 * 连接用户输入和视图输出
 * @param model
 * @param view
 */
class Controller {
    constructor(model, view) {
        this.model = model;
        this.view = view;

        //视图与model 绑定
        this.model.bindTodoListChanged(this.onTodoListChanged);
        this.view.bindAddTodo(this.handleAddTodo);
        this.view.bindDeleteTodo(this.handleDeleteTodo);
        this.view.bindToggleTodo(this.handleToggleTodo);
        this.view.bindEditTodo(this.handleEditTodo);

        //初始化展示 todos
        this.onTodoListChanged(this.model.todos);

    }

    onTodoListChanged = todos => {
        this.view.displayTodos(todos);
    };
    //方法函数
    handleAddTodo = todoText => {
        this.model.addTodo(todoText);
    };

    handleEditTodo = (id, todoText) => {
        this.model.editTodo(id, todoText);
    };
    handleDeleteTodo = id => {
        this.model.deleteTodo(id);
    };
    handleToggleTodo = id => {
        this.model.toggleTodo(id);
    };
}

const app = new Controller(new Model(), new View());

以上代码实现了一个完整的todos 网页,

在这里插入图片描述

想要复现整个过程
请参考(推荐英文版,英文版是原作者 )
Writing a Simple MVC App in Plain JavaScript
用纯Javascript撸一个MVC程序

如果ES6的代码有看不懂的地方,请参考:
ECMAScript 6 入门

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值