HTML组件(HTML conponent)是DTHML里的东西,相信现在大部分的web开发或者是网站都不使用DHTML了。它只能在IE下有直接的支持,它直接在样式表中用
来指示样式的行为使用的htc文件,IE支持behavior,但火狐不能支持。
如何让火狐等浏览器也支持htc呢,这里有个已实现的示例:
此方法来自http://dean.edwards.name/
它的示例http://dean.edwards.name/my/examples/moz-behaviors/
示例下载http://deanedwards.googlecode.com/svn/trunk/download/moz-behaviors.zip
下面以他的示例来解释下实现方法。
1. IE下是在样式表中用behavior,那么火狐下有它自己专用的css,那就是用-moz-binding,最终css就是这样的:
behavior: url(drag-box.htc);
/* 用于火狐的 behaviors */
-moz-binding: url(bindings.xml# drag-box.htc);
2. 发现css中有个bindings.xml文件全名,这个就是作者写的xml文件,其中主要是有两个地方是重要的,其他的可以不管它(除非你也需要)。
<binding id="behavior" extends="moz-behaviors.xml#behavior"/>
<!—类似这些都是用来标识需要用到的htc文件的 -->
<binding id="test.htc" extends="#behavior"/>
3. 对于moz-behaviors.xml文件是用来提供对behavior的支持的,它里面有段代码是进行“优化”后的,看起来是加密的,显示乱码。你下载的示例中有这个文件的源文件moz-behaviors-source.xml,它在运行中是不需要的,但这个文件才是火狐下使用htc最关键的地方,下面把源码贴出来供大家学习。
2 <bindings xmlns="http://www.mozilla.org/xbl" xmlns:html="http://www.w3.org/1999/xhtml">
3 <!--
4 moz-behaviors.xml - version 1.1.1 (2005-08-19)
5 Copyright 2004-2005, Dean Edwards
6 License: http://creativecommons.org/licenses/LGPL/2.1/
7 -->
8 <!--
9 =======================================================================
10 TO DO
11 =======================================================================
12
13 hideFocus
14 fix CSSStyleDeclaration pixel*.__defineGetter__
15 behaviors FAQ
16 test Event.returnValue
17 http://developer-test.mozilla.org/docs/Working_around_the_Firefox_1.0.3_DHTML_regression
18 -->
19
20 <!-- add a behavior through css -->
21 <binding id="behavior" extends="#-moz-behaviors">
22 <implementation><constructor>
23 <![CDATA[
24 addBehavior(0);
25 ]]></constructor></implementation>
26 </binding>
27
28 <!-- manually attach behaviors to child elements of <table>s -->
29 <binding id="table" extends="#-moz-behaviors">
30 <implementation><constructor><![CDATA[
31 var i, j;
32 if (tHead) tHead.addBehavior(0);
33 for (i = 0; i < tBodies.length; i++) tBodies[i].addBehavior(0);
34 for (i = 0; i < rows.length; i++) {
35 rows[i].addBehavior(0);
36 for (j = 0; j < rows[i].cells.length; j++) {
37 rows[i].cells[j].addBehavior(0);
38 }
39 }
40 if (tFoot) tFoot.addBehavior(0);
41 ]]></constructor></implementation>
42 </binding>
43
44 <binding id="-moz-behaviors" extends="#-moz-ie">
45 <implementation><constructor>
46 <![CDATA[if(!this.addBehavior){
47 // ------------------------------------------------------------------
48 // htc extensions for mozilla
49 // ------------------------------------------------------------------
50
51 /* here we define the addBehavior/removeBehavior methods for an element
52 these methods are used to add and remove all dhtml behaviors
53 */
54
55 var _cookie = -1; // no support for removeBehavior yet
56
57 // implement the addBehavior method for all elements
58 Element.prototype.addBehavior = function(_url) {
59 try {
60 // calling this method with the first argument as zero
61 // initialises the object's behaviors
62 if (_url === 0) {
63 // grab the htc's url from the css setting
64 var $binding = getComputedStyle(this, null).getPropertyValue("-moz-binding");
65 $binding = $binding.replace(/^url\(([^)]*)\)$/, "$1").split("#");
66 if ($binding) {
67 var $path = $binding[0].replace(/[^\/]+$/, "");
68 // support multiple behaviors
69 var $htcs = $binding[1].split("|");
70 var i = $htcs.length;
71 while (i--) this.addBehavior($path + $htcs[i]);
72 }
73 }
74 if (!_url) return;
75
76 // check the cache
77 if (!document.behaviorUrns[_url]) {
78 // constants
79 var $SEPARATOR = ";";
80
81 function _getTagName($node) {
82 var $tagName = $node.tagName.toLowerCase();
83 // this fixes a bug(?) in Mozilla 1.6b that includes the
84 // namespace prefix in the tagName
85 return $tagName.slice($tagName.indexOf(":") + 1);
86 };
87
88 function _getAttribute($node, $attribute) {
89 return $node.getAttribute($attribute) || $node.getAttribute($attribute.toUpperCase());
90 };
91
92 // this function converts elements in a behavior to a program
93 // declaration, for example:
94 // <public:attach for="window" event="onload" handler="init"/>
95 // becomes:
96 // window.addEventListener("load", init);
97 function _asDeclaration($behaviorNode) {
98 switch (_getTagName($behaviorNode)) {
99 case "event":
100 var id = _getAttribute($behaviorNode, "id");
101 return (id) ? "var " + id + "={fire:function(event){element.fireEvent('" +
102 _getAttribute($behaviorNode, "name") + "',event)}}" : "";
103 case "property":
104 var $name = _getAttribute($behaviorNode, "name");
105 var $get = _getAttribute($behaviorNode, "get") || "";
106 if ($get) $get = "__defineGetter__('" + $name + "'," + $get + ")";
107 var $put = _getAttribute($behaviorNode, "put") || "";
108 if ($put) $put = ";__defineSetter__('" + $name + "'," + $put + ")";
109 var id = _getAttribute($behaviorNode, "id") || "";
110 if (id) id = "var " + id + "={fireChange:new Function};";
111 return id + $get + $put;
112 case "method":
113 return "element." + _getAttribute($behaviorNode, "name") + "=" +
114 _getAttribute($behaviorNode, "name");
115 case "attach":
116 var $handler = _getAttribute($behaviorNode, "handler") || "";
117 $handler += ($handler) ? "()" : _getAttribute($behaviorNode, "onevent");
118 $handler = "function(event){window.event=event;return " + $handler + "}";
119 var $event = _getAttribute($behaviorNode, "event");
120 switch ($event) {
121 case "oncontentready": return "window.setTimeout(" + $handler + ",1)";
122 case "ondocumentready": return "document.behaviorUrns.__private.push(" + $handler + ")";
123 }
124 return (_getAttribute($behaviorNode, "for")||"element") +
125 ".addEventListener('" + $event.slice(2) + "'," + $handler + ",false)";
126 case "defaults":
127 // not implemented
128 default:
129 return "";
130 }
131 };
132
133 function _asDefault($node) {
134 return (_getAttribute($node, "put")) ? ";var __tmp=getAttribute('" + _getAttribute($node, "name") + "')||" +
135 (_getAttribute($node, "value") || "null") +
136 ";if(__tmp!=null)element['" + _getAttribute($node, "name") + "']=__tmp" : "";
137 };
138
139 // extract the body of a function
140 function _getFunctionBody($function) {
141 with (String($function)) return slice(indexOf("{") + 1, lastIndexOf("}"));
142 };
143
144 // behaviors are defined as xml documents, so we can use
145 // the http request object to load them and the dom parser
146 // object to parse them into a dom tree
147 var _httpRequest = new XMLHttpRequest;
148 function _loadFile($url) {
149 try {
150 // load the behavior
151 _httpRequest.open("GET", $url, false);
152 _httpRequest.send(null);
153 return _httpRequest.responseText;
154 } catch ($ignore) {
155 // ignore (but don't crash)
156 }};
157
158 // analyse the dom tree, build the interface and create the script
159 var _declarations = [];
160 var _defaults = "";
161 var _script = "";
162 function _load() {
163 // build a dom representation of the loaded xml document
164 var $dom = (new DOMParser).parseFromString(_loadFile(_url), "text/xml");
165 var $childNodes = $dom.documentElement.childNodes, $node;
166 for (var i = 0; ( $node = $childNodes[i]); i++) {
167 if ($node.nodeType == Node.ELEMENT_NODE) {
168 if (_getTagName($node) == "script") {
169 var $src = _getAttribute($node, "src");
170 if ($src) {
171 _script += _loadFile($src);
172 } else {
173 // build the script from the text nodes of the script element
174 for (var j = 0; j < $node.childNodes.length; j++)
175 _script += $node.childNodes[j].nodeValue;
176 }
177 } else {
178 // convert the dom node representation of a
179 // <public:declaration/> to a javascript statement
180 // and store it in our declarations collection
181 _declarations.push(_asDeclaration($node));
182 if (_getTagName($node) == "property") {
183 _defaults += _asDefault($node);
184 }
185 }
186 }
187 }
188 _defaults += ";delete __tmp";
189 };
190 _load();
191 // we've finished collecting interface declarations.
192 // they are now held as an array of strings.
193
194 // build a function from the script and extract the function body
195 // this has the effect of formatting the script (removing comments etc)
196 _script = _getFunctionBody(new Function(_script));
197
198 // support: new ActiveXObject
199 var $ACTIVEX = /\bnew\s+ActiveXObject\s*\(\s*(["'])\w\.XMLHTTP\1\s*\)/gi;
200 _script = _script.replace($ACTIVEX, "new XMLHttpRequest()");
201
202 // begin: annoying parse of script to "shuffle" declarations
203 // and inline code.
204 // microsoft dhtml behaviors add the interface first, then
205 // apply inline script.
206 // to achieve this, we have to strip out all of the inline
207 // code, leaving only function declarations. the inline code
208 // then gets appended to the script block for later
209 // execution.
210 // in between the function declarations and inline script, we
211 // sandwich the property getters and setters.
212 // this is a real nuisance actually...
213
214 // on the upside regular expressions are really quick...
215
216 // i'm using "#" as a placeholder, so i'll have to escape these out
217 _script = _script.replace(/#/g, "\\x23");
218
219 // parse out strings, regexps and program
220 // blocks - anything between curly braces {..}
221 var $ = [_declarations.join($SEPARATOR)];
222 var $BLOCKS_REGEXPS_STRINGS = /(\"[^\"\n]+\")|(\/[^\/\n]+\/)|(\{[^\{\}]*\})/g;
223 var _ENCODED = /#(\d+)\b/g;
224 // store a string and return a unique id
225 function _encode($match) {return "#" + $.push($match)};
226 function _decode($match, $index) {return $[$index - 1]};
227 while ($BLOCKS_REGEXPS_STRINGS.test(_script)) {
228 _script = _script.replace($BLOCKS_REGEXPS_STRINGS, _encode);
229 }
230 // we are now left with function declarations and inline statements
231
232 // remove function declarations and save them
233 var $FUNCTIONS = /\n\s*function[^\n]*\n/g;
234 var _functions = _script.match($FUNCTIONS) || [];
235 _script = _script.replace($FUNCTIONS, "");
236
237 // re-assemble the encoded script, in the following
238 // order: function declarations, interface definition
239 // (getters and setters), inline script
240 _script = _functions.concat("#1", _script).join($SEPARATOR);
241
242 // decode the script
243 var i = $.length;
244 $ = $.join("\x01").replace(/\$\$/g, "$$$$$$$$").split("\x01"); // Scott Shattuck
245 do _script = _script.replace("#" + i, $[--i]); while (i);
246 // end: annoying parse of script
247
248 // build the final script
249 _script += _defaults;
250
251 // create an anonymous function in the global namespace.
252 // this function will add the interface defined by the dhtml behavior.
253 // after we've built this function we'll store it so that we don't
254 // have to go through this process again.
255 document.behaviorUrns[_url] = new Function("element", "with(this){" + _script + "}");
256 }
257
258 // because we loaded synchronously (or got it from the cache)
259 // we can apply the behavior immediately...
260 document.behaviorUrns[_url].call(this, this);
261
262 // this might mean somthing later
263 return _cookie;
264 } catch ($error) {
265 return 0;
266 }};
267
268 // implement the removeBehavior method for all elements
269 Element.prototype.removeBehavior = function($cookie) {
270 // mmm, not in a hurry to write this
271 };
272
273 // cache for previously loaded behaviors
274 // -also store some "default" behaviors
275 document.behaviorUrns = {
276 __private : []
277 };
278
279 // support multiple behaviors and ondocumentready
280 window.addEventListener("load", function() {
281 try {
282 var $handlers = document.behaviorUrns.__private;
283 var i = $handlers.length;
284 while (i) $handlers[--i]();
285 delete document.behaviorUrns.__private;
286 } catch ($ignore) {
287 }}, false);
288
289 }]]></constructor></implementation>
290 </binding>
291
292 <binding id="-moz-ie">
293 <implementation><constructor>
294 <![CDATA[if(!this.attachEvent){
295 // ------------------------------------------------------------------
296 // explorer emulation for mozilla
297 // ------------------------------------------------------------------
298
299 // thanks to Erik Arvidsson (http://webfx.eae.net/dhtml/ieemu/)
300
301 /* we're going to mess about with some of mozilla's interfaces to
302 make them more explorer-like
303 */
304
305 /* note: in my comments where i say support/mimic a property
306 support = exactly the same as explorer
307 mimic = close enough
308 */
309
310 // CSSStyleDeclaration
311 // -------------------
312 // support microsoft's styleFloat
313 CSSStyleDeclaration.prototype.__defineGetter__("styleFloat", function() {
314 return this.cssFloat;
315 });
316 CSSStyleDeclaration.prototype.__defineSetter__("styleFloat", function($value) {
317 this.cssFloat = $value;
318 });
319 // mimic microsoft's pixel representations of left/top/width/height
320 // the getters only work for values that are already pixels
321 CSSStyleDeclaration.prototype.__defineGetter__("pixelLeft", function() {
322 return parseInt(this.left) || 0;
323 });
324 CSSStyleDeclaration.prototype.__defineSetter__("pixelLeft", function($value) {
325 this.left = $value + "px";
326 });
327 CSSStyleDeclaration.prototype.__defineGetter__("pixelHeight", function() {
328 return parseInt(this.height) || 0;
329 });
330 CSSStyleDeclaration.prototype.__defineSetter__("pixelHeight", function($value) {
331 this.height = $value + "px";
332 });
333 CSSStyleDeclaration.prototype.__defineGetter__("pixelTop", function() {
334 return parseInt(this.top) || 0;
335 });
336 CSSStyleDeclaration.prototype.__defineSetter__("pixelTop", function($value) {
337 this.top = $value + "px";
338 });
339 CSSStyleDeclaration.prototype.__defineGetter__("pixelWidth", function() {
340 return parseInt(this.width) || 0;
341 });
342 CSSStyleDeclaration.prototype.__defineSetter__("pixelWidth", function($value) {
343 this.width = $value + "px";
344 });
345
346 // for older versions of gecko we need to use getPropertyValue() to
347 // access css properties returned by getComputedStyle().
348 // we don't want this so we fix it.
349 try {
350 var $computedStyle = getComputedStyle(this, null);
351 // the next line will throw an error for some versions of mozilla
352 var $test = $computedStyle.display;
353 } catch ($ignore) {
354 // the previous line will throw an error for some versions of mozilla
355 } finally {
356 if (!$test) {
357 // the above code didn't work so we need to fix CSSStyleDeclaration
358 var $UPPER_CASE = /[A-Z]/g;
359 function _dashLowerCase($match){return "-" + $match.toLowerCase()};
360 function _cssName($propertyName) {return $propertyName.replace($UPPER_CASE, _dashLowerCase)};
361 function _assignStyleGetter($propertyName) {
362 var $cssName = _cssName($propertyName);
363 CSSStyleDeclaration.prototype.__defineGetter__($propertyName, function() {
364 return this.getPropertyValue($cssName);
365 });
366 };
367 for (var $propertyName in this.style) {
368 if (typeof this.style[$propertyName] == "string") {
369 _assignStyleGetter($propertyName);
370 }
371 }
372 }}
373
374 // HTMLDocument
375 // ------------
376 // support microsoft's "all" property
377 HTMLDocument.prototype.__defineGetter__("all", function() {
378 return this.getElementsByTagName("*");
379 });
380 // mimic the "createEventObject" method for the document object
381 HTMLDocument.prototype.createEventObject = function() {
382 return document.createEvent("Events");
383 };
384
385 // HTMLElement
386 // -----------
387 // mimic microsoft's "all" property
388 HTMLElement.prototype.__defineGetter__("all", function() {
389 return this.getElementsByTagName("*");
390 });
391 // support "parentElement"
392 HTMLElement.prototype.__defineGetter__("parentElement", function() {
393 return (this.parentNode == this.ownerDocument) ? null : this.parentNode;
394 });
395 // support "uniqueID"
396 HTMLElement.prototype.__defineGetter__("uniqueID", function() {
397 // a global counter is stored privately as a property of this getter function.
398 // initialise the counter
399 if (!arguments.callee.count) arguments.callee.count = 0;
400 // create the id and increment the counter
401 var $uniqueID = "moz_id" + arguments.callee.count++;
402 // creating a unique id, creates a global reference
403 window[$uniqueID] = this;
404 // we don't want to increment next time, so redefine the getter
405 this.__defineGetter__("uniqueID", function(){return $uniqueID});
406 return $uniqueID;
407 });
408 // mimic microsoft's "currentStyle"
409 HTMLElement.prototype.__defineGetter__("currentStyle", function() {
410 return getComputedStyle(this, null);
411 });
412 // mimic microsoft's "runtimeStyle"
413 HTMLElement.prototype.__defineGetter__("runtimeStyle", function() {
414 //# this doesn't work yet (https://bugzilla.mozilla.org/show_bug.cgi?id=45424)
415 //# return this.ownerDocument.defaultView.getOverrideStyle(this, null);
416 return this.style;
417 });
418 // support "innerText"
419 HTMLElement.prototype.__defineGetter__("innerText", function() {
420 return this.textContent;
421 });
422 HTMLElement.prototype.__defineSetter__("innerText", function($value) {
423 this.textContent = $value;
424 });
425 // mimic the "attachEvent" method
426 HTMLElement.prototype.attachEvent = function($name, $handler) {
427 this.addEventListener($name.slice(2), $handler, false);
428 };
429 // mimic the "removeEvent" method
430 HTMLElement.prototype.removeEvent = function($name, $handler) {
431 this.removeEventListener($name.slice(2), $handler, false);
432 };
433 // mimic the "createEventObject" method
434 HTMLElement.prototype.createEventObject = function() {
435 return this.ownerDocument.createEventObject();
436 };
437 // mimic the "fireEvent" method
438 HTMLElement.prototype.fireEvent = function($name, $event) {
439 if (!$event) $event = this.ownerDocument.createEventObject();
440 $event.initEvent($name.slice(2), false, false);
441 this.dispatchEvent($event);
442 // not sure that this should be here??
443 if (typeof this[$name] == "function") this[$name]();
444 else if (this.getAttribute($name)) eval(this.getAttribute($name));
445 };
446 // support the "contains" method
447 HTMLElement.prototype.contains = function($element) {
448 return Boolean($element == this || ($element && this.contains($element.parentElement)));
449 };
450
451 // Event
452 // -----
453 // support microsoft's proprietary event properties
454 Event.prototype.__defineGetter__("srcElement", function() {
455 return (this.target.nodeType == Node.ELEMENT_NODE) ? this.target : this.target.parentNode;
456 });
457 Event.prototype.__defineGetter__("fromElement",function() {
458 return (this.type == "mouseover") ? this.relatedTarget : (this.type == "mouseout") ? this.srcElement : null;
459 });
460 Event.prototype.__defineGetter__("toElement", function() {
461 return (this.type == "mouseout") ? this.relatedTarget : (this.type == "mouseover") ? this.srcElement : null;
462 });
463 // convert w3c button id's to microsoft's
464 Event.prototype.__defineGetter__("button", function() {
465 return (this.which == 1) ? 1 : (this.which == 2) ? 4 : 2;
466 });
467 // mimic "returnValue" (default is "true")
468 Event.prototype.__defineGetter__("returnValue", function() {
469 return true;
470 });
471 Event.prototype.__defineSetter__("returnValue", function($value) {
472 if (this.cancelable && !$value) {
473 // this can't be undone!
474 this.preventDefault();
475 this.__defineGetter__("returnValue", function() {
476 return false;
477 });
478 }
479 });
480 // mozilla already supports the read-only "cancelBubble"
481 // so we only need to define the setter
482 Event.prototype.__defineSetter__("cancelBubble", function($value) {
483 // this can't be undone!
484 if ($value) this.stopPropagation();
485 });
486 Event.prototype.__defineGetter__("offsetX", function() {
487 return this.layerX;
488 });
489 Event.prototype.__defineGetter__("offsetY", function() {
490 return this.layerY;
491 });
492 // and that's it!
493 // thanks mozilla for being such a developer's playground :D
494 }]]></constructor></implementation>
495 </binding>
496
497 <binding id="block-netscape6">
498 <content>
499 <html:script type="text/javascript"><![CDATA[
500 // netscape6 does not retain the -moz-binding css property value
501 // so we disable moz-behaviors
502 if (/netscape6/i.test(navigator.userAgent)) {
503 document.styleSheets[0].insertRule("*{-moz-binding:none!important}", 0);
504 }
505 ]]></html:script>
506 <children/>
507 </content>
508 </binding>
509
510 </bindings>
511
4. 在htc文件中也要注意一个地方,那就是要使用<![CDATA[ ]]>将javsscript代码括住,像下面这样。
//<![CDATA[
function beginDrag() {
if(true && true)
{
}
};
//]]>
</script>
如果不用它括住会出现很搞笑的事情,true&&true这样的javascript代码会有错,当然浏览器不会提示错误,firebug也无法调试(因为htc中的javascript并不是直接调用该文件执行的,而是由moz-behaviors.xml文件中的javascript翻译后执行的,执行是由与css样式关联的DOM对象的js对象原型prototype进行的),所以像&这样的特殊字符会影响到moz-behaviors.xml文件中的javascript的翻译过程,同时一旦“有错”,htc文件中的任何javascript代码都无法执行,因为整个翻译过程已经被打乱了,我是这样通俗理解的。