rudimentary validation in xoslib
diff --git a/planetstack/core/xoslib/static/css/xosAdminSite.css b/planetstack/core/xoslib/static/css/xosAdminSite.css
index f09bbcf..ad9eb47 100644
--- a/planetstack/core/xoslib/static/css/xosAdminSite.css
+++ b/planetstack/core/xoslib/static/css/xosAdminSite.css
@@ -66,6 +66,11 @@
text-decoration:none;
}
+.help-inline.error {
+ color: red;
+ font-weight: bold;
+}
+
/* these are for the inline list and detail titles */
.xos-list-title {
diff --git a/planetstack/core/xoslib/static/js/xoslib/xos-backbone.js b/planetstack/core/xoslib/static/js/xoslib/xos-backbone.js
index bbf13a4..a04fd8f 100644
--- a/planetstack/core/xoslib/static/js/xoslib/xos-backbone.js
+++ b/planetstack/core/xoslib/static/js/xoslib/xos-backbone.js
@@ -68,6 +68,21 @@
}
}
return res;
+ },
+
+ 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 @@
modelAttrs["defaults"] = xosdefaults[modelName];
}
+ if (xosvalidators && xosvalidators[modelName]) {
+ modelAttrs["validators"] = xosvalidators[modelName];
+ }
+
lib[modelName] = XOSModel.extend(modelAttrs);
collectionAttrs["model"] = lib[modelName];
diff --git a/planetstack/core/xoslib/static/js/xoslib/xos-util.js b/planetstack/core/xoslib/static/js/xoslib/xos-util.js
index 79ce0f8..bebc44a 100644
--- a/planetstack/core/xoslib/static/js/xoslib/xos-util.js
+++ b/planetstack/core/xoslib/static/js/xoslib/xos-util.js
@@ -21,3 +21,19 @@
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;
+}
diff --git a/planetstack/core/xoslib/static/js/xoslib/xosHelper.js b/planetstack/core/xoslib/static/js/xoslib/xosHelper.js
index 8fa27a6..30835c7 100644
--- a/planetstack/core/xoslib/static/js/xoslib/xosHelper.js
+++ b/planetstack/core/xoslib/static/js/xoslib/xosHelper.js
@@ -264,10 +264,22 @@
save: function() {
this.app.hideError();
- var infoMsgId = this.app.showInformational( {what: "save " + model.modelName + " " + model.attributes.humanReadableName, status: "", statusText: "in progress..."} );
var data = Backbone.Syphon.serialize(this);
var that = this;
var isNew = !this.model.id;
+
+ /* although model.validate() is called automatically by
+ model.save, we call it ourselves, so we can throw up our
+ validation error before creating the infoMsg in the log
+ */
+ errors = this.model.validate(data);
+ if (errors) {
+ this.onFormDataInvalid(errors);
+ return;
+ }
+
+ var infoMsgId = this.app.showInformational( {what: "save " + model.modelName + " " + model.attributes.humanReadableName, status: "", statusText: "in progress..."} );
+
this.model.save(data, {error: function(model, result, xhr) { that.saveError(model,result,xhr,infoMsgId);},
success: function(model, result, xhr) { that.saveSuccess(model,result,xhr,infoMsgId);}});
if (isNew) {
@@ -356,6 +368,19 @@
this.tabClick('#xos-nav-detail', 'detail');
},
+ onFormDataInvalid: function(errors) {
+ var self=this;
+ var markErrors = function(value, key) {
+ console.log("name='" + key + "'");
+ var $inputElement = self.$el.find("[name='" + key + "']");
+ var $inputContainer = $inputElement.parent();
+ $inputContainer.find(".help-inline").remove();
+ var $errorEl = $("<span>", {class: "help-inline error", text: value});
+ $inputContainer.append($errorEl).addClass("error");
+ }
+ _.each(errors, markErrors);
+ },
+
});
/* XOSItemView