Commit 4a4a6570 authored by webchick's avatar webchick

#315100 by Rob Loach, quicksketch, sun, skilip, aaron, et al: Add ability for...

#315100 by Rob Loach, quicksketch, sun, skilip, aaron, et al: Add ability for modules to register JS/CSS libraries.
parent a0d19b08
......@@ -2779,15 +2779,6 @@ function drupal_add_js($data = NULL, $options = NULL) {
'scope' => 'header',
'weight' => JS_LIBRARY,
),
'misc/jquery.js' => array(
'data' => 'misc/jquery.js',
'type' => 'file',
'scope' => 'header',
'weight' => JS_LIBRARY - 2,
'cache' => TRUE,
'defer' => FALSE,
'preprocess' => TRUE,
),
'misc/drupal.js' => array(
'data' => 'misc/drupal.js',
'type' => 'file',
......@@ -2798,6 +2789,8 @@ function drupal_add_js($data = NULL, $options = NULL) {
'preprocess' => TRUE,
),
);
// jQuery itself is registered as a library.
drupal_add_library('system', 'jquery');
}
switch ($options['type']) {
......@@ -2953,6 +2946,124 @@ function drupal_get_js($scope = 'header', $javascript = NULL) {
return $preprocessed . $no_preprocess . $output;
}
/**
* Adds multiple JavaScript or CSS files at the same time.
*
* A library defines a set of JavaScript and/or CSS files, optionally using
* settings, and optionally requiring another library. For example, a library
* can be a jQuery plugin, a JavaScript framework, or a CSS framework. This
* function allows modules to load a library defined/shipped by itself or a
* depending module; without having to add all files of the library separately.
* Each library is only loaded once.
*
* @param $module
* The name of the module that registered the library.
* @param $name
* The name of the library to add.
* @return
* TRUE when the library was successfully added or FALSE if the library or one
* of its dependencies could not be added.
*
* @see drupal_get_library()
* @see hook_library()
* @see hook_library_alter()
*/
function drupal_add_library($module, $name) {
$added = &drupal_static(__FUNCTION__, array());
// Only process the library if it exists and it was not added already.
if (!isset($added[$module][$name]) && $library = drupal_get_library($module, $name)) {
// Prevent repeated/recursive processing.
$added[$module][$name] = TRUE;
// Ensure dependencies first.
foreach ($library['dependencies'] as $dependency) {
if (drupal_add_library($dependency[0], $dependency[1]) === FALSE) {
// If any dependent library could not be added, this library will break;
// stop here.
$added[$module][$name] = FALSE;
return FALSE;
}
}
// Add defined JavaScript.
foreach ($library['js'] as $data => $options) {
// For JS settings we need to transform $options['data'] into $data.
if (isset($options['type'], $options['data']) && $options['type'] == 'setting') {
$data = $options['data'];
unset($options['data']);
}
// If not specified, assign a default weight of JS_LIBRARY.
elseif (!isset($options['weight'])) {
$options['weight'] = JS_LIBRARY;
}
drupal_add_js($data, $options);
}
// Add defined stylesheets.
foreach ($library['css'] as $data => $options) {
drupal_add_css($data, $options);
}
}
// Requested library does not exist.
else {
$added[$module][$name] = FALSE;
}
return $added[$module][$name];
}
/**
* Retrieves information for a JavaScript/CSS library.
*
* Library information is statically cached. Libraries are keyed by module for
* several reasons:
* - Libraries are not unique. Multiple modules might ship with the same library
* in a different version or variant. This registry cannot (and does not
* attempt to) prevent library conflicts.
* - Modules implementing and thereby depending on a library that is registered
* by another module can only rely on that module's library.
* - Two (or more) modules can still register the same library and use it
* without conflicts in case the libraries are loaded on certain pages only.
*
* @param $module
* The name of a module that registered a library.
* @param $library
* The name of a registered library.
* @return
* The definition of the requested library, if existent, or FALSE.
*
* @see drupal_add_library()
* @see hook_library()
* @see hook_library_alter()
*
* @todo The purpose of drupal_get_*() is completely different to other page
* requisite API functions; find and use a different name.
*/
function drupal_get_library($module, $name) {
$libraries = &drupal_static(__FUNCTION__, array());
if (!array_key_exists($module, $libraries)) {
// Retrieve all libraries associated with the module.
$module_libraries = module_invoke($module, 'library');
// Allow modules to alter the module's registered libraries.
if (!empty($module_libraries)) {
drupal_alter('library', $module_libraries, $module);
}
$libraries[$module] = $module_libraries;
}
if (!empty($libraries[$module][$name]) && is_array($libraries[$module][$name])) {
// Add default elements to allow for easier processing.
$libraries[$module][$name] += array('dependencies' => array(), 'js' => array(), 'css' => array());
}
else {
$libraries[$module][$name] = FALSE;
}
return $libraries[$module][$name];
}
/**
* Assist in adding the tableDrag JavaScript behavior to a themed table.
*
......
......@@ -2002,7 +2002,7 @@ function form_process_ahah($element) {
// 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']['callback']) || isset($element['#ahah']['path'])) && isset($element['#ahah']['event']) && !isset($js_added[$element['#id']])) {
drupal_add_js('misc/jquery.form.js', array('weight' => JS_LIBRARY));
drupal_add_library('system', 'form');
drupal_add_js('misc/ahah.js');
$ahah_binding = array(
......
......@@ -169,8 +169,7 @@ function color_scheme_form(&$form_state, $theme) {
$info = color_get_info($theme);
// Add Farbtastic color picker.
drupal_add_css('misc/farbtastic/farbtastic.css', array('preprocess' => FALSE));
drupal_add_js('misc/farbtastic/farbtastic.js', array('weight' => JS_LIBRARY));
drupal_add_library('system', 'farbtastic');
// Add custom CSS and JS.
drupal_add_css($base . '/color.css', array('preprocess' => FALSE));
......
......@@ -435,7 +435,7 @@ class JavaScriptTestCase extends DrupalWebTestCase {
/**
* Store configured value for JavaScript preprocessing.
*/
var $preprocess_js = NULL;
protected $preprocess_js = NULL;
public static function getInfo() {
return array(
......@@ -447,14 +447,15 @@ class JavaScriptTestCase extends DrupalWebTestCase {
function setUp() {
// Enable Locale and SimpleTest in the test environment.
parent::setUp('locale', 'simpletest');
parent::setUp('locale', 'simpletest', 'common_test');
// Disable preprocessing
$this->preprocess_js = variable_get('preprocess_js', 0);
variable_set('preprocess_js', 0);
// Reset drupal_add_js() before each test.
// Reset drupal_add_js() and drupal_add_library() statics before each test.
drupal_static_reset('drupal_add_js');
drupal_static_reset('drupal_add_library');
}
function tearDown() {
......@@ -562,7 +563,7 @@ class JavaScriptTestCase extends DrupalWebTestCase {
* Test rendering the JavaScript with a file's weight above jQuery's.
*/
function testRenderDifferentWeight() {
drupal_add_js('misc/collapse.js', array('weight' => JS_LIBRARY - 10));
drupal_add_js('misc/collapse.js', array('weight' => JS_LIBRARY - 21));
$javascript = drupal_get_js();
$this->assertTrue(strpos($javascript, 'misc/collapse.js') < strpos($javascript, 'misc/jquery.js'), t('Rendering a JavaScript file above jQuery.'));
}
......@@ -583,6 +584,58 @@ class JavaScriptTestCase extends DrupalWebTestCase {
$javascript = drupal_get_js();
$this->assertTrue(strpos($javascript, 'simpletest.js') < strpos($javascript, 'misc/tableselect.js'), t('Altering JavaScript weight through the alter hook.'));
}
/**
* Adds a library to the page and tests for both its JavaScript and its CSS.
*/
function testLibraryRender() {
$result = drupal_add_library('system', 'farbtastic');
$this->assertTrue($result !== FALSE, t('Library was added without errors.'));
$scripts = drupal_get_js();
$styles = drupal_get_css();
$this->assertTrue(strpos($scripts, 'misc/farbtastic/farbtastic.js'), t('JavaScript of library was added to the page.'));
$this->assertTrue(strpos($styles, 'misc/farbtastic/farbtastic.css'), t('Stylesheet of library was added to the page.'));
}
/**
* Adds a JavaScript library to the page and alters it.
*
* @see common_test_library_alter()
*/
function testLibraryAlter() {
// Verify that common_test altered the title of Farbtastic.
$library = drupal_get_library('system', 'farbtastic');
$this->assertEqual($library['title'], 'Farbtastic: Altered Library', t('Registered libraries were altered.'));
// common_test_library_alter() also added a dependency on jQuery Form.
drupal_add_library('system', 'farbtastic');
$scripts = drupal_get_js();
$this->assertTrue(strpos($scripts, 'misc/jquery.form.js'), t('Altered library dependencies are added to the page.'));
}
/**
* Tests that multiple modules can implement the same library.
*
* @see common_test_library()
*/
function testLibraryNameConflicts() {
$farbtastic = drupal_get_library('common_test', 'farbtastic');
$this->assertEqual($farbtastic['title'], 'Custom Farbtastic Library', t('Alternative libraries can be added to the page.'));
}
/**
* Tests non-existing libraries.
*/
function testLibraryUnknown() {
$result = drupal_get_library('unknown', 'unknown');
$this->assertFalse($result, t('Unknown library returned FALSE.'));
drupal_static_reset('drupal_get_library');
$result = drupal_add_library('unknown', 'unknown');
$this->assertFalse($result, t('Unknown library returned FALSE.'));
$scripts = drupal_get_js();
$this->assertTrue(strpos($scripts, 'unknown') === FALSE, t('Unknown library was not added to the page.'));
}
}
/**
......
......@@ -23,3 +23,35 @@ function common_test_theme() {
function theme_common_test_foo($foo, $bar) {
return $foo . $bar;
}
/**
* Implementation of hook_library_alter().
*/
function common_test_library_alter(&$libraries, $module) {
if ($module == 'system' && isset($libraries['farbtastic'])) {
// Change the title of Farbtastic to "Farbtastic: Altered Library".
$libraries['farbtastic']['title'] = 'Farbtastic: Altered Library';
// Make Farbtastic depend on jQuery Form to test library dependencies.
$libraries['farbtastic']['dependencies'][] = array('system', 'form');
}
}
/**
* Implementation of hook_library().
*
* Adds Farbtastic in a different version.
*/
function common_test_library() {
$libraries['farbtastic'] = array(
'title' => 'Custom Farbtastic Library',
'website' => 'http://code.google.com/p/farbtastic/',
'version' => '5.3',
'js' => array(
'misc/farbtastic/farbtastic.js' => array(),
),
'css' => array(
'misc/farbtastic/farbtastic.css' => array(),
),
);
return $libraries;
}
......@@ -182,6 +182,111 @@ function hook_js_alter(&$javascript) {
$javascript['misc/jquery.js']['data'] = drupal_get_path('module', 'jquery_update') . '/jquery.js';
}
/**
* Registers JavaScript/CSS libraries associated with a module.
*
* Modules implementing this return an array of arrays. The key to each
* sub-array is the machine readable name of the library. Each library may
* contain the following items:
*
* - 'title': The human readable name of the library.
* - 'website': The URL of the library's web site.
* - 'version': A string specifying the version of the library; intentionally
* not a float because a version like "1.2.3" is not a valid float. Use PHP's
* version_compare() to compare different versions.
* - 'js': An array of JavaScript elements; each element's key is used as $data
* argument, each element's value is used as $options array for
* drupal_add_js(). To add library-specific (not module-specific) JavaScript
* settings, the key may be skipped, the value must specify
* 'type' => 'setting', and the actual settings must be contained in a 'data'
* element of the value.
* - 'css': Like 'js', an array of CSS elements passed to drupal_add_css().
* - 'dependencies': An array of libraries that are required for a library. Each
* element is an array containing the module and name of the registered
* library. Note that all dependencies for each dependent library will be
* added when this library is added.
*
* Registered information for a library should contain re-usable data only.
* Module- or implementation-specific data and integration logic should be added
* separately.
*
* @return
* An array defining libraries associated with a module.
*
* @see system_library()
* @see drupal_add_library()
* @see drupal_get_library()
*/
function hook_library() {
// Library One.
$libraries['library-1'] = array(
'title' => 'Library One',
'website' => 'http://example.com/library-1',
'version' => '1.2',
'js' => array(
drupal_get_path('module', 'my_module') . '/library-1.js' => array(),
),
'css' => array(
drupal_get_path('module', 'my_module') . '/library-2.css' => array(
'type' => 'file',
'media' => 'screen',
),
),
);
// Library Two.
$libraries['library-2'] = array(
'title' => 'Library Two',
'website' => 'http://example.com/library-2',
'version' => '3.1-beta1',
'js' => array(
// JavaScript settings may use the 'data' key.
array(
'type' => 'setting',
'data' => array('library2' => TRUE),
),
),
'dependencies' => array(
// Require jQuery UI core by System module.
array('system' => 'ui'),
// Require our other library.
array('my_module', 'library-1'),
// Require another library.
array('other_module', 'library-3'),
),
);
return $libraries;
}
/**
* Alters the JavaScript/CSS library registry.
*
* Allows certain, contributed modules to update libraries to newer versions
* while ensuring backwards compatibility. In general, such manipulations should
* only be done by designated modules, since most modules that integrate with a
* certain library also depend on the API of a certain library version.
*
* @param $libraries
* The JavaScript/CSS libraries provided by $module. Keyed by internal library
* name and passed by reference.
* @param $module
* The name of the module that registered the libraries.
*
* @see hook_library()
*/
function hook_library_alter(&$libraries, $module) {
// Update Farbtastic to version 2.0.
if ($module == 'system' && isset($libraries['farbtastic'])) {
// Verify existing version is older than the one we are updating to.
if (version_compare($libraries['farbtastic']['version'], '2.0', '<')) {
// Update the existing Farbtastic to version 2.0.
$libraries['farbtastic']['version'] = '2.0';
$libraries['farbtastic']['js'] = array(
drupal_get_path('module', 'farbtastic_update') . '/farbtastic-2.0.js' => array(),
);
}
}
}
/**
* Perform alterations before a page is rendered.
*
......
......@@ -793,6 +793,359 @@ function system_menu() {
return $items;
}
/**
* Implementation of hook_library().
*/
function system_library() {
// jQuery.
$libraries['jquery'] = array(
'title' => 'jQuery',
'website' => 'http://jquery.com',
'version' => '1.3.2',
'js' => array(
'misc/jquery.js' => array('weight' => JS_LIBRARY - 20),
),
);
// jQuery Form Plugin.
$libraries['form'] = array(
'title' => 'jQuery Form Plugin',
'website' => 'http://malsup.com/jquery/form/',
'version' => '2.16',
'js' => array(
'misc/jquery.form.js' => array(),
),
);
// Farbtastic.
$libraries['farbtastic'] = array(
'title' => 'Farbtastic',
'website' => 'http://code.google.com/p/farbtastic/',
'version' => '1.2',
'js' => array(
'misc/farbtastic/farbtastic.js' => array(),
),
'css' => array(
'misc/farbtastic/farbtastic.css' => array('preprocess' => FALSE),
),
);
// Cookie.
$libraries['cookie'] = array(
'title' => 'Cookie',
'website' => 'http://plugins.jquery.com/project/cookie',
'version' => '1.0',
'js' => array(
'misc/jquery.cookie.js' => array(),
),
);
// jQuery UI.
$libraries['ui'] = array(
'title' => 'jQuery UI: Core',
'website' => 'http://jqueryui.com',
'version' => '1.7.2',
'js' => array(
'misc/ui/ui.core.js' => array('weight' => JS_LIBRARY - 10),
),
'css' => array(
'misc/ui/ui.core.css' => array(),
'misc/ui/ui.theme.css' => array(),
),
);
$libraries['ui.accordion'] = array(
'title' => 'jQuery UI: Accordion',
'website' => 'http://jqueryui.com/demos/accordion/',
'version' => '1.7.2',
'js' => array(
'misc/ui/ui.accordion.js' => array(),
),
'css' => array(
'misc/ui/ui.accordion.css' => array(),
),
'dependencies' => array(
array('system', 'ui'),
),
);
$libraries['ui.datepicker'] = array(
'title' => 'jQuery UI: Date Picker',
'website' => 'http://jqueryui.com/demos/datepicker/',
'version' => '1.7.2',
'js' => array(
'misc/ui/ui.datepicker.js' => array(),
),
'css' => array(
'misc/ui/ui.datepicker.css' => array(),
),
'dependencies' => array(
array('system', 'ui'),
),
);
$libraries['ui.dialog'] = array(
'title' => 'jQuery UI: Dialog',
'website' => 'http://jqueryui.com/demos/dialog/',
'version' => '1.7.2',
'js' => array(
'misc/ui/ui.dialog.js' => array(),
),
'css' => array(
'misc/ui/ui.dialog.css' => array(),
),
'dependencies' => array(
array('system', 'ui'),
),
);
$libraries['ui.draggable'] = array(
'title' => 'jQuery UI: Dialog',
'website' => 'http://jqueryui.com/demos/draggable/',
'version' => '1.7.2',
'js' => array(
'misc/ui/ui.draggable.js' => array(),
),
'dependencies' => array(
array('system', 'ui'),
),
);
$libraries['ui.droppable'] = array(
'title' => 'jQuery UI: Droppable',
'website' => 'http://jqueryui.com/demos/droppable/',
'version' => '1.7.2',
'js' => array(
'misc/ui/ui.droppable.js' => array(),
),
'dependencies' => array(
array('system', 'ui'),
array('system', 'ui.draggable'),
),
);
$libraries['ui.progressbar'] = array(
'title' => 'jQuery UI: Progress Bar',
'website' => 'http://jqueryui.com/demos/progressbar/',
'version' => '1.7.2',
'js' => array(
'misc/ui/ui.progressbar.js' => array(),
),
'css' => array(
'misc/ui/ui.progressbar.css' => array(),
),
'dependencies' => array(
array('system', 'ui'),
),
);
$libraries['ui.resizable'] = array(
'title' => 'jQuery UI: Resizable',
'website' => 'http://jqueryui.com/demos/resizable/',
'version' => '1.7.2',
'js' => array(
'misc/ui/ui.resizable.js' => array(),
),
'css' => array(
'misc/ui/ui.resizable.css' => array(),
),
'dependencies' => array(
array('system', 'ui'),
),
);
$libraries['ui.selectable'] = array(
'title' => 'jQuery UI: Selectable',
'website' => 'http://jqueryui.com/demos/selectable/',
'version' => '1.7.2',
'js' => array(
'misc/ui/ui.selectable.js' => array(),
),
'css' => array(
'misc/ui/ui.selectable.css' => array(),
),
'dependencies' => array(
array('system', 'ui'),
),
);
$libraries['ui.slider'] = array(
'title' => 'jQuery UI: Slider',
'website' => 'http://jqueryui.com/demos/slider/',
'version' => '1.7.2',
'js' => array(
'misc/ui/ui.slider.js' => array(),
),
'css' => array(
'misc/ui/ui.slider.css' => array(),
),
'dependencies' => array(
array('system', 'ui'),
),
);
$libraries['ui.sortable'] = array(
'title' => 'jQuery UI: Sortable',
'website' => 'http://jqueryui.com/demos/sortable/',
'version' => '1.7.2',
'js' => array(
'misc/ui/ui.sortable.js' => array(),
),
'dependencies' => array(
array('system', 'ui'),
),
);
$libraries['ui.tabs'] = array(
'title' => 'jQuery UI: Tabs',
'website' => 'http://jqueryui.com/demos/tabs/',
'version' => '1.7.2',
'js' => array(
'misc/ui/ui.tabs.js' => array(),
),
'css' => array(
'misc/ui/ui.tabs.css' => array(),
),
'dependencies' => array(
array('system', 'ui'),
),
);
$libraries['effects'] = array(
'title' => 'jQuery UI: Effects',
'website' => 'http://jqueryui.com/demos/effect/',
'version' => '1.7.2',
'js' => array(
'misc/ui/effects.core.js' => array('weight' => JS_LIBRARY - 9),
),
'dependencies' => array(
array('system', 'ui'),
),
);
$libraries['effects.blind'] = array(
'title' => 'jQuery UI: Effects Blind',
'website' => 'http://jqueryui.com/demos/effect/',
'version' => '1.7.2',
'js' => array(
'misc/ui/effects.blind.js' => array(),
),
'dependencies' => array(
array('system', 'effects'),