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
1 Answer
Reset to default 6Problem
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’sinnerHTML
. 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 usingv-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:
- Tokenize the string by splitting it by whitespace characters.
- Build up an array of elements to be rendered by iterating through each token and
- 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 aChip
, which just makes thefoo
bold) otherwise leave the token as is. - Accumulating the result of each iteration in an array.
- The array of Step 3 is then passed to
createElement
as the children of adiv
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条)