Using jQuery DataTables with Aurelia - aurelia

I need some advice regarding the usage of jQuery DataTables with Aurelia. Basically I'm running into two problems.
I can't determine the best way to initialize it AFTER the repeat.for binding loop has completed. Apparently that loop is still working even after the attached() lifecycle is fired.
If I use $(myRef).DataTables(data: myArray) method to populate the table, and insert links (<a href-route=... click.delegate=...>) into that table, Aurelia doesn't seem to recognize the links or activate the router.
Problem 1: Here's my attempt to populate the table using Aurelia's binding. Aurelia correctly makes the table, and I can just wait 2-3 seconds and then load DataTables, but that's not the right way. I don't have a definitive event to trigger the loading of the DataTables class because I don't know when repeat.for is completed.
<div class="table-responsive">
<table ref="tblUserList" class="table table-condensed table-hover table-striped" width="100%">
<thead>
<tr>
<th><span t="Username"></span></th>
<th><span t="First_name"></span></th>
<th><span t="Last_name"></span></th>
</tr>
</thead>
<tbody>
<tr repeat.for="record of records">
<td><a route-href="route: user; params.bind: {id: record.user_id}" click.delegate="$parent.select(record)">${record.user_username}</a></td>
<td>${record.p_fname}</td>
<td>${record.p_lname}</td>
</tr>
</tbody>
</table>
</div>
Problem 2: Here's my attempt to populate the table using the jQuery method. DataTables successfully loads the table, but Aurelia doesn't recognize the links or trigger action.
$(this.tblUserList).dataTable({
"paginate": true,
"pageLength": 10,
data: this.records,
columns: [
{ data: 'user_username',
fnCreatedCell: function (nTd, sData, oData, iRow, iCol) {
$(nTd).html("<a route-href='route: user; params.bind: {id:" + oData.user_id + "}' click.delegate='$parent.select(" + oData.user_id + ")'>" + oData.user_username + "</a>");
}
},
{ data: 'p_fname' },
{ data: 'p_lname' }
]
});
Can anyone help me solve any one of the above problems? Or... am I approaching this whole issue the wrong way? Is it better to use the jQuery method to populate, or the Aurelia repeat.for binding loop?

I know this is old, but just in case, if it can help.
When you add DOM elements after binding, they are not aureliazed. You should use the enhance method of TemplatingEngine:
import the TemplatingEngine and inject it:
import {inject, TemplatingEngine} from 'aurelia-framework';
#inject(TemplatingEngine)
In the constructor, initialize the template engine:
constructor(templatingEngine) {
this.templatingEngine = templatingEngine;
}
in Aurelia's attached() method, do your datatable init stuff, and add a class to be able to retrieve your newly created DOM elements:
$(nTd).html("<a class='myclass' ...
Enhance your elements:
$('.myclass').each(function (index, value) {
let el = $(this);
if (!el.hasClass('au-target')) { //can't enhance already aureliazed elm
this.templatingEngine.enhance({element: el[0], bindingContext: this});
//el[0] is important to have DOM and not jQuery object
}
});
Then your binding should work.

Using the first approach (aurelia binding), remove data from the config object and load your data in the activate lifecycle hook:
import 'datatables';
export class UserList {
activate() {
this.records = [...];
}
attached() {
$(this.tblUserList).dataTable();
}
}

Related

Svelte {#if variable} block does not react to variable updates within the block

I would like to populate a table with visible rows in Svelte.
My current attempt relies on a {#if variable} test, where the rendered row updates the variable. Unfortunately, the test does not appear to react to changes to the variable. Perhaps this is as designed but the documentation does not appear to address this. Essentially:
<table>
<tbody>
{#each rows as row}
{#if renderIt==true}
<tr use:updateRenderIt>
<td>cell</td>
</tr>
{/if}
{/each}
</tbody>
</table>
I think my understanding of the timing is lacking :(. Perhaps the {#if} block cannot react to each renderIt change. There are quite a few examples of {#if} blocks, but none appear to rely on a variable which is changed within the block.
There is a running example in the Svelte playground. The console divider can be moved vertically to change the viewport dimensions.
If someone knows of a way to achieve this it would be appreciated! I can do it in traditional Javascript, but my Svelte expertise is limited :).
What I'm assuming you want is to have a state on each row when it is visible.
To do so you will need to store some data with your row, so instead of your row being a list of numbers and a single boolean to say if all rows are visible or not, it will be a list of objets that have a property visible:
let rows = [];
for (let i = 0; i < 100; i++) {
rows.push({
index: i,
visible: false,
});
};
Next, to capture when visibility changes on those rows, use Intersection Observer API:
let observer = new IntersectionObserver(
(entries, observer) => {
console.log(entries);
}
);
And use a svelte action to add that observer to elements:
<script>
...
let intersect = (element) => {
observer.observe(element);
};
</script>
<table>
<tbody>
{#each rows as row (row.index)}
<tr
use:intersect>
<td>{row.visible}</td>
</tr>
{/each}
</tbody>
</table>
To pass the intersecting state back to the element throw a custom event on it:
let observer = new IntersectionObserver(
(entries, observer) => {
entries.forEach((entry) => {
entry.target.dispatchEvent(
new CustomEvent("intersect", { detail: entry.isIntersecting })
);
});
}
);
And finally capture that event and modify the state:
<tr use:intersect
on:intersect={(event) => (row.visible = event.detail)} >
<td>{row.visible}</td>
</tr>
To render rows up to how many can fit on screen you could make the defaut state visible: true, and then wrap the element with an {#if row.visible}<tr .... </tr>{/if}. After the first event you would then remove the observer from the element using observer.unobserve by either updating the svelte action or in the observer hook.

TYPO3 - Adding extbase viewhelper via javascript to partial

I have a partial ... and if I add a link via link.action ... the link is working perfectly fine:
MyPartial.html:
<f:link.action action="show" pageUid="43" pluginName="abc" controller="Abc" extensionName="abc" arguments="{record:1}">ActionLink</f:link.action>
But if I want to add the link.page viewhelper via Javascript to the partial ...
MyPartial.html
<table id="lei_all" width="100%">
<thead>
<tr>
<th>Column01</th>
<th>Column02</th>
</tr>
</thead>
</table>
JS:
var table_all = $('#table_all').DataTable( {
dom: "Blrtip",
ajax: {
url: "/source.php",
type: "POST"
},
serverSide: true,
processing: true,
columns: [
{ data: "column_01",
"render": function ( data, type, row ) {
return '<f:link.action action="show" pageUid="43" pluginName="abc" controller="Abc" extensionName="abc" arguments="{record:1}">'+data+'</f:link.action>';
} },
{ data: "column_02" }
],
...
} );
I'm getting the raw view helper like this:
<tbody>
<tr id=row_1 class="odd" role="row">
<td tabindex="0"><f:link.action action="show" pageUid="43" pluginName="abc" controller="Abc" extensionName="abc" arguments="{record:1}">ActionLink</f:link.action></td>
...
</tr>
</tbody>
How can I add the viewhelper in the above case?
ViewHelpers simply do not work that way. What you are trying is equivalent to calling a PHP function in JavaScript to build a string. The expected result of this is that you will see the Fluid tag output, exactly as you describe.
The only way is to construct your links in PHP and (somehow) transfer them to the JavaScript. Valid ways of doing that includes:
Using data-something properties to carry URLs from HTML to JS
Using an XHR request to create links for a certain purpose
Generating a list of all links indexed by whichever identifier it has in the table, as a JSON or other file in for example typo3temp, then reading this list and using the appropriate link when rendering your table in JS.
Depending on what you need, pick one and implement it so you do not have Fluid code in your JavaScript.

View not updating when using Master-Detail form with ASP.NET MVC 4 and KnockoutJS

I'm trying to create a single page editor for data records using ASP.NET MVC 4 and KnockoutJS. It is fairly straightforward with a table showing the records and a form to edit individual records.
When clicking 'Edit' to edit a record the form updates and the data is persisted to the database without problem. There are two issues after this:
The record being edited does not update in the table after saving (i.e. the observables do not update)
The controls containing the record being edited do not clear after saving.
I have no idea how to solve (1). For (2) is there some way of writing a generic extension method or function to clear ANY form after Knockout has finished with it. I could do it with JQuery reasonably easily but I may be missing something that Knockout can do already.
The code for the page is as below:
#model IEnumerable<SiteDto>
#{
ViewBag.Title = "Index";
}
<h2>Sites</h2>
<table>
<caption>Sites</caption>
<thead>
<tr>
<th>Name</th>
<th>Link</th>
<th>Url</th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: sites">
<tr>
<td><span data-bind="text: id"></span></td>
<td><span data-bind="text: name"></span></td>
<td><span data-bind="text: url"></span></td>
<td><button data-bind="click: $parent.selectItem">Edit</button></td>
</tr>
</tbody>
</table>
<div data-bind="with: selectedItem">
<table>
<caption data-bind="text: name"></caption>
<tbody>
<tr>
<td><input data-bind="value: id" /></td>
</tr>
<tr>
<td><input data-bind="value: url" /></td>
</tr>
<tr>
<td><input data-bind="value: name" /></td>
</tr>
</tbody>
</table>
<button data-bind="click: save">Save</button>
</div>
<script type="text/javascript">
function viewModel() {
var sites = ko.observableArray(#Html.Raw(Model.ToJson()));
var selectedItem = ko.observable();
selectItem = function (s) {
selectedItem(s);
};
save = function () {
alert(ko.toJSON(selectedItem));
$.ajax({
url: "/Home/Save",
type: "POST",
data: ko.toJSON(selectedItem),
contentType: "application/json",
dataType: "json",
success: function(result) {
alert(result);
},
error: function() {
alert("fail");
}
});
};
return {
sites: sites,
selectedItem: selectedItem,
selectItem: selectItem,
save: save
}
}
ko.applyBindings(viewModel);
</script>
I'll answer your points one at a time, since they are not really related.
1) The issue here is, you take your ASP.NET MVC model, and put it in an observableArray. The thing is, an observableArray will update the UI if items are added, deleted or swapped around, but it will not notify the UI of changes to a single item. So even though you're really editing the row correctly, the UI will never know. The ideal solution would be to not simple inject your MVC model into an observableArray, but to map the model to a datastructure where the editable properties of an item (id, url, name) are made observable. Untested demonstration code:
var rawSites = #Html.Raw(Model.ToJson()),
sites = ko.observableArray(rawSites.map(function (rawSite) {
return {
id: ko.observable(rawSite.id),
url: ko.observable(rawSite.url),
name: ko.observable(rawSite.name)
};
}));
Edit: My original answer suggested a second approach that 'hacked' a UI update by removing the edited item from the observableArray and re-adding it. #Tomalak made a better suggestion in the comments: use valueHasMutated() on the item instead. The result is the same but it's much less hacky. Note that the above solution is in my opinion still preferable because it will perform better (less UI reflow necessary), and it is more robust when you later add more functionality to this code.
2) Depends a bit on what you want. Do you want the edit-form to stay visible or to disappear? You're already using a with: selectedItem binding which makes the disappear-behavior very easy: Just call selectItem(null) from your save success-callback. If you want the form to stay visible all the time, and just clear the fields, I guess the following approach would work:
function viewModel() {
var sites = ko.observableArray(#Html.Raw(Model.ToJson()));
var originalItem = null;
var selectedItem = {
id: ko.observable(),
url: ko.observable(),
name: ko.observable()
};
var selectItem = function (s) {
// This function now copies the properties instead of using the item itself
selectedItem.id(ko.unwrap(s.id));
selectedItem.url(ko.unwrap(s.url));
selectedItem.name(ko.unwrap(s.name));
// Get a reference to s so we can update it when we are done editing
originalItem = s;
};
var resetSelectedItem = function () {
// Clear the form and reset the reference we held earlier
selectItem({
id: null,
url: null,
name: null
});
originalItem = null;
};
save = function () {
alert(ko.toJSON(selectedItem));
$.ajax({
url: "/Home/Save",
type: "POST",
data: ko.toJSON(selectedItem),
contentType: "application/json",
dataType: "json",
success: function(result) {
alert(result);
// Done editing: update the item we were editing
originalItem.id(selectedItem.id());
originalItem.url(selectedItem.url());
originalItem.name(selectedItem.name());
// Clear the form
resetSelectedItem();
},
error: function() {
alert("fail");
// Clear the form
resetSelectedItem();
}
});
};
return {
sites: sites,
selectedItem: selectedItem,
selectItem: selectItem,
save: save
}
}

Detecting Selected Row in html table Knockout/ASP.NET MVC

I've loaded an ASP.NET MVC viewModel into KnockoutJS using ko.mapping.fromJS(Model).
My viewModel looks something like this:
public IEnumerable<FunkyThing>funkyThings;
public FunkyThing selectedFunkyThing;
I've then got in my HTML View a table which looks something like this
<tbody data-bind="foreach: funkyThings">
<tr>
<td data-bind="text:name"></td>
<td data-bind="text:funkiness"></td>
<td>
</td>
</tr>
</tbody>
and all is good with this table. Clicking the select funky thing link happily calls the selectFunkyThing function:
model.selectFunkyThing= function (funkyThing) {
window.location = '#Url.Action(MVC.FunkyThingController.Index())' + "?funkyThingID=" + funkyThing.funkyThingID();
};
which in turn refreshes the page. The MVC viewmodels is reloaded and selectedFunkyThing is populated with the selected FunkyThing and the knockout view models are then re-read from the MVC viewmodel. So far so good.
I then wanted to update the table to highlight the selected entry.
So I updated the tr line as follows:
<tr data-bind="css:{'selected': $root.isSelected()}">
and created the new knockout function:
model.isSelected = function (funkyThing) {
return funkyThing.funkyThingID== model.selectedFunkyThing.funkyThingID;
};
but... it's not working.
Chrome throws a javascript exception stating that the FunkyThing parameter is undefined.
Technically I figure I could solve it by changing the MVC viewModel to actually set a isSelected on each FunkyThing within the array. However I figure there's got to be away of doing this from Knockout?
You were close! I added the ko.utils.unwrapObservable call because I bet the funkyThingID is an observable and not just a straight property - you did this yourself in your selectFunkyThing function. You could just execute them as well. I like the verbosity of unwrap though.
model.isSelected = function (funkyThing) {
var thisId = ko.utils.unwrapObservable(model.selectedFunkyThing.funkyThingID);
return ko.utils.unwrapObservable(funkyThing.funkyThingID) == thisId;
};
and then in your document you actually have to execute this function when ko parses the binding
<tr data-bind="css:{'selected': $root.isSelected($data)}">
Are those properties not both observables, so you need to reference them as functions? You also need to make your function a computed observable, I think:
model.isSelected = ko.computed(function (funkyThing) {
return funkyThing().funkyThingID() == model.selectedFunkyThing().funkyThingID();
});

Meteor with DataTables: Meteor._atFlush TypeError

I'm trying to use DataTables (via mrt add datatables) with Meteor. I've variously tried adding the $('#mytableid').dataTable() to the Meteor.subscribe callback, Meteor.autorun, Meteor.startup, and Template.mytemplate.rendered -- all resulting in the following exception and a No data available in table message.
Any pointers?
Exception from Meteor._atFlush: TypeError: Cannot call method 'insertBefore' of null
at http://localhost:3000/packages/liverange/liverange.js?bc1d62454d1fefbec95201344b27a7a5a7356293:405:27
at LiveRange.operate (http://localhost:3000/packages/liverange/liverange.js?bc1d62454d1fefbec95201344b27a7a5a7356293:459:11)
at LiveRange.replaceContents (http://localhost:3000/packages/liverange/liverange.js?bc1d62454d1fefbec95201344b27a7a5a7356293:403:17)
at http://localhost:3000/packages/spark/spark.js?c202b31550c71828e583606c7a5e233ae9ca50e9:996:37
at withEventGuard (http://localhost:3000/packages/spark/spark.js?c202b31550c71828e583606c7a5e233ae9ca50e9:105:16)
at http://localhost:3000/packages/spark/spark.js?c202b31550c71828e583606c7a5e233ae9ca50e9:981:9
at http://localhost:3000/packages/deps/deps-utils.js?f3fceedcb1921afe2b17e4dbd9d4c007f409eebb:106:13
at http://localhost:3000/packages/deps/deps.js?1df0a05d3ec8fd21f591cfc485e7b03d2e2b6a01:71:15
at Array.forEach (native)
at Function._.each._.forEach (http://localhost:3000/packages/underscore/underscore.js?47479149fe12fc56685a9de90c5a9903390cb451:79:11)
Update: Potentially related to this issue, for which the best solution found was to call dataTable() for each row -- not ideal in that case, and potentially catastrophic in mine given the very large number of rows.
Dror's answer is the right start for sure. Here's the best practice the way I see it currently:
HTML
<template name="data_table">
{{#constant}}
<table class="table table-striped" id="tblData">
</table>
{{/constant}}
</template>
JS:
Template.data_table.created = function() {
var _watch = Collection.find({});
var handle = _watch.observe({
addedAt: function (doc, atIndex, beforeId) {
$('#tblData').is(":visible") && $('#tblData').dataTable().fnAddData(doc);
}
, changedAt: function(newDoc, oldDoc, atIndex) {
$('#tblData').is(":visible") && $('#tblData').dataTable().fnUpdate(newDoc, atIndex);
}
, removedAt: function(oldDoc, atIndex) {
$('#tblData').is(":visible") && $('#tblData').dataTable().fnRemove(atIndex);
}
});
};
Template.data_table.rendered = function () {
if (!($("#tblData").hasClass("dataTable"))) {
$('#tblStudents').dataTable({
"aaSorting": []
, "sDom": "<'row-fluid'<'span6'l><'span6'f>r>t<'row-fluid'<'span6'i><'span6'p>>"
, "sPaginationType": "bootstrap"
, "oLanguage": {
"sLengthMenu": "_MENU_ records per page"
}
, "aoColumns": [
{ "sTitle": "First Data", "mData": function (data) { return data.firstData },
]
});
$('#tblData').dataTable().fnClearTable();
$('#tblData').dataTable().fnAddData(Collection.find().fetch());
}
};
Since Meteor is reactive, you need to create an empty table first and then add the rows.
Create the table:
$('#myTable').dataTable( {
"aoColumns": cols, //the column titles
"sDom": gridDom
} );
Add rows should look something like:
var myCursor = myCollection.find({});
var handle = myCursor.observe({
added: function (row) {
//Add in here the data to the table
},
});
The answered approach still stands but requires some changes after two years.
There is no such thing as constant anymore.
Instead of an empty table, put the thead but NOT the tbody. This way, columns are defined properly.
Put the observer in the onRendered (not rendered anymore) and NOT on onCreated (not created anymore). Otherwise, table is only filled at first creation and not when you come back.
clear and fill methods on the onRendered are not required.
In the observe method, make sure that you are adding an array and not an object. Otherwise fnAdd cannot render content
fnRemove is no more. Use fnDeleteRow instead
I'm not sure if the "is visible check" is required or not. There does not seem to be a problem when you remove it too, so I removed it.
With the above remarks, here is the code:
Template.creamDealsList.onRendered(function () {
if (!($("#tblData").hasClass("dataTable"))) {
$('#tblStudents').dataTable({
// Those configuration are not really important, use it as they are convenient to you
"aaSorting": []
, "sDom": "<'row-fluid'<'span6'l><'span6'f>r>t<'row-fluid'<'span6'i><'span6'p>>"
, "sPaginationType": "bootstrap"
, "oLanguage": {
"sLengthMenu": "_MENU_ records per page"
}
, "aoColumns": [
{ "sTitle": "First Data", "mData": function (data) { return data.firstData },
]
});
}
var _watch = Collection.find({});
var convertDocToArray = function (doc) {return [doc._id, doc.name]}
var handle = _watch.observe({
addedAt: function (doc, atIndex, beforeId) {
$('#tblData').dataTable().fnAddData(convertDocToArray(doc));
}
, changedAt: function(newDoc, oldDoc, atIndex) {
$('#tblData').dataTable().fnUpdate(convertDocToArray(newDoc), atIndex);
}
, removedAt: function(oldDoc, atIndex) {
$('#tblData').dataTable().fnDeleteRow(atIndex);
}
});
})
And simple HTML would be like:
<template name="data_table">
<table class="table table-striped" id="tblData">
<thead>
<th>Id</th>
<th>Name</th>
</thead>
</table>
</template>
This worked at least for me. I'm not seeing flush errors anymore. And also, when you render later, sometimes, there was a no data available message in the datatable and that disappeared as well.
Here's my implementation of dataTables in Meteor, using CoffeeScript and HTML. It works, and it's simpler, but may be less efficient than the existing answers for large tables (it's quick in my case though).
Template Function
Template.dashboard.rendered = ->
# Tear down and rebuild datatable
$('table:first').dataTable
"sPaginationType": "full_numbers"
"bDestroy": true
HTML
<template name="dashboard">
<table class="table table-bordered" id="datatable_3" style="min-width:1020px">
<thead>
<!-- code removed to save space -->
</thead>
<tbody id="previous-jobs-list">
{{#each jobs}}
{{> jobRow}}
{{/each}}
</tbody>
</table>
</template>
<template name="jobRow">
<tr>
<td>{{number}}</td>
<td>{{normalTime date}}</td>
<!-- more code removed to save space -->
</tr>
</template>
Where dashboard.jobs is a simple .find(...) on the collection.