$form_type,
'formId' => $form_id,
'elementId' => $element_id,
'html' => form_builder_field_render($form_type, $form_id, $element_id),
'positionForm' => drupal_get_form('form_builder_positions', $preview_form, $form_type, $form_id),
'messages' => theme('status_messages'),
);
drupal_json($data);
exit();
}
}
// Otherwise return to the previous page.
drupal_goto();
}
/**
* Menu callback for configuring a field.
*/
function form_builder_configure_page($form_type, $form_id, $element_id) {
$output = drupal_get_form('form_builder_field_configure', $form_type, $form_id, $element_id);
if (isset($_REQUEST['js'])) {
// Return the newly changed field.
if (isset($_REQUEST['return'])) {
form_builder_field_json($form_type, $form_id, $element_id);
}
// Display the configuration form for a field.
else {
$data = array(
'formType' => $form_type,
'formId' => $form_id,
'elementId' => $element_id,
'html' => $output,
'errors' => form_get_errors(),
'messages' => theme('status_messages'),
);
drupal_json($data);
exit();
}
}
return $output;
}
/**
* Menu callback for removing a field.
*/
function form_builder_remove_page($form_type, $form_id, $element_id) {
$output = drupal_get_form('form_builder_field_remove', $form_type, $form_id, $element_id);
if (isset($_REQUEST['js']) && !isset($_REQUEST['return'])) {
// This after build function immediately returns the form as JSON.
$data = array(
'formType' => $form_type,
'formId' => $form_id,
'elementId' => $element_id,
'html' => $output,
'messages' => theme('status_messages'),
);
drupal_json($data);
exit();
}
return $output;
}
/**
* Form. Given a form array, present it for editing in a preview.
*/
function form_builder_preview(&$form_state, $form, $form_type, $form_id) {
// Make modifications to all form elements recursively.
$element_ids = form_builder_preview_prepare($form, $form_type, $form_id);
// Record all the element IDs within the entire form.
$form['#form_builder']['element_ids'] = $element_ids;
$form['#form_builder']['form_type'] = $form_type;
$form['#form_builder']['form_id'] = $form_id;
// Add a pre_render to the entire form itself.
$form['#pre_render'][] = 'form_builder_pre_render_form';
$form['#theme'] = 'form_builder_preview';
return $form;
}
/**
* Form containing all the current weights and parents of elements.
*/
function form_builder_positions(&$form_state, $preview_form, $form_type, $form_id) {
$form = array(
'#tree' => TRUE,
'#form_builder' => array(
'form_type' => $form_type,
'form_id' => $form_id,
'form' => $preview_form,
),
);
// Because this form is updated via AJAX on the calling page, we need to
// manually set the action to be the same as the original page. We assume
// that the destination and the calling page are the same.
if (isset($_GET['destination'])) {
$form['#action'] = url($_GET['destination']);
}
form_builder_positions_prepare($form, $preview_form);
// Drupal MUST have a button to register submissions.
// Add a button even though the form is only submitted via AJAX.
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Update'),
);
return $form;
}
/**
* Recursive helper for form_builder_positions(). Add weight fields.
*/
function form_builder_positions_prepare(&$form, &$preview_form, $parent_id = FORM_BUILDER_ROOT) {
foreach (element_children($preview_form) as $key) {
// Keep record of the current parent ID.
$previous_parent_id = $parent_id;
if (isset($preview_form[$key]['#form_builder']['element_id'])) {
// Set the parent ID for this element.
$preview_form[$key]['#form_builder']['parent_id'] = $parent_id;
$element_id = $preview_form[$key]['#form_builder']['element_id'];
$parent_id = $element_id;
$form[$element_id]['weight'] = array(
'#type' => 'hidden',
'#default_value' => isset($preview_form[$key]['#weight']) ? $preview_form[$key]['#weight'] : 0,
'#attributes' => array('class' => 'form-builder-weight form-builder-element-' . $element_id),
);
$form[$element_id]['parent'] = array(
'#type' => 'hidden',
'#default_value' => $preview_form[$key]['#form_builder']['parent_id'],
'#attributes' => array('class' => 'form-builder-parent form-builder-element-' . $element_id),
);
}
form_builder_positions_prepare($form, $preview_form[$key], $parent_id);
$parent_id = $previous_parent_id;
}
}
/**
* Submit handler for the form_builder_positions form.
*/
function form_builder_positions_submit(&$form, &$form_state) {
module_load_include('inc', 'form_builder', 'includes/form_builder.api');
module_load_include('inc', 'form_builder', 'includes/form_builder.cache');
$form_type = $form['#form_builder']['form_type'];
$form_id = $form['#form_builder']['form_id'];
$preview_form = $form['#form_builder']['form'];
foreach (element_children($form) as $element_id) {
// Skip items without weight value (like the form token, build_id, etc).
if (!isset($form[$element_id]['weight'])) {
continue;
}
// Check for changed weights or parents.
$element = form_builder_get_element($preview_form, $element_id);
$element['#weight'] = $form_state['values'][$element_id]['weight'];
$element['#form_builder']['parent_id'] = $form_state['values'][$element_id]['parent'];
form_builder_set_element($preview_form, $element);
}
// Save all the changes made.
form_builder_cache_save($form_type, $form_id, $preview_form);
}
function theme_form_builder_preview($form) {
global $theme;
jquery_ui_add(array('ui.draggable', 'ui.droppable', 'ui.sortable'));
drupal_add_js('misc/jquery.form.js');
drupal_add_js(drupal_get_path('module', 'form_builder') .'/form_builder.js');
// TODO: This JS file should be loaded dynamically as needed.
drupal_add_js(drupal_get_path('module', 'options_element') .'/options_element.js');
drupal_add_js('misc/tabledrag.js');
drupal_add_js('misc/collapse.js');
drupal_add_js(array('formBuilder' => array('emptyFieldset' => theme('form_builder_empty_fieldset'))), 'setting');
drupal_add_css(drupal_get_path('module', 'form_builder') .'/form_builder.css');
drupal_add_css(drupal_get_path('module', 'options_element') .'/options_element.css');
// Check if the Form Builder block is enabled.
// Otherwise make our own columns.
$block_enabled = db_result(db_query("SELECT status FROM {blocks} WHERE module = 'form_builder' AND theme = '%s'", $theme));
if ($block_enabled && ($theme == 'garland' || $theme == 'minnelli')) {
drupal_add_css(drupal_get_path('module', 'form_builder') .'/form_builder.garland.css', 'theme');
}
$output = '';
$output .= '
';
return $output;
}
function theme_form_builder_element_prefix($element) {
$removable = isset($element['#form_builder']['removable']) ? $element['#form_builder']['removable'] : TRUE;
$configurable = isset($element['#form_builder']['configurable']) ? $element['#form_builder']['configurable'] : TRUE;
$output = '';
$output .= '';
}
/**
* Placeholder for empty fieldsets during drag and drop.
*/
function theme_form_builder_empty_fieldset() {
$output = '';
$output .= '';
$output .= '' . t('This fieldset is empty. Drag a form element into it.') .'';
$output .= '
';
return $output;
}
/**
* Block for adding new fields.
*
* @param $fields
* A list of all fields can be added to the current form type.
* @param $groups
* A list of groups that fields may be sorted into. Each field is assigned
* a 'palette_group' property which corresponds to one of these groups.
* @param $form_type
* The form type to which these blocks apply.
* @param $form_id
* The form ID that is being edited.
*/
function theme_form_builder_field_palette($fields, $groups, $form_type, $form_id) {
$output = '';
$lists = array();
foreach ($fields as $field_name => $field) {
$class = array('field-' . $field_name);
$style = '';
// If a field is unique, add a special class to identify it.
if ($field['unique']) {
$class[] = 'form-builder-unique';
$class[] = 'form-builder-element-' . $field_name;
// If already in use, do not display it in the list.
if (!empty($field['in_use'])) {
$style = 'display: none;';
}
}
$lists[$field['palette_group']]['#weight'] = $groups[$field['palette_group']]['weight'];
$lists[$field['palette_group']][] = array(
'data' => l($field['title'], 'admin/build/form-builder/add/' . $form_type . '/' . $form_id . '/' . $field_name, array('query' => drupal_get_destination())),
'class' => implode(' ', $class),
'style' => $style,
);
}
// Sort the lists by weight.
uasort($lists, 'element_sort');
$output .= '';
foreach ($lists as $group => $list) {
unset($list['#weight']);
$output .= theme('item_list', $list, (count($lists) > 1) ? $groups[$group]['title'] : t('Add a field'), 'ul', array('class' => 'form-builder-fields clear-block'));
}
$output .= '
';
return $output;
}
/**
* Take a form structure and add a prebuild function to every element.
*/
function form_builder_pre_render($element) {
// Allow modules to make modifications to this element.
drupal_alter('form_builder_preview', $element, $element['#form_builder']['form_type'], $element['#form_builder']['form_id']);
$element['#prefix'] = isset($element['#prefix']) ? theme('form_builder_element_prefix', $element) . $element['#prefix'] : theme('form_builder_element_prefix', $element);
$element['#suffix'] = isset($element['#suffix']) ? $element['#suffix'] . theme('form_builder_element_suffix', $element) : theme('form_builder_element_suffix', $element);
if ($element['#form_builder']['element_type'] == 'fieldset') {
$element['#attributes']['class'] = isset($element['#attributes']['class']) ? $element['#attributes']['class'] . ' form-builder-fieldset' : 'form-builder-fieldset';
}
if (isset($element['#type']) && $element['#type'] == 'fieldset' && count(element_children($element)) == 0) {
$element['#children'] = theme('form_builder_empty_fieldset');
}
return $element;
}
/**
* Change the type of the entire form to "markup" before rendering.
*/
function form_builder_pre_render_form($form) {
// We can't have forms inside of forms, so change this entire form a markup.
$form['#type'] = 'markup';
// Remove unnecessary FAPI elements.
unset($form['form_build_id']);
unset($form['form_token']);
unset($form['form_builder_preview']);
return $form;
}
function form_builder_after_build($element) {
$element['#attributes']['readonly'] = 'readonly';
foreach (element_children($element) as $key) {
$element[$key] = form_builder_after_build($element[$key]);
}
return $element;
}
/**
* Before editing a form, modify it slightly to add functionality used in
* the preview and disable use of the actual form fields in any way.
*
* @return
* A list of all element_ids currently used within this form.
*/
function form_builder_preview_prepare(&$form, $form_type, $form_id, $parent_id = FORM_BUILDER_ROOT) {
$element_ids = array();
foreach (element_children($form) as $key) {
// Keep record of the current parent ID.
$previous_parent_id = $parent_id;
if (isset($form[$key]['#form_builder']['element_id'])) {
$element_ids[] = $form[$key]['#form_builder']['element_id'];
$form[$key]['#pre_render'][] = 'form_builder_pre_render';
$form[$key]['#form_builder']['form_type'] = $form_type;
$form[$key]['#form_builder']['form_id'] = $form_id;
$form[$key]['#form_builder']['parent_id'] = $parent_id;
$parent_id = $form[$key]['#form_builder']['element_id'];
}
// Search within this element for further form elements.
$additional_ids = form_builder_preview_prepare($form[$key], $form_type, $form_id, $parent_id);
$element_ids = array_merge($element_ids, $additional_ids);
$parent_id = $previous_parent_id;
}
return $element_ids;
}
/**
* Form for editing a field.
*/
function form_builder_field_configure($form_state, $form_type, $form_id, $element_id) {
module_load_include('inc', 'form_builder', 'includes/form_builder.api');
module_load_include('inc', 'form_builder', 'includes/form_builder.cache');
$element = form_builder_cache_field_load($form_type, $form_id, $element_id);
// Assemble a form made up of all the configurable properties that this type
// of form supports.
$form = array();
foreach (form_builder_get_element_properties($form_type, $element['#form_builder']['element_type']) as $property => $property_settings) {
if (isset($property_settings['form']) && function_exists($property_settings['form'])) {
$function = $property_settings['form'];
// Set a default value on the property to avoid notices.
$element['#' . $property] = isset($element['#' . $property]) ? $element['#' . $property] : NULL;
$form = array_merge($form, $function($form_state, $form_type, $element));
}
}
$form['#form_type'] = $form_type;
$form['#form_id'] = $form_id;
$form['#element_id'] = $element_id;
$form['#element'] = $element;
$form['form_builder_submit'] = array(
'#type' => 'submit',
'#value' => t('Save configuration'),
'#access' => !isset($_REQUEST['js']),
'#weight' => 100,
);
return $form;
}
/**
* Theme function for the display of field configuration.
*/
function theme_form_builder_field_configure($form) {
// Group the properties into separate fieldsets (converted to tabs later).
$groups = module_invoke_all('form_builder_property_groups', $form['#form_type']);
foreach (element_children($form) as $key) {
// If no group is specified, put the element into the default group.
if (!isset($form[$key]['#form_builder']['property_group']) || !isset($groups[$form[$key]['#form_builder']['property_group']])) {
if (!isset($form[$key]['#type']) || (isset($form[$key]['#type']) && !in_array($form[$key]['#type'], array('hidden', 'button', 'submit', 'value', 'token')))) {
$form[$key]['#form_builder']['property_group'] = 'default';
}
}
if (isset($form[$key]['#form_builder']['property_group'])) {
$group = $form[$key]['#form_builder']['property_group'];
// We add "_property_group" to the field key to prevent conflicts of
// property names and group names.
if (!isset($form[$group . '_property_group'])) {
$form[$group . '_property_group'] = array(
'#type' => 'fieldset',
'#title' => $groups[$group]['title'],
'#weight' => $groups[$group]['weight'],
'#collapsible' => isset($groups[$group]['collapsible']) ? $groups[$group]['collapsible'] : FALSE,
'#collapsed' => isset($groups[$group]['collapsed']) ? $groups[$group]['collapsed'] : FALSE,
'#attributes' => array('class' => 'form-builder-group'),
);
}
$form[$group .'_property_group'][$key] = $form[$key];
unset($form[$key]);
}
}
return drupal_render($form);
}
function form_builder_field_configure_submit(&$form, &$form_state) {
$form_type = $form['#form_type'];
$form_id = $form['#form_id'];
$element_id = $form['#element_id'];
$element = $form['#element'];
// Allow each element to do any necessary submission handling.
foreach (form_builder_get_element_properties($form_type, $element['#form_builder']['element_type']) as $property => $property_settings) {
if (isset($property_settings['submit'])) {
foreach ($property_settings['submit'] as $function) {
if (function_exists($function)) {
$function($form, $form_state);
}
}
}
}
// Allow the element to be updated in a hard-coded fashion by altering the
// $form['#element'] item. Using this approach skips the property check.
$element = $form['#element'];
// Update the field according to the settings in $form_state['values'].
$saveable = form_builder_get_saveable_properties($form_type, $element);
foreach ($form_state['values'] as $property => $value) {
if (in_array($property, $saveable)) {
// Remove empty properties entirely.
if ($value == '') {
unset($element['#'. $property]);
}
else {
$element['#'. $property] = $value;
}
}
}
// Update the form builder cache.
form_builder_cache_field_save($form_type, $form_id, $element);
if (isset($_GET['js'])) {
// Option A: Use the destination variable to do a drupal_goto(). Allows
// other submit handlers to add on extra functionality.
// The destination variable takes precedence over $form_state['redirect'].
//$_REQUEST['destination'] = 'admin/build/form-builder/json/' . $form_type . '/' . $form_id . '/' . $element_id;
// Option B: Immediately print the JSON and exit. Faster and requires only
// one HTTP request instead of two. Other submit handlers must be before
// this on.
form_builder_field_json($form_type, $form_id, $element_id);
}
}
/**
* Form for removing a field.
*/
function form_builder_field_remove($form_state, $form_type, $form_id, $element_id) {
module_load_include('inc', 'form_builder', 'includes/form_builder.api');
module_load_include('inc', 'form_builder', 'includes/form_builder.cache');
$element = form_builder_cache_field_load($form_type, $form_id, $element_id);
$form = array();
$form['#form_type'] = $form_type;
$form['#form_id'] = $form_id;
$form['#element_id'] = $element_id;
$quesion = t('Remove the field %title?', array('%title' => $element['#title']));
$path = isset($_GET['destination']) ? $_GET['destination'] : NULL;
$description = t('Remove the field %title? This field will not be permanently removed until the form configuration is saved.', array('%title' => isset($element['#title']) ? $element['#title'] : $element['#form_builder']['element_id']));
$yes = t('Remove');
return confirm_form($form, $quesion, $path, $description, $yes);
}
function form_builder_field_remove_submit(&$form, &$form_state) {
$form_type = $form['#form_type'];
$form_id = $form['#form_id'];
$element_id = $form['#element_id'];
// Update the form builder cache.
form_builder_cache_field_delete($form_type, $form_id, $element_id);
if (isset($_GET['js'])) {
// See form_builder_field_configure_submit() for comparison between using
// redirect and immediately printing the JSON.
//$form_state['redirect'] = 'admin/build/form-builder/json/' . $form_type . '/' . $form_id . '/' . $element_id;
form_builder_field_json($form_type, $form_id, $element_id);
}
}
/**
* Render a single field independent of other settings.
*/
function form_builder_field_render($form_type, $form_id, $element_id, $wrapper = FALSE) {
module_load_include('inc', 'form_builder', 'includes/form_builder.api');
module_load_include('inc', 'form_builder', 'includes/form_builder.cache');
// Load the current state of the form and prepare it for rendering.
$form = form_builder_cache_load($form_type, $form_id);
$form_state = array('submitted' => FALSE);
$form = drupal_retrieve_form('form_builder_preview', $form_state, $form, $form_type, $form_id);
drupal_prepare_form('form_builder_preview', $form, $form_state);
$form['#post'] = array();
$form = form_builder('form_builder_preview', $form, $form_state);
// Get only the element wanted and render it.
$element = form_builder_get_element($form, $element_id);
$content = drupal_render($element);
$prefix = isset($element['#prefix']) ? $element['#prefix'] : '';
$suffix = isset($element['#suffix']) ? $element['#suffix'] : '';
if ($wrapper) {
$prefix .= theme('form_builder_element_prefix', $element);
$suffix .= theme('form_builder_element_suffix', $element);
}
return $suffix . $content . $suffix;
}
/**
* Menu callback to display a field as a JSON string.
*/
function form_builder_field_json($form_type, $form_id, $element_id) {
module_load_include('inc', 'form_builder', 'includes/form_builder.api');
module_load_include('inc', 'form_builder', 'includes/form_builder.cache');
$element = form_builder_cache_field_load($form_type, $form_id, $element_id);
$data = array(
'formType' => $form_type,
'formId' => $form_id,
'elementId' => $element_id,
'html' => form_builder_field_render($form_type, $form_id, $element_id),
'errors' => form_get_errors(),
'messages' => theme('status_messages'),
'time' => time(),
);
drupal_json($data);
exit();
}