界面實現
無論是WEX還是BEX,開發各種客觀題模塊都是最常見的。但是,最近在利用業餘時間想開發一個學生使用的小型客觀題系統時遇到了麻煩。首先,在論壇上搜索到了大量相似問題。問題或者過於簡單,或者過於特殊。且看一下本人提供的客觀題界面,我相信這種設計思路還是與大多數應用需求相一致的。
這是單選題界面和多選題界面。
注意:我每一個小題旁邊放置的按鈕是便於學生自我測試時看參考答案時使用的。這個小功能設計比較簡單(我使用的是PopOver組件,其內的span對應的bind-text令其等於 $model.singleData.val("fAnswer")類似的內容即可實現即時地查看對應小題的答案)。
問題
在開發類似於上面的系統時注意到不少像我這樣的初學者存在與我十分類似的困惑。
(補註:這樣人可能以前有JAVA,C#,VB等UI開發經驗,但遺憾的是,這些軟件開發思路與X5中的H5開發思想發生了重大變化--不知大夥與我意見一致否?)
歸納起來,問題有:
1,list組件的onClick 事件中 如何得到 list某一行的id? 地址:http://doc.wex5.com/comps-list/。
2,如何在list組件中放置radio組件或者radioGroup組件實現類似於我實現的上述選擇題界面?
3,在上面界面前提下,在用戶做過好幾個小題後,如果對比答案(比較庫中正確與錯誤的答案,檢查其做題效果如何)?
補充
由於我上述小系統比較簡單,所以暫時只考慮使用本地json方式存儲試題內容,單選,多選,判斷等都各自對應一個JSON文件。例如,單選題文件singleData.json類似於如下結構:
[
{"fCode":"0401",
"fTrunk":"在數據庫表格中唯一標識一條記錄的是( )。",
"fChoiceA":"A.主鍵",
"fChoiceB":"B.候選鍵",
"fChoiceC":"C.索引",
"fChoiceD":"D.關鍵字",
"fAnswer":"A"
},
{"fCode":"0402",
"fTrunk":"下列哪一種不屬於ACCESS 2010的數據類型( )。",
"fChoiceA":"A.數字",
"fChoiceB":"B.文本",
"fChoiceC":"C.附件",
"fChoiceD":"D.插件",
"fAnswer":"D"
},
{"fCode":"0403",
"fTrunk":"下列哪一種不屬於ACCESS 2010的關係表達式中的邏輯運算符( )。",
"fChoiceA":"A.And",
"fChoiceB":"B.Not",
"fChoiceC":"C.Or",
"fChoiceD":"D.Else",
"fAnswer":"D"
},
{"fCode":"0404",
"fTrunk":"ACCESS 2010六大對象不可以( )。",
"fChoiceA":"A.刪除",
"fChoiceB":"B.隱藏",
"fChoiceC":"C.創建",
"fChoiceD":"D.創建快捷方式",
"fAnswer":"D"
},
............
]
多選題文件結構類似於:
[
{"fCode":"0401",
"fTrunk":"數據庫管理系統主要包含以下功能( )。",
"fChoiceA":"A.數據定義",
"fChoiceB":"B.數據操縱",
"fChoiceC":"C.數據庫運行管理",
"fChoiceD":"D.數據庫建立和維護",
"fChoiceE":"E.數據通信功能",
"fAnswer":"ABCDE"
},
{"fCode":"0402",
"fTrunk":"數據庫管理系統的簡稱是( )。",
"fChoiceA":"A.DB",
"fChoiceB":"B.DBMS",
"fChoiceC":"C.ORDBS",
"fChoiceD":"D.RDBS",
"fChoiceE":"E.DDL",
"fAnswer":"B"
},
{"fCode":"0403",
"fTrunk":"關係是具有如下特徵的二維表( )。",
"fChoiceA":"A.行存儲實體數據",
"fChoiceB":"B.列存儲實體屬性",
"fChoiceC":"C.每列具有唯一名稱且數據類型一致",
"fChoiceD":"D.列的順序任意,行的順序也任意",
"fChoiceE":"E.任意兩行內容不能完全重複",
"fAnswer":"ABCDE"
},
{"fCode":"0404",
"fTrunk":"ACCESS 2010六大對象包括( )。",
"fChoiceA":"A.表",
"fChoiceB":"B.查詢",
"fChoiceC":"C.窗體",
"fChoiceD":"D.宏",
"fChoiceE":"E.模塊",
"fAnswer":"ABCDE"
},
{"fCode":"0405",
"fTrunk":"ACCESS 2010中查詢分爲兩大類型,它們是( )。",
"fChoiceA":"A.選擇查詢",
"fChoiceB":"B.刪除查詢",
"fChoiceC":"C.操作查詢",
"fChoiceD":"D.生成表查詢",
"fChoiceE":"E.SQL查詢",
"fAnswer":"AC"
}
]
加載上述試題內容的方式官方DEMO中大量提供類似代碼,直接使用即可(下面是我加載單選內容的代碼):
Model.prototype.singleDataCustomRefresh = function(event){
var singleData = event.source;
$.ajax({
type: "GET",
url: require.toUrl('./json/singleData.json'),
dataType: 'json',
async: false,
cache: false,
success: function(data){
singleData.loadData(data);//將返回的數據加載到data組件
},
error: function(){
throw justep.Error.create("加載數據失敗");
}
});
};
至於設置單選題對應數據組件singleData的limit等屬性小問題及radio和radioGroup組件的常見屬性設置在此小不贅述了(我使用的是radioGroup組件設計單選題界面)。
真正的問題
可能受到以前UI設計思路的影響,很可能大家的問題都集中在前面列舉的問題1上,即“list組件的onClick 事件中 如何得到 list某一行的id”。但是,看X5的UI組件設計思想,這種想法本身可以理解,但是在X5 UI編程中是不能考慮的(即它無法支持)。
例如,無論在官方‘文檔中心’的list組件介紹或者‘如何主動定位到行’(http://bbs.wex5.com/forum.php?mod=viewthread&tid=117208&highlight=list%E7%BB%84%E4%BB%B6)帖子中,都提供類似於這樣的描述:
“
數據再多也是遍歷!
wex5開發項目,準守的一個原則就是面向數據變成,面向data組件編程!
頁面一切操作都是直接操作數據組件的!”
如果上面這個問題1解決了,那麼其他相應問題便迎刃而解!不知官方權威開發朋友對此是如何解釋的(因爲那個‘文檔中心’的問題貴方並沒有給予解釋)。
我遇到的一個具體問題
在上面以單選題爲代表的設計下,我很自然地想到了radioGroup的onChanged事件,於是添加了如下代碼:
Model.prototype.radioGroupSingleChange = function(event){
var crow = this.comp('singleData').getCurrentRow();//start from 0
//var id1 = this.comp('singleData').getRowID(crow);
console.log('Current row index: '+" "+crow.index());
//var context=event.bindingContext;
//var span1=this.comp('outputSingle');
//console.log("No: "+span1.innerText);
//debugger;
var data=this.comp('singleData');
var v = data.getValue('fCode');
console.log("fCode: "+v);
//get question seqCode
var i=data.val("fCode").substring(3);
//怎麼總是輸出相同的第1題,選擇A
//store the user select
console.log('第'+i+'題');
//獲取radioGroup的值
var val = this.comp("radioGroupSingle").val();
console.log('select: '+val);
//data.next();
//var confirmRefresh = data.confirmRefresh;
//強制刷新數據
//data.refreshData();
// try{
// data.confirmRefresh = false;
// data.refreshData();
// }finally{
// data.confirmRefresh = confirmRefresh;
// }
//data.refresh();
};
上面的註釋內容我添加了又刪除,刪除了又添加,反覆試驗多次,結果只有一個:並不輸出當前題號,即只是顯示list中第一項,即數據組件第一行中對應內容!
一種勉強的解決辦法
一下找不到那個帖子了,他是在每一個界面中只顯示一個題(無論是單選還是多選),然後下面放置類似於‘第一個’‘下一個’‘最後一個’‘最開始’等常見導航按鈕,這樣設置界面的話問題簡單得多了,即使用類似於上面的radioGroup的onChanged事件中,調用數據組件的next,prev等方法即可實現每一小題的實時定位。這種方案容易多了!
但是,想一下,如今的智能手機屏幕越來越大,不考慮使用scrollView與list組件(其中放置每道試題相關內容)結合,每一頁中顯示N道題,顯然是不友好的設計。即上述設計方案存在片面性,而不是更爲一般的方案。
我目前想到的針對我的上面設計的另一種尚未試驗的方案是,在DATA組件設計中,再添加一個答案列。上面的fAnswer這一列對應於正確的標準答案,可以再添加一個用戶本人選擇答案的存儲列,例如稱爲fUserAnswer。我們不去考慮類似於上面onChanged事件中準確定位每一題的題號的問題,而只是關注用戶做題結束後通過對應上面的兩個列,讓用戶知道他做對了哪些?做錯了哪些即可。另外,有了上面的即時提示按鈕,他當時做第X小題的正確與否情況問題也解決了。不知各位看法如何?
我抓緊試驗一下,結果將會一併提交於下面。
奇怪的是,‘利用BeX5做隨機抽取試題的考試系統’(http://bbs.wex5.com/forum.php?mo ... 5%E9%80%89%E9%A2%98)與我忘記的一個帖子中提到的解決類似於上述試題系統的界面設計都是每一個界面顯示一個小題。我認爲這並不代表X5軟件設計的主流(靈活多變)。因此,可以把這種‘勉強’的方案放在一邊,而探討更爲通用的辦法。
小結
沒有問題,根據我的上述分析與猜測,終於解決了問題!
所犯的錯誤,現在根本來看,還是對於X5的UI設計思想沒有根本把握所致。也就是說,由於受以前語言UI設計思想的影響,還是想定位到LIST組件中某一項的ID!這種想法本身在X5設計來說就是“錯誤路線”!
其實,在X5數據組件爲核心的UI設計理念下,我們無需關注當前做某一小題對應的哪一個小題的做題(這着實令人感覺有些奇怪!?)
基於上面想法,我把數據組件修改了一下(JSON文件結構不必作任何改變!),如下圖所示:
設計中,需要把radioGroup組件的bind-ref關聯到上面SingleData組件等,請參考下圖:
另外,radioGroup組件的bind-itemset等屬性與另一個簡單數據組件radioGroupDataForSingle也關聯到一起,此組件的結構很簡單,只是想顯示radioGroup組件的A,B,C,D四個選項而已,代碼如下:
<div component="$UI/system/components/justep/data/data" autoLoad="true"
xid="radioGroupDataForSingle" idColumn="choice">
<column label="選項" name="choice" type="String" xid="xid21"/>
<data xid="default3">[{"choice":"A"},{"choice":"B"},{"choice":"C"},{"choice":"D"}]</data>
</div>
做題過程中,SingleData組件的fUserAnswer字段存儲當前用戶做每一個小題的實時選項。
最後,對照正確與錯誤答案統一進行處理即可。請參考如下代碼:
Model.prototype.button1Click = function(event){
//顯示用戶的做題結果
//如果data指定的是data對象
var rows = this.comp('singleList').getBindingObjects();
if(rows && rows.length>0)
$.each(rows,function(i,row){
console.log('i: '+i);
console.log('fCode: '+row.val('fCode'));
console.log('right: '+row.val('fAnswer')+'---'+'user: '+row.val('fUserAnswer'));
//......
});
};
控制檯輸出結果是沒有問題的,如下:
i: 0
console.js:7 fCode: 0401
console.js:7 right: A---user: C
console.js:7 i: 1
console.js:7 fCode: 0402
console.js:7 right: D---user: B
console.js:7 i: 2
console.js:7 fCode: 0403
console.js:7 right: D---user: A
console.js:7 i: 3
console.js:7 fCode: 0404
console.js:7 right: D---user: C
console.js:7 i: 4
console.js:7 fCode: 0405
console.js:7 right: A---user: C
console.js:7 i: 5
console.js:7 fCode: 0406
console.js:7 right: A---user: C
console.js:7 i: 6
console.js:7 fCode: 0407
console.js:7 right: B---user: C
console.js:7 i: 7
console.js:7 fCode: 0408
console.js:7 right: D---user: C
console.js:7 i: 8
console.js:7 fCode: 0409
console.js:7 right: C---user: B
console.js:7 i: 9
console.js:7 fCode: 0410
console.js:7 right: D---user: B
console.js:7 i: 10
console.js:7 fCode: 0411
console.js:7 right: D---user: A
console.js:7 i: 11
最後,祝各位一舉解決所有移動或者微信平臺客觀題有關UI設計問題.
又一扇大門敞開
補充此節,我不由得“冒了一身冷汗”(魯迅先生在哪一篇文章中使用了類似的術語?),虧得我前面的語言描述還算謙遜一些。
剛剛,在學習bind-select屬性時,我通過帖子http://bbs.wex5.com/forum.php?mod=viewthread&tid=82417&extra=&page=1,找到了又一種方法的啓示。以及上面的單選題UI設計爲例,即想尋找LIST組件的當前行序號是可以實現的,代碼如下:
Model.prototype.li1Touchend = function(event){
//var rows = this.comp(“list1”).getBindingObjects();
var row = event.bindingContext.$object;
//debugger;
console.log(row.val('fCode'));
};
注意,我使用的也是list中li的touchstart和touchend事件。另外,前面帖子中還提供另一個重要技巧,即:
根據索引獲得行的方法找到了
data.datas.get()[index]