I am trying to get jquery sortable to work in a Tapestry 5.4 application. The actual sortable table is working, but after a row is moved, I am unable to get the event to make a client side call to actually update the database.
Can anyone help determine what I may not be doing correctly with the $.ajax({}) that is preventing it from creating an event for the Tapestry groovy page to handle? The console.log lines are logging and the positions array has the data desired to completed the database update on the client side.
$(document).ready(function () {
$('table tbody').sortable({
update: function (event, ui) {
$(this).children().each(function (index) {
if ($(this).attr('data-position') != (index+1)) {
$(this).attr('data-position', (index+1)).addClass('updated');
}
console.log("tbody update function")
});
saveNewPositions();
}
});
});
function saveNewPositions() {
var positions = [];
$('.updated').each(function () {
positions.push([$(this).attr('data-index'), $(this).attr('data-position')]);
$(this).removeClass('updated');
});
console.log("saveNewPositions of table update" + positions)
$.ajax({
url: "configjquery/tableUpdate",
method: 'POST',
dataType: 'text',
data: {
update: 1,
positions: positions
}, success: function (response) {
console.log(response);
}
});
}
I have tried different tactics for the URL to no avail. When I tried to do the following to create a Tapestry event link and use it for the URL in the ajax function
In the Tapestry Groovy page
String getActionUrl() {
return resources.createEventLink("tableUpdate").toURI()
}
Javascript trying to reference method from Tapestry page:
function saveNewPositions() {
var positions = [];
$('.updated').each(function () {
positions.push([$(this).attr('data-index'), $(this).attr('data-position')]);
$(this).removeClass('updated');
});
console.log("saveNewPositions of table update" + positions)
$.ajax({
url: "${actionUrl}",
method: 'POST',
dataType: 'text',
data: {
update: 1,
positions: positions
}, success: function (response) {
console.log(response);
}
});
}
It resulted in an error logged to the console:
POST http://localhost:8080/cndc/${actionUrl}
The actionUrl is not being translated with the getter in the Tapestry page.
I tried creating a var in the $(document).ready(function () for the actionUrl
var eventURL = ${actionUrl}
and then passing that variable to the saveNewPositions(eventURL) function, but it also resulted in the var not having the actionUrl translated.
Replacing ${...} expressions works in template files only
Replacing
${...}expressions works in template (tml) files only. That is, if your${actionUrl}was contained directly in the template file it would get replaced by that property value of the page class. So, putting your JavaScript into a<script>block within the template file would do the trick, alas, at the cost of various disadvantages like (the lack of) reusability, asynchronous loading etc.Let Tapestry take care of loading the JavaScript
Here is the Tapestry way of solving that problem. Tapestry 5.4+ comes with RequireJS, a framework for loading JavaScript modules asynchronously. You organize your JavaScript logic in modules. Defining a module is often as simple as calling RequireJS's
define()function. You can also add entire libraries as modules by contributing to Tapestry'sModuleManagerservice. Once the modules are defined you can let the page class take care of rendering the code that will instruct the client to load the modules.The
sortable()function is provided by jQuery UI which unlike jQuery does not ship with Tapestry and hence needs to be added as a module. One way of doing that is as follows.src/main/resources/META-INF/assets/jquery-ui/AppModule, define jQuery UI as a module:With jQuery UI now available as
jqueryui, you can now go ahead and reference it in the definition of your own module. Its preliminary definition, insrc/main/resources/META-INF/modules/MyItemSorting.js, is as follows:Now with a rudimentary version of the module defined, you can instruct the page class to render the client code responsible for loading the module.
Having understood how the page class and the module are wired up, you can finish the module definition. Note another module,
t5/core/ajax(Tapestry built-in), is used here.I'm aware updating all items is somewhat different from your approach of updating only the ones changed. However, it allows for a more concise answer here. I'm sure you can adapt my example to your needs.
The last thing left to do is make sure the page class properly handles the
updateitemorderevent called by theajax()function. Read on.Use @RequestParameter in the event handler method
Assuming you are using Tapestry 5.4.2 or later, annotate the event handler with
@PublishEventwhich causes it to become known to theajax()function.Adding
@RequestParameterto the event handler is crucial as otherwise the data cannot be received from the client.Done! Granted, a bit of a rewrite from where you started (outside of the RequireJS world). But actually not so complicated when you are already in it.
As a closing remark, note that from version 5.5 Tapestry also supports TypeScript. Just add the
tapestry-webresourcesdependency and you are good to go.