Launching a college news site with Drupal

SUNY Oswego recently switched from a legacy install of Expression Engine to integrating with the rest of our web platform and using Drupal hosted by Acquia to “re-design” our new News site. Hopefully this post will help others thinking of migrating to Drupal and provide some helpful tips if you’re already in the process of doing so.

Our old site

Even though our old news site was in Expression Engine, the power of it being a PHP/MySQL app wasn’t fully utilized.

Now, this is through no fault of our office, it just hasn’t been looked at in a very long time. The old site worked well enough for what we needed so we left it alone until an opportunity presented itself to give it a little update.

As a result, the homepage was consistently static. Photos and images were dumped into one WYSIWYG text blob which meant it was difficult to display teaser photos or do anything that is possible with structured content.

Photo captions were placed at the bottom of the articles, separated from their photos. Tagging had no specific function other than creating a word cloud that kinda looked cool. Categories weren’t maintained and difficult to browse by. Finding a list of just all news ordered by date was a hard task to complete.

 

Moving forward

In order to move forward and utilize Drupal to the fullest extent, we planned out content types, audited our workflow, and looked for areas that could be improved as we moved into Drupal.

Auditing our content types

First we sat down and looked at what kind of content was being created. We had a few different content types that were regularly published. News story, People in action, and Spotlight. All of this content was published using one content type and tags to differentiate them in the system.

For the new news site, we now have the same three as before, plus a new Media mention content type. I separated each piece of content into different content types. This allows us to style the content differently and add different fields depending on what the content type needs. The content types are:

  • News story
    • This is the bread and butter of the news site. Most of the content being entered is a news story about something awesome happening on campus.
  • People in action
    • This content type is for faculty, staff, and students who are doing cool things but it’s not necessarily enough to make an entire story or Spotlight out of it.
  • Spotlight
    • This is for highlighting faculty, staff, and students in an in-depth in a Q&A format.
  • Media mention
    • When a news outlet picks up one of our stories, we log it in this content type and display it on the news site to show how our word of our college is spreading.

Social media sharing

When we looked at our analytics of the old site, it wasn’t surprising to find that a large portion of our traffic was when something was shared to Facebook or Twitter. This made the decision easy to invest some resources into:

  • Making sure that the site is easily sharable
  • Making sure the teasers look good on accounts

Metatag module to the rescue

The Drupal metatag module was a YUGE help in making sure that our teasers looked good when shared on social media. If you’re using Drupal, this module is a no brainer.

Beautiful social media teasers, made possible by content strategy and the metatag module.

Google news sitemap module

Another must-have module is the Google news sitemap module. This ensures your articles and content are in a format that is easily consumable by Google. This allows our news stories to be indexed by Google news.

How our news stories appear in Google news search results
How our news stories appear in Google news search results

Human-centered workflow

Last but certainly not least, massive improvements were made to the editorial experience. Drupal gives a site maintainer/developer a tremendous amount of control over the editorial experience, which I tried to leverage as much as possible to make the site easy to update and maintain.

Tabs for each type of content

I’ve referenced this technique in another post and I can’t recommend it enough for improving the editorial experience. By cloning some administration views, we gave editors a super easy way to look at all content of a certain type. This helps reduce cognitive load, making editors more efficient.

Our news admin content interface
Our news admin content interface

I can’t tell you how many times I’ve logged into Drupal with a task in mind, and by the time I figure out which admin area I need to go to, I’ve forgotten what I was doing. Small shortcuts like this technique can reduce these errors and make people’s lives a little better.

Make common tasks as easy as possible

One of the tasks that our news team needs to do is to switch out our featured story once or twice a week, depending on what is happening on campus. I tried to make this as painless as possible, understanding that our writers are not web people. They’re writers. They shouldn’t need to be a web genius to change the featured news story.

Using a combination of views and nodequeue, I added a column to the “News Stories” content tab that allows our senior editor to simply click a link to make that story the featured story on the home page.

Using Views and Nodequeue, a link was added to our news stories admin list to make switching the featured story as easy as clicking a link

Make content guidelines visible

For each of the different types of content, guidelines were developed to ensure that the content would look great once no matter where it is syndicated or in what context it is used.

To help editors know what character count they are at for the summary, I wrote a small module that counts characters for the summary field. As you can see from the screen shot, even the greatest technical solution is at the whim of people and process.

Syndication

A major feature of this news site is now the ability to syndicate news stories from the news site to academic department and college sites through the use of tags and AJAX.

We worked on a list of tags for editors to use that would allow them to syndicate a piece of content to a particular site based on what it was tagged. When implementing, I ensured the field had auto-complete in the add/edit form so we could reduce the number of slightly duplicate tags (for example, Physics department / Physics Department).

Example of news syndication to the Chemistry department website from our News site.
Example of news syndication to the Chemistry department website from our News site.

In closing: a note on people, process, and technology

As with any technology-related project, there are three components that lead to a successful product. People, process, and technology. People and process are vital, and also the most variable.

Sitting down with people to talk about their process is incredibly helpful when building the technical solutions. Often times when the technical team sits down to discuss process, the people who are closest to the process can’t really nail down what it is. That’s where it’s our job to ask questions and try to figure out how things work. This is absolutely necessary if we’re going to turn around a specific, concrete system that the process fits in.

It’s certainly a challenge and it’s never going to be perfect. People change and processes change. Good communication and trust between teams is the only way to get as close to the ideal as we can.

For this iteration, I think we did a really good job. The site is leaps and bounds better in regards to surfacing the latest goings-on at SUNY Oswego. Also, the editor experience is much better as pointed out by our writers who actually use the system (win!).

Playing around with the Drupal 8 JSON API as a data service

I’m familiar with the concepts of REST but have never done anything in practice before. I decided I wanted to end that and start messing with the JSON API in Drupal 8. It was surprisingly easy to get going with because of the incredible resources out there. In this multi-part series, I’m going to explore how to use Drupal 8 as a data back-end for a React app.

JSON API Drupal Playlist by Mateu Aguiló Bosch

This video series is an excellent job of succinctly explaining how the JSON API can be used to make basic requests to Drupal.

Postman is amazing

In the series, Mateu is using a tool called Postman (free!), that makes managing REST requests really easy. This is amazing for testing out your API before you hook it up to an application.

Using Postman, for requests that require authentication, you can set up basic authentication (which uses your Drupal username and password) for easy testing purposes.

The examples below were all run with Postman. I decided it would most likely be useful to someone if I showed how I POSTed data to a content type using the JSON API.

Potential gotcha
Make sure in your request headers, you are specifying the Content-Type as application/vnd.api+json. This is according to the JSONAPI spec. Not doing so will throw a 422 Response from the server.

My app use case

I’m building a workout routine React application. The main idea is that users have Routines which contain Timed Tasks (could be yoga postures, jumping jacks, whatever). The Routines hold references to the Timed Tasks. The user can create a Routine of Timed Tasks, and then “play” through the routine, which will prompt the user to perform a workout action.

Timed Task content type

  • Title
  • Body
  • Task Setup
    • Minutes
    • Seconds
  • Task Duration
    • Minutes
    • Seconds
  • Task Rest
    • Minutes
    • Seconds

Routine content type

  • Title
  • Body
  • Timed Task entity reference

Now, lets use Postman to test some basic REST calls that I’m sure my React app will have to make.

Examples:

Local site url: http://drupal-8-3-0.dd:8083

Adding TimedTask content

POST -> http://drupal-8-3-0.dd:8083/jsonapi/node/timed_task

Body

{
   "data":{
      "type":"node--timed_task",
      "attributes":{
         "status":true,
         "title":"Half Moon right side",
         "body":{
            "value":"This pose is tough",
            "format":"basic_html"
         },
         "field_setup_minutes":{
            "value":0
         },
         "field_setup_seconds":{
            "value":30
         },
         "field_task_minutes":{
            "value":1
         },
         "field_task_seconds":{
            "value":0
         },
         "field_rest_minutes":{
            "value":0
         },
         "field_rest_seconds":{
            "value":20
         }
      }
   }
}

Response

201 Created

{
  "data": {
    "type": "node--timed_task",
    "id": "ad4a8609-1b98-436c-9efd-fc9fe91ad908",
    "attributes": {
      "nid": 56,
      "uuid": "ad4a8609-1b98-436c-9efd-fc9fe91ad908",
      "vid": 56,
      "langcode": "en",
      "status": true,
      "title": "Half Moon right side",
      "created": 1491878234,
      "changed": 1491878234,
      "promote": false,
      "sticky": false,
      "revision_timestamp": 1491878234,
      "revision_log": null,
      "revision_translation_affected": true,
      "default_langcode": true,
      "content_translation_source": "und",
      "content_translation_outdated": false,
      "path": null,
      "body": {
        "value": "This pose is tough",
        "format": "basic_html",
        "summary": null
      },
      "field_rest_minutes": 0,
      "field_rest_seconds": 20,
      "field_setup_minutes": 0,
      "field_setup_seconds": 30,
      "field_task_minutes": 1,
      "field_task_seconds": 0
    },
    "relationships": {
      "type": {
        "data": {
          "type": "node_type--node_type",
          "id": "d1dd56a7-af44-4c01-931d-b441da7f40dc"
        },
        "links": {
          "self": "http://drupal-8-3-0.dd:8083/jsonapi/node/timed_task/ad4a8609-1b98-436c-9efd-fc9fe91ad908/relationships/type",
          "related": "http://drupal-8-3-0.dd:8083/jsonapi/node/timed_task/ad4a8609-1b98-436c-9efd-fc9fe91ad908/type"
        }
      },
      "uid": {
        "data": {
          "type": "user--user",
          "id": "a552daf1-9feb-47cf-958a-a50329b7d689"
        },
        "links": {
          "self": "http://drupal-8-3-0.dd:8083/jsonapi/node/timed_task/ad4a8609-1b98-436c-9efd-fc9fe91ad908/relationships/uid",
          "related": "http://drupal-8-3-0.dd:8083/jsonapi/node/timed_task/ad4a8609-1b98-436c-9efd-fc9fe91ad908/uid"
        }
      },
      "revision_uid": {
        "data": {
          "type": "user--user",
          "id": "a552daf1-9feb-47cf-958a-a50329b7d689"
        },
        "links": {
          "self": "http://drupal-8-3-0.dd:8083/jsonapi/node/timed_task/ad4a8609-1b98-436c-9efd-fc9fe91ad908/relationships/revision_uid",
          "related": "http://drupal-8-3-0.dd:8083/jsonapi/node/timed_task/ad4a8609-1b98-436c-9efd-fc9fe91ad908/revision_uid"
        }
      }
    },
    "links": {
      "self": "http://drupal-8-3-0.dd:8083/jsonapi/node/timed_task/ad4a8609-1b98-436c-9efd-fc9fe91ad908"
    }
  },
  "links": {
    "self": "http://drupal-8-3-0.dd:8083/jsonapi/node/timed_task"
  }
}

There’s a lot of stuff in there that we don’t need, but this piece is of interest:

"attributes": {
  "nid": 56,
  "uuid": "ad4a8609-1b98-436c-9efd-fc9fe91ad908",
  "vid": 56,
  "langcode": "en",
  "status": true,
  "title": "Half Moon right side",
  "created": 1491878234,
  "changed": 1491878234,
  "promote": false,
  "sticky": false,
  "revision_timestamp": 1491878234,
  "revision_log": null,
  "revision_translation_affected": true,
  "default_langcode": true,
  "content_translation_source": "und",
  "content_translation_outdated": false,
  "path": null,
  "body": {
    "value": "This pose is tough",
    "format": "basic_html",
    "summary": null
  },
  "field_rest_minutes": 0,
  "field_rest_seconds": 20,
  "field_setup_minutes": 0,
  "field_setup_seconds": 30,
  "field_task_minutes": 1,
  "field_task_seconds": 0
}

The core of the TimedTask is structured nicely for me to use in a front end framework like React.

Now, I want to be able to store a collection of these TimedTasks in a collection known as a Routine. A Routine will consist of many TimedTasks that can be rearranged.

I added 3 more Timed Tasks and noted their UUIDs in the response.

  • ad4a8609-1b98-436c-9efd-fc9fe91ad908
  • 390de987-11b2-47b4-a692-3c160bccab0a
  • 760a6c82-b0f1-4b2a-93d4-140061e878a9
  • 6548f256-a518-491a-8bba-d469778a3722

Now, lets add these as entity references to a Routine content type.

POST -> http://drupal-8-3-0.dd:8083/jsonapi/node/routine

Body

{
   "data":{
      "type":"node--routine",
      "attributes":{
         "langcode":"en",
         "status":true,
         "title":"My Yoga Routine :)",
         "body":{
            "value":"<p>This is my morning workout :D</p>\r\n",
            "format":"basic_html",
            "summary":""
         }
      },
      "relationships":{
         "field_timedtasks":{
            "data":[
               {
                  "type":"node--timed_task",
                  "id":"ad4a8609-1b98-436c-9efd-fc9fe91ad908"
               },
               {
                  "type":"node--timed_task",
                  "id":"390de987-11b2-47b4-a692-3c160bccab0a"
               },
               {
                  "type":"node--timed_task",
                  "id":"760a6c82-b0f1-4b2a-93d4-140061e878a9"
               },
               {
                  "type":"node--timed_task",
                  "id":"6548f256-a518-491a-8bba-d469778a3722"
               }
            ]
         }
      }
   }
}

Response

201 Created

{
  "data": {
    "type": "node--routine",
    "id": "5075e4b7-e2d6-49d0-ba65-c0b4b48fc8f1",
    "attributes": {
      "nid": 62,
      "uuid": "5075e4b7-e2d6-49d0-ba65-c0b4b48fc8f1",
      "vid": 62,
      "langcode": "en",
      "status": true,
      "title": "My Yoga Routine :)",
      "created": 1491879173,
      "changed": 1491879173,
      "promote": false,
      "sticky": false,
      "revision_timestamp": 1491879173,
      "revision_log": null,
      "revision_translation_affected": true,
      "default_langcode": true,
      "content_translation_source": "und",
      "content_translation_outdated": false,
      "path": null,
      "body": {
        "value": "<p>This is my morning workout :D</p>\r\n",
        "format": "basic_html",
        "summary": ""
      }
    },
    "relationships": {
      "type": {
        "data": {
          "type": "node_type--node_type",
          "id": "d50564ec-fb5e-4031-866a-38e8ca98dc0c"
        },
        "links": {
          "self": "http://drupal-8-3-0.dd:8083/jsonapi/node/routine/5075e4b7-e2d6-49d0-ba65-c0b4b48fc8f1/relationships/type",
          "related": "http://drupal-8-3-0.dd:8083/jsonapi/node/routine/5075e4b7-e2d6-49d0-ba65-c0b4b48fc8f1/type"
        }
      },
      "uid": {
        "data": {
          "type": "user--user",
          "id": "a552daf1-9feb-47cf-958a-a50329b7d689"
        },
        "links": {
          "self": "http://drupal-8-3-0.dd:8083/jsonapi/node/routine/5075e4b7-e2d6-49d0-ba65-c0b4b48fc8f1/relationships/uid",
          "related": "http://drupal-8-3-0.dd:8083/jsonapi/node/routine/5075e4b7-e2d6-49d0-ba65-c0b4b48fc8f1/uid"
        }
      },
      "revision_uid": {
        "data": {
          "type": "user--user",
          "id": "a552daf1-9feb-47cf-958a-a50329b7d689"
        },
        "links": {
          "self": "http://drupal-8-3-0.dd:8083/jsonapi/node/routine/5075e4b7-e2d6-49d0-ba65-c0b4b48fc8f1/relationships/revision_uid",
          "related": "http://drupal-8-3-0.dd:8083/jsonapi/node/routine/5075e4b7-e2d6-49d0-ba65-c0b4b48fc8f1/revision_uid"
        }
      },
      "field_timedtasks": {
        "data": [
          {
            "type": "node--timed_task",
            "id": "ad4a8609-1b98-436c-9efd-fc9fe91ad908"
          },
          {
            "type": "node--timed_task",
            "id": "390de987-11b2-47b4-a692-3c160bccab0a"
          },
          {
            "type": "node--timed_task",
            "id": "760a6c82-b0f1-4b2a-93d4-140061e878a9"
          },
          {
            "type": "node--timed_task",
            "id": "6548f256-a518-491a-8bba-d469778a3722"
          }
        ],
        "links": {
          "self": "http://drupal-8-3-0.dd:8083/jsonapi/node/routine/5075e4b7-e2d6-49d0-ba65-c0b4b48fc8f1/relationships/field_timedtasks",
          "related": "http://drupal-8-3-0.dd:8083/jsonapi/node/routine/5075e4b7-e2d6-49d0-ba65-c0b4b48fc8f1/field_timedtasks"
        }
      }
    },
    "links": {
      "self": "http://drupal-8-3-0.dd:8083/jsonapi/node/routine/5075e4b7-e2d6-49d0-ba65-c0b4b48fc8f1"
    }
  },
  "links": {
    "self": "http://drupal-8-3-0.dd:8083/jsonapi/node/routine"
  }
}

Using Postman, I helped validate the basic API structure that my React app will use. Next, I’m going to look into how we can hook this up into a React application.

Starting a Vimeo embed at a given timestamp using the JS API

Vimeo by far provides the best hosted video embed service out there by far, in my opinion. This post explains how to change the behavior of a Vimeo embed to skip to a given timestamp when the user plays the video. We’ll use data attributes to store the start time in the HTML.

Why would I want this?

In my situation, the college I work at runs a robust news site that often times includes videos. In that context, we format our videos to include an opening branding screen, then a brief intro paragraph to orient people to the video they are about to watch. This works great for the news site, but what if we come across a situation where we want to skip that intro and go right into the video?

Vimeo Player JavaScript API to the rescue

The Vimeo Player JS API allows developers to easily interact with their Vimeo embeds. The directions on including it are pretty straight forward, they even offer a CDN hosted version to include:

<iframe src="https://player.vimeo.com/video/208129791" width="500" height="281" frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen=""></iframe>

Next, we look for our Vimeo Player in our JavaScript. In my example, our Vimeo videos are put into our site as follows:


<div class="video" data-start-time="5">
  <iframe src="https://player.vimeo.com/video/204427616?color=498957&title=0&byline=0&portrait=0" width="640" height="360" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>
</div>

So, to access this Vimeo embed, in my JavaScript file I do something like:

if ($('div.video').length) {
  
  // Loop through all the videos
  $('div.video').each(function( index, value ) {

    // Grab the Vimeo player already on the page
    var vPlayer = new Vimeo.Player($(this).find('iframe')[0]);

    // Set a flag to determine if the player has been paused yet
    var pause = false;

    vPlayer.on('play', function() {

      // If the player has not yet been paused, start it at
      // the specified time indicated in data-start-time
      if (!pause)
        vPlayer.setCurrentTime($(value).attr('data-start-time'));
      });

    vPlayer.on('pause', function() {
      // Set the pause flag to true. Setting this prevents
      // the player from starting the video at 
      // the 'data-start-time' instead of just 
      // continuing the video where it was paused.
      pause = true;
    });
  });
}

Limitations

There’s nothing to stop people from rewinding back to the beginning and watching the intro if they choose, so if you’re trying to use this method to cut some video, it won’t work.

This also assumes you have the means to add data attributes to your HTML to store your start times. If you don’t have access to your HTML, you might need to rethink your approach.


Worked perfectly for my needs, hope it helps!

Follow up on scalable, maintainable CSS

I wrote down some ramblings about this subject a while back after first reading about it through @mrmrs on Twitter. The idea of single function CSS classes was weird and seemed unnatural at first, but I was very intrigued. I considered this idea around the same time I was realizing that, at work, I basically keep creating variations of the same designs over and over. Thus bloating our CSS file and wasting time developing components that were pretty much previously completed. Then a lightbulb went off. 💡

We were in the middle of a project at work and I decided to try something. I’d create a bunch of “helper” classes that would perform single functions on an HTML element. For instance, .text-center would align the text in the center of the element. .container-800 would create a container that was 800px wide. I created about 20 or 30 of these classes and tried to come up with rules that I routinely use when creating BEM or SMACSS style components.

The result? Incredibly fast iteration! I could piece together these classes in HTML to make interfaces in our CMS so quickly that I couldn’t deny there is some real value in this.

Another result: people who work on my team can no go forth and build awesome interfaces without necessarily needing to know about SCSS, preprocessors, etc. It lowers the barrier for entry and helps groups work together faster.

It makes designer and developers speak the same language. “I think there should be a little more padding on this container” can now be remedied by the designer or the developer by modifying the padding class on the element. If the designer doesn’t know CSS or any of the tooling the developer uses, it is still possible for the designer to change the interface to their liking. This is so powerful.

If you’d like to check out a library that has created tons of these “helper” classes already, look into Tachyons. It’s a wonderful concept and library.

A local Oswego counseling practice gets a website

Heather Rice CounselingMy wife, who is currently in grad school studying mental health counseling, is logging practicum hours with a local practitioner. Their old site was in Wix and had terrible SEO value and wouldn’t properly display on mobile phones. To remedy this, I whipped up a WordPress theme using Bootstrap v3, and gave them a fresh new site to build their practice off of. The site features a blog, which will educate the community on topics relating to mental health. Heather Rice, who offers counseling services in Oswego, NY, now has a simple new website built on WordPress that can grow with her business.

 

SUNY Oswego’s 2014 Annual Report

2014 Annual Report

I’m super proud of what our team at work was able to accomplish in the midst of a Drupal build/migration, as well as our day-to-day tasks. The writing is top-notch and dives deep into the happenings of SUNY Oswego over the past year.

The primary designer of the project was Kelli Ariel, who had a large part in deciding how the site looked. I took the lead on the front-end development. Since I was so involved in the front-end code and design, that’s mostly what this post will be about.

What did this project take to build?

It was about a month of throwing around ideas, designing, coding and Slack conversations. Our process usually involved Kelli coming up with a quick mock-up of what certain parts of the site would look like, then she’d pass those mocks to our Slack channel, some brief discussion would ensue with our team and then I’d code it to work on the web. It worked pretty well for us and allowed us to get a lot done in a short period of time.

Design, development, and content were all intertwined throughout the project. This helped quite a bit. The primary writer, Julie Blissert was very flexible to work with. We were able to get a content structure formed in the early part of the process so design and development could go on, even though the content was still in the process of being written. For example, by structuring the content, I mean the blocks that you can hover over on the site were defined as having:

  • A headline
  • Optional subhead
  • ~325 characters of content on hover

hover-blocks

Without designers, developers, and writers working together to define things like that, a project can get unnecessarily messy.

Development tools:

  • Bower to manage libraries
  • Grunt to build, minify, and concat files to cut down on requests and file size
  • Git for version control

Libraries used:

  • jQuery
  • Waypoints
    • Used to lock in the sticky horizontal navigation
    • Used to fire an event when you hit the bottom of the page. This would then slide out the ‘next’ arrow near the footer.
  • CountUp.js
    • Used in the number panels to count up our statistics
  • Chart.js
    • Used on the Facts and Figures page to build the charts
  • Bootstrap’s grid system
  • Modernizr for feature detection

Fun features

Theme Color Cupola Card

Theme Color

With support for theme-color in the newest versions of Android, it allows web developers to customize their site’s appearance within the Android OS. For example, I was able to change Chrome’s title bar to be in line with our brand colors. I was also able to add our signature cupola icon to the ‘card’ when the user looks at all open apps.

It’s little things like this that are invisible to most users, but adds to the experience.

Sticky Header

On every page, whether you are on a mobile device or a desktop computer, the navigation will ‘stick’ to the top of the view port, allowing users easy navigation to other sections of the site. It seems particularly useful on mobile devices since it can be cumbersome to scroll all the way back to the top of a long page in order to reach the navigation again.

Scrolling Meter

Our web technical lead, Rick Buck pushed for the addition of the scrolling meter, which gives the user a subtle sense of orientation while reading the page. While scrolling down the page, a small green bar will appear under the navigation that shows how far down the page you are. When the bar reaches the end of the screen, you’re at the bottom. This can give users the ability to know where they are on the page with a quick glance.

scrollingmeter

 

 

Using automation tools to deal with rem support in older versions of IE

We all know the wonders and joys of working with older versions of IE, namely < IE9. While I see a lot of sites are kind of throwing their hands up in the air when it comes to legacy IE support, I think its necessary to at least make sure your content is accessible. It’s a little irresponsible to throw up a message saying “Sorry, your browser is old. Download Google Chrome”. At the very least, you could serve them a print style sheet so they could read the content. While IE8 is dying a slow death, it still has a good amount of users.

I’ve been using rems in conjunction with ems now to give myself a consistent spacing unit when coding a website. As with all handy CSS properties, IE8 doesn’t support it so we have to look elsewhere. You could either a) Write pixel fallbacks before each time you use a rem.

h1 {
  font-size: 48px; /* for < IE8 */
  font-size: 3rem; /* for newer browsers */
}

IE will ignore the rem part but will pick up on the

font-size: 48px;

If this works for you that’s fine, but I’d rather have a script automate this process. That’s where grunt and rem_to_px comes in handy. My current grunt task is set up like so:

rem_to_px: {
  options: {
    baseFontSize: 16,
    removeFontFace: true,
  },
  dist: {
    src: ['source/css/style.css'],
    dest: 'public/css/ie/'
  }
},

The task will create a stylesheet where I told it do in

dest

that is the same filename as what is declared in

src

. This stylesheet will only contain rem to pixel substitutions. Then, in my HTML it’s simple to drop in a conditional statement to check if the browser is less than IE8. If so, I’ll include the stylesheet created by the grunt plugin.

<!-- link to the normal style sheet -->
<link rel="stylesheet" href="public/css/style.css">

<!--[if lte IE 8]>
<link rel="stylesheet" href="public/css/ie/style.css">
<!--[endif]>

Thoughts on my 2014

2014 has come to an end and it’s time to look ahead to 2015. 2014 was my first full year in a professional environment. I wrote my first jQuery plugin. This was also the year I really sunk my teeth into CSS preprocessors, automation, and version control to supplement my front end development.

Working for a full year doing web development has been a great experience. I used to think the hardest thing in programming was what to name your variables, but it turns out that – for me – it’s communicating your ideas. Because of this this, I want to blog more in 2015. I think it’ll help me understand my thoughts a little more. The ability to communicate technical ideas clearly to a non-technical audience is a skill I need to sharpen.

I dove into jQuery a little more thanks to videos by Paul Irish. This knowledge allowed me to write my first jQuery plugin. It’s nothing super amazing or technical, but it was a neat exercise in learning something new. Looking at example code of jQuery plugins made me feel uncomfortable at first, but as I stuck with it and kept reading, it started to gel together enough for me to write something that works.

This is the year I started using LESS as my CSS preprocessor of choice. I keep reading a lot about Sass, but haven’t tried it out yet because LESS does everything I need. LESS solves many problems I was tired of dealing with while writing plain CSS – namely lack of variables and mixins. The idea of compiling a few .LESS files into 1 CSS file seemed to make more organizational sense to me, so I never looked back.

Another addition to my workflow in 2014 was embracing front end tooling. I used to have no issue importing 9 non-minified .js files in the footer of a site. I used to FTP all my files manually every time they were updated. I used to download external libraries by just going to the library’s website, downloading the zip, then extracting it to my project. Now, I use grunt tasks to concatenate and minify my scripts and CSS. I use rsync to transfer my project to staging or production servers. I use bower to manage my site’s dependencies, allowing me to quickly download and update external libraries. These things used to seem advanced and scary to learn, but since I’ve gotten to know these tools, I can’t imagine working without them.

Finally, 2014 was the year I started using version control. Git tutorials and YouTube videos really helped me understand how important it is to a developer’s workflow, how it works, and how to use this powerful tool to manage your projects. Gone is the fear that something irreversible will happen to my project. I can rest assured knowing each version is backed up on my local repo and in the hosted repo (I like using BitBucket).

TL;DR – My workflow changed a lot for the better in 2014, and I got the chance to learn more about jQuery plugins (and as a side effect I now understand JavaScript and jQuery better). Looking forward to 2015, I hope to learn as much or more.