分类目录归档:jQuery

IE6中select元素z-index的bug

通常的情况下,IE6中的select元素永远是在最上面的,即使覆盖在它上面的元素的z-index再大也不起作用。比如说有个id为div1的元素绝对定位在select元素的上面。这里假设div1的z-index比select的大,但在IE6里看上去还是select在上面,div1并不能覆盖住它。为了解决这个问题,只能在div1和select中间插入一个和div1的位置以及大小一样的iframe来达到覆盖select的效果。

网上有个叫bgiframe的jQuery插件可以达到这个效果。拿上面的例子来说,它动态的在div1里插入一个透明的iframe,iframe的尺寸是用IE特有的CSS expression计算的,这么做的好处在于iframe随时都可以保持和div1同样大小,缺点是CSS expression太占资源了,可能会导致浏览器假死,如果覆盖在select上的元素的尺寸在运行时不会改变的话,就没必要用CSS expression。所以把bgiframe的代码改了一下。

改过之后的代码,记录一下:

(function($){
  $.fn.bgIframe = $.fn.bgiframe = function(s) {
    if ($.browser.msie && /6.0/.test(navigator.userAgent)) {
      s = $.extend({
        top    : 'auto',
        left    : 'auto',
        width   : 'auto',
        height  : 'auto',
        src     : 'javascript:false;'
      }, s || {});
      var prop = function(n) {
        return n&&n.constructor==Number?n+'px':n;
      };
      return this.each(function() {
        if ( $('> iframe.bgiframe', this).length == 0 ) {
          var iframe = $('<iframe frameborder="0" tabindex="-1"></iframe>')
          .addClass("bgiframe")
          .css({
            display: 'block',
            position: 'absolute',
            zIndex: '-1',
            opacity: 0,
            top: s.top === 'auto'?
              ((this.clientTop || 0)*-1 + 'px'): prop(s.top),
            left: s.left === 'auto'?
              ((this.clientLeft || 0)*-1 + 'px'): prop(s.left),
            width: s.width === 'auto'?
              (this.offsetWidth + 'px'): prop(s.width),
            height: s.height === 'auto'?
              (this.offsetHeight + 'px'): prop(s.height)
          })
          .insertBefore(this.firstChild);
        }
      });
    }
    return this;
  };
})(jQuery);

星级评价控件(jQuery版)

前几天写一个打分的UI,没有实现功能,现在把功能加入进来,并且用jQuery封装一下。

演示地址:http://www.zhoumingzhi.com/wp-content/uploads/2010/01/rating/demo.html

下载地址:http://code.google.com/p/rating-widget/downloads/list

用法:

$(function () {
    $(".ui-rating").rating({
        "activate": 7,
        "total": 10,
        "select": function (event, ui) {
        },
        "change": function (event, ui) {
        }
    });
});

用一个空的块元素标签实例化即可,最好是用div。

参数说明:

参数 类型 说明
total number/string 级别总数,通常来说就是指星星的个数,除非是用半个星星表示一个等级。
activate number/string 当前选中的级别,一般用来指示所有用户选的平均值。
select function(event, ui) 处理点击事件的函数。event参数代表事件,ui.level表示选中的级别。
change function(event, ui) 这个函数在鼠标移动时被触发。event参数代表事件,ui.level表示鼠标划过的级别。

方法说明:

$(".ui-rating").rating("option", "activate", 5);
$(".ui-rating").rating("enable");
$(".ui-rating").rating("disable");

activate方法有一个参数,表示要设定的等级。

扩展jQuery选择器


jQuery本身提供了一些强大的带有冒号的选择器,比如:first, :even这些。但是这些还满足不了需求的话,可以自己扩展一个选择器。比如要选择一些文本为”hello world”的链接,用jQuery自带的:contain是可以,但是它会把”hello world, I’m Michael”也选择进来,不够精确。于是我们就自己定义一个:text

$.extend($.expr[':'], {
    text: function(a, i, m) {
        return ((a.textContent
        || a.innerText
        || jQuery(a).text()
        || "") === m[3]);
    }
});

用$(“a:text(‘hello world’)”)来调用上面的方法,函数里的m[3]就是输入的参数,在这里为”hello world”。

测试一下:猛击我

  • hello world
  • hello world
  • hello world, I’m Michael.
  • hello world
  • hello world, I’m Leo.

跨域请求的iframe解决方案(2)

将上一篇文章中的代码封装一下,基于jQuery。

用法:

// xs的意思是cross site
$.xsget({
  url: "http://127.0.0.1/server.html",
  callback: function (data) {
    alert(data);
  }
});

源代码:

(function ($) {
  $.extend({
    "xsget": function (options) {
      $.extend(options, $.xsget.defaults);
      var iframe = document.createElement("iframe"),
                same_domain = false;
      iframe.style.display = "none";
      document.body.appendChild(iframe);
      // 当iframe加载完之后触发的函数
      function iframe_load() {
        if (same_domain) {
          // 调用回调函数
          if (typeof options.callback === "function") {
            options.callback(iframe.contentWindow.name);
          }
          // 关闭iframe的窗口
          iframe.contentWindow.close();
          // 移除iframe
          document.body.removeChild(iframe);
        } else {
          same_domain = true;
          iframe.contentWindow.location = options.proxyUrl;
        }
      }
      // 在IE下要用attachEvent来添加iframe的onload事件
      if (iframe.attachEvent) {
        iframe.attachEvent("onload", function () {
          iframe_load();
        });
      }
      else {
        iframe.onload = iframe_load;
      }
      iframe.src = options.url;
    }
  });
  $.extend($.xsget, {
    "defaults": {
      // 默认的空白页面,在网站的根目录下
      proxyUrl: "/empty.html"
    }
  });
})(jQuery);

跨域请求的iframe解决方案(1)

跨域的解决方案有许多种,就不一一介绍了,在这里主要总结一下用iframe来解决跨域的方法。

首先,说明一下window.name这个属性,我们要用这个属性来保存从服务器返回的数据。

  1. window.name的值在同一浏览器窗口加载不同的页面后依然存在。(比如在同一浏览器窗口下,先访问页面a.html,将window.name设定为”Hello World”,然后再访问b.html,window.name仍然是”Hello World”。)
  2. window.name最多可以支持2MB的值。

接下来结合代码来说明这个解决方案。为了实现跨域访问,当然要找到两个不同的域,最简单的,用http://localhost/和http://127.0.0.1/就可以了。新建一个client.html并假定其在http://localhost/这个域下面,用来向http://127.0.0.1/域下的页面server.html发送异步请求。下面是发送请求的代码:

var iframe1 = document.createElement("iframe");
iframe1.src = "http://127.0.0.1/server.html";
(function() {
	// 当iframe加载完之后触发的函数
	function iframe1_load() {					
	}
	// 在IE下要用attachEvent来添加iframe的onload事件
	if(iframe1.attachEvent) {
		iframe1.attachEvent("onload", function(){ 
			iframe1_load(); 
		});
	}
	else {
		iframe1.onload = iframe1_load;
	}
})();
document.body.appendChild(iframe1);

注意这里的iframe1_load函数,它在iframe1完全加载完之后被浏览器调用,我们在这里取回从服务器返回的数据。上面说过,从服务器返回的数据应该是保存在window.name里的,改写一下iframe1_load:

function iframe1_load() {
	alert(iframe1.contentWindow.name);
}

但由于同源策略的原因,上面的语句会报错,因为iframe1访问的页面在http://127.0.0.1/这个域,而这上面的JavaScript所在的页面是在http://localhost/,所以client.html暂时还不能访问iframe1的大部分属性。这个时候聪明的同学就能想到,如果把iframe1导航到http://localhost/下面的页面不就可以访问window.name了吗?而且上面也说过window.name在换了页面之后,还是存在的。

根据上面的分析,在http://localhost/下建一个新的页面empty.html(不用往里添加代码,只要保证不404就好。),在iframe1_load函数中先把iframe1导航到http://localhost/empty.html,然后再从iframe1的window.name里取数据。注意,iframe1导航到新页面之后浏览器会再次调用iframe1_load,从而造成死循环,所以还要加个标记。

var iframe1 = document.createElement("iframe");
iframe1.style.display = "none";
document.body.appendChild(iframe1);
(function () {
    var same_domain = false;
    // 当iframe加载完之后触发的函数
    function iframe1_load() {
        if (same_domain) {
            // 取得从服务器返回的数据
            alert(iframe1.contentWindow.name);
            // 关闭iframe1的窗口
            iframe1.contentWindow.close();
            // 移除iframe1
            document.body.removeChild(iframe1);
        } else {
            same_domain = true;
            // 不能用iframe1.src = "empty.html",在IE下有错误
            iframe1.contentWindow.location = "empty.html";
        }
    }
    // 在IE下要用attachEvent来添加iframe的onload处理函数
    if (iframe1.attachEvent) {
        iframe1.attachEvent("onload", function () {
            iframe1_load();
        });
    }
    else {
        iframe1.onload = iframe1_load;
    }
})();
iframe1.src = "http://127.0.0.1/server.html";

server.html的代码

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
	<title>Server</title>
	<script type="text/javascript">
		window.name = "HELLO WORLD";
	</script>
</head>
<body>
</body>
</html>

改写jQuery UI的Accordion

最近在做一个项目,其中有个UI要做成类似jQuery UI中Accordion的样子。但是Accordion在某一时刻只可以展开一个Panel,所以就改写了一下Accordion,让它可以同时展开多个Panel。

源代码:

(function($) {
    if ($.ui.accordion) {
        var old_clickHandler = $.ui.accordion.prototype._clickHandler;
        var new_clickHandler = function(event, target) {
            var o = this.options;
            if (o.disabled) return false;
            // called only when using activate(false) to close all parts programmatically
            if (!event.target && o.collapsible) {
                this.headers.removeClass("ui-state-active ui-corner-top")
                            .addClass("ui-state-default ui-corner-all")
                            .find(".ui-icon")
                            .removeClass(o.icons.headerSelected)
                            .addClass(o.icons.header);
                this.headers.next().addClass('ui-accordion-content-active');
                var toHide = this.headers.next(),
                            data = {
                                options: o,
                                newHeader: $([]),
                                oldHeader: o.headers,
                                newContent: $([]),
                                oldContent: toHide
                            },
                            toShow = (this.active = $([]));
                this._toggle(toShow, toHide, data);
                return false;
            }
            // get the click target
            var clicked = $(event.currentTarget || target);
            var clickedIsActive = clicked.next().css("display") != "none";
            // if animations are still active, or the active header is the target, ignore click
            if (this.running || (!o.collapsible && clickedIsActive)) {
                return false;
            }

            // switch classes
            clicked.toggleClass("ui-state-active")
                        .toggleClass("ui-corner-top")
                        .toggleClass("ui-state-default")
                        .toggleClass("ui-corner-all")
                        .find(".ui-icon")
                        .toggleClass(o.icons.headerSelected)
                        .toggleClass(o.icons.header);
            clicked.next().addClass('ui-accordion-content-active');

            // find elements to show and hide
            var toShow = clicked.next(),
                        toHide = clickedIsActive ? clicked.next() : $([]),
                        data = {
                            options: o,
                            newHeader: clickedIsActive && o.collapsible ? $([]) : clicked,
                            oldHeader: $([]),
                            newContent: clickedIsActive && o.collapsible ? $([]) : toShow.find('> *'),
                            oldContent: $([])
                        },
                        down = true;

            this._toggle(toShow, toHide, data, clickedIsActive, down);

            return false;
        };

        $.extend($.ui.accordion.defaults, {
            collapsible: true,
            multipleMode: false
        });

        $.extend($.ui.accordion.prototype, {
            _clickHandler: function(event, target) {
                if (this.options.multipleMode === true) {
                    new_clickHandler.apply(this, arguments);
                }
                else {
                    old_clickHandler.apply(this, arguments);
                }
            }
        });
    };
})(jQuery);

将上面的代码保存成.js文件并引入到页面,调用的时候只要设置multipleMode为true就可以了。

$("#accordion").accordion({multipleMode: true});

改进jQuery UI的Tabs

现将jQuery的Tabs控件添加了两个方法next和prev及一个事件beforegotonext,以便更好的支持用户操作。

源代码

(function($) {    
    $.extend($.ui.tabs.prototype, {
        next: function() {
            var self = this, o = this.options;
            var i = o.selected;
            var n = i + 1;
            while ((n < self.anchors.length) &&
            (self.lis.eq(n).hasClass('ui-state-disabled') ||
            (self.lis.eq(n).css("display") == "none"))) {
                n++;
            }
            if (n < self.anchors.length) {
                if (self._trigger('beforegotonext',
                    null,
			              self._ui(self.anchors[i],
			              self.panels[i])) !== false)
	                  self.select(n);
            }
        },
        prev: function() {
            var self = this, o = this.options;
            var p = o.selected - 1;
            while ((p >= 0) &&
            (self.lis.eq(p).hasClass('ui-state-disabled') ||
            (self.lis.eq(p).css("display") == "none"))) {
                p--;
            }
            if (p >= 0)
                self.select(p);
        }
    });
})(jQuery);

方法

  1. next使Tabs前进,如果当前步骤的下一步(或多步)为禁用或不可见状态,会自动跳过。
  2. prev使Tabs后退,如果当前步骤的上一步(或多步)为禁用或不可见状态,会自动跳过。
$("#tabs").tabs("next");
$("#tabs").tabs("prev");

事件

  1. beforegotonext事件在Tabs前进之前发生。如果Tabs没有前进的可能,这个事件不会触发,比如当前步骤为最后一步(或者后续的步骤都为禁用或不可见状态)调用$(“#tabs”).tabs(“next”)不会触发此事件。
  2. beforegotonext在Tabs原生select事件之前触发。
  3. 适用场合:beforegotonext只在单独处理next方法时适用,否则用select事件是最好的选择。
$("#tabs").tabs({
	"beforegotonext": function(event, ui) {
		// anchor element of the selected (clicked) tab
		var tab = ui.tab;
		// element, that contains the selected/clicked tab contents
		var panel = ui.panel;
		// zero-based index of the selected (clicked) tab
		var index = ui.index;
	}
});