From f59020fca27090c7e1d948fc82101ad99d662958 Mon Sep 17 00:00:00 2001 From: BorisMoore Date: Fri, 21 Sep 2018 11:54:42 -0700 Subject: [PATCH] Commit 91 (v0.9.91 - Beta) Major update Breaking change - Templates declared in
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 https://github.com/BorisMoore/jsviews/issues/408 $.observe('ns', model.list, '[]^qty', handler);?? - Issue https://github.com/BorisMoore/jsviews/issues/406 Weird data-linked `for` loop behaviour with deep observing after model update - Issue https://github.com/BorisMoore/jsviews/issues/404 Props Convert else statement not working - Issue https://github.com/BorisMoore/jsviews/issues/403 ~ operator support - Issue https://github.com/BorisMoore/jsviews/issues/400 Move the tag property in views to before rendering, to enable "get current path" path scenario - Issue https://github.com/BorisMoore/jsviews/issues/398 After DOM modifications to the child options of a data-linked select, the first option shows as selected - Issue https://github.com/BorisMoore/jsviews/issues/397 {^{radiogroup value disabled=notEnabled}} is now supported - Issue https://github.com/BorisMoore/jsviews/issues/198 Provide full documentation of custom tags using 2-way binding, binding to args or props etc. - Issue https://github.com/BorisMoore/jsviews/issues/374 Wrong initial value shown (timespinner control) - Issue https://github.com/BorisMoore/jsrender/issues/335 encode and unencode converters --- .gitignore | 3 +- _src/jquery.observable.js | 694 +- _src/jquery.views.js | 797 +- _src/jsrender-node-starter/package.json | 8 +- _src/jsrender.js | 765 +- _src/jsrender/package.json | 4 +- _src/jsviews/package.json | 11 +- _src/templates/-copyright.txt | 2 +- _src/templates/jquery.observable.js | 5 + _src/templates/jquery.views.js | 4 +- .../jsrender-node-starter/package.json | 2 +- _src/templates/jsrender.js | 2 +- _src/templates/jsrender/package.json | 2 +- _src/templates/jsviews.com/package.json | 7 +- _src/templates/jsviews/package.json | 7 +- _src/templates/tmplify/index.js | 2 +- dash.html | 2 +- documentation/contents-categories.js | 101 +- documentation/contents-categories.min.js | 2 +- documentation/contents-categories.min.js.map | 2 +- documentation/contents-download.js | 31 +- documentation/contents-download.min.js | 3 +- documentation/contents-download.min.js.map | 2 +- documentation/contents-getstarted.js | 2 +- documentation/contents-getstarted.min.js | 2 +- documentation/contents-getstarted.min.js.map | 2 +- documentation/contents-jsoapi.js | 180 +- documentation/contents-jsoapi.min.js | 8 +- documentation/contents-jsoapi.min.js.map | 2 +- documentation/contents-jsrapi.js | 7550 +++++++++-------- documentation/contents-jsrapi.min.js | 25 +- documentation/contents-jsrapi.min.js.map | 2 +- documentation/contents-jsvapi.js | 6548 ++++++++------ documentation/contents-jsvapi.min.js | 29 +- documentation/contents-jsvapi.min.js.map | 2 +- documentation/contents-samples.js | 457 +- documentation/contents-samples.min.js | 16 +- documentation/contents-samples.min.js.map | 2 +- documentation/find-download.js | 29 +- documentation/find-download.min.js | 2 +- documentation/find-download.min.js.map | 2 +- documentation/find-getstarted.js | 2 +- documentation/find-getstarted.min.js | 2 +- documentation/find-getstarted.min.js.map | 2 +- documentation/find-jsoapi.js | 65 +- documentation/find-jsoapi.min.js | 7 +- documentation/find-jsoapi.min.js.map | 2 +- documentation/find-jsrapi.js | 741 +- documentation/find-jsrapi.min.js | 21 +- documentation/find-jsrapi.min.js.map | 2 +- documentation/find-jsvapi.js | 887 +- documentation/find-jsvapi.min.js | 27 +- documentation/find-jsvapi.min.js.map | 2 +- documentation/find-samples.js | 160 +- documentation/find-samples.min.js | 12 +- documentation/find-samples.min.js.map | 2 +- download/jquery.observable.js | 705 +- download/jquery.observable.min.js | 4 +- download/jquery.observable.min.js.map | 2 +- download/jquery.views.js | 800 +- download/jquery.views.min.js | 6 +- download/jquery.views.min.js.map | 2 +- download/jsrender-node.js | 759 +- download/jsrender-node.min.js | 4 +- download/jsrender-node.min.js.map | 2 +- download/jsrender.js | 773 +- download/jsrender.min.js | 4 +- download/jsrender.min.js.map | 2 +- download/jsviews.js | 2255 +++-- download/jsviews.min.js | 8 +- download/jsviews.min.js.map | 2 +- .../areaslider/areaslider.js | 134 + .../areaslider/areaslider.min.js | 4 + .../areaslider/areaslider.min.js.map | 1 + .../colorpicker/colorpicker-multiformat.js | 246 + .../colorpicker-multiformat.min.js | 4 + .../colorpicker-multiformat.min.js.map | 1 + .../colorpicker/colorpicker-multiformat2.js | 251 + .../colorpicker-multiformat2.min.js | 4 + .../colorpicker-multiformat2.min.js.map | 1 + .../colorpicker/colorpicker.css | 19 + .../colorpicker/colorpicker.js | 199 + .../colorpicker/colorpicker.min.js | 4 + .../colorpicker/colorpicker.min.js.map | 1 + .../colorpicker/tinycolor.js | 1195 +++ .../colorpicker/tinycolor.min.js | 2 + .../colorpicker/tinycolor.min.js.map | 1 + download/sample-tag-controls/grid/grid.css | 57 + .../sample-tag-controls/jsonview/jsonview.css | 11 + .../sample-tag-controls/jsonview/jsonview.js | 109 +- .../jsonview/jsonview.min.js | 4 +- .../jsonview/jsonview.min.js.map | 2 +- .../jsviews-jqueryui-widgets.js | 248 +- .../jsviews-jqueryui-widgets.min.js | 4 +- .../jsviews-jqueryui-widgets.min.js.map | 2 +- .../multiselect/multiselect.js | 2 +- .../multiselect/multiselect.min.js.map | 2 +- download/sample-tag-controls/range/range.js | 58 +- .../sample-tag-controls/range/range.min.js | 4 +- .../range/range.min.js.map | 2 +- download/sample-tag-controls/slider/slider.js | 122 + .../sample-tag-controls/slider/slider.min.js | 4 + .../slider/slider.min.js.map | 1 + .../spinblock/spinblock.js | 38 + .../spinblock/spinblock.min.js | 4 + .../spinblock/spinblock.min.js.map | 1 + download/sample-tag-controls/tabs/tabs.css | 18 +- download/sample-tag-controls/tabs/tabs.js | 85 +- download/sample-tag-controls/tabs/tabs.min.js | 12 +- .../sample-tag-controls/tabs/tabs.min.js.map | 2 +- download/sample-tag-controls/tabs/tabs2.js | 56 + .../sample-tag-controls/tabs/tabs2.min.js | 5 + .../sample-tag-controls/tabs/tabs2.min.js.map | 1 + download/sample-tag-controls/tabs/tabs3.js | 75 + .../sample-tag-controls/tabs/tabs3.min.js | 10 + .../sample-tag-controls/tabs/tabs3.min.js.map | 1 + .../textbox/simple-textbox.js | 8 +- .../textbox/simple-textbox.min.js | 4 +- .../textbox/simple-textbox.min.js.map | 2 +- .../sample-tag-controls/treeview/tree-if.js | 2 +- .../treeview/tree-if.min.js.map | 2 +- .../treeview/tree-visible.js | 2 +- .../treeview/tree-visible.min.js.map | 2 +- .../sample-tag-controls/validate/validate.js | 4 +- .../validate/validate.min.js | 2 +- .../validate/validate.min.js.map | 2 +- download/tmplify/index.js | 2 +- gulpfile.js | 10 +- index.html | 6 +- index.js | 41 +- index.min.js | 2 +- index.min.js.map | 2 +- indexNotMinified.html | 8 +- indexNotMinified.js | 1558 ++++ indexNotMinified.min.js | 2 + indexNotMinified.min.js.map | 1 + package.json | 11 +- resources/tags/built-in-tags.js | 40 +- resources/tags/built-in-tags.min.js | 2 +- resources/tags/built-in-tags.min.js.map | 2 +- samples/change-log.css | 2 +- samples/computed/fullname/data.html | 2 +- samples/computed/fullname/helper.html | 2 +- samples/computed/fullname/prototype.html | 2 +- samples/computed/shopping-cart/tmpl.html | 2 +- samples/computed/shopping-cart/top-level.html | 2 +- samples/computed/team-manager/sample.html | 2 +- samples/data-link/10_linked-visibility.html | 4 +- samples/data-link/11_linked-hover.html | 2 +- samples/data-link/11b_linked-hover.html | 2 +- samples/data-link/11c_linked-hover.html | 2 +- samples/data-link/12_linked-css.html | 2 +- samples/data-link/13_linked-svg.html | 2 +- samples/data-link/1_if-tag-in-attribute.html | 2 +- .../data-link/2_mouse-events-in-template.html | 2 +- .../data-link/3_include-tag-in-attribute.html | 2 +- samples/data-link/4_linked-for-tag.html | 2 +- samples/data-link/5_linked-for-tag.html | 2 +- samples/data-link/6_linked-if-tag.html | 2 +- samples/data-link/7_link-to-class.html | 4 +- samples/data-link/8_toggle-class.html | 4 +- samples/data-link/9_linked-attributes.html | 2 +- samples/editable-data/compiled/sample.html | 2 +- .../editable-data/hash-dictionary/sample.html | 2 +- .../editable-data/linked-elems/sample.html | 2 +- samples/editable-data/linked-tags/sample.html | 2 +- samples/editable-data/observe/sample.html | 2 +- samples/editable-data/submit/sample.html | 2 +- .../editable-data/toplevel-for/sample.html | 2 +- samples/form-els/array-binding/sample.html | 2 +- samples/form-els/converters/day-to-int.html | 2 +- samples/form-els/converters/sample.html | 2 +- samples/form-els/simple/template.html | 4 +- samples/form-els/simple/top-level.html | 2 +- samples/form-els/visible-binding/sample.html | 2 +- samples/iframedefault.html | 11 +- samples/iframedefault_nocss.html | 9 +- samples/iframedefaultjqui.html | 13 +- samples/iframedefaultjqui_nocss.html | 11 +- samples/iframedefaultjsr.html | 11 +- samples/iframedefaultjsr_nocss.html | 9 +- .../composition/from-strings/sample.html | 2 +- .../composition/remote-tmpl/sample.html | 2 +- .../jsrender/composition/sub-tmpl/sample.css | 2 +- .../jsrender/composition/sub-tmpl/sample.html | 4 +- .../composition/tmpl-objects/sample.html | 2 +- samples/jsrender/composition/tmpl/sample.html | 2 +- samples/jsrender/converters/sample.html | 5 +- samples/jsrender/converters/sample.js | 9 +- samples/jsrender/helpers/sample.html | 10 +- samples/jsrender/helpers/sample.js | 30 +- samples/jsrender/paths/sample.html | 2 +- samples/jsrender/tags/extend-for/sample.css | 37 +- samples/jsrender/tags/extend-for/sample.html | 130 +- samples/jsrender/tags/extend-for/sample.js | 45 +- .../jsrender/tags/wrap-content/sample.html | 2 +- samples/samples.css | 43 +- samples/tag-controls/areaslider/sample.html | 27 + samples/tag-controls/areaslider/sample.js | 5 + .../colorpicker/colorpicker-multiformat.html | 73 + .../colorpicker/colorpicker-multiformat.js | 8 + .../colorpicker/colorpicker-multiformat2.html | 73 + .../colorpicker/colorpicker-multiformat2.js | 8 + .../tag-controls/colorpicker/colorpicker.html | 42 + .../tag-controls/colorpicker/colorpicker.js | 6 + .../jqui/accordion/collapsible.html | 2 +- .../tag-controls/jqui/accordion/sortable.html | 2 +- .../accordion/sortablearray-toplevel.html | 6 +- .../jqui/accordion/sortablearray.html | 8 +- .../jqui/autocomplete/variants.html | 7 +- .../jqui/datepicker/date-formats/basic.html | 2 +- .../jqui/datepicker/date-formats/custom.html | 2 +- .../jqui/datepicker/simple/sample.html | 2 +- .../jqui/datepicker/variants/sample.html | 2 +- .../with-validation-wizard/sample.html | 2 +- .../datepicker/with-validation/sample.html | 2 +- .../jqui/draggable-droppable/draggable.html | 2 +- .../jqui/draggable-droppable/draggable2.html | 2 +- .../draggable-droppable/photomanager.html | 6 +- samples/tag-controls/jqui/menu/menu.html | 2 +- .../jqui/progressbar/variants.html | 2 +- samples/tag-controls/jqui/resizable/grid.html | 2 +- .../tag-controls/jqui/resizable/grid2.html | 2 +- .../tag-controls/jqui/resizable/grid3.html | 2 +- .../selectable/sortablearray-toplevel.html | 6 +- .../jqui/selectable/sortablearray.html | 7 +- .../jqui/selectable/sortablearray.js | 7 +- .../jqui/selectmenu/product-selection.html | 2 +- .../jqui/slider/colorpicker/sample.html | 2 +- .../jqui/slider/simple-toplevel/sample.html | 2 +- .../jqui/slider/simple/sample.html | 2 +- .../jqui/slider/variants/sample.html | 2 +- .../jqui/slider/with-validation/sample.html | 4 +- .../tag-controls/jqui/sortable/variants.html | 2 +- .../tag-controls/jqui/spinner/accounting.html | 2 +- .../tag-controls/jqui/spinner/accounting.js | 38 +- .../tag-controls/jqui/spinner/dataformat.html | 4 +- .../tag-controls/jqui/spinner/dataformat.js | 31 +- .../tag-controls/jqui/spinner/globalize.html | 2 +- samples/tag-controls/jqui/spinner/moment.html | 2 +- samples/tag-controls/jqui/spinner/moment.js | 11 +- samples/tag-controls/jqui/spinner/sample.html | 2 +- .../tag-controls/jqui/tabs/collapsible.html | 2 +- samples/tag-controls/jqui/tabs/sortable.html | 2 +- .../jqui/tabs/sortablearray-toplevel.html | 6 +- .../tag-controls/jqui/tabs/sortablearray.html | 6 +- .../jqui/timespinner/dataformat.html | 2 +- .../jqui/timespinner/dataformat.js | 18 +- .../jqui/timespinner/globalize.html | 2 +- .../tag-controls/jqui/timespinner/moment.html | 2 +- .../tag-controls/jqui/timespinner/moment.js | 11 +- .../tag-controls/jqui/toolbar/toolbar.html | 2 +- .../jqui/toolbar/toolbararray.html | 15 +- samples/tag-controls/jsonview/sample.html | 4 +- samples/tag-controls/multiselect/sample.html | 2 +- .../tag-controls/purchases/images/down.png | Bin 0 -> 261 bytes samples/tag-controls/purchases/images/up.png | Bin 0 -> 259 bytes .../tag-controls/purchases/sample-jsr.html | 97 + samples/tag-controls/purchases/sample-jsr.js | 40 + .../tag-controls/purchases/sample-jsv.html | 47 + samples/tag-controls/purchases/sample-jsv.js | 62 + samples/tag-controls/range/sample.css | 55 - samples/tag-controls/range/sample.html | 63 - samples/tag-controls/range/sample.js | 27 - .../tag-controls/simple-textbox/sample.html | 2 +- samples/tag-controls/slider/sample.html | 25 + samples/tag-controls/slider/sample.js | 5 + samples/tag-controls/spinblock/sample.html | 62 + samples/tag-controls/spinblock/sample.js | 20 + samples/tag-controls/tabs/sample.html | 55 +- samples/tag-controls/tabs/sample.js | 38 +- .../tag-controls/tree/editable/sample.html | 6 +- .../tag-controls/tree/if-binding/sample.html | 2 +- .../tree/visible-binding/sample.html | 2 +- .../validate/array-binding/sample.html | 6 +- .../tag-controls/validate/simple/sample.html | 2 +- .../validate/validation-group/sample.html | 2 +- test/browserify/10-errors-unit-tests.js | 2 +- test/browserify/bundles/1-bundle.js | 773 +- test/browserify/bundles/10-errors-bundle.js | 2274 +++-- test/browserify/bundles/11-errors-bundle.js | 3838 ++++++--- test/browserify/bundles/12-nested-bundle.js | 773 +- test/browserify/bundles/2-bundle.js | 769 +- test/browserify/bundles/3-bundle.js | 769 +- test/browserify/bundles/4-bundle.js | 701 +- test/browserify/bundles/5-bundle.js | 701 +- test/browserify/bundles/6-bundle.js | 2272 +++-- test/browserify/bundles/7-bundle.js | 2272 +++-- test/browserify/bundles/8-bundle.js | 2255 +++-- test/browserify/bundles/8B-bundle.js | 3030 ++++--- test/browserify/bundles/9-bundle.js | 2255 +++-- .../bundles/htm-jsrender-tmpl-bundle.js | 771 +- .../bundles/html-jsr-tmpl-bundle.js | 771 +- test/index.html | 3 +- test/unit-tests-all-jsviews-jq1.html | 56 + ...3.html => unit-tests-all-jsviews-jq2.html} | 2 +- test/unit-tests-all-jsviews-slim.html | 2 +- test/unit-tests-all-jsviews.html | 3 +- ...nit-tests-all-observable-render-views.html | 2 +- ...nit-tests-all-render-observable-views.html | 2 +- test/unit-tests-amd-scriptloader.html | 2 +- test/unit-tests-browserify.html | 2 +- ...nit-tests-jsobservable-no-jqueryviews.html | 2 +- test/unit-tests-jsrender-no-jquery.html | 2 +- test/unit-tests-jsrender-with-jquery.html | 2 +- test/unit-tests-jsviews.html | 2 +- test/unit-tests-multiple-loads.html | 2 +- test/unit-tests/requirejs-config.js | 2 +- test/unit-tests/tests-jsobservable.js | 582 +- test/unit-tests/tests-jsrender-no-jquery.js | 218 +- test/unit-tests/tests-jsrender-with-jquery.js | 9 +- test/unit-tests/tests-jsviews.js | 2763 +++++- test/unit-tests/tests-node.js | 4 +- 313 files changed, 38835 insertions(+), 19945 deletions(-) create mode 100644 download/sample-tag-controls/areaslider/areaslider.js create mode 100644 download/sample-tag-controls/areaslider/areaslider.min.js create mode 100644 download/sample-tag-controls/areaslider/areaslider.min.js.map create mode 100644 download/sample-tag-controls/colorpicker/colorpicker-multiformat.js create mode 100644 download/sample-tag-controls/colorpicker/colorpicker-multiformat.min.js create mode 100644 download/sample-tag-controls/colorpicker/colorpicker-multiformat.min.js.map create mode 100644 download/sample-tag-controls/colorpicker/colorpicker-multiformat2.js create mode 100644 download/sample-tag-controls/colorpicker/colorpicker-multiformat2.min.js create mode 100644 download/sample-tag-controls/colorpicker/colorpicker-multiformat2.min.js.map create mode 100644 download/sample-tag-controls/colorpicker/colorpicker.css create mode 100644 download/sample-tag-controls/colorpicker/colorpicker.js create mode 100644 download/sample-tag-controls/colorpicker/colorpicker.min.js create mode 100644 download/sample-tag-controls/colorpicker/colorpicker.min.js.map create mode 100644 download/sample-tag-controls/colorpicker/tinycolor.js create mode 100644 download/sample-tag-controls/colorpicker/tinycolor.min.js create mode 100644 download/sample-tag-controls/colorpicker/tinycolor.min.js.map create mode 100644 download/sample-tag-controls/grid/grid.css create mode 100644 download/sample-tag-controls/slider/slider.js create mode 100644 download/sample-tag-controls/slider/slider.min.js create mode 100644 download/sample-tag-controls/slider/slider.min.js.map create mode 100644 download/sample-tag-controls/spinblock/spinblock.js create mode 100644 download/sample-tag-controls/spinblock/spinblock.min.js create mode 100644 download/sample-tag-controls/spinblock/spinblock.min.js.map create mode 100644 download/sample-tag-controls/tabs/tabs2.js create mode 100644 download/sample-tag-controls/tabs/tabs2.min.js create mode 100644 download/sample-tag-controls/tabs/tabs2.min.js.map create mode 100644 download/sample-tag-controls/tabs/tabs3.js create mode 100644 download/sample-tag-controls/tabs/tabs3.min.js create mode 100644 download/sample-tag-controls/tabs/tabs3.min.js.map create mode 100644 indexNotMinified.js create mode 100644 indexNotMinified.min.js create mode 100644 indexNotMinified.min.js.map create mode 100644 samples/tag-controls/areaslider/sample.html create mode 100644 samples/tag-controls/areaslider/sample.js create mode 100644 samples/tag-controls/colorpicker/colorpicker-multiformat.html create mode 100644 samples/tag-controls/colorpicker/colorpicker-multiformat.js create mode 100644 samples/tag-controls/colorpicker/colorpicker-multiformat2.html create mode 100644 samples/tag-controls/colorpicker/colorpicker-multiformat2.js create mode 100644 samples/tag-controls/colorpicker/colorpicker.html create mode 100644 samples/tag-controls/colorpicker/colorpicker.js create mode 100644 samples/tag-controls/purchases/images/down.png create mode 100644 samples/tag-controls/purchases/images/up.png create mode 100644 samples/tag-controls/purchases/sample-jsr.html create mode 100644 samples/tag-controls/purchases/sample-jsr.js create mode 100644 samples/tag-controls/purchases/sample-jsv.html create mode 100644 samples/tag-controls/purchases/sample-jsv.js delete mode 100644 samples/tag-controls/range/sample.css delete mode 100644 samples/tag-controls/range/sample.html delete mode 100644 samples/tag-controls/range/sample.js create mode 100644 samples/tag-controls/slider/sample.html create mode 100644 samples/tag-controls/slider/sample.js create mode 100644 samples/tag-controls/spinblock/sample.html create mode 100644 samples/tag-controls/spinblock/sample.js create mode 100644 test/unit-tests-all-jsviews-jq1.html rename test/{unit-tests-all-jsviews-jq3.html => unit-tests-all-jsviews-jq2.html} (97%) diff --git a/.gitignore b/.gitignore index c509399d..6a0b076b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ node_modules bower_components *.config Scripts --* \ No newline at end of file +-* +bin \ No newline at end of file diff --git a/_src/jquery.observable.js b/_src/jquery.observable.js index a29230f8..2e91e6b2 100644 --- a/_src/jquery.observable.js +++ b/_src/jquery.observable.js @@ -6,6 +6,7 @@ if (!$.observe) { concat = [].concat, PARSEINT = parseInt, rNotWhite = /\S+/g, + rShallowPath = /^[^.[]*$/, // No '.' or '[' in path propertyChangeStr = $sub.propChng = $sub.propChng || "propertyChange",// These two settings can be overridden on settings after loading arrayChangeStr = $sub.arrChng = $sub.arrChng || "arrayChange", // jsRender, and prior to loading jquery.observable.js and/or JsViews cbBindingsStore = {}, @@ -13,14 +14,15 @@ if (!$.observe) { observeObjKey = 1, observeCbKey = 1, observeInnerCbKey = 1, - $hasData = $.hasData, $data = $.data, remove = {}, // flag for removeProperty //========================== Top-level functions ========================== getCbKey = function(cb) { - return cb._cId = cb._cId || (".obs" + observeCbKey++); + return cb + ? (cb._cId = cb._cId || (".obs" + observeCbKey++)) + : ""; }, ObjectObservable = function(ns, data) { @@ -63,7 +65,7 @@ if (!$.observe) { out = out.concat(dependsPaths(path.call(root, rt, callback), rt, callback)); continue; } else if ("" + path !== path) { - root = nextObj = path; + root = nextObj = path = (path === undefined ? null : path); if (nextObj !== object) { out.push(object = nextObj); } @@ -75,7 +77,8 @@ if (!$.observe) { out.push(path); } if (out.length) { - out.unshift({_ar: 1}); // Switch on allowArray, for depends paths. + // Switch on allowArray, for depends paths, by passing {_ar: ...} objects to switch on allowArray then return to contextual allowArray value + out.unshift({_ar: 1}); out.push({_ar: -1}); } return out; @@ -91,7 +94,7 @@ if (!$.observe) { onDataChange = function(ev, eventArgs) { function isOb(val) { - return typeof val === OBJECT && (paths[0] || allowArray && $isArray(val)); + return typeof val === OBJECT && (paths[0] || !noArray && $isArray(val)); } if (!(ev.data && ev.data.off)) { @@ -102,7 +105,7 @@ if (!$.observe) { ctx = ev.data, observeAll = ctx.observeAll, cb = ctx.cb, - allowArray = ctx.arOk, + noArray = ctx.arOk ? 0 : 1, paths = ctx.paths, ns = ctx.ns; @@ -123,10 +126,10 @@ if (!$.observe) { } } else { if (isOb(oldValue)) { // oldValue is an object, so unobserve - observe_apply(undefined, ns, [oldValue], paths, cb, true); // unobserve + observe_apply(noArray, ns, [oldValue], paths, cb, true); // unobserve. Observe array change events too if this change is not from an 'observeAndBind' tag binding, or is from a 'depends' path } if (isOb(value)) { // value is an object, so observe - observe_apply(undefined, ns, [value], paths, cb); + observe_apply(noArray, ns, [value], paths, cb); // observe. Observe array change events too if this change is not from an 'observeAndBind' tag binding, or is from a 'depends' path } } ctx.cb(ev, eventArgs); @@ -159,15 +162,13 @@ if (!$.observe) { function filterAndObserveAll(obj, prop, unobs, nestedArray) { var newObject, newParentObs; - if (prop !== $expando) { - if (newObject = $observable._fltr(newAllPath, obj[prop], nextParentObs, filter)) { - newParentObs = nextParentObs.slice(); - if (nestedArray && updatedTgt && newParentObs[0] !== updatedTgt) { - newParentObs.unshift(updatedTgt); // For array change events when observing an array which is not the root, need to add updated array to parentObs - } - observeAll(namespace, newObject, cb, filter || (nestedArray ? undefined : 0), newParentObs, newAllPath, unobs, objMap); - // If nested array, need to observe the array too - so set filter to undefined + if (prop !== $expando && (newObject = $observable._fltr(newAllPath, obj[prop], nextParentObs, filter))) { + newParentObs = nextParentObs.slice(); + if (nestedArray && updatedTgt && newParentObs[0] !== updatedTgt) { + newParentObs.unshift(updatedTgt); // For array change events when observing an array which is not the root, need to add updated array to parentObs } + observeAll(namespace, newObject, cb, filter || (nestedArray ? undefined : 0), newParentObs, newAllPath, unobs, objMap); + // If nested array, need to observe the array too - so set filter to undefined } } @@ -198,7 +199,7 @@ if (!$.observe) { if (object && typeof object === OBJECT) { nextParentObs = [object].concat(parentObs); // The parentObs chain for the next depth of observeAll isObject = $isArray(object) ? "" : "*"; - if (objMap && notRemoving && $hasData(object) && objMap[obId = $data(object).obId]) { + if (objMap && notRemoving && $.hasData(object) && objMap[obId = $data(object).obId]) { objMap[obId]++; return; // This object has already being observed/unobserved by this observeAll/unobserveAll call (must be a cyclic object graph) so skip, to avoid // stack overflow/multiple instances of listener. See jsviews/pull/305 @@ -252,8 +253,8 @@ if (!$.observe) { } }, - shallowFilter = function(allPath /*, object, parentObs*/) { - return allPath.indexOf(".") < 0 && allPath.indexOf("[") < 0; + shallowFilter = function(path /*, object, parentObs*/) { + return rShallowPath.test(path); // No '.' and no '[' in path }, $unobserve = function() { @@ -265,10 +266,23 @@ if (!$.observe) { // $.observe([namespace, ]root, [1 or more objects, path or path Array params...], callback[, contextCallback][, unobserve]) function innerObserve() { + var p, parts, unobserve, callback, cbId, inId, data, contextCb, items, cbBindings, + innerCb, parentObs, allPath, filter, initNsArr, initNsArrLen, view, prop, events, el; + + function unobserveBinding(cb, binding) { + var object; + for (data in binding) { + object = binding[data]; + if ($isArray(object)) { + bindArray(cb, object, unobserve, unobserve); + } else { + observeOnOff(cb, object, undefined, ns, ""); + } + } + } - function observeOnOff(namespace, pathStr, isArrayBinding, off) { + function observeOnOff(cb, object, fullPath, namespace, pathStr, isArrayBinding, off) { var j, evData, - obIdExpando = $hasData(object), boundObOrArr = wrapArray(object), prntObs = parentObs, allPth = allPath; @@ -276,15 +290,14 @@ if (!$.observe) { namespace = initialNs ? namespace + "." + initialNs : namespace; if (!unobserve && (off || isArrayBinding)) { - events = obIdExpando && $._data(object); - events = events && events.events; + events = $._data(object).events; events = events && events[isArrayBinding ? arrayChangeStr : propertyChangeStr]; el = events && events.length; while (el--) { // Skip duplicates data = events[el] && events[el].data; if (data && (off && data.ns !== initialNs // When observing, don't unbind dups unless they have the same namespace - || !off && data.ns === initialNs && data.cb && data.cb._cId === callback._cId && (!callback._wrp || data.cb._wrp))) + || !off && data.ns === initialNs && data.cb && data.cb._cId === cb._cId && (!cb._wrp || data.cb._wrp))) // When observing and doing array binding, don't bind dups if they have the same namespace (Dups can happen e.g. with {^{for people ^~foo=people}}) { return; @@ -296,13 +309,13 @@ if (!$.observe) { } else { evData = isArrayBinding ? {} : { - fullPath: path, + fullPath: fullPath, paths: pathStr ? [pathStr] : [], prop: prop, arOk: allowArray }; evData.ns = initialNs; - evData.cb = callback; + evData.cb = cb; if (allPath) { // This is an observeAll call @@ -332,48 +345,11 @@ if (!$.observe) { } } - function getInnerCb(exprOb) { - // Returns the innerCb used for updating a computed in a compiled expression (setting the new instance as exprOb.ob, unobserving the previous object, - // and observing the new one), then calling the outerCB - i.e. the handler for the whole compiled expression. - // Initialized exprOb.ob to the current object. - // Uses the contextCb callback to execute the compiled exprOb template in the context of the view/data etc. to get the returned value, typically an object or array. - // If it is an array, registers array binding - var origRt = root; - // Note: For jsviews/issues/292 ctxCb will need var ctxCb = contextCb || function(exprOb, origRt) {return exprOb._cpfn(origRt);}; - - exprOb.ob = contextCb(exprOb, origRt); // Initialize object - - return exprOb.cb = function(ev, eventArgs) { - var obj = exprOb.ob, // The old object - sub = exprOb.sb, - newObj = contextCb(exprOb, origRt); - - if (newObj !== obj) { - if (typeof obj === OBJECT) { - bindArray(obj, true); - if (sub || allowArray && $isArray(obj)) { - innerObserve([obj], sub, callback, contextCb, true); // unobserve on the old object - } - } - exprOb.ob = newObj; - // Put the updated object instance onto the exprOb in the paths array, so subsequent string paths are relative to this object - if (typeof newObj === OBJECT) { - bindArray(newObj); - if (sub || allowArray && $isArray(newObj)) { // observe on new object - innerObserve([newObj], sub, callback, contextCb); - } - } - } - // Call the outerCb - to execute the compiled expression that this computed is part of - callback(ev, eventArgs); - }; - } - - function bindArray(arr, unbind, isArray, relPath) { + function bindArray(cb, arr, unbind, isArray, relPath) { if (allowArray) { // allowArray is 1 if this is a call to observe that does not come from observeAndBind (tag binding), or is from a 'depends' path, // so we allow arrayChange binding. Otherwise allowArray is zero. - var prevObj = object, + var object, prevAllPath = allPath; object = arr; @@ -385,186 +361,84 @@ if (!$.observe) { object = $observable._fltr(allPath, object, relPath ? [arr].concat(parentObs) : parentObs, filter); } if (object && (isArray || $isArray(object))) { - observeOnOff(arrayChangeStr + ".observe" + (callback ? getCbKey(callback) : ""), undefined, true, unbind); + observeOnOff(cb, object, undefined, arrayChangeStr + ".observe" + getCbKey(cb), undefined, true, unbind); } - object = prevObj; allPath = prevAllPath; } } - var i, p, skip, parts, prop, path, dep, unobserve, callback, cbId, inId, el, data, events, contextCb, innerContextCb, - items, cbBindings, depth, innerCb, parentObs, allPath, filter, initNsArr, initNsArrLen, view, cbItemCount, - ns = observeStr, - paths = this != 1 // Using != for IE<10 bug- see jsviews/issues/237 - ? concat.apply([], arguments) // Flatten the arguments - this is a 'recursive call' with params using the 'wrapped array' - // style - such as innerObserve([object], path.path, [origRoot], path.prm, innerCb, ...); - : slice.call(arguments), // Don't flatten - this is the first 'top-level call, to innerObserve.apply(1, paths) - lastArg = paths.pop() || false, - root = paths.shift(), - object = root, - l = paths.length; - - if (lastArg + "" === lastArg) { // If last arg is a string then this observe call is part of an observeAll call, - allPath = lastArg; // and the last three args are the parentObs array, the filter, and the allPath string. - parentObs = paths.pop(); - filter = paths.pop(); - lastArg = !!paths.pop(); // unobserve - l -= 3; - } - if (lastArg === !!lastArg) { - unobserve = lastArg; - lastArg = paths[l-1]; - lastArg = l && lastArg + "" !== lastArg && (!lastArg || $isFunction(lastArg)) ? (l--, paths.pop()) : undefined; - if (unobserve && !l && $isFunction(root)) { - lastArg = root; - root = undefined; - } - } - callback = lastArg; - if (l && $isFunction(paths[l - 1])) { - innerContextCb = contextCb = callback; - callback = paths.pop(); - l--; - } - - if (unobserve && callback && !callback._cId) { - return; - } - - // Use a unique namespace (e.g. obs7) associated with each observe() callback to allow unobserve to remove handlers - ns += callback - ? ((inId = callback._inId || ""), unobserve) - ? callback._cId + inId - : (cbId = getCbKey(callback)) + inId - : ""; - - if (cbId && !unobserve) { - cbBindings = cbBindingsStore[cbId] = cbBindingsStore[cbId] || {}; - } + function observeObjects(paths) { + + function observeObjectPaths(object, pths, callback, contextCb) { + + function getInnerCb(exprOb) { + exprOb.ob = contextCb(exprOb, object); // Initialize object + return exprOb.cb = function(ev, eventArgs) { + // The innerCb used for updating a computed in a compiled expression (setting the new instance as exprOb.ob, unobserving the previous object, + // and observing the new one), then calling the outerCB - i.e. the handler for the whole compiled expression. + // Initialized exprOb.ob to the current object. + // Uses the contextCb callback to execute the compiled exprOb template in the context of the view/data etc. to get the returned value, typically an object or array. + // If it is an array, registers array binding + // Note: For jsviews/issues/292 ctxCb will need var ctxCb = contextCb || function(exprOb, origRt) {return exprOb._cpfn(origRt);}; + var obj = exprOb.ob, // The old object + sub = exprOb.sb, + newObj = contextCb(exprOb, object); + + if (newObj !== obj) { + if (typeof obj === OBJECT) { + bindArray(callback, obj, true); + if (sub || allowArray && $isArray(obj)) { + innerObserve([obj], sub, callback, contextCb, true); // unobserve on the old object + } + } + exprOb.ob = newObj; + // Put the updated object instance onto the exprOb in the paths array, so subsequent string paths are relative to this object + if (typeof newObj === OBJECT) { + bindArray(callback, newObj); + if (sub || allowArray && $isArray(newObj)) { // observe on new object + innerObserve([newObj], sub, callback, contextCb); + } + } + } + // Call the outerCb - to execute the compiled expression that this computed is part of + callback(ev, eventArgs); + }; + } - initNsArr = initialNs && initialNs.match(rNotWhite) || [""]; - initNsArrLen = initNsArr.length; + function observePath(object, prts) { // Step through the path parts "this.is^some.path" and observe changes (on the leaf, or down to the bound depth) - while (initNsArrLen--) { - initialNs = initNsArr[initNsArrLen]; - if (root && !paths[0]) { - if ($isArray(root)) { - bindArray(root, unobserve, true); // observe(array, handler) - } - if (unobserve) { - observeOnOff(ns, ""); // unobserve(objectOrArray[, handler]) - } - } - if (unobserve && !l && !root) { // unobserve() - unobserves all - for (p in cbBindingsStore) { - p = cbBindingsStore[p]; - for (data in p) { - object = p[data]; - if ($isArray(object)) { - bindArray(object, unobserve, unobserve); - } else { - observeOnOff(ns, ""); + function obArrAddRemove(ev, eventArgs) { + // If a "[].*" or "[].prop" wild card path (for observing properties of array items) we need to observe or unobserve added or removed items + var l; + if (eventArgs.change === "insert" || (unobserve = eventArgs.change === "remove")) { + l = eventArgs.items.length; + while (l--) { + observePath(eventArgs.items[l], prts.slice()); + } + unobserve = false; } } - } - } - depth = 0; - cbItemCount = 0; - for (i = 0; i < l; i++) { - if (cbItemCount) { - cbItemCount--; // contextCb was moved to a contextual parameter outer context. Needs to revert after cbItemCount - } else { - contextCb = innerContextCb; - } - path = paths[i]; - if (path === "" || path === root) { - continue; - } - if (path && path._ar) { - allowArray += path._ar; // Switch on allowArray for depends paths, and off, afterwards. - continue; - } - object = root; - if ("" + path === path) { - // Consider support for computed paths: jsviews/issues/292 - //if (/[\(\[\+]/.test(path)) { - // var b={links:{}}, t = $sub.tmplFn("{:"+path+"}", b, true), items = t.paths[0]; - // l += items.length - 1; - // splice.apply(paths, [i--, 1].concat(items)); - // continue; - //} - parts = path.split("^"); - if (parts[1]) { - // We bind the leaf, plus additional nodes based on depth. - // "a.b.c^d.e" is depth 2, so listens to changes of e, plus changes of d and of c - depth = parts[0].split(".").length; - path = parts.join("."); - depth = path.split(".").length - depth; - // if more than one ^ in the path, the first one determines depth - } - if (contextCb) { - items = contextCb(path, root, depth); + //prts = $isArray(prts) ? prts : [prts]; + if (callback) { + obArrAddRemove._cId = getCbKey(callback); // Identify wrapped callback with unwrapped callback, so unobserveAll will + // remove previous observeAll wrapped callback, if inner callback was the same; } - contextCb = innerContextCb; - parts = path.split("."); - } else if (path && path._cxp) { // contextual parameter - view = path.shift(); // Contextual data - if (_ocp in view) { - root = view; // observable contextual parameter - contextCb = 0; - } else { - contextCb = $sub._gccb(view); // getContextCb: Get context callback for the contextual view (where contextual param evaluated/assigned) - root = view.data; - } - items = path; - items.push(origRoot); - cbItemCount = items.length; - } else { - if (!$isFunction(path)) { - if (path && path._cpfn) { - // Path is an exprOb returned by a computed property - helper/data function (compiled expr function). - // Set current object on exprOb.ob, and get innerCb for updating the object - innerCb = unobserve ? path.cb : getInnerCb(path); - // innerCb._ctx = callback._ctx; Could pass context (e.g. linkCtx) for use in a depends = function() {} call, so depends is different for different linkCtx's - innerCb._cId = callback._cId; - // Set the same cbBindingsStore key as for callback, so when callback is disposed, disposal of innerCb happens too. - innerCb._inId = innerCb._inId || ".obIn" + observeInnerCbKey++; - if (path.bnd || path.prm && path.prm.length || !path.sb) { - // If the exprOb is bound e.g. foo()^sub.path, or has parameters e.g. foo(bar) or is a leaf object (so no sub path) e.g. foo() - // then observe changes on the object, or its parameters and sub-path - innerObserve([object], path.path, [path.root||root], path.prm, innerCb, contextCb, unobserve); - } - if (path.sb) { // subPath - if (path.sb.prm) { - path.sb.root = root; - } - innerObserve([path.ob], path.sb, callback, contextCb, unobserve); - } - path = origRoot; - object = undefined; - } + + var arrIndex, skip, dep, obArr, prt, + obj = object; + if (object && object._cxp) { + return observeObjectPaths(object[0], [object[1]], callback, contextCb); } - parts = [root = path]; - } - if (items) { - // If the array of objects and paths returned by contextCb is non empty, insert them - // into the sequence, replacing the current item (path). Otherwise simply remove current item (path) - l += items.length - 1; - splice.apply(paths, [i--, 1].concat(items)); - items = undefined; - continue; - } - while (object && (prop = parts.shift()) !== undefined) { - if (typeof object === OBJECT) { - if ("" + prop === prop) { + + while ((prop = prts.shift()) !== undefined) { + if (obj && typeof obj === OBJECT && "" + prop === prop) { if (prop === "") { continue; } - if ((parts.length < depth + 1) && !object.nodeType) { + if ((prts.length < depth + 1) && !obj.nodeType) { // Add observer for each token in path starting at depth, and on to the leaf - if (!unobserve && (events = $hasData(object) && $._data(object))) { - events = events.events; + if (!unobserve && (events = $._data(obj).events)) { events = events && events[propertyChangeStr]; el = events && events.length; skip = 0; @@ -575,60 +449,254 @@ if (!$.observe) { && data.cb._cId === callback._cId && data.cb._inId === callback._inId && (data.prop === prop || data.prop === "*" || data.prop === "**")) { - if (p = parts.join(".")) { - data.paths.push(p); // We will skip this binding, but if it is not a leaf binding, - // need to keep bindings for rest of path, ready for if the object gets swapped. + if (prt = prts.join(".")) { + data.paths.push(prt); // We will skip this binding, but if it is not a leaf binding, + // need to keep bindings for rest of path, ready for if the obj gets swapped. } skip++; } } if (skip) { // Duplicate binding(s) found, so move on - object = object[prop]; + obj = obj[prop]; continue; } } if (prop === "*" || prop === "**") { // "*" => all properties. "**" => all properties and sub-properties (i.e. deep observeAll behavior) if (!unobserve && events && events.length) { // Remove existing bindings, since they will be duplicates with "*" or "**" - observeOnOff(ns, "", false, true); + observeOnOff(callback, obj, path, ns, "", false, true); } if (prop === "*") { - observeOnOff(ns, ""); // observe the object for any property change - for (p in object) { + observeOnOff(callback, obj, path, ns, ""); // observe the object for any property change + for (prt in obj) { // observing "*": So (in addition to listening to prop change, above) listen to arraychange on props of type array - if (p !== $expando) { - bindArray(object, unobserve, undefined, p); + if (prt !== $expando) { + bindArray(callback, obj, unobserve, undefined, prt); } } } else { - $.observable(initialNs, object)[(unobserve ? "un" : "") + "observeAll"](callback); // observe or unobserve the object for any property change + $.observable(initialNs, obj)[(unobserve ? "un" : "") + "observeAll"](callback); // observe or unobserve the object for any property change } break; + } else if (prop == "[]") { // "[].*" or "[].prop" wild card path, for observing properties of array items + if ($isArray(obj)) { + if (unobserve) { + observeOnOff(callback, obj, path, arrayChangeStr + getCbKey(callback), undefined, unobserve, unobserve); + } else { + $observe(initialNs, obj, obArrAddRemove, unobserve); // observe or unobserve added or removed items + } + } } else if (prop) { - observeOnOff(ns + ".p_" + prop, parts.join("^")); // By using "^" rather than "." we ensure that deep binding will be used on newly inserted object graphs + observeOnOff(callback, obj, path, ns + ".p_" + prop, prts.join("^")); // By using "^" rather than "." we ensure that deep binding will be used on newly inserted object graphs } } if (allPath) { allPath += "." + prop; } - prop = object[prop]; - if (!parts[0]) { - bindArray(prop, unobserve); // [un]observe(object, "arrayProperty") observes array changes on property of type array + if (prop === "[]") { + if ($isArray(obj)) { + obArr = obj; + arrIndex = obj.length; + } + while (arrIndex--) { + obj = obArr[arrIndex]; + observePath(obj, prts.slice()); + } + return; + } + prop = obj[prop]; + if (!prts[0]) { + bindArray(callback, prop, unobserve); // [un]observe(object, "arrayProperty") observes array changes on property of type array } } if ($isFunction(prop)) { if (dep = prop.depends) { // This is a computed observable. We will observe any declared dependencies. - // Pass {_ar: ...} objects to switch on allowArray, for depends paths, then return to contextual allowArray value - innerObserve([object], dependsPaths(dep, object, callback), callback, contextCb, unobserve); + if (obj._vw && obj._ocp) { + // Observable contextual parameter, so context was ocp object. Now move context to view.data for dependencies + obj = obj._vw; // storeView or tag (scope of contextual parameter) + if (obj._tgId) { + // Is a tag, so get view + obj = obj.tagCtx.view; + } + obj = obj.data; // view.data + } + observeObjects(concat.apply([], [[obj], dependsPaths(dep, obj, callback)])); } break; } - object = prop; + obj = prop; + } + } + + var i, path, + depth = 0, + l = pths.length; + if (object && !contextCb && ((view = object._is === "view") || object._is === "tag")) { + contextCb = $sub._gccb(view ? object : object.tagCtx.contentView); + if (callback && !unobserve) { + (function() { + var ob = object, + cb = callback; + callback = function(ev, eventArgs) { + // Wrapped callback so this pointer is tag or view + cb.call(ob, ev, eventArgs); + }; + callback._cId = cb._cId; + callback._inId = cb._inId; + })(); + } + object = view ? object.data : object; + } + if (!pths[0]) { + if ($isArray(object)) { + bindArray(callback, object, unobserve, true); // observe(array, handler) + } else if (unobserve) { + observeOnOff(callback, object, undefined, ns, ""); // unobserve(objectOrArray[, handler]) + } + } + for (i = 0; i < l; i++) { // Step through objects and paths + path = pths[i]; + if (path === "") { + continue; + } + if (path && path._ar) { + allowArray += path._ar; // Switch on allowArray for depends paths, and off, afterwards. + continue; + } + if ("" + path === path) { + parts = path.split("^"); + if (parts[1]) { + // We bind the leaf, plus additional nodes based on depth. + // "a.b.c^d.e" is depth 2, so listens to changes of e, plus changes of d and of c + depth = parts[0].split(".").length; + path = parts.join("."); + depth = path.split(".").length - depth; + // if more than one ^ in the path, the first one determines depth + } + if (contextCb && (items = contextCb(path, object, depth))) { + //object, paths + if (items.length) { + var ob = items[0], + pth = items[1]; + if (ob && ob._cxp) { // contextual parameter + pth = ob[1]; + ob = ob[0]; + if (ob._is === "view") { + observeObjectPaths(ob, [pth], callback); // Setting contextCb to undefined, to use passed in view for new contextCb + continue; + } + } + if (pth + "" === pth) { + observePath(ob, pth.split(".")); + } else { + observeObjectPaths(items.shift(), items, callback, contextCb); + } + } + } else { + observePath(object, path.split(".")); + } + } else if (!$isFunction(path) && path && path._cpfn) { + // Path is an exprOb returned by a computed property - helper/data function (compiled expr function). + // Get innerCb for updating the object + innerCb = unobserve ? path.cb : getInnerCb(path); + // innerCb._ctx = callback._ctx; Could pass context (e.g. linkCtx) for use in a depends = function() {} call, so depends is different for different linkCtx's + innerCb._cId = callback._cId; + // Set the same cbBindingsStore key as for callback, so when callback is disposed, disposal of innerCb happens too. + innerCb._inId = innerCb._inId || ".obIn" + observeInnerCbKey++; + if (path.bnd || path.prm && path.prm.length || !path.sb) { + // If the exprOb is bound e.g. foo()^sub.path, or has parameters e.g. foo(bar) or is a leaf object (so no sub path) e.g. foo() + // then observe changes on the object, or its parameters and sub-path + innerObserve([object], path.path, (path.prm.length ? [path.root||object] : []), path.prm, innerCb, contextCb, unobserve); + } + if (path.sb) { // Has a subPath + // Observe changes on the sub-path + if (path.sb.prm) { + path.sb.root = object; + } + // Set current object on exprOb.ob + observeObjectPaths(path.ob, [path.sb], callback, contextCb); + } + } + } + } + + var pth, + pths = [], // Array of paths for current object + l = paths.length; + while (l--) { // Step backwards through paths and objects + pth = paths[l]; + if (pth + "" === pth || pth && (pth._ar || pth._cpfn)) { + pths.unshift(pth); // This is a path so add to arr + } else { // This is an object + observeObjectPaths(pth, pths, callback, contextCb); + pths = []; // New array for next object + } + } + } + + var ns = observeStr, + paths = this != 1 // Using != for IE<10 bug- see jsviews/issues/237 + ? concat.apply([], arguments) // Flatten the arguments - this is a 'recursive call' with params using the 'wrapped array' + // style - such as innerObserve([object], path.path, [origRoot], path.prm, innerCb, ...); + : slice.call(arguments), // Don't flatten - this is the first 'top-level call, to innerObserve.apply(1, paths) + lastArg = paths.pop() || false, + m = paths.length; + +//END OF FUNCTIONS + if (lastArg + "" === lastArg) { // If last arg is a string then this observe call is part of an observeAll call, + allPath = lastArg; // and the last three args are the parentObs array, the filter, and the allPath string. + parentObs = paths.pop(); + filter = paths.pop(); + lastArg = !!paths.pop(); // unobserve + m -= 3; + } + if (lastArg === !!lastArg) { + unobserve = lastArg; + lastArg = paths[m-1]; + lastArg = m && lastArg + "" !== lastArg && (!lastArg || $isFunction(lastArg)) ? (m--, paths.pop()) : undefined; + if (unobserve && !m && $isFunction(paths[0])) { + lastArg = paths.shift(); + } + } + callback = lastArg; + if (m && $isFunction(paths[m - 1])) { + contextCb = callback; + lastArg = callback = paths.pop(); + m--; + } + + if (unobserve && callback && !callback._cId) { + return; + } + + // Use a unique namespace (e.g. obs7) associated with each observe() callback to allow unobserve to remove handlers + ns += callback + ? ((inId = callback._inId || ""), unobserve) + ? callback._cId + inId + : (cbId = getCbKey(callback)) + inId + : ""; + + if (cbId && !unobserve) { + cbBindings = cbBindingsStore[cbId] = cbBindingsStore[cbId] || {}; + } + + initNsArr = initialNs && initialNs.match(rNotWhite) || [""]; + initNsArrLen = initNsArr.length; + + while (initNsArrLen--) { // Step through multiple white-space separated namespaces if there are any + initialNs = initNsArr[initNsArrLen]; + if (unobserve && arguments.length < 3) { + if (callback) { + unobserveBinding(callback, cbBindingsStore[callback._cId]); // unobserve(handler) - unobserves this handler, all objects + } else if (!paths[0]) { + for (p in cbBindingsStore) { + unobserveBinding(callback, cbBindingsStore[p]); // unobserve() - unobserves all } } } + observeObjects(paths); } if (cbId) { removeCbBindings(cbBindings, cbId); @@ -643,12 +711,11 @@ if (!$.observe) { // arrayChange events in this scenario. Instead, {^{for}} and similar do specific arrayChange binding to the tagCtx.args[0] value, in onAfterLink. // Note deliberately using this == 1, rather than this === 1 because of IE<10 bug- see jsviews/issues/237 paths = slice.call(arguments), - origRoot = paths[0]; + pth = paths[0]; - if (origRoot + "" === origRoot && allowArray) { - initialNs = origRoot; // The first arg is a namespace, since it is a string, and this call is not from observeAndBind + if (pth + "" === pth) { + initialNs = pth; // The first arg is a namespace, since it is a string paths.shift(); - origRoot = paths[0]; } return innerObserve.apply(1, paths); }; @@ -666,10 +733,10 @@ if (!$.observe) { //========================== Initialize ========================== $.observable = $observable; - $observable._fltr = function(allPath, object, parentObs, filter) { + $observable._fltr = function(path, object, parentObs, filter) { if (filter && $isFunction(filter) - ? filter(allPath, object, parentObs) - : true // TODO Consider supporting filter being a string or strings to do RegEx filtering based on key and/or allPath + ? filter(path, object, parentObs) + : true // TODO Consider supporting filter being a string or strings to do RegEx filtering based on key and/or path ) { object = $isFunction(object) ? object.set && object.call(parentObs[0]) // It is a getter/setter @@ -697,7 +764,7 @@ if (!$.observe) { setProperty: function(path, value, nonStrict) { path = path || ""; var key, pair, parts, - multi = path + "" !== path && !path._is, // Hash of paths, not view object + multi = path + "" !== path, // Hash of paths self = this, object = self._data; @@ -720,13 +787,9 @@ if (!$.observe) { } } else if (path !== $expando) { // Simple single property case. - if (path._is) { - parts = [path]; - } else { - parts = path.split(/[.^]/); - while (object && parts.length > 1) { - object = object[parts.shift()]; - } + parts = path.split(/[.^]/); + while (object && parts.length > 1) { + object = object[parts.shift()]; } if (object) { self._setProperty(object, parts[0], value, nonStrict); @@ -742,20 +805,18 @@ if (!$.observe) { }, _setProperty: function(leaf, path, value, nonStrict) { - var setter, getter, removeProp, + var setter, getter, removeProp, eventArgs, view, property = path ? leaf[path] : leaf; - if ($isFunction(property)) { - if (property.set) { - // Case of property setter/getter - with convention that property is getter and property.set is setter - leaf = leaf._vw // Case of JsViews 2-way data-linking to an observable context parameter, with a setter. - // The view will be the this pointer for getter and setter. Note: this is the one scenario where path is "". - || leaf; - getter = property; - setter = getter.set === true ? getter : getter.set; - property = getter.call(leaf); // get - only treated as getter if also a setter. Otherwise it is simply a property of type function. - // See unit tests 'Can observe properties of type function'. - } + if ($isFunction(property) && property.set) { + // Case of property setter/getter - with convention that property is getter and property.set is setter + view = leaf._vw // Case of JsViews 2-way data-linking to an observable context parameter, with a setter. + // The view will be the this pointer for getter and setter. Note: this is the one scenario where path is "". + || leaf; + getter = property; + setter = getter.set === true ? getter : getter.set; + property = getter.call(view); // get - only treated as getter if also a setter. Otherwise it is simply a property of type function. + // See unit tests 'Can observe properties of type function'. } if ((property !== value || nonStrict && property != value) @@ -763,8 +824,8 @@ if (!$.observe) { // Date objects don't support != comparison. Treat as special case. && (!(property instanceof Date && value instanceof Date) || property > value || property < value)) { if (setter) { - setter.call(leaf, value); //set - value = getter.call(leaf); //get updated value + setter.call(view, value); // set + value = getter.call(view); // get updated value } else if (removeProp = value === remove) { if (property !== undefined) { delete leaf[path]; @@ -776,7 +837,11 @@ if (!$.observe) { leaf[path] = value; } if (path) { - this._trigger(leaf, {change: "set", path: path, value: value, oldValue: property, remove: removeProp}); + eventArgs = {change: "set", path: path, value: value, oldValue: property, remove: removeProp}; + if (leaf._ocp) { + eventArgs.ctxPrm = leaf._key; + } + this._trigger(leaf, eventArgs); } } }, @@ -930,7 +995,9 @@ if (!$.observe) { self.remove(j, dataLength - j); } self._srt = undefined; // We have finished sort operations during refresh - self._trigger({change: "refresh", oldItems: oldItems}, oldLength); + if (oldLength || newLength) { + self._trigger({change: "refresh", oldItems: oldItems}, oldLength); + } return self; }, @@ -975,19 +1042,31 @@ if (!$.observe) { } }; +//========================== +// dataMap with data-linking +//========================== + $views.map = function(mapDef) { - function Map(source, options, target, unbound) { - var changing, + function Map(source, options, oldMapOrTarget, unbound) { + var changing, updatedMap, map = this; - if (this.src) { - this.unmap(); // We are re-mapping a new source + if (map.src) { + map.unmap(); // We are re-mapping a new source + } + if (options) { + options.map = map; } - if (typeof source === OBJECT) { + if (typeof source === OBJECT || $isFunction(source)) { map.src = source; - map.tgt = target || map.tgt || []; + if (oldMapOrTarget) { + map.tgt = oldMapOrTarget.tgt || oldMapOrTarget; // Can provide an existing map, or a target array to be used on new map + } else { + map.tgt = map.tgt || []; + } map.options = options || map.options; - map.update(); - if (!unbound) { + if (updatedMap = map.update()) { + map = updatedMap; // If updating returns another map, then we can replace this one (so no need to bind it) + } else if (!unbound) { if (mapDef.obsSrc) { $observable(map.src).observeAll(map.obs = function(ev, eventArgs) { if (!changing) { @@ -999,11 +1078,11 @@ if (!$.observe) { } if (mapDef.obsTgt) { $observable(map.tgt).observeAll(map.obt = function(ev, eventArgs) { - if (!changing) { - changing = true; - mapDef.obsTgt(map, ev, eventArgs); - changing = undefined; - } + if (!changing && !map.tgt._updt) { + changing = true; + mapDef.obsTgt(map, ev, eventArgs); + changing = undefined; + } }, map.tgtFlt); } } @@ -1021,28 +1100,37 @@ if (!$.observe) { mapDef = $.extend({}, mapDef.baseMap, mapDef); } - mapDef.map = function(source, options, target, unbound) { - return new Map(source, options, target, unbound); + mapDef.map = function(source, options, oldMap, unbound) { + return new Map(source, options, oldMap, unbound); }; (Map.prototype = { srcFlt: mapDef.srcFlt || shallowFilter, // default to shallowFilter tgtFlt: mapDef.tgtFlt || shallowFilter, update: function(options) { - var map = this; - $observable(map.tgt).refresh(mapDef.getTgt(map.src, map.options = options || map.options)); + var oldMap, newMap, + map = this, + tgt = map.tgt; + if (!tgt._updt) { + tgt._updt = true; + oldMap = map.options && map.options.map; + $observable(tgt).refresh(mapDef.getTgt(map.src, map.options = options || map.options)); + tgt._updt = false; + newMap = map.options && map.options.map; + if (newMap && oldMap !== newMap) { + return newMap; + } + } }, unmap: function() { var map = this; - if (map.src) { - if (map.obs) { - $observable(map.src).unobserveAll(map.obs, map.srcFlt); - } - if (map.obt) { - $observable(map.tgt).unobserveAll(map.obt, map.tgtFlt); - } - map.src = undefined; + if (map.src && map.obs) { + $observable(map.src).unobserveAll(map.obs, map.srcFlt); + } + if (map.tgt && map.obt) { + $observable(map.tgt).unobserveAll(map.obt, map.tgtFlt); } + map.src = undefined; }, map: Map, _def: mapDef @@ -1060,4 +1148,6 @@ if (!$.observe) { : undefined; // In IE8 cannot do delete global._jsv }; $sub._dp = dependsPaths; + $sub._gck = getCbKey; + $sub._obs = $observe; } diff --git a/_src/jquery.views.js b/_src/jquery.views.js index 0a8f05a9..237b93f2 100644 --- a/_src/jquery.views.js +++ b/_src/jquery.views.js @@ -1,7 +1,10 @@ if ($.link) { return $; } // JsViews is already loaded $subSettings.trigger = true; + var activeBody, rTagDatalink, $view, $viewsLinkAttr, linkViewsSel, wrapMap, viewStore, oldAdvSet, useInput, + isIE = window.navigator.userAgent, + TEXTCONTENT = document.textContent !== undefined ? "textContent" : "innerText", jsvAttrStr = "data-jsv", elementChangeStr = "change.jsv", onBeforeChangeStr = "onBeforeChange", @@ -44,10 +47,15 @@ var activeBody, rTagDatalink, $view, $viewsLinkAttr, linkViewsSel, wrapMap, view rOpenViewMarkers = /(#)()(\d+)(_)/g, rOpenMarkers = /(#)()(\d+)([_^])/g, rViewMarkers = /(?:(#)|(\/))(\d+)(_)/g, + rTagMarkers = /(?:(#)|(\/))(\d+)(\^)/g, rOpenTagMarkers = /(#)()(\d+)(\^)/g, rMarkerTokens = /(?:(#)|(\/))(\d+)([_^])([-+@\d]+)?/g, rSplitBindings = /&(\d+)\+?/g, - getComputedStyle = global.getComputedStyle; + rShallowArrayPath = /^[^.]*$/, // No '.' in path + getComputedStyle = global.getComputedStyle, + $inArray = $.inArray; + +isIE = isIE.indexOf('MSIE ')>0 || isIE.indexOf('Trident/')>0; $observable = $.observable; @@ -65,115 +73,117 @@ $observe = $observable.observe; //=============== function updateValues(sourceValues, tagElse, bindId, ev) { -// Observably update data values targeted by bindTo -// Called when linkedElem changes: called as updateValues(sourceValues, tagElse, bindId, ev) - this: undefined +// Observably update a data value targeted by the binding.to binding of a 2way data-link binding. Called when elem changes +// Called when linkedElem of a tag control changes: as updateValue(val, index, tagElse, bindId, ev) - this: undefined // Called directly as tag.updateValues(val1, val2, val3, ...) - this: tag var linkCtx, cvtBack, cnvtName, target, view, binding, sourceValue, origVals, sourceElem, sourceEl, - oldLinkCtx, tos, to, tcpTag, exprOb, contextCb, l, tag; + oldLinkCtx, tos, to, tcpTag, exprOb, contextCb, l, m, tag; if (bindId && bindId._tgId) { tag = bindId; bindId = tag._tgId; - } - if (binding = bindingStore[bindId]) { - if (tos = binding.to) { - tos = tos[tagElse||0]; - // The binding has a 'to' field, which is of the form [tosForElse0, tosForElse1, ...] - // where tosForElseX is of the form [[[targetObject, toPath], [targetObject, toPath], ...], cvtBack] - linkCtx = binding.linkCtx; - sourceElem = linkCtx.elem; - view = linkCtx.view; - tag = linkCtx.tag; - if (!tag && tos._cxp) { - tag = tos._cxp.path !== _ocp && tos._cxp.tag; - sourceValue = sourceValues[0]; - sourceValues = []; - sourceValues[tos._cxp.ind] = sourceValue; - } + if (!tag.bindTo) { + defineBindToDataTargets(bindingStore[bindId], tag); // If this tag is updating for the first time, we need to create the 'to' bindings first + tag.bindTo = [0]; + } + } + if ((binding = bindingStore[bindId]) && (tos = binding.to)) { + tos = tos[tagElse||0]; + // The binding has a 'to' field, which is of the form [tosForElse0, tosForElse1, ...] + // where tosForElseX is of the form [[[targetObject, toPath], [targetObject, toPath], ...], cvtBack] + linkCtx = binding.linkCtx; + sourceElem = linkCtx.elem; + view = linkCtx.view; + tag = linkCtx.tag; + if (!tag && tos._cxp) { + tag = tos._cxp.path !== _ocp && tos._cxp.tag; + sourceValue = sourceValues[0]; + sourceValues = []; + sourceValues[tos._cxp.ind] = sourceValue; + } - if (tag) { - tag._.chg = 1; // Set 'changing' marker to prevent tag update from updating itself - if (cnvtName = tag.convertBack) { - if ($isFunction(cnvtName)) { - cvtBack = cnvtName; - } else { - cvtBack = view.getRsc("converters", cnvtName); - } + if (tag) { + tag._.chg = 1; // Set 'changing' marker to prevent tag update from updating itself + if (cnvtName = tag.convertBack) { + if ($isFunction(cnvtName)) { + cvtBack = cnvtName; + } else { + cvtBack = view.getRsc("converters", cnvtName); } } + } - if (sourceElem.nodeName === "SELECT") { - // data-link to string or (multiselect) array of strings + if (sourceElem.multiple && sourceValues[0] === null) { + // Case where sourceValues was undefined, and set to [null] by $source[setter]() above + sourceValues = [[]]; } - origVals = sourceValues; - if (cvtBack) { - sourceValues = cvtBack.apply(tag, sourceValues); - if (sourceValues === undefined) { - tos = []; // If cvtBack does not return anything, do not update target. - //(But cvtBack may be designed to modify observable values from code as a side effect) - } - sourceValues = $isArray(sourceValues) ? sourceValues : [sourceValues]; - // If there are multiple tos (e.g. multiple args on data-linked input) then cvtBack can update not only - // the first arg, but all of them by returning an array. + sourceElem._jsvSel = sourceValues; + } + origVals = sourceValues; + if (cvtBack) { + sourceValues = cvtBack.apply(tag, sourceValues); + if (sourceValues === undefined) { + tos = []; // If cvtBack does not return anything, do not update target. + //(But cvtBack may be designed to modify observable values from code as a side effect) } + sourceValues = $isArray(sourceValues) ? sourceValues : [sourceValues]; + // If there are multiple tos (e.g. multiple args on data-linked input) then cvtBack can update not only + // the first arg, but all of them by returning an array. + } - // Set linkCtx on view, dynamically, just during this handler call - oldLinkCtx = view.linkCtx; - view.linkCtx = linkCtx; - l = tos.length; - while (l--) { - if (to = tos[l]) { - to = to + "" === to ? [linkCtx.data, to] : to; // [object, path] - target = to[0]; - tcpTag = to.tag; // If this is a tag contextual parameter - the owner tag - sourceValue = (to[1] === _ocp - ? origVals // If to target is for tag contextual parameter set to static expression (or uninitialized) - we are - // binding to tag.ctx.foo._ocp - and we use original values, without applying cvtBack converter - : sourceValues // Otherwise use the converted value - )[l]; - if (sourceValue !== undefined && (!tag || !tag.onBeforeUpdateVal || tag.onBeforeUpdateVal(ev, { - change: "change", - data: target, - path: to[1], - index: l, - tagElse: tagElse, - value: sourceValue - }) !== false)) { - if (tcpTag) { // We are modifying a tag contextual parameter ~foo (e.g. from within block) so update 'owner' tag: tcpTag - tcpTag.updateValue(sourceValue, to.ind, to.tagElse, undefined, ev); - if (tcpTag.setValue) { - tcpTag.setValue(sourceValue, to.ind, to.tagElse); + // Set linkCtx on view, dynamically, just during this handler call + oldLinkCtx = view.linkCtx; + view.linkCtx = linkCtx; + l = tos.length; + while (l--) { + if (to = tos[l]) { + to = to + "" === to ? [linkCtx.data, to] : to; // [object, path] + target = to[0]; + tcpTag = to.tag; // If this is a tag contextual parameter - the owner tag + sourceValue = (target && target._ocp && !target._vw + ? origVals // If to target is for tag contextual parameter set to static expression (or uninitialized) - we are + // binding to tag.ctx.foo._ocp - and we use original values, without applying cvtBack converter + : sourceValues // Otherwise use the converted value + )[l]; + if (sourceValue !== undefined && (!tag || !tag.onBeforeUpdateVal || tag.onBeforeUpdateVal(ev, { + change: "change", + data: target, + path: to[1], + index: l, + tagElse: tagElse, + value: sourceValue + }) !== false)) { + if (tcpTag) { // We are modifying a tag contextual parameter ~foo (e.g. from within block) so update 'owner' tag: tcpTag + if ((m = tcpTag._.toIndex[to.ind]) !== undefined) { + tcpTag.updateValue(sourceValue, m, to.tagElse, undefined, undefined, ev); // if doesn't map, don't update, or update scoped tagCtxPrm. But should initialize from outer from binding... + } + tcpTag.setValue(sourceValue, to.ind, to.tagElse); + } else if (sourceValue !== undefined && target) { + if ((tcpTag = ev && (sourceEl = ev.target)._jsvInd === l && sourceEl._jsvLkEl) && (m = tcpTag._.fromIndex[l]) !== undefined) { + // The source is a tag linkedElem (linkedElement: [..., "elemSelector", ...], which is updating + tcpTag.setValue(origVals[l], m, sourceEl._jsvElse); + } + if (target._cpfn) { + contextCb = linkCtx._ctxCb; // This is the exprOb for a computed property + exprOb = target; + target = linkCtx.data; + if (exprOb._cpCtx) { // Computed value for a contextual parameter + target = exprOb.data; // The data for the contextual view (where contextual param expression evaluated/assigned) + contextCb = exprOb._cpCtx; // Context callback for contextual view } - } else if (sourceValue !== undefined && target) { - if (tcpTag = ev && (sourceEl = ev.target)._jsvInd === l && sourceEl._jsvLkEl) { - // The source is a tag linkedElem (linkedElement: [..., "elemSelector", ...], which is updating - tcpTag.setValue(origVals[l], l, sourceEl._jsvElse); + while (exprOb && exprOb.sb) { // Step through chained computed values to leaf one... + target = contextCb(exprOb, target); + exprOb = exprOb.sb; } - if (target._cpfn) { - contextCb = linkCtx._ctxCb; // This is the exprOb for a computed property - exprOb = target; - target = linkCtx.data; - if (exprOb._cpCtx) { // Computed value for a contextual parameter - target = exprOb.data; // The data for the contextual view (where contextual param expression evaluated/assigned) - contextCb = exprOb._cpCtx; // Context callback for contextual view - } - while (exprOb && exprOb.sb) { // Step through chained computed values to leaf one... - target = contextCb(exprOb, target); - exprOb = exprOb.sb; - } - } - $observable(target).setProperty(to[1], sourceValue); // 2way binding change event - observably updating bound object } + $observable(target).setProperty(to[1], sourceValue); // 2way binding change event - observably updating bound object } } } - view.linkCtx = oldLinkCtx; } + view.linkCtx = oldLinkCtx; } if (tag) { tag._.chg = undefined; // Clear marker @@ -199,7 +209,7 @@ function onElemChange(ev) { rSplitBindings.lastIndex = 0; // Ensure starts at zero while (bindId = rSplitBindings.exec(ev.target._jsvBnd)) { // _jsvBnd is a string with the syntax: "&bindingId1&bindingId2" - updateValue(val, source._jsvInd, source._jsvElse, bindId[1], ev); + updateValue(val, source._jsvInd, source._jsvElse, undefined, bindId[1], ev); } ev.target._jsvChg = undefined; // Clear marker } @@ -247,7 +257,7 @@ function onDataLinkedTagChange(ev, eventArgs) { // For data-link="{:xxx}" with no cvt or cvtBk returns value. Otherwise returns tagCtxs } // Compiled link expression for linkTag: return value for data-link="{:xxx}" with no cvt or cvtBk, otherwise tagCtx or tagCtxs - attr = tag && tag.attr || linkCtx.attr || defaultAttr(target, true, cvt !== undefined); + attr = tag && tag.attr || linkCtx.attr || (linkCtx._dfAt = defaultAttr(target, true, cvt !== undefined)); if (attr === VALUE && (tag && tag.parentElem || linkCtx.elem).type === CHECKBOX) { attr = CHECKED; } @@ -275,6 +285,9 @@ function onDataLinkedTagChange(ev, eventArgs) { if (eventArgs && (onEvent = changeHandler(view, onAfterChangeStr, tag))) { onEvent.call(tag || linkCtx, ev, eventArgs); } + if (tag.tagCtx.props.dataMap) { + tag.tagCtx.props.dataMap.map(tag.tagCtx.args[0], tag.tagCtx, tag.tagCtx.map, !tag._.bnd); + } return; } @@ -296,7 +309,7 @@ function onDataLinkedTagChange(ev, eventArgs) { addLinkMethods(tag = linkCtx.tag); // In both convertVal and renderTag we have instantiated a tag attr = linkCtx.attr || attr; // linkCtx.attr may have been set to tag.attr during tag instantiation in renderTag } - if (bindEarly = tag && !tag.inline && tag.template) { + if (bindEarly = tag && (!tag.inline || linkCtx.fn._lr) && tag.template) { // Data-linked tags with templated contents need to be data-linked before their contents, so that observable updates // will trigger the parent tags before the child tags. observeAndBind(linkCtx, source, target); @@ -314,6 +327,12 @@ function onDataLinkedTagChange(ev, eventArgs) { observeAndBind(linkCtx, source, target); } + if (tag && tag._.ths) { + // Tag has a this=expr binding for which we have created an additional 'to' (defineBindToDataTargets) target (at index bindTo.length) + // We now have the this pointer, so we push it to the binding, using updateValue(index) + tag.updateValue(tag, tag.bindTo ? tag.bindTo.length : 1); // If bindTo not defined yet, it will be [0], so length 1 + } + if (eventArgs && (onEvent = changeHandler(view, onAfterChangeStr, tag))) { onEvent.call(tag || linkCtx, ev, eventArgs); } @@ -332,7 +351,7 @@ function updateContent(sourceValue, linkCtx, attr, tag) { // When called (in onDataLinkedTagChange) for target HTML returns true // When called (in onDataLinkedTagChange) for other targets returns boolean for "changed" var setter, prevNode, nextNode, late, nodesToRemove, useProp, tokens, id, openIndex, closeIndex, testElem, nodeName, cStyle, jsvSel, - renders = attr !== NONE && sourceValue !== undefined && !linkCtx._noUpd && !((attr === VALUE || attr === HTML) && (tag ? tag._.chg : linkCtx.elem._jsvChg)), + renders = attr !== NONE && sourceValue !== undefined && !linkCtx._noUpd && !((attr === VALUE || attr === HTML) && (!tag && linkCtx.elem._jsvChg)), // For data-link="^{...}", don't update the first time (no initial render) - e.g. to leave server rendered values. source = linkCtx.data, target = tag && tag.parentElem || linkCtx.elem, @@ -434,20 +453,29 @@ function updateContent(sourceValue, linkCtx, attr, tag) { if (tag && tag.inline) { nodesToRemove = tag.nodes(true); if (tag._elCnt) { - if (prevNode && prevNode !== nextNode) { + if (prevNode && prevNode !== nextNode) { // nextNode !== prevNode // This prevNode will be removed from the DOM, so transfer the view tokens on prevNode to nextNode of this 'viewToRefresh' transferViewTokens(prevNode, nextNode, target, tag._tgId, "^", true); - } else if (tokens = target._df) { // This occurs when there is no nextNode, and so the target._df may include tokens referencing - // view and tag bindings contained within the open and close tokens of the updated tag control. They need to be processed (disposed) + } else { + // nextNode === prevNode, or there is no nextNode and so the target._df may have tokens + tokens = prevNode ? prevNode.getAttribute(jsvAttrStr) : target._df; id = tag._tgId + "^"; openIndex = tokens.indexOf("#" + id) + 1; closeIndex = tokens.indexOf("/" + id); if (openIndex && closeIndex > 0) { + // If prevNode, or target._df, include tokens referencing view and tag bindings contained within the open and close tokens + // of the updated tag control, they need to be processed (disposed) openIndex += id.length; if (closeIndex > openIndex) { - setDefer(target, tokens.slice(0, openIndex) + tokens.slice(closeIndex)); - disposeTokens(tokens.slice(openIndex, closeIndex)); + disposeTokens(tokens.slice(openIndex, closeIndex)); // Dispose view and tag bindings + tokens = tokens.slice(0, openIndex) + tokens.slice(closeIndex); + + if (prevNode) { + prevNode.setAttribute(jsvAttrStr, tokens); // Remove tokens of replaced content + } else if (target._df) { // Remove tokens of replaced content + setDefer(target, tokens); + } } } } @@ -476,11 +504,7 @@ function updateContent(sourceValue, linkCtx, attr, tag) { if (change = change || targetVal !== sourceValue) { if (attr === "text" && target.children && !target.children[0]) { // This code is faster then $target.text() - if (target.textContent !== undefined) { - target.textContent = sourceValue; - } else { - target.innerText = sourceValue === null ? "" : sourceValue; - } + target[TEXTCONTENT] = sourceValue === null ? "" : sourceValue; } else { $target[setter](sourceValue); } @@ -489,7 +513,7 @@ function updateContent(sourceValue, linkCtx, attr, tag) { // Setting value of
\\n Edit: \\n\\n\\n$.views.settings.debugMode(true);\\n\\nvar team = {owner:\\n {name:\\\"Jo\\\"}\\n}; // team.manager is undefined...\\n\\nvar tmpl = $.templates(\\\"#teamTmpl\\\");\\n\\ntmpl.link(\\\"#result\\\", {team: team}); // Error...\\nTemplate:\\nTeam:
\\n {{if owner}}\\n Owner: {^{:manager.name}}\\n {{/if}}\\n
\\nEdit: \\n\\nCode:\\n$.views.settings.debugMode(true);\\n// Debug mode is set to true, so error messages are rendered in place of the corresponding tag or data-link expression.\\n\\nvar team = {owner:\\n {name:\\\"Jo\\\"}\\n}; // team.manager is undefined...\\n...\\ntmpl.link(\\\"#result\\\", team); // Error...\\n\\nIf you choose Try it and change to $.views.settings.debugMode(false);, the error will instead be thrown as an exception.\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Setting debug mode to a string\",\r\n \"text\": \"Setting debug mode to a string\\nBy setting debug mode to a string rather than to true, no exception will be thrown, and the chosen string will be rendered, replacing the rendered tag.\\n$.views.settings.debugMode(\\\"Error!\\\");\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Debug mode set to a default string\",\r\n \"text\": \"Debug mode set to a default string\\n\\n\\n\\n{{for members}}\\n
{{:name}} - {{:address.street}}
\\n{{/for}}\\n\\n\\n$.views.settings.debugMode(\\\"Error!\\\"); // Do not throw exception - render \\\"Error!\\\"\\n\\nvar team = {members: [\\n {name:\\\"Jo\\\", address: {street: \\\"1st Ave\\\"}},\\n {name:\\\"Bill\\\"}, // Bill does not have an address!!\\n {name:\\\"Ava\\\", address: {street: \\\"Main St\\\"}}\\n]};\\n\\nvar html = $(\\\"#teamTmpl\\\").render(team);\\n\\n$(\\\"#result\\\").html(html);\\n\\n$.views.settings.debugMode(\\\"Error!\\\"); \\n\\nThe {{:address.street}} tag for Bill (who has no address) is replaced by \\\"Error!\\\".\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"In some scenarios the desired behavior may be to ignore errors during rendering, by skipping any tag with an error, rendering it as an empty string. This is achieved very easily, by simply writing:\\n$.views.settings.debugMode(\\\"\\\");\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Debug mode set to empty string\",\r\n \"text\": \"Debug mode set to empty string\\n\\n\\n\\n{{for members}}\\n
{{:name}} - {{:address.street}}
\\n{{/for}}\\n\\n\\n$.views.settings.debugMode(\\\"\\\"); // Do not throw exception - render \\\"\\\"\\n\\nvar team = {members: [\\n {name:\\\"Jo\\\", address: {street: \\\"1st Ave\\\"}},\\n {name:\\\"Bill\\\"}, // Bill does not have an address!!\\n {name:\\\"Ava\\\", address: {street: \\\"Main St\\\"}}\\n]};\\n\\nvar html = $(\\\"#teamTmpl\\\").render(team);\\n\\n$(\\\"#result\\\").html(html);\\n\\n$.views.settings.debugMode(\\\"\\\");\\n\\nThe {{:address.street}} tag for Bill (who has no address) is skipped.\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Providing a debug mode handler (function)\",\r\n \"text\": \"Providing a debug mode handler (function)\\nIf debug mode is set to a function, the function will be called each time an error is encountered during rendering.\\n\\nIf the function returns a string, then that string will be rendered, replacing the rendered tag\\nIf the function has no return value, then the error message will be rendered\\n\\n$.views.settings.debugMode(myOnErrorHandler);\\n\\nfunction myOnErrorHandler(e, fallback, view) {\\n // This handler will log the error, and then display the empty string\\n console.log(...);\\n return \\\"\\\"; \\n}\\n\\nThe parameters of the debug mode error handler function – myHandler(e, fallback, view) – will be:\\n\\ne – the error object\\nfallback – the fallback error string, provided by the onError fallback specified on the tag, if there is one\\nview – the current view object\\nThe this pointer will be the current data item, view.data\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Debug mode – onError handler\",\r\n \"text\": \"Debug mode – onError handler\\n\\n\\n\\n{{for members}}\\n
Name: {{:name}}
\\n {{:address.street onError='address'}}\\n
\\n
\\n{{/for}}\\n\\n\\nvar team = {\\n members: [\\n {name: \\\"Bill\\\", address: {street: \\\"1st Ave\\\"}},\\n {name: \\\"Jane\\\", address: undefined} // No address\\n ]\\n};\\n\\nfunction onErrorHandler(e, fallback, view) {\\n console.log(e.message);\\n if (fallback === \\\"address\\\") {\\n return 'Address error for ' + this.name + '. (\\\"' + e.message + '\\\")';\\n }\\n}\\n\\n$.views.settings.debugMode(onErrorHandler);\\n\\nvar html = $(\\\"#teamTmpl\\\").render(team);\\n\\n$(\\\"#result\\\").html(html);\\n\\n{{:address.street onError='address'}}\\n\\nfunction onErrorHandler(e, fallback, view) {\\n console.log(e.message);\\n if (fallback === \\\"address\\\") {\\n return 'Address error for ' + this.name + '. (\\\"' + e.message + '\\\")';\\n }\\n}\\n\\n$.views.settings.debugMode(onErrorHandler);\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Advanced debugging, using debugging helpers\",\r\n \"text\": \"Advanced debugging, using debugging helpers\\nInserting breakpoints during rendering:\\nJsRender (and JsViews) provide some helpers for debugging code within compiled templates:\\n\\nThe {{dbg expression/}} tag\\nThe {{dbg: expression}} converter\\nThe ~dbg(expression) helper function\\n\\nEach of the above will\\n\\nevaluate the expression\\noutput a console.log(...) call\\nthrow and catch an exception – which you can use as a break point by stopping on caught exceptions\\nrender the evaluated expression\\n\\nThis is done by inserting code into the compiled template which calls into the built-in dbgBreak code:\\nfunction dbgBreak(val) {\\n try {\\n console.log(\\\"JsRender dbg breakpoint: \\\" + val);\\n throw \\\"dbg breakpoint\\\"; // To break here, stop on caught exceptions.\\n }\\n catch (e) {}\\n\\nval will be the result of evaluating expression.\\nWhen rendering execution breaks at the above code, you can then step up through the call stack to the compiled template code, for further debugging.\\nUsage examples: {{dbg:...}}, {{:~dbg(...)}}, {{dbg .../}} etc.\\nBreakpoints during data linking:\\nIn JsViews, a breakpoint can also be inserted during template data-linking, as in {^{for ... onAfterLink=~dbg}}.\\nUsing {{*debugger}}:\\nAn alternative (but similar) debugging technique is to use allowCode to insert a debugger; statement directly into the compiled template code, as follows:\\nCode:\\nvar tmpl = $.templates({\\n markup: \\\"#myTmpl\\\",\\n allowCode: true // Alternatively use global setting: $.views.settings.allowCode(true)\\n});\\n\\nTemplate:\\n...\\n{{*debugger}}\\n...\\n\\n\"\r\n }\r\n ]\r\n },\r\n \"advanced\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"\",\r\n \"text\": \"\"\r\n }\r\n ]\r\n },\r\n \"apps\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Apps using JsRender\",\r\n \"text\": \"Apps using JsRender\\nJsRender is a simple light-weight templating engine. It can be used in the browser within simple web pages, or within complex single-page apps, or in conjunction with other frameworks. It can also be used on the server, using Node.js.\\nIt is highly flexible, expressive, and ‘unopinionated’ – so it leaves you free to work within your own choice of overall application architecture (including architectures based on MVVM, MVP or MVC – optionally with server/client integration), and lets you use your own flavor of data/model layer – whether simple plain JavaScript objects, hand-coded View Model instances, or compiled View Models.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Components of an app using JsRender\",\r\n \"text\": \"Components of an app using JsRender\\nAny app or web page using JsRender templates will generally involve defining or registering the following elements:\\n\\none or more templates – see Templates\\na ‘data Layer’ – see JsRender: Data or View Model\\noptionally, helpers – in the form of metadata, helper functions and converter functions, see Helpers and Converters\\noptionally, reusable components for use within your templates – see Custom tags\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Apps using JsViews\",\r\n \"text\": \"Apps using JsViews\\nJsRender also provides optional integration with JsViews. JsViews is much more of a framework than JsRender. It does much more than just templating – providing also data-binding, MVVM support, observability of the data/View Model layer, support for interactive encapsulated components (JsViews tag controls), and more. Nevertheless, it can also interoperate with other frameworks and components. See Building apps in JsViews for more information.\\n\"\r\n }\r\n ]\r\n },\r\n \"getindex\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"If you pass an array to the JsRender .render(myArray) method, or if you use {{for myArray}}, in a template, JsRender will iterate over the array, and render an item view for each item in the array.\\nWithin an item view you can access the array-index of the current item, using {{:#index}}:\\n\\nGetting item index within a top-level item view (from .render(myArray)):\\n...\\n{{:#index}}\\n...\\n\\nGetting item index within a {{for myArray}} block:\\n{{for myArray}}\\n ...\\n {{:#index}}\\n ...\\n{{/for}}\\n\\n\\nIf there are additional nested tags, then from within the nested tags you can still access the index, by using {{:#getIndex()}}:\\n\\nGetting item index from nested tags within an item view:\\n{{for myArray}}\\n ...\\n {{if ...}}\\n ...\\n {{:#getIndex()}}\\n ...\\n {{/if}}\\n ...\\n{{/for}}\\n\\n\\nSee index and getIndex() for additional details.\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"See also\",\r\n \"text\": \"See also\\n\"\r\n }\r\n ]\r\n },\r\n \"contextualparams\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Defining contextual parameters\",\r\n \"text\": \"Defining contextual parameters\\nContextual parameters provide a very convenient way of passing values in to nested tag contexts. (See View hierarchy.)\\nA contextual parameter is defined by simply writing ~myValue=... (for any expression) on any block tag, such as {{if}} or {{for}}.\\nThe resulting ~myValue parameter can then be accessed within the block tag – or deeper down within nested tag contexts, at any depth.\\nFor example, the following template defines three contextual parameters, and uses them in nested contexts:\\n...\\n{{if isActive ~teamTitle=title ~teamData=#data ~teamIndex=#index}}\\n {{for members}}\\n {{if ~teamIndex>2}}\\n {{:~teamTitle}} {{:~teamData.description}}\\n ...\\n\\nNote: You can also set contextual parameters on {{else}} blocks, such as in the following example which uses the same template for the {{if}} and {{else}} blocks, but assigns different values to the ~teamTitle parameter in each case:\\n{{if isActive ~teamTitle=activeTitle tmpl=\\\"teamTmpl\\\"}}\\n{{else ~teamTitle=inactiveTitle tmpl=\\\"teamTmpl\\\"}}\\n{{/if}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"itemVar – contextual parameter for data 'item' of block\",\r\n \"text\": \"itemVar – contextual parameter for data 'item' of block\\nThe itemVar feature lets you set up a contextual parameter for the current data ‘item’ of a block. It is in effect an ‘alias’ for #data within the block.\\nTo define an itemVar contextual parameter for a block tag, simply write itemVar=~someName. The parameter ~someName can then be accessed like any other helper variable or contextual parameter, within nested contexts to any depth.\\n...\\n{{for teams itemVar=\\\"~team\\\"}}\\n ...\\n {{for members itemVar=\\\"~member\\\"}}\\n ...\\n {{if isActive}}\\n {{:~team.title}} {{:~member.name}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Accessing root data: the built-in '~root' contextual parameter\",\r\n \"text\": \"Accessing root data: the built-in '~root' contextual parameter\\nThe built-in contextual parameter ~root provides direct access to the root data which was passed to the render() method (or link() method if you are using JsViews). It can be accessed from anywhere within a template, at an level of nested tags.\\nNote: If an array is passed to render() or link() then ~root will be the array (so you can render {{:root.length}} for example).\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"See also:\",\r\n \"text\": \"See also:\\n\"\r\n }\r\n ]\r\n },\r\n \"parentdata\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Accessing \\\"parent\\\" data, from nested views. Passing in template variables\",\r\n \"text\": \"Accessing \\\"parent\\\" data, from nested views. Passing in template variables\\nWhen a template (containing nested template tags) is rendered, the result is a view hierarchy – where the views provide information on how the underlying data objects map to the rendered UI.\\nOften it is helpful to be able to access the data for a parent view from a nested template or block (nested view).\\nThere are several ways to get to parent data:\\n\\nCreate a contextual parameter to pass a value to nested views.\\nHere are three examples:\\n...\\n{{if ... ~teamTitle=title ~teamData=#data ~teamIndex=#index}}\\n ...\\n {{for ...}}\\n ...\\n {{:~teamTitle}} {{:~teamData.title}} {{:~teamIndex}}\\n\\nUse itemVar to provide a contextual parameter for the current data ‘item’ of a block, to be passed in to deeper nested contexts\\n...\\n{{for members itemVar=\\\"~member\\\"}}\\n ...\\n {{props}}\\n ...\\n {{:~member.name}}\\n\\nUse the view.parent property to step up through successive parent views (#parent, #parent.parent etc.):\\n...\\n{{if ...}}\\n ...\\n {{for ...}}\\n ...\\n {{:#parent.parent.data.title}}\\n\\nUse the view.get(type) method to get to a parent view of a given type:\\n...\\n{{if ...}}\\n ...\\n {{for ...}}\\n ...\\n {{:#get(\\\"if\\\").data.title}}\\n\\n\\nUse the view.getIndex() method to get to the index of a parent “item” view:\\n{{if ...}}\\n ...\\n {{for ...}}\\n ...\\n {{:#parent.getIndex()}}\\n {{:#getIndex()}}\\n\\n\\nHere is a sample showing all of these methods:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"text\": \"\\n
\\n Team: {{:title}} -\\n {{mytag members/}}\\n\\n {{if members.length\\n ~teamTitle=title\\n ~teamData=#data\\n ~teamIndex=#index\\n }}\\n Members:
    \\n {{for members\\n itemVar=\\\"~member\\\"\\n }}\\n
  • \\n {{:name}}\\n (\\n {{:~teamTitle}}\\n {{:~teamData.title}}\\n {{:#parent.parent.data.title}}\\n {{:#get(\\\"if\\\").data.title}}\\n )\\n
    \\n [\\n {{:~teamIndex}}\\n = {{:#parent.getIndex()}}\\n : {{:#getIndex()}}\\n ]\\n
    \\n {{props}}\\n {{:key}}: {{:prop}}\\n (\\n {{:~member.name}}\\n )\\n {{/props}}\\n
  • \\n {{/for}}\\n
\\n {{/if}}\\n
\\n\\n\\n\\n// mytag: custom tag to output \\\"1 member\\\" or \\\"n members\\\"\\n$.views.tags(\\\"mytag\\\", \\\"{{:length == 1 ? '1 member' : length + ' members'}}\\\");\\n// Alternative version of mytag:\\n// $.views.tags(\\\"mytag\\\", \\\"{{if length == 1}}1 member{{else}}{{:length}} members{{/if}}\\\");\\n\\nvar teams = [\\n {title: \\\"The A Team\\\", members: [{name: \\\"Jeff\\\"}, {name: \\\"Maria\\\"}]},\\n {title: \\\"The B Team\\\", members: [{name: \\\"Francis\\\"}]}\\n];\\n\\nvar html = $(\\\"#teamTemplate\\\").render(teams);\\n\\n$(\\\"#result\\\").html(html);\\nThis sample shows all the ways to get to parent data described in the section above:\\n\\nCreate a contextual parameter to pass a value to nested views.\\nUse itemVar to provide a contextual parameter for the current data ‘item’ of a block, to be passed in to deeper nested contexts\\nUse the view.parent property to step up through successive parent views (#parent, #parent.parent etc.):\\nUse the view.get(type) method to get to a parent view of a given type:\\nUse the view.getIndex() method to get to the index of a parent “item” view:\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"See also:\",\r\n \"text\": \"See also:\\n\"\r\n }\r\n ]\r\n },\r\n \"jsrmodel\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"JsRender is designed to work well with either plain JavaScript objects and arrays, or with instances of JavaScript classes, such as View Model classes.\\nSo, for example, if you are using data obtained from a JSON request, you can choose between:\\n\\nrendering your templates directly against the objects and arrays returned from the JSON request\\npassing the data through a ‘mapping’ process to create a hierarchy of View Model instances, and rendering your templates against those objects\\n\\nThe plain objects approach is convenient and simple for getting rapidly up and running with templates. But for more complex projects the View Model approach is better for creating clean well-designed modular code, where each View Model has specific getters, setters and methods, and can have its own ‘private’ properties and state.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using JsRender built-in compiled View Models\",\r\n \"text\": \"Using JsRender built-in compiled View Models\\nJsRender will work well with your own ‘hand-coded’ View Model classes (see below).\\nBut in most cases it is simpler and better to use the $.views.viewModels(...) API. This API lets you very easily and rapidly compile View Model classes for your own needs, following a standard pattern, and with some additional powerful features:\\n\\nIt provides a built-in mapping and unmapping feature for automatically converting from a plain object hierarchy (such as from a JSON request) to a hierarchy of View Model instances, or for converting back to plain data (such as for submitting to the server)\\nIt also provides a merge(...) feature for incrementally updating the View Model hierarchy, using updated plain data from the server.\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Data / View Model with JsViews\",\r\n \"text\": \"Data / View Model with JsViews\\nAll of the alternatives mentioned above (plain object hierarchies, hand-coded View Model classes, or JsRender compiled View Model classes) can also be used with JsViews data-binding and observable data. (For more information see JsViews: Data / View Model and JsViews: Compiled View Models.)\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Example: JsRender with plain objects and arrays\",\r\n \"text\": \"Example: JsRender with plain objects and arrays\\n\"\r\n },\r\n {\r\n \"_type\": \"code\",\r\n \"title\": \"Suppose this is our data from a JSON request:\",\r\n \"text\": \"Suppose this is our data from a JSON request:\\nvar person = {\\n name: \\\"Pete\\\",\\n address: {\\n street: \\\"1st Ave\\\"\\n },\\n phones: [{number: \\\"111 111 1111\\\"}, {number:\\\"222 222 2222\\\"}] \\n};\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"template\",\r\n \"title\": \"We'll render using a template structured like this:\",\r\n \"text\": \"We'll render using a template structured like this:\\n... \\n{{:name}}\\n...\\n{{:address.street}}\\n...\\n{{for phones}}\\n ... \\n {{:number}}\\n ...\\n{{/for}}\\n...\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Render template directly against plain objects...\",\r\n \"text\": \"Render template directly against plain objects...\\n\\n\\n\\n \\n \\n \\n \\n
Name:{{:name}}
Street:{{:address.street}}
Phones:\\n \\n {{for phones}}\\n \\n {{/for}}\\n
{{:number}}
\\n
\\n\\n// Compiled template\\nvar tmpl = $.templates(\\\"#personTmpl\\\");\\n\\n// Data: hierarchy of plain objects and arrays\\nvar person = {\\n name: \\\"Pete\\\",\\n address: {\\n street: \\\"1st Ave\\\"\\n },\\n phones: [{number: \\\"111 111 1111\\\"}, {number:\\\"222 222 2222\\\"}] \\n};\\n\\n// Render template against plain object hierarchy\\n$(\\\"#result\\\").html(tmpl.render(person));\\n\\n\\n\\n... {{:name}} ...\\n\\nRender template against person (plain object)\\n\\n$(\\\"#result\\\").html(tmpl.render(person));\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Now we’ll convert the above sample to use View Model classes.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Example: JsRender with 'hand-coded' View Model objects\",\r\n \"text\": \"Example: JsRender with 'hand-coded' View Model objects\\nWe’ll convert the data to a corresponding hierarchy of simple ‘hand-coded’ View Model class instances. In each case we will replace properties by simple getters, and corresponding ‘private’ properties.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"View Model classes:\",\r\n \"text\": \"View Model classes:\\nHere is the class definition for Person:\\n// Constructor\\nfunction Person(name, address, phones) {\\n // Initialize private properties\\n this._name = name;\\n this._address = address;\\n this._phones = phones;\\n}\\n\\n// Prototype\\nvar personProto = {\\n // Define a getter for each property \\n name: function() {\\n return this._name;\\n },\\n address: function() {\\n return this._address;\\n },\\n phones: function() {\\n return this._phones;\\n }\\n};\\n...\\n\\nWe define exactly similar classes for our Address and Phone objects too.\\nThe above pattern for View Model classes will work well with JsRender. (It will also work seamlessly with JsViews data-binding, if at some point you choose to upgrade to use JsViews features).\\nNote: The standard JsRender View Model pattern provided by $.views.viewModels is similar, but provides also setters (along with optional ‘observability’ for two-way binding in JsViews).\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Getter functions\",\r\n \"text\": \"Getter functions\\nNote that properties are now getter functions, which return the appropriate value (which may be of any type, including objects or arrays – such as address and phones above).\\nIn fact they are particular case of computed properties – a concept that can be used quite generally within JsRender and JsViews, not only for View Model properties.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Template\",\r\n \"text\": \"Template\\nTo convert our template from using plain objects to using View Model objects, the only change we need to make is to add parens for our properties, which are now getter functions:\\n... \\n{{:name()}}\\n...\\n{{:address().street()}}\\n...\\n{{for phones()}}\\n ... \\n {{:number()}}\\n ...\\n{{/for}}\\n...\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Instantiate and render:\",\r\n \"text\": \"Instantiate and render:\\nNow all we need to do is to construct our root person object (with its underlying hierarchy of View Model instance objects) and render the template against that object in the usual way.\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Render template against a View Model object hierarchy\",\r\n \"text\": \"Render template against a View Model object hierarchy\\n\\n\\n\\n\\n\\n \\n \\n \\n \\n
Name:{{:name()}}
Street:{{:address().street()}}
Phones:\\n \\n {{for phones()}}\\n \\n {{/for}}\\n
\\n {{:number()}}\\n
\\n
\\n\\n// Compiled template\\nvar tmpl = $.templates(\\\"#personTmpl\\\");\\n\\n// Instantiate View Model hierarchy\\nvar person = new Person(\\n \\\"Pete\\\",\\n new Address(\\\"1st Ave\\\"),\\n [\\n new Phone(\\\"111 111 1111\\\"),\\n new Phone(\\\"222 222 2222\\\")\\n ]\\n );\\n\\n// Render template against person object (instance of Person)\\n$(\\\"#result\\\").html(tmpl.render(person));\\n... {{:name()}} ...\\n\\n\\nInstantiate View Model hierarchy\\n\\n// Use previously defined View Model classes: Person, Address, Phone\\nvar person = new Person(\\n \\\"Pete\\\",\\n new Address(\\n \\\"1st Ave\\\"),\\n [\\n new Phone(\\\"111 111 1111\\\"),\\n new Phone(\\\"222 222 2222\\\")\\n ]\\n );\\n\\n\\nRender template against person object (instance of Person)\\n\\n$(\\\"#result\\\").html(tmpl.render(person));\\n\\n// View Model class definitions using getter pattern\\n\\n// Person\\nfunction Person(name, address, phones) {\\n this._name = name;\\n this._address = address;\\n this._phones = phones;\\n}\\n\\nvar personProto = {\\n name: function() {\\n return this._name;\\n },\\n phones: function() {\\n return this._phones;\\n },\\n address: function() {\\n return this._address;\\n }\\n};\\n\\nPerson.prototype = personProto;\\n\\n// Address\\nfunction Address(street) {\\n this._street = street;\\n}\\n\\nvar addressProto = {\\n street: function() {\\n return this._street;\\n }\\n};\\n\\nAddress.prototype = addressProto;\\n\\n// Phone\\nfunction Phone(number) {\\n this._number = number;\\n}\\n\\nvar phoneProto = {\\n number: function() {\\n return this._number;\\n }\\n};\\n\\nPhone.prototype = phoneProto;\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using the same function as both getter and setter\",\r\n \"text\": \"Using the same function as both getter and setter\\nFor properties which are read-write, the above getter functions can be replaced by a corresponding getter/setter, as follows:\\nname: function(val) {\\n if (!arguments.length) {\\n return this._name; // If there is no argument, use as a getter\\n }\\n this._name = val; // If there is a value argument, treat as a setter\\n},\\n\\nNote that when JsRender renders a template using a get/set property {{:name()}} it will always call the function as a getter, not as a setter. However the setter feature lets you modify the value of name() from code, using:\\nsomePerson.name(\\\"newName\\\"); // setter\\n\\nAlso, if you use the same View Model class with JsViews then the setter will be called:\\n\\nwhen the user modifies a value with two-way data-binding such as \\nwhen using $.observable(person).setProperty(\\\"name\\\", \\\"newName\\\") from code\\n(See JsViews Data/View Model for details, and alternative setter patterns.)\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Adding methods and computed properties to the View Model \",\r\n \"text\": \"Adding methods and computed properties to the View Model \\nTypically a View Model does not only provide getter (or get/set) properties – but also other methods or computed properties corresponding to the appropriate logic at that point in the application. For example, a View Model for a Person might include a selectPhone(...) method or a fullName() computed property.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Example: Using JsRender compiled View Models, with $.view.viewModels(...)\",\r\n \"text\": \"Example: Using JsRender compiled View Models, with $.view.viewModels(...)\\nThe built-in support in both JsRender and JsViews for compiled View Models makes it extremely easy to define View Model classes that include get/set properties using the pattern described above, along with any desired additional methods and computed properties. Simple calls to $.views.viewModels(...) allow you to compile View Model classes conforming to these patterns without having to manually write repetitive code for multiple such get/set properties.\\nAnother advantage of the compiled View Model classes is when working with (or migrating to) JsViews. In that context the classes automatically become fully-fledged MVVM classes, with a rich range of features – where the Views are observable data-linked templates.\\nFor details on $.views.viewModels see: Compiled View Models.\\nTo illustrate, let’s convert our sample above to use compiled View Models. At the same time we will add a person.addPhone(...) custom method to the Person View Model class, and we’ll illustrate calling a setter – name(...):\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Render template against a hierarchy of compiled View Model objects\",\r\n \"text\": \"Render template against a hierarchy of compiled View Model objects\\nbutton {margin-bottom: 9px;}\\n\\nChange name\\nAdd Phone\\n\\n\\n\\n\\n \\n \\n \\n \\n
Name:{{:name()}}
Street:{{:address().street()}}
Phones:\\n \\n {{for phones()}}\\n \\n {{/for}}\\n
\\n {{:number()}}\\n
\\n
\\n\\n// Compiled template\\nvar tmpl = $.templates(\\\"#personTmpl\\\");\\n\\n// Method for Person class\\nfunction addPhone(phoneNo) {\\n // Uses Phone() View Model constructor to create Phone instance\\n this.phones().push(Phone(phoneNo));\\n}\\n\\n// Compile Person View Model, with addPhone method\\nvar Person = $.views.viewModels({\\n getters: [\\\"name\\\", \\\"address\\\", \\\"phones\\\"],\\n extend: {addPhone: addPhone}\\n});\\n\\n// Compile Address View Model\\nvar Address = $.views.viewModels({getters: [\\\"street\\\"]});\\n\\n// Compile Phone View Model\\nvar Phone = $.views.viewModels({getters: [\\\"number\\\"]});\\n\\n// Instantiate View Model hierarchy using constructors\\nvar person = Person(\\n \\\"Pete\\\",\\n Address(\\\"1st Ave\\\"),\\n [Phone(\\\"111 111 1111\\\"), Phone(\\\"222 222 2222\\\")]\\n);\\n\\n// Render template against person object (instance of Person)\\n$(\\\"#result\\\").html(tmpl.render(person));\\n\\n// Button handlers\\n$(\\\"#changeName\\\").on(\\\"click\\\", function() {\\n person.name(\\\"newName\\\"); // Use the name(...) setter\\n $(\\\"#result\\\").html(tmpl.render(person));\\n});\\n\\n$(\\\"#addPhone\\\").on(\\\"click\\\", function() {\\n person.addPhone(\\\"xxx xxx xxxx\\\"); // Call the addPhone(...) method\\n $(\\\"#result\\\").html(tmpl.render(person));\\n});\\n\\n... {{:name()}} ...\\n\\nCompile View Model classes\\n\\n...\\n// Compile Person View Model, with addPhone method\\nvar Person = $.views.viewModels({\\n getters: [\\\"name\\\", \\\"address\\\", \\\"phones\\\"], // get/set properties\\n extend: {addPhone: addPhone} // Additional methods or properties\\n});\\n...\\n\\nInstantiate View Model hierarchy using constructors\\n\\nvar person = Person(\\n \\\"Pete\\\",\\n Address(\\\"1st Ave\\\"),\\n [Phone(\\\"111 111 1111\\\"), Phone(\\\"222 222 2222\\\")]\\n);\\n\\n\\nRender template against person object (instance of Person)\\n\\n$(\\\"#result\\\").html(tmpl.render(person));\\n\\nCall setter, call method...\\n\\n...\\nperson.name(\\\"newName\\\"); // Use the name(...) setter\\n\\n...\\nperson.addPhone(\\\"xxx xxx xxxx\\\"); // Call the addPhone(...) method\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"See also the corresponding sample with JsViews and data-linking (and this version with two-way binding).\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"For additional details and scenarios for compiled View Models, see:\",\r\n \"text\": \"For additional details and scenarios for compiled View Models, see:\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"See also:\",\r\n \"text\": \"See also:\\n\"\r\n }\r\n ]\r\n },\r\n \"helpersapi\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"$.views.helpers() is used to register helpers, accessed within templates using the syntax ~myhelper. See Using helpers for information about what helpers are, and some additional ways of providing them to templates.\\nThis topic provides more details.\\nWith $.views.helpers(...) you can:\\n\\nregister one or more helpers globally, to be used in any template\\nadd one or more helpers as private resources for a parent template\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Registering one or more helpers\",\r\n \"text\": \"Registering one or more helpers\\n\"\r\n },\r\n {\r\n \"_type\": \"api\",\r\n \"title\": \"$.views.helpers(...)\",\r\n \"text\": \"$.views.helpers(...)\\nRegister a helper, for use in any template with the syntax:~name\\n\\n$.views.helpers(\\\"format\\\", myFormatFunction);\\n\\nRegister multiple helpers\\n\\n$.views.helpers({\\n format: myFormatFunction,\\n utilities: {},\\n mode: \\\"filtered\\\"\\n});\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Here is an example using a ‘hierarchy’ of helpers…\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Register multiple helpers, including objects, etc.\",\r\n \"text\": \"Register multiple helpers, including objects, etc.\\n\\n\\n\\n {{:~format(title, true)}}\\n\\n - availability:\\n {{if ~mode===\\\"filtered\\\"}}\\n \\n {{:~utilities.subtractMax(sold) > 0\\n ? ~utilities.errorMessages.msg1\\n : \\\"immediate\\\"\\n }}\\n \\n {{/if}}\\n\\nfunction myFormatFunction(value, upper) {\\n return upper ? value.toUpperCase() : value.toLowerCase();\\n}\\n\\n$.views.helpers({\\n format: myFormatFunction,\\n utilities: {\\n maxCount: 23,\\n subtractMax: function(val) {\\n return val - this.maxCount;\\n },\\n errorMessages: {\\n msg1: \\\"not available\\\"\\n }\\n },\\n mode: \\\"filtered\\\"\\n});\\n\\nvar html = $(\\\"#myTemplate\\\").render({title: \\\"gizmo\\\", sold: 27});\\n\\n$(\\\"#result\\\").html(html);\\nHere is an example using a ‘hierarchy’ of helpers…\\n$.views.helpers({\\n ...\\n utilities: {\\n maxCount: 23,\\n subtractMax: function(val) {\\n return val - this.maxCount;\\n },\\n errorMessages: {\\n msg1: \\\"not available\\\"\\n }\\n },\\n ...\\n});\\n\\n{{:~utilities.subtractMax(sold) > 0\\n ? ~utilities.errorMessages.msg1\\n : \\\"immediate\\\"\\n}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Adding helpers as private resources for a parent template\",\r\n \"text\": \"Adding helpers as private resources for a parent template\\nYou can pass in an existing template as an additional parentTemplate parameter, on any call to $.views.helpers(...).\\nIn that way the helper you are registering becomes a ‘private helper resource’ for the parentTemplate, rather than being registered globally:\\n\"\r\n },\r\n {\r\n \"_type\": \"api\",\r\n \"title\": \"$.views.helpers(namedHelpers[, parentTemplate])\",\r\n \"text\": \"$.views.helpers(namedHelpers[, parentTemplate])\\nAdd one or more helpers as private resources for a parent template\\n\\n$.views.helpers({\\n format: myFormatFunction,\\n ...\\n}, parentTemplate);\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"See also:\",\r\n \"text\": \"See also:\\n\"\r\n }\r\n ]\r\n },\r\n \"helpers\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"What are helpers?\",\r\n \"text\": \"What are helpers?\\nJsRender templates are made up of HTML markup, text, and template tags. Template tags are used to evaluate data-paths or computed expressions, and insert those values into the rendered output.\\nBut often the values you will want to insert are not actually taken from the data, but rather from other parameters or metadata which you want to use. And often you will want to process the values, using helper functions or other code, e.g. for converting values to other formats, or for computed values.\\nHelpers, in JsRender, refers to any functions, objects, parameters or metadata which you want to provide, in addition to the actual data you passed to the render() method (or link() method if you are using JsViews).\\nHelpers can also be objects, arrays, etc.\\nYou access helpers by prepending the ~ character. Here are some examples:\\n{{:~myHelperValue}}\\n{{:~myHelperFunction(name, title)}}\\n{{for ~myHelperObject.mySortFunction(people, \\\"increasing\\\")}} ... {{/for}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Passing in helpers\",\r\n \"text\": \"Passing in helpers\\nThere are three ways to provide helpers:\\n\\nGlobal helpers – registered using $.views.helpers(myHelpers)\\nHelpers registered for a specific template – $.templates(\\\"mytmpl\\\", {markup: ..., helpers: myHelpers}\\nHelpers passed in on a specific render call – tmpl.render(data, myHelpers)\\n(Similarly you can pass helpers to JsViews link() calls)\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Contextual parameters\",\r\n \"text\": \"Contextual parameters\\nIn addition to providing helpers as above, you can also define contextual parameters within a template, which you access using the same ~someName syntax as for regular helpers.\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Global helper: $.views.helpers(...)\",\r\n \"text\": \"Global helper: $.views.helpers(...)\\n\\n\\n\\n {{:~format(name, true)}}\\n\\nfunction myFormatFunction(value, upper) {\\n return upper ? value.toUpperCase() : value.toLowerCase();\\n}\\n\\nvar myHelpers = {format: myFormatFunction};\\n\\n$.views.helpers(myHelpers);\\n\\nvar html = $(\\\"#personTemplate\\\").render({name: \\\"Robert\\\"});\\n\\n$(\\\"#person\\\").html(html);\\nvar myHelpers = {format: myFormatFunction};\\n\\n$.views.helpers(myHelpers);\\n\\n{{:~format(name, true)}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Helper resource for a specific template\",\r\n \"text\": \"Helper resource for a specific template\\n\\n\\n\\n {{:~format(name)}}\\n {{:~format(name, true)}}\\n\\nfunction myFormatFunction(value, upper) {\\n return upper ? value.toUpperCase() : value.toLowerCase();\\n}\\n\\nvar myHelpers = {format: myFormatFunction};\\n\\n$.templates({\\n mytmpl: {\\n markup: \\\"#personTemplate\\\",\\n helpers: myHelpers\\n }\\n});\\n\\nvar html = $.render.mytmpl({name: \\\"Robert\\\"});\\n\\n$(\\\"#person\\\").html(html);\\nvar myHelpers = {format: myFormatFunction};\\n\\n$.templates({\\n mytmpl: {\\n markup: \\\"#personTemplate\\\",\\n helpers: myHelpers\\n }\\n});\\n\\n{{:~format(name)}}\\n{{:~format(name, true)}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Passing helpers with a render() call\",\r\n \"text\": \"Passing helpers with a render() call\\n\\n\\n\\n {{:~format(name, true)}}\\n {{:~format(name)}}\\n\\nfunction myFormatFunction(value, upper) {\\n return upper ? value.toUpperCase() : value.toLowerCase();\\n}\\n\\nvar data = {name: \\\"Robert\\\"};\\n\\nvar myHelpers = {format: myFormatFunction};\\n\\nvar html = $(\\\"#personTemplate\\\").render(data, myHelpers); \\n\\n$(\\\"#person\\\").html(html);\\nvar myHelpers = {format: myFormatFunction};\\n\\nvar html = $(\\\"#personTemplate\\\").render(data, myHelpers); \\n\\n{{:~format(name, true)}}\\n{{:~format(name)}}\\n\\nSee template.render(...)\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"For additional details and scenarios see:\",\r\n \"text\": \"For additional details and scenarios see:\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"See also:\",\r\n \"text\": \"See also:\\n\"\r\n }\r\n ]\r\n },\r\n \"usetags\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"What is a custom tag?\",\r\n \"text\": \"What is a custom tag?\\nJsRender custom tags are named tags {{mytag ...}}, which you can register, and then use in your templates.\\nA tag renders itself as part of the template output. You determine how it renders, generally by providing either a render function or a template, when you declare your custom tag.\\nThe render function, or the template, can access both named parameters (props) and unnamed parameters (args), as in:\\n{{mytag arg0 arg1 namedProp1=xxx namedProp2=yyy}} ... {{/mytag}}\\n\\nIn fact it can also access the current data item – or even the whole hierarchy of views and data…\\nWhen you also use JsViews, custom tags acquire a whole new dimension. – They become tag controls, and you can build rich and complex single page apps cleanly and simply using custom tag controls – following an MVP or MVVM coding pattern.\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"1 - Simple custom tag using just a function\",\r\n \"text\": \"1 - Simple custom tag using just a function\\n\\n\\n\\n This is the title:{{boldp title /}}\\n\\nfunction renderBoldP(value) {\\n return \\\"\\\" + value + \\\"\\\";\\n}\\n\\n$.views.tags(\\\"boldp\\\", renderBoldP);\\n\\nvar team = {\\n title: \\\"The A Team\\\"\\n};\\n\\nvar html = $(\\\"#teamTemplate\\\").render(team);\\n\\n$(\\\"#team\\\").html(html);\\nThe function is the render method for the tag\\n\\nDeclaring the custom tag\\n\\nfunction renderBoldP(value) {\\n return \\\"\\\" + value + \\\"\\\";\\n}\\n\\n$.views.tags(\\\"boldp\\\", renderBoldP);\\n\\nUsing the tag\\n\\nThis is the title:{{boldp title /}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Note: the this pointer within the tag render function is the instance of the tag, and can be used in more advanced usage, as in the next two examples:\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Wrapping block content using a function-based custom tag\",\r\n \"text\": \"Wrapping block content using a function-based custom tag\\nFirst of all – what if we want our tag to be used as a block tag, and to render itself by wrapping the rendered block content with the ‘bold p’ html –

...

as in:\\n{{boldp}}\\n This is inside our block content:
\\n {{:title}}\\n{{/boldp}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"2 - Rendering block content from a custom tag function\",\r\n \"text\": \"2 - Rendering block content from a custom tag function\\n\\n\\n\\n {{boldp}}\\n This is inside our block content:
\\n {{:title}}\\n {{/boldp}}\\n\\nfunction renderBoldP(val) {\\n return \\\"\\\" + this.tagCtx.render(val) + \\\"\\\";\\n}\\n\\n$.views.tags(\\\"boldp\\\", renderBoldP);\\n\\nvar team = {\\n title: \\\"The A Team\\\"\\n};\\n\\nvar html = $(\\\"#teamTemplate\\\").render(team);\\n\\n$(\\\"#team\\\").html(html);\\nTo render the block content, we call this.tagCtx.render(val):\\nfunction renderBoldP(val) {\\n return \\\"

\\\" + this.tagCtx.render(val) + \\\"

\\\";\\n}\\n\\n$.views.tags(\\\"boldp\\\", renderBoldP);\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"As well as calling the render() method of this.tagCtx, you can access this.tagCtx.args, this.tagCtx.props, this.tagCtx.view.data and more…\\nThe tagCtx.args are the unnamed parameters. So in this example, there are two of them:\\n{{sometag title name}}\\n\\nIn addition to being accessible as tagCtx.args, unnamed parameters are also passed directly as parameters to the render method (if your tag is using one):\\nfunction sometagRenderMethod(title, name) {\\n // Here, this.tagCtx.args[1] and the name parameter are the same thing\\n}\\n\\nNow here is an example which has one unnamed parameter and two named parameters. You can access named parameters from tagCtx.props:\\n{{range members start=2 end=4}}\\n\\nWe’ll use that in our third sample, to show accessing properties from the render function of the tag:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"3 - Accessing tagCtx properties from the tag render function\",\r\n \"text\": \"3 - Accessing tagCtx properties from the tag render function\\n\\n\\n\\n

{{:title}}

\\n
    \\n {{range members start=1 end=2}} \\n
  • \\n {{:name}}\\n
  • \\n {{/range}}\\n
\\n\\n$.views.tags(\\\"range\\\", function(array) {\\n var ret = \\\"\\\",\\n start = this.tagCtx.props.start,\\n end = this.tagCtx.props.end;\\n for (var i = start; i <= end; i++) {\\n // Render tag content, for this data item\\n ret += this.tagCtx.render(array[i]);\\n }\\n return ret;\\n});\\n\\nvar team = {\\n title: \\\"The A Team\\\",\\n members: [\\n {name: \\\"Robert\\\"},\\n {name: \\\"Sarah\\\"},\\n {name: \\\"Xavier\\\"},\\n {name: \\\"Adriana\\\"}\\n ]\\n};\\n\\nvar html = $(\\\"#teamTemplate\\\").render(team);\\n\\n$(\\\"#team\\\").html(html);\\nThis sample defines a {{range}} tag which iterates over an array which you pass as (unnamed) parameter. It also allows you to set named parameters start and end, to determine the range of iteration. (See also the range sample, for a more advanced implementation of a similar custom tag.)\\nYou call it like this:\\n{{range members start=1 end=2}}\\n ...\\n{{/range}}\\n\\nAnd the render function code accesses context to get at those named and unnamed parameters… :\\n$.views.tags(\\\"range\\\", function(array) {\\n ...\\n var start = this.tagCtx.props.start,\\n ...\\n // Render tag content, for this data item\\n ret += this.tagCtx.render(array[i]);\\n ...\\n return ret;\\n});\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using a tag template instead of a render function\",\r\n \"text\": \"Using a tag template instead of a render function\\nIf the tag definition includes a template, but no render method, then the template will be used to render the tag.\\nLet’s re-implement all three examples above using custom tags which use templates instead of render functions.\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"1b - Simple custom tag using just a template\",\r\n \"text\": \"1b - Simple custom tag using just a template\\n\\n\\n\\n {{boldp title /}}\\n\\n$.views.tags(\\\"boldp\\\", {\\n template: \\\"{{:~tag.tagCtx.args[0]}}\\\"\\n});\\n\\nvar team = {\\n title: \\\"The A Team\\\"\\n};\\n\\nvar html = $(\\\"#teamTemplate\\\").render(team);\\n\\n$(\\\"#team\\\").html(html);\\nDeclaring the custom tag\\n$.views.tags(\\\"boldp\\\", {\\n template: \\\"

{{:~tag.tagCtx.args[0]}}

\\\"\\n});\\n\\nAs you see, the template is accessing the unnamed parameter tagCtx.args[0].\\nThe result is identical to the other implementation using a function. You call it just the same:\\n{{boldp title /}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"2b - Rendering block content from a custom tag template\",\r\n \"text\": \"2b - Rendering block content from a custom tag template\\n\\n\\n\\n {{boldp}}\\n This is the title:
\\n {{:title}}\\n {{/boldp}}\\n\\n$.views.tags(\\\"boldp\\\", {\\n template: \\\"{{include tmpl=~tag.tagCtx.content/}}\\\"\\n});\\n\\nvar team = {\\n title: \\\"The A Team\\\"\\n};\\n\\nvar html = $(\\\"#teamTemplate\\\").render(team);\\n\\n$(\\\"#team\\\").html(html);\\nTo render block content, we use {{include tmpl=~tag.tagCtx.content/}}\\ntemplate: \\\"

{{include tmpl=~tag.tagCtx.content/}}

\\\"\\n\\nHere we are accessing the content property on the tagCtx, which provides a compiled template for the block content.\\nIt is also made available as a content property on the view object – and can be accessed from within a template using #content – which is an example of a view path – equivalent to #view.content. You can try out that alternative syntax by choosing Try it and changing the template above to

{{include tmpl=#content/}}

.\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Finally let’s re-implement the third example using just a template.\\nEven this example can be implemented as a custom tag which has no code at all. – Just a template, which is also able to access all the context that we were able to access from code in our render() function above.\\nThis illustrates the power of declarative templates…\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \" 3b - Accessing more context from the tag template\",\r\n \"text\": \" 3b - Accessing more context from the tag template\\n\\n\\n\\n

{{:title}}

\\n
    \\n {{range members start=1 end=2}} \\n
  • \\n {{:name}}\\n
  • \\n {{/range}}\\n
\\n\\n$.views.tags(\\\"range\\\", {\\n template: \\n \\\"{{for ~tag.tagCtx.args[0]}}\\\" +\\n \\\"{{if #index >= ~tag.tagCtx.props.start && #index <= ~tag.tagCtx.props.end}}\\\" +\\n \\\"{{include tmpl=~tag.tagCtx.content/}}\\\" +\\n \\\"{{/if}}\\\" +\\n \\\"{{/for}}\\\"\\n});\\n\\nvar team = {\\n title: \\\"The A Team\\\",\\n members: [\\n {name: \\\"Robert\\\"},\\n {name: \\\"Sarah\\\"},\\n {name: \\\"Xavier\\\"},\\n {name: \\\"Adriana\\\"}\\n ]\\n};\\n\\nvar html = $(\\\"#teamTemplate\\\").render(team);\\n\\n$(\\\"#team\\\").html(html);\\nThe template accesses the same context as the function code above, to get at those named and unnamed parameters… :\\n{{for ~tag.tagCtx.args[0]}}\\n {{if #index >= ~tag.tagCtx.props.start && #index <= ~tag.tagCtx.props.end}}\\n {{include tmpl=~tag.tagCtx.content/}}\\n {{/if}}\\n{{/for}}\\n\\nThen after filtering for the items within the chosen range, using nested {{for}}{{if} tags, it renders the original block content for those items using {{include tmpl=~tag.tagCtx.content/}}.\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Custom tags using both a render function and a template\",\r\n \"text\": \"Custom tags using both a render function and a template\\nIf there is both a template and a render method, then the template will only be used if the render method returns undefined\\nLet’s take our {{range}} example using a render function, but provide a template which will be used as “fallback” rendering for the tag in the case when there are no items to render in the chosen range:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"A render() function and a template as \\\"fallback\\\"\",\r\n \"text\": \"A render() function and a template as \\\"fallback\\\"\\n\\n\\n\\n

Members 2 to 4

\\n
    \\n {{range members start=1 end=3}} \\n
  • \\n {{:name}}\\n
  • \\n {{/range}}\\n
\\n\\n

Members 5 to 8

\\n
    \\n {{range members start=4 end=7}} \\n
  • \\n {{:name}}\\n
  • \\n {{/range}}\\n
\\n\\n$.views.tags({\\n range: {\\n render: function(array) {\\n var ret = \\\"\\\",\\n start = this.tagCtx.props.start,\\n end = this.tagCtx.props.end;\\n for (var i = start; i <= end; i++) {\\n if (array[i]) {\\n // Render tag block content, for this data item\\n ret += this.tagCtx.content.render(array[i]);\\n }\\n }\\n return ret || undefined;\\n },\\n template: \\\"Nothing to render\\\"\\n }\\n});\\n\\nvar team = {\\n title: \\\"The A Team\\\",\\n members: [\\n {name: \\\"Robert\\\"},\\n {name: \\\"Sarah\\\"},\\n {name: \\\"Xavier\\\"},\\n {name: \\\"Adriana\\\"}\\n ]\\n};\\n\\nvar html = $(\\\"#teamTemplate\\\").render(team);\\n\\n$(\\\"#team\\\").html(html);\\n\\nFirst we will change the original code to test whether the item exists in the array, before rendering the block content.\\nAnd secondly, we will make sure that when there is an item we do render the block content and not the template. So we call this.tagCtx.content.render(array[i]), rather than this.tagCtx.render(array[i]).\\nThat’s because this.tagCtx.render(...) will actually look to see if there is template associated with the tag, (either a template on the tag definition, or a tmpl property on the tag) – in which case it will render that template and not the block content…\\nfor (var i = start; i <= end; i++) {\\n if (array[i]) {\\n // Render tag block content, for this data item\\n ret += this.tagCtx.content.render(array[i]);\\n }\\n}\\n\\nFinally, if there are no items to render, we will return undefined, so the tag will fall back on the template rendering.\\nreturn ret || undefined;\\n\\nAnd here is the “fallback” template:\\ntemplate: \\\"
  • Nothing to render
  • \\\"\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Custom tags and 'tag controls'\",\r\n \"text\": \"Custom tags and 'tag controls'\\nIf you use JsViews, your custom tag can be developed into a fully functional tag control, with its own life-cycle, properties and methods, etc. It can be used as a presenter according to the MVP pattern.\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"See also:\",\r\n \"text\": \"See also:\\n\"\r\n }\r\n ]\r\n },\r\n \"convertersapi\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"See Using converters for an overview of what converters are, and some examples.\\nThis topic provided more details.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using custom or built-in converters\",\r\n \"text\": \"Using custom or built-in converters\\nIn JsRender, a converter is a convenient way of processing or formatting a data-value, or the result of expression evaluation.\\nYou use built-in converters to HTML-encode, attribute-encode, or URL-encode:\\n{{html:movie.description}} - This data is HTML encoded\\n{{>movie.description}} - (Alternative syntax) - This data is HTML encoded\\n\\n{{url:~getTheFilePath()}} - This expression will be URL-encoded\\n\\nAnd you can register custom converters. For example you might register a date formatter or an upper-case converter:\\n{{daymonth:invoice.date}} - This date uses my 'daymonth' formatter \\n{{upper:name}} - This uses my 'upper' converter \\n\\n(See: sample.)\\nYou can also use converters with any JsRender tag, not just the {{: ...}} tag, using the following syntax:\\n{{sometag convert='myconverter' ...}}\\n\\n(See: sample.)\\nNote: With JsViews, you can use converters with two-way data-binding, and you will have a convert and a convertBack converter – one for each direction.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Registering converters\",\r\n \"text\": \"Registering converters\\n$.views.converters() is used to register converters.\\nWith $.views.converters(...) you can:\\n\\nregister one or more converters globally, to be used in any template\\nadd one or more converters as private resources for a parent template\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Registering one or more converters\",\r\n \"text\": \"Registering one or more converters\\nA simple sample of registering a converter is shown here.\\n\"\r\n },\r\n {\r\n \"_type\": \"api\",\r\n \"title\": \"$.views.converters(...)\",\r\n \"text\": \"$.views.converters(...)\\nRegister a converter\\n\\n$.views.converters(\\\"upper\\\", function(val) {\\n return val.toUpperCase();\\n});\\n\\n{{upper: \\\"upper case: \\\" + nickname}}\\n\\nRegister multiple converters\\n\\n$.views.converters({\\n upper: function(val) {...},\\n lower: function(val) {...}\\n});\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Adding converters as private resources for a parent template\",\r\n \"text\": \"Adding converters as private resources for a parent template\\nYou can pass in an existing template as an additional parentTemplate parameter, on any call to $.views.converters(...).\\nIn that way the converter you are registering becomes a ‘private converter resource’ for the parentTemplate, rather than being registered globally:\\n\"\r\n },\r\n {\r\n \"_type\": \"api\",\r\n \"title\": \"$.views.converters(...) — adding to parent template\",\r\n \"text\": \"$.views.converters(...) — adding to parent template\\nRegister a converter as private resources for a parent template\\n\\n$.views.converters(\\n \\\"upper\\\",\\n function(val) { ... },\\n parentTemplate\\n);\\n\\nAdd one or more converters as private resources for a parent template\\n\\n$.views.converters({\\n upper: function(val) {...},\\n lower: function(val) {...}\\n}, parentTemplate);\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Unregister a named converter\",\r\n \"text\": \"Unregister a named converter\\nTo unregister a previously registered converter, pass null to $.views.converters():\\n$.views.converters(\\\"myCvt\\\", null);\\n// Named converter \\\"myCvt\\\" is no longer registered\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Converter functions\",\r\n \"text\": \"Converter functions\\nIn most cases a converter function will return a computed value based on the input parameter val:\\nfunction myConverter(val) {\\n ... \\n return computedVal; // converted/encoded/formatted value for 'val'\\n}\\n\\nwhere val comes from the data value or expression passed to the tag {{myconverter: someExpression}}.\\n(See: sample.)\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Converter function signature\",\r\n \"text\": \"Converter function signature\\nHowever a converter can access multiple tag arguments, to produce the computed value which it provides to the tag. Furthermore, the this pointer within the converter function is the instance of the tag, which allows it to access much more, including named tag parameters (this.tagCtx.props...), the full data object (this.tagCtx.view.data), and more…\\nfunction myConverter(arg1, arg2, arg3 ...) {\\n var tag = this;\\n var namedTagParameters = tag.tagCtx.props; \\n ...\\n return computedArg1; // converted value for 'arg1' passed to tag\\n}\\n\\n(See fullname sample.)\\nHere is the converterFn API definition:\\n\"\r\n },\r\n {\r\n \"_type\": \"api\",\r\n \"title\": \"function converterFn(val, ...) {...}\",\r\n \"text\": \"function converterFn(val, ...) {...}\\nA converter function registered using $.views.converters(...)\\nConverter function:parameters: one or more tag argumentsthis pointer: the tag instancecomputes return value: which is passed to tag as first argument\\n\\nfunction myConverterFn(val1, val2, ...) {\\n var tag = this;\\n var tagProperties = tag.tagCtx.props;\\n ...\\n return ...;\\n}\\n\\n$.views.converters(\\\"myconverter\\\", myConverterFn);\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using converters with other tags\",\r\n \"text\": \"Using converters with other tags\\nA converter can be used on any tag, thanks to the syntax\\n{{sometag ... convert=...}}\\n\\nwhere sometag can be any custom tag, or a built-in tag such as {{if}} or {{for}}.\\nSee the sample using {{for people convert='odd'}} to render only odd (or even) items in an array.\\n(Note: This syntax can actually be used with the {{: ...}} tag too – by writing {{:name convert='upper'}}…)\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Example: a converter for {{if}}\",\r\n \"text\": \"Example: a converter for {{if}}\\nHere is an advanced sample: an \\\"inlist\\\" converter for {{if}}.\\n\\nIt accepts an item argument and a list argument, and an optional field named property\\nIt returns true if the item is found in the list\\nIf there is a field specified, it takes the value of that field (property) on the item and searches for it in the list\\n\\nNote that the converter gets called once for the first {{if}} tag block and once for each subsequent {{else}} block.\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"'inlist' converter for {{if}} tag\",\r\n \"text\": \"'inlist' converter for {{if}} tag\\n\\n\\n\\n
      \\n {{for people}}\\n
    • \\n {{:name}}:\\n {{if #data ~root.team convert='inlist'}}\\n Team member\\n {{else #data ~root.reserve field=\\\"name\\\"}}\\n Reserve\\n {{else}}\\n Not in team\\n {{/if}}\\n
    • \\n {{/for}}\\n
    \\n\\nvar teamTmpl = $.templates(\\\"#teamTmpl\\\");\\n\\n// Converter function for looking for an item (first argument of tag) in a list (second argument of tag)\\nfunction inlistConverter(item, list) {\\n // If no arguments, this is the final {{else}}\\n if (!list) {\\n return true; // Final else, so return true\\n }\\n\\n var field = this.tagCtx.props.field;\\n var l = list.length;\\n\\n // If the tag has a 'field' property, look for the value of that field among the list items\\n if (field) {\\n while (l--) {\\n if (item[field] === list[l]) {\\n return true; // Return true if found\\n }\\n }\\n }\\n\\n // If no field property, look for the item among the list items\\n else {\\n while (l--) {\\n if (item === list[l]) {\\n return true; // Return true if found\\n }\\n }\\n }\\n return false; // Not found\\n}\\n\\n// Register 'inlist' converter just for the 'teamTmpl' template \\n$.views.converters({inlist: inlistConverter}, teamTmpl);\\n\\n// Define model \\nvar model= {people: [\\n {name: \\\"Jo\\\"},\\n {name: \\\"Liza\\\"},\\n {name: \\\"Eli\\\"},\\n {name: \\\"Pete\\\"},\\n {name: \\\"Zoey\\\"}\\n ],\\n // Specify list of reserves, by name\\n reserve: [\\\"Eli\\\", \\\"Liza\\\"]\\n};\\n\\n// Specify array of team members\\nmodel.team = [model.people[0], model.people[3]];\\n\\n$(\\\"#result\\\").html(teamTmpl.render(model));\\n\\n ...\\n {{for people}}\\n ...\\n {{if #data ~root.team convert='inlist'}}\\n ...\\n {{else #data ~root.reserve field=\\\"name\\\"}}\\n ...\\n {{else}}\\n ...\\n {{/for}}\\n ...\\n \\n\\n// Converter function for looking for an item (first argument of tag) in a list (second argument of tag)\\nfunction inlistConverter(item, list) {\\n // If no arguments, this is the final {{else}}.\\n ... // Return true\\n\\n // If the tag has a 'field' property, look for the value of that field among the list items\\n ... // Return true if found\\n\\n // If no field property, look for the item among the list items\\n ... // Return true if found\\n\\n return false; // Not found\\n}\\n\\n// Register 'inlist' converter just for the 'teamTmpl' template \\n$.views.converters({inlist: inlistConverter}, teamTmpl);\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using helper functions, or dynamically assigning converters\",\r\n \"text\": \"Using helper functions, or dynamically assigning converters\\nThe convert=... syntax allows you to assign a converter function without it being registered by name. For example it can be a data method or a helper function – such as {{sometag ... convert=~myConverterHelper}}.\\n(You can do this with the {{: ...}} tag too – by writing {{: ... convert=~myConverterHelper}}…)\\nYou can even assign a converter dynamically. For example you can write: {{sometag ... convert=~getConverter(...)}}, where the getConverter() helper might return either a string (for a converter registered by name) or a function to be used as converter.\\nTo illustrate, here is a modified version of the previous sample, using {{if ... convert=~getConverter()}}:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Dynamically assigning a converter\",\r\n \"text\": \"Dynamically assigning a converter\\n\\n\\n\\n
      \\n {{for people}}\\n
    • \\n {{:name}}:\\n {{if #data ~root.team convert=~getConverter()}}\\n Team member\\n {{else #data ~root.reserve field=\\\"name\\\"}}\\n Reserve\\n {{else}}\\n Not in team\\n {{/if}}\\n
    • \\n {{/for}}\\n
    \\n\\nvar teamTmpl = $.templates(\\\"#teamTmpl\\\");\\n\\n// Converter function for looking for an item (first argument of tag) in a list (second argument of tag)\\nfunction inlistConverter(item, list) {\\n // If no arguments, this is the final {{else}}\\n if (!list) {\\n return true; // Final else, so return true\\n }\\n\\n var field = this.tagCtx.props.field;\\n var l = list.length;\\n\\n // If the tag has a 'field' property, look for the value of that field among the list items\\n if (field) {\\n while (l--) {\\n if (item[field] === list[l]) {\\n return true; // Return true if found\\n }\\n }\\n }\\n\\n // If no field property, look for the item among the list items\\n else {\\n while (l--) {\\n if (item === list[l]) {\\n return true; // Return true if found\\n }\\n }\\n }\\n return false; // Not found\\n}\\n\\n// Helper to dynamically assign converters\\nfunction getConverter() {\\n return inlistConverter; // For this sample just return `inlistConverter` every time\\n}\\n\\n// Register 'getConverter' helper just for the 'teamTmpl' template \\n$.views.helpers(\\\"getConverter\\\", getConverter, teamTmpl);\\n\\n// Define model \\nvar model= {people: [\\n {name: \\\"Jo\\\"},\\n {name: \\\"Liza\\\"},\\n {name: \\\"Eli\\\"},\\n {name: \\\"Pete\\\"},\\n {name: \\\"Zoey\\\"}\\n ],\\n // Specify list of reserves, by name\\n reserve: [\\\"Eli\\\", \\\"Liza\\\"]\\n};\\n\\n// Specify array of team members\\nmodel.team = [model.people[0], model.people[3]];\\n\\n$(\\\"#result\\\").html(teamTmpl.render(model));\\n\\n// Converter function\\nfunction inlistConverter(item, list) { ... }\\n\\n// Helper to dynamically assign converters\\nfunction getConverter() {\\n return inlistConverter; // For this sample just return `inlistConverter` every time\\n}\\n\\n// Register 'getConverter' helper just for the 'teamTmpl' template \\n$.views.helpers(\\\"getConverter\\\", getConverter, teamTmpl);\\n\\n{{if #data ~root.team convert=~getConverter()}}\\n ...\\n{{/if}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Built-in converters:\",\r\n \"text\": \"Built-in converters:\\nJsRender has the following built-in converters/encoders:\\n\\nBuilt-in HTML encoder: {{html: ...}} – accessed programmatically as $.views.converters.html()\\nBuilt-in attribute encoder: {{attr: ...}} – accessed programmatically as $.views.converters.attr()\\nBuilt-in URL encoder: {{url: ...}} – accessed programmatically as $.views.converters.url()\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Built-in HTML encoder\",\r\n \"text\": \"Built-in HTML encoder\\nJsRender includes an HTML encoder, which you can use programmatically as follows:\\nvar myHtmlEncodedString = $.views.converters.html(myString);\\n\\nThe same encoder is accessed declaratively as a converter, as in the following two examples:\\n{{html:myExpression}}\\n\\n{{>myExpression}}\\n\\nIn fact {{>...}} is exactly equivalent to {{html:...}} and is provided as a simpler syntax for HTML encoding values taken from data or from expressions and rendered within HTML content.\\n(Note: the {{> ...}} tag should be used in place of the {{: ...}} tag whenever the data being rendered is not full trusted – in order to prevent HTML injection attacks.)\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Calling the HTML encoder\",\r\n \"text\": \"Calling the HTML encoder\\nShow result\\n\\n\\nvar value = \\\"< > ' \\\\\\\" &\\\";\\nvar result = $.views.converters.html(value);\\n\\n$(\\\"#show\\\").on(\\\"click\\\", function() {\\n alert(result);\\n});\\n\\nvar value = \\\"< > ' \\\\\\\" &\\\";\\n\\nvar result = $.views.converters.html(value);\\n\\nalert(result);\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"api\",\r\n \"title\": \"HTML encoder\",\r\n \"text\": \"HTML encoder\\nReturns the HTML-encoded string\\n\\nvar encoder = $.views.converters.html;\\nvar encodedString = encoder(myString);\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Built-in attribute encoder\",\r\n \"text\": \"Built-in attribute encoder\\nJsRender includes an encoder intended for use when attribute encoding is needed. You can use it programmatically as follows:\\nvar myAttributeEncodedString = $.views.converters.attr(myString);\\n\\nThe same encoder is accessed by declaratively as a converter:\\n{{attr:myExpression}}\\n\\nA typical use case would be to encode an HTML attribute value in a template:\\n
    ...
    \\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Calling the 'attribute' encoder\",\r\n \"text\": \"Calling the 'attribute' encoder\\nShow result\\n\\n\\nvar value = \\\"< > ' \\\\\\\" & =\\\";\\nvar result = $.views.converters.attr(value);\\n\\n$(\\\"#show\\\").on(\\\"click\\\", function() {\\n alert(result);\\n});\\n\\nvar value = \\\"< > ' \\\\\\\" &\\\";\\n\\nvar result = $.views.converters.attr(value);\\n\\nalert(result);\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"api\",\r\n \"title\": \"Attribute encoder\",\r\n \"text\": \"Attribute encoder\\nReturns the attribute-encoded string\\n\\nvar encoder = $.views.converters.attr;\\nvar encodedString = encoder(myString);\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Built-in URL encoder\",\r\n \"text\": \"Built-in URL encoder\\nJsRender includes a URL encoder, which you can use programmatically as follows:\\nvar myUrlEncodedString = $.views.converters.url(myString);\\n\\nThe same encoder is accessed by declaratively as a converter:\\n{{url:myExpression}}\\n\\nA typical use case would be to encode a HTML URL attribute value in a template:\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Calling the 'url' encoder\",\r\n \"text\": \"Calling the 'url' encoder\\nShow result\\n\\nvar value = \\\"<_>_\\\\\\\"_ \\\";\\nvar result = $.views.converters.url(value);\\n\\n$(\\\"#show\\\").on(\\\"click\\\", function() {\\n alert(result);\\n});\\n\\nvar value = \\\"<_>_\\\\\\\"_ \\\";\\n\\nvar result = $.views.converters.url(value);\\n\\nalert(result);\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"api\",\r\n \"title\": \"URL encoder\",\r\n \"text\": \"URL encoder\\nReturns the URL-encoded string\\n\\nvar encoder = $.views.converters.url;\\nvar encodedString = encoder(myString);\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"See also:\",\r\n \"text\": \"See also:\\n\"\r\n }\r\n ]\r\n },\r\n \"converters\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"What are converters?\",\r\n \"text\": \"What are converters?\\nIn JsRender, a converter is a convenient way of processing or formatting data-value, or the result of expression evaluation.\\nYou use built-in converters to HTML-encode, attribute-encode, or URL-encode:\\n{{html:movie.description}} - This data is HTML encoded\\n{{>movie.description}} - (Alternative syntax) - This data is HTML encoded\\n\\n{{url:~getTheFilePath()}} - This expression will be URL-encoded\\n\\nAnd you can register custom converters. For example you might register a date formatter or an upper-case converter:\\n{{daymonth:invoice.date}} - This date uses my 'daymonth' formatter \\n{{upper:name}} - This uses my 'upper' converter \\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Built-in converters\",\r\n \"text\": \"Built-in converters\\nJsRender has the following built-in converters – based on encoders:\\n\\nBuilt-in HTML encoder: {{> ...}}\\nBuilt-in attribute encoder: {{attr ...}}\\nBuilt-in URL encoder: {{url ...}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Registering a converter\",\r\n \"text\": \"Registering a converter\\nYou can register your own custom converters, using $.views.converters() as in:\\n$.views.converters(\\\"upper\\\", function(val) {\\n // Convert data-value or expression to upper case\\n return val.toUpperCase();\\n});\\n\\nTo use the \\\"upper\\\" converter with the {{:...}} tag, you write:\\n{{upper:...}}\\n\\nHere it is in a sample:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"A simple converter\",\r\n \"text\": \"A simple converter\\n\\n\\n\\n Name: {{:name}}. Upper case nickname: {{upper:nickname}}\\n

    \\n {{upper: \\\"This will be upper case too\\\"}} \\n\\n$.views.converters(\\\"upper\\\", function(val) {\\n return val.toUpperCase();\\n});\\n\\nvar person = {name: \\\"Robert\\\", nickname: \\\"Bob\\\"};\\n\\nvar html = $(\\\"#personTemplate\\\").render(person);\\n\\n$(\\\"#person\\\").html(html);\\n$.views.converters(\\\"upper\\\", function(val) {\\n return val.toUpperCase();\\n});\\n\\nName: {{:name}}. Upper case nickname: {{upper:nickname}}\\n...\\n{{upper: \\\"This will be upper case too\\\"}} \\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Converter arguments\",\r\n \"text\": \"Converter arguments\\nA converter can access any number of tag arguments, to produce the computed value which it provides to the tag:\\n$.views.converters(\\\"myConverter\\\", function(arg1, arg2, arg3 ...) {\\n\\nFurthermore, the this pointer within the converter function is the instance of the tag, which allows it to access much more, including named tag parameters (this.tagCtx.props...), the full data object (this.tagCtx.view.data), and more…\\nThe following sample shows a \\\"fullname\\\" converter, which provides a computed full name based on the first two tag arguments (first and last) and an optional named tag parameter reverse=true:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Full name converter – accessing multiple arguments\",\r\n \"text\": \"Full name converter – accessing multiple arguments\\n\\n\\n\\n

    {{fullname:first last}}

    \\n

    {{fullname:first last reverse=true}}

    \\n\\n$.views.converters(\\\"fullname\\\", function(first, last) {\\n var reverse = this.tagCtx.props.reverse; \\n if (reverse) {\\n return last.toUpperCase() + \\\" \\\" + first;\\n }\\n return first + \\\" \\\" + last;\\n});\\n\\nvar person = {first: \\\"Xavier\\\", last: \\\"Prieto\\\"};\\n\\nvar html = $(\\\"#personTemplate\\\").render(person);\\n\\n$(\\\"#person\\\").html(html);\\n$.views.converters(\\\"fullname\\\", function(first, last) {\\n var reverse = this.tagCtx.props.reverse; \\n if (reverse) {\\n return last.toUpperCase() + \\\" \\\" + first;\\n }\\n return first + \\\" \\\" + last;\\n});\\n\\n... {{fullname:first last}}\\n... {{fullname:first last reverse=true}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using converters with other tags\",\r\n \"text\": \"Using converters with other tags\\nA converter can be used on any tag, thanks to the syntax\\n{{sometag ... convert=...}}\\n\\nwhere sometag can be any custom tag, or a built-in tag such as {{if}}.\\n(Note: When using JsViews two-way binding, similar syntax is available for convertBack: convertBack=....)\\nFor example, you could register an \\\"inList\\\" converter which returns true if item is found in itemList:\\n{{if convert='inList' item itemList}}...{{/if}}\\n\\nThe following sample shows named converters used with the {{for ...}} tag – to iterate over an array and show only even or odd items:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Using converters with the {{for}} tag\",\r\n \"text\": \"Using converters with the {{for}} tag\\n\\n\\n\\n \\n \\n \\n
      {{for people convert='odd'}}
    • {{:name}}
    • {{/for}}
      {{for people convert='even'}}
    • {{:name}}
    • {{/for}}
    \\n\\n$.views.converters({\\n odd: function(arr) {\\n // return only the odd items in the array\\n return arr.filter(function(elem, index) {\\n return (index + 1)%2;\\n });\\n },\\n even: function(arr) {\\n // return only the even items in the array\\n return arr.filter(function(elem, index) {\\n return index%2;\\n });\\n }\\n});\\n\\nvar model= {people: [\\n {name: \\\"Jo1\\\"},\\n {name: \\\"Jo2\\\"},\\n {name: \\\"Jo3\\\"},\\n {name: \\\"Jo4\\\"},\\n {name: \\\"Jo5\\\"},\\n {name: \\\"Jo6\\\"}\\n]};\\n\\nvar html = $(\\\"#myTmpl\\\").render(model);\\n\\n$(\\\"#result\\\").html(html);\\n$.views.converters({\\n odd: function(arr) {\\n // return only the odd items in the array\\n return arr.filter(function(elem, index) {\\n return (index + 1)%2;\\n });\\n },\\n even: function(arr) {\\n // return only the even items in the array\\n return arr.filter(function(elem, index) {\\n return index%2;\\n });\\n }\\n});\\n\\n...\\n{{for people convert='odd'}}\\n...\\n{{for people convert='even'}}\\n...\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using helper functions or data methods as converters\",\r\n \"text\": \"Using helper functions or data methods as converters\\nThe convert=... syntax not only works on any tag, but also allows you to use not only registered converters, by name, as in\\n{{for people convert='odd'}}\\n\\nbut alternatively to use helpers, or data methods as in\\n{{for people convert=filter.odd}} // Using data method\\n\\nYou can also use that approach on {{:..}} tags as in\\n{{:name convert=~hlp.bold}} // Using helper\\n\\nNote that the one tag which does not support this syntax is {{>...}} – for which you would need instead to write:\\n{{>~hlp.bold(name)}} // Using helper \\n\\nHere is a modified version of the sample above, using helpers and data methods:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"text\": \"\\n\\n\\n \\n \\n \\n
      \\n {{for people convert=filter.odd}} {{!-- using data method --}}\\n
    • \\n {{:name convert=~hlp.bold}} {{!-- using helper --}}\\n
    • \\n {{/for}}\\n
      \\n {{for people convert=filter.even}} {{!-- using data method --}}\\n
    • \\n {{:name convert=~hlp.italic}} {{!-- using helper --}}\\n
    • \\n {{/for}}\\n
    \\n\\nvar helpers = {\\n hlp: {\\n bold: function(val) {\\n return \\\"\\\" + val + \\\"\\\";\\n },\\n italic: function(val) {\\n return \\\"\\\" + val + \\\"\\\";\\n }\\n }\\n};\\n\\nvar model= {people: [\\n {name: \\\"Jo1\\\"},\\n {name: \\\"Jo2\\\"},\\n {name: \\\"Jo3\\\"},\\n {name: \\\"Jo4\\\"}\\n ],\\n filter: {\\n odd: function(arr) {\\n // return only the odd items in the array\\n return arr.filter(function(elem, index) {\\n return (index + 1)%2;\\n });\\n },\\n even: function(arr) {\\n // return only the even items in the array\\n return arr.filter(function(elem, index) {\\n return index%2;\\n });\\n }\\n }\\n};\\n\\nvar html = $(\\\"#myTmpl\\\").render(model, helpers);\\n\\n$(\\\"#result\\\").html(html);\\n...\\n{{for people convert=filter.odd}} {{!-- using data method --}}\\n
  • \\n {{:name convert=~hlp.bold}} {{!-- using helper --}}\\n...\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"For additional details and scenarios see:\",\r\n \"text\": \"For additional details and scenarios see:\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"See also the following samples:\",\r\n \"text\": \"See also the following samples:\\nConverters and encoding\\nTwo-way binding and converters\\n\"\r\n }\r\n ]\r\n },\r\n \"nojqueryapi\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"JsRender can be loaded in the browser with or without jQuery, as in these example pages:\\n\\nJsRender with jQuery\\nJsRender without jQuery\\n\\nWhen jQuery is present:\\n\\nJsRender loads as a jQuery plugin and adds APIs to the jQuery global namespace object – usually aliased as var $ = jQuery;\\nThe JsRender APIs are\\n\\n$.views...\\n$.templates(...)\\n$.render....\\n\\n\\nIf jQuery is not present:\\n\\nJsRender automatically creates its own jsrender global namespace variable\\nJsRender APIs are the same as above, but they are now associated with the jsrender namespace variable:\\n\\njsrender.views...\\njsrender.templates(...)\\njsrender.render....\\n\\n\\nFor convenience you can follow the jQuery approach of creating a global $ – set this time to var $ = jsrender;\\nYou can then use the regular APIs: $.views..., $.templates..., $.render..., or copy code from the regular browser examples/samples – as if using JsRender with jQuery.\\nFor example:\\nvar $ = jsrender; // Alias for the jsrender namespace object - referenced for convenience as var $\\n\\nvar tmpl = $.templates('Name: {{:first}} {{upper:last'); // Compile template from string\\n\\n$.views.converters('upper', function(val) {return val.toUpperCase()}); // Register converter\\n \\nvar data = {first: 'Jo', last: 'Ryan'};\\n\\nvar html = tmpl.render(data);\\n// result: \\\"Name: Jo RYAN\\\" \\n\\nNote: The same approach can be used when using JsRender on the server with Node.js, where JsRender is also being used without jQuery.\\n\"\r\n }\r\n ]\r\n },\r\n \"node/webpack\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Webpack support for JsRender and JsViews\\nJsRender and JsViews can be loaded using webpack.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"JsRender as a webpack module\",\r\n \"text\": \"JsRender as a webpack module\\nAfter installing JsRender on the server (using $ npm install jsrender) it can then be included in the webpack client script bundle, and loaded in the browser.\\nThere are three options for loading JsRender in the browser as a webpack module:\\n\\nLoad jQuery globally (as a script tag – so window.jQuery is defined), then load JsRender as a module in the webpack client script bundle:\\nrequire('jsrender'); // Load JsRender as jQuery plugin (attached to global jQuery)\\n\\nLoad both jQuery and JsRender as modules in the webpack client script bundle:\\nvar $ = require('jquery'); // Load jQuery as a module\\nrequire('jsrender')($); // Load JsRender as jQuery plugin (jQuery instance as parameter)\\n\\nLoad JsRender as a module in the webpack client script bundle, without loading jQuery at all:\\nvar jsrender = require('jsrender')(); // Load JsRender without jQuery (function call, no parameter)\\n\\n\\nNote: In fact if jQuery is not defined globally, require('jsrender') returns a function.\\nCalling that function without a parameter then loads JsRender without jQuery (and returns the JsRender namespace).\\nAlternatively, calling that function with a reference to a jQuery instance as parameter loads JsRender as a plugin (attached to that jQuery instance) – and returns the jQuery instance.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Example – jQuery loaded globally:\",\r\n \"text\": \"Example – jQuery loaded globally:\\nindex.html:\\n\\n \\n\\n
    \\n \\n\\n\\nsource.js:\\nrequire('jsrender'); // Load JsRender (jQuery is loaded as global)\\nvar tmpl = $.templates('Name: {{:name}}');\\nvar data = {name: 'Jo'};\\nvar html = tmpl.render(data);\\n$('#container').html(html);\\n\\ncommand line:\\nwebpack ./source.js bundle.js\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Example – jQuery loaded as module:\",\r\n \"text\": \"Example – jQuery loaded as module:\\nindex.html:\\n\\n
    \\n \\n\\n\\nsource.js:\\nvar $ = require('jquery'); // Load jQuery as a module\\nrequire('jsrender')($); // Load JsRender as jQuery plugin (jQuery instance as parameter)\\nvar tmpl = $.templates('Name: {{:name}}');\\nvar data = {name: 'Jo'};\\nvar html = tmpl.render(data);\\n$('#container').html(html);\\n\\ncommand line:\\nwebpack ./source.js bundle.js\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Example – JsRender without jQuery:\",\r\n \"text\": \"Example – JsRender without jQuery:\\nindex.html:\\n\\n
    \\n \\n\\n\\nsource.js:\\nvar jsrender = require('jsrender')(); // Load JsRender without jQuery\\nvar tmpl = jsrender.templates('Name: {{:name}}');\\nvar data = {name: 'Jo'};\\nvar html = tmpl.render(data);\\ndocument.querySelector('#container').innerHTML = html;\\n\\ncommand line:\\nwebpack ./source.js bundle.js\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"JsViews as a webpack module\",\r\n \"text\": \"JsViews as a webpack module\\nJsViews can also be included in the webpack client-script bundle, and loaded in the browser.\\nAfter installing on the server (using $ npm install jsviews), call:\\nrequire('jsviews'); // Load JsViews (if jQuery is loaded globally)\\n\\nor – if also loading jQuery as a webpack module, use:\\nvar $ = require('jquery');\\n...\\nrequire('jsviews')($); // Load JsViews (passing local jQuery instance as parameter)\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"See also:\",\r\n \"text\": \"See also:\\nBrowserify support\\n\"\r\n }\r\n ]\r\n },\r\n \"viewmodelsapi\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"This topic provides details on using $.views.viewModels() to register/compile View Models.\\nThis is the third of the alternative approaches discussed in Data / View Models – namely:\\n\\nusing plain objects\\nusing ‘hand-coded’ View Models\\nusing $.views.viewModels() to compile and register View Models with specific get/set properties and methods.\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Advantages of compiled View Models\",\r\n \"text\": \"Advantages of compiled View Models\\nUsing $.views.viewModels() to compile View Models brings some important advantages over plain object hierarchies or ‘hand-coded’ View Models:\\n\\nSimple calls to $.views.viewModels(...) allow you to compile these View Model classes without having to manually write repetitive code for multiple such get/set properties\\nUsing compiled View Models rather than plain objects makes it easier to have clean well-designed modular code, since each View Model has specific getters, setters and methods, and can have its own ‘private’ properties and state\\nThe compiled View Models provide a built-in mapping and unmapping feature for automatically converting from a plain object hierarchy (such as from a JSON request) to a hierarchy of View Model instances, or for converting back to plain data (such as for submitting to the server)\\nThey also provide a merge(...) feature for incrementally updating the View Model hierarchy, using updated plain data from the server\\nWhen working with (or migrating to) JsViews the compiled classes automatically become fully-fledged MVVM classes, with a rich range of features – where the Views are observable data-linked templates. Updates to the View Model hierarchy, and calls to the View Model setters both trigger observable changes, with corresponding incremental updates to the Views. (For more information see JsViews: Data / View Model and JsViews: Compiled View Models.)\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using compiled View Models\",\r\n \"text\": \"Using compiled View Models\\nThe basic use scenarios of compiled View Models are as follows:\\n\\nUsing $.views.viewModels(...) to register/compile View Models (myVM)\\nUsing a compiled View Model myVM as constructor/factory method – MyVM(...) – to create View Model instances (myVmInstance)\\nUsing MyVM.map(...) to convert a plain object hierarchy (such as from a JSON request) to a hierarchy of View Model instances\\nUsing myVMInstance.merge(...) to incrementally update a View Model hierarchy, using updated plain data\\nUsing myVMInstance.unmap() to convert a View Model hierarchy back to a plain object hierarchy\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"API: $.views.viewModels(...)\",\r\n \"text\": \"API: $.views.viewModels(...)\\nTo register a View Model, you call the $.views.viewModels(...) API – with four alternative signatures:\\n\\nvar MyVM = $.views.viewModels(viewModelOptions);returning a compiled View Model\\n$.views.viewModels(\\\"MyVM\\\", viewModelOptions);registering a named View Model, accessible as $.views.viewModels.MyVM\\n$.views.viewModels(namedViewModels);where namedViewModels is a hash, declaring multiple View Models\\n$.views.viewModels(namedViewModels, myViewModels);where namedViewModels is a hash, declaring multiple View Models and myViewModels is a View Models collection (hash) which will provide access to the compiled View Models, as myViewModels.MyVM\\n\\nIn each case, the compiled View Model is specified by a viewModelOptions object, with a getters: gettersArray (specifying an array of get/set properties), and/or an extend: extendObject (specifying additional methods or properties).\\nExample:\\nvar Book = $.views.viewModels({ // Compile a Book View Model\\n getters: [\\\"title\\\", \\\"price\\\"], // getters array - signature of constructor\\n extend: { // extend object - additional methods \\n placeOrder: function() { ... }\\n }\\n});\\n\\nvar book1 = Book(\\\"Hope\\\", \\\"1.50\\\"); // Construct a Book View Model instance\\nbook.price(\\\"2.50\\\"); // Modify price\\nbook.placeOrder(); // Call method\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"api\",\r\n \"title\": \"$.views.viewModels(...)\",\r\n \"text\": \"$.views.viewModels(...)\\nRegister one or more View Models\\nReturn a compiled View Model (constructor/factory method) with specific get/set properties and methods\\n\\nvar Book = $.views.viewModels({\\n getters: [\\\"title\\\", \\\"price\\\"]\\n});\\n\\nvar bk1 = Book(\\\"Hope\\\", \\\"$1.50\\\");\\n\\nRegister (and return) a named View Model\\n\\n$.views.viewModels(\\\"Book\\\", {\\n getters: [\\\"title\\\", \\\"price\\\"]\\n});\\n\\nvar bk1 = $.views.viewModels.Book(\\\"Hope\\\", \\\"$1.50\\\");\\n\\n\\nRegister multiple named View Models\\n\\n$.views.viewModels({\\n Book: {getters: [\\\"title\\\", \\\"price\\\"]},\\n ...\\n});\\n\\nvar bk1 = $.views.viewModels.Book(\\\"Hope\\\", \\\"$1.50\\\");\\n\\n\\nAdd one or more named View Models to a View Model collection (hash)\\n\\nvar myVms = {};\\n\\n$.views.viewModels({\\n Book: {getters: [\\\"title\\\", \\\"price\\\"]},\\n ...\\n}, myVms);\\n\\nvar bk1 = myVms.Book(\\\"Hope\\\", \\\"$1.50\\\");\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Creating View Model instances, using the View Model constructor\",\r\n \"text\": \"Creating View Model instances, using the View Model constructor\\nView Models compiled/registered/returned by $.view.viewModels(...) are in fact constructors for instances of the View Model class.\\nvar Book = $.views.viewModels({ // Constructor\\n getters: [\\\"title\\\", \\\"price\\\"] // getters array - signature of constructor\\n ...\\n});\\n\\nvar book1 = Book(\\\"Hope\\\", \\\"$1.50\\\"); // Create Book instance\\n\\nNote that:\\n\\nThe new keyword is not necessary when calling the constructor. (It is in effect a factory method, that calls new internally.)\\nThe signature of the constructor call (parameters used to initialize the instance) corresponds to the array of getters specified in the viewModelOptions - in this case [\\\"title\\\", \\\"price\\\"]\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"View Model hierarchies\",\r\n \"text\": \"View Model hierarchies\\nThe Book View Model example above has simple get/set properties [\\\"title\\\", \\\"price\\\"] which are simple primitive types (string in this case).\\nBut consider the Person View Model, used in the overview topic Data / View Model. Here a person object (whether a plain object or a View Model instance) is in fact a hierarchy of objects, since the address and phones properties of a Person are themselves objects (an Address object and a Phone array)\\nHere is a person plain object/hierarchy (obtained perhaps by ‘evaluating’ JSON data from the server):\\nvar person = {\\n name: \\\"Pete\\\",\\n address: {\\n street: \\\"1st Ave\\\"\\n },\\n phones: [{number: \\\"111 111 1111\\\"}, {number:\\\"222 222 2222\\\"}] \\n};\\n\\nTo map this object hierarchy to the corresponding View Model hierarchy we need to define three View Models:\\n// Compile Person View Model, with addPhone method\\nvar Person = $.views.viewModels({\\n getters: [\\\"name\\\", \\\"address\\\", \\\"phones\\\"],\\n extend: {addPhone: addPhone}\\n});\\n\\n// Compile Address View Model\\nvar Address = $.views.viewModels({getters: [\\\"street\\\"]});\\n\\n// Compile Phone View Model\\nvar Phone = $.views.viewModels({getters: [\\\"number\\\"]});\\n\\nWe can then instanciate the corresponding View Model hierarchy, using constructors:\\nvar person = Person(\\n \\\"Pete\\\",\\n Address(\\\"1st Ave\\\"),\\n [Phone(\\\"111 111 1111\\\"), Phone(\\\"222 222 2222\\\")]\\n);\\n\\nSee the sample in the Data / View Model topic.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Creating View Model instances by mapping from data\",\r\n \"text\": \"Creating View Model instances by mapping from data\\nThe process of manually writing code to map from JSON data to a corresponding View Model hierarchy, as above, can be complex and inconvenient. It requires traversing the data hierarchy and using appropriate View Model constructors to instantiate corresponding View Model instances.\\nFortunately JsRender/JsViews compiled View Models provide a map(data) feature which when used together with View Model typed hierarchies makes this process quite trivial.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"API: MyViewModel.map(...)\",\r\n \"text\": \"API: MyViewModel.map(...)\\nAny compiled View Model, MyViewModel, provides a MyViewModel.map(...) method, which can be used to convert a plain object or an array of plain objects (or the equivalent JSON string) to the corresponding View Model instance (or array of View Model instances).\\n\"\r\n },\r\n {\r\n \"_type\": \"api\",\r\n \"title\": \"MyViewModel.map(...)\",\r\n \"text\": \"MyViewModel.map(...)\\nGenerate a View Model hierarchy from data\\nGenerate a View Model instance/hierarchy/array by mapping from data (a plain object instance/hierarchy/array, or JSON string)\\n\\n// View Model\\nvar Person = $.views.viewModels.Person;\\n\\n// View Model instance\\nvar person = Person.map(personData);\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Example:\\nvar Book = $.views.viewModels({ // Constructor\\n getters: [\\\"title\\\", \\\"price\\\"]\\n});\\n\\nMap from bookData plain object to book View Model instance:\\nvar bookData1 = {title: \\\"Hope\\\", price: \\\"$1.50\\\"}; // book (plain object)\\nvar book1 = Book.map(bookData1); // book (instance of Book View Model)\\n\\nMap from bookDataArray array of plain objects to bookArray array of View Model instances:\\nvar bookDataArray1 = [ // book array (plain objects)\\n {title: \\\"Hope\\\", price: \\\"$1.50\\\"},\\n {title: \\\"Courage\\\", price: \\\"$2.50\\\"}\\n];\\nvar booksArray1 = Book.map(bookDataArray1); // book array (instances of Book View Model)\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"View Model typed hierarchies\",\r\n \"text\": \"View Model typed hierarchies\\nWhen specifying getters in the $.views.viewModels(...) call, you can declare the type of a get/set property. For example an address get/set property can be specified as being of type Address – where Address is another View Model declared on the same collection.\\nBy specifying View Model types for properties (and declaring those View Models in the same collection) you obtain a ‘View Model typed hierarchy’.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using MyViewModel.map(...) to map a whole object hierarchy to a View Model instance hierarchy\",\r\n \"text\": \"Using MyViewModel.map(...) to map a whole object hierarchy to a View Model instance hierarchy\\nIn the case of a ‘View Model typed hierarchy’, simply pass the top-level plain object to the map() method for the top-level View Model class, and all View Model instances in the hierarchy will be correctly instantiated:\\nCompile View Model classes (typed hierarchy):\\n$.views.viewModels({\\n Person: {\\n getters: [\\n \\\"name\\\", // Declare 'name' as being a primitive type (string)\\n {getter: \\\"address\\\", type: \\\"Address\\\"}, // Declare 'address' as being an Address (View Model) type\\n {getter: \\\"phones\\\", type: \\\"Phone\\\"} // Declare 'phones' as being (an array) of Phone (View Model) types\\n ],\\n extend: {addPhone: addPhone}\\n },\\n Address: {\\n getters: [\\\"street\\\"]\\n },\\n Phone: ...\\n});\\n\\nPerson data (plain object hierarchy, or JSON string):\\nvar personData = {\\n name: \\\"Pete\\\",\\n address: {street: \\\"1st Ave\\\"},\\n phones: [{number: \\\"111 111 1111\\\"}, ...]\\n };\\n\\nUse map() to convert from personData plain object hierarchy (or JSON string) to person View Model hierarchy:\\nvar person = $.views.viewModels.Person.map(personData);\\n\\nThe getter properties then let you traverse the hierarchy, call methods, etc.\\nperson.name(\\\"newName\\\"); // Use setter: change name\\nperson.addPhone(...); // Call method: add phone\\nvar phone2 = person.phones()[1].number(); // Traverse and use getter: get number\\n\\nLet’s modify the sample in Data / View Model to use the map(...) approach:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Using map() to convert from a plain object hierarchy to a View Model hierarchy\",\r\n \"text\": \"Using map() to convert from a plain object hierarchy to a View Model hierarchy\\nbutton {margin-bottom: 9px;}\\n\\nChange name\\nAdd Phone\\n\\n\\n\\n\\n \\n \\n \\n \\n
    Name:{{:name()}}
    Street:{{:address().street()}}
    Phones:\\n \\n {{for phones()}}\\n \\n {{/for}}\\n
    \\n {{:number()}}\\n
    \\n
    \\n\\n// Compiled template\\nvar tmpl = $.templates(\\\"#personTmpl\\\");\\n\\n// Compile View Models\\n$.views.viewModels({\\n Person: {\\n getters: [\\n \\\"name\\\", // name is a primitive type (string)\\n {getter: \\\"address\\\", type: \\\"Address\\\"}, // address is of type Address (View Model)\\n {getter: \\\"phones\\\", type: \\\"Phone\\\"} // Each phone is of type Phone (View Model)\\n ],\\n extend: {addPhone: addPhone}\\n },\\n Address: {\\n getters: [\\\"street\\\"]\\n },\\n Phone:{\\n getters: [\\\"number\\\"]\\n }\\n});\\n\\nvar vmCollection = $.views.viewModels;\\n\\n// Method for Person class\\nfunction addPhone(phoneNo) {\\n // Uses Phone() View Model constructor to create Phone instance\\n this.phones().push(vmCollection.Phone(phoneNo));\\n}\\n\\n// person plain object hierarchy:\\nvar personData = {\\n name: \\\"Pete\\\",\\n address: {street: \\\"1st Ave\\\"},\\n phones: [{number: \\\"111 111 1111\\\"}, {number: \\\"222 222 2222\\\"}]\\n};\\n\\n// Instantiate View Model hierarchy using map()\\nvar person = vmCollection.Person.map(personData);\\n\\n// Render template against person object (instance of Person)\\n$(\\\"#result\\\").html(tmpl.render(person));\\n\\n// Button handlers\\n$(\\\"#changeName\\\").on(\\\"click\\\", function() {\\n person.name(\\\"newName\\\"); // Use the name(...) setter\\n $(\\\"#result\\\").html(tmpl.render(person));\\n});\\n\\n$(\\\"#addPhone\\\").on(\\\"click\\\", function() {\\n person.addPhone(\\\"xxx xxx xxxx\\\"); // Call the addPhone(...) method\\n $(\\\"#result\\\").html(tmpl.render(person));\\n});\\n\\n... {{:name()}} ...\\n\\nCompile View Model classes\\n\\n...\\n$.views.viewModels({\\n Person: {\\n getters: [\\n \\\"name\\\", // name is a primitive type (string)\\n {getter: \\\"address\\\", type: \\\"Address\\\"}, // address is of type Address (View Model)\\n {getter: \\\"phones\\\", type: \\\"Phone\\\"} // Each phone is of type Phone (View Model)\\n ],\\n extend: {addPhone: addPhone}\\n },\\n Address: ...\\n Phone: ...\\n});\\n\\n\\nInstantiate View Model hierarchy using Person.map(data)\\n\\nvar personData = {\\n name: \\\"Pete\\\",\\n address: {street: \\\"1st Ave\\\"},\\n phones: [{number: \\\"111 111 1111\\\"}, {number: \\\"222 222 2222\\\"}]\\n};\\n\\nvar person = vmCollection.Person.map(personData);\\n\\nRender template against person object (instance of Person)\\n\\n$(\\\"#result\\\").html(tmpl.render(person));\\n\\nCall setter, call method...\\n\\n...\\nperson.name(\\\"newName\\\"); // Use the name(...) setter\\n\\n...\\nperson.addPhone(\\\"xxx xxx xxxx\\\"); // Call the addPhone(...) method\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"(See also the corresponding sample with JsViews and data-linking.)\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Along with the map() feature – merge() and unmap()\",\r\n \"text\": \"Along with the map() feature – merge() and unmap()\\nWhen working with View Model typed hierarchies, there are two additional features that can be used together with the map() feature:\\n\\nIf later you obtain updated JSON data, personData2, you can use merge() (below) to trigger an incremental update to the View Model hierarchy:\\nperson.merge(personData2);\\n\\nIf values are modified (using setters, or methods) you can at any time can use unmap() (below) to convert back to plain data, but with updated values:\\nvar updatedPersonData = person.unmap();\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using myVMobjectOrArray.merge(...) to update a View Model hierarchy\",\r\n \"text\": \"Using myVMobjectOrArray.merge(...) to update a View Model hierarchy\\nIf a View Model hierarchy (or array of View Model instances) was created using the map() feature above to map from data, then the View Model instances (and arrays) will each have a merge() method available:\\nvar person = Person.map(personData1);\\nperson.merge(personData2); // Incrementally update person (hierarchy)\\n\\nor for an array:\\nvar peopleArray = Person.map(peopleDataArray1);\\npeopleArray.merge(peopleDataArray2); // Incrementally update people array\\n\\nOr, deeper in the hierarchy:\\nvar person = Person.map(personData1);\\nperson.phones.merge(phonesDataArray2); // Update just the person.phones array\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Updating with merge() makes minimal incremental changes, and preserves state\",\r\n \"text\": \"Updating with merge() makes minimal incremental changes, and preserves state\\nNote that the merge() update process does not replace the whole hierarchy of View Model instances, but works incrementally to add/remove/modify instances as appropriate. So if most of the data in personData2 is the same as personData1, calling merge(personData2) will make only minimal changes to the hierarchy.\\nThis means that if View Model instances have state (such as additional properties that were set programmatically and are not driven by data) then that state can be maintained across the merge() update.\\n\"\r\n },\r\n {\r\n \"_type\": \"api\",\r\n \"title\": \"myVMobjectOrArray.merge(...)\",\r\n \"text\": \"myVMobjectOrArray.merge(...)\\nUpdate a View Model hierarchy, from modified data\\nUpdate a previously generated View Model instance/hierarchy/array by mapping from updated data\\n\\nperson.merge(personData2);\\n// person (View Model hierarchy) has now\\n// been updated, with modified data...\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using myVMobjectOrArray.unmap() to convert back to a plain object hierarchy\",\r\n \"text\": \"Using myVMobjectOrArray.unmap() to convert back to a plain object hierarchy\\nIf a View Model hierarchy (or array of View Model instances) was created by mapping from data, using the map() feature above, then the View Model instances (and arrays) will each have an unmap() method (in addition to the merge() method mentioned above):\\nvar person = Person.map(personData1);\\nperson.addPhone(newPhone);\\nperson.name(newName)\\nvar modifiedPersonData = person.unmap(); // Convert back to a plain object hierarchy\\n\\nor for an array:\\nvar peopleArray = Person.map(peopleDataArray1);\\npeopleArray[1].address.street(newStreet) // Make changes anywhere in the peopleArray\\nvar modifiedPeopleDataArray = people.unmap(); // Convert back to a plain object array\\n\\nOr, deeper in the hierarchy:\\nvar person = Person.map(personData1);\\nperson.addPhone(newPhone);\\nvar modifiedPhonesArray = person.phones.unmap(); // Get a plain object array for person.phones\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"api\",\r\n \"title\": \"myVMobjectOrArray.unmap()\",\r\n \"text\": \"myVMobjectOrArray.unmap()\\nGet a plain object hierarchy from a View Model hierarchy\\nObtain an updated plain object instance/hierarchy/array, from a previously generated View Model instance/hierarchy/array\\n\\n// Convert back to a plain object hierarchy\\nvar modifiedPersonData = person.unmap();\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Here is an updated version of our previous sample, where now we have added the use of merge() and unmap()\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Using merge() to update View Models, and unmap() to return to plain objects\",\r\n \"text\": \"Using merge() to update View Models, and unmap() to return to plain objects\\nbutton {margin-bottom: 9px;}\\n\\nUpdate\\nRevert\\nGet Data\\nChange name\\nAdd Phone\\n\\n\\n\\n\\n \\n \\n \\n \\n
    Name:{{:name()}}
    Street:{{:address().street()}}
    Phones:\\n \\n {{for phones()}}\\n \\n {{/for}}\\n
    {{:number()}}
    \\n
    \\n\\n// Compiled template\\nvar tmpl = $.templates(\\\"#personTmpl\\\");\\n\\n// Compile View Models\\n$.views.viewModels({\\n Person: {\\n getters: [\\n \\\"name\\\", // name is a primitive type (string)\\n {getter: \\\"address\\\", type: \\\"Address\\\"}, // address is of type Address (View Model)\\n {getter: \\\"phones\\\", type: \\\"Phone\\\"} // Each phone is of type Phone (View Model)\\n ],\\n extend: {addPhone: addPhone}\\n },\\n Address: {\\n getters: [\\\"street\\\"]\\n },\\n Phone:{\\n getters: [\\\"number\\\"]\\n }\\n});\\n\\nvar vmCollection = $.views.viewModels;\\n\\n// Method for Person class\\nfunction addPhone(phoneNo) {\\n // Uses Phone() View Model constructor to create Phone instance\\n this.phones().push(vmCollection.Phone(phoneNo));\\n}\\n\\n// First version of data (e.g. from JSON request):\\nvar personData = {\\n name: \\\"Pete\\\",\\n address: {street: \\\"1st Ave\\\"},\\n phones: [{number: \\\"111 111 1111\\\"}, {number: \\\"222 222 2222\\\"}]\\n};\\n\\n// Second version of data (e.g. new JSON request):\\nvar personData2 = {\\n name: \\\"Peter\\\",\\n address: {street: \\\"2nd Ave\\\"},\\n phones: [{number: \\\"111 111 9999\\\"},{number: \\\"333 333 9999\\\"}]\\n};\\n\\n// Instantiate View Model hierarchy, using map()\\nvar person = vmCollection.Person.map(personData);\\n\\n// Render template against person object (instance of Person)\\n$(\\\"#result\\\").html(tmpl.render(person));\\n\\n// Button handlers\\n$(\\\"#update\\\").on(\\\"click\\\", function() {\\n // Update View Model hierarchy, using merge()\\n person.merge(personData2);\\n $(\\\"#result\\\").html(tmpl.render(person));\\n});\\n\\n$(\\\"#revert\\\").on(\\\"click\\\", function() {\\n // Revert View Model hierarchy, using merge()\\n person.merge(personData);\\n $(\\\"#result\\\").html(tmpl.render(person));\\n});\\n\\n$(\\\"#changeName\\\").on(\\\"click\\\", function() {\\n person.name(\\\"newName\\\"); // Use the name(...) setter\\n $(\\\"#result\\\").html(tmpl.render(person));\\n});\\n\\n$(\\\"#addPhone\\\").on(\\\"click\\\", function() {\\n person.addPhone(\\\"xxx xxx xxxx\\\"); // Call the addPhone(...) method\\n $(\\\"#result\\\").html(tmpl.render(person));\\n});\\n\\n$(\\\"#getData\\\").on(\\\"click\\\", function() {\\n // Get current data, using unmap()\\n var updatedPersonData = person.unmap();\\n window.alert(JSON.stringify(updatedPersonData));\\n});\\nCompile View Model classes\\n\\n...\\n$.views.viewModels({\\n Person: {\\n getters: [\\n \\\"name\\\", // name is a primitive type (string)\\n {getter: \\\"address\\\", type: \\\"Address\\\"}, // address is of type Address (View Model)\\n {getter: \\\"phones\\\", type: \\\"Phone\\\"} // Each phone is of type Phone (View Model)\\n ],\\n extend: {addPhone: addPhone}\\n },\\n Address: ...\\n Phone: ...\\n});\\n\\n\\nInstantiate View Model hierarchy, using map()\\n\\nvar personData = {\\n name: \\\"Pete\\\",\\n address: {street: \\\"1st Ave\\\"},\\n phones: [{number: \\\"111 111 1111\\\"}, {number: \\\"222 222 2222\\\"}]\\n};\\n\\nvar person = vmCollection.Person.map(personData);\\n\\nUpdate View Model hierarchy, using merge()\\n\\n$(\\\"#update\\\").on(\\\"click\\\", function() {\\n person.merge(personData2); // Update person View Model hierarchy\\n $(\\\"#result\\\").html(tmpl.render(person));\\n});\\n\\n\\nGet current data, using unmap()\\n\\n$(\\\"#getData\\\").on(\\\"click\\\", function() {\\n var updatedPersonData = person.unmap(); // Get plain object hierarchy from current View Model hierarchy\\n window.alert(JSON.stringify(updatedPersonData));\\n});\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"(See also the corresponding sample using JsViews and data-linking.)\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Overriding generated get/set functions\",\r\n \"text\": \"Overriding generated get/set functions\\nTo override a generated get/set property provided by a compiled View Model you can provide an implementation in the extend hash, with the same name as the get/set in the getters array:\\n// Define a myNameGetSet(...)function, to override the compiled name(...) get/set function\\nfunction myNameGetSet(val) {\\n if (!arguments.length) { // This is standard compiled get/set code\\n return this._name; // If there is no argument, use as a getter\\n }\\n this._name = val; // If there is an argument, use as a setter\\n console.log(\\\"name set to \\\" + val); // This is an additional line of code, for logging\\n}\\n\\n// Declare a Person View Model with an overridden name() get/set property\\n$.views.viewModels({\\n Person: {\\n getters: [\\n {getter: \\\"name\\\", ...}, // Compiled name() get/set\\n ...\\n ],\\n extend: {\\n name: myNameGetSet, // Override name() get/set\\n ...\\n }\\n ...\\n },\\n ...\\n});\\n\\nThe above is equivalent to the generated version except that it adds custom logging to the getter/setter function.\\nNote: In the context of JsViews, the View Model get/set properties can be data-linked (one-way or two-way data-binding) – and will then be invoked automatically during observable changes to the property. (This applies also to overridden properties – using a variant of the above pattern, described in the corresponding JsViews topic).\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Sample showing some of the advanced View Model features\",\r\n \"text\": \"Sample showing some of the advanced View Model features\\nThe next sample is similar to the previous one, but specifically highlights some of the advanced features of compiled View Models.\\n\\nIt stores compiled View Models on a myVmCollection hash, as a View Model typed collection, rather than on$.views.viewModels\\nIt maps from an array of ‘people’ rather than a single person:\\nvar people = Person.map(peopleData);\\nIt specifies an id key for Person. When updating the phones array the id value is treated as 'primary key’, and used to map 'identity’:\\nid: \\\"id\\\"\\nIt provides an id() callback on Person, for determining identity – allowing identification of corresponding View Model instances within the people array, and hence preventing unnecessary disposal and re-instantiation (which would destroy state, such as the comment value).\\nIt has a comment() get/set property that is added as part of the extend definition, not the getters, so it is not initialized from data, in the constructor. Note therefore that if you set a comment on each person instance, then click Update, then Revert, one comment is conserved (since that instance is never disposed – based on the ‘identity’ determination) but the other is lost since the instance is disposed and then re-created by Revert:\\nextend: {...comment: comment...}\\nIt has defaultVal specified for name, address and phones, either as ‘static’ values or computed by a callback function:\\naddress: {type: \\\"Address\\\", defaultVal: defaultStreet}\\nIt overrides the generated person.name() get/set by a myNameGetSet function which includes logging\\nIt passes a JSON string to merge() or map()\\n(See also the same sample using JsViews and data-linking.)\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Mapping from JSON data to View Model hierarchy – further features\",\r\n \"text\": \"Mapping from JSON data to View Model hierarchy – further features\\nbutton, table {margin-bottom: 9px;}\\n\\nUpdate\\nRevert\\nGet Data\\nChange name\\nAdd Phone\\n\\n\\n\\n\\n \\n \\n \\n \\n \\n
    Comment:
    Name:{{:name()}}
    Street:{{:address().street()}}
    Phones:\\n \\n {{for phones()}}\\n \\n {{/for}}\\n
    {{:number()}}
    \\n
    \\n\\nvar tmpl = $.templates(\\\"#personTmpl\\\");\\n\\nvar myVmCollection = {};\\n\\n// Compile View Models\\n$.views.viewModels({\\n Person: {\\n getters: [\\n {getter: \\\"name\\\", defaultVal: \\\"No name\\\"}, // Compiled name() get/set\\n {getter: \\\"address\\\", type: \\\"Address\\\", defaultVal: defaultAddress},\\n {getter: \\\"phones\\\", type: \\\"Phone\\\", defaultVal: []}\\n ],\\n extend: {\\n name: myNameGetSet, // Override name() get/set\\n addPhone: addPhone,\\n comment: comment // Additional get/set property, not initialized by data)\\n },\\n id: function(vm, plain) { // Callback function to determine 'identity'\\n return vm.personId === plain.personId;\\n }\\n },\\n Address: {\\n getters: [\\\"street\\\"]\\n },\\n Phone: {\\n getters: [\\\"number\\\"],\\n id: \\\"phoneId\\\" // Treat phoneId as 'primary key', for identity\\n }\\n}, myVmCollection); // Store View Models (typed hierarchy) on myVmCollection\\n\\n// Override generated name() get/set\\nfunction myNameGetSet(val) {\\n if (!arguments.length) { // This is standard compiled get/set code\\n return this._name; // If there is no argument, use as a getter\\n }\\n this._name = val; // If there is an argument, use as a setter\\n console.log(\\\"name set to \\\" + val); // This is an additional line of code, for logging\\n}\\n\\n// Method for Person class\\nfunction addPhone(phoneNo) { // Uses myVmCollection.Phone() to construct new instance\\n this.phones().push(myVmCollection.Phone(phoneNo));\\n}\\n\\n// get/set for comment (state on View Model instance, not initialized from data)\\nfunction comment(val) {\\n if (!arguments.length) {\\n return this._comment; // If there is no argument, use as a getter\\n }\\n this._comment = val;\\n}\\n\\nfunction defaultAddress() { // Function providing default address if undefined in data\\n return {street: 'No street for \\\"' + this.name + '\\\"'};\\n}\\n\\n// First version of data - array of objects (e.g. from JSON request):\\nvar peopleData = [\\n {\\n personId: \\\"1\\\",\\n address: {\\n street: \\\"2nd Ave\\\"\\n }\\n },\\n {\\n personId: \\\"2\\\",\\n name: \\\"Pete\\\",\\n phones: [\\n {number: \\\"333 333 3333\\\", phoneId: \\\"2a\\\"}\\n ]\\n }\\n];\\n\\n// Second version of data - JSON string (e.g. new JSON request):\\nvar peopleData2 = '[{\\\"personId\\\":\\\"2\\\",\\\"name\\\":\\\"Peter\\\",\\\"address\\\":{\\\"street\\\":\\\"11 1st Ave\\\"},'\\n+ '\\\"phones\\\":[{\\\"number\\\":\\\"111 111 9999\\\",\\\"phoneId\\\":\\\"1a\\\"},{\\\"number\\\":\\\"333 333 9999\\\",\\\"phoneId\\\":\\\"2a\\\"}]}]';\\n\\n// Instantiate View Model hierarchy using map()\\nvar people = myVmCollection.Person.map(peopleData);\\n\\n// Render template against people (array of Person instances)\\n$(\\\"#result\\\").html(tmpl.render(people));\\n\\n// Button handlers\\n$(\\\"#update\\\").on(\\\"click\\\", function() {\\n people.merge(peopleData2);\\n $(\\\"#result\\\").html(tmpl.render(people));\\n});\\n\\n$(\\\"#revert\\\").on(\\\"click\\\", function() {\\n people.merge(peopleData);\\n $(\\\"#result\\\").html(tmpl.render(people));\\n});\\n\\n$(\\\"#changeName\\\").on(\\\"click\\\", function() {\\n people[0].name(\\\"newName\\\");\\n $(\\\"#result\\\").html(tmpl.render(people));\\n});\\n\\n$(\\\"#addPhone\\\").on(\\\"click\\\", function() {\\n people[0].addPhone(\\\"xxx xxx xxxx\\\");\\n $(\\\"#result\\\").html(tmpl.render(people));\\n});\\n\\n$(\\\"#result\\\").on(\\\"change\\\", \\\".comment\\\", function(val) {\\n // If comment is modified, update View Model state with new value\\n people[this.getAttribute(\\\"data-index\\\")].comment(this.value);\\n});\\n\\n$(\\\"#getData\\\").on(\\\"click\\\", function() {\\n var updatedPeopleData = people.unmap();\\n window.alert(JSON.stringify(updatedPeopleData));\\n});\\n\\n\\nvar myVmCollection = {};\\n\\n// Compile View Models\\n$.views.viewModels({\\n Person: {\\n getters: [\\n {getter: \\\"name\\\", defaultVal: \\\"No name\\\"}, // Compiled name() get/set\\n {getter: \\\"address\\\", type: \\\"Address\\\", defaultVal: defaultAddress},\\n {getter: \\\"phones\\\", type: \\\"Phone\\\", defaultVal: []}\\n ],\\n extend: {\\n name: myNameGetSet, // Override name() get/set\\n addPhone: addPhone,\\n comment: comment // Additional get/set property, not initialized by data)\\n },\\n id: function(vm, plain) { // Callback function to determine 'identity'\\n return vm.personId === plain.personId;\\n }\\n },\\n ...\\n Phone: {\\n getters: [\\\"number\\\"],\\n id: \\\"phoneId\\\" // Treat phoneId as 'primary key', for identity\\n }\\n}, myVmCollection); // Store View Models (typed hierarchy) on myVmCollection\\n\\n// Override generated name() get/set\\nfunction myNameGetSet(val) {\\n if (!arguments.length) { // This is standard compiled get/set code\\n return this._name; // If there is no argument, use as a getter\\n }\\n this._name = val; // If there is an argument, use as a setter\\n console.log(\\\"name set to \\\" + val); // This is an additional line of code, for logging\\n}\\n\\n// Method for Person class\\nfunction addPhone(phoneNo) { // Uses myVmCollection.Phone() to construct new instance\\n this.phones().push(myVmCollection.Phone(phoneNo));\\n}\\n\\n// get/set for comment (state on View Model instance, not initialized from data)\\nfunction comment(val) {\\n if (!arguments.length) {\\n return this._comment;\\n }\\n this._comment = val;\\n}\\n\\nfunction defaultAddress() { // Function providing default address if undefined in data\\n return {street: 'No street for \\\"' + this.name + '\\\"'};\\n}\\n\\n// First version of data - array of objects (e.g. from JSON request):\\nvar peopleData = [{personId: \\\"1\\\", ...}, {personId: \\\"2\\\", name: \\\"Pete\\\",...}];\\n\\n// Second version of data - JSON string (e.g. new JSON request):\\nvar peopleData2 = '[{\\\"personId\\\":\\\"2\\\",\\\"name\\\":\\\"Peter\\\",\\\"address\\\":...}]';\\n\\n// Instantiate View Model hierarchy using map()\\nvar people = myVmCollection.Person.map(peopleData);\\n\\n// Render template against people (array of Person instances)\\n$(\\\"#result\\\").html(tmpl.render(people));\\n...\\n\\n// Button handlers\\n$(\\\"#update\\\").on(\\\"click\\\", function() {\\n people.merge(peopleData2);\\n ...\\n});\\n...\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Adding a custom get/set property to a compiled View Model \",\r\n \"text\": \"Adding a custom get/set property to a compiled View Model \\nFinally, here is a sample which extends a compiled View Model with a custom Person.isManager()get/set property. The property is coupled to the Team.manager() property – so setting Person.isManager(...) will update the Team.manager() correspondingly (and conversely when setting Team.manager(...).\\nPerson.isManager is not included in the getters declaration, so that the constructor for Person will not expect an isManager parameter to be provided for initialization.\\n(See also the related sample using JsViews and data-linking.)\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Sample: extending Person with an isManager property\",\r\n \"text\": \"Sample: extending Person with an isManager property\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n

    Team members:

    \\n\\n\\n \\n \\n {{for members()}}\\n \\n \\n \\n \\n \\n {{/for}}\\n \\n
    Is ManagerNameStreetZIP
    {{:name()}}{{:address().street()}}{{:address().ZIP()}}
    \\n\\n{{if manager()}}\\n

    Manager:

    \\n \\n \\n \\n \\n
    {{:manager().name()}}{{:manager().address().street()}}{{:manager().address().ZIP()}}
    \\n{{else}}\\n

    No manager

    \\n{{/if}}\\n\\n\\n// Compile template\\nvar tmpl = $.templates(\\\"#teamTmpl\\\");\\n\\n// Custom function for Person.isManager get/set property\\nfunction myIsManager(val) {\\n if (!arguments.length) {\\n return this === team.manager(); // If there is no argument, use as a getter\\n }\\n if (val) {\\n // Setting this.isManager() to true\\n // So make this team member manager\\n team.manager(this);\\n } else if (this.isManager()) {\\n // Setting this.isManager to false, and this team member is currently manager.\\n // So set team manager to null\\n team.manager(null);\\n }\\n}\\n\\n// Compile View Models\\n$.views.viewModels({\\n Team: {\\n getters: [\\n {\\n getter: \\\"manager\\\",\\n type: \\\"Person\\\"\\n },\\n {\\n getter: \\\"members\\\",\\n type: \\\"Person\\\"\\n }\\n ]\\n },\\n Person: {\\n getters: [\\n \\\"name\\\",\\n {\\n getter: \\\"address\\\",\\n type: \\\"Address\\\"\\n }\\n ],\\n extend: {\\n isManager: myIsManager // use custom function\\n }\\n },\\n Address: {\\n getters: [\\\"street\\\", \\\"ZIP\\\"]\\n }\\n});\\n\\n// Initial data \\nvar teamData = {\\n manager: null,\\n members: [{\\n name: \\\"Pete\\\",\\n address: {\\n street: \\\"1st Ave\\\",\\n ZIP: \\\"12345\\\"\\n }\\n },{\\n name: \\\"Bess\\\",\\n address: {\\n street: \\\"Central Way\\\",\\n ZIP: \\\"98765\\\"\\n }\\n },\\n {\\n name: \\\"Henry\\\",\\n address: {\\n street: \\\"Main St\\\",\\n ZIP: \\\"54321\\\"\\n }\\n }]\\n };\\n\\n// Instantiate View Models\\nvar team = $.views.viewModels.Team.map(teamData);\\n\\n//Initialize second team member to be manager.\\nvar manager = team.members()[1];\\nmanager.isManager(true);\\n\\nfunction renderTemplate() {\\n // Refresh template rendering completely\\n $(\\\"#result\\\").html(tmpl.render(team));\\n}\\n\\nrenderTemplate();\\n\\n// Attach handlers for checkbox and buttons\\n$(\\\"#result\\\")\\n .on(\\\"change\\\", \\\".isManager\\\", function() {\\n var memberIndex = $(this).data(\\\"index\\\"),\\n member = team.members()[memberIndex];\\n member.isManager(this.checked);\\n renderTemplate(); // Refresh rendering, with modified data\\n })\\n .on(\\\"click\\\", \\\".changeManager\\\", function() {\\n var memberIndex = $(this).data(\\\"index\\\"),\\n member = team.members()[memberIndex];\\n member.isManager(true);\\n renderTemplate(); // Refresh rendering, with modified data\\n })\\n .on(\\\"click\\\", \\\".noManager\\\", function() {\\n team.manager(null);\\n renderTemplate(); // Refresh rendering, with modified data\\n }\\n);\\n// Custom function for Person.isManager get/set property\\nfunction myIsManager(val) {\\n if (!arguments.length) {\\n return this === team.manager(); // If there is no argument, use as a getter\\n }\\n if (val) {\\n // Make this team member manager\\n team.manager(this);\\n } else if (this.isManager()) {\\n // Set team manager to null\\n team.manager(null);\\n }\\n}\\n\\n// Compile View Models\\n$.views.viewModels({\\n Team: {...},\\n Person: {\\n getters: [\\n \\\"name\\\",\\n ...\\n ],\\n extend: {\\n isManager: myIsManager // use custom function\\n }\\n },\\n Address: {...}\\n});\\n\\n...\\n\\n//Initialize second team member to be manager.\\nvar manager = team.members()[1];\\nmanager.isManager(true);\\n\\n...\\n\\n// Attach handler for checkbox\\n$(\\\"#result\\\")\\n .on(\\\"change\\\", \\\".isManager\\\", function() {\\n ...\\n member.isManager(this.checked);\\n renderTemplate(); // Refresh rendering, with modified data\\n })\\n ...\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"See also:\",\r\n \"text\": \"See also:\\n\"\r\n }\r\n ]\r\n },\r\n \"lifecycle\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"paragraph\\n\"\r\n }\r\n ]\r\n },\r\n \"globals\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"JsRender\\n\\nrender()\\ntemplates()\\nviews\\n\\nJsViews\\n\\nlink()\\nobserve()\\nobservable()\\nunlink()\\nunobserved()\\nview()\\n\\n\"\r\n }\r\n ]\r\n },\r\n \"tagsOld\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"What is a custom tag?\",\r\n \"text\": \"What is a custom tag?\\nJsRender custom tags are named tags {{mytag ...}}, which you can register, and then use in your templates.\\nA tag renders itself as part of the template output. You determine how it renders, generally by providing either a render function or a template, when you declare your custom tag.\\nThe render function, or the template, can access both named parameters (props) and unnamed parameters (args), as in:\\n{{mytag arg0 arg1 namedProp1=xxx namedProp2=yyy}} ... {{/mytag}}\\n\\nIn fact it can also access the current data item – or even the whole hierarchy of views and data…\\nNote: When you also use JsViews, custom tags acquire a whole new dimension. – They become tag controls, and you can build rich and complex single page apps cleanly and simply using custom tag controls – following an MVP or MVVM coding pattern.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"API: $.views.tags(name, tagFn)\",\r\n \"text\": \"API: $.views.tags(name, tagFn)\\nTo register a custom tag, you call the $.views.tags(...) API – with four alternative signatures:\\n\\n$.views.tags(\\\"mytag\\\", tagOptions); – where the properties of the tagOptions object will typically include a render: tagFn (specifying a render method), and/or a template: markupString (specifying a template to be rendered)\\n$.views.tags(\\\"mytag\\\", tagFn); – simplified form, when the only option being specified is a render method\\n$.views.tags(\\\"mytag\\\", tagTemplate); – simplified form, when the only option being specified is a template markup string\\n$.views.tags(namedTags); – where namedTags is a hash, declaring multiple custom tags\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"api\",\r\n \"title\": \"\",\r\n \"text\": \"Register a custom tag using a tagOptions object\\n\\n$.views.tags(\\\"mytag\\\", {\\n render: function(...) {...},\\n template: ...\\n});\\n\\n{{mytag ...}} ... {{/mytag}}\\n\\nRegister a simple 'render' function as a custom tag\\n\\n$.views.tags(\\\"mytag\\\", function(...) {\\n ...return rendered content\\n});\\n\\n{{mytag ...}} ... {{/mytag}}\\n\\nRegister a simple template string as a custom tag\\n\\n$.views.tags(\\\"mytag\\\", \\\"templateMarkup...\\\");\\n\\n{{mytag ...}} ... {{/mytag}}\\n\\nRegister multiple custom tags\\n\\n$.views.tags({\\n mytag1: {\\n render: function(val) {...},\\n template: ...\\n },\\n mytag2: function(val) {...},\\n mytag3: tag3TemplateString,\\n});\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"1 - Simple custom tag using just a function\",\r\n \"text\": \"1 - Simple custom tag using just a function\\n\\n\\n\\n This is the title:{{boldp title /}}\\n\\nfunction renderBoldP(value) {\\n return \\\"\\\" + value + \\\"\\\";\\n}\\n\\n$.views.tags(\\\"boldp\\\", renderBoldP);\\n\\nvar team = {\\n title: \\\"The A Team\\\"\\n};\\n\\nvar html = $(\\\"#teamTemplate\\\").render(team);\\n\\n$(\\\"#team\\\").html(html);\\nThe function is the render method for the tag\\n\\nDeclaring the custom tag\\n\\nfunction renderBoldP(value) {\\n return \\\"\\\" + value + \\\"\\\";\\n}\\n\\n$.views.tags(\\\"boldp\\\", renderBoldP);\\n\\nUsing the tag\\n\\nThis is the title:{{boldp title /}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Note: the this pointer within the tag render function is the instance of the tag, and can be used in more advanced usage, as in the next two examples:\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Wrapping block content using a function-based custom tag\",\r\n \"text\": \"Wrapping block content using a function-based custom tag\\nFirst of all – what if we want our tag to be used as a block tag, and to render itself by wrapping the rendered block content with the ‘bold p’ html –

    ...

    as in:\\n{{boldp}}\\n This is inside our block content:
    \\n {{:title}}\\n{{/boldp}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"2 - Rendering block content from a custom tag function\",\r\n \"text\": \"2 - Rendering block content from a custom tag function\\n\\n\\n\\n {{boldp}}\\n This is inside our block content:
    \\n {{:title}}\\n {{/boldp}}\\n\\nfunction renderBoldP(val) {\\n return \\\"\\\" + this.tagCtx.render(val) + \\\"\\\";\\n}\\n\\n$.views.tags(\\\"boldp\\\", renderBoldP);\\n\\nvar team = {\\n title: \\\"The A Team\\\"\\n};\\n\\nvar html = $(\\\"#teamTemplate\\\").render(team);\\n\\n$(\\\"#team\\\").html(html);\\nTo render the block content, we call this.tagCtx.render(val):\\nfunction renderBoldP(val) {\\n return \\\"

    \\\" + this.tagCtx.render(val) + \\\"

    \\\";\\n}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"As well as calling the render() method of this.tagCtx, you can access this.tagCtx.args, this.tagCtx.props, this.tagCtx.view.data and more…\\nThe tagCtx.args are the unnamed parameters. So in this example, there are two of them:\\n{{sometag title name}}\\n\\nIn addition to being accessible as tagCtx.args, unnamed parameters are also passed directly as parameters to the render method (if your tag is using one):\\nfunction sometagRenderMethod(title, name) {\\n // Here, this.tagCtx.args[1] and the name parameter are the same thing\\n}\\n\\nNow here is an example which has one unnamed parameter and two named parameters. You can access named parameters from tagCtx.props:\\n{{range members start=2 end=4}}\\n\\nWe’ll use that in our third sample, to show accessing properties from the render function of the tag:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"3 - Accessing tagCtx properties from the tag render function\",\r\n \"text\": \"3 - Accessing tagCtx properties from the tag render function\\n\\n\\n\\n

    {{:title}}

    \\n
      \\n {{range members start=1 end=2}} \\n
    • \\n {{:name}}\\n
    • \\n {{/range}}\\n
    \\n\\n$.views.tags(\\\"range\\\", function(array) {\\n var ret = \\\"\\\",\\n start = this.tagCtx.props.start,\\n end = this.tagCtx.props.end;\\n for (var i = start; i <= end; i++) {\\n // Render tag content, for this data item\\n ret += this.tagCtx.render(array[i]);\\n }\\n return ret;\\n});\\n\\nvar team = {\\n title: \\\"The A Team\\\",\\n members: [\\n {name: \\\"Robert\\\"},\\n {name: \\\"Sarah\\\"},\\n {name: \\\"Xavier\\\"},\\n {name: \\\"Adriana\\\"}\\n ]\\n};\\n\\nvar html = $(\\\"#teamTemplate\\\").render(team);\\n\\n$(\\\"#team\\\").html(html);\\nThis sample defines a {{range}} tag which iterates over an array which you pass as (unnamed) parameter. It also allows you to set named parameters start and end, to determine the range of iteration. (See also the range sample, for a more advanced implementation of a similar custom tag.)\\nYou call it like this:\\n{{range members start=1 end=2}}\\n ...\\n{{/range}}\\n\\nAnd the render function code accesses context to get at those named and unnamed parameters… :\\n$.views.tags(\\\"range\\\", function(array) {\\n ...\\n var start = this.tagCtx.props.start,\\n ...\\n // Render tag content, for this data item\\n ret += this.tagCtx.render(array[i]);\\n ...\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using a tag template instead of a render function\",\r\n \"text\": \"Using a tag template instead of a render function\\nIf the tag definition includes a template, but no render method, then the template will be used to render the tag.\\nLet’s re-implement all three examples above using custom tags which use templates instead of render functions.\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"1b - Simple custom tag using just a template\",\r\n \"text\": \"1b - Simple custom tag using just a template\\n\\n\\n\\n {{boldp title /}}\\n\\n$.views.tags(\\\"boldp\\\", {\\n template: \\\"{{:~tag.tagCtx.args[0]}}\\\"\\n});\\n\\nvar team = {\\n title: \\\"The A Team\\\"\\n};\\n\\nvar html = $(\\\"#teamTemplate\\\").render(team);\\n\\n$(\\\"#team\\\").html(html);\\nDeclaring the custom tag\\n$.views.tags(\\\"boldp\\\", {\\n template: \\\"

    {{:~tag.tagCtx.args[0]}}

    \\\"\\n});\\n\\nNote that since we are only declaring a template option, we could equivalently have used the simplified form:\\n$.views.tags(\\\"boldp\\\", \\\"

    {{:~tag.tagCtx.args[0]}}

    \\\");\\n\\nAs you see, the template is accessing the unnamed parameter tagCtx.args[0].\\nThe result is identical to the other implementation using a function. You call it just the same:\\n{{boldp title /}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"2b - Rendering block content from a custom tag template\",\r\n \"text\": \"2b - Rendering block content from a custom tag template\\n\\n\\n\\n {{boldp}}\\n This is the title:
    \\n {{:title}}\\n {{/boldp}}\\n\\n$.views.tags(\\\"boldp\\\", {\\n template: \\\"{{include tmpl=~tag.tagCtx.content/}}\\\"\\n});\\n\\nvar team = {\\n title: \\\"The A Team\\\"\\n};\\n\\nvar html = $(\\\"#teamTemplate\\\").render(team);\\n\\n$(\\\"#team\\\").html(html);\\nTo render block content, we use {{include tmpl=~tag.tagCtx.content/}}\\ntemplate: \\\"

    {{include tmpl=~tag.tagCtx.content/}}

    \\\"\\n\\nHere we are accessing the content property on the tagCtx, which provides a compiled template for the block content.\\nIt is also made available as a content property on the view object – and can be accessed from within a template using #content – which is an example of a view path – equivalent to #view.content. You can try out that alternative syntax by choosing Try it and changing the template above to

    {{include tmpl=#content/}}

    .\\nAgain, the result is identical to the other implementation using a function. You call it just the same:\\n{{boldp}}\\n This is the title:
    \\n {{:title}}\\n{{/boldp}}```\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Finally let’s re-implement the third example using just a template.\\nEven this example can be implemented as a custom tag which has no code at all. – Just a template, which is also able to access all the context that we were able to access from code in our render() function above.\\nThis illustrates the power of declarative templates…\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \" 3b - Accessing more context from the tag template\",\r\n \"text\": \" 3b - Accessing more context from the tag template\\n\\n\\n\\n

    {{:title}}

    \\n
      \\n {{range members start=1 end=2}} \\n
    • \\n {{:name}}\\n
    • \\n {{/range}}\\n
    \\n\\n$.views.tags(\\\"range\\\", {\\n template: \\n \\\"{{for ~tag.tagCtx.args[0]}}\\\" +\\n \\\"{{if #index >= ~tag.tagCtx.props.start && #index <= ~tag.tagCtx.props.end}}\\\" +\\n \\\"{{include tmpl=~tag.tagCtx.content/}}\\\" +\\n \\\"{{/if}}\\\" +\\n \\\"{{/for}}\\\"\\n});\\n\\nvar team = {\\n title: \\\"The A Team\\\",\\n members: [\\n {name: \\\"Robert\\\"},\\n {name: \\\"Sarah\\\"},\\n {name: \\\"Xavier\\\"},\\n {name: \\\"Adriana\\\"}\\n ]\\n};\\n\\nvar html = $(\\\"#teamTemplate\\\").render(team);\\n\\n$(\\\"#team\\\").html(html);\\nThe template accesses the same context as the function code above, to get at those named and unnamed parameters… :\\n{{for ~tag.tagCtx.args[0]}}\\n {{if #index >= ~tag.tagCtx.props.start && #index <= ~tag.tagCtx.props.end}}\\n {{include tmpl=~tag.tagCtx.content/}}\\n {{/if}}\\n{{/for}}\\n\\nThen after filtering for the items within the chosen range, using nested {{for}}{{if} tags, it renders the original block content for those items using {{include tmpl=~tag.tagCtx.content/}} (or you could use the equivalent {{include tmpl=#content/}}).\\nAgain, the result is identical to the other implementation using a function. You call it just the same:\\n{{range members start=1 end=2}}\\n ...\\n{{/range}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Custom tags using both a render function and a template\",\r\n \"text\": \"Custom tags using both a render function and a template\\nIf there is both a template and a render method, then the template will only be used if the render method returns undefined\\nLet’s take our {{range}} example using a render function, but provide a template which will be used as “fallback” rendering for the tag in the case when there are no items to render in the chosen range:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"A render() function and a template as \\\"fallback\\\"\",\r\n \"text\": \"A render() function and a template as \\\"fallback\\\"\\n\\n\\n\\n

    Members 2 to 4

    \\n
      \\n {{range members start=1 end=3}} \\n
    • \\n {{:name}}\\n
    • \\n {{/range}}\\n
    \\n\\n

    Members 5 to 8

    \\n
      \\n {{range members start=4 end=7}} \\n
    • \\n {{:name}}\\n
    • \\n {{/range}}\\n
    \\n\\n$.views.tags({\\n range: {\\n render: function(array) {\\n var ret = \\\"\\\",\\n start = this.tagCtx.props.start,\\n end = this.tagCtx.props.end;\\n for (var i = start; i <= end; i++) {\\n if (array[i]) {\\n // Render tag block content, for this data item\\n ret += this.tagCtx.content.render(array[i]);\\n }\\n }\\n return ret || undefined;\\n },\\n template: \\\"Nothing to render\\\"\\n }\\n});\\n\\nvar team = {\\n title: \\\"The A Team\\\",\\n members: [\\n {name: \\\"Robert\\\"},\\n {name: \\\"Sarah\\\"},\\n {name: \\\"Xavier\\\"},\\n {name: \\\"Adriana\\\"}\\n ]\\n};\\n\\nvar html = $(\\\"#teamTemplate\\\").render(team);\\n\\n$(\\\"#team\\\").html(html);\\n\\nFirst we will change the original code to test whether the item exists in the array, before rendering the block content.\\nAnd secondly, we will make sure that when there is an item we do render the block content and not the template. So we call this.tagCtx.content.render(array[i]), rather than this.tagCtx.render(array[i]).\\nThat’s because this.tagCtx.render(...) will actually look to see if there is template associated with the tag, (either a template on the tag definition, or a tmpl property on the tag) – in which case it will render that template and not the block content…\\nfor (var i = start; i <= end; i++) {\\n if (array[i]) {\\n // Render tag block content, for this data item\\n ret += this.tagCtx.content.render(array[i]);\\n }\\n}\\n\\nFinally, if there are no items to render, we will return undefined, so the tag will fall back on the template rendering.\\nreturn ret || undefined;\\n\\nAnd here is the “fallback” template:\\ntemplate: \\\"
  • Nothing to render
  • \\\"\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Adding tags as private resources for a parent template\",\r\n \"text\": \"Adding tags as private resources for a parent template\\nYou can pass in an existing template as an additional parentTemplate parameter, on any call to $.views.tags(...).\\nIn that way the tag (or tags) you are registering become ‘private tag resources’ for the parentTemplate, rather than being registered globally:\\n\"\r\n },\r\n {\r\n \"_type\": \"api\",\r\n \"title\": \"\",\r\n \"text\": \"Add multiple tags as resources, to a parent template\\n\\n$.views.tags({\\n mytag1: ...,\\n mytag2: ...\\n}, parentTemplate);\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Unregistering tags\",\r\n \"text\": \"Unregistering tags\\nTo unregister a previously registered tag, pass null to $.views.tags():\\n$.views.tags(\\\"mytag\\\", null);\\n// Tag \\\"mytag\\\" is no longer registered\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Custom tags and 'tag controls'\",\r\n \"text\": \"Custom tags and 'tag controls'\\nIf you use JsViews, your custom tag can be developed into a fully functional tag control, with its own life-cycle, properties and methods, etc. It can be used as a presenter according to the MVP pattern.\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"See also:\",\r\n \"text\": \"See also:\\n\"\r\n }\r\n ]\r\n },\r\n \"tagsapi\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"$.views.tags() is used to register custom tags. See Using custom tags for an overview, and simple examples.\\nThis topic provides more details.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"What is a custom tag?\",\r\n \"text\": \"What is a custom tag?\\nJsRender custom tags are named tags {{mytag ...}}, which you can register, and then use in your templates.\\nA custom tag can optionally use arguments (args) and named parameters (props), as in:\\n{{mytag arg0 arg1 namedProp1=xxx namedProp2=yyy}} ... {{/mytag}}\\n\\nNote: When you also use JsViews, custom tags acquire a whole new dimension. – They become tag controls, and you can build rich and complex single page apps cleanly and simply using custom tag controls – following an MVP or MVVM coding pattern.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Specifying tag options for a custom tag\",\r\n \"text\": \"Specifying tag options for a custom tag\\nThe following tag declaration registers a {{mytag}} custom tag:\\n$.views.tags(\\\"mytag\\\", tagOptions);\\n\\nThe tagOptions object (hash) specifies the tag options and determines how the tag will function. It can include:\\n\\nAn init() method: init: tagInitFn\\nA render() method: render: tagRenderFn\\nA template: template: tagTemplate\\n\\nIn addition tagOptions can specify tag inheritance (so that the custom tag derives from a base tag):\\n\\nbaseTag: ...\\n\\nIt can also specify the following more advanced options:\\n\\ncontentCtx: ...\\nconvert: ...\\nargDefault: ...\\nbindTo: ...\\nflow: ...\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Registering custom tags: $.views.tags(...)\",\r\n \"text\": \"Registering custom tags: $.views.tags(...)\\nTo register a custom tag, you call the $.views.tags(...) API.\\nThere are four alternative signatures:\\n\\n$.views.tags(\\\"mytag\\\", tagOptions); – where the properties of the tagOptions object will typically include a render: tagRenderFn (specifying a render() method), and/or a template: tagTemplate (specifying a template to be rendered)\\n$.views.tags(\\\"mytag\\\", tagRenderFn); – simplified form, when the only option being specified is a render() method\\n$.views.tags(\\\"mytag\\\", tagTemplate); – simplified form, when the only option being specified is a tag template to be rendered\\n$.views.tags(namedTags); This version is for declaring multiple custom tags, and namedTags is a hash (with custom tag names as keys and tagOption objects as values)\\n\\nHere are the details:\\n\"\r\n },\r\n {\r\n \"_type\": \"api\",\r\n \"title\": \"$.views.tags(...)\",\r\n \"text\": \"$.views.tags(...)\\nRegister one or more custom tags\\nRegister a custom tag, specifying chosen tag options\\n\\n$.views.tags(\\\"mytag\\\", {\\n render: function(...) {...},\\n template: ...\\n});\\n\\n{{mytag ...}} ... {{/mytag}}\\n\\nRegister a simple 'render' function as a custom tag\\n\\n$.views.tags(\\\"mytag\\\", function(...) {\\n ...return rendered content\\n});\\n\\n{{mytag ...}} ... {{/mytag}}\\n\\nRegister a template as a custom tag\\n\\n$.views.tags(\\\"mytag\\\", \\\"templateMarkup...\\\");\\n\\n{{mytag ...}} ... {{/mytag}}\\n\\nRegister multiple custom tags\\n\\n$.views.tags({\\n mytag1: {\\n render: function(val) {...},\\n template: ...\\n },\\n mytag2: function(val) {...},\\n mytag3: tag3TemplateString,\\n});\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"For simple samples showing the above alternative $.views.tags(...) signatures, see the Using custom tags overview topic:\\n\\nA custom tag using just a render() method\\nA custom tag using just a template\\nAccessing context within the render() method\\nAccessing context from the tag template\\n\\nThe Using custom tags overview also provides samples of custom tags which render block content – {{mytag}}...{{/mytag}}:\\n\\nRendering block content from a custom tag render() method\\nRendering block content from a custom tag template\\nA {{range}} custom tag, using a render() method\\nA {{range}} custom tag, with render() method and a template as “fallback”\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Custom tag options: Specifying init(), render(), template, baseTag:\",\r\n \"text\": \"Custom tag options: Specifying init(), render(), template, baseTag:\\nA custom tag in JsRender has a very simple ‘life-cyle’ consisting of two events for which you can optionally provide event handlers: the init() event, followed by the render() event. (If the custom tag is used in the context of JsViews, additional life-cycle events will also come into play, for data-binding, disposal, etc.)\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Providing an init() method\",\r\n \"text\": \"Providing an init() method\\nThe init() method acts as a handler for the init event of the custom tag, and is called with the tag instance as this parameter.\\n$.views.tags(\\\"mytag\\\", {\\n init: function(tagCtx, linkCtx, ctx) {\\n },\\n ...\\n});\\n\\nThe init() method arguments are:\\n\\ntagCtx: the tagCtx object, also available as this.tagCtx\\nlinkCtx: always 0 unless using data-linked tags with JsViews (See linkCtx object.)\\nctx: View context object\\n\\nThe following example uses the init() method to set the tag template based on the value of the mode prop:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Providing init()\",\r\n \"text\": \"Providing init()\\n\\n {{mytag name mode='a' /}}\\n {{mytag name mode='b' /}}\\n\\n\\n\\n\\n\\n$.views.tags(\\\"mytag\\\", {\\n init: function(tagCtx) {\\n this.template = (tagCtx.props.mode === \\\"a\\\" ? \\\"template A: {{:}} aaa\\\" : \\\"template B: {{:}} bbb\\\");\\n }\\n});\\n\\nvar myTmpl = $.templates(\\\"#myTmpl\\\"),\\n html = myTmpl.render({name: \\\"Jo\\\"});\\n\\n$(\\\"#page\\\").html(html);\\nTag declaration:\\n$.views.tags(\\\"mytag\\\", {\\n init: function(tagCtx) {\\n this.template = (tagCtx.props.mode === \\\"a\\\" ? \\\"template A ...\\\" : \\\"template B ...\\\");\\n }\\n});\\n\\nTag usage:\\n{{mytag name mode='a' /}}\\n{{mytag name mode='b' /}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Providing a render() method\",\r\n \"text\": \"Providing a render() method\\nThe render() method acts as a handler for the render event of the custom tag, and is called with the tag instance as this parameter, and with arguments arg1, arg2, ..., corresponding to the unnamed arguments passed in the tag declaration, {{mytag ...}}.\\nThe render() method can optionally be used to define how the tag renders, by returning an HTML markup string.\\n$.views.tags(\\\"mytag\\\", {\\n ...\\n render: function(...) {\\n return ...;\\n },\\n ...\\n});\\n\\nSee the example: A custom tag using just a render() method.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Providing a template\",\r\n \"text\": \"Providing a template\\nThe template option is used for declarative rendering, as an alternative to providing a render() method.\\nSee the example: A custom tag using just a template.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using both a template and a render() method\",\r\n \"text\": \"Using both a template and a render() method\\nIf the tag has both a render() method and a template, then the render() method is used to render the tag. But if render() returns undefined (or has no return value), then the template is used.\\nSee example: A {{range}} custom tag, with render() method and a template as “fallback”.\\nIt is also possible to provide both a template and a render() method, and to make use of the rendered template within the content returned by the render method. (In fact this.tagCtx.render(...) will return the rendered template).\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Specifying tag inheritance: the baseTag option\",\r\n \"text\": \"Specifying tag inheritance: the baseTag option\\nA custom tag can inherit from another tag (either built-in or custom).\\nFor example the {{range}} sample, linked above, can be rewritten in a more powerful but compact form, by making it inherit from the {{for}} tag (since the functionality of iterating over an array is common to both).\\nTo inherit from another tag, set the baseTag option to the name of the tag you want to derive from (or to the compiled tag object):\\n$.views.tags(\\\"range\\\", {\\n baseTag: \\\"for\\\",\\n ...\\n});\\n\\nSee the {{range}} sample: Extending the {{for}} tag.\\nCustom tag methods (init() or render()) can invoke the corresponding base tag method by calling one of the following API variants:\\nthis.base(a, b, ...); // Pass chosen arguments\\nthis.baseApply(arguments); // Pass on the calling arguments (or an array of args)\\n\\nThis is illustrated in the following sample, which takes the Providing init() sample above, and defines a derived {{mytag2}} which overrides init() and adds an error message when no valid mode was specified:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"baseTag\",\r\n \"text\": \"baseTag\\n\\n {{mytag2 name mode='a' /}}\\n {{mytag2 name mode='b' /}}\\n {{mytag2 name /}}\\n\\n\\n\\n\\n\\n$.views.tags(\\\"mytag\\\", {\\n init: function(tagCtx) {\\n this.templates = {\\n a: \\\"template A: {{:}} aaa\\\",\\n b: \\\"template B: {{:}} bbb\\\"\\n }; \\n this.template = this.templates[tagCtx.props.mode];\\n }\\n});\\n\\n$.views.tags(\\\"mytag2\\\", {\\n baseTag: \\\"mytag\\\",\\n init: function() { // Override the init() method\\n this.baseApply(arguments); // Call the base method\\n this.template = this.template || \\\"Error: Specify mode 'a' or 'b'\\\"; // If no template was assigned, render error message\\n }\\n});\\n\\nvar myTmpl = $.templates(\\\"#myTmpl\\\"),\\n html = myTmpl.render({name: \\\"Jo\\\"});\\n\\n$(\\\"#page\\\").html(html);\\nTag declaration:\\n$.views.tags(\\\"mytag2\\\", {\\n baseTag: \\\"mytag\\\",\\n init: function() { // Override the init() method\\n this.baseApply(arguments); // Call the base method\\n this.template = this.template || \\\"Error: Specify mode 'a' or 'b'\\\"; // If no template was assigned, render error message\\n }\\n});\\n\\nTag usage:\\n{{mytag2 name mode='a' /}}\\n{{mytag2 name mode='b' /}}\\n{{mytag2 name /}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Tag context\",\r\n \"text\": \"Tag context\\nWhen a custom tag is used in a template then the rendered template instance will be part of the view hierarchy.\\nThe instance of the tag is an object with properties and methods:\\n\\ntag object\\n\\nAssociated with the tag instance is a tag context object, tagCtx, providing most of the useful context for a tag, in particular:\\n\\ncontext passed down through the view hierarchy:\\n\\ncurrent view\\ncurrent data\\nparent tags\\ncontextual parameters\\n\\nadditional context coming from the tag itself, or its markup:\\n\\narguments (args) and named parameters (props)\\nrendered tag template\\nblock content\\ncontent of else blocks\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Accessing the tag instance object\",\r\n \"text\": \"Accessing the tag instance object\\nFrom a tag method (init() or render()), the this pointer is the instance of the tag (a tag object.)\\nFrom a tag template, the tag instance can be accessed as ~tag.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Accessing the tag context object: tagCtx\",\r\n \"text\": \"Accessing the tag context object: tagCtx\\nFrom a tag method the tagCtx object is available as this.tagCtx.\\nIn the init() method it is also passed directly as an argument (function(tagCtx ...)).\\nFrom a tag template, tagCtx can be accessed as ~tag.tagCtx.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Accessing the tag arguments or named parameters\",\r\n \"text\": \"Accessing the tag arguments or named parameters\\nThe values of arguments can be accessed as tagCtx.args, and named parameters as tagCtx.props.\\nFor example, if we have the following tag, which has two arguments and one named parameter:\\n{{sometag title name mode=\\\"edit\\\"}}\\n\\nthen from within the init() or render() method of sometag, the arguments and named parameters can be accessed as:\\nvar title = this.tagCtx.args[0];\\nvar name = this.tagCtx.args[1];\\nvar mode = this.tagCtx.props.mode;\\n\\nand from the tag template, the values can be accessed as ~tag.tagCtx.args or ~tag.tagCtx.props, and so might be rendered as:\\n...title: {{>~tag.tagCtx.args[0]}}
    name: {{>~tag.tagCtx.args[1]}}
    mode: {{>~tag.tagCtx.props.mode}}...\\n\\nIn addition to being available as tagCtx.args, arguments are also passed directly as arguments to the render() method, so sometag might use the following render() method, rather than a template, to render similar content:\\nfunction sometagRenderMethod(title, name) {\\n return \\\"...title: \\\" + title + \\\"
    name: \\\" + name + \\\"
    mode: \\\" + this.tagCtx.props.mode ...;\\n}\\n\\nThe tagCtx object also provides access to the markup expression for arguments and named parameters, as tagCtx.params.args and tagCtx.params.props.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Accessing the parent view and the current data\",\r\n \"text\": \"Accessing the parent view and the current data\\nThe contextual (parent) view for the tag instance is accessed as tagCtx.view. The corresponding (parent) data context is tagCtx.view.data.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Custom tag child views\",\r\n \"text\": \"Custom tag child views\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Custom tag rendering with template: \\\"mytag\\\" child view\",\r\n \"text\": \"Custom tag rendering with template: \\\"mytag\\\" child view\\nA custom tag template instance will be part of the view hierarchy, and the rendered tag may add additional child views to the view hierarchy.\\nIf {{mytag members}} renders using its template, that template will render as a child view (of type \\\"mytag\\\"). The default data context within the template will be the first argument passed to the tag (members in this case) which will be the view.data property of the child view.\\nIf the template markup includes template tags (other custom tags, or built-in tags) then there will be corresponding additional child views below the mytag view.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Rendering wrapped block content\",\r\n \"text\": \"Rendering wrapped block content\\n\\nAny tag can wrap block content, or use tmpl=... to reference external content:\\n{{mytag}}...{{/mytag}}\\n\\n{{mytag tmpl=... /}}\\n\\nBy default, a custom tag with no render() method or tag template will render its block content unchanged. A tag with an argument will move data context to the data passed in the argument: {{mytag somedata ...}}.\\nFor a custom tag rendering using a render() method, wrapped block content can be included using tagCtx.render().Note: To set the inner data context, pass in data as argument: tagCtx.render(someData). Otherwise inner and outer data context are the same.\\n$.views.tags(\\\"mytag\\\", {\\n ...\\n render: function() {\\n return ... + this.tagCtx.render() + ...;\\n },\\n ...\\n});\\n\\nSee the sample: Rendering block content from a custom tag render() method.\\n(For advanced scenarios the block content is also available as a compiled template object: tagCtx.content, so can be rendered using tagCtx.content.render(). See the template as fallback sample).\\nFor a custom tag rendering using a tag template, wrapped block content can be included using:\\n{{include tmpl=#content/}}\\n\\nor equivalently:\\n{{include tmpl=~tag.tagCtx.content/}}\\n\\nwhere in each case the inner data context can be modified by passing an argument, {{include someData tmpl=... /}}.\\nSee the sample: Rendering block content from a custom tag template.\\n\\nNote that if a custom tag has an external tmpl=... reference, and inline block content, then the external template takes precedence. However, the external template can behave as a wrapper, wrapping the inline block content (see: Wrapping content).\\nThis can provide for cascading content, as in the following sample:\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Cascading content\",\r\n \"text\": \"Cascading content\\n\\n {{mytag tmpl='#external'}}wrappedContent{{/mytag}}\\n\\n\\n\\n externalTmplStart
    {{include tmpl=#content/}}
    /externalTmplEnd\\n\\n\\n\\n\\n$.views.tags(\\\"mytag\\\", {\\n template: \\\"mytagStart{{include tmpl=#content/}}/mytagEnd\\\"\\n});\\n\\nvar myTmpl = $.templates(\\\"#myTmpl\\\"),\\n data = {},\\n html = myTmpl.render(data);\\n\\n$(\\\"#page\\\").html(html);\\n$.views.tags(\\\"mytag\\\", {\\n template: \\\"mytagStart...{{include tmpl=#content/}}.../mytagEnd\\\"\\n});\\n\\n{{mytag tmpl='#external'}}wrappedContent{{/mytag}}\\n\\n\\n\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"For a richer sample of a custom tag wrapping content, see: A {{range}} custom tag using a render() method.\\nThe following sample re-implements that {{range}} sample but uses a template rather than a render() method:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"A {{range}} custom tag using a tag template\",\r\n \"text\": \"A {{range}} custom tag using a tag template\\n\\n\\n\\n

    {{:title}}

    \\n
      \\n {{range members start=1 end=2}} \\n
    • \\n {{:name}}\\n
    • \\n {{/range}}\\n
    \\n\\n$.views.tags(\\\"range\\\", {\\n template: \\n \\\"{{for ~tag.tagCtx.args[0]}}\\\" +\\n \\\"{{if #index >= ~tag.tagCtx.props.start && #index <= ~tag.tagCtx.props.end}}\\\" +\\n \\\"{{include tmpl=#content/}}\\\" +\\n \\\"{{/if}}\\\" +\\n \\\"{{/for}}\\\"\\n});\\n\\nvar team = {\\n title: \\\"The A Team\\\",\\n members: [\\n {name: \\\"Robert\\\"},\\n {name: \\\"Sarah\\\"},\\n {name: \\\"Xavier\\\"},\\n {name: \\\"Adriana\\\"}\\n ]\\n};\\n\\nvar html = $(\\\"#teamTemplate\\\").render(team);\\n\\n$(\\\"#team\\\").html(html);\\nThe template accesses the same context as the render() method of the other {{range}} tag implementation, to obtain the arguments, named parameters and block content:\\n{{for ~tag.tagCtx.args[0]}}\\n {{if #index >= ~tag.tagCtx.props.start && #index <= ~tag.tagCtx.props.end}}\\n {{include tmpl=~#content/}}\\n {{/if}}\\n{{/for}}\\n\\nThen after filtering for the items within the chosen range, using nested {{for}}{{if} tags, it renders the original block content for those items using {{include tmpl=~#content/}} (or you could use the equivalent {{include tmpl=~tag.tagCtx.content/}}).\\nThe result is identical to the other implementation using a render() method. You call it just the same:\\n{{range members start=1 end=2}}\\n ...\\n{{/range}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Rendering else blocks\",\r\n \"text\": \"Rendering else blocks\\nAny tag can use {{else}} blocks. We might for example create a custom tag for rendering lists:\\n{{list}}\\n First item\\n{{else}}\\n Second item\\n{{else}}\\n Last item\\n{{/list}}\\n\\nA custom tag can provide specific behavior/rendering for {{else}} blocks:\\n\\nFor a tag with a render method, render() will be called once for the initial block and once for each {{else}} block.\\nSimilarly, for a custom tag with a tag template, the template will be rendered once for the initial block and once for each {{else}} block.\\nDuring rendering a custom tag can detect which block is being rendered, using tagCtx.index (see below), and can then output the content corresponding to the desired functionality.\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Tag context objects for {{else}} blocks: the tagCtxs array\",\r\n \"text\": \"Tag context objects for {{else}} blocks: the tagCtxs array\\nA tag with multiple blocks (initial block plus 1 or more {{else}} blocks) will have a tagCtxs array of tagCtx objects, one for each block.\\n\\nFrom a tag method the tagCtxs array is available as this.tagCtxs.\\nFrom a tag template, tagCtxs can be accessed as ~tag.tagCtxs.\\n\\nEach tagCtx object in tagCtxs has an index property (0 for the initial block), as well as the other properties (args, props etc.) corresponding to the markup (arguments, named properties…) on the corresponding tag ({{mytag ...}} or {{else ...}}).\\n\\nWithin a tag render() method, this.tagCtx will be the current tag context object for that block.\\nSimilarly, during rendering of the tag template, ~tag.tagCtx will be the current tagCtx.\\n\\nTo determine the index of the block being rendered, use tagCtx.index.\\nThese features are illustrated in the following sample:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Custom {{list}} tag using {{else}} blocks\",\r\n \"text\": \"Custom {{list}} tag using {{else}} blocks\\n\\n {{list numbered=true}}First{{else}}Second{{else}}Last{{/list}}\\n {{list}}first{{else}}last{{/list}}\\n\\n\\n\\n\\n\\n// Define custom {{list}} tag\\n$.views.tags(\\\"list\\\", function() {\\n // render() method\\n var ret = \\\"\\\", // Return value\\n index = this.tagCtx.index, // block index\\n listElem = this.tagCtxs[0].props.numbered ? \\\"ol\\\" : \\\"ul\\\"; // Wrapper or element, based on numbered=true property \\n\\n if (index===0) {\\n ret += \\\"<\\\" + listElem + \\\">\\\"; // First block: add opening wrapper\\n }\\n ret += \\\"\\\" + this.tagCtx.render() + \\\"\\\"; // Add li element and block content\\n if (index===this.tagCtxs.length-1) {\\n ret += \\\"\\\"; // Last block: add closing wrapper\\n }\\n return ret;\\n});\\n\\nvar myTmpl = $.templates(\\\"#myTmpl\\\"),\\n html = myTmpl.render();\\n\\n$(\\\"#page\\\").html(html);\\n\\nCustom {{list}} tag:\\n$.views.tags(\\\"list\\\", function() {\\n // render() method\\n var ret = \\\"\\\", // Return value\\n index = this.tagCtx.index, // block index\\n listElem = this.tagCtxs[0].props.numbered ? \\\"ol\\\" : \\\"ul\\\"; // Wrapper
      or
        element, based on numbered=true property \\n\\n if (index===0) {\\n ret += \\\"<\\\" + listElem + \\\">\\\"; // First block: add opening wrapper\\n }\\n ret += \\\"
      • \\\" + this.tagCtx.render() + \\\"
      • \\\"; // Add li element and block content\\n if (index===this.tagCtxs.length-1) {\\n ret += \\\"\\\"; // Last block: add closing wrapper\\n }\\n return ret;\\n});\\n\\nUsage:\\n{{list numbered=true}}First{{else}}Second{{else}}Last{{/list}}\\n{{list}}first{{else}}last{{/list}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"And here is a version of the sample with the same tag implemented using a tag template, rather than a render() method.\\nHere we use the init() method to assign a tag template dynamically, using a different wrapper (ol or ul) based on the numbered named property:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Custom {{list}} tag: Rendering {{else}} blocks from a tag template\",\r\n \"text\": \"Custom {{list}} tag: Rendering {{else}} blocks from a tag template\\n\\n {{list numbered=true}}First{{else}}Second{{else}}Last{{/list}}\\n {{list}}first{{else}}last{{/list}}\\n\\n\\n\\n\\n\\n// Define custom {{list}} tag\\n$.views.tags(\\\"list\\\", {\\n init: function() {\\n var listElem = this.tagCtx.props.numbered ? 'ol' : 'ul'; // Wrapper ol or ul element\\n this.template = \\n // First block: add opening wrapper\\n \\\"{{if ~tag.tagCtx.index===0}}<\\\" + listElem + \\\">{{/if}}\\\"\\n // Add li element and block content\\n + \\\"{{include tmpl=#content/}}\\\"\\n // Last block: add closing wrapper\\n + \\\"{{if ~tag.tagCtx.index===~tag.tagCtxs.length-1}}{{/if}}\\\";\\n }\\n});\\n\\nvar myTmpl = $.templates(\\\"#myTmpl\\\"),\\n html = myTmpl.render();\\n\\n$(\\\"#page\\\").html(html);\\n\\nCustom {{list}} tag:\\n$.views.tags(\\\"list\\\", {\\n init: function() {\\n var listElem = this.tagCtx.props.numbered ? 'ol' : 'ul'; // Wrapper ol or ul element\\n this.template = \\n // First block: add opening wrapper\\n \\\"{{if ~tag.tagCtx.index===0}}<\\\" + listElem + \\\">{{/if}}\\\"\\n // Add li element and block content\\n + \\\"
      • {{include tmpl=#content/}}
      • \\\"\\n // Last block: add closing wrapper\\n + \\\"{{if ~tag.tagCtx.index===~tag.tagCtxs.length-1}}{{/if}}\\\";\\n }\\n});\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Custom tags with no render() method and no tag template can also render multiple blocks, using {{else}}. Here is an example:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Default behavior for custom tag with {{else}} blocks\",\r\n \"text\": \"Default behavior for custom tag with {{else}} blocks\\n\\n {{mytag last}}\\n First: {{:}}
        \\n {{else first}}\\n Last: {{:}}
        \\n {{else phone}}\\n Phone: {{:}}
        \\n {{/mytag}}\\n\\n\\n\\n\\n\\n// Define custom {{mytag}} tag\\n$.views.tags(\\\"mytag\\\", {});\\n\\nvar myTmpl = $.templates(\\\"#myTmpl\\\"),\\n data = {first: \\\"Jo\\\", last: \\\"Blow\\\", phone: \\\"111-111-1111\\\"},\\n html = myTmpl.render(data);\\n\\n$(\\\"#page\\\").html(html);\\n\\n\\nCustom {{mytag}} which simply renders each block as is:\\n$.views.tags(\\\"mytag\\\", {});\\n\\nThe default data context of each block is the value passed to the first argument.\\n{{mytag last}}\\n First: {{:}}...\\n{{else first}}\\n ...\\n{{else phone}}\\n ...\\n{{/mytag}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Custom tag hierarchy – Accessing parent tags\",\r\n \"text\": \"Custom tag hierarchy – Accessing parent tags\\nNested custom tags can determine parent tags, and can be designed with functionality or rendering that is based on parent or child tags, as in the following example where a {{layout}} tag determines the layout for child {{cell}} tags:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"text\": \"\\n {{layout 'vertical'}}{{cell}}one{{/cell}}{{cell}}two{{/cell}}{{/layout}}\\n
        \\n {{layout 'horizontal'}}{{cell}}one{{/cell}}{{cell}}two{{/cell}}{{/layout}}\\n\\n\\n\\n$.views.tags({\\n layout: {\\n render: function(mode) {\\n if (mode === \\\"vertical\\\") {\\n this.vertical = true;\\n return \\\"\\\" + this.tagCtx.render() + \\\"\\\";\\n } else {\\n return \\\"\\\" + this.tagCtx.render() + \\\"\\\";\\n }\\n }\\n },\\n cell: {\\n render: function() {\\n return this.parents.layout.vertical\\n ? \\\"\\\" + this.tagCtx.render() + \\\"\\\"\\n : \\\"\\\" + this.tagCtx.render() + \\\"\\\";\\n }\\n }\\n})\\n\\nvar myTmpl = $.templates(\\\"#myTmpl\\\"),\\n data = { name: \\\"Jo\\\" },\\n html = myTmpl.render(data);\\n\\n$(\\\"#page\\\").html(html);\\n{{layout 'vertical'}}\\n {{cell}}one{{/cell}}\\n {{cell}}two{{/cell}}\\n{{/layout}}\\n
        \\n{{layout 'horizontal'}}\\n {{cell}}one{{/cell}}\\n {{cell}}two{{/cell}}\\n{{/layout}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"The following properties provide access to ancestor custom tags:\\nparents property:\\nThe parents property is a hash of all the ancestor custom tags. In the above sample the {{cell}} instances have a parents.layout property, used to determine whether the assigned layout is vertical, and to render accordingly:\\nrender: function() {\\n return this.parents.layout.vertical\\n ? \\\"\\\" + this.tagCtx.render() + \\\"\\\"\\n : \\\"\\\" + this.tagCtx.render() + \\\"\\\";\\n}\\n\\nparent property:\\nThe tag instance also has a parent property - in the above sample, the parent of the {{cell}} instance is the {{layout}} instance.\\n~parentTags contextual parameter:\\nThe ctx property of a tag instance also has a parentTags property, equivalent to the parents hash. This can be used in the following alternative implementation of the {{cell}} tag above, using a tag template rather than a render() method:\\n$.view.tags(\\\"cell\\\", {\\n template:\\n \\\"{{if ~parentTags.layout.vertical}}{{include tmpl=#content/}}\\\"\\n + \\\"{{else}}{{include tmpl=#content/}}{{/if}}\\\"\\n});\\n\\nIn fact, in a tag template ~parentTags and ~tag.parents are equivalent.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Accessing contextual parameters and helpers\",\r\n \"text\": \"Accessing contextual parameters and helpers\\n\\nFrom a tag template:\\n\\nContextual parameters and helpers can be accessed using ~myParamOrHelper\\n\\nFrom a tag method:\\n\\nContextual parameters and helpers can be accessed using this.ctxPrm(\\\"myParamOrHelper\\\")\\n(Note: contextual parameters can also be accessed using this.ctx.myParamOrHelper, and global helpers can be accessed using $views.helpers(\\\"myHelper\\\"))\\n\\n\\nAs an advanced example of custom tag rendering based on contextual parameters, here is a modified version of the above layout sample, where instead of wrapping {{cell}} tags in a {{layout}} tag, we instead wrap in a simple {{include}} on which we set a contextual parameter specifying layout: layout='vertical':\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"text\": \"\\n {{include ~layout='vertical'}}\\n {{cell}}one{{/cell}}\\n {{cell last=true}}two{{/cell}}\\n {{/include}}\\n
        \\n {{include ~layout='horizontal'}}\\n {{cell}}one{{/cell}}\\n {{cell last=true}}two{{/cell}}\\n {{/include}}\\n\\n\\n\\n$.views.tags({\\n cell: {\\n render: function() {\\n var res = \\\"\\\",\\n vertical = this.ctxPrm(\\\"layout\\\") === \\\"vertical\\\",\\n parentView = this.tagCtx.view.parent,\\n cellIndex = parentView.cellIndex = parentView.cellIndex === undefined ? 0 : parentView.cellIndex +1;\\n if (vertical) {\\n if (cellIndex===0) {\\n res += \\\"\\\";\\n }\\n res += \\\"\\\";\\n if (this.tagCtx.props.last) {\\n res += \\\"\\\" + this.tagCtx.render() + \\\"\\\";\\n }\\n } else {\\n if (cellIndex===0) {\\n res += \\\"\\\";\\n }\\n res += \\\"\\\";\\n if (this.tagCtx.props.last) {\\n res += \\\"\\\" + this.tagCtx.render() + \\\"\\\";\\n }\\n }\\n return res;\\n }\\n }\\n})\\n\\nvar myTmpl = $.templates(\\\"#myTmpl\\\"),\\n data = { name: \\\"Jo\\\" },\\n html = myTmpl.render(data);\\n\\n$(\\\"#page\\\").html(html);\\n{{include ~layout='vertical'}}\\n {{cell}}one{{/cell}}\\n {{cell last=true}}two{{/cell}}\\n{{/include}}\\n
        \\n{{include ~layout='horizontal'}}\\n {{cell}}one{{/cell}}\\n {{cell last=true}}two{{/cell}}\\n{{/include}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Advanced options\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Specifying data context within tag content: the contentCtx option\",\r\n \"text\": \"Specifying data context within tag content: the contentCtx option\\nDefault behavior:\\nBy default the data context within the tag is the value of the first argument. (See View hierarchy – inner data context).\\nSo if {{mytag}} uses a template then {{mytag members/}} will render the template with members as data context.\\nSimilarly if {{mytag}} is used as a block tag, then the block content within {{mytag members}}...{{/mytag}} will render with members as data context.\\nModified behavior:\\nTo make the data context for tag content the same as parent context, set the contentCtx option to true:\\n$.views.tags(\\\"mytag\\\", {\\n ...\\n contentCtx: true, // The data context inside {{mytag}} will be the same as the outer context\\n ...\\n});\\n\\nTo specify a different data context for tag content, set the contentCtx option to a function returning the chosen data. (The this pointer of the contentCtx function is the tag instance. The default data context, arg0 is passed to it as argument.)\\nFor example, with the following tag option setting, the inner data context is given by the dataCtx named property:\\n$.views.tags(\\\"mytag\\\", {\\n ...\\n contentCtx: function(arg0) {\\n return this.tagCtx.props.dataCtx; // The returned value will be the data context inside {{mytag}}\\n },\\n ...\\n});\\n\\nUsage:\\n{{mytag ... dataCtx=.../}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Providing a default converter: the convert option\",\r\n \"text\": \"Providing a default converter: the convert option\\nOn any tag, including custom tags, a converter can be specified directly on the tag (see Using converters with other tags):\\n{{mytag name convert='toUpperCase'/}}\\n\\nTo provide a default converter on a custom tag (used as fallback if no converter is specified on the tag), set the convert tag option to a function, or to a registered converter name:\\n$.views.tags(\\\"mytag\\\", {\\n ...\\n convert: 'toLowerCase', // Default converter. (A function or a registered converter name)\\n ...\\n});\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Specifying a default argument: the argDefault option\",\r\n \"text\": \"Specifying a default argument: the argDefault option\\nIf a custom tag uses a render() method, then the arguments of the tag are passed to the render method:\\n{{myTag arg0 arg1/}}\\n\\n$.views.tags(\\\"myTag\\\", {\\n render: function(arg0, arg1) {...}\\n});\\n\\nIf the tag is called without arguments, then the render method will be called with the current data context as first argument, so therefore writing {{myTag/}} is equivalent to writing {{myTag #data/}}\\nTo override this behavior, set the argDefault option to false. The first argument will then not default to current data, and the render method will instead be called without arguments.\\n{{myTag/}}\\n\\n$.views.tags(\\\"myTag\\\", {\\n render: function() {\\n // arguments.length is 0\\n },\\n argDefault: false\\n});\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Specifying bound arguments and properties: the bindTo option\",\r\n \"text\": \"Specifying bound arguments and properties: the bindTo option\\nThe bindTo option is designed primarily for use with data binding, with JsViews, and allows specifying which arguments/properties are data-bound for two-way binding.\\nIn JsRender, the bindTo option can be used in conjunction with converters. Set the bindTo option to an array, such as [0, 1, 2], or [\\\"title\\\", 1] – where integers refer to arguments and strings to named properties – to determine what values are passed to the converter. (If bindTo is not set, then the values of all the arguments will be passed to the converter.)\\nBy default the value returned by the converter will be passed as first argument to the render() method. However, if the converter returns an array, then the values will be used to convert each of the targeted arguments or properties specified in bindTo.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Specifying flow behavior: the flow option\",\r\n \"text\": \"Specifying flow behavior: the flow option\\nA ‘flow’ tag – which has the flow option set to true – is a tag that does not appear in the parent tags hierarchy, so is not accessed via this.parent, ~tagParents etc.\\nThe built-in tags such as {{for}}, {{props}} and {{if}} are flow tags.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Methods and properties available on a custom tag instance\",\r\n \"text\": \"Methods and properties available on a custom tag instance\\nA custom tag instance can access the following methods and properties\\n\\ntag.ctxPrm()\\ntag.cvt()\\ntag.cvtArgs()\\ntag.bndArgs()\\ntag.ctx\\ntag.parent\\ntag.parents\\ntag.tagCtx\\ntag.tagCtxs\\ntag.tagName\\ntag.base\\nrendering\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Adding tags as private resources for a parent template\",\r\n \"text\": \"Adding tags as private resources for a parent template\\nYou can pass in an existing template as an additional parentTemplate parameter, on any call to $.views.tags(...).\\nIn that way the tag (or tags) you are registering become ‘private tag resources’ for the parentTemplate, rather than being registered globally:\\n\"\r\n },\r\n {\r\n \"_type\": \"api\",\r\n \"title\": \"\",\r\n \"text\": \"Add multiple tags as resources, to a parent template\\n\\n$.views.tags({\\n mytag1: ...,\\n mytag2: ...\\n}, parentTemplate);\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Unregistering tags\",\r\n \"text\": \"Unregistering tags\\nTo unregister a previously registered tag, pass null to $.views.tags():\\n$.views.tags(\\\"mytag\\\", null);\\n// Tag \\\"mytag\\\" is no longer registered\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Custom tags and 'tag controls'\",\r\n \"text\": \"Custom tags and 'tag controls'\\nIf you use JsViews, your custom tag can be developed into a fully functional tag control, with its own life-cycle, properties and methods, etc. It can be used as a presenter according to the MVP pattern.\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"See also:\",\r\n \"text\": \"See also:\\n\"\r\n }\r\n ]\r\n }\r\n}"]} \ No newline at end of file +{"version":3,"sources":["find-jsrapi.js"],"names":["content","$","views","documentation","find","jsrapi","useStorage","parseJSON","localStorage","getItem","sections","_type","title","text","jsrtags","assigntag","htmltag","includetag","fortag","propstag","iftag","elsetag","commenttag","allowcodetag","customtagsapi","rendertmpl","tmplrender","d.render","db.render","compiletmpl","d.templates","jsrregister","tags","jsrobjects","viewsobject","settingsobject","subobject","templateobject","viewobject","tagobject","viewcontextobject","tagcontextobject","node/browserify","node/renderfile","node/filetmpls","jsrnode","node/install","node/express-hapi","node/server-browser","tagsyntax","paths","tmplsyntax","settings","settings/delimiters","settings/onerror","settings/dbgmode","settings/debugmode","settings/allowcode","settings/advanced","onerror","advanced","apps","getindex","contextualparams","parentdata","jsrmodel","helpersapi","helpers","convertersapi","converters","nojqueryapi","node/webpack","viewmodelsapi","lifecycle","globals","tagsapi","url"],"mappings":"AAAA,GAAIA,SAAUC,EAAEC,MAAMC,cAAcH,OAEpCA,SAAQI,KAAKC,OAASL,QAAQM,YAAcL,EAAEM,UAAUC,aAAaC,QAAQ,mCAE3EJ,QACEK,WAEIC,MAAS,OACTC,MAAS,GACTC,KAAQ,4DAGRF,MAAS,QACTC,MAAS,UACTC,KAAQ,eAIdC,SACEJ,WAEIC,MAAS,QACTC,MAAS,aACTC,KAAQ,iBAGRF,MAAS,OACTC,MAAS,uBACTC,KAAQ,+IAGRF,MAAS,OACTC,MAAS,aACTC,KAAQ,iQAGRF,MAAS,OACTC,MAAS,6BACTC,KAAQ,6EAGRF,MAAS,OACTC,MAAS,6BACTC,KAAQ,8PAIdE,WACEL,WAEIC,MAAS,MACTC,MAAS,YACTC,KAAQ,iLAGRF,MAAS,OACTC,MAAS,0BACTC,KAAQ,uHAGRF,MAAS,SACTC,MAAS,oBACTC,KAAQ,6EAGRF,MAAS,SACTC,MAAS,kBACTC,KAAQ,2PAGRF,MAAS,SACTC,MAAS,kBACTC,KAAQ,6KAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,0QAGRF,MAAS,QACTC,MAAS,YACTC,KAAQ,iBAIdG,SACEN,WAEIC,MAAS,MACTC,MAAS,WACTC,KAAQ,6MAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,0BAGRF,MAAS,SACTC,MAAS,GACTC,KAAQ,gHAGRF,MAAS,OACTC,MAAS,wDACTC,KAAQ,6RAGRF,MAAS,QACTC,MAAS,YACTC,KAAQ,iBAIdI,YACEP,WAEIC,MAAS,MACTC,MAAS,yBACTC,KAAQ,uNAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,0BAGRF,MAAS,SACTC,MAAS,GACTC,KAAQ,0cAGRF,MAAS,OACTC,MAAS,kDACTC,KAAQ,6gBAGRF,MAAS,MACTC,MAAS,yBACTC,KAAQ,kjBAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,k4BAGRF,MAAS,SACTC,MAAS,qCACTC,KAAQ,yHAGRF,MAAS,QACTC,MAAS,YACTC,KAAQ,iBAIdK,QACER,WAEIC,MAAS,MACTC,MAAS,cACTC,KAAQ,wiBAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,8BAGRF,MAAS,SACTC,MAAS,iBACTC,KAAQ,wFAGRF,MAAS,SACTC,MAAS,4BACTC,KAAQ,sjBAGRF,MAAS,SACTC,MAAS,gBACTC,KAAQ,4FAGRF,MAAS,OACTC,MAAS,sCACTC,KAAQ,gNAGRF,MAAS,MACTC,MAAS,oCACTC,KAAQ,oYAGRF,MAAS,SACTC,MAAS,sCACTC,KAAQ,iHAGRF,MAAS,SACTC,MAAS,uCACTC,KAAQ,uIAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,+GAGRF,MAAS,OACTC,MAAS,6GACTC,KAAQ,+MAGRF,MAAS,OACTC,MAAS,kGACTC,KAAQ,kaAGRF,MAAS,OACTC,MAAS,iEACTC,KAAQ,0tBAGRF,MAAS,SACTC,MAAS,4CACTC,KAAQ,irCAGRF,MAAS,SACTC,MAAS,8BACTC,KAAQ,kaAGRF,MAAS,SACTC,MAAS,kDACTC,KAAQ,ksDAGRF,MAAS,OACTC,MAAS,qEACTC,KAAQ,gzBAGRF,MAAS,SACTE,KAAQ,q9BAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,mOAGRF,MAAS,SACTE,KAAQ,iiCAGRF,MAAS,OACTC,MAAS,6HACTC,KAAQ,0wCAGRF,MAAS,SACTE,KAAQ,k4BAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,4FAGRF,MAAS,SACTE,KAAQ,w3CAGRF,MAAS,OACTC,MAAS,wDACTC,KAAQ,mSAGRF,MAAS,SACTE,KAAQ,sjCAGRF,MAAS,SACTE,KAAQ,kkBAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,sSAGRF,MAAS,OACTC,MAAS,0GACTC,KAAQ,q/BAGRF,MAAS,SACTC,MAAS,GACTC,KAAQ,uoCAGRF,MAAS,OACTC,MAAS,+DACTC,KAAQ,mjBAGRF,MAAS,SACTC,MAAS,2DACTC,KAAQ,q8BAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,wHAGRF,MAAS,SACTE,KAAQ,oqCAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,8IAGRF,MAAS,QACTC,MAAS,YACTC,KAAQ,iBAIdM,UACET,WAEIC,MAAS,MACTC,MAAS,gBACTC,KAAQ,qhBAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,8BAGRF,MAAS,SACTC,MAAS,mBACTC,KAAQ,wFAGRF,MAAS,SACTC,MAAS,8BACTC,KAAQ,gxBAGRF,MAAS,OACTC,MAAS,wCACTC,KAAQ,+MAGRF,MAAS,MACTC,MAAS,wCACTC,KAAQ;GAGRF,MAAS,SACTE,KAAQ,kHAGRF,MAAS,SACTE,KAAQ,u6BAGRF,MAAS,OACTC,MAAS,kCACTC,KAAQ,wQAGRF,MAAS,SACTE,KAAQ,igBAGRF,MAAS,OACTC,MAAS,8EACTC,KAAQ,meAGRF,MAAS,SACTE,KAAQ,ijCAGRF,MAAS,OACTC,MAAS,mHACTC,KAAQ,+UAGRF,MAAS,OACTC,MAAS,qGACTC,KAAQ,ubAGRF,MAAS,OACTC,MAAS,oEACTC,KAAQ,myBAGRF,MAAS,SACTC,MAAS,0CACTC,KAAQ,61CAGRF,MAAS,SACTC,MAAS,4BACTC,KAAQ,gcAGRF,MAAS,SACTC,MAAS,kDACTC,KAAQ,mxDAGRF,MAAS,OACTC,MAAS,wEACTC,KAAQ,64BAGRF,MAAS,SACTE,KAAQ,sjCAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,iOAGRF,MAAS,SACTE,KAAQ,+mCAGRF,MAAS,OACTC,MAAS,gIACTC,KAAQ,8wCAGRF,MAAS,SACTE,KAAQ,wjCAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,0FAGRF,MAAS,SACTE,KAAQ,y+CAGRF,MAAS,OACTC,MAAS,wDACTC,KAAQ,mSAGRF,MAAS,SACTE,KAAQ,mnCAGRF,MAAS,SACTE,KAAQ,wqBAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,0SAGRF,MAAS,QACTC,MAAS,YACTC,KAAQ,iBAIdO,OACEV,WAEIC,MAAS,MACTC,MAAS,aACTC,KAAQ,iaAGRF,MAAS,OACTC,MAAS,qCACTC,KAAQ,sJAGRF,MAAS,MACTC,MAAS,kCACTC,KAAQ,+hBAGRF,MAAS,OACTC,MAAS,kBACTC,KAAQ,qMAGRF,MAAS,MACTC,MAAS,kDACTC,KAAQ,2dAGRF,MAAS,SACTC,MAAS,8BACTC,KAAQ,4UAGRF,MAAS,QACTC,MAAS,YACTC,KAAQ,iBAIdQ,SACEX,WAEIC,MAAS,OACTC,MAAS,0EACTC,KAAQ,0/BAGRF,MAAS,QACTC,MAAS,WACTC,KAAQ,gBAIdS,YACEZ,WAEIC,MAAS,MACTC,MAAS,uBACTC,KAAQ,4aAGRF,MAAS,OACTC,MAAS,6CACTC,KAAQ,6TAIdU,cACEb,WAEIC,MAAS,OACTC,MAAS,GACTC,KAAQ,oyBAGRF,MAAS,MACTC,MAAS,YACTC,KAAQ,8JAGRF,MAAS,MACTC,MAAS,aACTC,KAAQ,2KAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,oGAGRF,MAAS,SACTC,MAAS,YACTC,KAAQ,soBAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,qGAGRF,MAAS,SACTC,MAAS,6BACTC,KAAQ,mvBAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,kUAGRF,MAAS,SACTC,MAAS,yBACTC,KAAQ,22BAIdW,eACEd,WAEIC,MAAS,OACTC,MAAS,uBACTC,KAAQ,0QAGRF,MAAS,QACTC,MAAS,OACTC,KAAQ,YAIdY,YACEf,WAEIC,MAAS,OACTC,MAAS,GACTC,KAAQ,8tBAGRF,MAAS,QACTC,MAAS,OACTC,KAAQ,YAIda,YACEhB,WAEIC,MAAS,OACTC,MAAS,kBACTC,KAAQ,smBAGRF,MAAS,MACTC,MAAS,wBACTC,KAAQ,uJAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,mHAGRF,MAAS,SACTC,MAAS,2BACTC,KAAQ,yUAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,4GAGRF,MAAS,SACTC,MAAS,0BACTC,KAAQ,uUAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,8CAGRF,MAAS,MACTC,MAAS,0CACTC,KAAQ;GAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,65BAGRF,MAAS,SACTC,MAAS,sCACTC,KAAQ,mpBAGRF,MAAS,OACTC,MAAS,uDACTC,KAAQ,0MAGRF,MAAS,MACTC,MAAS,uDACTC,KAAQ,iUAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,0XAGRF,MAAS,SACTC,MAAS,gDACTC,KAAQ,guBAGRF,MAAS,OACTC,MAAS,+CACTC,KAAQ,4TAGRF,MAAS,QACTC,MAAS,YACTC,KAAQ,iBAIdc,YACEjB,WAEIC,MAAS,OACTC,MAAS,oBACTC,KAAQ,keAGRF,MAAS,MACTC,MAAS,uDACTC,KAAQ,mUAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,0BAGRF,MAAS,SACTC,MAAS,4BACTC,KAAQ,0fAGRF,MAAS,QACTC,MAAS,YACTC,KAAQ,iBAIde,aACElB,WAEIC,MAAS,OACTC,MAAS,wBACTC,KAAQ,+XAGRF,MAAS,MACTC,MAAS,8DACTC,KAAQ,kVAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,0BAGRF,MAAS,SACTC,MAAS,oCACTC,KAAQ,+VAGRF,MAAS,QACTC,MAAS,YACTC,KAAQ,iBAIdgB,aACEnB,WAEIC,MAAS,OACTC,MAAS,GACTC,KAAQ,qEAGRF,MAAS,OACTC,MAAS,qBACTC,KAAQ,6xBAGRF,MAAS,SACTC,MAAS,wDACTC,KAAQ,idAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,4CAGRF,MAAS,SACTC,MAAS,mDACTC,KAAQ,2tBAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,saAGRF,MAAS,OACTC,MAAS,wDACTC,KAAQ,6NAGRF,MAAS,SACTC,MAAS,yFACTC,KAAQ,04BAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,2GAGRF,MAAS,SACTC,MAAS,mFACTC,KAAQ,y2BAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,uCAGRF,MAAS,OACTC,MAAS,4CACTC,KAAQ,oGAGRF,MAAS,QACTC,MAAS,YACTC,KAAQ,iBAIdiB,eACEpB,WAEIC,MAAS,OACTC,MAAS,GACTC,KAAQ,2JAGRF,MAAS,OACTC,MAAS,mBACTC,KAAQ,qgBAGRF,MAAS,MACTC,MAAS,mBACTC,KAAQ,mVAGRF,MAAS,SACTC,MAAS,mCACTC,KAAQ,yRAGRF,MAAS,SACTC,MAAS,gDACTC,KAAQ,kZAGRF,MAAS,SACTC,MAAS,wCACTC,KAAQ,0SAGRF,MAAS,SACTC,MAAS,4CACTC,KAAQ,sVAGRF,MAAS,OACTC,MAAS,0CACTC,KAAQ,kIAGRF,MAAS,MACTC,MAAS,8BACTC,KAAQ,oKAGRF,MAAS,SACTC,MAAS,iCACTC,KAAQ,wgBAGRF,MAAS,OACTC,MAAS,6CACTC,KAAQ,6NAGRF,MAAS,OACTC,MAAS,8BACTC,KAAQ,yNAGRF,MAAS,OACTC,MAAS,mEACTC,KAAQ,q2BAGRF,MAAS,MACTC,MAAS,iDACTC,KAAQ,oiBAGRF,MAAS,SACTC,MAAS,2DACTC,KAAQ,qjCAGRF,MAAS,OACTC,MAAS,8DACTC,KAAQ,sTAGRF,MAAS,SACTC,MAAS,wHACTC,KAAQ,gbAGRF,MAAS,OACTC,MAAS,sDACTC,KAAQ,0hBAGRF,MAAS,QACTC,MAAS,YACTC,KAAQ,iBAIdkB,aACErB,WAEIC,MAAS,QACTC,MAAS,GACTC,KAAQ,MAIdmB,MACEtB,WAEIC,MAAS,OACTC,MAAS,GACTC,KAAQ,2DAGRF,MAAS,OACTC,MAAS,wBACTC,KAAQ,g2BAGRF,MAAS,OACTC,MAAS,2BACTC,KAAQ,mvBAGRF,MAAS,SACTC,MAAS,4CACTC,KAAQ,kuBAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,+DAGRF,MAAS,SACTC,MAAS,qCACTC,KAAQ,qsBAGRF,MAAS,OACTC,MAAS,uFACTC,KAAQ,y8BAGRF,MAAS,SACTC,MAAS,+CACTC,KAAQ,msCAGRF,MAAS,OACTC,MAAS,0EACTC,KAAQ,sPAGRF,MAAS,SACTC,MAAS,0CACTC,KAAQ,stCAGRF,MAAS,OACTC,MAAS,iEACTC,KAAQ,uuBAGRF,MAAS,SACTC,MAAS,4DACTC,KAAQ,gyBAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,60BAGRF,MAAS,SACTC,MAAS,2DACTC,KAAQ;GAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,iSAGRF,MAAS,SACTC,MAAS,qDACTC,KAAQ,uuBAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,guBAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,8GAGRF,MAAS,OACTC,MAAS,iEACTC,KAAQ,mfAGRF,MAAS,SACTC,MAAS,mFACTC,KAAQ,2nFAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,8gBAGRF,MAAS,OACTC,MAAS,iCACTC,KAAQ,iPAGRF,MAAS,OACTC,MAAS,4CACTC,KAAQ,0FAGRF,MAAS,QACTC,MAAS,YACTC,KAAQ,iBAIdoB,YACEvB,WAEIC,MAAS,QACTC,MAAS,GACTC,KAAQ,MAIdqB,aACExB,WAEIC,MAAS,OACTC,MAAS,GACTC,KAAQ,gzBAIdsB,gBACEzB,aAEF0B,WACE1B,aAEF2B,gBACE3B,WAEIC,MAAS,OACTC,MAAS,GACTC,KAAQ,gQAGRF,MAAS,OACTC,MAAS,sBACTC,KAAQ,qHAGRF,MAAS,OACTC,MAAS,sBACTC,KAAQ,yPAGRF,MAAS,OACTC,MAAS,+DACTC,KAAQ,0WAIdyB,YACE5B,WAEIC,MAAS,OACTC,MAAS,GACTC,KAAQ,qDAGRF,MAAS,OACTC,MAAS,iEACTC,KAAQ,iPAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,4MAGRF,MAAS,OACTC,MAAS,yBACTC,KAAQ,2nBAGRF,MAAS,OACTC,MAAS,qBACTC,KAAQ,kVAGRF,MAAS,OACTC,MAAS,qBACTC,KAAQ,+gBAGRF,MAAS,OACTC,MAAS,uBACTC,KAAQ,qbAGRF,MAAS,OACTC,MAAS,sBACTC,KAAQ,qhBAGRF,MAAS,OACTC,MAAS,yBACTC,KAAQ,yjBAGRF,MAAS,SACTC,MAAS,+CACTC,KAAQ,kbAGRF,MAAS,OACTC,MAAS,wBACTC,KAAQ,2oCAGRF,MAAS,OACTC,MAAS,oEACTC,KAAQ,0jBAGRF,MAAS,SACTC,MAAS,wCACTC,KAAQ,uxBAGRF,MAAS,OACTC,MAAS,qBACTC,KAAQ,kMAGRF,MAAS,OACTC,MAAS,gCACTC,KAAQ,gcAGRF,MAAS,QACTC,MAAS,YACTC,KAAQ,iBAId0B,WACE7B,WAEIC,MAAS,OACTC,MAAS,0EACTC,KAAQ,uVAGRF,MAAS,OACTC,MAAS,6DACTC,KAAQ,gVAGRF,MAAS,OACTC,MAAS,wBACTC,KAAQ,ubAGRF,MAAS,OACTC,MAAS,sBACTC,KAAQ,kbAGRF,MAAS,OACTC,MAAS,uBACTC,KAAQ,yEAGRF,MAAS,OACTC,MAAS,sBACTC,KAAQ,wEAGRF,MAAS,OACTC,MAAS,uBACTC,KAAQ,yEAGRF,MAAS,OACTC,MAAS,uBACTC,KAAQ,yEAGRF,MAAS,OACTC,MAAS,yBACTC,KAAQ,2EAGRF,MAAS,OACTC,MAAS,sBACTC,KAAQ,wEAGRF,MAAS,OACTC,MAAS,mBACTC,KAAQ,qEAGRF,MAAS,OACTC,MAAS,uBACTC,KAAQ,yEAGRF,MAAS,OACTC,MAAS,uBACTC,KAAQ,yEAGRF,MAAS,OACTC,MAAS,oBACTC,KAAQ,sEAGRF,MAAS,OACTC,MAAS,yBACTC,KAAQ,4EAId2B,mBACE9B,aAEF+B,kBACE/B,WAEIC,MAAS,OACTC,MAAS,GACTC,KAAQ,kTAId6B,mBACEhC,WAEIC,MAAS,OACTC,MAAS,GACTC,KAAQ,iLAGRF,MAAS,OACTC,MAAS,kCACTC,KAAQ,+zCAGRF,MAAS,OACTC,MAAS,0CACTC,KAAQ,ogBAGRF,MAAS,OACTC,MAAS,2CACTC,KAAQ,wfAGRF,MAAS,OACTC,MAAS,2CACTC,KAAQ,6cAGRF,MAAS,OACTC,MAAS,iCACTC,KAAQ,wcAGRF,MAAS,OACTC,MAAS,0CACTC,KAAQ,woDAGRF,MAAS,OACTC,MAAS,mBACTC,KAAQ,mUAGRF,MAAS,OACTC,MAAS,+BACTC,KAAQ,opBAGRF,MAAS,OACTC,MAAS,qBACTC,KAAQ,2+BAGRF,MAAS,OACTC,MAAS,uEACTC,KAAQ,qhGAGRF,MAAS,OACTC,MAAS,cACTC,KAAQ,mNAGRF,MAAS,OACTC,MAAS,YACTC,KAAQ,kCAId8B,mBACEjC,WAEIC,MAAS,OACTC,MAAS,GACTC,KAAQ,2QAId+B,kBACElC,WAEIC,MAAS,OACTC,MAAS,GACTC,KAAQ,2BAGRF,MAAS,OACTC,MAAS,oCACTC,KAAQ;GAGRF,MAAS,OACTC,MAAS,sBACTC,KAAQ,iSAGRF,MAAS,MACTC,MAAS,sCACTC,KAAQ,oOAGRF,MAAS,OACTC,MAAS,qDACTC,KAAQ,u1BAGRF,MAAS,OACTC,MAAS,+DACTC,KAAQ,8YAGRF,MAAS,OACTC,MAAS,4CACTC,KAAQ,w6CAGRF,MAAS,OACTC,MAAS,2DACTC,KAAQ,qSAIdgC,SACEnC,WAEIC,MAAS,OACTC,MAAS,aACTC,KAAQ,qGAGRF,MAAS,QACTC,MAAS,iBACTC,KAAQ,sBAIdiC,gBACEpC,WAEIC,MAAS,OACTC,MAAS,GACTC,KAAQ,6ZAGRF,MAAS,OACTC,MAAS,8DACTC,KAAQ,0hCAGRF,MAAS,OACTC,MAAS,4CACTC,KAAQ,2gDAIdkC,qBACErC,WAEIC,MAAS,OACTC,MAAS,GACTC,KAAQ,mCAGRF,MAAS,OACTC,MAAS,oCACTC,KAAQ,ywBAGRF,MAAS,OACTC,MAAS,iCACTC,KAAQ,ghBAIdmC,uBACEtC,WAEIC,MAAS,OACTC,MAAS,GACTC,KAAQ,wMAGRF,MAAS,OACTC,MAAS,aACTC,KAAQ,ggBAGRF,MAAS,OACTC,MAAS,oEACTC,KAAQ,msBAGRF,MAAS,OACTC,MAAS,gEACTC,KAAQ,63BAGRF,MAAS,OACTC,MAAS,oDACTC,KAAQ,+uBAIdoC,WACEvC,WAEIC,MAAS,OACTC,MAAS,GACTC,KAAQ,wKAGRF,MAAS,OACTC,MAAS,uBACTC,KAAQ,4iBAGRF,MAAS,OACTC,MAAS,yCACTC,KAAQ,sqBAGRF,MAAS,OACTC,MAAS,8DACTC,KAAQ,skBAGRF,MAAS,OACTC,MAAS,kCACTC,KAAQ,k7BAGRF,MAAS,OACTC,MAAS,qCACTC,KAAQ,4lBAGRF,MAAS,OACTC,MAAS,oBACTC,KAAQ,krBAGRF,MAAS,OACTC,MAAS,2BACTC,KAAQ,q9BAGRF,MAAS,QACTC,MAAS,YACTC,KAAQ,iBAIdX,OACEQ,WAEIC,MAAS,OACTC,MAAS,0CACTC,KAAQ,i1BAGRF,MAAS,OACTC,MAAS,6BACTC,KAAQ,8QAGRF,MAAS,OACTC,MAAS,gFACTC,KAAQ,8oBAGRF,MAAS,OACTC,MAAS,kEACTC,KAAQ,ksBAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,6iBAGRF,MAAS,OACTC,MAAS,mDACTC,KAAQ,kqCAGRF,MAAS,OACTC,MAAS,wDACTC,KAAQ,okBAGRF,MAAS,SACTE,KAAQ,suBAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,wyBAGRF,MAAS,OACTC,MAAS,mEACTC,KAAQ,ulCAGRF,MAAS,OACTC,MAAS,oCACTC,KAAQ,ykBAGRF,MAAS,QACTC,MAAS,YACTC,KAAQ,iBAIdqC,OACExC,WAEIC,MAAS,OACTC,MAAS,GACTC,KAAQ,ulGAGRF,MAAS,OACTC,MAAS,mEACTC,KAAQ,ggBAGRF,MAAS,OACTC,MAAS,sBACTC,KAAQ,8+BAGRF,MAAS,OACTC,MAAS,4CACTC,KAAQ;GAGRF,MAAS,SACTC,MAAS,uCACTC,KAAQ,whCAGRF,MAAS,OACTC,MAAS,oCACTC,KAAQ,uZAGRF,MAAS,SACTC,MAAS,sCACTC,KAAQ,+rCAGRF,MAAS,QACTC,MAAS,YACTC,KAAQ,iBAIdsC,YACEzC,WAEIC,MAAS,OACTC,MAAS,GACTC,KAAQ,4EAGRF,MAAS,QACTC,MAAS,GACTC,KAAQ,MAIduC,UACE1C,WAEIC,MAAS,OACTC,MAAS,GACTC,KAAQ,mEAGRF,MAAS,QACTC,MAAS,GACTC,KAAQ,MAIdwC,uBACE3C,WAEIC,MAAS,OACTC,MAAS,GACTC,KAAQ,kDAGRF,MAAS,OACTC,MAAS,kCACTC,KAAQ,mLAGRF,MAAS,OACTC,MAAS,uBACTC,KAAQ,ifAGRF,MAAS,OACTC,MAAS,gDACTC,KAAQ,qMAGRF,MAAS,SACTC,MAAS,qDACTC,KAAQ,upBAGRF,MAAS,OACTC,MAAS,sEACTC,KAAQ,yeAGRF,MAAS,SACTC,MAAS,0BACTC,KAAQ,+sCAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,2NAGRF,MAAS,SACTC,MAAS,8BACTC,KAAQ,slBAIdyC,oBACE5C,aAEF6C,oBACE7C,aAEF8C,sBACE9C,WAEIC,MAAS,OACTC,MAAS,GACTC,KAAQ,mkCAId4C,sBACE/C,WAEIC,MAAS,OACTC,MAAS,GACTC,KAAQ,2yBAGRF,MAAS,OACTC,MAAS,sCACTC,KAAQ,g4BAGRF,MAAS,OACTC,MAAS,qCACTC,KAAQ,0HAGRF,MAAS,OACTC,MAAS,2CACTC,KAAQ,qFAGRF,MAAS,OACTC,MAAS,0CACTC,KAAQ,yHAGRF,MAAS,OACTC,MAAS,mDACTC,KAAQ,6LAId6C,qBACEhD,WAEIC,MAAS,OACTC,MAAS,GACTC,KAAQ,g+BAId8C,SACEjD,WAEIC,MAAS,OACTC,MAAS,GACTC,KAAQ,u0BAGRF,MAAS,OACTC,MAAS,8BACTC,KAAQ,8kBAGRF,MAAS,SACTC,MAAS,gCACTC,KAAQ,+8CAGRF,MAAS,OACTC,MAAS,mCACTC,KAAQ,qQAGRF,MAAS,SACTC,MAAS,4BACTC,KAAQ,stCAGRF,MAAS,OACTC,MAAS,gCACTC,KAAQ,m7BAGRF,MAAS,SACTC,MAAS,qBACTC,KAAQ,q1BAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,2BAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,+QAGRF,MAAS,OACTC,MAAS,6BACTC,KAAQ,otBAGRF,MAAS,SACTC,MAAS,yBACTC,KAAQ,q6BAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,+UAGRF,MAAS,SACTC,MAAS,yCACTC,KAAQ,s7BAGRF,MAAS,OACTC,MAAS,iCACTC,KAAQ,uOAGRF,MAAS,SACTC,MAAS,qCACTC,KAAQ,8kBAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,+OAGRF,MAAS,SACTC,MAAS,iCACTC,KAAQ,2iBAGRF,MAAS,OACTC,MAAS,4CACTC,KAAQ,i3BAGRF,MAAS,SACTC,MAAS,qCACTC,KAAQ,25BAGRF,MAAS,OACTC,MAAS,8CACTC,KAAQ,uiDAId+C,UACElD,WAEIC,MAAS,QACTC,MAAS,GACTC,KAAQ,MAIdgD,MACEnD,WAEIC,MAAS,OACTC,MAAS,sBACTC,KAAQ,opBAGRF,MAAS,OACTC,MAAS,sCACTC,KAAQ,mcAGRF,MAAS,OACTC,MAAS,qBACTC,KAAQ,0dAIdiD,UACEpD,WAEIC,MAAS,OACTC,MAAS,GACTC,KAAQ,41BAGRF,MAAS,QACTC,MAAS,WACTC,KAAQ,gBAIdkD,kBACErD,WAEIC,MAAS,OACTC,MAAS,iCACTC,KAAQ;GAGRF,MAAS,OACTC,MAAS,gEACTC,KAAQ,knBAGRF,MAAS,OACTC,MAAS,iEACTC,KAAQ,6bAGRF,MAAS,QACTC,MAAS,YACTC,KAAQ,iBAIdmD,YACEtD,WAEIC,MAAS,OACTC,MAAS,4EACTC,KAAQ,u8CAGRF,MAAS,SACTE,KAAQ,y3DAGRF,MAAS,QACTC,MAAS,YACTC,KAAQ,iBAIdoD,UACEvD,WAEIC,MAAS,OACTC,MAAS,GACTC,KAAQ,8zBAGRF,MAAS,OACTC,MAAS,+CACTC,KAAQ,2wBAGRF,MAAS,OACTC,MAAS,iCACTC,KAAQ,0UAGRF,MAAS,OACTC,MAAS,yDACTC,KAAQ,sDAGRF,MAAS,OACTC,MAAS,gDACTC,KAAQ,sMAGRF,MAAS,WACTC,MAAS,sDACTC,KAAQ,4KAGRF,MAAS,SACTC,MAAS,oDACTC,KAAQ,s3BAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,oEAGRF,MAAS,OACTC,MAAS,gEACTC,KAAQ,wQAGRF,MAAS,OACTC,MAAS,sBACTC,KAAQ,s7BAGRF,MAAS,OACTC,MAAS,mBACTC,KAAQ,6WAGRF,MAAS,OACTC,MAAS,WACTC,KAAQ,iUAGRF,MAAS,OACTC,MAAS,0BACTC,KAAQ,yNAGRF,MAAS,SACTC,MAAS,wDACTC,KAAQ,m9DAGRF,MAAS,OACTC,MAAS,oDACTC,KAAQ,i+BAGRF,MAAS,OACTC,MAAS,4DACTC,KAAQ,wXAGRF,MAAS,OACTC,MAAS,mFACTC,KAAQ,mkCAGRF,MAAS,SACTC,MAAS,qEACTC,KAAQ,s6EAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,+GAGRF,MAAS,QACTC,MAAS,sEACTC,KAAQ,0EAGRF,MAAS,QACTC,MAAS,YACTC,KAAQ,iBAIdqD,YACExD,WAEIC,MAAS,OACTC,MAAS,GACTC,KAAQ,gbAGRF,MAAS,OACTC,MAAS,kCACTC,KAAQ,sCAGRF,MAAS,MACTC,MAAS,uBACTC,KAAQ,yQAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,yDAGRF,MAAS,SACTC,MAAS,qDACTC,KAAQ,+lCAGRF,MAAS,OACTC,MAAS,4DACTC,KAAQ,uUAGRF,MAAS,MACTC,MAAS,kDACTC,KAAQ,0MAGRF,MAAS,QACTC,MAAS,YACTC,KAAQ,iBAIdsD,SACEzD,WAEIC,MAAS,OACTC,MAAS,GACTC,KAAQ,iEAGRF,MAAS,OACTC,MAAS,oBACTC,KAAQ,k+BAGRF,MAAS,OACTC,MAAS,qBACTC,KAAQ,iXAGRF,MAAS,OACTC,MAAS,wBACTC,KAAQ,8MAGRF,MAAS,SACTC,MAAS,sCACTC,KAAQ,4cAGRF,MAAS,SACTC,MAAS,0CACTC,KAAQ,gnBAGRF,MAAS,SACTC,MAAS,wCACTC,KAAQ,ijBAGRF,MAAS,OACTC,MAAS,4CACTC,KAAQ,gGAGRF,MAAS,QACTC,MAAS,YACTC,KAAQ,iBAIduD,eACE1D,WAEIC,MAAS,OACTC,MAAS,GACTC,KAAQ,yHAGRF,MAAS,OACTC,MAAS,sCACTC,KAAQ,4/BAGRF,MAAS,OACTC,MAAS,yBACTC,KAAQ,2QAGRF,MAAS,OACTC,MAAS,qCACTC,KAAQ,oGAGRF,MAAS,MACTC,MAAS,0BACTC,KAAQ,ySAGRF,MAAS,OACTC,MAAS,+DACTC,KAAQ,mVAGRF,MAAS,MACTC,MAAS,4DACTC,KAAQ,mYAGRF,MAAS,OACTC,MAAS,+BACTC,KAAQ,kNAGRF,MAAS,OACTC,MAAS,sBACTC,KAAQ,mWAGRF,MAAS,OACTC,MAAS,+BACTC,KAAQ,qrBAGRF,MAAS,MACTC,MAAS,uCACTC,KAAQ,0bAGRF,MAAS,OACTC,MAAS,mCACTC,KAAQ,mbAGRF,MAAS,OACTC,MAAS,kCACTC,KAAQ,geAGRF,MAAS,SACTC,MAAS,oCACTC,KAAQ;GAGRF,MAAS,OACTC,MAAS,8DACTC,KAAQ,ouBAGRF,MAAS,SACTC,MAAS,oCACTC,KAAQ,kvEAGRF,MAAS,OACTC,MAAS,uBACTC,KAAQ,0ZAGRF,MAAS,OACTC,MAAS,wBACTC,KAAQ,iqBAGRF,MAAS,SACTC,MAAS,2BACTC,KAAQ,4RAGRF,MAAS,MACTC,MAAS,eACTC,KAAQ,wIAGRF,MAAS,OACTC,MAAS,6BACTC,KAAQ,ybAGRF,MAAS,SACTC,MAAS,kCACTC,KAAQ,uSAGRF,MAAS,MACTC,MAAS,oBACTC,KAAQ,kJAGRF,MAAS,OACTC,MAAS,uBACTC,KAAQ,mXAGRF,MAAS,SACTC,MAAS,4BACTC,KAAQ,qRAGRF,MAAS,MACTC,MAAS,cACTC,KAAQ,qIAGRF,MAAS,OACTC,MAAS,6CACTC,KAAQ,yhBAGRF,MAAS,QACTC,MAAS,YACTC,KAAQ,iBAIdwD,YACE3D,WAEIC,MAAS,OACTC,MAAS,uBACTC,KAAQ,6pBAGRF,MAAS,OACTC,MAAS,sBACTC,KAAQ,kNAGRF,MAAS,OACTC,MAAS,0BACTC,KAAQ,8VAGRF,MAAS,SACTC,MAAS,qBACTC,KAAQ,wiBAGRF,MAAS,OACTC,MAAS,sBACTC,KAAQ,8nBAGRF,MAAS,SACTC,MAAS,4DACTC,KAAQ,8yBAGRF,MAAS,OACTC,MAAS,mCACTC,KAAQ,+mBAGRF,MAAS,SACTC,MAAS,wCACTC,KAAQ,+rBAGRF,MAAS,OACTC,MAAS,uDACTC,KAAQ,uqBAGRF,MAAS,SACTE,KAAQ,syBAGRF,MAAS,QACTC,MAAS,4CACTC,KAAQ,gDAGRF,MAAS,OACTC,MAAS,iCACTC,KAAQ,+DAIdyD,aACE5D,WAEIC,MAAS,OACTC,MAAS,GACTC,KAAQ,q8CAId0D,gBACE7D,WAEIC,MAAS,OACTC,MAAS,GACTC,KAAQ,kGAGRF,MAAS,OACTC,MAAS,+BACTC,KAAQ,6yCAGRF,MAAS,OACTC,MAAS,0CACTC,KAAQ,2fAGRF,MAAS,OACTC,MAAS,2CACTC,KAAQ,ifAGRF,MAAS,OACTC,MAAS,2CACTC,KAAQ,scAGRF,MAAS,OACTC,MAAS,8BACTC,KAAQ,6bAGRF,MAAS,OACTC,MAAS,YACTC,KAAQ,qCAId2D,eACE9D,WAEIC,MAAS,OACTC,MAAS,GACTC,KAAQ,mWAGRF,MAAS,OACTC,MAAS,qCACTC,KAAQ,66CAGRF,MAAS,OACTC,MAAS,6BACTC,KAAQ,6mBAGRF,MAAS,OACTC,MAAS,sCACTC,KAAQ,21CAGRF,MAAS,MACTC,MAAS,0BACTC,KAAQ,m0BAGRF,MAAS,OACTC,MAAS,kEACTC,KAAQ,ouBAGRF,MAAS,OACTC,MAAS,yBACTC,KAAQ,64CAGRF,MAAS,OACTC,MAAS,4DACTC,KAAQ,ggBAGRF,MAAS,OACTC,MAAS,4BACTC,KAAQ,qSAGRF,MAAS,MACTC,MAAS,uBACTC,KAAQ,kUAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,+pBAGRF,MAAS,OACTC,MAAS,gCACTC,KAAQ,8bAGRF,MAAS,OACTC,MAAS,gGACTC,KAAQ,4iDAGRF,MAAS,SACTC,MAAS,iFACTC,KAAQ;GAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,yEAGRF,MAAS,OACTC,MAAS,2DACTC,KAAQ,kjBAGRF,MAAS,OACTC,MAAS,sEACTC,KAAQ,8rBAGRF,MAAS,OACTC,MAAS,+EACTC,KAAQ,ulBAGRF,MAAS,MACTC,MAAS,+BACTC,KAAQ,wSAGRF,MAAS,OACTC,MAAS,8EACTC,KAAQ,+8BAGRF,MAAS,MACTC,MAAS,4BACTC,KAAQ,iTAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,gHAGRF,MAAS,SACTC,MAAS,8EACTC,KAAQ,iyHAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,0EAGRF,MAAS,OACTC,MAAS,yCACTC,KAAQ,67CAGRF,MAAS,OACTC,MAAS,0DACTC,KAAQ,4qDAGRF,MAAS,SACTC,MAAS,0EACTC,KAAQ,uyNAGRF,MAAS,OACTC,MAAS,6DACTC,KAAQ,qlBAGRF,MAAS,SACTC,MAAS,uDACTC,KAAQ,o4IAGRF,MAAS,QACTC,MAAS,YACTC,KAAQ,iBAId4D,WACE/D,WAEIC,MAAS,OACTC,MAAS,GACTC,KAAQ,iBAId6D,SACEhE,WAEIC,MAAS,OACTC,MAAS,GACTC,KAAQ,gIAId8D,SACEjE,WAEIC,MAAS,OACTC,MAAS,GACTC,KAAQ,qJAGRF,MAAS,OACTC,MAAS,wBACTC,KAAQ,whBAGRF,MAAS,OACTC,MAAS,0CACTC,KAAQ,ipBAGRF,MAAS,OACTC,MAAS,oDACTC,KAAQ,gzBAGRF,MAAS,MACTC,MAAS,oBACTC,KAAQ,6rBAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,gqBAGRF,MAAS,OACTC,MAAS,8HACTC,KAAQ,gZAGRF,MAAS,OACTC,MAAS,6BACTC,KAAQ,ojBAGRF,MAAS,SACTC,MAAS,mBACTC,KAAQ,4nBAGRF,MAAS,OACTC,MAAS,8BACTC,KAAQ,0uBAGRF,MAAS,OACTC,MAAS,uBACTC,KAAQ,2LAGRF,MAAS,OACTC,MAAS,iCACTC,KAAQ,oUAGRF,MAAS,OACTC,MAAS,8CACTC,KAAQ,4jBAGRF,MAAS,OACTC,MAAS,iDACTC,KAAQ;GAGRF,MAAS,SACTC,MAAS,UACTC,KAAQ,6nCAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,gIAGRF,MAAS,SACTC,MAAS,qDACTC,KAAQ,k1FAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,+vBAGRF,MAAS,SACTC,MAAS,2DACTgE,IAAO,0CACP/D,KAAQ,u1KAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,8GAGRF,MAAS,OACTC,MAAS,0CACTC,KAAQ,ioBAGRF,MAAS,OACTC,MAAS,oCACTC,KAAQ,6MAGRF,MAAS,OACTC,MAAS,2CACTC,KAAQ,sQAGRF,MAAS,OACTC,MAAS,kDACTC,KAAQ,0tCAGRF,MAAS,OACTC,MAAS,iDACTC,KAAQ,iMAGRF,MAAS,OACTC,MAAS,qDACTC,KAAQ,6BAGRF,MAAS,OACTC,MAAS,0DACTC,KAAQ,woBAGRF,MAAS,OACTC,MAAS,kCACTC,KAAQ,srDAGRF,MAAS,SACTC,MAAS,oBACTC,KAAQ,uoBAGRF,MAAS,OACTC,MAAS,oDACTC,KAAQ,wtBAGRF,MAAS,OACTC,MAAS,6DACTC,KAAQ,24BAGRF,MAAS,SACTC,MAAS,4CACTC,KAAQ,uhDAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,6QAGRF,MAAS,SACTC,MAAS,qEACTC,KAAQ,2wCAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,mIAGRF,MAAS,SACTC,MAAS,uDACTC,KAAQ,8tBAGRF,MAAS,OACTC,MAAS,iFACTC,KAAQ,+RAGRF,MAAS,SACTE,KAAQ,+5BAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,qpCAGRF,MAAS,OACTC,MAAS,0EACTC,KAAQ,6tBAGRF,MAAS,SACTE,KAAQ,0zCAGRF,MAAS,OACTC,MAAS,GACTC,KAAQ,uBAGRF,MAAS,OACTC,MAAS,oEACTC,KAAQ,szCAGRF,MAAS,OACTC,MAAS,oDACTC,KAAQ,oiBAGRF,MAAS,OACTC,MAAS,uDACTC,KAAQ,owBAGRF,MAAS,OACTC,MAAS,6EACTC,KAAQ,0+BAGRF,MAAS,OACTC,MAAS,4CACTC,KAAQ;GAGRF,MAAS,OACTC,MAAS,6CACTC,KAAQ,4TAGRF,MAAS,OACTC,MAAS,4DACTC,KAAQ,+RAGRF,MAAS,OACTC,MAAS,yDACTC,KAAQ,mUAGRF,MAAS,MACTC,MAAS,GACTC,KAAQ,mIAGRF,MAAS,OACTC,MAAS,qBACTC,KAAQ,0KAGRF,MAAS,OACTC,MAAS,iCACTC,KAAQ,iPAGRF,MAAS,QACTC,MAAS,YACTC,KAAQ","file":"find-jsrapi.min.js","sourcesContent":["var content = $.views.documentation.content;\r\n\r\ncontent.find.jsrapi = content.useStorage && $.parseJSON(localStorage.getItem(\"JsViewsDocTopics/find/jsrapi\")) ||\r\n{\r\n \"jsrapi\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"See JsRender Quickstart for an introductory overview.\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"Topics:\",\r\n \"text\": \"Topics:\\n\"\r\n }\r\n ]\r\n },\r\n \"jsrtags\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"Tag syntax\",\r\n \"text\": \"Tag syntax\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Tags without content\",\r\n \"text\": \"Tags without content\\n\\n{{: ...}} (Evaluate)\\n{{> ...}} (HTML encode)\\n{{!-- ... --}} (Comment)\\n{{* ...}} and {{*: ...}} (Allow code)\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Block tags\",\r\n \"text\": \"Block tags\\n\\n{{include ...}} (Template composition – partials)\\n{{for ...}} (Template composition, with iteration over arrays)\\n{{props ...}} (Iteration over properties of an object)\\n{{if ...}} (Conditional inclusion)\\n{{mytag ...}} (Custom tags)\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Alternative content blocks\",\r\n \"text\": \"Alternative content blocks\\n\\n{{else ...}} (Content block separator)\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Additional tags in JsViews\",\r\n \"text\": \"Additional tags in JsViews\\nWhen using data-linked templates, with JsViews, the following additional template tags are available:\\n\\n{^{radiogroup ...}} (Radio button group)\\n{^{on ...}} (Button, or event binding)\\n\\nSee: Template tags in JsViews\\n\"\r\n }\r\n ]\r\n },\r\n \"assigntag\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"tag\",\r\n \"title\": \"{{: ...}}\",\r\n \"text\": \"{{: ...}}\\nGet the value of the data path or expression, and insert it into the rendered output as a string\\nEvaluate the data-path or expression\\n\\n{{:address.street}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Here are some examples:\",\r\n \"text\": \"Here are some examples:\\n(Note the use of different kinds of data-path and expression in the different examples)\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"{{:dataproperty}}\",\r\n \"text\": \"{{:dataproperty}}\\nData:\\n\\n{name: \\\"Pete\\\"}\\n\\nTemplate:\\n\\n{{:name}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"{{:data.paths}}\",\r\n \"text\": \"{{:data.paths}}\\n{\\n name: \\\"Pete\\\",\\n address: {\\n city: \\\"Seattle\\\"\\n }\\n}\\n\\n~root is the top-level data, and #data is the current data item\\n{{:name}} ... {{:address.city}}\\n\\n... {{:~root.address.city}}\\n\\n... {{:#data.address.city}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"{{:#index ...}}\",\r\n \"text\": \"{{:#index ...}}\\n[\\n {name: \\\"Pete\\\", ...},\\n {name: \\\"Heidi\\\", ...}\\n]\\n\\n#xxx is the xxx property of the current view – so #index is the view.index\\n{{:#index+1}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Note: When rendering data which is not fully trusted, such as {{:untrustedValue}} it would be preferable from a security point of view to use the {{>untrustedValue}} – since the {{> ...}} tag will HTML encode the data, and thus prevent HTML injection attacks.\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"See also:\",\r\n \"text\": \"See also:\\n\"\r\n }\r\n ]\r\n },\r\n \"htmltag\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"tag\",\r\n \"title\": \"{{>...}}\",\r\n \"text\": \"{{>...}}\\nGet the HTML-encoded value of the data path or expression, and insert it into the rendered output\\nEvaluate the data-path or expression, and HTML encode the result\\n\\n{{>address.street}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Here is an example:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"\",\r\n \"text\": \"Data:\\n\\n{description: \\\"A very nice apartment\\\"}\\n\\nTemplate:\\n\\n{{:description}}\\n...\\n{{>description}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using {{> ...}} for preventing HTML injection attacks\",\r\n \"text\": \"Using {{> ...}} for preventing HTML injection attacks\\nThe {{> ...}} tag should be used instead of the {{: ...}} whenever data being rendered is not fully trusted – in order to protect against HTML injection attacks.\\nUsing {{>untrustedValue}} ensures appropriate HTML encoding.\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"See also:\",\r\n \"text\": \"See also:\\n\"\r\n }\r\n ]\r\n },\r\n \"includetag\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"tag\",\r\n \"title\": \"{{include tmpl=... /}}\",\r\n \"text\": \"{{include tmpl=... /}}\\nTemplate composition: – Include the referenced template: tmpl, rendered using the current data context.\\nInclude the specified template\\n\\n{{include tmpl=\\\"insertedPersonTemplate\\\" /}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Here is an example:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"\",\r\n \"text\": \"\\n
        \\n {{:name}} lives in {{include tmpl=\\\"#addressTemplate\\\"/}}\\n
        \\n\\n\\n\\n {{>address.city}}\\n\\n\\n\\nvar people = [\\n {\\n \\\"name\\\": \\\"Pete\\\",\\n \\\"address\\\": {\\n \\\"city\\\": \\\"Seattle\\\"\\n }\\n },\\n {\\n \\\"name\\\": \\\"Heidi\\\",\\n \\\"address\\\": {\\n \\\"city\\\": \\\"Sidney\\\"\\n }\\n }\\n];\\n\\nvar html = $(\\\"#peopleTemplate\\\").render(people);\\n\\n$(\\\"#peopleList\\\").html(html);\\n\\n{{:name}} lives in {{include tmpl=\\\"#addressTemplate\\\"/}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using {{include}} to move to a new data context\",\r\n \"text\": \"Using {{include}} to move to a new data context\\n{{include}} is similar to {{for}} in that it can take an argument for moving to a new data context – as in the following examples:\\nBlock tag with inline content:\\n{{include address}}\\n {{:street}}\\n{{/include}}\\n\\nSelf-closing tag, referencing block content as tmpl=... :\\n{{include address tmpl=\\\"#addressTemplate\\\"/}}\\n\\n\\n\\nThe above two examples are equivalent to:\\n{{:address.street}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"tag\",\r\n \"title\": \"{{include pathOrExpr}}\",\r\n \"text\": \"{{include pathOrExpr}}\\nTemplate composition: – Render the block content of the {{include}} (or the referenced external template), using the object or array specified by the path or expression as data context.(Similar to {{for pathOrExpr}} but with no iteration over arrays...)\\nRender the block content of the tag, with the given object or array as data context\\n\\n{{include billing.address}}\\n {{:city}}\\n{{/include}}\\n\\nRender the specified template, with the given object or array as data context\\n\\n{{include billing.address tmpl=\\\"addressTmpl\\\" /}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Unlike {{for objectOrArray}}, {{include objectOrArray}} does not iterate over arrays.\\nConsider this example:\\nNumber of friends: {{:friends.length}} {{!-- Get 'length' of 'friends' array --}}\\nFriends:\\n{{for friends}} {{!-- Iterate over 'friends' array --}}\\n {{name:}} {{!-- Current data context (#data) is a 'friend'. Get 'name' --}} \\n{{/for}}\\n\\nThe example could actually be rewritten, equivalently, as follows:\\n{{include friends}} {{!-- Move to 'friends' array as data context, no iteration --}}\\n Number of friends: {{:length}} {{!-- Current data context (#data) is 'friends'. Get 'length' --}}\\n Friends:\\n {{for}} {{!-- or {{for #data}} ... --}} {{!-- Iterate over current data context (friends array) --}}\\n {{name:}} \\n {{/for}}\\n{{/include}}\\n\\nHere it is as a running sample:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"{{include array}} does not iterate\",\r\n \"text\": \"{{include array}} does not iterate\\n{{include friends}}:\\n Number of friends {{:length}}\\n ...\\n{{/include}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"See also:\",\r\n \"text\": \"See also:\\n\"\r\n }\r\n ]\r\n },\r\n \"fortag\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"tag\",\r\n \"title\": \"{{for ...}}\",\r\n \"text\": \"{{for ...}}\\nTemplate composition: – Render the block content of the {{for}} (or the referenced external template), using the object or array specified by the path or expression as data context. If it is an array, iterate over the array, rendering once for each item.\\nRender the block content of the tag for the given object, or iterate over the given array\\n\\n{{for billing.address}}\\n {{:city}}\\n{{/for}}\\n\\nRender the specified template for the given object, or iterate over the given array\\n\\n{{for billing.address tmpl=\\\"addressTmpl\\\" /}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Here are some examples:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"{{for object}}\",\r\n \"text\": \"{{for object}}\\n\\n{{:name}} lives in \\n{{for address}}\\n {{>city}}\\n{{/for}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"{{for object tmpl=... /}}\",\r\n \"text\": \"{{for object tmpl=... /}}\\n\\n
        \\n {{:name}} lives in {{for address tmpl=\\\"#addressTemplate\\\" /}}\\n
        \\n\\n\\n\\n {{>city}}\\n\\n\\n\\nvar people = [\\n {\\n \\\"name\\\": \\\"Pete\\\",\\n \\\"address\\\": {\\n \\\"city\\\": \\\"Seattle\\\"\\n }\\n },\\n {\\n \\\"name\\\": \\\"Heidi\\\",\\n \\\"address\\\": {\\n \\\"city\\\": \\\"Sidney\\\"\\n }\\n }\\n];\\n\\nvar html = $(\\\"#peopleTemplate\\\").render(people);\\n\\n$(\\\"#result\\\").html(html);\\n{{:name}} lives in {{for address tmpl=\\\"#addressTemplate\\\" /}}\\n\\n\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"{{for array}}\",\r\n \"text\": \"{{for array}}\\n\\n{{:title}}\\n\\n {{for members}}\\n {{:name}} ...\\n {{/for}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using the {{else}} tag with {{for}}\",\r\n \"text\": \"Using the {{else}} tag with {{for}}\\nUsing the {{else}} tag between {{for}} and {{/for}}, allows alternate rendering based on the object or array returned from the path or expression {{for pathOrExpr}}\\n\"\r\n },\r\n {\r\n \"_type\": \"tag\",\r\n \"title\": \"{{for ...}}...{{else}}...{{/for}}\",\r\n \"text\": \"{{for ...}}...{{else}}...{{/for}}\\nConditional blocks: – Render the block content of the {{for}} tag (or referenced template) if the object is defined and is not an empty array, otherwise render the {{else}} block (or template)\\nRender first block if array is not empty, otherwise render second block\\n\\n{{for members}}\\n Name: {{:name}}\\n{{else}}\\n No members...\\n{{/for}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"{{for array}}...{{else}}...{{/for}}\",\r\n \"text\": \"{{for array}}...{{else}}...{{/for}}\\n\\n{{for members}}\\n {{:name}}\\n{{else}}\\n No members!\\n{{/for}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"{{for object}}...{{else}}...{{/for}}\",\r\n \"text\": \"{{for object}}...{{else}}...{{/for}}\\n\\n{{for manager}}\\n Manager: {{:name}}\\n{{else}}\\n There is no team manager!\\n{{/for}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Note: A {{for}} tag (like an {{if}} tag) can have multiple {{else}} blocks. See for example this sample.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using {{for array}} with sorting and filtering, or specifying a range of items\",\r\n \"text\": \"Using {{for array}} with sorting and filtering, or specifying a range of items\\nWhen using the {{for}} tag to render arrays, built-in features allow sorting, filtering and ‘slicing’ the rendered list:\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"The reverse property: specifying reverse ordering (or reverse sorting) on {{for array}}\",\r\n \"text\": \"The reverse property: specifying reverse ordering (or reverse sorting) on {{for array}}\\nTo iterate over an array in reverse order, set the reverse property to true:\\n{{for array reverse=true }}...{{/for}}\\n\\nSetting reverse=true can be combined with using the sort, filter, start, end or step properties, to reverse the order of iteration (for example to sort in descending order rather than ascending order).\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"The sort property: specifying sorting on {{for array}}\",\r\n \"text\": \"The sort property: specifying sorting on {{for array}}\\nTo specify sorting, set the sort property:\\n{{for array sort=\\\"firstName\\\" }}...{{/for}}\\n\\n\\nIf the array is an array of objects, the sort=... property of {{for}} is usually set to an object property to be sorted by, such as firstName, or to a data path, such as sort=\\\"address.street\\\"\\nTo sort an array of numbers, strings or Dates, set the sort property to the empty string: sort=\\\"\\\"\\nFor advanced scenarios you can provide your own sort function: sort=~mySortFunction\\n\\nSetting sort=... can be combined with using the reverse, filter, start, end or step properties.\\nThe following three samples illustrate the above scenarios, using the reverse and sort properties:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Sorting an array of strings/Numbers/Dates\",\r\n \"text\": \"Sorting an array of strings/Numbers/Dates\\n\\n Reverse sort strings:\\n {{for colors sort=\\\"\\\" reverse=true}}\\n {{:}}\\n {{/for}}
        \\n\\n Sort numbers:\\n {{for amounts sort=\\\"\\\"}}\\n {{:}},\\n {{/for}}
        \\n\\n Sort dates:\\n {{for dates sort=\\\"\\\"}}\\n {{formatDate:}} –\\n {{/for}}\\n\\n\\n\\n$.views.converters(\\\"formatDate\\\", function(date) {\\n // Converter to format Dates\\n return date.toLocaleDateString(\\\"en-US\\\");\\n});\\n\\nvar myTmpl = $.templates(\\\"#myTmpl\\\"),\\n\\n data = {\\n colors: [\\n \\\"red\\\",\\n \\\"white\\\",\\n \\\"blue\\\"\\n ],\\n amounts: [\\n 33,\\n -2.333,\\n 2.4,\\n -22,\\n 22\\n ],\\n dates: [\\n new Date(2000, 0, 1),\\n new Date(1998, 6, 30),\\n new Date(2000, 11, 31)\\n ]\\n },\\n\\n html = myTmpl.render(data);\\n\\n$(\\\"#page\\\").html(html);\\nTemplate:\\n{{for colors sort=\\\"\\\" reverse=true}}...{{/for}} {{!-- (Reverse) sort array of strings --}}\\n{{for amounts sort=\\\"\\\"}}...{{/for}} {{!-- Sort array of Numbers --}}\\n{{for dates sort=\\\"\\\"}}...{{/for}} {{!-- Sort array of Dates --}}\\n\\nData:\\ncolors: [\\\"red\\\", ...],\\namounts: [33.001, ...],\\ndates: [new Date(2000, 0, 1), ...]\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Sorting an array of objects\",\r\n \"text\": \"Sorting an array of objects\\nTemplate:\\n{{for people sort=\\\"firstName\\\"}}...{{/for}} {{!-- Sort by first name --}}\\n{{for people sort=\\\"lastName\\\" reverse=true}}...{{/for}} {{!-- Sort by last name, decreasing --}}\\n{{for people sort=\\\"address.street\\\"}} {{!-- Sort by address.street --}}\\n\\nData:\\npeople: [\\n {firstName: \\\"Jo\\\", ... address: {street: \\\"1st Street\\\" ...}},\\n ...\\n]\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Using a custom sort function: multi-level sort \",\r\n \"text\": \"Using a custom sort function: multi-level sort \\n\\n
          \\n {{for people sort=~multilevel}} {{!-- Sort using a custom helper function: ~multilevel --}}\\n
        • {{:name}}: ({{:details.role}}) – age {{:details.age}}
        • \\n {{/for}}\\n
        \\n\\n\\n\\n// Helper function for multi-level sort\\nfunction level(aField, bField) {\\n return aField > bField ? 1 : aField < bField ? -1 : 0;\\n}\\n\\n// Custom sort function\\nfunction multilevelSort(a, b) {\\n // Sort by role, then by age (descending) then by name\\n return level(a.details.role.toLowerCase(), b.details.role.toLowerCase()) // by role\\n || level(b.details.age, a.details.age) // by age\\n || level(a.name.toLowerCase(), b.name.toLowerCase()); // by name\\n}\\n\\nvar myTmpl = $.templates(\\\"#myTmpl\\\"),\\n\\n data = {people: [\\n {name: \\\"Bill\\\", details: {age: 22, role: \\\"Lead\\\"}},\\n {name: \\\"Anne\\\", details: {age: 32, role: \\\"Assistant\\\"}},\\n {name: \\\"Emma\\\", details: {age: 19.1, role: \\\"Team member\\\"}},\\n {name: \\\"Jeff\\\", details: {age: 33.5, role: \\\"Lead\\\"}},\\n {name: \\\"Xavier\\\", details: {age: 32, role: \\\"Team member\\\"}},\\n {name: \\\"Julia\\\", details: {age: 18, role: \\\"Assistant\\\"}},\\n {name: \\\"Bill\\\", details: {age: 32, role: \\\"Team member\\\"}}\\n ]},\\n\\n html = myTmpl.render(data, { \\n multilevel: multilevelSort\\n });\\n\\n$(\\\"#page\\\").html(html);\\n{{for people sort=~multilevel}}...{{/for}} {{!-- Sort using a custom helper function: ~multilevel --}}\\n\\nThe custom sort function takes arguments (a, b) for the two objects being compared. The this pointer is the current view object.\\n// Custom sort function\\nfunction multilevelSort(a, b) {\\n return ... // Return 1, -1 or 0 to specify relative position of `a` and `b` in the sort order\\n}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"The filter property: specifying filtering on {{for array}}\",\r\n \"text\": \"The filter property: specifying filtering on {{for array}}\\nTo filter the rendered items, use the filter property to specify a filter function:\\n{{for array filter=~myfilter}}...{{/for}}\\n\\nfunction myfilter(item, index, items) {\\n return ...; // Return true/false to include/exclude any item from the result\\n}\\n\\nThe filter function is called with the tagCtx object as this pointer, and with arguments:\\n\\nitem: The current item being processed in the array\\nindex: The index of the current item being processed in the array\\narray: The array being filtered\\n\\nSetting filter=... can be combined with using the sort, reverse, start, end or step properties (to filter the items after sorting or reversing, or before ‘slicing’).\\nThe following sample renders a subset of an array of people, filtered by age:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"text\": \"\\n Filter: age from 20 to 40\\n\\n
          \\n {{for people filter=~ageRange minAge=20 maxAge=40 sort=\\\"name\\\"}}\\n
        • {{:name}}: age {{:details.age}}
        • \\n {{/for}}\\n
        \\n\\n\\n\\nfunction ageRangeFilter(item, index, items) {\\n return item.details.age > this.props.minAge && item.details.age < this.props.maxAge;\\n}\\n\\nvar myTmpl = $.templates(\\\"#myTmpl\\\"),\\n\\n data = {people: [\\n {name: \\\"Bill\\\", details: {age: 25}},\\n {name: \\\"Anne\\\", details: {age: 32}},\\n {name: \\\"Emma\\\", details: {age: 19.1}},\\n {name: \\\"Jeff\\\", details: {age: 33.5}},\\n {name: \\\"Xavier\\\", details: {age: 52}},\\n {name: \\\"Julia\\\", details: {age: 18}},\\n {name: \\\"Jo\\\", details: {age: 30}}\\n ]},\\n\\n html = myTmpl.render(data, { \\n ageRange: ageRangeFilter\\n });\\n\\n$(\\\"#page\\\").html(html);\\nfunction ageRangeFilter(item, index, items) {\\n return item.details.age > this.props.minAge ...\\n}\\n\\n{{for people filter=~ageRange minAge=20 maxAge=40 sort=\\\"name\\\"}}...{{/for}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"The following sample renders an array of people in a two row layout – by filtering for the items with even (first row) and odd (second row) index.\\n(See also an alternative approach using step=..., in the section below).\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"text\": \"\\n \\n {{for people filter=~evenOdd odd=false sort=\\\"name\\\"}}\\n \\n {{/for}}\\n \\n {{for people filter=~evenOdd odd=true sort=\\\"name\\\"}}\\n \\n {{/for}}\\n
        {{:1+2*#index}} {{:name}}
        {{:2+2*#index}} {{:name}}
        \\n\\n\\n\\nvar myTmpl = $.templates(\\\"#myTmpl\\\"),\\n\\n data = {\\n people: [\\n {name: \\\"Jo\\\"},\\n {name: \\\"Adriana\\\"},\\n {name: \\\"Xavier\\\"},\\n {name: \\\"Juanita\\\"},\\n {name: \\\"Adeline\\\"},\\n {name: \\\"Pete\\\"},\\n {name: \\\"Jeff\\\"},\\n {name: \\\"Paul\\\"}\\n ]\\n },\\n\\n html = myTmpl.render(data, {\\n evenOdd: function(item, index, items) {\\n return this.props.odd === (index%2 === 1); // Include only items with even/odd index\\n }\\n });\\n\\n$(\\\"#page\\\").html(html);\\n\\n{{for people filter=~evenOdd odd=false sort=\\\"name\\\"}}...{{/for}}\\n...\\n{{for people filter=~evenOdd odd=true sort=\\\"name\\\"}}...{{/for}}\\n\\nevenOdd: function(item, index, items) {\\n return this.props.odd === (index%2 === 1); // Include only items with even/odd index\\n}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"The start, end and step properties: limiting range and/or selecting every n'th item of {{for array}}\",\r\n \"text\": \"The start, end and step properties: limiting range and/or selecting every n'th item of {{for array}}\\nTo limit the range of an array (‘slice’ the array) of rendered items, use the start and/or end properties to specify the starting and ending index. In addition, the step property lets you take every other n’th item in the array.\\nThe behavior of start and end corresponds to the array.slice(start, end) JavaScript method:\\n\\nstart: zero-based index at which to begin rendering\\n\\nA negative index indicates an offset from the end of the sequence\\nIf start is undefined, begins from index 0\\n\\nend: zero-based index before which to end rendering (render up to but not including end)\\n\\nA negative index indicates an offset from the end of the sequence\\nIf end is undefined, render through the end of the array\\n\\nstep: A positive integer ‘n’, in order to include every nth item, beginning with start. Defaults to 1\\n\\n{{for colors start=1 end=-1 step=2}}...{{/for}}\\n\\nSetting start=..., end=... and/or step=... can be combined with using the sort, reverse, filter to limit the item selection, after sorting, reversing or filtering.\\n{{for colors sort=\\\"name\\\" start=1 end=-1}}...{{/for}}\\n\\nThe following sample illustrates the use of start=... and end=... with or without sorting:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"text\": \"\\n Unsorted:\\n {{for colors}}{{:}} {{/for}}\\n
        \\n\\n Sliced:\\n {{for colors start=1 end=-1}}{{:}} {{/for}}\\n
        \\n\\n Alternate, odd:\\n {{for colors step=2}}{{:}} {{/for}}\\n
        \\n\\n Alternate, even:\\n {{for colors step=2 start=1}}{{:}} {{/for}}\\n
        \\n\\n Sorted:\\n {{for colors sort=\\\"\\\"}}{{:}} {{/for}}\\n
        \\n\\n Sorted then sliced:\\n {{for colors sort=\\\"\\\" start=1 end=-1}}{{:}} {{/for}}\\n\\n\\n\\nvar myTmpl = $.templates(\\\"#myTmpl\\\"),\\n\\n data = {\\n colors: [\\\"red\\\", \\\"orange\\\", \\\"yellow\\\", \\\"green\\\", \\\"blue\\\", \\\"indigo\\\", \\\"violet\\\"]\\n },\\n\\n html = myTmpl.render(data);\\n\\n$(\\\"#page\\\").html(html);\\n\\n{{for colors}}...{{/for}}\\n{{for colors start=1 end=-1}}...{{/for}}\\n{{for colors step=2}}...{{/for}}\\n{{for colors step=2 start=1}}...{{/for}}\\n{{for colors sort=\\\"\\\"}}...{{/for}}\\n{{for colors sort=\\\"\\\" start=1 end=-1}}...{{/for}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"The following sample uses step=... to render multi-row layouts of an array of people:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"text\": \"\\n Three row display: step=3\\n\\n \\n {{for people step=3 start=0 sort=\\\"name\\\" end=-2}}\\n \\n {{/for}}\\n \\n {{for people step=3 start=1 sort=\\\"name\\\" end=-2}}\\n \\n {{/for}}\\n \\n {{for people step=3 start=2 sort=\\\"name\\\" end=-2}}\\n \\n {{/for}}\\n
        {{:1+3*#index}} {{:name}}
        {{:2+3*#index}} {{:name}}
        {{:3+3*#index}} {{:name}}
        \\n\\n Two row display: step=2 reverse=true\\n\\n \\n {{for people step=2 start=0 sort=\\\"name\\\" reverse=true}}\\n \\n {{/for}}\\n \\n {{for people step=2 start=1 sort=\\\"name\\\" reverse=true}}\\n \\n {{/for}}\\n
        {{:1+2*#index}} {{:name}}
        {{:2+2*#index}} {{:name}}
        \\n\\n\\n\\nvar myTmpl = $.templates(\\\"#myTmpl\\\"),\\n\\n data = {\\n people: [\\n {name: \\\"Jo\\\"},\\n {name: \\\"Adriana\\\"},\\n {name: \\\"Xavier\\\"},\\n {name: \\\"Juanita\\\"},\\n {name: \\\"Adeline\\\"},\\n {name: \\\"Pete\\\"},\\n {name: \\\"Jeff\\\"},\\n {name: \\\"Paul\\\"}\\n ]\\n },\\n\\n html = myTmpl.render(data);\\n\\n$(\\\"#page\\\").html(html);\\n{{for people step=3 start=0 sort=\\\"name\\\" end=-2}}\\n{{for people step=3 start=1 sort=\\\"name\\\" end=-2}}\\n{{for people step=3 start=2 sort=\\\"name\\\" end=-2}}\\n\\n{{for people step=2 start=0 sort=\\\"name\\\" reverse=true}}\\n{{for people step=2 start=1 sort=\\\"name\\\" reverse=true}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Sorting, filtering, 'slicing' operations in any order\",\r\n \"text\": \"Sorting, filtering, 'slicing' operations in any order\\nSome of the above samples include applying a sort operation followed by a ‘slice’ operation. It is also possible reverse the order of operations, and to limit the range before sorting the result, as in the following two examples:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"text\": \"\\n Three row display (sliced then sorted)\\n\\n {{for people end=-2 noIteration=true}} {{!-- slice (remove last two) --}}\\n \\n {{for #data step=3 start=0 sort=\\\"name\\\"}} {{!-- sort --}}\\n \\n {{/for}}\\n \\n {{for #data step=3 start=1 sort=\\\"name\\\"}}\\n \\n {{/for}}\\n \\n {{for #data step=3 start=2 sort=\\\"name\\\"}}\\n \\n {{/for}}\\n
        {{:1+3*#index}} {{:name}}
        {{:2+3*#index}} {{:name}}
        {{:3+3*#index}} {{:name}}
        \\n {{/for}}\\n\\n\\n\\nvar myTmpl = $.templates(\\\"#myTmpl\\\"),\\n\\n data = {\\n people: [\\n {name: \\\"Jo\\\"},\\n {name: \\\"Adriana\\\"},\\n {name: \\\"Xavier\\\"},\\n {name: \\\"Juanita\\\"},\\n {name: \\\"Adeline\\\"},\\n {name: \\\"Pete\\\"},\\n {name: \\\"Jeff\\\"},\\n {name: \\\"Paul\\\"}\\n ]\\n },\\n\\n html = myTmpl.render(data);\\n\\n$(\\\"#page\\\").html(html);\\n\\n{{for people end=-2 noIteration=true}} {{!-- slice (remove last two) --}}\\n ...\\n {{for #data step=3 start=0 sort=\\\"name\\\"}} {{!-- sort ... --}}\\n ...\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"text\": \"\\n Sliced then sorted:\\n {{for colors start=1 end=-1 noIteration=true}} {{!-- slice (remove first and last) --}}\\n {{for #data sort=\\\"\\\"}}{{:}} {{/for}} {{!-- sort --}}\\n {{/for}}\\n\\n\\n\\nvar myTmpl = $.templates(\\\"#myTmpl\\\"),\\n\\n data = {\\n colors: [\\\"red\\\", \\\"orange\\\", \\\"yellow\\\", \\\"green\\\", \\\"blue\\\", \\\"indigo\\\", \\\"violet\\\"]\\n },\\n\\n html = myTmpl.render(data);\\n\\n$(\\\"#page\\\").html(html);\\n{{for colors start=1 end=-1 noIteration=true}} {{!-- slice (remove first and last) --}}\\n {{for #data sort=\\\"\\\"}}...{{/for}} {{!-- sort ... --}}\\n{{/for}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"A similar approach can be used to apply any desired filter, sort, reverse, or ‘slice’ operations in any order. For example:\\n{{for colors filter=~preSort noIteration=true}}\\n {{for #data sort=... noIteration=true}}\\n {{for #data filter=~afterSort}}...{{/for}}\\n {{/for}}\\n{{/for}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using {{for start=... end=... step=...}} to iterate over a range of numbers\",\r\n \"text\": \"Using {{for start=... end=... step=...}} to iterate over a range of numbers\\nThe {{for}} tag can be used to iterate over a range of numbers, rather than iterating over a data array.\\nFor example:\\n{{for start=0 end=4}}{{:}}, {{/for}}\\n\\nwill render the result 0, 1, 2, 3,.\\nBy setting the start and end properties (and optionally the step property) to appropriate Numbers, but without providing any argument as data array, the {{for}} tag will in fact generate a corresponding array of numbers (usually integers), and will iterate over that generated array.\\n\\nstart: Initial number for generated array. If undefined, defaults to 0\\nend: Number before which to end the array (generate numbers up to but not including end)\\nstep: Optional: the incremental amount for subsequent numbers in the array. Defaults to 1\\n\\nFor example:\\n{{for start=4.5 end=-2 step=-1.5}}{{:}}, {{/for}}\\n\\nwill output 4.5, 3, 1.5, 0, -1.5,\\nThe following sample uses generated arrays to render table layouts of people 'by rows’:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"\",\r\n \"text\": \"\\n Sorted table, by rows (2 columns):\\n\\n \\n {{for end=people.length/2 itemVar='~row'}}\\n \\n {{for ~root.people start=~row*2 end=(~row+1)*2 sort=\\\"name\\\"}}\\n \\n {{/for}}\\n \\n {{/for}}\\n
        {{:1+(~row*2)+#index}} {{:name}}
        \\n\\n Sorted table, by rows (4 columns):\\n\\n \\n {{for end=people.length/4 itemVar='~row'}}\\n \\n {{for ~root.people start=~row*4 end=(~row+1)*4 sort=\\\"name\\\"}}\\n \\n {{/for}}\\n \\n {{/for}}\\n
        {{:1+(~row*4)+#index}} {{:name}}
        \\n\\n\\n\\nvar myTmpl = $.templates(\\\"#myTmpl\\\"),\\n\\n data = {\\n people: [\\n {name: \\\"Jo\\\"},\\n {name: \\\"Adriana\\\"},\\n {name: \\\"Xavier\\\"},\\n {name: \\\"Juanita\\\"},\\n {name: \\\"Adeline\\\"},\\n {name: \\\"Pete\\\"},\\n {name: \\\"Jeff\\\"},\\n {name: \\\"Paul\\\"}\\n ]\\n },\\n\\n html = myTmpl.render(data);\\n\\n$(\\\"#page\\\").html(html);\\n\\nSorted table, by rows:\\n{{for end=people.length/2 itemVar='~row'}}\\n \\n {{for ~root.people start=~row*2 end=(~row+1)*2 sort=\\\"name\\\"}}\\n ...{{:name}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Use of itemVar with noIteration=true, to reference the array\",\r\n \"text\": \"Use of itemVar with noIteration=true, to reference the array\\nNote the use of itemVar='~row' in the above examples. itemVar is used to provide an alias for the current data in the wrapped content, so in this case it is the current integer as we iterate over the generated array.\\nSo the following:\\n{{for start=0 end=4 itemVar='row'}}{{:~row}} {{/for}}\\n\\n{{for start=0 end=4}}{{:}} {{/for}}\\n\\nare equivalent, and each render the result \\\"0 1 2 3\\\".\\nBy setting noIteration=true we can instead use itemVar for the array itself, as in the following sample:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"itemVar - passing arrays around then iterating over them\",\r\n \"text\": \"itemVar - passing arrays around then iterating over them\\n\\n {{for start=1 end=7 noIteration=true itemVar=\\\"~cols\\\"}}\\n {{for start=1 end=5 noIteration=true itemVar=\\\"~rows\\\"}}\\n \\n {{for ~rows itemVar=\\\"~j\\\"}}\\n \\n {{for ~cols itemVar=\\\"~i\\\"}}\\n \\n {{/for}}\\n \\n {{/for}}\\n
        {{:~i}}, {{:~j}}
        \\n {{/for}}\\n {{/for}}\\n\\n\\n\\nvar myTmpl = $.templates(\\\"#myTmpl\\\"),\\n\\n html = myTmpl.render({});\\n\\n$(\\\"#page\\\").html(html);\\n\\n{{for start=1 end=7 noIteration=true itemVar=\\\"~cols\\\"}} {{!-- ~rows is an array from 1 to 6 --}}\\n {{for start=1 end=5 noIteration=true itemVar=\\\"~rows\\\"}} {{!-- ~cols is an array from 1 to 4 --}}\\n \\n {{for ~rows itemVar=\\\"~j\\\"}} {{!-- iterate over ~rows array --}}\\n \\n {{for ~cols itemVar=\\\"~i\\\"}} {{!-- iterate over ~cols array --}}\\n \\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"The following more advanced approach to sorting by columns uses the noIteration=true itemVar=\\\"~sorted\\\" technique:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"text\": \"\\n Sorted table by columns (advanced alternative):\\n\\n
        {{:~i}}, {{:~j}}
        \\n {{for people sort=\\\"name\\\" noIteration=true itemVar=\\\"~sorted\\\"}}\\n \\n {{for end=length step=2 itemVar=\\\"~col\\\"}}\\n \\n {{/for}}\\n \\n {{for start=1 end=length step=2 itemVar=\\\"~col\\\"}}\\n \\n {{/for}}\\n \\n {{/for}}\\n
        {{:1+2*#index}} {{:~sorted[~col].name}}
        {{:2+2*#index}} {{:~sorted[~col].name}}
        \\n\\n\\n\\nvar myTmpl = $.templates(\\\"#myTmpl\\\"),\\n\\n data = {\\n people: [\\n {name: \\\"Jo\\\"},\\n {name: \\\"Adriana\\\"},\\n {name: \\\"Xavier\\\"},\\n {name: \\\"Juanita\\\"},\\n {name: \\\"Adeline\\\"},\\n {name: \\\"Pete\\\"},\\n {name: \\\"Jeff\\\"},\\n {name: \\\"Paul\\\"}\\n ]\\n },\\n\\n html = myTmpl.render(data);\\n\\n$(\\\"#page\\\").html(html);\\n\\n{{for people sort=\\\"name\\\" noIteration=true itemVar=\\\"~sorted\\\"}} {{!-- ~sorted is the sorted people array --}}\\n ...\\n {{for end=length step=2 itemVar=\\\"~col\\\"}} {{!-- iterate over even integers from 0 to ~sorted.length--}}\\n ... {{:~sorted[~col].name}} {{!-- render the person.name for ~sorted items with index 0, 2, 4... --}}\\n ...\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"See also the ‘range’ sample, for an example of dynamic use of the start and end properties of {{for}}, along with JsViews data-linking.\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"See also:\",\r\n \"text\": \"See also:\\n\"\r\n }\r\n ]\r\n },\r\n \"propstag\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"tag\",\r\n \"title\": \"{{props ...}}\",\r\n \"text\": \"{{props ...}}\\nTemplate composition: – Iterate over the properties of the object, and render the block content of the {{props}} tag (or the referenced external template) once for each property – using as data context: {key: propertyName, prop: propertyValue}.\\nRender the block content of the tag for each property of the given object\\n\\n{{props billing.address}}\\n {{>key}}: {{>prop}}\\n{{/props}}\\n\\nRender the specified template once for each property of the given object\\n\\n{{props billing.address tmpl=\\\"addressTmpl\\\" /}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Here are some examples:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"{{props object}}\",\r\n \"text\": \"{{props object}}\\n\\n...\\n{{props address}}\\n {{>key}}: {{>prop}}\\n{{/props}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"{{props object tmpl=... /}}\",\r\n \"text\": \"{{props object tmpl=... /}}\\n\\n \\n \\n \\n
        name: {{:name}}
        \\n {{props address tmpl=\\\"#addressTemplate\\\" /}}\\n
        \\n\\n\\n\\n {{>key}}: {{>prop}}
        \\n\\n\\n\\nvar people = [\\n {\\n \\\"name\\\": \\\"Pete\\\",\\n \\\"address\\\": {\\n \\\"street\\\": \\\"12 Pike Place\\\",\\n \\\"city\\\": \\\"Seattle\\\",\\n \\\"ZIP\\\": \\\"98101\\\"\\n }\\n },\\n {\\n \\\"name\\\": \\\"Heidi\\\",\\n \\\"address\\\": {\\n \\\"street\\\": \\\"5000 Broadway\\\",\\n \\\"city\\\": \\\"Sidney\\\",\\n \\\"country\\\": \\\"Australia\\\"\\n }\\n }\\n];\\n\\nvar html = $(\\\"#peopleTemplate\\\").render(people);\\n\\n$(\\\"#result\\\").html(html);\\n{{props address tmpl=\\\"#addressTemplate\\\" /}}\\n\\n\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using the {{else}} tag with {{props}}\",\r\n \"text\": \"Using the {{else}} tag with {{props}}\\nUsing the {{else}} tag between {{props}} and {{/props}}, allows alternate rendering based on the object returned from the path or expression {{props pathOrExpr}}\\n\"\r\n },\r\n {\r\n \"_type\": \"tag\",\r\n \"title\": \"{{props ...}}...{{else}}...{{/props}}\",\r\n \"text\": \"{{props ...}}...{{else}}...{{/props}}\\nConditional blocks: – Render the block content of the {{prop}} tag (or referenced template) if the object is defined and is not an empty object (no properties), otherwise render the {{else}} block (or template)\\nRender first block if object is not empty, otherwise render second block\\n\\n{{props address}}\\n Key: {{:key}} Value: {{:prop}}\\n{{else}}\\n No properties...\\n{{/for}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"text\": \"\\n{{props address}}\\n {{>key}}: {{>prop}}\\n{{else}}\\n The address is blank (no properties)!\\n{{/props}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"text\": \"\\n\\n \\n \\n
        name: {{:name}}
        \\n {{props USaddress}}\\n {{>key}}: {{>prop}}
        \\n {{else UKaddress}}\\n {{>key}}: {{>prop}}
        \\n {{else}}\\n The address is blank (no properties)!\\n {{/props}}\\n
        \\n\\n\\nvar myTmpl = $.templates(\\\"#myTmpl\\\"),\\n data = [\\n {\\n \\\"name\\\": \\\"Pete\\\",\\n \\\"USaddress\\\": {\\n \\\"street\\\": \\\"12 Pike Place\\\",\\n \\\"city\\\": \\\"Seattle\\\",\\n \\\"ZIP\\\": \\\"98101\\\"\\n }\\n },{\\n \\\"name\\\": \\\"Jeff\\\",\\n \\\"UKaddress\\\": {\\n \\\"street\\\": \\\"3a Upton Place\\\",\\n \\\"city\\\": \\\"London\\\",\\n \\\"code\\\": \\\"W2 1JA\\\"\\n }\\n },{\\n \\\"name\\\": \\\"Heidi\\\",\\n }\\n ],\\n html = myTmpl.render(data);\\n\\n$(\\\"#page\\\").html(html);\\n{{props USaddress}}\\n {{>key}}: {{>prop}}
        \\n{{else UKaddress}}\\n {{>key}}: {{>prop}}
        \\n{{else}}\\n The address is blank (no properties)!\\n{{/props}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"The noFunctions property\",\r\n \"text\": \"The noFunctions property\\nBy default {{props}} will iterate over all members of an object, including members of type: function (methods). To prevent outputting members of type function, set the noFunctions property to true:\\n{{props ... noFunctions=true}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"text\": \"\\n {{props person}}\\n {{>key}}: {{>prop}}
        \\n {{/props}}\\n\\n
        \\n\\n {{props person noFunctions=true}}\\n {{>key}}: {{>prop}}
        \\n {{/props}}\\n\\n\\n\\n\\nvar myTmpl = $.templates(\\\"#myTmpl\\\"),\\n data = {\\n person: {\\n first: \\\"Jo\\\",\\n last: \\\"Blow\\\",\\n fullName: function() {\\n return this.first + \\\" \\\" + this.last;\\n }\\n }\\n },\\n html = myTmpl.render(data);\\n\\n$(\\\"#page\\\").html(html);\\n{{props person noFunctions=true}}\\n {{>key}}: {{>prop}}
        \\n{{/props}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using {{props}} to iterate over a top-level dictionary/hash data-collection\",\r\n \"text\": \"Using {{props}} to iterate over a top-level dictionary/hash data-collection\\nIf the data (obtained for example, from the server) is a collection, but in the form of an object (dictionary/hash) rather than an array, then {{props}} without arguments (or equivalently {{props #data}}) can be used to iterate over the collection, as shown in the next sample:\\n(When using JsViews, see also {^{props}} for loading and providing complete editability of a top-level dictionary/hash.)\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"text\": \"\\n \\n {{props}}\\n \\n \\n \\n \\n {{/props}}\\n
        name: {{:prop.name}}
        \\n {{props prop.address tmpl=\\\"#addressTemplate\\\" /}}\\n
        \\n\\n\\n\\n {{>key}}: {{>prop}}
        \\n\\n\\n\\nvar people = {\\n pt1: {\\n \\\"name\\\": \\\"Pete\\\",\\n \\\"address\\\": {\\n \\\"street\\\": \\\"12 Pike Place\\\",\\n \\\"city\\\": \\\"Seattle\\\",\\n \\\"ZIP\\\": \\\"98101\\\"\\n }\\n },\\n Hd1: {\\n \\\"name\\\": \\\"Heidi\\\",\\n \\\"address\\\": {\\n \\\"street\\\": \\\"5000 Broadway\\\",\\n \\\"city\\\": \\\"Sidney\\\",\\n \\\"country\\\": \\\"Australia\\\"\\n }\\n }\\n};\\n\\nvar html = $(\\\"#peopleTemplate\\\").render(people);\\n\\n$(\\\"#result\\\").html(html);\\nDictionary/hash – collection of people:\\nvar people = {\\n pt1: {\\n \\\"name\\\": \\\"Pete\\\",\\n \\\"address\\\": {\\n ...\\n }\\n },\\n Hd1: {\\n \\\"name\\\": \\\"Heidi\\\",\\n \\\"address\\\": {\\n ...\\n }\\n }\\n};\\n\\nTemplate:\\n\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using {{props}} with specific sorting, filtering or range of the rendered properties\",\r\n \"text\": \"Using {{props}} with specific sorting, filtering or range of the rendered properties\\nWhen using the {{props}} tag to render properties, built-in features allow sorting, filtering and ‘slicing’ of the rendered list. (These features correspond exactly to the equivalent sorting and filtering features provided by the{{for}} tag):\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"The reverse property: specifying reverse ordering (or reverse sorting) on {{props object}}\",\r\n \"text\": \"The reverse property: specifying reverse ordering (or reverse sorting) on {{props object}}\\nTo iterate over the object properties in reverse order, set the reverse property to true:\\n{{props object reverse=true }}...{{/props}}\\n\\nSetting reverse=true can be combined with using the sort, filter, start, end or step properties, to reverse the order of iteration (for example to sort in descending order rather than ascending order).\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"The sort property: specifying sorting on {{props object}}\",\r\n \"text\": \"The sort property: specifying sorting on {{props object}}\\nTo specify sorting, set the sort property:\\n{{props object sort=\\\"prop.firstName\\\" }}...{{/props}}\\n\\n\\nTo sort the properties by key, set sort=\\\"key\\\"\\nIf the properties are objects (a ‘hash’ of objects), the sort=... property of {{props}} is usually set to an object property to be sorted by, such as prop.firstName, or to a data path, such as sort=\\\"prop.address.street\\\"\\nTo sort a hash of numbers, strings or Dates, set the sort property to: sort=\\\"prop\\\"\\nFor advanced scenarios you can provide your own sort function: sort=~mySortFunction\\n\\nSetting sort=... can be combined with using the reverse, filter, start, end or step properties.\\nThe following three samples illustrate the above scenarios, using the reverse and sort properties:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Sorting strings/Numbers/Date properties\",\r\n \"text\": \"Sorting strings/Numbers/Date properties\\n\\n Reverse sort strings:\\n {{props colors sort=\\\"prop\\\" reverse=true}}\\n {{:prop}}\\n {{/props}}
        \\n\\n Sort numbers:\\n {{props amounts sort=\\\"key\\\"}}\\n {{:key}}: {{:prop}},\\n {{/props}}
        \\n\\n Sort dates:\\n {{props dates sort=\\\"prop\\\"}}\\n {{:key}}: {{formatDate:prop}}.\\n {{/props}}\\n\\n\\n\\n$.views.converters(\\\"formatDate\\\", function(date) {\\n // Converter to format Dates\\n return date.toLocaleDateString(\\\"en-US\\\");\\n});\\n\\nvar myTmpl = $.templates(\\\"#myTmpl\\\"),\\n\\n data = {\\n colors: {\\n c1: \\\"red\\\",\\n c2: \\\"white\\\",\\n c3: \\\"blue\\\"\\n },\\n amounts: {\\n \\\"1st quarter\\\": 111.2,\\n \\\"3rd quarter\\\": -2.33,\\n \\\"4th quarter\\\": 2.4,\\n \\\"2nd quarter\\\": -22\\n },\\n dates: {\\n Created: new Date(2000, 0, 1),\\n Deleted: new Date(2000, 11, 31),\\n Edited: new Date(1998, 6, 30)\\n }\\n },\\n\\n html = myTmpl.render(data);\\n\\n$(\\\"#page\\\").html(html);\\nTemplate:\\n{{props colors sort=\\\"prop\\\" reverse=true}} {{!-- (Reverse) sort string properties --}}\\n{{props amounts sort=\\\"key\\\"}} {{!-- Sort Number properties by key --}}\\n{{props dates sort=\\\"prop\\\"}} {{!-- Sort Date properties --}}\\n\\nData:\\ncolors: {c1: \\\"red\\\", ...},\\namounts: {\\\"1st quarter\\\": 111.2, ...},\\ndates: {Created: new Date(2000, 0, 1), ...}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Sorting a hash of objects\",\r\n \"text\": \"Sorting a hash of objects\\nTemplate:\\n{{props people sort=\\\"prop.lastName\\\" reverse=true}}...{{/props}} {{!-- Sort by last name, decreasing --}}\\n{{props people sort=\\\"prop.address.street\\\"}} {{!-- Sort by address.street --}}\\n{{props people sort=\\\"key\\\" reverse=true}} {{!-- Reverse sort by key --}}\\n\\nData:\\npeople: {\\n p1: {firstName: \\\"Jo\\\", ... address: {street: \\\"1st Street\\\" ...}},\\n ...\\n}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Using a custom sort function: multi-level sort \",\r\n \"text\": \"Using a custom sort function: multi-level sort \\n\\n
          \\n {{props people sort=~multilevel}} {{!-- Sort using a custom helper function: ~multilevel --}}\\n
        • {{:prop.name}}: ({{:prop.details.role}}) – age {{:prop.details.age}}
        • \\n {{/props}}\\n
        \\n\\n\\n\\n// Helper function for multi-level sort\\nfunction level(aField, bField) {\\n return aField > bField ? 1 : aField < bField ? -1 : 0;\\n}\\n\\n// Custom sort function\\nfunction multilevelSort(a, b) {\\n // Sort by role, then by age (descending) then by name\\n return level(a.prop.details.role.toLowerCase(), b.prop.details.role.toLowerCase()) // by role\\n || level(b.prop.details.age, a.prop.details.age) // by age\\n || level(a.prop.name.toLowerCase(), b.prop.name.toLowerCase()); // by name\\n}\\n\\nvar myTmpl = $.templates(\\\"#myTmpl\\\"),\\n\\n data = {people: {\\n p1: {name: \\\"Bill\\\", details: {age: 22, role: \\\"Lead\\\"}},\\n p2: {name: \\\"Anne\\\", details: {age: 32, role: \\\"Assistant\\\"}},\\n p3: {name: \\\"Emma\\\", details: {age: 19.1, role: \\\"Team member\\\"}},\\n p4: {name: \\\"Jeff\\\", details: {age: 33.5, role: \\\"Lead\\\"}},\\n p5: {name: \\\"Xavier\\\", details: {age: 32, role: \\\"Team member\\\"}},\\n p6: {name: \\\"Julia\\\", details: {age: 18, role: \\\"Assistant\\\"}},\\n p7: {name: \\\"Bill\\\", details: {age: 32, role: \\\"Team member\\\"}}\\n }},\\n\\n html = myTmpl.render(data, { \\n multilevel: multilevelSort\\n });\\n\\n$(\\\"#page\\\").html(html);\\n{{props people sort=~multilevel}}...{{/props}} {{!-- Sort using a custom helper function: ~multilevel --}}\\n\\nThe custom sort function takes arguments (a, b) for the two objects being compared. The this pointer is the current view object.\\n// Custom sort function\\nfunction multilevelSort(a, b) {\\n return ... // Return 1, -1 or 0 to specify relative position of `a` and `b` in the sort order\\n}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"The filter property: specifying filtering on {{props object}}\",\r\n \"text\": \"The filter property: specifying filtering on {{props object}}\\nTo filter the rendered properties, use the filter property to specify a filter function:\\n{{props object filter=~myfilter}}...{{/props}}\\n\\nfunction myfilter(item, index, items) {\\n return ...; // Return true/false to include/exclude any item from the result\\n}\\n\\nThe filter function is called with the tagCtx object as this pointer, and with arguments:\\n\\nitem: The current {key:..., prop:...} ‘property’ object being processed\\nindex: The index of the current item being processed in the (sorted) array of ‘property’ objects\\narray: The (sorted) array of ‘property’ objects being filtered\\n\\nSetting filter=... can be combined with using the sort, reverse, start, end or step properties (to filter the items after sorting or reversing, or before ‘slicing’).\\nThe following sample renders a subset of a hash of people, filtered by age:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"text\": \"\\n Filter: age from 20 to 40\\n\\n
          \\n {{props people filter=~ageRange minAge=20 maxAge=40 sort=\\\"prop.name\\\"}}\\n
        • {{:prop.name}}: age {{:prop.details.age}}
        • \\n {{/props}}\\n
        \\n\\n\\n\\nfunction ageRangeFilter(item, index, items) {\\n return item.prop.details.age > this.props.minAge && item.prop.details.age < this.props.maxAge;\\n}\\n\\nvar myTmpl = $.templates(\\\"#myTmpl\\\"),\\n\\n data = {\\n people: {\\n p1: {name: \\\"Bill\\\", details: {age: 25}},\\n p2: {name: \\\"Anne\\\", details: {age: 32}},\\n p3: {name: \\\"Emma\\\", details: {age: 19.1}},\\n p4: {name: \\\"Jeff\\\", details: {age: 33.5}},\\n p5: {name: \\\"Xavier\\\", details: {age: 52}},\\n p6: {name: \\\"Julia\\\", details: {age: 18}},\\n p7: {name: \\\"Jo\\\", details: {age: 30}}\\n }\\n },\\n\\n html = myTmpl.render(data, { \\n ageRange: ageRangeFilter\\n });\\n\\n$(\\\"#page\\\").html(html);\\nfunction ageRangeFilter(item, index, items) {\\n return item.prop.details.age > this.props.minAge ...\\n}\\n\\n{{props people filter=~ageRange minAge=20 maxAge=40 sort=\\\"prop.name\\\"}}...{{/props}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"The following sample renders a hash of people in a two row layout – by filtering for the items with even (first row) and odd (second row) index.\\n(See also an alternative approach using step=..., in the section below).\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"text\": \"\\n \\n {{props people filter=~evenOdd odd=false sort=\\\"prop.name\\\"}}\\n \\n {{/props}}\\n \\n {{props people filter=~evenOdd odd=true sort=\\\"prop.name\\\"}}\\n \\n {{/props}}\\n
        {{:1+2*#index}} {{:prop.name}}
        {{:2+2*#index}} {{:prop.name}}
        \\n\\n\\n\\nvar myTmpl = $.templates(\\\"#myTmpl\\\"),\\n\\n data = {\\n people: {\\n p1: {name: \\\"Jo\\\"},\\n p2: {name: \\\"Adriana\\\"},\\n p3: {name: \\\"Xavier\\\"},\\n p4: {name: \\\"Juanita\\\"},\\n p5: {name: \\\"Adeline\\\"},\\n p6: {name: \\\"Pete\\\"},\\n p7: {name: \\\"Jeff\\\"},\\n p8: {name: \\\"Paul\\\"}\\n }\\n },\\n\\n html = myTmpl.render(data, {\\n evenOdd: function(item, index, items) {\\n return this.props.odd === (index%2 === 1); // Include only items with even/odd index\\n }\\n });\\n\\n$(\\\"#page\\\").html(html);\\n\\n{{props people filter=~evenOdd odd=false sort=\\\"prop.name\\\"}}...{{/props}}\\n...\\n{{props people filter=~evenOdd odd=true sort=\\\"prop.name\\\"}}...{{/props}}\\n\\nevenOdd: function(item, index, items) {\\n return this.props.odd === (index%2 === 1); // Include only items with even/odd index\\n}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"The start, end and step properties: limiting range and/or selecting every n'th item of {{props object}}\",\r\n \"text\": \"The start, end and step properties: limiting range and/or selecting every n'th item of {{props object}}\\nTo limit the range of rendered properties, use the start and/or end properties to specify the starting and ending index. In addition, the step property lets you take every other n’th item in the array of ‘property’ objects.\\nThe behavior of start and end corresponds to the array.slice(start, end) JavaScript method:\\n\\nstart: zero-based index at which to begin rendering\\n\\nA negative index indicates an offset from the end of the sequence\\nIf start is undefined, begins from index 0\\n\\nend: zero-based index before which to end rendering (render up to but not including end)\\n\\nA negative index indicates an offset from the end of the sequence\\nIf end is undefined, render through the end of the array\\n\\nstep: A positive integer ‘n’, in order to include every nth item, beginning with start. Defaults to 1\\n\\n{{props colors start=1 end=-1 step=2}}...{{/props}}\\n\\nSetting start=..., end=... and/or step=... can be combined with using the sort, reverse, filter to limit the range, after sorting, reversing or filtering.\\n{{props colors sort=\\\"name\\\" start=1 end=-1 step=2}}...{{/props}}\\n\\nThe following sample illustrates the use of start=... and end=... with or without sorting:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"text\": \"\\n Unsorted:\\n {{props colors}}{{:prop}} {{/props}}\\n
        \\n\\n Sliced:\\n {{props colors start=1 end=-1}}{{:prop}} {{/props}}\\n
        \\n\\n Alternate, odd:\\n {{props colors step=2}}{{:prop}} {{/props}}\\n
        \\n\\n Alternate, even:\\n {{props colors step=2 start=1}}{{:prop}} {{/props}}\\n
        \\n\\n Sorted:\\n {{props colors sort=\\\"prop\\\"}}{{:prop}} {{/props}}\\n
        \\n\\n Sorted then sliced:\\n {{props colors sort=\\\"prop.name\\\" start=1 end=-1}}{{:prop.name}} {{/props}}\\n\\n\\n\\nvar myTmpl = $.templates(\\\"#myTmpl\\\"),\\n\\n data = {\\n colors: {\\n c1: \\\"red\\\",\\n c2: \\\"orange\\\",\\n c3: \\\"yellow\\\",\\n c4: \\\"green\\\",\\n c5: \\\"blue\\\",\\n c6: \\\"indigo\\\",\\n c7: \\\"violet\\\"\\n }\\n },\\n\\n html = myTmpl.render(data);\\n\\n$(\\\"#page\\\").html(html);\\n\\n{{props colors}}...{{/props}}\\n{{props colors start=1 end=-1}}...{{/props}}\\n{{props colors step=2}}...{{/props}}\\n{{props colors step=2 start=1}}...{{/props}}\\n{{props colors sort=\\\"name\\\"}}...{{/props}}\\n{{props colors sort=\\\"name\\\" start=1 end=-1}}...{{/props}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"The following sample uses step=... to render multi-row layouts of a hash of people:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"text\": \"\\n Three row display: step=3\\n\\n \\n {{props people step=3 start=0 sort=\\\"prop.name\\\" end=-2}}\\n \\n {{/props}}\\n \\n {{props people step=3 start=1 sort=\\\"prop.name\\\" end=-2}}\\n \\n {{/props}}\\n \\n {{props people step=3 start=2 sort=\\\"prop.name\\\" end=-2}}\\n \\n {{/props}}\\n
        {{:1+3*#index}} {{:prop.name}}
        {{:2+3*#index}} {{:prop.name}}
        {{:3+3*#index}} {{:prop.name}}
        \\n\\n Two row display: step=2 reverse=true\\n\\n \\n {{props people step=2 start=0 sort=\\\"prop.name\\\" reverse=true}}\\n \\n {{/props}}\\n \\n {{props people step=2 start=1 sort=\\\"prop.name\\\" reverse=true}}\\n \\n {{/props}}\\n
        {{:1+2*#index}} {{:prop.name}}
        {{:2+2*#index}} {{:prop.name}}
        \\n\\n\\n\\nvar myTmpl = $.templates(\\\"#myTmpl\\\"),\\n\\n data = {\\n people: {\\n p1: {name: \\\"Jo\\\"},\\n p2: {name: \\\"Adriana\\\"},\\n p3: {name: \\\"Xavier\\\"},\\n p4: {name: \\\"Juanita\\\"},\\n p5: {name: \\\"Adeline\\\"},\\n p6: {name: \\\"Pete\\\"},\\n p7: {name: \\\"Jeff\\\"},\\n p8: {name: \\\"Paul\\\"}\\n }\\n },\\n\\n html = myTmpl.render(data);\\n\\n$(\\\"#page\\\").html(html);\\n{{props people step=3 start=0 sort=\\\"name\\\" end=-2}}\\n{{props people step=3 start=1 sort=\\\"name\\\" end=-2}}\\n{{props people step=3 start=2 sort=\\\"name\\\" end=-2}}\\n\\n{{props people step=2 start=0 sort=\\\"name\\\" reverse=true}}\\n{{props people step=2 start=1 sort=\\\"name\\\" reverse=true}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Sorting, filtering, 'slicing' operations in any order\",\r\n \"text\": \"Sorting, filtering, 'slicing' operations in any order\\nSome of the above samples include applying a sort operation followed by a ‘slice’ operation. It is also possible reverse the order of operations, and to limit the range before sorting the result, as in the following two examples:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"text\": \"\\n Three row display (sliced then sorted)\\n\\n {{props people end=-2 noIteration=true}} {{!-- slice (remove last two) --}}\\n \\n {{for #data step=3 sort=\\\"prop.name\\\"}} {{!-- sort ... --}}\\n \\n {{/for}}\\n \\n {{for #data step=3 start=1 sort=\\\"prop.name\\\"}}\\n \\n {{/for}}\\n \\n {{for #data step=3 start=2 sort=\\\"prop.name\\\"}}\\n \\n {{/for}}\\n
        {{:1+3*#index}} {{:prop.name}}
        {{:2+3*#index}} {{:prop.name}}
        {{:3+3*#index}} {{:prop.name}}
        \\n {{/props}}\\n\\n\\n\\nvar myTmpl = $.templates(\\\"#myTmpl\\\"),\\n\\n data = {\\n people: {\\n p1: {name: \\\"Jo\\\"},\\n p2: {name: \\\"Adriana\\\"},\\n p3: {name: \\\"Xavier\\\"},\\n p4: {name: \\\"Juanita\\\"},\\n p5: {name: \\\"Adeline\\\"},\\n p6: {name: \\\"Pete\\\"},\\n p7: {name: \\\"Jeff\\\"},\\n p8: {name: \\\"Paul\\\"}\\n }\\n },\\n\\n html = myTmpl.render(data);\\n\\n$(\\\"#page\\\").html(html);\\n\\n{{props people end=-2 noIteration=true}} {{!-- slice (remove last two) --}}\\n ...\\n {{for #data step=3 sort=\\\"prop.name\\\"}} {{!-- sort ... --}}\\n ...\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"text\": \"\\n Sliced then sorted:\\n {{props colors start=1 end=-1 noIteration=true}} {{!-- slice (remove first and last) --}}\\n {{for #data sort=\\\"prop\\\"}}{{:prop}} {{/for}} {{!-- sort ... --}}\\n {{/props}}\\n\\n\\n\\nvar myTmpl = $.templates(\\\"#myTmpl\\\"),\\n\\n data = {\\n colors: {\\n c1: \\\"red\\\",\\n c2: \\\"orange\\\",\\n c3: \\\"yellow\\\",\\n c4: \\\"green\\\",\\n c5: \\\"blue\\\",\\n c6: \\\"indigo\\\",\\n c7: \\\"violet\\\"\\n }\\n },\\n\\n html = myTmpl.render(data);\\n\\n$(\\\"#page\\\").html(html);\\n\\n{{props colors start=1 end=-1 noIteration=true}} {{!-- slice (remove first and last) --}}\\n {{for #data sort=\\\"prop\\\"}}...{{/for}} {{!-- sort ... --}}\\n{{/props}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"A similar approach can be used to apply any desired filter, sort, reverse, or ‘slice’ operations in any order. For example:\\n{{props colors filter=~preSort noIteration=true}}\\n {{for #data sort=... noIteration=true}}\\n {{for #data filter=~afterSort}}...{{/for}}\\n {{/for}}\\n{{/props}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"See also:\",\r\n \"text\": \"See also:\\n\"\r\n }\r\n ]\r\n },\r\n \"iftag\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"tag\",\r\n \"title\": \"{{if ...}}\",\r\n \"text\": \"{{if ...}}\\nConditional inclusion: – Render the block content of the {{if}} tag (or the referenced external template) only if the data-path or expression evaluates to true (or a 'truthy' value)\\nRender the block only if the expression is true\\n\\n{{if nickname}}\\n Nickname: {{:nickname}}\\n{{/if}}\\n\\nRender the specified template only if the expression is true\\n\\n{{if nickname tmpl=\\\"nicknameTemplate\\\" /}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using the {{else}} tag with {{if}}\",\r\n \"text\": \"Using the {{else}} tag with {{if}}\\nUsing the {{else}} tag between {{if}} and {{/if}}, allows alternate rendering based on 'if … else …' logic:\\n\"\r\n },\r\n {\r\n \"_type\": \"tag\",\r\n \"title\": \"{{if ...}}...{{else}}...{{/if}}\",\r\n \"text\": \"{{if ...}}...{{else}}...{{/if}}\\nAlternative conditional blocks: – Render the block content of the {{if}} tag (or referenced template) if the expression is true, otherwise render the {{else}} block (or template)\\nRender first block if condition is true, otherwise render second block\\n\\n{{if nickname}}\\n Nickname: {{:nickname}}\\n{{else}}\\n No nickname...\\n{{/if}}\\n\\nRender first template if condition is true, otherwise render second template\\n\\n{{if nickname tmpl=\\\"nicknameTemplate\\\"}}\\n{{else tmpl=\\\"noNicknameTemplate\\\"}}\\n{{/if}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"else and elseif\",\r\n \"text\": \"else and elseif\\nYou can add more than one {{else}} tag between {{if}} and {{/if}}, to get alternate rendering based on 'if … elseif … else …' logic. For elseif, just include an expression…:\\n\"\r\n },\r\n {\r\n \"_type\": \"tag\",\r\n \"title\": \"{{if ....}}...{{else ...}}...{{else}}...{{/if}}\",\r\n \"text\": \"{{if ....}}...{{else ...}}...{{else}}...{{/if}}\\nMultiple alternative conditional blocks: – Render the first {{if}} or {{else}} block for which the expression is true. If none are true, and there is an {{else}} without an expression, render that block\\nRender first block for which condition is true, otherwise last block\\n\\n{{if nickname}}\\n Nickname: {{:nickname}}\\n{{else altnickname}}\\n Alternate nickname: {{:altnickname}}\\n{{else}}\\n No nickname...\\n{{/if}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"{{if}}...{{else}}...{{/if}}\",\r\n \"text\": \"{{if}}...{{else}}...{{/if}}\\n[\\n {title: \\\"The A team\\\", members: [...], standby: [...]},\\n {title: \\\"The B team\\\", members: [], standby: [...]},\\n {title: \\\"The C team\\\", standby: []}\\n]\\n\\n{{if members && members.length}}\\n ...\\n{{else standby && standby.length}}\\n Standby only:\\n ...\\n{{else}}\\n No members!\\n{{/if}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"See also:\",\r\n \"text\": \"See also:\\n\"\r\n }\r\n ]\r\n },\r\n \"elsetag\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"{{else}} can be used with {{if}}, {{for}}, {{props}} or any custom tag!\",\r\n \"text\": \"{{else}} can be used with {{if}}, {{for}}, {{props}} or any custom tag!\\nThe {{else}} tag acts as a separator, for block tags, to divide the content of a tag into two or more different content blocks.\\nSo it allows a block tag to provide specific behavior involving more than one content block.\\nFor example, the {{if}} tag uses {{else}} to provide if-else, or if-elseif-else … behavior:\\n{{if firstExpression}}\\n render this if the firstExpression is true\\n{{else secondExpression}}\\n else render this if the secondExpression is true\\n{{else}}\\n else render this\\n{{/if}}\\n\\nAnd the {{for}} tag accepts alternative content to render if an array is empty (or an array or object is null or undefined):\\n{{for members}}\\n Member Name: {{:name}}\\n{{else}}\\n There are currently no members...\\n{{/for}}\\n\\nSimilarly you can use {{else}} with a custom tag, such as in this sample:\\n{{tabs caption=\\\"First Tab\\\"}}\\n first tab content\\n{{else caption=\\\"Second Tab\\\"}}\\n second tab content\\n{{/tabs}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"See also\",\r\n \"text\": \"See also\\n\"\r\n }\r\n ]\r\n },\r\n \"commenttag\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"tag\",\r\n \"title\": \"{{!-- a comment --}}\",\r\n \"text\": \"{{!-- a comment --}}\\nAdding comments to templates, or commenting out sections of a template\\nThe comment will be ignored during template rendering – and will produce no output\\n\\n{{!-- this is a comment --}}\\n\\nThe comment can be multiline. All content will be ignored during template rendering - and will produce no output\\n\\n{{!-- this section will be omitted \\n\\nDo I really want to show this? {{:password}}\\n\\n--}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"JsRender comment tags versus HTML comments\",\r\n \"text\": \"JsRender comment tags versus HTML comments\\nYou can include\\n\\n\\n— but unlike the JsRender comment tag, the HTML comment will not be ignored by JsRender or JsViews. It will be included in the rendered output, and will get inserted into the DOM along with other rendered markup.\\n\"\r\n }\r\n ]\r\n },\r\n \"allowcodetag\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"JsRender templates allow you to write rich expressions within the template tags, such as:\\n{{:person.firstName + ' ' + person.lastName.toUpperCase()}}\\n\\nNevertheless, in order to improve encapsulation and maintainability, they don’t allow arbitrary code. For example, they don’t allow you to access global variables, like window.\\nIf you want complete freedom to insert any code into a compiled template, you can set allowCode to true, either globally, or specifically for that template. You can then run any code as part of the template rendering, using the {{* ...}} tag, or you can return (render into the template output) the result of evaluating any expression, using the {{*: ...}} tag.\\n(Note: these allow code tags are not recommended for use within data-linked templates – with JsViews.)\\n\"\r\n },\r\n {\r\n \"_type\": \"tag\",\r\n \"title\": \"{{* ...}}\",\r\n \"text\": \"{{* ...}}\\nInsert code into the template\\nIf allowCode is set to true, include any code in the compiled template.\\n\\n{{* window.myvar=2; myvar+=4; }}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"tag\",\r\n \"title\": \"{{*: ...}}\",\r\n \"text\": \"{{*: ...}}\\nEvaluate any code expression\\nIf allowCode is set to true, evaluate any expression, and insert the result into the rendered output.\\n\\n{{*: myvar/2 }}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Here is an example, with allowCode set to true globally:\\n$.views.settings.allowCode(true);\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"allowCode\",\r\n \"text\": \"allowCode\\n\\n\\n {{* window.myvar=2; myvar+=4; }}\\n\\n
        Initial value: {{*:myvar}}
        \\n\\n {{* window.myvar+=11; }}\\n\\n
        New value: {{*:myvar}}
        \\n\\n\\n\\n\\n$.views.settings.allowCode(true); \\n\\nvar html = $(\\\"#myTemplate\\\").render();\\n\\n$(\\\"#result\\\").html(html);\\nEnable allowCode in all templates:\\n$.views.settings.allowCode(true);\\n\\nDefine a global variable, then increment it:\\n{{* window.myvar=2; myvar+=4; }}\\n\\nInsert the value into the rendered output:\\n
        Initial value: {{*:myvar}}
        \\n\\nIncrement the value again, and output the new value:\\n{{* window.myvar+=11; }}\\n\\n
        New value: {{*:myvar}}
        \\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"And here is an example that uses both regular JsRender tags, like {{for}}, and allowCode tags:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"allowCode and regular tags\",\r\n \"text\": \"allowCode and regular tags\\n\\n {{* window.total = 0}}\\n
          \\n {{for list}}\\n {{* total += data}}\\n
        1. \\n Amount {{:}} (Running total: {{*: total}})\\n
        2. \\n {{/for}}\\n
        \\n Total: {{*: total}}\\n\\n\\n\\nvar data = {\\n title: \\\"My list\\\",\\n list: [2, 10.3, 77, -44, -5.5]\\n };\\n\\n$.views.settings.allowCode(true);\\n\\nvar html = $(\\\"#myTemplate\\\").render(data);\\n\\n$(\\\"#result\\\").html(html);\\n$.views.settings.allowCode(true);\\n\\nDefine a global variable:\\n{{* window.total = 0}}\\n\\nIterate through a list, and use {{* ...}} to increment the total, and {{*:}} to return each value:\\n{{for list}}\\n {{* total += data}}\\n
      • \\n Amount {{:}} (Running total: {{*: total}})\\n
      • \\n{{/for}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Here is another example, in which we will replace the {{for list}} iteration by pure code-based iteration using {{* ...}}. This makes it easy to iterate only over the odd members of the array.\\nThis time we will allow code just for this template:\\n$.templates(..., {\\n markup: ...,\\n allowCode: true,\\n ...\\n})\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"allowCode for template\",\r\n \"text\": \"allowCode for template\\n\\n Here are the odd numbered items:\\n
          \\n {{* for (i=0; i\\n {{*: i+1}}: Amount {{*:data.list[i]}}\\n \\n {{* } }}\\n
        \\n\\n\\n\\nvar data = {\\n title: \\\"My list\\\",\\n list: [2, 10.3, 77, -44, -5.5]\\n };\\n\\nvar tmpl = $.templates({\\n markup: \\\"#myTemplate\\\",\\n allowCode: true\\n });\\n \\nvar html = tmpl.render(data);\\n\\n$(\\\"#result\\\").html(html);\\nEnable allowCode just for this template:\\nvar tmpl = $.templates({\\n markup: \\\"#myTemplate\\\",\\n allowCode: true\\n });\\n \\nvar html = tmpl.render(data);\\n\\nInsert template code to iterate over odd numbers:\\n{{* for (i=0; i\\n \\n {{:name}}\\n \\n \\n\\nvar myTmpl = $.templates(\\\"#personTmpl\\\");\\n\\nvar person = {\\n name: \\\"Adriana\\\"\\n };\\n\\nvar html = myTmpl.render(person);\\n\\n$(\\\"#person\\\").html(html);\\n\\nvar myTmpl = $.templates(\\\"#personTmpl\\\");\\n\\nvar html = myTmpl.render(person);\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Passing an array to the render() method.\\n— The template is rendered once for each item in the array:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"template.render(array):\",\r\n \"text\": \"template.render(array):\\n\\n\\n\\n \\n \\n {{:name}}\\n \\n \\n\\nvar myTmpl = $.templates(\\\"#personTmpl\\\");\\n\\nvar people = [\\n {\\n name: \\\"Adriana\\\"\\n },\\n {\\n name: \\\"Robert\\\"\\n }\\n];\\n\\nvar html = myTmpl.render(people);\\n\\n$(\\\"#peopleList\\\").html(html);\\n\\nvar html = myTmpl.render(people);\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Passing helpers to the render() method.\\n\"\r\n },\r\n {\r\n \"_type\": \"api\",\r\n \"title\": \"template.render(data, helpersOrContext)\",\r\n \"text\": \"template.render(data, helpersOrContext)\\nRender a template against data, along with helper objects or context, and return a string\\nRender template against data, and pass in helpers\\n\\nvar html = myTmpl.render(myData, myHelpers);\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"You can pass in any JavaScript type (object, string, number, function…) as helpers on the helpersOrContext object, and use them as metadata, or as helper functions for formatting etc.\\nNote: By passing in helpers in this way, you are making them specific to this render call. Alternatively, you can declare helpers globally, – and you can also declare helpers that are private to a specific template. See Registering helpers: $.views.helpers() for details…\\nWithin the template, helpers (whether global, or passed in to the render() method) are accessed by helper paths: ~keyName.\\nFor example you might pass in an object with some utility functions:\\nvar myHelpers = {\\n util: {\\n split: function(val, part) {...},\\n ...\\n },\\n ...\\n};\\n\\nvar html = myTmpl.render(myData, myHelpers);\\n\\n– and access them in the template using a helper path such as:\\n{{:~util.split(fullName, 0)}}\\n\\nSee Registering helpers.\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"template.render(object, myHelpers):\",\r\n \"text\": \"template.render(object, myHelpers):\\n\\n\\n\\n \\n \\n {{:~format(name)}}\\n \\n \\n\\nfunction toUpper(val) { return val.toUpperCase(); }\\n\\nvar myTmpl = $.templates(\\\"#personTemplate\\\");\\n\\nvar person = {\\n name: \\\"Adriana\\\"\\n };\\n\\nvar myHelpers = {color: \\\"red\\\", format: toUpper};\\n\\nvar html = myTmpl.render(person, myHelpers);\\n\\n$(\\\"#person\\\").html(html);\\nfunction toUpper(val) {...}\\n\\nvar myHelpers = {color: \\\"red\\\", format: toUpper};\\n\\nvar html = myTmpl.render(person, myHelpers);\\n\\n\\n {{:~format(name)}}\\n\\n\\nClick Try it and change the color to \\\"green\\\"…\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Passing an array to render(), but without iteration.\",\r\n \"text\": \"Passing an array to render(), but without iteration.\\nWhen rendering an array, an additional optional boolean parameter, true, can be passed to the render() method, in order to prevent iteration.\\n\"\r\n },\r\n {\r\n \"_type\": \"api\",\r\n \"title\": \"template.render(data, helpersOrContext, noIteration)\",\r\n \"text\": \"template.render(data, helpersOrContext, noIteration)\\nRender a template against data, along with helpers/context (and determine iteration behavior with array data). Return a string. \\nRender template against data, pass in helpers, and specify iteration behavior\\n\\nvar html = myTmpl.render(data, helpers, true);\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"By passing in true as the third ‘noIteration’ parameter (or as second parameter if no helpersOrContext are passed), the template renders just once, with the array itself as current data, rather than rendering once for each item in the array.\\nWithin the template, {{for}} (or equivalently {{for #data}}) can be used to iterate over the array, as in the following example:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"template.render(array, helpers, noIteration):\",\r\n \"text\": \"template.render(array, helpers, noIteration):\\n\\n\\n\\n \\n \\n \\n {{for}}\\n \\n {{/for}}\\n \\n
        \\n {{:#data.length}} people\\n
        \\n {{:name}}\\n
        \\n\\nvar myTmpl = $.templates(\\\"#personTmpl\\\");\\n\\nvar people = [\\n {\\n name: \\\"Adriana\\\"\\n },\\n {\\n name: \\\"Robert\\\"\\n }\\n];\\n\\nvar html = myTmpl.render(people, true);\\n\\n$(\\\"#peopleList\\\").html(html);\\nCode:\\nvar html = myTmpl.render(people, true);\\n\\nTemplate:\\n\\n \\n \\n {{for}}\\n \\n {{/for}}\\n \\n
        \\n {{:#data.length}} people\\n
        \\n {{:name}}\\n
        \\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Alternative compact syntax for render() call\",\r\n \"text\": \"Alternative compact syntax for render() call\\nThe compiled template is in fact itself a function, equivalent to its own render() method.\\nThis means that any render() call can be replaced by an equivalent (but more compact) syntax, as shown in the following example:\\nvar html = myTmpl(people, helpers, true);\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"See also:\",\r\n \"text\": \"See also:\\n\"\r\n }\r\n ]\r\n },\r\n \"d.render\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"$.render.myTmpl()\",\r\n \"text\": \"$.render.myTmpl()\\nIf a template has been registered as a named template:\\n$.templates(\\\"myTmpl\\\", \\\"#personTmpl\\\");\\n\\nor\\n$.templates(\\\"myTmpl\\\", \\\"some markup string\\\");\\n\\n…then you can call the render() method of the template without needing to hold on to the compiled template object returned from $.templates(...).\\nJust call $.render.myTmpl(...), or $.render[\\\"myTmpl\\\"](...)\\n(Note: there is also an alternative syntax for rendering a named template: $.templates.myTmpl(...);)\\n\"\r\n },\r\n {\r\n \"_type\": \"api\",\r\n \"title\": \"$.render.myTmpl(data, helpersOrContext, noIteration)\",\r\n \"text\": \"$.render.myTmpl(data, helpersOrContext, noIteration)\\nRender a template against data. Return a string.(Optionally provide helpers/context, and specify iteration behavior). \\nRender template against data. Optionally pass in helpers and specify iteration behavior.\\n\\nvar html = $.render.myTmpl(data, helpers, true);\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Here is an example:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"$.render.personTmpl(...):\",\r\n \"text\": \"$.render.personTmpl(...):\\n\\n\\n\\n \\n \\n {{:~format(name)}}\\n \\n \\n\\nfunction toUpper(val) { return val.toUpperCase(); }\\n\\n$.templates(\\\"personTmpl\\\", \\\"#personTemplate\\\");\\n\\nvar person = {\\n name: \\\"Adriana\\\"\\n };\\n\\nvar myHelpers = {color: \\\"red\\\", format: toUpper};\\n\\nvar html = $.render.personTmpl(person, myHelpers);\\n\\n$(\\\"#person\\\").html(html);\\n\\n$.templates(\\\"personTmpl\\\", \\\"#personTemplate\\\");\\n\\nvar html = $.render.personTmpl(person);\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"See also:\",\r\n \"text\": \"See also:\\n\"\r\n }\r\n ]\r\n },\r\n \"db.render\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"$(\\\"#myTmpl\\\").render()\",\r\n \"text\": \"$(\\\"#myTmpl\\\").render()\\nIf a template has been registered using a script block:\\n\\n\\n…then you can call the render() method of the template without needing to hold on to the compiled template object returned from $.templates(...), and without registering a named template.\\nJust call $(\\\"#myTmpl\\\").render(...)\\n\"\r\n },\r\n {\r\n \"_type\": \"api\",\r\n \"title\": \"$(tmplSelector).render(data, helpersOrContext, noIteration)\",\r\n \"text\": \"$(tmplSelector).render(data, helpersOrContext, noIteration)\\nRender a template against data. Return a string.(Optionally provide helpers/context, and specify iteration behavior). \\nRender template against data. Optionally pass in helpers and specify iteration behavior.\\n\\nvar html = $(\\\"#myTmpl\\\").render(myData, myHelpers, true);\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Here is an example:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"$(\\\"#personTemplate\\\").render(...):\",\r\n \"text\": \"$(\\\"#personTemplate\\\").render(...):\\n\\n\\n\\n \\n \\n {{:name}}\\n \\n \\n\\nvar person = {\\n name: \\\"Adriana\\\"\\n };\\n\\nvar html = $(\\\"#personTemplate\\\").render(person);\\n\\n$(\\\"#person\\\").html(html);\\n\\n\\nvar html = $(\\\"#personTemplate\\\").render(person);\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"See also:\",\r\n \"text\": \"See also:\\n\"\r\n }\r\n ]\r\n },\r\n \"compiletmpl\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"(See also Registering templates: The $.views.templates() API.)\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Defining templates\",\r\n \"text\": \"Defining templates\\nTo define a template you need to provide the markup for the template. JsRender will convert (compile) the markup into a JavaScript function – the ‘render’ function for your template. In fact for convenience, JsRender creates a template object which has a template.render() method which is the compiled function.\\nThere are two ways to create a template:\\n\\nPass the markup string to the $.templates() method\\nDeclare the template in a script block with type=\\\"text/x-jsrender\\\" (or at least a type other than the default text/javascript), then pass the jQuery selector for the script block to the $.templates() method\\n\\nIn either case, the $.templates() method will compile a template object, and optionally register it by name.\\nHere is an example of the first approach:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Registering a template from a template markup string:\",\r\n \"text\": \"Registering a template from a template markup string:\\n\\nvar myTmpl = $.templates(\\\"Name: {{:name}} \\\");\\n\\nvar people = [\\n {name: \\\"Adriana\\\"},\\n {name: \\\"Robert\\\"}\\n];\\n\\nvar html = myTmpl.render(people);\\n\\n$(\\\"#peopleList\\\").html(html);\\nWe pass our markup string to the $.templates() method:\\nvar myTmpl = $.templates(\\\" {{:name}} \\\");\\n\\nthen call the render() method on the returned template object:\\nvar html = myTmpl.render(people);\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"And here is an example of the second:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Registering a template declared in script block:\",\r\n \"text\": \"Registering a template declared in script block:\\n\\n\\n\\n {{:name}}\\n\\nvar myTmpl = $.templates(\\\"#personTemplate\\\");\\n\\nvar people = [\\n {name: \\\"Adriana\\\"},\\n {name: \\\"Robert\\\"}\\n];\\n\\nvar html =myTmpl.render(people);\\n\\n$(\\\"#peopleList\\\").html(html);\\nThis time we put our markup in a script block with type=\\\"text/x-jsrender\\\"\\n\\n\\nand then in the code we call the $.templates() method with a jQuery selector for that script block:\\nvar myTmpl = $.templates(\\\" {{:name}} \\\");\\n\\nThen as before we call the render() method on the returned template object:\\nvar html = myTmpl.render(people);\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"The first approach above has the advantage of keeping your template declaration independent of the HTML markup that you are loading into the browser. Indeed you may want to provide the template markup strings for your templates in different application-specific ways, such as loading the string from the server (using a script file or text or html file), creating ‘computed’ template markup strings on the fly, etc.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Example of fetching the markup string from the server\",\r\n \"text\": \"Example of fetching the markup string from the server\\nHere is a simple example of fetching the markup string from the server. We load a .../person.js file from the server which registers a named \\\"person\\\" template.\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Fetching a script file from the server, which registers a named template from a string\",\r\n \"text\": \"Fetching a script file from the server, which registers a named template from a string\\n\\n$.getScript(\\\"//www.jsviews.com/samples/resources/templates/person.js\\\", function() {\\n var html = $.render.person(people);\\n $(\\\"#peopleList\\\").html(html);\\n });\\n\\nvar people = [\\n {\\n name: \\\"Adriana\\\"\\n },\\n {\\n name: \\\"Robert\\\"\\n }\\n];\\nWe load the person.js script from the server, which registers a named \\\"person\\\" template:\\n$.templates(\\\"person\\\", \\\" {{:name}} \\\");\\n\\nAs soon as the script is loaded, we call the render(...) method for the registered template:\\n$.getScript(\\\".../person.js\\\", function() {\\n var html = $.render.person(people);\\n $(\\\"#peopleList\\\").html(html);\\n });\\n\\nNote: For a more sophisticated example of lazy loading of scripts for registering templates, see the remote templates sample.\\n\\n$.templates(\\\"person\\\", \\\" {{:name}} \\\");\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"And here is a variant of the same sample, where we fetch a text file containing the template markup:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Registering a named template using markup fetched from the server in a text file\",\r\n \"text\": \"Registering a named template using markup fetched from the server in a text file\\n\\n\\nvar personTemplate;\\n\\n$.get(\\\"resources/templates/person.txt\\\", function(value) {\\n personTemplate = $.templates(value);\\n var html = personTemplate.render(people);\\n $(\\\"#peopleList\\\").html(html);\\n});\\n\\nvar people = [\\n {name: \\\"Adriana\\\"},\\n {name: \\\"Robert\\\"}\\n];\\nThe markup string is fetched in an AJAX request (the person.txt file).\\n {{:name}}\\n\\nAs soon as the request returns, we use the markup string to compile the personTemplate object. This time we will not register it as a named template, but instead directly call the render(...) method of the returned personTemplate object:\\n$.get(\\\"...person.txt\\\", function(value) {\\n personTemplate = $.templates(value);\\n var html = personTemplate.render(people);\\n $(\\\"#peopleList\\\").html(html);\\n});\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"And here is the second approach:\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"For additional details and scenarios see:\",\r\n \"text\": \"For additional details and scenarios see:\\nRegistering templates: The $.views.templates() API\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"See also:\",\r\n \"text\": \"See also:\\n\"\r\n }\r\n ]\r\n },\r\n \"d.templates\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"$.templates() is used to register or compile templates. See Using templates for an overview, and simple examples.\\nThis topic provides more details.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Simple scenarios\",\r\n \"text\": \"Simple scenarios\\n$.templates(...) is powerful and flexible. You can use it for many scenarios, including the following:\\n\\nCompile a template from a string\\nGet a template object for a template declared in a script block\\nRegister a template (from either a string or a script block declaration) as a named template\\nGet a template object for a previously registered named template\\nOn Node.js: Get a template object for a template declared as a file on the file-system (see File-based templates on Node.js).\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"api\",\r\n \"title\": \"$.templates(...)\",\r\n \"text\": \"$.templates(...)\\nCreate one or more compiled templates – optionally registered as named templates\\nCompile a template from a string or selector, and return the template object\\n\\nvar myTemplate = $.templates(myMarkupString);\\n\\nRegister a named template from a string or selector\\n\\n$.templates(\\\"myTemplateName\\\", myMarkupString);\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Compile a template from a string\",\r\n \"text\": \"Compile a template from a string\\n\\n\\nvar myTmpl = $.templates(\\\"Name: {{:name}}\\\");\\n\\nvar person = {name: \\\"Robert\\\"};\\n\\nvar html = myTmpl.render(person);\\n\\n$(\\\"#peopleList\\\").html(html);\\n\\nvar myTmpl = $.templates(\\\"Name: {{:name}}\\\");\\n\\nvar html = myTmpl.render(person);\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Get template object for script block template\",\r\n \"text\": \"Get template object for script block template\\n\\n\\n\\n {{:name}}\\n\\nvar myTmpl = $.templates(\\\"#personTemplate\\\");\\n\\nvar person = {name: \\\"Robert\\\"};\\n\\nvar html = myTmpl.render(person);\\n\\n$(\\\"#peopleList\\\").html(html);\\n\\n\\nvar myTmpl = $.templates(\\\"#personTemplate\\\");\\n\\nvar html = myTmpl.render(person);\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Register named template from a string\",\r\n \"text\": \"Register named template from a string\\n\\n$.templates(\\\"personTmpl\\\", \\\"Name: {{:name}}\\\");\\n\\nvar person = {name: \\\"Robert\\\"};\\n\\nvar html = $.render.personTmpl(person);\\n\\n$(\\\"#peopleList\\\").html(html);\\n\\n$.templates(\\\"personTmpl\\\", \\\"Name: {{:name}}\\\");\\n\\nvar html = $.render.personTmpl(person);\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Register named template from script block\",\r\n \"text\": \"Register named template from script block\\n\\n\\n\\n {{:name}}\\n\\n$.templates(\\\"personTmpl\\\", \\\"#personTemplate\\\");\\n\\nvar person = {name: \\\"Robert\\\"};\\n\\nvar html = $.render.personTmpl(person);\\n\\n$(\\\"#peopleList\\\").html(html);\\n\\n$.templates(\\\"personTmpl\\\", \\\"#personTemplate\\\");\\n\\nvar html = $.render.personTmpl(person);\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Register multiple templates in one call\",\r\n \"text\": \"Register multiple templates in one call\\nYou can register multiple named templates in one call to $.templates() as follows:\\n\"\r\n },\r\n {\r\n \"_type\": \"api\",\r\n \"title\": \"$.templates(namedTemplates)\",\r\n \"text\": \"$.templates(namedTemplates)\\nRegister multiple named templates\\n\\n$.templates({\\n personTmpl: \\\"#personTemplate\\\",\\n labelTmpl: \\\"\\\"\\n});\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Registering multiple templates\",\r\n \"text\": \"Registering multiple templates\\n\\n\\n\\n {{include tmpl=\\\"labelTmpl\\\"/}} {{:name}}\\n\\n$.templates({\\n personTmpl: \\\"#personTemplate\\\",\\n labelTmpl: \\\"Name:\\\"\\n});\\n\\nvar person = {name: \\\"Robert\\\"};\\n\\nvar html = $.render.personTmpl(person);\\n\\n$(\\\"#peopleList\\\").html(html);\\n\\n\\n$.templates({\\n personTmpl: \\\"#personTemplate\\\",\\n labelTmpl: \\\"\\\"\\n});\\n\\nvar html = $.render.personTmpl(person);\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Get a template object for a named template\",\r\n \"text\": \"Get a template object for a named template\\nYou can get the template object for a previously registered named template as follows:\\nvar myTemplate = $.templates.myTemplateName; // or $.templates[\\\"myTemplateName\\\"]\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Unregister a named template\",\r\n \"text\": \"Unregister a named template\\nTo unregister a previously registered named template, pass null to $.templates():\\n$.templates(\\\"myTemplateName\\\", null);\\n// Named template \\\"myTemplateName\\\" is no longer registered\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Advanced scenarios: Associating private resources with templates\",\r\n \"text\": \"Advanced scenarios: Associating private resources with templates\\n$.templates() can also be used for the following more advanced scenarios:\\n\\nCompile a template, (or multiple templates) along with specified resources to be available only within that template\\nCompile one or more templates to be added to the set of private resources of another (already compiled) template\\n\\nYou can use $.templates() to compile or register not only a template, but in addition some helpers, converters, custom tags or nested sub-templates, to be made available to the new template as private resources.\\nNote that as an alternative you can register resources (helpers, converters, custom tags or templates) globally, using $.views.helpers(), $.views.converters(), $.views.tags(), or $.templates() – rather than making them private to the template that needs to reference them.\\n\"\r\n },\r\n {\r\n \"_type\": \"api\",\r\n \"title\": \"$.templates(...) — associating resources\",\r\n \"text\": \"$.templates(...) — associating resources\\nCompile a template, along with specified resources to be available only within this template\\n\\nvar myTmpl = $.templates({\\n markup: \\\"...\\\",\\n helpers: {...},\\n tags: {...}\\n ...\\n});\\n\\nRegister a named template, along with specified resources available only within that template\\n\\n$.templates(\\\"myTmpl\\\", {\\n markup: \\\"...\\\",\\n helpers: {...},\\n tags: {...}\\n ...\\n});\\n\\nRegister named templates as private resources for a 'parent template'\\n\\n$.templates(namedTemplates, parentTemplate);\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Register a named template along with specified resources\",\r\n \"text\": \"Register a named template along with specified resources\\n\\n\\n\\n {{upper:~append(\\\"Mr \\\", name)}}\\n\\n// Register a template along with a converter and a helper that it will use.\\n// These resources are private to the template, rather than being registered\\n// globally using $.views.converters or $.views.helpers\\n$.templates(\\\"personTmpl\\\", {\\n markup: \\\"#personTemplate\\\",\\n converters: {\\n upper: function(val) {return val.toUpperCase();}\\n },\\n helpers: {\\n append: function(a, b) {return a + b;}\\n }\\n});\\n\\nvar person = {name: \\\"Robert\\\"};\\n\\nvar html = $.render.personTmpl(person);\\n\\n$(\\\"#peopleList\\\").html(html);\\nA converter and a helper are registered as private resources for the personTmpl named template.\\n$.templates(\\\"personTmpl\\\", {\\n markup: \\\"#personTemplate\\\",\\n converters: {\\n upper: function(val) {return val.toUpperCase();}\\n },\\n helpers: {\\n append: function(a, b) {return a + b;}\\n }\\n});\\n\\nThey are accessed within the personTmpl\\n\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Adding templates as private resources for a parent template\",\r\n \"text\": \"Adding templates as private resources for a parent template\\nYou can pass in an existing template as an additional parentTemplate parameter, on any call to $.templates(...). In that way the template you are registering becomes a ‘private template resource’ for the parentTemplate.\\nHere is an example:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Add a \\\"labelTmpl\\\" template resource as a 'sub template' – a private resource for an existing \\\"personTemplate\\\"\",\r\n \"text\": \"Add a \\\"labelTmpl\\\" template resource as a 'sub template' – a private resource for an existing \\\"personTemplate\\\"\\n\\n\\n\\n {{include tmpl=\\\"labelTmpl\\\"/}} {{:name}}\\n\\nvar personTmpl = $.templates(\\\"#personTemplate\\\");\\n\\n$.templates(\\\"labelTmpl\\\", \\\"Name: \\\", personTmpl);\\n\\nvar person = {name: \\\"Robert\\\"};\\n\\nvar html = personTmpl.render(person);\\n\\n$(\\\"#peopleList\\\").html(html);\\n\\n$.templates(\\\"labelTmpl\\\", \\\"Name: \\\", personTmpl);\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Debug a template by including a debugger; statement\",\r\n \"text\": \"Debug a template by including a debugger; statement\\nAs a technique for debugging compiled templates, you can temporarily set the template option debug: true:\\n$.templates({\\n myTmpl: {\\n markup: \\\"...\\\",\\n debug: true // This option will add a debugger; statement to the compiled template\\n }\\n});\\n\\nThe result will be to include a debugger; statement at the beginning of the compiled template, which will behave as a breakpoint when debugging, and will facilitate understanding, or stepping through, the compiled template.\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"See also:\",\r\n \"text\": \"See also:\\n\"\r\n }\r\n ]\r\n },\r\n \"jsrregister\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"\",\r\n \"text\": \"\"\r\n }\r\n ]\r\n },\r\n \"tags\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"(See also Registering tags: The $.views.tags() API.)\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"What is a custom tag?\",\r\n \"text\": \"What is a custom tag?\\nJsRender custom tags are named tags {{mytag ...}}, which you can register, and then use in your templates.\\nA tag renders itself as part of the template output. You determine how it renders, generally by specifying either a function as render() method or a template, when you declare your custom tag.\\nThe render() method, or the template, can access both unnamed arguments (args) and named parameters (props) and , as in:\\n{{mytag arg0 arg1 namedProp1=xxx namedProp2=yyy}} ... {{/mytag}}\\n\\nIn fact it can also access the current data item – or even the whole hierarchy of views and data…\\nNote: When you also use JsViews, custom tags acquire a whole new dimension. – They become tag controls, and you can build rich and complex single page apps cleanly and simply using custom tag controls – following an MVP or MVVM coding pattern.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Registering a custom tag\",\r\n \"text\": \"Registering a custom tag\\nTo register a custom tag, you call $.views.tags(...):\\n$.views.tags(\\\"mytag\\\", tagOptions)\\n\\nYou provide a tagOptions object, whose properties will typically include a render: tagRenderFn (function to be used as render() method) and/or a template: tagTemplate (template to be rendered – markup string, selector string or template object).\\nFor the simple case where the only option you need to specify is a render() method, you can provide the function directly:\\n$.views.tags(\\\"mytag\\\", tagRenderFn);\\n\\nOr if you only want to provide a template markup string, to show how it renders, you can again provide it directly:\\n$.views.tags(\\\"mytag\\\", tagTemplate);\\n\\nHere is an example of a simple custom tag using just a function:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"A custom tag using just a render() method\",\r\n \"text\": \"A custom tag using just a render() method\\n\\n\\n\\n This is the title:{{boldp title /}}\\n\\n// Render method for the tag\\nfunction renderBoldP(value) {\\n return \\\"\\\" + value + \\\"\\\";\\n}\\n\\n$.views.tags(\\\"boldp\\\", renderBoldP); // Provide just a render method\\n\\nvar team = {\\n title: \\\"The A Team\\\"\\n};\\n\\nvar html = $(\\\"#teamTemplate\\\").render(team);\\n\\n$(\\\"#team\\\").html(html);\\n// Render method for the tag\\nfunction renderBoldP(value) {\\n return \\\"

        \\\" + value + \\\"

        \\\";\\n}\\n\\n$.views.tags(\\\"boldp\\\", renderBoldP); // Provide just a render method\\n\\nAlternatively we could have written:\\n$.views.tags(\\\"boldp\\\", {\\n render: renderBoldP); // Provide just a render method\\n});\\n\\n\\nUsing the tag\\n\\nThis is the title:{{boldp title /}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"And here is the equivalent sample using just a template:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"A custom tag using just a template\",\r\n \"text\": \"A custom tag using just a template\\n\\n\\n\\n This is the title:{{boldp title /}}\\n\\n// Template markup string for the tag\\nvar tagTemplate = \\\"{{:}}\\\";\\n\\n$.views.tags(\\\"boldp\\\", tagTemplate); // Provide just a template markup string\\n\\nvar team = {\\n title: \\\"The A Team\\\"\\n};\\n\\nvar html = $(\\\"#teamTemplate\\\").render(team);\\n\\n$(\\\"#team\\\").html(html);\\n// Template markup string for the tag\\nvar tagTemplate = \\\"

        {{:}}

        \\\";\\n\\n$.views.tags(\\\"boldp\\\", tagTemplate); // Provide just a template markup string\\n\\nAlternatively we could have written:\\n$.views.tags(\\\"boldp\\\", {\\n template: tagTemplate; // Provide just a template markup string\\n});\\n\\n\\nUsing the tag\\n\\nThis is the title:{{boldp title /}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Accessing unnamed arguments, named parameters, data, etc. within the render() method\",\r\n \"text\": \"Accessing unnamed arguments, named parameters, data, etc. within the render() method\\nThe this pointer within the tag render() method is the instance of the tag, and can be used to access parameters, data, view hierarchy, and more. Most of the useful context is provided via this.tagCtx. (See tagCtx object.)\\nIn particular, unnamed arguments can be accessed via tagCtx.args, and named parameters via tagCtx.props.\\nHere is tag with two arguments and one named parameter:\\n{{sometag title name mode=\\\"edit\\\"}}\\n\\nFrom within the render() method of sometag, you can access title and name as this.tagCtx.args[0] and this.tagCtx.args[1]. And you can access mode as this.tagCtx.props.mode.\\nIn addition to being accessible as tagCtx.args, unnamed arguments are also passed directly as arguments to the render() method (if your tag is using one):\\nfunction sometagRenderMethod(title, name) {\\n // Here, this.tagCtx.args[1] and the name argument are the same thing\\n}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Accessing context within the render() method\",\r\n \"text\": \"Accessing context within the render() method\\n\\n\\n\\n {{sometag title name mode=\\\"edit\\\"/}}\\n\\n// Render method for the tag\\nfunction sometagRenderMethod(title, name) {\\n var parentData = this.tagCtx.view.data;\\n\\n return \\\"title: \\\" + title + \\\"\\\" // Get argument passed to render method\\n + \\\"parentData.title: \\\" + this.tagCtx.view.data.title + \\\"\\\" // Get title from parent context\\n + \\\"args[1]: \\\" + this.tagCtx.args[1] + \\\"\\\" // Get argument from args[]\\n + \\\"mode: \\\" + this.tagCtx.props.mode; + \\\"\\\"// Get named parameter from props\\n}\\n\\n$.views.tags(\\\"sometag\\\", sometagRenderMethod); // Provide just a render method\\n\\nvar team = {\\n title: \\\"theTitle\\\",\\n name: \\\"theName\\\"\\n};\\n\\nvar html = $(\\\"#teamTemplate\\\").render(team);\\n\\n$(\\\"#team\\\").html(html);\\n// Render method for the tag\\nfunction sometagRenderMethod(title, name) {\\n var parentData = this.tagCtx.view.data;\\n\\n return\\n \\\"title: \\\" ... title ... // Get argument passed to render method\\n + \\\"parentData.title: \\\" ... this.tagCtx.view.data.title ... // Get title from parent context\\n\\n + \\\"args[1]: \\\" ... this.tagCtx.args[1] ... // Get argument from args[]\\n + \\\"mode: \\\" ... this.tagCtx.props.mode; // Get named parameter from props\\n}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Accessing arguments, named parameters, data, etc. from the tag template\",\r\n \"text\": \"Accessing arguments, named parameters, data, etc. from the tag template\\nWithin the template, the tag instance can be accessed as ~tag, and so unnamed arguments and named parameters are obtained using ~tagCtx.args[...] and ~tagCtx.props...\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Accessing context from the tag template\",\r\n \"text\": \"Accessing context from the tag template\\n\\n\\n\\n {{sometag title name mode=\\\"edit\\\"/}}\\n\\n// Template markup for the tag\\nvar sometagTemplate =\\n \\\"title: {{:}}\\\" // The data context within the tag is the first argument, title\\n + \\\"title (#data): {{:#data}}\\\" // Equivalent unabbreviated syntax for current data\\n + \\\"parentData.title: {{:~tagCtx.view.data.title}}\\\" // Get title from parent context\\n + \\\"args[1]: {{:~tagCtx.args[1]}}\\\" // Get argument from args[]\\n + \\\"mode: {{:~tagCtx.props.mode}}\\\"; // Get named parameter from props\\n\\n$.views.tags(\\\"sometag\\\", sometagTemplate ); // Provide just a template markup string\\n\\nvar team = {\\n title: \\\"theTitle\\\",\\n name: \\\"theName\\\"\\n};\\n\\nvar html = $(\\\"#teamTemplate\\\").render(team);\\n\\n$(\\\"#team\\\").html(html);\\n// Template markup for the tag\\nvar sometagTemplate =\\n \\\"title: {{:}}\\\" // The data context within the tag is the first argument, title\\n + \\\"title (#data): {{:#data}}\\\" // Equivalent unabbreviated syntax for current data\\n + \\\"parentData.title: {{:~tagCtx.view.data.title}}\\\" // Get title from parent context\\n\\n + \\\"args[1]: {{:~tagCtx.args[1]}}\\\" // Get argument from args[]\\n + \\\"mode: {{:~tagCtx.props.mode}}\\\"; // Get named parameter from props\\\";\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Accessing and rendering wrapped block content, in a custom tag\",\r\n \"text\": \"Accessing and rendering wrapped block content, in a custom tag\\nA common requirement is to define a custom tag to be used as a block tag, which renders itself by wrapping the rendered block content with other markup.\\nFor example, a boldp tag which wraps its content as:

        ...

        :\\n{{boldp}}\\n This is inside our block content:
        \\n {{:title}}\\n{{/boldp}}\\n\\nBlock content, using a render() method:\\nIn a render() method, the block content can be included in the rendered output using:\\n... this.tagCtx.render() ...\\n\\n(For advanced scenarios the block content is also available as a compiled template object: tagCtx.content, so can be rendered using tagCtx.content.render(). See template as fallback sample below)\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Rendering block content from a custom tag render() method\",\r\n \"text\": \"Rendering block content from a custom tag render() method\\n\\n\\n\\n This is outside our block content:
        \\n {{boldp}}\\n This is inside our block content:
        \\n {{:title}}\\n {{/boldp}}\\n\\nfunction renderBoldP(val) {\\n return \\\"\\\" + this.tagCtx.render() + \\\"\\\";\\n}\\n\\n$.views.tags(\\\"boldp\\\", renderBoldP); // User renderBoldP() as render method\\n\\nvar team = {\\n title: \\\"The A Team\\\"\\n};\\n\\nvar html = $(\\\"#teamTemplate\\\").render(team);\\n\\n$(\\\"#team\\\").html(html);\\nTag render method:\\nfunction renderBoldP(val) {\\n //To render the block content, we call this.tagCtx.render()\\n return \\\"

        \\\" + this.tagCtx.render() + \\\"

        \\\";\\n}\\n\\nUsing the tag:\\nThis is outside our block content: ...\\n{{boldp}}\\n This is inside our block content: ...\\n {{:title}}\\n{{/boldp}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"When using tagCtx.render() without arguments, the data context within the block content is the same as the data context outside our custom tag. However by passing an argument to tagCtx.render(myData) the inner data context can be moved to the chosen data.\\nThe following sample shows a custom {{runningTotal}} tag which renders an array of lineItems (with a column for each property), and provides a running total of one of the columns.\\nIt uses a render() method to access tag arguments and named parameters, and iterate over the lineItems array. It renders a row for each lineItem, using the code:\\nret += this.tagCtx.render(lineItem, {total: totalVal});\\n\\nHere, the row is rendered using the block content as template – with the lineItem passed in as data context. The running total totalVal is provided as contextual helper: ~total.\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"A {{runningTotal}} custom tag, using a render() method \",\r\n \"text\": \"A {{runningTotal}} custom tag, using a render() method \\n\\n\\n\\n \\n \\n {{runningTotal lineItems totalColumn=\\\"quantity\\\"}} \\n \\n \\n \\n \\n {{/runningTotal}}\\n
        QuantityTotal quantity
        {{:quantity}}{{:~total}}
        \\n\\nfunction renderLineItems(array) {\\n var lineItem,\\n ret = \\\"\\\",\\n totalVal = 0, // Initialize ~total to 0 before rendering\\n totalCol = this.tagCtx.props.totalColumn; // The column/property to use for running total\\n for (var i = 0; i < array.length; i++) { // Iterate over array and render a row for each lineItem \\n lineItem = array[i];\\n totalVal += lineItem[totalCol]; // Compute running total\\n ret += this.tagCtx.render(lineItem, {total: totalVal}); // Add the row for this lineItem - using the block content\\n // as template, and passing lineItem as current data and totalVal as helper: ~total\\n }\\n return ret;\\n}\\n\\n$.views.tags(\\\"runningTotal\\\", renderLineItems); // Use renderLineItems() as render method\\n\\nvar data = {\\n lineItems: [\\n {category: \\\"book\\\", quantity: 2, price: 3.40},\\n {category: \\\"grocery\\\", quantity: 5, price: 1.01},\\n {category: \\\"grocery\\\", quantity: 2, price: 13.10},\\n {category: \\\"book\\\", quantity: 1, price: 12.50}\\n ]\\n};\\nvar html = $(\\\"#myTmpl\\\").render(data);\\n\\n$(\\\"#lineItems\\\").html(html);\\nYou call the custom {{runningTotal}} tag like this:\\n{{runningTotal lineItems totalColumn=\\\"quantity\\\"}}\\n ...{{:quantity}}\\n ...{{:~total}}\\n{{/runningTotal}}\\n\\nAnd the render() method code accesses context (this.tagCtx) to get at the arguments and named parameters… :\\n$.views.tags(\\\"runningTotal\\\", function renderLineItems(array) {\\n ...\\n totalVal = 0; // Initialize ~total to 0 before rendering\\n totalCol = this.tagCtx.props.totalColumn; // The column/property to use for running total\\n for (var i = 0; i < array.length; i++) {\\n lineItem = array[i];\\n totalVal += lineItem[totalCol]; // Compute running total\\n ret += this.tagCtx.render(lineItem, {total: totalVal}); // Add the row for this lineItem - using the block content as\\n // template. Pass lineItem as data and totalVal as helper: ~total\\n }\\n ...\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Block content, using a template:\\nTo render block content declaratively within a custom tag template, use:\\n{{include tmpl=#content/}}\\n\\nor equivalently:\\n{{include tmpl=~tagCtx.content/}}\\n\\nHere is a modified {{boldp}} sample using a custom template instead of a render() method.\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Rendering block content from a custom tag template\",\r\n \"text\": \"Rendering block content from a custom tag template\\n\\n\\n\\n This is outside our block content:
        \\n {{boldp}}\\n This is inside our block content:
        \\n {{:title}}\\n {{/boldp}}\\n\\n$.views.tags(\\\"boldp\\\", {\\n template: \\\"{{include tmpl=#content/}}\\\"\\n});\\n\\nvar team = {\\n title: \\\"The A Team\\\"\\n};\\n\\nvar html = $(\\\"#teamTemplate\\\").render(team);\\n\\n$(\\\"#team\\\").html(html);\\nTo render block content, we use {{include tmpl=#content/}}\\ntemplate: \\\"

        {{include tmpl=#content/}}

        \\\"\\n\\n(The syntax #content is an example of a view path – equivalent to #view.content.)\\nThe content property on the view object is a compiled template for the block content, which is also available as the content property on the tagCtx.\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Here, the default data context within the block content is the same as the data context outside our custom tag (as was the case in the previous {{boldp}} sample). However by providing an argument to the {{include...}}, as in {{include myData tmpl=#content/}}, the inner data context can be moved to the chosen data.\\n(Note: To be precise, the default data in the two samples is different. When using tagCtx.render() the outer context is outside our {{boldp}} tag. Whereas when using {{include}}, it is outside the {{include}} and within the {{boldp}} template. If we provide an argument to the tag: ‘{{mytag someArgument}}…’ then in custom tag template approach the passed-in argument value will be used as default data context.)\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"For further details and examples of custom tags which wrap content, see Rendering wrapped block content\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Custom tags using both a render() method and a template\",\r\n \"text\": \"Custom tags using both a render() method and a template\\nIf there is both a template and a render() method, then the template will only be used if the render() method returns undefined.\\nLet’s take our {{runningTotal}} example using a render() method, but provide a template which will be used as “fallback” rendering for the tag in the case when there are no items to render in the chosen range. We will also provide support for limiting the range of line items by setting start=... end=...:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"A {{runningTotal}} custom tag, with render() method and a template as \\\"fallback\\\"\",\r\n \"text\": \"A {{runningTotal}} custom tag, with render() method and a template as \\\"fallback\\\"\\n\\n\\n\\n start=1 end=4:\\n \\n \\n {{runningTotal lineItems start=1 end=4 totalColumn=\\\"quantity\\\"}} \\n \\n \\n \\n \\n {{/runningTotal}}\\n
        QuantityTotal
        {{:quantity}}{{:~total}}
        \\n\\n start=4 end=5:\\n \\n \\n {{runningTotal lineItems start=4 end=5 totalColumn=\\\"quantity\\\"}} \\n \\n \\n \\n \\n {{/runningTotal}}\\n
        QuantityTotal
        {{:quantity}}{{:~total}}
        \\n\\n$.views.tags(\\\"runningTotal\\\", {\\n render: function(array) {\\n var lineItem,\\n ret = \\\"\\\",\\n totalVal = 0, // Initialize ~total to 0 before rendering\\n props = this.tagCtx.props,\\n totalCol = props.totalColumn; // The column/property to use for running total\\n start = props.start,\\n end = props.end;\\n for (var i=start; iNo line items\\\" // Template for fallback if no line items\\n});\\n\\nvar data = {\\n lineItems: [\\n {category: \\\"book\\\", quantity: 2, price: 3.40},\\n {category: \\\"grocery\\\", quantity: 5, price: 1.01},\\n {category: \\\"grocery\\\", quantity: 2, price: 13.10},\\n {category: \\\"book\\\", quantity: 1, price: 12.50}\\n ],\\n lineItems2: []\\n};\\n\\nvar html = $(\\\"#myTmpl\\\").render(data, {\\n category: function(item, index, items) {\\n return item.category === this.props.category;\\n }\\n});\\n\\n$(\\\"#purchases\\\").html(html);\\nFirst, in the render() method, we will change the original code to test whether the item exists in the array, before rendering the block content.\\nSecondly, we will make sure that when there is an item we do render the block content and not the template. So we call this.tagCtx.content.render(...), rather than this.tagCtx.render(...).\\nThat’s because this.tagCtx.render(...) will actually look to see if there is template associated with the tag, (either a template on the tag definition, or a tmpl property on the tag) – in which case it will render that template and not the block content…\\nfor (var i=start; iNo line items\\\"\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"In the above sample our feature for limiting the range of items by setting start=... end=... is basically identical to the corresponding feature available natively on the {{for}} tag:\\n{{for start=... end=...}}\\n\\nIn fact we can add this feature to our {{runningTotal}} tag for free (along with providing sorting, filtering etc.) by making {{runningTotal}} derive from {{for}}, as baseTag. This will also simplify our code considerably. See Specifying tag inheritance for details and an updated {{runningTotal}} sample.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Custom tags and 'tag controls'\",\r\n \"text\": \"Custom tags and 'tag controls'\\nIf you use JsViews, your custom tag can be developed into a fully functional tag control, with its own lifecycle, properties and methods, etc. It can be used as a presenter according to the MVP pattern.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"For additional details and scenarios see:\",\r\n \"text\": \"For additional details and scenarios see:\\nRegistering tags: The $.views.tags() API\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"See also:\",\r\n \"text\": \"See also:\\n\"\r\n }\r\n ]\r\n },\r\n \"jsrobjects\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"\",\r\n \"text\": \"\"\r\n }\r\n ]\r\n },\r\n \"viewsobject\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"The $.views object provides access to APIs for creating templates, tags, helpers etc.\\n\\n$.views.templates(...) – available also as $.templates(...)\\nUsed for defining templates – see: Registering templates\\n$.views.tags(...)\\nUsed for defining custom tags – see: Registering custom tags\\n$.views.converters(...)\\nUsed for defining converters – see: Registering converters\\n$.views.helpers(...)\\nUsed for defining helpers – see: Registering helpers\\n$.views.viewModels(...)\\nUsed for defining View Models – see: Compiled View Models\\n\\nIt also provides access to:\\n\\n$.views.settings\\nUsed for modifying JsViews settings and options – see: Settings\\n$.views.map(...)\\nUsed for defining custom maps (advanced)\\n$.views.jsviews\\nProvides the version number of the currently loaded JsViews or JsRender library\\n\\n\"\r\n }\r\n ]\r\n },\r\n \"settingsobject\": {\r\n \"sections\": []\r\n },\r\n \"subobject\": {\r\n \"sections\": []\r\n },\r\n \"templateobject\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"The $.templates() API can be used to obtain a compiled template object:\\nvar myTmpl = $.templates(\\\" {{:name}}\\\");\\n\\nThe compiled template object (myTmpl, in the example) provides a number of properties and methods, in particular:\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"The render() method\",\r\n \"text\": \"The render() method\\nvar html = myTmpl.render(person);\\n\\nSee Render a template against data objects or arrays\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"The markup property\",\r\n \"text\": \"The markup property\\nThe declarative markup string for the template (available whether the template was registered by providing a markup string, or by a script block reference).\\nvar test = myTmpl.markup; // \\\" {{:name}}\\\"\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"The compiled template object is actually a render() function\",\r\n \"text\": \"The compiled template object is actually a render() function\\nThe compiled template is itself a function, corresponding to its own render method, so the following two examples are actually equivalent.\\nCalling the render method:\\nvar html = myTmpl.render(person);\\n\\nInvoking the compiled template directly as render method:\\nvar html = myTmpl(person);\\n\\n\"\r\n }\r\n ]\r\n },\r\n \"viewobject\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"JsRender templates render as a view hierarchy.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"A view object has the following properties and methods:\",\r\n \"text\": \"A view object has the following properties and methods:\\n\\ntype property\\ndata property\\nparent property\\nindex property\\ngetIndex() method\\nget(type) method\\ncontent property\\nroot property\\nother properties (tmpl, views, ctx, tag)\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Note: When using JsViews .link() method rather than JsRender’s .render() method, the view objects have additional methods:\\n\\nrefresh()\\ncontents()\\nchildTags()\\nnodes()\\n\\nSee JsViews view object.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Accessing view objects\",\r\n \"text\": \"Accessing view objects\\nThe properties of the current view are accessed declaratively in a template using view paths – such as #parent for the view.parent property.\\nAccessing view objects programmatically is less common in JsRender, but can be useful for example:\\n\\nin a helper function, ~myHelper(), where the this pointer is the current view\\nin the render() method of a custom tag – using this.tagCtx.view\\n\\nNote: In JsViews, accessing view objects programmatically is very common, thanks to the $.view() method. For example in a click handler, $.view(this); returns the corresponding view object.\\nProperties and methods:\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"The type property:\",\r\n \"text\": \"The type property:\\nview.type: string corresponding to the type of view:\\n\\n\\\"data\\\" – for the top-level view from a render() call\\n\\\"array\\\" or \\\"item\\\" – from {{for array}} or {{props object}} (see array and item views)\\n\\\"sometag\\\" – for the view from {{sometag}}...{{sometag}} – for example: \\\"include\\\", \\\"if\\\", \\\"for\\\", \\\"props\\\", \\\"mytag\\\"…\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"The data property:\",\r\n \"text\": \"The data property:\\nview.data: the current data context for the view – as in:\\nvar team = view.data.team; // The team property of the current data object\\n\\nview.data can be accessed declaratively in templates as #data– as in:\\n{{:#data}}\\n{{>#data.description()}}\\n{{for #data.team.members}}...\\n\\nBut note that since #data, the current data context, is the starting point for data paths within templates, the above expressions with `#data’ can be abbreviated to:\\n{{:}}\\n{{>description()}}\\n{{for team.members}} etc.\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"The parent property:\",\r\n \"text\": \"The parent property:\\nview.parent: the parent view (used to step up through views in the hierarchy).\\nvar index = view.parent.index; // The index of the parent view\\n\\nAccessed declaratively as #parent:\\n{{>#parent.data.title()}}... {{!-- accessing data of parent view - view.parent --}}\\n{{if #parent.parent.parent.data.teams.length > 1}}... {{!-- accessing data of view.parent.parent... --}}\\n\\n(See also Accessing parent data)\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"The index property:\",\r\n \"text\": \"The index property:\\nview.index: the view index (only available on item views).\\nvar index = view.index; // The index of the view (for \\\"item\\\" views - otherwise an 'error string')\\n\\nAccessed declaratively as #index:\\n{{if #index > 2}} {{!-- we are in an \\\"item\\\" view --}}\\n {{:#parent.index}}... {{!-- \\\"item\\\" view index (- the parent - since we are inside the 'ifView') --}}\\n{{/if}}\\n\\nNote: On non-“item” views, accessing the index property returns the error message prompt: “For #index in nested block use #getIndex().”\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"The getIndex() method:\",\r\n \"text\": \"The getIndex() method:\\nview.getIndex(): get the index of current “item” view (steps up to nearest item view, and returns the index).\\nvar index = view.getIndex(); // The index of the view\\n\\nAccessed declaratively as #getIndex():\\n{{for teams}}\\n {{for members}}\\n {{if #getIndex() > 0}} {{!-- index of member (- this view is an \\\"item\\\" view for member) --}}\\n {{:#getIndex()}} {{!-- index of member --}}\\n {{/if}}\\n\\n {{:#parent.getIndex()}}... {{!-- index of team (-nearest \\\"item\\\" view of parent is team \\\"item\\\" view) --}}\\n {{/for}}\\n{{/for}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"getIndex() – iterating + grouping by 3\",\r\n \"text\": \"getIndex() – iterating + grouping by 3\\nIf index is a multiple of 3, render new tr, and format index in bold.\\nUse getIndex() to get item index from within if block.\\n\\n\\n{{for members}}\\n {{if #index===0}}\\n \\n{{/for}}\\n\\n
        1:\\n {{else #index%3===0}}\\n
        {{:#getIndex()+1}}:\\n {{else}}\\n {{:#getIndex()+1}}:\\n {{/if}}\\n {{:name}}\\n
        \\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"The get(type) method:\",\r\n \"text\": \"The get(type) method:\\nview.get(type): returns the nearest parent view of type type.\\nvar arrayView = view.get(\\\"array\\\"); // Step through parents to nearest \\\"array\\\" view\\nvar arrayLength = arrayView.data.length; // Get length of data array\\n\\nAccessed declaratively as #get(...):\\n{{for members}}\\n {{if #index+1 === #get(\\\"array\\\").data.length}}\\n The last member in the list\\n {{/if}}\\n{{/for}}\\n\\nNote: An additional signature is available: view.get(true, type) (for advanced scenarios) – which steps down through descendant views (depth first traversal) and returns the first descendant view of type type.\\n{{for members}}\\n {{:name}}\\n{{/for}}\\n{{:#get(true, \\\"item\\\").data.name}} {{!-- get the name of the first member --}}\\n\\nIn using this API it is sometimes necessary to be aware of the processing order. For example in the sample code above, placing {{:#get(true, \\\"item\\\")...}} before {{for members}} will not return any “item” view, since the {{:get(...)...}} is being evaluated during the rendering, and the “item” views for {{for ...}} will not yet have been rendered. (View instantiation is part of rendering, which is a single-pass process.)\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"The content property (for views which wrap inline block content):\",\r\n \"text\": \"The content property (for views which wrap inline block content):\\nview.content: template corresponding to the inline block content.\\nAccessed declaratively as #content:\\nIn the wrapping content scenarios, the tag:\\n{{sometag ... tmpl=\\\"externalTmpl\\\"}}...{{/sometag}}\\n\\nor\\n{{mytag}}...{{/mytag}}\\n\\nwill render with a view which has both a view.tmpl template property and a view.content template property.\\nThe view.content template corresponds to the inline block content, and is used for wrapping that content as in:\\nbefore {{include tmpl=#content /}} after\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"view.content – wrapping content\",\r\n \"text\": \"view.content – wrapping content\\n\\n\\n\\n{{mytag}}\\n
        inside mytag
        \\n{{/mytag}}\\n\\n
        \\n\\n{{mytag tmpl=\\\"externalTmpl\\\"}}\\n
        inside mytag with external tmpl
        \\n{{/mytag}}\\n\\n\\n$.views.tags(\\n \\\"mytag\\\",\\n \\\"startTag {{include tmpl=#content /}} endTag\\\"\\n);\\n\\n$.templates(\\n \\\"externalTmpl\\\",\\n \\\"startTmpl {{include tmpl=#content /}} endTmpl\\\"\\n);\\n\\n$(\\\"#result\\\").html(\\n $.templates(\\\"#myTmpl\\\").render()\\n);\\n\\nmytag:\\n$.views.tags(\\n \\\"mytag\\\",\\n \\\"startTag {{include tmpl=#content /}} endTag\\\"\\n);\\n\\nexternalTmpl:\\n$.templates(\\n \\\"externalTmpl\\\",\\n \\\"startTmpl {{include tmpl=#content /}} endTmpl\\\"\\n);\\n\\nTemplate:\\n{{mytag}}\\n
        inside mytag
        \\n{{/mytag}}\\n\\n
        \\n\\n{{mytag tmpl=\\\"externalTmpl\\\"}}\\n
        inside mytag with external tmpl
        \\n{{/mytag}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"The root property:\",\r\n \"text\": \"The root property:\\nview.root: the root view (top-level ancestor view for this view) – as in:\\nvar topLevelData = view.root.data; // Get the top-level data (obtained from the root view)\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Other view object properties:\",\r\n \"text\": \"Other view object properties:\\nThe following additional properties of the view object are used by JsRender for processing templates:\\n\\ntmpl: the template used to render the view\\nviews: the child views in the view hierarchy\\nctx: object (hash) with the named contextual helpers/template parameters for this view\\ntag: the \\\"mytag\\\" view rendered by a custom tag {{mytag ...}}, has a view.tag property – the instance of the mytag tag object.\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"See also:\",\r\n \"text\": \"See also:\\n\"\r\n }\r\n ]\r\n },\r\n \"tagobject\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Tag object properties and event handlers provided as tag options\",\r\n \"text\": \"Tag object properties and event handlers provided as tag options\\nThe following tag properties and event handlers can be specified as tag options in the $.views.tags() call, when registering a custom tag:\\nTag properties\\n\\nbaseTag\\nflow\\ntemplate\\nbindTo\\nctx\\ncontentCtx\\nargDefault\\n\\nEvent handlers:\\n\\ninit()\\nrender()\\nconvert()\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Additional properties and methods on the tag object\",\r\n \"text\": \"Additional properties and methods on the tag object\\nIn addition to the above properties and handlers set as tag options, the tag object has the following properties and methods:\\nTag properties\\n\\nparent\\nparents\\ntagCtx\\ntagCtxs\\ntagName\\nrendering\\n\\nTag methods\\n\\nctxPrm()\\ncvt()\\ncvtArgs()\\nbndArgs()\\nbase()\\nbaseApply()\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Accessing tag objects\",\r\n \"text\": \"Accessing tag objects\\nThe tag object can be accessed programmatically, for example in event handlers of custom tags, using the this pointer.\\nThe current tag can also be accessed declaratively (in a custom tag template, or in wrapped block content) using ~tag, as in:\\n{{:~tag.parent.tagName}}`\\n\\nIn addition, tag.tagCtx can be accessed declaratively using ~tagCtx, as in:\\n{{:~tagCtx.props.mode}}`\\n\\nTag properties and methods:\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"The parent property\",\r\n \"text\": \"The parent property\\ntag.parent: the parent tag (used to step up through views in the hierarchy).\\nvar index = view.parent.index; // The index of the parent view\\n\\nAccessed declaratively as #parent:\\n{{>#parent.data.title()}}... {{!-- accessing data of parent view - view.parent --}}\\n{{if #parent.parent.parent.data.teams.length > 1}}... {{!-- accessing data of view.parent.parent... --}}\\n\\n(See also Accessing parent data)\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"The parents property\",\r\n \"text\": \"The parents property\\nAdditional detailed documentation to follow…\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"The tagCtx property\",\r\n \"text\": \"The tagCtx property\\nAdditional detailed documentation to follow…\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"The tagCtxs property\",\r\n \"text\": \"The tagCtxs property\\nAdditional detailed documentation to follow…\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"The tagName property\",\r\n \"text\": \"The tagName property\\nAdditional detailed documentation to follow…\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"The rendering property\",\r\n \"text\": \"The rendering property\\nAdditional detailed documentation to follow…\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"The ctxPrm() method\",\r\n \"text\": \"The ctxPrm() method\\nAdditional detailed documentation to follow…\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"The cvt() method\",\r\n \"text\": \"The cvt() method\\nAdditional detailed documentation to follow…\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"The cvtArgs() method\",\r\n \"text\": \"The cvtArgs() method\\nAdditional detailed documentation to follow…\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"The bndArgs() method\",\r\n \"text\": \"The bndArgs() method\\nAdditional detailed documentation to follow…\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"The base() method\",\r\n \"text\": \"The base() method\\nAdditional detailed documentation to follow…\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"The baseApply() method\",\r\n \"text\": \"The baseApply() method\\nAdditional detailed documentation to follow…\\n\"\r\n }\r\n ]\r\n },\r\n \"viewcontextobject\": {\r\n \"sections\": []\r\n },\r\n \"tagcontextobject\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"\\nrender function(data, context, noIteration, parentView, key, onRender) {\\ntmpl function() {\\nargs [] Object, (Array)\\nctx {…} Object\\nindex 0 Number\\nparams {…} Object\\nprops {…} Object\\ntag {…} Object, (Tag)\\nview {…} Object\\n\\nAdditional detailed documentation to follow…\\n\"\r\n }\r\n ]\r\n },\r\n \"node/browserify\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Browserify support for JsRender and JsViews\\nBrowserify lets you create modular JavaScript projects for the browser, using the npm require() pattern for packages/modules.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"JsRender as a Browserify module\",\r\n \"text\": \"JsRender as a Browserify module\\nAfter installing JsRender on the server (using $ npm install jsrender) it can then be included in the Browserify client script bundle, and loaded in the browser.\\nThere are three options for loading JsRender in the browser as a Browserify module:\\n\\nLoad jQuery globally (as a script tag – so window.jQuery is defined), then load JsRender as a module in the Browserify client script bundle:\\nrequire('jsrender'); // Load JsRender as jQuery plugin (attached to global jQuery)\\n\\nLoad both jQuery and JsRender as modules in the Browserify client script bundle:\\nvar $ = require('jquery'); // Load jQuery as a module\\nrequire('jsrender')($); // Load JsRender as jQuery plugin (jQuery instance as parameter)\\n\\nLoad JsRender as a module in the Browserify client script bundle, without loading jQuery at all:\\nvar jsrender = require('jsrender')(); // Load JsRender without jQuery (function call, no parameter)\\n\\n\\nNote: In fact if jQuery is not defined globally, require('jsrender') returns a function.\\nCalling that function without a parameter then loads JsRender without jQuery (and returns the JsRender namespace).\\nAlternatively, calling that function with a reference to a jQuery instance as parameter loads JsRender as a plugin (attached to that jQuery instance) – and returns the jQuery instance.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Example – jQuery loaded globally:\",\r\n \"text\": \"Example – jQuery loaded globally:\\nindex.html:\\n\\n \\n\\n
        \\n \\n\\n\\nsource.js:\\nrequire('jsrender'); // Load JsRender (jQuery is loaded as global)\\nvar tmpl = $.templates('Name: {{:name}}');\\nvar data = {name: 'Jo'};\\nvar html = tmpl.render(data);\\n$('#container').html(html);\\n\\ncommand line:\\nbrowserify ./source.js > ./bundle.js\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Example – jQuery loaded as module:\",\r\n \"text\": \"Example – jQuery loaded as module:\\nindex.html:\\n\\n
        \\n \\n\\n\\nsource.js:\\nvar $ = require('jquery'); // Load jQuery as a module\\nrequire('jsrender')($); // Load JsRender as jQuery plugin (jQuery instance as parameter)\\nvar tmpl = $.templates('Name: {{:name}}');\\nvar data = {name: 'Jo'};\\nvar html = tmpl.render(data);\\n$('#container').html(html);\\n\\ncommand line:\\nbrowserify ./source.js > ./bundle.js\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Example – JsRender without jQuery:\",\r\n \"text\": \"Example – JsRender without jQuery:\\nindex.html:\\n\\n
        \\n \\n\\n\\nsource.js:\\nvar jsrender = require('jsrender')(); // Load JsRender without jQuery\\nvar tmpl = jsrender.templates('Name: {{:name}}');\\nvar data = {name: 'Jo'};\\nvar html = tmpl.render(data);\\ndocument.querySelector('#container').innerHTML = html;\\n\\ncommand line:\\nbrowserify ./source.js > ./bundle.js\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"JsViews as a Browserify module\",\r\n \"text\": \"JsViews as a Browserify module\\nJsViews can also be included in the Browserify client-script bundle, and loaded in the browser.\\nAfter installing on the server (using $ npm install jsviews), call:\\nrequire('jsviews'); // Load JsViews (if jQuery is loaded globally)\\n\\nor – if also loading jQuery as a Browserify module, use:\\nvar $ = require('jquery');\\n...\\nrequire('jsviews')($); // Load JsViews (passing local jQuery instance as a parameter)\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Loading templates as Browserify modules\",\r\n \"text\": \"Loading templates as Browserify modules\\nJsRender includes a Browserify transform: jsrender/tmplify (see below) which allows you also to include your server file-based templates in the client-script bundle generated by Browserify.\\nYou can then access the compiled templates in the browser, as modules.\\nThe exact syntax depends on whether jQuery is loaded globally, loaded as a Browserify module, or not loaded at all.\\n\\nIf jQuery is loaded globally then use:\\nvar tmpl = require('./templates/myTemplate.html'); // Load template (jQuery \\n // is loaded globally)\\nvar html = tmpl.render(myData);\\n...\\n\\nIf jQuery is loaded as a module, use:\\nvar $ = require('jquery');\\n...\\nvar tmpl = require('./templates/myTemplate.html')($); // Load template (local\\n // jQuery as parameter)\\nvar html = tmpl.render(myData);\\n...\\n\\nIf loading JsRender as a module, without jQuery, use:\\nvar jsrender = require('jsrender')(); // function call -- no parameter\\n...\\nvar tmpl = require('./templates/myTemplate.html')(jsrender); // Load template (jsrender\\n // namespace as parameter)\\nvar html = tmpl.render(myData);\\n...\\n\\n\\nNote on relative paths: The ./... paths used to identify bundled templates are always interpreted as relative paths relative to the location of your calling script, which in this case is the Browserify script that created the client bundle. (Note that declaring a templates folder for Express or Hapi does not change the origin of these relative paths).\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Nested templates\",\r\n \"text\": \"Nested templates\\nTemplate inclusion in the bundle can be recursive, so for example if you call require(\\\"./templates/myTemplate.html\\\"); and myTemplate.html includes a nested reference to another template, such as {{include tmpl=\\\"./another/tmpl2.html\\\"/}}, then the client-script bundle will include that template too.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Generating the client bundle\",\r\n \"text\": \"Generating the client bundle\\nIf source.js includes template references such as: var tmpl=require('./some/path/myTemplate.html'), then Browserify generates a client script bundle which will include the referenced templates.\\nBrowserify provides three different ways of generating a bundle.js script from a source.js script, and calling a transform:\\nCommand line:\\nbrowserify -t jsrender/tmplify ./source.js > ./bundle.js\\n\\npackage.json:\\n\\\"browserify\\\": {\\n \\\"transform\\\": [\\n [\\\"jsrender/tmplify\\\"]\\n ]\\n}\\n\\nAPI:\\nbrowserify('./source.js')\\n .transform(require('jsrender/tmplify'))\\n .bundle()\\n .pipe(fs.createWriteStream('./bundle.js'));\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Option: extensions\",\r\n \"text\": \"Option: extensions\\nThe jsrender/tmplify Browserify transform uses a white-space-separated list of extensions: \\\"html jsrender jsr\\\", by default. This means that when you generate a client-script bundle using the tmplify transform, it will treat any .html, .jsrender or .jsr file as a template, and will include the compiled template in the client-script bundle for rendering in the browser.\\nYou can instead specify a different list of file extensions for templates, by using the --extensions or -e option, as in the following examples:\\nbrowserify -t [jsrender/tmplify --extensions 'htm jsrender'] ./source.js > ./bundle.js\\n\\nbrowserify -t [jsrender/tmplify -e 'htm jsrender'] ./source.js > ./bundle.js\\n\\n\\\"browserify\\\": {\\n \\\"transform\\\": [\\n [\\\"jsrender/tmplify\\\", {\\n \\\"extensions\\\": \\\"htm jsrender\\\"\\n }]\\n ]\\n}\\n\\nbrowserify('./source.js')\\n .transform(require('jsrender/tmplify'), {extensions: 'htm jsrender'})\\n .bundle()\\n .pipe(fs.createWriteStream('./bundle.js'));\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Including jQuery and/or JsRender/JsViews in the client-script bundle\",\r\n \"text\": \"Including jQuery and/or JsRender/JsViews in the client-script bundle\\nWhen using Browserify with JsRender on Node.js, you will generally need jQuery and JsRender/JsViews in the client, to render (and optionally data-link) the templates.\\njQuery, JsRender and JsViews are all available as npm/Browserify modules, so you can choose whether to load them globally, using a script block, or as a module. Here are three examples following alternative strategies:\\nLoad jQuery and JsRender/JsViews globally\\n$ is defined as a global variable (window.$, or window.jQuery).\\nUse require(templatePath) to load templates as Browserify modules included in the client-script bundle, as in the following example:\\nindex.html:\\n\\n\\n...\\n\\n\\nsource.js:\\nvar myTmpl = require('./templates/myTemplate.html'); // Include compiled template in client-script bundle\\nvar html = myTmpl(data); // Render using compiled template\\n$('#result').html(html);\\n\\ncommand line:\\nbrowserify -t jsrender/tmplify ./source.js > ./bundle.js\\n\\nSee the JsRender Node Starter project for complete examples:\\n\\nclientcode-hello.js and layout-hello.html using JsRender\\nclientcode-movies.js and layout-movies.html using JsViews.\\n\\nLoad jQuery and JsRender/JsViews as Browserify modules\\nUse var $ = require('jquery') to load jQuery, and require('jsrender')($) or require('jsviews')($) to load JsRender/JsViews.\\nUse require(templatePath)($) to load templates as Browserify modules included in the client-script bundle, as in the following example:\\nindex.html:\\n...\\n\\n\\nsource.js:\\nvar $ = require('jquery');\\nrequire('jsrender')($);\\nvar myTmpl = require('./templates/myTemplate.html')($)\\nvar html = myTmpl(data);\\n$('#result').html(html);\\n\\ncommand line:\\nbrowserify -t jsrender/tmplify ./source.js > ./bundle.js\\n\\nSee:\\n\\nclientcode-hello-browserify.js and layout-hello-browserify.html for an example loading jQuery and JsRender as modules\\nclientcode-hello-browserify2.js and layout-hello-browserify2.html for an example loading JsRender as a module (without jQuery)\\nclientcode-movies-browserify2.js and layout-movies-browserify2.html for an example loading jQuery and JsViews as modules\\n\\nMixed approach: Load jQuery globally, and JsRender/JsViews as a Browserify module\\n$ is defined as a global variable (window.$ or window.jQuery).\\nUse require('jsrender') or require('jsviews') to load JsRender/JsViews.\\nUse require(templatePath) to load templates as Browserify modules included in the client-script bundle, as in the following example:\\nindex.html:\\n\\n...\\n\\n\\nsource.js:\\nrequire('jsrender');\\nvar myTmpl = require('./templates/myTemplate.html');\\nvar html = myTmpl(data);\\n$('#result').html(html);\\n\\ncommand line:\\nbrowserify -t jsrender/tmplify ./source.js > ./bundle.js\\n\\nSee clientcode-movies-browserify.js and layout-movies-browserify.html for an example using JsViews.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Sample code\",\r\n \"text\": \"Sample code\\nFor running code examples using JsRender, Browserify, and the tmplify transform, see the index-express-browserify.js and index-hapi-browserify.js samples in the JsRender Node Starter project.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"See also:\",\r\n \"text\": \"See also:\\nWebpack support\\n\"\r\n }\r\n ]\r\n },\r\n \"node/renderfile\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"JsRender on Node.js provides a shortcut renderFile method, for convenience, to compile and render in one step:\\nvar jsrender = require('jsrender');\\n\\nvar html = jsrender.renderFile('./templates/myTemplate.html', {name: \\\"Jim\\\"});\\n// result: Name: Jim
        \\n\\n\"\r\n }\r\n ]\r\n },\r\n \"node/filetmpls\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"File-based templates\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Defining templates as .html files\",\r\n \"text\": \"Defining templates as .html files\\nOn Node.js, JsRender templates can be stored directly in the file system (e.g. as .html, .jsr. or .jsrender files) – for example:\\nTemplate: ./templates/myTemplate.html – with contents:\\nName: {{:name}}
        \\n\\nCode: JsRender recognizes file paths (for valid relative file paths starting with './'), so you can write:\\nvar jsrender = require('jsrender');\\n\\nvar tmpl = jsrender.templates('./templates/myTemplate.html'); // Compile the template\\n\\nvar html = tmpl({name: \\\"Jim\\\"}); // Render\\n// result: Name: Jim
        \\n\\nNote: The ./... paths are always interpreted as relative paths relative to the location of your calling script. Declaring a templates folder for Express or Hapi does not change the origin of these relative paths.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"renderFile() method\",\r\n \"text\": \"renderFile() method\\nJsRender on Node.js provides a shortcut renderFile() method, for convenience, to compile and render in one step:\\nvar jsrender = require('jsrender');\\n\\nvar html = jsrender.renderFile('./templates/myTemplate.html', {name: \\\"Jim\\\"});\\n// result: Name: Jim
        \\n\\n\"\r\n },\r\n {\r\n \"_type\": \"api\",\r\n \"title\": \"jsrender.renderFile(filepath, data)\",\r\n \"text\": \"jsrender.renderFile(filepath, data)\\nShortcut method – compile and render\\nLoad file-based template, compile and render against data\\n\\nvar jsr = require('jsrender');\\nvar html = jsr.renderFile('./.../tmpl.html', data);\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Nested calls to file-based templates (composition)\",\r\n \"text\": \"Nested calls to file-based templates (composition)\\nJsRender’s awareness of Node.js file paths (relative paths starting with './') means your templates can include recursive calls to other templates (partials). You don’t need to register or compile those templates separately. (See also: template composition).\\nTemplate: ./templates/personTemplate.html:\\nName: {{:name}}
        Address: {{include tmpl='./templates/other/addressTemplate.jsr'}}\\n\\nTemplate: ./templates/other/addressTemplate.jsr:\\nStreet: {{:street}}\\n\\nCode: Compile and render, recursively:\\nvar jsrender = require('jsrender');\\n\\nvar tmpl = jsrender.templates('./templates/personTemplate.html');\\n// Compile template - and also any recursively called templates\\n\\nvar html = tmpl({name: \\\"Jim\\\", street: \\\"Main St\\\"});\\n// result: Name: Jim
        Address: Main St\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Register a file-based template by name – and render it\",\r\n \"text\": \"Register a file-based template by name – and render it\\nFor convenience you can register file-based templates by name, just as you can for templates from strings.\\n// Register named template - \\\"myTmpl1\\n$.templates(\\\"myTmpl1\\\", \\\"./templates/myTemplate.html\\\");\\n\\n// Render named template\\nvar html = $.templates.myTmpl1(person);\\n\\n// Alternative syntax: var html = $.render.myTmpl1(person);\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Automatic caching of file-based templates\",\r\n \"text\": \"Automatic caching of file-based templates\\nThe first time jsrender.templates('./templates/myTemplate.html') is called, JsRender will:\\n\\nload the template file from the file system\\ncompile the template\\ncache the template\\nreturn the compiled template\\n\\nThe cached template can be accessed directly as jsrender.templates['./templates/myTemplate.html'] - and can also be deleted by calling delete jsrender.templates['./templates/myTemplate.html'], or jsrender.templates('./templates/myTemplate.html', null)\\nOn subsequent calls, JsRender will simply:\\n\\nreturn the compiled template\\n\\nThe caching means you can load and compile the template during server initialization, and avoid the cost of reading the file or compiling during HTTP requests:\\njsrender.templates('./templates/myTemplate.html'); // Cache the compiled template\\n\\napp.get('/...', function(req, res) {\\n res.render('myTemplate', {name: \\\"Jim\\\"}); // Render previously cached template, using Express\\n});\\n\\nSimilarly when using the alternative forms for rendering templates:\\napp.get('/...', function(req, res) {\\n var tmpl = jsrender.templates('./templates/myTemplate.html'); // Get previously cached template\\n var html = tmpl.render({name: \\\"Jim\\\"});\\n res.send(html);\\n});\\n\\nor\\napp.get('/...', function(req, res) {\\n // Render previously cached template\\n var html = jsrender.renderFile('./templates/myTemplate.html', {name: \\\"Jim\\\"});\\n res.send(html);\\n});\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using the same template on the server and in the browser\",\r\n \"text\": \"Using the same template on the server and in the browser\\nJsRender lets you easily use the same templates for both server and browser rendering. See server/browser templates for details on two alternative approaches, one with the {{clientTemplate}} tag, and the other using Browserify.\\n\"\r\n }\r\n ]\r\n },\r\n \"jsrnode\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Quickstart\",\r\n \"text\": \"Quickstart\\nSee the JsRender Node.js Quickstart for an overview of JsRender support in Node.js\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"Detail topics:\",\r\n \"text\": \"Detail topics:\\n\"\r\n }\r\n ]\r\n },\r\n \"node/install\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Installation\\nOn Node.js from the command line, install jsrender:\\n$ npm install jsrender\\n\\nUsage\\nLoad the jsrender module:\\nvar jsrender = require('jsrender');\\n\\nNow call JsRender APIs, or use Express or Hapi integration, for server-rendering of JsRender templates.\\n(For loading JsRender in the browser using Browserify or webpack, see JsRender as a Browserify module and JsRender as a webpack module)\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"JsRender APIs on the server – same as in the browser!\",\r\n \"text\": \"JsRender APIs on the server – same as in the browser!\\nIn the browser, when jQuery is present, JsRender loads as a jQuery plugin and adds APIs to the jQuery namespace object, as:\\n$.views, $.templates and $.render\\nOn the server exactly the same APIs are provided, associated instead with the jsrender namespace:\\njsrender.views, jsrender.templates and jsrender.render.\\nFor convenience you can call the namespace $ and then use the regular APIs: $.views..., $.templates..., $.render..., or copy from the regular browser examples/samples – as if in the browser with jQuery.\\nFor example:\\nvar $ = require('jsrender'); // Returns the jsrender namespace object - referenced for convenience as var $\\n\\nvar tmpl = $.templates('Name: {{:first}} {{upper:last'); // Compile template from string\\n\\n$.views.converters('upper', function(val) {return val.toUpperCase()}); // Register converter\\n \\nvar data = {first: 'Jo', last: 'Ryan'};\\n\\nvar html = tmpl(data); // Or alternative syntax: var html = tmpl.render(data);\\n// result: \\\"Name: Jo RYAN\\\" \\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using helpers, converters, custom tags...\",\r\n \"text\": \"Using helpers, converters, custom tags...\\nOn Node.js you can use the full set of JsRender features, template tags and APIs, just as you would in the browser – by simply using the jsrender namespace object returned from require('jsrender'), instead of the jQuery object, $. In addition you can take advantage of file-based templates.\\nCustom Tags example: – For example, here is the JsRender Quickstart Custom Tags Sample, as you might write it on Node.js:\\nTemplate: ./templates/personTemplate.html:\\nName: {{fullName person/}}\\n\\nCode:\\nvar jsrender = require('jsrender');\\n\\njsrender.views.tags(\\\"fullName\\\", \\\"{{:first}} {{:last}}\\\"); // Register custom tag\\n\\nvar tmpl = jsrender.templates('./templates/personTemplate.html'); // Compile template\\n\\nvar html = tmpl({person: {first: \\\"Jim\\\", last: \\\"Varsov\\\"}}); // Render\\n// result: \\\"Jim Varsov\\\"\\n\\nHelpers example: – And here is the JsRender Quickstart Helpers example, in a version for Node.js:\\nTemplate: ./templates/personTemplate.html:\\n{{:~title}} {{:first}} {{:~upper(last)}}\\n\\nCode:\\nvar jsrender = require('jsrender');\\n\\nvar myHelpers = {\\n upper: function(val) { return val.toUpperCase(); },\\n title: \\\"Sir\\\"\\n};\\n\\nvar tmpl = $.templates('./templates/personTemplate.html');\\n\\nvar data = {first: \\\"Jim\\\", last: \\\"Varsov\\\"};\\n\\nvar html = tmpl(data, myHelpers);\\n// result: \\\"Sir Jim VARSOV\\\"\\n\\nOr we can register helpers globally:\\njsrender.views.helpers(myHelpers);\\n\\nvar data = {first: \\\"Jim\\\", last: \\\"Varsov\\\"};\\nvar html = tmpl(data);\\n// result: \\\"Sir Jim VARSOV\\\"\\n\\n\"\r\n }\r\n ]\r\n },\r\n \"node/express-hapi\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Express and Hapi integration\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using Express to render templates\",\r\n \"text\": \"Using Express to render templates\\nIn Express you can use JsRender APIs to render the template, as in the examples above, then return the html in the HTTP response:\\napp.get('/...', function(req, res) {\\n res.send(html);\\n});\\n\\nBut alternatively you can register JsRender as template engine for Express:\\nvar jsrender = require('jsrender');\\n\\napp.engine('html', jsrender.__express); // Set JsRender as template engine for .html files\\napp.set('view engine', 'html'); \\napp.set('views', __dirname + '/templates'); // Folder location for JsRender templates for Express\\n\\nRender template ./templates/myTemplate.html – content: Name: {{:name}}
        :\\napp.get('/...', function(req, res) {\\n res.render('myTemplate', {name: \\\"Jim\\\"}); \\n // result: Name: Jim
        \\n});\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using Hapi to render templates\",\r\n \"text\": \"Using Hapi to render templates\\nJsRender also has built-in support as template engine for Hapi:\\nSet JsRender as the template engine for Hapi:\\nvar jsrender = require('jsrender');\\n\\nserver.register(vision, function (err) {\\n ...\\n server.views({\\n engines: { html: jsrender },\\n relativeTo: __dirname,\\n path: 'templates'\\n });\\n\\nUse Hapi to render a template:\\nserver.route({\\n method: 'GET',\\n path: '/',\\n handler: function (request, reply) {\\n return reply.view('myTemplate', myData);\\n }\\n});\\n\\n\"\r\n }\r\n ]\r\n },\r\n \"node/server-browser\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Sharing the same templates between server and browser\\nJsRender lets you share templates between server and client, using either of the Browserify or {{clientTemplate}} approaches shown below.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Browserify\",\r\n \"text\": \"Browserify\\nUsing Browserify with the jsrender/tmplify transform allows you to include your server file-based templates in the Browserify client-script bundle.\\nYou can then access the compiled templates in the browser, as modules, using:\\nvar tmpl = require('./.../myTemplate.html)`\\nvar html = tmpl.render(myData);\\n...\\n\\nFor details, see the Browserify topic.\\nFor complete running samples, see the index-express-browserify.js and index-hapi-browserify.js samples in the JsRender Node Starter project.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Rendering file-based templates in the browser: {{clientTemplate}}\",\r\n \"text\": \"Rendering file-based templates in the browser: {{clientTemplate}}\\nJsRender also provides a {{clientTemplate}} tag that makes file-based templates available for rendering in the browser without needing to use Browserify.\\nSimply include {{clientTemplate \\\"templateFilePath...\\\"}} in the layout template, for any template you want to expose in the browser:\\n\\n {{clientTemplate \\\"./templates/myTemplate.html\\\" /}}\\n\\n\\n
        \\n\\n\\n\\nSee the index-express.js and index-hapi.js samples in the JsRender Node Starter project.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"JsRender on the server, JsRender or JsViews in the browser...\",\r\n \"text\": \"JsRender on the server, JsRender or JsViews in the browser...\\nBoth the Browserify and the {{clientTemplate}} approach to sharing templates between server and browser let you then render or link those templates in the browser, using JsRender or JsViews.\\nIn the browser, you reference the templates using the same ./file/path/template.html syntax as on the server.\\nFor example, in the JsRender Node Starter samples, the layout-movies.html template contains the following:\\n\\n {{include tmpl=\\\"./templates/movie-list.html\\\"/}}\\n\\n\\nHere, the {{include ...}} is used on the server to do initial rendering of the movies list using the movie-list.html template. Then in the browser, the data-link=\\\"{include ...} causes JsViews to access the same template in the browser, and provide dynamic data-binding of the list…\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Single Page Apps with initial rendering on server\",\r\n \"text\": \"Single Page Apps with initial rendering on server\\nAn important scenario is a single page app using JsRender or JsViews in the client to create dynamic UI, combined with initial rendering of the content on the server by JsRender using the same template.\\nThis can bring many advantages, including SEO, and eliminating flicker when the page is refreshed with a new server request.\\nNote: To completely eliminate flicker on data-linked content which has already been rendered on the server, it is sometimes useful to use the syntax data-link=\\\"...^{...}\\\" – which data-links without doing the initial render. Here is an example from movie-detail.html in the JsRender Node Starter:\\n
        \\n\\n\"\r\n }\r\n ]\r\n },\r\n \"tagsyntax\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Template tags in JsRender use the Mustache style: {{...}}.\\n(You can choose different delimiters, such as <%...%>, using $.views.settings.delimiters(\\\"<%\\\", \\\"%>\\\").\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Tags without content\",\r\n \"text\": \"Tags without content\\nThe most common JsRender tags are {{: pathOrExpr}} – which inserts the value of the path or expression, and {{> pathOrExpr}} which inserts the HTML-encoded value of the path or expression.\\nThose tags, along with the allow code tag {{* ...}} and comment tag {{!-- ... --}}, are self-contained tags which do not wrap other content:\\nBuilt-in tags without content:\\n{{: pathOrExpr}} (value)\\n{{> pathOrExpr}} (HTML-encoded value)\\n{{* mycode}} (using code)\\n{{!-- this is a comment --}} \\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Block tags – tags with content: \",\r\n \"text\": \"Block tags – tags with content: \\nAll other built-in tags, as well as all custom tags, use the block tag syntax:\\n{{include ...}}...{{/include}} or {{include .../}}\\n{{for}}...{{/for}} or {{for.../}}\\n{{props}}...{{/props}} or {{props .../}}\\n{{if}}...{{/if}} or {{if .../}}\\n{{myCustomTag}}...{{/myCustomTag}} or {{myCustomTag .../}}\\n\\nTags using the block tag syntax have open and close tags, with content, or else they use the self-closing syntax, without content:\\nBlock tag with content\\n{{sometag ...}}\\n content\\n{{/sometag}}\\n\\nSelf-closing block tag (empty tag) – no content:\\n{{sometag .../}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using tmpl=... to reference content as an external template\",\r\n \"text\": \"Using tmpl=... to reference content as an external template\\nA particular case of self-closing syntax is when any block tag uses the named parameter tmpl=... to reference an external template, which then replaces what would have been the block content.\\nThis is a very useful technique for encapsulation and reuse of tag content. The content becomes a ‘partial’ – and is included thanks to template composition:\\nSelf-closing block tag referencing an external template:\\n{{sometag ... tmpl=.../}}\\n\\n(See for example {{for languages tmpl=\\\"#columnTemplate\\\"/}} in this sample.)\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Template composition (partials)\",\r\n \"text\": \"Template composition (partials)\\nThe most common way of composing templates is to have a layout template, and to use {{include tmpl=... /}}:\\ntop level content\\n{{include tmpl='myInnerTemplate' /}}\\n\\nBut in fact template composition can be done by adding references to external templates using tmpl=... on any tag, as shown in the previous section.\\nDynamic composition\\nNote that the tmpl=... can use any expression, so you can assign different nested templates dynamically based on data or context. For example you might write {{include tmpl=~getTemplate(type) /}} – where ~getTemplate(...) is a helper which returns a different template based in this case on the type property of the current data item.\\nIn fact when setting tmpl=... dynamically, the returned template can be in any if the following forms:\\n\\na compiled template\\na markup string\\nthe name of a registered template\\na selector\\n(on Node.js) a file path to a template\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Tag arguments and named parameters\",\r\n \"text\": \"Tag arguments and named parameters\\nTags can take both unnamed arguments and named parameters:\\n{{sometag argument1 param1=...}}\\n content\\n{{/sometag}}\\n\\nAn example of a named parameter is the tmpl=... parameter mentioned above:\\n{{for languages tmpl=\\\"#columnTemplate\\\"/}}\\n\\nArguments and named parameters can be assigned values from simple data-paths such as:\\n{{formattedAddress address.street format=~util.formats.upper /}}\\n\\nor from richer expressions such as product.quantity * 3.1 / 4.5, or name.toUpperCase()\\n{{productValue product.quantity*3.1/4.5 description=name.toUpperCase() /}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Wrapping content \",\r\n \"text\": \"Wrapping content \\nIf a tag has an external tmpl=... reference, and inline block content, then the external template takes precedence. However, the external template can behave as a wrapper, wrapping the inline block content (one or more times), thanks to the view.content or #content property:\\n{{sometag ... tmpl=\\\"externalTmpl\\\"}}\\n inline block content\\n{{/sometag}}\\n\\n$.templates(\\\"externalTmpl\\\", \\\"before {{include tmpl=#content /}} after\\\";\\n\\nSimilarly, a custom tag can use a built-in template which wraps the inline content:\\n{{mytag}}\\n inline block content\\n{{/mytag}}\\n\\n$.view.tags(\\\"mytag\\\", {\\n ...\\n template: \\\"before {{include tmpl=#content /}} after\\\"),\\n ...\\n});\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Block tags with {{else}}\",\r\n \"text\": \"Block tags with {{else}}\\nSome block tags provide features which involve using alternative content blocks. Block tag syntax supports this by allowing the content to be separated into two or more alternative content blocks, using {{else}} tags as separators:\\nFor example, the {{if}} tag uses {{else}} to provide if-else, or if-elseif-else … behavior:\\n{{if firstExpression}}\\n render this if the firstExpression is true\\n{{else secondExpression}}\\n else render this if the secondExpression is true\\n{{else}}\\n else render this\\n{{/if}}\\n\\nAnd the {{for}} tag accepts alternative content to render if an array is empty (or an array or object is null or undefined):\\n{{for members}}\\n Member Name: {{:name}}\\n{{else}}\\n There are currently no members...\\n{{/for}}\\n\\nSimilarly you can use {{else}} with a custom tag, such as in this sample:\\n{{tabs caption=\\\"First Tab\\\"}}\\n first tab content\\n{{else caption=\\\"Second Tab\\\"}}\\n second tab content\\n{{/tabs}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"See also:\",\r\n \"text\": \"See also:\\n\"\r\n }\r\n ]\r\n },\r\n \"views\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"A view is a rendered template/block tag\",\r\n \"text\": \"A view is a rendered template/block tag\\nEach instance of a rendered template or a template block tag is associated with a JsViews “view” object.\\nFor example, if the following template is rendered, and inserted into the page –\\n\\n\\nvar team = {title: \\\"The A team\\\", members: [{name: \\\"Jeff\\\"}, {name: \\\"Maria\\\"}]};\\n\\nvar html = $(\\\"#teamTemplate\\\").render(team);\\n\\n– then the rendered result will have the following view structure:\\n— teamView (Team: The A team)\\n — ifView (The team has members!)\\n\\nEach view is associated with a view object, which provides APIs for accessing properties of that view, as well as for accessing parent or child views in the view hierarchy.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"The data context of a view\",\r\n \"text\": \"The data context of a view\\nIn particular, a view has a data property, which is the current data context used for rendering that view (rendering that template, or inline block content):\\n— teamView data: team\\n — ifView data: team\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Inline block content / external 'tmpl=...' reference: same view hierarchy...\",\r\n \"text\": \"Inline block content / external 'tmpl=...' reference: same view hierarchy...\\nA view corresponds to an instance of a block tag or a rendered template – so if we replace the inline content of a tag by an external reference: tmpl=..., the rendered result will be unchanged, and the view structure will also be identical:\\n\\n\\n\\n\\nSame view structure as before:\\n— teamView data: team\\n — ifView data: team\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Stepping into a block tag – what is the new data context?\",\r\n \"text\": \"Stepping into a block tag – what is the new data context?\\nLet’s add a custom tag {{mytag}} to our template:\\nMy team\\n{{mytag members/}}\\n...\\n\\nWe’ll define the custom tag, with a built-in template:\\n $.views.tags(\\\"mytag\\\", \\\"{{:length}} member(s)\\\");\\n\\n{{mytag members/}} will render block content (with an associated view) using its tag template \\\"{{:length}} members\\\".\\nWhat will the data context be for the mytag view?\\nBy default:\\n\\na block tag with no argument {{sometag}} will stay on the current data context\\na block tag with an argument {{sometag expr ...}} will move the data context to expr.\\n\\nSo {{mytag members}} (just like {{include members}}) will move the data context to members.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"However a block tag may be designed to simply stay on the same data context as the parent block – and that is the case for the {{if}} tag:\\n\\n{{if expr}} does not move the data context.\\n\\nSo our template\\n\\n\\nwill have this view structure:\\n— teamView data: team\\n — mytagView data: team.members\\n — ifView data: team (same as parent – teamView)\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Array views and item views – {{for array}}\",\r\n \"text\": \"Array views and item views – {{for array}}\\nNow let’s add a {{for members}} tag to iterate over the members, inside the {{if}} block:\\nTeam\\n{{mytag members/}}\\n\\n{{if members.length}}\\n Members:\\n {{for members}}\\n {{:name}}\\n {{/for}}\\n{{/if}}\\n\\nWhen a {{for ...}} tag is used with an array it creates:\\n\\nan “array” view, whose data property is the array – and under the “array” view:\\nan “item” view for each item in the array – with as data property the item, and as index property the index in the array:\\n\\n(Similarly, any tag which derives from the {{for}} tag – such as the {{props}} tag – will also add an “array” view and “item” views…)\\nSo our view structure with the {{for}} tag included will now be :\\n— teamView data: team type: \\\"data\\\"\\n — mytagView data: team.members type: \\\"mytag\\\"\\n — ifView data: team type: \\\"if\\\"\\n — arrayView data: team.members type: \\\"array\\\"\\n — itemView data: team.members[0] type: \\\"item\\\"\\n — itemView data: team.members[1] type: \\\"item\\\"\\n\\n– where we show also the type property of each view.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Array views and item views – tmpl.render(array)\",\r\n \"text\": \"Array views and item views – tmpl.render(array)\\nSuppose now we have an array of teams – and we pass the teams array to the render() method:\\nvar teams = [\\n {title: \\\"A Team\\\", members: [{name: \\\"Jeff\\\"}, {name: \\\"Maria\\\"}]},\\n {title: \\\"B Team\\\", members: [{name: \\\"Francis\\\"}]}\\n];\\n\\nvar html = $(\\\"#teamTemplate\\\").render(teams);\\n\\nJsRender will render the teamTemplate once for each team – and just like with the {{for}} it will create an “item” view for each item in the teams array – with the two “item” views as children of an “array” view.\\nHere it is as a working sample:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"text\": \"\\n
        \\n Team: {{:title}} -\\n {{mytag members/}}\\n\\n {{if members.length}}\\n Members:\\n {{for members}}\\n {{:name}}\\n {{/for}}\\n {{/if}}\\n
        \\n\\n\\n\\n// mytag: custom tag to output \\\"1 member\\\" or \\\"n members\\\"\\n$.views.tags(\\\"mytag\\\", \\\"{{:length == 1 ? '1 member' : length + ' members'}}\\\");\\n// Alternative version of mytag:\\n// $.views.tags(\\\"mytag\\\", \\\"{{if length == 1}}1 member{{else}}{{:length}} members{{/if}}\\\");\\n\\nvar teams = [\\n {title: \\\"The A Team\\\", members: [{name: \\\"Jeff\\\"}, {name: \\\"Maria\\\"}]},\\n {title: \\\"The B Team\\\", members: [{name: \\\"Francis\\\"}]}\\n];\\n\\nvar html = $(\\\"#teamTemplate\\\").render(teams);\\n\\n$(\\\"#result\\\").html(html);\\n\\nvar html = $(\\\"#teamTemplate\\\").render(teams);\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"And here is the resulting view structure:\\n— arrayView data: teams\\n — itemView data: teams[0] (Team: The A Team - )\\n — mytagView data: team.members (2 members)\\n — ifView data: teams[0] (Members:)\\n — arrayView data: teams[0].members\\n — itemView data: teams[0].members[0] (Jeff)\\n — itemView data: teams[0].members[1] (Maria)\\n — itemView data: teams[1] (Team: The B Team - )\\n — mytagView data: team.members (1 members)\\n — ifView data: teams[1] (Members:)\\n — arrayView data: teams[1].members\\n — itemView data: teams[1].members[0] (Francis)\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"The default argument for a tag is the current data – #data\",\r\n \"text\": \"The default argument for a tag is the current data – #data\\nFor all built-in tags (and custom tags if you don’t use the argDefault option), you can pass the current data to the tag by writing it without an argument.\\nSo the following:\\n{{:}} {{!--Render value of current data (string)--}}\\n{{>}} {{!--Render value of current data (string)--}}\\n{{for}}...{{/for}} {{!--Move to current data (object) or iterate over current data (array)--}}\\n{{if}}...{{/if}} {{!--Render block if current data is truthy--}}\\n{{props}}...{{/props}} {{!--Iterate over properties of current data (object)--}}\\n\\nare equivalent to:\\n{{:#data}} {{!--Render value of current data (string)--}}\\n{{>#data}} {{!--Render value of current data (string)--}}\\n{{for #data}}...{{/for}} {{!--Move to current data (object) or iterate over current data (array)--}}\\n{{if #data}}...{{/if}} {{!--Render block if current data is truey--}}\\n{{props #data}}...{{/props}} {{!--Iterate over properties of current data (object)--}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"In JsViews: From UI back to data:\",\r\n \"text\": \"In JsViews: From UI back to data:\\nNote: One of the features provided by JsViews data-linking (when you use the JsViews .link() method rather than JsRender’s .render() method) is the $.view(elem) method. This method provides a reverse mapping and lets you get from a rendered DOM element back to the corresponding view object in the view hierarchy. From the view you can get to the underlying data, the index, etc.\\nSo in effect in JsViews, the mapping from the view hierarchy to the UI becomes a two-way mapping…\\nSee Using $.view() to get from the rendered UI back to the data\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"See also:\",\r\n \"text\": \"See also:\\n\"\r\n }\r\n ]\r\n },\r\n \"paths\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"JsRender tags can take unamed arguments, or named parameters:\\n{{:arg0}}\\n\\n{{sometag arg1 arg2 param_a=param1 param_b=param2}}\\n content\\n{{/sometag}}\\n\\nThe values of the arguments or parameters (such as arg0… param1 … above) must be valid JsRender paths or expressions.\\nJsRender expressions are regular Javascript expressions, but with no access to global variables.\\nInstead of global Javascript variables, JsRender expressions use data paths, helper paths and view paths, to access data values, values provided by helpers, and values obtained from the view hierarchy, such as the #getIndex().\\nData paths are of the form dataProperty.bb.cc, and they step through the data hierarchy, starting from the current data item (the data context for the current view). They can include array access, such as team.members[id]\\nView paths are of the form #viewProperty.bb.cc, and they start from the current view. So for example, #data is short for #view.data – where #view is the current view.\\nHelper paths are of the form ~myHelper.bb.cc, and they start from the named helper \\\"myHelper\\\". In addition they can be used to access contextual parameters, or the built-in ~root\\nHere are some examples of JsRender paths and values:\\nData paths:\\n{{:name}}\\n{{for address.street}}...{{/for}}\\n{{>team.members[0].lastName}}\\n{{:name.toUpperCase()}}\\n\\nHelper paths:\\n{{>~utilities.errorMessages.msg1}}\\n{{if ~settings.show}}...{{/if}}\\n{{:~root.selectedName}} {{!--Accessing root data--}}\\n\\nView paths:\\n{{:#getIndex()}}\\n{{include #content /}}\\n{{if #parent.parent.data.isLead}}...{{/if}}\\n{{>~getDescription(#data)}}\\n\\nA primitive value of type string, number, boolean, null …:\\n{{if isOpen tmpl='It is open' /}}\\n{{for address tmpl=\\\"#addressTemplate\\\"}}...{{/for}}\\n{{for members start=1 end=5 /}}\\n{{for members reverse=true /}}\\n\\nJsRender expressions can combine values in more complex expressions, using functions, parens, operators such as + - * / ! === == > !== || &&, as well as ternary expressions: ...?...:..., array and object accessors: [...] etc.\\nHere are some examples of expressions:\\n{{if book.author === \\\"Jim Boyd\\\"}}...{{/if}}\\n{{:~utilities.format(book.title, 'upper', true)}}\\n{{for ~sort(~root.getMembers()}}}...{{/for}}\\n{{:person.firstName + ' ' + person.lastName.toUpperCase()}}\\n{{for #parent.data.members()/}}\\n{{:(~addRebate(book.price) + 23.2)*3.5/2.1}}\\n{{:~mode === \\\"useTitle\\\" ? book.title : book.name}}\\n{{if error}}...{{else !utilities.valid(book.description)}}...{{else}}...{{/if}}\\n{{:~books[id].title}}\\n{{:people[~currentIndex].name}}\\n\\nExpressions can include white space. The following two examples are equivalent:\\n{{averageValue product.quantity*3.1/4.5 description=~getDescription(#data) /}}\\n{{averageValue product.quantity * 3.1 / 4.5 description = ~getDescription( #data ) /}}\\n\\nThe {{averageValue}} tag is being assigned one argument, and one named “description” parameter. The two expressions differ only in white space, and both are syntactically valid. However, removing optional white space -– as in the first example -– makes it easier to see the distinct arguments and parameters of the tag.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Chained paths: Stepping through object properties (or functions)\",\r\n \"text\": \"Chained paths: Stepping through object properties (or functions)\\nAll of the paths above (whether Data/Helper/View paths) involve starting from an initial value (a current data item property/helper/view property) – and then, if it is an object, perhaps stepping through one or more chained properties.\\nFor example team.manager.address.street starts from a team object and steps through the manager property – which is itself a ‘person’ object with an address property, etc.\\n(See also Data-linked paths.)\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Computed properties\",\r\n \"text\": \"Computed properties\\nIn some cases a property may be of type function (possibly taking parameters), so you might have:\\nteam.manager().getAddress('home').street\\n– where the manager property is in fact a ‘getter’ function which returns a person object, which has a getAddress() parameterized accessor (taking 'home' or 'work' – or maybe a Boolean isHomeAddress). Similarly a path can include an array accessor such as team.members['id'].address.\\nProperties of type function – returning a value – are referred to as a computed properties, or getter properties, and \\nteam.manager().getAddress('home').street is an example of chained computed properties.\\n(See also Computed properties and computed observables – for using computed properties with JsViews and data-linking.)\\nA computed value can also use JavaScript methods, such toFixed() to format a number:\\n{{:price.toFixed(2)}} \\n{{:(+price).toFixed(2)}} \\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Getter properties and computed properties\",\r\n \"text\": \"Getter properties and computed properties\\nA common pattern using computed ‘getter’ functions would be to provide a person.firstName() ‘getter’ property which returns a value: person._firstName, considered as 'private’.\\nIn addition, there may be computed properties which depend on other properties, such as a person.fullName() which concatenates first and last name.\\nHere is a sample showing both types of computed property:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Getter properties with plain objects\",\r\n \"text\": \"Getter properties with plain objects\\n\\n\\n\\n First name: {{:person.firstName()}}
        \\n Last name: {{:person.lastName()}}
        \\n Full name: {{:person.fullName()}}\\n\\nfunction firstName() { return this._firstName; }\\nfunction lastName() { return this._lastName; }\\nfunction fullName() { return this._firstName + \\\" \\\" + this._lastName; }\\n\\nvar data = {\\n person: {\\n _firstName: \\\"Jo\\\",\\n _lastName: \\\"Blow\\\",\\n firstName: firstName,\\n lastName: lastName,\\n fullName: fullName\\n }\\n};\\n\\nvar html = $(\\\"#personTmpl\\\").render(data);\\n\\n$(\\\"#result\\\").html(html);\\nData:\\nfunction firstName() { return this._firstName; }\\nfunction lastName() { return this._lastName; }\\nfunction fullName() { return this._firstName + \\\" \\\" + this._lastName; }\\n\\nvar data = {\\n person: {\\n _firstName: \\\"Jo\\\",\\n _lastName: \\\"Blow\\\",\\n firstName: firstName,\\n lastName: lastName,\\n fullName: fullName\\n }\\n};\\n\\nTemplate:\\n First name: {{:person.firstName()}}\\n Last name: {{:person.lastName()}}\\n Full name: {{:person.fullName()}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Getter properties on a View Model\",\r\n \"text\": \"Getter properties on a View Model\\nRather than using plain JavaScript objects with getter functions, as above, a more common pattern (providing better encapsulation) would be to define a ‘View Model’ class – with getter properties defined in the class – and to instantiate that class to provide data instances.\\n(See Plain objects or View Model for details.)\\nThe following sample uses that approach:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Getter properties with a View Model\",\r\n \"text\": \"Getter properties with a View Model\\n\\n\\n\\n First name: {{:person.firstName()}}
        \\n Last name: {{:person.lastName()}}
        \\n Full name: {{:person.fullName()}}\\n\\nfunction firstName() { return this._firstName; }\\nfunction lastName() { return this._lastName; }\\nfunction fullName() { return this._firstName + \\\" \\\" + this._lastName; }\\n\\nfunction Person(first, last) {\\n this._firstName = first;\\n this._lastName = last;\\n}\\n\\nPerson.prototype = {\\n firstName: firstName,\\n lastName: lastName,\\n fullName: fullName\\n};\\n\\nvar data = {\\n person: new Person(\\\"Jo\\\", \\\"Blow\\\")\\n};\\n\\nvar html = $(\\\"#personTmpl\\\").render(data);\\n\\n$(\\\"#result\\\").html(html);\\nData:\\nfunction firstName() { return this._firstName; }\\nfunction lastName() { return this._lastName; }\\nfunction fullName() { return this._firstName + \\\" \\\" + this._lastName; }\\n\\nfunction Person(first, last) {\\n this._firstName = first;\\n this._lastName = last;\\n}\\n\\nPerson.prototype = {\\n firstName: firstName,\\n lastName: lastName,\\n fullName: fullName\\n};\\n\\nvar data = {\\n person: new Person(\\\"Jo\\\", \\\"Blow\\\")\\n};\\n\\nTemplate:\\n First name: {{:person.firstName()}}\\n Last name: {{:person.lastName()}}\\n Full name: {{:person.fullName()}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"See also:\",\r\n \"text\": \"See also:\\n\"\r\n }\r\n ]\r\n },\r\n \"tmplsyntax\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"The following topics provide information on JsRender template syntax:\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"\",\r\n \"text\": \"\"\r\n }\r\n ]\r\n },\r\n \"settings\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"JsRender provides the following APIs for modifying settings:\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"\",\r\n \"text\": \"\"\r\n }\r\n ]\r\n },\r\n \"settings/delimiters\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"See also Setting tag delimiters for JsViews\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"JsRender default tag delimiters\",\r\n \"text\": \"JsRender default tag delimiters\\nTemplate tags in JsRender use the Mustache style: {{...}}\\n(JsRender also accepts the data-linked tag syntax used in in JsViews: {^{...}}).\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Changing delimiters:\",\r\n \"text\": \"Changing delimiters:\\nSometimes there can be a need to use different delimiters. For example there may be a conflict if the template is being rendered on the server using a declarative syntax such as Django with the same default delimiters {{ and }}.\\nThe following call:\\n$.views.settings.delimiters(\\\"<%\\\", \\\"%>\\\");\\n\\nwill change the tag syntax to <%...%>.\\n(Note: $.views.settings.delimiters(...); also accepts as parameter an array such as [\\\"<%\\\", %>\\\"] – as shown in the last sample below.)\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Verifying current setting for tag delimiters:\",\r\n \"text\": \"Verifying current setting for tag delimiters:\\nvar delimiters = $.views.settings.delimiters();\\n// Returns an array [\\\"{{\\\", \\\"}}\\\", \\\"^\\\"] - JsRender tag delimiters (and JsViews link character)\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Choosing alternative tag delimiters, with JsRender\",\r\n \"text\": \"Choosing alternative tag delimiters, with JsRender\\n\\n\\n\\n [%:title%]\\n
          \\n [%for members%]\\n
        • Name: [%:name%]
        • \\n [%/for%]\\n
        \\n\\n$.views.settings.delimiters(\\\"[%\\\", \\\"%]\\\");\\n\\nvar tmpl = $.templates(\\\"#peopleTmpl\\\");\\n\\nvar team = {\\n title: \\\"A team\\\",\\n members: [{name: \\\"Jo\\\"}]\\n };\\n\\nvar html = tmpl.render(team);\\n\\n$(\\\"#result\\\").html(html);\\nMarkup:\\n\\n\\nCode\\n$.views.settings.delimiters(\\\"[%\\\", \\\"%]\\\");\\n\\nvar tmpl = $.templates(\\\"#peopleTmpl\\\");\\n...\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using alternative delimiters to 'render a template with a template'\",\r\n \"text\": \"Using alternative delimiters to 'render a template with a template'\\nIn some scenarios you might want to use a template to generate a template, such as a template on the server to generate/render a template that will then be used in the browser.\\nA good approach to achieving this is to use a different set of delimiters on the server.\\nA similar scenario is to use a ‘base’ template to render different versions of a template for different languages/localities, as in this example:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Template for a template\",\r\n \"text\": \"Template for a template\\n\\n <%:hello%>, {{:name}}
        \\n <%:welcome%> {{:place}}\\n\\n\\n\\n\\nvar spanishTerms = {\\n hello: \\\"Hola\\\",\\n welcome: \\\"Bienvenido a\\\"\\n};\\n\\nvar data = {\\n name: \\\"John\\\",\\n place: \\\"Madrid\\\"\\n};\\n\\n// Get current delimiters\\nvar currentDelimiters = $.views.settings.delimiters();\\n\\n// Temporarily switch delimiters\\n$.views.settings.delimiters(\\\"<%\\\", \\\"%>\\\");\\n\\n// Translate to Spanish localized version\\nvar localizedTemplate = $.templates(\\\"#baseTmpl\\\").render(spanishTerms);\\n\\n// Revert to original delimiters\\n$.views.settings.delimiters(currentDelimiters);\\n\\n// Render data using localized template\\nhtml = $.templates(localizedTemplate).render(data);\\n\\n$(\\\"#result\\\").html(html);\\n<%:hello%>, {{:name}}
        \\n<%:welcome%> {{:place}}\\n\\n// Get current delimiters\\nvar currentDelimiters = $.views.settings.delimiters();\\n\\n// Temporarily switch delimiters\\n$.views.settings.delimiters(\\\"<%\\\", \\\"%>\\\");\\n\\n// Translate to Spanish localized version\\nvar localizedTemplate = $.templates(\\\"#baseTmpl\\\").render(spanishTerms);\\n\\n// Revert to original delimiters\\n$.views.settings.delimiters(currentDelimiters);\\n\\n// Render data using localized template\\nhtml = $.templates(localizedTemplate).render(data);\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Incidentally the above scenario of localized terms in a template can be achieved without the ‘build step’ of creating localized templates, simply by passing in the terms as helpers, distinct from the data itself.\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Passing in terms as helpers\",\r\n \"text\": \"Passing in terms as helpers\\n\\n {{:~hello}}, {{:name}}
        \\n {{:~welcome}} {{:place}}\\n\\n\\n\\n\\nvar spanishTerms = {\\n hello: \\\"Hola\\\",\\n welcome: \\\"Bienvenido a\\\"\\n};\\n\\nvar data = {\\n name: \\\"John\\\",\\n place: \\\"Madrid\\\"\\n};\\n\\n// Pass in localized terms as helpers\\nvar html = $.templates(\\\"#tmpl\\\").render(data, spanishTerms );\\n\\n$(\\\"#result\\\").html(html);\\n\\nvar spanishTerms = {\\n hello: \\\"Hola\\\",\\n welcome: \\\"Bienvenido a\\\"\\n};\\n\\nvar data = {\\n name: \\\"John\\\",\\n place: \\\"Madrid\\\"\\n};\\n\\n// Pass in localized terms as helpers\\nvar html = $.templates(\\\"#tmpl\\\").render(data, spanishTerms );\\n\\n\\n\"\r\n }\r\n ]\r\n },\r\n \"settings/onerror\": {\r\n \"sections\": []\r\n },\r\n \"settings/dbgmode\": {\r\n \"sections\": []\r\n },\r\n \"settings/debugmode\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"JsRender has a ‘debug mode’ setting which determines whether error messages encountered during rendering are displayed.\\nTo get current debug mode:\\nvar isDebugMode = $.views.settings.debugMode(); // false by default\\n\\nTo set debug mode:\\n$.views.settings.debugMode(...);\\n\\nDebug mode can be set to any of the following:\\n\\nfalse – errors during rendering will not be rendered (but an exception will be thrown)\\ntrue – no exception will be thrown, but the error message will be rendered, in place of the template tag or block\\n\\\"some string\\\" – no exception. The string \\\"some string\\\" will be rendered in place of the tag or block\\n\\\"\\\" (empty string) – no exception. The tag or block will simply be replaced by the empty string\\na function (to be used as an error handler) – no exception. The handler will run, and the error string will be rendered, or else, if the function returns a string, that string will be rendered\\n\\nSee Error handling and debugging for a full discussion of alternative approaches, together with details and working examples of $.views.settings.debugMode(...).\\n\"\r\n }\r\n ]\r\n },\r\n \"settings/allowcode\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"JsRender templates allow you to write rich expressions within the template tags, such as:\\n{{:person.firstName + ' ' + person.lastName.toUpperCase()}}\\n\\nNevertheless, in order to improve encapsulation, security and maintainability, they don’t allow arbitrary code. For example, they don’t allow you to access global variables, like window.\\nIf you want complete freedom to insert any code into a compiled template, you can set allowCode to true, either globally, or specifically for that template. You can then run any code as part of the template rendering, using the {{* ...}} tag, or you can return (render into the template output) the result of evaluating any expression, using the {{*: ...}} tag.\\n(Note: it is not recommended to set allowCode to true within data-linked templates – with JsViews.)\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"User-defined templates and security\",\r\n \"text\": \"User-defined templates and security\\nFor most purposes there is no need to set allowCode to true, since the built-in template expressions provide rich functionality which is sufficient for most scenarios.\\nJsRender can be used to render templates either on the server or in the browser – and is often used for applications which allow users to create their own templates, or to insert markup and expressions into templates. With allowCode false, JsRender is designed to make it impossible for such user-defined templates to run arbitrary code.\\nUsers can include rich template expressions in the template, but they won’t be able to insert code that accesses any variables (or runs any methods) that are outside of the template scope. (They can only access the contextual data/model, use the standard operators, and use any helper methods and variables which the author decides to provide.)\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"To set allowCode to true, globally\",\r\n \"text\": \"To set allowCode to true, globally\\n$.views.settings.allowCode(true);\\n\\n(See samples for {{* ...}} and {{*: ...}})\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"To set allowCode back to false, globally\",\r\n \"text\": \"To set allowCode back to false, globally\\n$.views.settings.allowCode(false);\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"To get current global allowCode setting\",\r\n \"text\": \"To get current global allowCode setting\\nvar allowCodeIsTrue = $.views.settings.allowCode(); // false by default\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"To set allowCode to true for a specific template\",\r\n \"text\": \"To set allowCode to true for a specific template\\n$.templates(..., {\\n markup: ...,\\n allowCode: true,\\n ...\\n})\\n\\n(See {{* ...}} and {{*: ...}} sample: allowCode for template).\\n\"\r\n }\r\n ]\r\n },\r\n \"settings/advanced\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"JsRender has the following advanced setting:\\n\\nuseViews – default: false\\n\\nand also the following ‘private’ advanced setting:\\n\\n_jsv – default: false\\n\\nuseViews controls a JsRender performance optimization, while building the view hierarchy. In very simple templates there will usually not be any need to access the view. JsRender detects these cases, does not create a view, and hence obtains a slight performance gain. By setting useViews to true, you guarantee that JsRender will always create views for template blocks.\\n_jsv is a ‘private’ setting (could change in the future). If set to true JsRender provides a global _jsv variable, which gives access to the internal store of views.\\nTo get current advanced settings:\\nvar advancedSettings = $.views.settings.advanced();\\n\\nBy default the returned advancedSettings object is:\\n{useViews: false, _jsv: false}\\n\\nTo set advanced settings:\\n$.views.settings.advanced({useViews: true});\\n// Set one or more advanced settings\\n\\n\"\r\n }\r\n ]\r\n },\r\n \"onerror\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Sometimes when rendering a JsRender template, a JavaScript error is encountered. For example {{:address.street}} in a template will render without error provided there is an address property on the current data object. But if there is no address property, then there will be an error: “Cannot read property ‘street’ of undefined”.\\nJsRender provides two features which provide powerful control over rendering behavior when errors are encountered.\\n\\nThe optional onError=... property that can be set on any tag – for controlling error handling behavior on that specific tag\\nThe $.views.settings.debugMode(...) setting – which provides global control over error handling during rendering\\n\\nIn addition, for advanced debugging of compiled templates, see:\\n\\nUsing debugging helpers\\n\\n\\nSpecifying onError fallback behavior on a tag\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Setting onError to a string\",\r\n \"text\": \"Setting onError to a string\\nAll JsRender tags (including custom tags) such as {{address.street}} or {{for getItems()}} allow you to provide a onError tag property, with a fallback string to render in the case of errors:\\n{{:address.street onError=\\\"Address unavailable\\\"}}\\n\\n{{for phones() onError=\\\"No phones\\\"}}\\n\\n{{myCustomTag ... onError=\\\"\\\"}}\\n\\nThe onError fallback string will be rendered whenever there an error (or exception) is encountered during the tag rendering.\\nSetting to the empty string ensures that errors are simply ignored, and the tag renders as the empty string.\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"onError=\\\"fallback string...\\\" \",\r\n \"text\": \"onError=\\\"fallback string...\\\" \\n\\n\\n\\n{{for members}}\\n Phones:\\n {{for phones() onError=\\\"No phones\\\"}}\\n {{:}}\\n {{/for}}\\n
        \\n {{:address.street onError=\\\"Address unavailable\\\"}}\\n
        \\n{{/for}}\\n\\n\\nfunction phones() {\\n if (!this._phones) {\\n throw new Error(\\\"phones() error\\\");\\n }\\n return this._phones;\\n}\\n\\nvar team = {\\n members: [\\n {address: {street: \\\"1st Ave\\\"}, _phones: [\\\"888\\\", \\\"456\\\"],\\n phones: phones},\\n {address: undefined, _phones: [\\\"987\\\", \\\"111\\\"], // No address\\n phones: phones},\\n {address: {street: \\\"Main St\\\"}, _phones: undefined, // _No phones\\n phones: phones}\\n ]\\n};\\n\\nvar html = $(\\\"#teamTmpl\\\").render(team);\\n\\n$(\\\"#result\\\").html(html);\\nIn this sample, if a member object has no address property, the address.street expression will lead to a JavaScript error, and the {{:address.street onError=\\\"Address unavailable\\\"}} will render the fallback string: \\\"Address unavailable\\\".\\nSimilarly, {{for phones() onError=\\\"...\\\"}}, if phones() produces an error…\\nTemplate:\\n{{for phones() onError=\\\"No phones\\\"}} ...\\n{{:address.street onError=\\\"Address unavailable\\\"}}\\n\\nCode:\\nfunction phones() { if (!this._phones) { throw new Error(\\\"phones() error\\\"); } ... }\\n\\nData:\\nmembers: [\\n {address: {street: \\\"1st Ave\\\"}, _phones: [\\\"888\\\", \\\"456\\\"], ...\\n {address: undefined, _phones: [\\\"987\\\", \\\"111\\\"], ... // No address\\n {address: {street: \\\"Main St\\\"}, _phones: undefined, ... // _No phones\\n]\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Setting onError to an expression\",\r\n \"text\": \"Setting onError to an expression\\nMore specific or powerful behavior can be obtained by setting onError to an expression, such as:\\n{{:address.street onError=name + \\\" has no address\\\"}}\\n\\n{{:address.street onError=~errorMessages(1, name, 'address')}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"onError=someExpression...\",\r\n \"text\": \"onError=someExpression...\\n\\n\\n\\n{{for members}}\\n
        Name: {{:name}}
        \\n Phones:\\n {{for phones() onError=name + \\\" has no phones\\\"}}\\n {{:}}\\n {{/for}}\\n
        \\n {{:address.street onError=~errorMessages(1, name, \\\"address\\\")}}\\n
        \\n
        \\n{{/for}}\\n\\nfunction phones() {\\n if (!this._phones) {\\n throw new Error(\\\"phones() error\\\");\\n }\\n return this._phones;\\n}\\n\\nvar team = {\\n members: [\\n {name: \\\"Bill\\\", address: {street: \\\"1st Ave\\\"}, _phones: [\\\"888\\\", \\\"456\\\"],\\n phones: phones},\\n {name: \\\"Jane\\\", address: undefined, _phones: [\\\"987\\\", \\\"111\\\"], // No address\\n phones: phones},\\n {name: \\\"Ava\\\", address: {street: \\\"Main St\\\"}, _phones: undefined, // _No phones\\n phones: phones}\\n ]\\n};\\n\\n$.views.helpers(\\\"errorMessages\\\", function(id, param1, param2) {\\n if (id === 1) {\\n return param1 + \\\" has no \\\" + param2;\\n } \\n});\\n\\nvar html = $(\\\"#teamTmpl\\\").render(team);\\n\\n$(\\\"#result\\\").html(html);\\n\\n{{for phones() onError=name + \\\" has no phones\\\"}} ...\\n\\n{{:address.street onError=~errorMessages(1, name, \\\"address\\\")}}\\n\\n$.views.helpers(\\\"errorMessages\\\", function(id, param1, param2) {\\n if (id === 1) { return param1 + \\\" has no \\\" + param2; } ...\\n});\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Setting onError to a function\",\r\n \"text\": \"Setting onError to a function\\nIf onError=myOnErrorHandler is set to a function, then the function will be called when there is an error.\\n\\nIf the function returns a string, then that string will be rendered, replacing the output of the tag\\nIf the function has no return value, then the error message will be rendered\\n\\nFor example, you can provide a person.error() error handler method on a person object, and set onError=error. Or you can use global helper (or a helper passed to the render function), and set onError=~myErrorHandler, such as the following to log the error and display just the empty string:\\nfunction myErrorHandler(e, view) {\\n console.log(...); // Log the error \\n return \\\"\\\"; // Display the empty string \\n}\\n\\nThe parameters of the onError handler function – myHandler(e, view) – will be:\\n\\ne – the error object\\nview – the current view object\\nThe this pointer will be the current data item, view.data\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"onError=~myOnError\",\r\n \"text\": \"onError=~myOnError\\n\\n\\n\\n{{for members}}\\n
        Name: {{:name}}
        \\n {{:address.street onError=~myOnError}}\\n
        \\n
        \\n{{/for}}\\n\\n\\nvar team = {\\n members: [\\n {name: \\\"Bill\\\", address: {street: \\\"1st Ave\\\"}},\\n {name: \\\"Jane\\\", address: undefined} // No address\\n ]\\n};\\n\\nfunction onErrorHandler(e, view) {\\n console.log(e.message);\\n if (!this.address) {\\n return this.name + \\\" has no address (\\\" + e.message + \\\")\\\";\\n }\\n}\\n\\nvar html = $(\\\"#teamTmpl\\\").render(team, {myOnError: onErrorHandler});\\n\\n$(\\\"#result\\\").html(html);\\n\\n{{:address.street onError=~myOnError}}\\n\\nfunction onErrorHandler(e, view) {\\n console.log(e.message);\\n if (!this.address) {\\n return this.name + \\\" has no address (\\\" + e.message + \\\")\\\";\\n }\\n}\\n\\nvar html = $(\\\"#teamTmpl\\\").render(team, {myOnError: onErrorHandler});\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"\\nSetting debug mode\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"The $.views.settings.debugMode(...) setting provides control of error handling during rendering, similar to the onError feature above, but operating at a global level rather than on individual tags.\\nThese two approaches are complementary and can be used together.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Setting debug mode to true\",\r\n \"text\": \"Setting debug mode to true\\n\\nBy default debug mode is false – and an exception will be thrown if a JavaScript error is encountered while rendering a tag or template\\nIf debug mode is set to true – any error message encountered while rendering a tag will replace the rendered content of that tag\\n\\nTo set debug mode to true:\\n$.views.settings.debugMode(true);\\n\\nTo set debug mode back to false:\\n$.views.settings.debugMode(false);\\n\\nTo get current debug mode:\\nvar isDebugMode = $.views.settings.debugMode(); // false by default\\n\\nIn the following example debug mode is set to true. The error message is rendered, replacing the rendered tag.\\n(Choose Try it and change debug mode to false, to see the difference.)\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Debug mode set to true\",\r\n \"text\": \"Debug mode set to true\\n\\n\\n\\n{{for members}}\\n
        {{:name}} - {{:address.street}}
        \\n{{/for}}\\n\\n\\n$.views.settings.debugMode(true); \\n// Change to $.views.settings.debugMode(false); - The error\\n// will not be displayed, but an exception will be thrown.\\n\\nvar team = {members: [\\n {name:\\\"Jo\\\", address: {street: \\\"1st Ave\\\"}},\\n {name:\\\"Bill\\\"}, // Bill does not have an address!!\\n {name:\\\"Ava\\\", address: {street: \\\"Main St\\\"}}\\n]};\\n\\nvar html = $(\\\"#teamTmpl\\\").render(team);\\n\\n$(\\\"#result\\\").html(html);\\n\\nCode:\\n$.views.settings.debugMode(true);\\n\\nThe {{:address.street}} tag for Bill (who has no address) is replaced by the error message.\\nvar team = {members: [\\n {name:\\\"Jo\\\", address: {street: \\\"1st Ave\\\"}},\\n {name:\\\"Bill\\\"}, // Bill does not have an address!!\\n {name:\\\"Ava\\\", address: {street: \\\"Main St\\\"}}\\n]};\\n...\\n\\nTemplate:\\n{{for members}}\\n
        {{:name}} - {{:address.street}}
        \\n{{/for}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"The following example also illustrates setting debug mode to true, but this time it is used with JsViews, and the link(...) method, rather than JsRender and render(...).\\nThe error conditions can arise both in expressions within tags, such as {^{:manager.name}} and data-link expressions such as \\n {{for team}}\\n Owner: {^{:manager.name}}\\n {{/for}}\\n \\n Edit: \\n\\n\\n$.views.settings.debugMode(true);\\n\\nvar team = {owner:\\n {name:\\\"Jo\\\"}\\n}; // team.manager is undefined...\\n\\nvar tmpl = $.templates(\\\"#teamTmpl\\\");\\n\\ntmpl.link(\\\"#result\\\", {team: team}); // Error...\\nTemplate:\\nTeam:
        \\n {{if owner}}\\n Owner: {^{:manager.name}}\\n {{/if}}\\n
        \\nEdit: \\n\\nCode:\\n$.views.settings.debugMode(true);\\n// Debug mode is set to true, so error messages are rendered in place of the corresponding tag or data-link expression.\\n\\nvar team = {owner:\\n {name:\\\"Jo\\\"}\\n}; // team.manager is undefined...\\n...\\ntmpl.link(\\\"#result\\\", team); // Error...\\n\\nIf you choose Try it and change to $.views.settings.debugMode(false);, the error will instead be thrown as an exception.\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Setting debug mode to a string\",\r\n \"text\": \"Setting debug mode to a string\\nBy setting debug mode to a string rather than to true, no exception will be thrown, and the chosen string will be rendered, replacing the rendered tag.\\n$.views.settings.debugMode(\\\"Error!\\\");\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Debug mode set to a default string\",\r\n \"text\": \"Debug mode set to a default string\\n\\n\\n\\n{{for members}}\\n
        {{:name}} - {{:address.street}}
        \\n{{/for}}\\n\\n\\n$.views.settings.debugMode(\\\"Error!\\\"); // Do not throw exception - render \\\"Error!\\\"\\n\\nvar team = {members: [\\n {name:\\\"Jo\\\", address: {street: \\\"1st Ave\\\"}},\\n {name:\\\"Bill\\\"}, // Bill does not have an address!!\\n {name:\\\"Ava\\\", address: {street: \\\"Main St\\\"}}\\n]};\\n\\nvar html = $(\\\"#teamTmpl\\\").render(team);\\n\\n$(\\\"#result\\\").html(html);\\n\\n$.views.settings.debugMode(\\\"Error!\\\"); \\n\\nThe {{:address.street}} tag for Bill (who has no address) is replaced by \\\"Error!\\\".\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"In some scenarios the desired behavior may be to ignore errors during rendering, by skipping any tag with an error, rendering it as an empty string. This is achieved very easily, by simply writing:\\n$.views.settings.debugMode(\\\"\\\");\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Debug mode set to empty string\",\r\n \"text\": \"Debug mode set to empty string\\n\\n\\n\\n{{for members}}\\n
        {{:name}} - {{:address.street}}
        \\n{{/for}}\\n\\n\\n$.views.settings.debugMode(\\\"\\\"); // Do not throw exception - render \\\"\\\"\\n\\nvar team = {members: [\\n {name:\\\"Jo\\\", address: {street: \\\"1st Ave\\\"}},\\n {name:\\\"Bill\\\"}, // Bill does not have an address!!\\n {name:\\\"Ava\\\", address: {street: \\\"Main St\\\"}}\\n]};\\n\\nvar html = $(\\\"#teamTmpl\\\").render(team);\\n\\n$(\\\"#result\\\").html(html);\\n\\n$.views.settings.debugMode(\\\"\\\");\\n\\nThe {{:address.street}} tag for Bill (who has no address) is skipped.\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Providing a debug mode handler (function)\",\r\n \"text\": \"Providing a debug mode handler (function)\\nIf debug mode is set to a function, the function will be called each time an error is encountered during rendering.\\n\\nIf the function returns a string, then that string will be rendered, replacing the rendered tag\\nIf the function has no return value, then the error message will be rendered\\n\\n$.views.settings.debugMode(myOnErrorHandler);\\n\\nfunction myOnErrorHandler(e, fallback, view) {\\n // This handler will log the error, and then display the empty string\\n console.log(...);\\n return \\\"\\\"; \\n}\\n\\nThe parameters of the debug mode error handler function – myHandler(e, fallback, view) – will be:\\n\\ne – the error object\\nfallback – the fallback error string, provided by the onError fallback specified on the tag, if there is one\\nview – the current view object\\nThe this pointer will be the current data item, view.data\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Debug mode – onError handler\",\r\n \"text\": \"Debug mode – onError handler\\n\\n\\n\\n{{for members}}\\n
        Name: {{:name}}
        \\n {{:address.street onError='address'}}\\n
        \\n
        \\n{{/for}}\\n\\n\\nvar team = {\\n members: [\\n {name: \\\"Bill\\\", address: {street: \\\"1st Ave\\\"}},\\n {name: \\\"Jane\\\", address: undefined} // No address\\n ]\\n};\\n\\nfunction onErrorHandler(e, fallback, view) {\\n console.log(e.message);\\n if (fallback === \\\"address\\\") {\\n return 'Address error for ' + this.name + '. (\\\"' + e.message + '\\\")';\\n }\\n}\\n\\n$.views.settings.debugMode(onErrorHandler);\\n\\nvar html = $(\\\"#teamTmpl\\\").render(team);\\n\\n$(\\\"#result\\\").html(html);\\n\\n{{:address.street onError='address'}}\\n\\nfunction onErrorHandler(e, fallback, view) {\\n console.log(e.message);\\n if (fallback === \\\"address\\\") {\\n return 'Address error for ' + this.name + '. (\\\"' + e.message + '\\\")';\\n }\\n}\\n\\n$.views.settings.debugMode(onErrorHandler);\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Advanced debugging, using debugging helpers\",\r\n \"text\": \"Advanced debugging, using debugging helpers\\nInserting breakpoints during rendering:\\nJsRender (and JsViews) provide some helpers for debugging code within compiled templates:\\n\\nThe {{dbg expression/}} tag\\nThe {{dbg: expression}} converter\\nThe ~dbg(expression) helper function\\n\\nEach of the above will\\n\\nevaluate the expression\\noutput a console.log(...) call\\nthrow and catch an exception – which you can use as a break point by stopping on caught exceptions\\nrender the evaluated expression\\n\\nThis is done by inserting code into the compiled template which calls into the built-in dbgBreak code:\\nfunction dbgBreak(val) {\\n try {\\n console.log(\\\"JsRender dbg breakpoint: \\\" + val);\\n throw \\\"dbg breakpoint\\\"; // To break here, stop on caught exceptions.\\n }\\n catch (e) {}\\n\\nval will be the result of evaluating expression.\\nWhen rendering execution breaks at the above code, you can then step up through the call stack to the compiled template code, for further debugging.\\nUsage examples: {{dbg:...}}, {{:~dbg(...)}}, {{dbg .../}} etc.\\nBreakpoints during data linking:\\nIn JsViews, a breakpoint can also be inserted during template data-linking, as in {^{for ... onAfterLink=~dbg}}.\\nUsing {{*debugger}}:\\nAn alternative (but similar) debugging technique is to use allowCode to insert a debugger; statement directly into the compiled template code, as follows:\\nCode:\\nvar tmpl = $.templates({\\n markup: \\\"#myTmpl\\\",\\n allowCode: true // Alternatively use global setting: $.views.settings.allowCode(true)\\n});\\n\\nTemplate:\\n...\\n{{*debugger}}\\n...\\n\\n\"\r\n }\r\n ]\r\n },\r\n \"advanced\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"\",\r\n \"text\": \"\"\r\n }\r\n ]\r\n },\r\n \"apps\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Apps using JsRender\",\r\n \"text\": \"Apps using JsRender\\nJsRender is a simple light-weight templating engine. It can be used in the browser within simple web pages, or within complex single-page apps, or in conjunction with other frameworks. It can also be used on the server, using Node.js.\\nIt is highly flexible, expressive, and ‘unopinionated’ – so it leaves you free to work within your own choice of overall application architecture (including architectures based on MVVM, MVP or MVC – optionally with server/client integration), and lets you use your own flavor of data/model layer – whether simple plain JavaScript objects, hand-coded View Model instances, or compiled View Models.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Components of an app using JsRender\",\r\n \"text\": \"Components of an app using JsRender\\nAny app or web page using JsRender templates will generally involve defining or registering the following elements:\\n\\none or more templates – see Templates\\na ‘data Layer’ – see JsRender: Data or View Model\\noptionally, helpers – in the form of metadata, helper functions and converter functions, see Helpers and Converters\\noptionally, reusable components for use within your templates – see Custom tags\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Apps using JsViews\",\r\n \"text\": \"Apps using JsViews\\nJsRender also provides optional integration with JsViews. JsViews is much more of a framework than JsRender. It does much more than just templating – providing also data-binding, MVVM support, observability of the data/View Model layer, support for interactive encapsulated components (JsViews tag controls), and more. Nevertheless, it can also interoperate with other frameworks and components. See Building apps in JsViews for more information.\\n\"\r\n }\r\n ]\r\n },\r\n \"getindex\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"If you pass an array to the JsRender .render(myArray) method, or if you use {{for myArray}}, in a template, JsRender will iterate over the array, and render an item view for each item in the array.\\nWithin an item view you can access the array-index of the current item, using {{:#index}}:\\n\\nGetting item index within a top-level item view (from .render(myArray)):\\n...\\n{{:#index}}\\n...\\n\\nGetting item index within a {{for myArray}} block:\\n{{for myArray}}\\n ...\\n {{:#index}}\\n ...\\n{{/for}}\\n\\n\\nIf there are additional nested tags, then from within the nested tags you can still access the index, by using {{:#getIndex()}}:\\n\\nGetting item index from nested tags within an item view:\\n{{for myArray}}\\n ...\\n {{if ...}}\\n ...\\n {{:#getIndex()}}\\n ...\\n {{/if}}\\n ...\\n{{/for}}\\n\\n\\nSee index and getIndex() for additional details.\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"See also\",\r\n \"text\": \"See also\\n\"\r\n }\r\n ]\r\n },\r\n \"contextualparams\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Defining contextual parameters\",\r\n \"text\": \"Defining contextual parameters\\nContextual parameters provide a very convenient way of passing values in to nested tag contexts. (See View hierarchy.)\\nA contextual parameter is defined by simply writing ~myValue=... (for any expression) on any block tag, such as {{if}} or {{for}}.\\nThe resulting ~myValue parameter can then be accessed within the block tag – or deeper down within nested tag contexts, at any depth.\\nFor example, the following template defines three contextual parameters, and uses them in nested contexts:\\n...\\n{{if isActive ~teamTitle=title ~teamData=#data ~teamIndex=#index}}\\n {{for members}}\\n {{if ~teamIndex>2}}\\n {{:~teamTitle}} {{:~teamData.description}}\\n ...\\n\\nNote: You can also set contextual parameters on {{else}} blocks, such as in the following example which uses the same template for the {{if}} and {{else}} blocks, but assigns different values to the ~teamTitle parameter in each case:\\n{{if isActive ~teamTitle=activeTitle tmpl=\\\"teamTmpl\\\"}}\\n{{else ~teamTitle=inactiveTitle tmpl=\\\"teamTmpl\\\"}}\\n{{/if}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"itemVar – contextual parameter for data 'item' of block\",\r\n \"text\": \"itemVar – contextual parameter for data 'item' of block\\nThe itemVar feature lets you set up a contextual parameter for the current data ‘item’ of a block. It is in effect an ‘alias’ for #data within the block.\\nTo define an itemVar contextual parameter for a block tag, simply write itemVar=~someName. The parameter ~someName can then be accessed like any other helper variable or contextual parameter, within nested contexts to any depth.\\n...\\n{{for teams itemVar=\\\"~team\\\"}}\\n ...\\n {{for members itemVar=\\\"~member\\\"}}\\n ...\\n {{if isActive}}\\n {{:~team.title}} {{:~member.name}}\\n\\nSee also this sample.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Accessing root data: the built-in '~root' contextual parameter\",\r\n \"text\": \"Accessing root data: the built-in '~root' contextual parameter\\nThe built-in contextual parameter ~root provides direct access to the root data which was passed to the render() method (or link() method if you are using JsViews). It can be accessed from anywhere within a template, at an level of nested tags.\\nNote: If an array is passed to render() or link() then ~root will be the array (so you can render {{:root.length}} for example).\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"See also:\",\r\n \"text\": \"See also:\\n\"\r\n }\r\n ]\r\n },\r\n \"parentdata\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Accessing \\\"parent\\\" data, from nested views. Passing in template variables\",\r\n \"text\": \"Accessing \\\"parent\\\" data, from nested views. Passing in template variables\\nWhen a template (containing nested template tags) is rendered, the result is a view hierarchy – where the views provide information on how the underlying data objects map to the rendered UI.\\nOften it is helpful to be able to access the data for a parent view from a nested template or block (nested view).\\nThere are several ways to get to parent data:\\n\\nCreate a contextual parameter to pass a value to nested views.\\nHere are three examples:\\n...\\n{{if ... ~teamTitle=title ~teamData=#data ~teamIndex=#index}}\\n ...\\n {{for ...}}\\n ...\\n {{:~teamTitle}} {{:~teamData.title}} {{:~teamIndex}}\\n\\nUse itemVar to provide a contextual parameter for the current data ‘item’ of a block, to be passed in to deeper nested contexts\\n...\\n{{for members itemVar=\\\"~member\\\"}}\\n ...\\n {{props}}\\n ...\\n {{:~member.name}}\\n\\nUse the view.parent property to step up through successive parent views (#parent, #parent.parent etc.):\\n...\\n{{if ...}}\\n ...\\n {{for ...}}\\n ...\\n {{:#parent.parent.data.title}}\\n\\nUse the view.get(type) method to get to a parent view of a given type:\\n...\\n{{if ...}}\\n ...\\n {{for ...}}\\n ...\\n {{:#get(\\\"if\\\").data.title}}\\n\\n\\nUse the view.getIndex() method to get to the index of a parent “item” view:\\n{{if ...}}\\n ...\\n {{for ...}}\\n ...\\n {{:#parent.getIndex()}}\\n {{:#getIndex()}}\\n\\n\\nHere is a sample showing all of these methods:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"text\": \"\\n
        \\n Team: {{:title}} -\\n {{mytag members/}}\\n\\n {{if members.length\\n ~teamTitle=title\\n ~teamData=#data\\n ~teamIndex=#index\\n }}\\n Members:
          \\n {{for members\\n itemVar=\\\"~member\\\"\\n }}\\n
        • \\n {{:name}}\\n (\\n {{:~teamTitle}}\\n {{:~teamData.title}}\\n {{:#parent.parent.data.title}}\\n {{:#get(\\\"if\\\").data.title}}\\n )\\n
          \\n [\\n {{:~teamIndex}}\\n = {{:#parent.getIndex()}}\\n : {{:#getIndex()}}\\n ]\\n
          \\n {{props}}\\n {{:key}}: {{:prop}}\\n (\\n {{:~member.name}}\\n )\\n {{/props}}\\n
        • \\n {{/for}}\\n
        \\n {{/if}}\\n
        \\n\\n\\n\\n// mytag: custom tag to output \\\"1 member\\\" or \\\"n members\\\"\\n$.views.tags(\\\"mytag\\\", \\\"{{:length == 1 ? '1 member' : length + ' members'}}\\\");\\n// Alternative version of mytag:\\n// $.views.tags(\\\"mytag\\\", \\\"{{if length == 1}}1 member{{else}}{{:length}} members{{/if}}\\\");\\n\\nvar teams = [\\n {title: \\\"The A Team\\\", members: [{name: \\\"Jeff\\\"}, {name: \\\"Maria\\\"}]},\\n {title: \\\"The B Team\\\", members: [{name: \\\"Francis\\\"}]}\\n];\\n\\nvar html = $(\\\"#teamTemplate\\\").render(teams);\\n\\n$(\\\"#result\\\").html(html);\\nThis sample shows all the ways to get to parent data described in the section above:\\n\\nCreate a contextual parameter to pass a value to nested views.\\nUse itemVar to provide a contextual parameter for the current data ‘item’ of a block, to be passed in to deeper nested contexts\\nUse the view.parent property to step up through successive parent views (#parent, #parent.parent etc.):\\nUse the view.get(type) method to get to a parent view of a given type:\\nUse the view.getIndex() method to get to the index of a parent “item” view:\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"See also:\",\r\n \"text\": \"See also:\\n\"\r\n }\r\n ]\r\n },\r\n \"jsrmodel\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"JsRender is designed to work well with either plain JavaScript objects and arrays, or with instances of JavaScript classes, such as View Model classes.\\nSo, for example, if you are using data obtained from a JSON request, you can choose between:\\n\\nrendering your templates directly against the objects and arrays returned from the JSON request\\npassing the data through a ‘mapping’ process to create a hierarchy of View Model instances, and rendering your templates against those objects\\n\\nThe plain objects approach is convenient and simple for getting rapidly up and running with templates. But for more complex projects the View Model approach is better for creating clean well-designed modular code, where each View Model has specific getters, setters and methods, and can have its own ‘private’ properties and state.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using JsRender built-in compiled View Models\",\r\n \"text\": \"Using JsRender built-in compiled View Models\\nJsRender will work well with your own ‘hand-coded’ View Model classes (see below).\\nBut in most cases it is simpler and better to use the $.views.viewModels(...) API. This API lets you very easily and rapidly compile View Model classes for your own needs, following a standard pattern, and with some additional powerful features:\\n\\nIt provides a built-in mapping and unmapping feature for automatically converting from a plain object hierarchy (such as from a JSON request) to a hierarchy of View Model instances, or for converting back to plain data (such as for submitting to the server)\\nIt also provides a merge(...) feature for incrementally updating the View Model hierarchy, using updated plain data from the server.\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Data / View Model with JsViews\",\r\n \"text\": \"Data / View Model with JsViews\\nAll of the alternatives mentioned above (plain object hierarchies, hand-coded View Model classes, or JsRender compiled View Model classes) can also be used with JsViews data-binding and observable data. (For more information see JsViews: Data / View Model and JsViews: Compiled View Models.)\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Example: JsRender with plain objects and arrays\",\r\n \"text\": \"Example: JsRender with plain objects and arrays\\n\"\r\n },\r\n {\r\n \"_type\": \"code\",\r\n \"title\": \"Suppose this is our data from a JSON request:\",\r\n \"text\": \"Suppose this is our data from a JSON request:\\nvar person = {\\n name: \\\"Pete\\\",\\n address: {\\n street: \\\"1st Ave\\\"\\n },\\n phones: [{number: \\\"111 111 1111\\\"}, {number:\\\"222 222 2222\\\"}] \\n};\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"template\",\r\n \"title\": \"We'll render using a template structured like this:\",\r\n \"text\": \"We'll render using a template structured like this:\\n... \\n{{:name}}\\n...\\n{{:address.street}}\\n...\\n{{for phones}}\\n ... \\n {{:number}}\\n ...\\n{{/for}}\\n...\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Render template directly against plain objects...\",\r\n \"text\": \"Render template directly against plain objects...\\n\\n\\n\\n \\n \\n \\n \\n
        Name:{{:name}}
        Street:{{:address.street}}
        Phones:\\n \\n {{for phones}}\\n \\n {{/for}}\\n
        {{:number}}
        \\n
        \\n\\n// Compiled template\\nvar tmpl = $.templates(\\\"#personTmpl\\\");\\n\\n// Data: hierarchy of plain objects and arrays\\nvar person = {\\n name: \\\"Pete\\\",\\n address: {\\n street: \\\"1st Ave\\\"\\n },\\n phones: [{number: \\\"111 111 1111\\\"}, {number:\\\"222 222 2222\\\"}] \\n};\\n\\n// Render template against plain object hierarchy\\n$(\\\"#result\\\").html(tmpl.render(person));\\n\\n\\n\\n... {{:name}} ...\\n\\nRender template against person (plain object)\\n\\n$(\\\"#result\\\").html(tmpl.render(person));\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Now we’ll convert the above sample to use View Model classes.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Example: JsRender with 'hand-coded' View Model objects\",\r\n \"text\": \"Example: JsRender with 'hand-coded' View Model objects\\nWe’ll convert the data to a corresponding hierarchy of simple ‘hand-coded’ View Model class instances. In each case we will replace properties by simple getters, and corresponding ‘private’ properties.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"View Model classes:\",\r\n \"text\": \"View Model classes:\\nHere is the class definition for Person:\\n// Constructor\\nfunction Person(name, address, phones) {\\n // Initialize private properties\\n this._name = name;\\n this._address = address;\\n this._phones = phones;\\n}\\n\\n// Prototype\\nvar personProto = {\\n // Define a getter for each property \\n name: function() {\\n return this._name;\\n },\\n address: function() {\\n return this._address;\\n },\\n phones: function() {\\n return this._phones;\\n }\\n};\\n...\\n\\nWe define exactly similar classes for our Address and Phone objects too.\\nThe above pattern for View Model classes will work well with JsRender. (It will also work seamlessly with JsViews data-binding, if at some point you choose to upgrade to use JsViews features).\\nNote: The standard JsRender View Model pattern provided by $.views.viewModels is similar, but provides also setters (along with optional ‘observability’ for two-way binding in JsViews).\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Getter functions\",\r\n \"text\": \"Getter functions\\nNote that properties are now getter functions, which return the appropriate value (which may be of any type, including objects or arrays – such as address and phones above).\\nIn fact they are particular case of computed properties – a concept that can be used quite generally within JsRender and JsViews, not only for View Model properties.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Template\",\r\n \"text\": \"Template\\nTo convert our template from using plain objects to using View Model objects, the only change we need to make is to add parens for our properties, which are now getter functions:\\n... \\n{{:name()}}\\n...\\n{{:address().street()}}\\n...\\n{{for phones()}}\\n ... \\n {{:number()}}\\n ...\\n{{/for}}\\n...\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Instantiate and render:\",\r\n \"text\": \"Instantiate and render:\\nNow all we need to do is to construct our root person object (with its underlying hierarchy of View Model instance objects) and render the template against that object in the usual way.\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Render template against a View Model object hierarchy\",\r\n \"text\": \"Render template against a View Model object hierarchy\\n\\n\\n\\n\\n\\n \\n \\n \\n \\n
        Name:{{:name()}}
        Street:{{:address().street()}}
        Phones:\\n \\n {{for phones()}}\\n \\n {{/for}}\\n
        \\n {{:number()}}\\n
        \\n
        \\n\\n// Compiled template\\nvar tmpl = $.templates(\\\"#personTmpl\\\");\\n\\n// Instantiate View Model hierarchy\\nvar person = new Person(\\n \\\"Pete\\\",\\n new Address(\\\"1st Ave\\\"),\\n [\\n new Phone(\\\"111 111 1111\\\"),\\n new Phone(\\\"222 222 2222\\\")\\n ]\\n );\\n\\n// Render template against person object (instance of Person)\\n$(\\\"#result\\\").html(tmpl.render(person));\\n... {{:name()}} ...\\n\\n\\nInstantiate View Model hierarchy\\n\\n// Use previously defined View Model classes: Person, Address, Phone\\nvar person = new Person(\\n \\\"Pete\\\",\\n new Address(\\n \\\"1st Ave\\\"),\\n [\\n new Phone(\\\"111 111 1111\\\"),\\n new Phone(\\\"222 222 2222\\\")\\n ]\\n );\\n\\n\\nRender template against person object (instance of Person)\\n\\n$(\\\"#result\\\").html(tmpl.render(person));\\n\\n// View Model class definitions using getter pattern\\n\\n// Person\\nfunction Person(name, address, phones) {\\n this._name = name;\\n this._address = address;\\n this._phones = phones;\\n}\\n\\nvar personProto = {\\n name: function() {\\n return this._name;\\n },\\n phones: function() {\\n return this._phones;\\n },\\n address: function() {\\n return this._address;\\n }\\n};\\n\\nPerson.prototype = personProto;\\n\\n// Address\\nfunction Address(street) {\\n this._street = street;\\n}\\n\\nvar addressProto = {\\n street: function() {\\n return this._street;\\n }\\n};\\n\\nAddress.prototype = addressProto;\\n\\n// Phone\\nfunction Phone(number) {\\n this._number = number;\\n}\\n\\nvar phoneProto = {\\n number: function() {\\n return this._number;\\n }\\n};\\n\\nPhone.prototype = phoneProto;\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using the same function as both getter and setter\",\r\n \"text\": \"Using the same function as both getter and setter\\nFor properties which are read-write, the above getter functions can be replaced by a corresponding getter/setter, as follows:\\nname: function(val) {\\n if (!arguments.length) {\\n return this._name; // If there is no argument, use as a getter\\n }\\n this._name = val; // If there is a value argument, treat as a setter\\n},\\n\\nNote that when JsRender renders a template using a get/set property {{:name()}} it will always call the function as a getter, not as a setter. However the setter feature lets you modify the value of name() from code, using:\\nsomePerson.name(\\\"newName\\\"); // setter\\n\\nAlso, if you use the same View Model class with JsViews then the setter will be called:\\n\\nwhen the user modifies a value with two-way data-binding such as \\nwhen using $.observable(person).setProperty(\\\"name\\\", \\\"newName\\\") from code\\n(See JsViews Data/View Model for details, and alternative setter patterns.)\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Adding methods and computed properties to the View Model \",\r\n \"text\": \"Adding methods and computed properties to the View Model \\nTypically a View Model does not only provide getter (or get/set) properties – but also other methods or computed properties corresponding to the appropriate logic at that point in the application. For example, a View Model for a Person might include a selectPhone(...) method or a fullName() computed property.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Example: Using JsRender compiled View Models, with $.view.viewModels(...)\",\r\n \"text\": \"Example: Using JsRender compiled View Models, with $.view.viewModels(...)\\nThe built-in support in both JsRender and JsViews for compiled View Models makes it extremely easy to define View Model classes that include get/set properties using the pattern described above, along with any desired additional methods and computed properties. Simple calls to $.views.viewModels(...) allow you to compile View Model classes conforming to these patterns without having to manually write repetitive code for multiple such get/set properties.\\nAnother advantage of the compiled View Model classes is when working with (or migrating to) JsViews. In that context the classes automatically become fully-fledged MVVM classes, with a rich range of features – where the Views are observable data-linked templates.\\nFor details on $.views.viewModels see: Compiled View Models.\\nTo illustrate, let’s convert our sample above to use compiled View Models. At the same time we will add a person.addPhone(...) custom method to the Person View Model class, and we’ll illustrate calling a setter – name(...):\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Render template against a hierarchy of compiled View Model objects\",\r\n \"text\": \"Render template against a hierarchy of compiled View Model objects\\nbutton {margin-bottom: 9px;}\\n\\nChange name\\nAdd Phone\\n\\n\\n\\n\\n \\n \\n \\n \\n
        Name:{{:name()}}
        Street:{{:address().street()}}
        Phones:\\n \\n {{for phones()}}\\n \\n {{/for}}\\n
        \\n {{:number()}}\\n
        \\n
        \\n\\n// Compiled template\\nvar tmpl = $.templates(\\\"#personTmpl\\\");\\n\\n// Method for Person class\\nfunction addPhone(phoneNo) {\\n // Uses Phone() View Model constructor to create Phone instance\\n this.phones().push(Phone(phoneNo));\\n}\\n\\n// Compile Person View Model, with addPhone method\\nvar Person = $.views.viewModels({\\n getters: [\\\"name\\\", \\\"address\\\", \\\"phones\\\"],\\n extend: {addPhone: addPhone}\\n});\\n\\n// Compile Address View Model\\nvar Address = $.views.viewModels({getters: [\\\"street\\\"]});\\n\\n// Compile Phone View Model\\nvar Phone = $.views.viewModels({getters: [\\\"number\\\"]});\\n\\n// Instantiate View Model hierarchy using constructors\\nvar person = Person(\\n \\\"Pete\\\",\\n Address(\\\"1st Ave\\\"),\\n [Phone(\\\"111 111 1111\\\"), Phone(\\\"222 222 2222\\\")]\\n);\\n\\n// Render template against person object (instance of Person)\\n$(\\\"#result\\\").html(tmpl.render(person));\\n\\n// Button handlers\\n$(\\\"#changeName\\\").on(\\\"click\\\", function() {\\n person.name(\\\"newName\\\"); // Use the name(...) setter\\n $(\\\"#result\\\").html(tmpl.render(person));\\n});\\n\\n$(\\\"#addPhone\\\").on(\\\"click\\\", function() {\\n person.addPhone(\\\"xxx xxx xxxx\\\"); // Call the addPhone(...) method\\n $(\\\"#result\\\").html(tmpl.render(person));\\n});\\n\\n... {{:name()}} ...\\n\\nCompile View Model classes\\n\\n...\\n// Compile Person View Model, with addPhone method\\nvar Person = $.views.viewModels({\\n getters: [\\\"name\\\", \\\"address\\\", \\\"phones\\\"], // get/set properties\\n extend: {addPhone: addPhone} // Additional methods or properties\\n});\\n...\\n\\nInstantiate View Model hierarchy using constructors\\n\\nvar person = Person(\\n \\\"Pete\\\",\\n Address(\\\"1st Ave\\\"),\\n [Phone(\\\"111 111 1111\\\"), Phone(\\\"222 222 2222\\\")]\\n);\\n\\n\\nRender template against person object (instance of Person)\\n\\n$(\\\"#result\\\").html(tmpl.render(person));\\n\\nCall setter, call method...\\n\\n...\\nperson.name(\\\"newName\\\"); // Use the name(...) setter\\n\\n...\\nperson.addPhone(\\\"xxx xxx xxxx\\\"); // Call the addPhone(...) method\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"See also the corresponding sample with JsViews and data-linking (and this version with two-way binding).\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"For additional details and scenarios for compiled View Models, see:\",\r\n \"text\": \"For additional details and scenarios for compiled View Models, see:\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"See also:\",\r\n \"text\": \"See also:\\n\"\r\n }\r\n ]\r\n },\r\n \"helpersapi\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"$.views.helpers() is used to register helpers, accessed within templates using the syntax ~myhelper. See Using helpers for information about what helpers are, and some additional ways of providing them to templates.\\nThis topic provides more details.\\nWith $.views.helpers(...) you can:\\n\\nregister one or more helpers globally, to be used in any template\\nadd one or more helpers as private resources for a parent template\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Registering one or more helpers\",\r\n \"text\": \"Registering one or more helpers\\n\"\r\n },\r\n {\r\n \"_type\": \"api\",\r\n \"title\": \"$.views.helpers(...)\",\r\n \"text\": \"$.views.helpers(...)\\nRegister a helper, for use in any template with the syntax:~name\\n\\n$.views.helpers(\\\"format\\\", myFormatFunction);\\n\\nRegister multiple helpers\\n\\n$.views.helpers({\\n format: myFormatFunction,\\n utilities: {},\\n mode: \\\"filtered\\\"\\n});\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Here is an example using a ‘hierarchy’ of helpers…\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Register multiple helpers, including objects, etc.\",\r\n \"text\": \"Register multiple helpers, including objects, etc.\\n\\n\\n\\n {{:~format(title, true)}}\\n\\n - availability:\\n {{if ~mode===\\\"filtered\\\"}}\\n \\n {{:~utilities.subtractMax(sold) > 0\\n ? ~utilities.errorMessages.msg1\\n : \\\"immediate\\\"\\n }}\\n \\n {{/if}}\\n\\nfunction myFormatFunction(value, upper) {\\n return upper ? value.toUpperCase() : value.toLowerCase();\\n}\\n\\n$.views.helpers({\\n format: myFormatFunction,\\n utilities: {\\n maxCount: 23,\\n subtractMax: function(val) {\\n return val - this.maxCount;\\n },\\n errorMessages: {\\n msg1: \\\"not available\\\"\\n }\\n },\\n mode: \\\"filtered\\\"\\n});\\n\\nvar html = $(\\\"#myTemplate\\\").render({title: \\\"gizmo\\\", sold: 27});\\n\\n$(\\\"#result\\\").html(html);\\nHere is an example using a ‘hierarchy’ of helpers…\\n$.views.helpers({\\n ...\\n utilities: {\\n maxCount: 23,\\n subtractMax: function(val) {\\n return val - this.maxCount;\\n },\\n errorMessages: {\\n msg1: \\\"not available\\\"\\n }\\n },\\n ...\\n});\\n\\n{{:~utilities.subtractMax(sold) > 0\\n ? ~utilities.errorMessages.msg1\\n : \\\"immediate\\\"\\n}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Adding helpers as private resources for a parent template\",\r\n \"text\": \"Adding helpers as private resources for a parent template\\nYou can pass in an existing template as an additional parentTemplate parameter, on any call to $.views.helpers(...).\\nIn that way the helper you are registering becomes a ‘private helper resource’ for the parentTemplate, rather than being registered globally:\\n\"\r\n },\r\n {\r\n \"_type\": \"api\",\r\n \"title\": \"$.views.helpers(namedHelpers[, parentTemplate])\",\r\n \"text\": \"$.views.helpers(namedHelpers[, parentTemplate])\\nAdd one or more helpers as private resources for a parent template\\n\\n$.views.helpers({\\n format: myFormatFunction,\\n ...\\n}, parentTemplate);\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"See also:\",\r\n \"text\": \"See also:\\n\"\r\n }\r\n ]\r\n },\r\n \"helpers\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"(See also Registering helpers: The $.views.helpers() API.)\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"What are helpers?\",\r\n \"text\": \"What are helpers?\\nJsRender templates are made up of HTML markup, text, and template tags. Template tags are used to evaluate data-paths or computed expressions, and insert those values into the rendered output.\\nBut often the values you will want to insert are not actually taken from the data, but rather from other parameters or metadata which you want to use. And often you will want to process the values, using helper functions or other code, e.g. for converting values to other formats, or for computed values.\\nHelpers, in JsRender, refers to any functions, objects, parameters or metadata which you want to provide, in addition to the actual data you passed to the render() method (or link() method if you are using JsViews).\\nHelpers can also be objects, arrays, etc.\\nYou access helpers by prepending the ~ character. Here are some examples:\\n{{:~myHelperValue}}\\n{{:~myHelperFunction(name, title)}}\\n{{for ~myHelperObject.mySortFunction(people, \\\"increasing\\\")}} ... {{/for}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Passing in helpers\",\r\n \"text\": \"Passing in helpers\\nThere are three ways to provide helpers:\\n\\nGlobal helpers – registered using $.views.helpers(myHelpers)\\nHelpers registered for a specific template – $.templates(\\\"mytmpl\\\", {markup: ..., helpers: myHelpers}\\nHelpers passed in on a specific render call – tmpl.render(data, myHelpers)\\n(Similarly you can pass helpers to JsViews link() calls)\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Contextual parameters\",\r\n \"text\": \"Contextual parameters\\nIn addition to providing helpers as above, you can also define contextual parameters within a template, which you access using the same ~someName syntax as for regular helpers.\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Global helper: $.views.helpers(...)\",\r\n \"text\": \"Global helper: $.views.helpers(...)\\n\\n\\n\\n {{:~format(name, true)}}\\n\\nfunction myFormatFunction(value, upper) {\\n return upper ? value.toUpperCase() : value.toLowerCase();\\n}\\n\\nvar myHelpers = {format: myFormatFunction};\\n\\n$.views.helpers(myHelpers);\\n\\nvar html = $(\\\"#personTemplate\\\").render({name: \\\"Robert\\\"});\\n\\n$(\\\"#person\\\").html(html);\\nvar myHelpers = {format: myFormatFunction};\\n\\n$.views.helpers(myHelpers);\\n\\n{{:~format(name, true)}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Helper resource for a specific template\",\r\n \"text\": \"Helper resource for a specific template\\n\\n\\n\\n {{:~format(name)}}\\n {{:~format(name, true)}}\\n\\nfunction myFormatFunction(value, upper) {\\n return upper ? value.toUpperCase() : value.toLowerCase();\\n}\\n\\nvar myHelpers = {format: myFormatFunction};\\n\\n$.templates({\\n mytmpl: {\\n markup: \\\"#personTemplate\\\",\\n helpers: myHelpers\\n }\\n});\\n\\nvar html = $.render.mytmpl({name: \\\"Robert\\\"});\\n\\n$(\\\"#person\\\").html(html);\\nvar myHelpers = {format: myFormatFunction};\\n\\n$.templates({\\n mytmpl: {\\n markup: \\\"#personTemplate\\\",\\n helpers: myHelpers\\n }\\n});\\n\\n{{:~format(name)}}\\n{{:~format(name, true)}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Passing helpers with a render() call\",\r\n \"text\": \"Passing helpers with a render() call\\n\\n\\n\\n {{:~format(name, true)}}\\n {{:~format(name)}}\\n\\nfunction myFormatFunction(value, upper) {\\n return upper ? value.toUpperCase() : value.toLowerCase();\\n}\\n\\nvar data = {name: \\\"Robert\\\"};\\n\\nvar myHelpers = {format: myFormatFunction};\\n\\nvar html = $(\\\"#personTemplate\\\").render(data, myHelpers); \\n\\n$(\\\"#person\\\").html(html);\\nvar myHelpers = {format: myFormatFunction};\\n\\nvar html = $(\\\"#personTemplate\\\").render(data, myHelpers); \\n\\n{{:~format(name, true)}}\\n{{:~format(name)}}\\n\\nSee template.render(...)\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"For additional details and scenarios see:\",\r\n \"text\": \"For additional details and scenarios see:\\nRegistering helpers: The $.views.helpers() API\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"See also:\",\r\n \"text\": \"See also:\\n\"\r\n }\r\n ]\r\n },\r\n \"convertersapi\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"See Using converters for an overview of what converters are, and some examples.\\nThis topic provided more details.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using custom or built-in converters\",\r\n \"text\": \"Using custom or built-in converters\\nIn JsRender, a converter is a convenient way of processing or formatting a data-value, or the result of expression evaluation.\\nYou use built-in converters to HTML-encode, attribute-encode, or URL-encode:\\n{{html:movie.description}} - This data is HTML encoded\\n{{>movie.description}} - (Alternative syntax) - This data is HTML encoded\\n\\n{{url:~getTheFilePath()}} - This expression will be URL-encoded\\n\\nAnd you can register custom converters. For example you might register a date formatter or an upper-case converter:\\n{{daymonth:invoice.date}} - This date uses my 'daymonth' formatter \\n{{upper:name}} - This uses my 'upper' converter \\n\\n(See: sample.)\\nYou can also use converters with any JsRender tag, not just the {{: ...}} tag, using the following syntax:\\n{{sometag convert='myconverter' ...}}\\n\\n(See: sample.)\\nNote: With JsViews, you can use converters with two-way data-binding, and you will have a convert and a convertBack converter – one for each direction.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Registering converters\",\r\n \"text\": \"Registering converters\\n$.views.converters() is used to register converters.\\nWith $.views.converters(...) you can:\\n\\nregister one or more converters globally, to be used in any template\\nadd one or more converters as private resources for a parent template\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Registering one or more converters\",\r\n \"text\": \"Registering one or more converters\\nA simple sample of registering a converter is shown here.\\n\"\r\n },\r\n {\r\n \"_type\": \"api\",\r\n \"title\": \"$.views.converters(...)\",\r\n \"text\": \"$.views.converters(...)\\nRegister a converter\\n\\n$.views.converters(\\\"upper\\\", function(val) {\\n return val.toUpperCase();\\n});\\n\\n{{upper: \\\"upper case: \\\" + nickname}}\\n\\nRegister multiple converters\\n\\n$.views.converters({\\n upper: function(val) {...},\\n lower: function(val) {...}\\n});\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Adding converters as private resources for a parent template\",\r\n \"text\": \"Adding converters as private resources for a parent template\\nYou can pass in an existing template as an additional parentTemplate parameter, on any call to $.views.converters(...).\\nIn that way the converter you are registering becomes a ‘private converter resource’ for the parentTemplate, rather than being registered globally:\\n\"\r\n },\r\n {\r\n \"_type\": \"api\",\r\n \"title\": \"$.views.converters(...) — adding to parent template\",\r\n \"text\": \"$.views.converters(...) — adding to parent template\\nRegister a converter as private resources for a parent template\\n\\n$.views.converters(\\n \\\"upper\\\",\\n function(val) { ... },\\n parentTemplate\\n);\\n\\nAdd one or more converters as private resources for a parent template\\n\\n$.views.converters({\\n upper: function(val) {...},\\n lower: function(val) {...}\\n}, parentTemplate);\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Unregister a named converter\",\r\n \"text\": \"Unregister a named converter\\nTo unregister a previously registered converter, pass null to $.views.converters():\\n$.views.converters(\\\"myCvt\\\", null);\\n// Named converter \\\"myCvt\\\" is no longer registered\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Converter functions\",\r\n \"text\": \"Converter functions\\nIn most cases a converter function will return a computed value based on the input parameter val:\\nfunction myConverter(val) {\\n ... \\n return computedVal; // converted/encoded/formatted value for 'val'\\n}\\n\\nwhere val comes from the data value or expression passed to the tag {{myconverter: someExpression}}.\\n(See: sample.)\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Converter function signature\",\r\n \"text\": \"Converter function signature\\nHowever a converter can access multiple tag arguments, to produce the computed value which it provides to the tag. (See for example {{fullname: first last ...}}, in the fullname sample.)\\nFurthermore, the this pointer within the converter function is the instance of the tag, which allows it to access much more, including named tag parameters (this.tagCtx.props...), the full data object (this.tagCtx.view.data), and more…\\nfunction myConverter(arg1, arg2, arg3 ...) {\\n var tag = this;\\n var namedTagParameters = tag.tagCtx.props; \\n ...\\n return computedArg1; // converted value for 'arg1' passed to tag\\n}\\n\\nHere is the converterFn API definition:\\n\"\r\n },\r\n {\r\n \"_type\": \"api\",\r\n \"title\": \"function converterFn(val, ...) {...}\",\r\n \"text\": \"function converterFn(val, ...) {...}\\nA converter function registered using $.views.converters(...)\\nConverter function:parameters: one or more tag argumentsthis pointer: the tag instancecomputes return value: which is passed to tag as first argument\\n\\nfunction myConverterFn(val1, val2, ...) {\\n var tag = this;\\n var tagProperties = tag.tagCtx.props;\\n ...\\n return ...;\\n}\\n\\n$.views.converters(\\\"myconverter\\\", myConverterFn);\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using converters with other tags\",\r\n \"text\": \"Using converters with other tags\\nA converter can be used on any tag, thanks to the syntax\\n{{sometag ... convert=...}}\\n\\nwhere sometag can be any custom tag, or a built-in tag such as {{if}} or {{for}}.\\nSee the sample using {{for people convert='extraItems'}}, where the converter adds additional items to the array.\\n(Note: This syntax can actually be used with the {{: ...}} tag too – by writing {{:name convert='upper'}}…)\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Example: a converter for {{if}}\",\r\n \"text\": \"Example: a converter for {{if}}\\nHere is an advanced sample: an \\\"inlist\\\" converter for {{if}}.\\n\\nIt accepts an item argument and a list argument, and an optional field named property\\nIt returns true if the item is found in the list\\nIf there is a field specified, it takes the value of that field (property) on the item and searches for it in the list\\n\\nNote that the converter gets called once for the first {{if}} tag block and once for each subsequent {{else}} block.\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"'inlist' converter for {{if}} tag\",\r\n \"text\": \"'inlist' converter for {{if}} tag\\n\\n\\n\\n
          \\n {{for people}}\\n
        • \\n {{:name}}:\\n {{if #data ~root.team convert='inlist'}}\\n Team member\\n {{else #data ~root.reserve field=\\\"name\\\"}}\\n Reserve\\n {{else}}\\n Not in team\\n {{/if}}\\n
        • \\n {{/for}}\\n
        \\n\\nvar teamTmpl = $.templates(\\\"#teamTmpl\\\");\\n\\n// Converter function for looking for an item (first argument of tag) in a list (second argument of tag)\\nfunction inlistConverter(item, list) {\\n // If no arguments, this is the final {{else}}\\n if (!list) {\\n return true; // Final else, so return true\\n }\\n\\n var field = this.tagCtx.props.field;\\n var l = list.length;\\n\\n // If the tag has a 'field' property, look for the value of that field among the list items\\n if (field) {\\n while (l--) {\\n if (item[field] === list[l]) {\\n return true; // Return true if found\\n }\\n }\\n }\\n\\n // If no field property, look for the item among the list items\\n else {\\n while (l--) {\\n if (item === list[l]) {\\n return true; // Return true if found\\n }\\n }\\n }\\n return false; // Not found\\n}\\n\\n// Register 'inlist' converter just for the 'teamTmpl' template \\n$.views.converters({inlist: inlistConverter}, teamTmpl);\\n\\n// Define model \\nvar model= {people: [\\n {name: \\\"Jo\\\"},\\n {name: \\\"Liza\\\"},\\n {name: \\\"Eli\\\"},\\n {name: \\\"Pete\\\"},\\n {name: \\\"Zoey\\\"}\\n ],\\n // Specify list of reserves, by name\\n reserve: [\\\"Eli\\\", \\\"Liza\\\"]\\n};\\n\\n// Specify array of team members\\nmodel.team = [model.people[0], model.people[3]];\\n\\n$(\\\"#result\\\").html(teamTmpl.render(model));\\n\\n ...\\n {{for people}}\\n ...\\n {{if #data ~root.team convert='inlist'}}\\n ...\\n {{else #data ~root.reserve field=\\\"name\\\"}}\\n ...\\n {{else}}\\n ...\\n {{/for}}\\n ...\\n
      \\n\\n// Converter function for looking for an item (first argument of tag) in a list (second argument of tag)\\nfunction inlistConverter(item, list) {\\n // If no arguments, this is the final {{else}}.\\n ... // Return true\\n\\n // If the tag has a 'field' property, look for the value of that field among the list items\\n ... // Return true if found\\n\\n // If no field property, look for the item among the list items\\n ... // Return true if found\\n\\n return false; // Not found\\n}\\n\\n// Register 'inlist' converter just for the 'teamTmpl' template \\n$.views.converters({inlist: inlistConverter}, teamTmpl);\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using helper functions, or dynamically assigning converters\",\r\n \"text\": \"Using helper functions, or dynamically assigning converters\\nThe convert=... syntax allows you to assign a converter function without it being registered by name. For example it can be a data method or a helper function – such as {{sometag ... convert=~myConverterHelper}}.\\n(You can do this with the {{: ...}} tag too – by writing {{: ... convert=~myConverterHelper}}…)\\nYou can even assign a converter dynamically. For example you can write: {{sometag ... convert=~getConverter(...)}}, where the getConverter() helper might return either a string (for a converter registered by name) or a function to be used as converter.\\nTo illustrate, here is a modified version of the previous sample, using {{if ... convert=~getConverter()}}:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Dynamically assigning a converter\",\r\n \"text\": \"Dynamically assigning a converter\\n\\n\\n\\n
        \\n {{for people}}\\n
      • \\n {{:name}}:\\n {{if #data ~root.team convert=~getConverter()}}\\n Team member\\n {{else #data ~root.reserve field=\\\"name\\\"}}\\n Reserve\\n {{else}}\\n Not in team\\n {{/if}}\\n
      • \\n {{/for}}\\n
      \\n\\nvar teamTmpl = $.templates(\\\"#teamTmpl\\\");\\n\\n// Converter function for looking for an item (first argument of tag) in a list (second argument of tag)\\nfunction inlistConverter(item, list) {\\n // If no arguments, this is the final {{else}}\\n if (!list) {\\n return true; // Final else, so return true\\n }\\n\\n var field = this.tagCtx.props.field;\\n var l = list.length;\\n\\n // If the tag has a 'field' property, look for the value of that field among the list items\\n if (field) {\\n while (l--) {\\n if (item[field] === list[l]) {\\n return true; // Return true if found\\n }\\n }\\n }\\n\\n // If no field property, look for the item among the list items\\n else {\\n while (l--) {\\n if (item === list[l]) {\\n return true; // Return true if found\\n }\\n }\\n }\\n return false; // Not found\\n}\\n\\n// Helper to dynamically assign converters\\nfunction getConverter() {\\n return inlistConverter; // For this sample just return `inlistConverter` every time\\n}\\n\\n// Register 'getConverter' helper just for the 'teamTmpl' template \\n$.views.helpers(\\\"getConverter\\\", getConverter, teamTmpl);\\n\\n// Define model \\nvar model= {people: [\\n {name: \\\"Jo\\\"},\\n {name: \\\"Liza\\\"},\\n {name: \\\"Eli\\\"},\\n {name: \\\"Pete\\\"},\\n {name: \\\"Zoey\\\"}\\n ],\\n // Specify list of reserves, by name\\n reserve: [\\\"Eli\\\", \\\"Liza\\\"]\\n};\\n\\n// Specify array of team members\\nmodel.team = [model.people[0], model.people[3]];\\n\\n$(\\\"#result\\\").html(teamTmpl.render(model));\\n\\n// Converter function\\nfunction inlistConverter(item, list) { ... }\\n\\n// Helper to dynamically assign converters\\nfunction getConverter() {\\n return inlistConverter; // For this sample just return `inlistConverter` every time\\n}\\n\\n// Register 'getConverter' helper just for the 'teamTmpl' template \\n$.views.helpers(\\\"getConverter\\\", getConverter, teamTmpl);\\n\\n{{if #data ~root.team convert=~getConverter()}}\\n ...\\n{{/if}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Built-in converters:\",\r\n \"text\": \"Built-in converters:\\nJsRender has the following built-in converters/encoders:\\n\\nBuilt-in HTML encoder: {{html: ...}} – accessed programmatically as $.views.converters.html()\\nBuilt-in attribute encoder: {{attr: ...}} – accessed programmatically as $.views.converters.attr()\\nBuilt-in URL encoder: {{url: ...}} – accessed programmatically as $.views.converters.url()\\nBasic encode/unencode converters\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Built-in HTML encoder\",\r\n \"text\": \"Built-in HTML encoder\\nJsRender includes an HTML encoder, which you can use programmatically as follows:\\nvar myHtmlEncodedString = $.views.converters.html(myString);\\n\\nThe same encoder is accessed declaratively as a converter, as in the following two examples:\\n{{html:myExpression}}\\n\\n{{>myExpression}}\\n\\nIn fact {{>...}} is exactly equivalent to {{html:...}} and is provided as a simpler syntax for HTML encoding values taken from data or from expressions and rendered within HTML content.\\n(Note: the {{> ...}} tag should be used in place of the {{: ...}} tag whenever the data being rendered is not full trusted – in order to prevent HTML injection attacks.)\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Calling the HTML encoder\",\r\n \"text\": \"Calling the HTML encoder\\nShow result\\n\\n\\nvar value = \\\"< > ' \\\\\\\" &\\\";\\nvar result = $.views.converters.html(value);\\n\\n$(\\\"#show\\\").on(\\\"click\\\", function() {\\n alert(result);\\n});\\n\\nvar value = \\\"< > ' \\\\\\\" &\\\";\\n\\nvar result = $.views.converters.html(value);\\n\\nalert(result);\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"api\",\r\n \"title\": \"HTML encoder\",\r\n \"text\": \"HTML encoder\\nReturns the HTML-encoded string\\n\\nvar encoder = $.views.converters.html;\\nvar encodedString = encoder(myString);\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Built-in attribute encoder\",\r\n \"text\": \"Built-in attribute encoder\\nJsRender includes an encoder intended for use when attribute encoding is needed. You can use it programmatically as follows:\\nvar myAttributeEncodedString = $.views.converters.attr(myString);\\n\\nThe same encoder is accessed by declaratively as a converter:\\n{{attr:myExpression}}\\n\\nA typical use case would be to encode an HTML attribute value in a template:\\n
      ...
      \\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Calling the 'attribute' encoder\",\r\n \"text\": \"Calling the 'attribute' encoder\\nShow result\\n\\n\\nvar value = \\\"< > ' \\\\\\\" & =\\\";\\nvar result = $.views.converters.attr(value);\\n\\n$(\\\"#show\\\").on(\\\"click\\\", function() {\\n alert(result);\\n});\\n\\nvar value = \\\"< > ' \\\\\\\" &\\\";\\n\\nvar result = $.views.converters.attr(value);\\n\\nalert(result);\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"api\",\r\n \"title\": \"Attribute encoder\",\r\n \"text\": \"Attribute encoder\\nReturns the attribute-encoded string\\n\\nvar encoder = $.views.converters.attr;\\nvar encodedString = encoder(myString);\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Built-in URL encoder\",\r\n \"text\": \"Built-in URL encoder\\nJsRender includes a URL encoder, which you can use programmatically as follows:\\nvar myUrlEncodedString = $.views.converters.url(myString);\\n\\nThe same encoder is accessed by declaratively as a converter:\\n{{url:myExpression}}\\n\\nA typical use case would be to encode a HTML URL attribute value in a template:\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Calling the 'url' encoder\",\r\n \"text\": \"Calling the 'url' encoder\\nShow result\\n\\nvar value = \\\"<_>_\\\\\\\"_ \\\";\\nvar result = $.views.converters.url(value);\\n\\n$(\\\"#show\\\").on(\\\"click\\\", function() {\\n alert(result);\\n});\\n\\nvar value = \\\"<_>_\\\\\\\"_ \\\";\\n\\nvar result = $.views.converters.url(value);\\n\\nalert(result);\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"api\",\r\n \"title\": \"URL encoder\",\r\n \"text\": \"URL encoder\\nReturns the URL-encoded string\\n\\nvar encoder = $.views.converters.url;\\nvar encodedString = encoder(myString);\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Minimalist HTML encode/unencode converters\",\r\n \"text\": \"Minimalist HTML encode/unencode converters\\nIn addition JsRender and JsViews provide encode/unencode converters for minimal encoding to prevent HTML injection (see the JsViews topic: Encoding to avoid XSS), by encoding just < > and & by the corresponding HTML entities, and for unencoding back from entities to characters:\\n& ↔ &\\n< ↔ <\\n> ↔ >\\nUsage:\\nencodedValue = $.views.converters.encode(unencodedValue);\\nunencodedValue = $.views.converters.unencode(encodedValue);\\n\\nDeclarative usage:\\n{{encode:myExpression}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"See also:\",\r\n \"text\": \"See also:\\n\"\r\n }\r\n ]\r\n },\r\n \"converters\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"What are converters?\",\r\n \"text\": \"What are converters?\\nIn JsRender, a converter is a convenient way of processing or formatting data-value, or the result of expression evaluation.\\nYou use built-in converters to HTML-encode, attribute-encode, or URL-encode:\\n{{html:movie.description}} - This data is HTML encoded\\n{{>movie.description}} - (Alternative syntax) - This data is HTML encoded\\n\\n{{url:~getTheFilePath()}} - This expression will be URL-encoded\\n\\nAnd you can register custom converters. For example you might register a date formatter or an upper-case converter:\\n{{daymonth:invoice.date}} - This date uses my 'daymonth' formatter \\n{{upper:name}} - This uses my 'upper' converter \\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Built-in converters\",\r\n \"text\": \"Built-in converters\\nJsRender has the following built-in converters – based on encoders:\\n\\nBuilt-in HTML encoder: {{> ...}}\\nBuilt-in attribute encoder: {{attr ...}}\\nBuilt-in URL encoder: {{url ...}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Registering a converter\",\r\n \"text\": \"Registering a converter\\nYou can register your own custom converters, using $.views.converters() as in:\\n$.views.converters(\\\"upper\\\", function(val) {\\n // Convert data-value or expression to upper case\\n return val.toUpperCase();\\n});\\n\\nTo use the \\\"upper\\\" converter with the {{:...}} tag, you write:\\n{{upper:...}}\\n\\nHere it is in a sample:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"A simple converter\",\r\n \"text\": \"A simple converter\\n\\n\\n\\n Name: {{:name}}. Upper case nickname: {{upper:nickname}}\\n

      \\n {{upper: \\\"This will be upper case too\\\"}} \\n\\n$.views.converters(\\\"upper\\\", function(val) {\\n return val.toUpperCase();\\n});\\n\\nvar person = {name: \\\"Robert\\\", nickname: \\\"Bob\\\"};\\n\\nvar html = $(\\\"#personTemplate\\\").render(person);\\n\\n$(\\\"#person\\\").html(html);\\n$.views.converters(\\\"upper\\\", function(val) {\\n return val.toUpperCase();\\n});\\n\\nName: {{:name}}. Upper case nickname: {{upper:nickname}}\\n...\\n{{upper: \\\"This will be upper case too\\\"}} \\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Converter arguments\",\r\n \"text\": \"Converter arguments\\nA converter can access any number of tag arguments, to produce the computed value which it provides to the tag:\\n$.views.converters(\\\"myConverter\\\", function(arg1, arg2, arg3 ...) {\\n\\nFurthermore, the this pointer within the converter function is the instance of the tag, which allows it to access much more, including named tag parameters (this.tagCtx.props...), the full data object (this.tagCtx.view.data), and more…\\nThe following sample shows a \\\"fullname\\\" converter, which provides a computed full name based on the first two tag arguments (first and last) and an optional named tag parameter reverse=true:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Full name converter – accessing multiple arguments\",\r\n \"text\": \"Full name converter – accessing multiple arguments\\n\\n\\n\\n

      {{fullname:first last}}

      \\n

      {{fullname:first last reverse=true}}

      \\n\\n$.views.converters(\\\"fullname\\\", function(first, last) {\\n var reverse = this.tagCtx.props.reverse; \\n if (reverse) {\\n return last.toUpperCase() + \\\" \\\" + first;\\n }\\n return first + \\\" \\\" + last;\\n});\\n\\nvar person = {first: \\\"Xavier\\\", last: \\\"Prieto\\\"};\\n\\nvar html = $(\\\"#personTemplate\\\").render(person);\\n\\n$(\\\"#person\\\").html(html);\\n$.views.converters(\\\"fullname\\\", function(first, last) {\\n var reverse = this.tagCtx.props.reverse; \\n if (reverse) {\\n return last.toUpperCase() + \\\" \\\" + first;\\n }\\n return first + \\\" \\\" + last;\\n});\\n\\n... {{fullname:first last}}\\n... {{fullname:first last reverse=true}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using converters with other tags\",\r\n \"text\": \"Using converters with other tags\\nA converter can be used on any tag, thanks to the syntax\\n{{sometag ... convert=...}}\\n\\nwhere sometag can be any custom tag, or a built-in tag such as {{if}}.\\n(Note: When using JsViews two-way binding, similar syntax is available for convertBack: convertBack=....)\\nFor example, you could register an \\\"inList\\\" converter which returns true if item is found in itemList (see sample):\\n{{if convert='inList' item itemList}}...{{/if}}\\n\\nThe following sample shows the {{for ...}} tag used with a named converter which returns the array with additional appended and prepended items:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Using converters with the {{for}} tag\",\r\n \"text\": \"Using converters with the {{for}} tag\\n\\n\\n\\n
        \\n {{for people convert='extraItems'}}\\n
      • {{:name}}
      • \\n {{/for}}\\n
      \\n\\n$.views.converters({\\n extraItems: function(arr) {\\n // return array with additional items\\n return [{name: \\\"Prepended\\\"}].concat(arr, {name: \\\"Appended\\\"});\\n }\\n});\\n\\nvar model= {people: [\\n {name: \\\"Jo1\\\"},\\n {name: \\\"Jo2\\\"},\\n {name: \\\"Jo3\\\"}\\n]};\\n\\nvar html = $(\\\"#myTmpl\\\").render(model);\\n\\n$(\\\"#result\\\").html(html);\\n$.views.converters({\\n extraItems: function(arr) {\\n // return array with additional items\\n return [{name: \\\"Prepended\\\"}].concat(arr, {name: \\\"Appended\\\"});\\n }\\n});\\n\\n{{for people convert='extraItems'}}\\n ...\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using helper functions or data methods as converters\",\r\n \"text\": \"Using helper functions or data methods as converters\\nThe convert=... syntax not only works on any tag, but also allows you to use not only registered converters, by name, as in\\n{{for people convert='odd'}}\\n\\nbut alternatively to use helpers, or data methods as in\\n{{for people convert=utility.extraItems}} // Using data method\\n\\nYou can also use that approach on {{:..}} tags as in\\n{{:name convert=~hlp.bold}} // Using a helper\\n\\nNote that the one tag which does not support this syntax is {{>...}} – for which you would need instead to write:\\n{{>~hlp.bold(name)}} // Using helper \\n\\nHere is a modified version of the sample above, using helpers and data methods:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"text\": \"\\n\\n\\n
        \\n {{for people convert=utility.extraItems}} {{!-- using data method --}}\\n
      • \\n {{:name convert=~hlp.bold}} {{!-- using helper --}}\\n
      • \\n {{/for}}\\n
      \\n\\nvar helpers = {\\n hlp: {\\n bold: function(val) {\\n return \\\"\\\" + val + \\\"\\\";\\n }\\n }\\n};\\n\\nvar model= {people: [\\n {name: \\\"Jo1\\\"},\\n {name: \\\"Jo2\\\"},\\n {name: \\\"Jo3\\\"}\\n ],\\n utility: {\\n extraItems: function(arr) {\\n // return array with additional items\\n return [{name: \\\"Prepended\\\"}].concat(arr, {name: \\\"Appended\\\"});\\n }\\n }\\n};\\n\\nvar html = $(\\\"#myTmpl\\\").render(model, helpers);\\n\\n$(\\\"#result\\\").html(html);\\n...\\n{{for people convert=utility.extraItems}} {{!-- using data method --}}\\n
    1. \\n {{:name convert=~hlp.bold}} {{!-- using helper --}}\\n...\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"For additional details and scenarios see:\",\r\n \"text\": \"For additional details and scenarios see:\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"See also the following sample:\",\r\n \"text\": \"See also the following sample:\\nConverters and encoding\\n\"\r\n }\r\n ]\r\n },\r\n \"nojqueryapi\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"JsRender can be loaded in the browser with or without jQuery, as in these example pages:\\n\\nJsRender with jQuery\\nJsRender without jQuery\\n\\nWhen jQuery is present:\\n\\nJsRender loads as a jQuery plugin and adds APIs to the jQuery global namespace object – usually aliased as var $ = jQuery;\\nThe JsRender APIs are\\n\\n$.views...\\n$.templates(...)\\n$.render....\\n\\n\\nIf jQuery is not present:\\n\\nJsRender automatically creates its own jsrender global namespace variable\\nJsRender APIs are the same as above, but they are now associated with the jsrender namespace variable:\\n\\njsrender.views...\\njsrender.templates(...)\\njsrender.render....\\n\\n\\nFor convenience you can follow the jQuery approach of creating a global $ – set this time to var $ = jsrender;\\nYou can then use the regular APIs: $.views..., $.templates..., $.render..., or copy code from the regular browser examples/samples – as if using JsRender with jQuery.\\nFor example:\\nvar $ = jsrender; // Alias for the jsrender namespace object - referenced for convenience as var $\\n\\nvar tmpl = $.templates('Name: {{:first}} {{upper:last'); // Compile template from string\\n\\n$.views.converters('upper', function(val) {return val.toUpperCase()}); // Register converter\\n \\nvar data = {first: 'Jo', last: 'Ryan'};\\n\\nvar html = tmpl.render(data);\\n// result: \\\"Name: Jo RYAN\\\" \\n\\nNote: The same approach can be used when using JsRender on the server with Node.js, where JsRender is also being used without jQuery.\\n\"\r\n }\r\n ]\r\n },\r\n \"node/webpack\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Webpack support for JsRender and JsViews\\nJsRender and JsViews can be loaded using webpack.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"JsRender as a webpack module\",\r\n \"text\": \"JsRender as a webpack module\\nAfter installing JsRender on the server (using $ npm install jsrender) it can then be included in the webpack client script bundle, and loaded in the browser.\\nThere are three options for loading JsRender in the browser as a webpack module:\\n\\nLoad jQuery globally (as a script tag – so window.jQuery is defined), then load JsRender as a module in the webpack client script bundle:\\nrequire('jsrender'); // Load JsRender as jQuery plugin (attached to global jQuery)\\n\\nLoad both jQuery and JsRender as modules in the webpack client script bundle:\\nvar $ = require('jquery'); // Load jQuery as a module\\nrequire('jsrender')($); // Load JsRender as jQuery plugin (jQuery instance as parameter)\\n\\nLoad JsRender as a module in the webpack client script bundle, without loading jQuery at all:\\nvar jsrender = require('jsrender')(); // Load JsRender without jQuery (function call, no parameter)\\n\\n\\nNote: In fact if jQuery is not defined globally, require('jsrender') returns a function.\\nCalling that function without a parameter then loads JsRender without jQuery (and returns the JsRender namespace).\\nAlternatively, calling that function with a reference to a jQuery instance as parameter loads JsRender as a plugin (attached to that jQuery instance) – and returns the jQuery instance.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Example – jQuery loaded globally:\",\r\n \"text\": \"Example – jQuery loaded globally:\\nindex.html:\\n\\n \\n\\n
      \\n \\n\\n\\nsource.js:\\nrequire('jsrender'); // Load JsRender (jQuery is loaded as global)\\nvar tmpl = $.templates('Name: {{:name}}');\\nvar data = {name: 'Jo'};\\nvar html = tmpl.render(data);\\n$('#container').html(html);\\n\\ncommand line:\\nwebpack ./source.js bundle.js\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Example – jQuery loaded as module:\",\r\n \"text\": \"Example – jQuery loaded as module:\\nindex.html:\\n\\n
      \\n \\n\\n\\nsource.js:\\nvar $ = require('jquery'); // Load jQuery as a module\\nrequire('jsrender')($); // Load JsRender as jQuery plugin (jQuery instance as parameter)\\nvar tmpl = $.templates('Name: {{:name}}');\\nvar data = {name: 'Jo'};\\nvar html = tmpl.render(data);\\n$('#container').html(html);\\n\\ncommand line:\\nwebpack ./source.js bundle.js\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Example – JsRender without jQuery:\",\r\n \"text\": \"Example – JsRender without jQuery:\\nindex.html:\\n\\n
      \\n \\n\\n\\nsource.js:\\nvar jsrender = require('jsrender')(); // Load JsRender without jQuery\\nvar tmpl = jsrender.templates('Name: {{:name}}');\\nvar data = {name: 'Jo'};\\nvar html = tmpl.render(data);\\ndocument.querySelector('#container').innerHTML = html;\\n\\ncommand line:\\nwebpack ./source.js bundle.js\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"JsViews as a webpack module\",\r\n \"text\": \"JsViews as a webpack module\\nJsViews can also be included in the webpack client-script bundle, and loaded in the browser.\\nAfter installing on the server (using $ npm install jsviews), call:\\nrequire('jsviews'); // Load JsViews (if jQuery is loaded globally)\\n\\nor – if also loading jQuery as a webpack module, use:\\nvar $ = require('jquery');\\n...\\nrequire('jsviews')($); // Load JsViews (passing local jQuery instance as parameter)\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"See also:\",\r\n \"text\": \"See also:\\nBrowserify support\\n\"\r\n }\r\n ]\r\n },\r\n \"viewmodelsapi\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"This topic provides details on using $.views.viewModels() to register/compile View Models.\\nThis is the third of the alternative approaches discussed in Data / View Models – namely:\\n\\nusing plain objects\\nusing ‘hand-coded’ View Models\\nusing $.views.viewModels() to compile and register View Models with specific get/set properties and methods.\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Advantages of compiled View Models\",\r\n \"text\": \"Advantages of compiled View Models\\nUsing $.views.viewModels() to compile View Models brings some important advantages over plain object hierarchies or ‘hand-coded’ View Models:\\n\\nSimple calls to $.views.viewModels(...) allow you to compile these View Model classes without having to manually write repetitive code for multiple such get/set properties\\nUsing compiled View Models rather than plain objects makes it easier to have clean well-designed modular code, since each View Model has specific getters, setters and methods, and can have its own ‘private’ properties and state\\nThe compiled View Models provide a built-in mapping and unmapping feature for automatically converting from a plain object hierarchy (such as from a JSON request) to a hierarchy of View Model instances, or for converting back to plain data (such as for submitting to the server)\\nThey also provide a merge(...) feature for incrementally updating the View Model hierarchy, using updated plain data from the server\\nWhen working with (or migrating to) JsViews the compiled classes automatically become fully-fledged MVVM classes, with a rich range of features – where the Views are observable data-linked templates. Updates to the View Model hierarchy, and calls to the View Model setters both trigger observable changes, with corresponding incremental updates to the Views. (For more information see JsViews: Data / View Model and JsViews: Compiled View Models.)\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using compiled View Models\",\r\n \"text\": \"Using compiled View Models\\nThe basic use scenarios of compiled View Models are as follows:\\n\\nUsing $.views.viewModels(...) to register/compile View Models (myVM)\\nUsing a compiled View Model myVM as constructor/factory method – MyVM(...) – to create View Model instances (myVmInstance)\\nUsing MyVM.map(...) to convert a plain object hierarchy (such as from a JSON request) to a hierarchy of View Model instances\\nUsing myVMInstance.merge(...) to incrementally update a View Model hierarchy, using updated plain data\\nUsing myVMInstance.unmap() to convert a View Model hierarchy back to a plain object hierarchy\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"API: $.views.viewModels(...)\",\r\n \"text\": \"API: $.views.viewModels(...)\\nTo register a View Model, you call the $.views.viewModels(...) API – with four alternative signatures:\\n\\nvar MyVM = $.views.viewModels(viewModelOptions);returning a compiled View Model\\n$.views.viewModels(\\\"MyVM\\\", viewModelOptions);registering a named View Model, accessible as $.views.viewModels.MyVM\\n$.views.viewModels(namedViewModels);where namedViewModels is a hash, declaring multiple View Models\\n$.views.viewModels(namedViewModels, myViewModels);where namedViewModels is a hash, declaring multiple View Models and myViewModels is a View Models collection (hash) which will provide access to the compiled View Models, as myViewModels.MyVM\\n\\nIn each case, the compiled View Model is specified by a viewModelOptions object, with a getters: gettersArray (specifying an array of get/set properties), and/or an extend: extendObject (specifying additional methods or properties).\\nExample:\\nvar Book = $.views.viewModels({ // Compile a Book View Model\\n getters: [\\\"title\\\", \\\"price\\\"], // getters array - signature of constructor\\n extend: { // extend object - additional methods \\n placeOrder: function() { ... }\\n }\\n});\\n\\nvar book1 = Book(\\\"Hope\\\", \\\"1.50\\\"); // Construct a Book View Model instance\\nbook.price(\\\"2.50\\\"); // Modify price\\nbook.placeOrder(); // Call method\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"api\",\r\n \"title\": \"$.views.viewModels(...)\",\r\n \"text\": \"$.views.viewModels(...)\\nRegister one or more View Models\\nReturn a compiled View Model (constructor/factory method) with specific get/set properties and methods\\n\\nvar Book = $.views.viewModels({\\n getters: [\\\"title\\\", \\\"price\\\"]\\n});\\n\\nvar bk1 = Book(\\\"Hope\\\", \\\"$1.50\\\");\\n\\nRegister (and return) a named View Model\\n\\n$.views.viewModels(\\\"Book\\\", {\\n getters: [\\\"title\\\", \\\"price\\\"]\\n});\\n\\nvar bk1 = $.views.viewModels.Book(\\\"Hope\\\", \\\"$1.50\\\");\\n\\n\\nRegister multiple named View Models\\n\\n$.views.viewModels({\\n Book: {getters: [\\\"title\\\", \\\"price\\\"]},\\n ...\\n});\\n\\nvar bk1 = $.views.viewModels.Book(\\\"Hope\\\", \\\"$1.50\\\");\\n\\n\\nAdd one or more named View Models to a View Model collection (hash)\\n\\nvar myVms = {};\\n\\n$.views.viewModels({\\n Book: {getters: [\\\"title\\\", \\\"price\\\"]},\\n ...\\n}, myVms);\\n\\nvar bk1 = myVms.Book(\\\"Hope\\\", \\\"$1.50\\\");\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Creating View Model instances, using the View Model constructor\",\r\n \"text\": \"Creating View Model instances, using the View Model constructor\\nView Models compiled/registered/returned by $.view.viewModels(...) are in fact constructors for instances of the View Model class.\\nvar Book = $.views.viewModels({ // Constructor\\n getters: [\\\"title\\\", \\\"price\\\"] // getters array - signature of constructor\\n ...\\n});\\n\\nvar book1 = Book(\\\"Hope\\\", \\\"$1.50\\\"); // Create Book instance\\n\\nNote that:\\n\\nThe new keyword is not necessary when calling the constructor. (It is in effect a factory method, that calls new internally.)\\nThe signature of the constructor call (parameters used to initialize the instance) corresponds to the array of getters specified in the viewModelOptions - in this case [\\\"title\\\", \\\"price\\\"]\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"View Model hierarchies\",\r\n \"text\": \"View Model hierarchies\\nThe Book View Model example above has simple get/set properties [\\\"title\\\", \\\"price\\\"] which are simple primitive types (string in this case).\\nBut consider the Person View Model, used in the overview topic Data / View Model. Here a person object (whether a plain object or a View Model instance) is in fact a hierarchy of objects, since the address and phones properties of a Person are themselves objects (an Address object and a Phone array)\\nHere is a person plain object/hierarchy (obtained perhaps by ‘evaluating’ JSON data from the server):\\nvar person = {\\n name: \\\"Pete\\\",\\n address: {\\n street: \\\"1st Ave\\\"\\n },\\n phones: [{number: \\\"111 111 1111\\\"}, {number:\\\"222 222 2222\\\"}] \\n};\\n\\nTo map this object hierarchy to the corresponding View Model hierarchy we need to define three View Models:\\n// Compile Person View Model, with addPhone method\\nvar Person = $.views.viewModels({\\n getters: [\\\"name\\\", \\\"address\\\", \\\"phones\\\"],\\n extend: {addPhone: addPhone}\\n});\\n\\n// Compile Address View Model\\nvar Address = $.views.viewModels({getters: [\\\"street\\\"]});\\n\\n// Compile Phone View Model\\nvar Phone = $.views.viewModels({getters: [\\\"number\\\"]});\\n\\nWe can then instanciate the corresponding View Model hierarchy, using constructors:\\nvar person = Person(\\n \\\"Pete\\\",\\n Address(\\\"1st Ave\\\"),\\n [Phone(\\\"111 111 1111\\\"), Phone(\\\"222 222 2222\\\")]\\n);\\n\\nSee the sample in the Data / View Model topic.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Creating View Model instances by mapping from data\",\r\n \"text\": \"Creating View Model instances by mapping from data\\nThe process of manually writing code to map from JSON data to a corresponding View Model hierarchy, as above, can be complex and inconvenient. It requires traversing the data hierarchy and using appropriate View Model constructors to instantiate corresponding View Model instances.\\nFortunately JsRender/JsViews compiled View Models provide a map(data) feature which when used together with View Model typed hierarchies makes this process quite trivial.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"API: MyViewModel.map(...)\",\r\n \"text\": \"API: MyViewModel.map(...)\\nAny compiled View Model, MyViewModel, provides a MyViewModel.map(...) method, which can be used to convert a plain object or an array of plain objects (or the equivalent JSON string) to the corresponding View Model instance (or array of View Model instances).\\n\"\r\n },\r\n {\r\n \"_type\": \"api\",\r\n \"title\": \"MyViewModel.map(...)\",\r\n \"text\": \"MyViewModel.map(...)\\nGenerate a View Model hierarchy from data\\nGenerate a View Model instance/hierarchy/array by mapping from data (a plain object instance/hierarchy/array, or JSON string)\\n\\n// View Model\\nvar Person = $.views.viewModels.Person;\\n\\n// View Model instance\\nvar person = Person.map(personData);\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Example:\\nvar Book = $.views.viewModels({ // Constructor\\n getters: [\\\"title\\\", \\\"price\\\"]\\n});\\n\\nMap from bookData plain object to book View Model instance:\\nvar bookData1 = {title: \\\"Hope\\\", price: \\\"$1.50\\\"}; // book (plain object)\\nvar book1 = Book.map(bookData1); // book (instance of Book View Model)\\n\\nMap from bookDataArray array of plain objects to bookArray array of View Model instances:\\nvar bookDataArray1 = [ // book array (plain objects)\\n {title: \\\"Hope\\\", price: \\\"$1.50\\\"},\\n {title: \\\"Courage\\\", price: \\\"$2.50\\\"}\\n];\\nvar booksArray1 = Book.map(bookDataArray1); // book array (instances of Book View Model)\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"View Model typed hierarchies\",\r\n \"text\": \"View Model typed hierarchies\\nWhen specifying getters in the $.views.viewModels(...) call, you can declare the type of a get/set property. For example an address get/set property can be specified as being of type Address – where Address is another View Model declared on the same collection.\\nBy specifying View Model types for properties (and declaring those View Models in the same collection) you obtain a ‘View Model typed hierarchy’.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using MyViewModel.map(...) to map a whole object hierarchy to a View Model instance hierarchy\",\r\n \"text\": \"Using MyViewModel.map(...) to map a whole object hierarchy to a View Model instance hierarchy\\nIn the case of a ‘View Model typed hierarchy’, simply pass the top-level plain object to the map() method for the top-level View Model class, and all View Model instances in the hierarchy will be correctly instantiated:\\nCompile View Model classes (typed hierarchy):\\n$.views.viewModels({\\n Person: {\\n getters: [\\n \\\"name\\\", // Declare 'name' as being a primitive type (string)\\n {getter: \\\"address\\\", type: \\\"Address\\\"}, // Declare 'address' as being an Address (View Model) type\\n {getter: \\\"phones\\\", type: \\\"Phone\\\"} // Declare 'phones' as being (an array) of Phone (View Model) types\\n ],\\n extend: {addPhone: addPhone}\\n },\\n Address: {\\n getters: [\\\"street\\\"]\\n },\\n Phone: ...\\n});\\n\\nPerson data (plain object hierarchy, or JSON string):\\nvar personData = {\\n name: \\\"Pete\\\",\\n address: {street: \\\"1st Ave\\\"},\\n phones: [{number: \\\"111 111 1111\\\"}, ...]\\n };\\n\\nUse map() to convert from personData plain object hierarchy (or JSON string) to person View Model hierarchy:\\nvar person = $.views.viewModels.Person.map(personData);\\n\\nThe getter properties then let you traverse the hierarchy, call methods, etc.\\nperson.name(\\\"newName\\\"); // Use setter: change name\\nperson.addPhone(...); // Call method: add phone\\nvar phone2 = person.phones()[1].number(); // Traverse and use getter: get number\\n\\nLet’s modify the sample in Data / View Model to use the map(...) approach:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Using map() to convert from a plain object hierarchy to a View Model hierarchy\",\r\n \"text\": \"Using map() to convert from a plain object hierarchy to a View Model hierarchy\\nbutton {margin-bottom: 9px;}\\n\\nChange name\\nAdd Phone\\n\\n\\n\\n\\n \\n \\n \\n \\n
      Name:{{:name()}}
      Street:{{:address().street()}}
      Phones:\\n \\n {{for phones()}}\\n \\n {{/for}}\\n
      \\n {{:number()}}\\n
      \\n
      \\n\\n// Compiled template\\nvar tmpl = $.templates(\\\"#personTmpl\\\");\\n\\n// Compile View Models\\n$.views.viewModels({\\n Person: {\\n getters: [\\n \\\"name\\\", // name is a primitive type (string)\\n {getter: \\\"address\\\", type: \\\"Address\\\"}, // address is of type Address (View Model)\\n {getter: \\\"phones\\\", type: \\\"Phone\\\"} // Each phone is of type Phone (View Model)\\n ],\\n extend: {addPhone: addPhone}\\n },\\n Address: {\\n getters: [\\\"street\\\"]\\n },\\n Phone:{\\n getters: [\\\"number\\\"]\\n }\\n});\\n\\nvar vmCollection = $.views.viewModels;\\n\\n// Method for Person class\\nfunction addPhone(phoneNo) {\\n // Uses Phone() View Model constructor to create Phone instance\\n this.phones().push(vmCollection.Phone(phoneNo));\\n}\\n\\n// person plain object hierarchy:\\nvar personData = {\\n name: \\\"Pete\\\",\\n address: {street: \\\"1st Ave\\\"},\\n phones: [{number: \\\"111 111 1111\\\"}, {number: \\\"222 222 2222\\\"}]\\n};\\n\\n// Instantiate View Model hierarchy using map()\\nvar person = vmCollection.Person.map(personData);\\n\\n// Render template against person object (instance of Person)\\n$(\\\"#result\\\").html(tmpl.render(person));\\n\\n// Button handlers\\n$(\\\"#changeName\\\").on(\\\"click\\\", function() {\\n person.name(\\\"newName\\\"); // Use the name(...) setter\\n $(\\\"#result\\\").html(tmpl.render(person));\\n});\\n\\n$(\\\"#addPhone\\\").on(\\\"click\\\", function() {\\n person.addPhone(\\\"xxx xxx xxxx\\\"); // Call the addPhone(...) method\\n $(\\\"#result\\\").html(tmpl.render(person));\\n});\\n\\n... {{:name()}} ...\\n\\nCompile View Model classes\\n\\n...\\n$.views.viewModels({\\n Person: {\\n getters: [\\n \\\"name\\\", // name is a primitive type (string)\\n {getter: \\\"address\\\", type: \\\"Address\\\"}, // address is of type Address (View Model)\\n {getter: \\\"phones\\\", type: \\\"Phone\\\"} // Each phone is of type Phone (View Model)\\n ],\\n extend: {addPhone: addPhone}\\n },\\n Address: ...\\n Phone: ...\\n});\\n\\n\\nInstantiate View Model hierarchy using Person.map(data)\\n\\nvar personData = {\\n name: \\\"Pete\\\",\\n address: {street: \\\"1st Ave\\\"},\\n phones: [{number: \\\"111 111 1111\\\"}, {number: \\\"222 222 2222\\\"}]\\n};\\n\\nvar person = vmCollection.Person.map(personData);\\n\\nRender template against person object (instance of Person)\\n\\n$(\\\"#result\\\").html(tmpl.render(person));\\n\\nCall setter, call method...\\n\\n...\\nperson.name(\\\"newName\\\"); // Use the name(...) setter\\n\\n...\\nperson.addPhone(\\\"xxx xxx xxxx\\\"); // Call the addPhone(...) method\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"(See also the corresponding sample with JsViews and data-linking.)\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Along with the map() feature – merge() and unmap()\",\r\n \"text\": \"Along with the map() feature – merge() and unmap()\\nWhen working with View Model typed hierarchies, there are two additional features that can be used together with the map() feature:\\n\\nIf later you obtain updated JSON data, personData2, you can use merge() (below) to trigger an incremental update to the View Model hierarchy:\\nperson.merge(personData2);\\n\\nIf values are modified (using setters, or methods) you can at any time can use unmap() (below) to convert back to plain data, but with updated values:\\nvar updatedPersonData = person.unmap();\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using myVMobjectOrArray.merge(...) to update a View Model hierarchy\",\r\n \"text\": \"Using myVMobjectOrArray.merge(...) to update a View Model hierarchy\\nIf a View Model hierarchy (or array of View Model instances) was created using the map() feature above to map from data, then the View Model instances (and arrays) will each have a merge() method available:\\nvar person = Person.map(personData1);\\nperson.merge(personData2); // Incrementally update person (hierarchy)\\n\\nor for an array:\\nvar peopleArray = Person.map(peopleDataArray1);\\npeopleArray.merge(peopleDataArray2); // Incrementally update people array\\n\\nOr, deeper in the hierarchy:\\nvar person = Person.map(personData1);\\nperson.phones.merge(phonesDataArray2); // Update just the person.phones array\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Updating with merge() makes minimal incremental changes, and preserves state\",\r\n \"text\": \"Updating with merge() makes minimal incremental changes, and preserves state\\nNote that the merge() update process does not replace the whole hierarchy of View Model instances, but works incrementally to add/remove/modify instances as appropriate. So if most of the data in personData2 is the same as personData1, calling merge(personData2) will make only minimal changes to the hierarchy.\\nThis means that if View Model instances have state (such as additional properties that were set programmatically and are not driven by data) then that state can be maintained across the merge() update.\\n\"\r\n },\r\n {\r\n \"_type\": \"api\",\r\n \"title\": \"myVMobjectOrArray.merge(...)\",\r\n \"text\": \"myVMobjectOrArray.merge(...)\\nUpdate a View Model hierarchy, from modified data\\nUpdate a previously generated View Model instance/hierarchy/array by mapping from updated data\\n\\nperson.merge(personData2);\\n// person (View Model hierarchy) has now\\n// been updated, with modified data...\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using myVMobjectOrArray.unmap() to convert back to a plain object hierarchy\",\r\n \"text\": \"Using myVMobjectOrArray.unmap() to convert back to a plain object hierarchy\\nIf a View Model hierarchy (or array of View Model instances) was created by mapping from data, using the map() feature above, then the View Model instances (and arrays) will each have an unmap() method (in addition to the merge() method mentioned above):\\nvar person = Person.map(personData1);\\nperson.addPhone(newPhone);\\nperson.name(newName)\\nvar modifiedPersonData = person.unmap(); // Convert back to a plain object hierarchy\\n\\nor for an array:\\nvar peopleArray = Person.map(peopleDataArray1);\\npeopleArray[1].address.street(newStreet) // Make changes anywhere in the peopleArray\\nvar modifiedPeopleDataArray = people.unmap(); // Convert back to a plain object array\\n\\nOr, deeper in the hierarchy:\\nvar person = Person.map(personData1);\\nperson.addPhone(newPhone);\\nvar modifiedPhonesArray = person.phones.unmap(); // Get a plain object array for person.phones\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"api\",\r\n \"title\": \"myVMobjectOrArray.unmap()\",\r\n \"text\": \"myVMobjectOrArray.unmap()\\nGet a plain object hierarchy from a View Model hierarchy\\nObtain an updated plain object instance/hierarchy/array, from a previously generated View Model instance/hierarchy/array\\n\\n// Convert back to a plain object hierarchy\\nvar modifiedPersonData = person.unmap();\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Here is an updated version of our previous sample, where now we have added the use of merge() and unmap()\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Using merge() to update View Models, and unmap() to return to plain objects\",\r\n \"text\": \"Using merge() to update View Models, and unmap() to return to plain objects\\nbutton {margin-bottom: 9px;}\\n\\nUpdate\\nRevert\\nGet Data\\nChange name\\nAdd Phone\\n\\n\\n\\n\\n \\n \\n \\n \\n
      Name:{{:name()}}
      Street:{{:address().street()}}
      Phones:\\n \\n {{for phones()}}\\n \\n {{/for}}\\n
      {{:number()}}
      \\n
      \\n\\n// Compiled template\\nvar tmpl = $.templates(\\\"#personTmpl\\\");\\n\\n// Compile View Models\\n$.views.viewModels({\\n Person: {\\n getters: [\\n \\\"name\\\", // name is a primitive type (string)\\n {getter: \\\"address\\\", type: \\\"Address\\\"}, // address is of type Address (View Model)\\n {getter: \\\"phones\\\", type: \\\"Phone\\\"} // Each phone is of type Phone (View Model)\\n ],\\n extend: {addPhone: addPhone}\\n },\\n Address: {\\n getters: [\\\"street\\\"]\\n },\\n Phone:{\\n getters: [\\\"number\\\"]\\n }\\n});\\n\\nvar vmCollection = $.views.viewModels;\\n\\n// Method for Person class\\nfunction addPhone(phoneNo) {\\n // Uses Phone() View Model constructor to create Phone instance\\n this.phones().push(vmCollection.Phone(phoneNo));\\n}\\n\\n// First version of data (e.g. from JSON request):\\nvar personData = {\\n name: \\\"Pete\\\",\\n address: {street: \\\"1st Ave\\\"},\\n phones: [{number: \\\"111 111 1111\\\"}, {number: \\\"222 222 2222\\\"}]\\n};\\n\\n// Second version of data (e.g. new JSON request):\\nvar personData2 = {\\n name: \\\"Peter\\\",\\n address: {street: \\\"2nd Ave\\\"},\\n phones: [{number: \\\"111 111 9999\\\"},{number: \\\"333 333 9999\\\"}]\\n};\\n\\n// Instantiate View Model hierarchy, using map()\\nvar person = vmCollection.Person.map(personData);\\n\\n// Render template against person object (instance of Person)\\n$(\\\"#result\\\").html(tmpl.render(person));\\n\\n// Button handlers\\n$(\\\"#update\\\").on(\\\"click\\\", function() {\\n // Update View Model hierarchy, using merge()\\n person.merge(personData2);\\n $(\\\"#result\\\").html(tmpl.render(person));\\n});\\n\\n$(\\\"#revert\\\").on(\\\"click\\\", function() {\\n // Revert View Model hierarchy, using merge()\\n person.merge(personData);\\n $(\\\"#result\\\").html(tmpl.render(person));\\n});\\n\\n$(\\\"#changeName\\\").on(\\\"click\\\", function() {\\n person.name(\\\"newName\\\"); // Use the name(...) setter\\n $(\\\"#result\\\").html(tmpl.render(person));\\n});\\n\\n$(\\\"#addPhone\\\").on(\\\"click\\\", function() {\\n person.addPhone(\\\"xxx xxx xxxx\\\"); // Call the addPhone(...) method\\n $(\\\"#result\\\").html(tmpl.render(person));\\n});\\n\\n$(\\\"#getData\\\").on(\\\"click\\\", function() {\\n // Get current data, using unmap()\\n var updatedPersonData = person.unmap();\\n window.alert(JSON.stringify(updatedPersonData));\\n});\\nCompile View Model classes\\n\\n...\\n$.views.viewModels({\\n Person: {\\n getters: [\\n \\\"name\\\", // name is a primitive type (string)\\n {getter: \\\"address\\\", type: \\\"Address\\\"}, // address is of type Address (View Model)\\n {getter: \\\"phones\\\", type: \\\"Phone\\\"} // Each phone is of type Phone (View Model)\\n ],\\n extend: {addPhone: addPhone}\\n },\\n Address: ...\\n Phone: ...\\n});\\n\\n\\nInstantiate View Model hierarchy, using map()\\n\\nvar personData = {\\n name: \\\"Pete\\\",\\n address: {street: \\\"1st Ave\\\"},\\n phones: [{number: \\\"111 111 1111\\\"}, {number: \\\"222 222 2222\\\"}]\\n};\\n\\nvar person = vmCollection.Person.map(personData);\\n\\nUpdate View Model hierarchy, using merge()\\n\\n$(\\\"#update\\\").on(\\\"click\\\", function() {\\n person.merge(personData2); // Update person View Model hierarchy\\n $(\\\"#result\\\").html(tmpl.render(person));\\n});\\n\\n\\nGet current data, using unmap()\\n\\n$(\\\"#getData\\\").on(\\\"click\\\", function() {\\n var updatedPersonData = person.unmap(); // Get plain object hierarchy from current View Model hierarchy\\n window.alert(JSON.stringify(updatedPersonData));\\n});\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"(See also the corresponding sample using JsViews and data-linking.)\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Overriding generated get/set functions\",\r\n \"text\": \"Overriding generated get/set functions\\nTo override a generated get/set property provided by a compiled View Model you can provide an implementation in the extend hash, with the same name as the get/set in the getters array:\\n// Define a myNameGetSet(...)function, to override the compiled name(...) get/set function\\nfunction myNameGetSet(val) {\\n if (!arguments.length) { // This is standard compiled get/set code\\n return this._name; // If there is no argument, use as a getter\\n }\\n this._name = val; // If there is an argument, use as a setter\\n console.log(\\\"name set to \\\" + val); // This is an additional line of code, for logging\\n}\\n\\n// Declare a Person View Model with an overridden name() get/set property\\n$.views.viewModels({\\n Person: {\\n getters: [\\n {getter: \\\"name\\\", ...}, // Compiled name() get/set\\n ...\\n ],\\n extend: {\\n name: myNameGetSet, // Override name() get/set\\n ...\\n }\\n ...\\n },\\n ...\\n});\\n\\nThe above is equivalent to the generated version except that it adds custom logging to the getter/setter function.\\nNote: In the context of JsViews, the View Model get/set properties can be data-linked (one-way or two-way data-binding) – and will then be invoked automatically during observable changes to the property. (This applies also to overridden properties – using a variant of the above pattern, described in the corresponding JsViews topic).\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Sample showing some of the advanced View Model features\",\r\n \"text\": \"Sample showing some of the advanced View Model features\\nThe next sample is similar to the previous one, but specifically highlights some of the advanced features of compiled View Models.\\n\\nIt stores compiled View Models on a myVmCollection hash, as a View Model typed collection, rather than on$.views.viewModels\\nIt maps from an array of ‘people’ rather than a single person:\\nvar people = Person.map(peopleData);\\nIt specifies an id key for Person. When updating the phones array the id value is treated as 'primary key’, and used to map 'identity’:\\nid: \\\"id\\\"\\nIt provides an id() callback on Person, for determining identity – allowing identification of corresponding View Model instances within the people array, and hence preventing unnecessary disposal and re-instantiation (which would destroy state, such as the comment value).\\nIt has a comment() get/set property that is added as part of the extend definition, not the getters, so it is not initialized from data, in the constructor. Note therefore that if you set a comment on each person instance, then click Update, then Revert, one comment is conserved (since that instance is never disposed – based on the ‘identity’ determination) but the other is lost since the instance is disposed and then re-created by Revert:\\nextend: {...comment: comment...}\\nIt has defaultVal specified for name, address and phones, either as ‘static’ values or computed by a callback function:\\naddress: {type: \\\"Address\\\", defaultVal: defaultStreet}\\nIt overrides the generated person.name() get/set by a myNameGetSet function which includes logging\\nIt passes a JSON string to merge() or map()\\n(See also the same sample using JsViews and data-linking.)\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Mapping from JSON data to View Model hierarchy – further features\",\r\n \"text\": \"Mapping from JSON data to View Model hierarchy – further features\\nbutton, table {margin-bottom: 9px;}\\n\\nUpdate\\nRevert\\nGet Data\\nChange name\\nAdd Phone\\n\\n\\n\\n\\n \\n \\n \\n \\n \\n
      Comment:
      Name:{{:name()}}
      Street:{{:address().street()}}
      Phones:\\n \\n {{for phones()}}\\n \\n {{/for}}\\n
      {{:number()}}
      \\n
      \\n\\nvar tmpl = $.templates(\\\"#personTmpl\\\");\\n\\nvar myVmCollection = {};\\n\\n// Compile View Models\\n$.views.viewModels({\\n Person: {\\n getters: [\\n {getter: \\\"name\\\", defaultVal: \\\"No name\\\"}, // Compiled name() get/set\\n {getter: \\\"address\\\", type: \\\"Address\\\", defaultVal: defaultAddress},\\n {getter: \\\"phones\\\", type: \\\"Phone\\\", defaultVal: []}\\n ],\\n extend: {\\n name: myNameGetSet, // Override name() get/set\\n addPhone: addPhone,\\n comment: comment // Additional get/set property, not initialized by data)\\n },\\n id: function(vm, plain) { // Callback function to determine 'identity'\\n return vm.personId === plain.personId;\\n }\\n },\\n Address: {\\n getters: [\\\"street\\\"]\\n },\\n Phone: {\\n getters: [\\\"number\\\"],\\n id: \\\"phoneId\\\" // Treat phoneId as 'primary key', for identity\\n }\\n}, myVmCollection); // Store View Models (typed hierarchy) on myVmCollection\\n\\n// Override generated name() get/set\\nfunction myNameGetSet(val) {\\n if (!arguments.length) { // This is standard compiled get/set code\\n return this._name; // If there is no argument, use as a getter\\n }\\n this._name = val; // If there is an argument, use as a setter\\n console.log(\\\"name set to \\\" + val); // This is an additional line of code, for logging\\n}\\n\\n// Method for Person class\\nfunction addPhone(phoneNo) { // Uses myVmCollection.Phone() to construct new instance\\n this.phones().push(myVmCollection.Phone(phoneNo));\\n}\\n\\n// get/set for comment (state on View Model instance, not initialized from data)\\nfunction comment(val) {\\n if (!arguments.length) {\\n return this._comment; // If there is no argument, use as a getter\\n }\\n this._comment = val;\\n}\\n\\nfunction defaultAddress() { // Function providing default address if undefined in data\\n return {street: 'No street for \\\"' + this.name + '\\\"'};\\n}\\n\\n// First version of data - array of objects (e.g. from JSON request):\\nvar peopleData = [\\n {\\n personId: \\\"1\\\",\\n address: {\\n street: \\\"2nd Ave\\\"\\n }\\n },\\n {\\n personId: \\\"2\\\",\\n name: \\\"Pete\\\",\\n phones: [\\n {number: \\\"333 333 3333\\\", phoneId: \\\"2a\\\"}\\n ]\\n }\\n];\\n\\n// Second version of data - JSON string (e.g. new JSON request):\\nvar peopleData2 = '[{\\\"personId\\\":\\\"2\\\",\\\"name\\\":\\\"Peter\\\",\\\"address\\\":{\\\"street\\\":\\\"11 1st Ave\\\"},'\\n+ '\\\"phones\\\":[{\\\"number\\\":\\\"111 111 9999\\\",\\\"phoneId\\\":\\\"1a\\\"},{\\\"number\\\":\\\"333 333 9999\\\",\\\"phoneId\\\":\\\"2a\\\"}]}]';\\n\\n// Instantiate View Model hierarchy using map()\\nvar people = myVmCollection.Person.map(peopleData);\\n\\n// Render template against people (array of Person instances)\\n$(\\\"#result\\\").html(tmpl.render(people));\\n\\n// Button handlers\\n$(\\\"#update\\\").on(\\\"click\\\", function() {\\n people.merge(peopleData2);\\n $(\\\"#result\\\").html(tmpl.render(people));\\n});\\n\\n$(\\\"#revert\\\").on(\\\"click\\\", function() {\\n people.merge(peopleData);\\n $(\\\"#result\\\").html(tmpl.render(people));\\n});\\n\\n$(\\\"#changeName\\\").on(\\\"click\\\", function() {\\n people[0].name(\\\"newName\\\");\\n $(\\\"#result\\\").html(tmpl.render(people));\\n});\\n\\n$(\\\"#addPhone\\\").on(\\\"click\\\", function() {\\n people[0].addPhone(\\\"xxx xxx xxxx\\\");\\n $(\\\"#result\\\").html(tmpl.render(people));\\n});\\n\\n$(\\\"#result\\\").on(\\\"change\\\", \\\".comment\\\", function(val) {\\n // If comment is modified, update View Model state with new value\\n people[this.getAttribute(\\\"data-index\\\")].comment(this.value);\\n});\\n\\n$(\\\"#getData\\\").on(\\\"click\\\", function() {\\n var updatedPeopleData = people.unmap();\\n window.alert(JSON.stringify(updatedPeopleData));\\n});\\n\\n\\nvar myVmCollection = {};\\n\\n// Compile View Models\\n$.views.viewModels({\\n Person: {\\n getters: [\\n {getter: \\\"name\\\", defaultVal: \\\"No name\\\"}, // Compiled name() get/set\\n {getter: \\\"address\\\", type: \\\"Address\\\", defaultVal: defaultAddress},\\n {getter: \\\"phones\\\", type: \\\"Phone\\\", defaultVal: []}\\n ],\\n extend: {\\n name: myNameGetSet, // Override name() get/set\\n addPhone: addPhone,\\n comment: comment // Additional get/set property, not initialized by data)\\n },\\n id: function(vm, plain) { // Callback function to determine 'identity'\\n return vm.personId === plain.personId;\\n }\\n },\\n ...\\n Phone: {\\n getters: [\\\"number\\\"],\\n id: \\\"phoneId\\\" // Treat phoneId as 'primary key', for identity\\n }\\n}, myVmCollection); // Store View Models (typed hierarchy) on myVmCollection\\n\\n// Override generated name() get/set\\nfunction myNameGetSet(val) {\\n if (!arguments.length) { // This is standard compiled get/set code\\n return this._name; // If there is no argument, use as a getter\\n }\\n this._name = val; // If there is an argument, use as a setter\\n console.log(\\\"name set to \\\" + val); // This is an additional line of code, for logging\\n}\\n\\n// Method for Person class\\nfunction addPhone(phoneNo) { // Uses myVmCollection.Phone() to construct new instance\\n this.phones().push(myVmCollection.Phone(phoneNo));\\n}\\n\\n// get/set for comment (state on View Model instance, not initialized from data)\\nfunction comment(val) {\\n if (!arguments.length) {\\n return this._comment;\\n }\\n this._comment = val;\\n}\\n\\nfunction defaultAddress() { // Function providing default address if undefined in data\\n return {street: 'No street for \\\"' + this.name + '\\\"'};\\n}\\n\\n// First version of data - array of objects (e.g. from JSON request):\\nvar peopleData = [{personId: \\\"1\\\", ...}, {personId: \\\"2\\\", name: \\\"Pete\\\",...}];\\n\\n// Second version of data - JSON string (e.g. new JSON request):\\nvar peopleData2 = '[{\\\"personId\\\":\\\"2\\\",\\\"name\\\":\\\"Peter\\\",\\\"address\\\":...}]';\\n\\n// Instantiate View Model hierarchy using map()\\nvar people = myVmCollection.Person.map(peopleData);\\n\\n// Render template against people (array of Person instances)\\n$(\\\"#result\\\").html(tmpl.render(people));\\n...\\n\\n// Button handlers\\n$(\\\"#update\\\").on(\\\"click\\\", function() {\\n people.merge(peopleData2);\\n ...\\n});\\n...\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Adding a custom get/set property to a compiled View Model \",\r\n \"text\": \"Adding a custom get/set property to a compiled View Model \\nFinally, here is a sample which extends a compiled View Model with a custom Person.isManager()get/set property. The property is coupled to the Team.manager() property – so setting Person.isManager(...) will update the Team.manager() correspondingly (and conversely when setting Team.manager(...).\\nPerson.isManager is not included in the getters declaration, so that the constructor for Person will not expect an isManager parameter to be provided for initialization.\\n(See also the related sample using JsViews and data-linking.)\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Sample: extending Person with an isManager property\",\r\n \"text\": \"Sample: extending Person with an isManager property\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n

      Team members:

      \\n\\n\\n \\n \\n {{for members()}}\\n \\n \\n \\n \\n \\n {{/for}}\\n \\n
      Is ManagerNameStreetZIP
      {{:name()}}{{:address().street()}}{{:address().ZIP()}}
      \\n\\n{{if manager()}}\\n

      Manager:

      \\n \\n \\n \\n \\n
      {{:manager().name()}}{{:manager().address().street()}}{{:manager().address().ZIP()}}
      \\n{{else}}\\n

      No manager

      \\n{{/if}}\\n\\n\\n// Compile template\\nvar tmpl = $.templates(\\\"#teamTmpl\\\");\\n\\n// Custom function for Person.isManager get/set property\\nfunction myIsManager(val) {\\n if (!arguments.length) {\\n return this === team.manager(); // If there is no argument, use as a getter\\n }\\n if (val) {\\n // Setting this.isManager() to true\\n // So make this team member manager\\n team.manager(this);\\n } else if (this.isManager()) {\\n // Setting this.isManager to false, and this team member is currently manager.\\n // So set team manager to null\\n team.manager(null);\\n }\\n}\\n\\n// Compile View Models\\n$.views.viewModels({\\n Team: {\\n getters: [\\n {\\n getter: \\\"manager\\\",\\n type: \\\"Person\\\"\\n },\\n {\\n getter: \\\"members\\\",\\n type: \\\"Person\\\"\\n }\\n ]\\n },\\n Person: {\\n getters: [\\n \\\"name\\\",\\n {\\n getter: \\\"address\\\",\\n type: \\\"Address\\\"\\n }\\n ],\\n extend: {\\n isManager: myIsManager // use custom function\\n }\\n },\\n Address: {\\n getters: [\\\"street\\\", \\\"ZIP\\\"]\\n }\\n});\\n\\n// Initial data \\nvar teamData = {\\n manager: null,\\n members: [{\\n name: \\\"Pete\\\",\\n address: {\\n street: \\\"1st Ave\\\",\\n ZIP: \\\"12345\\\"\\n }\\n },{\\n name: \\\"Bess\\\",\\n address: {\\n street: \\\"Central Way\\\",\\n ZIP: \\\"98765\\\"\\n }\\n },\\n {\\n name: \\\"Henry\\\",\\n address: {\\n street: \\\"Main St\\\",\\n ZIP: \\\"54321\\\"\\n }\\n }]\\n };\\n\\n// Instantiate View Models\\nvar team = $.views.viewModels.Team.map(teamData);\\n\\n//Initialize second team member to be manager.\\nvar manager = team.members()[1];\\nmanager.isManager(true);\\n\\nfunction renderTemplate() {\\n // Refresh template rendering completely\\n $(\\\"#result\\\").html(tmpl.render(team));\\n}\\n\\nrenderTemplate();\\n\\n// Attach handlers for checkbox and buttons\\n$(\\\"#result\\\")\\n .on(\\\"change\\\", \\\".isManager\\\", function() {\\n var memberIndex = $(this).data(\\\"index\\\"),\\n member = team.members()[memberIndex];\\n member.isManager(this.checked);\\n renderTemplate(); // Refresh rendering, with modified data\\n })\\n .on(\\\"click\\\", \\\".changeManager\\\", function() {\\n var memberIndex = $(this).data(\\\"index\\\"),\\n member = team.members()[memberIndex];\\n member.isManager(true);\\n renderTemplate(); // Refresh rendering, with modified data\\n })\\n .on(\\\"click\\\", \\\".noManager\\\", function() {\\n team.manager(null);\\n renderTemplate(); // Refresh rendering, with modified data\\n }\\n);\\n// Custom function for Person.isManager get/set property\\nfunction myIsManager(val) {\\n if (!arguments.length) {\\n return this === team.manager(); // If there is no argument, use as a getter\\n }\\n if (val) {\\n // Make this team member manager\\n team.manager(this);\\n } else if (this.isManager()) {\\n // Set team manager to null\\n team.manager(null);\\n }\\n}\\n\\n// Compile View Models\\n$.views.viewModels({\\n Team: {...},\\n Person: {\\n getters: [\\n \\\"name\\\",\\n ...\\n ],\\n extend: {\\n isManager: myIsManager // use custom function\\n }\\n },\\n Address: {...}\\n});\\n\\n...\\n\\n//Initialize second team member to be manager.\\nvar manager = team.members()[1];\\nmanager.isManager(true);\\n\\n...\\n\\n// Attach handler for checkbox\\n$(\\\"#result\\\")\\n .on(\\\"change\\\", \\\".isManager\\\", function() {\\n ...\\n member.isManager(this.checked);\\n renderTemplate(); // Refresh rendering, with modified data\\n })\\n ...\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"See also:\",\r\n \"text\": \"See also:\\n\"\r\n }\r\n ]\r\n },\r\n \"lifecycle\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"paragraph\\n\"\r\n }\r\n ]\r\n },\r\n \"globals\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"JsRender\\n\\nrender()\\ntemplates()\\nviews\\n\\nJsViews\\n\\nlink()\\nobserve()\\nobservable()\\nunlink()\\nunobserved()\\nview()\\n\\n\"\r\n }\r\n ]\r\n },\r\n \"tagsapi\": {\r\n \"sections\": [\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"$.views.tags() is used to register custom tags. See Using custom tags for an overview, and simple examples.\\nThis topic provides more details.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"What is a custom tag?\",\r\n \"text\": \"What is a custom tag?\\nJsRender custom tags are named tags {{mytag ...}}, which you can register, and then use in your templates.\\nA custom tag can optionally use arguments (args) and named parameters (props), as in:\\n{{mytag arg0 arg1 namedProp1=xxx namedProp2=yyy}} ... {{/mytag}}\\n\\nNote: When you also use JsViews, custom tags acquire a whole new dimension. – They become tag controls, and you can build rich and complex single page apps cleanly and simply using custom tag controls – following an MVP or MVVM coding pattern.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Specifying tag options for a custom tag\",\r\n \"text\": \"Specifying tag options for a custom tag\\nThe following tag declaration registers a {{mytag}} custom tag:\\n$.views.tags(\\\"mytag\\\", tagOptions);\\n\\nThe tagOptions object (hash) specifies the tag options and determines how the tag will function. It can include:\\n\\nAn init() method: init: tagInitFn\\nA render() method: render: tagRenderFn\\nA template: template: tagTemplate\\n\\nIn addition tagOptions can specify tag inheritance (so that the custom tag derives from a base tag):\\n\\nbaseTag: ...\\n\\nIt can also specify the following more advanced options:\\n\\ncontentCtx: ...\\nconvert: ...\\nargDefault: ...\\nbindTo: ... / bindFrom: ...\\nflow: ...\\nctx: ...\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Registering custom tags: $.views.tags(...)\",\r\n \"text\": \"Registering custom tags: $.views.tags(...)\\nTo register a custom tag, you call the $.views.tags(...) API.\\nThere are four alternative signatures:\\n\\n$.views.tags(\\\"mytag\\\", tagOptions); – where the properties of the tagOptions object will typically include a render: tagRenderFn (specifying a render() method), and/or a template: tagTemplate (specifying a template to be rendered)\\n$.views.tags(\\\"mytag\\\", tagRenderFn); – simplified form, when the only option being specified is a render() method\\n$.views.tags(\\\"mytag\\\", tagTemplate); – simplified form, when the only option being specified is a tag template to be rendered\\n$.views.tags(namedTags); This version is for declaring multiple custom tags, and namedTags is a hash (with custom tag names as keys and tagOption objects as values)\\n\\nHere are the details:\\n\"\r\n },\r\n {\r\n \"_type\": \"api\",\r\n \"title\": \"$.views.tags(...)\",\r\n \"text\": \"$.views.tags(...)\\nRegister one or more custom tags\\nRegister a custom tag, specifying chosen tag options\\n\\n$.views.tags(\\\"mytag\\\", {\\n render: function(...) {...},\\n template: ...\\n});\\n\\n{{mytag ...}} ... {{/mytag}}\\n\\nRegister a simple 'render' function as a custom tag\\n\\n$.views.tags(\\\"mytag\\\", function(...) {\\n ...return rendered content\\n});\\n\\n{{mytag ...}} ... {{/mytag}}\\n\\nRegister a template as a custom tag\\n\\n$.views.tags(\\\"mytag\\\", \\\"templateMarkup...\\\");\\n\\n{{mytag ...}} ... {{/mytag}}\\n\\nRegister multiple custom tags\\n\\n$.views.tags({\\n mytag1: {\\n render: function(val) {...},\\n template: ...\\n },\\n mytag2: function(val) {...},\\n mytag3: tag3TemplateString,\\n});\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"For simple samples showing the above alternative $.views.tags(...) signatures, see the Using custom tags overview topic:\\n\\nA custom tag using just a render() method\\nA custom tag using just a template\\nAccessing context within the render() method\\nAccessing context from the tag template\\n\\nThe Using custom tags overview also provides samples of custom tags which render block content – {{mytag}}...{{/mytag}}:\\n\\nRendering block content from a custom tag render() method\\nRendering block content from a custom tag template\\nA {{runningTotal}} custom tag, using a render() method\\nA {{runningTotal}} custom tag, with render() method and a template as “fallback”\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Custom tag options: Specifying init(), render(), template, baseTag:\",\r\n \"text\": \"Custom tag options: Specifying init(), render(), template, baseTag:\\nA custom tag in JsRender has a very simple ‘life-cyle’ consisting of two events for which you can optionally provide event handlers: the init() event, followed by the render() event. (If the custom tag is used in the context of JsViews, additional lifecycle events will also come into play, for data-binding, disposal, etc.)\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Providing an init() method\",\r\n \"text\": \"Providing an init() method\\nThe init() method acts as a handler for the init event of the custom tag, and is called with the tag instance as this parameter.\\n$.views.tags(\\\"mytag\\\", {\\n init: function(tagCtx, linkCtx, ctx) { ... },\\n ...\\n});\\n\\nThe init() method arguments are:\\n\\ntagCtx: the tagCtx object, also available as this.tagCtx\\nlinkCtx: always 0 unless using data-linked tags with JsViews (See linkCtx object.)\\nctx: View context object\\n\\nThe following example uses the init() method to set the tag template based on the value of the mode prop:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Providing init()\",\r\n \"text\": \"Providing init()\\n\\n {{mytag name mode='a' /}}\\n {{mytag name mode='b' /}}\\n\\n\\n\\n\\n\\n$.views.tags(\\\"mytag\\\", {\\n init: function(tagCtx) {\\n this.template = tagCtx.props.mode === \\\"a\\\"\\n ? \\\"template A: {{:}} aaa\\\"\\n : \\\"template B: {{:}} bbb\\\";\\n }\\n});\\n\\nvar myTmpl = $.templates(\\\"#myTmpl\\\"),\\n html = myTmpl.render({name: \\\"Jo\\\"});\\n\\n$(\\\"#page\\\").html(html);\\nTag declaration:\\n$.views.tags(\\\"mytag\\\", {\\n init: function(tagCtx) {\\n this.template = tagCtx.props.mode === \\\"a\\\"\\n ? \\\"template A ...\\\"\\n : \\\"template B ...\\\";\\n }\\n});\\n\\nTag usage:\\n{{mytag name mode='a' /}}\\n{{mytag name mode='b' /}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Providing a render() method\",\r\n \"text\": \"Providing a render() method\\nThe render() method acts as a handler for the render event of the custom tag, and is called with the tag instance as this parameter, and with arguments arg1, arg2, ..., corresponding to the unnamed arguments passed in the tag markup, {{mytag expression1 expression2 ... }}.\\nIf no arguments are passed in the markup, then the render() method will be called with the current data context as argument (unless modified by the argDefault option.)\\n$.views.tags(\\\"mytag\\\", {\\n render: function(value1, value2) { ... return ...; },\\n ...\\n});\\n\\nThe render() method can optionally be used to define how the tag renders, by returning an HTML markup string.\\nSee the example: A custom tag using just a render() method.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Providing a template\",\r\n \"text\": \"Providing a template\\nThe template option is used for declarative rendering, as an alternative to providing a render() method.\\nSee the example: A custom tag using just a template.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Data context of a tag template\",\r\n \"text\": \"Data context of a tag template\\nIf the custom tag is called with an argument: {{mytag someArgument ...}} then the template will be rendered using the value of that argument as data context.\\nOtherwise, the data context will be the same as the outer data context.\\n(Note: This behavior can be changed using contentCtx)\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Using both a template and a render() method\",\r\n \"text\": \"Using both a template and a render() method\\nIf the tag has both a render() method and a template, then the render() method is used to render the tag. But if render() returns undefined (or has no return value), then the template is used.\\nSee example: A {{runningTotal}} custom tag, with render() method and a template as “fallback”.\\nIt is also possible to provide both a template and a render() method, and to make use of the rendered template within the content returned by the render method. (In fact this.tagCtx.render(...) will return the rendered template).\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Specifying tag inheritance: the baseTag option\",\r\n \"text\": \"Specifying tag inheritance: the baseTag option\\nA custom tag can inherit from another tag (either built-in or custom).\\nFor example the {{runningTotal}} sample, linked above, can be rewritten in a more powerful but compact form, by making it inherit from the {{for}} tag (since the functionality of iterating over an array is common to both).\\nTo inherit from another tag, set the baseTag option to the name of the tag you want to derive from (or to the compiled tag object):\\n$.views.tags(\\\"runningTotal\\\", {\\n baseTag: \\\"for\\\",\\n ...\\n});\\n\\nCustom tag methods (init() or render()) can invoke the corresponding base tag method by calling one of the following API variants:\\nthis.base(a, b, ...); // Pass chosen arguments\\nthis.baseApply(arguments); // Pass on the calling arguments (or an array of args)\\n\\nThis is illustrated in the following sample, which takes the Providing init() sample above, and defines a derived {{mytag2}} which overrides init() and adds an error message when no valid mode was specified:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"baseTag\",\r\n \"text\": \"baseTag\\n\\n {{mytag2 name mode='a' /}}\\n {{mytag2 name mode='b' /}}\\n {{mytag2 name /}}\\n\\n\\n\\n\\n\\n$.views.tags(\\\"mytag\\\", {\\n init: function(tagCtx) {\\n this.templates = {\\n a: \\\"template A: {{:}} aaa\\\",\\n b: \\\"template B: {{:}} bbb\\\"\\n }; \\n this.template = this.templates[tagCtx.props.mode];\\n }\\n});\\n\\n$.views.tags(\\\"mytag2\\\", {\\n baseTag: \\\"mytag\\\",\\n init: function() { // Override the init() method\\n this.baseApply(arguments); // Call the base method\\n // If no template was assigned, render error message\\n this.template = this.template || \\\"Error: Specify mode 'a' or 'b'\\\";\\n }\\n});\\n\\nvar myTmpl = $.templates(\\\"#myTmpl\\\"),\\n html = myTmpl.render({name: \\\"Jo\\\"});\\n\\n$(\\\"#page\\\").html(html);\\nTag declaration:\\n$.views.tags(\\\"mytag2\\\", {\\n baseTag: \\\"mytag\\\",\\n init: function() { // Override the init() method\\n this.baseApply(arguments); // Call the base method\\n this.template = this.template || \\\"Error: Specify mode 'a' or 'b'\\\"; // If no template was assigned, render error message\\n }\\n});\\n\\nTag usage:\\n{{mytag2 name mode='a' /}}\\n{{mytag2 name mode='b' /}}\\n{{mytag2 name /}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"The previous {{runningTotal}} sample was relatively complex. Here is an updated version rewritten to derive from {{for}}:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"A {{runningTotal}} custom tag derived from {{for}}\",\r\n \"text\": \"A {{runningTotal}} custom tag derived from {{for}}\\n\\n\\n\\n start=1 end=4:\\n \\n \\n {{runningTotal lineItems start=1 end=4 totalColumn=\\\"quantity\\\"}} \\n \\n \\n \\n \\n {{else}}\\n \\n {{/runningTotal}}\\n
      QuantityTotal
      {{:quantity}}{{:~total()}}
      No line items
      \\n\\n start=4 end=5:\\n \\n \\n {{runningTotal lineItems start=4 end=5 totalColumn=\\\"quantity\\\"}} \\n \\n \\n \\n \\n {{else}}\\n \\n {{/runningTotal}}\\n
      QuantityTotal
      {{:quantity}}{{:~total()}}
      No line items
      \\n\\n$.views.tags(\\\"runningTotal\\\", {\\n baseTag: \\\"for\\\",\\n ctx: {\\n total: function() { // A ~total() helper (now a function)\\n var tag = this.ctx.tag,\\n totalCol = tag.tagCtx.props.totalColumn\\n tag.totalVal += this.data[totalCol]; // Compute running total\\n return tag.totalVal; // Return value from ~total()\\n }\\n },\\n render: function() {\\n this.totalVal = 0; // Initialize total before rendering\\n return this.baseApply(arguments); // Render\\n }\\n});\\n\\nvar data = {\\n lineItems: [\\n {category: \\\"book\\\", quantity: 2, price: 3.40},\\n {category: \\\"grocery\\\", quantity: 5, price: 1.01},\\n {category: \\\"grocery\\\", quantity: 2, price: 13.10},\\n {category: \\\"book\\\", quantity: 1, price: 12.50}\\n ],\\n lineItems2: []\\n};\\n\\nvar html = $(\\\"#myTmpl\\\").render(data, {\\n category: function(item, index, items) {\\n return item.category === this.props.category;\\n }\\n});\\n\\n$(\\\"#lineItems\\\").html(html);\\nThis version is much simpler and supports sorting, filtering, etc. as well as start=... end=... step=..., without any additional code (thanks to the inherited features of {{for}}).\\nAlso the fallback rendering for No line items is no longer hard-coded in the tag, but instead uses the {{runningTotal}}...{{else}}... pattern.\\nNote that ~total() is a function. The call to ~total() increments the value and returns the running total.\\nTag declaration:\\n$.views.tags(\\\"runningTotal\\\", {\\n baseTag: \\\"for\\\",\\n ctx: {\\n total: function() { // A ~total() helper (now a function)\\n ...\\n tag.totalVal += this.data[totalCol]; // Compute running total\\n return tag.totalValue; // Return value from ~total()\\n }\\n },\\n render: function() {\\n this.totalVal = 0; // Initialize total before rendering\\n return this.baseApply(arguments); // Render\\n }\\n});\\n\\nTag usage:\\n{{runningTotal lineItems start=1 end=4 totalColumn=\\\"quantity\\\"}} \\n ...{{:~total()}}...\\n{{else}}\\n ...No line items...\\n{{/runningTotal}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Our {{runningTotal}} samples so far have initialized the running total to 0 in the render method, and then relied on the rendering process to do the incrementing of the running total. This approach would fail if the rendering sequence was changed for any reason.\\nThe sample below takes the {{runningTotal}} tag above, and converts it to a more complete and more powerful\\n{{purchases}} tag, again deriving from the {{for}} tag. The {{purchases}} tag, which is more flexible and more robust, and supports any number of running total columns.\\nThe ~total(expression) helper function now allows you to provide any expression as parameter. Here, running total values are recomputed for each line, separately, so no longer depend on the render processing sequence:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"A {{purchases}} tag supporting totals for any expression\",\r\n \"url\": \"samples/jsrender/tags/extend-for/sample\",\r\n \"text\": \"A {{purchases}} tag supporting totals for any expression\\nThe ~total(expr) helper function now accepts an expression parameter for each running total – to be used to compute the incremental amount for each row.\\nTag declaration:\\n$.views.tags(\\\"purchases\\\", {\\n baseTag: \\\"for\\\",\\n ctx: {\\n total: function(expr) { // A ~total(expression) helper\\n var tmpl = $.templates[expr] // Get named compiled template for expression, or else...\\n || $.templates(expr, \\\"{{:\\\" + expr + \\\"}}\\\"), // ...if this is first call, create it\\n\\n runningTotal = 0,\\n view = this, // The content view with the ~total(...) helper call\\n items = view.get(\\\"array\\\").data,\\n rowIndex = view.getIndex();\\n\\n for (var i = 0; i <= rowIndex; i++) {\\n runningTotal += +tmpl(items[i]); // Compute running total up to this row, using render function\\n } // of compiled tmpl (either tmpl() or tmpl.render()...)\\n return runningTotal; // Return value from ~total(...)\\n }\\n }\\n});\\n\\nTag usage:\\n{{purchases lineItems sort=\\\"category\\\" ...}} \\n ...{{:~total('quantity*price')}}...\\n{{else}}\\n ...No items...\\n{{/purchases}}\\n\\n\\n\\\"use strict\\\";\\r\\n$.views.converters(\\\"dec2\\\", function(val) {\\r\\n return val.toFixed(2);\\r\\n})\\r\\n\\r\\nfunction categoryFilter(item, index, items) {\\r\\n return this.props.category === item.category;\\r\\n}\\r\\n\\r\\n$.views.tags(\\\"purchases\\\", {\\r\\n baseTag: \\\"for\\\", // Inherit from the {{for}} tag\\r\\n ctx: {\\r\\n total: function(expr) { // A ~total(expression) helper\\r\\n var tmpl = $.templates[expr] // Get named compiled template for expression, or else...\\r\\n || $.templates(expr, \\\"{{:\\\" + expr + \\\"}}\\\"), // ...if this is first call, create it\\r\\n\\r\\n runningTotal = 0,\\r\\n view = this, // The content view of the ~total(...) helper call\\r\\n items = view.get(\\\"array\\\").data,\\r\\n rowIndex = view.getIndex();\\r\\n\\r\\n for (var i = 0; i <= rowIndex; i++) {\\r\\n runningTotal += +tmpl(items[i]); // Compute running total up to this row, using render function\\r\\n } // of compiled tmpl (either tmpl() or tmpl.render()...)\\r\\n return runningTotal; // Return value from ~total(...)\\r\\n }\\r\\n }\\r\\n});\\r\\n\\r\\nvar purchases = {\\r\\n lineItems: [\\r\\n {category: \\\"book\\\", quantity: 2, price: 3.40},\\r\\n {category: \\\"grocery\\\", quantity: 5, price: 1.01},\\r\\n {category: \\\"grocery\\\", quantity: 2, price: 13.10},\\r\\n {category: \\\"book\\\", quantity: 1, price: 12.50}\\r\\n ]\\r\\n};\\r\\nvar html = $(\\\"#myTmpl\\\").render(purchases, {category: categoryFilter});\\r\\n\\r\\n$(\\\"#purchases\\\").html(html);\\n\\r\\n\\r\\n
      \\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"The above {{purchases}} custom tag can be easily updated to support data-binding. See purchases sample.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Tag context\",\r\n \"text\": \"Tag context\\nWhen a custom tag is used in a template then the rendered template instance will be part of the view hierarchy.\\nThe instance of the tag is an object with properties and methods:\\n\\ntag object\\n\\nAssociated with the tag instance is a tag context object, tagCtx, providing most of the useful context for a tag, in particular:\\n\\ncontext passed down through the view hierarchy:\\n\\ncurrent view\\ncurrent data\\nparent tags\\ncontextual parameters\\n\\nadditional context coming from the tag itself, or its markup:\\n\\narguments (args) and named parameters (props)\\nrendered tag template\\nblock content\\ncontent of else blocks\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Accessing the tag instance object\",\r\n \"text\": \"Accessing the tag instance object\\nFrom a tag method (init() or render()), the this pointer is the instance of the tag (a tag object.)\\nFrom a tag template, the tag instance can be accessed as ~tag.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Accessing the tag context object: tagCtx\",\r\n \"text\": \"Accessing the tag context object: tagCtx\\nFrom a tag method the tagCtx object is available as this.tagCtx.\\nIn the init() method it is also passed directly as an argument (function(tagCtx ...)).\\nFrom a tag template, tagCtx can be accessed as ~tag.tagCtx.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Accessing the tag arguments or named parameters\",\r\n \"text\": \"Accessing the tag arguments or named parameters\\nThe values of arguments can be accessed as tagCtx.args, and named parameters as tagCtx.props.\\nFor example, if we have the following tag, which has two arguments and one named parameter:\\n{{sometag title name mode=\\\"edit\\\"}}\\n\\nthen from within the init() or render() method of sometag, the arguments and named parameters can be accessed as:\\nvar title = this.tagCtx.args[0];\\nvar name = this.tagCtx.args[1];\\nvar mode = this.tagCtx.props.mode;\\n\\nand from the tag template, the values can be accessed as ~tagCtx.args or ~tagCtx.props, and so might be rendered as:\\n...title: {{>~tagCtx.args[0]}}
      name: {{>~tagCtx.args[1]}}
      mode: {{>~tagCtx.props.mode}}...\\n\\nIn addition to being available as tagCtx.args, arguments are also passed directly as arguments to the render() method, so sometag might use the following render() method, rather than a template, to render similar content:\\nfunction sometagRenderMethod(title, name) {\\n return \\\"...title: \\\" + title + \\\"
      name: \\\" + name + \\\"
      mode: \\\" + this.tagCtx.props.mode ...;\\n}\\n\\nThe tagCtx object also provides access to the markup expression for arguments and named parameters, as tagCtx.params.args and tagCtx.params.props.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Accessing the parent view and the current data\",\r\n \"text\": \"Accessing the parent view and the current data\\nThe contextual (parent) view for the tag instance is accessed as tagCtx.view. The corresponding (parent) data context is tagCtx.view.data.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Custom tag child views\",\r\n \"text\": \"Custom tag child views\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Custom tag rendering with template: \\\"mytag\\\" child view\",\r\n \"text\": \"Custom tag rendering with template: \\\"mytag\\\" child view\\nA custom tag template instance will be part of the view hierarchy, and the rendered tag may add additional child views to the view hierarchy.\\nIf {{mytag members}} renders using its template, that template will render as a child view (of type \\\"mytag\\\"). The default data context within the template will be the first argument passed to the tag (members in this case) which will be the view.data property of the child view.\\nIf the template markup includes template tags (other custom tags, or built-in tags) then there will be corresponding additional child views below the mytag view.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Rendering wrapped block content\",\r\n \"text\": \"Rendering wrapped block content\\n\\nAny tag can wrap block content, or use tmpl=... to reference external content:\\n{{mytag}}...{{/mytag}}\\n\\n{{mytag tmpl=... /}}\\n\\nBy default, a custom tag with no render() method or tag template will render its block content unchanged. A tag with an argument will move data context to the data passed in the argument: {{mytag somedata ...}}.\\nFor a custom tag rendering using a render() method, wrapped block content can be included using tagCtx.render().Note: To set the inner data context, pass in data as argument: tagCtx.render(someData). Otherwise inner and outer data context are the same.\\n$.views.tags(\\\"mytag\\\", {\\n ...\\n render: function() {\\n return ... + this.tagCtx.render() + ...;\\n },\\n ...\\n});\\n\\nSee the sample: Rendering block content from a custom tag render() method.\\n(For advanced scenarios the block content is also available as a compiled template object: tagCtx.content, so can be rendered using tagCtx.content.render(). See the template as fallback sample).\\nFor a custom tag rendering using a tag template, wrapped block content can be included using:\\n{{include tmpl=#content/}}\\n\\nor equivalently:\\n{{include tmpl=~tagCtx.content/}}\\n\\nwhere in each case the inner data context can be modified by passing an argument, {{include someData tmpl=... /}}.\\nSee the sample: Rendering block content from a custom tag template.\\n\\nNote that if a custom tag has an external tmpl=... reference, and inline block content, then the external template takes precedence. However, the external template can behave as a wrapper, wrapping the inline block content (see: Wrapping content).\\nThis can provide for cascading content, as in the following sample:\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Cascading content\",\r\n \"text\": \"Cascading content\\n\\n {{mytag tmpl='#external'}}wrappedContent{{/mytag}}\\n\\n\\n\\n externalTmplStart
      {{include tmpl=#content/}}
      /externalTmplEnd\\n\\n\\n\\n\\n$.views.tags(\\\"mytag\\\", {\\n template: \\\"mytagStart{{include tmpl=#content/}}/mytagEnd\\\"\\n});\\n\\nvar myTmpl = $.templates(\\\"#myTmpl\\\"),\\n data = {},\\n html = myTmpl.render(data);\\n\\n$(\\\"#page\\\").html(html);\\n$.views.tags(\\\"mytag\\\", {\\n template: \\\"mytagStart...{{include tmpl=#content/}}.../mytagEnd\\\"\\n});\\n\\n{{mytag tmpl='#external'}}wrappedContent{{/mytag}}\\n\\n\\n\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Rendering else blocks\",\r\n \"text\": \"Rendering else blocks\\nAny tag can use {{else}} blocks. We might for example create a custom tag for rendering lists:\\n{{list}}\\n First item\\n{{else}}\\n Second item\\n{{else}}\\n Last item\\n{{/list}}\\n\\nA custom tag can provide specific behavior/rendering for {{else}} blocks:\\n\\nFor a tag with a render method, render() will be called once for the initial block and once for each {{else}} block.\\nSimilarly, for a custom tag with a tag template, the template will be rendered once for the initial block and once for each {{else}} block.\\nDuring rendering a custom tag can detect which block is being rendered, using tagCtx.index (see below), and can then output the content corresponding to the desired functionality.\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Tag context objects for {{else}} blocks: the tagCtxs array\",\r\n \"text\": \"Tag context objects for {{else}} blocks: the tagCtxs array\\nA tag with multiple blocks (initial block plus 1 or more {{else}} blocks) will have a tagCtxs array of tagCtx objects, one for each block.\\n\\nFrom a tag method the tagCtxs array is available as this.tagCtxs.\\nFrom a tag template, tagCtxs can be accessed as ~tag.tagCtxs.\\n\\nEach tagCtx object in tagCtxs has an index property (0 for the initial block), as well as the other properties (args, props etc.) corresponding to the markup (arguments, named properties…) on the corresponding tag ({{mytag ...}} or {{else ...}}).\\n\\nWithin a tag render() method, this.tagCtx will be the current tag context object for that block.\\nSimilarly, during rendering of the tag template, ~tag.tagCtx will be the current tagCtx.\\n\\nTo determine the index of the block being rendered, use tagCtx.index.\\nThese features are illustrated in the following sample:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Custom {{list}} tag using {{else}} blocks\",\r\n \"text\": \"Custom {{list}} tag using {{else}} blocks\\n\\n {{list numbered=true}}First{{else}}Second{{else}}Last{{/list}}\\n {{list}}first{{else}}last{{/list}}\\n\\n\\n\\n\\n\\n// Define custom {{list}} tag\\n$.views.tags(\\\"list\\\", function() {\\n // render() method\\n var ret = \\\"\\\", // Return value\\n index = this.tagCtx.index, // block index\\n listElem = this.tagCtxs[0].props.numbered ? \\\"ol\\\" : \\\"ul\\\"; // Wrapper or element, based on numbered=true property \\n\\n if (index===0) {\\n ret += \\\"<\\\" + listElem + \\\">\\\"; // First block: add opening wrapper\\n }\\n ret += \\\"\\\" + this.tagCtx.render() + \\\"\\\"; // Add li element and block content\\n if (index===this.tagCtxs.length-1) {\\n ret += \\\"\\\"; // Last block: add closing wrapper\\n }\\n return ret;\\n});\\n\\nvar myTmpl = $.templates(\\\"#myTmpl\\\"),\\n html = myTmpl.render();\\n\\n$(\\\"#page\\\").html(html);\\n\\nCustom {{list}} tag:\\n$.views.tags(\\\"list\\\", function() {\\n // render() method\\n var ret = \\\"\\\", // Return value\\n index = this.tagCtx.index, // block index\\n listElem = this.tagCtxs[0].props.numbered ? \\\"ol\\\" : \\\"ul\\\"; // Wrapper
        or
          element, based on numbered=true property \\n\\n if (index===0) {\\n ret += \\\"<\\\" + listElem + \\\">\\\"; // First block: add opening wrapper\\n }\\n ret += \\\"
        • \\\" + this.tagCtx.render() + \\\"
        • \\\"; // Add li element and block content\\n if (index===this.tagCtxs.length-1) {\\n ret += \\\"\\\"; // Last block: add closing wrapper\\n }\\n return ret;\\n});\\n\\nUsage:\\n{{list numbered=true}}First{{else}}Second{{else}}Last{{/list}}\\n{{list}}first{{else}}last{{/list}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"And here is a version of the sample with the same tag implemented using a tag template, rather than a render() method.\\nHere we use the init() method to assign a tag template dynamically, using a different wrapper (ol or ul) based on the numbered named property:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Custom {{list}} tag: Rendering {{else}} blocks from a tag template\",\r\n \"text\": \"Custom {{list}} tag: Rendering {{else}} blocks from a tag template\\n\\n {{list numbered=true}}First{{else}}Second{{else}}Last{{/list}}\\n {{list}}first{{else}}last{{/list}}\\n\\n\\n\\n\\n\\n// Define custom {{list}} tag\\n$.views.tags(\\\"list\\\", {\\n init: function() {\\n var listElem = this.tagCtx.props.numbered ? 'ol' : 'ul'; // Wrapper ol or ul element\\n this.template = \\n // First block: add opening wrapper\\n \\\"{{if ~tagCtx.index===0}}<\\\" + listElem + \\\">{{/if}}\\\"\\n // Add li element and block content\\n + \\\"{{include tmpl=#content/}}\\\"\\n // Last block: add closing wrapper\\n + \\\"{{if ~tagCtx.index===~tag.tagCtxs.length-1}}{{/if}}\\\";\\n }\\n});\\n\\nvar myTmpl = $.templates(\\\"#myTmpl\\\"),\\n html = myTmpl.render();\\n\\n$(\\\"#page\\\").html(html);\\n\\nCustom {{list}} tag:\\n$.views.tags(\\\"list\\\", {\\n init: function() {\\n var listElem = this.tagCtx.props.numbered ? 'ol' : 'ul'; // Wrapper ol or ul element\\n this.template = \\n // First block: add opening wrapper\\n \\\"{{if ~tagCtx.index===0}}<\\\" + listElem + \\\">{{/if}}\\\"\\n // Add li element and block content\\n + \\\"
        • {{include tmpl=#content/}}
        • \\\"\\n // Last block: add closing wrapper\\n + \\\"{{if ~tagCtx.index===~tag.tagCtxs.length-1}}{{/if}}\\\";\\n }\\n});\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Custom tags with no render() method and no tag template can also render multiple blocks, using {{else}}. Here is an example:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"title\": \"Default behavior for custom tag with {{else}} blocks\",\r\n \"text\": \"Default behavior for custom tag with {{else}} blocks\\n\\n {{mytag last}}\\n First: {{:}}
          \\n {{else first}}\\n Last: {{:}}
          \\n {{else phone}}\\n Phone: {{:}}
          \\n {{/mytag}}\\n\\n\\n\\n\\n\\n// Define custom {{mytag}} tag\\n$.views.tags(\\\"mytag\\\", {});\\n\\nvar myTmpl = $.templates(\\\"#myTmpl\\\"),\\n data = {first: \\\"Jo\\\", last: \\\"Blow\\\", phone: \\\"111-111-1111\\\"},\\n html = myTmpl.render(data);\\n\\n$(\\\"#page\\\").html(html);\\n\\n\\nCustom {{mytag}} which simply renders each block as is:\\n$.views.tags(\\\"mytag\\\", {});\\n\\nThe default data context of each block is the value passed to the first argument.\\n{{mytag last}}\\n First: {{:}}...\\n{{else first}}\\n ...\\n{{else phone}}\\n ...\\n{{/mytag}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Custom tag hierarchy – Accessing parent tags\",\r\n \"text\": \"Custom tag hierarchy – Accessing parent tags\\nNested custom tags can determine parent tags, and can be designed with functionality or rendering that is based on parent or child tags, as in the following example where a {{layout}} tag determines the layout for child {{cell}} tags:\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"text\": \"\\n {{layout 'vertical'}}{{cell}}one{{/cell}}{{cell}}two{{/cell}}{{/layout}}\\n
          \\n {{layout 'horizontal'}}{{cell}}one{{/cell}}{{cell}}two{{/cell}}{{/layout}}\\n\\n\\n\\n$.views.tags({\\n layout: {\\n render: function(mode) {\\n if (mode === \\\"vertical\\\") {\\n this.vertical = true;\\n return \\\"\\\" + this.tagCtx.render() + \\\"\\\";\\n } else {\\n return \\\"\\\" + this.tagCtx.render() + \\\"\\\";\\n }\\n }\\n },\\n cell: {\\n render: function() {\\n return this.parents.layout.vertical\\n ? \\\"\\\" + this.tagCtx.render() + \\\"\\\"\\n : \\\"\\\" + this.tagCtx.render() + \\\"\\\";\\n }\\n }\\n})\\n\\nvar myTmpl = $.templates(\\\"#myTmpl\\\"),\\n data = { name: \\\"Jo\\\" },\\n html = myTmpl.render(data);\\n\\n$(\\\"#page\\\").html(html);\\n{{layout 'vertical'}}\\n {{cell}}one{{/cell}}\\n {{cell}}two{{/cell}}\\n{{/layout}}\\n
          \\n{{layout 'horizontal'}}\\n {{cell}}one{{/cell}}\\n {{cell}}two{{/cell}}\\n{{/layout}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"The following properties provide access to ancestor custom tags:\\nparents property:\\nThe parents property is a hash of all the ancestor custom tags. In the above sample the {{cell}} instances have a parents.layout property, used to determine whether the assigned layout is vertical, and to render accordingly:\\nrender: function() {\\n return this.parents.layout.vertical\\n ? \\\"\\\" + this.tagCtx.render() + \\\"\\\"\\n : \\\"\\\" + this.tagCtx.render() + \\\"\\\";\\n}\\n\\nparent property:\\nThe tag instance also has a parent property - in the above sample, the parent of the {{cell}} instance is the {{layout}} instance.\\n~parentTags contextual parameter:\\nThe ctx property of a tag instance also has a parentTags property, equivalent to the parents hash. This can be used in the following alternative implementation of the {{cell}} tag above, using a tag template rather than a render() method:\\n$.view.tags(\\\"cell\\\", {\\n template:\\n \\\"{{if ~parentTags.layout.vertical}}{{include tmpl=#content/}}\\\"\\n + \\\"{{else}}{{include tmpl=#content/}}{{/if}}\\\"\\n});\\n\\nIn fact, in a tag template ~parentTags and ~tag.parents are equivalent.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Accessing contextual parameters and helpers\",\r\n \"text\": \"Accessing contextual parameters and helpers\\n\\nFrom a tag template:\\n\\nContextual parameters and helpers can be accessed using ~myParamOrHelper\\n\\nFrom a tag method:\\n\\nContextual parameters and helpers can be accessed using this.ctxPrm(\\\"myParamOrHelper\\\")\\n(Note: contextual parameters can also be accessed using this.ctx.myParamOrHelper, and global helpers can be accessed using $views.helpers(\\\"myHelper\\\"))\\n\\n\\nAs an advanced example of custom tag rendering based on contextual parameters, here is a modified version of the above layout sample, where instead of wrapping {{cell}} tags in a {{layout}} tag, we instead wrap in a simple {{include}} on which we set a contextual parameter specifying layout: layout='vertical':\\n\"\r\n },\r\n {\r\n \"_type\": \"sample\",\r\n \"text\": \"\\n {{include ~layout='vertical'}}\\n {{cell}}one{{/cell}}\\n {{cell last=true}}two{{/cell}}\\n {{/include}}\\n
          \\n {{include ~layout='horizontal'}}\\n {{cell}}one{{/cell}}\\n {{cell last=true}}two{{/cell}}\\n {{/include}}\\n\\n\\n\\n$.views.tags({\\n cell: {\\n render: function() {\\n var res = \\\"\\\",\\n vertical = this.ctxPrm(\\\"layout\\\") === \\\"vertical\\\",\\n parentView = this.tagCtx.view.parent,\\n cellIndex = parentView.cellIndex = parentView.cellIndex === undefined ? 0 : parentView.cellIndex +1;\\n if (vertical) {\\n if (cellIndex===0) {\\n res += \\\"\\\";\\n }\\n res += \\\"\\\";\\n if (this.tagCtx.props.last) {\\n res += \\\"\\\" + this.tagCtx.render() + \\\"\\\";\\n }\\n } else {\\n if (cellIndex===0) {\\n res += \\\"\\\";\\n }\\n res += \\\"\\\";\\n if (this.tagCtx.props.last) {\\n res += \\\"\\\" + this.tagCtx.render() + \\\"\\\";\\n }\\n }\\n return res;\\n }\\n }\\n})\\n\\nvar myTmpl = $.templates(\\\"#myTmpl\\\"),\\n data = { name: \\\"Jo\\\" },\\n html = myTmpl.render(data);\\n\\n$(\\\"#page\\\").html(html);\\n{{include ~layout='vertical'}}\\n {{cell}}one{{/cell}}\\n {{cell last=true}}two{{/cell}}\\n{{/include}}\\n
          \\n{{include ~layout='horizontal'}}\\n {{cell}}one{{/cell}}\\n {{cell last=true}}two{{/cell}}\\n{{/include}}\\n\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"\",\r\n \"text\": \"Advanced options\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Specifying data context within tag content: the contentCtx option\",\r\n \"text\": \"Specifying data context within tag content: the contentCtx option\\nDefault behavior:\\nBy default the data context within the tag is the value of the first argument. (See View hierarchy – inner data context).\\nSo if {{mytag}} uses a template then {{mytag members/}} will render the template with members as data context.\\nSimilarly if {{mytag}} is used as a block tag, then the block content within {{mytag members}}...{{/mytag}} will render with members as data context.\\nModified behavior:\\nTo make the data context for tag content the same as parent context, set the contentCtx option to true:\\n$.views.tags(\\\"mytag\\\", {\\n ...\\n contentCtx: true, // The data context inside {{mytag}} will be the same as the outer context\\n ...\\n});\\n\\nTo specify a different data context for tag content, set the contentCtx option to a function returning the chosen data. (The this pointer of the contentCtx function is the tag instance. The default data context, arg0 is passed to it as argument.)\\nFor example, with the following tag option setting, the inner data context is given by the dataCtx named property:\\n$.views.tags(\\\"mytag\\\", {\\n ...\\n contentCtx: function(arg0) {\\n return this.tagCtx.props.dataCtx; // The returned value will be the data context inside {{mytag}}\\n },\\n ...\\n});\\n\\nUsage:\\n{{mytag ... dataCtx=.../}}\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Providing a default converter: the convert option\",\r\n \"text\": \"Providing a default converter: the convert option\\nOn any tag, including custom tags, a converter can be specified directly on the tag (see Using converters with other tags):\\n{{mytag name convert='toUpperCase'/}}\\n\\nTo provide a default converter on a custom tag (used as fallback if no converter is specified on the tag), set the convert tag option to a function, or to a registered converter name:\\n$.views.tags(\\\"mytag\\\", {\\n ...\\n convert: 'toLowerCase', // Default converter. (A function or a registered converter name)\\n ...\\n});\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Specifying a default argument: the argDefault option\",\r\n \"text\": \"Specifying a default argument: the argDefault option\\nIf a custom tag uses a render() method, then the arguments of the tag are passed to the render method:\\n{{mytag arg0 arg1/}}\\n\\n$.views.tags(\\\"mytag\\\", {\\n render: function(arg0, arg1) {...}\\n});\\n\\nIf the tag is called without arguments, then the render method will be called with the current data context as first argument, so therefore writing {{mytag/}} is equivalent to writing {{mytag #data/}}\\nTo override this behavior, set the argDefault option to false. The first argument will then not default to current data, and the render method will instead be called without arguments.\\n{{mytag/}}\\n\\n$.views.tags(\\\"mytag\\\", {\\n render: function() {\\n // arguments.length is 0\\n },\\n argDefault: false\\n});\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Specifying bound arguments and properties: the bindTo and bindFrom options\",\r\n \"text\": \"Specifying bound arguments and properties: the bindTo and bindFrom options\\nThe bindTo and bindFrom options are designed primarily for use with data binding, with JsViews, and allow specifying which arguments/properties are data-bound for two-way binding.\\nIn JsRender, the bindTo or bindFrom option can be used in conjunction with converters. Set the bindFrom option (or the bindTo option if there is no bindFrom setting) to an array, such as [0, 1, 2], or [\\\"title\\\", 1] – where integers refer to arguments and strings to named properties – to determine what values are passed to the converter. (If neither bindFrom nor bindTo are set, then the values of all the arguments will be passed to the converter.)\\nBy default the value returned by the converter will be passed as first argument to the render() method. However, if the converter returns an array, then the values will be used to convert each of the targeted arguments or properties specified in bindTo/bindFrom.\\nSee also JsViews bindTo\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Specifying flow behavior: the flow option\",\r\n \"text\": \"Specifying flow behavior: the flow option\\nA ‘flow’ tag – which has the flow option set to true – is a tag that does not appear in the parent tags hierarchy, so is not accessed via this.parent, ~tagParents etc.\\nThe built-in tags such as {{for}}, {{props}} and {{if}} are flow tags.\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Specifying default context: the ctx option\",\r\n \"text\": \"Specifying default context: the ctx option\\nThe ctx option of a tag can be used to provide default values of contextual parameters:\\n$.views.tags(\\\"mytag\\\", {\\n template: \\\"{{:~mode}}\\\",\\n ctx: {mode: \\\"readonly\\\"}, // Specify default ~mode if not provided by a helper or as a contextual parameter, \\n ...\\n});\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Methods and properties available on a custom tag instance\",\r\n \"text\": \"Methods and properties available on a custom tag instance\\nA custom tag instance can access the following methods and properties\\n\\ntag.ctxPrm()\\ntag.cvt()\\ntag.cvtArgs()\\ntag.bndArgs()\\ntag.ctx\\ntag.parent\\ntag.parents\\ntag.tagCtx\\ntag.tagCtxs\\ntag.tagName\\ntag.base\\nrendering\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Adding tags as private resources for a parent template\",\r\n \"text\": \"Adding tags as private resources for a parent template\\nYou can pass in an existing template as an additional parentTemplate parameter, on any call to $.views.tags(...).\\nIn that way the tag (or tags) you are registering become ‘private tag resources’ for the parentTemplate, rather than being registered globally:\\n\"\r\n },\r\n {\r\n \"_type\": \"api\",\r\n \"title\": \"\",\r\n \"text\": \"Add multiple tags as resources, to a parent template\\n\\n$.views.tags({\\n mytag1: ...,\\n mytag2: ...\\n}, parentTemplate);\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Unregistering tags\",\r\n \"text\": \"Unregistering tags\\nTo unregister a previously registered tag, pass null to $.views.tags():\\n$.views.tags(\\\"mytag\\\", null);\\n// Tag \\\"mytag\\\" is no longer registered\\n\\n\"\r\n },\r\n {\r\n \"_type\": \"para\",\r\n \"title\": \"Custom tags and 'tag controls'\",\r\n \"text\": \"Custom tags and 'tag controls'\\nIf you use JsViews, your custom tag can be developed into a fully functional tag control, with its own lifecycle, properties and methods, etc. It can be used as a presenter according to the MVP pattern.\\n\"\r\n },\r\n {\r\n \"_type\": \"links\",\r\n \"title\": \"See also:\",\r\n \"text\": \"See also:\\n\"\r\n }\r\n ]\r\n }\r\n}"]} \ No newline at end of file diff --git a/documentation/find-jsvapi.js b/documentation/find-jsvapi.js index 5a04d147..41e00f01 100644 --- a/documentation/find-jsvapi.js +++ b/documentation/find-jsvapi.js @@ -89,7 +89,7 @@ content.find.jsvapi = content.useStorage && $.parseJSON(localStorage.getItem("Js { "_type": "sample", "title": "template.link(object):", - "text": "template.link(object):\n\n\n\n \n {^{:name}}\n \n \n\nvar myTmpl = $.templates(\"#personTmpl\");\n\nvar person = {\n name: \"Adriana\"\n };\n\nvar html = myTmpl.link(\"#person\", person);\n\n{^{:name}}\n\n\nvar myTmpl = $.templates(\"#personTmpl\");\n\nmyTmpl.link(\"#person\", person);\n\n\n" + "text": "template.link(object):\n\n\n\n \n {^{>name}}\n \n \n\nvar myTmpl = $.templates(\"#personTmpl\");\n\nvar person = {\n name: \"Adriana\"\n };\n\nvar html = myTmpl.link(\"#person\", person);\n\n{^{>name}}\n\n\nvar myTmpl = $.templates(\"#personTmpl\");\n\nmyTmpl.link(\"#person\", person);\n\n\n" }, { "_type": "para", @@ -99,7 +99,7 @@ content.find.jsvapi = content.useStorage && $.parseJSON(localStorage.getItem("Js { "_type": "sample", "title": "template.link(array):", - "text": "template.link(array):\nAdd person\n\n\n\n\n \n {^{:name}}\n \n \n\nvar myTmpl = $.templates(\"#personTmpl\");\n\nvar people = [\n {\n name: \"Adriana\"\n },\n {\n name: \"Robert\"\n }\n];\n\nmyTmpl.link(\"#peopleList\", people);\n\n$(\"#add\").on(\"click\", function() {\n $.observable(people).insert({\n name: \"name\"\n });\n});\n\nmyTmpl.link(\"#peopleList\", people);\n\n\n" + "text": "template.link(array):\nAdd person\n\n\n\n\n \n {^{>name}}\n \n \n\nvar myTmpl = $.templates(\"#personTmpl\");\n\nvar people = [\n {\n name: \"Adriana\"\n },\n {\n name: \"Robert\"\n }\n];\n\nmyTmpl.link(\"#peopleList\", people);\n\n$(\"#add\").on(\"click\", function() {\n $.observable(people).insert({\n name: \"name\"\n });\n});\n\nmyTmpl.link(\"#peopleList\", people);\n\n\n" }, { "_type": "para", @@ -139,7 +139,7 @@ content.find.jsvapi = content.useStorage && $.parseJSON(localStorage.getItem("Js { "_type": "sample", "title": "template.link(container, array, helpers, noIteration):", - "text": "template.link(container, array, helpers, noIteration):\nAdd person\n\n\n\n\n \n \n \n {^{for}}\n \n \n \n {{/for}}\n \n
          \n {^{:#data.length}} people\n
          {^{:name}}
          \n\nvar myTmpl = $.templates(\"#personTmpl\");\n\nvar people = [\n {\n name: \"Adriana\"\n },\n {\n name: \"Robert\"\n }\n];\n\nmyTmpl.link(\"#peopleList\", people, null, true);\n\n$(\"#add\").on(\"click\", function() {\n $.observable(people).insert({\n name: \"name\"\n });\n});\n\nCode:\nmyTmpl.link(\"#peopleList\", people, null, true);\n\nTemplate:\n\n \n \n {^{for}}\n \n \n \n {{/for}}\n \n
          \n {^{:#data.length}} people\n
          {^{:name}}
          \n\n\n" + "text": "template.link(container, array, helpers, noIteration):\nAdd person\n\n\n\n\n \n \n \n {^{for}}\n \n \n \n {{/for}}\n \n
          \n {^{:#data.length}} people\n
          {^{>name}}
          \n\nvar myTmpl = $.templates(\"#personTmpl\");\n\nvar people = [\n {\n name: \"Adriana\"\n },\n {\n name: \"Robert\"\n }\n];\n\nmyTmpl.link(\"#peopleList\", people, null, true);\n\n$(\"#add\").on(\"click\", function() {\n $.observable(people).insert({\n name: \"name\"\n });\n});\n\nCode:\nmyTmpl.link(\"#peopleList\", people, null, true);\n\nTemplate:\n\n \n \n {^{for}}\n \n \n \n {{/for}}\n \n
          \n {^{:#data.length}} people\n
          {^{>name}}
          \n\n\n" }, { "_type": "links", @@ -186,8 +186,8 @@ content.find.jsvapi = content.useStorage && $.parseJSON(localStorage.getItem("Js }, { "_type": "api", - "title": "$.link.myTmpl(container, data, helpersOrContext)", - "text": "$.link.myTmpl(container, data, helpersOrContext)\nRender and link a named template against data, along with helper objects or context\nRender and link template against data (as content of a container element) and pass in helpers\n\n$.link.myTmpl(\"#container\", myData, myHelpers);\n\n" + "title": "$.link.myTmpl(container, data, helpersOrContext, noIteration)", + "text": "$.link.myTmpl(container, data, helpersOrContext, noIteration)\nRender and link a named template against data, under a container element. (Optionally provide helpers/context and specify iteration behavior for arrays)\nRender and link template against data as content of a container element. Optionally pass in helpers and specify iteration behavior for arrays\n\n$.link.myTmpl(\"#container\", myData, myHelpers, true);\n\n" }, { "_type": "para", @@ -229,12 +229,17 @@ content.find.jsvapi = content.useStorage && $.parseJSON(localStorage.getItem("Js { "_type": "para", "title": "", - "text": "Each instance of a rendered template or a template block tag is associated with a JsViews “view” object.\nUse $.view() to get from the rendered HTML back to the data.\n" + "text": "Each instance of a rendered template or a template block tag is associated with a JsViews “view” object.\nOne of the features provided by JsViews data-linking (when you use the JsViews .link() method rather than JsRender’s .render() method) is the $.view(elem) method. This method provides a reverse mapping and lets you get from a rendered DOM element back to the corresponding view object in the view hierarchy. From the view you can get to the underlying data, the index, etc.\nSo in effect in JsViews, the mapping from the view hierarchy to the UI becomes a two-way mapping…\n" }, { "_type": "links", - "title": "", - "text": "" + "title": "See:", + "text": "See:\n" + }, + { + "_type": "para", + "title": "Technical note on how JsViews establishes two-way data binding:", + "text": "Technical note on how JsViews establishes two-way data binding:\nJsViews data-linking means that observable changes to the bound data automatically trigger dynamic updates to the rendered HTML.\nSuppose for example we have this template:\n
            \n {^{for people}}\n
          • Name: {^{:name}}
          • \n {{/for}}\n
          \n\nIf name changes observably, then the name value in both the and the text content (Name: XXX) will update to the new value. Similarly, adding or removing a person item from the people array will cause an
        • element to be added or removed.\nTo achieve this, JsViews link() method includes meta-data tokens in the rendered HTML, which it then uses as locators to ensure that content is updated at the correct places in the HTML.\nThe meta-data does not affect visual rendering in any way, and consists essentially of data-jsv attributes on some HTML elements, as well as some inserted script tags of type: jsv... when locators are needed within text content.\nIn the case of the above template, the HTML markup will be similar to the following:\n
            \n
          • \n Name: Jo Blow\n
          • \n
          \n\n" } ] }, @@ -353,7 +358,7 @@ content.find.jsvapi = content.useStorage && $.parseJSON(localStorage.getItem("Js { "_type": "para", "title": "A view object has the following properties and methods:", - "text": "A view object has the following properties and methods:\nJsViews – programmatic access only\nThe following methods are available only for programmatic access when using JsViews:\n\nrefresh() method\ncontents() method\nchildTags() method\nnodes() method\nctxPrm() get/set method\n\nBoth JsRender and JsViews (see JsRender view object)\nThe following properties and methods are available when using either JsRender or JsViews:\n\ntype property\ndata property\nparent property\nindex property\ngetIndex() method\nget(type) method\ncontent property\nroot property\nother properties (tmpl, views, ctx, tag)\n\n" + "text": "A view object has the following properties and methods:\nJsViews – programmatic access only\nThe following methods are available only for programmatic access when using JsViews:\n\nrefresh() method\ncontents() method\nchildTags() method\nnodes() method\nctxPrm() get/set method\n\nBoth JsRender and JsViews (see JsRender view object)\nThe following properties and methods are available when using either JsRender or JsViews (both for programmatic access and declaratively in templates):\n\ntype property\ndata property\nparent property\nindex property\ngetIndex() method\nget(type) method\ncontent property\nroot property\nother properties (tmpl, views, ctx, tag)\n\n" }, { "_type": "para", @@ -368,12 +373,12 @@ content.find.jsvapi = content.useStorage && $.parseJSON(localStorage.getItem("Js { "_type": "sample", "title": "view.refresh()", - "text": "view.refresh()\ntable td {padding: 5px;} table {margin-top: 6px;} #incrBtn {margin-bottom: 10px;}\n\n\n\n\n \n \n \n
          \n Year: {^{:year}}
          \n\n \n {{for people}}\n \n \n {{!-- no data-linking --}}\n \n \n \n {{/for}}\n
          Name: {{:name}}Age in {{:~root.year}}: {{:age + ~root.year - 2016}}
          \n\nvar tmpl = $.templates(\"#peopleTmpl\");\n\nvar model = {\n year: 2016,\n people: [{name: \"Adriana\", age: 4}, {name: \"Robert\", age: 34}]\n};\n\ntmpl.link(\"#peopleList\", model)\n .on(\"click\", \"#incrBtn\", function() {\n model.year++; // non-observable change\n })\n .on(\"click\", \"#incrObsBtn\", function() {\n $.observable(model).setProperty(\"year\", model.year + 1);\n })\n .on(\"click\", \"#incrRefreshBtn\", function() {\n model.year++; // non-observable change\n $.view(this).refresh();\n })\n .on(\"click\", \".refreshBtn\", function() {\n $.view(this).refresh();\n });\nTemplate: (No data-linking except )\n{{for people}}\n ...\n \n ...\n {{:name}} ... {{:~root.year}} ... {{:age + ~root.year - 2016}}\n ...\n \n{{/for}}\n\nCode:\n.on(\"click\", \"#incrBtn\", function() {\n model.year++; // non-observable change\n})\n.on(\"click\", \".refreshBtn\", function() {\n $.view(this).refresh(); // Refresh view, with updated values...\n});\n\n\n" + "text": "view.refresh()\ntable td {padding: 5px;} table {margin-top: 6px;} #incrBtn {margin-bottom: 10px;}\n\n\n\n\n \n \n \n
          \n Year: {^{>year}}
          \n\n \n {{for people}}\n \n \n {{!-- no data-linking --}}\n \n \n \n {{/for}}\n
          Name: {{>name}}Age in {{>~root.year}}: {{>age + ~root.year - 2016}}
          \n\nvar tmpl = $.templates(\"#peopleTmpl\");\n\nvar model = {\n year: 2016,\n people: [{name: \"Adriana\", age: 4}, {name: \"Robert\", age: 34}]\n};\n\ntmpl.link(\"#peopleList\", model)\n .on(\"click\", \"#incrBtn\", function() {\n model.year++; // non-observable change\n })\n .on(\"click\", \"#incrObsBtn\", function() {\n $.observable(model).setProperty(\"year\", model.year + 1);\n })\n .on(\"click\", \"#incrRefreshBtn\", function() {\n model.year++; // non-observable change\n $.view(this).refresh();\n })\n .on(\"click\", \".refreshBtn\", function() {\n $.view(this).refresh();\n });\nTemplate: (No data-linking except )\n{{for people}}\n ...\n \n ...\n {{>name}} ... {{>~root.year}} ... {{>age + ~root.year - 2016}}\n ...\n \n{{/for}}\n\nCode:\n.on(\"click\", \"#incrBtn\", function() {\n model.year++; // non-observable change\n})\n.on(\"click\", \".refreshBtn\", function() {\n $.view(this).refresh(); // Refresh view, with updated values...\n});\n\n\n" }, { "_type": "para", "title": "The contents() method", - "text": "The contents() method\nview.contents(…): returns a jQuery object of view content nodes – optionally filtered by a jQuery selector.\nvar jqMyClassElem = view.contents(true, \".myClass\"); // jQuery object for element with 'myClass'at any depth within view\n\n" + "text": "The contents() method\nview.contents(…): returns a jQuery object of view content nodes – optionally filtered by a jQuery selector.\nvar jqMyClassElem = view.contents(true, \".myClass\");\n// jQuery object for element with 'myClass'at any depth within view\n\n" }, { "_type": "api", @@ -427,7 +432,7 @@ content.find.jsvapi = content.useStorage && $.parseJSON(localStorage.getItem("Js }, { "_type": "sample", - "text": "\n\n\n {^{on ~set \"green\"}}set ~color to green{{/on}}\n {^{on ~set \"red\"}}set ~color to red{{/on}}\n {^{on ~get}}get ~color{{/on}}\n \n \n ({^{:~color}})\n\n\nvar tmpl = $.templates(\"#tmpl\"),\n\n model = {},\n\n helpers = {\n color: \"blue\",\n set: function(newColor, ev, eventArgs) {\n eventArgs.view.ctxPrm(\"color\", newColor); // Set contextual parameter: \"color\" to newColor\n },\n get: function(ev, eventArgs) {\n alert(eventArgs.view.ctxPrm(\"color\")); // Get current contextual parameter \"color\"\n }\n };\n\ntmpl.link(\"#result\", model, helpers);\nTemplate:\nTEXT\n\n({^{:~color}})\n\nCode:\nset: function(newColor, ev, eventArgs) {\n eventArgs.view.ctxPrm(\"color\", newColor); // Set contextual parameter: \"color\" to newColor \n},\nget: function(ev, eventArgs) {\n alert(eventArgs.view.ctxPrm(\"color\")); // Get current contextual parameter \"color\"\n}\n\n\n" + "text": "\n\n\n {^{on ~set \"green\"}}set ~color to green{{/on}}\n {^{on ~set \"red\"}}set ~color to red{{/on}}\n {^{on ~get}}get ~color{{/on}}\n \n \n ({^{>~color}})\n\n\nvar tmpl = $.templates(\"#tmpl\"),\n\n model = {},\n\n helpers = {\n color: \"blue\",\n set: function(newColor, ev, eventArgs) {\n eventArgs.view.ctxPrm(\"color\", newColor); // Set contextual parameter: \"color\" to newColor\n },\n get: function(ev, eventArgs) {\n alert(eventArgs.view.ctxPrm(\"color\")); // Get current contextual parameter \"color\"\n }\n };\n\ntmpl.link(\"#result\", model, helpers);\nTemplate:\nTEXT\n\n({^{>~color}})\n\nCode:\nset: function(newColor, ev, eventArgs) {\n eventArgs.view.ctxPrm(\"color\", newColor); // Set contextual parameter: \"color\" to newColor \n},\nget: function(ev, eventArgs) {\n alert(eventArgs.view.ctxPrm(\"color\")); // Get current contextual parameter \"color\"\n}\n\n\n" }, { "_type": "para", @@ -449,96 +454,77 @@ content.find.jsvapi = content.useStorage && $.parseJSON(localStorage.getItem("Js "sections": [ { "_type": "para", - "title": "", - "text": "JsRender\nDeclare\n\ntag.template\ntag.flow\ntag.baseTag\ntag.contentCtx\ntag.argDefault\ntag.bindTo\n\nEvent handlers\n\ntag.init()\ntag.render()\ntag.convert()\n\nProps/Methods\n\ntag.ctxPrm()\ntag.cvt()\ntag.cvtArgs()\ntag.bndArgs()\ntag.ctx\ntag.parent\ntag.parents\ntag.tagCtx\ntag.tagCtxs\ntag.tagName\ntag.base\ntag.baseApply\nrendering\n\nJsViews\nDeclare\n\ntag.boundProps\ntag.linkedCtxParam\ntag.mainElement\ntag.linkedElement\ntag.displayElement\ntag.setSize\ntag.attr (e.g. {show ...}\ntag.dataBoundOnly\ndateMap\nlateRender (feature)\n\nEvent handlers\n\ntag.convertBack()\ntag.domChange()\ntag.depends()\ntag.onBeforeBind()\ntag.onBind()\ntag.BeforeLink()\ntag.onAfterLink()\ntag.onUpdate()\ntag.onBeforeUpdateVal()\ntag.onDispose()\ntag.onBeforeChange()\ntag.onAfterChange()\n\nProps/Methods\n\ntag.refresh()\ntag.contents()\ntag.childTags()\ntag.nodes()\ntag.setValue()\ntag.setValues()\ntag.updateValue()\ntag.updateValues()\ntag.linkCtx\ntag.parentElem\ntag._.inline\ntag.linkedElem\ntag.displayElem\ntag.linkedElems\ntag.mainElem\n\n" + "title": "Tag object properties and event handlers provided as tag options", + "text": "Tag object properties and event handlers provided as tag options\nThe following tag properties and event handlers can be specified as tag options when registering a custom tag:\nTag properties (both in JsRender and JsViews – see $.views.tags()):\n\nbaseTag\nflow\ntemplate\nbindTo\nctx\ncontentCtx\nargDefault\n\nTag properties (only in JsViews – see tag control options):\n\ndataBoundOnly\nboundProps\ndepends\nattr\nsetSize\nheight\nwidth\nclassName\nlinkedElement\nmainElement\ndisplayElement\nlinkedCtxParam\ndataMap\nlateRender\ntrigger\n\nEvent handlers (both in JsRender and JsViews – see $.views.tags()):\n\ninit()\nrender()\nconvert\n\nEvent handlers (only in JsViews – see tag control options):\n\nonBind()\nonAfterLink()\nonUpdate()\nonDispose()\nconvertBack\nonUnbind()\nonBeforeUpdateVal()\nonBeforeChange()\nonAfterChange()\nonArrayChange()\nsetValue()\ndomChange()\n\n" }, { "_type": "para", - "title": "A view object has the following properties and methods:", - "text": "A view object has the following properties and methods:\nJsViews – programmatic access only\nThe following methods are available only for programmatic access when using JsViews:\n\nrefresh() method\ncontents() method\nchildTags() method\nnodes() method\nctxPrm() get/set method\n\nBoth JsRender and JsViews (see JsRender view object)\nThe following properties and methods are available when using either JsRender or JsViews:\n\ntype property\ndata property\nparent property\nindex property\ngetIndex() method\nget(type) method\ncontent property\nroot property\nother properties (tmpl, views, ctx, tag)\n\n" + "title": "Additional tag object properties and methods", + "text": "Additional tag object properties and methods\nIn addition to the above properties and handlers set as tag options, the tag object has the following properties and methods:\nTag properties (both in JsRender and JsViews)\n\nparent\nparents\ntagCtx\ntagCtxs\ntagName\nrendering\n\nTag properties (only in JsViews)\n\nlinkCtx\nparentElem\n\nTag methods (both in JsRender and JsViews)\n\nctxPrm()\ncvt()\ncvtArgs()\nbndArgs()\nbase()\nbaseApply()\n\nTag methods (only in JsViews)\n\nrefresh()\ncontents()\nchildTags()\nnodes()\nsetValue()\nsetValues()\nupdateValue()\nupdateValues()\n\n" }, { "_type": "para", - "title": "Accessing view objects", - "text": "Accessing view objects\nThe view object can be accessed programmatically in many contexts, such as:\n\nin a click handler (with JsViews) – using $.view(this) to return the view for a given HTML element (this)\nin a helper function, ~myHelper() – where the this pointer is the current view\nin any method of a custom tag – using this.tagCtx.view\n\nIn addition, properties and methods that are available to both JsRender and JsViews (second list above) can also be accessed declaratively in a template using view paths – such as #parent for the view.parent property.\n\nProperties and methods:\n" + "title": "Accessing tag objects", + "text": "Accessing tag objects\nThe tag object can be accessed programmatically, for example in event handlers of custom tags, using the this pointer.\nThe current tag can also be accessed declaratively (in a custom tag template, or in wrapped block content) using ~tag, as in:\n{{:~tag.parent.tagName}}`\n\nIn addition, tag.tagCtx can be accessed declaratively using ~tagCtx, as in:\n{{:~tagCtx.props.mode}}`\n\nTag properties and methods (JsViews only):\n(Additional details to follow.)\n" }, { "_type": "para", - "title": "The refresh() method", - "text": "The refresh() method\ntag.refresh(): refreshes the tag, by re-rendering its content.\nThis can be used to update a tag using modified data, helpers or tag parameters. See also the similar view.refresh() method.\ntag.refresh(); // Refresh the tag, using current data values, helpers and tag parameters.\n\nThe following example has a custom tag that uses a template rendering data without data-linking. (Even it had data-linking, it will also allow picking up data changes which were modified ‘non-observably’, as in: model.year++;).\nThe sample is similar to the sample provided with the view.refresh() documentation.\n" - }, - { - "_type": "sample", - "title": "tag.refresh()", - "text": "tag.refresh()\ntable td {padding: 5px;} table {margin-top: 6px;} #incrBtn {margin-bottom: 10px;}\n\n\n\n\n \n \n Name: {{dbg:name}} {{!-- no data-linking --}}\n Age in {{:~root.year}}: {{:age + ~root.year - 2016}} \n {^{on ~tag.callRefresh}}Refresh{{/on}}\n \n\n\n\n \n \n \n
          \n Year: {^{:year}}
          \n\n \n {{for people}}\n {^{personRow/}}\n {{/for}}\n
          \n\n$.views.tags(\"personRow\", {\n template: \"#personRowTmpl\",\n callRefresh: function() {\n // tag.refresh() will re-render tag, and take in to account modified data\n this.refresh();\n }\n}\n);\nvar tmpl = $.templates(\"#peopleTmpl\");\n\nvar model = {\n year: 2016,\n people: [{name: \"Adriana\", age: 4}, {name: \"Robert\", age: 34}]\n};\n\ntmpl.link(\"#peopleList\", model)\n .on(\"click\", \"#incrBtn\", function() {\n model.year++; // non-observable change\n })\n .on(\"click\", \"#incrObsBtn\", function() {\n $.observable(model).setProperty(\"year\", model.year + 1);\n })\n .on(\"click\", \"#incrRefreshBtn\", function() {\n model.year++; // non-observable change\n $.view(this).refresh();\n });\nTag template: (No data-linking except )\n...\n\n...\n{{:name}} ... {{:~root.year}} ... {{:age + ~root.year - 2016}}\n...\n{^{on ~tag.callRefresh}}Refresh{{/on}}\n\nTag code:\n$.views.tags(\"personRow\", {\n template: \"#personRowTmpl\",\n callRefresh: function() {\n // tag.refresh() will re-render tag, and take in to account modified data\n this.refresh();\n }\n});\n\nTemplate:\n\n...\n{{for people}}\n {^{personRow/}}\n{{/for}}\n\n\n" + "title": "The linkCtx property", + "text": "The linkCtx property\n" }, { "_type": "para", - "title": "The contents() method", - "text": "The contents() method\nview.contents(…): returns a jQuery object of view content nodes – optionally filtered by a jQuery selector.\nvar jqMyClassElem = view.contents(true, \".myClass\"); // jQuery object for element with 'myClass'at any depth within view\n\n" - }, - { - "_type": "api", - "title": "view.contents(...)", - "text": "view.contents(...)\nGet a jQuery object for the contents of the view (top-level child nodes – including text nodes)\n\nvar jqContents = view.contents();\njqContents.css(\"color\", \"red\");\n\nGet a jQuery object for the top-level contents of the view, filtered by the jQuery selector\n\nvar jqContents = view.contents(\".toRed\");\njqContents.css(\"color\", \"red\");\n\nGet a jQuery object for the contents of the view: child and descendant nodes, filtered by the selector\n\nvar jqContents = view.contents(true, \".toRed\");\njqContents.css(\"color\", \"red\");\n\n" + "title": "The parentElem property", + "text": "The parentElem property\n" }, { "_type": "para", - "title": "", - "text": "The following example uses view.contents() to find the \".nameId\" within the view, and set its background color:\n" + "title": "The refresh() method", + "text": "The refresh() method\n" }, { - "_type": "sample", - "title": "view.contents()", - "text": "view.contents()\n.nameTd {width: 60px; padding: 0 6px;} table {width: 230px;} button {margin: 4px;}\n\n\n\n\n \n \n {^{for people}}\n \n \n \n \n {{/for}}\n
          {^{:name}}\n \n \n
          \n\nvar tmpl = $.templates(\"#peopleTmpl\");\n\nvar model = {\n people: [{name: \"Adriana\"}, {name: \"Robert\"}]\n};\n\ntmpl.link(\"#peopleList\", model)\n .on(\"click\", \"#addBtn\", function() {\n var view = $.view(this); // \"data\" view\n var data = view.data; // model\n $.observable(data.people).insert({name: \"name\"});\n })\n .on(\"click\", \".orange, .yellow\", function() {\n var view = $.view(this); // \"item\" view\n\n // Get jQuery object for \".nameTd\" in this view:\n var jqNameTd = view.contents(true, \".nameTd\");\n\n // Set background color\n jqNameTd.css(\"backgroundColor\", this.className);\n });\n// Get jQuery object for \".nameTd\" in this view:\nvar jqNameTd = view.contents(true, \".nameTd\");\n\n// Set background color\njqNameTd.css(\"backgroundColor\", this.className);\n\n\n" + "_type": "para", + "title": "The contents() method", + "text": "The contents() method\n" }, { "_type": "para", "title": "The childTags() method", - "text": "The childTags() method\nview.childTags(…): returns an array of custom tag instances within the view – optionally filtered by tag name.\nvar mytagsArray = view.childTags(true, \"mytag\"); // {{mytag}} instances within view (at any depth)\n\n" - }, - { - "_type": "api", - "title": "view.childTags(...)", - "text": "view.childTags(...)\nGet top-level custom tag instances within the view\n\nvar tagsArray = view.childTags();\nvar firstTagName = tagsArray[0].tagName;\n\nGet instances of {{tagName}} in view (not nested in other custom tags)\n\nvar sliders = view.childTags(\"slider\");\nsliders[0].updateValue(25);\n\nGet instances of {{tagName}} in view (including those nested in other custom tags)\n\nvar jqContents = view.childTags(true, \".toRed\");\njqContents.css(\"color\", \"red\");\n\n" + "text": "The childTags() method\n" }, { "_type": "para", - "title": "", - "text": "Note that view.childTags() looks only for custom tags. (In fact it searches for tags which do not have the flow property set to true. All built-in tags such as {{for}} and {{if}} have the setting flow: true, so are ignored by childTags(). However even ‘flow tags’ will be returned if searched for my name, as in: view.childTags(\"if\").)\nThe following sample looks for {{textbox}} tags (in the case data-link=\"{textbox ...}\") and calls a method on each.\n" - }, - { - "_type": "sample", - "title": "view.childTags()", - "text": "view.childTags()\n#toggleBtn {margin-bottom: 14px;} .person {line-height: 26px;}\n\n\n\n \n\n {^{for people}}\n {{!--data-link to {{textbox}} tag --}}\n
          \n {{/for}}\n\n// Define a {{textbox}} tag - which allows editing, and has a toggleEdit() method\n$.views.tags({\n textbox: {\n init: function() {\n var path = this.tagCtx.params.props.path;\n\n this.template = \" \" // Checkbox to toggle edit\n + \"\" // for editing\n + \"\"; // for rendering\n },\n toggleEdit: function() {\n $.observable(this).setProperty(\"edit\", !this.edit);\n }\n }\n});\n\nvar tmpl = $.templates(\"#peopleTmpl\");\nvar model = {\n people: [{name: \"Adriana\"}, {name: \"Robert\"}]\n };\n\ntmpl.link(\"#peopleList\", model)\n .on(\"click\", \"#toggleBtn\", function() {\n var textBoxes = $.view(this).childTags(\"textbox\"); // Find all the {{textbox}} tags in the view\n for (var i=0; i{^{for people}}\n {{!--data-link to {{textbox}} tag --}}\n
          \n{{/for}}\n\n.on(\"click\", \"#toggleBtn\", function() {\n var textBoxes = $.view(this).childTags(\"textbox\"); // Find all the {{textbox}} tags in the view\n for (var i=0; iAdd\n \n {^{for people}}\n \n \n \n \n \n \n {{/for}}\n
          {^{:name}}
          \n \n \n
          \n\nvar tmpl = $.templates(\"#peopleTmpl\");\n\nvar model = {\n people: [{name: \"Adriana\"}, {name: \"Robert\"}]\n};\n\ntmpl.link(\"#peopleList\", model)\n .on(\"click\", \"#addBtn\", function() {\n var view = $.view(this); // \"data\" view\n var data = view.data; // model\n $.observable(data.people).insert({name: \"name\"});\n })\n .on(\"click\", \".orange, .yellow\", function() {\n var view = $.view(this); // \"item\" view\n\n // Get top-level nodes in this view - two nodes:\n var nodes = view.nodes();\n\n // Set colors\n nodes[0].style.color = this.className;\n nodes[0].style.backgroundColor = \"darkblue\";\n nodes[1].style.backgroundColor = this.className;\n });\n.on(\"click\", \".orange, .yellow\", function() {\n var view = $.view(this); // \"item\" view\n\n // Get top-level nodes in this view - two nodes:\n var nodes = view.nodes();\n\n // Set colors\n nodes[0].style.color = this.className;\n nodes[0].style.backgroundColor = \"darkblue\";\n nodes[1].style.backgroundColor = this.className;\n});\n\n\n" + "_type": "para", + "title": "The setValues() method", + "text": "The setValues() method\nIf a custom tag control uses linked elements then calling tag.setValues() will set the values of the linked elements. In addition, if the tag control has a setValue() event handler, then that event handler will be called prior to updating each targeted linked element.\nFor block tags with multiple {{else}} blocks, setValues() will set values on the linked elements on the initial tag (the first block). To set values on the linked elements in additional {{else}} blocks, use setValue().\nSee the Multiple two-way binding design patterns topic for additional discussion and examples.\n" }, { "_type": "para", - "title": "The ctxPrm() get/set method:", - "text": "The ctxPrm() get/set method:\nview.ctxPrm(name): returns the value of the named contextual parameter, within the context of that view.\nvar value = view.ctxPrm(\"color\"));\n// Get value of current contextual parameter \"color\"\n\nview.ctxPrm(name, newValue): observably modifies the value of the named contextual parameter, within the context of that view.\nview.ctxPrm(\"color\", \"green\"));\n// Set value of current contextual parameter \"color\" to \"green\"\n\n" + "title": "The updateValue() method", + "text": "The updateValue() method\nCalling tag.updateValue(newValue, index) will observably update the bound argument or property corresponding to the specified index of the bindTo array.\nFor block tags, with multiple {{else}} blocks, a third parameter can be passed specifying the index of the {{else}} block whose bound argument or property is being updated:\ntag.updateValue(value, index, elseBlock);\n\nThe call will use two-way data-binding to update the underlying data specified in the data-link expression.\n" }, { "_type": "sample", - "text": "\n\n\n {^{on ~set \"green\"}}set ~color to green{{/on}}\n {^{on ~set \"red\"}}set ~color to red{{/on}}\n {^{on ~get}}get ~color{{/on}}\n \n \n ({^{:~color}})\n\n\nvar tmpl = $.templates(\"#tmpl\"),\n\n model = {},\n\n helpers = {\n color: \"blue\",\n set: function(newColor, ev, eventArgs) {\n eventArgs.view.ctxPrm(\"color\", newColor); // Set contextual parameter: \"color\" to newColor\n },\n get: function(ev, eventArgs) {\n alert(eventArgs.view.ctxPrm(\"color\")); // Get current contextual parameter \"color\"\n }\n };\n\ntmpl.link(\"#result\", model, helpers);\nTemplate:\nTEXT\n\n({^{:~color}})\n\nCode:\nset: function(newColor, ev, eventArgs) {\n eventArgs.view.ctxPrm(\"color\", newColor); // Set contextual parameter: \"color\" to newColor \n},\nget: function(ev, eventArgs) {\n alert(eventArgs.view.ctxPrm(\"color\")); // Get current contextual parameter \"color\"\n}\n\n\n" + "text": "\n
          \n {^{mytag one two mode=three}}{{else four five mode=six}}{{/mytag}} \n
          \n \n \n\n\n\n\n$.views.tags({\nmytag: {\n template: 'update{{:~tagCtx.index}}',\n bindTo: [0, 1, \"mode\"],\n doUpdate: function(block) {\n this.updateValue(\"A\" + block);\n this.updateValue(\"B\" + block, 1);\n this.updateValue(\"C\" + block, 2);\n this.updateValue(\"D\" + block, 0, 1);\n this.updateValue(\"E\" + block, 1, 1);\n this.updateValue(\"F\" + block, 2, 1);\n }\n}\n});\n\nvar myTmpl = $.templates(\"#myTmpl\"),\n data = {one: \"a\", two: \"b\", three: \"c\", four: \"d\", five: \"e\", six: \"f\"};\n\nmyTmpl.link(\"#page\", data);\n{^{mytag one two mode=three}}{{else four five mode=six}}{{/mytag}} \n...\n\nmytag options\nmytag: {\n template: '',\n bindTo: [0, 1, \"mode\"],\n doUpdate: function(block) {\n this.updateValue(\"A\" + block);\n ...\n this.updateValue(\"F\" + block, 2, 1);\n },\n ...\n\n\n" }, { "_type": "para", "title": "", - "text": "view.ctxPrm() can be used to modify any contextual parameter or helper (~foo). In the above example, ~color is initialized as helper passed in the with the link() call.\nIn the case of a contextual parameter defined by a path expression, such as ~color=clr, using the setter view.ctxPrm(\"color\", \"newValue\") will update not only the contextual parameter but also the data value clr that it is bound to. (The path expression ~color=expr constitutes a two-way binding).\nThis is illustrated by the following sample:\n" + "text": "See the Programmatic two-way data-binding design patterns topic for additional discussion and examples.\nSee also updateValues() below.\n" }, { - "_type": "sample", - "text": "div {margin: 10px 0;}\n\n\n\n\n \n
          \n \n \n
          \n\n {{for person ~color=clr}}\n \n
          \n {^{on ~set \"green\"}}set ~color to green{{/on}}\n {^{on ~set \"red\"}}set ~color to red{{/on}}\n {^{on ~get}}get ~color{{/on}}\n \n \n
          \n {{/for}}\n\n\nvar tmpl = $.templates(\"#tmpl\"),\n\n model = {clr: \"orange\", person: {name: \"Jo\"}},\n\n helpers = {\n set: function(newColor, ev, eventArgs) {\n eventArgs.view.ctxPrm(\"color\", newColor); // Set contextual parameter: \"color\" to newColor\n },\n get: function(ev, eventArgs) {\n alert(eventArgs.view.ctxPrm(\"color\")); // Get current contextual parameter \"color\"\n }\n };\n\ntmpl.link(\"#result\", model, helpers);\nmodel = {clr: \"orange\", person: {name: \"Jo\"}};\n\n...\n\n\n...\n{{for person ~color=clr}}\n ...\n {^{on ~set \"red\"}}set ~color to red{{/on}}...\n ...\n{{/for}}\n\n\n" + "_type": "para", + "title": "The updateValues() method", + "text": "The updateValues() method\nCalling tag.updateValues() will observably update the bound arguments or properties specified in the bindTo option array:\ntag.updateValues(newValue1, newValue2, ...);\n\nThe call will use two-way data-binding to update the underlying data specified in the data-link expressions.\nFor block tags with multiple {{else}} blocks, updateValues() will update the bound data-link expressions on the initial tag (the first block). To drive updates on the additional {{else}} block bindings, use updateValue().\nThe bindTo option defaults to [0], so if there is no bindTo setting, updateValues(value) will observably update the first argument data path, and will be equivalent to updateValue(value).\nSee the Multiple two-way binding design patterns topic for additional discussion and examples.\n" }, { "_type": "links", @@ -587,7 +573,7 @@ content.find.jsvapi = content.useStorage && $.parseJSON(localStorage.getItem("Js { "_type": "para", "title": "JsViews data-linked tags", - "text": "JsViews data-linked tags\nA data-linked tag is like a regular JsRender tag (whether a built-in tag, or a custom tag) but has an additional ^ character to show that is data-linked. Let’s illustrate that by an example based on the Extending the {{for}} tag sample:\n
            \n {{for members}}\n
          • \n {{:name}}\n
          • \n {{/for}}\n
          \n\n
            \n {{range members start=1 end=3}}\n
          • \n {{:name}}\n
          • \n {{/range}}\n
          \n\nWe can data-link to the members – whether on the built-in {{for}}, or the custom {{range}} tag – like this:\n
            \n {^{for members}}\n
          • \n {^{:name}}\n
          • \n {{/for}}\n
          \n\n
            \n {^{range members start=1 end=3}}\n
          • \n {^{:name}}\n
          • \n {{/range}}\n
          \n\nNow if the members array changes, our rendered template content will automatically update to show the additional inserted (or removed) members in the list.\nHere is a live sample of the data-linked {^{for}} tag:\n" + "text": "JsViews data-linked tags\nA data-linked tag is like a regular JsRender tag (whether a built-in tag, or a custom tag) but has an additional ^ character to show that is data-linked. Let’s illustrate that by an example based on the Extending the {{for}} tag sample:\n
            \n {{for lineItems start=1 end=3}}\n
          • \n {{:price}}\n
          • \n {{/for}}\n
          \n\n
            \n {{purchases lineItems start=1 end=3}}\n
          • \n {{:price}}\n
          • \n {{/purchases}}\n
          \n\nWe can data-link to the lineItems – whether on the built-in {{for}}, or the custom {{purchases}} tag – like this:\n
            \n {^{for lineItems}}\n
          • \n {^{:price}}\n
          • \n {{/for}}\n
          \n\n
            \n {^{purchases lineItems start=1 end=3}}\n
          • \n {^{:price}}\n
          • \n {{/purchases}}\n
          \n\nNow if the lineItems array changes, our rendered template content will automatically update to show the additional inserted (or removed) members in the list.\nHere is a live sample of the data-linked {^{for}} tag:\n" }, { "_type": "sample", @@ -596,12 +582,12 @@ content.find.jsvapi = content.useStorage && $.parseJSON(localStorage.getItem("Js { "_type": "para", "title": "", - "text": "Notice that we also added a ^ to the {^{:name}} tag. That means that if the value of the name field is changed (‘observably’) then the value will update automatically within the rendered template.\nAnd here is a link to a complete sample showing a data-linked {^{range}} tag. It lets you modify both the members list and the name properties, and see how they automatically trigger updates in other parts of the page which bind to the same data.\nJsViews is smart about how it updates the HTML. Generally it does so incrementally – only modifying the affected part of the HTML by inserting or removing elements, or replacing values.\n" + "text": "Notice that we also added a ^ to the {^{:name}} tag. That means that if the value of the name field is changed (‘observably’) then the value will update automatically within the rendered template.\nAnd here is a link to a complete sample showing a data-linked {^{for}} tag. It lets you modify both the members list and the name properties, and see how they automatically trigger updates in other parts of the page which bind to the same data.\nJsViews is smart about how it updates the HTML. Generally it does so incrementally – only modifying the affected part of the HTML by inserting or removing elements, or replacing values.\n" }, { "_type": "para", "title": "Binding to named properties of tags", - "text": "Binding to named properties of tags\nIn the sample we went one step further than shown above. We added data-linking to the start and end named properties of the {{range}} tag:\n{^{range members ^start=start-1 ^end=end}}\n\nThe prefixed ^ on the name: ^start=... is used to specify that the start ‘named property’ is to be data-linked (so the whole tag will render if the start value changes). Change the value (using the drop-down in the sample) and you see that the displayed range updates automatically.\nBy default named properties are not data-linked. (This is made ‘opt-in’ for perf optimization reasons.)\n" + "text": "Binding to named properties of tags\nSee also this sample, which again uses a data-linked {^{for}} tag, but which also has data-linking to the tmpl named property of the tag:\n{^{for members ^tmpl=isEditable? ... : ... \" /}}\n\nThe prefixed ^ on the name: ^tmpl=... is used to specify that the tmpl ‘named property’ is to be data-linked (so the whole tag will re-render if the isEditable value changes). Change the value (using the ‘Editable’ checkbox) and you see that the displayed {^{for}} updates automatically.\nBy default, named properties are not data-linked. This is made ‘opt-in’ for perf optimization reasons.\n(Note, however, that custom tags can use the boundProps tag option to make specific named properties be data-linked by default. For example on the {^{for}} tag the filter, sort, reverse, start and end properties are all data-linked by default.)\n" }, { "_type": "para", @@ -620,7 +606,7 @@ content.find.jsvapi = content.useStorage && $.parseJSON(localStorage.getItem("Js { "_type": "sample", "title": "Data-linked elements in templates", - "text": "Data-linked elements in templates\nA data-linked input element (two-way data-binding, update triggered on keydown)\n\n\n\nTwo-way data-binding (no update on keydown, only on blur)\n\n\n\nA data-linked span element (data binding to innerText – default target)\n\n\n\nA data-linked tag (renders as a text node, not an element...)\n\n{^{:name}}\n\nCode:\n\n...\nvar template = $.templates(\"#theTmpl\");\ntemplate.link(\"#result\", data);\n\n\n" + "text": "Data-linked elements in templates\nA data-linked input element (two-way data-binding, update triggered on keydown)\n\n\n\nTwo-way data-binding (no update on keydown, only on blur)\n\n\n\nA data-linked span element (data binding to innerText – default target)\n\n\n\nA data-linked tag (renders as a text node, not an element...)\n\n{^{>name}}\n\nCode:\n\n...\nvar template = $.templates(\"#theTmpl\");\ntemplate.link(\"#result\", data);\n\n\n" }, { "_type": "para", @@ -650,7 +636,7 @@ content.find.jsvapi = content.useStorage && $.parseJSON(localStorage.getItem("Js { "_type": "para", "title": "Full syntax – multiple targets, multiple tags, multiple bindings...", - "text": "Full syntax – multiple targets, multiple tags, multiple bindings...\nThe full syntax allows you to bind multiple expressions each to a different target, and is written like this: data-link=\"target1{linkExpression1} target2{linkExpression2} ...\".\nPossible targets include the following:\n\nan HTML attribute (such as title{...}, class{...}, id{...}, disabled{...} or data-foo{...}\n)\nan HTML element property (such as prop-muted{...} for a
        • \n {^{:name}} ...\n
        • \n{{/for}}\n...\n\nCode:\n...\n$.templates("#teamTemplate").link("#team", team) ...\n\n\n'},{_type:"para",title:"",text:"Notice that we also added a ^ to the {^{:name}} tag. That means that if the value of the name field is changed (‘observably’) then the value will update automatically within the rendered template.\nAnd here is a link to a complete sample showing a data-linked {^{range}} tag. It lets you modify both the members list and the name properties, and see how they automatically trigger updates in other parts of the page which bind to the same data.\nJsViews is smart about how it updates the HTML. Generally it does so incrementally – only modifying the affected part of the HTML by inserting or removing elements, or replacing values.\n"},{_type:"para",title:"Binding to named properties of tags",text:"Binding to named properties of tags\nIn the sample we went one step further than shown above. We added data-linking to the start and end named properties of the {{range}} tag:\n{^{range members ^start=start-1 ^end=end}}\n\nThe prefixed ^ on the name: ^start=... is used to specify that the start ‘named property’ is to be data-linked (so the whole tag will render if the start value changes). Change the value (using the drop-down in the sample) and you see that the displayed range updates automatically.\nBy default named properties are not data-linked. (This is made ‘opt-in’ for perf optimization reasons.)\n"},{_type:"para",title:"See also:",text:"See also:\n\nJsViews API topic: Data-linked elements\nTutorial sequence of samples: Data-linking tags and elements\n\n"}]},"linked-elem-syntax":{sections:[{_type:"para",title:"JsViews data-link expressions, and syntax",text:"JsViews data-link expressions, and syntax\nData-linked elements are regular HTML elements which have been data-bound in the template by adding a data-link attribute.\nThey can be used within templated content, as in the following sample – and they can also be used on top-level non-templated content in your page – see Top-level data-linking.\n"},{_type:"sample",title:"Data-linked elements in templates",text:'Data-linked elements in templates\nA data-linked input element (two-way data-binding, update triggered on keydown)\n\n\n\nTwo-way data-binding (no update on keydown, only on blur)\n\n\n\nA data-linked span element (data binding to innerText – default target)\n\n\n\nA data-linked tag (renders as a text node, not an element...)\n\n{^{:name}}\n\nCode:\n\n...\nvar template = $.templates("#theTmpl");\ntemplate.link("#result", data);\n\n\n'},{_type:"para",title:"",text:'Notice that the tag automatically has two-way data-binding.\n'},{_type:"para",title:"Abbreviated syntax and full syntax for data-linked elements",text:"Abbreviated syntax and full syntax for data-linked elements\nIn fact the examples of data-linked elements above correspond to simple cases, where you can use abbreviated syntax. For more powerful or complex data-linking to elements, you can use the full syntax.\n"},{_type:"para",title:"Abbreviated syntax is just a data-path or expression that you are binding to:",text:'Abbreviated syntax is just a data-path or expression that you are binding to:\n\n\nFor example:\n\n\n\n\n'},{_type:"para",title:"The corresponding full syntax is a data-linked {{: ...}} tag",text:'The corresponding full syntax is a data-linked {{: ...}} tag\nIn fact it is short for this full syntax:\n\n\n– which is a data-linked version of the familiar JsRender tag: {{:pathOrExpression}}.\nExamples:\n\n\n\n\n\n\n\n'},{_type:"para",title:"Optional two-way data-binding",text:'Optional two-way data-binding\nNotice the full syntax for the has an additional : before the } at the end. It corresponds to the two-way data binding. (The same applies to other ‘user input elements’ such as select, textarea etc. (and also contenteditable elements).\nYou can provide both convert and convertBack converters if you want. (See the Two-way binding and converters sample):\n\n\n\n\nIf you want only one-way binding (from the data to the ) you simply eliminate the : at the end:\n\n\nSee the Two-way binding topic for additional details.\n'},{_type:"para",title:"Full syntax – multiple targets, multiple tags, multiple bindings...",text:'Full syntax – multiple targets, multiple tags, multiple bindings...\nThe full syntax allows you to bind multiple expressions each to a different target, and is written like this: data-link="target1{linkExpression1} target2{linkExpression2} ...".\nPossible targets include the following:\n\nan HTML attribute (such as title{...}, class{...}, id{...}, disabled{...} or data-foo{...}\n)\nan HTML element property (such as prop-muted{...} for a
        \"\n\t},\n\n\tsearchTreeNodeTag = {\n\t\tbaseTag: \"treeNode\",\n\t\ttemplate: \"{^{if !hidden && filtered}}
      1. \" +\n\t\t\t\"{^{if categories && categories.length }}\" +\n\t\t\t\t\"\" +\n\t\t\t\"{{else}}\" +\n\t\t\t\t\"\" +\n\t\t\t\"{{/if}}\" +\n\t\t\t\"{{>label}} {{if filtered.length}}{^{for filtered ~name=name}}
        {{:text}}
        {{/for}}
        {{/if}}\" +\n\t\t\"
      2. {{if filtered.length}}
      3. {{/if}}\" +\n\t\t\t\"
      4. \" +\n\t\t\t\t\"
          \" +\n\t\t\t\t\t\"{^{for categories }}\" +\n\t\t\t\t\t\t\"{^{searchTreeNode/}}\" +\n\t\t\t\t\t\"{{/for}}\" +\n\t\t\t\t\"
        \" +\n\t\t\t\"
      5. \" +\n\t\t\"{{/if}}\"\n\t},\n\n// {{section}}\n\n\tsectionTag = {\n\t\tinit: function(tagCtx) {\n\t\t\tthis.data = tagCtx.view.data;\n\t\t},\n\t\trender: function(type, mode) {\n\t\t\tvar editable = mode === \"edit\",\n\t\t\t\tbuttons = \"\";\n\t\t\tif (editable) {\n\t\t\t\tmode = this.selected ? mode : \"editview\";\n\t\t\t\tif (!this.parent.parents.section || this.parent.parents.section.selected) {\n\t\t\t\t\tbuttons = '
        ';\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.tagCtx.tmpl = this.templates[mode][type];\n\t\t\t\tbuttons += this.tagCtx.render();\n\t\t\treturn editable\n\t\t\t\t? ('
        ' + buttons + \"
        \") : buttons;\n\t\t},\n\t\tonAfterLink: function() {\n\t\t\tvar self = this;\n\t\t\tif (self.ctxPrm(\"mode\") === \"edit\") {\n\t\t\t\tself.contents(\".cmdbtn\").on(\"click\", function(ev) {\n\t\t\t\t\tev.stopPropagation();\n\t\t\t\t\tev.stopImmediatePropagation();\n\t\t\t\t\treturn false;\n\t\t\t\t});\n\t\t\t\tself.contents().on(\"click\", function(ev) {\n\t\t\t\t\tif ($(ev.target).is(\"cmdbtn,a,input,textarea,button,img,.removesection\")) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tself.toggleSelect(); \n\t\t\t\t});\n\t\t\t\tself.contents(true, \".toggleselect:first\").on(\"click\", function() {\n\t\t\t\t\tself.toggleSelect();\n\t\t\t\t});\n\t\t\t\tself.contents(true, \".up\").on(\"click\", function(ev) {\n\t\t\t\t\tself.moveUp();\n\t\t\t\t\tev.stopImmediatePropagation();\n\t\t\t\t});\n\t\t\t\tself.contents(true, \".down\").on(\"click\", function(ev) {\n\t\t\t\t\tself.moveDown();\n\t\t\t\t\tev.stopImmediatePropagation();\n\t\t\t\t});\n\t\t\t}\n\t\t},\n\t\tonDispose: function() {\n\t\t\tif (this.parent.selectedChild === this) {\n\t\t\t\tthis.parent.selectedChild = null;\n\t\t\t}\n\t\t},\n\n\t// methods\n\t\tmoveUp: function() {\n\t\t\tvar index = this.tagCtx.view.index,\n\t\t\t\tsections = this.parent.tagCtx.args[0];\n\t\t\tif (index) {\n\t\t\t\t$.observable(sections).move(index, index-1);\n\t\t\t}\n\t\t\tsave();\n\t\t},\n\t\tmoveDown: function() {\n\t\t\tvar index = this.tagCtx.view.index,\n\t\t\t\tsections = this.parent.tagCtx.args[0];\n\t\t\tif (index + 1 < sections.length) {\n\t\t\t\t$.observable(sections).move(index, index+1);\n\t\t\t}\n\t\t\tsave();\n\t\t},\n\t\ttoggleSelect: function() {\n\t\t\t$.observable(this).setProperty(\"selected\", !this.selected);\n\t\t\tthis.refresh();\n\t\t\tif (this.parent.onToggleSelect) {\n\t\t\t\tthis.parent.onToggleSelect(this);\n\t\t\t}\n\t\t\tsave();\n\t\t}\n\t},\n\n// {{sectionButton}}\n\n\tsectionButtonsTag = {\n\t\trender: function(sectionTypes) {\n\t\t\tvar ret = \"
        \",\n\t\t\t\tnestedSectionType = this.ctxPrm(\"sectionType\");\n\t\t\tfor (var type in sectionTypes) {\n\t\t\t\tret += \"\";\n\t\t\t}\n\t\t\treturn ret + \"
        \";\n\t\t}\n\t},\n\n// {{sectionTitle}}\n\n\tsectionTitleTag = {\n\t\ttemplate: \"#sectionTitleTmpl\"\n\t},\n\n// {{sectionHeader}}\n\n\tsectionHeaderTag = {\n\t\ttemplate: \"#sectionHeaderTmpl\"\n\t},\n\n// {{sampleFrame}}\n\n\tsampleFrameTag = {\n\t\tinit: function() {\n\t\t\tvar self = this,\n\t\t\t\tdata = $.parseJSON(stringify(self.parent.parents.section.data)),\n\t\t\t\tcodetabs = data.codetabs;\n\n\t\t\tself.parent.sampleFrame = self;\n\t\t\tself.getScript = function(loadScript) {\n\t\t\t\tself.loadScript = loadScript;\n\t\t\t\tif (data.url) {\n\t\t\t\t\tvar html = $.trim(self.iframeWnd.document.body.innerHTML).replace(/ /g, \"\\n\"), // IE puts entities in place of some newlines\n\t\t\t\t\t\ttoremove = html.indexOf(\"\\n\\n\\n\"\n\t\t\t\t\t+ \"\\n\"\n\t\t\t\t\t+ \"\\n\"\n\t\t\t\t\t+ \" \\n\"\n\t\t\t\t\t+ (url\n\t\t\t\t\t\t? (\" \\n\"\n\t\t\t\t\t\t\t+ (codeInHeader\n\t\t\t\t\t\t\t\t? (\"\\n\")\n\t\t\t\t\t\t\t\t: \"\"))\n\t\t\t\t\t\t: (\n\t\t\t\t\t(jsrJsvJqui === \"jqui\"\n\t\t\t\t\t\t? \" \\n\"\n\t\t\t\t\t\t\t+ \" \\n\"\n\t\t\t\t\t\t: \"\"\n\t\t\t\t\t)\n\t\t\t\t\t+ \" \\n\"\n\t\t\t\t\t+ \" \\n\"\n\t\t\t\t\t+ (jsrJsvJqui === \"jqui\"\n\t\t\t\t\t\t? \" \\n\"\n\t\t\t\t\t\t: \"\")\n\t\t\t\t\t))\n\t\t\t\t\t+ (header || \"\") + \"\\n\"\n\t\t\t\t\t+ \"\\n\\n\"\n\t\t\t\t\t+ (html\n\t\t\t\t\t\t? (html + (code && !codeInHeader ? \"\\n\\n\" : \"\"))\n\t\t\t\t\t\t: (tryItData.markup\n\t\t\t\t\t\t\t? (\"
        \\n\\n\"\n\t\t\t\t\t\t\t\t+ \"\\n\\n\"\n\t\t\t\t\t\t\t\t+ \"\"\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t: \"\"\n\t\t\t\t\t\t)\n\t\t\t\t\t)\n\t\t\t\t\t+ \"\\n\\n\\n\"\n\t\t\t\t\t+ \"\\n\";\n\t\t\t}\n\n\t\t\tfunction renderField(type, label) {\n\t\t\t\tvar value = tryItData[type],\n\t\t\t\t\tisData = type === \"data\",\n\t\t\t\t\thighlightSyntax = {\n\t\t\t\t\t\tcode: \"js\",\n\t\t\t\t\t\tmarkup: \"jsr\",\n\t\t\t\t\t\thtml: \"jsr\"\n\t\t\t\t\t};\n\n\t\t\t\tif (value) {\n\t\t\t\t\tvalue = \"\";\n\t\t\t\t\t} else {\n\t\t\t\t\t\tvalue += \":
        \"\n\t\t\t\t\t\t\t+ (isData\n\t\t\t\t\t\t\t\t? hljs.highlight(\"json\", stringify(tryItData[type])).value\n\t\t\t\t\t\t\t\t: hljs.highlight(highlightSyntax[type] || \"javascript\", tryItData[type]).value\n\t\t\t\t\t\t\t) + \"
        \";\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn value || \"\";\n\t\t\t}\n\n\t\t\tvar ret = \"\",\n\t\t\t\ttryItData = this.data,\n\t\t\t\torigData = this.tagCtx.view.data.origData,\n\t\t\t\tjsrJsvJqui = origData.jsrJsvJqui,\n\t\t\t\tnocss = origData.nocss,\n\t\t\t\theaderAction = origData.action,\n\t\t\t\teditable = mode===\"edit\",\n\t\t\t\turl = this.parent.parents.section.data.url;\n\n\t\t\tif (mode === \"full\") {\n\t\t\t\tret += \"\";\n\t\t\t} else if (mode === \"code\") {\n\t\t\t\tret += renderField(arg1, arg2);\n\t\t\t} else if (tryItData.html) {\n\t\t\t\tret += renderField(\"html\") + renderField(\"code\", \"javascript\"); // Could do + renderField(\"header\");\n\t\t\t} else if (tryItData.markup) {\n\t\t\t\tret += renderField(\"markup\", \"template markup\") + renderField(\"data\");\n\t\t\t}\n\t\t\treturn ret;\n\t\t}\n\t},\n\n//#endregion\n\n//#region TEMPLATES\n\n\ttemplates = {\n\t\tpage: $.templates({\n\t\t\tmarkup: \"#pageTmpl\",\n\t\t\ttags: {\n\t\t\t\tsectionButtons: sectionButtonsTag,\n\t\t\t\tsectionHeader: sectionHeaderTag,\n\t\t\t\tsectionTitle: sectionTitleTag\n\t\t\t},\n\t\t\tconverters: {\n\t\t\t\tnewsearch: function(val) {\n\t\t\t\t\treturn val.toLowerCase();\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t},\n\tsectionTemplates = {\n\t\tsummary: {\n\t\t\tpara: $.templates(\"#paraTmpl\"),\n\t\t\tapi: $.templates({\n\t\t\t\tmarkup: \"#apiTmpl\",\n\t\t\t\thelpers: {\n\t\t\t\t\tsignature: signature\n\t\t\t\t}\n\t\t\t}),\n\t\t\ttag: $.templates({\n\t\t\t\tmarkup: \"#tagTmpl\",\n\t\t\t\thelpers: {\n\t\t\t\t\tsignature: signature\n\t\t\t\t}\n\t\t\t}),\n\t\t\tdata: $.templates(\"#dataTmpl\"),\n\t\t\ttemplate: $.templates({\n\t\t\t\tmarkup: \"#templateTmpl\"\n\t\t\t}),\n\t\t\tcode: $.templates(\"#codeTmpl\"),\n\t\t\tsample: $.templates({\n\t\t\t\tmarkup: \"#sampleTmpl\",\n\t\t\t\ttags: {\n\t\t\t\t\tsampleFrame: sampleFrameTag,\n\t\t\t\t\tsampleFields: sampleFieldsTag\n\t\t\t\t},\n\t\t\t\thelpers: {\n\t\t\t\t\ttabsTmpl: function() {\n\t\t\t\t\t\tvar tab,\n\t\t\t\t\t\t\tcodetabs = this.data.codetabs || [],\n\t\t\t\t\t\t\tcodetabsLength = codetabs.length,\n\t\t\t\t\t\t\ttemplate = '{^{tabs tabCaption=\"How it works\"}}' +\n\t\t\t\t\t\t\t'{{for ~sections}}' +\n\t\t\t\t\t\t\t\t'{{if ~mode!==\"summary\" || !detail}}{^{section _type ~mode /}}{{/if}}' +\n\t\t\t\t\t\t\t'{{/for}}' +\n\t\t\t\t\t\t'{{else tabCaption=\"Code\"}}' +\n\t\t\t\t\t\t\t'
        ' +\n\t\t\t\t\t\t\t\t'{{sampleFields/}}' +\n\t\t\t\t\t\t\t'
        ';\n\n\t\t\t\t\t\tfor (tab = 0; tab < codetabsLength; tab++) { // Add an {{else}} block for each codetab\n\t\t\t\t\t\t\ttemplate += '{{else tabCaption=\"' + codetabs[tab].label + '\"}}' +\n\t\t\t\t\t\t\t\t'
        ' +\n\t\t\t\t\t\t\t\t\t'{{sampleFields \"code\" \"c' + tab + '\" \"' + codetabs[tab].url + '\"/}}' +\n\t\t\t\t\t\t\t\t'
        ';\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\ttemplate +=\n\t\t\t\t\t\t'{{else tabCaption=\"Full Code\"}}' +\n\t\t\t\t\t\t\t'{{sampleFields \"full\"/}}' +\n\t\t\t\t\t\t'{{else tabCaption=\"Try it\"}}' +\n\t\t\t\t\t\t\t'
        ' +\n\t\t\t\t\t\t\t\t'Try it: make changes below, then run... ' +\n\t\t\t\t\t\t\t\t'' +\n\t\t\t\t\t\t\t'
        ' +\n\t\t\t\t\t\t\t'
        ' +\n\t\t\t\t\t\t\t\t'{{sampleFields \"edit\"/}}' +\n\t\t\t\t\t\t\t'
        ' +\n\t\t\t\t\t\t'{{/tabs}}';\n\t\t\t\t\t\treturn template;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}),\n\t\t\tlinks: $.templates({\n\t\t\t\tmarkup: \"#linksTmpl\",\n\t\t\t\thelpers: {\n\t\t\t\t\turl: function(hash) {\n\t\t\t\t\t\treturn location.pathname +\"#\" + hash;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\t\tedit: {\n\t\t\tpara: $.templates(\"#editParaTmpl\"),\n\t\t\tapi: $.templates(\"#editApiTmpl\"),\n\t\t\ttag: $.templates(\"#editTagTmpl\"),\n\t\t\tdata: $.templates(\"#editDataTmpl\"),\n\t\t\ttemplate: $.templates(\"#editTemplateTmpl\"),\n\t\t\tcode: $.templates(\"#editCodeTmpl\"),\n\t\t\tsample: $.templates(\"#editSampleTmpl\"),\n\t\t\tlinks: $.templates(\"#editLinksTmpl\")\n\t\t}\n\t};\n\nvar testDiv = $(\"#testHtml\")[0];\n\ndocument.onkeydown = function(ev) {\n\tev = ev || window.event;\n\tvar keyCode = ev.keyCode;\n\tif (!ev.altKey && !ev.shiftKey) {\n\t\tif (keyCode === 27) { // Escape\n\t\t\tcontent.searchTree.close();\n\t\t} else if (ev.ctrlKey) {\n\t\t\tif (content.search) {\n\t\t\t\tif (keyCode === 39) { // Right arrow\n\t\t\t\t\tcontent.searchTree.next();\n\t\t\t\t} else if (keyCode === 37) { //Left Arrow\n\t\t\t\t\tcontent.searchTree.prev();\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (keyCode === 191) { // Ctrl+/\n\t\t\t\tsearchbox.focus();\n\t\t\t\twindow.scrollTo(0, 0);\n\t\t\t}\n\t\t}\n\t}\n};\n\nfunction treeGroup () {}\n\ntreeGroup.prototype = {\n\tselect: function(section) {\n\t\tif (section && this.selected !== section) {\n\t\t\tlocation.hash = \"search?s=\" + lowerCaseEncodeURI(content.search) + searchIncludeHash() + \"&l=\" + section;\n\t\t\tsearchtextElem.addClass(\"selected\");\n\t\t}\n\t},\n\tunselect: function() {\n\t\tlocation.hash = \"search?s=\" + lowerCaseEncodeURI(content.search) + searchIncludeHash();\n\t\tsearchtextElem.removeClass(\"selected\");\n\t},\n\tchanging: function(ev, eventArgs) {\n\t\tvar newSearch,\n\t\t\tnoSearch = \" \",\n\t\t\tsearchTerm = eventArgs.value;\n\t\tif (searchTerm) {\n\t\t\tif (searchTerm !== content.search) {\n\t\t\t\tnoSearch = \"Enter: Search...
        Escape: Quit
        Ctrl+Left/Right Arrow: Prev/Next result\";\n\t\t\t} else {\n\t\t\t\tnoSearch = \"\";\n\t\t\t}\n\t\t}\n\t\t$.observable(content).setProperty(\"noSearch\", noSearch);\n\t\treturn false;\n\t},\n\tonEnter: function(ev, eventArgs) {\n\t\tvar newSearch,\n\t\t\tnoSearch = \" \",\n\t\t\tsearchTerm = ev.target.value;\n\t\tif (ev.keyCode === 13 && searchTerm && !content.editable) {\n\t\t\tnewSearch = content.search !== searchTerm;\n\t\t\tsearchTerm = \"#search?s=\" + lowerCaseEncodeURI(searchTerm) + searchIncludeHash();\n\t\t\tif (location.hash !== searchTerm) {\n\t\t\t\tlocation.hash = searchTerm;\n\t\t\t\t$.observable(content).setProperty(\"noSearch\", newSearch ? \"Loading...\" : \"\");\n\t\t\t}\n\t\t\treturn; // Set content.search to value in text box.\n\t\t}\n\t},\n\tprev: function() {\n\t\t$(\"#prev\").focus();\n\t\tvar newIndex = this.sectionIndex === undefined ? content.filterlen - 1 : this.sectionIndex - 1;\n\t\tif (newIndex >= 0) {\n\t\t\tthis.select(content.filter[newIndex].section);\n\t\t} else if (newIndex === -1) {\n\t\t\tthis.unselect();\n\t\t}\n\t},\n\tnext: function() {\n\t\t$(\"#next\").focus();\n\t\tvar newIndex = this.sectionIndex === undefined ? 0 : this.sectionIndex + 1;\n\t\tif (newIndex < content.filterlen) {\n\t\t\tthis.select(content.filter[newIndex].section);\n\t\t} else if (newIndex === content.filterlen) {\n\t\t\tthis.unselect();\n\t\t}\n\t},\n\tclose: function() {\n\t\tvar catName = searchRegex.exec(location.hash);\n\t\tlocation.hash = catName && catName[7] || topCategoryName;\n\t},\n\tmouseenter: function(topicName, ev, eventArgs) { // Hover over a search target section\n\t\tvar section = eventArgs.linkCtx.data;\n\n\t\t$.observable(content).setProperty({\n\t\t\thoverText: section.text, // Set section text on hover div, which will also make it visible\n\t\t\thoverTopicSel: this.topicName === topicName // True for search section within the currently selected topic - sets class for colors.\n\t\t});\n\t\thoverTextElem\n\t\t\t.show()\n\t\t\t.offset({top: eventArgs.linkCtx.elem.offsetTop + searchNavElem.offset().top - searchNavElem.scrollTop()});\n\t},\n\tmouseleave: function(ev, eventArgs) {\n\t\thoverTextElem.hide();\n\t},\n\tmousewheel: function(ev, eventArgs) {\n\t\thoverTextElem.hide();\n\t\tev.preventDefault();\n\t\tsearchNavElem.scrollTop(searchNavElem.scrollTop() + ev.originalEvent.deltaY);\n\t},\n\tclick: function(ev, eventArgs) { // Click on overlay: \"#hoverText\"\n\t\tvar leftTarget = searchNavElem.position().left + 10;\n\t\thoverTextElem.hide();\n\t\tisSearchTreeSelectionChange = true;\n\t\tthis.select($.view(document.elementFromPoint(leftTarget, ev.clientY)).data.section);\n\t}\n};\n\nfunction lowerCaseEncodeURI(val) {\n\treturn encodeURIComponent(val).toLowerCase();\n}\n\nfunction searchIncludeHash() {\n\tvar hash = \"\";\n\tif (!(searchInclude.jsr && searchInclude.jsv && searchInclude.smp && searchInclude.txt)) {\n\t\thash += \"&f=\"\n\t\t+ (searchInclude.jsr ? \"\" : \"jsr-\")\n\t\t+ (searchInclude.jsv ? \"\" : \"jsv-\")\n\t\t+ (searchInclude.smp ? \"\" : \"smp-\")\n\t\t+ (searchInclude.txt ? \"\" : \"txt-\");\n\t}\n\treturn hash;\n}\n\nfunction searchContent(searchValue) {\n\tvar i, category, foundInCat, foundInCats,\n\t\tcategories = content.categories,\n\t\tl = categories.length;\n\tcontent.filter = [];\n\t$.observable(content).setProperty(\"searchtext\", \"\");\n\tsearchtextElem && searchtextElem.removeClass(\"selected\");\n\tfor (i=1; categories && i\" + searchValue + \"...\",\n\t\tfilterlen: content.filter.length\n\t});\n}\n\nfunction searchCat(category, search, topCatContent, topCatFindContent) {\n\tvar i, l, found, foundInTopic, foundInSubCat, categories, subCategory;\n\tif (search && (categories = category.categories)) {\n\t\tl = categories.length;\n\t\tfor (i=0; categories && i\"\n\t\t\t\t\t\t\t\t\t+ htmlConverter(displayText.substr(index, text.length))\n\t\t\t\t\t\t\t\t+ \"\"\n\t\t\t\t\t\t\t\t+ htmlConverter(postText)\n\t\t\t\t\t\t};\n\t\t\t\t\t\n\t\t\t\t\t\tfiltered.push(searchItem);\n\t\t\t\t\t\tcontent.filter.push(searchItem);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t$.observable(section).setProperty(\"searchAnchor\", searchAnchor);\n\t\t\t}\n\t\t}\n\t\t$.observable(topic).setProperty(\"filtered\", filtered); \n\t\treturn topic.filtered;\n\t}\n}\n\nfunction clearSearch(searchValue) {\n\tvar category,\n\t\tcategories = content.categories,\n\t\tl = categories.length;\n\tcontent.filter = [];\n\twhile (categories && l-- > 1) {\n\t\tcategory = categories[l];\n\t\tcategory.filtered = undefined;\n\t\t!category.hidden && clearSearchCat(category, content[category.name]);\n\t}\n}\n\nfunction clearSearchCat(category, topCatContent) {\n\tvar l, categories, subCategory;\n\tif (categories = category.categories) {\n\t\tl = categories.length;\n\t\twhile (categories && l--) {\n\t\t\tsubCategory = categories[l];\n\t\t\tif (!subCategory.hidden) {\n\t\t\t\tsubCategory.filtered = undefined;\n\t\t\t\tclearSearchTopic(subCategory.name, topCatContent);\n\t\t\t\tclearSearchCat(subCategory, topCatContent);\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction clearSearchTopic(topicName, topCatContent) {\n\tvar l, section,\n\t\ttopic = topCatContent[topicName];\n\tif (topic) {\n\t\tl = topic.sections.length;\n\t\ttopic.filtered = undefined;\n\t\twhile (l--) {\n\t\t\tsection = topic.sections[l];\n\t\t\t$.observable(section).setProperty(\"searchAnchor\", undefined);\n\t\t}\n\t}\n}\n\nsectionTemplates.editview = sectionTemplates.detail = sectionTemplates.summary; // for now\nsectionTag.templates = sectionTemplates;\n\n//#endregion\n\n//#region HELPER FUNCTIONS\n\nfunction getCategory(catName, searchTerm, searchExclude) {\n\tfunction getCategoryNode(name, subcategories, parent) {\n\t\tif (selectedCategory = categories[0][name]) {\n\t\t\treturn topCategory = homeCategory = selectedCategory;\n\t\t}\n\t\ttopCategory = undefined;\n\n\t\tstack.push(parent);\n\t\tvar category,\n\t\t\tl = subcategories.length;\n\t\twhile (l--) {\n\t\t\tcategory = subcategories[l];\n\t\t\tif (category.name === name || category.categories && (category = getCategoryNode(name, category.categories, category))) {\n\t\t\t\twhile (parent = stack.pop()) {\n\t\t\t\t\ttopCategory = parent;\n\t\t\t\t\tif (!parent.expanded) {\n\t\t\t\t\t\t$.observable(parent).setProperty(\"expanded\", true);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn category;\n\t\t\t}\n\t\t}\n\t\tstack.pop();\n\t}\n\tvar topCat, // specific to this getScript call\n\t\tstack = [],\n\t\tcategories = content.categories,\n\t\toldTopCategory = topCategoryName,\n\t\tloadedPromise = $.Deferred();\n\n\tselectedCategory = catName && getCategoryNode(catName, categories) || categories[0].jsrender;\n\ttopCategory = topCategory || selectedCategory;\n\tif (searchTerm && !catName) {\n\t\ttopCategory = searchCategory;\n\t\tif (page) {\n\t\t\tpage.category = undefined;\n\t\t}\n\t}\n\n\ttopCategoryName = topCategory.name;\n\n\tif (topCategoryName !== oldTopCategory) {\n\t\tif (oldTopCategory) {\n\t\t\t$(\"#id-\" + oldTopCategory)\n\t\t\t\t.removeClass(\"selected\")\n\t\t\t\t.addClass(\"unselected\");\n\t\t}\n\t\t$(\"#id-\" + topCategoryName)\n\t\t\t.removeClass(\"unselected\")\n\t\t\t.addClass(\"selected\");\n\t}\n\n\tif (content.topCategory !== topCategory) {\n\t\tcontent.catName = catName; // Don't change this observably, to avoid triggering an additional UI update before topCategory has been set.\n\t\t$.observable(content).setProperty(\"topCategory\", topCategory);\n\t}\n\n\tif (searchTerm) {\n\t\tif (content.searched !== searchTerm || content.searchExclude !== searchExclude) {\n\t\t\tloadAllContent()\n\t\t\t\t.then(function() {\n\t\t\t\t\tloadAllContent(\"find\")\n\t\t\t\t\t\t.then(function() {\n\t\t\t\t\t\t\tsetTimeout(function() {\n\t\t\t\t\t\t\t\tsearchContent(searchTerm);\n\t\t\t\t\t\t\t\t$.observable(content).setProperty(\"searched\", searchTerm);\n\t\t\t\t\t\t\t\tcontent.searchExclude = searchExclude;\n\t\t\t\t\t\t\t\tloadedPromise.resolve();\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\t\t} else {\n\t\t\tloadedPromise.resolve();\n\t\t}\n\t} else {\n\t\tif (!topCategory.loaded && !topCategory.loading) {\n\t\t\ttopCategory.loading = \" \"; // true, but render blank until after timeout\n\t\t\ttopCat = topCategory; // Specific to this getCategory() call. (Global topCategory var may change before then() returns)\n\n\t\t\t$.getScript(\"documentation/contents-\" + topCategory.name + \".min.js\")\n\t\t\t\t.then(function() {\n\t\t\t\t\t$.observable(topCat).setProperty(\"loaded\", true);\n\t\t\t\t\tloadedPromise.resolve();\n\t\t\t\t});\n\t\t} else if (topCategory.loaded) {\n\t\t\tif (content.searched) {\n\t\t\t\tsetTimeout(function() {\n\t\t\t\t\tclearSearch(); // lazy clear search annotations from content\n\t\t\t\t\t$.observable(content).setProperty({\n\t\t\t\t\t\tsearch: undefined,\n\t\t\t\t\t\tfilterlen: 0\n\t\t\t\t\t});\n\t\t\t\t\tcontent.searched = undefined;\n\t\t\t\t});\n\t\t\t}\n\t\t\tloadedPromise.resolve();\n\t\t}\n\t}\n\treturn loadedPromise.promise();\n}\n\nfunction fetchCategory() {\n\tsave();\n\n\tvar categoryName, searchTerm, sectionIndex, searchExclude,\n\t\toldTopCategory = topCategory,\n\t\toldCategory = selectedCategory,\n\t\tlochash = location.hash;\n\n\tif (history && history.replaceState) { // In IE9 history.replaceState not supported\n\t\tif (history.state && history.state.url === document.URL) {\n\t\t\thistoryIndex = history.state.index;\n\t\t\tif (historyStates[historyIndex]) {\n\t\t\t\thistory.replaceState(historyStates[historyIndex], \"\")\n\t\t\t} else {\n\t\t\t\thistoryStates[historyIndex] = history.state;\n\t\t\t}\n\t\t} else {\n\t\t\thistoryIndex++;\n\t\t\tvar newState = historyStates[historyIndex] = {\n\t\t\t\turl: document.URL,\n\t\t\t\tindex: historyIndex\n\t\t\t};\n\n\t\t\thistory.replaceState(newState, \"\")\n\n\t\t\tif (historyStates[historyIndex - 1]) {\n\t\t\t\thistoryStates[historyIndex - 1].scroll = $(window).scrollTop();\n\t\t\t}\n\t\t}\n\t}\n\n\tif (!lochash || lochash === \"#home\") {\n\t\tlochash = \"#\" + (homeCategory && homeCategory.key || \"jsrender\");\n\t}\n\tcategoryName = searchRegex.exec(lochash);\n\n\tif (categoryName[1]) {\n\t\t$.observable(searchInclude).setProperty({\n\t\t\tjsr: !categoryName[3],\n\t\t\tjsv: !categoryName[4],\n\t\t\tsmp: !categoryName[5],\n\t\t\ttxt: !categoryName[6]\n\t\t});\n\t\t$.observable(content).setProperty(\"search\", searchTerm = decodeURIComponent(categoryName[1]).toLowerCase());\n\t}\n\n\tsectionIndex = categoryName[8];\n\tsearchExclude = categoryName[2];\n\tcategoryName = categoryName[7];\n\n\treturn getCategory(categoryName, searchTerm, searchExclude)\n\t\t.then(function() {\n\t\t\tif (page) {\n\t\t\t\tvar newTopCat = oldTopCategory !== topCategory;\n\t\t\t\ttopCategoryName = topCategory.name;\n\t\t\t\tif (topCategoryName !== \"home\" && !content[topCategoryName]) {\n\t\t\t\t\tthrow topCategoryName + \" not loaded. Ensure category.loaded not saved as 'true'...\";\n\t\t\t\t}\n\t\t\t\t$.observable(page).setProperty({\n\t\t\t\t\tcategory: selectedCategory\n\t\t\t\t});\n\n\t\t\t\tif (newTopCat) {\n\t\t\t\t\toldTopCategory.loading = \"\"; // false\n\t\t\t\t\tif (oldTopCategory && oldTopCategory.key) {\n\t\t\t\t\t\t$(\"#logo-\" + oldTopCategory.key)\n\t\t\t\t\t\t\t.removeClass(\"selected\")\n\t\t\t\t\t\t\t.addClass(\"unselected\");\n\t\t\t\t\t}\n\t\t\t\t\tif (topCategory.key) {\n\t\t\t\t\t\t$(\"#logo-\" + topCategory.key)\n\t\t\t\t\t\t\t.removeClass(\"unselected\")\n\t\t\t\t\t\t\t.addClass(\"selected\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tpage.navTo(lochash, newTopCat || oldCategory !== selectedCategory);\n\t\t\t} else {\n\t\t\t\tinitPageParams = {catName: categoryName, searchTerm: searchTerm, section: sectionIndex};\n\t\t\t}\n\t\t});\n}\n\nfunction initPage(categoryName, searchTerm, sectionIndex) {\n\n\t$.observable(content).setProperty({\n\t\tsearchTitle: searchTerm && categoryName && content[topCategoryName][categoryName].title,\n\t\tcatName: categoryName\n\t});\n\tif (searchTerm) {\n\t\t$.observable(topSearchTree).setProperty({\n\t\t\tsectionIndex: sectionIndex && parseInt(sectionIndex),\n\t\t\ttopicName: categoryName,\n\t\t\tselected: sectionIndex && (categoryName + \"@\" + sectionIndex)\n\t\t});\n\t}\n}\n\nfunction signature(api) {\n\tvar param, i, l,\n\t\tsig = this.data,\n\t\tret = (api.object ? api.object + \".\" : \"\") + api.name;\n\n\tif (api.method) {\n\t\tret += \"(\";\n\t\tfor (i=0, l=sig.params.length; i 1) {\n\t\t\tcategory = categories[l];\n\t\t\tloaded[l] = category.loaded;\n\t\t\tloading[l] = category.loading;\n\t\t\tloadedfind[l] = category.loadedfind;\n\t\t\tloadingfind[l] = category.loadingfind;\n\t\t\tcategories[l].loaded = categories[l].loadedfind = false;\n\t\t\tcategories[l].loading = categories[l].loadingfind = \"\";\n\t\t}\n\t\tlocalStorage.setItem(\"JsViewsDocCategories\", stringify(categories));\n\t\tl = categories.length;\n\t\twhile (l-- > 1) {\n\t\t\tcategories[l].loaded = loaded[l];\n\t\t\tcategories[l].loading = loading[l];\n\t\t\tcategories[l].loadedfind = loadedfind[l];\n\t\t\tcategories[l].loadingfind = loadingfind[l];\n\t\t}\n\t\ttopics = content[topCategoryName];\n\t\tif (topics) {\n\t\t\tlocalStorage.setItem(\"JsViewsDocTopics/\" + topCategoryName, stringify(topics));\n\t\t}\n\t\ttextareas[0].value = getContent(topics);\n\t\ttextareas[1].value = getContent(categories);\n\t}\n}\n\n//#endregion\n\n//#region PAGE INITIALIZATION\n\n$.views.tags({\n\tpage: pageTag,\n\tsection: sectionTag,\n\tsearchTree: searchTreeTag,\n\tsearchTreeNode: searchTreeNodeTag\n});\n\n$.views.helpers({\n\tstringify: stringify\n});\n\n$.views.converters({\n\tstringify: stringify,\n\tgetContent: getContent,\n\tsyntax: function(val) {\n\t\treturn \"
        \" + hljs.highlight(this.tagCtx.args[1], val).value + \"
        \";\n\t},\n\tparse: parse,\n\tmd: function(val) {\n\t\treturn md.render(val);\n\t}\n});\n\ntopSearchTree = content.searchTree = new treeGroup();\n\tsetTimeout(function() {\n\t\t$(\"#initial-load\").html(\"Loading...\");\n\t}, 2000);\n\nfetchCategory()\n\t.then(function() {\n\t\tvar selectedLogo, categoryName,\n\t\tcategories = content.categories,\n\t\tl = categories.length;\n\n\t\twhile (l--) {\n\t\t\tcategories[l].loadedfind = categories[l].loadingfind = undefined;\n\t\t}\n\n\t\tcontent.topCategory = topCategory || selectedCategory;\n\n\t\t//================ Load Page ================\n\t\ttemplates.page.link(\"#id-content\", content, {templates: templates});\n\n\t\tsearchtextElem = $(\"#searchtext\");\n\t\tsearchbox = $(\"#id-searchbox\");\n\t\tsearchNavElem = $(\".searchnav\");\n\t\tsideNavElem = $(\".sidenav\");\n\t\thoverTextElem = $(\"#hovertext\");\n\n\t\tif ('scrollRestoration' in history) {\n\t\t\thistory.scrollRestoration = 'manual';\n\t\t}\n\t\t$(window)\n\t\t\t.on('hashchange', fetchCategory)\n\t\t\t.on('unload', save);\n\n\t\t$.observe(searchInclude, \"jsr\", \"jsv\", \"smp\", \"txt\", function(ev, eventArgs){\n\t\t\tlocation.hash = \"search?s=\" + searchRegex.exec(location.hash)[1] + searchIncludeHash();\n\t\t});\n\n\t\t$(\"#logo-\" + topCategory.key)\n\t\t\t.removeClass(\"unselected\")\n\t\t\t.addClass(\"selected\");\n\n\t\tselectedLogo = $(\".main-item.selected\");\n\n\t\tpage.navTo(location.hash);\n\n\t\t$(\".main-item\").on(\"click\", function() {\n\t\t\tselectedLogo.removeClass(\"selected\").addClass(\"unselected\");\n\t\t\tselectedLogo = $(this).addClass(\"selected\").removeClass(\"unselected\");\n\t\t\tlocation.hash = this.id.slice(5);\n\t\t});\n\t\tif (!loadedAll) {\n\t\t\tsetTimeout(function() {\n\t\t\t\tloadAllContent(); // lazy load other content in the background\n\t\t\t});\n\t\t}\n\t});\n\nfunction loadAllContent(prefix) {\n\tvar categories = content.categories,\n\t\tl = categories.length,\n\t\tnotLoaded = 0,\n\t\tallLoadedPromise = $.Deferred(),\n\t\tloadingLabel = \"loading\" + (prefix||\"\"),\n\t\tloadedLabel = \"loaded\" + (prefix||\"\");\n\tprefix = prefix || \"contents\";\n\tloadedAll = true;\n\twhile (l-- > 1) {\n\t\t(function() { // Equivalent to $.proxy. Ensure category is specific to this call\n\t\t\tvar category = categories[l];\n\t\t\tif (!category.hidden && !(prefix===\"contents\" ? content : content[prefix])[category.name]) {\n\t\t\t\tnotLoaded++;\n\t\t\t\tcategory[loadingLabel] = \" \";\n\t\t\t\t$.getScript(\"documentation/\" + prefix + \"-\" + category.name + \".min.js\")\n\t\t\t\t\t.then(function() {\n\t\t\t\t\t\t$.observable(category).setProperty(loadedLabel, true);\n\t\t\t\t\t\tcategory.loading = \"\";\n\t\t\t\t\t\tnotLoaded--;\n\t\t\t\t\t\tif (!notLoaded) {\n\t\t\t\t\t\t\tallLoadedPromise.resolve();\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t}\n\t\t})();\n\t}\n\tif (!notLoaded) {\n\t\tallLoadedPromise.resolve();\n\t}\n\treturn allLoadedPromise.promise();\n}\n//#endregion\n})(this, this.jQuery);\n"]} \ No newline at end of file diff --git a/package.json b/package.json index f0096476..8aeb5e17 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jsviews.com", - "version": "v0.9.90", + "version": "v0.9.91", "description": "Documentation for JsRender and JsViews", "repository": { "type": "git", @@ -15,7 +15,10 @@ "url": "https://github.com/borismoore/jsviews.com/issues" }, "homepage": "http://www.jsviews.com", - "dependencies": {}, + "dependencies": { + "jsviews": "^0.9.91", + "jquery": "^3.0.0" + }, "devDependencies": { "browserify": "^11.0.1", "glob-stream": "^5.0.0", @@ -29,11 +32,9 @@ "gulp-rename": "^1.2.2", "gulp-sourcemaps": "^1.5.2", "gulp-uglify": "^1.2.0", - "jquery": "^2.1.4", "jshint-stylish": "^2.0.1", "jshint-summary": "^0.4.0", - "jsrender": "^0.9.90", - "jsviews": "^0.9.90", + "jsrender": "^0.9.91", "node-qunit-phantomjs": "^1.2.1", "qunit": "^0.7.6", "qunitjs": "^1.18.0", diff --git a/resources/tags/built-in-tags.js b/resources/tags/built-in-tags.js index 635b62c6..295e3f6b 100644 --- a/resources/tags/built-in-tags.js +++ b/resources/tags/built-in-tags.js @@ -44,21 +44,18 @@ var treeNodeTmpl = $.templates( ""), tabsTmpl = $.templates( - - "" + - "" + - "{{for ~tag.tagCtxs }}" + - "" + - "{{/for}}" + - "" + - "" + - "" + - "" + - "
        " + - "{{>props.tabCaption}}" + - "
        " + - "{^{for ^tmpl=~tag.tagCtxs[~tag.selectedIndex].content /}}" + - "
        "), + '' + + // Tab strip UI with 'click' handler calling tag.setTab() method + '' + + '{{for ~tag.tagCtxs}}' + + '' + + '{{/for}}' + + '' + + // Tab content with wrapped content of selected {{else}} block + '' + + '' + + '' + + '
        '), //#endregion @@ -68,26 +65,19 @@ var treeNodeTmpl = $.templates( tabsTag = { init: function(tagCtx) { - this.selectedIndex = tagCtx.props.selectedTab || 0; + this.pane = tagCtx.props.selectedTab || 0; this.tabCount = this.tagCtxs.length; (this.parents.section || this.parents.page).tabs = this; }, render: function() { var tagCtx = this.tagCtx; - return this.selectedIndex === tagCtx.index ? tagCtx.render() : ""; - }, - activateTabs: function() { - var self = this; - self.contents(true, ".tabstrip").first() - .on("click", ".header_false", function() { - self.setTab($.view(this).index); - }); + return this.pane === tagCtx.index ? tagCtx.render() : ""; }, template: tabsTmpl, // methods setTab: function(index) { - $.observable(this).setProperty("selectedIndex", index); + $.observable(this).setProperty("pane", index); if (this.onSelectionChange) { this.onSelectionChange(index, this); } diff --git a/resources/tags/built-in-tags.min.js b/resources/tags/built-in-tags.min.js index 06564bd2..f3f1ad6e 100644 --- a/resources/tags/built-in-tags.min.js +++ b/resources/tags/built-in-tags.min.js @@ -1,2 +1,2 @@ -!function(e,t,i){"use strict";var a=t.templates('{^{if !hidden || ~tree.editable}}
      6. {^{if categories && categories.length }}{^{:expanded ? \'-\' : \'+\' }}{{else}}{{/if}}{^{if ~tree.editedNode(#data)}}{{if ~parentTags.treeNode }}{{/if}}
        {{else}}{{>label}}{{/if}}
      7. {^{if expanded }}
        • {^{for categories }}{^{treeNode/}}{{/for}}
      8. {{/if}}{{/if}}'),s=t.templates('
          {^{for}}{^{treeNode/}}{{/for}}
        '),n=t.templates('{{for ~tag.tagCtxs }}{{/for}}
        {{>props.tabCaption}}
        {^{for ^tmpl=~tag.tagCtxs[~tag.selectedIndex].content /}}
        '),o={init:function(e){this.selectedIndex=e.props.selectedTab||0,this.tabCount=this.tagCtxs.length,(this.parents.section||this.parents.page).tabs=this},render:function(){var e=this.tagCtx;return this.selectedIndex===e.index?e.render():""},activateTabs:function(){var e=this;e.contents(!0,".tabstrip").first().on("click",".header_false",function(){e.setTab(t.view(this).index)})},template:n,setTab:function(e){t.observable(this).setProperty("selectedIndex",e),this.onSelectionChange&&this.onSelectionChange(e,this)}},l={init:function(){this.parent.setTree(this),this.ctx.tree=this},onAfterLink:function(){var e=this;e.contents("ul").on("click",".selected, .unselected",function(){t.view(this).ctx.tag.select()}).on("click",".toggle",function(e){t.view(this).ctx.tag.toggle(),e.stopImmediatePropagation()}).on("click",".remove",function(e){t.view(this).ctx.tag.remove(),e.stopImmediatePropagation()}).on("click",".add",function(e){t.view(this).ctx.tag.addCategory(),e.stopImmediatePropagation()}).on("click",".up",function(e){t.view(this).ctx.tag.moveCategoryUp(),e.stopImmediatePropagation()}).on("click",".down",function(e){t.view(this).ctx.tag.moveCategoryDown(),e.stopImmediatePropagation()}).on("click",".hide",function(e){t.view(this).ctx.tag.hideCategory(),e.stopImmediatePropagation()})},template:s,_newNodeCounter:0,select:function(e){t.observable(this).setProperty("selected",e.data),this.onSelectionChange&&this.onSelectionChange(e.data)},editedNode:function(e){return this.editable&&this.selected===e}},d={init:function(e){this.data=e.view.data,this.tree=this.ctxPrm("tree")},template:a,toggle:function(){t.observable(this.data).setProperty("expanded",!this.data.expanded),this.tree.onExpansionChange&&this.tree.onExpansionChange(this.data)},remove:function(){var e,i=this.parent,a=i.data.categories,s=this.tagCtx.view.index;t.observable(a).remove(s),e=i.childTags(),this.tree.select(e[s]||e[s-1]||i)},select:function(){this.tree.selected!==this.data&&this.tree.select(this)},addCategory:function(){var e,i=this.data;i.categories=i.categories||[],t.observable(this.data.categories).insert({name:"name"+this.tree._newNodeCounter++,label:""}),t.observable(i).setProperty("expanded",!0),e=this.childTags(),this.tree.select(e[e.length-1])},moveCategoryUp:function(){var e=this.tagCtx.view.index,i=this.parent.data.categories;e&&t.observable(i).move(e,e-1)},moveCategoryDown:function(){var e=this.tagCtx.view.index,i=this.parent.data.categories;e+1{^{if categories && categories.length }}{^{:expanded ? \'-\' : \'+\' }}{{else}}{{/if}}{^{if ~tree.editedNode(#data)}}{{if ~parentTags.treeNode }}{{/if}}
        {{else}}{{>label}}{{/if}}{^{if expanded }}
        • {^{for categories }}{^{treeNode/}}{{/for}}
      9. {{/if}}{{/if}}'),s=e.templates('
          {^{for}}{^{treeNode/}}{{/for}}
        '),n=e.templates('{{for ~tag.tagCtxs}}{{/for}}
        '),o={init:function(t){this.pane=t.props.selectedTab||0,this.tabCount=this.tagCtxs.length,(this.parents.section||this.parents.page).tabs=this},render:function(){var t=this.tagCtx;return this.pane===t.index?t.render():""},template:n,setTab:function(t){e.observable(this).setProperty("pane",t),this.onSelectionChange&&this.onSelectionChange(t,this)}},l={init:function(){this.parent.setTree(this),this.ctx.tree=this},onAfterLink:function(){var t=this;t.contents("ul").on("click",".selected, .unselected",function(){e.view(this).ctx.tag.select()}).on("click",".toggle",function(t){e.view(this).ctx.tag.toggle(),t.stopImmediatePropagation()}).on("click",".remove",function(t){e.view(this).ctx.tag.remove(),t.stopImmediatePropagation()}).on("click",".add",function(t){e.view(this).ctx.tag.addCategory(),t.stopImmediatePropagation()}).on("click",".up",function(t){e.view(this).ctx.tag.moveCategoryUp(),t.stopImmediatePropagation()}).on("click",".down",function(t){e.view(this).ctx.tag.moveCategoryDown(),t.stopImmediatePropagation()}).on("click",".hide",function(t){e.view(this).ctx.tag.hideCategory(),t.stopImmediatePropagation()})},template:s,_newNodeCounter:0,select:function(t){e.observable(this).setProperty("selected",t.data),this.onSelectionChange&&this.onSelectionChange(t.data)},editedNode:function(t){return this.editable&&this.selected===t}},d={init:function(t){this.data=t.view.data,this.tree=this.ctxPrm("tree")},template:i,toggle:function(){e.observable(this.data).setProperty("expanded",!this.data.expanded),this.tree.onExpansionChange&&this.tree.onExpansionChange(this.data)},remove:function(){var t,a=this.parent,i=a.data.categories,s=this.tagCtx.view.index;e.observable(i).remove(s),t=a.childTags(),this.tree.select(t[s]||t[s-1]||a)},select:function(){this.tree.selected!==this.data&&this.tree.select(this)},addCategory:function(){var t,a=this.data;a.categories=a.categories||[],e.observable(this.data.categories).insert({name:"name"+this.tree._newNodeCounter++,label:""}),e.observable(a).setProperty("expanded",!0),t=this.childTags(),this.tree.select(t[t.length-1])},moveCategoryUp:function(){var t=this.tagCtx.view.index,a=this.parent.data.categories;t&&e.observable(a).move(t,t-1)},moveCategoryDown:function(){var t=this.tagCtx.view.index,a=this.parent.data.categories;t+1\" +\n\t\t\t\"{^{if categories && categories.length }}\" +\n\t\t\t\t\"{^{:expanded ? '-' : '+' }}\" +\n\t\t\t\"{{else}}\" +\n\t\t\t\t\"\" +\n\t\t\t\"{{/if}}\" +\n\t\t\t\"{^{if ~tree.editedNode(#data)}}\" +\n\t\t\t\t\"\" +\n\t\t\t\t\"\" +\n\t\t\t\t\"\" +\n\t\t\t\t\"{{if ~parentTags.treeNode }}\" +\n\t\t\t\t\t\"\" +\n\t\t\t\t\"{{/if}}\" +\n\t\t\t\t\"\" +\n\t\t\t\t\"\" +\n\t\t\t\t\"\" +\n\t\t\t\t\"
        \" +\n\t\t\t\"{{else}}\" +\n\t\t\t\t\"{{>label}}\" +\n\t\t\t\"{{/if}}\" +\n\t\t\"\" +\n\t\t\"{^{if expanded }}\" +\n\t\t\t\"
      10. \" +\n\t\t\t\t\"
          \" +\n\t\t\t\t\t\"{^{for categories }}\" +\n\t\t\t\t\t\t\"{^{treeNode/}}\" +\n\t\t\t\t\t\"{{/for}}\" +\n\t\t\t\t\"
        \" +\n\t\t\t\"
      11. \" +\n\t\t\"{{/if}}{{/if}}\"),\n\n\ttreeTmpl = $.templates(\n\n\t\t\"
          \" +\n\t\t\t\"{^{for}}\" +\n\t\t\t\t\"{^{treeNode/}}\" +\n\t\t\t\"{{/for}}\" +\n\t\t\"
        \"),\n\n\ttabsTmpl = $.templates(\n\n\t\t\"\" +\n\t\t\t\"\" +\n\t\t\t\t\"{{for ~tag.tagCtxs }}\" +\n\t\t\t\t\t\"\" +\n\t\t\t\t\"{{/for}}\" +\n\t\t\t\"\" +\n\t\t\t\"\" +\n\t\t\t\t\"\" +\n\t\t\t\"\" +\n\t\t\"
        \" +\n\t\t\t\t\t\t\"{{>props.tabCaption}}\" +\n\t\t\t\t\t\"
        \" +\n\t\t\t\t\t\"{^{for ^tmpl=~tag.tagCtxs[~tag.selectedIndex].content /}}\" +\n\t\t\t\t\"
        \"),\n\n//#endregion\n\n//#region TAG CONTROLS\n\n// {{tabs}}\n\n\ttabsTag = {\n\t\tinit: function(tagCtx) {\n\t\t\tthis.selectedIndex = tagCtx.props.selectedTab || 0;\n\t\t\tthis.tabCount = this.tagCtxs.length;\n\t\t\t(this.parents.section || this.parents.page).tabs = this;\n\t\t},\n\t\trender: function() {\n\t\t\tvar tagCtx = this.tagCtx;\n\t\t\treturn this.selectedIndex === tagCtx.index ? tagCtx.render() : \"\";\n\t\t},\n\t\tactivateTabs: function() {\n\t\t\tvar self = this;\n\t\t\tself.contents(true, \".tabstrip\").first()\n\t\t\t\t.on(\"click\", \".header_false\", function() {\n\t\t\t\t\tself.setTab($.view(this).index);\n\t\t\t\t});\n\t\t},\n\t\ttemplate: tabsTmpl,\n\n\t// methods\n\t\tsetTab: function(index) {\n\t\t\t$.observable(this).setProperty(\"selectedIndex\", index);\n\t\t\tif (this.onSelectionChange) {\n\t\t\t\tthis.onSelectionChange(index, this);\n\t\t\t}\n\t\t}\n\t},\n\n// {{tree}}\n\n\ttreeTag = {\n\t\tinit: function() {\n\t\t\tthis.parent.setTree(this);\n\t\t\tthis.ctx.tree = this; // Set contextual property ~tree\n\t\t},\n\t\tonAfterLink: function() {\n\t\t\tvar self = this;\n\t\t\tself.contents(\"ul\")\n\t\t\t\t.on(\"click\", \".selected, .unselected\", function() {\n\t\t\t\t\t$.view(this).ctx.tag.select();\n\t\t\t\t})\n\t\t\t\t.on(\"click\", \".toggle\", function(ev) {\n\t\t\t\t\t$.view(this).ctx.tag.toggle();\n\t\t\t\t\tev.stopImmediatePropagation();\n\t\t\t\t})\n\t\t\t\t.on(\"click\", \".remove\", function(ev) {\n\t\t\t\t\t$.view(this).ctx.tag.remove();\n\t\t\t\t\tev.stopImmediatePropagation();\n\t\t\t\t})\n\t\t\t\t.on(\"click\", \".add\", function(ev) {\n\t\t\t\t\t$.view(this).ctx.tag.addCategory();\n\t\t\t\t\tev.stopImmediatePropagation();\n\t\t\t\t})\n\t\t\t\t.on(\"click\", \".up\", function(ev) {\n\t\t\t\t\t$.view(this).ctx.tag.moveCategoryUp();\n\t\t\t\t\tev.stopImmediatePropagation();\n\t\t\t\t})\n\t\t\t\t.on(\"click\", \".down\", function(ev) {\n\t\t\t\t\t$.view(this).ctx.tag.moveCategoryDown();\n\t\t\t\t\tev.stopImmediatePropagation();\n\t\t\t\t})\n\t\t\t\t.on(\"click\", \".hide\", function(ev) {\n\t\t\t\t\t$.view(this).ctx.tag.hideCategory();\n\t\t\t\t\tev.stopImmediatePropagation();\n\t\t\t\t});\n\t\t},\n\t\ttemplate: treeTmpl,\n\n\t//PROPERTIES\n\t\t_newNodeCounter: 0,\n\n\t//METHODS\n\t\tselect: function(treeNode) {\n\t\t\t$.observable(this).setProperty(\"selected\", treeNode.data);\n\t\t\tif (this.onSelectionChange) {\n\t\t\t\tthis.onSelectionChange(treeNode.data);\n\t\t\t}\n\t\t},\n\t\teditedNode: function(treeNode) {\n\t\t\treturn this.editable && this.selected === treeNode;\n\t\t}\n\t},\n\n// {{treeNode}}\n\n\ttreeNodeTag = {\n\t\tinit: function(tagCtx) {\n\t\t\tthis.data = tagCtx.view.data;\n\t\t\tthis.tree = this.ctxPrm(\"tree\");\n\t\t},\n\t\ttemplate: treeNodeTmpl,\n\n\t// methods\n\t\ttoggle: function() {\n\t\t\t$.observable(this.data).setProperty(\"expanded\", !this.data.expanded);\n\t\t\tif (this.tree.onExpansionChange) {\n\t\t\t\tthis.tree.onExpansionChange(this.data);\n\t\t\t}\n\t\t},\n\t\tremove: function() {\n\t\t\tvar childNodes,\n\t\t\t\tparent = this.parent,\n\t\t\t\tcontentArray = parent.data.categories,\n\t\t\t\tindex = this.tagCtx.view.index;\n\t\t\t$.observable(contentArray).remove(index);\n\t\t\tchildNodes = parent.childTags();\n\t\t\tthis.tree.select(childNodes[index] || childNodes[index - 1] || parent);\n\t\t},\n\t\tselect: function() {\n\t\t\tif (this.tree.selected !== this.data) {\n\t\t\t\tthis.tree.select(this);\n\t\t\t}\n\t\t},\n\t\taddCategory: function() {\n\t\t\tvar childNodes,\n\t\t\t\tnodeData = this.data;\n\t\t\tnodeData.categories = nodeData.categories || [];\n\t\t\t$.observable(this.data.categories).insert({\n\t\t\t\tname: \"name\" + this.tree._newNodeCounter++,\n\t\t\t\tlabel: \"\"\n\t\t\t});\n\t\t\t$.observable(nodeData).setProperty(\"expanded\", true);\n\t\t\tchildNodes = this.childTags();\n\t\t\tthis.tree.select(childNodes[childNodes.length - 1]);\n\t\t},\n\t\tmoveCategoryUp: function() {\n\t\t\tvar index = this.tagCtx.view.index,\n\t\t\t\tcategories = this.parent.data.categories;\n\t\t\tif (index) {\n\t\t\t\t$.observable(categories).move(index, index-1);\n\t\t\t}\n\t\t},\n\t\tmoveCategoryDown: function() {\n\t\t\tvar index = this.tagCtx.view.index,\n\t\t\t\tcategories = this.parent.data.categories;\n\t\t\tif (index + 1 < categories.length) {\n\t\t\t\t$.observable(categories).move(index, index+1);\n\t\t\t}\n\t\t},\n\t\thideCategory: function() {\n\t\t\tvar category = this.parent.data.categories[this.tagCtx.view.index];\n\t\t\t$.observable(category).setProperty(\"hidden\", !category.hidden);\n\t\t}\n\t},\n\n// {{selectList}}\n// Derives from {{for}} control\n\n\tselectListTag = {\n\t\t// This tag control derives from the {{for}} tag.\n\t\tbaseTag: \"for\",\n\n\t\t// overrides of {{for}} tag.\n\n\t\tonArrayChange: function(ev, eventArgs) {\n\t\t\t// Call baseTag implementation\n\t\t\tthis.baseApply(arguments);\n\n\t\t\tif (eventArgs.change === \"insert\") {\n\t\t\t\tthis.toggleSelect(eventArgs.index);\n\t\t\t}\n\t\t},\n\t\tflow: false,\n\n\t\t// additional methods\n\n\t\ttoggleSelect: function(index) {\n\t\t\tthis.item(index).toggleSelect();\n\t\t},\n\t\titem: function(index) {\n\t\t\treturn this.childTags()[index];\n\t\t},\n\n\t\t// additional events\n\n\t\tonToggleSelect: function(child) {\n\t\t\tif (child === this.selectedChild) {\n\t\t\t\tthis.selectedChild = null;\n\t\t\t} else if (child.selected) {\n\t\t\t\tif (this.selectedChild) {\n\t\t\t\t\tthis.selectedChild.toggleSelect();\n\t\t\t\t}\n\t\t\t\tthis.selectedChild = child;\n\t\t\t}\n\t\t}\n\t};\n\n//#endregion\n\n//#region INITIALIZATION\n\ntreeTag.editedNode.depends = \"editable\";\n\n$.views.tags({\n\ttabs: tabsTag,\n\ttree: treeTag,\n\ttreeNode: treeNodeTag,\n\tselectList: selectListTag\n});\n\n//#endregion\n})(this, this.jQuery);\n"]} \ No newline at end of file +{"version":3,"sources":["built-in-tags.js"],"names":["window","$","undefined","treeNodeTmpl","templates","treeTmpl","tabsTmpl","tabsTag","init","tagCtx","this","pane","props","selectedTab","tabCount","tagCtxs","length","parents","section","page","tabs","render","index","template","setTab","observable","setProperty","onSelectionChange","treeTag","parent","setTree","ctx","tree","onAfterLink","self","contents","on","view","tag","select","ev","toggle","stopImmediatePropagation","remove","addCategory","moveCategoryUp","moveCategoryDown","hideCategory","_newNodeCounter","treeNode","data","editedNode","editable","selected","treeNodeTag","ctxPrm","expanded","onExpansionChange","childNodes","contentArray","categories","childTags","nodeData","insert","name","label","move","category","hidden","selectListTag","baseTag","onArrayChange","eventArgs","baseApply","arguments","change","toggleSelect","flow","item","onToggleSelect","child","selectedChild","depends","views","tags","selectList","jQuery"],"mappings":"CAAA,SAAUA,EAAQC,EAAGC,GACrB,YAIA,IAAIC,GAAeF,EAAEG,UACnB,2yBA+BDC,EAAYJ,EAAEG,UAEb,wDAMDE,EAAYL,EAAEG,UACb,4VAmBDG,GACCC,KAAM,SAASC,GACdC,KAAKC,KAAOF,EAAOG,MAAMC,aAAe,EACxCH,KAAKI,SAAWJ,KAAKK,QAAQC,QAC5BN,KAAKO,QAAQC,SAAWR,KAAKO,QAAQE,MAAMC,KAAOV,MAEpDW,OAAQ,WACP,GAAIZ,GAASC,KAAKD,MAClB,OAAOC,MAAKC,OAASF,EAAOa,MAAQb,EAAOY,SAAW,IAEvDE,SAAUjB,EAGVkB,OAAQ,SAASF,GAChBrB,EAAEwB,WAAWf,MAAMgB,YAAY,OAAQJ,GACnCZ,KAAKiB,mBACRjB,KAAKiB,kBAAkBL,EAAOZ,QAOjCkB,GACCpB,KAAM,WACLE,KAAKmB,OAAOC,QAAQpB,MACpBA,KAAKqB,IAAIC,KAAOtB,MAEjBuB,YAAa,WACZ,GAAIC,GAAOxB,IACXwB,GAAKC,SAAS,MACZC,GAAG,QAAS,yBAA0B,WACtCnC,EAAEoC,KAAK3B,MAAMqB,IAAIO,IAAIC,WAErBH,GAAG,QAAS,UAAW,SAASI,GAChCvC,EAAEoC,KAAK3B,MAAMqB,IAAIO,IAAIG,SACrBD,EAAGE,6BAEHN,GAAG,QAAS,UAAW,SAASI,GAChCvC,EAAEoC,KAAK3B,MAAMqB,IAAIO,IAAIK,SACrBH,EAAGE,6BAEHN,GAAG,QAAS,OAAQ,SAASI,GAC7BvC,EAAEoC,KAAK3B,MAAMqB,IAAIO,IAAIM,cACrBJ,EAAGE,6BAEHN,GAAG,QAAS,MAAO,SAASI,GAC5BvC,EAAEoC,KAAK3B,MAAMqB,IAAIO,IAAIO,iBACrBL,EAAGE,6BAEHN,GAAG,QAAS,QAAS,SAASI,GAC9BvC,EAAEoC,KAAK3B,MAAMqB,IAAIO,IAAIQ,mBACrBN,EAAGE,6BAEHN,GAAG,QAAS,QAAS,SAASI,GAC9BvC,EAAEoC,KAAK3B,MAAMqB,IAAIO,IAAIS,eACrBP,EAAGE,8BAGNnB,SAAUlB,EAGV2C,gBAAiB,EAGjBT,OAAQ,SAASU,GAChBhD,EAAEwB,WAAWf,MAAMgB,YAAY,WAAYuB,EAASC,MAChDxC,KAAKiB,mBACRjB,KAAKiB,kBAAkBsB,EAASC,OAGlCC,WAAY,SAASF,GACpB,MAAOvC,MAAK0C,UAAY1C,KAAK2C,WAAaJ,IAM5CK,GACC9C,KAAM,SAASC,GACdC,KAAKwC,KAAOzC,EAAO4B,KAAKa,KACxBxC,KAAKsB,KAAOtB,KAAK6C,OAAO,SAEzBhC,SAAUpB,EAGVsC,OAAQ,WACPxC,EAAEwB,WAAWf,KAAKwC,MAAMxB,YAAY,YAAahB,KAAKwC,KAAKM,UACvD9C,KAAKsB,KAAKyB,mBACb/C,KAAKsB,KAAKyB,kBAAkB/C,KAAKwC,OAGnCP,OAAQ,WACP,GAAIe,GACH7B,EAASnB,KAAKmB,OACd8B,EAAe9B,EAAOqB,KAAKU,WAC3BtC,EAAQZ,KAAKD,OAAO4B,KAAKf,KAC1BrB,GAAEwB,WAAWkC,GAAchB,OAAOrB,GAClCoC,EAAa7B,EAAOgC,YACpBnD,KAAKsB,KAAKO,OAAOmB,EAAWpC,IAAUoC,EAAWpC,EAAQ,IAAMO,IAEhEU,OAAQ,WACH7B,KAAKsB,KAAKqB,WAAa3C,KAAKwC,MAC/BxC,KAAKsB,KAAKO,OAAO7B,OAGnBkC,YAAa,WACZ,GAAIc,GACHI,EAAWpD,KAAKwC,IACjBY,GAASF,WAAaE,EAASF,eAC/B3D,EAAEwB,WAAWf,KAAKwC,KAAKU,YAAYG,QAClCC,KAAM,OAAStD,KAAKsB,KAAKgB,kBACzBiB,MAAO,KAERhE,EAAEwB,WAAWqC,GAAUpC,YAAY,YAAY,GAC/CgC,EAAahD,KAAKmD,YAClBnD,KAAKsB,KAAKO,OAAOmB,EAAWA,EAAW1C,OAAS,KAEjD6B,eAAgB,WACf,GAAIvB,GAAQZ,KAAKD,OAAO4B,KAAKf,MAC5BsC,EAAalD,KAAKmB,OAAOqB,KAAKU,UAC3BtC,IACHrB,EAAEwB,WAAWmC,GAAYM,KAAK5C,EAAOA,EAAM,IAG7CwB,iBAAkB,WACjB,GAAIxB,GAAQZ,KAAKD,OAAO4B,KAAKf,MAC5BsC,EAAalD,KAAKmB,OAAOqB,KAAKU,UAC3BtC,GAAQ,EAAIsC,EAAW5C,QAC1Bf,EAAEwB,WAAWmC,GAAYM,KAAK5C,EAAOA,EAAM,IAG7CyB,aAAc,WACb,GAAIoB,GAAWzD,KAAKmB,OAAOqB,KAAKU,WAAWlD,KAAKD,OAAO4B,KAAKf,MAC5DrB,GAAEwB,WAAW0C,GAAUzC,YAAY,UAAWyC,EAASC,UAOzDC,GAECC,QAAS,MAITC,cAAe,SAAS/B,EAAIgC,GAE3B9D,KAAK+D,UAAUC,WAEU,WAArBF,EAAUG,QACbjE,KAAKkE,aAAaJ,EAAUlD,QAG9BuD,MAAM,EAIND,aAAc,SAAStD,GACtBZ,KAAKoE,KAAKxD,GAAOsD,gBAElBE,KAAM,SAASxD,GACd,MAAOZ,MAAKmD,YAAYvC,IAKzByD,eAAgB,SAASC,GACpBA,IAAUtE,KAAKuE,cAClBvE,KAAKuE,cAAgB,KACXD,EAAM3B,WACZ3C,KAAKuE,eACRvE,KAAKuE,cAAcL,eAEpBlE,KAAKuE,cAAgBD,IASzBpD,GAAQuB,WAAW+B,QAAU,WAE7BjF,EAAEkF,MAAMC,MACPhE,KAAMb,EACNyB,KAAMJ,EACNqB,SAAUK,EACV+B,WAAYhB,KAIV3D,KAAMA,KAAK4E","file":"built-in-tags.min.js","sourcesContent":["(function(window, $, undefined) {\n\"use strict\";\n\n//#region TEMPLATES\n\nvar treeNodeTmpl = $.templates(\n\t\t\"{^{if !hidden || ~tree.editable}}
      12. \" +\n\t\t\t\"{^{if categories && categories.length }}\" +\n\t\t\t\t\"{^{:expanded ? '-' : '+' }}\" +\n\t\t\t\"{{else}}\" +\n\t\t\t\t\"\" +\n\t\t\t\"{{/if}}\" +\n\t\t\t\"{^{if ~tree.editedNode(#data)}}\" +\n\t\t\t\t\"\" +\n\t\t\t\t\"\" +\n\t\t\t\t\"\" +\n\t\t\t\t\"{{if ~parentTags.treeNode }}\" +\n\t\t\t\t\t\"\" +\n\t\t\t\t\"{{/if}}\" +\n\t\t\t\t\"\" +\n\t\t\t\t\"\" +\n\t\t\t\t\"\" +\n\t\t\t\t\"
        \" +\n\t\t\t\"{{else}}\" +\n\t\t\t\t\"{{>label}}\" +\n\t\t\t\"{{/if}}\" +\n\t\t\"
      13. \" +\n\t\t\"{^{if expanded }}\" +\n\t\t\t\"
      14. \" +\n\t\t\t\t\"
          \" +\n\t\t\t\t\t\"{^{for categories }}\" +\n\t\t\t\t\t\t\"{^{treeNode/}}\" +\n\t\t\t\t\t\"{{/for}}\" +\n\t\t\t\t\"
        \" +\n\t\t\t\"
      15. \" +\n\t\t\"{{/if}}{{/if}}\"),\n\n\ttreeTmpl = $.templates(\n\n\t\t\"
          \" +\n\t\t\t\"{^{for}}\" +\n\t\t\t\t\"{^{treeNode/}}\" +\n\t\t\t\"{{/for}}\" +\n\t\t\"
        \"),\n\n\ttabsTmpl = $.templates(\n\t\t'' +\n\t\t\t// Tab strip UI with 'click' handler calling tag.setTab() method\n\t\t\t'' +\n\t\t\t\t'{{for ~tag.tagCtxs}}' +\n\t\t\t\t\t'' +\n\t\t\t\t'{{/for}}' +\n\t\t\t'' +\n\t\t\t// Tab content with wrapped content of selected {{else}} block\n\t\t\t'' +\n\t\t\t\t'' +\n\t\t\t'' +\n\t\t'
        '),\n\n//#endregion\n\n//#region TAG CONTROLS\n\n// {{tabs}}\n\n\ttabsTag = {\n\t\tinit: function(tagCtx) {\n\t\t\tthis.pane = tagCtx.props.selectedTab || 0;\n\t\t\tthis.tabCount = this.tagCtxs.length;\n\t\t\t(this.parents.section || this.parents.page).tabs = this;\n\t\t},\n\t\trender: function() {\n\t\t\tvar tagCtx = this.tagCtx;\n\t\t\treturn this.pane === tagCtx.index ? tagCtx.render() : \"\";\n\t\t},\n\t\ttemplate: tabsTmpl,\n\n\t// methods\n\t\tsetTab: function(index) {\n\t\t\t$.observable(this).setProperty(\"pane\", index);\n\t\t\tif (this.onSelectionChange) {\n\t\t\t\tthis.onSelectionChange(index, this);\n\t\t\t}\n\t\t}\n\t},\n\n// {{tree}}\n\n\ttreeTag = {\n\t\tinit: function() {\n\t\t\tthis.parent.setTree(this);\n\t\t\tthis.ctx.tree = this; // Set contextual property ~tree\n\t\t},\n\t\tonAfterLink: function() {\n\t\t\tvar self = this;\n\t\t\tself.contents(\"ul\")\n\t\t\t\t.on(\"click\", \".selected, .unselected\", function() {\n\t\t\t\t\t$.view(this).ctx.tag.select();\n\t\t\t\t})\n\t\t\t\t.on(\"click\", \".toggle\", function(ev) {\n\t\t\t\t\t$.view(this).ctx.tag.toggle();\n\t\t\t\t\tev.stopImmediatePropagation();\n\t\t\t\t})\n\t\t\t\t.on(\"click\", \".remove\", function(ev) {\n\t\t\t\t\t$.view(this).ctx.tag.remove();\n\t\t\t\t\tev.stopImmediatePropagation();\n\t\t\t\t})\n\t\t\t\t.on(\"click\", \".add\", function(ev) {\n\t\t\t\t\t$.view(this).ctx.tag.addCategory();\n\t\t\t\t\tev.stopImmediatePropagation();\n\t\t\t\t})\n\t\t\t\t.on(\"click\", \".up\", function(ev) {\n\t\t\t\t\t$.view(this).ctx.tag.moveCategoryUp();\n\t\t\t\t\tev.stopImmediatePropagation();\n\t\t\t\t})\n\t\t\t\t.on(\"click\", \".down\", function(ev) {\n\t\t\t\t\t$.view(this).ctx.tag.moveCategoryDown();\n\t\t\t\t\tev.stopImmediatePropagation();\n\t\t\t\t})\n\t\t\t\t.on(\"click\", \".hide\", function(ev) {\n\t\t\t\t\t$.view(this).ctx.tag.hideCategory();\n\t\t\t\t\tev.stopImmediatePropagation();\n\t\t\t\t});\n\t\t},\n\t\ttemplate: treeTmpl,\n\n\t//PROPERTIES\n\t\t_newNodeCounter: 0,\n\n\t//METHODS\n\t\tselect: function(treeNode) {\n\t\t\t$.observable(this).setProperty(\"selected\", treeNode.data);\n\t\t\tif (this.onSelectionChange) {\n\t\t\t\tthis.onSelectionChange(treeNode.data);\n\t\t\t}\n\t\t},\n\t\teditedNode: function(treeNode) {\n\t\t\treturn this.editable && this.selected === treeNode;\n\t\t}\n\t},\n\n// {{treeNode}}\n\n\ttreeNodeTag = {\n\t\tinit: function(tagCtx) {\n\t\t\tthis.data = tagCtx.view.data;\n\t\t\tthis.tree = this.ctxPrm(\"tree\");\n\t\t},\n\t\ttemplate: treeNodeTmpl,\n\n\t// methods\n\t\ttoggle: function() {\n\t\t\t$.observable(this.data).setProperty(\"expanded\", !this.data.expanded);\n\t\t\tif (this.tree.onExpansionChange) {\n\t\t\t\tthis.tree.onExpansionChange(this.data);\n\t\t\t}\n\t\t},\n\t\tremove: function() {\n\t\t\tvar childNodes,\n\t\t\t\tparent = this.parent,\n\t\t\t\tcontentArray = parent.data.categories,\n\t\t\t\tindex = this.tagCtx.view.index;\n\t\t\t$.observable(contentArray).remove(index);\n\t\t\tchildNodes = parent.childTags();\n\t\t\tthis.tree.select(childNodes[index] || childNodes[index - 1] || parent);\n\t\t},\n\t\tselect: function() {\n\t\t\tif (this.tree.selected !== this.data) {\n\t\t\t\tthis.tree.select(this);\n\t\t\t}\n\t\t},\n\t\taddCategory: function() {\n\t\t\tvar childNodes,\n\t\t\t\tnodeData = this.data;\n\t\t\tnodeData.categories = nodeData.categories || [];\n\t\t\t$.observable(this.data.categories).insert({\n\t\t\t\tname: \"name\" + this.tree._newNodeCounter++,\n\t\t\t\tlabel: \"\"\n\t\t\t});\n\t\t\t$.observable(nodeData).setProperty(\"expanded\", true);\n\t\t\tchildNodes = this.childTags();\n\t\t\tthis.tree.select(childNodes[childNodes.length - 1]);\n\t\t},\n\t\tmoveCategoryUp: function() {\n\t\t\tvar index = this.tagCtx.view.index,\n\t\t\t\tcategories = this.parent.data.categories;\n\t\t\tif (index) {\n\t\t\t\t$.observable(categories).move(index, index-1);\n\t\t\t}\n\t\t},\n\t\tmoveCategoryDown: function() {\n\t\t\tvar index = this.tagCtx.view.index,\n\t\t\t\tcategories = this.parent.data.categories;\n\t\t\tif (index + 1 < categories.length) {\n\t\t\t\t$.observable(categories).move(index, index+1);\n\t\t\t}\n\t\t},\n\t\thideCategory: function() {\n\t\t\tvar category = this.parent.data.categories[this.tagCtx.view.index];\n\t\t\t$.observable(category).setProperty(\"hidden\", !category.hidden);\n\t\t}\n\t},\n\n// {{selectList}}\n// Derives from {{for}} control\n\n\tselectListTag = {\n\t\t// This tag control derives from the {{for}} tag.\n\t\tbaseTag: \"for\",\n\n\t\t// overrides of {{for}} tag.\n\n\t\tonArrayChange: function(ev, eventArgs) {\n\t\t\t// Call baseTag implementation\n\t\t\tthis.baseApply(arguments);\n\n\t\t\tif (eventArgs.change === \"insert\") {\n\t\t\t\tthis.toggleSelect(eventArgs.index);\n\t\t\t}\n\t\t},\n\t\tflow: false,\n\n\t\t// additional methods\n\n\t\ttoggleSelect: function(index) {\n\t\t\tthis.item(index).toggleSelect();\n\t\t},\n\t\titem: function(index) {\n\t\t\treturn this.childTags()[index];\n\t\t},\n\n\t\t// additional events\n\n\t\tonToggleSelect: function(child) {\n\t\t\tif (child === this.selectedChild) {\n\t\t\t\tthis.selectedChild = null;\n\t\t\t} else if (child.selected) {\n\t\t\t\tif (this.selectedChild) {\n\t\t\t\t\tthis.selectedChild.toggleSelect();\n\t\t\t\t}\n\t\t\t\tthis.selectedChild = child;\n\t\t\t}\n\t\t}\n\t};\n\n//#endregion\n\n//#region INITIALIZATION\n\ntreeTag.editedNode.depends = \"editable\";\n\n$.views.tags({\n\ttabs: tabsTag,\n\ttree: treeTag,\n\ttreeNode: treeNodeTag,\n\tselectList: selectListTag\n});\n\n//#endregion\n})(this, this.jQuery);\n"]} \ No newline at end of file diff --git a/samples/change-log.css b/samples/change-log.css index e5a5862d..a8a58b84 100644 --- a/samples/change-log.css +++ b/samples/change-log.css @@ -20,7 +20,7 @@ } .logBox .messages { - width: 546px; + width: 500px; clear: right; border-top: 2px solid #ccc; padding-top: 4px; diff --git a/samples/computed/fullname/data.html b/samples/computed/fullname/data.html index e8afb7dd..6190913b 100644 --- a/samples/computed/fullname/data.html +++ b/samples/computed/fullname/data.html @@ -1,7 +1,7 @@ - + diff --git a/samples/computed/fullname/helper.html b/samples/computed/fullname/helper.html index 7291c7bf..f5e697c0 100644 --- a/samples/computed/fullname/helper.html +++ b/samples/computed/fullname/helper.html @@ -1,7 +1,7 @@ - + diff --git a/samples/computed/fullname/prototype.html b/samples/computed/fullname/prototype.html index c8cae25e..28619609 100644 --- a/samples/computed/fullname/prototype.html +++ b/samples/computed/fullname/prototype.html @@ -1,7 +1,7 @@ - + diff --git a/samples/computed/shopping-cart/tmpl.html b/samples/computed/shopping-cart/tmpl.html index ec74dc99..7597bda5 100644 --- a/samples/computed/shopping-cart/tmpl.html +++ b/samples/computed/shopping-cart/tmpl.html @@ -1,7 +1,7 @@ - + diff --git a/samples/computed/shopping-cart/top-level.html b/samples/computed/shopping-cart/top-level.html index 6931bc00..1c3e30b0 100644 --- a/samples/computed/shopping-cart/top-level.html +++ b/samples/computed/shopping-cart/top-level.html @@ -1,7 +1,7 @@ - + diff --git a/samples/computed/team-manager/sample.html b/samples/computed/team-manager/sample.html index dd3dc334..2db9a28e 100644 --- a/samples/computed/team-manager/sample.html +++ b/samples/computed/team-manager/sample.html @@ -1,7 +1,7 @@ - + diff --git a/samples/data-link/10_linked-visibility.html b/samples/data-link/10_linked-visibility.html index a0db97f2..281bb205 100644 --- a/samples/data-link/10_linked-visibility.html +++ b/samples/data-link/10_linked-visibility.html @@ -1,7 +1,7 @@ - + @@ -31,7 +31,7 @@ The team is complete! -

        +
        {^{for people}}
        diff --git a/samples/data-link/11_linked-hover.html b/samples/data-link/11_linked-hover.html index b3870d54..57d90a11 100644 --- a/samples/data-link/11_linked-hover.html +++ b/samples/data-link/11_linked-hover.html @@ -1,7 +1,7 @@ - + diff --git a/samples/data-link/11b_linked-hover.html b/samples/data-link/11b_linked-hover.html index c04049b2..6417b76e 100644 --- a/samples/data-link/11b_linked-hover.html +++ b/samples/data-link/11b_linked-hover.html @@ -1,7 +1,7 @@ - + diff --git a/samples/data-link/11c_linked-hover.html b/samples/data-link/11c_linked-hover.html index 33bd8400..9effefee 100644 --- a/samples/data-link/11c_linked-hover.html +++ b/samples/data-link/11c_linked-hover.html @@ -1,7 +1,7 @@ - + diff --git a/samples/data-link/12_linked-css.html b/samples/data-link/12_linked-css.html index 02805d0b..49604ec1 100644 --- a/samples/data-link/12_linked-css.html +++ b/samples/data-link/12_linked-css.html @@ -1,7 +1,7 @@ - + diff --git a/samples/data-link/13_linked-svg.html b/samples/data-link/13_linked-svg.html index 564ed273..0d806734 100644 --- a/samples/data-link/13_linked-svg.html +++ b/samples/data-link/13_linked-svg.html @@ -1,7 +1,7 @@ - + diff --git a/samples/data-link/1_if-tag-in-attribute.html b/samples/data-link/1_if-tag-in-attribute.html index 775cb452..e0207ee4 100644 --- a/samples/data-link/1_if-tag-in-attribute.html +++ b/samples/data-link/1_if-tag-in-attribute.html @@ -1,7 +1,7 @@  - + diff --git a/samples/data-link/2_mouse-events-in-template.html b/samples/data-link/2_mouse-events-in-template.html index 30b8f92c..838b329c 100644 --- a/samples/data-link/2_mouse-events-in-template.html +++ b/samples/data-link/2_mouse-events-in-template.html @@ -1,7 +1,7 @@ - + diff --git a/samples/data-link/3_include-tag-in-attribute.html b/samples/data-link/3_include-tag-in-attribute.html index 11ecff7d..f22a674a 100644 --- a/samples/data-link/3_include-tag-in-attribute.html +++ b/samples/data-link/3_include-tag-in-attribute.html @@ -1,7 +1,7 @@ - + diff --git a/samples/data-link/4_linked-for-tag.html b/samples/data-link/4_linked-for-tag.html index e0be03d5..98460a0c 100644 --- a/samples/data-link/4_linked-for-tag.html +++ b/samples/data-link/4_linked-for-tag.html @@ -1,7 +1,7 @@ - + diff --git a/samples/data-link/5_linked-for-tag.html b/samples/data-link/5_linked-for-tag.html index 016756d7..3d19fe7a 100644 --- a/samples/data-link/5_linked-for-tag.html +++ b/samples/data-link/5_linked-for-tag.html @@ -1,7 +1,7 @@ - + diff --git a/samples/data-link/6_linked-if-tag.html b/samples/data-link/6_linked-if-tag.html index cff14a15..432bfaac 100644 --- a/samples/data-link/6_linked-if-tag.html +++ b/samples/data-link/6_linked-if-tag.html @@ -1,7 +1,7 @@ - + diff --git a/samples/data-link/7_link-to-class.html b/samples/data-link/7_link-to-class.html index 8f7dfef5..144281cd 100644 --- a/samples/data-link/7_link-to-class.html +++ b/samples/data-link/7_link-to-class.html @@ -1,7 +1,7 @@ - + @@ -15,7 +15,7 @@
        + @@ -19,7 +19,7 @@
        + diff --git a/samples/editable-data/compiled/sample.html b/samples/editable-data/compiled/sample.html index b56982c4..7e404503 100644 --- a/samples/editable-data/compiled/sample.html +++ b/samples/editable-data/compiled/sample.html @@ -1,7 +1,7 @@ - + diff --git a/samples/editable-data/hash-dictionary/sample.html b/samples/editable-data/hash-dictionary/sample.html index 7cd6e083..b5d49234 100644 --- a/samples/editable-data/hash-dictionary/sample.html +++ b/samples/editable-data/hash-dictionary/sample.html @@ -1,7 +1,7 @@ - + diff --git a/samples/editable-data/linked-elems/sample.html b/samples/editable-data/linked-elems/sample.html index fe191e8f..a8c561c9 100644 --- a/samples/editable-data/linked-elems/sample.html +++ b/samples/editable-data/linked-elems/sample.html @@ -1,7 +1,7 @@ - + diff --git a/samples/editable-data/linked-tags/sample.html b/samples/editable-data/linked-tags/sample.html index 59055a24..64fa9599 100644 --- a/samples/editable-data/linked-tags/sample.html +++ b/samples/editable-data/linked-tags/sample.html @@ -1,7 +1,7 @@ - + diff --git a/samples/editable-data/observe/sample.html b/samples/editable-data/observe/sample.html index 0db149ca..612a0544 100644 --- a/samples/editable-data/observe/sample.html +++ b/samples/editable-data/observe/sample.html @@ -1,7 +1,7 @@ - + diff --git a/samples/editable-data/submit/sample.html b/samples/editable-data/submit/sample.html index 1bcaa476..952a21d2 100644 --- a/samples/editable-data/submit/sample.html +++ b/samples/editable-data/submit/sample.html @@ -1,7 +1,7 @@ - + diff --git a/samples/editable-data/toplevel-for/sample.html b/samples/editable-data/toplevel-for/sample.html index b2932d54..be0a2a07 100644 --- a/samples/editable-data/toplevel-for/sample.html +++ b/samples/editable-data/toplevel-for/sample.html @@ -1,7 +1,7 @@ - + diff --git a/samples/form-els/array-binding/sample.html b/samples/form-els/array-binding/sample.html index e81e2ebe..faa723f2 100644 --- a/samples/form-els/array-binding/sample.html +++ b/samples/form-els/array-binding/sample.html @@ -1,7 +1,7 @@ - + diff --git a/samples/form-els/converters/day-to-int.html b/samples/form-els/converters/day-to-int.html index 5f3a68db..d8eb14ba 100644 --- a/samples/form-els/converters/day-to-int.html +++ b/samples/form-els/converters/day-to-int.html @@ -1,7 +1,7 @@ - + diff --git a/samples/form-els/converters/sample.html b/samples/form-els/converters/sample.html index dad9b5dd..04e4317a 100644 --- a/samples/form-els/converters/sample.html +++ b/samples/form-els/converters/sample.html @@ -1,7 +1,7 @@ - + diff --git a/samples/form-els/simple/template.html b/samples/form-els/simple/template.html index bdce35a0..8f7301c6 100644 --- a/samples/form-els/simple/template.html +++ b/samples/form-els/simple/template.html @@ -1,7 +1,7 @@ - + @@ -17,7 +17,7 @@

        JsViews data-linking to form elements - within a rendered template

        Amount: span

        - {^{:amount}}


        + {^{>amount}}


        diff --git a/samples/form-els/simple/top-level.html b/samples/form-els/simple/top-level.html index 65b9f44d..088d7eb2 100644 --- a/samples/form-els/simple/top-level.html +++ b/samples/form-els/simple/top-level.html @@ -1,7 +1,7 @@ - + diff --git a/samples/form-els/visible-binding/sample.html b/samples/form-els/visible-binding/sample.html index 849cc444..d05877eb 100644 --- a/samples/form-els/visible-binding/sample.html +++ b/samples/form-els/visible-binding/sample.html @@ -1,7 +1,7 @@ - + diff --git a/samples/iframedefault.html b/samples/iframedefault.html index 0fe7134d..4a1d6a90 100644 --- a/samples/iframedefault.html +++ b/samples/iframedefault.html @@ -1,12 +1,13 @@ - + - + +
        - - - + + + diff --git a/samples/iframedefault_nocss.html b/samples/iframedefault_nocss.html index f6dc66a2..9b15d47a 100644 --- a/samples/iframedefault_nocss.html +++ b/samples/iframedefault_nocss.html @@ -1,11 +1,12 @@ - + +
        - - - + + + diff --git a/samples/iframedefaultjqui.html b/samples/iframedefaultjqui.html index e5a18545..0eaecd37 100644 --- a/samples/iframedefaultjqui.html +++ b/samples/iframedefaultjqui.html @@ -1,15 +1,16 @@ - + - + +
        - - + + - - + + diff --git a/samples/iframedefaultjqui_nocss.html b/samples/iframedefaultjqui_nocss.html index 8dd64297..da437757 100644 --- a/samples/iframedefaultjqui_nocss.html +++ b/samples/iframedefaultjqui_nocss.html @@ -1,14 +1,15 @@ - + +
        - - + + - - + + diff --git a/samples/iframedefaultjsr.html b/samples/iframedefaultjsr.html index a58ae9ad..438f58e8 100644 --- a/samples/iframedefaultjsr.html +++ b/samples/iframedefaultjsr.html @@ -1,12 +1,13 @@ - + - + +
        - - - + + + diff --git a/samples/iframedefaultjsr_nocss.html b/samples/iframedefaultjsr_nocss.html index 17e9f54d..8b88aba7 100644 --- a/samples/iframedefaultjsr_nocss.html +++ b/samples/iframedefaultjsr_nocss.html @@ -1,11 +1,12 @@ - + +
        - - - + + + diff --git a/samples/jsrender/composition/from-strings/sample.html b/samples/jsrender/composition/from-strings/sample.html index d774466e..fe569b30 100644 --- a/samples/jsrender/composition/from-strings/sample.html +++ b/samples/jsrender/composition/from-strings/sample.html @@ -1,7 +1,7 @@ - + diff --git a/samples/jsrender/composition/remote-tmpl/sample.html b/samples/jsrender/composition/remote-tmpl/sample.html index d774466e..fe569b30 100644 --- a/samples/jsrender/composition/remote-tmpl/sample.html +++ b/samples/jsrender/composition/remote-tmpl/sample.html @@ -1,7 +1,7 @@ - + diff --git a/samples/jsrender/composition/sub-tmpl/sample.css b/samples/jsrender/composition/sub-tmpl/sample.css index d3bef561..e3739ebe 100644 --- a/samples/jsrender/composition/sub-tmpl/sample.css +++ b/samples/jsrender/composition/sub-tmpl/sample.css @@ -32,4 +32,4 @@ thead th { table td { padding: 3px; border: solid #77c 1px; -} +} \ No newline at end of file diff --git a/samples/jsrender/composition/sub-tmpl/sample.html b/samples/jsrender/composition/sub-tmpl/sample.html index 4f06cfa7..58949d41 100644 --- a/samples/jsrender/composition/sub-tmpl/sample.html +++ b/samples/jsrender/composition/sub-tmpl/sample.html @@ -1,7 +1,7 @@ - + @@ -41,7 +41,7 @@ {{/if}} - +
        diff --git a/samples/jsrender/composition/tmpl-objects/sample.html b/samples/jsrender/composition/tmpl-objects/sample.html index 84d7f092..948fba63 100644 --- a/samples/jsrender/composition/tmpl-objects/sample.html +++ b/samples/jsrender/composition/tmpl-objects/sample.html @@ -1,7 +1,7 @@ - + diff --git a/samples/jsrender/composition/tmpl/sample.html b/samples/jsrender/composition/tmpl/sample.html index 933b1353..056233c1 100644 --- a/samples/jsrender/composition/tmpl/sample.html +++ b/samples/jsrender/composition/tmpl/sample.html @@ -1,7 +1,7 @@ - + diff --git a/samples/jsrender/converters/sample.html b/samples/jsrender/converters/sample.html index b58dfd00..fe6e103d 100644 --- a/samples/jsrender/converters/sample.html +++ b/samples/jsrender/converters/sample.html @@ -1,7 +1,7 @@ - + @@ -11,7 +11,7 @@
        SynopsisFixed Template Template specified in dataConditional Template
        - +
        Title (loc:English)Title (loc:French)No ConvertHTML EncodeNo ConvertHTML EncodePrice
        @@ -22,6 +22,7 @@ {{loc:title lang='FR'}} {{:synopsis}} {{>synopsis}} + ${{dec:ticketPrice}} diff --git a/samples/jsrender/converters/sample.js b/samples/jsrender/converters/sample.js index 521f685d..69191b84 100644 --- a/samples/jsrender/converters/sample.js +++ b/samples/jsrender/converters/sample.js @@ -3,17 +3,22 @@ var movies = [{ availability: "Available in 'X&Y' Cinemas", title: "Meet Joe Black", synopsis: "The grim reaper visits" - + "Bill Parrish..." + + "Bill Parrish...", + ticketPrice: 23.4 }, { availability: "Available at < 20kms from London", title: "Eyes Wide Shut", synopsis: "Director Stanley Kubrick's final film:" - + "

        " + + "

        ", + ticketPrice: 18 } ]; $.views.converters({ + dec: function(value) { + return value.toFixed(2); + }, loc: function(value) { var language = this.tagCtx.props.lang; var result = ""; diff --git a/samples/jsrender/helpers/sample.html b/samples/jsrender/helpers/sample.html index 40c4fe3d..6240abd7 100644 --- a/samples/jsrender/helpers/sample.html +++ b/samples/jsrender/helpers/sample.html @@ -1,7 +1,7 @@ - + @@ -18,19 +18,19 @@ - {{for #data}} + {{for}} {{!-- iterate over movies --}} {{>~format(title)}} - {{sort languages reverse=~reverseSort}} + {{for languages reverse=~reverse}}
        {{>name}}
        - {{/sort}} + {{/for}} {{/for}} diff --git a/samples/jsrender/helpers/sample.js b/samples/jsrender/helpers/sample.js index 4df32b24..45dc3ecd 100644 --- a/samples/jsrender/helpers/sample.js +++ b/samples/jsrender/helpers/sample.js @@ -1,23 +1,4 @@ "use strict"; -$.views.tags({ - - // Tag to reverse-sort an array - sort: function(array) { - var ret = ""; - if (this.tagCtx.props.reverse) { - // Render in reverse order - for (var i = array.length; i; i--) { - ret += this.tagCtx.render(array[i - 1]); - } - } else { - // Render in original order - ret += this.tagCtx.render(array); - } - return ret; - } - -}); - var reverse = false, upperCase = false, movies = [ @@ -55,18 +36,19 @@ $("#movieList") function renderList() { var html = $("#movieTemplate").render( // Wrap movies array in an array, to render as a layout with header and footer - [movies], + movies, // pass in helpers { - reverseSort: reverse, + reverse: reverse, format: myFormatFunction, buttonCaption: function(val) { - if (val === 'sort') { - return reverse ? "Sort increasing" : "Sort decreasing"; + if (val === 'direction') { + return reverse ? "increasing" : "decreasing"; } return upperCase ? "to lower" : "to upper"; } - } + }, + true // noIteration ); $("#movieList").html(html); diff --git a/samples/jsrender/paths/sample.html b/samples/jsrender/paths/sample.html index f63d06d5..c79ebfc7 100644 --- a/samples/jsrender/paths/sample.html +++ b/samples/jsrender/paths/sample.html @@ -1,7 +1,7 @@ - + diff --git a/samples/jsrender/tags/extend-for/sample.css b/samples/jsrender/tags/extend-for/sample.css index 287ced88..943e343e 100644 --- a/samples/jsrender/tags/extend-for/sample.css +++ b/samples/jsrender/tags/extend-for/sample.css @@ -1,5 +1,40 @@ body { font-family: 'Helvetica Neue', HelveticaNeue, Helvetica, Arial, sans-serif; - font-size: 15px; + font-size: 14px; padding: 0 0 0 5px; + color: #172e40; } + +table tr { + color: #2b587a; + height: 25px; +} + +table { + border-collapse: collapse; + border: 2px solid #7c94a5; + padding: 2px; + background-color: #fbfdfe; + width: 500px; + margin: 10px 0; +} + + +table.nowidth, table.nowidth input { + width: auto; +} + +th { + padding: 3px 6px; + border: 1px solid #7c94a5; + background-color: #edf0f3; +} + +table td { + padding: 3px 6px; + border: solid #93a0a9 1px; +} + +.total { + background-color: #f2f7f7; +} \ No newline at end of file diff --git a/samples/jsrender/tags/extend-for/sample.html b/samples/jsrender/tags/extend-for/sample.html index ceef53d7..6bb7e292 100644 --- a/samples/jsrender/tags/extend-for/sample.html +++ b/samples/jsrender/tags/extend-for/sample.html @@ -1,74 +1,96 @@ - + - -
        + + +
        diff --git a/samples/jsrender/tags/extend-for/sample.js b/samples/jsrender/tags/extend-for/sample.js index 5758a6f6..a67fb928 100644 --- a/samples/jsrender/tags/extend-for/sample.js +++ b/samples/jsrender/tags/extend-for/sample.js @@ -1,13 +1,40 @@ "use strict"; -var team = { - members: [ - {name: "Robert"}, - {name: "Sarah"}, - {name: "Xavier"}, - {name: "Adriana"} +$.views.converters("dec2", function(val) { + return val.toFixed(2); +}) + +function categoryFilter(item, index, items) { + return this.props.category === item.category; +} + +$.views.tags("purchases", { + baseTag: "for", // Inherit from the {{for}} tag + ctx: { + total: function(expr) { // A ~total(expression) helper + var tmpl = $.templates[expr] // Get named compiled template for expression, or else... + || $.templates(expr, "{{:" + expr + "}}"), // ...if this is first call, create it + + runningTotal = 0, + view = this, // The content view of the ~total(...) helper call + items = view.get("array").data, + rowIndex = view.getIndex(); + + for (var i = 0; i <= rowIndex; i++) { + runningTotal += +tmpl(items[i]); // Compute running total up to this row, using render function + } // of compiled tmpl (either tmpl() or tmpl.render()...) + return runningTotal; // Return value from ~total(...) + } + } +}); + +var purchases = { + lineItems: [ + {category: "book", quantity: 2, price: 3.40}, + {category: "grocery", quantity: 5, price: 1.01}, + {category: "grocery", quantity: 2, price: 13.10}, + {category: "book", quantity: 1, price: 12.50} ] }; +var html = $("#myTmpl").render(purchases, {category: categoryFilter}); -var html = $("#teamTemplate").render(team); - -$("#team").html(html); +$("#purchases").html(html); \ No newline at end of file diff --git a/samples/jsrender/tags/wrap-content/sample.html b/samples/jsrender/tags/wrap-content/sample.html index 5f779387..69b9d624 100644 --- a/samples/jsrender/tags/wrap-content/sample.html +++ b/samples/jsrender/tags/wrap-content/sample.html @@ -1,7 +1,7 @@ - + diff --git a/samples/samples.css b/samples/samples.css index b02cc237..15e3a403 100644 --- a/samples/samples.css +++ b/samples/samples.css @@ -1,7 +1,8 @@ body { font-family: 'Helvetica Neue', HelveticaNeue, Helvetica, Arial, sans-serif; - font-size: 15px; + font-size: 14px; padding: 0 0 0 5px; + color: #172e40; } label { @@ -10,15 +11,15 @@ label { } table tr { - color: blue; + color: #2b587a; height: 25px; } table { border-collapse: collapse; - border: 2px solid blue; + border: 2px solid #7c94a5; padding: 2px; - background-color: #f8f8f8; + background-color: #fbfdfe; width: 500px; } @@ -26,27 +27,23 @@ table.nowidth, table.nowidth input { width: auto; } -thead tr { - color: #009; - border-bottom: solid #77c 2px; - background-color: #E8E8F7; -} - -thead th { - padding: 5px; - border: 1px solid #77c; +th { + padding: 3px 6px; + border: 1px solid #7c94a5; + background-color: #edf0f3; } table td { - padding: 3px; - border: solid #77c 1px; + padding: 3px 6px; + border: solid #93a0a9 1px; } table input { width: 94%; - border: 1px solid gray; + border: 1px solid #637e92; padding: 4px; height: 15px; + color: #172e40 } .floatleft { @@ -59,14 +56,14 @@ td span { } td label { - color: black; + color: #172e40; width: 48%; display: inline-block; } td label input { width: 55%; - color: black; + color: #172e40; margin-left: 10px; } @@ -87,7 +84,11 @@ td.center { button { cursor: pointer; - margin-bottom: 3px; + margin: 2px 2px 6px 2px; +} + +button:not([disabled]) { + color: #172e40; } .left { @@ -138,3 +139,7 @@ li .remove { pre { font-size: 12px; } + +option { + padding-left: 4px; +} \ No newline at end of file diff --git a/samples/tag-controls/areaslider/sample.html b/samples/tag-controls/areaslider/sample.html new file mode 100644 index 00000000..1bb746a2 --- /dev/null +++ b/samples/tag-controls/areaslider/sample.html @@ -0,0 +1,27 @@ + + + + + + + + + + + + + +
        + + + diff --git a/samples/tag-controls/areaslider/sample.js b/samples/tag-controls/areaslider/sample.js new file mode 100644 index 00000000..c889e7f4 --- /dev/null +++ b/samples/tag-controls/areaslider/sample.js @@ -0,0 +1,5 @@ +"use strict"; +var myTmpl = $.templates("#myTmpl"), + data = {x: 30, y: 10}; + +myTmpl.link("#page", data); \ No newline at end of file diff --git a/samples/tag-controls/colorpicker/colorpicker-multiformat.html b/samples/tag-controls/colorpicker/colorpicker-multiformat.html new file mode 100644 index 00000000..1b7c6448 --- /dev/null +++ b/samples/tag-controls/colorpicker/colorpicker-multiformat.html @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + +
        + + + diff --git a/samples/tag-controls/colorpicker/colorpicker-multiformat.js b/samples/tag-controls/colorpicker/colorpicker-multiformat.js new file mode 100644 index 00000000..41cc50fe --- /dev/null +++ b/samples/tag-controls/colorpicker/colorpicker-multiformat.js @@ -0,0 +1,8 @@ +"use strict"; +var myTmpl = $.templates("#myTmpl"); + +myTmpl.link('#page', { + color1: {h:194, s: 0.25, v: 0.9, a: 1}, + color2: {r:172, g: 216, b: 230, a: 1}, + color3: {hex:"#acd8e6"} +}); \ No newline at end of file diff --git a/samples/tag-controls/colorpicker/colorpicker-multiformat2.html b/samples/tag-controls/colorpicker/colorpicker-multiformat2.html new file mode 100644 index 00000000..9f7e7d20 --- /dev/null +++ b/samples/tag-controls/colorpicker/colorpicker-multiformat2.html @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + +
        + + + diff --git a/samples/tag-controls/colorpicker/colorpicker-multiformat2.js b/samples/tag-controls/colorpicker/colorpicker-multiformat2.js new file mode 100644 index 00000000..41cc50fe --- /dev/null +++ b/samples/tag-controls/colorpicker/colorpicker-multiformat2.js @@ -0,0 +1,8 @@ +"use strict"; +var myTmpl = $.templates("#myTmpl"); + +myTmpl.link('#page', { + color1: {h:194, s: 0.25, v: 0.9, a: 1}, + color2: {r:172, g: 216, b: 230, a: 1}, + color3: {hex:"#acd8e6"} +}); \ No newline at end of file diff --git a/samples/tag-controls/colorpicker/colorpicker.html b/samples/tag-controls/colorpicker/colorpicker.html new file mode 100644 index 00000000..f78a2ae8 --- /dev/null +++ b/samples/tag-controls/colorpicker/colorpicker.html @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + +
        + + + diff --git a/samples/tag-controls/colorpicker/colorpicker.js b/samples/tag-controls/colorpicker/colorpicker.js new file mode 100644 index 00000000..735c7fb7 --- /dev/null +++ b/samples/tag-controls/colorpicker/colorpicker.js @@ -0,0 +1,6 @@ +"use strict"; +var myTmpl = $.templates("#myTmpl"); + +myTmpl.link('#page', { + color: {h:194, s: 0.25, v: 0.9, a: 1} +}); \ No newline at end of file diff --git a/samples/tag-controls/jqui/accordion/collapsible.html b/samples/tag-controls/jqui/accordion/collapsible.html index 6208569e..113a633d 100644 --- a/samples/tag-controls/jqui/accordion/collapsible.html +++ b/samples/tag-controls/jqui/accordion/collapsible.html @@ -1,7 +1,7 @@  - + diff --git a/samples/tag-controls/jqui/accordion/sortable.html b/samples/tag-controls/jqui/accordion/sortable.html index 14c61f2f..ff36087a 100644 --- a/samples/tag-controls/jqui/accordion/sortable.html +++ b/samples/tag-controls/jqui/accordion/sortable.html @@ -1,7 +1,7 @@  - + diff --git a/samples/tag-controls/jqui/accordion/sortablearray-toplevel.html b/samples/tag-controls/jqui/accordion/sortablearray-toplevel.html index 8c4fc3c2..0488e72d 100644 --- a/samples/tag-controls/jqui/accordion/sortablearray-toplevel.html +++ b/samples/tag-controls/jqui/accordion/sortablearray-toplevel.html @@ -1,7 +1,7 @@  - + @@ -19,8 +19,8 @@ diff --git a/samples/tag-controls/jqui/accordion/sortablearray.html b/samples/tag-controls/jqui/accordion/sortablearray.html index a92f0650..64e7b4f8 100644 --- a/samples/tag-controls/jqui/accordion/sortablearray.html +++ b/samples/tag-controls/jqui/accordion/sortablearray.html @@ -1,7 +1,7 @@  - + @@ -25,8 +25,8 @@

        Drag headers to sort...

        {^{sortable elem='div' _axis='y' _handle='h3'}} {^{for panelData}}
        -

        {^{:header}}

        -
        {^{:content}}
        +

        {^{>header}}

        +
        {^{>content}}
        {{/for}} {{/sortable}} @@ -48,7 +48,7 @@

        Panel data (edit or sort):

        {^{if ~root.selectedPanel===#index}} (selected) {{/if}} - + {{/for}} {{/sortable}} diff --git a/samples/tag-controls/jqui/autocomplete/variants.html b/samples/tag-controls/jqui/autocomplete/variants.html index fe81594c..48ed731f 100644 --- a/samples/tag-controls/jqui/autocomplete/variants.html +++ b/samples/tag-controls/jqui/autocomplete/variants.html @@ -1,7 +1,7 @@  - + @@ -60,7 +60,7 @@

        Tag syntax, wrapping contenteditable div element:

        {^{/autocomplete}} -{^{autocomplete val _source=~suggestionList}} +{^{autocomplete val _source=~suggestionList convert='encode' convertBack='unencode'}}
        {{/autocomplete}} @@ -86,7 +86,8 @@

        Element-based data-link syntax (contenteditable div)

        <div contenteditable="true" ... data-link="{autocomplete val ...}"></div> -
        +

        Regular input tag without autocomplete

        diff --git a/samples/tag-controls/jqui/datepicker/date-formats/basic.html b/samples/tag-controls/jqui/datepicker/date-formats/basic.html index 044fe9ff..e80d7493 100644 --- a/samples/tag-controls/jqui/datepicker/date-formats/basic.html +++ b/samples/tag-controls/jqui/datepicker/date-formats/basic.html @@ -1,7 +1,7 @@  - + diff --git a/samples/tag-controls/jqui/datepicker/date-formats/custom.html b/samples/tag-controls/jqui/datepicker/date-formats/custom.html index cbea727a..f46a8d76 100644 --- a/samples/tag-controls/jqui/datepicker/date-formats/custom.html +++ b/samples/tag-controls/jqui/datepicker/date-formats/custom.html @@ -1,7 +1,7 @@  - + diff --git a/samples/tag-controls/jqui/datepicker/simple/sample.html b/samples/tag-controls/jqui/datepicker/simple/sample.html index d106927b..20311377 100644 --- a/samples/tag-controls/jqui/datepicker/simple/sample.html +++ b/samples/tag-controls/jqui/datepicker/simple/sample.html @@ -1,7 +1,7 @@  - + diff --git a/samples/tag-controls/jqui/datepicker/variants/sample.html b/samples/tag-controls/jqui/datepicker/variants/sample.html index c0ddba16..621faa6c 100644 --- a/samples/tag-controls/jqui/datepicker/variants/sample.html +++ b/samples/tag-controls/jqui/datepicker/variants/sample.html @@ -1,7 +1,7 @@  - + diff --git a/samples/tag-controls/jqui/datepicker/with-validation-wizard/sample.html b/samples/tag-controls/jqui/datepicker/with-validation-wizard/sample.html index 277d8664..61c7302e 100644 --- a/samples/tag-controls/jqui/datepicker/with-validation-wizard/sample.html +++ b/samples/tag-controls/jqui/datepicker/with-validation-wizard/sample.html @@ -1,7 +1,7 @@  - + diff --git a/samples/tag-controls/jqui/datepicker/with-validation/sample.html b/samples/tag-controls/jqui/datepicker/with-validation/sample.html index cd5dd9d4..c822da98 100644 --- a/samples/tag-controls/jqui/datepicker/with-validation/sample.html +++ b/samples/tag-controls/jqui/datepicker/with-validation/sample.html @@ -1,7 +1,7 @@  - + diff --git a/samples/tag-controls/jqui/draggable-droppable/draggable.html b/samples/tag-controls/jqui/draggable-droppable/draggable.html index e17a1113..dba290c5 100644 --- a/samples/tag-controls/jqui/draggable-droppable/draggable.html +++ b/samples/tag-controls/jqui/draggable-droppable/draggable.html @@ -1,7 +1,7 @@  - + diff --git a/samples/tag-controls/jqui/draggable-droppable/draggable2.html b/samples/tag-controls/jqui/draggable-droppable/draggable2.html index 9b8b861a..9d1bfdd1 100644 --- a/samples/tag-controls/jqui/draggable-droppable/draggable2.html +++ b/samples/tag-controls/jqui/draggable-droppable/draggable2.html @@ -1,7 +1,7 @@  - + diff --git a/samples/tag-controls/jqui/draggable-droppable/photomanager.html b/samples/tag-controls/jqui/draggable-droppable/photomanager.html index 0e1d6de3..e881b150 100644 --- a/samples/tag-controls/jqui/draggable-droppable/photomanager.html +++ b/samples/tag-controls/jqui/draggable-droppable/photomanager.html @@ -1,7 +1,7 @@  - + @@ -41,7 +41,7 @@

        JsViews 'draggable' and 'droppable' tag controls - using jQuery UI widgets{{:title}}

        - {{:description}} + {{:description}} View larger Trash Tr {^{draggable _cancel="a.ui-icon" _revert="invalid" _containment="document" _helper="clone" _cursor="move" elem="li" class="ui-widget-content ui-corner-tr"}}
        {{:title}}
        - {{:description}} + {{:description}}
        View larger - + diff --git a/samples/tag-controls/jqui/progressbar/variants.html b/samples/tag-controls/jqui/progressbar/variants.html index 4bc8df54..b6e4a369 100644 --- a/samples/tag-controls/jqui/progressbar/variants.html +++ b/samples/tag-controls/jqui/progressbar/variants.html @@ -1,7 +1,7 @@  - + diff --git a/samples/tag-controls/jqui/resizable/grid.html b/samples/tag-controls/jqui/resizable/grid.html index caf12998..9d9ff6c9 100644 --- a/samples/tag-controls/jqui/resizable/grid.html +++ b/samples/tag-controls/jqui/resizable/grid.html @@ -1,7 +1,7 @@  - + diff --git a/samples/tag-controls/jqui/resizable/grid2.html b/samples/tag-controls/jqui/resizable/grid2.html index 8f5396ef..43a3130c 100644 --- a/samples/tag-controls/jqui/resizable/grid2.html +++ b/samples/tag-controls/jqui/resizable/grid2.html @@ -1,7 +1,7 @@  - + diff --git a/samples/tag-controls/jqui/resizable/grid3.html b/samples/tag-controls/jqui/resizable/grid3.html index 6f8e22d1..6abbc552 100644 --- a/samples/tag-controls/jqui/resizable/grid3.html +++ b/samples/tag-controls/jqui/resizable/grid3.html @@ -1,7 +1,7 @@  - + diff --git a/samples/tag-controls/jqui/selectable/sortablearray-toplevel.html b/samples/tag-controls/jqui/selectable/sortablearray-toplevel.html index ac8f07fa..b7d095c4 100644 --- a/samples/tag-controls/jqui/selectable/sortablearray-toplevel.html +++ b/samples/tag-controls/jqui/selectable/sortablearray-toplevel.html @@ -1,7 +1,7 @@  - + @@ -35,8 +35,8 @@ diff --git a/samples/tag-controls/jqui/selectable/sortablearray.html b/samples/tag-controls/jqui/selectable/sortablearray.html index 6e9b6d18..dda5012c 100644 --- a/samples/tag-controls/jqui/selectable/sortablearray.html +++ b/samples/tag-controls/jqui/selectable/sortablearray.html @@ -1,7 +1,7 @@  - + @@ -74,12 +74,13 @@

        Selected people

        {^{for selectedPeople()}}
        Index: {^{:index}}
        -
        First name: {^{:person.name}}
        -
        Last name: {^{:person.lastName}}
        +
        First name: {^{>person.name}}
        +
        Last name: {^{>person.lastName}}
        {{/for}} {^{on insert}}Add item{{/on}} +{^{on swap}}Swap{{/on}}
        diff --git a/samples/tag-controls/jqui/selectable/sortablearray.js b/samples/tag-controls/jqui/selectable/sortablearray.js index 47cf1757..48c2dcbb 100644 --- a/samples/tag-controls/jqui/selectable/sortablearray.js +++ b/samples/tag-controls/jqui/selectable/sortablearray.js @@ -1,4 +1,6 @@ "use strict"; +var items1 = [1,3], + items2 = [0,2,4]; var cnt = 5, pageTmpl = $.templates("#pageTmpl"), model = { @@ -21,6 +23,9 @@ var cnt = 5, lastName: "lastName " + cnt }); }, + swap: function() { + $.observable(this).setProperty("selectedItems", this.selectedItems===items1 ? items2 : items1); + }, remove: function(index) { $.observable(this.people).remove(index); }, @@ -31,7 +36,7 @@ var cnt = 5, {name: "Mara", lastName: "May"}, {name: "Mando", lastName: "Mechy"} ], - selectedItems: [1,3] + selectedItems: items1 }; model.selectedPeople.depends = ["people", "selectedItems"]; diff --git a/samples/tag-controls/jqui/selectmenu/product-selection.html b/samples/tag-controls/jqui/selectmenu/product-selection.html index 860326a4..4fff4cf5 100644 --- a/samples/tag-controls/jqui/selectmenu/product-selection.html +++ b/samples/tag-controls/jqui/selectmenu/product-selection.html @@ -1,7 +1,7 @@  - + diff --git a/samples/tag-controls/jqui/slider/colorpicker/sample.html b/samples/tag-controls/jqui/slider/colorpicker/sample.html index c9b2703c..42343e00 100644 --- a/samples/tag-controls/jqui/slider/colorpicker/sample.html +++ b/samples/tag-controls/jqui/slider/colorpicker/sample.html @@ -1,7 +1,7 @@  - + diff --git a/samples/tag-controls/jqui/slider/simple-toplevel/sample.html b/samples/tag-controls/jqui/slider/simple-toplevel/sample.html index 7c0bc74c..4949bfe9 100644 --- a/samples/tag-controls/jqui/slider/simple-toplevel/sample.html +++ b/samples/tag-controls/jqui/slider/simple-toplevel/sample.html @@ -1,7 +1,7 @@  - + diff --git a/samples/tag-controls/jqui/slider/simple/sample.html b/samples/tag-controls/jqui/slider/simple/sample.html index dd92cfba..1fd5d0e2 100644 --- a/samples/tag-controls/jqui/slider/simple/sample.html +++ b/samples/tag-controls/jqui/slider/simple/sample.html @@ -1,7 +1,7 @@  - + diff --git a/samples/tag-controls/jqui/slider/variants/sample.html b/samples/tag-controls/jqui/slider/variants/sample.html index 8e9e61d0..9658962b 100644 --- a/samples/tag-controls/jqui/slider/variants/sample.html +++ b/samples/tag-controls/jqui/slider/variants/sample.html @@ -1,7 +1,7 @@  - + diff --git a/samples/tag-controls/jqui/slider/with-validation/sample.html b/samples/tag-controls/jqui/slider/with-validation/sample.html index bf8fcae8..08d25e01 100644 --- a/samples/tag-controls/jqui/slider/with-validation/sample.html +++ b/samples/tag-controls/jqui/slider/with-validation/sample.html @@ -1,7 +1,7 @@  - + @@ -16,7 +16,7 @@

        JsViews 'slider' tag control (using jQuery UI slider) with validation

        + diff --git a/samples/tag-controls/jqui/spinner/accounting.html b/samples/tag-controls/jqui/spinner/accounting.html index c6d217a4..c812d617 100644 --- a/samples/tag-controls/jqui/spinner/accounting.html +++ b/samples/tag-controls/jqui/spinner/accounting.html @@ -1,7 +1,7 @@  - + diff --git a/samples/tag-controls/jqui/spinner/accounting.js b/samples/tag-controls/jqui/spinner/accounting.js index 590aa33d..357aabf0 100644 --- a/samples/tag-controls/jqui/spinner/accounting.js +++ b/samples/tag-controls/jqui/spinner/accounting.js @@ -6,35 +6,37 @@ var accountingCulture = { var formatters = { - // Currency formatter using accounting.js + // Display formatter for currency, using accounting.js: + // amount (number) to currency string currency: { - format: function(value, props) { - return accounting.formatMoney( - value, - accountingCulture[props._culture] - ); - }, - parse: function(value, props) { + parse: function(string, props) { return accounting.unformat( - value, + string, accountingCulture[props._culture].decimal ); + }, + format: function(amount, props) { + return accounting.formatMoney( + amount, + accountingCulture[props._culture] + ); } }, - // Number formatter using accounting.js + // Display formatter for numbers, using accounting.js: + //amount (number) to string number: { - format: function(value, props) { - return accounting.formatNumber( - value, - accountingCulture[props._culture] - ); - }, - parse: function(value, props) { + parse: function(string, props) { return accounting.unformat( - value, + string, accountingCulture[props._culture].decimal ); + }, + format: function(number, props) { + return accounting.formatNumber( + number, + accountingCulture[props._culture] + ); } } }; diff --git a/samples/tag-controls/jqui/spinner/dataformat.html b/samples/tag-controls/jqui/spinner/dataformat.html index c627a1d2..b4b3d46d 100644 --- a/samples/tag-controls/jqui/spinner/dataformat.html +++ b/samples/tag-controls/jqui/spinner/dataformat.html @@ -1,7 +1,7 @@  - + @@ -26,7 +26,7 @@

        Choose culture:

        {^{:date}}

        -

        displayFormat=~time dataFormat=~dateToNumber
        +
        displayFormat=~time dataFormat=~numberToDate
        {^{spinner date ^_culture=culture _step=60000 _page=60 displayFormat=~time dataFormat=~dateToNumber width=100 /}}

        diff --git a/samples/tag-controls/jqui/spinner/dataformat.js b/samples/tag-controls/jqui/spinner/dataformat.js index d80549cc..32d4f7ff 100644 --- a/samples/tag-controls/jqui/spinner/dataformat.js +++ b/samples/tag-controls/jqui/spinner/dataformat.js @@ -1,34 +1,35 @@ "use strict"; var helpers = { - // Time formatter using moment.js + // Display formatter for timestamp (ticks) using moment.js: + // ticks to time string time: { - parse: function(value, props) { + parse: function(string, props) { var format = props._culture === "en-US" ? "h:mm A" : "HH:mm"; - return moment(value, format).toDate(); + return +moment(string, format).toDate(); }, - format: function(value, props) { + format: function(ticks, props) { var format = props._culture === "en-US" ? "h:mm A" : "HH:mm"; - return moment(value).format(format); + return moment(ticks).format(format); } }, - // Date to number formatter - dateToNumber: { - parse: function(value, props) { - return +value; + // Data formatter: ticks to Date + numberToDate: { + parse: function(date, props) { + return +date; }, - format: function(value, props) { - return new Date(value); + format: function(ticks, props) { + return new Date(ticks); } }, // Converters - toNumber: function(value, props) { - return +value; + toNumber: function(date, props) { + return +date; }, - toDate: function(value, props) { - return new Date(value); + toDate: function(ticks, props) { + return new Date(ticks); } }; diff --git a/samples/tag-controls/jqui/spinner/globalize.html b/samples/tag-controls/jqui/spinner/globalize.html index b56e16d6..978b8c04 100644 --- a/samples/tag-controls/jqui/spinner/globalize.html +++ b/samples/tag-controls/jqui/spinner/globalize.html @@ -1,7 +1,7 @@  - + diff --git a/samples/tag-controls/jqui/spinner/moment.html b/samples/tag-controls/jqui/spinner/moment.html index d13afd50..e29b506a 100644 --- a/samples/tag-controls/jqui/spinner/moment.html +++ b/samples/tag-controls/jqui/spinner/moment.html @@ -1,7 +1,7 @@  - + diff --git a/samples/tag-controls/jqui/spinner/moment.js b/samples/tag-controls/jqui/spinner/moment.js index 67ac1a20..0879e1a9 100644 --- a/samples/tag-controls/jqui/spinner/moment.js +++ b/samples/tag-controls/jqui/spinner/moment.js @@ -1,15 +1,16 @@ "use strict"; var formatters = { - // Time formatter using moment.js + // Display formatter for timestamp (ticks) using moment.js: + // ticks to time string time: { - parse: function(value, props) { + parse: function(string, props) { var format = props._culture === "en-US" ? "h:mm A" : "HH:mm"; - return moment(value, format).toDate(); + return +moment(string, format).toDate(); }, - format: function(value, props) { + format: function(ticks, props) { var format = props._culture === "en-US" ? "h:mm A" : "HH:mm"; - return moment(value).format(format); + return moment(ticks).format(format); } } diff --git a/samples/tag-controls/jqui/spinner/sample.html b/samples/tag-controls/jqui/spinner/sample.html index 61f76d6c..ba528c0b 100644 --- a/samples/tag-controls/jqui/spinner/sample.html +++ b/samples/tag-controls/jqui/spinner/sample.html @@ -1,7 +1,7 @@  - + diff --git a/samples/tag-controls/jqui/tabs/collapsible.html b/samples/tag-controls/jqui/tabs/collapsible.html index b85fe414..af5a3d90 100644 --- a/samples/tag-controls/jqui/tabs/collapsible.html +++ b/samples/tag-controls/jqui/tabs/collapsible.html @@ -1,7 +1,7 @@  - + diff --git a/samples/tag-controls/jqui/tabs/sortable.html b/samples/tag-controls/jqui/tabs/sortable.html index 8bf3674b..b77af96f 100644 --- a/samples/tag-controls/jqui/tabs/sortable.html +++ b/samples/tag-controls/jqui/tabs/sortable.html @@ -1,7 +1,7 @@  - + diff --git a/samples/tag-controls/jqui/tabs/sortablearray-toplevel.html b/samples/tag-controls/jqui/tabs/sortablearray-toplevel.html index a5bfa22c..f7599f6d 100644 --- a/samples/tag-controls/jqui/tabs/sortablearray-toplevel.html +++ b/samples/tag-controls/jqui/tabs/sortablearray-toplevel.html @@ -1,7 +1,7 @@  - + @@ -19,10 +19,10 @@ diff --git a/samples/tag-controls/jqui/tabs/sortablearray.html b/samples/tag-controls/jqui/tabs/sortablearray.html index 8a907474..0be627af 100644 --- a/samples/tag-controls/jqui/tabs/sortablearray.html +++ b/samples/tag-controls/jqui/tabs/sortablearray.html @@ -1,7 +1,7 @@  - + @@ -23,10 +23,10 @@

        Drag tabs to sort...

        {^{tabs selectedTab}} {^{sortable elem="ul" _axis="x"}} - {^{for tabData}}
      16. {^{:header}}
      17. {{/for}} + {^{for tabData}}
      18. {^{>header}}
      19. {{/for}} {{/sortable}} {^{for tabData}} -
        {^{:content}}
        +
        {^{>content}}
        {{/for}} {{/tabs}} diff --git a/samples/tag-controls/jqui/timespinner/dataformat.html b/samples/tag-controls/jqui/timespinner/dataformat.html index fb94b58d..2201a3c8 100644 --- a/samples/tag-controls/jqui/timespinner/dataformat.html +++ b/samples/tag-controls/jqui/timespinner/dataformat.html @@ -1,7 +1,7 @@  - + diff --git a/samples/tag-controls/jqui/timespinner/dataformat.js b/samples/tag-controls/jqui/timespinner/dataformat.js index ce6081fe..11e5e1e2 100644 --- a/samples/tag-controls/jqui/timespinner/dataformat.js +++ b/samples/tag-controls/jqui/timespinner/dataformat.js @@ -1,6 +1,6 @@ "use strict"; var helpers = { - // Data formatter using moment.js - for wcfDate serialization + // Data formatter for Date using moment.js: Date to wcfDate string wcfDate: { parse: function(wcfString, props) { return moment(wcfString, "/\\D\\at\\e(xZZ)/").toDate(); @@ -10,7 +10,8 @@ var helpers = { } }, - // Data formatter using moment.js - using custom full date/time string + // Data formatter for Date using moment.js: + // Date to custom date/time string fullDate: { parse: function(dateTimeString, props) { return moment(dateTimeString, "YYYY-MM-DD@HH:mm").toDate(); @@ -20,7 +21,8 @@ var helpers = { } }, - // Time display formatter (culture-based) using moment.js + // Display formatter for Date using moment.js: + // Date to time string time: { parse: function(timeString, props) { var format = props._culture === "en-US" ? "h:mm A" : "HH:mm"; @@ -32,16 +34,6 @@ var helpers = { } }, - // Date to number formatter - dateToNumber: { - parse: function(value, props) { - return +value; - }, - format: function(value, props) { - return new Date(value); - } - }, - // Converters for shifting time add: function(data, props) { var dt = this.dataFormat.parse(data); diff --git a/samples/tag-controls/jqui/timespinner/globalize.html b/samples/tag-controls/jqui/timespinner/globalize.html index 2b4ae4f6..4bfb72ed 100644 --- a/samples/tag-controls/jqui/timespinner/globalize.html +++ b/samples/tag-controls/jqui/timespinner/globalize.html @@ -1,7 +1,7 @@  - + diff --git a/samples/tag-controls/jqui/timespinner/moment.html b/samples/tag-controls/jqui/timespinner/moment.html index b0500793..173e2b30 100644 --- a/samples/tag-controls/jqui/timespinner/moment.html +++ b/samples/tag-controls/jqui/timespinner/moment.html @@ -1,7 +1,7 @@  - + diff --git a/samples/tag-controls/jqui/timespinner/moment.js b/samples/tag-controls/jqui/timespinner/moment.js index e660aa13..40396381 100644 --- a/samples/tag-controls/jqui/timespinner/moment.js +++ b/samples/tag-controls/jqui/timespinner/moment.js @@ -1,15 +1,16 @@ "use strict"; var formatters = { - // Time formatter using moment.js + // Display formatter for Date using moment.js: + // Date to time string time: { - parse: function(value, props) { + parse: function(timeString, props) { var format = props._culture === "en-US" ? "h:mm A" : "HH:mm"; - return moment(value, format).toDate(); + return moment(timeString, format).toDate(); }, - format: function(value, props) { + format: function(date, props) { var format = props._culture === "en-US" ? "h:mm A" : "HH:mm"; - return moment(value).format(format); + return moment(date).format(format); } } diff --git a/samples/tag-controls/jqui/toolbar/toolbar.html b/samples/tag-controls/jqui/toolbar/toolbar.html index 2e08dee6..b26d6dbb 100644 --- a/samples/tag-controls/jqui/toolbar/toolbar.html +++ b/samples/tag-controls/jqui/toolbar/toolbar.html @@ -1,7 +1,7 @@  - + diff --git a/samples/tag-controls/jqui/toolbar/toolbararray.html b/samples/tag-controls/jqui/toolbar/toolbararray.html index 611f13ee..2d2e858f 100644 --- a/samples/tag-controls/jqui/toolbar/toolbararray.html +++ b/samples/tag-controls/jqui/toolbar/toolbararray.html @@ -1,7 +1,7 @@  - + @@ -38,6 +38,7 @@ {{/on}} {{/controlgroup}} {^{checkbox reverse id="reverseChkBx" label="Reverse"/}} + {^{checkbox reverse id="reverseChkBx" label="Reverse"/}} {^{controlgroup _classes=~myUiOverrides}} {^{radiogroup mode}} {^{for modes}} @@ -46,13 +47,13 @@ {{/radiogroup}} {{/controlgroup}} {^{controlgroup onBind=~onbind}} + {^{on addSpeed}} + {^{button _label="+"/}} + {{/on}} + {^{on removeSpeed}} + {^{button _label="-"/}} + {{/on}} {^{radiogroup speed}} - {^{on addSpeed}} - {^{button _label="+"/}} - {{/on}} - {^{on removeSpeed}} - {^{button _label="-"/}} - {{/on}} {^{for speeds}} {^{radio label=label value=speedFactor/}} {{/for}} diff --git a/samples/tag-controls/jsonview/sample.html b/samples/tag-controls/jsonview/sample.html index 801998a0..83ceffd4 100644 --- a/samples/tag-controls/jsonview/sample.html +++ b/samples/tag-controls/jsonview/sample.html @@ -1,7 +1,7 @@ - + @@ -33,7 +33,7 @@
        - Data:
        {^{jsonview/}} + Data:
        {^{jsonview noFunctions=true/}}
        diff --git a/samples/tag-controls/multiselect/sample.html b/samples/tag-controls/multiselect/sample.html index 457f5ea0..39bb36a5 100644 --- a/samples/tag-controls/multiselect/sample.html +++ b/samples/tag-controls/multiselect/sample.html @@ -1,7 +1,7 @@  - + diff --git a/samples/tag-controls/purchases/images/down.png b/samples/tag-controls/purchases/images/down.png new file mode 100644 index 0000000000000000000000000000000000000000..187b0519d32a34d1f40321412ca0d815987ad555 GIT binary patch literal 261 zcmeAS@N?(olHy`uVBq!ia0vp^>_E)T!3HEZPnf;}NHG=%xjQkeJ16rJ$YDu$^mSxl z*x1kgCy^D%=PdAuEM{QfI}E~%$MaXD00kvVTq8 z4D^hCKU{tcsHV)*#W6%9IQQH}&ISV!mJ916EGB%@)YVzcojIQ|MkgUN@u9%Sx+0}I z8w;m*f*ogy?_B(HIkwO+`JKtp(2biHe6{ijF0}4kj3+TzK$^$13O;+g^?; zvn4`2(uI$|us!yW`8czynDdyJ?bjz?coQn(JMZ*}l`Uf6YwZHMfWgz%&t;ucLK6U$ CEnSKL literal 0 HcmV?d00001 diff --git a/samples/tag-controls/purchases/images/up.png b/samples/tag-controls/purchases/images/up.png new file mode 100644 index 0000000000000000000000000000000000000000..52fd2bcddeee53ae52d2218bf238f8d785a7a2b6 GIT binary patch literal 259 zcmeAS@N?(olHy`uVBq!ia0vp^>_E)T!3HEZPnf;}NHG=%xjQkeJ16rJ$YDu$^mSxl z*x1kgCy^D%=PdAuEM{QfI}E~%$MaXD00kvVTq8 z4D^hCKU{tcsHViz#W6%9IQ5(%UxR`Gi-U=Xokz77xAz0*Tb~*1-JCoVA1Z&~yOway z*G#-Tsp;3T-#fHI*=oKC_+07MUnVDIp7rqe@7c$ME0VGnItJ$}#PhDoV94iPkt87D yAiDX0PrsC88spY$$65=gA1Kw6>+y8T_TfKjRkkQ&zndk{eg;ohKbLh*2~7Z=x>U0O literal 0 HcmV?d00001 diff --git a/samples/tag-controls/purchases/sample-jsr.html b/samples/tag-controls/purchases/sample-jsr.html new file mode 100644 index 00000000..b140c00c --- /dev/null +++ b/samples/tag-controls/purchases/sample-jsr.html @@ -0,0 +1,97 @@ + + + + + + + + + + +
        + + + + + + diff --git a/samples/tag-controls/purchases/sample-jsr.js b/samples/tag-controls/purchases/sample-jsr.js new file mode 100644 index 00000000..a67fb928 --- /dev/null +++ b/samples/tag-controls/purchases/sample-jsr.js @@ -0,0 +1,40 @@ +"use strict"; +$.views.converters("dec2", function(val) { + return val.toFixed(2); +}) + +function categoryFilter(item, index, items) { + return this.props.category === item.category; +} + +$.views.tags("purchases", { + baseTag: "for", // Inherit from the {{for}} tag + ctx: { + total: function(expr) { // A ~total(expression) helper + var tmpl = $.templates[expr] // Get named compiled template for expression, or else... + || $.templates(expr, "{{:" + expr + "}}"), // ...if this is first call, create it + + runningTotal = 0, + view = this, // The content view of the ~total(...) helper call + items = view.get("array").data, + rowIndex = view.getIndex(); + + for (var i = 0; i <= rowIndex; i++) { + runningTotal += +tmpl(items[i]); // Compute running total up to this row, using render function + } // of compiled tmpl (either tmpl() or tmpl.render()...) + return runningTotal; // Return value from ~total(...) + } + } +}); + +var purchases = { + lineItems: [ + {category: "book", quantity: 2, price: 3.40}, + {category: "grocery", quantity: 5, price: 1.01}, + {category: "grocery", quantity: 2, price: 13.10}, + {category: "book", quantity: 1, price: 12.50} + ] +}; +var html = $("#myTmpl").render(purchases, {category: categoryFilter}); + +$("#purchases").html(html); \ No newline at end of file diff --git a/samples/tag-controls/purchases/sample-jsv.html b/samples/tag-controls/purchases/sample-jsv.html new file mode 100644 index 00000000..fb02ee0b --- /dev/null +++ b/samples/tag-controls/purchases/sample-jsv.html @@ -0,0 +1,47 @@ + + + + + + + + + + +
        + + + + + + diff --git a/samples/tag-controls/purchases/sample-jsv.js b/samples/tag-controls/purchases/sample-jsv.js new file mode 100644 index 00000000..3d081ce6 --- /dev/null +++ b/samples/tag-controls/purchases/sample-jsv.js @@ -0,0 +1,62 @@ +"use strict"; +$.views.converters("dec2", function(val) { + return val.toFixed(2); +}) + +function categoryFilter(item, index, items) { + return this.props.category === item.category; +} + +function total(expr) { // A ~total(expression) helper + var tmpl = $.templates[expr] // Get named compiled template for expression, or else... + || $.templates(expr, "{{:" + expr + "}}"), // ...if this is first call, create it + + runningTotal = 0, + view = this, // The content view of the ~total(...) helper call + items = view.get("array").data, + rowIndex = view.getIndex(); + + for (var i = 0; i <= rowIndex; i++) { + runningTotal += +tmpl(items[i]); // Compute running total up to this row, using render function + } // of compiled tmpl (either tmpl() or tmpl.render()...) + return runningTotal; // Return value from ~total(...) +} + +total.depends = "#parent.data.[].*"; + +$.views.tags("purchases", { + baseTag: "for", // Inherit from the {{for}} tag + ctx: {total: total} +}); + +var purchases = { + lineItems: [ + {category: "book", quantity: 2, price: 3.40}, + {category: "grocery", quantity: 5, price: 1.01}, + {category: "grocery", quantity: 2, price: 13.10}, + {category: "book", quantity: 1, price: 12.50} + ] +}; + +$.templates("#myTmpl").link("#purchases", purchases, { + sortBy: "category", + category: categoryFilter, + sortCol: function(sortBy, reverseSort, field, ev, eventArgs) { + if (ev.target.tagName === "INPUT") { + return; + } + var view = eventArgs.view; + + reverseSort = sortBy === field + ? !reverseSort + : false; + + sortBy = sortBy === field + ? reverseSort + ? sortBy + : false + : field; + view.ctxPrm("sortBy", sortBy) // NEED TO BRACKET THIS + .ctxPrm("reverseSort", reverseSort); + } + }); \ No newline at end of file diff --git a/samples/tag-controls/range/sample.css b/samples/tag-controls/range/sample.css deleted file mode 100644 index 89bbb9d7..00000000 --- a/samples/tag-controls/range/sample.css +++ /dev/null @@ -1,55 +0,0 @@ -body { - font-family: 'Helvetica Neue', HelveticaNeue, Helvetica, Arial, sans-serif; - font-size: 15px; - padding: 0 0 0 5px; -} - -.buttons { - margin-bottom: 15px; -} - -li { - margin-bottom: 10px; -} - -ul { - padding-left: 0; -} - -ol { - padding-left: 22px; -} - -ul li { - list-style: none; - margin-left: 0; -} - -.left { - float: left; - margin-right: 20px; - border: 1px solid grey; - padding: 20px; -} - -.remove { - margin-right: -7px; - display: inline-block; - cursor: pointer; -} - -.remove:after { - color: #be614c; - font-weight: bold; - padding: 0 6px 2px; - content: 'x'; -} - -.remove:hover:after { - color: red; - background-color: #dbffe0; -} - -select { - margin-right: 15px; -} \ No newline at end of file diff --git a/samples/tag-controls/range/sample.html b/samples/tag-controls/range/sample.html deleted file mode 100644 index 016b0d2b..00000000 --- a/samples/tag-controls/range/sample.html +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - - - -
        - - - - - diff --git a/samples/tag-controls/range/sample.js b/samples/tag-controls/range/sample.js deleted file mode 100644 index 8a658e06..00000000 --- a/samples/tag-controls/range/sample.js +++ /dev/null @@ -1,27 +0,0 @@ -"use strict"; -$.views.converters({ - strToInt: function(value) { - return parseInt(value); - } - }); - -var team = { - start: 2, - end: 4, - members: [ - {name: "Robert"}, - {name: "Sarah"}, - {name: "Xavier"}, - {name: "Adriana"} - ] -}, -cnt = 1; - -$.templates("#teamTemplate").link("#team", team) - .on("click", ".remove", function() { - var view = $.view(this); - $.observable(team.members).remove(view.index); - }) - .on("click", "#add", function() { - $.observable(team.members).insert(0, {name: "new" + cnt++}) - }); \ No newline at end of file diff --git a/samples/tag-controls/simple-textbox/sample.html b/samples/tag-controls/simple-textbox/sample.html index 5aeac72c..75799a9d 100644 --- a/samples/tag-controls/simple-textbox/sample.html +++ b/samples/tag-controls/simple-textbox/sample.html @@ -1,7 +1,7 @@  - + diff --git a/samples/tag-controls/slider/sample.html b/samples/tag-controls/slider/sample.html new file mode 100644 index 00000000..0762e033 --- /dev/null +++ b/samples/tag-controls/slider/sample.html @@ -0,0 +1,25 @@ + + + + + + + + + + + + + +
        + + + diff --git a/samples/tag-controls/slider/sample.js b/samples/tag-controls/slider/sample.js new file mode 100644 index 00000000..7c70e547 --- /dev/null +++ b/samples/tag-controls/slider/sample.js @@ -0,0 +1,5 @@ +"use strict"; +var myTmpl = $.templates("#myTmpl"), + data = {amount: 200}; + +myTmpl.link("#page", data); \ No newline at end of file diff --git a/samples/tag-controls/spinblock/sample.html b/samples/tag-controls/spinblock/sample.html new file mode 100644 index 00000000..854b9ad9 --- /dev/null +++ b/samples/tag-controls/spinblock/sample.html @@ -0,0 +1,62 @@ + + + + + + + + + + + +
        + + + + + diff --git a/samples/tag-controls/spinblock/sample.js b/samples/tag-controls/spinblock/sample.js new file mode 100644 index 00000000..f409447f --- /dev/null +++ b/samples/tag-controls/spinblock/sample.js @@ -0,0 +1,20 @@ +"use strict"; +var tabsTmpl = $.templates("#tabsTmpl"); + +tabsTmpl.link("#tabsView", + { + label: "Inner Label" + }, + { + state: { + outerSelect: 0, + innerSelect: 0 + }, + toInt: function(index) { + return +index; + }, + toString: function(index) { + return "" + index; + } + } +); \ No newline at end of file diff --git a/samples/tag-controls/tabs/sample.html b/samples/tag-controls/tabs/sample.html index 4156b386..0e4efab4 100644 --- a/samples/tag-controls/tabs/sample.html +++ b/samples/tag-controls/tabs/sample.html @@ -1,36 +1,53 @@  - + + - -

        JsViews 'tabs' tag control: The {{tabs}} tag

        - -
        ..loading
        +
        diff --git a/samples/tag-controls/tabs/sample.js b/samples/tag-controls/tabs/sample.js index 10b910e5..72c23631 100644 --- a/samples/tag-controls/tabs/sample.js +++ b/samples/tag-controls/tabs/sample.js @@ -1,25 +1,21 @@ "use strict"; -var state = { - innerSelect: 1 - }, - tabsTmpl = $.templates("#tabsTmpl"); +var tabsTmpl = $.templates("#tabsTmpl"); - tabsTmpl.link("#tabsView", - { - label2: "Inner Tab Label2", - width: 290 +tabsTmpl.link("#tabsView", + { + label2: "Inner Tab Label2", + width: 290 + }, + { + state: { + outerSelect: 0, + innerSelect: 1 + }, + toInt: function(index) { + return +index; }, - { - state: state, - onAfterCreate: function(view) { - // When the outer tabs controls tab changes to a tab containing - // a nested tabs control, the inner tabs control we must - // hook up the selectionChange handler - if (view.type === "tabs" && view.tag.tagCtx.props.id === "inner") { - view.tag.onSelectionChange = function(index) { - $.observable(state).setProperty("innerSelect", index); - } - } - } + toString: function(index) { + return "" + index; } - ); \ No newline at end of file + } +); \ No newline at end of file diff --git a/samples/tag-controls/tree/editable/sample.html b/samples/tag-controls/tree/editable/sample.html index 54e836d9..96d3dd87 100644 --- a/samples/tag-controls/tree/editable/sample.html +++ b/samples/tag-controls/tree/editable/sample.html @@ -1,7 +1,7 @@  - + @@ -54,7 +54,7 @@

        JsViews 'editable tree' tag control: The {{editableTree}} tag

        {{else}} {{/if}} - {^{if ~tag.tagCtx.props.editable}} + {^{if ~tagCtx.props.editable}} add {^{if ~tag.parent && ~tag.parent.tagName==='editableTree'}} @@ -69,7 +69,7 @@

        JsViews 'editable tree' tag control: The {{editableTree}} tag

        • {^{for folders}} - {^{editableTree editable=~tag.tagCtx.props.editable/}} + {^{editableTree editable=~tagCtx.props.editable/}} {{/for}}
      20. diff --git a/samples/tag-controls/tree/if-binding/sample.html b/samples/tag-controls/tree/if-binding/sample.html index bdd7771c..709dd3b3 100644 --- a/samples/tag-controls/tree/if-binding/sample.html +++ b/samples/tag-controls/tree/if-binding/sample.html @@ -1,7 +1,7 @@  - + diff --git a/samples/tag-controls/tree/visible-binding/sample.html b/samples/tag-controls/tree/visible-binding/sample.html index 588afa21..ffa49e47 100644 --- a/samples/tag-controls/tree/visible-binding/sample.html +++ b/samples/tag-controls/tree/visible-binding/sample.html @@ -1,7 +1,7 @@  - + diff --git a/samples/tag-controls/validate/array-binding/sample.html b/samples/tag-controls/validate/array-binding/sample.html index 71af5045..8ff0460a 100644 --- a/samples/tag-controls/validate/array-binding/sample.html +++ b/samples/tag-controls/validate/array-binding/sample.html @@ -1,7 +1,7 @@  - + @@ -73,7 +73,7 @@

        Data-linked tags

        convertBack=~lower }} {^{for people}} -
        +
        {{/for}} {{/validate}} @@ -102,7 +102,7 @@

        Data-linked elements

        convertBack=~lower }"> {^{for people}} -
        +
        {{/for}}
        {{/validation}} diff --git a/samples/tag-controls/validate/simple/sample.html b/samples/tag-controls/validate/simple/sample.html index 492adf55..1ee7aa15 100644 --- a/samples/tag-controls/validate/simple/sample.html +++ b/samples/tag-controls/validate/simple/sample.html @@ -1,7 +1,7 @@  - + diff --git a/samples/tag-controls/validate/validation-group/sample.html b/samples/tag-controls/validate/validation-group/sample.html index 3761235e..d52d10cc 100644 --- a/samples/tag-controls/validate/validation-group/sample.html +++ b/samples/tag-controls/validate/validation-group/sample.html @@ -1,7 +1,7 @@  - + diff --git a/test/browserify/10-errors-unit-tests.js b/test/browserify/10-errors-unit-tests.js index 6dea18eb..8c0a330e 100644 --- a/test/browserify/10-errors-unit-tests.js +++ b/test/browserify/10-errors-unit-tests.js @@ -128,7 +128,7 @@ test('Error cases for require() for JsRender, JsViews, JsObservable, JsRender te } // ............................... Assert ................................. - equal(result, "JsViews requires JsRender", 'require("jquery.views")(jQuery) throws "JsViews requires JsRender"'); + equal(result.slice(0, 25), "JsViews requires JsRender", 'require("jquery.views")(jQuery) throws "JsViews requires JsRender"'); // ................................ Act .................................. diff --git a/test/browserify/bundles/1-bundle.js b/test/browserify/bundles/1-bundle.js index 7ef08644..8398bf74 100644 --- a/test/browserify/bundles/1-bundle.js +++ b/test/browserify/bundles/1-bundle.js @@ -1,12 +1,12 @@ (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o=|[<>%*:?\/]|(=))\s*|(!*?[#~]?[\w$.^]+)([([])?)|(,\s*)|(\(?)\\?(?:(')|("))|(?:\s*(([)\]])(?=\s*[.^]|\s*$|[^([])|[)\]])([([]?))|(\s+)/g, - // lftPrn0 lftPrn bound path operator err eq path2 prn comma lftPrn2 apos quot rtPrn rtPrnDot prn2 space + rParams = /(\()(?=\s*\()|(?:([([])\s*)?(?:(\^?)(~?[\w$.^]+)?\s*((\+\+|--)|\+|-|~(?![\w$_])|&&|\|\||===|!==|==|!=|<=|>=|[<>%*:?\/]|(=))\s*|(!*?(@)?[#~]?[\w$.^]+)([([])?)|(,\s*)|(\(?)\\?(?:(')|("))|(?:\s*(([)\]])(?=[.^]|\s*$|[^([])|[)\]])([([]?))|(\s+)/g, + // lftPrn0 lftPrn bound path operator err eq path2 late prn comma lftPrn2 apos quot rtPrn rtPrnDot prn2 space // (left paren? followed by (path? followed by operator) or (path followed by left paren?)) or comma or apos or quot or right paren or space isRenderCall, @@ -71,6 +71,9 @@ var versionNumber = "v0.9.90", rHasHandlers = /^on[A-Z]|^convert(Back)?$/, rWrappedInViewMarker = /^\#\d+_`[\s\S]*\/\d+_`$/, rHtmlEncode = rAttrEncode, + rDataEncode = /[&<>]/g, + rDataUnencode = /&(amp|gt|lt);/g, + rBracketQuote = /\[['"]?|['"]?\]/g, viewId = 0, charEntities = { "&": "&", @@ -82,6 +85,11 @@ var versionNumber = "v0.9.90", "`": "`", "=": "=" }, + charsFromEntities = { + amp: "&", + gt: ">", + lt: "<" + }, HTML = "html", OBJECT = "object", tmplAttr = "data-jsv-tmpl", @@ -132,7 +140,7 @@ var versionNumber = "v0.9.90", allowCode: false }, advSet: noop, // Update advanced settings - _ths: tagHandlersFromProps, + _thp: tagHandlersFromProps, _gm: getMethod, _tg: function() {}, // Constructor for tagDef _cnvt: convertVal, @@ -187,7 +195,7 @@ function getMethod(baseMethod, method) { : getDerivedMethod(noop, baseMethod), // baseMethod is not derived so make its base method be the noop method method ); - method._d = 1; // Add flag that this is a derived method + method._d = (baseMethod && baseMethod._d || 0) + 1; // Add flag for derived method (incremented for derived of derived...) } return method; } @@ -349,66 +357,126 @@ getIndex.depends = "index"; // View.hlp //========== -function contextParameter(key, value, isContextCb) { +function getPathObject(ob, path, ltOb, fn) { // Iterate through path to late paths: @a.b.c paths + // Return "" (or noop if leaf is a function @a.b.c(...) ) if intermediate object not yet available + var prevOb, tokens, l, + i = 0; + if (ltOb === 1) { + fn = 1; + ltOb = undefined; + } + // Paths like ^a^b^c or ~^a^b^c will not throw if an object in path is undefined. + if (path) { + tokens = path.split("."); + l = tokens.length; + + for (; ob && i < l; i++) { + prevOb = ob; + ob = tokens[i] ? ob[tokens[i]] : ob; + } + } + if (ltOb) { + ltOb.lt = ltOb.lt || i 1, store = storeView.ctx; - if (key in store || key in (store = $helpers)) { - res = store && store[key]; - if (key === "tag" || key === "root" || key === "parentTags" || storeView._.it === key ) { - return res; + if (key) { + if (!storeView._) { // tagCtx.ctxPrm() call + tagElse = storeView.index; + storeView = storeView.tag; } - } else { - store = undefined; - } - if (!res || !$isFunction(res) && storeView.linked || storeView.tagCtx) { // Data-linked view, or tag instance - if (!res || !res._cxp) { - // Not a contextual parameter - if (store !== $helpers) { + callView = storeView; + if (store && store.hasOwnProperty(key) || (store = $helpers).hasOwnProperty(key)) { + res = store[key]; + if (key === "tag" || key === "tagCtx" || key === "root" || key === "parentTags" || storeView._.it === key ) { + return res; + } + } else { + store = undefined; + } + if (storeView.tagCtx || storeView.linked) { // Data-linked view, or tag instance + if (!res || !res._cxp) { + // Not a contextual parameter // Set storeView to tag (if this is a tag.ctxPrm() call) or to root view ("data" view of linked template) - storeView = storeView.tagCtx - ? storeView // Is a tag, not a view - : (storeView = storeView.scope || storeView, !storeView.isTop && storeView.ctx.tag || storeView); + storeView = storeView.tagCtx || $isFunction(res) + ? storeView // Is a tag, not a view, or is a computed contextual parameter, so scope to the callView, no the 'scope view' + : (storeView = storeView.scope || storeView, + !storeView.isTop && storeView.ctx.tag // If this view is in a tag, set storeView to the tag + || storeView); + if (res !== undefined && storeView.tagCtx) { + // If storeView is a tag, but the contextual parameter has been set at at higher level (e.g. helpers)... + storeView = storeView.tagCtx.view.scope; // then move storeView to the outer level (scope of tag container view) + } store = storeView._ocps; - res = store && store[key] || res; + res = store && store.hasOwnProperty(key) && store[key] || res; + if (!(res && res._cxp) && (get || isUpdate)) { + // Create observable contextual parameter + (store || (storeView._ocps = storeView._ocps || {}))[key] + = res + = [{ + _ocp: res, // The observable contextual parameter value + _vw: callView, + _key: key + }]; + res._cxp = { + path: _ocp, + ind: 0, + updateValue: function(val, path) { + $.observable(res[0]).setProperty(_ocp, val); // Set the value (res[0]._ocp) + return this; + } + }; + } } - if (!(res && res._cxp) && (isContextCb || isUpdate)) { - res = $sub._crcp(key, res, storeView, store); // Create observable contextual parameter + if (obsCtxPrm = res && res._cxp) { + // If this helper resource is an observable contextual parameter + if (arguments.length > 2) { + deps = res[1] ? $sub._ceo(res[1].deps) : [_ocp]; // fn deps (with any exprObs cloned using $sub._ceo) + deps.unshift(res[0]); // view + deps._cxp = obsCtxPrm; + // In a context callback for a contextual param, we set get = true, to get ctxPrm [view, dependencies...] array - needed for observe call + return deps; + } + tagElse = obsCtxPrm.tagElse; + newRes = res[1] // linkFn for compiled expression + ? obsCtxPrm.tag && obsCtxPrm.tag.cvtArgs + ? obsCtxPrm.tag.cvtArgs(1, tagElse)[obsCtxPrm.ind] // = tag.bndArgs() - for tag contextual parameter + : res[1](res[0].data, res[0], $sub) // = fn(data, view, $sub) for compiled binding expression + : res[0]._ocp; // Observable contextual parameter (uninitialized, or initialized as static expression, so no path dependencies) + if (isUpdate) { + if (res && newRes !== value) { + $sub._ucp(key, value, storeView, obsCtxPrm); // Update observable contextual parameter + } + return storeView; + } + res = newRes; } } - if (obsCtxPrm = res && res._cxp) { - if (isUpdate) { - return $sub._ucp(key, value, storeView, obsCtxPrm); // Update observable contextual parameter - } - if (isContextCb) { // If this helper resource is an observable contextual parameter - // In a context callback for a contextual param, return the [view, dependencies...] array - needed for observe call - deps = res[1] ? $sub._ceo(res[1].deps) : [_ocp]; // fn deps (with any exprObs cloned using $sub._ceo) - deps.unshift(res[0]); // view - deps._cxp = obsCtxPrm; - return deps; - } - res = res[1] // linkFn for compiled expression - ? obsCtxPrm.tag && obsCtxPrm.tag.cvtArgs - ? obsCtxPrm.tag.cvtArgs(true, obsCtxPrm.tagElse)[obsCtxPrm.ind] // = tag.bndArgs() - for tag contextual parameter - : res[1](res[0].data, res[0], $sub) // = fn(data, view, $sub) for compiled binding expression - : res[0]._ocp; // Observable contextual parameter (uninitialized, or initialized as static expression, so no path dependencies) - } - } - if (res && $isFunction(res)) { - // If a helper is of type function, and not already wrapped, we will wrap it, so if called with no this pointer it will be called with the - // view as 'this' context. If the helper ~foo() was in a data-link expression, the view will have a 'temporary' linkCtx property too. - // Note that helper functions on deeper paths will have specific this pointers, from the preceding path. - // For example, ~util.foo() will have the ~util object as 'this' pointer - wrapped = function() { - return res.apply((!this || this === global) ? storeView : this, arguments); - }; - $extend(wrapped, res); // Attach same expandos (if any) to the wrapped function - wrapped._vw = storeView; + if (res && $isFunction(res)) { + // If a helper is of type function we will wrap it, so if called with no this pointer it will be called with the + // view as 'this' context. If the helper ~foo() was in a data-link expression, the view will have a 'temporary' linkCtx property too. + // Note that helper functions on deeper paths will have specific this pointers, from the preceding path. + // For example, ~util.foo() will have the ~util object as 'this' pointer + wrapped = function() { + return res.apply((!this || this === global) ? callView : this, arguments); + }; + $extend(wrapped, res); // Attach same expandos (if any) to the wrapped function + wrapped._vw = callView; + } + return wrapped || res; } - return wrapped || res; } function getTemplate(tmpl) { @@ -438,7 +506,6 @@ function convertVal(converter, view, tagCtx, onError) { tagCtx = boundTag(view.data, view, $sub); } boundTag = boundTag._bd && boundTag; - value = tagCtx.args[0]; if (converter || boundTag) { tag = linkCtx && linkCtx.tag; tagCtx.view = view; @@ -446,13 +513,16 @@ function convertVal(converter, view, tagCtx, onError) { tag = $extend(new $sub._tg(), { _: { bnd: boundTag, - unlinked: true + unlinked: true, + lt: tagCtx.lt // If a late path @some.path has not returned @some object, mark tag as late }, inline: !linkCtx, tagName: ":", convert: converter, flow: true, - tagCtx: tagCtx + tagCtx: tagCtx, + tagCtxs: [tagCtx], + _is: "tag" }); argsLen = tagCtx.args.length; if (argsLen>1) { @@ -471,8 +541,10 @@ function convertVal(converter, view, tagCtx, onError) { tag._er = onError && value; tag.ctx = tagCtx.ctx || tag.ctx || {}; tagCtx.ctx = undefined; - value = tag.cvtArgs()[0]; // If there is a convertBack but no convert, converter will be "true" + tag._er = onError && value; + } else { + value = tagCtx.args[0]; } // Call onRender (used by JsViews if present, to add binding annotations around rendered content) @@ -482,8 +554,8 @@ function convertVal(converter, view, tagCtx, onError) { return value != undefined ? value : ""; } -function convertArgs(bound, tagElse) { // tag.cvtArgs() - var l, key, boundArgs, args, bindTo, tag, converter, +function convertArgs(bound, tagElse) { // tag.cvtArgs() or tag.cvtArgs(trueOrFalse, tagElse) + var l, key, boundArgs, args, bindFrom, tag, converter, tagCtx = this; if (tagCtx.tagName) { @@ -493,7 +565,7 @@ function convertArgs(bound, tagElse) { // tag.cvtArgs() tag = tagCtx.tag; } - bindTo = tag.bindTo; + bindFrom = tag.bindFrom; args = tagCtx.args; if ((converter = tag.convert) && "" + converter === converter) { @@ -502,38 +574,37 @@ function convertArgs(bound, tagElse) { // tag.cvtArgs() : (tagCtx.view.getRsc("converters", converter) || error("Unknown converter: '" + converter + "'")); } - if (bound && bound.length) { - args = bound; - } else { - if (converter && !bound) { // If there is a converter, use a copy of the tagCtx.args array for rendering, and replace the args[0] in - args = args.slice(); // the copied array with the converted value. But we do not modify the value of tag.tagCtx.args[0] (the original args array) + if (converter && !bound) { // If there is a converter, use a copy of the tagCtx.args array for rendering, and replace the args[0] in + args = args.slice(); // the copied array with the converted value. But we do not modify the value of tag.tagCtx.args[0] (the original args array) + } + if (bindFrom) { // Get the values of the boundArgs + boundArgs = []; + l = bindFrom.length; + while (l--) { + key = bindFrom[l]; + boundArgs.unshift(argOrProp(tagCtx, key)); } - if (bindTo) { // Get the values of the boundArgs - boundArgs = []; - l = bindTo.length; - while (l--) { - key = bindTo[l]; - boundArgs.unshift(argOrProp(tagCtx, key)); - } - if (bound) { - args = boundArgs; // Call to convertBoundArgs() - returns the boundArgs - } + if (bound) { + args = boundArgs; // Call to bndArgs() - returns the boundArgs } } if (converter) { - bindTo = bindTo || [0]; - l = bindTo.length; converter = converter.apply(tag, boundArgs || args); + if (converter === undefined) { + return args; // Returning undefined from a converter is equivalent to not having a converter. + } + bindFrom = bindFrom || [0]; + l = bindFrom.length; if (!$isArray(converter) || converter.length !== l) { converter = [converter]; - bindTo = [0]; + bindFrom = [0]; l = 1; } - if (bound) { // Call to bndArgs convertBoundArgs() - so apply converter to all boundArgs + if (bound) { // Call to bndArgs() - so apply converter to all boundArgs args = converter; // The array of values returned from the converter } else { // Call to cvtArgs() while (l--) { - key = bindTo[l]; + key = bindFrom[l]; if (+key === key) { args[key] = converter[l]; } @@ -549,7 +620,7 @@ function argOrProp(context, key) { } function convertBoundArgs(tagElse) { // tag.bndArgs() - return this.cvtArgs(true, tagElse); + return this.cvtArgs(1, tagElse); } //============= @@ -570,20 +641,26 @@ function getResource(resourceType, itemName) { } function renderTag(tagName, parentView, tmpl, tagCtxs, isUpdate, onError) { - function makeArray(type) { - var linkedElement; - if (linkedElement = tag[type]) { - tag[type] = linkedElement = $isArray(linkedElement) ? linkedElement: [linkedElement]; - - if (bindToLength !== linkedElement.length) { - error(type + " length not same as bindTo "); + function bindToOrBindFrom(type) { + var bindArray = tag[type]; + + if (bindArray !== undefined) { + bindArray = $isArray(bindArray) ? bindArray : [bindArray]; + m = bindArray.length; + while (m--) { + key = bindArray[m]; + if (!isNaN(parseInt(key))) { + bindArray[m] = parseInt(key); // Convert "0" to 0, etc. + } } } + + return bindArray || [0]; } parentView = parentView || topView; - var tag, tag_, tagDef, template, tags, attr, parentTag, l, m, n, itemRet, tagCtx, tagCtxCtx, ctxPrm, bindTo, - content, callInit, mapDef, thisMap, args, props, tagDataMap, contentCtx, key, bindToLength, + var tag, tagDef, template, tags, attr, parentTag, l, m, n, itemRet, tagCtx, tagCtxCtx, ctxPrm, bindTo, bindFrom, initVal, + content, callInit, mapDef, thisMap, args, bdArgs, props, tagDataMap, contentCtx, key, bindFromLength, bindToLength, linkedElement, defaultCtx, i = 0, ret = "", linkCtx = parentView.linkCtx || 0, @@ -601,14 +678,12 @@ function renderTag(tagName, parentView, tmpl, tagCtxs, isUpdate, onError) { tagDef = parentView.getRsc("tags", tagName) || error("Unknown tag: {{" + tagName + "}} "); template = tagDef.template; } - if (onError === undefined && boundTag) { - if (boundTag._lr = (tagDef.lateRender || boundTag._lr) && boundTag._lr !== "false") { - onError = ""; // If lateRender, set temporary onError, to skip initial rendering (and render just "") - } + if (onError === undefined && boundTag && (boundTag._lr = (tagDef.lateRender && boundTag._lr!== false || boundTag._lr))) { + onError = ""; // If lateRender, set temporary onError, to skip initial rendering (and render just "") } if (onError !== undefined) { ret += onError; - tagCtxs = onError = [{props: {}, args: [], params: {}}]; + tagCtxs = onError = [{props: {}, args: [], params: {props:{}}}]; } else if (boundTag) { tagCtxs = boundTag(parentView.data, parentView, $sub); } @@ -624,9 +699,12 @@ function renderTag(tagName, parentView, tmpl, tagCtxs, isUpdate, onError) { tagCtx.tmpl = tagCtx.content = parentTmpl.tmpls[content - 1]; // Set the tmpl property to the content of the block tag } tagCtx.index = i; + tagCtx.ctxPrm = contextParameter; tagCtx.render = renderContent; + tagCtx.cvtArgs = convertArgs; + tagCtx.bndArgs = convertBoundArgs; tagCtx.view = parentView; - tagCtx.ctx = extendCtx(tagCtx.ctx, ctx); // Clone and extend parentView.ctx + tagCtx.ctx = extendCtx(extendCtx(tagCtx.ctx, tagDef && tagDef.ctx), ctx); // Clone and extend parentView.ctx } if (tmpl = tagCtx.props.tmpl) { // If the tmpl property is overridden, set the value (when initializing, or, in case of binding: ^tmpl=..., when updating) @@ -644,7 +722,6 @@ function renderTag(tagName, parentView, tmpl, tagCtxs, isUpdate, onError) { tag.parent = parentTag = ctx && ctx.tag; tag.tagCtxs = tagCtxs; - tagDataMap = tag.dataMap; if (linkCtx) { tag.inline = false; @@ -653,14 +730,17 @@ function renderTag(tagName, parentView, tmpl, tagCtxs, isUpdate, onError) { } if (tag._.bnd = boundTag || linkCtx.fn) { // Bound if {^{tag...}} or data-link="{tag...}" + tag._.ths = tagCtx.params.props.this; // Tag has a this=expr binding, to get javascript reference to tag instance + tag._.lt = tagCtxs.lt; // If a late path @some.path has not returned @some object, mark tag as late tag._.arrVws = {}; } else if (tag.dataBoundOnly) { error(tagName + " must be data-bound:\n{^{" + tagName + "}}"); } //TODO better perf for childTags() - keep child tag.tags array, (and remove child, when disposed) // tag.tags = []; + } else if (linkCtx && linkCtx.fn._lr) { + callInit = !!tag.init; } - tagCtxs = tag.tagCtxs; tagDataMap = tag.dataMap; tagCtx.tag = tag; @@ -677,11 +757,12 @@ function renderTag(tagName, parentView, tmpl, tagCtxs, isUpdate, onError) { //TODO better perf for childTags: parentTag.tags.push(tag); } tags[tag.tagName] = tagCtxCtx.tag = tag; + tagCtxCtx.tagCtx = tagCtx; } } if (!(tag._er = onError)) { tagHandlersFromProps(tag, tagCtxs[0]); - tag.rendering = {}; // Provide object for state during render calls to tag and elses. (Used by {{if}} and {{for}}...) + tag.rendering = {rndr: tag.rendering}; // Provide object for state during render calls to tag and elses. (Used by {{if}} and {{for}}...) for (i = 0; i < l; i++) { // Iterate tagCtx for each {{else}} block tagCtx = tag.tagCtx = tagCtxs[i]; props = tagCtx.props; @@ -692,62 +773,95 @@ function renderTag(tagName, parentView, tmpl, tagCtxs, isUpdate, onError) { tag.init(tagCtx, linkCtx, tag.ctx); callInit = undefined; } - if (!tagCtx.args.length && tag.argDefault !== false) { + if (!tagCtx.args.length && tagCtx.argDefault !== false && tag.argDefault !== false) { tagCtx.args = args = [tagCtx.view.data]; // Missing first arg defaults to the current data context tagCtx.params.args = ["#data"]; } - bindTo = tag.bindTo; + bindTo = bindToOrBindFrom("bindTo"); - if (bindTo !== undefined) { - bindTo = tag.bindTo = $isArray(bindTo) ? bindTo : [bindTo]; - m = bindTo.length; - while (m--) { - key = bindTo[m]; - if (!isNaN(parseInt(key))) { - key = parseInt(key); // Convert "0" to 0, etc. - } - bindTo[m] = key; - } + if (tag.bindTo !== undefined) { + tag.bindTo = bindTo; } - bindTo = tag.bindTo || [0]; + if (tag.bindFrom !== undefined) { + tag.bindFrom = bindToOrBindFrom("bindFrom"); + } else if (tag.bindTo) { + tag.bindFrom = tag.bindTo = bindTo; + } + bindFrom = tag.bindFrom || bindTo; + bindToLength = bindTo.length; - if (tag._.bnd){ - makeArray("linkedElement"); - makeArray("linkedCtxParam"); + bindFromLength = bindFrom.length; + + if (tag._.bnd && (linkedElement = tag.linkedElement)) { + tag.linkedElement = linkedElement = $isArray(linkedElement) ? linkedElement: [linkedElement]; + + if (bindToLength !== linkedElement.length) { + error("linkedElement not same length as bindTo"); + } + } + if (linkedElement = tag.linkedCtxParam) { + tag.linkedCtxParam = linkedElement = $isArray(linkedElement) ? linkedElement: [linkedElement]; + + if (bindFromLength !== linkedElement.length) { + error("linkedCtxParam not same length as bindFrom/bindTo"); + } + } + + if (bindFrom) { + tag._.fromIndex = {}; // Hash of bindFrom index which has same path value as bindTo index. fromIndex = tag._.fromIndex[toIndex] + tag._.toIndex = {}; // Hash of bindFrom index which has same path value as bindTo index. fromIndex = tag._.fromIndex[toIndex] + n = bindFromLength; + while (n--) { + key = bindFrom[n]; + m = bindToLength; + while (m--) { + if (key === bindTo[m]) { + tag._.fromIndex[m] = n; + tag._.toIndex[n] = m; + } + } + } } if (linkCtx) { // Set attr on linkCtx to ensure outputting to the correct target attribute. // Setting either linkCtx.attr or this.attr in the init() allows per-instance choice of target attrib. - linkCtx.attr = tag.attr = linkCtx.attr || tag.attr; + linkCtx.attr = tag.attr = linkCtx.attr || tag.attr || linkCtx._dfAt; } attr = tag.attr; tag._.noVws = attr && attr !== HTML; } args = tag.cvtArgs(undefined, i); if (tag.linkedCtxParam) { - m = bindToLength; + bdArgs = tag.cvtArgs(1, i); + m = bindFromLength; + defaultCtx = tag.constructor.prototype.ctx; while (m--) { if (ctxPrm = tag.linkedCtxParam[m]) { - key = bindTo[m]; + key = bindFrom[m]; + initVal = bdArgs[m]; // Create tag contextual parameter - tagCtx.ctx[ctxPrm] = $sub._cp(argOrProp(tagCtx, key), argOrProp(tagCtx.params, key), tagCtx.view, tag._.bnd && {tag: tag, ind: m, tagElse: i}); + tagCtx.ctx[ctxPrm] = $sub._cp( + defaultCtx && initVal === undefined ? defaultCtx[ctxPrm]: initVal, + initVal !== undefined && argOrProp(tagCtx.params, key), + tagCtx.view, + tag._.bnd && {tag: tag, cvt: tag.convert, ind: m, tagElse: i} + ); } } } - if (mapDef = props.dataMap || tagDataMap) { - if (args.length || props.dataMap) { - thisMap = tagCtx.map; - if (!thisMap || thisMap.src !== args[0] || isUpdate) { - if (thisMap && thisMap.src) { - thisMap.unmap(); // only called if observable map - not when only used in JsRender, e.g. by {{props}} - } - thisMap = tagCtx.map = mapDef.map(args[0], props, undefined, !tag._.bnd); + if ((mapDef = props.dataMap || tagDataMap) && (args.length || props.dataMap)) { + thisMap = tagCtx.map; + if (!thisMap || thisMap.src !== args[0] || isUpdate) { + if (thisMap && thisMap.src) { + thisMap.unmap(); // only called if observable map - not when only used in JsRender, e.g. by {{props}} } - args = [thisMap.tgt]; + mapDef.map(args[0], tagCtx, thisMap, !tag._.bnd); + thisMap = tagCtx.map; } + args = [thisMap.tgt]; } itemRet = undefined; @@ -780,18 +894,16 @@ function renderTag(tagName, parentView, tmpl, tagCtxs, isUpdate, onError) { // No return value from render, and no template/content tagCtx.render(...), so return undefined ret = ret ? ret + (itemRet || "") : itemRet; // If no rendered content, this will be undefined } - tag.rendering = undefined; + tag.rendering = tag.rendering.rndr; // Remove tag.rendering object (if this is outermost render call. (In case of nested calls) } tag.tagCtx = tagCtxs[0]; tag.ctx = tag.tagCtx.ctx; - if (tag._.noVws) { - if (tag.inline) { - // inline tag with attr set to "text" will insert HTML-encoded content - as if it was element-based innerText - ret = attr === "text" - ? $converters.html(ret) - : ""; - } + if (tag._.noVws && tag.inline) { + // inline tag with attr set to "text" will insert HTML-encoded content - as if it was element-based innerText + ret = attr === "text" + ? $converters.html(ret) + : ""; } return boundTag && parentView._.onRender // Call onRender (used by JsViews if present, to add binding annotations around rendered content) @@ -831,6 +943,7 @@ function View(context, type, parentView, data, template, key, onRender, contentT parentView_ = parentView._; self.isTop = parentView_.scp; // Is top content view of a link("#container", ...) call self.scope = (!context.tag || context.tag === parentView.ctx.tag) && !self.isTop && parentView.scope || self; + // Scope for contextParams - closest non flow tag ancestor or root view if (parentView_.useKey) { // Parent is not an 'array view'. Add this view to its views object // self._key = is the key in the parent view hash @@ -856,6 +969,7 @@ View.prototype = { getRsc: getResource, getTmpl: getTemplate, ctxPrm: contextParameter, + getOb: getPathObject, _is: "view" }; @@ -880,8 +994,7 @@ function compileChildResources(parentTmpl) { //=============== function compileTag(name, tagDef, parentTmpl) { - var tmpl, baseTag, prop, l, key, bindToLength, - bindTo = tagDef.bindTo, + var tmpl, baseTag, prop, compiledDef = new $sub._tg(); function Tag() { @@ -905,10 +1018,12 @@ function compileTag(name, tagDef, parentTmpl) { if (baseTag = tagDef.baseTag) { tagDef.flow = !!tagDef.flow; // Set flow property, so defaults to false even if baseTag has flow=true - tagDef.baseTag = baseTag = "" + baseTag === baseTag + baseTag = "" + baseTag === baseTag ? (parentTmpl && parentTmpl.tags[baseTag] || $tags[baseTag]) : baseTag; - + if (!baseTag) { + error('baseTag: "' + tagDef.baseTag + '" not found'); + } compiledDef = $extend(compiledDef, baseTag); for (prop in tagDef) { @@ -967,11 +1082,9 @@ function compileTmpl(name, tmpl, parentTmpl, options) { }// END BROWSER-SPECIFIC CODE } //BROWSER-SPECIFIC CODE if (elem) { - // Generally this is a script element. - // However we allow it to be any element, so you can for example take the content of a div, - // use it as a template, and replace it by the same content rendered against data. - // e.g. for linking the content of a div to a container, and using the initial content as template: - // $.link("#content", model, {tmpl: "#content"}); + if (elem.tagName !== "SCRIPT") { + error(value + ": Use script block, not " + elem.tagName); + } if (options) { // We will compile a new template using the markup in the script element value = elem.innerHTML; @@ -1021,23 +1134,27 @@ function compileTmpl(name, tmpl, parentTmpl, options) { // If options, then this was already compiled from a (script) element template declaration. // If not, then if tmpl is a template object, use it for options - options = options || (tmpl.markup ? tmpl : {}); - options.tmplName = name; + options = options || (tmpl.markup + ? tmpl.bnds + ? $extend({}, tmpl) + : tmpl + : {} + ); + + options.tmplName = options.tmplName || name || "unnamed"; if (parentTmpl) { options._parentTmpl = parentTmpl; } // If tmpl is not a markup string or a selector string, then it must be a template object // In that case, get it from the markup property of the object - if (!tmplOrMarkup && tmpl.markup && (tmplOrMarkup = lookupTemplate(tmpl.markup))) { - if (tmplOrMarkup.fn) { - // If the string references a compiled template object, need to recompile to merge any modified options - tmplOrMarkup = tmplOrMarkup.markup; - } + if (!tmplOrMarkup && tmpl.markup && (tmplOrMarkup = lookupTemplate(tmpl.markup)) && tmplOrMarkup.fn) { + // If the string references a compiled template object, need to recompile to merge any modified options + tmplOrMarkup = tmplOrMarkup.markup; } if (tmplOrMarkup !== undefined) { - if (tmplOrMarkup.fn || tmpl.fn) { + if (tmplOrMarkup.render || tmpl.render) { // tmpl is already compiled, so use it - if (tmplOrMarkup.fn) { + if (tmplOrMarkup.tmpls) { compiledTmpl = tmplOrMarkup; } } else { @@ -1292,19 +1409,20 @@ function tmplObject(markup, options) { // Template object constructor var htmlTag, wrapMap = $subSettingsAdvanced._wm || {}, // Only used in JsViews. Otherwise empty: {} - tmpl = $extend( - { - tmpls: [], - links: {}, // Compiled functions for link expressions - bnds: [], - _is: "template", - render: renderContent - }, - options - ); + tmpl = { + tmpls: [], + links: {}, // Compiled functions for link expressions + bnds: [], + _is: "template", + render: renderContent + }; + + if (options) { + tmpl = $extend(tmpl, options); + } tmpl.markup = markup; - if (!options.htmlTag) { + if (!tmpl.htmlTag) { // Set tmpl.tag to the top-level HTML tag used in the template, if any... htmlTag = rFirstElem.exec(markup); tmpl.htmlTag = htmlTag ? htmlTag[1].toLowerCase() : ""; @@ -1395,13 +1513,14 @@ function addSetting(st) { }; } -//========= -// dataMap -//========= +//======================== +// dataMap for render only +//======================== function dataMap(mapDef) { function Map(source, options) { this.tgt = mapDef.getTgt(source, options); + options.map = this; } if ($isFunction(mapDef)) { @@ -1443,7 +1562,9 @@ function renderContent(data, context, noIteration, parentView, key, onRender) { view = view || tagCtx.view; tmpl = view.getTmpl(tag.template || tagCtx.tmpl); if (!arguments.length) { - data = view; + data = tag.contentCtx && $isFunction(tag.contentCtx) + ? data = tag.contentCtx(data) + : view; // Default data context for wrapped block content is the first argument } } else { // This is a template.render(...) call @@ -1510,7 +1631,7 @@ function renderWithViews(tmpl, data, context, noIteration, view, key, onRender, // Render template against data as a tree of subviews (nested rendered template instances), or as a string (top-level template). // If the data is the parent view, treat as noIteration, re-render with the same data context. // tmpl can be a string (e.g. rendered by a tag.render() method), or a compiled template. - var i, l, newView, childView, itemResult, swapContent, contentTmpl, outerOnRender, tmplName, itemVar, newCtx, tagCtx, + var i, l, newView, childView, itemResult, swapContent, contentTmpl, outerOnRender, tmplName, itemVar, newCtx, tagCtx, noLinking, result = ""; if (tag) { @@ -1541,7 +1662,6 @@ function renderWithViews(tmpl, data, context, noIteration, view, key, onRender, context = context || {}; context.link = false; } - if (itemVar = tagCtx.props.itemVar) { if (itemVar.charAt(0) !== "~") { syntaxError("Use itemVar='~myItem'"); @@ -1552,6 +1672,12 @@ function renderWithViews(tmpl, data, context, noIteration, view, key, onRender, if (view) { onRender = onRender || view._.onRender; + noLinking = context && context.link === false; + + if (noLinking && view._.nl) { + onRender = undefined; + } + context = extendCtx(context, view.ctx); } @@ -1561,7 +1687,7 @@ function renderWithViews(tmpl, data, context, noIteration, view, key, onRender, } // If link===false, do not call onRender, so no data-linking marker nodes - if (onRender && (context && context.link === false || tag && tag._.noVws)) { + if (onRender && tag && tag._.noVws) { onRender = undefined; } outerOnRender = onRender; @@ -1583,10 +1709,12 @@ function renderWithViews(tmpl, data, context, noIteration, view, key, onRender, ? view : (key !== undefined && view) || new View(context, "array", view, data, tmpl, key, onRender, contentTmpl); + newView._.nl= noLinking; if (view && view._.useKey) { // Parent is not an 'array view' newView._.bnd = !tag || tag._.bnd && tag; // For array views that are data bound for collection change events, set the // view._.bnd property to true for top-level link() or data-link="{for}", or to the tag instance for a data-bound tag, e.g. {^{for ...}} + newView.tag = tag; } for (i = 0, l = data.length; i < l; i++) { // Create a view for each data item. @@ -1607,10 +1735,11 @@ function renderWithViews(tmpl, data, context, noIteration, view, key, onRender, } newView = swapContent ? view : new View(newCtx, tmplName || "data", view, data, tmpl, key, onRender, contentTmpl); newView._.it = itemVar; + newView.tag = tag; + newView._.nl = noLinking; result += tmpl.fn(data, newView, $sub); } if (tag) { - newView.tag = tag; newView.tagElse = tagCtx.index; tagCtx.contentView = newView; } @@ -1715,7 +1844,7 @@ function tmplFn(markup, tmpl, isLinkExpr, convertBack, hasElse) { } slash = slash || isLinkExpr && !hasElse; - var late, + var late, openTagName, isLateOb, pathBindings = (bind || isLinkExpr) && [[]], // pathBindings is an array of arrays for arg bindings and a hash of arrays for prop bindings props = "", args = "", @@ -1739,23 +1868,31 @@ function tmplFn(markup, tmpl, isLinkExpr, convertBack, hasElse) { } else if (tagName) { if (tagName === "else") { if (rTestElseIf.test(params)) { - syntaxError('for "{{else if expr}}" use "{{else expr}}"'); + syntaxError('For "{{else if expr}}" use "{{else expr}}"'); } - pathBindings = current[8] && [[]]; - current[9] = markup.substring(current[9], index); // contentMarkup for block tag + pathBindings = current[9] && [[]]; + current[10] = markup.substring(current[10], index); // contentMarkup for block tag + openTagName = current[11] || current[0] || syntaxError("Mismatched: " + all); + // current[0] is tagName, but for {{else}} nodes, current[11] is tagName of preceding open tag current = stack.pop(); content = current[2]; block = true; } if (params) { // remove newlines from the params string, to avoid compiled code errors for unterminated strings - parseParams(params.replace(rNewLine, " "), pathBindings, tmpl) - .replace(rBuildHash, function(all, onerror, isCtx, key, keyToken, keyValue, arg, param) { + parseParams(params.replace(rNewLine, " "), pathBindings, tmpl, isLinkExpr) + .replace(rBuildHash, function(all, onerror, isCtxPrm, key, keyToken, keyValue, arg, param) { + if (key === "this:") { + keyValue = "undefined"; // this=some.path is always a to parameter (one-way), so don't need to compile/evaluate some.path initialization + } + if (param) { + isLateOb = isLateOb || param.charAt(0) === "@"; + } key = "'" + keyToken + "':"; if (arg) { - args += keyValue + ","; + args += isCtxPrm + keyValue + ","; paramsArgs += "'" + param + "',"; - } else if (isCtx) { + } else if (isCtxPrm) { // Contextual parameter, ~foo=expr ctxProps += key + 'j._cp(' + keyValue + ',"' + param + '",view),'; // Compiled code for evaluating tagCtx on a tag will have: ctx:{'foo':j._cp(compiledExpr, "expr", view)} paramsCtxProps += key + "'" + param + "',"; @@ -1766,7 +1903,7 @@ function tmplFn(markup, tmpl, isLinkExpr, convertBack, hasElse) { useTrigger += keyValue; } if (keyToken === "lateRender") { - late = param; // Render after first pass + late = param !== "false"; // Render after first pass } props += key + keyValue + ","; paramsProps += key + "'" + param + "',"; @@ -1789,17 +1926,19 @@ function tmplFn(markup, tmpl, isLinkExpr, convertBack, hasElse) { onError, useTrigger, late, + isLateOb, pathBindings || 0 ]; content.push(newNode); if (block) { stack.push(current); current = newNode; - current[9] = loc; // Store current location of open tag, to be able to add contentMarkup when we reach closing tag + current[10] = loc; // Store current location of open tag, to be able to add contentMarkup when we reach closing tag + current[11] = openTagName; // Used for checking syntax (matching close tag) } } else if (closeBlock) { - blockTagCheck(closeBlock !== current[0] && current[0] !== "else" && closeBlock, current[0]); - current[9] = markup.substring(current[9], index); // contentMarkup for block tag + blockTagCheck(closeBlock !== current[0] && closeBlock !== current[11] && closeBlock, current[0]); // Check matching close tag name + current[10] = markup.substring(current[10], index); // contentMarkup for block tag current = stack.pop(); } blockTagCheck(!current && closeBlock); @@ -1841,7 +1980,7 @@ function tmplFn(markup, tmpl, isLinkExpr, convertBack, hasElse) { pushprecedingContent(markup.length); if (loc = astTop[astTop.length - 1]) { - blockTagCheck("" + loc !== loc && (+loc[9] === loc[9]) && loc[0]); + blockTagCheck("" + loc !== loc && (+loc[10] === loc[10]) && loc[0]); } // result = tmplFnsCache[markup] = buildCode(astTop, tmpl); // } @@ -1851,7 +1990,7 @@ function tmplFn(markup, tmpl, isLinkExpr, convertBack, hasElse) { bindings = []; i = astTop.length; while (i--) { - bindings.unshift(astTop[i][8]); // With data-link expressions, pathBindings array for tagCtx[i] is astTop[i][8] + bindings.unshift(astTop[i][9]); // With data-link expressions, pathBindings array for tagCtx[i] is astTop[i][9] } setPaths(result, bindings); } else { @@ -1885,18 +2024,15 @@ function paramStructure(parts, type) { + (type ? type + ':{' : '') - + 'args:[' + parts[0] + ']' - + (parts[1] || !type - ? ',\n\tprops:{' + parts[1] + '}' - : "") + + 'args:[' + parts[0] + '],\n\tprops:{' + parts[1] + '}' + (parts[2] ? ',\n\tctx:{' + parts[2] + '}' : ""); } -function parseParams(params, pathBindings, tmpl) { +function parseParams(params, pathBindings, tmpl, isLinkExpr) { - function parseTokens(all, lftPrn0, lftPrn, bound, path, operator, err, eq, path2, prn, comma, lftPrn2, apos, quot, rtPrn, rtPrnDot, prn2, space, index, full) { - // /(\()(?=\s*\()|(?:([([])\s*)?(?:(\^?)(!*?[#~]?[\w$.^]+)?\s*((\+\+|--)|\+|-|&&|\|\||===|!==|==|!=|<=|>=|[<>%*:?\/]|(=))\s*|(!*?[#~]?[\w$.^]+)([([])?)|(,\s*)|(\(?)\\?(?:(')|("))|(?:\s*(([)\]])(?=\s*[.^]|\s*$|[^([])|[)\]])([([]?))|(\s+)/g, - // lftPrn0 lftPrn bound path operator err eq path2 prn comma lftPrn2 apos quot rtPrn rtPrnDot prn2 space + function parseTokens(all, lftPrn0, lftPrn, bound, path, operator, err, eq, path2, late, prn, comma, lftPrn2, apos, quot, rtPrn, rtPrnDot, prn2, space, index, full) { + // /(\()(?=\s*\()|(?:([([])\s*)?(?:(\^?)(~?[\w$.^]+)?\s*((\+\+|--)|\+|-|&&|\|\||===|!==|==|!=|<=|>=|[<>%*:?\/]|(=))\s*|(!*?(@)?[#~]?[\w$.^]+)([([])?)|(,\s*)|(\(?)\\?(?:(')|("))|(?:\s*(([)\]])(?=[.^]|\s*$|[^([])|[)\]])([([]?))|(\s+)/g, + // lftPrn0 lftPrn bound path operator err eq path2 late prn comma lftPrn2 apos quot rtPrn rtPrnDot prn2 space // (left paren? followed by (path? followed by operator) or (path followed by paren?)) or comma or apos or quot or right paren or space function parsePath(allPath, not, object, helper, view, viewProperty, pathTokens, leafToken) { //rPath = /^(!*?)(?:null|true|false|\d[\d.]*|([\w$]+|\.|~([\w$]+)|#(view|([\w$]+))?)([\w$.^]*?)(?:[.[^]([\w$]+)\]?)?)$/g, @@ -1908,11 +2044,19 @@ function parseParams(params, pathBindings, tmpl) { syntaxError(allPath); } if (!subPath) { - allPath = (helper + allPath = (late // late path @a.b.c: not throw on 'property of undefined' if a undefined, and will use getOb() after linking to resolve late. + ? (isLinkExpr ? '' : '(ltOb.lt=ltOb.lt||') + '(ob=' + : "" + ) + + (helper ? 'view.ctxPrm("' + helper + '")' : view ? "view" : "data") + + (late + ? ')===undefined' + (isLinkExpr ? '' : ')') + '?"":view.getOb(ob,"' + : "" + ) + (leafToken ? (viewProperty ? "." + viewProperty @@ -1921,15 +2065,18 @@ function parseParams(params, pathBindings, tmpl) { : (view ? "" : "." + object) ) + (pathTokens || "") : (leafToken = helper ? "" : view ? viewProperty || "" : object, "")); - allPath = allPath + (leafToken ? "." + leafToken : ""); allPath = not + (allPath.slice(0, 9) === "view.data" ? allPath.slice(5) // convert #view.data... to data... - : allPath); + : allPath) + + (late + ? (isLinkExpr ? '"': '",ltOb') + (prn ? ',1)':')') + : "" + ); } if (bindings) { - binds = named === "linkTo" ? (bindto = pathBindings._jsvto = pathBindings._jsvto || []) : bndCtx.bd; + binds = named === "_linkTo" ? (bindto = pathBindings._jsvto = pathBindings._jsvto || []) : bndCtx.bd; if (theOb = subPath && binds[binds.length-1]) { if (theOb._cpfn) { // Computed property exprOb while (theOb.sb) { @@ -1957,6 +2104,10 @@ function parseParams(params, pathBindings, tmpl) { operator = operator || ""; lftPrn = lftPrn || lftPrn0 || lftPrn2; path = path || path2; + + if (late && (late = !/\)|]/.test(full.charAt(index-1)))) { + path = path.slice(1).split(".").join("^"); // Late path @z.b.c. Use "^" rather than "." to ensure that deep binding will be used + } // Could do this - but not worth perf cost?? :- // if (!path.lastIndexOf("#data.", 0)) { path = path.slice(6); } // If path starts with "#data.", remove that. prn = prn || prn2 || ""; @@ -1965,7 +2116,7 @@ function parseParams(params, pathBindings, tmpl) { rtSq = ")"; if (prn === "[") { - prn ="[j._sq("; + prn = "[j._sq("; rtSq = ")]"; } @@ -1975,7 +2126,7 @@ function parseParams(params, pathBindings, tmpl) { if (bindings && rtPrnDot && !aposed && !quoted) { // This is a binding to a path in which an object is returned by a helper/data function/expression, e.g. foo()^x.y or (a?b:c)^x.y // We create a compiled function to get the object instance (which will be called when the dependent data of the subexpression changes, to return the new object, and trigger re-binding of the subsequent path) - if (!named || boundName || bindto) { + if (parenDepth && (!named || boundName || bindto)) { expr = pathStart[parenDepth - 1]; if (full.length - 1 > index - (expr || 0)) { // We need to compile a subexpression expr = full.slice(expr, index + all.length); @@ -2030,7 +2181,7 @@ function parseParams(params, pathBindings, tmpl) { ) : eq // named param. Remove bindings for arg and create instead bindings array for prop - ? (parenDepth && syntaxError(params), bindings && pathBindings.pop(), named = path, boundName = bound, paramIndex = index + all.length, + ? (parenDepth && syntaxError(params), bindings && pathBindings.pop(), named = "_" + path, boundName = bound, paramIndex = index + all.length, bindings && ((bindings = bndCtx.bd = pathBindings[named] = []), bindings.skp = !bound), path + ':') : path // path @@ -2073,7 +2224,12 @@ function parseParams(params, pathBindings, tmpl) { parenDepth = 0, fnCall = {}, // We are in a function call pathStart = {}, // tracks the start of the current path such as c^d() in the above example - result = (params + (tmpl ? " " : "")).replace(rParams, parseTokens); + result; + + if (params.charAt(0) === "@") { + params = params.replace(rBracketQuote, "."); + } + result = (params + (tmpl ? " " : "")).replace(rParams, parseTokens); return !parenDepth && result || syntaxError(params); // Syntax error if unbalanced parens in params expression } @@ -2083,7 +2239,7 @@ function buildCode(ast, tmpl, isLinkExpr) { // Used for compiling templates, and also by JsViews to build functions for data link expressions var i, node, tagName, converter, tagCtx, hasTag, hasEncoder, getsVal, hasCnvt, useCnvt, tmplBindings, pathBindings, params, boundOnErrStart, boundOnErrEnd, tagRender, nestedTmpls, tmplName, nestedTmpl, tagAndElses, content, markup, nextIsElse, oldCode, isElse, isGetVal, tagCtxFn, - onError, tagStart, trigger, lateRender, + onError, tagStart, trigger, lateRender, retStrOpen, retStrClose, tmplBindingKey = 0, useViews = $subSettingsAdvanced.useViews || tmpl.useViews || tmpl.tags || tmpl.templates || tmpl.helpers || tmpl.converters, code = "", @@ -2124,14 +2280,21 @@ function buildCode(ast, tmpl, isLinkExpr) { tagCtx = paramStructure(node[3], 'params') + '},' + paramStructure(params = node[4]); trigger = node[6]; lateRender = node[7]; - markup = node[9] && node[9].replace(rUnescapeQuotes, "$1"); + if (node[8]) { // latePath @a.b.c or @~a.b.c + retStrOpen = "\nvar ob,ltOb={},ctxs="; + retStrClose = ";\nctxs.lt=ltOb.lt;\nreturn ctxs;"; + } else { + retStrOpen = "\nreturn "; + retStrClose = ""; + } + markup = node[10] && node[10].replace(rUnescapeQuotes, "$1"); if (isElse = tagName === "else") { if (pathBindings) { - pathBindings.push(node[8]); + pathBindings.push(node[9]); } } else { onError = node[5] || $subSettings.debugMode !== false && "undefined"; // If debugMode not false, set default onError handler on tag to "undefined" (see onRenderError) - if (tmplBindings && (pathBindings = node[8])) { // Array of paths, or false if not data-bound + if (tmplBindings && (pathBindings = node[9])) { // Array of paths, or false if not data-bound pathBindings = [pathBindings]; tmplBindingKey = tmplBindings.push(1); // Add placeholder in tmplBindings for compiled function } @@ -2174,7 +2337,7 @@ function buildCode(ast, tmpl, isLinkExpr) { if (isGetVal && (pathBindings || trigger || converter && converter !== HTML || lateRender)) { // For convertVal we need a compiled function to return the new tagCtx(s) tagCtxFn = new Function("data,view,j,u", "// " + tmplName + " " + (++tmplBindingKey) + " " + tagName - + "\nreturn {" + tagCtx + "};"); + + retStrOpen + "{" + tagCtx + "};" + retStrClose); tagCtxFn._er = onError; tagCtxFn._tag = tagName; tagCtxFn._bd = !!pathBindings; // data-linked tag {^{.../}} @@ -2211,7 +2374,8 @@ function buildCode(ast, tmpl, isLinkExpr) { tagRender = 't("' + tagAndElses + '",view,this,'; if (isLinkExpr || pathBindings) { // This is a bound tag (data-link expression or inline bound tag {^{tag ...}}) so we store a compiled tagCtxs function in tmp.bnds - code = new Function("data,view,j,u", " // " + tmplName + " " + tmplBindingKey + " " + tagAndElses + "\nreturn " + code + ";"); + code = new Function("data,view,j,u", " // " + tmplName + " " + tmplBindingKey + " " + tagAndElses + retStrOpen + code + + retStrClose); code._er = onError; code._tag = tagAndElses; if (pathBindings) { @@ -2228,7 +2392,7 @@ function buildCode(ast, tmpl, isLinkExpr) { // This is the last {{else}} for an inline tag. // For a bound tag, pass the tagCtxs fn lookup key to renderTag. // For an unbound tag, include the code directly for evaluating tagCtxs array - code = oldCode + tagStart + tagRender + (code.deps && tmplBindingKey || code) + ")"; + code = oldCode + tagStart + tagRender + (pathBindings && tmplBindingKey || code) + ")"; pathBindings = 0; tagAndElses = 0; } @@ -2241,13 +2405,17 @@ function buildCode(ast, tmpl, isLinkExpr) { } // Include only the var references that are needed in the code code = "// " + tmplName - + + (tmplOptions.debug ? "\ndebugger;" : "") + "\nvar v" + (hasTag ? ",t=j._tag" : "") // has tag + (hasCnvt ? ",c=j._cnvt" : "") // converter + (hasEncoder ? ",h=j._html" : "") // html converter - + (isLinkExpr ? ";\n" : ',ret=""\n') - + (tmplOptions.debug ? "debugger;" : "") + + (isLinkExpr + ? (node[8] // late @... path? + ? ", ob" + : "" + ) + ";\n" + : ',ret=""') + code + (isLinkExpr ? "\n" : ";\nreturn ret;"); @@ -2278,26 +2446,103 @@ function extendCtx(context, parentContext) { : parentContext && $extend({}, parentContext); } -// Get character entity for HTML and Attribute encoding -function getCharEntity(ch) { - return charEntities[ch] || (charEntities[ch] = "&#" + ch.charCodeAt(0) + ";"); -} - -function getTargetProps(source) { +function getTargetProps(source, tagCtx) { // this pointer is theMap - which has tagCtx.props too // arguments: tagCtx.args. var key, prop, props = []; - if (typeof source === OBJECT) { + if (typeof source === OBJECT || $isFunction(source)) { for (key in source) { prop = source[key]; - if (key !== $expando && source.hasOwnProperty(key) && !$isFunction(prop)) { + if (key !== $expando && source.hasOwnProperty(key) && (!tagCtx.props.noFunctions || !$.isFunction(prop))) { props.push({key: key, prop: prop}); } } } - return props; + return getTargetSorted(props, tagCtx); +} + +function getTargetSorted(value, tagCtx) { + // getTgt + var mapped, start, end, + tag = tagCtx.tag, + props = tagCtx.props, + propParams = tagCtx.params.props, + filter = props.filter, + sort = props.sort, + directSort = sort === true, + step = parseInt(props.step), + reverse = props.reverse ? -1 : 1; + + if (!$isArray(value)) { + return value; + } + if (directSort || sort && "" + sort === sort) { + // Temporary mapped array holds objects with index and sort-value + mapped = value.map(function(item, i) { + item = directSort ? item : getPathObject(item, sort); + return {i: i, v: "" + item === item ? item.toLowerCase() : item}; + }); + // Sort mapped array + mapped.sort(function(a, b) { + return a.v > b.v ? reverse : a.v < b.v ? -reverse : 0; + }); + // Map to new array with resulting order + value = mapped.map(function(item){ + return value[item.i]; + }); + } else if ((sort || reverse < 0) && !tag.dataMap) { + value = value.slice(); // Clone array first if not already a new array + } + if ($isFunction(sort)) { + value = value.sort(sort); + } + if (reverse < 0 && !sort) { // Reverse result if not already reversed in sort + value = value.reverse(); + } + + if (value.filter && filter) { // IE8 does not support filter + value = value.filter(filter, tagCtx); + if (tagCtx.tag.onFilter) { + tagCtx.tag.onFilter(tagCtx); + } + } + + if (propParams.sorted) { + mapped = (sort || reverse < 0) ? value : value.slice(); + if (tag.sorted) { + $.observable(tag.sorted).refresh(mapped); // Note that this might cause the start and end props to be modified - e.g. by pager tag control + } else { + tagCtx.map.sorted = mapped; + } + } + + start = props.start; // Get current value - after possible changes triggered by tag.sorted refresh() above + end = props.end; + if (propParams.start && start === undefined || propParams.end && end === undefined) { + start = end = 0; + } + if (!isNaN(start) || !isNaN(end)) { // start or end specified, but not the auto-create Number array scenario of {{for start=xxx end=yyy}} + start = +start || 0; + end = end === undefined || end > value.length ? value.length : +end; +// end = end === undefined ? value.length : +end; + value = value.slice(start, end); + } + if (step > 1) { + start = 0; + end = value.length; + mapped = []; + for (; start & ' and " by corresponding entities. + // HTML encode: Replace < > & ' " ` etc. by corresponding entities. return text != undefined ? rIsHtml.test(text) && ("" + text).replace(rHtmlEncode, getCharEntity) || text : ""; } +function dataEncode(text) { + // Encode just < > and & - intended for 'safe data' along with {{:}} rather than {{>}} + return "" + text === text ? text.replace(rDataEncode, getCharEntity) : text; +} + +function dataUnencode(text) { + // Unencode just < > and & - intended for 'safe data' along with {{:}} rather than {{>}} + return "" + text === text ? text.replace(rDataUnencode, getCharFromEntity) : text; +} + //========================== Initialize ========================== $sub = $views.sub; @@ -2348,6 +2613,10 @@ if (!(jsr || $ && $.render)) { $.fn.render = $fnRender; $expando = $.expando; if ($.observable) { + if (versionNumber !== (versionNumber = $.views.jsviews)) { + // Different version of jsRender was loaded + throw "JsObservable requires JsRender " + versionNumber; + } $extend($sub, $.views.sub); // jquery.observable.js was loaded before jsrender.js $views.map = $.views.map; } @@ -2404,7 +2673,7 @@ if (!(jsr || $ && $.render)) { : ( $subSettings.debugMode = debugMode, $subSettings.onError = debugMode + "" === debugMode - ? new Function("", "return '" + debugMode + "';") + ? function() { return debugMode; } : $isFunction(debugMode) ? debugMode : undefined, @@ -2427,7 +2696,7 @@ if (!(jsr || $ && $.render)) { // Otherwise return "" var self = this, tagCtx = self.tagCtx, - ret = (self.rendering.done || !val && (arguments.length || !tagCtx.index)) + ret = (self.rendering.done || !val && (tagCtx.args.length || !tagCtx.index)) ? "" : (self.rendering.done = true, self.selected = tagCtx.index, @@ -2438,21 +2707,53 @@ if (!(jsr || $ && $.render)) { flow: true }, "for": { + sortDataMap: dataMap(getTargetSorted), + init: function(val, cloned) { + var l, tagCtx, props, sort, + self = this, + tagCtxs = self.tagCtxs; + l = tagCtxs.length; + while (l--) { + tagCtx = tagCtxs[l]; + props = tagCtx.props; + tagCtx.argDefault = props.end === undefined || tagCtx.args.length > 0; // Default to #data except for auto-create range scenario {{for start=xxx end=yyy step=zzz}} + + if (tagCtx.argDefault !== false && $isArray(tagCtx.args[0]) + && (props.sort !== undefined || tagCtx.params.props.start || tagCtx.params.props.end || props.step !== undefined || props.filter || props.reverse)) { + props.dataMap = self.sortDataMap; + } + } + }, render: function(val) { // This function is called once for {{for}} and once for each {{else}}. // We will use the tag.rendering object for carrying rendering state across the calls. - var finalElse = !arguments.length, - value, + var value, filter, srtField, isArray, i, sorted, end, step, self = this, tagCtx = self.tagCtx, + range = tagCtx.argDefault === false, + props = tagCtx.props, + iterate = range || tagCtx.args.length, // Not final else and not auto-create range result = "", done = 0; if (!self.rendering.done) { - value = finalElse ? tagCtx.view.data : val; // For the final else, defaults to current data without iteration. + value = iterate ? val : tagCtx.view.data; // For the final else, defaults to current data without iteration. + + if (range) { + range = props.reverse ? "unshift" : "push"; + end = +props.end; + step = +props.step || 1; + value = []; // auto-create integer array scenario of {{for start=xxx end=yyy}} + for (i = +props.start || 0; (end - i) * step > 0; i += step) { + value[range](i); + } + } if (value !== undefined) { - result += tagCtx.render(value, finalElse); // Iterates except on final else, if data is an array. (Use {{include}} to compose templates without array iteration) - done += $isArray(value) ? value.length : 1; + isArray = $isArray(value); + result += tagCtx.render(value, !iterate || props.noIteration); + // Iterates if data is an array, except on final else - or if noIteration property + // set to true. (Use {{include}} to compose templates without array iteration) + done += isArray ? value.length : 1; } if (self.rendering.done = done) { self.selected = tagCtx.index; @@ -2466,6 +2767,7 @@ if (!(jsr || $ && $.render)) { props: { baseTag: "for", dataMap: dataMap(getTargetProps), + init: noop, // Don't execute the base init() of the "for" tag flow: true }, include: { @@ -2487,6 +2789,8 @@ if (!(jsr || $ && $.render)) { $converters({ html: htmlEncode, attr: htmlEncode, // Includes > encoding since rConvertMarkers in JsViews does not skip > characters in attribute strings + encode: dataEncode, + unencode: dataUnencode, // Includes > encoding since rConvertMarkers in JsViews does not skip > characters in attribute strings url: function(text) { // URL encoding helper. return text != undefined ? encodeURI("" + text) : text === null ? text : ""; // null returns null, e.g. to remove attribute. undefined returns "" @@ -2498,7 +2802,6 @@ $subSettings = $sub.settings; $isArray = ($||jsr).isArray; $viewsSettings.delimiters("{{", "}}", "^"); - if (jsrToJq) { // Moving from jsrender namespace to jQuery namepace - copy over the stored items (templates, converters, helpers...) jsr.views.sub._jq($); } @@ -2568,4 +2871,4 @@ module.exports = $ ? $.templates("./test/templates/name-template.html", mkup) : }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{}]},{},[2]) -//# sourceMappingURL=data:application/json;charset:utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9icm93c2VyLXBhY2svX3ByZWx1ZGUuanMiLCJub2RlX21vZHVsZXMvanNyZW5kZXIvanNyZW5kZXIuanMiLCJ0ZXN0L2Jyb3dzZXJpZnkvMS11bml0LXRlc3RzLmpzIiwidGVzdC90ZW1wbGF0ZXMvbmFtZS10ZW1wbGF0ZS5odG1sIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0FDQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7OztBQ3o4RUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7Ozs7QUN4Q0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJmaWxlIjoiZ2VuZXJhdGVkLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXNDb250ZW50IjpbIihmdW5jdGlvbiBlKHQsbixyKXtmdW5jdGlvbiBzKG8sdSl7aWYoIW5bb10pe2lmKCF0W29dKXt2YXIgYT10eXBlb2YgcmVxdWlyZT09XCJmdW5jdGlvblwiJiZyZXF1aXJlO2lmKCF1JiZhKXJldHVybiBhKG8sITApO2lmKGkpcmV0dXJuIGkobywhMCk7dmFyIGY9bmV3IEVycm9yKFwiQ2Fubm90IGZpbmQgbW9kdWxlICdcIitvK1wiJ1wiKTt0aHJvdyBmLmNvZGU9XCJNT0RVTEVfTk9UX0ZPVU5EXCIsZn12YXIgbD1uW29dPXtleHBvcnRzOnt9fTt0W29dWzBdLmNhbGwobC5leHBvcnRzLGZ1bmN0aW9uKGUpe3ZhciBuPXRbb11bMV1bZV07cmV0dXJuIHMobj9uOmUpfSxsLGwuZXhwb3J0cyxlLHQsbixyKX1yZXR1cm4gbltvXS5leHBvcnRzfXZhciBpPXR5cGVvZiByZXF1aXJlPT1cImZ1bmN0aW9uXCImJnJlcXVpcmU7Zm9yKHZhciBvPTA7bzxyLmxlbmd0aDtvKyspcyhyW29dKTtyZXR1cm4gc30pIiwiLyohIEpzUmVuZGVyIHYwLjkuOTAgKEJldGEpOiBodHRwOi8vanN2aWV3cy5jb20vI2pzcmVuZGVyICovXG4vKiEgKipWRVJTSU9OIEZPUiBXRUIqKiAoRm9yIE5PREUuSlMgc2VlIGh0dHA6Ly9qc3ZpZXdzLmNvbS9kb3dubG9hZC9qc3JlbmRlci1ub2RlLmpzKSAqL1xuLypcbiAqIEJlc3Qtb2YtYnJlZWQgdGVtcGxhdGluZyBpbiBicm93c2VyIG9yIG9uIE5vZGUuanMuXG4gKiBEb2VzIG5vdCByZXF1aXJlIGpRdWVyeSwgb3IgSFRNTCBET01cbiAqIEludGVncmF0ZXMgd2l0aCBKc1ZpZXdzIChodHRwOi8vanN2aWV3cy5jb20vI2pzdmlld3MpXG4gKlxuICogQ29weXJpZ2h0IDIwMTcsIEJvcmlzIE1vb3JlXG4gKiBSZWxlYXNlZCB1bmRlciB0aGUgTUlUIExpY2Vuc2UuXG4gKi9cblxuLy9qc2hpbnQgLVcwMTgsIC1XMDQxLCAtVzEyMFxuXG4oZnVuY3Rpb24oZmFjdG9yeSwgZ2xvYmFsKSB7XG5cdC8vIGdsb2JhbCB2YXIgaXMgdGhlIHRoaXMgb2JqZWN0LCB3aGljaCBpcyB3aW5kb3cgd2hlbiBydW5uaW5nIGluIHRoZSB1c3VhbCBicm93c2VyIGVudmlyb25tZW50XG5cdHZhciAkID0gZ2xvYmFsLmpRdWVyeTtcblxuXHRpZiAodHlwZW9mIGV4cG9ydHMgPT09IFwib2JqZWN0XCIpIHsgLy8gQ29tbW9uSlMgZS5nLiBCcm93c2VyaWZ5XG5cdFx0bW9kdWxlLmV4cG9ydHMgPSAkXG5cdFx0XHQ/IGZhY3RvcnkoZ2xvYmFsLCAkKVxuXHRcdFx0OiBmdW5jdGlvbigkKSB7IC8vIElmIG5vIGdsb2JhbCBqUXVlcnksIHRha2Ugb3B0aW9uYWwgalF1ZXJ5IHBhc3NlZCBhcyBwYXJhbWV0ZXI6IHJlcXVpcmUoJ2pzcmVuZGVyJykoalF1ZXJ5KVxuXHRcdFx0XHRpZiAoJCAmJiAhJC5mbikge1xuXHRcdFx0XHRcdHRocm93IFwiUHJvdmlkZSBqUXVlcnkgb3IgbnVsbFwiO1xuXHRcdFx0XHR9XG5cdFx0XHRcdHJldHVybiBmYWN0b3J5KGdsb2JhbCwgJCk7XG5cdFx0XHR9O1xuXHR9IGVsc2UgaWYgKHR5cGVvZiBkZWZpbmUgPT09IFwiZnVuY3Rpb25cIiAmJiBkZWZpbmUuYW1kKSB7IC8vIEFNRCBzY3JpcHQgbG9hZGVyLCBlLmcuIFJlcXVpcmVKU1xuXHRcdGRlZmluZShmdW5jdGlvbigpIHtcblx0XHRcdHJldHVybiBmYWN0b3J5KGdsb2JhbCk7XG5cdFx0fSk7XG5cdH0gZWxzZSB7IC8vIEJyb3dzZXIgdXNpbmcgcGxhaW4gPHNjcmlwdD4gdGFnXG5cdFx0ZmFjdG9yeShnbG9iYWwsIGZhbHNlKTtcblx0fVxufSAoXG5cbi8vIGZhY3RvcnkgKGZvciBqc3JlbmRlci5qcylcbmZ1bmN0aW9uKGdsb2JhbCwgJCkge1xuXCJ1c2Ugc3RyaWN0XCI7XG5cbi8vPT09PT09PT09PT09PT09PT09PT09PT09PT0gVG9wLWxldmVsIHZhcnMgPT09PT09PT09PT09PT09PT09PT09PT09PT1cblxuLy8gZ2xvYmFsIHZhciBpcyB0aGUgdGhpcyBvYmplY3QsIHdoaWNoIGlzIHdpbmRvdyB3aGVuIHJ1bm5pbmcgaW4gdGhlIHVzdWFsIGJyb3dzZXIgZW52aXJvbm1lbnRcbnZhciBzZXRHbG9iYWxzID0gJCA9PT0gZmFsc2U7IC8vIE9ubHkgc2V0IGdsb2JhbHMgaWYgc2NyaXB0IGJsb2NrIGluIGJyb3dzZXIgKG5vdCBBTUQgYW5kIG5vdCBDb21tb25KUylcblxuJCA9ICQgJiYgJC5mbiA/ICQgOiBnbG9iYWwualF1ZXJ5OyAvLyAkIGlzIGpRdWVyeSBwYXNzZWQgaW4gYnkgQ29tbW9uSlMgbG9hZGVyIChCcm93c2VyaWZ5KSwgb3IgZ2xvYmFsIGpRdWVyeS5cblxudmFyIHZlcnNpb25OdW1iZXIgPSBcInYwLjkuOTBcIixcblx0anN2U3RvcmVOYW1lLCByVGFnLCByVG1wbFN0cmluZywgdG9wVmlldywgJHZpZXdzLFx0JGV4cGFuZG8sXG5cdF9vY3AgPSBcIl9vY3BcIiwgLy8gT2JzZXJ2YWJsZSBjb250ZXh0dWFsIHBhcmFtZXRlclxuXG4vL1RPRE9cdHRtcGxGbnNDYWNoZSA9IHt9LFxuXHQkaXNGdW5jdGlvbiwgJGlzQXJyYXksICR0ZW1wbGF0ZXMsICRjb252ZXJ0ZXJzLCAkaGVscGVycywgJHRhZ3MsICRzdWIsICRzdWJTZXR0aW5ncywgJHN1YlNldHRpbmdzQWR2YW5jZWQsICR2aWV3c1NldHRpbmdzLCBkZWxpbU9wZW5DaGFyMCwgZGVsaW1PcGVuQ2hhcjEsIGRlbGltQ2xvc2VDaGFyMCwgZGVsaW1DbG9zZUNoYXIxLCBsaW5rQ2hhciwgc2V0dGluZywgYmFzZU9uRXJyb3IsXG5cblx0clBhdGggPSAvXighKj8pKD86bnVsbHx0cnVlfGZhbHNlfFxcZFtcXGQuXSp8KFtcXHckXSt8XFwufH4oW1xcdyRdKyl8Iyh2aWV3fChbXFx3JF0rKSk/KShbXFx3JC5eXSo/KSg/OlsuW15dKFtcXHckXSspXFxdPyk/KSQvZyxcblx0Ly8gICAgICAgIG5vdCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvYmplY3QgICAgIGhlbHBlciAgICB2aWV3ICB2aWV3UHJvcGVydHkgcGF0aFRva2VucyAgICAgIGxlYWZUb2tlblxuXG5cdHJQYXJhbXMgPSAvKFxcKCkoPz1cXHMqXFwoKXwoPzooWyhbXSlcXHMqKT8oPzooXFxePykoISo/WyN+XT9bXFx3JC5eXSspP1xccyooKFxcK1xcK3wtLSl8XFwrfC18JiZ8XFx8XFx8fD09PXwhPT18PT18IT18PD18Pj18Wzw+JSo6P1xcL118KD0pKVxccyp8KCEqP1sjfl0/W1xcdyQuXl0rKShbKFtdKT8pfCgsXFxzKil8KFxcKD8pXFxcXD8oPzooJyl8KFwiKSl8KD86XFxzKigoWylcXF1dKSg/PVxccypbLl5dfFxccyokfFteKFtdKXxbKVxcXV0pKFsoW10/KSl8KFxccyspL2csXG5cdC8vICAgICAgICAgIGxmdFBybjAgICAgICAgIGxmdFBybiAgICAgICAgYm91bmQgICAgICAgICAgICBwYXRoICAgIG9wZXJhdG9yIGVyciAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVxICAgICAgICAgICAgIHBhdGgyICAgICAgIHBybiAgICBjb21tYSAgIGxmdFBybjIgICBhcG9zIHF1b3QgICAgICBydFBybiBydFBybkRvdCAgICAgICAgICAgICAgICAgICAgICAgICAgIHBybjIgIHNwYWNlXG5cdC8vIChsZWZ0IHBhcmVuPyBmb2xsb3dlZCBieSAocGF0aD8gZm9sbG93ZWQgYnkgb3BlcmF0b3IpIG9yIChwYXRoIGZvbGxvd2VkIGJ5IGxlZnQgcGFyZW4/KSkgb3IgY29tbWEgb3IgYXBvcyBvciBxdW90IG9yIHJpZ2h0IHBhcmVuIG9yIHNwYWNlXG5cblx0aXNSZW5kZXJDYWxsLFxuXHRyTmV3TGluZSA9IC9bIFxcdF0qKFxcclxcbnxcXG58XFxyKS9nLFxuXHRyVW5lc2NhcGVRdW90ZXMgPSAvXFxcXChbJ1wiXSkvZyxcblx0ckVzY2FwZVF1b3RlcyA9IC9bJ1wiXFxcXF0vZywgLy8gRXNjYXBlIHF1b3RlcyBhbmQgXFwgY2hhcmFjdGVyXG5cdHJCdWlsZEhhc2ggPSAvKD86XFx4MDh8Xikob25lcnJvcjopPyg/Oih+PykoKFtcXHckX1xcLl0rKTopPyhbXlxceDA4XSspKVxceDA4KCwpPyhbXlxceDA4XSspL2dpLFxuXHRyVGVzdEVsc2VJZiA9IC9eaWZcXHMvLFxuXHRyRmlyc3RFbGVtID0gLzwoXFx3KylbPlxcc10vLFxuXHRyQXR0ckVuY29kZSA9IC9bXFx4MDBgPjxcIicmPV0vZywgLy8gSW5jbHVkZXMgPiBlbmNvZGluZyBzaW5jZSByQ29udmVydE1hcmtlcnMgaW4gSnNWaWV3cyBkb2VzIG5vdCBza2lwID4gY2hhcmFjdGVycyBpbiBhdHRyaWJ1dGUgc3RyaW5nc1xuXHRySXNIdG1sID0gL1tcXHgwMGA+PFxcXCInJj1dLyxcblx0ckhhc0hhbmRsZXJzID0gL15vbltBLVpdfF5jb252ZXJ0KEJhY2spPyQvLFxuXHRyV3JhcHBlZEluVmlld01hcmtlciA9IC9eXFwjXFxkK19gW1xcc1xcU10qXFwvXFxkK19gJC8sXG5cdHJIdG1sRW5jb2RlID0gckF0dHJFbmNvZGUsXG5cdHZpZXdJZCA9IDAsXG5cdGNoYXJFbnRpdGllcyA9IHtcblx0XHRcIiZcIjogXCImYW1wO1wiLFxuXHRcdFwiPFwiOiBcIiZsdDtcIixcblx0XHRcIj5cIjogXCImZ3Q7XCIsXG5cdFx0XCJcXHgwMFwiOiBcIiYjMDtcIixcblx0XHRcIidcIjogXCImIzM5O1wiLFxuXHRcdCdcIic6IFwiJiMzNDtcIixcblx0XHRcImBcIjogXCImIzk2O1wiLFxuXHRcdFwiPVwiOiBcIiYjNjE7XCJcblx0fSxcblx0SFRNTCA9IFwiaHRtbFwiLFxuXHRPQkpFQ1QgPSBcIm9iamVjdFwiLFxuXHR0bXBsQXR0ciA9IFwiZGF0YS1qc3YtdG1wbFwiLFxuXHRqc3ZUbXBsID0gXCJqc3ZUbXBsXCIsXG5cdGluZGV4U3RyID0gXCJGb3IgI2luZGV4IGluIG5lc3RlZCBibG9jayB1c2UgI2dldEluZGV4KCkuXCIsXG5cdCRyZW5kZXIgPSB7fSxcblxuXHRqc3IgPSBnbG9iYWwuanNyZW5kZXIsXG5cdGpzclRvSnEgPSBqc3IgJiYgJCAmJiAhJC5yZW5kZXIsIC8vIEpzUmVuZGVyIGFscmVhZHkgbG9hZGVkLCB3aXRob3V0IGpRdWVyeS4gYnV0IHdlIHdpbGwgcmUtbG9hZCBpdCBub3cgdG8gYXR0YWNoIHRvIGpRdWVyeVxuXG5cdGpzdlN0b3JlcyA9IHtcblx0XHR0ZW1wbGF0ZToge1xuXHRcdFx0Y29tcGlsZTogY29tcGlsZVRtcGxcblx0XHR9LFxuXHRcdHRhZzoge1xuXHRcdFx0Y29tcGlsZTogY29tcGlsZVRhZ1xuXHRcdH0sXG5cdFx0dmlld01vZGVsOiB7XG5cdFx0XHRjb21waWxlOiBjb21waWxlVmlld01vZGVsXG5cdFx0fSxcblx0XHRoZWxwZXI6IHt9LFxuXHRcdGNvbnZlcnRlcjoge31cblx0fTtcblxuXHQvLyB2aWV3cyBvYmplY3QgKCQudmlld3MgaWYgalF1ZXJ5IGlzIGxvYWRlZCwganNyZW5kZXIudmlld3MgaWYgbm8galF1ZXJ5LCBlLmcuIGluIE5vZGUuanMpXG5cdCR2aWV3cyA9IHtcblx0XHRqc3ZpZXdzOiB2ZXJzaW9uTnVtYmVyLFxuXHRcdHN1Yjoge1xuXHRcdFx0Ly8gc3Vic2NyaXB0aW9uLCBlLmcuIEpzVmlld3MgaW50ZWdyYXRpb25cblx0XHRcdFZpZXc6IFZpZXcsXG5cdFx0XHRFcnI6IEpzVmlld3NFcnJvcixcblx0XHRcdHRtcGxGbjogdG1wbEZuLFxuXHRcdFx0cGFyc2U6IHBhcnNlUGFyYW1zLFxuXHRcdFx0ZXh0ZW5kOiAkZXh0ZW5kLFxuXHRcdFx0ZXh0ZW5kQ3R4OiBleHRlbmRDdHgsXG5cdFx0XHRzeW50YXhFcnI6IHN5bnRheEVycm9yLFxuXHRcdFx0b25TdG9yZToge1xuXHRcdFx0XHR0ZW1wbGF0ZTogZnVuY3Rpb24obmFtZSwgaXRlbSkge1xuXHRcdFx0XHRcdGlmIChpdGVtID09PSBudWxsKSB7XG5cdFx0XHRcdFx0XHRkZWxldGUgJHJlbmRlcltuYW1lXTtcblx0XHRcdFx0XHR9IGVsc2Uge1xuXHRcdFx0XHRcdFx0JHJlbmRlcltuYW1lXSA9IGl0ZW07XG5cdFx0XHRcdFx0fVxuXHRcdFx0XHR9XG5cdFx0XHR9LFxuXHRcdFx0YWRkU2V0dGluZzogYWRkU2V0dGluZyxcblx0XHRcdHNldHRpbmdzOiB7XG5cdFx0XHRcdGFsbG93Q29kZTogZmFsc2Vcblx0XHRcdH0sXG5cdFx0XHRhZHZTZXQ6IG5vb3AsIC8vIFVwZGF0ZSBhZHZhbmNlZCBzZXR0aW5nc1xuXHRcdFx0X3RoczogdGFnSGFuZGxlcnNGcm9tUHJvcHMsXG5cdFx0XHRfZ206IGdldE1ldGhvZCxcblx0XHRcdF90ZzogZnVuY3Rpb24oKSB7fSwgLy8gQ29uc3RydWN0b3IgZm9yIHRhZ0RlZlxuXHRcdFx0X2NudnQ6IGNvbnZlcnRWYWwsXG5cdFx0XHRfdGFnOiByZW5kZXJUYWcsXG5cdFx0XHRfZXI6IGVycm9yLFxuXHRcdFx0X2Vycjogb25SZW5kZXJFcnJvcixcblx0XHRcdF9jcDogcmV0VmFsLCAvLyBHZXQgb2JzZXJ2YWJsZSBjb250ZXh0dWFsIHBhcmFtZXRlcnMgKG9yIHByb3BlcnRpZXMpIH5mb289ZXhwci4gSW4gSnNSZW5kZXIsIHNpbXBseSByZXR1cm5zIHZhbC5cblx0XHRcdF9zcTogZnVuY3Rpb24odG9rZW4pIHtcblx0XHRcdFx0aWYgKHRva2VuID09PSBcImNvbnN0cnVjdG9yXCIpIHtcblx0XHRcdFx0XHRzeW50YXhFcnJvcihcIlwiKTtcblx0XHRcdFx0fVxuXHRcdFx0XHRyZXR1cm4gdG9rZW47XG5cdFx0XHR9XG5cdFx0fSxcblx0XHRzZXR0aW5nczoge1xuXHRcdFx0ZGVsaW1pdGVyczogJHZpZXdzRGVsaW1pdGVycyxcblx0XHRcdGFkdmFuY2VkOiBmdW5jdGlvbih2YWx1ZSkge1xuXHRcdFx0XHRyZXR1cm4gdmFsdWVcblx0XHRcdFx0XHQ/IChcblx0XHRcdFx0XHRcdFx0JGV4dGVuZCgkc3ViU2V0dGluZ3NBZHZhbmNlZCwgdmFsdWUpLFxuXHRcdFx0XHRcdFx0XHQkc3ViLmFkdlNldCgpLFxuXHRcdFx0XHRcdFx0XHQkdmlld3NTZXR0aW5nc1xuXHRcdFx0XHRcdFx0KVxuXHRcdFx0XHRcdFx0OiAkc3ViU2V0dGluZ3NBZHZhbmNlZDtcblx0XHRcdFx0fVxuXHRcdH0sXG5cdFx0bWFwOiBkYXRhTWFwICAgIC8vIElmIGpzT2JzZXJ2YWJsZSBsb2FkZWQgZmlyc3QsIHVzZSB0aGF0IGRlZmluaXRpb24gb2YgZGF0YU1hcFxuXHR9O1xuXG5mdW5jdGlvbiBnZXREZXJpdmVkTWV0aG9kKGJhc2VNZXRob2QsIG1ldGhvZCkge1xuXHRyZXR1cm4gZnVuY3Rpb24oKSB7XG5cdFx0dmFyIHJldCxcblx0XHRcdHRhZyA9IHRoaXMsXG5cdFx0XHRwcmV2QmFzZSA9IHRhZy5iYXNlO1xuXG5cdFx0dGFnLmJhc2UgPSBiYXNlTWV0aG9kOyAvLyBXaXRoaW4gbWV0aG9kIGNhbGwsIGNhbGxpbmcgdGhpcy5iYXNlIHdpbGwgY2FsbCB0aGUgYmFzZSBtZXRob2Rcblx0XHRyZXQgPSBtZXRob2QuYXBwbHkodGFnLCBhcmd1bWVudHMpOyAvLyBDYWxsIHRoZSBtZXRob2Rcblx0XHR0YWcuYmFzZSA9IHByZXZCYXNlOyAvLyBSZXBsYWNlIHRoaXMuYmFzZSB0byBiZSB0aGUgYmFzZSBtZXRob2Qgb2YgdGhlIHByZXZpb3VzIGNhbGwsIGZvciBjaGFpbmVkIGNhbGxzXG5cdFx0cmV0dXJuIHJldDtcblx0fTtcbn1cblxuZnVuY3Rpb24gZ2V0TWV0aG9kKGJhc2VNZXRob2QsIG1ldGhvZCkge1xuXHQvLyBGb3IgZGVyaXZlZCBtZXRob2RzIChvciBoYW5kbGVycyBkZWNsYXJlZCBkZWNsYXJhdGl2ZWx5IGFzIGluIHt7OmZvbyBvbkNoYW5nZT1+Zm9vQ2hhbmdlZH19IHJlcGxhY2UgYnkgYSBkZXJpdmVkIG1ldGhvZCwgdG8gYWxsb3cgdXNpbmcgdGhpcy5iYXNlKC4uLilcblx0Ly8gb3IgdGhpcy5iYXNlQXBwbHkoYXJndW1lbnRzKSB0byBjYWxsIHRoZSBiYXNlIGltcGxlbWVudGF0aW9uLiAoRXF1aXZhbGVudCB0byB0aGlzLl9zdXBlciguLi4pIGFuZCB0aGlzLl9zdXBlckFwcGx5KGFyZ3VtZW50cykgaW4galF1ZXJ5IFVJKVxuXHRpZiAoJGlzRnVuY3Rpb24obWV0aG9kKSkge1xuXHRcdG1ldGhvZCA9IGdldERlcml2ZWRNZXRob2QoXG5cdFx0XHRcdCFiYXNlTWV0aG9kXG5cdFx0XHRcdFx0PyBub29wIC8vIG5vIGJhc2UgbWV0aG9kIGltcGxlbWVudGF0aW9uLCBzbyB1c2Ugbm9vcCBhcyBiYXNlIG1ldGhvZFxuXHRcdFx0XHRcdDogYmFzZU1ldGhvZC5fZFxuXHRcdFx0XHRcdFx0PyBiYXNlTWV0aG9kIC8vIGJhc2VNZXRob2QgaXMgYSBkZXJpdmVkIG1ldGhvZCwgc28gdXNlIGl0XG5cdFx0XHRcdFx0XHQ6IGdldERlcml2ZWRNZXRob2Qobm9vcCwgYmFzZU1ldGhvZCksIC8vIGJhc2VNZXRob2QgaXMgbm90IGRlcml2ZWQgc28gbWFrZSBpdHMgYmFzZSBtZXRob2QgYmUgdGhlIG5vb3AgbWV0aG9kXG5cdFx0XHRcdG1ldGhvZFxuXHRcdFx0KTtcblx0XHRtZXRob2QuX2QgPSAxOyAvLyBBZGQgZmxhZyB0aGF0IHRoaXMgaXMgYSBkZXJpdmVkIG1ldGhvZFxuXHR9XG5cdHJldHVybiBtZXRob2Q7XG59XG5cbmZ1bmN0aW9uIHRhZ0hhbmRsZXJzRnJvbVByb3BzKHRhZywgdGFnQ3R4KSB7XG5cdHZhciBwcm9wLFxuXHRcdHByb3BzID0gdGFnQ3R4LnByb3BzO1xuXHRmb3IgKHByb3AgaW4gcHJvcHMpIHtcblx0XHRpZiAockhhc0hhbmRsZXJzLnRlc3QocHJvcCkgJiYgISh0YWdbcHJvcF0gJiYgdGFnW3Byb3BdLmZpeCkpIHsgLy8gRG9uJ3Qgb3ZlcnJpZGUgaGFuZGxlcnMgd2l0aCBmaXggZXhwYW5kbyAodXNlZCBpbiBkYXRlcGlja2VyIGFuZCBzcGlubmVyKVxuXHRcdFx0dGFnW3Byb3BdID0gcHJvcCAhPT0gXCJjb252ZXJ0XCIgPyBnZXRNZXRob2QodGFnLmNvbnN0cnVjdG9yLnByb3RvdHlwZVtwcm9wXSwgcHJvcHNbcHJvcF0pIDogcHJvcHNbcHJvcF07XG5cdFx0XHQvLyBDb3B5IG92ZXIgdGhlIG9uRm9vIHByb3BzLCBjb252ZXJ0IGFuZCBjb252ZXJ0QmFjayBmcm9tIHRhZ0N0eC5wcm9wcyB0byB0YWcgKG92ZXJyaWRlcyB2YWx1ZXMgaW4gdGFnRGVmKS5cblx0XHRcdC8vIE5vdGU6IHVuc3VwcG9ydGVkIHNjZW5hcmlvOiBpZiBoYW5kbGVycyBhcmUgZHluYW1pY2FsbHkgYWRkZWQgXm9uRm9vPWV4cHJlc3Npb24gdGhpcyB3aWxsIHdvcmssIGJ1dCBkeW5hbWljYWxseSByZW1vdmluZyB3aWxsIG5vdCB3b3JrLlxuXHRcdH1cblx0fVxufVxuXG5mdW5jdGlvbiByZXRWYWwodmFsKSB7XG5cdHJldHVybiB2YWw7XG59XG5cbmZ1bmN0aW9uIG5vb3AoKSB7XG5cdHJldHVybiBcIlwiO1xufVxuXG5mdW5jdGlvbiBkYmdCcmVhayh2YWwpIHtcblx0Ly8gVXNhZ2UgZXhhbXBsZXM6IHt7ZGJnOi4uLn19LCB7ezp+ZGJnKC4uLil9fSwge3tkYmcgLi4uL319LCB7Xntmb3IgLi4uIG9uQWZ0ZXJMaW5rPX5kYmd9fSBldGMuXG5cdHRyeSB7XG5cdFx0Y29uc29sZS5sb2coXCJKc1JlbmRlciBkYmcgYnJlYWtwb2ludDogXCIgKyB2YWwpO1xuXHRcdHRocm93IFwiZGJnIGJyZWFrcG9pbnRcIjsgLy8gVG8gYnJlYWsgaGVyZSwgc3RvcCBvbiBjYXVnaHQgZXhjZXB0aW9ucy5cblx0fVxuXHRjYXRjaCAoZSkge31cblx0cmV0dXJuIHRoaXMuYmFzZSA/IHRoaXMuYmFzZUFwcGx5KGFyZ3VtZW50cykgOiB2YWw7XG59XG5cbmZ1bmN0aW9uIEpzVmlld3NFcnJvcihtZXNzYWdlKSB7XG5cdC8vIEVycm9yIGV4Y2VwdGlvbiB0eXBlIGZvciBKc1ZpZXdzL0pzUmVuZGVyXG5cdC8vIE92ZXJyaWRlIG9mICQudmlld3Muc3ViLkVycm9yIGlzIHBvc3NpYmxlXG5cdHRoaXMubmFtZSA9ICgkLmxpbmsgPyBcIkpzVmlld3NcIiA6IFwiSnNSZW5kZXJcIikgKyBcIiBFcnJvclwiO1xuXHR0aGlzLm1lc3NhZ2UgPSBtZXNzYWdlIHx8IHRoaXMubmFtZTtcbn1cblxuZnVuY3Rpb24gJGV4dGVuZCh0YXJnZXQsIHNvdXJjZSkge1xuXHRpZiAodGFyZ2V0KSB7XG5cdFx0Zm9yICh2YXIgbmFtZSBpbiBzb3VyY2UpIHtcblx0XHRcdHRhcmdldFtuYW1lXSA9IHNvdXJjZVtuYW1lXTtcblx0XHR9XG5cdFx0cmV0dXJuIHRhcmdldDtcblx0fVxufVxuXG4oSnNWaWV3c0Vycm9yLnByb3RvdHlwZSA9IG5ldyBFcnJvcigpKS5jb25zdHJ1Y3RvciA9IEpzVmlld3NFcnJvcjtcblxuLy89PT09PT09PT09PT09PT09PT09PT09PT09PSBUb3AtbGV2ZWwgZnVuY3Rpb25zID09PT09PT09PT09PT09PT09PT09PT09PT09XG5cbi8vPT09PT09PT09PT09PT09PT09PVxuLy8gdmlld3MuZGVsaW1pdGVyc1xuLy89PT09PT09PT09PT09PT09PT09XG5cbmZ1bmN0aW9uICR2aWV3c0RlbGltaXRlcnMob3BlbkNoYXJzLCBjbG9zZUNoYXJzLCBsaW5rKSB7XG5cdC8vIFNldCB0aGUgdGFnIG9wZW5pbmcgYW5kIGNsb3NpbmcgZGVsaW1pdGVycyBhbmQgJ2xpbmsnIGNoYXJhY3Rlci4gRGVmYXVsdCBpcyBcInt7XCIsIFwifX1cIiBhbmQgXCJeXCJcblx0Ly8gb3BlbkNoYXJzLCBjbG9zZUNoYXJzOiBvcGVuaW5nIGFuZCBjbG9zaW5nIHN0cmluZ3MsIGVhY2ggd2l0aCB0d28gY2hhcmFjdGVyc1xuXHRpZiAoIW9wZW5DaGFycykge1xuXHRcdHJldHVybiAkc3ViU2V0dGluZ3MuZGVsaW1pdGVycztcblx0fVxuXHRpZiAoJGlzQXJyYXkob3BlbkNoYXJzKSkge1xuXHRcdHJldHVybiAkdmlld3NEZWxpbWl0ZXJzLmFwcGx5KCR2aWV3cywgb3BlbkNoYXJzKTtcblx0fVxuXG5cdCRzdWJTZXR0aW5ncy5kZWxpbWl0ZXJzID0gW29wZW5DaGFycywgY2xvc2VDaGFycywgbGlua0NoYXIgPSBsaW5rID8gbGluay5jaGFyQXQoMCkgOiBsaW5rQ2hhcl07XG5cblx0ZGVsaW1PcGVuQ2hhcjAgPSBvcGVuQ2hhcnMuY2hhckF0KDApOyAvLyBFc2NhcGUgdGhlIGNoYXJhY3RlcnMgLSBzaW5jZSB0aGV5IGNvdWxkIGJlIHJlZ2V4IHNwZWNpYWwgY2hhcmFjdGVyc1xuXHRkZWxpbU9wZW5DaGFyMSA9IG9wZW5DaGFycy5jaGFyQXQoMSk7XG5cdGRlbGltQ2xvc2VDaGFyMCA9IGNsb3NlQ2hhcnMuY2hhckF0KDApO1xuXHRkZWxpbUNsb3NlQ2hhcjEgPSBjbG9zZUNoYXJzLmNoYXJBdCgxKTtcblx0b3BlbkNoYXJzID0gXCJcXFxcXCIgKyBkZWxpbU9wZW5DaGFyMCArIFwiKFxcXFxcIiArIGxpbmtDaGFyICsgXCIpP1xcXFxcIiArIGRlbGltT3BlbkNoYXIxOyAvLyBEZWZhdWx0IGlzIFwie157XCJcblx0Y2xvc2VDaGFycyA9IFwiXFxcXFwiICsgZGVsaW1DbG9zZUNoYXIwICsgXCJcXFxcXCIgKyBkZWxpbUNsb3NlQ2hhcjE7ICAgICAgICAgICAgICAgICAgIC8vIERlZmF1bHQgaXMgXCJ9fVwiXG5cdC8vIEJ1aWxkIHJlZ2V4IHdpdGggbmV3IGRlbGltaXRlcnNcblx0Ly8gICAgICAgICAgW3RhZyAgICAoZm9sbG93ZWQgYnkgLyBzcGFjZSBvciB9KSAgb3IgY3Z0citjb2xvbiBvciBodG1sIG9yIGNvZGVdIGZvbGxvd2VkIGJ5IHNwYWNlK3BhcmFtcyB0aGVuIGNvbnZlcnRCYWNrP1xuXHRyVGFnID0gXCIoPzooXFxcXHcrKD89W1xcXFwvXFxcXHNcXFxcXCIgKyBkZWxpbUNsb3NlQ2hhcjAgKyBcIl0pKXwoXFxcXHcrKT8oOil8KD4pfChcXFxcKikpXFxcXHMqKCg/OlteXFxcXFwiXG5cdFx0KyBkZWxpbUNsb3NlQ2hhcjAgKyBcIl18XFxcXFwiICsgZGVsaW1DbG9zZUNoYXIwICsgXCIoPyFcXFxcXCIgKyBkZWxpbUNsb3NlQ2hhcjEgKyBcIikpKj8pXCI7XG5cblx0Ly8gTWFrZSByVGFnIGF2YWlsYWJsZSB0byBKc1ZpZXdzIChvciBvdGhlciBjb21wb25lbnRzKSBmb3IgcGFyc2luZyBiaW5kaW5nIGV4cHJlc3Npb25zXG5cdCRzdWIuclRhZyA9IFwiKD86XCIgKyByVGFnICsgXCIpXCI7XG5cdC8vICAgICAgICAgICAgICAgICAgICAgICAgeyBePyB7ICAgdGFnK3BhcmFtcyBzbGFzaD8gIG9yIGNsb3NpbmdUYWcgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvciBjb21tZW50XG5cdHJUYWcgPSBuZXcgUmVnRXhwKFwiKD86XCIgKyBvcGVuQ2hhcnMgKyByVGFnICsgXCIoXFxcXC8pP3xcXFxcXCIgKyBkZWxpbU9wZW5DaGFyMCArIFwiKFxcXFxcIiArIGxpbmtDaGFyICsgXCIpP1xcXFxcIiArIGRlbGltT3BlbkNoYXIxICsgXCIoPzooPzpcXFxcLyhcXFxcdyspKVxcXFxzKnwhLS1bXFxcXHNcXFxcU10qPy0tKSlcIiArIGNsb3NlQ2hhcnMsIFwiZ1wiKTtcblxuXHQvLyBEZWZhdWx0OiAgYmluZCAgICAgdGFnTmFtZSAgICAgICAgIGN2dCAgIGNsbiBodG1sIGNvZGUgICAgcGFyYW1zICAgICAgICAgICAgc2xhc2ggICBiaW5kMiAgICAgICAgIGNsb3NlQmxrICBjb21tZW50XG5cdC8vICAgICAgLyg/OnsoXFxeKT97KD86KFxcdysoPz1bXFwvXFxzfV0pKXwoXFx3Kyk/KDopfCg+KXwoXFwqKSlcXHMqKCg/OltefV18fSg/IX0pKSo/KShcXC8pP3x7KFxcXik/eyg/Oig/OlxcLyhcXHcrKSlcXHMqfCEtLVtcXHNcXFNdKj8tLSkpfX1cblxuXHQkc3ViLnJUbXBsID0gbmV3IFJlZ0V4cChcIl5cXFxcc3xcXFxccyR8PC4qPnwoW15cXFxcXFxcXF18Xilbe31dfFwiICsgb3BlbkNoYXJzICsgXCIuKlwiICsgY2xvc2VDaGFycyk7XG5cdC8vICRzdWIuclRtcGwgbG9va3MgZm9yIGluaXRpYWwgb3IgZmluYWwgd2hpdGUgc3BhY2UsIGh0bWwgdGFncyBvciB7IG9yIH0gY2hhciBub3QgcHJlY2VkZWQgYnkgXFxcXCwgb3IgSnNSZW5kZXIgdGFncyB7e3h4eH19LlxuXHQvLyBFYWNoIG9mIHRoZXNlIHN0cmluZ3MgYXJlIGNvbnNpZGVyZWQgTk9UIHRvIGJlIGpRdWVyeSBzZWxlY3RvcnNcblx0cmV0dXJuICR2aWV3c1NldHRpbmdzO1xufVxuXG4vLz09PT09PT09PVxuLy8gVmlldy5nZXRcbi8vPT09PT09PT09XG5cbmZ1bmN0aW9uIGdldFZpZXcoaW5uZXIsIHR5cGUpIHsgLy92aWV3LmdldChpbm5lciwgdHlwZSlcblx0aWYgKCF0eXBlICYmIGlubmVyICE9PSB0cnVlKSB7XG5cdFx0Ly8gdmlldy5nZXQodHlwZSlcblx0XHR0eXBlID0gaW5uZXI7XG5cdFx0aW5uZXIgPSB1bmRlZmluZWQ7XG5cdH1cblxuXHR2YXIgdmlld3MsIGksIGwsIGZvdW5kLFxuXHRcdHZpZXcgPSB0aGlzLFxuXHRcdHJvb3QgPSAhdHlwZSB8fCB0eXBlID09PSBcInJvb3RcIjtcblx0XHQvLyBJZiB0eXBlIGlzIHVuZGVmaW5lZCwgcmV0dXJucyByb290IHZpZXcgKHZpZXcgdW5kZXIgdG9wIHZpZXcpLlxuXG5cdGlmIChpbm5lcikge1xuXHRcdC8vIEdvIHRocm91Z2ggdmlld3MgLSB0aGlzIG9uZSwgYW5kIGFsbCBuZXN0ZWQgb25lcywgZGVwdGgtZmlyc3QgLSBhbmQgcmV0dXJuIGZpcnN0IG9uZSB3aXRoIGdpdmVuIHR5cGUuXG5cdFx0Ly8gSWYgdHlwZSBpcyB1bmRlZmluZWQsIGkuZS4gdmlldy5nZXQodHJ1ZSksIHJldHVybiBmaXJzdCBjaGlsZCB2aWV3LlxuXHRcdGZvdW5kID0gdHlwZSAmJiB2aWV3LnR5cGUgPT09IHR5cGUgJiYgdmlldztcblx0XHRpZiAoIWZvdW5kKSB7XG5cdFx0XHR2aWV3cyA9IHZpZXcudmlld3M7XG5cdFx0XHRpZiAodmlldy5fLnVzZUtleSkge1xuXHRcdFx0XHRmb3IgKGkgaW4gdmlld3MpIHtcblx0XHRcdFx0XHRpZiAoZm91bmQgPSB0eXBlID8gdmlld3NbaV0uZ2V0KGlubmVyLCB0eXBlKSA6IHZpZXdzW2ldKSB7XG5cdFx0XHRcdFx0XHRicmVhaztcblx0XHRcdFx0XHR9XG5cdFx0XHRcdH1cblx0XHRcdH0gZWxzZSB7XG5cdFx0XHRcdGZvciAoaSA9IDAsIGwgPSB2aWV3cy5sZW5ndGg7ICFmb3VuZCAmJiBpIDwgbDsgaSsrKSB7XG5cdFx0XHRcdFx0Zm91bmQgPSB0eXBlID8gdmlld3NbaV0uZ2V0KGlubmVyLCB0eXBlKSA6IHZpZXdzW2ldO1xuXHRcdFx0XHR9XG5cdFx0XHR9XG5cdFx0fVxuXHR9IGVsc2UgaWYgKHJvb3QpIHtcblx0XHQvLyBGaW5kIHJvb3Qgdmlldy4gKHZpZXcgd2hvc2UgcGFyZW50IGlzIHRvcCB2aWV3KVxuXHRcdGZvdW5kID0gdmlldy5yb290O1xuXHR9IGVsc2Uge1xuXHRcdHdoaWxlICh2aWV3ICYmICFmb3VuZCkge1xuXHRcdFx0Ly8gR28gdGhyb3VnaCB2aWV3cyAtIHRoaXMgb25lLCBhbmQgYWxsIHBhcmVudCBvbmVzIC0gYW5kIHJldHVybiBmaXJzdCBvbmUgd2l0aCBnaXZlbiB0eXBlLlxuXHRcdFx0Zm91bmQgPSB2aWV3LnR5cGUgPT09IHR5cGUgPyB2aWV3IDogdW5kZWZpbmVkO1xuXHRcdFx0dmlldyA9IHZpZXcucGFyZW50O1xuXHRcdH1cblx0fVxuXHRyZXR1cm4gZm91bmQ7XG59XG5cbmZ1bmN0aW9uIGdldE5lc3RlZEluZGV4KCkge1xuXHR2YXIgdmlldyA9IHRoaXMuZ2V0KFwiaXRlbVwiKTtcblx0cmV0dXJuIHZpZXcgPyB2aWV3LmluZGV4IDogdW5kZWZpbmVkO1xufVxuXG5nZXROZXN0ZWRJbmRleC5kZXBlbmRzID0gZnVuY3Rpb24oKSB7XG5cdHJldHVybiBbdGhpcy5nZXQoXCJpdGVtXCIpLCBcImluZGV4XCJdO1xufTtcblxuZnVuY3Rpb24gZ2V0SW5kZXgoKSB7XG5cdHJldHVybiB0aGlzLmluZGV4O1xufVxuXG5nZXRJbmRleC5kZXBlbmRzID0gXCJpbmRleFwiO1xuXG4vLz09PT09PT09PT1cbi8vIFZpZXcuaGxwXG4vLz09PT09PT09PT1cblxuZnVuY3Rpb24gY29udGV4dFBhcmFtZXRlcihrZXksIHZhbHVlLCBpc0NvbnRleHRDYikge1xuXHQvLyBIZWxwZXIgbWV0aG9kIGNhbGxlZCBhcyB2aWV3LmN0eFBybShrZXkpIGZvciBoZWxwZXJzIG9yIHRlbXBsYXRlIHBhcmFtZXRlcnMgfmZvbyAtIGZyb20gY29tcGlsZWQgdGVtcGxhdGUgb3IgZnJvbSBjb250ZXh0IGNhbGxiYWNrXG5cdHZhciB3cmFwcGVkLCBkZXBzLCByZXMsIG9ic0N0eFBybSxcblx0XHRzdG9yZVZpZXcgPSB0aGlzLFxuXHRcdGlzVXBkYXRlID0gIWlzUmVuZGVyQ2FsbCAmJiB2YWx1ZSAhPT0gdW5kZWZpbmVkLFxuXHRcdHN0b3JlID0gc3RvcmVWaWV3LmN0eDtcblxuXHRpZiAoa2V5IGluIHN0b3JlIHx8IGtleSBpbiAoc3RvcmUgPSAkaGVscGVycykpIHtcblx0XHRyZXMgPSBzdG9yZSAmJiBzdG9yZVtrZXldO1xuXHRcdGlmIChrZXkgPT09IFwidGFnXCIgfHwga2V5ID09PSBcInJvb3RcIiB8fCBrZXkgPT09IFwicGFyZW50VGFnc1wiIHx8IHN0b3JlVmlldy5fLml0ID09PSBrZXkgKSB7XG5cdFx0XHRyZXR1cm4gcmVzO1xuXHRcdH1cblx0fSBlbHNlIHtcblx0XHRzdG9yZSA9IHVuZGVmaW5lZDtcblx0fVxuXHRpZiAoIXJlcyB8fCAhJGlzRnVuY3Rpb24ocmVzKSAmJiBzdG9yZVZpZXcubGlua2VkIHx8IHN0b3JlVmlldy50YWdDdHgpIHsgLy8gRGF0YS1saW5rZWQgdmlldywgb3IgdGFnIGluc3RhbmNlXG5cdFx0aWYgKCFyZXMgfHwgIXJlcy5fY3hwKSB7XG5cdFx0XHQvLyBOb3QgYSBjb250ZXh0dWFsIHBhcmFtZXRlclxuXHRcdFx0aWYgKHN0b3JlICE9PSAkaGVscGVycykge1xuXHRcdFx0XHQvLyBTZXQgc3RvcmVWaWV3IHRvIHRhZyAoaWYgdGhpcyBpcyBhIHRhZy5jdHhQcm0oKSBjYWxsKSBvciB0byByb290IHZpZXcgKFwiZGF0YVwiIHZpZXcgb2YgbGlua2VkIHRlbXBsYXRlKVxuXHRcdFx0XHRzdG9yZVZpZXcgPSBzdG9yZVZpZXcudGFnQ3R4XG5cdFx0XHRcdFx0PyBzdG9yZVZpZXcgLy8gSXMgYSB0YWcsIG5vdCBhIHZpZXdcblx0XHRcdFx0XHQ6IChzdG9yZVZpZXcgPSBzdG9yZVZpZXcuc2NvcGUgfHwgc3RvcmVWaWV3LCAhc3RvcmVWaWV3LmlzVG9wICYmIHN0b3JlVmlldy5jdHgudGFnIHx8IHN0b3JlVmlldyk7XG5cdFx0XHRcdHN0b3JlID0gc3RvcmVWaWV3Ll9vY3BzO1xuXHRcdFx0XHRyZXMgPSBzdG9yZSAmJiBzdG9yZVtrZXldIHx8IHJlcztcblx0XHRcdH1cblx0XHRcdGlmICghKHJlcyAmJiByZXMuX2N4cCkgJiYgKGlzQ29udGV4dENiIHx8IGlzVXBkYXRlKSkge1xuXHRcdFx0XHRyZXMgPSAkc3ViLl9jcmNwKGtleSwgcmVzLCBzdG9yZVZpZXcsIHN0b3JlKTsgLy8gQ3JlYXRlIG9ic2VydmFibGUgY29udGV4dHVhbCBwYXJhbWV0ZXJcblx0XHRcdH1cblx0XHR9XG5cdFx0aWYgKG9ic0N0eFBybSA9IHJlcyAmJiByZXMuX2N4cCkge1xuXHRcdFx0aWYgKGlzVXBkYXRlKSB7XG5cdFx0XHRcdHJldHVybiAkc3ViLl91Y3Aoa2V5LCB2YWx1ZSwgc3RvcmVWaWV3LCBvYnNDdHhQcm0pOyAvLyBVcGRhdGUgb2JzZXJ2YWJsZSBjb250ZXh0dWFsIHBhcmFtZXRlclxuXHRcdFx0fVxuXHRcdFx0aWYgKGlzQ29udGV4dENiKSB7IC8vIElmIHRoaXMgaGVscGVyIHJlc291cmNlIGlzIGFuIG9ic2VydmFibGUgY29udGV4dHVhbCBwYXJhbWV0ZXJcblx0XHRcdFx0Ly8gSW4gYSBjb250ZXh0IGNhbGxiYWNrIGZvciBhIGNvbnRleHR1YWwgcGFyYW0sIHJldHVybiB0aGUgW3ZpZXcsIGRlcGVuZGVuY2llcy4uLl0gYXJyYXkgLSBuZWVkZWQgZm9yIG9ic2VydmUgY2FsbFxuXHRcdFx0XHRkZXBzID0gcmVzWzFdID8gJHN1Yi5fY2VvKHJlc1sxXS5kZXBzKSA6IFtfb2NwXTsgLy8gZm4gZGVwcyAod2l0aCBhbnkgZXhwck9icyBjbG9uZWQgdXNpbmcgJHN1Yi5fY2VvKVxuXHRcdFx0XHRkZXBzLnVuc2hpZnQocmVzWzBdKTsgLy8gdmlld1xuXHRcdFx0XHRkZXBzLl9jeHAgPSBvYnNDdHhQcm07XG5cdFx0XHRcdHJldHVybiBkZXBzO1xuXHRcdFx0fVxuXHRcdFx0cmVzID0gcmVzWzFdIC8vIGxpbmtGbiBmb3IgY29tcGlsZWQgZXhwcmVzc2lvblxuXHRcdFx0XHQ/IG9ic0N0eFBybS50YWcgJiYgb2JzQ3R4UHJtLnRhZy5jdnRBcmdzXG5cdFx0XHRcdFx0PyBvYnNDdHhQcm0udGFnLmN2dEFyZ3ModHJ1ZSwgb2JzQ3R4UHJtLnRhZ0Vsc2UpW29ic0N0eFBybS5pbmRdIC8vID0gdGFnLmJuZEFyZ3MoKSAtIGZvciB0YWcgY29udGV4dHVhbCBwYXJhbWV0ZXJcblx0XHRcdFx0XHQ6IHJlc1sxXShyZXNbMF0uZGF0YSwgcmVzWzBdLCAkc3ViKSAgICAvLyA9IGZuKGRhdGEsIHZpZXcsICRzdWIpIGZvciBjb21waWxlZCBiaW5kaW5nIGV4cHJlc3Npb25cblx0XHRcdFx0OiByZXNbMF0uX29jcDsgLy8gT2JzZXJ2YWJsZSBjb250ZXh0dWFsIHBhcmFtZXRlciAodW5pbml0aWFsaXplZCwgb3IgaW5pdGlhbGl6ZWQgYXMgc3RhdGljIGV4cHJlc3Npb24sIHNvIG5vIHBhdGggZGVwZW5kZW5jaWVzKVxuXHRcdH1cblx0fVxuXHRpZiAocmVzICYmICRpc0Z1bmN0aW9uKHJlcykpIHtcblx0XHQvLyBJZiBhIGhlbHBlciBpcyBvZiB0eXBlIGZ1bmN0aW9uLCBhbmQgbm90IGFscmVhZHkgd3JhcHBlZCwgd2Ugd2lsbCB3cmFwIGl0LCBzbyBpZiBjYWxsZWQgd2l0aCBubyB0aGlzIHBvaW50ZXIgaXQgd2lsbCBiZSBjYWxsZWQgd2l0aCB0aGVcblx0XHQvLyB2aWV3IGFzICd0aGlzJyBjb250ZXh0LiBJZiB0aGUgaGVscGVyIH5mb28oKSB3YXMgaW4gYSBkYXRhLWxpbmsgZXhwcmVzc2lvbiwgdGhlIHZpZXcgd2lsbCBoYXZlIGEgJ3RlbXBvcmFyeScgbGlua0N0eCBwcm9wZXJ0eSB0b28uXG5cdFx0Ly8gTm90ZSB0aGF0IGhlbHBlciBmdW5jdGlvbnMgb24gZGVlcGVyIHBhdGhzIHdpbGwgaGF2ZSBzcGVjaWZpYyB0aGlzIHBvaW50ZXJzLCBmcm9tIHRoZSBwcmVjZWRpbmcgcGF0aC5cblx0XHQvLyBGb3IgZXhhbXBsZSwgfnV0aWwuZm9vKCkgd2lsbCBoYXZlIHRoZSB+dXRpbCBvYmplY3QgYXMgJ3RoaXMnIHBvaW50ZXJcblx0XHR3cmFwcGVkID0gZnVuY3Rpb24oKSB7XG5cdFx0XHRyZXR1cm4gcmVzLmFwcGx5KCghdGhpcyB8fCB0aGlzID09PSBnbG9iYWwpID8gc3RvcmVWaWV3IDogdGhpcywgYXJndW1lbnRzKTtcblx0XHR9O1xuXHRcdCRleHRlbmQod3JhcHBlZCwgcmVzKTsgLy8gQXR0YWNoIHNhbWUgZXhwYW5kb3MgKGlmIGFueSkgdG8gdGhlIHdyYXBwZWQgZnVuY3Rpb25cblx0XHR3cmFwcGVkLl92dyA9IHN0b3JlVmlldztcblx0fVxuXHRyZXR1cm4gd3JhcHBlZCB8fCByZXM7XG59XG5cbmZ1bmN0aW9uIGdldFRlbXBsYXRlKHRtcGwpIHtcblx0cmV0dXJuIHRtcGwgJiYgKHRtcGwuZm5cblx0XHQ/IHRtcGxcblx0XHQ6IHRoaXMuZ2V0UnNjKFwidGVtcGxhdGVzXCIsIHRtcGwpIHx8ICR0ZW1wbGF0ZXModG1wbCkpOyAvLyBub3QgeWV0IGNvbXBpbGVkXG59XG5cbi8vPT09PT09PT09PT09PT1cbi8vIHZpZXdzLl9jbnZ0XG4vLz09PT09PT09PT09PT09XG5cbmZ1bmN0aW9uIGNvbnZlcnRWYWwoY29udmVydGVyLCB2aWV3LCB0YWdDdHgsIG9uRXJyb3IpIHtcblx0Ly8gQ2FsbGVkIGZyb20gY29tcGlsZWQgdGVtcGxhdGUgY29kZSBmb3Ige3s6fX1cblx0Ly8gc2VsZiBpcyB0ZW1wbGF0ZSBvYmplY3Qgb3IgbGlua0N0eCBvYmplY3Rcblx0dmFyIHRhZywgdmFsdWUsIGFyZ3NMZW4sIGJpbmRUbyxcblx0XHQvLyBJZiB0YWdDdHggaXMgYW4gaW50ZWdlciwgdGhlbiBpdCBpcyB0aGUga2V5IGZvciB0aGUgY29tcGlsZWQgZnVuY3Rpb24gdG8gcmV0dXJuIHRoZSBib3VuZFRhZyB0YWdDdHhcblx0XHRib3VuZFRhZyA9IHR5cGVvZiB0YWdDdHggPT09IFwibnVtYmVyXCIgJiYgdmlldy50bXBsLmJuZHNbdGFnQ3R4LTFdLFxuXHRcdGxpbmtDdHggPSB2aWV3LmxpbmtDdHg7IC8vIEZvciBkYXRhLWxpbms9XCJ7Y3Z0Oi4uLn1cIi4uLlxuXG5cdGlmIChvbkVycm9yID09PSB1bmRlZmluZWQgJiYgYm91bmRUYWcgJiYgYm91bmRUYWcuX2xyKSB7IC8vIGxhdGVSZW5kZXJcblx0XHRvbkVycm9yID0gXCJcIjtcblx0fVxuXHRpZiAob25FcnJvciAhPT0gdW5kZWZpbmVkKSB7XG5cdFx0dGFnQ3R4ID0gb25FcnJvciA9IHtwcm9wczoge30sIGFyZ3M6IFtvbkVycm9yXX07XG5cdH0gZWxzZSBpZiAoYm91bmRUYWcpIHtcblx0XHR0YWdDdHggPSBib3VuZFRhZyh2aWV3LmRhdGEsIHZpZXcsICRzdWIpO1xuXHR9XG5cdGJvdW5kVGFnID0gYm91bmRUYWcuX2JkICYmIGJvdW5kVGFnO1xuXHR2YWx1ZSA9IHRhZ0N0eC5hcmdzWzBdO1xuXHRpZiAoY29udmVydGVyIHx8IGJvdW5kVGFnKSB7XG5cdFx0dGFnID0gbGlua0N0eCAmJiBsaW5rQ3R4LnRhZztcblx0XHR0YWdDdHgudmlldyA9IHZpZXc7XG5cdFx0aWYgKCF0YWcpIHtcblx0XHRcdHRhZyA9ICRleHRlbmQobmV3ICRzdWIuX3RnKCksIHtcblx0XHRcdFx0Xzoge1xuXHRcdFx0XHRcdGJuZDogYm91bmRUYWcsXG5cdFx0XHRcdFx0dW5saW5rZWQ6IHRydWVcblx0XHRcdFx0fSxcblx0XHRcdFx0aW5saW5lOiAhbGlua0N0eCxcblx0XHRcdFx0dGFnTmFtZTogXCI6XCIsXG5cdFx0XHRcdGNvbnZlcnQ6IGNvbnZlcnRlcixcblx0XHRcdFx0ZmxvdzogdHJ1ZSxcblx0XHRcdFx0dGFnQ3R4OiB0YWdDdHhcblx0XHRcdH0pO1xuXHRcdFx0YXJnc0xlbiA9IHRhZ0N0eC5hcmdzLmxlbmd0aDtcblx0XHRcdGlmIChhcmdzTGVuPjEpIHtcblx0XHRcdFx0YmluZFRvID0gdGFnLmJpbmRUbyA9IFtdO1xuXHRcdFx0XHR3aGlsZSAoYXJnc0xlbi0tKSB7XG5cdFx0XHRcdFx0YmluZFRvLnVuc2hpZnQoYXJnc0xlbik7IC8vIEJpbmQgdG8gYWxsIHRoZSBhcmd1bWVudHMgLSBnZW5lcmF0ZSBiaW5kVG8gYXJyYXk6IFswLDEsMi4uLl1cblx0XHRcdFx0fVxuXHRcdFx0fVxuXHRcdFx0aWYgKGxpbmtDdHgpIHtcblx0XHRcdFx0bGlua0N0eC50YWcgPSB0YWc7XG5cdFx0XHRcdHRhZy5saW5rQ3R4ID0gbGlua0N0eDtcblx0XHRcdH1cblx0XHRcdHRhZ0N0eC5jdHggPSBleHRlbmRDdHgodGFnQ3R4LmN0eCwgKGxpbmtDdHggPyBsaW5rQ3R4LnZpZXcgOiB2aWV3KS5jdHgpO1xuXHRcdFx0dGFnSGFuZGxlcnNGcm9tUHJvcHModGFnLCB0YWdDdHgpO1xuXHRcdH1cblx0XHR0YWcuX2VyID0gb25FcnJvciAmJiB2YWx1ZTtcblx0XHR0YWcuY3R4ID0gdGFnQ3R4LmN0eCB8fCB0YWcuY3R4IHx8IHt9O1xuXHRcdHRhZ0N0eC5jdHggPSB1bmRlZmluZWQ7XG5cblx0XHR2YWx1ZSA9IHRhZy5jdnRBcmdzKClbMF07IC8vIElmIHRoZXJlIGlzIGEgY29udmVydEJhY2sgYnV0IG5vIGNvbnZlcnQsIGNvbnZlcnRlciB3aWxsIGJlIFwidHJ1ZVwiXG5cdH1cblxuXHQvLyBDYWxsIG9uUmVuZGVyICh1c2VkIGJ5IEpzVmlld3MgaWYgcHJlc2VudCwgdG8gYWRkIGJpbmRpbmcgYW5ub3RhdGlvbnMgYXJvdW5kIHJlbmRlcmVkIGNvbnRlbnQpXG5cdHZhbHVlID0gYm91bmRUYWcgJiYgdmlldy5fLm9uUmVuZGVyXG5cdFx0PyB2aWV3Ll8ub25SZW5kZXIodmFsdWUsIHZpZXcsIHRhZylcblx0XHQ6IHZhbHVlO1xuXHRyZXR1cm4gdmFsdWUgIT0gdW5kZWZpbmVkID8gdmFsdWUgOiBcIlwiO1xufVxuXG5mdW5jdGlvbiBjb252ZXJ0QXJncyhib3VuZCwgdGFnRWxzZSkgeyAvLyB0YWcuY3Z0QXJncygpXG5cdHZhciBsLCBrZXksIGJvdW5kQXJncywgYXJncywgYmluZFRvLCB0YWcsIGNvbnZlcnRlcixcblx0XHR0YWdDdHggPSB0aGlzO1xuXG5cdGlmICh0YWdDdHgudGFnTmFtZSkge1xuXHRcdHRhZyA9IHRhZ0N0eDtcblx0XHR0YWdDdHggPSB0YWcudGFnQ3R4cyA/IHRhZy50YWdDdHhzW3RhZ0Vsc2UgfHwgMF0gOiB0YWcudGFnQ3R4O1xuXHR9IGVsc2Uge1xuXHRcdHRhZyA9IHRhZ0N0eC50YWc7XG5cdH1cblxuXHRiaW5kVG8gPSB0YWcuYmluZFRvO1xuXHRhcmdzID0gdGFnQ3R4LmFyZ3M7XG5cblx0aWYgKChjb252ZXJ0ZXIgPSB0YWcuY29udmVydCkgJiYgXCJcIiArIGNvbnZlcnRlciA9PT0gY29udmVydGVyKSB7XG5cdFx0Y29udmVydGVyID0gY29udmVydGVyID09PSBcInRydWVcIlxuXHRcdFx0PyB1bmRlZmluZWRcblx0XHRcdDogKHRhZ0N0eC52aWV3LmdldFJzYyhcImNvbnZlcnRlcnNcIiwgY29udmVydGVyKSB8fCBlcnJvcihcIlVua25vd24gY29udmVydGVyOiAnXCIgKyBjb252ZXJ0ZXIgKyBcIidcIikpO1xuXHR9XG5cblx0aWYgKGJvdW5kICYmIGJvdW5kLmxlbmd0aCkge1xuXHQgYXJncyA9IGJvdW5kO1xuXHR9IGVsc2Uge1xuXHRcdGlmIChjb252ZXJ0ZXIgJiYgIWJvdW5kKSB7IC8vIElmIHRoZXJlIGlzIGEgY29udmVydGVyLCB1c2UgYSBjb3B5IG9mIHRoZSB0YWdDdHguYXJncyBhcnJheSBmb3IgcmVuZGVyaW5nLCBhbmQgcmVwbGFjZSB0aGUgYXJnc1swXSBpblxuXHRcdFx0YXJncyA9IGFyZ3Muc2xpY2UoKTsgLy8gdGhlIGNvcGllZCBhcnJheSB3aXRoIHRoZSBjb252ZXJ0ZWQgdmFsdWUuIEJ1dCB3ZSBkbyBub3QgbW9kaWZ5IHRoZSB2YWx1ZSBvZiB0YWcudGFnQ3R4LmFyZ3NbMF0gKHRoZSBvcmlnaW5hbCBhcmdzIGFycmF5KVxuXHRcdH1cblx0XHRpZiAoYmluZFRvKSB7IC8vIEdldCB0aGUgdmFsdWVzIG9mIHRoZSBib3VuZEFyZ3Ncblx0XHRcdGJvdW5kQXJncyA9IFtdO1xuXHRcdFx0bCA9IGJpbmRUby5sZW5ndGg7XG5cdFx0XHR3aGlsZSAobC0tKSB7XG5cdFx0XHRcdGtleSA9IGJpbmRUb1tsXTtcblx0XHRcdFx0Ym91bmRBcmdzLnVuc2hpZnQoYXJnT3JQcm9wKHRhZ0N0eCwga2V5KSk7XG5cdFx0XHR9XG5cdFx0XHRpZiAoYm91bmQpIHtcblx0XHRcdFx0YXJncyA9IGJvdW5kQXJnczsgLy8gQ2FsbCB0byBjb252ZXJ0Qm91bmRBcmdzKCkgLSByZXR1cm5zIHRoZSBib3VuZEFyZ3Ncblx0XHRcdH1cblx0XHR9XG5cdH1cblx0aWYgKGNvbnZlcnRlcikge1xuXHRcdGJpbmRUbyA9IGJpbmRUbyB8fCBbMF07XG5cdFx0bCA9IGJpbmRUby5sZW5ndGg7XG5cdFx0Y29udmVydGVyID0gY29udmVydGVyLmFwcGx5KHRhZywgYm91bmRBcmdzIHx8IGFyZ3MpO1xuXHRcdGlmICghJGlzQXJyYXkoY29udmVydGVyKSB8fCBjb252ZXJ0ZXIubGVuZ3RoICE9PSBsKSB7XG5cdFx0XHRjb252ZXJ0ZXIgPSBbY29udmVydGVyXTtcblx0XHRcdGJpbmRUbyA9IFswXTtcblx0XHRcdGwgPSAxO1xuXHRcdH1cblx0XHRpZiAoYm91bmQpIHsgICAgICAgIC8vIENhbGwgdG8gYm5kQXJncyBjb252ZXJ0Qm91bmRBcmdzKCkgLSBzbyBhcHBseSBjb252ZXJ0ZXIgdG8gYWxsIGJvdW5kQXJnc1xuXHRcdFx0YXJncyA9IGNvbnZlcnRlcjsgLy8gVGhlIGFycmF5IG9mIHZhbHVlcyByZXR1cm5lZCBmcm9tIHRoZSBjb252ZXJ0ZXJcblx0XHR9IGVsc2UgeyAgICAgICAgICAgIC8vIENhbGwgdG8gY3Z0QXJncygpXG5cdFx0XHR3aGlsZSAobC0tKSB7XG5cdFx0XHRcdGtleSA9IGJpbmRUb1tsXTtcblx0XHRcdFx0aWYgKCtrZXkgPT09IGtleSkge1xuXHRcdFx0XHRcdGFyZ3Nba2V5XSA9IGNvbnZlcnRlcltsXTtcblx0XHRcdFx0fVxuXHRcdFx0fVxuXHRcdH1cblx0fVxuXHRyZXR1cm4gYXJncztcbn1cblxuZnVuY3Rpb24gYXJnT3JQcm9wKGNvbnRleHQsIGtleSkge1xuXHRjb250ZXh0ID0gY29udGV4dFsra2V5ID09PSBrZXkgPyBcImFyZ3NcIiA6IFwicHJvcHNcIl07XG5cdHJldHVybiBjb250ZXh0ICYmIGNvbnRleHRba2V5XTtcbn1cblxuZnVuY3Rpb24gY29udmVydEJvdW5kQXJncyh0YWdFbHNlKSB7IC8vIHRhZy5ibmRBcmdzKClcblx0cmV0dXJuIHRoaXMuY3Z0QXJncyh0cnVlLCB0YWdFbHNlKTtcbn1cblxuLy89PT09PT09PT09PT09XG4vLyB2aWV3cy5fdGFnXG4vLz09PT09PT09PT09PT1cblxuZnVuY3Rpb24gZ2V0UmVzb3VyY2UocmVzb3VyY2VUeXBlLCBpdGVtTmFtZSkge1xuXHR2YXIgcmVzLCBzdG9yZSxcblx0XHR2aWV3ID0gdGhpcztcblx0aWYgKFwiXCIgKyBpdGVtTmFtZSA9PT0gaXRlbU5hbWUpIHtcblx0XHR3aGlsZSAoKHJlcyA9PT0gdW5kZWZpbmVkKSAmJiB2aWV3KSB7XG5cdFx0XHRzdG9yZSA9IHZpZXcudG1wbCAmJiB2aWV3LnRtcGxbcmVzb3VyY2VUeXBlXTtcblx0XHRcdHJlcyA9IHN0b3JlICYmIHN0b3JlW2l0ZW1OYW1lXTtcblx0XHRcdHZpZXcgPSB2aWV3LnBhcmVudDtcblx0XHR9XG5cdFx0cmV0dXJuIHJlcyB8fCAkdmlld3NbcmVzb3VyY2VUeXBlXVtpdGVtTmFtZV07XG5cdH1cbn1cblxuZnVuY3Rpb24gcmVuZGVyVGFnKHRhZ05hbWUsIHBhcmVudFZpZXcsIHRtcGwsIHRhZ0N0eHMsIGlzVXBkYXRlLCBvbkVycm9yKSB7XG5cdGZ1bmN0aW9uIG1ha2VBcnJheSh0eXBlKSB7XG5cdFx0dmFyIGxpbmtlZEVsZW1lbnQ7XG5cdFx0aWYgKGxpbmtlZEVsZW1lbnQgPSB0YWdbdHlwZV0pIHtcblx0XHRcdHRhZ1t0eXBlXSA9IGxpbmtlZEVsZW1lbnQgPSAkaXNBcnJheShsaW5rZWRFbGVtZW50KSA/IGxpbmtlZEVsZW1lbnQ6IFtsaW5rZWRFbGVtZW50XTtcblxuXHRcdFx0aWYgKGJpbmRUb0xlbmd0aCAhPT0gbGlua2VkRWxlbWVudC5sZW5ndGgpIHtcblx0XHRcdFx0ZXJyb3IodHlwZSArIFwiIGxlbmd0aCBub3Qgc2FtZSBhcyBiaW5kVG8gXCIpO1xuXHRcdFx0fVxuXHRcdH1cblx0fVxuXG5cdHBhcmVudFZpZXcgPSBwYXJlbnRWaWV3IHx8IHRvcFZpZXc7XG5cdHZhciB0YWcsIHRhZ18sIHRhZ0RlZiwgdGVtcGxhdGUsIHRhZ3MsIGF0dHIsIHBhcmVudFRhZywgbCwgbSwgbiwgaXRlbVJldCwgdGFnQ3R4LCB0YWdDdHhDdHgsIGN0eFBybSwgYmluZFRvLFxuXHRcdGNvbnRlbnQsIGNhbGxJbml0LCBtYXBEZWYsIHRoaXNNYXAsIGFyZ3MsIHByb3BzLCB0YWdEYXRhTWFwLCBjb250ZW50Q3R4LCBrZXksIGJpbmRUb0xlbmd0aCxcblx0XHRpID0gMCxcblx0XHRyZXQgPSBcIlwiLFxuXHRcdGxpbmtDdHggPSBwYXJlbnRWaWV3LmxpbmtDdHggfHwgMCxcblx0XHRjdHggPSBwYXJlbnRWaWV3LmN0eCxcblx0XHRwYXJlbnRUbXBsID0gdG1wbCB8fCBwYXJlbnRWaWV3LnRtcGwsXG5cdFx0Ly8gSWYgdGFnQ3R4cyBpcyBhbiBpbnRlZ2VyLCB0aGVuIGl0IGlzIHRoZSBrZXkgZm9yIHRoZSBjb21waWxlZCBmdW5jdGlvbiB0byByZXR1cm4gdGhlIGJvdW5kVGFnIHRhZ0N0eHNcblx0XHRib3VuZFRhZyA9IHR5cGVvZiB0YWdDdHhzID09PSBcIm51bWJlclwiICYmIHBhcmVudFZpZXcudG1wbC5ibmRzW3RhZ0N0eHMtMV07XG5cblx0aWYgKHRhZ05hbWUuX2lzID09PSBcInRhZ1wiKSB7XG5cdFx0dGFnID0gdGFnTmFtZTtcblx0XHR0YWdOYW1lID0gdGFnLnRhZ05hbWU7XG5cdFx0dGFnQ3R4cyA9IHRhZy50YWdDdHhzO1xuXHRcdHRlbXBsYXRlID0gdGFnLnRlbXBsYXRlO1xuXHR9IGVsc2Uge1xuXHRcdHRhZ0RlZiA9IHBhcmVudFZpZXcuZ2V0UnNjKFwidGFnc1wiLCB0YWdOYW1lKSB8fCBlcnJvcihcIlVua25vd24gdGFnOiB7e1wiICsgdGFnTmFtZSArIFwifX0gXCIpO1xuXHRcdHRlbXBsYXRlID0gdGFnRGVmLnRlbXBsYXRlO1xuXHR9XG5cdGlmIChvbkVycm9yID09PSB1bmRlZmluZWQgJiYgYm91bmRUYWcpIHtcblx0XHRpZiAoYm91bmRUYWcuX2xyID0gKHRhZ0RlZi5sYXRlUmVuZGVyIHx8IGJvdW5kVGFnLl9scikgJiYgYm91bmRUYWcuX2xyICE9PSBcImZhbHNlXCIpIHtcblx0XHRcdG9uRXJyb3IgPSBcIlwiOyAvLyBJZiBsYXRlUmVuZGVyLCBzZXQgdGVtcG9yYXJ5IG9uRXJyb3IsIHRvIHNraXAgaW5pdGlhbCByZW5kZXJpbmcgKGFuZCByZW5kZXIganVzdCBcIlwiKVxuXHRcdH1cblx0fVxuXHRpZiAob25FcnJvciAhPT0gdW5kZWZpbmVkKSB7XG5cdFx0cmV0ICs9IG9uRXJyb3I7XG5cdFx0dGFnQ3R4cyA9IG9uRXJyb3IgPSBbe3Byb3BzOiB7fSwgYXJnczogW10sIHBhcmFtczoge319XTtcblx0fSBlbHNlIGlmIChib3VuZFRhZykge1xuXHRcdHRhZ0N0eHMgPSBib3VuZFRhZyhwYXJlbnRWaWV3LmRhdGEsIHBhcmVudFZpZXcsICRzdWIpO1xuXHR9XG5cblx0bCA9IHRhZ0N0eHMubGVuZ3RoO1xuXHRmb3IgKDsgaSA8IGw7IGkrKykge1xuXHRcdHRhZ0N0eCA9IHRhZ0N0eHNbaV07XG5cdFx0Y29udGVudCA9IHRhZ0N0eC50bXBsO1xuXHRcdGlmICghbGlua0N0eCB8fCAhbGlua0N0eC50YWcgfHwgaSAmJiAhbGlua0N0eC50YWcuaW5saW5lIHx8IHRhZy5fZXIgfHwgY29udGVudCAmJiArY29udGVudD09PWNvbnRlbnQpIHtcblx0XHRcdC8vIEluaXRpYWxpemUgdGFnQ3R4XG5cdFx0XHQvLyBGb3IgYmxvY2sgdGFncywgdGFnQ3R4LnRtcGwgaXMgYW4gaW50ZWdlciA+IDBcblx0XHRcdGlmIChjb250ZW50ICYmIHBhcmVudFRtcGwudG1wbHMpIHtcblx0XHRcdFx0dGFnQ3R4LnRtcGwgPSB0YWdDdHguY29udGVudCA9IHBhcmVudFRtcGwudG1wbHNbY29udGVudCAtIDFdOyAvLyBTZXQgdGhlIHRtcGwgcHJvcGVydHkgdG8gdGhlIGNvbnRlbnQgb2YgdGhlIGJsb2NrIHRhZ1xuXHRcdFx0fVxuXHRcdFx0dGFnQ3R4LmluZGV4ID0gaTtcblx0XHRcdHRhZ0N0eC5yZW5kZXIgPSByZW5kZXJDb250ZW50O1xuXHRcdFx0dGFnQ3R4LnZpZXcgPSBwYXJlbnRWaWV3O1xuXHRcdFx0dGFnQ3R4LmN0eCA9IGV4dGVuZEN0eCh0YWdDdHguY3R4LCBjdHgpOyAvLyBDbG9uZSBhbmQgZXh0ZW5kIHBhcmVudFZpZXcuY3R4XG5cdFx0fVxuXHRcdGlmICh0bXBsID0gdGFnQ3R4LnByb3BzLnRtcGwpIHtcblx0XHRcdC8vIElmIHRoZSB0bXBsIHByb3BlcnR5IGlzIG92ZXJyaWRkZW4sIHNldCB0aGUgdmFsdWUgKHdoZW4gaW5pdGlhbGl6aW5nLCBvciwgaW4gY2FzZSBvZiBiaW5kaW5nOiBedG1wbD0uLi4sIHdoZW4gdXBkYXRpbmcpXG5cdFx0XHR0YWdDdHgudG1wbCA9IHBhcmVudFZpZXcuZ2V0VG1wbCh0bXBsKTtcblx0XHRcdHRhZ0N0eC5jb250ZW50ID0gdGFnQ3R4LmNvbnRlbnQgfHwgdGFnQ3R4LnRtcGw7XG5cdFx0fVxuXG5cdFx0aWYgKCF0YWcpIHtcblx0XHRcdC8vIFRoaXMgd2lsbCBvbmx5IGJlIGhpdCBmb3IgaW5pdGlhbCB0YWdDdHggKG5vdCBmb3Ige3tlbHNlfX0pIC0gaWYgdGhlIHRhZyBpbnN0YW5jZSBkb2VzIG5vdCBleGlzdCB5ZXRcblx0XHRcdC8vIElmIHRoZSB0YWcgaGFzIG5vdCBhbHJlYWR5IGJlZW4gaW5zdGFudGlhdGVkLCB3ZSB3aWxsIGNyZWF0ZSBhIG5ldyBpbnN0YW5jZS5cblx0XHRcdC8vIH50YWcgd2lsbCBhY2Nlc3MgdGhlIHRhZywgZXZlbiB3aXRoaW4gdGhlIHJlbmRlcmluZyBvZiB0aGUgdGVtcGxhdGUgY29udGVudCBvZiB0aGlzIHRhZy5cblx0XHRcdC8vIEZyb20gY2hpbGQvZGVzY2VuZGFudCB0YWdzLCBjYW4gYWNjZXNzIHVzaW5nIH50YWcucGFyZW50LCBvciB+cGFyZW50VGFncy50YWdOYW1lXG5cdFx0XHR0YWcgPSBuZXcgdGFnRGVmLl9jdHIoKTtcblx0XHRcdGNhbGxJbml0ID0gISF0YWcuaW5pdDtcblxuXHRcdFx0dGFnLnBhcmVudCA9IHBhcmVudFRhZyA9IGN0eCAmJiBjdHgudGFnO1xuXHRcdFx0dGFnLnRhZ0N0eHMgPSB0YWdDdHhzO1xuXHRcdFx0dGFnRGF0YU1hcCA9IHRhZy5kYXRhTWFwO1xuXG5cdFx0XHRpZiAobGlua0N0eCkge1xuXHRcdFx0XHR0YWcuaW5saW5lID0gZmFsc2U7XG5cdFx0XHRcdGxpbmtDdHgudGFnID0gdGFnO1xuXHRcdFx0XHR0YWcubGlua0N0eCA9IGxpbmtDdHg7XG5cdFx0XHR9XG5cdFx0XHRpZiAodGFnLl8uYm5kID0gYm91bmRUYWcgfHwgbGlua0N0eC5mbikge1xuXHRcdFx0XHQvLyBCb3VuZCBpZiB7Xnt0YWcuLi59fSBvciBkYXRhLWxpbms9XCJ7dGFnLi4ufVwiXG5cdFx0XHRcdHRhZy5fLmFyclZ3cyA9IHt9O1xuXHRcdFx0fSBlbHNlIGlmICh0YWcuZGF0YUJvdW5kT25seSkge1xuXHRcdFx0XHRlcnJvcih0YWdOYW1lICsgXCIgbXVzdCBiZSBkYXRhLWJvdW5kOlxcbntee1wiICsgdGFnTmFtZSArIFwifX1cIik7XG5cdFx0XHR9XG5cdFx0XHQvL1RPRE8gYmV0dGVyIHBlcmYgZm9yIGNoaWxkVGFncygpIC0ga2VlcCBjaGlsZCB0YWcudGFncyBhcnJheSwgKGFuZCByZW1vdmUgY2hpbGQsIHdoZW4gZGlzcG9zZWQpXG5cdFx0XHQvLyB0YWcudGFncyA9IFtdO1xuXHRcdH1cblx0XHR0YWdDdHhzID0gdGFnLnRhZ0N0eHM7XG5cdFx0dGFnRGF0YU1hcCA9IHRhZy5kYXRhTWFwO1xuXG5cdFx0dGFnQ3R4LnRhZyA9IHRhZztcblx0XHRpZiAodGFnRGF0YU1hcCAmJiB0YWdDdHhzKSB7XG5cdFx0XHR0YWdDdHgubWFwID0gdGFnQ3R4c1tpXS5tYXA7IC8vIENvcHkgb3ZlciB0aGUgY29tcGlsZWQgbWFwIGluc3RhbmNlIGZyb20gdGhlIHByZXZpb3VzIHRhZ0N0eHMgdG8gdGhlIHJlZnJlc2hlZCBvbmVzXG5cdFx0fVxuXHRcdGlmICghdGFnLmZsb3cpIHtcblx0XHRcdHRhZ0N0eEN0eCA9IHRhZ0N0eC5jdHggPSB0YWdDdHguY3R4IHx8IHt9O1xuXG5cdFx0XHQvLyB0YWdzIGhhc2g6IHRhZy5jdHgudGFncywgbWVyZ2VkIHdpdGggcGFyZW50Vmlldy5jdHgudGFncyxcblx0XHRcdHRhZ3MgPSB0YWcucGFyZW50cyA9IHRhZ0N0eEN0eC5wYXJlbnRUYWdzID0gY3R4ICYmIGV4dGVuZEN0eCh0YWdDdHhDdHgucGFyZW50VGFncywgY3R4LnBhcmVudFRhZ3MpIHx8IHt9O1xuXHRcdFx0aWYgKHBhcmVudFRhZykge1xuXHRcdFx0XHR0YWdzW3BhcmVudFRhZy50YWdOYW1lXSA9IHBhcmVudFRhZztcblx0XHRcdFx0Ly9UT0RPIGJldHRlciBwZXJmIGZvciBjaGlsZFRhZ3M6IHBhcmVudFRhZy50YWdzLnB1c2godGFnKTtcblx0XHRcdH1cblx0XHRcdHRhZ3NbdGFnLnRhZ05hbWVdID0gdGFnQ3R4Q3R4LnRhZyA9IHRhZztcblx0XHR9XG5cdH1cblx0aWYgKCEodGFnLl9lciA9IG9uRXJyb3IpKSB7XG5cdFx0dGFnSGFuZGxlcnNGcm9tUHJvcHModGFnLCB0YWdDdHhzWzBdKTtcblx0XHR0YWcucmVuZGVyaW5nID0ge307IC8vIFByb3ZpZGUgb2JqZWN0IGZvciBzdGF0ZSBkdXJpbmcgcmVuZGVyIGNhbGxzIHRvIHRhZyBhbmQgZWxzZXMuIChVc2VkIGJ5IHt7aWZ9fSBhbmQge3tmb3J9fS4uLilcblx0XHRmb3IgKGkgPSAwOyBpIDwgbDsgaSsrKSB7IC8vIEl0ZXJhdGUgdGFnQ3R4IGZvciBlYWNoIHt7ZWxzZX19IGJsb2NrXG5cdFx0XHR0YWdDdHggPSB0YWcudGFnQ3R4ID0gdGFnQ3R4c1tpXTtcblx0XHRcdHByb3BzID0gdGFnQ3R4LnByb3BzO1xuXHRcdFx0dGFnLmN0eCA9IHRhZ0N0eC5jdHg7XG5cblx0XHRcdGlmICghaSkge1xuXHRcdFx0XHRpZiAoY2FsbEluaXQpIHtcblx0XHRcdFx0XHR0YWcuaW5pdCh0YWdDdHgsIGxpbmtDdHgsIHRhZy5jdHgpO1xuXHRcdFx0XHRcdGNhbGxJbml0ID0gdW5kZWZpbmVkO1xuXHRcdFx0XHR9XG5cdFx0XHRcdGlmICghdGFnQ3R4LmFyZ3MubGVuZ3RoICYmIHRhZy5hcmdEZWZhdWx0ICE9PSBmYWxzZSkge1xuXHRcdFx0XHRcdHRhZ0N0eC5hcmdzID0gYXJncyA9IFt0YWdDdHgudmlldy5kYXRhXTsgLy8gTWlzc2luZyBmaXJzdCBhcmcgZGVmYXVsdHMgdG8gdGhlIGN1cnJlbnQgZGF0YSBjb250ZXh0XG5cdFx0XHRcdFx0dGFnQ3R4LnBhcmFtcy5hcmdzID0gW1wiI2RhdGFcIl07XG5cdFx0XHRcdH1cblxuXHRcdFx0XHRiaW5kVG8gPSB0YWcuYmluZFRvO1xuXG5cdFx0XHRcdGlmIChiaW5kVG8gIT09IHVuZGVmaW5lZCkge1xuXHRcdFx0XHRcdGJpbmRUbyA9IHRhZy5iaW5kVG8gPSAkaXNBcnJheShiaW5kVG8pID8gYmluZFRvIDogW2JpbmRUb107XG5cdFx0XHRcdFx0bSA9IGJpbmRUby5sZW5ndGg7XG5cdFx0XHRcdFx0d2hpbGUgKG0tLSkge1xuXHRcdFx0XHRcdFx0a2V5ID0gYmluZFRvW21dO1xuXHRcdFx0XHRcdFx0aWYgKCFpc05hTihwYXJzZUludChrZXkpKSkge1xuXHRcdFx0XHRcdFx0XHRrZXkgPSBwYXJzZUludChrZXkpOyAvLyBDb252ZXJ0IFwiMFwiIHRvIDAsICBldGMuXG5cdFx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0XHRiaW5kVG9bbV0gPSBrZXk7XG5cdFx0XHRcdFx0fVxuXHRcdFx0XHR9XG5cblx0XHRcdFx0YmluZFRvID0gdGFnLmJpbmRUbyB8fCBbMF07XG5cdFx0XHRcdGJpbmRUb0xlbmd0aCA9IGJpbmRUby5sZW5ndGg7XG5cdFx0XHRcdGlmICh0YWcuXy5ibmQpe1xuXHRcdFx0XHRcdG1ha2VBcnJheShcImxpbmtlZEVsZW1lbnRcIik7XG5cdFx0XHRcdFx0bWFrZUFycmF5KFwibGlua2VkQ3R4UGFyYW1cIik7XG5cdFx0XHRcdH1cblxuXHRcdFx0XHRpZiAobGlua0N0eCkge1xuXHRcdFx0XHRcdC8vIFNldCBhdHRyIG9uIGxpbmtDdHggdG8gZW5zdXJlIG91dHB1dHRpbmcgdG8gdGhlIGNvcnJlY3QgdGFyZ2V0IGF0dHJpYnV0ZS5cblx0XHRcdFx0XHQvLyBTZXR0aW5nIGVpdGhlciBsaW5rQ3R4LmF0dHIgb3IgdGhpcy5hdHRyIGluIHRoZSBpbml0KCkgYWxsb3dzIHBlci1pbnN0YW5jZSBjaG9pY2Ugb2YgdGFyZ2V0IGF0dHJpYi5cblx0XHRcdFx0XHRsaW5rQ3R4LmF0dHIgPSB0YWcuYXR0ciA9IGxpbmtDdHguYXR0ciB8fCB0YWcuYXR0cjtcblx0XHRcdFx0fVxuXHRcdFx0XHRhdHRyID0gdGFnLmF0dHI7XG5cdFx0XHRcdHRhZy5fLm5vVndzID0gYXR0ciAmJiBhdHRyICE9PSBIVE1MO1xuXHRcdFx0fVxuXHRcdFx0YXJncyA9IHRhZy5jdnRBcmdzKHVuZGVmaW5lZCwgaSk7XG5cdFx0XHRpZiAodGFnLmxpbmtlZEN0eFBhcmFtKSB7XG5cdFx0XHRcdG0gPSBiaW5kVG9MZW5ndGg7XG5cdFx0XHRcdHdoaWxlIChtLS0pIHtcblx0XHRcdFx0XHRpZiAoY3R4UHJtID0gdGFnLmxpbmtlZEN0eFBhcmFtW21dKSB7XG5cdFx0XHRcdFx0XHRrZXkgPSBiaW5kVG9bbV07XG5cdFx0XHRcdFx0XHQvLyBDcmVhdGUgdGFnIGNvbnRleHR1YWwgcGFyYW1ldGVyXG5cdFx0XHRcdFx0XHR0YWdDdHguY3R4W2N0eFBybV0gPSAkc3ViLl9jcChhcmdPclByb3AodGFnQ3R4LCBrZXkpLCBhcmdPclByb3AodGFnQ3R4LnBhcmFtcywga2V5KSwgdGFnQ3R4LnZpZXcsIHRhZy5fLmJuZCAmJiB7dGFnOiB0YWcsIGluZDogbSwgdGFnRWxzZTogaX0pO1xuXHRcdFx0XHRcdH1cblx0XHRcdFx0fVxuXHRcdFx0fVxuXHRcdFx0aWYgKG1hcERlZiA9IHByb3BzLmRhdGFNYXAgfHwgdGFnRGF0YU1hcCkge1xuXHRcdFx0XHRpZiAoYXJncy5sZW5ndGggfHwgcHJvcHMuZGF0YU1hcCkge1xuXHRcdFx0XHRcdHRoaXNNYXAgPSB0YWdDdHgubWFwO1xuXHRcdFx0XHRcdGlmICghdGhpc01hcCB8fCB0aGlzTWFwLnNyYyAhPT0gYXJnc1swXSB8fCBpc1VwZGF0ZSkge1xuXHRcdFx0XHRcdFx0aWYgKHRoaXNNYXAgJiYgdGhpc01hcC5zcmMpIHtcblx0XHRcdFx0XHRcdFx0dGhpc01hcC51bm1hcCgpOyAvLyBvbmx5IGNhbGxlZCBpZiBvYnNlcnZhYmxlIG1hcCAtIG5vdCB3aGVuIG9ubHkgdXNlZCBpbiBKc1JlbmRlciwgZS5nLiBieSB7e3Byb3BzfX1cblx0XHRcdFx0XHRcdH1cblx0XHRcdFx0XHRcdHRoaXNNYXAgPSB0YWdDdHgubWFwID0gbWFwRGVmLm1hcChhcmdzWzBdLCBwcm9wcywgdW5kZWZpbmVkLCAhdGFnLl8uYm5kKTtcblx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0YXJncyA9IFt0aGlzTWFwLnRndF07XG5cdFx0XHRcdH1cblx0XHRcdH1cblxuXHRcdFx0aXRlbVJldCA9IHVuZGVmaW5lZDtcblx0XHRcdGlmICh0YWcucmVuZGVyKSB7XG5cdFx0XHRcdGl0ZW1SZXQgPSB0YWcucmVuZGVyLmFwcGx5KHRhZywgYXJncyk7XG5cdFx0XHRcdGlmIChwYXJlbnRWaWV3LmxpbmtlZCAmJiBpdGVtUmV0ICYmICFyV3JhcHBlZEluVmlld01hcmtlci50ZXN0KGl0ZW1SZXQpKSB7XG5cdFx0XHRcdFx0Ly8gV2hlbiBhIHRhZyByZW5kZXJzIGNvbnRlbnQgZnJvbSB0aGUgcmVuZGVyIG1ldGhvZCwgd2l0aCBkYXRhIGxpbmtpbmcgdGhlbiB3ZSBuZWVkIHRvIHdyYXAgd2l0aCB2aWV3IG1hcmtlcnMsIGlmIGFic2VudCxcblx0XHRcdFx0XHQvLyB0byBwcm92aWRlIGEgY29udGVudFZpZXcgZm9yIHRoZSB0YWcsIHdoaWNoIHdpbGwgY29ycmVjdGx5IGRpc3Bvc2UgYmluZGluZ3MgaWYgZGVsZXRlZC4gVGhlICd0bXBsJyBmb3IgdGhpcyB2aWV3IHdpbGxcblx0XHRcdFx0XHQvLyBiZSBhIGR1bWJlZC1kb3duIHRlbXBsYXRlIHdoaWNoIHdpbGwgYWx3YXlzIHJldHVybiB0aGUgIGl0ZW1SZXQgc3RyaW5nIChubyBtYXR0ZXIgd2hhdCB0aGUgZGF0YSBpcykuIFRoZSBpdGVtUmV0IHN0cmluZ1xuXHRcdFx0XHRcdC8vIGlzIG5vdCBjb21waWxlZCBhcyB0ZW1wbGF0ZSBtYXJrdXAsIHNvIGNhbiBpbmNsdWRlIFwie3tcIiBvciBcIn19XCIgd2l0aG91dCB0cmlnZ2VyaW5nIHN5bnRheCBlcnJvcnNcblx0XHRcdFx0XHR0bXBsID0geyAvLyAnRHVtYmVkLWRvd24nIHRlbXBsYXRlIHdoaWNoIGFsd2F5cyByZW5kZXJzICdzdGF0aWMnIGl0ZW1SZXQgc3RyaW5nXG5cdFx0XHRcdFx0XHRsaW5rczogW11cblx0XHRcdFx0XHR9O1xuXHRcdFx0XHRcdHRtcGwucmVuZGVyID0gdG1wbC5mbiA9IGZ1bmN0aW9uKCkge1xuXHRcdFx0XHRcdFx0cmV0dXJuIGl0ZW1SZXQ7XG5cdFx0XHRcdFx0fTtcblx0XHRcdFx0XHRpdGVtUmV0ID0gcmVuZGVyV2l0aFZpZXdzKHRtcGwsIHBhcmVudFZpZXcuZGF0YSwgdW5kZWZpbmVkLCB0cnVlLCBwYXJlbnRWaWV3LCB1bmRlZmluZWQsIHVuZGVmaW5lZCwgdGFnKTtcblx0XHRcdFx0fVxuXHRcdFx0fVxuXHRcdFx0aWYgKCFhcmdzLmxlbmd0aCkge1xuXHRcdFx0XHRhcmdzID0gW3BhcmVudFZpZXddOyAvLyBubyBhcmd1bWVudHMgLSAoZS5nLiB7e2Vsc2V9fSkgZ2V0IGRhdGEgY29udGV4dCBmcm9tIHZpZXcuXG5cdFx0XHR9XG5cdFx0XHRpZiAoaXRlbVJldCA9PT0gdW5kZWZpbmVkKSB7XG5cdFx0XHRcdGNvbnRlbnRDdHggPSBhcmdzWzBdOyAvLyBEZWZhdWx0IGRhdGEgY29udGV4dCBmb3Igd3JhcHBlZCBibG9jayBjb250ZW50IGlzIHRoZSBmaXJzdCBhcmd1bWVudFxuXHRcdFx0XHRpZiAodGFnLmNvbnRlbnRDdHgpIHsgLy8gU2V0IHRhZy5jb250ZW50Q3R4IHRvIHRydWUsIHRvIGluaGVyaXQgcGFyZW50IGNvbnRleHQsIG9yIHRvIGEgZnVuY3Rpb24gdG8gcHJvdmlkZSBhbHRlcm5hdGUgY29udGV4dC5cblx0XHRcdFx0XHRjb250ZW50Q3R4ID0gdGFnLmNvbnRlbnRDdHggPT09IHRydWUgPyBwYXJlbnRWaWV3IDogdGFnLmNvbnRlbnRDdHgoY29udGVudEN0eCk7XG5cdFx0XHRcdH1cblx0XHRcdFx0aXRlbVJldCA9IHRhZ0N0eC5yZW5kZXIoY29udGVudEN0eCwgdHJ1ZSkgfHwgKGlzVXBkYXRlID8gdW5kZWZpbmVkIDogXCJcIik7XG5cdFx0XHR9XG5cdFx0XHQvLyBObyByZXR1cm4gdmFsdWUgZnJvbSByZW5kZXIsIGFuZCBubyB0ZW1wbGF0ZS9jb250ZW50IHRhZ0N0eC5yZW5kZXIoLi4uKSwgc28gcmV0dXJuIHVuZGVmaW5lZFxuXHRcdFx0cmV0ID0gcmV0ID8gcmV0ICsgKGl0ZW1SZXQgfHwgXCJcIikgOiBpdGVtUmV0OyAvLyBJZiBubyByZW5kZXJlZCBjb250ZW50LCB0aGlzIHdpbGwgYmUgdW5kZWZpbmVkXG5cdFx0fVxuXHRcdHRhZy5yZW5kZXJpbmcgPSB1bmRlZmluZWQ7XG5cdH1cblx0dGFnLnRhZ0N0eCA9IHRhZ0N0eHNbMF07XG5cdHRhZy5jdHggPSB0YWcudGFnQ3R4LmN0eDtcblxuXHRpZiAodGFnLl8ubm9Wd3MpIHtcblx0XHRpZiAodGFnLmlubGluZSkge1xuXHRcdFx0Ly8gaW5saW5lIHRhZyB3aXRoIGF0dHIgc2V0IHRvIFwidGV4dFwiIHdpbGwgaW5zZXJ0IEhUTUwtZW5jb2RlZCBjb250ZW50IC0gYXMgaWYgaXQgd2FzIGVsZW1lbnQtYmFzZWQgaW5uZXJUZXh0XG5cdFx0XHRyZXQgPSBhdHRyID09PSBcInRleHRcIlxuXHRcdFx0XHQ/ICRjb252ZXJ0ZXJzLmh0bWwocmV0KVxuXHRcdFx0XHQ6IFwiXCI7XG5cdFx0fVxuXHR9XG5cdHJldHVybiBib3VuZFRhZyAmJiBwYXJlbnRWaWV3Ll8ub25SZW5kZXJcblx0XHQvLyBDYWxsIG9uUmVuZGVyICh1c2VkIGJ5IEpzVmlld3MgaWYgcHJlc2VudCwgdG8gYWRkIGJpbmRpbmcgYW5ub3RhdGlvbnMgYXJvdW5kIHJlbmRlcmVkIGNvbnRlbnQpXG5cdFx0PyBwYXJlbnRWaWV3Ll8ub25SZW5kZXIocmV0LCBwYXJlbnRWaWV3LCB0YWcpXG5cdFx0OiByZXQ7XG59XG5cbi8vPT09PT09PT09PT09PT09PT1cbi8vIFZpZXcgY29uc3RydWN0b3Jcbi8vPT09PT09PT09PT09PT09PT1cblxuZnVuY3Rpb24gVmlldyhjb250ZXh0LCB0eXBlLCBwYXJlbnRWaWV3LCBkYXRhLCB0ZW1wbGF0ZSwga2V5LCBvblJlbmRlciwgY29udGVudFRtcGwpIHtcblx0Ly8gQ29uc3RydWN0b3IgZm9yIHZpZXcgb2JqZWN0IGluIHZpZXcgaGllcmFyY2h5LiAoQXVnbWVudGVkIGJ5IEpzVmlld3MgaWYgSnNWaWV3cyBpcyBsb2FkZWQpXG5cdHZhciB2aWV3cywgcGFyZW50Vmlld18sIHRhZywgc2VsZl8sXG5cdFx0c2VsZiA9IHRoaXMsXG5cdFx0aXNBcnJheSA9IHR5cGUgPT09IFwiYXJyYXlcIjtcblx0XHQvLyBJZiB0aGUgZGF0YSBpcyBhbiBhcnJheSwgdGhpcyBpcyBhbiAnYXJyYXkgdmlldycgd2l0aCBhIHZpZXdzIGFycmF5IGZvciBlYWNoIGNoaWxkICdpdGVtIHZpZXcnXG5cdFx0Ly8gSWYgdGhlIGRhdGEgaXMgbm90IGFuIGFycmF5LCB0aGlzIGlzIGFuICdpdGVtIHZpZXcnIHdpdGggYSB2aWV3cyAnaGFzaCcgb2JqZWN0IGZvciBhbnkgY2hpbGQgbmVzdGVkIHZpZXdzXG5cblx0c2VsZi5jb250ZW50ID0gY29udGVudFRtcGw7XG5cdHNlbGYudmlld3MgPSBpc0FycmF5ID8gW10gOiB7fTtcblx0c2VsZi5kYXRhID0gZGF0YTtcblx0c2VsZi50bXBsID0gdGVtcGxhdGU7XG5cdHNlbGZfID0gc2VsZi5fID0ge1xuXHRcdGtleTogMCxcblx0XHQvLyAuXy51c2VLZXkgaXMgbm9uIHplcm8gaWYgaXMgbm90IGFuICdhcnJheSB2aWV3JyAob3duaW5nIGEgZGF0YSBhcnJheSkuIFVzZSB0aGlzIGFzIG5leHQga2V5IGZvciBhZGRpbmcgdG8gY2hpbGQgdmlld3MgaGFzaFxuXHRcdHVzZUtleTogaXNBcnJheSA/IDAgOiAxLFxuXHRcdGlkOiBcIlwiICsgdmlld0lkKyssXG5cdFx0b25SZW5kZXI6IG9uUmVuZGVyLFxuXHRcdGJuZHM6IHt9XG5cdH07XG5cdHNlbGYubGlua2VkID0gISFvblJlbmRlcjtcblx0c2VsZi50eXBlID0gdHlwZSB8fCBcInRvcFwiO1xuXHRpZiAoc2VsZi5wYXJlbnQgPSBwYXJlbnRWaWV3KSB7XG5cdFx0c2VsZi5yb290ID0gcGFyZW50Vmlldy5yb290IHx8IHNlbGY7IC8vIHZpZXcgd2hvc2UgcGFyZW50IGlzIHRvcCB2aWV3XG5cdFx0dmlld3MgPSBwYXJlbnRWaWV3LnZpZXdzO1xuXHRcdHBhcmVudFZpZXdfID0gcGFyZW50Vmlldy5fO1xuXHRcdHNlbGYuaXNUb3AgPSBwYXJlbnRWaWV3Xy5zY3A7IC8vIElzIHRvcCBjb250ZW50IHZpZXcgb2YgYSBsaW5rKFwiI2NvbnRhaW5lclwiLCAuLi4pIGNhbGxcblx0XHRzZWxmLnNjb3BlID0gKCFjb250ZXh0LnRhZyB8fCBjb250ZXh0LnRhZyA9PT0gcGFyZW50Vmlldy5jdHgudGFnKSAmJiAhc2VsZi5pc1RvcCAmJiBwYXJlbnRWaWV3LnNjb3BlIHx8IHNlbGY7XG5cdFx0aWYgKHBhcmVudFZpZXdfLnVzZUtleSkge1xuXHRcdFx0Ly8gUGFyZW50IGlzIG5vdCBhbiAnYXJyYXkgdmlldycuIEFkZCB0aGlzIHZpZXcgdG8gaXRzIHZpZXdzIG9iamVjdFxuXHRcdFx0Ly8gc2VsZi5fa2V5ID0gaXMgdGhlIGtleSBpbiB0aGUgcGFyZW50IHZpZXcgaGFzaFxuXHRcdFx0dmlld3Nbc2VsZl8ua2V5ID0gXCJfXCIgKyBwYXJlbnRWaWV3Xy51c2VLZXkrK10gPSBzZWxmO1xuXHRcdFx0c2VsZi5pbmRleCA9IGluZGV4U3RyO1xuXHRcdFx0c2VsZi5nZXRJbmRleCA9IGdldE5lc3RlZEluZGV4O1xuXHRcdH0gZWxzZSBpZiAodmlld3MubGVuZ3RoID09PSAoc2VsZl8ua2V5ID0gc2VsZi5pbmRleCA9IGtleSkpIHsgLy8gUGFyZW50IGlzIGFuICdhcnJheSB2aWV3Jy4gQWRkIHRoaXMgdmlldyB0byBpdHMgdmlld3MgYXJyYXlcblx0XHRcdHZpZXdzLnB1c2goc2VsZik7IC8vIEFkZGluZyB0byBlbmQgb2Ygdmlld3MgYXJyYXkuIChVc2luZyBwdXNoIHdoZW4gcG9zc2libGUgLSBiZXR0ZXIgcGVyZiB0aGFuIHNwbGljZSlcblx0XHR9IGVsc2Uge1xuXHRcdFx0dmlld3Muc3BsaWNlKGtleSwgMCwgc2VsZik7IC8vIEluc2VydGluZyBpbiB2aWV3cyBhcnJheVxuXHRcdH1cblx0XHQvLyBJZiBubyBjb250ZXh0IHdhcyBwYXNzZWQgaW4sIHVzZSBwYXJlbnQgY29udGV4dFxuXHRcdC8vIElmIGNvbnRleHQgd2FzIHBhc3NlZCBpbiwgaXQgc2hvdWxkIGhhdmUgYmVlbiBtZXJnZWQgYWxyZWFkeSB3aXRoIHBhcmVudCBjb250ZXh0XG5cdFx0c2VsZi5jdHggPSBjb250ZXh0IHx8IHBhcmVudFZpZXcuY3R4O1xuXHR9IGVsc2Uge1xuXHRcdHNlbGYuY3R4ID0gY29udGV4dCB8fCB7fTtcblx0fVxufVxuXG5WaWV3LnByb3RvdHlwZSA9IHtcblx0Z2V0OiBnZXRWaWV3LFxuXHRnZXRJbmRleDogZ2V0SW5kZXgsXG5cdGdldFJzYzogZ2V0UmVzb3VyY2UsXG5cdGdldFRtcGw6IGdldFRlbXBsYXRlLFxuXHRjdHhQcm06IGNvbnRleHRQYXJhbWV0ZXIsXG5cdF9pczogXCJ2aWV3XCJcbn07XG5cbi8vPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuLy8gUmVnaXN0cmF0aW9uXG4vLz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT1cblxuZnVuY3Rpb24gY29tcGlsZUNoaWxkUmVzb3VyY2VzKHBhcmVudFRtcGwpIHtcblx0dmFyIHN0b3JlTmFtZSwgc3RvcmVOYW1lcywgcmVzb3VyY2VzO1xuXHRmb3IgKHN0b3JlTmFtZSBpbiBqc3ZTdG9yZXMpIHtcblx0XHRzdG9yZU5hbWVzID0gc3RvcmVOYW1lICsgXCJzXCI7XG5cdFx0aWYgKHBhcmVudFRtcGxbc3RvcmVOYW1lc10pIHtcblx0XHRcdHJlc291cmNlcyA9IHBhcmVudFRtcGxbc3RvcmVOYW1lc107ICAgIC8vIFJlc291cmNlcyBub3QgeWV0IGNvbXBpbGVkXG5cdFx0XHRwYXJlbnRUbXBsW3N0b3JlTmFtZXNdID0ge307ICAgICAgICAgICAgICAgLy8gUmVtb3ZlIHVuY29tcGlsZWQgcmVzb3VyY2VzXG5cdFx0XHQkdmlld3Nbc3RvcmVOYW1lc10ocmVzb3VyY2VzLCBwYXJlbnRUbXBsKTsgLy8gQWRkIGJhY2sgaW4gdGhlIGNvbXBpbGVkIHJlc291cmNlc1xuXHRcdH1cblx0fVxufVxuXG4vLz09PT09PT09PT09PT09PVxuLy8gY29tcGlsZVRhZ1xuLy89PT09PT09PT09PT09PT1cblxuZnVuY3Rpb24gY29tcGlsZVRhZyhuYW1lLCB0YWdEZWYsIHBhcmVudFRtcGwpIHtcblx0dmFyIHRtcGwsIGJhc2VUYWcsIHByb3AsIGwsIGtleSwgYmluZFRvTGVuZ3RoLFxuXHRcdGJpbmRUbyA9IHRhZ0RlZi5iaW5kVG8sXG5cdFx0Y29tcGlsZWREZWYgPSBuZXcgJHN1Yi5fdGcoKTtcblxuXHRmdW5jdGlvbiBUYWcoKSB7XG5cdFx0dmFyIHRhZyA9IHRoaXM7XG5cdFx0dGFnLl8gPSB7XG5cdFx0XHR1bmxpbmtlZDogdHJ1ZVxuXHRcdH07XG5cdFx0dGFnLmlubGluZSA9IHRydWU7XG5cdFx0dGFnLnRhZ05hbWUgPSBuYW1lO1xuXHR9XG5cblx0aWYgKCRpc0Z1bmN0aW9uKHRhZ0RlZikpIHtcblx0XHQvLyBTaW1wbGUgdGFnIGRlY2xhcmVkIGFzIGZ1bmN0aW9uLiBObyBwcmVzZW50ZXIgaW5zdGFudGF0aW9uLlxuXHRcdHRhZ0RlZiA9IHtcblx0XHRcdGRlcGVuZHM6IHRhZ0RlZi5kZXBlbmRzLFxuXHRcdFx0cmVuZGVyOiB0YWdEZWZcblx0XHR9O1xuXHR9IGVsc2UgaWYgKFwiXCIgKyB0YWdEZWYgPT09IHRhZ0RlZikge1xuXHRcdHRhZ0RlZiA9IHt0ZW1wbGF0ZTogdGFnRGVmfTtcblx0fVxuXG5cdGlmIChiYXNlVGFnID0gdGFnRGVmLmJhc2VUYWcpIHtcblx0XHR0YWdEZWYuZmxvdyA9ICEhdGFnRGVmLmZsb3c7IC8vIFNldCBmbG93IHByb3BlcnR5LCBzbyBkZWZhdWx0cyB0byBmYWxzZSBldmVuIGlmIGJhc2VUYWcgaGFzIGZsb3c9dHJ1ZVxuXHRcdHRhZ0RlZi5iYXNlVGFnID0gYmFzZVRhZyA9IFwiXCIgKyBiYXNlVGFnID09PSBiYXNlVGFnXG5cdFx0XHQ/IChwYXJlbnRUbXBsICYmIHBhcmVudFRtcGwudGFnc1tiYXNlVGFnXSB8fCAkdGFnc1tiYXNlVGFnXSlcblx0XHRcdDogYmFzZVRhZztcblxuXHRcdGNvbXBpbGVkRGVmID0gJGV4dGVuZChjb21waWxlZERlZiwgYmFzZVRhZyk7XG5cblx0XHRmb3IgKHByb3AgaW4gdGFnRGVmKSB7XG5cdFx0XHRjb21waWxlZERlZltwcm9wXSA9IGdldE1ldGhvZChiYXNlVGFnW3Byb3BdLCB0YWdEZWZbcHJvcF0pO1xuXHRcdH1cblx0fSBlbHNlIHtcblx0XHRjb21waWxlZERlZiA9ICRleHRlbmQoY29tcGlsZWREZWYsIHRhZ0RlZik7XG5cdH1cblxuXHQvLyBUYWcgZGVjbGFyZWQgYXMgb2JqZWN0LCB1c2VkIGFzIHRoZSBwcm90b3R5cGUgZm9yIHRhZyBpbnN0YW50aWF0aW9uIChjb250cm9sL3ByZXNlbnRlcilcblx0aWYgKCh0bXBsID0gY29tcGlsZWREZWYudGVtcGxhdGUpICE9PSB1bmRlZmluZWQpIHtcblx0XHRjb21waWxlZERlZi50ZW1wbGF0ZSA9IFwiXCIgKyB0bXBsID09PSB0bXBsID8gKCR0ZW1wbGF0ZXNbdG1wbF0gfHwgJHRlbXBsYXRlcyh0bXBsKSkgOiB0bXBsO1xuXHR9XG5cdChUYWcucHJvdG90eXBlID0gY29tcGlsZWREZWYpLmNvbnN0cnVjdG9yID0gY29tcGlsZWREZWYuX2N0ciA9IFRhZztcblxuXHRpZiAocGFyZW50VG1wbCkge1xuXHRcdGNvbXBpbGVkRGVmLl9wYXJlbnRUbXBsID0gcGFyZW50VG1wbDtcblx0fVxuXHRyZXR1cm4gY29tcGlsZWREZWY7XG59XG5cbmZ1bmN0aW9uIGJhc2VBcHBseShhcmdzKSB7XG5cdC8vIEluIGRlcml2ZWQgbWV0aG9kIChvciBoYW5kbGVyIGRlY2xhcmVkIGRlY2xhcmF0aXZlbHkgYXMgaW4ge3s6Zm9vIG9uQ2hhbmdlPX5mb29DaGFuZ2VkfX0gY2FuIGNhbGwgYmFzZSBtZXRob2QsXG5cdC8vIHVzaW5nIHRoaXMuYmFzZUFwcGx5KGFyZ3VtZW50cykgKEVxdWl2YWxlbnQgdG8gdGhpcy5fc3VwZXJBcHBseShhcmd1bWVudHMpIGluIGpRdWVyeSBVSSlcblx0cmV0dXJuIHRoaXMuYmFzZS5hcHBseSh0aGlzLCBhcmdzKTtcbn1cblxuLy89PT09PT09PT09PT09PT1cbi8vIGNvbXBpbGVUbXBsXG4vLz09PT09PT09PT09PT09PVxuXG5mdW5jdGlvbiBjb21waWxlVG1wbChuYW1lLCB0bXBsLCBwYXJlbnRUbXBsLCBvcHRpb25zKSB7XG5cdC8vIHRtcGwgaXMgZWl0aGVyIGEgdGVtcGxhdGUgb2JqZWN0LCBhIHNlbGVjdG9yIGZvciBhIHRlbXBsYXRlIHNjcmlwdCBibG9jaywgdGhlIG5hbWUgb2YgYSBjb21waWxlZCB0ZW1wbGF0ZSwgb3IgYSB0ZW1wbGF0ZSBvYmplY3RcblxuXHQvLz09PT0gbmVzdGVkIGZ1bmN0aW9ucyA9PT09XG5cdGZ1bmN0aW9uIGxvb2t1cFRlbXBsYXRlKHZhbHVlKSB7XG5cdFx0Ly8gSWYgdmFsdWUgaXMgb2YgdHlwZSBzdHJpbmcgLSB0cmVhdCBhcyBzZWxlY3Rvciwgb3IgbmFtZSBvZiBjb21waWxlZCB0ZW1wbGF0ZVxuXHRcdC8vIFJldHVybiB0aGUgdGVtcGxhdGUgb2JqZWN0LCBpZiBhbHJlYWR5IGNvbXBpbGVkLCBvciB0aGUgbWFya3VwIHN0cmluZ1xuXHRcdHZhciBjdXJyZW50TmFtZSwgdG1wbDtcblx0XHRpZiAoKFwiXCIgKyB2YWx1ZSA9PT0gdmFsdWUpIHx8IHZhbHVlLm5vZGVUeXBlID4gMCAmJiAoZWxlbSA9IHZhbHVlKSkge1xuXHRcdFx0aWYgKCFlbGVtKSB7XG5cdFx0XHRcdGlmICgvXlxcLlxcL1teXFxcXDoqP1wiPD5dKiQvLnRlc3QodmFsdWUpKSB7XG5cdFx0XHRcdFx0Ly8gdG1wbD1cIi4vc29tZS9maWxlLmh0bWxcIlxuXHRcdFx0XHRcdC8vIElmIHRoZSB0ZW1wbGF0ZSBpcyBub3QgbmFtZWQsIHVzZSBcIi4vc29tZS9maWxlLmh0bWxcIiBhcyBuYW1lLlxuXHRcdFx0XHRcdGlmICh0bXBsID0gJHRlbXBsYXRlc1tuYW1lID0gbmFtZSB8fCB2YWx1ZV0pIHtcblx0XHRcdFx0XHRcdHZhbHVlID0gdG1wbDtcblx0XHRcdFx0XHR9IGVsc2Uge1xuXHRcdFx0XHRcdFx0Ly8gQlJPV1NFUi1TUEVDSUZJQyBDT0RFIChub3Qgb24gTm9kZS5qcyk6XG5cdFx0XHRcdFx0XHQvLyBMb29rIGZvciBzZXJ2ZXItZ2VuZXJhdGVkIHNjcmlwdCBibG9jayB3aXRoIGlkIFwiLi9zb21lL2ZpbGUuaHRtbFwiXG5cdFx0XHRcdFx0XHRlbGVtID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQodmFsdWUpO1xuXHRcdFx0XHRcdH1cblx0XHRcdFx0fSBlbHNlIGlmICgkLmZuICYmICEkc3ViLnJUbXBsLnRlc3QodmFsdWUpKSB7XG5cdFx0XHRcdFx0dHJ5IHtcblx0XHRcdFx0XHRcdGVsZW0gPSAkICh2YWx1ZSwgZG9jdW1lbnQpWzBdOyAvLyBpZiBqUXVlcnkgaXMgbG9hZGVkLCB0ZXN0IGZvciBzZWxlY3RvciByZXR1cm5pbmcgZWxlbWVudHMsIGFuZCBnZXQgZmlyc3QgZWxlbWVudFxuXHRcdFx0XHRcdH0gY2F0Y2ggKGUpIHt9XG5cdFx0XHRcdH0vLyBFTkQgQlJPV1NFUi1TUEVDSUZJQyBDT0RFXG5cdFx0XHR9IC8vQlJPV1NFUi1TUEVDSUZJQyBDT0RFXG5cdFx0XHRpZiAoZWxlbSkge1xuXHRcdFx0XHQvLyBHZW5lcmFsbHkgdGhpcyBpcyBhIHNjcmlwdCBlbGVtZW50LlxuXHRcdFx0XHQvLyBIb3dldmVyIHdlIGFsbG93IGl0IHRvIGJlIGFueSBlbGVtZW50LCBzbyB5b3UgY2FuIGZvciBleGFtcGxlIHRha2UgdGhlIGNvbnRlbnQgb2YgYSBkaXYsXG5cdFx0XHRcdC8vIHVzZSBpdCBhcyBhIHRlbXBsYXRlLCBhbmQgcmVwbGFjZSBpdCBieSB0aGUgc2FtZSBjb250ZW50IHJlbmRlcmVkIGFnYWluc3QgZGF0YS5cblx0XHRcdFx0Ly8gZS5nLiBmb3IgbGlua2luZyB0aGUgY29udGVudCBvZiBhIGRpdiB0byBhIGNvbnRhaW5lciwgYW5kIHVzaW5nIHRoZSBpbml0aWFsIGNvbnRlbnQgYXMgdGVtcGxhdGU6XG5cdFx0XHRcdC8vICQubGluayhcIiNjb250ZW50XCIsIG1vZGVsLCB7dG1wbDogXCIjY29udGVudFwifSk7XG5cdFx0XHRcdGlmIChvcHRpb25zKSB7XG5cdFx0XHRcdFx0Ly8gV2Ugd2lsbCBjb21waWxlIGEgbmV3IHRlbXBsYXRlIHVzaW5nIHRoZSBtYXJrdXAgaW4gdGhlIHNjcmlwdCBlbGVtZW50XG5cdFx0XHRcdFx0dmFsdWUgPSBlbGVtLmlubmVySFRNTDtcblx0XHRcdFx0fSBlbHNlIHtcblx0XHRcdFx0XHQvLyBXZSB3aWxsIGNhY2hlIGEgc2luZ2xlIGNvcHkgb2YgdGhlIGNvbXBpbGVkIHRlbXBsYXRlLCBhbmQgYXNzb2NpYXRlIGl0IHdpdGggdGhlIG5hbWVcblx0XHRcdFx0XHQvLyAocmVuYW1pbmcgZnJvbSBhIHByZXZpb3VzIG5hbWUgaWYgdGhlcmUgd2FzIG9uZSkuXG5cdFx0XHRcdFx0Y3VycmVudE5hbWUgPSBlbGVtLmdldEF0dHJpYnV0ZSh0bXBsQXR0cik7XG5cdFx0XHRcdFx0aWYgKGN1cnJlbnROYW1lKSB7XG5cdFx0XHRcdFx0XHRpZiAoY3VycmVudE5hbWUgIT09IGpzdlRtcGwpIHtcblx0XHRcdFx0XHRcdFx0dmFsdWUgPSAkdGVtcGxhdGVzW2N1cnJlbnROYW1lXTtcblx0XHRcdFx0XHRcdFx0ZGVsZXRlICR0ZW1wbGF0ZXNbY3VycmVudE5hbWVdO1xuXHRcdFx0XHRcdFx0fSBlbHNlIGlmICgkLmZuKSB7XG5cdFx0XHRcdFx0XHRcdHZhbHVlID0gJC5kYXRhKGVsZW0pW2pzdlRtcGxdOyAvLyBHZXQgY2FjaGVkIGNvbXBpbGVkIHRlbXBsYXRlXG5cdFx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0fVxuXHRcdFx0XHRcdGlmICghY3VycmVudE5hbWUgfHwgIXZhbHVlKSB7IC8vIE5vdCB5ZXQgY29tcGlsZWQsIG9yIGNhY2hlZCB2ZXJzaW9uIGxvc3Rcblx0XHRcdFx0XHRcdG5hbWUgPSBuYW1lIHx8ICgkLmZuID8ganN2VG1wbCA6IHZhbHVlKTtcblx0XHRcdFx0XHRcdHZhbHVlID0gY29tcGlsZVRtcGwobmFtZSwgZWxlbS5pbm5lckhUTUwsIHBhcmVudFRtcGwsIG9wdGlvbnMpO1xuXHRcdFx0XHRcdH1cblx0XHRcdFx0XHR2YWx1ZS50bXBsTmFtZSA9IG5hbWUgPSBuYW1lIHx8IGN1cnJlbnROYW1lO1xuXHRcdFx0XHRcdGlmIChuYW1lICE9PSBqc3ZUbXBsKSB7XG5cdFx0XHRcdFx0XHQkdGVtcGxhdGVzW25hbWVdID0gdmFsdWU7XG5cdFx0XHRcdFx0fVxuXHRcdFx0XHRcdGVsZW0uc2V0QXR0cmlidXRlKHRtcGxBdHRyLCBuYW1lKTtcblx0XHRcdFx0XHRpZiAoJC5mbikge1xuXHRcdFx0XHRcdFx0JC5kYXRhKGVsZW0sIGpzdlRtcGwsIHZhbHVlKTtcblx0XHRcdFx0XHR9XG5cdFx0XHRcdH1cblx0XHRcdH0gLy8gRU5EIEJST1dTRVItU1BFQ0lGSUMgQ09ERVxuXHRcdFx0ZWxlbSA9IHVuZGVmaW5lZDtcblx0XHR9IGVsc2UgaWYgKCF2YWx1ZS5mbikge1xuXHRcdFx0dmFsdWUgPSB1bmRlZmluZWQ7XG5cdFx0XHQvLyBJZiB2YWx1ZSBpcyBub3QgYSBzdHJpbmcuIEhUTUwgZWxlbWVudCwgb3IgY29tcGlsZWQgdGVtcGxhdGUsIHJldHVybiB1bmRlZmluZWRcblx0XHR9XG5cdFx0cmV0dXJuIHZhbHVlO1xuXHR9XG5cblx0dmFyIGVsZW0sIGNvbXBpbGVkVG1wbCxcblx0XHR0bXBsT3JNYXJrdXAgPSB0bXBsID0gdG1wbCB8fCBcIlwiO1xuXHQkc3ViLl9odG1sID0gJGNvbnZlcnRlcnMuaHRtbDtcblxuXHQvLz09PT0gQ29tcGlsZSB0aGUgdGVtcGxhdGUgPT09PVxuXHRpZiAob3B0aW9ucyA9PT0gMCkge1xuXHRcdG9wdGlvbnMgPSB1bmRlZmluZWQ7XG5cdFx0dG1wbE9yTWFya3VwID0gbG9va3VwVGVtcGxhdGUodG1wbE9yTWFya3VwKTsgLy8gVG9wLWxldmVsIGNvbXBpbGUgc28gZG8gYSB0ZW1wbGF0ZSBsb29rdXBcblx0fVxuXG5cdC8vIElmIG9wdGlvbnMsIHRoZW4gdGhpcyB3YXMgYWxyZWFkeSBjb21waWxlZCBmcm9tIGEgKHNjcmlwdCkgZWxlbWVudCB0ZW1wbGF0ZSBkZWNsYXJhdGlvbi5cblx0Ly8gSWYgbm90LCB0aGVuIGlmIHRtcGwgaXMgYSB0ZW1wbGF0ZSBvYmplY3QsIHVzZSBpdCBmb3Igb3B0aW9uc1xuXHRvcHRpb25zID0gb3B0aW9ucyB8fCAodG1wbC5tYXJrdXAgPyB0bXBsIDoge30pO1xuXHRvcHRpb25zLnRtcGxOYW1lID0gbmFtZTtcblx0aWYgKHBhcmVudFRtcGwpIHtcblx0XHRvcHRpb25zLl9wYXJlbnRUbXBsID0gcGFyZW50VG1wbDtcblx0fVxuXHQvLyBJZiB0bXBsIGlzIG5vdCBhIG1hcmt1cCBzdHJpbmcgb3IgYSBzZWxlY3RvciBzdHJpbmcsIHRoZW4gaXQgbXVzdCBiZSBhIHRlbXBsYXRlIG9iamVjdFxuXHQvLyBJbiB0aGF0IGNhc2UsIGdldCBpdCBmcm9tIHRoZSBtYXJrdXAgcHJvcGVydHkgb2YgdGhlIG9iamVjdFxuXHRpZiAoIXRtcGxPck1hcmt1cCAmJiB0bXBsLm1hcmt1cCAmJiAodG1wbE9yTWFya3VwID0gbG9va3VwVGVtcGxhdGUodG1wbC5tYXJrdXApKSkge1xuXHRcdGlmICh0bXBsT3JNYXJrdXAuZm4pIHtcblx0XHRcdC8vIElmIHRoZSBzdHJpbmcgcmVmZXJlbmNlcyBhIGNvbXBpbGVkIHRlbXBsYXRlIG9iamVjdCwgbmVlZCB0byByZWNvbXBpbGUgdG8gbWVyZ2UgYW55IG1vZGlmaWVkIG9wdGlvbnNcblx0XHRcdHRtcGxPck1hcmt1cCA9IHRtcGxPck1hcmt1cC5tYXJrdXA7XG5cdFx0fVxuXHR9XG5cdGlmICh0bXBsT3JNYXJrdXAgIT09IHVuZGVmaW5lZCkge1xuXHRcdGlmICh0bXBsT3JNYXJrdXAuZm4gfHwgdG1wbC5mbikge1xuXHRcdFx0Ly8gdG1wbCBpcyBhbHJlYWR5IGNvbXBpbGVkLCBzbyB1c2UgaXRcblx0XHRcdGlmICh0bXBsT3JNYXJrdXAuZm4pIHtcblx0XHRcdFx0Y29tcGlsZWRUbXBsID0gdG1wbE9yTWFya3VwO1xuXHRcdFx0fVxuXHRcdH0gZWxzZSB7XG5cdFx0XHQvLyB0bXBsT3JNYXJrdXAgaXMgYSBtYXJrdXAgc3RyaW5nLCBub3QgYSBjb21waWxlZCB0ZW1wbGF0ZVxuXHRcdFx0Ly8gQ3JlYXRlIHRlbXBsYXRlIG9iamVjdFxuXHRcdFx0dG1wbCA9IHRtcGxPYmplY3QodG1wbE9yTWFya3VwLCBvcHRpb25zKTtcblx0XHRcdC8vIENvbXBpbGUgdG8gQVNUIGFuZCB0aGVuIHRvIGNvbXBpbGVkIGZ1bmN0aW9uXG5cdFx0XHR0bXBsRm4odG1wbE9yTWFya3VwLnJlcGxhY2UockVzY2FwZVF1b3RlcywgXCJcXFxcJCZcIiksIHRtcGwpO1xuXHRcdH1cblx0XHRpZiAoIWNvbXBpbGVkVG1wbCkge1xuXHRcdFx0Y29tcGlsZWRUbXBsID0gJGV4dGVuZChmdW5jdGlvbigpIHtcblx0XHRcdFx0cmV0dXJuIGNvbXBpbGVkVG1wbC5yZW5kZXIuYXBwbHkoY29tcGlsZWRUbXBsLCBhcmd1bWVudHMpO1xuXHRcdFx0fSwgdG1wbCk7XG5cblx0XHRcdGNvbXBpbGVDaGlsZFJlc291cmNlcyhjb21waWxlZFRtcGwpO1xuXHRcdH1cblx0XHRyZXR1cm4gY29tcGlsZWRUbXBsO1xuXHR9XG59XG5cbi8vPT09PSAvZW5kIG9mIGZ1bmN0aW9uIGNvbXBpbGVUbXBsID09PT1cblxuLy89PT09PT09PT09PT09PT09PVxuLy8gY29tcGlsZVZpZXdNb2RlbFxuLy89PT09PT09PT09PT09PT09PVxuXG5mdW5jdGlvbiBnZXREZWZhdWx0VmFsKGRlZmF1bHRWYWwsIGRhdGEpIHtcblx0cmV0dXJuICRpc0Z1bmN0aW9uKGRlZmF1bHRWYWwpXG5cdFx0PyBkZWZhdWx0VmFsLmNhbGwoZGF0YSlcblx0XHQ6IGRlZmF1bHRWYWw7XG59XG5cbmZ1bmN0aW9uIHVubWFwQXJyYXkobW9kZWxBcnIpIHtcblx0XHR2YXIgYXJyID0gW10sXG5cdFx0XHRpID0gMCxcblx0XHRcdGwgPSBtb2RlbEFyci5sZW5ndGg7XG5cdFx0Zm9yICg7IGk8bDsgaSsrKSB7XG5cdFx0XHRhcnIucHVzaChtb2RlbEFycltpXS51bm1hcCgpKTtcblx0XHR9XG5cdFx0cmV0dXJuIGFycjtcbn1cblxuZnVuY3Rpb24gY29tcGlsZVZpZXdNb2RlbChuYW1lLCB0eXBlKSB7XG5cdHZhciBpLCBjb25zdHJ1Y3Rvcixcblx0XHR2aWV3TW9kZWxzID0gdGhpcyxcblx0XHRnZXR0ZXJzID0gdHlwZS5nZXR0ZXJzLFxuXHRcdGV4dGVuZCA9IHR5cGUuZXh0ZW5kLFxuXHRcdGlkID0gdHlwZS5pZCxcblx0XHRwcm90byA9ICQuZXh0ZW5kKHtcblx0XHRcdF9pczogbmFtZSB8fCBcInVubmFtZWRcIixcblx0XHRcdHVubWFwOiB1bm1hcCxcblx0XHRcdG1lcmdlOiBtZXJnZVxuXHRcdH0sIGV4dGVuZCksXG5cdFx0YXJncyA9IFwiXCIsXG5cdFx0Ym9keSA9IFwiXCIsXG5cdFx0ZyA9IGdldHRlcnMgPyBnZXR0ZXJzLmxlbmd0aCA6IDAsXG5cdFx0JG9ic2VydmFibGUgPSAkLm9ic2VydmFibGUsXG5cdFx0Z2V0dGVyTmFtZXMgPSB7fTtcblxuXHRmdW5jdGlvbiBHZXROZXcoYXJncykge1xuXHRcdGNvbnN0cnVjdG9yLmFwcGx5KHRoaXMsIGFyZ3MpO1xuXHR9XG5cblx0ZnVuY3Rpb24gdm0oKSB7XG5cdFx0cmV0dXJuIG5ldyBHZXROZXcoYXJndW1lbnRzKTtcblx0fVxuXG5cdGZ1bmN0aW9uIGl0ZXJhdGUoZGF0YSwgYWN0aW9uKSB7XG5cdFx0dmFyIGdldHRlclR5cGUsIGRlZmF1bHRWYWwsIHByb3AsIG9iLFxuXHRcdFx0aiA9IDA7XG5cdFx0Zm9yICg7IGo8ZzsgaisrKSB7XG5cdFx0XHRwcm9wID0gZ2V0dGVyc1tqXTtcblx0XHRcdGdldHRlclR5cGUgPSB1bmRlZmluZWQ7XG5cdFx0XHRpZiAocHJvcCArIFwiXCIgIT09IHByb3ApIHtcblx0XHRcdFx0Z2V0dGVyVHlwZSA9IHByb3A7XG5cdFx0XHRcdHByb3AgPSBnZXR0ZXJUeXBlLmdldHRlcjtcblx0XHRcdH1cblx0XHRcdGlmICgob2IgPSBkYXRhW3Byb3BdKSA9PT0gdW5kZWZpbmVkICYmIGdldHRlclR5cGUgJiYgKGRlZmF1bHRWYWwgPSBnZXR0ZXJUeXBlLmRlZmF1bHRWYWwpICE9PSB1bmRlZmluZWQpIHtcblx0XHRcdFx0b2IgPSBnZXREZWZhdWx0VmFsKGRlZmF1bHRWYWwsIGRhdGEpO1xuXHRcdFx0fVxuXHRcdFx0YWN0aW9uKG9iLCBnZXR0ZXJUeXBlICYmIHZpZXdNb2RlbHNbZ2V0dGVyVHlwZS50eXBlXSwgcHJvcCk7XG5cdFx0fVxuXHR9XG5cblx0ZnVuY3Rpb24gbWFwKGRhdGEpIHtcblx0XHRkYXRhID0gZGF0YSArIFwiXCIgPT09IGRhdGFcblx0XHRcdD8gSlNPTi5wYXJzZShkYXRhKSAvLyBBY2NlcHQgSlNPTiBzdHJpbmdcblx0XHRcdDogZGF0YTsgICAgICAgICAgICAvLyBvciBvYmplY3QvYXJyYXlcblx0XHR2YXIgbCwgcHJvcCxcblx0XHRcdGogPSAwLFxuXHRcdFx0b2IgPSBkYXRhLFxuXHRcdFx0YXJyID0gW107XG5cblx0XHRpZiAoJGlzQXJyYXkoZGF0YSkpIHtcblx0XHRcdGRhdGEgPSBkYXRhIHx8IFtdO1xuXHRcdFx0bCA9IGRhdGEubGVuZ3RoO1xuXHRcdFx0Zm9yICg7IGo8bDsgaisrKSB7XG5cdFx0XHRcdGFyci5wdXNoKHRoaXMubWFwKGRhdGFbal0pKTtcblx0XHRcdH1cblx0XHRcdGFyci5faXMgPSBuYW1lO1xuXHRcdFx0YXJyLnVubWFwID0gdW5tYXA7XG5cdFx0XHRhcnIubWVyZ2UgPSBtZXJnZTtcblx0XHRcdHJldHVybiBhcnI7XG5cdFx0fVxuXG5cdFx0aWYgKGRhdGEpIHtcblx0XHRcdGl0ZXJhdGUoZGF0YSwgZnVuY3Rpb24ob2IsIHZpZXdNb2RlbCkge1xuXHRcdFx0XHRpZiAodmlld01vZGVsKSB7IC8vIEl0ZXJhdGUgdG8gYnVpbGQgZ2V0dGVycyBhcmcgYXJyYXkgKHZhbHVlLCBvciBtYXBwZWQgdmFsdWUpXG5cdFx0XHRcdFx0b2IgPSB2aWV3TW9kZWwubWFwKG9iKTtcblx0XHRcdFx0fVxuXHRcdFx0XHRhcnIucHVzaChvYik7XG5cdFx0XHR9KTtcblxuXHRcdFx0b2IgPSB0aGlzLmFwcGx5KHRoaXMsIGFycik7IC8vIEluc2FudGlhdGUgdGhpcyBWaWV3IE1vZGVsLCBwYXNzaW5nIGdldHRlcnMgYXJncyBhcnJheSB0byBjb25zdHJ1Y3RvclxuXHRcdFx0Zm9yIChwcm9wIGluIGRhdGEpIHsgLy8gQ29weSBvdmVyIGFueSBvdGhlciBwcm9wZXJ0aWVzLiB0aGF0IGFyZSBub3QgZ2V0L3NldCBwcm9wZXJ0aWVzXG5cdFx0XHRcdGlmIChwcm9wICE9PSAkZXhwYW5kbyAmJiAhZ2V0dGVyTmFtZXNbcHJvcF0pIHtcblx0XHRcdFx0XHRvYltwcm9wXSA9IGRhdGFbcHJvcF07XG5cdFx0XHRcdH1cblx0XHRcdH1cblx0XHR9XG5cdFx0cmV0dXJuIG9iO1xuXHR9XG5cblx0ZnVuY3Rpb24gbWVyZ2UoZGF0YSkge1xuXHRcdGRhdGEgPSBkYXRhICsgXCJcIiA9PT0gZGF0YVxuXHRcdFx0PyBKU09OLnBhcnNlKGRhdGEpIC8vIEFjY2VwdCBKU09OIHN0cmluZ1xuXHRcdFx0OiBkYXRhOyAgICAgICAgICAgIC8vIG9yIG9iamVjdC9hcnJheVxuXHRcdHZhciBqLCBsLCBtLCBwcm9wLCBtb2QsIGZvdW5kLCBhc3NpZ25lZCwgb2IsIG5ld01vZEFycixcblx0XHRcdGsgPSAwLFxuXHRcdFx0bW9kZWwgPSB0aGlzO1xuXG5cdFx0aWYgKCRpc0FycmF5KG1vZGVsKSkge1xuXHRcdFx0YXNzaWduZWQgPSB7fTtcblx0XHRcdG5ld01vZEFyciA9IFtdO1xuXHRcdFx0bCA9IGRhdGEubGVuZ3RoO1xuXHRcdFx0bSA9IG1vZGVsLmxlbmd0aDtcblx0XHRcdGZvciAoOyBrPGw7IGsrKykge1xuXHRcdFx0XHRvYiA9IGRhdGFba107XG5cdFx0XHRcdGZvdW5kID0gZmFsc2U7XG5cdFx0XHRcdGZvciAoaj0wOyBqPG0gJiYgIWZvdW5kOyBqKyspIHtcblx0XHRcdFx0XHRpZiAoYXNzaWduZWRbal0pIHtcblx0XHRcdFx0XHRcdGNvbnRpbnVlO1xuXHRcdFx0XHRcdH1cblx0XHRcdFx0XHRtb2QgPSBtb2RlbFtqXTtcblxuXHRcdFx0XHRcdGlmIChpZCkge1xuXHRcdFx0XHRcdFx0YXNzaWduZWRbal0gPSBmb3VuZCA9IGlkICsgXCJcIiA9PT0gaWRcblx0XHRcdFx0XHRcdD8gKG9iW2lkXSAmJiAoZ2V0dGVyTmFtZXNbaWRdID8gbW9kW2lkXSgpIDogbW9kW2lkXSkgPT09IG9iW2lkXSlcblx0XHRcdFx0XHRcdDogaWQobW9kLCBvYik7XG5cdFx0XHRcdFx0fVxuXHRcdFx0XHR9XG5cdFx0XHRcdGlmIChmb3VuZCkge1xuXHRcdFx0XHRcdG1vZC5tZXJnZShvYik7XG5cdFx0XHRcdFx0bmV3TW9kQXJyLnB1c2gobW9kKTtcblx0XHRcdFx0fSBlbHNlIHtcblx0XHRcdFx0XHRuZXdNb2RBcnIucHVzaCh2bS5tYXAob2IpKTtcblx0XHRcdFx0fVxuXHRcdFx0fVxuXHRcdFx0aWYgKCRvYnNlcnZhYmxlKSB7XG5cdFx0XHRcdCRvYnNlcnZhYmxlKG1vZGVsKS5yZWZyZXNoKG5ld01vZEFyciwgdHJ1ZSk7XG5cdFx0XHR9IGVsc2Uge1xuXHRcdFx0XHRtb2RlbC5zcGxpY2UuYXBwbHkobW9kZWwsIFswLCBtb2RlbC5sZW5ndGhdLmNvbmNhdChuZXdNb2RBcnIpKTtcblx0XHRcdH1cblx0XHRcdHJldHVybjtcblx0XHR9XG5cdFx0aXRlcmF0ZShkYXRhLCBmdW5jdGlvbihvYiwgdmlld01vZGVsLCBnZXR0ZXIpIHtcblx0XHRcdGlmICh2aWV3TW9kZWwpIHtcblx0XHRcdFx0bW9kZWxbZ2V0dGVyXSgpLm1lcmdlKG9iKTsgLy8gVXBkYXRlIHR5cGVkIHByb3BlcnR5XG5cdFx0XHR9IGVsc2Uge1xuXHRcdFx0XHRtb2RlbFtnZXR0ZXJdKG9iKTsgLy8gVXBkYXRlIG5vbi10eXBlZCBwcm9wZXJ0eVxuXHRcdFx0fVxuXHRcdH0pO1xuXHRcdGZvciAocHJvcCBpbiBkYXRhKSB7XG5cdFx0XHRpZiAocHJvcCAhPT0gJGV4cGFuZG8gJiYgIWdldHRlck5hbWVzW3Byb3BdKSB7XG5cdFx0XHRcdG1vZGVsW3Byb3BdID0gZGF0YVtwcm9wXTtcblx0XHRcdH1cblx0XHR9XG5cdH1cblxuXHRmdW5jdGlvbiB1bm1hcCgpIHtcblx0XHR2YXIgb2IsIHByb3AsIGdldHRlclR5cGUsIGFyciwgdmFsdWUsXG5cdFx0XHRrID0gMCxcblx0XHRcdG1vZGVsID0gdGhpcztcblxuXHRcdGlmICgkaXNBcnJheShtb2RlbCkpIHtcblx0XHRcdHJldHVybiB1bm1hcEFycmF5KG1vZGVsKTtcblx0XHR9XG5cdFx0b2IgPSB7fTtcblx0XHRmb3IgKDsgazxnOyBrKyspIHtcblx0XHRcdHByb3AgPSBnZXR0ZXJzW2tdO1xuXHRcdFx0Z2V0dGVyVHlwZSA9IHVuZGVmaW5lZDtcblx0XHRcdGlmIChwcm9wICsgXCJcIiAhPT0gcHJvcCkge1xuXHRcdFx0XHRnZXR0ZXJUeXBlID0gcHJvcDtcblx0XHRcdFx0cHJvcCA9IGdldHRlclR5cGUuZ2V0dGVyO1xuXHRcdFx0fVxuXHRcdFx0dmFsdWUgPSBtb2RlbFtwcm9wXSgpO1xuXHRcdFx0b2JbcHJvcF0gPSBnZXR0ZXJUeXBlICYmIHZhbHVlICYmIHZpZXdNb2RlbHNbZ2V0dGVyVHlwZS50eXBlXVxuXHRcdFx0XHQ/ICRpc0FycmF5KHZhbHVlKVxuXHRcdFx0XHRcdD8gdW5tYXBBcnJheSh2YWx1ZSlcblx0XHRcdFx0XHQ6IHZhbHVlLnVubWFwKClcblx0XHRcdFx0OiB2YWx1ZTtcblx0XHR9XG5cdFx0Zm9yIChwcm9wIGluIG1vZGVsKSB7XG5cdFx0XHRpZiAocHJvcCAhPT0gXCJfaXNcIiAmJiAhZ2V0dGVyTmFtZXNbcHJvcF0gJiYgcHJvcCAhPT0gJGV4cGFuZG8gICYmIChwcm9wLmNoYXJBdCgwKSAhPT0gXCJfXCIgfHwgIWdldHRlck5hbWVzW3Byb3Auc2xpY2UoMSldKSAmJiAhJGlzRnVuY3Rpb24obW9kZWxbcHJvcF0pKSB7XG5cdFx0XHRcdG9iW3Byb3BdID0gbW9kZWxbcHJvcF07XG5cdFx0XHR9XG5cdFx0fVxuXHRcdHJldHVybiBvYjtcblx0fVxuXG5cdEdldE5ldy5wcm90b3R5cGUgPSBwcm90bztcblxuXHRmb3IgKGk9MDsgaTxnOyBpKyspIHtcblx0XHQoZnVuY3Rpb24oZ2V0dGVyKSB7XG5cdFx0XHRnZXR0ZXIgPSBnZXR0ZXIuZ2V0dGVyIHx8IGdldHRlcjtcblx0XHRcdGdldHRlck5hbWVzW2dldHRlcl0gPSBpKzE7XG5cdFx0XHR2YXIgcHJpdkZpZWxkID0gXCJfXCIgKyBnZXR0ZXI7XG5cblx0XHRcdGFyZ3MgKz0gKGFyZ3MgPyBcIixcIiA6IFwiXCIpICsgZ2V0dGVyO1xuXHRcdFx0Ym9keSArPSBcInRoaXMuXCIgKyBwcml2RmllbGQgKyBcIiA9IFwiICsgZ2V0dGVyICsgXCI7XFxuXCI7XG5cdFx0XHRwcm90b1tnZXR0ZXJdID0gcHJvdG9bZ2V0dGVyXSB8fCBmdW5jdGlvbih2YWwpIHtcblx0XHRcdFx0aWYgKCFhcmd1bWVudHMubGVuZ3RoKSB7XG5cdFx0XHRcdFx0cmV0dXJuIHRoaXNbcHJpdkZpZWxkXTsgLy8gSWYgdGhlcmUgaXMgbm8gYXJndW1lbnQsIHVzZSBhcyBhIGdldHRlclxuXHRcdFx0XHR9XG5cdFx0XHRcdGlmICgkb2JzZXJ2YWJsZSkge1xuXHRcdFx0XHRcdCRvYnNlcnZhYmxlKHRoaXMpLnNldFByb3BlcnR5KGdldHRlciwgdmFsKTtcblx0XHRcdFx0fSBlbHNlIHtcblx0XHRcdFx0XHR0aGlzW3ByaXZGaWVsZF0gPSB2YWw7XG5cdFx0XHRcdH1cblx0XHRcdH07XG5cblx0XHRcdGlmICgkb2JzZXJ2YWJsZSkge1xuXHRcdFx0XHRwcm90b1tnZXR0ZXJdLnNldCA9IHByb3RvW2dldHRlcl0uc2V0IHx8IGZ1bmN0aW9uKHZhbCkge1xuXHRcdFx0XHRcdHRoaXNbcHJpdkZpZWxkXSA9IHZhbDsgLy8gU2V0dGVyIGNhbGxlZCBieSBvYnNlcnZhYmxlIHByb3BlcnR5IGNoYW5nZVxuXHRcdFx0XHR9O1xuXHRcdFx0fVxuXHRcdH0pKGdldHRlcnNbaV0pO1xuXHR9XG5cblx0Y29uc3RydWN0b3IgPSBuZXcgRnVuY3Rpb24oYXJncywgYm9keS5zbGljZSgwLCAtMSkpO1xuXHRjb25zdHJ1Y3Rvci5wcm90b3R5cGUgPSBwcm90bztcblx0cHJvdG8uY29uc3RydWN0b3IgPSBjb25zdHJ1Y3RvcjtcblxuXHR2bS5tYXAgPSBtYXA7XG5cdHZtLmdldHRlcnMgPSBnZXR0ZXJzO1xuXHR2bS5leHRlbmQgPSBleHRlbmQ7XG5cdHZtLmlkID0gaWQ7XG5cdHJldHVybiB2bTtcbn1cblxuZnVuY3Rpb24gdG1wbE9iamVjdChtYXJrdXAsIG9wdGlvbnMpIHtcblx0Ly8gVGVtcGxhdGUgb2JqZWN0IGNvbnN0cnVjdG9yXG5cdHZhciBodG1sVGFnLFxuXHRcdHdyYXBNYXAgPSAkc3ViU2V0dGluZ3NBZHZhbmNlZC5fd20gfHwge30sIC8vIE9ubHkgdXNlZCBpbiBKc1ZpZXdzLiBPdGhlcndpc2UgZW1wdHk6IHt9XG5cdFx0dG1wbCA9ICRleHRlbmQoXG5cdFx0XHR7XG5cdFx0XHRcdHRtcGxzOiBbXSxcblx0XHRcdFx0bGlua3M6IHt9LCAvLyBDb21waWxlZCBmdW5jdGlvbnMgZm9yIGxpbmsgZXhwcmVzc2lvbnNcblx0XHRcdFx0Ym5kczogW10sXG5cdFx0XHRcdF9pczogXCJ0ZW1wbGF0ZVwiLFxuXHRcdFx0XHRyZW5kZXI6IHJlbmRlckNvbnRlbnRcblx0XHRcdH0sXG5cdFx0XHRvcHRpb25zXG5cdFx0KTtcblxuXHR0bXBsLm1hcmt1cCA9IG1hcmt1cDtcblx0aWYgKCFvcHRpb25zLmh0bWxUYWcpIHtcblx0XHQvLyBTZXQgdG1wbC50YWcgdG8gdGhlIHRvcC1sZXZlbCBIVE1MIHRhZyB1c2VkIGluIHRoZSB0ZW1wbGF0ZSwgaWYgYW55Li4uXG5cdFx0aHRtbFRhZyA9IHJGaXJzdEVsZW0uZXhlYyhtYXJrdXApO1xuXHRcdHRtcGwuaHRtbFRhZyA9IGh0bWxUYWcgPyBodG1sVGFnWzFdLnRvTG93ZXJDYXNlKCkgOiBcIlwiO1xuXHR9XG5cdGh0bWxUYWcgPSB3cmFwTWFwW3RtcGwuaHRtbFRhZ107XG5cdGlmIChodG1sVGFnICYmIGh0bWxUYWcgIT09IHdyYXBNYXAuZGl2KSB7XG5cdFx0Ly8gV2hlbiB1c2luZyBKc1ZpZXdzLCB3ZSB0cmltIHRlbXBsYXRlcyB3aGljaCBhcmUgaW5zZXJ0ZWQgaW50byBIVE1MIGNvbnRleHRzIHdoZXJlIHRleHQgbm9kZXMgYXJlIG5vdCByZW5kZXJlZCAoaS5lLiBub3QgJ1BocmFzaW5nIENvbnRlbnQnKS5cblx0XHQvLyBDdXJyZW50bHkgbm90IHRyaW1tZWQgZm9yIDxsaT4gdGFnLiAoTm90IHdvcnRoIGFkZGluZyBwZXJmIGNvc3QpXG5cdFx0dG1wbC5tYXJrdXAgPSAkLnRyaW0odG1wbC5tYXJrdXApO1xuXHR9XG5cblx0cmV0dXJuIHRtcGw7XG59XG5cbi8vPT09PT09PT09PT09PT1cbi8vIHJlZ2lzdGVyU3RvcmVcbi8vPT09PT09PT09PT09PT1cblxuZnVuY3Rpb24gcmVnaXN0ZXJTdG9yZShzdG9yZU5hbWUsIHN0b3JlU2V0dGluZ3MpIHtcblxuXHRmdW5jdGlvbiB0aGVTdG9yZShuYW1lLCBpdGVtLCBwYXJlbnRUbXBsKSB7XG5cdFx0Ly8gVGhlIHN0b3JlIGlzIGFsc28gdGhlIGZ1bmN0aW9uIHVzZWQgdG8gYWRkIGl0ZW1zIHRvIHRoZSBzdG9yZS4gZS5nLiAkLnRlbXBsYXRlcywgb3IgJC52aWV3cy50YWdzXG5cblx0XHQvLyBGb3Igc3RvcmUgb2YgbmFtZSAndGhpbmcnLCBDYWxsIGFzOlxuXHRcdC8vICAgICQudmlld3MudGhpbmdzKGl0ZW1zWywgcGFyZW50VG1wbF0pLFxuXHRcdC8vIG9yICQudmlld3MudGhpbmdzKG5hbWUsIGl0ZW1bLCBwYXJlbnRUbXBsXSlcblxuXHRcdHZhciBjb21waWxlLCBpdGVtTmFtZSwgdGhpc1N0b3JlLCBjbnQsXG5cdFx0XHRvblN0b3JlID0gJHN1Yi5vblN0b3JlW3N0b3JlTmFtZV07XG5cblx0XHRpZiAobmFtZSAmJiB0eXBlb2YgbmFtZSA9PT0gT0JKRUNUICYmICFuYW1lLm5vZGVUeXBlICYmICFuYW1lLm1hcmt1cCAmJiAhbmFtZS5nZXRUZ3QgJiYgIShzdG9yZU5hbWUgPT09IFwidmlld01vZGVsXCIgJiYgbmFtZS5nZXR0ZXJzIHx8IG5hbWUuZXh0ZW5kKSkge1xuXHRcdFx0Ly8gQ2FsbCB0byAkLnZpZXdzLnRoaW5ncyhpdGVtc1ssIHBhcmVudFRtcGxdKSxcblxuXHRcdFx0Ly8gQWRkaW5nIGl0ZW1zIHRvIHRoZSBzdG9yZVxuXHRcdFx0Ly8gSWYgbmFtZSBpcyBhIGhhc2gsIHRoZW4gaXRlbSBpcyBwYXJlbnRUbXBsLiBJdGVyYXRlIG92ZXIgaGFzaCBhbmQgY2FsbCBzdG9yZSBmb3Iga2V5LlxuXHRcdFx0Zm9yIChpdGVtTmFtZSBpbiBuYW1lKSB7XG5cdFx0XHRcdHRoZVN0b3JlKGl0ZW1OYW1lLCBuYW1lW2l0ZW1OYW1lXSwgaXRlbSk7XG5cdFx0XHR9XG5cdFx0XHRyZXR1cm4gaXRlbSB8fCAkdmlld3M7XG5cdFx0fVxuXHRcdC8vIEFkZGluZyBhIHNpbmdsZSB1bm5hbWVkIGl0ZW0gdG8gdGhlIHN0b3JlXG5cdFx0aWYgKGl0ZW0gPT09IHVuZGVmaW5lZCkge1xuXHRcdFx0aXRlbSA9IG5hbWU7XG5cdFx0XHRuYW1lID0gdW5kZWZpbmVkO1xuXHRcdH1cblx0XHRpZiAobmFtZSAmJiBcIlwiICsgbmFtZSAhPT0gbmFtZSkgeyAvLyBuYW1lIG11c3QgYmUgYSBzdHJpbmdcblx0XHRcdHBhcmVudFRtcGwgPSBpdGVtO1xuXHRcdFx0aXRlbSA9IG5hbWU7XG5cdFx0XHRuYW1lID0gdW5kZWZpbmVkO1xuXHRcdH1cblx0XHR0aGlzU3RvcmUgPSBwYXJlbnRUbXBsXG5cdFx0XHQ/IHN0b3JlTmFtZSA9PT0gXCJ2aWV3TW9kZWxcIlxuXHRcdFx0XHQ/IHBhcmVudFRtcGxcblx0XHRcdFx0OiAocGFyZW50VG1wbFtzdG9yZU5hbWVzXSA9IHBhcmVudFRtcGxbc3RvcmVOYW1lc10gfHwge30pXG5cdFx0XHQ6IHRoZVN0b3JlO1xuXHRcdGNvbXBpbGUgPSBzdG9yZVNldHRpbmdzLmNvbXBpbGU7XG5cblx0XHRpZiAoaXRlbSA9PT0gbnVsbCkge1xuXHRcdFx0Ly8gSWYgaXRlbSBpcyBudWxsLCBkZWxldGUgdGhpcyBlbnRyeVxuXHRcdFx0aWYgKG5hbWUpIHtcblx0XHRcdFx0ZGVsZXRlIHRoaXNTdG9yZVtuYW1lXTtcblx0XHRcdH1cblx0XHR9IGVsc2Uge1xuXHRcdFx0aWYgKGNvbXBpbGUpIHtcblx0XHRcdFx0aXRlbSA9IGNvbXBpbGUuY2FsbCh0aGlzU3RvcmUsIG5hbWUsIGl0ZW0sIHBhcmVudFRtcGwsIDApIHx8IHt9O1xuXHRcdFx0XHRpdGVtLl9pcyA9IHN0b3JlTmFtZTsgLy8gT25seSBkbyB0aGlzIGZvciBjb21waWxlZCBvYmplY3RzICh0YWdzLCB0ZW1wbGF0ZXMuLi4pXG5cdFx0XHR9XG5cdFx0XHRpZiAobmFtZSkge1xuXHRcdFx0XHR0aGlzU3RvcmVbbmFtZV0gPSBpdGVtO1xuXHRcdFx0fVxuXHRcdH1cblx0XHRpZiAob25TdG9yZSkge1xuXHRcdFx0Ly8gZS5nLiBKc1ZpZXdzIGludGVncmF0aW9uXG5cdFx0XHRvblN0b3JlKG5hbWUsIGl0ZW0sIHBhcmVudFRtcGwsIGNvbXBpbGUpO1xuXHRcdH1cblx0XHRyZXR1cm4gaXRlbTtcblx0fVxuXG5cdHZhciBzdG9yZU5hbWVzID0gc3RvcmVOYW1lICsgXCJzXCI7XG5cdCR2aWV3c1tzdG9yZU5hbWVzXSA9IHRoZVN0b3JlO1xufVxuXG5mdW5jdGlvbiBhZGRTZXR0aW5nKHN0KSB7XG5cdCR2aWV3c1NldHRpbmdzW3N0XSA9IGZ1bmN0aW9uKHZhbHVlKSB7XG5cdFx0cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGhcblx0XHRcdD8gKCRzdWJTZXR0aW5nc1tzdF0gPSB2YWx1ZSwgJHZpZXdzU2V0dGluZ3MpXG5cdFx0XHQ6ICRzdWJTZXR0aW5nc1tzdF07XG5cdH07XG59XG5cbi8vPT09PT09PT09XG4vLyBkYXRhTWFwXG4vLz09PT09PT09PVxuXG5mdW5jdGlvbiBkYXRhTWFwKG1hcERlZikge1xuXHRmdW5jdGlvbiBNYXAoc291cmNlLCBvcHRpb25zKSB7XG5cdFx0dGhpcy50Z3QgPSBtYXBEZWYuZ2V0VGd0KHNvdXJjZSwgb3B0aW9ucyk7XG5cdH1cblxuXHRpZiAoJGlzRnVuY3Rpb24obWFwRGVmKSkge1xuXHRcdC8vIFNpbXBsZSBtYXAgZGVjbGFyZWQgYXMgZnVuY3Rpb25cblx0XHRtYXBEZWYgPSB7XG5cdFx0XHRnZXRUZ3Q6IG1hcERlZlxuXHRcdH07XG5cdH1cblxuXHRpZiAobWFwRGVmLmJhc2VNYXApIHtcblx0XHRtYXBEZWYgPSAkZXh0ZW5kKCRleHRlbmQoe30sIG1hcERlZi5iYXNlTWFwKSwgbWFwRGVmKTtcblx0fVxuXG5cdG1hcERlZi5tYXAgPSBmdW5jdGlvbihzb3VyY2UsIG9wdGlvbnMpIHtcblx0XHRyZXR1cm4gbmV3IE1hcChzb3VyY2UsIG9wdGlvbnMpO1xuXHR9O1xuXHRyZXR1cm4gbWFwRGVmO1xufVxuXG4vLz09PT09PT09PT09PT09XG4vLyByZW5kZXJDb250ZW50XG4vLz09PT09PT09PT09PT09XG5cbmZ1bmN0aW9uIHJlbmRlckNvbnRlbnQoZGF0YSwgY29udGV4dCwgbm9JdGVyYXRpb24sIHBhcmVudFZpZXcsIGtleSwgb25SZW5kZXIpIHtcblx0dmFyIGksIGwsIHRhZywgdG1wbCwgdGFnQ3R4LCBpc1RvcFJlbmRlckNhbGwsIHByZXZEYXRhLCBwcmV2SW5kZXgsXG5cdFx0dmlldyA9IHBhcmVudFZpZXcsXG5cdFx0cmVzdWx0ID0gXCJcIjtcblxuXHRpZiAoY29udGV4dCA9PT0gdHJ1ZSkge1xuXHRcdG5vSXRlcmF0aW9uID0gY29udGV4dDsgLy8gcGFzc2luZyBib29sZWFuIGFzIHNlY29uZCBwYXJhbSAtIG5vSXRlcmF0aW9uXG5cdFx0Y29udGV4dCA9IHVuZGVmaW5lZDtcblx0fSBlbHNlIGlmICh0eXBlb2YgY29udGV4dCAhPT0gT0JKRUNUKSB7XG5cdFx0Y29udGV4dCA9IHVuZGVmaW5lZDsgLy8gY29udGV4dCBtdXN0IGJlIGEgYm9vbGVhbiAobm9JdGVyYXRpb24pIG9yIGEgcGxhaW4gb2JqZWN0XG5cdH1cblxuXHRpZiAodGFnID0gdGhpcy50YWcpIHtcblx0XHQvLyBUaGlzIGlzIGEgY2FsbCBmcm9tIHJlbmRlclRhZyBvciB0YWdDdHgucmVuZGVyKC4uLilcblx0XHR0YWdDdHggPSB0aGlzO1xuXHRcdHZpZXcgPSB2aWV3IHx8IHRhZ0N0eC52aWV3O1xuXHRcdHRtcGwgPSB2aWV3LmdldFRtcGwodGFnLnRlbXBsYXRlIHx8IHRhZ0N0eC50bXBsKTtcblx0XHRpZiAoIWFyZ3VtZW50cy5sZW5ndGgpIHtcblx0XHRcdGRhdGEgPSB2aWV3O1xuXHRcdH1cblx0fSBlbHNlIHtcblx0XHQvLyBUaGlzIGlzIGEgdGVtcGxhdGUucmVuZGVyKC4uLikgY2FsbFxuXHRcdHRtcGwgPSB0aGlzO1xuXHR9XG5cblx0aWYgKHRtcGwpIHtcblx0XHRpZiAoIXBhcmVudFZpZXcgJiYgZGF0YSAmJiBkYXRhLl9pcyA9PT0gXCJ2aWV3XCIpIHtcblx0XHRcdHZpZXcgPSBkYXRhOyAvLyBXaGVuIHBhc3NpbmcgaW4gYSB2aWV3IHRvIHJlbmRlciBvciBsaW5rIChhbmQgbm90IHBhc3NpbmcgaW4gYSBwYXJlbnQgdmlldykgdXNlIHRoZSBwYXNzZWQtaW4gdmlldyBhcyBwYXJlbnRWaWV3XG5cdFx0fVxuXG5cdFx0aWYgKHZpZXcgJiYgZGF0YSA9PT0gdmlldykge1xuXHRcdFx0Ly8gSW5oZXJpdCB0aGUgZGF0YSBmcm9tIHRoZSBwYXJlbnQgdmlldy5cblx0XHRcdGRhdGEgPSB2aWV3LmRhdGE7XG5cdFx0fVxuXG5cdFx0aXNUb3BSZW5kZXJDYWxsID0gIXZpZXc7XG5cdFx0aXNSZW5kZXJDYWxsID0gaXNSZW5kZXJDYWxsIHx8IGlzVG9wUmVuZGVyQ2FsbDtcblx0XHRpZiAoIXZpZXcpIHtcblx0XHRcdChjb250ZXh0ID0gY29udGV4dCB8fCB7fSkucm9vdCA9IGRhdGE7IC8vIFByb3ZpZGUgfnJvb3QgYXMgc2hvcnRjdXQgdG8gdG9wLWxldmVsIGRhdGEuXG5cdFx0fVxuXHRcdGlmICghaXNSZW5kZXJDYWxsIHx8ICRzdWJTZXR0aW5nc0FkdmFuY2VkLnVzZVZpZXdzIHx8IHRtcGwudXNlVmlld3MgfHwgdmlldyAmJiB2aWV3ICE9PSB0b3BWaWV3KSB7XG5cdFx0XHRyZXN1bHQgPSByZW5kZXJXaXRoVmlld3ModG1wbCwgZGF0YSwgY29udGV4dCwgbm9JdGVyYXRpb24sIHZpZXcsIGtleSwgb25SZW5kZXIsIHRhZyk7XG5cdFx0fSBlbHNlIHtcblx0XHRcdGlmICh2aWV3KSB7IC8vIEluIGEgYmxvY2tcblx0XHRcdFx0cHJldkRhdGEgPSB2aWV3LmRhdGE7XG5cdFx0XHRcdHByZXZJbmRleCA9IHZpZXcuaW5kZXg7XG5cdFx0XHRcdHZpZXcuaW5kZXggPSBpbmRleFN0cjtcblx0XHRcdH0gZWxzZSB7XG5cdFx0XHRcdHZpZXcgPSB0b3BWaWV3O1xuXHRcdFx0XHRwcmV2RGF0YSA9IHZpZXcuZGF0YTtcblx0XHRcdFx0dmlldy5kYXRhID0gZGF0YTtcblx0XHRcdFx0dmlldy5jdHggPSBjb250ZXh0O1xuXHRcdFx0fVxuXHRcdFx0aWYgKCRpc0FycmF5KGRhdGEpICYmICFub0l0ZXJhdGlvbikge1xuXHRcdFx0XHQvLyBDcmVhdGUgYSB2aWV3IGZvciB0aGUgYXJyYXksIHdob3NlIGNoaWxkIHZpZXdzIGNvcnJlc3BvbmQgdG8gZWFjaCBkYXRhIGl0ZW0uIChOb3RlOiBpZiBrZXkgYW5kIHBhcmVudFZpZXcgYXJlIHBhc3NlZCBpblxuXHRcdFx0XHQvLyBhbG9uZyB3aXRoIHBhcmVudCB2aWV3LCB0cmVhdCBhcyBpbnNlcnQgLWUuZy4gZnJvbSB2aWV3LmFkZFZpZXdzIC0gc28gcGFyZW50VmlldyBpcyBhbHJlYWR5IHRoZSB2aWV3IGl0ZW0gZm9yIGFycmF5KVxuXHRcdFx0XHRmb3IgKGkgPSAwLCBsID0gZGF0YS5sZW5ndGg7IGkgPCBsOyBpKyspIHtcblx0XHRcdFx0XHR2aWV3LmluZGV4ID0gaTtcblx0XHRcdFx0XHR2aWV3LmRhdGEgPSBkYXRhW2ldO1xuXHRcdFx0XHRcdHJlc3VsdCArPSB0bXBsLmZuKGRhdGFbaV0sIHZpZXcsICRzdWIpO1xuXHRcdFx0XHR9XG5cdFx0XHR9IGVsc2Uge1xuXHRcdFx0XHR2aWV3LmRhdGEgPSBkYXRhO1xuXHRcdFx0XHRyZXN1bHQgKz0gdG1wbC5mbihkYXRhLCB2aWV3LCAkc3ViKTtcblx0XHRcdH1cblx0XHRcdHZpZXcuZGF0YSA9IHByZXZEYXRhO1xuXHRcdFx0dmlldy5pbmRleCA9IHByZXZJbmRleDtcblx0XHR9XG5cdFx0aWYgKGlzVG9wUmVuZGVyQ2FsbCkge1xuXHRcdFx0aXNSZW5kZXJDYWxsID0gdW5kZWZpbmVkO1xuXHRcdH1cblx0fVxuXHRyZXR1cm4gcmVzdWx0O1xufVxuXG5mdW5jdGlvbiByZW5kZXJXaXRoVmlld3ModG1wbCwgZGF0YSwgY29udGV4dCwgbm9JdGVyYXRpb24sIHZpZXcsIGtleSwgb25SZW5kZXIsIHRhZykge1xuXHRmdW5jdGlvbiBzZXRJdGVtVmFyKGl0ZW0pIHtcblx0XHQvLyBXaGVuIGl0ZW1WYXIgaXMgc3BlY2lmaWVkLCBzZXQgbW9kaWZpZWQgY3R4IHdpdGggdXNlci1uYW1lZCB+aXRlbVxuXHRcdG5ld0N0eCA9ICRleHRlbmQoe30sIGNvbnRleHQpO1xuXHRcdG5ld0N0eFtpdGVtVmFyXSA9IGl0ZW07XG5cdH1cblxuXHQvLyBSZW5kZXIgdGVtcGxhdGUgYWdhaW5zdCBkYXRhIGFzIGEgdHJlZSBvZiBzdWJ2aWV3cyAobmVzdGVkIHJlbmRlcmVkIHRlbXBsYXRlIGluc3RhbmNlcyksIG9yIGFzIGEgc3RyaW5nICh0b3AtbGV2ZWwgdGVtcGxhdGUpLlxuXHQvLyBJZiB0aGUgZGF0YSBpcyB0aGUgcGFyZW50IHZpZXcsIHRyZWF0IGFzIG5vSXRlcmF0aW9uLCByZS1yZW5kZXIgd2l0aCB0aGUgc2FtZSBkYXRhIGNvbnRleHQuXG5cdC8vIHRtcGwgY2FuIGJlIGEgc3RyaW5nIChlLmcuIHJlbmRlcmVkIGJ5IGEgdGFnLnJlbmRlcigpIG1ldGhvZCksIG9yIGEgY29tcGlsZWQgdGVtcGxhdGUuXG5cdHZhciBpLCBsLCBuZXdWaWV3LCBjaGlsZFZpZXcsIGl0ZW1SZXN1bHQsIHN3YXBDb250ZW50LCBjb250ZW50VG1wbCwgb3V0ZXJPblJlbmRlciwgdG1wbE5hbWUsIGl0ZW1WYXIsIG5ld0N0eCwgdGFnQ3R4LFxuXHRcdHJlc3VsdCA9IFwiXCI7XG5cblx0aWYgKHRhZykge1xuXHRcdC8vIFRoaXMgaXMgYSBjYWxsIGZyb20gcmVuZGVyVGFnIG9yIHRhZ0N0eC5yZW5kZXIoLi4uKVxuXHRcdHRtcGxOYW1lID0gdGFnLnRhZ05hbWU7XG5cdFx0dGFnQ3R4ID0gdGFnLnRhZ0N0eDtcblx0XHRjb250ZXh0ID0gY29udGV4dCA/IGV4dGVuZEN0eChjb250ZXh0LCB0YWcuY3R4KSA6IHRhZy5jdHg7XG5cblx0XHRpZiAodG1wbCA9PT0gdmlldy5jb250ZW50KSB7IC8vIHt7eHh4IHRtcGw9I2NvbnRlbnR9fVxuXHRcdFx0Y29udGVudFRtcGwgPSB0bXBsICE9PSB2aWV3LmN0eC5fd3JwIC8vIFdlIGFyZSByZW5kZXJpbmcgdGhlICNjb250ZW50XG5cdFx0XHRcdD8gdmlldy5jdHguX3dycCAvLyAjY29udGVudCB3YXMgdGhlIHRhZ0N0eC5wcm9wcy50bXBsIHdyYXBwZXIgb2YgdGhlIGJsb2NrIGNvbnRlbnQgLSBzbyB3aXRoaW4gdGhpcyB2aWV3LCAjY29udGVudCB3aWxsIG5vdyBiZSB0aGUgdmlldy5jdHguX3dycCBibG9jayBjb250ZW50XG5cdFx0XHRcdDogdW5kZWZpbmVkOyAvLyAjY29udGVudCB3YXMgdGhlIHZpZXcuY3R4Ll93cnAgYmxvY2sgY29udGVudCAtIHNvIHdpdGhpbiB0aGlzIHZpZXcsIHRoZXJlIGlzIG5vIGxvbmdlciBhbnkgI2NvbnRlbnQgdG8gd3JhcC5cblx0XHR9IGVsc2UgaWYgKHRtcGwgIT09IHRhZ0N0eC5jb250ZW50KSB7XG5cdFx0XHRpZiAodG1wbCA9PT0gdGFnLnRlbXBsYXRlKSB7IC8vIFJlbmRlcmluZyB7e3RhZ319IHRhZy50ZW1wbGF0ZSwgcmVwbGFjaW5nIGJsb2NrIGNvbnRlbnQuXG5cdFx0XHRcdGNvbnRlbnRUbXBsID0gdGFnQ3R4LnRtcGw7IC8vIFNldCAjY29udGVudCB0byBibG9jayBjb250ZW50IChvciB3cmFwcGVkIGJsb2NrIGNvbnRlbnQgaWYgdGFnQ3R4LnByb3BzLnRtcGwgaXMgc2V0KVxuXHRcdFx0XHRjb250ZXh0Ll93cnAgPSB0YWdDdHguY29udGVudDsgLy8gUGFzcyB3cmFwcGVkIGJsb2NrIGNvbnRlbnQgdG8gbmVzdGVkIHZpZXdzXG5cdFx0XHR9IGVsc2UgeyAvLyBSZW5kZXJpbmcgdGFnQ3R4LnByb3BzLnRtcGwgd3JhcHBlclxuXHRcdFx0XHRjb250ZW50VG1wbCA9IHRhZ0N0eC5jb250ZW50IHx8IHZpZXcuY29udGVudDsgLy8gU2V0ICNjb250ZW50IHRvIHdyYXBwZWQgYmxvY2sgY29udGVudFxuXHRcdFx0fVxuXHRcdH0gZWxzZSB7XG5cdFx0XHRjb250ZW50VG1wbCA9IHZpZXcuY29udGVudDsgLy8gTmVzdGVkIHZpZXdzIGluaGVyaXQgc2FtZSB3cmFwcGVkICNjb250ZW50IHByb3BlcnR5XG5cdFx0fVxuXG5cdFx0aWYgKHRhZ0N0eC5wcm9wcy5saW5rID09PSBmYWxzZSkge1xuXHRcdFx0Ly8gbGluaz1mYWxzZSBzZXR0aW5nIG9uIGJsb2NrIHRhZ1xuXHRcdFx0Ly8gV2Ugd2lsbCBvdmVycmlkZSBpbmhlcml0ZWQgdmFsdWUgb2YgbGluayBieSB0aGUgZXhwbGljaXQgc2V0dGluZyBsaW5rPWZhbHNlIHRha2VuIGZyb20gcHJvcHNcblx0XHRcdC8vIFRoZSBjaGlsZCB2aWV3cyBvZiBhbiB1bmxpbmtlZCB2aWV3IGFyZSBhbHNvIHVubGlua2VkLiBTbyBzZXR0aW5nIGNoaWxkIGJhY2sgdG8gdHJ1ZSB3aWxsIG5vdCBoYXZlIGFueSBlZmZlY3QuXG5cdFx0XHRjb250ZXh0ID0gY29udGV4dCB8fCB7fTtcblx0XHRcdGNvbnRleHQubGluayA9IGZhbHNlO1xuXHRcdH1cblxuXHRcdGlmIChpdGVtVmFyID0gdGFnQ3R4LnByb3BzLml0ZW1WYXIpIHtcblx0XHRcdGlmIChpdGVtVmFyLmNoYXJBdCgwKSAhPT0gXCJ+XCIpIHtcblx0XHRcdFx0c3ludGF4RXJyb3IoXCJVc2UgaXRlbVZhcj0nfm15SXRlbSdcIik7XG5cdFx0XHR9XG5cdFx0XHRpdGVtVmFyID0gaXRlbVZhci5zbGljZSgxKTtcblx0XHR9XG5cdH1cblxuXHRpZiAodmlldykge1xuXHRcdG9uUmVuZGVyID0gb25SZW5kZXIgfHwgdmlldy5fLm9uUmVuZGVyO1xuXHRcdGNvbnRleHQgPSBleHRlbmRDdHgoY29udGV4dCwgdmlldy5jdHgpO1xuXHR9XG5cblx0aWYgKGtleSA9PT0gdHJ1ZSkge1xuXHRcdHN3YXBDb250ZW50ID0gdHJ1ZTtcblx0XHRrZXkgPSAwO1xuXHR9XG5cblx0Ly8gSWYgbGluaz09PWZhbHNlLCBkbyBub3QgY2FsbCBvblJlbmRlciwgc28gbm8gZGF0YS1saW5raW5nIG1hcmtlciBub2Rlc1xuXHRpZiAob25SZW5kZXIgJiYgKGNvbnRleHQgJiYgY29udGV4dC5saW5rID09PSBmYWxzZSB8fCB0YWcgJiYgdGFnLl8ubm9Wd3MpKSB7XG5cdFx0b25SZW5kZXIgPSB1bmRlZmluZWQ7XG5cdH1cblx0b3V0ZXJPblJlbmRlciA9IG9uUmVuZGVyO1xuXHRpZiAob25SZW5kZXIgPT09IHRydWUpIHtcblx0XHQvLyBVc2VkIGJ5IHZpZXcucmVmcmVzaCgpLiBEb24ndCBjcmVhdGUgYSBuZXcgd3JhcHBlciB2aWV3LlxuXHRcdG91dGVyT25SZW5kZXIgPSB1bmRlZmluZWQ7XG5cdFx0b25SZW5kZXIgPSB2aWV3Ll8ub25SZW5kZXI7XG5cdH1cblx0Ly8gU2V0IGFkZGl0aW9uYWwgY29udGV4dCBvbiB2aWV3cyBjcmVhdGVkIGhlcmUsIChhcyBtb2RpZmllZCBjb250ZXh0IGluaGVyaXRlZCBmcm9tIHRoZSBwYXJlbnQsIGFuZCB0byBiZSBpbmhlcml0ZWQgYnkgY2hpbGQgdmlld3MpXG5cdGNvbnRleHQgPSB0bXBsLmhlbHBlcnNcblx0XHQ/IGV4dGVuZEN0eCh0bXBsLmhlbHBlcnMsIGNvbnRleHQpXG5cdFx0OiBjb250ZXh0O1xuXG5cdG5ld0N0eCA9IGNvbnRleHQ7XG5cdGlmICgkaXNBcnJheShkYXRhKSAmJiAhbm9JdGVyYXRpb24pIHtcblx0XHQvLyBDcmVhdGUgYSB2aWV3IGZvciB0aGUgYXJyYXksIHdob3NlIGNoaWxkIHZpZXdzIGNvcnJlc3BvbmQgdG8gZWFjaCBkYXRhIGl0ZW0uIChOb3RlOiBpZiBrZXkgYW5kIHZpZXcgYXJlIHBhc3NlZCBpblxuXHRcdC8vIGFsb25nIHdpdGggcGFyZW50IHZpZXcsIHRyZWF0IGFzIGluc2VydCAtZS5nLiBmcm9tIHZpZXcuYWRkVmlld3MgLSBzbyB2aWV3IGlzIGFscmVhZHkgdGhlIHZpZXcgaXRlbSBmb3IgYXJyYXkpXG5cdFx0bmV3VmlldyA9IHN3YXBDb250ZW50XG5cdFx0XHQ/IHZpZXdcblx0XHRcdDogKGtleSAhPT0gdW5kZWZpbmVkICYmIHZpZXcpXG5cdFx0XHRcdHx8IG5ldyBWaWV3KGNvbnRleHQsIFwiYXJyYXlcIiwgdmlldywgZGF0YSwgdG1wbCwga2V5LCBvblJlbmRlciwgY29udGVudFRtcGwpO1xuXHRcdGlmICh2aWV3ICYmIHZpZXcuXy51c2VLZXkpIHtcblx0XHRcdC8vIFBhcmVudCBpcyBub3QgYW4gJ2FycmF5IHZpZXcnXG5cdFx0XHRuZXdWaWV3Ll8uYm5kID0gIXRhZyB8fCB0YWcuXy5ibmQgJiYgdGFnOyAvLyBGb3IgYXJyYXkgdmlld3MgdGhhdCBhcmUgZGF0YSBib3VuZCBmb3IgY29sbGVjdGlvbiBjaGFuZ2UgZXZlbnRzLCBzZXQgdGhlXG5cdFx0XHQvLyB2aWV3Ll8uYm5kIHByb3BlcnR5IHRvIHRydWUgZm9yIHRvcC1sZXZlbCBsaW5rKCkgb3IgZGF0YS1saW5rPVwie2Zvcn1cIiwgb3IgdG8gdGhlIHRhZyBpbnN0YW5jZSBmb3IgYSBkYXRhLWJvdW5kIHRhZywgZS5nLiB7Xntmb3IgLi4ufX1cblx0XHR9XG5cdFx0Zm9yIChpID0gMCwgbCA9IGRhdGEubGVuZ3RoOyBpIDwgbDsgaSsrKSB7XG5cdFx0XHQvLyBDcmVhdGUgYSB2aWV3IGZvciBlYWNoIGRhdGEgaXRlbS5cblx0XHRcdGlmIChpdGVtVmFyKSB7XG5cdFx0XHRcdHNldEl0ZW1WYXIoZGF0YVtpXSk7IC8vIHVzZSBtb2RpZmllZCBjdHggd2l0aCB1c2VyLW5hbWVkIH5pdGVtXG5cdFx0XHR9XG5cdFx0XHRjaGlsZFZpZXcgPSBuZXcgVmlldyhuZXdDdHgsIFwiaXRlbVwiLCBuZXdWaWV3LCBkYXRhW2ldLCB0bXBsLCAoa2V5IHx8IDApICsgaSwgb25SZW5kZXIsIG5ld1ZpZXcuY29udGVudCk7XG5cdFx0XHRjaGlsZFZpZXcuXy5pdCA9IGl0ZW1WYXI7XG5cblx0XHRcdGl0ZW1SZXN1bHQgPSB0bXBsLmZuKGRhdGFbaV0sIGNoaWxkVmlldywgJHN1Yik7XG5cdFx0XHRyZXN1bHQgKz0gbmV3Vmlldy5fLm9uUmVuZGVyID8gbmV3Vmlldy5fLm9uUmVuZGVyKGl0ZW1SZXN1bHQsIGNoaWxkVmlldykgOiBpdGVtUmVzdWx0O1xuXHRcdH1cblx0fSBlbHNlIHtcblx0XHQvLyBDcmVhdGUgYSB2aWV3IGZvciBzaW5nbGV0b24gZGF0YSBvYmplY3QuIFRoZSB0eXBlIG9mIHRoZSB2aWV3IHdpbGwgYmUgdGhlIHRhZyBuYW1lLCBlLmcuIFwiaWZcIiBvciBcIm15dGFnXCIgZXhjZXB0IGZvclxuXHRcdC8vIFwiaXRlbVwiLCBcImFycmF5XCIgYW5kIFwiZGF0YVwiIHZpZXdzLiBBIFwiZGF0YVwiIHZpZXcgaXMgZnJvbSBwcm9ncmFtbWF0aWMgcmVuZGVyKG9iamVjdCkgYWdhaW5zdCBhICdzaW5nbGV0b24nLlxuXHRcdGlmIChpdGVtVmFyKSB7XG5cdFx0XHRzZXRJdGVtVmFyKGRhdGEpO1xuXHRcdH1cblx0XHRuZXdWaWV3ID0gc3dhcENvbnRlbnQgPyB2aWV3IDogbmV3IFZpZXcobmV3Q3R4LCB0bXBsTmFtZSB8fCBcImRhdGFcIiwgdmlldywgZGF0YSwgdG1wbCwga2V5LCBvblJlbmRlciwgY29udGVudFRtcGwpO1xuXHRcdG5ld1ZpZXcuXy5pdCA9IGl0ZW1WYXI7XG5cdFx0cmVzdWx0ICs9IHRtcGwuZm4oZGF0YSwgbmV3VmlldywgJHN1Yik7XG5cdH1cblx0aWYgKHRhZykge1xuXHRcdG5ld1ZpZXcudGFnID0gdGFnO1xuXHRcdG5ld1ZpZXcudGFnRWxzZSA9IHRhZ0N0eC5pbmRleDtcblx0XHR0YWdDdHguY29udGVudFZpZXcgPSBuZXdWaWV3O1xuXHR9XG5cdHJldHVybiBvdXRlck9uUmVuZGVyID8gb3V0ZXJPblJlbmRlcihyZXN1bHQsIG5ld1ZpZXcpIDogcmVzdWx0O1xufVxuXG4vLz09PT09PT09PT09PT09PT09PT09PT09PT09PVxuLy8gQnVpbGQgYW5kIGNvbXBpbGUgdGVtcGxhdGVcbi8vPT09PT09PT09PT09PT09PT09PT09PT09PT09XG5cbi8vIEdlbmVyYXRlIGEgcmV1c2FibGUgZnVuY3Rpb24gdGhhdCB3aWxsIHNlcnZlIHRvIHJlbmRlciBhIHRlbXBsYXRlIGFnYWluc3QgZGF0YVxuLy8gKENvbXBpbGUgQVNUIHRoZW4gYnVpbGQgdGVtcGxhdGUgZnVuY3Rpb24pXG5cbmZ1bmN0aW9uIG9uUmVuZGVyRXJyb3IoZSwgdmlldywgZmFsbGJhY2spIHtcblx0dmFyIG1lc3NhZ2UgPSBmYWxsYmFjayAhPT0gdW5kZWZpbmVkXG5cdFx0PyAkaXNGdW5jdGlvbihmYWxsYmFjaylcblx0XHRcdD8gZmFsbGJhY2suY2FsbCh2aWV3LmRhdGEsIGUsIHZpZXcpXG5cdFx0XHQ6IGZhbGxiYWNrIHx8IFwiXCJcblx0XHQ6IFwie0Vycm9yOiBcIiArIChlLm1lc3NhZ2V8fGUpICsgXCJ9XCI7XG5cblx0aWYgKCRzdWJTZXR0aW5ncy5vbkVycm9yICYmIChmYWxsYmFjayA9ICRzdWJTZXR0aW5ncy5vbkVycm9yLmNhbGwodmlldy5kYXRhLCBlLCBmYWxsYmFjayAmJiBtZXNzYWdlLCB2aWV3KSkgIT09IHVuZGVmaW5lZCkge1xuXHRcdG1lc3NhZ2UgPSBmYWxsYmFjazsgLy8gVGhlcmUgaXMgYSBzZXR0aW5ncy5kZWJ1Z01vZGUoaGFuZGxlcikgb25FcnJvciBvdmVycmlkZS4gQ2FsbCBpdCwgYW5kIHVzZSByZXR1cm4gdmFsdWUgKGlmIGFueSkgdG8gcmVwbGFjZSBtZXNzYWdlXG5cdH1cblxuXHRyZXR1cm4gdmlldyAmJiAhdmlldy5saW5rQ3R4ID8gJGNvbnZlcnRlcnMuaHRtbChtZXNzYWdlKSA6IG1lc3NhZ2U7XG59XG5cbmZ1bmN0aW9uIGVycm9yKG1lc3NhZ2UpIHtcblx0dGhyb3cgbmV3ICRzdWIuRXJyKG1lc3NhZ2UpO1xufVxuXG5mdW5jdGlvbiBzeW50YXhFcnJvcihtZXNzYWdlKSB7XG5cdGVycm9yKFwiU3ludGF4IGVycm9yXFxuXCIgKyBtZXNzYWdlKTtcbn1cblxuZnVuY3Rpb24gdG1wbEZuKG1hcmt1cCwgdG1wbCwgaXNMaW5rRXhwciwgY29udmVydEJhY2ssIGhhc0Vsc2UpIHtcblx0Ly8gQ29tcGlsZSBtYXJrdXAgdG8gQVNUIChhYnRyYWN0IHN5bnRheCB0cmVlKSB0aGVuIGJ1aWxkIHRoZSB0ZW1wbGF0ZSBmdW5jdGlvbiBjb2RlIGZyb20gdGhlIEFTVCBub2Rlc1xuXHQvLyBVc2VkIGZvciBjb21waWxpbmcgdGVtcGxhdGVzLCBhbmQgYWxzbyBieSBKc1ZpZXdzIHRvIGJ1aWxkIGZ1bmN0aW9ucyBmb3IgZGF0YSBsaW5rIGV4cHJlc3Npb25zXG5cblx0Ly89PT09IG5lc3RlZCBmdW5jdGlvbnMgPT09PVxuXHRmdW5jdGlvbiBwdXNocHJlY2VkaW5nQ29udGVudChzaGlmdCkge1xuXHRcdHNoaWZ0IC09IGxvYztcblx0XHRpZiAoc2hpZnQpIHtcblx0XHRcdGNvbnRlbnQucHVzaChtYXJrdXAuc3Vic3RyKGxvYywgc2hpZnQpLnJlcGxhY2Uock5ld0xpbmUsIFwiXFxcXG5cIikpO1xuXHRcdH1cblx0fVxuXG5cdGZ1bmN0aW9uIGJsb2NrVGFnQ2hlY2sodGFnTmFtZSwgYmxvY2spIHtcblx0XHRpZiAodGFnTmFtZSkge1xuXHRcdFx0dGFnTmFtZSArPSAnfX0nO1xuXHRcdFx0Ly9cdFx0XHQne3tpbmNsdWRlfX0gYmxvY2sgaGFzIHt7L2Zvcn19IHdpdGggbm8gb3BlbiB7e2Zvcn19J1xuXHRcdFx0c3ludGF4RXJyb3IoKFxuXHRcdFx0XHRibG9ja1xuXHRcdFx0XHRcdD8gJ3t7JyArIGJsb2NrICsgJ319IGJsb2NrIGhhcyB7ey8nICsgdGFnTmFtZSArICcgd2l0aG91dCB7eycgKyB0YWdOYW1lXG5cdFx0XHRcdFx0OiAnVW5tYXRjaGVkIG9yIG1pc3Npbmcge3svJyArIHRhZ05hbWUpICsgJywgaW4gdGVtcGxhdGU6XFxuJyArIG1hcmt1cCk7XG5cdFx0fVxuXHR9XG5cblx0ZnVuY3Rpb24gcGFyc2VUYWcoYWxsLCBiaW5kLCB0YWdOYW1lLCBjb252ZXJ0ZXIsIGNvbG9uLCBodG1sLCBjb2RlVGFnLCBwYXJhbXMsIHNsYXNoLCBiaW5kMiwgY2xvc2VCbG9jaywgaW5kZXgpIHtcbi8qXG5cbiAgICAgYmluZCAgICAgdGFnTmFtZSAgICAgICAgIGN2dCAgIGNsbiBodG1sIGNvZGUgICAgcGFyYW1zICAgICAgICAgICAgc2xhc2ggICBiaW5kMiAgICAgICAgIGNsb3NlQmxrICBjb21tZW50XG4vKD86eyhcXF4pP3soPzooXFx3Kyg/PVtcXC9cXHN9XSkpfChcXHcrKT8oOil8KD4pfChcXCopKVxccyooKD86W159XXx9KD8hfSkpKj8pKFxcLyk/fHsoXFxeKT97KD86KD86XFwvKFxcdyspKVxccyp8IS0tW1xcc1xcU10qPy0tKSl9fS9nXG5cbig/OlxuICB7KFxcXik/eyAgICAgICAgICAgIGJpbmRcbiAgKD86XG4gICAgKFxcdysgICAgICAgICAgICAgdGFnTmFtZVxuICAgICAgKD89W1xcL1xcc31dKVxuICAgIClcbiAgICB8XG4gICAgKFxcdyspPyg6KSAgICAgICAgY29udmVydGVyIGNvbG9uXG4gICAgfFxuICAgICg+KSAgICAgICAgICAgICAgaHRtbFxuICAgIHxcbiAgICAoXFwqKSAgICAgICAgICAgICBjb2RlVGFnXG4gIClcbiAgXFxzKlxuICAoICAgICAgICAgICAgICAgICAgcGFyYW1zXG4gICAgKD86W159XXx9KD8hfSkpKj9cbiAgKVxuICAoXFwvKT8gICAgICAgICAgICAgIHNsYXNoXG4gIHxcbiAgeyhcXF4pP3sgICAgICAgICAgICBiaW5kMlxuICAoPzpcbiAgICAoPzpcXC8oXFx3KykpXFxzKiAgIGNsb3NlQmxvY2tcbiAgICB8XG4gICAgIS0tW1xcc1xcU10qPy0tICAgIGNvbW1lbnRcbiAgKVxuKVxufX0vZ1xuXG4qL1xuXHRcdGlmIChjb2RlVGFnICYmIGJpbmQgfHwgc2xhc2ggJiYgIXRhZ05hbWUgfHwgcGFyYW1zICYmIHBhcmFtcy5zbGljZSgtMSkgPT09IFwiOlwiIHx8IGJpbmQyKSB7XG5cdFx0XHRzeW50YXhFcnJvcihhbGwpO1xuXHRcdH1cblxuXHRcdC8vIEJ1aWxkIGFic3RyYWN0IHN5bnRheCB0cmVlIChBU1QpOiBbdGFnTmFtZSwgY29udmVydGVyLCBwYXJhbXMsIGNvbnRlbnQsIGhhc2gsIGJpbmRpbmdzLCBjb250ZW50TWFya3VwXVxuXHRcdGlmIChodG1sKSB7XG5cdFx0XHRjb2xvbiA9IFwiOlwiO1xuXHRcdFx0Y29udmVydGVyID0gSFRNTDtcblx0XHR9XG5cdFx0c2xhc2ggPSBzbGFzaCB8fCBpc0xpbmtFeHByICYmICFoYXNFbHNlO1xuXG5cdFx0dmFyIGxhdGUsXG5cdFx0XHRwYXRoQmluZGluZ3MgPSAoYmluZCB8fCBpc0xpbmtFeHByKSAmJiBbW11dLCAvLyBwYXRoQmluZGluZ3MgaXMgYW4gYXJyYXkgb2YgYXJyYXlzIGZvciBhcmcgYmluZGluZ3MgYW5kIGEgaGFzaCBvZiBhcnJheXMgZm9yIHByb3AgYmluZGluZ3Ncblx0XHRcdHByb3BzID0gXCJcIixcblx0XHRcdGFyZ3MgPSBcIlwiLFxuXHRcdFx0Y3R4UHJvcHMgPSBcIlwiLFxuXHRcdFx0cGFyYW1zQXJncyA9IFwiXCIsXG5cdFx0XHRwYXJhbXNQcm9wcyA9IFwiXCIsXG5cdFx0XHRwYXJhbXNDdHhQcm9wcyA9IFwiXCIsXG5cdFx0XHRvbkVycm9yID0gXCJcIixcblx0XHRcdHVzZVRyaWdnZXIgPSBcIlwiLFxuXHRcdFx0Ly8gQmxvY2sgdGFnIGlmIG5vdCBzZWxmLWNsb3NpbmcgYW5kIG5vdCB7ezp9fSBvciB7ez59fSAoc3BlY2lhbCBjYXNlKSBhbmQgbm90IGEgZGF0YS1saW5rIGV4cHJlc3Npb25cblx0XHRcdGJsb2NrID0gIXNsYXNoICYmICFjb2xvbjtcblxuXHRcdC8vPT09PSBuZXN0ZWQgaGVscGVyIGZ1bmN0aW9uID09PT1cblx0XHR0YWdOYW1lID0gdGFnTmFtZSB8fCAocGFyYW1zID0gcGFyYW1zIHx8IFwiI2RhdGFcIiwgY29sb24pOyAvLyB7ezp9fSBpcyBlcXVpdmFsZW50IHRvIHt7OiNkYXRhfX1cblx0XHRwdXNocHJlY2VkaW5nQ29udGVudChpbmRleCk7XG5cdFx0bG9jID0gaW5kZXggKyBhbGwubGVuZ3RoOyAvLyBsb2NhdGlvbiBtYXJrZXIgLSBwYXJzZWQgdXAgdG8gaGVyZVxuXHRcdGlmIChjb2RlVGFnKSB7XG5cdFx0XHRpZiAoYWxsb3dDb2RlKSB7XG5cdFx0XHRcdGNvbnRlbnQucHVzaChbXCIqXCIsIFwiXFxuXCIgKyBwYXJhbXMucmVwbGFjZSgvXjovLCBcInJldCs9IFwiKS5yZXBsYWNlKHJVbmVzY2FwZVF1b3RlcywgXCIkMVwiKSArIFwiO1xcblwiXSk7XG5cdFx0XHR9XG5cdFx0fSBlbHNlIGlmICh0YWdOYW1lKSB7XG5cdFx0XHRpZiAodGFnTmFtZSA9PT0gXCJlbHNlXCIpIHtcblx0XHRcdFx0aWYgKHJUZXN0RWxzZUlmLnRlc3QocGFyYW1zKSkge1xuXHRcdFx0XHRcdHN5bnRheEVycm9yKCdmb3IgXCJ7e2Vsc2UgaWYgZXhwcn19XCIgdXNlIFwie3tlbHNlIGV4cHJ9fVwiJyk7XG5cdFx0XHRcdH1cblx0XHRcdFx0cGF0aEJpbmRpbmdzID0gY3VycmVudFs4XSAmJiBbW11dO1xuXHRcdFx0XHRjdXJyZW50WzldID0gbWFya3VwLnN1YnN0cmluZyhjdXJyZW50WzldLCBpbmRleCk7IC8vIGNvbnRlbnRNYXJrdXAgZm9yIGJsb2NrIHRhZ1xuXHRcdFx0XHRjdXJyZW50ID0gc3RhY2sucG9wKCk7XG5cdFx0XHRcdGNvbnRlbnQgPSBjdXJyZW50WzJdO1xuXHRcdFx0XHRibG9jayA9IHRydWU7XG5cdFx0XHR9XG5cdFx0XHRpZiAocGFyYW1zKSB7XG5cdFx0XHRcdC8vIHJlbW92ZSBuZXdsaW5lcyBmcm9tIHRoZSBwYXJhbXMgc3RyaW5nLCB0byBhdm9pZCBjb21waWxlZCBjb2RlIGVycm9ycyBmb3IgdW50ZXJtaW5hdGVkIHN0cmluZ3Ncblx0XHRcdFx0cGFyc2VQYXJhbXMocGFyYW1zLnJlcGxhY2Uock5ld0xpbmUsIFwiIFwiKSwgcGF0aEJpbmRpbmdzLCB0bXBsKVxuXHRcdFx0XHRcdC5yZXBsYWNlKHJCdWlsZEhhc2gsIGZ1bmN0aW9uKGFsbCwgb25lcnJvciwgaXNDdHgsIGtleSwga2V5VG9rZW4sIGtleVZhbHVlLCBhcmcsIHBhcmFtKSB7XG5cdFx0XHRcdFx0XHRrZXkgPSBcIidcIiArIGtleVRva2VuICsgXCInOlwiO1xuXHRcdFx0XHRcdFx0aWYgKGFyZykge1xuXHRcdFx0XHRcdFx0XHRhcmdzICs9IGtleVZhbHVlICsgXCIsXCI7XG5cdFx0XHRcdFx0XHRcdHBhcmFtc0FyZ3MgKz0gXCInXCIgKyBwYXJhbSArIFwiJyxcIjtcblx0XHRcdFx0XHRcdH0gZWxzZSBpZiAoaXNDdHgpIHtcblx0XHRcdFx0XHRcdFx0Y3R4UHJvcHMgKz0ga2V5ICsgJ2ouX2NwKCcgKyBrZXlWYWx1ZSArICcsXCInICsgcGFyYW0gKyAnXCIsdmlldyksJztcblx0XHRcdFx0XHRcdFx0Ly8gQ29tcGlsZWQgY29kZSBmb3IgZXZhbHVhdGluZyB0YWdDdHggb24gYSB0YWcgd2lsbCBoYXZlOiBjdHg6eydmb28nOmouX2NwKGNvbXBpbGVkRXhwciwgXCJleHByXCIsIHZpZXcpfVxuXHRcdFx0XHRcdFx0XHRwYXJhbXNDdHhQcm9wcyArPSBrZXkgKyBcIidcIiArIHBhcmFtICsgXCInLFwiO1xuXHRcdFx0XHRcdFx0fSBlbHNlIGlmIChvbmVycm9yKSB7XG5cdFx0XHRcdFx0XHRcdG9uRXJyb3IgKz0ga2V5VmFsdWU7XG5cdFx0XHRcdFx0XHR9IGVsc2Uge1xuXHRcdFx0XHRcdFx0XHRpZiAoa2V5VG9rZW4gPT09IFwidHJpZ2dlclwiKSB7XG5cdFx0XHRcdFx0XHRcdFx0dXNlVHJpZ2dlciArPSBrZXlWYWx1ZTtcblx0XHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdFx0XHRpZiAoa2V5VG9rZW4gPT09IFwibGF0ZVJlbmRlclwiKSB7XG5cdFx0XHRcdFx0XHRcdFx0bGF0ZSA9IHBhcmFtOyAvLyBSZW5kZXIgYWZ0ZXIgZmlyc3QgcGFzc1xuXHRcdFx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0XHRcdHByb3BzICs9IGtleSArIGtleVZhbHVlICsgXCIsXCI7XG5cdFx0XHRcdFx0XHRcdHBhcmFtc1Byb3BzICs9IGtleSArIFwiJ1wiICsgcGFyYW0gKyBcIicsXCI7XG5cdFx0XHRcdFx0XHRcdGhhc0hhbmRsZXJzID0gaGFzSGFuZGxlcnMgfHwgckhhc0hhbmRsZXJzLnRlc3Qoa2V5VG9rZW4pO1xuXHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdFx0cmV0dXJuIFwiXCI7XG5cdFx0XHRcdFx0fSkuc2xpY2UoMCwgLTEpO1xuXHRcdFx0fVxuXG5cdFx0XHRpZiAocGF0aEJpbmRpbmdzICYmIHBhdGhCaW5kaW5nc1swXSkge1xuXHRcdFx0XHRwYXRoQmluZGluZ3MucG9wKCk7IC8vIFJlbW92ZSB0aGUgYmluZGluZyB0aGF0IHdhcyBwcmVwYXJlZCBmb3IgbmV4dCBhcmcuIChUaGVyZSBpcyBhbHdheXMgYW4gZXh0cmEgb25lIHJlYWR5KS5cblx0XHRcdH1cblxuXHRcdFx0bmV3Tm9kZSA9IFtcblx0XHRcdFx0XHR0YWdOYW1lLFxuXHRcdFx0XHRcdGNvbnZlcnRlciB8fCAhIWNvbnZlcnRCYWNrIHx8IGhhc0hhbmRsZXJzIHx8IFwiXCIsXG5cdFx0XHRcdFx0YmxvY2sgJiYgW10sXG5cdFx0XHRcdFx0cGFyc2VkUGFyYW0ocGFyYW1zQXJncyB8fCAodGFnTmFtZSA9PT0gXCI6XCIgPyBcIicjZGF0YScsXCIgOiBcIlwiKSwgcGFyYW1zUHJvcHMsIHBhcmFtc0N0eFByb3BzKSwgLy8ge3s6fX0gZXF1aXZhbGVudCB0byB7ezojZGF0YX19XG5cdFx0XHRcdFx0cGFyc2VkUGFyYW0oYXJncyB8fCAodGFnTmFtZSA9PT0gXCI6XCIgPyBcImRhdGEsXCIgOiBcIlwiKSwgcHJvcHMsIGN0eFByb3BzKSxcblx0XHRcdFx0XHRvbkVycm9yLFxuXHRcdFx0XHRcdHVzZVRyaWdnZXIsXG5cdFx0XHRcdFx0bGF0ZSxcblx0XHRcdFx0XHRwYXRoQmluZGluZ3MgfHwgMFxuXHRcdFx0XHRdO1xuXHRcdFx0Y29udGVudC5wdXNoKG5ld05vZGUpO1xuXHRcdFx0aWYgKGJsb2NrKSB7XG5cdFx0XHRcdHN0YWNrLnB1c2goY3VycmVudCk7XG5cdFx0XHRcdGN1cnJlbnQgPSBuZXdOb2RlO1xuXHRcdFx0XHRjdXJyZW50WzldID0gbG9jOyAvLyBTdG9yZSBjdXJyZW50IGxvY2F0aW9uIG9mIG9wZW4gdGFnLCB0byBiZSBhYmxlIHRvIGFkZCBjb250ZW50TWFya3VwIHdoZW4gd2UgcmVhY2ggY2xvc2luZyB0YWdcblx0XHRcdH1cblx0XHR9IGVsc2UgaWYgKGNsb3NlQmxvY2spIHtcblx0XHRcdGJsb2NrVGFnQ2hlY2soY2xvc2VCbG9jayAhPT0gY3VycmVudFswXSAmJiBjdXJyZW50WzBdICE9PSBcImVsc2VcIiAmJiBjbG9zZUJsb2NrLCBjdXJyZW50WzBdKTtcblx0XHRcdGN1cnJlbnRbOV0gPSBtYXJrdXAuc3Vic3RyaW5nKGN1cnJlbnRbOV0sIGluZGV4KTsgLy8gY29udGVudE1hcmt1cCBmb3IgYmxvY2sgdGFnXG5cdFx0XHRjdXJyZW50ID0gc3RhY2sucG9wKCk7XG5cdFx0fVxuXHRcdGJsb2NrVGFnQ2hlY2soIWN1cnJlbnQgJiYgY2xvc2VCbG9jayk7XG5cdFx0Y29udGVudCA9IGN1cnJlbnRbMl07XG5cdH1cblx0Ly89PT09IC9lbmQgb2YgbmVzdGVkIGZ1bmN0aW9ucyA9PT09XG5cblx0dmFyIGksIHJlc3VsdCwgbmV3Tm9kZSwgaGFzSGFuZGxlcnMsIGJpbmRpbmdzLFxuXHRcdGFsbG93Q29kZSA9ICRzdWJTZXR0aW5ncy5hbGxvd0NvZGUgfHwgdG1wbCAmJiB0bXBsLmFsbG93Q29kZVxuXHRcdFx0fHwgJHZpZXdzU2V0dGluZ3MuYWxsb3dDb2RlID09PSB0cnVlLCAvLyBpbmNsdWRlIGRpcmVjdCBzZXR0aW5nIG9mIHNldHRpbmdzLmFsbG93Q29kZSB0cnVlIGZvciBiYWNrd2FyZCBjb21wYXQgb25seVxuXHRcdGFzdFRvcCA9IFtdLFxuXHRcdGxvYyA9IDAsXG5cdFx0c3RhY2sgPSBbXSxcblx0XHRjb250ZW50ID0gYXN0VG9wLFxuXHRcdGN1cnJlbnQgPSBbLCxhc3RUb3BdO1xuXG5cdGlmIChhbGxvd0NvZGUgJiYgdG1wbC5faXMpIHtcblx0XHR0bXBsLmFsbG93Q29kZSA9IGFsbG93Q29kZTtcblx0fVxuXG4vL1RPRE9cdHJlc3VsdCA9IHRtcGxGbnNDYWNoZVttYXJrdXBdOyAvLyBPbmx5IGNhY2hlIGlmIHRlbXBsYXRlIGlzIG5vdCBuYW1lZCBhbmQgbWFya3VwIGxlbmd0aCA8IC4uLixcbi8vYW5kIHRoZXJlIGFyZSBubyBiaW5kaW5ncyBvciBzdWJ0ZW1wbGF0ZXM/PyBDb25zaWRlciBzdGFuZGFyZCBvcHRpbWl6YXRpb24gZm9yIGRhdGEtbGluaz1cImEuYi5jXCJcbi8vXHRcdGlmIChyZXN1bHQpIHtcbi8vXHRcdFx0dG1wbC5mbiA9IHJlc3VsdDtcbi8vXHRcdH0gZWxzZSB7XG5cbi8vXHRcdHJlc3VsdCA9IG1hcmt1cDtcblx0aWYgKGlzTGlua0V4cHIpIHtcblx0XHRpZiAoY29udmVydEJhY2sgIT09IHVuZGVmaW5lZCkge1xuXHRcdFx0bWFya3VwID0gbWFya3VwLnNsaWNlKDAsIC1jb252ZXJ0QmFjay5sZW5ndGggLSAyKSArIGRlbGltQ2xvc2VDaGFyMDtcblx0XHR9XG5cdFx0bWFya3VwID0gZGVsaW1PcGVuQ2hhcjAgKyBtYXJrdXAgKyBkZWxpbUNsb3NlQ2hhcjE7XG5cdH1cblxuXHRibG9ja1RhZ0NoZWNrKHN0YWNrWzBdICYmIHN0YWNrWzBdWzJdLnBvcCgpWzBdKTtcblx0Ly8gQnVpbGQgdGhlIEFTVCAoYWJzdHJhY3Qgc3ludGF4IHRyZWUpIHVuZGVyIGFzdFRvcFxuXHRtYXJrdXAucmVwbGFjZShyVGFnLCBwYXJzZVRhZyk7XG5cblx0cHVzaHByZWNlZGluZ0NvbnRlbnQobWFya3VwLmxlbmd0aCk7XG5cblx0aWYgKGxvYyA9IGFzdFRvcFthc3RUb3AubGVuZ3RoIC0gMV0pIHtcblx0XHRibG9ja1RhZ0NoZWNrKFwiXCIgKyBsb2MgIT09IGxvYyAmJiAoK2xvY1s5XSA9PT0gbG9jWzldKSAmJiBsb2NbMF0pO1xuXHR9XG4vL1x0XHRcdHJlc3VsdCA9IHRtcGxGbnNDYWNoZVttYXJrdXBdID0gYnVpbGRDb2RlKGFzdFRvcCwgdG1wbCk7XG4vL1x0XHR9XG5cblx0aWYgKGlzTGlua0V4cHIpIHtcblx0XHRyZXN1bHQgPSBidWlsZENvZGUoYXN0VG9wLCBtYXJrdXAsIGlzTGlua0V4cHIpO1xuXHRcdGJpbmRpbmdzID0gW107XG5cdFx0aSA9IGFzdFRvcC5sZW5ndGg7XG5cdFx0d2hpbGUgKGktLSkge1xuXHRcdFx0YmluZGluZ3MudW5zaGlmdChhc3RUb3BbaV1bOF0pOyAvLyBXaXRoIGRhdGEtbGluayBleHByZXNzaW9ucywgcGF0aEJpbmRpbmdzIGFycmF5IGZvciB0YWdDdHhbaV0gaXMgYXN0VG9wW2ldWzhdXG5cdFx0fVxuXHRcdHNldFBhdGhzKHJlc3VsdCwgYmluZGluZ3MpO1xuXHR9IGVsc2Uge1xuXHRcdHJlc3VsdCA9IGJ1aWxkQ29kZShhc3RUb3AsIHRtcGwpO1xuXHR9XG5cdHJldHVybiByZXN1bHQ7XG59XG5cbmZ1bmN0aW9uIHNldFBhdGhzKGZuLCBwYXRoc0Fycikge1xuXHR2YXIga2V5LCBwYXRocyxcblx0XHRpID0gMCxcblx0XHRsID0gcGF0aHNBcnIubGVuZ3RoO1xuXHRmbi5kZXBzID0gW107XG5cdGZuLnBhdGhzID0gW107IC8vIFRoZSBhcnJheSBvZiBwYXRoIGJpbmRpbmcgKGFycmF5L2RpY3Rpb25hcnkpcyBmb3IgZWFjaCB0YWcvZWxzZSBibG9jaydzIGFyZ3MgYW5kIHByb3BzXG5cdGZvciAoOyBpIDwgbDsgaSsrKSB7XG5cdFx0Zm4ucGF0aHMucHVzaChwYXRocyA9IHBhdGhzQXJyW2ldKTtcblx0XHRmb3IgKGtleSBpbiBwYXRocykge1xuXHRcdFx0aWYgKGtleSAhPT0gXCJfanN2dG9cIiAmJiBwYXRocy5oYXNPd25Qcm9wZXJ0eShrZXkpICYmIHBhdGhzW2tleV0ubGVuZ3RoICYmICFwYXRoc1trZXldLnNrcCkge1xuXHRcdFx0XHRmbi5kZXBzID0gZm4uZGVwcy5jb25jYXQocGF0aHNba2V5XSk7IC8vIGRlcHMgaXMgdGhlIGNvbmNhdGVuYXRpb24gb2YgdGhlIHBhdGhzIGFycmF5cyBmb3IgdGhlIGRpZmZlcmVudCBiaW5kaW5nc1xuXHRcdFx0fVxuXHRcdH1cblx0fVxufVxuXG5mdW5jdGlvbiBwYXJzZWRQYXJhbShhcmdzLCBwcm9wcywgY3R4KSB7XG5cdHJldHVybiBbYXJncy5zbGljZSgwLCAtMSksIHByb3BzLnNsaWNlKDAsIC0xKSwgY3R4LnNsaWNlKDAsIC0xKV07XG59XG5cbmZ1bmN0aW9uIHBhcmFtU3RydWN0dXJlKHBhcnRzLCB0eXBlKSB7XG5cdHJldHVybiAnXFxuXFx0J1xuXHRcdCsgKHR5cGVcblx0XHRcdD8gdHlwZSArICc6eydcblx0XHRcdDogJycpXG5cdFx0KyAnYXJnczpbJyArIHBhcnRzWzBdICsgJ10nXG5cdFx0KyAocGFydHNbMV0gfHwgIXR5cGVcblx0XHRcdD8gJyxcXG5cXHRwcm9wczp7JyArIHBhcnRzWzFdICsgJ30nXG5cdFx0XHQ6IFwiXCIpXG5cdFx0KyAocGFydHNbMl0gPyAnLFxcblxcdGN0eDp7JyArIHBhcnRzWzJdICsgJ30nIDogXCJcIik7XG59XG5cbmZ1bmN0aW9uIHBhcnNlUGFyYW1zKHBhcmFtcywgcGF0aEJpbmRpbmdzLCB0bXBsKSB7XG5cblx0ZnVuY3Rpb24gcGFyc2VUb2tlbnMoYWxsLCBsZnRQcm4wLCBsZnRQcm4sIGJvdW5kLCBwYXRoLCBvcGVyYXRvciwgZXJyLCBlcSwgcGF0aDIsIHBybiwgY29tbWEsIGxmdFBybjIsIGFwb3MsIHF1b3QsIHJ0UHJuLCBydFBybkRvdCwgcHJuMiwgc3BhY2UsIGluZGV4LCBmdWxsKSB7XG5cdC8vIC8oXFwoKSg/PVxccypcXCgpfCg/OihbKFtdKVxccyopPyg/OihcXF4/KSghKj9bI35dP1tcXHckLl5dKyk/XFxzKigoXFwrXFwrfC0tKXxcXCt8LXwmJnxcXHxcXHx8PT09fCE9PXw9PXwhPXw8PXw+PXxbPD4lKjo/XFwvXXwoPSkpXFxzKnwoISo/WyN+XT9bXFx3JC5eXSspKFsoW10pPyl8KCxcXHMqKXwoXFwoPylcXFxcPyg/OignKXwoXCIpKXwoPzpcXHMqKChbKVxcXV0pKD89XFxzKlsuXl18XFxzKiR8W14oW10pfFspXFxdXSkoWyhbXT8pKXwoXFxzKykvZyxcblx0Ly8gICBsZnRQcm4wICAgICAgICBsZnRQcm4gICAgICAgIGJvdW5kICAgICAgICAgICAgcGF0aCAgICBvcGVyYXRvciBlcnIgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlcSAgICAgICAgICAgICBwYXRoMiAgICAgICBwcm4gICAgY29tbWEgICBsZnRQcm4yICAgYXBvcyBxdW90ICAgICAgcnRQcm4gcnRQcm5Eb3QgICAgICAgICAgICAgICAgICAgICAgICBwcm4yICBzcGFjZVxuXHRcdC8vIChsZWZ0IHBhcmVuPyBmb2xsb3dlZCBieSAocGF0aD8gZm9sbG93ZWQgYnkgb3BlcmF0b3IpIG9yIChwYXRoIGZvbGxvd2VkIGJ5IHBhcmVuPykpIG9yIGNvbW1hIG9yIGFwb3Mgb3IgcXVvdCBvciByaWdodCBwYXJlbiBvciBzcGFjZVxuXHRcdGZ1bmN0aW9uIHBhcnNlUGF0aChhbGxQYXRoLCBub3QsIG9iamVjdCwgaGVscGVyLCB2aWV3LCB2aWV3UHJvcGVydHksIHBhdGhUb2tlbnMsIGxlYWZUb2tlbikge1xuXHRcdFx0Ly9yUGF0aCA9IC9eKCEqPykoPzpudWxsfHRydWV8ZmFsc2V8XFxkW1xcZC5dKnwoW1xcdyRdK3xcXC58fihbXFx3JF0rKXwjKHZpZXd8KFtcXHckXSspKT8pKFtcXHckLl5dKj8pKD86Wy5bXl0oW1xcdyRdKylcXF0/KT8pJC9nLFxuXHRcdFx0Ly8gICAgICAgICAgbm90ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9iamVjdCAgICAgaGVscGVyICAgIHZpZXcgIHZpZXdQcm9wZXJ0eSBwYXRoVG9rZW5zICAgICAgbGVhZlRva2VuXG5cdFx0XHR2YXIgc3ViUGF0aCA9IG9iamVjdCA9PT0gXCIuXCI7XG5cdFx0XHRpZiAob2JqZWN0KSB7XG5cdFx0XHRcdHBhdGggPSBwYXRoLnNsaWNlKG5vdC5sZW5ndGgpO1xuXHRcdFx0XHRpZiAoL15cXC4/Y29uc3RydWN0b3IkLy50ZXN0KGxlYWZUb2tlbnx8cGF0aCkpIHtcblx0XHRcdFx0XHRzeW50YXhFcnJvcihhbGxQYXRoKTtcblx0XHRcdFx0fVxuXHRcdFx0XHRpZiAoIXN1YlBhdGgpIHtcblx0XHRcdFx0XHRhbGxQYXRoID0gKGhlbHBlclxuXHRcdFx0XHRcdFx0XHQ/ICd2aWV3LmN0eFBybShcIicgKyBoZWxwZXIgKyAnXCIpJ1xuXHRcdFx0XHRcdFx0XHQ6IHZpZXdcblx0XHRcdFx0XHRcdFx0XHQ/IFwidmlld1wiXG5cdFx0XHRcdFx0XHRcdFx0OiBcImRhdGFcIilcblx0XHRcdFx0XHRcdCsgKGxlYWZUb2tlblxuXHRcdFx0XHRcdFx0XHQ/ICh2aWV3UHJvcGVydHlcblx0XHRcdFx0XHRcdFx0XHQ/IFwiLlwiICsgdmlld1Byb3BlcnR5XG5cdFx0XHRcdFx0XHRcdFx0OiBoZWxwZXJcblx0XHRcdFx0XHRcdFx0XHRcdD8gXCJcIlxuXHRcdFx0XHRcdFx0XHRcdFx0OiAodmlldyA/IFwiXCIgOiBcIi5cIiArIG9iamVjdClcblx0XHRcdFx0XHRcdFx0XHQpICsgKHBhdGhUb2tlbnMgfHwgXCJcIilcblx0XHRcdFx0XHRcdFx0OiAobGVhZlRva2VuID0gaGVscGVyID8gXCJcIiA6IHZpZXcgPyB2aWV3UHJvcGVydHkgfHwgXCJcIiA6IG9iamVjdCwgXCJcIikpO1xuXG5cdFx0XHRcdFx0YWxsUGF0aCA9IGFsbFBhdGggKyAobGVhZlRva2VuID8gXCIuXCIgKyBsZWFmVG9rZW4gOiBcIlwiKTtcblxuXHRcdFx0XHRcdGFsbFBhdGggPSBub3QgKyAoYWxsUGF0aC5zbGljZSgwLCA5KSA9PT0gXCJ2aWV3LmRhdGFcIlxuXHRcdFx0XHRcdFx0PyBhbGxQYXRoLnNsaWNlKDUpIC8vIGNvbnZlcnQgI3ZpZXcuZGF0YS4uLiB0byBkYXRhLi4uXG5cdFx0XHRcdFx0XHQ6IGFsbFBhdGgpO1xuXHRcdFx0XHR9XG5cdFx0XHRcdGlmIChiaW5kaW5ncykge1xuXHRcdFx0XHRcdGJpbmRzID0gbmFtZWQgPT09IFwibGlua1RvXCIgPyAoYmluZHRvID0gcGF0aEJpbmRpbmdzLl9qc3Z0byA9IHBhdGhCaW5kaW5ncy5fanN2dG8gfHwgW10pIDogYm5kQ3R4LmJkO1xuXHRcdFx0XHRcdGlmICh0aGVPYiA9IHN1YlBhdGggJiYgYmluZHNbYmluZHMubGVuZ3RoLTFdKSB7XG5cdFx0XHRcdFx0XHRpZiAodGhlT2IuX2NwZm4pIHsgLy8gQ29tcHV0ZWQgcHJvcGVydHkgZXhwck9iXG5cdFx0XHRcdFx0XHRcdHdoaWxlICh0aGVPYi5zYikge1xuXHRcdFx0XHRcdFx0XHRcdHRoZU9iID0gdGhlT2Iuc2I7XG5cdFx0XHRcdFx0XHRcdH1cblx0XHRcdFx0XHRcdFx0aWYgKHRoZU9iLmJuZCkge1xuXHRcdFx0XHRcdFx0XHRcdHBhdGggPSBcIl5cIiArIHBhdGguc2xpY2UoMSk7XG5cdFx0XHRcdFx0XHRcdH1cblx0XHRcdFx0XHRcdFx0dGhlT2Iuc2IgPSBwYXRoO1xuXHRcdFx0XHRcdFx0XHR0aGVPYi5ibmQgPSB0aGVPYi5ibmQgfHwgcGF0aC5jaGFyQXQoMCkgPT09IFwiXlwiO1xuXHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdH0gZWxzZSB7XG5cdFx0XHRcdFx0XHRiaW5kcy5wdXNoKHBhdGgpO1xuXHRcdFx0XHRcdH1cblx0XHRcdFx0XHRwYXRoU3RhcnRbcGFyZW5EZXB0aF0gPSBpbmRleCArIChzdWJQYXRoID8gMSA6IDApO1xuXHRcdFx0XHR9XG5cdFx0XHR9XG5cdFx0XHRyZXR1cm4gYWxsUGF0aDtcblx0XHR9XG5cblx0XHQvL2JvdW5kID0gYmluZGluZ3MgJiYgYm91bmQ7XG5cdFx0aWYgKGJvdW5kICYmICFlcSkge1xuXHRcdFx0cGF0aCA9IGJvdW5kICsgcGF0aDsgLy8gZS5nLiBzb21lLmZuKC4uLilec29tZS5wYXRoIC0gc28gaGVyZSBwYXRoIGlzIFwiXnNvbWUucGF0aFwiXG5cdFx0fVxuXHRcdG9wZXJhdG9yID0gb3BlcmF0b3IgfHwgXCJcIjtcblx0XHRsZnRQcm4gPSBsZnRQcm4gfHwgbGZ0UHJuMCB8fCBsZnRQcm4yO1xuXHRcdHBhdGggPSBwYXRoIHx8IHBhdGgyO1xuXHRcdC8vIENvdWxkIGRvIHRoaXMgLSBidXQgbm90IHdvcnRoIHBlcmYgY29zdD8/IDotXG5cdFx0Ly8gaWYgKCFwYXRoLmxhc3RJbmRleE9mKFwiI2RhdGEuXCIsIDApKSB7IHBhdGggPSBwYXRoLnNsaWNlKDYpOyB9IC8vIElmIHBhdGggc3RhcnRzIHdpdGggXCIjZGF0YS5cIiwgcmVtb3ZlIHRoYXQuXG5cdFx0cHJuID0gcHJuIHx8IHBybjIgfHwgXCJcIjtcblxuXHRcdHZhciBleHByLCBleHByRm4sIGJpbmRzLCB0aGVPYiwgbmV3T2IsXG5cdFx0XHRydFNxID0gXCIpXCI7XG5cblx0XHRpZiAocHJuID09PSBcIltcIikge1xuXHRcdFx0cHJuID1cIltqLl9zcShcIjtcblx0XHRcdHJ0U3EgPSBcIildXCI7XG5cdFx0fVxuXG5cdFx0aWYgKGVyciAmJiAhYXBvc2VkICYmICFxdW90ZWQpIHtcblx0XHRcdHN5bnRheEVycm9yKHBhcmFtcyk7XG5cdFx0fSBlbHNlIHtcblx0XHRcdGlmIChiaW5kaW5ncyAmJiBydFBybkRvdCAmJiAhYXBvc2VkICYmICFxdW90ZWQpIHtcblx0XHRcdFx0Ly8gVGhpcyBpcyBhIGJpbmRpbmcgdG8gYSBwYXRoIGluIHdoaWNoIGFuIG9iamVjdCBpcyByZXR1cm5lZCBieSBhIGhlbHBlci9kYXRhIGZ1bmN0aW9uL2V4cHJlc3Npb24sIGUuZy4gZm9vKCleeC55IG9yIChhP2I6YyleeC55XG5cdFx0XHRcdC8vIFdlIGNyZWF0ZSBhIGNvbXBpbGVkIGZ1bmN0aW9uIHRvIGdldCB0aGUgb2JqZWN0IGluc3RhbmNlICh3aGljaCB3aWxsIGJlIGNhbGxlZCB3aGVuIHRoZSBkZXBlbmRlbnQgZGF0YSBvZiB0aGUgc3ViZXhwcmVzc2lvbiBjaGFuZ2VzLCB0byByZXR1cm4gdGhlIG5ldyBvYmplY3QsIGFuZCB0cmlnZ2VyIHJlLWJpbmRpbmcgb2YgdGhlIHN1YnNlcXVlbnQgcGF0aClcblx0XHRcdFx0aWYgKCFuYW1lZCB8fCBib3VuZE5hbWUgfHwgYmluZHRvKSB7XG5cdFx0XHRcdFx0ZXhwciA9IHBhdGhTdGFydFtwYXJlbkRlcHRoIC0gMV07XG5cdFx0XHRcdFx0aWYgKGZ1bGwubGVuZ3RoIC0gMSA+IGluZGV4IC0gKGV4cHIgfHwgMCkpIHsgLy8gV2UgbmVlZCB0byBjb21waWxlIGEgc3ViZXhwcmVzc2lvblxuXHRcdFx0XHRcdFx0ZXhwciA9IGZ1bGwuc2xpY2UoZXhwciwgaW5kZXggKyBhbGwubGVuZ3RoKTtcblx0XHRcdFx0XHRcdGlmIChleHByRm4gIT09IHRydWUpIHsgLy8gSWYgbm90IHJlZW50cmFudCBjYWxsIGR1cmluZyBjb21waWxhdGlvblxuXHRcdFx0XHRcdFx0XHRiaW5kcyA9IGJpbmR0byB8fCBibmRTdGFja1twYXJlbkRlcHRoLTFdLmJkO1xuXHRcdFx0XHRcdFx0XHQvLyBJbnNlcnQgZXhwck9iIG9iamVjdCwgdG8gYmUgdXNlZCBkdXJpbmcgYmluZGluZyB0byByZXR1cm4gdGhlIGNvbXB1dGVkIG9iamVjdFxuXHRcdFx0XHRcdFx0XHR0aGVPYiA9IGJpbmRzW2JpbmRzLmxlbmd0aC0xXTtcblx0XHRcdFx0XHRcdFx0aWYgKHRoZU9iICYmIHRoZU9iLnBybSkge1xuXHRcdFx0XHRcdFx0XHRcdHdoaWxlICh0aGVPYi5zYiAmJiB0aGVPYi5zYi5wcm0pIHtcblx0XHRcdFx0XHRcdFx0XHRcdHRoZU9iID0gdGhlT2Iuc2I7XG5cdFx0XHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdFx0XHRcdG5ld09iID0gdGhlT2Iuc2IgPSB7cGF0aDogdGhlT2Iuc2IsIGJuZDogdGhlT2IuYm5kfTtcblx0XHRcdFx0XHRcdFx0fSBlbHNlIHtcblx0XHRcdFx0XHRcdFx0XHRiaW5kcy5wdXNoKG5ld09iID0ge3BhdGg6IGJpbmRzLnBvcCgpfSk7IC8vIEluc2VydCBleHByT2Igb2JqZWN0LCB0byBiZSB1c2VkIGR1cmluZyBiaW5kaW5nIHRvIHJldHVybiB0aGUgY29tcHV0ZWQgb2JqZWN0XG5cdFx0XHRcdFx0XHRcdH1cdFx0XHRcdFx0XHRcdFx0XHRcdFx0IC8vIChlLmcuIFwic29tZS5vYmplY3QoKVwiIGluIFwic29tZS5vYmplY3QoKS5hLmJcIiAtIHRvIGJlIHVzZWQgYXMgY29udGV4dCBmb3IgYmluZGluZyB0aGUgZm9sbG93aW5nIHRva2VucyBcImEuYlwiKVxuXHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdFx0cnRQcm5Eb3QgPSBkZWxpbU9wZW5DaGFyMSArIFwiOlwiICsgZXhwciAvLyBUaGUgcGFyYW1ldGVyIG9yIGZ1bmN0aW9uIHN1YmV4cHJlc3Npb25cblx0XHRcdFx0XHRcdFx0KyBcIiBvbmVycm9yPScnXCIgLy8gc2V0IG9uZXJyb3I9JycgaW4gb3JkZXIgdG8gd3JhcCBnZW5lcmF0ZWQgY29kZSB3aXRoIGEgdHJ5IGNhdGNoIC0gcmV0dXJuaW5nICcnIGFzIG9iamVjdCBpbnN0YW5jZSBpZiB0aGVyZSBpcyBhbiBlcnJvci9taXNzaW5nIHBhcmVudFxuXHRcdFx0XHRcdFx0XHQrIGRlbGltQ2xvc2VDaGFyMDtcblx0XHRcdFx0XHRcdGV4cHJGbiA9IHRtcGxMaW5rc1tydFBybkRvdF07XG5cdFx0XHRcdFx0XHRpZiAoIWV4cHJGbikge1xuXHRcdFx0XHRcdFx0XHR0bXBsTGlua3NbcnRQcm5Eb3RdID0gdHJ1ZTsgLy8gRmxhZyB0aGF0IHRoaXMgZXhwckZuIChmb3IgcnRQcm5Eb3QpIGlzIGJlaW5nIGNvbXBpbGVkXG5cdFx0XHRcdFx0XHRcdHRtcGxMaW5rc1tydFBybkRvdF0gPSBleHByRm4gPSB0bXBsRm4ocnRQcm5Eb3QsIHRtcGwsIHRydWUpOyAvLyBDb21waWxlIHRoZSBleHByZXNzaW9uIChvciB1c2UgY2FjaGVkIGNvcHkgYWxyZWFkeSBpbiB0bXBsLmxpbmtzKVxuXHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdFx0aWYgKGV4cHJGbiAhPT0gdHJ1ZSAmJiBuZXdPYikge1xuXHRcdFx0XHRcdFx0XHQvLyBJZiBub3QgcmVlbnRyYW50IGNhbGwgZHVyaW5nIGNvbXBpbGF0aW9uXG5cdFx0XHRcdFx0XHRcdG5ld09iLl9jcGZuID0gZXhwckZuO1xuXHRcdFx0XHRcdFx0XHRuZXdPYi5wcm0gPSBibmRDdHguYmQ7XG5cdFx0XHRcdFx0XHRcdG5ld09iLmJuZCA9IG5ld09iLmJuZCB8fCBuZXdPYi5wYXRoICYmIG5ld09iLnBhdGguaW5kZXhPZihcIl5cIikgPj0gMDtcblx0XHRcdFx0XHRcdH1cblx0XHRcdFx0XHR9XG5cdFx0XHRcdH1cblx0XHRcdH1cblx0XHRcdHJldHVybiAoYXBvc2VkXG5cdFx0XHRcdC8vIHdpdGhpbiBzaW5nbGUtcXVvdGVkIHN0cmluZ1xuXHRcdFx0XHQ/IChhcG9zZWQgPSAhYXBvcywgKGFwb3NlZCA/IGFsbCA6IGxmdFBybjIgKyAnXCInKSlcblx0XHRcdFx0OiBxdW90ZWRcblx0XHRcdFx0Ly8gd2l0aGluIGRvdWJsZS1xdW90ZWQgc3RyaW5nXG5cdFx0XHRcdFx0PyAocXVvdGVkID0gIXF1b3QsIChxdW90ZWQgPyBhbGwgOiBsZnRQcm4yICsgJ1wiJykpXG5cdFx0XHRcdFx0OlxuXHRcdFx0XHQoXG5cdFx0XHRcdFx0KGxmdFByblxuXHRcdFx0XHRcdFx0PyAocGF0aFN0YXJ0W3BhcmVuRGVwdGhdID0gaW5kZXgrKywgYm5kQ3R4ID0gYm5kU3RhY2tbKytwYXJlbkRlcHRoXSA9IHtiZDogW119LCBsZnRQcm4pXG5cdFx0XHRcdFx0XHQ6IFwiXCIpXG5cdFx0XHRcdFx0KyAoc3BhY2Vcblx0XHRcdFx0XHRcdD8gKHBhcmVuRGVwdGhcblx0XHRcdFx0XHRcdFx0PyBcIlwiXG5cdFx0XHRcdC8vIE5ldyBhcmcgb3IgcHJvcCAtIHNvIGluc2VydCBiYWNrc3BhY2UgXFxiIChcXHgwOCkgYXMgc2VwYXJhdG9yIGZvciBuYW1lZCBwYXJhbXMsIHVzZWQgc3Vic2VxdWVudGx5IGJ5IHJCdWlsZEhhc2gsIGFuZCBwcmVwYXJlIG5ldyBiaW5kaW5ncyBhcnJheVxuXHRcdFx0XHRcdFx0XHQ6IChwYXJhbUluZGV4ID0gZnVsbC5zbGljZShwYXJhbUluZGV4LCBpbmRleCksIG5hbWVkXG5cdFx0XHRcdFx0XHRcdFx0PyAobmFtZWQgPSBib3VuZE5hbWUgPSBiaW5kdG8gPSBmYWxzZSwgXCJcXGJcIilcblx0XHRcdFx0XHRcdFx0XHQ6IFwiXFxiLFwiKSArIHBhcmFtSW5kZXggKyAocGFyYW1JbmRleCA9IGluZGV4ICsgYWxsLmxlbmd0aCwgYmluZGluZ3MgJiYgcGF0aEJpbmRpbmdzLnB1c2goYm5kQ3R4LmJkID0gW10pLCBcIlxcYlwiKVxuXHRcdFx0XHRcdFx0KVxuXHRcdFx0XHRcdFx0OiBlcVxuXHRcdFx0XHQvLyBuYW1lZCBwYXJhbS4gUmVtb3ZlIGJpbmRpbmdzIGZvciBhcmcgYW5kIGNyZWF0ZSBpbnN0ZWFkIGJpbmRpbmdzIGFycmF5IGZvciBwcm9wXG5cdFx0XHRcdFx0XHRcdD8gKHBhcmVuRGVwdGggJiYgc3ludGF4RXJyb3IocGFyYW1zKSwgYmluZGluZ3MgJiYgcGF0aEJpbmRpbmdzLnBvcCgpLCBuYW1lZCA9IHBhdGgsIGJvdW5kTmFtZSA9IGJvdW5kLCBwYXJhbUluZGV4ID0gaW5kZXggKyBhbGwubGVuZ3RoLFxuXHRcdFx0XHRcdFx0XHRcdFx0YmluZGluZ3MgJiYgKChiaW5kaW5ncyA9IGJuZEN0eC5iZCA9IHBhdGhCaW5kaW5nc1tuYW1lZF0gPSBbXSksIGJpbmRpbmdzLnNrcCA9ICFib3VuZCksIHBhdGggKyAnOicpXG5cdFx0XHRcdFx0XHRcdDogcGF0aFxuXHRcdFx0XHQvLyBwYXRoXG5cdFx0XHRcdFx0XHRcdFx0PyAocGF0aC5zcGxpdChcIl5cIikuam9pbihcIi5cIikucmVwbGFjZShyUGF0aCwgcGFyc2VQYXRoKVxuXHRcdFx0XHRcdFx0XHRcdFx0KyAocHJuXG5cdFx0XHRcdC8vIHNvbWUuZm5jYWxsKFxuXHRcdFx0XHRcdFx0XHRcdFx0XHQ/IChibmRDdHggPSBibmRTdGFja1srK3BhcmVuRGVwdGhdID0ge2JkOiBbXX0sIGZuQ2FsbFtwYXJlbkRlcHRoXSA9IHJ0U3EsIHBybilcblx0XHRcdFx0XHRcdFx0XHRcdFx0OiBvcGVyYXRvcilcblx0XHRcdFx0XHRcdFx0XHQpXG5cdFx0XHRcdFx0XHRcdFx0OiBvcGVyYXRvclxuXHRcdFx0XHQvLyBvcGVyYXRvclxuXHRcdFx0XHRcdFx0XHRcdFx0PyBvcGVyYXRvclxuXHRcdFx0XHRcdFx0XHRcdFx0OiBydFByblxuXHRcdFx0XHQvLyBmdW5jdGlvblxuXHRcdFx0XHRcdFx0XHRcdFx0XHQ/ICgocnRQcm4gPSBmbkNhbGxbcGFyZW5EZXB0aF0gfHwgcnRQcm4sIGZuQ2FsbFtwYXJlbkRlcHRoXSA9IGZhbHNlLCBibmRDdHggPSBibmRTdGFja1stLXBhcmVuRGVwdGhdLCBydFBybilcblx0XHRcdFx0XHRcdFx0XHRcdFx0XHQrIChwcm4gLy8gcnRQcm4gYW5kIHBybiwgZS5nICkoIGluIChhKSgpIG9yIGEoKSgpLCBvciApWyBpbiBhKClbXVxuXHRcdFx0XHRcdFx0XHRcdFx0XHRcdFx0PyAoYm5kQ3R4ID0gYm5kU3RhY2tbKytwYXJlbkRlcHRoXSwgZm5DYWxsW3BhcmVuRGVwdGhdID0gcnRTcSwgcHJuKVxuXHRcdFx0XHRcdFx0XHRcdFx0XHRcdFx0OiBcIlwiKVxuXHRcdFx0XHRcdFx0XHRcdFx0XHQpXG5cdFx0XHRcdFx0XHRcdFx0XHRcdDogY29tbWFcblx0XHRcdFx0XHRcdFx0XHRcdFx0XHQ/IChmbkNhbGxbcGFyZW5EZXB0aF0gfHwgc3ludGF4RXJyb3IocGFyYW1zKSwgXCIsXCIpIC8vIFdlIGRvbid0IGFsbG93IHRvcC1sZXZlbCBsaXRlcmFsIGFycmF5cyBvciBvYmplY3RzXG5cdFx0XHRcdFx0XHRcdFx0XHRcdFx0OiBsZnRQcm4wXG5cdFx0XHRcdFx0XHRcdFx0XHRcdFx0XHQ/IFwiXCJcblx0XHRcdFx0XHRcdFx0XHRcdFx0XHRcdDogKGFwb3NlZCA9IGFwb3MsIHF1b3RlZCA9IHF1b3QsICdcIicpXG5cdFx0XHRcdCkpXG5cdFx0XHQpO1xuXHRcdH1cblx0fVxuXG5cdHZhciBuYW1lZCwgYmluZHRvLCBib3VuZE5hbWUsXG5cdFx0cXVvdGVkLCAvLyBib29sZWFuIGZvciBzdHJpbmcgY29udGVudCBpbiBkb3VibGUgcXVvdGVzXG5cdFx0YXBvc2VkLCAvLyBvciBpbiBzaW5nbGUgcXVvdGVzXG5cdFx0YmluZGluZ3MgPSBwYXRoQmluZGluZ3MgJiYgcGF0aEJpbmRpbmdzWzBdLCAvLyBiaW5kaW5ncyBhcnJheSBmb3IgdGhlIGZpcnN0IGFyZ1xuXHRcdGJuZEN0eCA9IHtiZDogYmluZGluZ3N9LFxuXHRcdGJuZFN0YWNrID0gezA6IGJuZEN0eH0sXG5cdFx0cGFyYW1JbmRleCA9IDAsIC8vIGxpc3QsXG5cdFx0dG1wbExpbmtzID0gKHRtcGwgPyB0bXBsLmxpbmtzIDogYmluZGluZ3MgJiYgKGJpbmRpbmdzLmxpbmtzID0gYmluZGluZ3MubGlua3MgfHwge30pKSB8fCB0b3BWaWV3LnRtcGwubGlua3MsXG5cdFx0Ly8gVGhlIGZvbGxvd2luZyBhcmUgdXNlZCBmb3IgdHJhY2tpbmcgcGF0aCBwYXJzaW5nIGluY2x1ZGluZyBuZXN0ZWQgcGF0aHMsIHN1Y2ggYXMgXCJhLmIoY15kICsgKGUpKV5mXCIsIGFuZCBjaGFpbmVkIGNvbXB1dGVkIHBhdGhzIHN1Y2ggYXNcblx0XHQvLyBcImEuYigpLmNeZCgpLmUuZigpLmdcIiAtIHdoaWNoIGhhcyBmb3VyIGNoYWluZWQgcGF0aHMsIFwiYS5iKClcIiwgXCJeYy5kKClcIiwgXCIuZS5mKClcIiBhbmQgXCIuZ1wiXG5cdFx0cGFyZW5EZXB0aCA9IDAsXG5cdFx0Zm5DYWxsID0ge30sIC8vIFdlIGFyZSBpbiBhIGZ1bmN0aW9uIGNhbGxcblx0XHRwYXRoU3RhcnQgPSB7fSwgLy8gdHJhY2tzIHRoZSBzdGFydCBvZiB0aGUgY3VycmVudCBwYXRoIHN1Y2ggYXMgY15kKCkgaW4gdGhlIGFib3ZlIGV4YW1wbGVcblx0XHRyZXN1bHQgPSAocGFyYW1zICsgKHRtcGwgPyBcIiBcIiA6IFwiXCIpKS5yZXBsYWNlKHJQYXJhbXMsIHBhcnNlVG9rZW5zKTtcblxuXHRyZXR1cm4gIXBhcmVuRGVwdGggJiYgcmVzdWx0IHx8IHN5bnRheEVycm9yKHBhcmFtcyk7IC8vIFN5bnRheCBlcnJvciBpZiB1bmJhbGFuY2VkIHBhcmVucyBpbiBwYXJhbXMgZXhwcmVzc2lvblxufVxuXG5mdW5jdGlvbiBidWlsZENvZGUoYXN0LCB0bXBsLCBpc0xpbmtFeHByKSB7XG5cdC8vIEJ1aWxkIHRoZSB0ZW1wbGF0ZSBmdW5jdGlvbiBjb2RlIGZyb20gdGhlIEFTVCBub2RlcywgYW5kIHNldCBhcyBwcm9wZXJ0eSBvbiB0aGUgcGFzc2VkLWluIHRlbXBsYXRlIG9iamVjdFxuXHQvLyBVc2VkIGZvciBjb21waWxpbmcgdGVtcGxhdGVzLCBhbmQgYWxzbyBieSBKc1ZpZXdzIHRvIGJ1aWxkIGZ1bmN0aW9ucyBmb3IgZGF0YSBsaW5rIGV4cHJlc3Npb25zXG5cdHZhciBpLCBub2RlLCB0YWdOYW1lLCBjb252ZXJ0ZXIsIHRhZ0N0eCwgaGFzVGFnLCBoYXNFbmNvZGVyLCBnZXRzVmFsLCBoYXNDbnZ0LCB1c2VDbnZ0LCB0bXBsQmluZGluZ3MsIHBhdGhCaW5kaW5ncywgcGFyYW1zLCBib3VuZE9uRXJyU3RhcnQsXG5cdFx0Ym91bmRPbkVyckVuZCwgdGFnUmVuZGVyLCBuZXN0ZWRUbXBscywgdG1wbE5hbWUsIG5lc3RlZFRtcGwsIHRhZ0FuZEVsc2VzLCBjb250ZW50LCBtYXJrdXAsIG5leHRJc0Vsc2UsIG9sZENvZGUsIGlzRWxzZSwgaXNHZXRWYWwsIHRhZ0N0eEZuLFxuXHRcdG9uRXJyb3IsIHRhZ1N0YXJ0LCB0cmlnZ2VyLCBsYXRlUmVuZGVyLFxuXHRcdHRtcGxCaW5kaW5nS2V5ID0gMCxcblx0XHR1c2VWaWV3cyA9ICRzdWJTZXR0aW5nc0FkdmFuY2VkLnVzZVZpZXdzIHx8IHRtcGwudXNlVmlld3MgfHwgdG1wbC50YWdzIHx8IHRtcGwudGVtcGxhdGVzIHx8IHRtcGwuaGVscGVycyB8fCB0bXBsLmNvbnZlcnRlcnMsXG5cdFx0Y29kZSA9IFwiXCIsXG5cdFx0dG1wbE9wdGlvbnMgPSB7fSxcblx0XHRsID0gYXN0Lmxlbmd0aDtcblxuXHRpZiAoXCJcIiArIHRtcGwgPT09IHRtcGwpIHtcblx0XHR0bXBsTmFtZSA9IGlzTGlua0V4cHIgPyAnZGF0YS1saW5rPVwiJyArIHRtcGwucmVwbGFjZShyTmV3TGluZSwgXCIgXCIpLnNsaWNlKDEsIC0xKSArICdcIicgOiB0bXBsO1xuXHRcdHRtcGwgPSAwO1xuXHR9IGVsc2Uge1xuXHRcdHRtcGxOYW1lID0gdG1wbC50bXBsTmFtZSB8fCBcInVubmFtZWRcIjtcblx0XHRpZiAodG1wbC5hbGxvd0NvZGUpIHtcblx0XHRcdHRtcGxPcHRpb25zLmFsbG93Q29kZSA9IHRydWU7XG5cdFx0fVxuXHRcdGlmICh0bXBsLmRlYnVnKSB7XG5cdFx0XHR0bXBsT3B0aW9ucy5kZWJ1ZyA9IHRydWU7XG5cdFx0fVxuXHRcdHRtcGxCaW5kaW5ncyA9IHRtcGwuYm5kcztcblx0XHRuZXN0ZWRUbXBscyA9IHRtcGwudG1wbHM7XG5cdH1cblx0Zm9yIChpID0gMDsgaSA8IGw7IGkrKykge1xuXHRcdC8vIEFTVCBub2RlczogWzA6IHRhZ05hbWUsIDE6IGNvbnZlcnRlciwgMjogY29udGVudCwgMzogcGFyYW1zLCA0OiBjb2RlLCA1OiBvbkVycm9yLCA2OiB0cmlnZ2VyLCA3OnBhdGhCaW5kaW5ncywgODogY29udGVudE1hcmt1cF1cblx0XHRub2RlID0gYXN0W2ldO1xuXG5cdFx0Ly8gQWRkIG5ld2xpbmUgZm9yIGVhY2ggY2FsbG91dCB0byB0KCkgYygpIGV0Yy4gYW5kIGVhY2ggbWFya3VwIHN0cmluZ1xuXHRcdGlmIChcIlwiICsgbm9kZSA9PT0gbm9kZSkge1xuXHRcdFx0Ly8gYSBtYXJrdXAgc3RyaW5nIHRvIGJlIGluc2VydGVkXG5cdFx0XHRjb2RlICs9ICdcXG4rXCInICsgbm9kZSArICdcIic7XG5cdFx0fSBlbHNlIHtcblx0XHRcdC8vIGEgY29tcGlsZWQgdGFnIGV4cHJlc3Npb24gdG8gYmUgaW5zZXJ0ZWRcblx0XHRcdHRhZ05hbWUgPSBub2RlWzBdO1xuXHRcdFx0aWYgKHRhZ05hbWUgPT09IFwiKlwiKSB7XG5cdFx0XHRcdC8vIENvZGUgdGFnOiB7eyogfX1cblx0XHRcdFx0Y29kZSArPSBcIjtcXG5cIiArIG5vZGVbMV0gKyBcIlxcbnJldD1yZXRcIjtcblx0XHRcdH0gZWxzZSB7XG5cdFx0XHRcdGNvbnZlcnRlciA9IG5vZGVbMV07XG5cdFx0XHRcdGNvbnRlbnQgPSAhaXNMaW5rRXhwciAmJiBub2RlWzJdO1xuXHRcdFx0XHR0YWdDdHggPSBwYXJhbVN0cnVjdHVyZShub2RlWzNdLCAncGFyYW1zJykgKyAnfSwnICsgcGFyYW1TdHJ1Y3R1cmUocGFyYW1zID0gbm9kZVs0XSk7XG5cdFx0XHRcdHRyaWdnZXIgPSBub2RlWzZdO1xuXHRcdFx0XHRsYXRlUmVuZGVyID0gbm9kZVs3XTtcblx0XHRcdFx0bWFya3VwID0gbm9kZVs5XSAmJiBub2RlWzldLnJlcGxhY2UoclVuZXNjYXBlUXVvdGVzLCBcIiQxXCIpO1xuXHRcdFx0XHRpZiAoaXNFbHNlID0gdGFnTmFtZSA9PT0gXCJlbHNlXCIpIHtcblx0XHRcdFx0XHRpZiAocGF0aEJpbmRpbmdzKSB7XG5cdFx0XHRcdFx0XHRwYXRoQmluZGluZ3MucHVzaChub2RlWzhdKTtcblx0XHRcdFx0XHR9XG5cdFx0XHRcdH0gZWxzZSB7XG5cdFx0XHRcdFx0b25FcnJvciA9IG5vZGVbNV0gfHwgJHN1YlNldHRpbmdzLmRlYnVnTW9kZSAhPT0gZmFsc2UgJiYgXCJ1bmRlZmluZWRcIjsgLy8gSWYgZGVidWdNb2RlIG5vdCBmYWxzZSwgc2V0IGRlZmF1bHQgb25FcnJvciBoYW5kbGVyIG9uIHRhZyB0byBcInVuZGVmaW5lZFwiIChzZWUgb25SZW5kZXJFcnJvcilcblx0XHRcdFx0XHRpZiAodG1wbEJpbmRpbmdzICYmIChwYXRoQmluZGluZ3MgPSBub2RlWzhdKSkgeyAvLyBBcnJheSBvZiBwYXRocywgb3IgZmFsc2UgaWYgbm90IGRhdGEtYm91bmRcblx0XHRcdFx0XHRcdHBhdGhCaW5kaW5ncyA9IFtwYXRoQmluZGluZ3NdO1xuXHRcdFx0XHRcdFx0dG1wbEJpbmRpbmdLZXkgPSB0bXBsQmluZGluZ3MucHVzaCgxKTsgLy8gQWRkIHBsYWNlaG9sZGVyIGluIHRtcGxCaW5kaW5ncyBmb3IgY29tcGlsZWQgZnVuY3Rpb25cblx0XHRcdFx0XHR9XG5cdFx0XHRcdH1cblx0XHRcdFx0dXNlVmlld3MgPSB1c2VWaWV3cyB8fCBwYXJhbXNbMV0gfHwgcGFyYW1zWzJdIHx8IHBhdGhCaW5kaW5ncyB8fCAvdmlldy4oPyFpbmRleCkvLnRlc3QocGFyYW1zWzBdKTtcblx0XHRcdFx0Ly8gdXNlVmlld3MgaXMgZm9yIHBlcmYgb3B0aW1pemF0aW9uLiBGb3IgcmVuZGVyKCkgd2Ugb25seSB1c2Ugdmlld3MgaWYgbmVjZXNzYXJ5IC0gZm9yIHRoZSBtb3JlIGFkdmFuY2VkIHNjZW5hcmlvcy5cblx0XHRcdFx0Ly8gV2UgdXNlIHZpZXdzIGlmIHRoZXJlIGFyZSBwcm9wcywgY29udGV4dHVhbCBwcm9wZXJ0aWVzIG9yIGFyZ3Mgd2l0aCAjLi4uIChvdGhlciB0aGFuICNpbmRleCkgLSBidXQgeW91IGNhbiBmb3JjZVxuXHRcdFx0XHQvLyB1c2luZyB0aGUgZnVsbCB2aWV3IGluZnJhc3RydWN0dXJlLCAoYW5kIHBheSBhIHBlcmYgcHJpY2UpIGJ5IG9wdGluZyBpbjogU2V0IHVzZVZpZXdzOiB0cnVlIG9uIHRoZSB0ZW1wbGF0ZSwgbWFudWFsbHkuLi5cblx0XHRcdFx0aWYgKGlzR2V0VmFsID0gdGFnTmFtZSA9PT0gXCI6XCIpIHtcblx0XHRcdFx0XHRpZiAoY29udmVydGVyKSB7XG5cdFx0XHRcdFx0XHR0YWdOYW1lID0gY29udmVydGVyID09PSBIVE1MID8gXCI+XCIgOiBjb252ZXJ0ZXIgKyB0YWdOYW1lO1xuXHRcdFx0XHRcdH1cblx0XHRcdFx0fSBlbHNlIHtcblx0XHRcdFx0XHRpZiAoY29udGVudCkgeyAvLyBUT0RPIG9wdGltaXplIC0gaWYgY29udGVudC5sZW5ndGggPT09IDAgb3IgaWYgdGhlcmUgaXMgYSB0bXBsPVwiLi4uXCIgc3BlY2lmaWVkIC0gc2V0IGNvbnRlbnQgdG8gbnVsbCAvIGRvbid0IHJ1biB0aGlzIGNvbXBpbGF0aW9uIGNvZGUgLSBzaW5jZSBjb250ZW50IHdvbid0IGdldCB1c2VkISFcblx0XHRcdFx0XHRcdC8vIENyZWF0ZSB0ZW1wbGF0ZSBvYmplY3QgZm9yIG5lc3RlZCB0ZW1wbGF0ZVxuXHRcdFx0XHRcdFx0bmVzdGVkVG1wbCA9IHRtcGxPYmplY3QobWFya3VwLCB0bXBsT3B0aW9ucyk7XG5cdFx0XHRcdFx0XHRuZXN0ZWRUbXBsLnRtcGxOYW1lID0gdG1wbE5hbWUgKyBcIi9cIiArIHRhZ05hbWU7XG5cdFx0XHRcdFx0XHQvLyBDb21waWxlIHRvIEFTVCBhbmQgdGhlbiB0byBjb21waWxlZCBmdW5jdGlvblxuXHRcdFx0XHRcdFx0bmVzdGVkVG1wbC51c2VWaWV3cyA9IG5lc3RlZFRtcGwudXNlVmlld3MgfHwgdXNlVmlld3M7XG5cdFx0XHRcdFx0XHRidWlsZENvZGUoY29udGVudCwgbmVzdGVkVG1wbCk7XG5cdFx0XHRcdFx0XHR1c2VWaWV3cyA9IG5lc3RlZFRtcGwudXNlVmlld3M7XG5cdFx0XHRcdFx0XHRuZXN0ZWRUbXBscy5wdXNoKG5lc3RlZFRtcGwpO1xuXHRcdFx0XHRcdH1cblxuXHRcdFx0XHRcdGlmICghaXNFbHNlKSB7XG5cdFx0XHRcdFx0XHQvLyBUaGlzIGlzIG5vdCBhbiBlbHNlIHRhZy5cblx0XHRcdFx0XHRcdHRhZ0FuZEVsc2VzID0gdGFnTmFtZTtcblx0XHRcdFx0XHRcdHVzZVZpZXdzID0gdXNlVmlld3MgfHwgdGFnTmFtZSAmJiAoISR0YWdzW3RhZ05hbWVdIHx8ICEkdGFnc1t0YWdOYW1lXS5mbG93KTtcblx0XHRcdFx0XHRcdC8vIFN3aXRjaCB0byBhIG5ldyBjb2RlIHN0cmluZyBmb3IgdGhpcyBib3VuZCB0YWcgKGFuZCBpdHMgZWxzZXMsIGlmIGl0IGhhcyBhbnkpIC0gZm9yIHJldHVybmluZyB0aGUgdGFnQ3R4cyBhcnJheVxuXHRcdFx0XHRcdFx0b2xkQ29kZSA9IGNvZGU7XG5cdFx0XHRcdFx0XHRjb2RlID0gXCJcIjtcblx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0bmV4dElzRWxzZSA9IGFzdFtpICsgMV07XG5cdFx0XHRcdFx0bmV4dElzRWxzZSA9IG5leHRJc0Vsc2UgJiYgbmV4dElzRWxzZVswXSA9PT0gXCJlbHNlXCI7XG5cdFx0XHRcdH1cblx0XHRcdFx0dGFnU3RhcnQgPSBvbkVycm9yID8gXCI7XFxudHJ5e1xcbnJldCs9XCIgOiBcIlxcbitcIjtcblx0XHRcdFx0Ym91bmRPbkVyclN0YXJ0ID0gXCJcIjtcblx0XHRcdFx0Ym91bmRPbkVyckVuZCA9IFwiXCI7XG5cblx0XHRcdFx0aWYgKGlzR2V0VmFsICYmIChwYXRoQmluZGluZ3MgfHwgdHJpZ2dlciB8fCBjb252ZXJ0ZXIgJiYgY29udmVydGVyICE9PSBIVE1MIHx8IGxhdGVSZW5kZXIpKSB7XG5cdFx0XHRcdFx0Ly8gRm9yIGNvbnZlcnRWYWwgd2UgbmVlZCBhIGNvbXBpbGVkIGZ1bmN0aW9uIHRvIHJldHVybiB0aGUgbmV3IHRhZ0N0eChzKVxuXHRcdFx0XHRcdHRhZ0N0eEZuID0gbmV3IEZ1bmN0aW9uKFwiZGF0YSx2aWV3LGosdVwiLCBcIi8vIFwiICsgdG1wbE5hbWUgKyBcIiBcIiArICgrK3RtcGxCaW5kaW5nS2V5KSArIFwiIFwiICsgdGFnTmFtZVxuXHRcdFx0XHRcdFx0XHRcdFx0XHQrIFwiXFxucmV0dXJuIHtcIiArIHRhZ0N0eCArIFwifTtcIik7XG5cdFx0XHRcdFx0dGFnQ3R4Rm4uX2VyID0gb25FcnJvcjtcblx0XHRcdFx0XHR0YWdDdHhGbi5fdGFnID0gdGFnTmFtZTtcblx0XHRcdFx0XHR0YWdDdHhGbi5fYmQgPSAhIXBhdGhCaW5kaW5nczsgLy8gZGF0YS1saW5rZWQgdGFnIHteey4uLi99fVxuXHRcdFx0XHRcdHRhZ0N0eEZuLl9sciA9IGxhdGVSZW5kZXI7XG5cblx0XHRcdFx0XHRpZiAoaXNMaW5rRXhwcikge1xuXHRcdFx0XHRcdFx0cmV0dXJuIHRhZ0N0eEZuO1xuXHRcdFx0XHRcdH1cblxuXHRcdFx0XHRcdHNldFBhdGhzKHRhZ0N0eEZuLCBwYXRoQmluZGluZ3MpO1xuXHRcdFx0XHRcdHRhZ1JlbmRlciA9ICdjKFwiJyArIGNvbnZlcnRlciArICdcIix2aWV3LCc7XG5cdFx0XHRcdFx0dXNlQ252dCA9IHRydWU7XG5cdFx0XHRcdFx0Ym91bmRPbkVyclN0YXJ0ID0gdGFnUmVuZGVyICsgdG1wbEJpbmRpbmdLZXkgKyBcIixcIjtcblx0XHRcdFx0XHRib3VuZE9uRXJyRW5kID0gXCIpXCI7XG5cdFx0XHRcdH1cblx0XHRcdFx0Y29kZSArPSAoaXNHZXRWYWxcblx0XHRcdFx0XHQ/IChpc0xpbmtFeHByID8gKG9uRXJyb3IgPyBcInRyeXtcXG5cIiA6IFwiXCIpICsgXCJyZXR1cm4gXCIgOiB0YWdTdGFydCkgKyAodXNlQ252dCAvLyBDYWxsIF9jbnZ0IGlmIHRoZXJlIGlzIGEgY29udmVydGVyOiB7e2NudnQ6IC4uLiB9fSBvciB7XntjbnZ0OiAuLi4gfX1cblx0XHRcdFx0XHRcdD8gKHVzZUNudnQgPSB1bmRlZmluZWQsIHVzZVZpZXdzID0gaGFzQ252dCA9IHRydWUsIHRhZ1JlbmRlciArICh0YWdDdHhGblxuXHRcdFx0XHRcdFx0XHQ/ICgodG1wbEJpbmRpbmdzW3RtcGxCaW5kaW5nS2V5IC0gMV0gPSB0YWdDdHhGbiksIHRtcGxCaW5kaW5nS2V5KSAvLyBTdG9yZSB0aGUgY29tcGlsZWQgdGFnQ3R4Rm4gaW4gdG1wbC5ibmRzLCBhbmQgcGFzcyB0aGUga2V5IHRvIGNvbnZlcnRWYWwoKVxuXHRcdFx0XHRcdFx0XHQ6IFwie1wiICsgdGFnQ3R4ICsgXCJ9XCIpICsgXCIpXCIpXG5cdFx0XHRcdFx0XHQ6IHRhZ05hbWUgPT09IFwiPlwiXG5cdFx0XHRcdFx0XHRcdD8gKGhhc0VuY29kZXIgPSB0cnVlLCBcImgoXCIgKyBwYXJhbXNbMF0gKyBcIilcIilcblx0XHRcdFx0XHRcdFx0OiAoZ2V0c1ZhbCA9IHRydWUsIFwiKCh2PVwiICsgcGFyYW1zWzBdICsgJykhPW51bGw/djonICsgKGlzTGlua0V4cHIgPyAnbnVsbCknIDogJ1wiXCIpJykpXG5cdFx0XHRcdFx0XHRcdC8vIE5vbiBzdHJpY3QgZXF1YWxpdHkgc28gZGF0YS1saW5rPVwidGl0bGV7OmV4cHJ9XCIgd2l0aCBleHByPW51bGwvdW5kZWZpbmVkIHJlbW92ZXMgdGl0bGUgYXR0cmlidXRlXG5cdFx0XHRcdFx0KVxuXHRcdFx0XHRcdDogKGhhc1RhZyA9IHRydWUsIFwiXFxue3ZpZXc6dmlldyx0bXBsOlwiIC8vIEFkZCB0aGlzIHRhZ0N0eCB0byB0aGUgY29tcGlsZWQgY29kZSBmb3IgdGhlIHRhZ0N0eHMgdG8gYmUgcGFzc2VkIHRvIHJlbmRlclRhZygpXG5cdFx0XHRcdFx0XHQrIChjb250ZW50ID8gbmVzdGVkVG1wbHMubGVuZ3RoIDogXCIwXCIpICsgXCIsXCIgLy8gRm9yIGJsb2NrIHRhZ3MsIHBhc3MgaW4gdGhlIGtleSAobmVzdGVkVG1wbHMubGVuZ3RoKSB0byB0aGUgbmVzdGVkIGNvbnRlbnQgdGVtcGxhdGVcblx0XHRcdFx0XHRcdCsgdGFnQ3R4ICsgXCJ9LFwiKSk7XG5cblx0XHRcdFx0aWYgKHRhZ0FuZEVsc2VzICYmICFuZXh0SXNFbHNlKSB7XG5cdFx0XHRcdFx0Ly8gVGhpcyBpcyBhIGRhdGEtbGluayBleHByZXNzaW9uIG9yIGFuIGlubGluZSB0YWcgd2l0aG91dCBhbnkgZWxzZXMsIG9yIHRoZSBsYXN0IHt7ZWxzZX19IG9mIGFuIGlubGluZSB0YWdcblx0XHRcdFx0XHQvLyBXZSBjb21wbGV0ZSB0aGUgY29kZSBmb3IgcmV0dXJuaW5nIHRoZSB0YWdDdHhzIGFycmF5XG5cdFx0XHRcdFx0Y29kZSA9IFwiW1wiICsgY29kZS5zbGljZSgwLCAtMSkgKyBcIl1cIjtcblx0XHRcdFx0XHR0YWdSZW5kZXIgPSAndChcIicgKyB0YWdBbmRFbHNlcyArICdcIix2aWV3LHRoaXMsJztcblx0XHRcdFx0XHRpZiAoaXNMaW5rRXhwciB8fCBwYXRoQmluZGluZ3MpIHtcblx0XHRcdFx0XHRcdC8vIFRoaXMgaXMgYSBib3VuZCB0YWcgKGRhdGEtbGluayBleHByZXNzaW9uIG9yIGlubGluZSBib3VuZCB0YWcge157dGFnIC4uLn19KSBzbyB3ZSBzdG9yZSBhIGNvbXBpbGVkIHRhZ0N0eHMgZnVuY3Rpb24gaW4gdG1wLmJuZHNcblx0XHRcdFx0XHRcdGNvZGUgPSBuZXcgRnVuY3Rpb24oXCJkYXRhLHZpZXcsaix1XCIsIFwiIC8vIFwiICsgdG1wbE5hbWUgKyBcIiBcIiArIHRtcGxCaW5kaW5nS2V5ICsgXCIgXCIgKyB0YWdBbmRFbHNlcyArIFwiXFxucmV0dXJuIFwiICsgY29kZSArIFwiO1wiKTtcblx0XHRcdFx0XHRcdGNvZGUuX2VyID0gb25FcnJvcjtcblx0XHRcdFx0XHRcdGNvZGUuX3RhZyA9IHRhZ0FuZEVsc2VzO1xuXHRcdFx0XHRcdFx0aWYgKHBhdGhCaW5kaW5ncykge1xuXHRcdFx0XHRcdFx0XHRzZXRQYXRocyh0bXBsQmluZGluZ3NbdG1wbEJpbmRpbmdLZXkgLSAxXSA9IGNvZGUsIHBhdGhCaW5kaW5ncyk7XG5cdFx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0XHRjb2RlLl9sciA9IGxhdGVSZW5kZXI7XG5cdFx0XHRcdFx0XHRpZiAoaXNMaW5rRXhwcikge1xuXHRcdFx0XHRcdFx0XHRyZXR1cm4gY29kZTsgLy8gRm9yIGEgZGF0YS1saW5rIGV4cHJlc3Npb24gd2UgcmV0dXJuIHRoZSBjb21waWxlZCB0YWdDdHhzIGZ1bmN0aW9uXG5cdFx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0XHRib3VuZE9uRXJyU3RhcnQgPSB0YWdSZW5kZXIgKyB0bXBsQmluZGluZ0tleSArIFwiLHVuZGVmaW5lZCxcIjtcblx0XHRcdFx0XHRcdGJvdW5kT25FcnJFbmQgPSBcIilcIjtcblx0XHRcdFx0XHR9XG5cblx0XHRcdFx0XHQvLyBUaGlzIGlzIHRoZSBsYXN0IHt7ZWxzZX19IGZvciBhbiBpbmxpbmUgdGFnLlxuXHRcdFx0XHRcdC8vIEZvciBhIGJvdW5kIHRhZywgcGFzcyB0aGUgdGFnQ3R4cyBmbiBsb29rdXAga2V5IHRvIHJlbmRlclRhZy5cblx0XHRcdFx0XHQvLyBGb3IgYW4gdW5ib3VuZCB0YWcsIGluY2x1ZGUgdGhlIGNvZGUgZGlyZWN0bHkgZm9yIGV2YWx1YXRpbmcgdGFnQ3R4cyBhcnJheVxuXHRcdFx0XHRcdGNvZGUgPSBvbGRDb2RlICsgdGFnU3RhcnQgKyB0YWdSZW5kZXIgKyAoY29kZS5kZXBzICYmIHRtcGxCaW5kaW5nS2V5IHx8IGNvZGUpICsgXCIpXCI7XG5cdFx0XHRcdFx0cGF0aEJpbmRpbmdzID0gMDtcblx0XHRcdFx0XHR0YWdBbmRFbHNlcyA9IDA7XG5cdFx0XHRcdH1cblx0XHRcdFx0aWYgKG9uRXJyb3IgJiYgIW5leHRJc0Vsc2UpIHtcblx0XHRcdFx0XHR1c2VWaWV3cyA9IHRydWU7XG5cdFx0XHRcdFx0Y29kZSArPSAnO1xcbn1jYXRjaChlKXtyZXQnICsgKGlzTGlua0V4cHIgPyBcInVybiBcIiA6IFwiKz1cIikgKyBib3VuZE9uRXJyU3RhcnQgKyAnai5fZXJyKGUsdmlldywnICsgb25FcnJvciArICcpJyArIGJvdW5kT25FcnJFbmQgKyAnO30nICsgKGlzTGlua0V4cHIgPyBcIlwiIDogJ3JldD1yZXQnKTtcblx0XHRcdFx0fVxuXHRcdFx0fVxuXHRcdH1cblx0fVxuXHQvLyBJbmNsdWRlIG9ubHkgdGhlIHZhciByZWZlcmVuY2VzIHRoYXQgYXJlIG5lZWRlZCBpbiB0aGUgY29kZVxuXHRjb2RlID0gXCIvLyBcIiArIHRtcGxOYW1lXG5cblx0XHQrIFwiXFxudmFyIHZcIlxuXHRcdCsgKGhhc1RhZyA/IFwiLHQ9ai5fdGFnXCIgOiBcIlwiKSAgICAgICAgICAgICAgICAvLyBoYXMgdGFnXG5cdFx0KyAoaGFzQ252dCA/IFwiLGM9ai5fY252dFwiIDogXCJcIikgICAgICAgICAgICAgIC8vIGNvbnZlcnRlclxuXHRcdCsgKGhhc0VuY29kZXIgPyBcIixoPWouX2h0bWxcIiA6IFwiXCIpICAgICAgICAgICAvLyBodG1sIGNvbnZlcnRlclxuXHRcdCsgKGlzTGlua0V4cHIgPyBcIjtcXG5cIiA6ICcscmV0PVwiXCJcXG4nKVxuXHRcdCsgKHRtcGxPcHRpb25zLmRlYnVnID8gXCJkZWJ1Z2dlcjtcIiA6IFwiXCIpXG5cdFx0KyBjb2RlXG5cdFx0KyAoaXNMaW5rRXhwciA/IFwiXFxuXCIgOiBcIjtcXG5yZXR1cm4gcmV0O1wiKTtcblxuXHR0cnkge1xuXHRcdGNvZGUgPSBuZXcgRnVuY3Rpb24oXCJkYXRhLHZpZXcsaix1XCIsIGNvZGUpO1xuXHR9IGNhdGNoIChlKSB7XG5cdFx0c3ludGF4RXJyb3IoXCJDb21waWxlZCB0ZW1wbGF0ZSBjb2RlOlxcblxcblwiICsgY29kZSArICdcXG46IFwiJyArIChlLm1lc3NhZ2V8fGUpICsgJ1wiJyk7XG5cdH1cblx0aWYgKHRtcGwpIHtcblx0XHR0bXBsLmZuID0gY29kZTtcblx0XHR0bXBsLnVzZVZpZXdzID0gISF1c2VWaWV3cztcblx0fVxuXHRyZXR1cm4gY29kZTtcbn1cblxuLy89PT09PT09PT09XG4vLyBVdGlsaXRpZXNcbi8vPT09PT09PT09PVxuXG4vLyBNZXJnZSBvYmplY3RzLCBpbiBwYXJ0aWN1bGFyIGNvbnRleHRzIHdoaWNoIGluaGVyaXQgZnJvbSBwYXJlbnQgY29udGV4dHNcbmZ1bmN0aW9uIGV4dGVuZEN0eChjb250ZXh0LCBwYXJlbnRDb250ZXh0KSB7XG5cdC8vIFJldHVybiBjb3B5IG9mIHBhcmVudENvbnRleHQsIHVubGVzcyBjb250ZXh0IGlzIGRlZmluZWQgYW5kIGlzIGRpZmZlcmVudCwgaW4gd2hpY2ggY2FzZSByZXR1cm4gYSBuZXcgbWVyZ2VkIGNvbnRleHRcblx0Ly8gSWYgbmVpdGhlciBjb250ZXh0IG5vciBwYXJlbnRDb250ZXh0IGFyZSBkZWZpbmVkLCByZXR1cm4gdW5kZWZpbmVkXG5cdHJldHVybiBjb250ZXh0ICYmIGNvbnRleHQgIT09IHBhcmVudENvbnRleHRcblx0XHQ/IChwYXJlbnRDb250ZXh0XG5cdFx0XHQ/ICRleHRlbmQoJGV4dGVuZCh7fSwgcGFyZW50Q29udGV4dCksIGNvbnRleHQpXG5cdFx0XHQ6IGNvbnRleHQpXG5cdFx0OiBwYXJlbnRDb250ZXh0ICYmICRleHRlbmQoe30sIHBhcmVudENvbnRleHQpO1xufVxuXG4vLyBHZXQgY2hhcmFjdGVyIGVudGl0eSBmb3IgSFRNTCBhbmQgQXR0cmlidXRlIGVuY29kaW5nXG5mdW5jdGlvbiBnZXRDaGFyRW50aXR5KGNoKSB7XG5cdHJldHVybiBjaGFyRW50aXRpZXNbY2hdIHx8IChjaGFyRW50aXRpZXNbY2hdID0gXCImI1wiICsgY2guY2hhckNvZGVBdCgwKSArIFwiO1wiKTtcbn1cblxuZnVuY3Rpb24gZ2V0VGFyZ2V0UHJvcHMoc291cmNlKSB7XG5cdC8vIHRoaXMgcG9pbnRlciBpcyB0aGVNYXAgLSB3aGljaCBoYXMgdGFnQ3R4LnByb3BzIHRvb1xuXHQvLyBhcmd1bWVudHM6IHRhZ0N0eC5hcmdzLlxuXHR2YXIga2V5LCBwcm9wLFxuXHRcdHByb3BzID0gW107XG5cblx0aWYgKHR5cGVvZiBzb3VyY2UgPT09IE9CSkVDVCkge1xuXHRcdGZvciAoa2V5IGluIHNvdXJjZSkge1xuXHRcdFx0cHJvcCA9IHNvdXJjZVtrZXldO1xuXHRcdFx0aWYgKGtleSAhPT0gJGV4cGFuZG8gJiYgc291cmNlLmhhc093blByb3BlcnR5KGtleSkgJiYgISRpc0Z1bmN0aW9uKHByb3ApKSB7XG5cdFx0XHRcdHByb3BzLnB1c2goe2tleToga2V5LCBwcm9wOiBwcm9wfSk7XG5cdFx0XHR9XG5cdFx0fVxuXHR9XG5cdHJldHVybiBwcm9wcztcbn1cblxuZnVuY3Rpb24gJGZuUmVuZGVyKGRhdGEsIGNvbnRleHQsIG5vSXRlcmF0aW9uKSB7XG5cdHZhciB0bXBsRWxlbSA9IHRoaXMuanF1ZXJ5ICYmICh0aGlzWzBdIHx8IGVycm9yKCdVbmtub3duIHRlbXBsYXRlJykpLCAvLyBUYXJnZXRlZCBlbGVtZW50IG5vdCBmb3VuZCBmb3IgalF1ZXJ5IHRlbXBsYXRlIHNlbGVjdG9yIHN1Y2ggYXMgXCIjbXlUbXBsXCJcblx0XHR0bXBsID0gdG1wbEVsZW0uZ2V0QXR0cmlidXRlKHRtcGxBdHRyKTtcblxuXHRyZXR1cm4gcmVuZGVyQ29udGVudC5jYWxsKHRtcGwgJiYgJC5kYXRhKHRtcGxFbGVtKVtqc3ZUbXBsXSB8fCAkdGVtcGxhdGVzKHRtcGxFbGVtKSxcblx0XHRkYXRhLCBjb250ZXh0LCBub0l0ZXJhdGlvbik7XG59XG5cbi8vPT09PT09PT09PT09PT09PT09PT09PT09PT0gUmVnaXN0ZXIgY29udmVydGVycyA9PT09PT09PT09PT09PT09PT09PT09PT09PVxuXG5mdW5jdGlvbiBodG1sRW5jb2RlKHRleHQpIHtcblx0Ly8gSFRNTCBlbmNvZGU6IFJlcGxhY2UgPCA+ICYgJyBhbmQgXCIgYnkgY29ycmVzcG9uZGluZyBlbnRpdGllcy5cblx0cmV0dXJuIHRleHQgIT0gdW5kZWZpbmVkID8gcklzSHRtbC50ZXN0KHRleHQpICYmIChcIlwiICsgdGV4dCkucmVwbGFjZShySHRtbEVuY29kZSwgZ2V0Q2hhckVudGl0eSkgfHwgdGV4dCA6IFwiXCI7XG59XG5cbi8vPT09PT09PT09PT09PT09PT09PT09PT09PT0gSW5pdGlhbGl6ZSA9PT09PT09PT09PT09PT09PT09PT09PT09PVxuXG4kc3ViID0gJHZpZXdzLnN1YjtcbiR2aWV3c1NldHRpbmdzID0gJHZpZXdzLnNldHRpbmdzO1xuXG5pZiAoIShqc3IgfHwgJCAmJiAkLnJlbmRlcikpIHtcblx0Ly8gSnNSZW5kZXIgbm90IGFscmVhZHkgbG9hZGVkLCBvciBsb2FkZWQgd2l0aG91dCBqUXVlcnksIGFuZCB3ZSBhcmUgbm93IG1vdmluZyBmcm9tIGpzcmVuZGVyIG5hbWVzcGFjZSB0byBqUXVlcnkgbmFtZXBhY2Vcblx0Zm9yIChqc3ZTdG9yZU5hbWUgaW4ganN2U3RvcmVzKSB7XG5cdFx0cmVnaXN0ZXJTdG9yZShqc3ZTdG9yZU5hbWUsIGpzdlN0b3Jlc1tqc3ZTdG9yZU5hbWVdKTtcblx0fVxuXG5cdCRjb252ZXJ0ZXJzID0gJHZpZXdzLmNvbnZlcnRlcnM7XG5cdCRoZWxwZXJzID0gJHZpZXdzLmhlbHBlcnM7XG5cdCR0YWdzID0gJHZpZXdzLnRhZ3M7XG5cblx0JHN1Yi5fdGcucHJvdG90eXBlID0ge1xuXHRcdGJhc2VBcHBseTogYmFzZUFwcGx5LFxuXHRcdGN2dEFyZ3M6IGNvbnZlcnRBcmdzLFxuXHRcdGJuZEFyZ3M6IGNvbnZlcnRCb3VuZEFyZ3MsXG5cdFx0Y3R4UHJtOiBjb250ZXh0UGFyYW1ldGVyXG5cdH07XG5cblx0dG9wVmlldyA9ICRzdWIudG9wVmlldyA9IG5ldyBWaWV3KCk7XG5cblx0Ly9CUk9XU0VSLVNQRUNJRklDIENPREVcblx0aWYgKCQpIHtcblxuXHRcdC8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vL1xuXHRcdC8vIGpRdWVyeSAoPSAkKSBpcyBsb2FkZWRcblxuXHRcdCQuZm4ucmVuZGVyID0gJGZuUmVuZGVyO1xuXHRcdCRleHBhbmRvID0gJC5leHBhbmRvO1xuXHRcdGlmICgkLm9ic2VydmFibGUpIHtcblx0XHRcdCRleHRlbmQoJHN1YiwgJC52aWV3cy5zdWIpOyAvLyBqcXVlcnkub2JzZXJ2YWJsZS5qcyB3YXMgbG9hZGVkIGJlZm9yZSBqc3JlbmRlci5qc1xuXHRcdFx0JHZpZXdzLm1hcCA9ICQudmlld3MubWFwO1xuXHRcdH1cblxuXHR9IGVsc2Uge1xuXHRcdC8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vL1xuXHRcdC8vIGpRdWVyeSBpcyBub3QgbG9hZGVkLlxuXG5cdFx0JCA9IHt9O1xuXG5cdFx0aWYgKHNldEdsb2JhbHMpIHtcblx0XHRcdGdsb2JhbC5qc3JlbmRlciA9ICQ7IC8vIFdlIGFyZSBsb2FkaW5nIGpzcmVuZGVyLmpzIGZyb20gYSBzY3JpcHQgZWxlbWVudCwgbm90IEFNRCBvciBDb21tb25KUywgc28gc2V0IGdsb2JhbFxuXHRcdH1cblxuXHRcdC8vIEVycm9yIHdhcm5pbmcgaWYganNyZW5kZXIuanMgaXMgdXNlZCBhcyB0ZW1wbGF0ZSBlbmdpbmUgb24gTm9kZS5qcyAoZS5nLiBFeHByZXNzIG9yIEhhcGkuLi4pXG5cdFx0Ly8gVXNlIGpzcmVuZGVyLW5vZGUuanMgaW5zdGVhZC4uLlxuXHRcdCQucmVuZGVyRmlsZSA9ICQuX19leHByZXNzID0gJC5jb21waWxlID0gZnVuY3Rpb24oKSB7IHRocm93IFwiTm9kZS5qczogdXNlIG5wbSBqc3JlbmRlciwgb3IganNyZW5kZXItbm9kZS5qc1wiOyB9O1xuXG5cdFx0Ly9FTkQgQlJPV1NFUi1TUEVDSUZJQyBDT0RFXG5cdFx0JC5pc0Z1bmN0aW9uID0gZnVuY3Rpb24ob2IpIHtcblx0XHRcdHJldHVybiB0eXBlb2Ygb2IgPT09IFwiZnVuY3Rpb25cIjtcblx0XHR9O1xuXG5cdFx0JC5pc0FycmF5ID0gQXJyYXkuaXNBcnJheSB8fCBmdW5jdGlvbihvYmopIHtcblx0XHRcdHJldHVybiAoe30udG9TdHJpbmcpLmNhbGwob2JqKSA9PT0gXCJbb2JqZWN0IEFycmF5XVwiO1xuXHRcdH07XG5cblx0XHQkc3ViLl9qcSA9IGZ1bmN0aW9uKGpxKSB7IC8vIHByaXZhdGUgbWV0aG9kIHRvIG1vdmUgZnJvbSBKc1JlbmRlciBBUElzIGZyb20ganNyZW5kZXIgbmFtZXNwYWNlIHRvIGpRdWVyeSBuYW1lc3BhY2Vcblx0XHRcdGlmIChqcSAhPT0gJCkge1xuXHRcdFx0XHQkZXh0ZW5kKGpxLCAkKTsgLy8gbWFwIG92ZXIgZnJvbSBqc3JlbmRlciBuYW1lc3BhY2UgdG8galF1ZXJ5IG5hbWVzcGFjZVxuXHRcdFx0XHQkID0ganE7XG5cdFx0XHRcdCQuZm4ucmVuZGVyID0gJGZuUmVuZGVyO1xuXHRcdFx0XHRkZWxldGUgJC5qc3JlbmRlcjtcblx0XHRcdFx0JGV4cGFuZG8gPSAkLmV4cGFuZG87XG5cdFx0XHR9XG5cdFx0fTtcblxuXHRcdCQuanNyZW5kZXIgPSB2ZXJzaW9uTnVtYmVyO1xuXHR9XG5cdCRzdWJTZXR0aW5ncyA9ICRzdWIuc2V0dGluZ3M7XG5cdCRzdWJTZXR0aW5ncy5hbGxvd0NvZGUgPSBmYWxzZTtcblx0JGlzRnVuY3Rpb24gPSAkLmlzRnVuY3Rpb247XG5cdCQucmVuZGVyID0gJHJlbmRlcjtcblx0JC52aWV3cyA9ICR2aWV3cztcblx0JC50ZW1wbGF0ZXMgPSAkdGVtcGxhdGVzID0gJHZpZXdzLnRlbXBsYXRlcztcblxuXHRmb3IgKHNldHRpbmcgaW4gJHN1YlNldHRpbmdzKSB7XG5cdFx0YWRkU2V0dGluZyhzZXR0aW5nKTtcblx0fVxuXG5cdCgkdmlld3NTZXR0aW5ncy5kZWJ1Z01vZGUgPSBmdW5jdGlvbihkZWJ1Z01vZGUpIHtcblx0XHRyZXR1cm4gZGVidWdNb2RlID09PSB1bmRlZmluZWRcblx0XHRcdD8gJHN1YlNldHRpbmdzLmRlYnVnTW9kZVxuXHRcdFx0OiAoXG5cdFx0XHRcdCRzdWJTZXR0aW5ncy5kZWJ1Z01vZGUgPSBkZWJ1Z01vZGUsXG5cdFx0XHRcdCRzdWJTZXR0aW5ncy5vbkVycm9yID0gZGVidWdNb2RlICsgXCJcIiA9PT0gZGVidWdNb2RlXG5cdFx0XHRcdFx0PyBuZXcgRnVuY3Rpb24oXCJcIiwgXCJyZXR1cm4gJ1wiICsgZGVidWdNb2RlICsgXCInO1wiKVxuXHRcdFx0XHRcdDogJGlzRnVuY3Rpb24oZGVidWdNb2RlKVxuXHRcdFx0XHRcdFx0PyBkZWJ1Z01vZGVcblx0XHRcdFx0XHRcdDogdW5kZWZpbmVkLFxuXHRcdFx0XHQkdmlld3NTZXR0aW5ncyk7XG5cdH0pKGZhbHNlKTsgLy8ganNoaW50IGlnbm9yZTpsaW5lXG5cblx0JHN1YlNldHRpbmdzQWR2YW5jZWQgPSAkc3ViU2V0dGluZ3MuYWR2YW5jZWQgPSB7XG5cdFx0dXNlVmlld3M6IGZhbHNlLFxuXHRcdF9qc3Y6IGZhbHNlIC8vIEZvciBnbG9iYWwgYWNjZXNzIHRvIEpzVmlld3Mgc3RvcmVcblx0fTtcblxuXHQvLz09PT09PT09PT09PT09PT09PT09PT09PT09IFJlZ2lzdGVyIHRhZ3MgPT09PT09PT09PT09PT09PT09PT09PT09PT1cblxuXHQkdGFncyh7XG5cdFx0XCJpZlwiOiB7XG5cdFx0XHRyZW5kZXI6IGZ1bmN0aW9uKHZhbCkge1xuXHRcdFx0XHQvLyBUaGlzIGZ1bmN0aW9uIGlzIGNhbGxlZCBvbmNlIGZvciB7e2lmfX0gYW5kIG9uY2UgZm9yIGVhY2gge3tlbHNlfX0uXG5cdFx0XHRcdC8vIFdlIHdpbGwgdXNlIHRoZSB0YWcucmVuZGVyaW5nIG9iamVjdCBmb3IgY2FycnlpbmcgcmVuZGVyaW5nIHN0YXRlIGFjcm9zcyB0aGUgY2FsbHMuXG5cdFx0XHRcdC8vIElmIG5vdCBkb25lIChhIHByZXZpb3VzIGJsb2NrIGhhcyBub3QgYmVlbiByZW5kZXJlZCksIGxvb2sgYXQgZXhwcmVzc2lvbiBmb3IgdGhpcyBibG9jayBhbmQgcmVuZGVyIHRoZSBibG9jayBpZiBleHByZXNzaW9uIGlzIHRydXRoeVxuXHRcdFx0XHQvLyBPdGhlcndpc2UgcmV0dXJuIFwiXCJcblx0XHRcdFx0dmFyIHNlbGYgPSB0aGlzLFxuXHRcdFx0XHRcdHRhZ0N0eCA9IHNlbGYudGFnQ3R4LFxuXHRcdFx0XHRcdHJldCA9IChzZWxmLnJlbmRlcmluZy5kb25lIHx8ICF2YWwgJiYgKGFyZ3VtZW50cy5sZW5ndGggfHwgIXRhZ0N0eC5pbmRleCkpXG5cdFx0XHRcdFx0XHQ/IFwiXCJcblx0XHRcdFx0XHRcdDogKHNlbGYucmVuZGVyaW5nLmRvbmUgPSB0cnVlLFxuXHRcdFx0XHRcdFx0XHRzZWxmLnNlbGVjdGVkID0gdGFnQ3R4LmluZGV4LFxuXHRcdFx0XHRcdFx0XHR1bmRlZmluZWQpOyAvLyBUZXN0IGlzIHNhdGlzZmllZCwgc28gcmVuZGVyIGNvbnRlbnQgb24gY3VycmVudCBjb250ZXh0XG5cdFx0XHRcdHJldHVybiByZXQ7XG5cdFx0XHR9LFxuXHRcdFx0Y29udGVudEN0eDogdHJ1ZSwgLy8gSW5oZXJpdCBwYXJlbnQgdmlldyBkYXRhIGNvbnRleHRcblx0XHRcdGZsb3c6IHRydWVcblx0XHR9LFxuXHRcdFwiZm9yXCI6IHtcblx0XHRcdHJlbmRlcjogZnVuY3Rpb24odmFsKSB7XG5cdFx0XHRcdC8vIFRoaXMgZnVuY3Rpb24gaXMgY2FsbGVkIG9uY2UgZm9yIHt7Zm9yfX0gYW5kIG9uY2UgZm9yIGVhY2gge3tlbHNlfX0uXG5cdFx0XHRcdC8vIFdlIHdpbGwgdXNlIHRoZSB0YWcucmVuZGVyaW5nIG9iamVjdCBmb3IgY2FycnlpbmcgcmVuZGVyaW5nIHN0YXRlIGFjcm9zcyB0aGUgY2FsbHMuXG5cdFx0XHRcdHZhciBmaW5hbEVsc2UgPSAhYXJndW1lbnRzLmxlbmd0aCxcblx0XHRcdFx0XHR2YWx1ZSxcblx0XHRcdFx0XHRzZWxmID0gdGhpcyxcblx0XHRcdFx0XHR0YWdDdHggPSBzZWxmLnRhZ0N0eCxcblx0XHRcdFx0XHRyZXN1bHQgPSBcIlwiLFxuXHRcdFx0XHRcdGRvbmUgPSAwO1xuXG5cdFx0XHRcdGlmICghc2VsZi5yZW5kZXJpbmcuZG9uZSkge1xuXHRcdFx0XHRcdHZhbHVlID0gZmluYWxFbHNlID8gdGFnQ3R4LnZpZXcuZGF0YSA6IHZhbDsgLy8gRm9yIHRoZSBmaW5hbCBlbHNlLCBkZWZhdWx0cyB0byBjdXJyZW50IGRhdGEgd2l0aG91dCBpdGVyYXRpb24uXG5cdFx0XHRcdFx0aWYgKHZhbHVlICE9PSB1bmRlZmluZWQpIHtcblx0XHRcdFx0XHRcdHJlc3VsdCArPSB0YWdDdHgucmVuZGVyKHZhbHVlLCBmaW5hbEVsc2UpOyAvLyBJdGVyYXRlcyBleGNlcHQgb24gZmluYWwgZWxzZSwgaWYgZGF0YSBpcyBhbiBhcnJheS4gKFVzZSB7e2luY2x1ZGV9fSB0byBjb21wb3NlIHRlbXBsYXRlcyB3aXRob3V0IGFycmF5IGl0ZXJhdGlvbilcblx0XHRcdFx0XHRcdGRvbmUgKz0gJGlzQXJyYXkodmFsdWUpID8gdmFsdWUubGVuZ3RoIDogMTtcblx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0aWYgKHNlbGYucmVuZGVyaW5nLmRvbmUgPSBkb25lKSB7XG5cdFx0XHRcdFx0XHRzZWxmLnNlbGVjdGVkID0gdGFnQ3R4LmluZGV4O1xuXHRcdFx0XHRcdH1cblx0XHRcdFx0XHQvLyBJZiBub3RoaW5nIHdhcyByZW5kZXJlZCB3ZSB3aWxsIGxvb2sgYXQgdGhlIG5leHQge3tlbHNlfX0uIE90aGVyd2lzZSwgd2UgYXJlIGRvbmUuXG5cdFx0XHRcdH1cblx0XHRcdFx0cmV0dXJuIHJlc3VsdDtcblx0XHRcdH0sXG5cdFx0XHRmbG93OiB0cnVlXG5cdFx0fSxcblx0XHRwcm9wczoge1xuXHRcdFx0YmFzZVRhZzogXCJmb3JcIixcblx0XHRcdGRhdGFNYXA6IGRhdGFNYXAoZ2V0VGFyZ2V0UHJvcHMpLFxuXHRcdFx0ZmxvdzogdHJ1ZVxuXHRcdH0sXG5cdFx0aW5jbHVkZToge1xuXHRcdFx0ZmxvdzogdHJ1ZVxuXHRcdH0sXG5cdFx0XCIqXCI6IHtcblx0XHRcdC8vIHt7KiBjb2RlLi4uIH19IC0gSWdub3JlZCBpZiB0ZW1wbGF0ZS5hbGxvd0NvZGUgYW5kICQudmlld3Muc2V0dGluZ3MuYWxsb3dDb2RlIGFyZSBmYWxzZS4gT3RoZXJ3aXNlIGluY2x1ZGUgY29kZSBpbiBjb21waWxlZCB0ZW1wbGF0ZVxuXHRcdFx0cmVuZGVyOiByZXRWYWwsXG5cdFx0XHRmbG93OiB0cnVlXG5cdFx0fSxcblx0XHRcIjoqXCI6IHtcblx0XHRcdC8vIHt7OiogcmV0dXJuZWRFeHByZXNzaW9uIH19IC0gSWdub3JlZCBpZiB0ZW1wbGF0ZS5hbGxvd0NvZGUgYW5kICQudmlld3Muc2V0dGluZ3MuYWxsb3dDb2RlIGFyZSBmYWxzZS4gT3RoZXJ3aXNlIGluY2x1ZGUgY29kZSBpbiBjb21waWxlZCB0ZW1wbGF0ZVxuXHRcdFx0cmVuZGVyOiByZXRWYWwsXG5cdFx0XHRmbG93OiB0cnVlXG5cdFx0fSxcblx0XHRkYmc6ICRoZWxwZXJzLmRiZyA9ICRjb252ZXJ0ZXJzLmRiZyA9IGRiZ0JyZWFrIC8vIFJlZ2lzdGVyIHt7ZGJnL319LCB7e2RiZzouLi59fSBhbmQgfmRiZygpIHRvIHRocm93IGFuZCBjYXRjaCwgYXMgYnJlYWtwb2ludHMgZm9yIGRlYnVnZ2luZy5cblx0fSk7XG5cblx0JGNvbnZlcnRlcnMoe1xuXHRcdGh0bWw6IGh0bWxFbmNvZGUsXG5cdFx0YXR0cjogaHRtbEVuY29kZSwgLy8gSW5jbHVkZXMgPiBlbmNvZGluZyBzaW5jZSByQ29udmVydE1hcmtlcnMgaW4gSnNWaWV3cyBkb2VzIG5vdCBza2lwID4gY2hhcmFjdGVycyBpbiBhdHRyaWJ1dGUgc3RyaW5nc1xuXHRcdHVybDogZnVuY3Rpb24odGV4dCkge1xuXHRcdFx0Ly8gVVJMIGVuY29kaW5nIGhlbHBlci5cblx0XHRcdHJldHVybiB0ZXh0ICE9IHVuZGVmaW5lZCA/IGVuY29kZVVSSShcIlwiICsgdGV4dCkgOiB0ZXh0ID09PSBudWxsID8gdGV4dCA6IFwiXCI7IC8vIG51bGwgcmV0dXJucyBudWxsLCBlLmcuIHRvIHJlbW92ZSBhdHRyaWJ1dGUuIHVuZGVmaW5lZCByZXR1cm5zIFwiXCJcblx0XHR9XG5cdH0pO1xufVxuLy89PT09PT09PT09PT09PT09PT09PT09PT09PSBEZWZpbmUgZGVmYXVsdCBkZWxpbWl0ZXJzID09PT09PT09PT09PT09PT09PT09PT09PT09XG4kc3ViU2V0dGluZ3MgPSAkc3ViLnNldHRpbmdzO1xuJGlzQXJyYXkgPSAoJHx8anNyKS5pc0FycmF5O1xuJHZpZXdzU2V0dGluZ3MuZGVsaW1pdGVycyhcInt7XCIsIFwifX1cIiwgXCJeXCIpO1xuXG5cbmlmIChqc3JUb0pxKSB7IC8vIE1vdmluZyBmcm9tIGpzcmVuZGVyIG5hbWVzcGFjZSB0byBqUXVlcnkgbmFtZXBhY2UgLSBjb3B5IG92ZXIgdGhlIHN0b3JlZCBpdGVtcyAodGVtcGxhdGVzLCBjb252ZXJ0ZXJzLCBoZWxwZXJzLi4uKVxuXHRqc3Iudmlld3Muc3ViLl9qcSgkKTtcbn1cbnJldHVybiAkIHx8IGpzcjtcbn0sIHdpbmRvdykpO1xuIiwiLypnbG9iYWwgUVVuaXQsIHRlc3QsIGVxdWFsLCBvayovXG4oZnVuY3Rpb24odW5kZWZpbmVkKSB7XG5cInVzZSBzdHJpY3RcIjtcblxuYnJvd3NlcmlmeS5kb25lLm9uZSA9IHRydWU7XG5cblFVbml0Lm1vZHVsZShcIkJyb3dzZXJpZnkgLSBjbGllbnQgY29kZVwiKTtcblxudmFyIGlzSUU4ID0gd2luZG93LmF0dGFjaEV2ZW50ICYmICF3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcjtcblxuaWYgKCFpc0lFOCkge1xuXG50ZXN0KFwiTm8galF1ZXJ5IGdsb2JhbDogcmVxdWlyZSgnanNyZW5kZXInKSgpXCIsIGZ1bmN0aW9uKCkge1xuXHQvLyAuLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uIEhpZGUgUVVuaXQgZ2xvYmFsIGpRdWVyeSBhbmQgYW55IHByZXZpb3VzIGdsb2JhbCBqc3JlbmRlci4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLlxuXHR2YXIgalF1ZXJ5ID0gZ2xvYmFsLmpRdWVyeSwganNyID0gZ2xvYmFsLmpzcmVuZGVyO1xuXHRnbG9iYWwualF1ZXJ5ID0gZ2xvYmFsLmpzcmVuZGVyID0gdW5kZWZpbmVkO1xuXG5cdC8vID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0gQXJyYW5nZSA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09XG5cdHZhciBkYXRhID0ge25hbWU6IFwiSm9cIn07XG5cblx0Ly8gLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4gQWN0IC4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi5cblx0dmFyIGpzcmVuZGVyID0gcmVxdWlyZSgnanNyZW5kZXInKSgpOyAvLyBOb3QgcGFzc2luZyBpbiBqUXVlcnksIHNvIHJldHVybnMgdGhlIGpzcmVuZGVyIG5hbWVzcGFjZVxuXG5cdC8vIFVzZSByZXF1aXJlIHRvIGdldCBzZXJ2ZXIgdGVtcGxhdGUsIHRoYW5rcyB0byBCcm93c2VyaWZ5IGJ1bmRsZSB0aGF0IHVzZWQganNyZW5kZXIvdG1wbGlmeSB0cmFuc2Zvcm1cblx0dmFyIHRtcGwgPSByZXF1aXJlKCcuLi90ZW1wbGF0ZXMvbmFtZS10ZW1wbGF0ZS5odG1sJykoanNyZW5kZXIpOyAvLyBQcm92aWRlIGpzcmVuZGVyXG5cblx0dmFyIHJlc3VsdCA9IHRtcGwoZGF0YSk7XG5cblx0cmVzdWx0ICs9IFwiIFwiICsgKGpzcmVuZGVyICE9PSBqUXVlcnkpO1xuXG5cdC8vIC4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4gQXNzZXJ0IC4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLlxuXHRlcXVhbChyZXN1bHQsIFwiTmFtZTogSm8gKG5hbWUtdGVtcGxhdGUuaHRtbCkgdHJ1ZVwiLCBcInJlc3VsdDogTm8galF1ZXJ5IGdsb2JhbDogcmVxdWlyZSgnanNyZW5kZXInKSgpXCIpO1xuXG5cdC8vIC4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4gUmVzZXQgLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uXG5cdGdsb2JhbC5qUXVlcnkgPSBqUXVlcnk7IC8vIFJlcGxhY2UgUVVuaXQgZ2xvYmFsIGpRdWVyeVxuXHRnbG9iYWwuanNyZW5kZXIgPSBqc3I7IC8vIFJlcGxhY2UgYW55IHByZXZpb3VzIGdsb2JhbCBqc3JlbmRlclxufSk7XG5cbn1cbn0pKCk7XG4iLCJ2YXIgdG1wbFJlZnMgPSBbXSxcbiAgbWt1cCA9ICdOYW1lOiB7ezpuYW1lfX0gKG5hbWUtdGVtcGxhdGUuaHRtbCknLFxuICAkID0gZ2xvYmFsLmpzcmVuZGVyIHx8IGdsb2JhbC5qUXVlcnk7XG5cbm1vZHVsZS5leHBvcnRzID0gJCA/ICQudGVtcGxhdGVzKFwiLi90ZXN0L3RlbXBsYXRlcy9uYW1lLXRlbXBsYXRlLmh0bWxcIiwgbWt1cCkgOlxuICBmdW5jdGlvbigkKSB7XG4gICAgaWYgKCEkIHx8ICEkLnZpZXdzKSB7dGhyb3cgXCJSZXF1aXJlcyBqc3JlbmRlci9qUXVlcnlcIjt9XG4gICAgd2hpbGUgKHRtcGxSZWZzLmxlbmd0aCkge1xuICAgICAgdG1wbFJlZnMucG9wKCkoJCk7IC8vIGNvbXBpbGUgbmVzdGVkIHRlbXBsYXRlXG4gICAgfVxuXG4gICAgcmV0dXJuICQudGVtcGxhdGVzKFwiLi90ZXN0L3RlbXBsYXRlcy9uYW1lLXRlbXBsYXRlLmh0bWxcIiwgbWt1cClcbiAgfTsiXX0= +//# sourceMappingURL=data:application/json;charset:utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9icm93c2VyLXBhY2svX3ByZWx1ZGUuanMiLCJub2RlX21vZHVsZXMvanNyZW5kZXIvanNyZW5kZXIuanMiLCJ0ZXN0L2Jyb3dzZXJpZnkvMS11bml0LXRlc3RzLmpzIiwidGVzdC90ZW1wbGF0ZXMvbmFtZS10ZW1wbGF0ZS5odG1sIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0FDQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7OztBQ3h2RkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7Ozs7QUN4Q0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJmaWxlIjoiZ2VuZXJhdGVkLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXNDb250ZW50IjpbIihmdW5jdGlvbiBlKHQsbixyKXtmdW5jdGlvbiBzKG8sdSl7aWYoIW5bb10pe2lmKCF0W29dKXt2YXIgYT10eXBlb2YgcmVxdWlyZT09XCJmdW5jdGlvblwiJiZyZXF1aXJlO2lmKCF1JiZhKXJldHVybiBhKG8sITApO2lmKGkpcmV0dXJuIGkobywhMCk7dmFyIGY9bmV3IEVycm9yKFwiQ2Fubm90IGZpbmQgbW9kdWxlICdcIitvK1wiJ1wiKTt0aHJvdyBmLmNvZGU9XCJNT0RVTEVfTk9UX0ZPVU5EXCIsZn12YXIgbD1uW29dPXtleHBvcnRzOnt9fTt0W29dWzBdLmNhbGwobC5leHBvcnRzLGZ1bmN0aW9uKGUpe3ZhciBuPXRbb11bMV1bZV07cmV0dXJuIHMobj9uOmUpfSxsLGwuZXhwb3J0cyxlLHQsbixyKX1yZXR1cm4gbltvXS5leHBvcnRzfXZhciBpPXR5cGVvZiByZXF1aXJlPT1cImZ1bmN0aW9uXCImJnJlcXVpcmU7Zm9yKHZhciBvPTA7bzxyLmxlbmd0aDtvKyspcyhyW29dKTtyZXR1cm4gc30pIiwiLyohIEpzUmVuZGVyIHYwLjkuOTEgKEJldGEpOiBodHRwOi8vanN2aWV3cy5jb20vI2pzcmVuZGVyICovXG4vKiEgKipWRVJTSU9OIEZPUiBXRUIqKiAoRm9yIE5PREUuSlMgc2VlIGh0dHA6Ly9qc3ZpZXdzLmNvbS9kb3dubG9hZC9qc3JlbmRlci1ub2RlLmpzKSAqL1xuLypcbiAqIEJlc3Qtb2YtYnJlZWQgdGVtcGxhdGluZyBpbiBicm93c2VyIG9yIG9uIE5vZGUuanMuXG4gKiBEb2VzIG5vdCByZXF1aXJlIGpRdWVyeSwgb3IgSFRNTCBET01cbiAqIEludGVncmF0ZXMgd2l0aCBKc1ZpZXdzIChodHRwOi8vanN2aWV3cy5jb20vI2pzdmlld3MpXG4gKlxuICogQ29weXJpZ2h0IDIwMTgsIEJvcmlzIE1vb3JlXG4gKiBSZWxlYXNlZCB1bmRlciB0aGUgTUlUIExpY2Vuc2UuXG4gKi9cblxuLy9qc2hpbnQgLVcwMTgsIC1XMDQxLCAtVzEyMFxuXG4oZnVuY3Rpb24oZmFjdG9yeSwgZ2xvYmFsKSB7XG5cdC8vIGdsb2JhbCB2YXIgaXMgdGhlIHRoaXMgb2JqZWN0LCB3aGljaCBpcyB3aW5kb3cgd2hlbiBydW5uaW5nIGluIHRoZSB1c3VhbCBicm93c2VyIGVudmlyb25tZW50XG5cdHZhciAkID0gZ2xvYmFsLmpRdWVyeTtcblxuXHRpZiAodHlwZW9mIGV4cG9ydHMgPT09IFwib2JqZWN0XCIpIHsgLy8gQ29tbW9uSlMgZS5nLiBCcm93c2VyaWZ5XG5cdFx0bW9kdWxlLmV4cG9ydHMgPSAkXG5cdFx0XHQ/IGZhY3RvcnkoZ2xvYmFsLCAkKVxuXHRcdFx0OiBmdW5jdGlvbigkKSB7IC8vIElmIG5vIGdsb2JhbCBqUXVlcnksIHRha2Ugb3B0aW9uYWwgalF1ZXJ5IHBhc3NlZCBhcyBwYXJhbWV0ZXI6IHJlcXVpcmUoJ2pzcmVuZGVyJykoalF1ZXJ5KVxuXHRcdFx0XHRpZiAoJCAmJiAhJC5mbikge1xuXHRcdFx0XHRcdHRocm93IFwiUHJvdmlkZSBqUXVlcnkgb3IgbnVsbFwiO1xuXHRcdFx0XHR9XG5cdFx0XHRcdHJldHVybiBmYWN0b3J5KGdsb2JhbCwgJCk7XG5cdFx0XHR9O1xuXHR9IGVsc2UgaWYgKHR5cGVvZiBkZWZpbmUgPT09IFwiZnVuY3Rpb25cIiAmJiBkZWZpbmUuYW1kKSB7IC8vIEFNRCBzY3JpcHQgbG9hZGVyLCBlLmcuIFJlcXVpcmVKU1xuXHRcdGRlZmluZShmdW5jdGlvbigpIHtcblx0XHRcdHJldHVybiBmYWN0b3J5KGdsb2JhbCk7XG5cdFx0fSk7XG5cdH0gZWxzZSB7IC8vIEJyb3dzZXIgdXNpbmcgcGxhaW4gPHNjcmlwdD4gdGFnXG5cdFx0ZmFjdG9yeShnbG9iYWwsIGZhbHNlKTtcblx0fVxufSAoXG5cbi8vIGZhY3RvcnkgKGZvciBqc3JlbmRlci5qcylcbmZ1bmN0aW9uKGdsb2JhbCwgJCkge1xuXCJ1c2Ugc3RyaWN0XCI7XG5cbi8vPT09PT09PT09PT09PT09PT09PT09PT09PT0gVG9wLWxldmVsIHZhcnMgPT09PT09PT09PT09PT09PT09PT09PT09PT1cblxuLy8gZ2xvYmFsIHZhciBpcyB0aGUgdGhpcyBvYmplY3QsIHdoaWNoIGlzIHdpbmRvdyB3aGVuIHJ1bm5pbmcgaW4gdGhlIHVzdWFsIGJyb3dzZXIgZW52aXJvbm1lbnRcbnZhciBzZXRHbG9iYWxzID0gJCA9PT0gZmFsc2U7IC8vIE9ubHkgc2V0IGdsb2JhbHMgaWYgc2NyaXB0IGJsb2NrIGluIGJyb3dzZXIgKG5vdCBBTUQgYW5kIG5vdCBDb21tb25KUylcblxuJCA9ICQgJiYgJC5mbiA/ICQgOiBnbG9iYWwualF1ZXJ5OyAvLyAkIGlzIGpRdWVyeSBwYXNzZWQgaW4gYnkgQ29tbW9uSlMgbG9hZGVyIChCcm93c2VyaWZ5KSwgb3IgZ2xvYmFsIGpRdWVyeS5cblxudmFyIHZlcnNpb25OdW1iZXIgPSBcInYwLjkuOTFcIixcblx0anN2U3RvcmVOYW1lLCByVGFnLCByVG1wbFN0cmluZywgdG9wVmlldywgJHZpZXdzLFx0JGV4cGFuZG8sXG5cdF9vY3AgPSBcIl9vY3BcIiwgLy8gT2JzZXJ2YWJsZSBjb250ZXh0dWFsIHBhcmFtZXRlclxuXG4vL1RPRE9cdHRtcGxGbnNDYWNoZSA9IHt9LFxuXHQkaXNGdW5jdGlvbiwgJGlzQXJyYXksICR0ZW1wbGF0ZXMsICRjb252ZXJ0ZXJzLCAkaGVscGVycywgJHRhZ3MsICRzdWIsICRzdWJTZXR0aW5ncywgJHN1YlNldHRpbmdzQWR2YW5jZWQsICR2aWV3c1NldHRpbmdzLCBkZWxpbU9wZW5DaGFyMCwgZGVsaW1PcGVuQ2hhcjEsIGRlbGltQ2xvc2VDaGFyMCwgZGVsaW1DbG9zZUNoYXIxLCBsaW5rQ2hhciwgc2V0dGluZywgYmFzZU9uRXJyb3IsXG5cblx0clBhdGggPSAvXighKj8pKD86bnVsbHx0cnVlfGZhbHNlfFxcZFtcXGQuXSp8KFtcXHckXSt8XFwufH4oW1xcdyRdKyl8Iyh2aWV3fChbXFx3JF0rKSk/KShbXFx3JC5eXSo/KSg/OlsuW15dKFtcXHckXSspXFxdPyk/KSQvZyxcblx0Ly8gICAgICAgIG5vdCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvYmplY3QgICAgIGhlbHBlciAgICB2aWV3ICB2aWV3UHJvcGVydHkgcGF0aFRva2VucyAgICAgIGxlYWZUb2tlblxuXG5cdHJQYXJhbXMgPSAvKFxcKCkoPz1cXHMqXFwoKXwoPzooWyhbXSlcXHMqKT8oPzooXFxePykofj9bXFx3JC5eXSspP1xccyooKFxcK1xcK3wtLSl8XFwrfC18fig/IVtcXHckX10pfCYmfFxcfFxcfHw9PT18IT09fD09fCE9fDw9fD49fFs8PiUqOj9cXC9dfCg9KSlcXHMqfCghKj8oQCk/WyN+XT9bXFx3JC5eXSspKFsoW10pPyl8KCxcXHMqKXwoXFwoPylcXFxcPyg/OignKXwoXCIpKXwoPzpcXHMqKChbKVxcXV0pKD89Wy5eXXxcXHMqJHxbXihbXSl8WylcXF1dKShbKFtdPykpfChcXHMrKS9nLFxuXHQvLyAgICAgICAgIGxmdFBybjAgICAgICAgICAgbGZ0UHJuICAgICAgICBib3VuZCBwYXRoICAgICAgICAgICAgIG9wZXJhdG9yIGVyciAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZXEgICAgICBwYXRoMiBsYXRlICAgICAgICAgICAgcHJuICAgICAgY29tbWEgIGxmdFBybjIgICBhcG9zIHF1b3QgICAgICAgIHJ0UHJuICBydFBybkRvdCAgICAgICAgICAgICAgICAgIHBybjIgICAgIHNwYWNlXG5cdC8vIChsZWZ0IHBhcmVuPyBmb2xsb3dlZCBieSAocGF0aD8gZm9sbG93ZWQgYnkgb3BlcmF0b3IpIG9yIChwYXRoIGZvbGxvd2VkIGJ5IGxlZnQgcGFyZW4/KSkgb3IgY29tbWEgb3IgYXBvcyBvciBxdW90IG9yIHJpZ2h0IHBhcmVuIG9yIHNwYWNlXG5cblx0aXNSZW5kZXJDYWxsLFxuXHRyTmV3TGluZSA9IC9bIFxcdF0qKFxcclxcbnxcXG58XFxyKS9nLFxuXHRyVW5lc2NhcGVRdW90ZXMgPSAvXFxcXChbJ1wiXSkvZyxcblx0ckVzY2FwZVF1b3RlcyA9IC9bJ1wiXFxcXF0vZywgLy8gRXNjYXBlIHF1b3RlcyBhbmQgXFwgY2hhcmFjdGVyXG5cdHJCdWlsZEhhc2ggPSAvKD86XFx4MDh8Xikob25lcnJvcjopPyg/Oih+PykoKFtcXHckX1xcLl0rKTopPyhbXlxceDA4XSspKVxceDA4KCwpPyhbXlxceDA4XSspL2dpLFxuXHRyVGVzdEVsc2VJZiA9IC9eaWZcXHMvLFxuXHRyRmlyc3RFbGVtID0gLzwoXFx3KylbPlxcc10vLFxuXHRyQXR0ckVuY29kZSA9IC9bXFx4MDBgPjxcIicmPV0vZywgLy8gSW5jbHVkZXMgPiBlbmNvZGluZyBzaW5jZSByQ29udmVydE1hcmtlcnMgaW4gSnNWaWV3cyBkb2VzIG5vdCBza2lwID4gY2hhcmFjdGVycyBpbiBhdHRyaWJ1dGUgc3RyaW5nc1xuXHRySXNIdG1sID0gL1tcXHgwMGA+PFxcXCInJj1dLyxcblx0ckhhc0hhbmRsZXJzID0gL15vbltBLVpdfF5jb252ZXJ0KEJhY2spPyQvLFxuXHRyV3JhcHBlZEluVmlld01hcmtlciA9IC9eXFwjXFxkK19gW1xcc1xcU10qXFwvXFxkK19gJC8sXG5cdHJIdG1sRW5jb2RlID0gckF0dHJFbmNvZGUsXG5cdHJEYXRhRW5jb2RlID0gL1smPD5dL2csXG5cdHJEYXRhVW5lbmNvZGUgPSAvJihhbXB8Z3R8bHQpOy9nLFxuXHRyQnJhY2tldFF1b3RlID0gL1xcW1snXCJdP3xbJ1wiXT9cXF0vZyxcblx0dmlld0lkID0gMCxcblx0Y2hhckVudGl0aWVzID0ge1xuXHRcdFwiJlwiOiBcIiZhbXA7XCIsXG5cdFx0XCI8XCI6IFwiJmx0O1wiLFxuXHRcdFwiPlwiOiBcIiZndDtcIixcblx0XHRcIlxceDAwXCI6IFwiJiMwO1wiLFxuXHRcdFwiJ1wiOiBcIiYjMzk7XCIsXG5cdFx0J1wiJzogXCImIzM0O1wiLFxuXHRcdFwiYFwiOiBcIiYjOTY7XCIsXG5cdFx0XCI9XCI6IFwiJiM2MTtcIlxuXHR9LFxuXHRjaGFyc0Zyb21FbnRpdGllcyAgPSB7XG5cdFx0YW1wOiBcIiZcIixcblx0XHRndDogXCI+XCIsXG5cdFx0bHQ6IFwiPFwiXG5cdH0sXG5cdEhUTUwgPSBcImh0bWxcIixcblx0T0JKRUNUID0gXCJvYmplY3RcIixcblx0dG1wbEF0dHIgPSBcImRhdGEtanN2LXRtcGxcIixcblx0anN2VG1wbCA9IFwianN2VG1wbFwiLFxuXHRpbmRleFN0ciA9IFwiRm9yICNpbmRleCBpbiBuZXN0ZWQgYmxvY2sgdXNlICNnZXRJbmRleCgpLlwiLFxuXHQkcmVuZGVyID0ge30sXG5cblx0anNyID0gZ2xvYmFsLmpzcmVuZGVyLFxuXHRqc3JUb0pxID0ganNyICYmICQgJiYgISQucmVuZGVyLCAvLyBKc1JlbmRlciBhbHJlYWR5IGxvYWRlZCwgd2l0aG91dCBqUXVlcnkuIGJ1dCB3ZSB3aWxsIHJlLWxvYWQgaXQgbm93IHRvIGF0dGFjaCB0byBqUXVlcnlcblxuXHRqc3ZTdG9yZXMgPSB7XG5cdFx0dGVtcGxhdGU6IHtcblx0XHRcdGNvbXBpbGU6IGNvbXBpbGVUbXBsXG5cdFx0fSxcblx0XHR0YWc6IHtcblx0XHRcdGNvbXBpbGU6IGNvbXBpbGVUYWdcblx0XHR9LFxuXHRcdHZpZXdNb2RlbDoge1xuXHRcdFx0Y29tcGlsZTogY29tcGlsZVZpZXdNb2RlbFxuXHRcdH0sXG5cdFx0aGVscGVyOiB7fSxcblx0XHRjb252ZXJ0ZXI6IHt9XG5cdH07XG5cblx0Ly8gdmlld3Mgb2JqZWN0ICgkLnZpZXdzIGlmIGpRdWVyeSBpcyBsb2FkZWQsIGpzcmVuZGVyLnZpZXdzIGlmIG5vIGpRdWVyeSwgZS5nLiBpbiBOb2RlLmpzKVxuXHQkdmlld3MgPSB7XG5cdFx0anN2aWV3czogdmVyc2lvbk51bWJlcixcblx0XHRzdWI6IHtcblx0XHRcdC8vIHN1YnNjcmlwdGlvbiwgZS5nLiBKc1ZpZXdzIGludGVncmF0aW9uXG5cdFx0XHRWaWV3OiBWaWV3LFxuXHRcdFx0RXJyOiBKc1ZpZXdzRXJyb3IsXG5cdFx0XHR0bXBsRm46IHRtcGxGbixcblx0XHRcdHBhcnNlOiBwYXJzZVBhcmFtcyxcblx0XHRcdGV4dGVuZDogJGV4dGVuZCxcblx0XHRcdGV4dGVuZEN0eDogZXh0ZW5kQ3R4LFxuXHRcdFx0c3ludGF4RXJyOiBzeW50YXhFcnJvcixcblx0XHRcdG9uU3RvcmU6IHtcblx0XHRcdFx0dGVtcGxhdGU6IGZ1bmN0aW9uKG5hbWUsIGl0ZW0pIHtcblx0XHRcdFx0XHRpZiAoaXRlbSA9PT0gbnVsbCkge1xuXHRcdFx0XHRcdFx0ZGVsZXRlICRyZW5kZXJbbmFtZV07XG5cdFx0XHRcdFx0fSBlbHNlIHtcblx0XHRcdFx0XHRcdCRyZW5kZXJbbmFtZV0gPSBpdGVtO1xuXHRcdFx0XHRcdH1cblx0XHRcdFx0fVxuXHRcdFx0fSxcblx0XHRcdGFkZFNldHRpbmc6IGFkZFNldHRpbmcsXG5cdFx0XHRzZXR0aW5nczoge1xuXHRcdFx0XHRhbGxvd0NvZGU6IGZhbHNlXG5cdFx0XHR9LFxuXHRcdFx0YWR2U2V0OiBub29wLCAvLyBVcGRhdGUgYWR2YW5jZWQgc2V0dGluZ3Ncblx0XHRcdF90aHA6IHRhZ0hhbmRsZXJzRnJvbVByb3BzLFxuXHRcdFx0X2dtOiBnZXRNZXRob2QsXG5cdFx0XHRfdGc6IGZ1bmN0aW9uKCkge30sIC8vIENvbnN0cnVjdG9yIGZvciB0YWdEZWZcblx0XHRcdF9jbnZ0OiBjb252ZXJ0VmFsLFxuXHRcdFx0X3RhZzogcmVuZGVyVGFnLFxuXHRcdFx0X2VyOiBlcnJvcixcblx0XHRcdF9lcnI6IG9uUmVuZGVyRXJyb3IsXG5cdFx0XHRfY3A6IHJldFZhbCwgLy8gR2V0IG9ic2VydmFibGUgY29udGV4dHVhbCBwYXJhbWV0ZXJzIChvciBwcm9wZXJ0aWVzKSB+Zm9vPWV4cHIuIEluIEpzUmVuZGVyLCBzaW1wbHkgcmV0dXJucyB2YWwuXG5cdFx0XHRfc3E6IGZ1bmN0aW9uKHRva2VuKSB7XG5cdFx0XHRcdGlmICh0b2tlbiA9PT0gXCJjb25zdHJ1Y3RvclwiKSB7XG5cdFx0XHRcdFx0c3ludGF4RXJyb3IoXCJcIik7XG5cdFx0XHRcdH1cblx0XHRcdFx0cmV0dXJuIHRva2VuO1xuXHRcdFx0fVxuXHRcdH0sXG5cdFx0c2V0dGluZ3M6IHtcblx0XHRcdGRlbGltaXRlcnM6ICR2aWV3c0RlbGltaXRlcnMsXG5cdFx0XHRhZHZhbmNlZDogZnVuY3Rpb24odmFsdWUpIHtcblx0XHRcdFx0cmV0dXJuIHZhbHVlXG5cdFx0XHRcdFx0PyAoXG5cdFx0XHRcdFx0XHRcdCRleHRlbmQoJHN1YlNldHRpbmdzQWR2YW5jZWQsIHZhbHVlKSxcblx0XHRcdFx0XHRcdFx0JHN1Yi5hZHZTZXQoKSxcblx0XHRcdFx0XHRcdFx0JHZpZXdzU2V0dGluZ3Ncblx0XHRcdFx0XHRcdClcblx0XHRcdFx0XHRcdDogJHN1YlNldHRpbmdzQWR2YW5jZWQ7XG5cdFx0XHRcdH1cblx0XHR9LFxuXHRcdG1hcDogZGF0YU1hcCAgICAvLyBJZiBqc09ic2VydmFibGUgbG9hZGVkIGZpcnN0LCB1c2UgdGhhdCBkZWZpbml0aW9uIG9mIGRhdGFNYXBcblx0fTtcblxuZnVuY3Rpb24gZ2V0RGVyaXZlZE1ldGhvZChiYXNlTWV0aG9kLCBtZXRob2QpIHtcblx0cmV0dXJuIGZ1bmN0aW9uKCkge1xuXHRcdHZhciByZXQsXG5cdFx0XHR0YWcgPSB0aGlzLFxuXHRcdFx0cHJldkJhc2UgPSB0YWcuYmFzZTtcblxuXHRcdHRhZy5iYXNlID0gYmFzZU1ldGhvZDsgLy8gV2l0aGluIG1ldGhvZCBjYWxsLCBjYWxsaW5nIHRoaXMuYmFzZSB3aWxsIGNhbGwgdGhlIGJhc2UgbWV0aG9kXG5cdFx0cmV0ID0gbWV0aG9kLmFwcGx5KHRhZywgYXJndW1lbnRzKTsgLy8gQ2FsbCB0aGUgbWV0aG9kXG5cdFx0dGFnLmJhc2UgPSBwcmV2QmFzZTsgLy8gUmVwbGFjZSB0aGlzLmJhc2UgdG8gYmUgdGhlIGJhc2UgbWV0aG9kIG9mIHRoZSBwcmV2aW91cyBjYWxsLCBmb3IgY2hhaW5lZCBjYWxsc1xuXHRcdHJldHVybiByZXQ7XG5cdH07XG59XG5cbmZ1bmN0aW9uIGdldE1ldGhvZChiYXNlTWV0aG9kLCBtZXRob2QpIHtcblx0Ly8gRm9yIGRlcml2ZWQgbWV0aG9kcyAob3IgaGFuZGxlcnMgZGVjbGFyZWQgZGVjbGFyYXRpdmVseSBhcyBpbiB7ezpmb28gb25DaGFuZ2U9fmZvb0NoYW5nZWR9fSByZXBsYWNlIGJ5IGEgZGVyaXZlZCBtZXRob2QsIHRvIGFsbG93IHVzaW5nIHRoaXMuYmFzZSguLi4pXG5cdC8vIG9yIHRoaXMuYmFzZUFwcGx5KGFyZ3VtZW50cykgdG8gY2FsbCB0aGUgYmFzZSBpbXBsZW1lbnRhdGlvbi4gKEVxdWl2YWxlbnQgdG8gdGhpcy5fc3VwZXIoLi4uKSBhbmQgdGhpcy5fc3VwZXJBcHBseShhcmd1bWVudHMpIGluIGpRdWVyeSBVSSlcblx0aWYgKCRpc0Z1bmN0aW9uKG1ldGhvZCkpIHtcblx0XHRtZXRob2QgPSBnZXREZXJpdmVkTWV0aG9kKFxuXHRcdFx0XHQhYmFzZU1ldGhvZFxuXHRcdFx0XHRcdD8gbm9vcCAvLyBubyBiYXNlIG1ldGhvZCBpbXBsZW1lbnRhdGlvbiwgc28gdXNlIG5vb3AgYXMgYmFzZSBtZXRob2Rcblx0XHRcdFx0XHQ6IGJhc2VNZXRob2QuX2Rcblx0XHRcdFx0XHRcdD8gYmFzZU1ldGhvZCAvLyBiYXNlTWV0aG9kIGlzIGEgZGVyaXZlZCBtZXRob2QsIHNvIHVzZSBpdFxuXHRcdFx0XHRcdFx0OiBnZXREZXJpdmVkTWV0aG9kKG5vb3AsIGJhc2VNZXRob2QpLCAvLyBiYXNlTWV0aG9kIGlzIG5vdCBkZXJpdmVkIHNvIG1ha2UgaXRzIGJhc2UgbWV0aG9kIGJlIHRoZSBub29wIG1ldGhvZFxuXHRcdFx0XHRtZXRob2Rcblx0XHRcdCk7XG5cdFx0bWV0aG9kLl9kID0gKGJhc2VNZXRob2QgJiYgYmFzZU1ldGhvZC5fZCB8fCAwKSArIDE7IC8vIEFkZCBmbGFnIGZvciBkZXJpdmVkIG1ldGhvZCAoaW5jcmVtZW50ZWQgZm9yIGRlcml2ZWQgb2YgZGVyaXZlZC4uLilcblx0fVxuXHRyZXR1cm4gbWV0aG9kO1xufVxuXG5mdW5jdGlvbiB0YWdIYW5kbGVyc0Zyb21Qcm9wcyh0YWcsIHRhZ0N0eCkge1xuXHR2YXIgcHJvcCxcblx0XHRwcm9wcyA9IHRhZ0N0eC5wcm9wcztcblx0Zm9yIChwcm9wIGluIHByb3BzKSB7XG5cdFx0aWYgKHJIYXNIYW5kbGVycy50ZXN0KHByb3ApICYmICEodGFnW3Byb3BdICYmIHRhZ1twcm9wXS5maXgpKSB7IC8vIERvbid0IG92ZXJyaWRlIGhhbmRsZXJzIHdpdGggZml4IGV4cGFuZG8gKHVzZWQgaW4gZGF0ZXBpY2tlciBhbmQgc3Bpbm5lcilcblx0XHRcdHRhZ1twcm9wXSA9IHByb3AgIT09IFwiY29udmVydFwiID8gZ2V0TWV0aG9kKHRhZy5jb25zdHJ1Y3Rvci5wcm90b3R5cGVbcHJvcF0sIHByb3BzW3Byb3BdKSA6IHByb3BzW3Byb3BdO1xuXHRcdFx0Ly8gQ29weSBvdmVyIHRoZSBvbkZvbyBwcm9wcywgY29udmVydCBhbmQgY29udmVydEJhY2sgZnJvbSB0YWdDdHgucHJvcHMgdG8gdGFnIChvdmVycmlkZXMgdmFsdWVzIGluIHRhZ0RlZikuXG5cdFx0XHQvLyBOb3RlOiB1bnN1cHBvcnRlZCBzY2VuYXJpbzogaWYgaGFuZGxlcnMgYXJlIGR5bmFtaWNhbGx5IGFkZGVkIF5vbkZvbz1leHByZXNzaW9uIHRoaXMgd2lsbCB3b3JrLCBidXQgZHluYW1pY2FsbHkgcmVtb3Zpbmcgd2lsbCBub3Qgd29yay5cblx0XHR9XG5cdH1cbn1cblxuZnVuY3Rpb24gcmV0VmFsKHZhbCkge1xuXHRyZXR1cm4gdmFsO1xufVxuXG5mdW5jdGlvbiBub29wKCkge1xuXHRyZXR1cm4gXCJcIjtcbn1cblxuZnVuY3Rpb24gZGJnQnJlYWsodmFsKSB7XG5cdC8vIFVzYWdlIGV4YW1wbGVzOiB7e2RiZzouLi59fSwge3s6fmRiZyguLi4pfX0sIHt7ZGJnIC4uLi99fSwge157Zm9yIC4uLiBvbkFmdGVyTGluaz1+ZGJnfX0gZXRjLlxuXHR0cnkge1xuXHRcdGNvbnNvbGUubG9nKFwiSnNSZW5kZXIgZGJnIGJyZWFrcG9pbnQ6IFwiICsgdmFsKTtcblx0XHR0aHJvdyBcImRiZyBicmVha3BvaW50XCI7IC8vIFRvIGJyZWFrIGhlcmUsIHN0b3Agb24gY2F1Z2h0IGV4Y2VwdGlvbnMuXG5cdH1cblx0Y2F0Y2ggKGUpIHt9XG5cdHJldHVybiB0aGlzLmJhc2UgPyB0aGlzLmJhc2VBcHBseShhcmd1bWVudHMpIDogdmFsO1xufVxuXG5mdW5jdGlvbiBKc1ZpZXdzRXJyb3IobWVzc2FnZSkge1xuXHQvLyBFcnJvciBleGNlcHRpb24gdHlwZSBmb3IgSnNWaWV3cy9Kc1JlbmRlclxuXHQvLyBPdmVycmlkZSBvZiAkLnZpZXdzLnN1Yi5FcnJvciBpcyBwb3NzaWJsZVxuXHR0aGlzLm5hbWUgPSAoJC5saW5rID8gXCJKc1ZpZXdzXCIgOiBcIkpzUmVuZGVyXCIpICsgXCIgRXJyb3JcIjtcblx0dGhpcy5tZXNzYWdlID0gbWVzc2FnZSB8fCB0aGlzLm5hbWU7XG59XG5cbmZ1bmN0aW9uICRleHRlbmQodGFyZ2V0LCBzb3VyY2UpIHtcblx0aWYgKHRhcmdldCkge1xuXHRcdGZvciAodmFyIG5hbWUgaW4gc291cmNlKSB7XG5cdFx0XHR0YXJnZXRbbmFtZV0gPSBzb3VyY2VbbmFtZV07XG5cdFx0fVxuXHRcdHJldHVybiB0YXJnZXQ7XG5cdH1cbn1cblxuKEpzVmlld3NFcnJvci5wcm90b3R5cGUgPSBuZXcgRXJyb3IoKSkuY29uc3RydWN0b3IgPSBKc1ZpZXdzRXJyb3I7XG5cbi8vPT09PT09PT09PT09PT09PT09PT09PT09PT0gVG9wLWxldmVsIGZ1bmN0aW9ucyA9PT09PT09PT09PT09PT09PT09PT09PT09PVxuXG4vLz09PT09PT09PT09PT09PT09PT1cbi8vIHZpZXdzLmRlbGltaXRlcnNcbi8vPT09PT09PT09PT09PT09PT09PVxuXG5mdW5jdGlvbiAkdmlld3NEZWxpbWl0ZXJzKG9wZW5DaGFycywgY2xvc2VDaGFycywgbGluaykge1xuXHQvLyBTZXQgdGhlIHRhZyBvcGVuaW5nIGFuZCBjbG9zaW5nIGRlbGltaXRlcnMgYW5kICdsaW5rJyBjaGFyYWN0ZXIuIERlZmF1bHQgaXMgXCJ7e1wiLCBcIn19XCIgYW5kIFwiXlwiXG5cdC8vIG9wZW5DaGFycywgY2xvc2VDaGFyczogb3BlbmluZyBhbmQgY2xvc2luZyBzdHJpbmdzLCBlYWNoIHdpdGggdHdvIGNoYXJhY3RlcnNcblx0aWYgKCFvcGVuQ2hhcnMpIHtcblx0XHRyZXR1cm4gJHN1YlNldHRpbmdzLmRlbGltaXRlcnM7XG5cdH1cblx0aWYgKCRpc0FycmF5KG9wZW5DaGFycykpIHtcblx0XHRyZXR1cm4gJHZpZXdzRGVsaW1pdGVycy5hcHBseSgkdmlld3MsIG9wZW5DaGFycyk7XG5cdH1cblxuXHQkc3ViU2V0dGluZ3MuZGVsaW1pdGVycyA9IFtvcGVuQ2hhcnMsIGNsb3NlQ2hhcnMsIGxpbmtDaGFyID0gbGluayA/IGxpbmsuY2hhckF0KDApIDogbGlua0NoYXJdO1xuXG5cdGRlbGltT3BlbkNoYXIwID0gb3BlbkNoYXJzLmNoYXJBdCgwKTsgLy8gRXNjYXBlIHRoZSBjaGFyYWN0ZXJzIC0gc2luY2UgdGhleSBjb3VsZCBiZSByZWdleCBzcGVjaWFsIGNoYXJhY3RlcnNcblx0ZGVsaW1PcGVuQ2hhcjEgPSBvcGVuQ2hhcnMuY2hhckF0KDEpO1xuXHRkZWxpbUNsb3NlQ2hhcjAgPSBjbG9zZUNoYXJzLmNoYXJBdCgwKTtcblx0ZGVsaW1DbG9zZUNoYXIxID0gY2xvc2VDaGFycy5jaGFyQXQoMSk7XG5cdG9wZW5DaGFycyA9IFwiXFxcXFwiICsgZGVsaW1PcGVuQ2hhcjAgKyBcIihcXFxcXCIgKyBsaW5rQ2hhciArIFwiKT9cXFxcXCIgKyBkZWxpbU9wZW5DaGFyMTsgLy8gRGVmYXVsdCBpcyBcIntee1wiXG5cdGNsb3NlQ2hhcnMgPSBcIlxcXFxcIiArIGRlbGltQ2xvc2VDaGFyMCArIFwiXFxcXFwiICsgZGVsaW1DbG9zZUNoYXIxOyAgICAgICAgICAgICAgICAgICAvLyBEZWZhdWx0IGlzIFwifX1cIlxuXHQvLyBCdWlsZCByZWdleCB3aXRoIG5ldyBkZWxpbWl0ZXJzXG5cdC8vICAgICAgICAgIFt0YWcgICAgKGZvbGxvd2VkIGJ5IC8gc3BhY2Ugb3IgfSkgIG9yIGN2dHIrY29sb24gb3IgaHRtbCBvciBjb2RlXSBmb2xsb3dlZCBieSBzcGFjZStwYXJhbXMgdGhlbiBjb252ZXJ0QmFjaz9cblx0clRhZyA9IFwiKD86KFxcXFx3Kyg/PVtcXFxcL1xcXFxzXFxcXFwiICsgZGVsaW1DbG9zZUNoYXIwICsgXCJdKSl8KFxcXFx3Kyk/KDopfCg+KXwoXFxcXCopKVxcXFxzKigoPzpbXlxcXFxcIlxuXHRcdCsgZGVsaW1DbG9zZUNoYXIwICsgXCJdfFxcXFxcIiArIGRlbGltQ2xvc2VDaGFyMCArIFwiKD8hXFxcXFwiICsgZGVsaW1DbG9zZUNoYXIxICsgXCIpKSo/KVwiO1xuXG5cdC8vIE1ha2UgclRhZyBhdmFpbGFibGUgdG8gSnNWaWV3cyAob3Igb3RoZXIgY29tcG9uZW50cykgZm9yIHBhcnNpbmcgYmluZGluZyBleHByZXNzaW9uc1xuXHQkc3ViLnJUYWcgPSBcIig/OlwiICsgclRhZyArIFwiKVwiO1xuXHQvLyAgICAgICAgICAgICAgICAgICAgICAgIHsgXj8geyAgIHRhZytwYXJhbXMgc2xhc2g/ICBvciBjbG9zaW5nVGFnICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3IgY29tbWVudFxuXHRyVGFnID0gbmV3IFJlZ0V4cChcIig/OlwiICsgb3BlbkNoYXJzICsgclRhZyArIFwiKFxcXFwvKT98XFxcXFwiICsgZGVsaW1PcGVuQ2hhcjAgKyBcIihcXFxcXCIgKyBsaW5rQ2hhciArIFwiKT9cXFxcXCIgKyBkZWxpbU9wZW5DaGFyMSArIFwiKD86KD86XFxcXC8oXFxcXHcrKSlcXFxccyp8IS0tW1xcXFxzXFxcXFNdKj8tLSkpXCIgKyBjbG9zZUNoYXJzLCBcImdcIik7XG5cblx0Ly8gRGVmYXVsdDogIGJpbmQgICAgIHRhZ05hbWUgICAgICAgICBjdnQgICBjbG4gaHRtbCBjb2RlICAgIHBhcmFtcyAgICAgICAgICAgIHNsYXNoICAgYmluZDIgICAgICAgICBjbG9zZUJsayAgY29tbWVudFxuXHQvLyAgICAgIC8oPzp7KFxcXik/eyg/OihcXHcrKD89W1xcL1xcc31dKSl8KFxcdyspPyg6KXwoPil8KFxcKikpXFxzKigoPzpbXn1dfH0oPyF9KSkqPykoXFwvKT98eyhcXF4pP3soPzooPzpcXC8oXFx3KykpXFxzKnwhLS1bXFxzXFxTXSo/LS0pKX19XG5cblx0JHN1Yi5yVG1wbCA9IG5ldyBSZWdFeHAoXCJeXFxcXHN8XFxcXHMkfDwuKj58KFteXFxcXFxcXFxdfF4pW3t9XXxcIiArIG9wZW5DaGFycyArIFwiLipcIiArIGNsb3NlQ2hhcnMpO1xuXHQvLyAkc3ViLnJUbXBsIGxvb2tzIGZvciBpbml0aWFsIG9yIGZpbmFsIHdoaXRlIHNwYWNlLCBodG1sIHRhZ3Mgb3IgeyBvciB9IGNoYXIgbm90IHByZWNlZGVkIGJ5IFxcXFwsIG9yIEpzUmVuZGVyIHRhZ3Mge3t4eHh9fS5cblx0Ly8gRWFjaCBvZiB0aGVzZSBzdHJpbmdzIGFyZSBjb25zaWRlcmVkIE5PVCB0byBiZSBqUXVlcnkgc2VsZWN0b3JzXG5cdHJldHVybiAkdmlld3NTZXR0aW5ncztcbn1cblxuLy89PT09PT09PT1cbi8vIFZpZXcuZ2V0XG4vLz09PT09PT09PVxuXG5mdW5jdGlvbiBnZXRWaWV3KGlubmVyLCB0eXBlKSB7IC8vdmlldy5nZXQoaW5uZXIsIHR5cGUpXG5cdGlmICghdHlwZSAmJiBpbm5lciAhPT0gdHJ1ZSkge1xuXHRcdC8vIHZpZXcuZ2V0KHR5cGUpXG5cdFx0dHlwZSA9IGlubmVyO1xuXHRcdGlubmVyID0gdW5kZWZpbmVkO1xuXHR9XG5cblx0dmFyIHZpZXdzLCBpLCBsLCBmb3VuZCxcblx0XHR2aWV3ID0gdGhpcyxcblx0XHRyb290ID0gIXR5cGUgfHwgdHlwZSA9PT0gXCJyb290XCI7XG5cdFx0Ly8gSWYgdHlwZSBpcyB1bmRlZmluZWQsIHJldHVybnMgcm9vdCB2aWV3ICh2aWV3IHVuZGVyIHRvcCB2aWV3KS5cblxuXHRpZiAoaW5uZXIpIHtcblx0XHQvLyBHbyB0aHJvdWdoIHZpZXdzIC0gdGhpcyBvbmUsIGFuZCBhbGwgbmVzdGVkIG9uZXMsIGRlcHRoLWZpcnN0IC0gYW5kIHJldHVybiBmaXJzdCBvbmUgd2l0aCBnaXZlbiB0eXBlLlxuXHRcdC8vIElmIHR5cGUgaXMgdW5kZWZpbmVkLCBpLmUuIHZpZXcuZ2V0KHRydWUpLCByZXR1cm4gZmlyc3QgY2hpbGQgdmlldy5cblx0XHRmb3VuZCA9IHR5cGUgJiYgdmlldy50eXBlID09PSB0eXBlICYmIHZpZXc7XG5cdFx0aWYgKCFmb3VuZCkge1xuXHRcdFx0dmlld3MgPSB2aWV3LnZpZXdzO1xuXHRcdFx0aWYgKHZpZXcuXy51c2VLZXkpIHtcblx0XHRcdFx0Zm9yIChpIGluIHZpZXdzKSB7XG5cdFx0XHRcdFx0aWYgKGZvdW5kID0gdHlwZSA/IHZpZXdzW2ldLmdldChpbm5lciwgdHlwZSkgOiB2aWV3c1tpXSkge1xuXHRcdFx0XHRcdFx0YnJlYWs7XG5cdFx0XHRcdFx0fVxuXHRcdFx0XHR9XG5cdFx0XHR9IGVsc2Uge1xuXHRcdFx0XHRmb3IgKGkgPSAwLCBsID0gdmlld3MubGVuZ3RoOyAhZm91bmQgJiYgaSA8IGw7IGkrKykge1xuXHRcdFx0XHRcdGZvdW5kID0gdHlwZSA/IHZpZXdzW2ldLmdldChpbm5lciwgdHlwZSkgOiB2aWV3c1tpXTtcblx0XHRcdFx0fVxuXHRcdFx0fVxuXHRcdH1cblx0fSBlbHNlIGlmIChyb290KSB7XG5cdFx0Ly8gRmluZCByb290IHZpZXcuICh2aWV3IHdob3NlIHBhcmVudCBpcyB0b3Agdmlldylcblx0XHRmb3VuZCA9IHZpZXcucm9vdDtcblx0fSBlbHNlIHtcblx0XHR3aGlsZSAodmlldyAmJiAhZm91bmQpIHtcblx0XHRcdC8vIEdvIHRocm91Z2ggdmlld3MgLSB0aGlzIG9uZSwgYW5kIGFsbCBwYXJlbnQgb25lcyAtIGFuZCByZXR1cm4gZmlyc3Qgb25lIHdpdGggZ2l2ZW4gdHlwZS5cblx0XHRcdGZvdW5kID0gdmlldy50eXBlID09PSB0eXBlID8gdmlldyA6IHVuZGVmaW5lZDtcblx0XHRcdHZpZXcgPSB2aWV3LnBhcmVudDtcblx0XHR9XG5cdH1cblx0cmV0dXJuIGZvdW5kO1xufVxuXG5mdW5jdGlvbiBnZXROZXN0ZWRJbmRleCgpIHtcblx0dmFyIHZpZXcgPSB0aGlzLmdldChcIml0ZW1cIik7XG5cdHJldHVybiB2aWV3ID8gdmlldy5pbmRleCA6IHVuZGVmaW5lZDtcbn1cblxuZ2V0TmVzdGVkSW5kZXguZGVwZW5kcyA9IGZ1bmN0aW9uKCkge1xuXHRyZXR1cm4gW3RoaXMuZ2V0KFwiaXRlbVwiKSwgXCJpbmRleFwiXTtcbn07XG5cbmZ1bmN0aW9uIGdldEluZGV4KCkge1xuXHRyZXR1cm4gdGhpcy5pbmRleDtcbn1cblxuZ2V0SW5kZXguZGVwZW5kcyA9IFwiaW5kZXhcIjtcblxuLy89PT09PT09PT09XG4vLyBWaWV3LmhscFxuLy89PT09PT09PT09XG5cbmZ1bmN0aW9uIGdldFBhdGhPYmplY3Qob2IsIHBhdGgsIGx0T2IsIGZuKSB7IC8vIEl0ZXJhdGUgdGhyb3VnaCBwYXRoIHRvIGxhdGUgcGF0aHM6IEBhLmIuYyBwYXRoc1xuXHQvLyBSZXR1cm4gXCJcIiAob3Igbm9vcCBpZiBsZWFmIGlzIGEgZnVuY3Rpb24gQGEuYi5jKC4uLikgKSBpZiBpbnRlcm1lZGlhdGUgb2JqZWN0IG5vdCB5ZXQgYXZhaWxhYmxlXG5cdHZhciBwcmV2T2IsIHRva2VucywgbCxcblx0XHRpID0gMDtcblx0aWYgKGx0T2IgPT09IDEpIHtcblx0XHRmbiA9IDE7XG5cdFx0bHRPYiA9IHVuZGVmaW5lZDtcblx0fVxuXHQvLyBQYXRocyBsaWtlIF5hXmJeYyBvciB+XmFeYl5jIHdpbGwgbm90IHRocm93IGlmIGFuIG9iamVjdCBpbiBwYXRoIGlzIHVuZGVmaW5lZC5cblx0aWYgKHBhdGgpIHtcblx0XHR0b2tlbnMgPSBwYXRoLnNwbGl0KFwiLlwiKTtcblx0XHRsID0gdG9rZW5zLmxlbmd0aDtcblxuXHRcdGZvciAoOyBvYiAmJiBpIDwgbDsgaSsrKSB7XG5cdFx0XHRwcmV2T2IgPSBvYjtcblx0XHRcdG9iID0gdG9rZW5zW2ldID8gb2JbdG9rZW5zW2ldXSA6IG9iO1xuXHRcdH1cblx0fVxuXHRpZiAobHRPYikge1xuXHRcdGx0T2IubHQgPSBsdE9iLmx0IHx8IGk8bDsgLy8gSWYgaSA8IGwgdGhlcmUgd2FzIGFuIG9iamVjdCBpbiB0aGUgcGF0aCBub3QgeWV0IGF2YWlsYWJsZVxuXHR9XG5cdHJldHVybiBvYiA9PT0gdW5kZWZpbmVkXG5cdFx0PyBmbiA/IG5vb3AgOiBcIlwiXG5cdFx0OiBmbiA/IGZ1bmN0aW9uKCkge1xuXHRcdFx0cmV0dXJuIG9iLmFwcGx5KHByZXZPYiwgYXJndW1lbnRzKTtcblx0XHR9IDogb2I7XG59XG5cbmZ1bmN0aW9uIGNvbnRleHRQYXJhbWV0ZXIoa2V5LCB2YWx1ZSwgZ2V0KSB7XG5cdC8vIEhlbHBlciBtZXRob2QgY2FsbGVkIGFzIHZpZXcuY3R4UHJtKGtleSkgZm9yIGhlbHBlcnMgb3IgdGVtcGxhdGUgcGFyYW1ldGVycyB+Zm9vIC0gZnJvbSBjb21waWxlZCB0ZW1wbGF0ZSBvciBmcm9tIGNvbnRleHQgY2FsbGJhY2tcblx0dmFyIHdyYXBwZWQsIGRlcHMsIHJlcywgb2JzQ3R4UHJtLCB0YWdFbHNlLCBjYWxsVmlldywgbmV3UmVzLFxuXHRcdHN0b3JlVmlldyA9IHRoaXMsXG5cdFx0aXNVcGRhdGUgPSAhaXNSZW5kZXJDYWxsICYmIGFyZ3VtZW50cy5sZW5ndGggPiAxLFxuXHRcdHN0b3JlID0gc3RvcmVWaWV3LmN0eDtcblxuXHRpZiAoa2V5KSB7XG5cdFx0aWYgKCFzdG9yZVZpZXcuXykgeyAvLyB0YWdDdHguY3R4UHJtKCkgY2FsbFxuXHRcdFx0dGFnRWxzZSA9IHN0b3JlVmlldy5pbmRleDtcblx0XHRcdHN0b3JlVmlldyA9IHN0b3JlVmlldy50YWc7XG5cdFx0fVxuXHRcdGNhbGxWaWV3ID0gc3RvcmVWaWV3O1xuXHRcdGlmIChzdG9yZSAmJiBzdG9yZS5oYXNPd25Qcm9wZXJ0eShrZXkpIHx8IChzdG9yZSA9ICRoZWxwZXJzKS5oYXNPd25Qcm9wZXJ0eShrZXkpKSB7XG5cdFx0XHRyZXMgPSBzdG9yZVtrZXldO1xuXHRcdFx0aWYgKGtleSA9PT0gXCJ0YWdcIiB8fCBrZXkgPT09IFwidGFnQ3R4XCIgfHwga2V5ID09PSBcInJvb3RcIiB8fCBrZXkgPT09IFwicGFyZW50VGFnc1wiIHx8IHN0b3JlVmlldy5fLml0ID09PSBrZXkgKSB7XG5cdFx0XHRcdHJldHVybiByZXM7XG5cdFx0XHR9XG5cdFx0fSBlbHNlIHtcblx0XHRcdHN0b3JlID0gdW5kZWZpbmVkO1xuXHRcdH1cblx0XHRpZiAoc3RvcmVWaWV3LnRhZ0N0eCB8fCBzdG9yZVZpZXcubGlua2VkKSB7IC8vIERhdGEtbGlua2VkIHZpZXcsIG9yIHRhZyBpbnN0YW5jZVxuXHRcdFx0aWYgKCFyZXMgfHwgIXJlcy5fY3hwKSB7XG5cdFx0XHRcdC8vIE5vdCBhIGNvbnRleHR1YWwgcGFyYW1ldGVyXG5cdFx0XHRcdC8vIFNldCBzdG9yZVZpZXcgdG8gdGFnIChpZiB0aGlzIGlzIGEgdGFnLmN0eFBybSgpIGNhbGwpIG9yIHRvIHJvb3QgdmlldyAoXCJkYXRhXCIgdmlldyBvZiBsaW5rZWQgdGVtcGxhdGUpXG5cdFx0XHRcdHN0b3JlVmlldyA9IHN0b3JlVmlldy50YWdDdHggfHwgJGlzRnVuY3Rpb24ocmVzKVxuXHRcdFx0XHRcdD8gc3RvcmVWaWV3IC8vIElzIGEgdGFnLCBub3QgYSB2aWV3LCBvciBpcyBhIGNvbXB1dGVkIGNvbnRleHR1YWwgcGFyYW1ldGVyLCBzbyBzY29wZSB0byB0aGUgY2FsbFZpZXcsIG5vIHRoZSAnc2NvcGUgdmlldydcblx0XHRcdFx0XHQ6IChzdG9yZVZpZXcgPSBzdG9yZVZpZXcuc2NvcGUgfHwgc3RvcmVWaWV3LFxuXHRcdFx0XHRcdFx0IXN0b3JlVmlldy5pc1RvcCAmJiBzdG9yZVZpZXcuY3R4LnRhZyAvLyBJZiB0aGlzIHZpZXcgaXMgaW4gYSB0YWcsIHNldCBzdG9yZVZpZXcgdG8gdGhlIHRhZ1xuXHRcdFx0XHRcdFx0XHR8fCBzdG9yZVZpZXcpO1xuXHRcdFx0XHRpZiAocmVzICE9PSB1bmRlZmluZWQgJiYgc3RvcmVWaWV3LnRhZ0N0eCkge1xuXHRcdFx0XHRcdC8vIElmIHN0b3JlVmlldyBpcyBhIHRhZywgYnV0IHRoZSBjb250ZXh0dWFsIHBhcmFtZXRlciBoYXMgYmVlbiBzZXQgYXQgYXQgaGlnaGVyIGxldmVsIChlLmcuIGhlbHBlcnMpLi4uXG5cdFx0XHRcdFx0c3RvcmVWaWV3ID0gc3RvcmVWaWV3LnRhZ0N0eC52aWV3LnNjb3BlOyAvLyAgdGhlbiBtb3ZlIHN0b3JlVmlldyB0byB0aGUgb3V0ZXIgbGV2ZWwgKHNjb3BlIG9mIHRhZyBjb250YWluZXIgdmlldylcblx0XHRcdFx0fVxuXHRcdFx0XHRzdG9yZSA9IHN0b3JlVmlldy5fb2Nwcztcblx0XHRcdFx0cmVzID0gc3RvcmUgJiYgc3RvcmUuaGFzT3duUHJvcGVydHkoa2V5KSAmJiBzdG9yZVtrZXldIHx8IHJlcztcblx0XHRcdFx0aWYgKCEocmVzICYmIHJlcy5fY3hwKSAmJiAoZ2V0IHx8IGlzVXBkYXRlKSkge1xuXHRcdFx0XHRcdC8vIENyZWF0ZSBvYnNlcnZhYmxlIGNvbnRleHR1YWwgcGFyYW1ldGVyXG5cdFx0XHRcdFx0KHN0b3JlIHx8IChzdG9yZVZpZXcuX29jcHMgPSBzdG9yZVZpZXcuX29jcHMgfHwge30pKVtrZXldXG5cdFx0XHRcdFx0XHQ9IHJlc1xuXHRcdFx0XHRcdFx0PSBbe1xuXHRcdFx0XHRcdFx0XHRfb2NwOiByZXMsIC8vIFRoZSBvYnNlcnZhYmxlIGNvbnRleHR1YWwgcGFyYW1ldGVyIHZhbHVlXG5cdFx0XHRcdFx0XHRcdF92dzogY2FsbFZpZXcsXG5cdFx0XHRcdFx0XHRcdF9rZXk6IGtleVxuXHRcdFx0XHRcdFx0fV07XG5cdFx0XHRcdFx0cmVzLl9jeHAgPSB7XG5cdFx0XHRcdFx0XHRwYXRoOiBfb2NwLFxuXHRcdFx0XHRcdFx0aW5kOiAwLFxuXHRcdFx0XHRcdFx0dXBkYXRlVmFsdWU6IGZ1bmN0aW9uKHZhbCwgcGF0aCkge1xuXHRcdFx0XHRcdFx0XHQkLm9ic2VydmFibGUocmVzWzBdKS5zZXRQcm9wZXJ0eShfb2NwLCB2YWwpOyAvLyBTZXQgdGhlIHZhbHVlIChyZXNbMF0uX29jcClcblx0XHRcdFx0XHRcdFx0cmV0dXJuIHRoaXM7XG5cdFx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0fTtcblx0XHRcdFx0fVxuXHRcdFx0fVxuXHRcdFx0aWYgKG9ic0N0eFBybSA9IHJlcyAmJiByZXMuX2N4cCkge1xuXHRcdFx0XHQvLyBJZiB0aGlzIGhlbHBlciByZXNvdXJjZSBpcyBhbiBvYnNlcnZhYmxlIGNvbnRleHR1YWwgcGFyYW1ldGVyXG5cdFx0XHRcdGlmIChhcmd1bWVudHMubGVuZ3RoID4gMikge1xuXHRcdFx0XHRcdGRlcHMgPSByZXNbMV0gPyAkc3ViLl9jZW8ocmVzWzFdLmRlcHMpIDogW19vY3BdOyAvLyBmbiBkZXBzICh3aXRoIGFueSBleHByT2JzIGNsb25lZCB1c2luZyAkc3ViLl9jZW8pXG5cdFx0XHRcdFx0ZGVwcy51bnNoaWZ0KHJlc1swXSk7IC8vIHZpZXdcblx0XHRcdFx0XHRkZXBzLl9jeHAgPSBvYnNDdHhQcm07XG5cdFx0XHRcdFx0Ly8gSW4gYSBjb250ZXh0IGNhbGxiYWNrIGZvciBhIGNvbnRleHR1YWwgcGFyYW0sIHdlIHNldCBnZXQgPSB0cnVlLCB0byBnZXQgY3R4UHJtICBbdmlldywgZGVwZW5kZW5jaWVzLi4uXSBhcnJheSAtIG5lZWRlZCBmb3Igb2JzZXJ2ZSBjYWxsXG5cdFx0XHRcdFx0cmV0dXJuIGRlcHM7XG5cdFx0XHRcdH1cblx0XHRcdFx0dGFnRWxzZSA9IG9ic0N0eFBybS50YWdFbHNlO1xuXHRcdFx0XHRuZXdSZXMgPSByZXNbMV0gLy8gbGlua0ZuIGZvciBjb21waWxlZCBleHByZXNzaW9uXG5cdFx0XHRcdFx0PyBvYnNDdHhQcm0udGFnICYmIG9ic0N0eFBybS50YWcuY3Z0QXJnc1xuXHRcdFx0XHRcdFx0PyBvYnNDdHhQcm0udGFnLmN2dEFyZ3MoMSwgdGFnRWxzZSlbb2JzQ3R4UHJtLmluZF0gLy8gPSB0YWcuYm5kQXJncygpIC0gZm9yIHRhZyBjb250ZXh0dWFsIHBhcmFtZXRlclxuXHRcdFx0XHRcdFx0OiByZXNbMV0ocmVzWzBdLmRhdGEsIHJlc1swXSwgJHN1YikgICAgLy8gPSBmbihkYXRhLCB2aWV3LCAkc3ViKSBmb3IgY29tcGlsZWQgYmluZGluZyBleHByZXNzaW9uXG5cdFx0XHRcdFx0OiByZXNbMF0uX29jcDsgLy8gT2JzZXJ2YWJsZSBjb250ZXh0dWFsIHBhcmFtZXRlciAodW5pbml0aWFsaXplZCwgb3IgaW5pdGlhbGl6ZWQgYXMgc3RhdGljIGV4cHJlc3Npb24sIHNvIG5vIHBhdGggZGVwZW5kZW5jaWVzKVxuXHRcdFx0XHRpZiAoaXNVcGRhdGUpIHtcblx0XHRcdFx0XHRpZiAocmVzICYmIG5ld1JlcyAhPT0gdmFsdWUpIHtcblx0XHRcdFx0XHRcdCRzdWIuX3VjcChrZXksIHZhbHVlLCBzdG9yZVZpZXcsIG9ic0N0eFBybSk7IC8vIFVwZGF0ZSBvYnNlcnZhYmxlIGNvbnRleHR1YWwgcGFyYW1ldGVyXG5cdFx0XHRcdFx0fVxuXHRcdFx0XHRcdHJldHVybiBzdG9yZVZpZXc7XG5cdFx0XHRcdH1cblx0XHRcdFx0cmVzID0gbmV3UmVzO1xuXHRcdFx0fVxuXHRcdH1cblx0XHRpZiAocmVzICYmICRpc0Z1bmN0aW9uKHJlcykpIHtcblx0XHRcdC8vIElmIGEgaGVscGVyIGlzIG9mIHR5cGUgZnVuY3Rpb24gd2Ugd2lsbCB3cmFwIGl0LCBzbyBpZiBjYWxsZWQgd2l0aCBubyB0aGlzIHBvaW50ZXIgaXQgd2lsbCBiZSBjYWxsZWQgd2l0aCB0aGVcblx0XHRcdC8vIHZpZXcgYXMgJ3RoaXMnIGNvbnRleHQuIElmIHRoZSBoZWxwZXIgfmZvbygpIHdhcyBpbiBhIGRhdGEtbGluayBleHByZXNzaW9uLCB0aGUgdmlldyB3aWxsIGhhdmUgYSAndGVtcG9yYXJ5JyBsaW5rQ3R4IHByb3BlcnR5IHRvby5cblx0XHRcdC8vIE5vdGUgdGhhdCBoZWxwZXIgZnVuY3Rpb25zIG9uIGRlZXBlciBwYXRocyB3aWxsIGhhdmUgc3BlY2lmaWMgdGhpcyBwb2ludGVycywgZnJvbSB0aGUgcHJlY2VkaW5nIHBhdGguXG5cdFx0XHQvLyBGb3IgZXhhbXBsZSwgfnV0aWwuZm9vKCkgd2lsbCBoYXZlIHRoZSB+dXRpbCBvYmplY3QgYXMgJ3RoaXMnIHBvaW50ZXJcblx0XHRcdHdyYXBwZWQgPSBmdW5jdGlvbigpIHtcblx0XHRcdFx0cmV0dXJuIHJlcy5hcHBseSgoIXRoaXMgfHwgdGhpcyA9PT0gZ2xvYmFsKSA/IGNhbGxWaWV3IDogdGhpcywgYXJndW1lbnRzKTtcblx0XHRcdH07XG5cdFx0XHQkZXh0ZW5kKHdyYXBwZWQsIHJlcyk7IC8vIEF0dGFjaCBzYW1lIGV4cGFuZG9zIChpZiBhbnkpIHRvIHRoZSB3cmFwcGVkIGZ1bmN0aW9uXG5cdFx0XHR3cmFwcGVkLl92dyA9IGNhbGxWaWV3O1xuXHRcdH1cblx0XHRyZXR1cm4gd3JhcHBlZCB8fCByZXM7XG5cdH1cbn1cblxuZnVuY3Rpb24gZ2V0VGVtcGxhdGUodG1wbCkge1xuXHRyZXR1cm4gdG1wbCAmJiAodG1wbC5mblxuXHRcdD8gdG1wbFxuXHRcdDogdGhpcy5nZXRSc2MoXCJ0ZW1wbGF0ZXNcIiwgdG1wbCkgfHwgJHRlbXBsYXRlcyh0bXBsKSk7IC8vIG5vdCB5ZXQgY29tcGlsZWRcbn1cblxuLy89PT09PT09PT09PT09PVxuLy8gdmlld3MuX2NudnRcbi8vPT09PT09PT09PT09PT1cblxuZnVuY3Rpb24gY29udmVydFZhbChjb252ZXJ0ZXIsIHZpZXcsIHRhZ0N0eCwgb25FcnJvcikge1xuXHQvLyBDYWxsZWQgZnJvbSBjb21waWxlZCB0ZW1wbGF0ZSBjb2RlIGZvciB7ezp9fVxuXHQvLyBzZWxmIGlzIHRlbXBsYXRlIG9iamVjdCBvciBsaW5rQ3R4IG9iamVjdFxuXHR2YXIgdGFnLCB2YWx1ZSwgYXJnc0xlbiwgYmluZFRvLFxuXHRcdC8vIElmIHRhZ0N0eCBpcyBhbiBpbnRlZ2VyLCB0aGVuIGl0IGlzIHRoZSBrZXkgZm9yIHRoZSBjb21waWxlZCBmdW5jdGlvbiB0byByZXR1cm4gdGhlIGJvdW5kVGFnIHRhZ0N0eFxuXHRcdGJvdW5kVGFnID0gdHlwZW9mIHRhZ0N0eCA9PT0gXCJudW1iZXJcIiAmJiB2aWV3LnRtcGwuYm5kc1t0YWdDdHgtMV0sXG5cdFx0bGlua0N0eCA9IHZpZXcubGlua0N0eDsgLy8gRm9yIGRhdGEtbGluaz1cIntjdnQ6Li4ufVwiLi4uXG5cblx0aWYgKG9uRXJyb3IgPT09IHVuZGVmaW5lZCAmJiBib3VuZFRhZyAmJiBib3VuZFRhZy5fbHIpIHsgLy8gbGF0ZVJlbmRlclxuXHRcdG9uRXJyb3IgPSBcIlwiO1xuXHR9XG5cdGlmIChvbkVycm9yICE9PSB1bmRlZmluZWQpIHtcblx0XHR0YWdDdHggPSBvbkVycm9yID0ge3Byb3BzOiB7fSwgYXJnczogW29uRXJyb3JdfTtcblx0fSBlbHNlIGlmIChib3VuZFRhZykge1xuXHRcdHRhZ0N0eCA9IGJvdW5kVGFnKHZpZXcuZGF0YSwgdmlldywgJHN1Yik7XG5cdH1cblx0Ym91bmRUYWcgPSBib3VuZFRhZy5fYmQgJiYgYm91bmRUYWc7XG5cdGlmIChjb252ZXJ0ZXIgfHwgYm91bmRUYWcpIHtcblx0XHR0YWcgPSBsaW5rQ3R4ICYmIGxpbmtDdHgudGFnO1xuXHRcdHRhZ0N0eC52aWV3ID0gdmlldztcblx0XHRpZiAoIXRhZykge1xuXHRcdFx0dGFnID0gJGV4dGVuZChuZXcgJHN1Yi5fdGcoKSwge1xuXHRcdFx0XHRfOiB7XG5cdFx0XHRcdFx0Ym5kOiBib3VuZFRhZyxcblx0XHRcdFx0XHR1bmxpbmtlZDogdHJ1ZSxcblx0XHRcdFx0XHRsdDogdGFnQ3R4Lmx0IC8vIElmIGEgbGF0ZSBwYXRoIEBzb21lLnBhdGggaGFzIG5vdCByZXR1cm5lZCBAc29tZSBvYmplY3QsIG1hcmsgdGFnIGFzIGxhdGVcblx0XHRcdFx0fSxcblx0XHRcdFx0aW5saW5lOiAhbGlua0N0eCxcblx0XHRcdFx0dGFnTmFtZTogXCI6XCIsXG5cdFx0XHRcdGNvbnZlcnQ6IGNvbnZlcnRlcixcblx0XHRcdFx0ZmxvdzogdHJ1ZSxcblx0XHRcdFx0dGFnQ3R4OiB0YWdDdHgsXG5cdFx0XHRcdHRhZ0N0eHM6IFt0YWdDdHhdLFxuXHRcdFx0XHRfaXM6IFwidGFnXCJcblx0XHRcdH0pO1xuXHRcdFx0YXJnc0xlbiA9IHRhZ0N0eC5hcmdzLmxlbmd0aDtcblx0XHRcdGlmIChhcmdzTGVuPjEpIHtcblx0XHRcdFx0YmluZFRvID0gdGFnLmJpbmRUbyA9IFtdO1xuXHRcdFx0XHR3aGlsZSAoYXJnc0xlbi0tKSB7XG5cdFx0XHRcdFx0YmluZFRvLnVuc2hpZnQoYXJnc0xlbik7IC8vIEJpbmQgdG8gYWxsIHRoZSBhcmd1bWVudHMgLSBnZW5lcmF0ZSBiaW5kVG8gYXJyYXk6IFswLDEsMi4uLl1cblx0XHRcdFx0fVxuXHRcdFx0fVxuXHRcdFx0aWYgKGxpbmtDdHgpIHtcblx0XHRcdFx0bGlua0N0eC50YWcgPSB0YWc7XG5cdFx0XHRcdHRhZy5saW5rQ3R4ID0gbGlua0N0eDtcblx0XHRcdH1cblx0XHRcdHRhZ0N0eC5jdHggPSBleHRlbmRDdHgodGFnQ3R4LmN0eCwgKGxpbmtDdHggPyBsaW5rQ3R4LnZpZXcgOiB2aWV3KS5jdHgpO1xuXHRcdFx0dGFnSGFuZGxlcnNGcm9tUHJvcHModGFnLCB0YWdDdHgpO1xuXHRcdH1cblx0XHR0YWcuX2VyID0gb25FcnJvciAmJiB2YWx1ZTtcblx0XHR0YWcuY3R4ID0gdGFnQ3R4LmN0eCB8fCB0YWcuY3R4IHx8IHt9O1xuXHRcdHRhZ0N0eC5jdHggPSB1bmRlZmluZWQ7XG5cdFx0dmFsdWUgPSB0YWcuY3Z0QXJncygpWzBdOyAvLyBJZiB0aGVyZSBpcyBhIGNvbnZlcnRCYWNrIGJ1dCBubyBjb252ZXJ0LCBjb252ZXJ0ZXIgd2lsbCBiZSBcInRydWVcIlxuXHRcdHRhZy5fZXIgPSBvbkVycm9yICYmIHZhbHVlO1xuXHR9IGVsc2Uge1xuXHRcdHZhbHVlID0gdGFnQ3R4LmFyZ3NbMF07XG5cdH1cblxuXHQvLyBDYWxsIG9uUmVuZGVyICh1c2VkIGJ5IEpzVmlld3MgaWYgcHJlc2VudCwgdG8gYWRkIGJpbmRpbmcgYW5ub3RhdGlvbnMgYXJvdW5kIHJlbmRlcmVkIGNvbnRlbnQpXG5cdHZhbHVlID0gYm91bmRUYWcgJiYgdmlldy5fLm9uUmVuZGVyXG5cdFx0PyB2aWV3Ll8ub25SZW5kZXIodmFsdWUsIHZpZXcsIHRhZylcblx0XHQ6IHZhbHVlO1xuXHRyZXR1cm4gdmFsdWUgIT0gdW5kZWZpbmVkID8gdmFsdWUgOiBcIlwiO1xufVxuXG5mdW5jdGlvbiBjb252ZXJ0QXJncyhib3VuZCwgdGFnRWxzZSkgeyAvLyB0YWcuY3Z0QXJncygpIG9yIHRhZy5jdnRBcmdzKHRydWVPckZhbHNlLCB0YWdFbHNlKVxuXHR2YXIgbCwga2V5LCBib3VuZEFyZ3MsIGFyZ3MsIGJpbmRGcm9tLCB0YWcsIGNvbnZlcnRlcixcblx0XHR0YWdDdHggPSB0aGlzO1xuXG5cdGlmICh0YWdDdHgudGFnTmFtZSkge1xuXHRcdHRhZyA9IHRhZ0N0eDtcblx0XHR0YWdDdHggPSB0YWcudGFnQ3R4cyA/IHRhZy50YWdDdHhzW3RhZ0Vsc2UgfHwgMF0gOiB0YWcudGFnQ3R4O1xuXHR9IGVsc2Uge1xuXHRcdHRhZyA9IHRhZ0N0eC50YWc7XG5cdH1cblxuXHRiaW5kRnJvbSA9IHRhZy5iaW5kRnJvbTtcblx0YXJncyA9IHRhZ0N0eC5hcmdzO1xuXG5cdGlmICgoY29udmVydGVyID0gdGFnLmNvbnZlcnQpICYmIFwiXCIgKyBjb252ZXJ0ZXIgPT09IGNvbnZlcnRlcikge1xuXHRcdGNvbnZlcnRlciA9IGNvbnZlcnRlciA9PT0gXCJ0cnVlXCJcblx0XHRcdD8gdW5kZWZpbmVkXG5cdFx0XHQ6ICh0YWdDdHgudmlldy5nZXRSc2MoXCJjb252ZXJ0ZXJzXCIsIGNvbnZlcnRlcikgfHwgZXJyb3IoXCJVbmtub3duIGNvbnZlcnRlcjogJ1wiICsgY29udmVydGVyICsgXCInXCIpKTtcblx0fVxuXG5cdGlmIChjb252ZXJ0ZXIgJiYgIWJvdW5kKSB7IC8vIElmIHRoZXJlIGlzIGEgY29udmVydGVyLCB1c2UgYSBjb3B5IG9mIHRoZSB0YWdDdHguYXJncyBhcnJheSBmb3IgcmVuZGVyaW5nLCBhbmQgcmVwbGFjZSB0aGUgYXJnc1swXSBpblxuXHRcdGFyZ3MgPSBhcmdzLnNsaWNlKCk7IC8vIHRoZSBjb3BpZWQgYXJyYXkgd2l0aCB0aGUgY29udmVydGVkIHZhbHVlLiBCdXQgd2UgZG8gbm90IG1vZGlmeSB0aGUgdmFsdWUgb2YgdGFnLnRhZ0N0eC5hcmdzWzBdICh0aGUgb3JpZ2luYWwgYXJncyBhcnJheSlcblx0fVxuXHRpZiAoYmluZEZyb20pIHsgLy8gR2V0IHRoZSB2YWx1ZXMgb2YgdGhlIGJvdW5kQXJnc1xuXHRcdGJvdW5kQXJncyA9IFtdO1xuXHRcdGwgPSBiaW5kRnJvbS5sZW5ndGg7XG5cdFx0d2hpbGUgKGwtLSkge1xuXHRcdFx0a2V5ID0gYmluZEZyb21bbF07XG5cdFx0XHRib3VuZEFyZ3MudW5zaGlmdChhcmdPclByb3AodGFnQ3R4LCBrZXkpKTtcblx0XHR9XG5cdFx0aWYgKGJvdW5kKSB7XG5cdFx0XHRhcmdzID0gYm91bmRBcmdzOyAvLyBDYWxsIHRvIGJuZEFyZ3MoKSAtIHJldHVybnMgdGhlIGJvdW5kQXJnc1xuXHRcdH1cblx0fVxuXHRpZiAoY29udmVydGVyKSB7XG5cdFx0Y29udmVydGVyID0gY29udmVydGVyLmFwcGx5KHRhZywgYm91bmRBcmdzIHx8IGFyZ3MpO1xuXHRcdGlmIChjb252ZXJ0ZXIgPT09IHVuZGVmaW5lZCkge1xuXHRcdFx0cmV0dXJuIGFyZ3M7IC8vIFJldHVybmluZyB1bmRlZmluZWQgZnJvbSBhIGNvbnZlcnRlciBpcyBlcXVpdmFsZW50IHRvIG5vdCBoYXZpbmcgYSBjb252ZXJ0ZXIuXG5cdFx0fVxuXHRcdGJpbmRGcm9tID0gYmluZEZyb20gfHwgWzBdO1xuXHRcdGwgPSBiaW5kRnJvbS5sZW5ndGg7XG5cdFx0aWYgKCEkaXNBcnJheShjb252ZXJ0ZXIpIHx8IGNvbnZlcnRlci5sZW5ndGggIT09IGwpIHtcblx0XHRcdGNvbnZlcnRlciA9IFtjb252ZXJ0ZXJdO1xuXHRcdFx0YmluZEZyb20gPSBbMF07XG5cdFx0XHRsID0gMTtcblx0XHR9XG5cdFx0aWYgKGJvdW5kKSB7ICAgICAgICAvLyBDYWxsIHRvIGJuZEFyZ3MoKSAtIHNvIGFwcGx5IGNvbnZlcnRlciB0byBhbGwgYm91bmRBcmdzXG5cdFx0XHRhcmdzID0gY29udmVydGVyOyAvLyBUaGUgYXJyYXkgb2YgdmFsdWVzIHJldHVybmVkIGZyb20gdGhlIGNvbnZlcnRlclxuXHRcdH0gZWxzZSB7ICAgICAgICAgICAgLy8gQ2FsbCB0byBjdnRBcmdzKClcblx0XHRcdHdoaWxlIChsLS0pIHtcblx0XHRcdFx0a2V5ID0gYmluZEZyb21bbF07XG5cdFx0XHRcdGlmICgra2V5ID09PSBrZXkpIHtcblx0XHRcdFx0XHRhcmdzW2tleV0gPSBjb252ZXJ0ZXJbbF07XG5cdFx0XHRcdH1cblx0XHRcdH1cblx0XHR9XG5cdH1cblx0cmV0dXJuIGFyZ3M7XG59XG5cbmZ1bmN0aW9uIGFyZ09yUHJvcChjb250ZXh0LCBrZXkpIHtcblx0Y29udGV4dCA9IGNvbnRleHRbK2tleSA9PT0ga2V5ID8gXCJhcmdzXCIgOiBcInByb3BzXCJdO1xuXHRyZXR1cm4gY29udGV4dCAmJiBjb250ZXh0W2tleV07XG59XG5cbmZ1bmN0aW9uIGNvbnZlcnRCb3VuZEFyZ3ModGFnRWxzZSkgeyAvLyB0YWcuYm5kQXJncygpXG5cdHJldHVybiB0aGlzLmN2dEFyZ3MoMSwgdGFnRWxzZSk7XG59XG5cbi8vPT09PT09PT09PT09PVxuLy8gdmlld3MuX3RhZ1xuLy89PT09PT09PT09PT09XG5cbmZ1bmN0aW9uIGdldFJlc291cmNlKHJlc291cmNlVHlwZSwgaXRlbU5hbWUpIHtcblx0dmFyIHJlcywgc3RvcmUsXG5cdFx0dmlldyA9IHRoaXM7XG5cdGlmIChcIlwiICsgaXRlbU5hbWUgPT09IGl0ZW1OYW1lKSB7XG5cdFx0d2hpbGUgKChyZXMgPT09IHVuZGVmaW5lZCkgJiYgdmlldykge1xuXHRcdFx0c3RvcmUgPSB2aWV3LnRtcGwgJiYgdmlldy50bXBsW3Jlc291cmNlVHlwZV07XG5cdFx0XHRyZXMgPSBzdG9yZSAmJiBzdG9yZVtpdGVtTmFtZV07XG5cdFx0XHR2aWV3ID0gdmlldy5wYXJlbnQ7XG5cdFx0fVxuXHRcdHJldHVybiByZXMgfHwgJHZpZXdzW3Jlc291cmNlVHlwZV1baXRlbU5hbWVdO1xuXHR9XG59XG5cbmZ1bmN0aW9uIHJlbmRlclRhZyh0YWdOYW1lLCBwYXJlbnRWaWV3LCB0bXBsLCB0YWdDdHhzLCBpc1VwZGF0ZSwgb25FcnJvcikge1xuXHRmdW5jdGlvbiBiaW5kVG9PckJpbmRGcm9tKHR5cGUpIHtcblx0XHR2YXIgYmluZEFycmF5ID0gdGFnW3R5cGVdO1xuXG5cdFx0aWYgKGJpbmRBcnJheSAhPT0gdW5kZWZpbmVkKSB7XG5cdFx0XHRiaW5kQXJyYXkgPSAkaXNBcnJheShiaW5kQXJyYXkpID8gYmluZEFycmF5IDogW2JpbmRBcnJheV07XG5cdFx0XHRtID0gYmluZEFycmF5Lmxlbmd0aDtcblx0XHRcdHdoaWxlIChtLS0pIHtcblx0XHRcdFx0a2V5ID0gYmluZEFycmF5W21dO1xuXHRcdFx0XHRpZiAoIWlzTmFOKHBhcnNlSW50KGtleSkpKSB7XG5cdFx0XHRcdFx0YmluZEFycmF5W21dID0gcGFyc2VJbnQoa2V5KTsgLy8gQ29udmVydCBcIjBcIiB0byAwLCAgZXRjLlxuXHRcdFx0XHR9XG5cdFx0XHR9XG5cdFx0fVxuXG5cdFx0cmV0dXJuIGJpbmRBcnJheSB8fCBbMF07XG5cdH1cblxuXHRwYXJlbnRWaWV3ID0gcGFyZW50VmlldyB8fCB0b3BWaWV3O1xuXHR2YXIgdGFnLCB0YWdEZWYsIHRlbXBsYXRlLCB0YWdzLCBhdHRyLCBwYXJlbnRUYWcsIGwsIG0sIG4sIGl0ZW1SZXQsIHRhZ0N0eCwgdGFnQ3R4Q3R4LCBjdHhQcm0sIGJpbmRUbywgYmluZEZyb20sIGluaXRWYWwsXG5cdFx0Y29udGVudCwgY2FsbEluaXQsIG1hcERlZiwgdGhpc01hcCwgYXJncywgYmRBcmdzLCBwcm9wcywgdGFnRGF0YU1hcCwgY29udGVudEN0eCwga2V5LCBiaW5kRnJvbUxlbmd0aCwgYmluZFRvTGVuZ3RoLCBsaW5rZWRFbGVtZW50LCBkZWZhdWx0Q3R4LFxuXHRcdGkgPSAwLFxuXHRcdHJldCA9IFwiXCIsXG5cdFx0bGlua0N0eCA9IHBhcmVudFZpZXcubGlua0N0eCB8fCAwLFxuXHRcdGN0eCA9IHBhcmVudFZpZXcuY3R4LFxuXHRcdHBhcmVudFRtcGwgPSB0bXBsIHx8IHBhcmVudFZpZXcudG1wbCxcblx0XHQvLyBJZiB0YWdDdHhzIGlzIGFuIGludGVnZXIsIHRoZW4gaXQgaXMgdGhlIGtleSBmb3IgdGhlIGNvbXBpbGVkIGZ1bmN0aW9uIHRvIHJldHVybiB0aGUgYm91bmRUYWcgdGFnQ3R4c1xuXHRcdGJvdW5kVGFnID0gdHlwZW9mIHRhZ0N0eHMgPT09IFwibnVtYmVyXCIgJiYgcGFyZW50Vmlldy50bXBsLmJuZHNbdGFnQ3R4cy0xXTtcblxuXHRpZiAodGFnTmFtZS5faXMgPT09IFwidGFnXCIpIHtcblx0XHR0YWcgPSB0YWdOYW1lO1xuXHRcdHRhZ05hbWUgPSB0YWcudGFnTmFtZTtcblx0XHR0YWdDdHhzID0gdGFnLnRhZ0N0eHM7XG5cdFx0dGVtcGxhdGUgPSB0YWcudGVtcGxhdGU7XG5cdH0gZWxzZSB7XG5cdFx0dGFnRGVmID0gcGFyZW50Vmlldy5nZXRSc2MoXCJ0YWdzXCIsIHRhZ05hbWUpIHx8IGVycm9yKFwiVW5rbm93biB0YWc6IHt7XCIgKyB0YWdOYW1lICsgXCJ9fSBcIik7XG5cdFx0dGVtcGxhdGUgPSB0YWdEZWYudGVtcGxhdGU7XG5cdH1cblx0aWYgKG9uRXJyb3IgPT09IHVuZGVmaW5lZCAmJiBib3VuZFRhZyAmJiAoYm91bmRUYWcuX2xyID0gKHRhZ0RlZi5sYXRlUmVuZGVyICYmIGJvdW5kVGFnLl9sciE9PSBmYWxzZSB8fCBib3VuZFRhZy5fbHIpKSkge1xuXHRcdG9uRXJyb3IgPSBcIlwiOyAvLyBJZiBsYXRlUmVuZGVyLCBzZXQgdGVtcG9yYXJ5IG9uRXJyb3IsIHRvIHNraXAgaW5pdGlhbCByZW5kZXJpbmcgKGFuZCByZW5kZXIganVzdCBcIlwiKVxuXHR9XG5cdGlmIChvbkVycm9yICE9PSB1bmRlZmluZWQpIHtcblx0XHRyZXQgKz0gb25FcnJvcjtcblx0XHR0YWdDdHhzID0gb25FcnJvciA9IFt7cHJvcHM6IHt9LCBhcmdzOiBbXSwgcGFyYW1zOiB7cHJvcHM6e319fV07XG5cdH0gZWxzZSBpZiAoYm91bmRUYWcpIHtcblx0XHR0YWdDdHhzID0gYm91bmRUYWcocGFyZW50Vmlldy5kYXRhLCBwYXJlbnRWaWV3LCAkc3ViKTtcblx0fVxuXG5cdGwgPSB0YWdDdHhzLmxlbmd0aDtcblx0Zm9yICg7IGkgPCBsOyBpKyspIHtcblx0XHR0YWdDdHggPSB0YWdDdHhzW2ldO1xuXHRcdGNvbnRlbnQgPSB0YWdDdHgudG1wbDtcblx0XHRpZiAoIWxpbmtDdHggfHwgIWxpbmtDdHgudGFnIHx8IGkgJiYgIWxpbmtDdHgudGFnLmlubGluZSB8fCB0YWcuX2VyIHx8IGNvbnRlbnQgJiYgK2NvbnRlbnQ9PT1jb250ZW50KSB7XG5cdFx0XHQvLyBJbml0aWFsaXplIHRhZ0N0eFxuXHRcdFx0Ly8gRm9yIGJsb2NrIHRhZ3MsIHRhZ0N0eC50bXBsIGlzIGFuIGludGVnZXIgPiAwXG5cdFx0XHRpZiAoY29udGVudCAmJiBwYXJlbnRUbXBsLnRtcGxzKSB7XG5cdFx0XHRcdHRhZ0N0eC50bXBsID0gdGFnQ3R4LmNvbnRlbnQgPSBwYXJlbnRUbXBsLnRtcGxzW2NvbnRlbnQgLSAxXTsgLy8gU2V0IHRoZSB0bXBsIHByb3BlcnR5IHRvIHRoZSBjb250ZW50IG9mIHRoZSBibG9jayB0YWdcblx0XHRcdH1cblx0XHRcdHRhZ0N0eC5pbmRleCA9IGk7XG5cdFx0XHR0YWdDdHguY3R4UHJtID0gY29udGV4dFBhcmFtZXRlcjtcblx0XHRcdHRhZ0N0eC5yZW5kZXIgPSByZW5kZXJDb250ZW50O1xuXHRcdFx0dGFnQ3R4LmN2dEFyZ3MgPSBjb252ZXJ0QXJncztcblx0XHRcdHRhZ0N0eC5ibmRBcmdzID0gY29udmVydEJvdW5kQXJncztcblx0XHRcdHRhZ0N0eC52aWV3ID0gcGFyZW50Vmlldztcblx0XHRcdHRhZ0N0eC5jdHggPSBleHRlbmRDdHgoZXh0ZW5kQ3R4KHRhZ0N0eC5jdHgsIHRhZ0RlZiAmJiB0YWdEZWYuY3R4KSwgY3R4KTsgLy8gQ2xvbmUgYW5kIGV4dGVuZCBwYXJlbnRWaWV3LmN0eFxuXHRcdH1cblx0XHRpZiAodG1wbCA9IHRhZ0N0eC5wcm9wcy50bXBsKSB7XG5cdFx0XHQvLyBJZiB0aGUgdG1wbCBwcm9wZXJ0eSBpcyBvdmVycmlkZGVuLCBzZXQgdGhlIHZhbHVlICh3aGVuIGluaXRpYWxpemluZywgb3IsIGluIGNhc2Ugb2YgYmluZGluZzogXnRtcGw9Li4uLCB3aGVuIHVwZGF0aW5nKVxuXHRcdFx0dGFnQ3R4LnRtcGwgPSBwYXJlbnRWaWV3LmdldFRtcGwodG1wbCk7XG5cdFx0XHR0YWdDdHguY29udGVudCA9IHRhZ0N0eC5jb250ZW50IHx8IHRhZ0N0eC50bXBsO1xuXHRcdH1cblxuXHRcdGlmICghdGFnKSB7XG5cdFx0XHQvLyBUaGlzIHdpbGwgb25seSBiZSBoaXQgZm9yIGluaXRpYWwgdGFnQ3R4IChub3QgZm9yIHt7ZWxzZX19KSAtIGlmIHRoZSB0YWcgaW5zdGFuY2UgZG9lcyBub3QgZXhpc3QgeWV0XG5cdFx0XHQvLyBJZiB0aGUgdGFnIGhhcyBub3QgYWxyZWFkeSBiZWVuIGluc3RhbnRpYXRlZCwgd2Ugd2lsbCBjcmVhdGUgYSBuZXcgaW5zdGFuY2UuXG5cdFx0XHQvLyB+dGFnIHdpbGwgYWNjZXNzIHRoZSB0YWcsIGV2ZW4gd2l0aGluIHRoZSByZW5kZXJpbmcgb2YgdGhlIHRlbXBsYXRlIGNvbnRlbnQgb2YgdGhpcyB0YWcuXG5cdFx0XHQvLyBGcm9tIGNoaWxkL2Rlc2NlbmRhbnQgdGFncywgY2FuIGFjY2VzcyB1c2luZyB+dGFnLnBhcmVudCwgb3IgfnBhcmVudFRhZ3MudGFnTmFtZVxuXHRcdFx0dGFnID0gbmV3IHRhZ0RlZi5fY3RyKCk7XG5cdFx0XHRjYWxsSW5pdCA9ICEhdGFnLmluaXQ7XG5cblx0XHRcdHRhZy5wYXJlbnQgPSBwYXJlbnRUYWcgPSBjdHggJiYgY3R4LnRhZztcblx0XHRcdHRhZy50YWdDdHhzID0gdGFnQ3R4cztcblxuXHRcdFx0aWYgKGxpbmtDdHgpIHtcblx0XHRcdFx0dGFnLmlubGluZSA9IGZhbHNlO1xuXHRcdFx0XHRsaW5rQ3R4LnRhZyA9IHRhZztcblx0XHRcdFx0dGFnLmxpbmtDdHggPSBsaW5rQ3R4O1xuXHRcdFx0fVxuXHRcdFx0aWYgKHRhZy5fLmJuZCA9IGJvdW5kVGFnIHx8IGxpbmtDdHguZm4pIHtcblx0XHRcdFx0Ly8gQm91bmQgaWYge157dGFnLi4ufX0gb3IgZGF0YS1saW5rPVwie3RhZy4uLn1cIlxuXHRcdFx0XHR0YWcuXy50aHMgPSB0YWdDdHgucGFyYW1zLnByb3BzLnRoaXM7IC8vIFRhZyBoYXMgYSB0aGlzPWV4cHIgYmluZGluZywgdG8gZ2V0IGphdmFzY3JpcHQgcmVmZXJlbmNlIHRvIHRhZyBpbnN0YW5jZVxuXHRcdFx0XHR0YWcuXy5sdCA9IHRhZ0N0eHMubHQ7IC8vIElmIGEgbGF0ZSBwYXRoIEBzb21lLnBhdGggaGFzIG5vdCByZXR1cm5lZCBAc29tZSBvYmplY3QsIG1hcmsgdGFnIGFzIGxhdGVcblx0XHRcdFx0dGFnLl8uYXJyVndzID0ge307XG5cdFx0XHR9IGVsc2UgaWYgKHRhZy5kYXRhQm91bmRPbmx5KSB7XG5cdFx0XHRcdGVycm9yKHRhZ05hbWUgKyBcIiBtdXN0IGJlIGRhdGEtYm91bmQ6XFxue157XCIgKyB0YWdOYW1lICsgXCJ9fVwiKTtcblx0XHRcdH1cblx0XHRcdC8vVE9ETyBiZXR0ZXIgcGVyZiBmb3IgY2hpbGRUYWdzKCkgLSBrZWVwIGNoaWxkIHRhZy50YWdzIGFycmF5LCAoYW5kIHJlbW92ZSBjaGlsZCwgd2hlbiBkaXNwb3NlZClcblx0XHRcdC8vIHRhZy50YWdzID0gW107XG5cdFx0fSBlbHNlIGlmIChsaW5rQ3R4ICYmIGxpbmtDdHguZm4uX2xyKSB7XG5cdFx0XHRjYWxsSW5pdCA9ICEhdGFnLmluaXQ7XG5cdFx0fVxuXHRcdHRhZ0RhdGFNYXAgPSB0YWcuZGF0YU1hcDtcblxuXHRcdHRhZ0N0eC50YWcgPSB0YWc7XG5cdFx0aWYgKHRhZ0RhdGFNYXAgJiYgdGFnQ3R4cykge1xuXHRcdFx0dGFnQ3R4Lm1hcCA9IHRhZ0N0eHNbaV0ubWFwOyAvLyBDb3B5IG92ZXIgdGhlIGNvbXBpbGVkIG1hcCBpbnN0YW5jZSBmcm9tIHRoZSBwcmV2aW91cyB0YWdDdHhzIHRvIHRoZSByZWZyZXNoZWQgb25lc1xuXHRcdH1cblx0XHRpZiAoIXRhZy5mbG93KSB7XG5cdFx0XHR0YWdDdHhDdHggPSB0YWdDdHguY3R4ID0gdGFnQ3R4LmN0eCB8fCB7fTtcblxuXHRcdFx0Ly8gdGFncyBoYXNoOiB0YWcuY3R4LnRhZ3MsIG1lcmdlZCB3aXRoIHBhcmVudFZpZXcuY3R4LnRhZ3MsXG5cdFx0XHR0YWdzID0gdGFnLnBhcmVudHMgPSB0YWdDdHhDdHgucGFyZW50VGFncyA9IGN0eCAmJiBleHRlbmRDdHgodGFnQ3R4Q3R4LnBhcmVudFRhZ3MsIGN0eC5wYXJlbnRUYWdzKSB8fCB7fTtcblx0XHRcdGlmIChwYXJlbnRUYWcpIHtcblx0XHRcdFx0dGFnc1twYXJlbnRUYWcudGFnTmFtZV0gPSBwYXJlbnRUYWc7XG5cdFx0XHRcdC8vVE9ETyBiZXR0ZXIgcGVyZiBmb3IgY2hpbGRUYWdzOiBwYXJlbnRUYWcudGFncy5wdXNoKHRhZyk7XG5cdFx0XHR9XG5cdFx0XHR0YWdzW3RhZy50YWdOYW1lXSA9IHRhZ0N0eEN0eC50YWcgPSB0YWc7XG5cdFx0XHR0YWdDdHhDdHgudGFnQ3R4ID0gdGFnQ3R4O1xuXHRcdH1cblx0fVxuXHRpZiAoISh0YWcuX2VyID0gb25FcnJvcikpIHtcblx0XHR0YWdIYW5kbGVyc0Zyb21Qcm9wcyh0YWcsIHRhZ0N0eHNbMF0pO1xuXHRcdHRhZy5yZW5kZXJpbmcgPSB7cm5kcjogdGFnLnJlbmRlcmluZ307IC8vIFByb3ZpZGUgb2JqZWN0IGZvciBzdGF0ZSBkdXJpbmcgcmVuZGVyIGNhbGxzIHRvIHRhZyBhbmQgZWxzZXMuIChVc2VkIGJ5IHt7aWZ9fSBhbmQge3tmb3J9fS4uLilcblx0XHRmb3IgKGkgPSAwOyBpIDwgbDsgaSsrKSB7IC8vIEl0ZXJhdGUgdGFnQ3R4IGZvciBlYWNoIHt7ZWxzZX19IGJsb2NrXG5cdFx0XHR0YWdDdHggPSB0YWcudGFnQ3R4ID0gdGFnQ3R4c1tpXTtcblx0XHRcdHByb3BzID0gdGFnQ3R4LnByb3BzO1xuXHRcdFx0dGFnLmN0eCA9IHRhZ0N0eC5jdHg7XG5cblx0XHRcdGlmICghaSkge1xuXHRcdFx0XHRpZiAoY2FsbEluaXQpIHtcblx0XHRcdFx0XHR0YWcuaW5pdCh0YWdDdHgsIGxpbmtDdHgsIHRhZy5jdHgpO1xuXHRcdFx0XHRcdGNhbGxJbml0ID0gdW5kZWZpbmVkO1xuXHRcdFx0XHR9XG5cdFx0XHRcdGlmICghdGFnQ3R4LmFyZ3MubGVuZ3RoICYmIHRhZ0N0eC5hcmdEZWZhdWx0ICE9PSBmYWxzZSAmJiB0YWcuYXJnRGVmYXVsdCAhPT0gZmFsc2UpIHtcblx0XHRcdFx0XHR0YWdDdHguYXJncyA9IGFyZ3MgPSBbdGFnQ3R4LnZpZXcuZGF0YV07IC8vIE1pc3NpbmcgZmlyc3QgYXJnIGRlZmF1bHRzIHRvIHRoZSBjdXJyZW50IGRhdGEgY29udGV4dFxuXHRcdFx0XHRcdHRhZ0N0eC5wYXJhbXMuYXJncyA9IFtcIiNkYXRhXCJdO1xuXHRcdFx0XHR9XG5cblx0XHRcdFx0YmluZFRvID0gYmluZFRvT3JCaW5kRnJvbShcImJpbmRUb1wiKTtcblxuXHRcdFx0XHRpZiAodGFnLmJpbmRUbyAhPT0gdW5kZWZpbmVkKSB7XG5cdFx0XHRcdFx0dGFnLmJpbmRUbyA9IGJpbmRUbztcblx0XHRcdFx0fVxuXG5cdFx0XHRcdGlmICh0YWcuYmluZEZyb20gIT09IHVuZGVmaW5lZCkge1xuXHRcdFx0XHRcdHRhZy5iaW5kRnJvbSA9IGJpbmRUb09yQmluZEZyb20oXCJiaW5kRnJvbVwiKTtcblx0XHRcdFx0fSBlbHNlIGlmICh0YWcuYmluZFRvKSB7XG5cdFx0XHRcdFx0dGFnLmJpbmRGcm9tID0gdGFnLmJpbmRUbyA9IGJpbmRUbztcblx0XHRcdFx0fVxuXHRcdFx0XHRiaW5kRnJvbSA9IHRhZy5iaW5kRnJvbSB8fCBiaW5kVG87XG5cblx0XHRcdFx0YmluZFRvTGVuZ3RoID0gYmluZFRvLmxlbmd0aDtcblx0XHRcdFx0YmluZEZyb21MZW5ndGggPSBiaW5kRnJvbS5sZW5ndGg7XG5cblx0XHRcdFx0aWYgKHRhZy5fLmJuZCAmJiAobGlua2VkRWxlbWVudCA9IHRhZy5saW5rZWRFbGVtZW50KSkge1xuXHRcdFx0XHRcdHRhZy5saW5rZWRFbGVtZW50ID0gbGlua2VkRWxlbWVudCA9ICRpc0FycmF5KGxpbmtlZEVsZW1lbnQpID8gbGlua2VkRWxlbWVudDogW2xpbmtlZEVsZW1lbnRdO1xuXG5cdFx0XHRcdFx0aWYgKGJpbmRUb0xlbmd0aCAhPT0gbGlua2VkRWxlbWVudC5sZW5ndGgpIHtcblx0XHRcdFx0XHRcdGVycm9yKFwibGlua2VkRWxlbWVudCBub3Qgc2FtZSBsZW5ndGggYXMgYmluZFRvXCIpO1xuXHRcdFx0XHRcdH1cblx0XHRcdFx0fVxuXHRcdFx0XHRpZiAobGlua2VkRWxlbWVudCA9IHRhZy5saW5rZWRDdHhQYXJhbSkge1xuXHRcdFx0XHRcdHRhZy5saW5rZWRDdHhQYXJhbSA9IGxpbmtlZEVsZW1lbnQgPSAkaXNBcnJheShsaW5rZWRFbGVtZW50KSA/IGxpbmtlZEVsZW1lbnQ6IFtsaW5rZWRFbGVtZW50XTtcblxuXHRcdFx0XHRcdGlmIChiaW5kRnJvbUxlbmd0aCAhPT0gbGlua2VkRWxlbWVudC5sZW5ndGgpIHtcblx0XHRcdFx0XHRcdGVycm9yKFwibGlua2VkQ3R4UGFyYW0gbm90IHNhbWUgbGVuZ3RoIGFzIGJpbmRGcm9tL2JpbmRUb1wiKTtcblx0XHRcdFx0XHR9XG5cdFx0XHRcdH1cblxuXHRcdFx0XHRpZiAoYmluZEZyb20pIHtcblx0XHRcdFx0XHR0YWcuXy5mcm9tSW5kZXggPSB7fTsgLy8gSGFzaCBvZiBiaW5kRnJvbSBpbmRleCB3aGljaCBoYXMgc2FtZSBwYXRoIHZhbHVlIGFzIGJpbmRUbyBpbmRleC4gZnJvbUluZGV4ID0gdGFnLl8uZnJvbUluZGV4W3RvSW5kZXhdXG5cdFx0XHRcdFx0dGFnLl8udG9JbmRleCA9IHt9OyAvLyBIYXNoIG9mIGJpbmRGcm9tIGluZGV4IHdoaWNoIGhhcyBzYW1lIHBhdGggdmFsdWUgYXMgYmluZFRvIGluZGV4LiBmcm9tSW5kZXggPSB0YWcuXy5mcm9tSW5kZXhbdG9JbmRleF1cblx0XHRcdFx0XHRuID0gYmluZEZyb21MZW5ndGg7XG5cdFx0XHRcdFx0d2hpbGUgKG4tLSkge1xuXHRcdFx0XHRcdFx0a2V5ID0gYmluZEZyb21bbl07XG5cdFx0XHRcdFx0XHRtID0gYmluZFRvTGVuZ3RoO1xuXHRcdFx0XHRcdFx0d2hpbGUgKG0tLSkge1xuXHRcdFx0XHRcdFx0XHRpZiAoa2V5ID09PSBiaW5kVG9bbV0pIHtcblx0XHRcdFx0XHRcdFx0XHR0YWcuXy5mcm9tSW5kZXhbbV0gPSBuO1xuXHRcdFx0XHRcdFx0XHRcdHRhZy5fLnRvSW5kZXhbbl0gPSBtO1xuXHRcdFx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0fVxuXHRcdFx0XHR9XG5cblx0XHRcdFx0aWYgKGxpbmtDdHgpIHtcblx0XHRcdFx0XHQvLyBTZXQgYXR0ciBvbiBsaW5rQ3R4IHRvIGVuc3VyZSBvdXRwdXR0aW5nIHRvIHRoZSBjb3JyZWN0IHRhcmdldCBhdHRyaWJ1dGUuXG5cdFx0XHRcdFx0Ly8gU2V0dGluZyBlaXRoZXIgbGlua0N0eC5hdHRyIG9yIHRoaXMuYXR0ciBpbiB0aGUgaW5pdCgpIGFsbG93cyBwZXItaW5zdGFuY2UgY2hvaWNlIG9mIHRhcmdldCBhdHRyaWIuXG5cdFx0XHRcdFx0bGlua0N0eC5hdHRyID0gdGFnLmF0dHIgPSBsaW5rQ3R4LmF0dHIgfHwgdGFnLmF0dHIgfHwgbGlua0N0eC5fZGZBdDtcblx0XHRcdFx0fVxuXHRcdFx0XHRhdHRyID0gdGFnLmF0dHI7XG5cdFx0XHRcdHRhZy5fLm5vVndzID0gYXR0ciAmJiBhdHRyICE9PSBIVE1MO1xuXHRcdFx0fVxuXHRcdFx0YXJncyA9IHRhZy5jdnRBcmdzKHVuZGVmaW5lZCwgaSk7XG5cdFx0XHRpZiAodGFnLmxpbmtlZEN0eFBhcmFtKSB7XG5cdFx0XHRcdGJkQXJncyA9IHRhZy5jdnRBcmdzKDEsIGkpO1xuXHRcdFx0XHRtID0gYmluZEZyb21MZW5ndGg7XG5cdFx0XHRcdGRlZmF1bHRDdHggPSB0YWcuY29uc3RydWN0b3IucHJvdG90eXBlLmN0eDtcblx0XHRcdFx0d2hpbGUgKG0tLSkge1xuXHRcdFx0XHRcdGlmIChjdHhQcm0gPSB0YWcubGlua2VkQ3R4UGFyYW1bbV0pIHtcblx0XHRcdFx0XHRcdGtleSA9IGJpbmRGcm9tW21dO1xuXHRcdFx0XHRcdFx0aW5pdFZhbCA9IGJkQXJnc1ttXTtcblx0XHRcdFx0XHRcdC8vIENyZWF0ZSB0YWcgY29udGV4dHVhbCBwYXJhbWV0ZXJcblx0XHRcdFx0XHRcdHRhZ0N0eC5jdHhbY3R4UHJtXSA9ICRzdWIuX2NwKFxuXHRcdFx0XHRcdFx0XHRkZWZhdWx0Q3R4ICYmIGluaXRWYWwgPT09IHVuZGVmaW5lZCA/IGRlZmF1bHRDdHhbY3R4UHJtXTogaW5pdFZhbCxcblx0XHRcdFx0XHRcdFx0aW5pdFZhbCAhPT0gdW5kZWZpbmVkICYmIGFyZ09yUHJvcCh0YWdDdHgucGFyYW1zLCBrZXkpLFxuXHRcdFx0XHRcdFx0XHR0YWdDdHgudmlldyxcblx0XHRcdFx0XHRcdFx0dGFnLl8uYm5kICYmIHt0YWc6IHRhZywgY3Z0OiB0YWcuY29udmVydCwgaW5kOiBtLCB0YWdFbHNlOiBpfVxuXHRcdFx0XHRcdFx0KTtcblx0XHRcdFx0XHR9XG5cdFx0XHRcdH1cblx0XHRcdH1cblx0XHRcdGlmICgobWFwRGVmID0gcHJvcHMuZGF0YU1hcCB8fCB0YWdEYXRhTWFwKSAmJiAoYXJncy5sZW5ndGggfHwgcHJvcHMuZGF0YU1hcCkpIHtcblx0XHRcdFx0dGhpc01hcCA9IHRhZ0N0eC5tYXA7XG5cdFx0XHRcdGlmICghdGhpc01hcCB8fCB0aGlzTWFwLnNyYyAhPT0gYXJnc1swXSB8fCBpc1VwZGF0ZSkge1xuXHRcdFx0XHRcdGlmICh0aGlzTWFwICYmIHRoaXNNYXAuc3JjKSB7XG5cdFx0XHRcdFx0XHR0aGlzTWFwLnVubWFwKCk7IC8vIG9ubHkgY2FsbGVkIGlmIG9ic2VydmFibGUgbWFwIC0gbm90IHdoZW4gb25seSB1c2VkIGluIEpzUmVuZGVyLCBlLmcuIGJ5IHt7cHJvcHN9fVxuXHRcdFx0XHRcdH1cblx0XHRcdFx0XHRtYXBEZWYubWFwKGFyZ3NbMF0sIHRhZ0N0eCwgdGhpc01hcCwgIXRhZy5fLmJuZCk7XG5cdFx0XHRcdFx0dGhpc01hcCA9IHRhZ0N0eC5tYXA7XG5cdFx0XHRcdH1cblx0XHRcdFx0YXJncyA9IFt0aGlzTWFwLnRndF07XG5cdFx0XHR9XG5cblx0XHRcdGl0ZW1SZXQgPSB1bmRlZmluZWQ7XG5cdFx0XHRpZiAodGFnLnJlbmRlcikge1xuXHRcdFx0XHRpdGVtUmV0ID0gdGFnLnJlbmRlci5hcHBseSh0YWcsIGFyZ3MpO1xuXHRcdFx0XHRpZiAocGFyZW50Vmlldy5saW5rZWQgJiYgaXRlbVJldCAmJiAhcldyYXBwZWRJblZpZXdNYXJrZXIudGVzdChpdGVtUmV0KSkge1xuXHRcdFx0XHRcdC8vIFdoZW4gYSB0YWcgcmVuZGVycyBjb250ZW50IGZyb20gdGhlIHJlbmRlciBtZXRob2QsIHdpdGggZGF0YSBsaW5raW5nIHRoZW4gd2UgbmVlZCB0byB3cmFwIHdpdGggdmlldyBtYXJrZXJzLCBpZiBhYnNlbnQsXG5cdFx0XHRcdFx0Ly8gdG8gcHJvdmlkZSBhIGNvbnRlbnRWaWV3IGZvciB0aGUgdGFnLCB3aGljaCB3aWxsIGNvcnJlY3RseSBkaXNwb3NlIGJpbmRpbmdzIGlmIGRlbGV0ZWQuIFRoZSAndG1wbCcgZm9yIHRoaXMgdmlldyB3aWxsXG5cdFx0XHRcdFx0Ly8gYmUgYSBkdW1iZWQtZG93biB0ZW1wbGF0ZSB3aGljaCB3aWxsIGFsd2F5cyByZXR1cm4gdGhlICBpdGVtUmV0IHN0cmluZyAobm8gbWF0dGVyIHdoYXQgdGhlIGRhdGEgaXMpLiBUaGUgaXRlbVJldCBzdHJpbmdcblx0XHRcdFx0XHQvLyBpcyBub3QgY29tcGlsZWQgYXMgdGVtcGxhdGUgbWFya3VwLCBzbyBjYW4gaW5jbHVkZSBcInt7XCIgb3IgXCJ9fVwiIHdpdGhvdXQgdHJpZ2dlcmluZyBzeW50YXggZXJyb3JzXG5cdFx0XHRcdFx0dG1wbCA9IHsgLy8gJ0R1bWJlZC1kb3duJyB0ZW1wbGF0ZSB3aGljaCBhbHdheXMgcmVuZGVycyAnc3RhdGljJyBpdGVtUmV0IHN0cmluZ1xuXHRcdFx0XHRcdFx0bGlua3M6IFtdXG5cdFx0XHRcdFx0fTtcblx0XHRcdFx0XHR0bXBsLnJlbmRlciA9IHRtcGwuZm4gPSBmdW5jdGlvbigpIHtcblx0XHRcdFx0XHRcdHJldHVybiBpdGVtUmV0O1xuXHRcdFx0XHRcdH07XG5cdFx0XHRcdFx0aXRlbVJldCA9IHJlbmRlcldpdGhWaWV3cyh0bXBsLCBwYXJlbnRWaWV3LmRhdGEsIHVuZGVmaW5lZCwgdHJ1ZSwgcGFyZW50VmlldywgdW5kZWZpbmVkLCB1bmRlZmluZWQsIHRhZyk7XG5cdFx0XHRcdH1cblx0XHRcdH1cblx0XHRcdGlmICghYXJncy5sZW5ndGgpIHtcblx0XHRcdFx0YXJncyA9IFtwYXJlbnRWaWV3XTsgLy8gbm8gYXJndW1lbnRzIC0gKGUuZy4ge3tlbHNlfX0pIGdldCBkYXRhIGNvbnRleHQgZnJvbSB2aWV3LlxuXHRcdFx0fVxuXHRcdFx0aWYgKGl0ZW1SZXQgPT09IHVuZGVmaW5lZCkge1xuXHRcdFx0XHRjb250ZW50Q3R4ID0gYXJnc1swXTsgLy8gRGVmYXVsdCBkYXRhIGNvbnRleHQgZm9yIHdyYXBwZWQgYmxvY2sgY29udGVudCBpcyB0aGUgZmlyc3QgYXJndW1lbnRcblx0XHRcdFx0aWYgKHRhZy5jb250ZW50Q3R4KSB7IC8vIFNldCB0YWcuY29udGVudEN0eCB0byB0cnVlLCB0byBpbmhlcml0IHBhcmVudCBjb250ZXh0LCBvciB0byBhIGZ1bmN0aW9uIHRvIHByb3ZpZGUgYWx0ZXJuYXRlIGNvbnRleHQuXG5cdFx0XHRcdFx0Y29udGVudEN0eCA9IHRhZy5jb250ZW50Q3R4ID09PSB0cnVlID8gcGFyZW50VmlldyA6IHRhZy5jb250ZW50Q3R4KGNvbnRlbnRDdHgpO1xuXHRcdFx0XHR9XG5cdFx0XHRcdGl0ZW1SZXQgPSB0YWdDdHgucmVuZGVyKGNvbnRlbnRDdHgsIHRydWUpIHx8IChpc1VwZGF0ZSA/IHVuZGVmaW5lZCA6IFwiXCIpO1xuXHRcdFx0fVxuXHRcdFx0Ly8gTm8gcmV0dXJuIHZhbHVlIGZyb20gcmVuZGVyLCBhbmQgbm8gdGVtcGxhdGUvY29udGVudCB0YWdDdHgucmVuZGVyKC4uLiksIHNvIHJldHVybiB1bmRlZmluZWRcblx0XHRcdHJldCA9IHJldCA/IHJldCArIChpdGVtUmV0IHx8IFwiXCIpIDogaXRlbVJldDsgLy8gSWYgbm8gcmVuZGVyZWQgY29udGVudCwgdGhpcyB3aWxsIGJlIHVuZGVmaW5lZFxuXHRcdH1cblx0XHR0YWcucmVuZGVyaW5nID0gdGFnLnJlbmRlcmluZy5ybmRyOyAvLyBSZW1vdmUgdGFnLnJlbmRlcmluZyBvYmplY3QgKGlmIHRoaXMgaXMgb3V0ZXJtb3N0IHJlbmRlciBjYWxsLiAoSW4gY2FzZSBvZiBuZXN0ZWQgY2FsbHMpXG5cdH1cblx0dGFnLnRhZ0N0eCA9IHRhZ0N0eHNbMF07XG5cdHRhZy5jdHggPSB0YWcudGFnQ3R4LmN0eDtcblxuXHRpZiAodGFnLl8ubm9Wd3MgJiYgdGFnLmlubGluZSkge1xuXHRcdC8vIGlubGluZSB0YWcgd2l0aCBhdHRyIHNldCB0byBcInRleHRcIiB3aWxsIGluc2VydCBIVE1MLWVuY29kZWQgY29udGVudCAtIGFzIGlmIGl0IHdhcyBlbGVtZW50LWJhc2VkIGlubmVyVGV4dFxuXHRcdHJldCA9IGF0dHIgPT09IFwidGV4dFwiXG5cdFx0XHQ/ICRjb252ZXJ0ZXJzLmh0bWwocmV0KVxuXHRcdFx0OiBcIlwiO1xuXHR9XG5cdHJldHVybiBib3VuZFRhZyAmJiBwYXJlbnRWaWV3Ll8ub25SZW5kZXJcblx0XHQvLyBDYWxsIG9uUmVuZGVyICh1c2VkIGJ5IEpzVmlld3MgaWYgcHJlc2VudCwgdG8gYWRkIGJpbmRpbmcgYW5ub3RhdGlvbnMgYXJvdW5kIHJlbmRlcmVkIGNvbnRlbnQpXG5cdFx0PyBwYXJlbnRWaWV3Ll8ub25SZW5kZXIocmV0LCBwYXJlbnRWaWV3LCB0YWcpXG5cdFx0OiByZXQ7XG59XG5cbi8vPT09PT09PT09PT09PT09PT1cbi8vIFZpZXcgY29uc3RydWN0b3Jcbi8vPT09PT09PT09PT09PT09PT1cblxuZnVuY3Rpb24gVmlldyhjb250ZXh0LCB0eXBlLCBwYXJlbnRWaWV3LCBkYXRhLCB0ZW1wbGF0ZSwga2V5LCBvblJlbmRlciwgY29udGVudFRtcGwpIHtcblx0Ly8gQ29uc3RydWN0b3IgZm9yIHZpZXcgb2JqZWN0IGluIHZpZXcgaGllcmFyY2h5LiAoQXVnbWVudGVkIGJ5IEpzVmlld3MgaWYgSnNWaWV3cyBpcyBsb2FkZWQpXG5cdHZhciB2aWV3cywgcGFyZW50Vmlld18sIHRhZywgc2VsZl8sXG5cdFx0c2VsZiA9IHRoaXMsXG5cdFx0aXNBcnJheSA9IHR5cGUgPT09IFwiYXJyYXlcIjtcblx0XHQvLyBJZiB0aGUgZGF0YSBpcyBhbiBhcnJheSwgdGhpcyBpcyBhbiAnYXJyYXkgdmlldycgd2l0aCBhIHZpZXdzIGFycmF5IGZvciBlYWNoIGNoaWxkICdpdGVtIHZpZXcnXG5cdFx0Ly8gSWYgdGhlIGRhdGEgaXMgbm90IGFuIGFycmF5LCB0aGlzIGlzIGFuICdpdGVtIHZpZXcnIHdpdGggYSB2aWV3cyAnaGFzaCcgb2JqZWN0IGZvciBhbnkgY2hpbGQgbmVzdGVkIHZpZXdzXG5cblx0c2VsZi5jb250ZW50ID0gY29udGVudFRtcGw7XG5cdHNlbGYudmlld3MgPSBpc0FycmF5ID8gW10gOiB7fTtcblx0c2VsZi5kYXRhID0gZGF0YTtcblx0c2VsZi50bXBsID0gdGVtcGxhdGU7XG5cdHNlbGZfID0gc2VsZi5fID0ge1xuXHRcdGtleTogMCxcblx0XHQvLyAuXy51c2VLZXkgaXMgbm9uIHplcm8gaWYgaXMgbm90IGFuICdhcnJheSB2aWV3JyAob3duaW5nIGEgZGF0YSBhcnJheSkuIFVzZSB0aGlzIGFzIG5leHQga2V5IGZvciBhZGRpbmcgdG8gY2hpbGQgdmlld3MgaGFzaFxuXHRcdHVzZUtleTogaXNBcnJheSA/IDAgOiAxLFxuXHRcdGlkOiBcIlwiICsgdmlld0lkKyssXG5cdFx0b25SZW5kZXI6IG9uUmVuZGVyLFxuXHRcdGJuZHM6IHt9XG5cdH07XG5cdHNlbGYubGlua2VkID0gISFvblJlbmRlcjtcblx0c2VsZi50eXBlID0gdHlwZSB8fCBcInRvcFwiO1xuXHRpZiAoc2VsZi5wYXJlbnQgPSBwYXJlbnRWaWV3KSB7XG5cdFx0c2VsZi5yb290ID0gcGFyZW50Vmlldy5yb290IHx8IHNlbGY7IC8vIHZpZXcgd2hvc2UgcGFyZW50IGlzIHRvcCB2aWV3XG5cdFx0dmlld3MgPSBwYXJlbnRWaWV3LnZpZXdzO1xuXHRcdHBhcmVudFZpZXdfID0gcGFyZW50Vmlldy5fO1xuXHRcdHNlbGYuaXNUb3AgPSBwYXJlbnRWaWV3Xy5zY3A7IC8vIElzIHRvcCBjb250ZW50IHZpZXcgb2YgYSBsaW5rKFwiI2NvbnRhaW5lclwiLCAuLi4pIGNhbGxcblx0XHRzZWxmLnNjb3BlID0gKCFjb250ZXh0LnRhZyB8fCBjb250ZXh0LnRhZyA9PT0gcGFyZW50Vmlldy5jdHgudGFnKSAmJiAhc2VsZi5pc1RvcCAmJiBwYXJlbnRWaWV3LnNjb3BlIHx8IHNlbGY7XG5cdFx0Ly8gU2NvcGUgZm9yIGNvbnRleHRQYXJhbXMgLSBjbG9zZXN0IG5vbiBmbG93IHRhZyBhbmNlc3RvciBvciByb290IHZpZXdcblx0XHRpZiAocGFyZW50Vmlld18udXNlS2V5KSB7XG5cdFx0XHQvLyBQYXJlbnQgaXMgbm90IGFuICdhcnJheSB2aWV3Jy4gQWRkIHRoaXMgdmlldyB0byBpdHMgdmlld3Mgb2JqZWN0XG5cdFx0XHQvLyBzZWxmLl9rZXkgPSBpcyB0aGUga2V5IGluIHRoZSBwYXJlbnQgdmlldyBoYXNoXG5cdFx0XHR2aWV3c1tzZWxmXy5rZXkgPSBcIl9cIiArIHBhcmVudFZpZXdfLnVzZUtleSsrXSA9IHNlbGY7XG5cdFx0XHRzZWxmLmluZGV4ID0gaW5kZXhTdHI7XG5cdFx0XHRzZWxmLmdldEluZGV4ID0gZ2V0TmVzdGVkSW5kZXg7XG5cdFx0fSBlbHNlIGlmICh2aWV3cy5sZW5ndGggPT09IChzZWxmXy5rZXkgPSBzZWxmLmluZGV4ID0ga2V5KSkgeyAvLyBQYXJlbnQgaXMgYW4gJ2FycmF5IHZpZXcnLiBBZGQgdGhpcyB2aWV3IHRvIGl0cyB2aWV3cyBhcnJheVxuXHRcdFx0dmlld3MucHVzaChzZWxmKTsgLy8gQWRkaW5nIHRvIGVuZCBvZiB2aWV3cyBhcnJheS4gKFVzaW5nIHB1c2ggd2hlbiBwb3NzaWJsZSAtIGJldHRlciBwZXJmIHRoYW4gc3BsaWNlKVxuXHRcdH0gZWxzZSB7XG5cdFx0XHR2aWV3cy5zcGxpY2Uoa2V5LCAwLCBzZWxmKTsgLy8gSW5zZXJ0aW5nIGluIHZpZXdzIGFycmF5XG5cdFx0fVxuXHRcdC8vIElmIG5vIGNvbnRleHQgd2FzIHBhc3NlZCBpbiwgdXNlIHBhcmVudCBjb250ZXh0XG5cdFx0Ly8gSWYgY29udGV4dCB3YXMgcGFzc2VkIGluLCBpdCBzaG91bGQgaGF2ZSBiZWVuIG1lcmdlZCBhbHJlYWR5IHdpdGggcGFyZW50IGNvbnRleHRcblx0XHRzZWxmLmN0eCA9IGNvbnRleHQgfHwgcGFyZW50Vmlldy5jdHg7XG5cdH0gZWxzZSB7XG5cdFx0c2VsZi5jdHggPSBjb250ZXh0IHx8IHt9O1xuXHR9XG59XG5cblZpZXcucHJvdG90eXBlID0ge1xuXHRnZXQ6IGdldFZpZXcsXG5cdGdldEluZGV4OiBnZXRJbmRleCxcblx0Z2V0UnNjOiBnZXRSZXNvdXJjZSxcblx0Z2V0VG1wbDogZ2V0VGVtcGxhdGUsXG5cdGN0eFBybTogY29udGV4dFBhcmFtZXRlcixcblx0Z2V0T2I6IGdldFBhdGhPYmplY3QsXG5cdF9pczogXCJ2aWV3XCJcbn07XG5cbi8vPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuLy8gUmVnaXN0cmF0aW9uXG4vLz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT1cblxuZnVuY3Rpb24gY29tcGlsZUNoaWxkUmVzb3VyY2VzKHBhcmVudFRtcGwpIHtcblx0dmFyIHN0b3JlTmFtZSwgc3RvcmVOYW1lcywgcmVzb3VyY2VzO1xuXHRmb3IgKHN0b3JlTmFtZSBpbiBqc3ZTdG9yZXMpIHtcblx0XHRzdG9yZU5hbWVzID0gc3RvcmVOYW1lICsgXCJzXCI7XG5cdFx0aWYgKHBhcmVudFRtcGxbc3RvcmVOYW1lc10pIHtcblx0XHRcdHJlc291cmNlcyA9IHBhcmVudFRtcGxbc3RvcmVOYW1lc107ICAgIC8vIFJlc291cmNlcyBub3QgeWV0IGNvbXBpbGVkXG5cdFx0XHRwYXJlbnRUbXBsW3N0b3JlTmFtZXNdID0ge307ICAgICAgICAgICAgICAgLy8gUmVtb3ZlIHVuY29tcGlsZWQgcmVzb3VyY2VzXG5cdFx0XHQkdmlld3Nbc3RvcmVOYW1lc10ocmVzb3VyY2VzLCBwYXJlbnRUbXBsKTsgLy8gQWRkIGJhY2sgaW4gdGhlIGNvbXBpbGVkIHJlc291cmNlc1xuXHRcdH1cblx0fVxufVxuXG4vLz09PT09PT09PT09PT09PVxuLy8gY29tcGlsZVRhZ1xuLy89PT09PT09PT09PT09PT1cblxuZnVuY3Rpb24gY29tcGlsZVRhZyhuYW1lLCB0YWdEZWYsIHBhcmVudFRtcGwpIHtcblx0dmFyIHRtcGwsIGJhc2VUYWcsIHByb3AsXG5cdFx0Y29tcGlsZWREZWYgPSBuZXcgJHN1Yi5fdGcoKTtcblxuXHRmdW5jdGlvbiBUYWcoKSB7XG5cdFx0dmFyIHRhZyA9IHRoaXM7XG5cdFx0dGFnLl8gPSB7XG5cdFx0XHR1bmxpbmtlZDogdHJ1ZVxuXHRcdH07XG5cdFx0dGFnLmlubGluZSA9IHRydWU7XG5cdFx0dGFnLnRhZ05hbWUgPSBuYW1lO1xuXHR9XG5cblx0aWYgKCRpc0Z1bmN0aW9uKHRhZ0RlZikpIHtcblx0XHQvLyBTaW1wbGUgdGFnIGRlY2xhcmVkIGFzIGZ1bmN0aW9uLiBObyBwcmVzZW50ZXIgaW5zdGFudGF0aW9uLlxuXHRcdHRhZ0RlZiA9IHtcblx0XHRcdGRlcGVuZHM6IHRhZ0RlZi5kZXBlbmRzLFxuXHRcdFx0cmVuZGVyOiB0YWdEZWZcblx0XHR9O1xuXHR9IGVsc2UgaWYgKFwiXCIgKyB0YWdEZWYgPT09IHRhZ0RlZikge1xuXHRcdHRhZ0RlZiA9IHt0ZW1wbGF0ZTogdGFnRGVmfTtcblx0fVxuXG5cdGlmIChiYXNlVGFnID0gdGFnRGVmLmJhc2VUYWcpIHtcblx0XHR0YWdEZWYuZmxvdyA9ICEhdGFnRGVmLmZsb3c7IC8vIFNldCBmbG93IHByb3BlcnR5LCBzbyBkZWZhdWx0cyB0byBmYWxzZSBldmVuIGlmIGJhc2VUYWcgaGFzIGZsb3c9dHJ1ZVxuXHRcdGJhc2VUYWcgPSBcIlwiICsgYmFzZVRhZyA9PT0gYmFzZVRhZ1xuXHRcdFx0PyAocGFyZW50VG1wbCAmJiBwYXJlbnRUbXBsLnRhZ3NbYmFzZVRhZ10gfHwgJHRhZ3NbYmFzZVRhZ10pXG5cdFx0XHQ6IGJhc2VUYWc7XG5cdFx0aWYgKCFiYXNlVGFnKSB7XG5cdFx0XHRlcnJvcignYmFzZVRhZzogXCInICsgdGFnRGVmLmJhc2VUYWcgKyAnXCIgbm90IGZvdW5kJyk7XG5cdFx0fVxuXHRcdGNvbXBpbGVkRGVmID0gJGV4dGVuZChjb21waWxlZERlZiwgYmFzZVRhZyk7XG5cblx0XHRmb3IgKHByb3AgaW4gdGFnRGVmKSB7XG5cdFx0XHRjb21waWxlZERlZltwcm9wXSA9IGdldE1ldGhvZChiYXNlVGFnW3Byb3BdLCB0YWdEZWZbcHJvcF0pO1xuXHRcdH1cblx0fSBlbHNlIHtcblx0XHRjb21waWxlZERlZiA9ICRleHRlbmQoY29tcGlsZWREZWYsIHRhZ0RlZik7XG5cdH1cblxuXHQvLyBUYWcgZGVjbGFyZWQgYXMgb2JqZWN0LCB1c2VkIGFzIHRoZSBwcm90b3R5cGUgZm9yIHRhZyBpbnN0YW50aWF0aW9uIChjb250cm9sL3ByZXNlbnRlcilcblx0aWYgKCh0bXBsID0gY29tcGlsZWREZWYudGVtcGxhdGUpICE9PSB1bmRlZmluZWQpIHtcblx0XHRjb21waWxlZERlZi50ZW1wbGF0ZSA9IFwiXCIgKyB0bXBsID09PSB0bXBsID8gKCR0ZW1wbGF0ZXNbdG1wbF0gfHwgJHRlbXBsYXRlcyh0bXBsKSkgOiB0bXBsO1xuXHR9XG5cdChUYWcucHJvdG90eXBlID0gY29tcGlsZWREZWYpLmNvbnN0cnVjdG9yID0gY29tcGlsZWREZWYuX2N0ciA9IFRhZztcblxuXHRpZiAocGFyZW50VG1wbCkge1xuXHRcdGNvbXBpbGVkRGVmLl9wYXJlbnRUbXBsID0gcGFyZW50VG1wbDtcblx0fVxuXHRyZXR1cm4gY29tcGlsZWREZWY7XG59XG5cbmZ1bmN0aW9uIGJhc2VBcHBseShhcmdzKSB7XG5cdC8vIEluIGRlcml2ZWQgbWV0aG9kIChvciBoYW5kbGVyIGRlY2xhcmVkIGRlY2xhcmF0aXZlbHkgYXMgaW4ge3s6Zm9vIG9uQ2hhbmdlPX5mb29DaGFuZ2VkfX0gY2FuIGNhbGwgYmFzZSBtZXRob2QsXG5cdC8vIHVzaW5nIHRoaXMuYmFzZUFwcGx5KGFyZ3VtZW50cykgKEVxdWl2YWxlbnQgdG8gdGhpcy5fc3VwZXJBcHBseShhcmd1bWVudHMpIGluIGpRdWVyeSBVSSlcblx0cmV0dXJuIHRoaXMuYmFzZS5hcHBseSh0aGlzLCBhcmdzKTtcbn1cblxuLy89PT09PT09PT09PT09PT1cbi8vIGNvbXBpbGVUbXBsXG4vLz09PT09PT09PT09PT09PVxuXG5mdW5jdGlvbiBjb21waWxlVG1wbChuYW1lLCB0bXBsLCBwYXJlbnRUbXBsLCBvcHRpb25zKSB7XG5cdC8vIHRtcGwgaXMgZWl0aGVyIGEgdGVtcGxhdGUgb2JqZWN0LCBhIHNlbGVjdG9yIGZvciBhIHRlbXBsYXRlIHNjcmlwdCBibG9jaywgdGhlIG5hbWUgb2YgYSBjb21waWxlZCB0ZW1wbGF0ZSwgb3IgYSB0ZW1wbGF0ZSBvYmplY3RcblxuXHQvLz09PT0gbmVzdGVkIGZ1bmN0aW9ucyA9PT09XG5cdGZ1bmN0aW9uIGxvb2t1cFRlbXBsYXRlKHZhbHVlKSB7XG5cdFx0Ly8gSWYgdmFsdWUgaXMgb2YgdHlwZSBzdHJpbmcgLSB0cmVhdCBhcyBzZWxlY3Rvciwgb3IgbmFtZSBvZiBjb21waWxlZCB0ZW1wbGF0ZVxuXHRcdC8vIFJldHVybiB0aGUgdGVtcGxhdGUgb2JqZWN0LCBpZiBhbHJlYWR5IGNvbXBpbGVkLCBvciB0aGUgbWFya3VwIHN0cmluZ1xuXHRcdHZhciBjdXJyZW50TmFtZSwgdG1wbDtcblx0XHRpZiAoKFwiXCIgKyB2YWx1ZSA9PT0gdmFsdWUpIHx8IHZhbHVlLm5vZGVUeXBlID4gMCAmJiAoZWxlbSA9IHZhbHVlKSkge1xuXHRcdFx0aWYgKCFlbGVtKSB7XG5cdFx0XHRcdGlmICgvXlxcLlxcL1teXFxcXDoqP1wiPD5dKiQvLnRlc3QodmFsdWUpKSB7XG5cdFx0XHRcdFx0Ly8gdG1wbD1cIi4vc29tZS9maWxlLmh0bWxcIlxuXHRcdFx0XHRcdC8vIElmIHRoZSB0ZW1wbGF0ZSBpcyBub3QgbmFtZWQsIHVzZSBcIi4vc29tZS9maWxlLmh0bWxcIiBhcyBuYW1lLlxuXHRcdFx0XHRcdGlmICh0bXBsID0gJHRlbXBsYXRlc1tuYW1lID0gbmFtZSB8fCB2YWx1ZV0pIHtcblx0XHRcdFx0XHRcdHZhbHVlID0gdG1wbDtcblx0XHRcdFx0XHR9IGVsc2Uge1xuXHRcdFx0XHRcdFx0Ly8gQlJPV1NFUi1TUEVDSUZJQyBDT0RFIChub3Qgb24gTm9kZS5qcyk6XG5cdFx0XHRcdFx0XHQvLyBMb29rIGZvciBzZXJ2ZXItZ2VuZXJhdGVkIHNjcmlwdCBibG9jayB3aXRoIGlkIFwiLi9zb21lL2ZpbGUuaHRtbFwiXG5cdFx0XHRcdFx0XHRlbGVtID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQodmFsdWUpO1xuXHRcdFx0XHRcdH1cblx0XHRcdFx0fSBlbHNlIGlmICgkLmZuICYmICEkc3ViLnJUbXBsLnRlc3QodmFsdWUpKSB7XG5cdFx0XHRcdFx0dHJ5IHtcblx0XHRcdFx0XHRcdGVsZW0gPSAkICh2YWx1ZSwgZG9jdW1lbnQpWzBdOyAvLyBpZiBqUXVlcnkgaXMgbG9hZGVkLCB0ZXN0IGZvciBzZWxlY3RvciByZXR1cm5pbmcgZWxlbWVudHMsIGFuZCBnZXQgZmlyc3QgZWxlbWVudFxuXHRcdFx0XHRcdH0gY2F0Y2ggKGUpIHt9XG5cdFx0XHRcdH0vLyBFTkQgQlJPV1NFUi1TUEVDSUZJQyBDT0RFXG5cdFx0XHR9IC8vQlJPV1NFUi1TUEVDSUZJQyBDT0RFXG5cdFx0XHRpZiAoZWxlbSkge1xuXHRcdFx0XHRpZiAoZWxlbS50YWdOYW1lICE9PSBcIlNDUklQVFwiKSB7XG5cdFx0XHRcdFx0ZXJyb3IodmFsdWUgKyBcIjogVXNlIHNjcmlwdCBibG9jaywgbm90IFwiICsgZWxlbS50YWdOYW1lKTtcblx0XHRcdFx0fVxuXHRcdFx0XHRpZiAob3B0aW9ucykge1xuXHRcdFx0XHRcdC8vIFdlIHdpbGwgY29tcGlsZSBhIG5ldyB0ZW1wbGF0ZSB1c2luZyB0aGUgbWFya3VwIGluIHRoZSBzY3JpcHQgZWxlbWVudFxuXHRcdFx0XHRcdHZhbHVlID0gZWxlbS5pbm5lckhUTUw7XG5cdFx0XHRcdH0gZWxzZSB7XG5cdFx0XHRcdFx0Ly8gV2Ugd2lsbCBjYWNoZSBhIHNpbmdsZSBjb3B5IG9mIHRoZSBjb21waWxlZCB0ZW1wbGF0ZSwgYW5kIGFzc29jaWF0ZSBpdCB3aXRoIHRoZSBuYW1lXG5cdFx0XHRcdFx0Ly8gKHJlbmFtaW5nIGZyb20gYSBwcmV2aW91cyBuYW1lIGlmIHRoZXJlIHdhcyBvbmUpLlxuXHRcdFx0XHRcdGN1cnJlbnROYW1lID0gZWxlbS5nZXRBdHRyaWJ1dGUodG1wbEF0dHIpO1xuXHRcdFx0XHRcdGlmIChjdXJyZW50TmFtZSkge1xuXHRcdFx0XHRcdFx0aWYgKGN1cnJlbnROYW1lICE9PSBqc3ZUbXBsKSB7XG5cdFx0XHRcdFx0XHRcdHZhbHVlID0gJHRlbXBsYXRlc1tjdXJyZW50TmFtZV07XG5cdFx0XHRcdFx0XHRcdGRlbGV0ZSAkdGVtcGxhdGVzW2N1cnJlbnROYW1lXTtcblx0XHRcdFx0XHRcdH0gZWxzZSBpZiAoJC5mbikge1xuXHRcdFx0XHRcdFx0XHR2YWx1ZSA9ICQuZGF0YShlbGVtKVtqc3ZUbXBsXTsgLy8gR2V0IGNhY2hlZCBjb21waWxlZCB0ZW1wbGF0ZVxuXHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdH1cblx0XHRcdFx0XHRpZiAoIWN1cnJlbnROYW1lIHx8ICF2YWx1ZSkgeyAvLyBOb3QgeWV0IGNvbXBpbGVkLCBvciBjYWNoZWQgdmVyc2lvbiBsb3N0XG5cdFx0XHRcdFx0XHRuYW1lID0gbmFtZSB8fCAoJC5mbiA/IGpzdlRtcGwgOiB2YWx1ZSk7XG5cdFx0XHRcdFx0XHR2YWx1ZSA9IGNvbXBpbGVUbXBsKG5hbWUsIGVsZW0uaW5uZXJIVE1MLCBwYXJlbnRUbXBsLCBvcHRpb25zKTtcblx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0dmFsdWUudG1wbE5hbWUgPSBuYW1lID0gbmFtZSB8fCBjdXJyZW50TmFtZTtcblx0XHRcdFx0XHRpZiAobmFtZSAhPT0ganN2VG1wbCkge1xuXHRcdFx0XHRcdFx0JHRlbXBsYXRlc1tuYW1lXSA9IHZhbHVlO1xuXHRcdFx0XHRcdH1cblx0XHRcdFx0XHRlbGVtLnNldEF0dHJpYnV0ZSh0bXBsQXR0ciwgbmFtZSk7XG5cdFx0XHRcdFx0aWYgKCQuZm4pIHtcblx0XHRcdFx0XHRcdCQuZGF0YShlbGVtLCBqc3ZUbXBsLCB2YWx1ZSk7XG5cdFx0XHRcdFx0fVxuXHRcdFx0XHR9XG5cdFx0XHR9IC8vIEVORCBCUk9XU0VSLVNQRUNJRklDIENPREVcblx0XHRcdGVsZW0gPSB1bmRlZmluZWQ7XG5cdFx0fSBlbHNlIGlmICghdmFsdWUuZm4pIHtcblx0XHRcdHZhbHVlID0gdW5kZWZpbmVkO1xuXHRcdFx0Ly8gSWYgdmFsdWUgaXMgbm90IGEgc3RyaW5nLiBIVE1MIGVsZW1lbnQsIG9yIGNvbXBpbGVkIHRlbXBsYXRlLCByZXR1cm4gdW5kZWZpbmVkXG5cdFx0fVxuXHRcdHJldHVybiB2YWx1ZTtcblx0fVxuXG5cdHZhciBlbGVtLCBjb21waWxlZFRtcGwsXG5cdFx0dG1wbE9yTWFya3VwID0gdG1wbCA9IHRtcGwgfHwgXCJcIjtcblx0JHN1Yi5faHRtbCA9ICRjb252ZXJ0ZXJzLmh0bWw7XG5cblx0Ly89PT09IENvbXBpbGUgdGhlIHRlbXBsYXRlID09PT1cblx0aWYgKG9wdGlvbnMgPT09IDApIHtcblx0XHRvcHRpb25zID0gdW5kZWZpbmVkO1xuXHRcdHRtcGxPck1hcmt1cCA9IGxvb2t1cFRlbXBsYXRlKHRtcGxPck1hcmt1cCk7IC8vIFRvcC1sZXZlbCBjb21waWxlIHNvIGRvIGEgdGVtcGxhdGUgbG9va3VwXG5cdH1cblxuXHQvLyBJZiBvcHRpb25zLCB0aGVuIHRoaXMgd2FzIGFscmVhZHkgY29tcGlsZWQgZnJvbSBhIChzY3JpcHQpIGVsZW1lbnQgdGVtcGxhdGUgZGVjbGFyYXRpb24uXG5cdC8vIElmIG5vdCwgdGhlbiBpZiB0bXBsIGlzIGEgdGVtcGxhdGUgb2JqZWN0LCB1c2UgaXQgZm9yIG9wdGlvbnNcblx0b3B0aW9ucyA9IG9wdGlvbnMgfHwgKHRtcGwubWFya3VwXG5cdFx0PyB0bXBsLmJuZHNcblx0XHRcdD8gJGV4dGVuZCh7fSwgdG1wbClcblx0XHRcdDogdG1wbFxuXHRcdDoge31cblx0KTtcblxuXHRvcHRpb25zLnRtcGxOYW1lID0gb3B0aW9ucy50bXBsTmFtZSB8fCBuYW1lIHx8IFwidW5uYW1lZFwiO1xuXHRpZiAocGFyZW50VG1wbCkge1xuXHRcdG9wdGlvbnMuX3BhcmVudFRtcGwgPSBwYXJlbnRUbXBsO1xuXHR9XG5cdC8vIElmIHRtcGwgaXMgbm90IGEgbWFya3VwIHN0cmluZyBvciBhIHNlbGVjdG9yIHN0cmluZywgdGhlbiBpdCBtdXN0IGJlIGEgdGVtcGxhdGUgb2JqZWN0XG5cdC8vIEluIHRoYXQgY2FzZSwgZ2V0IGl0IGZyb20gdGhlIG1hcmt1cCBwcm9wZXJ0eSBvZiB0aGUgb2JqZWN0XG5cdGlmICghdG1wbE9yTWFya3VwICYmIHRtcGwubWFya3VwICYmICh0bXBsT3JNYXJrdXAgPSBsb29rdXBUZW1wbGF0ZSh0bXBsLm1hcmt1cCkpICYmIHRtcGxPck1hcmt1cC5mbikge1xuXHRcdC8vIElmIHRoZSBzdHJpbmcgcmVmZXJlbmNlcyBhIGNvbXBpbGVkIHRlbXBsYXRlIG9iamVjdCwgbmVlZCB0byByZWNvbXBpbGUgdG8gbWVyZ2UgYW55IG1vZGlmaWVkIG9wdGlvbnNcblx0XHR0bXBsT3JNYXJrdXAgPSB0bXBsT3JNYXJrdXAubWFya3VwO1xuXHR9XG5cdGlmICh0bXBsT3JNYXJrdXAgIT09IHVuZGVmaW5lZCkge1xuXHRcdGlmICh0bXBsT3JNYXJrdXAucmVuZGVyIHx8IHRtcGwucmVuZGVyKSB7XG5cdFx0XHQvLyB0bXBsIGlzIGFscmVhZHkgY29tcGlsZWQsIHNvIHVzZSBpdFxuXHRcdFx0aWYgKHRtcGxPck1hcmt1cC50bXBscykge1xuXHRcdFx0XHRjb21waWxlZFRtcGwgPSB0bXBsT3JNYXJrdXA7XG5cdFx0XHR9XG5cdFx0fSBlbHNlIHtcblx0XHRcdC8vIHRtcGxPck1hcmt1cCBpcyBhIG1hcmt1cCBzdHJpbmcsIG5vdCBhIGNvbXBpbGVkIHRlbXBsYXRlXG5cdFx0XHQvLyBDcmVhdGUgdGVtcGxhdGUgb2JqZWN0XG5cdFx0XHR0bXBsID0gdG1wbE9iamVjdCh0bXBsT3JNYXJrdXAsIG9wdGlvbnMpO1xuXHRcdFx0Ly8gQ29tcGlsZSB0byBBU1QgYW5kIHRoZW4gdG8gY29tcGlsZWQgZnVuY3Rpb25cblx0XHRcdHRtcGxGbih0bXBsT3JNYXJrdXAucmVwbGFjZShyRXNjYXBlUXVvdGVzLCBcIlxcXFwkJlwiKSwgdG1wbCk7XG5cdFx0fVxuXHRcdGlmICghY29tcGlsZWRUbXBsKSB7XG5cdFx0XHRjb21waWxlZFRtcGwgPSAkZXh0ZW5kKGZ1bmN0aW9uKCkge1xuXHRcdFx0XHRyZXR1cm4gY29tcGlsZWRUbXBsLnJlbmRlci5hcHBseShjb21waWxlZFRtcGwsIGFyZ3VtZW50cyk7XG5cdFx0XHR9LCB0bXBsKTtcblxuXHRcdFx0Y29tcGlsZUNoaWxkUmVzb3VyY2VzKGNvbXBpbGVkVG1wbCk7XG5cdFx0fVxuXHRcdHJldHVybiBjb21waWxlZFRtcGw7XG5cdH1cbn1cblxuLy89PT09IC9lbmQgb2YgZnVuY3Rpb24gY29tcGlsZVRtcGwgPT09PVxuXG4vLz09PT09PT09PT09PT09PT09XG4vLyBjb21waWxlVmlld01vZGVsXG4vLz09PT09PT09PT09PT09PT09XG5cbmZ1bmN0aW9uIGdldERlZmF1bHRWYWwoZGVmYXVsdFZhbCwgZGF0YSkge1xuXHRyZXR1cm4gJGlzRnVuY3Rpb24oZGVmYXVsdFZhbClcblx0XHQ/IGRlZmF1bHRWYWwuY2FsbChkYXRhKVxuXHRcdDogZGVmYXVsdFZhbDtcbn1cblxuZnVuY3Rpb24gdW5tYXBBcnJheShtb2RlbEFycikge1xuXHRcdHZhciBhcnIgPSBbXSxcblx0XHRcdGkgPSAwLFxuXHRcdFx0bCA9IG1vZGVsQXJyLmxlbmd0aDtcblx0XHRmb3IgKDsgaTxsOyBpKyspIHtcblx0XHRcdGFyci5wdXNoKG1vZGVsQXJyW2ldLnVubWFwKCkpO1xuXHRcdH1cblx0XHRyZXR1cm4gYXJyO1xufVxuXG5mdW5jdGlvbiBjb21waWxlVmlld01vZGVsKG5hbWUsIHR5cGUpIHtcblx0dmFyIGksIGNvbnN0cnVjdG9yLFxuXHRcdHZpZXdNb2RlbHMgPSB0aGlzLFxuXHRcdGdldHRlcnMgPSB0eXBlLmdldHRlcnMsXG5cdFx0ZXh0ZW5kID0gdHlwZS5leHRlbmQsXG5cdFx0aWQgPSB0eXBlLmlkLFxuXHRcdHByb3RvID0gJC5leHRlbmQoe1xuXHRcdFx0X2lzOiBuYW1lIHx8IFwidW5uYW1lZFwiLFxuXHRcdFx0dW5tYXA6IHVubWFwLFxuXHRcdFx0bWVyZ2U6IG1lcmdlXG5cdFx0fSwgZXh0ZW5kKSxcblx0XHRhcmdzID0gXCJcIixcblx0XHRib2R5ID0gXCJcIixcblx0XHRnID0gZ2V0dGVycyA/IGdldHRlcnMubGVuZ3RoIDogMCxcblx0XHQkb2JzZXJ2YWJsZSA9ICQub2JzZXJ2YWJsZSxcblx0XHRnZXR0ZXJOYW1lcyA9IHt9O1xuXG5cdGZ1bmN0aW9uIEdldE5ldyhhcmdzKSB7XG5cdFx0Y29uc3RydWN0b3IuYXBwbHkodGhpcywgYXJncyk7XG5cdH1cblxuXHRmdW5jdGlvbiB2bSgpIHtcblx0XHRyZXR1cm4gbmV3IEdldE5ldyhhcmd1bWVudHMpO1xuXHR9XG5cblx0ZnVuY3Rpb24gaXRlcmF0ZShkYXRhLCBhY3Rpb24pIHtcblx0XHR2YXIgZ2V0dGVyVHlwZSwgZGVmYXVsdFZhbCwgcHJvcCwgb2IsXG5cdFx0XHRqID0gMDtcblx0XHRmb3IgKDsgajxnOyBqKyspIHtcblx0XHRcdHByb3AgPSBnZXR0ZXJzW2pdO1xuXHRcdFx0Z2V0dGVyVHlwZSA9IHVuZGVmaW5lZDtcblx0XHRcdGlmIChwcm9wICsgXCJcIiAhPT0gcHJvcCkge1xuXHRcdFx0XHRnZXR0ZXJUeXBlID0gcHJvcDtcblx0XHRcdFx0cHJvcCA9IGdldHRlclR5cGUuZ2V0dGVyO1xuXHRcdFx0fVxuXHRcdFx0aWYgKChvYiA9IGRhdGFbcHJvcF0pID09PSB1bmRlZmluZWQgJiYgZ2V0dGVyVHlwZSAmJiAoZGVmYXVsdFZhbCA9IGdldHRlclR5cGUuZGVmYXVsdFZhbCkgIT09IHVuZGVmaW5lZCkge1xuXHRcdFx0XHRvYiA9IGdldERlZmF1bHRWYWwoZGVmYXVsdFZhbCwgZGF0YSk7XG5cdFx0XHR9XG5cdFx0XHRhY3Rpb24ob2IsIGdldHRlclR5cGUgJiYgdmlld01vZGVsc1tnZXR0ZXJUeXBlLnR5cGVdLCBwcm9wKTtcblx0XHR9XG5cdH1cblxuXHRmdW5jdGlvbiBtYXAoZGF0YSkge1xuXHRcdGRhdGEgPSBkYXRhICsgXCJcIiA9PT0gZGF0YVxuXHRcdFx0PyBKU09OLnBhcnNlKGRhdGEpIC8vIEFjY2VwdCBKU09OIHN0cmluZ1xuXHRcdFx0OiBkYXRhOyAgICAgICAgICAgIC8vIG9yIG9iamVjdC9hcnJheVxuXHRcdHZhciBsLCBwcm9wLFxuXHRcdFx0aiA9IDAsXG5cdFx0XHRvYiA9IGRhdGEsXG5cdFx0XHRhcnIgPSBbXTtcblxuXHRcdGlmICgkaXNBcnJheShkYXRhKSkge1xuXHRcdFx0ZGF0YSA9IGRhdGEgfHwgW107XG5cdFx0XHRsID0gZGF0YS5sZW5ndGg7XG5cdFx0XHRmb3IgKDsgajxsOyBqKyspIHtcblx0XHRcdFx0YXJyLnB1c2godGhpcy5tYXAoZGF0YVtqXSkpO1xuXHRcdFx0fVxuXHRcdFx0YXJyLl9pcyA9IG5hbWU7XG5cdFx0XHRhcnIudW5tYXAgPSB1bm1hcDtcblx0XHRcdGFyci5tZXJnZSA9IG1lcmdlO1xuXHRcdFx0cmV0dXJuIGFycjtcblx0XHR9XG5cblx0XHRpZiAoZGF0YSkge1xuXHRcdFx0aXRlcmF0ZShkYXRhLCBmdW5jdGlvbihvYiwgdmlld01vZGVsKSB7XG5cdFx0XHRcdGlmICh2aWV3TW9kZWwpIHsgLy8gSXRlcmF0ZSB0byBidWlsZCBnZXR0ZXJzIGFyZyBhcnJheSAodmFsdWUsIG9yIG1hcHBlZCB2YWx1ZSlcblx0XHRcdFx0XHRvYiA9IHZpZXdNb2RlbC5tYXAob2IpO1xuXHRcdFx0XHR9XG5cdFx0XHRcdGFyci5wdXNoKG9iKTtcblx0XHRcdH0pO1xuXG5cdFx0XHRvYiA9IHRoaXMuYXBwbHkodGhpcywgYXJyKTsgLy8gSW5zYW50aWF0ZSB0aGlzIFZpZXcgTW9kZWwsIHBhc3NpbmcgZ2V0dGVycyBhcmdzIGFycmF5IHRvIGNvbnN0cnVjdG9yXG5cdFx0XHRmb3IgKHByb3AgaW4gZGF0YSkgeyAvLyBDb3B5IG92ZXIgYW55IG90aGVyIHByb3BlcnRpZXMuIHRoYXQgYXJlIG5vdCBnZXQvc2V0IHByb3BlcnRpZXNcblx0XHRcdFx0aWYgKHByb3AgIT09ICRleHBhbmRvICYmICFnZXR0ZXJOYW1lc1twcm9wXSkge1xuXHRcdFx0XHRcdG9iW3Byb3BdID0gZGF0YVtwcm9wXTtcblx0XHRcdFx0fVxuXHRcdFx0fVxuXHRcdH1cblx0XHRyZXR1cm4gb2I7XG5cdH1cblxuXHRmdW5jdGlvbiBtZXJnZShkYXRhKSB7XG5cdFx0ZGF0YSA9IGRhdGEgKyBcIlwiID09PSBkYXRhXG5cdFx0XHQ/IEpTT04ucGFyc2UoZGF0YSkgLy8gQWNjZXB0IEpTT04gc3RyaW5nXG5cdFx0XHQ6IGRhdGE7ICAgICAgICAgICAgLy8gb3Igb2JqZWN0L2FycmF5XG5cdFx0dmFyIGosIGwsIG0sIHByb3AsIG1vZCwgZm91bmQsIGFzc2lnbmVkLCBvYiwgbmV3TW9kQXJyLFxuXHRcdFx0ayA9IDAsXG5cdFx0XHRtb2RlbCA9IHRoaXM7XG5cblx0XHRpZiAoJGlzQXJyYXkobW9kZWwpKSB7XG5cdFx0XHRhc3NpZ25lZCA9IHt9O1xuXHRcdFx0bmV3TW9kQXJyID0gW107XG5cdFx0XHRsID0gZGF0YS5sZW5ndGg7XG5cdFx0XHRtID0gbW9kZWwubGVuZ3RoO1xuXHRcdFx0Zm9yICg7IGs8bDsgaysrKSB7XG5cdFx0XHRcdG9iID0gZGF0YVtrXTtcblx0XHRcdFx0Zm91bmQgPSBmYWxzZTtcblx0XHRcdFx0Zm9yIChqPTA7IGo8bSAmJiAhZm91bmQ7IGorKykge1xuXHRcdFx0XHRcdGlmIChhc3NpZ25lZFtqXSkge1xuXHRcdFx0XHRcdFx0Y29udGludWU7XG5cdFx0XHRcdFx0fVxuXHRcdFx0XHRcdG1vZCA9IG1vZGVsW2pdO1xuXG5cdFx0XHRcdFx0aWYgKGlkKSB7XG5cdFx0XHRcdFx0XHRhc3NpZ25lZFtqXSA9IGZvdW5kID0gaWQgKyBcIlwiID09PSBpZFxuXHRcdFx0XHRcdFx0PyAob2JbaWRdICYmIChnZXR0ZXJOYW1lc1tpZF0gPyBtb2RbaWRdKCkgOiBtb2RbaWRdKSA9PT0gb2JbaWRdKVxuXHRcdFx0XHRcdFx0OiBpZChtb2QsIG9iKTtcblx0XHRcdFx0XHR9XG5cdFx0XHRcdH1cblx0XHRcdFx0aWYgKGZvdW5kKSB7XG5cdFx0XHRcdFx0bW9kLm1lcmdlKG9iKTtcblx0XHRcdFx0XHRuZXdNb2RBcnIucHVzaChtb2QpO1xuXHRcdFx0XHR9IGVsc2Uge1xuXHRcdFx0XHRcdG5ld01vZEFyci5wdXNoKHZtLm1hcChvYikpO1xuXHRcdFx0XHR9XG5cdFx0XHR9XG5cdFx0XHRpZiAoJG9ic2VydmFibGUpIHtcblx0XHRcdFx0JG9ic2VydmFibGUobW9kZWwpLnJlZnJlc2gobmV3TW9kQXJyLCB0cnVlKTtcblx0XHRcdH0gZWxzZSB7XG5cdFx0XHRcdG1vZGVsLnNwbGljZS5hcHBseShtb2RlbCwgWzAsIG1vZGVsLmxlbmd0aF0uY29uY2F0KG5ld01vZEFycikpO1xuXHRcdFx0fVxuXHRcdFx0cmV0dXJuO1xuXHRcdH1cblx0XHRpdGVyYXRlKGRhdGEsIGZ1bmN0aW9uKG9iLCB2aWV3TW9kZWwsIGdldHRlcikge1xuXHRcdFx0aWYgKHZpZXdNb2RlbCkge1xuXHRcdFx0XHRtb2RlbFtnZXR0ZXJdKCkubWVyZ2Uob2IpOyAvLyBVcGRhdGUgdHlwZWQgcHJvcGVydHlcblx0XHRcdH0gZWxzZSB7XG5cdFx0XHRcdG1vZGVsW2dldHRlcl0ob2IpOyAvLyBVcGRhdGUgbm9uLXR5cGVkIHByb3BlcnR5XG5cdFx0XHR9XG5cdFx0fSk7XG5cdFx0Zm9yIChwcm9wIGluIGRhdGEpIHtcblx0XHRcdGlmIChwcm9wICE9PSAkZXhwYW5kbyAmJiAhZ2V0dGVyTmFtZXNbcHJvcF0pIHtcblx0XHRcdFx0bW9kZWxbcHJvcF0gPSBkYXRhW3Byb3BdO1xuXHRcdFx0fVxuXHRcdH1cblx0fVxuXG5cdGZ1bmN0aW9uIHVubWFwKCkge1xuXHRcdHZhciBvYiwgcHJvcCwgZ2V0dGVyVHlwZSwgYXJyLCB2YWx1ZSxcblx0XHRcdGsgPSAwLFxuXHRcdFx0bW9kZWwgPSB0aGlzO1xuXG5cdFx0aWYgKCRpc0FycmF5KG1vZGVsKSkge1xuXHRcdFx0cmV0dXJuIHVubWFwQXJyYXkobW9kZWwpO1xuXHRcdH1cblx0XHRvYiA9IHt9O1xuXHRcdGZvciAoOyBrPGc7IGsrKykge1xuXHRcdFx0cHJvcCA9IGdldHRlcnNba107XG5cdFx0XHRnZXR0ZXJUeXBlID0gdW5kZWZpbmVkO1xuXHRcdFx0aWYgKHByb3AgKyBcIlwiICE9PSBwcm9wKSB7XG5cdFx0XHRcdGdldHRlclR5cGUgPSBwcm9wO1xuXHRcdFx0XHRwcm9wID0gZ2V0dGVyVHlwZS5nZXR0ZXI7XG5cdFx0XHR9XG5cdFx0XHR2YWx1ZSA9IG1vZGVsW3Byb3BdKCk7XG5cdFx0XHRvYltwcm9wXSA9IGdldHRlclR5cGUgJiYgdmFsdWUgJiYgdmlld01vZGVsc1tnZXR0ZXJUeXBlLnR5cGVdXG5cdFx0XHRcdD8gJGlzQXJyYXkodmFsdWUpXG5cdFx0XHRcdFx0PyB1bm1hcEFycmF5KHZhbHVlKVxuXHRcdFx0XHRcdDogdmFsdWUudW5tYXAoKVxuXHRcdFx0XHQ6IHZhbHVlO1xuXHRcdH1cblx0XHRmb3IgKHByb3AgaW4gbW9kZWwpIHtcblx0XHRcdGlmIChwcm9wICE9PSBcIl9pc1wiICYmICFnZXR0ZXJOYW1lc1twcm9wXSAmJiBwcm9wICE9PSAkZXhwYW5kbyAgJiYgKHByb3AuY2hhckF0KDApICE9PSBcIl9cIiB8fCAhZ2V0dGVyTmFtZXNbcHJvcC5zbGljZSgxKV0pICYmICEkaXNGdW5jdGlvbihtb2RlbFtwcm9wXSkpIHtcblx0XHRcdFx0b2JbcHJvcF0gPSBtb2RlbFtwcm9wXTtcblx0XHRcdH1cblx0XHR9XG5cdFx0cmV0dXJuIG9iO1xuXHR9XG5cblx0R2V0TmV3LnByb3RvdHlwZSA9IHByb3RvO1xuXG5cdGZvciAoaT0wOyBpPGc7IGkrKykge1xuXHRcdChmdW5jdGlvbihnZXR0ZXIpIHtcblx0XHRcdGdldHRlciA9IGdldHRlci5nZXR0ZXIgfHwgZ2V0dGVyO1xuXHRcdFx0Z2V0dGVyTmFtZXNbZ2V0dGVyXSA9IGkrMTtcblx0XHRcdHZhciBwcml2RmllbGQgPSBcIl9cIiArIGdldHRlcjtcblxuXHRcdFx0YXJncyArPSAoYXJncyA/IFwiLFwiIDogXCJcIikgKyBnZXR0ZXI7XG5cdFx0XHRib2R5ICs9IFwidGhpcy5cIiArIHByaXZGaWVsZCArIFwiID0gXCIgKyBnZXR0ZXIgKyBcIjtcXG5cIjtcblx0XHRcdHByb3RvW2dldHRlcl0gPSBwcm90b1tnZXR0ZXJdIHx8IGZ1bmN0aW9uKHZhbCkge1xuXHRcdFx0XHRpZiAoIWFyZ3VtZW50cy5sZW5ndGgpIHtcblx0XHRcdFx0XHRyZXR1cm4gdGhpc1twcml2RmllbGRdOyAvLyBJZiB0aGVyZSBpcyBubyBhcmd1bWVudCwgdXNlIGFzIGEgZ2V0dGVyXG5cdFx0XHRcdH1cblx0XHRcdFx0aWYgKCRvYnNlcnZhYmxlKSB7XG5cdFx0XHRcdFx0JG9ic2VydmFibGUodGhpcykuc2V0UHJvcGVydHkoZ2V0dGVyLCB2YWwpO1xuXHRcdFx0XHR9IGVsc2Uge1xuXHRcdFx0XHRcdHRoaXNbcHJpdkZpZWxkXSA9IHZhbDtcblx0XHRcdFx0fVxuXHRcdFx0fTtcblxuXHRcdFx0aWYgKCRvYnNlcnZhYmxlKSB7XG5cdFx0XHRcdHByb3RvW2dldHRlcl0uc2V0ID0gcHJvdG9bZ2V0dGVyXS5zZXQgfHwgZnVuY3Rpb24odmFsKSB7XG5cdFx0XHRcdFx0dGhpc1twcml2RmllbGRdID0gdmFsOyAvLyBTZXR0ZXIgY2FsbGVkIGJ5IG9ic2VydmFibGUgcHJvcGVydHkgY2hhbmdlXG5cdFx0XHRcdH07XG5cdFx0XHR9XG5cdFx0fSkoZ2V0dGVyc1tpXSk7XG5cdH1cblxuXHRjb25zdHJ1Y3RvciA9IG5ldyBGdW5jdGlvbihhcmdzLCBib2R5LnNsaWNlKDAsIC0xKSk7XG5cdGNvbnN0cnVjdG9yLnByb3RvdHlwZSA9IHByb3RvO1xuXHRwcm90by5jb25zdHJ1Y3RvciA9IGNvbnN0cnVjdG9yO1xuXG5cdHZtLm1hcCA9IG1hcDtcblx0dm0uZ2V0dGVycyA9IGdldHRlcnM7XG5cdHZtLmV4dGVuZCA9IGV4dGVuZDtcblx0dm0uaWQgPSBpZDtcblx0cmV0dXJuIHZtO1xufVxuXG5mdW5jdGlvbiB0bXBsT2JqZWN0KG1hcmt1cCwgb3B0aW9ucykge1xuXHQvLyBUZW1wbGF0ZSBvYmplY3QgY29uc3RydWN0b3Jcblx0dmFyIGh0bWxUYWcsXG5cdFx0d3JhcE1hcCA9ICRzdWJTZXR0aW5nc0FkdmFuY2VkLl93bSB8fCB7fSwgLy8gT25seSB1c2VkIGluIEpzVmlld3MuIE90aGVyd2lzZSBlbXB0eToge31cblx0XHR0bXBsID0ge1xuXHRcdFx0dG1wbHM6IFtdLFxuXHRcdFx0bGlua3M6IHt9LCAvLyBDb21waWxlZCBmdW5jdGlvbnMgZm9yIGxpbmsgZXhwcmVzc2lvbnNcblx0XHRcdGJuZHM6IFtdLFxuXHRcdFx0X2lzOiBcInRlbXBsYXRlXCIsXG5cdFx0XHRyZW5kZXI6IHJlbmRlckNvbnRlbnRcblx0XHR9O1xuXG5cdGlmIChvcHRpb25zKSB7XG5cdFx0dG1wbCA9ICRleHRlbmQodG1wbCwgb3B0aW9ucyk7XG5cdH1cblxuXHR0bXBsLm1hcmt1cCA9IG1hcmt1cDtcblx0aWYgKCF0bXBsLmh0bWxUYWcpIHtcblx0XHQvLyBTZXQgdG1wbC50YWcgdG8gdGhlIHRvcC1sZXZlbCBIVE1MIHRhZyB1c2VkIGluIHRoZSB0ZW1wbGF0ZSwgaWYgYW55Li4uXG5cdFx0aHRtbFRhZyA9IHJGaXJzdEVsZW0uZXhlYyhtYXJrdXApO1xuXHRcdHRtcGwuaHRtbFRhZyA9IGh0bWxUYWcgPyBodG1sVGFnWzFdLnRvTG93ZXJDYXNlKCkgOiBcIlwiO1xuXHR9XG5cdGh0bWxUYWcgPSB3cmFwTWFwW3RtcGwuaHRtbFRhZ107XG5cdGlmIChodG1sVGFnICYmIGh0bWxUYWcgIT09IHdyYXBNYXAuZGl2KSB7XG5cdFx0Ly8gV2hlbiB1c2luZyBKc1ZpZXdzLCB3ZSB0cmltIHRlbXBsYXRlcyB3aGljaCBhcmUgaW5zZXJ0ZWQgaW50byBIVE1MIGNvbnRleHRzIHdoZXJlIHRleHQgbm9kZXMgYXJlIG5vdCByZW5kZXJlZCAoaS5lLiBub3QgJ1BocmFzaW5nIENvbnRlbnQnKS5cblx0XHQvLyBDdXJyZW50bHkgbm90IHRyaW1tZWQgZm9yIDxsaT4gdGFnLiAoTm90IHdvcnRoIGFkZGluZyBwZXJmIGNvc3QpXG5cdFx0dG1wbC5tYXJrdXAgPSAkLnRyaW0odG1wbC5tYXJrdXApO1xuXHR9XG5cblx0cmV0dXJuIHRtcGw7XG59XG5cbi8vPT09PT09PT09PT09PT1cbi8vIHJlZ2lzdGVyU3RvcmVcbi8vPT09PT09PT09PT09PT1cblxuZnVuY3Rpb24gcmVnaXN0ZXJTdG9yZShzdG9yZU5hbWUsIHN0b3JlU2V0dGluZ3MpIHtcblxuXHRmdW5jdGlvbiB0aGVTdG9yZShuYW1lLCBpdGVtLCBwYXJlbnRUbXBsKSB7XG5cdFx0Ly8gVGhlIHN0b3JlIGlzIGFsc28gdGhlIGZ1bmN0aW9uIHVzZWQgdG8gYWRkIGl0ZW1zIHRvIHRoZSBzdG9yZS4gZS5nLiAkLnRlbXBsYXRlcywgb3IgJC52aWV3cy50YWdzXG5cblx0XHQvLyBGb3Igc3RvcmUgb2YgbmFtZSAndGhpbmcnLCBDYWxsIGFzOlxuXHRcdC8vICAgICQudmlld3MudGhpbmdzKGl0ZW1zWywgcGFyZW50VG1wbF0pLFxuXHRcdC8vIG9yICQudmlld3MudGhpbmdzKG5hbWUsIGl0ZW1bLCBwYXJlbnRUbXBsXSlcblxuXHRcdHZhciBjb21waWxlLCBpdGVtTmFtZSwgdGhpc1N0b3JlLCBjbnQsXG5cdFx0XHRvblN0b3JlID0gJHN1Yi5vblN0b3JlW3N0b3JlTmFtZV07XG5cblx0XHRpZiAobmFtZSAmJiB0eXBlb2YgbmFtZSA9PT0gT0JKRUNUICYmICFuYW1lLm5vZGVUeXBlICYmICFuYW1lLm1hcmt1cCAmJiAhbmFtZS5nZXRUZ3QgJiYgIShzdG9yZU5hbWUgPT09IFwidmlld01vZGVsXCIgJiYgbmFtZS5nZXR0ZXJzIHx8IG5hbWUuZXh0ZW5kKSkge1xuXHRcdFx0Ly8gQ2FsbCB0byAkLnZpZXdzLnRoaW5ncyhpdGVtc1ssIHBhcmVudFRtcGxdKSxcblxuXHRcdFx0Ly8gQWRkaW5nIGl0ZW1zIHRvIHRoZSBzdG9yZVxuXHRcdFx0Ly8gSWYgbmFtZSBpcyBhIGhhc2gsIHRoZW4gaXRlbSBpcyBwYXJlbnRUbXBsLiBJdGVyYXRlIG92ZXIgaGFzaCBhbmQgY2FsbCBzdG9yZSBmb3Iga2V5LlxuXHRcdFx0Zm9yIChpdGVtTmFtZSBpbiBuYW1lKSB7XG5cdFx0XHRcdHRoZVN0b3JlKGl0ZW1OYW1lLCBuYW1lW2l0ZW1OYW1lXSwgaXRlbSk7XG5cdFx0XHR9XG5cdFx0XHRyZXR1cm4gaXRlbSB8fCAkdmlld3M7XG5cdFx0fVxuXHRcdC8vIEFkZGluZyBhIHNpbmdsZSB1bm5hbWVkIGl0ZW0gdG8gdGhlIHN0b3JlXG5cdFx0aWYgKGl0ZW0gPT09IHVuZGVmaW5lZCkge1xuXHRcdFx0aXRlbSA9IG5hbWU7XG5cdFx0XHRuYW1lID0gdW5kZWZpbmVkO1xuXHRcdH1cblx0XHRpZiAobmFtZSAmJiBcIlwiICsgbmFtZSAhPT0gbmFtZSkgeyAvLyBuYW1lIG11c3QgYmUgYSBzdHJpbmdcblx0XHRcdHBhcmVudFRtcGwgPSBpdGVtO1xuXHRcdFx0aXRlbSA9IG5hbWU7XG5cdFx0XHRuYW1lID0gdW5kZWZpbmVkO1xuXHRcdH1cblx0XHR0aGlzU3RvcmUgPSBwYXJlbnRUbXBsXG5cdFx0XHQ/IHN0b3JlTmFtZSA9PT0gXCJ2aWV3TW9kZWxcIlxuXHRcdFx0XHQ/IHBhcmVudFRtcGxcblx0XHRcdFx0OiAocGFyZW50VG1wbFtzdG9yZU5hbWVzXSA9IHBhcmVudFRtcGxbc3RvcmVOYW1lc10gfHwge30pXG5cdFx0XHQ6IHRoZVN0b3JlO1xuXHRcdGNvbXBpbGUgPSBzdG9yZVNldHRpbmdzLmNvbXBpbGU7XG5cblx0XHRpZiAoaXRlbSA9PT0gbnVsbCkge1xuXHRcdFx0Ly8gSWYgaXRlbSBpcyBudWxsLCBkZWxldGUgdGhpcyBlbnRyeVxuXHRcdFx0aWYgKG5hbWUpIHtcblx0XHRcdFx0ZGVsZXRlIHRoaXNTdG9yZVtuYW1lXTtcblx0XHRcdH1cblx0XHR9IGVsc2Uge1xuXHRcdFx0aWYgKGNvbXBpbGUpIHtcblx0XHRcdFx0aXRlbSA9IGNvbXBpbGUuY2FsbCh0aGlzU3RvcmUsIG5hbWUsIGl0ZW0sIHBhcmVudFRtcGwsIDApIHx8IHt9O1xuXHRcdFx0XHRpdGVtLl9pcyA9IHN0b3JlTmFtZTsgLy8gT25seSBkbyB0aGlzIGZvciBjb21waWxlZCBvYmplY3RzICh0YWdzLCB0ZW1wbGF0ZXMuLi4pXG5cdFx0XHR9XG5cdFx0XHRpZiAobmFtZSkge1xuXHRcdFx0XHR0aGlzU3RvcmVbbmFtZV0gPSBpdGVtO1xuXHRcdFx0fVxuXHRcdH1cblx0XHRpZiAob25TdG9yZSkge1xuXHRcdFx0Ly8gZS5nLiBKc1ZpZXdzIGludGVncmF0aW9uXG5cdFx0XHRvblN0b3JlKG5hbWUsIGl0ZW0sIHBhcmVudFRtcGwsIGNvbXBpbGUpO1xuXHRcdH1cblx0XHRyZXR1cm4gaXRlbTtcblx0fVxuXG5cdHZhciBzdG9yZU5hbWVzID0gc3RvcmVOYW1lICsgXCJzXCI7XG5cdCR2aWV3c1tzdG9yZU5hbWVzXSA9IHRoZVN0b3JlO1xufVxuXG5mdW5jdGlvbiBhZGRTZXR0aW5nKHN0KSB7XG5cdCR2aWV3c1NldHRpbmdzW3N0XSA9IGZ1bmN0aW9uKHZhbHVlKSB7XG5cdFx0cmV0dXJuIGFyZ3VtZW50cy5sZW5ndGhcblx0XHRcdD8gKCRzdWJTZXR0aW5nc1tzdF0gPSB2YWx1ZSwgJHZpZXdzU2V0dGluZ3MpXG5cdFx0XHQ6ICRzdWJTZXR0aW5nc1tzdF07XG5cdH07XG59XG5cbi8vPT09PT09PT09PT09PT09PT09PT09PT09XG4vLyBkYXRhTWFwIGZvciByZW5kZXIgb25seVxuLy89PT09PT09PT09PT09PT09PT09PT09PT1cblxuZnVuY3Rpb24gZGF0YU1hcChtYXBEZWYpIHtcblx0ZnVuY3Rpb24gTWFwKHNvdXJjZSwgb3B0aW9ucykge1xuXHRcdHRoaXMudGd0ID0gbWFwRGVmLmdldFRndChzb3VyY2UsIG9wdGlvbnMpO1xuXHRcdG9wdGlvbnMubWFwID0gdGhpcztcblx0fVxuXG5cdGlmICgkaXNGdW5jdGlvbihtYXBEZWYpKSB7XG5cdFx0Ly8gU2ltcGxlIG1hcCBkZWNsYXJlZCBhcyBmdW5jdGlvblxuXHRcdG1hcERlZiA9IHtcblx0XHRcdGdldFRndDogbWFwRGVmXG5cdFx0fTtcblx0fVxuXG5cdGlmIChtYXBEZWYuYmFzZU1hcCkge1xuXHRcdG1hcERlZiA9ICRleHRlbmQoJGV4dGVuZCh7fSwgbWFwRGVmLmJhc2VNYXApLCBtYXBEZWYpO1xuXHR9XG5cblx0bWFwRGVmLm1hcCA9IGZ1bmN0aW9uKHNvdXJjZSwgb3B0aW9ucykge1xuXHRcdHJldHVybiBuZXcgTWFwKHNvdXJjZSwgb3B0aW9ucyk7XG5cdH07XG5cdHJldHVybiBtYXBEZWY7XG59XG5cbi8vPT09PT09PT09PT09PT1cbi8vIHJlbmRlckNvbnRlbnRcbi8vPT09PT09PT09PT09PT1cblxuZnVuY3Rpb24gcmVuZGVyQ29udGVudChkYXRhLCBjb250ZXh0LCBub0l0ZXJhdGlvbiwgcGFyZW50Vmlldywga2V5LCBvblJlbmRlcikge1xuXHR2YXIgaSwgbCwgdGFnLCB0bXBsLCB0YWdDdHgsIGlzVG9wUmVuZGVyQ2FsbCwgcHJldkRhdGEsIHByZXZJbmRleCxcblx0XHR2aWV3ID0gcGFyZW50Vmlldyxcblx0XHRyZXN1bHQgPSBcIlwiO1xuXG5cdGlmIChjb250ZXh0ID09PSB0cnVlKSB7XG5cdFx0bm9JdGVyYXRpb24gPSBjb250ZXh0OyAvLyBwYXNzaW5nIGJvb2xlYW4gYXMgc2Vjb25kIHBhcmFtIC0gbm9JdGVyYXRpb25cblx0XHRjb250ZXh0ID0gdW5kZWZpbmVkO1xuXHR9IGVsc2UgaWYgKHR5cGVvZiBjb250ZXh0ICE9PSBPQkpFQ1QpIHtcblx0XHRjb250ZXh0ID0gdW5kZWZpbmVkOyAvLyBjb250ZXh0IG11c3QgYmUgYSBib29sZWFuIChub0l0ZXJhdGlvbikgb3IgYSBwbGFpbiBvYmplY3Rcblx0fVxuXG5cdGlmICh0YWcgPSB0aGlzLnRhZykge1xuXHRcdC8vIFRoaXMgaXMgYSBjYWxsIGZyb20gcmVuZGVyVGFnIG9yIHRhZ0N0eC5yZW5kZXIoLi4uKVxuXHRcdHRhZ0N0eCA9IHRoaXM7XG5cdFx0dmlldyA9IHZpZXcgfHwgdGFnQ3R4LnZpZXc7XG5cdFx0dG1wbCA9IHZpZXcuZ2V0VG1wbCh0YWcudGVtcGxhdGUgfHwgdGFnQ3R4LnRtcGwpO1xuXHRcdGlmICghYXJndW1lbnRzLmxlbmd0aCkge1xuXHRcdFx0ZGF0YSA9IHRhZy5jb250ZW50Q3R4ICYmICRpc0Z1bmN0aW9uKHRhZy5jb250ZW50Q3R4KVxuXHRcdFx0XHQ/IGRhdGEgPSB0YWcuY29udGVudEN0eChkYXRhKVxuXHRcdFx0XHQ6IHZpZXc7IC8vIERlZmF1bHQgZGF0YSBjb250ZXh0IGZvciB3cmFwcGVkIGJsb2NrIGNvbnRlbnQgaXMgdGhlIGZpcnN0IGFyZ3VtZW50XG5cdFx0fVxuXHR9IGVsc2Uge1xuXHRcdC8vIFRoaXMgaXMgYSB0ZW1wbGF0ZS5yZW5kZXIoLi4uKSBjYWxsXG5cdFx0dG1wbCA9IHRoaXM7XG5cdH1cblxuXHRpZiAodG1wbCkge1xuXHRcdGlmICghcGFyZW50VmlldyAmJiBkYXRhICYmIGRhdGEuX2lzID09PSBcInZpZXdcIikge1xuXHRcdFx0dmlldyA9IGRhdGE7IC8vIFdoZW4gcGFzc2luZyBpbiBhIHZpZXcgdG8gcmVuZGVyIG9yIGxpbmsgKGFuZCBub3QgcGFzc2luZyBpbiBhIHBhcmVudCB2aWV3KSB1c2UgdGhlIHBhc3NlZC1pbiB2aWV3IGFzIHBhcmVudFZpZXdcblx0XHR9XG5cblx0XHRpZiAodmlldyAmJiBkYXRhID09PSB2aWV3KSB7XG5cdFx0XHQvLyBJbmhlcml0IHRoZSBkYXRhIGZyb20gdGhlIHBhcmVudCB2aWV3LlxuXHRcdFx0ZGF0YSA9IHZpZXcuZGF0YTtcblx0XHR9XG5cblx0XHRpc1RvcFJlbmRlckNhbGwgPSAhdmlldztcblx0XHRpc1JlbmRlckNhbGwgPSBpc1JlbmRlckNhbGwgfHwgaXNUb3BSZW5kZXJDYWxsO1xuXHRcdGlmICghdmlldykge1xuXHRcdFx0KGNvbnRleHQgPSBjb250ZXh0IHx8IHt9KS5yb290ID0gZGF0YTsgLy8gUHJvdmlkZSB+cm9vdCBhcyBzaG9ydGN1dCB0byB0b3AtbGV2ZWwgZGF0YS5cblx0XHR9XG5cdFx0aWYgKCFpc1JlbmRlckNhbGwgfHwgJHN1YlNldHRpbmdzQWR2YW5jZWQudXNlVmlld3MgfHwgdG1wbC51c2VWaWV3cyB8fCB2aWV3ICYmIHZpZXcgIT09IHRvcFZpZXcpIHtcblx0XHRcdHJlc3VsdCA9IHJlbmRlcldpdGhWaWV3cyh0bXBsLCBkYXRhLCBjb250ZXh0LCBub0l0ZXJhdGlvbiwgdmlldywga2V5LCBvblJlbmRlciwgdGFnKTtcblx0XHR9IGVsc2Uge1xuXHRcdFx0aWYgKHZpZXcpIHsgLy8gSW4gYSBibG9ja1xuXHRcdFx0XHRwcmV2RGF0YSA9IHZpZXcuZGF0YTtcblx0XHRcdFx0cHJldkluZGV4ID0gdmlldy5pbmRleDtcblx0XHRcdFx0dmlldy5pbmRleCA9IGluZGV4U3RyO1xuXHRcdFx0fSBlbHNlIHtcblx0XHRcdFx0dmlldyA9IHRvcFZpZXc7XG5cdFx0XHRcdHByZXZEYXRhID0gdmlldy5kYXRhO1xuXHRcdFx0XHR2aWV3LmRhdGEgPSBkYXRhO1xuXHRcdFx0XHR2aWV3LmN0eCA9IGNvbnRleHQ7XG5cdFx0XHR9XG5cdFx0XHRpZiAoJGlzQXJyYXkoZGF0YSkgJiYgIW5vSXRlcmF0aW9uKSB7XG5cdFx0XHRcdC8vIENyZWF0ZSBhIHZpZXcgZm9yIHRoZSBhcnJheSwgd2hvc2UgY2hpbGQgdmlld3MgY29ycmVzcG9uZCB0byBlYWNoIGRhdGEgaXRlbS4gKE5vdGU6IGlmIGtleSBhbmQgcGFyZW50VmlldyBhcmUgcGFzc2VkIGluXG5cdFx0XHRcdC8vIGFsb25nIHdpdGggcGFyZW50IHZpZXcsIHRyZWF0IGFzIGluc2VydCAtZS5nLiBmcm9tIHZpZXcuYWRkVmlld3MgLSBzbyBwYXJlbnRWaWV3IGlzIGFscmVhZHkgdGhlIHZpZXcgaXRlbSBmb3IgYXJyYXkpXG5cdFx0XHRcdGZvciAoaSA9IDAsIGwgPSBkYXRhLmxlbmd0aDsgaSA8IGw7IGkrKykge1xuXHRcdFx0XHRcdHZpZXcuaW5kZXggPSBpO1xuXHRcdFx0XHRcdHZpZXcuZGF0YSA9IGRhdGFbaV07XG5cdFx0XHRcdFx0cmVzdWx0ICs9IHRtcGwuZm4oZGF0YVtpXSwgdmlldywgJHN1Yik7XG5cdFx0XHRcdH1cblx0XHRcdH0gZWxzZSB7XG5cdFx0XHRcdHZpZXcuZGF0YSA9IGRhdGE7XG5cdFx0XHRcdHJlc3VsdCArPSB0bXBsLmZuKGRhdGEsIHZpZXcsICRzdWIpO1xuXHRcdFx0fVxuXHRcdFx0dmlldy5kYXRhID0gcHJldkRhdGE7XG5cdFx0XHR2aWV3LmluZGV4ID0gcHJldkluZGV4O1xuXHRcdH1cblx0XHRpZiAoaXNUb3BSZW5kZXJDYWxsKSB7XG5cdFx0XHRpc1JlbmRlckNhbGwgPSB1bmRlZmluZWQ7XG5cdFx0fVxuXHR9XG5cdHJldHVybiByZXN1bHQ7XG59XG5cbmZ1bmN0aW9uIHJlbmRlcldpdGhWaWV3cyh0bXBsLCBkYXRhLCBjb250ZXh0LCBub0l0ZXJhdGlvbiwgdmlldywga2V5LCBvblJlbmRlciwgdGFnKSB7XG5cdGZ1bmN0aW9uIHNldEl0ZW1WYXIoaXRlbSkge1xuXHRcdC8vIFdoZW4gaXRlbVZhciBpcyBzcGVjaWZpZWQsIHNldCBtb2RpZmllZCBjdHggd2l0aCB1c2VyLW5hbWVkIH5pdGVtXG5cdFx0bmV3Q3R4ID0gJGV4dGVuZCh7fSwgY29udGV4dCk7XG5cdFx0bmV3Q3R4W2l0ZW1WYXJdID0gaXRlbTtcblx0fVxuXG5cdC8vIFJlbmRlciB0ZW1wbGF0ZSBhZ2FpbnN0IGRhdGEgYXMgYSB0cmVlIG9mIHN1YnZpZXdzIChuZXN0ZWQgcmVuZGVyZWQgdGVtcGxhdGUgaW5zdGFuY2VzKSwgb3IgYXMgYSBzdHJpbmcgKHRvcC1sZXZlbCB0ZW1wbGF0ZSkuXG5cdC8vIElmIHRoZSBkYXRhIGlzIHRoZSBwYXJlbnQgdmlldywgdHJlYXQgYXMgbm9JdGVyYXRpb24sIHJlLXJlbmRlciB3aXRoIHRoZSBzYW1lIGRhdGEgY29udGV4dC5cblx0Ly8gdG1wbCBjYW4gYmUgYSBzdHJpbmcgKGUuZy4gcmVuZGVyZWQgYnkgYSB0YWcucmVuZGVyKCkgbWV0aG9kKSwgb3IgYSBjb21waWxlZCB0ZW1wbGF0ZS5cblx0dmFyIGksIGwsIG5ld1ZpZXcsIGNoaWxkVmlldywgaXRlbVJlc3VsdCwgc3dhcENvbnRlbnQsIGNvbnRlbnRUbXBsLCBvdXRlck9uUmVuZGVyLCB0bXBsTmFtZSwgaXRlbVZhciwgbmV3Q3R4LCB0YWdDdHgsIG5vTGlua2luZyxcblx0XHRyZXN1bHQgPSBcIlwiO1xuXG5cdGlmICh0YWcpIHtcblx0XHQvLyBUaGlzIGlzIGEgY2FsbCBmcm9tIHJlbmRlclRhZyBvciB0YWdDdHgucmVuZGVyKC4uLilcblx0XHR0bXBsTmFtZSA9IHRhZy50YWdOYW1lO1xuXHRcdHRhZ0N0eCA9IHRhZy50YWdDdHg7XG5cdFx0Y29udGV4dCA9IGNvbnRleHQgPyBleHRlbmRDdHgoY29udGV4dCwgdGFnLmN0eCkgOiB0YWcuY3R4O1xuXG5cdFx0aWYgKHRtcGwgPT09IHZpZXcuY29udGVudCkgeyAvLyB7e3h4eCB0bXBsPSNjb250ZW50fX1cblx0XHRcdGNvbnRlbnRUbXBsID0gdG1wbCAhPT0gdmlldy5jdHguX3dycCAvLyBXZSBhcmUgcmVuZGVyaW5nIHRoZSAjY29udGVudFxuXHRcdFx0XHQ/IHZpZXcuY3R4Ll93cnAgLy8gI2NvbnRlbnQgd2FzIHRoZSB0YWdDdHgucHJvcHMudG1wbCB3cmFwcGVyIG9mIHRoZSBibG9jayBjb250ZW50IC0gc28gd2l0aGluIHRoaXMgdmlldywgI2NvbnRlbnQgd2lsbCBub3cgYmUgdGhlIHZpZXcuY3R4Ll93cnAgYmxvY2sgY29udGVudFxuXHRcdFx0XHQ6IHVuZGVmaW5lZDsgLy8gI2NvbnRlbnQgd2FzIHRoZSB2aWV3LmN0eC5fd3JwIGJsb2NrIGNvbnRlbnQgLSBzbyB3aXRoaW4gdGhpcyB2aWV3LCB0aGVyZSBpcyBubyBsb25nZXIgYW55ICNjb250ZW50IHRvIHdyYXAuXG5cdFx0fSBlbHNlIGlmICh0bXBsICE9PSB0YWdDdHguY29udGVudCkge1xuXHRcdFx0aWYgKHRtcGwgPT09IHRhZy50ZW1wbGF0ZSkgeyAvLyBSZW5kZXJpbmcge3t0YWd9fSB0YWcudGVtcGxhdGUsIHJlcGxhY2luZyBibG9jayBjb250ZW50LlxuXHRcdFx0XHRjb250ZW50VG1wbCA9IHRhZ0N0eC50bXBsOyAvLyBTZXQgI2NvbnRlbnQgdG8gYmxvY2sgY29udGVudCAob3Igd3JhcHBlZCBibG9jayBjb250ZW50IGlmIHRhZ0N0eC5wcm9wcy50bXBsIGlzIHNldClcblx0XHRcdFx0Y29udGV4dC5fd3JwID0gdGFnQ3R4LmNvbnRlbnQ7IC8vIFBhc3Mgd3JhcHBlZCBibG9jayBjb250ZW50IHRvIG5lc3RlZCB2aWV3c1xuXHRcdFx0fSBlbHNlIHsgLy8gUmVuZGVyaW5nIHRhZ0N0eC5wcm9wcy50bXBsIHdyYXBwZXJcblx0XHRcdFx0Y29udGVudFRtcGwgPSB0YWdDdHguY29udGVudCB8fCB2aWV3LmNvbnRlbnQ7IC8vIFNldCAjY29udGVudCB0byB3cmFwcGVkIGJsb2NrIGNvbnRlbnRcblx0XHRcdH1cblx0XHR9IGVsc2Uge1xuXHRcdFx0Y29udGVudFRtcGwgPSB2aWV3LmNvbnRlbnQ7IC8vIE5lc3RlZCB2aWV3cyBpbmhlcml0IHNhbWUgd3JhcHBlZCAjY29udGVudCBwcm9wZXJ0eVxuXHRcdH1cblxuXHRcdGlmICh0YWdDdHgucHJvcHMubGluayA9PT0gZmFsc2UpIHtcblx0XHRcdC8vIGxpbms9ZmFsc2Ugc2V0dGluZyBvbiBibG9jayB0YWdcblx0XHRcdC8vIFdlIHdpbGwgb3ZlcnJpZGUgaW5oZXJpdGVkIHZhbHVlIG9mIGxpbmsgYnkgdGhlIGV4cGxpY2l0IHNldHRpbmcgbGluaz1mYWxzZSB0YWtlbiBmcm9tIHByb3BzXG5cdFx0XHQvLyBUaGUgY2hpbGQgdmlld3Mgb2YgYW4gdW5saW5rZWQgdmlldyBhcmUgYWxzbyB1bmxpbmtlZC4gU28gc2V0dGluZyBjaGlsZCBiYWNrIHRvIHRydWUgd2lsbCBub3QgaGF2ZSBhbnkgZWZmZWN0LlxuXHRcdFx0Y29udGV4dCA9IGNvbnRleHQgfHwge307XG5cdFx0XHRjb250ZXh0LmxpbmsgPSBmYWxzZTtcblx0XHR9XG5cdFx0aWYgKGl0ZW1WYXIgPSB0YWdDdHgucHJvcHMuaXRlbVZhcikge1xuXHRcdFx0aWYgKGl0ZW1WYXIuY2hhckF0KDApICE9PSBcIn5cIikge1xuXHRcdFx0XHRzeW50YXhFcnJvcihcIlVzZSBpdGVtVmFyPSd+bXlJdGVtJ1wiKTtcblx0XHRcdH1cblx0XHRcdGl0ZW1WYXIgPSBpdGVtVmFyLnNsaWNlKDEpO1xuXHRcdH1cblx0fVxuXG5cdGlmICh2aWV3KSB7XG5cdFx0b25SZW5kZXIgPSBvblJlbmRlciB8fCB2aWV3Ll8ub25SZW5kZXI7XG5cdFx0bm9MaW5raW5nID0gY29udGV4dCAmJiBjb250ZXh0LmxpbmsgPT09IGZhbHNlO1xuXG5cdFx0aWYgKG5vTGlua2luZyAmJiB2aWV3Ll8ubmwpIHtcblx0XHRcdG9uUmVuZGVyID0gdW5kZWZpbmVkO1xuXHRcdH1cblxuXHRcdGNvbnRleHQgPSBleHRlbmRDdHgoY29udGV4dCwgdmlldy5jdHgpO1xuXHR9XG5cblx0aWYgKGtleSA9PT0gdHJ1ZSkge1xuXHRcdHN3YXBDb250ZW50ID0gdHJ1ZTtcblx0XHRrZXkgPSAwO1xuXHR9XG5cblx0Ly8gSWYgbGluaz09PWZhbHNlLCBkbyBub3QgY2FsbCBvblJlbmRlciwgc28gbm8gZGF0YS1saW5raW5nIG1hcmtlciBub2Rlc1xuXHRpZiAob25SZW5kZXIgJiYgdGFnICYmIHRhZy5fLm5vVndzKSB7XG5cdFx0b25SZW5kZXIgPSB1bmRlZmluZWQ7XG5cdH1cblx0b3V0ZXJPblJlbmRlciA9IG9uUmVuZGVyO1xuXHRpZiAob25SZW5kZXIgPT09IHRydWUpIHtcblx0XHQvLyBVc2VkIGJ5IHZpZXcucmVmcmVzaCgpLiBEb24ndCBjcmVhdGUgYSBuZXcgd3JhcHBlciB2aWV3LlxuXHRcdG91dGVyT25SZW5kZXIgPSB1bmRlZmluZWQ7XG5cdFx0b25SZW5kZXIgPSB2aWV3Ll8ub25SZW5kZXI7XG5cdH1cblx0Ly8gU2V0IGFkZGl0aW9uYWwgY29udGV4dCBvbiB2aWV3cyBjcmVhdGVkIGhlcmUsIChhcyBtb2RpZmllZCBjb250ZXh0IGluaGVyaXRlZCBmcm9tIHRoZSBwYXJlbnQsIGFuZCB0byBiZSBpbmhlcml0ZWQgYnkgY2hpbGQgdmlld3MpXG5cdGNvbnRleHQgPSB0bXBsLmhlbHBlcnNcblx0XHQ/IGV4dGVuZEN0eCh0bXBsLmhlbHBlcnMsIGNvbnRleHQpXG5cdFx0OiBjb250ZXh0O1xuXG5cdG5ld0N0eCA9IGNvbnRleHQ7XG5cdGlmICgkaXNBcnJheShkYXRhKSAmJiAhbm9JdGVyYXRpb24pIHtcblx0XHQvLyBDcmVhdGUgYSB2aWV3IGZvciB0aGUgYXJyYXksIHdob3NlIGNoaWxkIHZpZXdzIGNvcnJlc3BvbmQgdG8gZWFjaCBkYXRhIGl0ZW0uIChOb3RlOiBpZiBrZXkgYW5kIHZpZXcgYXJlIHBhc3NlZCBpblxuXHRcdC8vIGFsb25nIHdpdGggcGFyZW50IHZpZXcsIHRyZWF0IGFzIGluc2VydCAtZS5nLiBmcm9tIHZpZXcuYWRkVmlld3MgLSBzbyB2aWV3IGlzIGFscmVhZHkgdGhlIHZpZXcgaXRlbSBmb3IgYXJyYXkpXG5cdFx0bmV3VmlldyA9IHN3YXBDb250ZW50XG5cdFx0XHQ/IHZpZXdcblx0XHRcdDogKGtleSAhPT0gdW5kZWZpbmVkICYmIHZpZXcpXG5cdFx0XHRcdHx8IG5ldyBWaWV3KGNvbnRleHQsIFwiYXJyYXlcIiwgdmlldywgZGF0YSwgdG1wbCwga2V5LCBvblJlbmRlciwgY29udGVudFRtcGwpO1xuXHRcdG5ld1ZpZXcuXy5ubD0gbm9MaW5raW5nO1xuXHRcdGlmICh2aWV3ICYmIHZpZXcuXy51c2VLZXkpIHtcblx0XHRcdC8vIFBhcmVudCBpcyBub3QgYW4gJ2FycmF5IHZpZXcnXG5cdFx0XHRuZXdWaWV3Ll8uYm5kID0gIXRhZyB8fCB0YWcuXy5ibmQgJiYgdGFnOyAvLyBGb3IgYXJyYXkgdmlld3MgdGhhdCBhcmUgZGF0YSBib3VuZCBmb3IgY29sbGVjdGlvbiBjaGFuZ2UgZXZlbnRzLCBzZXQgdGhlXG5cdFx0XHQvLyB2aWV3Ll8uYm5kIHByb3BlcnR5IHRvIHRydWUgZm9yIHRvcC1sZXZlbCBsaW5rKCkgb3IgZGF0YS1saW5rPVwie2Zvcn1cIiwgb3IgdG8gdGhlIHRhZyBpbnN0YW5jZSBmb3IgYSBkYXRhLWJvdW5kIHRhZywgZS5nLiB7Xntmb3IgLi4ufX1cblx0XHRcdG5ld1ZpZXcudGFnID0gdGFnO1xuXHRcdH1cblx0XHRmb3IgKGkgPSAwLCBsID0gZGF0YS5sZW5ndGg7IGkgPCBsOyBpKyspIHtcblx0XHRcdC8vIENyZWF0ZSBhIHZpZXcgZm9yIGVhY2ggZGF0YSBpdGVtLlxuXHRcdFx0aWYgKGl0ZW1WYXIpIHtcblx0XHRcdFx0c2V0SXRlbVZhcihkYXRhW2ldKTsgLy8gdXNlIG1vZGlmaWVkIGN0eCB3aXRoIHVzZXItbmFtZWQgfml0ZW1cblx0XHRcdH1cblx0XHRcdGNoaWxkVmlldyA9IG5ldyBWaWV3KG5ld0N0eCwgXCJpdGVtXCIsIG5ld1ZpZXcsIGRhdGFbaV0sIHRtcGwsIChrZXkgfHwgMCkgKyBpLCBvblJlbmRlciwgbmV3Vmlldy5jb250ZW50KTtcblx0XHRcdGNoaWxkVmlldy5fLml0ID0gaXRlbVZhcjtcblxuXHRcdFx0aXRlbVJlc3VsdCA9IHRtcGwuZm4oZGF0YVtpXSwgY2hpbGRWaWV3LCAkc3ViKTtcblx0XHRcdHJlc3VsdCArPSBuZXdWaWV3Ll8ub25SZW5kZXIgPyBuZXdWaWV3Ll8ub25SZW5kZXIoaXRlbVJlc3VsdCwgY2hpbGRWaWV3KSA6IGl0ZW1SZXN1bHQ7XG5cdFx0fVxuXHR9IGVsc2Uge1xuXHRcdC8vIENyZWF0ZSBhIHZpZXcgZm9yIHNpbmdsZXRvbiBkYXRhIG9iamVjdC4gVGhlIHR5cGUgb2YgdGhlIHZpZXcgd2lsbCBiZSB0aGUgdGFnIG5hbWUsIGUuZy4gXCJpZlwiIG9yIFwibXl0YWdcIiBleGNlcHQgZm9yXG5cdFx0Ly8gXCJpdGVtXCIsIFwiYXJyYXlcIiBhbmQgXCJkYXRhXCIgdmlld3MuIEEgXCJkYXRhXCIgdmlldyBpcyBmcm9tIHByb2dyYW1tYXRpYyByZW5kZXIob2JqZWN0KSBhZ2FpbnN0IGEgJ3NpbmdsZXRvbicuXG5cdFx0aWYgKGl0ZW1WYXIpIHtcblx0XHRcdHNldEl0ZW1WYXIoZGF0YSk7XG5cdFx0fVxuXHRcdG5ld1ZpZXcgPSBzd2FwQ29udGVudCA/IHZpZXcgOiBuZXcgVmlldyhuZXdDdHgsIHRtcGxOYW1lIHx8IFwiZGF0YVwiLCB2aWV3LCBkYXRhLCB0bXBsLCBrZXksIG9uUmVuZGVyLCBjb250ZW50VG1wbCk7XG5cdFx0bmV3Vmlldy5fLml0ID0gaXRlbVZhcjtcblx0XHRuZXdWaWV3LnRhZyA9IHRhZztcblx0XHRuZXdWaWV3Ll8ubmwgPSBub0xpbmtpbmc7XG5cdFx0cmVzdWx0ICs9IHRtcGwuZm4oZGF0YSwgbmV3VmlldywgJHN1Yik7XG5cdH1cblx0aWYgKHRhZykge1xuXHRcdG5ld1ZpZXcudGFnRWxzZSA9IHRhZ0N0eC5pbmRleDtcblx0XHR0YWdDdHguY29udGVudFZpZXcgPSBuZXdWaWV3O1xuXHR9XG5cdHJldHVybiBvdXRlck9uUmVuZGVyID8gb3V0ZXJPblJlbmRlcihyZXN1bHQsIG5ld1ZpZXcpIDogcmVzdWx0O1xufVxuXG4vLz09PT09PT09PT09PT09PT09PT09PT09PT09PVxuLy8gQnVpbGQgYW5kIGNvbXBpbGUgdGVtcGxhdGVcbi8vPT09PT09PT09PT09PT09PT09PT09PT09PT09XG5cbi8vIEdlbmVyYXRlIGEgcmV1c2FibGUgZnVuY3Rpb24gdGhhdCB3aWxsIHNlcnZlIHRvIHJlbmRlciBhIHRlbXBsYXRlIGFnYWluc3QgZGF0YVxuLy8gKENvbXBpbGUgQVNUIHRoZW4gYnVpbGQgdGVtcGxhdGUgZnVuY3Rpb24pXG5cbmZ1bmN0aW9uIG9uUmVuZGVyRXJyb3IoZSwgdmlldywgZmFsbGJhY2spIHtcblx0dmFyIG1lc3NhZ2UgPSBmYWxsYmFjayAhPT0gdW5kZWZpbmVkXG5cdFx0PyAkaXNGdW5jdGlvbihmYWxsYmFjaylcblx0XHRcdD8gZmFsbGJhY2suY2FsbCh2aWV3LmRhdGEsIGUsIHZpZXcpXG5cdFx0XHQ6IGZhbGxiYWNrIHx8IFwiXCJcblx0XHQ6IFwie0Vycm9yOiBcIiArIChlLm1lc3NhZ2V8fGUpICsgXCJ9XCI7XG5cblx0aWYgKCRzdWJTZXR0aW5ncy5vbkVycm9yICYmIChmYWxsYmFjayA9ICRzdWJTZXR0aW5ncy5vbkVycm9yLmNhbGwodmlldy5kYXRhLCBlLCBmYWxsYmFjayAmJiBtZXNzYWdlLCB2aWV3KSkgIT09IHVuZGVmaW5lZCkge1xuXHRcdG1lc3NhZ2UgPSBmYWxsYmFjazsgLy8gVGhlcmUgaXMgYSBzZXR0aW5ncy5kZWJ1Z01vZGUoaGFuZGxlcikgb25FcnJvciBvdmVycmlkZS4gQ2FsbCBpdCwgYW5kIHVzZSByZXR1cm4gdmFsdWUgKGlmIGFueSkgdG8gcmVwbGFjZSBtZXNzYWdlXG5cdH1cblxuXHRyZXR1cm4gdmlldyAmJiAhdmlldy5saW5rQ3R4ID8gJGNvbnZlcnRlcnMuaHRtbChtZXNzYWdlKSA6IG1lc3NhZ2U7XG59XG5cbmZ1bmN0aW9uIGVycm9yKG1lc3NhZ2UpIHtcblx0dGhyb3cgbmV3ICRzdWIuRXJyKG1lc3NhZ2UpO1xufVxuXG5mdW5jdGlvbiBzeW50YXhFcnJvcihtZXNzYWdlKSB7XG5cdGVycm9yKFwiU3ludGF4IGVycm9yXFxuXCIgKyBtZXNzYWdlKTtcbn1cblxuZnVuY3Rpb24gdG1wbEZuKG1hcmt1cCwgdG1wbCwgaXNMaW5rRXhwciwgY29udmVydEJhY2ssIGhhc0Vsc2UpIHtcblx0Ly8gQ29tcGlsZSBtYXJrdXAgdG8gQVNUIChhYnRyYWN0IHN5bnRheCB0cmVlKSB0aGVuIGJ1aWxkIHRoZSB0ZW1wbGF0ZSBmdW5jdGlvbiBjb2RlIGZyb20gdGhlIEFTVCBub2Rlc1xuXHQvLyBVc2VkIGZvciBjb21waWxpbmcgdGVtcGxhdGVzLCBhbmQgYWxzbyBieSBKc1ZpZXdzIHRvIGJ1aWxkIGZ1bmN0aW9ucyBmb3IgZGF0YSBsaW5rIGV4cHJlc3Npb25zXG5cblx0Ly89PT09IG5lc3RlZCBmdW5jdGlvbnMgPT09PVxuXHRmdW5jdGlvbiBwdXNocHJlY2VkaW5nQ29udGVudChzaGlmdCkge1xuXHRcdHNoaWZ0IC09IGxvYztcblx0XHRpZiAoc2hpZnQpIHtcblx0XHRcdGNvbnRlbnQucHVzaChtYXJrdXAuc3Vic3RyKGxvYywgc2hpZnQpLnJlcGxhY2Uock5ld0xpbmUsIFwiXFxcXG5cIikpO1xuXHRcdH1cblx0fVxuXG5cdGZ1bmN0aW9uIGJsb2NrVGFnQ2hlY2sodGFnTmFtZSwgYmxvY2spIHtcblx0XHRpZiAodGFnTmFtZSkge1xuXHRcdFx0dGFnTmFtZSArPSAnfX0nO1xuXHRcdFx0Ly9cdFx0XHQne3tpbmNsdWRlfX0gYmxvY2sgaGFzIHt7L2Zvcn19IHdpdGggbm8gb3BlbiB7e2Zvcn19J1xuXHRcdFx0c3ludGF4RXJyb3IoKFxuXHRcdFx0XHRibG9ja1xuXHRcdFx0XHRcdD8gJ3t7JyArIGJsb2NrICsgJ319IGJsb2NrIGhhcyB7ey8nICsgdGFnTmFtZSArICcgd2l0aG91dCB7eycgKyB0YWdOYW1lXG5cdFx0XHRcdFx0OiAnVW5tYXRjaGVkIG9yIG1pc3Npbmcge3svJyArIHRhZ05hbWUpICsgJywgaW4gdGVtcGxhdGU6XFxuJyArIG1hcmt1cCk7XG5cdFx0fVxuXHR9XG5cblx0ZnVuY3Rpb24gcGFyc2VUYWcoYWxsLCBiaW5kLCB0YWdOYW1lLCBjb252ZXJ0ZXIsIGNvbG9uLCBodG1sLCBjb2RlVGFnLCBwYXJhbXMsIHNsYXNoLCBiaW5kMiwgY2xvc2VCbG9jaywgaW5kZXgpIHtcbi8qXG5cbiAgICAgYmluZCAgICAgdGFnTmFtZSAgICAgICAgIGN2dCAgIGNsbiBodG1sIGNvZGUgICAgcGFyYW1zICAgICAgICAgICAgc2xhc2ggICBiaW5kMiAgICAgICAgIGNsb3NlQmxrICBjb21tZW50XG4vKD86eyhcXF4pP3soPzooXFx3Kyg/PVtcXC9cXHN9XSkpfChcXHcrKT8oOil8KD4pfChcXCopKVxccyooKD86W159XXx9KD8hfSkpKj8pKFxcLyk/fHsoXFxeKT97KD86KD86XFwvKFxcdyspKVxccyp8IS0tW1xcc1xcU10qPy0tKSl9fS9nXG5cbig/OlxuICB7KFxcXik/eyAgICAgICAgICAgIGJpbmRcbiAgKD86XG4gICAgKFxcdysgICAgICAgICAgICAgdGFnTmFtZVxuICAgICAgKD89W1xcL1xcc31dKVxuICAgIClcbiAgICB8XG4gICAgKFxcdyspPyg6KSAgICAgICAgY29udmVydGVyIGNvbG9uXG4gICAgfFxuICAgICg+KSAgICAgICAgICAgICAgaHRtbFxuICAgIHxcbiAgICAoXFwqKSAgICAgICAgICAgICBjb2RlVGFnXG4gIClcbiAgXFxzKlxuICAoICAgICAgICAgICAgICAgICAgcGFyYW1zXG4gICAgKD86W159XXx9KD8hfSkpKj9cbiAgKVxuICAoXFwvKT8gICAgICAgICAgICAgIHNsYXNoXG4gIHxcbiAgeyhcXF4pP3sgICAgICAgICAgICBiaW5kMlxuICAoPzpcbiAgICAoPzpcXC8oXFx3KykpXFxzKiAgIGNsb3NlQmxvY2tcbiAgICB8XG4gICAgIS0tW1xcc1xcU10qPy0tICAgIGNvbW1lbnRcbiAgKVxuKVxufX0vZ1xuXG4qL1xuXHRcdGlmIChjb2RlVGFnICYmIGJpbmQgfHwgc2xhc2ggJiYgIXRhZ05hbWUgfHwgcGFyYW1zICYmIHBhcmFtcy5zbGljZSgtMSkgPT09IFwiOlwiIHx8IGJpbmQyKSB7XG5cdFx0XHRzeW50YXhFcnJvcihhbGwpO1xuXHRcdH1cblxuXHRcdC8vIEJ1aWxkIGFic3RyYWN0IHN5bnRheCB0cmVlIChBU1QpOiBbdGFnTmFtZSwgY29udmVydGVyLCBwYXJhbXMsIGNvbnRlbnQsIGhhc2gsIGJpbmRpbmdzLCBjb250ZW50TWFya3VwXVxuXHRcdGlmIChodG1sKSB7XG5cdFx0XHRjb2xvbiA9IFwiOlwiO1xuXHRcdFx0Y29udmVydGVyID0gSFRNTDtcblx0XHR9XG5cdFx0c2xhc2ggPSBzbGFzaCB8fCBpc0xpbmtFeHByICYmICFoYXNFbHNlO1xuXG5cdFx0dmFyIGxhdGUsIG9wZW5UYWdOYW1lLCBpc0xhdGVPYixcblx0XHRcdHBhdGhCaW5kaW5ncyA9IChiaW5kIHx8IGlzTGlua0V4cHIpICYmIFtbXV0sIC8vIHBhdGhCaW5kaW5ncyBpcyBhbiBhcnJheSBvZiBhcnJheXMgZm9yIGFyZyBiaW5kaW5ncyBhbmQgYSBoYXNoIG9mIGFycmF5cyBmb3IgcHJvcCBiaW5kaW5nc1xuXHRcdFx0cHJvcHMgPSBcIlwiLFxuXHRcdFx0YXJncyA9IFwiXCIsXG5cdFx0XHRjdHhQcm9wcyA9IFwiXCIsXG5cdFx0XHRwYXJhbXNBcmdzID0gXCJcIixcblx0XHRcdHBhcmFtc1Byb3BzID0gXCJcIixcblx0XHRcdHBhcmFtc0N0eFByb3BzID0gXCJcIixcblx0XHRcdG9uRXJyb3IgPSBcIlwiLFxuXHRcdFx0dXNlVHJpZ2dlciA9IFwiXCIsXG5cdFx0XHQvLyBCbG9jayB0YWcgaWYgbm90IHNlbGYtY2xvc2luZyBhbmQgbm90IHt7On19IG9yIHt7Pn19IChzcGVjaWFsIGNhc2UpIGFuZCBub3QgYSBkYXRhLWxpbmsgZXhwcmVzc2lvblxuXHRcdFx0YmxvY2sgPSAhc2xhc2ggJiYgIWNvbG9uO1xuXG5cdFx0Ly89PT09IG5lc3RlZCBoZWxwZXIgZnVuY3Rpb24gPT09PVxuXHRcdHRhZ05hbWUgPSB0YWdOYW1lIHx8IChwYXJhbXMgPSBwYXJhbXMgfHwgXCIjZGF0YVwiLCBjb2xvbik7IC8vIHt7On19IGlzIGVxdWl2YWxlbnQgdG8ge3s6I2RhdGF9fVxuXHRcdHB1c2hwcmVjZWRpbmdDb250ZW50KGluZGV4KTtcblx0XHRsb2MgPSBpbmRleCArIGFsbC5sZW5ndGg7IC8vIGxvY2F0aW9uIG1hcmtlciAtIHBhcnNlZCB1cCB0byBoZXJlXG5cdFx0aWYgKGNvZGVUYWcpIHtcblx0XHRcdGlmIChhbGxvd0NvZGUpIHtcblx0XHRcdFx0Y29udGVudC5wdXNoKFtcIipcIiwgXCJcXG5cIiArIHBhcmFtcy5yZXBsYWNlKC9eOi8sIFwicmV0Kz0gXCIpLnJlcGxhY2UoclVuZXNjYXBlUXVvdGVzLCBcIiQxXCIpICsgXCI7XFxuXCJdKTtcblx0XHRcdH1cblx0XHR9IGVsc2UgaWYgKHRhZ05hbWUpIHtcblx0XHRcdGlmICh0YWdOYW1lID09PSBcImVsc2VcIikge1xuXHRcdFx0XHRpZiAoclRlc3RFbHNlSWYudGVzdChwYXJhbXMpKSB7XG5cdFx0XHRcdFx0c3ludGF4RXJyb3IoJ0ZvciBcInt7ZWxzZSBpZiBleHByfX1cIiB1c2UgXCJ7e2Vsc2UgZXhwcn19XCInKTtcblx0XHRcdFx0fVxuXHRcdFx0XHRwYXRoQmluZGluZ3MgPSBjdXJyZW50WzldICYmIFtbXV07XG5cdFx0XHRcdGN1cnJlbnRbMTBdID0gbWFya3VwLnN1YnN0cmluZyhjdXJyZW50WzEwXSwgaW5kZXgpOyAvLyBjb250ZW50TWFya3VwIGZvciBibG9jayB0YWdcblx0XHRcdFx0b3BlblRhZ05hbWUgPSBjdXJyZW50WzExXSB8fCBjdXJyZW50WzBdIHx8IHN5bnRheEVycm9yKFwiTWlzbWF0Y2hlZDogXCIgKyBhbGwpO1xuXHRcdFx0XHQvLyBjdXJyZW50WzBdIGlzIHRhZ05hbWUsIGJ1dCBmb3Ige3tlbHNlfX0gbm9kZXMsIGN1cnJlbnRbMTFdIGlzIHRhZ05hbWUgb2YgcHJlY2VkaW5nIG9wZW4gdGFnXG5cdFx0XHRcdGN1cnJlbnQgPSBzdGFjay5wb3AoKTtcblx0XHRcdFx0Y29udGVudCA9IGN1cnJlbnRbMl07XG5cdFx0XHRcdGJsb2NrID0gdHJ1ZTtcblx0XHRcdH1cblx0XHRcdGlmIChwYXJhbXMpIHtcblx0XHRcdFx0Ly8gcmVtb3ZlIG5ld2xpbmVzIGZyb20gdGhlIHBhcmFtcyBzdHJpbmcsIHRvIGF2b2lkIGNvbXBpbGVkIGNvZGUgZXJyb3JzIGZvciB1bnRlcm1pbmF0ZWQgc3RyaW5nc1xuXHRcdFx0XHRwYXJzZVBhcmFtcyhwYXJhbXMucmVwbGFjZShyTmV3TGluZSwgXCIgXCIpLCBwYXRoQmluZGluZ3MsIHRtcGwsIGlzTGlua0V4cHIpXG5cdFx0XHRcdFx0LnJlcGxhY2UockJ1aWxkSGFzaCwgZnVuY3Rpb24oYWxsLCBvbmVycm9yLCBpc0N0eFBybSwga2V5LCBrZXlUb2tlbiwga2V5VmFsdWUsIGFyZywgcGFyYW0pIHtcblx0XHRcdFx0XHRcdGlmIChrZXkgPT09IFwidGhpczpcIikge1xuXHRcdFx0XHRcdFx0XHRrZXlWYWx1ZSA9IFwidW5kZWZpbmVkXCI7IC8vIHRoaXM9c29tZS5wYXRoIGlzIGFsd2F5cyBhIHRvIHBhcmFtZXRlciAob25lLXdheSksIHNvIGRvbid0IG5lZWQgdG8gY29tcGlsZS9ldmFsdWF0ZSBzb21lLnBhdGggaW5pdGlhbGl6YXRpb25cblx0XHRcdFx0XHRcdH1cblx0XHRcdFx0XHRcdGlmIChwYXJhbSkge1xuXHRcdFx0XHRcdFx0XHRpc0xhdGVPYiA9IGlzTGF0ZU9iIHx8IHBhcmFtLmNoYXJBdCgwKSA9PT0gXCJAXCI7XG5cdFx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0XHRrZXkgPSBcIidcIiArIGtleVRva2VuICsgXCInOlwiO1xuXHRcdFx0XHRcdFx0aWYgKGFyZykge1xuXHRcdFx0XHRcdFx0XHRhcmdzICs9IGlzQ3R4UHJtICsga2V5VmFsdWUgKyBcIixcIjtcblx0XHRcdFx0XHRcdFx0cGFyYW1zQXJncyArPSBcIidcIiArIHBhcmFtICsgXCInLFwiO1xuXHRcdFx0XHRcdFx0fSBlbHNlIGlmIChpc0N0eFBybSkgeyAvLyBDb250ZXh0dWFsIHBhcmFtZXRlciwgfmZvbz1leHByXG5cdFx0XHRcdFx0XHRcdGN0eFByb3BzICs9IGtleSArICdqLl9jcCgnICsga2V5VmFsdWUgKyAnLFwiJyArIHBhcmFtICsgJ1wiLHZpZXcpLCc7XG5cdFx0XHRcdFx0XHRcdC8vIENvbXBpbGVkIGNvZGUgZm9yIGV2YWx1YXRpbmcgdGFnQ3R4IG9uIGEgdGFnIHdpbGwgaGF2ZTogY3R4OnsnZm9vJzpqLl9jcChjb21waWxlZEV4cHIsIFwiZXhwclwiLCB2aWV3KX1cblx0XHRcdFx0XHRcdFx0cGFyYW1zQ3R4UHJvcHMgKz0ga2V5ICsgXCInXCIgKyBwYXJhbSArIFwiJyxcIjtcblx0XHRcdFx0XHRcdH0gZWxzZSBpZiAob25lcnJvcikge1xuXHRcdFx0XHRcdFx0XHRvbkVycm9yICs9IGtleVZhbHVlO1xuXHRcdFx0XHRcdFx0fSBlbHNlIHtcblx0XHRcdFx0XHRcdFx0aWYgKGtleVRva2VuID09PSBcInRyaWdnZXJcIikge1xuXHRcdFx0XHRcdFx0XHRcdHVzZVRyaWdnZXIgKz0ga2V5VmFsdWU7XG5cdFx0XHRcdFx0XHRcdH1cblx0XHRcdFx0XHRcdFx0aWYgKGtleVRva2VuID09PSBcImxhdGVSZW5kZXJcIikge1xuXHRcdFx0XHRcdFx0XHRcdGxhdGUgPSBwYXJhbSAhPT0gXCJmYWxzZVwiOyAvLyBSZW5kZXIgYWZ0ZXIgZmlyc3QgcGFzc1xuXHRcdFx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0XHRcdHByb3BzICs9IGtleSArIGtleVZhbHVlICsgXCIsXCI7XG5cdFx0XHRcdFx0XHRcdHBhcmFtc1Byb3BzICs9IGtleSArIFwiJ1wiICsgcGFyYW0gKyBcIicsXCI7XG5cdFx0XHRcdFx0XHRcdGhhc0hhbmRsZXJzID0gaGFzSGFuZGxlcnMgfHwgckhhc0hhbmRsZXJzLnRlc3Qoa2V5VG9rZW4pO1xuXHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdFx0cmV0dXJuIFwiXCI7XG5cdFx0XHRcdFx0fSkuc2xpY2UoMCwgLTEpO1xuXHRcdFx0fVxuXG5cdFx0XHRpZiAocGF0aEJpbmRpbmdzICYmIHBhdGhCaW5kaW5nc1swXSkge1xuXHRcdFx0XHRwYXRoQmluZGluZ3MucG9wKCk7IC8vIFJlbW92ZSB0aGUgYmluZGluZyB0aGF0IHdhcyBwcmVwYXJlZCBmb3IgbmV4dCBhcmcuIChUaGVyZSBpcyBhbHdheXMgYW4gZXh0cmEgb25lIHJlYWR5KS5cblx0XHRcdH1cblxuXHRcdFx0bmV3Tm9kZSA9IFtcblx0XHRcdFx0XHR0YWdOYW1lLFxuXHRcdFx0XHRcdGNvbnZlcnRlciB8fCAhIWNvbnZlcnRCYWNrIHx8IGhhc0hhbmRsZXJzIHx8IFwiXCIsXG5cdFx0XHRcdFx0YmxvY2sgJiYgW10sXG5cdFx0XHRcdFx0cGFyc2VkUGFyYW0ocGFyYW1zQXJncyB8fCAodGFnTmFtZSA9PT0gXCI6XCIgPyBcIicjZGF0YScsXCIgOiBcIlwiKSwgcGFyYW1zUHJvcHMsIHBhcmFtc0N0eFByb3BzKSwgLy8ge3s6fX0gZXF1aXZhbGVudCB0byB7ezojZGF0YX19XG5cdFx0XHRcdFx0cGFyc2VkUGFyYW0oYXJncyB8fCAodGFnTmFtZSA9PT0gXCI6XCIgPyBcImRhdGEsXCIgOiBcIlwiKSwgcHJvcHMsIGN0eFByb3BzKSxcblx0XHRcdFx0XHRvbkVycm9yLFxuXHRcdFx0XHRcdHVzZVRyaWdnZXIsXG5cdFx0XHRcdFx0bGF0ZSxcblx0XHRcdFx0XHRpc0xhdGVPYixcblx0XHRcdFx0XHRwYXRoQmluZGluZ3MgfHwgMFxuXHRcdFx0XHRdO1xuXHRcdFx0Y29udGVudC5wdXNoKG5ld05vZGUpO1xuXHRcdFx0aWYgKGJsb2NrKSB7XG5cdFx0XHRcdHN0YWNrLnB1c2goY3VycmVudCk7XG5cdFx0XHRcdGN1cnJlbnQgPSBuZXdOb2RlO1xuXHRcdFx0XHRjdXJyZW50WzEwXSA9IGxvYzsgLy8gU3RvcmUgY3VycmVudCBsb2NhdGlvbiBvZiBvcGVuIHRhZywgdG8gYmUgYWJsZSB0byBhZGQgY29udGVudE1hcmt1cCB3aGVuIHdlIHJlYWNoIGNsb3NpbmcgdGFnXG5cdFx0XHRcdGN1cnJlbnRbMTFdID0gb3BlblRhZ05hbWU7IC8vIFVzZWQgZm9yIGNoZWNraW5nIHN5bnRheCAobWF0Y2hpbmcgY2xvc2UgdGFnKVxuXHRcdFx0fVxuXHRcdH0gZWxzZSBpZiAoY2xvc2VCbG9jaykge1xuXHRcdFx0YmxvY2tUYWdDaGVjayhjbG9zZUJsb2NrICE9PSBjdXJyZW50WzBdICYmIGNsb3NlQmxvY2sgIT09IGN1cnJlbnRbMTFdICYmIGNsb3NlQmxvY2ssIGN1cnJlbnRbMF0pOyAvLyBDaGVjayBtYXRjaGluZyBjbG9zZSB0YWcgbmFtZVxuXHRcdFx0Y3VycmVudFsxMF0gPSBtYXJrdXAuc3Vic3RyaW5nKGN1cnJlbnRbMTBdLCBpbmRleCk7IC8vIGNvbnRlbnRNYXJrdXAgZm9yIGJsb2NrIHRhZ1xuXHRcdFx0Y3VycmVudCA9IHN0YWNrLnBvcCgpO1xuXHRcdH1cblx0XHRibG9ja1RhZ0NoZWNrKCFjdXJyZW50ICYmIGNsb3NlQmxvY2spO1xuXHRcdGNvbnRlbnQgPSBjdXJyZW50WzJdO1xuXHR9XG5cdC8vPT09PSAvZW5kIG9mIG5lc3RlZCBmdW5jdGlvbnMgPT09PVxuXG5cdHZhciBpLCByZXN1bHQsIG5ld05vZGUsIGhhc0hhbmRsZXJzLCBiaW5kaW5ncyxcblx0XHRhbGxvd0NvZGUgPSAkc3ViU2V0dGluZ3MuYWxsb3dDb2RlIHx8IHRtcGwgJiYgdG1wbC5hbGxvd0NvZGVcblx0XHRcdHx8ICR2aWV3c1NldHRpbmdzLmFsbG93Q29kZSA9PT0gdHJ1ZSwgLy8gaW5jbHVkZSBkaXJlY3Qgc2V0dGluZyBvZiBzZXR0aW5ncy5hbGxvd0NvZGUgdHJ1ZSBmb3IgYmFja3dhcmQgY29tcGF0IG9ubHlcblx0XHRhc3RUb3AgPSBbXSxcblx0XHRsb2MgPSAwLFxuXHRcdHN0YWNrID0gW10sXG5cdFx0Y29udGVudCA9IGFzdFRvcCxcblx0XHRjdXJyZW50ID0gWywsYXN0VG9wXTtcblxuXHRpZiAoYWxsb3dDb2RlICYmIHRtcGwuX2lzKSB7XG5cdFx0dG1wbC5hbGxvd0NvZGUgPSBhbGxvd0NvZGU7XG5cdH1cblxuLy9UT0RPXHRyZXN1bHQgPSB0bXBsRm5zQ2FjaGVbbWFya3VwXTsgLy8gT25seSBjYWNoZSBpZiB0ZW1wbGF0ZSBpcyBub3QgbmFtZWQgYW5kIG1hcmt1cCBsZW5ndGggPCAuLi4sXG4vL2FuZCB0aGVyZSBhcmUgbm8gYmluZGluZ3Mgb3Igc3VidGVtcGxhdGVzPz8gQ29uc2lkZXIgc3RhbmRhcmQgb3B0aW1pemF0aW9uIGZvciBkYXRhLWxpbms9XCJhLmIuY1wiXG4vL1x0XHRpZiAocmVzdWx0KSB7XG4vL1x0XHRcdHRtcGwuZm4gPSByZXN1bHQ7XG4vL1x0XHR9IGVsc2Uge1xuXG4vL1x0XHRyZXN1bHQgPSBtYXJrdXA7XG5cdGlmIChpc0xpbmtFeHByKSB7XG5cdFx0aWYgKGNvbnZlcnRCYWNrICE9PSB1bmRlZmluZWQpIHtcblx0XHRcdG1hcmt1cCA9IG1hcmt1cC5zbGljZSgwLCAtY29udmVydEJhY2subGVuZ3RoIC0gMikgKyBkZWxpbUNsb3NlQ2hhcjA7XG5cdFx0fVxuXHRcdG1hcmt1cCA9IGRlbGltT3BlbkNoYXIwICsgbWFya3VwICsgZGVsaW1DbG9zZUNoYXIxO1xuXHR9XG5cblx0YmxvY2tUYWdDaGVjayhzdGFja1swXSAmJiBzdGFja1swXVsyXS5wb3AoKVswXSk7XG5cdC8vIEJ1aWxkIHRoZSBBU1QgKGFic3RyYWN0IHN5bnRheCB0cmVlKSB1bmRlciBhc3RUb3Bcblx0bWFya3VwLnJlcGxhY2UoclRhZywgcGFyc2VUYWcpO1xuXG5cdHB1c2hwcmVjZWRpbmdDb250ZW50KG1hcmt1cC5sZW5ndGgpO1xuXG5cdGlmIChsb2MgPSBhc3RUb3BbYXN0VG9wLmxlbmd0aCAtIDFdKSB7XG5cdFx0YmxvY2tUYWdDaGVjayhcIlwiICsgbG9jICE9PSBsb2MgJiYgKCtsb2NbMTBdID09PSBsb2NbMTBdKSAmJiBsb2NbMF0pO1xuXHR9XG4vL1x0XHRcdHJlc3VsdCA9IHRtcGxGbnNDYWNoZVttYXJrdXBdID0gYnVpbGRDb2RlKGFzdFRvcCwgdG1wbCk7XG4vL1x0XHR9XG5cblx0aWYgKGlzTGlua0V4cHIpIHtcblx0XHRyZXN1bHQgPSBidWlsZENvZGUoYXN0VG9wLCBtYXJrdXAsIGlzTGlua0V4cHIpO1xuXHRcdGJpbmRpbmdzID0gW107XG5cdFx0aSA9IGFzdFRvcC5sZW5ndGg7XG5cdFx0d2hpbGUgKGktLSkge1xuXHRcdFx0YmluZGluZ3MudW5zaGlmdChhc3RUb3BbaV1bOV0pOyAvLyBXaXRoIGRhdGEtbGluayBleHByZXNzaW9ucywgcGF0aEJpbmRpbmdzIGFycmF5IGZvciB0YWdDdHhbaV0gaXMgYXN0VG9wW2ldWzldXG5cdFx0fVxuXHRcdHNldFBhdGhzKHJlc3VsdCwgYmluZGluZ3MpO1xuXHR9IGVsc2Uge1xuXHRcdHJlc3VsdCA9IGJ1aWxkQ29kZShhc3RUb3AsIHRtcGwpO1xuXHR9XG5cdHJldHVybiByZXN1bHQ7XG59XG5cbmZ1bmN0aW9uIHNldFBhdGhzKGZuLCBwYXRoc0Fycikge1xuXHR2YXIga2V5LCBwYXRocyxcblx0XHRpID0gMCxcblx0XHRsID0gcGF0aHNBcnIubGVuZ3RoO1xuXHRmbi5kZXBzID0gW107XG5cdGZuLnBhdGhzID0gW107IC8vIFRoZSBhcnJheSBvZiBwYXRoIGJpbmRpbmcgKGFycmF5L2RpY3Rpb25hcnkpcyBmb3IgZWFjaCB0YWcvZWxzZSBibG9jaydzIGFyZ3MgYW5kIHByb3BzXG5cdGZvciAoOyBpIDwgbDsgaSsrKSB7XG5cdFx0Zm4ucGF0aHMucHVzaChwYXRocyA9IHBhdGhzQXJyW2ldKTtcblx0XHRmb3IgKGtleSBpbiBwYXRocykge1xuXHRcdFx0aWYgKGtleSAhPT0gXCJfanN2dG9cIiAmJiBwYXRocy5oYXNPd25Qcm9wZXJ0eShrZXkpICYmIHBhdGhzW2tleV0ubGVuZ3RoICYmICFwYXRoc1trZXldLnNrcCkge1xuXHRcdFx0XHRmbi5kZXBzID0gZm4uZGVwcy5jb25jYXQocGF0aHNba2V5XSk7IC8vIGRlcHMgaXMgdGhlIGNvbmNhdGVuYXRpb24gb2YgdGhlIHBhdGhzIGFycmF5cyBmb3IgdGhlIGRpZmZlcmVudCBiaW5kaW5nc1xuXHRcdFx0fVxuXHRcdH1cblx0fVxufVxuXG5mdW5jdGlvbiBwYXJzZWRQYXJhbShhcmdzLCBwcm9wcywgY3R4KSB7XG5cdHJldHVybiBbYXJncy5zbGljZSgwLCAtMSksIHByb3BzLnNsaWNlKDAsIC0xKSwgY3R4LnNsaWNlKDAsIC0xKV07XG59XG5cbmZ1bmN0aW9uIHBhcmFtU3RydWN0dXJlKHBhcnRzLCB0eXBlKSB7XG5cdHJldHVybiAnXFxuXFx0J1xuXHRcdCsgKHR5cGVcblx0XHRcdD8gdHlwZSArICc6eydcblx0XHRcdDogJycpXG5cdFx0KyAnYXJnczpbJyArIHBhcnRzWzBdICsgJ10sXFxuXFx0cHJvcHM6eycgKyBwYXJ0c1sxXSArICd9J1xuXHRcdCsgKHBhcnRzWzJdID8gJyxcXG5cXHRjdHg6eycgKyBwYXJ0c1syXSArICd9JyA6IFwiXCIpO1xufVxuXG5mdW5jdGlvbiBwYXJzZVBhcmFtcyhwYXJhbXMsIHBhdGhCaW5kaW5ncywgdG1wbCwgaXNMaW5rRXhwcikge1xuXG5cdGZ1bmN0aW9uIHBhcnNlVG9rZW5zKGFsbCwgbGZ0UHJuMCwgbGZ0UHJuLCBib3VuZCwgcGF0aCwgb3BlcmF0b3IsIGVyciwgZXEsIHBhdGgyLCBsYXRlLCBwcm4sIGNvbW1hLCBsZnRQcm4yLCBhcG9zLCBxdW90LCBydFBybiwgcnRQcm5Eb3QsIHBybjIsIHNwYWNlLCBpbmRleCwgZnVsbCkge1xuXHQvLyAvKFxcKCkoPz1cXHMqXFwoKXwoPzooWyhbXSlcXHMqKT8oPzooXFxePykofj9bXFx3JC5eXSspP1xccyooKFxcK1xcK3wtLSl8XFwrfC18JiZ8XFx8XFx8fD09PXwhPT18PT18IT18PD18Pj18Wzw+JSo6P1xcL118KD0pKVxccyp8KCEqPyhAKT9bI35dP1tcXHckLl5dKykoWyhbXSk/KXwoLFxccyopfChcXCg/KVxcXFw/KD86KCcpfChcIikpfCg/OlxccyooKFspXFxdXSkoPz1bLl5dfFxccyokfFteKFtdKXxbKVxcXV0pKFsoW10/KSl8KFxccyspL2csXG5cdC8vICBsZnRQcm4wICAgICAgICAgIGxmdFBybiAgICAgICAgYm91bmQgcGF0aCAgICAgICAgICAgICBvcGVyYXRvciBlcnIgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVxICAgICAgcGF0aDIgbGF0ZSAgICAgICAgICAgIHBybiAgICAgIGNvbW1hICBsZnRQcm4yICAgYXBvcyBxdW90ICAgICAgICBydFBybiAgcnRQcm5Eb3QgICAgICAgICAgICAgICAgICBwcm4yICAgICBzcGFjZVxuXHRcdC8vIChsZWZ0IHBhcmVuPyBmb2xsb3dlZCBieSAocGF0aD8gZm9sbG93ZWQgYnkgb3BlcmF0b3IpIG9yIChwYXRoIGZvbGxvd2VkIGJ5IHBhcmVuPykpIG9yIGNvbW1hIG9yIGFwb3Mgb3IgcXVvdCBvciByaWdodCBwYXJlbiBvciBzcGFjZVxuXHRcdGZ1bmN0aW9uIHBhcnNlUGF0aChhbGxQYXRoLCBub3QsIG9iamVjdCwgaGVscGVyLCB2aWV3LCB2aWV3UHJvcGVydHksIHBhdGhUb2tlbnMsIGxlYWZUb2tlbikge1xuXHRcdFx0Ly9yUGF0aCA9IC9eKCEqPykoPzpudWxsfHRydWV8ZmFsc2V8XFxkW1xcZC5dKnwoW1xcdyRdK3xcXC58fihbXFx3JF0rKXwjKHZpZXd8KFtcXHckXSspKT8pKFtcXHckLl5dKj8pKD86Wy5bXl0oW1xcdyRdKylcXF0/KT8pJC9nLFxuXHRcdFx0Ly8gICAgICAgICAgbm90ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9iamVjdCAgICAgaGVscGVyICAgIHZpZXcgIHZpZXdQcm9wZXJ0eSBwYXRoVG9rZW5zICAgICAgbGVhZlRva2VuXG5cdFx0XHR2YXIgc3ViUGF0aCA9IG9iamVjdCA9PT0gXCIuXCI7XG5cdFx0XHRpZiAob2JqZWN0KSB7XG5cdFx0XHRcdHBhdGggPSBwYXRoLnNsaWNlKG5vdC5sZW5ndGgpO1xuXHRcdFx0XHRpZiAoL15cXC4/Y29uc3RydWN0b3IkLy50ZXN0KGxlYWZUb2tlbnx8cGF0aCkpIHtcblx0XHRcdFx0XHRzeW50YXhFcnJvcihhbGxQYXRoKTtcblx0XHRcdFx0fVxuXHRcdFx0XHRpZiAoIXN1YlBhdGgpIHtcblx0XHRcdFx0XHRhbGxQYXRoID0gKGxhdGUgLy8gbGF0ZSBwYXRoIEBhLmIuYzogbm90IHRocm93IG9uICdwcm9wZXJ0eSBvZiB1bmRlZmluZWQnIGlmIGEgdW5kZWZpbmVkLCBhbmQgd2lsbCB1c2UgZ2V0T2IoKSBhZnRlciBsaW5raW5nIHRvIHJlc29sdmUgbGF0ZS5cblx0XHRcdFx0XHRcdFx0PyAoaXNMaW5rRXhwciA/ICcnIDogJyhsdE9iLmx0PWx0T2IubHR8fCcpICsgJyhvYj0nXG5cdFx0XHRcdFx0XHRcdDogXCJcIlxuXHRcdFx0XHRcdFx0KVxuXHRcdFx0XHRcdFx0KyAoaGVscGVyXG5cdFx0XHRcdFx0XHRcdD8gJ3ZpZXcuY3R4UHJtKFwiJyArIGhlbHBlciArICdcIiknXG5cdFx0XHRcdFx0XHRcdDogdmlld1xuXHRcdFx0XHRcdFx0XHRcdD8gXCJ2aWV3XCJcblx0XHRcdFx0XHRcdFx0XHQ6IFwiZGF0YVwiKVxuXHRcdFx0XHRcdFx0KyAobGF0ZVxuXHRcdFx0XHRcdFx0XHQ/ICcpPT09dW5kZWZpbmVkJyArIChpc0xpbmtFeHByID8gJycgOiAnKScpICsgJz9cIlwiOnZpZXcuZ2V0T2Iob2IsXCInXG5cdFx0XHRcdFx0XHRcdDogXCJcIlxuXHRcdFx0XHRcdFx0KVxuXHRcdFx0XHRcdFx0KyAobGVhZlRva2VuXG5cdFx0XHRcdFx0XHRcdD8gKHZpZXdQcm9wZXJ0eVxuXHRcdFx0XHRcdFx0XHRcdD8gXCIuXCIgKyB2aWV3UHJvcGVydHlcblx0XHRcdFx0XHRcdFx0XHQ6IGhlbHBlclxuXHRcdFx0XHRcdFx0XHRcdFx0PyBcIlwiXG5cdFx0XHRcdFx0XHRcdFx0XHQ6ICh2aWV3ID8gXCJcIiA6IFwiLlwiICsgb2JqZWN0KVxuXHRcdFx0XHRcdFx0XHRcdCkgKyAocGF0aFRva2VucyB8fCBcIlwiKVxuXHRcdFx0XHRcdFx0XHQ6IChsZWFmVG9rZW4gPSBoZWxwZXIgPyBcIlwiIDogdmlldyA/IHZpZXdQcm9wZXJ0eSB8fCBcIlwiIDogb2JqZWN0LCBcIlwiKSk7XG5cdFx0XHRcdFx0YWxsUGF0aCA9IGFsbFBhdGggKyAobGVhZlRva2VuID8gXCIuXCIgKyBsZWFmVG9rZW4gOiBcIlwiKTtcblxuXHRcdFx0XHRcdGFsbFBhdGggPSBub3QgKyAoYWxsUGF0aC5zbGljZSgwLCA5KSA9PT0gXCJ2aWV3LmRhdGFcIlxuXHRcdFx0XHRcdFx0PyBhbGxQYXRoLnNsaWNlKDUpIC8vIGNvbnZlcnQgI3ZpZXcuZGF0YS4uLiB0byBkYXRhLi4uXG5cdFx0XHRcdFx0XHQ6IGFsbFBhdGgpXG5cdFx0XHRcdFx0KyAobGF0ZVxuXHRcdFx0XHRcdFx0XHQ/IChpc0xpbmtFeHByID8gJ1wiJzogJ1wiLGx0T2InKSArIChwcm4gPyAnLDEpJzonKScpXG5cdFx0XHRcdFx0XHRcdDogXCJcIlxuXHRcdFx0XHRcdFx0KTtcblx0XHRcdFx0fVxuXHRcdFx0XHRpZiAoYmluZGluZ3MpIHtcblx0XHRcdFx0XHRiaW5kcyA9IG5hbWVkID09PSBcIl9saW5rVG9cIiA/IChiaW5kdG8gPSBwYXRoQmluZGluZ3MuX2pzdnRvID0gcGF0aEJpbmRpbmdzLl9qc3Z0byB8fCBbXSkgOiBibmRDdHguYmQ7XG5cdFx0XHRcdFx0aWYgKHRoZU9iID0gc3ViUGF0aCAmJiBiaW5kc1tiaW5kcy5sZW5ndGgtMV0pIHtcblx0XHRcdFx0XHRcdGlmICh0aGVPYi5fY3BmbikgeyAvLyBDb21wdXRlZCBwcm9wZXJ0eSBleHByT2Jcblx0XHRcdFx0XHRcdFx0d2hpbGUgKHRoZU9iLnNiKSB7XG5cdFx0XHRcdFx0XHRcdFx0dGhlT2IgPSB0aGVPYi5zYjtcblx0XHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdFx0XHRpZiAodGhlT2IuYm5kKSB7XG5cdFx0XHRcdFx0XHRcdFx0cGF0aCA9IFwiXlwiICsgcGF0aC5zbGljZSgxKTtcblx0XHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdFx0XHR0aGVPYi5zYiA9IHBhdGg7XG5cdFx0XHRcdFx0XHRcdHRoZU9iLmJuZCA9IHRoZU9iLmJuZCB8fCBwYXRoLmNoYXJBdCgwKSA9PT0gXCJeXCI7XG5cdFx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0fSBlbHNlIHtcblx0XHRcdFx0XHRcdGJpbmRzLnB1c2gocGF0aCk7XG5cdFx0XHRcdFx0fVxuXHRcdFx0XHRcdHBhdGhTdGFydFtwYXJlbkRlcHRoXSA9IGluZGV4ICsgKHN1YlBhdGggPyAxIDogMCk7XG5cdFx0XHRcdH1cblx0XHRcdH1cblx0XHRcdHJldHVybiBhbGxQYXRoO1xuXHRcdH1cblxuXHRcdC8vYm91bmQgPSBiaW5kaW5ncyAmJiBib3VuZDtcblx0XHRpZiAoYm91bmQgJiYgIWVxKSB7XG5cdFx0XHRwYXRoID0gYm91bmQgKyBwYXRoOyAvLyBlLmcuIHNvbWUuZm4oLi4uKV5zb21lLnBhdGggLSBzbyBoZXJlIHBhdGggaXMgXCJec29tZS5wYXRoXCJcblx0XHR9XG5cdFx0b3BlcmF0b3IgPSBvcGVyYXRvciB8fCBcIlwiO1xuXHRcdGxmdFBybiA9IGxmdFBybiB8fCBsZnRQcm4wIHx8IGxmdFBybjI7XG5cdFx0cGF0aCA9IHBhdGggfHwgcGF0aDI7XG5cblx0XHRpZiAobGF0ZSAmJiAobGF0ZSA9ICEvXFwpfF0vLnRlc3QoZnVsbC5jaGFyQXQoaW5kZXgtMSkpKSkge1xuXHRcdFx0cGF0aCA9IHBhdGguc2xpY2UoMSkuc3BsaXQoXCIuXCIpLmpvaW4oXCJeXCIpOyAvLyBMYXRlIHBhdGggQHouYi5jLiBVc2UgXCJeXCIgcmF0aGVyIHRoYW4gXCIuXCIgdG8gZW5zdXJlIHRoYXQgZGVlcCBiaW5kaW5nIHdpbGwgYmUgdXNlZFxuXHRcdH1cblx0XHQvLyBDb3VsZCBkbyB0aGlzIC0gYnV0IG5vdCB3b3J0aCBwZXJmIGNvc3Q/PyA6LVxuXHRcdC8vIGlmICghcGF0aC5sYXN0SW5kZXhPZihcIiNkYXRhLlwiLCAwKSkgeyBwYXRoID0gcGF0aC5zbGljZSg2KTsgfSAvLyBJZiBwYXRoIHN0YXJ0cyB3aXRoIFwiI2RhdGEuXCIsIHJlbW92ZSB0aGF0LlxuXHRcdHBybiA9IHBybiB8fCBwcm4yIHx8IFwiXCI7XG5cblx0XHR2YXIgZXhwciwgZXhwckZuLCBiaW5kcywgdGhlT2IsIG5ld09iLFxuXHRcdFx0cnRTcSA9IFwiKVwiO1xuXG5cdFx0aWYgKHBybiA9PT0gXCJbXCIpIHtcblx0XHRcdHBybiA9IFwiW2ouX3NxKFwiO1xuXHRcdFx0cnRTcSA9IFwiKV1cIjtcblx0XHR9XG5cblx0XHRpZiAoZXJyICYmICFhcG9zZWQgJiYgIXF1b3RlZCkge1xuXHRcdFx0c3ludGF4RXJyb3IocGFyYW1zKTtcblx0XHR9IGVsc2Uge1xuXHRcdFx0aWYgKGJpbmRpbmdzICYmIHJ0UHJuRG90ICYmICFhcG9zZWQgJiYgIXF1b3RlZCkge1xuXHRcdFx0XHQvLyBUaGlzIGlzIGEgYmluZGluZyB0byBhIHBhdGggaW4gd2hpY2ggYW4gb2JqZWN0IGlzIHJldHVybmVkIGJ5IGEgaGVscGVyL2RhdGEgZnVuY3Rpb24vZXhwcmVzc2lvbiwgZS5nLiBmb28oKV54Lnkgb3IgKGE/YjpjKV54Lnlcblx0XHRcdFx0Ly8gV2UgY3JlYXRlIGEgY29tcGlsZWQgZnVuY3Rpb24gdG8gZ2V0IHRoZSBvYmplY3QgaW5zdGFuY2UgKHdoaWNoIHdpbGwgYmUgY2FsbGVkIHdoZW4gdGhlIGRlcGVuZGVudCBkYXRhIG9mIHRoZSBzdWJleHByZXNzaW9uIGNoYW5nZXMsIHRvIHJldHVybiB0aGUgbmV3IG9iamVjdCwgYW5kIHRyaWdnZXIgcmUtYmluZGluZyBvZiB0aGUgc3Vic2VxdWVudCBwYXRoKVxuXHRcdFx0XHRpZiAocGFyZW5EZXB0aCAmJiAoIW5hbWVkIHx8IGJvdW5kTmFtZSB8fCBiaW5kdG8pKSB7XG5cdFx0XHRcdFx0ZXhwciA9IHBhdGhTdGFydFtwYXJlbkRlcHRoIC0gMV07XG5cdFx0XHRcdFx0aWYgKGZ1bGwubGVuZ3RoIC0gMSA+IGluZGV4IC0gKGV4cHIgfHwgMCkpIHsgLy8gV2UgbmVlZCB0byBjb21waWxlIGEgc3ViZXhwcmVzc2lvblxuXHRcdFx0XHRcdFx0ZXhwciA9IGZ1bGwuc2xpY2UoZXhwciwgaW5kZXggKyBhbGwubGVuZ3RoKTtcblx0XHRcdFx0XHRcdGlmIChleHByRm4gIT09IHRydWUpIHsgLy8gSWYgbm90IHJlZW50cmFudCBjYWxsIGR1cmluZyBjb21waWxhdGlvblxuXHRcdFx0XHRcdFx0XHRiaW5kcyA9IGJpbmR0byB8fCBibmRTdGFja1twYXJlbkRlcHRoLTFdLmJkO1xuXHRcdFx0XHRcdFx0XHQvLyBJbnNlcnQgZXhwck9iIG9iamVjdCwgdG8gYmUgdXNlZCBkdXJpbmcgYmluZGluZyB0byByZXR1cm4gdGhlIGNvbXB1dGVkIG9iamVjdFxuXHRcdFx0XHRcdFx0XHR0aGVPYiA9IGJpbmRzW2JpbmRzLmxlbmd0aC0xXTtcblx0XHRcdFx0XHRcdFx0aWYgKHRoZU9iICYmIHRoZU9iLnBybSkge1xuXHRcdFx0XHRcdFx0XHRcdHdoaWxlICh0aGVPYi5zYiAmJiB0aGVPYi5zYi5wcm0pIHtcblx0XHRcdFx0XHRcdFx0XHRcdHRoZU9iID0gdGhlT2Iuc2I7XG5cdFx0XHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdFx0XHRcdG5ld09iID0gdGhlT2Iuc2IgPSB7cGF0aDogdGhlT2Iuc2IsIGJuZDogdGhlT2IuYm5kfTtcblx0XHRcdFx0XHRcdFx0fSBlbHNlIHtcblx0XHRcdFx0XHRcdFx0XHRiaW5kcy5wdXNoKG5ld09iID0ge3BhdGg6IGJpbmRzLnBvcCgpfSk7IC8vIEluc2VydCBleHByT2Igb2JqZWN0LCB0byBiZSB1c2VkIGR1cmluZyBiaW5kaW5nIHRvIHJldHVybiB0aGUgY29tcHV0ZWQgb2JqZWN0XG5cdFx0XHRcdFx0XHRcdH1cdFx0XHRcdFx0XHRcdFx0XHRcdFx0IC8vIChlLmcuIFwic29tZS5vYmplY3QoKVwiIGluIFwic29tZS5vYmplY3QoKS5hLmJcIiAtIHRvIGJlIHVzZWQgYXMgY29udGV4dCBmb3IgYmluZGluZyB0aGUgZm9sbG93aW5nIHRva2VucyBcImEuYlwiKVxuXHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdFx0cnRQcm5Eb3QgPSBkZWxpbU9wZW5DaGFyMSArIFwiOlwiICsgZXhwciAvLyBUaGUgcGFyYW1ldGVyIG9yIGZ1bmN0aW9uIHN1YmV4cHJlc3Npb25cblx0XHRcdFx0XHRcdFx0KyBcIiBvbmVycm9yPScnXCIgLy8gc2V0IG9uZXJyb3I9JycgaW4gb3JkZXIgdG8gd3JhcCBnZW5lcmF0ZWQgY29kZSB3aXRoIGEgdHJ5IGNhdGNoIC0gcmV0dXJuaW5nICcnIGFzIG9iamVjdCBpbnN0YW5jZSBpZiB0aGVyZSBpcyBhbiBlcnJvci9taXNzaW5nIHBhcmVudFxuXHRcdFx0XHRcdFx0XHQrIGRlbGltQ2xvc2VDaGFyMDtcblx0XHRcdFx0XHRcdGV4cHJGbiA9IHRtcGxMaW5rc1tydFBybkRvdF07XG5cdFx0XHRcdFx0XHRpZiAoIWV4cHJGbikge1xuXHRcdFx0XHRcdFx0XHR0bXBsTGlua3NbcnRQcm5Eb3RdID0gdHJ1ZTsgLy8gRmxhZyB0aGF0IHRoaXMgZXhwckZuIChmb3IgcnRQcm5Eb3QpIGlzIGJlaW5nIGNvbXBpbGVkXG5cdFx0XHRcdFx0XHRcdHRtcGxMaW5rc1tydFBybkRvdF0gPSBleHByRm4gPSB0bXBsRm4ocnRQcm5Eb3QsIHRtcGwsIHRydWUpOyAvLyBDb21waWxlIHRoZSBleHByZXNzaW9uIChvciB1c2UgY2FjaGVkIGNvcHkgYWxyZWFkeSBpbiB0bXBsLmxpbmtzKVxuXHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdFx0aWYgKGV4cHJGbiAhPT0gdHJ1ZSAmJiBuZXdPYikge1xuXHRcdFx0XHRcdFx0XHQvLyBJZiBub3QgcmVlbnRyYW50IGNhbGwgZHVyaW5nIGNvbXBpbGF0aW9uXG5cdFx0XHRcdFx0XHRcdG5ld09iLl9jcGZuID0gZXhwckZuO1xuXHRcdFx0XHRcdFx0XHRuZXdPYi5wcm0gPSBibmRDdHguYmQ7XG5cdFx0XHRcdFx0XHRcdG5ld09iLmJuZCA9IG5ld09iLmJuZCB8fCBuZXdPYi5wYXRoICYmIG5ld09iLnBhdGguaW5kZXhPZihcIl5cIikgPj0gMDtcblx0XHRcdFx0XHRcdH1cblx0XHRcdFx0XHR9XG5cdFx0XHRcdH1cblx0XHRcdH1cblx0XHRcdHJldHVybiAoYXBvc2VkXG5cdFx0XHRcdC8vIHdpdGhpbiBzaW5nbGUtcXVvdGVkIHN0cmluZ1xuXHRcdFx0XHQ/IChhcG9zZWQgPSAhYXBvcywgKGFwb3NlZCA/IGFsbCA6IGxmdFBybjIgKyAnXCInKSlcblx0XHRcdFx0OiBxdW90ZWRcblx0XHRcdFx0Ly8gd2l0aGluIGRvdWJsZS1xdW90ZWQgc3RyaW5nXG5cdFx0XHRcdFx0PyAocXVvdGVkID0gIXF1b3QsIChxdW90ZWQgPyBhbGwgOiBsZnRQcm4yICsgJ1wiJykpXG5cdFx0XHRcdFx0OlxuXHRcdFx0XHQoXG5cdFx0XHRcdFx0KGxmdFByblxuXHRcdFx0XHRcdFx0PyAocGF0aFN0YXJ0W3BhcmVuRGVwdGhdID0gaW5kZXgrKywgYm5kQ3R4ID0gYm5kU3RhY2tbKytwYXJlbkRlcHRoXSA9IHtiZDogW119LCBsZnRQcm4pXG5cdFx0XHRcdFx0XHQ6IFwiXCIpXG5cdFx0XHRcdFx0KyAoc3BhY2Vcblx0XHRcdFx0XHRcdD8gKHBhcmVuRGVwdGhcblx0XHRcdFx0XHRcdFx0PyBcIlwiXG5cdFx0XHRcdC8vIE5ldyBhcmcgb3IgcHJvcCAtIHNvIGluc2VydCBiYWNrc3BhY2UgXFxiIChcXHgwOCkgYXMgc2VwYXJhdG9yIGZvciBuYW1lZCBwYXJhbXMsIHVzZWQgc3Vic2VxdWVudGx5IGJ5IHJCdWlsZEhhc2gsIGFuZCBwcmVwYXJlIG5ldyBiaW5kaW5ncyBhcnJheVxuXHRcdFx0XHRcdFx0XHQ6IChwYXJhbUluZGV4ID0gZnVsbC5zbGljZShwYXJhbUluZGV4LCBpbmRleCksIG5hbWVkXG5cdFx0XHRcdFx0XHRcdFx0PyAobmFtZWQgPSBib3VuZE5hbWUgPSBiaW5kdG8gPSBmYWxzZSwgXCJcXGJcIilcblx0XHRcdFx0XHRcdFx0XHQ6IFwiXFxiLFwiKSArIHBhcmFtSW5kZXggKyAocGFyYW1JbmRleCA9IGluZGV4ICsgYWxsLmxlbmd0aCwgYmluZGluZ3MgJiYgcGF0aEJpbmRpbmdzLnB1c2goYm5kQ3R4LmJkID0gW10pLCBcIlxcYlwiKVxuXHRcdFx0XHRcdFx0KVxuXHRcdFx0XHRcdFx0OiBlcVxuXHRcdFx0XHQvLyBuYW1lZCBwYXJhbS4gUmVtb3ZlIGJpbmRpbmdzIGZvciBhcmcgYW5kIGNyZWF0ZSBpbnN0ZWFkIGJpbmRpbmdzIGFycmF5IGZvciBwcm9wXG5cdFx0XHRcdFx0XHRcdD8gKHBhcmVuRGVwdGggJiYgc3ludGF4RXJyb3IocGFyYW1zKSwgYmluZGluZ3MgJiYgcGF0aEJpbmRpbmdzLnBvcCgpLCBuYW1lZCA9IFwiX1wiICsgcGF0aCwgYm91bmROYW1lID0gYm91bmQsIHBhcmFtSW5kZXggPSBpbmRleCArIGFsbC5sZW5ndGgsXG5cdFx0XHRcdFx0XHRcdFx0XHRiaW5kaW5ncyAmJiAoKGJpbmRpbmdzID0gYm5kQ3R4LmJkID0gcGF0aEJpbmRpbmdzW25hbWVkXSA9IFtdKSwgYmluZGluZ3Muc2twID0gIWJvdW5kKSwgcGF0aCArICc6Jylcblx0XHRcdFx0XHRcdFx0OiBwYXRoXG5cdFx0XHRcdC8vIHBhdGhcblx0XHRcdFx0XHRcdFx0XHQ/IChwYXRoLnNwbGl0KFwiXlwiKS5qb2luKFwiLlwiKS5yZXBsYWNlKHJQYXRoLCBwYXJzZVBhdGgpXG5cdFx0XHRcdFx0XHRcdFx0XHQrIChwcm5cblx0XHRcdFx0Ly8gc29tZS5mbmNhbGwoXG5cdFx0XHRcdFx0XHRcdFx0XHRcdD8gKGJuZEN0eCA9IGJuZFN0YWNrWysrcGFyZW5EZXB0aF0gPSB7YmQ6IFtdfSwgZm5DYWxsW3BhcmVuRGVwdGhdID0gcnRTcSwgcHJuKVxuXHRcdFx0XHRcdFx0XHRcdFx0XHQ6IG9wZXJhdG9yKVxuXHRcdFx0XHRcdFx0XHRcdClcblx0XHRcdFx0XHRcdFx0XHQ6IG9wZXJhdG9yXG5cdFx0XHRcdC8vIG9wZXJhdG9yXG5cdFx0XHRcdFx0XHRcdFx0XHQ/IG9wZXJhdG9yXG5cdFx0XHRcdFx0XHRcdFx0XHQ6IHJ0UHJuXG5cdFx0XHRcdC8vIGZ1bmN0aW9uXG5cdFx0XHRcdFx0XHRcdFx0XHRcdD8gKChydFBybiA9IGZuQ2FsbFtwYXJlbkRlcHRoXSB8fCBydFBybiwgZm5DYWxsW3BhcmVuRGVwdGhdID0gZmFsc2UsIGJuZEN0eCA9IGJuZFN0YWNrWy0tcGFyZW5EZXB0aF0sIHJ0UHJuKVxuXHRcdFx0XHRcdFx0XHRcdFx0XHRcdCsgKHBybiAvLyBydFBybiBhbmQgcHJuLCBlLmcgKSggaW4gKGEpKCkgb3IgYSgpKCksIG9yIClbIGluIGEoKVtdXG5cdFx0XHRcdFx0XHRcdFx0XHRcdFx0XHQ/IChibmRDdHggPSBibmRTdGFja1srK3BhcmVuRGVwdGhdLCBmbkNhbGxbcGFyZW5EZXB0aF0gPSBydFNxLCBwcm4pXG5cdFx0XHRcdFx0XHRcdFx0XHRcdFx0XHQ6IFwiXCIpXG5cdFx0XHRcdFx0XHRcdFx0XHRcdClcblx0XHRcdFx0XHRcdFx0XHRcdFx0OiBjb21tYVxuXHRcdFx0XHRcdFx0XHRcdFx0XHRcdD8gKGZuQ2FsbFtwYXJlbkRlcHRoXSB8fCBzeW50YXhFcnJvcihwYXJhbXMpLCBcIixcIikgLy8gV2UgZG9uJ3QgYWxsb3cgdG9wLWxldmVsIGxpdGVyYWwgYXJyYXlzIG9yIG9iamVjdHNcblx0XHRcdFx0XHRcdFx0XHRcdFx0XHQ6IGxmdFBybjBcblx0XHRcdFx0XHRcdFx0XHRcdFx0XHRcdD8gXCJcIlxuXHRcdFx0XHRcdFx0XHRcdFx0XHRcdFx0OiAoYXBvc2VkID0gYXBvcywgcXVvdGVkID0gcXVvdCwgJ1wiJylcblx0XHRcdFx0KSlcblx0XHRcdCk7XG5cdFx0fVxuXHR9XG5cblx0dmFyIG5hbWVkLCBiaW5kdG8sIGJvdW5kTmFtZSxcblx0XHRxdW90ZWQsIC8vIGJvb2xlYW4gZm9yIHN0cmluZyBjb250ZW50IGluIGRvdWJsZSBxdW90ZXNcblx0XHRhcG9zZWQsIC8vIG9yIGluIHNpbmdsZSBxdW90ZXNcblx0XHRiaW5kaW5ncyA9IHBhdGhCaW5kaW5ncyAmJiBwYXRoQmluZGluZ3NbMF0sIC8vIGJpbmRpbmdzIGFycmF5IGZvciB0aGUgZmlyc3QgYXJnXG5cdFx0Ym5kQ3R4ID0ge2JkOiBiaW5kaW5nc30sXG5cdFx0Ym5kU3RhY2sgPSB7MDogYm5kQ3R4fSxcblx0XHRwYXJhbUluZGV4ID0gMCwgLy8gbGlzdCxcblx0XHR0bXBsTGlua3MgPSAodG1wbCA/IHRtcGwubGlua3MgOiBiaW5kaW5ncyAmJiAoYmluZGluZ3MubGlua3MgPSBiaW5kaW5ncy5saW5rcyB8fCB7fSkpIHx8IHRvcFZpZXcudG1wbC5saW5rcyxcblx0XHQvLyBUaGUgZm9sbG93aW5nIGFyZSB1c2VkIGZvciB0cmFja2luZyBwYXRoIHBhcnNpbmcgaW5jbHVkaW5nIG5lc3RlZCBwYXRocywgc3VjaCBhcyBcImEuYihjXmQgKyAoZSkpXmZcIiwgYW5kIGNoYWluZWQgY29tcHV0ZWQgcGF0aHMgc3VjaCBhc1xuXHRcdC8vIFwiYS5iKCkuY15kKCkuZS5mKCkuZ1wiIC0gd2hpY2ggaGFzIGZvdXIgY2hhaW5lZCBwYXRocywgXCJhLmIoKVwiLCBcIl5jLmQoKVwiLCBcIi5lLmYoKVwiIGFuZCBcIi5nXCJcblx0XHRwYXJlbkRlcHRoID0gMCxcblx0XHRmbkNhbGwgPSB7fSwgLy8gV2UgYXJlIGluIGEgZnVuY3Rpb24gY2FsbFxuXHRcdHBhdGhTdGFydCA9IHt9LCAvLyB0cmFja3MgdGhlIHN0YXJ0IG9mIHRoZSBjdXJyZW50IHBhdGggc3VjaCBhcyBjXmQoKSBpbiB0aGUgYWJvdmUgZXhhbXBsZVxuXHRcdHJlc3VsdDtcblxuXHRpZiAocGFyYW1zLmNoYXJBdCgwKSA9PT0gXCJAXCIpIHtcblx0XHRwYXJhbXMgPSBwYXJhbXMucmVwbGFjZShyQnJhY2tldFF1b3RlLCBcIi5cIik7XG5cdH1cblx0cmVzdWx0ID0gKHBhcmFtcyArICh0bXBsID8gXCIgXCIgOiBcIlwiKSkucmVwbGFjZShyUGFyYW1zLCBwYXJzZVRva2Vucyk7XG5cblx0cmV0dXJuICFwYXJlbkRlcHRoICYmIHJlc3VsdCB8fCBzeW50YXhFcnJvcihwYXJhbXMpOyAvLyBTeW50YXggZXJyb3IgaWYgdW5iYWxhbmNlZCBwYXJlbnMgaW4gcGFyYW1zIGV4cHJlc3Npb25cbn1cblxuZnVuY3Rpb24gYnVpbGRDb2RlKGFzdCwgdG1wbCwgaXNMaW5rRXhwcikge1xuXHQvLyBCdWlsZCB0aGUgdGVtcGxhdGUgZnVuY3Rpb24gY29kZSBmcm9tIHRoZSBBU1Qgbm9kZXMsIGFuZCBzZXQgYXMgcHJvcGVydHkgb24gdGhlIHBhc3NlZC1pbiB0ZW1wbGF0ZSBvYmplY3Rcblx0Ly8gVXNlZCBmb3IgY29tcGlsaW5nIHRlbXBsYXRlcywgYW5kIGFsc28gYnkgSnNWaWV3cyB0byBidWlsZCBmdW5jdGlvbnMgZm9yIGRhdGEgbGluayBleHByZXNzaW9uc1xuXHR2YXIgaSwgbm9kZSwgdGFnTmFtZSwgY29udmVydGVyLCB0YWdDdHgsIGhhc1RhZywgaGFzRW5jb2RlciwgZ2V0c1ZhbCwgaGFzQ252dCwgdXNlQ252dCwgdG1wbEJpbmRpbmdzLCBwYXRoQmluZGluZ3MsIHBhcmFtcywgYm91bmRPbkVyclN0YXJ0LFxuXHRcdGJvdW5kT25FcnJFbmQsIHRhZ1JlbmRlciwgbmVzdGVkVG1wbHMsIHRtcGxOYW1lLCBuZXN0ZWRUbXBsLCB0YWdBbmRFbHNlcywgY29udGVudCwgbWFya3VwLCBuZXh0SXNFbHNlLCBvbGRDb2RlLCBpc0Vsc2UsIGlzR2V0VmFsLCB0YWdDdHhGbixcblx0XHRvbkVycm9yLCB0YWdTdGFydCwgdHJpZ2dlciwgbGF0ZVJlbmRlciwgcmV0U3RyT3BlbiwgcmV0U3RyQ2xvc2UsXG5cdFx0dG1wbEJpbmRpbmdLZXkgPSAwLFxuXHRcdHVzZVZpZXdzID0gJHN1YlNldHRpbmdzQWR2YW5jZWQudXNlVmlld3MgfHwgdG1wbC51c2VWaWV3cyB8fCB0bXBsLnRhZ3MgfHwgdG1wbC50ZW1wbGF0ZXMgfHwgdG1wbC5oZWxwZXJzIHx8IHRtcGwuY29udmVydGVycyxcblx0XHRjb2RlID0gXCJcIixcblx0XHR0bXBsT3B0aW9ucyA9IHt9LFxuXHRcdGwgPSBhc3QubGVuZ3RoO1xuXG5cdGlmIChcIlwiICsgdG1wbCA9PT0gdG1wbCkge1xuXHRcdHRtcGxOYW1lID0gaXNMaW5rRXhwciA/ICdkYXRhLWxpbms9XCInICsgdG1wbC5yZXBsYWNlKHJOZXdMaW5lLCBcIiBcIikuc2xpY2UoMSwgLTEpICsgJ1wiJyA6IHRtcGw7XG5cdFx0dG1wbCA9IDA7XG5cdH0gZWxzZSB7XG5cdFx0dG1wbE5hbWUgPSB0bXBsLnRtcGxOYW1lIHx8IFwidW5uYW1lZFwiO1xuXHRcdGlmICh0bXBsLmFsbG93Q29kZSkge1xuXHRcdFx0dG1wbE9wdGlvbnMuYWxsb3dDb2RlID0gdHJ1ZTtcblx0XHR9XG5cdFx0aWYgKHRtcGwuZGVidWcpIHtcblx0XHRcdHRtcGxPcHRpb25zLmRlYnVnID0gdHJ1ZTtcblx0XHR9XG5cdFx0dG1wbEJpbmRpbmdzID0gdG1wbC5ibmRzO1xuXHRcdG5lc3RlZFRtcGxzID0gdG1wbC50bXBscztcblx0fVxuXHRmb3IgKGkgPSAwOyBpIDwgbDsgaSsrKSB7XG5cdFx0Ly8gQVNUIG5vZGVzOiBbMDogdGFnTmFtZSwgMTogY29udmVydGVyLCAyOiBjb250ZW50LCAzOiBwYXJhbXMsIDQ6IGNvZGUsIDU6IG9uRXJyb3IsIDY6IHRyaWdnZXIsIDc6cGF0aEJpbmRpbmdzLCA4OiBjb250ZW50TWFya3VwXVxuXHRcdG5vZGUgPSBhc3RbaV07XG5cblx0XHQvLyBBZGQgbmV3bGluZSBmb3IgZWFjaCBjYWxsb3V0IHRvIHQoKSBjKCkgZXRjLiBhbmQgZWFjaCBtYXJrdXAgc3RyaW5nXG5cdFx0aWYgKFwiXCIgKyBub2RlID09PSBub2RlKSB7XG5cdFx0XHQvLyBhIG1hcmt1cCBzdHJpbmcgdG8gYmUgaW5zZXJ0ZWRcblx0XHRcdGNvZGUgKz0gJ1xcbitcIicgKyBub2RlICsgJ1wiJztcblx0XHR9IGVsc2Uge1xuXHRcdFx0Ly8gYSBjb21waWxlZCB0YWcgZXhwcmVzc2lvbiB0byBiZSBpbnNlcnRlZFxuXHRcdFx0dGFnTmFtZSA9IG5vZGVbMF07XG5cdFx0XHRpZiAodGFnTmFtZSA9PT0gXCIqXCIpIHtcblx0XHRcdFx0Ly8gQ29kZSB0YWc6IHt7KiB9fVxuXHRcdFx0XHRjb2RlICs9IFwiO1xcblwiICsgbm9kZVsxXSArIFwiXFxucmV0PXJldFwiO1xuXHRcdFx0fSBlbHNlIHtcblx0XHRcdFx0Y29udmVydGVyID0gbm9kZVsxXTtcblx0XHRcdFx0Y29udGVudCA9ICFpc0xpbmtFeHByICYmIG5vZGVbMl07XG5cdFx0XHRcdHRhZ0N0eCA9IHBhcmFtU3RydWN0dXJlKG5vZGVbM10sICdwYXJhbXMnKSArICd9LCcgKyBwYXJhbVN0cnVjdHVyZShwYXJhbXMgPSBub2RlWzRdKTtcblx0XHRcdFx0dHJpZ2dlciA9IG5vZGVbNl07XG5cdFx0XHRcdGxhdGVSZW5kZXIgPSBub2RlWzddO1xuXHRcdFx0XHRpZiAobm9kZVs4XSkgeyAvLyBsYXRlUGF0aCBAYS5iLmMgb3IgQH5hLmIuY1xuXHRcdFx0XHRcdHJldFN0ck9wZW4gPSBcIlxcbnZhciBvYixsdE9iPXt9LGN0eHM9XCI7XG5cdFx0XHRcdFx0cmV0U3RyQ2xvc2UgPSBcIjtcXG5jdHhzLmx0PWx0T2IubHQ7XFxucmV0dXJuIGN0eHM7XCI7XG5cdFx0XHRcdH0gZWxzZSB7XG5cdFx0XHRcdFx0cmV0U3RyT3BlbiA9IFwiXFxucmV0dXJuIFwiO1xuXHRcdFx0XHRcdHJldFN0ckNsb3NlID0gXCJcIjtcblx0XHRcdFx0fVxuXHRcdFx0XHRtYXJrdXAgPSBub2RlWzEwXSAmJiBub2RlWzEwXS5yZXBsYWNlKHJVbmVzY2FwZVF1b3RlcywgXCIkMVwiKTtcblx0XHRcdFx0aWYgKGlzRWxzZSA9IHRhZ05hbWUgPT09IFwiZWxzZVwiKSB7XG5cdFx0XHRcdFx0aWYgKHBhdGhCaW5kaW5ncykge1xuXHRcdFx0XHRcdFx0cGF0aEJpbmRpbmdzLnB1c2gobm9kZVs5XSk7XG5cdFx0XHRcdFx0fVxuXHRcdFx0XHR9IGVsc2Uge1xuXHRcdFx0XHRcdG9uRXJyb3IgPSBub2RlWzVdIHx8ICRzdWJTZXR0aW5ncy5kZWJ1Z01vZGUgIT09IGZhbHNlICYmIFwidW5kZWZpbmVkXCI7IC8vIElmIGRlYnVnTW9kZSBub3QgZmFsc2UsIHNldCBkZWZhdWx0IG9uRXJyb3IgaGFuZGxlciBvbiB0YWcgdG8gXCJ1bmRlZmluZWRcIiAoc2VlIG9uUmVuZGVyRXJyb3IpXG5cdFx0XHRcdFx0aWYgKHRtcGxCaW5kaW5ncyAmJiAocGF0aEJpbmRpbmdzID0gbm9kZVs5XSkpIHsgLy8gQXJyYXkgb2YgcGF0aHMsIG9yIGZhbHNlIGlmIG5vdCBkYXRhLWJvdW5kXG5cdFx0XHRcdFx0XHRwYXRoQmluZGluZ3MgPSBbcGF0aEJpbmRpbmdzXTtcblx0XHRcdFx0XHRcdHRtcGxCaW5kaW5nS2V5ID0gdG1wbEJpbmRpbmdzLnB1c2goMSk7IC8vIEFkZCBwbGFjZWhvbGRlciBpbiB0bXBsQmluZGluZ3MgZm9yIGNvbXBpbGVkIGZ1bmN0aW9uXG5cdFx0XHRcdFx0fVxuXHRcdFx0XHR9XG5cdFx0XHRcdHVzZVZpZXdzID0gdXNlVmlld3MgfHwgcGFyYW1zWzFdIHx8IHBhcmFtc1syXSB8fCBwYXRoQmluZGluZ3MgfHwgL3ZpZXcuKD8haW5kZXgpLy50ZXN0KHBhcmFtc1swXSk7XG5cdFx0XHRcdC8vIHVzZVZpZXdzIGlzIGZvciBwZXJmIG9wdGltaXphdGlvbi4gRm9yIHJlbmRlcigpIHdlIG9ubHkgdXNlIHZpZXdzIGlmIG5lY2Vzc2FyeSAtIGZvciB0aGUgbW9yZSBhZHZhbmNlZCBzY2VuYXJpb3MuXG5cdFx0XHRcdC8vIFdlIHVzZSB2aWV3cyBpZiB0aGVyZSBhcmUgcHJvcHMsIGNvbnRleHR1YWwgcHJvcGVydGllcyBvciBhcmdzIHdpdGggIy4uLiAob3RoZXIgdGhhbiAjaW5kZXgpIC0gYnV0IHlvdSBjYW4gZm9yY2Vcblx0XHRcdFx0Ly8gdXNpbmcgdGhlIGZ1bGwgdmlldyBpbmZyYXN0cnVjdHVyZSwgKGFuZCBwYXkgYSBwZXJmIHByaWNlKSBieSBvcHRpbmcgaW46IFNldCB1c2VWaWV3czogdHJ1ZSBvbiB0aGUgdGVtcGxhdGUsIG1hbnVhbGx5Li4uXG5cdFx0XHRcdGlmIChpc0dldFZhbCA9IHRhZ05hbWUgPT09IFwiOlwiKSB7XG5cdFx0XHRcdFx0aWYgKGNvbnZlcnRlcikge1xuXHRcdFx0XHRcdFx0dGFnTmFtZSA9IGNvbnZlcnRlciA9PT0gSFRNTCA/IFwiPlwiIDogY29udmVydGVyICsgdGFnTmFtZTtcblx0XHRcdFx0XHR9XG5cdFx0XHRcdH0gZWxzZSB7XG5cdFx0XHRcdFx0aWYgKGNvbnRlbnQpIHsgLy8gVE9ETyBvcHRpbWl6ZSAtIGlmIGNvbnRlbnQubGVuZ3RoID09PSAwIG9yIGlmIHRoZXJlIGlzIGEgdG1wbD1cIi4uLlwiIHNwZWNpZmllZCAtIHNldCBjb250ZW50IHRvIG51bGwgLyBkb24ndCBydW4gdGhpcyBjb21waWxhdGlvbiBjb2RlIC0gc2luY2UgY29udGVudCB3b24ndCBnZXQgdXNlZCEhXG5cdFx0XHRcdFx0XHQvLyBDcmVhdGUgdGVtcGxhdGUgb2JqZWN0IGZvciBuZXN0ZWQgdGVtcGxhdGVcblx0XHRcdFx0XHRcdG5lc3RlZFRtcGwgPSB0bXBsT2JqZWN0KG1hcmt1cCwgdG1wbE9wdGlvbnMpO1xuXHRcdFx0XHRcdFx0bmVzdGVkVG1wbC50bXBsTmFtZSA9IHRtcGxOYW1lICsgXCIvXCIgKyB0YWdOYW1lO1xuXHRcdFx0XHRcdFx0Ly8gQ29tcGlsZSB0byBBU1QgYW5kIHRoZW4gdG8gY29tcGlsZWQgZnVuY3Rpb25cblx0XHRcdFx0XHRcdG5lc3RlZFRtcGwudXNlVmlld3MgPSBuZXN0ZWRUbXBsLnVzZVZpZXdzIHx8IHVzZVZpZXdzO1xuXHRcdFx0XHRcdFx0YnVpbGRDb2RlKGNvbnRlbnQsIG5lc3RlZFRtcGwpO1xuXHRcdFx0XHRcdFx0dXNlVmlld3MgPSBuZXN0ZWRUbXBsLnVzZVZpZXdzO1xuXHRcdFx0XHRcdFx0bmVzdGVkVG1wbHMucHVzaChuZXN0ZWRUbXBsKTtcblx0XHRcdFx0XHR9XG5cblx0XHRcdFx0XHRpZiAoIWlzRWxzZSkge1xuXHRcdFx0XHRcdFx0Ly8gVGhpcyBpcyBub3QgYW4gZWxzZSB0YWcuXG5cdFx0XHRcdFx0XHR0YWdBbmRFbHNlcyA9IHRhZ05hbWU7XG5cdFx0XHRcdFx0XHR1c2VWaWV3cyA9IHVzZVZpZXdzIHx8IHRhZ05hbWUgJiYgKCEkdGFnc1t0YWdOYW1lXSB8fCAhJHRhZ3NbdGFnTmFtZV0uZmxvdyk7XG5cdFx0XHRcdFx0XHQvLyBTd2l0Y2ggdG8gYSBuZXcgY29kZSBzdHJpbmcgZm9yIHRoaXMgYm91bmQgdGFnIChhbmQgaXRzIGVsc2VzLCBpZiBpdCBoYXMgYW55KSAtIGZvciByZXR1cm5pbmcgdGhlIHRhZ0N0eHMgYXJyYXlcblx0XHRcdFx0XHRcdG9sZENvZGUgPSBjb2RlO1xuXHRcdFx0XHRcdFx0Y29kZSA9IFwiXCI7XG5cdFx0XHRcdFx0fVxuXHRcdFx0XHRcdG5leHRJc0Vsc2UgPSBhc3RbaSArIDFdO1xuXHRcdFx0XHRcdG5leHRJc0Vsc2UgPSBuZXh0SXNFbHNlICYmIG5leHRJc0Vsc2VbMF0gPT09IFwiZWxzZVwiO1xuXHRcdFx0XHR9XG5cdFx0XHRcdHRhZ1N0YXJ0ID0gb25FcnJvciA/IFwiO1xcbnRyeXtcXG5yZXQrPVwiIDogXCJcXG4rXCI7XG5cdFx0XHRcdGJvdW5kT25FcnJTdGFydCA9IFwiXCI7XG5cdFx0XHRcdGJvdW5kT25FcnJFbmQgPSBcIlwiO1xuXG5cdFx0XHRcdGlmIChpc0dldFZhbCAmJiAocGF0aEJpbmRpbmdzIHx8IHRyaWdnZXIgfHwgY29udmVydGVyICYmIGNvbnZlcnRlciAhPT0gSFRNTCB8fCBsYXRlUmVuZGVyKSkge1xuXHRcdFx0XHRcdC8vIEZvciBjb252ZXJ0VmFsIHdlIG5lZWQgYSBjb21waWxlZCBmdW5jdGlvbiB0byByZXR1cm4gdGhlIG5ldyB0YWdDdHgocylcblx0XHRcdFx0XHR0YWdDdHhGbiA9IG5ldyBGdW5jdGlvbihcImRhdGEsdmlldyxqLHVcIiwgXCIvLyBcIiArIHRtcGxOYW1lICsgXCIgXCIgKyAoKyt0bXBsQmluZGluZ0tleSkgKyBcIiBcIiArIHRhZ05hbWVcblx0XHRcdFx0XHRcdCsgcmV0U3RyT3BlbiArIFwie1wiICsgdGFnQ3R4ICsgXCJ9O1wiICsgcmV0U3RyQ2xvc2UpO1xuXHRcdFx0XHRcdHRhZ0N0eEZuLl9lciA9IG9uRXJyb3I7XG5cdFx0XHRcdFx0dGFnQ3R4Rm4uX3RhZyA9IHRhZ05hbWU7XG5cdFx0XHRcdFx0dGFnQ3R4Rm4uX2JkID0gISFwYXRoQmluZGluZ3M7IC8vIGRhdGEtbGlua2VkIHRhZyB7XnsuLi4vfX1cblx0XHRcdFx0XHR0YWdDdHhGbi5fbHIgPSBsYXRlUmVuZGVyO1xuXG5cdFx0XHRcdFx0aWYgKGlzTGlua0V4cHIpIHtcblx0XHRcdFx0XHRcdHJldHVybiB0YWdDdHhGbjtcblx0XHRcdFx0XHR9XG5cblx0XHRcdFx0XHRzZXRQYXRocyh0YWdDdHhGbiwgcGF0aEJpbmRpbmdzKTtcblx0XHRcdFx0XHR0YWdSZW5kZXIgPSAnYyhcIicgKyBjb252ZXJ0ZXIgKyAnXCIsdmlldywnO1xuXHRcdFx0XHRcdHVzZUNudnQgPSB0cnVlO1xuXHRcdFx0XHRcdGJvdW5kT25FcnJTdGFydCA9IHRhZ1JlbmRlciArIHRtcGxCaW5kaW5nS2V5ICsgXCIsXCI7XG5cdFx0XHRcdFx0Ym91bmRPbkVyckVuZCA9IFwiKVwiO1xuXHRcdFx0XHR9XG5cdFx0XHRcdGNvZGUgKz0gKGlzR2V0VmFsXG5cdFx0XHRcdFx0PyAoaXNMaW5rRXhwciA/IChvbkVycm9yID8gXCJ0cnl7XFxuXCIgOiBcIlwiKSArIFwicmV0dXJuIFwiIDogdGFnU3RhcnQpICsgKHVzZUNudnQgLy8gQ2FsbCBfY252dCBpZiB0aGVyZSBpcyBhIGNvbnZlcnRlcjoge3tjbnZ0OiAuLi4gfX0gb3Ige157Y252dDogLi4uIH19XG5cdFx0XHRcdFx0XHQ/ICh1c2VDbnZ0ID0gdW5kZWZpbmVkLCB1c2VWaWV3cyA9IGhhc0NudnQgPSB0cnVlLCB0YWdSZW5kZXIgKyAodGFnQ3R4Rm5cblx0XHRcdFx0XHRcdFx0PyAoKHRtcGxCaW5kaW5nc1t0bXBsQmluZGluZ0tleSAtIDFdID0gdGFnQ3R4Rm4pLCB0bXBsQmluZGluZ0tleSkgLy8gU3RvcmUgdGhlIGNvbXBpbGVkIHRhZ0N0eEZuIGluIHRtcGwuYm5kcywgYW5kIHBhc3MgdGhlIGtleSB0byBjb252ZXJ0VmFsKClcblx0XHRcdFx0XHRcdFx0OiBcIntcIiArIHRhZ0N0eCArIFwifVwiKSArIFwiKVwiKVxuXHRcdFx0XHRcdFx0OiB0YWdOYW1lID09PSBcIj5cIlxuXHRcdFx0XHRcdFx0XHQ/IChoYXNFbmNvZGVyID0gdHJ1ZSwgXCJoKFwiICsgcGFyYW1zWzBdICsgXCIpXCIpXG5cdFx0XHRcdFx0XHRcdDogKGdldHNWYWwgPSB0cnVlLCBcIigodj1cIiArIHBhcmFtc1swXSArICcpIT1udWxsP3Y6JyArIChpc0xpbmtFeHByID8gJ251bGwpJyA6ICdcIlwiKScpKVxuXHRcdFx0XHRcdFx0XHQvLyBOb24gc3RyaWN0IGVxdWFsaXR5IHNvIGRhdGEtbGluaz1cInRpdGxlezpleHByfVwiIHdpdGggZXhwcj1udWxsL3VuZGVmaW5lZCByZW1vdmVzIHRpdGxlIGF0dHJpYnV0ZVxuXHRcdFx0XHRcdClcblx0XHRcdFx0XHQ6IChoYXNUYWcgPSB0cnVlLCBcIlxcbnt2aWV3OnZpZXcsdG1wbDpcIiAvLyBBZGQgdGhpcyB0YWdDdHggdG8gdGhlIGNvbXBpbGVkIGNvZGUgZm9yIHRoZSB0YWdDdHhzIHRvIGJlIHBhc3NlZCB0byByZW5kZXJUYWcoKVxuXHRcdFx0XHRcdFx0KyAoY29udGVudCA/IG5lc3RlZFRtcGxzLmxlbmd0aCA6IFwiMFwiKSArIFwiLFwiIC8vIEZvciBibG9jayB0YWdzLCBwYXNzIGluIHRoZSBrZXkgKG5lc3RlZFRtcGxzLmxlbmd0aCkgdG8gdGhlIG5lc3RlZCBjb250ZW50IHRlbXBsYXRlXG5cdFx0XHRcdFx0XHQrIHRhZ0N0eCArIFwifSxcIikpO1xuXG5cdFx0XHRcdGlmICh0YWdBbmRFbHNlcyAmJiAhbmV4dElzRWxzZSkge1xuXHRcdFx0XHRcdC8vIFRoaXMgaXMgYSBkYXRhLWxpbmsgZXhwcmVzc2lvbiBvciBhbiBpbmxpbmUgdGFnIHdpdGhvdXQgYW55IGVsc2VzLCBvciB0aGUgbGFzdCB7e2Vsc2V9fSBvZiBhbiBpbmxpbmUgdGFnXG5cdFx0XHRcdFx0Ly8gV2UgY29tcGxldGUgdGhlIGNvZGUgZm9yIHJldHVybmluZyB0aGUgdGFnQ3R4cyBhcnJheVxuXHRcdFx0XHRcdGNvZGUgPSBcIltcIiArIGNvZGUuc2xpY2UoMCwgLTEpICsgXCJdXCI7XG5cdFx0XHRcdFx0dGFnUmVuZGVyID0gJ3QoXCInICsgdGFnQW5kRWxzZXMgKyAnXCIsdmlldyx0aGlzLCc7XG5cdFx0XHRcdFx0aWYgKGlzTGlua0V4cHIgfHwgcGF0aEJpbmRpbmdzKSB7XG5cdFx0XHRcdFx0XHQvLyBUaGlzIGlzIGEgYm91bmQgdGFnIChkYXRhLWxpbmsgZXhwcmVzc2lvbiBvciBpbmxpbmUgYm91bmQgdGFnIHtee3RhZyAuLi59fSkgc28gd2Ugc3RvcmUgYSBjb21waWxlZCB0YWdDdHhzIGZ1bmN0aW9uIGluIHRtcC5ibmRzXG5cdFx0XHRcdFx0XHRjb2RlID0gbmV3IEZ1bmN0aW9uKFwiZGF0YSx2aWV3LGosdVwiLCBcIiAvLyBcIiArIHRtcGxOYW1lICsgXCIgXCIgKyB0bXBsQmluZGluZ0tleSArIFwiIFwiICsgdGFnQW5kRWxzZXMgKyByZXRTdHJPcGVuICsgY29kZVxuXHRcdFx0XHRcdFx0XHQrIHJldFN0ckNsb3NlKTtcblx0XHRcdFx0XHRcdGNvZGUuX2VyID0gb25FcnJvcjtcblx0XHRcdFx0XHRcdGNvZGUuX3RhZyA9IHRhZ0FuZEVsc2VzO1xuXHRcdFx0XHRcdFx0aWYgKHBhdGhCaW5kaW5ncykge1xuXHRcdFx0XHRcdFx0XHRzZXRQYXRocyh0bXBsQmluZGluZ3NbdG1wbEJpbmRpbmdLZXkgLSAxXSA9IGNvZGUsIHBhdGhCaW5kaW5ncyk7XG5cdFx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0XHRjb2RlLl9sciA9IGxhdGVSZW5kZXI7XG5cdFx0XHRcdFx0XHRpZiAoaXNMaW5rRXhwcikge1xuXHRcdFx0XHRcdFx0XHRyZXR1cm4gY29kZTsgLy8gRm9yIGEgZGF0YS1saW5rIGV4cHJlc3Npb24gd2UgcmV0dXJuIHRoZSBjb21waWxlZCB0YWdDdHhzIGZ1bmN0aW9uXG5cdFx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0XHRib3VuZE9uRXJyU3RhcnQgPSB0YWdSZW5kZXIgKyB0bXBsQmluZGluZ0tleSArIFwiLHVuZGVmaW5lZCxcIjtcblx0XHRcdFx0XHRcdGJvdW5kT25FcnJFbmQgPSBcIilcIjtcblx0XHRcdFx0XHR9XG5cblx0XHRcdFx0XHQvLyBUaGlzIGlzIHRoZSBsYXN0IHt7ZWxzZX19IGZvciBhbiBpbmxpbmUgdGFnLlxuXHRcdFx0XHRcdC8vIEZvciBhIGJvdW5kIHRhZywgcGFzcyB0aGUgdGFnQ3R4cyBmbiBsb29rdXAga2V5IHRvIHJlbmRlclRhZy5cblx0XHRcdFx0XHQvLyBGb3IgYW4gdW5ib3VuZCB0YWcsIGluY2x1ZGUgdGhlIGNvZGUgZGlyZWN0bHkgZm9yIGV2YWx1YXRpbmcgdGFnQ3R4cyBhcnJheVxuXHRcdFx0XHRcdGNvZGUgPSBvbGRDb2RlICsgdGFnU3RhcnQgKyB0YWdSZW5kZXIgKyAocGF0aEJpbmRpbmdzICYmIHRtcGxCaW5kaW5nS2V5IHx8IGNvZGUpICsgXCIpXCI7XG5cdFx0XHRcdFx0cGF0aEJpbmRpbmdzID0gMDtcblx0XHRcdFx0XHR0YWdBbmRFbHNlcyA9IDA7XG5cdFx0XHRcdH1cblx0XHRcdFx0aWYgKG9uRXJyb3IgJiYgIW5leHRJc0Vsc2UpIHtcblx0XHRcdFx0XHR1c2VWaWV3cyA9IHRydWU7XG5cdFx0XHRcdFx0Y29kZSArPSAnO1xcbn1jYXRjaChlKXtyZXQnICsgKGlzTGlua0V4cHIgPyBcInVybiBcIiA6IFwiKz1cIikgKyBib3VuZE9uRXJyU3RhcnQgKyAnai5fZXJyKGUsdmlldywnICsgb25FcnJvciArICcpJyArIGJvdW5kT25FcnJFbmQgKyAnO30nICsgKGlzTGlua0V4cHIgPyBcIlwiIDogJ3JldD1yZXQnKTtcblx0XHRcdFx0fVxuXHRcdFx0fVxuXHRcdH1cblx0fVxuXHQvLyBJbmNsdWRlIG9ubHkgdGhlIHZhciByZWZlcmVuY2VzIHRoYXQgYXJlIG5lZWRlZCBpbiB0aGUgY29kZVxuXHRjb2RlID0gXCIvLyBcIiArIHRtcGxOYW1lXG5cdFx0KyAodG1wbE9wdGlvbnMuZGVidWcgPyBcIlxcbmRlYnVnZ2VyO1wiIDogXCJcIilcblx0XHQrIFwiXFxudmFyIHZcIlxuXHRcdCsgKGhhc1RhZyA/IFwiLHQ9ai5fdGFnXCIgOiBcIlwiKSAgICAgICAgICAgICAgICAvLyBoYXMgdGFnXG5cdFx0KyAoaGFzQ252dCA/IFwiLGM9ai5fY252dFwiIDogXCJcIikgICAgICAgICAgICAgIC8vIGNvbnZlcnRlclxuXHRcdCsgKGhhc0VuY29kZXIgPyBcIixoPWouX2h0bWxcIiA6IFwiXCIpICAgICAgICAgICAvLyBodG1sIGNvbnZlcnRlclxuXHRcdCsgKGlzTGlua0V4cHJcblx0XHRcdFx0PyAobm9kZVs4XSAgLy8gbGF0ZSBALi4uIHBhdGg/XG5cdFx0XHRcdFx0XHQ/IFwiLCBvYlwiXG5cdFx0XHRcdFx0XHQ6IFwiXCJcblx0XHRcdFx0XHQpICsgXCI7XFxuXCJcblx0XHRcdFx0OiAnLHJldD1cIlwiJylcblx0XHQrIGNvZGVcblx0XHQrIChpc0xpbmtFeHByID8gXCJcXG5cIiA6IFwiO1xcbnJldHVybiByZXQ7XCIpO1xuXG5cdHRyeSB7XG5cdFx0Y29kZSA9IG5ldyBGdW5jdGlvbihcImRhdGEsdmlldyxqLHVcIiwgY29kZSk7XG5cdH0gY2F0Y2ggKGUpIHtcblx0XHRzeW50YXhFcnJvcihcIkNvbXBpbGVkIHRlbXBsYXRlIGNvZGU6XFxuXFxuXCIgKyBjb2RlICsgJ1xcbjogXCInICsgKGUubWVzc2FnZXx8ZSkgKyAnXCInKTtcblx0fVxuXHRpZiAodG1wbCkge1xuXHRcdHRtcGwuZm4gPSBjb2RlO1xuXHRcdHRtcGwudXNlVmlld3MgPSAhIXVzZVZpZXdzO1xuXHR9XG5cdHJldHVybiBjb2RlO1xufVxuXG4vLz09PT09PT09PT1cbi8vIFV0aWxpdGllc1xuLy89PT09PT09PT09XG5cbi8vIE1lcmdlIG9iamVjdHMsIGluIHBhcnRpY3VsYXIgY29udGV4dHMgd2hpY2ggaW5oZXJpdCBmcm9tIHBhcmVudCBjb250ZXh0c1xuZnVuY3Rpb24gZXh0ZW5kQ3R4KGNvbnRleHQsIHBhcmVudENvbnRleHQpIHtcblx0Ly8gUmV0dXJuIGNvcHkgb2YgcGFyZW50Q29udGV4dCwgdW5sZXNzIGNvbnRleHQgaXMgZGVmaW5lZCBhbmQgaXMgZGlmZmVyZW50LCBpbiB3aGljaCBjYXNlIHJldHVybiBhIG5ldyBtZXJnZWQgY29udGV4dFxuXHQvLyBJZiBuZWl0aGVyIGNvbnRleHQgbm9yIHBhcmVudENvbnRleHQgYXJlIGRlZmluZWQsIHJldHVybiB1bmRlZmluZWRcblx0cmV0dXJuIGNvbnRleHQgJiYgY29udGV4dCAhPT0gcGFyZW50Q29udGV4dFxuXHRcdD8gKHBhcmVudENvbnRleHRcblx0XHRcdD8gJGV4dGVuZCgkZXh0ZW5kKHt9LCBwYXJlbnRDb250ZXh0KSwgY29udGV4dClcblx0XHRcdDogY29udGV4dClcblx0XHQ6IHBhcmVudENvbnRleHQgJiYgJGV4dGVuZCh7fSwgcGFyZW50Q29udGV4dCk7XG59XG5cbmZ1bmN0aW9uIGdldFRhcmdldFByb3BzKHNvdXJjZSwgdGFnQ3R4KSB7XG5cdC8vIHRoaXMgcG9pbnRlciBpcyB0aGVNYXAgLSB3aGljaCBoYXMgdGFnQ3R4LnByb3BzIHRvb1xuXHQvLyBhcmd1bWVudHM6IHRhZ0N0eC5hcmdzLlxuXHR2YXIga2V5LCBwcm9wLFxuXHRcdHByb3BzID0gW107XG5cblx0aWYgKHR5cGVvZiBzb3VyY2UgPT09IE9CSkVDVCB8fCAkaXNGdW5jdGlvbihzb3VyY2UpKSB7XG5cdFx0Zm9yIChrZXkgaW4gc291cmNlKSB7XG5cdFx0XHRwcm9wID0gc291cmNlW2tleV07XG5cdFx0XHRpZiAoa2V5ICE9PSAkZXhwYW5kbyAmJiBzb3VyY2UuaGFzT3duUHJvcGVydHkoa2V5KSAmJiAoIXRhZ0N0eC5wcm9wcy5ub0Z1bmN0aW9ucyB8fCAhJC5pc0Z1bmN0aW9uKHByb3ApKSkge1xuXHRcdFx0XHRwcm9wcy5wdXNoKHtrZXk6IGtleSwgcHJvcDogcHJvcH0pO1xuXHRcdFx0fVxuXHRcdH1cblx0fVxuXHRyZXR1cm4gZ2V0VGFyZ2V0U29ydGVkKHByb3BzLCB0YWdDdHgpO1xufVxuXG5mdW5jdGlvbiBnZXRUYXJnZXRTb3J0ZWQodmFsdWUsIHRhZ0N0eCkge1xuXHQvLyBnZXRUZ3Rcblx0dmFyIG1hcHBlZCwgc3RhcnQsIGVuZCxcblx0XHR0YWcgPSB0YWdDdHgudGFnLFxuXHRcdHByb3BzID0gdGFnQ3R4LnByb3BzLFxuXHRcdHByb3BQYXJhbXMgPSB0YWdDdHgucGFyYW1zLnByb3BzLFxuXHRcdGZpbHRlciA9IHByb3BzLmZpbHRlcixcblx0XHRzb3J0ID0gcHJvcHMuc29ydCxcblx0XHRkaXJlY3RTb3J0ID0gc29ydCA9PT0gdHJ1ZSxcblx0XHRzdGVwID0gcGFyc2VJbnQocHJvcHMuc3RlcCksXG5cdFx0cmV2ZXJzZSA9IHByb3BzLnJldmVyc2UgPyAtMSA6IDE7XG5cblx0aWYgKCEkaXNBcnJheSh2YWx1ZSkpIHtcblx0XHRyZXR1cm4gdmFsdWU7XG5cdH1cblx0aWYgKGRpcmVjdFNvcnQgfHwgc29ydCAmJiBcIlwiICsgc29ydCA9PT0gc29ydCkge1xuXHRcdC8vIFRlbXBvcmFyeSBtYXBwZWQgYXJyYXkgaG9sZHMgb2JqZWN0cyB3aXRoIGluZGV4IGFuZCBzb3J0LXZhbHVlXG5cdFx0bWFwcGVkID0gdmFsdWUubWFwKGZ1bmN0aW9uKGl0ZW0sIGkpIHtcblx0XHRcdGl0ZW0gPSBkaXJlY3RTb3J0ID8gaXRlbSA6IGdldFBhdGhPYmplY3QoaXRlbSwgc29ydCk7XG5cdFx0XHRyZXR1cm4ge2k6IGksIHY6IFwiXCIgKyBpdGVtID09PSBpdGVtID8gaXRlbS50b0xvd2VyQ2FzZSgpIDogaXRlbX07XG5cdFx0fSk7XG5cdFx0Ly8gU29ydCBtYXBwZWQgYXJyYXlcblx0XHRtYXBwZWQuc29ydChmdW5jdGlvbihhLCBiKSB7XG5cdFx0XHRyZXR1cm4gYS52ID4gYi52ID8gcmV2ZXJzZSA6IGEudiA8IGIudiA/IC1yZXZlcnNlIDogMDtcblx0XHR9KTtcblx0XHQvLyBNYXAgdG8gbmV3IGFycmF5IHdpdGggcmVzdWx0aW5nIG9yZGVyXG5cdFx0dmFsdWUgPSBtYXBwZWQubWFwKGZ1bmN0aW9uKGl0ZW0pe1xuXHRcdFx0cmV0dXJuIHZhbHVlW2l0ZW0uaV07XG5cdFx0fSk7XG5cdH0gZWxzZSBpZiAoKHNvcnQgfHwgcmV2ZXJzZSA8IDApICYmICF0YWcuZGF0YU1hcCkge1xuXHRcdHZhbHVlID0gdmFsdWUuc2xpY2UoKTsgLy8gQ2xvbmUgYXJyYXkgZmlyc3QgaWYgbm90IGFscmVhZHkgYSBuZXcgYXJyYXlcblx0fVxuXHRpZiAoJGlzRnVuY3Rpb24oc29ydCkpIHtcblx0XHR2YWx1ZSA9IHZhbHVlLnNvcnQoc29ydCk7XG5cdH1cblx0aWYgKHJldmVyc2UgPCAwICYmICFzb3J0KSB7IC8vIFJldmVyc2UgcmVzdWx0IGlmIG5vdCBhbHJlYWR5IHJldmVyc2VkIGluIHNvcnRcblx0XHR2YWx1ZSA9IHZhbHVlLnJldmVyc2UoKTtcblx0fVxuXG5cdGlmICh2YWx1ZS5maWx0ZXIgJiYgZmlsdGVyKSB7IC8vIElFOCBkb2VzIG5vdCBzdXBwb3J0IGZpbHRlclxuXHRcdHZhbHVlID0gdmFsdWUuZmlsdGVyKGZpbHRlciwgdGFnQ3R4KTtcblx0XHRpZiAodGFnQ3R4LnRhZy5vbkZpbHRlcikge1xuXHRcdFx0dGFnQ3R4LnRhZy5vbkZpbHRlcih0YWdDdHgpO1xuXHRcdH1cblx0fVxuXG5cdGlmIChwcm9wUGFyYW1zLnNvcnRlZCkge1xuXHRcdG1hcHBlZCA9IChzb3J0IHx8IHJldmVyc2UgPCAwKSA/IHZhbHVlIDogdmFsdWUuc2xpY2UoKTtcblx0XHRpZiAodGFnLnNvcnRlZCkge1xuXHRcdFx0JC5vYnNlcnZhYmxlKHRhZy5zb3J0ZWQpLnJlZnJlc2gobWFwcGVkKTsgLy8gTm90ZSB0aGF0IHRoaXMgbWlnaHQgY2F1c2UgdGhlIHN0YXJ0IGFuZCBlbmQgcHJvcHMgdG8gYmUgbW9kaWZpZWQgLSBlLmcuIGJ5IHBhZ2VyIHRhZyBjb250cm9sXG5cdFx0fSBlbHNlIHtcblx0XHRcdHRhZ0N0eC5tYXAuc29ydGVkID0gbWFwcGVkO1xuXHRcdH1cblx0fVxuXG5cdHN0YXJ0ID0gcHJvcHMuc3RhcnQ7IC8vIEdldCBjdXJyZW50IHZhbHVlIC0gYWZ0ZXIgcG9zc2libGUgIGNoYW5nZXMgdHJpZ2dlcmVkIGJ5IHRhZy5zb3J0ZWQgcmVmcmVzaCgpIGFib3ZlXG5cdGVuZCA9IHByb3BzLmVuZDtcblx0aWYgKHByb3BQYXJhbXMuc3RhcnQgJiYgc3RhcnQgPT09IHVuZGVmaW5lZCB8fCBwcm9wUGFyYW1zLmVuZCAmJiBlbmQgPT09IHVuZGVmaW5lZCkge1xuXHRcdHN0YXJ0ID0gZW5kID0gMDtcblx0fVxuXHRpZiAoIWlzTmFOKHN0YXJ0KSB8fCAhaXNOYU4oZW5kKSkgeyAvLyBzdGFydCBvciBlbmQgc3BlY2lmaWVkLCBidXQgbm90IHRoZSBhdXRvLWNyZWF0ZSBOdW1iZXIgYXJyYXkgc2NlbmFyaW8gb2Yge3tmb3Igc3RhcnQ9eHh4IGVuZD15eXl9fVxuXHRcdHN0YXJ0ID0gK3N0YXJ0IHx8IDA7XG5cdFx0ZW5kID0gZW5kID09PSB1bmRlZmluZWQgfHwgZW5kID4gdmFsdWUubGVuZ3RoID8gdmFsdWUubGVuZ3RoIDogK2VuZDtcbi8vXHRcdGVuZCA9IGVuZCA9PT0gdW5kZWZpbmVkID8gdmFsdWUubGVuZ3RoIDogK2VuZDtcblx0XHR2YWx1ZSA9IHZhbHVlLnNsaWNlKHN0YXJ0LCBlbmQpO1xuXHR9XG5cdGlmIChzdGVwID4gMSkge1xuXHRcdHN0YXJ0ID0gMDtcblx0XHRlbmQgPSB2YWx1ZS5sZW5ndGg7XG5cdFx0bWFwcGVkID0gW107XG5cdFx0Zm9yICg7IHN0YXJ0PGVuZDsgc3RhcnQrPXN0ZXApIHtcblx0XHRcdG1hcHBlZC5wdXNoKHZhbHVlW3N0YXJ0XSk7XG5cdFx0fVxuXHRcdHZhbHVlID0gbWFwcGVkO1xuXHR9XG5cdGlmIChwcm9wUGFyYW1zLnBhZ2VkICYmIHRhZy5wYWdlZCkge1xuXHRcdCRvYnNlcnZhYmxlKHRhZy5wYWdlZCkucmVmcmVzaCh2YWx1ZSk7XG5cdH1cblxuXHRyZXR1cm4gdmFsdWU7XG59XG5cbmZ1bmN0aW9uICRmblJlbmRlcihkYXRhLCBjb250ZXh0LCBub0l0ZXJhdGlvbikge1xuXHR2YXIgdG1wbEVsZW0gPSB0aGlzLmpxdWVyeSAmJiAodGhpc1swXSB8fCBlcnJvcignVW5rbm93biB0ZW1wbGF0ZScpKSwgLy8gVGFyZ2V0ZWQgZWxlbWVudCBub3QgZm91bmQgZm9yIGpRdWVyeSB0ZW1wbGF0ZSBzZWxlY3RvciBzdWNoIGFzIFwiI215VG1wbFwiXG5cdFx0dG1wbCA9IHRtcGxFbGVtLmdldEF0dHJpYnV0ZSh0bXBsQXR0cik7XG5cblx0cmV0dXJuIHJlbmRlckNvbnRlbnQuY2FsbCh0bXBsICYmICQuZGF0YSh0bXBsRWxlbSlbanN2VG1wbF0gfHwgJHRlbXBsYXRlcyh0bXBsRWxlbSksXG5cdFx0ZGF0YSwgY29udGV4dCwgbm9JdGVyYXRpb24pO1xufVxuXG4vLz09PT09PT09PT09PT09PT09PT09PT09PT09IFJlZ2lzdGVyIGNvbnZlcnRlcnMgPT09PT09PT09PT09PT09PT09PT09PT09PT1cblxuZnVuY3Rpb24gZ2V0Q2hhckVudGl0eShjaCkge1xuXHQvLyBHZXQgY2hhcmFjdGVyIGVudGl0eSBmb3IgSFRNTCwgQXR0cmlidXRlIGFuZCBvcHRpb25hbCBkYXRhIGVuY29kaW5nXG5cdHJldHVybiBjaGFyRW50aXRpZXNbY2hdIHx8IChjaGFyRW50aXRpZXNbY2hdID0gXCImI1wiICsgY2guY2hhckNvZGVBdCgwKSArIFwiO1wiKTtcbn1cblxuZnVuY3Rpb24gZ2V0Q2hhckZyb21FbnRpdHkobWF0Y2gsIHRva2VuKSB7XG5cdC8vIEdldCBjaGFyYWN0ZXIgZnJvbSBIVE1MIGVudGl0eSwgZm9yIG9wdGlvbmFsIGRhdGEgdW5lbmNvZGluZ1xuXHRyZXR1cm4gY2hhcnNGcm9tRW50aXRpZXNbdG9rZW5dIHx8IFwiXCI7XG59XG5cbmZ1bmN0aW9uIGh0bWxFbmNvZGUodGV4dCkge1xuXHQvLyBIVE1MIGVuY29kZTogUmVwbGFjZSA8ID4gJiAnIFwiIGAgZXRjLiBieSBjb3JyZXNwb25kaW5nIGVudGl0aWVzLlxuXHRyZXR1cm4gdGV4dCAhPSB1bmRlZmluZWQgPyBySXNIdG1sLnRlc3QodGV4dCkgJiYgKFwiXCIgKyB0ZXh0KS5yZXBsYWNlKHJIdG1sRW5jb2RlLCBnZXRDaGFyRW50aXR5KSB8fCB0ZXh0IDogXCJcIjtcbn1cblxuZnVuY3Rpb24gZGF0YUVuY29kZSh0ZXh0KSB7XG5cdC8vIEVuY29kZSBqdXN0IDwgPiBhbmQgJiAtIGludGVuZGVkIGZvciAnc2FmZSBkYXRhJyBhbG9uZyB3aXRoIHt7On19IHJhdGhlciB0aGFuIHt7Pn19XG4gIHJldHVybiBcIlwiICsgdGV4dCA9PT0gdGV4dCA/IHRleHQucmVwbGFjZShyRGF0YUVuY29kZSwgZ2V0Q2hhckVudGl0eSkgOiB0ZXh0O1xufVxuXG5mdW5jdGlvbiBkYXRhVW5lbmNvZGUodGV4dCkge1xuICAvLyBVbmVuY29kZSBqdXN0IDwgPiBhbmQgJiAtIGludGVuZGVkIGZvciAnc2FmZSBkYXRhJyBhbG9uZyB3aXRoIHt7On19IHJhdGhlciB0aGFuIHt7Pn19XG4gIHJldHVybiBcIlwiICsgdGV4dCA9PT0gdGV4dCA/IHRleHQucmVwbGFjZShyRGF0YVVuZW5jb2RlLCBnZXRDaGFyRnJvbUVudGl0eSkgOiB0ZXh0O1xufVxuXG4vLz09PT09PT09PT09PT09PT09PT09PT09PT09IEluaXRpYWxpemUgPT09PT09PT09PT09PT09PT09PT09PT09PT1cblxuJHN1YiA9ICR2aWV3cy5zdWI7XG4kdmlld3NTZXR0aW5ncyA9ICR2aWV3cy5zZXR0aW5ncztcblxuaWYgKCEoanNyIHx8ICQgJiYgJC5yZW5kZXIpKSB7XG5cdC8vIEpzUmVuZGVyIG5vdCBhbHJlYWR5IGxvYWRlZCwgb3IgbG9hZGVkIHdpdGhvdXQgalF1ZXJ5LCBhbmQgd2UgYXJlIG5vdyBtb3ZpbmcgZnJvbSBqc3JlbmRlciBuYW1lc3BhY2UgdG8galF1ZXJ5IG5hbWVwYWNlXG5cdGZvciAoanN2U3RvcmVOYW1lIGluIGpzdlN0b3Jlcykge1xuXHRcdHJlZ2lzdGVyU3RvcmUoanN2U3RvcmVOYW1lLCBqc3ZTdG9yZXNbanN2U3RvcmVOYW1lXSk7XG5cdH1cblxuXHQkY29udmVydGVycyA9ICR2aWV3cy5jb252ZXJ0ZXJzO1xuXHQkaGVscGVycyA9ICR2aWV3cy5oZWxwZXJzO1xuXHQkdGFncyA9ICR2aWV3cy50YWdzO1xuXG5cdCRzdWIuX3RnLnByb3RvdHlwZSA9IHtcblx0XHRiYXNlQXBwbHk6IGJhc2VBcHBseSxcblx0XHRjdnRBcmdzOiBjb252ZXJ0QXJncyxcblx0XHRibmRBcmdzOiBjb252ZXJ0Qm91bmRBcmdzLFxuXHRcdGN0eFBybTogY29udGV4dFBhcmFtZXRlclxuXHR9O1xuXG5cdHRvcFZpZXcgPSAkc3ViLnRvcFZpZXcgPSBuZXcgVmlldygpO1xuXG5cdC8vQlJPV1NFUi1TUEVDSUZJQyBDT0RFXG5cdGlmICgkKSB7XG5cblx0XHQvLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy9cblx0XHQvLyBqUXVlcnkgKD0gJCkgaXMgbG9hZGVkXG5cblx0XHQkLmZuLnJlbmRlciA9ICRmblJlbmRlcjtcblx0XHQkZXhwYW5kbyA9ICQuZXhwYW5kbztcblx0XHRpZiAoJC5vYnNlcnZhYmxlKSB7XG5cdFx0XHRpZiAodmVyc2lvbk51bWJlciAhPT0gKHZlcnNpb25OdW1iZXIgPSAkLnZpZXdzLmpzdmlld3MpKSB7XG5cdFx0XHRcdC8vIERpZmZlcmVudCB2ZXJzaW9uIG9mIGpzUmVuZGVyIHdhcyBsb2FkZWRcblx0XHRcdFx0dGhyb3cgXCJKc09ic2VydmFibGUgcmVxdWlyZXMgSnNSZW5kZXIgXCIgKyB2ZXJzaW9uTnVtYmVyO1xuXHRcdFx0fVxuXHRcdFx0JGV4dGVuZCgkc3ViLCAkLnZpZXdzLnN1Yik7IC8vIGpxdWVyeS5vYnNlcnZhYmxlLmpzIHdhcyBsb2FkZWQgYmVmb3JlIGpzcmVuZGVyLmpzXG5cdFx0XHQkdmlld3MubWFwID0gJC52aWV3cy5tYXA7XG5cdFx0fVxuXG5cdH0gZWxzZSB7XG5cdFx0Ly8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vXG5cdFx0Ly8galF1ZXJ5IGlzIG5vdCBsb2FkZWQuXG5cblx0XHQkID0ge307XG5cblx0XHRpZiAoc2V0R2xvYmFscykge1xuXHRcdFx0Z2xvYmFsLmpzcmVuZGVyID0gJDsgLy8gV2UgYXJlIGxvYWRpbmcganNyZW5kZXIuanMgZnJvbSBhIHNjcmlwdCBlbGVtZW50LCBub3QgQU1EIG9yIENvbW1vbkpTLCBzbyBzZXQgZ2xvYmFsXG5cdFx0fVxuXG5cdFx0Ly8gRXJyb3Igd2FybmluZyBpZiBqc3JlbmRlci5qcyBpcyB1c2VkIGFzIHRlbXBsYXRlIGVuZ2luZSBvbiBOb2RlLmpzIChlLmcuIEV4cHJlc3Mgb3IgSGFwaS4uLilcblx0XHQvLyBVc2UganNyZW5kZXItbm9kZS5qcyBpbnN0ZWFkLi4uXG5cdFx0JC5yZW5kZXJGaWxlID0gJC5fX2V4cHJlc3MgPSAkLmNvbXBpbGUgPSBmdW5jdGlvbigpIHsgdGhyb3cgXCJOb2RlLmpzOiB1c2UgbnBtIGpzcmVuZGVyLCBvciBqc3JlbmRlci1ub2RlLmpzXCI7IH07XG5cblx0XHQvL0VORCBCUk9XU0VSLVNQRUNJRklDIENPREVcblx0XHQkLmlzRnVuY3Rpb24gPSBmdW5jdGlvbihvYikge1xuXHRcdFx0cmV0dXJuIHR5cGVvZiBvYiA9PT0gXCJmdW5jdGlvblwiO1xuXHRcdH07XG5cblx0XHQkLmlzQXJyYXkgPSBBcnJheS5pc0FycmF5IHx8IGZ1bmN0aW9uKG9iaikge1xuXHRcdFx0cmV0dXJuICh7fS50b1N0cmluZykuY2FsbChvYmopID09PSBcIltvYmplY3QgQXJyYXldXCI7XG5cdFx0fTtcblxuXHRcdCRzdWIuX2pxID0gZnVuY3Rpb24oanEpIHsgLy8gcHJpdmF0ZSBtZXRob2QgdG8gbW92ZSBmcm9tIEpzUmVuZGVyIEFQSXMgZnJvbSBqc3JlbmRlciBuYW1lc3BhY2UgdG8galF1ZXJ5IG5hbWVzcGFjZVxuXHRcdFx0aWYgKGpxICE9PSAkKSB7XG5cdFx0XHRcdCRleHRlbmQoanEsICQpOyAvLyBtYXAgb3ZlciBmcm9tIGpzcmVuZGVyIG5hbWVzcGFjZSB0byBqUXVlcnkgbmFtZXNwYWNlXG5cdFx0XHRcdCQgPSBqcTtcblx0XHRcdFx0JC5mbi5yZW5kZXIgPSAkZm5SZW5kZXI7XG5cdFx0XHRcdGRlbGV0ZSAkLmpzcmVuZGVyO1xuXHRcdFx0XHQkZXhwYW5kbyA9ICQuZXhwYW5kbztcblx0XHRcdH1cblx0XHR9O1xuXG5cdFx0JC5qc3JlbmRlciA9IHZlcnNpb25OdW1iZXI7XG5cdH1cblx0JHN1YlNldHRpbmdzID0gJHN1Yi5zZXR0aW5ncztcblx0JHN1YlNldHRpbmdzLmFsbG93Q29kZSA9IGZhbHNlO1xuXHQkaXNGdW5jdGlvbiA9ICQuaXNGdW5jdGlvbjtcblx0JC5yZW5kZXIgPSAkcmVuZGVyO1xuXHQkLnZpZXdzID0gJHZpZXdzO1xuXHQkLnRlbXBsYXRlcyA9ICR0ZW1wbGF0ZXMgPSAkdmlld3MudGVtcGxhdGVzO1xuXG5cdGZvciAoc2V0dGluZyBpbiAkc3ViU2V0dGluZ3MpIHtcblx0XHRhZGRTZXR0aW5nKHNldHRpbmcpO1xuXHR9XG5cblx0KCR2aWV3c1NldHRpbmdzLmRlYnVnTW9kZSA9IGZ1bmN0aW9uKGRlYnVnTW9kZSkge1xuXHRcdHJldHVybiBkZWJ1Z01vZGUgPT09IHVuZGVmaW5lZFxuXHRcdFx0PyAkc3ViU2V0dGluZ3MuZGVidWdNb2RlXG5cdFx0XHQ6IChcblx0XHRcdFx0JHN1YlNldHRpbmdzLmRlYnVnTW9kZSA9IGRlYnVnTW9kZSxcblx0XHRcdFx0JHN1YlNldHRpbmdzLm9uRXJyb3IgPSBkZWJ1Z01vZGUgKyBcIlwiID09PSBkZWJ1Z01vZGVcblx0XHRcdFx0XHQ/IGZ1bmN0aW9uKCkgeyByZXR1cm4gZGVidWdNb2RlOyB9XG5cdFx0XHRcdFx0OiAkaXNGdW5jdGlvbihkZWJ1Z01vZGUpXG5cdFx0XHRcdFx0XHQ/IGRlYnVnTW9kZVxuXHRcdFx0XHRcdFx0OiB1bmRlZmluZWQsXG5cdFx0XHRcdCR2aWV3c1NldHRpbmdzKTtcblx0fSkoZmFsc2UpOyAvLyBqc2hpbnQgaWdub3JlOmxpbmVcblxuXHQkc3ViU2V0dGluZ3NBZHZhbmNlZCA9ICRzdWJTZXR0aW5ncy5hZHZhbmNlZCA9IHtcblx0XHR1c2VWaWV3czogZmFsc2UsXG5cdFx0X2pzdjogZmFsc2UgLy8gRm9yIGdsb2JhbCBhY2Nlc3MgdG8gSnNWaWV3cyBzdG9yZVxuXHR9O1xuXG5cdC8vPT09PT09PT09PT09PT09PT09PT09PT09PT0gUmVnaXN0ZXIgdGFncyA9PT09PT09PT09PT09PT09PT09PT09PT09PVxuXG5cdCR0YWdzKHtcblx0XHRcImlmXCI6IHtcblx0XHRcdHJlbmRlcjogZnVuY3Rpb24odmFsKSB7XG5cdFx0XHRcdC8vIFRoaXMgZnVuY3Rpb24gaXMgY2FsbGVkIG9uY2UgZm9yIHt7aWZ9fSBhbmQgb25jZSBmb3IgZWFjaCB7e2Vsc2V9fS5cblx0XHRcdFx0Ly8gV2Ugd2lsbCB1c2UgdGhlIHRhZy5yZW5kZXJpbmcgb2JqZWN0IGZvciBjYXJyeWluZyByZW5kZXJpbmcgc3RhdGUgYWNyb3NzIHRoZSBjYWxscy5cblx0XHRcdFx0Ly8gSWYgbm90IGRvbmUgKGEgcHJldmlvdXMgYmxvY2sgaGFzIG5vdCBiZWVuIHJlbmRlcmVkKSwgbG9vayBhdCBleHByZXNzaW9uIGZvciB0aGlzIGJsb2NrIGFuZCByZW5kZXIgdGhlIGJsb2NrIGlmIGV4cHJlc3Npb24gaXMgdHJ1dGh5XG5cdFx0XHRcdC8vIE90aGVyd2lzZSByZXR1cm4gXCJcIlxuXHRcdFx0XHR2YXIgc2VsZiA9IHRoaXMsXG5cdFx0XHRcdFx0dGFnQ3R4ID0gc2VsZi50YWdDdHgsXG5cdFx0XHRcdFx0cmV0ID0gKHNlbGYucmVuZGVyaW5nLmRvbmUgfHwgIXZhbCAmJiAodGFnQ3R4LmFyZ3MubGVuZ3RoIHx8ICF0YWdDdHguaW5kZXgpKVxuXHRcdFx0XHRcdFx0PyBcIlwiXG5cdFx0XHRcdFx0XHQ6IChzZWxmLnJlbmRlcmluZy5kb25lID0gdHJ1ZSxcblx0XHRcdFx0XHRcdFx0c2VsZi5zZWxlY3RlZCA9IHRhZ0N0eC5pbmRleCxcblx0XHRcdFx0XHRcdFx0dW5kZWZpbmVkKTsgLy8gVGVzdCBpcyBzYXRpc2ZpZWQsIHNvIHJlbmRlciBjb250ZW50IG9uIGN1cnJlbnQgY29udGV4dFxuXHRcdFx0XHRyZXR1cm4gcmV0O1xuXHRcdFx0fSxcblx0XHRcdGNvbnRlbnRDdHg6IHRydWUsIC8vIEluaGVyaXQgcGFyZW50IHZpZXcgZGF0YSBjb250ZXh0XG5cdFx0XHRmbG93OiB0cnVlXG5cdFx0fSxcblx0XHRcImZvclwiOiB7XG5cdFx0XHRzb3J0RGF0YU1hcDogZGF0YU1hcChnZXRUYXJnZXRTb3J0ZWQpLFxuXHRcdFx0aW5pdDogZnVuY3Rpb24odmFsLCBjbG9uZWQpIHtcblx0XHRcdFx0dmFyIGwsIHRhZ0N0eCwgcHJvcHMsIHNvcnQsXG5cdFx0XHRcdFx0c2VsZiA9IHRoaXMsXG5cdFx0XHRcdFx0dGFnQ3R4cyA9IHNlbGYudGFnQ3R4cztcblx0XHRcdFx0bCA9IHRhZ0N0eHMubGVuZ3RoO1xuXHRcdFx0XHR3aGlsZSAobC0tKSB7XG5cdFx0XHRcdFx0dGFnQ3R4ID0gdGFnQ3R4c1tsXTtcblx0XHRcdFx0XHRwcm9wcyA9IHRhZ0N0eC5wcm9wcztcblx0XHRcdFx0XHR0YWdDdHguYXJnRGVmYXVsdCA9IHByb3BzLmVuZCA9PT0gdW5kZWZpbmVkIHx8IHRhZ0N0eC5hcmdzLmxlbmd0aCA+IDA7IC8vIERlZmF1bHQgdG8gI2RhdGEgZXhjZXB0IGZvciBhdXRvLWNyZWF0ZSByYW5nZSBzY2VuYXJpbyB7e2ZvciBzdGFydD14eHggZW5kPXl5eSBzdGVwPXp6en19XG5cblx0XHRcdFx0XHRpZiAodGFnQ3R4LmFyZ0RlZmF1bHQgIT09IGZhbHNlICYmICRpc0FycmF5KHRhZ0N0eC5hcmdzWzBdKVxuXHRcdFx0XHRcdFx0JiYgKHByb3BzLnNvcnQgIT09IHVuZGVmaW5lZCB8fCB0YWdDdHgucGFyYW1zLnByb3BzLnN0YXJ0IHx8IHRhZ0N0eC5wYXJhbXMucHJvcHMuZW5kIHx8IHByb3BzLnN0ZXAgIT09IHVuZGVmaW5lZCB8fCBwcm9wcy5maWx0ZXIgfHwgcHJvcHMucmV2ZXJzZSkpIHtcblx0XHRcdFx0XHRcdHByb3BzLmRhdGFNYXAgPSBzZWxmLnNvcnREYXRhTWFwO1xuXHRcdFx0XHRcdH1cblx0XHRcdFx0fVxuXHRcdFx0fSxcblx0XHRcdHJlbmRlcjogZnVuY3Rpb24odmFsKSB7XG5cdFx0XHRcdC8vIFRoaXMgZnVuY3Rpb24gaXMgY2FsbGVkIG9uY2UgZm9yIHt7Zm9yfX0gYW5kIG9uY2UgZm9yIGVhY2gge3tlbHNlfX0uXG5cdFx0XHRcdC8vIFdlIHdpbGwgdXNlIHRoZSB0YWcucmVuZGVyaW5nIG9iamVjdCBmb3IgY2FycnlpbmcgcmVuZGVyaW5nIHN0YXRlIGFjcm9zcyB0aGUgY2FsbHMuXG5cdFx0XHRcdHZhciB2YWx1ZSwgZmlsdGVyLCBzcnRGaWVsZCwgaXNBcnJheSwgaSwgc29ydGVkLCBlbmQsIHN0ZXAsXG5cdFx0XHRcdFx0c2VsZiA9IHRoaXMsXG5cdFx0XHRcdFx0dGFnQ3R4ID0gc2VsZi50YWdDdHgsXG5cdFx0XHRcdFx0cmFuZ2UgPSB0YWdDdHguYXJnRGVmYXVsdCA9PT0gZmFsc2UsXG5cdFx0XHRcdFx0cHJvcHMgPSB0YWdDdHgucHJvcHMsXG5cdFx0XHRcdFx0aXRlcmF0ZSA9ICByYW5nZSB8fCB0YWdDdHguYXJncy5sZW5ndGgsIC8vIE5vdCBmaW5hbCBlbHNlIGFuZCBub3QgYXV0by1jcmVhdGUgcmFuZ2Vcblx0XHRcdFx0XHRyZXN1bHQgPSBcIlwiLFxuXHRcdFx0XHRcdGRvbmUgPSAwO1xuXG5cdFx0XHRcdGlmICghc2VsZi5yZW5kZXJpbmcuZG9uZSkge1xuXHRcdFx0XHRcdHZhbHVlID0gaXRlcmF0ZSA/IHZhbCA6IHRhZ0N0eC52aWV3LmRhdGE7IC8vIEZvciB0aGUgZmluYWwgZWxzZSwgZGVmYXVsdHMgdG8gY3VycmVudCBkYXRhIHdpdGhvdXQgaXRlcmF0aW9uLlxuXG5cdFx0XHRcdFx0aWYgKHJhbmdlKSB7XG5cdFx0XHRcdFx0XHRyYW5nZSA9IHByb3BzLnJldmVyc2UgPyBcInVuc2hpZnRcIiA6IFwicHVzaFwiO1xuXHRcdFx0XHRcdFx0ZW5kID0gK3Byb3BzLmVuZDtcblx0XHRcdFx0XHRcdHN0ZXAgPSArcHJvcHMuc3RlcCB8fCAxO1xuXHRcdFx0XHRcdFx0dmFsdWUgPSBbXTsgLy8gYXV0by1jcmVhdGUgaW50ZWdlciBhcnJheSBzY2VuYXJpbyBvZiB7e2ZvciBzdGFydD14eHggZW5kPXl5eX19XG5cdFx0XHRcdFx0XHRmb3IgKGkgPSArcHJvcHMuc3RhcnQgfHwgMDsgKGVuZCAtIGkpICogc3RlcCA+IDA7IGkgKz0gc3RlcCkge1xuXHRcdFx0XHRcdFx0XHR2YWx1ZVtyYW5nZV0oaSk7XG5cdFx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0fVxuXHRcdFx0XHRcdGlmICh2YWx1ZSAhPT0gdW5kZWZpbmVkKSB7XG5cdFx0XHRcdFx0XHRpc0FycmF5ID0gJGlzQXJyYXkodmFsdWUpO1xuXHRcdFx0XHRcdFx0cmVzdWx0ICs9IHRhZ0N0eC5yZW5kZXIodmFsdWUsICFpdGVyYXRlIHx8IHByb3BzLm5vSXRlcmF0aW9uKTtcblx0XHRcdFx0XHRcdC8vIEl0ZXJhdGVzIGlmIGRhdGEgaXMgYW4gYXJyYXksIGV4Y2VwdCBvbiBmaW5hbCBlbHNlIC0gb3IgaWYgbm9JdGVyYXRpb24gcHJvcGVydHlcblx0XHRcdFx0XHRcdC8vIHNldCB0byB0cnVlLiAoVXNlIHt7aW5jbHVkZX19IHRvIGNvbXBvc2UgdGVtcGxhdGVzIHdpdGhvdXQgYXJyYXkgaXRlcmF0aW9uKVxuXHRcdFx0XHRcdFx0ZG9uZSArPSBpc0FycmF5ID8gdmFsdWUubGVuZ3RoIDogMTtcblx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0aWYgKHNlbGYucmVuZGVyaW5nLmRvbmUgPSBkb25lKSB7XG5cdFx0XHRcdFx0XHRzZWxmLnNlbGVjdGVkID0gdGFnQ3R4LmluZGV4O1xuXHRcdFx0XHRcdH1cblx0XHRcdFx0XHQvLyBJZiBub3RoaW5nIHdhcyByZW5kZXJlZCB3ZSB3aWxsIGxvb2sgYXQgdGhlIG5leHQge3tlbHNlfX0uIE90aGVyd2lzZSwgd2UgYXJlIGRvbmUuXG5cdFx0XHRcdH1cblx0XHRcdFx0cmV0dXJuIHJlc3VsdDtcblx0XHRcdH0sXG5cdFx0XHRmbG93OiB0cnVlXG5cdFx0fSxcblx0XHRwcm9wczoge1xuXHRcdFx0YmFzZVRhZzogXCJmb3JcIixcblx0XHRcdGRhdGFNYXA6IGRhdGFNYXAoZ2V0VGFyZ2V0UHJvcHMpLFxuXHRcdFx0aW5pdDogbm9vcCwgLy8gRG9uJ3QgZXhlY3V0ZSB0aGUgYmFzZSBpbml0KCkgb2YgdGhlIFwiZm9yXCIgdGFnXG5cdFx0XHRmbG93OiB0cnVlXG5cdFx0fSxcblx0XHRpbmNsdWRlOiB7XG5cdFx0XHRmbG93OiB0cnVlXG5cdFx0fSxcblx0XHRcIipcIjoge1xuXHRcdFx0Ly8ge3sqIGNvZGUuLi4gfX0gLSBJZ25vcmVkIGlmIHRlbXBsYXRlLmFsbG93Q29kZSBhbmQgJC52aWV3cy5zZXR0aW5ncy5hbGxvd0NvZGUgYXJlIGZhbHNlLiBPdGhlcndpc2UgaW5jbHVkZSBjb2RlIGluIGNvbXBpbGVkIHRlbXBsYXRlXG5cdFx0XHRyZW5kZXI6IHJldFZhbCxcblx0XHRcdGZsb3c6IHRydWVcblx0XHR9LFxuXHRcdFwiOipcIjoge1xuXHRcdFx0Ly8ge3s6KiByZXR1cm5lZEV4cHJlc3Npb24gfX0gLSBJZ25vcmVkIGlmIHRlbXBsYXRlLmFsbG93Q29kZSBhbmQgJC52aWV3cy5zZXR0aW5ncy5hbGxvd0NvZGUgYXJlIGZhbHNlLiBPdGhlcndpc2UgaW5jbHVkZSBjb2RlIGluIGNvbXBpbGVkIHRlbXBsYXRlXG5cdFx0XHRyZW5kZXI6IHJldFZhbCxcblx0XHRcdGZsb3c6IHRydWVcblx0XHR9LFxuXHRcdGRiZzogJGhlbHBlcnMuZGJnID0gJGNvbnZlcnRlcnMuZGJnID0gZGJnQnJlYWsgLy8gUmVnaXN0ZXIge3tkYmcvfX0sIHt7ZGJnOi4uLn19IGFuZCB+ZGJnKCkgdG8gdGhyb3cgYW5kIGNhdGNoLCBhcyBicmVha3BvaW50cyBmb3IgZGVidWdnaW5nLlxuXHR9KTtcblxuXHQkY29udmVydGVycyh7XG5cdFx0aHRtbDogaHRtbEVuY29kZSxcblx0XHRhdHRyOiBodG1sRW5jb2RlLCAvLyBJbmNsdWRlcyA+IGVuY29kaW5nIHNpbmNlIHJDb252ZXJ0TWFya2VycyBpbiBKc1ZpZXdzIGRvZXMgbm90IHNraXAgPiBjaGFyYWN0ZXJzIGluIGF0dHJpYnV0ZSBzdHJpbmdzXG5cdFx0ZW5jb2RlOiBkYXRhRW5jb2RlLFxuXHRcdHVuZW5jb2RlOiBkYXRhVW5lbmNvZGUsIC8vIEluY2x1ZGVzID4gZW5jb2Rpbmcgc2luY2UgckNvbnZlcnRNYXJrZXJzIGluIEpzVmlld3MgZG9lcyBub3Qgc2tpcCA+IGNoYXJhY3RlcnMgaW4gYXR0cmlidXRlIHN0cmluZ3Ncblx0XHR1cmw6IGZ1bmN0aW9uKHRleHQpIHtcblx0XHRcdC8vIFVSTCBlbmNvZGluZyBoZWxwZXIuXG5cdFx0XHRyZXR1cm4gdGV4dCAhPSB1bmRlZmluZWQgPyBlbmNvZGVVUkkoXCJcIiArIHRleHQpIDogdGV4dCA9PT0gbnVsbCA/IHRleHQgOiBcIlwiOyAvLyBudWxsIHJldHVybnMgbnVsbCwgZS5nLiB0byByZW1vdmUgYXR0cmlidXRlLiB1bmRlZmluZWQgcmV0dXJucyBcIlwiXG5cdFx0fVxuXHR9KTtcbn1cbi8vPT09PT09PT09PT09PT09PT09PT09PT09PT0gRGVmaW5lIGRlZmF1bHQgZGVsaW1pdGVycyA9PT09PT09PT09PT09PT09PT09PT09PT09PVxuJHN1YlNldHRpbmdzID0gJHN1Yi5zZXR0aW5ncztcbiRpc0FycmF5ID0gKCR8fGpzcikuaXNBcnJheTtcbiR2aWV3c1NldHRpbmdzLmRlbGltaXRlcnMoXCJ7e1wiLCBcIn19XCIsIFwiXlwiKTtcblxuaWYgKGpzclRvSnEpIHsgLy8gTW92aW5nIGZyb20ganNyZW5kZXIgbmFtZXNwYWNlIHRvIGpRdWVyeSBuYW1lcGFjZSAtIGNvcHkgb3ZlciB0aGUgc3RvcmVkIGl0ZW1zICh0ZW1wbGF0ZXMsIGNvbnZlcnRlcnMsIGhlbHBlcnMuLi4pXG5cdGpzci52aWV3cy5zdWIuX2pxKCQpO1xufVxucmV0dXJuICQgfHwganNyO1xufSwgd2luZG93KSk7XG4iLCIvKmdsb2JhbCBRVW5pdCwgdGVzdCwgZXF1YWwsIG9rKi9cbihmdW5jdGlvbih1bmRlZmluZWQpIHtcblwidXNlIHN0cmljdFwiO1xuXG5icm93c2VyaWZ5LmRvbmUub25lID0gdHJ1ZTtcblxuUVVuaXQubW9kdWxlKFwiQnJvd3NlcmlmeSAtIGNsaWVudCBjb2RlXCIpO1xuXG52YXIgaXNJRTggPSB3aW5kb3cuYXR0YWNoRXZlbnQgJiYgIXdpbmRvdy5hZGRFdmVudExpc3RlbmVyO1xuXG5pZiAoIWlzSUU4KSB7XG5cbnRlc3QoXCJObyBqUXVlcnkgZ2xvYmFsOiByZXF1aXJlKCdqc3JlbmRlcicpKClcIiwgZnVuY3Rpb24oKSB7XG5cdC8vIC4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4gSGlkZSBRVW5pdCBnbG9iYWwgalF1ZXJ5IGFuZCBhbnkgcHJldmlvdXMgZ2xvYmFsIGpzcmVuZGVyLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uXG5cdHZhciBqUXVlcnkgPSBnbG9iYWwualF1ZXJ5LCBqc3IgPSBnbG9iYWwuanNyZW5kZXI7XG5cdGdsb2JhbC5qUXVlcnkgPSBnbG9iYWwuanNyZW5kZXIgPSB1bmRlZmluZWQ7XG5cblx0Ly8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PSBBcnJhbmdlID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT1cblx0dmFyIGRhdGEgPSB7bmFtZTogXCJKb1wifTtcblxuXHQvLyAuLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLiBBY3QgLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLlxuXHR2YXIganNyZW5kZXIgPSByZXF1aXJlKCdqc3JlbmRlcicpKCk7IC8vIE5vdCBwYXNzaW5nIGluIGpRdWVyeSwgc28gcmV0dXJucyB0aGUganNyZW5kZXIgbmFtZXNwYWNlXG5cblx0Ly8gVXNlIHJlcXVpcmUgdG8gZ2V0IHNlcnZlciB0ZW1wbGF0ZSwgdGhhbmtzIHRvIEJyb3dzZXJpZnkgYnVuZGxlIHRoYXQgdXNlZCBqc3JlbmRlci90bXBsaWZ5IHRyYW5zZm9ybVxuXHR2YXIgdG1wbCA9IHJlcXVpcmUoJy4uL3RlbXBsYXRlcy9uYW1lLXRlbXBsYXRlLmh0bWwnKShqc3JlbmRlcik7IC8vIFByb3ZpZGUganNyZW5kZXJcblxuXHR2YXIgcmVzdWx0ID0gdG1wbChkYXRhKTtcblxuXHRyZXN1bHQgKz0gXCIgXCIgKyAoanNyZW5kZXIgIT09IGpRdWVyeSk7XG5cblx0Ly8gLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLiBBc3NlcnQgLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uXG5cdGVxdWFsKHJlc3VsdCwgXCJOYW1lOiBKbyAobmFtZS10ZW1wbGF0ZS5odG1sKSB0cnVlXCIsIFwicmVzdWx0OiBObyBqUXVlcnkgZ2xvYmFsOiByZXF1aXJlKCdqc3JlbmRlcicpKClcIik7XG5cblx0Ly8gLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLiBSZXNldCAuLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi5cblx0Z2xvYmFsLmpRdWVyeSA9IGpRdWVyeTsgLy8gUmVwbGFjZSBRVW5pdCBnbG9iYWwgalF1ZXJ5XG5cdGdsb2JhbC5qc3JlbmRlciA9IGpzcjsgLy8gUmVwbGFjZSBhbnkgcHJldmlvdXMgZ2xvYmFsIGpzcmVuZGVyXG59KTtcblxufVxufSkoKTtcbiIsInZhciB0bXBsUmVmcyA9IFtdLFxuICBta3VwID0gJ05hbWU6IHt7Om5hbWV9fSAobmFtZS10ZW1wbGF0ZS5odG1sKScsXG4gICQgPSBnbG9iYWwuanNyZW5kZXIgfHwgZ2xvYmFsLmpRdWVyeTtcblxubW9kdWxlLmV4cG9ydHMgPSAkID8gJC50ZW1wbGF0ZXMoXCIuL3Rlc3QvdGVtcGxhdGVzL25hbWUtdGVtcGxhdGUuaHRtbFwiLCBta3VwKSA6XG4gIGZ1bmN0aW9uKCQpIHtcbiAgICBpZiAoISQgfHwgISQudmlld3MpIHt0aHJvdyBcIlJlcXVpcmVzIGpzcmVuZGVyL2pRdWVyeVwiO31cbiAgICB3aGlsZSAodG1wbFJlZnMubGVuZ3RoKSB7XG4gICAgICB0bXBsUmVmcy5wb3AoKSgkKTsgLy8gY29tcGlsZSBuZXN0ZWQgdGVtcGxhdGVcbiAgICB9XG5cbiAgICByZXR1cm4gJC50ZW1wbGF0ZXMoXCIuL3Rlc3QvdGVtcGxhdGVzL25hbWUtdGVtcGxhdGUuaHRtbFwiLCBta3VwKVxuICB9OyJdfQ== diff --git a/test/browserify/bundles/10-errors-bundle.js b/test/browserify/bundles/10-errors-bundle.js index 524df443..0fce9f1c 100644 --- a/test/browserify/bundles/10-errors-bundle.js +++ b/test/browserify/bundles/10-errors-bundle.js @@ -9815,14 +9815,14 @@ return jQuery; })); },{}],2:[function(require,module,exports){ -/*! JsRender v0.9.90 (Beta): http://jsviews.com/#jsrender */ +/*! JsRender v0.9.91 (Beta): http://jsviews.com/#jsrender */ /*! **VERSION FOR WEB** (For NODE.JS see http://jsviews.com/download/jsrender-node.js) */ /* * Best-of-breed templating in browser or on Node.js. * Does not require jQuery, or HTML DOM * Integrates with JsViews (http://jsviews.com/#jsviews) * - * Copyright 2017, Boris Moore + * Copyright 2018, Boris Moore * Released under the MIT License. */ @@ -9861,7 +9861,7 @@ var setGlobals = $ === false; // Only set globals if script block in browser (no $ = $ && $.fn ? $ : global.jQuery; // $ is jQuery passed in by CommonJS loader (Browserify), or global jQuery. -var versionNumber = "v0.9.90", +var versionNumber = "v0.9.91", jsvStoreName, rTag, rTmplString, topView, $views, $expando, _ocp = "_ocp", // Observable contextual parameter @@ -9871,8 +9871,8 @@ var versionNumber = "v0.9.90", rPath = /^(!*?)(?:null|true|false|\d[\d.]*|([\w$]+|\.|~([\w$]+)|#(view|([\w$]+))?)([\w$.^]*?)(?:[.[^]([\w$]+)\]?)?)$/g, // not object helper view viewProperty pathTokens leafToken - rParams = /(\()(?=\s*\()|(?:([([])\s*)?(?:(\^?)(!*?[#~]?[\w$.^]+)?\s*((\+\+|--)|\+|-|&&|\|\||===|!==|==|!=|<=|>=|[<>%*:?\/]|(=))\s*|(!*?[#~]?[\w$.^]+)([([])?)|(,\s*)|(\(?)\\?(?:(')|("))|(?:\s*(([)\]])(?=\s*[.^]|\s*$|[^([])|[)\]])([([]?))|(\s+)/g, - // lftPrn0 lftPrn bound path operator err eq path2 prn comma lftPrn2 apos quot rtPrn rtPrnDot prn2 space + rParams = /(\()(?=\s*\()|(?:([([])\s*)?(?:(\^?)(~?[\w$.^]+)?\s*((\+\+|--)|\+|-|~(?![\w$_])|&&|\|\||===|!==|==|!=|<=|>=|[<>%*:?\/]|(=))\s*|(!*?(@)?[#~]?[\w$.^]+)([([])?)|(,\s*)|(\(?)\\?(?:(')|("))|(?:\s*(([)\]])(?=[.^]|\s*$|[^([])|[)\]])([([]?))|(\s+)/g, + // lftPrn0 lftPrn bound path operator err eq path2 late prn comma lftPrn2 apos quot rtPrn rtPrnDot prn2 space // (left paren? followed by (path? followed by operator) or (path followed by left paren?)) or comma or apos or quot or right paren or space isRenderCall, @@ -9887,6 +9887,9 @@ var versionNumber = "v0.9.90", rHasHandlers = /^on[A-Z]|^convert(Back)?$/, rWrappedInViewMarker = /^\#\d+_`[\s\S]*\/\d+_`$/, rHtmlEncode = rAttrEncode, + rDataEncode = /[&<>]/g, + rDataUnencode = /&(amp|gt|lt);/g, + rBracketQuote = /\[['"]?|['"]?\]/g, viewId = 0, charEntities = { "&": "&", @@ -9898,6 +9901,11 @@ var versionNumber = "v0.9.90", "`": "`", "=": "=" }, + charsFromEntities = { + amp: "&", + gt: ">", + lt: "<" + }, HTML = "html", OBJECT = "object", tmplAttr = "data-jsv-tmpl", @@ -9948,7 +9956,7 @@ var versionNumber = "v0.9.90", allowCode: false }, advSet: noop, // Update advanced settings - _ths: tagHandlersFromProps, + _thp: tagHandlersFromProps, _gm: getMethod, _tg: function() {}, // Constructor for tagDef _cnvt: convertVal, @@ -10003,7 +10011,7 @@ function getMethod(baseMethod, method) { : getDerivedMethod(noop, baseMethod), // baseMethod is not derived so make its base method be the noop method method ); - method._d = 1; // Add flag that this is a derived method + method._d = (baseMethod && baseMethod._d || 0) + 1; // Add flag for derived method (incremented for derived of derived...) } return method; } @@ -10165,66 +10173,126 @@ getIndex.depends = "index"; // View.hlp //========== -function contextParameter(key, value, isContextCb) { +function getPathObject(ob, path, ltOb, fn) { // Iterate through path to late paths: @a.b.c paths + // Return "" (or noop if leaf is a function @a.b.c(...) ) if intermediate object not yet available + var prevOb, tokens, l, + i = 0; + if (ltOb === 1) { + fn = 1; + ltOb = undefined; + } + // Paths like ^a^b^c or ~^a^b^c will not throw if an object in path is undefined. + if (path) { + tokens = path.split("."); + l = tokens.length; + + for (; ob && i < l; i++) { + prevOb = ob; + ob = tokens[i] ? ob[tokens[i]] : ob; + } + } + if (ltOb) { + ltOb.lt = ltOb.lt || i 1, store = storeView.ctx; - if (key in store || key in (store = $helpers)) { - res = store && store[key]; - if (key === "tag" || key === "root" || key === "parentTags" || storeView._.it === key ) { - return res; + if (key) { + if (!storeView._) { // tagCtx.ctxPrm() call + tagElse = storeView.index; + storeView = storeView.tag; } - } else { - store = undefined; - } - if (!res || !$isFunction(res) && storeView.linked || storeView.tagCtx) { // Data-linked view, or tag instance - if (!res || !res._cxp) { - // Not a contextual parameter - if (store !== $helpers) { + callView = storeView; + if (store && store.hasOwnProperty(key) || (store = $helpers).hasOwnProperty(key)) { + res = store[key]; + if (key === "tag" || key === "tagCtx" || key === "root" || key === "parentTags" || storeView._.it === key ) { + return res; + } + } else { + store = undefined; + } + if (storeView.tagCtx || storeView.linked) { // Data-linked view, or tag instance + if (!res || !res._cxp) { + // Not a contextual parameter // Set storeView to tag (if this is a tag.ctxPrm() call) or to root view ("data" view of linked template) - storeView = storeView.tagCtx - ? storeView // Is a tag, not a view - : (storeView = storeView.scope || storeView, !storeView.isTop && storeView.ctx.tag || storeView); + storeView = storeView.tagCtx || $isFunction(res) + ? storeView // Is a tag, not a view, or is a computed contextual parameter, so scope to the callView, no the 'scope view' + : (storeView = storeView.scope || storeView, + !storeView.isTop && storeView.ctx.tag // If this view is in a tag, set storeView to the tag + || storeView); + if (res !== undefined && storeView.tagCtx) { + // If storeView is a tag, but the contextual parameter has been set at at higher level (e.g. helpers)... + storeView = storeView.tagCtx.view.scope; // then move storeView to the outer level (scope of tag container view) + } store = storeView._ocps; - res = store && store[key] || res; + res = store && store.hasOwnProperty(key) && store[key] || res; + if (!(res && res._cxp) && (get || isUpdate)) { + // Create observable contextual parameter + (store || (storeView._ocps = storeView._ocps || {}))[key] + = res + = [{ + _ocp: res, // The observable contextual parameter value + _vw: callView, + _key: key + }]; + res._cxp = { + path: _ocp, + ind: 0, + updateValue: function(val, path) { + $.observable(res[0]).setProperty(_ocp, val); // Set the value (res[0]._ocp) + return this; + } + }; + } } - if (!(res && res._cxp) && (isContextCb || isUpdate)) { - res = $sub._crcp(key, res, storeView, store); // Create observable contextual parameter + if (obsCtxPrm = res && res._cxp) { + // If this helper resource is an observable contextual parameter + if (arguments.length > 2) { + deps = res[1] ? $sub._ceo(res[1].deps) : [_ocp]; // fn deps (with any exprObs cloned using $sub._ceo) + deps.unshift(res[0]); // view + deps._cxp = obsCtxPrm; + // In a context callback for a contextual param, we set get = true, to get ctxPrm [view, dependencies...] array - needed for observe call + return deps; + } + tagElse = obsCtxPrm.tagElse; + newRes = res[1] // linkFn for compiled expression + ? obsCtxPrm.tag && obsCtxPrm.tag.cvtArgs + ? obsCtxPrm.tag.cvtArgs(1, tagElse)[obsCtxPrm.ind] // = tag.bndArgs() - for tag contextual parameter + : res[1](res[0].data, res[0], $sub) // = fn(data, view, $sub) for compiled binding expression + : res[0]._ocp; // Observable contextual parameter (uninitialized, or initialized as static expression, so no path dependencies) + if (isUpdate) { + if (res && newRes !== value) { + $sub._ucp(key, value, storeView, obsCtxPrm); // Update observable contextual parameter + } + return storeView; + } + res = newRes; } } - if (obsCtxPrm = res && res._cxp) { - if (isUpdate) { - return $sub._ucp(key, value, storeView, obsCtxPrm); // Update observable contextual parameter - } - if (isContextCb) { // If this helper resource is an observable contextual parameter - // In a context callback for a contextual param, return the [view, dependencies...] array - needed for observe call - deps = res[1] ? $sub._ceo(res[1].deps) : [_ocp]; // fn deps (with any exprObs cloned using $sub._ceo) - deps.unshift(res[0]); // view - deps._cxp = obsCtxPrm; - return deps; - } - res = res[1] // linkFn for compiled expression - ? obsCtxPrm.tag && obsCtxPrm.tag.cvtArgs - ? obsCtxPrm.tag.cvtArgs(true, obsCtxPrm.tagElse)[obsCtxPrm.ind] // = tag.bndArgs() - for tag contextual parameter - : res[1](res[0].data, res[0], $sub) // = fn(data, view, $sub) for compiled binding expression - : res[0]._ocp; // Observable contextual parameter (uninitialized, or initialized as static expression, so no path dependencies) + if (res && $isFunction(res)) { + // If a helper is of type function we will wrap it, so if called with no this pointer it will be called with the + // view as 'this' context. If the helper ~foo() was in a data-link expression, the view will have a 'temporary' linkCtx property too. + // Note that helper functions on deeper paths will have specific this pointers, from the preceding path. + // For example, ~util.foo() will have the ~util object as 'this' pointer + wrapped = function() { + return res.apply((!this || this === global) ? callView : this, arguments); + }; + $extend(wrapped, res); // Attach same expandos (if any) to the wrapped function + wrapped._vw = callView; } + return wrapped || res; } - if (res && $isFunction(res)) { - // If a helper is of type function, and not already wrapped, we will wrap it, so if called with no this pointer it will be called with the - // view as 'this' context. If the helper ~foo() was in a data-link expression, the view will have a 'temporary' linkCtx property too. - // Note that helper functions on deeper paths will have specific this pointers, from the preceding path. - // For example, ~util.foo() will have the ~util object as 'this' pointer - wrapped = function() { - return res.apply((!this || this === global) ? storeView : this, arguments); - }; - $extend(wrapped, res); // Attach same expandos (if any) to the wrapped function - wrapped._vw = storeView; - } - return wrapped || res; } function getTemplate(tmpl) { @@ -10254,7 +10322,6 @@ function convertVal(converter, view, tagCtx, onError) { tagCtx = boundTag(view.data, view, $sub); } boundTag = boundTag._bd && boundTag; - value = tagCtx.args[0]; if (converter || boundTag) { tag = linkCtx && linkCtx.tag; tagCtx.view = view; @@ -10262,13 +10329,16 @@ function convertVal(converter, view, tagCtx, onError) { tag = $extend(new $sub._tg(), { _: { bnd: boundTag, - unlinked: true + unlinked: true, + lt: tagCtx.lt // If a late path @some.path has not returned @some object, mark tag as late }, inline: !linkCtx, tagName: ":", convert: converter, flow: true, - tagCtx: tagCtx + tagCtx: tagCtx, + tagCtxs: [tagCtx], + _is: "tag" }); argsLen = tagCtx.args.length; if (argsLen>1) { @@ -10287,8 +10357,10 @@ function convertVal(converter, view, tagCtx, onError) { tag._er = onError && value; tag.ctx = tagCtx.ctx || tag.ctx || {}; tagCtx.ctx = undefined; - value = tag.cvtArgs()[0]; // If there is a convertBack but no convert, converter will be "true" + tag._er = onError && value; + } else { + value = tagCtx.args[0]; } // Call onRender (used by JsViews if present, to add binding annotations around rendered content) @@ -10298,8 +10370,8 @@ function convertVal(converter, view, tagCtx, onError) { return value != undefined ? value : ""; } -function convertArgs(bound, tagElse) { // tag.cvtArgs() - var l, key, boundArgs, args, bindTo, tag, converter, +function convertArgs(bound, tagElse) { // tag.cvtArgs() or tag.cvtArgs(trueOrFalse, tagElse) + var l, key, boundArgs, args, bindFrom, tag, converter, tagCtx = this; if (tagCtx.tagName) { @@ -10309,7 +10381,7 @@ function convertArgs(bound, tagElse) { // tag.cvtArgs() tag = tagCtx.tag; } - bindTo = tag.bindTo; + bindFrom = tag.bindFrom; args = tagCtx.args; if ((converter = tag.convert) && "" + converter === converter) { @@ -10318,38 +10390,37 @@ function convertArgs(bound, tagElse) { // tag.cvtArgs() : (tagCtx.view.getRsc("converters", converter) || error("Unknown converter: '" + converter + "'")); } - if (bound && bound.length) { - args = bound; - } else { - if (converter && !bound) { // If there is a converter, use a copy of the tagCtx.args array for rendering, and replace the args[0] in - args = args.slice(); // the copied array with the converted value. But we do not modify the value of tag.tagCtx.args[0] (the original args array) + if (converter && !bound) { // If there is a converter, use a copy of the tagCtx.args array for rendering, and replace the args[0] in + args = args.slice(); // the copied array with the converted value. But we do not modify the value of tag.tagCtx.args[0] (the original args array) + } + if (bindFrom) { // Get the values of the boundArgs + boundArgs = []; + l = bindFrom.length; + while (l--) { + key = bindFrom[l]; + boundArgs.unshift(argOrProp(tagCtx, key)); } - if (bindTo) { // Get the values of the boundArgs - boundArgs = []; - l = bindTo.length; - while (l--) { - key = bindTo[l]; - boundArgs.unshift(argOrProp(tagCtx, key)); - } - if (bound) { - args = boundArgs; // Call to convertBoundArgs() - returns the boundArgs - } + if (bound) { + args = boundArgs; // Call to bndArgs() - returns the boundArgs } } if (converter) { - bindTo = bindTo || [0]; - l = bindTo.length; converter = converter.apply(tag, boundArgs || args); + if (converter === undefined) { + return args; // Returning undefined from a converter is equivalent to not having a converter. + } + bindFrom = bindFrom || [0]; + l = bindFrom.length; if (!$isArray(converter) || converter.length !== l) { converter = [converter]; - bindTo = [0]; + bindFrom = [0]; l = 1; } - if (bound) { // Call to bndArgs convertBoundArgs() - so apply converter to all boundArgs + if (bound) { // Call to bndArgs() - so apply converter to all boundArgs args = converter; // The array of values returned from the converter } else { // Call to cvtArgs() while (l--) { - key = bindTo[l]; + key = bindFrom[l]; if (+key === key) { args[key] = converter[l]; } @@ -10365,7 +10436,7 @@ function argOrProp(context, key) { } function convertBoundArgs(tagElse) { // tag.bndArgs() - return this.cvtArgs(true, tagElse); + return this.cvtArgs(1, tagElse); } //============= @@ -10386,20 +10457,26 @@ function getResource(resourceType, itemName) { } function renderTag(tagName, parentView, tmpl, tagCtxs, isUpdate, onError) { - function makeArray(type) { - var linkedElement; - if (linkedElement = tag[type]) { - tag[type] = linkedElement = $isArray(linkedElement) ? linkedElement: [linkedElement]; + function bindToOrBindFrom(type) { + var bindArray = tag[type]; - if (bindToLength !== linkedElement.length) { - error(type + " length not same as bindTo "); + if (bindArray !== undefined) { + bindArray = $isArray(bindArray) ? bindArray : [bindArray]; + m = bindArray.length; + while (m--) { + key = bindArray[m]; + if (!isNaN(parseInt(key))) { + bindArray[m] = parseInt(key); // Convert "0" to 0, etc. + } } } + + return bindArray || [0]; } parentView = parentView || topView; - var tag, tag_, tagDef, template, tags, attr, parentTag, l, m, n, itemRet, tagCtx, tagCtxCtx, ctxPrm, bindTo, - content, callInit, mapDef, thisMap, args, props, tagDataMap, contentCtx, key, bindToLength, + var tag, tagDef, template, tags, attr, parentTag, l, m, n, itemRet, tagCtx, tagCtxCtx, ctxPrm, bindTo, bindFrom, initVal, + content, callInit, mapDef, thisMap, args, bdArgs, props, tagDataMap, contentCtx, key, bindFromLength, bindToLength, linkedElement, defaultCtx, i = 0, ret = "", linkCtx = parentView.linkCtx || 0, @@ -10417,14 +10494,12 @@ function renderTag(tagName, parentView, tmpl, tagCtxs, isUpdate, onError) { tagDef = parentView.getRsc("tags", tagName) || error("Unknown tag: {{" + tagName + "}} "); template = tagDef.template; } - if (onError === undefined && boundTag) { - if (boundTag._lr = (tagDef.lateRender || boundTag._lr) && boundTag._lr !== "false") { - onError = ""; // If lateRender, set temporary onError, to skip initial rendering (and render just "") - } + if (onError === undefined && boundTag && (boundTag._lr = (tagDef.lateRender && boundTag._lr!== false || boundTag._lr))) { + onError = ""; // If lateRender, set temporary onError, to skip initial rendering (and render just "") } if (onError !== undefined) { ret += onError; - tagCtxs = onError = [{props: {}, args: [], params: {}}]; + tagCtxs = onError = [{props: {}, args: [], params: {props:{}}}]; } else if (boundTag) { tagCtxs = boundTag(parentView.data, parentView, $sub); } @@ -10440,9 +10515,12 @@ function renderTag(tagName, parentView, tmpl, tagCtxs, isUpdate, onError) { tagCtx.tmpl = tagCtx.content = parentTmpl.tmpls[content - 1]; // Set the tmpl property to the content of the block tag } tagCtx.index = i; + tagCtx.ctxPrm = contextParameter; tagCtx.render = renderContent; + tagCtx.cvtArgs = convertArgs; + tagCtx.bndArgs = convertBoundArgs; tagCtx.view = parentView; - tagCtx.ctx = extendCtx(tagCtx.ctx, ctx); // Clone and extend parentView.ctx + tagCtx.ctx = extendCtx(extendCtx(tagCtx.ctx, tagDef && tagDef.ctx), ctx); // Clone and extend parentView.ctx } if (tmpl = tagCtx.props.tmpl) { // If the tmpl property is overridden, set the value (when initializing, or, in case of binding: ^tmpl=..., when updating) @@ -10460,7 +10538,6 @@ function renderTag(tagName, parentView, tmpl, tagCtxs, isUpdate, onError) { tag.parent = parentTag = ctx && ctx.tag; tag.tagCtxs = tagCtxs; - tagDataMap = tag.dataMap; if (linkCtx) { tag.inline = false; @@ -10469,14 +10546,17 @@ function renderTag(tagName, parentView, tmpl, tagCtxs, isUpdate, onError) { } if (tag._.bnd = boundTag || linkCtx.fn) { // Bound if {^{tag...}} or data-link="{tag...}" + tag._.ths = tagCtx.params.props.this; // Tag has a this=expr binding, to get javascript reference to tag instance + tag._.lt = tagCtxs.lt; // If a late path @some.path has not returned @some object, mark tag as late tag._.arrVws = {}; } else if (tag.dataBoundOnly) { error(tagName + " must be data-bound:\n{^{" + tagName + "}}"); } //TODO better perf for childTags() - keep child tag.tags array, (and remove child, when disposed) // tag.tags = []; + } else if (linkCtx && linkCtx.fn._lr) { + callInit = !!tag.init; } - tagCtxs = tag.tagCtxs; tagDataMap = tag.dataMap; tagCtx.tag = tag; @@ -10493,11 +10573,12 @@ function renderTag(tagName, parentView, tmpl, tagCtxs, isUpdate, onError) { //TODO better perf for childTags: parentTag.tags.push(tag); } tags[tag.tagName] = tagCtxCtx.tag = tag; + tagCtxCtx.tagCtx = tagCtx; } } if (!(tag._er = onError)) { tagHandlersFromProps(tag, tagCtxs[0]); - tag.rendering = {}; // Provide object for state during render calls to tag and elses. (Used by {{if}} and {{for}}...) + tag.rendering = {rndr: tag.rendering}; // Provide object for state during render calls to tag and elses. (Used by {{if}} and {{for}}...) for (i = 0; i < l; i++) { // Iterate tagCtx for each {{else}} block tagCtx = tag.tagCtx = tagCtxs[i]; props = tagCtx.props; @@ -10508,62 +10589,95 @@ function renderTag(tagName, parentView, tmpl, tagCtxs, isUpdate, onError) { tag.init(tagCtx, linkCtx, tag.ctx); callInit = undefined; } - if (!tagCtx.args.length && tag.argDefault !== false) { + if (!tagCtx.args.length && tagCtx.argDefault !== false && tag.argDefault !== false) { tagCtx.args = args = [tagCtx.view.data]; // Missing first arg defaults to the current data context tagCtx.params.args = ["#data"]; } - bindTo = tag.bindTo; + bindTo = bindToOrBindFrom("bindTo"); - if (bindTo !== undefined) { - bindTo = tag.bindTo = $isArray(bindTo) ? bindTo : [bindTo]; - m = bindTo.length; - while (m--) { - key = bindTo[m]; - if (!isNaN(parseInt(key))) { - key = parseInt(key); // Convert "0" to 0, etc. - } - bindTo[m] = key; - } + if (tag.bindTo !== undefined) { + tag.bindTo = bindTo; + } + + if (tag.bindFrom !== undefined) { + tag.bindFrom = bindToOrBindFrom("bindFrom"); + } else if (tag.bindTo) { + tag.bindFrom = tag.bindTo = bindTo; } + bindFrom = tag.bindFrom || bindTo; - bindTo = tag.bindTo || [0]; bindToLength = bindTo.length; - if (tag._.bnd){ - makeArray("linkedElement"); - makeArray("linkedCtxParam"); + bindFromLength = bindFrom.length; + + if (tag._.bnd && (linkedElement = tag.linkedElement)) { + tag.linkedElement = linkedElement = $isArray(linkedElement) ? linkedElement: [linkedElement]; + + if (bindToLength !== linkedElement.length) { + error("linkedElement not same length as bindTo"); + } + } + if (linkedElement = tag.linkedCtxParam) { + tag.linkedCtxParam = linkedElement = $isArray(linkedElement) ? linkedElement: [linkedElement]; + + if (bindFromLength !== linkedElement.length) { + error("linkedCtxParam not same length as bindFrom/bindTo"); + } + } + + if (bindFrom) { + tag._.fromIndex = {}; // Hash of bindFrom index which has same path value as bindTo index. fromIndex = tag._.fromIndex[toIndex] + tag._.toIndex = {}; // Hash of bindFrom index which has same path value as bindTo index. fromIndex = tag._.fromIndex[toIndex] + n = bindFromLength; + while (n--) { + key = bindFrom[n]; + m = bindToLength; + while (m--) { + if (key === bindTo[m]) { + tag._.fromIndex[m] = n; + tag._.toIndex[n] = m; + } + } + } } if (linkCtx) { // Set attr on linkCtx to ensure outputting to the correct target attribute. // Setting either linkCtx.attr or this.attr in the init() allows per-instance choice of target attrib. - linkCtx.attr = tag.attr = linkCtx.attr || tag.attr; + linkCtx.attr = tag.attr = linkCtx.attr || tag.attr || linkCtx._dfAt; } attr = tag.attr; tag._.noVws = attr && attr !== HTML; } args = tag.cvtArgs(undefined, i); if (tag.linkedCtxParam) { - m = bindToLength; + bdArgs = tag.cvtArgs(1, i); + m = bindFromLength; + defaultCtx = tag.constructor.prototype.ctx; while (m--) { if (ctxPrm = tag.linkedCtxParam[m]) { - key = bindTo[m]; + key = bindFrom[m]; + initVal = bdArgs[m]; // Create tag contextual parameter - tagCtx.ctx[ctxPrm] = $sub._cp(argOrProp(tagCtx, key), argOrProp(tagCtx.params, key), tagCtx.view, tag._.bnd && {tag: tag, ind: m, tagElse: i}); + tagCtx.ctx[ctxPrm] = $sub._cp( + defaultCtx && initVal === undefined ? defaultCtx[ctxPrm]: initVal, + initVal !== undefined && argOrProp(tagCtx.params, key), + tagCtx.view, + tag._.bnd && {tag: tag, cvt: tag.convert, ind: m, tagElse: i} + ); } } } - if (mapDef = props.dataMap || tagDataMap) { - if (args.length || props.dataMap) { - thisMap = tagCtx.map; - if (!thisMap || thisMap.src !== args[0] || isUpdate) { - if (thisMap && thisMap.src) { - thisMap.unmap(); // only called if observable map - not when only used in JsRender, e.g. by {{props}} - } - thisMap = tagCtx.map = mapDef.map(args[0], props, undefined, !tag._.bnd); + if ((mapDef = props.dataMap || tagDataMap) && (args.length || props.dataMap)) { + thisMap = tagCtx.map; + if (!thisMap || thisMap.src !== args[0] || isUpdate) { + if (thisMap && thisMap.src) { + thisMap.unmap(); // only called if observable map - not when only used in JsRender, e.g. by {{props}} } - args = [thisMap.tgt]; + mapDef.map(args[0], tagCtx, thisMap, !tag._.bnd); + thisMap = tagCtx.map; } + args = [thisMap.tgt]; } itemRet = undefined; @@ -10596,18 +10710,16 @@ function renderTag(tagName, parentView, tmpl, tagCtxs, isUpdate, onError) { // No return value from render, and no template/content tagCtx.render(...), so return undefined ret = ret ? ret + (itemRet || "") : itemRet; // If no rendered content, this will be undefined } - tag.rendering = undefined; + tag.rendering = tag.rendering.rndr; // Remove tag.rendering object (if this is outermost render call. (In case of nested calls) } tag.tagCtx = tagCtxs[0]; tag.ctx = tag.tagCtx.ctx; - if (tag._.noVws) { - if (tag.inline) { - // inline tag with attr set to "text" will insert HTML-encoded content - as if it was element-based innerText - ret = attr === "text" - ? $converters.html(ret) - : ""; - } + if (tag._.noVws && tag.inline) { + // inline tag with attr set to "text" will insert HTML-encoded content - as if it was element-based innerText + ret = attr === "text" + ? $converters.html(ret) + : ""; } return boundTag && parentView._.onRender // Call onRender (used by JsViews if present, to add binding annotations around rendered content) @@ -10647,6 +10759,7 @@ function View(context, type, parentView, data, template, key, onRender, contentT parentView_ = parentView._; self.isTop = parentView_.scp; // Is top content view of a link("#container", ...) call self.scope = (!context.tag || context.tag === parentView.ctx.tag) && !self.isTop && parentView.scope || self; + // Scope for contextParams - closest non flow tag ancestor or root view if (parentView_.useKey) { // Parent is not an 'array view'. Add this view to its views object // self._key = is the key in the parent view hash @@ -10672,6 +10785,7 @@ View.prototype = { getRsc: getResource, getTmpl: getTemplate, ctxPrm: contextParameter, + getOb: getPathObject, _is: "view" }; @@ -10696,8 +10810,7 @@ function compileChildResources(parentTmpl) { //=============== function compileTag(name, tagDef, parentTmpl) { - var tmpl, baseTag, prop, l, key, bindToLength, - bindTo = tagDef.bindTo, + var tmpl, baseTag, prop, compiledDef = new $sub._tg(); function Tag() { @@ -10721,10 +10834,12 @@ function compileTag(name, tagDef, parentTmpl) { if (baseTag = tagDef.baseTag) { tagDef.flow = !!tagDef.flow; // Set flow property, so defaults to false even if baseTag has flow=true - tagDef.baseTag = baseTag = "" + baseTag === baseTag + baseTag = "" + baseTag === baseTag ? (parentTmpl && parentTmpl.tags[baseTag] || $tags[baseTag]) : baseTag; - + if (!baseTag) { + error('baseTag: "' + tagDef.baseTag + '" not found'); + } compiledDef = $extend(compiledDef, baseTag); for (prop in tagDef) { @@ -10783,11 +10898,9 @@ function compileTmpl(name, tmpl, parentTmpl, options) { }// END BROWSER-SPECIFIC CODE } //BROWSER-SPECIFIC CODE if (elem) { - // Generally this is a script element. - // However we allow it to be any element, so you can for example take the content of a div, - // use it as a template, and replace it by the same content rendered against data. - // e.g. for linking the content of a div to a container, and using the initial content as template: - // $.link("#content", model, {tmpl: "#content"}); + if (elem.tagName !== "SCRIPT") { + error(value + ": Use script block, not " + elem.tagName); + } if (options) { // We will compile a new template using the markup in the script element value = elem.innerHTML; @@ -10837,23 +10950,27 @@ function compileTmpl(name, tmpl, parentTmpl, options) { // If options, then this was already compiled from a (script) element template declaration. // If not, then if tmpl is a template object, use it for options - options = options || (tmpl.markup ? tmpl : {}); - options.tmplName = name; + options = options || (tmpl.markup + ? tmpl.bnds + ? $extend({}, tmpl) + : tmpl + : {} + ); + + options.tmplName = options.tmplName || name || "unnamed"; if (parentTmpl) { options._parentTmpl = parentTmpl; } // If tmpl is not a markup string or a selector string, then it must be a template object // In that case, get it from the markup property of the object - if (!tmplOrMarkup && tmpl.markup && (tmplOrMarkup = lookupTemplate(tmpl.markup))) { - if (tmplOrMarkup.fn) { - // If the string references a compiled template object, need to recompile to merge any modified options - tmplOrMarkup = tmplOrMarkup.markup; - } + if (!tmplOrMarkup && tmpl.markup && (tmplOrMarkup = lookupTemplate(tmpl.markup)) && tmplOrMarkup.fn) { + // If the string references a compiled template object, need to recompile to merge any modified options + tmplOrMarkup = tmplOrMarkup.markup; } if (tmplOrMarkup !== undefined) { - if (tmplOrMarkup.fn || tmpl.fn) { + if (tmplOrMarkup.render || tmpl.render) { // tmpl is already compiled, so use it - if (tmplOrMarkup.fn) { + if (tmplOrMarkup.tmpls) { compiledTmpl = tmplOrMarkup; } } else { @@ -11108,19 +11225,20 @@ function tmplObject(markup, options) { // Template object constructor var htmlTag, wrapMap = $subSettingsAdvanced._wm || {}, // Only used in JsViews. Otherwise empty: {} - tmpl = $extend( - { - tmpls: [], - links: {}, // Compiled functions for link expressions - bnds: [], - _is: "template", - render: renderContent - }, - options - ); + tmpl = { + tmpls: [], + links: {}, // Compiled functions for link expressions + bnds: [], + _is: "template", + render: renderContent + }; + + if (options) { + tmpl = $extend(tmpl, options); + } tmpl.markup = markup; - if (!options.htmlTag) { + if (!tmpl.htmlTag) { // Set tmpl.tag to the top-level HTML tag used in the template, if any... htmlTag = rFirstElem.exec(markup); tmpl.htmlTag = htmlTag ? htmlTag[1].toLowerCase() : ""; @@ -11211,13 +11329,14 @@ function addSetting(st) { }; } -//========= -// dataMap -//========= +//======================== +// dataMap for render only +//======================== function dataMap(mapDef) { function Map(source, options) { this.tgt = mapDef.getTgt(source, options); + options.map = this; } if ($isFunction(mapDef)) { @@ -11259,7 +11378,9 @@ function renderContent(data, context, noIteration, parentView, key, onRender) { view = view || tagCtx.view; tmpl = view.getTmpl(tag.template || tagCtx.tmpl); if (!arguments.length) { - data = view; + data = tag.contentCtx && $isFunction(tag.contentCtx) + ? data = tag.contentCtx(data) + : view; // Default data context for wrapped block content is the first argument } } else { // This is a template.render(...) call @@ -11326,7 +11447,7 @@ function renderWithViews(tmpl, data, context, noIteration, view, key, onRender, // Render template against data as a tree of subviews (nested rendered template instances), or as a string (top-level template). // If the data is the parent view, treat as noIteration, re-render with the same data context. // tmpl can be a string (e.g. rendered by a tag.render() method), or a compiled template. - var i, l, newView, childView, itemResult, swapContent, contentTmpl, outerOnRender, tmplName, itemVar, newCtx, tagCtx, + var i, l, newView, childView, itemResult, swapContent, contentTmpl, outerOnRender, tmplName, itemVar, newCtx, tagCtx, noLinking, result = ""; if (tag) { @@ -11357,7 +11478,6 @@ function renderWithViews(tmpl, data, context, noIteration, view, key, onRender, context = context || {}; context.link = false; } - if (itemVar = tagCtx.props.itemVar) { if (itemVar.charAt(0) !== "~") { syntaxError("Use itemVar='~myItem'"); @@ -11368,6 +11488,12 @@ function renderWithViews(tmpl, data, context, noIteration, view, key, onRender, if (view) { onRender = onRender || view._.onRender; + noLinking = context && context.link === false; + + if (noLinking && view._.nl) { + onRender = undefined; + } + context = extendCtx(context, view.ctx); } @@ -11377,7 +11503,7 @@ function renderWithViews(tmpl, data, context, noIteration, view, key, onRender, } // If link===false, do not call onRender, so no data-linking marker nodes - if (onRender && (context && context.link === false || tag && tag._.noVws)) { + if (onRender && tag && tag._.noVws) { onRender = undefined; } outerOnRender = onRender; @@ -11399,10 +11525,12 @@ function renderWithViews(tmpl, data, context, noIteration, view, key, onRender, ? view : (key !== undefined && view) || new View(context, "array", view, data, tmpl, key, onRender, contentTmpl); + newView._.nl= noLinking; if (view && view._.useKey) { // Parent is not an 'array view' newView._.bnd = !tag || tag._.bnd && tag; // For array views that are data bound for collection change events, set the // view._.bnd property to true for top-level link() or data-link="{for}", or to the tag instance for a data-bound tag, e.g. {^{for ...}} + newView.tag = tag; } for (i = 0, l = data.length; i < l; i++) { // Create a view for each data item. @@ -11423,10 +11551,11 @@ function renderWithViews(tmpl, data, context, noIteration, view, key, onRender, } newView = swapContent ? view : new View(newCtx, tmplName || "data", view, data, tmpl, key, onRender, contentTmpl); newView._.it = itemVar; + newView.tag = tag; + newView._.nl = noLinking; result += tmpl.fn(data, newView, $sub); } if (tag) { - newView.tag = tag; newView.tagElse = tagCtx.index; tagCtx.contentView = newView; } @@ -11531,7 +11660,7 @@ function tmplFn(markup, tmpl, isLinkExpr, convertBack, hasElse) { } slash = slash || isLinkExpr && !hasElse; - var late, + var late, openTagName, isLateOb, pathBindings = (bind || isLinkExpr) && [[]], // pathBindings is an array of arrays for arg bindings and a hash of arrays for prop bindings props = "", args = "", @@ -11555,23 +11684,31 @@ function tmplFn(markup, tmpl, isLinkExpr, convertBack, hasElse) { } else if (tagName) { if (tagName === "else") { if (rTestElseIf.test(params)) { - syntaxError('for "{{else if expr}}" use "{{else expr}}"'); + syntaxError('For "{{else if expr}}" use "{{else expr}}"'); } - pathBindings = current[8] && [[]]; - current[9] = markup.substring(current[9], index); // contentMarkup for block tag + pathBindings = current[9] && [[]]; + current[10] = markup.substring(current[10], index); // contentMarkup for block tag + openTagName = current[11] || current[0] || syntaxError("Mismatched: " + all); + // current[0] is tagName, but for {{else}} nodes, current[11] is tagName of preceding open tag current = stack.pop(); content = current[2]; block = true; } if (params) { // remove newlines from the params string, to avoid compiled code errors for unterminated strings - parseParams(params.replace(rNewLine, " "), pathBindings, tmpl) - .replace(rBuildHash, function(all, onerror, isCtx, key, keyToken, keyValue, arg, param) { + parseParams(params.replace(rNewLine, " "), pathBindings, tmpl, isLinkExpr) + .replace(rBuildHash, function(all, onerror, isCtxPrm, key, keyToken, keyValue, arg, param) { + if (key === "this:") { + keyValue = "undefined"; // this=some.path is always a to parameter (one-way), so don't need to compile/evaluate some.path initialization + } + if (param) { + isLateOb = isLateOb || param.charAt(0) === "@"; + } key = "'" + keyToken + "':"; if (arg) { - args += keyValue + ","; + args += isCtxPrm + keyValue + ","; paramsArgs += "'" + param + "',"; - } else if (isCtx) { + } else if (isCtxPrm) { // Contextual parameter, ~foo=expr ctxProps += key + 'j._cp(' + keyValue + ',"' + param + '",view),'; // Compiled code for evaluating tagCtx on a tag will have: ctx:{'foo':j._cp(compiledExpr, "expr", view)} paramsCtxProps += key + "'" + param + "',"; @@ -11582,7 +11719,7 @@ function tmplFn(markup, tmpl, isLinkExpr, convertBack, hasElse) { useTrigger += keyValue; } if (keyToken === "lateRender") { - late = param; // Render after first pass + late = param !== "false"; // Render after first pass } props += key + keyValue + ","; paramsProps += key + "'" + param + "',"; @@ -11605,17 +11742,19 @@ function tmplFn(markup, tmpl, isLinkExpr, convertBack, hasElse) { onError, useTrigger, late, + isLateOb, pathBindings || 0 ]; content.push(newNode); if (block) { stack.push(current); current = newNode; - current[9] = loc; // Store current location of open tag, to be able to add contentMarkup when we reach closing tag + current[10] = loc; // Store current location of open tag, to be able to add contentMarkup when we reach closing tag + current[11] = openTagName; // Used for checking syntax (matching close tag) } } else if (closeBlock) { - blockTagCheck(closeBlock !== current[0] && current[0] !== "else" && closeBlock, current[0]); - current[9] = markup.substring(current[9], index); // contentMarkup for block tag + blockTagCheck(closeBlock !== current[0] && closeBlock !== current[11] && closeBlock, current[0]); // Check matching close tag name + current[10] = markup.substring(current[10], index); // contentMarkup for block tag current = stack.pop(); } blockTagCheck(!current && closeBlock); @@ -11657,7 +11796,7 @@ function tmplFn(markup, tmpl, isLinkExpr, convertBack, hasElse) { pushprecedingContent(markup.length); if (loc = astTop[astTop.length - 1]) { - blockTagCheck("" + loc !== loc && (+loc[9] === loc[9]) && loc[0]); + blockTagCheck("" + loc !== loc && (+loc[10] === loc[10]) && loc[0]); } // result = tmplFnsCache[markup] = buildCode(astTop, tmpl); // } @@ -11667,7 +11806,7 @@ function tmplFn(markup, tmpl, isLinkExpr, convertBack, hasElse) { bindings = []; i = astTop.length; while (i--) { - bindings.unshift(astTop[i][8]); // With data-link expressions, pathBindings array for tagCtx[i] is astTop[i][8] + bindings.unshift(astTop[i][9]); // With data-link expressions, pathBindings array for tagCtx[i] is astTop[i][9] } setPaths(result, bindings); } else { @@ -11701,18 +11840,15 @@ function paramStructure(parts, type) { + (type ? type + ':{' : '') - + 'args:[' + parts[0] + ']' - + (parts[1] || !type - ? ',\n\tprops:{' + parts[1] + '}' - : "") + + 'args:[' + parts[0] + '],\n\tprops:{' + parts[1] + '}' + (parts[2] ? ',\n\tctx:{' + parts[2] + '}' : ""); } -function parseParams(params, pathBindings, tmpl) { +function parseParams(params, pathBindings, tmpl, isLinkExpr) { - function parseTokens(all, lftPrn0, lftPrn, bound, path, operator, err, eq, path2, prn, comma, lftPrn2, apos, quot, rtPrn, rtPrnDot, prn2, space, index, full) { - // /(\()(?=\s*\()|(?:([([])\s*)?(?:(\^?)(!*?[#~]?[\w$.^]+)?\s*((\+\+|--)|\+|-|&&|\|\||===|!==|==|!=|<=|>=|[<>%*:?\/]|(=))\s*|(!*?[#~]?[\w$.^]+)([([])?)|(,\s*)|(\(?)\\?(?:(')|("))|(?:\s*(([)\]])(?=\s*[.^]|\s*$|[^([])|[)\]])([([]?))|(\s+)/g, - // lftPrn0 lftPrn bound path operator err eq path2 prn comma lftPrn2 apos quot rtPrn rtPrnDot prn2 space + function parseTokens(all, lftPrn0, lftPrn, bound, path, operator, err, eq, path2, late, prn, comma, lftPrn2, apos, quot, rtPrn, rtPrnDot, prn2, space, index, full) { + // /(\()(?=\s*\()|(?:([([])\s*)?(?:(\^?)(~?[\w$.^]+)?\s*((\+\+|--)|\+|-|&&|\|\||===|!==|==|!=|<=|>=|[<>%*:?\/]|(=))\s*|(!*?(@)?[#~]?[\w$.^]+)([([])?)|(,\s*)|(\(?)\\?(?:(')|("))|(?:\s*(([)\]])(?=[.^]|\s*$|[^([])|[)\]])([([]?))|(\s+)/g, + // lftPrn0 lftPrn bound path operator err eq path2 late prn comma lftPrn2 apos quot rtPrn rtPrnDot prn2 space // (left paren? followed by (path? followed by operator) or (path followed by paren?)) or comma or apos or quot or right paren or space function parsePath(allPath, not, object, helper, view, viewProperty, pathTokens, leafToken) { //rPath = /^(!*?)(?:null|true|false|\d[\d.]*|([\w$]+|\.|~([\w$]+)|#(view|([\w$]+))?)([\w$.^]*?)(?:[.[^]([\w$]+)\]?)?)$/g, @@ -11724,11 +11860,19 @@ function parseParams(params, pathBindings, tmpl) { syntaxError(allPath); } if (!subPath) { - allPath = (helper + allPath = (late // late path @a.b.c: not throw on 'property of undefined' if a undefined, and will use getOb() after linking to resolve late. + ? (isLinkExpr ? '' : '(ltOb.lt=ltOb.lt||') + '(ob=' + : "" + ) + + (helper ? 'view.ctxPrm("' + helper + '")' : view ? "view" : "data") + + (late + ? ')===undefined' + (isLinkExpr ? '' : ')') + '?"":view.getOb(ob,"' + : "" + ) + (leafToken ? (viewProperty ? "." + viewProperty @@ -11737,15 +11881,18 @@ function parseParams(params, pathBindings, tmpl) { : (view ? "" : "." + object) ) + (pathTokens || "") : (leafToken = helper ? "" : view ? viewProperty || "" : object, "")); - allPath = allPath + (leafToken ? "." + leafToken : ""); allPath = not + (allPath.slice(0, 9) === "view.data" ? allPath.slice(5) // convert #view.data... to data... - : allPath); + : allPath) + + (late + ? (isLinkExpr ? '"': '",ltOb') + (prn ? ',1)':')') + : "" + ); } if (bindings) { - binds = named === "linkTo" ? (bindto = pathBindings._jsvto = pathBindings._jsvto || []) : bndCtx.bd; + binds = named === "_linkTo" ? (bindto = pathBindings._jsvto = pathBindings._jsvto || []) : bndCtx.bd; if (theOb = subPath && binds[binds.length-1]) { if (theOb._cpfn) { // Computed property exprOb while (theOb.sb) { @@ -11773,6 +11920,10 @@ function parseParams(params, pathBindings, tmpl) { operator = operator || ""; lftPrn = lftPrn || lftPrn0 || lftPrn2; path = path || path2; + + if (late && (late = !/\)|]/.test(full.charAt(index-1)))) { + path = path.slice(1).split(".").join("^"); // Late path @z.b.c. Use "^" rather than "." to ensure that deep binding will be used + } // Could do this - but not worth perf cost?? :- // if (!path.lastIndexOf("#data.", 0)) { path = path.slice(6); } // If path starts with "#data.", remove that. prn = prn || prn2 || ""; @@ -11781,7 +11932,7 @@ function parseParams(params, pathBindings, tmpl) { rtSq = ")"; if (prn === "[") { - prn ="[j._sq("; + prn = "[j._sq("; rtSq = ")]"; } @@ -11791,7 +11942,7 @@ function parseParams(params, pathBindings, tmpl) { if (bindings && rtPrnDot && !aposed && !quoted) { // This is a binding to a path in which an object is returned by a helper/data function/expression, e.g. foo()^x.y or (a?b:c)^x.y // We create a compiled function to get the object instance (which will be called when the dependent data of the subexpression changes, to return the new object, and trigger re-binding of the subsequent path) - if (!named || boundName || bindto) { + if (parenDepth && (!named || boundName || bindto)) { expr = pathStart[parenDepth - 1]; if (full.length - 1 > index - (expr || 0)) { // We need to compile a subexpression expr = full.slice(expr, index + all.length); @@ -11846,7 +11997,7 @@ function parseParams(params, pathBindings, tmpl) { ) : eq // named param. Remove bindings for arg and create instead bindings array for prop - ? (parenDepth && syntaxError(params), bindings && pathBindings.pop(), named = path, boundName = bound, paramIndex = index + all.length, + ? (parenDepth && syntaxError(params), bindings && pathBindings.pop(), named = "_" + path, boundName = bound, paramIndex = index + all.length, bindings && ((bindings = bndCtx.bd = pathBindings[named] = []), bindings.skp = !bound), path + ':') : path // path @@ -11889,7 +12040,12 @@ function parseParams(params, pathBindings, tmpl) { parenDepth = 0, fnCall = {}, // We are in a function call pathStart = {}, // tracks the start of the current path such as c^d() in the above example - result = (params + (tmpl ? " " : "")).replace(rParams, parseTokens); + result; + + if (params.charAt(0) === "@") { + params = params.replace(rBracketQuote, "."); + } + result = (params + (tmpl ? " " : "")).replace(rParams, parseTokens); return !parenDepth && result || syntaxError(params); // Syntax error if unbalanced parens in params expression } @@ -11899,7 +12055,7 @@ function buildCode(ast, tmpl, isLinkExpr) { // Used for compiling templates, and also by JsViews to build functions for data link expressions var i, node, tagName, converter, tagCtx, hasTag, hasEncoder, getsVal, hasCnvt, useCnvt, tmplBindings, pathBindings, params, boundOnErrStart, boundOnErrEnd, tagRender, nestedTmpls, tmplName, nestedTmpl, tagAndElses, content, markup, nextIsElse, oldCode, isElse, isGetVal, tagCtxFn, - onError, tagStart, trigger, lateRender, + onError, tagStart, trigger, lateRender, retStrOpen, retStrClose, tmplBindingKey = 0, useViews = $subSettingsAdvanced.useViews || tmpl.useViews || tmpl.tags || tmpl.templates || tmpl.helpers || tmpl.converters, code = "", @@ -11940,14 +12096,21 @@ function buildCode(ast, tmpl, isLinkExpr) { tagCtx = paramStructure(node[3], 'params') + '},' + paramStructure(params = node[4]); trigger = node[6]; lateRender = node[7]; - markup = node[9] && node[9].replace(rUnescapeQuotes, "$1"); + if (node[8]) { // latePath @a.b.c or @~a.b.c + retStrOpen = "\nvar ob,ltOb={},ctxs="; + retStrClose = ";\nctxs.lt=ltOb.lt;\nreturn ctxs;"; + } else { + retStrOpen = "\nreturn "; + retStrClose = ""; + } + markup = node[10] && node[10].replace(rUnescapeQuotes, "$1"); if (isElse = tagName === "else") { if (pathBindings) { - pathBindings.push(node[8]); + pathBindings.push(node[9]); } } else { onError = node[5] || $subSettings.debugMode !== false && "undefined"; // If debugMode not false, set default onError handler on tag to "undefined" (see onRenderError) - if (tmplBindings && (pathBindings = node[8])) { // Array of paths, or false if not data-bound + if (tmplBindings && (pathBindings = node[9])) { // Array of paths, or false if not data-bound pathBindings = [pathBindings]; tmplBindingKey = tmplBindings.push(1); // Add placeholder in tmplBindings for compiled function } @@ -11990,7 +12153,7 @@ function buildCode(ast, tmpl, isLinkExpr) { if (isGetVal && (pathBindings || trigger || converter && converter !== HTML || lateRender)) { // For convertVal we need a compiled function to return the new tagCtx(s) tagCtxFn = new Function("data,view,j,u", "// " + tmplName + " " + (++tmplBindingKey) + " " + tagName - + "\nreturn {" + tagCtx + "};"); + + retStrOpen + "{" + tagCtx + "};" + retStrClose); tagCtxFn._er = onError; tagCtxFn._tag = tagName; tagCtxFn._bd = !!pathBindings; // data-linked tag {^{.../}} @@ -12027,7 +12190,8 @@ function buildCode(ast, tmpl, isLinkExpr) { tagRender = 't("' + tagAndElses + '",view,this,'; if (isLinkExpr || pathBindings) { // This is a bound tag (data-link expression or inline bound tag {^{tag ...}}) so we store a compiled tagCtxs function in tmp.bnds - code = new Function("data,view,j,u", " // " + tmplName + " " + tmplBindingKey + " " + tagAndElses + "\nreturn " + code + ";"); + code = new Function("data,view,j,u", " // " + tmplName + " " + tmplBindingKey + " " + tagAndElses + retStrOpen + code + + retStrClose); code._er = onError; code._tag = tagAndElses; if (pathBindings) { @@ -12044,7 +12208,7 @@ function buildCode(ast, tmpl, isLinkExpr) { // This is the last {{else}} for an inline tag. // For a bound tag, pass the tagCtxs fn lookup key to renderTag. // For an unbound tag, include the code directly for evaluating tagCtxs array - code = oldCode + tagStart + tagRender + (code.deps && tmplBindingKey || code) + ")"; + code = oldCode + tagStart + tagRender + (pathBindings && tmplBindingKey || code) + ")"; pathBindings = 0; tagAndElses = 0; } @@ -12057,13 +12221,17 @@ function buildCode(ast, tmpl, isLinkExpr) { } // Include only the var references that are needed in the code code = "// " + tmplName - + + (tmplOptions.debug ? "\ndebugger;" : "") + "\nvar v" + (hasTag ? ",t=j._tag" : "") // has tag + (hasCnvt ? ",c=j._cnvt" : "") // converter + (hasEncoder ? ",h=j._html" : "") // html converter - + (isLinkExpr ? ";\n" : ',ret=""\n') - + (tmplOptions.debug ? "debugger;" : "") + + (isLinkExpr + ? (node[8] // late @... path? + ? ", ob" + : "" + ) + ";\n" + : ',ret=""') + code + (isLinkExpr ? "\n" : ";\nreturn ret;"); @@ -12094,26 +12262,103 @@ function extendCtx(context, parentContext) { : parentContext && $extend({}, parentContext); } -// Get character entity for HTML and Attribute encoding -function getCharEntity(ch) { - return charEntities[ch] || (charEntities[ch] = "&#" + ch.charCodeAt(0) + ";"); -} - -function getTargetProps(source) { +function getTargetProps(source, tagCtx) { // this pointer is theMap - which has tagCtx.props too // arguments: tagCtx.args. var key, prop, props = []; - if (typeof source === OBJECT) { + if (typeof source === OBJECT || $isFunction(source)) { for (key in source) { prop = source[key]; - if (key !== $expando && source.hasOwnProperty(key) && !$isFunction(prop)) { + if (key !== $expando && source.hasOwnProperty(key) && (!tagCtx.props.noFunctions || !$.isFunction(prop))) { props.push({key: key, prop: prop}); } } } - return props; + return getTargetSorted(props, tagCtx); +} + +function getTargetSorted(value, tagCtx) { + // getTgt + var mapped, start, end, + tag = tagCtx.tag, + props = tagCtx.props, + propParams = tagCtx.params.props, + filter = props.filter, + sort = props.sort, + directSort = sort === true, + step = parseInt(props.step), + reverse = props.reverse ? -1 : 1; + + if (!$isArray(value)) { + return value; + } + if (directSort || sort && "" + sort === sort) { + // Temporary mapped array holds objects with index and sort-value + mapped = value.map(function(item, i) { + item = directSort ? item : getPathObject(item, sort); + return {i: i, v: "" + item === item ? item.toLowerCase() : item}; + }); + // Sort mapped array + mapped.sort(function(a, b) { + return a.v > b.v ? reverse : a.v < b.v ? -reverse : 0; + }); + // Map to new array with resulting order + value = mapped.map(function(item){ + return value[item.i]; + }); + } else if ((sort || reverse < 0) && !tag.dataMap) { + value = value.slice(); // Clone array first if not already a new array + } + if ($isFunction(sort)) { + value = value.sort(sort); + } + if (reverse < 0 && !sort) { // Reverse result if not already reversed in sort + value = value.reverse(); + } + + if (value.filter && filter) { // IE8 does not support filter + value = value.filter(filter, tagCtx); + if (tagCtx.tag.onFilter) { + tagCtx.tag.onFilter(tagCtx); + } + } + + if (propParams.sorted) { + mapped = (sort || reverse < 0) ? value : value.slice(); + if (tag.sorted) { + $.observable(tag.sorted).refresh(mapped); // Note that this might cause the start and end props to be modified - e.g. by pager tag control + } else { + tagCtx.map.sorted = mapped; + } + } + + start = props.start; // Get current value - after possible changes triggered by tag.sorted refresh() above + end = props.end; + if (propParams.start && start === undefined || propParams.end && end === undefined) { + start = end = 0; + } + if (!isNaN(start) || !isNaN(end)) { // start or end specified, but not the auto-create Number array scenario of {{for start=xxx end=yyy}} + start = +start || 0; + end = end === undefined || end > value.length ? value.length : +end; +// end = end === undefined ? value.length : +end; + value = value.slice(start, end); + } + if (step > 1) { + start = 0; + end = value.length; + mapped = []; + for (; start & ' and " by corresponding entities. + // HTML encode: Replace < > & ' " ` etc. by corresponding entities. return text != undefined ? rIsHtml.test(text) && ("" + text).replace(rHtmlEncode, getCharEntity) || text : ""; } +function dataEncode(text) { + // Encode just < > and & - intended for 'safe data' along with {{:}} rather than {{>}} + return "" + text === text ? text.replace(rDataEncode, getCharEntity) : text; +} + +function dataUnencode(text) { + // Unencode just < > and & - intended for 'safe data' along with {{:}} rather than {{>}} + return "" + text === text ? text.replace(rDataUnencode, getCharFromEntity) : text; +} + //========================== Initialize ========================== $sub = $views.sub; @@ -12164,6 +12429,10 @@ if (!(jsr || $ && $.render)) { $.fn.render = $fnRender; $expando = $.expando; if ($.observable) { + if (versionNumber !== (versionNumber = $.views.jsviews)) { + // Different version of jsRender was loaded + throw "JsObservable requires JsRender " + versionNumber; + } $extend($sub, $.views.sub); // jquery.observable.js was loaded before jsrender.js $views.map = $.views.map; } @@ -12220,7 +12489,7 @@ if (!(jsr || $ && $.render)) { : ( $subSettings.debugMode = debugMode, $subSettings.onError = debugMode + "" === debugMode - ? new Function("", "return '" + debugMode + "';") + ? function() { return debugMode; } : $isFunction(debugMode) ? debugMode : undefined, @@ -12243,7 +12512,7 @@ if (!(jsr || $ && $.render)) { // Otherwise return "" var self = this, tagCtx = self.tagCtx, - ret = (self.rendering.done || !val && (arguments.length || !tagCtx.index)) + ret = (self.rendering.done || !val && (tagCtx.args.length || !tagCtx.index)) ? "" : (self.rendering.done = true, self.selected = tagCtx.index, @@ -12254,21 +12523,53 @@ if (!(jsr || $ && $.render)) { flow: true }, "for": { + sortDataMap: dataMap(getTargetSorted), + init: function(val, cloned) { + var l, tagCtx, props, sort, + self = this, + tagCtxs = self.tagCtxs; + l = tagCtxs.length; + while (l--) { + tagCtx = tagCtxs[l]; + props = tagCtx.props; + tagCtx.argDefault = props.end === undefined || tagCtx.args.length > 0; // Default to #data except for auto-create range scenario {{for start=xxx end=yyy step=zzz}} + + if (tagCtx.argDefault !== false && $isArray(tagCtx.args[0]) + && (props.sort !== undefined || tagCtx.params.props.start || tagCtx.params.props.end || props.step !== undefined || props.filter || props.reverse)) { + props.dataMap = self.sortDataMap; + } + } + }, render: function(val) { // This function is called once for {{for}} and once for each {{else}}. // We will use the tag.rendering object for carrying rendering state across the calls. - var finalElse = !arguments.length, - value, + var value, filter, srtField, isArray, i, sorted, end, step, self = this, tagCtx = self.tagCtx, + range = tagCtx.argDefault === false, + props = tagCtx.props, + iterate = range || tagCtx.args.length, // Not final else and not auto-create range result = "", done = 0; if (!self.rendering.done) { - value = finalElse ? tagCtx.view.data : val; // For the final else, defaults to current data without iteration. + value = iterate ? val : tagCtx.view.data; // For the final else, defaults to current data without iteration. + + if (range) { + range = props.reverse ? "unshift" : "push"; + end = +props.end; + step = +props.step || 1; + value = []; // auto-create integer array scenario of {{for start=xxx end=yyy}} + for (i = +props.start || 0; (end - i) * step > 0; i += step) { + value[range](i); + } + } if (value !== undefined) { - result += tagCtx.render(value, finalElse); // Iterates except on final else, if data is an array. (Use {{include}} to compose templates without array iteration) - done += $isArray(value) ? value.length : 1; + isArray = $isArray(value); + result += tagCtx.render(value, !iterate || props.noIteration); + // Iterates if data is an array, except on final else - or if noIteration property + // set to true. (Use {{include}} to compose templates without array iteration) + done += isArray ? value.length : 1; } if (self.rendering.done = done) { self.selected = tagCtx.index; @@ -12282,6 +12583,7 @@ if (!(jsr || $ && $.render)) { props: { baseTag: "for", dataMap: dataMap(getTargetProps), + init: noop, // Don't execute the base init() of the "for" tag flow: true }, include: { @@ -12303,6 +12605,8 @@ if (!(jsr || $ && $.render)) { $converters({ html: htmlEncode, attr: htmlEncode, // Includes > encoding since rConvertMarkers in JsViews does not skip > characters in attribute strings + encode: dataEncode, + unencode: dataUnencode, // Includes > encoding since rConvertMarkers in JsViews does not skip > characters in attribute strings url: function(text) { // URL encoding helper. return text != undefined ? encodeURI("" + text) : text === null ? text : ""; // null returns null, e.g. to remove attribute. undefined returns "" @@ -12314,7 +12618,6 @@ $subSettings = $sub.settings; $isArray = ($||jsr).isArray; $viewsSettings.delimiters("{{", "}}", "^"); - if (jsrToJq) { // Moving from jsrender namespace to jQuery namepace - copy over the stored items (templates, converters, helpers...) jsr.views.sub._jq($); } @@ -12322,12 +12625,12 @@ return $ || jsr; }, window)); },{}],3:[function(require,module,exports){ -/*! JsObservable v0.9.90 (Beta): http://jsviews.com/#jsobservable */ +/*! JsObservable v0.9.91 (Beta): http://jsviews.com/#jsobservable */ /* * Subcomponent of JsViews * Data change events for data-linking * - * Copyright 2017, Boris Moore + * Copyright 2018, Boris Moore * Released under the MIT License. */ @@ -12368,7 +12671,7 @@ if (!$ || !$.fn) { throw "JsObservable requires jQuery"; // We require jQuery } -var versionNumber = "v0.9.90", +var versionNumber = "v0.9.91", _ocp = "_ocp", // Observable contextual parameter $observe, $observable, @@ -12402,6 +12705,11 @@ var versionNumber = "v0.9.90", $isArray = $.isArray, OBJECT = "object"; +if ($views.jsviews !== versionNumber) { + // Different version of jsRender was loaded + throw "JsObservable requires JsRender " + versionNumber; +} + if (!$.observe) { var $eventSpecial = $.event.special, @@ -12410,6 +12718,7 @@ if (!$.observe) { concat = [].concat, PARSEINT = parseInt, rNotWhite = /\S+/g, + rShallowPath = /^[^.[]*$/, // No '.' or '[' in path propertyChangeStr = $sub.propChng = $sub.propChng || "propertyChange",// These two settings can be overridden on settings after loading arrayChangeStr = $sub.arrChng = $sub.arrChng || "arrayChange", // jsRender, and prior to loading jquery.observable.js and/or JsViews cbBindingsStore = {}, @@ -12417,14 +12726,15 @@ if (!$.observe) { observeObjKey = 1, observeCbKey = 1, observeInnerCbKey = 1, - $hasData = $.hasData, $data = $.data, remove = {}, // flag for removeProperty //========================== Top-level functions ========================== getCbKey = function(cb) { - return cb._cId = cb._cId || (".obs" + observeCbKey++); + return cb + ? (cb._cId = cb._cId || (".obs" + observeCbKey++)) + : ""; }, ObjectObservable = function(ns, data) { @@ -12467,7 +12777,7 @@ if (!$.observe) { out = out.concat(dependsPaths(path.call(root, rt, callback), rt, callback)); continue; } else if ("" + path !== path) { - root = nextObj = path; + root = nextObj = path = (path === undefined ? null : path); if (nextObj !== object) { out.push(object = nextObj); } @@ -12479,7 +12789,8 @@ if (!$.observe) { out.push(path); } if (out.length) { - out.unshift({_ar: 1}); // Switch on allowArray, for depends paths. + // Switch on allowArray, for depends paths, by passing {_ar: ...} objects to switch on allowArray then return to contextual allowArray value + out.unshift({_ar: 1}); out.push({_ar: -1}); } return out; @@ -12495,7 +12806,7 @@ if (!$.observe) { onDataChange = function(ev, eventArgs) { function isOb(val) { - return typeof val === OBJECT && (paths[0] || allowArray && $isArray(val)); + return typeof val === OBJECT && (paths[0] || !noArray && $isArray(val)); } if (!(ev.data && ev.data.off)) { @@ -12506,7 +12817,7 @@ if (!$.observe) { ctx = ev.data, observeAll = ctx.observeAll, cb = ctx.cb, - allowArray = ctx.arOk, + noArray = ctx.arOk ? 0 : 1, paths = ctx.paths, ns = ctx.ns; @@ -12527,10 +12838,10 @@ if (!$.observe) { } } else { if (isOb(oldValue)) { // oldValue is an object, so unobserve - observe_apply(undefined, ns, [oldValue], paths, cb, true); // unobserve + observe_apply(noArray, ns, [oldValue], paths, cb, true); // unobserve. Observe array change events too if this change is not from an 'observeAndBind' tag binding, or is from a 'depends' path } if (isOb(value)) { // value is an object, so observe - observe_apply(undefined, ns, [value], paths, cb); + observe_apply(noArray, ns, [value], paths, cb); // observe. Observe array change events too if this change is not from an 'observeAndBind' tag binding, or is from a 'depends' path } } ctx.cb(ev, eventArgs); @@ -12563,15 +12874,13 @@ if (!$.observe) { function filterAndObserveAll(obj, prop, unobs, nestedArray) { var newObject, newParentObs; - if (prop !== $expando) { - if (newObject = $observable._fltr(newAllPath, obj[prop], nextParentObs, filter)) { - newParentObs = nextParentObs.slice(); - if (nestedArray && updatedTgt && newParentObs[0] !== updatedTgt) { - newParentObs.unshift(updatedTgt); // For array change events when observing an array which is not the root, need to add updated array to parentObs - } - observeAll(namespace, newObject, cb, filter || (nestedArray ? undefined : 0), newParentObs, newAllPath, unobs, objMap); - // If nested array, need to observe the array too - so set filter to undefined + if (prop !== $expando && (newObject = $observable._fltr(newAllPath, obj[prop], nextParentObs, filter))) { + newParentObs = nextParentObs.slice(); + if (nestedArray && updatedTgt && newParentObs[0] !== updatedTgt) { + newParentObs.unshift(updatedTgt); // For array change events when observing an array which is not the root, need to add updated array to parentObs } + observeAll(namespace, newObject, cb, filter || (nestedArray ? undefined : 0), newParentObs, newAllPath, unobs, objMap); + // If nested array, need to observe the array too - so set filter to undefined } } @@ -12602,7 +12911,7 @@ if (!$.observe) { if (object && typeof object === OBJECT) { nextParentObs = [object].concat(parentObs); // The parentObs chain for the next depth of observeAll isObject = $isArray(object) ? "" : "*"; - if (objMap && notRemoving && $hasData(object) && objMap[obId = $data(object).obId]) { + if (objMap && notRemoving && $.hasData(object) && objMap[obId = $data(object).obId]) { objMap[obId]++; return; // This object has already being observed/unobserved by this observeAll/unobserveAll call (must be a cyclic object graph) so skip, to avoid // stack overflow/multiple instances of listener. See jsviews/pull/305 @@ -12656,8 +12965,8 @@ if (!$.observe) { } }, - shallowFilter = function(allPath /*, object, parentObs*/) { - return allPath.indexOf(".") < 0 && allPath.indexOf("[") < 0; + shallowFilter = function(path /*, object, parentObs*/) { + return rShallowPath.test(path); // No '.' and no '[' in path }, $unobserve = function() { @@ -12669,10 +12978,23 @@ if (!$.observe) { // $.observe([namespace, ]root, [1 or more objects, path or path Array params...], callback[, contextCallback][, unobserve]) function innerObserve() { + var p, parts, unobserve, callback, cbId, inId, data, contextCb, items, cbBindings, + innerCb, parentObs, allPath, filter, initNsArr, initNsArrLen, view, prop, events, el; + + function unobserveBinding(cb, binding) { + var object; + for (data in binding) { + object = binding[data]; + if ($isArray(object)) { + bindArray(cb, object, unobserve, unobserve); + } else { + observeOnOff(cb, object, undefined, ns, ""); + } + } + } - function observeOnOff(namespace, pathStr, isArrayBinding, off) { + function observeOnOff(cb, object, fullPath, namespace, pathStr, isArrayBinding, off) { var j, evData, - obIdExpando = $hasData(object), boundObOrArr = wrapArray(object), prntObs = parentObs, allPth = allPath; @@ -12680,15 +13002,14 @@ if (!$.observe) { namespace = initialNs ? namespace + "." + initialNs : namespace; if (!unobserve && (off || isArrayBinding)) { - events = obIdExpando && $._data(object); - events = events && events.events; + events = $._data(object).events; events = events && events[isArrayBinding ? arrayChangeStr : propertyChangeStr]; el = events && events.length; while (el--) { // Skip duplicates data = events[el] && events[el].data; if (data && (off && data.ns !== initialNs // When observing, don't unbind dups unless they have the same namespace - || !off && data.ns === initialNs && data.cb && data.cb._cId === callback._cId && (!callback._wrp || data.cb._wrp))) + || !off && data.ns === initialNs && data.cb && data.cb._cId === cb._cId && (!cb._wrp || data.cb._wrp))) // When observing and doing array binding, don't bind dups if they have the same namespace (Dups can happen e.g. with {^{for people ^~foo=people}}) { return; @@ -12700,13 +13021,13 @@ if (!$.observe) { } else { evData = isArrayBinding ? {} : { - fullPath: path, + fullPath: fullPath, paths: pathStr ? [pathStr] : [], prop: prop, arOk: allowArray }; evData.ns = initialNs; - evData.cb = callback; + evData.cb = cb; if (allPath) { // This is an observeAll call @@ -12736,48 +13057,11 @@ if (!$.observe) { } } - function getInnerCb(exprOb) { - // Returns the innerCb used for updating a computed in a compiled expression (setting the new instance as exprOb.ob, unobserving the previous object, - // and observing the new one), then calling the outerCB - i.e. the handler for the whole compiled expression. - // Initialized exprOb.ob to the current object. - // Uses the contextCb callback to execute the compiled exprOb template in the context of the view/data etc. to get the returned value, typically an object or array. - // If it is an array, registers array binding - var origRt = root; - // Note: For jsviews/issues/292 ctxCb will need var ctxCb = contextCb || function(exprOb, origRt) {return exprOb._cpfn(origRt);}; - - exprOb.ob = contextCb(exprOb, origRt); // Initialize object - - return exprOb.cb = function(ev, eventArgs) { - var obj = exprOb.ob, // The old object - sub = exprOb.sb, - newObj = contextCb(exprOb, origRt); - - if (newObj !== obj) { - if (typeof obj === OBJECT) { - bindArray(obj, true); - if (sub || allowArray && $isArray(obj)) { - innerObserve([obj], sub, callback, contextCb, true); // unobserve on the old object - } - } - exprOb.ob = newObj; - // Put the updated object instance onto the exprOb in the paths array, so subsequent string paths are relative to this object - if (typeof newObj === OBJECT) { - bindArray(newObj); - if (sub || allowArray && $isArray(newObj)) { // observe on new object - innerObserve([newObj], sub, callback, contextCb); - } - } - } - // Call the outerCb - to execute the compiled expression that this computed is part of - callback(ev, eventArgs); - }; - } - - function bindArray(arr, unbind, isArray, relPath) { + function bindArray(cb, arr, unbind, isArray, relPath) { if (allowArray) { // allowArray is 1 if this is a call to observe that does not come from observeAndBind (tag binding), or is from a 'depends' path, // so we allow arrayChange binding. Otherwise allowArray is zero. - var prevObj = object, + var object, prevAllPath = allPath; object = arr; @@ -12789,186 +13073,84 @@ if (!$.observe) { object = $observable._fltr(allPath, object, relPath ? [arr].concat(parentObs) : parentObs, filter); } if (object && (isArray || $isArray(object))) { - observeOnOff(arrayChangeStr + ".observe" + (callback ? getCbKey(callback) : ""), undefined, true, unbind); + observeOnOff(cb, object, undefined, arrayChangeStr + ".observe" + getCbKey(cb), undefined, true, unbind); } - object = prevObj; allPath = prevAllPath; } } - var i, p, skip, parts, prop, path, dep, unobserve, callback, cbId, inId, el, data, events, contextCb, innerContextCb, - items, cbBindings, depth, innerCb, parentObs, allPath, filter, initNsArr, initNsArrLen, view, cbItemCount, - ns = observeStr, - paths = this != 1 // Using != for IE<10 bug- see jsviews/issues/237 - ? concat.apply([], arguments) // Flatten the arguments - this is a 'recursive call' with params using the 'wrapped array' - // style - such as innerObserve([object], path.path, [origRoot], path.prm, innerCb, ...); - : slice.call(arguments), // Don't flatten - this is the first 'top-level call, to innerObserve.apply(1, paths) - lastArg = paths.pop() || false, - root = paths.shift(), - object = root, - l = paths.length; + function observeObjects(paths) { - if (lastArg + "" === lastArg) { // If last arg is a string then this observe call is part of an observeAll call, - allPath = lastArg; // and the last three args are the parentObs array, the filter, and the allPath string. - parentObs = paths.pop(); - filter = paths.pop(); - lastArg = !!paths.pop(); // unobserve - l -= 3; - } - if (lastArg === !!lastArg) { - unobserve = lastArg; - lastArg = paths[l-1]; - lastArg = l && lastArg + "" !== lastArg && (!lastArg || $isFunction(lastArg)) ? (l--, paths.pop()) : undefined; - if (unobserve && !l && $isFunction(root)) { - lastArg = root; - root = undefined; - } - } - callback = lastArg; - if (l && $isFunction(paths[l - 1])) { - innerContextCb = contextCb = callback; - callback = paths.pop(); - l--; - } + function observeObjectPaths(object, pths, callback, contextCb) { - if (unobserve && callback && !callback._cId) { - return; - } + function getInnerCb(exprOb) { + exprOb.ob = contextCb(exprOb, object); // Initialize object + return exprOb.cb = function(ev, eventArgs) { + // The innerCb used for updating a computed in a compiled expression (setting the new instance as exprOb.ob, unobserving the previous object, + // and observing the new one), then calling the outerCB - i.e. the handler for the whole compiled expression. + // Initialized exprOb.ob to the current object. + // Uses the contextCb callback to execute the compiled exprOb template in the context of the view/data etc. to get the returned value, typically an object or array. + // If it is an array, registers array binding + // Note: For jsviews/issues/292 ctxCb will need var ctxCb = contextCb || function(exprOb, origRt) {return exprOb._cpfn(origRt);}; + var obj = exprOb.ob, // The old object + sub = exprOb.sb, + newObj = contextCb(exprOb, object); - // Use a unique namespace (e.g. obs7) associated with each observe() callback to allow unobserve to remove handlers - ns += callback - ? ((inId = callback._inId || ""), unobserve) - ? callback._cId + inId - : (cbId = getCbKey(callback)) + inId - : ""; - - if (cbId && !unobserve) { - cbBindings = cbBindingsStore[cbId] = cbBindingsStore[cbId] || {}; - } + if (newObj !== obj) { + if (typeof obj === OBJECT) { + bindArray(callback, obj, true); + if (sub || allowArray && $isArray(obj)) { + innerObserve([obj], sub, callback, contextCb, true); // unobserve on the old object + } + } + exprOb.ob = newObj; + // Put the updated object instance onto the exprOb in the paths array, so subsequent string paths are relative to this object + if (typeof newObj === OBJECT) { + bindArray(callback, newObj); + if (sub || allowArray && $isArray(newObj)) { // observe on new object + innerObserve([newObj], sub, callback, contextCb); + } + } + } + // Call the outerCb - to execute the compiled expression that this computed is part of + callback(ev, eventArgs); + }; + } - initNsArr = initialNs && initialNs.match(rNotWhite) || [""]; - initNsArrLen = initNsArr.length; + function observePath(object, prts) { // Step through the path parts "this.is^some.path" and observe changes (on the leaf, or down to the bound depth) - while (initNsArrLen--) { - initialNs = initNsArr[initNsArrLen]; - if (root && !paths[0]) { - if ($isArray(root)) { - bindArray(root, unobserve, true); // observe(array, handler) - } - if (unobserve) { - observeOnOff(ns, ""); // unobserve(objectOrArray[, handler]) - } - } - if (unobserve && !l && !root) { // unobserve() - unobserves all - for (p in cbBindingsStore) { - p = cbBindingsStore[p]; - for (data in p) { - object = p[data]; - if ($isArray(object)) { - bindArray(object, unobserve, unobserve); - } else { - observeOnOff(ns, ""); + function obArrAddRemove(ev, eventArgs) { + // If a "[].*" or "[].prop" wild card path (for observing properties of array items) we need to observe or unobserve added or removed items + var l; + if (eventArgs.change === "insert" || (unobserve = eventArgs.change === "remove")) { + l = eventArgs.items.length; + while (l--) { + observePath(eventArgs.items[l], prts.slice()); + } + unobserve = false; } } - } - } - depth = 0; - cbItemCount = 0; - for (i = 0; i < l; i++) { - if (cbItemCount) { - cbItemCount--; // contextCb was moved to a contextual parameter outer context. Needs to revert after cbItemCount - } else { - contextCb = innerContextCb; - } - path = paths[i]; - if (path === "" || path === root) { - continue; - } - if (path && path._ar) { - allowArray += path._ar; // Switch on allowArray for depends paths, and off, afterwards. - continue; - } - object = root; - if ("" + path === path) { - // Consider support for computed paths: jsviews/issues/292 - //if (/[\(\[\+]/.test(path)) { - // var b={links:{}}, t = $sub.tmplFn("{:"+path+"}", b, true), items = t.paths[0]; - // l += items.length - 1; - // splice.apply(paths, [i--, 1].concat(items)); - // continue; - //} - parts = path.split("^"); - if (parts[1]) { - // We bind the leaf, plus additional nodes based on depth. - // "a.b.c^d.e" is depth 2, so listens to changes of e, plus changes of d and of c - depth = parts[0].split(".").length; - path = parts.join("."); - depth = path.split(".").length - depth; - // if more than one ^ in the path, the first one determines depth + //prts = $isArray(prts) ? prts : [prts]; + if (callback) { + obArrAddRemove._cId = getCbKey(callback); // Identify wrapped callback with unwrapped callback, so unobserveAll will + // remove previous observeAll wrapped callback, if inner callback was the same; } - if (contextCb) { - items = contextCb(path, root, depth); - } - contextCb = innerContextCb; - parts = path.split("."); - } else if (path && path._cxp) { // contextual parameter - view = path.shift(); // Contextual data - if (_ocp in view) { - root = view; // observable contextual parameter - contextCb = 0; - } else { - contextCb = $sub._gccb(view); // getContextCb: Get context callback for the contextual view (where contextual param evaluated/assigned) - root = view.data; - } - items = path; - items.push(origRoot); - cbItemCount = items.length; - } else { - if (!$isFunction(path)) { - if (path && path._cpfn) { - // Path is an exprOb returned by a computed property - helper/data function (compiled expr function). - // Set current object on exprOb.ob, and get innerCb for updating the object - innerCb = unobserve ? path.cb : getInnerCb(path); - // innerCb._ctx = callback._ctx; Could pass context (e.g. linkCtx) for use in a depends = function() {} call, so depends is different for different linkCtx's - innerCb._cId = callback._cId; - // Set the same cbBindingsStore key as for callback, so when callback is disposed, disposal of innerCb happens too. - innerCb._inId = innerCb._inId || ".obIn" + observeInnerCbKey++; - if (path.bnd || path.prm && path.prm.length || !path.sb) { - // If the exprOb is bound e.g. foo()^sub.path, or has parameters e.g. foo(bar) or is a leaf object (so no sub path) e.g. foo() - // then observe changes on the object, or its parameters and sub-path - innerObserve([object], path.path, [path.root||root], path.prm, innerCb, contextCb, unobserve); - } - if (path.sb) { // subPath - if (path.sb.prm) { - path.sb.root = root; - } - innerObserve([path.ob], path.sb, callback, contextCb, unobserve); - } - path = origRoot; - object = undefined; - } + + var arrIndex, skip, dep, obArr, prt, + obj = object; + if (object && object._cxp) { + return observeObjectPaths(object[0], [object[1]], callback, contextCb); } - parts = [root = path]; - } - if (items) { - // If the array of objects and paths returned by contextCb is non empty, insert them - // into the sequence, replacing the current item (path). Otherwise simply remove current item (path) - l += items.length - 1; - splice.apply(paths, [i--, 1].concat(items)); - items = undefined; - continue; - } - while (object && (prop = parts.shift()) !== undefined) { - if (typeof object === OBJECT) { - if ("" + prop === prop) { + + while ((prop = prts.shift()) !== undefined) { + if (obj && typeof obj === OBJECT && "" + prop === prop) { if (prop === "") { continue; } - if ((parts.length < depth + 1) && !object.nodeType) { + if ((prts.length < depth + 1) && !obj.nodeType) { // Add observer for each token in path starting at depth, and on to the leaf - if (!unobserve && (events = $hasData(object) && $._data(object))) { - events = events.events; + if (!unobserve && (events = $._data(obj).events)) { events = events && events[propertyChangeStr]; el = events && events.length; skip = 0; @@ -12979,61 +13161,255 @@ if (!$.observe) { && data.cb._cId === callback._cId && data.cb._inId === callback._inId && (data.prop === prop || data.prop === "*" || data.prop === "**")) { - if (p = parts.join(".")) { - data.paths.push(p); // We will skip this binding, but if it is not a leaf binding, - // need to keep bindings for rest of path, ready for if the object gets swapped. + if (prt = prts.join(".")) { + data.paths.push(prt); // We will skip this binding, but if it is not a leaf binding, + // need to keep bindings for rest of path, ready for if the obj gets swapped. } skip++; } } if (skip) { // Duplicate binding(s) found, so move on - object = object[prop]; + obj = obj[prop]; continue; } } if (prop === "*" || prop === "**") { // "*" => all properties. "**" => all properties and sub-properties (i.e. deep observeAll behavior) if (!unobserve && events && events.length) { // Remove existing bindings, since they will be duplicates with "*" or "**" - observeOnOff(ns, "", false, true); + observeOnOff(callback, obj, path, ns, "", false, true); } if (prop === "*") { - observeOnOff(ns, ""); // observe the object for any property change - for (p in object) { + observeOnOff(callback, obj, path, ns, ""); // observe the object for any property change + for (prt in obj) { // observing "*": So (in addition to listening to prop change, above) listen to arraychange on props of type array - if (p !== $expando) { - bindArray(object, unobserve, undefined, p); + if (prt !== $expando) { + bindArray(callback, obj, unobserve, undefined, prt); } } } else { - $.observable(initialNs, object)[(unobserve ? "un" : "") + "observeAll"](callback); // observe or unobserve the object for any property change + $.observable(initialNs, obj)[(unobserve ? "un" : "") + "observeAll"](callback); // observe or unobserve the object for any property change } break; + } else if (prop == "[]") { // "[].*" or "[].prop" wild card path, for observing properties of array items + if ($isArray(obj)) { + if (unobserve) { + observeOnOff(callback, obj, path, arrayChangeStr + getCbKey(callback), undefined, unobserve, unobserve); + } else { + $observe(initialNs, obj, obArrAddRemove, unobserve); // observe or unobserve added or removed items + } + } } else if (prop) { - observeOnOff(ns + ".p_" + prop, parts.join("^")); // By using "^" rather than "." we ensure that deep binding will be used on newly inserted object graphs + observeOnOff(callback, obj, path, ns + ".p_" + prop, prts.join("^")); // By using "^" rather than "." we ensure that deep binding will be used on newly inserted object graphs } } if (allPath) { allPath += "." + prop; } - prop = object[prop]; - if (!parts[0]) { - bindArray(prop, unobserve); // [un]observe(object, "arrayProperty") observes array changes on property of type array + if (prop === "[]") { + if ($isArray(obj)) { + obArr = obj; + arrIndex = obj.length; + } + while (arrIndex--) { + obj = obArr[arrIndex]; + observePath(obj, prts.slice()); + } + return; + } + prop = obj[prop]; + if (!prts[0]) { + bindArray(callback, prop, unobserve); // [un]observe(object, "arrayProperty") observes array changes on property of type array } } if ($isFunction(prop)) { if (dep = prop.depends) { // This is a computed observable. We will observe any declared dependencies. - // Pass {_ar: ...} objects to switch on allowArray, for depends paths, then return to contextual allowArray value - innerObserve([object], dependsPaths(dep, object, callback), callback, contextCb, unobserve); + if (obj._vw && obj._ocp) { + // Observable contextual parameter, so context was ocp object. Now move context to view.data for dependencies + obj = obj._vw; // storeView or tag (scope of contextual parameter) + if (obj._tgId) { + // Is a tag, so get view + obj = obj.tagCtx.view; + } + obj = obj.data; // view.data + } + observeObjects(concat.apply([], [[obj], dependsPaths(dep, obj, callback)])); } break; } - object = prop; + obj = prop; } } + + var i, path, + depth = 0, + l = pths.length; + if (object && !contextCb && ((view = object._is === "view") || object._is === "tag")) { + contextCb = $sub._gccb(view ? object : object.tagCtx.contentView); + if (callback && !unobserve) { + (function() { + var ob = object, + cb = callback; + callback = function(ev, eventArgs) { + // Wrapped callback so this pointer is tag or view + cb.call(ob, ev, eventArgs); + }; + callback._cId = cb._cId; + callback._inId = cb._inId; + })(); + } + object = view ? object.data : object; + } + if (!pths[0]) { + if ($isArray(object)) { + bindArray(callback, object, unobserve, true); // observe(array, handler) + } else if (unobserve) { + observeOnOff(callback, object, undefined, ns, ""); // unobserve(objectOrArray[, handler]) + } + } + for (i = 0; i < l; i++) { // Step through objects and paths + path = pths[i]; + if (path === "") { + continue; + } + if (path && path._ar) { + allowArray += path._ar; // Switch on allowArray for depends paths, and off, afterwards. + continue; + } + if ("" + path === path) { + parts = path.split("^"); + if (parts[1]) { + // We bind the leaf, plus additional nodes based on depth. + // "a.b.c^d.e" is depth 2, so listens to changes of e, plus changes of d and of c + depth = parts[0].split(".").length; + path = parts.join("."); + depth = path.split(".").length - depth; + // if more than one ^ in the path, the first one determines depth + } + if (contextCb && (items = contextCb(path, object, depth))) { + //object, paths + if (items.length) { + var ob = items[0], + pth = items[1]; + if (ob && ob._cxp) { // contextual parameter + pth = ob[1]; + ob = ob[0]; + if (ob._is === "view") { + observeObjectPaths(ob, [pth], callback); // Setting contextCb to undefined, to use passed in view for new contextCb + continue; + } + } + if (pth + "" === pth) { + observePath(ob, pth.split(".")); + } else { + observeObjectPaths(items.shift(), items, callback, contextCb); + } + } + } else { + observePath(object, path.split(".")); + } + } else if (!$isFunction(path) && path && path._cpfn) { + // Path is an exprOb returned by a computed property - helper/data function (compiled expr function). + // Get innerCb for updating the object + innerCb = unobserve ? path.cb : getInnerCb(path); + // innerCb._ctx = callback._ctx; Could pass context (e.g. linkCtx) for use in a depends = function() {} call, so depends is different for different linkCtx's + innerCb._cId = callback._cId; + // Set the same cbBindingsStore key as for callback, so when callback is disposed, disposal of innerCb happens too. + innerCb._inId = innerCb._inId || ".obIn" + observeInnerCbKey++; + if (path.bnd || path.prm && path.prm.length || !path.sb) { + // If the exprOb is bound e.g. foo()^sub.path, or has parameters e.g. foo(bar) or is a leaf object (so no sub path) e.g. foo() + // then observe changes on the object, or its parameters and sub-path + innerObserve([object], path.path, (path.prm.length ? [path.root||object] : []), path.prm, innerCb, contextCb, unobserve); + } + if (path.sb) { // Has a subPath + // Observe changes on the sub-path + if (path.sb.prm) { + path.sb.root = object; + } + // Set current object on exprOb.ob + observeObjectPaths(path.ob, [path.sb], callback, contextCb); + } + } + } + } + + var pth, + pths = [], // Array of paths for current object + l = paths.length; + while (l--) { // Step backwards through paths and objects + pth = paths[l]; + if (pth + "" === pth || pth && (pth._ar || pth._cpfn)) { + pths.unshift(pth); // This is a path so add to arr + } else { // This is an object + observeObjectPaths(pth, pths, callback, contextCb); + pths = []; // New array for next object + } + } + } + + var ns = observeStr, + paths = this != 1 // Using != for IE<10 bug- see jsviews/issues/237 + ? concat.apply([], arguments) // Flatten the arguments - this is a 'recursive call' with params using the 'wrapped array' + // style - such as innerObserve([object], path.path, [origRoot], path.prm, innerCb, ...); + : slice.call(arguments), // Don't flatten - this is the first 'top-level call, to innerObserve.apply(1, paths) + lastArg = paths.pop() || false, + m = paths.length; + +//END OF FUNCTIONS + if (lastArg + "" === lastArg) { // If last arg is a string then this observe call is part of an observeAll call, + allPath = lastArg; // and the last three args are the parentObs array, the filter, and the allPath string. + parentObs = paths.pop(); + filter = paths.pop(); + lastArg = !!paths.pop(); // unobserve + m -= 3; + } + if (lastArg === !!lastArg) { + unobserve = lastArg; + lastArg = paths[m-1]; + lastArg = m && lastArg + "" !== lastArg && (!lastArg || $isFunction(lastArg)) ? (m--, paths.pop()) : undefined; + if (unobserve && !m && $isFunction(paths[0])) { + lastArg = paths.shift(); } } + callback = lastArg; + if (m && $isFunction(paths[m - 1])) { + contextCb = callback; + lastArg = callback = paths.pop(); + m--; + } + + if (unobserve && callback && !callback._cId) { + return; + } + + // Use a unique namespace (e.g. obs7) associated with each observe() callback to allow unobserve to remove handlers + ns += callback + ? ((inId = callback._inId || ""), unobserve) + ? callback._cId + inId + : (cbId = getCbKey(callback)) + inId + : ""; + + if (cbId && !unobserve) { + cbBindings = cbBindingsStore[cbId] = cbBindingsStore[cbId] || {}; + } + + initNsArr = initialNs && initialNs.match(rNotWhite) || [""]; + initNsArrLen = initNsArr.length; + + while (initNsArrLen--) { // Step through multiple white-space separated namespaces if there are any + initialNs = initNsArr[initNsArrLen]; + if (unobserve && arguments.length < 3) { + if (callback) { + unobserveBinding(callback, cbBindingsStore[callback._cId]); // unobserve(handler) - unobserves this handler, all objects + } else if (!paths[0]) { + for (p in cbBindingsStore) { + unobserveBinding(callback, cbBindingsStore[p]); // unobserve() - unobserves all + } + } + } + observeObjects(paths); + } if (cbId) { removeCbBindings(cbBindings, cbId); } @@ -13047,12 +13423,11 @@ if (!$.observe) { // arrayChange events in this scenario. Instead, {^{for}} and similar do specific arrayChange binding to the tagCtx.args[0] value, in onAfterLink. // Note deliberately using this == 1, rather than this === 1 because of IE<10 bug- see jsviews/issues/237 paths = slice.call(arguments), - origRoot = paths[0]; + pth = paths[0]; - if (origRoot + "" === origRoot && allowArray) { - initialNs = origRoot; // The first arg is a namespace, since it is a string, and this call is not from observeAndBind + if (pth + "" === pth) { + initialNs = pth; // The first arg is a namespace, since it is a string paths.shift(); - origRoot = paths[0]; } return innerObserve.apply(1, paths); }; @@ -13070,10 +13445,10 @@ if (!$.observe) { //========================== Initialize ========================== $.observable = $observable; - $observable._fltr = function(allPath, object, parentObs, filter) { + $observable._fltr = function(path, object, parentObs, filter) { if (filter && $isFunction(filter) - ? filter(allPath, object, parentObs) - : true // TODO Consider supporting filter being a string or strings to do RegEx filtering based on key and/or allPath + ? filter(path, object, parentObs) + : true // TODO Consider supporting filter being a string or strings to do RegEx filtering based on key and/or path ) { object = $isFunction(object) ? object.set && object.call(parentObs[0]) // It is a getter/setter @@ -13101,7 +13476,7 @@ if (!$.observe) { setProperty: function(path, value, nonStrict) { path = path || ""; var key, pair, parts, - multi = path + "" !== path && !path._is, // Hash of paths, not view object + multi = path + "" !== path, // Hash of paths self = this, object = self._data; @@ -13124,13 +13499,9 @@ if (!$.observe) { } } else if (path !== $expando) { // Simple single property case. - if (path._is) { - parts = [path]; - } else { - parts = path.split(/[.^]/); - while (object && parts.length > 1) { - object = object[parts.shift()]; - } + parts = path.split(/[.^]/); + while (object && parts.length > 1) { + object = object[parts.shift()]; } if (object) { self._setProperty(object, parts[0], value, nonStrict); @@ -13146,20 +13517,18 @@ if (!$.observe) { }, _setProperty: function(leaf, path, value, nonStrict) { - var setter, getter, removeProp, + var setter, getter, removeProp, eventArgs, view, property = path ? leaf[path] : leaf; - if ($isFunction(property)) { - if (property.set) { - // Case of property setter/getter - with convention that property is getter and property.set is setter - leaf = leaf._vw // Case of JsViews 2-way data-linking to an observable context parameter, with a setter. - // The view will be the this pointer for getter and setter. Note: this is the one scenario where path is "". - || leaf; - getter = property; - setter = getter.set === true ? getter : getter.set; - property = getter.call(leaf); // get - only treated as getter if also a setter. Otherwise it is simply a property of type function. - // See unit tests 'Can observe properties of type function'. - } + if ($isFunction(property) && property.set) { + // Case of property setter/getter - with convention that property is getter and property.set is setter + view = leaf._vw // Case of JsViews 2-way data-linking to an observable context parameter, with a setter. + // The view will be the this pointer for getter and setter. Note: this is the one scenario where path is "". + || leaf; + getter = property; + setter = getter.set === true ? getter : getter.set; + property = getter.call(view); // get - only treated as getter if also a setter. Otherwise it is simply a property of type function. + // See unit tests 'Can observe properties of type function'. } if ((property !== value || nonStrict && property != value) @@ -13167,8 +13536,8 @@ if (!$.observe) { // Date objects don't support != comparison. Treat as special case. && (!(property instanceof Date && value instanceof Date) || property > value || property < value)) { if (setter) { - setter.call(leaf, value); //set - value = getter.call(leaf); //get updated value + setter.call(view, value); // set + value = getter.call(view); // get updated value } else if (removeProp = value === remove) { if (property !== undefined) { delete leaf[path]; @@ -13180,7 +13549,11 @@ if (!$.observe) { leaf[path] = value; } if (path) { - this._trigger(leaf, {change: "set", path: path, value: value, oldValue: property, remove: removeProp}); + eventArgs = {change: "set", path: path, value: value, oldValue: property, remove: removeProp}; + if (leaf._ocp) { + eventArgs.ctxPrm = leaf._key; + } + this._trigger(leaf, eventArgs); } } }, @@ -13334,7 +13707,9 @@ if (!$.observe) { self.remove(j, dataLength - j); } self._srt = undefined; // We have finished sort operations during refresh - self._trigger({change: "refresh", oldItems: oldItems}, oldLength); + if (oldLength || newLength) { + self._trigger({change: "refresh", oldItems: oldItems}, oldLength); + } return self; }, @@ -13379,19 +13754,31 @@ if (!$.observe) { } }; +//========================== +// dataMap with data-linking +//========================== + $views.map = function(mapDef) { - function Map(source, options, target, unbound) { - var changing, + function Map(source, options, oldMapOrTarget, unbound) { + var changing, updatedMap, map = this; - if (this.src) { - this.unmap(); // We are re-mapping a new source + if (map.src) { + map.unmap(); // We are re-mapping a new source + } + if (options) { + options.map = map; } - if (typeof source === OBJECT) { + if (typeof source === OBJECT || $isFunction(source)) { map.src = source; - map.tgt = target || map.tgt || []; + if (oldMapOrTarget) { + map.tgt = oldMapOrTarget.tgt || oldMapOrTarget; // Can provide an existing map, or a target array to be used on new map + } else { + map.tgt = map.tgt || []; + } map.options = options || map.options; - map.update(); - if (!unbound) { + if (updatedMap = map.update()) { + map = updatedMap; // If updating returns another map, then we can replace this one (so no need to bind it) + } else if (!unbound) { if (mapDef.obsSrc) { $observable(map.src).observeAll(map.obs = function(ev, eventArgs) { if (!changing) { @@ -13403,11 +13790,11 @@ if (!$.observe) { } if (mapDef.obsTgt) { $observable(map.tgt).observeAll(map.obt = function(ev, eventArgs) { - if (!changing) { - changing = true; - mapDef.obsTgt(map, ev, eventArgs); - changing = undefined; - } + if (!changing && !map.tgt._updt) { + changing = true; + mapDef.obsTgt(map, ev, eventArgs); + changing = undefined; + } }, map.tgtFlt); } } @@ -13425,28 +13812,37 @@ if (!$.observe) { mapDef = $.extend({}, mapDef.baseMap, mapDef); } - mapDef.map = function(source, options, target, unbound) { - return new Map(source, options, target, unbound); + mapDef.map = function(source, options, oldMap, unbound) { + return new Map(source, options, oldMap, unbound); }; (Map.prototype = { srcFlt: mapDef.srcFlt || shallowFilter, // default to shallowFilter tgtFlt: mapDef.tgtFlt || shallowFilter, update: function(options) { - var map = this; - $observable(map.tgt).refresh(mapDef.getTgt(map.src, map.options = options || map.options)); + var oldMap, newMap, + map = this, + tgt = map.tgt; + if (!tgt._updt) { + tgt._updt = true; + oldMap = map.options && map.options.map; + $observable(tgt).refresh(mapDef.getTgt(map.src, map.options = options || map.options)); + tgt._updt = false; + newMap = map.options && map.options.map; + if (newMap && oldMap !== newMap) { + return newMap; + } + } }, unmap: function() { var map = this; - if (map.src) { - if (map.obs) { - $observable(map.src).unobserveAll(map.obs, map.srcFlt); - } - if (map.obt) { - $observable(map.tgt).unobserveAll(map.obt, map.tgtFlt); - } - map.src = undefined; + if (map.src && map.obs) { + $observable(map.src).unobserveAll(map.obs, map.srcFlt); } + if (map.tgt && map.obt) { + $observable(map.tgt).unobserveAll(map.obt, map.tgtFlt); + } + map.src = undefined; }, map: Map, _def: mapDef @@ -13464,13 +13860,15 @@ if (!$.observe) { : undefined; // In IE8 cannot do delete global._jsv }; $sub._dp = dependsPaths; + $sub._gck = getCbKey; + $sub._obs = $observe; } return $; }, window)); },{}],4:[function(require,module,exports){ -/*! jquery.views.js v0.9.90 (Beta): http://jsviews.com/ */ +/*! jquery.views.js v0.9.91 (Beta): http://jsviews.com/ */ /* * Interactive data-driven views using JsRender templates. * Subcomponent of JsViews @@ -13479,7 +13877,7 @@ return $; * Also requires jquery.observable.js * See JsObservable at http://jsviews.com/#download and http://github.com/BorisMoore/jsviews * - * Copyright 2017, Boris Moore + * Copyright 2018, Boris Moore * Released under the MIT License. */ @@ -13516,7 +13914,7 @@ var setGlobals = $ === false; // Only set globals if script block in browser (no jsr = jsr || setGlobals && global.jsrender; $ = $ || global.jQuery; -var versionNumber = "v0.9.90", +var versionNumber = "v0.9.91", requiresStr = "JsViews requires "; if (!$ || !$.fn) { @@ -13532,9 +13930,9 @@ var $observe, $observable, $isArray = $.isArray, $views = $.views; -if (!$views || !$views.map) { +if (!$views || !$views.map || $views.jsviews !== versionNumber) { // JsRender is not loaded. - throw requiresStr + "JsRender"; // jsrender.js must be loaded before JsViews and after jQuery + throw requiresStr + "JsRender " + versionNumber; // jsrender.js must be loaded before JsViews and after jQuery } var document = global.document, @@ -13563,7 +13961,10 @@ var document = global.document, if ($.link) { return $; } // JsViews is already loaded $subSettings.trigger = true; + var activeBody, rTagDatalink, $view, $viewsLinkAttr, linkViewsSel, wrapMap, viewStore, oldAdvSet, useInput, + isIE = window.navigator.userAgent, + TEXTCONTENT = document.textContent !== undefined ? "textContent" : "innerText", jsvAttrStr = "data-jsv", elementChangeStr = "change.jsv", onBeforeChangeStr = "onBeforeChange", @@ -13606,10 +14007,15 @@ var activeBody, rTagDatalink, $view, $viewsLinkAttr, linkViewsSel, wrapMap, view rOpenViewMarkers = /(#)()(\d+)(_)/g, rOpenMarkers = /(#)()(\d+)([_^])/g, rViewMarkers = /(?:(#)|(\/))(\d+)(_)/g, + rTagMarkers = /(?:(#)|(\/))(\d+)(\^)/g, rOpenTagMarkers = /(#)()(\d+)(\^)/g, rMarkerTokens = /(?:(#)|(\/))(\d+)([_^])([-+@\d]+)?/g, rSplitBindings = /&(\d+)\+?/g, - getComputedStyle = global.getComputedStyle; + rShallowArrayPath = /^[^.]*$/, // No '.' in path + getComputedStyle = global.getComputedStyle, + $inArray = $.inArray; + +isIE = isIE.indexOf('MSIE ')>0 || isIE.indexOf('Trident/')>0; $observable = $.observable; @@ -13627,115 +14033,117 @@ $observe = $observable.observe; //=============== function updateValues(sourceValues, tagElse, bindId, ev) { -// Observably update data values targeted by bindTo -// Called when linkedElem changes: called as updateValues(sourceValues, tagElse, bindId, ev) - this: undefined +// Observably update a data value targeted by the binding.to binding of a 2way data-link binding. Called when elem changes +// Called when linkedElem of a tag control changes: as updateValue(val, index, tagElse, bindId, ev) - this: undefined // Called directly as tag.updateValues(val1, val2, val3, ...) - this: tag var linkCtx, cvtBack, cnvtName, target, view, binding, sourceValue, origVals, sourceElem, sourceEl, - oldLinkCtx, tos, to, tcpTag, exprOb, contextCb, l, tag; + oldLinkCtx, tos, to, tcpTag, exprOb, contextCb, l, m, tag; if (bindId && bindId._tgId) { tag = bindId; bindId = tag._tgId; - } - if (binding = bindingStore[bindId]) { - if (tos = binding.to) { - tos = tos[tagElse||0]; - // The binding has a 'to' field, which is of the form [tosForElse0, tosForElse1, ...] - // where tosForElseX is of the form [[[targetObject, toPath], [targetObject, toPath], ...], cvtBack] - linkCtx = binding.linkCtx; - sourceElem = linkCtx.elem; - view = linkCtx.view; - tag = linkCtx.tag; - if (!tag && tos._cxp) { - tag = tos._cxp.path !== _ocp && tos._cxp.tag; - sourceValue = sourceValues[0]; - sourceValues = []; - sourceValues[tos._cxp.ind] = sourceValue; - } + if (!tag.bindTo) { + defineBindToDataTargets(bindingStore[bindId], tag); // If this tag is updating for the first time, we need to create the 'to' bindings first + tag.bindTo = [0]; + } + } + if ((binding = bindingStore[bindId]) && (tos = binding.to)) { + tos = tos[tagElse||0]; + // The binding has a 'to' field, which is of the form [tosForElse0, tosForElse1, ...] + // where tosForElseX is of the form [[[targetObject, toPath], [targetObject, toPath], ...], cvtBack] + linkCtx = binding.linkCtx; + sourceElem = linkCtx.elem; + view = linkCtx.view; + tag = linkCtx.tag; + if (!tag && tos._cxp) { + tag = tos._cxp.path !== _ocp && tos._cxp.tag; + sourceValue = sourceValues[0]; + sourceValues = []; + sourceValues[tos._cxp.ind] = sourceValue; + } - if (tag) { - tag._.chg = 1; // Set 'changing' marker to prevent tag update from updating itself - if (cnvtName = tag.convertBack) { - if ($isFunction(cnvtName)) { - cvtBack = cnvtName; - } else { - cvtBack = view.getRsc("converters", cnvtName); - } + if (tag) { + tag._.chg = 1; // Set 'changing' marker to prevent tag update from updating itself + if (cnvtName = tag.convertBack) { + if ($isFunction(cnvtName)) { + cvtBack = cnvtName; + } else { + cvtBack = view.getRsc("converters", cnvtName); } } + } - if (sourceElem.nodeName === "SELECT") { - // data-link to string or (multiselect) array of strings + if (sourceElem.multiple && sourceValues[0] === null) { + // Case where sourceValues was undefined, and set to [null] by $source[setter]() above + sourceValues = [[]]; } - origVals = sourceValues; - if (cvtBack) { - sourceValues = cvtBack.apply(tag, sourceValues); - if (sourceValues === undefined) { - tos = []; // If cvtBack does not return anything, do not update target. - //(But cvtBack may be designed to modify observable values from code as a side effect) - } - sourceValues = $isArray(sourceValues) ? sourceValues : [sourceValues]; - // If there are multiple tos (e.g. multiple args on data-linked input) then cvtBack can update not only - // the first arg, but all of them by returning an array. + sourceElem._jsvSel = sourceValues; + } + origVals = sourceValues; + if (cvtBack) { + sourceValues = cvtBack.apply(tag, sourceValues); + if (sourceValues === undefined) { + tos = []; // If cvtBack does not return anything, do not update target. + //(But cvtBack may be designed to modify observable values from code as a side effect) } + sourceValues = $isArray(sourceValues) ? sourceValues : [sourceValues]; + // If there are multiple tos (e.g. multiple args on data-linked input) then cvtBack can update not only + // the first arg, but all of them by returning an array. + } - // Set linkCtx on view, dynamically, just during this handler call - oldLinkCtx = view.linkCtx; - view.linkCtx = linkCtx; - l = tos.length; - while (l--) { - if (to = tos[l]) { - to = to + "" === to ? [linkCtx.data, to] : to; // [object, path] - target = to[0]; - tcpTag = to.tag; // If this is a tag contextual parameter - the owner tag - sourceValue = (to[1] === _ocp - ? origVals // If to target is for tag contextual parameter set to static expression (or uninitialized) - we are - // binding to tag.ctx.foo._ocp - and we use original values, without applying cvtBack converter - : sourceValues // Otherwise use the converted value - )[l]; - if (sourceValue !== undefined && (!tag || !tag.onBeforeUpdateVal || tag.onBeforeUpdateVal(ev, { - change: "change", - data: target, - path: to[1], - index: l, - tagElse: tagElse, - value: sourceValue - }) !== false)) { - if (tcpTag) { // We are modifying a tag contextual parameter ~foo (e.g. from within block) so update 'owner' tag: tcpTag - tcpTag.updateValue(sourceValue, to.ind, to.tagElse, undefined, ev); - if (tcpTag.setValue) { - tcpTag.setValue(sourceValue, to.ind, to.tagElse); + // Set linkCtx on view, dynamically, just during this handler call + oldLinkCtx = view.linkCtx; + view.linkCtx = linkCtx; + l = tos.length; + while (l--) { + if (to = tos[l]) { + to = to + "" === to ? [linkCtx.data, to] : to; // [object, path] + target = to[0]; + tcpTag = to.tag; // If this is a tag contextual parameter - the owner tag + sourceValue = (target && target._ocp && !target._vw + ? origVals // If to target is for tag contextual parameter set to static expression (or uninitialized) - we are + // binding to tag.ctx.foo._ocp - and we use original values, without applying cvtBack converter + : sourceValues // Otherwise use the converted value + )[l]; + if (sourceValue !== undefined && (!tag || !tag.onBeforeUpdateVal || tag.onBeforeUpdateVal(ev, { + change: "change", + data: target, + path: to[1], + index: l, + tagElse: tagElse, + value: sourceValue + }) !== false)) { + if (tcpTag) { // We are modifying a tag contextual parameter ~foo (e.g. from within block) so update 'owner' tag: tcpTag + if ((m = tcpTag._.toIndex[to.ind]) !== undefined) { + tcpTag.updateValue(sourceValue, m, to.tagElse, undefined, undefined, ev); // if doesn't map, don't update, or update scoped tagCtxPrm. But should initialize from outer from binding... + } + tcpTag.setValue(sourceValue, to.ind, to.tagElse); + } else if (sourceValue !== undefined && target) { + if ((tcpTag = ev && (sourceEl = ev.target)._jsvInd === l && sourceEl._jsvLkEl) && (m = tcpTag._.fromIndex[l]) !== undefined) { + // The source is a tag linkedElem (linkedElement: [..., "elemSelector", ...], which is updating + tcpTag.setValue(origVals[l], m, sourceEl._jsvElse); + } + if (target._cpfn) { + contextCb = linkCtx._ctxCb; // This is the exprOb for a computed property + exprOb = target; + target = linkCtx.data; + if (exprOb._cpCtx) { // Computed value for a contextual parameter + target = exprOb.data; // The data for the contextual view (where contextual param expression evaluated/assigned) + contextCb = exprOb._cpCtx; // Context callback for contextual view } - } else if (sourceValue !== undefined && target) { - if (tcpTag = ev && (sourceEl = ev.target)._jsvInd === l && sourceEl._jsvLkEl) { - // The source is a tag linkedElem (linkedElement: [..., "elemSelector", ...], which is updating - tcpTag.setValue(origVals[l], l, sourceEl._jsvElse); + while (exprOb && exprOb.sb) { // Step through chained computed values to leaf one... + target = contextCb(exprOb, target); + exprOb = exprOb.sb; } - if (target._cpfn) { - contextCb = linkCtx._ctxCb; // This is the exprOb for a computed property - exprOb = target; - target = linkCtx.data; - if (exprOb._cpCtx) { // Computed value for a contextual parameter - target = exprOb.data; // The data for the contextual view (where contextual param expression evaluated/assigned) - contextCb = exprOb._cpCtx; // Context callback for contextual view - } - while (exprOb && exprOb.sb) { // Step through chained computed values to leaf one... - target = contextCb(exprOb, target); - exprOb = exprOb.sb; - } - } - $observable(target).setProperty(to[1], sourceValue); // 2way binding change event - observably updating bound object } + $observable(target).setProperty(to[1], sourceValue); // 2way binding change event - observably updating bound object } } } - view.linkCtx = oldLinkCtx; } + view.linkCtx = oldLinkCtx; } if (tag) { tag._.chg = undefined; // Clear marker @@ -13761,7 +14169,7 @@ function onElemChange(ev) { rSplitBindings.lastIndex = 0; // Ensure starts at zero while (bindId = rSplitBindings.exec(ev.target._jsvBnd)) { // _jsvBnd is a string with the syntax: "&bindingId1&bindingId2" - updateValue(val, source._jsvInd, source._jsvElse, bindId[1], ev); + updateValue(val, source._jsvInd, source._jsvElse, undefined, bindId[1], ev); } ev.target._jsvChg = undefined; // Clear marker } @@ -13809,7 +14217,7 @@ function onDataLinkedTagChange(ev, eventArgs) { // For data-link="{:xxx}" with no cvt or cvtBk returns value. Otherwise returns tagCtxs } // Compiled link expression for linkTag: return value for data-link="{:xxx}" with no cvt or cvtBk, otherwise tagCtx or tagCtxs - attr = tag && tag.attr || linkCtx.attr || defaultAttr(target, true, cvt !== undefined); + attr = tag && tag.attr || linkCtx.attr || (linkCtx._dfAt = defaultAttr(target, true, cvt !== undefined)); if (attr === VALUE && (tag && tag.parentElem || linkCtx.elem).type === CHECKBOX) { attr = CHECKED; } @@ -13837,6 +14245,9 @@ function onDataLinkedTagChange(ev, eventArgs) { if (eventArgs && (onEvent = changeHandler(view, onAfterChangeStr, tag))) { onEvent.call(tag || linkCtx, ev, eventArgs); } + if (tag.tagCtx.props.dataMap) { + tag.tagCtx.props.dataMap.map(tag.tagCtx.args[0], tag.tagCtx, tag.tagCtx.map, !tag._.bnd); + } return; } @@ -13858,7 +14269,7 @@ function onDataLinkedTagChange(ev, eventArgs) { addLinkMethods(tag = linkCtx.tag); // In both convertVal and renderTag we have instantiated a tag attr = linkCtx.attr || attr; // linkCtx.attr may have been set to tag.attr during tag instantiation in renderTag } - if (bindEarly = tag && !tag.inline && tag.template) { + if (bindEarly = tag && (!tag.inline || linkCtx.fn._lr) && tag.template) { // Data-linked tags with templated contents need to be data-linked before their contents, so that observable updates // will trigger the parent tags before the child tags. observeAndBind(linkCtx, source, target); @@ -13876,6 +14287,12 @@ function onDataLinkedTagChange(ev, eventArgs) { observeAndBind(linkCtx, source, target); } + if (tag && tag._.ths) { + // Tag has a this=expr binding for which we have created an additional 'to' (defineBindToDataTargets) target (at index bindTo.length) + // We now have the this pointer, so we push it to the binding, using updateValue(index) + tag.updateValue(tag, tag.bindTo ? tag.bindTo.length : 1); // If bindTo not defined yet, it will be [0], so length 1 + } + if (eventArgs && (onEvent = changeHandler(view, onAfterChangeStr, tag))) { onEvent.call(tag || linkCtx, ev, eventArgs); } @@ -13894,7 +14311,7 @@ function updateContent(sourceValue, linkCtx, attr, tag) { // When called (in onDataLinkedTagChange) for target HTML returns true // When called (in onDataLinkedTagChange) for other targets returns boolean for "changed" var setter, prevNode, nextNode, late, nodesToRemove, useProp, tokens, id, openIndex, closeIndex, testElem, nodeName, cStyle, jsvSel, - renders = attr !== NONE && sourceValue !== undefined && !linkCtx._noUpd && !((attr === VALUE || attr === HTML) && (tag ? tag._.chg : linkCtx.elem._jsvChg)), + renders = attr !== NONE && sourceValue !== undefined && !linkCtx._noUpd && !((attr === VALUE || attr === HTML) && (!tag && linkCtx.elem._jsvChg)), // For data-link="^{...}", don't update the first time (no initial render) - e.g. to leave server rendered values. source = linkCtx.data, target = tag && tag.parentElem || linkCtx.elem, @@ -13996,20 +14413,29 @@ function updateContent(sourceValue, linkCtx, attr, tag) { if (tag && tag.inline) { nodesToRemove = tag.nodes(true); if (tag._elCnt) { - if (prevNode && prevNode !== nextNode) { + if (prevNode && prevNode !== nextNode) { // nextNode !== prevNode // This prevNode will be removed from the DOM, so transfer the view tokens on prevNode to nextNode of this 'viewToRefresh' transferViewTokens(prevNode, nextNode, target, tag._tgId, "^", true); - } else if (tokens = target._df) { // This occurs when there is no nextNode, and so the target._df may include tokens referencing - // view and tag bindings contained within the open and close tokens of the updated tag control. They need to be processed (disposed) + } else { + // nextNode === prevNode, or there is no nextNode and so the target._df may have tokens + tokens = prevNode ? prevNode.getAttribute(jsvAttrStr) : target._df; id = tag._tgId + "^"; openIndex = tokens.indexOf("#" + id) + 1; closeIndex = tokens.indexOf("/" + id); if (openIndex && closeIndex > 0) { + // If prevNode, or target._df, include tokens referencing view and tag bindings contained within the open and close tokens + // of the updated tag control, they need to be processed (disposed) openIndex += id.length; if (closeIndex > openIndex) { - setDefer(target, tokens.slice(0, openIndex) + tokens.slice(closeIndex)); - disposeTokens(tokens.slice(openIndex, closeIndex)); + disposeTokens(tokens.slice(openIndex, closeIndex)); // Dispose view and tag bindings + tokens = tokens.slice(0, openIndex) + tokens.slice(closeIndex); + + if (prevNode) { + prevNode.setAttribute(jsvAttrStr, tokens); // Remove tokens of replaced content + } else if (target._df) { // Remove tokens of replaced content + setDefer(target, tokens); + } } } } @@ -14038,11 +14464,7 @@ function updateContent(sourceValue, linkCtx, attr, tag) { if (change = change || targetVal !== sourceValue) { if (attr === "text" && target.children && !target.children[0]) { // This code is faster then $target.text() - if (target.textContent !== undefined) { - target.textContent = sourceValue; - } else { - target.innerText = sourceValue === null ? "" : sourceValue; - } + target[TEXTCONTENT] = sourceValue === null ? "" : sourceValue; } else { $target[setter](sourceValue); } @@ -14051,7 +14473,7 @@ function updateContent(sourceValue, linkCtx, attr, tag) { // Setting value of