Repository: mockup
Branch: refs/heads/master
Date: 2014-11-13T13:00:42+01:00
Author: JC Brand (jcbrand) <jc@opkode.com>
Commit: https://github.com/plone/mockup/commit/b389fbd8ed012be46c74e9c338c7eabce43d72fa

Updates #299. New pattern for legacy "inline validation" JS.

Files changed:
A mockup/patterns/inlinevalidation/pattern.js
A mockup/tests/pattern-inlinevalidation-test.js
M mockup/js/bundles/docs.js
M mockup/js/config.js

diff --git a/mockup/js/bundles/docs.js b/mockup/js/bundles/docs.js
index 43b60e7..8db620f 100644
--- a/mockup/js/bundles/docs.js
+++ b/mockup/js/bundles/docs.js
@@ -57,6 +57,11 @@ require([
             description: 'A pattern to warn user when changes are unsaved and they try to navigate away from page',
             url: 'patterns/formunloadalert/pattern.js'
           },
+          { id: 'inlinevalidation',
+            title: 'Inline Validation',
+            description: 'Client side validation of form fields',
+            url: 'patterns/inlinevalidation/pattern.js'
+          },
           { id: 'modal',
             title: 'Modal',
             description: 'Creates a modal dialog (also called overlay)',
diff --git a/mockup/js/config.js b/mockup/js/config.js
index 7b27662..1ab1ef6 100644
--- a/mockup/js/config.js
+++ b/mockup/js/config.js
@@ -68,6 +68,7 @@
       'mockup-patterns-filemanager-url': 'patterns/filemanager',
       'mockup-patterns-formautofocus': 'patterns/formautofocus/pattern',
       'mockup-patterns-formunloadalert': 'patterns/formunloadalert/pattern',
+      'mockup-patterns-inlinevalidation': 'patterns/inlinevalidation/pattern',
       'mockup-patterns-modal': 'patterns/modal/pattern',
       'mockup-patterns-moment': 'patterns/moment/pattern',
       'mockup-patterns-pickadate': 'patterns/pickadate/pattern',
diff --git a/mockup/patterns/inlinevalidation/pattern.js b/mockup/patterns/inlinevalidation/pattern.js
new file mode 100644
index 0000000..a79b4dd
--- /dev/null
+++ b/mockup/patterns/inlinevalidation/pattern.js
@@ -0,0 +1,135 @@
+/* Inline Validation pattern.
+ *
+ * Options:
+ *    type(string): The type of form generating library. Either z3c.form, formlib or archetypes
+ *
+ * Documentation:
+ *
+ *    # z3c.form
+ *
+ *    {{ example-1 }}
+ *
+ * Example: example-1
+ *    <div class="pat-inlinevalidation" data-pat-upload='{"type": "z3c.form"}'>
+ *      <input id="form-widgets-IDublinCore-title"
+ *             name="form.widgets.IDublinCore.title"
+ *             class="text-widget required textline-field"
+ *             value="Welcome to Plone" type="text">
+ *    </div>
+ */
+
+define([
+  'jquery',
+  'mockup-patterns-base'
+], function ($, Base) {
+  'use strict';
+
+  var InlineValidation = Base.extend({
+    name: 'inlinevalidation',
+
+    render_error: function ($field, errmsg) {
+       var $errbox = $('div.fieldErrorBox', $field);
+       if (errmsg !== '') {
+           $field.addClass('error');
+           $errbox.html(errmsg);
+       } else {
+           $field.removeClass('error');
+           $errbox.html('');
+       }
+    },
+
+    append_url_path: function (url, extra) {
+        // Add '/extra' on to the end of the URL, respecting querystring
+        var i, ret, urlParts = url.split(/\?/);
+        ret = urlParts[0];
+        if (ret[ret.length - 1] !== '/') { ret += '/'; }
+        ret += extra;
+        for (i = 1; i < urlParts.length; i+=1) {
+            ret += '?' + urlParts[i];
+        }
+        return ret;
+    },
+
+    validate_archetypes_field: function (input) {
+        var $input = $(input),
+            $field = $input.closest('.field'),
+            uid = $field.attr('data-uid'),
+            fname = $field.attr('data-fieldname'),
+            value = $input.val();
+
+        // value is null for empty multiSelection select, turn it into a [] instead
+        // so it does not break at_validate_field
+        if ($input.attr('multiple') === 'multiple' && value === null) {
+            value = $([]).serialize();
+        }
+
+        // if value is an Array, it will be send as value[]=value1&value[]=value2 by $.post
+        // turn it into something that will be useable or value will be omitted from the request
+        var traditional;
+        var params = $.param({uid: uid, fname: fname, value: value}, traditional = true);
+        if ($field && uid && fname) {
+            $.post($('base').attr('href') + '/at_validate_field', params, function (data) {
+                this.render_error($field, data.errmsg);
+            });
+        }
+    },
+
+    validate_formlib_field: function (input) {
+        var $input = $(input),
+            $field = $input.closest('.field'),
+            $form = $field.closest('form'),
+            fname = $field.attr('data-fieldname');
+
+        $form.ajaxSubmit({
+            url: this.append_url_path($form.attr('action'), '@@formlib_validate_field'),
+            data: {fname: fname},
+            iframe: false,
+            success: $.proxy(function (data) {
+                this.render_error($field, data.errmsg);
+            }, this),
+            dataType: 'json'
+        });
+    },
+
+    validate_z3cform_field: function (input) {
+        var $input = $(input),
+            $field = $input.closest('.field'),
+            $form = $field.closest('form'),
+            fset = $input.closest('fieldset').attr('data-fieldset'),
+            fname = $field.attr('data-fieldname');
+
+        if (fname) {
+            $form.ajaxSubmit({
+                url: this.append_url_path($form.attr('action'), '@@z3cform_validate_field'),
+                data: {fname: fname, fset: fset},
+                iframe: false,
+                success: $.proxy(function (data) {
+                    this.render_error($field, data.errmsg);
+                }, this),
+                dataType: 'json'
+            });
+        }
+    },
+
+    init: function () {
+
+      this.$el.find(
+          'input[type="text"], ' +
+          'input[type="password"], ' +
+          'input[type="checkbox"], ' +
+          'select, ' +
+          'textarea').on('blur', 
+
+          $.proxy(function (ev) {
+            if (this.options.type === 'archetypes') {
+              this.validate_archetypes_field(ev.target);
+            } else if (this.options.type === 'z3c.form') {
+              this.validate_z3cform_field(ev.target);
+            } else if (this.options.type === 'formlib') {
+              this.validate_formlib_field(ev.target);
+            }
+          }, this));
+      },
+  });
+  return InlineValidation;
+});
diff --git a/mockup/tests/pattern-inlinevalidation-test.js b/mockup/tests/pattern-inlinevalidation-test.js
new file mode 100644
index 0000000..8795c65
--- /dev/null
+++ b/mockup/tests/pattern-inlinevalidation-test.js
@@ -0,0 +1,57 @@
+define([
+  'expect',
+  'jquery',
+  'sinon',
+  'mockup-registry',
+  'mockup-patterns-inlinevalidation'
+], function(expect, $, sinon, registry, Pattern) {
+  'use strict';
+
+  window.mocha.setup('bdd');
+  $.fx.off = true;
+
+/* ==========================
+   TEST: Inline Validation
+   ========================== */
+
+  describe('Inline Validation', function () {
+    afterEach(function() {
+      $('body').empty();
+    });
+
+    it('A z3c.form input is validated on the "blur" event', function() {
+      var widget = '<div class="pat-inlinevalidation" data-pat-inlinevalidation=\'{"type": "z3c.form"}\'>' +
+        '  <input id="form-widgets-IDublinCore-title"' +
+        '         name="form.widgets.IDublinCore.title"' +
+        '         class="text-widget required textline-field"' +
+        '         value="Welcome to Plone" type="text">' +
+        '</div>';
+      var $el = $(widget).appendTo('body');
+      Pattern.extend({validate_z3cform_field: sinon.spy()});
+      var pattern = registry.init($el, 'inlinevalidation', {type: 'z3c.form'});
+      expect(pattern.validate_z3cform_field.called).to.equal(false);
+      $el.children('input').blur();
+      expect(pattern.validate_z3cform_field.called).to.equal(true);
+    });
+
+    it('A formlib input is validated on the "blur" event', function() {
+      var $el = $('<div class="pat-inlinevalidation" data-pat-inlinevalidation=\'{"type": "formlib"}\'> <input type="text"> </div>')
+                .appendTo('body');
+      Pattern.extend({validate_formlib_field: sinon.spy()});
+      var pattern = registry.init($el, 'inlinevalidation', {type: 'formlib'});
+      expect(pattern.validate_formlib_field.called).to.equal(false);
+      $el.children('input').blur();
+      expect(pattern.validate_formlib_field.called).to.equal(true);
+    });
+
+    it('An archetypes input is validated on the "blur" event', function() {
+      var $el = $('<div class="pat-inlinevalidation" data-pat-inlinevalidation=\'{"type": "archetypes"}\'> <input class="blurrable" type="text"> </div>')
+                .appendTo('body');
+      Pattern.extend({validate_archetypes_field: sinon.spy()});
+      var pattern = registry.init($el, 'inlinevalidation', {type: 'archetypes'});
+      expect(pattern.validate_archetypes_field.called).to.equal(false);
+      $el.children('input').blur();
+      expect(pattern.validate_archetypes_field.called).to.equal(true);
+    });
+  });
+});


Repository: mockup
Branch: refs/heads/master
Date: 2014-11-13T09:00:09-06:00
Author: Nathan Van Gheem (vangheem) <vangheem@gmail.com>
Commit: https://github.com/plone/mockup/commit/4b19b3a62ce1c7c0ae76b7d2a2c1b197bb967cf1

Merge pull request #437 from plone/inlinevalidation

 New pattern for legacy "inline validation" JS.

Files changed:
A mockup/patterns/inlinevalidation/pattern.js
A mockup/tests/pattern-inlinevalidation-test.js
M mockup/js/bundles/docs.js
M mockup/js/config.js

diff --git a/mockup/js/bundles/docs.js b/mockup/js/bundles/docs.js
index 43b60e7..8db620f 100644
--- a/mockup/js/bundles/docs.js
+++ b/mockup/js/bundles/docs.js
@@ -57,6 +57,11 @@ require([
             description: 'A pattern to warn user when changes are unsaved and they try to navigate away from page',
             url: 'patterns/formunloadalert/pattern.js'
           },
+          { id: 'inlinevalidation',
+            title: 'Inline Validation',
+            description: 'Client side validation of form fields',
+            url: 'patterns/inlinevalidation/pattern.js'
+          },
           { id: 'modal',
             title: 'Modal',
             description: 'Creates a modal dialog (also called overlay)',
diff --git a/mockup/js/config.js b/mockup/js/config.js
index 7b27662..1ab1ef6 100644
--- a/mockup/js/config.js
+++ b/mockup/js/config.js
@@ -68,6 +68,7 @@
       'mockup-patterns-filemanager-url': 'patterns/filemanager',
       'mockup-patterns-formautofocus': 'patterns/formautofocus/pattern',
       'mockup-patterns-formunloadalert': 'patterns/formunloadalert/pattern',
+      'mockup-patterns-inlinevalidation': 'patterns/inlinevalidation/pattern',
       'mockup-patterns-modal': 'patterns/modal/pattern',
       'mockup-patterns-moment': 'patterns/moment/pattern',
       'mockup-patterns-pickadate': 'patterns/pickadate/pattern',
diff --git a/mockup/patterns/inlinevalidation/pattern.js b/mockup/patterns/inlinevalidation/pattern.js
new file mode 100644
index 0000000..a79b4dd
--- /dev/null
+++ b/mockup/patterns/inlinevalidation/pattern.js
@@ -0,0 +1,135 @@
+/* Inline Validation pattern.
+ *
+ * Options:
+ *    type(string): The type of form generating library. Either z3c.form, formlib or archetypes
+ *
+ * Documentation:
+ *
+ *    # z3c.form
+ *
+ *    {{ example-1 }}
+ *
+ * Example: example-1
+ *    <div class="pat-inlinevalidation" data-pat-upload='{"type": "z3c.form"}'>
+ *      <input id="form-widgets-IDublinCore-title"
+ *             name="form.widgets.IDublinCore.title"
+ *             class="text-widget required textline-field"
+ *             value="Welcome to Plone" type="text">
+ *    </div>
+ */
+
+define([
+  'jquery',
+  'mockup-patterns-base'
+], function ($, Base) {
+  'use strict';
+
+  var InlineValidation = Base.extend({
+    name: 'inlinevalidation',
+
+    render_error: function ($field, errmsg) {
+       var $errbox = $('div.fieldErrorBox', $field);
+       if (errmsg !== '') {
+           $field.addClass('error');
+           $errbox.html(errmsg);
+       } else {
+           $field.removeClass('error');
+           $errbox.html('');
+       }
+    },
+
+    append_url_path: function (url, extra) {
+        // Add '/extra' on to the end of the URL, respecting querystring
+        var i, ret, urlParts = url.split(/\?/);
+        ret = urlParts[0];
+        if (ret[ret.length - 1] !== '/') { ret += '/'; }
+        ret += extra;
+        for (i = 1; i < urlParts.length; i+=1) {
+            ret += '?' + urlParts[i];
+        }
+        return ret;
+    },
+
+    validate_archetypes_field: function (input) {
+        var $input = $(input),
+            $field = $input.closest('.field'),
+            uid = $field.attr('data-uid'),
+            fname = $field.attr('data-fieldname'),
+            value = $input.val();
+
+        // value is null for empty multiSelection select, turn it into a [] instead
+        // so it does not break at_validate_field
+        if ($input.attr('multiple') === 'multiple' && value === null) {
+            value = $([]).serialize();
+        }
+
+        // if value is an Array, it will be send as value[]=value1&value[]=value2 by $.post
+        // turn it into something that will be useable or value will be omitted from the request
+        var traditional;
+        var params = $.param({uid: uid, fname: fname, value: value}, traditional = true);
+        if ($field && uid && fname) {
+            $.post($('base').attr('href') + '/at_validate_field', params, function (data) {
+                this.render_error($field, data.errmsg);
+            });
+        }
+    },
+
+    validate_formlib_field: function (input) {
+        var $input = $(input),
+            $field = $input.closest('.field'),
+            $form = $field.closest('form'),
+            fname = $field.attr('data-fieldname');
+
+        $form.ajaxSubmit({
+            url: this.append_url_path($form.attr('action'), '@@formlib_validate_field'),
+            data: {fname: fname},
+            iframe: false,
+            success: $.proxy(function (data) {
+                this.render_error($field, data.errmsg);
+            }, this),
+            dataType: 'json'
+        });
+    },
+
+    validate_z3cform_field: function (input) {
+        var $input = $(input),
+            $field = $input.closest('.field'),
+            $form = $field.closest('form'),
+            fset = $input.closest('fieldset').attr('data-fieldset'),
+            fname = $field.attr('data-fieldname');
+
+        if (fname) {
+            $form.ajaxSubmit({
+                url: this.append_url_path($form.attr('action'), '@@z3cform_validate_field'),
+                data: {fname: fname, fset: fset},
+                iframe: false,
+                success: $.proxy(function (data) {
+                    this.render_error($field, data.errmsg);
+                }, this),
+                dataType: 'json'
+            });
+        }
+    },
+
+    init: function () {
+
+      this.$el.find(
+          'input[type="text"], ' +
+          'input[type="password"], ' +
+          'input[type="checkbox"], ' +
+          'select, ' +
+          'textarea').on('blur', 
+
+          $.proxy(function (ev) {
+            if (this.options.type === 'archetypes') {
+              this.validate_archetypes_field(ev.target);
+            } else if (this.options.type === 'z3c.form') {
+              this.validate_z3cform_field(ev.target);
+            } else if (this.options.type === 'formlib') {
+              this.validate_formlib_field(ev.target);
+            }
+          }, this));
+      },
+  });
+  return InlineValidation;
+});
diff --git a/mockup/tests/pattern-inlinevalidation-test.js b/mockup/tests/pattern-inlinevalidation-test.js
new file mode 100644
index 0000000..8795c65
--- /dev/null
+++ b/mockup/tests/pattern-inlinevalidation-test.js
@@ -0,0 +1,57 @@
+define([
+  'expect',
+  'jquery',
+  'sinon',
+  'mockup-registry',
+  'mockup-patterns-inlinevalidation'
+], function(expect, $, sinon, registry, Pattern) {
+  'use strict';
+
+  window.mocha.setup('bdd');
+  $.fx.off = true;
+
+/* ==========================
+   TEST: Inline Validation
+   ========================== */
+
+  describe('Inline Validation', function () {
+    afterEach(function() {
+      $('body').empty();
+    });
+
+    it('A z3c.form input is validated on the "blur" event', function() {
+      var widget = '<div class="pat-inlinevalidation" data-pat-inlinevalidation=\'{"type": "z3c.form"}\'>' +
+        '  <input id="form-widgets-IDublinCore-title"' +
+        '         name="form.widgets.IDublinCore.title"' +
+        '         class="text-widget required textline-field"' +
+        '         value="Welcome to Plone" type="text">' +
+        '</div>';
+      var $el = $(widget).appendTo('body');
+      Pattern.extend({validate_z3cform_field: sinon.spy()});
+      var pattern = registry.init($el, 'inlinevalidation', {type: 'z3c.form'});
+      expect(pattern.validate_z3cform_field.called).to.equal(false);
+      $el.children('input').blur();
+      expect(pattern.validate_z3cform_field.called).to.equal(true);
+    });
+
+    it('A formlib input is validated on the "blur" event', function() {
+      var $el = $('<div class="pat-inlinevalidation" data-pat-inlinevalidation=\'{"type": "formlib"}\'> <input type="text"> </div>')
+                .appendTo('body');
+      Pattern.extend({validate_formlib_field: sinon.spy()});
+      var pattern = registry.init($el, 'inlinevalidation', {type: 'formlib'});
+      expect(pattern.validate_formlib_field.called).to.equal(false);
+      $el.children('input').blur();
+      expect(pattern.validate_formlib_field.called).to.equal(true);
+    });
+
+    it('An archetypes input is validated on the "blur" event', function() {
+      var $el = $('<div class="pat-inlinevalidation" data-pat-inlinevalidation=\'{"type": "archetypes"}\'> <input class="blurrable" type="text"> </div>')
+                .appendTo('body');
+      Pattern.extend({validate_archetypes_field: sinon.spy()});
+      var pattern = registry.init($el, 'inlinevalidation', {type: 'archetypes'});
+      expect(pattern.validate_archetypes_field.called).to.equal(false);
+      $el.children('input').blur();
+      expect(pattern.validate_archetypes_field.called).to.equal(true);
+    });
+  });
+});