龍昌博客

从Pythoneer转向Rubist

AngularJS学习笔记7——与rails整合

| Comments

要在 rails 中使用 angular 直接在页面中引入进行即可,这个倒是不难。 只是在开发过程中突然发现了一个问题,就是 angular 的模板应该如何组织呢。 如果全写成内联模板这个不太好维护,如果是写成单个文件放在 public 目录下也不太妥。 不过好在这个问题已经有人解决了,有 angular-rails-templates 这么一个库:https://github.com/pitr/angular-rails-templates

首先安装该库:gem 'angular-rails-templates'

然后创建目录 app/assets/javascripts/templates, 并在 app/assets/javascripts/application.js 中加载对应的文件:

1
2
//= require angular-rails-templates
//= require_tree ./templates

该目录下的模板文件命名与 rails 的视图命名类似,如: foo.htmlfoo.html.erbfoo.html.haml,foo.html.slim

可以参考我的一个例子: https://github.com/wusuopu/rails-billing

这里我是使用 bower 进行安装 angular 的库,首先安装 gem 'bower-rails'

然后初始化 bower_rails: rails g bower_rails:initialize
编辑 Bowerfile,添加所需要的依赖包:

1
2
3
4
5
6
7
8
9
10
11
asset 'angular'
asset 'angular-route'
asset 'angular-resource'
asset 'angular-mocks'
asset 'angular-flash'
asset 'angular-loading-bar'
asset 'angular-flash-messages'
asset 'angular-translate'
asset 'angular-bootstrap'
asset 'bootstrap-sass-official'
asset 'components-font-awesome'

再执行命令: rake bower:install 进行安装。

接着编辑 config/initializers/assets.rb 添加配置: Rails.application.config.assets.paths << Rails.root.join("vendor","assets","bower_components")

最后加载依赖文件 app/assets/javascripts/application.js

1
2
3
4
5
6
7
8
9
//= require angular/angular
//= require angular-route/angular-route
//= require angular-resource/angular-resource
//= require angular-flash-messages/angular-flash
//= require angular-loading-bar/build/loading-bar
//= require angular-translate/angular-translate
//= require angular-bootstrap/ui-bootstrap-tpls
//= require angular-rails-templates
//= require_tree ./templates

AngularJS学习笔记6——与jQuery、Bootstrap结合

| Comments

jQuery

如果想要在 angular 内部调用 jQuery 的函数(如 jQuery 的 ajax 功能)比较简单,直接调用 $.ajax 即可。
但是如果想要在 angular 外部调用其函数就稍微麻烦一点,毕竟这也与 angular 的设计理念不符。

1
2
3
4
5
6
7
8
9
<div ng-controller="PageController">
  ...
</div>

<script>
  var appModule = angular.module('myApp', []);
  appModule.controller('PageController', ['$scope', '$http', function($scope, $http){
      ...
  }]);

例如上个这段代码,如果想要从外部访问 PageController 中的某些内容。则可以先获取 PageController 的上下文对象($scope):angular.element($('[ng-controller="PageController"]')).scope();
在外部修改了 scope 的某些值时会发现对应的视图并没有更新,这时还需要调用 scope$digest 方法进行同步,或者直接调用 $apply 方法进行操作。

Bootstrap

之前使用 angularjs 时遇到了 Bootstrap 的控件不能正常使用了,如 dropdown 组件点击了没有效果。
经过分析发现是 angularjs 将 Bootstrap 的组件的事件给截获了。

好在有 angular-bootstrap 这么一个组件可以将它们整合在一起。

http://angular-ui.github.io/bootstrap/

在Ruby中使用DATA和__END__将代码和数据混合

| Comments

之前一直不理解 __END__ 的用法,现在看了这篇文章后才算是了解了,于是便翻译之。
《Mixing code and data in Ruby with DATA and __END__》: http://blog.honeybadger.io/data-and-end-in-ruby/


你知道 Ruby 提供了一种方法在你的脚本中可以将源文件作为数据源来使用吗?当你在写一些一次性的脚本用于验证概念时这个小技巧会为你节约一些时间。让我们来看看吧。

DATA 和 __END__

在下面这个例子中,我使用了一个有趣的关键字 __END__。所有在 __END__ 下面的内容将会被 Ruby 解释器所忽略。但是有趣的是 ruby 为你提供了一个称为 DATA 的 IO 对象,就像你可以读取其他任何文件一样,它能让你读取到 __END__ 以下的所有内容。

下面这个例子中,我们遍历每一行并进行输出。

1
2
3
4
5
6
7
8
DATA.each_line do |line|
  puts line
end

__END__
Doom
Quake
Diablo

关于这个技术我最喜欢的实例是使用 DATA 来包含一个 ERB 模板。它同样也可用于 YAML、CSV等等。

1
2
3
4
5
6
7
8
require 'erb'

time = Time.now
renderer = ERB.new(DATA.read)
puts renderer.result()

__END__
The current time is <%= time %>.

实际上你也可以使用 DATA 来读取 __END__ 关键字以上的内容。那是因为 DATA 实际上是一个指向了整个源文件,并定位到 __END__ 关键字的位置。你可以试试看在输出之前将 IO 对象反转。下面这个例子将会输出整个源文件。

1
2
3
4
5
DATA.rewind
puts DATA.read # prints the entire source file

__END__
meh

多文件问题

这个技术最大的缺点是它只能用于单个文件的脚本,直接运行该文件,不能在其他文件进行导入。

下面这个例子,我们有两个文件,并且每个都有它们自己的 __END__ 部分。然而却只有一个全局 DATA 对象。因此第二个文件的 __END__ 部分刚访问不到了。

1
2
3
4
5
6
7
8
9
10
# first.rb
require "./second"

puts "First file\n----------------------"
puts DATA.read

print_second_data()

__END__
First end clause
1
2
3
4
5
6
7
8
9
10
# second.rb

def print_second_data
  puts "Second file\n----------------------"
  puts DATA.read # Won't output anything, since first.rb read the entire file
end

__END__
 
Second end clause
1
2
3
4
5
6
7
snhorne ~/tmp $ ruby first.rb
First file
----------------------
First end clause
 
Second file
----------------------

对于多文件的一个解决方案

在 Sinatra 中有一个很酷的特性是它允许你在你应用的 __END__ 部分添加多个内联模板。它看起来像下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# This code is from the Sinatra docs at http://www.sinatrarb.com/intro.html
require 'sinatra'

get '/' do
  haml :index
end

__END__
 
@@ layout
%html
 
  = yield
 
@@ index
%div.title Hello world.

sinatra 是如何实现的呢?毕竟你的应用可能是运行在 rack 上。在生产环境中你不能再通过 ruby myapp.rb 来运行!他们必须有一种在多文件中使用 DATA 的解决方案。

因此如果你稍微深入一下 Sinatra 的源代码,你会发现它们并没有使用 DATA。而是使用了跟下面这段代码类似的方案。

1
2
# I'm paraphrasing. See the original at https://github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb#L1284
app, data = File.read(__FILE__).split(/^__END__$/, 2)

实际上它比这个要更复杂一些,因为它们不想读取 __FILE__,它只是 sinatra/base.rb 文件。它们其实是需要获取调用了该方法的文件的内容。它们通过解析 caller 的结果来获取。

caller 方法将会告诉你当前运行的方法是从哪调用的。这里是个简单的例子:

1
2
3
4
5
def some_method
  puts caller
end

some_method # => caller.rb:5:in `<main>'

现在可以简单地获取到文件名了,然后从该文件中再提取出与 DATA 等价的内容。

1
2
3
def get_caller_data
  puts File.read(caller.first.split(":").first).split("__END__", 2).last
end

请善用它,不要作恶

希望对于这些技巧你不要经常使用。它们不会使得代码干净、可维护。

然后,你偶尔需要一些又快又脏的实现一个一次性的脚本或者验证一些概念。此时 DATA__END__ 就非常有用了。

Mac下使用supervisor进行服务管理

| Comments

最近刚切换到 Mac 平台上,感觉各种不适应。之前使用 Ubuntu 时,有 service 命令可以对服务进行管理, 但是在 Mac 系统下没有对应的工具。也许有人说可以用 launchctl 啊。但是 launchctl 的服务是开机自动启动的, 而我又不想要开机自动启动,只想在需要时启动,使用完后就停止。

由于没有相应的工具,因此我只得在终端下通过命令来启动服务,但是这个又得一直打开着一个新的终端标签。 对于有洁癖的我来说,表示很不爽。本来想自己写个脚本来管理的,但是这个又得针对每个服务写个脚本,也很麻烦。 正在纠结的时候想起了还有 supervisor 可以用。

supervisor 是使用 python 开发的一个后台服务管理程序。

首先使用 brew 安装 python 工具: brew install python,并覆盖掉系统自带的 python。 因为我有洁癖不想将软件安装在系统目录中,因此就再单独安装一个 python。 若对此不在意的可跳过此步。

然后再安装 supervisor: pip install supervisor。 supervisor 不支持 python3,并且如果你使用的是系统自带的 python ,可能需要在命令前加上 sudo。

安装完成之后默认是不会创建配置文件的,因此再手动创建配置文件 /usr/local/etc/supervisord.conf,我的配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
[unix_http_server]
file=/tmp/supervisor.sock                                 ; path to your socket file

[supervisord]
logfile=/usr/local/var/log/supervisord/supervisord.log    ; supervisord log file
logfile_maxbytes=50MB                                     ; maximum size of logfile before rotation
logfile_backups=10                                        ; number of backed up logfiles
loglevel=error                                            ; info, debug, warn, trace
pidfile=/usr/local/var/run/supervisord.pid                ; pidfile location
nodaemon=false                                            ; run supervisord as a daemon
minfds=1024                                               ; number of startup file descriptors
minprocs=200                                              ; number of process descriptors
user=root                                                 ; default user
childlogdir=/usr/local/var/log/supervisord/               ; where child log files will live

[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[supervisorctl]
serverurl=unix:///tmp/supervisor.sock                     ; use a unix:// URL  for a unix socket

[program:mongod]
command=/usr/local/bin/mongod --config /usr/local/etc/mongod.conf
autostart=false
autorestart=true

[program:redis]
command=/usr/local/bin/redis-server /usr/local/etc/redis.conf
autostart=false
autorestart=true

[program:nginx]
command=/usr/local/bin/nginx -c /usr/local/etc/nginx/nginx.conf
autostart=false
autorestart=true

这里我的这几个服务都没有设置为自动启动,如有需要可自行将 autostart 设置为 true。

最后执行 supervisord 命令启动 supervisor 服务。之后就可以通过 supervisorctl 命令来进行服务管理了。

Riot.js之初体验

| Comments

Riot(http://riotjs.com/)按照官方的介绍,它是一个类似于 React 的微型框架。 压缩之后的文件只有差不多 15K 的大小,相比其他基本上都是上百K大小的框架来说确实是很微型的。

同时它的官方还给出了与 React 和 Polymer 的对比,各位感兴趣可以看看: http://riotjs.com/compare/

下面通过一个例子来体验一下。

先下载 Riot 库文件: https://raw.githubusercontent.com/riot/riot/master/riot+compiler.min.js 然后新建一个文件 index.html,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>Tabs exampe</title>
    <style type="text/css" media="screen">
      .tabContent__item{
        display:none;
      }
      .tabContent__item.is-active{
        display:block;
      }
    </style>
  </head>
  <body>
    <riot-tabs></riot-tabs>

    <script src="tabs.tag" type="riot/tag"></script>
    <script src="riot+compiler.min.js"></script>
    <script type="text/javascript" charset="utf-8">
      riot.mount('riot-tabs');
    </script>
  </body>
</html>

接着再创建文件 tabs.tag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<riot-tabs>
  <h2>Tabs</h2>
  <ul>
    <li each={ tab, i in tabs } class="tabItem { is-active: parent.isActiveTab(tab.ref) }" onclick={ parent.toggleTab }>{tab.title}</li>
  </ul>
  <div class="tabContent">
    <div each={ tab, i in tabs } class="tabContent__item { is-active: parent.isActiveTab(tab.ref) }">{tab.content}</div>
  </div>

  this.tabs = [
    { title: 'Tab 1', ref: 'tab1', content: "(1) Lorem ipsum dolor" },
    { title: 'Tab 2', ref: 'tab2', content: "(2) Lorem ipsum dolor" },
    { title: 'Tab 3', ref: 'tab3', content: "(3) Lorem ipsum dolor" }
  ]

  this.activeTab = 'tab1'

  isActiveTab(tab) {
    return this.activeTab === tab
  }

  toggleTab(e) {
    this.activeTab = e.item.tab.ref
    return true
  }
</riot-tabs>

这个是 javascript 与 html 的混合。当然还可以使用纯 js 的写法,将 tabs.tag 改为 tabs.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
riot.tag('riot-tabs', '<h2>Tabs</h2> <ul> <li each="{ tab, i in tabs }" class="tabItem { is-active: parent.isActiveTab(tab.ref) }" onclick="{ parent.toggleTab }">{tab.title}</li> </ul> <div class="tabContent"> <div each="{ tab, i in tabs }" class="tabContent__item { is-active: parent.isActiveTab(tab.ref) }">{tab.content}</div> </div>', function(opts) {

  this.tabs = [
    { title: 'Tab 1', ref: 'tab1', content: "(1) Lorem ipsum dolor" },
    { title: 'Tab 2', ref: 'tab2', content: "(2) Lorem ipsum dolor" },
    { title: 'Tab 3', ref: 'tab3', content: "(3) Lorem ipsum dolor" }
  ];

  this.activeTab = 'tab1';

  this.isActiveTab = function(tab) {
    return this.activeTab === tab;
  }.bind(this);

  this.toggleTab = function(e) {
    this.activeTab = e.item.tab.ref;
    return true;
  }.bind(this);

});

同时将 index.html 中的 <script src="tabs.tag" type="riot/tag"></script> 修改为 <script src="tabs.js" type="riot/tag"></script>

好了,现在打开浏览器看下效果吧。

体验了一下,感觉它比 angular 之类的框架的学习成本要低。

AngularJS学习笔记5——路由

| Comments

angularjs 的 ngRoute 模块提供了前端路由的功能。 在 angularjs 1.3 中 route 模块被单独提取了出来,要使用其功能需要将引用进来。

1
2
  <script src="/lib/angular-1.3.15.min.js"></script>
  <script src="/lib/angular-route-1.3.15.min.js"></script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var routeApp = angular.module('routeApp', ['ngRoute']);
routeApp.config(['$routeProvider',function ($routeProvider) {
    $routeProvider
    .when('/list', {
      templateUrl: 'list.html',
      controller: 'RouteListCtl'
    })
    .when('/list/:id', {
        templateUrl: 'detail.html',
        controller: 'RouteDetailCtl'
    })
    .otherwise({
      redirectTo: '/list'
    });
}]);

这里定义了两条路由规则,对应的视图模板分别为 list.html 和 detail.html

视图模板有三种定义方式:
方法1:编写在单独的文件中,然后 angularjs 通过 ajax 获取其内容;
方法2:直接在页面中使用 script 标签定义,如: <script type="text/ng-template" id="list.html"> .... </script>
方法3:使用 $templateCache 进行定义,如:

1
2
3
routeApp.run(function($templateCache) {
  $templateCache.put('list.html', '...');
});

AngularJS学习笔记4——依赖注入

| Comments

依赖注入

之前有遇到过 angularjs 的代码压缩之后就报错了,通过查文档得知是因为 angularjs 是通过变量名来查找相应服务的。 而 js 代码压缩之后变量名就变化了,从而无法找到相应的服务而报错。因此就需要指明依赖服务。

1
function SomeController($scope, $http) { ... }

例如以上代码就是定义了 SomeController 这个一个控制器,并依赖 $scope$http 两个服务。 但是当该代码压缩之后,这两个参数名都变化了。因此还需要使用以下代码来指明所需的依赖。

1
SomeController.$inject = ['$scope', '$http'];

或者另一个写法:

1
2
3
4
angular.module('app', [])
       .controller('SomeController', ['$scope', '$http', function($scope, $http){

}]);

这样就可以保证我们的代码压缩之后也能正常运行了。

自定义服务

上面的 $http 服务为我们提供了 ajax 的功能。

需要自定义服务的可以使用 module.factory 方法或者 module.service 方法进行注册服务。 这两个方法稍微有些不同,一般我习惯使用 module.factory 。 然后在需要使用自定义服务的地方,将其添加到依赖列表中即可。

AngularJS学习笔记3——表单

| Comments

双向绑定

使用 ngModel 指令将输入框与 model 进行绑定,如: <input type="text" ng-model="text" name="text" />
当输入框或者 model 的值其中一方改变都会影响另一方。

表单验证

之前表单验证都是用的 jquery-validation 这个插件,这需要编写大量的 js 代码用于逻辑控制, 现在改用 angularjs 之后方便了许多,不过还是要编写大量的视图代码。

一个简单的例子: https://github.com/wusuopu/angularjs-practice/blob/master/static/form/validate.html

1
2
3
4
5
6
7
8
9
<form action="#" name="user_form" ng-submit="submitForm(user_form.$valid, $event)" novalidate>

    <div class="form-group" ng-class="{'has-error' : user_form.name.$invalid && user_form.name.$dirty, 'has-success' : user_form.name.$valid}">
        <label>Name</label>
        <input type="text" name="name" class="form-control" ng-model="formData.name" required>
        <p ng-show="user_form.name.$invalid && user_form.name.$dirty" class="help-block">You name is required.</p>
    </div>
.....
</form>

首先为 form 表单设置 name 属性,并设置 novalidate 属性禁止浏览器使用 HTML5 自带的验证功能。 然后对于需要验证的字段添加相应的验证属性,angularjs 内建的验证规则如下:

  • email
  • max
  • maxlength
  • min
  • minlength
  • number
  • pattern
  • required
  • url
  • date
  • datetimelocal
  • time
  • week
  • month

AngularJS学习笔记2——模板

| Comments

ngRepeat

对于一些需要循环遍历的对象可以使用 ngRepeat 指令。 如:

1
2
3
4
5
6
7
8
9
10
  <li ng-repeat="msg in messages"> </li>

  // 对于 messages 数组进行遍历
  $scope.messages =  [
    "第一条消息",
    "第二条消息",
    "第三条消息",
    "第四条消息",
    "第五条消息"
  ];

ngSwitch

使用 ngSwitch 指令在不同条件下显示不同内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div ng-controller="ExampleController">
  <select ng-model="selection" ng-options="item for item in items">
  </select>
  <code>selection=</code>
  <hr/>
  <div class="animate-switch-container"
    ng-switch="selection">
      <div class="animate-switch" ng-switch-when="settings">Settings Div</div>
      <div class="animate-switch" ng-switch-when="home">Home Span</div>
      <div class="animate-switch" ng-switch-default>default</div>
  </div>
</div>

<script type="text/javascript">
angular.module('myApp', []).controller('ExampleController', ['$scope', function($scope) {
  $scope.items = ['settings', 'home', 'other'];
  $scope.selection = $scope.items[0];
}]);
</script>

Filter

使用 filter 指令过滤需要的内容,基本形式: “ 如果有用过其他模板引擎的话(如:Jinja)应该很好理解的。

angularjs 提供了一些内置的过滤器:date, json, lowercase, uppercase 等。

ngIf

基本形式: <ANY ng-if="expression"> ... </ANY>expression 的值为真时才会输出该标签。

AngularJS学习笔记1——基础入门

| Comments

angularjs 是一款前端的 MVVM 框架,目前 2.0 好像也快要发布了。 angularjs 2.0 的理念又变为了 Web 组件,与 1.x 不兼容。 于是这里就把之前 angularjs 1.x 的学习笔记整理一下,作为以后回顾吧。

相关的事例代码可以从 https://github.com/wusuopu/angularjs-practice 获取。

简单事例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!doctype html>
<html lang="en" ng-app>
<head>
    <meta charset="utf-8">
    <title>基本例子</title>
    <script src="/lib/angular-1.0.4.min.js"></script>
</head>
<body>
  <p>1 + 2 = 1</p>
  <div ng-controller="DemoController">
    <p></p>
  </div>
</body>
<script type="text/javascript" charset="utf-8">
  function DemoController($scope){
    $scope.content = "简单的控制器例子";
  }
</script>
</html>

以上是一个最简单的例子。

1
<html lang="en" ng-app>

html 的 ng-app 属性表明在该页面中启用 angularjs。

1
  <p>1 + 2 = 1</p>

在页面加载完成之后 angularjs 会解析大括号内的表达式,并进行渲染。

1
2
3
  <div ng-controller="DemoController">
    <p></p>
  </div>

ng-controller 表示在该标签下定义一个控制器。一个控制器就是一个普通的 js 函数。

1
2
3
4
5
<script type="text/javascript" charset="utf-8">
  function DemoController($scope){
    $scope.content = "简单的控制器例子";
  }
</script>

这里定义 DemoController 控制器函数,并传入一个 $scope 参数,它是用于表示该控制器的上下文。