Last week my colleague Jim Matthews and I added support for map and area elements to Watir. Watir users often ask how they can get Watir to support new elements, such as list items (”<li>“), so I thought I would walk through our new code.
Here’s the code for map:
module Watir
class Map < NonControlElement
TAG = 'MAP'
end
module Container
def map(how, what)
return Map.new(self, how, what)
end
end
end
First, we created the Map class. All we had to do was specify the
tag that indicates a map in HTML (”<MAP>“). The rest of the behavior
for the class is inherited from NonControlElement.
Second, we created the map method that users will use. This is added
to the Container module. Modules are used to allow methods to be added
to different classes. In Watir, most of the classes include
Container: the IE class, the Frame class, and all the element
classes (include NonControlElement). Therefore this method can be
called as ie.map(), or @ie.frame().map() or
even ie.table().cell().map().
This method is simply a wrapper around a call to create an instance of
the Map class that we just created. The standard element constructor
needs not only the “how” and the “what” but also a reference to the
containing object. This is ie in the case of ie.map(), but a
cell in the case of ie.table().cell().map(). It is the
Container object, and in Ruby self tells us what the current
object is (because its class included the Container module).
We write basically the same code for the area element. As demonstrated
by Jim’s tests,
this is all the code necessary to get the behavior you expect
Watir to provide. You can reference maps and areas by :name, :url,
:id, or :alt. You can even use multiple attributes. You can reference
an area in a map. And you get an exists? method that returns true or
false depending on whether the referenced element exists on the page,
a click method (for clicking), and name, url, id and alt
methods that you can use to check the attributes of the referenced
element. All of that comes in through inheritance.
Watir.define_element :map
Ruby certainly makes it pretty easy to define powerful functions like this. The define_element method could create the class the add a method to the container class. This is sometimes the right thing to do. It is central to the Don’t Repeat Yourself philosophy.
But from working with Jim and experimenting with Rails, I’ve learned that this ultra-condensed approach undermines understandabality. By using effectively six lines of code instead of one, we also provide a framework that can be expanded further.
Suppose we wanted ie.map(:name, 'map1').area(:alt,
'Texas').accesskey to return the character that highlights the
area. The “verbose” code makes it pretty clear where we would have to
define this method:
module Watir
class Area < NonControlElement
TAG = 'AREA'
def accesskey
// more code here...
end
end
end