1. What is amis and how do you use it

1.1 Introduction to concepts

Amis: Front-end low-code framework that generates various backend pages through JSON configuration, greatly reducing development costs and even requiring no front-end knowledge.

Git git git git git git git git git git git git git It looks pretty bad. But it really does. Let’s go and see.

Before INTRODUCING Amis, you might not know what a low-code platform is. Here xiaobai with the help of a simple introduction of zhihu’s answer

Low-code frameworks can be thought of as evolved versions of common frameworks. We all know that frameworks like React and Vue were developed to simplify development. But using them still requires some front-end knowledge, so can the framework evolve to the end to achieve such an effect. We don’t need any knowledge, (like some quick visualization of a website) the framework automatically helps us generate a page

Like this:

Uh huh… If frameworks go in this direction in the future, will a bunch of front-end engineers be optimized?

I don’t know what the future holds, but at least for now this low code framework is not ready. They are not very customizable, but they are super easy to develop for some MIS projects

As a professional front-end development, we are certainly not using what visual things to do. Let’s focus on how amis uses JSON configuration to write pages

1.2 How to Use Amis

Amis is used in two ways: 1 through SDK and 2 through React. Let’s use the react form as an example.

I’m just going to pull up a demo of it and look directly at app.tsx

I’m going to fold up everything that’s not so important just to make it a little bit clearer

It looks like renderAmis is responsible for converting the stuff passed to it into a JSX, i.e. (schema, props, env) => jsx.element;

It takes three arguments

  • schema
  • props
  • env

The most important thing here is the schema, which is the JSON configuration that we’re going to write. So let’s just talk a little bit about what the other two do

Props: Something like a global variable in which the data inside the props is passed to all the internal components

Env: utility functions passed to the component, comparing requests, system messages…

Now let’s see how we can use amis to write components, like a form

  render() {
    return renderAmis(

      {
        "type": "page"."body": {
          "type": "form"."api": "https://houtai.baidu.com/api/mock2/form/saveForm"."controls": [{"type": "text"."name": "name"."label": "Name:"
            },
            {
              "name": "email"."type": "email"."label": "Email:"}]}}, {// props...
      },
      env
    );
  }
Copy the code

A brief description of what attributes mean

  • Type: Specifies the component type of amis
  • Body: can be understood as a container property
          "type": "form"Is specified as a form component"api": "https://houtai.baidu.com/api/mock2/form/saveForm", the address to which the form component submits"controls": [The {that holds the form item"type": "text", specifying the Text component for input"name": "name", equivalent to the input name value, same as below"label": "Name:"
            },
            {
              "name": "email"."type": "email"."label": "Email:"}]Copy the code

I’m not going to talk about amis anymore but I’m going to write my own amis component and look at the documentation

Two: A brief introduction to amis fundamentals

Our main goal is to encapsulate an AMIS component. We definitely need to understand the rationale before we can encapsulate it. How can we write a JSON configuration and end up with a corresponding page?

How it works is pretty simple. What would a normal UI framework look like? Write the template component and export it directly. This is just one more step.

Amis’s core job is you give a schema and it maps you to the corresponding template component and React does the rendering

So the key is how does it generate this mapping

For example, in the example above, why does type: Form amis help me map to the form template component already written inside it

There must be two steps to get there

  1. Write the template component with a “unique identifier “, that is, mark it so that we can locate it by Courier
  2. Schema resolution, for example, resolves to a schema with a type value of “form”, and immediately locates the template component

The core code is here, and you can refer to it yourself if you are interested (this article will not be introduced in detail).

Since these are the two most important, and we found that at the beginning. The renderAmis function in app.tsx does the job of parsing JSON

So we’re going to have to write our own custom Amis component and that’s one step away from having a logo when we write the template component.

Happily, though, Amis also provides one such utility function, Renderer. It’s a HOC

Like this

@Renderer({
  test: /(^|\/)my-amis$/
})
export class FormRenderer extends React.Component { 
  render() {
    return (
      <div>Hello.</div>); }}Copy the code

So now we have a simple mapping

When you specify type as my-amis in the schema, the react component is found and rendered. The other values in the schema are passed in as props for this component

In fact, it is to make a dictionary, simple post its implementation (first remove unimportant) core code in factory. TSX are interested in direct access to ok

Three: expression input box to do things and pay attention to the pit

All right, here we go!

Let’s take a look at the final effect we want to achieve next (we can’t use emoji directly either because of front-end display or database storage, so I will choose the emoji picture here).

This is a simple emoticon input box. I chose this as an example because I found this little thing easy to look at but still a lot of bugs

So let’s first decide how do we divide components

  • The editing zone
  • Face area

If you haven’t done this kind of requirement before, what do you do? Just make a Textarea in the edit area, okay?

That’s what I was thinking, but just to be clear this input box is the most flawed requirement

3.1.1 Dictionary mapping and preparation of basic components

The dictionary mapping

The following is a private message from CSDN that uses a simple textarea directly

Look at the Nuggets, (from the technical point of view, you know who is more professional bar, and directly put the emoji format to the background presumably it also updated the database. Because even though emoji is encoded in Unicode, it looks like it’s encoded in UTF8 which is 4 bytes in the original mysql utF8 which is 3 bytes. Too lazy to look it up)

In order not to touch the database, we transfer the information to [xx] when it is sent

So the first thing to do is to make a mapping, such as the input box should have [smile]=>πŸ™‚, send from πŸ™‚ to [smile](here is a small emoji-formatted operation we put a picture). It’s a little bit easier to get a dictionary

Something like this :(the corresponding value is the address of each little emoticon)

const emojiDictionary = {
    "[smiling]": imgMap.img01,
    "[pie mouth]": imgMap.img02,
    "[color]": imgMap.img03,
    "[stunned]": imgMap.img04,
    "[得意]": imgMap.img05,
    "[and]": imgMap.img06,
    "[shy]": imgMap.img07,
    "[shut]." ": imgMap.img08,
    "[sleep]": imgMap.img09,
    "[crying]": imgMap.img10,
   ...
}

Copy the code

What’s the logic behind that?

  1. Select the emoticon box and click to get the selected little emoticon information
  2. Create an IMG DOM structure and stuff it into the input box
  3. When sending, replace the corresponding IMG DOM with the corresponding [xx] string

Components are written

Let’s write down the basic components first

Input box component

import * as React from 'react';
import { useState } from "react";
import { Renderer } from 'amis';
import classnames from 'classnames'

import Emoji from './emojinew'
import './blog.css'

const emojiInput = (props) = > {

    const [show, setShow] = useState(true)

    const showEmoji = () = >{ setShow(! show) }return (
        <div className="emoji-input">
            <div className="input-textarea">Please input...</div>
            <div className="input-col">
                <i className="iconfont icon-biaoqing" onClick={showEmoji}></i>
                <i className="iconfont icon-icon-"></i>
            </div>
            <div className={classnames('input-emoji'{show: show})} >
                <Emoji />
            </div>
        </div>
    );
}


const emojiInputRender = Renderer({
    test: /(^|\/)emoji-input$/,
    name: "emoji-input"
})(emojiInput)
export default emojiInputRender;
Copy the code

Emoticons component

import * as React from 'react';
import { useEffect } from 'react'
import { Scrollbars } from 'react-custom-scrollbars';


import emojiDictionary from '.. /lib/emojiDictionaries'
const newEmojiDictionary = Object.entries(emojiDictionary)

const EmojiItem = (props) = > {
    const { msg, pic } = props
    return (
        <span className="emoji-item-img">
            <img src={pic} data-msg={msg} />
        </span>)}const EmojiNew = (props) = > {
    const { } = props
    useEffect(() = > {
        console.log();
    })
    return (
        <Scrollbars
            style={{ height: '100px'}}autoHide
        >
            <div className="emoji-new">

                {
                    newEmojiDictionary.map(
                        item => {
                            return <EmojiItem
                                key={item[0]}
                                msg={item[0]}
                                pic={item[1]}
                            />})}</div>
        </Scrollbars >
    );
}

export default EmojiNew;
Copy the code

3.1.2 Adding events, parsing and replacing

Now let’s keep it simple and put the little emoji at the end every time we click on it.

Pass a processing event to the emoticon component and add it to the Input component

   const clickEmoji = (pic, msg) = > {
        const img = document.createElement('img')
        img.src = pic
        img.setAttribute("data-msg", msg)
        inputRef.current.appendChild(img)

    }
Copy the code

Pass to EmojiItem as a handler for its click event and take it to PIC and MSG

const EmojiItem = (props) = > {
    const { msg, pic, clickEmoji } = props
    return (
        <span className="emoji-item-img" onClick={()= > { clickEmoji(pic, msg) }}>
            <img src={pic} data-msg={msg} />
        </span>)}Copy the code

And then how do you edit text in div? It’s easy to add a property to the div

However, setting it in React will cause a nasty warning on the console, so you need to set another property to kill it.

To this final form

 <div className="input-textarea" ref={inputRef} contentEditable={true}
                suppressContentEditableWarning={true}
            >Please input...</div>
Copy the code

Ok, so now we have the basic effect

Replace img with description [xx]

One word of caution: the innerHTML of the DOM is completely handled as a string. So that’s all we need to do now let’s just make a regular string substitution

Give the small plane icon a send click event

    const sendData = () = > {
        let str = inputRef.current.innerHTML
        let imgReg = /
      
       |\/>)/gi
      *?>
        let nameReg = /
      
       ]+data-msg[=\'\"\s]+([^\'\"]*)[\'\"]? [\s\S]*/i
      [^>
        let arr = str.match(imgReg)

        if (arr) {
            for (let i = 0; i < arr.length; i++) {
                let names = arr[i].match(nameReg)
                if (names && names[1]) {
                    str = str.replace(arr[i], names[1])}}}console.log(str)
    }
Copy the code

Okay, so this looks like it’s pretty much done

3.1.3 Handling line breaks and Spaces

Don’t get too excited when you change lines and empty Spaces in the input field

Boy, it turns out that in an editable div it does a line break by making a div

But it’s a little annoying, but it’s pretty easy to handle. Replace div with \n, using the regular method.

namely

   str = str.replace(/<div><\/div>/g."")
   str = str.replace(/<div>/g."\n")
   str = str.replace(/<br>/g."\n")
   str = str.replace(/<\/div>/g."")
Copy the code

Let’s do one more space case

 str = str.replace(/Β  /g."")
Copy the code

One more thing to consider is that if I delete the first line, then the bottom line is this

I’m white.
I haven’t seen you for a long time. We handle is just in front of the < div > with \ n, processing is not finished at the beginning will give somebody else make a line break, it’s not very suitable

Make a judgment call, if \n is in the front, then kill it

  if (str.indexOf('\n') = = =0) {
            str = str.replace(/\n/."")}Copy the code

Well, we’ve almost finished our work at this stage

3.1.4 Handle the style problem of copied text

So let’s try it out here, so let’s go to any url and paste a little text into it

Oh, my God, this is really just me. We expected to post a plain text, but we didn’t expect to post the whole style

How to do this, or re to get the data? This looks like a pain in the head. How do you write this re?

When I was working on this, the main idea was how I should write the re. But when I look for relevant information, I find myself like a fool…

Remember how we made a div editable, adding a contentEditable property to it. We gave it a value of true. But the contentEditable has other property values as well. Plaintext-only Look at this property value literal seems to be what we need. Let’s give it a try

Er… Cowhide. The style problem is that I gave the edit box a fixed height. It would have been better if the content had supported it.

3.1.5 Dealing with focus issues

Getting here, we’ve taken care of the basics, but don’t forget. At first we just made the little emoticons at the end of the editing box by default, but who’s got such a stupid thing… It’s supposed to follow the cursor

Let’s deal with this last problem

The solution is not complicated. Like in the picture above, we want to put an expression after failure. The first step is to set the cursor there, and then click the emoticon box to select the corresponding emoticon

So what do we do, you know when we select an expression in the emoticon box and click on it the focus is gone from the input box, so we need to record the last position, right

Give the edit box an out-of-focus event to record the last cursor position (MDN if you’re not familiar with getSelection)

Let’s put one more variable in there

  // Lose focus on things to do
const record = () = > {
        let selection = getSelection()
        let range = selection.getRangeAt(0)
        lastRange = range
    }
Copy the code

Re-engineer the processing function for clicking on the little emoticons

    const clickEmoji = (pic, msg) = > {
        const img = document.createElement('img')
        img.src = pic
        img.setAttribute("data-msg", msg)
        let selection = window.getSelection()
        // See if there is a final cursor object
        if (lastRange) {
            selection.removeAllRanges()
            selection.addRange(lastRange)
        }
        let range = selection.getRangeAt(0)
        range.insertNode(img)
        range.collapse(false)
        lastRange = range

    }
Copy the code

And you’re done!

Four: Write at the end

These months have been very busy, recently calm down to think. I don’t know what I’ve been up to. It’s really a little impetuous. Next oneself can’t stuffy head to play again, each stage should have the goal of each stage, the person is after all inert, still have to force oneself right amount

2021 Workers refueling together

With the help of a lyric

I’m tired of hearing the irony of ripping off those labels

I never thought of being a dreamer

Always too idealistic doesn’t it need inspiration

Go through each stage with your heart and you don’t need any judgment from them

Lead your own team not to be a loser from the beginning never thought of fallback

No compromise no matter who makes it up don’t try to tie me down