| 1401 | | |
| 1402 | | /** |
| 1403 | | * A handy, and fast, way to traverse in a particular direction and find |
| 1404 | | * a specific element. |
| 1405 | | * |
| 1406 | | * @private |
| 1407 | | * @name $.nth |
| 1408 | | * @type DOMElement |
| 1409 | | * @param DOMElement cur The element to search from. |
| 1410 | | * @param Number|String num The Nth result to match. Can be a number or a string (like 'even' or 'odd'). |
| 1411 | | * @param String dir The direction to move in (pass in something like 'previousSibling' or 'nextSibling'). |
| 1412 | | * @cat DOM/Traversing |
| 1413 | | */ |
| 1414 | | nth: function(cur,result,dir){ |
| 1415 | | result = result || 1; |
| 1416 | | var num = 0; |
| 1417 | | for ( ; cur; cur = cur[dir] ) { |
| 1418 | | if ( cur.nodeType == 1 ) num++; |
| 1419 | | if ( num == result || result == "even" && num % 2 == 0 && num > 1 || |
| 1420 | | result == "odd" && num % 2 == 1 ) return cur; |
| 1421 | | } |
| 1422 | | }, |
| 1423 | | |
| 1424 | | expr: { |
| 1425 | | "": "m[2]== '*'||a.nodeName.toUpperCase()==m[2].toUpperCase()", |
| 1426 | | "#": "a.getAttribute('id')==m[2]", |
| 1427 | | ":": { |
| 1428 | | // Position Checks |
| 1429 | | lt: "i<m[3]-0", |
| 1430 | | gt: "i>m[3]-0", |
| 1431 | | nth: "m[3]-0==i", |
| 1432 | | eq: "m[3]-0==i", |
| 1433 | | first: "i==0", |
| 1434 | | last: "i==r.length-1", |
| 1435 | | even: "i%2==0", |
| 1436 | | odd: "i%2", |
| 1437 | | |
| 1438 | | // Child Checks |
| 1439 | | "nth-child": "jQuery.nth(a.parentNode.firstChild,m[3],'nextSibling')==a", |
| 1440 | | "first-child": "jQuery.nth(a.parentNode.firstChild,1,'nextSibling')==a", |
| 1441 | | "last-child": "jQuery.nth(a.parentNode.lastChild,1,'previousSibling')==a", |
| 1442 | | "only-child": "jQuery.sibling(a.parentNode.firstChild).length==1", |
| 1443 | | |
| 1444 | | // Parent Checks |
| 1445 | | parent: "a.childNodes.length", |
| 1446 | | empty: "!a.childNodes.length", |
| 1447 | | |
| 1448 | | // Text Check |
| 1449 | | contains: "jQuery.fn.text.apply([a]).indexOf(m[3])>=0", |
| 1450 | | |
| 1451 | | // Visibility |
| 1452 | | visible: "a.type!='hidden'&&jQuery.css(a,'display')!='none'&&jQuery.css(a,'visibility')!='hidden'", |
| 1453 | | hidden: "a.type=='hidden'||jQuery.css(a,'display')=='none'||jQuery.css(a,'visibility')=='hidden'", |
| 1454 | | |
| 1455 | | // Form attributes |
| 1456 | | enabled: "!a.disabled", |
| 1457 | | disabled: "a.disabled", |
| 1458 | | checked: "a.checked", |
| 1459 | | selected: "a.selected || jQuery.attr(a, 'selected')", |
| 1460 | | |
| 1461 | | // Form elements |
| 1462 | | text: "a.type=='text'", |
| 1463 | | radio: "a.type=='radio'", |
| 1464 | | checkbox: "a.type=='checkbox'", |
| 1465 | | file: "a.type=='file'", |
| 1466 | | password: "a.type=='password'", |
| 1467 | | submit: "a.type=='submit'", |
| 1468 | | image: "a.type=='image'", |
| 1469 | | reset: "a.type=='reset'", |
| 1470 | | button: "a.type=='button'||a.nodeName=='BUTTON'", |
| 1471 | | input: "/input|select|textarea|button/i.test(a.nodeName)" |
| 1472 | | }, |
| 1473 | | ".": "jQuery.className.has(a,m[2])", |
| 1474 | | "@": { |
| 1475 | | "=": "z==m[4]", |
| 1476 | | "!=": "z!=m[4]", |
| 1477 | | "^=": "z && !z.indexOf(m[4])", |
| 1478 | | "$=": "z && z.substr(z.length - m[4].length,m[4].length)==m[4]", |
| 1479 | | "*=": "z && z.indexOf(m[4])>=0", |
| 1480 | | "": "z", |
| 1481 | | _resort: function(m){ |
| 1482 | | return ["", m[1], m[3], m[2], m[5]]; |
| 1483 | | }, |
| 1484 | | _prefix: "z=jQuery.attr(a,m[3]);" |
| 1485 | | }, |
| 1486 | | "[": "jQuery.find(m[2],a).length" |
| 1487 | | }, |
| 1488 | | |
| 1489 | | /** |
| 1490 | | * All elements on a specified axis. |
| 1491 | | * |
| 1492 | | * @private |
| 1493 | | * @name $.sibling |
| 1494 | | * @type Array |
| 1495 | | * @param Element elem The element to find all the siblings of (including itself). |
| 1496 | | * @cat DOM/Traversing |
| 1497 | | */ |
| 1498 | | sibling: function( n, elem ) { |
| 1499 | | var r = []; |
| 1500 | | |
| 1501 | | for ( ; n; n = n.nextSibling ) { |
| 1502 | | if ( n.nodeType == 1 && (!elem || n != elem) ) |
| 1503 | | r.push( n ); |
| 1504 | | } |
| 1505 | | |
| 1506 | | return r; |
| 1507 | | }, |
| 1508 | | |
| 1509 | | token: [ |
| 1510 | | "\\.\\.|/\\.\\.", "a.parentNode", |
| 1511 | | ">|/", "jQuery.sibling(a.firstChild)", |
| 1512 | | "\\+", "jQuery.nth(a,2,'nextSibling')", |
| 1513 | | "~", function(a){ |
| 1514 | | var s = jQuery.sibling(a.parentNode.firstChild); |
| 1515 | | return s.slice(0, jQuery.inArray(a,s)); |
| 1516 | | } |
| 1517 | | ], |
| 1518 | | |
| 1519 | | /** |
| 1520 | | * @name $.find |
| 1521 | | * @type Array<Element> |
| 1522 | | * @private |
| 1523 | | * @cat Core |
| 1524 | | */ |
| 1525 | | find: function( t, context ) { |
| 1526 | | // Quickly handle non-string expressions |
| 1527 | | if ( typeof t != "string" ) |
| 1528 | | return [ t ]; |
| 1529 | | |
| 1530 | | // Make sure that the context is a DOM Element |
| 1531 | | if ( context && context.nodeType == undefined ) |
| 1532 | | context = null; |
| 1533 | | |
| 1534 | | // Set the correct context (if none is provided) |
| 1535 | | context = context || document; |
| 1536 | | |
| 1537 | | // Handle the common XPath // expression |
| 1538 | | if ( !t.indexOf("//") ) { |
| 1539 | | context = context.documentElement; |
| 1540 | | t = t.substr(2,t.length); |
| 1541 | | |
| 1542 | | // And the / root expression |
| 1543 | | } else if ( !t.indexOf("/") ) { |
| 1544 | | context = context.documentElement; |
| 1545 | | t = t.substr(1,t.length); |
| 1546 | | if ( t.indexOf("/") >= 1 ) |
| 1547 | | t = t.substr(t.indexOf("/"),t.length); |
| 1548 | | } |
| 1549 | | |
| 1550 | | // Initialize the search |
| 1551 | | var ret = [context], done = [], last = null; |
| 1552 | | |
| 1553 | | // Continue while a selector expression exists, and while |
| 1554 | | // we're no longer looping upon ourselves |
| 1555 | | while ( t && last != t ) { |
| 1556 | | var r = []; |
| 1557 | | last = t; |
| 1558 | | |
| 1559 | | t = jQuery.trim(t).replace( /^\/\//i, "" ); |
| 1560 | | |
| 1561 | | var foundToken = false; |
| 1562 | | |
| 1563 | | // An attempt at speeding up child selectors that |
| 1564 | | // point to a specific element tag |
| 1565 | | var re = /^[\/>]\s*([a-z0-9*-]+)/i; |
| 1566 | | var m = re.exec(t); |
| 1567 | | |
| 1568 | | if ( m ) { |
| 1569 | | // Perform our own iteration and filter |
| 1570 | | for ( var i = 0, rl = ret.length; i < rl; i++ ) |
| 1571 | | for ( var c = ret[i].firstChild; c; c = c.nextSibling ) |
| 1572 | | if ( c.nodeType == 1 && ( c.nodeName == m[1].toUpperCase() || m[1] == "*" ) ) |
| 1573 | | r.push( c ); |
| 1574 | | |
| 1575 | | ret = r; |
| 1576 | | t = jQuery.trim( t.replace( re, "" ) ); |
| 1577 | | foundToken = true; |
| 1578 | | } else { |
| 1579 | | // Look for pre-defined expression tokens |
| 1580 | | for ( var i = 0; i < jQuery.token.length; i += 2 ) { |
| 1581 | | // Attempt to match each, individual, token in |
| 1582 | | // the specified order |
| 1583 | | var re = new RegExp("^(" + jQuery.token[i] + ")"); |
| 1584 | | var m = re.exec(t); |
| 1585 | | |
| 1586 | | // If the token match was found |
| 1587 | | if ( m ) { |
| 1588 | | // Map it against the token's handler |
| 1589 | | r = ret = jQuery.map( ret, jQuery.token[i+1].constructor == Function ? |
| 1590 | | jQuery.token[i+1] : |
| 1591 | | function(a){ return eval(jQuery.token[i+1]); }); |
| 1592 | | |
| 1593 | | // And remove the token |
| 1594 | | t = jQuery.trim( t.replace( re, "" ) ); |
| 1595 | | foundToken = true; |
| 1596 | | break; |
| 1597 | | } |
| 1598 | | } |
| 1599 | | } |
| 1600 | | |
| 1601 | | // See if there's still an expression, and that we haven't already |
| 1602 | | // matched a token |
| 1603 | | if ( t && !foundToken ) { |
| 1604 | | // Handle multiple expressions |
| 1605 | | if ( !t.indexOf(",") || !t.indexOf("|") ) { |
| 1606 | | // Clean teh result set |
| 1607 | | if ( ret[0] == context ) ret.shift(); |
| 1608 | | |
| 1609 | | // Merge the result sets |
| 1610 | | jQuery.merge( done, ret ); |
| 1611 | | |
| 1612 | | // Reset the context |
| 1613 | | r = ret = [context]; |
| 1614 | | |
| 1615 | | // Touch up the selector string |
| 1616 | | t = " " + t.substr(1,t.length); |
| 1617 | | |
| 1618 | | } else { |
| 1619 | | // Optomize for the case nodeName#idName |
| 1620 | | var re2 = /^([a-z0-9_-]+)(#)([a-z0-9\\*_-]*)/i; |
| 1621 | | var m = re2.exec(t); |
| 1622 | | |
| 1623 | | // Re-organize the results, so that they're consistent |
| 1624 | | if ( m ) { |
| 1625 | | m = [ 0, m[2], m[3], m[1] ]; |
| 1626 | | |
| 1627 | | } else { |
| 1628 | | // Otherwise, do a traditional filter check for |
| 1629 | | // ID, class, and element selectors |
| 1630 | | re2 = /^([#.]?)([a-z0-9\\*_-]*)/i; |
| 1631 | | m = re2.exec(t); |
| 1632 | | } |
| 1633 | | |
| 1634 | | // Try to do a global search by ID, where we can |
| 1635 | | if ( m[1] == "#" && ret[ret.length-1].getElementById ) { |
| 1636 | | // Optimization for HTML document case |
| 1637 | | var oid = ret[ret.length-1].getElementById(m[2]); |
| 1638 | | |
| 1639 | | // Do a quick check for node name (where applicable) so |
| 1640 | | // that div#foo searches will be really fast |
| 1641 | | ret = r = oid && |
| 1642 | | (!m[3] || oid.nodeName == m[3].toUpperCase()) ? [oid] : []; |
| 1643 | | |
| 1644 | | // Use the DOM 0 shortcut for the body element |
| 1645 | | } else if ( m[1] == "" && m[2] == "body" ) { |
| 1646 | | ret = r = [ document.body ]; |
| 1647 | | |
| 1648 | | } else { |
| 1649 | | // Pre-compile a regular expression to handle class searches |
| 1650 | | if ( m[1] == "." ) |
| 1651 | | var rec = new RegExp("(^|\\s)" + m[2] + "(\\s|$)"); |
| 1652 | | |
| 1653 | | // We need to find all descendant elements, it is more |
| 1654 | | // efficient to use getAll() when we are already further down |
| 1655 | | // the tree - we try to recognize that here |
| 1656 | | for ( var i = 0, rl = ret.length; i < rl; i++ ) |
| 1657 | | jQuery.merge( r, |
| 1658 | | m[1] != "" && ret.length != 1 ? |
| 1659 | | jQuery.getAll( ret[i], [], m[1], m[2], rec ) : |
| 1660 | | ret[i].getElementsByTagName( m[1] != "" || m[0] == "" ? "*" : m[2] ) |
| 1661 | | ); |
| 1662 | | |
| 1663 | | // It's faster to filter by class and be done with it |
| 1664 | | if ( m[1] == "." && ret.length == 1 ) |
| 1665 | | r = jQuery.grep( r, function(e) { |
| 1666 | | return rec.test(e.className); |
| 1667 | | }); |
| 1668 | | |
| 1669 | | // Same with ID filtering |
| 1670 | | if ( m[1] == "#" && ret.length == 1 ) { |
| 1671 | | // Remember, then wipe out, the result set |
| 1672 | | var tmp = r; |
| 1673 | | r = []; |
| 1674 | | |
| 1675 | | // Then try to find the element with the ID |
| 1676 | | for ( var i = 0, tl = tmp.length; i < tl; i++ ) |
| 1677 | | if ( tmp[i].getAttribute("id") == m[2] ) { |
| 1678 | | r = [ tmp[i] ]; |
| 1679 | | break; |
| 1680 | | } |
| 1681 | | } |
| 1682 | | |
| 1683 | | ret = r; |
| 1684 | | } |
| 1685 | | |
| 1686 | | t = t.replace( re2, "" ); |
| 1687 | | } |
| 1688 | | |
| 1689 | | } |
| 1690 | | |
| 1691 | | // If a selector string still exists |
| 1692 | | if ( t ) { |
| 1693 | | // Attempt to filter it |
| 1694 | | var val = jQuery.filter(t,r); |
| 1695 | | ret = r = val.r; |
| 1696 | | t = jQuery.trim(val.t); |
| 1697 | | } |
| 1698 | | } |
| 1699 | | |
| 1700 | | // Remove the root context |
| 1701 | | if ( ret && ret[0] == context ) ret.shift(); |
| 1702 | | |
| 1703 | | // And combine the results |
| 1704 | | jQuery.merge( done, ret ); |
| 1705 | | |
| 1706 | | return done; |
| 1707 | | }, |
| 1708 | | |
| 1709 | | getAll: function( o, r, token, name, re ) { |
| 1710 | | for ( var s = o.firstChild; s; s = s.nextSibling ) |
| 1711 | | if ( s.nodeType == 1 ) { |
| 1712 | | var add = true; |
| 1713 | | |
| 1714 | | if ( token == "." ) |
| 1715 | | add = s.className && re.test(s.className); |
| 1716 | | else if ( token == "#" ) |
| 1717 | | add = s.getAttribute('id') == name; |
| 1784 | | |
| 1785 | | // The regular expressions that power the parsing engine |
| 1786 | | parse: [ |
| 1787 | | // Match: [@value='test'], [@foo] |
| 1788 | | "\\[ *(@)S *([!*$^=]*) *('?\"?)(.*?)\\4 *\\]", |
| 1789 | | |
| 1790 | | // Match: [div], [div p] |
| 1791 | | "(\\[)\\s*(.*?)\\s*\\]", |
| 1792 | | |
| 1793 | | // Match: :contains('foo') |
| 1794 | | "(:)S\\(\"?'?([^\\)]*?)\"?'?\\)", |
| 1795 | | |
| 1796 | | // Match: :even, :last-chlid |
| 1797 | | "([:.#]*)S" |
| 1798 | | ], |
| 1799 | | |
| 1800 | | filter: function(t,r,not) { |
| 1801 | | // Look for common filter expressions |
| 1802 | | while ( t && /^[a-z[({<*:.#]/i.test(t) ) { |
| 1803 | | |
| 1804 | | var p = jQuery.parse; |
| 1805 | | |
| 1806 | | for ( var i = 0, pl = p.length; i < pl; i++ ) { |
| 1807 | | |
| 1808 | | // Look for, and replace, string-like sequences |
| 1809 | | // and finally build a regexp out of it |
| 1810 | | var re = new RegExp( |
| 1811 | | "^" + p[i].replace("S", "([a-z*_-][a-z0-9_-]*)"), "i" ); |
| 1812 | | |
| 1813 | | var m = re.exec( t ); |
| 1814 | | |
| 1815 | | if ( m ) { |
| 1816 | | // Re-organize the first match |
| 1817 | | if ( jQuery.expr[ m[1] ]._resort ) |
| 1818 | | m = jQuery.expr[ m[1] ]._resort( m ); |
| 1819 | | |
| 1820 | | // Remove what we just matched |
| 1821 | | t = t.replace( re, "" ); |
| 1822 | | |
| 1823 | | break; |
| 1824 | | } |
| 1825 | | } |
| 1826 | | |
| 1827 | | // :not() is a special case that can be optimized by |
| 1828 | | // keeping it out of the expression list |
| 1829 | | if ( m[1] == ":" && m[2] == "not" ) |
| 1830 | | r = jQuery.filter(m[3], r, true).r; |
| 1831 | | |
| 1832 | | // Handle classes as a special case (this will help to |
| 1833 | | // improve the speed, as the regexp will only be compiled once) |
| 1834 | | else if ( m[1] == "." ) { |
| 1835 | | |
| 1836 | | var re = new RegExp("(^|\\s)" + m[2] + "(\\s|$)"); |
| 1837 | | r = jQuery.grep( r, function(e){ |
| 1838 | | return re.test(e.className || ''); |
| 1839 | | }, not); |
| 1840 | | |
| 1841 | | // Otherwise, find the expression to execute |
| 1842 | | } else { |
| 1843 | | var f = jQuery.expr[m[1]]; |
| 1844 | | if ( typeof f != "string" ) |
| 1845 | | f = jQuery.expr[m[1]][m[2]]; |
| 1846 | | |
| 1847 | | // Build a custom macro to enclose it |
| 1848 | | eval("f = function(a,i){" + |
| 1849 | | ( jQuery.expr[ m[1] ]._prefix || "" ) + |
| 1850 | | "return " + f + "}"); |
| 1851 | | |
| 1852 | | // Execute it against the current filter |
| 1853 | | r = jQuery.grep( r, f, not ); |
| 1854 | | } |
| 1855 | | } |
| 1856 | | |
| 1857 | | // Return an array of filtered elements (r) |
| 1858 | | // and the modified expression string (t) |
| 1859 | | return { r: r, t: t }; |
| 1860 | | }, |
| 1861 | | |
| | 1455 | |