无处不在
在相当长一段时间内,Java都未能兑现“一次编写,随处运行”的承诺。 DukeScript希望通过在跨平台应用程序中实现视图和逻辑的清晰分离来改变这种状况。 在本文中,一个简单的场景用于介绍DukeScript的基础。
多年以来,Java Swing使开发人员能够编写可以在任何操作系统上运行的应用程序。 随着智能手机,平板电脑和嵌入式计算机的到来,一切都结束了。 在企业中,台式机占据了很多年。 但是,与此同时,每个IT项目都包括未来的计划,将来需要将应用程序移植到移动平台。 为所有平台构建本机应用程序需要特殊技能,并且在维护和开发方面都非常昂贵。 什么是要做?
DukeScript( DukeScript.com )再次为您提供了基于Java的解决方案,使您可以开发跨平台应用程序。 DukeScript提供了视图和逻辑的清晰分离,使UI设计人员可以专注于UI设计,而编码人员可以专注于编写经过良好测试的应用程序代码。
两全其美的
DukeScript的基本思想很简单。 每个操作系统都可以运行基本的Java应用程序。 在Android上,这是通过Dalvik Runtime和ART原生完成的 ,在iOS上您拥有RoboVM ,在其他许多平台上,您拥有OpenJDK和Oracle的Java SE Embedded 。 同时,有一系列Java虚拟机可用于浏览器( TeaVM , Doppio , Bck2Brwsr ),所有这些虚拟机都可以在没有浏览器插件的情况下运行。 但是,这里缺少的是统一视图技术,而与此同时,几乎所有平台上都提供了现代HTML渲染器组件。 将这些不同的技术,虚拟机和组件组合在一起时,便具有了全面框架的基础。
通过将所有这些部分组合在一起,可以同时利用其所有优势。 例如,由于Java具有静态类型,因此在所有编程语言中,Java都提供了最佳的IDE支持,并使可维护的代码得以编写并易于重构。 由于这些原因,它比JavaScript更适合用作大型项目的语言。 在UI方面,对于HTML和CSS,您可以访问免费的商业框架和服务库。 当UI和业务逻辑彼此清晰地分开时,我们可以毫无例外地,不受任何限制地利用完整的武器库。 为了证明这些观点,我们现在将开发和设计“待办事项列表”应用程序。
ViewModel
DukeScript使用Model-View-ViewModel(MVVM)设计模式将可视化和逻辑分离。 View是用标记语言定义的,并且将活动元素声明性地绑定到ViewModel的属性。 通过这种架构,ViewModel不需要了解View。 无需任何更改,即可从ViewModel换出View。 所有View逻辑都在ViewModel中定义。 在MVVM模式中,模型是应用程序的其余部分,而如何可视化模型则是未定义的,因此不受限制。
让我们从ViewModel开始。 清单1显示了如何创建ViewModel。 @Model批注确保将生成名为Task的类。 同时,将自动创建属性title和complete的 setter和getter。 这使开发人员不必编写一堆代码,并且ViewModel类的结构紧凑且一目了然。 创建过程会在后台自动进行,因此在IDE中进行开发时可以立即使用该类。
清单1
@Model(className = "Task", properties = {
@Property(name = "title", type = String.class),
@Property(name = "complete", type = boolean.class)
})
public static class TaskModel {}
对于更复杂的任务,我们可以包装模型。 清单2显示了一个TaskListViewModel ,它表示Tasks列表以及其他属性。 @Function注释标记可以从View调用的方法。
清单2
@Model(className = "TaskListViewModel", properties = {
@Property(name = "input", type = String.class),
@Property(name = "tasks", type = Task.class, array = true),
@Property(name = "editing", type = Task.class)
}, targetId = "body")
final class TaskListViewModelDefinition {
@Function
public static void editTask(TaskListViewModel list, Task data) {
list.setEditing(data);
}
@Function
public static void stopEditing(TaskListViewModel list) {
list.setEditing(null);
}
@Function
@ModelOperation
public static void deleteTask(TaskListViewModel model, Task data) {
model.getTasks().remove(data);
}
@Function
@ModelOperation
public static void addTask(TaskListViewModel model) {
if (null == model.getInput() || model.getInput().length() == 0) {
return;
}
Task task = new Task(model.getInput(), false);
model.setInput("");
model.getTasks().add(task);
}
}
使用DukeScript进行单元测试
上面代码中的两个方法被标记为@ModelOperation 。 在DukeScript中,您使用的方法也需要从View外部调用,在我们的示例(清单3)中,这是一个单元测试。 该测试显示了如何使用生成的ViewModel。 在第一个测试用例中,我们模拟用户输入新任务(输入一个输入( setInput )并确认输入,例如通过按钮或Enter键( addTask )来键入新任务。 即使还没有View,我们已经可以测试ViewModel的方法了。 这种情况确实很好地显示了组件的干净去耦。
清单3
public class TodoListTest {
@Test
public void testAddTask() {
TaskListViewModel taskList = new TaskListViewModel();
Assert.assertEquals(taskList.getTasks().size(), 0);
taskList.setInput("Buy milk!");
taskList.addTask();
Assert.assertEquals(taskList.getTasks().size(), 1);
Task task = taskList.getTasks().get(0);
Assert.assertEquals(task.getTitle(), "Buy milk!");
}
@Test
public void testDeleteTask() {
TaskListViewModel taskList = new TaskListViewModel();
taskList.getTasks().add(new Task("Buy milk!", false));
Assert.assertEquals(taskList.getTasks().size(), 1);
Task task = taskList.getTasks().get(0);
taskList.deleteTask(task);
Assert.assertEquals(taskList.getTasks().size(), 0);
}
}
JSON序列化
当您查看定义ViewModel类的注释时,您应该注意到它们看起来有点像JSON消息。 这并非巧合,因为DukeScript能够轻松地与JSON集成,因此具有巨大的价值。 ViewModel类的toString方法返回JSON字符串。 正如您可以轻松地从JSON字符串创建ViewModel对象一样。 清单4展示了ViewModel如何再次序列化和反序列化。 当您只需要一个对象的副本时,请使用clone方法。 Models.parse的目的是反序列化来自服务器或本地持久化数据的消息 。
清单4
TaskListViewModel copy;
String json = original.toString();
InputStream inputStream = new ByteArrayInputStream(
json.getBytes(StandardCharsets.UTF_8));
try {
copy = Models.parse(BrwsrCtx.findDefault(TaskListViewModel.class),
TaskListViewModel.class, inputStream);
} catch (IOException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
风景
默认情况下,DukeScript使用HTML定义视图。 由于将View与ViewModel完全分开,因此也可以使用其他格式。 dukescript-javafx项目演示了它是如何工作的。 通过该项目,可以在JavaFX应用程序中轻松可视化DukeScript ViewModels。 在这种情况下,用于定义视图的语言是FXML。 Controls.js还使用另一种格式定义Views,本文稍后将对此进行讨论。
对于本文中创建的“待办事项列表”应用程序,我们将仅使用清单5所示的标准HTML格式。依赖于ViewModel的元素将使用data-bind属性。 这样,它们以声明方式绑定到ViewModel的属性和方法。 同样,可以用这种方式定义for-each循环和条件语句。
在大多数情况下, data-bind属性可以满足您的所有需求。 但是,有时您需要创建一个HTML元素,尽管View可能不需要HTML元素。 对于这些情况,有特殊的评论。 在下面清单5的示例中,通过<!– ko foreach:tasks –>遍历Tasks列表,而<!– / ko –>关闭循环。 可以从清单5中的注释以及DukeScript网站中了解有关绑定语法的更多信息。
清单5
<ul>
<!-- Iterate over the List of Tasks in the TaskListViewModel -->
<!-- ko foreach: tasks -->
<li>
<!-- When the Task is not being edited... -->
<!-- ko ifnot: $root.editing()===$data -->
<!-- ...bind the checkbox state to the Task property named "complete" -->
<input type="checkbox" name="" data-bind="checked: complete"/>
<!--...bind the text of the span to the Task property named "title" -->
<span data-bind="text: title"></span>
<span class="btns">
<!-- ...on click, call the 'editTask' method -->
<button data-bind="click: $root.editTask">Edit</button>
<!-- ...on click, call the 'deleteTask' method -->
<button data-bind="click: $root.deleteTask">Delete</button>
</span>
<!-- /ko -->
<!-- When the Task is being edited, show an input field... -->
<!-- ko if: $root.editing()===$data -->
<!-- ...on Submit (Enter) call the 'stopEditing' method -->
<form data-bind="submit: $root.stopEditing">
<!-- ...bind the entered text to the Task property named "title"`-->
<input type="text" data-bind="textInput: title"/>
</form>
<!-- /ko -->
</li>
<!-- /ko -->
<li>
<!-- On Submit (Enter) call the 'addTask' method... -->
<form data-bind="submit: addTask">
<!-- ...bind the entered text to the Task property named "input" -->
<input type="text" data-bind="textInput: input"/>
</form>
</li>
</ul>
上面定义的功能原型如下图所示。 从外观上看,它可能看起来有点谦虚,所以让我们在以下各节中进行更改!
设计者/开发者实验
在DukeScript到来之前,已经出现了其他几个框架,它们有望将设计与开发脱钩。 在现实世界中,几乎没有任何东西可以实现。 设计往往需要专有的工具,这些工具所带来的不仅仅是专业设计师的疲倦之笑。 JavaFX就是一个例子。 使用JavaFX Scene Builder,您甚至都无法创建多边形, 而该工具的未来却一无所获 。 此外,创建设计的任务往往留给开发人员,开发人员需要花费时间来适应各种不同且相互冲突的工具。
为了测试DukeScript是否可以更好地解决所有问题,我给自己设定了一个小挑战。 首先,我为“待办事项列表”应用程序找到并购买了完整的设计 ,如下面的屏幕快照所示。
然后,我到处找人将PSD文件更改为HTML(当您搜索“ PSD到HTML”时,可以在线找到数百个用于此任务的服务)。 我最终选择了Rapidxhtml,因为该服务价格便宜,尽管如此,但受到了好评。
对于“真实”项目,并确保更好的沟通,我绝对希望与设计师直接互动。 但是,对于本实验而言,通信仅限于信用卡和Web表单是一个优点,因为通过这种方式,我们可以确保设计人员不了解DukeScript可能需要的任何特殊要求。
我将PSD文件上传到网站并指出我的样式要求。 例如,由于适用的样式更加精细,因此复选框和滚动条会额外花费。 因此,我决定不进行这些更详细的选择。 最后,完整的订单并不昂贵。 每页的转换成本,包括所有选定的额外功能(宽度调整大小,带有CSS3HTML5大小),约为170欧元。预付款并在原则上在24小时内交付。 听起来不错,我等着悬念。
6小时后,我已经收到一封包含该设计链接的电子邮件。 不错。 乍一看,结果看起来不错,尽管宽度调整没有用。 两个小时后,在回发了我的评论后,我有了一个可以正确调整大小的新版本。 下面的屏幕截图显示了结果。
关于小缺陷的进一步评论被忽略。 对于“实际”项目,优质服务或具有适用建议的设计机构可能是一个更好的选择。 清单6显示了我收到HTML。
清单6
<!DOCTYPE html>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
<head>
<title>TODO</title>
<meta name="robots" content="index, follow">
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="author" content="RapidxHTML" />
<link rel="stylesheet" href="css/normalize.css">
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<!--[if lt IE 7]>
<p class="chromeframe">You are using an outdated browser.
<a href="http://browsehappy.com/">Upgrade your browser today</a>
or <a href="http://www.google.com/chromeframe/?redirect=true">install
Google Chrome Frame</a> to better experience this site.</p>
<![endif]-->
<!-- box -->
<div id="box">
<div class="box-cont">
<header class="box-header">
<div class="box-title">My tasks for today</div>
<div class="box-links">
<a href=""><img src="images/btn-cal.png" alt="" /></a>
<a href=""><img src="images/btn-settings.png" alt="" /></a>
</div>
</header>
<section class="todo">
<section class="todo-bg">
<ul class="todo-list">
<li class="done">
<input type="checkbox" name=""
class="toggle" checked="checked" />
Design a to-do list
<span class="btns">
<a href=""><img src="images/icon-edit.png" /></a>
<a href=""><img src="images/icon-delete.png" /></a>
</span>
</li>
<li><input type="checkbox" name="" class="toggle" />
Design a super task<br />with 2 lines
<span class="btns">
<a href=""><img src="images/icon-edit.png" /></a>
<a href=""><img src="images/icon-delete.png" /></a>
</span>
</li>
<li><input type="checkbox" name="" class="toggle" />
fix the dog toy
<span class="btns">
<a href=""><img src="images/icon-edit.png" /></a>
<a href=""><img src="images/icon-delete.png" /></a>
</span>
</li>
<li><input type="checkbox" name="" class="toggle" />
buy coffee <span class="btns">
<a href=""><img src="images/icon-edit.png" /></a>
<a href=""><img src="images/icon-delete.png" /></a>
</span>
</li>
<li><input type="checkbox" name="" class="toggle" />
feed the dog <span class="btns">
<a href=""><img src="images/icon-edit.png" /></a>
<a href=""><img src="images/icon-delete.png" /></a>
</span>
</li>
<li><input type="checkbox" name="" class="toggle" />
take a walk with the dog
<img src="icon_smile.gif"
alt=":)"
class="wp-smiley">
<span class="btns">
<a href=""><img src="images/icon-edit.png" /></a>
<a href=""><img src="images/icon-delete.png" /></a>
</span>
</li>
</ul>
</section>
</section>
</div>
</div>
<!-- / box -->
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/modernizr-2.6.2.min.js"></script>
</body>
</html>
接下来,我不得不为UI添加生命。 从原型开始,您已经在下面看到的绑定已经很熟悉了。 清单7显示了重写的代码。
清单7
<!DOCTYPE html>
<head>
<title>TODO</title>
<meta charset="utf-8">
<link rel="stylesheet" href="css/normalize.css">
<link rel="stylesheet" href="css/style.css">
</head>
<body id="body">
<!-- box -->
<div id="box">
<div class="box-cont">
<header class="box-header">
<div class="box-title">My tasks for today</div>
<div class="box-links">
<a href=""><img src="images/btn-cal.png" alt="" /></a>
<a href=""><img src="images/btn-settings.png" alt="" /></a>
</div>
</header>
<section class="todo">
<section class="todo-bg">
<ul class="todo-list" >
<!-- ko foreach: tasks -->
<li>
<!-- ko ifnot: $root.editing()===$data -->
<input type="checkbox" name="" class="toggle"
data-bind="checked: complete"/>
<span data-bind="text: title"></span>
<span class="btns">
<img src="images/icon-edit.png" alt=""
data-bind="click: $root.editTask" />
<img src="images/icon-delete.png" alt=""
data-bind="click: $root.deleteTask" />
</span>
<!-- /ko -->
<!-- ko if: $root.editing()===$data -->
<form data-bind="submit: $root.stopEditing">
<input type="text" data-bind="textInput: title"/>
</form>
<!-- /ko -->
</li>
<!-- /ko -->
<li>
<form data-bind="submit: addTask">
<input type="text" data-bind="textInput: input"/>
</form>
</li>
</ul>
</section>
</section>
</div>
</div>
<!-- / box -->
</body>
</html>
在下面的屏幕截图中,您可以看到结果。
UI看起来像设计示例,并且在所有平台上均起作用。 这样,实验取得了圆满成功-可以完全委托设计。 在没有特殊要求的情况下,设计人员和转换服务能够提供可用的资产,这些资产可以以最小的更改集成到应用程序中。
通过这种方式,开发人员能够完全专注于所需功能的实现,并专注于应用程序的业务逻辑。 那些具有桌面应用程序开发经验的人都知道,定期转换应用程序设计会浪费大量开发时间。 使用DukeScript,您可以放心地将这些问题委托给设计专业人员,这可以节省宝贵的时间来实现应用程序的功能要求。
如何开发DukeScript应用程序
目前,DukeScript支持各种桌面平台以及iOS,Android和浏览器。 可用的Maven原型为每个受支持的平台创建一个单独的子项目。 对于每个平台,可以使用特定任务来测试和打包子项目。 例如,Android和iOS的子项目提供了在模拟器或连接的设备上运行它们的可能性,而浏览器的子项目则自动构建了一个静态网站。
最新的增强功能已启用对嵌入式平台的支持,以使DukeScript可以在IoT应用程序中使用。 因此,由于Oracle 结束了对嵌入式平台上JavaFX的支持 ,因此现在再次有可能在嵌入式设备上使用Java开发专业的GUI。 在这些情况下,OpenJDK通常足以用作JVM,因此,即使对于商业项目,也不需要昂贵的Java SE嵌入式许可。
借助Maven原型,可以通过各种Java IDE完成DukeScript应用程序的开发。 专门针对NetBeans IDE,还有一个具有一系列支持功能的插件, 使开发更加舒适 。 例如,HTML编辑器中有data-bind指令的代码完成,而DOM Inspector使您可以检查正在运行的应用程序。 对HTML和CSS的更改将由正在运行的应用程序自动获取。 从0.8版开始,甚至Maven原型都引入了热插拔。 代码更改将自动部署到正在运行的应用程序中,并且可以立即进行测试,如下面的屏幕快照所示。 甚至JavaScript开发人员也应该嫉妒,因为与JavaScript开发不同,应用程序的状态得以保持,而无需进行任何手动重新加载。
用于Java的Controls.js-不带HTML的DukeScript
DukeScript致力于在没有JavaScript的情况下实现跨平台开发。 通常,DukeScript应用程序的前端是在HTML和CSS的帮助下编写的。 如已显示的,可以委派应用程序开发的这一方面,同时继续要求编写测试并针对各种平台进行修改以及手动编辑HTML文件。
作为一种替代方法, Controls.js for Java项目使您可以通过拖放来开发完整的UI。 采用这种方法时,您将拥有Maven原型和一个NetBeans插件来为您提供支持。 Controls.js利用其自己的组件库。 每个单独的控件都可以通过皮肤进行可视化,同时提供了用于创建自定义皮肤的皮肤编辑器。 ViewModel保持不变,而绑定是在可视编辑器的帮助下完成的。
结论
最后,让我们研究一下百万美元的问题:“ DukeScript是否适合我的项目?” 优点很明显。
- 使用通用代码库,可以为许多平台开发应用程序。
- 工作流程结构合理,同时可用丰富且完善的工具来为您提供支持。
- 可以委派设计任务,从而极大地减少了应用程序维护和开发的成本。
尽管如此,DukeScript并不是每个应用程序的最佳解决方案。 与那些对本机外观和风格感兴趣的人相比,那些重视自己的品牌以及统一的跨平台设计的人,可以通过DukeScript获得更好的服务。 我也不会尝试通过DukeScript创建3D建模工具,也不会尝试通过优化的渲染管道来使用任何类型的应用程序。
但是,DukeScript是业务应用程序的很好选择。 对于简单的业务应用程序,使用Controls.js for Java会有所回报,从而可以实现快速的应用程序开发工作流程。 此外,由于其简单的通信机制,对于具有服务器后端的应用程序,DukeScript非常适合。 总体而言,DukeScript为Java开发人员提供了一个跨平台开发的顺利切入点,而无需放弃世界上使用最广泛的编程语言,该语言以静态方式提供最好的IDE支持,无疑比当今世界上任何其他编程语言都要好。
- 本文经Geertjan Wielenga的翻译,由Anton Epple 用德语翻译而成。
翻译自: https://www.javacodegeeks.com/2015/08/java-everywhere-write-once-run-anywhere-with-dukescript.html
无处不在