Never been to DZone Snippets before?

Snippets is a public source code repository. Easily build up your personal collection of code snippets, categorize them with tags / keywords, and share them with the world

About this user

Todd Sayre http://del.icio.us/sporkyy

« Newer Snippets
Older Snippets »
Showing 1-3 of 3 total  RSS 

Javascript Rails-like inflector

I was using Ajax to do some DOM manipulation in a Rails app I was working on. None of what I was doing needed to involve a round trip to server, the only reason I hit the server at all was to take advantage of Rails' (well, ActiveSupport's) pluralize function. Also, this technique required an extra action that I decided to eliminate as I try to move to a more RESTful architecture.

The only part of ActiveSupport's Inflector class I implemented are the ones I needed. I implemented even fewer of Rails' Inflector-based functions.

Stick all this in a file, such as "inflector.js".
   1  /*
   2   * This script depends on no outside libraries.
   3   */
   4  
   5  Inflector = {
   6      /*
   7       * The order of all these lists has been reversed from the way 
   8       * ActiveSupport had them to keep the correct priority.
   9       */
  10      Inflections: {
  11          plural: [
  12              [/(quiz)$/i,               "$1zes"  ],
  13              [/^(ox)$/i,                "$1en"   ],
  14              [/([m|l])ouse$/i,          "$1ice"  ],
  15              [/(matr|vert|ind)ix|ex$/i, "$1ices" ],
  16              [/(x|ch|ss|sh)$/i,         "$1es"   ],
  17              [/([^aeiouy]|qu)y$/i,      "$1ies"  ],
  18              [/(hive)$/i,               "$1s"    ],
  19              [/(?:([^f])fe|([lr])f)$/i, "$1$2ves"],
  20              [/sis$/i,                  "ses"    ],
  21              [/([ti])um$/i,             "$1a"    ],
  22              [/(buffal|tomat)o$/i,      "$1oes"  ],
  23              [/(bu)s$/i,                "$1ses"  ],
  24              [/(alias|status)$/i,       "$1es"   ],
  25              [/(octop|vir)us$/i,        "$1i"    ],
  26              [/(ax|test)is$/i,          "$1es"   ],
  27              [/s$/i,                    "s"      ],
  28              [/$/,                      "s"      ]
  29          ],
  30          singular: [
  31              [/(quiz)zes$/i,                                                    "$1"     ],
  32              [/(matr)ices$/i,                                                   "$1ix"   ],
  33              [/(vert|ind)ices$/i,                                               "$1ex"   ],
  34              [/^(ox)en/i,                                                       "$1"     ],
  35              [/(alias|status)es$/i,                                             "$1"     ],
  36              [/(octop|vir)i$/i,                                                 "$1us"   ],
  37              [/(cris|ax|test)es$/i,                                             "$1is"   ],
  38              [/(shoe)s$/i,                                                      "$1"     ],
  39              [/(o)es$/i,                                                        "$1"     ],
  40              [/(bus)es$/i,                                                      "$1"     ],
  41              [/([m|l])ice$/i,                                                   "$1ouse" ],
  42              [/(x|ch|ss|sh)es$/i,                                               "$1"     ],
  43              [/(m)ovies$/i,                                                     "$1ovie" ],
  44              [/(s)eries$/i,                                                     "$1eries"],
  45              [/([^aeiouy]|qu)ies$/i,                                            "$1y"    ],
  46              [/([lr])ves$/i,                                                    "$1f"    ],
  47              [/(tive)s$/i,                                                      "$1"     ],
  48              [/(hive)s$/i,                                                      "$1"     ],
  49              [/([^f])ves$/i,                                                    "$1fe"   ],
  50              [/(^analy)ses$/i,                                                  "$1sis"  ],
  51              [/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i, "$1$2sis"],
  52              [/([ti])a$/i,                                                      "$1um"   ],
  53              [/(n)ews$/i,                                                       "$1ews"  ],
  54              [/s$/i,                                                            ""       ]
  55          ],
  56          irregular: [
  57              ['move',   'moves'   ],
  58              ['sex',    'sexes'   ],
  59              ['child',  'children'],
  60              ['man',    'men'     ],
  61              ['person', 'people'  ]
  62          ],
  63          uncountable: [
  64              "sheep",
  65              "fish",
  66              "series",
  67              "species",
  68              "money",
  69              "rice",
  70              "information",
  71              "equipment"
  72          ]
  73      },
  74      ordinalize: function(number) {
  75          if (11 <= parseInt(number) % 100 && parseInt(number) % 100 <= 13) {
  76              return number + "th";
  77          } else {
  78              switch (parseInt(number) % 10) {
  79                  case  1: return number + "st";
  80                  case  2: return number + "nd";
  81                  case  3: return number + "rd";
  82                  default: return number + "th";
  83              }
  84          }
  85      },
  86      pluralize: function(word) {
  87          for (var i = 0; i < Inflector.Inflections.uncountable.length; i++) {
  88              var uncountable = Inflector.Inflections.uncountable[i];
  89              if (word.toLowerCase == uncountable) {
  90                  return uncountable;
  91              }
  92          }
  93          for (var i = 0; i < Inflector.Inflections.irregular.length; i++) {
  94              var singular = Inflector.Inflections.irregular[i][0];
  95              var plural   = Inflector.Inflections.irregular[i][1];
  96              if ((word.toLowerCase == singular) || (word == plural)) {
  97                  return plural;
  98              }
  99          }
 100          for (var i = 0; i < Inflector.Inflections.plural.length; i++) {
 101              var regex          = Inflector.Inflections.plural[i][0];
 102              var replace_string = Inflector.Inflections.plural[i][1];
 103              if (regex.test(word)) {
 104                  return word.replace(regex, replace_string);
 105              }
 106          }
 107      }
 108  }
 109  
 110  function ordinalize(number) {
 111      return Inflector.ordinalize(number);
 112  }
 113  
 114  /*
 115   * Javascript doesn't have optional parameters or overloading so I had to use
 116   * the ever popular pseudo options hash object technique.
 117   * required properties:
 118   *     count    Number of objects to pluralize
 119   *     singular Singular noun for the objects
 120   * optional property:
 121   *     plural   Plural (probably irregular) noun for the objects
 122   * examples:
 123   *      pluralize({ count: total_count, singular: "Issue" })
 124   *      pluralize({ count: total_count, singular: "Goose", plural: "Geese" })
 125   */
 126  function pluralize(options) {
 127      return options.count + " " + (1 == parseInt(options.count) ?
 128          options.singular :
 129          options.plural || Inflector.pluralize(options.singular));
 130  }

FirstLast (real classes for Ajaxability)

I know CSS has the :first-child and :last-child pseudo classes. But :first-child is a part of CSS 2 and has poor browsers support and :last-child is a part of CSS 3 and that's not worth thinking about using yet. The idea is sound though, so I worked up this JavaScript method of getting the same effect.

One advantage of doing this in JavaScript rather than using CSS is that the classes will change if you reorder the child nodes with more JavaScript. In my case I'm using Scriptaculous sortables. Or to be more specific, I'm using a Ruby on Rails helper method to make something sortable through Scriptaculous:
   1  <%= sortable_element 'image-list',
   2      :constraint => false,
   3      :url => { :action => 'sort', :issue_id => params[:issue_id] }
   4  -%>


I've chosen to not make this script run unobtrusively. Most of it can go into a file and be included in the header or added to your own common JavaScript include file. To get it to run on a page you will need to include something like the below in each page.
   1  <script type="text/javascript">
   2  //<![CDATA[
   3      Event.onDOMReady(function(){FirstLast.go("image-list")});
   4      Ajax.Responders.register({
   5          onComplete: function(){FirstLast.go("image-list");}
   6     });
   7  //]]>
   8  </script>

I use the DOMReady extension for Prototype's Event object, but you can use any loader you want. The Ajax.Responders.register part reruns the script after a drag-and-drop operation (or any Ajax operation really).

Download Scriptaculous and Prototype.

   1  var FirstLast = {
   2      go: function(el) {
   3          el = $(el);
   4  
   5          // Whitespace nodes need to be cleaned to get the intended effect
   6          var children = Element.cleanWhitespace(el).childNodes;
   7  
   8          // Return if there are not any children
   9          if (0 == children.length) return
  10          
  11          if (1 == children.length) {
  12              // Cheap shortcut if there is only 1 child node
  13              children[0].addClassName(this._firstChildClassName);
  14              children[0].addClassName(this._lastChildClassName);
  15          } else {
  16              for (var i = 0; i < children.length; i++) {
  17                  switch (i) {
  18                      // First child
  19                      case 0:
  20                          children[i].addClassName(this._firstChildClassName);
  21                          children[i].removeClassName(this._lastChildClassName);
  22                          break;
  23                      // Last child
  24                      case children.length - 1:
  25                          children[i].removeClassName(this._firstChildClassName);
  26                          children[i].addClassName(this._lastChildClassName);
  27                          break;
  28                      // Every child other than the first or last
  29                      default:
  30                          children[i].removeClassName(this._firstChildClassName);
  31                          children[i].removeClassName(this._lastChildClassName);
  32                          break;  // I know it is unnecessary
  33                  }
  34              }
  35          }
  36      },
  37      // Pseudo Private methods and attributes
  38      _firstChildClassName: "first",
  39      _lastChildClassName: "last"
  40  };

ZebraStripes

   1  
   2  /*
   3   * This script requires:
   4   *   1. Prototype version 1.5 or greater
   5   *     - Homepage: http://prototype.conio.net/
   6   *     - Download: http://script.aculo.us/downloads
   7   *   2. DomReady addon for Prototype
   8   *     - Homepage: http://www.vivabit.com/bollocks/2006/06/21/a-dom-ready-extension-for-prototype
   9   *     - Download: http://www.vivabit.com/code/domready/domready.js
  10   */
  11  
  12  /*
  13   * I needed something capable of not just unobtrusively running when a page
  14   * was loaded but also easily being called at my whim as a part of an Ajax
  15   * transaction.
  16   */
  17  var ZebraStripes = {
  18      /*
  19       * Call this function when you want something striped.  It will figure out
  20       * how to stripe the element.
  21       */
  22      stripe: function(el) {
  23          el = $(el);
  24          switch (el.tagName) {
  25              case "TABLE":
  26                  this._stripeTable(el);
  27                  break;
  28              case "OL":
  29              case "UL":
  30                  this._stripeNormalList(el);
  31                  break;
  32              case "DL":
  33                  this._stripeDefinitionList(el);
  34                  break;
  35          }
  36      },
  37      /***************************************************************************
  38       * Everything below here is psuedo-private
  39       **************************************************************************/
  40      /*
  41       * This class name will be applied to the odd numbered elements
  42       */
  43      _altClassName: "alt",
  44      /*
  45       * This property persists the data that tells the object whether
  46       * to stripe (_isEven == false) or unstripe (_isEven == true) an element
  47       */
  48      _isEven: true,
  49      /*
  50       * Cycles the _isEven property of this object between true and false
  51       */
  52      _cycle: function() {
  53          this._isEven = ! this._isEven;
  54      },
  55      /*
  56       * As a part of the Ajax-friendliness, it is important that we remove the
  57       * alt class from elements as well as add it.
  58       */
  59      _stripeElement: function(el) {
  60          el = $(el);
  61          if (this._isEven) {
  62              el.removeClassName(this._altClassName);
  63          } else {
  64              el.addClassName(this._altClassName);
  65          }
  66      },
  67      /*
  68       * This works to stripe the child nodes of TABLE, TBODY, OL and UL elements.
  69       */
  70      _stripeElements: function(els) {
  71          els = $(els);
  72          if (els.length == 0) {
  73              return
  74          }
  75          var parent = els[0].parentNode;
  76          this._isEven = true;
  77          for (var i = 0; i < els.length; i++ ) {
  78              if ((parent == els[i].parentNode) && (els[i].visible)) {
  79                  this._stripeElement(els[i]);
  80                  this._cycle();
  81              }
  82          }
  83      },
  84      /*
  85       * TBODY is not necessary, but I recommend it.  I debated about striping
  86       * THEAD and TFOOT, but chose not to.  Might be added later.
  87       */
  88      _stripeTable: function(table) {
  89          table = $(table);
  90          if (table.getElementsByTagName("tbody")) {
  91              var tableBodies = table.getElementsByTagName("tbody");
  92              for (var i = 0; i < tableBodies.length; i++) {
  93                  this._stripeElements(tableBodies[i].getElementsByTagName("tr"));
  94              }
  95          } else {
  96              this._stripeElements(table.getElementsByTagName("tr"));
  97          }
  98      },
  99      /*
 100       * This stripes OL and UL since they both have LI and only LI child nodes.
 101       */
 102      _stripeNormalList: function(list) {
 103          list = $(list);
 104          this._stripeElements(list.getElementsByTagName("li"));
 105      },
 106      /*
 107       * I have seen other function out there that can stripe DL, but they all
 108       * assumed that each DT would have only one DD following it.  That is not
 109       * always the case, and not the case in the project that spawned this
 110       * javascript.
 111       */
 112      _stripeDefinitionList: function(list) {
 113          list = $(list);
 114          Element.cleanWhitespace(list);
 115          var children = list.childNodes;
 116          var previousDt;
 117          for (var i = 0; i < children.length; i++) {
 118              switch (children[i].tagName) {
 119                  case "DT":
 120                      if (children[i].visible) {
 121                          this._stripeElement(children[i]);
 122                          this._cycle();
 123                      }
 124                      previousDt = children[i];
 125                      break;
 126                  case "DD":
 127                      if (previousDt.visible) {
 128                          this._stripeElement(children[i]);
 129                      }
 130                      break;
 131              }
 132          }
 133      }
 134  };
 135  
 136  /*
 137   * This function will go ahead and stripe all the elligible elements on the
 138   * page when the page first loads.
 139   */
 140  function initZebraStripes() {
 141      var toStripe = $$("dl.striped")
 142          .concat($$("ol.striped"))
 143          .concat($$("table.striped"))
 144          .concat($$("ul.striped"));
 145  
 146      toStripe.each(
 147          function(el) {
 148              ZebraStripes.stripe(el);
 149          }
 150      );
 151  }
 152  
 153  Event.onDOMReady(initZebraStripes);
 154  // Or you could substitute a different loader:
 155  //Event.observe(window, 'load', initZebraStripes)
« Newer Snippets
Older Snippets »
Showing 1-3 of 3 total  RSS