Posted by tomsm on April 28, 2009 at 1:02pm
I have a content type called product.
This product has a multivalue node reference field called: standard accessories.
These accessories are also of the content type products.
How can I add the number of each accessory, for example:
2 x accessory 1
5 x accessory 2
1 x accessory 3
Accessory 1, 2 and 3 are node references and 2, 5 and 1 is the number (a numeric CCK field?) of each accessory.
For each node reference, a number must be entered, 1 or higher.
How can I do this?
Comments
Are you using Ubercart? if
Are you using Ubercart? if so, there are "attribute" filters which might do what you want.
If you're dealing with numeric CCK fields, then http://drupal.org/project/views_calc might help.
No, I am not using Ubercart
No, I am not using Ubercart because it does not support prices per role.
I made a custom content type "products".
A node reference is not a numeric field, so views_calc won't work and I do not need to make a calculation.
I can create a multivalue node reference field and a multivalue numeric field.
But how can I link these field so that value 1 of field 1 is linked to value 1 of field 2, etc.?
I also would like to do
I also would like to do something like this. Anyone know how?
Mine is not a product, so I also don't have Ubercart.
Thanks,
TJ
Bump. Having the same issue
Bump. Having the same issue w/ CCK. Want to specify the quantity of a referenced node.
Seems there should be a generic way to add fields to the reference between the two nodes but I can't see to find it.
Of the top of my head I'd
Of the top of my head I'd recommend this:
Limit # of noderefs & integer fields to 10 or some number. (If that's acceptable in your case)
Theme the node input form so that the display of the fields alternates:
int field 1 noderef 1
int field 2 noderef 2
Etc.
With a bit of jquery magic you could even have the list grow visually as required, not showing more than one empty field at a time.
I'm trying to do exactly the
I'm trying to do exactly the same thing.
Is anyone found a solution ?
http://www.kiliweb.fr/
Who can do this? please help
Who can do this? please help :) or suggest other solutions...
one more time!
You can do that with the dev
You can do that with the dev version of CCK 3, by using multigroup.
http://www.kiliweb.fr/
Creating your own custom CCK field by creating a module.
Here are the contents of the .module file from a quick module I put together (D6.22 installation using View 6.2x and CCK 6.2x). I used the Node Reference module and an article by Jennifer Hodgdon as a great starting point:
<?php
// $Id:$
/**
* @file
* This is a Drupal Module - Node Reference and Quantity Field
* Implements a Custom Compound CCK Field
*
* Copyright 2011 Dion DiFelice, GreenWide.com
*
* Licensed under the GNU Public License
*/
/**
* Implementation of CCK's hook_field_info().
*
* Returns basic information about this Custom CCK Field.
*/
function noderef_qty_field_info() {
return array(
'noderef_qty' => array(
'label' => t('Node Reference and Quantity'),
'description' => t('Stores a Node Reference and Quantity'),
)
);
}
/**
* Implementation of hook_install().
*
* Use a CCK Function to install this module.
*/
function noderef_qty_install() {
content_notify('install', 'noderef_qty');
}
/**
* Implementation of hook_uninstall().
*
* Use a CCK Function to uninstall this module.
*/
function noderef_qty_uninstall() {
content_notify('uninstall', 'noderef_qty');
}
/**
* Implementation of hook_enable().
*
* Use a CCK Function to enable this module.
*/
function noderef_qty_enable() {
content_notify('enable', 'noderef_qty');
}
/**
* Implementation of hook_disable().
*
* Use a CCK Function to disable this module.
*/
function noderef_qty_disable() {
content_notify('disable', 'noderef_qty');
}
/**
* Implementation of CCK hook_field_settings().
*
* Configure the available properties to this Custom CCK Field
*/
function noderef_qty_field_settings($op, $field) {
switch ($op) {
case 'form':
$form = array();
$form['referenceable_types'] = array(
'#type' => 'checkboxes',
'#title' => t('Content types that can be referenced'),
'#multiple' => TRUE,
'#default_value' => is_array($field['referenceable_types']) ? $field['referenceable_types'] : array(),
'#options' => array_map('check_plain', node_get_types('names')),
);
if (module_exists('views')) {
$views = array('--' => '--');
$all_views = views_get_all_views();
foreach ($all_views as $view) {
// Only 'node' views that have fields will work for our purpose.
if ($view->base_table == 'node' && !empty($view->display['default']->display_options['fields'])) {
if ($view->type == 'Default') {
$views[t('Default Views')][$view->name] = $view->name;
}
else {
$views[t('Existing Views')][$view->name] = $view->name;
}
}
}
$form['advanced'] = array(
'#type' => 'fieldset',
'#title' => t('Advanced - Nodes that can be referenced (View)'),
'#collapsible' => TRUE,
'#collapsed' => !isset($field['advanced_view']) || $field['advanced_view'] == '--',
);
if (count($views) > 1) {
$form['advanced']['advanced_view'] = array(
'#type' => 'select',
'#title' => t('View used to select the nodes'),
'#options' => $views,
'#default_value' => isset($field['advanced_view']) ? $field['advanced_view'] : '--',
'#description' => t('<p>Choose the "Views module" view that selects the nodes that can be referenced.<br />Note:</p>') .
t('<ul><li>Only views that have fields will work for this purpose.</li><li>This will discard the "Content types" settings above. Use the view\'s "filters" section instead.</li><li>Use the view\'s "fields" section to display additional informations about candidate nodes on node creation/edition form.</li><li>Use the view\'s "sort criteria" section to determine the order in which candidate nodes will be displayed.</li></ul>'),
);
$form['advanced']['advanced_view_args'] = array(
'#type' => 'textfield',
'#title' => t('View arguments'),
'#default_value' => isset($field['advanced_view_args']) ? $field['advanced_view_args'] : '',
'#required' => FALSE,
'#description' => t('Provide a comma separated list of arguments to pass to the view.'),
);
}
else {
$form['advanced']['no_view_help'] = array(
'#value' => t('<p>The list of nodes that can be referenced can be based on a "Views module" view but no appropriate views were found. <br />Note:</p>') .
t('<ul><li>Only views that have fields will work for this purpose.</li><li>This will discard the "Content types" settings above. Use the view\'s "filters" section instead.</li><li>Use the view\'s "fields" section to display additional informations about candidate nodes on node creation/edition form.</li><li>Use the view\'s "sort criteria" section to determine the order in which candidate nodes will be displayed.</li></ul>'),
);
}
}
return $form;
case 'save':
$settings = array();
$settings[] = 'referenceable_types';
if (module_exists('views')) {
$settings[] = 'advanced_view';
$settings[] = 'advanced_view_args';
}
return $settings;
case 'database columns':
$columns['nid'] = array('type' => 'int', 'unsigned' => TRUE, 'not null' => FALSE, 'index' => TRUE);
$columns['quantity'] = array('type' => 'varchar', 'length' => 255, 'not null' => FALSE, 'sortable' => FALSE, 'default' => '');
return $columns;
}
}
/**
* Implementation of CCK hook_field().
*
* Validate and sanitize functions available
*/
function noderef_qty_field($op, &$node, $field, &$items, $teaser, $page) {
switch ($op) {
case 'validate':
if (is_array($items)) {
foreach ($items as $delta => $item) {
if ($item['quantity'] != '') {
if (is_numeric(trim($item['quantity']))) {
if ($item['quantity'] < 1 || $item['quantity'] > 1000000000) {
form_set_error($field['field_name'],t('"%qty" is not between 1 and 1 000 000 000.',array('%qty' => $item['quantity'])));
}
} else {
form_set_error($field['field_name'],t('"%qty" is not a valid number.',array('%qty' => $item['quantity'])));
}
}
}
}
break;
case 'sanitize':
foreach ($items as $delta => $item) {
foreach ($item as $col => $dat) {
$items[$delta]['safe_' . $col ] = check_plain($item[ $col ]);
}
}
break;
}
}
/**
* Implementation of hook_content_is_empty().
*
* Ensures a Node Reference was selected
*/
function noderef_qty_content_is_empty($item, $field) {
if (empty($item['nid']) || empty($item['quantity'])) {
return TRUE;
}
return FALSE;
}
/**
* Implementation of hook_widget_info().
*/
function noderef_qty_widget_info() {
return array(
'noderef_qty_entry' => array(
'label' => t('Node Reference and Quantity'),
'field types' => array('noderef_qty'),
'multiple values' => CONTENT_HANDLE_CORE,
'callbacks' => array(
'default value' => CONTENT_CALLBACK_DEFAULT,
),
'description' => t('An edit widget for Node Items and Quantities.' ),
),
);
}
/**
* Implementation of Form API's hook_elements().
*
* Returns a skeleton Form API array that defines callbacks
* for the widget form.
*/
function noderef_qty_elements() {
$elements = array('noderef_qty_entry' =>
array(
'#input' => TRUE,
'#process' => array('noderef_qty_entry_process'),
),
);
return $elements;
}
/**
* Process callback for widget
*
* Returns a Forms API array that defines the widget's editing form.
*/
function noderef_qty_entry_process($element, $edit, &$form_state, $form) {
$defaults = $element['#value'];
$field = content_fields($element['#field_name'], $element['#type_name']);
$options = noderef_qty_allowed_values($field);
$options = array('' => t('- None -')) + $options;
$element['nid'] = array(
'#title' => t('Item'),
'#type' => 'select',
'#default_value' => $defaults['nid'],
'#description' => t('Please select an item from the dropdown. Choose - None - to delete this item and quantity.'),
'#options' => $options,
'#weight' => 2,
);
$element['quantity'] = array(
'#title' => t('Quantity'),
'#type' => 'textfield',
'#description' => t('Please enter a quantity between 1 and 1 000 000 000. Leave blank to delete this item and quantity.'),
'#default_value' => $defaults['quantity'],
'#weight' => 3,
);
return $element;
}
/**
* Implementation of hook_theme().
*/
function noderef_qty_theme() {
return array(
'noderef_qty_entry' => array(
'arguments' => array('element' => NULL),
),
'noderef_qty_formatter_default' => array(
'arguments' => array('element' => NULL),
),
);
}
/**
* FAPI theme for an individual text elements.
*/
function theme_noderef_qty_entry($element) {
return $element['#children'];
}
/**
* Implementation of hook_widget().
*/
function noderef_qty_widget(&$form, &$form_state, $field, $items, $delta = 0) {
$element = array(
'#type' => $field['widget']['type'],
'#default_value' => isset($items[$delta]) ? $items[$delta] : '',
);
return $element;
}
/**
* Implementation of CCK's hook_field_formatter_info().
*
* Returns information about available field formatters.
*/
function noderef_qty_field_formatter_info() {
return array(
'default' => array(
'label' => t('Node Reference and Quantitye Display'),
'field types' => array('noderef_qty'),
),
);
}
/**
* Theme function for default formatter.
*/
function theme_noderef_qty_formatter_default($element = NULL) {
if(empty($element['#item'])) {
return '';
}
$item = $element['#item'];
$fields = array('quantity','nid');
$result = !empty($item['nid']) ? '<span class="noderef_qty_item">' : '';
foreach($fields as $field) {
if(!empty($item['safe_' . $field ]) && is_numeric($item['safe_' . $field ]) ) {
switch($field) {
case 'quantity':
$result .= '<span class="noderef_qty_' . $field . '">(' . $item['safe_' . $field ] . ')</span> ';
break;
case 'nid':
$nid = $item['safe_' . $field ];
$node_title = db_result(db_query(db_rewrite_sql('SELECT n.title FROM {node} n WHERE n.nid = %d'), $nid));
$result .= '<span class="noderef_qty_' . $field . '">' . $node_title . '</span> ';
break;
}
}
}
if(!empty($item['safe_email' ])) {
$result .= $sep . '<a class="noderef_qty_email" href="mailto:' . $item['safe_email' ] .
'">' . $item['safe_email' ] . "</a>";
}
$result .= !empty($item['nid']) ? '</span>' : '';
return $result;
}
/**
* Theme preprocess function for field.tpl.php.
*
* The $variables array contains the following arguments:
* - $node
* - $field
* - $items
* - $teaser
* - $page
*
* @see field.tpl.php
*
* TODO : this should live in theme/theme.inc, but then the preprocessor
* doesn't get called when the theme overrides the template. Bug in theme layer ?
*/
function noderef_qty_preprocess_content_field(&$variables) {
$element = $variables['element'];
$field = content_fields($element['#field_name'], $element['#node']->type);
$variables['node'] = $element['#node'];
$variables['field'] = $field;
$variables['items'] = array();
if ($element['#single']) {
// Single value formatter.
foreach (element_children($element['items']) as $delta) {
$variables['items'][$delta] = $element['items'][$delta]['#item'];
// Use isset() to avoid undefined index message on #children when field values are empty.
$variables['items'][$delta]['view'] = isset($element['items'][$delta]['#children']) ? $element['items'][$delta]['#children'] : '';
}
}
else {
// Multiple values formatter.
// We display the 'all items' output as $items[0], as if it was the
// output of a single valued field.
// Raw values are still exposed for all items.
foreach (element_children($element['items']) as $delta) {
$variables['items'][$delta] = $element['items'][$delta]['#item'];
}
$variables['items'][0]['view'] = $element['items']['#children'];
}
$variables['teaser'] = $element['#teaser'];
$variables['page'] = $element['#page'];
$field_empty = TRUE;
foreach ($variables['items'] as $delta => $item) {
if (!isset($item['view']) || (empty($item['view']) && (string)$item['view'] !== '0')) {
$variables['items'][$delta]['empty'] = TRUE;
}
else {
$field_empty = FALSE;
$variables['items'][$delta]['empty'] = FALSE;
}
}
$additions = array(
'field_type' => $field['type'],
'field_name' => $field['field_name'],
'field_type_css' => strtr($field['type'], '_', '-'),
'field_name_css' => strtr($field['field_name'], '_', '-'),
'label' => check_plain(t($field['widget']['label'])),
'label_display' => $element['#label_display'],
'field_empty' => $field_empty,
'template_files' => array(
'content-field',
'content-field-noderef_qty',
'content-field-'. $element['#field_name'],
'content-field-'. $element['#node']->type,
'content-field-'. $element['#field_name'] .'-'. $element['#node']->type,
),
);
$variables = array_merge($variables, $additions);
}
/**
* Implementation of hook_theme_registry_alter()
*
* Register our custom tpl suggestion with the Drupal Theming Registry.
*/
function noderef_qty_theme_registry_alter(&$theme_registry) {
$thisPath = drupal_get_path('module', 'noderef_qty');
array_unshift($theme_registry['content_field']['theme paths'],$thisPath);
}
/**
* Collects an array of all candidate referenceable nodes.
*/
function noderef_qty_allowed_values($field) {
$references = _noderef_qty_potential_references($field);
$options = array();
foreach ($references as $key => $value) {
$options[$key] = $value['title'] . ' [nid:' . $key . ']';
}
return $options;
}
/**
* Fetch an array of all candidate referenced nodes.
*
* This info is used in various places (allowed values, autocomplete results,
* input validation...). Some of them only need the nids, others nid + titles,
* others yet nid + titles + rendered row (for display in widgets).
* The array we return contains all the potentially needed information, and lets
* consumers use the parts they actually need.
*
* @param $field
* The field description.
* @param $string
* Optional string to filter titles on (used by autocomplete).
* @param $match
* Operator to match filtered name against, can be any of:
* 'contains', 'equals', 'starts_with'
* @param $ids
* Optional node ids to lookup (the $string and $match arguments will be
* ignored).
* @param $limit
* If non-zero, limit the size of the result set.
*
* @return
* An array of valid nodes in the form:
* array(
* nid => array(
* 'title' => The node title,
* 'rendered' => The text to display in widgets (can be HTML)
* ),
* ...
* )
*/
function _noderef_qty_potential_references($field, $string = '', $match = 'contains', $ids = array(), $limit = NULL) {
static $results = array();
// Create unique id for static cache.
$cid = $field['field_name'] .':'. $match .':'. ($string !== '' ? $string : implode('-', $ids)) .':'. $limit;
if (!isset($results[$cid])) {
$references = FALSE;
if (module_exists('views') && !empty($field['advanced_view']) && $field['advanced_view'] != '--') {
$references = _noderef_qty_potential_references_views($field, $string, $match, $ids, $limit);
}
// If the view doesn't exist, we got FALSE, and fallback to the regular 'standard mode'.
if ($references === FALSE) {
$references = _noderef_qty_potential_references_standard($field, $string, $match, $ids, $limit);
}
// Store the results.
$results[$cid] = !empty($references) ? $references : array();
}
return $results[$cid];
}
/**
* Helper function for _nodereference_potential_references():
* case of Views-defined referenceable nodes.
*/
function _noderef_qty_potential_references_views($field, $string = '', $match = 'contains', $ids = array(), $limit = NULL) {
$view_name = $field['advanced_view'];
if ($view = views_get_view($view_name)) {
// We add a display, and let it derive from the 'default' display.
// TODO: We should let the user pick a display in the fields settings - sort of requires AHAH...
$display = $view->add_display('content_references');
$view->set_display($display);
// TODO from merlinofchaos on IRC : arguments using summary view can defeat the style setting.
// We might also need to check if there's an argument, and set *its* style_plugin as well.
$view->display_handler->set_option('style_plugin', 'content_php_array_autocomplete');
$view->display_handler->set_option('row_plugin', 'fields');
// Used in content_plugin_style_php_array::render(), to get
// the 'field' to be used as title.
$view->display_handler->set_option('content_title_field', 'title');
// Additional options to let content_plugin_display_references::query()
// narrow the results.
$options = array(
'table' => 'node',
'field_string' => 'title',
'string' => $string,
'match' => $match,
'field_id' => 'nid',
'ids' => $ids,
);
$view->display_handler->set_option('content_options', $options);
// TODO : for consistency, a fair amount of what's below
// should be moved to content_plugin_display_references
// Limit result set size.
$limit = isset($limit) ? $limit : 0;
$view->display_handler->set_option('items_per_page', $limit);
// Get arguments for the view.
if (!empty($field['advanced_view_args'])) {
// TODO: Support Tokens using token.module ?
$view_args = array_map('trim', explode(',', $field['advanced_view_args']));
}
else {
$view_args = array();
}
// We do need title field, so add it if not present (unlikely, but...)
$fields = $view->get_items('field', $display);
if (!isset($fields['title'])) {
$view->add_item($display, 'field', 'node', 'title');
}
// If not set, make all fields inline and define a separator.
$options = $view->display_handler->get_option('row_options');
if (empty($options['inline'])) {
$options['inline'] = drupal_map_assoc(array_keys($view->get_items('field', $display)));
}
if (empty($options['separator'])) {
$options['separator'] = '-';
}
$view->display_handler->set_option('row_options', $options);
// Make sure the query is not cached
$view->is_cacheable = FALSE;
// Get the results.
$result = $view->execute_display($display, $view_args);
}
else {
$result = FALSE;
}
return($result);
}
/**
* Helper function for _nodereference_potential_references():
* referenceable nodes defined by content types.
*/
function _noderef_qty_potential_references_standard($field, $string = '', $match = 'contains', $ids = array(), $limit = NULL) {
$related_types = array();
$where = array();
$args = array();
if (is_array($field['referenceable_types'])) {
foreach (array_filter($field['referenceable_types']) as $related_type) {
$related_types[] = "n.type = '%s'";
$args[] = $related_type;
}
}
$where[] = implode(' OR ', $related_types);
if (!count($related_types)) {
return array();
}
if ($string !== '') {
$like = $GLOBALS["db_type"] == 'pgsql' ? "ILIKE" : "LIKE";
$match_clauses = array(
'contains' => "$like '%%%s%%'",
'equals' => "= '%s'",
'starts_with' => "$like '%s%%'",
);
$where[] = 'n.title '. (isset($match_clauses[$match]) ? $match_clauses[$match] : $match_clauses['contains']);
$args[] = $string;
}
elseif ($ids) {
$where[] = 'n.nid IN (' . db_placeholders($ids) . ')';
$args = array_merge($args, $ids);
}
$where_clause = $where ? 'WHERE ('. implode(') AND (', $where) .')' : '';
$sql = db_rewrite_sql("SELECT n.nid, n.title AS node_title, n.type AS node_type FROM {node} n $where_clause ORDER BY n.title, n.type");
$result = $limit ? db_query_range($sql, $args, 0, $limit) : db_query($sql, $args);
$references = array();
while ($node = db_fetch_object($result)) {
$references[$node->nid] = array(
'title' => $node->node_title,
'rendered' => check_plain($node->node_title),
);
}
return $references;
}
Regards,
Dion DiFelice
www.greenwide.com
Email: dion.difelice (-at-) gmail.com
Twitter: diondifelice
Facebook: dion.difelice
Skype: dion416