I am adding Vue.js to an existing webpage with an HTML table. (I can't control the HTML, but can inject a link to my Vue file.)
I cannot insert dynamic JavaScript, but the data is already on the page. I hence want to give Vue the ability to read the data from the HTML.
The simplified table is like this:
<table>
<thead>
<tr>
<th>Name</th>
<th>DOB</th>
<th>Sex</th>
</tr>
</thead>
<tbody>
<tr>
<td>John</td>
<td>01 Jan</td>
<td>M</td>
</tr>
<tr>
<td>Jane</td>
<td>25 Dec</td>
<td>F</td>
</tr>
</tbody>
</table>
I want to turn this into the equivalent of:
<some-component
v-bind:data="[
{'Name':'John', 'DOB': '01 Jan', 'Sex' : 'M'},
{'Name':'Jane', 'DOB': '25 Dec', 'Sex' : 'F'}
]">
</some-component>
I can obviously loop through the table to create a JavaScript variable manually, but it seems very inefficient - can Vue read data from HTML like this?
Vue has no built-in mechanism to build an object from arbitrary HMTL. (The virtual DOM works in the opposite way, building a virtual HTML structure from objects.)
In this specific case, it's not hard to create the object in plain JavaScript
var tableEl = document.querySelector("table");
var keys = Array.from(tableEl.tHead.rows[0].cells).map(el => el.textContent.trim());
var array = Array.from(tableEl.tBodies[0].rows).map(row => {
var entry = {};
keys.forEach((key, index) => {entry[key] = row.cells[index].textContent.trim();})
return entry;
});
Yes you can do it with vue :
First you need to create the component with the following template:
var template = "
<table>
<thead>
<th>Name</th><th>DOB</th><th>Sex</th>
</thead>
<tbody>
<tr v-for="obj in data" :key="obj.Name">
<td>{{obj["Name"]}}</td>
<td>{{obj["DOB"]}}</td>
<td>{{obj["Sex"]}}</td>
</tr>
</tbody>
</table>"
then just pass the data you want to display on the component below as prop (bind)
run the following snippet :
// the component
Vue.component('some-component',{
props : ['data'],
template : '<table><thead><th>Name</th><th>DOB</th><th>Sex</th></thead><tbody><tr v-for="obj in data" :key="obj.Name"><td>{{obj["Name"]}}</td><td>{{obj["DOB"]}}</td><td>{{obj["Sex"]}}</td></tr></tbody></table>'
})
// the instance
new Vue({
el : '#app'
})
table,th,td {
padding : 10px;
border : 1px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<some-component v-bind:data="[
{'Name':'John', 'DOB': '01 Jan', 'Sex' : 'M'},
{'Name':'Jane', 'DOB': '25 Dec', 'Sex' : 'F'},
{'Name':'foo', 'DOB': '22 Dec', 'Sex' : 'M'},
{'Name':'bar', 'DOB': '21 Dec', 'Sex' : 'M'}
]">
</some-component>
</div>
Related
I have a Vue3 component which make an ajax request to server which sends array with articles back. Then I need to render the html table according the filled model via v-for and then init DataTable via DataTable() call on this rendered table. But it seems the problem is that DataTable() call runs before html table is rendered so the table is empty.
The ajax code looks like:
getArticles()
{
axios.get( apiRoutes.ARTICLES_URL )
.then((response) => {
let data = response.data.articles;
// Fill the model
this.articles = data;
// Init DataTable
$('#dataTable').DataTable({ ... });
//setTimeout(this.setDataTable, 3000); // This works but want to avoid it.
})
},
The template code
<template>
<div class="table-responsive">
<table :id="id" class="table table-bordered nowrap" width="100%;">
<thead>
<tr>
<th>id</th>
<th>Title</th>
<th>Created</th>
<th>Visible</th>
<th>User</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="article in articles" :key="article.id">
<td>{{article.id}}</td>
<td>{{article.title}}</td>
<td data-order='{{article.created_at}}'>{{getCreatedAt(article.created_at)}}</td>
<td>{{article.visible}}</td>
<td>{{article.user.name}}</td>
<td>
{{article.visible === 1 ? 'hide' : 'show'}}
delete
</td>
</tr>
</tbody>
</table>
</div>
The question is how to properly wait for the render is done? Thanks for any help.
So if I understand it I need to use $nextTick() which will wait while the DOM render is done.
I am trying to filter a table that get data from api and I tried this solution but it doesnt work.
I couldnt find where the problem is and if I pass the search input event listener
and here is my table component :
<template>
<table class="mt-12 border-2 border-gray-600">
<thead>
<tr>
<th v-for="header in data.headers" :key="header" class="text-left border-l-2 border-gray-600 border-b-2 border-gray-600 bg-red-400 ">{{ header }}</th>
</tr>
</thead>
<tbody>
<tr v-for="(rows, index) in data.rows" :key="index">
<td v-for="row in rows" :key="row" class="border-l-2 border-gray-600" >{{ row }}</td>
</tr>
</tbody>
</table>
</template>
<script>
export default {
props: {
data: Object,
default: {}
}
}
</script>
<style src="../assets/tailwind.css"/>
My question:
If anyone can help me to define my problem and solve this ?
Thanks for help.
You can use your data as computed where you pass it as a prop.
<BaseTable :data='data' />
Here instead of using like this create a computed which can be filteredData.
<BaseTable :data='filteredData' />
and in your props you can simply filter it or just send the data as it is.
computed: {
filteredData() {
if(this.search) {
// filter your data as you want and return
}
else // return your main data
}
}
Here is a working simple example:
https://codesandbox.io/s/filterlist-example-vdwhg?file=/src/App.vue
And change your include to includes.
what error you are getting ? I think you are using this.search inside filteredRows function which is not a vue instance property. it should be this.data.search. The search is used with V-model as well so you should declare it outside data (JSON object).
I am quite new to Vue.
I am working on a table as component, which is supposed to be a lot of times. So far, so good, but now I want to use a filter, which can be optional passed into it.
That is how I "call" the table:
<table
:headers="headers"
:items="some.data"
></table>
data () {
return {
headers: [
{ title: 'date', value: ['date'], filter: 'truncate(0, 10, '...')' },
]
}
}
Here is my table component
<template>
<div>
<table class="table">
<thead>
<tr>
<!-- <th v-for="header in headers" :key="header.id" scope="col">{{ header.title }}</th> -->
<th v-for="header in headers" :key="header.id" scope="col">
{{ header.title }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="item in items" :key="item.id" scope="col">
<td v-for="header in headers" :key="header.id" scope="row">
<!-- {{item}} -->
<span v-for="val in header.value" :key="val.id">
{{item[val] | truncate(0, 10, '...') }}
</span>
<!-- {{header.filter}} -->
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script >
export default {
name: 'Table',
props: {
headers: Array,
items: Array
}
}
</script>
My global filter:
Vue.filter('truncate', function (text, start, truncLength, clamp = '') {
return text.slice(start, truncLength) + clamp
// return text.slice(0, stop) + (stop < text.length ? clamp || ' ...' : '')
})
I was hopping, to add by that an optional filter (via v-if I would chech for it). So far I can render the filter as string ... but not execute it.
Even if I put the filter in the span, it does not work (it says then, "text.slice is not a function" in the console.
I was not successful with googling it, because with filters/filter it is mostly about how to use .filter(...) on data as JS method, but not as Vue filter.
Any advise?
A filter is a function that runs inside JSX template in html. Example of how to create custom Vue.js filter
Vue.filter('ssn', function (ssn) { // ssn filter
return ssn.replace(/(\d{3})(\d{2})(\d{4})/, '$1-$2-$3');
});
Using it
{{stringToTrans | ssn}}
To use a filter outside this you can use a computed property or standard function like so
convertedDateString() { // computed
return this.$moment(this.dateString).format('MM/DD/YYYY')
}
convertedDateString(dateString) { // method
return this.$moment(dateString).format('MM/DD/YYYY')
}
I am trying to style the table in this vue component, my understanding is that I need to create the css from the data function and then bind it to the appropriate element in the template.
What I am trying to do is apply the following css to my table
css:
.table-striped>tr:nth-of-type(odd) {
background-color: #f9f9f9
}
vue component:
Vue.component('overview', {
delimiters: [ '[[', ']]' ],
props: ['job_execs'],
template: `
<div overview>
<h3>Overview</h3>
<table class="table table-striped table-bordered">
<tr>
<td>Start Time</td>
<td>[[ job_execs[0].time_start ]]</td>
</tr>
<tr>
<td>End Time</td>
<td>[[ job_execs[0].time_end ]]</td>
</tr>
<tr>
<td>Job</td>
<td>http://placeholder</td>
</tr>
</table>
</div>
`,
data: function() {
return {
divExample: {
color: 'red',
fontSize: '13px'
}
}
},
});
Im not sure how to 1. create this CSS from the data function and 2. Bind it within the template.
I am currently working on a project where I need to take an existing web app and convert the front end to vue.js and it seems like its going to be a real headache extracting the css I need from the already bloated css that exists and then converting it to vue js functions that inject that css per component.
You can bind css properties to a tag in Vue using the following way:
<tr>
<td>Start Time</td>
<td>[[ job_execs[0].time_start ]]</td>
</tr>
<tr v-bind:style="strippedTr">
<td>End Time</td>
<td>[[ job_execs[0].time_end ]]</td>
</tr>
<tr>
<td>Job</td>
<td>http://placeholder</td>
</tr>
...
data: function() {
return {
strippedTr: {
'background-color': '#f9f9f9'
}
}
},
After reading docs & forums for hours... still have no answer.
Have the following JS/HTML:
Vue.component("my-component", {
props: ["id"],
render: function(h) {
return h("tr", this.$slots.default);
}
});
var vapp = new Vue();
<script src="https://unpkg.com/vue#2.4.2/dist/vue.js"></script>
<table>
<tr is="my-component" :name="yo">
<td>
<span v-text="name"></span>
</td>
</tr>
</table>
Use tr + "is" attribute to specify component within the table otherwise browser will hoist it out as invalid content. Done
Use render + h("tr"...) because vuejs doesn't render tr element, but instead table > td and again browser hoist it out.Done
Now I have table > tr > td rendered well but how can I add children bound to the props/data, so I will see "yo" on the screen.
Thanks a lot!
If the elements in the slot need access to data inside the component, then you need to use a scoped slot.
Since you are using a render function, the scoped slot is available as a function through this.$scopedSlots.default() and you pass it an object with the data you want to be made available to the scoped slot.
You also need to define the scoped slot in the template. Here is an example.
Vue.component("my-component", {
props: ["id", "name"],
render: function(h) {
return h("tr", this.$scopedSlots.default({name: this.name}));
}
});
var vapp = new Vue({ el:"#app"});
<script src="https://unpkg.com/vue#2.4.2/dist/vue.js"></script>
<div id="app">
<table>
<tr is="my-component" :name="'yo'">
<template scope="{name}">
<td>
<span v-text="name"></span>
</td>
</template>
</tr>
</table>
</div>
If you are using .vue files you can have the table row component defined like this:
<template>
<tr>{{ name }}</tr>
</template>
<script>
export default {
name: 'table-row',
props: ['name'],
};
</script>
Then use it like this:
<table>
<tr is="TableRow" name="Some name here"></tr>
</table>
<table>
<thead>
<!-- ...some th -->
</thead>
<tbody>
<tr>
<td>..</rd>
</tr>
<template> <row-component my-param="blabla"></row-component> <!-- component return full row <tr>...</tr> -->
<some-other-recomponent my-par="blabla"></some-other-recomponent> <!-- component return full row <tr>...</tr> -->
</template>
</tbody>
</table>
If your're interested in returning multiple rows from your component, you can do like this.
In your main component.
<table width="90%">
<thead>
<tr>
<th width="1%">#</th>
<th>header description</th>
</tr>
</thead>
<template>
<your-child-component
:prop="prop-data"
v-for="(lancamento, index) in lancamentos"
:key="lancamento.id">
</your-child-component
</template>
</table>
And inside your child component
<template>
<tbody> <!-- dont forget this tag -->
<tr>
<td rowspan="2" v-html="prop.id"></td>
<td><td>
</tr>
<tr>
<td colspan="2" class="text-center"></td>
</tr>
</tbody>
</template>