Emerson Fabrication Redesign - A Success Story

The redesign of Emerson Fabrication has been a fairly involved project - especially since this was a solo project. But it has been a fun site to work on, mainly because I had the rare opportunity to do exactly what I wanted, within reason, with no real limits or constraints. My boss told me that he wanted a website and that is about all the input I got.

In this case study I am going to attempt to detail, as much as possible, many of the steps I took in building the site. I will start with the general outline and a list of functionality that I wanted the site to have. Then I will detail some of the steps I took to solve various issues encountered and also go over some of the contributed modules I used and explain how some of them work together to provide that functionality.

Overview

Emerson Fabrication Group, LLC. designs and fabricates various products from structural steel, steel plates and other material. The company does a lot of work for the Water Treatment field fabricating all manner of clarifier tanks, raker arms and other large scale steel products. Many of the companies customers are in fact other companies who engineer, sell, and install steel fabricated structures to counties, municipalities, etc. So a customer might have 3 or 4 different fabrication jobs going on at the same time.

I happen to work at Emerson Fabrication. I operate the computer controlled (CNC) plasma burning machine. The company's President, Ed Emerson, knew that I built websites and told me one day that the company needed one. I was in the lucky (depending on your perspective) position of being able to build the website with no real direction from above. The downside to it was that I had no real direction from above! There was also an upside. Being an employee of the company, I understand the interaction with the customers. I believe I know what the customers will like. And I am in a good position to understand some of the ways a website could benefit both customer as well as Emerson Employees.

I had in mind a nice professional look and decided on using a customized Newsflash theme from RoopleTheme. The site would serve three unique purposes.

The Anonymous Viewer Experience - Emerson Fabrication's outwardly facing internet, providing infomation about the company, services offered, location and contact data.
The Customer Experience - A customer portal, allowing customers to log in and track their job through the fabrication process.
The Employee Experience - A number of various pages and functions that are helpful to Emerson Fabrication employees. Intranet functionality would be built up over time as problems/solutions are found.

The Anonymous Viewer Experience

From an anonymous viewers perspective, it is a normal brochure type site. You have all the usual 'About Us', 'Services', 'Contact', and 'Career Opportunities' pages. The 'About Us' page is a Views module page display. I created a "Staff" content-type with 3 fields; Job Title, Email, and Picture. The node title serves as the persons name and the body field will serve any future biographical requirements.


Main Navigation

The view displays the Node Title, Picture, and Job Title as a grid and the top paragraph comes from the header field of the view. The Shadowbox module provides a nice fade in and zoom transition that happens when one clicks on one of the pictures.

The "Our People" page is also a view and was built the same way. I created a content-type called "Photos" that I use to upload various images to the system. I added a checkbox field to the photo content-type that has a description of "Add to Our People Gallery". Basically what this does is it allows me to use Views to select all photo content-type nodes with this option checked and build a page view called "Our People." I plan on expanding on this in the future to include photo categories so that all photos can be categorized into various galleries.

The Contact page is a simple site contact form and all other public internet pages are static pages.

The Customer Experience

The Customer Experience is where all the fun is. To explain it simply, I wanted the customer to be able to log in and learn all there is to know about their job order. To be able to view the job as it is being fabricated, and to get an update of the progress of their complete order. Just about all of the items I have listed below in the "Website Requirements" section have to do with the customer section of the site.

The Employee Experience

The main thing I had to keep in mind while planning this design was the fact that it needed to be fairly user friendly. The day to day operation of the site, things like adding users, uploading pictures, creating new customers and jobs, etc. would be turned over to Johnny, the company's AutoCAD engineer and CNC programmer. While Johnny is technically proficient when it comes to this sort of thing, I still wanted to make things as intuitive to use as possible and keep any employee training to a minimum.

Website Requirements

While blue-skying these ideas I came up with the following list of requirements that I thought should be met.

*note: you can click on the following and go straight to that section or just read straight through.

General Access...

Job Detail pages should...

I wanted to...

I needed to...


1. Customers should be able to log in and view the status of their jobs.
No problem. Drupal is made for that sort of thing.


2. When a user (customer) logs in, they should be taken to their companies own custom page with a list of their current jobs.

Because many of the fabricated projects require several people to be involved this took a little more thought than I had at first anticipated. In an earlier version of this site built on Drupal version 5, I handled this situation by using roles. Each customer that got an account was put into a certain role. The role was a company name. For instance, if there was a company called ACME, Inc. then a role would be created called acme. And a user account would be created for access to that companies page and all their jobs. Then every employee of ACME that needed to log into the website would be given those credentials. Upon login, the login destination module would redirect the user to a particular company "homepage" based on that users role. Not a very good solution, I know.

Beause of some of the new features I planned on integrating into the redesign, it was absolutely necessary that every user had a unique username and password. And that another way was found to tie that user to the company he/she works for.

I found the Content Profile module and after a little thought came up with a good solution. The Content Profile module lets you use other content-types as profile nodes. So you can use CCK and add new fields to the profile content-type and link it all together so that the new profile fields would show on the user account page. I added two fields to profile, a text field for fullname and a nodereference field called "company". The nodereference field is a select list generated from the "Customer" content-type.

I then set up the user registration and activation so that a person could request an account but first had to be approved by Johnny, the Site Admin. So Johnny receives an email that says, john_user has requested an account. He then goes to the user account page and clicks to edit john_user's account. First thing he does is click on the "profile" tab and drops down the company select list and picks the company that he knows john_user works for. Then he activates the account and john_user is sent an email telling him he can now log in.

<?php
global $user;
  if (
$user->uid == 1) {
   
// Redirect the Administrator
   
return 'emfab';
  } elseif (
in_array('customer', $user->roles)) {
 
// Redirect customers to their page
 
$contentprofile = content_profile_load('profile', $user->uid);
 
//test for a value for variable company
 
$customer_homepage = $contentprofile->field_profile_company[0]['nid'];
  return
'node/' . $customer_homepage;
  } elseif (
in_array('staff', $user->roles)) {
    return
'emfab';
  } else {
    return
'home';
  }
?>
Login Destination Code

With the Login Destination module you can set up rules to redirect users based on pretty much anything. Immediately after logging in the code runs to determine who the user is, $user->uid is tested and if it's user 1, he is redirect to the 'admin' page. If the user is in the customer role, the value of Company from their user profile is used to generate a path for the redirection. The value in the 'Company' field is simply a node id that happens to point a customer node (content-type customer).

return 'node/' . $customer_homepage;

The above code will redirect to a path like 'emersonfab.com/node/212'.

This effectively redirects the user to his Company homepage. If the user is a member of the "Staff" role (Emerson Fabrication Employee), they are redirected to the Emerson Fabrication administration page.

I expect to have a managable amount of users so I picked this technique instead of something like say, Organic Groups.

Notes on profile setup and login destination

  1. install content profile
  2. add Company field (nodereference to customer content-type) to content-type named profile
  3. set profile to be used for user pages
  4. auto-nodetitles to [type-name]-[field_profile_fullname-formatted] will give "profile-Tim Wooten". Very useful in content listings.
  5. url automated alias settings for profile node set to "profile/[field_profile_fullname-formatted] provides a nice path."


3. Customer pages should list both current and past jobs for that particular customer, these should link to the correct Job Detail page.

Views block displays handle this chore. I created two block displays. One lists all current jobs and the other lists jobs that have already shipped.

Job nodes have a node reference pointing to the customer node they belong to. I set up a view to display a list of reverse node references in a block. The block with Node Titles and Project Description fields would only appear in the content bottom region of the Customer page so the node id could be used as an argument to views. Basically, the view generates a list of all job nodes that reference the current page through the nodereference field named 'customer'.

Thanks to FiNeX for this comment and providing the following steps.

  1. Add the node_refence as argument
  2. Set "Action to take if argument is not present:" to "Provide default argument"
  3. Set "Default argument type:" to "Node ID from URL"
  4. Set "Basic Validation"

With Views you can add as many "block" displays as you want so on the Job Detail pages I display an "Other Jobs" block in the sidebar and on the Customers page, I have another block display at the bottom of the main content.


4. List as much information as possible concerning each particular job.

Each fabrication job goes through basically the same number of steps from beginning to end so there were a number of things to track.

  1. Acquiring the blueprints and all the various approved assembly and fabrication drawings.
  2. Creating the CNC program files for the structural and plate parts from the above documents.
  3. Ordering and receiving raw material.
  4. Shop Phase One
    1. Sawing
    2. CNC Plasma Burning
    3. Drilling
    4. Forming
    5. Fitting
    6. First Inspection
  5. Shop Phase Two
    1. Galvanizing (depending on job)
    2. Welding
    3. Cleaning
    4. Sandblasting
    5. Assembly (depending on job)
    6. Painting
    7. Final Inspection
  6. Shipping

The Job Detail page is visually broken up into several sections. Customer name, Job Number, Purchase Order Numbers, etc. are in the top left section of the page and the customers logo is in the top right section of the page.

The only tricky part here was in retrieving the customer logo because the field 'field_customer_logo' is used in the 'customer' content-type and not 'job'. I spent a little too long trying to get this to work in various other ways before I had a revelation.... use Drupals views_embed_view() function to print the output of a view. So I created a view to accept an argument (the node ID) and print the logo field. Then I put the following code where I wanted the logo to show up and there ya go! I'm fairly certain there is a better way to do this, feel free to leave a comment.

  <div id="customer_logo">
  <?php
   
// call the customer_logo view and send it the customers id
    // print the customers logo
   
$customer_nid = $node->field_customer_name[0]['nid'];
   
$viewName = 'customer_logo';
    print
views_embed_view($viewName, 'Default', $customer_nid);
 
?>

  </div>

I created a new content-type named 'job' to enter the job information into the system. And a content-type named 'job_image' lets me upload images and associate them to the correct job.

My content-types

Job - Very detailed content-type with lots of information about each job.
Job Image - A content-type with an ImageField utilizing the FUpload module to allow uploading of multiple images of ordered parts in various phases of the fabrication process.

I used nodereference fields to associate each job to the appropriate customer. I used two check box fields to act as flags to determine if the 'Assembly' and 'Galvanizing' section of the page should be turned on. Some jobs require assembly of parts with picture proof that everything fits together as ordered. In this case a lot of close-up pictures are taken from different angles. If the job requires it, the box is checked and a section for these pictures is created on the job page. The same method is used for the galvanizing section.

For all the fabrication steps under Phase One and Phase Two (sawing, drilling, welding, etc.) I used a CCK text select field with options ranging from 0 to 100 in 10 point increments. The value of this field is what determines the value on the jQuery progress indicator.

For the display of the Job Detail page I made a new template called 'node-job.tpl.php'. Setting up the node-job.tpl.php was a fairly painless process. I simply copied the default node.tpl.php and added my own customizations. Things like calling drupal_add_js() to include the code to make the jQuery progress bars work. node-job.tpl.php is where all the progress bar divs are printed, the view is called to display customer logos, and all the job image grids are printed.


5. Have a block displaying the customers other jobs for quick navigation. (Mini Beauty Tips Tutorial)

The solution to this was like the solution to number 3 above, create a views block to list job nodes, the difference being that instead of setting "Default argument type:" to "Node ID from URL" as I did for the block on the customer page, I set "Default argument type:" to "PHP Code" and used the following;

if ( arg(0) == 'node' && is_numeric(arg(1)) && ! arg(2) ) {
    $node = node_load(arg(1));
    $customer_nid = $node->field_customer_name[0]['nid'];
    return $customer_nid;
  }

This returns the value of field_customer_name[0]['nid'] which is the nodereference field that associates each job to the customer it belongs to. So basically it tell views to limit the results to only jobs belonging to the same customer.


Views Argument


View Fields for Beauty Tips Display


Job data showing in a Beauty Tip

I wanted to use the jQuery Beauty Tips module to help me convey more information to the user while saving space on the page.

The Beauty Tips jQuery plugin was created by Jeff Robbins of Lullabot fame and has a project page at this link.

You have probably seen the video about how to enhance drupal views with Beauty Tips at this link. I came up with the following method while thinking about the Views Customfield module and other potential uses for it. By using the Views Customfield and Beauty Tips modules you can easily integrate Beauty Tips into your Views output.

The final outcome is what you see in the image above and to the right. When the user rolls over the PO# (Purchase Order), the Beauty Tip function activates and displays data from the associated job.

This was very easy and quick to set up. I first added all the fields that I wanted to show in the Beauty Tip. I'm including a screenshot here to give you an idea of what I mean. For each field I checked 'Exclude from display'. I then added a 'Customfield: Markup' field and also checked 'Exclude from display' on that one, then I checked the box that says 'Rewrite the output of this field'. The help below the input box says 'If checked, you can alter the output of this field by specifying a string of text with replacement tokens that can use any existing field output'.

Following is what I put in the box to rewrite the output with. Each field has available to it the value of all the fields listed before it. This was a little hard to explain so I got a screenshot of that as well.

<h3>[field_project_description_value_1]</h3>
<strong>Ship Date:</strong> [field_ship_date_value]</strong>
<hr>
<strong>Programming</strong><br>
Plate: [field_plate_programming_status_value]% Complete<br>
Structural: [field_structural_programming_st_value]% Complete<br>
<strong>Phase 1</strong><br>
Burning: [field_burn_status_value]% Complete<br>
Drilling: [field_drill_status_value]% Complete<br>
Sawing: [field_saw_status_value]% Complete<br>
Fitting: [field_fit_status_value]% Complete<br>
<strong>Phase 2</strong><br>
Forming: [field_form_status_value]% Complete<br>
Welding: [field_weld_status_value]% Complete<br>
Cleaning: [field_clean_status_value]% Complete<br>
Sandblasting: [field_sand_blast_status_value]% Complete<br>
Assembly: [field_assembly_status_value]% Complete<br>
Painting: [field_paint_status_value]% Complete

So what I did above was just like the help said 'specified a string of text with replacement tokens'. It's one long string of html containing values from the fields I previously excluded from display. This is what the Markup field will be rewritten with.

Next I added two fields that I would NOT exclude from display. These two fields print in the block. I added the PO# (field_po_number) field to attach the beauty tip function to and the Project Description (field_project_description) field because I wanted to have a description along with the PO number. On the field_po_number field I checked 'Rewrite the output of this field', and entered the following;

<span class='btytp' title='[markup]'>[field_po_number_value]</span>

Because the Customfield: Markup comes before the PO field, the PO field has access to its value as well as the values of all the fields above it. Setting the title attribute to [markup] here means that the value of title is all the html being output by that field (which is excluded from display so will not show up anywhere else).

[field_po_number_value] is the value of field_po_number so that is what will be printed to the screen. So we're putting all that custom markup code into the 'title' attribute of the span tag and giving it CSS class 'btytp'. Next under 'Basic Settings' I clicked on Header and entered the following and set it as PHP code.

<?php
drupal_add_js
(
 
'$(document).ready(function()
  {
    $(".btytp").bt();
  }); '
,
   
'inline');
?>

This is the code that attaches the Beauty Tip function to any element on the page with a class of 'btytp'. By default Beauty Tips will use whatever is in the title as the text to print in the ballon. The key part to making this work so easily is using the Customfield to build the html that makes up the title of the span that is printed.

I would love get some feedback on using this technique for integrating Beauty Tips with Views.


6. Graphical status indicator showing the percentage of completion for the different phases of the job.

I wanted to use something eye catching to display the percentage for each step of the fabrication process. I found jQuery Progress Bar and it works nicely.

$(document).ready(function() {
$("#plate_status").progressBar({
  barImage: '/sites/all/themes/newsflash/jquery.progressbar/images/progressbg_red.gif'
} );
$("#final_inspection_status").progressBar({
  barImage: '/sites/all/themes/newsflash/jquery.progressbar/images/progressbg_red.gif'
} );
...
...more of the same cut to save space...
});

The above code attaches the progress bar function to the correct elements.

  <div class="progress_bar_box">
    <div class="bar_box">
      <h3 class="progress_field_label">Plate Programming</h3>
        <span class="progressBar" id="plate_status">
  <?php print $node->field_plate_programming_status[0]['view']; ?></span>
    </div>
    <div class="bar_box">
      <h3 class="progress_field_label">Structural Programming</h3>
        <span class="progressBar" id="struct_status">
  <?php print $node->field_structural_programming_st[0]['view']; ?></span>
    </div>
  </div>
</div>
</fieldset>
<div class="clr"></div>

And this is the element that will be turned into a fancy progress bar.

7. Lots of pictures so the customer can see actual images of their order throughout the process, from fabrication to shipment.

Pictures are taken at various stages in the fabrication process and uploaded through the “Job Image” content-type. I used the Imagefield and Filefield modules to create the Job Image upload page. I used the Imagecache module and created several preset image sizes that I use for display throughout the site. I used CCK's Node Reference to link each job image to the job it belongs to. Taxonomy is used to tag each image and specify which phase it belongs in; "phase 1", "assembly", "shipping", etc.

This design allowed me to use the Views module to generate the grid of images. The Views 2 module has this cool concept of "Displays". You can set up many different Displays for each view. So I set up a new view called "Job Phase Images" and created 4 display blocks, one each for phases 1 and 2, assembly, galvanizing, and shipping. The view accepts 2 arguments, the Node:Nid and a Taxonomy id. In my custom node-job.tpl.php I put the following code to generate the views exactly where I wanted them to show.

<div class='phase_image_box'>
    <?php
    $viewName
= 'job_phase_image';
   
$display_id = 'block_1'; # This is named 'Phase 1 Block' in the
    // 'Job Phase Image' view
    //  Need this to select the right images based on Node:Nid,
    //  this is a node reference to the job!
   
$parent_job = $node->nid;
   
$phase = "17"; # 17 is taxonomy id for phase 1
   
print views_embed_view($viewName, $display_id, $parent_job, $phase);
   
?>

  </div>

The above snippet will display a grid of images from phase 1 because I am passing "17" as an argument to the view. 17 is the taxonomy id for phase 1 that the images were tagged with. I use the exact same code to display the phase 2 images with one minor change, I pass "18" as the $phase argument which is the taxonomy id for phase 2.


Example of a Job Detail page with images


Assembly, To Galvanizer and Shipping Images

Because the display area allows room for a greater number of columns I set up other displays for galvanizing and shipping to show 6 columns instead of 4.


8. Have a way for the customer to ask questions or give feedback from the Job Detail page.

The idea here was that I wanted to provide a way for the project engineers and Emerson Fabrication project managers to easily communicate and collaborate with all the other members involved in the project. I was able to make the default comment functionality work quite well, especially once I added the Watcher module and the ability to upload files.

The Watcher module sends notification e-mails to customers about new comments on pages where they have commented, effectively keeping everyone involved and informed. The structure of the comments section actually creates sort of an historical overview or timeline of changes per job.


9. and be able to easily attach or upload files in various formats.

I also integrated the contributed Comment Upload module into the Job content-type so that project documents such as blueprint plan revisions can be attached to the comments.


Job comments


Comment Uploads


10. I wanted to create a page that showed all jobs in a list sorted by their shipment deadline date.


jQuery Countdown in Action


Views Detail

I had known about the jQuery Countdown module for a while but it wasn't until I found the Views Custom Field module that it dawned upon me how easily this could be set up to work with views output.

I talked about the Views Custom Field module in number 5 above where I was setting up Beauty Tips to work with views output. There is a slight difference, however, between the way I configured customfield for Beauty Tips and the way I configured it for the jQuery Countdown. For the Beauty Tips I added a field of type "Customfield: Markup". The jQuery Countdown will require just a small bit of processing so I added a "Customfield: PHP Code" field here. It allows usage of custom PHP code (with access to view's database result).

The countdown function itself would be provided by the jQuery Countdown module. The description on the project page is

Provides the jQuery Countdown plugin by Keith Wood, along with a simple API function (jquery_countdown_add) to easily add countdown/countup timer elements to the page.

How did I put all these modules to work to get the result you see in the image to the right? Simple, I first created the view called "All Jobs" and added a couple fields, "Title", "Project Description", "Ship Date", and "Customfield: PHP Code".

The part to know here is to check 'Exclude from display' on the "Ship Date" field. That will prevent the date field from displaying but will still load that value for use by the Customfield. For the Customfield PHP Code I put the following;

<?php
$node
= node_load($data->nid);
$node_num = $data->nid;

if (
$node->field_ship_date[0]['value']) {
 
$ship_on_date = date("F d Y", strtotime($node->field_ship_date[0]['value']));
  print
"<div id=countdown_box" . $node_num . " class=countdown></div>";
 
jquery_countdown_add("#countdown_box$node_num", array('until' => $ship_on_date,
            
'format' => 'OWD', 'description' => "until shipment"));
}
?>

The help text under the value textarea states "$data: contains the retrieved record from the database (e.g. $data->nid)." So as you can see everytime this code is run for each row of data I'm loading the node of the current record to get the value of the $node->field_ship_date field.

The jquery_countdown_add() function is easy to understand. For my usage I simply provided the $ship_date value, the format I wanted which was Months, Weeks, and Days (OWD), and a string of descriptive text that I wanted to appear under the countdown (until shipment).

You should note that a unique CSS ID is necessary for each countdown div. So knowing that every Node ID would be unique, I concatenated the NID to the id=countdown_box and called the jquery_countdown_add function with "#countdown_box$node_num" as the div to target. I also added a class=countdown to make theming a bit easier. This was the "small bit of processing" part I was talking about earlier and the reason I chose "PHP Code" instead of "Markup" for the type of customfield to add.

I was really blown away when I first made this work. Views Customfield is very powerful in my opinion. Being able to include a bit of PHP in the view output really makes for some interesting possibilities.

By the way, keep this in mind if you try this technique, the countdown display would NOT appear in the Views "Live Preview" area. I had to give my view a page display and a URL and visit that URL to see my work.


11. I wanted to provide a quick and easy way for users to give feedback about the new site.

One of the best ways to make forms in Drupal is with the Webform module. It's highly configurable and easy to use. On most websites you have to look around for a contact or feedback link, click on that link, then possibly fill out a form with your name, email, comment, etc. I wanted to make this process much easier so created a webform with only one field, "feedback". Then I installed the Webform Block module and put the feedback webform in a block. I configured the block to show on every page but only to logged in users. I also installed the Ajax module to ajaxify the feedback form.

So when someone wants to send feedback all they have to do is start typing in the feedback box, then click the 'Send Feedback' button when they are finished. An email is sent directly to me with all the pertinent information such as username, message, etc.


12. Make it easy.

The company has a person who is responsible for approving new users and configuring their account. I needed this person to be able to do that without my assistance but with certain restrictions. For instance, he needs to be able to assign new customers to the "customer" or "staff" role but not to the "admin" role.

Also, by default, if you allow a user to have user admininstration priviledges, then they can edit (DELETE) user #1. Not really what you want to happen.

I found the Role Assign module and it handles the first issue by creating a new permission, "assign roles", that lets a trusted user assign others to roles that you have approved for assignment.

The User Protect module takes care of the second issue. It allows you to set up all sorts of user protections. I used it to protect user #1 from any edits or deletions.

What's Left

There are several really useful modules that I also have installed but did not really talk about here, I figured it was long enough as it is. Anyway, here are a few in no special order or preference.

Focus
Automatic Nodetitles
Shadowbox
Token - Very useful!
Views Rotator
Weight

I still have a bit of work to do with this site. Obviously from an anonymous visitors viewpoint things could be prettier, but you know there are only so many hours in a day! I plan on using the Dynamic Display Block module to fancy up the 'Services' page.

The 'Our People' page could use some love as well. Most likely I will add some tagging capabilities and set up some categories. That will make it easier to filter the display.

Finally

I have been working with Drupal for a little over 3 years now and the more I do, the more I love it. I'm sure there are probably other, even better ways to do these things. And I know I need to make things prettier, just ask my wife, she'll tell you. I tend to be better at making things function, but anyway, I welcome any feedback from the community. And I'd especially like to know what you think about my Beauty Tips method that I detailed in number 5 above. I think it's a much easier method of using Beauty Tips with Views.

very impressive and staright forward

Hi, thanks for sharing your development process the site is very functional and i appreiciate the information you have provided here. people like you make Drupal a sustainable project.
thanks again

Thanks for sharing

Thank you for sharing. Sometimes its a bit difficult to get the different modules working together (unless you truly understand the system - which I think you do). With regards to making things look better, I would suggest starting with a small pallet of colors - even gray scale...and increase the constrast among different sections of the site. For example, make the menus stand out from what follows below. Do this both by using different colors, font sizes, and white space. It won't be perfect the first time, so you'll need to go over it a couple of times, just like an artist applying large brush strokes to define a painting, then smaller ones to refine it. Hope my suggestions were able to help you...even a fraction of how much your writeup is helping me!

Contact Me

Feel free to contact me.

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.