TYPO3 - Adding extbase viewhelper via javascript to partial - datatables

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.

Related

File upload with extra data in Vue JS

I have used Vue Dropzone to upload files before which is fairly self explanatory but I had a question about something that I don't even know is possible but here goes:
Users drags files to dropzone area and they are added to the queue
User uploads files
User then sees a table with each file as a row where they have to select a category for each file
However, I don't want to upload anything until categories have been selected for each file so instead my thought is to bind the dropzone queue to a data property.
Something like this
mounted() {
this.dropzone = new Dropzone("#dropzone", {
url: "/file/upload",
addRemoveLinks: true
});
this.files = this.dropzone.files;
}
Then use that to create a table using v-for and in each row there would be a category selection dropdown.
<table>
<thead>
<tr>
<th>File Name</th>
<th>File Size</th>
</tr>
</thead>
<tbody>
<tr v-for="file in files">
<td>{{ file.name }}</td>
<td>{{ file.size }}</td>
</tr>
</tbody>
My question then becomes how would you assign a category to each file in the files data property?
In the end I want to send through the file and the category.
From the documentation's list of config options there is the autoProcess option you can use to prevent automatic file upload
If false, files will be added to the queue but the queue will not be processed automatically. This can be useful if you need some additional user input before sending files (or if you want want all files sent at once). If you're ready to send the file simply call myDropzone.processQueue().
By listening to the dropzone's addFile event, you can access the new file and add it to a files array that you can display in your table and later access to add categories (however you want to do that).
this.myDropzone = Dropzone("#my-element", {
url: "/file/upload",
addRemoveLinks: true,
autoProcess: false
});
this.myDropzone.on("addedfile", file => {
this.files.push(file)
});
Finally, you can manually or programmatically trigger the upload with the processQueue function you can call however you'd like, such as on a button click
<button #click="upload">Upload Files</button>
upload() {
this.myDropzone.processQueue();
}

Vue.js Update Array not working using this.$set

I do a request to a service and fill an object that have multiple arrays inside and it's an array itself. ex: this.Jprojs: [{name : 'test', ListItem1 : [], ListItem2 : [] }]
I put that object in a v-for:
<div id="app">
<table class="table table-striped">
<tr>
<th width="15%">Proj</th>
<th>Detail</th>
</tr>
<tr v-for="proj in Jprojs" :key="proj.name">
<td style="vertical-align:middle;"><strong>{{proj.name}}</strong><br/><a v-on:click="list(proj)"> <font-awesome-icon icon="tasks" /></a></td>
<td>{{proj.ListItem1.length}}</td>
</tr>
</table>
I have the method list:
list : function(proj){
axios.get(url).then(
response => {
this.$set(proj.ListItem1,0,response.data.value);
//Vue.set(proj.ListItem1,0,response.data.value);
this.nextTick;
console.log(proj)
},
error => {
},
err => { }
);
}
The console shows the update but the html is not updated.
Make sure to update the Jprojs value, instead of proj. You coud pass an index instead of the proj object.
Get the index with v-for="(proj, index) in Jprojs" and pass it as list(index). Then just edit the Jprojs array with the given index; Jprojs[index].ListItem1 = ...
I found the problem, Actually the property ListItem1 was not in the original Json so vue was not recognizing. what I did was use vue.$set correctly, was using Wrong
both work
this.$set(proj,"ListItem1",response.data.value);
Vue.set(proj,"ListItem1",response.data.value);

Using jQuery DataTables with 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();
}
}

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
}
}

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.