龍昌博客

从Pythoneer转向Rubist

Mac OS中VirtualBox的Android蓝牙设置

| Comments

在做手机开发时,由于没有 Android 设备,只得在模拟器中进行测试。然而在模拟器却没法访问本机的蓝牙设备,这对于要做蓝牙开发来说很不方便。

经过各种搜索终于找到了一个解决方案。首先需要以下工具:

1.禁用系统的蓝牙服务:

1
2
3
4
5
6
7
$ sudo launchctl unload /System/Library/LaunchDaemons/com.apple.blued.plist
# 对于 Mountain Lion 系统执行如下命令:
$ sudo kextunload -b com.apple.iokit.IOBluetoothSerialManager
$ sudo kextunload -b com.apple.iokit.BroadcomBluetoothHCIControllerUSBTransport
# 对于 Snow Leopard 系统执行如下命令:
$ sudo kextunload -b com.apple.driver.BroadcomUSBBluetoothHCIController
$ sudo kextunload -b com.apple.driver.AppleUSBBluetoothHCIController

2.运行 VirtualBox

设置启用 USB 控制器,添加蓝牙设备,如图:

然后运行 android 系统即可。

3.在退出 VirtualBox 之后,重新启用系统的蓝牙服务:

1
2
3
4
5
6
7
$ sudo launchctl load /System/Library/LaunchDaemons/com.apple.blued.plist
# 对于 Mountain Lion 系统执行如下命令:
$ sudo kextload -b com.apple.iokit.IOBluetoothSerialManager
$ sudo kextload -b com.apple.iokit.BroadcomBluetoothHCIControllerUSBTransport
# 对于 Snow Leopard 系统执行如下命令:
$ sudo kextload -b com.apple.driver.BroadcomUSBBluetoothHCIController
$ sudo kextload -b com.apple.driver.AppleUSBBluetoothHCIController 

参考:
https://www.virtualbox.org/ticket/2372#comment:17

gulp+browserSync配置

| Comments

Browsersync 是一个前端调试的利器,它能够让你在页面文件改动之后自动刷新浏览器,从而方便了前端的调试工作。

本文就是对于 Browsersync + Gulp 的配置作个简单的笔记。

  1. 首先安装 Browsersync 与 Gulp:
1
$ npm install browser-sync gulp --save-dev
  1. gulpfile.js 中创建新任务:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var gulp = require('gulp');
var browserSync = require('browser-sync').create();

var config = {
  baseDir: 'src',
  watchFiles: [ 'src/**/*.html', 'src/assets/css/*.css', 'src/assets/js/*.js' ]
}

gulp.task('serve', function() {
  browserSync.init({
    files: config.watchFiles,
    server: {
      baseDir: config.baseDir
    }
  });
})

这里表示以 src 目录作为根目录启动 HTTP 服务,并监听 src 目录下的所有 htmlcss 以及 js 类型的文件,当这些文件有改动时 Browsersync 会自动刷新浏览器页面。

如果想配合使用 SASS 之类的,可以参考: https://www.browsersync.io/docs/gulp/

同时为了避免之后每次都要重新配置一遍,于是我自己写了个简单的 yo 生成器: https://github.com/wusuopu/my-yeoman-generator

由于这只是我自己 generator,并没有发布到 npm 上,因此只能手动进行安装。各位有兴趣的可以试试。使用方法:

  1. 安装 yo 和 bower: $ npm install -g yo bower

  2. 安装 generator:

1
2
3
$ git clone https://github.com/wusuopu/my-yeoman-generator generator-wusuopu
$ cd generator-wusuopu
$ npm link
  1. 生成项目:
1
2
3
$ mkdir webpage
$ cd webpage
$ yo wusuopu:bootstrap3

这里 bootstrap3 generator 包含了 bootstrap3、font-awesome、jquery 这些常用的前端库,省得每次都需要重新下载一遍。

HTTP Access Control跨域请求

| Comments

最近在使用 Ajax api 请求时遇到了跨域的问题,现在问题解决了顺便做个笔记。

场景:现在主站域名为 example.org ,需要通过 ajax 请求 hello-world.example 上的资源。

Access-Control-Allow-Origin

如果请求时遇到如下错误:
No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://example.org' is therefore not allowed access.

则需要在 hello-world.example 的 server 端 Response Headers 中设置 Access-Control-Allow-Origin 字段。其值根据情况设置为 http://example.org 或者 https://example.org

Access-Control-Allow-Methods

一般情况下只允许 GET 和 POST 请求,对于 RESTful 的 api 可能会有其他类型的请求。例如:

1
2
3
4
5
$.ajax({
  url: 'http://hello-world.example/sessions/me.json',
  method: 'DELETE',
  dataType: 'json'
});

这时如果出现方法不被允许,则需要在 server 端 Response Headers 中设置 Access-Control-Allow-Methods 字段。如: Access-Control-Allow-Methods: GET, POST, DELETE

Access-Control-Allow-Credentials

当在 hello-world.example 站点登录之后,浏览器会保存对应的 Cookies ,但是在 example.org 站点中使用 ajax 时发现 hello-world.example 的 Cookies 并没有附加到 Request Headers 中。

此时就需要设置 XMLHttpRequest 的 withCredentials 属性,例如:

1
2
3
4
5
6
7
8
$.ajax({
  url: 'http://hello-world.example/session/me.json',
  method: 'GET',
  dataType: 'json',
  xhrFields: {
      withCredentials: true
  }
});

同时在 server 端 Response Headers 中设置 Access-Control-Allow-Credentials 字段。说明允许通过跨域修改 Cookies 。如: Access-Control-Allow-Credentials: true

以上是常用的几个字段,更多设置参考手册: https://www.w3.org/TR/access-control/

Xcode文档离线安装

| Comments

在 Xcode 中下载安装文档速度太慢了,不得已只得自行下载,然后再手动安装。

  1. 首先在 https://developer.apple.com/library/downloads/docset-index.dvtdownloadableindex 找到需要下载的文档的下载地址。

这里我需要下载的是 iOS 9.2 的文档,内容如下:

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
<!-- START iOS doc set -->
<dict>
  <key>fileSize</key>
  <integer>1071665431</integer>
  <key>identifier</key>
  <string>com.apple.adc.documentation.iOS</string>
  <key>name</key>
  <string>iOS 9.2 Documentation</string>
  <key>source</key>
  <string>https://devimages.apple.com.edgekey.net/docsets/20151208/031-43202-A.dmg</string>
  <key>userInfo</key>
  <dict>
    <key>ActivationPredicate</key>
    <string>$XCODE_VERSION >= '7.2'</string>
    <key>Category</key>
    <string>Documentation</string>
    <key>IconType</key>
    <string>IDEDownloadablesTypeDocSet</string>
    <key>InstallPrefix</key>
    <string>$(HOME)/Library/Developer/Shared/Documentation/DocSets</string>
    <key>InstalledIfAllReceiptsArePresentOrNewer</key>
    <dict>
      <key>com.apple.pkg.7.0.iOSDocset</key>
      <string>10.9.0.0.1.1449203766</string>
    </dict>
    <key>RequiresADCAuthentication</key>
    <false/>
    <key>Summary</key>
    <string>My description of content</string>
  </dict>
  <key>version</key>
  <string>92.7</string>
</dict>
<!-- END iOS doc set -->

下载地址为: https://devimages.apple.com.edgekey.net/docsets/20151208/031-43202-A.dmg

如果觉得官网下载速度太慢了,也可以从我的百度网盘下载: http://pan.baidu.com/s/1pKsmkY3 。下载完成之后自行进行文件合并、md5校验。

  1. 将下载的 dmg 文件移动到 ~/Library/Caches/com.apple.dt.Xcode/Downloads/ 目录下(如果目录不存在,自行创建), 并重命名为 <identifier>-<version>.dmg 这样的形式,在这里为: com.apple.adc.documentation.iOS-92.7.dmg

然后转到文档所在目录: ~/Library/Developer/Shared/Documentation/DocSets,如果对应的文档文件已存在则删除。 rm -rf com.apple.adc.documentation.iOS.docset

  1. 打开 Xcode ,点击下载对应的文档。此时应该会跳过下载步骤而直接进行安装。

Xcode

Javascript使用async进行流程控制

| Comments

前言

突然发现博客好久没有更新了,主要是因为最近这几个月比较忙。之前由 Python 转向了 Ruby,现在又由后端转向了前端。 这几个月接触的内容略有点多,信息量有点大,主要都是 js 相关的。准备之后抽时间将这些知识整理整理,沉淀沉淀。

Async

由于 js 是异步的,之前在使用 loopback 进行 server 端开发时,很容易就出现了比较深层次的回调嵌套。 async.js是 js 的一个工具,可以用来方便的控制 js 中的异步流程的,类似的库还有 Promise、RxJS 等。 最初它是设计用于 nodejs 的,不过在浏览器端也可以使用。

安装

安装方法很简单,直接使用 npm 即可: npm install async

使用方法

首先是加载 async 库,在 server 端使用 var async = require('async');, 在浏览器端直接引用即可: <script type="text/javascript" src="async.js"></script>

async 提供一些集合操作方法和流程控制方法,我比较常用的是:eachmapserieswaterfall 这些方法。 其中 eachmap 方法与 lodash 的类似,可以用来遍历某个集合并执行一些操作。

each 方法定义如下:

1
async.each(collection, iterator, [callback])

该方法会对 collection 每个元素都调用 iterator 操作, iterator 函数原型为: iterator(item, callback)。 当 collection 中的所有元素遍历完成或者执行 iterator 时发生错误就会调用 callback 回调,原型为: callback(err)

each 方法只是对每个元素进行操作,如果还需要获取操作的结果,那么可以使用 map 方法。定义如下:

1
async.map(collection, iterator, [callback])

mapeach 类似,只是 callback 定义为: callback(err, results)resultsiterator 操作的结果集合。

如下是一个例子,一次读取多个文件的内容:

1
2
3
async.map(['file1','file2','file3'], fs.readFile, function(err, results){
    // doSomething();
});

seriesmap 类似,不过 series 是遍历一个方法合集并挨个执行,然后返回结果:

1
async.series(tasks, [callback])

如:

1
2
3
4
5
6
7
8
9
10
11
async.series([
  function fun1(callback){
    callback(null, 'one');
  },
  function fun2(callback){
    callback(null, 'two');
  }
],
function(err, results){
  // doSomething();
});

注意,以上这些方法各个任务的完成时间顺序是不确定的。如果有一些操作是需要按照先后顺序执行,可以使用 waterfall

1
async.waterfall(tasks, [callback])

例如在 loopback 的一个 controller 中,提供修改用户密码的功能。原始写法如下:

1
2
3
4
5
6
7
8
9
10
router.post('/user/password', function(req, res) {
  User.findById(req.body.id, function(err, user){
    if (err) doSomething();
    user.password = req.body.password;
    user.save(function(err){
      if (err) doSomething();
      res.status(200).end();
    });
  });
});

上面的例子功能还比较简单,回调层级不是很深。不过如果使用 waterfall 来控制就更为简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
router.post('/user/password', function(req, res) {
  async.waterfall([
    function(callback){
      User.findById(req.body.id, callback);
    },
    function(user, callback){
      user.password = req.body.password;
      user.save(callback);
    }
  ], function(err){
    if (err) doSomething();
    res.status(200).end();
  });
});

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 之类的框架的学习成本要低。