--- availability_calendars.install Fri Jan 21 08:16:11 2011 +++ availability_calendars.install Wed Feb 09 12:26:18 2011 @@ -45,8 +45,8 @@ ), 'status' => array( 'description' => 'The status.', - 'type' => 'text', - 'size' => 'medium', + 'type' => 'varchar', + 'length' => 64, // status = class or (split day) calsplit cal-_ = 14 + 2 * length of a class ), 'date' => array( 'description' => 'Datetime representation of availability', @@ -89,6 +89,33 @@ ) ) ); + + $schema['availability_calendars_states'] = array( + 'description' => 'Store classes and labels for the possible states in availability calendars', + 'fields' => array( + 'class' => array( + 'description' => 'The class used for this state', + 'type' => 'varchar', + 'length' => 24, + 'not null' => TRUE, + ), + 'label' => array( + 'description' => 'The label as displayed to users for this state', + 'type' => 'varchar', + 'length' => 64, // should not be too long: will give display problems + 'not null' => TRUE, + ), + 'weight' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'size' => 'tiny', + 'description' => 'The weight of this state', + ), + ), + 'primary key' => array('class'), + ); + return $schema; } @@ -96,8 +123,40 @@ * Implementation of hook_install(). */ function availability_calendars_install() { + // Install schema drupal_install_schema('availability_calendars'); - drupal_set_message(t('Availability Calendars module installed successfully.'), 'warning'); + + // Fill schema: add a default (starter, example) set of states to the database + // @TODO: shorten the classes if no css refers to these classes anymore + $states = array( + array( + 'class' => 'calavailable', + 'label' => 'Available', + 'weight' => 1, + ), + array( + 'class' => 'calnotavailable', + 'label' => 'Fully booked', + 'weight' => 2, + ), + array( + 'class' => 'calnotavailableprov', + 'label' => 'Provisionally booked', + 'weight' => 3, + ), + ); + $insert_query = "INSERT INTO {availability_calendars_states} (class,label,weight) VALUES ('%s','%s',%d)"; + $success = true; + foreach ($states as $state) { + $success = db_query($insert_query, $state['class'], $state['label'], $state['weight']) !== false && $success; + } + + if (!$success) { + drupal_set_message(t('Availability Calendars module not installed successfully.'), 'error'); + } + else { + drupal_set_message(t('Availability Calendars module installed successfully.'), 'warning'); + } } /** @@ -197,7 +256,7 @@ ); return $ret; } - + /** * Implementation of hook_update_N(). * Change statuses to now use class strings instead of integers and alter defaultstatus setting if set. @@ -253,5 +312,68 @@ 'success' => TRUE, 'query' => 'Fixed variables for ' . $sandbox['max'] . ' availability calendar variable settings.', ); + return $ret; +} + +/** + * Implementation of hook_update_N(). + * Add custom states + * + * @return array + */ +function availability_calendars_update_6104(&$sandbox) { + $ret = array(); + + // Change type of field status of table availability_calendars_day, + // as it (kind of) refers to the class field of the new states table + db_change_field($ret, 'availability_calendars_day', 'status', 'status', array('type' => 'varchar', 'length' => 64)); + + // Add table to store configurable statuses + $tables = availability_calendars_schema(); //DRY: get table def from schema + $table_name = 'availability_calendars_states'; + db_create_table($ret, $table_name, $tables[$table_name]); + + // Add existing (hard-coded) states to the database + // Note: We can never shorten in an update situation, unless we update the database *contents* as well. + $states = array( + array( + 'class' => 'calavailable', + 'label' => 'Available', + 'weight' => 1, + ), + array( + 'class' => 'calnotavailable', + 'label' => 'Fully booked', + 'weight' => 2, + ), + array( + 'class' => 'calnotavailableprov', + 'label' => 'Provisionally booked', + 'weight' => 3, + ), + ); + $insert_query = "INSERT INTO {availability_calendars_states} (class,label,weight) VALUES ('%s','%s',%d)"; + $success = true; + foreach ($states as $state) { + $success = db_query($insert_query, $state['class'], $state['label'], $state['weight']) !== false && $success; + } + $ret[] = array('success' => $success, 'query' => $insert_query); + + // Update split statuses: the old way of assembling them did not work any more with configurable states and classes + $split_state_updates = array( + 'calsplit cal-available_notavailable' => 'calsplit cal-calavailable_calnotavailable', + 'calsplit cal-available_notavailableprov' => 'calsplit cal-calavailable_calnotavailableprov', + 'calsplit cal-notavailable_available' => 'calsplit cal-calnotavailable_calavailable', + 'calsplit cal-notavailable_notavailableprov' => 'calsplit cal-calnotavailable_calnotavailableprov', + 'calsplit cal-notavailableprov_available' => 'calsplit cal-calnotavailableprov_calavailable', + 'calsplit cal-notavailableprov_notavailable' => 'calsplit cal-calnotavailableprov_calnotavailable', + ); + $update_query = "UPDATE {availability_calendars_day} SET status = '%s' WHERE status = '%s'"; + $success = true; + foreach ($split_state_updates as $split_state_update_old => $split_state_update_new) { + $success = db_query($update_query, $split_state_update_new, $split_state_update_old) !== false && $success; + } + $ret[] = array('success' => $success, 'query' => $update_query); + return $ret; } --- availability_calendars.module Sun Jan 23 05:35:46 2011 +++ availability_calendars.module Wed Feb 09 12:31:33 2011 @@ -102,8 +102,12 @@ * @return array */ function availability_calendars_admin_settings() { - $form = array(); $settings = availability_calendar_getsettings(); + + $form = array(); + $form['#validate'][] = 'availability_calendars_admin_settings_validate'; + $form['#submit'][] = 'availability_calendars_admin_settings_submit'; + $form['display'] = array( '#type' => 'fieldset', '#title' => t('View settings'), @@ -125,12 +129,6 @@ '#title' => t('Allow split day statuses.'), '#default_value' => $settings->splitday ); - $form['display']['availability_calendars_settings_system_defaultstatus'] = array( - '#type' => 'select', - '#options' => availability_calendars_options(), - '#title' => t("Set the default status in which to set all newly created nodes' statuses."), - '#default_value' => $settings->defaultstatus - ); $form['global'] = array( '#type' => 'fieldset', '#title' => t('Global settings'), @@ -138,7 +136,7 @@ ); $form['global']['availability_calendars_settings_system_hideold'] = array( '#type' => 'checkbox', - '#title' => t('Set past dates to fully booked.'), + '#title' => t('Do not show availability state of dates in the past.'), '#default_value' => $settings->hideold, ); $form['global']['availability_calendars_settings_system_showteaser'] = array( @@ -146,11 +144,166 @@ '#title' => t('Show availability calendars within teasers.'), '#default_value' => $settings->showteaser, ); - //TODO: add status codes and css classes in here + + // Add states + $form['states'] = array( + '#type' => 'fieldset', + '#title' => t('States'), + '#tree' => TRUE, + '#description' => t('

You can modify the availability states here.

+'), + '#attributes' => array('class' => 'state-list'), + ); + + $element = &$form['states']; + $states = availability_calendars_get_states(); + $i = 0; + foreach ($states as $state) { + availability_calendars_admin_settings_add_state($element, $i, $state, $settings); + $i++; + } + + // Show a minimum of 4 available states with at least one empty state + do { + availability_calendars_admin_settings_add_state($element, $i, array('class' => '', 'label' => '', 'weight' => 0), $settings); + $i++; + } while ($i < 4); + return system_settings_form($form); } /** + * Helper function to add a state item to a form. + * Only the first item gets labels, as the fields will be presented below each other. + * + * @param array $element the form element to add the state item to + * @param int $i the state item count + * @param array $state array containing a state record + * @param array $settings array containing general (i.e. non per state) settings + */ +function availability_calendars_admin_settings_add_state(&$element, $i, $state, $settings) { + static $max_weight = 0; + $element[$i]['label'] = array( + '#type' => 'textfield', + '#title' => $i == 0 ? t('Label') : '', + '#default_value' => $state['label'], + '#size' => 40, + '#prefix' => '
', + ); + $element[$i]['class'] = array( + '#type' => 'textfield', + '#title' => $i == 0 ? t('Class') : '', + '#default_value' => $state['class'], + '#size' => 24, + ); + // Bit difficult to add a set of related radio button's that do not appear together + $element[$i]['defaultstatus'] = array( + '#type' => 'radio', + '#tree' => false, + // Use our own label format (as with a radio button the label appears after the radio button) + // and wrap it in our own div.form-item + '#prefix' => $i == 0 ? '
' : '', + '#suffix' => $i == 0 ? '
' : '', + '#return_value' => $i, + '#default_value' => !empty($state['class']) && $state['class'] == $settings->defaultstatus ? $i : 0, + // use the name attribute to group the radio buttons scattered over the items/rows + '#attributes' => array('name' => 'defaultstatus'), + ); + $element[$i]['weight'] = array( + '#type' => 'select', + '#title' => $i == 0 ? t('Weight') : '', + '#default_value' => $state['weight'] > 0 ? $state['weight'] : ++$max_weight, + '#options' => array_combine(range(1, 20, 1), range(1, 20, 1)), + '#suffix' => '
', + ); + if ($state['weight'] > $max_weight) { + $max_weight = $state['weight']; + } +} + +/** + * Validate callback for the admin_settings form + * - at least one label should be filled + * - entered classes should be a valid css class + */ +function availability_calendars_admin_settings_validate($form, &$form_state) { + $op = isset($form_state['values']['op']) ? $form_state['values']['op'] : ''; + if ($op == t('Save configuration')) { + $element = $form_state['values']['states']; + $all_empty = true; + foreach ($element as $i => $state_fields){ + if (!empty($state_fields['label'])) { + $all_empty = false; + } + if (!empty($state_fields['class']) && !availability_calendars_is_safe_id($state_fields['class'])) { + form_set_error("states][$i][class", t('Class should be usable as a valid css class.')); + } + } + + if ($all_empty) { + form_set_error('states][0][label', t('At least 1 state should be defined.')); + } + + $default = $form_state['values']['defaultstatus']; + if (empty($element[$default]['label'])) { + form_set_error("states][$default][label", t('The default state may not be empty or deleted.')); + } + } +} + +/** + * Submit callback for the admin_settings form + * + * Processes the submitted form. the states are non system settings, and are handled here. + * Other values are handled by the default system settings form handling. + */ +function availability_calendars_admin_settings_submit($form, &$form_state) { + $op = isset($form_state['values']['op']) ? $form_state['values']['op'] : ''; + if ($op == t('Save configuration')) { + $element = $form_state['values']['states']; + // Do not process the states by the default submit handler for system settings forms + unset($form_state['values']['states']); + $states = array(); + foreach ($element as $i => $state_fields){ + // Only add non-empty labels + if (!empty($state_fields['label'])) { + if (empty($state_fields['class'])) { + $state_fields['class'] = availability_calendars_string_to_safe_id($state_fields['label']); + } + $states[$state_fields['class']] = array( + 'class' => $state_fields['class'], + 'label' => $state_fields['label'], + 'weight' => $state_fields['weight'], + ); + } + } + + $default = $form_state['values']['defaultstatus']; + if (!empty($element[$default]['class'])) { + $form_state['values']['availability_calendars_settings_system_defaultstatus'] = $element[$default]['class']; + } + + $existing_States = availability_calendars_get_states(); + if ($states != $existing_States) { + // update states: dellete all existing, insert all states on the form + db_query("DELETE FROM {availability_calendars_states}"); + foreach ($states as $state) { + db_query("INSERT INTO {availability_calendars_states} (class,label,weight) VALUES ('%s','%s',%d)", $state['class'], $state['label'], $state['weight']); + } + } + } +} + +/** * Create tab to show node availability. * * @return string or FALSE @@ -251,6 +404,7 @@ $notes[$note['week']] = $note['note']; } + $day_status = array(); $status_result = db_query('SELECT day, status FROM {availability_calendars_day} WHERE nid = %d AND year = %d AND month = %d', $nid, $year, $month); while ($status = db_fetch_array($status_result)) { $day_status[$status['day']] = $status['status']; @@ -279,7 +433,7 @@ '#type' => 'select', '#title' => date('l d F', strtotime("$year-$month-$day")), '#options' => availability_calendars_options(), - '#default_value' => ($day_status[$day] === NULL) ? $settings->defaultstatus : $day_status[$day] + '#default_value' => !empty($day_status[$day]) ? $day_status[$day] : $settings->defaultstatus ); $day++; $daysinweekremaining--; @@ -385,7 +539,9 @@ * Otherwise settings are set within the root of the object. */ function availability_calendar_getsettings($scope = NULL, $arg = NULL) { - switch ($scope): + $defaultstatus = reset(availability_calendars_get_states()); + $defaultstatus = $defaultstatus['class']; + switch ($scope) { case 'nodetype': $settings = variable_get('availability_calendars_settings_system-type_' . $arg, 0); break; @@ -395,9 +551,16 @@ $settings->showkey = variable_get('availability_calendars_settings_node_' . $arg . '_showkey', 1); $settings->firstletter = variable_get('availability_calendars_settings_node_' . $arg . '_firstletter', 0); $settings->showteaser = variable_get('availability_calendars_settings_node_' . $arg . '_showteaser', 0); - $settings->hideold = (variable_get('availability_calendars_settings_system_hideold', 0)) ? 1 : variable_get('availability_calendars_settings_node_' . $arg . '_hideold', 0); - $settings->defaultstatus = (variable_get('availability_calendars_settings_node_' . $arg . '_defaultstatus', NULL) === NULL) ? variable_get('availability_calendars_settings_system_defaultstatus', 'calavailable') : variable_get('availability_calendars_settings_node_' . $arg . '_defaultstatus', 'calavailable'); + $settings->hideold = variable_get('availability_calendars_settings_node_' . $arg . '_hideold', null); + if ($settings->hideold === null) { + $settings->hideold = variable_get('availability_calendars_settings_system_hideold', 0); + } + $settings->defaultstatus = variable_get('availability_calendars_settings_node_' . $arg . '_defaultstatus', null); + if ($settings->defaultstatus === null) { + $settings->defaultstatus = variable_get('availability_calendars_settings_system_defaultstatus', $defaultstatus); + } } + // explicit fall-through default: $types = node_get_types(); $system->status = array(); @@ -409,11 +572,11 @@ $system->showteaser = variable_get('availability_calendars_settings_system_showteaser', 1); $system->splitday = variable_get('availability_calendars_settings_system_splitday', 0); $system->hideold = variable_get('availability_calendars_settings_system_hideold', 0); - $system->defaultstatus = variable_get('availability_calendars_settings_system_defaultstatus', 'calavailable'); + $system->defaultstatus = variable_get('availability_calendars_settings_system_defaultstatus', $defaultstatus); if ($scope == 'node' && $arg) $settings->system = $system; else $settings = $system; break; - endswitch; + }; return $settings; } @@ -478,37 +641,21 @@ t('Color'), t('Availability') ); - $rows = array( // keystatus class used to allow better styling of the status column - array( - array( - 'data' => ' ', - 'class' => 'calavailable' - ), - array( - 'data' => t('Available'), - 'class' => 'keystatus' - ) - ), - array( - array( - 'data' => ' ', - 'class' => 'calnotavailable' - ), - array( - 'data' => t('Fully booked'), - 'class' => 'keystatus' - ) - ), - array( + $rows = array(); + $states = availability_calendars_get_states(); + foreach ($states as $class => $state) { + $rows[] = array( array( 'data' => ' ', - 'class' => 'calnotavailableprov' + 'class' => $class ), array( - 'data' => t('Provisionally booked'), - 'class' => 'keystatus' + 'data' => check_plain(t($state['label'])), + 'class' => 'keystatus' // keystatus class used to allow better styling of the status column ) - ), + ); + } + $rows += array( array( array( 'data' => ' ', @@ -611,15 +758,14 @@ while ($note = db_fetch_array($notes_result)) { $notes[$note['week']] = $note['note']; } + $day_status = array(); $status_result = db_query('SELECT day, status FROM {availability_calendars_day} WHERE nid = %d AND year = %d AND month = %d', $node->nid, $year, $month); while ($status = db_fetch_array($status_result)) { $day_status[$status['day']] = $status['status']; } - $availability_calendars_options = availability_calendars_options(); $today = mktime(0, 0, 0, date('m'), date('j'), date('Y')); $rows = array(); // our container for rows $cells = array(); // our container for cells - $options = availability_calendars_options(); foreach ($week as $key => $val) { $weeknumber = $key + 1; array_push($cells, array('data' => $notes[$weeknumber], 'class' => 'calnote')); // add the note cell to the cells array @@ -628,16 +774,15 @@ $daystamp = mktime(0, 0, 0, (int) $month, (int) $day, (int) $year); // if there's a date, it's part of this month if ($day) { + $status = !empty($day_status[$day]) ? $day_status[$day] : $settings->defaultstatus; $classes = array(); if ($today == $daystamp) $classes[] = 'caltoday'; // today if ($daystamp < $today) $classes[] = 'calpastdate'; // past date - if ($settings->hideold === 1 && $daystamp < $today) $classes[] = 'calnotavailable'; // past dates should be "fully booked" - else { - if ($day_status[$day] === NULL) $classes[] = ($settings->defaultstatus) ? $settings->defaultstatus : $options[0]; - else $classes[] = $day_status[$day]; + if ($settings->hideold !== 1 || $daystamp >= $today) { + $classes[] = $status; } $classes = implode(' ', $classes); - $day = (strpos($day_status[$day], 'calsplit ') === 0) ? ' 
' . $day . '
' : $day; + $day = strpos($status, 'calsplit ') === 0 ? ' 
' . $day . '
' : $day; array_push($cells, array('data' => $day, 'class' => $classes)); } else { // empty, typically row 1 or 5 in a month @@ -660,27 +805,26 @@ /** * availability_calendars status options. + * note: we return unescaped labels as they might be used as options in a select where they get escaped again * - * @return array + * @return array array with the classes as the keys and the translated but unescaped labels as values */ function availability_calendars_options() { - // TODO: make these configurable - $settings = availability_calendar_getsettings(); - $statuses = array( - 'available' => t('Available'), - 'notavailable' => t('Fully booked'), - 'notavailableprov' => t('Provisionally booked'), - ); - $ret = array(); - foreach ($statuses as $class => $label) { - $ret['cal' . $class] = $label; - } - if ($settings->splitday === 1) { - foreach ($statuses as $class => $label) { - $sub = $statuses; - unset($sub[$class]); - foreach ($sub as $subclass => $sublabel) { - $ret['calsplit cal-' . $class . '_' . $subclass] = t('@a (am)/@b (pm)', array('@a' => $label, '@b' => $sublabel)); + static $ret = null; + if ($ret === null) { + $ret = array(); + $settings = availability_calendar_getsettings(); + $statuses = availability_calendars_get_states(); + foreach ($statuses as $class => $state) { + $ret[$class] = $state['label']; + } + if ($settings->splitday === 1) { + foreach ($statuses as $class => $state) { + $sub = $statuses; + unset($sub[$class]); + foreach ($sub as $subclass => $substate) { + $ret['calsplit cal-' . $class . '_' . $subclass] = t('!a (am)/!b (pm)', array('!a' => $state['label'], '!b' => $substate['label'])); + } } } } @@ -688,6 +832,21 @@ } /** + * @return array array with records for all states (unescaped strings) + */ +function availability_calendars_get_states() { + static $states = null; + if ($states === null) { + $states = array(); + $result = db_query("SELECT * FROM {availability_calendars_states} ORDER BY weight"); + while ($row = db_fetch_array($result)) { + $states[$row['class']] = $row; + } + } + return $states; +} + +/** * Implementation of hook_form_alter(). * All form alterations needed for the calendars. * @@ -864,4 +1023,27 @@ } return $block; } +} + +/** +* Converts a string to a valid html id/class attribute. +* +* http://www.w3.org/TR/html4/struct/global.html#h-7.5.2 specifies what makes a +* valid id/class attribute in HTML. This function: +* +* - Ensure an ID starts with an alpha character by prefixing with 'cal'. +* - Replaces any character except a-z, A-Z, numbers, and underscores with dashes. +* - Converts entire string to lowercase. +* +* @param $string The string +* @return the converted string +*/ +function availability_calendars_string_to_safe_id($string) { + return 'cal' . strtolower(preg_replace('/[^a-zA-Z0-9_-]+/', '-', $string)); + //D7: return 'cal' . drupal_html_class($string); + +} + +function availability_calendars_is_safe_id($string) { + return preg_match('/^[a-zA-Z][a-zA-Z0-9_-]*$/', $string) > 0; } --- availability_calendars.css Tue Jan 18 05:10:01 2011 +++ availability_calendars.css Wed Feb 09 12:21:12 2011 @@ -19,15 +19,21 @@ td.calnotavailableprov,tr.odd td.calnotavailableprov,tr.even td.calnotavailableprov{color:#ccc;text-align:center;background:#FFFFE0;} td.caltoday,tr.odd td.caltoday,tr.even td.caltoday{border:1px solid #A00000;} /* split day statuses */ -td.cal-available_notavailable,tr.odd td.cal-available_notavailable,tr.even td.cal-available_notavailable{background:#FFB6C1;} -td.cal-available_notavailable > .cal-split-bg{border-color:#90EE90 #FFB6C1 #FFB6C1 #90EE90;} -td.cal-available_notavailableprov,tr.odd td.cal-available_notavailableprov,tr.even td.cal-available_notavailableprov{background:#FFFFE0;} -td.cal-available_notavailableprov > .cal-split-bg{border-color:#90EE90 #FFFFE0 #FFFFE0 #90EE90;} -td.cal-notavailable_available,tr.odd td.cal-notavailable_available, tr.even td.cal-notavailable_available{background:#90EE90;} -td.cal-notavailable_available > .cal-split-bg{border-color:#FFB6C1 #90EE90 #90EE90 #FFB6C1;} -td.cal-notavailable_notavailableprov,tr.odd td.cal-notavailable_notavailableprov,tr.even td.cal-notavailable_notavailableprov{background:#FFFFE0;} -td.cal-notavailable_notavailableprov > .cal-split-bg{border-color:#FFB6C1 #FFFFE0 #FFFFE0 #FFB6C1;} -td.cal-notavailableprov_available,tr.odd td.cal-notavailableprov_available,tr.even td.cal-notavailableprov_available{background:#90EE90;} -td.cal-notavailableprov_available > .cal-split-bg{border-color:#FFFFE0 #90EE90 #90EE90 #FFFFE0;} -td.cal-notavailableprov_notavailable,tr.odd td.cal-notavailableprov_notavailable,tr.even td.cal-notavailableprov_notavailable{background:#FFB6C1;} -td.cal-notavailableprov_notavailable > .cal-split-bg{border-color:#FFFFE0 #FFB6C1 #FFB6C1 #FFFFE0;} +td.cal-calavailable_calnotavailable,tr.odd td.cal-calavailable_calnotavailable,tr.even td.cal-calavailable_calnotavailable{background:#FFB6C1;} +td.cal-calavailable_calnotavailable > .cal-split-bg{border-color:#90EE90 #FFB6C1 #FFB6C1 #90EE90;} +td.cal-calavailable_calnotavailableprov,tr.odd td.cal-calavailable_calnotavailableprov,tr.even td.cal-calavailable_calnotavailableprov{background:#FFFFE0;} +td.cal-calavailable_calnotavailableprov > .cal-split-bg{border-color:#90EE90 #FFFFE0 #FFFFE0 #90EE90;} +td.cal-calnotavailable_calavailable,tr.odd td.cal-calnotavailable_calavailable, tr.even td.cal-calnotavailable_calavailable{background:#90EE90;} +td.cal-calnotavailable_calavailable > .cal-split-bg{border-color:#FFB6C1 #90EE90 #90EE90 #FFB6C1;} +td.cal-calnotavailable_calnotavailableprov,tr.odd td.cal-calnotavailable_calnotavailableprov,tr.even td.cal-calnotavailable_calnotavailableprov{background:#FFFFE0;} +td.cal-calnotavailable_calnotavailableprov > .cal-split-bg{border-color:#FFB6C1 #FFFFE0 #FFFFE0 #FFB6C1;} +td.cal-calnotavailableprov_calavailable,tr.odd td.cal-calnotavailableprov_calavailable,tr.even td.cal-calnotavailableprov_calavailable{background:#90EE90;} +td.cal-calnotavailableprov_calavailable > .cal-split-bg{border-color:#FFFFE0 #90EE90 #90EE90 #FFFFE0;} +td.cal-calnotavailableprov_calnotavailable,tr.odd td.cal-calnotavailableprov_calnotavailable,tr.even td.cal-calnotavailableprov_calnotavailable{background:#FFB6C1;} +td.cal-calnotavailableprov_calnotavailable > .cal-split-bg{border-color:#FFFFE0 #FFB6C1 #FFB6C1 #FFFFE0;} + +/* admin settings form */ +fieldset.state-list .state-item { position: relative; width: 100%; overflow: auto; } +fieldset.state-list .cal-state-label, fieldset.state-list .form-item { float: left; margin-right: 1em; } +fieldset.state-list .cal-state-label .form-item { margin: 0; float: none; } +fieldset.state-list input.form-radio { margin-right: 5em; }