In my code i am tring to loop thorugh an object. Would this Vue js code be considered "AntiPattern"? - javascript

I'm new to Vue js and want to see if this proper coding practice. I am using v-for to loop through the objects but am not sure if this an antipattern. The first object is what i'd like to use to build the header colum. The next object(s) would be the item information. To clarify i am not sure if looping through an object this way is bad practice. I am looping through the first item to use the values as an index so that i can create rows for property in each object.
<thead>
<tr >
<th v-for="comparison in cartComparisons" class="flex-1 bg-gray-200 border-b border-white">
<a v-if="comparison.model.link" :href="comparison.model.link | ''">
{{comparison.model.name}}
</a>
<template v-else>{{comparison.model.name}}</template >
</th>
</tr>
</thead>
<tbody >
<template v-for='(comparison,key,index) in cartComparisons[0]'>
<tr v-if="index !== 0">
<td v-for="comparison in cartComparisons">
{{comparison[key]}}
</td>
</tr>
</template>
</tbody>
<script>
export default{
props:{
product: Object,
},
data () {
return {
cartComparisons: [
{
model: {
name: "Model",
},
price: 'Our Price',
frame: 'Frame',
frameTreatment: 'Frame Treatment',
loadCapacity: 'Load Capacity',
folding: 'Folding',
wheels: 'Wheels',
straps: 'Straps',
kickstand: 'Kickstand',
padding: 'Padding',
hardware: 'Hardware',
storageBag: 'Storage Bag',
},
{
model: {
name: "bike",
},
price: "$45.95",
frame: "Aluminum",
frameTreatment: "Treated",
loadCapacity: "65lbs",
folding: "Yes",
wheels: '7"',
straps: "3 Included",
kickstand: "Single Leg",
padding: "Rubber",
hardware: "Stainless Steel",
storageBag: "Bag included",
},
],
};
},
};
</script>

Related

How can I extend a table with backend data by columns instead of rows in Vue.js?

I have a table layout that currently looks like this:
This works well because I can place all my data for one row inside of my item object, and just render the TableRow object as <tr><td>...</td><td>...</td>....
One solution could be to subdivide my item object by column, and return only the corresponding value:
<tbody>
<tr v-for="(item, rowIndex) in store.selectedItems" :key="item.id">
<!-- iterate over each column within each row -->
<td v-for="(col, colIndex) in columns" :key="colIndex">
{{ item[col.field] }}
</td>
</tr>
</tbody>
However, ideally I would like to define the whole column in a single TableCol component instead of having to render the entire table like this. Is this somehow possible?
As per my understanding you want to create a separate shared component for column, So that you can reuse that anywhere by just passing a dynamic props. If Yes, Here you go :
Vue.component('tableCol', {
props: ['column', 'rowitem'],
template: `<td>{{ rowitem[column.field] }}</td>`
});
var app = new Vue({
el: '#app',
data: {
store: {
selectedItems: [{
id: 1,
name: 'Alpha',
age: 21
}, {
id: 2,
name: 'Beta',
age: 25
}, {
id: 3,
name: 'Gamma',
age: 30
}]
},
columns: [{
field: 'name'
}, {
field: 'age'
}]
}
});
table, tr, td {
border: 1px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<table>
<tbody>
<tr v-for="item in store.selectedItems" :key="item.id">
<template>
<table-col v-for="(column, index) in columns" :column="column" :rowitem="item" :key="index"/>
</template>
</tr>
</tbody>
</table>
</div>

#click doesn't work when rendering it using v-html

I have a component which I wish I could create rows and columns with data and columns object from the parent.
There is a situation where I need to render an html template to create a clickable link, I make it want to display the link in the Actions column. vue version: ^2.5.17
below is the code from parent.vue:
// parent.vue
<table-component :data="category" :column="column" class="bg-red-200 py-4 mt-8"/>
// parent.vue
data(){
return {
modalMode: '',
isShowModalEdit: false,
category: [
{ id: 1, name: 'Jasa Pre-Order', color: '#eb4034' },
{ id: 2, name: 'Jualan', color: '#fcba03' },
{ id: 3, name: 'Jasa Design', color: '#9f34eb' },
],
}
}
// parent.vue
methods: {
toggleModalEdit(){
this.isShowModalEdit = !this.isShowModalEdit
this.modalMode = 'EDIT'
}
}
// parent.vue
computed: {
column() {
return [
{
dataField: 'name',
text: 'Name',
},
{
dataField: 'color',
text: 'Category Color',
formatter: (cell,row) => {
return `
<div style="background-color: ${cell};" class="rounded-full h-8 w-8 flex items-center justify-center mr-2"></div>
<div class="font-bold text-gray-500">${cell}</div>
`
},
classes: (cell, row, rowIndex, colIndex) => {
return 'flex';
}
},
{
dataField: 'actions',
text: 'Actions',
formatter: (cell,row) => {
return `
Edit
`
},
},
]
},
}
and this is the sample code from component.vue:
// component.vue
<tbody class="bg-white divide-y divide-gray-200">
<tr v-for="(row, rowIndex) in data" :key="rowIndex">
<td v-for="(col, colIndex) in column" :key="col.dataField" :class=" col.classes ? col.classes(row[col.dataField],row,rowIndex,colIndex) : '' " v-html=" col.formatter ? col.formatter(row[col.dataField],row) : row[col.dataField] " class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"></td>
<tr>
</tbody>
// component.vue
props: {
data: {
type: Array,
required: true
},
column: {
type: Array,
required: true
},
}
the result is like this:
but the link in the Actions column does not work as it should, I hope that when clicking this link will run the method of the perent namely toggleModalEdit. and this is what the link looks like when i inspect it:
i am still new to vue, i am not sure what i did best or not, i hope you guys can help.
Your issue is, that the HTML inside the v-html directive is not processed by Vue's template compiler at all.
Because the compiler doesn't compile this HTML, the #click is not interpreted (but simply rendered without triggering any action).
For this to work, you'd need to iterate over all columns and initialize a new component that handles what's inside the cell yourself directly in HTML (and not in some string that's gonna be rendered later on).
I guess that this is enough - if you still need to interpret what's in the string, you may use Vue.compile to interpret the content. But be careful as it's not safe in case there's some malicious code in it - but since the directive by default has no sanitizing at all, I guess that's just the way Vue.js works.
Thanks to #SimplyComple0x78 for the answer, I marked your suggestions:
For this to work, you'd need to iterate over all columns and initialize a new component that handles what's inside the cell yourself directly in HTML (and not in some string that's gonna be rendered later on).
so I try to create and initialize a new component, I call it element-generator. reference from here. here's the code:
// element-generator.vue
<script>
export default {
render: function (createElement) {
const generatedChildren = (child) => {
if(!child) return // when child of undefined
if(typeof child === 'string') return child // when children is String
return child.map((e,i,a)=>{
if(typeof child[i] == 'string'){
return child[i]
}else{
return createElement(
child[i].tag,
child[i].attributes,
generatedChildren(child[i].children) // javascript recursive
)
}
})
}
return createElement(
this.formatter.html.tag,
this.formatter.html.attributes,
generatedChildren(this.formatter.html.children)
)
},
props: {
formatter: {
type: Object,
required: true
},
},
}
</script>
and I no longer use v-html in component.vue instead I just do a check inside <td> and call element-generator to handle what's inside the cell:
// component.vue
<tbody class="bg-white divide-y divide-gray-200">
<tr v-for="(row, rowIndex) in data" :key="rowIndex">
<td v-for="(col, colIndex) in column" :key="col.dataField"
:class=" col.classes ? col.classes(row[col.dataField],row,rowIndex,colIndex) : '' "
class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"
>
<element-generator v-if="col.formatter" :formatter="col.formatter(row[col.dataField],row)"></element-generator>
<div v-else>{{row[col.dataField]}}</div>
</td>
</tr>
</tbody>
and in parent.vue I replaced the String with the Object that will be passed to the element-generator later, it looks like this:
// parent.vue
computed: {
column() {
return [
{
dataField: 'name',
text: 'Name',
},
{
dataField: 'color',
text: 'Category Color',
formatter: (cell,row) => {
return {
html: {
tag: 'div',
attributes: {
class: 'flex'
},
children:[
{
tag: 'div',
attributes: {
style: `background-color: ${cell};`,
class: 'rounded-full h-8 w-8 flex items-center justify-center mr-2',
},
},
{
tag: 'div',
attributes: {
class: 'font-bold text-gray-500',
},
children: cell
},
]
}
}
},
},
{
dataField: 'actions',
text: 'Actions',
formatter: (cell,row) => {
return {
html: {
tag: 'a',
attributes: {
class: 'text-indigo-600 hover:text-indigo-900',
on: {
click: this.toggleModalEdit
},
attrs: {
href: "#"
},
},
children: 'Edit'
},
}
},
},
]
},
},
then when I inspect it in the browser, the result is like this(this is different from the previous one):
and finally what I want to display when Edit is clicked is now displayed:
Thank you very much everyone.

Javascript JSON data with DataTables

I am using a JavaScript with jQuery DataTables but I have been having problem in loading the JSON data into the Bootstrap table.
This is a sample of the table
[ { name: 'Bryant Stone', hobbies: 'Football', favorite_team: 'Sea hawks', height: '5 Feet 10 in' },
{ name: 'Jesse Smith', hobbies: 'boxing', favorite_team: 'Spurs', height: '6 feet 6 in' },
{ name: 'Emily Wyclef', hobbies: 'cooking', favorite_team: 'Venus Williams', height: '5 feet 2 in' }
]
In JavaScript I get the data in this format and put it inside JavaScript like this
$(document).ready(function(){
$(\'#respondent\').dataTable({
destroy: true,
data: jsonStr1,
columns:[
{title:"Name", data: "name"},
{title:"Hobbies", data: "hobbies"},
{title:"Favorite team" ,data: "favorite_team"},
{title: "Height" ,data: "height"}
]
});
})
When I load the page it shows my data in the console but a DataTables dialog box shows this message
DataTable warning table id=respondent-
Requested unknown parameter 'name' for
row0, column 0. For information more.....
I don't know what else I can do. It has taken the whole day from me. Any help would be appreciated.
UPDATE
Thanks to all who helped by providing some answers all of which i have done.
This is my html
<table class="display" id="respondent">
<thead>
<tr>
<th>Name</th>
<th>Hobbies</th>
<th>Favorite Team</th>
<th>Height</th>
</tr>
</thead>
</table>
I have made needed typo correction to the code displayed earlier
but I still keep on getting this error message
DataTables warning: table id=respondent-
Requested unknown parameter 'name' for
row 0, column 0, for more information about this
error, please see http://datatables.net/tn/4
I went to the link but could not get anything helpful.
After the above message, the table get filled up with empty spaces
and after going to some pages I see just one character in the first
cell only. Those characters either letter or braces I do not know where
they came from because I could not see such sequence in my json data even
numbers shows up.
It keeps on baffling me I do not know what else to do.
Any help would be appreciated.
UPDATE
I discovered that the problem was that the data was in a string.
Does anyone know how to convert string in javascript to object without using eval(). JSON.parse return string and not object which is what is being looked for.
Minor changes:
, missing after data option
Column is passed as favourite_team (British english) but data has favorite_team (American English)
Column contains hobbies where as data contains hobbies in the first row while hobbie in the other rows. You gotta match them.
Use title option in the columns to show the header name
Info: That error/alert is usually due to missing columns in the data.
Here's a code snippet fixing the above:
$(document).ready(function(){
var jsonStr1 = [ { name: 'Bryant Stone', hobbies: 'Football', favorite_team: 'Sea hawks', height: '5 Feet 10 in' },
{ name: 'Jesse Smith', hobbies: 'boxing', favorite_team: 'Spurs', height: '6 feet 6 in' },
{ name: 'Emily Wyclef', hobbies: 'cooking', favorite_team: 'Venus Williams', height: '5 feet 2 in' }
];
$('#respondent').dataTable({
destroy: true,
data: jsonStr1,
columns:[
{title: "name", data: "name"},
{title: "hobbies", data: "hobbies"},
{title: "favorite_team", data: "favorite_team"},
{title: "height", data: "height"}
]
});
})
<script src="https://code.jquery.com/jquery-3.2.1.js"></script>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/v/dt/dt-1.10.18/datatables.min.css"/>
<script src="//cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js"></script>
<table id="respondent" class="display">
<thead>
</thead>
<tbody>
</tbody>
</table>
Hope this helps.
Typo errors of column names - hobbies are mentioned as "hobbie" and favourite_team as favorite_team.
Maintain same property names for all objects to avoid that error
Code sample for reference - https://codepen.io/nagasai/pen/vzNXPe
HTML:
<table class="display" id="respondent">
<thead>
<tr>
<th>Name</th>
<th>Hobbies</th>
<th>Favorite Team</th>
<th>Height</th>
</tr>
</thead>
</table>
JS
var jsonStr1 = [ { name: 'Bryant Stone', hobbies: 'Football', favorite_team: 'Sea hawks', height: '5 Feet 10 in' },
{ name: 'Jesse Smith', hobbies: 'boxing', favorite_team: 'Spurs', height: '6 feet 6 in' },
{ name: 'Emily Wyclef', hobbies: 'cooking', favorite_team: 'Venus Williams', height: '5 feet 2 in' }
]
$(document).ready(function() {
$('#respondent').dataTable({
destroy: true,
data: jsonStr1,
columns:[
{title:"Name", data: "name"},
{title:"Hobbies", data: "hobbies"},
{title:"Favorite team" ,data: "favorite_team"},
{title: "Height" ,data: "height"}
]
});
} );

table in Angular using dynamic table headers

I was creating an angular app to display data from various data sources. I configured the list of various data sources in a JSON file each data source has a set of values attached to it.
Here is an example
var configurableConfigurations=[
{
name:"Locations",
table:"location_set",
columnList:[
{
name:"LOCATION_NAME",
alias:"Location",
primary:true
},
{
name:"DESCRIPTION",
alias:"Description",
primary:false
}
]
},
{
name:"System Parameters",
table:"system_params",
columnList:[
{
name:"param_Key",
alias:"Parameter",
primary:true
},
{
name:"param_Value",
alias:"Value",
primary:false
}
]
}
];
I then created an HTML page to display this using angular : the page has 2 parts
1. A select box which shows various parameters this is done using ng-repeat
<select name="ConfigurationName" ng-model="selected" ng-options="eachConfiguration.name for eachConfiguration in configurableConfigurations" ng-change="fetchRequiredConfiguration()">
A table which I want to generate using the headers of the parameter selected
this is my code to do that
<table id="configtable">
<thead>
<tr>
<th class="col-md-1" ng-repeat="column in selected.columnList" >{{column.alias}}</th>
</tr>
</thead>
This works great for the first time. But when the option is selected again using the select box the table header is not shown.
The table data is being populated properly , its just the table headers that are getting messed up.
Could anyone please help me get around this problem. I am new to angularjs. May be I am missing something important.
Edit ::
I should Also Mention that I fetch the data from the API and then was using the Data table plugin(https://www.datatables.net/download/) to show this as Data
$("#configtable").DataTable({
"ajax": "../fetchvalue/config/"+this.selected.table,
destroy: true,
"columns":{ "data": "ColumnXXX"},
{"data": "ColumnYYY" }
});
As it turns out, I have a problem with the disappearing headers only when I use the DataTable
I don't know how the table data is store but this way is good:
$scope.configurableConfigurations = [{
name: "Locations",
table: "location_set",
columnList: [{
name: "LOCATION_NAME",
alias: "Location",
primary: true
}, {
name: "DESCRIPTION",
alias: "Description",
primary: false
}],
data:[[12,"home"],[43,"work"]]
}, {
name: "System Parameters",
table: "system_params",
columnList: [{
name: "param_Key",
alias: "Parameter",
primary: true
}, {
name: "param_Value",
alias: "Value",
primary: false
}],
data:[["GOO",586],["FOO",123]]
}];
And then you can print the table like this:
<select name="ConfigurationName" ng-model="selected" ng-options="eachConfiguration as eachConfiguration.name for eachConfiguration in configurableConfigurations" ng-change="fetchRequiredConfiguration()"></select>
<table id="configtable">
<thead>
<tr>
<th class="col-md-1" ng-repeat="column in selected.columnList">{{column.alias}}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="row in selected.data" >
<td class="col-md-1" ng-repeat="col in row">{{col}}</td>
</tr>
</tbody>
</table>
Example here

Javascript inside Kendo Template is giving incorrect result

I have a template with javascript inside it
# if(IsSelected) { #
<tr>
<td data-bind="text: name"></td>
<td data-bind="text: age"></td>
</tr>
# } #
It is intended to show only those records for which the IsSelected value is true. Though it shows only two records – the records displayed are not correct. What is the reason for this?
Fiddle: http://jsfiddle.net/Lijo/Z86dq/4/
CODE
<html>
<head>
<title>Template Filtering</title>
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="http://cdn.kendostatic.com/2013.2.716/js/kendo.all.min.js"></script>
<script id="row-template" type="text/x-kendo-template">
# if(IsSelected) { #
<tr>
<td data-bind="text: name"></td>
<td data-bind="text: age"></td>
</tr>
# } #
</script>
<!--MVVM Wiring using Kendo Binding-->
<script type="text/javascript">
$(document).ready(function () {
kendo.bind($("body"), viewModel);
});
</script>
<script type="text/javascript">
var viewModel = kendo.observable({
employees: [
{ name: "Lijo", age: "28", IsSelected: true },
{ name: "Binu", age: "33", IsSelected: false },
{ name: "Kiran", age: "29", IsSelected: true }
]
});
</script>
<style>
table, th, td
{
border: 1px solid black;
}
</style>
</head>
<body>
<table id="resultTable">
<tbody data-template="row-template" data-bind="source: employees">
</tbody>
</table>
</body>
</html>
Try defining the template as:
<script id="row-template" type="text/x-kendo-template">
# if(IsSelected) { #
<tr>
<td>#= name #</td>
<td>#= age #</td>
</tr>
# } #
</script>
in order to avoid a double bind first in the tbody and then in the td.
EDIT: In order to avoid the problems with the error that throws KendoUI, I would change your code a little and instead of using a kendo.observable I would use a DataSource that implements filters.
So, you don't need to print or not the row in your template but setting the filter condition in the DataSource.
Define the template as follow:
<script id="row-template" type="text/x-kendo-template">
<tr>
<td>#= name #</td>
<td>#= age #</td>
</tr>
</script>
and your JavaScript code as:
var ds = new kendo.data.DataSource({
data : {
employees: [
{ name: "Lijo", age: "28", IsSelected: true },
{ name: "Binu", age: "33", IsSelected: false },
{ name: "Kiran", age: "29", IsSelected: true }
]
},
schema: {
data: "employees"
},
filter: { field: "IsSelected", operator: "eq", value: true }
});
Where I set a filter that filters out elements where isSelected is not equal to true.
Then initialize a ListView as follow:
$("#list").kendoListView({
dataSource: ds,
template : $("#row-template").html()
});
You can see the code here: http://jsfiddle.net/OnaBai/Z86dq/16/
This is my syntax, but this is coming from a sub grid. So I think the syntax you need may be a little different. I think //# just becomes # if it's in the parent Grid but don't quote me on it. Looks like your missing some " " and concatenation
Here's the razor syntax for what it's worth, maybe you can convert it to your needs
.Columns(columns =>
{
columns.Bound(x => x.FirstName).Title("First Name").ClientTemplate(
"# if (Id == 5) { #" +
("<a class href='javascript: void(0);' onclick=\"return MyFunction('#=LastName#', '#= FirstName #');\">Convert</a>") +
"# } else { #" +
"#= FirstName #" +
"# } #");
columns.Bound(x => x.LastName).Title("Last Name");
columns.Bound(x => x.Id).Title("User ID");
})
So this code reads as, If the Id of the user is 5, the column will have a hyperlink that says "convert" and will call MyFunction with those parameters. If not show the user's name
The problem comes from the fact that the MVVM implementation expects the template to render one element. In your case, you can use the visible binding - check an updated version of the fiddle.
<tr data-bind="visible: IsSelected">
<td data-bind="text: name"></td>
<td data-bind="text: age"></td>
</tr>

Categories

Resources