How to Increase Bookings by Improving Your Vacation Rental’s Online Presence - Part 1

At Roomify we build reservation solutions for the web integrated in beautiful websites. Our interest came about because of very real needs. I actually rent out a vacation rental in South-East Sicily. In addition, Roomify team members have been involved with online tourism and travel in a number of different ways over the years. We will be using this vacation rental as a case study over a series of blog posts on how to improve vacation rental bookings.

Read More

The State of Drupal OpenSaaS and What Next

At Roomify we are working on OpenSaaS products for booking solutions (for the travel market and others) and are trying to figure out how best to talk about the benefits. As such, we thought it would be useful to take a step back, make sure we were all on the same page about what OpenSaaS means, figure out what is state of OpenSaaS right now and what are the challenges in getting clients to share that vision.

Read More

Building a Drupal Product? Let's talk at DrupalCon Los Angeles

The overwhelming majority of businesses in the Drupal community are consulting agencies. Groups of experts that make their skills available to clients at any hourly or daily rate. A much smaller number of Drupal businesses are creating products using Drupal. The problems product companies face are quite different and we are hoping to meet other product companies and share experiences at DrupalCon LA. We have a BOF spot booked so if you are working on a product or thinking to do so come to share and hear what others have to say. Below are some of our initial thoughts and questions we are grappling with. After the BOF we will follow up with another blog post with some of the insights we gained.

Read More

Vacation Rentals, Hotels, B&Bs, Multiple Properties and Booking with Drupal.. oh my!

It’s now just over three months since roomify launched. We’ve been enjoying the process immensely, despite the inevitable frustration of trying to set up everything that a business requires. What has kept us going is the satisfaction of hitting product milestones, and hearing back from users and clients about how our products are helping them. (We also like getting paid - that’s awesome too!).

As such, we are particularly pleased to share this update with you.

Our main product line-up is shaping up and we have demos and clients in beta trials for each. We have three discrete product solutions for the travel industry:

Casa: for vacation rentals

    Soon to be available as a SaaS service and an open-source counterpart already on drupal.org - https://www.drupal.org/project/roomifycasa

Locanda: for hotels and B&Bs

    This is in the final stages before release as a SaaS solution, and will also be released on drupal.org

Agency: for multiple properties

    Agency is also in the final stages before release as a SaaS solution, and a release as a distro on drupal.org

Yes - you got that right. All products will be available on Drupal.org and as SaaS solutions. We have our reasons, which we will be sharing but it would be great to hear what you think - just hit us up on @roomify.us or hello@roomify.us

Check out the screencast below for a quick overview of all three:

 

Roomify Drupal Product Overview from Ronald Ashri on Vimeo.

Read More

Object-Oriented Forms in Drupal 7

While working on a channel management plugin for Drupal Rooms, (beta coming soon!) I had a couple of interesting requirements to satisfy. Many booking service providers use the iCal standard in a way that is generically similar, but we wished to be able to support differences in implementation or capabilities between services. In addition, while we are initially focused on supporting channel management via iCal to cover as many providers as possible, it was important to me to do things generically enough to be able to support different APIs and interactions when pushing and pulling event and booking data without having to do a major re-architecture down the road. (Fingers crossed!)

To almost anyone that has been doing software development in the past decade or two in a language other than Fortran77, (couldn't resist a dig there, sorry Dad!) this quickly sounds ideally suited to an object-oriented architecture. To wit: Generic Importer Class > extended as iCal Importer > extended as Airbnb importer. This should lead to a flexible and extensible architecture, without needing to explicitly enable overridden functionality via the hook system.

However, there are certain challenges to integrating fully with Drupal's APIs when doing OOP. I can recommend this excellent series on Writing Drupal 7 code with an eye towards Drupal 8 for general pointers, and used their technique for implementing page callbacks on this project. The specific issue that confronted me is that drupal_get_form requires that the form ID be a valid function name, used to construct the callback when generating a new form, and I wished to define (and override) configuration forms in class methods, as well as handling form validation and submission. After some digging through the bowels of form.inc, I believe I came up with a reasonably efficient way of replicating drupal_get_form's functionality. The essential idea is that each specific availability source module implements a hook with information about its plugin. (Similar to the approach you may have seen used in the feeds module and some others) In the page callback for the channel management tab on the Rooms unit, we look for enabled implementations and build their forms:

 

 foreach (module_invoke_all('rooms_channel_source') as $source) {

  // Load this importer's class.
  $importer = new $source['handler']['class'];

  // Set the Rooms unit ID and load its specific configuration.
  $importer->config->unit_id = $unit->unit_id;
  $importer->load();

  // Get the importer's form array.
  $form = $importer->config_form();

  // Add a hidden form element with the class name, for use in validation/submission.
  $form['class'] = array(
    '#type' => 'hidden',
    '#value' => $source['handler']['class'],
  );

  // Define a form ID. 
  $form_id = $source['handler']['class'] . '_config_form';

  // Do the setup Drupal requires when not using drupal_get_form().
  $form_state = form_state_defaults();
  if (!isset($form_state['input'])) {
    $form_state['input'] = $form_state['method'] == 'get' ? $_GET : $_POST;
  }

  // Explicitly set the validation and submission handlers to our global
  // functions.
  $form_state['validate_handlers'] = array('rooms_channel_manager_config_form_validate');
  $form_state['submit_handlers'] = array('rooms_channel_manager_config_form_submit');

  // Go through the FAPI preparation and processing stages.
  drupal_prepare_form($form_id, $form, $form_state);
  drupal_process_form($form_id, $form, $form_state);
} 

Most of the above is boilerplate replicating the things that drupal_get_form does, the two pieces to pay particular attention to are:

  1. The $form['class'] element - this hidden element is what gives our form validation and submission wrapper functions the information they need to call the correct class method.
  2. The override of $form_state['submit_handlers'] (and validate_handlers) - this lets the FAPI know which functions to call for validation/submission.
The wrappers themselves are actually quite simple, it's just a matter of instantiating the correct class and calling the method if it exists:
/**
 * Wrapper function - execute submission handler for configuration forms in
 * source classes.
 */
function rooms_channel_manager_config_form_submit($form, &$form_state) {
  $importer = new $form['class']['#value'];
  if (method_exists($importer, 'config_form_submit')) {
    $importer->config_form_submit($form, $form_state);
  }
} 
The net result is that we are able to define a form and handle validation/submission in a class, over-riding a base class and form:
 /** 
 * Override config form with Airbnb-specific configuration.
 */                    
public function config_form() {
  $form = parent::config_form();
  $form[$this->source_name]['ical_url']['#title'] = t('Airbnb iCal link');
  return $form;
}

/**
 * Save iCal import configuration settings.
 */
public function config_form_submit($form, &$form_state) {
  $this->load();               
  if (isset($form_state['values']['unit_id'])) {
    $this->config->unit_id = $form_state['values']['unit_id'];
    $this->config->confirm_bookings = $form_state['values']['confirm_bookings'];
    $this->config->url = $form_state['values']['ical_url'];
    $this->save();             
  }                            
}                               
This certainly feels like more work than calling drupal_get_form, but I think the benefits more than make up for it. It's also worth mentioning that the cool module offers an alternative approach for performing this task and others. I generally like to avoid adding module dependencies for single tasks, but am going to keep it in mind for future projects. To summarize, the basic steps we have followed are:
  1. Define your classes in standard OOP fashion.
  2. Include the relevant form configurations/overrides within your classes.
  3. Load those form configurations from your classes in a page callback. (or ctools content_type plugin, etc.).
  4. Make sure you let the validation / submission handlers know what is up by specifying the class to load.
I hope you'll find this technique useful, and look forward to hearing about possible improvements!

 

Read More