diff --git a/examples/widgetapp/own_validator.e b/examples/widgetapp/own_validator.e new file mode 100644 index 00000000..1ba0395a --- /dev/null +++ b/examples/widgetapp/own_validator.e @@ -0,0 +1,31 @@ +note + description: "Summary description for {OWN_VALIDATOR}." + author: "" + date: "$Date$" + revision: "$Revision$" + +class + OWN_VALIDATOR + +inherit + + WSF_VALIDATOR [STRING] + +create + make_own + +feature {NONE} + + make_own + do + error := "Input to long" + end + +feature + + is_valid (input: STRING): BOOLEAN + do + Result := input.count < 5 + end + +end diff --git a/examples/widgetapp/sample_page.e b/examples/widgetapp/sample_page.e index a4040ada..f3452c73 100644 --- a/examples/widgetapp/sample_page.e +++ b/examples/widgetapp/sample_page.e @@ -23,36 +23,49 @@ feature initialize_controls local form: WSF_FORM_CONTROL + n1_container: WSF_FORM_ELEMENT_CONTROL [STRING] + n2_container: WSF_FORM_ELEMENT_CONTROL [STRING] do - create textbox1.make_text ("txtBox1", "1") - create textbox2.make_text ("txtBox2", "2") + create textbox1.make_input ("txtBox1", "1") + create textbox2.make_input ("txtBox2", "2") create button1.make_button ("sample_button1", "SUM") create textbox_result.make_textarea ("txtBox3", "") button1.set_click_event (agent handle_click) + button1.add_class ("col-lg-offset-2") create form.make_form_control ("panel") form.add_class ("form-horizontal") - create cklist.make_checkbox_list_control("categories") - cklist.add_control (create {WSF_CHECKBOX_CONTROL}.make_checkbox("net","Network","net")) - cklist.add_control (create {WSF_CHECKBOX_CONTROL}.make_checkbox("os","Operating Systems","os")) - form.add_control (create {WSF_FORM_ELEMENT_CONTROL[STRING]}.make_form_element("Number1",textbox1)) - form.add_control (create {WSF_FORM_ELEMENT_CONTROL[STRING]}.make_form_element("Number2",textbox2)) - form.add_control (create {WSF_FORM_ELEMENT_CONTROL[LIST[STRING]]}.make_form_element("Categories",cklist)) + create cklist.make_checkbox_list_control ("categories") + cklist.add_control (create {WSF_CHECKBOX_CONTROL}.make_checkbox ("net", "Network", "net")) + cklist.add_control (create {WSF_CHECKBOX_CONTROL}.make_checkbox ("os", "Operating Systems", "os")) + create n1_container.make_form_element ("Number1", textbox1) + n1_container.add_validator (create {WSF_DECIMAL_VALIDATOR}.make_decimal_validator ("Invalid Number")) + n1_container.add_validator (create {OWN_VALIDATOR}.make_own) + create n2_container.make_form_element ("Number2", textbox2) + n2_container.add_validator (create {WSF_DECIMAL_VALIDATOR}.make_decimal_validator ("Invalid Number")) + form.add_control (n1_container) + form.add_control (n2_container) + form.add_control (create {WSF_FORM_ELEMENT_CONTROL [LIST [STRING]]}.make_form_element ("Categories", cklist)) form.add_control (button1) - form.add_control (create {WSF_FORM_ELEMENT_CONTROL[STRING]}.make_form_element("Result",textbox_result)) + form.add_control (create {WSF_FORM_ELEMENT_CONTROL [STRING]}.make_form_element ("Result", textbox_result)) control := form end handle_click local - text:STRING + text: STRING do - text := textbox1.text + " + " + textbox2.text + " = " + (textbox1.text.to_integer_16 + textbox2.text.to_integer_16).out - across - cklist.value as s - loop - text.append ("%N-"+s.item) + if attached {WSF_FORM_CONTROL} control as form then + form.validate + if form.is_valid then + text := textbox1.text + " + " + textbox2.text + " = " + (textbox1.text.to_integer_64 + textbox2.text.to_integer_64).out + across + cklist.value as s + loop + text.append ("%N-" + s.item) + end + textbox_result.set_text (text) + end end - textbox_result.set_text (text) end process @@ -61,9 +74,9 @@ feature button1: WSF_BUTTON_CONTROL - textbox1: WSF_TEXT_CONTROL + textbox1: WSF_INPUT_CONTROL - textbox2: WSF_TEXT_CONTROL + textbox2: WSF_INPUT_CONTROL cklist: WSF_CHECKBOX_LIST_CONTROL diff --git a/examples/widgetapp/widget.coffee b/examples/widgetapp/widget.coffee index 9ca48ee9..814ace72 100644 --- a/examples/widgetapp/widget.coffee +++ b/examples/widgetapp/widget.coffee @@ -10,17 +10,53 @@ trigger_callback = (control_name,event)-> for name,state of new_states controls[name]?.update(state) return +class WSF_VALIDATOR + constructor: (@parent_control, @settings)-> + @error = @settings.error + return + + validate: ()-> + return true + +class WSF_REGEXP_VALIDATOR extends WSF_VALIDATOR + constructor: ()-> + super + @pattern = new RegExp(@settings.expression,'g') + + validate: ()-> + val = @parent_control.value() + res = val.match(@pattern) + return (res!=null) + +validatormap = + "WSF_REGEXP_VALIDATOR":WSF_REGEXP_VALIDATOR class WSF_CONTROL constructor: (@control_name, @$el)-> - @attach_events() return attach_events: ()-> return update: (state)-> - return + return + + #Simple event listener + on: (name, callback, context)-> + if not @_events? + @_events = {} + if not @_events[name]? + @_events[name] = [] + @_events[name].push({callback:callback,context:context}) + return @ + + trigger: (name)-> + if not @_events?[name]? + return @ + for ev in @_events[name] + ev.callback.call(ev.context) + return @ + controls = {} @@ -39,16 +75,21 @@ class WSF_BUTTON_CONTROL extends WSF_CONTROL window.states[@control_name]['text'] = state.text @$el.text(state.text) -class WSF_TEXT_CONTROL extends WSF_CONTROL +class WSF_INPUT_CONTROL extends WSF_CONTROL attach_events: ()-> self = @ @$el.change ()-> self.change() + change: ()-> #update local state window.states[@control_name]['text'] = @$el.val() if window.states[@control_name]['callback_change'] trigger_callback(@control_name, 'change') + @trigger('change') + + value:()-> + return @$el.val() update: (state) -> if state.text? @@ -65,6 +106,10 @@ class WSF_TEXTAREA_CONTROL extends WSF_CONTROL window.states[@control_name]['text'] = @$el.val() if window.states[@control_name]['callback_change'] trigger_callback(@control_name, 'change') + @trigger('change') + + value:()-> + return @$el.val() update: (state) -> if state.text? @@ -76,23 +121,70 @@ class WSF_CHECKBOX_CONTROL extends WSF_CONTROL self = @ @$el.change ()-> self.change() + change: ()-> #update local state window.states[@control_name]['checked'] = @$el.is(':checked') if window.states[@control_name]['callback_change'] trigger_callback(@control_name, 'change') + @trigger('change') + + value:()-> + return @$el.is(':checked') update: (state) -> if state.text? window.states[@control_name]['checked'] = state.checked @$el.prop('checked',state.checked) +class WSF_FORM_ELEMENT_CONTROL extends WSF_CONTROL + attach_events: ()-> + self = @ + @value_control = controls[window.states[@control_name]['value_control']] + if @value_control? + @value_control.on('change',@change,@) + @serverside_validator = false + @validators = [] + for validator in window.states[@control_name]['validators'] + if validatormap[validator.name]? + @validators.push new validatormap[validator.name](@,validator) + else + #Use serverside validator if no js implementation + @serverside_validator = true + return + + change: ()-> + for validator in @validators + if not validator.validate() + @showerror(validator.error) + return + @showerror("") + if @serverside_validator + trigger_callback(@control_name, 'validate') + return + + showerror: (message)-> + @$el.removeClass("has-error") + @$el.find(".validation").remove() + if message.length>0 + @$el.addClass("has-error") + errordiv = $("
").addClass('help-block').addClass('validation').text(message) + @$el.find(".col-lg-10").append(errordiv) + + update: (state) -> + if state.error? + @showerror(state.error) + + value: ()-> + @value_control.value() + #map class name to effective class typemap = "WSF_BUTTON_CONTROL":WSF_BUTTON_CONTROL - "WSF_TEXT_CONTROL":WSF_TEXT_CONTROL + "WSF_INPUT_CONTROL":WSF_INPUT_CONTROL "WSF_TEXTAREA_CONTROL":WSF_TEXTAREA_CONTROL "WSF_CHECKBOX_CONTROL":WSF_CHECKBOX_CONTROL + "WSF_FORM_ELEMENT_CONTROL": WSF_FORM_ELEMENT_CONTROL #create a js class for each control for name,state of window.states @@ -103,4 +195,6 @@ for name,state of window.states #create class if type? and typemap[type]? controls[name]=new typemap[type](name,$el) +for name,state of window.states + controls[name]?.attach_events() diff --git a/examples/widgetapp/widget.js b/examples/widgetapp/widget.js index 9cda85af..4fca7c78 100644 --- a/examples/widgetapp/widget.js +++ b/examples/widgetapp/widget.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 1.6.1 (function() { - var $el, WSF_BUTTON_CONTROL, WSF_CHECKBOX_CONTROL, WSF_CONTROL, WSF_TEXTAREA_CONTROL, WSF_TEXT_CONTROL, controls, name, state, trigger_callback, type, typemap, _ref, + var $el, WSF_BUTTON_CONTROL, WSF_CHECKBOX_CONTROL, WSF_CONTROL, WSF_FORM_ELEMENT_CONTROL, WSF_INPUT_CONTROL, WSF_REGEXP_VALIDATOR, WSF_TEXTAREA_CONTROL, WSF_VALIDATOR, controls, name, state, trigger_callback, type, typemap, validatormap, _ref, _ref1, _ref2, __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; @@ -23,12 +23,52 @@ }); }; + WSF_VALIDATOR = (function() { + + function WSF_VALIDATOR(parent_control, settings) { + this.parent_control = parent_control; + this.settings = settings; + this.error = this.settings.error; + return; + } + + WSF_VALIDATOR.prototype.validate = function() { + return true; + }; + + return WSF_VALIDATOR; + + })(); + + WSF_REGEXP_VALIDATOR = (function(_super) { + + __extends(WSF_REGEXP_VALIDATOR, _super); + + function WSF_REGEXP_VALIDATOR() { + WSF_REGEXP_VALIDATOR.__super__.constructor.apply(this, arguments); + this.pattern = new RegExp(this.settings.expression, 'g'); + } + + WSF_REGEXP_VALIDATOR.prototype.validate = function() { + var res, val; + val = this.parent_control.value(); + res = val.match(this.pattern); + return res !== null; + }; + + return WSF_REGEXP_VALIDATOR; + + })(WSF_VALIDATOR); + + validatormap = { + "WSF_REGEXP_VALIDATOR": WSF_REGEXP_VALIDATOR + }; + WSF_CONTROL = (function() { function WSF_CONTROL(control_name, $el) { this.control_name = control_name; this.$el = $el; - this.attach_events(); return; } @@ -36,6 +76,33 @@ WSF_CONTROL.prototype.update = function(state) {}; + WSF_CONTROL.prototype.on = function(name, callback, context) { + if (this._events == null) { + this._events = {}; + } + if (this._events[name] == null) { + this._events[name] = []; + } + this._events[name].push({ + callback: callback, + context: context + }); + return this; + }; + + WSF_CONTROL.prototype.trigger = function(name) { + var ev, _i, _len, _ref, _ref1; + if (((_ref = this._events) != null ? _ref[name] : void 0) == null) { + return this; + } + _ref1 = this._events[name]; + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + ev = _ref1[_i]; + ev.callback.call(ev.context); + } + return this; + }; + return WSF_CONTROL; })(); @@ -76,15 +143,15 @@ })(WSF_CONTROL); - WSF_TEXT_CONTROL = (function(_super) { + WSF_INPUT_CONTROL = (function(_super) { - __extends(WSF_TEXT_CONTROL, _super); + __extends(WSF_INPUT_CONTROL, _super); - function WSF_TEXT_CONTROL() { - return WSF_TEXT_CONTROL.__super__.constructor.apply(this, arguments); + function WSF_INPUT_CONTROL() { + return WSF_INPUT_CONTROL.__super__.constructor.apply(this, arguments); } - WSF_TEXT_CONTROL.prototype.attach_events = function() { + WSF_INPUT_CONTROL.prototype.attach_events = function() { var self; self = this; return this.$el.change(function() { @@ -92,21 +159,26 @@ }); }; - WSF_TEXT_CONTROL.prototype.change = function() { + WSF_INPUT_CONTROL.prototype.change = function() { window.states[this.control_name]['text'] = this.$el.val(); if (window.states[this.control_name]['callback_change']) { - return trigger_callback(this.control_name, 'change'); + trigger_callback(this.control_name, 'change'); } + return this.trigger('change'); }; - WSF_TEXT_CONTROL.prototype.update = function(state) { + WSF_INPUT_CONTROL.prototype.value = function() { + return this.$el.val(); + }; + + WSF_INPUT_CONTROL.prototype.update = function(state) { if (state.text != null) { window.states[this.control_name]['text'] = state.text; return this.$el.val(state.text); } }; - return WSF_TEXT_CONTROL; + return WSF_INPUT_CONTROL; })(WSF_CONTROL); @@ -129,8 +201,13 @@ WSF_TEXTAREA_CONTROL.prototype.change = function() { window.states[this.control_name]['text'] = this.$el.val(); if (window.states[this.control_name]['callback_change']) { - return trigger_callback(this.control_name, 'change'); + trigger_callback(this.control_name, 'change'); } + return this.trigger('change'); + }; + + WSF_TEXTAREA_CONTROL.prototype.value = function() { + return this.$el.val(); }; WSF_TEXTAREA_CONTROL.prototype.update = function(state) { @@ -163,8 +240,13 @@ WSF_CHECKBOX_CONTROL.prototype.change = function() { window.states[this.control_name]['checked'] = this.$el.is(':checked'); if (window.states[this.control_name]['callback_change']) { - return trigger_callback(this.control_name, 'change'); + trigger_callback(this.control_name, 'change'); } + return this.trigger('change'); + }; + + WSF_CHECKBOX_CONTROL.prototype.value = function() { + return this.$el.is(':checked'); }; WSF_CHECKBOX_CONTROL.prototype.update = function(state) { @@ -178,11 +260,81 @@ })(WSF_CONTROL); + WSF_FORM_ELEMENT_CONTROL = (function(_super) { + + __extends(WSF_FORM_ELEMENT_CONTROL, _super); + + function WSF_FORM_ELEMENT_CONTROL() { + return WSF_FORM_ELEMENT_CONTROL.__super__.constructor.apply(this, arguments); + } + + WSF_FORM_ELEMENT_CONTROL.prototype.attach_events = function() { + var self, validator, _i, _len, _ref; + self = this; + this.value_control = controls[window.states[this.control_name]['value_control']]; + if (this.value_control != null) { + this.value_control.on('change', this.change, this); + } + this.serverside_validator = false; + this.validators = []; + _ref = window.states[this.control_name]['validators']; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + validator = _ref[_i]; + if (validatormap[validator.name] != null) { + this.validators.push(new validatormap[validator.name](this, validator)); + } else { + this.serverside_validator = true; + } + } + }; + + WSF_FORM_ELEMENT_CONTROL.prototype.change = function() { + var validator, _i, _len, _ref; + _ref = this.validators; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + validator = _ref[_i]; + if (!validator.validate()) { + this.showerror(validator.error); + return; + } + } + this.showerror(""); + if (this.serverside_validator) { + trigger_callback(this.control_name, 'validate'); + } + }; + + WSF_FORM_ELEMENT_CONTROL.prototype.showerror = function(message) { + var errordiv; + this.$el.removeClass("has-error"); + this.$el.find(".validation").remove(); + if (message.length > 0) { + this.$el.addClass("has-error"); + errordiv = $("").addClass('help-block').addClass('validation').text(message); + return this.$el.find(".col-lg-10").append(errordiv); + } + }; + + WSF_FORM_ELEMENT_CONTROL.prototype.update = function(state) { + if (state.error != null) { + return this.showerror(state.error); + } + }; + + WSF_FORM_ELEMENT_CONTROL.prototype.value = function() { + return this.value_control.value(); + }; + + return WSF_FORM_ELEMENT_CONTROL; + + })(WSF_CONTROL); + typemap = { "WSF_BUTTON_CONTROL": WSF_BUTTON_CONTROL, - "WSF_TEXT_CONTROL": WSF_TEXT_CONTROL, + "WSF_INPUT_CONTROL": WSF_INPUT_CONTROL, "WSF_TEXTAREA_CONTROL": WSF_TEXTAREA_CONTROL, - "WSF_CHECKBOX_CONTROL": WSF_CHECKBOX_CONTROL + "WSF_CHECKBOX_CONTROL": WSF_CHECKBOX_CONTROL, + "WSF_FORM_ELEMENT_CONTROL": WSF_FORM_ELEMENT_CONTROL }; _ref = window.states; @@ -195,4 +347,12 @@ } } + _ref1 = window.states; + for (name in _ref1) { + state = _ref1[name]; + if ((_ref2 = controls[name]) != null) { + _ref2.attach_events(); + } + } + }).call(this); diff --git a/library/server/wsf_html/webcontrol/validators/wsf_decimal_validator.e b/library/server/wsf_html/webcontrol/validators/wsf_decimal_validator.e index f7fd7f6d..ad68866d 100644 --- a/library/server/wsf_html/webcontrol/validators/wsf_decimal_validator.e +++ b/library/server/wsf_html/webcontrol/validators/wsf_decimal_validator.e @@ -18,7 +18,7 @@ feature {NONE} make_decimal_validator (e: STRING) do - make_regexp_validator ("[0-9]+(\\.[0-9]*)?|\\.[0-9]+", e) + make_regexp_validator ("^[0-9]+(\.[0-9]*)?$|^\.[0-9]+$", e) end end diff --git a/library/server/wsf_html/webcontrol/validators/wsf_email_validator.e b/library/server/wsf_html/webcontrol/validators/wsf_email_validator.e index 68ff9360..552aead7 100644 --- a/library/server/wsf_html/webcontrol/validators/wsf_email_validator.e +++ b/library/server/wsf_html/webcontrol/validators/wsf_email_validator.e @@ -14,7 +14,7 @@ inherit create make_email_validator -feature{NONE} +feature {NONE} make_email_validator (e: STRING) do diff --git a/library/server/wsf_html/webcontrol/validators/wsf_regexp_validator.e b/library/server/wsf_html/webcontrol/validators/wsf_regexp_validator.e index 8e20b710..5dcc6e7c 100644 --- a/library/server/wsf_html/webcontrol/validators/wsf_regexp_validator.e +++ b/library/server/wsf_html/webcontrol/validators/wsf_regexp_validator.e @@ -10,6 +10,9 @@ class inherit WSF_VALIDATOR [STRING] + redefine + state + end create make_regexp_validator @@ -18,21 +21,32 @@ feature {NONE} make_regexp_validator (r, e: STRING) do - make(e) + make (e) regexp_string := r create regexp - regexp.compile (r) end feature -- Implementation - validate (input: STRING): BOOLEAN + is_valid (input: STRING): BOOLEAN do + --Only compile when used + if not regexp.is_compiled then + regexp.compile (regexp_string) + end Result := regexp.matches (input) end feature + state: JSON_OBJECT + do + create Result.make + Result.put (create {JSON_STRING}.make_json ("WSF_REGEXP_VALIDATOR"), create {JSON_STRING}.make_json ("name")) + Result.put (create {JSON_STRING}.make_json (regexp_string), create {JSON_STRING}.make_json ("expression")) + Result.put (create {JSON_STRING}.make_json (error), create {JSON_STRING}.make_json ("error")) + end + regexp_string: STRING regexp: REGULAR_EXPRESSION diff --git a/library/server/wsf_html/webcontrol/validators/wsf_validatable.e b/library/server/wsf_html/webcontrol/validators/wsf_validatable.e new file mode 100644 index 00000000..4b7a60fa --- /dev/null +++ b/library/server/wsf_html/webcontrol/validators/wsf_validatable.e @@ -0,0 +1,20 @@ +note + description: "Summary description for {WSF_VALIDATABLE}." + author: "" + date: "$Date$" + revision: "$Revision$" + +deferred class + WSF_VALIDATABLE + +feature + + validate + deferred + end + + is_valid: BOOLEAN + deferred + end + +end diff --git a/library/server/wsf_html/webcontrol/validators/wsf_validator.e b/library/server/wsf_html/webcontrol/validators/wsf_validator.e index 58415781..ad3f27bd 100644 --- a/library/server/wsf_html/webcontrol/validators/wsf_validator.e +++ b/library/server/wsf_html/webcontrol/validators/wsf_validator.e @@ -16,7 +16,14 @@ feature {NONE} feature - validate (input: G): BOOLEAN + state: JSON_OBJECT + do + create Result.make + Result.put (create {JSON_STRING}.make_json (generator), create {JSON_STRING}.make_json ("name")) + Result.put (create {JSON_STRING}.make_json (error), create {JSON_STRING}.make_json ("error")) + end + + is_valid (input: G): BOOLEAN deferred end diff --git a/library/server/wsf_html/webcontrol/wsf_button_control.e b/library/server/wsf_html/webcontrol/wsf_button_control.e index 2e9a901d..cfd5ec05 100644 --- a/library/server/wsf_html/webcontrol/wsf_button_control.e +++ b/library/server/wsf_html/webcontrol/wsf_button_control.e @@ -19,6 +19,8 @@ feature {NONE} make_button (n: STRING; t: STRING) do make (n, "button") + add_class ("btn") + add_class ("btn-default") text := t end diff --git a/library/server/wsf_html/webcontrol/wsf_checkbox_control.e b/library/server/wsf_html/webcontrol/wsf_checkbox_control.e index b1281ac2..42233d74 100644 --- a/library/server/wsf_html/webcontrol/wsf_checkbox_control.e +++ b/library/server/wsf_html/webcontrol/wsf_checkbox_control.e @@ -68,7 +68,7 @@ feature -- Implementation if checked then attributes := attributes + " checked" end - Result := render_tag_with_tagname ("div",render_tag_with_tagname ("label", render_tag ("", attributes) + " " + label, "",""), "","checkbox") + Result := render_tag_with_tagname ("div", render_tag_with_tagname ("label", render_tag ("", attributes) + " " + label, "", ""), "", "checkbox") end value: BOOLEAN diff --git a/library/server/wsf_html/webcontrol/wsf_control.e b/library/server/wsf_html/webcontrol/wsf_control.e index 8a092305..9032e940 100644 --- a/library/server/wsf_html/webcontrol/wsf_control.e +++ b/library/server/wsf_html/webcontrol/wsf_control.e @@ -88,14 +88,14 @@ feature loop css_classes_string := css_classes_string + " " + c.item end - Result:=render_tag_with_tagname(tag_name,body,attributes,css_classes_string) + Result := render_tag_with_tagname (tag_name, body, attributes, css_classes_string) end - render_tag_with_tagname (tag, body, attributes,css_classes_string: STRING): STRING + render_tag_with_tagname (tag, body, attributes, css_classes_string: STRING): STRING local l_attributes: STRING do - l_attributes:=attributes + l_attributes := attributes if not css_classes_string.is_empty then l_attributes := " class=%"" + css_classes_string + "%"" end diff --git a/library/server/wsf_html/webcontrol/wsf_form_control.e b/library/server/wsf_html/webcontrol/wsf_form_control.e index dec7fa21..e4048080 100644 --- a/library/server/wsf_html/webcontrol/wsf_form_control.e +++ b/library/server/wsf_html/webcontrol/wsf_form_control.e @@ -11,6 +11,8 @@ inherit WSF_MULTI_CONTROL [WSF_CONTROL] + WSF_VALIDATABLE + create make_form_control @@ -24,22 +26,23 @@ feature {NONE} feature -- Validation - validate: BOOLEAN + validate do - Result := True + is_valid := True across controls as c until - Result = False + is_valid = False loop - -- TODO: Change generic parameter of elm from ANY to