avalon2已經穩定下來,是時候教大家如何使用組件這個高級功能了。
組件是我們實現疊積木開發的關鍵。
avalon2實現一個組件非常輕松,並且如何操作這個組件也比以前的avalon2,還是react, angular輕松多了,不需要flux這樣奇怪的額外設施。
avalon2的組件包含三部分,以經典的行為結構樣式相分離。通常我們命名為index.js, template.html, style.scss。
比如我們開發一個彈出層組件(有遮罩的那種),其目錄結構就是如下。
modal
----|index.js
----|template.html
----|style.scss
webpack及各種loader配置
index.js是使用nodejs的模塊機制,不過通過webpack,我們可以對require進來的文件進行預處理。因此我們要事先搞定webpack及其一些常用loader。其實也不多,就是css-loader, text-loader, style-loader, sass-loader。而sass-loader要依賴node-sass這個巨可怕的模塊,它要依賴更多東西,這對你的網速與耐性有一定要求。為了讓你盡快搞定這些依賴,建議你設置一下npm 的代理,如使用cnpm。
安裝webpack時最好指定版本,使用1.13.1,2.*並不穩定。我們整個應用都使用webpack打包。
我們建立一個ms-modal的文件夾。然后在命令行底下,使用npm init命令來初始化倉庫吧,一路回車,最后敲上你的大名:
然后修改package.json文件,添加devDependencies這個鍵值對,最后變成這樣
{
"name": "ms-modal",
"version": "1.0.0",
"description": "modal",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/RubyLouvre/ms-modal.git"
},
"dependencies": {
"avalon2": "~2.1.1",
"url-loader": "0.5.7",
"node-sass": "^3.8.0",
"sass-loader": "^3.2.2",
"style-loader": "~0.13.1",
"css-loader": "~0.8.0",
"text-loader": "0.0.1",
"webpack": "^1.13.1"
},
"author": "RubyLouvre",
"license": "MIT",
"bugs": {
"url": "https://github.com/RubyLouvre/ms-modal/issues"
},
"homepage": "https://github.com/RubyLouvre/ms-modal#readme"
}
各個模塊的作用
- avalon2 這是核心庫,負責所有DOM操作與數據處理
- url-loader 用於抽取圖片與字體等各種資源
- sass-loader, node-sass, 用於編譯SCSS文件
- css-loader 處理CSS文件中url,添加局作用域支持(css modules)及壓縮或美化CSS
- style-loader 創建一個style標簽,將所有模塊引用的樣式全部塞進去,大大減少請求
- text-loader 將我們的模板文件變成一個字符串。群里有人建議使用 raw!html-minify 這兩個loader,下次試試
- webpack 將工程的所有資源通過各種loader整合起來!
然后npm install
樣式與模板
為了美觀,我們用到了iconfont,這個我已經為你們准備好了。
建立一個子目錄放 iconfont字體。
然后建立一個font.scss,內容省去,不是我們學習的重點。
建立一個btn.scss子模塊,內容省去,不是我們學習的重點。
再建立一個style.scss模塊,它引入btn.scss, font.scss,內容如下:
@import "./font.scss";
@import "./btn.scss";
.modal-mask{
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(55,55,55,.6);
z-index: 100;
display: flex;
align-items: center;
justify-content: center;
}
.confirm-content{
padding-left: 30px;
padding-top: 30px;
padding-bottom: 30px;
}
.modal-confirm{
width: 400px;
box-sizing: border-box;
padding: 30px 40px;
background-color: #fff;
border-radius: 6px;
transition: transform .3s ease;
i{
color: #fa0;
font-size: 24px;
position: relative;
top: 2px;
}
.confirm-btns{
text-align: right;
}
}
.modal-box{
width: 520px;
box-sizing: border-box;
background-color: #fff;
border-radius: 6px;
}
@media only screen and (max-width: 640px) {
.modal-confirm{
width: 100%;
margin: 0 20px;
padding: 10px 20px;
}
.modal-box{
width: 100%;
margin: 0 20px;
}
}
.modal-header{
padding: 13px 18px 14px 16px;
border-bottom: 1px solid #e9e9e9;
position: relative;
i{
position: absolute;
right: 20px;
top: 15px;
font-size: 14px;
cursor: pointer;
}
h3{
font-size: 14px;
}
}
.modal-body{
padding: 16px;
}
.modal-footer{
padding: 10px 18px 10px 10px;
border-top: 1px solid #e9e9e9;
background: #fff;
border-radius: 0 0 6px 6px;
text-align: right;
}
.modal-enter {
opacity: 0;
}
.modal-enter-active{
opacity: 1;
.modal-confirm{
transform: scale(1.1);
}
.modal-box{
transform: scale(1.1);
}
}
.modal-leave {
opacity: 1;
}
.modal-leave-active{
opacity: 0;
.modal-confirm{
transform: scale(1.1);
}
.modal-box{
transform: scale(1.1);
}
}
.modal-enter,.modal-leave {
transition: all .3s ease;
}
注意: scss文件中不能出現 中文,否則會編譯失敗
這里用到了動畫效果,使用modal-enter, modal-leave, modal-enter-active, modal-leave-active等類名實現,原理與angular是一致的。可以到這里溫習一下。
再建立一個template.html.之所以不學vue, polymer將組件所有東西並成一個文件,是因為對於普通的html文件,所有IDE或文本編輯器都有自帶的語法高亮。並且里面需要什么處理,可以直接在webpack中配置。
template.html的內容如下:
<div class="modal-mask" ms-visible="@isShow" ms-effect="{is:'modal'}">
<div class="modal-box">
<div class="modal-header">
<h3>{{@title}}</h3>
<i class="iconfont icon-cross" ms-click="@cbProxy(false)"></i>
</div>
<div class="modal-body">
<slot name="content"></slot>
</div>
<div class="modal-footer">
<button class="btn" ms-click="@cbProxy(false)">取 消</button>
<button class="btn btn-primary" ms-click="@cbProxy(true)">確 定</button>
</div>
</div>
</div>
需要說明一下modal組件一般要直接放在body底下,是其直接子元素,方便其蒙板能罩住整個頁面。上面的.modal-mask
就是蒙板,.model-box
就是彈出層,彈出層里面又分三大部分,標題欄,內容區與底部的按鈕區。內容區比如復雜,我們使用DOM插入點機制來設置,換言之,那里使用slot元素占位。以后我們直接在自定義標簽里面添加對應標簽,它就會挪到slot的位置上了!
視圖模型
modal組件一般有如下屬性:
- isShow: 用於控制顯示與否
- title: 標題
- content: 內容,這個是一個非常復雜的HTML結構
- 回調: 這里設計了兩種回調onOk, onCancel。它們的行為很相近,因此我在模板上都封裝成cbProxy,通過傳參來區分它們。
然后是組件本身index.js
到目前為止,我們的目錄如下:
index.js主要是引用模板與樣式與avalon2主庫,然后代碼主體為avalon.component這個方法的使用。
var avalon = require('avalon2')
require('./style.scss')
avalon.component('ms-modal', {
template: require('text!./template.html'),
defaults: {
title:'modal',
isShow: true,
cbProxy: function(ok){
}
},
soleSlot: 'content'
})
defaults對象里面定義組件要用到的屬性,soleSlot是占位元素slot的名字。
事件與鈎子
關鍵是dbProxy的實現,我們要求點擊上面的叉叉與下面的取消按鈕時調用onCanel回調,如果這個回調什么都不返回,就直接隱藏彈層。如果onCancel是返回false或是返回一個類似Promise的對象(帶next方法),就不會隱藏彈層。 點擊其確認按鈕,則是調用onOk回調,其他邏輯與onCancel相似!
cbProxy: function (ok) {
var cbName = ok ? 'onConfirm' : 'onClose'
if (this.hasOwnProperty(cbName)) {
var ret = this[cbName]()
if (ret !== false || (ret && typeof ret.next === 'function')) {
this.isShow = false
}
} else {
this.isShow = false
}
}
然后我在組件onReady鈎子處理一些樣式問題,比如說你要蓋住整個窗口,如果不想拉大整個蒙板,那么最好就是將body的overflow:hidden。 此外還要阻止第一次動畫效果。
onReady: function(){
var el = this.$element
el.style.display = 'none'//強制阻止動畫發生
this.$watch('isShow', function(a){
if(a){
document.body.style.overflow = 'hidden'
}else{
document.body.style.overflow = ''
}
})
}
到此為止,我們的組件已經寫好了,是不是快得難以想象。
然后我們建立一個main.js入口文件,它里面調用modal組件或引入其他業務代碼,為簡單起見,我們現在只是定義了一個頁面vm。
var avalon = require('avalon2')
require('./index')
avalon.define({
$id: 'test',
show: function(){
this.config.isShow = true
},
config: {
isShow: false,
onCancel: function(){
alert('cancel')
},
onOk: function(){
alert('ok')
},
title:'這是測試'
}
})
module.exports = avalon
注意,最后必須返回avalon,用於webpack.cofig的 output配置
當然以后改一下webpack.config,可能就不需要這樣寫。有關如何利用webpack 進行工程化,我也在不斷探索。大家有好的方案可以留言給我。
打包與運行
我們再看一下如何打包。建議一個webpack.config.js,內容如下:
var webpack = require('webpack');
var path = require('path');
function heredoc(fn) {
return fn.toString().replace(/^[^\/]+\/\*!?\s?/, '').
replace(/\*\/[^\/]+$/, '').trim().replace(/>\s*</g, '><')
}
var api = heredoc(function () {
/*
avalon的彈出層組件
1. isShow: 用於控制顯示與否
2. title: 標題
3. content: 內容,這個是一個非常復雜的HTML結構
4. onOk
5: onCancel
使用
兼容IE6-8
<xmp ms-widget="[{is:'ms-modal'}, @config]">
<p>彈窗的內容</p>
<p>彈窗的內容</p>
<p>彈窗的內容結束!</p>
</xmp>
只支持現代瀏覽器(IE9+)
<ms-modal ms-widget="@config">
<p>彈窗的內容</p>
<p>彈窗的內容</p>
<p>彈窗的內容結束!</p>
</ms-modal>
*/
})
module.exports = {
entry: {
index: './main'
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].js',
libraryTarget: 'umd',
library: 'avalon'
}, //頁面引用的文件
plugins: [
new webpack.BannerPlugin('彈出層組件 by 司徒正美\n' + api)
],
module: {
loaders: [
//ExtractTextPlugin.extract('style-loader', 'css-loader','sass-loader')
//http://react-china.org/t/webpack-extracttextplugin-autoprefixer/1922/4
//http://stackoverflow.com/questions/34639720/webpack-font-include-issue
// https://github.com/b82/webpack-basic-starter/blob/master/webpack.config.js
{test: /\.scss$/, loader:'style!css!sass',exclude: /node_modules/},
{test: /\.(ttf|eot|svg|woff2?)((\?|#)[^\'\"]+)?$/, loader: 'url-loader'}
]
},
resolve: {
extensions: ['.js', '', '.css']
}
}
我們執行webpack命令,它就合並成一個文件,放在dist目錄下,但有時你死活裝不上node-sass什么,搞不定SASS的編譯,也沒問題,可以koala編譯好,直接在index.js中引用純CSS文件。
最后建立一個page.html文件,欣賞一下我們的勞動成果:
<!DOCTYPE html>
<html>
<head>
<title>modal</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="./dist/index.js"></script>
</head>
<body ms-controller="test">
<xmp ms-widget="[{is:'ms-modal'}, @config]">
<p>彈窗的內容</p>
<p>彈窗的內容</p>
<p>彈窗的內容結束!</p>
</xmp>
<p><button ms-click="@show">顯示彈出</button></p>
</body>
</html>
大家可以到這里下載到此工程
其他參考資料: