前言
說起Custom Element那必然會想起那個相似而又以失敗告終的HTML Component。HTML Component是在IE5開始引入的新技術,用于對原生元素作功能"增強",雖然僅僅被IE所支持,雖然IE10也開始放棄它了,雖然掌握了也用不上,但還是不影響我們以研究的心態去了解它的:)
把玩HTML Component
HTML Component簡稱HTC,它由定義和應用兩部分組成。定義部分寫在.htc
文件中(MIME為text/x-component),由HTC獨有標簽、JScript和全局對象(element
,window
等)組成;而應用部分則寫在html文件中,通過CSS的behavior規則附加到特定的元素上。
定義部分
HTC獨有標簽
PUBLIC:COMPONENT
, 根節點.
PUBLIC:PROPERTY
, 定義元素公開自定義屬性/特性.
屬性
NAME
,html文件中使用的屬性名
INTERNALNAME
,htc文件內使用的屬性名,默認與NAME
一致
VALUE
,屬性默認值
PUT
,setter對應的函數名
GET
,getter對應的函數名
PUBLIC:EVENT
, 定義元素公開自定義事件.
屬性
NAME
,公開事件名稱,如onheadingchange
ID
,htc內使用的事件名稱,如ohc.然后通過ohc.fire(createEventObject())
來觸發事件
PUBLIC:ATTACH
,訂閱事件
屬性
EVENT
,訂閱的事件名稱,如onheadingchange
ONEVENT
,事件處理函數體,如headingchangehandler()
FOR
,事件發生的宿主(element
,document
,window
,默認是element
)
PUBLIC:METHOD
, 定義元素公開方法
屬性
NAME
,html文件中使用的方法名
INTERNALNAME
,htc文件內使用的方法名,默認與NAME
一致。在JScript中實現具體的方法體
PUBLIC:DEFAULTS
,設置HTC默認配置
HTC生命周期事件
ondocumentready
, 添加到DOM tree時觸發,在oncontentready后觸發
oncontentready
, 添加到DOM tree時觸發
ondetach
, 脫離DOM tree時觸發, 刷新頁面時也會觸發
oncontentsave
, 當復制(ctrl-c)element內容時觸發
HTC全局對象
element
, 所附加到的元素實例
runtimeStyle
,所附加到的元素實例的style屬性
document
,html的文檔對象
HTC全局函數
createEventObject()
,創建事件對象
attachEvent(evtName, handler)
, 訂閱事件.注意:一般不建議使用attachEvent來訂閱事件,采用<PUBLIC:ATTACH>
來訂閱事件,它會自動幫我們執行detach操作,避免內存泄露.
detachEvent(evtName[, handler])
, 取消訂閱事件
應用部分
引入.htc
1.基本打開方式
<style>
css-selector{
behavior: url(file.htc);
}
</style>
2.打開多個
<style>
css-selector{
behavior: url(file1.htc) url(file2.htc);
}
</style>
可以看到是通過css-selector匹配元素然后將htc附加到元素上,感覺是不是跟AngularJS通過屬性E指定附加元素的方式差不多呢!
3.自定義元素
<html xmlns:x>
<head>
<style>
x\:alert{
behavior: url(x-alert.htc);
}
</style>
</head>
<body>
<x:alert></x:alert>
</body>
</html>
自定義元素則有些麻煩,就是要為自定義元素指定命名空間x:alert
,然后在html節點上列出命名空間xmlns:x
。(可多個命名空間并存<html xmlns:x xmlns:y>
)
下面我們來嘗試定義一個x:alert
元素來看看具體怎么玩吧!
自定義x:alert
元素
x-alert.htc
<PUBLIC:COMPONENT>
<PUBLIC:ATTACH EVENT="oncontentready" ONEVENT="onattach()"></PUBLIC:ATTACH>
<PUBLIC:ATTACH EVENT="ondetach" ONEVENT="ondetach()"></PUBLIC:ATTACH>
<PUBLIC:METHOD NAME="close"></PUBLIC:METHOD>
<PUBLIC:METHOD NAME="show"></PUBLIC:METHOD>
<PUBLIC:PROPERTY NAME="heading" PUT="putHeading" SET="setHeading"></PUBLIC:PROPERTY>
<PUBLIC:EVENT NAME="onheadingchange" ID="ohc"></PUBLIC:EVENT>
<PUBLIC:ATTACH EVENT="onclick" ONEVENT="onclick()"></PUBLIC:ATTACH>
<script language="JScript">
/*
* private region
*/
function toArray(arrayLike, sIdx, eIdx){
return Array.prototype.slice.call(arrayLike, sIdx || 0, eIdx || arrayLike.length)
}
function curry(fn /*, ...args*/){
var len = fn.length
, args = toArray(arguments, 1)
return len <= args.length
? fn.apply(null, args.slice(0, len))
: function next(args){
return function(){
var tmpArgs = args.concat(toArray(arguments))
return len <= tmpArgs.length ? fn.apply(null, tmpArgs.slice(0, len)) : next(tmpArgs)
}
}(args)
}
function compile(tpl, ctx){
var k
for (k in ctx){
tpl = tpl.replace(RegExp('\$\{' + k + '\}'), ctx[k]
}
return tpl
}
// 元素內部結構
var tpl = '<div class="alert alert-warning alert-dismissible fade in">\
<button type="button" class="close" aria-label="Close">\
<span aria-hidden="true">×</span>\
</button>\
<div class="content">${raw}</div>\
</div>'
var getHtml = curry(compile, tpl)
/*
* leftcycle region
*/
var inited = 0, oHtml = ''
function onattach(){
if (inited) return
oHtml = element.innerHTML
var ctx = {
raw: heading + oHtml
}
var html = genHtml(ctx)
element.innerHTML = html
runtimeStyle.display = 'block'
runtimeStyle.border = 'solid 1px red'
}
function ondetach(){}
/*
* public method region
*/
function show(){
runtimeStyle.display = 'block'
}
function close(){
runtimeStyle.display = 'none'
}
/*
* public property region
*/
var heading = ''
function putHeading(val){
if (heading !== val){
setTimeout(function(){
var evt = createEventObject()
evt.propertyName = 'heading'
ohc.fire(evt)
}, 0)
}
heading = val
}
function getHeading(){
return heading
}
/*
* attach event region
*/
function onclick(){
if (/^\s*close\s*$/.test(event.srcElement.className)){
close()
}
}
</script>
</PUBLIC:COMPONENT>
引用x:alert
index.html
<html xmlns:x>
<head>
<title></title>
<style>
x\:alert{
behavior: url(x-alert.htc);
}
</style>
</head>
<body>
<x:alert id="a" heading="Hello world!"></x:alert>
<script language="JScript">
var a = document.getElementById('a')
a.onheadingchange = function(){
alert(event.propertyName + ':' + a.heading)
}
// a.show()
// a.close()
// document.body.appendChilid(document.createElement('x:alert'))
</script>
</body>
</html>
感受
在寫HTC時我有種寫C的感覺,先通過HTC獨有標簽聲明事件、屬性、方法等,然后通過JScript來提供具體實現,其實寫Angular2時也有這樣的感覺,不過這里的感覺更強烈一些。
這里先列出開發時HTC給我驚喜的地方吧!
- htc文件內的JScript代碼作用域為htc文件本身,并不污染html文件的腳本上下文;
- 帶屬性訪問器的自定義屬性大大提高我們對自定義屬性的可控性;
然后就是槽點了
- htc行為與元素綁定分離,好處是靈活,缺點是非自包含,每次引入都要應用者自己綁定一次太啰嗦了。我覺得Angular通過屬性E綁定元素既靈活又實現自包含才是正路啊!
- API有bug。如ondocumentready事件說好了是html文檔加載完就會觸發,按理只會觸發一下,可實際上它總會在oncontentready事件后觸發,還有fireEvent的API根本就沒有,只能說繼承了IE一如既往的各種坑。
- 通過runtimeStyle來設置inline style,從而會丟失繼承、層疊等CSS特性的好處;
- 自定義元素內部結構會受到外部JS、CSS的影響,并不是一個真正閉合的元素。
總結
很抱歉本文的內容十分對不住標題所述,更全面的觀點請查看徐飛老師的《從HTML Components的衰落看Web Components的危機》。假如單獨看Custom Element,其實它跟HTML Component無異,都沒有完整的解決自定義元素/組件的問題,但WebComponent除了Custom Element,還有另外3個好伙伴(Shadow DOM,template,html imports)來一起為自定義元素提供完整的解決方案,其中Shadow DOM可謂是重中之重,后續繼續研究研究:)
尊重原創,轉載請注明來自:http://www.cnblogs.com/fsjohnhuang/p/5987853.html ^_^肥仔John
感謝
《從HTML Components的衰落看Web Components的危機》
HTC Reference
Using HTML Components to Implement DHTML Behaviors in Script
文章列表