• 周五. 12月 2nd, 2022

5G编程聚合网

5G时代下一个聚合的编程学习网

热门标签

Build an MVC framework with pure JavaScript

[db:作者]

1月 6, 2022
//  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

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注