One of the major changes in Bootstrap 3 is an update to the grid. It makes the grid always responsive and is simpler and more straight-forward than earlier versions of Bootstrap. The grid consists of three main components: containers, rows, and columns.


The container defines the max-width for the site for different viewports, centers the content, and applies a clearfix to make sure all floated elements stay inside the container.

LESS code: .container;

SASS code: @extend .container;


Use rows when you have columns of layout. These cannot be the same html element as a container. It must be inside the container, but you don't necessarily have to have a container in your markup at all for rows to still work.

A row uses negative margins since each column has a "gutter" margin applied to the right and left of it. The negative margin for the row makes sure the first column is aligned with the left end of the container.

Note that these negative margins mess up the iframe size for Facebook apps if the row is up against the edge of the frame. You should give the container a max-width: 810px with overflow-x: hidden or enough padding or margin so the negative half-gutter margin of the row doesn't spill outside the frame.

A row also uses clearfix to make sure the columns stay contained in the row.

LESS code: .make-row();

SASS code: @include make-row();


Bootstrap 3 changes make-column() to variants for different screen sizes which determine when the columns will become full screen width instead of the specified percentage. There are no fixed widths. It's always percentage based (hurrah!).

@include make-md-column() is basically equivalent to make-column() in lower versions of Bootstrap. But you can now also include make-sm-column() if you want the column to still exist on tablet and make-xs-column() for mobile screen sizes. There's also make-lg-column() for large desktops.

The media queries for each of the column mixins use min-width so you only need to specify the lowest setting if they are all the same on larger screen sizes. For example, if you want there to be three columns on tablets and everything larger but no columns on small screen mobile devices, use make-sm-column(4). There are 12 columns to the default boostrap grid, so 12 total columns / 3 desired columns = 4. If you have a row that is just one row on larger viewports, but goes to multiple rows on smaller screens, you can add a clearing div that is only used on the small screen. This and more is explained in the boostrap documentation. If you want to nest columns inside other columns, you'll need to put a row element inside first and then your columns inside that. You can't just add more columns directly inside another column.

Just because you have multiple elements in the same row in your design doesn't mean you always need to use a row and columns. The columns make sure things align together to a grid, but your design might simply call for inline-block or floated elements with fixed widths or custom percentages. There should be a discussion with the designer to identify what was designed to the grid and what wasn't.

LESS code: .make-md-column();

SASS code: @include make-md-column();


  • Containers are optional.
  • Rows cannot be the same element as containers.
  • Columns cannot be the same element as containers or rows. Columns must be placed inside rows.

Ever copy and paste code into a text editor and then realize you need to indent every line by four spaces to get it all escaped properly? Drive you crazy? Yeah, me too. I wrote a bookmarklet to take the currently selected text in a textarea input box and indent everything by four spaces. So you paste your code, select it, then click on this bookmarklet. I’ve only tested it on Chrome, so it might need slight modifications for other browsers. Here’s the source.

Ironically, markdown won’t let me create a link that has javascript in the href field, so I can’t post a link that you can simply drag to your bookmarks bar to install. Instead, right-click on the bookmarks bar and select “Add Page…” and paste this into the url field.  Or duplicate an existing bookmark and edit the url setting to be this:

javascript:(function(){var d=document.activeElement,c=document.getSelection().toString(),a="",b=c.split("\n");indent="    ",i=0,total=b.length;if(c==""){return}for(i=0;i < total;i++){a+=indent+b[i]+"\n"}d.value=d.value.split(c).join(a)}());

Thanks to everyone who made it out to the first-ever NYC Lithium Meetup on Tuesday, February 7th! Approximately 25 people made it out - a great start!

We were able to stream the event live via Ustream, so thanks to those who tuned in and submitted questions remotely via #li3 IRC during the presentations. 

You can watch an archive of the Meetup below:

Learn more about Lithium here.

Learn more about the NYC Lithium Meetup here.



lithium image

As you may have noticed from the code samples we post, at Affinitive we've become big proponents of Lithium, and it has become our "go to" framework for developing our social applications.

We're happy to announce the formation of the NYC Lithium Meetup group! This group is a place for the NYC developer community to get together and learn more about Lithium, the first truly "modern", full-stack PHP web application framework. Whether you are already developing applications using Lithium or are simply interested in exploring a new framework, please join us the meetup and hopefully we'll see you at one of our monthly get-togethers.

In terms of format, instead of a single long presentation, the focus will be on quick-format demos of applications built using Lithium, hacks, tips and tricks, and general QA/discussion/networking. And in true hacker spirit, free pizza and beer will be provided! Visit the Meetup group and join us now!

Interested in presenting? Drop us a line.


Here at Affinitive we have been starting to use the php framework named “Lithium” or “Li3”. One of the nice little features of this framework is that it comes built in with some very handy validators for validating your data before sending it to the database. But, as with all default measures that are taken to ensure your data is complete, sometimes you need to extend upon it, you do this with li3 validators by writing your own.

The process of writing a validator is very simple, you define it in the __init function of the specific Model. A sample validator is shown below

class ValidateExample extends \lithium\data\Model {
    public $validates = array();
    public static function __init() {
        $self = static::_object();
        // your __init code here 
        Validator::add('sampleValidator', function(&$value, $format = null, array $options = array()) {
            // your code here
            return $is_valid;

Lets look at the three values that are given to the Validator::add function so that it can work and validate effectively [see for complete official api documentation].

  • &$value – a reference to the given value
  • $format – a string that describes the format of the input. (see `$options[‘format’])
  • $options – a range of options that are based on the data that may or not be saved
    • $options['values'] – an array of values that will be saved if all the validators return true
    • $options['messages'] – the message that will be used if your validator failed
    • $options['format'] – This is the source for your raw format, the $format variable is set to 0. At the time of publication, there are two bug reports dealing with this, see bug reports here: here and here

The $options array allows you to check other values very easily. For instance, lets say you have a normal web signup form where you ask the user to put in their email address twice to make sure that they typed it in correctly. You can use the following validator (with a custom option) to specify that you want to compare the two form values.

Validator::add('match', function(&$value, $format = null, array $options = array()){
    if (isset($options['against']) && in_array($options['against'], array_keys($options['values']))) {
        return $options['values'][$options['against']] == $value;
    } else if (!isset($options['against'])) {
        throw new InvalidArgumentException("You did not specify a value to match against using the keyword 'against' ");
    } else {
        throw new InvalidArgumentException("The name {$options['against']} is not valid");

You can use this to make sure that two entries on your form are the same by entering the following line into your list of validations for a field.

    'message' => "field1 must match this field",
    'against' => "field1"

With this array, we call the “match” validator and give it two options, a message string and an against string. These both get passed to the validator, where it uses the against string to find the value of the intended variable and compare it to the given value.

We must also check to make sure that we are not being given faulty data (ex. wrong column names, etc.), so we sprinkle a few if statements and exception throwing to make sure the developer knows there is something wrong.

We're honored to have been named to the Facebook Preferred Developer Consultant (PDC) program this week! We're looking forward to helping Facebook to continue to improve the Facebook Platform by offering suggestions and continuing to file bugs as we find them. Read here for Affinitive's official announcement.

PDC badge image

Facebook recently announced it is possible to retrieve statistics on the new Send Button via the Graph API.  The statistics currently available are only for the domain as a whole - not a particular page.  These include domain_widget_send_views, domain_widget_send_clicks, domain_widget_send_inbox_views, and domain_widget_send_inbox_clicks.  However, if you list the statistics available for your app (, you won't see domain_* statistics listed.

Your domain has a different object_id in the Graph API than your app.  In order to get the object_id for your domain, you can use the domain FQL table:  You can also use a little trick if you have already claimed the domain at  You can claim domains that have fb:app_id open graph tags at the root of the domain (i.e. where you have been given at least Insights access for the app.

If you see a claimed domain listed in the websites section of, the "View Insights" link will take you to a url like this:  That number after is the object_id of your domain.  You can confirm that via this url:

Once you have the domain object id, you can now get the domain insights including the new Send Button data via If you need to obtain an access token, the Graph API Explorer tool makes it easy. Click on "Get Access Token" and then check off "read_insights" from the Extended Permissions tab.

I use Homebrew for package management in OSX which is awesome, but it doesn’t provide php. The homebrew philosophy is to not provide things that Snow Leopard already does a good job of providing out of the box.

However, as a heavy php developer, you might want a more recent version of php than the latest Snow Leopard provides. Or you might want to compile in readline support which doesn’t come in the Snow Leopard version of php (at least at the time of writing).

Why do you want readline support? It gives you access to a php interpreter by typing “php -a” — which is really awesome for quickly testing things. In fact, I use it often just to get unix timestamps:

$ php -a
Interactive shell

php > echo strtotime('next friday');

You could compile your own version and put it in /usr/local. But I wanted to stick with the standard location where Snow Leopard installs php.

First, you should install readline from Homebrew: brew install readline

I’m also using Homebrew’s version of mysql. I point the mysql socket in the server and client section of /etc/my.cnf to /var/mysql/mysql.sock – “socket = /var/mysql/mysql.sock”. Therefore the php configure command points to that location for the mysql socket. You made need to change that section of the configure command if you put it elsewhere.

This php configure command uses the mysqlnd driver for mysqli which I highly recommend. It also installs the mysql extension for backwards compatibility. You might not need it.

I also compile in pdo_mysql and a bunch of other extensions that I find essential (most of which are included in the php executable provided by Snow Leopard).

You can create a “config.nice” file, put the configure script contents in there and then run the following and you are good to go:

make install

Anyway… here is the script:

#! /bin/sh
# Created by configure

'./configure' \
'--prefix=/usr' \
'--mandir=/usr/share/man' \
'--infodir=/usr/share/info' \
'--sysconfdir=/private/etc' \
'--with-apxs2=/usr/sbin/apxs' \
'--enable-fpm' \
'--with-config-file-path=/etc' \
'--with-libxml-dir=/usr' \
'--with-openssl=/usr' \
'--with-kerberos=/usr' \
'--with-zlib=/usr' \
'--enable-bcmath' \
'--with-bz2=/usr' \
'--enable-calendar' \
'--with-curl=/usr' \
'--enable-exif' \
'--enable-ftp' \
'--with-gd' \
'--with-jpeg-dir=/usr/local' \
'--with-png-dir=/usr/X11' \
'--enable-gd-native-ttf' \
'--with-ldap=/usr' \
'--with-ldap-sasl=/usr' \
'--enable-mbstring' \
'--enable-mbregex' \
'--with-mysql=mysqlnd' \
'--with-mysqli=mysqlnd' \
'--with-pdo-mysql=mysqlnd' \
'--with-mysql-sock=/var/mysql/mysql.sock' \
'--with-iodbc=/usr' \
'--enable-shmop' \
'--with-snmp=/usr' \
'--enable-soap' \
'--enable-sockets' \
'--enable-sysvmsg' \
'--enable-sysvsem' \
'--enable-sysvshm' \
'--with-xmlrpc' \
'--with-iconv-dir=/usr' \
'--with-xsl=/usr' \
'--enable-zend-multibyte' \
'--enable-zip' \
'--with-pcre-regex=/usr' \
'--with-readline=/usr/local/Cellar/readline/6.1' \

So with that, you’ll have compiled php exactly as Apple does but with your own custom php version, readline support, and mysql support.

IMPORTANT NOTE: Any time you install a new version of xcode or update the OS, it will likely overwrite your custom php binary. Fortunately, as long as you keep your php source directory intact after you compile, all you need to do is go back there and run “make install” and it restores your custom php file.

Facebook introduced a new app setting called Secure Tab URL. If it is empty (which it is by default), then your iframe tab will no longer display on pages that have added it when the user viewing the page has enabled the Secure Browsing setting.

secure browsing image

What does this mean for developers? It means you need to setup https for your web server as soon as possible. You'll need to purchase an SSL/TLS certificate for your domain and install it on your webserver (GoDaddy has a pretty good deal for certificates). After https is setup, you also need to make sure your code loads all images, css, and scripts on the page with https if the page was loaded with https (otherwise, browsers - especially IE - will complain that the page contains non-secure items). In PHP with Apache or Litespeed web servers, you can check isset($_SERVER['HTTPS']) to know if the page was loaded with https.

After you get this all rocking, you need to update the Secure Canvas URL and Secure Tab URL settings in your application settings on Facebook.

secure tab url image

Update 5/24/2012: Facebook has finally added the ability to manage app restrictions into the Developer App.  See

We posted sample code demonstrating the capabilities of Facebook's iframe Tabs for Pages. You can view the sample tab here and the code on GitHub: The code also contains sample php scripts for setting and removing demographic restrictions for Facebook applications.

Demographic restrictions are based on the user's Facebook account settings. If a person doesn't meet the demographics or is not logged in to Facebook, she will not see the application or tab at all. So this has it's advantages (unauthorized people can't reach the app at all) and disadvantages (not visible to people not logged into Facebook).

With the March 2011 release of iframe Tabs for Pages, it allows us to use normal GeoIP methods to detect a person's country. This can be used for display of geo-targetted data to people not logged into Facebook. If the user is logged in to Facebook, her min age, country, and locale are all passed in the signed request.

The way to do GeoIP targeting as well as access all of the Facebook data that is available is demonstrated in the sample code. Also see Facebook's official documentation on Custom Page Tabs.


(function(where) {
  if (typeof(console) !== 'undefined') {
    console.log('Hello ' + where);

Affinitive Dev Blog

Ramblings from the dev team at Affinitive, a word of mouth and social media marketing and technology solutions pioneer and Facebook Preferred Marketing Developer (PMD). Also see @Affinitive, @RMarscher, and @BobTroia.