Notes of Drill Development

Table of Contents

1 文件夹结构

Drill项目本身包括了Java后端跟一般常用的web前端两部分.开发的源代码在 src中而和发布/运行的内容则在target中.开发时的源代码文件夹结构如下

/src
   /main
      /java
      /resources
         /config
         /i18n
         ...
      /webapp
   /test

测试时的对象文件夹结构如下

/target
   /classes
      /config
      /i18n
      ...
      /org
   /test-classes
   ...

请注意,发布的时候,会把开发时 resources 中的内容拷贝到 classes 下 面,而 classes 本身对应Java中 classpath.

2 背景知识   blog

2.1 JSP

JavaServer Page(JSP)是一种动态网页技术标准.在HTML中插入Java片段 (Scriptlet),以.jsp为文件后缀.在接到客户端请求的时候,服务器根据XXX.jsp文 件生成XXXServlet.java文件,然后编译运行,最后返回结果给客户端.

2.1.1 Servelet

Servelet是一种 独立于平台和协议服务器端 的Java应用程序,生 成动态页面.为HTTP客户端跟HTTP服务器程序的中间层.好处是快(比CGI快), 每个用户请求被激活成程序中的一个线程.Servlet是纯粹的Java,jsp介于 html和java质检,有助于美工人员来设计界面.

2.1.2 CGI 和 WSGI

既然上一节提到了CGI,这里稍微展开一下.Common Gateway Interface (CGI)是一种利用程序的标准输入,输出流来完成HTTP通信的方式.HTTP本身 是文本协议,请求是由服务器(Apache)处理,而应用程序可能由各种语言来实 现.CGI则将这个文本协议以标准输入流的方式传递给服务器端的CGI程序. 与之类似的还有WSGI,原理类似,但是是Python专用的协议.对于实现了 call 的对象,将 request 经过包装作为参数传入,同时也返回进过 包装的 response.

2.2 Tomcat + Apache

Apache和Tomcat都是提供服务器功能的软件.Apache可以使用 PHP,CGI,Perl,Tomcat在Apache后端提供对Java的支持.Apache+Tomcat处理 JSP的时候,Apache只是转发.Apache是Web服务器,Tomcat是应用服务器,是 Servlet容器,可以单独运行.

Tomcat是Servlet的容器,可以独立运行,也可以作为现有服务器的附加(支持 Apache,IIS,Netscape).配置Tomcat的时候选择运行模式.

3 Drill的Java后端   blog

Java后端负责业务逻辑,数据持久化(数据库),安全等等.目前接触到的内容有 账号注册登录管理,本地化设置等等.将整个后端当做一个应用,那么在主目录 下有一个 Application.java 类作为整个应用的入口.这个类最基本的应用需要 @ComponentScan@EnableAutoConfiguration 两个标签来声明自动配 置和扫描组件;同时在 main 函数里实例化一个 SpringApplication 对象 并调用对象函数 run() 就行了. 目前的程序里还用 app.setAdditionalProfiles 在没有命令行参数的时候默认加载dev配置.

3.1 环境与配置加载

3.1.1 配置文件

SpringApplication 会自动搜索以下路径来寻找 application.properties 作为配置文件.

  1. 当前路径下地 /config 子文件夹
  2. 当前路径
  3. classpath:/config
  4. classpath

同时,SpringApplication还支持 YAML 文件,即 applicaiton.yml. 函数 SpringApplication.setAdditionalProfiles("dev") 则会额外加载 application-dev.yml (或者 application-dev.properties), 而不是 dev.yml.

3.1.2 获取配置内容

当我们有类继承了EnvironmentAware这个接口的时候,框架会自动调用这个类 的 setEnvironment 方法,将一个 Environment 对象交给这个类的对象 从而使这个类获得配置文件中提供的参数. LocaleConfigurationMailConfiguration 两个类展示了相关的编程方法.

public class MailConfiguration implements EnvironmentAware {
    public void setEnvironment(Environment environment) {
        this.propertyResolver = new RelaxedPropertyResolver(environment, ENV_SPRING_MAIL);
    }
}

另一种方法是直接 @Inject 一个 Environment 的对象.

public class MailService {
    @Inject
    private Environment env;
}

<–这种方法跟之前所介绍的有什么区别,暂时不知道.–>

3.1.3 根据配置进行渲染

LocalResolverInterceptor. 在接受请求的时候, Interceptor 拦截请求中特定的参数值,并且调用 LocalResolver 的函数来进行相关渲 染处理.在 LocaleConfiguration 中,龙伟权通过定义Bean来指定特定的 LocalResolver, 即 AngularCookieLocaleResolver.

问题:

  1. LocaleConfigurationThymeleafConfiguration 都有方法返回 MessageSource, MailService 里面貌似引用的是 LocaleConfiguration 返回的对象,为什么?

3.2 持久化

对象的关联问题.Mongodb本身作为NoSql的典范,内部以文件和类似json的数 据形式来储存数据.Mongodb中一个Document可以看做关系数据库中的一行数 据,一个Repository可以看做关系数据库中的一个表(table).目前我们有三个 标记为 @Document 的类 PersistentToken,Task,User 和对应的 Repository 类.

3.3 业务层

3.3.1 Controller

控制器类都在org.team.drill.web下面,统一以 XXXResource 命名.

3.3.1.1 AccountResource
  • registerAccount 注册账户,接受新账户信息,验证账户,创建账户,发送激活邮件.
  • activateAccount 激活账户,通过激活key来确定当前User.
  • isAuthenticate 这个什么时候会用到?
    • HttpServletRequest request.getRemoteUser()
  • getAccount 这个貌似是浏览账户信息的时候用, 返回 UserDTO
  • saveAccount 这个是修改用户信息用, email 地址必须唯一
  • changePassword (newPassword) 这个是修改用户密码用
  • getCurrentSession 貌似是获得所有当前用户的登录记录(多地自动登录产生多个记录)
  • invalideSession 取消某个登录记录(已登录)
3.3.1.2 UserResource
  • getUser 通过String login获取user, 直接返回 User

登录功能是在 SecurityConfig 中间接实现的.

3.3.2 Service

Service 里面通过 SecurityContextHolder 来获取当前用户.

3.3.3 DTO

位于 web.dto 包下面的 UserDTO 类型,似乎是用来作为JS交互的数据 对象?但是内容上跟 domain 里的 User 差不多,用来保证安全.在更复 杂的应用环境中有用.

3.4 Security

不论是Service还是Resource,都是靠 SecurityUtils.getCurrentLogin() 来获取当前用户的.那么问题来了,SecurityUtils如何知道当前用户是谁?

public final class SecurityUtils {

    private SecurityUtils() {
    }

    /**
     * Get the login of the current user.
     */
    public static String getCurrentLogin() {
        SecurityContext securityContext = SecurityContextHolder.getContext();
        Authentication authentication = securityContext.getAuthentication();
        UserDetails springSecurityUser = null;
        String userName = null;

        if(authentication != null) {
            if (authentication.getPrincipal() instanceof UserDetails) {
                springSecurityUser = (UserDetails) authentication.getPrincipal();
                userName = springSecurityUser.getUsername();
            } else if (authentication.getPrincipal() instanceof String) {
                userName = (String) authentication.getPrincipal();
            }
        }

        return userName;
    }
}

3.4.1 SecurityContextHolder

包含安全上下文信息,并且默认为/ThreadLocal/,即对于一个线程采用一 个统一的安全上下文信息.同时,=SecurityContextHolder= 中还储存了当前 与App进行交互的主体(principal)的详情,用 Authentication 对象来表 示.上面的代码示例中体现的是获取当前用户信息的标准用法.

<–spring应用内部线程到底是如何管理 的?–>

3.4.2 UserDetails

在以上代码片段中我们可以看到一个特殊的类 UserDetails,在获取当前主 体的时候,返回的可能是一个 String 或者一个 UserDetails 的对象.那 么这个/UserDetails/到底是什么东西,为什么它的方法 getUsername() 刚 好能包含我们的 User 类所需的 login?

UserDetails 是一个接口,包含了 getPassword(), getUsername() 等 一系列方法. 在 UserDetails 中加入我们定义的 login,这是靠实现 UserDetailsService 接口来做到的. 这个接口只包含一个方法,这个方法 接受一个 String 参数,并返回一个实现了 UserDetails 接口的对象.目 前在org.team.drill.security包中的 UserDetailsService 类里面,龙伟 权实现了这个方法,并且将 org.team.drill.domain.Userlogin 包 装到实现了 UserDetails 接口的 org.springframework.security.core.userdetails.User 类中.

到这里,我试着把 SecurityContextHolderUserDetails 串起来. 个 人认为,在通过 SecurityContextHolder.getContext().getAuthentication().getPrincipal() 来获取 UserDetails 的时候,spring通过调用实现了 org.springframework.security.core.userdetails.UserDetailsService 接口的对象的方法来尝试获取 UserDetails, 这个方法本身接受一个 String参数. 又绕回来了,这个String参数是 从哪来的?依靠http协议?还是session? 因为某种原因, getPrincipal 方法返回的是一个 Object,那么实际上给出了一种可能性,就 是通过某个实现了 UserDetails 的类使用额外的业务逻辑.我们可以创建 一个实现了 UserDetals 的类 A,在 UserDetailsService 中返回 A 的对象,然后将 getPrincipal() 返回的对象强制转换为 A 并调用相关 方法.当然,强制转换类型总是有风险的.

3.4.3 GrantedAuthority

Authentication 提供两个重要的方法,第一个是上面说过的 getPrincipal,另一个则是 getAuthorities .它返回一个 GrantedAuthoriy 对象数组.顾名思义,这是用来管理用户权限的.在 UserDetailsService 里面进行加载.目前drill中加载的是 SimpleGrantedAuthority 数组,每个 SimpleGrantedAuthority 里面包 含一个字符串,代表某种权限(自定义于 org.team.drill.security.Authority 中).

3.4.4 其它

random.nextBytes 来产生 PersistentToken 的序列号,不怕重复? 虽然16位Byte确实已经很大(10亿的4次方).

4 Angular

4.1 AngularJS Tutorial 入门教程学习笔记   blog

4.1.1 01和02,静态模板和AngularJS模板

在HTML里面用np-app来定义Angular的上下文,可以用 ng-app="AAA" 来制定一个名字空间.JS中 通过这个名字来获取HTML里的上下文 var AAA = angular.module('AAA', []);.

在HTML中Angular的范围内,用 ng-controller="BBB"标记一个div,在里 面用一些Angular命令(ng-repeat)和表达式设定显示方式.在JS里面用 AAA.controller('BBB',function ...) 给Angular表达式中的域赋值.

AAA 中的 $scope$ 是AAA的根作用域,控制器的作用域则在 BBB 之中.也 就是说,如果在AAA下面有多个 controller, controller X 的数据绑定 在 controller Y 下是无效的. AngularJS作用域文档.

略:AngularJS的开发者倾向于使用 Jasmine 行为驱动开发(BBD)框架进行 测试.

写在后面,对angular.module是所有JS中创建,注册和获取模组的地方.HTML里 面通过ng-app="AA"来绑定模组AA的上下文

4.1.2 03 迭代器

建立输入表单 <input ng-model="query"> ,修改 ng-repeat<li ng-repeat="x in list|filter:query">.只要在输入框里输入文本,就会自动 地修改列表,只显示包含这个文本的项目.这代表两点:1. 数据绑定同名变量 query,值的同步是实时的; 2.filter过滤器,生成新数组,自动更新视图.

略:Angular端到端测试.

4.1.3 04双向绑定

在HTML里面添加选择器

<select ng-model="orderProp">
  <option value="name">Alphabetical</option>
  <option value="age">Newest</option>
</select>

同时在迭代器中加入条件 <li ng-repeat="phone in phones | filter:query | orderBy:orderProp">

JS中给每个数据加上 age 这个属性,同时设定 $scope.orderProp = 'age'设定选择器 的默认值.

select 的元素和 orderProp 之间建立了双向绑定.

4.1.4 05XHR和依赖注入

通过 $http.get(AA/BB.json) 来读取json文件. $http 是AngularJS提 供的内建服务,通过依赖注入DI子系统.

phonecatApp.controller('PhoneListCtrl', ['$scope', '$http', function($scope, $http) {
    $http.get('phones/phones.json').success(function(data) {
        $scope.phones = data;
    });
}]);

4.1.5 06链接与图片模板

<ul class="phones">
  <li ng-repeat="phone in phones | filter:query | orderBy:orderProp" class="thumbnail">
    <a href="#/phones/{{phone.id}}" class="thumb"><img ng-src="{{phone.imageUrl}}"></a>
    <a href="#/phones/{{phone.id}}">{{phone.name}}</a>
    <p>{{phone.snippet}}</p>
  </li>
</ul>

4.1.6 07路由与多视图 & 08更多模板

$routeProvider 重定向url,把某个url路径定向到某个HTML模板上去, 并指定相对应的controller. index.html 中定义一个域,加载所有JS文件.加 载的 app.js 设定重定向规则.

phonecatApp.config(['$routeProvider',
  function($routeProvider) {
    $routeProvider.
      when('/phones', {
        templateUrl: 'partials/phone-list.html',
        controller: 'PhoneListCtrl'
      }).
      when('/phones/:phoneId', {
        templateUrl: 'partials/phone-detail.html',
        controller: 'PhoneDetailCtrl'
      }).
      otherwise({
        redirectTo: '/phones'
      });
  }]);

:phoneId 是从将URL路径的对应部分作为变量.这个变量通过 $routeParams 来访问.

最后,在index.html里面加上 <div ng-view></div> 来为当前路由把对应 视图加上去.

08更多模板 之中用 $http 加载不同的json文件来根据phoneId生成每 部手机的细节说明网页.

4.1.7 09过滤器

建立自己的 filter FF,实现更复杂的规则,然后像之前使用 | filter:query 一样用类似pipe的语法把输出传给自己的filter | FF.filter并不限定在 ng-repeat 中使用.

angular.module('phonecatFilters', []).filter('checkmark', function() {return function(input) {
    return input ? '\u2713' : '\u2718';
  };
});

AngularJS内置过滤器 uppercase, json, date, date:"MM/dd/yyy@ h:mma".

4.1.8 10事件

通过JS在上下文中定义函数,通过 ng-click 来设定点击事件,调用函数.

phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams', '$http',
  function($scope, $routeParams, $http) {
    $http.get('phones/' + $routeParams.phoneId + '.json').success(function(data) {
      $scope.phone = data;
      $scope.mainImageUrl = data.images[0];
    });

    $scope.setImage = function(imageUrl) {
      $scope.mainImageUrl = imageUrl;
    }
  }]);

$scope.setImage 就是绑定的函数.

<img ng-src="{{mainImageUrl}}" class="phone">

...

<ul class="phone-thumbs">
  <li ng-repeat="img in phone.images">
    <img ng-src="{{img}}" ng-click="setImage(img)">
  </li>
</ul>

点击图片的时候调用函数,把 mainImageUrl 的值设定为当前图片.

4.1.9 11Rest和定制服务

依赖为 angular-resource/angular-resource.js.一般我们的JS相应功能 命名为 XXXService.首先注册 Service 模块.

var phonecatServices = angular.module('phonecatServices', ['ngResource']);

phonecatServices.factory('Phone', ['$resource',
  function($resource){
    return $resource('phones/:phoneId.json', {}, {
      query: {method:'GET', params:{phoneId:'phones'}, isArray:true}
    });
  }]);

建立一个 Phone 工厂,用 $resource 来取得数据.相应的controller改 为$scope.phone = Phone.get({phoneId:$routeParams.phoneId})$scope.phones = Phone.query().

query 方法是自己声明和定义的 query: {method:'GET', params:{phoneId:'phones'}, isArray:true}.

4.1.10 12 Animations

TODO

更多的教程 开发指南Cookbook. 推荐使用AngularJS种子项目来引导开发新项目

4.2 AngularJS Developer Guide 开发指南学习笔记   blog

4.2.1 Introduction & Conceptual Overview

HTML本身是静态的,对于现在的动态网页无能为力.为了弥合静态和动态之间 的鸿沟,有两种静态方式:库(jQuery),框架(durandal, ember). AngularJS 则提供了第三种方式,通过数据绑定等方式让浏览 器学会新的语法. AngularJS 是完全基于前端的,适用于CRUD应用.

4.2.2 DI

AngularJS中的模组类似于名字空间,在几乎所有东西都在名字空间下注册.这 些注册的东西是通过名字推断来进行绑定(引用)的.AngularJS自带的那功能,都 是可以直接引用的.自己定义的东西,也可以直接靠名字推定,但是要给模组 加上依赖关系.

service, directive, filter 都是通过 factory/directive/filter 方 法注册在模组中的,这些方法统称工厂(factory)方法.

我们可以通过 config/run 方法注册在模组配置和运行的时候需要运行的 函数,这些函数也同样的可以被以类似上面的方式来调用.这些方法被归类为 模组(module)方法.

控制器(controller),被解释为classes或者constructor function,同样需 要注册到模组中,通过名字来引用.

这些东西在引用的时候,可以直接当函数参数名,可以用 $inject 来显示 说明,也可以用数组.后两种基本上是在第一种的基础上多写些代码来说明.直 接用函数参数名是最简单的,但是不被官方所推荐.

4.2.3 Data Binding & Scopes & Template

传统模板是通过模板Tempalte和模型Model来建立视图View. AngularJS是通过模 板建立起View和Model的对应,让View跟Model直接互动. AngularJS中,template是包含AngularJS元素和属性的HTML.template中可用 的AngularJS元素和属性为 Directive, Markup ({{}}), Filter, Form controls.通过这些template,JS(model)和(view)被连接了起来(数 据绑定).

上下文(Scope)提供表达式的运行环境,它被组织成一个类似DOM的层级结构.上 下文监视表达式并传递事件.API($watch)监视模型的变更,($apply)将任意 由Angular之外的事件对模型的改变传递到View.这里所谓的Angular之外的 事件,指的是controllers,services,event handlers.

上下文之间可以嵌套,嵌套的上下文有两种 child scopeisolate scope, child scope 继承上层上下文的内容, isolate scope 不继承. 如果上下文中没有对应的属性,表达式是没有任何作用的.

上下文可以看做将应用控制器controller和视图view联系起来的胶水. 在template连接的时候,directive会设置上下文中的$watch,$watch会通知 directive任何属性的变更,从而使directive能根据更新的数据来渲染DOM 控制器controller和directive都能直接访问scope,但是不能直接访问对方.这 种设计孤立controller和directive. $rootScope 提供对root上下文的访 问,一般指向ng-app所在的范围.

如果用chrome的inspect element功能查看页面元素,会发现 class="ng-scope ng-binding"之类的属 性.Anuglar会自动将 ng-scope 加入制定了上下文的元素, ng-binding 则说明这里面的内容是用绑定实现的.

在不同层级使用同一个controller,

<div ng-controller="EventController">
  Root scope <tt>MyEvent</tt> count: {{count}}
  <ul>
    <li ng-repeat="i in [1]" ng-controller="EventController">
      <button ng-click="$emit('MyEvent')">$emit('MyEvent')</button>
      <button ng-click="$broadcast('MyEvent')">$broadcast('MyEvent')</button>
      <br>
      Middle scope <tt>MyEvent</tt> count: {{count}}
      <ul>
        <li ng-repeat="item in [1, 2]" ng-controller="EventController">
          Leaf scope <tt>MyEvent</tt> count: {{count}}
        </li>
      </ul>
    </li>
  </ul>
</div>
angular.module('eventExample', [])
.controller('EventController', ['$scope', function($scope) {
  $scope.count = 0;
  $scope.$on('MyEvent', function() {
    $scope.count++;
  });
}]);

这个例子中,首先我们看JS的部分.这部分注册了一个EventController,这个 controller对于给定的上下文 $scope 设定属性 count,同时注册一个 名为 MyEvent 的事件,调用一个闭包函数对于当前上下文进行操作.

接下来看HTML,里面把同一个controller用了三次,也就意味着对于 eventExample,给予三个不同的 $scope.而这三个 $scope 各自拥有自 己的 MyEvent 函数副本.最后, $emit('MyEvent') 将调用这个函数的 动作传递给上层 $scope, $broadcast('MyEvent') 则传递给下层 $scope.

$scope 的生命周期中,模型的改变不会立即触发 $watch, 而是通过在 $apply 结束时的 $digest 来触发 $watch,这么做的好处在于将多个 改变合为一个 $watch 动作. 当子上下文不需要继续存在时,子上下文的 创建者负责调用 scope.$destroy() 负责停止事件传递,允许垃圾回收来 释放内存.

4.2.4 Module & Provider

angular.module会覆盖已存在的名字.模组提供 provider, factory, service, value, constant, animation, filter, controller, directive, config, run 这些方法.除了最后两个以 外,都是注册某一种功能. provider,factory,service,value,constant 都是JS内部用的, directive,filter,animation,controller 都是在html里面用 的(JS里面定义的).

  • JS
    • provider 提供一个在config阶段配置内容的机会,通过 $get 函数返 回绑定的对象.
    • factory 注册一个名字空间,跟value类似,但是可以引用其它的服务
    • service 注册一个类,用 new 来获得对象
    • value 注册一个变量,也可以在变量下加入函数,但是不能引用其它的服务.
  • html
    • directive, 定义一个html元素,属性,类名,对相应的元素加入定义好的模块
    • controller, 注册一个可在html里面引用的属性值,对相应元素中的变 量进行绑定和操作.
    • animation, 动作效果
  • html and JS
    • filter, 过滤器. 返回一个过滤函数并绑定在这个名字上. 过滤函数接 受整个数组为第一个参数,还可以自定附加参数.

Provider是AngularJS的基础,Value,Factory,Service,Constant都建立在Provider之上.下 面的 myApp.XXX 方法实际上都是调用 $provider.XXX.

  • Value recipe
    var myApp = angular.module('myApp', []);
    myApp.value('clientId', 'a12345654321x');
    myApp.controller('DemoController', ['clientId', function DemoController(clientId) {
      this.clientId = clientId;
    }]);
    
    <html ng-app="myApp">
      <body ng-controller="DemoController as demo">
        Client ID: {{demo.clientId}}
      </body>
    </html>
    

    其实就是定义一个模组下的变量/对象,可以是简单值,也可以在这个变量 下加入函数.

  • Factory recipe 相对Value,可使用其他服务,初始化服务,惰性初始化. 使用 myApp.factory 方法.返回的是一系列变量,函数.
  • Service recipe 使用 myApp.service() 方法,注册一个构造函数,得到一个类(class), 用 new 来得到新实例.返回的是构造函数.
  • Provider Recipe 比Factory更高一层,能够让用户在使用服务之前进行定制化,用config函 数来实现.Provider里面有个 $get 方法,DI会把这个方法返回的东西绑 定到注册的Provider名字上.
    myApp.provider('AAA', function AAAProvider(){
        this.configFunc = function(bla){blabla};
        this.$get = function(lab){ return BBB};
    });
    myApp.config(function(AAAProvider){
        AAAProvider.configFunc(...);
    });
    myApp.controller('CCC', function(AAA){ /*AAA is BBB*/});
    

    provider 只能在config里面,而在run里面,只能用非 provider.

  • Constant Recipe Constant在config和run的过程中都存在(在他们之前).用 myApp.constant 来注册.
  • Special Purpose Objects 包括controller, directive, filter, animation.
4.2.4.1 Service

服务都是惰性加载和单例,通过 factory 注册.服务本身对于html是不可 见的.在JS里面引用其功能. 除了用工厂方法意外,还可以在模组的config方法中通过 $provie.factory 来 注册服务.这里的Service和前面Provider段落中的不是一个概念.

4.2.4.2 Controller

控制器是用于连接上下文和服务的,通过 controller 注册,对于html可见,通 过 ng-controller 来进行绑定.本身也可以定义一些行为.不需要返回什 么东西.

4.2.5 Directive

Directive提供自己定义的HTML标记,基于AngularJS HTML compiler ($compile). ngApp, ngRepeat 等等就是AngularJS自带的 directive,这些directive通过 ng-app, ng-repeat 标记在HTML文件中 改变元素的行为模式.当然,这里的compile并不是真正意义上的编 译,AngularJS只是将事件监听器绑定到HTML元素上,使得这些元素可以互动. 在 $compile 的API中可以找到directive的各种option的详细说明.

命名方面, -,:,_ 都可以作为分隔符, ng-modelng:model 是等 效的.同时还可以在前面加上 x- 或者 data- 前缀, ng-modeldata-ng:model 是等效的.

使用directive可以将它作为tag name, attribute name, comment 或者 class name.

<my-dir></my-dir>
<span my-dir="exp"></span>
<!-- directive:my-dir exp-->
<span class="my-dir: exp"></span>

以上三种标记方式是等效的.当然,推荐使用tag name和attribute name.

ngAttr 能够绕过浏览器限制使用AngularJS的表达式设定attribute.

<circle cx="{{cx}}"></circle>
<circle ng-attr-cx="{{cx}}"></circle>
<svg viewBox="{{viewBox}}"></svg>
<svg ng-attr-view-box="{{viewBox}}"></svg>

Directive返回固定的option.如果说Service(Factory)是注册名字和对应的属性,函数;Directive则是将值和函数绑定到预先定义好的选项

4.2.5.1 注册Directive

module.directive 函数注册directive,它接受一个名字和一个工厂方法.方 法返回template,用其填充directive元素的内容.

4.2.5.2 restrict option

值为 'A' (match attribute), 'E' (match element), 'C' (match class).指定这个directive被用作元素的属性,元素名或类

4.2.5.3 link option

操作DOM.这个option需要指定一个函数,函数的参数为 scope (上下文),element (当前元素),attrs (当前元素的属性), ctrl (array of controllers required by the directive).

angular.module('docsTimeDirective', [])
.controller('Controller', ['$scope', function($scope) {
  $scope.format = 'M/d/yy h:mm:ss a';
}])
.directive('myCurrentTime', ['$interval', 'dateFilter', function($interval, dateFilter) {

  function link(scope, element, attrs, ctrl) {
    var format,
        timeoutId;

    function updateTime() {
      element.text(dateFilter(new Date(), format));
    }

    scope.$watch(attrs.myCurrentTime, function(value) {
      format = value;
      updateTime();
    });

    element.on('$destroy', function() {
      $interval.cancel(timeoutId);
    });

    // start the UI update process; save the timeoutId for canceling
    timeoutId = $interval(function() {
      updateTime(); // update DOM
    }, 1000);
  }

  return {
    link: link
  };
}]);

设定函数 updateTime 直接操纵元素内容,通过 $watch 将元素中的属 性变动跟操作函数绑定在一起. 通过 $interval 来实现自动读秒更新时 间. 绑定 $destroy 来终止 $interval 读秒.

4.2.5.4 isolate scope
angular.module('docsIsolateScopeDirective', [])
.controller('Controller', ['$scope', function($scope) {
  $scope.naomi = { name: 'Naomi', address: '1600 Amphitheatre' };
  $scope.igor = { name: 'Igor', address: '123 Somewhere' };
}])
.directive('myCustomer', function() {
  return {
    restrict: 'E',
    scope: {
      customerInfo: '=info'
    },
    templateUrl: 'my-customer-iso.html'
  };
});
<div ng-controller="Controller">
  <my-customer info="naomi"></my-customer>
  <hr>
  <my-customer info="igor"></my-customer>
</div>

controller里面绑定了 naomiingor 的值,html里面将directive 元素中的 info 绑定为 naomiingor. directive里面定义独立 上下文,将 customerInfo 绑定到元素里的 info. my-customer-iso.html 里面引用 customerInfo 的内容. 如果要 引用 <div bind-to-this="thing">, 在directive里面应该用 =bindToThis.如果isolate scope里的 属性名跟directive标签中的一样,则可以缩写为 XXX: '='. 注意:如同之前提到过的,isolate scope没有继承高级上下文的内容,在这 里面调用高级上下文中的变量,只能是空 template里面或者templateUrl引用的html里面的内容继承的是directive 的上下文

4.2.5.5 transclude

给予外部上下文访问能力.同时也意味着之前isolate scope 被遮盖了,无法访问.

angular.module('docsIsoFnBindExample', [])
.controller('Controller', ['$scope', '$timeout', function($scope, $timeout) {
  $scope.name = 'Tobias';
  $scope.hideDialog = function () {
    $scope.dialogIsHidden = true;
    $timeout(function () {
      $scope.dialogIsHidden = false;
    }, 2000);
  };
}])
.directive('myDialog', function() {
  return {
    restrict: 'E',
    transclude: true,
    scope: {
      'close': '&onClose'
    },
    templateUrl: '<div class="alert"><a href class="close" ng-click="close()">&times;</a><div ng-transclude></div></div>'
  };
});
<div ng-controller="Controller">
  <my-dialog ng-hide="dialogIsHidden" on-close="hideDialog()">
    Check out the contents, {{name}}!
  </my-dialog>
</div>

点击动作为ng-click="close()",而 close 动作被绑定到 &onClose 这个directive. on-close 又在 html里面绑定到 hideDialog 函数上. 这个示例中显示了如何将函数表 达式传递给directive,让directive在一定时候选择执行这个函数.scope中 读取值用'@XX',读取表达式结果用'=XX',读取表达式但不执行用'&XX'.

4.2.5.6 scope

不管是template还是templateUrl都是在directive的上下文下.当没有 isolate scope的时候,直接访问外部上下文.有isolate scope的时候,只能 访问isolate scope.这个时候html里面元素的内容,比如 <AA>blabla</AA> 里面的 blabla 是被忽略/覆盖的. 当在template中加入一个含有transclude的元素的时候,html里面的 blabla 会被显示在这个元素之中,而这个含有transclude的元素的内容 却不会被显示,同时html里面的表达式无法访问isolate scope.

...
return {
    template: <div>CONTENT IN JAVASCRIPT (DIRECTIVE SCOPE (OUTER/ISOLATE SCOPE))</div><div ng-transclude>CONTENT FROM HTML (OUTER SCOPE)</div>
}
4.2.5.7 controller in directive.

4.2.6 Filters

在html里面用的时候 {{expression | filter1:argument1:argument2... | filter2 ...}} 在JS里面使用一个叫做 filter 的过滤器

angular.module('FilterInControllerModule', []).
controller('FilterController', ['filterFilter', function(filterFilter) {
  this.array = [
    {name: 'Tobias'},
    {name: 'Jeff'},
    {name: 'Brian'},
    {name: 'Igor'},
    {name: 'James'},
    {name: 'Brad'}
  ];
  this.filteredArray = filterFilter(this.array, 'a');
}]);

创建过滤器

angular.module('myReverseFilterApp', [])
.filter('reverse', function() {
  return function(input, uppercase) {
    input = input || '';
    var out = "";
    for (var i = 0; i < input.length; i++) {
      out = input.charAt(i) + out;
    }
    // conditional based on optional argument
    if (uppercase) {
      out = out.toUpperCase();
    }
    return out;
  };
})

$filter 服务通过名字调用相应的过滤器.

4.2.7 Forms

4.2.8 Expressions

4.2.9 HTML Compiler

4.2.10 Security

4.2.11 i18n and l10n

4.2.12 Accessibility

4.2.13 Bootstrap

4.2.14 Running in Production

4.2.15 Animations

4.3 Bootstrap CSS

4.3.1 Grid System

定义row class="row", 定义column class="col-". 元素必须在column里面.可 以同时定义几种 col-xl-NN, col-sm-NN, col-md-NN, col-lg-NN 使得页面 在不同大小的时候自动采用不同的比例. column如果嵌套的话,父子column的 宽度必须相等.

col-XX-push, col-XX-pull 是什么意思?

Less mixins,修改CSS.在CSS里面定义新的class,里面用类似function的形式 调用bootstrap提供的功能,生成新样式(组合).

4.3.2 Typography

类似html,用<h1></h1>到<h6></h6>来生成不同大小的标题. 用 <small></small> 生成比当前上下文小一号的文字. 在段落 <p></p>里面加 上 class="lead" 建立一个大字段落.<mark>高亮,<del>和<s>横线穿过文 字,<u>和<ins>下划线,<strong>粗体,<em>斜体.

对齐: text-left, text-center, text-right, 顾名思义. text-justify, text-nowwrap??

大小写转换 text-lowercase, text-uppercase, text-capitalize

缩写 <abbr title="attribute">attr</abbr>.鼠标放到attr上时呈现问号,停留 则会显示一个小标签,里面的内容就是title的值. 加入 class="initalism" 使得attr文本使用稍微小一点的字体.

地址 <address></address>, href="mailto:EMAIL@ADRESS" 添加可点击email地址,点击自动触 发发送邮件的事件,在我的电脑上会打开Mail客户端.

块 <blockquote></blockquote>, <footer></footer> <cite title="Source Title">Source Title</cite>这里的title也是停留显示标签. 在blockquote里面加入 class="blockquote-reverse" 可以将quote对齐右 边.

列表 在列表开头加入 class="list-unstyled" 可以消除列表的缩进和开头的点. class="list-inline" 可以把列表的内容放在一行里面. 描述列表 <dl> <dt>粗体的项目</dt> <dd>正常字体的项目描述</dd> </dl 在描述列表中加入 class="dl-horizontal" 可以将项目和描述放在一行,并 且项目右对齐,描述左对齐,就像我CV里面的效果.太长的项目会自动缩减超 出的部分,并显示省略号.

4.3.3 Code

文字中的代码块 <code></code> (浅红底,红字). <kbd></kbd>表示键盘输 入 (黑底白字). 例子:<kbd><kbd>ctrl</kbd> + <kbd>,</kbd></kbd>. <pre></pre>多行代码块, 可以加上 pre-scrollable 来固定高度,并提供滑 动条.<var></var>变量.<samp></samp>输出样例 (等宽字符).

4.3.4 Table

class="table",基本表格样式. class="table-strped", 交错使用不同颜色 作为每行的底色. class="table-bordered", 加上所有的行列边框. class="table-hover", 鼠标悬停行用不同低色. class="condensed", 单元 之间的空间减半,使表格更加紧凑. 在行<tr>或者单元<td>里面加入 class="XX", 对 这一行或者单元用不同底色, 可选值为:active (灰), success (绿), warning (黄), danger (红), info (蓝). table-responseive 横向滚动条,适用于小设备.

4.3.5 Form

所有文本的input,textarea,select 都可以加入 form-control 来设定宽度. form-group可以将form的标题和form本身看做一组并安排空间. 可以用一个 有 help-block 类别的文本元素显示帮助文本. 表单也可以被放在一行, 使 用 form-inline. bootstrap的建议是永远都要加入说明性的label,即时在 一行的时候一部分label可能不显示.同时像checkbox这样的表单,被放在 label的里面. 含有input-groupe-addon的div元素被放置在表单周围,用来 显示紧挨着的一个标签块. form-horizontal 类似列表中的dl-horizontal, 把说明标签和表单分两列,左右对齐.

input中type可以选择很多类型,datetime, datetime-local, date, month, time, email 等等.会给出不同的样式.值得一提的是,如果type是file,会显 示一个按钮,点击后出现文件选择器.input中加入disabled属性能使其不可 填写, readonly也可以做到同样效果.

textarea用row="XX"来控制高度.

class="checkbox disabled" 让checkbox不可选,一般是用一个div把整个包 含了checkbox的label包起来, 同时向这个div中加入此class. checkbox-inline, 同一行.

select中加入 multiple 属性(注意,不是classs),会把所有选项都显示出来,还 带滚动条.

在form-group中,如果需要用text代替真的form,用 class="form-control-static"

使用<fieldset disabled>可以把之下所有的form-group变成不可操作的. 在form-group旁边加入has-[success]|[error]|[warning],在label里面加 上class="control-label"可以使用相应的颜色,颜色跟之前表格里的式样统 一.这就是所谓的semantic style.

form-control旁边加入input-lg|sm可以控制表单的大小. form-group旁边 加入form-group-lg|sm控制group的大小.

aria-describedby="ID", 另一个元素中id="ID" ???sr-only 和 aria-describedby, aria-hidden, has-feedback 有什么用??? ???name 和 id 有什么区别???

4.4 Drill的前端(front-end)

4.4.1 index.html中直接使用的AngularJS

  • ng-app, ng-click, ng-controller, ng-repeat, ng-view
  • ng-show 根据表达式显示或者隐藏元素.通过将特定的css类 .ng-hide 加入到或 移除出元素来实现隐藏.
    <div ng-show="myValue"></div>
    

    myValue 的值是true的时候显示,false隐藏. 注意跟 ng-switch 的区别

  • ng-switch/ng-switch-when 根据表达式替换DOM结构.选择包含的某个元素,使其可见.
    1. 定义一个容器元素,加入 ng-swith="XXX"
    2. 在容器元素里面的元素中加入 ng-swith-when="someVal" 根据XXX的值是否等于someVal来决定是否显示这个元素.

4.4.2 JS

4.4.2.1 定义的模块
  • app.js: angular.module('app', ['http-auth-interceptor', 'tmh.dynamicLocale','ngResource', 'ngRoute', 'ngCookies', 'appUtils', 'pascalprecht.translate', 'truncate', 'ngCacheBuster']);
  • http-auth-interceptor.js: angular.module('http-auth-interceptor', ['http-auth-interceptor-buffer'])
  • http-auth-interceptor.js: angular.module('http-auth-interceptor-buffer', [])
  • truncate.js: angular.module('truncate', [])
  • utils.js: angular.module('appUtils', [])
  1. app模块

    设定网站url重定向,初始化多语言, ?tmhDynamicLocaleProvider. 在注入injector创建后,初始化.

5 Mongodb & Spring

看代码

Query query1 = Query.query(Criteria.where("_id").is(instance));
//建立一个靠_id找LetterInstance的query
query1.fields().include("sections._id");
// 在query中特别说明,找到的LetterInstance只需要返回里面的sections._id就行了,其它的域不需要返回.
LetterInstance letter1 = mongoTemplate.findOne(query1, LetterInstance.class);
// 从数据库中取出LetterInstance
LetterSection emptySection = letter1.findSectionById(sectionId);
// 从letter中得到section, 但是这个section是空的,里面只有id
int index = letter1.getSections().indexOf(emptySection);

Query query2 = Query.query(Criteria.where("_id").is(instance));
query2.fields().include("sections").slice("sections", index, 1);
// 在query中说明,找到的letter中要包含sections,但是只返回其中的第 index 个.
LetterInstance letter2 = mongoTemplate.findOne(query2, LetterInstance.class);
LetterSection section = letter2.getSections().get(0);

//This is an alternative solution loading all sections, but omitting the other (large) fields.
Query query = Query.query(Criteria.where("_id").is(instance));
query.fields().include("sections");
LetterInstance letter = mongoTemplate.findOne(query, LetterInstance.class);
LetterSection section = letter.findSectionById(sectionId);

// This is the code I use for storing only a single collection element:
MongoConverter converter = mongoTemplate.getConverter();
DBObject newSectionRec = (DBObject)converter.convertToMongoType(newSection);

Query query = Query.query(Criteria.where("_id").is(instance).and("sections._id").is(new ObjectId(newSection.getSectionId())));
Update update = new Update().set("sections.$", newSectionRec);
mongoTemplate.updateFirst(query, update, LetterInstance.class);

Author: Xiao LIU

Created: 2015-02-28 Sat 22:37

Emacs 24.4.1 (Org mode 8.2.10)