I am trying to build a grid element, but am having some issues with a content insertion points. Here's my element with content insertion points:
<template>
<content select="[data-guerilla-grid-service]"></content>
<table id="guerillaGrid">
<thead>
<tr>
<template is="dom-repeat" items="{{columns}}" as="column">
<th style$="{{getColumnStyle(column)}}" data-sort-field$="{{column.sortField}}" on-click="sortClick"><span>{{column.header}}</span></th>
</template>
</tr>
</thead>
<content select="[data-guerilla-grid-items]"></content>
</table>
And here's where I'm using it:
<template>
<guerilla-grid id="feedbackGrid">
<muted-feedback-service id="service" items="{{items}}" data-guerilla-grid-service on-load-finished="serviceLoadFinished" sort-field="Version"></muted-feedback-service>
<tbody data-guerilla-grid-items>
<template is="dom-repeat" items="{{items}}">
<tr>
<td>{{item.formattedTimestamp}}</td>
<td>{{item.hash}}</td>
<td>{{item.version}}</td>
<td>{{item.serialNumber}}</td>
</tr>
</template>
</tbody>
</guerilla-grid>
Am I doing this right?
Content selector does not appear to work with tag. Switched to a doing a table with divs using css display attribute table options.
Related
I'm struggling to develop a simple component and use it inside a loop:
<template id="measurement">
<tr class="d-flex">
</tr>
</template>
Vue.component('measurement', {
template: '#measurement',
props: {
name: String,
data: Object,
val: String,
},
});
This is obviously not functional yet but already fails:
<table v-for="(m, idx) in sortedMeters">
<measurement v-bind:data="m"></measurement>
</table>
gives ReferenceError: Can't find variable: m inside view. For a strange reason the same thing works, i.e. without error, in a paragraph:
<p v-for="(m, idx) in sortedMeters">
<measurement v-bind:data="m"></measurement>
</p>
What causes the variable to be not found?
PS.: here's a fiddle: https://jsfiddle.net/andig2/u47gh3w1/. It shows a different error as soon as the table is included.
Update It is intended that the loop produces multiple tables. Rows per table will be created by multiple measurements
TLDR: Before Vue is passed the DOM template, the browser is hoisting <measurement v-bind:name="i" v-bind:data="m"> outside the <table> (outside v-for context), leading to the errors in Vue. This is a known caveat of DOM template parsing.
The HTML spec requires the <table> contain only specific child elements:
<caption>
<colgroup>
<thead>
<tbody>
<tr>
<tfoot>
<script> or <template> intermixed with above
Similarly, the content model of <tr> is:
<td>
<th>
<script> or <template> intermixed with above
The DOM parser of compliant browsers automatically hoists disallowed elements – such as <measurement> – outside the table. This happens before the scripting stage (before Vue even gets to see it).
For instance, this markup:
<table>
<tr v-for="(m,i) in obj">
<measurement v-bind:name="i" v-bind:data="m"></measurement>
</tr>
</table>
...becomes this after DOM parsing (before any scripting):
<measurement v-bind:name="i" v-bind:data="m"></measurement> <!-- hoisted outside v-for -->
<table>
<tr v-for="(m,i) in obj">
</tr>
</table>
Notice how i and m are then outside the context of the v-for loop, which results in Vue runtime errors about i and m not defined (unless by chance your component coincidentally declared them already). m was intended to be bound to <measurement>'s data prop, but since that failed, data is simply its initial value (also undefined), causing the rendering of {{data.value}} to fail with Error in render: "TypeError: Cannot read property 'value' of undefined".
To demonstrate hoisting without these runtime errors and without Vue, run the code snippet below:
<table style="border: solid green">
<tr>
<div>1. hoisted outside</div>
<td>3. inside table</td>
2. also hoisted outside
</tr>
</table>
...then inspect the result in your browser's DevTools, which should look like this:
<div>1. hoisted outside</div>
2. also hoisted outside
<table style="border: solid green">
<tr>
<td>3. inside table</td>
</tr>
</table>
Solution 1: Use <tr is="measurement">
If you prefer DOM templates, you could use the is attribute on a <tr> to specify measurement as the type (as suggested by the Vue docs and by another answer). This first requires the <measurement> template use <td> or <th> as a container element inside <tr> to be valid HTML:
<template id="measurement">
<tr>
<td>{{name}} -> {{data.value}}</td>
</tr>
</template>
<div id="app">
<table v-for="(m,i) in sortedMeters">
<tr is="measurement" v-bind:name="i" v-bind:data="m" v-bind:key="i"></tr>
</table>
</div>
Vue.component('measurement', {
template: '#measurement',
props: {
name: String,
data: Object
}
})
new Vue({
el: '#app',
data: {
sortedMeters: {
apple: {value: 100},
banana: {value: 200}
},
}
})
<script src="https://unpkg.com/vue#2.6.11"></script>
<template id="measurement">
<tr>
<td>{{name}} -> {{data.value}}</td>
</tr>
</template>
<div id="app">
<table v-for="(m,i) in sortedMeters">
<tr is="measurement" v-bind:name="i" v-bind:data="m" v-bind:key="i"></tr>
</table>
</div>
Solution 2: Wrap <table> in component
If you prefer DOM templates, you could use a wrapper component for <table>, which would be able to contain <measurement> without the hoisting caveat.
Vue.component('my-table', {
template: `<table><slot/></table>`
})
<div id="app">
<my-table v-for="(m, i) in sortedMeters">
<measurement v-bind:name="i" v-bind:data="m"></measurement>
</my-table>
</div>
Vue.component('measurement', {
template: '#measurement',
props: {
name: String,
data: Object
}
})
Vue.component('my-table', {
template: `<table><slot/></table>`
})
new Vue({
el: '#app',
data: {
sortedMeters: {
apple: {value: 100},
banana: {value: 200}
},
}
})
<script src="https://unpkg.com/vue#2.6.11"></script>
<template id="measurement">
<tr>
<td>{{name}} -> {{data.value}}</td>
</tr>
</template>
<div id="app">
<my-table v-for="(m, i) in sortedMeters">
<measurement v-bind:name="i" v-bind:data="m"></measurement>
</my-table>
</div>
Solution 3: Move <table> markup into template string
You could move the entire <table> into a component's template string, where the DOM template caveats could be avoided. Similarly, you could move the <table> into a single file component, but I assume you have a significant need for DOM templates instead.
Vue.component('my-table', {
template: `<div>
<table v-for="(m, idx) in sortedMeters">
<measurement v-bind:data="m"></measurement>
</table>
</div>`,
props: {
sortedMeters: Object
}
})
<div id="app">
<my-table v-bind:sorted-meters="sortedMeters"></my-table>
</div>
Vue.component('measurement', {
template: '#measurement',
props: {
name: String,
data: Object
}
})
Vue.component('my-table', {
template: `<div>
<table v-for="(m,i) in sortedMeters">
<measurement v-bind:name="i" v-bind:data="m" v-bind:key="i"></measurement>
</table>
</div>`,
props: {
sortedMeters: Object
}
})
new Vue({
el: '#app',
data: {
sortedMeters: {
apple: {value: 100},
banana: {value: 200}
},
}
})
<script src="https://unpkg.com/vue#2.6.11"></script>
<template id="measurement">
<tr>
<td>{{name}} -> {{data.value}}</td>
</tr>
</template>
<div id="app">
<my-table :sorted-meters="sortedMeters"></my-table>
</div>
If you replace
<table v-for="(m, idx) in sortedMeters">
<measurement v-bind:data="m"></measurement>
</table>
with
<template v-for="(m, idx) in sortedMeters">
<table>
<measurement v-bind:data="m"></measurement>
</table>
</template>
You'll end up with working code.
But you'll most likely want to use
<table>
<template v-for="(m, idx) in sortedMeters">
<measurement v-bind:data="m"></measurement>
</template>
</table>
or
<table>
<measurement v-for="(m, idx) in sortedMeters" v-bind:data="m"></measurement>
</table>
It's because you've missing <td> inside <tr>. Without it your component produces invalid html markup and extracts "slot" data outside <tr> causing error.
Your template should looks like:
<template id="measurement">
<tr>
<td>{{name}} -> {{data.value}}</td>
</tr>
</template>
You also need to move v-for to measurement:
<table border="1">
<tr is="measurement" v-for="(m,index) in obj" :key="index" v-bind:name="index" v-bind:data="m"></measurement>
</table>
You can use is attribute to set component name.
Working fiddle: https://jsfiddle.net/cyaj0ukh/
I'm using a dojo widget to display some data through a dojo Template (which uses django templates). When using a for loop inside of an html element, the loop only executes once and is unable to access the currently looping variable. However, the same loop outside of a table is able to loop as expected.
I'm not sure why this {% for %} loop will not work inside a element but it works outside.
I've tried including "dojo/dom-construct", and have "dojox/dtl/tag/logic" included in my widget. My widget is currently defined as follows:
define([
"dojo/_base/declare",
"dijit/_WidgetBase",
"dijit/_OnDijitClickMixin",
"dijit/_TemplatedMixin",
"dijit/_WidgetsInTemplateMixin",
"dojo/text!views/siteInfo/siteBatteries.html",
"dijit/registry",
"dojo/dom",
"dojox/dtl/_DomTemplated",
"dojox/dtl/tag/logic",
"dojo/dom-construct",
],function(declare, _WidgetBase, _OnDijitClickMixin, _TemplatedMixin, _WidgetsInTemplateMixin, template, registry, dom, _DomTemplated){
return declare([_WidgetBase, _OnDijitClickMixin, _TemplatedMixin, _WidgetsInTemplateMixin, _DomTemplated], {
// WidgetLogic
});
});
Template:
<div class="container" id="SiteOverviewController">
{{ batteryList.length }}
<table>
<thead>
<tr>
<th>ObjectId</th>
</tr>
</thead>
<tbody>
{% for battery in batteryList %}
<tr>
<th>{{ battery.attributes.OBJECTID }}</th>
</tr>
{% endfor %}
</tbody>
</table>
END TABLE
START DIV
{% for battery in batteryList %}
<div>{{ battery.attributes.OBJECTID }}</div>
{% endfor %}
</div>
The output of the template above is as follows:
<div class="container" id="SiteBatteryController" widgetid="SiteBatteryController" style="">
4
<table style="">
<thead style="">
<tr style="">
<th style="">ObjectId</th>
</tr>
</thead>
<tbody style="">
<tr style="">
<th style=""></th>
</tr>
</tbody>
</table>
END TABLE
START DIV
<div style="">2225</div>
<div style="">2226</div>
<div style="">2227</div>
<div style="">2228</div>
</div>
From the output you can see that the table only has one row with empty output:
<tr style=""><th style=""></th></tr> and loops only once when it should be looping 4 times (as seen with the elements) and have data.
I think this is a bug in dojo. I created a github issue in dojox for this, see it here
i am new in VueJs and i am having that little problem, first here is my sample HTML code :
<div class="search">
<input :class="{ longwidth : isActive }" v-show="showInput" type="text">
<img #click="hideImgShowInput" v-show="hideImg" src="Assets/images/search-icon.png">
</div>
i have followed the documentation exactly, and i am using PHPStorm as editor, but my function that changes the 'isActive' variable is not working i am having this error:
Attribute :class is not allowed here
Any help would be much appreciated.
That sounds like a PHPStorm warning. Ignore it, or try a Vue-aware editor like vs code or atom. Your code looks fine to me.
This error causes the component to not render
My case is similar to the following
<script type="text/x-template" id="game-row">
<tr>
<td>{{ title }}</td>
...
</tr>
</script>
This way when adding the class attribute to tr element, causes the mentioned message
<script type="text/x-template" id="game-row">
<tr :class="{ active: isActive }">
<td>{{ title }}</td>
...
</tr>
</script>
The way to fix it was to pass the class attribute in the custom component call, as follows
<script type="text/x-template" id="game">
<div>
<p v-if="isLoading">Loading...</p>
<template v-else>
<table>
<caption>Game {{ title }}</caption>
<thead>
<tr>
<th>Name</th>
</tr>
</thead>
<tbody>
<tr
is="game-day-row"
v-for="game of games"
:class="{ 'active': isActive }" <!-- Here I set the class -->
:key="game.id"
:game="game"
></tr>
</tbody>
</table>
</template>
</div>
</script>
You can read about it in the vue class and style guide
https://v2.vuejs.org/v2/guide/class-and-style.html#With-Components
In the html output the class active is added to the tr element
Let me setup the question with a simple case.
I have an HTML table, the rows of which are controlled by an observableArray. It works great.
If the observableArray has zero elements in it however, I want a single row to say so. I tried this markup, which "kind of" works:
<tbody data-bind="if: $root.data.contacts().length == 0">
<tr>
<td>There are no contacts specified yet.</td>
</tr>
</tbody>
<tbody data-bind="foreach: $root.data.contacts">
SNIP - a tbody with the rows is here when elements > zero
</tbody>
When I say "kind of", I mean VISIBLY. It really does show up at zero elements and really does go away at > zero elements like what you would expect. However when you open the DOM inspector (dev tools) and look at the DOM in memory, you find that there are TWO tbody sections, not one. Now one tbody is always empty of course, but two tbody tags is not HTML5 correct, so this must be fixed this is not the desired markup.
Being a Knockout newbie, I tried to fix this problem with a virtual element:
<!-- ko if: $root.data.contacts().length == 0 -->
<tbody>
<tr>
<td>There are no contacts specified yet.</td>
</tr>
</tbody>
<!-- /ko -->
Unfortunately this doesn't work for our build process: we minify HTML prior to compression and comments get eliminated.
I was under the impression that KO bindings applied to the CONTAINER ELEMENT ITSELF as well as descendants, but this seems to not be so. Is there a way to tell KO to apply to container elements as well as children, or do I need to change the markup in some way OTHER THAN a virtual container?
Like you, my first choice would be virtual tags for an if binding. But since that's not an option, how about swappable templates?
var vm = {
contacts: ko.observableArray()
};
ko.applyBindings(vm);
setTimeout(function() {
vm.contacts(['One', 'Two', 'Three']);
}, 2500);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<template id="empty-body">
<tbody>
<tr>
<td>There are no contacts specified yet.</td>
</tr>
</tbody>
</template>
<template id="normal-body">
<tbody data-bind="foreach: contacts">
<tr>
<td data-bind="text:$data"></td>
</tr>
</tbody>
</template>
<table data-bind="template: contacts().length === 0 ? 'empty-body' : 'normal-body'"></table>
The Knockout-Repeat binding applies the binding to the element itself. It does so by using a node preprocessor to wrap elements with the repeat binding in virtual (comment-based) elements at run time.
var vm = {
contacts: ko.observableArray()
};
ko.applyBindings(vm);
setTimeout(function() {
vm.contacts(['One', 'Two', 'Three']);
}, 2500);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.3.0/knockout-min.js"></script>
<script src="https://rawgit.com/mbest/knockout-repeat/master/knockout-repeat.js"></script>
<table>
<tbody data-bind="repeat: !contacts().length && 1">
<tr>
<td>There are no contacts specified yet.</td>
</tr>
</tbody>
<tbody data-bind="repeat: contacts().length && 1" data-repeat-bind="foreach: contacts">
<tr>
<td data-bind="text:$data"></td>
</tr>
</tbody>
</table>
I'm trying to create a table component using ember.js. I need to pass, the table name, the columns header names and column names, and the table should be rendered accordingly. The number of columns vary per table.
But my code is not working with parameters passed as a list. Here's the part of the code:
<script type="text/x-handlebars" data-template-name="index">
<h1> Testing Table 1</h1>
{{table-ember tableName="Table 1" noOfColumns="3" columnHeaders="['LineID', 'Placement Name', 'Pricing Type']" columnNames="['lineId', 'name', 'pricingType']"}}
<h1>Testing Table 2</h1>
{{table-ember tableName="Table 2" noOfColumns="2" columnHeaders="['LineID', 'Placement Name']" columnNames="['lineId', 'name']"}}
</script>
<script type="text/x-handlebars" data-template-name="components/table-ember">
<p>{{tableName}}</p>
{{#if columnHeaders}}
<table>
<thead>
{{#each columnHeaders}}
<tr>
<th>{{columnHeaders}}</th>
</tr>
{{/each}}
</thead>
{{#each}}
<tr>
<td>{{#link-to 'columnName' this}}{{columnNames}}{{/link-to}}</td>
</tr>
{{/each}}
</table>
{{/if}}
</script>