CSS trick: Place Scrollbar outside of the client area

Today, I found a interesting difference between padding and margin when I’m working on Metrics 2.0 Introduction page. There are several VideoThumbnail widget on the page, which contains a video snapshot and a paragraph text description.
Here is the Html DOM of the widget, written in Haml:

VideoThumbnail Widget Html
1
2
3
4
5
6
7
8
9
%li.span4
%a.thumbnail.new(data-widget="IntroductionPage.VideoThumbnail")
.snapshot-container
%img.snapshot{ src: snapshot }
%img.status(src="/assets/new.png" )
.caption
.title
%h3 #{index}. #{title}
%p.description #{description}

Since the description could be very short or very long, so I make the div that contains the description scrollable, so I wrote the following stylesheet for caption div:

VideoThumbnail Widget Stylesheet
1
2
3
4
5
.caption {
padding: 9px;
height: 150px;
overflow-y: auto;
}

The style looks fine, and here is how it looks:

Wiget

But very soon, I found the widget with scrollbar is taller than the one without it, it is because padding on 2 elements next to each other will not be merged: Red rect in following snapshot

Padding

It is caused because padding will not merged together as margin does, To solve the issue, I changed the padding to margin in the stylesheet:

VideoThumbnail Widget Stylesheet
1
2
3
4
5
6
.caption {
padding: 0;
margin: 9px;
height: 150px;
overflow-y: auto;
}

But bottom margin is corrected, but I found the scrollbar begin to occupy the space of content, which is not good! The center widget uses padding(Blue) and the right one uses margin(Red)

Margin

And I remember if I uses padding, the scrollbar takes the space of right padding; but if I use margin, it takes the space of the content. So I update the stylesheet in this way:

VideoThumbnail Widget Stylesheet
1
2
3
4
5
6
.caption {
padding: 0 9px 0 0;
margin: 9px 0 9px 9px;
height: 150px;
overflow-y: auto;
}
Padding Margin Mixing

I use padding on the right but uses margin on other side, so vertical scrollbar will take the right padding when necessary. It is a very interesting CSS trick, and it works fine under webkit based browser.

Pitfall in Nokogiri XPath and Namespace

Nokogiri is a really popular Xml and Html library for Ruby. People loves Nokogiri is not just because it is powerful and fast, the most important is its flexible and convenient.
Nokogiri works perfect in most aspects, but there is a big pitfall when handling the xml namespace!

I met a super weird issue when processing xml returned by Google Data API, and the API returns the following xml document:

API response
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" xmlns:yt="http://gdata.youtube.com/schemas/2007">
<entry>
<media:group>(...)</media:group>
<yt:position>1</yt:position>
</entry>
<entry>
<media:group>(...)</media:group>
<yt:position>2</yt:position>
</entry>
<entry>
<media:group>(...)</media:group>
<yt:position>3</yt:position>
</entry>
<entry>
<media:group>(...)</media:group>
<yt:position>4</yt:position>
</entry>
</feed>

I instantiated a Nokogiri::XML DOM with the xml document, and then I try to query the DOM with XPath: xml_dom.xpath '//entry':

Query DOM
1
2
xml_dom = Nokogiri::XML Faraday.get api_url
entries = xml_dom.xpath '//entry'

I’m expecting entries is an array with 4 elements, but actually it is empty array. After a few tries, I found the query yields empty array when I introduce the element name in the query.

Try Xpath Queries
1
2
3
4
5
xml_dom.xpath '.' # returns document
xml_dom.xapth '//.' # returns all element nodes
xml_dom.xpath '/feed' # returns empty array
xml_dom.xpath '//entry' # returns empty array
xml_dom.xpath '//media:group', 'media' => 'http://search.yahoo.com/mrss/' # returns 4 the media:group nodes

It is super weird.

After half an hour fighting against the Nokogiri, I begin to realize that it must be related to the namespace.
And I found that there is an attribute applied to the root element of the document: xmlns="http://www.w3.org/2005/Atom", which means all the elements without explicit namespace declaration in the xml dom are under the namespace http://www.w3.org/2005/Atom by default.

And for some reason, the XPath query is namespace sensitive! It requires the full name rather than the local name, which means we should query the DOM with the code: xml_dom.xpath '//atom:entry', 'atom' => 'http://www.w3.org/2005/Atom'.

Fixed XPath Queries
1
2
3
4
5
xml_dom.xpath '.' # returns document
xml_dom.xapth '//.' # returns all element nodes
xml_dom.xpath '/atom:feed', 'atom' => 'http://www.w3.org/2005/Atom' # returns root node
xml_dom.xpath '//atom:entry', 'atom' => 'http://www.w3.org/2005/Atom' # returns 4 entry nodes
xml_dom.xpath '//media:group', 'media' => 'http://search.yahoo.com/mrss/' # returns 4 the media:group nodes

So in a sentence: XPath in Nokogiri doesn’t inherit the default namespace, so when query the DOM with default namespace, we need to explicitly specify the namespace in XPath query. It is really a hidden requirement and is very likely to be ignored by the developers!

So if there is no naming collision issue, it is recommeded to avoid this kind of “silly” issues by removing the namespaces in the DOM. Nokogiri::XML::Document class provides Nokogiri::XML::Document#remove_namespaces! method to achieve this goal.