听晴空讲Drupal主题——第七章 主题中的JS(1)

7.1 主题中的JS

和上一章类似,本章我们会讲一些Drupal主题中和JS有关的知识点,但是并不讲JS本身。如果你对JS还不很了解,建议你花时间去学习一下,因为JS真的非常有用。

在你往主题中写入JS语句之前,你应该了解以下事实:

1.  Drupal程序自带jQuery和jQuery UI

2.  在页面上写任意一句JS语句,Drupal就会自动为你载入jQuery(及另一个自带的js文件:drupal.js)

3.  Drupal 7中自带的jQuery版本是1.4.4,jQuery UI的版本是1.8.7

4.  Drupal 8中目前自带的jQuery版本是2.0,jQuery UI的版本是1.10(这也意味着drupal正式抛弃ie6/7/8),如果你发现当你读这个内容的时候,版本又变化了,不妨在下面留个言。

5.  使用jQuery Update模块可以让你在后台管理/升级你的jQuery版本(暂无d8版)官网地址:https://www.drupal.org/project/jquery_update

6.   如果你需要在你的主题中使用多个版本的jQuery库,你可以使用jQuery Multi模块(暂无d8版)。官网地址:https://www.drupal.org/project/jqmulti

7.  Drupal7中的jQuery使用的是 no-conflict mode,详见http://api.jquery.com/jQuery.noConflict/,你要用以下代码将你写的jQuery代码包起来,不然你会遇到类似:Uncaught TypeError: Property '$' of object [object DOMWindow] is not a function的错误:

(function ($) {

  // 你的代码

})(jQuery);

或者 

(function ($) {

  // 你的代码

}(jQuery));

以上两种写法都是被允许的,用来关闭第一个左括号的右括号,可以写在(jQuery)的前面,也可以写在它的后面。

8.  其实你也可以直接把你的js写在tpl中(就好像写静态页面那样),但是这样就不能使用drupal的JS aggregation功能,所以正式场合并不推荐。

9.  为了更好的性能,你可以使用Core   Library模块(暂无d8版)来改善核心提供的aggregation机制,官网地址:https://www.drupal.org/project/core_library,或者使用Advanced CSS/JS Aggregation模块(暂无d8版),官网地址: https://www.drupal.org/project/advagg

Drupal 版本: 

听晴空讲Drupal主题——第七章 主题中的JS(2)

7.2 在主题中引入JS

7.2.1 使用.info文件添加js

在dp中引入js最简单的方法是在.info文件中进行声明。这里就不重复叙述了,但是你应该记住两点:第一,用这个方法添加的js会出现在网站的所有页面上,因此,要考虑性能问题。第二,用这个方法添加的js是主题层的js,当drupal页面加载的时候,drupal会首先加载库js(core/library)和模块js,然后才加载主题js。

或者,你也可以用JS Injector 模块来执行这个动作,官网地址:https://www.drupal.org/project/js_injector。

7.2.2 从template.php中加载js

在Drupal 7的template.php文件中,你可以通过 drupal_add_js() 或者 drupal_add_library()来添加js,d6中就只能使用前者。

关于drupal_add_js的详细内容,请查看官网https://api.drupal.org/api/drupal/includes%21common.inc/function/drupal_add_js/7,以下只给出一些例子。

比如,你的根目录下有一个名为foo.js的脚本,那么引入的时候就大概是下面这样的:

Drupal 6:

<?php

function example_preprocess_page(&$variables) {

  drupal_add_js(drupal_get_path('theme', 'example'). '/foo.js', 'theme');

  // We need to rebuild the scripts variable with the new script included.

  $variables['scripts'] = drupal_get_js();

}

?>

 Drupal 7:
 

<?php

function example_preprocess_html(&$variables) {

  $options = array(

    'group' => JS_THEME,

  );

  drupal_add_js(drupal_get_path('theme', 'example'). '/foo.js', $options);

}

?>

 在下面的代码片段,我们先判断当前页面是否为网站首页,如果是,添加js。

function my_theme_preprocess_html(&$variables) {

  if (drupal_is_front_page()) {

  drupal_add_js(drupal_get_path('theme', 'my_theme') . '/js/my_script.js');

   }

}

在上面的代码示例,你需要用你实际的主题机器名来替换'my_theme'  , 'js' 是你主题目录中包含js脚本的一个目录,你也可以使用drupal_add_js来为具体的内容类型添加js,比如:

function my_theme_preprocess_node(&$variables) {

  if (isset($variables['node']) && $variables['node']->type == 'page') {

    // Add js

    drupal_add_js(drupal_get_path('theme', 'my_theme') . '/js/scripts.js');

    $variables['scripts'] = drupal_get_js();

  }

}

这样将添加脚本到内容类型为”page"的所有页面中。

你也可以使用drupal_add_js来添加一个脚本到指定的路径(nid),此例中的路径是  /node/8

function my_theme_preprocess_node(&$variables) {

   if(arg(0) == 'node' && arg(1) == '8' && arg(3) == null) {

     drupal_add_js(drupal_get_path('theme', 'my_theme') . '/js/custom.js');

  }

}

下面还有一些例子,看得懂的就看,我就不解释了,事实证明,教程不能写得太详细,不然就会有那种连<p>是什么都不知道的人说你的教程写得太乱了,然后帮你出个整理版。

Javascript direkt einbinden

<?php

drupal_add_js('    

  (function ($) {

    Drupal.behaviors.exampleModule = {

      attach: function(context, settings) {

        // Hier kann nun wie gewohnt jQuery-Code stehen.

      }

    };

  })(jQuery);

', 'inline');

?>

Eine Javascript-Datei einbinden

<?php

drupal_add_js('/pfad/zur/javascript-datei.js', 'file');

?>

Dieser Code sollte entweder der Datei template.php im aktiven Theme Ordner platziert werden, oder in einem eigenen Modul. Zum Beispiel über hook_init().

 

<?php

function deinModulName_init() {

  // Javascript direkt einbinden

  drupal_add_js('    

    (function ($) {

      Drupal.behaviors.exampleModule = {

        attach: function(context, settings) {

          // Hier kann nun wie gewohnt jQuery-Code stehen.

        }

      };

    })(jQuery);

  ', 'inline');



  // Eine Javascript-Datei einbinden

  drupal_add_js( drupal_get_path('module', 'deinModulName') . '/javascript-datei.js', 'file');

}

?>

Weitere Beispiele:

<?php

  // Eine Datei einbinden.

  drupal_add_js('misc/collapse.js', 'file');

  // Inline-JS einbinden.

  drupal_add_js('jQuery(document).ready(function () { alert("Hello!"); });', 'inline');

  // Inline-JS mit weiteren Optionen einbinden.

  drupal_add_js('jQuery(document).ready(function () { alert("Hello!"); });',

    array('type' => 'inline', 'scope' => 'footer', 'weight' => 5)

  );

  // Externes JS einbinden.

  drupal_add_js('http://example.com/example.js', 'external');

  // JS-Setting einbinden.

  drupal_add_js(array('myModule' => array('key' => 'value')), 'setting');

  // Eine Library (z.b. jQuery Plugin) einbinden.

  drupal_add_js('misc/collapse.js',

    array('type' => 'file', 'scope' => 'footer', 'group' => JS_LIBRARY)

  );

?>

 

Javascript auf ausgewählten Seiten einbinden

 

Wenn man Javascript nur auf einigen Seiten einbinden möchte kann man das zum Beispiel über eine if-Abfrage im hook_init machen.

<?php

function deinModulName_init() {

  // Nur auf node/123 einbinden

  if (arg(0) == 'node' AND arg(1) == 123) {

    // Hier drupal_add_js(...);

  }

}

?>

7.2.3 Js的加载顺序

在Drupal中引入JS的时候,你可以用表示权重的“group”来控制JS在页面上的载入顺序,以下是你可以在"group"中使用的常数:

JS_LIBRARY: JS库,设置(settings),jQuery插件

JS_DEFAULT:模块层JS

JS_THEME: 模版层JS

Drupal页面加载JS的顺序是:先加载JS_LIBRARY,然后加载JS_DEFAULT,最后是JS_THEME。

当你使用drupal_add_js引入JS的时候,你可以使用类似下面的代码来控制JS的加载顺序:

 

<?php

drupal_add_js(

  'jQuery(document).ready(function () { alert("Hello!"); });',

  array(

    'type' => 'inline',

    'scope' => 'footer',

    'group' => JS_THEME,

    'weight' => 5,

  )

);

?>
Drupal 版本: 

听晴空讲Drupal主题——第七章 主题中的JS(3)

7.2.4 drupal_add_library

另外,你还可以使用drupal_add_library添加js库。格式如下:

drupal_add_library($module, $library);

这里,库指的是一个js+css的集合,库中还可以包含其它依赖的库。例如,Drupal7中所包含jQuery UI,就是一个库,并且,这个库存在于核心的 system模块中,如果你想引入整个jQuery UI,你可以这样:

<?php

drupal_add_library('system', ‘ui');

?>

 

当它其中的子库ui.autocomplete被页面某个元素调用的时候,为了使其正常工作,还需要同时在页面上引入ui.core和ui.position。如果你使用drupal_add_js的话需要引入三次,这还不包括对应的css。但是,drupal_add_library可以一次性为我们搞定这些事情。你只需要使用以下代码引入ui.autocomplete,就可以引入相关的CSS/JS/依赖的子库:

<?php

drupal_add_library('system', 'ui.autocomplete');

?>

这一个语句就引入了 jquery.ui.autocomplete.js, jquery.ui.autocomplete.css, 以及所依赖的 jquery.ui.position.js, jquery.ui.widget.js, jquery.ui.core.js, jquery.ui.core.css, 和 jquery.ui.theme.css.

7.3.1 drupal中的JS 的写法

ok,经过上面的学习,你已经学会如何引入js语句了,那么在Drupal中js的写法有什么特殊之处呢?

假设,我们的jQuery文件内容如下——它看上去和平常的jQuery代码没有什么区别:

$(document).ready(function(){
	$(‘h1’).addClass('my-class');
});

如果我们直接引入这个文件,在Drupal中就会出现本章第一节中第7点所提到的错误,解决方案如下:

1  用jQuery代替美元符号$:

jQuery(document).ready(function(){

    $(‘h1’).addClass('my-class');

});

2 用下面红色代码包围原始文件:

(function($){

    $(document).ready(function(){

        $(‘h1’).addClass('my-class');

    });

})(jQuery);

另外,由于在jQuery中        //https://api.jquery.com/ready/

$( document ).ready(function() {

  // Handler for .ready() called.

});

$(function() {

  // Handler for .ready() called.

});

是等效的,所以你也可以把上面的代码写为:

(function($){

    $(function() {

        $(‘h1’).addClass('my-class');

    });

})(jQuery);

 

3 用drupal7自己的 behaviors

 

(function($){

    Drupal.behaviors.behaviorName = {

          attach: function (context, settings) {

            $(‘h1’).addClass('my-class');

        }

    };

})(jQuery);

我们还可以将context变量作为jQuery选择器的第二个参数,这样效率更高。所以代码还可以写成这样:

(function($){

    Drupal.behaviors.behaviorName = {

          attach: function (context, settings) {

            $(‘h1’, context).addClass('my-class');

        }

    };

})(jQuery);

 

7.3.2 Drupal.behaviorsv和 jQuery $.once()

 

在上面提到的方法2中的    

$(document).ready(function(){

        $(‘h1’).addClass('my-class');

    });

只会在页面完全加载后执行一次,这种方式对于大多数的情况都没有问题,但是对于AJAX事件就会力不从心了。因为AJAX事件会在页面加载之后改变DOM,而事件发生之后,由于页面并没有重新加载,因此js语句不会再次执行。

为了解决这个问题,jQuery给出了live()方法,以确保AJAX事件发生之后JS语句的有效性。

与此同时,Drupal也给出了自己的方法Drupal.behaviors,也就是上节中的方法3。

Drupal.behaviors的特点在于,每当DOM被完全载入,或者每当DOM发生改变,它所包含的js语句都会被执行一次。因此,当你使用Drupal.behaviors的时候,你不再需要使用$(document).ready来帮你判断你的DOM是否已经做好准备了。并且,当DOM发生改变的时候,你也不再需要使用jQuery的live()。

使用Drupal.behaviors不是必须的,但是是推荐的最佳实践。如下:

Drupal.behaviors.behaviorName = {

  attach: function (context, settings) {

    // Do something.

  },

  detach: function (context, settings, trigger) {

    // Undo something.

  }

};

Drupal.behaviors往往会在页面上被调用多次。这样,发生Ajax事件以后,就可以确保对DOM中新的元素执行Drupal.behaviors中的命令。但是,这样会使得DOM中原有的元素被重复执行Drupal.behaviors中的命令——虽然,很多时候,肉眼看不出变化。这样,效率会比较低,有些时候,可能还会导致一些意想不到的错误。因此我们使用  jQuery.once()来保证JS语句只运行一遍,而且它被加入了Drupal的核心中。 jQuery.once()会对已经被执行过对应JS语句的元素添加一个简单的class作为标记,并在下一次Drupal.behaviors执行的时候,找到这些标记,并跳过相应的元素。

例子如下

(function ($) {

Drupal.behaviors.mybehavior = {

  attach: function (context, settings) {

    $('#some_element', context).once('mybehavior', function () {

      // Code here will only be applied to $('#some_element')

      // a single time.

    });

  }

};

})(jQuery);

更多详情请参考: https://www.drupal.org/node/756722

https://www.lullabot.com/articles/understanding-javascript-behaviors-in-drupal

 

最后仅以下图纪念在社区中那些快乐的片段:

drupal.behaviorsliao_tian_ji_lu_.png

 

子曰:“学而时习之,不亦悦乎?有朋自远方来,不亦乐乎?人不知而不愠,不亦君子乎?”—— 《论语·学而》

Drupal 版本: