updated core to 7.58 (right after the site was hacked)
This commit is contained in:
693
sites/all/modules/contrib/fields/hierarchical_select/API.txt
Normal file
693
sites/all/modules/contrib/fields/hierarchical_select/API.txt
Normal file
@@ -0,0 +1,693 @@
|
||||
|
||||
Terminology
|
||||
-----------
|
||||
- item: an item in the hierarchy. A hierarchy can also be seen as a tree. In
|
||||
that case, an item can be either a parent or a child. However, if
|
||||
"multiple parents" are supported (i.e. a child can have multiple
|
||||
parents), then it's actually not a tree but a directed acyclic graph
|
||||
(see http://en.wikipedia.org/wiki/Directed_acyclic_graph), in which
|
||||
each case technically is a "node".
|
||||
An example: in the case of taxonomy, this is the term id (tid).
|
||||
- label: the label associated with an item in the hierarchy. You may now it
|
||||
as "title" or something else similar.
|
||||
An example: in the case of taxonomy, this is the actual term.
|
||||
- item type: a per-level, human-readable name that describes what kind of
|
||||
items that level contains.
|
||||
- entity: an item is often associated with an entity. E.g. a term is usually
|
||||
associated with a node.
|
||||
- form element: a form element allows the developer to assign a new value to
|
||||
a #type property in a form item. Examples of form elements
|
||||
supported by Drupal core are: select, checkboxes, textfield.
|
||||
- form item: an instance of a form element, with various other properties
|
||||
defined, such as #title, #default_value and #description. These
|
||||
are used to define a form in Drupal.
|
||||
- Hierarchical Select: this is the name of the module.
|
||||
- hierarchical_select: this is the internal name of the Hierarchical Select
|
||||
form element.
|
||||
- hierarchical select: (note the difference in case) this is the part of the
|
||||
widget with the multiple selects.
|
||||
- dropbox: this is the part of the widget where the selections are stored when
|
||||
multiple selections are allowed.
|
||||
|
||||
|
||||
Form API usage
|
||||
--------------
|
||||
You have to make sure your form item is using the "hierarchical_select" form
|
||||
element type:
|
||||
|
||||
$form['select_some_term'] = array(
|
||||
'#type' => 'hierarchical_select',
|
||||
'#title' => t('Select the tag you wish to use.'),
|
||||
'#size' => 1,
|
||||
'#config' => array(
|
||||
'module' => 'hs_taxonomy',
|
||||
'params' => array(
|
||||
'vid' => $vid,
|
||||
),
|
||||
'save_lineage' => 0,
|
||||
'enforce_deepest' => 0,
|
||||
'resizable' => 1,
|
||||
'level_labels' => array(
|
||||
'status' => 0,
|
||||
'labels' => array(
|
||||
0 => t('Main category'),
|
||||
1 => t('Subcategory'),
|
||||
2 => t('Third level category'),
|
||||
),
|
||||
),
|
||||
'dropbox' => array(
|
||||
'status' => 0,
|
||||
'title' => t('All selections'),
|
||||
'limit' => 0,
|
||||
'reset_hs' => 1,
|
||||
'sort' => 1,
|
||||
),
|
||||
'editability' => array(
|
||||
'status' => 0,
|
||||
'item_types' => array(),
|
||||
'allowed_levels' => array(
|
||||
0 => 0,
|
||||
1 => 0,
|
||||
2 => 1,
|
||||
),
|
||||
'allow_new_levels' => 0,
|
||||
'max_levels' => 3,
|
||||
),
|
||||
'entity_count' => array(
|
||||
'enabled' => 0,
|
||||
'require_entity' => 0,
|
||||
'settings' => array(
|
||||
'count_children' => 0,
|
||||
'entity_types' => array(),
|
||||
),
|
||||
),
|
||||
// These settings cannot be configured through the UI: they can only be
|
||||
// overridden through code.
|
||||
'animation_delay' => 400,
|
||||
'special_items' => array(),
|
||||
'render_flat_select' => 0,
|
||||
),
|
||||
'#default_value' => '83',
|
||||
);
|
||||
|
||||
Now, let's explain what we see here:
|
||||
1) We've set the #type property to "hierarchical_select" instead of "select".
|
||||
2) The #size property is inherited by the selects of the hierarchical select.
|
||||
You can use it to change a vertical size of the select (i.e. change how many
|
||||
items are displayed in the select, similar to a form select multiple).
|
||||
3) There's a new property: #config. This must be an
|
||||
array. These are the items it can contain:
|
||||
- module (required)
|
||||
This will be passed through in the AJAX requests, to let Hierarchical
|
||||
Select know which module's hooks should be used.
|
||||
|
||||
- params (optional, may be necessary for some implementations)
|
||||
An array of parameters that will also be passed through in every AJAX
|
||||
request.
|
||||
e.g. In the case of taxonomy, this is the vocabulary id (vid). In case of
|
||||
content_taxonomy, there's three parameters: vid, tid and depth (tid allows
|
||||
one to define a new root, depth allows one to limit the depth of the
|
||||
displayed hierarchy).
|
||||
|
||||
- save_lineage (optional, defaults to 0)
|
||||
Triggers the lineage saving functionality. If enabled, the selection can
|
||||
consist of multiple values.
|
||||
|
||||
- enforce_deepest (optional, defaults to 0)
|
||||
Triggers the enforcing of a selection in the deepest level. If enabled, the
|
||||
selection will always be a single value.
|
||||
|
||||
- resizable (optional, defaults to 1)
|
||||
Makes the hierarchical select resizable.
|
||||
|
||||
- level_labels['status'] (optional, defaults to 0)
|
||||
Whether level labels should be enabled or not. When save_lineage is
|
||||
enabled, this will result in *empty* level labels.
|
||||
|
||||
- level_labels['labels'] (optional)
|
||||
An array of labels, one per level. The label for the first level should be
|
||||
the value of key 0.
|
||||
When enforce_deepest is set to:
|
||||
- 0, then you can provide n level labels, with n the number of levels
|
||||
- 1, then you can provide only one level label.
|
||||
|
||||
- dropbox['status'] (optional, defaults to 0)
|
||||
Whether the dropbox is enabled or not (the dropbox allows the user to make
|
||||
multiple selections).
|
||||
|
||||
- dropbox['title'] (optional, defaults to "All selections:")
|
||||
The title of the dropbox. The dropbox is the area where all selections are
|
||||
displayed when the dropbox is enabled.
|
||||
|
||||
- dropbox['limit'] (optional, defaults to 0, which means "no limit")
|
||||
Limit the number of selection that can be added to the dropbox. So this
|
||||
allows you the restrict the number of items that can be selected when
|
||||
the dropbox has been enabled.
|
||||
|
||||
- dropbox['reset_hs'] (optional, defaults to 1, which means "do reset")
|
||||
Determines what will happen to the hierarchical select when the user has
|
||||
added a selection to the dropbox.
|
||||
|
||||
- dropbox['sort'] (optional, defaults to 1, which means "do sort")
|
||||
Determines whether the items in the dropbox will be automatically sorted.
|
||||
|
||||
- editability['status] (optional, defaults to 0)
|
||||
Allow the user to create new items in the hierarchy.
|
||||
|
||||
- editability['item_types'] (optional, defaults to the empty array)
|
||||
Only meaningful when editable is set to TRUE.
|
||||
Set the item type for each level. E.g.: "country" for the first level,
|
||||
"region" for the second and "city" for the third. When the user then wants
|
||||
to create a new item, the default label for the new item will be of the
|
||||
form "new <item type>", e.g. "new region".
|
||||
|
||||
- editability['allowed_levels'] (optional, defaults to 1 for each level)
|
||||
Only meaningful when editable is set to TRUE.
|
||||
Specify in which levels the user is allowed to create new items. In the
|
||||
example, the user is only allowed to create new items in the third level.
|
||||
When a setting for a level is ommitted, it defaults to 1 (i.e. allowed for
|
||||
that level). This means you only have to specify in which levels the user
|
||||
is not allowed to create new items.
|
||||
This only applies to *existing* levels: it does not affect the
|
||||
allow_new_levels setting (the next setting).
|
||||
|
||||
- editability['allow_new_levels'] (optional, defaults to 0)
|
||||
Only meaningful when editable is set to TRUE.
|
||||
Allow the user to create new levels, i.e. when a certain item does not yet
|
||||
have children, the user can create a first child for it (thus thereby
|
||||
creating a new level).
|
||||
|
||||
- editability['max_levels'] (optional, defaults to 3)
|
||||
Only meaningful when editable_settings['allow_new_levels'] is set to TRUE.
|
||||
Limits the maximum number of levels. Don't set this too high or you'll end
|
||||
up with very deep hierarchies. This only affects how deep new levels can be
|
||||
created, it will not affect the existing hierarchy.
|
||||
|
||||
- entity_count['enabled'] (optional, defaults to 0)
|
||||
Enables the display of entity counts, between parentheses, for each item in
|
||||
the hierarchy.
|
||||
|
||||
- entity_count['require_entity'] (optional, defaults to 0)
|
||||
Whether an item should only be displayed if it has at least one associated
|
||||
entity.
|
||||
|
||||
- entity_count['settings']['count_children'] (optional, defaults to 0)
|
||||
Whether the entity count should also count associated children of the entity.
|
||||
|
||||
- entity_count['settings']['entity_types'] (optional, defaults to array())
|
||||
Which types of entities should be counted. This is a list of checkboxes that
|
||||
allow the user to select entity types by bundles.
|
||||
|
||||
- animation_delay (optional, defaults to 400)
|
||||
The delay of each animation (the drop in left and right animations), in ms.
|
||||
|
||||
- special_items (optional, defaults to the empty array)
|
||||
Through this setting, you can mark each item with special properties it
|
||||
possesses. There currently are two special properties: 'exclusive' and
|
||||
'none'.
|
||||
Note: you should include these items in the hierarchy as if it were a
|
||||
normal item and then you can mark them as special through this property.
|
||||
* 'exclusive': Sometimes it's desirable to have exclusive lineages. When
|
||||
such an option is selected, the user should not be able to
|
||||
select anything else. This also means that nothing else in
|
||||
the dropbox can be selected: if the dropbox contains
|
||||
anything, it will be reset.
|
||||
Can be applied to multiple items.
|
||||
e.g. an 'entire_tree' item:
|
||||
'special_items' => array(
|
||||
'entire_tree' => array('exclusive'),
|
||||
)
|
||||
* 'none': Sometimes you want to replace the default '<none>' option by
|
||||
something else. This replacement should of course also exist in
|
||||
the root level.
|
||||
Can be applied to only one item.
|
||||
e.g. an 'any' item (used in hs_taxonomy_views):
|
||||
'special_items' => array(
|
||||
'any' => array('none', 'exclusive'),
|
||||
)
|
||||
And a final example for a better overview:
|
||||
'special_items' => array(
|
||||
'entire_tree' => array('exclusive'),
|
||||
'any' => array('none', 'exclusive'),
|
||||
)
|
||||
|
||||
- render_flat_select (optional, defaults to 0)
|
||||
Because the hierarchical_select form element consists of multiple form
|
||||
items, it doesn't work well in GET forms. By enabling this setting, a flat
|
||||
select will also be rendered, that contains only the selected lineages.
|
||||
Combine that with Drupal.HierarchicalSelect.prepareGETSubmit in the JS code
|
||||
(or, alternatively, the 'prepare-GET-submit' event that can be triggered,
|
||||
see the JavaScript events section for details) and you have a work-around
|
||||
(which, admittedly, only works when JS is enabled).
|
||||
|
||||
3) We *don't* specify a list of options: Hierarchical Select automatically
|
||||
generates the options for us, thanks to the 'module' and 'params' settings.
|
||||
|
||||
|
||||
Concepts
|
||||
--------
|
||||
- Item Unicity: each item in the hierarchy must be *unique*. It doesn't have
|
||||
to be numerical, it can also be a string.
|
||||
If your hierarchy does not have unique items by nature or by
|
||||
design (your items may be unique per level instead), that's
|
||||
not a problem. You can simply prepend the item's ancestors to
|
||||
get a unique item.
|
||||
e.g. you have an item "foobar" at the first, second and third
|
||||
levels. By prepending the ancestors using the dash as the
|
||||
separator, you'd get an item "foobar-foobar-foobar" at the
|
||||
third level.
|
||||
Also see the "Reserved item values" section.
|
||||
- #options: it's gone, because it was the inherent cause for scalability
|
||||
problems: if a hierarchy consists of 10,000 or even 100,000 items,
|
||||
this results in huge HTML being generated. Huge HTML means more
|
||||
processing power necessary, and more bandwidth necessary. So where
|
||||
does Hierarchical Select get its "options"? It uses the hooks that
|
||||
every implementation has to implement to only get what it needs.
|
||||
- The General Concept: you should think of Hierarchical Select as an abstract
|
||||
widget that can represent *any* hierarchy. To be able
|
||||
to display any hierarchy, you obviously need some
|
||||
universal way to "browse" a hierarchy.
|
||||
If you are familiar with C++ or Java iterators, this
|
||||
should come natural: the hooks you have to implement
|
||||
is what allows Hierarchical Select to iterate over your
|
||||
hierarchy. Then the heart of the iterator would be the
|
||||
root_level() and children() hooks. params() allows you
|
||||
to define which information is necessary before you can
|
||||
determine *which* hierarchy or which *part* of the
|
||||
hierarchy is being browsed. lineage() must return the
|
||||
lineage, i.e. the item itself and all its ancestors,
|
||||
this allows a hierarchy to be generated from just one
|
||||
(selected) item.
|
||||
|
||||
|
||||
Reserved item values
|
||||
--------------------
|
||||
- Ensure that your items don't have a "none", "all", "create_new_item" nor
|
||||
"label_\d+" values (the latter means "label_" followed by one or more
|
||||
digits). Your values should also not contain a pipe ("|"), since pipes are
|
||||
used to separate the selection of values that are sent back to the server
|
||||
in the callbacks.
|
||||
- Valid 'empty' selections (i.e. if you want to set the #default_value
|
||||
property of your form item), are -1 and the empty array. The empty string is
|
||||
also considered valid, because Drupal core's Taxonomy module uses this as
|
||||
the empty selection.
|
||||
|
||||
|
||||
Developer mode
|
||||
--------------
|
||||
When you are writing your implementation of the Hierarchical Select API, you
|
||||
will often wonder what Hierarchical Select is doing internally with the data
|
||||
you're feeding it. That's why there's a developer mode: it will show you this
|
||||
data, even the data generated in AJAX callbacks. It'll also show you the time
|
||||
it took to generate the lineage, to fill up the levels and to calculate the
|
||||
child info, to track down badly performing code.
|
||||
Also, when you're just creating a new HS config and it doesn't quite work
|
||||
right, it can be helpful to enable the developer mode. It will perform some
|
||||
basic diagnostics that might help you track down the cause.
|
||||
To use this, you must have a browser with console.log() support. Install
|
||||
Firebug Lite (http://getfirebug.com/lite.html) if your browser does not
|
||||
suport this. Next, go to Hierarchical Select's .module file and set the define
|
||||
for the HS_DEVELOPER_MODE constant to TRUE.
|
||||
When you now open Firebug (Firefox) or the Web Inspector (Safari), you'll see
|
||||
the debug output. New output is added after each callback to the server.
|
||||
|
||||
|
||||
Hierarchical Select implementations: gotcha's
|
||||
---------------------------------------------
|
||||
- "warning: Missing argument 1 for drupal_retrieve_form() …"
|
||||
This implies that your implementation's module weight is heavier than
|
||||
hierarchical_select.module. In that case, Hierarchical Select will not be
|
||||
able to detect hierarchical_select form items, preventing it from applying
|
||||
some magic, and AJAX updates won't work.
|
||||
|
||||
|
||||
Hierarchical Select compatibility: gotcha's
|
||||
-------------------------------------------
|
||||
- "Invalid response from server"
|
||||
This typically means that some functions could not be found when
|
||||
Hierarchical Select does an AJAX callback to the server, which in turn means
|
||||
that some code (some PHP file) has not been included, while it should have
|
||||
been. Instead of using module_load_include() or even require_once, you
|
||||
should use form_load_include(). This function is new in Drupal 7 and will
|
||||
ensure that all required PHP files are included automatically.
|
||||
|
||||
|
||||
Hierarchical Select API Tutorial
|
||||
--------------------------------
|
||||
Written by Stephen Barker of Digital Frontiers Media
|
||||
(http://drupal.org/user/106070) and reviewed by Wim Leers:
|
||||
http://drupal.org/node/532724
|
||||
|
||||
|
||||
Hierarchical Select Small Hierarchy
|
||||
-----------------------------------
|
||||
Hierarchical Select includes a Hierarchical Select API implementation that
|
||||
allows one to use a hardcoded hierarchy. When it becomes to slow, you should
|
||||
move the hierarchy into the database and write a proper implementation.
|
||||
Below you can find an example of how to use the hs_smallhierarchy module. Just
|
||||
change the $hierarchy array to suit your needs and off you go! Look at the
|
||||
code of hs_smallhierarchy.module for full details, but this code example
|
||||
should get you started.
|
||||
|
||||
$hierarchy = array(
|
||||
'win' => array(
|
||||
'label' => 'Windows',
|
||||
'children' => array(
|
||||
'xp' => array('label' => 'XP'),
|
||||
'vista' => array(
|
||||
'label' => 'Vista',
|
||||
'children' => array(
|
||||
'x86' => array('label' => '32-bits'),
|
||||
'x64' => array('label' => '64-bits'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$form['select_some_term'] = array(
|
||||
'#type' => 'hierarchical_select',
|
||||
'#title' => t('Select the tag you wish to use.'),
|
||||
'#size' => 1,
|
||||
'#config' => array(
|
||||
'module' => 'hs_smallhierarchy',
|
||||
'params' => array(
|
||||
'hierarchy' => $hierarchy,
|
||||
'id' => 'my-hierarchy-about-windows',
|
||||
'separator' => '|',
|
||||
),
|
||||
'save_lineage' => 0,
|
||||
'enforce_deepest' => 0,
|
||||
'resizable' => 1,
|
||||
'level_labels' => array(
|
||||
'status' => 0,
|
||||
'labels' => array(
|
||||
0 => t('Main category'),
|
||||
1 => t('Subcategory'),
|
||||
2 => t('Third level category'),
|
||||
),
|
||||
),
|
||||
'dropbox' => array(
|
||||
'status' => 0,
|
||||
'title' => t('All selections'),
|
||||
'limit' => 0,
|
||||
'reset_hs' => 1,
|
||||
'sort' => 1,
|
||||
),
|
||||
'editability' => array(
|
||||
'status' => 0,
|
||||
'item_types' => array(),
|
||||
'allowed_levels' => array(
|
||||
0 => 0,
|
||||
1 => 0,
|
||||
2 => 1,
|
||||
),
|
||||
'allow_new_levels' => 0,
|
||||
'max_levels' => 3,
|
||||
),
|
||||
'entity_count' => array(
|
||||
'enabled' => 0,
|
||||
'require_entity' => 0,
|
||||
'settings' => array(
|
||||
'count_children' => 0,
|
||||
'entity_types' => array(),
|
||||
),
|
||||
),
|
||||
// These settings cannot be configured through the UI: they can only be
|
||||
// overridden through code.
|
||||
'animation_delay' => 400,
|
||||
'exclusive_lineages' => array(),
|
||||
'render_flat_select' => 0,
|
||||
),
|
||||
'#description' => 'Put your description here',
|
||||
'#default_value' => 'win|xp|x86',
|
||||
);
|
||||
|
||||
|
||||
Hooks
|
||||
-----
|
||||
1) hook_hierarchical_select_params();
|
||||
Returns an array with the names of all parameters that are necessary for
|
||||
this implementation to work.
|
||||
|
||||
2) hook_hierarchical_select_root_level($params, $dropbox = FALSE);
|
||||
Returns the root level of the hierarchy: an array of (item, label) pairs.
|
||||
The $dropbox parameter can is optional and can even ommitted, as it's only
|
||||
necessary if you need the dropbox to influence your hierarchy.
|
||||
|
||||
3) hook_hierarchical_select_children($parent, $params, $dropbox = FALSE);
|
||||
Gets the children of $parent ($parent is an item in the hierarchy) and
|
||||
returns them: an array of (item, label) pairs, or the empty array if the
|
||||
given $parent has no children.
|
||||
The $dropbox parameter can is optional and can even ommitted, as it's only
|
||||
necessary if you need the dropbox to influence your hierarchy.
|
||||
|
||||
4) hook_hierarchical_select_lineage($item, $params);
|
||||
Calculates the lineage of $item (array of items, with $item the last) and
|
||||
returns it. Necessary when the "enforce_deepest" option is enabled.
|
||||
|
||||
5) hook_hierarchical_select_valid_item($item, $params);
|
||||
Validates an item, returns TRUE if valid, FALSE if invalid.
|
||||
|
||||
6) hook_hierarchical_select_item_get_label($item, $params);
|
||||
Given a valid item, returns the label. Is only used for rendering the
|
||||
selections in the dropbox.
|
||||
|
||||
7) hook_hierarchical_select_create_item($label, $parent, $params);
|
||||
Given a parent item and the label of a new item, create a new item as a
|
||||
child of the parent item. When $parent == 0, this means a new item is being
|
||||
created at the root level.
|
||||
Optional hook. When this hook is not implemented, this functionality will
|
||||
never be used, even when you configure it that way in code.
|
||||
|
||||
8) hook_hierarchical_select_entity_count($item, $params);
|
||||
Given a item, get the number of entities (most of the time the entity type
|
||||
is 'node') that are related to the given item. Used for the entity_count
|
||||
and require_entity settings.
|
||||
Optional hook. When this hook is not implemented, this functionality will
|
||||
never be used, even when you configure it that way (i.e. when you enable
|
||||
the entity_count and require_entity settings).
|
||||
|
||||
9) hook_hierarchical_select_implementation_info();
|
||||
Return metadata about this implementation.
|
||||
This information is used to generate the implementations overview at
|
||||
admin/settings/hierarchical_select/implementations. The expected format is:
|
||||
|
||||
array(
|
||||
'hierarchy type' => t('Taxonomy'),
|
||||
'entity type' => t('Node'),
|
||||
'entity' => t('Story'),
|
||||
'context type' => t('Node form'),
|
||||
'context' => '',
|
||||
);
|
||||
|
||||
another example:
|
||||
|
||||
array(
|
||||
'hierarchy type' => t('Taxonomy'),
|
||||
'entity type' => t('Node'),
|
||||
'entity' => '',
|
||||
'context type' => t('Views exposed filter'),
|
||||
'context' => t('some view'),
|
||||
);
|
||||
|
||||
10) hook_hierarchical_select_config_info();
|
||||
Return metadata about each available user-editable configuration for this
|
||||
implementation.
|
||||
Optional hook. This information is used to generate the configurations
|
||||
overview at admin/settings/hierarchical_select/configs. The expected
|
||||
format is:
|
||||
|
||||
$config_info[$config_id] = array(
|
||||
'config_id' => $config_id,
|
||||
'hierarchy type' => t('Taxonomy'),
|
||||
'hierarchy' => t($vocabulary->name),
|
||||
'entity type' => t('Node'),
|
||||
'entity' => implode(', ', array_map('t', $entities)),
|
||||
'edit link' => "admin/content/taxonomy/edit/vocabulary/$vid",
|
||||
);
|
||||
|
||||
|
||||
Standardized configuration form
|
||||
-------------------------------
|
||||
Hierarchical Select 3 comes with a standardized configuration form:
|
||||
hierarchical_select_common_config_form(). This function accepts a lot of
|
||||
parameters, which allows you to use names typical to your module's hierarchy
|
||||
(e.g. 'leaf' instead of 'term' and 'tree' instead of 'vocabulary'). A submit
|
||||
handler is also provided, of course.
|
||||
An example:
|
||||
|
||||
// I'm not configuring all parameters here. For an example of that, see one
|
||||
// of the included modules.
|
||||
$form['foobar_hierarchical_select_config'] = hierarchical_select_common_config_form($module, $params, $config_id, $defaults, $strings, $max_hierarchy_depth, $preview_is_required);
|
||||
|
||||
// Add the the submit handler for the Hierarchical Select config form.
|
||||
$parents = array('foobar_hierarchical_select_config');
|
||||
$form['#submit'][] = 'hierarchical_select_common_config_form_submit';
|
||||
$form['#hs_common_config_form_parents'] = $parents;
|
||||
|
||||
|
||||
Configuration management
|
||||
------------------------
|
||||
It's now possible to export Hierarchical Select configurations, and there is a
|
||||
function to set the configuration of a certain Hierarchical Select. Combine
|
||||
the two and you can manage your Hierarchical Select configurations in code!
|
||||
An example:
|
||||
|
||||
// The exported configuration.
|
||||
$config = array( … );
|
||||
$config_id = $config['config_id];
|
||||
|
||||
// Apply the configuration.
|
||||
require_once(drupal_get_path('module', 'hierarchical_select') .'/includes/common.inc');
|
||||
hierarchical_select_common_config_set($config_id, $config);
|
||||
|
||||
|
||||
JavaScript events
|
||||
-----------------
|
||||
The Hierarchical Select module's JavaScript code triggers several events, to
|
||||
allow for advanced interactions.
|
||||
|
||||
You can find all hierarchical_select form items using this selector:
|
||||
|
||||
$('.hierarchical-select-wrapper');
|
||||
|
||||
You can find a *specific* hierarchical_select form item using this selector:
|
||||
|
||||
$('#hierarchical-select-x-wrapper');
|
||||
|
||||
where x is a number, or more accurately: a hsid (hierarchical select id).
|
||||
Retrieving all hsids in the current document can be done like this:
|
||||
|
||||
for (var hsid in Drupal.settings.HierarchicalSelect.settings) {
|
||||
// …
|
||||
}
|
||||
|
||||
Alternatively, you can use one of the transliterated class names. A wrapper
|
||||
for Hierarchical Select looks like this:
|
||||
<div class="hierarchical-select-wrapper
|
||||
hierarchical-select-level-labels-style-none
|
||||
hierarchical-select-wrapper-for-name-edit-taxonomy-1
|
||||
hierarchical-select-wrapper-for-config-taxonomy-1
|
||||
hierarchical-select-wrapper-processed"
|
||||
id="hierarchical-select-35-wrapper">
|
||||
…
|
||||
</div>
|
||||
Hence, you could also use selectors such as these, to achieve the same effect,
|
||||
but with more robust code:
|
||||
$('.hierarchical-select-wrapper-for-config-taxonomy-1:first')
|
||||
.trigger('enforce-update');
|
||||
$('.hierarchical-select-wrapper-for-name-edit-taxonomy-1:first')
|
||||
.trigger('enforce-update');
|
||||
|
||||
The following events are triggered:
|
||||
- change-hierarchical-select
|
||||
- update-hierarchical-select
|
||||
- create-new-item
|
||||
- cancel-new-item
|
||||
- add-to-dropbox (check https://www.drupal.org/node/1277068)
|
||||
- remove-from-dropbox
|
||||
- enforced-update
|
||||
- prepared-GET-submit
|
||||
All events are triggered *after* the animations have completed.
|
||||
|
||||
However, it's often useful to do something *before* an event (especially
|
||||
because all of the above events perform an AJAX request to the server). So,
|
||||
the equivalent "before" events exist as well:
|
||||
- before-update-hierarchical-select
|
||||
- before-create-new-item
|
||||
- before-cancel-new-item
|
||||
- before-add-to-dropbox
|
||||
- before-remove-from-dropbox
|
||||
- before-enforced-update
|
||||
There is one exception: when the cache is enabled, the "before update
|
||||
hierarchical select" event will not be triggered. This makes sense, because
|
||||
updates from the cache are instantaneous.
|
||||
|
||||
An example of binding a function to the 'create-new-item' event of the second
|
||||
(hsid == 1) hierarchical_select form item on the page:
|
||||
|
||||
$('#hierarchical-select-1-wrapper')
|
||||
.bind('create-new-item', function() {
|
||||
// …
|
||||
});
|
||||
|
||||
And finally, you can trigger a special event to enforce an update (this can be
|
||||
useful when you have changed a hierarchy through another form item, or for
|
||||
live previews, or …). You can then also pass additional information that will
|
||||
be POSTed. You can even disable normal updates, to manage that completely
|
||||
yourself via enforced updates. This allows you to write a Hierarchical Select
|
||||
implementation that gets some of its information ($params) from another form
|
||||
item!
|
||||
Suppose you'd like to enforce an update of the first (hsid == 0)
|
||||
hierarchical_select form item on the page:
|
||||
|
||||
$('#hierarchical-select-0-wrapper')
|
||||
.trigger('enforce-update');
|
||||
|
||||
Now let's move on to a more advanced example, in which we will disable normal
|
||||
updates and let another form item (here a select) provide a part of the
|
||||
information that will be used to render the Hierarchical Select. Effectively,
|
||||
this other form item will *influence* the hierarchy that will be presented by
|
||||
Hierarchical Select!
|
||||
|
||||
$(document).ready(function() {
|
||||
Drupal.settings.specialfilter = {};
|
||||
|
||||
// .specialfilter-first: a select form item
|
||||
// .specialfilter-second: a hierarchical_select form item
|
||||
|
||||
update = function() {
|
||||
var selection = Drupal.settings.specialfilter.currentSelection;
|
||||
|
||||
// Send an extra parameter via POST: dynamicParameter. This is the stored
|
||||
// selection.
|
||||
$('.specialfilter-second')
|
||||
.trigger('enforce-update',
|
||||
[
|
||||
{ name : 'dynamicParameter', value : selection }
|
||||
]
|
||||
);
|
||||
};
|
||||
|
||||
attachHSBindings = function() {
|
||||
// When a user navigates the hierarchical_select form item, we still want to
|
||||
// POST the the extra dynamicParameter, or otherwise we will no longer have
|
||||
// a hierarchy in the hierarchical_select form item that really depends on
|
||||
// the select.
|
||||
$('.specialfilter-second .hierarchical-select > select')
|
||||
.change(function() { update(); });
|
||||
|
||||
$('.specialfilter-second')
|
||||
.unbind('enforced-update').bind('enforced-update', function() { return attachHSBindings(); });
|
||||
};
|
||||
|
||||
// Initialize after 25 ms, because otherwise the event binding of HS will
|
||||
// not yet be ready, and hence this won't have any effect
|
||||
setTimeout(function() {
|
||||
// Get the initial selection (before the user has changed anything).
|
||||
Drupal.settings.specialfilter.currentSelection = $('.specialfilter-first').attr('value');
|
||||
|
||||
// When the select form item changes, we want to *store* that selection, and
|
||||
// update the hierarchical_select form item.
|
||||
$('.specialfilter-first')
|
||||
.change(function() {
|
||||
// Store the current selection.
|
||||
Drupal.settings.specialfilter.currentSelection = $(this).attr('value');
|
||||
|
||||
update();
|
||||
});
|
||||
|
||||
$('.specialfilter-second')
|
||||
.trigger('disable-updates');
|
||||
|
||||
attachHSBindings();
|
||||
}, 25);
|
||||
});
|
||||
|
||||
The 'enforced-update' (notice the past tense!) event is triggered upon
|
||||
completion.
|
||||
An even more rarely used special event can be triggered to prepare the
|
||||
hierarchical_select form element for a get submit: the 'prepare GET submit'
|
||||
event. To use this event, the 'render_flat_select' setting should be enabled
|
||||
in the config.
|
339
sites/all/modules/contrib/fields/hierarchical_select/LICENSE.txt
Normal file
339
sites/all/modules/contrib/fields/hierarchical_select/LICENSE.txt
Normal file
@@ -0,0 +1,339 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
200
sites/all/modules/contrib/fields/hierarchical_select/README.txt
Normal file
200
sites/all/modules/contrib/fields/hierarchical_select/README.txt
Normal file
@@ -0,0 +1,200 @@
|
||||
|
||||
Description
|
||||
-----------
|
||||
This module defines the "hierarchical_select" form element, which is a greatly
|
||||
enhanced way for letting the user select items in a hierarchy.
|
||||
|
||||
Hierarchical Select has the ability to save the entire lineage of a selection
|
||||
or only the "deepest" selection. You can configure it to force the user to
|
||||
make a selection as deep as possible in the tree, or allow the user to select
|
||||
an item anywhere in the tree. Levels can be labeled, you can configure limit
|
||||
the number of items that can be selected, configure a title for the dropbox,
|
||||
choose a site-wide animation delay, and so on. You can even create new items
|
||||
and levels through Hierarchical Select!
|
||||
|
||||
|
||||
Integrates with
|
||||
---------------
|
||||
* Taxonomy (Drupal core)
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
1) Place this module directory in your "modules" folder (this will usually be
|
||||
"sites/all/modules/"). Don't install your module in Drupal core's "modules"
|
||||
folder, since that will cause problems and is bad practice in general. If
|
||||
"sites/all/modules" doesn't exist yet, just create it.
|
||||
|
||||
2) Enable the Hierarchical Select and Hierarchical Select Taxonomy modules.
|
||||
|
||||
3) If you want to use it for one or more of your vocabularies, go to
|
||||
admin/structure/types and click the "manage fields" link for a content type on
|
||||
which you're using a Term reference field. Click the "edit" link for this Term
|
||||
reference field and then go to the "widget type" tab in the upper right corner.
|
||||
There, you can choose the "Hierarchical Select" widget type, and when you do,
|
||||
the entire Hierarchical Select configuration UI will appear: here you'll find
|
||||
a whole range of Hierarchical Select settings. All settings are explained
|
||||
there as well!
|
||||
|
||||
|
||||
Troubleshooting
|
||||
---------------
|
||||
If you ever have problems, make sure to go through these steps:
|
||||
|
||||
1) Go to admin/reports/status (i.e. the Status Report). Ensure that the status
|
||||
of the Hierarchical Select module is ok.
|
||||
|
||||
2) Ensure that the page isn't being served from your browser's cache. Use
|
||||
CTRL+R in Windows/Linux browsers, CMD+R in Mac OS X browsers to enforce the
|
||||
browser to reload everything, preventing it from using its cache.
|
||||
|
||||
3) When you're getting a JS alert with the following message: "Received an
|
||||
invalid response from the server.", ensure that the page (of which this
|
||||
form is a part) is *not* being cached.
|
||||
|
||||
4) When Hierarchical Select seems to be misbehaving in a certain use case in
|
||||
which terms with multiple parents are being used, make sure to enable the
|
||||
"Save term lineage" setting.
|
||||
Note: you may have to repeat this for every configuration in which the
|
||||
vocabulary with terms that have multiple parents are being used. E.g. if
|
||||
such a vocabulary is called "A", then go to
|
||||
admin/config/content/hierarchical_select/configs
|
||||
and edit all configuration that have "A" in the "Hierarchy" column.
|
||||
|
||||
In case of problems, don't forget to try a hard refresh in your browser!
|
||||
|
||||
|
||||
Limitations
|
||||
-----------
|
||||
- Creating new items in the hierarchy in a multiple parents hierarchy (more
|
||||
scientifically: a directed acyclic graph) is *not* supported.
|
||||
- Not the entire scalability problem can be solved by installing this set of
|
||||
modules; read the maximum scalability section for details.
|
||||
- The child indicators only work in Firefox. This *cannot* be supported in
|
||||
Safari or IE. See http://drupal.org/node/180691#comment-1044691.
|
||||
- The special [save-lineage-termpath] token only works with content_taxonomy
|
||||
fields as long as you have the "Save option" set to either "Tag" or "Both".
|
||||
- In hierarchies where items can have multiple parent items and where you have
|
||||
enabled Hierarchical Select's "save lineage" setting, it is impossible to
|
||||
remember individual hierarchies, unless the underlying module supports it.
|
||||
So far, no module supports this. Hierarchical Select is just a form element,
|
||||
not a system for storing hierarchies.
|
||||
For example, if you have created a multiple parent vocabulary through the
|
||||
Taxonomy module, and you have terms like this:
|
||||
A -> C
|
||||
A -> D
|
||||
B -> C
|
||||
B -> D
|
||||
If you then save any two lineages in which all four terms exist, all four
|
||||
lineages will be rendered by Hierarchical Select, because only the four
|
||||
terms are stored and thus there is no way to recover the originally selected
|
||||
two lineages.
|
||||
- You can NOT expect the Hierarchical Select Taxonomy module to automagically
|
||||
fix all existing nodes when you enable or disable the "save lineage" setting
|
||||
and neither can you expect it to keep working properly when you reorganize
|
||||
the term hierarchy. There's nothing I can do about this. Hierarchical Select
|
||||
is merely a form element, it can't be held responsible for features that
|
||||
Drupal core lacks or supports poorly.
|
||||
See the following issues:
|
||||
* http://drupal.org/node/1023762#comment-4054386
|
||||
* http://drupal.org/node/976394#comment-4054456
|
||||
|
||||
|
||||
Rendering hierarchy lineages when viewing content
|
||||
-------------------------------------------------
|
||||
Hierarchical Select is obviously only used for input. Hence it is only used on
|
||||
the create/edit forms of content.
|
||||
Combine that with the fact that Hierarchical Select is the only module capable
|
||||
of restoring the lineage of saved items (e.g. Taxonomy terms). None of the
|
||||
Drupal core modules is capable of storing the lineage, but Hierarchical Select
|
||||
can reconstruct it relatively efficiently. However, this lineage is only
|
||||
visible when creating/editing content, not when viewing it.
|
||||
To allow you to display the lineages of stored items, I have provided a
|
||||
theming function that you can call from within e.g. your node.tpl.php file:
|
||||
the theme_hierarchical_select_selection_as_lineages($selection, $config)
|
||||
function.
|
||||
|
||||
Sample usage (using Taxonomy and Hierarchical Select Taxonomy):
|
||||
<?php if ($taxonomy):
|
||||
require_once(drupal_get_path('module', 'hierarchical_select') . '/includes/common.inc');
|
||||
$vid = 2; // Vocabulary ID. CHANGE THIS!
|
||||
$config_id = "taxonomy-$vid"; // Generate the config ID.
|
||||
$config = hierarchical_select_common_config_get($config_id); // Get the Hierarchical Select configuration through the config ID.
|
||||
$config['module'] = 'hs_taxonomy'; // Set the module.
|
||||
$config['params']['vid'] = $vid; // Set the parameters.
|
||||
?>
|
||||
<div class="terms"><?php print theme('hierarchical_select_selection_as_lineages', $node->taxonomy, $config); ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
This will automatically render all lineages for vocabulary 2 (meaning that if
|
||||
you want to render the lineages of multiple vocabularies, you'll have to clone
|
||||
this piece of code once for every vocabulary). It will also automatically get
|
||||
the current Hierarchical Select configuration for that vocabulary.
|
||||
|
||||
Alternatively, you could provide the $config array yourself. Only three keys
|
||||
are required: 1) module, 2) params, 3) save_lineage. For example:
|
||||
<?php if ($taxonomy):
|
||||
$vid = 2; // Vocabulary ID. CHANGE THIS!
|
||||
$config['module'] = 'hs_taxonomy'; // Set the module.
|
||||
$config['params']['vid'] = $vid; // Set the parameters.
|
||||
$config['save_lineage'] = 1; // save_lineage setting is enabled. CHANGE THIS!
|
||||
?>
|
||||
<div class="terms"><?php print theme('hierarchical_select_selection_as_lineages', $node->taxonomy, $config); ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
If you don't like how the lineage is displayed, simply override the
|
||||
theme_hierarchical_select_selection_as_lineages() function from within your
|
||||
theme, create e.g. garland_hierarchical_select_selection_as_lineages().
|
||||
|
||||
It's also worth mentioning that the 'hs_taxonomy_tree' tag was added to the
|
||||
queries that build the term tree. As a result now you can easily change/filter
|
||||
the elements that are selected by the module (see hs_taxonomy.module for more
|
||||
info).
|
||||
|
||||
|
||||
Setting a fixed size
|
||||
--------------------
|
||||
When you don't want users to be able to resize a hierarchical select
|
||||
themselves, you can set a fixed size in advance yourself
|
||||
Setting #size to >1 does *not* generate #multiple = TRUE selects! And the
|
||||
opposite is also true. #multiple sets the "multiple" HTML attribute. This
|
||||
enables the user to select multiple options of a select. #size just controls
|
||||
the "size" HTML attribute. This increases the vertical size of selects,
|
||||
thereby showing more options.
|
||||
See http://www.w3.org/TR/html401/interact/forms.html#adef-size-SELECT.
|
||||
|
||||
|
||||
Sponsors
|
||||
--------
|
||||
* Initial development:
|
||||
Paul Ektov of http://autobin.ru.
|
||||
* Abstraction, to let other modules than taxonomy hook in:
|
||||
Etienne Leers of http://creditcalc.biz.
|
||||
* Support for saving the term lineage:
|
||||
Paul Ektov of http://autobin.ru.
|
||||
* Multiple select support:
|
||||
Marmaladesoul, http://marmaladesoul.com.
|
||||
* Taxonomy Subscriptions support:
|
||||
Mr Bidster Inc.
|
||||
* Ability to create new items/levels:
|
||||
The Worx Company, http://www.worxco.com.
|
||||
* Ability to only show items that are associated with at least one entity:
|
||||
Merge, http://merge.nl.
|
||||
* Views 2 support:
|
||||
Merge, http://merge.nl.
|
||||
* Initial Drupal 7 port + folow-up fixes:
|
||||
PingV, http://pingv.com.
|
||||
* Port of "save lineage" functionality to Drupal 7:
|
||||
Bancard Data Service
|
||||
|
||||
|
||||
Author
|
||||
------
|
||||
Wim Leers
|
||||
|
||||
* website: http://wimleers.com/
|
||||
* contact: http://wimleers.com/contact
|
||||
|
||||
The author can be contacted for paid development on this module. This can vary
|
||||
from new features to Hierarchical Select itself, to new implementations (i.e.
|
||||
support for new kinds of hierarchies).
|
@@ -0,0 +1,44 @@
|
||||
HS core:
|
||||
✓ port: initial port
|
||||
✓ fix: JS code cleanup (remove hardcoded hacks)
|
||||
✓ fix: title + description (i.e. something's off with the theme wrapper)
|
||||
✓ fix: #value_callback may be necessary? (see file.module) OR: ensure #return_value works
|
||||
✓ fix: #element_validate callback: _hierarchical_select_validate() — verify this still works
|
||||
✓ port: support multiple HS on the same page
|
||||
✓ port: admin UI
|
||||
✓ port: "dropbox" support
|
||||
✓ upgrade path: delete cache_hierarchical_select
|
||||
✓ upgrade path: documentation
|
||||
✓ port: "create new item" support — see http://drupal.org/node/1087620
|
||||
✓ port: status report
|
||||
- port: render_flat_select support
|
||||
- port: client-side caching (use _hierarchical_select_json_convert_hierarchy_to_cache())
|
||||
- feature: live preview of HS on the common config form
|
||||
- refactor: use the proper #value_callback -> #process callback -> #after_build callback pipeline as described in the documentation for form_builder() in form.inc
|
||||
|
||||
Taxonomy:
|
||||
✓ port: admin UI
|
||||
✓ port: "dropbox" support
|
||||
✓ port: "save lineage" support (i.e. support multiple parents, automatic warning shown through hs_taxonomy_hierarchical_select_root_level())
|
||||
✓ port: field formatters (from content_taxonomy)
|
||||
✓ port: taxonomy term (create/edit) form should be altered to include HS
|
||||
✓ upgrade path: migrate settings (no migration necessary)
|
||||
✓ upgrade path: documentation (no migration, no docs)
|
||||
✓ port: "create new item" support — see http://drupal.org/node/1087620
|
||||
- port: "entity_count" support — see http://drupal.org/node/1068462
|
||||
- refactor: use the vocabulary machine name internally instead of the vid
|
||||
- port: token support — see http://drupal.org/node/1248908
|
||||
- port: forum support
|
||||
- refactor: optimize HS API implementation: take advantage of improvements in Taxonomy
|
||||
|
||||
HS Taxonomy Views:
|
||||
- everything — see http://drupal.org/node/1170192
|
||||
|
||||
Menu:
|
||||
✓ everything
|
||||
|
||||
Flat List:
|
||||
✓ everything
|
||||
|
||||
Small Hierarchy:
|
||||
✓ everything
|
@@ -0,0 +1,20 @@
|
||||
# Upgrading (from Drupal 6 to 7)
|
||||
|
||||
1. **BE WARE THAT NOT ALL FUNCTIONALITY HAS BEEN PORTED!**
|
||||
|
||||
Make sure that you know if the part of Hierarchical Select's functionality
|
||||
that you want to use has been ported. Otherwise, you may be in for a
|
||||
frustrating upgrade experience.
|
||||
|
||||
See the included TODO.txt file for details. In a nutshell:
|
||||
|
||||
- Taxonomy support is almost complete, only "create new item", "entity count" and token support are missing
|
||||
- Forum support has **not** yet been ported (but relies on Taxonomy, so this is trivial)
|
||||
- Taxonomy Views support has **not** yet been ported
|
||||
- Menu support has **not** yet been ported
|
||||
|
||||
2. Upgrade this module just like any other: delete the old module, copy the
|
||||
files of the new module and run update.php.
|
||||
For details, see <http://drupal.org/node/570162>.
|
||||
|
||||
3. That's it! :)
|
@@ -0,0 +1,57 @@
|
||||
|
||||
|
||||
/* The hierarchical select. */
|
||||
.hierarchical-select-wrapper .hierarchical-select .selects {
|
||||
float: right; /* If a block is floated, it won't consume as much width as
|
||||
available, only just enough. This allows the grippie to
|
||||
perfectly scale with the with consumed by the selects. */
|
||||
}
|
||||
|
||||
.hierarchical-select-wrapper .hierarchical-select .selects .grippie {
|
||||
clear: right; /* clear: left; */
|
||||
height: 9px;
|
||||
overflow: hidden;
|
||||
background: #eee url(images/grippie.png) no-repeat center 2px;
|
||||
border: 1px solid #ddd;
|
||||
border-top-width: 0;
|
||||
cursor: s-resize;
|
||||
margin-left: 0.5em; /* margin-right: 0.5em; */ /* Give the grippie the same margin as each select. */
|
||||
min-width: 70px; /* Hack for IE, makes the grip usable, but not yet the same as in other browsers. */
|
||||
}
|
||||
|
||||
.hierarchical-select-wrapper .hierarchical-select select,
|
||||
.hierarchical-select-wrapper .hierarchical-select .add-to-dropbox,
|
||||
.hierarchical-select-wrapper .hierarchical-select .create-new-item {
|
||||
margin-left: .5em;
|
||||
margin-right: 0; /* Reset ltr style */
|
||||
float: right;
|
||||
}
|
||||
|
||||
|
||||
/* The pseudo-modal window for creating a new item or new level. */
|
||||
.hierarchical-select-wrapper .hierarchical-select .create-new-item-create,
|
||||
.hierarchical-select-wrapper .hierarchical-select .create-new-item-cancel {
|
||||
float: left;
|
||||
margin-right: .4em;
|
||||
margin-left: 0; /* Reset ltr style */
|
||||
}
|
||||
|
||||
.hierarchical-select-wrapper .hierarchical-select .create-new-item-input {
|
||||
float: right;
|
||||
clear: left;
|
||||
}
|
||||
|
||||
|
||||
/* Child level indicator. */
|
||||
.hierarchical-select-wrapper .hierarchical-select option.has-children {
|
||||
background: url(images/arrow-rtl.png) no-repeat left center;
|
||||
padding-left: 20px;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
|
||||
/* Dropbox limit warning.*/
|
||||
p.hierarchical-select-dropbox-limit-warning {
|
||||
padding-right: .5em;
|
||||
padding-left: 0; /* Reset ltr style */
|
||||
}
|
@@ -0,0 +1,313 @@
|
||||
<?php
|
||||
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Module settings and configuration administration UI.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Form definition; admin settings.
|
||||
*/
|
||||
function hierarchical_select_admin_settings($form, &$form_state) {
|
||||
$form['description'] = array(
|
||||
'#markup' => t('All settings below will be used as site-wide defaults.'),
|
||||
'#prefix' => '<div>',
|
||||
'#suffix' => '</div>',
|
||||
);
|
||||
$form['hierarchical_select_animation_delay'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Animation delay'),
|
||||
'#description' => t(
|
||||
'The delay that will be used for the "drop in/out" effect when a
|
||||
hierarchical select is being updated (in milliseconds).'
|
||||
),
|
||||
'#size' => 5,
|
||||
'#maxlength' => 5,
|
||||
'#default_value' => variable_get('hierarchical_select_animation_delay', 400),
|
||||
);
|
||||
$form['hierarchical_select_level_labels_style'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Level labels style'),
|
||||
'#description' => t(
|
||||
'The style that will be used for level labels. This is not supported by
|
||||
all browsers! If you want a consistent interface, choose to use no
|
||||
style.'
|
||||
),
|
||||
'#options' => array(
|
||||
'none' => t('No style'),
|
||||
'bold' => t('Bold'),
|
||||
'inversed' => t('Inversed'),
|
||||
'underlined' => t('Underlined'),
|
||||
),
|
||||
'#default_value' => variable_get('hierarchical_select_level_labels_style', 'none'),
|
||||
);
|
||||
// TODO: port the HS client-side cache system to Drupal 7.
|
||||
/*
|
||||
$form['hierarchical_select_js_cache_system'] = array(
|
||||
'#type' => 'radios',
|
||||
'#title' => t('Cache in a HTML 5 client-side database'),
|
||||
'#description' => t(
|
||||
'This feature only works in browsers that support the
|
||||
<a href="!spec-url">HTML 5 client-side database storage specification
|
||||
</a>.</br>
|
||||
After enabling this, you will notice (in supporting browsers) that
|
||||
refreshing the hierarchical select will not require a request to the
|
||||
server when a part is being requested that has been requested before.',
|
||||
array('!spec-url' => url('http://www.whatwg.org/specs/web-apps/current-work/multipage/section-sql.html'))
|
||||
),
|
||||
'#options' => array(
|
||||
0 => t('Disabled'),
|
||||
1 => t('Enabled'),
|
||||
),
|
||||
'#default_value' => variable_get('hierarchical_select_js_cache_system', 0),
|
||||
);
|
||||
*/
|
||||
|
||||
return system_settings_form($form);
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu callback; a table that lists all Hierarchical Select configs.
|
||||
*/
|
||||
function hierarchical_select_admin_configs() {
|
||||
$header = array(t('Hierarchy type'), t('Hierarchy'), t('Entity type'), t('Bundle'), t('Context type'), t('Context'), t('Actions'));
|
||||
|
||||
// Retrieve all information items
|
||||
$info_items = array();
|
||||
foreach (module_implements('hierarchical_select_config_info') as $module) {
|
||||
$info_items = array_merge_recursive($info_items, module_invoke($module, 'hierarchical_select_config_info'));
|
||||
}
|
||||
|
||||
// Process the retrieved information into rows.
|
||||
$rows = array();
|
||||
foreach ($info_items as $id => $item) {
|
||||
$config_id = $item['config_id'];
|
||||
|
||||
$rows[$id] = array(
|
||||
$item['hierarchy type'],
|
||||
$item['hierarchy'],
|
||||
$item['entity type'],
|
||||
$item['bundle'],
|
||||
$item['context type'],
|
||||
$item['context'],
|
||||
theme('links', array('links' => array(
|
||||
array(
|
||||
'title' => t('Edit'),
|
||||
'href' => $item['edit link'],
|
||||
'fragment' => "hierarchical-select-config-form-$config_id",
|
||||
),
|
||||
array(
|
||||
'title' => t('Export'),
|
||||
'href' => "admin/config/content/hierarchical_select/export/$config_id",
|
||||
),
|
||||
array(
|
||||
'title' => t('Import'),
|
||||
'href' => "admin/config/content/hierarchical_select/import/$config_id",
|
||||
),
|
||||
))),
|
||||
);
|
||||
}
|
||||
|
||||
return theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array(), 'caption' => t('Overview of all Hierarchical Select configurations.')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu callback; a table that lists all Hierarchical Select implementations
|
||||
* and the features they support.
|
||||
*/
|
||||
function hierarchical_select_admin_implementations() {
|
||||
$output = '';
|
||||
$header = array(t('Implementation (module)'), t('Hierarchy type'), t('Entity type'), t('Create new items'), t('Entity count'));
|
||||
|
||||
// Retrieve all information items
|
||||
$rows = array();
|
||||
foreach (module_implements('hierarchical_select_root_level') as $module) {
|
||||
$filename = db_query("SELECT filename FROM {system} WHERE type = :type AND name = :name", array(':type' => 'module', ':name' => $module))->fetchField();
|
||||
$module_info = drupal_parse_info_file(dirname($filename) . "/$module.info");
|
||||
// Try to extract the hierarchy type from the optional hook_hierarchical_select_config_info().
|
||||
$hierarchy_type = $entity_type = t('unknown');
|
||||
if (module_hook($module, 'hierarchical_select_implementation_info')) {
|
||||
$implementation = module_invoke($module, 'hierarchical_select_implementation_info');
|
||||
$hierarchy_type = $implementation['hierarchy type'];
|
||||
$entity_type = $implementation['entity type'];
|
||||
}
|
||||
|
||||
$rows[] = array(
|
||||
$module_info['name'],
|
||||
$hierarchy_type,
|
||||
$entity_type,
|
||||
(module_hook($module, 'hierarchical_select_create_item')) ? t('Yes') : t('No'),
|
||||
(module_hook($module, 'hierarchical_select_entity_count')) ? t('Yes') : t('No'),
|
||||
);
|
||||
}
|
||||
|
||||
$output .= '<p>';
|
||||
$output .= t('
|
||||
The table below allows you to find out <strong>which Hierarchical Select
|
||||
features are supported</strong> by the implementations of the Hierarchical
|
||||
Select API.<br />
|
||||
It is <strong>not a reflection of some settings</strong>.
|
||||
');
|
||||
$output .= '</p>';
|
||||
|
||||
$output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array(), 'caption' => t('Overview of all installed Hierarchical Select implementations.')));
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Form definition; config export form.
|
||||
*/
|
||||
function hierarchical_select_admin_export($form, &$form_state, $config_id) {
|
||||
require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'hierarchical_select') . '/includes/common.inc';
|
||||
|
||||
$config = hierarchical_select_common_config_get($config_id);
|
||||
$code = _hierarchical_select_create_export_code($config);
|
||||
|
||||
drupal_add_css(drupal_get_path('module', 'hierarchical_select') . '/hierarchical_select.css');
|
||||
drupal_add_js('$(document).ready(function() { $(".hierarchical-select-code").focus(); });', array('type' => 'inline', 'scope' => JS_DEFAULT));
|
||||
|
||||
$lines = substr_count($code, "\n") + 1;
|
||||
$form['config'] = array(
|
||||
'#type' => 'textarea',
|
||||
'#title' => t('Hierarchical Select configuration %config_id', array('%config_id' => $config_id)),
|
||||
'#default_value' => $code,
|
||||
'#rows' => $lines,
|
||||
'#attributes' => array('class' => array('hierarchical-select-config-code')),
|
||||
);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Form definition; config import form.
|
||||
*/
|
||||
function hierarchical_select_admin_import($form, &$form_state, $config_id) {
|
||||
require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'hierarchical_select') . '/includes/common.inc';
|
||||
|
||||
drupal_add_css(drupal_get_path('module', 'hierarchical_select') . '/hierarchical_select.css');
|
||||
drupal_add_js('$(document).ready(function() { $(".hierarchical-select-code").focus(); });', array('type' => 'inline', 'scope' => JS_DEFAULT));
|
||||
|
||||
$form['config'] = array(
|
||||
'#type' => 'textarea',
|
||||
'#title' => t('Import Hierarchical Select configuration code'),
|
||||
'#cols' => 60,
|
||||
'#rows' => 15,
|
||||
'#description' => t('Copy and paste the results of an exported
|
||||
Hierarchical Select configuration here.<br />This will override the
|
||||
current Hierarchical Select configuration for %config_id.',
|
||||
array('%config_id' => $config_id)
|
||||
),
|
||||
'#attributes' => array('class' => array('hierarchical-select-config-code')),
|
||||
);
|
||||
$form['interpreted_config'] = array('#type' => 'value', '#value' => NULL);
|
||||
$form['submit'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => t("Import"),
|
||||
);
|
||||
$form_state['#redirect'] = NULL;
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate callback; config import form.
|
||||
*/
|
||||
function hierarchical_select_admin_import_validate($form, &$form_state) {
|
||||
ob_start();
|
||||
eval($form_state['values']['config']);
|
||||
ob_end_clean();
|
||||
|
||||
form_set_value($form['interpreted_config'], serialize($config), $form_state);
|
||||
|
||||
if (empty($form_state['values']['config'])) {
|
||||
form_error($form['config'], t('You did not enter anything.'));
|
||||
}
|
||||
elseif ($config == NULL) {
|
||||
form_error($form['config'], t('There is a syntax error in the Hierarchical Select configuration you entered.'));
|
||||
}
|
||||
elseif (!isset($config['config_id']) || empty($config['config_id'])) {
|
||||
form_error($form['config'], t('Unable to import this configuration, because no Hierarchical Select <em>config id</em> is set.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit callback; config import form.
|
||||
*/
|
||||
function hierarchical_select_admin_import_submit($form, &$form_state) {
|
||||
$config = unserialize($form_state['values']['interpreted_config']);
|
||||
$config_id = $config['config_id'];
|
||||
hierarchical_select_common_config_set($config_id, $config);
|
||||
drupal_set_message(t('Hierarchical Select configuration for %config_id imported!', array('%config_id' => $config_id)));
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Private functions.
|
||||
|
||||
/**
|
||||
* Given a config array, create the export code for it.
|
||||
*
|
||||
* @param array $config
|
||||
* A Hierarchical Select config array, as described in API.txt
|
||||
* @return string
|
||||
* The code as it would appear in an editor.
|
||||
*/
|
||||
function _hierarchical_select_create_export_code($config) {
|
||||
$output = _hierarchical_select_create_code_from_array($config);
|
||||
$output = '$config = ' . $output . ";\n";
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a array, create the export code for it.
|
||||
*
|
||||
* This functions is a refactoring of features_var_export() to use with the
|
||||
* hierarchical select module.
|
||||
*
|
||||
* @param mixed $config
|
||||
* A value to export as code.
|
||||
* @param string $prefix
|
||||
* Padding for nested array.
|
||||
* @param boolean $init
|
||||
* Indicator of the first level of the export.
|
||||
* @return string
|
||||
* The code as it would appear in an editor.
|
||||
*/
|
||||
function _hierarchical_select_create_code_from_array($var, $prefix = '', $init = TRUE) {
|
||||
$output = "";
|
||||
$type = gettype($var);
|
||||
switch ($type) {
|
||||
case 'array':
|
||||
if (empty($var)) {
|
||||
$output = "array()";
|
||||
}
|
||||
else {
|
||||
$output = "array(\n";
|
||||
foreach ($var as $key => $value) {
|
||||
$value = _hierarchical_select_create_code_from_array($value, ' ', FALSE);
|
||||
$output .= " '$key' => " . $value . ",\n";
|
||||
}
|
||||
$output .= ')';
|
||||
}
|
||||
break;
|
||||
case 'string':
|
||||
$var = str_replace("\n", "***BREAK***", $var);
|
||||
$output = var_export($var, TRUE);
|
||||
break;
|
||||
case 'boolean':
|
||||
$var = empty($var) ? 'FALSE' : 'TRUE';
|
||||
$output = var_export($var, TRUE);
|
||||
break;
|
||||
default:
|
||||
$output = var_export($var, TRUE);
|
||||
}
|
||||
if ($prefix) {
|
||||
$output = str_replace("\n", "\n$prefix", $output);
|
||||
}
|
||||
if ($init) {
|
||||
$output = str_replace("***BREAK***", "\n", $output);
|
||||
}
|
||||
return $output;
|
||||
}
|
@@ -0,0 +1,226 @@
|
||||
|
||||
|
||||
/* The hierarchical select. */
|
||||
.hierarchical-select-wrapper .hierarchical-select .selects {
|
||||
float: left; /* If a block is floated, it won't consume as much width as
|
||||
available, only just enough. This allows the grippie to
|
||||
perfectly scale with the with consumed by the selects. */
|
||||
}
|
||||
|
||||
.hierarchical-select-wrapper .hierarchical-select .selects .grippie {
|
||||
clear: left;
|
||||
height: 9px;
|
||||
overflow: hidden;
|
||||
background: #eee url(images/grippie.png) no-repeat center 2px;
|
||||
border: 1px solid #ddd;
|
||||
border-top-width: 0;
|
||||
cursor: s-resize;
|
||||
margin-right: 0.5em; /* Give the grippie the same margin as each select. */
|
||||
min-width: 50px; /* Hack for IE, makes the grip usable, but not yet the same as in other browsers. */
|
||||
}
|
||||
|
||||
.hierarchical-select-wrapper .hierarchical-select select,
|
||||
.hierarchical-select-wrapper .hierarchical-select .add-to-dropbox,
|
||||
.hierarchical-select-wrapper .hierarchical-select .create-new-item {
|
||||
margin: 0;
|
||||
margin-right: .5em;
|
||||
margin-bottom: 3px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
|
||||
/* The flat select (only used in GET forms). */
|
||||
.hierarchical-select-wrapper .flat-select {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
/* The pseudo-modal window for creating a new item or new level. */
|
||||
.hierarchical-select-wrapper .hierarchical-select .create-new-item {
|
||||
padding: .7em;
|
||||
border: 2px outset gray;
|
||||
}
|
||||
|
||||
.hierarchical-select-wrapper .hierarchical-select .create-new-item {
|
||||
width: 11em;
|
||||
}
|
||||
|
||||
.hierarchical-select-wrapper .hierarchical-select .create-new-item-create,
|
||||
.hierarchical-select-wrapper .hierarchical-select .create-new-item-cancel {
|
||||
float: right;
|
||||
margin: 0;
|
||||
margin-left: .4em;
|
||||
}
|
||||
|
||||
.hierarchical-select-wrapper .hierarchical-select .create-new-item-input {
|
||||
width: 10.5em;
|
||||
margin: 0;
|
||||
margin-bottom: 1em;
|
||||
float: left;
|
||||
clear: right;
|
||||
}
|
||||
|
||||
|
||||
/* Level labels styles. */
|
||||
.hierarchical-select-level-labels-style-bold .hierarchical-select select option.level-label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.hierarchical-select-level-labels-style-inversed .hierarchical-select select option.level-label {
|
||||
background-color: #000000;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.hierarchical-select-level-labels-style-underlined .hierarchical-select select option.level-label {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
|
||||
/* Child level indicator. */
|
||||
.hierarchical-select-wrapper .hierarchical-select option.has-children {
|
||||
background: url(images/arrow.png) no-repeat right center;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
|
||||
/* Dropbox limit warning.*/
|
||||
p.hierarchical-select-dropbox-limit-warning {
|
||||
padding: 0;
|
||||
color: #F7A54F;
|
||||
font-size: 110%;
|
||||
padding-left: .5em;
|
||||
}
|
||||
|
||||
|
||||
/* The dropbox table. */
|
||||
.hierarchical-select-wrapper .dropbox-title {
|
||||
font-size: 115%;
|
||||
color: #898989;
|
||||
margin-bottom: 0.2em;
|
||||
}
|
||||
|
||||
.hierarchical-select-wrapper .dropbox {
|
||||
display: inline-block;
|
||||
margin: .5em 0;
|
||||
}
|
||||
|
||||
.hierarchical-select-wrapper .dropbox table {
|
||||
margin: 0;
|
||||
width: auto;
|
||||
max-width: 100%;
|
||||
min-width: 20em;
|
||||
color: gray;
|
||||
font-size: 90%;
|
||||
border: 1px solid gray;
|
||||
}
|
||||
|
||||
tr.dropbox-entry {
|
||||
line-height: 1.3em;
|
||||
padding: .3em .6em;
|
||||
}
|
||||
|
||||
tr.dropbox-entry.even {
|
||||
background-color: transparent;
|
||||
border-bottom: 1px solid #CCCCCC;
|
||||
}
|
||||
|
||||
tr.dropbox-entry.odd {
|
||||
background-color: #EDF5FA;
|
||||
border-bottom: 1px solid #CCCCCC;
|
||||
}
|
||||
|
||||
tr.dropbox-entry.first {
|
||||
border-top: 1px solid gray;
|
||||
}
|
||||
|
||||
tr.dropbox-entry.last {
|
||||
border-bottom: 1px solid gray;
|
||||
}
|
||||
|
||||
.dropbox-selected-item {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.hierarchical-select-item-separator {
|
||||
padding-left: .5em;
|
||||
padding-right: .5em;
|
||||
}
|
||||
|
||||
td.dropbox-remove *,
|
||||
td.dropbox-remove a:link,
|
||||
td.dropbox-remove a:visited {
|
||||
color: #F7A54F;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
td.dropbox-remove a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
tr.dropbox-is-empty {
|
||||
padding: .5em 1em;
|
||||
}
|
||||
|
||||
|
||||
/* The "Update" button and help text (used when Javascript is disabled). */
|
||||
.hierarchical-select-wrapper .nojs .update-button {
|
||||
margin: 0 0 1em;
|
||||
}
|
||||
|
||||
.hierarchical-select-wrapper .nojs .help-text {
|
||||
font-size: 90%;
|
||||
color: transparent;
|
||||
display: block;
|
||||
border: 1px dotted black;
|
||||
overflow: hidden;
|
||||
width: 34em;
|
||||
height: 1.2em;
|
||||
padding: .6em;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.hierarchical-select-wrapper .nojs .help-text:hover {
|
||||
height: auto;
|
||||
width: auto;
|
||||
min-width: 25em;
|
||||
max-width: 45em;
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.hierarchical-select-wrapper .nojs .help-text .ask-to-hover {
|
||||
color: gray;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hierarchical-select-wrapper .nojs .help-text:hover .ask-to-hover {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hierarchical-select-wrapper .nojs .help-text .highlight {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.hierarchical-select-wrapper .nojs .help-text .warning {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.hierarchical-select-wrapper .nojs .help-text .solutions {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
/* The 'waiting' class is set dynamically, during a callback to the server. */
|
||||
.hierarchical-select-wrapper.waiting {
|
||||
opacity: 0.5;
|
||||
|
||||
/* IE doesn't support CSS 2 properly. */
|
||||
zoom: 1;
|
||||
filter: alpha(opacity=50);
|
||||
}
|
||||
|
||||
|
||||
/* Use a monospace font for the import/export config code text areas. */
|
||||
.hierarchical-select-config-code {
|
||||
font-family: 'Monaco', 'Lucida Console', 'Consolas', monospace;
|
||||
}
|
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Integration with the features module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_features_export().
|
||||
*/
|
||||
function hierarchical_select_features_export($data, &$export, $module_name) {
|
||||
// Add hierarchical_select dependency.
|
||||
$export['dependencies']['hierarchical_select'] = 'hierarchical_select';
|
||||
|
||||
// Retrieve dependencies from all information items.
|
||||
$dependencies = array();
|
||||
foreach (module_implements('hierarchical_select_config_info') as $module) {
|
||||
$configs = module_invoke($module, 'hierarchical_select_config_info');
|
||||
foreach ($configs as $config_id => $config) {
|
||||
$dependencies[$config_id] = $module;
|
||||
}
|
||||
}
|
||||
|
||||
// Add features and dependencies.
|
||||
foreach ($data as $config_id) {
|
||||
$export['features']['hierarchical_select'][$config_id] = $config_id;
|
||||
if (isset($dependencies[$config_id])) {
|
||||
$module = $dependencies[$config_id];
|
||||
$export['dependencies'][$module] = $module;
|
||||
}
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_features_export_options().
|
||||
*/
|
||||
function hierarchical_select_features_export_options() {
|
||||
// Retrieve all information items.
|
||||
$info_items = array();
|
||||
foreach (module_implements('hierarchical_select_config_info') as $module) {
|
||||
$info_items = array_merge_recursive($info_items, module_invoke($module, 'hierarchical_select_config_info'));
|
||||
}
|
||||
|
||||
// Process the retrieved information into options.
|
||||
$options = array();
|
||||
foreach ($info_items as $id => $item) {
|
||||
$config_id = $item['config_id'];
|
||||
$options[$config_id] = $item['hierarchy type'] . ': ' . $item['hierarchy'] . ' - ' . $item['context type'] . (!empty($item['context']) ? ': ' . $item['context'] : '');
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_features_export_render().
|
||||
*/
|
||||
function hierarchical_select_features_export_render($module, $data) {
|
||||
module_load_include('inc', 'hierarchical_select', 'includes/common');
|
||||
module_load_include('inc', 'hierarchical_select', 'hierarchical_select.admin');
|
||||
|
||||
$code = array();
|
||||
$code[] = '$configs = array();';
|
||||
foreach ($data as $config_id) {
|
||||
$config = hierarchical_select_common_config_get($config_id);
|
||||
$config['config_id'] = $config_id;
|
||||
|
||||
$code[] = _hierarchical_select_create_export_code($config);
|
||||
$code[] = "\$configs['{$config_id}'] = \$config;";
|
||||
}
|
||||
$code[] = "return \$configs;";
|
||||
$code = implode("\n", $code);
|
||||
|
||||
return array('hierarchical_select_default_configs' => $code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_features_revert().
|
||||
*/
|
||||
function hierarchical_select_features_revert($module) {
|
||||
hierarchical_select_features_rebuild($module);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_features_rebuild().
|
||||
*/
|
||||
function hierarchical_select_features_rebuild($module) {
|
||||
module_load_include('inc', 'hierarchical_select', 'includes/common');
|
||||
$configs = features_get_default('hierarchical_select', $module);
|
||||
if (!empty($configs)) {
|
||||
// Apply the configuration.
|
||||
require_once(drupal_get_path('module', 'hierarchical_select') .'/includes/common.inc');
|
||||
|
||||
foreach ($configs as $config_id => $config) {
|
||||
hierarchical_select_common_config_set($config_id, $config);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
name = Hierarchical Select
|
||||
description = Simplifies the selection of one or multiple items in a hierarchical tree.
|
||||
package = Form Elements
|
||||
|
||||
core = 7.x
|
||||
configure = admin/config/content/hierarchical_select
|
||||
files[] = tests/internals.test
|
||||
|
||||
; Information added by Drupal.org packaging script on 2017-02-15
|
||||
version = "7.x-3.0-beta8"
|
||||
core = "7.x"
|
||||
project = "hierarchical_select"
|
||||
datestamp = "1487167708"
|
||||
|
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Install, update and uninstall functions for the Hierarchical Select module.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Implements hook_uninstall().
|
||||
*/
|
||||
function hierarchical_select_uninstall() {
|
||||
db_delete('variable')
|
||||
->condition('name', 'hs_config_%', 'LIKE')
|
||||
->execute();
|
||||
|
||||
db_delete('variable')
|
||||
->condition('name', 'hierarchical_select_%', 'LIKE')
|
||||
->execute();
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Updates.
|
||||
|
||||
/**
|
||||
* Update Hierarchical Select to Drupal 7. Basically remove a lot of cruft.
|
||||
*/
|
||||
function hierarchical_select_update_7001() {
|
||||
// Drop Hierarchical Select's cache table, which is now obsolete.
|
||||
db_drop_table('cache_hierarchical_select');
|
||||
|
||||
// Undo Hierarchical Select module weight changes, because they're no longer
|
||||
// necessary.
|
||||
db_update('system')
|
||||
->fields(array(
|
||||
'weight' => 0,
|
||||
))
|
||||
->condition('name', 'hierarchical_select')
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Hierarchical Select config to support improved "entity count".
|
||||
*/
|
||||
function hierarchical_select_update_7002() {
|
||||
module_load_include('inc', 'hierarchical_select', 'includes/common');
|
||||
// Retrieve all information items.
|
||||
$info_items = array();
|
||||
foreach (module_implements('hierarchical_select_config_info') as $module) {
|
||||
$info_items = array_merge_recursive($info_items, module_invoke($module, 'hierarchical_select_config_info'));
|
||||
}
|
||||
foreach ($info_items as $info_item) {
|
||||
// Load config.
|
||||
$config = hierarchical_select_common_config_get($info_item['config_id']);
|
||||
|
||||
// Move old settings to new location.
|
||||
$config['entity_count'] = array(
|
||||
'enabled' => $config['entity_count'],
|
||||
'require_entity' => $config['require_entity'],
|
||||
);
|
||||
|
||||
// Remove old setting.
|
||||
unset($config['require_entity']);
|
||||
|
||||
// Add entity types settings.
|
||||
$entity_info = entity_get_info();
|
||||
foreach ($entity_info as $entity => $entity_info) {
|
||||
if (!empty($entity_info['bundles']) && $entity_info['fieldable'] === TRUE) {
|
||||
foreach ($entity_info['bundles'] as $bundle => $bundle_info) {
|
||||
if ($entity == 'node') {
|
||||
$config['entity_count']['settings']['entity_types'][$entity]['count_' . $entity][$bundle] = $bundle;
|
||||
}
|
||||
else {
|
||||
$config['entity_count']['settings']['entity_types'][$entity]['count_' . $entity][$bundle] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save new config.
|
||||
hierarchical_select_common_config_set($info_item['config_id'], $config);
|
||||
}
|
||||
}
|
@@ -0,0 +1,702 @@
|
||||
|
||||
(function($) {
|
||||
|
||||
Drupal.behaviors.HierarchicalSelect = {
|
||||
attach: function (context) {
|
||||
$('.hierarchical-select-wrapper:not(.hierarchical-select-wrapper-processed)', context)
|
||||
.addClass('hierarchical-select-wrapper-processed').each(function() {
|
||||
var hsid = $(this).attr('id').replace(/^hierarchical-select-(.+)-wrapper$/, "$1");
|
||||
Drupal.HierarchicalSelect.initialize(hsid);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Drupal.HierarchicalSelect = {};
|
||||
|
||||
Drupal.HierarchicalSelect.state = [];
|
||||
|
||||
Drupal.HierarchicalSelect.context = function() {
|
||||
return $("form .hierarchical-select-wrapper");
|
||||
};
|
||||
|
||||
Drupal.HierarchicalSelect.initialize = function(hsid) {
|
||||
// Prevent JS errors when Hierarchical Select is loaded dynamically.
|
||||
if (undefined == Drupal.settings.HierarchicalSelect || undefined == Drupal.settings.HierarchicalSelect.settings["hs-" + hsid]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If you set Drupal.settings.HierarchicalSelect.pretendNoJS to *anything*,
|
||||
// and as such, Hierarchical Select won't initialize its Javascript! It
|
||||
// will seem as if your browser had Javascript disabled.
|
||||
if (undefined != Drupal.settings.HierarchicalSelect.pretendNoJS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var form = $('#hierarchical-select-'+ hsid +'-wrapper').parents('form');
|
||||
|
||||
// Pressing the 'enter' key on a form that contains an HS widget, depending
|
||||
// on which browser, usually causes the first submit button to be pressed
|
||||
// (likely an HS button). This results in unpredictable behaviour. There is
|
||||
// no way to determine the 'real' submit button, so disable the enter key.
|
||||
form.find('input').keypress(function(event) {
|
||||
if (event.keyCode == 13) {
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// Turn off Firefox' autocomplete feature. This causes Hierarchical Select
|
||||
// form items to be disabled after a hard refresh.
|
||||
// See http://drupal.org/node/453048 and
|
||||
// http://www.ryancramer.com/journal/entries/radio_buttons_firefox/
|
||||
if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1) {
|
||||
form.attr('autocomplete', 'off');
|
||||
}
|
||||
|
||||
// Enable *all* submit buttons in this form, as well as all input-related
|
||||
// elements of the current hierarchical select, in case we reloaded while
|
||||
// they were disabled.
|
||||
form.add('#hierarchical-select-' + hsid +'-wrapper .hierarchical-select .selects select')
|
||||
.add('#hierarchical-select-' + hsid +'-wrapper .hierarchical-select input')
|
||||
.attr('disabled', false);
|
||||
|
||||
if (this.cache != null) {
|
||||
this.cache.initialize();
|
||||
}
|
||||
|
||||
Drupal.settings.HierarchicalSelect.settings["hs-" + hsid]['updatesEnabled'] = true;
|
||||
if (undefined == Drupal.HierarchicalSelect.state["hs-" + hsid]) {
|
||||
Drupal.HierarchicalSelect.state["hs-" + hsid] = {};
|
||||
}
|
||||
|
||||
this.transform(hsid);
|
||||
if (Drupal.settings.HierarchicalSelect.settings["hs-" + hsid].resizable) {
|
||||
this.resizable(hsid);
|
||||
}
|
||||
Drupal.HierarchicalSelect.attachBindings(hsid);
|
||||
|
||||
if (this.cache != null && this.cache.status()) {
|
||||
this.cache.load(hsid);
|
||||
}
|
||||
|
||||
Drupal.HierarchicalSelect.log(hsid);
|
||||
};
|
||||
|
||||
Drupal.HierarchicalSelect.log = function(hsid, messages) {
|
||||
// Only perform logging if logging is enabled.
|
||||
if (Drupal.settings.HierarchicalSelect.initialLog == undefined || Drupal.settings.HierarchicalSelect.initialLog["hs-" + hsid] == undefined) {
|
||||
return;
|
||||
}
|
||||
else {
|
||||
Drupal.HierarchicalSelect.state["hs-" + hsid].log = [];
|
||||
}
|
||||
|
||||
// Store the log messages. The first call to this function may not contain a
|
||||
// message: the initial log included in the initial HTML rendering should be
|
||||
// used instead..
|
||||
if (Drupal.HierarchicalSelect.state["hs-" + hsid].log.length == 0) {
|
||||
Drupal.HierarchicalSelect.state["hs-" + hsid].log.push(Drupal.settings.HierarchicalSelect.initialLog["hs-" + hsid]);
|
||||
}
|
||||
else {
|
||||
Drupal.HierarchicalSelect.state["hs-" + hsid].log.push(messages);
|
||||
}
|
||||
|
||||
// Print the log messages.
|
||||
console.log("HIERARCHICAL SELECT " + hsid);
|
||||
var logIndex = Drupal.HierarchicalSelect.state["hs-" + hsid].log.length - 1;
|
||||
for (var i = 0; i < Drupal.HierarchicalSelect.state["hs-" + hsid].log[logIndex].length; i++) {
|
||||
console.log(Drupal.HierarchicalSelect.state["hs-" + hsid].log[logIndex][i]);
|
||||
}
|
||||
console.log(' ');
|
||||
};
|
||||
|
||||
Drupal.HierarchicalSelect.transform = function(hsid) {
|
||||
var removeString = $('#hierarchical-select-'+ hsid +'-wrapper .dropbox .dropbox-remove:first', Drupal.HierarchicalSelect.context).text();
|
||||
|
||||
$('#hierarchical-select-'+ hsid +'-wrapper', Drupal.HierarchicalSelect.context)
|
||||
// Remove the .nojs div.
|
||||
.find('.nojs').hide().end()
|
||||
// Find all .dropbox-remove cells in the dropbox table.
|
||||
.find('.dropbox .dropbox-remove')
|
||||
// Hide the children of these table cells. We're not removing them because
|
||||
// we want to continue to use the "Remove" checkboxes.
|
||||
.find('*').css('display', 'none').end() // We can't use .hide() because of collapse.js: http://drupal.org/node/351458#comment-1258303.
|
||||
// Put a "Remove" link there instead.
|
||||
.append('<a href="">'+ removeString +'</a>');
|
||||
};
|
||||
|
||||
Drupal.HierarchicalSelect.resizable = function(hsid) {
|
||||
var $selectsWrapper = $('#hierarchical-select-' + hsid + '-wrapper .hierarchical-select .selects', Drupal.HierarchicalSelect.context);
|
||||
|
||||
// No select wrapper present: the user is creating a new item.
|
||||
if ($selectsWrapper.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Append the drag handle ("grippie").
|
||||
$selectsWrapper.append($('<div class="grippie"></div>'));
|
||||
|
||||
// jQuery object that contains all selects in the hierarchical select, to
|
||||
// speed up DOM manipulation during dragging.
|
||||
var $selects = $selectsWrapper.find('select');
|
||||
|
||||
var defaultPadding = parseInt($selects.slice(0, 1).css('padding-top').replace(/^(\d+)px$/, "$1")) + parseInt($selects.slice(0, 1).css('padding-bottom').replace(/^(\d+)px$/, "$1"));
|
||||
var defaultHeight = Drupal.HierarchicalSelect.state["hs-" + hsid].defaultHeight = $selects.slice(0, 1).height() + defaultPadding;
|
||||
var defaultSize = Drupal.HierarchicalSelect.state["hs-" + hsid].defaultSize = $selects.slice(0, 1).attr('size');
|
||||
defaultSize = (defaultSize == 0) ? 1 : defaultSize;
|
||||
var margin = Drupal.HierarchicalSelect.state["hs-" + hsid].margin = parseInt($selects.slice(0, 1).css('margin-bottom').replace(/^(\d+)px$/, "$1"));
|
||||
|
||||
// Bind the drag event.
|
||||
$('.grippie', $selectsWrapper)
|
||||
.mousedown(startDrag)
|
||||
.dblclick(function() {
|
||||
if (Drupal.HierarchicalSelect.state["hs-" + hsid].resizedHeight == undefined) {
|
||||
Drupal.HierarchicalSelect.state["hs-" + hsid].resizedHeight = defaultHeight;
|
||||
}
|
||||
var resizedHeight = Drupal.HierarchicalSelect.state["hs-" + hsid].resizedHeight = (Drupal.HierarchicalSelect.state["hs-" + hsid].resizedHeight > defaultHeight + 2) ? defaultHeight : 4.6 / defaultSize * defaultHeight;
|
||||
Drupal.HierarchicalSelect.resize($selects, defaultHeight, resizedHeight, defaultSize, margin);
|
||||
});
|
||||
|
||||
function startDrag(e) {
|
||||
staticOffset = $selects.slice(0, 1).height() - e.pageY;
|
||||
$selects.css('opacity', 0.25);
|
||||
$(document).mousemove(performDrag).mouseup(endDrag);
|
||||
return false;
|
||||
}
|
||||
|
||||
function performDrag(e) {
|
||||
var resizedHeight = staticOffset + e.pageY;
|
||||
Drupal.HierarchicalSelect.resize($selects, defaultHeight, resizedHeight, defaultSize, margin);
|
||||
return false;
|
||||
}
|
||||
|
||||
function endDrag(e) {
|
||||
var height = $selects.slice(0, 1).height();
|
||||
|
||||
$(document).unbind("mousemove", performDrag).unbind("mouseup", endDrag);
|
||||
$selects.css('opacity', 1);
|
||||
if (height != Drupal.HierarchicalSelect.state["hs-" + hsid].resizedHeight) {
|
||||
Drupal.HierarchicalSelect.state["hs-" + hsid].resizedHeight = (height > defaultHeight) ? height : defaultHeight;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Drupal.HierarchicalSelect.resize = function($selects, defaultHeight, resizedHeight, defaultSize, margin) {
|
||||
if (resizedHeight == undefined) {
|
||||
resizedHeight = defaultHeight;
|
||||
}
|
||||
|
||||
$selects
|
||||
.attr('size', (resizedHeight > defaultHeight) ? 2 : defaultSize)
|
||||
.height(Math.max(defaultHeight + margin, resizedHeight)); // Without the margin component, the height() method would allow the select to be sized to low: defaultHeight - margin.
|
||||
};
|
||||
|
||||
Drupal.HierarchicalSelect.disableForm = function(hsid) {
|
||||
// Disable *all* submit buttons in this form, as well as all input-related
|
||||
// elements of the current hierarchical select.
|
||||
$('form:has(#hierarchical-select-' + hsid +'-wrapper) :submit')
|
||||
.add('#hierarchical-select-' + hsid +'-wrapper .hierarchical-select .selects select')
|
||||
.add('#hierarchical-select-' + hsid +'-wrapper .hierarchical-select :input')
|
||||
.attr('disabled', true);
|
||||
|
||||
// Add the 'waiting' class. Default style: make everything transparent.
|
||||
$('#hierarchical-select-' + hsid +'-wrapper').addClass('waiting');
|
||||
|
||||
// Indicate that the user has to wait.
|
||||
$('body').css('cursor', 'wait');
|
||||
};
|
||||
|
||||
Drupal.HierarchicalSelect.enableForm = function(hsid) {
|
||||
// This method undoes everything the disableForm() method did.
|
||||
|
||||
$e = $('form:has(#hierarchical-select-' + hsid +'-wrapper) :submit')
|
||||
.add('#hierarchical-select-' + hsid +'-wrapper .hierarchical-select :input:not(:submit)');
|
||||
|
||||
// Don't enable the selects again if they've been disabled because the
|
||||
// dropbox limit was exceeded.
|
||||
dropboxLimitExceeded = $('#hierarchical-select-' + hsid +'-wrapper .hierarchical-select-dropbox-limit-warning').length > 0;
|
||||
if (!dropboxLimitExceeded) {
|
||||
$e = $e.add($('#hierarchical-select-' + hsid +'-wrapper .hierarchical-select .selects select'));
|
||||
}
|
||||
$e.removeAttr("disabled");
|
||||
|
||||
// Don't enable the 'Add' button again if it's been disabled because the
|
||||
// dropbox limit was exceeded.
|
||||
if (dropboxLimitExceeded) {
|
||||
$('#hierarchical-select-' + hsid +'-wrapper .hierarchical-select :submit')
|
||||
.attr('disabled', true);
|
||||
}
|
||||
|
||||
$('#hierarchical-select-' + hsid +'-wrapper').removeClass('waiting');
|
||||
|
||||
$('body').css('cursor', 'auto');
|
||||
};
|
||||
|
||||
Drupal.HierarchicalSelect.throwError = function(hsid, message) {
|
||||
// Show the error to the user.
|
||||
alert(message);
|
||||
|
||||
// Log the error.
|
||||
Drupal.HierarchicalSelect.log(hsid, [ message ]);
|
||||
|
||||
// Re-enable the form to allow the user to retry, but reset the selection to
|
||||
// the level label if possible, otherwise the "<none>" option if possible.
|
||||
var $select = $('#hierarchical-select-' + hsid +'-wrapper .hierarchical-select .selects select:first');
|
||||
var levelLabelOption = $('option[value^=label_]', $select).val();
|
||||
if (levelLabelOption !== undefined) {
|
||||
$select.val(levelLabelOption);
|
||||
}
|
||||
else {
|
||||
var noneOption = $('option[value=none]', $select).val();
|
||||
if (noneOption !== undefined) {
|
||||
$select.val(noneOption);
|
||||
}
|
||||
}
|
||||
Drupal.HierarchicalSelect.enableForm(hsid);
|
||||
};
|
||||
|
||||
Drupal.HierarchicalSelect.prepareGETSubmit = function(hsid) {
|
||||
// Remove the name attributes of all form elements that end up in GET,
|
||||
// except for the "flat select" form element.
|
||||
$('#hierarchical-select-'+ hsid +'-wrapper', Drupal.HierarchicalSelect.context)
|
||||
.find('input, select')
|
||||
.not('.flat-select')
|
||||
.removeAttr('name');
|
||||
|
||||
// Update the name attribute of the "flat select" form element
|
||||
var $flatSelect = $('#hierarchical-select-'+ hsid +'-wrapper .flat-select', Drupal.HierarchicalSelect.context);
|
||||
var newName = $flatSelect.attr('name').replace(/^([a-zA-Z0-9_\-]*)(?:\[flat_select\]){1}(\[\])?$/, "$1$2");
|
||||
$flatSelect.attr('name', newName);
|
||||
|
||||
Drupal.HierarchicalSelect.triggerEvents(hsid, 'prepared-GET-submit', {});
|
||||
};
|
||||
|
||||
Drupal.HierarchicalSelect.attachBindings = function(hsid) {
|
||||
var updateOpString = $('#hierarchical-select-'+ hsid +'-wrapper .update-button').val();
|
||||
var addOpString = $('#hierarchical-select-'+ hsid +'-wrapper .hierarchical-select .add-to-dropbox', Drupal.HierarchicalSelect.context).val();
|
||||
var createNewItemOpString = $('#hierarchical-select-'+ hsid +'-wrapper .hierarchical-select .create-new-item-create', Drupal.HierarchicalSelect.context).val();
|
||||
var cancelNewItemOpString = $('#hierarchical-select-'+ hsid +'-wrapper .hierarchical-select .create-new-item-cancel', Drupal.HierarchicalSelect.context).val();
|
||||
|
||||
var data = {};
|
||||
data.hsid = hsid;
|
||||
|
||||
$('#hierarchical-select-'+ hsid +'-wrapper', this.context)
|
||||
// "disable-updates" event
|
||||
.unbind('disable-updates').bind('disable-updates', data, function(e) {
|
||||
Drupal.settings.HierarchicalSelect.settings["hs-" + e.data.hsid]['updatesEnabled'] = false;
|
||||
})
|
||||
|
||||
// "enforce-update" event
|
||||
.unbind('enforce-update').bind('enforce-update', data, function(e, extraPost) {
|
||||
Drupal.HierarchicalSelect.update(e.data.hsid, 'enforced-update', { opString: updateOpString, extraPost: extraPost });
|
||||
})
|
||||
|
||||
// "prepare-GET-submit" event
|
||||
.unbind('prepare-GET-submit').bind('prepare-GET-submit', data, function(e) {
|
||||
Drupal.HierarchicalSelect.prepareGETSubmit(e.data.hsid);
|
||||
})
|
||||
|
||||
// "update-hierarchical-select" event
|
||||
.find('.hierarchical-select .selects select').unbind().change(function(_hsid) {
|
||||
return function() {
|
||||
if (Drupal.settings.HierarchicalSelect.settings["hs-" + _hsid]['updatesEnabled']) {
|
||||
Drupal.HierarchicalSelect.update(_hsid, 'update-hierarchical-select', { opString: updateOpString, select_id : $(this).attr('id') });
|
||||
}
|
||||
};
|
||||
}(hsid)).end()
|
||||
|
||||
// "create-new-item" event
|
||||
.find('.hierarchical-select .create-new-item .create-new-item-create').unbind().click(function(_hsid) {
|
||||
return function() {
|
||||
Drupal.HierarchicalSelect.update(_hsid, 'create-new-item', { opString : createNewItemOpString });
|
||||
return false; // Prevent the browser from POSTing the page.
|
||||
};
|
||||
}(hsid)).end()
|
||||
|
||||
// "cancel-new-item" event"
|
||||
.find('.hierarchical-select .create-new-item .create-new-item-cancel').unbind().click(function(_hsid) {
|
||||
return function() {
|
||||
Drupal.HierarchicalSelect.update(_hsid, 'cancel-new-item', { opString : cancelNewItemOpString });
|
||||
return false; // Prevent the browser from POSTing the page (in case of the "Cancel" button).
|
||||
};
|
||||
}(hsid)).end()
|
||||
|
||||
// "add-to-dropbox" event
|
||||
.find('.hierarchical-select .add-to-dropbox').unbind().click(function(_hsid) {
|
||||
return function() {
|
||||
Drupal.HierarchicalSelect.update(_hsid, 'add-to-dropbox', { opString : addOpString });
|
||||
return false; // Prevent the browser from POSTing the page.
|
||||
};
|
||||
}(hsid)).end()
|
||||
|
||||
// "remove-from-dropbox" event
|
||||
// (anchors in the .dropbox-remove cells in the .dropbox table)
|
||||
.find('.dropbox .dropbox-remove a').unbind().click(function(_hsid) {
|
||||
return function() {
|
||||
var isDisabled = $('#hierarchical-select-'+ hsid +'-wrapper', Drupal.HierarchicalSelect.context).attr('disabled');
|
||||
|
||||
// If the hierarchical select is disabled, then ignore this click.
|
||||
if (isDisabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check the (hidden, because JS is enabled) checkbox that marks this
|
||||
// dropbox entry for removal.
|
||||
$(this).parent().find('input[type=checkbox]').attr('checked', true);
|
||||
Drupal.HierarchicalSelect.update(_hsid, 'remove-from-dropbox', { opString: updateOpString });
|
||||
return false; // Prevent the browser from POSTing the page.
|
||||
};
|
||||
}(hsid));
|
||||
};
|
||||
|
||||
Drupal.HierarchicalSelect.preUpdateAnimations = function(hsid, updateType, lastUnchanged, callback) {
|
||||
switch (updateType) {
|
||||
case 'update-hierarchical-select':
|
||||
// Drop out the selects of the levels deeper than the select of the
|
||||
// level that just changed.
|
||||
var animationDelay = Drupal.settings.HierarchicalSelect.settings["hs-" + hsid]['animationDelay'];
|
||||
var $animatedSelects = $('#hierarchical-select-'+ hsid +'-wrapper .hierarchical-select .selects select', Drupal.HierarchicalSelect.context).slice(lastUnchanged);
|
||||
if ($animatedSelects.size() > 0) {
|
||||
$animatedSelects.hide();
|
||||
for (var i = 0; i < $animatedSelects.size(); i++) {
|
||||
if (i < $animatedSelects.size() - 1) {
|
||||
$animatedSelects.slice(i, i + 1).hide("drop", { direction: "left" }, animationDelay);
|
||||
}
|
||||
else {
|
||||
$animatedSelects.slice(i, i + 1).hide("drop", { direction: "left" }, animationDelay, callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (callback) {
|
||||
callback();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
Drupal.HierarchicalSelect.postUpdateAnimations = function(hsid, updateType, lastUnchanged, callback) {
|
||||
if (Drupal.settings.HierarchicalSelect.settings["hs-" + hsid].resizable) {
|
||||
// Restore the resize.
|
||||
Drupal.HierarchicalSelect.resize(
|
||||
$('#hierarchical-select-' + hsid + '-wrapper .hierarchical-select .selects select', Drupal.HierarchicalSelect.context),
|
||||
Drupal.HierarchicalSelect.state["hs-" + hsid].defaultHeight,
|
||||
Drupal.HierarchicalSelect.state["hs-" + hsid].resizedHeight,
|
||||
Drupal.HierarchicalSelect.state["hs-" + hsid].defaultSize,
|
||||
Drupal.HierarchicalSelect.state["hs-" + hsid].margin
|
||||
);
|
||||
}
|
||||
|
||||
switch (updateType) {
|
||||
case 'update-hierarchical-select':
|
||||
var $createNewItemInput = $('#hierarchical-select-'+ hsid +'-wrapper .hierarchical-select .create-new-item-input', Drupal.HierarchicalSelect.context);
|
||||
// Hide the loaded selects after the one that was just changed, then
|
||||
// drop them in.
|
||||
var animationDelay = Drupal.settings.HierarchicalSelect.settings["hs-" + hsid]['animationDelay'];
|
||||
var $animatedSelects = $('#hierarchical-select-'+ hsid +'-wrapper .hierarchical-select .selects select', Drupal.HierarchicalSelect.context).slice(lastUnchanged);
|
||||
if ($animatedSelects.size() > 0) {
|
||||
$animatedSelects.hide();
|
||||
for (var i = 0; i < $animatedSelects.size(); i++) {
|
||||
if (i < $animatedSelects.size() - 1) {
|
||||
$animatedSelects.slice(i, i + 1).show("drop", { direction: "left" }, animationDelay);
|
||||
}
|
||||
else {
|
||||
$animatedSelects.slice(i, i + 1).show("drop", { direction: "left" }, animationDelay, callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (callback) {
|
||||
callback();
|
||||
}
|
||||
if ($createNewItemInput.size() == 0) {
|
||||
// Give focus to the level below the one that has changed, if it
|
||||
// exists.
|
||||
setTimeout(
|
||||
function() {
|
||||
$('#hierarchical-select-'+ hsid +'-wrapper .hierarchical-select .selects select', Drupal.HierarchicalSelect.context)
|
||||
.slice(lastUnchanged, lastUnchanged + 1)
|
||||
.focus();
|
||||
},
|
||||
animationDelay + 100
|
||||
);
|
||||
}
|
||||
else {
|
||||
// Give focus to the input field of the "create new item/level"
|
||||
// section, if it exists, and also select the existing text.
|
||||
$createNewItemInput.focus();
|
||||
$createNewItemInput[0].select();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'create-new-item':
|
||||
// Make sure that other Hierarchical Selects that represent the same
|
||||
// hierarchy are also updated, to make sure that they have the newly
|
||||
// created item!
|
||||
var cacheId = Drupal.settings.HierarchicalSelect.settings["hs-" + hsid].cacheId;
|
||||
for (var otherHsid in Drupal.settings.HierarchicalSelect.settings) {
|
||||
if (Drupal.settings.HierarchicalSelect.settings[otherHsid].cacheId == cacheId) {
|
||||
$('#hierarchical-select-'+ otherHsid +'-wrapper')
|
||||
.trigger('enforce-update');
|
||||
}
|
||||
}
|
||||
// TRICKY: NO BREAK HERE!
|
||||
|
||||
case 'cancel-new-item':
|
||||
// After an item/level has been created/cancelled, reset focus to the
|
||||
// beginning of the hierarchical select.
|
||||
$('#hierarchical-select-'+ hsid +'-wrapper .hierarchical-select .selects select', Drupal.HierarchicalSelect.context)
|
||||
.slice(0, 1)
|
||||
.focus();
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
Drupal.HierarchicalSelect.triggerEvents = function(hsid, updateType, settings) {
|
||||
$('#hierarchical-select-'+ hsid +'-wrapper', Drupal.HierarchicalSelect.context)
|
||||
.trigger(updateType, [ hsid, settings ]);
|
||||
};
|
||||
|
||||
Drupal.HierarchicalSelect.update = function(hsid, updateType, settings) {
|
||||
var post = $('form:has(#hierarchical-select-' + hsid +'-wrapper)', Drupal.HierarchicalSelect.context).formToArray();
|
||||
var hs_current_language = Drupal.settings.HierarchicalSelect.hs_current_language;
|
||||
|
||||
// Pass the hierarchical_select id via POST.
|
||||
post.push({ name : 'hsid', value : hsid });
|
||||
// Send the current language so we can use the same language during the AJAX callback.
|
||||
post.push({ name : 'hs_current_language', value : hs_current_language});
|
||||
// Emulate the AJAX data sent normally so that we get the same theme.
|
||||
post.push({ name : 'ajax_page_state[theme]', value : Drupal.settings.ajaxPageState.theme });
|
||||
post.push({ name : 'ajax_page_state[theme_token]', value : Drupal.settings.ajaxPageState.theme_token });
|
||||
|
||||
// If a cache system is installed, let the server know if it's running
|
||||
// properly. If it is running properly, the server will send back additional
|
||||
// information to maintain a lazily-loaded cache.
|
||||
if (Drupal.HierarchicalSelect.cache != null) {
|
||||
post.push({ name : 'client_supports_caching', value : Drupal.HierarchicalSelect.cache.status() });
|
||||
}
|
||||
|
||||
// updateType is one of:
|
||||
// - 'none' (default)
|
||||
// - 'update-hierarchical-select'
|
||||
// - 'enforced-update'
|
||||
// - 'create-new-item'
|
||||
// - 'cancel-new-item'
|
||||
// - 'add-to-dropbox'
|
||||
// - 'remove-from-dropbox'
|
||||
switch (updateType) {
|
||||
case 'update-hierarchical-select':
|
||||
var value = $('#'+ settings.select_id).val();
|
||||
var lastUnchanged = parseInt(settings.select_id.replace(/^.*-hierarchical-select-selects-(\d+)/, "$1")) + 1;
|
||||
var optionClass = $('#'+ settings.select_id).find('option[value="'+ value +'"]').attr('class');
|
||||
|
||||
// Don't do anything (also no callback to the server!) when the selected
|
||||
// item is:
|
||||
// - the '<none>' option and the renderFlatSelect setting is disabled, or
|
||||
// - a level label, or
|
||||
// - an option of class 'has-no-children', and
|
||||
// (the renderFlatSelect setting is disabled or the dropbox is enabled)
|
||||
// and
|
||||
// (the createNewLevels setting is disabled).
|
||||
if ((value == 'none' && Drupal.settings.HierarchicalSelect.settings["hs-" + hsid]['renderFlatSelect'] == false)
|
||||
|| value.match(/^label_\d+$/)
|
||||
|| (optionClass == 'has-no-children'
|
||||
&&
|
||||
(
|
||||
(Drupal.settings.HierarchicalSelect.settings["hs-" + hsid]['renderFlatSelect'] == false
|
||||
|| $('#hierarchical-select-'+ hsid +'-wrapper .dropbox').length > 0
|
||||
)
|
||||
&&
|
||||
Drupal.settings.HierarchicalSelect.settings["hs-" + hsid]['createNewLevels'] == false
|
||||
)
|
||||
)
|
||||
)
|
||||
{
|
||||
Drupal.HierarchicalSelect.preUpdateAnimations(hsid, updateType, lastUnchanged, function() {
|
||||
// Remove the sublevels.
|
||||
$('#hierarchical-select-'+ hsid +'-wrapper .hierarchical-select .selects select', Drupal.HierarchicalSelect.context)
|
||||
.slice(lastUnchanged)
|
||||
.remove();
|
||||
|
||||
// The selection of this hierarchical select has changed!
|
||||
Drupal.HierarchicalSelect.triggerEvents(hsid, 'change-hierarchical-select', settings);
|
||||
});
|
||||
return;
|
||||
}
|
||||
post.push({ name : 'op', value : settings.opString });
|
||||
break;
|
||||
|
||||
case 'enforced-update':
|
||||
post.push({ name : 'op', value : settings.opString });
|
||||
post = post.concat(settings.extraPost);
|
||||
break;
|
||||
|
||||
case 'create-new-item':
|
||||
case 'cancel-new-item':
|
||||
case 'add-to-dropbox':
|
||||
case 'remove-from-dropbox':
|
||||
post.push({ name : 'op', value : settings.opString });
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Construct the URL the request should be made to.
|
||||
var url = Drupal.settings.HierarchicalSelect.settings["hs-" + hsid].ajax_url;
|
||||
|
||||
// Construct the object that contains the options for a callback to the
|
||||
// server. If a client-side cache is found however, it's possible that this
|
||||
// won't be used.
|
||||
var ajaxOptions = $.extend({}, Drupal.ajax.prototype, {
|
||||
url: url,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
data: post,
|
||||
effect: 'fade',
|
||||
wrapper: '#hierarchical-select-' + hsid + '-wrapper',
|
||||
beforeSend: function() {
|
||||
Drupal.HierarchicalSelect.triggerEvents(hsid, 'before-' + updateType, settings);
|
||||
Drupal.HierarchicalSelect.disableForm(hsid);
|
||||
},
|
||||
error: function (XMLHttpRequest, textStatus, errorThrown) {
|
||||
// When invalid HTML is received in Safari, jQuery calls this function.
|
||||
Drupal.HierarchicalSelect.throwError(hsid, Drupal.t('Received an invalid response from the server.'));
|
||||
},
|
||||
success: function(response, status) {
|
||||
// An invalid response may be returned by the server, in case of a PHP
|
||||
// error. Detect this and let the user know.
|
||||
if (response === null || response.length == 0) {
|
||||
Drupal.HierarchicalSelect.throwError(hsid, Drupal.t('Received an invalid response from the server.'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Execute all AJAX commands in the response. But pass an additional
|
||||
// hsid parameter, which is then only used by the commands written
|
||||
// for Hierarchical Select.
|
||||
|
||||
// This is another hack because of the non-Drupal ajax implementation
|
||||
// of this module, one of the response that can come from a drupal
|
||||
// ajax command is insert, which expects a Drupal.ajax object as the first
|
||||
// arguments and assumes that certain functions/settings are available.
|
||||
// Because we are calling a Drupal.ajax.command but providing the regular
|
||||
// jQuery ajax object itself, we are allowing Drupal.ajax.prototype.commands
|
||||
// to misserably fail.
|
||||
//
|
||||
// This hack attempts to fix one issue with an insert command,
|
||||
// @see https://www.drupal.org/node/2393695, allowing it to work properly
|
||||
// Other hacks might be necessary for other ajax commands if they are added
|
||||
// by external modules.
|
||||
this.effect = 'none';
|
||||
this.getEffect = Drupal.ajax.prototype.getEffect;
|
||||
|
||||
for (var i in response) {
|
||||
if (response[i]['command'] && Drupal.ajax.prototype.commands[response[i]['command']]) {
|
||||
Drupal.ajax.prototype.commands[response[i]['command']](this, response[i], status, hsid);
|
||||
}
|
||||
}
|
||||
|
||||
// Attach behaviors. This is just after the HTML has been updated, so
|
||||
// it's as soon as we can.
|
||||
Drupal.attachBehaviors($('#hierarchical-select-' + hsid + '-wrapper').parents('div.form-type-hierarchical-select')[0]);
|
||||
|
||||
// Transform the hierarchical select and/or dropbox to the JS variant,
|
||||
// make it resizable again and re-enable the disabled form items.
|
||||
Drupal.HierarchicalSelect.enableForm(hsid);
|
||||
|
||||
Drupal.HierarchicalSelect.postUpdateAnimations(hsid, updateType, lastUnchanged, function() {
|
||||
// Update the client-side cache when:
|
||||
// - information for in the cache is provided in the response, and
|
||||
// - the cache system is available, and
|
||||
// - the cache system is running.
|
||||
if (response.cache != null && Drupal.HierarchicalSelect.cache != null && Drupal.HierarchicalSelect.cache.status()) {
|
||||
Drupal.HierarchicalSelect.cache.sync(hsid, response.cache);
|
||||
}
|
||||
|
||||
if (response.log != undefined) {
|
||||
Drupal.HierarchicalSelect.log(hsid, response.log);
|
||||
}
|
||||
|
||||
Drupal.HierarchicalSelect.triggerEvents(hsid, updateType, settings);
|
||||
|
||||
if (updateType == 'update-hierarchical-select') {
|
||||
// The selection of this hierarchical select has changed!
|
||||
Drupal.HierarchicalSelect.triggerEvents(hsid, 'change-hierarchical-select', settings);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Use the client-side cache to update the hierarchical select when:
|
||||
// - the hierarchical select is being updated (i.e. no add/remove), and
|
||||
// - the renderFlatSelect setting is disabled, and
|
||||
// - the createNewItems setting is disabled, and
|
||||
// - the cache system is available, and
|
||||
// - the cache system is running.
|
||||
// Otherwise, perform a normal dynamic form submit.
|
||||
if (updateType == 'update-hierarchical-select'
|
||||
&& Drupal.settings.HierarchicalSelect.settings["hs-" + hsid]['renderFlatSelect'] == false
|
||||
&& Drupal.settings.HierarchicalSelect.settings["hs-" + hsid]['createNewItems'] == false
|
||||
&& Drupal.HierarchicalSelect.cache != null
|
||||
&& Drupal.HierarchicalSelect.cache.status())
|
||||
{
|
||||
Drupal.HierarchicalSelect.cache.updateHierarchicalSelect(hsid, value, settings, lastUnchanged, ajaxOptions);
|
||||
}
|
||||
else {
|
||||
Drupal.HierarchicalSelect.preUpdateAnimations(hsid, updateType, lastUnchanged, function() {
|
||||
// Adding current theme to prevent conflicts, @see ajax.js
|
||||
// @TODO, try converting to use Drupal.ajax instead.
|
||||
|
||||
// Prevent duplicate HTML ids in the returned markup.
|
||||
// @see drupal_html_id()
|
||||
var ids = [];
|
||||
$('[id]').each(function () {
|
||||
ids.push(this.id);
|
||||
});
|
||||
|
||||
ajaxOptions.data.push({ name : 'ajax_html_ids[]', value : ids });
|
||||
|
||||
ajaxOptions.data.push({ name : 'ajax_page_state[theme]', value : Drupal.settings.ajaxPageState.theme });
|
||||
ajaxOptions.data.push({ name : 'ajax_page_state[theme_token]', value : Drupal.settings.ajaxPageState.theme_token });
|
||||
for (var key in Drupal.settings.ajaxPageState.css) {
|
||||
ajaxOptions.data.push({ name : 'ajax_page_state[css][' + key + ']', value : 1});
|
||||
}
|
||||
for (var key in Drupal.settings.ajaxPageState.js) {
|
||||
ajaxOptions.data.push({ name : 'ajax_page_state[js][' + key + ']', value : 1});
|
||||
}
|
||||
|
||||
// Make it work with jquery update
|
||||
if (Drupal.settings.ajaxPageState.jquery_version) {
|
||||
ajaxOptions.data.push({ name : 'ajax_page_state[jquery_version]', value : Drupal.settings.ajaxPageState.jquery_version });
|
||||
}
|
||||
|
||||
$.ajax(ajaxOptions);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Drupal.ajax.prototype.commands.hierarchicalSelectUpdate = function(ajax, response, status, hsid) {
|
||||
// Replace the old HTML with the (relevant part of) retrieved HTML.
|
||||
$('#hierarchical-select-'+ hsid +'-wrapper', Drupal.HierarchicalSelect.context)
|
||||
.parent('.form-item')
|
||||
.replaceWith($(response.output));
|
||||
};
|
||||
|
||||
Drupal.ajax.prototype.commands.hierarchicalSelectSettingsUpdate = function(ajax, response, status, hsid) {
|
||||
Drupal.settings.HierarchicalSelect.settings["hs-" + response.hsid] = response.settings;
|
||||
};
|
||||
|
||||
})(jQuery);
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,229 @@
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Cache system for Hierarchical Select.
|
||||
* This cache system takes advantage of the HTML 5 client-side database
|
||||
* storage specification to reduce the number of queries to the server. A lazy
|
||||
* loading strategy is used.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Note: this cache system can be replaced by another one, as long as you
|
||||
* provide the following methods:
|
||||
* - initialize()
|
||||
* - status()
|
||||
* - load()
|
||||
* - sync()
|
||||
* - updateHierarchicalSelect()
|
||||
*
|
||||
* TODO: better documentation
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
|
||||
Drupal.HierarchicalSelect.cache = {};
|
||||
|
||||
Drupal.HierarchicalSelect.cache.initialize = function() {
|
||||
try {
|
||||
if (window.openDatabase) {
|
||||
this.db = openDatabase("Hierarchical Select", "3.x", "Hierarchical Select cache", 200000);
|
||||
|
||||
this.db
|
||||
// Create the housekeeping table if it doesn't exist yet.
|
||||
.transaction(function(tx) {
|
||||
tx.executeSql("SELECT COUNT(*) FROM hierarchical_select", [], null, function(tx, error) {
|
||||
tx.executeSql("CREATE TABLE hierarchical_select (table_name TEXT UNIQUE, expires REAL)", []);
|
||||
console.log("Created housekeeping table.");
|
||||
});
|
||||
})
|
||||
// Empty tables that have expired, based on the information in the
|
||||
// housekeeping table.
|
||||
.transaction(function(tx) {
|
||||
tx.executeSql("SELECT table_name FROM hierarchical_select WHERE expires < ?", [ new Date().getTime() ], function(tx, resultSet) {
|
||||
for (var i = 0; i < resultSet.rows.length; i++) {
|
||||
var row = resultSet.rows.item(i);
|
||||
var newExpiresTimestamp = new Date().getTime() + 86400;
|
||||
|
||||
tx.executeSql("DELETE * FROM " + row.table_name);
|
||||
tx.executeSql("UPDATE hierarchical_select SET expires = ? WHERE table_name = ?", [ newExpiresTimestamp, row.table_name ]);
|
||||
|
||||
console.log("Table "+ row.table_name +" was expired: emptied it. Will expire again in "+ (newExpiresTimestamp - new Date().getTime()) / 3600 +" hours.");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
this.db = false;
|
||||
}
|
||||
}
|
||||
catch(err) { }
|
||||
};
|
||||
|
||||
Drupal.HierarchicalSelect.cache.status = function() {
|
||||
return Drupal.HierarchicalSelect.cache.db !== false;
|
||||
};
|
||||
|
||||
Drupal.HierarchicalSelect.cache.table = function(hsid) {
|
||||
return Drupal.settings.HierarchicalSelect.settings[hsid].cacheId;
|
||||
};
|
||||
|
||||
Drupal.HierarchicalSelect.cache.load = function(hsid) {
|
||||
// If necessary, create the cache table for the given Hierarchical Select.
|
||||
Drupal.HierarchicalSelect.cache.db.transaction(function(tx) {
|
||||
var table = Drupal.HierarchicalSelect.cache.table(hsid);
|
||||
|
||||
tx.executeSql("SELECT value FROM "+ table, [], function(tx, resultSet) {
|
||||
console.log("" + resultSet.rows.length + " cached items in the " + table + " table.");
|
||||
}, function(tx, error) {
|
||||
var expiresTimestamp = new Date().getTime() + 86400;
|
||||
|
||||
tx.executeSql("CREATE TABLE "+ table +" (parent REAL, value REAL UNIQUE, label REAL, weight REAL)");
|
||||
tx.executeSql("INSERT INTO hierarchical_select (table_name, expires) VALUES (?, ?)", [ table, expiresTimestamp ]);
|
||||
|
||||
console.log("Created table "+ table +", will expire in "+ (expiresTimestamp - new Date().getTime()) / 3600 +" hours.");
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Drupal.HierarchicalSelect.cache.insertOnDuplicateKeyUpdate = function(table, row) {
|
||||
// console.log("storing: value: "+ row.value +", label: "+ row.label +", parent: "+ row.parent +", weight: "+ row.weight);
|
||||
Drupal.HierarchicalSelect.cache.db.transaction(function(tx) {
|
||||
tx.executeSql("INSERT INTO "+ table +" (parent, value, label, weight) VALUES (?, ?, ?, ?)", [ row.parent, row.value, row.label, row.weight ], null, function(tx, error) {
|
||||
// console.log("UPDATING value: "+ row.value +", label: "+ row.label +", parent: "+ row.parent +", weight: "+ row.weight);
|
||||
tx.executeSql("UPDATE "+ table +" SET parent = ?, label = ?, weight = ? WHERE value = ?", [ row.parent, row.label, row.weight, row.value ], null, function(tx, error) {
|
||||
// console.log("sql error: " + error.message);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Drupal.HierarchicalSelect.cache.sync = function(hsid, info) {
|
||||
var table = Drupal.HierarchicalSelect.cache.table(hsid);
|
||||
for (var id in info) {
|
||||
var closure = function(_info, id) {
|
||||
Drupal.HierarchicalSelect.cache.insertOnDuplicateKeyUpdate(table, _info[id]);
|
||||
} (info, id);
|
||||
}
|
||||
};
|
||||
|
||||
Drupal.HierarchicalSelect.cache.hasChildren = function(hsid, value, successCallback, failCallback) {
|
||||
var table = Drupal.HierarchicalSelect.cache.table(hsid);
|
||||
Drupal.HierarchicalSelect.cache.db.transaction(function(tx) {
|
||||
tx.executeSql("SELECT * FROM "+ table +" WHERE parent = ?", [ value ], function(tx, resultSet) {
|
||||
if (resultSet.rows.length > 0) {
|
||||
successCallback();
|
||||
}
|
||||
else {
|
||||
failCallback();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Drupal.HierarchicalSelect.cache.getSubLevels = function(hsid, value, callback, previousSubLevels) {
|
||||
var table = Drupal.HierarchicalSelect.cache.table(hsid);
|
||||
|
||||
var subLevels = new Array();
|
||||
if (previousSubLevels != undefined) {
|
||||
subLevels = previousSubLevels;
|
||||
}
|
||||
|
||||
Drupal.HierarchicalSelect.cache.db.transaction(function(tx) {
|
||||
tx.executeSql("SELECT value, label FROM "+ table +" WHERE parent = ? ORDER BY weight", [ value ], function(tx, resultSet) {
|
||||
var numChildren = resultSet.rows.length;
|
||||
|
||||
// If there's only one child, check if it has the dummy "<value>-has-no-children" value.
|
||||
if (numChildren == 1) {
|
||||
var valueOfFirstRow = String(resultSet.rows.item(0).value);
|
||||
var isDummy = valueOfFirstRow.match(/^.*-has-no-children$/);
|
||||
}
|
||||
|
||||
// Only pass the children if there are any (and not a fake one either).
|
||||
if (numChildren && !isDummy) {
|
||||
var level = new Array();
|
||||
for (var i = 0; i < resultSet.rows.length; i++) {
|
||||
var row = resultSet.rows.item(i);
|
||||
level[i] = { 'value' : row.value, 'label' : row.label };
|
||||
console.log("child of "+ value +": ("+ row.value +", "+ row.label +")");
|
||||
}
|
||||
|
||||
subLevels.push(level);
|
||||
|
||||
Drupal.HierarchicalSelect.cache.getSubLevels(hsid, level[0].value, callback, subLevels);
|
||||
}
|
||||
else {
|
||||
if (subLevels.length > 0) {
|
||||
callback(subLevels);
|
||||
}
|
||||
else {
|
||||
callback(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Drupal.HierarchicalSelect.cache.createAndUpdateSelects = function(hsid, subLevels, lastUnchanged) {
|
||||
// Remove all levels below the level in which a value was selected, if they
|
||||
// exist.
|
||||
// Note: the root level can never change because of this!
|
||||
$('#hierarchical-select-'+ hsid +'-wrapper .hierarchical-select .selects select').slice(lastUnchanged).remove();
|
||||
|
||||
// Create the new sublevels, by cloning the root level and then modifying
|
||||
// that clone.
|
||||
var $rootSelect = $('#hierarchical-select-'+ hsid +'-wrapper .hierarchical-select .selects select:first');
|
||||
for (var depth in subLevels) {
|
||||
var optionElements = $.map(subLevels[depth], function(item) { return '<option value="'+ item.value +'">'+ item.label +'</option>'; });
|
||||
|
||||
var level = parseInt(lastUnchanged) + parseInt(depth);
|
||||
|
||||
$('#hierarchical-select-'+ hsid +'-wrapper .hierarchical-select .selects select:last').after(
|
||||
$rootSelect.clone()
|
||||
// Update the name attribute.
|
||||
.attr('name', $rootSelect.attr('name').replace(/(.*)\d+\]$/, "$1"+ level +"]"))
|
||||
// Update the id attribute.
|
||||
.attr('id', $rootSelect.attr('id').replace(/(.*-hierarchical-select-selects-)\d+/, "$1"+ level))
|
||||
// Remove the existing options and set the new ones.
|
||||
.empty().append(optionElements.join(''))
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Drupal.HierarchicalSelect.cache.updateHierarchicalSelect = function(hsid, value, settings, lastUnchanged, ajaxOptions) {
|
||||
// If the selected value has children
|
||||
Drupal.HierarchicalSelect.cache.hasChildren(hsid, value, function() {
|
||||
console.log("Cache hit.");
|
||||
Drupal.HierarchicalSelect.cache.getSubLevels(hsid, value, function(subLevels) {
|
||||
Drupal.HierarchicalSelect.preUpdateAnimations(hsid, 'update-hierarchical-select', lastUnchanged, function() {
|
||||
if (subLevels !== false) {
|
||||
Drupal.HierarchicalSelect.cache.createAndUpdateSelects(hsid, subLevels, lastUnchanged);
|
||||
}
|
||||
else {
|
||||
// Nothing must happen: the user selected a value that doesn't
|
||||
// have any subLevels.
|
||||
$('#hierarchical-select-' + hsid + '-wrapper .hierarchical-select .selects select').slice(lastUnchanged).remove();
|
||||
}
|
||||
|
||||
Drupal.HierarchicalSelect.postUpdateAnimations(hsid, 'update-hierarchical-select', lastUnchanged, function() {
|
||||
// Reattach the bindings.
|
||||
Drupal.HierarchicalSelect.attachBindings(hsid);
|
||||
|
||||
Drupal.HierarchicalSelect.triggerEvents(hsid, 'update-hierarchical-select', settings);
|
||||
|
||||
// The selection of this hierarchical select has changed!
|
||||
Drupal.HierarchicalSelect.triggerEvents(hsid, 'change-hierarchical-select', settings);
|
||||
});
|
||||
});
|
||||
});
|
||||
}, function() {
|
||||
// This item was not yet requested before, so we still have to perform
|
||||
// the dynamic form submit.
|
||||
console.log("Cache miss. Querying the server.");
|
||||
Drupal.HierarchicalSelect.preUpdateAnimations(hsid, 'update-hierarchical-select', lastUnchanged, function() {
|
||||
$.ajax(ajaxOptions);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
})(jQuery);
|
@@ -0,0 +1,95 @@
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains the formToArray method and the method it depends on. Taken from
|
||||
* jQuery Form Plugin 2.12. (http://www.malsup.com/jquery/form/)
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
|
||||
/**
|
||||
* formToArray() gathers form element data into an array of objects that can
|
||||
* be passed to any of the following ajax functions: $.get, $.post, or load.
|
||||
* Each object in the array has both a 'name' and 'value' property. An example of
|
||||
* an array for a simple login form might be:
|
||||
*
|
||||
* [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
|
||||
*
|
||||
* It is this array that is passed to pre-submit callback functions provided to the
|
||||
* ajaxSubmit() and ajaxForm() methods.
|
||||
*/
|
||||
$.fn.formToArray = function(semantic) {
|
||||
var a = [];
|
||||
if (this.length == 0) return a;
|
||||
|
||||
var form = this[0];
|
||||
var els = semantic ? form.getElementsByTagName('*') : form.elements;
|
||||
if (!els) return a;
|
||||
for(var i=0, max=els.length; i < max; i++) {
|
||||
var el = els[i];
|
||||
var n = el.name;
|
||||
if (!n) continue;
|
||||
|
||||
if (semantic && form.clk && el.type == "image") {
|
||||
// handle image inputs on the fly when semantic == true
|
||||
if(!el.disabled && form.clk == el)
|
||||
a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
|
||||
continue;
|
||||
}
|
||||
|
||||
var v = $.fieldValue(el, true);
|
||||
if (v && v.constructor == Array) {
|
||||
for(var j=0, jmax=v.length; j < jmax; j++)
|
||||
a.push({name: n, value: v[j]});
|
||||
}
|
||||
else if (v !== null && typeof v != 'undefined')
|
||||
a.push({name: n, value: v});
|
||||
}
|
||||
|
||||
if (!semantic && form.clk) {
|
||||
// input type=='image' are not found in elements array! handle them here
|
||||
var inputs = form.getElementsByTagName("input");
|
||||
for(var i=0, max=inputs.length; i < max; i++) {
|
||||
var input = inputs[i];
|
||||
var n = input.name;
|
||||
if(n && !input.disabled && input.type == "image" && form.clk == input)
|
||||
a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
|
||||
}
|
||||
}
|
||||
return a;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the value of the field element.
|
||||
*/
|
||||
$.fieldValue = function(el, successful) {
|
||||
var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
|
||||
if (typeof successful == 'undefined') successful = true;
|
||||
|
||||
if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
|
||||
(t == 'checkbox' || t == 'radio') && !el.checked ||
|
||||
(t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
|
||||
tag == 'select' && el.selectedIndex == -1))
|
||||
return null;
|
||||
|
||||
if (tag == 'select') {
|
||||
var index = el.selectedIndex;
|
||||
if (index < 0) return null;
|
||||
var a = [], ops = el.options;
|
||||
var one = (t == 'select-one');
|
||||
var max = (one ? index+1 : ops.length);
|
||||
for(var i=(one ? index : 0); i < max; i++) {
|
||||
var op = ops[i];
|
||||
if (op.selected) {
|
||||
// extra pain for IE...
|
||||
var v = $.browser.msie && !(op.attributes['value'].specified) ? op.text : op.value;
|
||||
if (one) return v;
|
||||
a.push(v);
|
||||
}
|
||||
}
|
||||
return a;
|
||||
}
|
||||
return el.value;
|
||||
};
|
||||
|
||||
})(jQuery);
|
Binary file not shown.
After Width: | Height: | Size: 139 B |
Binary file not shown.
After Width: | Height: | Size: 172 B |
Binary file not shown.
After Width: | Height: | Size: 162 B |
@@ -0,0 +1,441 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Functions used by more than one Hierarchical Select implementation.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Retrieve a config. If certain settings are not yet configured by the user,
|
||||
* defaults will be set. These defaults can also be overriden. This allows
|
||||
* modules to provide their own meaningful defaults.
|
||||
*
|
||||
* @param $config_id
|
||||
* A config id, typically of the form "module-someid", e.g.
|
||||
* "taxonomy-field_name".
|
||||
* @param $defaults_override
|
||||
* Optionally override the defaults.
|
||||
*
|
||||
* @return array
|
||||
* An array of default config.
|
||||
*/
|
||||
function hierarchical_select_common_config_get($config_id, $defaults_override = array()) {
|
||||
$config = variable_get('hs_config_' . $config_id, array());
|
||||
return _hierarchical_select_inherit_default_config($config, $defaults_override);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a config.
|
||||
*
|
||||
* @param $config_id
|
||||
* A config id.
|
||||
* @param $config
|
||||
* The config to store.
|
||||
*/
|
||||
function hierarchical_select_common_config_set($config_id, $config) {
|
||||
variable_set('hs_config_' . $config_id, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a config.
|
||||
*
|
||||
* @param $config_id
|
||||
* The config id to delete.
|
||||
*/
|
||||
function hierarchical_select_common_config_del($config_id) {
|
||||
variable_del('hs_config_' . $config_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a config to a form item.
|
||||
*
|
||||
* @param $form_item
|
||||
* The form item that will be updated.
|
||||
* @param $config_id
|
||||
* A config id.
|
||||
* @param $defaults_override
|
||||
* Optionally override the defaults.
|
||||
* @see hierarchical_select_common_config_get()
|
||||
*/
|
||||
function hierarchical_select_common_config_apply(&$form_item, $config_id, $defaults_override = array()) {
|
||||
$config = hierarchical_select_common_config_get($config_id, $defaults_override);
|
||||
$form_item['#config'] = array_merge((isset($form_item['#config']) && is_array($form_item['#config'])) ? $form_item['#config'] : array(), $config);
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Forms API callbacks.
|
||||
|
||||
/**
|
||||
* Form definition of the hierarchical_select_common_config_form form.
|
||||
*/
|
||||
function hierarchical_select_common_config_form($module, $params, $config_id, $defaults_override, $strings, $max_hierarchy_depth, $preview_is_required) {
|
||||
$config = hierarchical_select_common_config_get($config_id, $defaults_override);
|
||||
$args = array(
|
||||
'!item' => $strings['item'],
|
||||
'!items' => $strings['items'],
|
||||
'!entity' => $strings['entity'],
|
||||
'!entities' => $strings['entities'],
|
||||
'!hierarchy' => $strings['hierarchy'],
|
||||
'!hierarchies' => $strings['hierarchies']
|
||||
);
|
||||
|
||||
$form = array(
|
||||
'#tree' => TRUE,
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Hierarchical Select configuration'),
|
||||
'#attributes' => array(
|
||||
'class' => array('hierarchical-select-config-form'),
|
||||
'id' => 'hierarchical-select-config-form-' . $config_id,
|
||||
),
|
||||
'#attached' => array(
|
||||
'css' => array(
|
||||
drupal_get_path('module', 'hierarchical_select') . '/includes/common_config_form.css'
|
||||
),
|
||||
'js' => array(
|
||||
array(
|
||||
'type' => 'file',
|
||||
'data' => drupal_get_path('module', 'hierarchical_select') . '/includes/common_config_form.js',
|
||||
),
|
||||
array(
|
||||
'type' => 'setting',
|
||||
'data' => array('HierarchicalSelect' => array('configForm' => array($config_id))),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
$form['config_id'] = array('#type' => 'value', '#value' => $config_id);
|
||||
|
||||
// TODO: really make this a *live* preview, i.e. refresh the preview on each
|
||||
// change in the form. This cannot be done easily in Drupal 5 or 6, so let's
|
||||
// do so in Drupal 7. See cfg.livePreview in common_config_form.js.
|
||||
$form['live_preview'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Preview'),
|
||||
'#description' => t('This is what the Hierarchical Select will look like with your current configuration.'),
|
||||
'#collapsible' => FALSE,
|
||||
'#attributes' => array('class' => array('live-preview')),
|
||||
);
|
||||
$form['live_preview']['example'] = array(
|
||||
'#type' => 'hierarchical_select',
|
||||
'#required' => $preview_is_required,
|
||||
'#title' => t('Preview'),
|
||||
'#description' => t('The description.'),
|
||||
// Skip al validation for this form element: the data collected through it
|
||||
// is always discarded, it's merely here for illustrative purposes.
|
||||
'#validated' => TRUE,
|
||||
);
|
||||
hierarchical_select_common_config_apply($form['live_preview']['example'], $config_id, array_merge($defaults_override, array('module' => $module, 'params' => $params)));
|
||||
|
||||
$form['save_lineage'] = array(
|
||||
'#type' => 'radios',
|
||||
'#title' => t('Save lineage'),
|
||||
'#options' => array(
|
||||
1 => t('Save !item lineage', $args),
|
||||
0 => t('Save only the deepest !item', $args),
|
||||
),
|
||||
'#default_value' => (isset($config['save_lineage'])) ? $config['save_lineage'] : NULL,
|
||||
'#description' => t(
|
||||
'Saving the !item lineage means saving the <em>the !item itself and all
|
||||
its ancestors</em>.',
|
||||
$args
|
||||
),
|
||||
);
|
||||
|
||||
$form['enforce_deepest'] = array(
|
||||
'#type' => 'radios',
|
||||
'#title' => t('Level choice'),
|
||||
'#options' => array(
|
||||
1 => t('Force the user to choose a !item from a <em>deepest level</em>', $args),
|
||||
0 => t('Allow the user to choose a !item from <em>any level</em>', $args),
|
||||
),
|
||||
'#default_value' => (isset($config['enforce_deepest'])) ? $config['enforce_deepest'] : NULL,
|
||||
'#description' => t(
|
||||
'This setting determines from which level in the !hierarchy tree a
|
||||
user can select a !item.',
|
||||
$args
|
||||
),
|
||||
'#attributes' => array('class' => array('enforce-deepest')),
|
||||
);
|
||||
|
||||
$form['resizable'] = array(
|
||||
'#type' => 'radios',
|
||||
'#title' => t('Resizable'),
|
||||
'#description' => t(
|
||||
"When enabled, a handle appears below the Hierarchical Select to allow
|
||||
the user to dynamically resize it. Double clicking will toggle between
|
||||
the smallest and a sane 'big size'."
|
||||
),
|
||||
'#options' => array(
|
||||
0 => t('Disabled'),
|
||||
1 => t('Enabled'),
|
||||
),
|
||||
'#default_value' => (isset($config['resizable'])) ? $config['resizable'] : NULL,
|
||||
'#attributes' => array('class' => array('resizable')),
|
||||
);
|
||||
|
||||
$form['level_labels'] = array(
|
||||
'#tree' => TRUE,
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Level labels'),
|
||||
'#description' => t(
|
||||
'When the user is allowed to choose a !item from any level in the
|
||||
<em>Level choice</em> setting, you can enter a label for <em>each</em>
|
||||
level.<br />
|
||||
However, when the user is only allowed to choose a !item from the
|
||||
deepest level, then you can only enter a label for the <em>root</em>
|
||||
level.',
|
||||
$args
|
||||
),
|
||||
'#collapsible' => TRUE,
|
||||
);
|
||||
$form['level_labels']['status'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Enable level labels'),
|
||||
'#default_value' => (isset($config['level_labels']['status'])) ? $config['level_labels']['status'] : NULL,
|
||||
'#attributes' => array('class' => array('level-labels-status')),
|
||||
);
|
||||
for ($depth = 0; $depth <= $max_hierarchy_depth; $depth++) {
|
||||
$form['level_labels']['labels'][$depth] = array(
|
||||
'#type' => 'textfield',
|
||||
'#size' => 20,
|
||||
'#maxlength' => 255,
|
||||
'#default_value' => (isset($config['level_labels']['labels'][$depth])) ? $config['level_labels']['labels'][$depth] : NULL,
|
||||
'#attributes' => array('class' => array('level-label')),
|
||||
);
|
||||
}
|
||||
$form['level_labels']['#theme'] = 'hierarchical_select_common_config_form_level_labels';
|
||||
$form['level_labels']['#strings'] = $strings;
|
||||
|
||||
$form['dropbox'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Dropbox settings'),
|
||||
'#description' => t('The dropbox allows the user to <strong>make multiple selections</strong>.'),
|
||||
'#collapsible' => TRUE,
|
||||
);
|
||||
$form['dropbox']['status'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Enable the dropbox'),
|
||||
'#default_value' => (isset($config['dropbox']['status'])) ? $config['dropbox']['status'] : NULL,
|
||||
'#attributes' => array('class' => array('dropbox-status')),
|
||||
);
|
||||
$form['dropbox']['title'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Title'),
|
||||
'#description' => t('The title you enter here appears above the dropbox.'),
|
||||
'#size' => 20,
|
||||
'#maxlength' => 255,
|
||||
'#default_value' => (isset($config['dropbox']['title'])) ? $config['dropbox']['title'] : NULL,
|
||||
'#attributes' => array('class' => array('dropbox-title')),
|
||||
);
|
||||
$form['dropbox']['limit'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Limit the number of selections'),
|
||||
'#description' => t(
|
||||
'Limits the number of selections that can be added to the dropbox.
|
||||
0 means no limit.
|
||||
<br />
|
||||
Note: the "Save !item lineage" option has no effect on this, even if
|
||||
a lineage consists of 3 !items, this will count as only one selection
|
||||
in the dropbox.',
|
||||
$args
|
||||
),
|
||||
'#size' => 5,
|
||||
'#maxlength' => 5,
|
||||
'#default_value' => (isset($config['dropbox']['limit'])) ? $config['dropbox']['limit'] : NULL,
|
||||
'#attributes' => array('class' => array('dropbox-limit')),
|
||||
);
|
||||
$form['dropbox']['reset_hs'] = array(
|
||||
'#type' => 'radios',
|
||||
'#title' => t('Reset selection of hierarchical select'),
|
||||
'#description' => t(
|
||||
'This setting determines what will happen to the hierarchical select
|
||||
when the user has added a selection to the dropbox.'
|
||||
),
|
||||
'#options' => array(
|
||||
0 => t('Disabled'),
|
||||
1 => t('Enabled'),
|
||||
),
|
||||
'#default_value' => (isset($config['dropbox']['reset_hs'])) ? $config['dropbox']['reset_hs'] : NULL,
|
||||
'#attributes' => array('class' => array('dropbox-reset-hs')),
|
||||
);
|
||||
$form['dropbox']['sort'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Sort dropbox items'),
|
||||
'#description' => t('Automatically sort items added to the dropbox. If unchecked new items will be added to the end of the dropbox list.'),
|
||||
'#default_value' => (isset($config['dropbox']['sort'])) ? $config['dropbox']['sort'] : 1,
|
||||
'#attributes' => array('class' => array('dropbox-sort')),
|
||||
);
|
||||
if (module_hook($module, 'hierarchical_select_create_item')) {
|
||||
$form['editability'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Editability settings'),
|
||||
'#description' => t(
|
||||
'You can allow the user to <strong>add new !items to this
|
||||
!hierarchy</strong> <em>through</em> Hierarchical Select.',
|
||||
$args
|
||||
),
|
||||
'#collapsible' => TRUE,
|
||||
);
|
||||
$form['editability']['status'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Allow creation of new !items', $args),
|
||||
'#options' => array(
|
||||
0 => t('Disabled'),
|
||||
1 => t('Enabled'),
|
||||
),
|
||||
'#default_value' => (isset($config['editability']['status'])) ? $config['editability']['status'] : NULL,
|
||||
'#attributes' => array('class' => array('editability-status')),
|
||||
);
|
||||
for ($depth = 0; $depth <= $max_hierarchy_depth; $depth++) {
|
||||
$form['editability']['item_types'][$depth] = array(
|
||||
'#type' => 'textfield',
|
||||
'#size' => 20,
|
||||
'#maxlength' => 255,
|
||||
'#default_value' => (isset($config['editability']['item_types'][$depth])) ? $config['editability']['item_types'][$depth] : NULL,
|
||||
'#attributes' => array('class' => array('editability-item-type')),
|
||||
);
|
||||
}
|
||||
for ($depth = 0; $depth <= $max_hierarchy_depth; $depth++) {
|
||||
$form['editability']['allowed_levels'][$depth] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#default_value' => (isset($config['editability']['allowed_levels'][$depth])) ? $config['editability']['allowed_levels'][$depth] : 1,
|
||||
);
|
||||
}
|
||||
$form['editability']['allow_new_levels'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Allow creation of new levels'),
|
||||
'#default_value' => $config['editability']['allow_new_levels'],
|
||||
'#description' => t(
|
||||
'Allow the user to create child !items for !items that do not yet have
|
||||
children.',
|
||||
$args
|
||||
),
|
||||
'#attributes' => array('class' => array('editability-allow-new-levels')),
|
||||
);
|
||||
$form['editability']['max_levels'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Maximum number of levels allowed'),
|
||||
'#options' => array(
|
||||
0 => t('0 (no limit)'), 1, 2, 3, 4, 5, 6, 7, 8, 9
|
||||
),
|
||||
'#default_value' => (isset($config['editability']['max_levels'])) ? $config['editability']['max_levels'] : NULL,
|
||||
'#description' => t(
|
||||
'When the user is allowed to create new levels, this option prevents
|
||||
the user from creating extremely deep !hierarchies.',
|
||||
$args
|
||||
),
|
||||
'#attributes' => array('class' => array('editability-max-levels')),
|
||||
);
|
||||
|
||||
$form['editability']['#theme'] = 'hierarchical_select_common_config_form_editability';
|
||||
$form['editability']['#strings'] = $strings;
|
||||
}
|
||||
|
||||
if (module_hook($module, 'hierarchical_select_entity_count')) {
|
||||
$form['entity_count'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Entity Count'),
|
||||
'#collapsible' => TRUE,
|
||||
);
|
||||
|
||||
$form['entity_count']['enabled'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Display number of entities'),
|
||||
'#description' => t('Display the number of entities associated with the !item. Do not forget to check which entities should be counted.', $args),
|
||||
'#default_value' => isset($config['entity_count']['enabled']) ? $config['entity_count']['enabled'] : FALSE,
|
||||
'#weight' => -1,
|
||||
'#attributes' => array('class' => array('entity-count-enabled')),
|
||||
);
|
||||
|
||||
$form['entity_count']['require_entity'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Require associated entity'),
|
||||
'#description' => t('If checked only !items that have at least one entity associated with them will be displayed.', $args),
|
||||
'#default_value' => (isset($config['entity_count']['require_entity'])) ? $config['entity_count']['require_entity'] : FALSE,
|
||||
);
|
||||
|
||||
$form['entity_count']['settings'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Entity count settings'),
|
||||
'#collapsible' => TRUE,
|
||||
'#collapsed' => FALSE,
|
||||
'#weight' => -1,
|
||||
'#attributes' => array('class' => array('entity-count-settings')),
|
||||
);
|
||||
|
||||
$form['entity_count']['settings']['count_children'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Also count children of !item.', $args),
|
||||
'#description' => t('If checked this will result in a larger number because the children will be counted also.'),
|
||||
'#default_value' => isset($config['entity_count']['settings']['count_children']) ? $config['entity_count']['settings']['count_children'] : FALSE,
|
||||
);
|
||||
|
||||
$form['entity_count']['settings']['entity_types'] = array(
|
||||
'#type' => 'item',
|
||||
'#title' => t('Select entities that should be counted.'),
|
||||
'#description' => t('Select entity type or one of it\'s bundles that should be counted'),
|
||||
);
|
||||
|
||||
$entity_info = entity_get_info();
|
||||
foreach ($entity_info as $entity => $entity_info) {
|
||||
if (!empty($entity_info['bundles']) && $entity_info['fieldable'] === TRUE) {
|
||||
$options = array();
|
||||
$default_values = array();
|
||||
|
||||
$form['entity_count']['settings']['entity_types'][$entity] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => check_plain($entity_info['label']),
|
||||
'#collapsible' => TRUE,
|
||||
'#collapsed' => TRUE,
|
||||
);
|
||||
|
||||
foreach ($entity_info['bundles'] as $bundle => $bundle_info) {
|
||||
$options[$bundle] = check_plain($bundle_info['label']);
|
||||
$default_values[$entity][$bundle] = isset($config['entity_count']['settings']['entity_types'][$entity]['count_' . $entity][$bundle]) ? $config['entity_count']['settings']['entity_types'][$entity]['count_' . $entity][$bundle] : 0;
|
||||
}
|
||||
|
||||
$form['entity_count']['settings']['entity_types'][$entity]['count_' . $entity] = array(
|
||||
'#type' => 'checkboxes',
|
||||
'#options' => $options,
|
||||
'#default_value' => $default_values[$entity],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit callback for the hierarchical_select_common_config_form form.
|
||||
*/
|
||||
function hierarchical_select_common_config_form_submit($form, &$form_state) {
|
||||
$config = _hierarchical_select_get_form_item_by_parents($form_state['values'], $form['#hs_common_config_form_parents']);
|
||||
|
||||
// Don't include the value of the live preview in the config.
|
||||
unset($config['live_preview']);
|
||||
|
||||
hierarchical_select_common_config_set($config['config_id'], $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the form element of a form that has a certain lineage of parents.
|
||||
*
|
||||
* @param $form
|
||||
* A structured array for use in the Forms API.
|
||||
* @param $parents
|
||||
* An array of parent form element names.
|
||||
* @return
|
||||
* The form element that has the specified lineage of parents.
|
||||
*/
|
||||
function _hierarchical_select_get_form_item_by_parents($form, $parents) {
|
||||
if (count($parents)) {
|
||||
$parent = array_shift($parents);
|
||||
return _hierarchical_select_get_form_item_by_parents($form[$parent], $parents);
|
||||
}
|
||||
else {
|
||||
return $form;
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
|
||||
.hierarchical-select-config-form .live-preview {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: 25em;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.hierarchical-select-config-form fieldset {
|
||||
clear: right;
|
||||
}
|
@@ -0,0 +1,148 @@
|
||||
|
||||
Drupal.HierarchicalSelectConfigForm = {};
|
||||
|
||||
(function ($, cfg) {
|
||||
|
||||
cfg.context = function(configId) {
|
||||
if (configId === undefined) {
|
||||
return $('.hierarchical-select-config-form > *').not('.live-preview');
|
||||
}
|
||||
else {
|
||||
return $('#hierarchical-select-config-form-'+ configId + ' > *').not('.live-preview');
|
||||
}
|
||||
};
|
||||
|
||||
cfg.levelLabels = function(configId) {
|
||||
var $status = $('.level-labels-status', cfg.context(configId));
|
||||
var $enforceDeepest = $('.enforce-deepest input', cfg.context(configId));
|
||||
|
||||
var showHide = function(speed) {
|
||||
$affected = $('.level-labels-settings', cfg.context(configId));
|
||||
if (!$status.is(':checked')) {
|
||||
$affected.hide(speed);
|
||||
}
|
||||
else {
|
||||
// For showing/hiding rows, I'm relying on setting the style
|
||||
// "display: none" and removing it again. jQuery's show()/hide() leave
|
||||
// "display: block" behind and are thereby messing up the table layout.
|
||||
if ($enforceDeepest.slice(1, 2).is(':checked')) {
|
||||
$affected.find('tr').removeAttr('style');
|
||||
}
|
||||
else {
|
||||
// We need to take special measures if sticky headers are enabled, so
|
||||
// handle the show/hide separately when it's enabled.
|
||||
if ($affected.find('table.sticky-header').length == 0) {
|
||||
$affected.find('tr').slice(0, 2).removeAttr('style'); // Show header tr and root level tr.
|
||||
$affected.find('tr').slice(2).attr('style', 'display: none'); // Hide all other tr's.
|
||||
}
|
||||
else {
|
||||
$affected.find('table').show(speed); // Show both tables (the one with the sticky headers and the one with the actual content).
|
||||
$affected.find('table').slice(1).find('tr').slice(2).attr('style', 'display: none'); // Show all tr's after the header tr and root level tr of the 2nd table (the one with the actual content).
|
||||
}
|
||||
}
|
||||
|
||||
// If $status was unchecked previously, the entire div would have been
|
||||
// hidden!
|
||||
if ($affected.css('display') == 'none') {
|
||||
$affected.show(speed);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$status.click(function() { showHide(200); });
|
||||
$enforceDeepest.click(function() { showHide(200); });
|
||||
showHide(0);
|
||||
};
|
||||
|
||||
cfg.dropbox = function(configId) {
|
||||
var $status = $('.dropbox-status', cfg.context(configId));
|
||||
|
||||
var showHide = function(speed) {
|
||||
var $affected = $('.dropbox-title, .dropbox-limit, .dropbox-reset-hs', cfg.context(configId)).parent();
|
||||
if ($status.is(':checked')) {
|
||||
$affected.show(speed);
|
||||
}
|
||||
else {
|
||||
$affected.hide(speed);
|
||||
}
|
||||
};
|
||||
|
||||
$status.click(function() { showHide(200); });
|
||||
showHide(0);
|
||||
};
|
||||
|
||||
cfg.editability = function(configId) {
|
||||
var $status = $('.editability-status', cfg.context(configId));
|
||||
var $allowNewLevels = $('.editability-allow-new-levels', cfg.context(configId));
|
||||
|
||||
var showHide = function(speed) {
|
||||
var $affected = $('.editability-per-level-settings, .form-item:has(.editability-allow-new-levels)', cfg.context(configId));
|
||||
var $maxLevels = $('.form-item:has(.editability-max-levels)', cfg.context(configId));
|
||||
if ($status.is(':checked')) {
|
||||
if ($allowNewLevels.is(':checked')) {
|
||||
$affected.add($maxLevels).show(speed);
|
||||
}
|
||||
else {
|
||||
$affected.show(speed);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$affected.add($maxLevels).hide(speed);
|
||||
}
|
||||
};
|
||||
|
||||
var showHideMaxLevels = function(speed) {
|
||||
$affected = $('.editability-max-levels', cfg.context(configId)).parent();
|
||||
if ($allowNewLevels.is(':checked')) {
|
||||
$affected.show(speed);
|
||||
}
|
||||
else {
|
||||
$affected.hide(speed);
|
||||
}
|
||||
};
|
||||
|
||||
$status.click(function() { showHide(200); });
|
||||
$allowNewLevels.click(function() { showHideMaxLevels(200); });
|
||||
showHideMaxLevels(0);
|
||||
showHide(0);
|
||||
};
|
||||
|
||||
cfg.entityCount = function(configId) {
|
||||
var $status = $('.entity-count-enabled', cfg.context(configId));
|
||||
|
||||
var showHide = function(speed) {
|
||||
var $affected = $('.entity-count-settings', cfg.context(configId));
|
||||
if ($status.is(':checked')) {
|
||||
$affected.show(speed);
|
||||
}
|
||||
else {
|
||||
$affected.hide(speed);
|
||||
}
|
||||
};
|
||||
|
||||
$status.click(function() { showHide(200); });
|
||||
showHide(0);
|
||||
};
|
||||
|
||||
cfg.livePreview = function(configId) {
|
||||
// React on changes to any input, except the ones in the live preview.
|
||||
$updateLivePreview = $('input', cfg.context(configId))
|
||||
.filter(':not(.create-new-item-input):not(.create-new-item-create):not(.create-new-item-cancel)')
|
||||
.change(function() {
|
||||
// TODO: Do an AJAX submit of the entire form.
|
||||
});
|
||||
};
|
||||
|
||||
$(document).ready(function() {
|
||||
for (var id in Drupal.settings.HierarchicalSelect.configForm) {
|
||||
var configId = Drupal.settings.HierarchicalSelect.configForm.id;
|
||||
|
||||
cfg.levelLabels(configId);
|
||||
cfg.dropbox(configId);
|
||||
cfg.editability(configId);
|
||||
cfg.entityCount(configId);
|
||||
//cfg.livePreview(configId);
|
||||
}
|
||||
});
|
||||
|
||||
})(jQuery, Drupal.HierarchicalSelectConfigForm);
|
@@ -0,0 +1,413 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* All theme functions for the Hierarchical Select module.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @ingroup themeable
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Return a themed Hierarchical Select form element.
|
||||
*
|
||||
* @param array $variables
|
||||
* An associative array containing the properties of the element.
|
||||
* Properties used: title, description, id, required
|
||||
*
|
||||
* @return string
|
||||
* A string representing the form element.
|
||||
*
|
||||
* @ingroup themeable
|
||||
*/
|
||||
function theme_hierarchical_select_form_element($variables) {
|
||||
$element = $variables['element'];
|
||||
$value = $variables['value'];
|
||||
|
||||
$output = '<div class="form-item hierarchical-select-wrapper-wrapper"';
|
||||
if (!empty($element['#id'])) {
|
||||
$output .= ' id="' . $element['#id'] . '-wrapper"';
|
||||
}
|
||||
$output .= ">\n";
|
||||
$required = !empty($element['#required']) ? '<span class="form-required" title="' . t('This field is required.') . '">*</span>' : '';
|
||||
|
||||
if (!empty($element['#title'])) {
|
||||
$title = $element['#title'];
|
||||
if (!empty($element['#id'])) {
|
||||
$output .= ' <label for="' . $element['#id'] . '">' . t('!title: !required', array('!title' => filter_xss_admin($title), '!required' => $required)) . "</label>\n";
|
||||
}
|
||||
else {
|
||||
$output .= ' <label>' . t('!title: !required', array('!title' => filter_xss_admin($title), '!required' => $required)) . "</label>\n";
|
||||
}
|
||||
}
|
||||
|
||||
$output .= " $value\n";
|
||||
|
||||
if (!empty($element['#description'])) {
|
||||
$output .= ' <div class="description">' . $element['#description'] . "</div>\n";
|
||||
}
|
||||
|
||||
$output .= "</div>\n";
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a hierarchical select.
|
||||
*
|
||||
* @param array $variables
|
||||
* An associative array containing the properties of the element.
|
||||
* @return string
|
||||
* A themed HTML string representing the form element.
|
||||
*/
|
||||
function theme_hierarchical_select($variables) {
|
||||
$element = $variables['element'];
|
||||
$output = '';
|
||||
|
||||
// Update $element['#attributes']['class'].
|
||||
if (!isset($element['#attributes']['class'])) {
|
||||
$element['#attributes']['class'] = array();
|
||||
}
|
||||
$hsid = $element['hsid']['#value'];
|
||||
$level_labels_style = variable_get('hierarchical_select_level_labels_style', 'none');
|
||||
$classes = array(
|
||||
'hierarchical-select-wrapper',
|
||||
"hierarchical-select-level-labels-style-$level_labels_style",
|
||||
// Classes that make it possible to override the styling of specific
|
||||
// instances of Hierarchical Select, based on either the ID of the form
|
||||
// element or the config that it uses.
|
||||
'hierarchical-select-wrapper-for-name-' . $element['#id'],
|
||||
(isset($element['#config']['config_id'])) ? 'hierarchical-select-wrapper-for-config-' . $element['#config']['config_id'] : NULL,
|
||||
);
|
||||
$element['#attributes']['class'] = array_merge($element['#attributes']['class'], $classes);
|
||||
$element['#attributes']['id'] = "hierarchical-select-$hsid-wrapper";
|
||||
$element['#id'] = "hierarchical-select-$hsid-wrapper"; // This ensures the label's for attribute is correct.
|
||||
|
||||
return '<div ' . drupal_attributes($element['#attributes']) . '>' . drupal_render_children($element) . '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the container for all selects in the hierarchical select.
|
||||
*
|
||||
* @param array $variables
|
||||
* An associative array containing the properties of the element.
|
||||
* @return string
|
||||
* A themed HTML string representing the form element.
|
||||
*/
|
||||
function theme_hierarchical_select_selects_container($variables) {
|
||||
$element = $variables['element'];
|
||||
$output = '';
|
||||
$output .= '<div class="hierarchical-select clearfix">';
|
||||
$output .= drupal_render_children($element);
|
||||
$output .= '</div>';
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a select in the .hierarchial-select div: prevent it from being
|
||||
* wrapped in a div. This simplifies the CSS and JS code.
|
||||
*
|
||||
* @param array $variables
|
||||
* An associative array containing the properties of the element.
|
||||
* @return string
|
||||
* A themed HTML string representing the form element.
|
||||
*/
|
||||
function theme_hierarchical_select_select($variables) {
|
||||
$element = $variables['element'];
|
||||
element_set_attributes($element, array('id', 'name', 'size'));
|
||||
_form_set_class($element, array('form-select'));
|
||||
|
||||
return '<select' . drupal_attributes($element['#attributes']) . '>' . _hierarchical_select_options($element) . '</select>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Format an item separator (for use in a lineage).
|
||||
*/
|
||||
function theme_hierarchical_select_item_separator($variables) {
|
||||
$output = '';
|
||||
$output .= '<span class="hierarchical-select-item-separator">';
|
||||
$output .= '›';
|
||||
$output .= '</span>';
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a special option in a Hierarchical Select select. For example the
|
||||
* "none" option or the "create new item" option. This theme function allows
|
||||
* you to change how a special option is indicated textually.
|
||||
*
|
||||
* @param array $variables
|
||||
* A special option.
|
||||
* @return string
|
||||
* A textually indicated special option.
|
||||
*/
|
||||
function theme_hierarchical_select_special_option($variables) {
|
||||
$option = $variables['option'];
|
||||
return '<' . $option . '>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Forms API theming callback for the dropbox. Renders the dropbox as a table.
|
||||
*
|
||||
* @param array $variables
|
||||
* An element for which the #theme property was set to this function.
|
||||
* @return string
|
||||
* A themed HTML string.
|
||||
*/
|
||||
function theme_hierarchical_select_dropbox_table($variables) {
|
||||
$element = $variables['element'];
|
||||
$output = '';
|
||||
|
||||
$class = 'dropbox';
|
||||
if (form_get_error($element) === '') {
|
||||
$class .= ' error';
|
||||
}
|
||||
|
||||
$title = $element['title']['#value'];
|
||||
$separator = $element['separator']['#value'];
|
||||
$is_empty = $element['is_empty']['#value'];
|
||||
|
||||
$separator_html = '<span class="hierarchical-select-item-separator">' . $separator . '</span>';
|
||||
|
||||
$output .= '<div class="' . $class . '">';
|
||||
$output .= '<table>';
|
||||
$output .= '<caption class="dropbox-title">' . $title . '</caption>';
|
||||
$output .= '<tbody>';
|
||||
|
||||
if (!$is_empty) {
|
||||
// Each lineage in the dropbox corresponds to an entry in the dropbox table.
|
||||
$lineage_count = count(element_children($element['lineages']));
|
||||
for ($x = 0; $x < $lineage_count; $x++) {
|
||||
$db_entry = $element['lineages']["lineage-$x"];
|
||||
$zebra = $db_entry['#zebra'];
|
||||
$first = $db_entry['#first'];
|
||||
$last = $db_entry['#last'];
|
||||
// The deepest level is the number of child levels minus one. This "one"
|
||||
// is the element for the "Remove" checkbox.
|
||||
$deepest_level = count(element_children($db_entry)) - 1;
|
||||
|
||||
$output .= '<tr class="dropbox-entry ' . $first . ' ' . $last . ' ' . $zebra . '">';
|
||||
$output .= '<td>';
|
||||
// Each item in a lineage is separated by the separator string.
|
||||
for ($depth = 0; $depth < $deepest_level; $depth++) {
|
||||
$output .= drupal_render($db_entry[$depth]);
|
||||
|
||||
if ($depth < $deepest_level - 1) {
|
||||
$output .= $separator_html;
|
||||
}
|
||||
}
|
||||
$output .= '</td>';
|
||||
$output .= '<td class="dropbox-remove">' . drupal_render($db_entry['remove']) . '</td>';
|
||||
$output .= '</tr>';
|
||||
}
|
||||
}
|
||||
else {
|
||||
$output .= '<tr class="dropbox-entry first last dropbox-is-empty"><td>';
|
||||
$output .= t('Nothing has been selected.');
|
||||
$output .= '</td></tr>';
|
||||
}
|
||||
|
||||
$output .= '</tbody>';
|
||||
$output .= '</table>';
|
||||
$output .= '</div>';
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Themeing function to render the level_labels settings as a table.
|
||||
*/
|
||||
// TODO: rename $form to $element for consistency (and update hook_theme() after that), make the comment consistent.
|
||||
/**
|
||||
* @todo Please document this function.
|
||||
* @see http://drupal.org/node/1354
|
||||
*/
|
||||
function theme_hierarchical_select_common_config_form_level_labels($variables) {
|
||||
$form = $variables['form'];
|
||||
// Recover the stored strings.
|
||||
$strings = $form['#strings'];
|
||||
|
||||
$output = '';
|
||||
$header = array(t('Level'), t('Label'));
|
||||
$rows = array();
|
||||
|
||||
$output .= drupal_render($form['status']);
|
||||
|
||||
$output .= '<div class="level-labels-settings">';
|
||||
if (isset($form['labels']) && count(element_children($form['labels']))) {
|
||||
foreach (element_children($form['labels']) as $depth) {
|
||||
$row = array();
|
||||
$row[]['data'] = ($depth == 0) ? t('Root level') : t('Sublevel !depth', array('!depth' => $depth));
|
||||
$row[]['data'] = drupal_render($form['labels'][$depth]);
|
||||
$rows[] = $row;
|
||||
}
|
||||
|
||||
// Render the table.
|
||||
$output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('style' => 'width: auto;')));
|
||||
}
|
||||
else {
|
||||
// No levels exist yet in the hierarchy!
|
||||
$output .= '<p><strong>';
|
||||
$output .= t('There are no levels yet in this !hierarchy!', array('!hierarchy' => $strings['hierarchy']));
|
||||
$output .= '</strong></p>';
|
||||
}
|
||||
$output .= '</div>';
|
||||
|
||||
// Render the remaining form items.
|
||||
$output .= drupal_render_children($form);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Themeing function to render the per-level editability settings as a table,
|
||||
* (these are the item_types and allowed_levels settings).
|
||||
*/
|
||||
// TODO: rename $form to $element for consistency (and update hook_theme() after that), make the comment consistent.
|
||||
/**
|
||||
* @todo Please document this function.
|
||||
* @see http://drupal.org/node/1354
|
||||
*/
|
||||
function theme_hierarchical_select_common_config_form_editability($variables) {
|
||||
$form = $variables['form'];
|
||||
// Recover the stored strings.
|
||||
$strings = $form['#strings'];
|
||||
|
||||
$output = '';
|
||||
$header = array(t('Level'), t('Allow'), t('!item_type', array('!item_type' => drupal_ucfirst($strings['item_type']))));
|
||||
$rows = array();
|
||||
|
||||
$output .= drupal_render($form['status']);
|
||||
|
||||
$output .= '<div class="editability-per-level-settings">';
|
||||
if (isset($form['item_types']) && count(element_children($form['item_types']))) {
|
||||
foreach (element_children($form['item_types']) as $depth) {
|
||||
$row = array();
|
||||
$row[]['data'] = ($depth == 0) ? t('Root level') : t('Sublevel !depth', array('!depth' => $depth));
|
||||
$row[]['data'] = drupal_render($form['allowed_levels'][$depth]);
|
||||
$row[]['data'] = drupal_render($form['item_types'][$depth]);
|
||||
$rows[] = $row;
|
||||
}
|
||||
|
||||
// Render the table and description.
|
||||
$output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('style' => 'width: auto;'), 'caption' => '<em>' . t('Per-level settings for creating new !items.', array('!items' => $strings['items']))));
|
||||
$output .= '<div class="description">';
|
||||
$output .= t(
|
||||
'The %item_type you enter for each level is what will be used in
|
||||
each level to replace a "<create new item>" option with a
|
||||
"<create new %item_type>" option, which is often more
|
||||
intuitive.',
|
||||
array(
|
||||
'%item_type' => $strings['item_type'],
|
||||
)
|
||||
);
|
||||
$output .= '</div>';
|
||||
}
|
||||
else {
|
||||
// No levels exist yet in the hierarchy!
|
||||
$output .= '<p><strong>';
|
||||
$output .= t('There are no levels yet in this !hierarchy!', array('!hierarchy' => $strings['hierarchy']));
|
||||
$output .= '</strong></p>';
|
||||
}
|
||||
$output .= '</div>';
|
||||
|
||||
// Render the remaining form items.
|
||||
$output .= drupal_render_children($form);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Themeing function to render a selection (of items) according to a given
|
||||
* Hierarchical Select configuration as one or more lineages.
|
||||
*
|
||||
* @param $selection
|
||||
* A selection of items of a hierarchy.
|
||||
* @param $config
|
||||
* A config array with at least the following settings:
|
||||
* - module
|
||||
* - save_lineage
|
||||
* - params
|
||||
*/
|
||||
function theme_hierarchical_select_selection_as_lineages($variables) {
|
||||
$selection = $variables['selection'];
|
||||
$config = $variables['config'];
|
||||
$output = '';
|
||||
|
||||
$selection = (!is_array($selection)) ? array($selection) : $selection;
|
||||
|
||||
// Generate a dropbox out of the selection. This will automatically
|
||||
// calculate all lineages for us.
|
||||
$selection = array_keys($selection);
|
||||
$dropbox = _hierarchical_select_dropbox_generate($config, $selection);
|
||||
|
||||
// Actual formatting.
|
||||
foreach ($dropbox->lineages as $id => $lineage) {
|
||||
if ($id > 0) {
|
||||
$output .= '<br />';
|
||||
}
|
||||
|
||||
$items = array();
|
||||
foreach ($lineage as $level => $item) {
|
||||
$items[] = $item['label'];
|
||||
}
|
||||
$output .= implode('<span class="hierarchical-select-item-separator">›</span>', $items);
|
||||
}
|
||||
|
||||
// Add the CSS.
|
||||
drupal_add_css(drupal_get_path('module', 'hierarchical_select') . '/hierarchical_select.css');
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "ingroup themeable".
|
||||
*/
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Private functions.
|
||||
|
||||
/**
|
||||
* This is an altered clone of form_select_options(). The reason: I need to be
|
||||
* able to set a class on an option element if it contains a level label, to
|
||||
* allow for level label styles.
|
||||
* TODO: rename to _hierarchical_select_select_options().
|
||||
*/
|
||||
function _hierarchical_select_options($element) {
|
||||
if (!isset($choices)) {
|
||||
$choices = $element['#options'];
|
||||
}
|
||||
// array_key_exists() accommodates the rare event where $element['#value'] is NULL.
|
||||
// isset() fails in this situation.
|
||||
$value_valid = isset($element['#value']) || array_key_exists('#value', $element);
|
||||
$value_is_array = isset($element['#value']) && is_array($element['#value']);
|
||||
$options = '';
|
||||
foreach ($choices as $key => $choice) {
|
||||
$key = (string) $key;
|
||||
if ($value_valid && (!$value_is_array && (string) $element['#value'] === $key || ($value_is_array && in_array($key, $element['#value'])))) {
|
||||
$selected = ' selected="selected"';
|
||||
}
|
||||
else {
|
||||
$selected = '';
|
||||
}
|
||||
|
||||
// If an option DOES NOT have child info, then it's a special option:
|
||||
// - label_\d+ (level label)
|
||||
// - none ("<none>")
|
||||
// - create_new_item ("<create new item>")
|
||||
// Only when it's a level label, we have to add a class to this option.
|
||||
if (!isset($element['#childinfo'][$key])) {
|
||||
$class = (preg_match('/label_\d+/', $key)) ? ' level-label' : '';
|
||||
}
|
||||
else {
|
||||
$class = ($element['#childinfo'][$key] == 0) ? 'has-no-children' : 'has-children';
|
||||
}
|
||||
|
||||
$options .= '<option value="' . check_plain($key) . '" class="' . $class . '"' . $selected . '>' . check_plain($choice) . '</option>';
|
||||
}
|
||||
return $options;
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Make Hierarchical Select work in Views' exposed filters form.
|
||||
*
|
||||
* Views' exposed filters form is a GET form, but since Hierarchical Select
|
||||
* really is a combination of various form items, this will result in a very
|
||||
* ugly and unnecessarily long GET URL, which also breaks the exposed filters.
|
||||
* This piece of JavaScript is a necessity to make it work again, but it will
|
||||
* of course only work when JavaScript is enabled!
|
||||
*/
|
||||
|
||||
|
||||
if (Drupal.jsEnabled) {
|
||||
$(document).ready(function(){
|
||||
$('.view-filters form').submit(function() {
|
||||
// Remove the Hierarchical Select form build id and the form id, to
|
||||
// prevent them from ending up in the GET URL.
|
||||
$('#edit-hs-form-build-id').remove();
|
||||
|
||||
// Prepare the hierarchical select form elements that are used as
|
||||
// exposed filters for a GET submit.
|
||||
$('.view-filters form')
|
||||
.find('.hierarchical-select-wrapper')
|
||||
.trigger('prepare-GET-submit');
|
||||
});
|
||||
});
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
name = Hierarchical Select Flat List
|
||||
description = Allows Hierarchical Select's dropbox to be used for selecting multiple items in a flat list of options.
|
||||
dependencies[] = hierarchical_select
|
||||
package = Form Elements
|
||||
core = 7.x
|
||||
|
||||
; Information added by Drupal.org packaging script on 2017-02-15
|
||||
version = "7.x-3.0-beta8"
|
||||
core = "7.x"
|
||||
project = "hierarchical_select"
|
||||
datestamp = "1487167708"
|
||||
|
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Implementation of the Hierarchical Select API that allows one to use
|
||||
* Hierarchical Select's dropbox for selecting multiple items of a flat list.
|
||||
*/
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Hierarchical Select hooks.
|
||||
|
||||
/**
|
||||
* Implements hook_hierarchical_select_params().
|
||||
*/
|
||||
function hs_flatlist_hierarchical_select_params() {
|
||||
$params = array(
|
||||
'options',
|
||||
);
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_hierarchical_select_root_level().
|
||||
*/
|
||||
function hs_flatlist_hierarchical_select_root_level($params) {
|
||||
return $params['options'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_hierarchical_select_children().
|
||||
*/
|
||||
function hs_flatlist_hierarchical_select_children($parent, $params) {
|
||||
return array(); // A flat list doesn't have any children, ever.
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_hierarchical_select_lineage().
|
||||
*/
|
||||
function hs_flatlist_hierarchical_select_lineage($item, $params) {
|
||||
return array($item); // No hierarchies exist in flat lists.
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_hierarchical_select_valid_item().
|
||||
*/
|
||||
function hs_flatlist_hierarchical_select_valid_item($item, $params) {
|
||||
return (in_array($item, array_keys($params['options'])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_hierarchical_select_item_get_label().
|
||||
*/
|
||||
function hs_flatlist_hierarchical_select_item_get_label($item, $params) {
|
||||
return $params['options'][$item];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_hierarchical_select_implementation_info().
|
||||
*/
|
||||
function hs_flatlist_hierarchical_select_implementation_info() {
|
||||
return array(
|
||||
'hierarchy type' => t('None: flat list'),
|
||||
'entity type' => t('N/A'),
|
||||
);
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
name = Hierarchical Select Menu
|
||||
description = Use Hierarchical Select for menu parent selection.
|
||||
dependencies[] = hierarchical_select
|
||||
dependencies[] = menu
|
||||
package = Form Elements
|
||||
core = 7.x
|
||||
|
||||
; Information added by Drupal.org packaging script on 2017-02-15
|
||||
version = "7.x-3.0-beta8"
|
||||
core = "7.x"
|
||||
project = "hierarchical_select"
|
||||
datestamp = "1487167708"
|
||||
|
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Install file for the Hierarchical Select Menu module.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Implements hook_install().
|
||||
*/
|
||||
function hs_menu_install() {
|
||||
// Ensure hs_menu runs after Core's menu.
|
||||
db_update('system')
|
||||
->fields(array('weight' => 1))
|
||||
->condition('name', 'hs_menu')
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementats hook_uninstall().
|
||||
*/
|
||||
function hs_menu_uninstall() {
|
||||
db_delete('variable')
|
||||
->condition('name', 'hs_menu_%', 'LIKE')
|
||||
->execute();
|
||||
}
|
@@ -0,0 +1,326 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Implementation of the Hierarchical Select API for the Menu module.
|
||||
*/
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Drupal core hooks.
|
||||
|
||||
/**
|
||||
* Implements of hook_menu().
|
||||
*/
|
||||
function hs_menu_menu() {
|
||||
$items['admin/config/content/hierarchical_select/menu'] = array(
|
||||
'title' => 'Menu',
|
||||
'description' => 'Hierarchical Select configuration for Menu',
|
||||
'access arguments' => array('administer site configuration'),
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('hs_menu_admin_settings'),
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
);
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_FORMID_alter().
|
||||
*
|
||||
* Alter the node form's menu form.
|
||||
*/
|
||||
function hs_menu_form_node_form_alter(&$form, &$form_state) {
|
||||
$active_types = array_filter(variable_get('hs_menu_content_types', array()));
|
||||
$active = empty($active_types) || in_array($form_state['node']->type, $active_types);
|
||||
if ($active && isset($form['menu']['link']['parent']) && isset($form['menu']['#access']) && $form['menu']['#access']) {
|
||||
unset($form['menu']['link']['parent']['#options']);
|
||||
$form['menu']['link']['parent']['#type'] = 'hierarchical_select';
|
||||
// Get menu name, needed to exclude current node.
|
||||
$menu_name = explode(':', $form['menu']['link']['parent']['#default_value']);
|
||||
_hs_menu_apply_config($form['menu']['link']['parent'], array(
|
||||
0 => $menu_name[0],
|
||||
1 => $form['menu']['link']['mlid']['#value'],
|
||||
'type' => $form['type']['#value'],
|
||||
));
|
||||
|
||||
// Set custom submit callback.
|
||||
array_unshift($form['#submit'], 'hs_menu_node_form_submit');
|
||||
// Change the loaded default value into an array so we can populate the
|
||||
// Hierarchical Select element.
|
||||
$form['menu']['link']['parent']['#default_value'] = array($form['menu']['link']['parent']['#default_value']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_BASE_FORMID_alter().
|
||||
*
|
||||
* Alter the widget type form; dynamically add the Hierarchical Select
|
||||
* Configuration form when it is needed.
|
||||
*/
|
||||
function hs_menu_form_menu_edit_item_alter(&$form, &$form_state) {
|
||||
unset($form['parent']['#options']);
|
||||
$original_item = $form['original_item']['#value'];
|
||||
$form['parent']['#type'] = 'hierarchical_select';
|
||||
_hs_menu_apply_config($form['parent'], array('exclude' => array(
|
||||
$original_item['menu_name'],
|
||||
$original_item['mlid'],
|
||||
)));
|
||||
|
||||
// Set custom submit callback.
|
||||
array_unshift($form['#submit'], 'hs_menu_menu_edit_item_form_submit');
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Form API callbacks.
|
||||
|
||||
/**
|
||||
* Submit callback; menu edit item form.
|
||||
*/
|
||||
function hs_menu_menu_edit_item_form_submit(&$form, &$form_state) {
|
||||
// Don't return an array, but a single item.
|
||||
$form_state['values']['parent'] = $form_state['values']['parent'][0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit callback; node edit form.
|
||||
*/
|
||||
function hs_menu_node_form_submit(&$form, &$form_state) {
|
||||
// Don't return an array, but a single item.
|
||||
$form_state['values']['menu']['parent'] = $form_state['values']['menu']['parent'][0];
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Menu callbacks.
|
||||
|
||||
/**
|
||||
* Form definition; admin settings.
|
||||
*/
|
||||
function hs_menu_admin_settings() {
|
||||
$form['hs_menu_resizable'] = array(
|
||||
'#type' => 'radios',
|
||||
'#title' => t('Resizable'),
|
||||
'#description' => t(
|
||||
"When enabled, a handle appears below the Hierarchical Select to allow
|
||||
the user to dynamically resize it. Double clicking will toggle between
|
||||
the smallest and a sane 'big size'."
|
||||
),
|
||||
'#options' => array(
|
||||
0 => t('Disabled'),
|
||||
1 => t('Enabled'),
|
||||
),
|
||||
'#default_value' => variable_get('hs_menu_resizable', 1),
|
||||
);
|
||||
|
||||
$form['hs_menu_content_types'] = array(
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => t('Content types'),
|
||||
'#description' => t("Select the content types to use Hierarchical Select Menu on. If no content types are selected, then it will apply to all content types."),
|
||||
'#options' => node_type_get_names(),
|
||||
'#default_value' => variable_get('hs_menu_content_types', array()),
|
||||
);
|
||||
|
||||
return system_settings_form($form);
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Hierarchical Select hooks.
|
||||
|
||||
/**
|
||||
* Implements hook_hierarchical_select_params().
|
||||
*/
|
||||
function hs_menu_hierarchical_select_params() {
|
||||
$params = array(
|
||||
'exclude', // The menu_name and mlid (in an array) of a menu link that should be excluded from the hierarchy.
|
||||
);
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_hierarchical_select_root_level().
|
||||
*/
|
||||
function hs_menu_hierarchical_select_root_level($params) {
|
||||
$menus = array();
|
||||
|
||||
$result = db_query("SELECT menu_name, title FROM {menu_custom} ORDER BY title");
|
||||
// If the type is set, respect the core menu options setting.
|
||||
if (isset($params['type'])) {
|
||||
$type_menus = variable_get('menu_options_' . $params['type'], array('main-menu' => 'main-menu'));
|
||||
while ($menu = $result->fetchObject()) {
|
||||
if (in_array($menu->menu_name, $type_menus)) {
|
||||
$menus[$menu->menu_name . ':0'] = $menu->title;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fall back to the legacy approach, show all menu's.
|
||||
else {
|
||||
while ($menu = $result->fetchObject()) {
|
||||
$menus[$menu->menu_name . ':0'] = $menu->title;
|
||||
}
|
||||
}
|
||||
|
||||
return $menus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_hierarchical_select_children().
|
||||
*/
|
||||
function hs_menu_hierarchical_select_children($parent, $params) {
|
||||
$children = array();
|
||||
list($menu_name, $plid) = explode(':', $parent);
|
||||
$tree = menu_tree_all_data($menu_name, NULL);
|
||||
return _hs_menu_children($tree, $menu_name, $plid, $params['exclude']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_hierarchical_select_lineage().
|
||||
*/
|
||||
function hs_menu_hierarchical_select_lineage($item, $params) {
|
||||
$lineage = array($item);
|
||||
|
||||
list($menu_name, $mlid) = explode(':', $item);
|
||||
|
||||
// If the initial mlid is zero, then this is the root level, so we don't
|
||||
// have to get the lineage.
|
||||
if ($mlid > 0) {
|
||||
// Prepend each parent mlid (i.e. plid) to the lineage.
|
||||
do {
|
||||
$plid = db_query("SELECT plid FROM {menu_links} WHERE mlid = :mlid", array(':mlid' => $mlid))->fetchField();
|
||||
array_unshift($lineage, "$menu_name:$plid");
|
||||
if ($mlid == $plid) {
|
||||
// Somehow we have an infinite loop situation. Bail out of the loop.
|
||||
break;
|
||||
}
|
||||
$mlid = $plid;
|
||||
} while ($plid > 0);
|
||||
}
|
||||
|
||||
return $lineage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_hierarchical_select_valid_item().
|
||||
*/
|
||||
function hs_menu_hierarchical_select_valid_item($item, $params) {
|
||||
$parts = explode(':', $item);
|
||||
|
||||
$valid = TRUE;
|
||||
|
||||
// Validate menu name.
|
||||
$valid = (array_key_exists($parts[0], menu_get_menus()));
|
||||
|
||||
// Validate hierarchy of mlids.
|
||||
for ($i = 1; $valid && $i < count($parts); $i++) {
|
||||
$valid = $valid && is_numeric($parts[$i]);
|
||||
}
|
||||
|
||||
// Ensure that this isn't the excluded menu link.
|
||||
$valid = $valid && $item != $params['exclude'][0] . $params['exclude'][1];
|
||||
|
||||
return $valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_hierarchical_select_item_get_label().
|
||||
*/
|
||||
function hs_menu_hierarchical_select_item_get_label($item, $params) {
|
||||
static $labels = array();
|
||||
|
||||
$parts = explode(':', $item);
|
||||
if (count($parts) == 1) { // Get the menu name.
|
||||
$menu_name = $parts[0];
|
||||
$labels[$item] = db_query("SELECT title FROM {menu_custom} WHERE menu_name = :menu_name", array(':menu_name' => $menu_name))->fetchField();
|
||||
}
|
||||
else { // Get the menu link title.
|
||||
$mlid = end($parts);
|
||||
$menu_link = menu_link_load($mlid);
|
||||
$labels[$item] = $menu_link['title'];
|
||||
}
|
||||
|
||||
return $labels[$item];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_hierarchical_select_implementation_info().
|
||||
*/
|
||||
function hs_menu_hierarchical_select_implementation_info() {
|
||||
return array(
|
||||
'hierarchy type' => t('Menu'),
|
||||
'entity type' => t('N/A'),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Private functions.
|
||||
|
||||
/**
|
||||
* Recursive helper function for hs_menu_hierarchical_select_children().
|
||||
*/
|
||||
function _hs_menu_children($tree, $menu_name, $plid = 0, $exclude = FALSE) {
|
||||
$children = array();
|
||||
|
||||
foreach ($tree as $data) {
|
||||
if ($data['link']['plid'] == $plid && $data['link']['hidden'] >= 0) {
|
||||
if ($exclude && $data['link']['menu_name'] === $exclude[0] && $data['link']['mlid'] == $exclude[1]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$title = truncate_utf8($data['link']['title'], 30, TRUE, FALSE);
|
||||
if ($data['link']['hidden']) {
|
||||
$title .= ' (' . t('disabled') . ')';
|
||||
}
|
||||
$children[$menu_name . ':' . $data['link']['mlid']] = $title;
|
||||
if ($data['below']) {
|
||||
$children += _hs_menu_children($data['below'], $menu_name, $plid, $exclude);
|
||||
}
|
||||
}
|
||||
elseif ($data['below']) {
|
||||
$children += _hs_menu_children($data['below'], $menu_name, $plid, $exclude);
|
||||
}
|
||||
}
|
||||
|
||||
return $children;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to apply the HS config to a form item.
|
||||
*/
|
||||
function _hs_menu_apply_config(&$form, $params) {
|
||||
// The following is to ensure via javascript self is not listed.
|
||||
if (!empty($params['exclude'])) {
|
||||
$params['exclude'] = $params['exclude'][0] .':'. $params['exclude'][1];
|
||||
drupal_add_js('jQuery(document).ready(function () {
|
||||
jQuery("[value*=\"' . $params['exclude'] . '\"]").hide();
|
||||
});', 'inline');
|
||||
}
|
||||
$form['#config'] = array(
|
||||
'module' => 'hs_menu',
|
||||
'params' => array(
|
||||
'exclude' => isset($params['exclude']) ? $params['exclude'] : NULL,
|
||||
'type' => isset($params['type']) ? $params['type'] : NULL,
|
||||
),
|
||||
'save_lineage' => 0,
|
||||
'enforce_deepest' => 0,
|
||||
'resizable' => variable_get('hs_menu_resizable', 1),
|
||||
'level_labels' => array(
|
||||
'status' => 0,
|
||||
),
|
||||
'dropbox' => array(
|
||||
'status' => 0,
|
||||
),
|
||||
'editability' => array(
|
||||
'status' => 0,
|
||||
),
|
||||
'entity_count' => array(
|
||||
'enabled' => 0,
|
||||
'require_entity' => 0,
|
||||
'settings' => array(
|
||||
'count_children' => 0,
|
||||
'entity_types' => array(),
|
||||
),
|
||||
),
|
||||
'render_flat_select' => 0,
|
||||
);
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
name = Hierarchical Select Small Hierarchy
|
||||
description = Allows Hierarchical Select to be used for a hardcoded hierarchy. When it becomes to slow, you should move the hierarchy into the database and write a proper implementation.
|
||||
dependencies[] = hierarchical_select
|
||||
package = Form Elements
|
||||
core = 7.x
|
||||
|
||||
; Information added by Drupal.org packaging script on 2017-02-15
|
||||
version = "7.x-3.0-beta8"
|
||||
core = "7.x"
|
||||
project = "hierarchical_select"
|
||||
datestamp = "1487167708"
|
||||
|
@@ -0,0 +1,214 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Implementation of the Hierarchical Select API that allows one to use a
|
||||
* hardcoded hierarchy. When it becomes to slow, you should move the hierarchy
|
||||
* into the database and write a proper implementation.
|
||||
*/
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Hierarchical Select hooks.
|
||||
|
||||
/**
|
||||
* Implementation of hook_hierarchical_select_params().
|
||||
*/
|
||||
function hs_smallhierarchy_hierarchical_select_params() {
|
||||
$params = array(
|
||||
'hierarchy',
|
||||
'id',
|
||||
'separator',
|
||||
);
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_hierarchical_select_root_level().
|
||||
*/
|
||||
function hs_smallhierarchy_hierarchical_select_root_level($params) {
|
||||
$hierarchy = _hs_smallhierarchy_transform($params['hierarchy'], $params['id'], $params['separator']);
|
||||
|
||||
$children = $hierarchy['root']['children'];
|
||||
|
||||
$level = array();
|
||||
foreach ($children as $item) {
|
||||
$level[$item] = $hierarchy[$item]['label'];
|
||||
}
|
||||
|
||||
return $level;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_hierarchical_select_children().
|
||||
*/
|
||||
function hs_smallhierarchy_hierarchical_select_children($parent, $params) {
|
||||
$hierarchy = _hs_smallhierarchy_transform($params['hierarchy'], $params['id'], $params['separator']);
|
||||
|
||||
$children = (isset($hierarchy[$parent]['children'])) ? $hierarchy[$parent]['children'] : array();
|
||||
|
||||
$level = array();
|
||||
foreach ($children as $item) {
|
||||
$level[$item] = $hierarchy[$item]['label'];
|
||||
}
|
||||
|
||||
return $level;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_hierarchical_select_lineage().
|
||||
*/
|
||||
function hs_smallhierarchy_hierarchical_select_lineage($item, $params) {
|
||||
$parts = explode($params['separator'], $item);
|
||||
|
||||
$lineage = array();
|
||||
for ($i = 0; $i < count($parts); $i++) {
|
||||
$lineage[$i] = implode($params['separator'], array_slice($parts, 0, $i + 1));
|
||||
}
|
||||
|
||||
return $lineage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_hierarchical_select_valid_item().
|
||||
*/
|
||||
function hs_smallhierarchy_hierarchical_select_valid_item($item, $params) {
|
||||
$hierarchy = _hs_smallhierarchy_transform($params['hierarchy'], $params['id'], $params['separator']);
|
||||
|
||||
// All valid items are in the keys of the $hierarchy array. Only the fake
|
||||
// "root" item is not a valid item.
|
||||
$items = array_keys($hierarchy);
|
||||
unset($items['root']);
|
||||
|
||||
return (in_array($item, $items));
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_hierarchical_select_item_get_label().
|
||||
*/
|
||||
function hs_smallhierarchy_hierarchical_select_item_get_label($item, $params) {
|
||||
$hierarchy = _hs_smallhierarchy_transform($params['hierarchy'], $params['id'], $params['separator']);
|
||||
return $hierarchy[$item]['label'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_hierarchical_select_implementation_info().
|
||||
*/
|
||||
function hs_smallhierarchy_hierarchical_select_implementation_info() {
|
||||
return array(
|
||||
'hierarchy type' => t('Custom'),
|
||||
'entity type' => t('N/A'),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Private functions.
|
||||
|
||||
/**
|
||||
* Automatically transform a given hierarchy with this format:
|
||||
* array(
|
||||
* 'win' => array(
|
||||
* 'label' => 'Windows',
|
||||
* 'children' => array(
|
||||
* 'xp' => array('label' => 'XP'),
|
||||
* 'vista' => array(
|
||||
* 'label' => 'Vista',
|
||||
* 'children' => array(
|
||||
* 'x86' => array('label' => '32-bits'),
|
||||
* 'x64' => array('label' => '64-bits'),
|
||||
* ),
|
||||
* ),
|
||||
* ),
|
||||
* ),
|
||||
* )
|
||||
*
|
||||
* to one with this format:
|
||||
* array(
|
||||
* 'root' => array(
|
||||
* 'children' => array(
|
||||
* 'xp',
|
||||
* 'vista',
|
||||
* ),
|
||||
* ),
|
||||
* 'win' => array(
|
||||
* 'label' => 'Windows',
|
||||
* 'children' => array(
|
||||
* 'win|xp',
|
||||
* 'win|vista',
|
||||
* ),
|
||||
* ),
|
||||
* 'win|xp' => array(
|
||||
* 'label' => 'XP',
|
||||
* ),
|
||||
* 'win|vista' => array(
|
||||
* 'label' => 'Vista',
|
||||
* 'children' => array(
|
||||
* 'win|vista|x86',
|
||||
* 'win|vista|x64',
|
||||
* ),
|
||||
* ),
|
||||
* 'win|vista|x86' => array(
|
||||
* 'label' => '32-bits',
|
||||
* ),
|
||||
* 'win|vista|x64' => array(
|
||||
* 'label' => '64-bits',
|
||||
* ),
|
||||
* )
|
||||
*
|
||||
* This new format:
|
||||
* - ensures unique identifiers for each item
|
||||
* - makes it very easy to find the parent of a given item.
|
||||
* - makes it very easy to find the label and children of a given item.
|
||||
*
|
||||
* @params $hierarchy
|
||||
* The hierarchy.
|
||||
* @params $id
|
||||
* A unique identifier for the hierarchy, for caching purposes.
|
||||
* @params $separator
|
||||
* The separator to use.
|
||||
*/
|
||||
function _hs_smallhierarchy_transform($hierarchy, $id, $separator = '|') {
|
||||
// Make sure each hierarchy is only transformed once.
|
||||
if (!isset($hs_hierarchy[$id])) {
|
||||
$hs_hierarchy[$id] = array();
|
||||
|
||||
// Build the root level.
|
||||
foreach ($hierarchy as $item => $children) {
|
||||
$hs_hierarchy[$id]['root']['children'][] = $item;
|
||||
$hs_hierarchy[$id][$item]['label'] = $children['label'];
|
||||
|
||||
// Build the subsequent levels.
|
||||
if (isset($children['children'])) {
|
||||
_hs_smallhierarchy_transform_recurse($item, $hs_hierarchy[$id], $children['children'], $separator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $hs_hierarchy[$id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for _hs_smallhierarchy_transform().
|
||||
*
|
||||
* @params $parent
|
||||
* The parent item of the current level.
|
||||
* @params $hs_hierarchy
|
||||
* The HS hierarchy.
|
||||
* @params $relative_hierarchy
|
||||
* The hierarchy relative to the current level.
|
||||
* @params $separator
|
||||
* The separator to use.
|
||||
*/
|
||||
function _hs_smallhierarchy_transform_recurse($parent, &$hs_hierarchy, $relative_hierarchy, $separator = '|') {
|
||||
foreach ($relative_hierarchy as $item => $children) {
|
||||
$generated_item = $parent . $separator . $item;
|
||||
$hs_hierarchy[$parent]['children'][] = $generated_item;
|
||||
$hs_hierarchy[$generated_item]['label'] = $children['label'];
|
||||
|
||||
// Build the subsequent levels.
|
||||
if (isset($children['children'])) {
|
||||
_hs_smallhierarchy_transform_recurse($generated_item, $hs_hierarchy, $children['children'], $separator);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
name = Hierarchical Select Taxonomy
|
||||
description = Use Hierarchical Select for Taxonomy.
|
||||
dependencies[] = hierarchical_select
|
||||
dependencies[] = taxonomy
|
||||
package = Form Elements
|
||||
|
||||
core = 7.x
|
||||
|
||||
; Information added by Drupal.org packaging script on 2017-02-15
|
||||
version = "7.x-3.0-beta8"
|
||||
core = "7.x"
|
||||
project = "hierarchical_select"
|
||||
datestamp = "1487167708"
|
||||
|
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Install file for the Hierarchical Select Taxonomy module.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of hook_uninstall().
|
||||
*/
|
||||
function hs_taxonomy_uninstall() {
|
||||
db_delete('variable')
|
||||
->condition('name', 'taxonomy_hierarchical_select_%', 'LIKE')
|
||||
->execute();
|
||||
variable_del('taxonomy_override_selector');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_enable().
|
||||
*/
|
||||
function hs_taxonomy_enable() {
|
||||
variable_set('taxonomy_override_selector', TRUE);
|
||||
drupal_set_message(t("Drupal core's taxonomy selects are now overridden on the
|
||||
Taxonomy Term form. They've been replaced by
|
||||
Hierarchical Selects for better scalability.<br />
|
||||
You can <a href=\"!configure-url\">configure</a> it to
|
||||
be used on node forms too!",
|
||||
array(
|
||||
'!configure-url' => url('admin/config/content/hierarchical_select/configs'),
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_disable().
|
||||
*/
|
||||
function hs_taxonomy_disable() {
|
||||
variable_set('taxonomy_override_selector', FALSE);
|
||||
drupal_set_message(t("Drupal core's taxonomy selects are now restored.
|
||||
Please remember that they're not scalable!."),
|
||||
'warning');
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Schema updates.
|
||||
|
||||
/**
|
||||
* Upgrade path from Drupal 6 to Drupal 7 version of Hierarchical Select:
|
||||
* - delete the taxonomy_override_selector variable if it exists.
|
||||
*/
|
||||
function hs_taxonomy_update_7300() {
|
||||
variable_del('taxonomy_override_selector');
|
||||
}
|
||||
|
||||
/**
|
||||
* Apparently, taxonomy_override_selector still exists in *one* location in
|
||||
* Drupal 7 core: on the form_taxonomy_form_term form (where you can create or
|
||||
* edit a Taxonomy term).
|
||||
*/
|
||||
function hs_taxonomy_update_7301() {
|
||||
variable_set('taxonomy_override_selector', TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Taxonomy vocabulary config IDs to use machine name instead of serial
|
||||
* vocabulary ID.
|
||||
*/
|
||||
function hs_taxonomy_update_7302() {
|
||||
require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'hierarchical_select') . '/includes/common.inc';
|
||||
|
||||
$vocabularies = taxonomy_vocabulary_get_names();
|
||||
|
||||
foreach ($vocabularies as $machine_name => $vocabulary) {
|
||||
$old_config_id = "taxonomy-{$vocabulary->vid}";
|
||||
$new_config_id = "taxonomy-{$machine_name}";
|
||||
|
||||
$old_config = variable_get('hs_config_' . $old_config_id, NULL);
|
||||
|
||||
if (!empty($old_config)) {
|
||||
hierarchical_select_common_config_set($new_config_id, $old_config);
|
||||
hierarchical_select_common_config_del($old_config_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Taxonomy vocabulary config IDs to use field name.
|
||||
*/
|
||||
function hs_taxonomy_update_7303() {
|
||||
require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'hierarchical_select') . '/includes/common.inc';
|
||||
|
||||
foreach (field_info_instances() as $entity_type => $bundles) {
|
||||
foreach ($bundles as $bundle => $field_list) {
|
||||
foreach ($field_list as $field_name => $instance) {
|
||||
if ($instance['widget']['type'] == 'taxonomy_hs') {
|
||||
$field_info = field_info_field($field_name);
|
||||
$allowed_value = $field_info['settings']['allowed_values'][0];
|
||||
$vocabulary_name = $allowed_value['vocabulary'];
|
||||
$old_config_id = "taxonomy-{$vocabulary_name}";
|
||||
$new_config_id = "taxonomy-{$field_name}";
|
||||
$old_config = hierarchical_select_common_config_get($old_config_id);
|
||||
if (!empty($old_config)) {
|
||||
hierarchical_select_common_config_set($new_config_id, $old_config);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup old-named configs.
|
||||
*/
|
||||
function hs_taxonomy_update_7304() {
|
||||
require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'hierarchical_select') . '/includes/common.inc';
|
||||
|
||||
$vocabularies = taxonomy_get_vocabularies();
|
||||
foreach ($vocabularies as $vid => $vocabulary) {
|
||||
$old_config_id = "taxonomy-{$vocabulary->machine_name}";
|
||||
hierarchical_select_common_config_del($old_config_id);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
This module allows you to use hierarchical_select (version 7.x-3.x) as a widget
|
||||
for a taxonomy-based entityreference field.
|
||||
|
||||
To use it, create an entityreference field, with the Hierarchical Select widget,
|
||||
select "Taxonomy term" as the target type, and select one vocabulary (you must
|
||||
choose exactly one) as the target bundle.
|
||||
|
||||
Credit: John Morahan, iO1 and iVillage.
|
@@ -0,0 +1,18 @@
|
||||
name = Hierarchical Select Entity Reference
|
||||
description = Use the hierarchical select widget for entity reference fields, using taxonomy to provide hierarchy if appropriate, otherwise flat.
|
||||
package = Form Elements
|
||||
core = 7.x
|
||||
dependencies[] = hierarchical_select
|
||||
dependencies[] = hs_taxonomy
|
||||
dependencies[] = entityreference
|
||||
dependencies[] = entity
|
||||
dependencies[] = ctools
|
||||
dependencies[] = options
|
||||
dependencies[] = field
|
||||
|
||||
; Information added by Drupal.org packaging script on 2017-02-15
|
||||
version = "7.x-3.0-beta8"
|
||||
core = "7.x"
|
||||
project = "hierarchical_select"
|
||||
datestamp = "1487167708"
|
||||
|
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Allows hierarchical select to be used with entity reference fields.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_field_widget_info().
|
||||
*/
|
||||
function hser_field_widget_info() {
|
||||
return array(
|
||||
'hser_hierarchy' => array(
|
||||
'label' => t('Hierarchical Select'),
|
||||
'field types' => array('entityreference'),
|
||||
'behaviors' => array(
|
||||
'multiple values' => FIELD_BEHAVIOR_CUSTOM,
|
||||
),
|
||||
'settings' => array(
|
||||
'editable' => FALSE,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_widget_settings_form().
|
||||
*/
|
||||
function hser_field_widget_settings_form($field, $instance) {
|
||||
$widget = $instance['widget'];
|
||||
$settings = $widget['settings'] + field_info_widget_settings($widget['type']);
|
||||
|
||||
$form = array();
|
||||
|
||||
if ($widget['type'] == 'hser_hierarchy') {
|
||||
$form['editable'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Editable'),
|
||||
'#default_value' => $settings['editable'],
|
||||
'#description' => t('Select this to allow users to use the hierarchical select widget to create new terms in the selected vocabulary.'),
|
||||
);
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Implements hook_field_widget_form().
|
||||
*/
|
||||
function hser_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
|
||||
if ($field['settings']['target_type'] == 'taxonomy_term') {
|
||||
$vocabularies = $field['settings']['handler_settings']['target_bundles'];
|
||||
if ((count($vocabularies) == 1) && ($vocabulary = taxonomy_vocabulary_machine_name_load(reset($vocabularies)))) {
|
||||
$default_value = array();
|
||||
foreach ($items as $item) {
|
||||
$default_value[] = $item['target_id'];
|
||||
}
|
||||
$element += array(
|
||||
'#type' => 'hierarchical_select',
|
||||
'#size' => 1,
|
||||
'#default_value' => $default_value,
|
||||
'#element_validate' => array('_hser_element_validate'),
|
||||
'#required' => $instance['required'],
|
||||
'#config' => array(
|
||||
'module' => 'hs_taxonomy',
|
||||
'params' => array(
|
||||
'vid' => $vocabulary->vid,
|
||||
),
|
||||
'save_lineage' => FALSE,
|
||||
'enforce_deepest' => FALSE,
|
||||
'resizable' => FALSE,
|
||||
'level_labels' => array('status' => FALSE),
|
||||
'dropbox' => array(
|
||||
'status' => ($field['cardinality'] != 1),
|
||||
'limit' => $field['cardinality'],
|
||||
),
|
||||
'editability' => array(
|
||||
'status' => $instance['widget']['settings']['editable'],
|
||||
'allow_new_levels' => TRUE,
|
||||
'max_levels' => 0,
|
||||
),
|
||||
'entity_count' => array(
|
||||
'enabled' => 0,
|
||||
'require_entity' => 0,
|
||||
'settings' => array(
|
||||
'count_children' => 0,
|
||||
'entity_types' => array(),
|
||||
),
|
||||
),
|
||||
'render_flat_select' => FALSE,
|
||||
),
|
||||
);
|
||||
return $element;
|
||||
}
|
||||
}
|
||||
// If we reach this point, we decided that hierarchical_select would not be
|
||||
// appropriate for some reason (not taxonomy, no vocabulary selected, etc).
|
||||
// So instead we fall back to a normal select widget.
|
||||
$instance['widget']['type'] = 'options_select';
|
||||
return options_field_widget_form($form, $form_state, $field, $instance, $langcode, $items, $delta, $element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Element validation callback for field widget hierarchical select element.
|
||||
*/
|
||||
function _hser_element_validate($element, &$form_state, $form) {
|
||||
$value = array();
|
||||
foreach ($element['#value'] as $delta => $target_id) {
|
||||
$value[$delta]['target_id'] = $target_id;
|
||||
}
|
||||
form_set_value($element, $value, $form_state);
|
||||
|
||||
if ($element['#required'] && (!isset($form_state['submit_handlers'][0]) || $form_state['submit_handlers'][0] !== 'hierarchical_select_ajax_update_submit')) {
|
||||
if (!count($element['#value']) || (is_string($element['#value']) && strlen(trim($element['#value'])) == 0) || (array_key_exists(0, $element['#value'])) && !$element['#value'][0]) {
|
||||
form_error($element, t('!name field is required.', array('!name' => $element['#title'])));
|
||||
_hierarchical_select_form_set_error_class($element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_node_validate().
|
||||
*
|
||||
* Temporary workaround for https://drupal.org/node/1293166 - remove when that
|
||||
* bug is fixed.
|
||||
*/
|
||||
function hser_node_validate($node, $form, &$form_state) {
|
||||
if (arg(0) == 'hierarchical_select_ajax') {
|
||||
form_set_error('');
|
||||
}
|
||||
}
|
@@ -0,0 +1,433 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* This file contains the unit tests of the internals.
|
||||
*/
|
||||
|
||||
|
||||
//file_put_contents('test.html', print_r(array($hierarchy, $reference), TRUE));
|
||||
define('SEP', '|');
|
||||
define('EURO', 'europe');
|
||||
define('EURO_BE', 'belgium');
|
||||
define('EURO_BE_BRU', 'brussels');
|
||||
define('EURO_BE_HAS', 'hasselt');
|
||||
define('EURO_FR', 'france');
|
||||
define('ASIA', 'asia');
|
||||
define('ASIA_CH', 'china');
|
||||
define('ASIA_JP', 'japan');
|
||||
define('ASIA_JP_TOK', 'tokyo');
|
||||
|
||||
define('LABEL_EURO', 'Europe');
|
||||
define('LABEL_EURO_BE', 'Belgium');
|
||||
define('LABEL_EURO_BE_BRU', 'Brussels');
|
||||
define('LABEL_EURO_BE_HAS', 'Hasselt');
|
||||
define('LABEL_EURO_FR', 'France');
|
||||
define('LABEL_ASIA', 'Asia');
|
||||
define('LABEL_ASIA_CH', 'China');
|
||||
define('LABEL_ASIA_JP', 'Japan');
|
||||
define('LABEL_ASIA_JP_TOK', 'tokyo');
|
||||
|
||||
define('LINEAGE_EURO', implode(SEP, array(EURO)));
|
||||
define('LINEAGE_EURO_BE', implode(SEP, array(EURO, EURO_BE)));
|
||||
define('LINEAGE_EURO_BE_BRU', implode(SEP, array(EURO, EURO_BE, EURO_BE_BRU)));
|
||||
define('LINEAGE_EURO_BE_HAS', implode(SEP, array(EURO, EURO_BE, EURO_BE_HAS)));
|
||||
define('LINEAGE_EURO_FR', implode(SEP, array(EURO, EURO_FR)));
|
||||
define('LINEAGE_ASIA', implode(SEP, array(ASIA)));
|
||||
define('LINEAGE_ASIA_CH', implode(SEP, array(ASIA, ASIA_CH)));
|
||||
define('LINEAGE_ASIA_JP', implode(SEP, array(ASIA, ASIA_JP)));
|
||||
define('LINEAGE_ASIA_JP_TOK', implode(SEP, array(ASIA, ASIA_JP, ASIA_JP_TOK)));
|
||||
|
||||
|
||||
class HierarchicalSelectInternals extends DrupalWebTestCase {
|
||||
private $small_hierarchy = array(
|
||||
EURO => array(
|
||||
'label' => LABEL_EURO,
|
||||
'children' => array(
|
||||
EURO_BE => array(
|
||||
'label' => LABEL_EURO_BE,
|
||||
'children' => array(
|
||||
EURO_BE_BRU => array(
|
||||
'label' => LABEL_EURO_BE_BRU,
|
||||
),
|
||||
EURO_BE_HAS => array(
|
||||
'label' => LABEL_EURO_BE_HAS,
|
||||
),
|
||||
),
|
||||
),
|
||||
EURO_FR => array(
|
||||
'label' => LABEL_EURO_FR,
|
||||
),
|
||||
),
|
||||
),
|
||||
ASIA => array(
|
||||
'label' => LABEL_ASIA,
|
||||
'children' => array(
|
||||
ASIA_CH => array(
|
||||
'label' => LABEL_ASIA_CH,
|
||||
),
|
||||
ASIA_JP => array(
|
||||
'label' => LABEL_ASIA_JP,
|
||||
'children' => array(
|
||||
ASIA_JP_TOK => array(
|
||||
'label' => LABEL_ASIA_JP_TOK,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of getInfo().
|
||||
*/
|
||||
public function getInfo() {
|
||||
return array(
|
||||
'name' => 'Internals',
|
||||
'description' => 'Checks whether all internals are working: the
|
||||
building of the hierarchy and dropbox objects.',
|
||||
'group' => 'Hierarchical Select',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of setUp().
|
||||
*/
|
||||
public function setUp() {
|
||||
parent::setUp('hierarchical_select', 'hs_smallhierarchy');
|
||||
}
|
||||
|
||||
// In this test, all settings are disabled.
|
||||
public function testAllSettingsOff() {
|
||||
// Generate form item.
|
||||
$form_item = array(
|
||||
'#required' => FALSE,
|
||||
'#config' => array(
|
||||
'module' => 'hs_smallhierarchy',
|
||||
'params' => array(
|
||||
'hierarchy' => $this->small_hierarchy,
|
||||
'id' => 'driverpack_platforms',
|
||||
'separator' => '|',
|
||||
),
|
||||
'save_lineage' => 0,
|
||||
'enforce_deepest' => 0,
|
||||
'resizable' => 1,
|
||||
'level_labels' => array(
|
||||
'status' => 0,
|
||||
),
|
||||
'dropbox' => array(
|
||||
'status' => 0,
|
||||
'limit' => 0,
|
||||
'reset_hs' => 1,
|
||||
),
|
||||
'editability' => array(
|
||||
'status' => 0,
|
||||
'item_types' => array(),
|
||||
'allowed_levels' => array(),
|
||||
'allow_new_levels' => 0,
|
||||
'max_levels' => 3,
|
||||
),
|
||||
'entity_count' => array(
|
||||
'enabled' => 0,
|
||||
'require_entity' => 0,
|
||||
'settings' => array(
|
||||
'count_children' => 0,
|
||||
'entity_types' => array(),
|
||||
),
|
||||
),
|
||||
'animation_delay' => 400,
|
||||
'exclusive_lineages' => array(),
|
||||
'render_flat_select' => 0,
|
||||
),
|
||||
);
|
||||
|
||||
// No selection
|
||||
list($hierarchy, $dropbox) = $this->generate($form_item, array(), array());
|
||||
$reference = new stdClass();
|
||||
$reference->lineage = array(
|
||||
0 => 'none',
|
||||
);
|
||||
$reference->levels = array(
|
||||
0 => array(
|
||||
'none' => '<none>',
|
||||
LINEAGE_EURO => LABEL_EURO,
|
||||
LINEAGE_ASIA => LABEL_ASIA,
|
||||
),
|
||||
);
|
||||
$reference->childinfo = array(
|
||||
0 => array(
|
||||
LINEAGE_EURO => 2,
|
||||
LINEAGE_ASIA => 2,
|
||||
),
|
||||
);
|
||||
$this->assertHierarchy($hierarchy, $reference);
|
||||
|
||||
// Europe
|
||||
list($hierarchy, $dropbox) = $this->generate($form_item, array(LINEAGE_EURO), array());
|
||||
$reference->lineage = array(
|
||||
0 => LINEAGE_EURO,
|
||||
1 => 'label_1',
|
||||
);
|
||||
$reference->levels[1] = array(
|
||||
'label_1' => '',
|
||||
LINEAGE_EURO_BE => LABEL_EURO_BE,
|
||||
LINEAGE_EURO_FR => LABEL_EURO_FR,
|
||||
);
|
||||
$reference->childinfo[1] = array(
|
||||
LINEAGE_EURO_BE => 2,
|
||||
LINEAGE_EURO_FR => 0,
|
||||
);
|
||||
$this->assertHierarchy($hierarchy, $reference);
|
||||
|
||||
// Europe > France
|
||||
list($hierarchy, $dropbox) = $this->generate($form_item, array(LINEAGE_EURO_FR), array());
|
||||
$reference->lineage = array(
|
||||
0 => LINEAGE_EURO,
|
||||
1 => LINEAGE_EURO_FR,
|
||||
);
|
||||
$this->assertHierarchy($hierarchy, $reference);
|
||||
|
||||
// Europe > Belgium
|
||||
list($hierarchy, $dropbox) = $this->generate($form_item, array(LINEAGE_EURO_BE), array());
|
||||
$reference->lineage = array(
|
||||
0 => LINEAGE_EURO,
|
||||
1 => LINEAGE_EURO_BE,
|
||||
2 => 'label_2',
|
||||
);
|
||||
$reference->levels[1] = array(
|
||||
'label_1' => '',
|
||||
LINEAGE_EURO_BE => LABEL_EURO_BE,
|
||||
LINEAGE_EURO_FR => LABEL_EURO_FR,
|
||||
);
|
||||
$reference->levels[2] = array(
|
||||
'label_2' => '',
|
||||
LINEAGE_EURO_BE_BRU => LABEL_EURO_BE_BRU,
|
||||
LINEAGE_EURO_BE_HAS => LABEL_EURO_BE_HAS,
|
||||
);
|
||||
$reference->childinfo[1] = array(
|
||||
LINEAGE_EURO_BE => 2,
|
||||
LINEAGE_EURO_FR => 0,
|
||||
);
|
||||
$reference->childinfo[2] = array(
|
||||
LINEAGE_EURO_BE_BRU => 0,
|
||||
LINEAGE_EURO_BE_HAS => 0,
|
||||
);
|
||||
$this->assertHierarchy($hierarchy, $reference);
|
||||
|
||||
// Asia
|
||||
list($hierarchy, $dropbox) = $this->generate($form_item, array(LINEAGE_ASIA), array());
|
||||
$reference->lineage = array(
|
||||
0 => LINEAGE_ASIA,
|
||||
1 => 'label_1',
|
||||
);
|
||||
$reference->levels[1] = array(
|
||||
'label_1' => '',
|
||||
LINEAGE_ASIA_CH => LABEL_ASIA_CH,
|
||||
LINEAGE_ASIA_JP => LABEL_ASIA_JP,
|
||||
);
|
||||
unset($reference->levels[2]);
|
||||
$reference->childinfo[1] = array(
|
||||
LINEAGE_ASIA_CH => 0,
|
||||
LINEAGE_ASIA_JP => 1,
|
||||
);
|
||||
unset($reference->childinfo[2]);
|
||||
$this->assertHierarchy($hierarchy, $reference);
|
||||
|
||||
// Asia > Japan > Tokyo
|
||||
list($hierarchy, $dropbox) = $this->generate($form_item, array(LINEAGE_ASIA_JP_TOK), array());
|
||||
$reference->lineage = array(
|
||||
0 => LINEAGE_ASIA,
|
||||
1 => LINEAGE_ASIA_JP,
|
||||
2 => LINEAGE_ASIA_JP_TOK,
|
||||
);
|
||||
$reference->levels[2] = array(
|
||||
'label_2' => '',
|
||||
LINEAGE_ASIA_JP_TOK => LABEL_ASIA_JP_TOK,
|
||||
);
|
||||
$reference->childinfo[2] = array(
|
||||
LINEAGE_ASIA_JP_TOK => 0,
|
||||
);
|
||||
$this->assertHierarchy($hierarchy, $reference);
|
||||
}
|
||||
|
||||
// In this test, only enforce_deepest enabled.
|
||||
public function testEnforceDeepest() {
|
||||
// Generate form item.
|
||||
$form_item = array(
|
||||
'#required' => FALSE,
|
||||
'#config' => array(
|
||||
'module' => 'hs_smallhierarchy',
|
||||
'params' => array(
|
||||
'hierarchy' => $this->small_hierarchy,
|
||||
'id' => 'driverpack_platforms',
|
||||
'separator' => '|',
|
||||
),
|
||||
'save_lineage' => 0,
|
||||
'enforce_deepest' => 1,
|
||||
'resizable' => 1,
|
||||
'level_labels' => array(
|
||||
'status' => 0,
|
||||
),
|
||||
'dropbox' => array(
|
||||
'status' => 0,
|
||||
'limit' => 0,
|
||||
'reset_hs' => 1,
|
||||
),
|
||||
'editability' => array(
|
||||
'status' => 0,
|
||||
'item_types' => array(),
|
||||
'allowed_levels' => array(),
|
||||
'allow_new_levels' => 0,
|
||||
'max_levels' => 3,
|
||||
),
|
||||
'entity_count' => array(
|
||||
'enabled' => 0,
|
||||
'require_entity' => 0,
|
||||
'settings' => array(
|
||||
'count_children' => 0,
|
||||
'entity_types' => array(),
|
||||
),
|
||||
),
|
||||
'animation_delay' => 400,
|
||||
'exclusive_lineages' => array(),
|
||||
'render_flat_select' => 0,
|
||||
),
|
||||
);
|
||||
|
||||
// No selection
|
||||
list($hierarchy, $dropbox) = $this->generate($form_item, array(), array());
|
||||
$reference = new stdClass();
|
||||
$reference->lineage = array(
|
||||
0 => 'label_0',
|
||||
);
|
||||
$reference->levels = array(
|
||||
0 => array(
|
||||
'none' => '<none>',
|
||||
LINEAGE_EURO => LABEL_EURO,
|
||||
LINEAGE_ASIA => LABEL_ASIA,
|
||||
),
|
||||
);
|
||||
$reference->childinfo = array(
|
||||
0 => array(
|
||||
LINEAGE_EURO => 2,
|
||||
LINEAGE_ASIA => 2,
|
||||
),
|
||||
);
|
||||
$this->assertHierarchy($hierarchy, $reference);
|
||||
|
||||
// Europe
|
||||
list($hierarchy, $dropbox) = $this->generate($form_item, array(LINEAGE_EURO), array());
|
||||
$reference->lineage = array(
|
||||
0 => LINEAGE_EURO,
|
||||
1 => LINEAGE_EURO_BE,
|
||||
2 => LINEAGE_EURO_BE_BRU,
|
||||
);
|
||||
$reference->levels[1] = array(
|
||||
LINEAGE_EURO_BE => LABEL_EURO_BE,
|
||||
LINEAGE_EURO_FR => LABEL_EURO_FR,
|
||||
);
|
||||
$reference->levels[2] = array(
|
||||
LINEAGE_EURO_BE_BRU => LABEL_EURO_BE_BRU,
|
||||
LINEAGE_EURO_BE_HAS => LABEL_EURO_BE_HAS,
|
||||
);
|
||||
$reference->childinfo[1] = array(
|
||||
LINEAGE_EURO_BE => 2,
|
||||
LINEAGE_EURO_FR => 0,
|
||||
);
|
||||
$reference->childinfo[2] = array(
|
||||
LINEAGE_EURO_BE_BRU => 0,
|
||||
LINEAGE_EURO_BE_HAS => 0,
|
||||
);
|
||||
$this->assertHierarchy($hierarchy, $reference);
|
||||
|
||||
// Europe > France
|
||||
list($hierarchy, $dropbox) = $this->generate($form_item, array(LINEAGE_EURO_FR), array());
|
||||
$reference->lineage = array(
|
||||
0 => LINEAGE_EURO,
|
||||
1 => LINEAGE_EURO_FR,
|
||||
);
|
||||
unset($reference->levels[2]);
|
||||
unset($reference->childinfo[2]);
|
||||
$this->assertHierarchy($hierarchy, $reference);
|
||||
|
||||
// Europe > Belgium
|
||||
list($hierarchy, $dropbox) = $this->generate($form_item, array(LINEAGE_EURO_BE), array());
|
||||
$reference->lineage = array(
|
||||
0 => LINEAGE_EURO,
|
||||
1 => LINEAGE_EURO_BE,
|
||||
2 => LINEAGE_EURO_BE_BRU,
|
||||
);
|
||||
$reference->levels[1] = array(
|
||||
LINEAGE_EURO_BE => LABEL_EURO_BE,
|
||||
LINEAGE_EURO_FR => LABEL_EURO_FR,
|
||||
);
|
||||
$reference->levels[2] = array(
|
||||
LINEAGE_EURO_BE_BRU => LABEL_EURO_BE_BRU,
|
||||
LINEAGE_EURO_BE_HAS => LABEL_EURO_BE_HAS,
|
||||
);
|
||||
$reference->childinfo[1] = array(
|
||||
LINEAGE_EURO_BE => 2,
|
||||
LINEAGE_EURO_FR => 0,
|
||||
);
|
||||
$reference->childinfo[2] = array(
|
||||
LINEAGE_EURO_BE_BRU => 0,
|
||||
LINEAGE_EURO_BE_HAS => 0,
|
||||
);
|
||||
$this->assertHierarchy($hierarchy, $reference);
|
||||
|
||||
// Asia
|
||||
list($hierarchy, $dropbox) = $this->generate($form_item, array(LINEAGE_ASIA), array());
|
||||
$reference->lineage = array(
|
||||
0 => LINEAGE_ASIA,
|
||||
1 => LINEAGE_ASIA_CH,
|
||||
);
|
||||
$reference->levels[1] = array(
|
||||
LINEAGE_ASIA_CH => LABEL_ASIA_CH,
|
||||
LINEAGE_ASIA_JP => LABEL_ASIA_JP,
|
||||
);
|
||||
unset($reference->levels[2]);
|
||||
$reference->childinfo[1] = array(
|
||||
LINEAGE_ASIA_CH => 0,
|
||||
LINEAGE_ASIA_JP => 1,
|
||||
);
|
||||
unset($reference->childinfo[2]);
|
||||
$this->assertHierarchy($hierarchy, $reference);
|
||||
|
||||
// Asia > Japan > Tokyo
|
||||
list($hierarchy, $dropbox) = $this->generate($form_item, array(LINEAGE_ASIA_JP_TOK), array());
|
||||
$reference->lineage = array(
|
||||
0 => LINEAGE_ASIA,
|
||||
1 => LINEAGE_ASIA_JP,
|
||||
2 => LINEAGE_ASIA_JP_TOK,
|
||||
);
|
||||
$reference->levels[2] = array(
|
||||
LINEAGE_ASIA_JP_TOK => LABEL_ASIA_JP_TOK,
|
||||
);
|
||||
$reference->childinfo[2] = array(
|
||||
LINEAGE_ASIA_JP_TOK => 0,
|
||||
);
|
||||
$this->assertHierarchy($hierarchy, $reference);
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private methods.
|
||||
|
||||
private function generate($element, $hs_selection, $db_selection, $op = 'Update') {
|
||||
$config = $element['#config'];
|
||||
|
||||
// Generate the $hierarchy and $dropbox objects using the selections that
|
||||
// were just calculated.
|
||||
$dropbox = (!$config['dropbox']['status']) ? FALSE : _hierarchical_select_dropbox_generate($config, $db_selection);
|
||||
$hierarchy = _hierarchical_select_hierarchy_generate($config, $hs_selection, $element['#required'], $dropbox);
|
||||
|
||||
return array($hierarchy, $dropbox);
|
||||
}
|
||||
|
||||
private function assertHierarchy($hierarchy, $reference) {
|
||||
$this->assertIdentical($hierarchy->lineage, $reference->lineage, 'Hierarchy lineage is correct.');
|
||||
$this->assertIdentical($hierarchy->levels, $reference->levels, 'Hierarchy levels is correct.');
|
||||
$this->assertIdentical($hierarchy->childinfo, $reference->childinfo, 'Hierarchy child info is correct.');
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user