遠いリポジトリに便利なsvk
第五回目です。
Subversionのlogやdiff、
ただ、
そんな時にはsvk を使う手があります。
svkのmirrorサブコマンドを使うと、
% svk mirror http://svn.rubyonrails.org/rails/spinoffs/prototype //prototype
Committed revision 3764.
% svk sync //prototype
Retrieving log information from 1 to 7693
Committed revision 3765 from revision 3362.
... (ここで途中で失敗する場合は再度 svk sync)
% svk checkout //prototype prototype
...
% cd prototype/tags/rel_1-5-1-1/src
% svk log dom.js | less
という手順で利用できます。
では、
Element への拡張
1290: if (!window.Element) var Element = {};
1291:
まず、
1292: Element.extend = function(element) {
1293: var F = Prototype.BrowserFeatures;
1294: if (!element || !element.tagName || element.nodeType == 3 ||
1295: element._extended || F.SpecificElementExtensions || element == window)
1296: return element;
1297:
1298: var methods = {}, tagName = element.tagName, cache = Element.extend.cache,
1299: T = Element.Methods.ByTag;
1300:
1301: // extend methods for all tags (Safari doesn't need this)
1302: if (!F.ElementExtensions) {
1303: Object.extend(methods, Element.Methods),
1304: Object.extend(methods, Element.Methods.Simulated);
1305: }
1306:
1307: // extend methods for specific tags
1308: if (T[tagName]) Object.extend(methods, T[tagName]);
1309:
1310: for (var property in methods) {
1311: var value = methods[property];
1312: if (typeof value == 'function' && !(property in element))
1313: element[property] = cache.findOrStore(value);
1314: }
1315:
1316: element._extended = Prototype.emptyFunction;
1317: return element;
1318: };
1319:
まずはElement.
昔はElement以下の追加メソッドは、
できるだけ効率的に、
1294行目からは、
条件文 | 付け加える必要がない理由 |
---|---|
!element | そもそも引数elementが無効、 |
!element. |
tagNameプロパティが無いので要素ではない |
element. |
テキストノード(3)は対象外 |
element._extended | すでにこの関数により拡張済み |
F. |
コード最後部のElement. |
element == window | windowは対象外。それ以前にtagNameのチェックで除外されているはず |
1298行目では、
1301行目からは、
グローバルなHTMLElementが定義されている場合、
Element.
あとは、
最後に、
1320: Element.extend.cache = {
1321: findOrStore: function(value) {
1322: return this[value] = this[value] || function() {
1323: return value.apply(null, [this].concat($A(arguments)));
1324: }
1325: }
1326: };
1327:
1320行目からはElement.
findOrStore()関数は、
return this[value] = this[value] || function(){...} という形は、
無名関数の中では、
- ECMA-262 第三版 - 15.
3.4. 3 Function. prototype. apply (thisArg, argArray)
Element.Methodsオブジェクト
1328: Element.Methods = {
1329: visible: function(element) {
1330: return $(element).style.display != 'none';
1331: },
1332:
1333: toggle: function(element) {
1334: element = $(element);
1335: Element[Element.visible(element) ? 'hide' : 'show'](element);
1336: return element;
1337: },
1338:
1339: hide: function(element) {
1340: $(element).style.display = 'none';
1341: return element;
1342: },
1343:
1344: show: function(element) {
1345: $(element).style.display = '';
1346: return element;
1347: },
1348:
1328行目から実際にElementに追加されるメソッド群です。
1329行目のvisible()は要素のstyle.
1333行目のtoggle()は、
1339行目のhide()では、
1344行目のshow()は逆に、
1349: remove: function(element) {
1350: element = $(element);
1351: element.parentNode.removeChild(element);
1352: return element;
1353: },
1354:
1355: update: function(element, html) {
1356: html = typeof html == 'undefined' ? '' : html.toString();
1357: $(element).innerHTML = html.stripScripts();
1358: setTimeout(function() {html.evalScripts()}, 10);
1359: return element;
1360: },
1361:
1349行目からのremove()は、
1355行目からのupdate()では、
1362: replace: function(element, html) {
1363: element = $(element);
1364: html = typeof html == 'undefined' ? '' : html.toString();
1365: if (element.outerHTML) {
1366: element.outerHTML = html.stripScripts();
1367: } else {
1368: var range = element.ownerDocument.createRange();
1369: range.selectNodeContents(element);
1370: element.parentNode.replaceChild(
1371: range.createContextualFragment(html.stripScripts()), element);
1372: }
1373: setTimeout(function() {html.evalScripts()}, 10);
1374: return element;
1375: },
1376:
1362行目からはreplace()メソッドです。
まず引数elementをElement化し、
もしelement.
outerHTMLが使えない場合は、
createContextualFragment()は、
最後に、
- DOM Level 2 Traversal and Range Specification
- DOM Level 2 Traversal and Range Specification 抄訳
- MDC - DOM:range.
createContextualFragment
1377: inspect: function(element) {
1378: element = $(element);
1379: var result = '<' + element.tagName.toLowerCase();
1380: $H({'id': 'id', 'className': 'class'}).each(function(pair) {
1381: var property = pair.first(), attribute = pair.last();
1382: var value = (element[property] || '').toString();
1383: if (value) result += ' ' + attribute + '=' + value.inspect(true);
1384: });
1385: return result + '>';
1386: },
1387:
1377行目からはElement用のinspect()です。
タグの名前と、
まずelementをElement化し、
$H()を使って、
イテレータ関数内では、
valueに値があれば、
ループ後に、
1388: recursivelyCollect: function(element, property) {
1389: element = $(element);
1390: var elements = [];
1391: while (element = element[property])
1392: if (element.nodeType == 1)
1393: elements.push(Element.extend(element));
1394: return elements;
1395: },
1396:
1388行目からはrecursiveCollect()です。主にライブラリ内部から利用されます。
elementをElement化し、
後は、
最後に溜まったelementsを返します。
1397: ancestors: function(element) {
1398: return $(element).recursivelyCollect('parentNode');
1399: },
1400:
1401: descendants: function(element) {
1402: return $A($(element).getElementsByTagName('*')).each(Element.extend);
1403: },
1404:
1397行目からはancestors()です。自らの祖先をたどります。
実装は先ほどのrecursivelyCollect()を'parentNode'を引数として呼び出しているだけです。
1401行目からはdescendants()です。こちらは自分の子孫をすべて列挙します。
elementに対してgetElementsByTagName('*')で、
1405: firstDescendant: function(element) {
1406: element = $(element).firstChild;
1407: while (element && element.nodeType != 1) element = element.nextSibling;
1408: return $(element);
1409: },
1410:
1411: immediateDescendants: function(element) {
1412: if (!(element = $(element).firstChild)) return [];
1413: while (element && element.nodeType != 1) element = element.nextSibling;
1414: if (element) return [element].concat($(element).nextSiblings());
1415: return [];
1416: },
1417:
1405行目からはfirstDescendant()です。
DOMのfirstChildはすべてのノードを対象としますが、
まず、
その後、
nextSiblingを使っているので、
最終的にwhileを抜けるのは、
1411行目からはimmediateDescendants()です。自分の直下の要素をすべて返します。これもコメントやテキストノードは無視して、
まず、
firstDescendat()の時と同様に、
もし要素が見つかったら、
1418: previousSiblings: function(element) {
1419: return $(element).recursivelyCollect('previousSibling');
1420: },
1421:
1422: nextSiblings: function(element) {
1423: return $(element).recursivelyCollect('nextSibling');
1424: },
1425:
1426: siblings: function(element) {
1427: element = $(element);
1428: return element.previousSiblings().reverse().concat(element.nextSiblings());
1429: },
1430:
1418行目からはpreviousSiblings()です。
実装はrecursivelyCollect('previousSibling')を呼び出して返しているだけです。
1422行目からのnextSiblings()も、
1426行目のsiblings()は、
element.
1431: match: function(element, selector) {
1432: if (typeof selector == 'string')
1433: selector = new Selector(selector);
1434: return selector.match($(element));
1435: },
1436:
1431行目はmatch()です。指定されたCSSセレクタにマッチする要素を返します。実際には後述するSelectorクラスのmatch()メソッドとなっています。
1437: up: function(element, expression, index) {
1438: element = $(element);
1439: if (arguments.length == 1) return $(element.parentNode);
1440: var ancestors = element.ancestors();
1441: return expression ? Selector.findElement(ancestors, expression, index) :
1442: ancestors[index || 0];
1443: },
1444:
1445: down: function(element, expression, index) {
1446: element = $(element);
1447: if (arguments.length == 1) return element.firstDescendant();
1448: var descendants = element.descendants();
1449: return expression ? Selector.findElement(descendants, expression, index) :
1450: descendants[index || 0];
1451: },
1452:
1437行目からはup()メソッドです。
引数がelementしか指定されていない場合、
次に、
1445行目からのdown()メソッドは、
後は、
1453: previous: function(element, expression, index) {
1454: element = $(element);
1455: if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
1456: var previousSiblings = element.previousSiblings();
1457: return expression ? Selector.findElement(previousSiblings, expression, index) :
1458: previousSiblings[index || 0];
1459: },
1460:
1461: next: function(element, expression, index) {
1462: element = $(element);
1463: if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
1464: var nextSiblings = element.nextSiblings();
1465: return expression ? Selector.findElement(nextSiblings, expression, index) :
1466: nextSiblings[index || 0];
1467: },
1468:
1453行目からのprevios()は、
expression、
後は、
1461行目からのnext()はprevious()の逆で、
実装としてはprevious()を単純に逆にしていて、
1469: getElementsBySelector: function() {
1470: var args = $A(arguments), element = $(args.shift());
1471: return Selector.findChildElements(element, args);
1472: },
1473:
1469行目はgetElementsBySelector()です。指定された要素以下で、
実際にはSelector.
1474: getElementsByClassName: function(element, className) {
1475: return document.getElementsByClassName(className, element);
1476: },
1477:
1474行目はgetElementsByClassName()です。
実際には、
1478: readAttribute: function(element, name) {
1479: element = $(element);
1480: if (Prototype.Browser.IE) {
1481: if (!element.attributes) return null;
1482: var t = Element._attributeTranslations;
1483: if (t.values[name]) return t.values[name](element, name);
1484: if (t.names[name]) name = t.names[name];
1485: var attribute = element.attributes[name];
1486: return attribute ? attribute.nodeValue : null;
1487: }
1488: return element.getAttribute(name);
1489: },
1490:
1478行目からはreadAttribute()です。基本的にはgetAttribute()と同様ですが、
まず、
1481行目ではelement.
IEの場合、
namesではちょっとした属性名の違いを吸収するためのテーブルとなっており、
あとは、
- Attribute nightmare in IE (Tobie Langel のブログ)
- それに関連した ajaxian への投稿
- Subversion commit r7222
- Trac Ticket 8481
1491: getHeight: function(element) {
1492: return $(element).getDimensions().height;
1493: },
1494:
1495: getWidth: function(element) {
1496: return $(element).getDimensions().width;
1497: },
1498:
1491行目からはgetHeight()、
これらは後述するgetDimensions()を呼び出し、
1499: classNames: function(element) {
1500: return new Element.ClassNames(element);
1501: },
1502:
1499行目はclassNames()です。単にElementClassNames()クラスのインスタンスを作成して返しています。
1503: hasClassName: function(element, className) {
1504: if (!(element = $(element))) return;
1505: var elementClassName = element.className;
1506: if (elementClassName.length == 0) return false;
1507: if (elementClassName == className ||
1508: elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)")))
1509: return true;
1510: return false;
1511: },
1512:
1503行目からはhasClassName()です。要素が指定されたクラス名を持っているかどうかを調べます。
まず、
次に、
後は、
1513: addClassName: function(element, className) {
1514: if (!(element = $(element))) return;
1515: Element.classNames(element).add(className);
1516: return element;
1517: },
1518:
1519: removeClassName: function(element, className) {
1520: if (!(element = $(element))) return;
1521: Element.classNames(element).remove(className);
1522: return element;
1523: },
1524:
1525: toggleClassName: function(element, className) {
1526: if (!(element = $(element))) return;
1527: Element.classNames(element)[element.hasClassName(className) ? 'remove' : 'add'](className);
1528: return element;
1529: },
1530:
1513行目からはaddClassName()です。
まずは渡されたelementが存在しなければundefinedを返して終了します。
次に、
1519行目のremoveClassName()もaddClassName()とほぼ同様です。最後にadd()の代わりにremove()メソッドを呼んでいます。
1525行目からはtoggleClassName()です。クラス名をトグルする、
まず[]の内側でhasClassName()を使って指定されたクラスが要素に指定されているかどうかを確認します。真なら'remove'、
これがclassNames()の返り値に対するメソッド呼び出しとなり、
最後に変更された要素オブジェクトを返します。
1531: observe: function() {
1532: Event.observe.apply(Event, arguments);
1533: return $A(arguments).first();
1534: },
1535:
1531行目からはobserve()関数です。
Event.
この関数が呼び出されるタイミングで、
argumentsには上記のような形で引数が入っているので、
最後にelementを返して終了です。
Array.
1536: stopObserving: function() {
1537: Event.stopObserving.apply(Event, arguments);
1538: return $A(arguments).first();
1539: },
1540:
1536行目からのstopObserving()もobserve()と同様、
1541: // removes whitespace-only text node children
1542: cleanWhitespace: function(element) {
1543: element = $(element);
1544: var node = element.firstChild;
1545: while (node) {
1546: var nextNode = node.nextSibling;
1547: if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
1548: element.removeChild(node);
1549: node = nextNode;
1550: }
1551: return element;
1552: },
1553:
1541行目からのcleanWhitespace()では、
実装は、
途中でnodeTypeが3 (TEXT_
element.
1554: empty: function(element) {
1555: return $(element).innerHTML.blank();
1556: },
1557:
1554行目からはempty()です。
innerHTMLプロパティを読んで、
1558: descendantOf: function(element, ancestor) {
1559: element = $(element), ancestor = $(ancestor);
1560: while (element = element.parentNode)
1561: if (element == ancestor) return true;
1562: return false;
1563: },
1564:
1558行目からはdescendantOf()です。
ancestorに自分の祖先かどうかを確認したい要素を渡します。
whileループでparentNodeがある限りたどっていき、
1565: scrollTo: function(element) {
1566: element = $(element);
1567: var pos = Position.cumulativeOffset(element);
1568: window.scrollTo(pos[0], pos[1]);
1569: return element;
1570: },
1571:
1565行目からはscrollTo()です。
Position.
あとはwindow.
1572: getStyle: function(element, style) {
1573: element = $(element);
1574: style = style == 'float' ? 'cssFloat' : style.camelize();
1575: var value = element.style[style];
1576: if (!value) {
1577: var css = document.defaultView.getComputedStyle(element, null);
1578: value = css ? css[style] : null;
1579: }
1580: if (style == 'opacity') return value ? parseFloat(value) : 1.0;
1581: return value == 'auto' ? null : value;
1582: },
1583:
1572行目からはgetStyle()です。このメソッドはOpera、
まずelementをElement化して、
うまく取れなかった場合、
取得しようとしたスタイルが'opacity'だった場合、
最後に、
1584: getOpacity: function(element) {
1585: return $(element).getStyle('opacity');
1586: },
1587:
1584行目からはgetOpacity()です。
先ほどのgetStyle()を使って取得した値を返しているだけです。
1588: setStyle: function(element, styles, camelized) {
1589: element = $(element);
1590: var elementStyle = element.style;
1591:
1592: for (var property in styles)
1593: if (property == 'opacity') element.setOpacity(styles[property])
1594: else
1595: elementStyle[(property == 'float' || property == 'cssFloat') ?
1596: (elementStyle.styleFloat === undefined ? 'cssFloat' : 'styleFloat') :
1597: (camelized ? property : property.camelize())] = styles[property];
1598:
1599: return element;
1600: },
1601:
1588行目からはsetStyle()です。stylesにはキャメルケースにしたプロパティ名を持つハッシュ形式のオブジェクトになります。
まずelementをElement化するのと、
次にfor (var property in styles)で渡されたオブジェクト内をループします。もしプロパティが'opacity'なら後述するsetOpacity()メソッドを使い、
その際、
また、
1602: setOpacity: function(element, value) {
1603: element = $(element);
1604: element.style.opacity = (value == 1 || value === '') ? '' :
1605: (value < 0.00001) ? 0 : value;
1606: return element;
1607: },
1608:
1602行目からはsetOpacity()です。
指定された値が1が空文字なら空文字を、