batman.js 是一个很令人兴奋的前端mvc框架,不过文档是硬伤,外文资料少的可怜,中文资料更是一个悲剧!
这几天因为一个项目需要用到大量ajax,所以有一个想法用前端mvc来做一个整站的ajax,我看了backbone.js spine.js ember.js 等前端mvc,可是他们各自硬伤啊
banbone.js 他的mvc让我感到陌生,从django到rails等web mvc我没看到这么样的mvc,十分复杂,而且让我写出来的代码不可控制
ember.js 他是一个很好的框架,可是依赖让我却步,比如你得使用jquery低版本,这是我没法接受的,而且ember太大了,文档也不是很好
spine.js 是一个很好的框架,例子和文档都很好,可是他却不能实时更新ui
所以我选择baman.js, 不过这东西会让初学者感到沮丧,因为没有一个好的教程,而且官网文档也少得可怜, 有一个和rails结合的例子,可是作者又是haml拥戴者(这~~)硬着头皮看完,并且看了文档,写一下日志,方便自己以后查看
这里使用rails作为后端
install
在Gemfilel里面添加
安装目录结构
因为我们要使用mvc,所以我们最好关闭掉rails的自动生成coffeescipt
在application.rb添加
config.generators.javascripts = false
如果你想把自动生成scss的功能也给关掉可以继续添加上
config.generators.stylesheets = false
这样我们的基础工作就算ok了
来一个demo吧!
最简单也是最好用的一个demo就是写一个blog吧,这里我们用一个blog来开始我们的batman之旅,这个blog有这么几个要求:
1. 文章要有标题,内容和图片
2. 无刷新,整站使用ajax
ruby
我们需要使用使用基础的命令来生成一个项目
在我们Gemfile里面添加上
1
2
3
gem 'batman-rails'
gem 'carrierwave' #因为我们需要进行图片上传
gem 'bootstrap-sass' , '~> 2.2.2.0' # 为了让我们的demo好看点,我们用这个吧
使用bundle进行gem的安装
之后我们先把自动生产javascipt的功能关掉
在application.rb添加
config.generators.javascripts = false
生成我们需要的api,这里因为我们需要快速完成,使用scaffold
1
rails g scaffold post title : string content : text image : string
然后我们运行migrate来生成数据库
现在生成root首页
1
rails g controller home index
删除public/index.html,并且把app/views/home/index.html.erb 清空
添加root路由
config/routes.rb
1
2
3
4
BatmatBlog : :Application . routes . draw do
root to : "home#index"
resources :pots
end
安装batman
1
2
3
rails g batman : install
#在javascipt里面创建views目录
mkdir app / assets / javascipts / views
修改application.css 为application.css.scss
添加以下内容
1
2
3
4
5
6
/*
*= require_self
*= require_tree .
*/
@import "bootstrap" ;
在application.js添加以下内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//= require jquery
//= require jquery_ujs
//= require bootstrap
// Batman.js and its adapters
//= require batman/es5-shim
//= require batman/batman
//= require batman/batman.jquery
//= require batman/batman.rails
//= require batmat_blog
//= require_tree ./models
//= require_tree ./controllers
//= require_tree ./helpers
//= require_tree .
// Run the Batman app
$ ( document ). ready ( function (){
BatmatBlog . run ();
});
因为我们需要上传文件,所以我们需要使用carrierwave来做
1
rails generate uploader image
修改model
app/models/post.rb
1
2
3
4
class Post < ActiveRecord : :Base
attr_accessible :content , :image , :title
mount_uploader :image , ImageUploader
end
好了接下来我们需要进入batman,batman也为了我们准备了很多很有用生成器
1
2
3
4
5
6
Batman :
batman : controller
batman : helper
batman : install
batman : model
batman : scaffold
我们使用batman:scaffold来创建我们需要的东西,也方便我们熟悉batman的写法
1
rails g batman : scaffold post
batman
打开app/assets/javascipts/post.js
1
2
3
4
5
class BatmatBlog . Post extends Batman . Model
@storageKey: 'posts'
@persist Batman . RailsStorage
@encode "title" , "content" , "image"
下面解释一下上面几个东西,因为我的英语翻译确实比较烂,所以还是直接引用英文了
1
2
@storageKey is a class level option which gives the storage adapters something to interpolate into their specific key generation schemes . In the case of LocalStorage or SessionStorage adapters , the storageKey defines what namespace to store this record under in the localStorage or sessionStorage host objects , and with the case of the RestStorage family of adapters , the storageKey assists in URL generation . See the documentation for the storage adapter of your choice for more information .
The default storageKey is null .
1
@persist is how a Model subclass is told to persist itself by means of a StorageAdapter . @persist accepts either a StorageAdapter class or instance and will return either the instantiated class or the instance passed to it for further modification .
1
2
@encode specifies a list of keys a model should expect from and send back to a storage adapter , and any transforms to apply to those attributes as they enter and exit the world of Batman in the optional encoderObject .
The encoderObject should have an encode and/or a decode key which point to functions . The functions accept the "raw" data ( the Batman land value in the case of encode , and the backend land value in the case of decode ), and should return the data suitable for the other side of the link . The functions should have the following signatures
接下来我们进入controller
我们可以看到batman为我们生成了基本的index show create update
不过我们因为需要有一个get action来生成new和edit
所以我们加上这两个方法,代码就如下面这个样子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class BatmatBlog . PostsController extends Batman . Controller
index: (params) ->
show: (params) ->
new : (params) ->
create: (params) ->
edit: (params) ->
update: (params) ->
destroy: (params) ->
现在先放着,不写实现先,我们需要写路由了
这个时候,可以说说路由在那里实现了,我们看以我们项目名称命名的coffee文件
app/assets/javascipts/batmat_blog.js.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
window . BatmatBlog = class BatmatBlog extends Batman . App
# @root 'controller#all'
# @route '/controller/:id', 'controller#show', resource: 'model', action: 'show'
@ on 'run' , ->
console ? . log "Running ...."
@ on 'ready' , ->
console ? . log "BatmatBlog ready for use."
@flash: Batman ()
@flash . accessor
get: (key) -> @ [ key ]
set: (key, value) ->
@ [ key ] = value
if value isnt ''
setTimeout =>
@set ( key , '' )
, 2000
value
@flashSuccess: (message) -> @set 'flash.success' , message
@flashError: (message) -> @set 'flash.error' , message
我们需要添加路由,和指定view的存放位置
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
window . BatmatBlog = class BatmatBlog extends Batman . App
# @root 'controller#all'
# @route '/controller/:id', 'controller#show', resource: 'model', action: 'show'
@root 'posts#index'
@resources 'posts'
Batman.ViewStore.prefix = 'assets/views' # 设置view目录
@ on 'run' , ->
console ? . log "Running ...."
@ on 'ready' , ->
console ? . log "BatmatBlog ready for use."
@flash: Batman ()
@flash . accessor
get: (key) -> @ [ key ]
set: (key, value) ->
@ [ key ] = value
if value isnt ''
setTimeout =>
@set ( key , '' )
, 2000
value
@flashSuccess: (message) -> @set 'flash.success' , message
@flashError: (message) -> @set 'flash.error' , message
好了我们来写view, 首先修改appliction.html.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html>
<head>
<title> BatmatBlog</title>
< %= stylesheet_link_tag "application", :media => "all" %>
< %= javascript_include_tag "application" %>
< %= csrf_meta_tags %>
</head>
<body>
<div data-yield= "main" ></div>
</body>
</html>
这时打开主页,并打开console,看到一个错误
Uncaught DevelopmentError: Please define routingKey
on the prototype of PostsController in order for your controller to be minification safe. batman.js:750
这是因为rails js会压缩,如果你不压缩js的话,这个无需介意,不过我们的js需要压缩,所以我们必须在controller里面写入这一行代码,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class BatmatBlog . PostsController extends Batman . Controller
routingKey: "posts"
index: (params) ->
BatmatBlog . Post . load (err, posts) =>
@set 'posts' , posts
show: (params) ->
new : (params) ->
create: (params) ->
edit: (params) ->
update: (params) ->
destroy: (params) ->
现在来写new的view和controller action
新建app/assets/javascipts/views/posts/new.html.erb
1
2
3
4
5
6
<form class= "new-post" accept-charset= "utf-8" data-event-submit= "create" data-formfor-post= "post" >
<input type= "text" data-bind= "post.title" name= "post[title]" >
<input type= "text" data-bind= "post.content" name= "post[content]" >
<input type= "file" data-bind= "post.image" name= "post[image]" >
<input type= "submit" value= "save" >
</form>
然后写new和create方法
1
2
3
4
5
6
7
8
9
10
new : (params) ->
@set "post" , new BatmatBlog . Post ()
create: (params) ->
@get ( 'post' ). save (err) =>
if err
# 做一些错误处理
throw err
else
document . getElementsByName ( "post[image]" )[ 0 ]. value = "" # 因为图片上传之后,你再次新建的时候记录还是在的,所以我们要手动清楚掉
现在我们写index的view
app/assets/javascipts/posts/index.html.erb
1
2
3
4
5
6
7
8
<div data-foreach-post= "posts" >
<div>
<p data-bind= "post.title" ></p>
<a data-route= "routes.posts[post]" > show</a>
</div>
</div>
<a data-route= "routes.posts.new" > new</a>
好了想在写show的view
app/assets/javascipts/posts/show.html.erb
1
2
3
4
<p data-bind= "post.title" ></p>
<p data-bind= "post.content" ></p>
<img data-bind-src= "post.image.url" alt= "" >
<a data-route= "routes.posts" > back</a>
补写controller
app/assets/javascipt/controller/posts_controller.js.coffee
1
2
3
show: (params) ->
@set "post" , BatmatBlog . Post . find params . id , (err, post) =>
throw err if err
现在打开浏览器看看,我们的基本样子就出来了,下一篇将介绍用户登陆,和美化,这篇就到这里