How to render VueJs component based child node text? - vue.js

======Updated with more background=====
I am working on a tool to convert text to a sequence diagram like this:
In the current implementation, the code (on the left) is provisioned programmatically by calling store.dispatch. I would like to make it simpler for other projects to integrate. What I wanted to achieve is to create a web component: <sequence-diagram />. It can be used in this way:
// Format A
<sequence-diagram>
response = BookController.Get(id)
....
</sequence-diagram>
The above DOM element would be rendered as a sequence diagrams (as shown on the right side of the above picture.
For the component to render properly, it needs to know what the "code" is. To pass the code (response = ....) to the component, I know I can use attributes and access it via props like this:
// Format B
<sequence-diagram code="response = ..." />
However, when the code is very long the above format is not as readable (imagine multiline code) as putting the code as child node text. If I use "Format A", how can I get the code content in my web component?
======Original question=====
What I want to implement is like this:
<my-component>
some text
</my-component>
I have managed to make it work by using attributes:
<my-component code="some text" />
Using child node text is much more readable in my case, as the text can be very long.
In the template, it already has a child component. The current template is like
<div> <myChildComponent/> </div>
I don't need to keep the text in the result dom.

I think what you want are slots. (See https://v2.vuejs.org/v2/guide/components.html#Content-Distribution-with-Slots).
The code of your component would look like this:
<div>
<slot></slot>
<myChildComponent/>
</div>
A runnable example
Below we have an alert-box component that displays an error message inside a <div> styled with a red background. This is how we use it:
<alert-box> This email address is already in use. </alert-box>
And it generates HTML that looks like this:
<div>
<strong>Error!</strong>
This email address is already in use.
</div>
See it in action:
Vue.component('alert-box', {
template: `
<div class="demo-alert-box" style="background-color: red; color: white;">
<strong>Error!</strong>
<slot></slot>
</div>
`
})
new Vue({
el: "#app"
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<alert-box> This email address is already in use. </alert-box>
<p> I'm a normal paragraph. </p>
</div>

Related

How to properly create a popup component in Vue 3

As part of becoming a better Vue programmer, I am trying to implement a popup similar to Popper with a clean and Vueish architecture. Here is a simple schematic that I came up with:
So basically there is a target component, which is the reference for the popup's position. The popup can be positioned above, below, right and left of the target, therefore I will need to have access to the target element in my popup. Also, the target can be an arbitrary component. It can be a simple button or span, but also something much more complex.
Then there is the popup itself, which will be put into a modal at the end of the body, It contains the actual content. The content again can be an arbitrary component.
I have a working implementation of the popup, but the basic structure seems to be far from perfect. I am using two slots, one for the target element and one for the content.
Here is what I have come up with so far for the template:
<template>
<div ref="targetContainer">
<slot name="target"></slot>
</div>
<teleport to="body">
<div v-show="show" class="modal" ref="modal">
<div ref="popover" class="popover" :style="{top: popoverTop + 'px', left: popoverLeft + 'px'}">
<slot name="content"></slot>
</div>
</div>
</teleport>
</template>
There are several issues with this that I am not really happy with.
Using the popup is not very simple
When using this popup in another component, two <template> tags are rquired. This is ungly and not very intuitive. A very simple use case looks like this:
<modal :show="showPopup" #close="showPopup=false">
<template v-slot:target>
<button #click="showPopup=true"></button>
</template>
<template v-slot:content>
<div>Hello World!</div>
</template>
</modal>
The target is wrapped in another <div>
This is done to get access to the target element, that I need for the layout. In mounted() I am referencing the target element like this:
let targetElement = this.$refs.targetContainer.children[0];
Is this really the best way to do this? I would like to get rid of the wrapping <div> element, which just asks for unintended side effects.
The best solution would be to get rid of one slot and somehow reference the target element in another way because I only need its layout information, it does not have to be rendered inside the popover component.
Can someone point me in the right direction?
Here is my solution, which was inspired by a comment on my question and which I think is worth sharing.
Instead of putting the target element into a slot, I am now passing its ref as a prop, which makes things much cleaner.
The popover component's template now looks like this.
<template>
<teleport to="body">
<div v-show="show" class="modal" ref="modal">
<div ref="popover" class="popover" :style="{top: popoverTop + 'px', left: popoverLeft + 'px'}">
<slot ref="content"></slot>
</div>
</div>
</teleport>
</template>
I has a targetRefprop, so the component can be simply used like this:
<div ref="myTargetElement" #click="isPopupVisible=true">
</div>
<modal :show="isPopupVisible" #close="isPopupVisible=false" targetRef="myTargetElement">
<!-- popup content goes here -->
</modal>
And after mounting I can access the target element like this:
let targetElement = this.$parent.$refs[this.targetRef];
I like this solution a lot. However, ideas, advice or words of caution are still highly welcome.

Link from Object as src to img tag

I want to pass information (link) form Object, and give it as the src to image. Somehow tag doesn't see it. Even though it console log proper link and the link is working.
Object
setup() {
const state = reactive({
flashcardObject: {
linkToGraphic: 'https://static.fajnyzwierzak.pl/media/uploads/media_image/auto/entry-content/785/mobile/dog-niemiecki.jpg'}
})
return{
state
}
}
Where is the bug
<template>
<div>
<div class="ViewFlashcards">
<div class="image_div">
<img class="picture" src="{{state.flashcardObject.linkToGraphic}}"/>
</div>
</div>
</div>
</template>
Thank you for your help!
Use v-bind, like so:
<img v-bind:src="state.flashcardObject.linkToGraphic" class="picture"/>
Full code:
<template>
<div>
<div class="ViewFlashcards">
<div class="image_div">
<img class="picture" v-bind:src="state.flashcardObject.linkToGraphic"/>
</div>
</div>
</div>
</template>
v-bind allows you to bind an (HTML) attribute to a data property or just some JS code. In this case you just pass along your image URL to the src attribute of the <image>.
Note that mustache syntax, {{ something }}, does not work in HTML attributes; it only will work within elements, like <p>{{ something }}</p>.
Also, note that instead of v-bind:attribute, you can omit the v-bind part and just keep the colon, like so: :attribute. This makes it easier to bind attributes.
For more info and examples see the docs
You should require it and use : to bind the image src to the required path if the image is stored in the app :
<img class="picture" :src="require(state.flashcardObject.linkToGraphic)"/>
or :
<img class="picture" :src="state.flashcardObject.linkToGraphic"/>
if the image is hosted online.

Is there any library or package to show and real time edit in vuejs?

I want to show my portion of code in vuejs template just like below.
So, How can I add show some piece of code & also let the user realtime edit there & render the output above.
Run following snippet! -- Type something in textbox it will change the title accordingly.
new Vue({
el:"#app",
data:{
title:"Hello!"
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div class="alert alert-success" id="app" role="alert">
<h4 class="alert-heading">{{ title }}</h4>
<p>Aww yeah, you successfully read this important alert message. This example text is going to run a bit longer so that you can see how spacing within an alert works with this kind of content.</p>
<hr>
<p class="mb-0">Whenever you need to, be sure to use margin utilities to keep things nice and tidy.</p>
Change Title : <input type="text" v-model="title">
</div>

How to avoid vue component redraw?

I have prepared tag input control in Vue with tag grouping. Templates includes:
<script type="text/x-template" id="tem_vtags">
<div class="v-tags">
<ul>
<li v-for="(item, index) in model.items" :key="index" :data-group="getGroupName(item)"><div :data-value="item"><span v-if="typeof model.tagRenderer != 'function'">{{ item }}</span><span v-if="typeof model.tagRenderer == 'function'" v-html="tagRender(item)"></span></div><div data-remove="" #click="remove(index)">x</div></li>
</ul>
<textarea v-model="input" placeholder="type value and hit enter" #keydown="inputKeydown($event,input)"></textarea>
<button v-on:click="add(input)">Apply</button>
</div>
</script>
I have defined component method called .getGroupName() which relays on other function called .qualifier() that can be set over props.
My problem: once I add any tags to collection (.items) when i type anything into textarea for each keydown .getGroupName() seems to be called. It looks like entering anything to textarea results all component rerender?
Do you know how to avoid this behavior? I expect .getGroupName to be called only when new tag is added.
Heres the full code:
https://codepen.io/anon/pen/bKOJjo?editors=1011 (i have placed debugger; to catch when runtime enters .qualifier().
Any help appriciated.
It Man
TL;DR;
You can't, what you can do is optimize to reduce function calls.
the redraws are dynamic, triggered by data change. because you have functions (v-model and #keydown) you will update the data. The issue is that when you call a function: :data-group="getGroupName(item)" it will execute every time, because it makes no assumptions on what data may have changed.
One way of dealing with is is setting groupName as a computed key-val object that you can look up without calling the function. Then you can use :data-group="getGroupName[item]" without calling the function on redraw. The same should be done for v-html="tagRender(item)"
Instead of trying to fight how the framework handles data input events and rendering, instead use it to your advantage:
new Vue({
el: '#app',
template: '#example',
data() {
return {
someInput: '',
someInputStore: []
};
},
methods: {
add() {
if (this.someInputStore.indexOf(this.someInput) === -1) {
this.someInputStore.push(this.someInput);
this.someInput = '';
}
},
}
});
<html>
<body>
<div id="app"></div>
<template id="example">
<div>
<textarea v-model="someInput" #keyup.enter.exact="add" #keyup.shift.enter=""></textarea>
<button #click="add">click to add new input</button>
<div>
{{ someInputStore }}
</div>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
</body>
</html>
Event modifiers modifying
In the example, you can see that I am using 4 different event modifiers in order to achieve the desired outcome, but I will focus on the combination of them here:
#keyup.enter.exact - allows control of the exact combination of system modifiers needed to trigger an event. In this case, we are looking for the enter button.
#keyup.shift.enter - this is the interesting bit. Instead of trying to hackily prevent the framework from firing on both events, we can use this (and a blank value) to prevent the event we added into #keyup.enter.exact from firing. I must note that ="" is critical to the whole setup working properly. Without it, you aren't giving vue an alternative to firing the add method, as shown by my example.
I hope this helps!

Vue.js: initialize the data object with data from the page or from v-text directive

So I'm just starting to play with Vue.js and I would like to know if there is a nice way to initialize the data object with html tags content from the page.
Basically I have a page that displays information and I would like to turn it into a tiny Vue application to avoid having a separated edit page.
So I added the form with 2 way binding which submits the values via ajax.
The thing is I really want to avoid Flash Of Uncompiled Content, but then I feel like I have to duplicate all my data, once in the html tag, once in the data object.
<div id="app">
<span v-text="prop1">This is filled by the backend</span>
<input v-model="prop1" type="text" v-cloak />
</div>
<script>
new Vue({
el: "#app",
data: {prop1: "This is filled by the backend..again"} // << Can I avoid this?
});
</script>
Could I tell Vue to get the data from the html tags since it's already there?
Thanks!
You are looking for props
Your html element would look something like
<custom-element dataToPass="dataFromHtml"></custom-element>.
Then you can access it in your vue instance via this.dataToPass.
Have a read through the documentation, there is a difference if you pass a string or an expression (from another vue instance for example).
In your example:
<div id="app">
<span prop1="{ backendVariable}" v-text="prop1"></span>
<input v-model="prop1" type="text" v-cloak />
</div>
<script>
new Vue({
el: "#app",
props: ['prop1']
});
</script>
```