Custom Pseudo Selectors

I've wrote about how to extend the class creation (Binds and Exposes mutator) and implement a custom Event (outerClick). In this post I'll show you the creation of custom CSS pseudo selectors to use in any MooTools CSS based selector (for example with $$ or getElement). Note, that these Selectors are fully cross browser. (Sure thing you can't use them inside your CSS files.)

MooTools already includes most of the CSS3 pseudo selectors (:nth-child, :contains, and :not to name a few examples).

To add your own pseudo selector you have to add it as a function to Selectors.Pseudo. Two arguments will be passed to the function. First argument is the parameter given to pseudo selector in the form :pseudo(parameter) or undefined if none given. The second argument is an object in which you can store temporary data for following elements visited during the traversing of one selector. This is used for example in the :nth-child pseudo selector. The actual element being filtered is available as the scope of that function (this). The function should return true when the element is matched by the pseudo or false otherwise.

To show you the usage I'll create a simple pseudo selector, :random, with probably little practical use. It will have an optional parameter for the probability to select the element with a default value of 0.5 (50%).

  1. Selectors.Pseudo.random = function(probability, local){
  2. return Math.random() < (probability || .5).toFloat();
  3. };
  4.  
  5. // selects half of the divs on average
  6. $$('div:random');
  7. // selects 20% of the h2 elements with class article on average
  8. $$('h2.article:random(.2)');

Note, that the element bound to the filter is not extended for performance reasons. That means if you want to use a Element method inside the filter, say getStyle, this.getStyle(style) doesn't work. You'd have to extend the this with $. For example: $(this).getStyle(style).
But this would cause each element inspected to be extended. To increase the speed of the pseudo selector generics can be used on the unextended Element: Element.getStyle(this, style).

outerClick event

Someone in the #mootoos irc channel wanted an event triggered only when clicked outside of an element but there's nothing built-in, so I thought that it's an interesting event to implement. And since it has at least some reusability I'm going to share the code along with a description of what it's doing.

  1. (function(){
  2. var events;
  3. var check = function(e){
  4. var target = $(e.target);
  5. var parents = target.getParents();
  6. events.each(function(item){
  7. var element = item.element;
  8. if (element != target && !parents.contains(element))
  9. item.fn.call(element, e);
  10. });
  11. };
  12. Element.Events.outerClick = {
  13. onAdd: function(fn){
  14. if(!events) {
  15. document.addEvent('click', check);
  16. events = [];
  17. }
  18. events.push({element: this, fn: fn});
  19. },
  20. onRemove: function(fn){
  21. events = events.filter(function(item){
  22. return item.element != this || item.fn != fn;
  23. }, this);
  24. if (!events.length) {
  25. document.removeEvent('click', check);
  26. events = null;
  27. }
  28. }
  29. };
  30. })();
  31.  
  32. // usage
  33. window.addEvent('domready', function(){
  34. $('test').addEvent('outerClick', function(){
  35. alert('You clicked outside of $("test").');
  36. });
  37. });
  38.  

First thing you may notice is the (function(){ /* code here */ })() pattern. That creates a private area for the events variable, which will store all the added outerClick events and the check function. This way the global namespace is not polluted.

Now let's take a look at onAdd. It checks if the events variable is set, and if not it initializes it with an empty array and adds the check function as a document click event listener. Note that this is only done once for all outerClick events added. The element and the user supplied event listener fn are pushed to the events array as the last step of onAdd.

onRemove does the same things onAdd does but in reverse. It filters the element with the function and element out of the events array and if the array is empty it removes the document click listener.

Last part of the code to discuss is the check function. The idea behind it is to check if the event target is outside the the element in events and if so it calls the event function. The target is 'inside' the element if its the element itself or if the element is one of the targets parents. In order to do the search for the parents only once per click target.getParents() is saved to a temporary variable.

The post is now way longer than expected, though I hope you've learned a bit from my first serious post.

 1

About

I'm a MooTools core developer and this is the place where I publish some snippets and other mootools related stuff. I study computer science in Darmstadt, Germany.

Idea for a Topic?

If you've got an idea for a topic to write about, let me know!

Other Blogs

User