Creating an ember.js table component with parameters - javascript

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>

Related

Vue.js not rendering table data [duplicate]

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/

How to call a controller function from another controller component and pass control to the called controller component

I have HTML structure like this
<aside class="side" align="left">
<table onclick="reply_click(event)" ng-controller="TableCtrl" >
<tr ng-repeat = "table in tables">
<td><button id = "{{table}}" width = "70">{{table}}</button></td>
</tr>
</table>
</aside>
<article class="tabs">
<section id="erd">
<h2>ERD</h2>
<p>This content appears on tab ERD.</p>
</section>
<section id="col">
<h2>Columns</h2>
<h2>Columns</h2>
<div id="list" ng-controller="ColCtrl">
<table>
<tr>
<th>Column Name</th>
</tr>
<tr ng-repeat="column in columns">
<td>{{column.name}}</td>
</tr>
</table>
</div>
</section>
<section id="home">
<h2>Home</h2>
<p>This content appears on tab Home. lfkdgl;k lkfd;lkg ';lkfg ;lkg 'df;lk ;lk ';lk ';fdlkg ';fdlkg';dflk;ldfkg';lkdlfkdfkglkdf lkjhlsdjhfljdfhlkjdh jh jhkjdhfkjsdhf skjdhf lk h dsfjlkjsdlkfj;dslkfj dskfj;kj sdfkj fkdj;lfkjsd;lkfj sdkfj ;slkj sdfj;lskjf skdj flksjdf ; sdfkj ;sdlkfj dskfj sdkjfhueuu suehu heu he u heu heh ueh ufhu fhe uueh ush uhsudh ue huhuhufheuheu u heiu h euh eh ue </p>
</section>
</article>
I used the suggestions from this SO answer to call a controller
AngularJS. How to call controller function from outside of controller component
The controller I and calling is
angular.element(document.getElementById("list")).scope().loadColumns();
It fires correctly. The problems I am calling it from a button in TableCtrl component. After loadColumns() is fired it went to the TableCtrl component to loop which is the wrong place. How can I tell it to go to the correct place ColCtrl area to loop and populate data?
Thanks,

Can Knockout.js bindings apply to container tags AND descendants both?

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>

Pass object to isolated directive

When my application loads a function to get items from a server runs, this function get the title and Id of all items on the server,
then puts them in an array.
In my main html I have a table that displays this array, and when i click the title of an Item, I call another function (onView)
which gathers more information about that item from the server. This new information is passed to
a variable called '$scope.currentItem'.
I also have a directive called 'currentItem', this is used to display that item for the user.
myApp.directive('currentItem',function(){
return{
restrict:'E',
scope:{
data:'='
},
templateUrl: 'currentItem.html',
controller:function($scope){
}
};
});
To know if a user has clicked an item I have a boolean called isView, so when the function to gather more information runs
the isView is set to 'true'.
My html looks like this:
<div ng-app="myApp">
<hr>
<div ng-controller="appCtrl">
<div ng-hide="isView" class="col-md-12">
<table id="mainTable" ng-table="tableParams" class="table">
<tbody ng-repeat="item in items">
<tr>
<td id="Title" data-title="'Title'">
{{item.id}} |<a ng-click="onView(item.id)">{{item.title}}</a>
</td>
<td data-title="'Description'">
{{item.description | date:'dd-MM-yyyy'}}
</td>
<td data-title="'Created'">
{{item.created | date:'dd-MM-yyyy'}}
</td>
</tr>
</tbody>
</table>
</div>
<div ng-show="isView" class="col-md-12">
<current-item data="currentItem"></current-item>
</div>
</div>
</div>
This can't be the correct way to pass an object to a directive, seeing I always use the same 'currentItem' object.
How could I improve this, so that each object is isolated and editable in a seperate view?

Polymer content insertion points?

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.

Categories

Resources