我正在尝试让 jquery 可排序以在 Tapestry 5.4 应用程序中工作。实际的可排序表正在工作,但是在移动一行后,我无法获取事件来进行客户端调用以实际更新数据库。
任何人都可以帮助确定我可能没有正确使用 $.ajax({}) 来阻止它为 Tapestry 常规页面创建要处理的事件吗? console.log 行正在记录,位置数组包含在客户端完成数据库更新所需的数据。
$(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);
}
});
}
我针对该 URL 尝试了不同的策略,但均无济于事。当我尝试执行以下操作来创建 Tapestry 事件链接并将其用作 ajax 函数中的 URL 时
在 Tapestry Groovy 页面中
String getActionUrl() {
return resources.createEventLink("tableUpdate").toURI()
}
Javascript 尝试从 Tapestry 页面引用方法:
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);
}
});
}
它导致控制台记录错误:
POST http://localhost:8080/cndc/${actionUrl}
Tapestry 页面中的 actionUrl 未使用 getter 进行翻译。
我尝试在 $(document).ready(function () 中为 actionUrl 创建一个 var
var eventURL = ${actionUrl}
然后将该变量传递给 saveNewPositions(eventURL) 函数,但这也导致 var 没有翻译 actionUrl。
替换
${...}
表达式仅适用于模板 (tml) 文件。也就是说,如果您的 ${actionUrl}
直接包含在模板文件中,它将被页面类的属性值替换。因此,将 JavaScript 放入模板文件中的 <script>
块中就可以解决问题,可惜的是,代价是各种缺点,例如(缺乏)可重用性、异步加载等。
这是解决该问题的 Tapestry 方法。 Tapestry 5.4+ 附带 RequireJS,一个用于异步加载 JavaScript 模块的框架。您可以在模块中组织 JavaScript 逻辑。定义模块通常就像调用 RequireJS 的
define()
函数一样简单。您还可以通过为 Tapestry 的 ModuleManager
服务做出贡献,将整个库添加为模块。定义模块后,您可以让页面类负责渲染指示客户端加载模块的代码。
sortable()
函数由 jQuery UI 提供,与 jQuery 不同,它不随 Tapestry 一起提供,因此需要作为模块添加。一种方法如下。
src/main/resources/META-INF/assets/jquery-ui/
AppModule
中,将 jQuery UI 定义为模块:@Contribute(ModuleManager.class)
public static void addModules(MappedConfiguration<String, JavaScriptModuleConfiguration> conf,
@Path("classpath:META-INF/assets/jquery-ui/jquery-ui.min.js") Resource jqueryui) {
conf.add("jqueryui", new JavaScriptModuleConfiguration(jqueryui));
}
现在 jQuery UI 可用为
jqueryui
,您现在可以继续并在您自己的模块的定义中引用它。其初步定义在src/main/resources/META-INF/modules/MyItemSorting.js
中如下:
define(['jquery', 'jqueryui'], function($, qui) {
// The clientId identifies the DOM element to which the sortable
// widget will be applied. It will be passed by the Tapestry page
// class.
return function(clientId) {
$("#" + clientId).sortable(
// ..
);
}
});
现在定义了模块的基本版本,您可以指示页面类呈现负责加载模块的客户端代码。
@InjectComponent
Any itemContainer; // Expects <t:any element="div" t:id="itemContainer"> in the template file.
@Environmental
JavaScriptSupport jsSupport;
@AfterRenderTemplate
void requireModules() {
jsSupport.require("MyItemSorting").with(itemContainer.getClientId());
}
了解了页面类和模块的连接方式后,就可以完成模块定义了。请注意,此处使用了另一个模块
t5/core/ajax
(Tapestry 内置)。
define(['jquery', 'jqueryui', 't5/core/ajax'], function($, qui, ajax) {
return function(clientId) {
$("#" + clientId).sortable({
update: function(event, ui) {
// (1) Build an array of item identifiers (obtained
// from the data attributes previously rendered by
// the template) in the new order
var order = [];
$("#" + clientId).find("div[data-itemid]")
.each(function(index) {
order.push($(this).attr("data-itemid"))
});
// (2) Send the new order to the page class
ajax('updateitemorder', {
element: null, // null necessary when the page
// (rather than a component)
// is supposed to handle the event
data: {order: JSON.stringify(order)},
// response.json is the object returned by
// the event handler method
success: function(response) {
console.log(response.json.numUpdated +
" items updated on server.");
}
});
}
});
}
});
我知道更新所有项目与仅更新已更改项目的方法有些不同。然而,它允许在这里提供更简洁的答案。我相信您可以根据您的需要调整我的示例。
剩下要做的最后一件事是确保页面类正确处理由
updateitemorder
函数调用的 ajax()
事件。继续阅读。
假设您使用 Tapestry 5.4.2 或更高版本,请使用
@PublishEvent
注释事件处理程序,这会使其被 ajax()
函数识别。
将
@RequestParameter
添加到事件处理程序至关重要,否则无法从客户端接收数据。
@Inject
ItemService itemService;
@OnEvent("updateBoatOrder")
@PublishEvent
JSONObject updateItemOrder(@RequestParameter("order") JSONArray order) {
long numUpdated = itemService.reorder(order);
return new JSONObject("numUpdated", numUpdated);
}
完成!当然,从你开始的地方(在 RequireJS 世界之外)进行了一些重写。但当你已经身处其中时,其实并没有那么复杂。
作为结束语,请注意,从版本 5.5 Tapestry 开始也支持 TypeScript。只需添加
tapestry-webresources
依赖项即可开始。