我将它放在了在GitHub上面,希望有高手一起来帮助完善它,这里附上网址:
之前写过XML技术在界面生成中的简单应用的文章,后来由于一直很忙,就没有再写。但是东西已经可以用了, 附上截图给大家预览下,等自己不那么紧张的时候,再接着跟大家分享,这里先行道歉!
再附上一TabPanel为例子的XML代码:
1 <Tab ID="tab1" Cols="12" Offset="0"> 2 <TabItem Key="tabitem1" LabelText="选项卡一"> 3 <TextBox ID="tabitemtextbox1" LabelWidth="10" Cols="3" Offset="0" LabelText ="文字输入一:" Value="$root.text"/> 4 <TextBox ID="tabitemtextbox2" LabelWidth="100" Cols="3" Offset="0" LabelText ="文字输入二:" Value="$root.text" NotWripRow="true"/> 5 <TextBox ID="tabitemtextbox3" LabelWidth="100" Cols="3" Offset="0" LabelText ="文字输入三:" Value="$root.text" NotWripRow="true"/> 6 <TextBox ID="tabitemtextbox4" LabelWidth="100" Cols="3" Offset="0" LabelText ="文字输入四:" Value="$root.text" NotWripRow="true"/> 7 </TabItem> 8 </Tab>
然后这里是关于操作的定义方式,分为服务器端和客户端操作,其中客户端操作不需要额外手写代码,服务器端也是ajax交互的。也贴出来给大家探讨下(注释部分表示可以那样嵌套定义操作):
1 <Operations> 2 <Operation ID="saveData"> 3 <ServerOperation Handler="SaveChanges" FinishAlert="'保存数据'"> 4 <!--<ServerOperation Handler="SaveChanges" FinishAlert="'Second'"></ServerOperation> 5 <ClientOperation Handler="Confirm" Para="'From client'"></ClientOperation> 6 <ClientOperation Handler="Information" Para="'From client'"></ClientOperation> 7 <FailbackOperation> 8 <ClientOperation Handler="Error" Para="'From client'"></ClientOperation> 9 </FailbackOperation>--> 10 </ServerOperation> 11 </Operation> 12 <Operation ID="changeValue"> 13 <ClientOperation Handler="ChangeValue" Para="'$root.mCourseList[0].Name', $root.mCourseList()[0].Desc" /> 14 </Operation> 15 <Operation ID="changeValue1"> 16 <ClientOperation Handler="ChangeValue" Para="'$root.mCourseList[*].Name', $root.mCourseList()[1].Desc" /> 17 </Operation> 18 <Operation ID="openWindow"> 19 <ClientOperation Handler="OpenWindow" Para="'window1'" /> 20 </Operation> 21 </Operations>
当然,OpenWindow有关于Window的定义,如下:
1 <PopWindow Height="100" Width="100" IsEnable="true" IsVisible="true" ID="window1" Title="这是个窗口" > 2 <TableContainer> 3 <DataGrid Rows="6" SelectColumnName ="isSelected" SelectMode="multi" ID="datagrid1" DataSource="$root.mCourseList"> 4 <Column BindFieldKey="Name" Header="名称" Tip="This is tip"/> 5 <Column BindFieldKey="Desc" Header="描述" Tip="This is tip"/> 6 </DataGrid> 7 </TableContainer> 8 </PopWindow>
看看Rhyme定义的客户端操作类库, 例如ChangeValue,解析'$root.mCourseList[*].Name'这个表达式的代码还算有些价值, 当然,还不太完善:
1 ChangeValue: function (args, successCallBack, failureCallBack) { 2 var _$root, _$data, _event, _target, _assignTo, _from, _dataContext, _statements, _assignmentInContextByStatementsWithValue; 3 4 // Context params. 5 _$root = args.$root; // Root context of view model. 6 _$data = args.$data; // Current context of view model. 7 _event = args.event; // Event from dom or jQuery . 8 _target = args.target; // Source element of event. 9 10 // The change where target. 11 _assignTo = args.params[0]; 12 // The new value. 13 _from = ko.utils.unwrapObservable(args.params[1]); 14 15 // When the target was observable object, Assignment new value to it. 16 if (typeof (_assignTo) === 'function') { 17 _assignTo(_from); 18 } else if (typeof (_assignTo) === 'string') { 19 // The list of statements need to parse. 20 _statements = _assignTo.replace(/\[\*\]/g, '.*').replace(/\[\d\]/g, function (s, t) { return '.' + s.replace(/[^\d]/g, ''); }).split('.'); 21 _assignmentInContextByStatementsWithValue = function (dataContext, statements, newValue, currentStatementIndex) { 22 var _currentContext = dataContext, 23 _currentContextLength, 24 _currentStatementIndex = currentStatementIndex, 25 _currentStatement, 26 _indexStatement, 27 _totalStatements = statements.length; 28 29 // Syntactic analysis. 30 for (var i = _currentStatementIndex; i < _totalStatements; i++) { 31 _currentStatement = statements[i]; 32 33 if (_currentStatement === '$root' || _currentStatement === '$data') { 34 continue; 35 } else if (/^\d+$/.test(_currentStatement)) { // Match index statement. 36 _indexStatement = parseInt(statements[i]); 37 38 if (i === _totalStatements - 1) { 39 _currentContext[_indexStatement](newValue); 40 } else { 41 _assignmentInContextByStatementsWithValue(_currentContext[_indexStatement], statements, newValue, i + 1); 42 } 43 } else if (_currentStatement === '*') { // Match every item. 44 _currentContextLength = ko.utils.unwrapObservable(_currentContext).length; 45 for (var j = 0; j < _currentContextLength; j++) { 46 debugger; 47 if (i === _totalStatements - 1) { 48 _currentContext[j](newValue); 49 } else { 50 _assignmentInContextByStatementsWithValue(_currentContext[j], statements, newValue, i + 1); 51 } 52 } 53 } else { // In normal case. 54 if (i === _totalStatements - 1) { 55 dataContext[_currentStatement.replace(/\(\)/g, '')](newValue); 56 } else { 57 _currentContext = ko.utils.unwrapObservable(dataContext[_currentStatement.replace(/\(\)/g, '')]); 58 } 59 } 60 } 61 }; 62 63 if (_statements[0].search("\\u0024data") != -1) { 64 _dataContext = _$data; 65 } else { 66 _dataContext = _$root; 67 } 68 69 _assignmentInContextByStatementsWithValue(_dataContext, _statements, _from, 0); 70 } 71 }
还有Rhyme 为操作的定义自动生成了相应的操作代码:
1 (function(){ $(function(){initViewModel('PageLoaded', 'Evaluation');}); })(); 2 3 var saveData = function ($root, $data, event, target){window.appViewModel.doAction('SaveChanges','保存数据' , function (viewModel, response) { }, function (xhr) { });} 4 5 var changeValue = function ($root, $data, event, target){ 6 ClientEventHandlers.ChangeValue({ $root: $root, $data: $data, event: event, target: target, params: ['$root.mCourseList[0].Name', $root.mCourseList()[0].Desc] }, function(){ }, function(){ }); 7 } 8 9 var changeValue1 = function ($root, $data, event, target){ 10 ClientEventHandlers.ChangeValue({ $root: $root, $data: $data, event: event, target: target, params: ['$root.mCourseList[*].Name', $root.mCourseList()[1].Desc] }, function(){ }, function(){ }); 11 } 12 13 var openWindow = function ($root, $data, event, target){ 14 ClientEventHandlers.OpenWindow({ $root: $root, $data: $data, event: event, target: target, params: ['window1'] }, function(){ }, function(){ }); 15 }
由于Rhyme比较懒惰,所以ViewModel定义了个通用的,当然,如果你不懒惰,定义N个慢慢玩玩后再决定是否也会学Rhyme偷懒吧。(:以后会慢慢完善:
1 var ViewModel = function (url, pageCode, initAction) { 2 3 var self = this; 4 5 self.url = url; 6 self.pageCode = ko.observable(pageCode); 7 8 self.loadData = function (dataHandler, successMsg, successCallBack, failureCallBack) { 9 var _data = {}; 10 debugger; 11 12 if (typeof (dataHandler) === 'function') { 13 _data = dataHandler(ko.toJS(self)); 14 } 15 16 $.ajax({ 17 url: self.url, 18 data: { pageCode: _data.pageCode, actionName: _data.actionName, "datasource": ko.toJSON(_data) }, 19 type: "POST", 20 dataType: 'json', 21 error: function (xhr) { 22 if (typeof (failureCallBack) === 'function') { 23 try { 24 failureCallBack(self, xhr); 25 MessageCenter.fromXhr(e); 26 } catch (e) { 27 MessageCenter.fromException(e); 28 } 29 } 30 }, 31 success: function (response) { 32 if (successMsg) { 33 MessageCenter.success(successMsg); 34 } 35 36 if (typeof (successCallBack) === 'function') { 37 try { 38 successCallBack(self, response); 39 } catch (e) { 40 MessageCenter.fromException(e); 41 } 42 } 43 } 44 }); 45 46 return self; 47 }; 48 49 // Call service. 50 self.doAction = function (actionName, successMsg, successCallBack, failureCallBack) { 51 debugger; 52 self.loadData(function (model) { 53 model.actionName = actionName; 54 55 return model; 56 }, successMsg, successCallBack, failureCallBack); 57 }; 58 59 //self.saveChanges = function () { 60 // self.doAction("SaveChanges", successMsg, function (viewModel, response) { 61 // debugger; 62 // ko.mapping.fromJS(response, {}, viewModel); 63 // }); 64 //}; 65 66 self.bind = function(element) { 67 if(element) { 68 ko.applyBindings(self); 69 } else { 70 ko.applyBindings(self); 71 } 72 }; 73 74 (function(){ 75 if(typeof(initAction) === 'function') { 76 try { 77 initAction(self); 78 } catch(ex) { 79 // init error 80 } 81 } 82 })(); 83 };
还有为了兼容万恶的IE, 不得不用JS来调整组件大小。Rhyme的布局是支持响应式布局的。当然,也是基于Bootstrap的。
1 (function () { 2 $(function () { 3 var resizeControls = function (forVisible, containerSelector) { 4 var selectorTemplate = 'input[type="text"]{forVisible},input[type="password"]{forVisible},select{forVisible},textarea{forVisible}'; 5 var selector = ''; 6 7 if (forVisible === true) { 8 selector = selectorTemplate.replace(/{forVisible}/g, ':visible'); 9 } else { 10 selector = selectorTemplate.replace(/{forVisible}/g, ''); 11 } 12 13 var $resizeControl = $(selector, containerSelector); 14 var totalControlForResize = $resizeControl.length; 15 for (var i = 0; i < totalControlForResize; i++) { 16 17 var currentControl = $resizeControl.get(i), 18 $currentControl = $(currentControl), 19 $parent = $currentControl.parents('.control-group'), 20 $label = $parent.children('.control-label'); 21 if ($label && $label.get(0)) { $currentControl.width($parent.get(0).offsetWidth - $label.get(0).offsetWidth - 15); } 22 else { 23 $currentControl.width($parent.get(0).offsetWidth - 15); 24 } 25 } 26 }; 27 28 resizeControls(true, '.controls'); 29 30 window.onresize = function () { 31 resizeControls(true, '.controls'); 32 }; 33 34 $('[data-toggle="tab"]').live('click', function () { 35 var container = $(this).attr('href'); 36 resizeControls(false, container); 37 }); 38 39 $('[data-toggle="details"]').live('click', function () { 40 var container = $(this).attr('data-toggle-target'); 41 window.setTimeout(function () { resizeControls(false, container); }, 10) 42 }); 43 }); 44 })();
Rhyme 也为布局提供了模版,类似母版页,当然,用的是HTML,生成后的也是纯HTML,由于服务于企业内部系统,所以没有对SEO做任何考虑。
Rhyme由于工作比较忙,所以难得抽出时间整理东西,接下来要是再有时间,会把那个语法解析的完善下,争取让Knockout中让人头疼的括号语法消失。Rhyme初步的想法是在绑定数据前对原有表达式语法重新解析,这样需要括号的地方就可以解析的时候在用代码插入了。有哪位有更好的想法请求分享一下。(:
Rhyme接下来的源代码会托管到GitHub,也会在博客园放下载,大家共同学习,也多帮我找找Bug,提点不足,在这里先行谢过了。