javascript - How can I dynamically wrap a substring with a component in Vue.js? - Stack Overflow

What am I trying to doGiven a string from a user input, I am attempting to render this string with part

What am I trying to do

Given a string from a user input, I am attempting to render this string with particular substrings wrapped in a ponent. In my particular case the substring being matched is a date that matches a regex pattern and the ponent that is supposed to wrap it is a chip from Vuetify.

What I have done

Above is a screenshot of what I have achieved so far. The input of the textarea is rendered below it with certain substrings wrapped in the chip ponent from Vuetify. The above was achieved by replacing the substring matched by a regex pattern with the HTML to render the ponent and giving this string to a v-html directive for rendering. Below is some code showing how this was done.

<div style="line-height:40px;" v-html="messageOutput"></div>
let finalStr = ''
let str = 'Your a/c no. XXXXXXXXXX85 is credited on 15-11-17.'

let dateReg = /((?=\d{4})\d{4}|(?=[a-zA-Z]{3})[a-zA-Z]{3}|\d{2})((?=\/)\/|-)((?=[0-9]{2})[0-9]{2}|(?=[0-9]{1,2})[0-9]{1,2}|[a-zA-Z]{3})((?=\/)\/|-)((?=[0-9]{4})[0-9]{4}|(?=[0-9]{2})[0-9]{2}|[a-zA-Z]{3})/

const date = dateReg.exec(str)
finalStr = str.replace(date[0], `
 <div class="md-chip md-chip-clickable">
  <div class="md-chip-icon primary"><i class="material-icons">date_range</i></div>
   ${date[0]}
 </div>
`)

What is not working

The problem is using custom ponents as opposed to plain HTML does not give the expected output. The styling is not rendered and the ponent does not react to events.

How can I dynamically wrap a substring with a ponent in Vue.js?

What am I trying to do

Given a string from a user input, I am attempting to render this string with particular substrings wrapped in a ponent. In my particular case the substring being matched is a date that matches a regex pattern and the ponent that is supposed to wrap it is a chip from Vuetify.

What I have done

Above is a screenshot of what I have achieved so far. The input of the textarea is rendered below it with certain substrings wrapped in the chip ponent from Vuetify. The above was achieved by replacing the substring matched by a regex pattern with the HTML to render the ponent and giving this string to a v-html directive for rendering. Below is some code showing how this was done.

<div style="line-height:40px;" v-html="messageOutput"></div>
let finalStr = ''
let str = 'Your a/c no. XXXXXXXXXX85 is credited on 15-11-17.'

let dateReg = /((?=\d{4})\d{4}|(?=[a-zA-Z]{3})[a-zA-Z]{3}|\d{2})((?=\/)\/|-)((?=[0-9]{2})[0-9]{2}|(?=[0-9]{1,2})[0-9]{1,2}|[a-zA-Z]{3})((?=\/)\/|-)((?=[0-9]{4})[0-9]{4}|(?=[0-9]{2})[0-9]{2}|[a-zA-Z]{3})/

const date = dateReg.exec(str)
finalStr = str.replace(date[0], `
 <div class="md-chip md-chip-clickable">
  <div class="md-chip-icon primary"><i class="material-icons">date_range</i></div>
   ${date[0]}
 </div>
`)

What is not working

The problem is using custom ponents as opposed to plain HTML does not give the expected output. The styling is not rendered and the ponent does not react to events.

How can I dynamically wrap a substring with a ponent in Vue.js?

Share Improve this question edited Nov 23, 2017 at 0:46 Wing 9,7314 gold badges42 silver badges52 bronze badges asked Nov 22, 2017 at 9:25 Em Ji MadhuEm Ji Madhu 7242 gold badges9 silver badges25 bronze badges 4
  • "I give a string with date in it." where does string e from? How are you finding it? When is the JS method running – does it run on a specific user event, or is it called from a ponent method? Would you be able to share the code for the entire ponent? – Wing Commented Nov 22, 2017 at 10:54
  • @wing as you can see in the picture. its a modal box with text area. User will enter the message. and when user clicks on the load button, i invoke the method. and it will show the output as in the picture. – Em Ji Madhu Commented Nov 22, 2017 at 10:56
  • If I understand it correctly, you are trying to render a string submitted by the user with HTML wrapped around pieces of information captured by RegEx patterns? – Wing Commented Nov 22, 2017 at 11:01
  • @wing yes exactly – Em Ji Madhu Commented Nov 22, 2017 at 11:17
Add a ment  | 

1 Answer 1

Reset to default 6

Problem

The problem of custom ponents not working as expected stems from the attempt of including them inside a v-html directive. Due to the value of the v-html directive being inserted as plain HTML, by setting an element's innerHTML, data and events are not reactively bound.

Note that you cannot use v-html to pose template partials, because Vue is not a string-based templating engine. Instead, ponents are preferred as the fundamental unit for UI reuse and position.

– Vue guide on interpolating raw HTML

[v-html updates] the element’s innerHTML. Note that the contents are inserted as plain HTML - they will not be piled as Vue templates. If you find yourself trying to pose templates using v-html, try to rethink the solution by using ponents instead.

– Vue API documentation on the v-html directive

Solution

Components are the fundamental units for UI reuse and position. We now must build a ponent that is capable of identifying particular substrings and wrapping a ponent around them. Vue's ponents/templates and directives by themselves would not be able to handle this task – it just isn't possible. However Vue does provide a way of building ponents at a lower level through render functions.

With a render function we can accept a string as a prop, tokenize it and build out a view with matching substrings wrapped in a ponent. Below is a naive implementation of such a solution:

const Chip = {
  template: `
    <div class="chip">
      <slot></slot>
    </div>
  `,
};

const SmartRenderer = {
  props: [
    'string',
  ],

  render(createElement) {
    const TOKEN_DELIMITER_REGEX = /(\s+)/;
    const tokens = this.string.split(TOKEN_DELIMITER_REGEX);
    const children = tokens.reduce((acc, token) => {
      if (token === 'foo') return [...acc, createElement(Chip, token)];
      return [...acc, token];
    }, []);

    return createElement('div', children);
  },
};

const SmartInput = {
  ponents: {
    SmartRenderer
  },

  data: () => ({
    value: '',
  }),

  template: `
    <div class="smart-input">
      <textarea
        class="input"
        v-model="value"
      >
      </textarea>
      <SmartRenderer :string="value" />
    </div>
  `,
};

new Vue({
  el: '#root',

  ponents: {
    SmartInput,
  },

  template: `
    <SmartInput />
  `,

  data: () => ({}),
});
.chip {
  display: inline-block;
  font-weight: bold;
}

.smart-input .input {
  font: inherit;
  resize: vertical;
}

.smart-input .output {
  overflow-wrap: break-word;
  word-break: break-all;
}
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <link rel="stylesheet" href="https://unpkg./[email protected]/dist/milligram.css">
</head>
<body>
  <p>Start typing below. At some point include <strong>foo</strong>, separated from other words with at least one whitespace character.</p>
  <div id="root"></div>
  <script src="https://unpkg./[email protected]/dist/vue.min.js"></script>
</body>
</html>

There is a SmartRenderer ponent which accepts a string through a prop. Within the render function we:

  1. Tokenize the string by splitting it by whitespace characters.
  2. Build up an array of elements to be rendered by iterating through each token and
  3. Checking if the token matches a rule (in our naive implementation seeing if the string matches foo) and wrapping it in a ponent (in our naive implementation the ponent is a Chip, which just makes the foo bold) otherwise leave the token as is.
  4. Accumulating the result of each iteration in an array.
  5. The array of Step 3 is then passed to createElement as the children of a div element to be created.
render(createElement) {
  const TOKEN_DELIMITER_REGEX = /(\s+)/;
  const tokens = this.string.split(TOKEN_DELIMITER_REGEX);
  const children = tokens.reduce((acc, token) => {
    if (token === 'foo') return [...acc, createElement(Chip, token)];
    return [...acc, token];
  }, []);

  return createElement('div', children);
},

createElement takes an HTML tag name, ponent options (or a function) as its first argument and in our case the second argument takes a child or children to render. You can read more about createElement in the docs.

The solution as posted has some unsolved problems such as:

  • Handling a variety of whitespace characters, such as newlines (\n).
  • Handling multiple occurrences of whitespace characters, such as (\s\s\s).

It is also naive in the way it checks if a token needs to be wrapped and how it wraps it – it's just an if statement with the wrapping ponent hard-coded in. You could implement a prop called rules which is an array of objects specifying a rule to test and a ponent to wrap the token in if the test passes. However this solution should be enough to get you started.

Further reading

  • Vue guide on render functions

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744977129a4604213.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信