在某些輸入類組件中,擁有綁定一個 Model 的屬性和數值的功能。與 bind(key, value)
方法不同的是,他們需要根據該 Model 的 屬性類型去綁定數值,而非像 bind
一樣可任意綁定自定義的鍵和任意數值。
以下為 TextInputField
工廠的綁定屬性方法源碼:
@Override
public TextInputFactory bindInput(String field, String initValue) {
bind(AttributeController.FIELD_TAG, field);
bind(AttributeController.VALUE_TAG, initValue);
return this;
}
從上述代碼可以看到,所綁定的方式並不是以 屬性作爲 key,數值作爲 value,而是把他們透過特定的 key 來分開綁定。因此,一個組件只能綁定一個屬性數值。
我們以框架内的 demo 作爲教學:
{% tabs %} {% tab title="數據模型" %}
public class TestModel {
public Color testColor;
public LocalDate testDate;
public LocalTime testTime;
@Override
public String toString() {
return "TestModel{" +
"testColor=" + testColor.toString() +
", testDate=" + testDate.toString() +
", testTime=" + testTime.toString() +
'}';
}
}
{% endtab %}
{% tab title="界面" %}
@ViewDescriptor(
name = "Test GUI",
rows = 2,
patterns = {
"ZZZZZZZZZ",
"ZZZZZZZZA"
},
cancelMove = {'Z', 'A'}
)
public class TestView implements View<Void> {
@Override
public void renderView(Void model, UIContext context) {
ButtonFactory button = context.factory(ButtonFactory.class);
RGBSelectorFactory rgbSelector = context.factory(RGBSelectorFactory.class);
DateSelectorFactory dateSelector = context.factory(DateSelectorFactory.class);
TimeSelectorFactory timeSelector = context.factory(TimeSelectorFactory.class);
context.pattern('Z')
.components(
rgbSelector
.bindInput("testColor", Color.WHITE)
.label("&aColor Select: (shift to move color, click to +/-, middle to input)")
.create(),
dateSelector
.bindInput("testDate", LocalDate.now())
.label("&aDate Select: (shift move unit, click to +/-, middle to input)")
.icon(Material.BEACON)
.create(),
timeSelector
.bindInput("testTime", LocalTime.now())
.label("&aTime Select: (shift move unit, click to +/-, middle to input)")
.icon(Material.CLOCK)
.create()
)
.and()
.pattern('A')
.components(
button.title("&aTest").icon(Material.DIAMOND_BLOCK).create()
);
}
}
{% endtab %} {% endtabs %}
從上述代碼中你可以發現,bindInput
所綁定的 屬性名稱 與 數據模型内的屬性名稱是一致的。
控制器代碼如下
@UIController("test")
public class TestController {
@PostConstruct
public void beforeCreate(Player player){
player.sendMessage("life cycle: before create for test controller");
}
public BukkitView<?, ?> index(){
return new BukkitView<>(TestView.class);
}
@ClickMapping(view = TestView.class, pattern = 'A')
public void onClick(@ModelAttribute('Z') TestModel test, Player player){
player.sendMessage(test.toString());
}
@PreDestroy
public void beforeDestroy(Player player){
player.sendMessage("life cycle: before destroy for test controller");
}
}
Pattern Z 内有三個組件,他們分別是 RGB顔色選擇器,日期選擇器和時間選擇器。於是,本框架會根據該組件所綁定的屬性和數值,為指定的 POJO 類型創建一個新實例並為該實例賦值。
{% hint style="danger" %} 該 POJO 類型不能爲泛型且必須擁有無參數構造器,否則會報錯。 {% endhint %}
@MapAttribute
與 @ModelAttribue
性質大致相同。唯一與 @ModelAttribute
不一樣的是,@MapAttribute
必須以 Map<String, Object>
作爲其返回類型。
@UIController("test")
public class TestController {
@PostConstruct
public void beforeCreate(Player player){
player.sendMessage("life cycle: before create for test controller");
}
public BukkitView<?, ?> index(){
return new BukkitView<>(TestView.class);
}
@ClickMapping(view = TestView.class, pattern = 'A')
public void onClick(Player player, @MapAttribute('Z') Map<String, Object> map){
player.sendMessage(map.toString());
}
@PreDestroy
public void beforeDestroy(Player player){
player.sendMessage("life cycle: before destroy for test controller");
}
}
返回的屬性數值與剛才使用 @ModelAttribute
所返回的基本相同,只是改用了 Map 的形式裝載著。
@ModelAttribute
和 @MapAttribute
可以接受數值為 null 的屬性,你可以用以檢測界面使用者有什麼數值尚未填寫。例如:
@ClickMapping(view = UserUpdateView.class, pattern = 'B')
public BukkitView<?, ?> onSave(@ModelAttribute('A') User user, Player player) {
if (user.username == null){ // 若果是 MapAttribute, 則 map.get("username") == null
player.sendMessage("username is required."); // 玩家尚未填寫 username 欄位
return null; // 返回 null 以不跳轉任何界面
}
player.sendMessage("Pre Saving: "+user.toString());
userService.save(user);
player.sendMessage("Save Success");
return index();
}