How to get raw html from a $refs? - vue.js

I need to retrieve raw HTML from a component and then insert it into a textarea.
For that I wrapped my component in a DIV:
<div id="content" ref="content">
<Nl v-bind:json="json" />
</div>
When my json change I would like to get a raw html of the generated text based on my json:
watch: {
json: function (val) {
console.log(this.$refs.content);
console.log(this.$refs.content.innerHTML);
this.rawHtml = ???;
}
},
Here's what I get with my two console.log():
The innerHtml give me a html comment?! How can I get a raw html of this.$refs.content ?

You may have to wait the DOM being updated before reading it. You can use $nextTick.
watch: {
json(val) {
this.$nextTick().then(() => {
console.log(this.$refs.content);
console.log(this.$refs.content.innerHTML);
this.rawHtml = this.$refs.content.innerHTML;
});
}
},
Works fine for me.

Related

Vue class components dynamically add component depending on answer from backend

So from the backend I get a array of objects that look kind of like this
ItemsToAdd
{
Page: MemberPage
Feature: Search
Text: "Something to explain said feature"
}
So i match these values to enums in the frontend and then on for example the memberpage i do this check
private get itemsForPageFeatures(): ItemsToAdd[] {
return this.items.filter(
(f) =>
f.page== Pages.MemberPage &&
f.feature != null
);
}
What we get from the backend will change a lot over time and is only the same for weeks at most. So I would like to avoid to have to add the components in the template as it will become dead code fast and will become a huge thing to have to just go around and delete dead code. So preferably i would like to add it using a function and then for example for the search feature i would have a ref on the parent like
<SearchBox :ref="Features.Search" />
and in code just add elements where the ItemsToAdd objects Feature property match the ref
is this possible in Vue? things like appendChild and so on doesn't work in Vue but that is the closest thing i can think of to kind of what I want. This function would basically just loop through the itemsForPageFeatures and add the features belonging to the page it is run on.
For another example how the template looks
<template>
<div class="container-fluid mt-3">
<div
class="d-flex flex-row justify-content-between flex-wrap align-items-center"
>
<div class="d-align-self-end">
<SearchBox :ref="Features.Search" />
</div>
</div>
<MessagesFilter
:ref="Features.MessagesFilter"
/>
<DataChart
:ref="Features.DataChart"
/>
So say we got an answer from backend where it contains an object that has a feature property DataChart and another one with Search so now i would want components to be added under the DataChart component and the SearchBox component but not the messagesFilter one as we didnt get that from the backend. But then next week we change in backend so we no longer want to display the Search feature component under searchbox. so we only get the object with DataChart so then it should only render the DataChart one. So the solution would have to work without having to make changes to the frontend everytime we change what we want to display as the backend will only be database configs that dont require releases.
Closest i can come up with is this function that does not work for Vue as appendChild doesnt work there but to help with kind of what i imagine. So the component to be generated is known and will always be the same type of component. It is where it is to be placed that is the dynamic part.
private showTextBoxes() {
this.itemsForPageFeatures.forEach((element) => {
let el = this.$createElement(NewMinorFeatureTextBox, {
props: {
item: element,
},
});
var ref = `${element.feature}`
this.$refs.ref.appendChild(el);
});
}
You can use dynamic components for it. use it like this:
<component v-for="item in itemsForPageFeatures" :is="getComponent(item.Feature)" :key="item.Feature"/>
also inside your script:
export default {
data() {
return {
items: [
{
Page: "MemberPage",
Feature: "Search",
Text: "Something to explain said feature"
}
]
};
},
computed: {
itemsForPageFeatures() {
return this.items.filter(
f =>
f.Page === "MemberPage" &&
f.Feature != null
);
}
},
methods: {
getComponent(feature) {
switch (feature) {
case "Search":
return "search-box";
default:
return "";
}
}
}
};

How to handle event on html render by computed function in vue js?

I'm using vue 2. I have a text get from api.
"Hello everyone! My name is [input]. I'm [input] year old".
Now, I have to replace the [input] with an html input and handle the onKeyUp for this input.
What I have to do?
I used computed render html, but it not work with v-on:xxx.
content.replaceAll('[answer]', '<input type="text" class="input_answer" v-on:click="handleInput()"/>')
Thanks!
After spending an hour and so on this requirement, I came up with the solution.
Here you go (I added all the descriptive comments/steps in the below code snippet itself) :
// Template coming from API
var textFromAPI = "<p>Hello everyone! My name is [input]. I'm [input] year old</p>";
// getting the array of input tags. So that we can loop and create the proper input element.
const matched = textFromAPI.match(/(input)/g);
// Iterating over an array of matched substrings and creating a HTML element along with the required attributes and events.
matched.forEach((el, index) => {
textFromAPI = textFromAPI.replace('[input]', `<input type="text" id="${index + 1}" v-model="inputValue[${index}]" v-on:keyup="getValue"/>`);
})
// Here, we are compiling the whole string so that it will behave in a Vue way.
var res = Vue.compile(textFromAPI)
var app = new Vue({
el: '#app',
data: {
compiled: null,
inputValue: []
},
render: res.render,
staticRenderFns: res.staticRenderFns,
mounted() {
setTimeout(() => {
this.compiled = res;
})
},
methods: {
getValue() {
// Here you will get the updated values of the inputs.
console.log(this.inputValue);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
</div>
Thanks # Rohit Jíndal, but!
When I use vue2 and it doesn't work. And there is an error:
TypeError: Cannot read properties of undefined (reading 'string')
I build this to a component and use anywhere in my project.
<render-html :text="question.quest_content" #handleAnswer="handleAnswer"></render-html>
I used and it's work.
this.$options.staticRenderFns = string.staticRenderFns;
return string.render.bind(this)(h)
Thanks so much!

Twitter typeahead.js not working in Vue component

I'm trying to use Twitter's typeahead.js in a Vue component, but although I have it set up correctly as tested out outside any Vue component, when used within a component, no suggestions appear, and no errors are written to the console. It is simply as if it is not there. This is my typeahead setup code:
var codes = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('code'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
prefetch: contextPath + "/product/codes"
});
$('.typeahead').typeahead({
hint: true,
highlight: true,
minLength: 3
},
{
name: 'codes',
display: 'code',
source: codes,
templates: {
suggestion: (data)=> {
return '<div><strong>' + data.code + '</strong> - ' + data.name + '</div>';
}
}
});
I use it with this form input:
<form>
<input id="item" ref="ttinput" autocomplete="off" placeholder="Enter code" name="item" type="text" class="typeahead"/>
</form>
As mentioned, if I move this to a div outside Vue.js control, and put the Javascript in a document ready block, it works just fine, a properly formatted set of suggestions appears as soon as 3 characters are input in the field. If, however, I put the Javascript in the mounted() for the component (or alternatively in a watch, I've tried both), no typeahead functionality kicks in (i.e., nothing happens after typing in 3 characters), although the Bloodhound prefetch call is made. For the life of me I can't see what the difference is.
Any suggestions as to where to look would be appreciated.
LATER: I've managed to get it to appear by putting the typeahead initialization code in the updated event (instead of mounted or watch). It must have been some problem with the DOM not being in the right state. I have some formatting issues but at least I can move on now.
The correct place to initialize Twitter Typeahead/Bloodhound is in the mounted() hook since thats when the DOM is completely built. (Ref)
Find below the relevant snippet: (Source: https://digitalfortress.tech/js/using-twitter-typeahead-with-vuejs/)
mounted() {
// configure datasource for the suggestions (i.e. Bloodhound)
this.suggestions = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('title'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
identify: item => item.id,
remote: {
url: http://example.com/search + '/%QUERY',
wildcard: '%QUERY'
}
});
// get the input element and init typeahead on it
let inputEl = $('.globalSearchInput input');
inputEl.typeahead(
{
minLength: 1,
highlight: true,
},
{
name: 'suggestions',
source: this.suggestions,
limit: 5,
display: item => item.title,
templates: {
suggestion: data => `${data.title}`;
}
}
);
}
You can also find a working example: https://gospelmusic.io/
and a Reference Tutorial to integrate twitter typeahead with your VueJS app.

how to do pagination in vuejs

hi i want to do pagination in my view page.can anyone tell me how to do that in vuejs..
Here is my view page:
<div class="container">
<div class="row">
<el-row :gutter="12">
<el-col>
<p>View Candidates</p>
</el-col>
</el-row>
<el-row :gutter="12">
<template v-for="c in candidates">
<el-col :span="6">
<Candidate :c="c" :key="c.id"></Candidate>
</el-col>
</template>
</el-row>
</div>
here is my js page:
const app = new Vue({
el: '#app',
data() {
return {
candidates: window.data.candidates,
}
},
components: { Candidate }
});
i am working on laravel5.4 and vuejs 2
Please can anyone help me..how to do this..
For real pagination you will need to ensure that your endpoints (from your post I'd say something like /candidates) will return json AND that it will return a pagniated object ofcourse.
In Laravel you'd do it like
public function index() {
return Candidates::paginate(10);
}
EDIT: for more information regarding laravel pagination you can take a look at their examples and docs: https://laravel.com/docs/5.4/pagination
A full example is rather hard to give but here a really short one
routes/web.php
Route::get('candidates', 'CandidateController#index');
app/http/controller/CandidateController
public function index() {
$candidates = App\Candidate::paginate(10);
return $candidates;
}
For a more detailed version of the laravel part you should provide your Controller, Migration, Routing setup.
In Vue I'd suggest you load all your data from within Vue and not with blade. Even though you could keep it as it is - it would be more "unified".
data: function() {
return { paginator: null }
},
created: function() {
// load initial first 10 entries
axios.get('/candidates').then(function(response) {
this.paginator = response.data;
}.bind(this));
}
Ok so now you have the initial load as you had it before. You can loop through pagniator.data which is your actual list now. Small example:
<ul v-if="paginator"><!-- important !not loaded on initial render-->
<li v-for="paginator.data as candidate">{{ candidate.name }}</li>
</ul>
Now to the load more. Let's say you want a button for that. The paginator has a pro called next_page_url to give you the next http endpoint. If it's null - now data is left to load.
<button v-if="paginator && paginator.next_page_url" #click.prevent="loadMore">Load more</button>
Button is setup - now the load more
methods: {
loadMore: function() {
// load next 10 elements
axios.get(this.paginator.next_page_url).then(function(response) {
// you have two options now. Either replace the paginator fully - then you will actually "page" your results.
// only 10 will be visible at any time
this.paginator = response.data;
}.bind(this));
}
}
There you go this is an actual pagination. If you want to loadMore to add 10 elements to your current list it is a little bit more tricky because you don't want to replace the paginator.data with the new loaded stuff. You want to concat it.
...
axios.get(this.paginator.next_page_url).then(function(response) {
response.data.data = this.paginator.data.concat(response.data.data);
this.paginator = response.data;
}.bind(this));

Rendering template strings in RiotJS

I have a template string "<a onclick={ parent.foo }>Link</a>". I want pass it to other tag and than render it correctly.
I added short example of my code. It doesn't work, just try to show what I need.
<child-tag>
<div>{ opts.data }</div>
</child-tag>
<parent-tag>
<child-tag data={ html }></child-tag>
<script>
this.html = "<a onclick={ parent.foo }>Link</a>";
foo() {
console.log("Hello");
}
</script>
</parent-tag>
Riot will escape the html where you have { opts.data }. You can use the <raw> tag as described in the riot documentation. But it may be best to rewrite the code as such.
https://jsfiddle.net/nnyc688e/1/
<child-tag>
<yield/>
</child-tag>
<parent-tag>
<child-tag> <a onclick={ parent.foo }>Link</a> </child-tag>
foo() {
console.log("Hello");
}
</parent-tag>
That's easier to understand too.