昔話
Google MapsやPrototypeライブラリが認知されるにつれ、
それ以前は、
JavaScriptを使ったとしても、
今ではブラウザの UserAgent 文字列やバージョンを見て分岐する方法よりは、
最近は
では、
1609: getDimensions: function(element) {
1610: element = $(element);
1611: var display = $(element).getStyle('display');
1612: if (display != 'none' && display != null) // Safari bug
1613: return {width: element.offsetWidth, height: element.offsetHeight};
1614:
1615: // All *Width and *Height properties give 0 on elements with display none,
1616: // so enable the element temporarily
1617: var els = element.style;
1618: var originalVisibility = els.visibility;
1619: var originalPosition = els.position;
1620: var originalDisplay = els.display;
1621: els.visibility = 'hidden';
1622: els.position = 'absolute';
1623: els.display = 'block';
1624: var originalWidth = element.clientWidth;
1625: var originalHeight = element.clientHeight;
1626: els.display = originalDisplay;
1627: els.position = originalPosition;
1628: els.visibility = originalVisibility;
1629: return {width: originalWidth, height: originalHeight};
1630: },
1631:
1609行目からはgetDimensions()です。
style.
次に、
displayの値によって、
1632: makePositioned: function(element) {
1633: element = $(element);
1634: var pos = Element.getStyle(element, 'position');
1635: if (pos == 'static' || !pos) {
1636: element._madePositioned = true;
1637: element.style.position = 'relative';
1638: // Opera returns the offset relative to the positioning context, when an
1639: // element is position relative but top and left have not been defined
1640: if (window.opera) {
1641: element.style.top = 0;
1642: element.style.left = 0;
1643: }
1644: }
1645: return element;
1646: },
1647:
1648: undoPositioned: function(element) {
1649: element = $(element);
1650: if (element._madePositioned) {
1651: element._madePositioned = undefined;
1652: element.style.position =
1653: element.style.top =
1654: element.style.left =
1655: element.style.bottom =
1656: element.style.right = '';
1657: }
1658: return element;
1659: },
1660:
1632行目からはmakePositioned()です。
style.
'relative'に設定することで、
コメントで言及されているOperaでの問題ですが、
1648行目からはundoPositioned()です。
先ほどmakePositioned()で設定した_madePositionedフラグが設定されていれば元に戻す処理を行い、
戻す際には、
1661: makeClipping: function(element) {
1662: element = $(element);
1663: if (element._overflow) return element;
1664: element._overflow = element.style.overflow || 'auto';
1665: if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden')
1666: element.style.overflow = 'hidden';
1667: return element;
1668: },
1669:
1670: undoClipping: function(element) {
1671: element = $(element);
1672: if (!element._overflow) return element;
1673: element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
1674: element._overflow = null;
1675: return element;
1676: }
1677: };
1678:
1661行目からはmakeClipping()です。
この関数とundoClipping()では、
そして、
次にElement.
そして1670行目からはundoClipping()です。
makeClipping()関数により_overflowプロパティが設定されていなければ、
設定されていれば、
最後に、
1679: Object.extend(Element.Methods, {
1680: childOf: Element.Methods.descendantOf,
1681: childElements: Element.Methods.immediateDescendants
1682: });
1683:
1679行目からはElement.
1684: if (Prototype.Browser.Opera) {
1685: Element.Methods._getStyle = Element.Methods.getStyle;
1686: Element.Methods.getStyle = function(element, style) {
1687: switch(style) {
1688: case 'left':
1689: case 'top':
1690: case 'right':
1691: case 'bottom':
1692: if (Element._getStyle(element, 'position') == 'static') return null;
1693: default: return Element._getStyle(element, style);
1694: }
1695: };
1696: }
ここからは、
まずはOpera用にgetStyle()を再定義します。
元のgetStyleを_getStyleとして保存しておき、
先ほどと似ていますが、
1697: else if (Prototype.Browser.IE) {
1698: Element.Methods.getStyle = function(element, style) {
1699: element = $(element);
1700: style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
1701: var value = element.style[style];
1702: if (!value && element.currentStyle) value = element.currentStyle[style];
1703:
1704: if (style == 'opacity') {
1705: if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
1706: if (value[1]) return parseFloat(value[1]) / 100;
1707: return 1.0;
1708: }
1709:
1710: if (value == 'auto') {
1711: if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))
1712: return element['offset'+style.capitalize()] + 'px';
1713: return null;
1714: }
1715: return value;
1716: };
1717:
IE用の修正として、
1698行目のgetStyle()では、
また、
'opacity'を取得しようとしている場合は、
このalpha()の内側は、
得られた値が'auto'の場合、
1718: Element.Methods.setOpacity = function(element, value) {
1719: element = $(element);
1720: var filter = element.getStyle('filter'), style = element.style;
1721: if (value == 1 || value === '') {
1722: style.filter = filter.replace(/alpha\([^\)]*\)/gi,'');
1723: return element;
1724: } else if (value < 0.00001) value = 0;
1725: style.filter = filter.replace(/alpha\([^\)]*\)/gi, '') +
1726: 'alpha(opacity=' + (value * 100) + ')';
1727: return element;
1728: };
1729:
1718行目からはsetOpacity()の上書きです。
半透明化はIEではfilter: alpha(opacity=<integer>)を使うので、
まず、
もし指定しようとしている値が極端に小さければ0として扱うようにし、
先ほども書きましたが、
1730: // IE is missing .innerHTML support for TABLE-related elements
1731: Element.Methods.update = function(element, html) {
1732: element = $(element);
1733: html = typeof html == 'undefined' ? '' : html.toString();
1734: var tagName = element.tagName.toUpperCase();
1735: if (['THEAD','TBODY','TR','TD'].include(tagName)) {
1736: var div = document.createElement('div');
1737: switch (tagName) {
1738: case 'THEAD':
1739: case 'TBODY':
1740: div.innerHTML = '<table><tbody>' + html.stripScripts() + '</tbody></table>';
1741: depth = 2;
1742: break;
1743: case 'TR':
1744: div.innerHTML = '<table><tbody><tr>' + html.stripScripts() + '</tr></tbody></table>';
1745: depth = 3;
1746: break;
1747: case 'TD':
1748: div.innerHTML = '<table><tbody><tr><td>' + html.stripScripts() + '</td></tr></tbody></table>';
1749: depth = 4;
1750: }
1751: $A(element.childNodes).each(function(node) { element.removeChild(node) });
1752: depth.times(function() { div = div.firstChild });
1753: $A(div.childNodes).each(function(node) { element.appendChild(node) });
1754: } else {
1755: element.innerHTML = html.stripScripts();
1756: }
1757: setTimeout(function() { html.evalScripts() }, 10);
1758: return element;
1759: }
1760: }
1730行目からはupdate()の上書きです。
IEではテーブル関係の一部だけをinnerHTMLで書き換えることができないので、
書き換えようとしている要素のタグ名がthead、
例外対象のタグの場合は、
1751行目で、
最後に、
1761: else if (Prototype.Browser.Gecko) {
1762: Element.Methods.setOpacity = function(element, value) {
1763: element = $(element);
1764: element.style.opacity = (value == 1) ? 0.999999 :
1765: (value === '') ? '' : (value < 0.00001) ? 0 : value;
1766: return element;
1767: };
1768: }
1769:
1761行目からは Gecko
この0.
1770: Element._attributeTranslations = {
1771: names: {
1772: colspan: "colSpan",
1773: rowspan: "rowSpan",
1774: valign: "vAlign",
1775: datetime: "dateTime",
1776: accesskey: "accessKey",
1777: tabindex: "tabIndex",
1778: enctype: "encType",
1779: maxlength: "maxLength",
1780: readonly: "readOnly",
1781: longdesc: "longDesc"
1782: },
1783: values: {
1784: _getAttr: function(element, attribute) {
1785: return element.getAttribute(attribute, 2);
1786: },
1787: _flag: function(element, attribute) {
1788: return $(element).hasAttribute(attribute) ? attribute : null;
1789: },
1790: style: function(element) {
1791: return element.style.cssText.toLowerCase();
1792: },
1793: title: function(element) {
1794: var node = element.getAttributeNode('title');
1795: return node.specified ? node.nodeValue : null;
1796: }
1797: }
1798: };
1799:
1770行目からはElement._attributeTranslationsです。
Element.
namesプロパティに入っているのは、
valuesの方には、
_getAttr()内ではgetAttribute()関数の第二引数に2を渡しています。これは、
_flags()内では、
1800: (function() {
1801: Object.extend(this, {
1802: href: this._getAttr,
1803: src: this._getAttr,
1804: type: this._getAttr,
1805: disabled: this._flag,
1806: checked: this._flag,
1807: readonly: this._flag,
1808: multiple: this._flag
1809: });
1810: }).call(Element._attributeTranslations.values);
1811:
"href"、
Element._attributeTranslations.
1812: Element.Methods.Simulated = {
1813: hasAttribute: function(element, attribute) {
1814: var t = Element._attributeTranslations, node;
1815: attribute = t.names[attribute] || attribute;
1816: node = $(element).getAttributeNode(attribute);
1817: return node && node.specified;
1818: }
1819: };
1820:
Element.
といっても今のところはIE 6, 7用のhasAttribute()関数のみです。Element.
hasAttribute()では、
1821: Element.Methods.ByTag = {};
1822:
1821行目では、
1823: Object.extend(Element, Element.Methods);
1824:
ここまでで用意してきたElement.
1825: if (!Prototype.BrowserFeatures.ElementExtensions &&
1826: document.createElement('div').__proto__) {
1827: window.HTMLElement = {};
1828: window.HTMLElement.prototype = document.createElement('div').__proto__;
1829: Prototype.BrowserFeatures.ElementExtensions = true;
1830: }
1831:
1825行目からは、
div要素の __
1832: Element.hasAttribute = function(element, attribute) {
1833: if (element.hasAttribute) return element.hasAttribute(attribute);
1834: return Element.Methods.Simulated.hasAttribute(element, attribute);
1835: };
1836:
クロスブラウザで使えるhasAttribute()関数です。
Element化した要素は、
1837: Element.addMethods = function(methods) {
1838: var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;
1839:
1840: if (!methods) {
1841: Object.extend(Form, Form.Methods);
1842: Object.extend(Form.Element, Form.Element.Methods);
1843: Object.extend(Element.Methods.ByTag, {
1844: "FORM": Object.clone(Form.Methods),
1845: "INPUT": Object.clone(Form.Element.Methods),
1846: "SELECT": Object.clone(Form.Element.Methods),
1847: "TEXTAREA": Object.clone(Form.Element.Methods)
1848: });
1849: }
1850:
1851: if (arguments.length == 2) {
1852: var tagName = methods;
1853: methods = arguments[1];
1854: }
1855:
1856: if (!tagName) Object.extend(Element.Methods, methods || {});
1857: else {
1858: if (tagName.constructor == Array) tagName.each(extend);
1859: else extend(tagName);
1860: }
1861:
1862: function extend(tagName) {
1863: tagName = tagName.toUpperCase();
1864: if (!Element.Methods.ByTag[tagName])
1865: Element.Methods.ByTag[tagName] = {};
1866: Object.extend(Element.Methods.ByTag[tagName], methods);
1867: }
1868:
1869: function copy(methods, destination, onlyIfAbsent) {
1870: onlyIfAbsent = onlyIfAbsent || false;
1871: var cache = Element.extend.cache;
1872: for (var property in methods) {
1873: var value = methods[property];
1874: if (!onlyIfAbsent || !(property in destination))
1875: destination[property] = cache.findOrStore(value);
1876: }
1877: }
1878:
1879: function findDOMClass(tagName) {
1880: var klass;
1881: var trans = {
1882: "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
1883: "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
1884: "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
1885: "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
1886: "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
1887: "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
1888: "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
1889: "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
1890: "FrameSet", "IFRAME": "IFrame"
1891: };
1892: if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
1893: if (window[klass]) return window[klass];
1894: klass = 'HTML' + tagName + 'Element';
1895: if (window[klass]) return window[klass];
1896: klass = 'HTML' + tagName.capitalize() + 'Element';
1897: if (window[klass]) return window[klass];
1898:
1899: window[klass] = {};
1900: window[klass].prototype = document.createElement(tagName).__proto__;
1901: return window[klass];
1902: }
1903:
1904: if (F.ElementExtensions) {
1905: copy(Element.Methods, HTMLElement.prototype);
1906: copy(Element.Methods.Simulated, HTMLElement.prototype, true);
1907: }
1908:
1909: if (F.SpecificElementExtensions) {
1910: for (var tag in Element.Methods.ByTag) {
1911: var klass = findDOMClass(tag);
1912: if (typeof klass == "undefined") continue;
1913: copy(T[tag], klass.prototype);
1914: }
1915: }
1916:
1917: Object.extend(Element, Element.Methods);
1918: delete Element.ByTag;
1919: };
1920:
ちょっと大きめなElement.
$()でElement化される要素に自動的に追加されるメソッドに、
まずは関数内で短く記述するために、
次に、
addMethods()は、
1856行目で、
もしタグ名が指定されているなら、
1862行目からは、
1869行目からも関数内の関数copy()です。これは1905行目以降で使います。
Element.
次も関数内関数でfindDOMClass()です。
Firefox、
DOMで定義されているHTML Elementインターフェイスには、
その対応表に存在するタグで、
それでもダメならタグ名の部分の先頭だけ大文字にした形"HTMLDivElement"等で試します。
それでも見つからなければ、
ということで、
1904行目からまたaddMethods()のメインの処理に戻ります。実行中のブラウザにwindow.
1909行目からは、
Element.
後は、
また、
最後の delete Element.
1921: var Toggle = { display: Element.toggle };
1922:
1923: /*--------------------------------------------------------------------------*/
1924:
Toggleオブジェクトを定義していますが、
Abstract.Insertion オブジェクト
1925: Abstract.Insertion = function(adjacency) {
1926: this.adjacency = adjacency;
1927: }
1928:
Abstract.
adjacencyには、
1929: Abstract.Insertion.prototype = {
1930: initialize: function(element, content) {
1931: this.element = $(element);
1932: this.content = content.stripScripts();
1933:
1934: if (this.adjacency && this.element.insertAdjacentHTML) {
1935: try {
1936: this.element.insertAdjacentHTML(this.adjacency, this.content);
1937: } catch (e) {
1938: var tagName = this.element.tagName.toUpperCase();
1939: if (['TBODY', 'TR'].include(tagName)) {
1940: this.insertContent(this.contentFromAnonymousTable());
1941: } else {
1942: throw e;
1943: }
1944: }
1945: } else {
1946: this.range = this.element.ownerDocument.createRange();
1947: if (this.initializeRange) this.initializeRange();
1948: this.insertContent([this.range.createContextualFragment(this.content)]);
1949: }
1950:
1951: setTimeout(function() {content.evalScripts()}, 10);
1952: },
1953:
1954: contentFromAnonymousTable: function() {
1955: var div = document.createElement('div');
1956: div.innerHTML = '<table><tbody>' + this.content + '</tbody></table>';
1957: return $A(div.childNodes[0].childNodes[0].childNodes);
1958: }
1959: }
1960:
Abstract.
具体的なInsertion.
Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), { ... }
のような形を取ります。new Abstract.
よって、
Insertion.
もし偽の場合、
insertAdjacentHTML()が使える場合はそれをtry {}し、
最後に、
1954行目からは、
IEでは<td>, <th>タグを頂点とするDOMツリー生成ができないので、
1961: var Insertion = new Object();
1962:
Insertion.*クラス用の入れ物として、
Insertion.Before クラス
1963: Insertion.Before = Class.create();
1964: Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), {
1965: initializeRange: function() {
1966: this.range.setStartBefore(this.element);
1967: },
1968:
1969: insertContent: function(fragments) {
1970: fragments.each((function(fragment) {
1971: this.element.parentNode.insertBefore(fragment, this.element);
1972: }).bind(this));
1973: }
1974: });
1975:
1963行目からは Insertion.
Insertion.* 各クラスで拡張しているのは、
initializeRange()は、
Insertion.
1969行目からのinsertContent()では、
Insertion.Top クラス
1976: Insertion.Top = Class.create();
1977: Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), {
1978: initializeRange: function() {
1979: this.range.selectNodeContents(this.element);
1980: this.range.collapse(true);
1981: },
1982:
1983: insertContent: function(fragments) {
1984: fragments.reverse(false).each((function(fragment) {
1985: this.element.insertBefore(fragment, this.element.firstChild);
1986: }).bind(this));
1987: }
1988: });
1989:
1976行目からはInsertion.
基本的な構造はInsertion.
initializeRange()では、
insertContent()の方は、
Insertion.Bottomクラス
1990: Insertion.Bottom = Class.create();
1991: Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), {
1992: initializeRange: function() {
1993: this.range.selectNodeContents(this.element);
1994: this.range.collapse(this.element);
1995: },
1996:
1997: insertContent: function(fragments) {
1998: fragments.each((function(fragment) {
1999: this.element.appendChild(fragment);
2000: }).bind(this));
2001: }
2002: });
2003:
1990行目からはInsertion.
initializeRange()はInsertion.
insertContent()では、
Insertion.After クラス
2004: Insertion.After = Class.create();
2005: Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), {
2006: initializeRange: function() {
2007: this.range.setStartAfter(this.element);
2008: },
2009:
2010: insertContent: function(fragments) {
2011: fragments.each((function(fragment) {
2012: this.element.parentNode.insertBefore(fragment,
2013: this.element.nextSibling);
2014: }).bind(this));
2015: }
2016: });
2017:
2018: /*--------------------------------------------------------------------------*/
2019:
2004行目からはInsertion.
initializeRange()では、
insertContent()では、
elementのnextSiblingが存在しない場合、
Element.ClassNames
2020: Element.ClassNames = Class.create();
ある要素が持つ、
2021: Element.ClassNames.prototype = {
2022: initialize: function(element) {
2023: this.element = $(element);
2024: },
2025:
コンストラクタです。渡されたelementをthis.
2026: _each: function(iterator) {
2027: this.element.className.split(/\s+/).select(function(name) {
2028: return name.length > 0;
2029: })._each(iterator);
2030: },
2031:
Enumerable.
これにより、
2032: set: function(className) {
2033: this.element.className = className;
2034: },
2035:
2032行目からはset()です。これは単に指定された文字列をclassNameプロパティにセットします。事前にクラスが指定されていたとしても、
2036: add: function(classNameToAdd) {
2037: if (this.include(classNameToAdd)) return;
2038: this.set($A(this).concat(classNameToAdd).join(' '));
2039: },
2040:
add()メソッドでは、
$A(this)すると Enumerable.
2041: remove: function(classNameToRemove) {
2042: if (!this.include(classNameToRemove)) return;
2043: this.set($A(this).without(classNameToRemove).join(' '));
2044: },
2045:
2041行目からはremove()メソッドです。
this.
含まれていれば、
2046: toString: function() {
2047: return $A(this).join(' ');
2048: }
2049: };
2050:
toString()では、
2051: Object.extend(Element.ClassNames.prototype, Enumerable);
最後にElement.