Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

observe "array^*" (not "array^**") #408

Closed
ainglese opened this issue Jul 27, 2018 · 12 comments
Closed

observe "array^*" (not "array^**") #408

ainglese opened this issue Jul 27, 2018 · 12 comments

Comments

@ainglese
Copy link

when using:

var model = {
	list: [
		{  qty:0, attributes:[...] },
		{  qty:1, attributes:[...] }
	]
}
$.observe(model, 'list^**', handler)

handler gets correctly invoked when everything in list changes.

the problem is, that binding performance get worse when attributes property has many level in depth. because i just need to listen to "qty" property, i've tried:

$.observe(model, 'list^*', handler1)
$.observe(model, 'list^qty', handler2)

but none of the two works;

here's a fiddle: https://jsfiddle.net/wcro58mL/1/

am i missing something?
thanks

@BorisMoore
Copy link
Owner

BorisMoore commented Jul 28, 2018

There is no path syntax for specific properties of any array item. (That would require that each time a new item is inserted or removed from the array, internal smarts would need to add/remove listeners for "qty" on the item.)

So either you need to use "**" or observeAll and then test for eventArgs.path==="qty" in the handler (and otherwise simply return), or you would need to write code yourself:

  • Iterate over the array and observe(item, "qty", handler) for each item (or when new items are added).
  • Then if appropriate do unobserve when items are removed...

@ainglese
Copy link
Author

thank you for your fast reply!

i think i've to go with the "code yourself" way, because the problem origin from a performance problem with observeAll.

@ainglese
Copy link
Author

can you confirm there's no syntax also for "property exclusion", something like observeAll but attribute property?

@eugene-panferov
Copy link

eugene-panferov commented Jul 28, 2018 via email

@ainglese
Copy link
Author

ainglese commented Jul 28, 2018

@eugene-panferov of course, it's the first thing we did. But our model isn't that complex, it's a document, an object with a rows property. in each row there are many property but one of this is "custom_data", a prop where we can store arbitrary json that is used to store customized information related to the document. We need to observe change to trigger autosave and do stuff like validation.

We can't predit the structure in the custom_data field, but sure we don't need to observe that piece of information. We faced the problem when a customer started to store big structure in that field in combination with hundreds of rows.

we already know we can:

  • use a readonly view and edit each row via an edit button
  • paginate rows
  • change drastically the model and handle the data in another, non observed, structure
  • for each field in the doument/row, make a property that updates timestamp on set, and then monitor that.
  • asyncronously render the rows into the dom (so databindings are attached progressively)
  • remove unused observable

the last one seems to us the fastest way and the less impacting to the user.

if you have a suggestion on how to reenginer the model, your feedback would be really appreciated!

@ainglese
Copy link
Author

ainglese commented Jul 28, 2018

i've come up with something like this (sure there are better names):

https://jsfiddle.net/wcro58mL/20/

observeArrayProps('ns', model.list, 'qty', function(){ .. });
unobserveArrayProps('ns', model.list);


    function observeArrayProps() {

        var observeArgs = Array.prototype.slice.call(arguments);
        var ns = arguments[0];
        var array = arguments[1];

        function arrayObserver(e, o) {
            if (o.change === 'insert') {
                for (var i = 0; i < o.items.length; i++) {
                    observeArgs[1] = o.items[i];
                    $.observe.apply(null, observeArgs);
                }
            }
            else if (o.change === 'remove') {
                for (var i = 0; i < o.items.length; i++) {
                    observeArgs[1] = o.items[i];
                    $.unobserve.apply(null, observeArgs);
                }
            }
        }
        
        $.observe(array, arrayObserver);

        for (var i = 0; i < array.length; i++) {
            observeArgs[1] = array[i];
            $.observe.apply(null, observeArgs);
        }

    }

    function unobserveArrayProps() {

        var observeArgs = Array.prototype.slice.call(arguments);
        var ns = arguments[0];
        var array = arguments[1];

        $.unobserve(ns, array);
        for (var i = 0; i < array.length; i++) {
            observeArgs[0] = array[i];
            $.unobserve.apply(null, observeArgs);
        }

    }

@BorisMoore
Copy link
Owner

That approach looks correct - so you should be able to do it that way - as long as you don't use $.observable().refresh() - or else you can add support for that too.

But there is a filtering feature built in which is undocumented right now. (So it could change in the future). It looks like this:

$.observable(model.list).observeAll(
function handler() {
  do stuff
},
function filter(allPath, object, parentObs) {
  return !/]\./.test(allPath); // Don't observe any object with `].` in the allPath string.
});

The filter function is called for each object in the hierarchy and you return false if you don't want to observe that object (or any of its property objects). In your example it will be called for each of the attributes objects, with allPath "root[].attributes". Return false to tell it not to recursively do an observeAll on that object.

@BorisMoore
Copy link
Owner

BorisMoore commented Jul 30, 2018

Actually your code above might be fine with .refresh() too, since .refresh() actually triggers a corresponding sequence of insert remove or move operations.

I am looking at a possible string path based approach along the lines of

$.observe(model, 'list.[].qty', handler);

Not sure yet if it can be achieved appropriately - we'll see. I'll let you know...

@ainglese
Copy link
Author

thank you very much @BorisMoore!
i've updated the fiddle to also test the refresh; after the refresh, the handler still gets called.
https://jsfiddle.net/wcro58mL/22/

@BorisMoore
Copy link
Owner

@ainglese: Here is a candidate update: jsviews.js.txt.

It supports the following syntax:

Observe changes of item.qty for all current items in list:

$.observe('ns', model.list, '[].qty', handler);

Observe changes of item.qty for all items in list - including new items if list changes - and remove handlers for removed items:

$.observe('ns', model.list, '[]^qty', handler);

I went ahead with this not only for your scenario, but also for other scenarios I wanted to support, such as having a computed obersvable foo(...) and wanting to set depends paths such as foo.depends = "some.list.[]^*" (for example "#parent.data.[]^*")

Let me know if it works well for you.

@ainglese
Copy link
Author

ainglese commented Aug 6, 2018

thats great! thanks i'll let you know as soon as i try it

BorisMoore added a commit to BorisMoore/jsviews.com that referenced this issue Sep 21, 2018
Major update

Breaking change

- Templates declared in <div> or other non script element are not
  supported. (Throws with error message)

Minor breaking changes

- The {{range}} tag has been removed - replaced by {{for}} tag using
  built-in range features. See https://www.jsviews.com#jsvfortag@jsvsortfilterrange

- Changed behavior of .setValue() API on custom tags. The .setValue()
  method is called even for undefined values. The .getValue() method
  is not called. Return false from setValue to not data-link a linked
  element. (e.g. jQuery UI slider). See https://www.jsviews.com#tagoptions@setvalue

- {{tabs}} API changes: The selectedIndex property is renamed to the
  pane property. The tabCaption property is renamed to the caption
  property

- Some changes to behavior/effect of the bindTo tag option - which is
  now associated with the new bindFrom tag option.
  See https://www.jsviews.com#tagoptions@bindto and https://www.jsviews.com#tagoptions@bindfrom

Major feature improvements

- The {{for}} tag now has built-in support for sort, filter, start and
  end. See https://www.jsviews.com#fortag@sortfilterrange

- The {{props}} tag now has built-in support for sort, filter, start
  and end. See https://www.jsviews.com#propstag@sortfilterrange

- New converters encode/unencode. See https://www.jsviews.com#convertersapi@encode

- New tag options: bindFrom - together with tag option bindTo - provide
  improved features for custom tags.
  See https://www.jsviews.com#tagoptions@bindto and https://www.jsviews.com#tagoptions@bindfrom

- New support for observing/unobserving contextual parameters.
  $.observe(tagOrView, "~foo", callback) - documentation to follow

- New support for observing properties of any item in an array, using
  "array.[].*" or "array.[].foo" (or "array.[]^*", etc.) - documentation to follow

- Late paths support: "@a.b.c" - documentation to follow

- New support for getting a reference to a tag instance using
  {^{mytag this=~myTag}} - documentation to follow

Minor feature improvements

- Custom tag linkedElem can now target non-editable and contentEditable.
  See https://www.jsviews.com#bindingpatterns@linkedelem

- New APIs added for tagCtx.ctxPrm(), tagCtx.cvtArgs() and
  tagCtx.bndArgs() even for non-data-linked tags - documentation to follow

- The contentCtx option now works also for custom tag using render(),
  rather than a template. See https://www.jsviews.com#tagsapi@contentctx

- In a template, ~tag.tagCtx.xxx can now be written ~tagCtx.xxx,
  and works correctly e.g. for data-linking using
  {{mytag}}{{else}}{{~tagCtx...}}{{/mytag}} - documentation to follow

- ~tagCtx is now a reserved name like ~tag ~root ~parentTags...

- On a custom tag, the bindTo option is not needed with 2-way linking
  to first argument

- updateValue() now supports an async option: pass true for the fourth
  parameter, defer - updateValue(val, index, tagElse, tag, defer).
  - Documentation to follow

- New debug:true option on a compiled template.
  See https://www.jsviews.com#d.templates@debug

- New depends option on custom tags (see https://www.jsviews.com#tagoptions@depends)
  or an instance property {{someTag depends='a.b'}} or
  {{someTag depends=~someTagDependsArray}}

- An error will be thrown if different versions of jsrender.js,
  jquery.observable.js and  jquery.views.js are loaded together

- DataMap, {{props}} and {{jsonview}} now include support for function
  properties too, unless opt out using {{props foo noFunctions=true}}.
  See https://www.jsviews.com#propstag@nofunctions

- Support for nested {{for}} tags without iteration, and for then
  applying operations such as sorting to arrays in nexted context, as in
  {{for filter=... noIteration=true}}{{for sort=... noIteration=true}}{{for start=...  end=...}}.
  See https://www.jsviews.com#fortag@itemvar

Documentation

- Extensive new documentation, especially on custom tag controls:
  See https://www.jsviews.com#jsvtagcontrols. For an example of a JsViews
  custom tag control see https://www.jsviews.com#samples/tag-controls/colorpicker

Minor bug fixes, including:

- a contentEditable bug for IE

- a bug for data-linking to 'length' property.

- a bug when a computed property 'depends' mixes data and contextual paths

- a bug in jquery-1.x cleanData

- a bug in move() arrayChange on {{for}}

- Issue BorisMoore/jsviews#408
  $.observe('ns', model.list, '[]^qty', handler);??

- Issue BorisMoore/jsviews#406
  Weird data-linked `for` loop behaviour with deep observing after model update

- Issue BorisMoore/jsviews#404
  Props Convert else statement not working

- Issue BorisMoore/jsviews#403
  ~ operator support

- Issue BorisMoore/jsviews#400
  Move the tag property in views to before rendering, to enable "get current path" path scenario

- Issue BorisMoore/jsviews#398
  After DOM modifications to the child options of a data-linked select, the first option shows as selected

- Issue BorisMoore/jsviews#397
  {^{radiogroup value disabled=notEnabled}} is now supported

- Issue BorisMoore/jsviews#198
  Provide full documentation of custom tags using 2-way binding, binding to args or props etc.

- Issue BorisMoore/jsviews#374
  Wrong initial value shown (timespinner control)

- Issue BorisMoore/jsrender#335
  encode and unencode converters
BorisMoore added a commit to BorisMoore/jsviews.com that referenced this issue Sep 21, 2018
Major update

Breaking change

- Templates declared in <div> or other non script element are not
  supported. (Throws with error message)

Minor breaking changes

- The {{range}} tag has been removed - replaced by {{for}} tag using
  built-in range features. See https://www.jsviews.com#jsvfortag@jsvsortfilterrange

- Changed behavior of .setValue() API on custom tags. The .setValue()
  method is called even for undefined values. The .getValue() method
  is not called. Return false from setValue to not data-link a linked
  element. (e.g. jQuery UI slider). See https://www.jsviews.com#tagoptions@setvalue

- {{tabs}} API changes: The selectedIndex property is renamed to the
  pane property. The tabCaption property is renamed to the caption
  property

- Some changes to behavior/effect of the bindTo tag option - which is
  now associated with the new bindFrom tag option.
  See https://www.jsviews.com#tagoptions@bindto and https://www.jsviews.com#tagoptions@bindfrom

Major feature improvements

- The {{for}} tag now has built-in support for sort, filter, start and
  end. See https://www.jsviews.com#fortag@sortfilterrange

- The {{props}} tag now has built-in support for sort, filter, start
  and end. See https://www.jsviews.com#propstag@sortfilterrange

- New converters encode/unencode. See https://www.jsviews.com#convertersapi@encode

- New tag options: bindFrom - together with tag option bindTo - provide
  improved features for custom tags.
  See https://www.jsviews.com#tagoptions@bindto and https://www.jsviews.com#tagoptions@bindfrom

- New support for observing/unobserving contextual parameters.
  $.observe(tagOrView, "~foo", callback) - documentation to follow

- New support for observing properties of any item in an array, using
  "array.[].*" or "array.[].foo" (or "array.[]^*", etc.) - documentation to follow

- Late paths support: "@a.b.c" - documentation to follow

- New support for getting a reference to a tag instance using
  {^{mytag this=~myTag}} - documentation to follow

Minor feature improvements

- Custom tag linkedElem can now target non-editable and contentEditable.
  See https://www.jsviews.com#bindingpatterns@linkedelem

- New APIs added for tagCtx.ctxPrm(), tagCtx.cvtArgs() and
  tagCtx.bndArgs() even for non-data-linked tags - documentation to follow

- The contentCtx option now works also for custom tag using render(),
  rather than a template. See https://www.jsviews.com#tagsapi@contentctx

- In a template, ~tag.tagCtx.xxx can now be written ~tagCtx.xxx,
  and works correctly e.g. for data-linking using
  {{mytag}}{{else}}{{~tagCtx...}}{{/mytag}} - documentation to follow

- ~tagCtx is now a reserved name like ~tag ~root ~parentTags...

- On a custom tag, the bindTo option is not needed with 2-way linking
  to first argument

- updateValue() now supports an async option: pass true for the fourth
  parameter, defer - updateValue(val, index, tagElse, tag, defer).
  - Documentation to follow

- New debug:true option on a compiled template.
  See https://www.jsviews.com#d.templates@debug

- New depends option on custom tags (see https://www.jsviews.com#tagoptions@depends)
  or an instance property {{someTag depends='a.b'}} or
  {{someTag depends=~someTagDependsArray}}

- An error will be thrown if different versions of jsrender.js,
  jquery.observable.js and  jquery.views.js are loaded together

- DataMap, {{props}} and {{jsonview}} now include support for function
  properties too, unless opt out using {{props foo noFunctions=true}}.
  See https://www.jsviews.com#propstag@nofunctions

- Support for nested {{for}} tags without iteration, and for then
  applying operations such as sorting to arrays in nexted context, as in
  {{for filter=... noIteration=true}}{{for sort=... noIteration=true}}{{for start=...  end=...}}.
  See https://www.jsviews.com#fortag@itemvar

Documentation

- Extensive new documentation, especially on custom tag controls:
  See https://www.jsviews.com#jsvtagcontrols. For an example of a JsViews
  custom tag control see https://www.jsviews.com#samples/tag-controls/colorpicker

Minor bug fixes, including:

- a contentEditable bug for IE

- a bug for data-linking to 'length' property.

- a bug when a computed property 'depends' mixes data and contextual paths

- a bug in jquery-1.x cleanData

- a bug in move() arrayChange on {{for}}

- Issue BorisMoore/jsviews#408
  $.observe('ns', model.list, '[]^qty', handler);??

- Issue BorisMoore/jsviews#406
  Weird data-linked `for` loop behaviour with deep observing after model update

- Issue BorisMoore/jsviews#404
  Props Convert else statement not working

- Issue BorisMoore/jsviews#403
  ~ operator support

- Issue BorisMoore/jsviews#400
  Move the tag property in views to before rendering, to enable "get current path" path scenario

- Issue BorisMoore/jsviews#398
  After DOM modifications to the child options of a data-linked select, the first option shows as selected

- Issue BorisMoore/jsviews#397
  {^{radiogroup value disabled=notEnabled}} is now supported

- Issue BorisMoore/jsviews#198
  Provide full documentation of custom tags using 2-way binding, binding to args or props etc.

- Issue BorisMoore/jsviews#374
  Wrong initial value shown (timespinner control)

- Issue BorisMoore/jsrender#335
  encode and unencode converters
BorisMoore added a commit that referenced this issue Sep 22, 2018
Major update

Breaking change

- Templates declared in <div> or other non script element are not
  supported. (Throws with error message)

Minor breaking changes

- The {{range}} tag has been removed - replaced by {{for}} tag using
  built-in range features. See https://www.jsviews.com#jsvfortag@jsvsortfilterrange

- Changed behavior of .setValue() API on custom tags. The .setValue()
  method is called even for undefined values. The .getValue() method
  is not called. Return false from setValue to not data-link a linked
  element. (e.g. jQuery UI slider). See https://www.jsviews.com#tagoptions@setvalue

- {{tabs}} API changes: The selectedIndex property is renamed to the
  pane property. The tabCaption property is renamed to the caption
  property

- Some changes to behavior/effect of the bindTo tag option - which is
  now associated with the new bindFrom tag option.
  See https://www.jsviews.com#tagoptions@bindto and https://www.jsviews.com#tagoptions@bindfrom

Major feature improvements

- The {{for}} tag now has built-in support for sort, filter, start and
  end. See https://www.jsviews.com#fortag@sortfilterrange

- The {{props}} tag now has built-in support for sort, filter, start
  and end. See https://www.jsviews.com#propstag@sortfilterrange

- New converters encode/unencode. See https://www.jsviews.com#convertersapi@encode

- New tag options: bindFrom - together with tag option bindTo - provide
  improved features for custom tags.
  See https://www.jsviews.com#tagoptions@bindto and https://www.jsviews.com#tagoptions@bindfrom

- New support for observing/unobserving contextual parameters.
  $.observe(tagOrView, "~foo", callback) - documentation to follow

- New support for observing properties of any item in an array, using
  "array.[].*" or "array.[].foo" (or "array.[]^*", etc.) - documentation to follow

- Late paths support: "@a.b.c" - documentation to follow

- New support for getting a reference to a tag instance using
  {^{mytag this=~myTag}} - documentation to follow

Minor feature improvements

- Custom tag linkedElem can now target non-editable and contentEditable.
  See https://www.jsviews.com#bindingpatterns@linkedelem

- New APIs added for tagCtx.ctxPrm(), tagCtx.cvtArgs() and
  tagCtx.bndArgs() even for non-data-linked tags - documentation to follow

- The contentCtx option now works also for custom tag using render(),
  rather than a template. See https://www.jsviews.com#tagsapi@contentctx

- In a template, ~tag.tagCtx.xxx can now be written ~tagCtx.xxx,
  and works correctly e.g. for data-linking using
  {{mytag}}{{else}}{{~tagCtx...}}{{/mytag}} - documentation to follow

- ~tagCtx is now a reserved name like ~tag ~root ~parentTags...

- On a custom tag, the bindTo option is not needed with 2-way linking
  to first argument

- updateValue() now supports an async option: pass true for the fourth
  parameter, defer - updateValue(val, index, tagElse, tag, defer).
  - Documentation to follow

- New debug:true option on a compiled template.
  See https://www.jsviews.com#d.templates@debug

- New depends option on custom tags (see https://www.jsviews.com#tagoptions@depends)
  or an instance property {{someTag depends='a.b'}} or
  {{someTag depends=~someTagDependsArray}}

- An error will be thrown if different versions of jsrender.js,
  jquery.observable.js and  jquery.views.js are loaded together

- DataMap, {{props}} and {{jsonview}} now include support for function
  properties too, unless opt out using {{props foo noFunctions=true}}.
  See https://www.jsviews.com#propstag@nofunctions

- Support for nested {{for}} tags without iteration, and for then
  applying operations such as sorting to arrays in nexted context, as in
  {{for filter=... noIteration=true}}{{for sort=... noIteration=true}}{{for start=...  end=...}}.
  See https://www.jsviews.com#fortag@itemvar

Documentation

- Extensive new documentation, especially on custom tag controls:
  See https://www.jsviews.com#jsvtagcontrols. For an example of a JsViews
  custom tag control see https://www.jsviews.com#samples/tag-controls/colorpicker

Minor bug fixes, including:

- a contentEditable bug for IE

- a bug for data-linking to 'length' property.

- a bug when a computed property 'depends' mixes data and contextual paths

- a bug in jquery-1.x cleanData

- a bug in move() arrayChange on {{for}}

- Issue #408
  $.observe('ns', model.list, '[]^qty', handler);??

- Issue #406
  Weird data-linked `for` loop behaviour with deep observing after model update

- Issue #404
  Props Convert else statement not working

- Issue #403
  ~ operator support

- Issue #400
  Move the tag property in views to before rendering, to enable "get current path" path scenario

- Issue #398
  After DOM modifications to the child options of a data-linked select, the first option shows as selected

- Issue #397
  {^{radiogroup value disabled=notEnabled}} is now supported

- Issue #198
  Provide full documentation of custom tags using 2-way binding, binding to args or props etc.

- Issue #374
  Wrong initial value shown (timespinner control)

- Issue BorisMoore/jsrender#335
  encode and unencode converters
@BorisMoore
Copy link
Owner

This has been fixed in release v0.9.91.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants