Combining LESS loops with lettering.js for typographical effects

I’ve recently been rebuilding the website for Edinburgh Samaritans (it’s not live yet…), part of which has involved creating a design which follows the visual identity guidelines created as part of the charity’s rebranding exercise last year. One of those guidelines relates to how the charity’s new bespoke font should be used.

Samaritans visual guidelines for headings

The guidelines state that for headings:

[b]aseline shift is added in increments up to 3pt/-3pt throughout the headline. This helps create the hand-drawn feel. This option is not available in Microsoft packages or online

Note the final sentence — ‘not available online’. Well sod that, say I. I’ve been using lesscss for over a year now and I think it’s awesome, although until now I hadn’t had cause to make use of the quasi-loops that parametric mixins can generate. Coupled with lettering.js, which admittedly I thought was a bit of a gimmick when I first came across it many months ago, the marriage of the two seemed perfect for this particular challenge.

lettering.js

Lettering.js heading

This is where lettering.js comes in. It’s a simple jQuery plugin which splits up a string into an array of numbered spans separated by sentence, word or letter. The individual spans can then be styled with CSS to create interesting effects.

However if you’re styling every letter in a heading that can be upwards of 100 characters long, maintaining all that CSS can get laborious very quickly:

<h1 class="page-title"><span class="char1">W</span><span class="char2">e</span><span class="char3">l</span><span class="char4">c</span><span class="char5">o</span><span class="char6">m</span><span class="char7">e</span><span> </span><span class="char8">t</span><span class="char9">o</span>
<!-- and so on... -->

Migraine ahoy. And that’s before you look at the CSS.

Applying lettering.js to our heading is really simple. Just include the plugin alongside jQuery, and target your text element in the $(document).ready() function:

$(document).ready(function() {
    $('h1.page-title').lettering();
});

Other options can be set in lettering to split your string into sentences or words instead of the letters, which is the default. The project’s Github wiki has more on that. For my purposes though, that’s it! Everything’s ready for CSS.

creating loops in lesscss

One clever way to get round the problem of styling those characters individually is to create a loop in lesscss to automatically generate all those classes and assign the proper styles. Since in this case the styling is a simple ±3pt (px) shift in each character’s baseline, using the loop’s index to apply the correct amount of differential should be fairly straightforward.

First I set up my h1 styles for page headers:

h1 {
    font-size: 1.8em;
    font-family: 'Samaritans', sans-serif;
    text-transform: uppercase;

    &.page-title {
        span {
            position: relative;
        }
    }
}

Here I’ve made every span in an h1 with the class page-title have a position style of relative, which allows for baselines deviations to be introduced later without otherwise disrupting the flow of the page’s layout (there’s a good explanation of this over on CSS Tricks). Now, once lettering.js has converted all the heading’s letters into individual spans, each one will be positioned relatively, ready for the baseline shift to be applied.

Creating the loop

Now we need to add the lesscss loop to the h1.page-title style. This will generate all the additional classes which need to be applied within this element, thereby styling each of the characters in our heading.

h1 {
    font-size: 1.8em;
    font-family: 'Samaritans', sans-serif;
    text-transform: uppercase;

    &.page-title {
        span {
            position: relative;
        }

        // Number of styles we'll generate (i.e. # of characters affected)
        @numStyledChars: 60;

        // Create loop mixin, ready for firing
        .charX (@index) when (@index > 0) {  // index > 0 condition prevents infinite loop
	        (~".char@{index}") {         // Less outputs code between quotes when ~ is used
	            top: (@index % 3px);     // Use the loop's index and modulus operator to generate a top style for the character
	        }

            .charX(@index - 1);         // Decrement the index and start again
        }

	    .charX(@numStyledChars);         // Execute the loop by feeding it the max number of styles

    }
}

The basic key here is the modulus operator (%) which returns the remainder of two integers. We divide the index (a number between 1 and @numStyledChars, which in this case is 60) by 3 and output the remainder as the span‘s top style. For example: 60 / 3 = 20 r 0; 59 / 3 = 19.6 r 2; 58 / 3 = 19.3 r 1 and so on. The result is we have a cycling top value: 0px, 2px, 1px, 0px, 2px, 1px and so on. These are applied sequentially to the characters in our heading, and the result is this:


instead of

It’s subtle, but it definitely enhances the friendliness of the site (which was a core priority of the rebrand) and means there’s consistency with the charity’s other marketing materials.
It’s not strictly accurate (the characters are only shifted between 0 and 3px rather than the full ±3px range suggested by the guidelines), but compared with the example above I think it works just as well.

The new site will hopefully launch sometime in the summer.

Create a searchable page selector attribute in concrete5

(thanks to @jordanlev in this thread on the concrete5 forums)

Concrete5 allows developers to create custom attributes designed to take in and process particular types of information which can then be associated with a page, file or user. This powerful framework allows for a great deal of extensibility and customised functionality.

Here I’m going to build a custom page attribute which allows the user to select from a checkbox list of other pages on the site. Why is this useful? Well, out-of-the-box, concrete5′s page categorisation architecture is limited mostly to the site’s hierarchy. In other words, a page’s ‘category’ is defined by its parent page within the site’s tree. This is really sensible and caters for a great deal of relatively simple site architectures. But what if you have a page type which you’d like to associate with more than one parent page? For example, you might have a main News page with multiple news articles beneath it. But those news articles might relate to the project pages under your top-level Projects hub, so it might be useful to associate the appropriate news articles with the relevant project pages, with a page list on the latter to display those relevant articles. A page selector attribute provides a simple and elegant way of achieving this.

1. Create the attribute’s controller

Create a file controller.php at /models/attribute/types/page_selector and paste in the following code (see comments):

<?php defined('C5_EXECUTE') or die(_('Access Denied.'));

	Loader::model('attribute/types/default/controller');

	/*
	 * Page selector attribute
	 *
	 * Grabs a list of pages of a particular type.
	 * Separate attributes are used to extend this class
	 * and specify the correct page type handle using the
	 * setHandle() method.
	 *
	 */
	class PageSelectorAttributeTypeController extends DefaultAttributeTypeController {

		/*
		 * Setup attribute form
		 */
		public function form() {
		    $this->set('fieldPostName', $this->field('value'));

		    // Get all possible options (cIDs and collection names)
		    $options = $this->getAvailablePages();
		    $this->set('options', $options);

		    // Get currently selection values (page IDs)
		    $selected = is_object($this->attributeValue) ? $this->getAttributeValue()->getValue() : '';
		    $selected = explode("\n", trim($selected));
		    $this->set('selected', $selected);

		    $availablePages = $this->getAvailablePages();
		    $this->set('availablePages', $availablePages);

		    $this->set('fieldPostName', $this->field('value'));
		}

		/*
		 * Get available pages using handle provided handle
		 */
		public function getAvailablePages() {
			Loader::model('page_list');
			$pl = new PageList;

			// Check for external handle or set manually
            // (1) You can filter by a particular page type handle by specifying it below, or use this attribute as
            //     a parent class for a set of child classes which specify the page type handle to filter by
            //     on an ad-hoc basis (this means there is one central controller containing the attribute's logic,
            //     which you can extend with further skeleton page selector attributes to provide page selectors for
            //     different page type handles
            // (2) To remove the filter, so all the site's pages are listed, comment out the next 3 lines
			//$ext_handle = $this->setHandle();
			$handle = ($ext_handle == '') ? 'define_handle' : $ext_handle;
			$pl->filterByCollectionTypeHandle($handle);

                        // Get the (filtered, if specified above) list of pages
			$pages = $pl->get();

			$retArray = array();
			foreach ($pages as $page) {
				$retArray[$page->getCollectionID()] = $page->getCollectionName();
			}
			asort($retArray);
			return $retArray;
		}

		/*
		 * Serialise selected page IDs and save to DB
		 */
		public function saveForm($data){
			$valueArray = $data['value'];

            // Using the manual serialization method rather than serialize() to
            // make searching more straightforward
			$valueString = implode("\n", $valueArray);
			if (empty($valueString)) {
				$this->saveValue('');
			} else {
				$this->saveValue("\n{$valueString}\n");
			}
		}

		/*
		 * Pass available options to search.php
		 */
		public function search() {
			$this->set('fieldPostName', $this->field('value'));
			$options = $this->getAvailablePages();
			$this->set('options', $options);
		}

		/*
		 * Derives page names from DB-stored page IDs for user-friendliness
		 */
		public function getDisplayValue() {
			$results = $this->getValue();
			$results = explode("\n", trim($results));

			$string = '';
			foreach ($results as $result) {
				$page = Page::getByID($result);
				$string .= $page->getCollectionName() . '<br/>';
			}
			$string = substr($string, 0, strlen($string)-2);
			return $string;
		}

		/*
		 * Provides search functionality for concrete5's Sitemap Page Search
                 * This function is optional -- the attribute will work without it, but
                 * the Sitemap Page Search won't work. Requires search.php.
		 */
		public function searchForm($list) {
		    $terms = $this->request('value');
		    $tbl = $this->attributeKey->getIndexedSearchTable();

        // If no options are set, return an unfiltered list of the site's pages,
        // otherwise build the DB search query, filter the site's pages by it
        // and return the result
        if (!is_array($terms)) {
			    return $list;
		    } else {
				$searchString = "(";
				foreach ($terms as $term) {
					$searchString .= $tbl . ".ak_" . $this->attributeKey->getAttributeKeyHandle() . " LIKE '%\n{$term}\n%' OR ";
				}
				$searchString = substr( $searchString, 0, (strlen($searchString)-4) );
				$searchString .= ")";
				$list->filter(false, $searchString);
				//$list->debug();
				return $list;
		    }
		}

	}

2. Create the attribute’s form

The attribute’s form defines the interface that will be displayed in a page’s properties when this attribute is added. It handles the interface both when adding a fresh instance of the attribute and when editing an existing one. This file is the front-end to the form() function in controller.php, which provides it with a list of selectable pages, and a list of currently selected pages (if applicable to the current page).

Create the form by pasting the following code into /models/attribute/types/page_selector/form.php:

<?php defined('C5_EXECUTE') or die("Access Denied.");
	$form = Loader::helper('form');

        // Arrays of (1) possible pages, and (2) currently selected pages
	$options_array = array();
	$selected_ids = array();

        // If $selected is an array there must be pages selected already -- get their IDs
	if (is_array($selected)) {
		foreach($selected as $key => $value){
			$selected_ids[] = $value;
		}
	}

        // If there is at least 1 page that can be selected, display a list. Otherwise warn the user that
        // there are no options to choose from. This is determined by the filtering methods applied to
        // the pagelist object in getAvailablePages() in controller.php
	if (count($options) > 0) {
		echo '<fieldset>';

                // Loop through available options and output checkboxes, setting to checked where page IDs
                // match those in the array of selected page IDs
		foreach ($options as $key=>$option) {
			$selected = '';
			if ( in_array($key, $selected_ids)) $selected = ' checked';
		?>

			<label class="checkbox inline">
				<input type="checkbox" name="<?=$fieldPostName?>[]" value="<?=$key?>"<?=$selected?> />
				<?=$option?>
			</label>

	<?php
		}
		echo '</fieldset>';
	} else {

		echo '<strong style="line-height:30px">No options have been defined yet.</strong>';

	}

That’s it — the attribute should function as expected once added in the Dashboard.

Here are a couple of enhancements to improve the page selector’s functionality and extensibility.

3. Searching the page selector attribute’s values

First, if you want to be able to search the attribute’s values from the Sitemap’s Page Search page, you’ll need to (1) include the searchForm() function in controller.php, and (2) include the following code in /models/attribute/types/page_selector/search.php, which defines the interface to show in the Page Search form:

<?php

defined('C5_EXECUTE') or die("Access Denied.");

        /*
         * Loop through the available options and output checkboxes
         */
	foreach ($options as $key=> $option) {
	?>

	<label class="checkbox inline" style="margin:2px 0; float: none; display: block">
	    <input type="checkbox" name="<?=$fieldPostName?>[]" value="<?=$key?>"/> <?=$option?>
	</label>

	<?php
	}

Pretty simple!

4. Abstracting the attribute’s code

If you only plan to have one page selector attribute, the above code will work perfectly well. But the project in which I was using this code required multiple selector attributes which filtered by different page types (e.g. news articles, events, projects). One way to achieve this is simply (1) to duplicate the /models/attribute/types/page_selector directory, (2) change the class name in controller.php from PageSelectorAttributeTypeController to AnotherPageSelectorAttributeTypeController and (3) alter the filters in getAvailablePages() to filter out a different page type.

This works, but it doesn’t respect the DRY principle, and although the code involved is pretty simple and straightforward, it’s not great from a maintainability point of view.

One solution is therefore to create a generic page selector attribute (as the above code demonstrates), then a set of “sub-attributes” which feed the correct page type filter to the parent class. The hook for this is already in place in the code above in the getAvailablePages() function. When uncommented, this line

//$ext_handle = $this->setHandle();

attempts to get a handle from the setHandle() function. Now we’ll create that function in a sub-attribute, so we can pass arbitrary values to the parent class and execute the same controller code regardless of how many page selector attributes we’ve created.

Create a new sub-attribute by pasting the following code into /models/attribute/types/another_page_selector/controller.php:

<?php defined('C5_EXECUTE') or die(_('Access Denied.'));

	class AnotherPageSelectorAttributeTypeController extends PageSelectorAttributeTypeController {

		/*
		 * Set the page type handle for this attribute
		 */
		public function setHandle() {
			return 'another_page_type';
		}

	}

That’s all there is to the controller. Because this new class (AnotherPageSelectorAttributeTypeController) is extending PageSelectorAttributeTypeController, the latter has access to all the functions defined in the former. So when the core page selector attribute controller attemps to execute setHandle(), in this case it’ll get the value ‘another_page_type’, which it’ll use to filter out the site’s pages. Note the conventions for using CamelCase in the class names, and how this relates to the use of underscores in file names (so a controller class called NewsArticleAttributeTypeController should live in /models/attribute/types/news_article/). For more info see this article on the concrete5 website.

Another note — if you go down this sub-attribute route, you need to copy search.php and form.php from the original Page Selector directory (/models/attribute/types/page_selector/) to the new sub-attribute’s directory, and to any other sub-attributes you create. Once you’ve done this you can safely delete form.php and search.php from the original Page Selector’s directory.

5. Using the attribute in practice

As mentioned above, a practical use for this attribute might be to associate pages to one another which otherwise don’t have any hierarchical relevance. The following code might be placed on a project page to automatically filter out news articles which, by using the page selector attribute, have been associated to it:


<aside class="news">
        <?php
        $c = Page::getCurrentPage();
        $c_id = $c->getCollectionID();
        Loader::model('page_list');
        $nh = Loader::helper('navigation');
        $pl = new PageList;

        // First filter by the correct page type, to narrow the scope down first
        $pl->filterByCollectionTypeHandle('news_article');
        // Filter by the page selector attribute
        // (1) ak_page_selector matches the attribute's handle, e.g. ak_news_selector, ak_project_selector etc
        // (2) $c_id is the page ID that should be searched for. In this instance we're looking for news
        //     articles associated with the current page, so we pass in this page's ID
        $pl->filter(false, "(ak_page_selector LIKE '%\n{$c_id}\n%')");
        $relevant_news = $pl->get();

        // Outputs a list of relevant news article titles and links
        echo '<ul>';
        foreach ($relevant_news as $news_article) {
                <li><?php echo htmlspecialchars($page->getCollectionName(), ENT_QUOTES, APP_CHARSET); ?></li>
                <li><a href="<?php echo $nh->getLinkToCollection($news_article);?>">Read more &gt;</a></li>
        }
        echo '</ul>';
</aside>

Obviously the full flexibility of the Page List and Collection objects are available to you when deciding how to output the results.

Facebook: our internal MiniTrue

This is a submission I made to the recent Guardian article titled Facebook and loneliness: our readers respond. Before I submitted a response by email to the article’s author, I commented on the article expressing my surprise that the stories that had been published above the line were all positive and that there were none about the alienating effects of Facebook. The author responded suggesting that I send in my thoughts, which I duly did. They weren’t published, perhaps because I didn’t express them well enough, or perhaps for some other reason. At any rate, had they been added to the article, they would have stood out as a very negative interpretation of the impact that Facebook can have.

I had initially interpreted the title of the article (‘Facebook and loneliness’) as referring to the loneliness caused or accentuated by Facebook, as opposed to the use of Facebook as a tool to reduce feelings of loneliness. While I think it can go some way to achieving the latter, the former is a much more prevalent effect of its (mis)use.

Anyway, here’s what I submitted:

Facebook can be a great communication tool, if that’s how it is viewed by those who use it. But in many cases, Facebook alienates its users from each other. Unfortunately for many it has become an addiction, where preening our online persona to be a mirage of the person we wish we were has become the priority over the much more difficult task of becoming that person in the real world. Why worry about your development as a human being when you can take a shortcut to perfection on your Facebook profile? The computer creates a buffer which allows us to consider everything we do before we do it, removing the risk inherent in being spontaneous and genuine around people in the physical world. But it is the results of taking that risk that opens us up to forming real connections with our fellow human beings.

 

The torrent of poised fakery that we’re exposed to when browsing Facebook comes at a price. I and many people I’ve spoken to have felt deeply dejected after a session, as the flow of witty banter, ‘fun’ photos in exciting places and lives which seem much more interesting than our own leave us questioning whether we’re any good at all.

 

The irony is that most of us are complicit in the Facebook charade — we all try to put forward only the best of ourselves on Facebook. The end result is an worryingly pervasive version of the school playground — but now instead of being judged on what trainers you wear, it’s your whole life that gets the X Factor treatment. Unfortunately with Facebook the shallowest and most destructive parts of being a teenager are kept alive and well in adulthood.

Since the word limit was 250, I’ve obviously condensed a lot of thoughts into a small space without elaborating on them fully. It’s important to emphasise the first sentence — Facebook, if treated as a tool for communication like email or SMS, can be a good thing. But observing the way that people use it in the real world shows that it has managed to latch onto an insecurity that, it appears, a huge number of people have. The need to be included and to feel accepted has become warped out of shape to the point where it’s not enough to be who we are, rather we need to obsessively filter and amplify the best parts of our lives while ignoring the worst.

We become invested in our own cult of personality, and the person on our profile becomes a perfectly manicured avatar of who we really are. Our Wall is a mechanism through which to publish our personal propaganda, where reality is sanitised and edited so our ‘friends’ see only those things that show us in a good light. Just look at the average profile photo and consider what probably went through that person’s mind when they chose it (and, in many cases, took it with the intention of using it as their profile pic).

Facebook gives that worst of human traits — narcissism — the platform it has always wanted. Not only can we tailor our presentation of ourselves, but we also get the delicious prospect of our perfected pseudo-self being beamed directly into the computers and phones of those around us with only minimal effort. Direct manipulation of our PR is just a witty quip or a strategic ‘like’ away. The price of all this is more fakery, less genuine self-belief, and ultimately a reduction in the quality of the relationships we have with other people.

Web workflow: tools and frameworks

For the last few years ‘frameworks’ (or ‘boilerplates’, or ‘bootstraps’) have been all the rage – they’re intended to provide developers/designers with prefabricated foundations for their work, removing some of the tedium involved in some of the more robotic aspects of web design and development. I admit that I came a little late to that party – it was only in September 2010 that I stumbled across the 960 Grid System and the full benefits of a CSS framework became clear to me. Prior to that I hand-coded pretty much every site, and many a yard of my hair was lost trying to figure out the idiosyncrasies of the various browsers (not naming any names, but this was of course at a time when version 6 of a certain browser was still very prevalent and still required full or near-full support). It was pretty revelatory to me to have a massive chunk of that work done already.

So by no means am I disparaging the idea of frameworks – when done right they can be a great example of open source in action and can be genuinely useful. I’m always cheered by the work that people put into these things to prevent others from having to re-invent the wheel – the altruism/kudos incentive is a fascinating thing. I have a lot to thank them for, as do my clients, in saved time and cost and, of course, enhanced end results.

Lameworks (ho ho)

But there are downsides. Given the number of frameworks out there it’s quite easy to be overwhelmed and to worry that you’re using X when you should be using Y, or that if you only used Z your life would be Just Awesome™. My approach has been to proceed with caution – don’t assume you need something just because it’s new and shiny. Even when I have come across something that looks genuinely useful, my experience is that it takes time to overhaul my workflow to integrating new tools and practices, and attempting to do too much at once can be really confusing and ultimately counterproductive if those new tools or code conflict or create new and novel administrative burdens. I personally find it more effective to note down new tools/approaches/ideas as I find them (while reading blogs or web dev sites) and play with them outside first in a sandbox, outside of the real development context, to find out whether there would be real value in integrating them properly into my workflow.

A problem that didn’t need solving

I’ve lost count of how many web ‘apps’ I’ve signed up to which are supposed to enhance my productivity but which I’ve realised, on reflection, don’t actually solve any problem I’m experiencing. Web 2.0 is littered with these answers to problems that don’t exist – and as much as I admire the entrepreneurial zeal of the starry-eyed Zuckerbergs who create them, the vast majority do seem destined to fold sooner or later, and no amount of 1px white text shadow and noisy background textures is likely to change that.

Nagging doubts

Depending on where you rank on the perfectionist scale, frameworks can also have the welcome side-effect of making you constantly feel like there’s unfinished business with your past projects – a nagging desire to retrospectively inject some of the new whizzbang skills you’ve acquired to bring those oldies up to the cutting edge. It’s worth remembering that you were working with what you knew at the time, and if everything was and still is working, and the client was and is happy with it, then you did your job. Most clients have no interest in the gubbins anyway – if it looks like it works and it smells like it works, then (to them) it works.

It might sound like like I have a downer on frameworks but that’s not the case at all, and I’ll describe which ones I use in a later post. My problem isn’t with frameworks per se; I’m just wary that all the choice out there can create more problems than it solves. Bizarrely, the incredibly positive trend in the web community towards share techniques and code to make each others’ lives easier can, if we don’t step back a bit, confuse matters and distract us from concentrating on the core skills necessary to create websites, on both the code and organisational sides.

So, when exactly did we finally ‘make poverty history’?


I’m only asking because nobody seems to be wearing the little white arm bands any more. I can only assume this is because the fight was fought and won, and poverty has indeed been eradicated – ‘made history’ – and the world’s poor are now living it up like the rest of us, buying the latest consumer smack from Apple and congratulating themselves on how they’ve arrived. That’s that then. Job done.

But wait, it seems that’s not quite how things have worked out. Apparently far from being consigned to history, poverty is still alive and kicking – indeed it’s creeping closer to home all the time. So where, therefore, have the arm bands gone? Surely ‘the message’ still needs to be got out there and awareness still needs to be ‘raised’?

Read the rest of this entry »

2 column news and events page list template for concrete5

Hi all

I’ve been building the theme for a new concrete5 site, and the designers of the site specified a two-column, four-item news area on the homepage, showing news items in descending date order, in a Z shape. This is what the layout looks like:

concrete5′s pagelist block is the best thing for this job, but I wasn’t sure how to make it play such that (a) the news items would show in two columns, and (b) the items would be displayed in reverse chronological order, in a layout like this:

1 2
3 4

People tend to read text on a page in a F-pattern, moving across the top of the page (first row) before down the left-hand side. So it makes sense that news items should flow from top left to top right, down to bottom left then bottom right. More can be seen on Jakob Nielsen’s web usability site.

Read the rest of this entry »

Integrate Google Site Search into concrete5

Hi all

I’ve just succeeded in adding Google Site Search to my concrete5 site and I thought I’d share how. This is for sites where you want a search box on every page (for example in the sidebar or header), and the results to display on their own page. Here”s an example:

Google site search is in many ways better than the c5 search, which isn’t really surprising given Google’s core business. Things like internal PDF/DOC/ODT searching and whatnot are helpful too, especially if you’re building a site with a lot of document downloads (as I am).

Here’s what I did: Read the rest of this entry »

($happy) ? :D : ):