rudimentary validation in xoslib
authorScott Baker <smbaker@gmail.com>
Wed, 26 Nov 2014 01:15:21 +0000 (17:15 -0800)
committerScott Baker <smbaker@gmail.com>
Wed, 26 Nov 2014 01:15:21 +0000 (17:15 -0800)
planetstack/core/xoslib/dashboards/test.html
planetstack/core/xoslib/dashboards/xosAdminDashboard.html
planetstack/core/xoslib/dashboards/xosAdminWholePage.html
planetstack/core/xoslib/static/css/xosAdminSite.css
planetstack/core/xoslib/static/js/xoslib/xos-backbone.js
planetstack/core/xoslib/static/js/xoslib/xos-util.js
planetstack/core/xoslib/static/js/xoslib/xosHelper.js

index 27b0b8d..e6faf3a 100644 (file)
@@ -9,6 +9,7 @@
 
 <script src="{{ STATIC_URL }}/js/xoslib/xos-util.js"></script>
 <script src="{{ STATIC_URL }}/js/xoslib/xos-defaults.js"></script>
+<script src="{{ STATIC_URL }}/js/xoslib/xos-validators.js"></script>
 <script src="{{ STATIC_URL }}/js/xoslib/xos-backbone.js"></script>
 <script src="{{ STATIC_URL }}/js/xoslib/xosHelper.js"></script>
 <script src="{{ STATIC_URL }}/js/test.js"></script>
index 8011884..1544d9f 100644 (file)
@@ -11,6 +11,7 @@
 
 <script src="{{ STATIC_URL }}/js/xoslib/xos-util.js"></script>
 <script src="{{ STATIC_URL }}/js/xoslib/xos-defaults.js"></script>
+<script src="{{ STATIC_URL }}/js/xoslib/xos-validators.js"></script>
 <script src="{{ STATIC_URL }}/js/xoslib/xos-backbone.js"></script>
 <script src="{{ STATIC_URL }}/js/xoslib/xosHelper.js"></script>
 <script src="{{ STATIC_URL }}/js/xosAdminSite.js"></script>
index 4af6093..8aaa8c1 100644 (file)
@@ -11,6 +11,7 @@
 
 <script src="{{ STATIC_URL }}/js/xoslib/xos-util.js"></script>
 <script src="{{ STATIC_URL }}/js/xoslib/xos-defaults.js"></script>
+<script src="{{ STATIC_URL }}/js/xoslib/xos-validators.js"></script>
 <script src="{{ STATIC_URL }}/js/xoslib/xos-backbone.js"></script>
 <script src="{{ STATIC_URL }}/js/xoslib/xosHelper.js"></script>
 <script src="{{ STATIC_URL }}/js/xosAdminSite.js"></script>
index f09bbcf..ad9eb47 100644 (file)
     text-decoration:none;
 }
 
+.help-inline.error {
+    color: red;
+    font-weight: bold;
+}
+
 /* these are for the inline list and detail titles */
 
 .xos-list-title {
index bbf13a4..a04fd8f 100644 (file)
@@ -68,6 +68,21 @@ if (! window.XOSLIB_LOADED ) {
                     }\r
                 }\r
                 return res;\r
+            },
+
+            validate: function(attrs, options) {
+                errors = {};
+                _.each(this.validators, function(validatorList, fieldName) {
+                    _.each(validatorList, function(validator) {
+                        if (fieldName in attrs) {
+                            validatorResult = validateField(validator, attrs[fieldName])
+                            if (validatorResult != true) {
+                                errors[fieldName] = validatorResult;
+                            }
+                        }
+                    });
+                });
+                return errors;
             }
     });
 
@@ -287,6 +302,10 @@ if (! window.XOSLIB_LOADED ) {
             modelAttrs["defaults"] = xosdefaults[modelName];
         }
 
+        if (xosvalidators && xosvalidators[modelName]) {
+            modelAttrs["validators"] = xosvalidators[modelName];
+        }
+
         lib[modelName] = XOSModel.extend(modelAttrs);
 
         collectionAttrs["model"] = lib[modelName];
index 79ce0f8..bebc44a 100644 (file)
@@ -21,3 +21,19 @@ function limitTableRows(tableSelector, maxRows) {
         wrapper.style.height = height + "px";
     }
 }
+
+function validateField(validatorName, value) {
+    switch (validatorName) {
+        case "notBlank":
+            if ((value==undefined) || (value=="")) {
+                return "can not be blank";
+            }
+            break;
+        case "isUrl":
+            if (! /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(value)) {
+                return "must be a valid url";
+            }
+            break;
+    }
+    return true;
+}
index 8fa27a6..30835c7 100644 (file)
@@ -264,10 +264,22 @@ XOSDetailView = Marionette.ItemView.extend({
 
             save: function() {
                 this.app.hideError();
-                var infoMsgId = this.app.showInformational( {what: "save " + model.modelName + " " + model.attributes.humanReadableName, status: "", statusText: "in progress..."} );\r
                 var data = Backbone.Syphon.serialize(this);\r
                 var that = this;\r
                 var isNew = !this.model.id;\r
+\r
+                /* although model.validate() is called automatically by\r
+                   model.save, we call it ourselves, so we can throw up our\r
+                   validation error before creating the infoMsg in the log\r
+                */\r
+                errors =  this.model.validate(data);\r
+                if (errors) {\r
+                    this.onFormDataInvalid(errors);\r
+                    return;\r
+                }\r
+\r
+                var infoMsgId = this.app.showInformational( {what: "save " + model.modelName + " " + model.attributes.humanReadableName, status: "", statusText: "in progress..."} );\r
+\r
                 this.model.save(data, {error: function(model, result, xhr) { that.saveError(model,result,xhr,infoMsgId);},\r
                                        success: function(model, result, xhr) { that.saveSuccess(model,result,xhr,infoMsgId);}});\r
                 if (isNew) {\r
@@ -356,6 +368,19 @@ XOSDetailView = Marionette.ItemView.extend({
                     this.tabClick('#xos-nav-detail', 'detail');\r
               },\r
 \r
+            onFormDataInvalid: function(errors) {\r
+                var self=this;\r
+                var markErrors = function(value, key) {\r
+                    console.log("name='" + key + "'");\r
+                    var $inputElement = self.$el.find("[name='" + key + "']");\r
+                    var $inputContainer = $inputElement.parent();\r
+                    $inputContainer.find(".help-inline").remove();\r
+                    var $errorEl = $("<span>", {class: "help-inline error", text: value});\r
+                    $inputContainer.append($errorEl).addClass("error");\r
+                }\r
+                _.each(errors, markErrors);\r
+            },\r
+\r
 });\r
 
 /* XOSItemView