From a8ca9b35b155e96eccb64d1c8142e35cdf641288 Mon Sep 17 00:00:00 2001 From: mike Date: Sat, 18 Apr 2020 23:53:04 +0300 Subject: [PATCH 01/21] add materiazliers --- Rakefile | 1 + src/base_model.coffee | 14 + src/materialization.coffee | 309 ++++++++++++++++++ test/SpecRunner.html | 19 +- .../specs/source/materialization_specs.coffee | 220 +++++++++++++ 5 files changed, 554 insertions(+), 9 deletions(-) create mode 100644 src/materialization.coffee create mode 100644 test/specs/source/materialization_specs.coffee diff --git a/Rakefile b/Rakefile index 8401b65..ad68d70 100644 --- a/Rakefile +++ b/Rakefile @@ -108,6 +108,7 @@ task :build do logger internal promise utils sirius validators observer view base_model transformer collection + materialization )) core_files = coffee(src, %w( diff --git a/src/base_model.coffee b/src/base_model.coffee index f8e102f..32734e4 100644 --- a/src/base_model.coffee +++ b/src/base_model.coffee @@ -285,6 +285,8 @@ class Sirius.BaseModel @_is_valid_attr = {} # save pair attribute and validation state attrs0 = @attrs() # from class body + @binding = {} + for attr in attrs0 # @attrs: [{key: value}] @logger.info("define '#{JSON.stringify(attr)}' attribute for '#{name}'") @@ -348,6 +350,8 @@ class Sirius.BaseModel tmp[name] = klass @_applicable_validators[key] = tmp + @_gen_binding_names() + @after_create() # @private @@ -362,6 +366,16 @@ class Sirius.BaseModel else @get(attribute) + _gen_binding_names: () -> + obj = {} + for attribute in @get_attributes() + obj[attribute] = attribute + + @binding = obj + + get_binding: () -> + @binding + # @private # @nodoc diff --git a/src/materialization.coffee b/src/materialization.coffee new file mode 100644 index 0000000..21288af --- /dev/null +++ b/src/materialization.coffee @@ -0,0 +1,309 @@ + +### + + probably like: + + Materializer.build(T <: BaseModel|View, R <: BaseModel|View|Function) + .field((x) -> s.attr()) # does it possible in coffee? + .field('attr_name', to: "input[name='some-attr']", attribute: 'data-attr', with: () ->) + # or + field('attr_name').to("input").attribute("data-attr").with(() ->) + field('attr_name).to("input") + .dump() => log output as string, dump is a terminal operation + # or build + # does it possible? + field('attr_name').to((v) -> v.zoom('input')).attribute('data-attr').with(() -> ) + + # TODO spec syntax, add to .prototype. + field((model) -> model.from{or something like that}.attr()) + field( view -> view.zoom("el")) + + # view to view + Materializer.build(v1, v2) + .field("element").from("attribute").to("element).with((v2, v1_attribute) -> ) + .field(v -> v.zoom("element")).from("attribute").to(v2 -> v.zoom("el")) + .with(() ->) + # or + .field("element").from("attr").with((v2, attr) -> ) # user decides what should do with v2 (zoom) and attr + + # view to model + Materilizer.build(v, m) + .field("element").from("attr").to('m_attr') + .field(v -> v.zoom("el")).from("attr").to(m -> m.attr_name) + with ? (m, attr_changes) -> ??? is it need? + + # view to function + Materializer.build(v) # second param is empty + .field('element').attribute('data-class').to((changes) ->) + + # model to function + Materializer.build(m) # second param is empty + .field('attr').to(changes) -> ) + + # first iteration: + - make fieldMapper + - second add to proto + - third integration with current + + +### + + +# ok, it's for BaseModelToView +class FieldMaker + constructor: (@_from, @_to, @_attribute, @_with) -> + + has_to: () -> + @_to? + + has_attribute: () -> + @_attribute? + + has_with: () -> + @_with? + + field: () -> + @_from + + to: (x) -> + if x? + @_to = x + else + @_to + + attribute: (x) -> + @_attribute = x + + with: (x) -> + @_with = x + + # fill with default parameters + normalize: () -> + if !@has_with() + @_with = (x) -> x + + if !@has_attribute() + @_attribute = "text" # make constant + + + to_string: () -> + "#{@_from} ~> #{@_with} ~> #{@_to}##{@_attribute}" + + @build: (from) -> + new FieldMaker(from) + + +class AbstractMaterializer + constructor: (@_from, @_to) -> + @fields = [] + @current = null + + field: (from_name) -> + if @current? + @current.normalize() + + @current = FieldMaker.build(from_name) + @fields.push(@current) + + _zoom_with: (view, maybeView) -> + if Sirius.Utils.is_string(maybeView) + view.zoom(maybeView) + else + maybeView + + +# interface-like +class MaterializerWithImpl extends AbstractMaterializer + + with: (f) -> + unless Sirius.Utils.is_function(f) + throw new Error("With attribute must be function, #{typeof f} given") + + unless @current? + throw new Error("Incorrect call. Call 'with' after 'to' or 'attribute'") + + unless @current.has_to() + throw new Error("Incorrect call. Call 'to' before 'with'") + + if @current.has_with() + throw new Error("Incorrect call. The field already has 'with' function") + + @current.with(f) + @ + + +class ModelToViewMaterializer extends MaterializerWithImpl + field: (from_name) -> + result = from_name + if Sirius.Utils.is_function(from_name) + result = from_name(@_from.get_binding()) + + super.field(result) + # check model attributes + @ + + to: (arg) -> + unless @current? + throw new Error("Incorrect call. Call 'to' after 'field'") + + unless Sirius.Utils.is_function(arg) || Sirius.Utils.is_string(arg) || arg instanceof Sirius.View + throw new Error("'to' must be string or function, or instance of Sirius.View") + + result = arg + if Sirius.Utils.is_string(arg) + result = @_zoom_with(@_to, arg) + + if Sirius.Utils.is_function(arg) + result = @_zoom_with(@_to, arg(@_to)) + + if @current.has_to() + throw new Error("Incorrect call. '#{@current.field()}' already has 'to'") + + @current.to(result) + @ + + attribute: (attr) -> + unless @current? + throw new Error("Incorrect call. Define 'field' firstly, and then call 'attribute' after 'to'") + + unless @current.has_to() + throw new Error("Incorrect call. Call 'to' before 'attribute'") + + if @current.has_attribute() + throw new Error("Incorrect call. '#{@current.field()}' already has 'attribute'") + + @current.attribute(attr) + @ + +class ViewToModelMaterializer extends MaterializerWithImpl + field: (element) -> + el = null + if Sirius.Utils.is_string(element) + el = @_from.zoom(element) + else if Sirius.Utils.is_function(element) + el = @_zoom_with(@_from, element(@_from)) + else if element instanceof Sirius.View + el = element + else + throw new Error("Element must be string or function, or instance of Sirius.View") + + super.field(el) + @ + + from: (attribute) -> + unless @current? + throw new Error("Incorrect call. Define 'field' firstly, and then call 'from'") + + if @current.has_to() + throw new Error("Incorrect call. Call 'from' before 'to'") + + if @current.has_attribute() + throw new Error("Incorrect call. '#{@current.field().get_element()}' already has 'from'") + + @current.attribute(attribute) + @ + + to: (attribute) -> + # todo check model attributes + unless @current? + throw new Error("Incorrect call. Define 'field' firstly, and then call 'from'") + + if @current.has_to() + throw new Error("Incorrect call. '#{@current.field().get_element()}' already has 'to'") + + result = attribute + if @_to? && Sirius.Utils.is_function(attribute) + result = attribute(@_to.get_binding()) + + @current.to(result) + @ + +class ViewToViewMaterializer extends ViewToModelMaterializer + to: (element) -> + el = null + if Sirius.Utils.is_string(element) + el = @_to.zoom(element) + else if element instanceof Sirius.View + el = element + else if Sirius.Utils.is_function(element) + el = @_zoom_with(@_to, element(@_to)) + else + throw new Error("Element must be string or function, or instance of Sirius.View") + + super.to(el) + @ + +class ViewToFunctionMaterializer extends ViewToModelMaterializer + to: (f) -> + unless Sirius.Utils.is_function(f) + throw new Error("Function is required") + + super.to(f) + @ + +class ModelToFunctionMaterializer extends AbstractMaterializer + field: (attr) -> + result = attr + if Sirius.Utils.is_function(attr) + result = attr(@_from.get_binding()) + + super.field(result) + # check model attributes + @ + + to: (f) -> + unless @current? + throw new Error("Incorrect call. Define 'field' firstly") + + if @current.has_to() + throw new Error("Incorrect call. The field already has 'to'") + + unless Sirius.Utils.is_function(f) + throw new Error("Function is required") + + @current.to(f) + @ + + +class Materializer + + # from must be View or BaseModel + # to is View, BaseModel, or Function + constructor: (from, to) -> + if from instanceof Sirius.BaseModel && to instanceof Sirius.View + return new ModelToViewMaterializer(from, to) + if from instanceof Sirius.View && to instanceof Sirius.BaseModel + return new ViewToModelMaterializer(from, to) + if from instanceof Sirius.View && to instanceof Sirius.View + return new ViewToViewMaterializer(from, to) + if from instanceof Sirius.View && !to? + return new ViewToFunctionMaterializer(from) + if from instanceof Sirius.BaseModel && !to? + return new ModelToFunctionMaterializer(from) + else + throw new Error("Not implemented") + + dump: () -> + xs = @fields.map (x) -> x.to_string() + xs.join("\n") + + to_string: () -> + @dump() + + + + @build: (from, to) -> + new Materializer(from, to) + + + + + + + + + + + + + diff --git a/test/SpecRunner.html b/test/SpecRunner.html index 7c39729..c6f7aa4 100644 --- a/test/SpecRunner.html +++ b/test/SpecRunner.html @@ -122,15 +122,16 @@ - - - - - - - - - + + + + + + + + + + diff --git a/test/specs/source/materialization_specs.coffee b/test/specs/source/materialization_specs.coffee new file mode 100644 index 0000000..a3aba1e --- /dev/null +++ b/test/specs/source/materialization_specs.coffee @@ -0,0 +1,220 @@ +describe "Materialization", -> + class Test1 extends Sirius.BaseModel + @attrs: ['id'] + + describe "BaseModel to View", -> + materializer = null + beforeEach () -> + materializer = Materializer.build(new Test1(), new Sirius.View("#test")) + + describe "field", -> + it "unwrap function", -> + materializer.field((b) -> b.id).to("test") + expect(materializer.current.field()).toEqual("id") + + + describe "to", -> + it "unwrap function", -> + expect(() -> + materializer.field('id').to((v) -> v.zoom("asd")) + ).not.toThrowError() + + it "check types", -> + expect(() -> + materializer.field('id').to(1) + ).toThrowError("'to' must be string or function, or instance of Sirius.View") + + it "'to' without 'field'", -> + expect(() -> + materializer.to("test") + ).toThrowError("Incorrect call. Call 'to' after 'field'") + + it "double-to", -> + expect(() -> + materializer.field("id").to("test").to("test1") + ).toThrowError("Incorrect call. 'id' already has 'to'") + + describe "attribute", -> + it "without 'field'", -> + expect(() -> + materializer.attribute("class") + ).toThrowError("Incorrect call. Define 'field' firstly, and then call 'attribute' after 'to'") + + it "without 'to'", -> + expect(() -> + materializer.field("id").attribute("class") + ).toThrowError("Incorrect call. Call 'to' before 'attribute'") + + it "double-attribute", -> + expect(() -> + materializer.field("id").to("test").attribute("class").attribute("class") + ).toThrowError("Incorrect call. 'id' already has 'attribute'") + + describe "with", -> + it "'with' are not a function", -> + expect(() -> + materializer.field("id") + .to("test").with(1) + ).toThrowError("With attribute must be function, #{typeof 1} given") + + it "'with' without field", -> + expect(() -> + materializer.with(() ->) + ).toThrowError("Incorrect call. Call 'with' after 'to' or 'attribute'") + + it "without 'to'", -> + expect( () -> + materializer.field("id").with(() ->) + ).toThrowError("Incorrect call. Call 'to' before 'with'") + + it "double-with", -> + expect(() -> + materializer.field("id") + .to("test").with(() ->).with(() ->) + ).toThrowError("Incorrect call. The field already has 'with' function") + + describe "View To Model", -> + + materializer = null + beforeEach () -> + materializer = Materializer.build(new Sirius.View("#test"), new Test1()) + + describe "field", -> + it "unwrap function", -> + expect(() -> + materializer.field((v) -> v.zoom("view")).to((b) -> b.id) + ).not.toThrowError() + + it "'field' must be string or view", -> + expect(() -> + materializer.field(1) + ).toThrowError("Element must be string or function, or instance of Sirius.View") + + expect(() -> + materializer.field("test") + ).not.toThrowError() + + expect(() -> + materializer.field(new Sirius.View("test")) + ).not.toThrowError() + + describe "to", -> + it "unwrap function", -> + materializer.field("test").to((b) -> b.id) + expect(materializer.current.to()).toEqual("id") + + it "call before 'field'", -> + expect(() -> + materializer.to("id") + ).toThrowError("Incorrect call. Define 'field' firstly, and then call 'from'") + + it "double-to", -> + expect(() -> + materializer.field("test").to("id").to("id") + ).toThrowError("Incorrect call. '#test test' already has 'to'") + + describe "from", -> + it "without context", -> + expect(() -> + materializer.from("test") + ).toThrowError("Incorrect call. Define 'field' firstly, and then call 'from'") + + it "before to", -> + expect(() -> + materializer.field("test").to("id").from("test") + ).toThrowError("Incorrect call. Call 'from' before 'to'") + + it "double-from", -> + expect(() -> + materializer.field("test").from("id").from("id") + ).toThrowError("Incorrect call. '#test test' already has 'from'") + + describe "with", -> + it "'with' are not function", -> + expect(() -> + materializer.field("id") + .to("test").with(1) + ).toThrowError("With attribute must be function, #{typeof 1} given") + + it "'with' without field", -> + expect(() -> + materializer.with(() ->) + ).toThrowError("Incorrect call. Call 'with' after 'to' or 'attribute'") + + it "without 'to'", -> + expect( () -> + materializer.field("id").with(() ->) + ).toThrowError("Incorrect call. Call 'to' before 'with'") + + it "double-with", -> + expect(() -> + materializer.field("id") + .to("test").with(() ->).with(() ->) + ).toThrowError("Incorrect call. The field already has 'with' function") + + describe "View To View", -> + + describe "to", -> + materializer = null + + beforeEach () -> + materializer = Materializer.build(new Sirius.View("#test"), new Sirius.View("#test1")) + + it "unwrap function", -> + expect(() -> + materializer.field("asd").to((v) -> v.zoom("das")) + ).not.toThrowError() + + it "should be string or Sirius.View", -> + expect(() -> + materializer.field("test") + .to(1) + ).toThrowError("Element must be string or function, or instance of Sirius.View") + + expect(() -> + materializer.field("test").to("asd") + ).not.toThrowError() + + describe "View To Function", -> + + describe "to", -> + it "function is required", -> + materializer = Materializer.build(new Sirius.View("test")) + expect(() -> + materializer.field("test").to(1) + ).toThrowError("Function is required") + + expect(() -> + materializer.field("test").to(() ->) + ).not.toThrowError() + + describe "Model To Function", -> + describe "to", -> + materializer = null + + beforeEach () -> + materializer = Materializer.build(new Test1()) + + it "unwrap function", -> + materializer.field((b) -> b.id) + expect(materializer.current.field()).toEqual("id") + + it "without context", -> + expect(() -> + materializer.to(() ->) + ).toThrowError("Incorrect call. Define 'field' firstly") + + it "double-to", -> + expect(() -> + materializer.field("id").to(() -> ).to(() -> ) + ).toThrowError("Incorrect call. The field already has 'to'") + + it "function is required", -> + expect(() -> + materializer.field("id").to(1) + ).toThrowError("Function is required") + + expect(() -> + materializer.field("id").to(() ->) + ).not.toThrowError() + From f8d1ab80b3e10279060cd4a51c0bbc55723e75de Mon Sep 17 00:00:00 2001 From: mike Date: Sun, 19 Apr 2020 11:11:36 +0300 Subject: [PATCH 02/21] binding --- src/base_model.coffee | 12 ++++ src/materialization.coffee | 59 +++++++++++++++---- .../specs/source/materialization_specs.coffee | 49 +++++++++++++-- 3 files changed, 104 insertions(+), 16 deletions(-) diff --git a/src/base_model.coffee b/src/base_model.coffee index 32734e4..bfba009 100644 --- a/src/base_model.coffee +++ b/src/base_model.coffee @@ -367,9 +367,21 @@ class Sirius.BaseModel @get(attribute) _gen_binding_names: () -> + # attributes + validators + # @_applicable_validators # {id: presence : P, numbericalluy: N ...} obj = {} for attribute in @get_attributes() obj[attribute] = attribute + # TODO check model should not contains 'error' attribute + if Object.keys(@_applicable_validators).length != 0 + errors = {} + # id: presence, num, custom + for key, value of @_applicable_validators + tmp = {} + for v in Object.keys(value) + tmp[v] = "errors.#{key}.#{v}" + errors[key] = tmp + obj['errors'] = errors @binding = obj diff --git a/src/materialization.coffee b/src/materialization.coffee index 21288af..0b36ca6 100644 --- a/src/materialization.coffee +++ b/src/materialization.coffee @@ -41,8 +41,6 @@ .field('attr').to(changes) -> ) # first iteration: - - make fieldMapper - - second add to proto - third integration with current @@ -111,6 +109,25 @@ class AbstractMaterializer else maybeView + dump: () -> + xs = @fields.map (x) -> x.to_string() + xs.join("\n") + + to_string: () -> + @dump() + + get_from: () -> + @_from + + get_to: () -> + @_to() + + has_to: () -> + @_to? + + materialize: () -> + @fields + # interface-like class MaterializerWithImpl extends AbstractMaterializer @@ -138,8 +155,10 @@ class ModelToViewMaterializer extends MaterializerWithImpl if Sirius.Utils.is_function(from_name) result = from_name(@_from.get_binding()) + Materializer._check_model_compliance(@_from, result) + super.field(result) - # check model attributes + @ to: (arg) -> @@ -204,7 +223,6 @@ class ViewToModelMaterializer extends MaterializerWithImpl @ to: (attribute) -> - # todo check model attributes unless @current? throw new Error("Incorrect call. Define 'field' firstly, and then call 'from'") @@ -215,6 +233,9 @@ class ViewToModelMaterializer extends MaterializerWithImpl if @_to? && Sirius.Utils.is_function(attribute) result = attribute(@_to.get_binding()) + if @_to? && @_to instanceof Sirius.BaseModel + Materializer._check_model_compliance(@_to, result) + @current.to(result) @ @@ -247,8 +268,10 @@ class ModelToFunctionMaterializer extends AbstractMaterializer if Sirius.Utils.is_function(attr) result = attr(@_from.get_binding()) + Materializer._check_model_compliance(@_from, result) + super.field(result) - # check model attributes + @ to: (f) -> @@ -281,15 +304,29 @@ class Materializer if from instanceof Sirius.BaseModel && !to? return new ModelToFunctionMaterializer(from) else - throw new Error("Not implemented") + throw new Error("Illegal arguments: 'from'/'to' must be instance of Sirius.View/or Sirius.BaseModel") - dump: () -> - xs = @fields.map (x) -> x.to_string() - xs.join("\n") + @_check_model_compliance: (model, maybe_model_attribute) -> + name = model._klass_name() + attrs = model.get_attributes() - to_string: () -> - @dump() + if attrs.indexOf(maybe_model_attribute) != -1 + return true + else + if maybe_model_attribute.indexOf(".") == -1 + throw new Error("Attribute '#{maybe_model_attribute}' not found in model attributes: '#{name}', available: '[#{attrs}]'") + + # check for validators + splitted = maybe_model_attribute.split(".") + if splitted.length != 3 + throw new Error("Try to bind '#{maybe_model_attribute}' from errors properties, but validator is not found, correct definition should be as 'errors.id.numericality'") + + [_, attr, validator_key] = splitted + unless model._is_valid_validator("#{attr}.#{validator_key}") + throw new Error("Unexpected '#{maybe_model_attribute}' errors attribute for '#{name}' (check validators)") + else + return true @build: (from, to) -> diff --git a/test/specs/source/materialization_specs.coffee b/test/specs/source/materialization_specs.coffee index a3aba1e..b0c1583 100644 --- a/test/specs/source/materialization_specs.coffee +++ b/test/specs/source/materialization_specs.coffee @@ -1,6 +1,39 @@ describe "Materialization", -> class Test1 extends Sirius.BaseModel @attrs: ['id'] + @validate : + id: + presence: true + + describe "Materializer", -> + it "illegal arguments", -> + expect(() -> + Materializer.build(1, 2) + ).toThrowError(/Illegal arguments/) + expect(() -> + Materializer.build(new Test1(), 1) + ).toThrowError(/Illegal arguments/) + expect(() -> + Materializer.build(new Sirius.View("asd"), "asd") + ).toThrowError(/Illegal arguments/) + + it "check model attributes", -> + m = new Test1() + expect(() -> + Materializer._check_model_compliance(m, "id") + ).not.toThrowError() + expect(() -> + Materializer._check_model_compliance(m, "foo") + ).toThrowError(/Attribute 'foo' not found in model/) + expect(() -> + Materializer._check_model_compliance(m, "errors.id.presence") + ).not.toThrowError() + expect(() -> + Materializer._check_model_compliance(m, "errors.id.numericality") + ).toThrowError(/Unexpected 'errors.id.numericality' errors attribute/) + expect(() -> + Materializer._check_model_compliance(m, "foo.bar") + ).toThrowError(/Try to bind 'foo.bar' from errors properties/) describe "BaseModel to View", -> materializer = null @@ -11,7 +44,13 @@ describe "Materialization", -> it "unwrap function", -> materializer.field((b) -> b.id).to("test") expect(materializer.current.field()).toEqual("id") + materializer.field((b) -> b.errors.id.presence).to("test") + expect(materializer.current.field()).toEqual("errors.id.presence") + it "when attribute are not exist", -> + expect(() -> + materializer.field("foo") + ).toThrowError("Attribute 'foo' not found in model attributes: 'Test1', available: '[id]'") describe "to", -> it "unwrap function", -> @@ -132,8 +171,8 @@ describe "Materialization", -> describe "with", -> it "'with' are not function", -> expect(() -> - materializer.field("id") - .to("test").with(1) + materializer.field("input") + .to("id").with(1) ).toThrowError("With attribute must be function, #{typeof 1} given") it "'with' without field", -> @@ -143,13 +182,13 @@ describe "Materialization", -> it "without 'to'", -> expect( () -> - materializer.field("id").with(() ->) + materializer.field("input").with(() ->) ).toThrowError("Incorrect call. Call 'to' before 'with'") it "double-with", -> expect(() -> - materializer.field("id") - .to("test").with(() ->).with(() ->) + materializer.field("input") + .to("id").with(() ->).with(() ->) ).toThrowError("Incorrect call. The field already has 'with' function") describe "View To View", -> From 0fcd18cb81479beba2bef078d48ce3742289dc94 Mon Sep 17 00:00:00 2001 From: mike Date: Sun, 19 Apr 2020 12:59:12 +0300 Subject: [PATCH 03/21] add handle --- src/materialization.coffee | 95 +++++++++++++--- .../specs/source/materialization_specs.coffee | 102 ++++++++++++++---- 2 files changed, 157 insertions(+), 40 deletions(-) diff --git a/src/materialization.coffee b/src/materialization.coffee index 0b36ca6..6a3db4e 100644 --- a/src/materialization.coffee +++ b/src/materialization.coffee @@ -5,9 +5,9 @@ Materializer.build(T <: BaseModel|View, R <: BaseModel|View|Function) .field((x) -> s.attr()) # does it possible in coffee? - .field('attr_name', to: "input[name='some-attr']", attribute: 'data-attr', with: () ->) # or field('attr_name').to("input").attribute("data-attr").with(() ->) + field('attr_name).to("input") .dump() => log output as string, dump is a terminal operation # or build @@ -49,7 +49,7 @@ # ok, it's for BaseModelToView class FieldMaker - constructor: (@_from, @_to, @_attribute, @_with) -> + constructor: (@_from, @_to, @_attribute, @_transform, @_handle) -> has_to: () -> @_to? @@ -57,8 +57,11 @@ class FieldMaker has_attribute: () -> @_attribute? - has_with: () -> - @_with? + has_transform: () -> + @_transform? + + has_handle: () -> + @_handle? field: () -> @_from @@ -69,23 +72,35 @@ class FieldMaker else @_to + handle: (x) -> + if x? + @_handle = x + else + @_handle + attribute: (x) -> - @_attribute = x + if x? + @_attribute = x + else + @_attribute - with: (x) -> - @_with = x + transform: (x) -> + if x? + @_transform = x + else + @_transform # fill with default parameters normalize: () -> - if !@has_with() - @_with = (x) -> x + if !@has_transform() + @_transform = (x) -> x if !@has_attribute() @_attribute = "text" # make constant to_string: () -> - "#{@_from} ~> #{@_with} ~> #{@_to}##{@_attribute}" + "#{@_from} ~> #{@_transform} ~> #{@_to}##{@_attribute}" @build: (from) -> new FieldMaker(from) @@ -128,27 +143,31 @@ class AbstractMaterializer materialize: () -> @fields + run: () -> + throw new Error("Not Implemented") + # interface-like class MaterializerWithImpl extends AbstractMaterializer - with: (f) -> + transform: (f) -> unless Sirius.Utils.is_function(f) - throw new Error("With attribute must be function, #{typeof f} given") + throw new Error("'transform' attribute must be function, #{typeof f} given") unless @current? - throw new Error("Incorrect call. Call 'with' after 'to' or 'attribute'") + throw new Error("Incorrect call. Call 'transform' after 'to' or 'attribute'") unless @current.has_to() - throw new Error("Incorrect call. Call 'to' before 'with'") + throw new Error("Incorrect call. Call 'to' before 'transform'") - if @current.has_with() - throw new Error("Incorrect call. The field already has 'with' function") + if @current.has_transform() + throw new Error("Incorrect call. The field already has 'transform' function") - @current.with(f) + @current.transform(f) @ + class ModelToViewMaterializer extends MaterializerWithImpl field: (from_name) -> result = from_name @@ -194,6 +213,32 @@ class ModelToViewMaterializer extends MaterializerWithImpl @current.attribute(attr) @ + handle: (f) -> + unless @current? + throw new Error("Incorrect call. 'field' is not defined") + + unless @current.has_to() + throw new Error("Incorrect call. define 'to'") + + unless Sirius.Utils.is_function(f) + throw new Error("'handle' must be a function") + + if @current.has_handle() + throw new Error("'handle' already defined") + + @current.handle(f) + @ + + + run: () -> + obj = {} + for f in @fields + obj[f.field()] = f + clb = (attribute, changes) -> + if obj[attribute]? + f.trnaform(changes, f.to()) + + class ViewToModelMaterializer extends MaterializerWithImpl field: (element) -> el = null @@ -254,6 +299,22 @@ class ViewToViewMaterializer extends ViewToModelMaterializer super.to(el) @ + handle: (f) -> + unless @current? + throw new Error("Incorrect call. 'field' is not defined") + + unless @current.has_to() + throw new Error("Incorrect call. define 'to'") + + unless Sirius.Utils.is_function(f) + throw new Error("'handle' must be a function") + + if @current.has_handle() + throw new Error("'handle' already defined") + + @current.handle(f) + @ + class ViewToFunctionMaterializer extends ViewToModelMaterializer to: (f) -> unless Sirius.Utils.is_function(f) diff --git a/test/specs/source/materialization_specs.coffee b/test/specs/source/materialization_specs.coffee index b0c1583..307b782 100644 --- a/test/specs/source/materialization_specs.coffee +++ b/test/specs/source/materialization_specs.coffee @@ -89,28 +89,52 @@ describe "Materialization", -> materializer.field("id").to("test").attribute("class").attribute("class") ).toThrowError("Incorrect call. 'id' already has 'attribute'") - describe "with", -> + describe "transform", -> it "'with' are not a function", -> expect(() -> materializer.field("id") - .to("test").with(1) - ).toThrowError("With attribute must be function, #{typeof 1} given") + .to("test").transform(1) + ).toThrowError("'transform' attribute must be function, #{typeof 1} given") - it "'with' without field", -> + it "'transform' without field", -> expect(() -> - materializer.with(() ->) - ).toThrowError("Incorrect call. Call 'with' after 'to' or 'attribute'") + materializer.transform(() ->) + ).toThrowError("Incorrect call. Call 'transform' after 'to' or 'attribute'") it "without 'to'", -> expect( () -> - materializer.field("id").with(() ->) - ).toThrowError("Incorrect call. Call 'to' before 'with'") + materializer.field("id").transform(() ->) + ).toThrowError("Incorrect call. Call 'to' before 'transform'") - it "double-with", -> + it "double-transform", -> expect(() -> materializer.field("id") - .to("test").with(() ->).with(() ->) - ).toThrowError("Incorrect call. The field already has 'with' function") + .to("test").transform(() ->).transform(() ->) + ).toThrowError("Incorrect call. The field already has 'transform' function") + + describe "handle", -> + it "without field", -> + expect(() -> + materializer.handle(() -> ) + ).toThrowError("Incorrect call. 'field' is not defined") + + it "without to", -> + expect(() -> + materializer.field((x) -> x.id) + .handle(() -> ) + ).toThrowError("Incorrect call. define 'to'") + + it "when not a function", -> + expect(() -> + materializer.field('id') + .to('input').handle(1) + ).toThrowError("'handle' must be a function") + + it "double-handle", -> + expect(() -> + materializer.field('id').to('input').handle(() -> ).handle(() -> ) + ).toThrowError("'handle' already defined") + describe "View To Model", -> @@ -168,28 +192,28 @@ describe "Materialization", -> materializer.field("test").from("id").from("id") ).toThrowError("Incorrect call. '#test test' already has 'from'") - describe "with", -> - it "'with' are not function", -> + describe "transform", -> + it "'transform' are not function", -> expect(() -> materializer.field("input") - .to("id").with(1) - ).toThrowError("With attribute must be function, #{typeof 1} given") + .to("id").transform(1) + ).toThrowError("'transform' attribute must be function, #{typeof 1} given") - it "'with' without field", -> + it "'transform' without field", -> expect(() -> - materializer.with(() ->) - ).toThrowError("Incorrect call. Call 'with' after 'to' or 'attribute'") + materializer.transform(() ->) + ).toThrowError("Incorrect call. Call 'transform' after 'to' or 'attribute'") it "without 'to'", -> expect( () -> - materializer.field("input").with(() ->) - ).toThrowError("Incorrect call. Call 'to' before 'with'") + materializer.field("input").transform(() ->) + ).toThrowError("Incorrect call. Call 'to' before 'transform'") - it "double-with", -> + it "double-transform", -> expect(() -> materializer.field("input") - .to("id").with(() ->).with(() ->) - ).toThrowError("Incorrect call. The field already has 'with' function") + .to("id").transform(() ->).transform(() ->) + ).toThrowError("Incorrect call. The field already has 'transform' function") describe "View To View", -> @@ -214,6 +238,38 @@ describe "Materialization", -> materializer.field("test").to("asd") ).not.toThrowError() + describe "handle", -> + materializer = null + + beforeEach () -> + materializer = Materializer.build(new Sirius.View("#test"), new Sirius.View("#test1")) + + it "without field", -> + expect(() -> + materializer.handle(() ->) + ).toThrowError("Incorrect call. 'field' is not defined") + + it "without to", -> + expect(() -> + materializer.field("input") + .handle(() -> ) + ).toThrowError("Incorrect call. define 'to'") + + it "when is not a function", -> + expect(() -> + materializer.field("input") + .to("div").handle(1) + ).toThrowError("'handle' must be a function") + + it "double handle", -> + expect(() -> + materializer.field("input") + .to("div") + .handle(() -> ) + .handle(() -> ) + ).toThrowError("'handle' already defined") + + describe "View To Function", -> describe "to", -> From 01d2cd9abae68fa84a5abfb08a95220855483504 Mon Sep 17 00:00:00 2001 From: mike Date: Sun, 19 Apr 2020 20:22:44 +0300 Subject: [PATCH 04/21] rm specs --- src/base_model.coffee | 36 +- src/materialization.coffee | 96 ++++- test/SpecRunner.html | 18 +- test/SpecRunnerPrototype.html | 3 +- test/SpecRunnerVanilla.html | 3 +- test/fixtures.coffee | 2 +- test/specs/source/base_model_spec.coffee | 9 +- test/specs/source/binding_specs.coffee | 225 +++++++++++ .../specs/source/transformations_specs.coffee | 365 ------------------ 9 files changed, 358 insertions(+), 399 deletions(-) create mode 100644 test/specs/source/binding_specs.coffee delete mode 100644 test/specs/source/transformations_specs.coffee diff --git a/src/base_model.coffee b/src/base_model.coffee index bfba009..c142dc6 100644 --- a/src/base_model.coffee +++ b/src/base_model.coffee @@ -425,28 +425,33 @@ class Sirius.BaseModel # Base setter # @param attr [String] - attribute # @param value [Any] - value - # @note if attribute is object, then and value should be object, if value keys and values copy into attribute object + # an attribute will be updated only if the attriubute is valid # @throw Error, when attributes not defined for current model # @return [Void] set: (attr, value) -> if @_is_computed_attribute(attr) throw new Error("Impossible set computed attribute '#{attr}' in '#{@_klass_name()}'") - @_set(attr, value) - _set: (attr, value) -> + _set: (attr, value, force = false) -> @_attribute_present(attr) oldvalue = @["_#{attr}"] @["_#{attr}"] = value - @logger.debug("[#{@constructor.name}] set: '#{attr}' to '#{value}'") - @validate(attr) - @_compute(attr, value) - @_call_callbacks(attr, value, oldvalue) + + # is should set any way if force is true + flag = force || @is_valid(attr) + + if flag + @logger.debug("[#{@constructor.name}] set: '#{attr}' to '#{value}'") + @_compute(attr, value) + @_call_callbacks(attr, value, oldvalue) + else + @["_#{attr}"] = oldvalue # # Base getter @@ -470,7 +475,7 @@ class Sirius.BaseModel @attrs() @logger.debug("Reset attributes: '#{tmp.join(",")}' for #{@_klass_name()}") for attr in tmp - @set(attr, null) + @_set(attr, null, true) if @errors[attr]? @errors[attr] = {} @@ -489,8 +494,17 @@ class Sirius.BaseModel # Check, if model instance is valid # @return [Boolean] true, when is valid, otherwise false - is_valid: () -> - Object.keys(@_is_valid_attr).filter((key) => !@_is_valid_attr[key]).length == 0 + is_valid: (attr = null) -> + if attr? + if @_is_valid_attr[attr]? + @_is_valid_attr[attr] + else + true + else + Object.keys(@_is_valid_attr).filter((key) => + !@_is_valid_attr[key] + ).length == 0 + # Call when you want validate model # @nodoc @@ -664,7 +678,7 @@ class Sirius.BaseModel @logger.debug("Register new listener for #{@constructor.name}") @_listeners.push(transformer) - # sync state + # sync state ???? _attrs = @get_attributes() for attr in _attrs if @["_#{attr}"] isnt null diff --git a/src/materialization.coffee b/src/materialization.coffee index 6a3db4e..cf53754 100644 --- a/src/materialization.coffee +++ b/src/materialization.coffee @@ -140,6 +140,12 @@ class AbstractMaterializer has_to: () -> @_to? + fields_map: () -> + obj = {} + for f in @fields + obj[f.field()] = f + obj + materialize: () -> @fields @@ -148,7 +154,7 @@ class AbstractMaterializer # interface-like -class MaterializerWithImpl extends AbstractMaterializer +class MaterializerTransformImpl extends AbstractMaterializer transform: (f) -> unless Sirius.Utils.is_function(f) @@ -168,7 +174,7 @@ class MaterializerWithImpl extends AbstractMaterializer -class ModelToViewMaterializer extends MaterializerWithImpl +class ModelToViewMaterializer extends MaterializerTransformImpl field: (from_name) -> result = from_name if Sirius.Utils.is_function(from_name) @@ -231,15 +237,23 @@ class ModelToViewMaterializer extends MaterializerWithImpl run: () -> - obj = {} - for f in @fields - obj[f.field()] = f - clb = (attribute, changes) -> - if obj[attribute]? - f.trnaform(changes, f.to()) + @current.normalize() + + obj = @fields_map() + clb = (attribute, value) -> + f = obj[attribute] + if f? + transformed = f.transform().call(this, value, f.to()) + if f.has_handle() + f.handle().call(null, f.to(), transformed) # view and changes + else + # default, just a swap + f.to().render(transformed).swap(f.attribute()) + @_from._register_state_listener(clb) -class ViewToModelMaterializer extends MaterializerWithImpl + +class ViewToModelMaterializer extends MaterializerTransformImpl field: (element) -> el = null if Sirius.Utils.is_string(element) @@ -284,6 +298,27 @@ class ViewToModelMaterializer extends MaterializerWithImpl @current.to(result) @ + run: () -> + @current.normalize() + model = @_to + for field in @fields + element = field.field().get_element() + clb = (result) -> + transformed = field.transform().call(null, result) + if field.to().indexOf(".") != -1 # validator + model.set_error(field.to(), transformed) + else + model.set(field.to(), transformed) + + observer = new Sirius.Internal.Observer( + element, + element, + field.attribute(), + clb + ) + field.field()._register_state_listener(observer) + + class ViewToViewMaterializer extends ViewToModelMaterializer to: (element) -> el = null @@ -315,6 +350,27 @@ class ViewToViewMaterializer extends ViewToModelMaterializer @current.handle(f) @ + run: () -> + @current.normalize() + for field in @fields + element = field.field().get_element() + clb = (result) -> + transformed = field.transform(result) + if field.has_handle() + field.handle().call(this, transformed, field.to()) + else + # TODO checkbox !!!! + field.to().render(transformed).swap() + + observer = new Sirius.Internal.Observer( + element, + element, + field.attribute(), + clb + ) + field.field()._register_state_listener(observer) + + class ViewToFunctionMaterializer extends ViewToModelMaterializer to: (f) -> unless Sirius.Utils.is_function(f) @@ -323,6 +379,20 @@ class ViewToFunctionMaterializer extends ViewToModelMaterializer super.to(f) @ + run: () -> + @current.normalize() + # already zoomed + for field in @fields + element = field.field().get_element() + observer = new Sirius.Internal.Observer( + element, + element, + field.attribute(), + field.to() + ) + field.field()._register_state_listener(observer) + + class ModelToFunctionMaterializer extends AbstractMaterializer field: (attr) -> result = attr @@ -348,6 +418,14 @@ class ModelToFunctionMaterializer extends AbstractMaterializer @current.to(f) @ + run: () -> + obj = @fields_map() + clb = (attribute, value) -> + if obj[attribute]? + obj[attribute].to().call(null, value) + + @_from._register_state_listener(clb) + class Materializer diff --git a/test/SpecRunner.html b/test/SpecRunner.html index c6f7aa4..b1ae505 100644 --- a/test/SpecRunner.html +++ b/test/SpecRunner.html @@ -122,16 +122,16 @@ - - - - - - - - - + + + + + + + + + diff --git a/test/SpecRunnerPrototype.html b/test/SpecRunnerPrototype.html index 0bf6fc7..4e13d07 100644 --- a/test/SpecRunnerPrototype.html +++ b/test/SpecRunnerPrototype.html @@ -125,7 +125,8 @@ - + + diff --git a/test/SpecRunnerVanilla.html b/test/SpecRunnerVanilla.html index 074334a..a7bd7fb 100644 --- a/test/SpecRunnerVanilla.html +++ b/test/SpecRunnerVanilla.html @@ -126,7 +126,8 @@ - + + diff --git a/test/fixtures.coffee b/test/fixtures.coffee index cf646fc..bc1505e 100644 --- a/test/fixtures.coffee +++ b/test/fixtures.coffee @@ -98,7 +98,7 @@ class MyCustomValidator extends Sirius.Validator Sirius.BaseModel.register_validator('custom', MyCustomValidator) class ModelwithValidators extends Sirius.BaseModel - @attrs: ["id", "title", "description"] + @attrs: ["id", "title", "description", "context"] @validate : id: presence: true, diff --git a/test/specs/source/base_model_spec.coffee b/test/specs/source/base_model_spec.coffee index 9be4be8..701989d 100644 --- a/test/specs/source/base_model_spec.coffee +++ b/test/specs/source/base_model_spec.coffee @@ -55,7 +55,7 @@ describe "BaseModel", -> @attrs: ["id"] @guid_for: ["id"] - expect(new Test1().id()).not.toBeNull() + expect(new Test2().id()).not.toBeNull() class Test3 extends Sirius.BaseModel @attrs: ["id"] @@ -304,16 +304,19 @@ describe "BaseModel", -> expect(m.get_errors('title')).toEqual([]) expect(m.get_errors('description')).toEqual([]) expect(m.is_valid()).toBeFalse() + expect(m.is_valid('context')).toBeTrue() it "failed only integers and range", -> m.id("123.1") expect(m.get_errors('id').length).toEqual(2) expect(m.is_valid()).toBeFalse() + expect(m.is_valid('context')).toBeTrue() it "failed in range", -> m.id(12) expect(m.get_errors('id').length).toEqual(1) expect(m.is_valid()).toBeFalse() + expect(m.is_valid('context')).toBeTrue() describe "validate title", -> it "failed format", -> @@ -403,7 +406,7 @@ describe "BaseModel", -> @comp("full", "full_name", "full_name1") @validate : full_name: - length: min: 3, max: 7 + length: min: 3, max: 8 model = new Test1() expect(model.full_name()).toBeNull() @@ -413,7 +416,9 @@ describe "BaseModel", -> expect(model.full_name()).toEqual("John Doe") expect(model.full_name1()).toEqual("John-Doe") expect(model.full()).toEqual("John Doe John-Doe") + model.first_name("1") expect(model.get_errors('full_name').length).toEqual(1) + expect(model.full_name()).toEqual("John Doe") it "checks computed attributes", -> class Test1 extends Sirius.BaseModel diff --git a/test/specs/source/binding_specs.coffee b/test/specs/source/binding_specs.coffee new file mode 100644 index 0000000..4f5d681 --- /dev/null +++ b/test/specs/source/binding_specs.coffee @@ -0,0 +1,225 @@ +describe "Binding", -> + describe "Model To Function", -> + class Test1 extends Sirius.BaseModel + @attrs: ["id", "name"] + @validate: + id: + numericality: only_integers: true + + it "produce changes", -> + model = new Test1() + results = [] + materializer = Materializer.build(model) + materializer + .field((x) -> x.id) + .to((id) -> results.push(id)) + .field((x) -> x.name) + .to((name) -> results.push(name)) + .field((x) -> x.errors.id.numericality) + .to((error) -> results.push(error)) + .run() + + expect(results.length).toEqual(0) + + model.name("test") + expect(results).toEqual(["test"]) + model.id("asd") + expect(results).toEqual(["test", "Only allows integer numbers"]) + model.id(123) + # '' - reset validation + expect(results).toEqual(["test", "Only allows integer numbers", '', 123]) + + describe "View To Function", -> + rootElement = "#view2function" + inputElement = "input[name='email']" + inputCheckbox = "input[type='checkbox']" + + view = new Sirius.View(rootElement) + + it "push changes from view", (done) -> + expected = "baz" + given = null + + materializer = Materializer.build(view) + materializer + .field(inputElement) + .to((result) -> + given = result.text; + ) + .run() + + input_text("#{rootElement} #{inputElement}", expected) + setTimeout( + () -> + expect(given).toEqual(expected) + done() + 1000 + ) + + it "push changes from checkbox view", (done) -> + given = null + func = (result, view, logger) -> + given = result['state'] + + materializer = Materializer.build(view) + materializer + .field(inputCheckbox) + .to((result) -> given = result.state) + .run() + + check_element("#{rootElement} #{inputCheckbox}", true) + setTimeout( + () -> + expect(given).toEqual(true) + done() + 1000 + ) + + it "push changes from view#attribute", (done) -> + expected = "new-bind-class" + given = [] + + materializer = Materializer.build(view) + materializer + .field(inputElement) + .from('class') + .to((result) -> + given.push(result['attribute'], result['text']) + ).run() + + adapter.set_attr("#{rootElement} #{inputElement}", "class", expected) + + setTimeout( + () -> + expect(given).toEqual(["class", expected]) + done() + 1000 + ) + + describe "View To View", -> + rootElement = "#view2view" + view = new Sirius.View(rootElement) + + it "from source to mirror", -> + sourceElement = "input[name='source']" + source = view.zoom(sourceElement) + mirror = view.zoom(".mirror") + materializer = Materializer.build(view, view) + materializer + .field(source) + .to(mirror) + .handle((result, v) -> + v.zoom(".mirror1").render(result.text).swap() + v.zoom(".mirror-attr1").render(result.text).swap('data-mirror') + ).run() + + text = "foo" + input_text("#{rootElement} #{sourceElement}", text) + expect(get_text("#{rootElement} .mirror1")).toEqual(text) + expect(adapter.get_attr("#{rootElement} .mirror-attr1", 'data-mirror')).toEqual(text) + + describe "View To Model", -> + class Test1 extends Sirius.BaseModel + @attrs: ["foo", "is_checked"] + @validate: + foo: + length: min: 3, max: 10 + + rootElement = "#view2model" + view = new Sirius.View(rootElement) + + it "from text to model (+validation)", -> + model = new Test1({foo: "abcd"}) + + materializer = Materializer.build(view, model) + materializer + .field("input[name='source']") + .to((b) -> b.foo) + .transform((x) -> "#{x.text}!") + .run() + + input = "q4444" + input_text("#{rootElement} input[name='source']", input) + expect(model.foo()).toEqual("#{input}!") + expect(model.is_valid()).toBeTrue() + + it "from checkbox to bool attribute", -> + model = new Test1() + materializer = Materializer.build(view, model) + materializer + .field("input[name='bool-source']") + .to((b) -> b.is_checked) + .transform((x) -> x.state) + .run() + + check_element("#{rootElement} input[name='bool-source']", true) + expect(model.is_checked()).toBeTrue() + check_element("#{rootElement} input[name='bool-source']", false) + expect(model.is_checked()).toBeFalse() + + describe "model to view", -> + class Test1 extends Sirius.BaseModel + @attrs: ["foo", "is_checked"] + @validate: + foo: + length: min: 3, max: 10 + + rootElement = "#model2view" + view = new Sirius.View(rootElement) + + it "pass from property to input", () -> + model = new Test1() + output = "input[name='output-foo']" + Materializer.build(model, view) + .field((x) -> x.foo) + .to(output) + .run() + + model.foo("abcd") + + expect(get_text("#{rootElement} #{output}")).toEqual("abcd") + + it "pass from property to data-attribute", () -> + model = new Test1() + output = "input[name='output-foo']" + Materializer.build(model, view) + .field((x) -> x.foo) + .to(output) + .attribute('data-output') + .run() + + model.foo("booom!") + + expect(adapter.get_attr("#{rootElement} #{output}", "data-output")).toEqual(model.foo()) + + it "pass from property to span", () -> + more = "+test" + model = new Test1() + output = "span.output-foo" + Materializer.build(model, view) + .field((x) -> x.foo) + .to(output) + .transform((r) -> "#{r}#{more}") + .handle((view, result) -> + view.render(result).swap() + ).run() + + model.foo("span!") + + expect(get_text("#{rootElement} #{output}")).toEqual("#{model.foo()}#{more}") + + it "pass from validation to span", -> + model = new Test1() + output = "span.output-validation-foo" + Materializer.build(model, view) + .field((x) -> x.errors.foo.length) + .to(output) + .run() + + model.foo("Z") + + expect(get_text("#{rootElement} #{output}")).toMatch(/Required length/) + + # and reset validation + model.foo("abcd") + expect(get_text("#{rootElement} #{output}")).toBe("") \ No newline at end of file diff --git a/test/specs/source/transformations_specs.coffee b/test/specs/source/transformations_specs.coffee deleted file mode 100644 index 12c2c07..0000000 --- a/test/specs/source/transformations_specs.coffee +++ /dev/null @@ -1,365 +0,0 @@ - -describe "Transformations", -> - - describe "common errors", -> - it "fails for non-View/Model in #from position", -> - expect(() -> - new Sirius.Transformer(1, () -> ) - ).toThrowError("Bad argument: Model or View required, 'number' given") - - it "fails for non-V/M/F in #to position", -> - expect(() -> - new Sirius.Transformer(new Sirius.BaseModel(), 1) - ).toThrowError("Bind works only with BaseModel, BaseView or Function, 'number' given") - - it "fails for bind m2m", -> - class Test1 extends Sirius.BaseModel - @attrs: ['id'] - class Test2 extends Sirius.BaseModel - @attrs: ['id'] - - expect(() -> - new Sirius.Transformer(new Test1(), new Test2()) - ).toThrowError("No way to bind two Models: 'Test1' and 'Test2'") - - describe "#compliance", -> - view = new Sirius.View("#transformations-view") - - class Test1 extends Sirius.BaseModel - @attrs: ["id"] - @validate: - id: - presence: true - - it "fails when materializer is not an object", -> - expect(() -> - new Sirius.Transformer(view, new Test1()).run(1) - ).toThrowError("Materializer must be object, '#{typeof 1}' given") - - it "fails when materizlier is empty object", -> - expect(() -> - new Sirius.Transformer(view, new Test1()).run({}) - ).toThrowError("Materializer must be non empty object") - - describe "from model", -> - it "fails when materializer does not contain attribute", -> - expect(() -> - new Sirius.Transformer(new Test1(), () -> ).run({ - "name": { - "to": "input" - } - }) - ).toThrowError("Attribute 'name' not found in model attributes: 'Test1', available: '[id]'") - - it "fails with invalid error binding", -> - expect(() -> - new Sirius.Transformer(new Test1(), () ->).run({ - "errors.id.numericality": { - "to": "input" - } - }) - ).toThrowError("Unexpected 'errors.id.numericality' errors attribute for 'Test1' (check validators)") - - it "success for validation", -> - expect(() -> - new Sirius.Transformer(new Test1(), () ->).run({ - "errors.id.presence": { - "to": "input" - } - }) - ).not.toThrowError() - - describe "#to model", -> - it "fails when materializer.to is not defined", -> - expect(() -> new Sirius.Transformer(view, new Test1()).run({foo: "bar"})) - .toThrowError("Failed to create transformer for 'Test1', because '#{JSON.stringify({foo: "bar"})}', does not contain 'to'-property") - - it "fails, with unexpected properties", -> - expect(() -> - new Sirius.Transformer(view, new Test1()).run({ - "input": { - to: "name" - } - }) - ).toThrowError( - "Unexpected 'name' for model binding. Model is: 'Test1', available attributes: '[id]'" - ) - - describe "view to view", -> - it "to is required", -> - expect(() -> - new Sirius.Transformer(view, view).run({ - "foo": "bar" - }) - ).toThrowError(/Define View to View binding with/) - - it "to must be array or string", -> - expect(() -> - new Sirius.Transformer(view, view).run({ - "to": 1 - }) - ).toThrowError("View to View binding must contains 'to' as an array or a string, but number given") - - it "to must contains selector propery", -> - expect(() -> - new Sirius.Transformer(view, view).run({ - "to": [ - { - "foo": "#my-id" - } - ] - }) - ).toThrowError(/You defined binding with/) - - - describe "view to function", -> - it "from property must be required", -> - expect(() -> - new Sirius.Transformer(view, () ->).run({ - "input": { - "foo": "bar" - } - }) - ).toThrowError(/View to Function binding must contain/) - - describe "model to function", -> - class Test1 extends Sirius.BaseModel - @attrs: ["name"] - @validate: - name: - length: min: 3, max: 7 - - it "push attribute changes from model to function", -> - tmp = [] - new_name = "new" - f = (attr, value) -> - tmp.push(attr, value) - - model = new Test1() - model.pipe(f) - - model.name(new_name) - expect(model.name()).toEqual(new_name) - - new_name1 = "boo" - model.set("name", new_name1) - expect(model.get('name')).toEqual(new_name1) - expect(tmp.indexOf("name")).not.toEqual(-1) - expect(tmp.indexOf(new_name)).not.toEqual(-1) - expect(tmp.indexOf(new_name1)).not.toEqual(-1) - - it "calls on invalid validation too", -> - tmp = [] - f = (attr, value) -> - tmp.push(attr, value) - - model = new Test1() - model.pipe(f) - model.name("1") - expect(model.is_valid()).toBeFalse() - expect(tmp).toEqual(["errors.name.length", "Required length in range [0..7], given: 1", 'name', "1"]) - - describe "model to view", -> - class Test1 extends Sirius.BaseModel - @attrs: ["foo", "is_checked"] - @validate: - foo: - length: min: 3, max: 10 - - rootElement = "#model2view" - view = new Sirius.View(rootElement) - - it "pass from property to input", () -> - model = new Test1() - output = "input[name='output-foo']" - model.bind(view, { - "foo": { - "to": output - } - }) - model.foo("abcd") - - get_text("#{rootElement} #{output}") - - - it "pass from property to data-attribute", () -> - model = new Test1() - output = "input[name='output-foo']" - model.bind(view, { - "foo": { - "to": output, - "attr": "data-output" - } - }) - model.foo("booom!") - - expect(adapter.get_attr("#{rootElement} #{output}", "data-output")).toEqual(model.foo()) - - it "pass from property to span", () -> - more = "+test" - model = new Test1() - output = "span.output-foo" - model.bind(view, { - "foo": { - "to": output, - "with": (value, selector, view, attribute = 'text') -> - view.zoom(selector).render("#{value}#{more}").swap(attribute) - } - }) - model.foo("span!") - - expect(get_text("#{rootElement} #{output}")).toEqual("#{model.foo()}#{more}") - - it "pass from validation to span", -> - model = new Test1() - output = "span.output-validation-foo" - model.bind(view, { - "errors.foo.length": { - "to": output - } - }) - model.foo("Z") - - expect(get_text("#{rootElement} #{output}")).toMatch(/Required length/) - - # and reset validation - model.foo("abcd") - expect(get_text("#{rootElement} #{output}")).toBe("") - - describe "view to model", -> - class Test1 extends Sirius.BaseModel - @attrs: ["foo", "is_checked"] - @validate: - foo: - length: min: 3, max: 10 - - rootElement = "#view2model" - view = new Sirius.View(rootElement) - - it "from text to model (+validation)", -> - model = new Test1({foo: "abcd"}) - view.bind(model, { - "input[name='source']": { - to: "foo", - with: (value) -> "#{value}!" - } - }) - input = "q" - input_text("#{rootElement} input[name='source']", input) - expect(model.foo()).toEqual("#{input}!") - expect(model.is_valid()).toBeFalse() - - it "from checkbox to bool attribute", -> - model = new Test1() - view.bind(model, { - "input[name='bool-source']": { - to: "is_checked" - from: "checked" - } - }) - check_element("#{rootElement} input[name='bool-source']", true) - expect(model.is_checked()).toBeTrue() - check_element("#{rootElement} input[name='bool-source']", false) - expect(model.is_checked()).toBeFalse() - - - - describe "view to view", -> - rootElement = "#view2view" - view = new Sirius.View(rootElement) - - it "from source to mirror", -> - more = "+bar" - sourceElement = "input[name='source']" - source = view.zoom(sourceElement) - mirror = view.zoom(".mirror") - source.bind(mirror, { - to: [ - { - "selector": ".mirror1" - }, - "selector": ".mirror-attr1", - "attribute": "data-mirror", - "with" : (value, selector, view, attribute = 'text') -> - view.zoom(selector).render("#{value}#{more}").swap(attribute) - ] - }) - - text = "foo" - input_text("#{rootElement} #{sourceElement}", text) - expect(get_text("#{rootElement} .mirror1")).toEqual(text) - expect(adapter.get_attr("#{rootElement} .mirror-attr1", 'data-mirror')).toEqual("#{text}#{more}") - - - describe "view to function", -> - rootElement = "#view2function" - inputElement = "input[name='email']" - inputCheckbox = "input[type='checkbox']" - - view = new Sirius.View(rootElement) - - - it "push changes from view", (done) -> - expected = "baz" - given = null - func = (result, view, logger) -> - given = result['text'] - - - materializer = { - "#{inputElement}": { - from: 'text' - } - } - view.pipe(func, materializer) - - input_text("#{rootElement} #{inputElement}", expected) - setTimeout( - () -> - expect(given).toEqual(expected) - done() - 1000 - ) - - it "push changes from checkbox view", (done) -> - given = null - func = (result, view, logger) -> - given = result['state'] - - materializer = { - "#{inputCheckbox}": { - from: 'text' - } - } - view.pipe(func, materializer) - - check_element("#{rootElement} #{inputCheckbox}", true) - setTimeout( - () -> - expect(given).toEqual(true) - done() - 1000 - ) - - it "push changes from view#attribute", (done) -> - expected = "new-bind-class" - given = [] - func = (result, view, logger) -> - given.push(result['attribute'], result['text']) - - materializer = { - "#{inputElement}": { - from: 'class' - } - } - view.pipe(func, materializer) - - adapter.set_attr("#{rootElement} #{inputElement}", "class", expected) - - setTimeout( - () -> - expect(given).toEqual(["class", expected]) - done() - 1000 - ) - From 61b8e156dc1605a4099b933b9fd645f501b01262 Mon Sep 17 00:00:00 2001 From: mike Date: Sun, 19 Apr 2020 20:36:22 +0300 Subject: [PATCH 05/21] new binding --- Rakefile | 3 +- src/base_model.coffee | 13 - src/materialization.coffee | 40 +- src/transformer.coffee | 359 ------------------ src/view.coffee | 12 - test/specs/source/binding_specs.coffee | 22 +- .../specs/source/materialization_specs.coffee | 30 +- 7 files changed, 47 insertions(+), 432 deletions(-) delete mode 100644 src/transformer.coffee diff --git a/Rakefile b/Rakefile index ad68d70..5e41af7 100644 --- a/Rakefile +++ b/Rakefile @@ -107,8 +107,7 @@ task :build do comment_header version adapter vanilla_js_adapter logger internal promise utils sirius validators observer - view base_model transformer collection - materialization + view base_model collection materialization )) core_files = coffee(src, %w( diff --git a/src/base_model.coffee b/src/base_model.coffee index c142dc6..cbdd478 100644 --- a/src/base_model.coffee +++ b/src/base_model.coffee @@ -684,19 +684,6 @@ class Sirius.BaseModel if @["_#{attr}"] isnt null transformer.apply(null, [attr, @["_#{attr}"]]) - # @alias `bind` - pipe: (output, materializer = {}) -> - # TODO default attributes - t = new Sirius.Transformer(@, output) - t.run(materializer) - - return - - # @param [Function] - binding function - # @param [Object] - pet-attribute transformation description - bind: (output, materializer = {}) -> - @pipe(output, materializer) - # Register pair - name and class for validate # @param [String] - validator name # @param [T <: Sirius.Validator] - class which extends Sirius.Validator diff --git a/src/materialization.coffee b/src/materialization.coffee index cf53754..bb7b56c 100644 --- a/src/materialization.coffee +++ b/src/materialization.coffee @@ -48,7 +48,7 @@ # ok, it's for BaseModelToView -class FieldMaker +class Sirius.FieldMaker constructor: (@_from, @_to, @_attribute, @_transform, @_handle) -> has_to: () -> @@ -103,10 +103,10 @@ class FieldMaker "#{@_from} ~> #{@_transform} ~> #{@_to}##{@_attribute}" @build: (from) -> - new FieldMaker(from) + new Sirius.FieldMaker(from) -class AbstractMaterializer +class Sirius.AbstractMaterializer constructor: (@_from, @_to) -> @fields = [] @current = null @@ -115,7 +115,7 @@ class AbstractMaterializer if @current? @current.normalize() - @current = FieldMaker.build(from_name) + @current = Sirius.FieldMaker.build(from_name) @fields.push(@current) _zoom_with: (view, maybeView) -> @@ -154,7 +154,7 @@ class AbstractMaterializer # interface-like -class MaterializerTransformImpl extends AbstractMaterializer +class Sirius.MaterializerTransformImpl extends Sirius.AbstractMaterializer transform: (f) -> unless Sirius.Utils.is_function(f) @@ -174,13 +174,13 @@ class MaterializerTransformImpl extends AbstractMaterializer -class ModelToViewMaterializer extends MaterializerTransformImpl +class Sirius.ModelToViewMaterializer extends Sirius.MaterializerTransformImpl field: (from_name) -> result = from_name if Sirius.Utils.is_function(from_name) result = from_name(@_from.get_binding()) - Materializer._check_model_compliance(@_from, result) + Sirius.Materializer._check_model_compliance(@_from, result) super.field(result) @@ -253,7 +253,7 @@ class ModelToViewMaterializer extends MaterializerTransformImpl @_from._register_state_listener(clb) -class ViewToModelMaterializer extends MaterializerTransformImpl +class Sirius.ViewToModelMaterializer extends Sirius.MaterializerTransformImpl field: (element) -> el = null if Sirius.Utils.is_string(element) @@ -293,7 +293,7 @@ class ViewToModelMaterializer extends MaterializerTransformImpl result = attribute(@_to.get_binding()) if @_to? && @_to instanceof Sirius.BaseModel - Materializer._check_model_compliance(@_to, result) + Sirius.Materializer._check_model_compliance(@_to, result) @current.to(result) @ @@ -319,7 +319,7 @@ class ViewToModelMaterializer extends MaterializerTransformImpl field.field()._register_state_listener(observer) -class ViewToViewMaterializer extends ViewToModelMaterializer +class Sirius.ViewToViewMaterializer extends Sirius.ViewToModelMaterializer to: (element) -> el = null if Sirius.Utils.is_string(element) @@ -371,7 +371,7 @@ class ViewToViewMaterializer extends ViewToModelMaterializer field.field()._register_state_listener(observer) -class ViewToFunctionMaterializer extends ViewToModelMaterializer +class Sirius.ViewToFunctionMaterializer extends Sirius.ViewToModelMaterializer to: (f) -> unless Sirius.Utils.is_function(f) throw new Error("Function is required") @@ -393,13 +393,13 @@ class ViewToFunctionMaterializer extends ViewToModelMaterializer field.field()._register_state_listener(observer) -class ModelToFunctionMaterializer extends AbstractMaterializer +class Sirius.ModelToFunctionMaterializer extends Sirius.AbstractMaterializer field: (attr) -> result = attr if Sirius.Utils.is_function(attr) result = attr(@_from.get_binding()) - Materializer._check_model_compliance(@_from, result) + Sirius.Materializer._check_model_compliance(@_from, result) super.field(result) @@ -427,21 +427,21 @@ class ModelToFunctionMaterializer extends AbstractMaterializer @_from._register_state_listener(clb) -class Materializer +class Sirius.Materializer # from must be View or BaseModel # to is View, BaseModel, or Function constructor: (from, to) -> if from instanceof Sirius.BaseModel && to instanceof Sirius.View - return new ModelToViewMaterializer(from, to) + return new Sirius.ModelToViewMaterializer(from, to) if from instanceof Sirius.View && to instanceof Sirius.BaseModel - return new ViewToModelMaterializer(from, to) + return new Sirius.ViewToModelMaterializer(from, to) if from instanceof Sirius.View && to instanceof Sirius.View - return new ViewToViewMaterializer(from, to) + return new Sirius.ViewToViewMaterializer(from, to) if from instanceof Sirius.View && !to? - return new ViewToFunctionMaterializer(from) + return new Sirius.ViewToFunctionMaterializer(from) if from instanceof Sirius.BaseModel && !to? - return new ModelToFunctionMaterializer(from) + return new Sirius.ModelToFunctionMaterializer(from) else throw new Error("Illegal arguments: 'from'/'to' must be instance of Sirius.View/or Sirius.BaseModel") @@ -469,7 +469,7 @@ class Materializer @build: (from, to) -> - new Materializer(from, to) + new Sirius.Materializer(from, to) diff --git a/src/transformer.coffee b/src/transformer.coffee deleted file mode 100644 index de21c51..0000000 --- a/src/transformer.coffee +++ /dev/null @@ -1,359 +0,0 @@ -### - Create Binding between Model and View and vice versa - @see https://github.com/fntz/sirius/issues/31 - - This class have own Logger Name [Transformer] - - i.e. Pipe - - note only one `pipe` between one model class and view class - - @example - - #Base Idea - - - - class MyModel extends Sirius.Model - @attrs: ['name'] - - view = new Sirius.View("#my-name") - - model = new MyModel() - - - pipe_view_to_model = Sirius.Transformer.draw - "#my-name": - to: 'name' - from: 'text' # class, id, *, default: text - via: (value) -> - return "#{value}!!!" - - view.bind(model, pipe_view_to_model) - - - pipe_model_to_view = Sirius.Transformer.draw - "name": - to: "#my-name" - via: (value, selector, view) -> - @view.zoom(selector).set_value(value) - # or maybe - - model.bind(view, pipe_model_to_view) - - - Small notes: - + Only one pipe - + Need method for reverse pipe (from view to model -> from model to view) - + Draw default for simple cases (text to text) - - - Questions: - + How to transformer should works between js objects? - -### - -class Sirius.Internal.AbstractTransformer - - constructor: (@_path, @_from, @_to) -> - @logger = Sirius.Application.get_logger(@constructor.name) - @_register() - - _register: () -> - - -# @private -# @nodoc -class Sirius.Internal.ToFunctionTransformer extends Sirius.Internal.AbstractTransformer - - _register: () -> - if @_from instanceof Sirius.BaseModel - @_from._register_state_listener(@_to) - else - @_from._register_state_listener(@) - clb = @_fire_generator() - top = @_from.get_element() - for selector, value of @_path - from_property = value["from"] || Sirius.Internal.DefaultProperty - new Sirius.Internal.Observer("#{top} #{selector}", selector, from_property, clb) - - _fire_generator: () -> - view = @_from - func = @_to - - callback = (result) -> - func(result, view) - - callback - -# @private -# @nodoc -class Sirius.Internal.ToViewTransformer extends Sirius.Internal.AbstractTransformer - _register: () -> - clb = @_fire_generator() - - if @_from instanceof Sirius.View - top = @_from.get_element() - to = @_path['to'] - for o in to - [w, attr, selector] = if Sirius.Utils.is_string(o) - [null, 'text', o] - else - [o['from'] || top, o['attribute'] || Sirius.Internal.DefaultProperty, o['selector']] - - top = if top == w - top - else if w? - "#{top} #{w}" - else - top - - @logger.debug("Observe '#{top}' -> '#{@_to.get_element()} #{selector}'") - new Sirius.Internal.Observer(top, w, attr, clb) - - else # Model - @_from._register_state_listener(clb) - - - @_default_materializer_method: () -> - (value, selector, view, attribute = 'text') -> - view.zoom(selector).render(value).swap(attribute) - - _fire_generator: () -> - view = @_to - path = @_path - logger = @logger - - # view 2 view - if @_from instanceof Sirius.View - callback = (result) -> - to = path['to'] - value = result.text - for o in to - if Sirius.Utils.is_string(o) - materializer = Sirius.Internal.ToViewTransformer._default_materializer_method() - materializer(value, o, view, attribute) - - else # object - selector = o['selector'] - attr = o['attribute'] || 'text' - materializer = o['with'] || Sirius.Internal.ToViewTransformer._default_materializer_method() - materializer(value, selector, view, attr) - - - callback - - else # model 2 view - model = @_from - callback = (attribute, value) -> - obj = path[attribute] - if obj - logger.debug("Apply new value for '#{attribute}' for '#{view.get_element()}', value: #{value} from #{model._klass_name()}") - to = obj['to'] - attr = obj['attr'] || 'text' - materializer = obj['with'] || Sirius.Internal.ToViewTransformer._default_materializer_method() - - materializer(value, to, view, attr) - - callback - -# @private -# @nodoc -class Sirius.Internal.ToModelTransformer extends Sirius.Internal.AbstractTransformer - _register: () -> - @_from._register_state_listener(@) - clb = @_fire_generator() - top = @_from.get_element() - for k, v of @_path - w = @_path["from"] || "text" - new Sirius.Internal.Observer("#{top} #{k}", k, w, clb) - - _default_materializer_method: () -> - (value) -> value - - _fire_generator: () -> - view = @_from - path = @_path - model = @_to - logger = @logger - - callback = (result) -> - - value = path[result.original] - if value - to = value['to'] - from = value['from'] || 'text' - materializer = value['with'] || ((value) -> value) - logger.debug("Apply new value from #{result.from} (#{result.original}) to #{model._klass_name()}.#{to}") - # result, view, selector, attribute, element - model.set(to, materializer(result.text || result.state, view, result.original, from, result.element)) - - callback - - -class Sirius.Transformer - - # from - @_Model : 0 - @_View : 1 - - # to - # Model, View, Function - @_Function: 2 - - _from: null - - _to: null - - # hash object - _path: null - - constructor: (from, to) -> - @logger = Sirius.Application.get_logger(@constructor.name) - - if from instanceof Sirius.BaseModel - @_from = from - else if from instanceof Sirius.View - @_from = from - else - throw new Error("Bad argument: Model or View required, '#{typeof from}' given") - - if to instanceof Sirius.BaseModel - if !(@_from instanceof Sirius.BaseModel) - @_to = to - else - throw new Error("No way to bind two Models: '#{from._klass_name()}' and '#{to._klass_name()}'") - else if to instanceof Sirius.View - @_to = to - else if Sirius.Utils.is_function(to) - @_to = to - else - throw new Error("Bind works only with BaseModel, BaseView or Function, '#{typeof to}' given") - - - # @private - # @nodoc - # maybe should be a part of model? - _check_from_model_compliance: (materializer) -> - if @_from instanceof Sirius.BaseModel - name = @_from._klass_name() - attrs = @_from.get_attributes() - - for k, v of materializer - txt = "Attribute '#{k}' not found in model attributes: '#{name}', available: '[#{attrs}]'" - is_validator = false - if k.indexOf(".") != -1 # k is validator attribute - if k.lastIndexOf("errors") == 0 # start - tmp = k.split(".") # errors.id.numericality => [errors, id, numericality] - - if tmp.length != 3 - throw new Error("Try to bind '#{k}' from errors properties, but validator is not found, correct definition should be as 'errors.id.numericality'") - - [_, attr, validator_key] = tmp - - unless @_from._is_valid_validator("#{attr}.#{validator_key}") - throw new Error("Unexpected '#{k}' errors attribute for '#{name}' (check validators)") - else - is_validator = true - - throw new Error(txt) if attrs.indexOf(k) == -1 && !is_validator - - # actual bind - @logger.debug("bind: '#{name}.#{k}' -> #{v['to']}") - - # @nodoc - # @private - _check_to_model_compliance: (materializer) -> - if @_to instanceof Sirius.BaseModel - # {id: {to: attr}} - name = @_to._klass_name() - for k, v of materializer - attr = v['to'] - unless attr? - throw new Error("Failed to create transformer for '#{name}', because '#{JSON.stringify(materializer)}', does not contain 'to'-property") - else - attrs = @_to.get_attributes() - throw new Error("Unexpected '#{attr}' for model binding. Model is: '#{name}', available attributes: '[#{attrs}]'") if attrs.indexOf(attr) == -1 - @logger.debug("bind: '#{k}' -> '#{name}.#{attr}'") - - # @private - # @nodoc - _check_view_to_view_compliance: (materializer) -> - e = @_from.get_element() - e1 = @_to.get_element() - # validate, need 'to'-property - to = materializer['to'] - unless to - correct_way = '{"to": "selector"}' - throw new Error("Define View to View binding with: 'view1.bind(view2, #{correct_way})', but 'to'-property was not found") - else - if Sirius.Utils.is_array(to) - # check that in array string or object with selector property - for element in to - unless element['selector'] - correct_help = '{to: [{"selector": "#my-id", "attribute": "data-attr"}]}' - _e1 = "You defined binding with 'to' as an array of objects, but 'selector' property was not found" - _e2 = "Correct definition is: #{correct_help}" - throw new Error("#{_e1} #{_e2}") - else - selector = element['selector'] - @logger.debug("bind: '#{e}' -> '#{e1} #{selector}'") - - return materializer - else if Sirius.Utils.is_string(to) - @logger.debug("bind: '#{e}' -> '#{e1} #{to}'") - materializer['to'] = [to] - return materializer - else - throw new Error("View to View binding must contains 'to' as an array or a string, but #{typeof(to)} given") - - # @private - # @nodoc - _check_view_to_function_compliance: (materializer) -> - # check that 'from' is present - element = @_from.get_element() - - for k, v of materializer - unless v['from'] - correct_way = '{"selector": {"from": "text"}}' - throw new Error("View to Function binding must contain 'from'-property: #{correct_way}") - else - f = v['from'] - @logger.debug("bind: '#{element} #{k}' (from '#{f}') -> function") - - materializer - - # called implicitly with a `materializer` method - run: (materializer) -> - throw new Error("Materializer must be object, '#{typeof materializer}' given") unless Sirius.Utils.is_object(materializer) - - unless Sirius.Utils.is_function(@_to) - throw new Error("Materializer must be non empty object") if Object.keys(materializer).length == 0 - - throw new Error("Not all parameters defined for transformer: from: #{@_from}, to: #{@_to}") if !@_from || !@_to - - # checkers - @_check_from_model_compliance(materializer) - @_check_to_model_compliance(materializer) - - if @_from instanceof Sirius.View && @_to instanceof Sirius.View - @_path = @_check_view_to_view_compliance(materializer) - else if @_from instanceof Sirius.View && Sirius.Utils.is_function(@_to) - @_path = @_check_view_to_function_compliance(materializer) - else - @_path = materializer - - # strategies - if @_to instanceof Sirius.BaseModel - new Sirius.Internal.ToModelTransformer(materializer, @_from, @_to) - else if @_to instanceof Sirius.View - new Sirius.Internal.ToViewTransformer(materializer, @_from, @_to) - else if Sirius.Utils.is_function(@_to) - new Sirius.Internal.ToFunctionTransformer(materializer, @_from, @_to) - - - - - - - diff --git a/src/view.coffee b/src/view.coffee index c7d7d3d..348b29a 100644 --- a/src/view.coffee +++ b/src/view.coffee @@ -215,18 +215,6 @@ class Sirius.View @_Strategies.filter((arr) -> arr[0] == s).length != 0 - # @alias `bind` - pipe: (output, materializer) -> - @bind(output, materializer) - - bind: (output, materializer) -> - t = new Sirius.Transformer(@, output) - t.run(materializer) - - return - - - # Register new strategy for View # @param [String] - strategy name # @param [Object] - object with transform and render functions, take oldvalue, and newvalue for attribute diff --git a/test/specs/source/binding_specs.coffee b/test/specs/source/binding_specs.coffee index 4f5d681..43785ab 100644 --- a/test/specs/source/binding_specs.coffee +++ b/test/specs/source/binding_specs.coffee @@ -9,7 +9,7 @@ describe "Binding", -> it "produce changes", -> model = new Test1() results = [] - materializer = Materializer.build(model) + materializer = Sirius.Materializer.build(model) materializer .field((x) -> x.id) .to((id) -> results.push(id)) @@ -40,7 +40,7 @@ describe "Binding", -> expected = "baz" given = null - materializer = Materializer.build(view) + materializer = Sirius.Materializer.build(view) materializer .field(inputElement) .to((result) -> @@ -61,7 +61,7 @@ describe "Binding", -> func = (result, view, logger) -> given = result['state'] - materializer = Materializer.build(view) + materializer = Sirius.Materializer.build(view) materializer .field(inputCheckbox) .to((result) -> given = result.state) @@ -79,7 +79,7 @@ describe "Binding", -> expected = "new-bind-class" given = [] - materializer = Materializer.build(view) + materializer = Sirius.Materializer.build(view) materializer .field(inputElement) .from('class') @@ -104,7 +104,7 @@ describe "Binding", -> sourceElement = "input[name='source']" source = view.zoom(sourceElement) mirror = view.zoom(".mirror") - materializer = Materializer.build(view, view) + materializer = Sirius.Materializer.build(view, view) materializer .field(source) .to(mirror) @@ -131,7 +131,7 @@ describe "Binding", -> it "from text to model (+validation)", -> model = new Test1({foo: "abcd"}) - materializer = Materializer.build(view, model) + materializer = Sirius.Materializer.build(view, model) materializer .field("input[name='source']") .to((b) -> b.foo) @@ -145,7 +145,7 @@ describe "Binding", -> it "from checkbox to bool attribute", -> model = new Test1() - materializer = Materializer.build(view, model) + materializer = Sirius.Materializer.build(view, model) materializer .field("input[name='bool-source']") .to((b) -> b.is_checked) @@ -170,7 +170,7 @@ describe "Binding", -> it "pass from property to input", () -> model = new Test1() output = "input[name='output-foo']" - Materializer.build(model, view) + Sirius.Materializer.build(model, view) .field((x) -> x.foo) .to(output) .run() @@ -182,7 +182,7 @@ describe "Binding", -> it "pass from property to data-attribute", () -> model = new Test1() output = "input[name='output-foo']" - Materializer.build(model, view) + Sirius.Materializer.build(model, view) .field((x) -> x.foo) .to(output) .attribute('data-output') @@ -196,7 +196,7 @@ describe "Binding", -> more = "+test" model = new Test1() output = "span.output-foo" - Materializer.build(model, view) + Sirius.Materializer.build(model, view) .field((x) -> x.foo) .to(output) .transform((r) -> "#{r}#{more}") @@ -211,7 +211,7 @@ describe "Binding", -> it "pass from validation to span", -> model = new Test1() output = "span.output-validation-foo" - Materializer.build(model, view) + Sirius.Materializer.build(model, view) .field((x) -> x.errors.foo.length) .to(output) .run() diff --git a/test/specs/source/materialization_specs.coffee b/test/specs/source/materialization_specs.coffee index 307b782..60647da 100644 --- a/test/specs/source/materialization_specs.coffee +++ b/test/specs/source/materialization_specs.coffee @@ -5,40 +5,40 @@ describe "Materialization", -> id: presence: true - describe "Materializer", -> + describe "Sirius.Materializer", -> it "illegal arguments", -> expect(() -> - Materializer.build(1, 2) + Sirius.Materializer.build(1, 2) ).toThrowError(/Illegal arguments/) expect(() -> - Materializer.build(new Test1(), 1) + Sirius.Materializer.build(new Test1(), 1) ).toThrowError(/Illegal arguments/) expect(() -> - Materializer.build(new Sirius.View("asd"), "asd") + Sirius.Materializer.build(new Sirius.View("asd"), "asd") ).toThrowError(/Illegal arguments/) it "check model attributes", -> m = new Test1() expect(() -> - Materializer._check_model_compliance(m, "id") + Sirius.Materializer._check_model_compliance(m, "id") ).not.toThrowError() expect(() -> - Materializer._check_model_compliance(m, "foo") + Sirius.Materializer._check_model_compliance(m, "foo") ).toThrowError(/Attribute 'foo' not found in model/) expect(() -> - Materializer._check_model_compliance(m, "errors.id.presence") + Sirius.Materializer._check_model_compliance(m, "errors.id.presence") ).not.toThrowError() expect(() -> - Materializer._check_model_compliance(m, "errors.id.numericality") + Sirius.Materializer._check_model_compliance(m, "errors.id.numericality") ).toThrowError(/Unexpected 'errors.id.numericality' errors attribute/) expect(() -> - Materializer._check_model_compliance(m, "foo.bar") + Sirius.Materializer._check_model_compliance(m, "foo.bar") ).toThrowError(/Try to bind 'foo.bar' from errors properties/) describe "BaseModel to View", -> materializer = null beforeEach () -> - materializer = Materializer.build(new Test1(), new Sirius.View("#test")) + materializer = Sirius.Materializer.build(new Test1(), new Sirius.View("#test")) describe "field", -> it "unwrap function", -> @@ -140,7 +140,7 @@ describe "Materialization", -> materializer = null beforeEach () -> - materializer = Materializer.build(new Sirius.View("#test"), new Test1()) + materializer = Sirius.Materializer.build(new Sirius.View("#test"), new Test1()) describe "field", -> it "unwrap function", -> @@ -221,7 +221,7 @@ describe "Materialization", -> materializer = null beforeEach () -> - materializer = Materializer.build(new Sirius.View("#test"), new Sirius.View("#test1")) + materializer = Sirius.Materializer.build(new Sirius.View("#test"), new Sirius.View("#test1")) it "unwrap function", -> expect(() -> @@ -242,7 +242,7 @@ describe "Materialization", -> materializer = null beforeEach () -> - materializer = Materializer.build(new Sirius.View("#test"), new Sirius.View("#test1")) + materializer = Sirius.Materializer.build(new Sirius.View("#test"), new Sirius.View("#test1")) it "without field", -> expect(() -> @@ -274,7 +274,7 @@ describe "Materialization", -> describe "to", -> it "function is required", -> - materializer = Materializer.build(new Sirius.View("test")) + materializer = Sirius.Materializer.build(new Sirius.View("test")) expect(() -> materializer.field("test").to(1) ).toThrowError("Function is required") @@ -288,7 +288,7 @@ describe "Materialization", -> materializer = null beforeEach () -> - materializer = Materializer.build(new Test1()) + materializer = Sirius.Materializer.build(new Test1()) it "unwrap function", -> materializer.field((b) -> b.id) From cc543b77514356e6a7dfcf34cd18933b05bd9648 Mon Sep 17 00:00:00 2001 From: mike Date: Mon, 20 Apr 2020 20:57:06 +0300 Subject: [PATCH 06/21] update readme --- Readme.md | 124 ++++++++++++++++++++++++++---------------------------- 1 file changed, 59 insertions(+), 65 deletions(-) diff --git a/Readme.md b/Readme.md index 784adbc..9b99245 100644 --- a/Readme.md +++ b/Readme.md @@ -223,14 +223,40 @@ person.find("name", "Joe").to_json() # => {"id" : "g-u-i-d", "name" : "Joe", "ag [more about collections](https://github.com/fntz/sirius/wiki/Collections) -### 8. Binding +### 8. Binding (Materialization) -Support binding: view to model, view to view, model to view, or model|view to function. -And ot them support all strategies (how to change a content or an attribute) or transform (how to transform a value) methods. +Supported binding: + 1. view to model + 2. view to view + 3. model to view + 4. or model|view to function. +#### View To Model + +```coffee +# view +
+ +
+ +# model +class MyModel extends Sirius.BaseModel + @attrs: ["id", "name"] + +model = new MyModel() +view = new Sirius.View("#my-input") + +# and now materialize! +Materializer.build(view, model) # from view to model + .field((v) -> v.zoom("input")) + .to((m) -> m.name) # or just .to('name') + .transform((result) -> "#{result.text}!") + .run() + +``` + +#### View To View ```coffee -# view to view -# html # view1
@@ -245,76 +271,44 @@ And ot them support all strategies (how to change a content or an attribute) or view1 = new Sirius.View("#element") view2 = new Sirius.View("#my-input") -materializer = { - to: [{ - from: 'input' - selector: 'p' - with: (new_value, selector, view, attribute) -> - view.zoom(selector).render(new_value).swap(attribute) - }] -} +Sirius.Materializer.build(view2, view1) # from view2 to view1 + .field("input") # or field((view) -> view.zoom("input")) + .to('p') # the same ^ + .handle((result, view) -> # you can define own handler + view.render(result.text).swap() # default + ) + .run() -view2.bind(vew1, materializer) ``` +#### Model to View + ```coffee -# view to model -# html -
- +# model +class MyModel extends Sirius.BaseModel + @attrs: ["name"] + @validate: + name: + length: min: 3, max: 10 + +# view +
+
+
-model = new MyModel() -view = new Sirius.View("#my-input") -materializer = { - "input": { - to: 'title' - from: 'text' - with: (new_value, view, selector, from, event_target) -> - new_value - } -} -view.bind(model, materializer) +model = new MyModel() +view = new Sirius.View("#view") - -# and then fill input, and check - -model.title() # => your input +Sirius.Materializer.build(model, view) + .field((m) -> m.name) + .to('.model-name') + .field((m) -> m.errors.name.length) # path to validator + .to('.model-errors') + .run() ``` -```coffee -# model to view - -
-

-
- - model = new MyModel() # [id, title] - view = new Sirius.View("#element") - - materializer = { - "title": { - to: 'p' - attr: 'text' - via: (new_value, selector, view, attribute) -> - view.zoom(selector).render(new_value).swap(attribute) - } - } - - model.bind(view, materializer) - - # and then in application: - - model.title("new title") - - # and new html - -
-

new title

-
- -``` [more about binding](https://github.com/fntz/sirius/wiki/Binding) From 7b05c10d695725efa5e3555c1b5671b3dba593b3 Mon Sep 17 00:00:00 2001 From: mike Date: Mon, 20 Apr 2020 21:57:46 +0300 Subject: [PATCH 07/21] update logging --- src/logger.coffee | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/logger.coffee b/src/logger.coffee index 25e596e..544a43a 100644 --- a/src/logger.coffee +++ b/src/logger.coffee @@ -41,6 +41,8 @@ class Sirius.Logger log_function: null enable_logging: null + _is_initialized: false + # should be called in the initialization time # options.enable_logging # options.minimum_log_level @@ -68,6 +70,8 @@ class Sirius.Logger throw new Error("'log_to' argument must be a function") unless Sirius.Utils.is_function(options['log_to']) options['log_to'] + @_is_initialized = true + ### Check if given string is valid log level ### @@ -87,6 +91,18 @@ class Sirius.Logger maybe[0] constructor: (@log_source) -> + @_log_queue = [] + + unless Sirius.Logger.Configuration._is_initialized + _timer = setInterval( + () -> _flush_logs() + 100) + + _flush_logs = () => + if Sirius.Logger.Configuration._is_initialized + for obj in @_log_queue + @_write(obj.level, obj.message) + clearInterval(_timer) @build: (log_source) -> new Sirius.Logger(log_source) @@ -110,9 +126,14 @@ class Sirius.Logger # @private # @nodoc _write: (log_level, message) -> - if log_level.gte(Sirius.Logger.Configuration.minimum_log_level) - Sirius.Logger.Configuration - .log_function(log_level.get_value().toUpperCase(), @log_source, message) + if Sirius.Logger.Configuration._is_initialized + if log_level.gte(Sirius.Logger.Configuration.minimum_log_level) + Sirius.Logger.Configuration + .log_function(log_level.get_value().toUpperCase(), @log_source, message) + else + @_log_queue.push[{level: log_level, message: message}] + + From 80936074e75eb295b81d5af18267b6dc253191a4 Mon Sep 17 00:00:00 2001 From: mike Date: Tue, 21 Apr 2020 20:16:36 +0300 Subject: [PATCH 08/21] update todo --- Rakefile | 2 +- src/base_model.coffee | 2 +- src/sirius.coffee | 8 ---- src/view.coffee | 2 +- todomvc/js/controllers/main_controller.coffee | 12 +++-- todomvc/js/utils/renderer.coffee | 45 +++++++++---------- 6 files changed, 28 insertions(+), 43 deletions(-) diff --git a/Rakefile b/Rakefile index 5e41af7..903c1e4 100644 --- a/Rakefile +++ b/Rakefile @@ -153,7 +153,7 @@ namespace :todo do end desc "TODOApp compile" - task :compile => [:build] do + task :compile do #=> [:build] do app = 'todomvc' app_files = coffee(app, [ "js/utils/template", diff --git a/src/base_model.coffee b/src/base_model.coffee index cbdd478..bf8a4e6 100644 --- a/src/base_model.coffee +++ b/src/base_model.coffee @@ -724,7 +724,7 @@ class Sirius.BaseModel Sirius.BaseModel.register_validator("numericality", Sirius.NumericalityValidator) Sirius.BaseModel.register_validator("presence", Sirius.PresenceValidator) - +Sirius.BaseModel._run_base_model_validator_registration() diff --git a/src/sirius.coffee b/src/sirius.coffee index 4b338b5..3c5c6ff 100644 --- a/src/sirius.coffee +++ b/src/sirius.coffee @@ -638,16 +638,8 @@ Sirius.Application = _initialize: (options) -> # configure logging Sirius.Logger.Configuration.configure(options) - logger = new Sirius.Logger("Sirius.Application") - # especial for sirius-core where these modules are not available - if Sirius.BaseModel - Sirius.BaseModel._run_base_model_validator_registration() - - if Sirius.View - Sirius.View._run_view_strategy_registration() - _get_key_or_default = (k, _default) -> if options[k]? options[k] diff --git a/src/view.coffee b/src/view.coffee index 348b29a..0183c75 100644 --- a/src/view.coffee +++ b/src/view.coffee @@ -324,4 +324,4 @@ class Sirius.View ) - +Sirius.View._run_view_strategy_registration() diff --git a/todomvc/js/controllers/main_controller.coffee b/todomvc/js/controllers/main_controller.coffee index e6670e5..8f850fa 100644 --- a/todomvc/js/controllers/main_controller.coffee +++ b/todomvc/js/controllers/main_controller.coffee @@ -17,13 +17,11 @@ MainController = view = new Sirius.View(HtmlElements.todoapp) model = new Task() - transformer = { - "#{HtmlElements.new_todo}": { - to: 'title' - } - } - - view.bind(model, transformer) + Sirius.Materializer.build(view, model) + .field(HtmlElements.new_todo) + .to((m) -> m.title) + .transform((r) -> r.text) + .run() view.on(HtmlElements.new_todo, 'keypress', 'todo:create', model) diff --git a/todomvc/js/utils/renderer.coffee b/todomvc/js/utils/renderer.coffee index d3baa2e..cfab297 100644 --- a/todomvc/js/utils/renderer.coffee +++ b/todomvc/js/utils/renderer.coffee @@ -3,6 +3,7 @@ Renderer = todo_template: () -> ejs.compile(Template.todo_template) view: new Sirius.View(HtmlElements.todo_list) + clear_view: new Sirius.View(HtmlElements.clear_completed, (size) -> "Clear completed (#{size})") render: (todo_list) -> @@ -11,37 +12,31 @@ Renderer = @append(todo) append: (todo) -> - template = @todo_template()({todo: todo}) @view.render(template).append() id = "\##{todo.id()}" todo_view = new Sirius.View(id) - to_view_transformer = { - 'completed': { - to: "input[type='checkbox']", - via: (value, selector, view, attribute) -> - view.zoom(selector).render(value).swap('checked') - }, - 'title': { - to: 'label' - } - } - todo.bind(todo_view, to_view_transformer) - - to_model_transformer = { - "input[type='checkbox']": { - to: 'completed', - from: 'checked', - via: (value, view, selector, attribute) -> - view.zoom(selector).get_attr(attribute) - }, - "input.edit": { - to: 'title' - } - } - todo_view.bind(todo, to_model_transformer) + + Sirius.Materializer.build(todo, todo_view) + .field((m) -> m.completed) + .to("input[type='checkbox']") + .handle((view, result) -> + view.render(result).swap('checked') + ) + .field((m) -> m.title) + .to("label") + .run() + + Sirius.Materializer.build(todo_view, todo) + .field((v) -> v.zoom("input[type='checkbox']")) + .to((m) -> m.completed) + .transform((r) -> r.state) + .field((v) -> v.zoom("input.edit")) + .to((m) -> m.title) + .run() + todo_view.on('div', 'dblclick', (x) -> todo_view.render('editing').swap('class') From 0d95582348f333c15544dbe9935bf5136aa664d9 Mon Sep 17 00:00:00 2001 From: mike Date: Tue, 21 Apr 2020 20:50:47 +0300 Subject: [PATCH 09/21] add some doc --- src/materialization.coffee | 95 ++++++++++++++++++++++++++++++++++---- 1 file changed, 86 insertions(+), 9 deletions(-) diff --git a/src/materialization.coffee b/src/materialization.coffee index bb7b56c..2dc876e 100644 --- a/src/materialization.coffee +++ b/src/materialization.coffee @@ -46,44 +46,55 @@ ### - -# ok, it's for BaseModelToView +# @private +# @nodoc +# I use this class for save infromation about Field Mapping like: names, attributes... class Sirius.FieldMaker constructor: (@_from, @_to, @_attribute, @_transform, @_handle) -> + # @return[Boolean] has_to: () -> @_to? + # @return[Boolean] has_attribute: () -> @_attribute? + # @return[Boolean] has_transform: () -> @_transform? + # @return[Boolean] has_handle: () -> @_handle? + # @return[String|Sirius.View] - current start mapping property field: () -> @_from + # @param[String|Sirius.View] + # @return[String|Sirius.View|Void] - current end mapping property to: (x) -> if x? @_to = x else @_to + # @param x [Function] handle: (x) -> if x? @_handle = x else @_handle + # @param x [String] attribute: (x) -> if x? @_attribute = x else @_attribute + # @param x [Function] - a function for middle transform input changes transform: (x) -> if x? @_transform = x @@ -91,6 +102,7 @@ class Sirius.FieldMaker @_transform # fill with default parameters + # @return[Void] normalize: () -> if !@has_transform() @_transform = (x) -> x @@ -98,26 +110,34 @@ class Sirius.FieldMaker if !@has_attribute() @_attribute = "text" # make constant - to_string: () -> "#{@_from} ~> #{@_transform} ~> #{@_to}##{@_attribute}" + # Static constructor @build: (from) -> new Sirius.FieldMaker(from) - +# @private +# @nodoc +# Base class for describe different types of Materializers class Sirius.AbstractMaterializer + # @param _from [BaseModel, Sirius.View] + # @param _to [BaseModel, View, Function] constructor: (@_from, @_to) -> @fields = [] @current = null + # @param from_name [String, Sirius.View] field: (from_name) -> if @current? @current.normalize() @current = Sirius.FieldMaker.build(from_name) @fields.push(@current) + @ + # @nodoc + # @private _zoom_with: (view, maybeView) -> if Sirius.Utils.is_string(maybeView) view.zoom(maybeView) @@ -146,14 +166,14 @@ class Sirius.AbstractMaterializer obj[f.field()] = f obj - materialize: () -> - @fields - + # run Materializer for given fields run: () -> throw new Error("Not Implemented") # interface-like +# @nodoc +# @private class Sirius.MaterializerTransformImpl extends Sirius.AbstractMaterializer transform: (f) -> @@ -173,8 +193,36 @@ class Sirius.MaterializerTransformImpl extends Sirius.AbstractMaterializer @ - +# @private +# Provide binding between Sirius.BaseModel and Sirius.View class Sirius.ModelToViewMaterializer extends Sirius.MaterializerTransformImpl + # @param: [String|Function] + # if string - field should be attribute of Sirius.BaseModel instance + # if function - a function should returns attribute. + # function take binding object + # @example + # class MyMode extends Sirius.BaseModel + # @attrs: ["id", "name"] + # @validate: + # id: + # numericality: only_integers: true + # presence: true + # + # # then function will take the next object + # { + # id : "id", + # name : "name", + # 'errors.id.numericality' : "errors.id.numericality", + # 'errors.id.presence' : "errors.id.presence", + # 'errors.id' : "errors.id" + # 'errors.all' : "errors.all" + # } + # # and you can get these: + # + # (x) -> x.id + # (x) -> x.errors.id.numericality + # (x) -> x.errors.all + # field: (from_name) -> result = from_name if Sirius.Utils.is_function(from_name) @@ -186,6 +234,17 @@ class Sirius.ModelToViewMaterializer extends Sirius.MaterializerTransformImpl @ + # @param: [String, Function, Sirius.View] - all of therse will be transformer to Sirius.View + # if String - argument will be transformed to Sirius.View, with common Sirius.View.zoom function + # if Sirius.View - nothing to do here + # if Function - function will be called with @to + # @note Should be called after 'field' function + # @example + # all of below are the same + # to('input') + # to(Sirius.View('input')) + # to((view) -> view.zoom('input') + # to: (arg) -> unless @current? throw new Error("Incorrect call. Call 'to' after 'field'") @@ -206,6 +265,12 @@ class Sirius.ModelToViewMaterializer extends Sirius.MaterializerTransformImpl @current.to(result) @ + # @param String - attribute of View, whereto changes will be reflected. That's an usual html property, like + # class, data-attribute, or checked + # @note Should be called after `field` function, and after `to` + # @example + # .attribute('data-id') + # attribute: (attr) -> unless @current? throw new Error("Incorrect call. Define 'field' firstly, and then call 'attribute' after 'to'") @@ -219,6 +284,18 @@ class Sirius.ModelToViewMaterializer extends Sirius.MaterializerTransformImpl @current.attribute(attr) @ + # @alias attribute + to_attribute: (attr) -> + @attribute(attr) + + # @param - user defined function for handle changes from BaseModel to View + # Function will take Sirius.View (from `to`) and changes + # @default apply `swap` strategy to `to`-attribute above + # @note `field` should be called before, `to` should be called before + # @example + # + # .handle((view, changes) -> view.render(changes).append()) + # handle: (f) -> unless @current? throw new Error("Incorrect call. 'field' is not defined") @@ -235,7 +312,7 @@ class Sirius.ModelToViewMaterializer extends Sirius.MaterializerTransformImpl @current.handle(f) @ - + # call materializer run: () -> @current.normalize() From d1441fa0573a7617dfa87c2bfab9656885fc3a58 Mon Sep 17 00:00:00 2001 From: mike Date: Tue, 21 Apr 2020 22:14:19 +0300 Subject: [PATCH 10/21] add docs --- src/materialization.coffee | 155 +++++++++++++++++++++++++------------ 1 file changed, 104 insertions(+), 51 deletions(-) diff --git a/src/materialization.coffee b/src/materialization.coffee index 2dc876e..8cf2069 100644 --- a/src/materialization.coffee +++ b/src/materialization.coffee @@ -1,49 +1,6 @@ ### - - probably like: - - Materializer.build(T <: BaseModel|View, R <: BaseModel|View|Function) - .field((x) -> s.attr()) # does it possible in coffee? - # or - field('attr_name').to("input").attribute("data-attr").with(() ->) - - field('attr_name).to("input") - .dump() => log output as string, dump is a terminal operation - # or build - # does it possible? - field('attr_name').to((v) -> v.zoom('input')).attribute('data-attr').with(() -> ) - - # TODO spec syntax, add to .prototype. - field((model) -> model.from{or something like that}.attr()) - field( view -> view.zoom("el")) - - # view to view - Materializer.build(v1, v2) - .field("element").from("attribute").to("element).with((v2, v1_attribute) -> ) - .field(v -> v.zoom("element")).from("attribute").to(v2 -> v.zoom("el")) - .with(() ->) - # or - .field("element").from("attr").with((v2, attr) -> ) # user decides what should do with v2 (zoom) and attr - - # view to model - Materilizer.build(v, m) - .field("element").from("attr").to('m_attr') - .field(v -> v.zoom("el")).from("attr").to(m -> m.attr_name) - with ? (m, attr_changes) -> ??? is it need? - - # view to function - Materializer.build(v) # second param is empty - .field('element').attribute('data-class').to((changes) ->) - - # model to function - Materializer.build(m) # second param is empty - .field('attr').to(changes) -> ) - - # first iteration: - - third integration with current - - + Different type of Materializers ### # @private @@ -176,6 +133,7 @@ class Sirius.AbstractMaterializer # @private class Sirius.MaterializerTransformImpl extends Sirius.AbstractMaterializer + # @param f [Function] - function for transforming results from @from to @to transform: (f) -> unless Sirius.Utils.is_function(f) throw new Error("'transform' attribute must be function, #{typeof f} given") @@ -193,8 +151,16 @@ class Sirius.MaterializerTransformImpl extends Sirius.AbstractMaterializer @ +# @class # @private # Provide binding between Sirius.BaseModel and Sirius.View +# @example +# Sirius.Materializer.build(model, view) +# .field((x) -> x.model_attribute) +# .to((v) -> v.zoom("input") +# .transform((x) -> "#{x}!!!") +# run() +# class Sirius.ModelToViewMaterializer extends Sirius.MaterializerTransformImpl # @param: [String|Function] # if string - field should be attribute of Sirius.BaseModel instance @@ -320,7 +286,7 @@ class Sirius.ModelToViewMaterializer extends Sirius.MaterializerTransformImpl clb = (attribute, value) -> f = obj[attribute] if f? - transformed = f.transform().call(this, value, f.to()) + transformed = f.transform().call(null, value, f.to()) if f.has_handle() f.handle().call(null, f.to(), transformed) # view and changes else @@ -329,8 +295,27 @@ class Sirius.ModelToViewMaterializer extends Sirius.MaterializerTransformImpl @_from._register_state_listener(clb) - +# @class +# @private +# Provide binding between Sirius.View and Sirius.BaseModel +# @example +# Sirius.Materializer.build(view, model) +# .field((v) -> v.zoom("input")) +# .to((m) -> m.attribute) +# .transform((x) -> x.result) +# .run() class Sirius.ViewToModelMaterializer extends Sirius.MaterializerTransformImpl + + # @param element [String|Function|Sirius.View] - view where need control changes + # if String - argument will be wrapped to Sirius.View + # if Sirius.View - nothing to do + # if Function - function will be called result should be a string or Sirius.View + # @example + # + # .field('input') + # .field((v) -> v.zoom('input') + # .field(new Sirius.View('input')) + # field: (element) -> el = null if Sirius.Utils.is_string(element) @@ -345,6 +330,11 @@ class Sirius.ViewToModelMaterializer extends Sirius.MaterializerTransformImpl super.field(el) @ + # @param attribute [String] + # control changes from specific html attribute: class, data-*, checked ... + # @example + # .from('data-id') + # from: (attribute) -> unless @current? throw new Error("Incorrect call. Define 'field' firstly, and then call 'from'") @@ -358,6 +348,18 @@ class Sirius.ViewToModelMaterializer extends Sirius.MaterializerTransformImpl @current.attribute(attribute) @ + # @alias from + from_attribute: (attribute) -> + @from(attribute) + + # @param attribute [String, Function] + # if String - nothing to do + # if Function - function will be called. an argument will be model.binding object (@see Sirius.ModelToViewMaterializer) + # @note attribute should be exist in model + # @note `field` should be called before + # @example + # .field((x) -> x.attribute) + # .field('attribute') to: (attribute) -> unless @current? throw new Error("Incorrect call. Define 'field' firstly, and then call 'from'") @@ -375,6 +377,7 @@ class Sirius.ViewToModelMaterializer extends Sirius.MaterializerTransformImpl @current.to(result) @ + # run Materializer run: () -> @current.normalize() model = @_to @@ -395,8 +398,28 @@ class Sirius.ViewToModelMaterializer extends Sirius.MaterializerTransformImpl ) field.field()._register_state_listener(observer) - +# @class +# @private +# Describe View to View transformation +# @example +# +# Sirius.Materializer.build(view1, view2) +# .field((view1) -> view1.zoom('input')) +# .to((view2) -> view2.zoom('div')) +# .transform((result) -> result.text) +# .handle((transformed_result, view_to) -> view_to.render(transformed_result).append()) +# .run() +# class Sirius.ViewToViewMaterializer extends Sirius.ViewToModelMaterializer + # @param element [String|Sirius.View|Function] + # if String - element will be converted to Sirius.View + # if Sirius.View - nothing to do + # if Function - will be called with argument @to + # @example + # .to('input') + # .to((v) -> v.zoom('input')) + # .to(new Sirius.View('input')) + # to: (element) -> el = null if Sirius.Utils.is_string(element) @@ -411,6 +434,8 @@ class Sirius.ViewToViewMaterializer extends Sirius.ViewToModelMaterializer super.to(el) @ + # @param f [Function] - transformation handler + # Function will take two arguments: changes and view from `to` method handle: (f) -> unless @current? throw new Error("Incorrect call. 'field' is not defined") @@ -427,6 +452,7 @@ class Sirius.ViewToViewMaterializer extends Sirius.ViewToModelMaterializer @current.handle(f) @ + # run Materializer run: () -> @current.normalize() for field in @fields @@ -434,7 +460,7 @@ class Sirius.ViewToViewMaterializer extends Sirius.ViewToModelMaterializer clb = (result) -> transformed = field.transform(result) if field.has_handle() - field.handle().call(this, transformed, field.to()) + field.handle().call(null, transformed, field.to()) else # TODO checkbox !!!! field.to().render(transformed).swap() @@ -447,8 +473,16 @@ class Sirius.ViewToViewMaterializer extends Sirius.ViewToModelMaterializer ) field.field()._register_state_listener(observer) - +# @class +# @private +# Describe how to pass changes from View to Function +# @example +# Sirius.Materializer.build(view) +# .field((v) -> v.zoom('input')) +# .to((changes) -> changes) +# .run() class Sirius.ViewToFunctionMaterializer extends Sirius.ViewToModelMaterializer + # @param f [Function] - function for changes handling to: (f) -> unless Sirius.Utils.is_function(f) throw new Error("Function is required") @@ -456,6 +490,7 @@ class Sirius.ViewToFunctionMaterializer extends Sirius.ViewToModelMaterializer super.to(f) @ + # run Materializer run: () -> @current.normalize() # already zoomed @@ -469,8 +504,21 @@ class Sirius.ViewToFunctionMaterializer extends Sirius.ViewToModelMaterializer ) field.field()._register_state_listener(observer) - +# @class +# @private +# Describe transformation from Sirius.BaseModel to function +# @example +# Sirius.Materializer.build(model) +# .field((m) -> m.attribute) +# .to((changes) -> changes) +# .run() +# class Sirius.ModelToFunctionMaterializer extends Sirius.AbstractMaterializer + # + # @param attr [String, Function] + # if String - nothing to do + # if Function - function will be called with binding parameters @see Sirius.ModelToViewMaterializer + # @note attribute should be present in model field: (attr) -> result = attr if Sirius.Utils.is_function(attr) @@ -482,6 +530,8 @@ class Sirius.ModelToFunctionMaterializer extends Sirius.AbstractMaterializer @ + # @param f [Function] + # function should have one input parameter - actual changes from model to: (f) -> unless @current? throw new Error("Incorrect call. Define 'field' firstly") @@ -495,6 +545,7 @@ class Sirius.ModelToFunctionMaterializer extends Sirius.AbstractMaterializer @current.to(f) @ + # run Materialization process run: () -> obj = @fields_map() clb = (attribute, value) -> @@ -522,6 +573,8 @@ class Sirius.Materializer else throw new Error("Illegal arguments: 'from'/'to' must be instance of Sirius.View/or Sirius.BaseModel") + # @private + # @nodoc @_check_model_compliance: (model, maybe_model_attribute) -> name = model._klass_name() attrs = model.get_attributes() @@ -544,7 +597,7 @@ class Sirius.Materializer else return true - + # static constructor @build: (from, to) -> new Sirius.Materializer(from, to) From 7a31aca22a451e4209912cee1bd86d5ea07012b1 Mon Sep 17 00:00:00 2001 From: mike Date: Tue, 21 Apr 2020 22:14:50 +0300 Subject: [PATCH 11/21] typo --- src/materialization.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/materialization.coffee b/src/materialization.coffee index 8cf2069..07d051e 100644 --- a/src/materialization.coffee +++ b/src/materialization.coffee @@ -5,7 +5,7 @@ # @private # @nodoc -# I use this class for save infromation about Field Mapping like: names, attributes... +# I use this class for save information about Field Mapping like: names, attributes... class Sirius.FieldMaker constructor: (@_from, @_to, @_attribute, @_transform, @_handle) -> From 68c370eabbe6c1bd1104ab9f0dd7e7465d9ffdf6 Mon Sep 17 00:00:00 2001 From: mike Date: Wed, 22 Apr 2020 22:44:17 +0300 Subject: [PATCH 12/21] add all --- src/base_model.coffee | 28 +++++++++++-- src/internal.coffee | 1 + src/materialization.coffee | 42 +++++++++++++++++-- test/specs/source/base_model_spec.coffee | 25 +++++++++++ test/specs/source/binding_specs.coffee | 20 +++++++-- .../specs/source/materialization_specs.coffee | 8 +++- 6 files changed, 113 insertions(+), 11 deletions(-) diff --git a/src/base_model.coffee b/src/base_model.coffee index bf8a4e6..042b8f6 100644 --- a/src/base_model.coffee +++ b/src/base_model.coffee @@ -379,12 +379,34 @@ class Sirius.BaseModel for key, value of @_applicable_validators tmp = {} for v in Object.keys(value) - tmp[v] = "errors.#{key}.#{v}" + tmp[v] = "#{Sirius.Internal.Errors}.#{key}.#{v}" + + tmp["all"] = "errors.#{key}.all" # TODO protect & make methods are reserved errors[key] = tmp - obj['errors'] = errors + + errors["all"] = "#{Sirius.Internal.Errors}.all" + obj[Sirius.Internal.Errors] = errors @binding = obj + # Function return generated objects for convenient work with materialization (avoid `"` or `'`) + # @example + # class MyModel extends Sirius.BaseModel + # @attrs: ["id", "foo"] + # @validate: + # id: + # presence: true + # inclusion: within: [1..10] + # foo: + # format: with: /^[A-Z].+/ + # + # model = new MyModel() + # b = model.get_binding() + # b.id # => "id" + # b.foo # => "foo" + # b.errors.id.all # => "errors.id.all" represents all validations of the `id` + # b.errors.id.presence # => "errors.id.presence" + # b.errors.all # => "erros.all" all validations of all attributes get_binding: () -> @binding @@ -418,7 +440,7 @@ class Sirius.BaseModel # @_call_callbacks_for_errors(key, validator_key, "") _call_callbacks_for_errors: (key, validator_key, message) -> - key = "errors.#{key}.#{validator_key}" + key = "#{Sirius.Internal.Errors}.#{key}.#{validator_key}" for clb in @_listeners clb.apply(null, [key, message]) # diff --git a/src/internal.coffee b/src/internal.coffee index 76913fb..296ad41 100644 --- a/src/internal.coffee +++ b/src/internal.coffee @@ -1,4 +1,5 @@ # all non-public api Sirius.Internal = { DefaultProperty: "text" + Errors: "errors" } \ No newline at end of file diff --git a/src/materialization.coffee b/src/materialization.coffee index 07d051e..cd019ef 100644 --- a/src/materialization.coffee +++ b/src/materialization.coffee @@ -284,8 +284,8 @@ class Sirius.ModelToViewMaterializer extends Sirius.MaterializerTransformImpl obj = @fields_map() clb = (attribute, value) -> - f = obj[attribute] - if f? + callers = Sirius.Materializer.get_necessary_functions(obj, attribute) + for f in callers transformed = f.transform().call(null, value, f.to()) if f.has_handle() f.handle().call(null, f.to(), transformed) # view and changes @@ -293,6 +293,7 @@ class Sirius.ModelToViewMaterializer extends Sirius.MaterializerTransformImpl # default, just a swap f.to().render(transformed).swap(f.attribute()) + @_from._register_state_listener(clb) # @class @@ -547,16 +548,20 @@ class Sirius.ModelToFunctionMaterializer extends Sirius.AbstractMaterializer # run Materialization process run: () -> + errors_all = "#{Sirius.Internal.Errors}.all" obj = @fields_map() clb = (attribute, value) -> - if obj[attribute]? - obj[attribute].to().call(null, value) + callers = Sirius.Materializer.get_necessary_functions(obj, attribute) + for f in callers + f.to().call(null, value) @_from._register_state_listener(clb) class Sirius.Materializer + @errors_all: "#{Sirius.Internal.Errors}.all" + # from must be View or BaseModel # to is View, BaseModel, or Function constructor: (from, to) -> @@ -582,21 +587,50 @@ class Sirius.Materializer if attrs.indexOf(maybe_model_attribute) != -1 return true else + if maybe_model_attribute == "#{Sirius.Internal.Errors}.all" # pass all errors + return true + if maybe_model_attribute.indexOf(".") == -1 throw new Error("Attribute '#{maybe_model_attribute}' not found in model attributes: '#{name}', available: '[#{attrs}]'") # check for validators splitted = maybe_model_attribute.split(".") + if splitted.length != 3 throw new Error("Try to bind '#{maybe_model_attribute}' from errors properties, but validator is not found, correct definition should be as 'errors.id.numericality'") [_, attr, validator_key] = splitted + if validator_key == "all" + return true + unless model._is_valid_validator("#{attr}.#{validator_key}") throw new Error("Unexpected '#{maybe_model_attribute}' errors attribute for '#{name}' (check validators)") else return true + # @private + # @nodoc + # @returns [Function] + @get_necessary_functions: (obj, attribute) -> + results = [] + if attribute.startsWith(Sirius.Internal.Errors) + + # check errors.all + if obj[Sirius.Materializer.errors_all]? + results.push(obj[Sirius.Materializer.errors_all]) + + # check errors.`attr`.all + [_, attr, validator_key] = attribute.split(".") # errors.id.presence + error_attribute_all = "#{Sirius.Internal.Errors}.#{attr}.all" + if obj[error_attribute_all]? + results.push(obj[error_attribute_all]) + + # any key + if obj[attribute]? + results.push(obj[attribute]) + results + # static constructor @build: (from, to) -> new Sirius.Materializer(from, to) diff --git a/test/specs/source/base_model_spec.coffee b/test/specs/source/base_model_spec.coffee index 701989d..de860c1 100644 --- a/test/specs/source/base_model_spec.coffee +++ b/test/specs/source/base_model_spec.coffee @@ -185,6 +185,31 @@ describe "BaseModel", -> expect(t.id()).toEqual(t.get('id')) expect(after_update).toEqual(["id", 1, null, "id", 10, 1]) + describe "get_binding", -> + it "returns binding for attributes, and for validators", -> + class Test1 extends Sirius.BaseModel + @attrs: ["id", "foo"] + @validate: + id: + presence: true + inclusion: within: [1..10] + foo: + format: with: /^[A-Z].+/ + b = new Test1().get_binding() + expect(b.id).toEqual("id") + expect(b.foo).toEqual("foo") + expect(b.errors.id.presence).toEqual("errors.id.presence") + expect(b.errors.id.inclusion).toEqual("errors.id.inclusion") + expect(b.errors.foo.format).toEqual("errors.foo.format") + expect(b.errors.foo.all).toEqual("errors.foo.all") + expect(b.errors.id.all).toEqual("errors.id.all") + expect(b.errors.all).toEqual("errors.all") + expect(Object.keys(b)).toEqual(["id", "foo", "errors"]) + expect(Object.keys(b.errors)).toEqual(["id", "foo", "all"]) + expect(Object.keys(b.errors.foo)).toEqual(["format", "all"]) + console.log(b) + + describe "reset", -> class Test1 extends Sirius.BaseModel diff --git a/test/specs/source/binding_specs.coffee b/test/specs/source/binding_specs.coffee index 43785ab..9b21e8b 100644 --- a/test/specs/source/binding_specs.coffee +++ b/test/specs/source/binding_specs.coffee @@ -5,12 +5,20 @@ describe "Binding", -> @validate: id: numericality: only_integers: true + name: + exclusion: within: ["test"] it "produce changes", -> model = new Test1() results = [] + all_errors = [] + id_errors = [] materializer = Sirius.Materializer.build(model) materializer + .field((x) -> x.errors.all) + .to((err) -> all_errors.push(err)) + .field((x) -> x.errors.id.all) + .to((err) -> id_errors.push(err) ) .field((x) -> x.id) .to((id) -> results.push(id)) .field((x) -> x.name) @@ -22,12 +30,18 @@ describe "Binding", -> expect(results.length).toEqual(0) model.name("test") - expect(results).toEqual(["test"]) + expect(results).toEqual([]) + expect(all_errors).toEqual(["Value test reserved"]) + expect(id_errors).toEqual([]) model.id("asd") - expect(results).toEqual(["test", "Only allows integer numbers"]) + expect(all_errors).toEqual(["Value test reserved", "Only allows integer numbers"]) + expect(id_errors).toEqual(["Only allows integer numbers"]) + expect(results).toEqual(["Only allows integer numbers"]) model.id(123) # '' - reset validation - expect(results).toEqual(["test", "Only allows integer numbers", '', 123]) + expect(results).toEqual(["Only allows integer numbers", "", 123]) + expect(id_errors).toEqual(["Only allows integer numbers", ""]) + expect(all_errors).toEqual(["Value test reserved", "Only allows integer numbers", ""]) describe "View To Function", -> rootElement = "#view2function" diff --git a/test/specs/source/materialization_specs.coffee b/test/specs/source/materialization_specs.coffee index 60647da..cdf44d0 100644 --- a/test/specs/source/materialization_specs.coffee +++ b/test/specs/source/materialization_specs.coffee @@ -1,7 +1,7 @@ describe "Materialization", -> class Test1 extends Sirius.BaseModel @attrs: ['id'] - @validate : + @validate: id: presence: true @@ -22,6 +22,12 @@ describe "Materialization", -> expect(() -> Sirius.Materializer._check_model_compliance(m, "id") ).not.toThrowError() + expect(() -> + Sirius.Materializer._check_model_compliance(m, "errors.id.all") + ).not.toThrowError() + expect(() -> + Sirius.Materializer._check_model_compliance(m, "errors.all") + ).not.toThrowError() expect(() -> Sirius.Materializer._check_model_compliance(m, "foo") ).toThrowError(/Attribute 'foo' not found in model/) From a35d0fd01b0220542d2c88c7c9c8c1a4ff49154d Mon Sep 17 00:00:00 2001 From: mike Date: Wed, 22 Apr 2020 22:56:14 +0300 Subject: [PATCH 13/21] protect names --- src/base_model.coffee | 10 ++++++++-- test/specs/source/base_model_spec.coffee | 13 ++++++++++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/base_model.coffee b/src/base_model.coffee index 042b8f6..8f5b286 100644 --- a/src/base_model.coffee +++ b/src/base_model.coffee @@ -371,17 +371,23 @@ class Sirius.BaseModel # @_applicable_validators # {id: presence : P, numbericalluy: N ...} obj = {} for attribute in @get_attributes() + # will be throw before that code will be reachable + if attribute is Sirius.Internal.Errors + throw new Error("'errors' name is reserved") + obj[attribute] = attribute - # TODO check model should not contains 'error' attribute + if Object.keys(@_applicable_validators).length != 0 errors = {} # id: presence, num, custom for key, value of @_applicable_validators tmp = {} for v in Object.keys(value) + if v is "all" + throw new Error("Name 'all' for validators is reserved") tmp[v] = "#{Sirius.Internal.Errors}.#{key}.#{v}" - tmp["all"] = "errors.#{key}.all" # TODO protect & make methods are reserved + tmp["all"] = "#{Sirius.Internal.Errors}.#{key}.all" errors[key] = tmp errors["all"] = "#{Sirius.Internal.Errors}.all" diff --git a/test/specs/source/base_model_spec.coffee b/test/specs/source/base_model_spec.coffee index de860c1..bc5cf6d 100644 --- a/test/specs/source/base_model_spec.coffee +++ b/test/specs/source/base_model_spec.coffee @@ -207,7 +207,18 @@ describe "BaseModel", -> expect(Object.keys(b)).toEqual(["id", "foo", "errors"]) expect(Object.keys(b.errors)).toEqual(["id", "foo", "all"]) expect(Object.keys(b.errors.foo)).toEqual(["format", "all"]) - console.log(b) + + it "generate exceptions on reserved names", -> + Sirius.BaseModel.register_validator("all", MyCustomValidator) + + expect(() -> + class Test2 extends Sirius.BaseModel + @attrs: ["id"] + @validate: + id: + all: true + new Test2() + ).toThrowError("Name 'all' for validators is reserved") describe "reset", -> From 780bc308625a95b3eeb8ad2ab49afec7ce543b59 Mon Sep 17 00:00:00 2001 From: mike Date: Thu, 23 Apr 2020 19:07:08 +0300 Subject: [PATCH 14/21] update --- Rakefile | 2 +- src/adapter.coffee | 15 +++++ src/materialization.coffee | 56 +++++++++++------- src/observer.coffee | 59 +++++++++++++++---- src/sirius.coffee | 4 +- todomvc/js/app.coffee | 4 +- ...ffee => additional_info_controller.coffee} | 2 +- todomvc/js/controllers/todo_controller.coffee | 1 - todomvc/js/utils/renderer.coffee | 5 +- 9 files changed, 105 insertions(+), 43 deletions(-) rename todomvc/js/controllers/{bottom_controller.coffee => additional_info_controller.coffee} (95%) diff --git a/Rakefile b/Rakefile index 903c1e4..2c59569 100644 --- a/Rakefile +++ b/Rakefile @@ -164,7 +164,7 @@ namespace :todo do "js/utils/renderer", "js/controllers/main_controller", "js/controllers/todo_controller", - "js/controllers/bottom_controller", + "js/controllers/additional_info_controller", "js/controllers/link_controller", "js/app" ]) diff --git a/src/adapter.coffee b/src/adapter.coffee index ad4f9f1..a4afe77 100644 --- a/src/adapter.coffee +++ b/src/adapter.coffee @@ -77,3 +77,18 @@ class Adapter # first from selector get: (selector) -> document.querySelector(selector) + + as_string: (doc) -> + if doc is document + return "document" + else if Object.prototype.toString.call(doc) is '[object String]' + return doc + else + try + if Array.isArray(doc) && doc.size > 1 + klasses = doc.map (x) -> "#{x.tagName}.#{x.className}" + klasses.slice(0, 3).join(", ") + else + "#{doc.tagName}.#{id}" + catch + doc diff --git a/src/materialization.coffee b/src/materialization.coffee index cd019ef..973aa4f 100644 --- a/src/materialization.coffee +++ b/src/materialization.coffee @@ -68,7 +68,19 @@ class Sirius.FieldMaker @_attribute = "text" # make constant to_string: () -> - "#{@_from} ~> #{@_transform} ~> #{@_to}##{@_attribute}" + from = if @_from instanceof Sirius.View + @_from.get_element() + else + @_from._klass_name() + + to = if @_to instanceof Sirius.View + @_to.get_element() + else if @_to instanceof Sirius.BaseModel + @_to._klass_name() + else + @_to + + "#{from} ~> #{@_transform} ~> #{to}##{@_attribute}" # Static constructor @build: (from) -> @@ -133,7 +145,8 @@ class Sirius.AbstractMaterializer # @private class Sirius.MaterializerTransformImpl extends Sirius.AbstractMaterializer - # @param f [Function] - function for transforming results from @from to @to + # @param f [Function] - a function for transforming results from @from to @to + # the function should take parameter as Sirius.View and Sirius.AbstractChangesResult transform: (f) -> unless Sirius.Utils.is_function(f) throw new Error("'transform' attribute must be function, #{typeof f} given") @@ -255,7 +268,8 @@ class Sirius.ModelToViewMaterializer extends Sirius.MaterializerTransformImpl @attribute(attr) # @param - user defined function for handle changes from BaseModel to View - # Function will take Sirius.View (from `to`) and changes + # the function will take Sirius.View (from `to`) and changes + # changes are T <: Sirius.AbstractChangesResult # @default apply `swap` strategy to `to`-attribute above # @note `field` should be called before, `to` should be called before # @example @@ -382,18 +396,21 @@ class Sirius.ViewToModelMaterializer extends Sirius.MaterializerTransformImpl run: () -> @current.normalize() model = @_to - for field in @fields - element = field.field().get_element() - clb = (result) -> - transformed = field.transform().call(null, result) + + generator = (field) -> + clb = (changes) -> + transformed = field.transform().call(null, changes) if field.to().indexOf(".") != -1 # validator model.set_error(field.to(), transformed) else model.set(field.to(), transformed) + return clb + + for field in @fields + clb = generator(field) observer = new Sirius.Internal.Observer( - element, - element, + field.field().get_element(), field.attribute(), clb ) @@ -436,7 +453,7 @@ class Sirius.ViewToViewMaterializer extends Sirius.ViewToModelMaterializer @ # @param f [Function] - transformation handler - # Function will take two arguments: changes and view from `to` method + # Function will take two arguments: View (from `to`) and changes as Sirius.AbstractChangesResult handle: (f) -> unless @current? throw new Error("Incorrect call. 'field' is not defined") @@ -456,21 +473,21 @@ class Sirius.ViewToViewMaterializer extends Sirius.ViewToModelMaterializer # run Materializer run: () -> @current.normalize() - for field in @fields - element = field.field().get_element() + + generator = (field) -> clb = (result) -> transformed = field.transform(result) if field.has_handle() field.handle().call(null, transformed, field.to()) else - # TODO checkbox !!!! field.to().render(transformed).swap() + clb + for field in @fields observer = new Sirius.Internal.Observer( - element, - element, + field.field().get_element(), field.attribute(), - clb + generator(field) ) field.field()._register_state_listener(observer) @@ -483,7 +500,8 @@ class Sirius.ViewToViewMaterializer extends Sirius.ViewToModelMaterializer # .to((changes) -> changes) # .run() class Sirius.ViewToFunctionMaterializer extends Sirius.ViewToModelMaterializer - # @param f [Function] - function for changes handling + # @param f [Function] - a function for changes handling + # the function should take T <: @Sirius.AbstractChangesResult to: (f) -> unless Sirius.Utils.is_function(f) throw new Error("Function is required") @@ -496,10 +514,8 @@ class Sirius.ViewToFunctionMaterializer extends Sirius.ViewToModelMaterializer @current.normalize() # already zoomed for field in @fields - element = field.field().get_element() observer = new Sirius.Internal.Observer( - element, - element, + field.field().get_element(), field.attribute(), field.to() ) diff --git a/src/observer.coffee b/src/observer.coffee index a708790..e3af827 100644 --- a/src/observer.coffee +++ b/src/observer.coffee @@ -27,7 +27,40 @@ Sirius.Internal.CacheObserverHandlers = # hacks for observer when property or text changed into DOM +# @class +# describe changes from events +class Sirius.AbstractChangesResult + constructor: () -> + + # @private + @build: (object) -> + from = object.from + target = object.element + if object['state']? + return new Sirius.StateChanges(object.state, from, target) + if object['attribute']? + return new Sirius.AttributeChanges(object.text, object.attribute, from, target) + return new Sirius.TextChanges(object.text, from, target) + + +# @class +# changes from input/textarea +class Sirius.TextChanges extends Sirius.AbstractChangesResult + constructor: (@text, @from, @target) -> + super() + +class Sirius.AttributeChanges extends Sirius.AbstractChangesResult + constructor: (@text, @attribute, @from, @target) -> + super() +# @class +# changes from selector or input checkbox/radio elements +class Sirius.StateChanges extends Sirius.AbstractChangesResult + constructor: (@state, @from, @target) -> + super() + +# @class # @private +# events observer, it produces changes for materializing class Sirius.Internal.Observer MO = window.MutationObserver || @@ -69,7 +102,7 @@ class Sirius.Internal.Observer # new Sirius.Internal.Observer("#id input[name='email']", "input[name='email']", "text") # # - constructor: (@from_element, @original, @watch_for, @clb = ->) -> + constructor: (@from_element, @watch_for, @clb = ->) -> adapter = Sirius.Application.get_adapter() adapter.and_then(@_create) @@ -79,7 +112,6 @@ class Sirius.Internal.Observer logger = Sirius.Application.get_logger(@constructor.name) clb = @clb from = @from_element - original = @original current_value = null watch_for = @watch_for @@ -93,37 +125,38 @@ class Sirius.Internal.Observer # base callback handler = (e) -> logger.debug("Handler Function: given #{e.type} event") - result = {text: null, attribute: null, from: from, original: original, element: e.target} + result = {text: null, attribute: null, from: from, element: e.target} return if O.is_focus_event(e) txt = adapter.text(from) - return if [O.Ev.input, O.Ev.selectionchange].indexOf(e.type) != -1 && txt == current_value + if [O.Ev.input, O.Ev.selectionchange].indexOf(e.type) != -1 && txt == current_value + return # no changes here if O.is_text_event(e) - result['text'] = txt + result.text = txt current_value = txt if e.type == O.Ev.change # get a state for input enable or disable - result['state'] = adapter.get_attr(from, 'checked') + result.state = adapter.get_attr(from, 'checked') if e.type == "attributes" attr_name = e.attributeName old_attr = e.oldValue || [] # FIXME remove this, because not used new_attr = adapter.get_attr(from, attr_name) - result['text'] = new_attr - result['attribute'] = attr_name - result['previous'] = old_attr + result.text = new_attr + result.attribute = attr_name + result.previous = old_attr if e.type == O.Ev.DOMAttrModified # for ie 9... attr_name = e.originalEvent.attrName old_attr = e.originalEvent.prevValue new_attr = adapter.get_attr(from, attr_name) - result['text'] = new_attr - result['attribute'] = attr_name - result['previous'] = old_attr + result.text = new_attr + result.attribute = attr_name + result.previous = old_attr - clb(result) + clb(Sirius.AbstractChangesResult.build(result)) # how to handle diff --git a/src/sirius.coffee b/src/sirius.coffee index 3c5c6ff..dbde377 100644 --- a/src/sirius.coffee +++ b/src/sirius.coffee @@ -361,7 +361,7 @@ Sirius.Internal.RouteSystem = event_name = z[1] selector = z[3] || document #when it a custom event: 'custom:event' for example adapter.bind(document, selector, event_name, handler) - logger.debug("RouteSystem: define event route: '#{event_name}' for '#{selector}'", logger.routing) + logger.debug("RouteSystem: define event route: '#{event_name}' for '#{adapter.as_string(selector)}'", logger.routing) _get_hash_routes: (routes, wrapper, adapter, logger) -> for url, action of routes when @_is_hash_route(url) @@ -663,7 +663,7 @@ Sirius.Application = logger.info("Logger enabled? #{Sirius.Logger.Configuration.enable_logging}") logger.info("Minimum log level: #{Sirius.Logger.Configuration.minimum_log_level.get_value()}") - logger.info("Adapter: #{@adapter.name}") + logger.info("Adapter: #{@adapter.constructor.name}") logger.info("Use hash routing for old browsers: #{@use_hash_routing_for_old_browsers}") logger.info("Current browser: #{navigator.userAgent}") logger.info("Ignore not matched urls: #{@ignore_not_matched_urls}") diff --git a/todomvc/js/app.coffee b/todomvc/js/app.coffee index 1c252ff..d904d65 100644 --- a/todomvc/js/app.coffee +++ b/todomvc/js/app.coffee @@ -17,9 +17,9 @@ routes = 'todo:create' : {controller: TodoController, action: 'create', guard: 'is_enter', after: 'clear_input'} 'application:urlchange': {controller: LinkController, action: 'url'} 'click #toggle-all' : {controller: TodoController, action: 'mark_all', data: 'class'} - 'collection:change' : {controller: BottomController, action: 'change'} + 'collection:change' : {controller: AdditionalInfoController, action: 'change'} 'click button.destroy' : {controller: TodoController, action: 'destroy', data: 'data-id'} - 'click #clear-completed': {controller: BottomController, action: 'clear'} + 'click #clear-completed': {controller: AdditionalInfoController, action: 'clear'} # ----------------------- Start -------------------- # diff --git a/todomvc/js/controllers/bottom_controller.coffee b/todomvc/js/controllers/additional_info_controller.coffee similarity index 95% rename from todomvc/js/controllers/bottom_controller.coffee rename to todomvc/js/controllers/additional_info_controller.coffee index dd2e6b6..f468b22 100644 --- a/todomvc/js/controllers/bottom_controller.coffee +++ b/todomvc/js/controllers/additional_info_controller.coffee @@ -1,5 +1,5 @@ -BottomController = +AdditionalInfoController = length_view: new Sirius.View(HtmlElements.todo_count) footer: new Sirius.View(HtmlElements.footer) diff --git a/todomvc/js/controllers/todo_controller.coffee b/todomvc/js/controllers/todo_controller.coffee index 084d776..3017604 100644 --- a/todomvc/js/controllers/todo_controller.coffee +++ b/todomvc/js/controllers/todo_controller.coffee @@ -20,7 +20,6 @@ TodoController = $(HtmlElements.toggle_all).toggleClass('completed') - destroy: (e, id) -> todo = TodoList.filter((t) -> t.id() == id)[0] TodoList.remove(todo) diff --git a/todomvc/js/utils/renderer.coffee b/todomvc/js/utils/renderer.coffee index cfab297..f8fbe79 100644 --- a/todomvc/js/utils/renderer.coffee +++ b/todomvc/js/utils/renderer.coffee @@ -33,10 +33,9 @@ Renderer = .field((v) -> v.zoom("input[type='checkbox']")) .to((m) -> m.completed) .transform((r) -> r.state) - .field((v) -> v.zoom("input.edit")) + .field((v) -> v.zoom("input.edit")) .to((m) -> m.title) - .run() - + .transform((r) -> r.text).run() todo_view.on('div', 'dblclick', (x) -> todo_view.render('editing').swap('class') From 0e409bbf26a36d7a3d710437fddee0e7c6b17df6 Mon Sep 17 00:00:00 2001 From: mike Date: Sun, 26 Apr 2020 10:23:36 +0300 Subject: [PATCH 15/21] rename properties --- src/adapter.coffee | 2 +- src/jquery_adapter.coffee | 2 +- src/prototype_js_adapter.coffee | 2 +- src/sirius.coffee | 2 +- src/vanilla_js_adapter.coffee | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/adapter.coffee b/src/adapter.coffee index a4afe77..875faec 100644 --- a/src/adapter.coffee +++ b/src/adapter.coffee @@ -37,7 +37,7 @@ class Adapter # @param [Array] properties - names of attributes # @return [Array] # - get_property: (event, properties...) -> + get_properties: (event, properties...) -> get_attr: (element, attr) -> diff --git a/src/jquery_adapter.coffee b/src/jquery_adapter.coffee index a9c1c22..b97cab5 100644 --- a/src/jquery_adapter.coffee +++ b/src/jquery_adapter.coffee @@ -19,7 +19,7 @@ class JQueryAdapter extends Adapter jQuery(element).trigger(event, params) return - get_property: (event, properties) -> + get_properties: (event, properties) -> for p in properties then jQuery(event.target).attr(p) get_attr: (element, attr) -> diff --git a/src/prototype_js_adapter.coffee b/src/prototype_js_adapter.coffee index e509d33..f48aeba 100644 --- a/src/prototype_js_adapter.coffee +++ b/src/prototype_js_adapter.coffee @@ -42,7 +42,7 @@ class PrototypeAdapter extends Adapter $(element).fire(event, params) return - get_property: (event, properties...) -> + get_properties: (event, properties...) -> element = Event.element(event) self = @ properties.flatten().inject([], (acc, p) -> diff --git a/src/sirius.coffee b/src/sirius.coffee index dbde377..5fd663f 100644 --- a/src/sirius.coffee +++ b/src/sirius.coffee @@ -200,7 +200,7 @@ class Sirius.Internal.ControlFlow @logger.debug("ControlFlow: Start event processing", @logger.routing) if e data = if Sirius.Utils.is_array(@data) then @data else if @data then [@data] else [] - result = Sirius.Application.adapter.get_property(e, data) #FIXME use Promise + result = Sirius.Application.adapter.get_properties(e, data) #FIXME use Promise merge = [].concat([], [e], result) # fix bug#4 when event is a custom event we should get an args for this event diff --git a/src/vanilla_js_adapter.coffee b/src/vanilla_js_adapter.coffee index c9cb154..d097dff 100644 --- a/src/vanilla_js_adapter.coffee +++ b/src/vanilla_js_adapter.coffee @@ -39,7 +39,7 @@ class VanillaJsAdapter extends Adapter document.dispatchEvent(ev) return - get_property: (event, properties) -> + get_properties: (event, properties) -> e = event.target for p in properties @get_attr(e, p) From a0f31707fae67d1160a561eca926b6dd7fdfbcda Mon Sep 17 00:00:00 2001 From: mike Date: Sun, 26 Apr 2020 10:47:40 +0300 Subject: [PATCH 16/21] r msetup --- src/sirius.coffee | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/sirius.coffee b/src/sirius.coffee index 5fd663f..1626421 100644 --- a/src/sirius.coffee +++ b/src/sirius.coffee @@ -386,8 +386,8 @@ Sirius.Internal.RouteSystem = # @param routes [Object] object with routes # @param fn [Function] callback, which will be called, after routes will be defined # @event application:urlchange - generate, when url change - # @event application:404 - generate, if given url not matched defined routes - # @event application:run - generate, after application running + # @event application:404 - generate, if given url does not match in the defined routes + # @event application:run - generate, after application run # setting : old, top, support create: (routes, setting, fn = ->) -> logger = Sirius.Application.get_logger(@constructor.name) @@ -574,10 +574,7 @@ Sirius.Application = application adapter for javascript frameworks @see Adapter documentation ### adapter: null - ### - true, when application already running - ### - running: false + ### user routes ### @@ -646,7 +643,6 @@ Sirius.Application = else _default - @running = true @adapter = options["adapter"] || new VanillaJsAdapter() @route = options["route"] || @route @ignore_not_matched_urls = _get_key_or_default('ignore_not_matched_urls', @ignore_not_matched_urls) From f3b0ecf8bec97f994f42a675732d5697eb23d280 Mon Sep 17 00:00:00 2001 From: mike Date: Mon, 27 Apr 2020 11:47:19 +0300 Subject: [PATCH 17/21] update docs --- package.json | 1 - src/adapter.coffee | 134 +++++++++++++++++++++++++++------------------ src/logger.coffee | 11 +++- 3 files changed, 90 insertions(+), 56 deletions(-) diff --git a/package.json b/package.json index 13f0f2a..2362cdb 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,6 @@ "prototypejs_adapter.min.js" ], "devDependencies": { - "codo": "2.0.9", "coffeescript": "2.2.1" }, "scripts": { diff --git a/src/adapter.coffee b/src/adapter.coffee index 875faec..7229351 100644 --- a/src/adapter.coffee +++ b/src/adapter.coffee @@ -1,83 +1,113 @@ -# -# Adapter -# It's a base class, which must be redefine for concrete javascript library: prototype.js or jQuery or mootools or etc. -# @abstract -# +### + Adapter + It's a base class, which must be redefined for concrete javascript library: prototype.js or jQuery or mootools or etc. + @abstract +### class Adapter - - # Attach event to an element - # @param [String] main the element for bind - # @param [String] selector - selector query string - # @param [String] event - event name - # @param [Function] fn - callback, will be called, when event fired - # @return [Void] - # + ### + Attach event to an element + @param {string} main the element for bind + @param {string} selector - selector query string + @param {string} event - event name + @param {function} fn - callback, will be called, when event fired + @returns {void} + ### bind: (element, selector, event, fn) -> - # Remove event listener for an element - # @param [String] main the element for bind - # @param [String] selector - selector query string - # @param [String] event - event name - # @param [Function] fn - callback, will be called, when event fired - # @return [Void] - # + ### + Remove event listener for an element + @param {string} main the element for bind + @param {string} selector - selector query string + @param {string} event - event name + @param {function} fn - callback, will be called, when event fired + @returns {void} + ### off: (element, selector, event, fn) -> - - # Call an custom event with params - # @param [String] element - selector for the event - # @param [String] event - event name - # @param [Array] params - params which will be passed into event callback - # @return [Void] - # + ### + Call an custom event with params + @param {string} element - selector for the event + @param {string} event - event name + @param {array} params - params which will be passed into event callback + @returns {void} + ### fire: (element, event, params...) -> - # Extract attribute from a target element from an event - # @param [EventObject] event - the event object - # @param [Array] properties - names of attributes - # @return [Array] - # + ### + Fetch properties from a target element from an event + @param {EventObject} event - the event object + @param {array} properties - names of attributes + @returns {array} + ### get_properties: (event, properties...) -> + ### + Fetch an attribute from an element + @param {HtmlElement} - document + @param {string} - an attribute name + @returns {null|boolean|string} - result (depends on attribute meaning) + ### get_attr: (element, attr) -> - - # Change content into element - # @param [String] element - selector - # @content [String] content - new content + ### + Change a content into an element + @param {string} element - a selector + @param {string} content - a new content + @returns {void} + ### swap: (element, content) -> - # Add in bottom - # @param [String] element - selector - # @content [String] content - new content + ### + Add into bottom + @param {string} element - a selector + @param {string} content - a new content + @returns {void} + ### append: (element, content) -> - # Add in top - # @param [String] element - selector - # @content [String] content - new content + ### + Add into top + @param {string} element - a selector + @param {string} content - a new content + @returns {void} + ### prepend: (element, content) -> - # Remove content from element - # @param [String] element - selector + ### + Remove a content from an element + @param {string} element - a selector + @returns {void} + ### clear: (element) -> - # set new attribute for element - # @param [String] - selector - # @param [String] - attribute - # @param [String] - new value for attribute + ### + set new attribute for element + @param {string} - a selector + @param {string} - an attribute + @param {string} - a new value for the attribute + @returns {void} + ### set_attr: (element, attr, value) -> - - # return all selectors + ### + @param {string} - a selector + @returns {array} + ### all: (selector) -> return selector if (typeof(selector) == "object" && selector.nodeType == 1 || selector.nodeType == 9) document.querySelectorAll(selector) - - # first from selector + ### + find the first document by a selector + @returns {HtmlDocument|null} + ### get: (selector) -> document.querySelector(selector) + ### + pretty print HtmlDocument + @returns {string} + ### as_string: (doc) -> if doc is document return "document" diff --git a/src/logger.coffee b/src/logger.coffee index 544a43a..58d2b0f 100644 --- a/src/logger.coffee +++ b/src/logger.coffee @@ -1,6 +1,7 @@ - -# @class -# Base logger class for use in Sirius Application +### +@class +Base logger class for use in Sirius Application +### class Sirius.Logger # should be passed as parameter @@ -90,6 +91,10 @@ class Sirius.Logger else maybe[0] + ### + @constructor + @param {string} - a logger source name + ### constructor: (@log_source) -> @_log_queue = [] From 6f492486b2c4cbcfb79f2a55e3c2170e1027ebef Mon Sep 17 00:00:00 2001 From: mike Date: Mon, 27 Apr 2020 11:48:04 +0300 Subject: [PATCH 18/21] update --- package.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/package.json b/package.json index 2362cdb..4a5227a 100644 --- a/package.json +++ b/package.json @@ -25,9 +25,6 @@ "devDependencies": { "coffeescript": "2.2.1" }, - "scripts": { - "doc": "cake doc" - }, "main": "sirius.min.js", "version": "1.2.0", "license": "MIT", From 708cd85257d4b95b8e4f9fb89fabeb99ec785f16 Mon Sep 17 00:00:00 2001 From: mike Date: Mon, 27 Apr 2020 13:14:42 +0300 Subject: [PATCH 19/21] upd logs --- .gitignore | 3 ++- src/base_model.coffee | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 109f603..4cb958b 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ app.rb todomvc/node_modules/* todomvc/js/app.js node_modules/* -package-lock.json \ No newline at end of file +package-lock.json +test.* \ No newline at end of file diff --git a/src/base_model.coffee b/src/base_model.coffee index 8f5b286..429f4e9 100644 --- a/src/base_model.coffee +++ b/src/base_model.coffee @@ -475,7 +475,7 @@ class Sirius.BaseModel flag = force || @is_valid(attr) if flag - @logger.debug("[#{@constructor.name}] set: '#{attr}' to '#{value}'") + @logger.debug("[#{@constructor.name}] set: '#{value}' to '#{attr}'") @_compute(attr, value) @_call_callbacks(attr, value, oldvalue) else From 4108b9a35dc04f9be878af924562f48a53fd8b8e Mon Sep 17 00:00:00 2001 From: mike Date: Mon, 27 Apr 2020 14:58:05 +0300 Subject: [PATCH 20/21] update --- Readme.md | 2 +- src/materialization.coffee | 4 ++-- test/specs/source/binding_specs.coffee | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Readme.md b/Readme.md index 9b99245..2f1be9e 100644 --- a/Readme.md +++ b/Readme.md @@ -274,7 +274,7 @@ view2 = new Sirius.View("#my-input") Sirius.Materializer.build(view2, view1) # from view2 to view1 .field("input") # or field((view) -> view.zoom("input")) .to('p') # the same ^ - .handle((result, view) -> # you can define own handler + .handle((view, result) -> # you can define own handler view.render(result.text).swap() # default ) .run() diff --git a/src/materialization.coffee b/src/materialization.coffee index 973aa4f..4e4c18f 100644 --- a/src/materialization.coffee +++ b/src/materialization.coffee @@ -476,9 +476,9 @@ class Sirius.ViewToViewMaterializer extends Sirius.ViewToModelMaterializer generator = (field) -> clb = (result) -> - transformed = field.transform(result) + transformed = field.transform().call(null, result) if field.has_handle() - field.handle().call(null, transformed, field.to()) + field.handle().call(null, field.to(), transformed) else field.to().render(transformed).swap() clb diff --git a/test/specs/source/binding_specs.coffee b/test/specs/source/binding_specs.coffee index 9b21e8b..21ee6d6 100644 --- a/test/specs/source/binding_specs.coffee +++ b/test/specs/source/binding_specs.coffee @@ -122,7 +122,7 @@ describe "Binding", -> materializer .field(source) .to(mirror) - .handle((result, v) -> + .handle((v, result) -> v.zoom(".mirror1").render(result.text).swap() v.zoom(".mirror-attr1").render(result.text).swap('data-mirror') ).run() From fd07a4d3183c95445c77bbdcfceff9ec16451181 Mon Sep 17 00:00:00 2001 From: mike Date: Mon, 27 Apr 2020 15:07:57 +0300 Subject: [PATCH 21/21] update --- Changelog.md | 21 +++++ jquery_adapter.min.js | 15 +-- package.json | 2 +- prototypejs_adapter.min.js | 11 ++- sirius-core.min.js | 48 +++++----- sirius.min.js | 189 ++++++++++++++++++++----------------- src/comment_header.coffee | 2 +- vanillajs_adapter.min.js | 11 ++- 8 files changed, 172 insertions(+), 127 deletions(-) diff --git a/Changelog.md b/Changelog.md index 4eb627c..277a186 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,24 @@ +=== 1.3.0 + +1. update binding: Implement new Materialization process + +2. change behaviour of `Sirus.BaseModel.reset` methods + +3. update todo-mvc example + +4. rename Adapter property method to `get_properties` + +5. improve internal logging with `Adapter.as_string` method + +6. remove `running` property + +7. rename `logging` to `enable_logging` + + +=== 1.2.0 + +1. improve logging + === 1.0.0 1. update docs, todo-app diff --git a/jquery_adapter.min.js b/jquery_adapter.min.js index 088d367..57b5658 100644 --- a/jquery_adapter.min.js +++ b/jquery_adapter.min.js @@ -1,5 +1,5 @@ /* - Sirius.js v1.2.0 + Sirius.js v1.3.0 (c) 2014-2020 fntz license: MIT */ @@ -10,9 +10,10 @@ $jscomp.inherits=function(a,b){a.prototype=$jscomp.objectCreate(b.prototype);a.p $jscomp.findInternal=function(a,b,c){a instanceof String&&(a=String(a));for(var d=a.length,e=0;e=d&&d>f;b=0<=f?++d:--d)a.item(b).addEventListener(c,e)}; lib$sirius_core$classdecl$var1.prototype.off=function(a,b,c,e){var d,f;if(null===b||b===a)a.removeEventListener(c,e);else for(a=this.all(b),b=d=0,f=a.length;0<=f?0<=d&&d=d&&d>f;b=0<=f?++d:--d)a.item(b).removeEventListener(c,e)};lib$sirius_core$classdecl$var1.prototype.fire=function(a,b,c){for(var e=[],d=2;d=a.weight};b.prototype.get_value=function(){return this.value};a.Debug=new b("debug",10);a.Info=new b("info",20);a.Warn=new b("warn",30);a.Error=new b("error",40);a.NoLog=new b("no-log",100);a.Levels=[a.Debug,a.Info,a.Warn,a.Error];a.Default={enable_logging:!1,minimum_log_level:a.Debug.get_value(),default_log_function:function(a,b,d){return console&&console.log?console.log(a+ -" ["+b+"]: "+d):alert("Not supported `console`. You should define own `logger` function for a Sirius.Application")}};a.Configuration={minimum_log_level:null,log_function:null,enable_logging:null,configure:function(b){b=void 0===b?{}:b;var c;var d=b.enable_logging||a.Default.enable_logging;if((c=b.minimum_log_level)&&!this.is_valid_level(c))throw b=a.Levels.map(function(a){return a.get_value()}),Error("Invalid 'minimum_log_level' value: '"+c+"', available options are: "+b.join(", "));c=c||a.Default.minimum_log_level; -this.minimum_log_level=d?this._get_logger_from_input(c):a.NoLog;this.enable_logging=d;if(b.log_to){if(!Sirius.Utils.is_function(b.log_to))throw Error("'log_to' argument must be a function");b=b.log_to}else b=a.Default.default_log_function;return this.log_function=b},is_valid_level:function(b){return 0!==a.Levels.filter(function(a){return a.get_value()===b}).length},_get_logger_from_input:function(b){var c;return null!=b&&Sirius.Utils.is_string(b)?(c=a.Levels.filter(function(a){return a.get_value()=== -b.toLowerCase()}))&&0===c.length?a.Debug:c[0]:a.Debug}};return a}.call(this);Sirius.Internal={DefaultProperty:"text"};var lib$sirius_core$classdecl$var3=function(a){this.value=a;this.fn=function(){}};lib$sirius_core$classdecl$var3.prototype.and_then=function(a){return null!=this.value?a(this.value):this.fn=a};lib$sirius_core$classdecl$var3.prototype.set_value=function(a){this.value=a;return this.fn(a)};Sirius.Promise=lib$sirius_core$classdecl$var3;var lib$sirius_core$classdecl$var4=function(){}; +Sirius.Logger=function(){var a=function(a){var b=this;this.log_source=a;this._log_queue=[];if(!Sirius.Logger.Configuration._is_initialized){var c=setInterval(function(){return f()},100);var f=function(){var a;if(Sirius.Logger.Configuration._is_initialized){var e=b._log_queue;var f=0;for(a=e.length;f=a.weight};b.prototype.get_value=function(){return this.value};a.Debug=new b("debug",10);a.Info=new b("info",20);a.Warn=new b("warn",30);a.Error=new b("error",40);a.NoLog=new b("no-log",100);a.Levels=[a.Debug,a.Info,a.Warn,a.Error];a.Default={enable_logging:!1,minimum_log_level:a.Debug.get_value(),default_log_function:function(a,b,d){return console&&console.log?console.log(a+" ["+b+"]: "+d):alert("Not supported `console`. You should define own `logger` function for a Sirius.Application")}}; +a.Configuration={minimum_log_level:null,log_function:null,enable_logging:null,_is_initialized:!1,configure:function(b){b=void 0===b?{}:b;var c;var d=b.enable_logging||a.Default.enable_logging;if((c=b.minimum_log_level)&&!this.is_valid_level(c))throw b=a.Levels.map(function(a){return a.get_value()}),Error("Invalid 'minimum_log_level' value: '"+c+"', available options are: "+b.join(", "));c=c||a.Default.minimum_log_level;this.minimum_log_level=d?this._get_logger_from_input(c):a.NoLog;this.enable_logging= +d;if(b.log_to){if(!Sirius.Utils.is_function(b.log_to))throw Error("'log_to' argument must be a function");b=b.log_to}else b=a.Default.default_log_function;this.log_function=b;return this._is_initialized=!0},is_valid_level:function(b){return 0!==a.Levels.filter(function(a){return a.get_value()===b}).length},_get_logger_from_input:function(b){var c;return null!=b&&Sirius.Utils.is_string(b)?(c=a.Levels.filter(function(a){return a.get_value()===b.toLowerCase()}))&&0===c.length?a.Debug:c[0]:a.Debug}}; +return a}.call(this);Sirius.Internal={DefaultProperty:"text",Errors:"errors"};var lib$sirius_core$classdecl$var3=function(a){this.value=a;this.fn=function(){}};lib$sirius_core$classdecl$var3.prototype.and_then=function(a){return null!=this.value?a(this.value):this.fn=a};lib$sirius_core$classdecl$var3.prototype.set_value=function(a){this.value=a;return this.fn(a)};Sirius.Promise=lib$sirius_core$classdecl$var3;var lib$sirius_core$classdecl$var4=function(){}; lib$sirius_core$classdecl$var4.is_function=function(a){return"[object Function]"===Object.prototype.toString.call(a)};lib$sirius_core$classdecl$var4.is_string=function(a){return"[object String]"===Object.prototype.toString.call(a)};lib$sirius_core$classdecl$var4.is_array=function(a){return"[object Array]"===Object.prototype.toString.call(a)};lib$sirius_core$classdecl$var4.is_object=function(a){return null!==a&&"object"===typeof a}; lib$sirius_core$classdecl$var4.camelize=function(a){return a.charAt(0).toUpperCase()+a.slice(1)};lib$sirius_core$classdecl$var4.underscore=function(a){return a.replace(/([A-Z])/g,"_$1").replace(/^_/,"").toLowerCase()};lib$sirius_core$classdecl$var4.ie_version=function(){var a=window.navigator.userAgent;var b=a.indexOf("MSIE ");var c=a.indexOf("Trident/");return 0f;){f++;var g=$jscomp.makeIterator([this.parts[f],b[f]]);var k=g.next().value;g=g.next().value;if(!k||!g)break;if(c(k))a.push(b[f]); else if(e(k)){k=new RegExp("^"+k+"$");if(!k.test(g))return!1;a.push(k.exec(g)[0])}else{if(d(k)){a=a.concat(b.slice(f));break}if(k!==g)return!1}}this.args=a;return!0};Sirius.Internal.RoutePart=lib$sirius_core$classdecl$var5; var lib$sirius_core$classdecl$var6=function(a,b){b=void 0===b?function(a){return a}:b;this.logger=Sirius.Application.get_logger(this.constructor.name);var c;if(!(c=a.controller))throw Error("Params must contain a Controller");var e=a.action;if(Sirius.Utils.is_string(e))var d=c[e];else if(Sirius.Utils.is_function(e))d=e;else throw b="Action must be string or function",this.logger.error("ControlFlow: "+b,this.logger.routing),Error(b);if(!d)throw b="action "+e+" not found in controller "+c,this.logger.error("ControlFlow: "+ -b,this.logger.routing),Error(b);this.action=b(d);b=function(b,d){d=void 0===d?!1:d;var f=a[b];var g=c[b+"_"+e];var u=function(a){return Error(a+" action must be string or function")};if(Sirius.Utils.is_string(f)){d=c[f];if(!Sirius.Utils.is_function(d))throw u(Sirius.Utils.camelize(b));return d}if(Sirius.Utils.is_function(f))return f;if(f)throw u(Sirius.Utils.camelize(b));if(g){if(!Sirius.Utils.is_function(g))throw u(Sirius.Utils.camelize(b));return g}return d?null:function(){}};this.before=b("before"); +b,this.logger.routing),Error(b);this.action=b(d);b=function(b,d){d=void 0===d?!1:d;var f=a[b];var g=c[b+"_"+e];var t=function(a){return Error(a+" action must be string or function")};if(Sirius.Utils.is_string(f)){d=c[f];if(!Sirius.Utils.is_function(d))throw t(Sirius.Utils.camelize(b));return d}if(Sirius.Utils.is_function(f))return f;if(f)throw t(Sirius.Utils.camelize(b));if(g){if(!Sirius.Utils.is_function(g))throw t(Sirius.Utils.camelize(b));return g}return d?null:function(){}};this.before=b("before"); this.after=b("after");this.guard=b("guard",!0);this.data=a.data||null;this.controller=c}; -lib$sirius_core$classdecl$var6.prototype.handle_event=function(a,b){for(var c=[],e=1;e=e&&e>f;b=0<=f?++e:--e)a.item(b).addEventListener(c,d)}; +$jscomp.polyfill=function(a,b,c,d){if(b){c=$jscomp.global;a=a.split(".");for(d=0;d=f}},"es6","es3");var Adapter,ComputedField,Sirius,VanillaJsAdapter;Sirius={VERSION:"1.0.3"};var lib$sirius$classdecl$var0=function(){};lib$sirius$classdecl$var0.prototype.bind=function(a,b,c,d){}; +lib$sirius$classdecl$var0.prototype.off=function(a,b,c,d){};lib$sirius$classdecl$var0.prototype.fire=function(a,b,c){};lib$sirius$classdecl$var0.prototype.get_properties=function(a,b){};lib$sirius$classdecl$var0.prototype.get_attr=function(a,b){};lib$sirius$classdecl$var0.prototype.swap=function(a,b){};lib$sirius$classdecl$var0.prototype.append=function(a,b){};lib$sirius$classdecl$var0.prototype.prepend=function(a,b){};lib$sirius$classdecl$var0.prototype.clear=function(a){}; +lib$sirius$classdecl$var0.prototype.set_attr=function(a,b,c){};lib$sirius$classdecl$var0.prototype.all=function(a){return"object"===typeof a&&1===a.nodeType||9===a.nodeType?a:document.querySelectorAll(a)};lib$sirius$classdecl$var0.prototype.get=function(a){return document.querySelector(a)}; +lib$sirius$classdecl$var0.prototype.as_string=function(a){if(a===document)return"document";if("[object String]"===Object.prototype.toString.call(a))return a;try{if(Array.isArray(a)&&1=e&&e>f;b=0<=f?++e:--e)a.item(b).addEventListener(c,d)}; lib$sirius$classdecl$var1.prototype.off=function(a,b,c,d){var e,f;if(null===b||b===a)a.removeEventListener(c,d);else for(a=this.all(b),b=e=0,f=a.length;0<=f?0<=e&&e=e&&e>f;b=0<=f?++e:--e)a.item(b).removeEventListener(c,d)};lib$sirius$classdecl$var1.prototype.fire=function(a,b,c){for(var d=[],e=2;e=a.weight};b.prototype.get_value=function(){return this.value};a.Debug=new b("debug",10);a.Info=new b("info",20);a.Warn=new b("warn",30);a.Error=new b("error",40);a.NoLog=new b("no-log",100);a.Levels=[a.Debug,a.Info,a.Warn,a.Error];a.Default={enable_logging:!1,minimum_log_level:a.Debug.get_value(),default_log_function:function(a,b,e){return console&&console.log?console.log(a+ -" ["+b+"]: "+e):alert("Not supported `console`. You should define own `logger` function for a Sirius.Application")}};a.Configuration={minimum_log_level:null,log_function:null,enable_logging:null,configure:function(b){b=void 0===b?{}:b;var c;var e=b.enable_logging||a.Default.enable_logging;if((c=b.minimum_log_level)&&!this.is_valid_level(c))throw b=a.Levels.map(function(a){return a.get_value()}),Error("Invalid 'minimum_log_level' value: '"+c+"', available options are: "+b.join(", "));c=c||a.Default.minimum_log_level; -this.minimum_log_level=e?this._get_logger_from_input(c):a.NoLog;this.enable_logging=e;if(b.log_to){if(!Sirius.Utils.is_function(b.log_to))throw Error("'log_to' argument must be a function");b=b.log_to}else b=a.Default.default_log_function;return this.log_function=b},is_valid_level:function(b){return 0!==a.Levels.filter(function(a){return a.get_value()===b}).length},_get_logger_from_input:function(b){var c;return null!=b&&Sirius.Utils.is_string(b)?(c=a.Levels.filter(function(a){return a.get_value()=== -b.toLowerCase()}))&&0===c.length?a.Debug:c[0]:a.Debug}};return a}.call(this);Sirius.Internal={DefaultProperty:"text"};var lib$sirius$classdecl$var3=function(a){this.value=a;this.fn=function(){}};lib$sirius$classdecl$var3.prototype.and_then=function(a){return null!=this.value?a(this.value):this.fn=a};lib$sirius$classdecl$var3.prototype.set_value=function(a){this.value=a;return this.fn(a)};Sirius.Promise=lib$sirius$classdecl$var3;var lib$sirius$classdecl$var4=function(){}; +Sirius.Logger=function(){var a=function(a){var b=this;this.log_source=a;this._log_queue=[];if(!Sirius.Logger.Configuration._is_initialized){var c=setInterval(function(){return f()},100);var f=function(){var a;if(Sirius.Logger.Configuration._is_initialized){var d=b._log_queue;var e=0;for(a=d.length;e=a.weight};b.prototype.get_value=function(){return this.value};a.Debug=new b("debug",10);a.Info=new b("info",20);a.Warn=new b("warn",30);a.Error=new b("error",40);a.NoLog=new b("no-log",100);a.Levels=[a.Debug,a.Info,a.Warn,a.Error];a.Default={enable_logging:!1,minimum_log_level:a.Debug.get_value(),default_log_function:function(a,b,e){return console&&console.log?console.log(a+" ["+b+"]: "+e):alert("Not supported `console`. You should define own `logger` function for a Sirius.Application")}}; +a.Configuration={minimum_log_level:null,log_function:null,enable_logging:null,_is_initialized:!1,configure:function(b){b=void 0===b?{}:b;var c;var e=b.enable_logging||a.Default.enable_logging;if((c=b.minimum_log_level)&&!this.is_valid_level(c))throw b=a.Levels.map(function(a){return a.get_value()}),Error("Invalid 'minimum_log_level' value: '"+c+"', available options are: "+b.join(", "));c=c||a.Default.minimum_log_level;this.minimum_log_level=e?this._get_logger_from_input(c):a.NoLog;this.enable_logging= +e;if(b.log_to){if(!Sirius.Utils.is_function(b.log_to))throw Error("'log_to' argument must be a function");b=b.log_to}else b=a.Default.default_log_function;this.log_function=b;return this._is_initialized=!0},is_valid_level:function(b){return 0!==a.Levels.filter(function(a){return a.get_value()===b}).length},_get_logger_from_input:function(b){var c;return null!=b&&Sirius.Utils.is_string(b)?(c=a.Levels.filter(function(a){return a.get_value()===b.toLowerCase()}))&&0===c.length?a.Debug:c[0]:a.Debug}}; +return a}.call(this);Sirius.Internal={DefaultProperty:"text",Errors:"errors"};var lib$sirius$classdecl$var3=function(a){this.value=a;this.fn=function(){}};lib$sirius$classdecl$var3.prototype.and_then=function(a){return null!=this.value?a(this.value):this.fn=a};lib$sirius$classdecl$var3.prototype.set_value=function(a){this.value=a;return this.fn(a)};Sirius.Promise=lib$sirius$classdecl$var3;var lib$sirius$classdecl$var4=function(){}; lib$sirius$classdecl$var4.is_function=function(a){return"[object Function]"===Object.prototype.toString.call(a)};lib$sirius$classdecl$var4.is_string=function(a){return"[object String]"===Object.prototype.toString.call(a)};lib$sirius$classdecl$var4.is_array=function(a){return"[object Array]"===Object.prototype.toString.call(a)};lib$sirius$classdecl$var4.is_object=function(a){return null!==a&&"object"===typeof a};lib$sirius$classdecl$var4.camelize=function(a){return a.charAt(0).toUpperCase()+a.slice(1)}; lib$sirius$classdecl$var4.underscore=function(a){return a.replace(/([A-Z])/g,"_$1").replace(/^_/,"").toLowerCase()};lib$sirius$classdecl$var4.ie_version=function(){var a=window.navigator.userAgent;var b=a.indexOf("MSIE ");var c=a.indexOf("Trident/");return 0f;){f++;var g=$jscomp.makeIterator([this.parts[f],b[f]]);var k=g.next().value;g=g.next().value;if(!k||!g)break;if(c(k))a.push(b[f]); -else if(d(k)){k=new RegExp("^"+k+"$");if(!k.test(g))return!1;a.push(k.exec(g)[0])}else{if(e(k)){a=a.concat(b.slice(f));break}if(k!==g)return!1}}this.args=a;return!0};Sirius.Internal.RoutePart=lib$sirius$classdecl$var5; +lib$sirius$classdecl$var5.prototype.match=function(a){this.args=[];var b=a.replace(/\/$/,"").split("/");""===b[0]&&(b[0]="#");if(b.length!==this.parts.length&&this.end||1f;){f++;var g=$jscomp.makeIterator([this.parts[f],b[f]]);var h=g.next().value;g=g.next().value;if(!h||!g)break;if(c(h))a.push(b[f]); +else if(d(h)){h=new RegExp("^"+h+"$");if(!h.test(g))return!1;a.push(h.exec(g)[0])}else{if(e(h)){a=a.concat(b.slice(f));break}if(h!==g)return!1}}this.args=a;return!0};Sirius.Internal.RoutePart=lib$sirius$classdecl$var5; var lib$sirius$classdecl$var6=function(a,b){b=void 0===b?function(a){return a}:b;this.logger=Sirius.Application.get_logger(this.constructor.name);var c;if(!(c=a.controller))throw Error("Params must contain a Controller");var d=a.action;if(Sirius.Utils.is_string(d))var e=c[d];else if(Sirius.Utils.is_function(d))e=d;else throw b="Action must be string or function",this.logger.error("ControlFlow: "+b,this.logger.routing),Error(b);if(!e)throw b="action "+d+" not found in controller "+c,this.logger.error("ControlFlow: "+ b,this.logger.routing),Error(b);this.action=b(e);b=function(b,e){e=void 0===e?!1:e;var f=a[b];var g=c[b+"_"+d];var l=function(a){return Error(a+" action must be string or function")};if(Sirius.Utils.is_string(f)){e=c[f];if(!Sirius.Utils.is_function(e))throw l(Sirius.Utils.camelize(b));return e}if(Sirius.Utils.is_function(f))return f;if(f)throw l(Sirius.Utils.camelize(b));if(g){if(!Sirius.Utils.is_function(g))throw l(Sirius.Utils.camelize(b));return g}return e?null:function(){}};this.before=b("before"); this.after=b("after");this.guard=b("guard",!0);this.data=a.data||null;this.controller=c}; -lib$sirius$classdecl$var6.prototype.handle_event=function(a,b){for(var c=[],d=1;d=d&&a<=c)return!0;this.msg="Required length in range [0.."+c+"], given: "+a;return!1}this.msg="Given null for LengthValidator";return!1};Sirius.LengthValidator=lib$sirius$classdecl$var7; var lib$sirius$classdecl$var8=function(){return Sirius.Validator.apply(this,arguments)||this};$jscomp.inherits(lib$sirius$classdecl$var8,Sirius.Validator);lib$sirius$classdecl$var8.prototype.validate=function(a,b){this.logger.info("ExclusionValidator: start validate '"+a+"'");if(-1===(b.within||[]).indexOf(a))return!0;this.msg="Value "+a+" reserved";return!1};Sirius.ExclusionValidator=lib$sirius$classdecl$var8; var lib$sirius$classdecl$var9=function(){return Sirius.Validator.apply(this,arguments)||this};$jscomp.inherits(lib$sirius$classdecl$var9,Sirius.Validator);lib$sirius$classdecl$var9.prototype.validate=function(a,b){this.logger.info("InclusionValidator: start validate '"+a+"'");b=b.within||[];if(-1 '"+this._to.get_element()+ -" "+k+"'");e.push(new Sirius.Internal.Observer(c,h,g,b))}return e}return this._from._register_state_listener(b)};lib$sirius$classdecl$var18._default_materializer_method=function(){return function(a,b,c,d){d=void 0===d?"text":d;return c.zoom(b).render(a).swap(d)}}; -lib$sirius$classdecl$var18.prototype._fire_generator=function(){var a=this._to;var b=this._path;var c=this.logger;if(this._from instanceof Sirius.View)var d=function(c){var d;var e=b.to;var f=c.text;var l=[];var n=0;for(d=e.length;n "+g.to))}return f}};a.prototype._check_to_model_compliance= -function(a){var b;if(this._to instanceof Sirius.BaseModel){var d=this._to._klass_name();var e=[];for(b in a){var f=a[b];f=f.to;if(null==f)throw Error("Failed to create transformer for '"+d+"', because '"+JSON.stringify(a)+"', does not contain 'to'-property");var g=this._to.get_attributes();if(-1===g.indexOf(f))throw Error("Unexpected '"+f+"' for model binding. Model is: '"+d+"', available attributes: '["+g+"]'");e.push(this.logger.debug("bind: '"+b+"' -> '"+d+"."+f+"'"))}return e}};a.prototype._check_view_to_view_compliance= -function(a){var b,d;var e=this._from.get_element();var f=this._to.get_element();if(d=a.to){if(Sirius.Utils.is_array(d)){var g=0;for(b=d.length;g '"+f+" "+k+"'");else throw Error('You defined binding with \'to\' as an array of objects, but \'selector\' property was not found Correct definition is: {to: [{"selector": "#my-id", "attribute": "data-attr"}]}');}return a}if(Sirius.Utils.is_string(d))return this.logger.debug("bind: '"+ -e+"' -> '"+f+" "+d+"'"),a.to=[d],a;throw Error("View to View binding must contains 'to' as an array or a string, but "+typeof d+" given");}throw Error("Define View to View binding with: 'view1.bind(view2, {\"to\": \"selector\"})', but 'to'-property was not found");};a.prototype._check_view_to_function_compliance=function(a){var b;var d=this._from.get_element();for(b in a){var e=a[b];if(e.from)e=e.from,this.logger.debug("bind: '"+d+" "+b+"' (from '"+e+"') -> function");else throw Error('View to Function binding must contain \'from\'-property: {"selector": {"from": "text"}}'); -}return a};a.prototype.run=function(a){if(!Sirius.Utils.is_object(a))throw Error("Materializer must be object, '"+typeof a+"' given");if(!Sirius.Utils.is_function(this._to)&&0===Object.keys(a).length)throw Error("Materializer must be non empty object");if(!this._from||!this._to)throw Error("Not all parameters defined for transformer: from: "+this._from+", to: "+this._to);this._check_from_model_compliance(a);this._check_to_model_compliance(a);this._from instanceof Sirius.View&&this._to instanceof Sirius.View? -this._path=this._check_view_to_view_compliance(a):this._from instanceof Sirius.View&&Sirius.Utils.is_function(this._to)?this._path=this._check_view_to_function_compliance(a):this._path=a;if(this._to instanceof Sirius.BaseModel)return new Sirius.Internal.ToModelTransformer(a,this._from,this._to);if(this._to instanceof Sirius.View)return new Sirius.Internal.ToViewTransformer(a,this._from,this._to);if(Sirius.Utils.is_function(this._to))return new Sirius.Internal.ToFunctionTransformer(a,this._from,this._to)}; -a._Model=0;a._View=1;a._Function=2;a.prototype._from=null;a.prototype._to=null;a.prototype._path=null;return a}.call(this); +if(-1!==Object.keys(this).indexOf(d))throw Error("Method '"+d+"' already exist");return this[d]=function(c){return null!=c?b.set(a,c):b.get(a)}};a.prototype._gen_binding_names=function(){var a,c,d;var e={};var f=this.get_attributes();var g=0;for(c=f.length;gd}).map(function(a){return--b["index_"+c][a]})}))};a.prototype.index=function(a){var b,d;var e=null;var f=this._array;var g=b=0;for(d=f.length;bd}).map(function(a){return--b["index_"+c][a]})}))};a.prototype.index=function(a){var b,d;var e=null;var f=this._array;var g=b=0;for(d=f.length;b "+this._transform+" ~> "+b+"#"+this._attribute};lib$sirius$classdecl$var20.build=function(a){return new Sirius.FieldMaker(a)};Sirius.FieldMaker=lib$sirius$classdecl$var20; +var lib$sirius$classdecl$var21=function(a,b){this._from=a;this._to=b;this.fields=[];this.current=null};lib$sirius$classdecl$var21.prototype.field=function(a){null!=this.current&&this.current.normalize();this.current=Sirius.FieldMaker.build(a);this.fields.push(this.current);return this};lib$sirius$classdecl$var21.prototype._zoom_with=function(a,b){return Sirius.Utils.is_string(b)?a.zoom(b):b};lib$sirius$classdecl$var21.prototype.dump=function(){return this.fields.map(function(a){return a.to_string()}).join("\n")}; +lib$sirius$classdecl$var21.prototype.to_string=function(){return this.dump()};lib$sirius$classdecl$var21.prototype.get_from=function(){return this._from};lib$sirius$classdecl$var21.prototype.get_to=function(){return this._to()};lib$sirius$classdecl$var21.prototype.has_to=function(){return null!=this._to};lib$sirius$classdecl$var21.prototype.fields_map=function(){var a;var b={};var c=this.fields;var d=0;for(a=c.length;d=d&&d>f;b=0<=f?++d:--d)a.item(b).addEventListener(c,e)}; lib$vanillajs_adapter$classdecl$var1.prototype.off=function(a,b,c,e){var d,f;if(null===b||b===a)a.removeEventListener(c,e);else for(a=this.all(b),b=d=0,f=a.length;0<=f?0<=d&&d=d&&d>f;b=0<=f?++d:--d)a.item(b).removeEventListener(c,e)};lib$vanillajs_adapter$classdecl$var1.prototype.fire=function(a,b,c){for(var e=[],d=2;d