Upgrade Your Drupal Skills

We trained 1,000+ Drupal Developers over the last decade.

See Advanced Courses NAH, I know Enough
Oct 05 2017
Oct 05
October 5th, 2017

Welcome to the first episode in our new video series for Emulsify. Emulsify 2.x is a new release that embodies our commitment to component-driven design within Drupal. We’ve added Composer and Drush support, as well as open-source Twig functions and many other changes to increase ease-of-use.

In this video, we’re going to get you up and running with Emulsify. This blog post accompanies a tutorial video, which you can find embedded at the end.

Emulsify is, at it’s core, a prototyping tool. At Four Kitchens we also use it as a Drupal 8 theme starter kit. Depending on how you want to use it, the installation steps will vary. I’ll quickly go over how to install and use Emulsify as a stand alone prototyping tool, then I’ll show you how we use it to theme Drupal 8 sites.

Emulsify Standalone

Installing Emulsify core as a stand alone tool is a simple process with Composer and NPM (or Yarn).

  1. composer create-project fourkitchens/emulsify --stability dev --no-interaction emulsify
  2. cd emulsify
  3. yarn install (or npm install, if you don’t have yarn installed)

Once the installation process is complete, you can start it with either npm start or yarn start:

  1. yarn start

Once it’s up, you can use the either the Local or External links to view the Pattern Lab instance in the browser. (The External link is useful for physical device testing, like on your phone or tablet, but can vary per-machine. So, if you’re using hosted fonts, you might have to add a bunch of IPs to your account to accommodate all of your developers.)

The start process runs all of the build and watch commands. So once it’s up, all of your changes are instantly reflected in the browser.

I can add additional colors to the _color-vars.scss file, Edit the card.yml example data, or even update the 01-card.twig file to modify the structure of the card component.

That’s really all there is to using Emulsify as a prototyping tool. You can quickly build out your components using component-driven design without having to have a full web server, and site, up and running.

Emulsify in a Composer-Based Drupal 8 Installation

It’s general best practice to install Drupal 8 via Composer, and that’s what we do at Four Kitchens. So, we’ve built Emulsify 2 to work great in that environment. I won’t cover the details of installing Drupal via Composer since that’s out of scope for this video, and there are videos that cover that already. Instead, I’ll quickly run through that process, and then come back and walk through the details of how to install Emulsify in a Composer-based Drupal 8 site.

Okay, I’ve got a fresh Drupal 8 site installed. Let’s install Emulsify alongside it.

From the project root, we’ll run the composer require command:

  • composer require fourkitchens/emulsify

Next, we’ll enable Emulsify and its dependencies:

  • cd web
  • drush en emulsify components unified_twig_ext -y

At this point, we highly recommend you use the Drush script that comes with Emulsify to create a custom clone of Emulsify for your actual production site. The reason is that any change you make to Emulsify core will be overwritten when you update Emulsify, and there’s currently no real good way to create a child theme of a component-based, Pattern Lab -powered, Drupal theme. So, the Drush script simply creates a clone of Emulsify and makes the file renaming process into a simple script.

We have another video covering the Drush script, so definitely watch that for all of the details. For this video though, I’ll just use emulsify core, since I’m not going to make any customizations.

  • cd web/themes/contrib/emulsify/ (If you do create a clone with the drush script, you’ll cd web/themes/custom/THEME_NAME/)
  • yarn install

  • yarn start

Now we have our Pattern Lab instance up and running, accessible at the links provided.

We can also head over to the “Appearance” page on our site, and set our theme as the default. When we do that, and go back to the homepage, it looks all boring and gray, but that’s just because we haven’t started doing any actual theming yet.

At this point, the theme is installed, and you’re ready to create your components and make your site look beautiful!

[embedded content]

Thanks for following our Emulsify 2.x tutorials. Miss a post? Read the full series is here.

Pt 1: Installing Emulsify | Pt 2: Creating your Emulsify 2.0 Starter Kit with Drush | Pt 3: BEM Twig Function | Pt 4: DRY Twig Approach | Pt 5: Building a Full Site Header in Drupal

Just need the videos? Watch them all on our channel.

Download Emulsify

Web Chef Brian Lewis
Brian Lewis

Brian Lewis is a frontend engineer at Four Kitchens, and is passionate about sharing knowledge and learning new tools and techniques.

Nov 16 2016
Nov 16
November 16th, 2016

Speed Up Migration Development

One of the things that Drupal developers do for clients is content migration. This process uses hours of development time and often has one developer dedicated to it for the first half of the project. In the end, the completeness of the migration depends partly on how much time your client is willing to spend on building out migration for each piece of their content and settings. If you’ve come here, you probably want to learn how to speed up your migration development so you can move on to more fun aspects of a project.

The Challenge

Our client, the NYU Wagner Graduate School of Public Service was no exception when they decided to move to Drupal 8. Since our client had 65 content types and 84 vocabularies to weed through, our challenge was to build all those migrations into their budget and schedule.

The Proposed Solution

Since this was one of our first Drupal 8 sites, I was the first to dig my hands into the migration system. I was particularly stoked in the fact that everything in Drupal 8 is considered to be an entity. This opened up a bunch of possibilities. Also, the new automated migration system—Migrate Drupal—that came with core was particularly intriguing. In fact, our client had started down the path of using Migrate Drupal to upgrade their site to D8. Given they had field collections, entity references, and the fact that the Migrate Drupal module was still very much experimental for Drupal 7 upgrades, this didn’t pan out with a complete migration of their data.

The proposed solution was to use the --configure-only method on the drush tool migrate-upgrade. Doing so would build out templated upgrade configurations that would move all data from Drupal 7 or Drupal 6 to Drupal 8. The added bonus is that you can use that as a starting point and modify them from there.

Migration in Drupal 7 vs Drupal 8

Since we have the 100 mile high view of what the end game is, lets talk a little about why and how this works. In Drupal 7 Migrations are strictly class-based. You can see an example of a Drupal 7 migration in the Migrate Example module. The structure of the migration tends to be one big blob of logic (broken up by class functions of course) around a single migration. Here are the parts:

  • Class Constructor: where you define your source, destination, and field mapping
  • Prepare: a function where you do all your data processing

In Drupal 8, the concept of a migration has been abstracted out into the various parts that makes them reusable and feel more like “building with blocks” approach. You can find an example inside the Migrate Plus module. Here are the parts:

  • Source Plugins: a class defining the query, initial data alteration, keys, and fields provided by the source
  • Destination Plugins: a class defining how to store the data received in Drupal 8
  • Process Plugins: a class defining how to transform data from the source to something that can be used by the destination or other process plugins; you can find a full list of what comes with core in Migrate’s documenation
  • Migration Configuration: a configuration file that brings the configuration of all the source, destination, and process plugins to make a migration

Now yall might have noticed I left out hook_prepare_row. Granted, this is still available. It was also a key way many people used to manipulate data across several source fields that behaved the same. With the ideal of process plugins, you can now abstract out that functionality and use it in your field mapping.

How “Migrate Drupal” Makes the Process Better

There are tons of reasons to use Migrate Drupal to start your migration.

It builds a migration from your Drupal site

You might have seen above that I mentioned that Migrate Drupal provides a templated set of configurations. This is a product of some very elaborate migration detection classes. This means you will get all the configurations for:

  • content types
  • field setup
  • field configuration
  • various site settings
  • taxonomy vocabularies
  • custom blocks
  • content and their revisions
  • etc…

These will be built specifically for the site you are migrating from. This results in tons of configuration files—my first attempt created over 140 migration YAML files.

It’s hookable

Hookable means that it’s not just a part of core thing and that it’s expandable. That means that contributed modules can provide their own templates for their entities and field types, allowing Migrate Drupal to move over that data too. For example, it is completely possible (and in progress) for the Field Collection module to build in migration templates so that the migration will know how to import a field collection field. Not only that, the plugins provided by the contributed modules can be used in your custom migrations as well.

No initial need for redirection of content

Here’s an interesting one, everything comes over pretty much verbatim. Node IDs, term IDs, etc. are exactly the same. URL aliases come over, too, by default. Theoretically, you could have the same exact site from D7 on D8 if you ported over the theme.

More time to do the alterations the client wants

Since you aren’t spending your time building all the initial source plugins, process plugins, destination plugins, and configurations, you now have more time to alter the migrations to fit the new content model, or work with the new spiffy module like paragraphs.

How-To: Start a Migration with “Migrate Drupal”

Ok so here is the technical part. From here on is a quick How-To that gets you up and going. Things you will need are:

  • a Drupal 6 or 7 site
  • your brand new Drupal 8 site
  • a text editor
  • drush

1. Do a little research and install contrib modules.

We first need to find out if our contrib modules that are installed on our Drupal 6/7 site are available and have a migration component to them in Drupal 8. Once we identify the ones that we can use, go ahead and install them in Drupal 8 so they can help you do the migration. Here are a couple of thoughts:

Is the field structure the same as in Drupal 6/7? The entity destination plugin is a glorified way to say $entity->create($data); $entity->save();. Given this, if you know that on Drupal 6/7 that the field value was, for example…

[
  'value' => 'This is my value.',
  'format' => 'this is my format'
]

…and that it’s the same on Drupal 8, then you can rest easy. The custom field will be migrated over perfectly.

Is there a cckfield process plugin in the Drupal 8 Module for the custom field type? When porting fields, there is an automated process of detecting field types. If the field type you are pulling from equates to a known set of field types by the cckfield migration plugin, it will be used. You can find these in src/Plugin/migrate/cckfield of any given module. The Text core module has an example.

Is there a migration template for your entity or field in the Drupal 8 module? A migration template tells the Drupal Migrate module that there are other migrations that need to be created. In the case of the Text module. you will see one for the teaser length configuration. There can be multiple and look like migrations themselves, but are appended to in such a way to make them special for your site. You can find these in
migration_templates in the module.

Are there source, process, or destination plugins in the Drupal 8 module? These all help you (or the Migrate Drupal module) move content from your old site to your new one. It’s very possible that there are plugins not wired up to be used in an automated way yet, but that doesn’t keep you from using them! Look for them in src/plugin/migrate.

2. Install the contrib migrate modules.

First you must install all the various contributed modules that help you build these configurations and test your migrations. Using your favorite build method, add the following modules to your project:

NOTE: Keep in mind that you will need to be mindful of the version that goes with what version of Drupal Core. Example 8.x-1.x goes with Drupal 8.0.*, 8.x-2.x goes with Drupal 8.1.*, and 8.x-3.x goes with Drupal 8.2.*.

3. Set up the upgrade/migrate databases.

Be sure to give your database an key. The default is ‘upgrade’ for drush migrate-upgrade and ‘migrate’ for drush migrate-import. I personally stick with ‘migrate’ and just be sure to give the custom settings to migrate-upgrade. I use drush migrate-import a ton more than drush migrate-upgrade.

$databases = [
  'default' => [
    'default' => [
      'database' => 'drupal',
      'username' => 'user',
      'password' => 'pass',
      'host' => 'localhost',
      'port' => '',
      'driver' => 'mysql',
      'prefix' => '',
    ],
  ],
  'migrate' => [
    'default' => [
      'database' => 'migrate',
      'username' => 'user',
      'password' => 'pass',
      'host' => 'localhost',
      'port' => '',
      'driver' => 'mysql',
      'prefix' => '',
    ],
  ],
];

4. Export the migration configuration.

First I want to give credit to Mike Ryan for originally documenting this process. Without it, or his help in IRC, you wouldn’t have gotten this article today.

Go ahead and import your Drupal 6/7 database if you aren’t connecting to a live instance in your database settings with your preferred method. Take your pick:

  • drush sql-sync
  • drush sql-drop --database=migrate; gunzip -c /path/to/migrate.sql.gz | drush sqlc --database=migrate

Next run Migrate Upgrade to get your configuration built and stored in the Drupal 8 site.

drush migrate-upgrade --legacy-db-key=migrate --configure-only

Finally store your configuration. I prefer just to stick it in the sync directory created by Drupal 8 (or in my case configure for checking into Git).

drush config-export sync -y

I’m verbose about the directory because we usually have one for local development stored in the local key also. You can leave off the word sync if you only have a single sync directory.

5. Update your migration group with the info for the migration.

This is a quick and simple step. Find migrate_plus.migration_group.migrate_drupal_7.yml or migrate_plus.migration_group.migrate_drupal_6.yml and set the shared configuration. I usually make mine look like this:

langcode: en
status: true
dependencies: {  }
id: migrate_drupal_7
label: 'Import from Drupal 7'
description: 'Migrations originally generated from drush migrate-upgrade --configure-only'
source_type: 'Drupal 7'
module: null
shared_configuration:
  source:
    key: migrate

5. Alter the configuration.

Ok here comes the fun part. You should now have all the configurations to import everything. You could in fact now run drush mi --all and in theory get a complete migration of your old site to your new site in the data sense.

With that said, you will most likely need to make alterations. For example, in my migration we didn’t want all of the filters migrated over. Instead, we wanted to define the filters first, and then use a map to map filters from one type to another. So I did a global find across all the migration files for:

    plugin: migration
    migration: upgrade_d7_filter_format
    source: format

And replaced it with the following:

    plugin: static_map
    source: format
    map:
      php_code: filter_null
      filtered_html: basic_html

Another example of a change you can make is the change of the source plugin. This allows you to change the data you wanted. For example, I extended the node source plugin to add a where-clause so that I could only get data created after a certain time.

namespace Drupalwg_drupal7_migratePluginmigratesource;

use DrupalnodePluginmigratesourced7Node as MigrateD7Node;
use DrupalmigrateRow;

/**
 * Drupal 7 nodes source from database.
 *
 * @MigrateSource(
 *   id = "wg_d7_node",
 *   source_provider = "node"
 * )
 */
class Node extends MigrateD7Node {

  /**
   * {@inheritdoc}
   */
  public function query() {
    $query = parent::query();
    // If we pass in a timestamp... only get things created since then.
    if (isset($this->configuration['highwater'])) {
      $query->condition('n.created', $this->configuration['highwater'], '>=');
    }
    return $query;
  }

}

Lastly, you may want to change the destination configuration. By default, the configuration of the migration will go to a content type with the same name. It may be possible that you changed the name of the content type or are merging several content types together. Simply altering…

destination:
  plugin: 'entity:node'
  default_bundle: page

…to be…

destination:
  plugin: 'entity:node'
  default_bundle: landing_page

…may be something you need to do.

Once you are done altering the migration save the configuration files. You can use the sync directory or if you plan on distributing it in a module, you can use the
config/install folder of you module.

Rebuild your site with the new configuration via your preferred method, or simply run drush config-import sync -y.

6. Migrate the data.

This is the last step. When you are ready, migrate the data either by running each of the migrations individually using --force, run the migration even though other pieces haven’t, use the --execute-dependencies, or just go ahead and go for the gold drush migrate-import --all

Caveats

So finally after you go through all the good news, there are a few valid points that need to be made about the limitations of this method.

IDs are verbatim due to the complexity of dependencies

So this means that the migrations are currently expecting all the nids, tids, fids, and other IDs, to be exactly what they were on Drupal 6 or 7. This causes issues when your client is building new staged data. You have three options in this case:

  1. Alter the node, node_revision, file_managed, taxonomy_term_data, users, and probably some others I’m missing here that house the main entities that entity reference fields will need, so that their keys are something your client will not reach on their current production site while you are developing.
  2. Do not start adding or altering content on Drupal 8 until all migrations are done.
  3. Go through all the migrations and add migration process plugins where an entity is referenced, and then remove the main id from the migration of that entity.

In my case, I went with the first solution because this realization hit me kinda late. Our plan was to migrate now for data so our client would have something to show their stakeholders, and then migrate again later to get the latest data before going live.

There are superfluous migrations

You will always find out that you don’t want to keep the settings verbatim to the Drupal 6 or 7 site. This means you will have to remove that migration and remove it’s dependency from all the other migrations that depend on it. Afterwords, you will need to make sure that that case is covered. I shared an example in this article where we decided to go ahead and configure new filter formats. Another example may be that you don’t even give a crap about the dblog settings from your old Drupal site.

Final Thoughts

For NYU Wagner, we were able to save a ton of time having the migrations built out for us to start with. Just the hours spent on building the field configurations for the majority of the content types that were to stay the same was worth it. It was also a great bridge into “How Do Migrations Work?” We now have a more complete custom migration for our client in a fraction of the time once our feature set was nailed down, than if we were to go build out the migrations one at a time. Happy migrating.

Allan Chappell
Allan Chappell

Allan brings technological know-how and grounds it with some simple country living. His interests include DevOps, animal husbandry (raising rabbits and chickens), hiking, and automated testing.

Jul 18 2012
Jul 18

Introduction

There are times when you need to build a custom block that a site builder can utilize in various places on a page. Drupal 7 provides several hooks that allow you to accomplish this goal:

For this tutorial, we will be using the first four hooks above to build a custom block that will contain WYSIWYG markup and an uploaded picture file. This custom block will be built within a module called custom_block.

Making the Block Selectable

In order to use the block, we have to make it selectable in the block administration menu. To accomplish this, we will use hook_block_info(). Inside this hook, you can assign multiple blocks to your returned array. We will only be adding one block in this tutorial. Add the following code to your module (remember to swap out ‘custom_block’ with the name of your module):

/**
 * Implements hook_block_info().
 */
function custom_block_block_info() {
  $blocks = array();
  $blocks['my_block'] = array(
    'info' => t('My Custom Block'),
  );
 
  return $blocks;
}

After you load this code up in your custom module, you will have the option of selecting and configuring ‘My Custom Block’:


Screenshot of block selection with custom block added.

When you click on configure at this point, you will get the standard block configuration page. We will need additional code in order to enable more block options.

Building the Block Configuration Form

We want to add a WYSIWYG text form for html markup and a file upload widget for our picture file. This can be accomplished within hook_block_configure(). Additionally, we will need two separate form types to pull this off. Inspecting the Form API Reference for Drupal 7, we see that there are two form types that will work perfectly: #text_format and #managed_file.

For the #text_format (WYSIWYG) type form, we need to supply #type, #title, and #default_value. In order for Drupal to remember what you store in this form, you will need to assign a stored variable to #default_value using the variable_get() function.

For the #managed_file (AJAX file upload widget) type form, we need to supply #name, #type, #title, #description, #default_value, #upload_location, and #upload_validators. Again, in order for Drupal to remember the file that we uploaded, #default_value will need to be assigned to the stored file FID variable using the variable_get() function. Additionally, you can specify the subdirectory within your Drupal files directory using #upload_location and allowable file extensions using #upload_validators.

Add the following code to your module:

/**
 * Implements hook_block_configure().
 */
function custom_block_block_configure($delta='') {
  $form = array();
 
  switch($delta) {
    case 'my_block' :
      // Text field form element
      $form['text_body'] = array(
        '#type' => 'text_format',
        '#title' => t('Enter your text here in WYSIWYG format'),
        '#default_value' => variable_get('text_variable', ''),
      );
 
      // File selection form element
      $form['file'] = array(
        '#name' => 'block_image',
        '#type' => 'managed_file',
        '#title' => t('Choose an Image File'),
        '#description' => t('Select an Image for the custom block.  Only *.gif, *.png, *.jpg, and *.jpeg images allowed.'),
        '#default_value' => variable_get('block_image_fid', ''),
        '#upload_location' => 'public://block_image/',
        '#upload_validators' => array(
          'file_validate_extensions' => array('gif png jpg jpeg'),
        ),
      );
      break;
  }
  return $form;
}

Once you’ve added the code to your module, you should have a block configure form that looks like this:


Screenshot of custom block configuration page.

Handling the File Saving

When saving the block, we want to make sure that Drupal will take the necessary steps in order to properly save our text and uploaded file. To accomplish this, we’ll make use of hook_block_save(). This hook is called whenever the ‘Save block’ button is clicked on the block modification form. Two arguments, $delta and $edit, are supplied into hook_block_save(). The $delta variable will contain a string identifier of which block is being configured and $edit will contain an array of submitted block form information.

In order to save our WYSIWYG text, we will use variable_set() function. You’ll need to supply a name and the value as arguments. In our case, the text is captured in the $edit array, specifically within $edit['text_body']['value'].

Saving our uploaded file requires several more steps. First, we need to build a file object for the uploaded file. This is done using the file_load() function and supplying the FID value as the argument. Once the file object is established, the status value needs to be set to FILE_STATUS_PERMANENT. This constant tells Drupal that this file is permanent and should not be deleted. If the file status value is not changed, Drupal assumes that it is temporary and will delete it after the DRUPAL_MAXIMUM_TEMP_FILE_AGE time value is exceeded.

Next, we need to save our file object to the database using the file_save() function. Additionally, we will want to map our block’s usage to the file. To perform this, we first need to build a block object using block_load() while supplying the module name and block name arguments. Then we can set the file usage via the file_usage_add() function. This function passes in the file object, module name, object type associated with the file, and a numeric ID of the object containing the file (in our case, the block BID). Finally, the file FID gets associated with a variable using the variable_set() function, similar to how the text was stored earlier.

Add the following code to your module:

/**
 * Implements hook_block_save().
 */
function custom_block_block_save($delta = '', $edit = array()) {
  switch($delta) {
    case 'my_block' :
      // Saving the WYSIWYG text      
      variable_set('text_variable', $edit['text_body']['value']);
 
      // Saving the file, setting it to a permanent state, setting a FID variable
      $file = file_load($edit['file']);
      $file->status = FILE_STATUS_PERMANENT;
      file_save($file);
      $block = block_load('custom_block', $delta);
      file_usage_add($file, 'custom_block', 'block', $block->bid);
      variable_set('block_image_fid', $file->fid);
      break;
  }
}

After implementing this code, uploading a file, and saving the block, you should have an entry in your file_usage table like this:


Screenshot of file_usage table entry from uploaded file.

Also, there should be an entry in your file_managed table like this:


Screenshot of the file_managed table entry from uploaded file.

Building the Block Output

Now all that is left to do is output the block contents in a meaningful way. To achieve this, we will need to use hook_block_view(). Within this hook all that needs to be done is return a renderable array. To decouple the renderable array construction, we’ll place this logic in a separate function called my_block_view() that simply returns the final renderable array.

Within the my_block_view() function, we retrieve the image file using file_load() and supply it with the stored file FID reference variable. We then check to see if the image file path exists and assign it if present. The image markup is built using theme_image() with an array of attributes getting passed in as the argument. The WYSIWYG text markup variable is retrieved using variable_get(). Finally, the block renderable array is constructed using two sub-arrays ‘image’ and ‘message’.

Add the following code to your module:

/**
 * Implements hook_block_view().
 */
function custom_block_block_view($delta='') {
  $block = array();
 
  switch($delta) {
    case 'my_block' :
      $block['content'] = my_block_view();
      break;
  }
 
  return $block;
}
 
/**
 * Custom function to assemble renderable array for block content.
 * Returns a renderable array with the block content.
 * @return
 *   returns a renderable array of block content.
 */
function my_block_view() {
  $block = array();
 
  // Capture the image file path and form into HTML with attributes
  $image_file = file_load(variable_get('block_image_fid', ''));
  $image_path = '';
 
  if (isset($image_file->uri)) {
    $image_path = $image_file->uri;
  }
 
  $image = theme_image(array(
    'path' => ($image_path),
    'alt' => t('Image description here.'),
    'title' => t('This is our block image.'),
    'attributes' => array('class' => 'class_name'),
  ));
 
  // Capture WYSIWYG text from the variable
  $text = variable_get('text_variable', '');
 
  // Block output in HTML with div wrapper
  $block = array(
    'image' => array(
      '#prefix' => '<div class="class_name">',
      '#type' => 'markup',
      '#markup' => $image,
    ),
    'message' => array(
      '#type' => 'markup',
      '#markup' => $text,
      '#suffix' => '</div>',
    ),
  );
 
  return $block;
}

Now you have all the necessary pieces in place to output the block on your page. Here is the un-styled block output within the ‘Featured’ section:


Screenshot of final custom block output in the featured section of the Drupal site.

Summary

As you have seen, Drupal 7 provides you with several tools to build custom blocks. Using hooks, the Form API, and various storage/retrieval functions, the block construction possibilites are endless. Have suggestions, comments, or better solutions? Let us know!

Jul 06 2012
Jul 06

When the Four Kitchens’ team of web chefs develop a new training course, our guiding principle is: Provide a strong return on investment. You invest the time traveling to the training, attending, and afterwards, practicing the skills acquired. You also invest the energy and effort necessary to develop new skills. You place your trust in the trainers to guide you from where you are now to where you need to be. In return, we invest our time, energy, and best effort in creating training experiences that give you a stronger, more relevant, skillset and the confidence you need to apply it.

We also want you and the training to be the right match, building on your current skillset. Before the event, we send a very specific list of required skills, so that you can be certain that the training you purchased is right for you.

To ensure a valuable return on your investment, we develop our trainings with four essentials in mind.

  1. You leave with skills you need. We are interested in many things. The web chefs’ IRC chat room is a steady stream of links and memes. But when it comes to training, we make sure that the skills we teach are the ones you must have as a web professional. We want the skills you develop to increase your value in the marketplace.
  2. Hands-on experience, in class. Seeing is not doing. We know that the only way to develop a skill is to jump in and do it. We provide a safety net. We approach training as an obstacle course designed to build confidence. Instructions are given and then, you tackle the obstacle. We put the smaller obstacles first so that by the end, you are scaling big walls without breaking a sweat.
  3. Subject matter expertise AND training expertise. Many technical training courses fail because the trainers are not subject matter experts or the subject matter experts are not trainers. We develop trainings as a team, combining expertise in the subject with expertise in the art of training. The finished product is an intellectually satisfying, fun, and valuable day with the web chefs.
  4. Enjoyable, cooperative, encouraging. Training is a community experience. We create an environment where trainees can help each other, receive help from us, and participate in every discussion so that the group builds their skills in a cohesive, connected way. We also have a lot of fun.

Our next training is at DrupalCon Munich. Join us for Responsive Websites: Design and Build for All Devices. Also, keep an eye out for more trainings at BadCamp and DrupalCamp Austin.

Do you need personalized training for your team? Contact us for more information about we help teams become Drupal Experts.

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web