Data driven websites using Macaw + Publisher Demo

Tutorial: Turning Macaw into a data-driven multi-language Publisher website

I’ve decided to show you how to change Macaw example website into Publisher powered website with data comming from a data source rather being hard-coded in the Macaw HTML export. Together we’ll create a website in two languages with one design/code base:

I’ll setup a fresh new project for you to follow, but you can already download the end result including the MCW file from it’s repository and study the example file as you read the tutorial.

Publisher is a small flat PHP engine I wrote and I open-sourced it under MIT licence.

Enjoy :)

Table of Contents:

  1. Get ready: Download components
  2. Setup a webserver (optional)
  3. Setup the Publisher project
  4. Publish Macaw as HTML/CSS/JS
  5. Configure the app
  6. Changing the homepage template to load Macaw index.html
  7. Filling the Publisher’s YAML database
  8. Replacing texts with placeholders in Macaw
  9. Translating the templates
  10. Mixing Macaw and default Publisher templates

Step #1: Get ready: Download components

  1. Download Macaw
  2. Download the (example) Macaw .mcw website
  3. Download Publisher, upload files to some hosting via FTP or setup a local MAMP server.

If you have Composer installed, you can clone Publisher repository using Git. If you downloaded a release, you can skip to Step #2.

Last login: Wed Apr 30 14:00:40 on ttys000
MBP:~ martinadamko$ cd /Users/martinadamko/Sites/Virtual_Hosts/macaw.sk/sub/www
MBP:www martinadamko$ git clone https://github.com/attitude/publisher.git .
Cloning into '.'...
remote: Counting objects: 48, done.
remote: Compressing objects: 100% (31/31), done.
remote: Total 48 (delta 7), reused 43 (delta 6)
Receiving objects: 100% (48/48), 41.50 KiB | 0 bytes/s, done.
Resolving deltas: 100% (7/7), done.
Checking connectivity... done.
MBP:www martinadamko$
MBP:www martinadamko$ composer install
Loading composer repositories with package information
Installing dependencies (including require-dev)
Writing lock file

Generating autoload files
MBP:www martinadamko$

You need to run Composer update after the install. I know it’s odd, but maybe after some structure changes of components it will be possible to install correctly the first time.

MBP:www martinadamko$ composer update
Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Installing attitude/elements/singleton (v0.2.0)
    Loading from cache

Generating autoload files
MBP:www martinadamko$

Back to top ↑

Step #2: Setup a webserver (optional)

In my example I’ve set-up a local server, masked as www.macaw.sk to localhost. I’ve also set up static.macaw.sk for serving CSS and javaScript from Cookie-less domain. To turn it on, edit /apps/macaw/app.php, Set as true to concatenate assets to http://static.macaw.sk.

$as_static_subdomain = false;

Note: Your hosting directory structure might be different than in this example.

Edit /Applications/MAMP/conf/apache/extra/httpd-vhosts.conf:

<VirtualHost *:80>
    ServerName macaw.sk
    ServerAlias www.macaw.sk
    ServerAlias static.macaw.sk
    DocumentRoot /Users/martinadamko/Sites/Virtual_Hosts/macaw.sk/
    <Directory /Users/martinadamko/Sites/Virtual_Hosts/macaw.sk/>
        Options Indexes FollowSymLinks MultiViews
        AllowOverride All
        Order allow,deny
        Allow from all
    </Directory>
</VirtualHost>

Edit /private/etc/hosts. You need admin access to do so and you should be careful not to mess things up.

##
# Host Database
#
# localhost is used to configure the loopback interface
# when the system is booting.  Do not change this entry.
##
127.0.0.1           localhost
127.0.0.1           macaw.sk www.macaw.sk static.macaw.sk
255.255.255.255     broadcasthost
::1                 localhost
fe80::1%lo0         localhost

The .htaccess in /Users/martinadamko/Sites/Virtual_Hosts/macaw.sk/ to enable subdomains is needed only in local environments. On online hosting you might need to edit few configuration paths (ask in the comment and take a screenshot of directory structure).

<IfModule mod_rewrite.c>
Options +FollowSymlinks

RewriteEngine on

RewriteCond %{HTTP_HOST} ^(.+?)\.[a-z-]+\.[a-z-]+(?:\.dev)?$ [NC]
RewriteCond %{REQUEST_URI} !^/sub/
RewriteRule ^(.*)$ /sub/%1/$1 [QSA,L]

RewriteCond %{HTTP_HOST} ^[a-z-]+\.[a-z-]+(?:\.dev)?$ [NC]
RewriteCond %{REQUEST_URI} !^/sub/
RewriteRule ^(.*)$ /sub/www/$1 [QSA,L]

RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [QSA,L]

</IfModule>

You should end up with structure like this:

Screen Shot 2014-04-30 at 14.46.50

Note: You might need to restart MAMP after changing the hosts file.

Back to top ↑

Step #3: Setup the Publisher project

You will need a place where your Macaw export would live, so let’s create it up-front. Create a copy of default app, by copying the /apps/default directory. Rename it to /apps/macaw. and run Publisher instal by navigating to http://www.macaw.sk/ in you browser and name your app macaw.

Screen Shot 2014-04-30 at 15.01.34

A .htaccess file is created for you redirecting all requests to /apps/macaw/public. The default App should run with some translations examples.

Screen Shot 2014-04-30 at 15.21.14

The default app had it’s own styles which are being included by homepage template located in /apps/macaw/public/templates/views/homepage:

  • styles.css (generated from styles.less file)
  • normalize.css

You might now want to remove all of the styles not to interfere with the .mcw exported CSS. I’ve only kept some to redefine font, since Macaw uses Typekit to display museo-sans*, which probably is forbidden to use on other than macaw.co domain #imho. I’ve changed it to **Source Sans Pro for example purposes.

Back to top ↑

Step #4: Publish Macaw as HTML/CSS/JS

Move the downloaded macaw-site.mcw into /apps/macaw/public, launch Macaw and export using Cmd+P. You should see:

Screen Shot 2014-04-30 at 15.27.17

You might notice, templates folder next to the macaw folder. That’s where the mustache template live. You would be able to mix them together.

Back to top ↑

Step #5: Configure the app

Locate /apps/macaw/config.app and fire up your favourite code editor. By default Publisher is setup to load views from ./templates/views and any partial from ./templates relatively to app’s dir. The config of DataPreprocessor_Component, which is a wrapper class for Mustache lives around line 160 (reformatted for readability:

DependencyContainer::set(
    'global::mustacheViews',
    new AtomicLoader_FilesystemLoader(WWW_ROOT_DIR.'/templates/views', $loader_args)
);
DependencyContainer::set(
    'global::mustachePartials',
    new AtomicLoader_FilesystemLoader(WWW_ROOT_DIR.'/templates', $loader_args)
);

We need to change it to an array of loaders adding a MacawLoader to the mix. First add use statement somewhere to the beginning of the config.php file:

use \attitude\Mustache\AtomicLoader_MacawLoader;

Then change global::mustacheViews and global::mustachePartials dependencies according to:

DependencyContainer::set(
    'global::mustacheViews',
    new \Mustache_Loader_CascadingLoader(array(
        new AtomicLoader_FilesystemLoader(WWW_ROOT_DIR.'/templates/views', $loader_args),
        new AtomicLoader_MacawLoader(WWW_ROOT_DIR.'/macaw/templates/views', $loader_args)
    ))
);
DependencyContainer::set(
    'global::mustachePartials',
    new \Mustache_Loader_CascadingLoader(array(
        new AtomicLoader_FilesystemLoader(WWW_ROOT_DIR.'/templates', $loader_args),
        new AtomicLoader_MacawLoader(WWW_ROOT_DIR.'/macaw/templates', $loader_args)
    ))
);

Try to reload the browser, all should continue functioning the same.

Back to top ↑

Step #6: Changing the homepage template to load Macaw index page

The homepage template is located under /apps/macaw/public/templates/views/homepage/template.mustache.

replace the content with simple mustache partial syntax:

{{> views/index}}

Reload the browser, you shoudl see the index:

Screen Shot 2014-04-30 at 15.43.07

Back to top ↑

Step #7: Filling the Publisher’s YAML database

Now the fun part begins. We’ll need to edit content.yaml to add Macaw texts to the Publisher’s database. Hit apps/macaw/public/macaw/macaw-site/index.html in editor, and strip most of the tags, and create YAML structure. If your editor is capable of regular expressions you might get some productivity boost. E.g.: remove end tags by replacing </.+>$ with empty string.

Skip the UI text like button texts. We’ll use template translations to keep data separate from interface:

<a href="http://secure.macaw.co" class="get-it-button">Get it now</a>

Use section classes for as hints for your database structure:

  <section class="testimonial clearfix">
    <blockquote class="_text">"The superhot web design tool of the future."</blockquote>
    <div class="by">—Jeffrey Zeldman, Founder of Happy Cog</div>
  </section>

as YAML:

testimonial:
    quote: "The superhot web design tool of the future."
    by: —Jeffrey Zeldman, Founder of Happy Cog

Turn unordered lists to arrays of objects:

    <ul class="feature-list clearfix">
      <li class="feature">
        <img class="feature-image" src="images/responsive-icon.png">
        <h3 class="feature-name">Responsive</h3>
        <p class="feature-text">Set breakpoints and optimize your site for all devices.</p>
      </li>
      <li class="feature feature-2">
        <img class="feature-image" src="images/type-icon.png">
        <h3 class="feature-name">Typography</h3>
        <p class="feature-text">Pull in web fonts or use system fonts like never before.</p>
      </li>
      ...

as YAML:

featureList:
    -
        src: images/responsive-icon.png
        name: Responsive
        text: Set breakpoints and optimize your site for all devices.
    -
        src: images/type-icon.png
        name: Typography
        text: Pull in web fonts or use system fonts like never before.
    -
        ...

There’s no option so far to edit the src of image in Macaw, we’ll create a partial for that in apps/macaw/public/templates/elements/image/template.mustache as:

<img class="feature-image" src="{{src}}">

I’ve put the template to the standard Publisher templates under .

Let’s put my first transcription of HTML into /apps/macaw/db/content.yaml database file. I’ve removed most of the default app’s data, just kept the essentials. content.yaml can contain multiple documents separated by --- at the beginning of a document and ... at the end.

If you hit the http://macaw.sk/en?forma=json-pretty, you can see the data ready to be filled:

Screen Shot 2014-04-30 at 16.26.15

Tip: I’m using JSON Formatter to display JSON.

Back to top ↑

Step #8: Replacing texts with placeholders in Macaw

In order to display data from the database, we need to replace texts in macaw-site.mcw with variable placeholders. Since I wanted to design look as decent as possible I decided to use this format for variables:

[any.object.attribute.content]

Check your JSON data for data hierarchy. There are three main attributes:

  • website – all data of homepage to be available on all subpages
  • collection – current collection data (e.g. /blog collection) available on for /collection and /collection/item123
  • item – current item data (e.g. /collection/item123 item)

We’ll use javaScript/Mustache dot notation to address data, e.g. website.title.

Start editing .mcw:

Screen Shot 2014-04-30 at 16.42.28

Replacing sections is pretty straightforward. Redefining repeatable is more head-scratching experience. Adding .repeat-item-features-featureList to the ul.feature-list should do it. We’ll only need one instance of feature though. Variable names are relative to the repeat.

The repeat class is basically a directive. Keep in mind, that Macaw lets you write class names only with [a-z-_]. Let’s break the peaces of repeat-item-features-_feature_list_:

  • repeat-: Publisher looks for a match on transforming HTML info mustache non-empty list section
  • _ at the beginning and the end of a variable name causes the variable trun to camelCase
  • item-features-_feature_list_: is like dot notation, except - are being used. It is the same as [item.features.featureList] in the JSON data.
Screen Shot 2014-04-30 at 17.43.31

You also need to replace the img tag with div.feature-image placeholder with the partial we’ve created previously.

By now you might find out that no images are exported, therefore it is a good idea to re-publish the old file again and keep the images. I moved them to the /apps/macaw/public/images folder and updated content.yaml appropriately. More on assets later.

features:
    line: Built for today's web designer
    featureList:
        -
            src: /images/responsive-icon.png
            name: Responsive
            text: Set breakpoints and optimize your site for all devices.
        -
            src: /images/type-icon.png
            name: Typography
            text: Pull in web fonts or use system fonts like never before.
        -
            ...

To display team-member twitter link, we’ll need to create another element partial, let’s call it apps/macaw/public/templates/elements/twitter-handle/template.mustache. The original looked like this:

<div onClick="window.location='http://twitter.com/gesusc';" class="handle">@gesusc</div>

Let’s recreate partial using mustache and variables:

<div onClick="window.location='{{url}}';" class="handle">{{handle}}</div>

Actually, we could use plain HTML:

<a href="{{url}}" class="handle">{{handle}}</a>

In Macaw, fill objects with variable placeholders. One instance is necessary.

Screen Shot 2014-04-30 at 18.44.34

I’ve changed the copyright to 2013 – year now. 2013 is the year Macaw was founded by a Kickstarter project. I’ve used mustache false values section.

In the yaml there’s dynamic data expander:

copyrightYear: { copyrightYear(): {since: 2013}}

…which in case of actual year being larger than since: 2013 will expand to:

{
   "copyrightYear": 2014
}

Expander is defined by default in the /apps/macaw/config.php as:

// Set expanders
DependencyContainer::set('global::dataExpanders', array(
    'link' => function($args) { ... },
    'href' => function($args) { ... },
    'title' => function($args) { ... },
    'query' => function($args) { ... },
    'content' => function($args) { ... },
    'copyrightYear' => function ($args) {
        $Y = date('Y');

        if (isset($args['since']) && $Y > $args['since']) {
            return $Y;
        }

        return false;
    }
));

In the time of writing, I’ve found out that anchors, the <a> tags can have any value. So I’ve tried out set link without the partial. The parent nav has .repeat-website-footer-nav class set, and the context is already set as website.footer.nav and links will be repeated.

Screen Shot 2014-04-30 at 22.07.56

I’ve added real data to the content.yaml:

footer:
    nav:
      -
          url: http://secure.macaw.co
          text: Store
      -
          url: http://forum.macaw.co
          text: Forums
      -
          url: http://macaw.co/videos
          text: Videos

Back to top ↑

Step #9: Translating the templates

There is not much left to translate in this example, but you’ll get the point anyway. All you need to do is to mark strings you wish to translate with ^ on both ends.

If you ever need to pluralise the string, e.g. your team is growing an you need to reflect the number of team members: We are team of {} passionate members. The {} is a placeholder where to put the actual number. To tell Publisher, which variable in the context holds the number, you need to use tertiary operator like:

[ website.team.count ? I am a passionate member : We are team of {} passionate members ]

By doing so, your templates will be ready to display a number, when your team eventually grows. If you only plan to use number, you might omit the first part:

[ website.team.count ? We are team of {} passionate members ]

You can even add variation to the translations.yaml database if you change your mind, without changing the .mcv. The tertiary operator is more declarative about the variation to be used when the number is 1.

That said, you can rewrite the translations without changing the .mcw however you find interesting. I’ve changed the “Get it now” to “Buy it” without changing the source and I also added Slovak translation:

'Get it now':
    sk_SK: Kúpiť
    en_EN: Buy it

index (20140430) index (20140430)-2

The tertiary operator consists of two markers: ? and :. If you ever need to use any of them in a sentence, you can use ? as much as you want. Only the first ? is important for parsing. To use more :, you can escape them, just as you’re probably used to from programming languages as \:. There’s also an option to put sentences in the quotes, to protect what’s inside.

Following examples will pass OK:

[ website.team.count ? We are team of {} passionate members. Would you like to join us? ]
[ website.team.count ? Number of passionate members of the team\: {}. Would you like to join us? ]
[ website.team.count ? "Number of passionate members of the team: {}. Would you like to join us?" ]

This markup is inspired by my previous experience with gettext, WordPress and the pluralisation logic roots in AngularJS.

Back to top ↑

Step #10: Mixing Macaw an default Publisher templates

This step is optional, however, it is recommended. As Macaw Loader already loads the CSS behind the scenes automatically, it strips exported HTML from relative styles in the <header>.

As you can recall, I’ve changed the content of homepage/template.mustache to this:

{{> views/index}}

Macaw loader uses the views prefix to recognise your intents:

  • {{> views/page-name}} loads full HTML page
  • {{> sections/page-name}} loads only the portion inside of the <body> tag

This can be used to further customise CSS, use preprocessor or simply allows you to write more complex CSS or javaScript outside of the Macaw.

I am about to include HTML5 Boilerplate in my version of the Macaw site. The new homepage template:

<!doctype html>
<html class="no-js">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <title>{{website.title}}</title>
        <meta name="description" content="{{ website.nowAvailableNotice | plaintext }}">
        <meta name="viewport" content="width=device-width, initial-scale=1">
    </head>
    <body>
        <!--[if lt IE 8]>
            <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
        <![endif]-->

        {{> sections/index}}
    </body>
</html>

Adding apps/macaw/public/templates/views/homepage/modernizr-v2.7.1.min.js to the template directory, will auto load it and concatenate. Since jQuery is exported by Macaw, it’s already included automagically.

You can put some extra CSS to the apps/macaw/public/templates/views/homepage/styles.less to customise tiny bits. There’s no need to include assets like CSS and JS. Publisher is set up to append these types of files automatically to each loaded partial/template. Just by sitting in the same folder of the used template/partial, they are automatically included.

To see the page without concatenation add ?combine-assets=false after the site’s URL.

Guess we’re done here. If you have any questions, hit me in the comments.

Thanks for reading.