|
109 | 109 | /**
|
110 | 110 | * @type {RegExp}
|
111 | 111 | */
|
112 |
| - isNumericString = /^-?\d/; |
| 112 | + isNumericString = /^-?\d/, |
| 113 | + |
| 114 | + /** |
| 115 | + * @type {string} |
| 116 | + */ |
| 117 | + hoverClass = ns + '-hover'; |
113 | 118 |
|
114 | 119 | /**
|
115 | 120 | * Proxy function.
|
|
167 | 172 | return 0;
|
168 | 173 | }
|
169 | 174 |
|
| 175 | + /** |
| 176 | + * Determines whether or not an element is a button input. |
| 177 | + * @param {Element} element |
| 178 | + * @return {boolean} |
| 179 | + */ |
| 180 | + function isButton(element) { |
| 181 | + var tagName, type; |
| 182 | + |
| 183 | + if (!element || !element.nodeName || !element.type) { |
| 184 | + return false; |
| 185 | + } |
| 186 | + |
| 187 | + tagName = element.nodeName.toUpperCase(); |
| 188 | + type = element.type.toLowerCase(); |
| 189 | + |
| 190 | + return (tagName === 'BUTTON' || (tagName === 'INPUT' |
| 191 | + && (type === 'image' || type === 'button' || type === 'submit' || type === 'reset'))); |
| 192 | + } |
| 193 | + |
| 194 | + /** |
| 195 | + * Determines whether or not an element is a text input. |
| 196 | + * @param {Element} element |
| 197 | + * @return {boolean} |
| 198 | + */ |
| 199 | + function isTextField(element) { |
| 200 | + var tagName, type; |
| 201 | + |
| 202 | + if (!element || !element.nodeName) { |
| 203 | + return false; |
| 204 | + } |
| 205 | + |
| 206 | + tagName = element.nodeName.toUpperCase(); |
| 207 | + type = tagName === 'INPUT' ? element.type.toLowerCase() : null; |
| 208 | + |
| 209 | + return ((tagName === 'INPUT' && (element.type === 'text' || element.type === 'password')) |
| 210 | + || tagName === 'TEXTAREA'); |
| 211 | + } |
| 212 | + |
170 | 213 | /**
|
171 | 214 | * Enables VML on the current page.
|
172 | 215 | */
|
|
287 | 330 | var self = this,
|
288 | 331 | property = window.event.propertyName;
|
289 | 332 |
|
290 |
| - //console.log('PROPCHANGE ' + property); |
291 |
| - |
292 | 333 | setTimeout(function () {
|
293 | 334 | self.onPropertyChange.call(self, property);
|
294 | 335 | });
|
|
298 | 339 | var self = this,
|
299 | 340 | eventType = window.event.type;
|
300 | 341 |
|
301 |
| - //console.log('STATECHANGE ' + eventType); |
302 |
| - |
303 | 342 | setTimeout(function () {
|
304 | 343 | self.onStateChange.call(self, eventType);
|
305 | 344 | });
|
306 | 345 | });
|
307 | 346 |
|
308 | 347 | this.onVmlStateChangeProxy = proxy(this, function () {
|
309 |
| - //console.log('VMLSTATECHANGE' + event.type); |
| 348 | + // IE seems to sometimes ditch properties from the event object |
| 349 | + // if we do not create references to them here before passing to |
| 350 | + // the statechange function |
| 351 | + var eventType = window.event.type; |
310 | 352 |
|
311 |
| - this.onVmlStateChange(window.event.type); |
| 353 | + this.onVmlStateChange(eventType); |
312 | 354 | });
|
313 | 355 |
|
314 | 356 | this.events = {
|
|
620 | 662 |
|
621 | 663 | /**
|
622 | 664 | * Breaks references to the DOM to allow Microsoft’s crap GC to GC.
|
| 665 | + * (I am not entirely sure how many of this is actually necessary, |
| 666 | + * since 1. who cares about IE6, 2. sIEve is actually incredibly |
| 667 | + * unreliable at determining leaks, and 3. leaks are fixed in IE8 |
| 668 | + * and will get collected when navigating to another page in IE7. |
| 669 | + * Expert advice is appreciated.) |
623 | 670 | * @param {boolean=} restoreStyles Whether or not to restore inline
|
624 | 671 | * styles from when the element was first run through RoundRect.
|
625 | 672 | */
|
626 | 673 | destroy: function (restoreStyles) {
|
627 | 674 | var id = this.element[expando], i;
|
628 |
| - this.stop(); |
629 | 675 | this.removeEvent();
|
630 | 676 | this.element.removeAttribute(expando);
|
631 | 677 |
|
632 | 678 | if (this.vml) {
|
633 | 679 | for (i in this.vml) {
|
634 | 680 | if (this.vml.hasOwnProperty(i)) {
|
635 | 681 | this.vml[i].filler = null;
|
| 682 | + this.vml[i] = null; |
636 | 683 | }
|
637 | 684 | }
|
638 | 685 | }
|
639 | 686 |
|
640 | 687 | if (this.container) {
|
641 |
| - // IE will leak orphan nodes if we do not empty out the |
642 |
| - // innerHTML before calling removeChild |
643 |
| - this.container.innerHTML = ''; |
644 | 688 | if (this.container && this.container.parentNode) {
|
645 |
| - this.container.parentNode.insertBefore(this.element, this.container); |
646 | 689 | this.container.parentNode.removeChild(this.container);
|
647 | 690 | }
|
648 | 691 | }
|
|
653 | 696 | this.element.style[i] = this.originalStyles[i];
|
654 | 697 | }
|
655 | 698 | }
|
| 699 | + |
| 700 | + this.element.parentNode.style.width = ''; |
656 | 701 | }
|
657 | 702 |
|
| 703 | + this.container = null; |
658 | 704 | this.element = null;
|
659 | 705 | delete collection[id];
|
660 | 706 | },
|
|
710 | 756 | this[method](c, 'click', vcp);
|
711 | 757 | },
|
712 | 758 |
|
| 759 | + /** |
| 760 | + * Add a class to the element referenced by this RoundRect object. |
| 761 | + * @type {string} className |
| 762 | + */ |
| 763 | + addClass: function (className) { |
| 764 | + var oldClassName = ' ' + this.element.className + ' '; |
| 765 | + |
| 766 | + if (oldClassName.indexOf(' ' + className + ' ') === -1) { |
| 767 | + this.element.className += ' ' + className; |
| 768 | + } |
| 769 | + }, |
| 770 | + |
| 771 | + /** |
| 772 | + * Remove a class from the element referenced by this RoundRect object. |
| 773 | + * @type {string} className |
| 774 | + */ |
| 775 | + removeClass: function (className) { |
| 776 | + var oldClassName = ' ' + this.element.className + ' '; |
| 777 | + |
| 778 | + if (oldClassName.indexOf(' ' + className + ' ') !== -1) { |
| 779 | + this.element.className = oldClassName.replace(' ' + ns + '-hover ', ' ').replace(/^\s+|\s+$/g, ''); |
| 780 | + } |
| 781 | + }, |
| 782 | + |
713 | 783 | /**
|
714 | 784 | * Proxy for onVmlStateChange, binds ‘this’. Defined in the
|
715 | 785 | * constructor.
|
|
725 | 795 | * @param {string} eventType
|
726 | 796 | */
|
727 | 797 | onVmlStateChange: function (eventType) {
|
728 |
| - var className = ' ' + this.element.className + ' '; |
729 |
| - |
730 |
| - if (eventType === 'click' |
| 798 | + if (eventType === 'click' && isTextField(this.element)) { |
| 799 | + // With RoundRect applied, text inputs can only be |
| 800 | + // clicked on where text has already been written. This |
| 801 | + // partially works around this issue. It is not perfect: |
| 802 | + // clicking empty lines in textareas, for instance, puts the |
| 803 | + // carat in the wrong place, but it works in most common cases |
| 804 | + // and is much better than the default behaviour. |
| 805 | + var range = this.element.createTextRange(); |
| 806 | + range.moveStart('textedit'); |
| 807 | + range.select(); |
| 808 | + } |
| 809 | + else if (eventType === 'click' && isButton(this.element)) { |
| 810 | + // Much like text fields, clicking on the VML part of a button |
| 811 | + // will not trigger the button click |
| 812 | + this.element.click(); |
| 813 | + } |
| 814 | + else if (eventType === 'click' |
731 | 815 | && document.activeElement !== this.element
|
732 | 816 | && document.activeElement !== document.body) {
|
733 | 817 | document.activeElement.blur();
|
734 | 818 | }
|
735 |
| - else if (eventType === 'mouseover' |
736 |
| - && className.indexOf(' ' + ns + '-hover ') === -1) { |
737 |
| - this.element.className += ' ' + ns + '-hover'; |
| 819 | + else if (eventType === 'mouseover') { |
| 820 | + this.addClass(hoverClass); |
738 | 821 | }
|
739 |
| - else if (eventType === 'mouseout' |
740 |
| - && className.indexOf(' ' + ns + '-hover ') !== -1) { |
741 |
| - this.element.className = className.replace(' ' + ns + '-hover ', ' ').replace(/^\s+|\s+$/g, ''); |
| 822 | + else if (eventType === 'mouseout') { |
| 823 | + this.removeClass(hoverClass); |
742 | 824 | }
|
743 | 825 | },
|
744 | 826 |
|
|
839 | 921 | }
|
840 | 922 | }
|
841 | 923 | else {
|
| 924 | + // Buttons always fail to change their hover states properly; |
| 925 | + // though maybe it is just because it is really slow? |
| 926 | + // TODO: Borders width/colour doesn’t seem to update properly |
| 927 | + // even with this change for some reason. |
| 928 | + if (isButton(this.element)) { |
| 929 | + if (eventType === 'mouseenter') { |
| 930 | + this.addClass(hoverClass); |
| 931 | + } |
| 932 | + else if (eventType === 'mouseleave') { |
| 933 | + this.removeClass(hoverClass); |
| 934 | + } |
| 935 | + } |
| 936 | + |
842 | 937 | this.element.runtimeStyle.cssText = '';
|
843 | 938 | this.dimensions = this.calculateDimensions();
|
844 | 939 | this.borderWidths = this.calculateBorderWidths();
|
|
1007 | 1102 | if (tagName === 'IMG') {
|
1008 | 1103 | e.style.visibility = 'hidden';
|
1009 | 1104 | }
|
1010 |
| - else if (tagName === 'INPUT' || tagName === 'TEXTAREA') { |
1011 |
| - // With RoundRect applied, text inputs can only be |
1012 |
| - // clicked on where text has already been written. This |
1013 |
| - // partially works around this issue. It is not perfect: |
1014 |
| - // clicking empty lines in textareas, for instance, puts the |
1015 |
| - // carat in the wrong place, but it works in most common cases |
1016 |
| - // and is much better than the default behaviour. |
| 1105 | + else if (isTextField(e)) { |
1017 | 1106 | this.container.style.cursor = cs.cursor === 'auto' ? 'text' : cs.cursor;
|
1018 |
| - this.addEvent('container', 'click', proxy(this, function () { |
1019 |
| - var range = this.element.createTextRange(); |
1020 |
| - range.moveStart('textedit'); |
1021 |
| - range.select(); |
1022 |
| - })); |
| 1107 | + } |
| 1108 | + else if (isButton(e)) { |
| 1109 | + this.container.style.cursor = cs.cursor === 'auto' ? 'pointer' : cs.cursor; |
1023 | 1110 | }
|
1024 | 1111 |
|
1025 |
| - // Without a timeout, IE will throw “unspecified errors” |
| 1112 | + // Without a timeout, IE will throw “unspecified error”s |
1026 | 1113 | setTimeout(proxy(this, function () {
|
1027 | 1114 | this.addEvent('container', 'mouseenter', proxy(this, function () {
|
1028 | 1115 | var fakeEvent = document.createEventObject(window.event);
|
|
1041 | 1128 | },
|
1042 | 1129 |
|
1043 | 1130 | /**
|
1044 |
| - * Applies all changes to the VML elements for an element. |
| 1131 | + * Applies all changes to the VML elements for an element. You can call |
| 1132 | + * this if you are not using RoundRect’s dynamic properties |
| 1133 | + * functionality and need to update the style, or if it doesn’t work |
| 1134 | + * properly for some reason. |
1045 | 1135 | */
|
1046 | 1136 | applyVML: function () {
|
1047 | 1137 | // If the element thinks it is invisible, chances are it was
|
|
1102 | 1192 | var dimensions = this.dimensions,
|
1103 | 1193 | i,
|
1104 | 1194 | vml = this.vml,
|
1105 |
| - multiplier; |
| 1195 | + multiplier, |
| 1196 | + parent = this.element.parentNode; |
1106 | 1197 |
|
1107 | 1198 | /**
|
1108 | 1199 | * Copies style properties from the dimensions hash to another
|
|
1127 | 1218 |
|
1128 | 1219 | assign(this.container, false);
|
1129 | 1220 |
|
| 1221 | + // IE7 inappropriately collapses table cells and gives outlandish |
| 1222 | + // values for offsetWidth |
| 1223 | + if (!ie8 && (parent.nodeName.toUpperCase() === 'TD' || parent.nodeName.toUpperCase() === 'TH')) { |
| 1224 | + parent.style.width = ''; |
| 1225 | + if (parent.currentStyle.width === 'auto') { |
| 1226 | + parent.style.width = dimensions.width + 'px'; |
| 1227 | + } |
| 1228 | + } |
| 1229 | + |
1130 | 1230 | // I don’t know what this was *supposed* to do, but it seems to
|
1131 | 1231 | // just fuck up the borders.
|
1132 | 1232 | /*if (ie8) {
|
|
1278 | 1378 | if (imageMap[vmlBg] === undefined) {
|
1279 | 1379 | img = new Image();
|
1280 | 1380 | img.attachEvent('onload', proxy(this, function () {
|
1281 |
| - // img.detachEvent('onload', arguments.callee.callee); |
1282 |
| - |
1283 |
| - // Replace the object in the map with something more |
1284 |
| - // primitive to save memory |
| 1381 | + // Replace the Image object in the map with something |
| 1382 | + // more primitive to save memory |
1285 | 1383 | imageMap[vmlBg] = {
|
1286 | 1384 | width: this.width,
|
1287 | 1385 | height: this.height
|
|
0 commit comments