Build an MVC framework with pure JavaScript

Front end pioneer 2021-06-23 17:41:22
build mvc framework pure javascript


//  Night talk at the front of the day   The first 561  piece 
//  The text is :4200  word
//  Estimated reading time :12  minute

 picture

I want to use model-view-controller[1] Architecture pattern in pure JavaScript Write a simple program in , So I did . I hope it helps you understand MVC, Because when you first came into contact with it , It's a hard concept to understand .

What have I done This todo Applications [2], This is a simple and compact browser application , Allow you to do CRUD( establish , Read , Update and delete ) operation . It contains only index.htmlstyle.css and script.js Three files , It's simple , No need for any dependencies or frameworks .

precondition

  • Basic JavaScript and HTML knowledge
  • Be familiar with the latest JavaScript grammar [3]

The goal is

In pure JavaScript Create a... In the browser todo Applications , And be familiar with MVC( and OOP—— object-oriented programming ) The concept of .

  • View the demo of the program [4]
  • Look at the source code of the program [5]

Be careful : Because this program uses  ES2017 function , So in some browsers ( Such as Safari) It doesn't work Babel Compile to backward compatible JavaScript grammar .

What is? MVC?

MVC It's a very popular way to organize code .

  • Model( Model ) - Data management program
  • View( View ) - Visual representation of model
  • Controller( controller )  - Linking users and systems

Model Is the data . In this todo In the program , This will be the actual to-do , And will add 、 How to edit or delete them .

View It's the way data is displayed . In this program , yes DOM and CSS Presented in HTML.

controller Used to connect models and views . It requires user input , For example, click or type , And handle user interaction callbacks .

The model never touches the view . Views never touch the model . The controller is used to connect them .

I want to mention , For a simple todo Program do MVC It's actually a bunch of templates . If this is the program you want to create and create the whole system , That really makes things too complicated . The key is to try to understand it on a smaller level .

Initial settings

It will be a complete use JavaScript Written program , It means that everything will go through JavaScript Handle ,HTML Will contain only the root element .

index.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>

I wrote a small part CSS Just to make it look acceptable , You can find This file [6] And save to style.css . I'm not going to write any more CSS 了 , Because it's not the focus of this article .

well , Now we have HTML and CSS, Now it's time to start programming .

introduction

I'll make this tutorial easy to understand , It makes it easy for you to know which class belongs to MVC Which part of . I will create a Model class ,View Classes and Controller class . The program will be an example of the controller .

If you're not familiar with the way classes work , Please read about it JavaScript Class in [7].

class Model {
  constructor() {}
}

class View {
  constructor() {}
}

class Controller {
  constructor(model, view) {
    this.model = model
    this.view = view
  }
}

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

Model

Let's focus on the model first , Because it's the simplest of the three parts . It does not involve any event or DOM operation . It's just storing and modifying data .

// Model 
class Model {
  constructor() {
    // The state of the model, an array of todo objects, prepopulated with some data
    this.todos = [
      { id1text'Run a marathon'completefalse },
      { id2text'Plant a garden'completefalse },
    ]
  }

  // Append a todo to the todos array
  addTodo(todo) {
    this.todos = [...this.todos, todo]
  }

  // Map through all todos, and replace the text of the todo with the specified id
  editTodo(id, updatedText) {
    this.todos = this.todos.map(todo =>
      todo.id === id ? { id: todo.id, text: updatedText, complete: todo.complete } : todo
    )
  }

  // Filter a todo out of the array by id
  deleteTodo(id) {
    this.todos = this.todos.filter(todo => todo.id !== id)
  }

  // Flip the complete boolean on the specified todo
  toggleTodo(id) {
    this.todos = this.todos.map(todo =>
      todo.id === id ? { id: todo.id, text: todo.text, complete: !todo.complete } : todo
    )
  }
}

We defined addTodoeditTododeleteTodo and toggleTodo. These should be clear at a glance :add Add to array ,edit find todo Of id Edit and replace ,delete Filter the... In the array todo, And switch complete Boolean properties .

Because we do this in the browser , And from the window ( overall situation ) visit , So you can easily test these things , Enter the following :

app.model.addTodo({ id3text'Take a nap'completefalse })

A to-do item will be added to the list , You can check app.model.todos The content of .

That's enough for the current model . Finally, we will store the to-do items in local storage[8] in , To make it semi permanent , But now just refresh the page ,todo It will refresh .

We can see , The model only processes and modifies the actual data . It doesn't understand or know Input  —— Modifying it , or Output  —— What will eventually show .

At this time, if you manually input all operations through the console , And see the output in the console , You can get a fully functional CRUD Everything the program needs .

View

We're going to do it through manipulation DOM  —— Document object model to create views . Because there is no React Of JSX Or template language , In ordinary JavaScript Do this in , So it will be lengthy and ugly , But it's direct manipulation DOM The essence of .

Neither the controller nor the model should know about DOM、HTML Elements 、CSS Or anything in it . Anything related to it should be in the view .

If you're not familiar with it DOM or DOM And HTML What's the difference between the source code , Please read DOM brief introduction [9].

The first thing to do is to create helper methods to retrieve and create elements .

// View 
class View {
  constructor() {}

  // Create an element with an optional CSS class
  createElement(tag, className) {
    const element = document.createElement(tag)
    if (className) element.classList.add(className)

    return element
  }

  // Retrieve an element from the DOM
  getElement(selector) {
    const element = document.querySelector(selector)

    return element
  }
}

So far so good . Then in the constructor , I'll set everything I need for the view :

  • Root element of the application  - #root
  • title h1
  • A form , Input box and submit button , Use to add to-do items - form, input, button
  • To do list - ul

I'll create all the variables in the constructor , So that they can be easily referenced .

// View 
class View {
  constructor() {
    // The root element
    this.app = this.getElement('#root')

    // The title of the app
    this.title = this.createElement('h1')
    this.title.textContent = 'Todos'

    // The form, with a [type="text"] input, and a submit button
    this.form = this.createElement('form')

    this.input = this.createElement('input')
    this.input.type = 'text'
    this.input.placeholder = 'Add todo'
    this.input.name = 'todo'

    this.submitButton = this.createElement('button')
    this.submitButton.textContent = 'Submit'

    // The visual representation of the todo list
    this.todoList = this.createElement('ul''todo-list')

    // Append the input and submit button to the form
    this.form.append(this.input, this.submitButton)

    // Append the title, form, and todo list to the app
    this.app.append(this.title, this.form, this.todoList)
  }
  // ...
}

Now? , Will set the view section that will not be changed .

 picture

Two other little things : Input (new todo) It's worth it getter and resetter.

//  View 
get todoText() {
  return this.input.value
}

resetInput() {
  this.input.value = ''
}

Now all the settings are complete . The most complicated part is to show the to-do list , This is the part that will be changed every time the to-do is modified .

// View 
displayTodos(todos) {
  // ...
}

displayTodos Method will create the ul and li And display them . Each modification 、 Add or remove todo when , Will use the todos Call again displayTodos Method , Reset the list and redisplay them . This will keep the view in sync with the state of the model .

The first thing we need to do is to delete all the todo node . Then check for a to-do . If you don't do that , We'll get an empty list message .

//  View 
// Delete all nodes
while (this.todoList.firstChild) {
  this.todoList.removeChild(this.todoList.firstChild)
}

// Show default message
if (todos.length === 0) {
  const p = this.createElement('p')
  p.textContent = 'Nothing to do! Add a task?'
  this.todoList.append(p)
else {
  // ...
}

Now loop through the to-do and show a check box for each existing to-do 、span And delete button .

//  View 
else {
  // Create todo item nodes for each todo in state
  todos.forEach(todo => {
    const li = this.createElement('li')
    li.id = todo.id

    // Each todo item will have a checkbox you can toggle
    const checkbox = this.createElement('input')
    checkbox.type = 'checkbox'
    checkbox.checked = todo.complete

    // The todo item text will be in a contenteditable span
    const span = this.createElement('span')
    span.contentEditable = true
    span.classList.add('editable')

    // If the todo is complete, it will have a strikethrough
    if (todo.complete) {
      const strike = this.createElement('s')
      strike.textContent = todo.text
      span.append(strike)
    } else {
      // Otherwise just display the text
      span.textContent = todo.text
    }

    // The todos will also have a delete button
    const deleteButton = this.createElement('button''delete')
    deleteButton.textContent = 'Delete'
    li.append(checkbox, span, deleteButton)

    // Append nodes to the todo list
    this.todoList.append(li)
  })
}

Now set up the view and model . We just can't connect them , Because there is no event monitoring user input yet , There's no way to handle the output of this kind of event handle.

The console still exists as a temporary controller , You can use it to add and delete to-do items .

 picture

controller

Last , The controller is the model ( data ) And views ( What users see ) Links between . This is what we have in the controller so far .

// controller 
class Controller {
  constructor(model, view) {
    this.model = model
    this.view = view
  }
}

The first link between the view and the model is to create one each time todo Call... On change displayTodos Methods . We can also do that constructor Call it once , To display the initial todos( If any ).

// controller 
class Controller {
  constructor(model, view) {
    this.model = model
    this.view = view

    // Display initial todos
    this.onTodoListChanged(this.model.todos)
  }

  onTodoListChanged = todos => {
    this.view.displayTodos(todos)
  }
}

The controller will handle the event after triggering . When you submit a new to-do 、 When you click the delete button or click the to-do check box , An event will be triggered . The view must listen for these events , Because they are user input to the view , It assigns the work to the controller to respond to the event .

We're going to create... For the event handler. First , To submit a handleAddTodo event , When the to-do input form we created is submitted , You can press Enter Key or click “ Submit ” Button to trigger . This is a submit event .

Go back to the view , We will this.input.value Of getter As get todoText. Make sure the input cannot be empty , And then we're going to create something with idtext also complete The value is false Of todo. take todo Add to model , Then reset the input box .

//  controller 
// Handle submit event for adding a todo
handleAddTodo = event => {
  event.preventDefault()

  if (this.view.todoText) {
    const todo = {
      idthis.model.todos.length > 0 ? this.model.todos[this.model.todos.length - 1].id + 1 : 1,
      textthis.view.todoText,
      completefalse,
    }

    this.model.addTodo(todo)
    this.view.resetInput()
  }
}

Delete todo Similar to . It will respond to... On the delete button click event . The parent element of the delete button is todo li In itself , It comes with a corresponding id. We need to send that data to the right model method .

//  controller 
// Handle click event for deleting a todo
handleDeleteTodo = event => {
  if (event.target.className === 'delete') {
    const id = parseInt(event.target.parentElement.id)

    this.model.deleteTodo(id)
  }
}

stay JavaScript in , When you click the check box to toggle it , Will be issued change event . Handle this method the same way you would click the delete button , And call the model method .

//  controller 
// Handle change event for toggling a todo
handleToggle = event => {
  if (event.target.type === 'checkbox') {
    const id = parseInt(event.target.parentElement.id)

    this.model.toggleTodo(id)
  }
}

These controller methods are a bit messy - Ideally, they shouldn't deal with any logic , Instead, you should simply call the model .

Set the event listener

Now we have these three handler , But the controller still doesn't know when to call them . You have to put the event listener in the DOM Element . We're going to reply to submit event , as well as todo On the list click and change event .

stay View Add a bindEvents Method , This method will call these events .

//  View 
bindEvents(controller) {
  this.form.addEventListener('submit', controller.handleAddTodo)
  this.todoList.addEventListener('click', controller.handleDeleteTodo)
  this.todoList.addEventListener('change', controller.handleToggle)
}

Then bind the method that listens to the event to the view . stay Controller Of constructor in , call bindEvents And transfer the controller information this Context .

Arrow functions are used on all handle events . This allows us to use the controller's this The context calls them from the view. . If you don't use the arrow function , We will have to bind them manually , Such as controller.handleAddTodo.bind(this).

//  controller 
this.view.bindEvents(this)

Now? , When the specified element occurs submitclick or change When an event is , The corresponding handler.

Callbacks in the response model

We've also missed something : Event listening ,handler Called , But there was no reaction . This is because the model doesn't know that the view should be updated , And don't know how to update the view . We have some on the view displayTodos To solve this problem , But as mentioned before , Models and views should not understand each other .

It's like listening to events , The model should go back to the controller , Let it know what happened .

We've created... On the controller onTodoListChanged How to deal with this problem , Next, just let the model know it . We bind it to the model , It's just like on the view handler The same thing .

In the model , by onTodoListChanged add to bindEvents.

//  Model 
bindEvents(controller) {
  this.onTodoListChanged = controller.onTodoListChanged
}

In the controller , send out this Context .

//  controller 
constructor() {
  // ...
  this.model.bindEvents(this)
  this.view.bindEvents(this)
}

Now? , After each method in the model , You will call onTodoListChanged Callback .

In more complex programs , There may be different callbacks for different events , But in this simple to-do program , We can share a callback between all methods .

// Model 
addTodo(todo) {
  this.todos = [...this.todos, todo]

  this.onTodoListChanged(this.todos)
}

add to local storage

Most of the program is now complete , All the concepts have been demonstrated . We can save the data in the browser's local storage To make it persistent .

If you don't understand local storage How it works , Please read how to use it JavaScript local storage[10].

Now we can set the initial value of the to-do to local storage or empty array .

//  Model 
class Model {
  constructor() {
    this.todos = JSON.parse(localStorage.getItem('todos')) || []
  }
}

And then create a update Function to update localStorage Value .

// Model 
update() {
  localStorage.setItem('todos'JSON.stringify(this.todos))
}

Every change this.todos after , We can all call it .

// Model 
addTodo(todo) {
  this.todos = [...this.todos, todo]
  this.update()

  this.onTodoListChanged(this.todos)
}

Add real-time editing function

The last part of the puzzle is the ability to edit existing to-do items . Editing is always trickier than adding or deleting . I want to simplify it , There is no need to edit the button or use input Or anything span. We don't want to call every input letter editTodo, Because it re renders the entire to-do list UI.

I decided to create a method on the controller , Update temporary state variables with new edit values , Another method calls... In the model editTodo Method .

// controller 
constructor() {
  // ...
  this.temporaryEditValue
}

// Update temporary state
handleEditTodo = event => {
  if (event.target.className === 'editable') {
    this.temporaryEditValue = event.target.innerText
  }
}

// Send the completed value to the model
handleEditTodoComplete = event => {
  if (this.temporaryEditValue) {
    const id = parseInt(event.target.parentElement.id)

    this.model.editTodo(id, this.temporaryEditValue)
    this.temporaryEditValue = ''
  }
}

I admit the solution is a bit messy , because temporaryEditValue Variables should technically be in the view, not in the controller , Because it's a view related state .

Now we can add these to the view's event listener . When you are in contenteditable Element input ,input Event will be triggered , Leave contenteditable Element time ,focusout Will trigger .

// View 
bindEvents(controller) {
  this.form.addEventListener('submit', controller.handleAddTodo)
  this.todoList.addEventListener('click', controller.handleDeleteTodo)
  this.todoList.addEventListener('input', controller.handleEditTodo)
  this.todoList.addEventListener('focusout', controller.handleEditTodoComplete)
  this.todoList.addEventListener('change', controller.handleToggle)
}

Now? , When you click on any to-do , Will enter the “ edit ” Pattern , This will update the temporary state variable , When a to-do item is selected or clicked , Will be saved in the model and reset the temporary state .

summary

Now you have a pure JavaScript Written todo Program , It demonstrates the model - View - The concept of controller architecture . Here is a link to the demo and source code .

  • View the demo of the program [11]
  • Look at the source code of the program [12]

I hope this tutorial can help you understand MVC. Using this loosely coupled pattern can add a lot of templates and abstractions to the program , It's also a pattern that developers are familiar with , Is an important concept commonly used in many frameworks .

Reference

[1]

model-view-controller:https://en.wikipedia.org/wiki/Model-view-controller

[2]

This todo Applications :https://taniarascia.github.io/mvc

[3]

Abreast of the times JavaScript grammar :https://www.taniarascia.com/es6-syntax-and-feature-overview/

[4]

View the demo of the program :https://taniarascia.github.io/mvc

[5]

Look at the source code of the program :https://github.com/taniarascia/mvc

[6]

This file :https://github.com/taniarascia/mvc/blob/master/style.css

[7]

understand JavaScript Class in :https://www.taniarascia.com/understanding-classes-in-javascript/

[8]

local storage:https://www.taniarascia.com/how-to-use-local-storage-with-javascript/

[9]

DOM brief introduction :https://www.taniarascia.com/introduction-to-the-dom/

[10]

How to use JavaScript local storage:https://www.taniarascia.com/how-to-use-local-storage-with-javascript/

[11]

View the demo of the program :https://taniarascia.github.io/mvc

[12]

Look at the source code of the program :https://github.com/taniarascia/mvc




Strongly recommend the magic tool of front end interview


 picture

 picture
Review of wonderful articles , Click through


 picture

 picture


版权声明
本文为[Front end pioneer]所创,转载请带上原文链接,感谢
https://javamana.com/2021/06/20210623173903037E.html

  1. 【计算机网络 12(1),尚学堂马士兵Java视频教程
  2. 【程序猿历程,史上最全的Java面试题集锦在这里
  3. 【程序猿历程(1),Javaweb视频教程百度云
  4. Notes on MySQL 45 lectures (1-7)
  5. [computer network 12 (1), Shang Xuetang Ma soldier java video tutorial
  6. The most complete collection of Java interview questions in history is here
  7. [process of program ape (1), JavaWeb video tutorial, baidu cloud
  8. Notes on MySQL 45 lectures (1-7)
  9. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  10. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  11. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  12. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  13. 【递归,Java传智播客笔记
  14. [recursion, Java intelligence podcast notes
  15. [adhere to painting for 386 days] the beginning of spring of 24 solar terms
  16. K8S系列第八篇(Service、EndPoints以及高可用kubeadm部署)
  17. K8s Series Part 8 (service, endpoints and high availability kubeadm deployment)
  18. 【重识 HTML (3),350道Java面试真题分享
  19. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  20. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  21. [re recognize HTML (3) and share 350 real Java interview questions
  22. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  23. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  24. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  25. RPC 1: how to develop RPC framework from scratch
  26. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  27. RPC 1: how to develop RPC framework from scratch
  28. 一次性捋清楚吧,对乱糟糟的,Spring事务扩展机制
  29. 一文彻底弄懂如何选择抽象类还是接口,连续四年百度Java岗必问面试题
  30. Redis常用命令
  31. 一双拖鞋引发的血案,狂神说Java系列笔记
  32. 一、mysql基础安装
  33. 一位程序员的独白:尽管我一生坎坷,Java框架面试基础
  34. Clear it all at once. For the messy, spring transaction extension mechanism
  35. A thorough understanding of how to choose abstract classes or interfaces, baidu Java post must ask interview questions for four consecutive years
  36. Redis common commands
  37. A pair of slippers triggered the murder, crazy God said java series notes
  38. 1、 MySQL basic installation
  39. Monologue of a programmer: despite my ups and downs in my life, Java framework is the foundation of interview
  40. 【大厂面试】三面三问Spring循环依赖,请一定要把这篇看完(建议收藏)
  41. 一线互联网企业中,springboot入门项目
  42. 一篇文带你入门SSM框架Spring开发,帮你快速拿Offer
  43. 【面试资料】Java全集、微服务、大数据、数据结构与算法、机器学习知识最全总结,283页pdf
  44. 【leetcode刷题】24.数组中重复的数字——Java版
  45. 【leetcode刷题】23.对称二叉树——Java版
  46. 【leetcode刷题】22.二叉树的中序遍历——Java版
  47. 【leetcode刷题】21.三数之和——Java版
  48. 【leetcode刷题】20.最长回文子串——Java版
  49. 【leetcode刷题】19.回文链表——Java版
  50. 【leetcode刷题】18.反转链表——Java版
  51. 【leetcode刷题】17.相交链表——Java&python版
  52. 【leetcode刷题】16.环形链表——Java版
  53. 【leetcode刷题】15.汉明距离——Java版
  54. 【leetcode刷题】14.找到所有数组中消失的数字——Java版
  55. 【leetcode刷题】13.比特位计数——Java版
  56. oracle控制用户权限命令
  57. 三年Java开发,继阿里,鲁班二期Java架构师
  58. Oracle必须要启动的服务
  59. 万字长文!深入剖析HashMap,Java基础笔试题大全带答案
  60. 一问Kafka就心慌?我却凭着这份,图灵学院vip课程百度云