Category Archives: CSS

Target and style an HTML element only if it has two classes

I was working with custom menus in my WordPress theme and assigned each menu item its own class so that I could have a custom background image.

This is basically how WordPress renders the menu (I simplified the number of classes in the example below, as WordPress generates a lot more than that):

1
2
3
4
5
6
7
8
9
10
11
<ul id="sub-menu">
    <li id="menu-item-12" class="custom-class-1 menu-item-12">
        <a href="#">Link 1</a>
    </li>
    <li id="menu-item-15" class="custom-class-2 menu-item-15">
        <a href="#">Link 2</a>
    </li>
    <li id="menu-item-18" class="custom-class-3 current-menu-item menu-item-18">
        <a href="#">Link 3</a>
    </li>
</ul>
<ul id="sub-menu">
	<li id="menu-item-12" class="custom-class-1 menu-item-12">
		<a href="#">Link 1</a>
	</li>
	<li id="menu-item-15" class="custom-class-2 menu-item-15">
		<a href="#">Link 2</a>
	</li>
	<li id="menu-item-18" class="custom-class-3 current-menu-item menu-item-18">
		<a href="#">Link 3</a>
	</li>
</ul>

So if I want to style the menu items at the sub-menu level, the only way to distinguish each menu item (without referencing ID numbers) is via my custom-class-#.

Now, what if I want to make the menu item appear differently if you’re on that page?

Fortunately, WordPress automatically creates a class called current-menu-item. If I were then to write a style like this:

1
2
3
4
5
6
7
.custom-class-1 a {
  color: #f00;
}
 
.current-menu-item a {
  color: #00f;
}
.custom-class-1 a {
  color: #f00;
}

.current-menu-item a {
  color: #00f;
}

Both those classes would have the same specificity and one would take over the other, depending on the order they were written. In a complex setup, this can get very messy, especially if you’re fiddling with a background image and its position.

Turns out, you can actually combine classes, in other words, define a multi-class union.

You can do that like so:

1
2
3
4
5
6
7
.custom-class-1.current-menu-item a {
  color: #f00;
}
 
.custom-class-1.current-menu-item a {
  color: #00f;
}
.custom-class-1.current-menu-item a {
  color: #f00;
}

.custom-class-1.current-menu-item a {
  color: #00f;
}

Now your style will only be applied if your HTML element has both of those classes. I never really had a need for this before today, as there was always another way, but this is good to know!

CSS clearfix leaves space below container

I’ve been using clearfix to clear floats for years now, and I always knew that it left a little bit of space after the HTML container that is was applied to. Most of the time I sort of worked around it, but I was curious whether there was a better solution.

Turns out, there is.

The problem is actually with the content of .clearfix:after:

1
2
3
4
5
6
7
8
.clearfix:after {
    content: '.';
    display: block;
    clear: both;
    visibility: hidden;
    line-height: 0;
    height: 0;
}
.clearfix:after {
	content: '.';
	display: block;
	clear: both;
	visibility: hidden;
	line-height: 0;
	height: 0;
}

The period is appended and hence there is a bit of a padding. The way to work around that is to simply change the period into a space, which isn’t rendered by the browser, and hence there’s nothing added to the end of your HTML container.

The complete clearfix then looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
.clearfix:after {
    content: ' ';
    display: block;
    clear: both;
    visibility: hidden;
    line-height: 0;
    height: 0;
}
 
.clearfix {
    display: inline-block;
}
 
html[xmlns] .clearfix {
    display: block;
}
 
* html .clearfix {
    height: 1%;
}
.clearfix:after {
	content: ' ';
	display: block;
	clear: both;
	visibility: hidden;
	line-height: 0;
	height: 0;
}

.clearfix {
	display: inline-block;
}

html[xmlns] .clearfix {
	display: block;
}

* html .clearfix {
	height: 1%;
}

This solution worked beautifully.

Change your list bullet to a custom character in CSS

Today I was wondering whether I could use a custom character as my list bullet. The requirement was that it was strictly CSS and not a background image.

Turns out, it’s actually pretty straightforward:

1
2
3
4
5
6
7
8
9
10
ul {
    list-style: none;
}
 
ul li:before {
    color: #f00;
    content: '» ';
    font-size: 1.2em;
    font-weight: bold;
}
ul {
	list-style: none;
}

ul li:before {
	color: #f00;
	content: '» ';
	font-size: 1.2em;
	font-weight: bold;
}

What’s interesting to note is that you can define a different style for this character, too, so it doesn’t have to match the rest of the list text.

The only caveat is that defining a margin between the character and your text is a bit trickier, since you can’t accomplish that with margin or padding, unless you wrap your list text in another HTML tag.

Unfortunately you can’t use HTML entities in your content either, but you can use Unicode. The Unicode value for space is \0000a0, so if you add that within your content value, you can achieve additional space.

1
2
3
4
5
6
7
8
9
10
ul {
    list-style: none;
}
 
ul li:before {
    color: #f00;
    content: \0000a0';
    font-size: 1.2em;
    font-weight: bold;
}
ul {
	list-style: none;
}

ul li:before {
	color: #f00;
	content: '» \0000a0';
	font-size: 1.2em;
	font-weight: bold;
}

That’s it. Let me know if you come up with an alternative or an improvement in the comments below.

Hybrid form validation: security of server-side (PHP) with efficiency of client-side (jQuery)

Preface

I’ve built a lot of forms in my day, and to be honest, it’s not one of my favorite things to do. The designing and building aspect is fun, but the validation and error reporting, ehh. We all know that server-side validation is a must, and technically speaking, client-side validation optional, but if you love your users, you’ll do it anyway. The challenge then for you, as the web developer, is to vigorously ensure that the client-side (jQuery) and server-side (PHP) validation are identical, so you don’t have discrepancies that confuse your users. The reason client-side validation makes users happy, is because the errors pop up right away and the page doesn’t need to be reloaded.

What if you could trade the pain of writing a client-side validation script with making  server-side validation instantaneous? Yes, AJAX comes to mind, however, the one thing people tend to do with that is display all the errors grouped together, because the AJAX response either updates or adds a div below or above the form, making the user have to figure out which error belongs to which field.

Project

The goal is to validate forms in the most efficient and user-friendly way, without having to compromise speed or security.

Requirements

  • Completely omit client-side validation
  • Display errors instantaneously next to corresponding fields
  • Prevent the page from being refreshed

Solution

The user fills out a form and it gets submitted via jQuery. If the submission takes just a bit longer, a processing message will appear. A separate processing script will validate each field and upon error, store that error in an array using the field’s label as the array item’s key and the error as the array item’s value. At the end of the validation, it will send the form back by calling the form’s function — a user-defined PHP function — with the error array as an argument. jQuery then replaces the existing form on the page with a new form that displays the errors underneath each field. jQuery’s on event handler then ensures that the replaced form is re-submittable.

Implementation

Below is the user-defined PHP function that creates the HTML form:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Returns a login form
// @param (array) errors [optional]
// @return (string) login form
function getLoginForm($errors = false) {
    $output .= '<form action="#" name="login" method="post">';
        $output .= '<h2>Sign in to ' . APPNAME . '</h2>';
        $output .= '<div class="field">';
            $output .= '<label for="username">Username</label>';
            $output .= '<input type="text" id="username" name="username" autofocus="autofocus"' . getPostValue('username')  . ' />';
            if(isset($errors['username'])) {
                $output .= '<div class="help">' . $errors['username'] . '</div>';
            }
        $output .= '</div>';
        $output .= '<div class="field">';
            $output .= '<label for="password">Password</label>';
            $output .= '<input type="password" id="password" name="password"' . getPostValue('password')  . ' />';
            if(isset($errors['passsword'])) {
                $output .= '<div class="help">' . $errors['username'] . '</div>';
            }
        $output .= '</div>';
        $output .= '<div class="message"></div>';
        $output .= '<div class="button">';
            $output .= '<button type="button" name="commit" disabled="disabled">Login</button>';
        $output .= '</div>';
    $output .= '</form>';
    return $output;
}
// Returns a login form
// @param (array) errors [optional]
// @return (string) login form
function getLoginForm($errors = false) {
	$output .= '<form action="#" name="login" method="post">';
		$output .= '<h2>Sign in to ' . APPNAME . '</h2>';
		$output .= '<div class="field">';
			$output .= '<label for="username">Username</label>';
			$output .= '<input type="text" id="username" name="username" autofocus="autofocus"' . getPostValue('username')  . ' />';
			if(isset($errors['username'])) {
				$output .= '<div class="help">' . $errors['username'] . '</div>';
			}
		$output .= '</div>';
		$output .= '<div class="field">';
			$output .= '<label for="password">Password</label>';
			$output .= '<input type="password" id="password" name="password"' . getPostValue('password')  . ' />';
			if(isset($errors['passsword'])) {
				$output .= '<div class="help">' . $errors['username'] . '</div>';
			}
		$output .= '</div>';
		$output .= '<div class="message"></div>';
		$output .= '<div class="button">';
			$output .= '<button type="button" name="commit" disabled="disabled">Login</button>';
		$output .= '</div>';
	$output .= '</form>';
	return $output;
}
  • On line 4 you see the optional argument. The variable is set to false so that PHP doesn’t complain when the function is called without a parameter.
  • On line 6 I’m using a named constant to display what the user is logging into. You could replace that with static text.
  • On line 9 I have another custom function called getPostValue() that basically escapes the user’s input before displaying it inside the field.
  • On line 10 we’re checking to see whether an array item with a key, that matches the field’s label, exists. If it does, we know to display an error, if not, that whole help div container is omitted.

Here is how the rendered form looks like in HTML:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<div class="init">
    <form action="#" name="login" method="post">
        <h2>Sign in to SME</h2>
        <div class="field">
            <label for="username">Username</label>
            <input type="text" id="username" name="username" autofocus="autofocus" />
        </div>
        <div class="field">
            <label for="password">Password</label>
            <input type="password" id="password" name="password" />
        </div>
        <div class="message"></div>
        <div class="button">
            <button type="button" name="commit" disabled="disabled">Login</button>
        </div>
    </form>
</div>
<div class="init">
	<form action="#" name="login" method="post">
		<h2>Sign in to SME</h2>
		<div class="field">
			<label for="username">Username</label>
			<input type="text" id="username" name="username" autofocus="autofocus" />
		</div>
		<div class="field">
			<label for="password">Password</label>
			<input type="password" id="password" name="password" />
		</div>
		<div class="message"></div>
		<div class="button">
			<button type="button" name="commit" disabled="disabled">Login</button>
		</div>
	</form>
</div>
  • On line 1 we need to wrap the form in a div, so we can later monitor it with jQuery. What happens is that jQuery will replace the entire form with one that shows the errors. Because that new form is a new object to the page, jQuery doesn’t know about it. It needs a fixed element on the page, such as the init div, whose contents it can monitor.
  • On line 2 you’ll notice that the action attribute is empty. This particular project requires users to have JavaScript turned on, but if you expect users without JavaScript, it’s a good idea to enter the path of the processor,.
  • On line 12 we will show a message to the user when the button gets clicked, so they know the form is processing.
  • On line 14 you’ll notice that I used the button tag to render a button as apposed to the classic <input type=”submit”>. I do that simply so I can style my text fields without having to worry that it will affect my buttons. My button is by default disabled and I then enable it with JavaScript, that way if JavaScript is turned off, the form can’t be submitted. Lastly, my button is of type=”button”, which by default doesn’t do anything (I assign an event handler with jQuery later). You’d make it of type=”submit” if you wanted that button to work without JavaScript.

Below is the jQuery that is in charge of submitting the form:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$(document).ready(function(){
 
    initBinding();
 
    // Send any form to its specific processor
    $('body').on('submit', 'form', function(e){
        var form = $(this);
        form.find('button[name=commit]').attr('disabled', 'disabled');
        form.find('.message').delay(100).queue(function() {
            $(this).html('<div class="pending">Sending your request... please wait...</div>');
        });
        $.post('process.php?p=' + form.attr('name'), form.serialize(), function(response) {
            form.parent('div').html(response);
            initBinding();
        });
        e.preventDefault();
    });
 
    function initBinding() {
        // Enable the form's submit button and assign event handler
        $('button[name=commit]').removeAttr('disabled').click(function() {
            $(this).trigger('submit');
        });
    }
 
});
$(document).ready(function(){

	initBinding();

	// Send any form to its specific processor
	$('body').on('submit', 'form', function(e){
		var form = $(this);
		form.find('button[name=commit]').attr('disabled', 'disabled');
		form.find('.message').delay(100).queue(function() {
			$(this).html('<div class="pending">Sending your request... please wait...</div>');
		});
		$.post('process.php?p=' + form.attr('name'), form.serialize(), function(response) {
			form.parent('div').html(response);
			initBinding();
		});
		e.preventDefault();
	});

	function initBinding() {
		// Enable the form's submit button and assign event handler
		$('button[name=commit]').removeAttr('disabled').click(function() {
			$(this).trigger('submit');
		});
	}

});
  • On line 3 I’m calling a function that enables my disabled submit button and assigns a click event to it, so it can submit the form, since my button doesn’t natively do that.
  • On line 6 we’re using jQuery’s on event handler, which basically monitors the HTML body and looks for changes in a form element. This is important so that the form can be resubmitted. When the form gets replaced, it’s an unknown object on the page, however, since jQuery is monitoring it, it will convert the newly replaced form to a submittable form.
  • On line 8 I’m disabling the button, so while the form is processing, it can’t be submitted a second time.
  • On line 9 you’ll notice that we delay the “processing” message slightly, because if it submits right away, there’s no reason to flash it on the screen, but if it takes a bit longer, the user should know that something is happening.
  • On line 12 is where we actually post the form. I’m sending it to process.php, which contains a switch control structure that receives the form attribute’s name, so that the processor knows which fields to expect and validate.
  • On line 14 I’m calling initBinding() again, so that the newly replaced form’s submit button will be re-enabled and can submit again.

Conclusion

You get an easily maintainable and secure form for which you don’t have to write any client-side validation. The form is submitted via jQuery, which doesn’t require the page to be reloaded, and the validation appears almost instantaneous to the user, just as if you had used client-side validation.

If you have any questions or suggestions, please feel free to leave comments below.

Letting users print coupons the easy way

Project

The goal is to present numerous coupons to the user, who can then freely select which ones they’re interested in and easily print them.

Requirements

  • Simple and intuitive user interface
  • Print-out will only contain selected coupons

Solution

We decided to stack all of the coupons, so they could be scrolled through without having to click anything or opening a new window. Each coupon is preceded with a checkbox and the user can click on the checkbox or the coupon itself to mark it for print. Below the coupons is a print button, which uses a specially crafted print stylesheet to strip the entire website and only print the selected coupons.

Implementation

The structure of our XHTML document looks as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<div id="lookbook">
    <p><a href="#" class="select">Select All</a> / <a href="#" class="deselect">Deselect All</a></p>
    <div class="coupons">
        <ul>
            <li>
                <div class="check"></div>
                <div class="coupon">
                    <img src="COUPONS.jpg" alt="Coupon 1" height="175" width="690" />
                </div>
            </li>
            <li>
                <div class="check"></div>
                <div class="coupon">
                    <img src="COUPONS2.jpg" alt="Coupon 2" height="175" width="690" />
                </div>
            </li>
            <li>
                <div class="check"></div>
                <div class="coupon">
                    <img src="COUPONS3.jpg" alt="Coupon 3" height="175" width="690" />
                </div>
            </li>
        </ul>
    </div>
    <div class="button"><div class="print">Print Selected Coupons</div></div>
</div>
<div id="lookbook">
	<p><a href="#" class="select">Select All</a> / <a href="#" class="deselect">Deselect All</a></p>
	<div class="coupons">
		<ul>
			<li>
				<div class="check"></div>
				<div class="coupon">
					<img src="COUPONS.jpg" alt="Coupon 1" height="175" width="690" />
				</div>
			</li>
			<li>
				<div class="check"></div>
				<div class="coupon">
					<img src="COUPONS2.jpg" alt="Coupon 2" height="175" width="690" />
				</div>
			</li>
			<li>
				<div class="check"></div>
				<div class="coupon">
					<img src="COUPONS3.jpg" alt="Coupon 3" height="175" width="690" />
				</div>
			</li>
		</ul>
	</div>
	<div class="button"><div class="print">Print Selected Coupons</div></div>
</div>

The relevant CSS is below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#lookbook .coupons ul {
    list-style: none;
    margin: 0;
    padding: 0;
    }
 
#lookbook .coupons ul li {
    cursor: pointer;
    display: block;
    margin-bottom: 25px;
    position: relative;
    height: 175px;
    width: 778px;
    }
 
#lookbook .coupons ul li .coupon {
    border: 2px dashed #ccc;
    padding: 5px;
    position: absolute;
    right: 0;
    height: 175px;
    width: 690px;
    }
 
#lookbook .coupons ul li:hover .coupon {
    border-color: #666;
    }
 
#lookbook .coupons ul li .check {
    background-image: url('checksprite.png');
    background-position: 0 0;
    background-repeat: no-repeat;
    position: absolute;
    top: 50px;
    left: 0;
    height: 70px;
    width: 70px;
    }
 
#lookbook .coupons ul li:hover .check {
    background-position: -100px 0;
    }
 
#lookbook .coupons ul li.print .check {
    background-position: -200px 0;
    }
 
#lookbook .button .print {
    background-color: #ED1C24;
    border: 1px solid #333;
    border-radius: 5px;
    box-shadow: 2px 2px 3px #999, inset 1px 1px #fff;
    color: #fff;
    cursor: pointer;
    font-size: 1.2em;
    font-weight: bold;
    margin: 0 auto;
    padding: 10px;
    text-align: center;
    text-shadow: 0 1px 0 #000;
    width: 250px;
    }
 
#lookbook .button .print:hover {
    background-color: #D11920;
    border-color: #000;
    }
 
@media print {
 
        #sitesearch, #eyebrow, #navigation-layer, #branding-layer, .leaderboard, #breadcrumb-layer, #column-3, #footer,
        #lookbook .header, #lookbook .intro, #lookbook .help, #lookbook .sel-links, #lookbook .coupons ul li .check, #lookbook .button {
            display: none;
            }
 
        #page-body {
            overflow: visible !important;
            }
 
        #lookbook .coupons {
            position: absolute;
            top: 0;
            left: 0;
            }
 
        #lookbook .coupons ul li {
            display: none;
            }
 
        #lookbook .coupons ul li.print {
            display: block;
            }
 
        #lookbook .coupons ul li .coupon {
            position: relative;
            }
}
#lookbook .coupons ul {
    list-style: none;
    margin: 0;
    padding: 0;
    }

#lookbook .coupons ul li {
    cursor: pointer;
    display: block;
    margin-bottom: 25px;
    position: relative;
    height: 175px;
    width: 778px;
    }

#lookbook .coupons ul li .coupon {
    border: 2px dashed #ccc;
    padding: 5px;
    position: absolute;
    right: 0;
    height: 175px;
    width: 690px;
    }

#lookbook .coupons ul li:hover .coupon {
    border-color: #666;
    }

#lookbook .coupons ul li .check {
    background-image: url('checksprite.png');
    background-position: 0 0;
    background-repeat: no-repeat;
    position: absolute;
    top: 50px;
    left: 0;
    height: 70px;
    width: 70px;
    }

#lookbook .coupons ul li:hover .check {
    background-position: -100px 0;
    }

#lookbook .coupons ul li.print .check {
    background-position: -200px 0;
    }

#lookbook .button .print {
    background-color: #ED1C24;
    border: 1px solid #333;
    border-radius: 5px;
    box-shadow: 2px 2px 3px #999, inset 1px 1px #fff;
    color: #fff;
    cursor: pointer;
    font-size: 1.2em;
    font-weight: bold;
    margin: 0 auto;
    padding: 10px;
    text-align: center;
    text-shadow: 0 1px 0 #000;
    width: 250px;
    }

#lookbook .button .print:hover {
    background-color: #D11920;
    border-color: #000;
    }

@media print {

        #sitesearch, #eyebrow, #navigation-layer, #branding-layer, .leaderboard, #breadcrumb-layer, #column-3, #footer,
        #lookbook .header, #lookbook .intro, #lookbook .help, #lookbook .sel-links, #lookbook .coupons ul li .check, #lookbook .button {
            display: none;
            }

        #page-body {
            overflow: visible !important;
            }

        #lookbook .coupons {
            position: absolute;
            top: 0;
            left: 0;
            }

        #lookbook .coupons ul li {
            display: none;
            }

        #lookbook .coupons ul li.print {
            display: block;
            }

        #lookbook .coupons ul li .coupon {
            position: relative;
            }
}

Here are a few things of importance in our CSS:

  • On line 30 we reference a single image called checksprite.png, which contains the three states of our checkbox: an empty checkbox, a checkbox while the mouse is hovering over it, and a selected checkbox.
  • On line 69 we declare an internal stylesheet that will be called upon when a user prints our page.
  • On line 71 we exclude elements from our site by hiding them (display="none"), so that we really only get the coupons on the print-out, not the site header, navigation, etc.

Last, but not least, here’s the jQuery:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Event.observe(window, 'load', function() {
    jQuery('#lookbook li').click(function(){
        jQuery(this).toggleClass('print');
    });
    jQuery('#lookbook .select').click(function(e){
        jQuery('#lookbook li').removeClass('print');
        jQuery('#lookbook li').addClass('print');
        e.preventDefault();
    });
    jQuery('#lookbook .deselect').click(function(e){
        jQuery('#lookbook li').removeClass('print');
        e.preventDefault();
    });
    jQuery('#lookbook .print').click(function(e){
        window.print();
    });
});
Event.observe(window, 'load', function() {
    jQuery('#lookbook li').click(function(){
        jQuery(this).toggleClass('print');
    });
    jQuery('#lookbook .select').click(function(e){
        jQuery('#lookbook li').removeClass('print');
        jQuery('#lookbook li').addClass('print');
        e.preventDefault();
    });
    jQuery('#lookbook .deselect').click(function(e){
        jQuery('#lookbook li').removeClass('print');
        e.preventDefault();
    });
    jQuery('#lookbook .print').click(function(e){
        window.print();
    });
});

I should note that the page this code was written for primarily uses prototype, which means our jQuery is running in no conflict mode. This is why we didn’t use the normal $ to reference jQuery functions and that’s also why we’re using Event.observe to load our code.

Result

We’re displaying a list of coupons in an unordered list. jQuery is listening for the user to click on any one of the coupons, and upon the user doing so, it adds a CSS class to that coupon, which is how the print stylesheet knows which coupons to hide and which ones to show.

This project has been implemented on St. Louis Magazine’s Look Book page, but below is a screenshot of the result as well:

If you have any questions or comments, feel free to leave them below.

June 13, 2013 Update: Here is a jsFiddle demo and the corresponding code.

December 28, 2013 Update: If you are having trouble with the jQuery code, it’s probably because you’re missing the function that checks to ensure the document is ready before running any code. Here is an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
jQuery(document).ready(function($){
    $('#lookbook li').click(function(){
        $(this).toggleClass('print');
    });
    $('#lookbook .select').click(function(e){
        $('#lookbook li').removeClass('print');
        $('#lookbook li').addClass('print');
        e.preventDefault();
    });
    $('#lookbook .deselect').click(function(e){
        $('#lookbook li').removeClass('print');
        e.preventDefault();
    });
    $('#lookbook .print').click(function(e){
        window.print();
    });
});
jQuery(document).ready(function($){
    $('#lookbook li').click(function(){
        $(this).toggleClass('print');
    });
    $('#lookbook .select').click(function(e){
        $('#lookbook li').removeClass('print');
        $('#lookbook li').addClass('print');
        e.preventDefault();
    });
    $('#lookbook .deselect').click(function(e){
        $('#lookbook li').removeClass('print');
        e.preventDefault();
    });
    $('#lookbook .print').click(function(e){
        window.print();
    });
});