Active context: blog_bios

Customizing Multiple Views to Create a Porftolio Page

Custom Drupal Development

BellaPictures.com's portfolio page is one of the most important components of the site in terms of generating customers. On this page, Bella wanted to display the high-quality work of their photographers and also allow users to interact with the various portfolios in an application-like format.

Bella Portfolio

The main focus of the page is a slideshow that displays wedding or engagement photos or, when available, a video. Below that are a small navigation bar and an image carousel. The left side of the page features information on the wedding and a link that allows the user to search for weddings in her/his area. Below this link is a list of other available wedding portfolios.

For all these components to work together, the Achieve Internet team had to dig deep into Views and JQuery.

Adding Additional Logic to Exposed Filters in Drupal's Views Module

The “Find weddings in my area” link generates a modal dialog that displays the wedding view’s exposed filter. In our Javascript for the page, we have a click handler that opens the pop-up:
 

$("#find-weddings").click(function(fwc) { 

    fwc.preventDefault();

    container_dialog.dialog('open');

  });

Find popup This would be a fairly straightforward filter, but Bella organizes its services around markets, as opposed to cities. It’s a bit creepy to ask a user what “market” they’re in, so instead the user enters a city and state, and we convert that into a market behind-the-scenes. Then we use the market to filter the view. For example, if a user enters Costa Mesa, California, we get a response back from the web service indicating that, yes, that city is in a service area and the market is Southern California. Therefore, we'd show the user a list of weddings in SoCal.

Hook_form_validate, in our custom Drupal module, performs the market lookup:
 

function bp_find_weddings_views_exposed_form_validate($form, &$form_state) {
  
  // Get the entered city
  $city_name = $form_state['values']['tid'] ? $form_state['values']['tid'] : $_SESSION['views']['container_list']['city'];
  $city_name = ucfirst(check_plain($city_name));

  // Get the entered state
  $state_name = ($form_state['values']['tid_1'] != 'All') ? $form_state['values']['tid_1'] : $_SESSION['views']['container_list']['state'];
  $state_name = bp_find_weddings_get_state_name($state_name);

  // Send city and state off to the BellaAPI
  if ($city_name && $state_name) {
    $params = array(
      'c' => $city_name,
      's' => $state_name,
      'b' => variable_get('check_availability_brand', '1'),
    );

    $valid_markets = bp_find_weddings_verify_location($params);

    // If not ok, post an error message
    if (!$valid_markets) {
      $error_text = variable_get('bp_find_weddings_error_text', 'Unable to find the location, please try again.');
      drupal_get_messages('error');
      $_SESSION['views']['container_list']['block_1'] = array();
      form_set_error('tid', t('@text', array('@text' => $error_text)));
    }
    else {
      // Set the market associations to search for
      $search_markets = bp_find_weddings_convert_market_codes($valid_markets);
      $form_state['values']['tid_2'] = $search_markets;

      // Unset the city and state so search is only on market
      if ($form_state['values']['tid']) {
        $_SESSION['views']['container_list']['city'] = $form_state['values']['tid'];
      }

      if ($form_state['values']['tid_1'] != 'All') {
        $_SESSION['views']['container_list']['state'] = $form_state['values']['tid_1'];
      }

      $form_state['values']['tid'] = '';
      $form_state['values']['tid_1'] = 'All';

      drupal_get_messages('error');
    }
  }
}

Notice that in addition to obtaining the market, we also nullify the user-entered data, since we won't be using it for filtering. Now here's the function called to access the web service:

function bp_find_weddings_verify_location($params) {
$api_url = variable_get('bp_find_weddings_api_url', "http://[URLService]/GetRegionMarkets.php");
   $full_url = $api_url . "/?" . http_build_query($params, '', '&');
    $response = drupal_http_request($full_url);
    $market = simplexml_load_string($response->data);

    $is_valid_market = (string) $market->result;

    if ($is_valid_market == 'true') {
        return $market->marketcode;
    }
    return FALSE;
}

Brilliant as that code looks, it didn’t work at first. We discovered that other functions were firing before our form_validate and this prevented ours from running. We addressed the priority of our function with a form_alter:

function bp_find_weddings_form_views_exposed_form_alter(&$form, &$form_state) {
  if ($form['#id'] == 'views-exposed-form-container-list-block-1') {
    array_unshift($form['#validate'], 'bp_find_weddings_views_exposed_form_validate');
  }
}

When our form_validate hook had the right-of-way, our clever behind-the-scenes market lookup and manipulation worked successfully.

Using Pre_Render to Generate a Semi-Random List in Views

A fairly common UI issue is if you’ve got a list and a user is viewing detail on one of the items in that list, you want to highlight it to indicate that it’s currently being viewed. This is a regular feature of navigation buttons that change when active.

In the case of the list of weddings on the bottom left of the portfolio page, Bella needed the first wedding to be the one currently being viewed, displayed as active, with the remaining items in random order.

A hook_pre_render is the appropriate place for this. In the function below, we get the current viewed wedding and make sure it’s in the list. If it’s not, we add it. Then, we make sure it’s in the first position in the array.

/**
 * Implementation of hook_views_pre_render().
 */
function bp_find_weddings_views_pre_render(&$view) {
  if ($view->name == 'container_list') {
    if (empty($view->result)) {
      // Clear the stored session filters
      $_SESSION['views']['container_list']['block_1'] = array();
      $_SESSION['views']['container_list']['city'] = '';
      $_SESSION['views']['container_list']['state'] = 'All';
    }
    else {
      // Make sure this isn't an ajax request
      if (strpos(request_uri(), 'ajax') === FALSE) {
        // Get the current wedding name
        $wedding_name = arg(1);

        // Find the container node for that wedding name
        $wedding_info = bp_find_weddings_get_wedding_info_by_name($wedding_name);

        // Check if that node is in the current results list
        $results = $view->result;
        $index = bp_find_weddings_results_list_search($wedding_info, $results);

        if ($index === FALSE ) {
          // If not, override the first node in the list with the current wedding info
          $results[0]->nid = $wedding_info->nid;
          $results[0]->node_vid = $wedding_info->node_vid;
          $results[0]->node_title = $wedding_info->node_title;
          $view->result = $results;
        }
        else {
          // If it is, make sure its info is first
          $old_first = clone $results[0];

          $results[0]->nid = $wedding_info->nid;
          $results[0]->node_vid = $wedding_info->node_vid;
          $results[0]->node_title = $wedding_info->node_title;

          $results[$index]->nid = $old_first->nid;
          $results[$index]->node_vid = $old_first->node_vid;
          $results[$index]->node_title = $old_first->node_title;

          $view->result = $results;
        }
      }
    }
  }
}

On the Javascript side, a small amount of code ensures that the first item in the list is active. We use the JQuery addClass function to adjust the styling of the first item in the list:

$("#navbar-inner ul.links li.first").addClass('active');
  $("#navbar-inner ul.links li.first a").addClass('active');

A Brief Nod to Performance

Because the portfolio page required a boatload of css and Javascript that the rest of the site didn't need, we used some functions from the Drupal API in a page pre-process to load the apposite files:

function phptemplate_preprocess_page(&$vars) {
  // If this is the portfolio page, import the js we need
  if (arg(0) == 'portfolio' && arg(1)) {
    $vars['category_name'] = 'PORTFOLIO';
    drupal_add_css(drupal_get_path('theme', 'bp_2') . '/css/portfolio.css');
    drupal_add_js(drupal_get_path('theme', 'bp_2') . '/js/jquery.cycle.all.min.js');
    drupal_add_js(drupal_get_path('theme', 'bp_2') . '/js/portfolio-wedding.js');
    jcarousel_add('#icon-carousel', array(), 'portfolio', drupal_get_path('theme', 'bp_2') . '/css/portfolio-skin.css');
    jcarousel_add('#video-icon-carousel', array(), 'portfolio', drupal_get_path('theme', 'bp_2') . '/css/portfolio-skin.css');

    ...I'm omitting the rest of the code in this rather long function...
}

Picking Your Spots

We all know that views is a powerful module; it allowed the Achieve Internet team to get the framework for this portfolio page up ridiculously fast. It's also quite complex. Figuring out where to stick in your nose to make customizations can be difficult. Hopefully seeing what we did here will be useful to others creating complex UI's. Feel free to read more about Achieve's Bella Pictures project here.

Comments

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

More information about formatting options