diff --git a/ding_reservation.info b/ding_reservation.info index bc113f9..9bfd698 100644 --- a/ding_reservation.info +++ b/ding_reservation.info @@ -3,7 +3,7 @@ description = Allows users to reserve materials. package = Ding! version = "7.x-0.18" core = 7.x -files[] = ding_reservation.module +files[] = includes/ding_reservation_entity.inc files[] = ding_reservation.test dependencies[] = ding_base dependencies[] = ding_provider diff --git a/ding_reservation.make b/ding_reservation.make index d6c6336..6269553 100644 --- a/ding_reservation.make +++ b/ding_reservation.make @@ -3,27 +3,27 @@ core = 7.x ; Contrib -projects[date][subdir] = contrib -projects[date][version] = "2.6" +projects[date][subdir] = "contrib" +projects[date][version] = "2.8" ; Ding 2 modules projects[ding_base][type] = "module" projects[ding_base][download][type] = "git" projects[ding_base][download][url] = "git@github.com:ding2/ding_base.git" -projects[ding_base][download][tag] = "7.x-0.4" +projects[ding_base][download][branch] = "master" projects[ding_popup][type] = "module" projects[ding_popup][download][type] = "git" projects[ding_popup][download][url] = "git@github.com:ding2/ding_popup.git" -projects[ding_popup][download][tag] = "7.x-0.4" +projects[ding_popup][download][branch] = "master" projects[ding_user][type] = "module" projects[ding_user][download][type] = "git" projects[ding_user][download][url] = "git@github.com:ding2/ding_user.git" -projects[ding_user][download][tag] = "7.x-0.19" +projects[ding_user][download][branch] = "master" projects[ding_provider][type] = "module" projects[ding_provider][download][type] = "git" projects[ding_provider][download][url] = "git@github.com:ding2/ding_provider.git" -projects[ding_provider][download][tag] = "7.x-0.13" +projects[ding_provider][download][branch] = "master" diff --git a/ding_reservation.module b/ding_reservation.module index 93a6939..142920c 100644 --- a/ding_reservation.module +++ b/ding_reservation.module @@ -1,12 +1,15 @@ 'Update reservations', 'page callback' => 'drupal_get_form', - 'page arguments' => array('ding_reservation_update_reservations_form', 1, 5), + 'page arguments' => array('ding_reservation_update_reservations_form', 1, 5, 6), 'access callback' => 'ding_reservation_access', 'access arguments' => array(1), ); + $items['user/%user/status/reservations/delete/%'] = array( + 'title' => 'Delete reservations', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('ding_reservation_delete_reservations_form', 1, 5), + 'access callback' => 'ding_reservation_access', + 'access arguments' => array(1), + ); + + $items['ting/object/%ting_object/reserve'] = array( + 'page callback' => 'ding_reservation_reserve_ajax', + 'page arguments' => array(2), + 'delivery callback' => 'ajax_deliver', + 'access arguments' => array('perform reservation'), + ); + return $items; } @@ -46,6 +70,18 @@ function ding_reservation_access($account) { return $user->uid == $account->uid; } +/** + * Implements hook_permission(). + */ +function ding_reservation_permission() { + return array( + 'perform reservation' => array( + 'title' => t('Perform reservation'), + 'description' => t('Perform reservation in the library system.'), + ), + ); +} + /** * Implements hook_ding_entity_menu(). */ @@ -63,10 +99,121 @@ function ding_reservation_ding_entity_menu(&$items, $type, $path, $index) { /** * Implements hook_ding_entity_buttons(). */ -function ding_reservation_ding_entity_buttons($type, $entity) { +function ding_reservation_ding_entity_buttons($type, $entity, $widget = 'default') { + $button = ''; + if ($type == 'ding_entity' && $entity->is('reservable')) { - return array(ding_provider_get_form('ding_reservation_reserve_form', new DingReservationReservableEntity($entity), TRUE)); + switch ($widget) { + case 'ajax': + drupal_add_library('system', 'drupal.ajax'); + + $button = array( + array( + '#theme' => 'link', + '#text' => t('Reserve'), + '#path' => 'ting/object/' . $entity->id . '/reserve', + '#options' => array( + 'attributes' => array( + 'class' => array( + 'action-button', + 'reserve-button', + 'use-ajax', + ), + 'id' => 'reservation-' . $entity->id, + ), + 'html' => FALSE, + ), + ), + ); + break; + + default: + // The last parameter to the form below (TRUE) hides the provider + // options in the form (interest period and branch). + $button = array(ding_provider_get_form('ding_reservation_reserve_form', new DingReservationReservableEntity($entity), TRUE)); + break; + } + } + + return $button; +} + +/** + * Ajax entry callback. + * + * Try to reserve the material, if the user is not logged in trigger a ajax + * login. + * + * @param TingEntity $entity + * Ting entity object. + * @param DingReservationReservable $reservable + * Object with information about the entity to reserve. Used to make + * reservation of periodical, where volume and issue is part of the + * reservation. + * + * @return array + * Render array with Ajax commands. + */ +function ding_reservation_reserve_ajax($entity, $reservable = NULL) { + $commands = array(); + + // Check if the logged in user is a library user. + global $user; + if (!user_is_logged_in()) { + // Trigger log-in (the reservation link will be triggered on success). + $commands[] = ajax_command_ding_user_authenticate(''); } + elseif (!ding_user_is_provider_user($user)) { + // Error not library user. + $commands[] = ajax_command_ding_popup('ding_reservation', t('Error'), '
' . t('Only library user can make reservations.') . '
'); + } + elseif (!(is_object($entity) && $entity instanceof TingEntity)) { + // Error not ting entity. + $commands[] = ajax_command_ding_popup('ding_reservation', t('Error'), '' . t('Unable to load information about the material.') . '
'); + } + else { + // Check if reservable object was paste. + if (is_null($reservable)) { + // If no object passed assume "normal" reservation (not periodical). + $reservable = new DingReservationReservableEntity($entity); + } + + // Try to make reservation. + try { + // Check if user have preferred branch and interest period, if so + // submit the reservation form. If not display another form for with + // the options to select branch and period. + $defaults = ding_provider_invoke('reservation', 'default_options', $user); + $matches = preg_grep("/preferred_branch$/", array_keys($defaults)); + if (empty($defaults[array_shift($matches)])) { + $form = ding_provider_get_form('ding_reservation_reserve_form', $reservable, FALSE); + $commands[] = ajax_command_ding_popup('ding_reservation', t('Reservation'), render($form)); + } + else { + $form_state = array('values' => array()); + drupal_form_submit('ding_reservation_reserve_form', $form_state, $reservable); + + // Return any status messages set by the form. + $commands[] = ajax_command_ding_popup('ding_reservation', t('Reservation'), theme('status_messages')); + } + } + catch (DingProviderAuthException $exception) { + // The form may have thrown an Auth exception, so display login. (the + // reservation link will be triggered on success). + $commands[] = ajax_command_ding_user_authenticate(''); + } + catch (Exception $exception) { + // The form may have thrown an auth exception as the login may have + // timed-out (the reservation link will be triggered on success). + $commands[] = ajax_command_ding_popup('ding_reservation', t('Error'), '' . t('Unknown error in reservation, please contact the library.') . '
'); + + // Log exception. + watchdog_exception('ding_reservation', $exception); + } + } + + // Return the ajax commands as an render array. + return array('#type' => 'ajax', '#commands' => $commands); } /** @@ -90,13 +237,17 @@ function ding_reservation_ding_provider_user() { * function, use this to let have their own form id. */ function ding_reservation_forms($form_id, $args) { - $forms['ding_reservation_reservations_ready_form'] = array( - 'callback' => 'ding_reservation_reservations_form', - ); - $forms['ding_reservation_reservations_notready_form'] = array( - 'callback' => 'ding_reservation_reservations_form', + return array( + 'ding_reservation_reservations_ready_form' => array( + 'callback' => 'ding_reservation_reservations_form', + ), + 'ding_reservation_reservations_notready_form' => array( + 'callback' => 'ding_reservation_reservations_form', + ), + 'ding_reservation_reservations_ill' => array( + 'callback' => 'ding_reservation_reservations_form', + ), ); - return $forms; } /** @@ -130,20 +281,37 @@ function ding_reservation_reserve_form($form, &$form_state, $reservable, $hide_o '#value' => array(), ); + // Helps decide if the provider options should be displayed in the reserve + // form. If the user have default value these are used to make a quicker + // reservation process. $hide_options = !isset($form_state['options_hidden']) ? $hide_options : FALSE; $form_state['options_hidden'] = $hide_options; if (!$hide_options) { - if (ding_provider_implements('reservation', 'options') && $provider_form = ding_provider_invoke('reservation', 'options', 'create', $user, $reservable)) { + if (ding_provider_implements('reservation', 'options') && $provider_form = ding_provider_invoke('reservation', 'options', $user)) { $form['provider_options'] = $provider_form + array( '#tree' => TRUE, ); + + // The normal reserve button and the reserve for with provider options are + // the same form. But DDBasic hides the reserve buttons until availability + // have been confirmed. So we need to add a class to the form to make it + // visible. + $form['#attributes'] = array( + 'class' => array('reservable'), + ); } } $form['submit'] = array( '#type' => 'submit', '#value' => t('Reserve'), + '#attributes' => array( + 'class' => array( + 'action-button', + 'reserve-button', + ), + ), '#ajax' => array( 'callback' => 'ding_reservation_reserve_form_callback', 'wrapper' => 'ding-reservation-reserve-form', @@ -154,37 +322,45 @@ function ding_reservation_reserve_form($form, &$form_state, $reservable, $hide_o } /** - * Form validation. + * Reserve form validation. */ function ding_reservation_reserve_form_validate($form, &$form_state) { global $user; - if (ding_provider_implements('reservation', 'options_validate')) { - $res = ding_provider_invoke('reservation', 'options_validate', 'create', $user, $form_state['values']['reservable'], $form_state['values']['provider_options']); - /** - * We cannot set the value of the individual provider form elements, as - * they might not have been show, and thus not exist. However, setting the - * value of the parent element to an associative array gives the same end - * result. - */ - $provider_options = array(); - foreach ($res as $key => $value) { - if (is_array($value) && !empty($value['#error'])) { - if (!$form_state['options_hidden']) { - // Only show an error if the user had a choice. - form_error($form['provider_options'], $res['#error']); + if (user_is_logged_in() && ding_user_is_provider_user($user)) { + if (ding_provider_implements('reservation', 'default_options')) { + $defaults = ding_provider_invoke('reservation', 'default_options', $user); + + $provider_options = array(); + foreach ($defaults as $key => $default) { + // Check if the current provider options has a default value. + if (empty($default) && empty($form_state['values']['provider_options'][$key])) { + // Set form error to trigger display of the form in ding pop-up. The + // message will not be shown and the user will have to select values. + form_error($key, t('Please select a valid value.')); + $form_state['rebuild'] = TRUE; } else { - // Else simply rebuild the form. - $form_state['rebuild'] = TRUE; + if (empty($default) || !empty($form_state['values']['provider_options'][$key])) { + // If default value id not defined, try using the forms value. + $provider_options[$key] = $form_state['values']['provider_options'][$key]; + } + else { + // Default value was set, so we use it (as the forms selection + // options should not have been shown to the user). + $provider_options[$key] = $default; + } } } - else { - $provider_options[$key] = $value; + + // Do not set provider options, if the form was marked for rebuild. The + // user should have a change to select the values first. + if (!$form_state['rebuild']) { + form_set_value($form['provider_options'], $provider_options, $form_state); } } - if (!empty($provider_options)) { - form_set_value($form['provider_options'], $provider_options, $form_state); - } + } + else { + throw new DingProviderAuthException(); } } @@ -194,7 +370,7 @@ function ding_reservation_reserve_form_validate($form, &$form_state) { function ding_reservation_reserve_form_submit($form, &$form_state) { global $user; if (ding_provider_implements('reservation', 'options_submit')) { - ding_provider_invoke('reservation', 'options_submit', 'create', $user, $form_state['values']['reservable'], $form_state['values']['provider_options']); + ding_provider_invoke('reservation', 'options_submit', $user, $form_state['values']['provider_options']); } if ($form_state['values']['reservable']) { $reservable = $form_state['values']['reservable']; @@ -207,12 +383,15 @@ function ding_reservation_reserve_form_submit($form, &$form_state) { drupal_set_message(t('"@title" reserved and will be available for pickup at @branch.', array('@title' => $reservable->getTitle(), '@branch' => $branch_name))); } else { - drupal_set_message(t('"@title" reserved.', array('@title' => $reservable->getProviderId()))); + drupal_set_message(t('"@title" reserved.', array('@title' => $reservable->getTitle()))); } if (is_array($reservation_result) and !empty($reservation_result['queue_number'])) { drupal_set_message(t('You are number @number in queue.', array('@number' => $reservation_result['queue_number']))); } + + // Clear reservation session cache. + ding_reservation_cache_clear(); } catch (DingProviderUserException $e) { drupal_set_message($e->getMessageT(array('@title' => $reservable->getTitle())), 'error'); @@ -239,6 +418,22 @@ function ding_reservation_reserve_form_callback($form, &$form_state) { $html = theme('status_messages'); if ($form_state['rebuild'] || form_get_errors()) { + // Get the default interest period for the current user. + $default_interest_period = ding_provider_invoke('reservation', 'default_interest_period'); + + // Use #value instead of #default_value when rendering forms + // using drupal_render(). + $form['provider_options']['interest_period']['#value'] = $default_interest_period; + + // Hide certain fields, if any. + if (is_array($form_state['removable'])) { + $removal = $form_state['removable']; + foreach ($removal as $v) { + unset($form['provider_options'][$v]); + unset($form['provider_options'][$v . 'description']); + } + } + // Redisplay form. $html .= drupal_render($form); } @@ -251,173 +446,272 @@ function ding_reservation_reserve_form_callback($form, &$form_state) { /** * Show a reservation list form. */ -function ding_reservation_reservations_form($form, &$form_state, $items = array(), $type = 'not_ready_for_pickup') { - $options = array(); - $destination = drupal_get_destination(); - switch ($type) { - case 'ready_for_pickup': - $header = array( - 'title' => t('Title'), - 'created' => t('Created date'), - 'pickup_date' => t('Pickup date'), - 'pickup_branch' => t('Pickup branch'), - 'operations' => '', - ); - uasort($items, 'ding_reservation_sort_queue_by_pickup_date'); - break; - case 'not_ready_for_pickup': - $header = array( - 'title' => t('Title'), - 'created' => t('Created date'), - 'expiry' => t('Expiry date'), - 'pickup_branch' => t('Pickup branch'), - 'queue_number' => t('Queue number'), - 'operations' => '', - ); - uasort($items, 'ding_reservation_sort_queue_by_queue_number'); - break; - } - foreach ($items as $id => $item) { - $entity = $item->entity; - $pickup_branch = ding_provider_invoke('reservation', 'branch_name', $item->pickup_branch_id); - switch ($type) { - case 'ready_for_pickup': - $options[$item->id] = array( - 'title' => array( - 'data' => array($entity ? ting_object_view($entity, 'user_list') : array('#markup' => $item->display_name)), - 'class' => 'title', - ), - 'created' => array( - 'data' => $item->created ? format_date(strtotime(check_plain($item->created)), 'date_only') : '', - 'class' => 'created-date', - ), - 'pickup_date' => array( - 'data' => $item->pickup_date ? format_date(strtotime(check_plain($item->pickup_date)), 'date_only' ) : '', - 'class' => 'pickup-date', - ), - 'pickup_branch' => array( - 'data' => $pickup_branch ? check_plain($pickup_branch) : '', - 'class' => 'pickup-branch', - ), - 'operations' => array( - 'data' => array( - '#prefix' => '', - '#markup' => t('(Pickup id: @pickup_id)', array('@pickup_id' => check_plain($item->pickup_order_id))), - '#suffix' => '
', ); - } - if (!isset($item->pickup_order_id) && isset($item->order_id)) { - $options[$item->id]['title']['data'][] = array( - '#type' => 'markup', - '#prefix' => '', - '#markup' => t('(Order no. @order_id)', array('@order_id' => check_plain($item->order_id))), - '#suffix' => '
', + break; + + case DING_RESERVATION_NOT_READY: + $item = array( + '#type' => 'material_item', + '#id' => $reservation->id, + '#title' => $title, + '#cover' => field_view_field('ting_object', $entity, 'ting_cover', 'user_list'), + '#information' => array( + 'queue_number' => array( + 'label' => t('Queue number'), + 'data' => $reservation->queue_number ? check_plain($reservation->queue_number) : '', + 'class' => 'queue-number', + '#weight' => 0, + ), + 'expiry' => array( + 'label' => t('Expiry date'), + 'data' => $reservation->created ? format_date(strtotime(check_plain($reservation->expiry)), 'ding_material_lists_date') : '', + 'class' => 'expire-date', + '#weight' => 4, + ), + 'pickup_branch' => array( + 'label' => t('Pickup branch'), + 'data' => $pickup_branch ? check_plain($pickup_branch) : '', + 'class' => 'pickup-branch', + '#weight' => 8, + ), + 'created' => array( + 'label' => t('Created date'), + 'data' => $reservation->created ? format_date(strtotime(check_plain($reservation->created)), 'ding_material_lists_date') : '', + 'class' => 'created-date', + '#weight' => 16, + ), + 'order_nr' => array( + 'label' => t('Order nr.'), + 'data' => ding_reservation_get_order_nr($reservation), + 'class' => 'pickup-id', + '#weight' => 32, + ), + ), ); - } - $form['reservations'] = array( - '#type' => 'tableselect_form', - '#header' => $header, - '#options' => $options, - '#empty' => t('No reservations ready for pickup'), - ); - break; - case 'not_ready_for_pickup': - $options[$item->id] = array( - 'title' => array( - 'data' => array($entity ? ting_object_view($entity, 'user_list') : array('#markup' => $item->display_name)), - 'class' => 'title', - ), - 'created' => array( - 'data' => $item->created ? format_date(strtotime(check_plain($item->created)), 'date_only') : '', - 'class' => 'created-date', - ), - 'expiry' => array( - 'data' => $item->created ? format_date(strtotime(check_plain($item->expiry)), 'date_only') : '', - 'class' => 'expire-date', - ), - 'pickup_branch' => array( - 'data' => $pickup_branch ? check_plain($pickup_branch) : '', - 'class' => 'pickup-branch', - ), - 'queue_number' => array( - 'data' => $item->queue_number ? check_plain($item->queue_number) : '', - 'class' => 'queue-number', - ), - 'operations' => array( - 'data' => array( - '#prefix' => '', - '#markup' => t('(Order no. @order_id)', array('@order_id' => check_plain($item->order_id))), - '#suffix' => '
', + if (in_array($type, array(DING_RESERVATION_NOT_READY, DING_RESERVATION_INTERLIBRARY_LOANS))) { + // Set reservation expire message. + $expire = strtotime(check_plain($reservation->expiry)); + if ($expire - variable_get('reservation_expire', 604800) <= time()) { + $item['#material_message'] = array( + 'message' => t('This reservation is about to expire.'), + 'class' => 'messages warning', ); + $item['#weight'] = -30; } + } - $form['reservations'] = array( - '#type' => 'tableselect_form', - '#header' => $header, - '#options' => $options, - '#empty' => t('No Reservations'), + // Add extra information if it's a periodical. There is an exception because + // the notes field is also used to set library information for ill's but + // only when they are ready for pick-up. + if (!empty($reservation->notes) && !($reservation->ill_status && $type == DING_RESERVATION_READY)) { + $item['#information']['periodical_number'] = array( + 'label' => t('Periodical no.'), + 'data' => check_plain($reservation->notes), + 'class' => 'periodical-number', + '#weight' => -4, ); - break; } + + // Add the reservation to the form. + $form['reservations'][$reservation->id] = $item; } - $form['submit'] = array( - '#prefix' => '' . t('In order to make quick reservations, you must select a default pickup branch.') . '
', ); } + return $form; } - /** - * Create an interest period selector. + * Create an interest period form select element. * - * Returns form element(s) for selecting an interest period. + * @param string $name + * The name of the form element to build. + * @param int $default + * The pre-selected value. + * @param array $options + * The periods that should be available for selection in the form element. + * + * @return array + * Form element with a selection input type to select interest period. */ -function ding_reservation_interest_period_selector($type, $name, $default, $options) { - $create = ($type == 'create'); - $allowed_periods = ($create ? array() : array('' => t('No change'))) + $options; - // $default_value = $create ? $default : ''; +function ding_reservation_interest_period_selector($name, $default, $options) { + $form = array(); + + // Used to enable translation of options. + if (is_array($options)) { + foreach ($options as $k => $v) { + $options[$k] = t(check_plain($v)); + } + } + $form[$name] = array( '#type' => 'select', '#title' => t('Select interest period'), - '#options' => $allowed_periods, - '#default_value' => $default, + '#options' => $options, + '#required' => TRUE, + '#default_value' => !empty($default) ? $default : '', ); - if ($create) { + + if (empty($default)) { $form[$name . 'description'] = array( '#markup' => '' . t('Select an interest period.') . '
', ); } + return $form; } - /** - * Validate pickup branch selector. - * - * Returns what ding_reservation expects. + * Clears the reservations cache if ding_session_cache is active. */ -function ding_reservation_default_options_branch_validate($type, $name, $default, $values) { - if (empty($values[$name])) { - if ($type == 'create' && empty($default)) { - $result['openruth_preferred_branch'] = array( - '#error' => 'You must select a branch', - ); - } - else { - $result[$name] = $default; - } - return $result; - } else { - return $values; +function ding_reservation_cache_clear() { + if (module_exists('ding_session_cache')) { + ding_session_cache_clear('ding_reservation', 'reservations'); } } /** - * Submit pickup branch selector. + * Implements hook_ding_session_cache_defaults(). * - * Returns new properties to save, if any. - */ -function ding_reservation_default_options_branch_submit($type, $name, $default, $values) { - $result = array(); - if ($type == 'create' && !empty($values[$name]) && $values[$name] != $default) { - $result[$name] = $values['name']; - } - return $result; -} - - -/** - * Callback function to sort array by pickup date - */ -function ding_reservation_sort_queue_by_pickup_date($a, $b) { - if ($a->pickup_date == $b->pickup_date) { - return 0; - } - return ($a->pickup_date < $b->pickup_date) ? -1 : 1; -} - -/** - * Callback function for sorting loans by queue_number + * Set default ding_session_cache settings and tell ding_session_cache that this + * module supports it. */ -function ding_reservation_sort_queue_by_queue_number($a, $b) { - if ($a->queue_number == $b->queue_number) { - return 0; - } - return ($a->queue_number < $b->queue_number) ? -1 : 1; -} - -/** - * Interface for reservable items. - */ -interface DingReservationReservable { - public function getProviderId(); - // @todo, this should be optional. - public function getEntity(); - public function getTitle(); -} - -/** - * A reservable entity. - */ -class DingReservationReservableEntity implements DingReservationReservable { - public function __construct($entity) { - $this->entity = $entity; - } - - public function getProviderId() { - return $this->entity->provider_id; - } - - public function getEntity() { - return $this->entity; - } - public function getTitle() { - return $this->entity->title; - } +function ding_reservation_ding_session_cache_defaults() { + return array( + 'titel' => 'Ding reservation', + 'enabled' => TRUE, + 'expire' => 3600, + ); } - diff --git a/ding_reservation.test b/ding_reservation.test index d17df23..a5787cb 100644 --- a/ding_reservation.test +++ b/ding_reservation.test @@ -1,4 +1,8 @@ entity = $entity; + } + + public function getProviderId() { + return isset($this->entity->provider_id) ? $this->entity->provider_id : NULL; + } + + public function getEntity() { + return $this->entity; + } + + public function getTitle() { + return isset($this->entity->record['dc:title'][''][0]) ? $this->entity->record['dc:title'][''][0] : ''; + } +} \ No newline at end of file diff --git a/js/ding_reservation.js b/js/ding_reservation.js new file mode 100644 index 0000000..52ffa53 --- /dev/null +++ b/js/ding_reservation.js @@ -0,0 +1,89 @@ +/** + * Handle reservation checkboxes select all and select count on buttons. + */ +(function ($) { + "use strict"; + $(document).ready(function($) { + // Variables used to make the buttons follow scroll. + var actions = $(".action-buttons"); + var actions_offset = 0; + var win = $(window); + + // Ensure that all checkboxes are not checked (if user reloads the page + // etc.). + $('form input[type=checkbox]').prop('checked', false); + $('.action-buttons input[type=submit]').prop('disabled', 'disabled'); + + // Handle select all checkboxes. + $('.select-all input[type=checkbox]').click(function() { + var checkboxes = $('input[type=checkbox]', $(this).closest('form')); + if ($(this).prop('checked')) { + checkboxes.prop('checked', true); + } + else { + checkboxes.prop('checked', false); + } + checkboxes.change(); + }); + + // Handle checkbox button count. + $('.material-item input[type=checkbox]').change(function() { + var form = $(this).closest('form'); + var buttons = $('input[type=submit]', form); + var count = $('.material-item input[type=checkbox]:checked', form).length; + update_buttons(buttons, count); + + // Handle all checkbox checked state, so if not are all selected the + // checkbox is not checked. + if ($('.material-item input[type=checkbox]', form).length != count) { + $('.select-all input[type=checkbox]:not(:disabled)', form).prop('checked', false); + } + else { + $('.select-all input[type=checkbox]:not(:disabled)', form).prop('checked', true); + } + }); + + /** + * Update count string on the buttons. + */ + function update_buttons(buttons, count) { + buttons.each(function(index) { + var btn = $(buttons[index]); + btn.val(btn.val().replace(/\(\d+\)/, '(' + count + ')')); + + // Toggle buttons based on count. + if (count > 0) { + if (!actions_offset) { + // First time buttons are shown, get their offset value. + actions_offset = actions.offset().top; + } + btn.removeAttr("disabled"); + } + else { + btn.prop('disabled', 'disabled'); + } + }); + + toggle_scroll_buttons(); + } + + // Enable scroll and toggle of buttons. It uses class to this effect can be + // cancelled by removing classes from the theme. + $(window).scroll(function(){ + toggle_scroll_buttons(); + }); + + /** + * Helper function to toggle the "action-buttons-is-scrolling" class, which + * moves the out of flow to follow the top of the screen on scroll. + */ + function toggle_scroll_buttons() { + if (actions_offset < win.scrollTop()) { + actions.addClass('action-buttons-is-scrolling'); + } + else { + actions.removeClass('action-buttons-is-scrolling'); + } + } + }); +})(jQuery); diff --git a/plugins/content_types/reservations/reservations.inc b/plugins/content_types/reservations/reservations.inc index 34eecdf..f8f67ae 100644 --- a/plugins/content_types/reservations/reservations.inc +++ b/plugins/content_types/reservations/reservations.inc @@ -1,9 +1,10 @@ t('User reservation list'), 'description' => t('The current reservations for a user'), @@ -14,73 +15,145 @@ $plugin = array( ); /** - * Render the block + * Render the block. */ function ding_reservation_reservations_content_type_render($subtype, $conf, $panel_args, $context) { - static $preload_reservations = TRUE; - $account = isset($context->data) ? $context->data : NULL; - $preload_ids = array(); - $items = array(); - $reservation_list = array(); + $account = isset($context->data) ? $context->data : NULL; - $block = new stdClass(); + // Defined the return block. + $block = new stdClass(); + $block->title = t('My reservations'); $block->module = 'ding_reservation'; $block->delta = 'reservations'; - switch ($conf['reservation_type_list']) { - case 'ready_for_pickup': - $block->title = t('Reservations ready for pickup'); - $no_reservation_text = t('There are no reservations ready for pickup.'); - $form_id = 'ding_reservation_reservations_ready_form'; - break; - default: - case 'not_ready_for_pickup': - $block->title = t('Reservations'); - $no_reservation_text = t('There are no reservations.'); - $form_id = 'ding_reservation_reservations_notready_form'; - break; + // Get configuration for this pane and set empty text. + $types = array_filter($conf['reservation_type_list']); + $block->content = t($conf['reservation_empty_text']); + $form_id = $conf['reservation_form_id']; + + $content = array(); + if (module_exists('ding_session_cache')) { + $content = ding_session_cache_get('ding_reservation', 'reservations'); } - $list = ding_provider_invoke_page('reservation', 'list', $account); + if (empty($content) || !isset($content[$form_id])) { + // Get the reservations lists. + $reservations = ding_provider_invoke_page('reservation', 'list', $account); - foreach ($list as $item) { - if (isset($item->ding_entity_id)) { - $preload_ids[] = $item->ding_entity_id; - } - if (isset($conf['reservation_type_list'])) { - if ($conf['reservation_type_list'] == 'ready_for_pickup' && $item->ready_for_pickup == TRUE || $conf['reservation_type_list'] == 'not_ready_for_pickup' && $item->ready_for_pickup == FALSE) { - $reservation_list[] = $item; + // Filter items base on configuration and pre-load ting entities. + $preloaded_reservations = &drupal_static(__FUNCTION__, array()); + $items = array(); + foreach ($types as $type) { + // Try to pre-load ting entities, if not already loaded. + if (!isset($preloaded_reservations[$type])) { + $preload_ids = array(); + foreach ($reservations[$type] as $item) { + if (isset($item->ding_entity_id)) { + $preload_ids[] = $item->ding_entity_id; + } + } + + // Pre-load all ting entities. + if (!empty($preload_ids)) { + ding_entity_load_multiple($preload_ids); + } + + // Set this list type as preloaded. + $preloaded_reservations[$type] = TRUE; } + + // Sort the reservations based on type. + $sorted = array(); + switch ($type) { + case DING_RESERVATION_READY: + $sorted = $reservations[$type]; + uasort($sorted, 'ding_reservation_sort_queue_by_pickup_date'); + break; + + case DING_RESERVATION_NOT_READY: + $sorted = $reservations[$type]; + uasort($sorted, 'ding_reservation_sort_queue_by_queue_number'); + break; + + case DING_RESERVATION_INTERLIBRARY_LOANS: + $sorted = $reservations[$type]; + break; + } + + // Add the current type to the items we want. + $items += $sorted; + } + + // Add the pre-load entity to each loan (side effect of getting the entity). + foreach ($items as $id => &$item) { + $item->entity; + } + + $content[$form_id] = $items; + + if (module_exists('ding_session_cache')) { + ding_session_cache_set('ding_reservation', 'reservations', $content); } } - // Preload all ting objects. - if ($preload_reservations) { - ding_entity_load_multiple($preload_ids); - $preload_reservations = FALSE; + + // Build block content. + if (!empty($content[$form_id])) { + if (count($types) > 1) { + // If more than one type is selected we will fallback to the most generic + // form type. + $block->content = ding_provider_get_form($form_id, $content[$form_id], DING_RESERVATION_NOT_READY, $conf); + } + else { + $block->content = ding_provider_get_form($form_id, $content[$form_id], array_shift($types), $conf); + } } - $block->content = count($reservation_list) == TRUE ? ding_provider_get_form($form_id, $reservation_list, $conf['reservation_type_list']) : $no_reservation_text; + return $block; } /** - * Adding the admin form, to be able to control the required context + * Adding the admin form, to be able to control the required context. */ function ding_reservation_reservations_content_type_edit_form($form, &$form_state) { $conf = $form_state['conf']; $form['reservation_type_list'] = array( - '#type' => 'radios', - '#title' => t('Reservation list type'), + '#type' => 'checkboxes', + '#title' => t('Reservation list type(s)'), '#options' => array( - 'not_ready_for_pickup' => t('Not ready for pickup'), - 'ready_for_pickup' => t('Ready for pickup'), + DING_RESERVATION_NOT_READY => t('Not ready for pickup'), + DING_RESERVATION_READY => t('Ready for pickup'), + DING_RESERVATION_INTERLIBRARY_LOANS => t('Interlibrary loans'), ), '#default_value' => !empty($conf['reservation_type_list']) ? $conf['reservation_type_list'] : NULL, ); + + $form['reservation_title'] = array( + '#type' => 'textfield', + '#title' => t('List title'), + '#default_value' => !empty($conf['reservation_title']) ? $conf['reservation_title'] : 'Reservations', + ); + + $form['reservation_empty_text'] = array( + '#type' => 'textfield', + '#title' => t('Empty text'), + '#default_value' => !empty($conf['reservation_empty_text']) ? $conf['reservation_empty_text'] : 'There are no reservations.', + ); + + $form['reservation_form_id'] = array( + '#type' => 'select', + '#title' => t('Form to user for reservations'), + '#options' => array( + 'ding_reservation_reservations_notready_form' => t('Reservation form'), + 'ding_reservation_reservations_ready_form' => t('Reservations ready form'), + 'ding_reservation_reservations_ill' => t('Interlibrary reservation form'), + ), + '#default_value' => !empty($conf['reservation_form_id']) ? $conf['reservation_form_id'] : 'ding_reservation_reservations_notready_form', + ); return $form; } /** - * Submit handler for the admin form + * Submit handler for the admin form. */ function ding_reservation_reservations_content_type_edit_form_submit(&$form, &$form_state) { foreach (element_children($form) as $key) { @@ -89,3 +162,31 @@ function ding_reservation_reservations_content_type_edit_form_submit(&$form, &$f } } } + +/** + * Returns the administrative title. + */ +function ding_reservation_reservations_content_type_admin_title($subtype, $conf) { + $types = implode(', ', array_filter($conf['reservation_type_list'])); + return 'Reservations (' . $types . ')'; +} + +/** + * Callback function to sort array by pickup date. + */ +function ding_reservation_sort_queue_by_pickup_date($a, $b) { + if ($a->pickup_date == $b->pickup_date) { + return 0; + } + return ($a->pickup_date < $b->pickup_date) ? -1 : 1; +} + +/** + * Callback function for sorting loans by queue_number. + */ +function ding_reservation_sort_queue_by_queue_number($a, $b) { + if ($a->queue_number == $b->queue_number) { + return 0; + } + return ($a->queue_number < $b->queue_number) ? -1 : 1; +}