什么是pjax

Ajax是Web开发非常重要的工具,只不过有一点单纯拿AJAX来请求一个页面时特别是单页应用时,无法正常使用浏览器历史记录、后退、刷新时同时替换文档标题。

而pjax就是用来解决这个问题,使用的是jQuery ajax 和 pushState(查看支持pushState浏览器版本情况)。

一、pushState、replaceState

pushState往历史堆栈的顶部添加一条记录,replaceState更改当前历史记录,包括三个参数:

  • data为一个可以被解析的对象或null,不能够是一个jQuery对象。在触发popstate事件时,会做为state参数传递过去;
  • title为页面的标题;
  • url为页面的URL,不写则为当前页。

二、pjax应用

pjax并不是完全自动的,需要自己调用。

首先:需要给页面设定一下被替换的位置,例:

<!DOCTYPE html>
<html>
<head>
  <!-- styles, scripts, etc -->
</head>
<body>
  <h1>My Site</h1>
  <div class="container" id="pjax-container">
    Go to <a href="/page/2">next page</a>.
  </div>
</body>
</html>

如果我们希望点击 /page/2 链接,会将 /page/2 HTML代码替换到 #pjax-container 容器中,则还需要告知pjax监听 a 标签。

$(document).pjax('a', '#pjax-container')

以上是一个完整的例子;如果你希望跟Unobtrusive JavaScript兼容,那么可以尝试使用符合Unobtrusive JavaScript写法,当然并不能完整的利用data-ajax-loading、data-ajax-mode、data-ajax-update属性。

每个PJAX请求都会自动加入一个 X-PJAX 请求头,以便于我们可以来判断是否需要引入 layout 文件,这里我是通过一个Filter来判断:

    public class PJaxFilter : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            base.OnActionExecuting(filterContext);
            filterContext.Controller.ViewBag.layout = "~/Views/Shared/_Layout.cshtml";
            string pjaxHeader = filterContext.RequestContext.HttpContext.Request.Headers["X-PJAX"];
            if (!String.IsNullOrEmpty(pjaxHeader)) filterContext.Controller.ViewBag.layout = null;
        }
    }

cshtml页面对于布局需要根据 @{ Layout = ViewBag.layout; } 来,这样当我们直接通过 /page/2 请求页面时也能够正常显示。

页面标题可以通过在HTML中输出 <title>页面标题</title>,pjax会自动获取。

pjax也提供一系列事件,比如 pjax:sendpjax:complete 来处理加载状态:

$(document).on('pjax:send', function() {
  $('#loading').show()
})
$(document).on('pjax:complete', function() {
  $('#loading').hide()
})

二、pjax原理

pjax的AJAX请求是基于jquery的ajax,所以pjax的options参数大部分才是jquery ajax请求的原样参数。

1、每次PAJX发生请求时会先通过 pushState 将请求页压入历史堆栈的顶部。

  if (xhr.readyState > 0) {
    if (options.push && !options.replace) {
      // Cache current container element before replacing it
      cachePush(pjax.state.id, context.clone().contents())
      window.history.pushState(null, "", stripPjaxParam(options.requestUrl))
    }
    fire('pjax:start', [xhr, options])
    fire('pjax:send', [xhr, options])
  }

2、请求成功后会:替换当前历史记录页、修改页面标题、把HTML写入相应容器当中。

    if (options.push || options.replace) {
      window.history.replaceState(pjax.state, container.title, container.url)
    }
    // Clear out any focused controls before inserting new page contents.
    try {
      document.activeElement.blur()
    } catch (e) { }
    // 这里的页面标题,会依次按<head><title /></head>、按Response片断根节点的title属性、Response带有<title />。
    if (container.title) document.title = container.title
    fire('pjax:beforeReplace', [container.contents, options], {
      state: pjax.state,
      previousState: previousState
    })
    context.html(container.contents)

这里要特意提一下pjax对脚本的处理方式。

因为 .html() 是基于innerHTML来改变容器代码,所以如果在页面使用 document.write 变成很不可取的方式,因为不同版本浏览器解析会有不可预知的情况发生,参考

还有针对 <script src="" /> 的处理是将未加载过的脚本写入到 <head> 中。

这里pjax的顺序是先写入HTML,再加载script,这好处就是避免DOM渲染和脚本资源请求受阻,倒置页面假死。

所以这里要特别注意加载顺序问题,如果某个页面每次请求都需要引用外部脚本,建议用 getScript。

3、监控window.onpopstate事件,后退时重新发送AJAX请求。

pjax会先从缓存中获取数据,而缓存并非无限制的,可以通过调用 pjax 时指定 maxCacheLength 参数大小(默认:20);和重新发送请求。

详见 onPjaxPopstate 函数。

4、重新加载布局

我们可以重新指定Response的请求头pjax版本号,来强制重新刷新页面。

if request.headers['X-PJAX']
  response.headers['X-PJAX-Version'] = "v123"
end

或html代码加入:

<meta http-equiv="x-pjax-version" content="v123">

其实pjax和模块化编程(例:require.js)真的是天生一对,之前有好几个小项目已经做了大量开发,效率、用户体验真的上升一个档次。

以上。

ckfinder怪事件新解

在做ckeditor整合ckfinder时,如果你也遇到以下几种怪咖问题时:

IE拒绝访问

有一种情况下是Chrome和Firefox正常,但IE无法打开ckfinder,会提示脚本“拒绝访问”。

Chrome下CPU占100%

一般是firefox正常,而Chrome占100%CPU。

ckfinder无”Select“选项

Chrome下ckfinder右击图片时,无“Select”选项。只有”view”、”download”等。

以上三种情况,可尝试去掉页面所有javascript文件。我遇到就是JQuery UI冲突,倒置以上三种问题。

你准备好写jQuery插件了吗?

开始之前我得先声明关于几点jQuery的概念:

1、$是jQuery的简写,任何出现$()的地方你都可以转换成jQuery()。

2、jQuery.fn只是jQuery.prototype的一个别名而已。但前提是你要知道JavaScript里面prototype是什么意思。JavaScript prototype整理

OK,如果你清楚上面两个概念,那么我们进入正题:

jQuery提供两种实现插件机制:jQuery.extend(object)、jQuery.fn.extend(object)。

一、jQuery中的$.extend

$.extend也就是支撑起jQuery插件半天边吧,所以我们还是得先了解一下他:

$.extend(object) 将object对象逐个复制给jQuery对象。

问题:

$.extend({ showMsg: function(msg) {  } });

$.showMsg = function(msg) { }

这两者是没有区别,只是第二种方式看起来更加简洁(至少我喜欢这样子写),当然$.extend支持更加复杂操作,比如:

$.extend({

min: function(a, b) { // do something },

max: function(a, b) { // do something }

}); // 逐个将min、max方法复制给jQuery对象。

当然还可以提供另一个对象,并不一定非要复制给jQuery对象,比如:jQuery.extend({ args:'我是args属性' }, options); // 将args复制给options对象

理解上面这些将对我们后面写插件很有帮助。

二、jQuery.extend(object)

扩展jQuery对象本身,意思就是说在jQuery对象本身增加新函数。

当然这是针对jQuery做的一个扩展,实际上你也可以这么写:

jQuery.extend({ pluginName: function(args) { } });

当然还有更简洁、更漂亮的办法:

jQuery.pluginName = function(args) {}

它的使用方法非常简单:$.pluginName(‘我就是这么被调用的’);

jQuery官网就提供一个cookie插件,她就是使用这种插件机制实现的,感兴趣的同学可以看看

三、jQuery.fn.extend(object)

扩展 jQuery 元素集来提供新的方法,绝大部分我们都需要通过jQuery筛选出来一些元素集,然后对元素集进行操作,所以呢,这种插件机制也成了首选。

jQuery.fn.extend({  // 上面已经提到extend如果有多个成员、方法都将会逐个复制给jQuery
 check: function() {
 return this.each(function() { this.checked = true; });
 },
 uncheck: function() {
 return this.each(function() { this.checked = false; });
 }
 });

实现全选和非全选两个插件。

使用方法:$("input[type=checkbox]").check(); // 全选所有checkbox选项框

其中:$("input[type=checkbox]") 是一个元素集。

四、我需要很多参数

很多情况下,我们需要传递很多参数,但是绝大部分又是可选的,比如一个基于FLASH来切换图片效果示例(这在大部分网站里面都会出现的):

$.fn.SwfSlide = function(options) {

options = $.extend({ // 上面我已经讲明了,$.extend可以将对象复制到另一个对象上,而实现可选参数方法是通过她来实现
 swfFile: '/images/pixviewer.swf?1',
 fwidth: 200,  // 默认宽度
 fheight: 160, // 默认高度
 theight: 18,
 files: '',
 links: '',
 texts: ''
 }, options || {});

}

这里面有七个参数,但是当你在调用的时候你不需要七个全指明,也许你只要三个,像这样:$(‘div’).SwfSlide({ files:”, links:”, texts:” });

五、可不是这么就完了

JavaScript脚本框架非常多,最经典像是prototype、yui、mootools等等,如果说一个项目运用多套框架的话,遇到最多关于$冲突问题。jQuery本身提供一些多库共存的解决方案。但是做为我们基于jQuery来写插件,我们还是要考虑到这个多库共存带来的问题。

其实也只是非常简单的一个技巧,将插件代码放到一个包裹器里面:

(function($) {

// 插件代码

})(jQuery); // javascript的匿名对象,强制jQuery对象传递给$,确保我们插件里面使用$都是jQuery对象。

jQuery Boilerplate 插件模板

    // 这个分号的作用是防止和其他jquery插件合并时,别人不规范的jquery插件忘记使用分号结束
    //影响到我们当前的插件,导致无法运行的问题。 
    ;(function ( $, window, document, undefined ) {

            // undefined作为形参的目的是因为在es3中undefined是可以被修改的
            //比如我们可以声明var undefined = 123,这样就影响到了undefined值的判断,幸运的是在es5中,undefined不能被修改了。
            // window和document本身是全局变量,在这个地方作为形参的目的是因为js执行是从里到外查找变量的(作用域),把它们作为局部变量传进来,就避免了去外层查找,提高了效率。

            // 声明默认属性对象
            var pluginName = "defaultPluginName",
                    defaults = {
                    propertyName: "value"
            };

            // 构造函数
            function Plugin ( element, options ) {
                    this.element = element;
                    // 将默认属性对象和传递的参数对象合并到第一个空对象中
                    this.settings = $.extend( {}, defaults, options );
                    this._defaults = defaults;
                    this._name = pluginName;
                    this.init();
            }

            // 为了避免和原型对象Plugin.prototype的冲突,这地方采用继承原型对象的方法
            $.extend(Plugin.prototype, {
                    init: function () {
                                // 初始化,由于继承自Plugin原型,
                                // 你可以在这里直接使用this.element或者this.settings
                            console.log("xD");
                    },
                    yourOtherFunction: function () {
                            // some logic
                    }
            });

            // 对构造函数的一个轻量级封装,
            // 防止产生多个实例
            $.fn[ pluginName ] = function ( options ) {
                    this.each(function() {
                            if ( !$.data( this, "plugin_" + pluginName ) ) {
                                    $.data( this, "plugin_" + pluginName, new Plugin( this, options ) );
                            }
                    });

                    // 方便链式调用
                    return this;
            };

    })( jQuery, window, document );

相比较有这么一个用于开发jQuery插件基础模板,自己写插件也会规范许多。

另外我还写过一篇关于Bootstrap插件开发概述,他所用的jQuery插件开发模板也是非常值得学习的地方。

© 2017 卡片机色彩 沪ICP备13032872号-3

Theme by cipchk

to top