Don't like ads? PRO users don't see any ads ;-)
Guest

Untitled

By: a guest on May 1st, 2012  |  syntax: None  |  size: 11.14 KB  |  hits: 12  |  expires: Never
download  |  raw  |  embed  |  report abuse  |  print
Text below is selected. Please press Ctrl+C to copy to your clipboard. (⌘+C on Mac)
  1. var mailer = require('mailer');
  2.  
  3. var humanize = function(string) {
  4.   var str=string.toLowerCase();
  5.   str=str.replace(/_/g,' ');
  6.   str=str.substring(0,1).toUpperCase()+str.substring(1);
  7.   return str;
  8. };
  9.  
  10. var escapeHTML = function(html) {
  11.   return new String(html)
  12.     .replace(/&/gmi, '&')
  13.     .replace(/'/gmi, ''')
  14.     .replace(/"/gmi, '"')
  15.     .replace(/>/gmi, '>')
  16.     .replace(/</gmi, '<');
  17. };
  18.  
  19. var Errors = function() {
  20.   this._errors = [];
  21. };
  22.  
  23. Errors.prototype.isEmpty = function() {
  24.     return this._errors.length === 0;
  25. };
  26.  
  27. Errors.prototype.add = function(error) {
  28.   this._errors.push(error);
  29. };
  30.  
  31. Errors.prototype.__defineGetter__('length', function() { return this._errors.length; });
  32.  
  33. Errors.prototype.forEach = function(fn) {
  34.   return this._errors.forEach(function(error) {
  35.     fn(error);
  36.   });
  37. };
  38.  
  39. var Error = function(field, name, message) {
  40.   this.field   = field;
  41.   this.name    = name;
  42.   this.message = message;
  43. };
  44.  
  45. Error.prototype.html = function() {
  46.   return this.field.title + ": " + this.message;
  47. };
  48.  
  49. var Field = function(formId, options, data) {
  50.   this.type     = options.type || "text";
  51.   this.title    = options.title || options.label || humanize(options.name);
  52.   this._label   = options.label || this.title;
  53.   this.name     = formId + "[" +  options.name + "]";
  54.   this.id       = formId + "_" + options.name;
  55.   this['class'] = options['class'];
  56.   this.value_options  = options.options && options.options.split(',').map(function(v) { return v.trim(); });
  57.   this.labels   = options.labels  && options.labels.split(',').map(function(v) { return v.trim(); });  
  58.   this.value    = data ? this._cast(data[options.name]) : "";
  59.   this.placeholder = options.placeholder || "";
  60.   this.required = {"true": true, "yes": true}[options.required] || false;
  61.   this.include_blank = options.include_blank ? true : false;
  62.   this.pattern  = options.pattern || "";
  63.   this.on       = options.on || "Checked";
  64.   this.off      = options.off || "";
  65.   this.min      = this._cast(options.min);
  66.   this.max      = this._cast(options.max);
  67.   this.multiple = options.multiple || "";
  68.   this._options  = options;
  69.   if (this.type == "range") {
  70.     this.min = this.min || 0;
  71.     this.max = this.max || 100;
  72.   }
  73.   this.validate = data ? true : false;  
  74.   this._errors  = [];
  75. };
  76. Field.prototype._cast = function(value) {
  77.   var self = this;
  78.   if (value === "" || typeof value === "undefined") return "";
  79.   try {
  80.     switch(this.type) {
  81.       case "number":
  82.       case "range":
  83.         return parseInt(value, 10);
  84.       case "select":
  85.       case "radio":
  86.         var val = value.map ? value.map(function(val) { return self._castOption.call(self, val); }) : this._castOption(value);
  87.         return value.map ? Array.prototype.slice.call(val) : val;
  88.       default:
  89.         return (value && value.trim()) || "";
  90.     }
  91.   } catch(e) {
  92.     this._typeMismatch = true;
  93.     return value;
  94.   }
  95. };
  96. Field.prototype._castOption = function(value) {
  97.   value = value.trim();
  98.   if (this.value_options) {
  99.     for (var i=0; i<this.value_options.length; i++) {
  100.       if (value === this.value_options[i]) return value;
  101.     }
  102.   } else {
  103.     return "";
  104.   }
  105.   return "";  
  106. };
  107. Field.prototype.isRange = function() {
  108.   return this.type == "range" || (this.type == "number" && this.max);
  109. };
  110.  
  111. Field.prototype.label = function(options) {
  112.   return {
  113.     html: "<label for='" + this.id + "'>" + this._label + "</label>"
  114.   };
  115. };
  116. Field.prototype.classes = function() {
  117.   return [
  118.     this.type,
  119.     this.validate && this.valid()   ? "valid"    : "",
  120.     this.validate && this.invalid() ? "invalid"  : "",
  121.     this.required                   ? "required" : "optional",
  122.     this.validate && this.isRange() ? (this.rangeOverflow() || this.rangeUnderflow() ? "out-of-range" : "in-range") : ""
  123.   ].filter(function(klass) { return klass; }).join(" ");
  124. };
  125.  
  126. Field.prototype._attrFor = function(attr, enable, value) {
  127.   return enable ? " " + attr + "='" + (value || attr) + "'" : "";
  128. };
  129.  
  130. Field.prototype._htmlForOption = function(option, label) {
  131.   if (this.type == "radio") {
  132.     var id = this.id + "_" + option;
  133.     return "<label for='" + id + "'>" + label + "</label>" +
  134.            "<input type='radio' name='" + this.name + "' " + this._attrFor("required", this.required) +
  135.            " value='" + option + "' id='" + id + "' " + this._attrFor("checked", this.value === option) + "/>";
  136.   } else {
  137.     var selected = this.value.indexOf ? this.value.indexOf(option) >= 0 : this.value === option;
  138.     return "<option value='" + option + "' " + this._attrFor("selected", selected) + ">" + label + "</option>";
  139.   }
  140. };
  141.  
  142. Field.prototype.options = function() {
  143.   var self = this;
  144.   return (this.value_options || []).map(function(option, i) {
  145.     var label = self.labels && self.labels[i] || option;
  146.     return {
  147.       label: label,
  148.       option: option,
  149.       value: option,
  150.       html: function() { return self._htmlForOption(option, label); }
  151.     };
  152.   });
  153. };
  154.  
  155. Field.prototype.input = function(options) {
  156.   var field = this;
  157.   var html;
  158.   switch(this.type) {
  159.     case "textarea":
  160.       html = "<textarea id='" + this.id + "' name='" + this.name + "'" + this._attrFor("required", this.required) +
  161.              this._attrFor("placeholder", this.placeholder, this.placeholder) + ">" + escapeHTML(this.value) + "</textarea>";
  162.       break;
  163.     case "checkbox":
  164.       html = (this.off ? "<input type='hidden' name='" + this.name +"' value='" + this.off + "' />" : "") +
  165.              "<input id='" + this.id + "' name='" + this.name + "' type='" + this.type +
  166.              "' value='" + this.on + "'" +  (this.value === this.on && " checked='checked'") +
  167.              (this._attrFor("required", this.required)) + " />";
  168.       break;
  169.     case "radio":
  170.       html = this.options().map(function(option) { return option.html(); }).join("\n");
  171.       break;
  172.     case "select":
  173.       html = "<select id='" + this.id + "' name='" + this.name + (this.multiple && "[]") + "' " +
  174.              this._attrFor("required", this.required) + this._attrFor("multiple", this.multiple) + ">" +
  175.              (this.required && !this.include_blank ? "" : "<option value='' " + this._attrFor("selected", this.value === "") + "></option>") +
  176.              this.options().map(function(option) { return option.html(); }).join("\n") +
  177.              "</select>";
  178.       break;
  179.     default:
  180.       html = "<input id='" + this.id + "' name='" + this.name + "' type='" + this.type +
  181.              "' value='" + escapeHTML(this.value) + "'" + (this.placeholder ? " placeholder='" + this.placeholder + "'" : "") +
  182.              this._attrFor("multiple", this.multiple) + this._attrFor("required", this.required) +
  183.              this._attrFor("pattern", this.pattern, this.pattern) + "/>";
  184.   }
  185.   return {
  186.     name: this.name,
  187.     type: this.type,
  188.     value: this.value,
  189.     html: html
  190.   };
  191. };
  192. Field.prototype.html = function() {
  193.   if (this.type == "hidden") return this.input().html;
  194.  
  195.   return "<p class='" + (this['class'] || "field " + this.classes()) + "'>" +
  196.     this.label().html + this.input().html +
  197.   "</p>";
  198. };
  199. Field.prototype.errors = function() {
  200.   if (this._errors.length) return this._errors;
  201.   var errors = {
  202.     valueMissing: "Please fill out this field",
  203.     tooLong: "Please enter a shorter value",
  204.     patternMismatch: "Please match the requested format",
  205.     typeMismatch: "Please match the requested format",
  206.     rangeUnderflow: "Please enter a higher value",
  207.     rangeOverflow: "Please enter a lower value",
  208.     stepMismatch: "Please choose an allowed value"
  209.   };
  210.  
  211.   for (var error in errors) {
  212.     if (this[error]()) {
  213.       this["_" + error] = true;
  214.       this._errors.push(new Error(this, error, this._options[error] || errors[error]));
  215.     }
  216.   }
  217.  
  218.   return this._errors;
  219. };
  220. Field.prototype.valid = function() { return this.validate ? this.errors().length == 0 : true; };
  221. Field.prototype.invalid = function() { return !this.valid(); };
  222. Field.prototype.valueMissing = function() {
  223.   return this._valueMissing || (this.required ? this.off === this.value : false);
  224. };
  225. Field.prototype.typeMismatch = function() {
  226.   if (this._typeMismatch)   return this._typeMismatch;
  227.   if (this.pattern)         return false;
  228.   if (this.type == "email") {
  229.     var regExp = /^[a-z0-9_.%+\-]+@[0-9a-z.\-]+\.[a-z.]{2,6}$/i;
  230.     if (this.multiple) {
  231.       return this.value && this.value.split(",").reduce(function(invalid, email) {
  232.         return invalid || !regExp.test(email.trim());
  233.       }, false);
  234.     } else {
  235.       return this.value && !regExp.test(this.value);
  236.     }
  237.   }
  238.   if (this.type == "url")   return this.value && !(/[a-z][\-\.+a-z]*:\/\//i).test(this.value);
  239.   return false;
  240. };
  241. Field.prototype.patternMismatch = function() {
  242.   if (this._patternMismatch) return this._patternMismatch;
  243.   return this.pattern && this.value && !(new RegExp(this.pattern).test(this.value));
  244. };
  245. Field.prototype.tooLong = function() {
  246.   return this._tooLong || (this.maxlength ? (this.value && this.value.length > this.maxlength ? true : false) : false);
  247. };
  248. Field.prototype.rangeUnderflow = function() {
  249.   return this._rangeUnderflow || (this.min === "" ? false : this.value < this.min);
  250. };
  251. Field.prototype.rangeOverflow = function() {
  252.   return this._rangeOverflow || (this.max === "" ? false : this.value > this.max);
  253. };
  254. Field.prototype.stepMismatch = function() {
  255.   if (this._stepMismatch) return this._stepMismatch;
  256.   if (this.step) {
  257.     return ((this.value - this.min) % this.step) === 0;
  258.   }
  259.   return false;
  260. };
  261.  
  262. exports.form = function(options, enclosed) {
  263.   var name = options.name || "form";
  264.  
  265.   var success = false;
  266.   var errors = new Errors();
  267.   var title = options.title || humanize(name);
  268.   var data = request.params[name];
  269.   var submitted = request.request_method == "POST" && data;
  270.   var to = options.mail_to;
  271.   var fields = [];
  272.   var curField = 0;
  273.  
  274.   enclosed.tags(function(tag) { return tag.name == "fields";}).forEach(function(fieldsTag) {
  275.     fieldsTag.tags(function(tag) { return tag.name == "field";}).forEach(function(tag) {
  276.       var field = new Field(name, tag.options, data);
  277.       fields.push(field);
  278.       if (submitted) {
  279.         field.errors().forEach(function(error) {
  280.           errors.add(error);
  281.         });
  282.       }
  283.     });
  284.   });
  285.  
  286.   if (request.request_method == "POST" && data) {        
  287.     if (errors.isEmpty()) {
  288.       var body = [];
  289.      
  290.       fields.forEach(function(field) {
  291.         body.push(field.title + ": " + (field.value.join ? field.value.join(", ") : field.value));
  292.       });
  293.      
  294.       if (to) {
  295.         mailer.send(
  296.           to,
  297.           options.mail_subject || data.subject || "Form submission",
  298.           body.join("\n\n"),
  299.           {reply_to: data.email}
  300.         );
  301.       }
  302.       success = true;
  303.     }
  304.   }
  305.  
  306.   return success ? {success: success, fields: null} :
  307.     "<form" + (options.name ? " name='" + options.name + "'" : "") + (options.id ? " id='" + options.id + "'" : "") +
  308.           " action='" + (options.action || "") + "' method='post' " + (options.novalidate ? "novalidate='novalidate'" : "") + ">" +
  309.     enclosed.render({
  310.       success: false,
  311.       fields: {
  312.         field: function(fieldOptions) {
  313.           var field = fields[curField];
  314.           curField++;
  315.           return field;
  316.         },
  317.         errors: errors.isEmpty() ? null : function(options, enclosing) {
  318.           return enclosing ? errors : {html: errors._errors.map(function(error) { return error.html(); }).join("<br />")};
  319.         }
  320.       }
  321.     }) +
  322.     "</form>";
  323. };