Index: modules/block/block-admin-display-form.tpl.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/block/block-admin-display-form.tpl.php,v
retrieving revision 1.6
diff -u -r1.6 block-admin-display-form.tpl.php
--- modules/block/block-admin-display-form.tpl.php 15 May 2008 21:30:02 -0000 1.6
+++ modules/block/block-admin-display-form.tpl.php 29 Jun 2008 02:04:40 -0000
@@ -26,7 +26,7 @@
?>
JS_CORE_WEIGHT));
drupal_add_js(drupal_get_path('module', 'block') . '/block.js');
foreach ($block_regions as $region => $title) {
drupal_add_tabledrag('blocks', 'match', 'sibling', 'block-region-select', 'block-region-' . $region, NULL, FALSE);
Index: includes/theme.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/theme.inc,v
retrieving revision 1.428
diff -u -r1.428 theme.inc
--- includes/theme.inc 25 Jun 2008 09:12:24 -0000 1.428
+++ includes/theme.inc 29 Jun 2008 02:04:38 -0000
@@ -158,7 +158,7 @@
// Add scripts used by this theme.
foreach ($final_scripts as $script) {
- drupal_add_js($script, 'theme');
+ drupal_add_js($script, array('weight' => JS_THEME_WEIGHT));
}
$theme_engine = NULL;
@@ -1284,7 +1284,7 @@
// Add sticky headers, if applicable.
if (count($header)) {
- drupal_add_js('misc/tableheader.js');
+ drupal_add_js('misc/tableheader.js', array('weight' => JS_CORE_WEIGHT));
// Add 'sticky-enabled' class to the table to identify it for JS.
// This is needed to target tables constructed by this function.
$attributes['class'] = empty($attributes['class']) ? 'sticky-enabled' : ($attributes['class'] . ' sticky-enabled');
@@ -1401,7 +1401,7 @@
* Returns a header cell for tables that have a select all functionality.
*/
function theme_table_select_header_cell() {
- drupal_add_js('misc/tableselect.js');
+ drupal_add_js('misc/tableselect.js', array('weight' => JS_CORE_WEIGHT));
return array('class' => 'select-all');
}
Index: includes/form.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/form.inc,v
retrieving revision 1.275
diff -u -r1.275 form.inc
--- includes/form.inc 25 Jun 2008 09:57:07 -0000 1.275
+++ includes/form.inc 29 Jun 2008 02:04:34 -0000
@@ -1489,7 +1489,7 @@
*/
function theme_fieldset($element) {
if ($element['#collapsible']) {
- drupal_add_js('misc/collapse.js');
+ drupal_add_js('misc/collapse.js', array('weight' => JS_CORE_WEIGHT));
if (!isset($element['#attributes']['class'])) {
$element['#attributes']['class'] = '';
@@ -1787,8 +1787,8 @@
// Adding the same javascript settings twice will cause a recursion error,
// we avoid the problem by checking if the javascript has already been added.
if (isset($element['#ahah']['path']) && isset($element['#ahah']['event']) && !isset($js_added[$element['#id']])) {
- drupal_add_js('misc/jquery.form.js');
- drupal_add_js('misc/ahah.js');
+ drupal_add_js('misc/jquery.form.js', array('weight' => JS_CORE_WEIGHT));
+ drupal_add_js('misc/ahah.js', array('weight' => JS_CORE_WEIGHT));
$ahah_binding = array(
'url' => url($element['#ahah']['path']),
@@ -1813,7 +1813,7 @@
// Add progress.js if we're doing a bar display.
if ($ahah_binding['progress']['type'] == 'bar') {
- drupal_add_js('misc/progress.js');
+ drupal_add_js('misc/progress.js', array('weight' => JS_CORE_WEIGHT));
}
drupal_add_js(array('ahah' => array($element['#id'] => $ahah_binding)), 'setting');
@@ -2001,7 +2001,7 @@
$output = '';
if ($element['#autocomplete_path']) {
- drupal_add_js('misc/autocomplete.js');
+ drupal_add_js('misc/autocomplete.js', array('weight' => JS_CORE_WEIGHT));
$class[] = 'form-autocomplete';
$extra = '';
}
@@ -2053,16 +2053,14 @@
// Add teaser behavior (must come before resizable)
if (!empty($element['#teaser'])) {
- drupal_add_js('misc/teaser.js');
- // Note: arrays are merged in drupal_get_js().
- drupal_add_js(array('teaserCheckbox' => array($element['#id'] => $element['#teaser_checkbox'])), 'setting');
- drupal_add_js(array('teaser' => array($element['#id'] => $element['#teaser'])), 'setting');
+ drupal_add_js('misc/teaser.js', array('weight' => JS_CORE_WEIGHT));
+ drupal_add_js(array('teaserCheckbox' => array($element['#id'] => $element['#teaser_checkbox']), 'teaser' => array($element['#id'] => $element['#teaser'])), 'setting');
$class[] = 'teaser';
}
// Add resizable behavior
if ($element['#resizable'] !== FALSE) {
- drupal_add_js('misc/textarea.js');
+ drupal_add_js('misc/textarea.js', array('weight' => JS_CORE_WEIGHT));
$class[] = 'resizable';
}
Index: includes/batch.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/batch.inc,v
retrieving revision 1.20
diff -u -r1.20 batch.inc
--- includes/batch.inc 24 Jun 2008 21:51:02 -0000 1.20
+++ includes/batch.inc 29 Jun 2008 02:04:21 -0000
@@ -80,7 +80,7 @@
// and the initialization and error messages.
$current_set = _batch_current_set();
drupal_set_title($current_set['title']);
- drupal_add_js('misc/progress.js', 'core', 'header', FALSE, FALSE);
+ drupal_add_js('misc/progress.js', array('cache' => FALSE, 'weight' => JS_CORE_WEIGHT, 'attributes' => array('defer' => 'defer')));
$url = url($batch['url'], array('query' => array('id' => $batch['id'])));
$js_setting = array(
@@ -91,7 +91,7 @@
),
);
drupal_add_js($js_setting, 'setting');
- drupal_add_js('misc/batch.js', 'core', 'header', FALSE, FALSE);
+ drupal_add_js('misc/batch.js', array('cache' => FALSE, 'weight' => JS_CORE_WEIGHT, 'attributes' => array('defer' => 'defer')));
$output = '
';
return $output;
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.773
diff -u -r1.773 common.inc
--- includes/common.inc 24 Jun 2008 22:09:52 -0000 1.773
+++ includes/common.inc 29 Jun 2008 02:04:29 -0000
@@ -25,6 +25,26 @@
define('SAVED_DELETED', 3);
/**
+ * Weight of JavaScript for files loaded from core (files in /misc/).
+ */
+define('JS_CORE_WEIGHT', -20);
+
+/**
+ * Weight of JavaScript files loaded by modules.
+ */
+define('JS_MODULE_WEIGHT', 0);
+
+/**
+ * Weight of JavaScript files loaded by themes.
+ */
+define('JS_THEME_WEIGHT', 20);
+
+/**
+ * Weight of JavaScript libraries needed by other scripts, eg. jQuery.
+ */
+define('JS_LIBRARY_WEIGHT', -100);
+
+/**
* Set content for a specified region.
*
* @param $region
@@ -1445,9 +1465,9 @@
function l($text, $path, $options = array()) {
// Merge in defaults.
$options += array(
- 'attributes' => array(),
- 'html' => FALSE,
- );
+ 'attributes' => array(),
+ 'html' => FALSE,
+ );
// Append active class.
if ($path == $_GET['q'] || ($path == '' && drupal_is_front_page())) {
@@ -1909,16 +1929,17 @@
* reference to an existing file or as inline code. The following actions can be
* performed using this function:
*
- * - Add a file ('core', 'module' and 'theme'):
- * Adds a reference to a JavaScript file to the page. JavaScript files
- * are placed in a certain order, from 'core' first, to 'module' and finally
- * 'theme' so that files, that are added later, can override previously added
- * files with ease.
+ * - Add a file ('file'):
+ * Adds a reference to a JavaScript file to the page and supports files on
+ * external servers. JavaScript files are placed in a certain order depending
+ * on their given weight. Files from core, modules, and themes have predefined
+ * constants for their weights of JS_CORE_WEIGHT (-20), JS_MODULE_WEIGHT (0),
+ * and JS_THEME_WEIGHT (20).
*
* - Add inline JavaScript code ('inline'):
* Executes a piece of JavaScript code on the current page by placing the code
- * directly in the page. This can, for example, be useful to tell the user that
- * a new message arrived, by opening a pop up, alert box etc.
+ * directly in the page. This can, for example, be useful to tell the user
+ * that a new message arrived, by opening a pop up, alert box, etc.
*
* - Add settings ('setting'):
* Adds a setting to Drupal's global storage of JavaScript settings. Per-page
@@ -1926,92 +1947,192 @@
* will be accessible at Drupal.settings.
*
* @param $data
- * (optional) If given, the value depends on the $type parameter:
- * - 'core', 'module' or 'theme': Path to the file relative to base_path().
+ * (optional) If given, the value depends on the $options parameter:
+ * - 'file': Either the path to the file relative to base_path(), or the
+ * absolute URL to the Javascript file on an external server.
* - 'inline': The JavaScript code that should be placed in the given scope.
* - 'setting': An array with configuration options as associative array. The
- * array is directly placed in Drupal.settings. You might want to wrap your
- * actual configuration settings in another variable to prevent the pollution
- * of the Drupal.settings namespace.
- * @param $type
- * (optional) The type of JavaScript that should be added to the page. Allowed
- * values are 'core', 'module', 'theme', 'inline' and 'setting'. You
- * can, however, specify any value. It is treated as a reference to a JavaScript
- * file. Defaults to 'module'.
- * @param $scope
- * (optional) The location in which you want to place the script. Possible
- * values are 'header' and 'footer' by default. If your theme implements
- * different locations, however, you can also use these.
- * @param $defer
- * (optional) If set to TRUE, the defer attribute is set on the \n";
+ if (!empty($data)) {
+ $renderable_js[] = array(
+ 'type' => 'setting',
+ 'data' => $data,
+ );
+ }
break;
case 'inline':
- foreach ($data as $info) {
- $output .= '\n";
+ foreach ($data as $js => $options) {
+ $renderable_js[] = array(
+ 'type' => 'inline',
+ 'data' => $js,
+ 'attributes' => $options['attributes'],
+ );
}
break;
+ case 'file':
default:
- // If JS preprocessing is off, we still need to output the scripts.
- // Additionally, go through any remaining scripts if JS preprocessing is on and output the non-cached ones.
foreach ($data as $path => $info) {
- if (!$info['preprocess'] || !$is_writable || !$preprocess_js) {
- $no_preprocess[$type] .= '\n";
+ // Create the source of the Javscript, checking if base_path()
+ // is required, as well as placing the dummy query string at the
+ // end of the path.
+ if (valid_url($path, TRUE)) {
+ $info['attributes']['src'] = $path;
}
else {
- $files[$path] = $info;
+ $info['attributes']['src'] = base_path() . $path . ($info['cache'] ? $query_string : '?' . time());
}
+ $renderable_js[] = array(
+ 'type' => 'file',
+ 'attributes' => $info['attributes']
+ );
}
}
}
+ return theme('scripts', $renderable_js, $scope);
+}
- // Aggregate any remaining JS files that haven't already been output.
- if ($is_writable && $preprocess_js && count($files) > 0) {
- $filename = md5(serialize($files) . $query_string) . '.js';
- $preprocess_file = drupal_build_js_cache($files, $filename);
- $preprocessed .= '' . "\n";
- }
-
- // Keep the order of JS files consistent as some are preprocessed and others are not.
- // Make sure any inline or JS setting variables appear last after libraries have loaded.
- $output = $preprocessed . implode('', $no_preprocess) . $output;
+/**
+ * Themes a list of scripts, which are then rendered to the page.
+ *
+ * @param $scripts
+ * An array of scripts to be output.
+ * @param $scope
+ * The scope of the JavaScript: where it's going to be located
+ * on the page.
+ * @return
+ * HTML containing the script tags.
+ */
+function theme_scripts($scripts, $scope) {
+ $output = '';
+ foreach ($scripts as $script) {
+ switch ($script['type']) {
+ case 'setting':
+ $output .= '\n";
+ break;
+ case 'inline':
+ $output .= '\n";
+ break;
+ case 'file':
+ $output .= '\n";
+ }
+ }
return $output;
}
@@ -2211,7 +2446,7 @@
function drupal_add_tabledrag($table_id, $action, $relationship, $group, $subgroup = NULL, $source = NULL, $hidden = TRUE, $limit = 0) {
static $js_added = FALSE;
if (!$js_added) {
- drupal_add_js('misc/tabledrag.js', 'core');
+ drupal_add_js('misc/tabledrag.js', array('weight' => JS_CORE_WEIGHT));
$js_added = TRUE;
}
@@ -2231,15 +2466,15 @@
/**
* Aggregate JS files, putting them in the files directory.
- *
+ *
* @param $files
* An array of JS files to aggregate and compress into one file.
* @param $filename
* The name of the aggregate JS file.
* @return
- * The name of the JS file.
+ * The name of the JS file or FALSE on failure.
*/
-function drupal_build_js_cache($files, $filename) {
+function drupal_aggregate_js($files, $filename) {
$contents = '';
// Create the js/ within the files folder.
@@ -2247,11 +2482,15 @@
file_check_directory($jspath, FILE_CREATE_DIRECTORY);
if (!file_exists($jspath . '/' . $filename)) {
- // Build aggregate JS file.
foreach ($files as $path => $info) {
- if ($info['preprocess']) {
- // Append a ';' after each JS file to prevent them from running together.
- $contents .= file_get_contents($path) . ';';
+ $tmp = file_get_contents($path);
+ if ($tmp) {
+ // Append a ';' after each JS file to prevent them from running
+ // together.
+ $contents .= $tmp . ';';
+ }
+ else {
+ return FALSE;
}
}
@@ -2263,10 +2502,36 @@
}
/**
+ * Cache locally external JS files.
+ *
+ * @param $path
+ * A URL to an external JavaScript file.
+ * @return
+ * A string representing the local version or FALSE if caching failed.
+ */
+function drupal_cache_external_js($path) {
+
+ // Create the js/ within the files folder.
+ $jspath = file_create_path('js');
+ if (!file_check_directory($jspath, FILE_CREATE_DIRECTORY)) {
+ return FALSE;
+ }
+
+ $request = drupal_http_request($path);
+ if ($request->code == '200') {
+ $filename = md5($path) . '.js';
+ return file_save_data($request->data, $jspath . '/' . $filename, FILE_EXISTS_REPLACE);
+ }
+
+ return FALSE;
+}
+
+/**
* Delete all cached JS files.
*/
function drupal_clear_js_cache() {
file_scan_directory(file_create_path('js'), '.*', array('.', '..', 'CVS'), 'file_delete', TRUE);
+ // TODO: Move this locale.module-specific code into locale.module somehow.
variable_set('javascript_parsed', array());
}
@@ -2274,6 +2539,11 @@
* Converts a PHP variable into its Javascript equivalent.
*
* We use HTML-safe strings, i.e. with <, > and & escaped.
+ * @param $var
+ * The variable to encode.
+ * @return
+ * A string, which is a JavaScript-encoded version of $var.
+ * @see drupal_json
*/
function drupal_to_js($var) {
// json_encode() does not escape <, > and &, so we do it with str_replace()
@@ -2288,6 +2558,7 @@
*
* @param $var
* (optional) If set, the variable will be converted to JSON and output.
+ * @see drupal_to_js
*/
function drupal_json($var = NULL) {
// We are returning JavaScript, so tell the browser.
@@ -3030,6 +3301,10 @@
'form_element' => array(
'arguments' => array('element' => NULL, 'value' => NULL),
),
+ // theme_scripts() exists in form.inc.
+ 'scripts' => array(
+ 'arguments' => array('scripts' => NULL, 'scope' => NULL),
+ ),
);
}
Index: modules/user/user.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.admin.inc,v
retrieving revision 1.22
diff -u -r1.22 user.admin.inc
--- modules/user/user.admin.inc 7 May 2008 19:34:24 -0000 1.22
+++ modules/user/user.admin.inc 29 Jun 2008 02:04:51 -0000
@@ -83,7 +83,7 @@
);
}
- drupal_add_js('misc/form.js', 'core');
+ drupal_add_js('misc/form.js', array('weight' => JS_CORE_WEIGHT));
return $form;
}
Index: modules/user/user.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.module,v
retrieving revision 1.911
diff -u -r1.911 user.module
--- modules/user/user.module 27 Jun 2008 07:25:11 -0000 1.911
+++ modules/user/user.module 29 Jun 2008 02:04:57 -0000
@@ -2122,7 +2122,7 @@
global $user;
// Only need to do once per page.
if (!$complete) {
- drupal_add_js(drupal_get_path('module', 'user') . '/user.js', 'module');
+ drupal_add_js(drupal_get_path('module', 'user') . '/user.js');
drupal_add_js(array(
'password' => array(
Index: includes/tests/common.test
===================================================================
RCS file: /cvs/drupal/drupal/includes/tests/common.test,v
retrieving revision 1.1
diff -u -r1.1 common.test
--- includes/tests/common.test 26 Jun 2008 21:04:17 -0000 1.1
+++ includes/tests/common.test 29 Jun 2008 02:04:40 -0000
@@ -52,3 +52,370 @@
}
}
}
+
+class JavaScriptAddRemoveTestCase extends DrupalWebTestCase {
+ /**
+ * Implementation of getInfo().
+ */
+ function getInfo() {
+ return array(
+ 'name' => t('JavaScript add/remove test'),
+ 'description' => t('Tests the various options to add/remove JavaScript from a page.'),
+ 'group' => t('System'),
+ );
+ }
+
+ function test_drupal_add_js() {
+ // Clear out all cached JavaScript files.
+ drupal_clear_js_cache();
+
+ // Setup some variables needed throughout this test.
+ $jspath = file_create_path('js');
+ file_check_directory($jspath, FILE_CREATE_DIRECTORY);
+
+ // Reset the JavaScript.
+ $original_js = drupal_add_js(NULL, array(), TRUE);
+
+ // Regex for reading relevant values from