How to Ajaxify your website with Ajaxify and history.js

Ajaxify is a great gist over on github by balupton that allows you to load page changes in via AJAX with a nice fade effect. URL’s are also kept intact while browsing and using History.js, browser history is handled correctly.

It is very easy to install, however, aside from the the initial set up, there is not much documentation so I’m going to share some things that I discovered while setting up a couple of different sites.

The initial set up if offensively easy and is exactly the as described on the Gist page. You just need to include jQuery, jQuery ScrollTo, History.js and the Ajaxify gist:

<!-- jQuery --> 
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script> 
 
<!-- jQuery ScrollTo Plugin -->
<script defer src="http://balupton.github.com/jquery-scrollto/scripts/jquery.scrollto.min.js"></script>
 
<!-- History.js --> 
<script defer src="http://balupton.github.com/history.js/scripts/bundled/html4+html5/jquery.history.js"></script>
 
<!-- Ajaxify -->
<script defer src="http://gist.github.com/raw/854622/ajaxify-html5.js"></script>

This is all you need. If you take a look at this EXAMPLE, you will see that pages are already loading with AJAX.

Something quite annoying I have noticed though is that it also outputs the page title at the top of the page on page change. You can easily hide this in CSS with:

.document-title{display:none}

The page change here is using AJAX but the whole page fades in and out with a white haze which is fine, but if you have a site with a common layout – static nav and content area throughout – we can improve on it by specifying just the main content to be Ajaxified.

Looking in the code for the gist, you will notice this line:

       ......
       contentSelector = '#content,article:first,.article:first,.post:first',
       ......

Ajaxify looks for any elements matching these selectors and applies the effect just to them. So, using the above example, we can Ajaxify just the main content div by adding an id of “content” to it (you can also achieve this by structuring your page using the HTML5 elements “article” or “post”):

.....
<div class="mainContent" id="content">
  ......
</div>
.....

Now, when you look at this EXAMPLE, you can see that when you change pages, only the main content is switched – notice the URL and titles still change too. You are also able to navigate to either of the pages directly by entering their URL in the address bar and clicking the ‘back’ and ‘forward’ buttons in your browser takes you through your history correctly.

Sometimes, you will want to disable AJAX from a link. A common situation is with image lightboxes such as fancybox where ajaxyify will interfere with the plugin. You will have a link like so:

<a class="fancybox" href="image.jpg"><img src="thumb.jpg" alt="Image Title" /></a>

AJAX will automatically be applied to this link so you will need to disable it. In ajaxyify.html5.js there is this line watches links to ajaxify:

...
$this.find('a:internal:not(.no-ajaxy)').click(function(event){
...

From this selector, you can easily see that by adding the class no-ajaxy to a link you can get ajaxify to ignore it. Like so:

<a href="/link/" class="no-ajaxy">Click me</a>
<a class="fancybox no-ajaxy" href="image.jpg"><img src="thumb.jpg" alt="Image Title" /></a>

From the below lines, you will notice that Ajaxify triggers a ‘statechangecomplete’ event on page change:

...
completedEventName = 'statechangecomplete',
...
// Complete the change
$window.trigger(completedEventName);
...

You can listen for this event like so:

...
$(window).on('statechangecomplete', function(e, eventInfo){
//your code
})
...

Silverstripe 3 SQL Queries

You will occasionally need to to perform custom SQL queries in Silverstripe 3 and return this data for display in your template rather than just returning a standard DataObject.

Rather than just writing raw SQL, you need to build your SQL query using the SQLQuery class and execute the query:

$sqlQuery = new SQLQuery();
$sqlQuery->setFrom('Source_Table'); //the name of the table you want to select data from
$sqlQuery->selectField('Title, Content, Other_Field'); //the fields you want to return
$sqlQuery->addLeftJoin('Table_To_Join', '"Source_Table"."Relation_Column" = "Table_To_Join"."Relation_Column"'); //any joining tables
//other joins can also be specified like the left join example above
$sqlQuery->addWhere('"Column" = "Value"'); //add a where clause
$result = $sqlQuery->execute(); //execute the SQL

Now you have your result, you need to create a new ArrayList and into it, push the results from the SQL query to return to your template:

$returnedRecords = new ArrayList();
foreach($result as $row) { 
    $returnedRecords->push(new ArrayData($row)); 
}

Full example:

YourClass.php

function getResults(){
    $sqlQuery = new SQLQuery();
    $sqlQuery->setFrom('Source_Table');
    $sqlQuery->selectField('Title, Content, Other_Field');
    $sqlQuery->addLeftJoin('Table_To_Join', '"Source_Table"."Relation_Column" = "Table_To_Join"."Relation_Column"');
    $sqlQuery->addWhere('"Column" = "Value"');
    $result = $sqlQuery->execute();
    $returnedRecords = new ArrayList();
    foreach($result as $row) { 
        $returnedRecords->push(new ArrayData($row)); 
    }
    return $returnedRecords;
}

And loop through your results like so:

YourClass.ss

<% loop Results %>
	$Title
	$Content
	$etc
<% end_loop %>

Thumbnails in Silverstripe 3 GridField Summaries

Often, you will use Silverstripe 3 GridFields to handle images and will want to show image thumbnails in the summary view rather than have to give every image a title that you can later refer to when you need to edit the image. I have managed to achieve this fairly simply using the below code snippet which will give you something looking like this:

Silverstripe 3 GridField summary with Thumbnails

The important parts of the snippet are the ‘Thumbnail’ item in the the summary_fields static and the getThumbnail function:

<?php
class HeroImage extends DataObject{
	public static $db = array(
		'Title' => 'Varchar(255)',
  		'SortOrder' => 'Int'
 	);
 
 	static $has_one = array( 
		'HomePage' => 'HomePage',
		'HeroImage' => 'Image'
	);
 
 	public static $summary_fields = array(
  		'Thumbnail'=>'Thumbnail',
  		'Title' => 'Title'
 	);
 
 	public function getThumbnail() { 
		if ($Image = $this->HeroImage()->ID) { 
			return $this->HeroImage()->SetWidth(80); 
		} else { 
			return '(No Image)'; 
		} 
	}
 
 	public static $default_sort='SortOrder';
 
 	public function getCMSFields() {
	  	return new FieldList(
	   		new TextField('Title', 'Title'),
	   		new UploadField('HeroImage', 'Image')
	  	);
	}
}

A fairly short post but if you have any questions about anything on here, post them in the comments below or you can contact me and I will get back to you with an answer.

Add Captions to jQuery Cycle Slideshows

It is pretty easy to add captions to jQuery Cycle slideshows even though there is no ‘out of the box’ way of doing this.

The after option allows you to specify a function to be executed after the slide transition has finished and this is what we will be using to add captions to your slideshow.

You simply need to modify your image tags to include a caption for the image and add in a placeholder for the caption to go:

<ul id="slides">
	<li><img src="img1.jpg" alt="" data-caption="Caption for Image 1"></li>
	<li><img src="img2.jpg" alt="" data-caption="Caption for Image 2"></li>
	<li><img src="img3.jpg" alt="" data-caption="Caption for Image 3"></li>
	<li><img src="img4.jpg" alt="" data-caption="Caption for Image 4"></li>
</ul>
</div>
<div id="imageCaption"></div>

Then, you can put a simple function in the after option to extract the caption from the visible slide with jQuery and put this into your placeholder:

$("ul#slides").cycle({
	after: function(){
		$('#imageCaption').html($('#slides li:visible img').data('caption'));
	}
});

This can be easily extended to titles and any other information you need to present with your images. You just put any additional data for the image in to the image tag’s data-attributes and you can access them in the same way:

$('#slides li:visible img').data('your-data-attribute');

You can download the jQuery Cycle plug here

jQuery Content Slider – Basic plug in

I was looking for a jQuery Content Slider plug in for a project I was working on today. I couldn’t find one that fit the bill so decided to develop my own. It is in no way as fully featured as some of the other jQuery sliders out there as it was developed in as quick a time as possible to fill the specific need I had.

If you have any questions or comments about the usage of this plug in or would like to see me develop this further, let me know in the comments or contact me.

SEE THE DEMO HERE.

DOWNLOAD THE JQUERY CONTENT SLIDER PLUG-IN HERE.

Usage
Firstly, you need to link to jQuery and the plug-in files (download the files using the above link).

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min.js"></script>
<script src="jquery.content.slider.min.js"></script>
<link rel="stylesheet" type="text/css" href="jquery.content.slider.css" />

You will want to change (at least) 3 of the values in the CSS to make your slider the required size. The first 2 define the height and width of the viewable area and the third defines with width of the individual slides. If want to show 1 slide at a time you simply need to set both widths to the same value. In the demo you will see that we have 3 slides visible at a time. You can of course change other values to tailor the content slider.

You can put any content you like into this jQuery content slider. Whatever you put between list items constitutes a slide.

Your slider is laid out like this:

<div id="contentSlider">
	<ul>
		<li>Content area 1</li>
		<li>Content area 2</li>
		<li>Content area 3</li>
		<li>Content area 4</li>
		<li>Content area 5</li>
		<li>Content area 6</li>
		<li>Content area 7</li>
		<li>Content area 8</li>
		<li>Content area 9</li>
	</ul>
</div>

And finally you initialise the slider like so:

$('#contentSlider').subSlider();

Options
As mentioned previously, this content slider is not as fully featured as some out there at the moment. Currently, there are only 2 configurable options which can be specified at initialisation:

$('#contentSlider').subSlider({
    speed: 200, //specifies the time in milliseconds for each transition
    addClasses: 'additionalClass1 additionalClass1' //allows you to specify additional classes to add to the slider (useful if you need 2 or more differently styled sliders on your page)
});

jQuery Sprite Animation Tutorial

Here is a simple method of jQuery Sprite Animation. I also tie it to a parallax scrolling effect. The methods you see here will be easily adaptable and transferable to any application.

I previously wrote a post on simple jQuery parallax scrolling. I will use the same method here for the parallax scrolling and extend it to add the animation.

See the DEMO or DOWNLOAD the files.

I have 2 sprites that I will be animating; one of a bird and one of a kangaroo. The sprites each contain 3 frames of animation of which you only show one at a time. To achieve the effect, we need to reposition the background image of the sprite at the appropriate time to create the effect that the object is moving. Due to the way these animals move, they need to be worked in slightly different ways.

This is what the sprites look like. Notice the 3 “frames” in each:

  

Here is the HTML.

<div class="bgWrapper"></div>
<div class="bgBird"></div>
<div class="bgRoo"></div>
<div class="mainWrapper"><!-- --></div>

here is the CSS

.bgWrapper{
	position:fixed;
	background:url(bg.jpg) top center no-repeat; 
	height:100%; 
	width:100%; 
	top:0; 
	left:0;
}
.bgBird{
	position:fixed;
	background:url(bg-bird.png);
	width:171px; 
	height:70px; 
	left:50%; 
	margin-left:-800px;
	margin-top:300px;
}
.bgRoo{
	position:fixed;
	background:url(bg-roo.png);
	width:225px;
	height:239px;
	left:50%;
	margin-left:250px;
	margin-top:400px;
}

and the jQuery:

$(document).ready(function(){
	$(window).bind('scroll',function(e){
   		parallaxScroll();
   	});
 
   	//We use these presets for the bird animation
   	var ScrollCount = 1;
   	var wingDirection = 'down';
 
   	function parallaxScroll(){
   		var scrolledY = $(window).scrollTop();
 
		//to move the background slowest (as this is the "furthest" away from you)
		$('.bgWrapper').css('background-position','center -'+((scrolledY*0.1))+'px');
 
		//To move the Kangaroo object in accordance with the scroll. 
	   	//We create the "bounce" effect by adjusting the 'top' value in along a sine wave
		$('.bgRoo').css('top',''+((Math.sin(scrolledY/30)*40)+(scrolledY*0.1)+'px'));
 
		//We adjust the left margin to move the kangaroo from right to left
	   	$('.bgRoo').css('margin-left',(250+(scrolledY*.5))+'px');
 
		//To animate the kangaroo to simulate it's legs compressing at the bottom of the jump
		if ((Math.sin(scrolledY/30)*40)&gt;37){
			$('.bgRoo').css('background-position','0px -478px');
		} else if ((Math.sin(scrolledY/30)*40)&gt;25){
			$('.bgRoo').css('background-position','0px -239px');
		} else {
			$('.bgRoo').css('background-position','0px 0px');
		};
 
		//To move the Bird object in accordance with the scroll
		//We adjust the top and margin-left values in accordance with the scroll to move the bird up and down
		$('.bgBird').css('top','-'+((scrolledY*0.4))+'px');
		$('.bgBird').css('margin-left',(-800+(scrolledY*0.2))+'px');
 
		//To animate the bird sprite. 
		//Note: I am only changing "frames" every 3rd pixel of scroll. Otherwise the wings appear to move too quickly
		//You can adjust this to make animation move faster or slower
		if (ScrollCount == 3){
			ScrollCount = 1;
			if ($('.bgBird').css('background-position') == '0px 0px'){
				wingDirection = 'down';
				$('.bgBird').css('background-position','0px -70px');
			} else if ($('.bgBird').css('background-position') == '0px -70px'){
				if (wingDirection == 'down'){
					$('.bgBird').css('background-position','0px -140px');
				} else {
					$('.bgBird').css('background-position','0px 0px');
				}
			} else {
				wingDirection = 'up';
				$('.bgBird').css('background-position','0px -70px');
			}
		}
 
		ScrollCount++;
 
   	}
 
});

Note: I a got a leg up for the sprite animation from a blog post that I came accross at tonylea.com (http://www.tonylea.com/2012/animating-a-sprite-with-jquery/ – well worth checking out).

jQuery Parallax Scrolling Tutorial

Here I will show all that’s needed to achieve jQuery parallax scrolling using 3 image layers (this can easily be expanded to more layers) and a few lines of code. You can see the full DEMO or DOWNLOAD the files.

I have 3 images: One solid background image and 2 transparent PNG’s to layer on top of the background at perceived differing closeness to the screen. To achieve the parallax scrolling effect, the background image should have the slowest speed, and the “closer” layers should move progressively faster. It’s just like when you are looking out the window of a car; The hill on the horizon moves by much slower than that burning warehouse in the middle distance and trees on the side of the road fly by much quicker.

The logic is offensively simple. For every X pixels you scroll the window, with jQuery, you move the “nearest” layer 0.8 x X pixels, the middle layer at 0.5 x X pixels and the “furthest” layer 0.2 x X pixels. Of course, you can easily adjust these values to achieve different effects.

Here’s the HTML:

<div class="bgWrapper"></div>
<div class="shark"></div>
<div class="fish"></div>
<div class="mainWrapper"><!-- --></div>

The CSS:

.bgWrapper{
	position:fixed;
	background:url(underwater-bg.jpg) top center no-repeat; 
	height:100%; 
	width:100%; 
	top:0; 
	left:0;
}
.shark{
	position:fixed;
	background:url(shark.png);
	width:250px; 
	height:187px; 
	left:50%; 
	margin-left:-600px;
	margin-top:400px;
}
.fish{
	position:fixed;
	background:url(fish.png);
	width:300px;
	height:246px;
	left:50%;
	margin-left:400px;
	margin-top:500px;
}

..And the jQuery

$(document).ready(function(){
	$(window).bind('scroll',function(e){
   		parallaxScroll();
   	});
 
   	function parallaxScroll(){
   		var scrolledY = $(window).scrollTop();
		$('.bgWrapper').css('background-position','center -'+((scrolledY*0.2))+'px');
		$('.shark').css('top','-'+((scrolledY*0.5))+'px');
		$('.fish').css('top','-'+((scrolledY*0.8))+'px');
   	}
 
});

Want to hear me talk? I co-host the web development podcast “Three Devs and a Maybe”. Check us out here

Silverstripe 3 GridField with many_many relationship

After getting used to the DataObjectManager in Silverstripe 2.4 and below I’ve been trying to achieve similar functionality using the new GridFields in Silverstripe 3. A fairly common situation I encounter is the need to handle many to many relationships. In the below example, I have Tour Pages, which can have many Activities associated with them and an Activity can also be associated with many Tour Pages.

We create the activity class:

MySite/Code/Activity.php

class Activity extends DataObject{
	public static $db = array(
		'Title' => 'Varchar(255)',
  		'Summary' => 'HTMLText'
 	);
 
 	static $belongs_many_many = array( 
		'TourPages' => 'TourPage' 
	);
 
 	public static $summary_fields = array(
  		'Title' => 'Title'
 	);
 
 	public function getCMSFields_forPopup() {
	  	return new FieldList(
	   		new TextField('Title', 'Title'),
	   		new HTMLEditorField('Summary', 'Brief Summary'),
	  	);
	}
}

We now need to add the relationship to the tour page and create the GridField: Note the unlinkrelation delete action. There was a change to the codebase in the last 7 days or so (at date of post) which changed the action of the delete button in GridFields to actually delete the item rather than unlink the relation. This adds in the unlink relation button so it will not affect any of your other relations.

MySite/Code/TourPage.php

static $many_many = array(
	'Activities' => 'Activity'
);
 
function getCMSFields() {
	$fields = parent::getCMSFields();
 
	...
	$gridFieldConfig = GridFieldConfig_RelationEditor::create()->addComponents(
		new GridFieldDeleteAction('unlinkrelation')
	);
 
	$gridField = new GridField("Activities", "Activities", $this->Activities(), $gridFieldConfig);
 
	$fields->addFieldToTab("Root.Activities", $gridField);
 
	return $fields;
}

Once you have run a dev/build you will have something similar to the below.

You may have noticed the “Allow Drag and Drop” link to allow you to order these.

To allow Drag and Drop reordering, you will need to download the SortableGridField module and run a dev/build/flush=all. You will also need to add a few additional lines of code to the Tour Page.

Firstly you need to add a many_many_extraFields static to add an extra row to the linking table to store the sort order. In my example:

static $many_many_extraFields = array( 
	'Activities' => array( 
		'SortOrder' => "Int" 
	)
}

Finally add the GridFieldSortableRows to your GridField config:

$gridFieldConfig = GridFieldConfig_RelationEditor::create()->addComponents(
	new GridFieldDeleteAction('unlinkrelation'),
	new GridFieldSortableRows('SortOrder') //add to allow sorting of many_many's
);

You will also need to sort the GridField based on the sort order field created above when you are creating it:

$gridField = new GridField("Activities", "Activities", $this->Activities()->sort('SortOrder'), $gridFieldConfig);

So the final code looks like this:

MySite/Code/Activity.php

class Activity extends DataObject{
	public static $db = array(
		'Title' => 'Varchar(255)',
  		'Summary' => 'HTMLText'
 	);
 
 	static $belongs_many_many = array( 
		'TourPages' => 'TourPage' 
	);
 
 	public static $summary_fields = array(
  		'Title' => 'Title'
 	);
 
 
 	public function getCMSFields_forPopup() {
	  	return new FieldList(
	   		new TextField('Title', 'Title'),
	   		new HTMLEditorField('Summary', 'Brief Summary'),
	  	);
	}
}

MySite/Code/TourPage.php

static $many_many = array(
	'Activities' => 'Activity'
);
static $many_many_extraFields = array( 
        'Activities' => array( 
                'SortOrder' => "Int" 
        )
);
 
function getCMSFields() {
	$fields = parent::getCMSFields();
 
	...
	$gridFieldConfig = GridFieldConfig_RelationEditor::create()->addComponents(
		new GridFieldDeleteAction('unlinkrelation'),
                new GridFieldSortableRows('SortOrder') //add to allow sorting of many_many's
	);
 
	$gridField = new GridField("Activities", "Activities", $this->Activities()->sort('SortOrder'), $gridFieldConfig);
 
	$fields->addFieldToTab("Root.Activities", $gridField);
 
	return $fields;
}