在LawAndRegulation项目中添加导航路由(Abp添加菜单)对应的客户端页面。
创建文件
客户端页面在Abp模板项目中默认存放在Abp/Main/views文件夹下,在项目中我们创建属于字典管理的新文件夹,名字命名为DictionaryManger。
在文件夹中创建文件index.cshtml和同名的js文件index.js。
创建的index.cshtml文件中只需要编写页面内容部分,idnex.js中编写当前页面逻辑。
创建index.cshtml需要注意:
- 创建时选择“带有布局的MVC5视图页”选项,选择合适的母版(Views/Shared/_Layout.cshtml);
- 删除原有内容;
同样添加index.js文件。
编辑index.cshtml
在视图页面中编写你的html内容,在Abp母版项目中已经引入了Bootstrap,项目DictionaryManager在编写页面内容时,使用Bootstrap作为前端开发框架。
在项目的Bundle.config文件中已经通过文件夹引入的方式引入了/App/Main文件夹下所有css文件所以页面编写需要编写的css可以直接在index.cshtml项目所在目录下创建文件添加,在本项目中我们添加了dicCommon.css文件。
页面中使用Bootstrap模态框,想要详细学习模态框,请移步这里,有详细的使用介绍。
css文件中定义了很多自定义样式,这些样式中的单位存在px和em,存在这样的问题是因为部分内容继承自原有代码,还没有时间进行统一更改,单位之间换算参考链接,这部分内容还没有进行验证。按照文中介绍,默认情况下1em=16px,但em是相对单位所以如果父元素定义font-size字段那么1em=父元素定义的大小。
添加html,代码如下:
<form id="dicForm" runat="server" ng-controller="app.views.dictionarymanager.index as vm"> <div class="row"> <div class="col-xs-12 dic_header" id="dicHeaderBtn"> <button type="button" class="btn btn-primary pull-left" id="btnAddDicCategory" ng-disabled="btnAddDicCategoryDisabled"> 新增字典分类 </button> <button type="button" class="btn btn-primary pull-left" id="btnAddDic" ng-disabled="btnAddDicDisabled" ng-click="AddDictinary()"> 新增字典 </button> <button type="button" class="btn btn-primary pull-left" id="btnEditDic" ng-disabled="btnEditDicDisabled"> 编辑 </button> <button type="button" class="btn btn-primary pull-left" id="btnDeleteDic" ng-disabled="btnDeleteDicDisabled"> 删除 </button> <button type="button" class="btn btn-primary pull-left" id="btnExport" ng-disabled="btnExportDisabled"> 导出 </button> <button type="button" class="btn btn-primary pull-left" id="btnImport"> 导入 </button> </div> <div class="col-xs-12 d-p-l"> <div class="col-sm-4 col-lg-3 dic_noderow" id="dicNodeRow"> <div id="dicTree"> </div> </div> <div class="col-sm-8 col-lg-9 d-p-l" id="divDetail"> <iframe id="showInfomation" ng-src="{{srcValue}}" name="iframeSelf"></iframe> </div> </div> </div> </form>
上面代码为bootstrap(css/html开源框架),其中dic-header、d-p-l、dic-noderow为自定义css其他为bootstrap原生要素及属性,如果想进一步学习bootstrap使用可以查看链接。上面代码除了使用布局及按钮之外,还使用了模态框,关于模态框菜鸟有介绍,移步链接。class内包含col-sm-12等col开头的样式,这些样式为bootstrap的网格系统,网格系统的具体介绍请查看上面bootstrap链接。
视图中涉及到的样式
bootstrap默认样式介绍:
- btn:原始按钮样式;
- btn-primary:基本按钮样式;
- pull-left:整体左滑动;类似于左对齐,比如页面中控件隐藏,则右侧控件像左侧滑动;
- col-xm-12在小型设备上的布局,视图总计为12,class中存在这个预定义类的要素相当于充满整个屏幕;
- col-lg-3为在大型设备上布局,结合上面的col-xm-使用就可以达到响应式(一个站点适应多个终端)的布局;
这些默认样式具体定义了什么可以查看bootstrap-theme.css(可以通过下载bootstrap框架得到这个文件)中源码进行查看。
自定义样式:
.r-p-l { padding-left: 10px; } div.col-xs-12.dic_header { padding-top: 10px; margin-left: 15px; padding-bottom: 50px; } div.dic_noderow, #dicTree { overflow-x: hidden; overflow-y: auto; max-width: 500px; }
自定义样式中我们定义了以下属性:
- padding-left、padding-top、padding-bottom:盒子模型的内边距;
- margin-left:盒子模型的外边距;
- overflow-x、overflow-y:内容溢出是否裁剪;
视图中涉及的标签
- button:按钮标签;
- form:负责数据采集的一组标签;
- div:将视图划分为不同区域;
还有其他bootstrap自定义样式,这里不再详细介绍,如果有时间开设专门的博客介绍bootstrap。
要素包含ng-字符的属性属于angular,这些属性大部分把ng-去掉即为要素原生属性,我们在视图中添加这些属性,就可以在控制器中直接对这些属性进行赋值,下一节我们详细介绍这些属性的使用。
编辑index.js
立即执行函数-如果已经掌握跳过
首先在inde.js文件中编写一个立即执行函数,立即执行函数就是在函数定义的地方直接执行这个函数。作用及好处可以归纳为隔离作用域,在私有作用域内变量及函数不会被污染,相当于起到命名空间的作用。
编写方式有以下几种(详细了解参考链接):
//user () operational character (function(){})(); (function(){}()); //user ! operational character !(function(){})(); //uer + operational character +(function(){})(); //uer + operational character -(function(){})(); //uer + operational character var temp=(function(){})();
在我们的js文件中我们使用了第一种方式编写以及执行函数。
angular模块module
在js文件的立即执行函数中,我们首先定义一个模块,在angularjs中模块是应用程序中不同部分的容器,在这些容器中我们可以添加controllers(控制器)、services(服务)、filters(过滤器)、directives(指令)等内容。
angular中模块通过angular.mudole(name,requires,configfn)方法创建:
- name:模块的名称,与视图中html标签的ng-app属性相关联;
- requires:模块的依赖关系,如果在调用方法时没有设置此参数,那么方法默认为获取这个模块;如果创建新模块并确定没有依赖关系,那么需要设置这个参数为[]数组;
- configfn:负责在模块初始化时做一些配置,参数是方法或者数组,这个参数是数组,那么最后一个参数必须是方法。
angular控制器controller
控制器是对视图的数据和时间处理函数进行挂载,同时进行一定的业务功能的底层封装和处理,起到的作用就是增强视图,在视图的作用于中增加额外的功能,我们用它来给作用于对象设置初始化状态,添加自定义行为。
创建完成angular模块后,调用模块方法controller创建控制器,controller的参数组成为:
- controllerName:控制器名称,控制器名称和ag-controller属性值相对应;
- controllerConstructor:第二个参数为一个回调函数,用于执行构造函数初始化内容;
angular作用域scope
在项目代码中,controller的参数是一个scope对象,scope是视图与控制器之间的纽带,scope是一个javascript对象,scope可以定义属性和方法,这些属性和方法可以在视图和控制器之间使用。
调用标签事件
通用事件
//angular angular.element("#id").click(); //jquery $("#id").click(); //原生 document.getElementById("#id").trigger("click");
标签特定事件
//angular angular.element("#id").on("eventName",function(event,data){}); //jquery $("#id").on("eventName",function(event,data){});
基于bootstrap的jQuery多级列表树插件
装载及初始化插件:
//初始化treeview angular.element("#dicTree").treeview({ data: getDictinary(), levels: 2, color: '#000000', backColor: '#FFFFFF', href:'#node-1' }); function getDictinary() { var tree = [ { text: "Parent 1", nodes: [{text: "Child 1",nodes: [{text: "Grandchild 1"}]}] } ]; return tree; }
以上为最简单结构,还有其他扩展属性可以查看链接,节点属性一节中有详细的介绍。
添加treeview控件的节点选择事件:
angular.element("#dicTree").on('nodeSelected', function (event, data) { alert(data.text); });
当节点被选择后可以触发事件调用该方法,在本项目中为了显示节点的详细信息。
持久化当前选择节点内容
所谓的持久化内容,也就是在当前视图的生命周期内保存视图中需要的内容。
在节点选择事件中对视图中form表单的隐藏标签赋值,首先添加隐藏标签到index.cshtml中:
<input type="hidden" id="currentNodeText" /> <input type="hidden" id="currentNodeId" /> <input type="hidden" id="currentHref" />
在节点改变事件绑定的方法中赋值:
angular.element("#dicTree").on('nodeSelected', function (event, data) {
angular.element("#currentNodeText").val(data.text);
angular.element("#currentNodeId").val(data.id);
});
存储当前选择的节点是为了在视图作用域内持久化节点内容。
组件化模块(angular component)
解耦复杂系统时将多个功能模块拆分、重组的过程就叫做模块化,其中有多个属性、状态描述其内部特性。
组件化与传统的编写方式相比较,有什么优势呢,或者叫解决了什么问题,根据园内CooMark的总结如下:
- app中的某个部分如何重用(在这里我们可以叫做,视图内的某个模块如何重用);
- app中的scope作用域不是隔离的,变量和方法存在污染的可能;
- 解决了传统模式不能重用的问题;
- 简化了视图中的标签,便于维护;
- 组件间是隔离的,互不影响;
- 可以独立测试组件;
使用component:
- 文件名建议包含.component.js
- component命名采用驼峰式命名法,在视图中使用时采用“-”进行分割;
- template使用controller的实例$ctrl替代scope访问数据,别名可以使用controllerAs进行自定义;
- component内的controller可以通过controller方法进行定义;
- templateUrl定义时需要注意,根目录为当前视图目录(如果是mvc项目,那么根目录就是母版目录_Layout.cshtml);
定义模块:
var controllerId = 'app.views.dictionarymanager.index'; var myModule = angular.module("app");
注册组件到模块:
- template:元素组件本身;
- templateUrl:通过文件方式定义元素;
- controller:定义组件controller,这样组件内就可以通过默认的$ctrl方式调用数据;
- bingdings:绑定数据到元素,其中<为单向绑定,父作用域的变化影响子作用域;
- scope:组件内单独的作用域;
- controllerAs:重命名控制器;
- restrict:字符型,E 表示该指令是一个element; A 表示该指令是attribute; C 表示该指令是class; M 表示该指令是注视;
myModule.component('myComp', { //template: '<div>My name is {{$ctrl.text}}</div>', templateUrl:'././App/Main/views/dictionarymanager/dicItem.html', controller: function () { this.name = 'shahar'; }, bindings: { text:'<' }
其中注册组件可以通过两种方式引入模板,第一种通过直接编写的方式,也就是代码中template的内容,也可以通过文件引入的方式,如图中templateUrl的方式。
视图中引入组件,视图中通过-分割驼峰式命名法的组件,并定义绑定的属性,绑定的属性可以通过controller的别名调用父scope内的数据进行绑定:
<my-comp text="vm.dicMainInfo"></my-comp>
父组件向组件传递数据:
myModule.controller(controllerId, ['$scope', function ($scope) { this.dicMainInfo = 'hello'; }]);
indes.js代码如下:
(function () { var controllerId = 'app.views.dictionarymanager.index'; angular.module("app").controller(controllerId, ['$scope', function ($scope) { $scope.AddDictinary = function () { alert("hello"); $scope.btnADCDis = false; }; function initialize() { $scope.srcValue = ""; $scope.btnAddDicCategoryDisabled = true; $scope.btnAddDicDisabled = true; $scope.btnEditDicDisabled = true; $scope.btnDeleteDicDisabled = true; $scope.btnExportDisabled = true; } initialize(); }]); })();
在上面的代码中实践了以上介绍的知识点,首先定义controllerId变量,窗体与视图中ag-controller属性相同的值,通过module定义模块,因为在mvc项目中app的div也就是区域已经在模板视图中定义,所以在定义模块时统一都是用app名称进行定义,再通过调用模块的controller方法创建控制器,控制器中传递scope参数,在回调函数中编写初始化代码及逻辑代码,作用域(scopde)中定义的属性及方法与视图中的属性及事件相关联,这样即实现了初始化属性又装载了事件方法。
错误处理