diff --git a/CHANGELOG.md b/CHANGELOG.md index 888bfee72..10306f8e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,18 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [6.0.5] - 2022-04-21 + +##### Changed + +- Fix: escape querySelector variables to avoid error. (#871) +- Fix: improve error messages for missing external instances (#873) +- Fix: don't randomize unrelated itemsets outside repeat (#876) +- Update datepicker-extended widget to properly unpack data from paste event (#878) +- Code style/formatting: Prettier + Airbnb ESLint preset (#866) +- Fix: unescaped HTML in CHANGELOG (#864) +- Update format-date to return empty string instead of 'Invalid Date' (openrosa-xpath-evaluator#155) + ## [6.0.4] - 2022-02-04 ##### Changed diff --git a/docs/AnalogScaleWidget.html b/docs/AnalogScaleWidget.html index b9e1d0991..28243d379 100644 --- a/docs/AnalogScaleWidget.html +++ b/docs/AnalogScaleWidget.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -254,7 +254,7 @@

(readonly)
Source:
@@ -332,7 +332,7 @@

(readonly)
Source:
@@ -411,7 +411,7 @@

propsSource:
@@ -485,7 +485,7 @@

(readonly) Source:
@@ -563,7 +563,7 @@

valueSource:
@@ -757,7 +757,7 @@

_getPropsSource:
@@ -871,7 +871,7 @@

_initSource:
@@ -963,7 +963,7 @@

_renderL
Source:
@@ -1050,7 +1050,7 @@

_resetSource:
@@ -1142,7 +1142,7 @@

_se
Source:
@@ -1231,7 +1231,7 @@

_
Source:
@@ -1390,7 +1390,7 @@

_stretc
Source:
@@ -1477,7 +1477,7 @@

_updateL
Source:
@@ -1564,7 +1564,7 @@

_update
Source:
@@ -1701,7 +1701,7 @@

disableSource:
@@ -1793,7 +1793,7 @@

enableSource:
@@ -1885,7 +1885,7 @@

updateSource:
diff --git a/docs/AutocompleteSelectpicker.html b/docs/AutocompleteSelectpicker.html index 7b03440a5..03e44e1e8 100644 --- a/docs/AutocompleteSelectpicker.html +++ b/docs/AutocompleteSelectpicker.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -80,7 +80,7 @@

Source:
@@ -189,7 +189,7 @@

(static) listSource:
@@ -258,7 +258,7 @@

(static) sel
Source:
@@ -327,7 +327,7 @@

(readonly)
Source:
@@ -405,7 +405,7 @@

(readonly)
Source:
@@ -484,7 +484,7 @@

(readonly) props
Source:
@@ -562,7 +562,7 @@

(readonly) Source:
@@ -640,7 +640,7 @@

(readonly) value
Source:
@@ -728,7 +728,7 @@

_findLabel<
Source:
@@ -882,7 +882,7 @@

_findValue<
Source:
@@ -1036,7 +1036,7 @@

_getPropsSource:
@@ -1150,7 +1150,7 @@

_initSource:
@@ -1242,7 +1242,7 @@

Source:
@@ -1329,7 +1329,7 @@

_set
Source:
@@ -1416,7 +1416,7 @@

_sho
Source:
@@ -1503,7 +1503,7 @@

disableSource:
@@ -1595,7 +1595,7 @@

enableSource:
@@ -1687,7 +1687,7 @@

updateSource:
diff --git a/docs/Columns.html b/docs/Columns.html index 9b26a3b01..25bfad937 100644 --- a/docs/Columns.html +++ b/docs/Columns.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -259,7 +259,7 @@

(readonly)
Source:
@@ -337,7 +337,7 @@

(readonly)
Source:
@@ -416,7 +416,7 @@

(readonly) props
Source:
@@ -494,7 +494,7 @@

(readonly) Source:
@@ -572,7 +572,7 @@

(readonly) value
Source:
@@ -660,7 +660,7 @@

_getPropsSource:
@@ -774,7 +774,7 @@

_initSource:
@@ -866,7 +866,7 @@

disableSource:
@@ -958,7 +958,7 @@

enableSource:
@@ -1050,7 +1050,7 @@

updateSource:
diff --git a/docs/Comment.html b/docs/Comment.html index 625b24bdd..1d875cfa4 100644 --- a/docs/Comment.html +++ b/docs/Comment.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -327,7 +327,7 @@

(readonly)
Source:
@@ -405,7 +405,7 @@

(readonly)
Source:
@@ -484,7 +484,7 @@

(readonly) props
Source:
@@ -562,7 +562,7 @@

(readonly) Source:
@@ -640,7 +640,7 @@

(readonly) value
Source:
@@ -728,7 +728,7 @@

_comm
Source:
@@ -833,7 +833,7 @@

_ge
Source:
@@ -987,7 +987,7 @@

_getPropsSource:
@@ -1101,7 +1101,7 @@

_hid
Source:
@@ -1237,7 +1237,7 @@

_initSource:
@@ -1329,7 +1329,7 @@

_
Source:
@@ -1483,7 +1483,7 @@

Source:
@@ -1570,7 +1570,7 @@

Source:
@@ -1725,7 +1725,7 @@

_setF
Source:
@@ -1812,7 +1812,7 @@

Source:
@@ -1899,7 +1899,7 @@

_sho
Source:
@@ -1986,7 +1986,7 @@

disableSource:
@@ -2078,7 +2078,7 @@

enableSource:
@@ -2170,7 +2170,7 @@

updateSource:
diff --git a/docs/DatepickerExtended.html b/docs/DatepickerExtended.html index 6ba13a9d5..618748028 100644 --- a/docs/DatepickerExtended.html +++ b/docs/DatepickerExtended.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -259,7 +259,7 @@

display
Source:
@@ -328,7 +328,7 @@

(readonly)
Source:
@@ -406,7 +406,7 @@

(readonly)
Source:
@@ -485,7 +485,7 @@

(readonly) props
Source:
@@ -563,7 +563,7 @@

(readonly) Source:
@@ -641,7 +641,7 @@

valueSource:
@@ -808,7 +808,7 @@

_
Source:
@@ -966,7 +966,7 @@

_getPropsSource:
@@ -1080,7 +1080,7 @@

_initSource:
@@ -1172,7 +1172,7 @@

_set
Source:
@@ -1308,7 +1308,7 @@

_setF
Source:
@@ -1445,7 +1445,7 @@

_setR
Source:
@@ -1581,7 +1581,7 @@

_toActua
Source:
@@ -1747,7 +1747,7 @@

_toDisp
Source:
@@ -1913,7 +1913,7 @@

disableSource:
@@ -2005,7 +2005,7 @@

enableSource:
@@ -2097,7 +2097,7 @@

updateSource:
diff --git a/docs/DatepickerMobile.html b/docs/DatepickerMobile.html index 02633a61c..d8fc93bd1 100644 --- a/docs/DatepickerMobile.html +++ b/docs/DatepickerMobile.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -259,7 +259,7 @@

(readonly)
Source:
@@ -337,7 +337,7 @@

(readonly)
Source:
@@ -416,7 +416,7 @@

(readonly) props
Source:
@@ -494,7 +494,7 @@

(readonly) Source:
@@ -572,7 +572,7 @@

valueSource:
@@ -810,7 +810,7 @@

_getPropsSource:
@@ -924,7 +924,7 @@

_initSource:
@@ -1016,7 +1016,7 @@

disableSource:
@@ -1108,7 +1108,7 @@

enableSource:
@@ -1200,7 +1200,7 @@

updateSource:
diff --git a/docs/DatepickerNative.html b/docs/DatepickerNative.html index 6048d4b6b..5250b5a64 100644 --- a/docs/DatepickerNative.html +++ b/docs/DatepickerNative.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -264,7 +264,7 @@

(readonly)
Source:
@@ -342,7 +342,7 @@

(readonly)
Source:
@@ -421,7 +421,7 @@

(readonly) props
Source:
@@ -499,7 +499,7 @@

(readonly) Source:
@@ -577,7 +577,7 @@

(readonly) value
Source:
@@ -819,7 +819,7 @@

_getPropsSource:
@@ -933,7 +933,7 @@

_initSource:
@@ -1025,7 +1025,7 @@

disableSource:
@@ -1117,7 +1117,7 @@

enableSource:
@@ -1209,7 +1209,7 @@

updateSource:
diff --git a/docs/DatepickerNativeIos.html b/docs/DatepickerNativeIos.html index 3c6651cab..35a849af3 100644 --- a/docs/DatepickerNativeIos.html +++ b/docs/DatepickerNativeIos.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -258,7 +258,7 @@

(readonly)
Source:
@@ -336,7 +336,7 @@

(readonly)
Source:
@@ -415,7 +415,7 @@

(readonly) props
Source:
@@ -493,7 +493,7 @@

(readonly) Source:
@@ -571,7 +571,7 @@

(readonly) value
Source:
@@ -813,7 +813,7 @@

_getPropsSource:
@@ -927,7 +927,7 @@

_initSource:
@@ -1019,7 +1019,7 @@

disableSource:
@@ -1111,7 +1111,7 @@

enableSource:
@@ -1203,7 +1203,7 @@

updateSource:
diff --git a/docs/DatetimepickerExtended.html b/docs/DatetimepickerExtended.html index e83b81d67..a2a643491 100644 --- a/docs/DatetimepickerExtended.html +++ b/docs/DatetimepickerExtended.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -76,7 +76,7 @@

Source:
@@ -185,7 +185,7 @@

(static) sel
Source:
@@ -254,7 +254,7 @@

(readonly)
Source:
@@ -332,7 +332,7 @@

(readonly)
Source:
@@ -411,7 +411,7 @@

(readonly) props
Source:
@@ -489,7 +489,7 @@

(readonly) Source:
@@ -567,7 +567,7 @@

valueSource:
@@ -651,7 +651,7 @@

(static) co
Source:
@@ -756,7 +756,7 @@

_
Source:
@@ -861,7 +861,7 @@

_
Source:
@@ -966,7 +966,7 @@

_getPropsSource:
@@ -1080,7 +1080,7 @@

_initSource:
@@ -1172,7 +1172,7 @@

_setF
Source:
@@ -1304,7 +1304,7 @@

disableSource:
@@ -1396,7 +1396,7 @@

enableSource:
@@ -1488,7 +1488,7 @@

updateSource:
diff --git a/docs/DesktopSelectpicker.html b/docs/DesktopSelectpicker.html index d6d5d2250..6362e116d 100644 --- a/docs/DesktopSelectpicker.html +++ b/docs/DesktopSelectpicker.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -81,7 +81,7 @@

ne
Source:
@@ -190,7 +190,7 @@

(static) listSource:
@@ -259,7 +259,7 @@

(static) sel
Source:
@@ -328,7 +328,7 @@

(readonly)
Source:
@@ -406,7 +406,7 @@

(readonly)
Source:
@@ -485,7 +485,7 @@

(readonly) props
Source:
@@ -563,7 +563,7 @@

(readonly) Source:
@@ -641,7 +641,7 @@

(readonly) value
Source:
@@ -729,7 +729,7 @@

(static) co
Source:
@@ -834,7 +834,7 @@

_clickL
Source:
@@ -921,7 +921,7 @@

_focusL
Source:
@@ -1008,7 +1008,7 @@

_getLisHtm
Source:
@@ -1095,7 +1095,7 @@

_getPropsSource:
@@ -1209,7 +1209,7 @@

_getTempl
Source:
@@ -1314,7 +1314,7 @@

_initSource:
@@ -1406,7 +1406,7 @@

_showSel
Source:
@@ -1542,7 +1542,7 @@

disableSource:
@@ -1634,7 +1634,7 @@

enableSource:
@@ -1726,7 +1726,7 @@

updateSource:
diff --git a/docs/DrawWidget.html b/docs/DrawWidget.html index 95b7c850e..d17c78a67 100644 --- a/docs/DrawWidget.html +++ b/docs/DrawWidget.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -80,7 +80,7 @@

new DrawWid
Source:
@@ -189,7 +189,7 @@

(static) sel
Source:
@@ -258,7 +258,7 @@

(readonly)
Source:
@@ -336,7 +336,7 @@

(readonly)
Source:
@@ -415,7 +415,7 @@

propsSource:
@@ -489,7 +489,7 @@

(readonly) Source:
@@ -567,7 +567,7 @@

valueSource:
@@ -651,7 +651,7 @@

_getMarkup<
Source:
@@ -756,7 +756,7 @@

_getPropsSource:
@@ -870,7 +870,7 @@

_handleFi
Source:
@@ -1002,7 +1002,7 @@

_handleR
Source:
@@ -1138,7 +1138,7 @@

_initSource:
@@ -1230,7 +1230,7 @@

_load
Source:
@@ -1387,7 +1387,7 @@

_resetSource:
@@ -1474,7 +1474,7 @@

_resizeC
Source:
@@ -1612,7 +1612,7 @@

_showFee
Source:
@@ -1744,7 +1744,7 @@

_showFil
Source:
@@ -1876,7 +1876,7 @@

_up
Source:
@@ -2095,7 +2095,7 @@

_updateVa
Source:
@@ -2251,7 +2251,7 @@

disableSource:
@@ -2343,7 +2343,7 @@

enableSource:
@@ -2435,7 +2435,7 @@

updateSource:
diff --git a/docs/Filepicker.html b/docs/Filepicker.html index dbb84c221..6a23a8c55 100644 --- a/docs/Filepicker.html +++ b/docs/Filepicker.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -259,7 +259,7 @@

(readonly)
Source:
@@ -337,7 +337,7 @@

(readonly)
Source:
@@ -416,7 +416,7 @@

propsSource:
@@ -490,7 +490,7 @@

(readonly) Source:
@@ -568,7 +568,7 @@

valueSource:
@@ -652,7 +652,7 @@

_getPropsSource:
@@ -766,7 +766,7 @@

_initSource:
@@ -858,7 +858,7 @@

_resizeFil
Source:
@@ -1035,7 +1035,7 @@

_se
Source:
@@ -1122,7 +1122,7 @@

_set
Source:
@@ -1209,7 +1209,7 @@

Source:
@@ -1345,7 +1345,7 @@

_showFee
Source:
@@ -1523,7 +1523,7 @@

_showFil
Source:
@@ -1659,7 +1659,7 @@

_showPrev
Source:
@@ -1814,7 +1814,7 @@

_up
Source:
@@ -2056,7 +2056,7 @@

disableSource:
@@ -2148,7 +2148,7 @@

enableSource:
@@ -2240,7 +2240,7 @@

updateSource:
diff --git a/docs/Form.html b/docs/Form.html index 989f74754..ed6086cc3 100644 --- a/docs/Form.html +++ b/docs/Form.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -76,7 +76,7 @@

new FormSource:
@@ -396,7 +396,7 @@

(
Source:
@@ -474,7 +474,7 @@

actionSource:
@@ -543,7 +543,7 @@

c
Source:
@@ -616,7 +616,7 @@

Source:
@@ -689,7 +689,7 @@

curren
Source:
@@ -758,7 +758,7 @@

deprecate
Source:
@@ -827,7 +827,7 @@

editStatus<
Source:
@@ -896,7 +896,7 @@

encrypti
Source:
@@ -965,7 +965,7 @@

eval
Source:
@@ -1034,7 +1034,7 @@

Source:
@@ -1103,7 +1103,7 @@

idSource:
@@ -1172,7 +1172,7 @@

instanceID<
Source:
@@ -1241,7 +1241,7 @@

instanceN
Source:
@@ -1310,7 +1310,7 @@

languagesSource:
@@ -1379,7 +1379,7 @@

methodSource:
@@ -1448,7 +1448,7 @@

recordName<
Source:
@@ -1517,7 +1517,7 @@

surveyName<
Source:
@@ -1586,7 +1586,7 @@

versionSource:
@@ -1665,7 +1665,7 @@

addModuleSource:
@@ -1823,7 +1823,7 @@

bl
Source:
@@ -1911,7 +1911,7 @@

clear
Source:
@@ -1998,7 +1998,7 @@

Source:
@@ -2152,7 +2152,7 @@

getDataStr<
Source:
@@ -2322,7 +2322,7 @@

Source:
@@ -2435,7 +2435,7 @@

getGoToT
Source:
@@ -2592,7 +2592,7 @@

getModel
Source:
@@ -2749,7 +2749,7 @@

Source:
@@ -2953,7 +2953,7 @@

getRel
Source:
@@ -3187,7 +3187,7 @@

goToSource:
@@ -3341,7 +3341,7 @@

goToTarget<
Source:
@@ -3499,7 +3499,7 @@

Source:
@@ -3595,7 +3595,7 @@

initSource:
@@ -3705,7 +3705,7 @@

isValidSource:
@@ -3863,7 +3863,7 @@

pathToA
Source:
@@ -4040,7 +4040,7 @@

re
Source:
@@ -4292,7 +4292,7 @@

resetViewSource:
@@ -4404,7 +4404,7 @@

setAllVals<
Source:
@@ -4565,7 +4565,7 @@

setEv
Source:
@@ -4652,7 +4652,7 @@

setInvalid<
Source:
@@ -4831,7 +4831,7 @@

setValidSource:
@@ -5010,7 +5010,7 @@

upd
Source:
@@ -5165,7 +5165,7 @@

validateSource:
@@ -5252,7 +5252,7 @@

validateAl
Source:
@@ -5362,7 +5362,7 @@

valida
Source:
@@ -5520,7 +5520,7 @@

validate
Source:
@@ -5678,7 +5678,7 @@

valid
Source:
diff --git a/docs/FormLogicError.html b/docs/FormLogicError.html index 4ca226507..271de084a 100644 --- a/docs/FormLogicError.html +++ b/docs/FormLogicError.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

diff --git a/docs/FormModel.html b/docs/FormModel.html index 99d2058d6..1839bb964 100644 --- a/docs/FormModel.html +++ b/docs/FormModel.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -76,7 +76,7 @@

new FormMode
Source:
@@ -331,7 +331,7 @@

bindJs
Source:
@@ -405,7 +405,7 @@

deprecate
Source:
@@ -474,7 +474,7 @@

instanceID<
Source:
@@ -543,7 +543,7 @@

instanceN
Source:
@@ -612,7 +612,7 @@

typesSource:
@@ -685,7 +685,7 @@

versionSource:
@@ -764,7 +764,7 @@

ad
Source:
@@ -919,7 +919,7 @@

addRepeatSource:
@@ -1102,7 +1102,7 @@

addR
Source:
@@ -1234,7 +1234,7 @@

addTemplat
Source:
@@ -1412,7 +1412,7 @@

conv
Source:
@@ -1612,7 +1612,7 @@

createSe
Source:
@@ -1787,7 +1787,7 @@

determi
Source:
@@ -1945,7 +1945,7 @@

evaluateSource:
@@ -2258,7 +2258,7 @@

e
Source:
@@ -2390,7 +2390,7 @@

extra
Source:
@@ -2477,7 +2477,7 @@

getMetaNod
Source:
@@ -2631,7 +2631,7 @@

get
Source:
@@ -2788,7 +2788,7 @@

getNsRes
Source:
@@ -2897,7 +2897,7 @@

ge
Source:
@@ -2984,7 +2984,7 @@

get
Source:
@@ -3161,7 +3161,7 @@

Source:
@@ -3315,7 +3315,7 @@

g
Source:
@@ -3469,7 +3469,7 @@

getRep
Source:
@@ -3650,7 +3650,7 @@

g
Source:
@@ -3812,7 +3812,7 @@

getStrSource:
@@ -3921,7 +3921,7 @@

getTe
Source:
@@ -4026,7 +4026,7 @@

get
Source:
@@ -4113,7 +4113,7 @@

importNode<
Source:
@@ -4273,7 +4273,7 @@

initSource:
@@ -4382,7 +4382,7 @@

makeB
Source:
@@ -4576,7 +4576,7 @@

mergeXmlSource:
@@ -4712,7 +4712,7 @@

nodeSource:
@@ -4960,7 +4960,7 @@

Source:
@@ -5114,7 +5114,7 @@

Source:
@@ -5250,7 +5250,7 @@

repla
Source:
@@ -5434,7 +5434,7 @@

Source:
@@ -5639,7 +5639,7 @@

repl
Source:
@@ -5799,7 +5799,7 @@

repl
Source:
@@ -5999,7 +5999,7 @@

repla
Source:
@@ -6153,7 +6153,7 @@

Source:
@@ -6240,7 +6240,7 @@

setNames
Source:
@@ -6327,7 +6327,7 @@

shiftRootSource:
@@ -6485,7 +6485,7 @@

thro
Source:
@@ -6640,7 +6640,7 @@

trimValues<
Source:
diff --git a/docs/Geopicker.html b/docs/Geopicker.html index 19a991083..e806c8a09 100644 --- a/docs/Geopicker.html +++ b/docs/Geopicker.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -76,7 +76,7 @@

new Geopicke
Source:
@@ -185,7 +185,7 @@

(static) sel
Source:
@@ -254,7 +254,7 @@

(readonly)
Source:
@@ -332,7 +332,7 @@

(readonly)
Source:
@@ -411,7 +411,7 @@

propsSource:
@@ -479,7 +479,7 @@

(readonly) Source:
@@ -557,7 +557,7 @@

valueSource:
@@ -641,7 +641,7 @@

(static) co
Source:
@@ -795,7 +795,7 @@

_addDo
Source:
@@ -882,7 +882,7 @@

_addDyn
Source:
@@ -987,7 +987,7 @@

_addPointSource:
@@ -1074,7 +1074,7 @@

_addPoint
Source:
@@ -1161,7 +1161,7 @@

_cleanLat
Source:
@@ -1296,7 +1296,7 @@

_closePo
Source:
@@ -1383,7 +1383,7 @@

Source:
@@ -1544,7 +1544,7 @@

_
Source:
@@ -1649,7 +1649,7 @@

_editPoint<
Source:
@@ -1810,7 +1810,7 @@

_enab
Source:
@@ -1897,7 +1897,7 @@

_enableS
Source:
@@ -1985,7 +1985,7 @@

_getBas
Source:
@@ -2139,7 +2139,7 @@

_getD
Source:
@@ -2293,7 +2293,7 @@

_g
Source:
@@ -2474,7 +2474,7 @@

_getLayers<
Source:
@@ -2583,7 +2583,7 @@

_
Source:
@@ -2764,7 +2764,7 @@

_getPropsSource:
@@ -2878,7 +2878,7 @@

_getTi
Source:
@@ -3059,7 +3059,7 @@

_inF
Source:
@@ -3164,7 +3164,7 @@

_initSource:
@@ -3256,7 +3256,7 @@

_isVa
Source:
@@ -3416,7 +3416,7 @@

_isVali
Source:
@@ -3577,7 +3577,7 @@

_is
Source:
@@ -3735,7 +3735,7 @@

Source:
@@ -3845,7 +3845,7 @@

_markAs
Source:
@@ -3981,7 +3981,7 @@

_markAsVa
Source:
@@ -4068,7 +4068,7 @@

_removePo
Source:
@@ -4155,7 +4155,7 @@

_setCurren
Source:
@@ -4291,7 +4291,7 @@

_s
Source:
@@ -4378,7 +4378,7 @@

_swit
Source:
@@ -4510,7 +4510,7 @@

_updateAre
Source:
@@ -4646,7 +4646,7 @@

Source:
@@ -4824,7 +4824,7 @@

_updateI
Source:
@@ -5006,7 +5006,7 @@

_updateMap<
Source:
@@ -5189,7 +5189,7 @@

_update
Source:
@@ -5276,7 +5276,7 @@

_update
Source:
@@ -5364,7 +5364,7 @@

_updat
Source:
@@ -5451,7 +5451,7 @@

_updateVa
Source:
@@ -5560,7 +5560,7 @@

co
Source:
@@ -5761,7 +5761,7 @@

disableSource:
@@ -5853,7 +5853,7 @@

enableSource:
@@ -5945,7 +5945,7 @@

updateSource:
@@ -6037,7 +6037,7 @@

Source:
diff --git a/docs/ImageMap.html b/docs/ImageMap.html index a128a7ad2..2dce6e22c 100644 --- a/docs/ImageMap.html +++ b/docs/ImageMap.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -81,7 +81,7 @@

new ImageMap<
Source:
@@ -190,7 +190,7 @@

(static) sel
Source:
@@ -259,7 +259,7 @@

(readonly)
Source:
@@ -337,7 +337,7 @@

(readonly)
Source:
@@ -416,7 +416,7 @@

(readonly) props
Source:
@@ -494,7 +494,7 @@

(readonly) Source:
@@ -572,7 +572,7 @@

valueSource:
@@ -656,7 +656,7 @@

_add
Source:
@@ -788,7 +788,7 @@

_addMarkup<
Source:
@@ -942,7 +942,7 @@

_getInputSource:
@@ -1096,7 +1096,7 @@

_getPropsSource:
@@ -1210,7 +1210,7 @@

_initSource:
@@ -1302,7 +1302,7 @@

_isSvgDocSource:
@@ -1456,7 +1456,7 @@

_r
Source:
@@ -1614,7 +1614,7 @@

_set
Source:
@@ -1701,7 +1701,7 @@

_setH
Source:
@@ -1788,7 +1788,7 @@

_setPa
Source:
@@ -1875,7 +1875,7 @@

_s
Source:
@@ -1962,7 +1962,7 @@

Source:
@@ -2094,7 +2094,7 @@

_updateIm
Source:
@@ -2182,7 +2182,7 @@

disableSource:
@@ -2274,7 +2274,7 @@

enableSource:
@@ -2366,7 +2366,7 @@

updateSource:
diff --git a/docs/ImageViewer.html b/docs/ImageViewer.html index 85b1c15e6..15a576878 100644 --- a/docs/ImageViewer.html +++ b/docs/ImageViewer.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -258,7 +258,7 @@

(readonly)
Source:
@@ -336,7 +336,7 @@

(readonly)
Source:
@@ -415,7 +415,7 @@

(readonly) props
Source:
@@ -493,7 +493,7 @@

(readonly) Source:
@@ -571,7 +571,7 @@

(readonly) value
Source:
@@ -659,7 +659,7 @@

_getPropsSource:
@@ -773,7 +773,7 @@

_initSource:
@@ -865,7 +865,7 @@

disableSource:
@@ -957,7 +957,7 @@

enableSource:
@@ -1049,7 +1049,7 @@

updateSource:
diff --git a/docs/LikertItem.html b/docs/LikertItem.html index bde610896..e4a552634 100644 --- a/docs/LikertItem.html +++ b/docs/LikertItem.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

diff --git a/docs/MediaPicker.html b/docs/MediaPicker.html index 7fe6b3ccc..ac5a2385f 100644 --- a/docs/MediaPicker.html +++ b/docs/MediaPicker.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -258,7 +258,7 @@

(readonly)
Source:
@@ -336,7 +336,7 @@

(readonly)
Source:
@@ -415,7 +415,7 @@

(readonly) props
Source:
@@ -493,7 +493,7 @@

(readonly) Source:
@@ -571,7 +571,7 @@

(readonly) value
Source:
@@ -659,7 +659,7 @@

_getPropsSource:
@@ -773,7 +773,7 @@

_initSource:
@@ -865,7 +865,7 @@

disableSource:
@@ -957,7 +957,7 @@

enableSource:
@@ -1049,7 +1049,7 @@

updateSource:
diff --git a/docs/MobileSelectPicker.html b/docs/MobileSelectPicker.html index 24683d96d..8d8f9874f 100644 --- a/docs/MobileSelectPicker.html +++ b/docs/MobileSelectPicker.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -259,7 +259,7 @@

(readonly)
Source:
@@ -337,7 +337,7 @@

(readonly)
Source:
@@ -416,7 +416,7 @@

(readonly) props
Source:
@@ -494,7 +494,7 @@

(readonly) Source:
@@ -572,7 +572,7 @@

(readonly) value
Source:
@@ -765,7 +765,7 @@

_getPropsSource:
@@ -879,7 +879,7 @@

_initSource:
@@ -971,7 +971,7 @@

_s
Source:
@@ -1058,7 +1058,7 @@

disableSource:
@@ -1150,7 +1150,7 @@

enableSource:
@@ -1242,7 +1242,7 @@

updateSource:
diff --git a/docs/MyWidget.html b/docs/MyWidget.html index d0dd54197..9e06a6f29 100644 --- a/docs/MyWidget.html +++ b/docs/MyWidget.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -245,7 +245,7 @@

valueSource:
@@ -318,7 +318,7 @@

valueSource:
@@ -480,7 +480,7 @@

disableSource:
@@ -567,7 +567,7 @@

enableSource:
@@ -654,7 +654,7 @@

updateSource:
diff --git a/docs/Nodeset.html b/docs/Nodeset.html index 2b76449e0..351334fca 100644 --- a/docs/Nodeset.html +++ b/docs/Nodeset.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -342,7 +342,7 @@

convertSource:
@@ -547,7 +547,7 @@

getCl
Source:
@@ -659,7 +659,7 @@

getElement<
Source:
@@ -764,7 +764,7 @@

getElement
Source:
@@ -869,7 +869,7 @@

getValSource:
@@ -981,7 +981,7 @@

isRequired<
Source:
@@ -1147,7 +1147,7 @@

removeSource:
@@ -1234,7 +1234,7 @@

setIndexSource:
@@ -1382,7 +1382,7 @@

setValSource:
@@ -1592,7 +1592,7 @@

validateSource:
@@ -1792,7 +1792,7 @@

Source:
@@ -1995,7 +1995,7 @@

valid
Source:
diff --git a/docs/NoteWidget.html b/docs/NoteWidget.html index 89ffacaf7..a74b309a0 100644 --- a/docs/NoteWidget.html +++ b/docs/NoteWidget.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

diff --git a/docs/Radiopicker.html b/docs/Radiopicker.html index 55713377c..b74e248c6 100644 --- a/docs/Radiopicker.html +++ b/docs/Radiopicker.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -258,7 +258,7 @@

(readonly)
Source:
@@ -336,7 +336,7 @@

(readonly)
Source:
@@ -415,7 +415,7 @@

(readonly) props
Source:
@@ -493,7 +493,7 @@

(readonly) Source:
@@ -571,7 +571,7 @@

(readonly) value
Source:
@@ -659,7 +659,7 @@

_getPropsSource:
@@ -773,7 +773,7 @@

_initSource:
@@ -865,7 +865,7 @@

_up
Source:
@@ -997,7 +997,7 @@

disableSource:
@@ -1089,7 +1089,7 @@

enableSource:
@@ -1181,7 +1181,7 @@

updateSource:
diff --git a/docs/RangeWidget.html b/docs/RangeWidget.html index 9b162d778..582eef408 100644 --- a/docs/RangeWidget.html +++ b/docs/RangeWidget.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -254,7 +254,7 @@

(readonly)
Source:
@@ -332,7 +332,7 @@

(readonly)
Source:
@@ -411,7 +411,7 @@

propsSource:
@@ -485,7 +485,7 @@

(readonly) Source:
@@ -563,7 +563,7 @@

valueSource:
@@ -647,7 +647,7 @@

_getHtmlSt
Source:
@@ -756,7 +756,7 @@

_getPropsSource:
@@ -870,7 +870,7 @@

_initSource:
@@ -962,7 +962,7 @@

_resetSource:
@@ -1049,7 +1049,7 @@

_
Source:
@@ -1203,7 +1203,7 @@

_update
Source:
@@ -1335,7 +1335,7 @@

disableSource:
@@ -1427,7 +1427,7 @@

enableSource:
@@ -1519,7 +1519,7 @@

updateSource:
diff --git a/docs/RankWidget.html b/docs/RankWidget.html index 12442e9b1..c07821d79 100644 --- a/docs/RankWidget.html +++ b/docs/RankWidget.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -323,7 +323,7 @@

(readonly)
Source:
@@ -401,7 +401,7 @@

ori
Source:
@@ -475,7 +475,7 @@

ori
Source:
@@ -553,7 +553,7 @@

(readonly) props
Source:
@@ -631,7 +631,7 @@

(readonly) Source:
@@ -709,7 +709,7 @@

valueSource:
@@ -793,7 +793,7 @@

_getPropsSource:
@@ -907,7 +907,7 @@

_initSource:
@@ -999,7 +999,7 @@

_resetSource:
@@ -1086,7 +1086,7 @@

disableSource:
@@ -1178,7 +1178,7 @@

enableSource:
@@ -1270,7 +1270,7 @@

updateSource:
diff --git a/docs/RatingWidget.html b/docs/RatingWidget.html index 87861977e..738b48a17 100644 --- a/docs/RatingWidget.html +++ b/docs/RatingWidget.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -254,7 +254,7 @@

(readonly)
Source:
@@ -332,7 +332,7 @@

(readonly)
Source:
@@ -411,7 +411,7 @@

propsSource:
@@ -485,7 +485,7 @@

(readonly) Source:
@@ -563,7 +563,7 @@

valueSource:
@@ -647,7 +647,7 @@

_getHtmlSt
Source:
@@ -761,7 +761,7 @@

_getPropsSource:
@@ -875,7 +875,7 @@

_initSource:
@@ -967,7 +967,7 @@

_resetSource:
@@ -1059,7 +1059,7 @@

_
Source:
@@ -1218,7 +1218,7 @@

_update
Source:
@@ -1355,7 +1355,7 @@

disableSource:
@@ -1447,7 +1447,7 @@

enableSource:
@@ -1539,7 +1539,7 @@

updateSource:
diff --git a/docs/TableWidget.html b/docs/TableWidget.html index bdb75d55a..1d201c094 100644 --- a/docs/TableWidget.html +++ b/docs/TableWidget.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

diff --git a/docs/TextMaxWidget.html b/docs/TextMaxWidget.html index 7eb349039..fb50fc346 100644 --- a/docs/TextMaxWidget.html +++ b/docs/TextMaxWidget.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

diff --git a/docs/TextPrintWidget.html b/docs/TextPrintWidget.html index 23fb4ab7a..f468807a6 100644 --- a/docs/TextPrintWidget.html +++ b/docs/TextPrintWidget.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

diff --git a/docs/TextareaWidget.html b/docs/TextareaWidget.html index 52179b1b0..a35a31f26 100644 --- a/docs/TextareaWidget.html +++ b/docs/TextareaWidget.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -80,7 +80,7 @@

new Tex
Source:
@@ -189,7 +189,7 @@

(static) sel
Source:
@@ -258,7 +258,7 @@

(readonly)
Source:
@@ -336,7 +336,7 @@

(readonly)
Source:
@@ -415,7 +415,7 @@

(readonly) props
Source:
@@ -493,7 +493,7 @@

(readonly) Source:
@@ -571,7 +571,7 @@

(readonly) value
Source:
@@ -659,7 +659,7 @@

_getPropsSource:
@@ -773,7 +773,7 @@

_initSource:
@@ -865,7 +865,7 @@

disableSource:
@@ -957,7 +957,7 @@

enableSource:
@@ -1049,7 +1049,7 @@

updateSource:
diff --git a/docs/ThousandsSeparatorWidget.html b/docs/ThousandsSeparatorWidget.html index b4e690a3c..a62ab6f95 100644 --- a/docs/ThousandsSeparatorWidget.html +++ b/docs/ThousandsSeparatorWidget.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -179,7 +179,7 @@

valueSource:
@@ -252,7 +252,7 @@

valueSource:
@@ -325,7 +325,7 @@

_initSource:
@@ -412,7 +412,7 @@

updateSource:
diff --git a/docs/TimepickerExtended.html b/docs/TimepickerExtended.html index dda01d96f..cc83affa7 100644 --- a/docs/TimepickerExtended.html +++ b/docs/TimepickerExtended.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -254,7 +254,7 @@

(readonly)
Source:
@@ -332,7 +332,7 @@

(readonly)
Source:
@@ -411,7 +411,7 @@

(readonly) props
Source:
@@ -489,7 +489,7 @@

(readonly) Source:
@@ -567,7 +567,7 @@

valueSource:
@@ -756,7 +756,7 @@

_getPropsSource:
@@ -870,7 +870,7 @@

_initSource:
@@ -962,7 +962,7 @@

_resetSource:
@@ -1049,7 +1049,7 @@

disableSource:
@@ -1141,7 +1141,7 @@

enableSource:
@@ -1233,7 +1233,7 @@

updateSource:
diff --git a/docs/TranslatedError.html b/docs/TranslatedError.html index 2d6a6a14d..c043d6bd6 100644 --- a/docs/TranslatedError.html +++ b/docs/TranslatedError.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

diff --git a/docs/UrlWidget.html b/docs/UrlWidget.html index daa00d436..39823ed50 100644 --- a/docs/UrlWidget.html +++ b/docs/UrlWidget.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -254,7 +254,7 @@

(readonly)
Source:
@@ -332,7 +332,7 @@

(readonly)
Source:
@@ -411,7 +411,7 @@

(readonly) props
Source:
@@ -489,7 +489,7 @@

(readonly) Source:
@@ -567,7 +567,7 @@

valueSource:
@@ -651,7 +651,7 @@

_getPropsSource:
@@ -765,7 +765,7 @@

_initSource:
@@ -857,7 +857,7 @@

disableSource:
@@ -949,7 +949,7 @@

enableSource:
@@ -1041,7 +1041,7 @@

updateSource:
diff --git a/docs/Widget.html b/docs/Widget.html index 0ddaa4d79..971aacacb 100644 --- a/docs/Widget.html +++ b/docs/Widget.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -80,7 +80,7 @@

new WidgetSource:
@@ -273,7 +273,7 @@

(static, readonly) Source:
@@ -346,7 +346,7 @@

(static, readonly) Source:
@@ -419,7 +419,7 @@

(readonly)
Source:
@@ -492,7 +492,7 @@

(readonly)
Source:
@@ -566,7 +566,7 @@

ori
Source:
@@ -640,7 +640,7 @@

(readonly) props
Source:
@@ -713,7 +713,7 @@

(readonly) Source:
@@ -786,7 +786,7 @@

(readonly) value
Source:
@@ -859,7 +859,7 @@

valueSource:
@@ -942,7 +942,7 @@

(static) co
Source:
@@ -1052,7 +1052,7 @@

_getPropsSource:
@@ -1161,7 +1161,7 @@

_initSource:
@@ -1248,7 +1248,7 @@

disableSource:
@@ -1335,7 +1335,7 @@

enableSource:
@@ -1422,7 +1422,7 @@

updateSource:
diff --git a/docs/external-SignaturePad.html b/docs/external-SignaturePad.html index 334a2d0d2..007cb083a 100644 --- a/docs/external-SignaturePad.html +++ b/docs/external-SignaturePad.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -67,7 +67,7 @@

Source:
@@ -146,7 +146,7 @@

fromObje
Source:
@@ -457,7 +457,7 @@

updateData<
Source:
diff --git a/docs/external-jQuery.html b/docs/external-jQuery.html index 0876da824..8db2b6523 100644 --- a/docs/external-jQuery.html +++ b/docs/external-jQuery.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -146,7 +146,7 @@

reverseSource:
diff --git a/docs/global.html b/docs/global.html index ecfa228f8..7f502bf30 100644 --- a/docs/global.html +++ b/docs/global.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -460,7 +460,7 @@

Type Definitions

-

FormDataObj

+

ExternalInstance

@@ -493,6 +493,166 @@

FormDataObj

+ + + + + + + + + + + + + + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
id + + +string + + + +
src + + +string + + + +
xml + + +string +| + +Document + + + +
+ + + + + + + + + + + + + + + +

FormDataObj

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + @@ -633,7 +793,7 @@
Properties:
-object +Array.<(ExternalInstance|null|undefined)> @@ -952,7 +1112,7 @@

jQuery

Source:
@@ -1025,7 +1185,7 @@

LatLngArray

Source:
@@ -1251,7 +1411,7 @@

LatLngObj

Source:
@@ -1595,7 +1755,7 @@

UpdatedDataNodes

Source:
@@ -1813,7 +1973,7 @@

ValidateInputResolution

Source:
diff --git a/docs/index.html b/docs/index.html index b5f66bef7..2a3cbb0e0 100644 --- a/docs/index.html +++ b/docs/index.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

diff --git a/docs/js_calculate.js.html b/docs/js_calculate.js.html index 4018d4e3d..701b83f41 100644 --- a/docs/js_calculate.js.html +++ b/docs/js_calculate.js.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -64,88 +64,120 @@

js/calculate.js

* @param {string} [filter] - CSS selector filter * @param {boolean} [emptyNonRelevant] - Whether to empty non-relevant calculation nodes */ - update( updated = {}, filter = '', emptyNonRelevant = false ) { + update(updated = {}, filter = '', emptyNonRelevant = false) { let nodes; - if ( !this.form ) { - throw new Error( 'Calculation module not correctly instantiated with form property.' ); + if (!this.form) { + throw new Error( + 'Calculation module not correctly instantiated with form property.' + ); } // Filter is used in custom applications that make a distinction between types of calculations. - if ( updated.relevantPath ) { + if (updated.relevantPath) { // Questions that are descendants of a group: - nodes = this.form.getRelatedNodes( 'data-calculate', `[name^="${updated.relevantPath}/"]${filter}` ) + nodes = this.form + .getRelatedNodes( + 'data-calculate', + `[name^="${updated.relevantPath}/"]${filter}` + ) // Individual questions: - .add( this.form.getRelatedNodes( 'data-calculate', `[name="${updated.relevantPath}"]${filter}` ) ) + .add( + this.form.getRelatedNodes( + 'data-calculate', + `[name="${updated.relevantPath}"]${filter}` + ) + ) // Individual radiobutton questions with a calculate....: - .add( this.form.getRelatedNodes( 'data-calculate', `[data-name="${updated.relevantPath}"]${filter}` ) ) + .add( + this.form.getRelatedNodes( + 'data-calculate', + `[data-name="${updated.relevantPath}"]${filter}` + ) + ) .get(); } else { - nodes = this.form.getRelatedNodes( 'data-calculate', filter, updated ) + nodes = this.form + .getRelatedNodes('data-calculate', filter, updated) .get(); } - nodes.forEach( control => { - const name = this.form.input.getName( control ); - const dataNodesObj = this.form.model.node( name ); + nodes.forEach((control) => { + const name = this.form.input.getName(control); + const dataNodesObj = this.form.model.node(name); const dataNodes = dataNodesObj.getElements(); const props = { name, - expr: this.form.input.getCalculation( control ), - dataType: this.form.input.getXmlType( control ), - relevantExpr: this.form.input.getRelevant( control ), + expr: this.form.input.getCalculation(control), + dataType: this.form.input.getXmlType(control), + relevantExpr: this.form.input.getRelevant(control), index: 0, - dataNodesObj + dataNodesObj, }; - if ( dataNodes.length > 1 ) { - - if ( updated.repeatPath && name.indexOf( updated.repeatPath + '/' ) !== -1 && dataNodes[updated.repeatIndex] ) { + if (dataNodes.length > 1) { + if ( + updated.repeatPath && + name.indexOf(`${updated.repeatPath}/`) !== -1 && + dataNodes[updated.repeatIndex] + ) { /* * If the update was triggered by a datanode inside a repeat * and the dependent node is inside the same repeat, we can prevent the expensive index determination */ - const dataNodeName = ( name.lastIndexOf( '/' ) !== -1 ) ? name.substring( name.lastIndexOf( '/' ) + 1 ) : name; - const childNodeList = this.form.model.node( updated.repeatPath, updated.repeatIndex ).getElement().querySelectorAll( dataNodeName ); - const dataNode = Array.from( childNodeList ).filter( node => dataNodes.includes( node ) )[0]; - props.index = dataNodes.indexOf( dataNode ); - this._updateCalc( control, props, emptyNonRelevant ); - } else if ( control.type === 'hidden' ) { + const dataNodeName = + name.lastIndexOf('/') !== -1 + ? name.substring(name.lastIndexOf('/') + 1) + : name; + const childNodeList = this.form.model + .node(updated.repeatPath, updated.repeatIndex) + .getElement() + .querySelectorAll(dataNodeName); + const dataNode = Array.from(childNodeList).filter((node) => + dataNodes.includes(node) + )[0]; + props.index = dataNodes.indexOf(dataNode); + this._updateCalc(control, props, emptyNonRelevant); + } else if (control.type === 'hidden') { /* * This case is the consequence of the decision to place calculated items without a visible form control, * as a separate group (.or-calculated-items, or .or-setvalue-items, or .or-setgeopoint-items), instead of in the Form DOM in the locations . * This occurs when update is called with empty updated object and multiple repeats are present. */ - dataNodes.forEach( ( el, index ) => { - const obj = Object.create( props ); + dataNodes.forEach((el, index) => { + const obj = Object.create(props); obj.index = index; - this._updateCalc( control, obj, emptyNonRelevant ); - } ); + this._updateCalc(control, obj, emptyNonRelevant); + }); } else { /* * This occurs when the updated object contains a relevantPath that refers to a repeat and multiple repeats are * present, without calculated items that HAVE a visible form control. */ - const repeatSiblings = getSiblingElementsAndSelf( control.closest( '.or-repeat' ), '.or-repeat' ); - if ( repeatSiblings.length === dataNodes.length ) { - props.index = repeatSiblings.indexOf( control.closest( '.or-repeat' ) ); - this._updateCalc( control, props, emptyNonRelevant ); + const repeatSiblings = getSiblingElementsAndSelf( + control.closest('.or-repeat'), + '.or-repeat' + ); + if (repeatSiblings.length === dataNodes.length) { + props.index = repeatSiblings.indexOf( + control.closest('.or-repeat') + ); + this._updateCalc(control, props, emptyNonRelevant); } } - } else if ( dataNodes.length === 1 ) { - this._updateCalc( control, props, emptyNonRelevant ); + } else if (dataNodes.length === 1) { + this._updateCalc(control, props, emptyNonRelevant); } - - } ); + }); }, /** * @param {'setvalue' | 'setgeopoint'} action - the action being performed. * @param {CustomEvent} [event] - the event type that triggered the action. */ - _getNodesForAction( action, event ) { - if ( event.type === new events.InstanceFirstLoad().type ) { + _getNodesForAction(action, event) { + if (event.type === new events.InstanceFirstLoad().type) { // We ignore relevance for the data-instance-first-load, as that will likely never be what users want for a default value. // Do not use getRelatedNodes here, because the obtaining (and caching) of nodes inside repeats is (and should be) disabled at the // time this event fires. @@ -153,21 +185,51 @@

js/calculate.js

// We change the order by first evaluating the non-formcontrol actions (in document order), and then // the ones with form controls. // https://github.com/OpenClinica/enketo-express-oc/issues/355#issuecomment-725640823 - return [ ...this.form.view.html.querySelectorAll( `.${action} [data-${action}][data-event*="${event.type}"]` ) ].concat( - this.form.filterRadioCheckSiblings( [ ...this.form.view.html.querySelectorAll( `.question [data-${action}][data-event*="${event.type}"]` ) ] ) ); - } else if ( event.type === new events.NewRepeat().type ) { + return [ + ...this.form.view.html.querySelectorAll( + `.${action} [data-${action}][data-event*="${event.type}"]` + ), + ].concat( + this.form.filterRadioCheckSiblings([ + ...this.form.view.html.querySelectorAll( + `.question [data-${action}][data-event*="${event.type}"]` + ), + ]) + ); + } + if (event.type === new events.NewRepeat().type) { // Only this event requires specific index targeting through the "updated" object // We change the order by first evaluating the non-formcontrol actions (in document order), and then // the ones with form controls. // https://github.com/OpenClinica/enketo-express-oc/issues/355#issuecomment-725640823 // https://github.com/OpenClinica/enketo-express-oc/issues/419 - return this.form.getRelatedNodes( `data-${action}`, `.${action} [data-event*="${event.type}"]`, event.detail ).get().concat( - this.form.getRelatedNodes( `data-${action}`, `.question [data-event*="${event.type}"]`, event.detail ).get() ); - - } else if ( event.type === new events.XFormsValueChanged().type ) { - const question = event.target.closest( '.question' ); - - return question ? [ ...question.querySelectorAll( `[data-${action}][data-event*="${event.type}"]` ) ] : []; + return this.form + .getRelatedNodes( + `data-${action}`, + `.${action} [data-event*="${event.type}"]`, + event.detail + ) + .get() + .concat( + this.form + .getRelatedNodes( + `data-${action}`, + `.question [data-event*="${event.type}"]`, + event.detail + ) + .get() + ); + } + if (event.type === new events.XFormsValueChanged().type) { + const question = event.target.closest('.question'); + + return question + ? [ + ...question.querySelectorAll( + `[data-${action}][data-event*="${event.type}"]` + ), + ] + : []; } }, @@ -177,66 +239,77 @@

js/calculate.js

* @param {'setvalue' | 'setgeopoint'} action - the action to perform. * @param {CustomEvent} [event] - the event type that triggered the action. */ - performAction( action, event ) { - if ( !event ) { + performAction(action, event) { + if (!event) { return; } - if ( !this.form ) { - throw new Error( `${action} action not correctly instantiated with form property.` ); + if (!this.form) { + throw new Error( + `${action} action not correctly instantiated with form property.` + ); } - const nodes = this._getNodesForAction( action, event ); + const nodes = this._getNodesForAction(action, event); - nodes.forEach( actionControl => { - const name = this.form.input.getName( actionControl ); - const dataNodesObj = this.form.model.node( name ); + nodes.forEach((actionControl) => { + const name = this.form.input.getName(actionControl); + const dataNodesObj = this.form.model.node(name); const dataNodes = dataNodesObj.getElements(); const props = { name, - dataType: this.form.input.getXmlType( actionControl ), - relevantExpr: this.form.input.getRelevant( actionControl ), - index: event.detail && typeof event.detail.repeatIndex !== 'undefined' ? event.detail.repeatIndex : 0, + dataType: this.form.input.getXmlType(actionControl), + relevantExpr: this.form.input.getRelevant(actionControl), + index: + event.detail && + typeof event.detail.repeatIndex !== 'undefined' + ? event.detail.repeatIndex + : 0, dataNodesObj, type: action, }; - if ( action === 'setvalue' ) { + if (action === 'setvalue') { props.expr = actionControl.dataset.setvalue; } - if ( dataNodes.length > 1 && event.type !== new events.NewRepeat().type && event.type !== new events.XFormsValueChanged().type ) { + if ( + dataNodes.length > 1 && + event.type !== new events.NewRepeat().type && + event.type !== new events.XFormsValueChanged().type + ) { /* * This case is the consequence of the decision to place action elements that are siblings of bind in the XForm * as a separate group (.or-setvalue-items, .or-setgeopoint-items), instead of in the Form DOM in the locations where they belong. * This occurs when update is called when multiple repeats are present. * For now this is only relevant for events that are *not* odk-new-repeat and *not* xforms-value-changed. */ - dataNodes.forEach( ( el, index ) => { - const obj = Object.create( props ); + dataNodes.forEach((el, index) => { + const obj = Object.create(props); const control = actionControl; obj.index = index; - this._updateCalc( control, obj ); - } ); - - } else if ( event.type === new events.XFormsValueChanged().type ) { + this._updateCalc(control, obj); + }); + } else if (event.type === new events.XFormsValueChanged().type) { // Control for xforms-value-changed is located elsewhere, or does not exist. // First we test if the control can be found by looking for the same index as the trigger - let control = this.form.input.find( props.name, props.index ); - if ( !control ){ + let control = this.form.input.find(props.name, props.index); + if (!control) { // In case the trigger was inside a repeat, but the target is not. props.index = 0; - control = this.form.input.find( props.name, 0 ); + control = this.form.input.find(props.name, 0); } - this._updateCalc( control, props ); - } else if ( dataNodes[ props.index ] ) { + this._updateCalc(control, props); + } else if (dataNodes[props.index]) { const control = actionControl; - this._updateCalc( control, props ); + this._updateCalc(control, props); } else { - console.error( 'performAction called for node that does not exist in model.' ); + console.error( + 'performAction called for node that does not exist in model.' + ); } - } ); + }); }, /** * Updates a calculation. @@ -245,37 +318,58 @@

js/calculate.js

* @param {*} props - properties of a calculation element * @param {boolean} [emptyNonRelevant] - Whether to set the calculation result to empty if non-relevant */ - _updateCalc( control, props, emptyNonRelevant ) { - if ( !emptyNonRelevant && props.type !== 'setvalue' && props.type !== 'setgeopoint' && this._hasNeverBeenRelevant( control, props ) && !this._isRelevant( props ) ){ + _updateCalc(control, props, emptyNonRelevant) { + if ( + !emptyNonRelevant && + props.type !== 'setvalue' && + props.type !== 'setgeopoint' && + this._hasNeverBeenRelevant(control, props) && + !this._isRelevant(props) + ) { return; } - if ( props.type === 'setgeopoint' ) { + if (props.type === 'setgeopoint') { const options = { enableHighAccuracy: true, maximumAge: 0, }; - getCurrentPosition( options ).then( ( { geopoint } ) => { - this._updateValue( control, props, geopoint ); - } ).catch( () => { - this._updateValue( control, props, '' ); - } ); + getCurrentPosition(options) + .then(({ geopoint }) => { + this._updateValue(control, props, geopoint); + }) + .catch(() => { + this._updateValue(control, props, ''); + }); return; } - const empty = emptyNonRelevant ? !this._isRelevant( props ) : false; + const empty = emptyNonRelevant ? !this._isRelevant(props) : false; // Not sure if using 'string' is always correct - const newExpr = this.form.replaceChoiceNameFn( props.expr, 'string', props.name, props.index ); + const newExpr = this.form.replaceChoiceNameFn( + props.expr, + 'string', + props.name, + props.index + ); // It is possible that the fixed expr is '' which causes an error in XPath // const xpathType = this.form.input.getInputType( control ) === 'number' ? 'number' : 'string'; - const result = !empty && newExpr ? this.form.model.evaluate( newExpr, 'string', props.name, props.index ) : ''; + const result = + !empty && newExpr + ? this.form.model.evaluate( + newExpr, + 'string', + props.name, + props.index + ) + : ''; // Filter the result set to only include the target node - this._updateValue( control, props, result ); + this._updateValue(control, props, result); }, /** @@ -285,19 +379,19 @@

js/calculate.js

* @param {*} props - properties of a calculation element * @param {*} result - result of a calculation */ - _updateValue( control, props, result ) { + _updateValue(control, props, result) { // Filter the result set to only include the target node - props.dataNodesObj.setIndex( props.index ); + props.dataNodesObj.setIndex(props.index); const existingModelValue = props.dataNodesObj.getVal(); // Set the value - props.dataNodesObj.setVal( result, props.dataType ); + props.dataNodesObj.setVal(result, props.dataType); const newModelValue = props.dataNodesObj.getVal(); // This is okay for an xforms-value-changed action (may be no form control) - if ( !control ) { + if (!control) { return; } @@ -305,15 +399,18 @@

js/calculate.js

// of the node, that we already have... // We should not use value "result" here because node.setVal() may have done a data type conversion - if ( existingModelValue !== newModelValue ) { - this.form.input.setVal( control, newModelValue ); + if (existingModelValue !== newModelValue) { + this.form.input.setVal(control, newModelValue); /* * We need to specifically call validate on the question itself, because the validationUpdate * in the evaluation cascade only updates questions with a _dependency_ on this question. */ - if ( control.type !== 'hidden' && config.validateContinuously === true ) { - this.form.validateInput( control ); + if ( + control.type !== 'hidden' && + config.validateContinuously === true + ) { + this.form.validateInput(control); } } }, @@ -324,12 +421,19 @@

js/calculate.js

* @param {*} props - properties of a node * @return {boolean} whether the node is relevant */ - _isRelevant( props ) { - let relevant = props.relevantExpr ? this.form.model.evaluate( props.relevantExpr, 'boolean', props.name, props.index ) : true; + _isRelevant(props) { + let relevant = props.relevantExpr + ? this.form.model.evaluate( + props.relevantExpr, + 'boolean', + props.name, + props.index + ) + : true; // Only look at ancestors if self is relevant. - if ( relevant ) { - const pathParts = props.name.split( '/' ); + if (relevant) { + const pathParts = props.name.split('/'); /* * First determine immediate group parent of node, which will always be in correct location in DOM. This is where * we can use the index to be guaranteed to get the correct node. @@ -341,69 +445,107 @@

js/calculate.js

* * Note: getting the parents of control wouldn't work for nodes inside #calculated-items! */ - const parentPath = pathParts.splice( 0, pathParts.length - 1 ).join( '/' ); + const parentPath = pathParts + .splice(0, pathParts.length - 1) + .join('/'); let startElement; - if ( props.index === 0 ) { - startElement = this.form.view.html.querySelector( `.or-group[name="${parentPath}"],.or-group-data[name="${parentPath}"]` ); + if (props.index === 0) { + startElement = this.form.view.html.querySelector( + `.or-group[name="${parentPath}"],.or-group-data[name="${parentPath}"]` + ); } else { - startElement = this.form.view.html.querySelectorAll( `.or-repeat[name="${parentPath}"]` )[ props.index ] || - this.form.view.html.querySelectorAll( `.or-group[name="${parentPath}"],.or-group-data[name="${parentPath}"]` )[ props.index ]; + startElement = + this.form.view.html.querySelectorAll( + `.or-repeat[name="${parentPath}"]` + )[props.index] || + this.form.view.html.querySelectorAll( + `.or-group[name="${parentPath}"],.or-group-data[name="${parentPath}"]` + )[props.index]; } - const ancestorGroups = startElement ? [ startElement ].concat( getAncestors( startElement, '.or-group, .or-group-data' ) ) : []; + const ancestorGroups = startElement + ? [startElement].concat( + getAncestors(startElement, '.or-group, .or-group-data') + ) + : []; - if ( ancestorGroups.length ) { + if (ancestorGroups.length) { // Start at the highest level, and traverse down to the immediate parent group. - relevant = ancestorGroups.filter( el => el.matches( '[data-relevant]' ) ).map( group => { - const nm = this.form.input.getName( group ); - - return { - context: nm, - // thankfully relevants on repeats are not possible with XLSForm-produced forms - index: [ ...this.form.view.html.querySelectorAll( `.or-group[name="${nm}"], .or-group-data[name="${nm}"]` ) ].indexOf( group ), // performance.... - expr: this.form.input.getRelevant( group ) - }; - } ).concat( [ { - context: props.name, - index: props.index, - expr: props.relevantExpr - } ] ).every( item => item.expr ? this.form.model.evaluate( item.expr, 'boolean', item.context, item.index ) : true ); + relevant = ancestorGroups + .filter((el) => el.matches('[data-relevant]')) + .map((group) => { + const nm = this.form.input.getName(group); + + return { + context: nm, + // thankfully relevants on repeats are not possible with XLSForm-produced forms + index: [ + ...this.form.view.html.querySelectorAll( + `.or-group[name="${nm}"], .or-group-data[name="${nm}"]` + ), + ].indexOf(group), // performance.... + expr: this.form.input.getRelevant(group), + }; + }) + .concat([ + { + context: props.name, + index: props.index, + expr: props.relevantExpr, + }, + ]) + .every((item) => + item.expr + ? this.form.model.evaluate( + item.expr, + 'boolean', + item.context, + item.index + ) + : true + ); } } return relevant; }, - _hasNeverBeenRelevant( control, props ){ - if ( control && control.closest( '.pre-init' ) ){ + _hasNeverBeenRelevant(control, props) { + if (control && control.closest('.pre-init')) { return true; } // Check parents including when the calculation has no form control. - const pathParts = props.name.split( '/' ); + const pathParts = props.name.split('/'); /* - * First determine immediate group parent of node, which will always be in correct location in DOM. This is where - * we can use the index to be guaranteed to get the correct node. - * (also for nodes in #calculated-items). - * - * Then get all the group parents of that node. - * - * TODO: determine index at every level to properly support repeats and nested repeats - * - * Note: getting the parents of control wouldn't work for nodes inside #calculated-items! - */ - const parentPath = pathParts.splice( 0, pathParts.length - 1 ).join( '/' ); + * First determine immediate group parent of node, which will always be in correct location in DOM. This is where + * we can use the index to be guaranteed to get the correct node. + * (also for nodes in #calculated-items). + * + * Then get all the group parents of that node. + * + * TODO: determine index at every level to properly support repeats and nested repeats + * + * Note: getting the parents of control wouldn't work for nodes inside #calculated-items! + */ + const parentPath = pathParts.splice(0, pathParts.length - 1).join('/'); let startElement; - if ( props.index === 0 ) { - startElement = this.form.view.html.querySelector( `.or-group[name="${parentPath}"],.or-group-data[name="${parentPath}"]` ); + if (props.index === 0) { + startElement = this.form.view.html.querySelector( + `.or-group[name="${parentPath}"],.or-group-data[name="${parentPath}"]` + ); } else { - startElement = this.form.view.html.querySelectorAll( `.or-repeat[name="${parentPath}"]` )[ props.index ] || - this.form.view.html.querySelectorAll( `.or-group[name="${parentPath}"],.or-group-data[name="${parentPath}"]` )[ props.index ]; + startElement = + this.form.view.html.querySelectorAll( + `.or-repeat[name="${parentPath}"]` + )[props.index] || + this.form.view.html.querySelectorAll( + `.or-group[name="${parentPath}"],.or-group-data[name="${parentPath}"]` + )[props.index]; } - return startElement ? !!startElement.closest( '.pre-init' ) : false; - } - + return startElement ? !!startElement.closest('.pre-init') : false; + }, }; diff --git a/docs/js_dom-utils.js.html b/docs/js_dom-utils.js.html index e57e05a4e..27b54cc28 100644 --- a/docs/js_dom-utils.js.html +++ b/docs/js_dom-utils.js.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -59,8 +59,8 @@

js/dom-utils.js

* @param {string} [selector] - A CSS selector for siblings (not for self). * @return {Array<Node>} Array of sibling nodes plus target element. */ -function getSiblingElementsAndSelf( element, selector ) { - return _getSiblingElements( element, selector, true ); +function getSiblingElementsAndSelf(element, selector) { + return _getSiblingElements(element, selector, true); } /** @@ -71,8 +71,8 @@

js/dom-utils.js

* @param {string} [selector] - A CSS selector. * @return {Array<Node>} Array of sibling nodes. */ -function getSiblingElements( element, selector ) { - return _getSiblingElements( element, selector ); +function getSiblingElements(element, selector) { + return _getSiblingElements(element, selector); } /** @@ -82,12 +82,12 @@

js/dom-utils.js

* @param {string} [selector] - A CSS selector. * @return {Node} First sibling element in DOM order */ -function getSiblingElement( element, selector = '*' ){ +function getSiblingElement(element, selector = '*') { let found; let current = element.parentElement.firstElementChild; - while ( current && !found ) { - if ( current !== element && current.matches( selector ) ) { + while (current && !found) { + if (current !== element && current.matches(selector)) { found = current; } current = current.nextElementSibling; @@ -104,13 +104,16 @@

js/dom-utils.js

* @param {boolean} [includeSelf] - Whether to include self. * @return {Array<Node>} Array of sibling nodes. */ -function _getSiblingElements( element, selector = '*', includeSelf = false ) { +function _getSiblingElements(element, selector = '*', includeSelf = false) { const results = []; let current = element.parentElement.firstElementChild; - while ( current ) { - if ( ( current === element && includeSelf ) || ( current !== element && current.matches( selector ) ) ){ - results.push( current ); + while (current) { + if ( + (current === element && includeSelf) || + (current !== element && current.matches(selector)) + ) { + results.push(current); } current = current.nextElementSibling; } @@ -127,16 +130,19 @@

js/dom-utils.js

* @param {string} [endSelector] - A CSS selector indicating where to stop. It will include this element if matched by the filter. * @return {Array<Node>} Array of ancestors. */ -function getAncestors( element, filterSelector = '*', endSelector ) { +function getAncestors(element, filterSelector = '*', endSelector = null) { const ancestors = []; let parent = element.parentElement; - while ( parent ) { - if ( parent.matches( filterSelector ) ) { + while (parent) { + if (parent.matches(filterSelector)) { // document order - ancestors.unshift( parent ); + ancestors.unshift(parent); } - parent = endSelector && parent.matches( endSelector ) ? null : parent.parentElement; + parent = + endSelector && parent.matches(endSelector) + ? null + : parent.parentElement; } return ancestors; @@ -151,15 +157,22 @@

js/dom-utils.js

* @param {string} [endSelector] - A CSS selector indicating where to stop. It will include this element if matched by the filter. * @return {Node} Closest ancestor. */ -function closestAncestorUntil( element, filterSelector = '*', endSelector ) { +function closestAncestorUntil( + element, + filterSelector = '*', + endSelector = null +) { let parent = element.parentElement; let found = null; - while ( parent && !found ) { - if ( parent.matches( filterSelector ) ) { + while (parent && !found) { + if (parent.matches(filterSelector)) { found = parent; } - parent = endSelector && parent.matches( endSelector ) ? null : parent.parentElement; + parent = + endSelector && parent.matches(endSelector) + ? null + : parent.parentElement; } return found; @@ -172,9 +185,8 @@

js/dom-utils.js

* @param {string} selector - A CSS selector. * @return {Array<Node>} Array of child elements. */ -function getChildren( element, selector = '*' ) { - return [ ...element.children ] - .filter( el => el.matches( selector ) ); +function getChildren(element, selector = '*') { + return [...element.children].filter((el) => el.matches(selector)); } /** @@ -184,9 +196,8 @@

js/dom-utils.js

* @param {string} selector - A CSS selector. * @return {Node} - First child element. */ -function getChild( element, selector = '*' ) { - return [ ...element.children ] - .find( el => el.matches( selector ) ); +function getChild(element, selector = '*') { + return [...element.children].find((el) => el.matches(selector)); } /** @@ -196,23 +207,23 @@

js/dom-utils.js

* @param {Node} element - Target element. * @return {undefined} */ -function empty( element ) { - [ ...element.children ].forEach( el => el.remove() ); +function empty(element) { + [...element.children].forEach((el) => el.remove()); } /** * @param {Element} el - Target node * @return {boolean} Whether previous sibling has the same node name */ -function hasPreviousSiblingElementSameName( el ) { +function hasPreviousSiblingElementSameName(el) { let found = false; - const nodeName = el.nodeName; + const { nodeName } = el; el = el.previousSibling; - while ( el ) { + while (el) { // Ignore any sibling text and comment nodes (e.g. whitespace with a newline character) // also deal with repeats that have non-repeat siblings in between them, event though that would be a bug. - if ( el.nodeName && el.nodeName === nodeName ) { + if (el.nodeName && el.nodeName === nodeName) { found = true; break; } @@ -226,15 +237,15 @@

js/dom-utils.js

* @param {Element} el - Target node * @return {boolean} Whether next sibling has the same node name */ -function hasNextSiblingElementSameName( el ) { +function hasNextSiblingElementSameName(el) { let found = false; - const nodeName = el.nodeName; + const { nodeName } = el; el = el.nextSibling; - while ( el ) { + while (el) { // Ignore any sibling text and comment nodes (e.g. whitespace with a newline character) // also deal with repeats that have non-repeat siblings in between them, event though that would be a bug. - if ( el.nodeName && el.nodeName === nodeName ) { + if (el.nodeName && el.nodeName === nodeName) { found = true; break; } @@ -248,8 +259,11 @@

js/dom-utils.js

* @param {Element} el - Target node * @return {boolean} Whether a sibling has the same node name */ -function hasSiblingElementSameName( el ) { - return hasNextSiblingElementSameName( el ) || hasPreviousSiblingElementSameName( el ); +function hasSiblingElementSameName(el) { + return ( + hasNextSiblingElementSameName(el) || + hasPreviousSiblingElementSameName(el) + ); } /** @@ -257,12 +271,15 @@

js/dom-utils.js

* @param {string} content - Text content to look for * @return {boolean} Whether previous comment sibling has given text content */ -function hasPreviousCommentSiblingWithContent( node, content ) { +function hasPreviousCommentSiblingWithContent(node, content) { let found = false; node = node.previousSibling; - while ( node ) { - if ( node.nodeType === Node.COMMENT_NODE && node.textContent === content ) { + while (node) { + if ( + node.nodeType === Node.COMMENT_NODE && + node.textContent === content + ) { found = true; break; } @@ -272,7 +289,6 @@

js/dom-utils.js

return found; } - /** * Creates an XPath from a node * @@ -281,37 +297,43 @@

js/dom-utils.js

* @param {boolean} [includePosition] - Whether or not to include the positions `/path/to/repeat[2]/node` * @return {string} XPath */ -function getXPath( node, rootNodeName = '#document', includePosition = false ) { +function getXPath(node, rootNodeName = '#document', includePosition = false) { let index; const steps = []; let position = ''; - if ( !node || node.nodeType !== 1 ) { + if (!node || node.nodeType !== 1) { return null; } - const nodeName = node.nodeName; + const { nodeName } = node; let parent = node.parentElement; let parentName = parent ? parent.nodeName : null; - if ( includePosition ) { - index = getRepeatIndex( node ); - if ( index > 0 ) { + if (includePosition) { + index = getRepeatIndex(node); + if (index > 0) { position = `[${index + 1}]`; } } - steps.push( nodeName + position ); - - while ( parent && parentName !== rootNodeName && parentName !== '#document' ) { - if ( includePosition ) { - index = getRepeatIndex( parent ); - position = hasSiblingElementSameName( parent ) ? `[${index + 1}]` : ''; + steps.push(nodeName + position); + + while ( + parent && + parentName !== rootNodeName && + parentName !== '#document' + ) { + if (includePosition) { + index = getRepeatIndex(parent); + position = hasSiblingElementSameName(parent) + ? `[${index + 1}]` + : ''; } - steps.push( parentName + position ); + steps.push(parentName + position); parent = parent.parentElement; parentName = parent ? parent.nodeName : null; } - return `/${steps.reverse().join( '/' )}`; + return `/${steps.reverse().join('/')}`; } /** @@ -320,14 +342,14 @@

js/dom-utils.js

* @param {Element} node - XML node * @return {number} index */ -function getRepeatIndex( node ) { +function getRepeatIndex(node) { let index = 0; - const nodeName = node.nodeName; + const { nodeName } = node; let prevSibling = node.previousSibling; - while ( prevSibling ) { + while (prevSibling) { // ignore any sibling text and comment nodes (e.g. whitespace with a newline character) - if ( prevSibling.nodeName && prevSibling.nodeName === nodeName ) { + if (prevSibling.nodeName && prevSibling.nodeName === nodeName) { index++; } prevSibling = prevSibling.previousSibling; @@ -357,11 +379,11 @@

js/dom-utils.js

* @param {string} key - name of the stored data * @param {object} obj - stored data */ - put: function( element, key, obj ) { - if ( !this._storage.has( element ) ) { - this._storage.set( element, new Map() ); + put(element, key, obj) { + if (!this._storage.has(element)) { + this._storage.set(element, new Map()); } - this._storage.get( element ).set( key, obj ); + this._storage.get(element).set(key, obj); }, /** * Return object from element storage. @@ -370,10 +392,10 @@

js/dom-utils.js

* @param {string} key - name of the stored data * @return {object} stored data object */ - get: function( element, key ) { - const item = this._storage.get( element ); + get(element, key) { + const item = this._storage.get(element); - return item ? item.get( key ) : item; + return item ? item.get(key) : item; }, /** * Checkes whether element has given storage item. @@ -382,10 +404,10 @@

js/dom-utils.js

* @param {string} key - name of the stored data * @return {boolean} whether data is present */ - has: function( element, key ) { - const item = this._storage.get( element ); + has(element, key) { + const item = this._storage.get(element); - return item && item.has( key ); + return item && item.has(key); }, /** * Removes item from element storage. Removes element storage if empty. @@ -394,90 +416,100 @@

js/dom-utils.js

* @param {string} key - name of the stored data * @return {object} removed data object */ - remove: function( element, key ) { - var ret = this._storage.get( element ).delete( key ); - if ( !this._storage.get( key ).size === 0 ) { - this._storage.delete( element ); + remove(element, key) { + const ret = this._storage.get(element).delete(key); + if (!this._storage.get(key).size === 0) { + this._storage.delete(element); } return ret; - } + }, }; -class MutationsTracker{ - - constructor( el = document.documentElement ){ +class MutationsTracker { + constructor(el = document.documentElement) { let currentMutations = 0; let previousMutations = currentMutations; this.classChanges = new WeakMap(); this.quiet = true; - const mutationObserver = new MutationObserver( mutations => { - mutations.forEach( mutation => { + const mutationObserver = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { currentMutations++; - if ( mutation.type === 'attributes' && mutation.attributeName === 'class' ){ - const trackedClasses = this.classChanges.get( mutation.target ) || []; - trackedClasses.forEach( obj => { - if( mutation.target.classList.contains( obj.className ) ){ + if ( + mutation.type === 'attributes' && + mutation.attributeName === 'class' + ) { + const trackedClasses = + this.classChanges.get(mutation.target) || []; + trackedClasses.forEach((obj) => { + if (mutation.target.classList.contains(obj.className)) { obj.completed = true; - this.classChanges.set( mutation.target, trackedClasses ); + this.classChanges.set( + mutation.target, + trackedClasses + ); } - } ); + }); } - } ); - } ); + }); + }); - mutationObserver.observe( el, { + mutationObserver.observe(el, { attributes: true, characterData: true, childList: true, subtree: true, attributeOldValue: true, - characterDataOldValue: true - } ); + characterDataOldValue: true, + }); - const checkInterval = setInterval( () => { - if ( previousMutations === currentMutations ){ + const checkInterval = setInterval(() => { + if (previousMutations === currentMutations) { this.quiet = true; mutationObserver.disconnect(); - clearInterval( checkInterval ); + clearInterval(checkInterval); } else { this.quiet = false; previousMutations = currentMutations; } - }, 100 ); + }, 100); } - _resolveWhenTrue( fn ){ - if ( typeof fn !== 'function' ){ + _resolveWhenTrue(fn) { + if (typeof fn !== 'function') { return Promise.reject(); } - return new Promise( resolve => { - const checkInterval = setInterval( () => { - if ( fn.call( this ) ){ - clearInterval( checkInterval ); + return new Promise((resolve) => { + const checkInterval = setInterval(() => { + if (fn.call(this)) { + clearInterval(checkInterval); resolve(); } - }, 10 ); - } ); + }, 10); + }); } - waitForClassChange( element, className ){ - const trackedClasses = this.classChanges.get( element ) || []; + waitForClassChange(element, className) { + const trackedClasses = this.classChanges.get(element) || []; - if ( !trackedClasses.some( obj => obj.className === className ) ){ - trackedClasses.push( { className } ); - this.classChanges.set( element, trackedClasses ); + if (!trackedClasses.some((obj) => obj.className === className)) { + trackedClasses.push({ className }); + this.classChanges.set(element, trackedClasses); } - return this._resolveWhenTrue( () => this.classChanges.get( element ).find( obj => obj.className === className ).completed ); + return this._resolveWhenTrue( + () => + this.classChanges + .get(element) + .find((obj) => obj.className === className).completed + ); } - waitForQuietness(){ - return this._resolveWhenTrue( () => this.quiet ); + waitForQuietness() { + return this._resolveWhenTrue(() => this.quiet); } - } export { @@ -500,7 +532,7 @@

js/dom-utils.js

hasSiblingElementSameName, closestAncestorUntil, empty, - MutationsTracker + MutationsTracker, }; diff --git a/docs/js_download-utils.js.html b/docs/js_download-utils.js.html index 7db1f1a0d..592749c7d 100644 --- a/docs/js_download-utils.js.html +++ b/docs/js_download-utils.js.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -55,17 +55,22 @@

js/download-utils.js

* @param {string} objectUrl - The objectUrl to download * @param {string} fileName - The filename of the file */ -function updateDownloadLink( anchor, objectUrl, fileName ) { - if ( window.updateDownloadLinkIe11 ) { - return window.updateDownloadLinkIe11( ...arguments ); +function updateDownloadLink(anchor, objectUrl, fileName, ...rest) { + if (window.updateDownloadLinkIe11) { + return window.updateDownloadLinkIe11( + anchor, + objectUrl, + fileName, + ...rest + ); } - anchor.setAttribute( 'href', objectUrl || '' ); - anchor.setAttribute( 'download', fileName || '' ); + anchor.setAttribute('href', objectUrl || ''); + anchor.setAttribute('download', fileName || ''); } // Export as default to facilitate overriding this function. export default { - updateDownloadLink + updateDownloadLink, }; diff --git a/docs/js_event.js.html b/docs/js_event.js.html index 5ee324368..6b73e56b9 100644 --- a/docs/js_event.js.html +++ b/docs/js_event.js.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -59,8 +59,8 @@

js/event.js

* @param {*} detail - Data to be passed with event * @return {CustomEvent} Custom "dataupdate" event */ -function DataUpdate( detail ) { - return new CustomEvent( 'dataupdate', { detail } ); +function DataUpdate(detail) { + return new CustomEvent('dataupdate', { detail }); } /** @@ -69,7 +69,7 @@

js/event.js

* @return {CustomEvent} Custom "fakefocus" event (bubbling) */ function FakeFocus() { - return new CustomEvent( 'fakefocus', { bubbles: true } ); + return new CustomEvent('fakefocus', { bubbles: true }); } /** @@ -78,7 +78,7 @@

js/event.js

* @return {CustomEvent} Custom "applyfocus" event */ function ApplyFocus() { - return new CustomEvent( 'applyfocus' ); + return new CustomEvent('applyfocus'); } /** @@ -87,7 +87,7 @@

js/event.js

* @return {CustomEvent} Custom "pageflip" event (bubbling) */ function PageFlip() { - return new CustomEvent( 'pageflip', { bubbles: true } ); + return new CustomEvent('pageflip', { bubbles: true }); } /** @@ -96,8 +96,8 @@

js/event.js

* @param {*} detail - Data to be passed with event * @return {CustomEvent} Custom "removed" event (bubbling) */ -function Removed( detail ) { - return new CustomEvent( 'removed', { detail, bubbles: true } ); +function Removed(detail) { + return new CustomEvent('removed', { detail, bubbles: true }); } /** @@ -107,7 +107,7 @@

js/event.js

*@return {CustomEvent} Custom "odk-instance-first-load" event (bubbling) */ function InstanceFirstLoad() { - return new CustomEvent( 'odk-instance-first-load', { bubbles: true } ); + return new CustomEvent('odk-instance-first-load', { bubbles: true }); } /** @@ -117,8 +117,8 @@

js/event.js

* @param {{repeatPath: string, repeatIndex: number, trigger: string}} detail - Data to be passed with event. * @return {CustomEvent} Custom "odk-new-repeat" event (bubbling) */ -function NewRepeat( detail ) { - return new CustomEvent( 'odk-new-repeat', { detail, bubbles: true } ); +function NewRepeat(detail) { + return new CustomEvent('odk-new-repeat', { detail, bubbles: true }); } /** @@ -127,8 +127,8 @@

js/event.js

* @param {{repeatPath: string, repeatIndex: number, trigger: string}} detail - Data to be passed with event. * @return {CustomEvent} Custom "odk-new-repeat" event (bubbling) */ -function AddRepeat( detail ) { - return new CustomEvent( 'addrepeat', { detail, bubbles: true } ); +function AddRepeat(detail) { + return new CustomEvent('addrepeat', { detail, bubbles: true }); } /** @@ -137,7 +137,7 @@

js/event.js

* @return {CustomEvent} Custom "removerepeat" event (bubbling) */ function RemoveRepeat() { - return new CustomEvent( 'removerepeat', { bubbles: true } ); + return new CustomEvent('removerepeat', { bubbles: true }); } /** @@ -146,7 +146,7 @@

js/event.js

* @return {CustomEvent} Custom "changelanguage" event (bubbling) */ function ChangeLanguage() { - return new CustomEvent( 'changelanguage', { bubbles: true } ); + return new CustomEvent('changelanguage', { bubbles: true }); } /** @@ -155,7 +155,7 @@

js/event.js

* @return {Event} The regular HTML "change" event (bubbling) */ function Change() { - return new Event( 'change', { bubbles: true } ); + return new Event('change', { bubbles: true }); } /** @@ -165,8 +165,8 @@

js/event.js

* @param {{repeatIndex: number}} detail - Data to be passed with event. * @return {CustomEvent} Custom "xforms-value-changed" event (bubbling). */ -function XFormsValueChanged( detail ) { - return new CustomEvent( 'xforms-value-changed', { detail, bubbles: true } ); +function XFormsValueChanged(detail) { + return new CustomEvent('xforms-value-changed', { detail, bubbles: true }); } /** @@ -175,7 +175,7 @@

js/event.js

* @return {Event} "input" event (bubbling) */ function Input() { - return new Event( 'input', { bubbles: true } ); + return new Event('input', { bubbles: true }); } /** @@ -184,7 +184,7 @@

js/event.js

* @return {CustomEvent} Custom "inputupdate" event (bubbling) */ function InputUpdate() { - return new CustomEvent( 'inputupdate', { bubbles: true } ); + return new CustomEvent('inputupdate', { bubbles: true }); } /** @@ -193,17 +193,16 @@

js/event.js

* @return {CustomEvent} Custom "edited" event (bubbling) */ function Edited() { - return new CustomEvent( 'edited', { bubbles: true } ); + return new CustomEvent('edited', { bubbles: true }); } - /** * Before save event. * * @return {CustomEvent} Custom "edited" event (bubbling) */ function BeforeSave() { - return new CustomEvent( 'before-save', { bubbles: true } ); + return new CustomEvent('before-save', { bubbles: true }); } /** @@ -212,7 +211,7 @@

js/event.js

* @return {CustomEvent} Custom "validationcomplete" event (bubbling) */ function ValidationComplete() { - return new CustomEvent( 'validation-complete', { bubbles: true } ); + return new CustomEvent('validation-complete', { bubbles: true }); } /** @@ -221,7 +220,7 @@

js/event.js

* @return {CustomEvent} Custom "invalidated" event (bubbling) */ function Invalidated() { - return new CustomEvent( 'invalidated', { bubbles: true } ); + return new CustomEvent('invalidated', { bubbles: true }); } /** @@ -230,8 +229,8 @@

js/event.js

* @param {*} detail - Data to be passed with event * @return {CustomEvent} Custom "progressupdate" event (bubbling) */ -function ProgressUpdate( detail ) { - return new CustomEvent( 'progress-update', { detail, bubbles: true } ); +function ProgressUpdate(detail) { + return new CustomEvent('progress-update', { detail, bubbles: true }); } /** @@ -240,7 +239,7 @@

js/event.js

* @return {CustomEvent} Custom "gotoirrelevant" event (bubbling) */ function GoToIrrelevant() { - return new CustomEvent( 'goto-irrelevant', { bubbles: true } ); + return new CustomEvent('goto-irrelevant', { bubbles: true }); } /** @@ -250,11 +249,11 @@

js/event.js

* @return {CustomEvent} Custom "gotoinvisible" event (bubbling) */ function GoToInvisible() { - return new CustomEvent( 'goto-invisible', { bubbles: true } ); + return new CustomEvent('goto-invisible', { bubbles: true }); } function ChangeOption() { - return new CustomEvent( 'change-option', { bubbles: true } ); + return new CustomEvent('change-option', { bubbles: true }); } /** @@ -263,7 +262,7 @@

js/event.js

* @return {CustomEvent} Custom "printify" event (bubbling) */ function Printify() { - return new CustomEvent( 'printify', { bubbles: true } ); + return new CustomEvent('printify', { bubbles: true }); } /** @@ -272,11 +271,11 @@

js/event.js

* @return {CustomEvent} Custom "deprintify" event (bubbling) */ function DePrintify() { - return new CustomEvent( 'deprintify', { bubbles: true } ); + return new CustomEvent('deprintify', { bubbles: true }); } function UpdateMaxSize() { - return new CustomEvent( 'update-max-size', { bubbles: true } ); + return new CustomEvent('update-max-size', { bubbles: true }); } export default { @@ -304,7 +303,7 @@

js/event.js

ChangeOption, Printify, DePrintify, - UpdateMaxSize + UpdateMaxSize, }; diff --git a/docs/js_fake-dialog.js.html b/docs/js_fake-dialog.js.html index d9c0664f8..f3bb8c285 100644 --- a/docs/js_fake-dialog.js.html +++ b/docs/js_fake-dialog.js.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -63,8 +63,8 @@

js/fake-dialog.js

* @static * @param {string | DialogContentObj} content - Dialog content */ -function alert( content ) { - window.alert( content ); +function alert(content) { + window.alert(content); return Promise.resolve(); } @@ -73,10 +73,10 @@

js/fake-dialog.js

* @static * @param {string | DialogContentObj} content - Dialog content */ -function confirm( content ) { +function confirm(content) { const msg = content.message ? content.message : content; - return Promise.resolve( window.confirm( msg ) ); + return Promise.resolve(window.confirm(msg)); } /** @@ -84,14 +84,14 @@

js/fake-dialog.js

* @param {string | DialogContentObj} content - Dialog content * @param {string} def - Default input value */ -function prompt( content, def ) { - return Promise.resolve( window.prompt( content, def ) ); +function prompt(content, def) { + return Promise.resolve(window.prompt(content, def)); } export default { alert, confirm, - prompt + prompt, }; diff --git a/docs/js_fake-translator.js.html b/docs/js_fake-translator.js.html index d22f53b78..752228db0 100644 --- a/docs/js_fake-translator.js.html +++ b/docs/js_fake-translator.js.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -56,68 +56,72 @@

js/fake-translator.js

// This is NOT a complete list of all enketo-core UI strings. Use a parser to find // all strings. E.g. https://github.com/i18next/i18next-parser const SOURCE_STRINGS = { - 'constraint': { - 'invalid': 'Value not allowed', - 'required': 'This field is required' + constraint: { + invalid: 'Value not allowed', + required: 'This field is required', }, - 'filepicker': { - 'placeholder': 'Click here to upload file. (< __maxSize__)', - 'notFound': 'File __existing__ could not be found (leave unchanged if already submitted and you want to preserve it).', - 'waitingForPermissions': 'Waiting for user permissions.', - 'resetWarning': 'This will remove the __item__. Are you sure you want to do this?', - 'toolargeerror': 'File too large (> __maxSize__)', - 'file': 'file' + filepicker: { + placeholder: 'Click here to upload file. (< __maxSize__)', + notFound: + 'File __existing__ could not be found (leave unchanged if already submitted and you want to preserve it).', + waitingForPermissions: 'Waiting for user permissions.', + resetWarning: + 'This will remove the __item__. Are you sure you want to do this?', + toolargeerror: 'File too large (> __maxSize__)', + file: 'file', }, - 'drawwidget': { - 'drawing': 'drawing', - 'signature': 'signature', - 'annotation': 'file and drawing' + drawwidget: { + drawing: 'drawing', + signature: 'signature', + annotation: 'file and drawing', }, - 'form': { - 'required': 'required' + form: { + required: 'required', }, - 'geopicker': { - 'accuracy': 'accuracy (m)', - 'altitude': 'altitude (m)', - 'closepolygon': 'close polygon', - 'kmlcoords': 'KML coordinates', - 'kmlpaste': 'paste KML coordinates here', - 'latitude': 'latitude (x.y °)', - 'longitude': 'longitude (x.y °)', - 'points': 'points', - 'searchPlaceholder': 'search for place or address', - 'removePoint': 'This will completely remove the current geopoint from the list of geopoints and cannot be undone. Are you sure you want to do this?' + geopicker: { + accuracy: 'accuracy (m)', + altitude: 'altitude (m)', + closepolygon: 'close polygon', + kmlcoords: 'KML coordinates', + kmlpaste: 'paste KML coordinates here', + latitude: 'latitude (x.y °)', + longitude: 'longitude (x.y °)', + points: 'points', + searchPlaceholder: 'search for place or address', + removePoint: + 'This will completely remove the current geopoint from the list of geopoints and cannot be undone. Are you sure you want to do this?', }, - 'selectpicker': { - 'noneselected': 'none selected', - 'numberselected': '__number__ selected' + selectpicker: { + noneselected: 'none selected', + numberselected: '__number__ selected', }, - 'imagemap': { - 'svgNotFound': 'SVG image could not be found' + imagemap: { + svgNotFound: 'SVG image could not be found', }, - 'rankwidget': { - 'tapstart': 'Tap to start', - 'clickstart': 'Click to start' + rankwidget: { + tapstart: 'Tap to start', + clickstart: 'Click to start', }, - 'widget': { - 'comment': { - 'update': 'Update' - } + widget: { + comment: { + update: 'Update', + }, + }, + alert: { + gotonotfound: { + msg: "Failed to find question '__path__' in form. Is it a valid path?", + }, + valuehasspaces: { + multiple: + 'Select multiple question has an illegal value "__value__" that contains a space.', + }, }, - 'alert': { - 'gotonotfound': { - 'msg': 'Failed to find question \'__path__\' in form. Is it a valid path?' + confirm: { + repeatremove: { + heading: 'Delete this group of responses?', + msg: 'This action is irreversible. Are you sure you want to proceed?', }, - 'valuehasspaces': { - 'multiple': 'Select multiple question has an illegal value "__value__" that contains a space.' - } }, - 'confirm': { - 'repeatremove': { - 'heading': 'Delete this group of responses?', - 'msg': 'This action is irreversible. Are you sure you want to proceed?' - } - } }; /** @@ -136,18 +140,18 @@

js/fake-translator.js

* @param {object} [options] - Translation options object * @return {string} Translation output */ -function t( key, options ) { +function t(key, options) { let str = ''; let target = SOURCE_STRINGS; // crude string getter - key.split( '.' ).forEach( part => { - target = target ? target[ part ] : ''; + key.split('.').forEach((part) => { + target = target ? target[part] : ''; str = target; - } ); + }); // crude interpolator options = options || {}; - str = str.replace( /__([^_]+)__/, ( match, p1 ) => options[ p1 ] ); + str = str.replace(/__([^_]+)__/, (match, p1) => options[p1]); // Enable line below to switch to fake Arabic, very useful for testing RTL // var AR = 'العربية '; return str.split( '' ).map( function( char, i ) { return AR[ i % AR.length ];} ).join( '' ); diff --git a/docs/js_file-manager.js.html b/docs/js_file-manager.js.html index 9b7e77b6b..ba0fadbe7 100644 --- a/docs/js_file-manager.js.html +++ b/docs/js_file-manager.js.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -60,9 +60,10 @@

js/file-manager.js

import $ from 'jquery'; +import { t } from 'enketo/translator'; import { getFilename, dataUriToBlobSync } from './utils'; + const fileManager = {}; -import { t } from 'enketo/translator'; const URL_RE = /[a-zA-Z0-9+-.]+?:\/\//; /** @@ -73,7 +74,7 @@

js/file-manager.js

* * @return {Promise|boolean|Error} promise boolean or rejection with Error */ -fileManager.init = () => { return Promise.resolve( true ); }; +fileManager.init = () => Promise.resolve(true); /** * @static @@ -83,7 +84,7 @@

js/file-manager.js

* * @return {boolean} [description] */ -fileManager.isWaitingForPermissions = () => { return false; }; +fileManager.isWaitingForPermissions = () => false; /** * @static @@ -97,35 +98,38 @@

js/file-manager.js

* @param {?string|object} subject - File or filename in local storage * @return {Promise|string|Error} promise url string or rejection with Error */ -fileManager.getFileUrl = subject => { - return new Promise( ( resolve, reject ) => { +fileManager.getFileUrl = (subject) => + new Promise((resolve, reject) => { let error; - if ( !subject ) { - resolve( null ); - } else if ( typeof subject === 'string' ) { + if (!subject) { + resolve(null); + } else if (typeof subject === 'string') { // TODO obtain from storage as http URL or objectURL // or from model for default binary files // Very crude URL checker which is fine for now, // because at this point we don't expect anything other than jr:// - if ( URL_RE.test( subject ) ) { - resolve( subject ); + if (URL_RE.test(subject)) { + resolve(subject); } else { - reject( 'no!' ); + reject('no!'); } - } else if ( typeof subject === 'object' ) { - if ( fileManager.isTooLarge( subject ) ) { - error = new Error( t( 'filepicker.toolargeerror', { maxSize: fileManager.getMaxSizeReadable() } ) ); - reject( error ); + } else if (typeof subject === 'object') { + if (fileManager.isTooLarge(subject)) { + error = new Error( + t('filepicker.toolargeerror', { + maxSize: fileManager.getMaxSizeReadable(), + }) + ); + reject(error); } else { - resolve( URL.createObjectURL( subject ) ); + resolve(URL.createObjectURL(subject)); } } else { - reject( new Error( 'Unknown error occurred' ) ); + reject(new Error('Unknown error occurred')); } - } ); -}; + }); /** * @static @@ -138,14 +142,14 @@

js/file-manager.js

* @param {?string|object} subject - File or filename in local storage * @return {Promise|string|Error} promise url string or rejection with Error */ -fileManager.getObjectUrl = subject => fileManager.getFileUrl( subject ) - .then( url => { - if ( /https?:\/\//.test( url ) ) { - return fileManager.urlToBlob( url ).then( URL.createObjectURL ); +fileManager.getObjectUrl = (subject) => + fileManager.getFileUrl(subject).then((url) => { + if (/https?:\/\//.test(url)) { + return fileManager.urlToBlob(url).then(URL.createObjectURL); } return url; - } ); + }); /** * @static @@ -154,17 +158,17 @@

js/file-manager.js

* @param {string} url - url to get * @return {Promise} promise of XMLHttpRequesting given url */ -fileManager.urlToBlob = url => { +fileManager.urlToBlob = (url) => { const xhr = new XMLHttpRequest(); - return new Promise( resolve => { - xhr.open( 'GET', url ); + return new Promise((resolve) => { + xhr.open('GET', url); xhr.responseType = 'blob'; xhr.onload = () => { - resolve( xhr.response ); + resolve(xhr.response); }; xhr.send(); - } ); + }); }; /** @@ -179,38 +183,44 @@

js/file-manager.js

const files = []; // Get any files inside file input elements or text input elements for drawings. - $( 'form.or' ).find( 'input[type="file"]:not(.ignore), input[type="text"][data-drawing="true"]' ).each( function() { - let newFilename; - let file = null; - let canvas = null; - if ( this.type === 'file' ) { - file = this.files[ 0 ]; // Why doesn't this fail for empty file inputs? - } else if ( this.value ) { - canvas = $( this ).closest( '.question' )[ 0 ].querySelector( '.draw-widget canvas' ); - if ( canvas && !URL_RE.test( this.value ) ) { - // TODO: In the future, we could simply do canvas.toBlob() instead - file = dataUriToBlobSync( canvas.toDataURL() ); - file.name = this.value; + $('form.or') + .find( + 'input[type="file"]:not(.ignore), input[type="text"][data-drawing="true"]' + ) + .each(function () { + let newFilename; + let file = null; + let canvas = null; + if (this.type === 'file') { + file = this.files[0]; // Why doesn't this fail for empty file inputs? + } else if (this.value) { + canvas = $(this) + .closest('.question')[0] + .querySelector('.draw-widget canvas'); + if (canvas && !URL_RE.test(this.value)) { + // TODO: In the future, we could simply do canvas.toBlob() instead + file = dataUriToBlobSync(canvas.toDataURL()); + file.name = this.value; + } } - } - if ( file && file.name ) { - // Correct file names by adding a unique-ish postfix - // First create a clone, because the name property is immutable - // TODO: in the future, when browser support increase we can invoke - // the File constructor to do this. - newFilename = getFilename( file, this.dataset.filenamePostfix ); - - // If file is resized, get Blob representation of data URI - if ( this.dataset.resized && this.dataset.resizedDataURI ) { - file = dataUriToBlobSync( this.dataset.resizedDataURI ); + if (file && file.name) { + // Correct file names by adding a unique-ish postfix + // First create a clone, because the name property is immutable + // TODO: in the future, when browser support increase we can invoke + // the File constructor to do this. + newFilename = getFilename(file, this.dataset.filenamePostfix); + + // If file is resized, get Blob representation of data URI + if (this.dataset.resized && this.dataset.resizedDataURI) { + file = dataUriToBlobSync(this.dataset.resizedDataURI); + } + file = new Blob([file], { + type: file.type, + }); + file.name = newFilename; + files.push(file); } - file = new Blob( [ file ], { - type: file.type - } ); - file.name = newFilename; - files.push( file ); - } - } ); + }); return files; }; @@ -223,7 +233,7 @@

js/file-manager.js

* * @return {boolean} whether file is too large */ -fileManager.isTooLarge = () => { return false; }; +fileManager.isTooLarge = () => false; /** * @static @@ -233,7 +243,7 @@

js/file-manager.js

* * @return {string} human radable maximiym size */ -fileManager.getMaxSizeReadable = () => { return `${5}MB`; }; +fileManager.getMaxSizeReadable = () => `${5}MB`; export default fileManager; diff --git a/docs/js_form-logic-error.js.html b/docs/js_form-logic-error.js.html index ac4deca27..fa190ad2f 100644 --- a/docs/js_form-logic-error.js.html +++ b/docs/js_form-logic-error.js.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -54,13 +54,13 @@

js/form-logic-error.js

* @augments Error * @param {string} message - Optional message. */ -function FormLogicError( message ) { +function FormLogicError(message) { this.message = message || 'unknown'; this.name = 'FormLogicError'; - this.stack = ( new Error() ).stack; + this.stack = new Error().stack; } -FormLogicError.prototype = Object.create( Error.prototype ); +FormLogicError.prototype = Object.create(Error.prototype); FormLogicError.prototype.constructor = FormLogicError; export default FormLogicError; diff --git a/docs/js_form-model.js.html b/docs/js_form-model.js.html index 4a321d647..f8aed6b37 100644 --- a/docs/js_form-model.js.html +++ b/docs/js_form-model.js.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -48,25 +48,32 @@

js/form-model.js

import MergeXML from 'mergexml/mergexml';
+import config from 'enketo/config';
+import bindJsEvaluator from 'enketo/xpath-evaluator-binding';
 import { readCookie, parseFunctionFromExpression, stripQuotes } from './utils';
-import { getSiblingElementsAndSelf, getXPath, getRepeatIndex, hasPreviousCommentSiblingWithContent, hasPreviousSiblingElementSameName } from './dom-utils';
+import {
+    getSiblingElementsAndSelf,
+    getXPath,
+    getRepeatIndex,
+    hasPreviousCommentSiblingWithContent,
+    hasPreviousSiblingElementSameName,
+} from './dom-utils';
 import FormLogicError from './form-logic-error';
-import config from 'enketo/config';
 import types from './types';
 import event from './event';
 import { Nodeset } from './nodeset';
-import bindJsEvaluator from 'enketo/xpath-evaluator-binding';
+
+import './extend';
 
 const REPEAT_COMMENT_PREFIX = 'repeat:/';
 const INSTANCE = /instance\(\s*(["'])((?:(?!\1)[A-z0-9.\-_]+))\1\s*\)/g;
-const OPENROSA = /(decimal-date-time\(|pow\(|indexed-repeat\(|format-date\(|coalesce\(|join\(|max\(|min\(|random\(|substr\(|int\(|uuid\(|regex\(|now\(|today\(|date\(|if\(|boolean-from-string\(|checklist\(|selected\(|selected-at\(|round\(|area\(|position\([^)])/;
+const OPENROSA =
+    /(decimal-date-time\(|pow\(|indexed-repeat\(|format-date\(|coalesce\(|join\(|max\(|min\(|random\(|substr\(|int\(|uuid\(|regex\(|now\(|today\(|date\(|if\(|boolean-from-string\(|checklist\(|selected\(|selected-at\(|round\(|area\(|position\([^)])/;
 const OPENROSA_XFORMS_NS = 'http://openrosa.org/xforms';
 const JAVAROSA_XFORMS_NS = 'http://openrosa.org/javarosa';
 const ENKETO_XFORMS_NS = 'http://enketo.org/xforms';
 const ODK_XFORMS_NS = 'http://www.opendatakit.org/xforms';
 
-import './extend';
-
 const parser = new DOMParser();
 
 /**
@@ -77,20 +84,20 @@ 

js/form-model.js

* @param {object=} options - FormModel options * @param {string=} options.full - Whether to initialize the full model or only the primary instance. */ -const FormModel = function( data, options ) { - - if ( typeof data === 'string' ) { +const FormModel = function (data, options) { + if (typeof data === 'string') { data = { - modelStr: data + modelStr: data, }; } data.external = data.external || []; - data.submitted = ( typeof data.submitted !== 'undefined' ) ? data.submitted : true; + data.submitted = + typeof data.submitted !== 'undefined' ? data.submitted : true; options = options || {}; - options.full = ( typeof options.full !== 'undefined' ) ? options.full : true; + options.full = typeof options.full !== 'undefined' ? options.full : true; - this.events = document.createElement( 'div' ); + this.events = document.createElement('div'); this.convertedExpressions = {}; this.templates = {}; this.loadErrors = []; @@ -108,25 +115,25 @@

js/form-model.js

* @type {string} */ get version() { - return this.evaluate( '/*/@version', 'string', null, null, true ); + return this.evaluate('/*/@version', 'string', null, null, true); }, /** * @type {string} */ get instanceID() { - return this.getMetaNode( 'instanceID' ).getVal(); + return this.getMetaNode('instanceID').getVal(); }, /** * @type {string} */ get deprecatedID() { - return this.getMetaNode( 'deprecatedID' ).getVal() || ''; + return this.getMetaNode('deprecatedID').getVal() || ''; }, /** * @type {string} */ get instanceName() { - return this.getMetaNode( 'instanceName' ).getVal(); + return this.getMetaNode('instanceName').getVal(); }, }; @@ -135,7 +142,7 @@

js/form-model.js

* * @return {Array<string>} list of initialization errors */ -FormModel.prototype.init = function() { +FormModel.prototype.init = function () { let id; let i; let instanceDoc; @@ -148,58 +155,78 @@

js/form-model.js

* * If the regex is later deemed too aggressive, it could target the model, primary instance and primary instance child only, after creating an XML Document. */ - this.data.modelStr = this.data.modelStr.replace( /\s(xmlns=("|')[^\s>]+("|'))/g, ' data-$1' ); + this.data.modelStr = this.data.modelStr.replace( + /\s(xmlns=("|')[^\s>]+("|'))/g, + ' data-$1' + ); - if ( !this.options.full ) { + if (!this.options.full) { // Strip all secondary instances from string before parsing // This regex works because the model never includes itext in Enketo - this.data.modelStr = this.data.modelStr.replace( /^(<model\s*><instance((?!<instance).)+<\/instance\s*>\s*)(<instance.+<\/instance\s*>)*/, '$1' ); + this.data.modelStr = this.data.modelStr.replace( + /^(<model\s*><instance((?!<instance).)+<\/instance\s*>\s*)(<instance.+<\/instance\s*>)*/, + '$1' + ); } // Create the model try { id = 'model'; // The default model - this.xml = parser.parseFromString( this.data.modelStr, 'text/xml' ); - this.throwParserErrors( this.xml, this.data.modelStr ); + this.xml = parser.parseFromString(this.data.modelStr, 'text/xml'); + this.throwParserErrors(this.xml, this.data.modelStr); // Add external data to model - this.data.external.forEach( instance => { - id = instance.id ? `instance "${instance.id}"` : 'instance "unknown"'; - instanceDoc = that.getSecondaryInstance( instance.id ); + this.data.external.forEach((instance) => { + if (instance == null || !instance.xml) { + return; + } + + id = instance.id + ? `instance "${instance.id}"` + : 'instance "unknown"'; + instanceDoc = that.getSecondaryInstance(instance.id); // remove any existing content that is just an XLSForm hack to pass ODK Validate secondaryInstanceChildren = instanceDoc.children; - for ( i = secondaryInstanceChildren.length - 1; i >= 0; i-- ) { - instanceDoc.removeChild( secondaryInstanceChildren[ i ] ); + for (i = secondaryInstanceChildren.length - 1; i >= 0; i--) { + instanceDoc.removeChild(secondaryInstanceChildren[i]); } let rootEl; - if ( instance.xml instanceof XMLDocument ) { - if ( window.navigator.userAgent.indexOf( 'Trident/' ) >= 0 ) { + if (instance.xml instanceof XMLDocument) { + if (window.navigator.userAgent.indexOf('Trident/') >= 0) { // IE does not support importNode - rootEl = that.importNode( instance.xml.documentElement, true ); + rootEl = that.importNode( + instance.xml.documentElement, + true + ); } else { // Create a clone of the root node - rootEl = that.xml.importNode( instance.xml.documentElement, true ); + rootEl = that.xml.importNode( + instance.xml.documentElement, + true + ); } } - if ( rootEl ) { - instanceDoc.appendChild( rootEl ); + if (rootEl) { + instanceDoc.appendChild(rootEl); } - } ); + }); // TODO: in the future, we should search for jr://instance/session and // populate that one. This is just moving in that direction to implement preloads. - this.createSession( '__session', this.data.session ); - } catch ( e ) { - console.error( 'parseXML error' ); - this.loadErrors.push( `Error trying to parse XML ${id}. ${e.message}` ); + this.createSession('__session', this.data.session); + } catch (e) { + console.error('parseXML error'); + this.loadErrors.push(`Error trying to parse XML ${id}. ${e.message}`); } // Initialize/process the model - if ( this.xml ) { + if (this.xml) { try { - this.hasInstance = !!this.xml.querySelector( 'model > instance' ); - this.rootElement = this.xml.querySelector( 'instance > *' ) || this.xml.documentElement; + this.hasInstance = !!this.xml.querySelector('model > instance'); + this.rootElement = + this.xml.querySelector('instance > *') || + this.xml.documentElement; this.setNamespaces(); // Determine whether it is possible that this form uses incorrect absolute/path/to/repeat/node syntax when @@ -207,43 +234,63 @@

js/form-model.js

// In the future, if there are more use cases for odk:xforms-version, we'll probably have to use a semver-parser // to do a comparison. In this case, the presence of the attribute is sufficient, as we know no older versions // than odk:xforms-version="1.0.0" exist. Previous versions had no number. - this.noRepeatRefErrorExpected = this.evaluate( `/model/@${this.getNamespacePrefix( ODK_XFORMS_NS )}:xforms-version`, 'boolean', null, null, true ); + this.noRepeatRefErrorExpected = this.evaluate( + `/model/@${this.getNamespacePrefix( + ODK_XFORMS_NS + )}:xforms-version`, + 'boolean', + null, + null, + true + ); // Check if instanceID is present - if ( !this.getMetaNode( 'instanceID' ).getElement() ) { - that.loadErrors.push( 'Invalid primary instance. Missing instanceID node.' ); + if (!this.getMetaNode('instanceID').getElement()) { + that.loadErrors.push( + 'Invalid primary instance. Missing instanceID node.' + ); } // Check if all secondary instances with an external source have been populated - Array.prototype.slice.call( this.xml.querySelectorAll( 'model > instance[src]:empty' ) ).forEach( instance => { - that.loadErrors.push( `External instance "${instance.id}" is empty.` ); - } ); + Array.prototype.slice + .call(this.xml.querySelectorAll('model > instance[src]:empty')) + .forEach((instance) => { + const src = instance.getAttribute('src'); + + const errorMessage = + src == null + ? `External instance "${instance.id}" is empty.` + : `Can't find ${src.replace(/.*\//, '')}.`; + + that.loadErrors.push(errorMessage); + }); this.trimValues(); this.extractTemplates(); - } catch ( e ) { - console.error( e ); - this.loadErrors.push( `${e.name}: ${e.message}` ); + } catch (e) { + console.error(e); + this.loadErrors.push(`${e.name}: ${e.message}`); } // Merge an existing instance into the model, AFTER templates have been removed try { id = 'record'; - if ( this.data.instanceStr ) { - this.mergeXml( this.data.instanceStr ); + if (this.data.instanceStr) { + this.mergeXml(this.data.instanceStr); } // Set the two most important meta fields before any field 'dataupdate' event fires. // The first dataupdate event will fire in response to the instance-first-load event. this.setInstanceIdAndDeprecatedId(); - if ( !this.data.instanceStr ){ + if (!this.data.instanceStr) { // Only dispatch for newly created records - this.events.dispatchEvent( event.InstanceFirstLoad() ); + this.events.dispatchEvent(event.InstanceFirstLoad()); } - - } catch ( e ) { - console.error( e ); - this.loadErrors.push( `Error trying to parse XML ${id}. ${e.message}` ); + } catch (e) { + console.error(e); + this.loadErrors.push( + `Error trying to parse XML ${id}. ${e.message}` + ); } } @@ -254,9 +301,9 @@

js/form-model.js

* @param {Document} xmlDoc - XML Document * @param {string} xmlStr - XML string */ -FormModel.prototype.throwParserErrors = ( xmlDoc, xmlStr ) => { - if ( !xmlDoc || xmlDoc.querySelector( 'parsererror' ) ) { - throw new Error( `Invalid XML: ${xmlStr}` ); +FormModel.prototype.throwParserErrors = (xmlDoc, xmlStr) => { + if (!xmlDoc || xmlDoc.querySelector('parsererror')) { + throw new Error(`Invalid XML: ${xmlStr}`); } }; @@ -264,35 +311,53 @@

js/form-model.js

* @param {string} id - Instance ID * @param {object} [sessObj] - session object */ -FormModel.prototype.createSession = function( id, sessObj ) { +FormModel.prototype.createSession = function (id, sessObj) { let instance; let session; - const model = this.xml.querySelector( 'model' ); - const fixedProps = [ 'deviceid', 'username', 'email', 'phonenumber', 'simserial', 'subscriberid' ]; - if ( !model ) { + const model = this.xml.querySelector('model'); + const fixedProps = [ + 'deviceid', + 'username', + 'email', + 'phonenumber', + 'simserial', + 'subscriberid', + ]; + if (!model) { return; } - sessObj = ( typeof sessObj === 'object' ) ? sessObj : {}; - instance = model.querySelector( `instance#${CSS.escape( id )}` ); + sessObj = typeof sessObj === 'object' ? sessObj : {}; + instance = model.querySelector(`instance#${CSS.escape(id)}`); - if ( !instance ) { - instance = parser.parseFromString( `<instance id="${id}"/>`, 'text/xml' ).documentElement; - this.xml.adoptNode( instance ); - model.appendChild( instance ); + if (!instance) { + instance = parser.parseFromString( + `<instance id="${id}"/>`, + 'text/xml' + ).documentElement; + this.xml.adoptNode(instance); + model.appendChild(instance); } // fixed: /sesssion/context properties - fixedProps.forEach( prop => { - sessObj[ prop ] = sessObj[ prop ] || readCookie( `__enketo_meta_${prop}` ) || `${prop} not found`; - } ); - - session = parser.parseFromString( `<session><context>${fixedProps.map( prop => `<${prop}>${sessObj[ prop ]}</${prop}>` ).join( '' )}</context></session>`, 'text/xml' ).documentElement; + fixedProps.forEach((prop) => { + sessObj[prop] = + sessObj[prop] || + readCookie(`__enketo_meta_${prop}`) || + `${prop} not found`; + }); + + session = parser.parseFromString( + `<session><context>${fixedProps + .map((prop) => `<${prop}>${sessObj[prop]}</${prop}>`) + .join('')}</context></session>`, + 'text/xml' + ).documentElement; // TODO: custom properties could be added to /session/user/data or to /session/data - this.xml.adoptNode( session ); - instance.appendChild( session ); + this.xml.adoptNode(session); + instance.appendChild(session); }; /** @@ -302,19 +367,18 @@

js/form-model.js

* @param {string} id - DOM element id. * @return {Element|undefined} secondary instance XML element */ -FormModel.prototype.getSecondaryInstance = function( id ) { +FormModel.prototype.getSecondaryInstance = function (id) { let instanceEl; - [ ...this.xml.querySelectorAll( 'model > instance' ) ].some( el => { - const idAttr = el.getAttribute( 'id' ); - if ( idAttr === id ) { + [...this.xml.querySelectorAll('model > instance')].some((el) => { + const idAttr = el.getAttribute('id'); + if (idAttr === id) { instanceEl = el; return true; - } else { - return false; } - } ); + return false; + }); return instanceEl; }; @@ -327,8 +391,8 @@

js/form-model.js

* @param {NodesetFilter|null} [filter] - filter to apply * @return {Nodeset} Nodeset instance */ -FormModel.prototype.node = function( selector, index, filter ) { - return new Nodeset( selector, index, filter, this ); +FormModel.prototype.node = function (selector, index, filter) { + return new Nodeset(selector, index, filter, this); }; /** @@ -338,28 +402,43 @@

js/form-model.js

* @param {Element} node - Node to be imported * @param {Array<Node>} allChildren - All children of imported Node */ -FormModel.prototype.importNode = function( node, allChildren ) { +FormModel.prototype.importNode = function (node, allChildren) { let i; let il; - switch ( node.nodeType ) { + switch (node.nodeType) { case document.ELEMENT_NODE: { - const newNode = document.createElementNS( node.namespaceURI, node.nodeName ); - if ( node.attributes && node.attributes.length > 0 ) { - for ( i = 0, il = node.attributes.length; i < il; i++ ) { - const attr = node.attributes[ i ]; - if ( attr.namespaceURI ) { - newNode.setAttributeNS( attr.namespaceURI, attr.nodeName, node.getAttributeNS( attr.namespaceURI, attr.localName ) ); + const newNode = document.createElementNS( + node.namespaceURI, + node.nodeName + ); + if (node.attributes && node.attributes.length > 0) { + for (i = 0, il = node.attributes.length; i < il; i++) { + const attr = node.attributes[i]; + if (attr.namespaceURI) { + newNode.setAttributeNS( + attr.namespaceURI, + attr.nodeName, + node.getAttributeNS( + attr.namespaceURI, + attr.localName + ) + ); } else { - newNode.setAttribute( attr.nodeName, node.getAttribute( attr.nodeName ) ); + newNode.setAttribute( + attr.nodeName, + node.getAttribute(attr.nodeName) + ); } } } - if ( allChildren && node.children.length ) { - for ( i = 0, il = node.children.length; i < il; i++ ) { - newNode.appendChild( this.importNode( node.children[ i ], allChildren ) ); + if (allChildren && node.children.length) { + for (i = 0, il = node.children.length; i < il; i++) { + newNode.appendChild( + this.importNode(node.children[i], allChildren) + ); } } - if ( !node.children.length && node.textContent ) { + if (!node.children.length && node.textContent) { newNode.textContent = node.textContent; } @@ -368,7 +447,7 @@

js/form-model.js

case document.TEXT_NODE: case document.CDATA_SECTION_NODE: case document.COMMENT_NODE: - return document.createTextNode( node.nodeValue ); + return document.createTextNode(node.nodeValue); } }; /** @@ -376,7 +455,7 @@

js/form-model.js

* * @param {string} recordStr - The XML record as string */ -FormModel.prototype.mergeXml = function( recordStr ) { +FormModel.prototype.mergeXml = function (recordStr) { let modelInstanceChildStr; let merger; let modelInstanceEl; @@ -386,29 +465,31 @@

js/form-model.js

let templateEls; let record; - if ( !recordStr ) { + if (!recordStr) { return; } - modelInstanceEl = this.xml.querySelector( 'instance' ); - modelInstanceChildEl = this.xml.querySelector( 'instance > *' ); // do not use firstChild as it may find a #textNode + modelInstanceEl = this.xml.querySelector('instance'); + modelInstanceChildEl = this.xml.querySelector('instance > *'); // do not use firstChild as it may find a #textNode - if ( !modelInstanceChildEl ) { - throw new Error( 'Model is corrupt. It does not contain a childnode of instance' ); + if (!modelInstanceChildEl) { + throw new Error( + 'Model is corrupt. It does not contain a childnode of instance' + ); } /** * A Namespace merge problem occurs when ODK decides to invent a new namespace for a submission * that is different from the XForm model namespace... So we just remove this nonsense. */ - recordStr = recordStr.replace( /\s(xmlns=("|')[^\s>]+("|'))/g, '' ); + recordStr = recordStr.replace(/\s(xmlns=("|')[^\s>]+("|'))/g, ''); /** * Comments aren't merging in document order (which would be impossible also). * This may mess up repeat functionality, so until we actually need * comments, we simply remove them (multiline comments are probably not removed, but we don't care about them). */ - recordStr = recordStr.replace( /<!--[^>]*-->/g, '' ); - record = parser.parseFromString( recordStr, 'text/xml' ); + recordStr = recordStr.replace(/<!--[^>]*-->/g, ''); + record = parser.parseFromString(recordStr, 'text/xml'); /** * Normally records will not contain the special "jr:template" attribute. However, we should still be able to deal with @@ -421,10 +502,10 @@

js/form-model.js

* nodes with a template attribute name IN ANY NAMESPACE. */ - templateEls = record.querySelectorAll( '[*|template]' ); + templateEls = record.querySelectorAll('[*|template]'); - for ( let i = 0; i < templateEls.length; i++ ) { - templateEls[ i ].remove(); + for (let i = 0; i < templateEls.length; i++) { + templateEls[i].remove(); } /** @@ -436,86 +517,109 @@

js/form-model.js

* in the model, that node will be missing in the result. */ // TODO: ES6 for (var node of record.querySelectorAll('*')){} - Array.prototype.slice.call( record.querySelectorAll( '*' ) ) - .forEach( node => { - let path; - let repeatIndex = 0; - let positionedPath; - let repeatParts; - try { - path = getXPath( node, 'instance', false ); - // If this is a templated repeat (check templates) - // or a repeat without templates - if ( typeof that.templates[ path ] !== 'undefined' || getRepeatIndex( node ) > 0 ) { - positionedPath = getXPath( node, 'instance', true ); - if ( !that.evaluate( positionedPath, 'node', null, null, true ) ) { - repeatParts = positionedPath.match( /([^[]+)\[(\d+)\]\//g ); - // If the positionedPath has a non-0 repeat index followed by (at least) 1 node, avoid cloning out of order. - if ( repeatParts && repeatParts.length > 0 ) { - // TODO: Does this work for triple-nested repeats. I don't really care though. - // repeatIndex of immediate parent repeat of deepest nested repeat in positionedPath - repeatIndex = repeatParts[ repeatParts.length - 1 ].match( /\[(\d+)\]/ )[ 1 ] - 1; - } - that.addRepeat( path, repeatIndex, true ); + Array.prototype.slice.call(record.querySelectorAll('*')).forEach((node) => { + let path; + let repeatIndex = 0; + let positionedPath; + let repeatParts; + try { + path = getXPath(node, 'instance', false); + // If this is a templated repeat (check templates) + // or a repeat without templates + if ( + typeof that.templates[path] !== 'undefined' || + getRepeatIndex(node) > 0 + ) { + positionedPath = getXPath(node, 'instance', true); + if (!that.evaluate(positionedPath, 'node', null, null, true)) { + repeatParts = positionedPath.match(/([^[]+)\[(\d+)\]\//g); + // If the positionedPath has a non-0 repeat index followed by (at least) 1 node, avoid cloning out of order. + if (repeatParts && repeatParts.length > 0) { + // TODO: Does this work for triple-nested repeats. I don't really care though. + // repeatIndex of immediate parent repeat of deepest nested repeat in positionedPath + repeatIndex = + repeatParts[repeatParts.length - 1].match( + /\[(\d+)\]/ + )[1] - 1; } + that.addRepeat(path, repeatIndex, true); } - } catch ( e ) { - console.warn( 'Ignored error:', e ); } - } ); + } catch (e) { + console.warn('Ignored error:', e); + } + }); /** * Any default values in the model, may have been emptied in the record. * MergeXML will keep those default values, which would be bad, so we manually clear defaults before merging. */ // first find all empty leaf nodes in record - Array.prototype.slice.call( record.querySelectorAll( '*' ) ) - .filter( recordNode => { + Array.prototype.slice + .call(record.querySelectorAll('*')) + .filter((recordNode) => { const val = recordNode.textContent; return recordNode.children.length === 0 && val.trim().length === 0; - } ) - .forEach( leafNode => { - const path = getXPath( leafNode, 'instance', true ); - const instanceNode = that.node( path, 0 ).getElement(); - if ( instanceNode ) { + }) + .forEach((leafNode) => { + const path = getXPath(leafNode, 'instance', true); + const instanceNode = that.node(path, 0).getElement(); + if (instanceNode) { // TODO: after dropping support for IE11, we can also use instanceNode.children.length - if ( that.evaluate( './*', 'nodes-ordered', path, 0, true ).length === 0 ) { + if ( + that.evaluate('./*', 'nodes-ordered', path, 0, true) + .length === 0 + ) { // Select all text nodes (excluding repeat COMMENT nodes!) - that.evaluate( './text()', 'nodes-ordered', path, 0, true ).forEach( node => { + that.evaluate( + './text()', + 'nodes-ordered', + path, + 0, + true + ).forEach((node) => { node.textContent = ''; - } ); + }); } else { // If the node in the default instance is a group (empty in record, so appears to be a leaf node // but isn't), empty all true leaf node descendants. - that.evaluate( './/*[not(*)]', 'nodes-ordered', path, 0, true ).forEach( node => { + that.evaluate( + './/*[not(*)]', + 'nodes-ordered', + path, + 0, + true + ).forEach((node) => { node.textContent = ''; - } ); + }); } } - } ); + }); - merger = new MergeXML( { - join: false - } ); + merger = new MergeXML({ + join: false, + }); - modelInstanceChildStr = ( new XMLSerializer() ).serializeToString( modelInstanceChildEl ); - recordStr = ( new XMLSerializer() ).serializeToString( record ); + modelInstanceChildStr = new XMLSerializer().serializeToString( + modelInstanceChildEl + ); + recordStr = new XMLSerializer().serializeToString(record); // first the model, to preserve DOM order of that of the default instance - merger.AddSource( modelInstanceChildStr ); + merger.AddSource(modelInstanceChildStr); // then merge the record into the model - merger.AddSource( recordStr ); + merger.AddSource(recordStr); - if ( merger.error.code ) { - throw new Error( merger.error.text ); + if (merger.error.code) { + throw new Error(merger.error.text); } /** * Beware: merge.Get(0) returns an ActiveXObject in IE11. We turn this * into a proper XML document by parsing the XML string instead. */ - mergeResultDoc = parser.parseFromString( merger.Get( 1 ), 'text/xml' ); + mergeResultDoc = parser.parseFromString(merger.Get(1), 'text/xml'); /** * To properly show 0 repeats, if the form definition contains multiple default instances @@ -528,55 +632,62 @@

js/form-model.js

*/ // Remove the primary instance childnode from the original model - this.xml.querySelector( 'instance' ).removeChild( modelInstanceChildEl ); + this.xml.querySelector('instance').removeChild(modelInstanceChildEl); // checking if IE - if ( window.navigator.userAgent.indexOf( 'Trident/' ) >= 0 ) { + if (window.navigator.userAgent.indexOf('Trident/') >= 0) { // IE does not support adoptNode - modelInstanceChildEl = this.importNode( mergeResultDoc.documentElement, true ); + modelInstanceChildEl = this.importNode( + mergeResultDoc.documentElement, + true + ); } else { // adopt the merged instance childnode - modelInstanceChildEl = this.xml.adoptNode( mergeResultDoc.documentElement, true ); + modelInstanceChildEl = this.xml.adoptNode( + mergeResultDoc.documentElement, + true + ); } // append the adopted node to the primary instance - modelInstanceEl.appendChild( modelInstanceChildEl ); + modelInstanceEl.appendChild(modelInstanceChildEl); // reset the rootElement this.rootElement = modelInstanceChildEl; - }; /** * Trims values of all Form elements */ -FormModel.prototype.trimValues = function() { - this.node( null, null, { - noEmpty: true - } ).getElements().forEach( element => { - element.textContent = element.textContent.trim(); - } ); +FormModel.prototype.trimValues = function () { + this.node(null, null, { + noEmpty: true, + }) + .getElements() + .forEach((element) => { + element.textContent = element.textContent.trim(); + }); }; /** * Sets instance ID and deprecated ID */ -FormModel.prototype.setInstanceIdAndDeprecatedId = function() { +FormModel.prototype.setInstanceIdAndDeprecatedId = function () { let instanceIdObj; let instanceIdEl; let deprecatedIdEl; let metaEl; let instanceIdExistingVal; - instanceIdObj = this.getMetaNode( 'instanceID' ); + instanceIdObj = this.getMetaNode('instanceID'); instanceIdEl = instanceIdObj.getElement(); instanceIdExistingVal = instanceIdObj.getVal(); - if ( !instanceIdEl ){ - console.warn( 'Model has no instanceID element' ); + if (!instanceIdEl) { + console.warn('Model has no instanceID element'); return; } - if ( this.data.instanceStr && this.data.submitted ) { - deprecatedIdEl = this.getMetaNode( 'deprecatedID' ).getElement(); + if (this.data.instanceStr && this.data.submitted) { + deprecatedIdEl = this.getMetaNode('deprecatedID').getElement(); // set the instanceID value to empty instanceIdEl.textContent = ''; @@ -584,24 +695,35 @@

js/form-model.js

const namespace = instanceIdEl.namespaceURI; // add deprecatedID node if necessary - if ( !deprecatedIdEl ) { - const nsPrefix = namespace ? this.getNamespacePrefix( namespace ) : ''; - const nsDeclaration = namespace ? `xmlns:${nsPrefix}="${namespace}"` : ''; - deprecatedIdEl = parser.parseFromString( `<${nsPrefix ? nsPrefix + ':' : ''}deprecatedID ${nsDeclaration}/>`, 'text/xml' ).documentElement; - this.xml.adoptNode( deprecatedIdEl ); - metaEl = this.xml.querySelector( '* > meta' ); - metaEl.appendChild( deprecatedIdEl ); + if (!deprecatedIdEl) { + const nsPrefix = namespace + ? this.getNamespacePrefix(namespace) + : ''; + const nsDeclaration = namespace + ? `xmlns:${nsPrefix}="${namespace}"` + : ''; + deprecatedIdEl = parser.parseFromString( + `<${ + nsPrefix ? `${nsPrefix}:` : '' + }deprecatedID ${nsDeclaration}/>`, + 'text/xml' + ).documentElement; + this.xml.adoptNode(deprecatedIdEl); + metaEl = this.xml.querySelector('* > meta'); + metaEl.appendChild(deprecatedIdEl); } } - if ( !instanceIdObj.getVal() ) { - instanceIdObj.setVal( this.evaluate( 'concat("uuid:", uuid())', 'string' ) ); + if (!instanceIdObj.getVal()) { + instanceIdObj.setVal( + this.evaluate('concat("uuid:", uuid())', 'string') + ); } // after setting instanceID, give deprecatedID element the old value of the instanceId // ensure dataupdate event fires by using setVal - if ( deprecatedIdEl ) { - this.getMetaNode( 'deprecatedID' ).setVal( instanceIdExistingVal ); + if (deprecatedIdEl) { + this.getMetaNode('deprecatedID').setVal(instanceIdExistingVal); } }; @@ -617,12 +739,12 @@

js/form-model.js

* @param {string} localName - node name without namespace * @return {Element} node */ -FormModel.prototype.getMetaNode = function( localName ) { - const orPrefix = this.getNamespacePrefix( OPENROSA_XFORMS_NS ); - let n = this.node( `/*/${orPrefix}:meta/${orPrefix}:${localName}` ); +FormModel.prototype.getMetaNode = function (localName) { + const orPrefix = this.getNamespacePrefix(OPENROSA_XFORMS_NS); + let n = this.node(`/*/${orPrefix}:meta/${orPrefix}:${localName}`); - if ( !n.getElement() ) { - n = this.node( `/*/meta/${localName}` ); + if (!n.getElement()) { + n = this.node(`/*/meta/${localName}`); } return n; @@ -632,7 +754,7 @@

js/form-model.js

* @param {string} path - path to repeat * @return {string} repeat comment text */ -FormModel.prototype.getRepeatCommentText = path => { +FormModel.prototype.getRepeatCommentText = (path) => { path = path.trim(); return REPEAT_COMMENT_PREFIX + path; @@ -642,8 +764,10 @@

js/form-model.js

* @param {string} repeatPath - path to repeat * @return {string} selector */ -FormModel.prototype.getRepeatCommentSelector = function( repeatPath ) { - return `//comment()[self::comment()="${this.getRepeatCommentText( repeatPath )}"]`; +FormModel.prototype.getRepeatCommentSelector = function (repeatPath) { + return `//comment()[self::comment()="${this.getRepeatCommentText( + repeatPath + )}"]`; }; /** @@ -651,8 +775,17 @@

js/form-model.js

* @param {number} repeatSeriesIndex - index of repeat series * @return {Element} node */ -FormModel.prototype.getRepeatCommentEl = function( repeatPath, repeatSeriesIndex ) { - return this.evaluate( this.getRepeatCommentSelector( repeatPath ), 'nodes-ordered', null, null, true )[ repeatSeriesIndex ]; +FormModel.prototype.getRepeatCommentEl = function ( + repeatPath, + repeatSeriesIndex +) { + return this.evaluate( + this.getRepeatCommentSelector(repeatPath), + 'nodes-ordered', + null, + null, + true + )[repeatSeriesIndex]; }; /** @@ -663,51 +796,62 @@

js/form-model.js

* @param {boolean} merge - whether this operation is part of a merge operation (won't send dataupdate event, clears all values and * will not add ordinal attributes as these should be provided in the record) */ -FormModel.prototype.addRepeat = function( repeatPath, repeatSeriesIndex, merge ) { +FormModel.prototype.addRepeat = function ( + repeatPath, + repeatSeriesIndex, + merge +) { let templateClone; const that = this; - if ( !this.templates[ repeatPath ] ) { + if (!this.templates[repeatPath]) { // This allows the model itself without requiring the controller to cal call .extractFakeTemplates() // to extract non-jr:templates by assuming that addRepeat would only called for a repeat. - this.extractFakeTemplates( [ repeatPath ] ); + this.extractFakeTemplates([repeatPath]); } - const template = this.templates[ repeatPath ]; - const repeatSeries = this.getRepeatSeries( repeatPath, repeatSeriesIndex ); - const insertAfterNode = repeatSeries.length ? repeatSeries[ repeatSeries.length - 1 ] : this.getRepeatCommentEl( repeatPath, repeatSeriesIndex ); + const template = this.templates[repeatPath]; + const repeatSeries = this.getRepeatSeries(repeatPath, repeatSeriesIndex); + const insertAfterNode = repeatSeries.length + ? repeatSeries[repeatSeries.length - 1] + : this.getRepeatCommentEl(repeatPath, repeatSeriesIndex); // if not exists and not a merge operation - if ( !merge ) { - repeatSeries.forEach( el => { - that.addOrdinalAttribute( el, repeatSeries[ 0 ] ); - } ); + if (!merge) { + repeatSeries.forEach((el) => { + that.addOrdinalAttribute(el, repeatSeries[0]); + }); } /** * If templatenodes and insertAfterNode(s) have been identified */ - if ( template && insertAfterNode ) { - templateClone = template.cloneNode( true ); - insertAfterNode.after( templateClone ); + if (template && insertAfterNode) { + templateClone = template.cloneNode(true); + insertAfterNode.after(templateClone); - this.removeOrdinalAttributes( templateClone ); + this.removeOrdinalAttributes(templateClone); // We should not automatically add ordinal attributes for an existing record as the ordinal values cannot be determined. // They should be provided in the instanceStr (record). - if ( !merge ) { - this.addOrdinalAttribute( templateClone, repeatSeries[ 0 ] ); + if (!merge) { + this.addOrdinalAttribute(templateClone, repeatSeries[0]); } // If part of a merge operation (during form load) where the values will be populated from the record, defaults are not desired. - if ( merge ) { - Array.prototype.slice.call( templateClone.querySelectorAll( '*' ) ) - .filter( node => node.children.length === 0 ) - .forEach( node => { node.textContent = ''; } ); + if (merge) { + Array.prototype.slice + .call(templateClone.querySelectorAll('*')) + .filter((node) => node.children.length === 0) + .forEach((node) => { + node.textContent = ''; + }); } // Note: the addrepeat eventhandler in Form.js takes care of initializing branches etc, so no need to fire an event here. } else { - console.error( 'Could not find template node and/or node to insert the clone after' ); + console.error( + 'Could not find template node and/or node to insert the clone after' + ); } }; @@ -715,19 +859,33 @@

js/form-model.js

* @param {Element} repeat - Set ordinal attribue to this node * @param {Element} firstRepeatInSeries - Used to know what the next ordinal attribute value should be. Defaults to `repeat` node. */ -FormModel.prototype.addOrdinalAttribute = function( repeat, firstRepeatInSeries ) { +FormModel.prototype.addOrdinalAttribute = function ( + repeat, + firstRepeatInSeries +) { let lastUsedOrdinal; let newOrdinal; - const enkNs = this.getNamespacePrefix( ENKETO_XFORMS_NS ); + const enkNs = this.getNamespacePrefix(ENKETO_XFORMS_NS); firstRepeatInSeries = firstRepeatInSeries || repeat; - if ( config.repeatOrdinals === true && !repeat.getAttributeNS( ENKETO_XFORMS_NS, 'ordinal' ) ) { + if ( + config.repeatOrdinals === true && + !repeat.getAttributeNS(ENKETO_XFORMS_NS, 'ordinal') + ) { // getAttributeNs and setAttributeNs results in duplicate namespace declarations on each repeat node in IE11 when serializing the model. // However, the regular getAttribute and setAttribute do not work properly in IE11. - lastUsedOrdinal = firstRepeatInSeries.getAttributeNS( ENKETO_XFORMS_NS, 'last-used-ordinal' ) || 0; - newOrdinal = Number( lastUsedOrdinal ) + 1; - firstRepeatInSeries.setAttributeNS( ENKETO_XFORMS_NS, `${enkNs}:last-used-ordinal`, newOrdinal ); - - repeat.setAttributeNS( ENKETO_XFORMS_NS, `${enkNs}:ordinal`, newOrdinal ); + lastUsedOrdinal = + firstRepeatInSeries.getAttributeNS( + ENKETO_XFORMS_NS, + 'last-used-ordinal' + ) || 0; + newOrdinal = Number(lastUsedOrdinal) + 1; + firstRepeatInSeries.setAttributeNS( + ENKETO_XFORMS_NS, + `${enkNs}:last-used-ordinal`, + newOrdinal + ); + + repeat.setAttributeNS(ENKETO_XFORMS_NS, `${enkNs}:ordinal`, newOrdinal); } }; @@ -736,16 +894,18 @@

js/form-model.js

* * @param {Element} el - Target node */ -FormModel.prototype.removeOrdinalAttributes = el => { - if ( config.repeatOrdinals === true ) { +FormModel.prototype.removeOrdinalAttributes = (el) => { + if (config.repeatOrdinals === true) { // Find all nested repeats first (this is only used for repeats that have no template). // The querySelector is actually too unspecific as it matches all ordinal attributes in ANY namespace. // However the proper [enk\\:ordinal] doesn't work if setAttributeNS was used to add the attribute. - const repeats = Array.prototype.slice.call( el.querySelectorAll( '[*|ordinal]' ) ); - repeats.push( el ); - for ( let i = 0; i < repeats.length; i++ ) { - repeats[ i ].removeAttributeNS( ENKETO_XFORMS_NS, 'last-used-ordinal' ); - repeats[ i ].removeAttributeNS( ENKETO_XFORMS_NS, 'ordinal' ); + const repeats = Array.prototype.slice.call( + el.querySelectorAll('[*|ordinal]') + ); + repeats.push(el); + for (let i = 0; i < repeats.length; i++) { + repeats[i].removeAttributeNS(ENKETO_XFORMS_NS, 'last-used-ordinal'); + repeats[i].removeAttributeNS(ENKETO_XFORMS_NS, 'ordinal'); } } }; @@ -757,26 +917,31 @@

js/form-model.js

* @param {number} repeatSeriesIndex - The index of the series of that repeat. * @return {Array<Element>} Array of all repeat elements in a series. */ -FormModel.prototype.getRepeatSeries = function( repeatPath, repeatSeriesIndex ) { +FormModel.prototype.getRepeatSeries = function (repeatPath, repeatSeriesIndex) { let pathSegments; let nodeName; let checkEl; - const repeatCommentEl = this.getRepeatCommentEl( repeatPath, repeatSeriesIndex ); + const repeatCommentEl = this.getRepeatCommentEl( + repeatPath, + repeatSeriesIndex + ); const result = []; // RepeatCommentEl is null if the requested repeatseries is a nested repeat and its ancestor repeat // has 0 instances. - if ( repeatCommentEl ) { - pathSegments = repeatCommentEl.textContent.substr( REPEAT_COMMENT_PREFIX.length ).split( '/' ); - nodeName = pathSegments[ pathSegments.length - 1 ]; + if (repeatCommentEl) { + pathSegments = repeatCommentEl.textContent + .substr(REPEAT_COMMENT_PREFIX.length) + .split('/'); + nodeName = pathSegments[pathSegments.length - 1]; checkEl = repeatCommentEl.nextSibling; // then add all subsequent repeats - while ( checkEl ) { + while (checkEl) { // Ignore any sibling text and comment nodes (e.g. whitespace with a newline character) // also deal with repeats that have non-repeat siblings in between them, event though that would be a bug. - if ( checkEl.nodeName && checkEl.nodeName === nodeName ) { - result.push( checkEl ); + if (checkEl.nodeName && checkEl.nodeName === nodeName) { + result.push(checkEl); } checkEl = checkEl.nextSibling; } @@ -791,70 +956,79 @@

js/form-model.js

* @param {Element} element - Target node * @return {number} Determined index */ -FormModel.prototype.determineIndex = function( element ) { - if ( element ) { - const nodeName = element.nodeName; - const path = getXPath( element, 'instance' ); - const family = Array.prototype.slice.call( this.xml.querySelectorAll( nodeName.replace( /\./g, '\\.' ) ) ) - .filter( node => path === getXPath( node, 'instance' ) ); - - return family.length === 1 ? null : family.indexOf( element ); - } else { - console.error( 'no node, or multiple nodes, provided to determineIndex function' ); - - return -1; +FormModel.prototype.determineIndex = function (element) { + if (element) { + const { nodeName } = element; + const path = getXPath(element, 'instance'); + const family = Array.prototype.slice + .call(this.xml.querySelectorAll(nodeName.replace(/\./g, '\\.'))) + .filter((node) => path === getXPath(node, 'instance')); + + return family.length === 1 ? null : family.indexOf(element); } + console.error( + 'no node, or multiple nodes, provided to determineIndex function' + ); + + return -1; }; /** * Extracts all templates from the model and stores them in a Javascript object. */ -FormModel.prototype.extractTemplates = function() { +FormModel.prototype.extractTemplates = function () { const that = this; // in reverse document order to properly deal with nested repeat templates - this.getTemplateNodes().reverse().forEach( templateEl => { - const xPath = getXPath( templateEl, 'instance' ); - that.addTemplate( xPath, templateEl ); - /* - * Nested repeats that have a template attribute are correctly added to the templates object. - * The template of the repeat ancestor of the nested repeat contains the correct comment. - * However, since the ancestor repeat (template) - */ - templateEl.remove(); - } ); + this.getTemplateNodes() + .reverse() + .forEach((templateEl) => { + const xPath = getXPath(templateEl, 'instance'); + that.addTemplate(xPath, templateEl); + /* + * Nested repeats that have a template attribute are correctly added to the templates object. + * The template of the repeat ancestor of the nested repeat contains the correct comment. + * However, since the ancestor repeat (template) + */ + templateEl.remove(); + }); }; /** * @param {Array<string>} repeatPaths - repeat paths */ -FormModel.prototype.extractFakeTemplates = function( repeatPaths ) { +FormModel.prototype.extractFakeTemplates = function (repeatPaths) { const that = this; let repeat; - repeatPaths.forEach( repeatPath => { + repeatPaths.forEach((repeatPath) => { // Filter by elements that are the first in a series. This means that multiple instances of nested repeats // all get a comment insertion point. - repeat = that.evaluate( repeatPath, 'node', null, null, true ); - if ( repeat ) { - that.addTemplate( repeatPath, repeat, true ); + repeat = that.evaluate(repeatPath, 'node', null, null, true); + if (repeat) { + that.addTemplate(repeatPath, repeat, true); } - } ); + }); }; /** * @param {string} repeatPath - path to repeat */ -FormModel.prototype.addRepeatComments = function( repeatPath ) { - const comment = this.getRepeatCommentText( repeatPath ); +FormModel.prototype.addRepeatComments = function (repeatPath) { + const comment = this.getRepeatCommentText(repeatPath); // Find all repeat series. - this.evaluate( repeatPath, 'nodes-ordered', null, null, true ).forEach( repeat => { - if ( !hasPreviousSiblingElementSameName( repeat ) && !hasPreviousCommentSiblingWithContent( repeat, comment ) ) { - // Add a comment to the primary instance that serves as an insertion point for each repeat series, - repeat.before( document.createComment( comment ) ); + this.evaluate(repeatPath, 'nodes-ordered', null, null, true).forEach( + (repeat) => { + if ( + !hasPreviousSiblingElementSameName(repeat) && + !hasPreviousCommentSiblingWithContent(repeat, comment) + ) { + // Add a comment to the primary instance that serves as an insertion point for each repeat series, + repeat.before(document.createComment(comment)); + } } - } ); + ); }; /** @@ -862,32 +1036,39 @@

js/form-model.js

* @param {Element} repeat - Target node * @param {boolean} empty - whether to empty values before adding the template */ -FormModel.prototype.addTemplate = function( repeatPath, repeat, empty ) { - this.addRepeatComments( repeatPath ); - - if ( !this.templates[ repeatPath ] ) { - const clone = repeat.cloneNode( true ); - clone.removeAttribute( 'template' ); - clone.removeAttribute( 'jr:template' ); - if ( empty ) { - Array.prototype.slice.call( clone.querySelectorAll( '*' ) ) - .filter( node => node.children.length === 0 ) - .forEach( node => { +FormModel.prototype.addTemplate = function (repeatPath, repeat, empty) { + this.addRepeatComments(repeatPath); + + if (!this.templates[repeatPath]) { + const clone = repeat.cloneNode(true); + clone.removeAttribute('template'); + clone.removeAttribute('jr:template'); + if (empty) { + Array.prototype.slice + .call(clone.querySelectorAll('*')) + .filter((node) => node.children.length === 0) + .forEach((node) => { node.textContent = ''; - } ); + }); } // Add to templates object. - this.templates[ repeatPath ] = clone; + this.templates[repeatPath] = clone; } }; /** * @return {Array<Element>} template nodes list */ -FormModel.prototype.getTemplateNodes = function() { - const jrPrefix = this.getNamespacePrefix( JAVAROSA_XFORMS_NS ); - - return this.evaluate( `/model/instance[1]/*//*[@${jrPrefix}:template]`, 'nodes-ordered', null, null, true ); +FormModel.prototype.getTemplateNodes = function () { + const jrPrefix = this.getNamespacePrefix(JAVAROSA_XFORMS_NS); + + return this.evaluate( + `/model/instance[1]/*//*[@${jrPrefix}:template]`, + 'nodes-ordered', + null, + null, + true + ); }; /** @@ -895,15 +1076,21 @@

js/form-model.js

* * @return {string} XML string */ -FormModel.prototype.getStr = function() { - let dataStr = ( new XMLSerializer() ).serializeToString( this.xml.querySelector( 'instance > *' ) || this.xml.documentElement, 'text/xml' ); +FormModel.prototype.getStr = function () { + let dataStr = new XMLSerializer().serializeToString( + this.xml.querySelector('instance > *') || this.xml.documentElement, + 'text/xml' + ); // restore default namespaces - dataStr = dataStr.replace( /\s(data-)(xmlns=("|')[^\s>]+("|'))/g, ' $2' ); + dataStr = dataStr.replace(/\s(data-)(xmlns=("|')[^\s>]+("|'))/g, ' $2'); // remove repeat comments - dataStr = dataStr.replace( new RegExp( `<!--${REPEAT_COMMENT_PREFIX}\\/[^>]+-->`, 'g' ), '' ); + dataStr = dataStr.replace( + new RegExp(`<!--${REPEAT_COMMENT_PREFIX}\\/[^>]+-->`, 'g'), + '' + ); // If not IE, strip duplicate namespace declarations. IE doesn't manage to add a namespace declaration to the root element. - if ( navigator.userAgent.indexOf( 'Trident/' ) === -1 ) { - dataStr = this.removeDuplicateEnketoNsDeclarations( dataStr ); + if (navigator.userAgent.indexOf('Trident/') === -1) { + dataStr = this.removeDuplicateEnketoNsDeclarations(dataStr); } return dataStr; @@ -913,18 +1100,22 @@

js/form-model.js

* @param {string} xmlStr - XML string * @return {string} XML string without duplicates */ -FormModel.prototype.removeDuplicateEnketoNsDeclarations = function( xmlStr ) { +FormModel.prototype.removeDuplicateEnketoNsDeclarations = function (xmlStr) { let i = 0; - const declarationExp = new RegExp( `( xmlns:${this.getNamespacePrefix( ENKETO_XFORMS_NS )}="${ENKETO_XFORMS_NS}")`, 'g' ); - - return xmlStr.replace( declarationExp, match => { + const declarationExp = new RegExp( + `( xmlns:${this.getNamespacePrefix( + ENKETO_XFORMS_NS + )}="${ENKETO_XFORMS_NS}")`, + 'g' + ); + + return xmlStr.replace(declarationExp, (match) => { i++; - if ( i > 1 ) { + if (i > 1) { return ''; - } else { - return match; } - } ); + return match; + }); }; /** @@ -950,39 +1141,49 @@

js/form-model.js

* @param {string} selector - Selector of the (context) node on which expression is evaluated * @param {number} index - Index of the instance node with that selector */ -FormModel.prototype.makeBugCompliant = function( expr, selector, index ) { - if ( this.noRepeatRefErrorExpected ) { +FormModel.prototype.makeBugCompliant = function (expr, selector, index) { + if (this.noRepeatRefErrorExpected) { return expr; } - let target = this.node( selector, index ).getElement(); + let target = this.node(selector, index).getElement(); // target is null for nested repeats if no repeats exist - if ( !target ) { + if (!target) { return expr; } - const parents = [ target ]; + const parents = [target]; - while ( target && target.parentElement && target.nodeName.toLowerCase() !== 'instance' ) { + while ( + target && + target.parentElement && + target.nodeName.toLowerCase() !== 'instance' + ) { target = target.parentElement; - parents.push( target ); + parents.push(target); } // traverse collection in reverse - parents.forEach( element => { + parents.forEach((element) => { // escape any dots in the node name - const nodeName = element.nodeName.replace( /\./g, '\\.' ); - const siblingsAndSelf = getSiblingElementsAndSelf( element, `${nodeName}:not([template])` ); + const nodeName = element.nodeName.replace(/\./g, '\\.'); + const siblingsAndSelf = getSiblingElementsAndSelf( + element, + `${nodeName}:not([template])` + ); // if the node is a repeat node that has been cloned at least once (i.e. if it has siblings with the same nodeName) - if ( siblingsAndSelf.length > 1 ) { - const parentSelector = getXPath( element, 'instance' ); - const parentIndex = siblingsAndSelf.indexOf( element ); + if (siblingsAndSelf.length > 1) { + const parentSelector = getXPath(element, 'instance'); + const parentIndex = siblingsAndSelf.indexOf(element); // Add position to segments that do not have an XPath predicate. - expr = expr.replace( new RegExp( `${parentSelector}/`, 'g' ), `${parentSelector}[${parentIndex + 1}]/` ); + expr = expr.replace( + new RegExp(`${parentSelector}/`, 'g'), + `${parentSelector}[${parentIndex + 1}]/` + ); } - } ); + }); return expr; }; @@ -990,7 +1191,7 @@

js/form-model.js

/** * Set namespaces for all nodes */ -FormModel.prototype.setNamespaces = function() { +FormModel.prototype.setNamespaces = function () { /** * Passing through all nodes would be very slow with an XForms model that contains lots of nodes such as large secondary instances. * (The namespace XPath axis is not support in native browser XPath evaluators unfortunately). @@ -999,46 +1200,59 @@

js/form-model.js

* We can always expand that later. */ const start = this.hasInstance ? '/model/instance' : ''; - const nodes = this.evaluate( `${start}/*`, 'nodes-ordered', null, null, true ); + const nodes = this.evaluate( + `${start}/*`, + 'nodes-ordered', + null, + null, + true + ); const that = this; let prefix; - nodes.forEach( node => { - if ( node.hasAttributes() ) { - Array.from( node.attributes ).forEach( attribute => { - if ( attribute.name.indexOf( 'xmlns:' ) === 0 ) { - that.namespaces[ attribute.name.substring( 6 ) ] = attribute.value; + nodes.forEach((node) => { + if (node.hasAttributes()) { + Array.from(node.attributes).forEach((attribute) => { + if (attribute.name.indexOf('xmlns:') === 0) { + that.namespaces[attribute.name.substring(6)] = + attribute.value; } - } ); + }); } // add required namespaces to resolver and document if they are missing [ - [ 'orx', OPENROSA_XFORMS_NS, false ], - [ 'jr', JAVAROSA_XFORMS_NS, false ], - [ 'enk', ENKETO_XFORMS_NS, config.repeatOrdinals === true ], - [ 'odk', ODK_XFORMS_NS, false ] - ].forEach( arr => { - if ( !that.getNamespacePrefix( arr[ 1 ] ) ) { - prefix = ( !that.namespaces[ arr[ 0 ] ] ) ? arr[ 0 ] : `__${arr[ 0 ]}`; + ['orx', OPENROSA_XFORMS_NS, false], + ['jr', JAVAROSA_XFORMS_NS, false], + ['enk', ENKETO_XFORMS_NS, config.repeatOrdinals === true], + ['odk', ODK_XFORMS_NS, false], + ].forEach((arr) => { + if (!that.getNamespacePrefix(arr[1])) { + prefix = !that.namespaces[arr[0]] ? arr[0] : `__${arr[0]}`; // add to resolver - that.namespaces[ prefix ] = arr[ 1 ]; + that.namespaces[prefix] = arr[1]; // add to document - if ( arr[ 2 ] ) { - node.setAttributeNS( 'http://www.w3.org/2000/xmlns/', `xmlns:${prefix}`, arr[ 1 ] ); + if (arr[2]) { + node.setAttributeNS( + 'http://www.w3.org/2000/xmlns/', + `xmlns:${prefix}`, + arr[1] + ); } } - } ); - } ); + }); + }); }; /** * @param {string} namespace - Target namespace * @return {string|undefined} Namespace prefix */ -FormModel.prototype.getNamespacePrefix = function( namespace ) { - const found = Object.entries( this.namespaces ).find( arr => arr[ 1 ] === namespace ); +FormModel.prototype.getNamespacePrefix = function (namespace) { + const found = Object.entries(this.namespaces).find( + (arr) => arr[1] === namespace + ); - return found ? found[ 0 ] : undefined; + return found ? found[0] : undefined; }; /** @@ -1046,43 +1260,55 @@

js/form-model.js

* * @return {{lookupNamespaceURI: Function}} namespace resolver */ -FormModel.prototype.getNsResolver = function() { - const namespaces = ( typeof this.namespaces === 'undefined' ) ? {} : this.namespaces; +FormModel.prototype.getNsResolver = function () { + const namespaces = + typeof this.namespaces === 'undefined' ? {} : this.namespaces; return { - lookupNamespaceURI( prefix ) { - return namespaces[ prefix ] || null; - } + lookupNamespaceURI(prefix) { + return namespaces[prefix] || null; + }, }; }; - /** * Shift root to first instance for all absolute paths not starting with /model * * @param {string} expr - Original expression * @return {string} New expression */ -FormModel.prototype.shiftRoot = function( expr ) { +FormModel.prototype.shiftRoot = function (expr) { const LITERALS = /"([^"]*)(")|'([^']*)(')/g; - if ( this.hasInstance ) { + if (this.hasInstance) { // Encode all string literals in order to exclude them, without creating a monsterly regex - expr = expr.replace( LITERALS, ( m, p1, p2, p3, p4 ) => { - const encoded = typeof p1 !== 'undefined' ? encodeURIComponent( p1 ) : encodeURIComponent( p3 ); + expr = expr.replace(LITERALS, (m, p1, p2, p3, p4) => { + const encoded = + typeof p1 !== 'undefined' + ? encodeURIComponent(p1) + : encodeURIComponent(p3); const quote = p2 || p4; return quote + encoded + quote; - } ); + }); // Insert /model/instance[1] - expr = expr.replace( /^(\/(?!model\/)[^/][^/\s,"']*\/)/g, '/model/instance[1]$1' ); - expr = expr.replace( /([^a-zA-Z0-9.\])/*_-])(\/(?!model\/)[^/][^/\s,"']*\/)/g, '$1/model/instance[1]$2' ); + expr = expr.replace( + /^(\/(?!model\/)[^/][^/\s,"']*\/)/g, + '/model/instance[1]$1' + ); + expr = expr.replace( + /([^a-zA-Z0-9.\])/*_-])(\/(?!model\/)[^/][^/\s,"']*\/)/g, + '$1/model/instance[1]$2' + ); // Decode string literals - expr = expr.replace( LITERALS, ( m, p1, p2, p3, p4 ) => { - const decoded = typeof p1 !== 'undefined' ? decodeURIComponent( p1 ) : decodeURIComponent( p3 ); + expr = expr.replace(LITERALS, (m, p1, p2, p3, p4) => { + const decoded = + typeof p1 !== 'undefined' + ? decodeURIComponent(p1) + : decodeURIComponent(p3); const quote = p2 || p4; return quote + decoded + quote; - } ); + }); } return expr; @@ -1096,20 +1322,19 @@

js/form-model.js

* @param {string} expr - Original expression * @return {string} New expression */ -FormModel.prototype.replaceInstanceFn = function( expr ) { +FormModel.prototype.replaceInstanceFn = function (expr) { let prefix; const that = this; // TODO: would be more consistent to use utils.parseFunctionFromExpression() and utils.stripQuotes - return expr.replace( INSTANCE, ( match, quote, id ) => { + return expr.replace(INSTANCE, (match, quote, id) => { prefix = `/model/instance[@id="${id}"]`; // check if referred instance exists in model - if ( that.evaluate( prefix, 'nodes-ordered', null, null, true ).length ) { + if (that.evaluate(prefix, 'nodes-ordered', null, null, true).length) { return prefix; - } else { - throw new FormLogicError( `instance "${id}" does not exist in model` ); } - } ); + throw new FormLogicError(`instance "${id}" does not exist in model`); + }); }; /** @@ -1124,9 +1349,8 @@

js/form-model.js

* @param {string} contextSelector - Context selector * @return {string} New expression */ -FormModel.prototype.replaceCurrentFn = ( expr, contextSelector ) => { - return expr.replace( /current\(\)/g, `${contextSelector}` ); -}; +FormModel.prototype.replaceCurrentFn = (expr, contextSelector) => + expr.replace(/current\(\)/g, `${contextSelector}`); /** * Replaces indexed-repeat(node, path, position, path, position, etc) substrings by converting them @@ -1137,33 +1361,39 @@

js/form-model.js

* @param {number} index - index of context node * @return {string} Converted XPath expression */ -FormModel.prototype.replaceIndexedRepeatFn = function( expr, selector, index ) { +FormModel.prototype.replaceIndexedRepeatFn = function (expr, selector, index) { const that = this; - const indexedRepeats = parseFunctionFromExpression( expr, 'indexed-repeat' ); + const indexedRepeats = parseFunctionFromExpression(expr, 'indexed-repeat'); - indexedRepeats.forEach( indexedRepeat => { - let i, positionedPath; + indexedRepeats.forEach((indexedRepeat) => { + let i; + let positionedPath; let position; - const params = indexedRepeat[ 1 ]; + const params = indexedRepeat[1]; - if ( params.length % 2 === 1 ) { + if (params.length % 2 === 1) { + positionedPath = params[0]; - positionedPath = params[ 0 ]; - - for ( i = params.length - 1; i > 1; i -= 2 ) { + for (i = params.length - 1; i > 1; i -= 2) { // The position will become an XPath predicate. The context for an XPath predicate, is not the same // as the context for the complete expression, so we have to evaluate the position separately. Otherwise // relative paths would break. - position = !isNaN( params[ i ] ) ? params[ i ] : that.evaluate( params[ i ], 'number', selector, index, true ); - positionedPath = positionedPath.replace( params[ i - 1 ], `${params[ i - 1 ]}[position() = ${position}]` ); + position = !isNaN(params[i]) + ? params[i] + : that.evaluate(params[i], 'number', selector, index, true); + positionedPath = positionedPath.replace( + params[i - 1], + `${params[i - 1]}[position() = ${position}]` + ); } - expr = expr.replace( indexedRepeat[ 0 ], positionedPath ); - + expr = expr.replace(indexedRepeat[0], positionedPath); } else { - throw new FormLogicError( `indexed repeat with incorrect number of parameters found: ${indexedRepeat[ 0 ]}` ); + throw new FormLogicError( + `indexed repeat with incorrect number of parameters found: ${indexedRepeat[0]}` + ); } - } ); + }); return expr; }; @@ -1172,16 +1402,17 @@

js/form-model.js

* @param {string} expr - The XPath expression * @return {string} Converted XPath expression */ -FormModel.prototype.replaceVersionFn = function( expr ) { +FormModel.prototype.replaceVersionFn = function (expr) { const that = this; let version; - const versions = parseFunctionFromExpression( expr, 'version' ); + const versions = parseFunctionFromExpression(expr, 'version'); - versions.forEach( versionPart => { - version = version || that.evaluate( '/*/@version', 'string', null, 0, true ); + versions.forEach((versionPart) => { + version = + version || that.evaluate('/*/@version', 'string', null, 0, true); // ignore arguments - expr = expr.replace( versionPart[ 0 ], `"${version}"` ); - } ); + expr = expr.replace(versionPart[0], `"${version}"`); + }); return expr; }; @@ -1192,16 +1423,22 @@

js/form-model.js

* @param {number} index - index of context node * @return {string} Converted XPath expression */ -FormModel.prototype.replacePullDataFn = function( expr, selector, index ) { +FormModel.prototype.replacePullDataFn = function (expr, selector, index) { let pullDataResult; const that = this; - const replacements = this.convertPullDataFn( expr, selector, index ); + const replacements = this.convertPullDataFn(expr, selector, index); - for ( const pullData in replacements ) { - if ( Object.prototype.hasOwnProperty.call( replacements, pullData ) ) { + for (const pullData in replacements) { + if (Object.prototype.hasOwnProperty.call(replacements, pullData)) { // We evaluate this here, so we can use the native evaluator safely. This speeds up pulldata() by about a factor *740*! - pullDataResult = that.evaluate( replacements[ pullData ], 'string', selector, index, true ); - expr = expr.replace( pullData, `"${pullDataResult}"` ); + pullDataResult = that.evaluate( + replacements[pullData], + 'string', + selector, + index, + true + ); + expr = expr.replace(pullData, `"${pullDataResult}"`); } } @@ -1214,40 +1451,46 @@

js/form-model.js

* @param {number} index - index of context node * @return {string} Converted XPath expression */ -FormModel.prototype.convertPullDataFn = function( expr, selector, index ) { +FormModel.prototype.convertPullDataFn = function (expr, selector, index) { const that = this; - const pullDatas = parseFunctionFromExpression( expr, 'pulldata' ); + const pullDatas = parseFunctionFromExpression(expr, 'pulldata'); const replacements = {}; - if ( !pullDatas.length ) { + if (!pullDatas.length) { return replacements; } - pullDatas.forEach( pullData => { + pullDatas.forEach((pullData) => { let searchValue; let searchXPath; - const params = pullData[ 1 ]; - - if ( params.length === 4 ) { + const params = pullData[1]; + if (params.length === 4) { // strip quotes - params[ 1 ] = stripQuotes( params[ 1 ] ); - params[ 2 ] = stripQuotes( params[ 2 ] ); + params[1] = stripQuotes(params[1]); + params[2] = stripQuotes(params[2]); // TODO: the 2nd and 3rd parameter could probably also be expressions. // The 4th argument will become an XPath predicate. The context for an XPath predicate, is not the same // as the context for the complete expression, so we have to evaluate the position separately. Otherwise // relative paths would break. - searchValue = `'${that.evaluate( params[ 3 ], 'string', selector, index, true )}'`; - searchXPath = `instance(${params[ 0 ]})/root/item[${params[ 2 ]} = ${searchValue}]/${params[ 1 ]}`; - - replacements[ pullData[ 0 ] ] = searchXPath; - + searchValue = `'${that.evaluate( + params[3], + 'string', + selector, + index, + true + )}'`; + searchXPath = `instance(${params[0]})/root/item[${params[2]} = ${searchValue}]/${params[1]}`; + + replacements[pullData[0]] = searchXPath; } else { - throw new FormLogicError( `pulldata with incorrect number of parameters found: ${pullData[ 0 ]}` ); + throw new FormLogicError( + `pulldata with incorrect number of parameters found: ${pullData[0]}` + ); } - } ); + }); return replacements; }; @@ -1267,8 +1510,25 @@

js/form-model.js

* certain that there are no date comparisons) * @return {number|string|boolean|Array<Element>} The result */ -FormModel.prototype.evaluate = function( expr, resTypeStr, selector, index, tryNative ) { - let j, context, doc, resTypeNum, resultTypes, result, collection, response, repeats, cacheKey, original, cacheable; +FormModel.prototype.evaluate = function ( + expr, + resTypeStr, + selector, + index, + tryNative +) { + let j; + let context; + let doc; + let resTypeNum; + let resultTypes; + let result; + let collection; + let response; + let repeats; + let cacheKey; + let original; + let cacheable; // console.debug( 'evaluating expr: ' + expr + ' with context selector: ' + selector + ', 0-based index: ' + // index + ' and result type: ' + resTypeStr ); @@ -1279,69 +1539,69 @@

js/form-model.js

doc = this.xml; repeats = null; - if ( selector ) { - collection = this.node( selector ).getElements(); + if (selector) { + collection = this.node(selector).getElements(); repeats = collection.length; - context = collection[ index ]; + context = collection[index]; } else { // either the first data child of the first instance or the first child (for loaded instances without a model) context = this.rootElement; } - if ( !context ) { - console.error( 'no context element found', selector, index ); + if (!context) { + console.error('no context element found', selector, index); } // cache key includes the number of repeated context nodes, // to force a new cache item if the number of repeated changes to > 0 // TODO: these cache keys can get quite large. Would it be beneficial to get the md5 of the key? - cacheKey = [ expr, selector, index, repeats ].join( '|' ); + cacheKey = [expr, selector, index, repeats].join('|'); // These functions need to come before makeBugCompliant. // An expression transformation with indexed-repeat or pulldata cannot be cached because in // "indexed-repeat(node, repeat nodeset, index)" the index parameter could be an expression. - expr = this.replaceIndexedRepeatFn( expr, selector, index ); - expr = this.replacePullDataFn( expr, selector, index ); - cacheable = ( original === expr ); + expr = this.replaceIndexedRepeatFn(expr, selector, index); + expr = this.replacePullDataFn(expr, selector, index); + cacheable = original === expr; // if no cached conversion exists - if ( !this.convertedExpressions[ cacheKey ] ) { + if (!this.convertedExpressions[cacheKey]) { expr = expr.trim(); - expr = this.replaceInstanceFn( expr ); - expr = this.replaceVersionFn( expr ); - expr = this.replaceCurrentFn( expr, getXPath( context, 'instance', true ) ); + expr = this.replaceInstanceFn(expr); + expr = this.replaceVersionFn(expr); + expr = this.replaceCurrentFn(expr, getXPath(context, 'instance', true)); // shiftRoot should come after replaceCurrentFn - expr = this.shiftRoot( expr ); + expr = this.shiftRoot(expr); // path corrections for repeated nodes: http://opendatakit.github.io/odk-xform-spec/#a-big-deviation-with-xforms - if ( repeats && repeats > 1 ) { - expr = this.makeBugCompliant( expr, selector, index ); + if (repeats && repeats > 1) { + expr = this.makeBugCompliant(expr, selector, index); } // decode - expr = expr.replace( /&lt;/g, '<' ); - expr = expr.replace( /&gt;/g, '>' ); - expr = expr.replace( /&quot;/g, '"' ); - if ( cacheable ) { - this.convertedExpressions[ cacheKey ] = expr; + expr = expr.replace(/&lt;/g, '<'); + expr = expr.replace(/&gt;/g, '>'); + expr = expr.replace(/&quot;/g, '"'); + if (cacheable) { + this.convertedExpressions[cacheKey] = expr; } } else { - expr = this.convertedExpressions[ cacheKey ]; + expr = this.convertedExpressions[cacheKey]; } resultTypes = { - 0: [ 'any', 'ANY_TYPE' ], - 1: [ 'number', 'NUMBER_TYPE', 'numberValue' ], - 2: [ 'string', 'STRING_TYPE', 'stringValue' ], - 3: [ 'boolean', 'BOOLEAN_TYPE', 'booleanValue' ], - 6: [ 'nodes', 'UNORDERED_NODE_SNAPSHOT_TYPE' ], - 7: [ 'nodes-ordered', 'ORDERED_NODE_SNAPSHOT_TYPE' ], - 9: [ 'node', 'FIRST_ORDERED_NODE_TYPE', 'singleNodeValue' ] + 0: ['any', 'ANY_TYPE'], + 1: ['number', 'NUMBER_TYPE', 'numberValue'], + 2: ['string', 'STRING_TYPE', 'stringValue'], + 3: ['boolean', 'BOOLEAN_TYPE', 'booleanValue'], + 6: ['nodes', 'UNORDERED_NODE_SNAPSHOT_TYPE'], + 7: ['nodes-ordered', 'ORDERED_NODE_SNAPSHOT_TYPE'], + 9: ['node', 'FIRST_ORDERED_NODE_TYPE', 'singleNodeValue'], }; // translate typeStr to number according to DOM level 3 XPath constants - for ( resTypeNum in resultTypes ) { - if ( Object.prototype.hasOwnProperty.call( resultTypes, resTypeNum ) ) { - resTypeNum = Number( resTypeNum ); - if ( resultTypes[ resTypeNum ][ 0 ] === resTypeStr ) { + for (resTypeNum in resultTypes) { + if (Object.prototype.hasOwnProperty.call(resultTypes, resTypeNum)) { + resTypeNum = Number(resTypeNum); + if (resultTypes[resTypeNum][0] === resTypeStr) { break; } else { resTypeNum = 0; @@ -1350,64 +1610,92 @@

js/form-model.js

} // try native to see if that works... (will not work if the expr contains custom OpenRosa functions) - if ( tryNative && typeof doc.evaluate !== 'undefined' && !OPENROSA.test( expr ) ) { + if ( + tryNative && + typeof doc.evaluate !== 'undefined' && + !OPENROSA.test(expr) + ) { try { // console.log( 'trying the blazing fast native XPath Evaluator for', expr, index ); - result = doc.evaluate( expr, context, this.getNsResolver(), resTypeNum, null ); - } catch ( e ) { - //console.log( '%cWell native XPath evaluation did not work... No worries, worth a shot, the expression probably ' + + result = doc.evaluate( + expr, + context, + this.getNsResolver(), + resTypeNum, + null + ); + } catch (e) { + // console.log( '%cWell native XPath evaluation did not work... No worries, worth a shot, the expression probably ' + // 'contained unknown OpenRosa functions or errors:', expr ); } } // if that didn't work, try the slow XPathJS evaluator - if ( !result ) { + if (!result) { try { - if ( typeof doc.jsEvaluate === 'undefined' ) { + if (typeof doc.jsEvaluate === 'undefined') { this.bindJsEvaluator(); } // console.log( 'trying the slow enketo-xpathjs "openrosa" evaluator for', expr, index ); - result = doc.jsEvaluate( expr, context, this.getNsResolver(), resTypeNum, null ); - } catch ( e ) { - throw new FormLogicError( `Could not evaluate: ${expr}, message: ${e.message}` ); + result = doc.jsEvaluate( + expr, + context, + this.getNsResolver(), + resTypeNum, + null + ); + } catch (e) { + throw new FormLogicError( + `Could not evaluate: ${expr}, message: ${e.message}` + ); } } // get desired value from the result object - if ( result ) { + if (result) { // for type = any, see if a valid string, number or boolean is returned - if ( resTypeNum === 0 ) { - for ( resTypeNum in resultTypes ) { - if ( Object.prototype.hasOwnProperty.call( resultTypes, resTypeNum ) ) { - resTypeNum = Number( resTypeNum ); - if ( resTypeNum === Number( result.resultType ) && resTypeNum > 0 && resTypeNum < 4 ) { - response = result[ resultTypes[ resTypeNum ][ 2 ] ]; + if (resTypeNum === 0) { + for (resTypeNum in resultTypes) { + if ( + Object.prototype.hasOwnProperty.call( + resultTypes, + resTypeNum + ) + ) { + resTypeNum = Number(resTypeNum); + if ( + resTypeNum === Number(result.resultType) && + resTypeNum > 0 && + resTypeNum < 4 + ) { + response = result[resultTypes[resTypeNum][2]]; break; } } } - if ( !response ) { - console.error( `Expression: ${expr} did not return any boolean, string or number value as expected` ); + if (!response) { + console.error( + `Expression: ${expr} did not return any boolean, string or number value as expected` + ); } - } else if ( resTypeNum === 6 || resTypeNum === 7 ) { + } else if (resTypeNum === 6 || resTypeNum === 7) { // response is an array of Elements response = []; - for ( j = 0; j < result.snapshotLength; j++ ) { - response.push( result.snapshotItem( j ) ); + for (j = 0; j < result.snapshotLength; j++) { + response.push(result.snapshotItem(j)); } } else { - response = result[ resultTypes[ resTypeNum ][ 2 ] ]; + response = result[resultTypes[resTypeNum][2]]; } return response; } }; - /** * Placeholder function meant to be overwritten */ -FormModel.prototype.getUpdateEventData = () => /*node, type*/ {}; +FormModel.prototype.getUpdateEventData = () => /* node, type */ {}; /** * Placeholder function meant to be overwritten diff --git a/docs/js_form.js.html b/docs/js_form.js.html index 64e83aa03..f3ffc0cc8 100644 --- a/docs/js_form.js.html +++ b/docs/js_form.js.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -47,12 +47,22 @@

js/form.js

-
import { FormModel } from './form-model';
-import $ from 'jquery';
-import { parseFunctionFromExpression, stripQuotes, getFilename, joinPath } from './utils';
-import { getXPath, getChild, closestAncestorUntil, getSiblingElement } from './dom-utils';
+            
import $ from 'jquery';
 import { t } from 'enketo/translator';
 import config from 'enketo/config';
+import { FormModel } from './form-model';
+import {
+    parseFunctionFromExpression,
+    stripQuotes,
+    getFilename,
+    joinPath,
+} from './utils';
+import {
+    getXPath,
+    getChild,
+    closestAncestorUntil,
+    getSiblingElement,
+} from './dom-utils';
 import inputHelper from './input';
 import repeatModule from './repeat';
 import tocModule from './toc';
@@ -85,12 +95,15 @@ 

js/form.js

* @param {string} [options.language] - Overrides the default languages rules of the XForm itself. Pass any valid and present-in-the-form IANA subtag string, e.g. `ar`. * @class */ -function Form( formEl, data, options ) { - const $form = $( formEl ); - - if ( typeof formEl === 'string' ) { - console.deprecate( 'Form instantiation using a selector', 'a HTML <form> element' ); - formEl = $form[ 0 ]; +function Form(formEl, data, options) { + const $form = $(formEl); + + if (typeof formEl === 'string') { + console.deprecate( + 'Form instantiation using a selector', + 'a HTML <form> element' + ); + formEl = $form[0]; } this.nonRepeats = {}; @@ -100,10 +113,10 @@

js/form.js

this.view = { $: $form, html: formEl, - clone: formEl.cloneNode( true ) + clone: formEl.cloneNode(true), }; - this.model = new FormModel( data ); - this.repeatsPresent = !!this.view.html.querySelector( '.or-repeat' ); + this.model = new FormModel(data); + this.repeatsPresent = !!this.view.html.querySelector('.or-repeat'); this.widgetsInitialized = false; this.repeatsInitialized = false; this.pageNavigationBlocked = false; @@ -123,24 +136,24 @@

js/form.js

*/ get evaluationCascade() { return [ - this.calc.update.bind( this.calc ), - this.repeats.countUpdate.bind( this.repeats ), - this.relevant.update.bind( this.relevant ), - this.output.update.bind( this.output ), - this.itemset.update.bind( this.itemset ), - this.required.update.bind( this.required ), - this.readonly.update.bind( this.readonly ), - this.validationUpdate - ].concat( this.evaluationCascadeAdditions ); + this.calc.update.bind(this.calc), + this.repeats.countUpdate.bind(this.repeats), + this.relevant.update.bind(this.relevant), + this.output.update.bind(this.output), + this.itemset.update.bind(this.itemset), + this.required.update.bind(this.required), + this.readonly.update.bind(this.readonly), + this.validationUpdate, + ].concat(this.evaluationCascadeAdditions); }, /** * @type {string} */ get recordName() { - return this.view.$.attr( 'name' ); + return this.view.$.attr('name'); }, - set recordName( name ) { - this.view.$.attr( 'name', name ); + set recordName(name) { + this.view.$.attr('name', name); }, /** * @type {boolean} @@ -148,10 +161,10 @@

js/form.js

get editStatus() { return this.view.html.dataset.edited === 'true'; }, - set editStatus( status ) { + set editStatus(status) { // only trigger edit event once - if ( status && status !== this.editStatus ) { - this.view.html.dispatchEvent( events.Edited() ); + if (status && status !== this.editStatus) { + this.view.html.dispatchEvent(events.Edited()); } this.view.html.dataset.edited = status; }, @@ -159,7 +172,7 @@

js/form.js

* @type {string} */ get surveyName() { - return this.view.$.find( '#form-title' ).text(); + return this.view.$.find('#form-title').text(); }, /** * @type {string} @@ -189,19 +202,19 @@

js/form.js

* @type {string} */ get encryptionKey() { - return this.view.$.data( 'base64rsapublickey' ); + return this.view.$.data('base64rsapublickey'); }, /** * @type {string} */ get action() { - return this.view.$.attr( 'action' ); + return this.view.$.attr('action'); }, /** * @type {string} */ get method() { - return this.view.$.attr( 'method' ); + return this.view.$.attr('method'); }, /** * @type {string} @@ -215,7 +228,7 @@

js/form.js

* @type {Array<string>} */ get constraintClassesInvalid() { - return [ 'invalid-constraint' ]; + return ['invalid-constraint']; }, /** * To facilitate forks that support multiple constraints per question @@ -223,7 +236,7 @@

js/form.js

* @type {Array<string>} */ get constraintAttributes() { - return [ 'data-constraint' ]; + return ['data-constraint']; }, /** * @type {Array<string>} @@ -236,7 +249,7 @@

js/form.js

*/ get currentLanguage() { return this.langs.currentLanguage; - } + }, }; /** @@ -245,12 +258,12 @@

js/form.js

* @param {object} module - Enketo Core module * @return {object} updated module */ -Form.prototype.addModule = function( module ) { - return Object.create( module, { +Form.prototype.addModule = function (module) { + return Object.create(module, { form: { - value: this - } - } ); + value: this, + }, + }); }; /** @@ -260,60 +273,69 @@

js/form.js

* * @return {Array<string>} List of initialization errors. */ -Form.prototype.init = function() { +Form.prototype.init = function () { let loadErrors = []; const that = this; - this.toc = this.addModule( tocModule ); - this.pages = this.addModule( pageModule ); - this.langs = this.addModule( languageModule ); - this.progress = this.addModule( progressModule ); - this.widgets = this.addModule( widgetModule ); - this.preloads = this.addModule( preloadModule ); - this.relevant = this.addModule( relevantModule ); - this.repeats = this.addModule( repeatModule ); - this.input = this.addModule( inputHelper ); - this.output = this.addModule( outputModule ); - this.itemset = this.addModule( itemsetModule ); - this.calc = this.addModule( calculationModule ); - this.required = this.addModule( requiredModule ); - this.mask = this.addModule( maskModule ); - this.readonly = this.addModule( readonlyModule ); + this.toc = this.addModule(tocModule); + this.pages = this.addModule(pageModule); + this.langs = this.addModule(languageModule); + this.progress = this.addModule(progressModule); + this.widgets = this.addModule(widgetModule); + this.preloads = this.addModule(preloadModule); + this.relevant = this.addModule(relevantModule); + this.repeats = this.addModule(repeatModule); + this.input = this.addModule(inputHelper); + this.output = this.addModule(outputModule); + this.itemset = this.addModule(itemsetModule); + this.calc = this.addModule(calculationModule); + this.required = this.addModule(requiredModule); + this.mask = this.addModule(maskModule); + this.readonly = this.addModule(readonlyModule); // Handle odk-instance-first-load event - this.model.events.addEventListener( events.InstanceFirstLoad().type, event => { - this.calc.performAction( 'setvalue', event ); - this.calc.performAction( 'setgeopoint', event ); - } ); + this.model.events.addEventListener( + events.InstanceFirstLoad().type, + (event) => { + this.calc.performAction('setvalue', event); + this.calc.performAction('setgeopoint', event); + } + ); // Handle odk-new-repeat event before initializing repeats - this.view.html.addEventListener( events.NewRepeat().type, event => { - this.calc.performAction( 'setvalue', event ); - this.calc.performAction( 'setgeopoint', event ); - } ); + this.view.html.addEventListener(events.NewRepeat().type, (event) => { + this.calc.performAction('setvalue', event); + this.calc.performAction('setgeopoint', event); + }); // Handle xforms-value-changed - this.view.html.addEventListener( events.XFormsValueChanged().type, event => { - this.calc.performAction( 'setvalue', event ); - this.calc.performAction( 'setgeopoint', event ); - } ); + this.view.html.addEventListener( + events.XFormsValueChanged().type, + (event) => { + this.calc.performAction('setvalue', event); + this.calc.performAction('setgeopoint', event); + } + ); // Before initializing form view and model, passthrough some model events externally // Because of instance-first-load actions, this should be done before the model is initialized. This is important for custom // applications that submit each individual value separately (opposed to a full XML model at the end). - this.model.events.addEventListener( events.DataUpdate().type, event => { - that.view.html.dispatchEvent( events.DataUpdate( event.detail ) ); - } ); + this.model.events.addEventListener(events.DataUpdate().type, (event) => { + that.view.html.dispatchEvent(events.DataUpdate(event.detail)); + }); // This probably does not need to be before model.init(); - this.model.events.addEventListener( events.Removed().type, event => { - that.view.html.dispatchEvent( events.Removed( event.detail ) ); - } ); + this.model.events.addEventListener(events.Removed().type, (event) => { + that.view.html.dispatchEvent(events.Removed(event.detail)); + }); - loadErrors = loadErrors.concat( this.model.init() ); + loadErrors = loadErrors.concat(this.model.init()); - if ( typeof this.model === 'undefined' || !( this.model instanceof FormModel ) ) { - loadErrors.push( 'Form could not be initialized without a model.' ); + if ( + typeof this.model === 'undefined' || + !(this.model instanceof FormModel) + ) { + loadErrors.push('Form could not be initialized without a model.'); return loadErrors; } @@ -323,9 +345,9 @@

js/form.js

// before widgets.init (as instanceID used in offlineFilepicker widget) // store the current instanceID as data on the form element so it can be easily accessed by e.g. widgets - this.view.$.data( { - instanceID: this.model.instanceID - } ); + this.view.$.data({ + instanceID: this.model.instanceID, + }); // before calc.update! this.grosslyViolateStandardComplianceByIgnoringCertainCalcs(); @@ -333,21 +355,33 @@

js/form.js

this.calc.update(); // before itemset.update - this.langs.init( this.options.language ); + this.langs.init(this.options.language); // before repeats.init so that template contains role="page" when applicable this.pages.init(); // after radio button data-name setting (now done in XLST) // Set temporary event handler to ensure calculations in newly added repeats are run for the first time - const tempHandlerAddRepeat = event => this.calc.update( event.detail ); - const tempHandlerRemoveRepeat = () => this.all = {}; - this.view.html.addEventListener( events.AddRepeat().type, tempHandlerAddRepeat ); - this.view.html.addEventListener( events.RemoveRepeat().type, tempHandlerRemoveRepeat ); + const tempHandlerAddRepeat = (event) => this.calc.update(event.detail); + const tempHandlerRemoveRepeat = () => (this.all = {}); + this.view.html.addEventListener( + events.AddRepeat().type, + tempHandlerAddRepeat + ); + this.view.html.addEventListener( + events.RemoveRepeat().type, + tempHandlerRemoveRepeat + ); this.repeatsInitialized = true; this.repeats.init(); - this.view.html.removeEventListener( events.AddRepeat().type, tempHandlerAddRepeat ); - this.view.html.removeEventListener( events.RemoveRepeat().type, tempHandlerRemoveRepeat ); + this.view.html.removeEventListener( + events.AddRepeat().type, + tempHandlerAddRepeat + ); + this.view.html.removeEventListener( + events.RemoveRepeat().type, + tempHandlerRemoveRepeat + ); // after repeats.init, but before itemset.update this.output.update(); @@ -363,10 +397,10 @@

js/form.js

// after setAllVals, after repeats.init this.options.input = this.input; - this.options.pathToAbsolute = this.pathToAbsolute.bind( this ); - this.options.evaluate = this.model.evaluate.bind( this.model ); - this.options.getModelValue = this.getModelValue.bind( this ); - this.widgetsInitialized = this.widgets.init( null, this.options ); + this.options.pathToAbsolute = this.pathToAbsolute.bind(this); + this.options.evaluate = this.model.evaluate.bind(this.model); + this.options.getModelValue = this.getModelValue.bind(this); + this.widgetsInitialized = this.widgets.init(null, this.options); // after widgets.init(), and after repeats.init(), and after pages.init() this.relevant.update(); @@ -385,23 +419,23 @@

js/form.js

this.editStatus = false; - if ( this.options.printRelevantOnly !== false ) { - this.view.$.addClass( 'print-relevant-only' ); + if (this.options.printRelevantOnly !== false) { + this.view.$.addClass('print-relevant-only'); } - setTimeout( () => { + setTimeout(() => { that.progress.update(); - }, 0 ); + }, 0); this.initialized = true; return loadErrors; - } catch ( e ) { - console.error( e ); - loadErrors.push( `${e.name}: ${e.message}` ); + } catch (e) { + console.error(e); + loadErrors.push(`${e.name}: ${e.message}`); } - document.querySelector( 'body' ).scrollIntoView(); + document.querySelector('body').scrollIntoView(); return loadErrors; }; @@ -410,12 +444,14 @@

js/form.js

* @param {string} xpath - simple path to question * @return {Array<string>} A list of errors originated from `goToTarget`. Empty if everything went fine. */ -Form.prototype.goTo = function( xpath ) { +Form.prototype.goTo = function (xpath) { const errors = []; - if ( !this.goToTarget( this.getGoToTarget( xpath ) ) ) { - errors.push( t( 'alert.gotonotfound.msg', { - path: xpath.substring( xpath.lastIndexOf( '/' ) + 1 ) - } ) ); + if (!this.goToTarget(this.getGoToTarget(xpath))) { + errors.push( + t('alert.gotonotfound.msg', { + path: xpath.substring(xpath.lastIndexOf('/') + 1), + }) + ); } return errors; @@ -427,9 +463,9 @@

js/form.js

* @param {{include: boolean}} [include] - Optional object items to exclude if false * @return {string} XML string of primary instance */ -Form.prototype.getDataStr = function( include = {} ) { +Form.prototype.getDataStr = function (include = {}) { // By default everything is included - if ( include.irrelevant === false ) { + if (include.irrelevant === false) { return this.getDataStrWithoutIrrelevantNodes(); } @@ -444,14 +480,14 @@

js/form.js

* * @return {Element} the new form element */ -Form.prototype.resetView = function() { - //form language selector was moved outside of <form> so has to be separately removed - if ( this.langs.formLanguages ) { +Form.prototype.resetView = function () { + // form language selector was moved outside of <form> so has to be separately removed + if (this.langs.formLanguages) { this.langs.formLanguages.remove(); } - this.view.html.replaceWith( this.view.clone ); + this.view.html.replaceWith(this.view.clone); - return document.querySelector( 'form.or' ); + return document.querySelector('form.or'); }; /** @@ -466,47 +502,82 @@

js/form.js

* @param {boolean} tryNative - whether to try the native evaluator, i.e. if there is no risk it would create an incorrect result such as with date comparisons * @return {string} updated expression */ -Form.prototype.replaceChoiceNameFn = function( expr, resTypeStr, context, index, tryNative ){ - const choiceNames = parseFunctionFromExpression( expr, 'jr:choice-name' ); - - choiceNames.forEach( choiceName => { - const params = choiceName[ 1 ]; - - if ( params.length === 2 ) { +Form.prototype.replaceChoiceNameFn = function ( + expr, + resTypeStr, + context, + index, + tryNative +) { + const choiceNames = parseFunctionFromExpression(expr, 'jr:choice-name'); + + choiceNames.forEach((choiceName) => { + const params = choiceName[1]; + + if (params.length === 2) { let label = ''; - const value = this.model.evaluate( params[ 0 ], resTypeStr, context, index, tryNative ); - let name = stripQuotes( params[ 1 ] ).trim(); - name = name.startsWith( '/' ) ? name : joinPath( context, name ); - const inputs = [ ...this.view.html.querySelectorAll( `[name="${name}"], [data-name="${name}"]` ) ]; - const nodeName = inputs.length ? inputs[0].nodeName.toLowerCase() : null; - - if ( !value || !inputs.length ) { + const value = this.model.evaluate( + params[0], + resTypeStr, + context, + index, + tryNative + ); + let name = stripQuotes(params[1]).trim(); + name = name.startsWith('/') ? name : joinPath(context, name); + const inputs = [ + ...this.view.html.querySelectorAll( + `[name="${name}"], [data-name="${name}"]` + ), + ]; + const nodeName = inputs.length + ? inputs[0].nodeName.toLowerCase() + : null; + + if (!value || !inputs.length) { label = ''; - } else if ( nodeName === 'select' ) { - const found = inputs.find( input => input.querySelector( `[value="${value}"]` ) ); - label = found ? found.querySelector( `[value="${value}"]` ).textContent : ''; - } else if ( nodeName === 'input' ) { - const list = inputs[0].getAttribute( 'list' ); - - if ( !list ){ - const found = inputs.find( input => input.getAttribute( 'value' ) === value ); - const firstSiblingLabelEl = found ? getSiblingElement( found, '.option-label.active' ) : []; - label = firstSiblingLabelEl ? firstSiblingLabelEl.textContent : ''; + } else if (nodeName === 'select') { + const found = inputs.find((input) => + input.querySelector(`[value="${CSS.escape(value)}"]`) + ); + label = found + ? found.querySelector(`[value="${CSS.escape(value)}"]`) + .textContent + : ''; + } else if (nodeName === 'input') { + const list = inputs[0].getAttribute('list'); + + if (!list) { + const found = inputs.find( + (input) => input.getAttribute('value') === value + ); + const firstSiblingLabelEl = found + ? getSiblingElement(found, '.option-label.active') + : []; + label = firstSiblingLabelEl + ? firstSiblingLabelEl.textContent + : ''; } else { - const firstSiblingListEl = getSiblingElement( inputs[0], `datalist#${CSS.escape( list )}` ); - if ( firstSiblingListEl ){ - const optionEl = firstSiblingListEl.querySelector( `[data-value="${value}"]` ); - label = optionEl ? optionEl.getAttribute( 'value' ) : ''; + const firstSiblingListEl = getSiblingElement( + inputs[0], + `datalist#${CSS.escape(list)}` + ); + if (firstSiblingListEl) { + const optionEl = firstSiblingListEl.querySelector( + `[data-value="${value}"]` + ); + label = optionEl ? optionEl.getAttribute('value') : ''; } } } - expr = expr.replace( choiceName[ 0 ], `"${label}"` ); + expr = expr.replace(choiceName[0], `"${label}"`); } else { - throw new FormLogicError( `jr:choice-name function has incorrect number of parameters: ${choiceName[ 0 ]}` ); + throw new FormLogicError( + `jr:choice-name function has incorrect number of parameters: ${choiceName[0]}` + ); } - - } ); + }); return expr; }; @@ -519,49 +590,68 @@

js/form.js

* @param {jQuery} $group - group of elements for which form controls should be updated (with current model values) * @param {number} groupIndex - index of the group */ -Form.prototype.setAllVals = function( $group, groupIndex ) { +Form.prototype.setAllVals = function ($group, groupIndex) { const that = this; - const selector = ( $group && $group.attr( 'name' ) ) ? $group.attr( 'name' ) : null; + const selector = $group && $group.attr('name') ? $group.attr('name') : null; + + groupIndex = typeof groupIndex !== 'undefined' ? groupIndex : null; - groupIndex = ( typeof groupIndex !== 'undefined' ) ? groupIndex : null; + this.model + .node(selector, groupIndex) + .getElements() + .reduce((nodes, current) => { + const newNodes = [...current.querySelectorAll('*')].filter( + (n) => n.children.length === 0 && n.textContent + ); - this.model.node( selector, groupIndex ).getElements() - .reduce( ( nodes, current ) => { - const newNodes = [ ...current.querySelectorAll( '*' ) ].filter( ( n ) => n.children.length === 0 && n.textContent ); + return nodes.concat(newNodes); + }, []) + .forEach((element) => { + let value; + let name; - return nodes.concat( newNodes ); - }, [] ) - .forEach( element => { try { - var value = element.textContent; - var name = getXPath( element, 'instance' ); - const index = that.model.node( name ).getElements().indexOf( element ); - const control = that.input.find( name, index ); - if ( control ) { - that.input.setVal( control, value, null ); - if ( that.input.getXmlType( control ) === 'binary' && value.startsWith( 'jr://' ) && element.getAttribute( 'src' ) ) { - control.setAttribute( 'data-loaded-url', element.getAttribute( 'src' ) ); + value = element.textContent; + name = getXPath(element, 'instance'); + const index = that.model + .node(name) + .getElements() + .indexOf(element); + const control = that.input.find(name, index); + if (control) { + that.input.setVal(control, value, null); + if ( + that.input.getXmlType(control) === 'binary' && + value.startsWith('jr://') && + element.getAttribute('src') + ) { + control.setAttribute( + 'data-loaded-url', + element.getAttribute('src') + ); } } - } catch ( e ) { - console.error( e ); + } catch (e) { + console.error(e); // TODO: Test if this correctly adds to loadErrors - //loadErrors.push( 'Could not load input field value with name: ' + name + ' and value: ' + value ); - throw new Error( `Could not load input field value with name: ${name} and value: ${value}` ); + // loadErrors.push( 'Could not load input field value with name: ' + name + ' and value: ' + value ); + throw new Error( + `Could not load input field value with name: ${name} and value: ${value}` + ); } - } ); + }); }; /** * @param {jQuery} $control - HTML form control * @return {string|undefined} Value */ -Form.prototype.getModelValue = function( $control ) { - const control = $control[ 0 ]; - const path = this.input.getName( control ); - const index = this.input.getIndex( control ); +Form.prototype.getModelValue = function ($control) { + const control = $control[0]; + const path = this.input.getName(control); + const index = this.input.getIndex(control); - return this.model.node( path, index ).getVal(); + return this.model.node(path, index).getVal(); }; /** @@ -572,39 +662,52 @@

js/form.js

* @param {UpdatedDataNodes} updated - object that contains information on updated nodes * @return {jQuery} - A jQuery collection of elements */ -Form.prototype.getRelatedNodes = function( attr, filter, updated ) { +Form.prototype.getRelatedNodes = function (attr, filter, updated) { let repeatControls = null; let controls; updated = updated || {}; filter = filter || ''; + const { cloned, repeatPath } = updated; + // The collection of non-repeat inputs, calculations and groups is cached (unchangeable) - if ( !this.nonRepeats[ attr ] ) { - controls = [ ...this.view.html.querySelectorAll( `:not(.or-repeat-info)[${attr}]` ) ] - .filter( el => !el.closest( '.or-repeat' ) ); - this.nonRepeats[ attr ] = this.filterRadioCheckSiblings( controls ); + if (!this.nonRepeats[attr]) { + controls = [ + ...this.view.html.querySelectorAll( + `:not(.or-repeat-info)[${attr}]` + ), + ].filter((el) => !el.closest('.or-repeat')); + this.nonRepeats[attr] = this.filterRadioCheckSiblings(controls); } // If the updated node is inside a repeat (and there are multiple repeats present) - if ( typeof updated.repeatPath !== 'undefined' && updated.repeatIndex >= 0 ) { - const repeatEl = [ ...this.view.html.querySelectorAll( `.or-repeat[name="${updated.repeatPath}"]` ) ][ updated.repeatIndex ]; - controls = repeatEl ? [ ...repeatEl.querySelectorAll( `[${attr}]` ) ] : []; - repeatControls = this.filterRadioCheckSiblings( controls ); + if (typeof repeatPath !== 'undefined' && updated.repeatIndex >= 0) { + const repeatEl = [ + ...this.view.html.querySelectorAll( + `.or-repeat[name="${repeatPath}"]` + ), + ][updated.repeatIndex]; + controls = repeatEl ? [...repeatEl.querySelectorAll(`[${attr}]`)] : []; + repeatControls = this.filterRadioCheckSiblings(controls); } // If a new repeat was created, update the cached collection of all form controls with that attribute - // If a repeat was deleted ( update.repeatPath && !updated.cloned), rebuild cache. + // If a repeat was deleted (updated.repeatPath && !updated.cloned), rebuild cache. // Exclude outputs from the cache, because outputs can be added via itemsets (in labels). - if ( !this.all[ attr ] || ( updated.repeatPath && !updated.cloned ) || filter === '.or-output' ) { + if (!this.all[attr] || (repeatPath && !cloned) || filter === '.or-output') { // (re)build the cache // However, if repeats have not been initialized exclude nodes inside a repeat until the first repeat has been added during repeat initialization. // The default view repeat will be removed during initialization (and stored as template), before it is re-added, if necessary. // We need to avoid adding these fields to the initial cache, // so we don't waste time evaluating logic, and don't have to rebuild the cache after repeats have been initialized. - this.all[ attr ] = this.repeatsInitialized ? this.filterRadioCheckSiblings( [ ...this.view.html.querySelectorAll( `[${attr}]` ) ] ) : this.nonRepeats[ attr ]; - } else if ( updated.cloned && repeatControls ) { + this.all[attr] = this.repeatsInitialized + ? this.filterRadioCheckSiblings([ + ...this.view.html.querySelectorAll(`[${attr}]`), + ]) + : this.nonRepeats[attr]; + } else if (cloned && repeatControls) { // update the cache - this.all[ attr ] = this.all[ attr ].concat( repeatControls ); + this.all[attr] = this.all[attr].concat(repeatControls); } /** @@ -613,55 +716,72 @@

js/form.js

* However, this will break if people are referring to nodes in other * repeats such as with /path/to/repeat[3]/node, /path/to/repeat[position() = 3]/node or indexed-repeat(/path/to/repeat/node, /path/to/repeat, 3). * We accept that for now. - **/ + * */ let collection; - if ( repeatControls ) { + if (repeatControls) { // The non-repeat fields have to be added too, e.g. to update a calculated item with count(to/repeat/node) at the top level - collection = this.nonRepeats[ attr ].concat( repeatControls ); + collection = this.nonRepeats[attr].concat(repeatControls); } else { - collection = this.all[ attr ]; + collection = this.all[attr]; } let selector = []; // Add selectors based on specific changed nodes - if ( !updated.nodes || updated.nodes.length === 0 ) { - selector = [ `${filter}[${attr}]` ]; + if (!updated.nodes || updated.nodes.length === 0) { + if ( + repeatControls != null && + cloned && + filter === '.itemset-template' + ) { + selector = [`.or-repeat[name="${repeatPath}"] ${filter}[${attr}]`]; + } else { + selector = [`${filter}[${attr}]`]; + } } else { - updated.nodes.forEach( node => { - selector = selector.concat( this.getQuerySelectorsForLogic( filter, attr, node ) ); - } ); + updated.nodes.forEach((node) => { + selector = selector.concat( + this.getQuerySelectorsForLogic(filter, attr, node) + ); + }); // add all the paths that use the /* selector at end of path - selector = selector.concat( this.getQuerySelectorsForLogic( filter, attr, '*' ) ); + selector = selector.concat( + this.getQuerySelectorsForLogic(filter, attr, '*') + ); } - const selectorStr = selector.join( ', ' ); - collection = selectorStr ? collection.filter( el => el.matches( selectorStr ) ) : collection; + const selectorStr = selector.join(', '); + collection = selectorStr + ? collection.filter((el) => el.matches(selectorStr)) + : collection; // TODO: exclude descendents of disabled elements? .find( ':not(:disabled) span.active' ) // TODO: remove jQuery wrapper, just return array of elements - return $( collection ); + return $(collection); }; /** * @param {Array<Element>} controls - radiobutton/checkbox HTML input elements * @return {Array<Element>} filtered controls without any sibling radiobuttons and checkboxes (only the first) */ -Form.prototype.filterRadioCheckSiblings = controls => { +Form.prototype.filterRadioCheckSiblings = (controls) => { const wrappers = []; - return controls.filter( control => { + return controls.filter((control) => { // TODO: can this be further performance-optimized? - const wrapper = control.type === 'radio' || control.type === 'checkbox' ? closestAncestorUntil( control, '.option-wrapper', '.question' ) : null; + const wrapper = + control.type === 'radio' || control.type === 'checkbox' + ? closestAncestorUntil(control, '.option-wrapper', '.question') + : null; // Filter out duplicate radiobuttons and checkboxes - if ( wrapper ) { - if ( wrappers.includes( wrapper ) ) { + if (wrapper) { + if (wrappers.includes(wrapper)) { return false; } - wrappers.push( wrapper ); + wrappers.push(wrapper); } return true; - } ); + }); }; /** @@ -672,7 +792,7 @@

js/form.js

* @param {string} nodeName - The XML nodeName to find * @return {string} The selector */ -Form.prototype.getQuerySelectorsForLogic = ( filter, attr, nodeName ) => [ +Form.prototype.getQuerySelectorsForLogic = (filter, attr, nodeName) => [ // The target node name is ALWAYS at the END of a path inside the expression. // #1: followed by space `${filter}[${attr}*="/${nodeName} "]`, @@ -685,7 +805,7 @@

js/form.js

// #5: followed by ] (used in itemset filters) `${filter}[${attr}*="/${nodeName}]"]`, // #6: followed by [ (used when filtering nodes in repeat instances) - `${filter}[${attr}*="/${nodeName}["]` + `${filter}[${attr}*="/${nodeName}["]`, ]; /** @@ -698,47 +818,55 @@

js/form.js

* * @return {string} Data string */ -Form.prototype.getDataStrWithoutIrrelevantNodes = function() { +Form.prototype.getDataStrWithoutIrrelevantNodes = function () { const that = this; - const modelClone = new FormModel( this.model.getStr() ); + const modelClone = new FormModel(this.model.getStr()); modelClone.init(); // Since we are removing nodes, we need to go in reverse order to make sure // the indices are still correct! - this.getRelatedNodes( 'data-relevant' ).reverse().each( function() { - const node = this; - const relevant = that.input.getRelevant( node ); - const index = that.input.getIndex( node ); - const path = that.input.getName( node ); - let target; - - /* - * Copied from relevant.js: - * - * If the relevant is placed on a group and that group contains repeats with the same name, - * but currently has 0 repeats, the context will not be available. - */ - if ( getChild( node, `.or-repeat-info[data-name="${path}"]` ) && !getChild( node, `.or-repeat[name="${path}"]` ) ) { - target = null; - } else { - // If a calculation without a form control (i.e. in .calculated-items) inside a repeat - // has a relevant, and there 0 instances of that repeat, - // there is nothing to remove (and target is undefined) - // https://github.com/enketo/enketo-core/issues/761 - // TODO: It would be so much nicer if form-control-less calculations were placed inside the repeat instead. - target = modelClone.node( path, index ).getElement(); - } + this.getRelatedNodes('data-relevant') + .reverse() + .each(function () { + const node = this; + const relevant = that.input.getRelevant(node); + const index = that.input.getIndex(node); + const path = that.input.getName(node); + let target; - /* - * If performance becomes an issue, some opportunities are: - * - check if ancestor is relevant - * - use cache of relevant.update - * - check for repeatClones to avoid calculating index (as in relevant.update) - */ - if ( target && !that.model.evaluate( relevant, 'boolean',path, index ) ) { - target.remove(); - } - } ); + /* + * Copied from relevant.js: + * + * If the relevant is placed on a group and that group contains repeats with the same name, + * but currently has 0 repeats, the context will not be available. + */ + if ( + getChild(node, `.or-repeat-info[data-name="${path}"]`) && + !getChild(node, `.or-repeat[name="${path}"]`) + ) { + target = null; + } else { + // If a calculation without a form control (i.e. in .calculated-items) inside a repeat + // has a relevant, and there 0 instances of that repeat, + // there is nothing to remove (and target is undefined) + // https://github.com/enketo/enketo-core/issues/761 + // TODO: It would be so much nicer if form-control-less calculations were placed inside the repeat instead. + target = modelClone.node(path, index).getElement(); + } + + /* + * If performance becomes an issue, some opportunities are: + * - check if ancestor is relevant + * - use cache of relevant.update + * - check for repeatClones to avoid calculating index (as in relevant.update) + */ + if ( + target && + !that.model.evaluate(relevant, 'boolean', path, index) + ) { + target.remove(); + } + }); return modelClone.getStr(); }; @@ -757,12 +885,15 @@

js/form.js

* 3. Formhub has re-generated all stored XML forms from the stored XLS forms with the updated pyxforms * */ -Form.prototype.grosslyViolateStandardComplianceByIgnoringCertainCalcs = function() { - const $culprit = this.view.$.find( '[name$="instanceID"][data-calculate]' ); - if ( $culprit.length > 0 ) { - $culprit.removeAttr( 'data-calculate' ); - } -}; +Form.prototype.grosslyViolateStandardComplianceByIgnoringCertainCalcs = + function () { + const $culprit = this.view.$.find( + '[name$="instanceID"][data-calculate]' + ); + if ($culprit.length > 0) { + $culprit.removeAttr('data-calculate'); + } + }; /** * This re-validates questions that have a dependency on a question that has just been updated. @@ -771,10 +902,10 @@

js/form.js

* * @param {UpdatedDataNodes} updated - object that contains information on updated nodes */ -Form.prototype.validationUpdate = function( updated = {} ) { - if ( config.validateContinuously === true ) { +Form.prototype.validationUpdate = function (updated = {}) { + if (config.validateContinuously === true) { let upd = { ...updated }; - if ( updated.cloned ) { + if (updated.cloned) { /* * We don't want requireds and constraints of questions in a newly created * repeat to be evaluated immediately after the repeat is created. @@ -784,26 +915,30 @@

js/form.js

* to a regular "node" updated object. */ upd = { - nodes: updated.repeatPath.split( '/' ).reverse().slice( 0, 1 ) + nodes: updated.repeatPath.split('/').reverse().slice(0, 1), }; } // Find all inputs that have a dependency on the changed node. Avoid duplicates with Set. - const nodes = new Set( this.getRelatedNodes( 'data-required', '', upd ).get() ); - this.constraintAttributes.forEach( attr => this.getRelatedNodes( attr, '', upd ).get().forEach( nodes.add, nodes ) ); - - nodes.forEach( this.validateInput, this ); + const nodes = new Set( + this.getRelatedNodes('data-required', '', upd).get() + ); + this.constraintAttributes.forEach((attr) => + this.getRelatedNodes(attr, '', upd).get().forEach(nodes.add, nodes) + ); + + nodes.forEach(this.validateInput, this); } }; /** * A big function that sets event handlers. */ -Form.prototype.setEventHandlers = function() { +Form.prototype.setEventHandlers = function () { const that = this; // Prevent default submission, e.g. when text field is filled in and Enter key is pressed - this.view.$.attr( 'onsubmit', 'return false;' ); + this.view.$.attr('onsubmit', 'return false;'); /* * The listener below catches both change and change.file events. @@ -817,83 +952,102 @@

js/form.js

* 2. readonly field becomes non-relevant (e.g. parent group with relevant) * 3. this clears value in view, which should propagate to model via 'change' event */ - this.view.$.on( 'change.file', + this.view.$.on( + 'change.file', 'input:not(.ignore), select:not(.ignore), textarea:not(.ignore)', - function() { + function () { const input = this; const n = { - path: that.input.getName( input ), - inputType: that.input.getInputType( input ), - xmlType: that.input.getXmlType( input ), - val: that.input.getVal( input ), - index: that.input.getIndex( input ) + path: that.input.getName(input), + inputType: that.input.getInputType(input), + xmlType: that.input.getXmlType(input), + val: that.input.getVal(input), + index: that.input.getIndex(input), }; // set file input values to the uniqified actual name of file (without c://fakepath or anything like that) - if ( n.val.length > 0 && n.inputType === 'file' && input.files[ 0 ] && input.files[ 0 ].size > 0 ) { - n.val = getFilename( input.files[ 0 ], input.dataset.filenamePostfix ); + if ( + n.val.length > 0 && + n.inputType === 'file' && + input.files[0] && + input.files[0].size > 0 + ) { + n.val = getFilename( + input.files[0], + input.dataset.filenamePostfix + ); } - if ( n.val.length > 0 && n.inputType === 'drawing' ) { - n.val = getFilename( { - name: n.val - }, input.dataset.filenamePostfix ); + if (n.val.length > 0 && n.inputType === 'drawing') { + n.val = getFilename( + { + name: n.val, + }, + input.dataset.filenamePostfix + ); } - const updated = that.model.node( n.path, n.index ).setVal( n.val, n.xmlType ); - - if ( updated ) { - that.validateInput( input ) - .then( () => { - // after internal processing is completed - input.dispatchEvent( events.XFormsValueChanged( { repeatIndex: n.index } ) ); - } ); + const updated = that.model + .node(n.path, n.index) + .setVal(n.val, n.xmlType); + + if (updated) { + that.validateInput(input).then(() => { + // after internal processing is completed + input.dispatchEvent( + events.XFormsValueChanged({ repeatIndex: n.index }) + ); + }); } - } ); + } + ); // doing this on the focus event may have little effect on performance, because nothing else is happening :) - this.view.html.addEventListener( 'focusin', event => { + this.view.html.addEventListener('focusin', (event) => { // update the form progress status - this.progress.update( event.target ); - } ); + this.progress.update(event.target); + }); - this.view.html.addEventListener( events.FakeFocus().type, event => { + this.view.html.addEventListener(events.FakeFocus().type, (event) => { // update the form progress status - this.progress.update( event.target ); - } ); + this.progress.update(event.target); + }); - this.model.events.addEventListener( events.DataUpdate().type, event => { - that.evaluationCascade.forEach( fn => { - fn.call( that, event.detail ); - }, true ); + this.model.events.addEventListener(events.DataUpdate().type, (event) => { + that.evaluationCascade.forEach((fn) => { + fn.call(that, event.detail); + }, true); // edit is fired when the model changes after the form has been initialized that.editStatus = true; - } ); + }); - this.view.html.addEventListener( events.AddRepeat().type, event => { - const $clone = $( event.target ); + this.view.html.addEventListener(events.AddRepeat().type, (event) => { + const $clone = $(event.target); // Set template-defined static defaults of added repeats in Form, setAllVals does not trigger change event - this.setAllVals( $clone, event.detail.repeatIndex ); + this.setAllVals($clone, event.detail.repeatIndex); // Initialize calculations, relevant, itemset, required, output inside that repeat. - this.evaluationCascade.forEach( fn => { - fn.call( that, event.detail ); - } ); + this.evaluationCascade.forEach((fn) => { + fn.call(that, event.detail); + }); this.progress.update(); - } ); + }); - this.view.html.addEventListener( events.RemoveRepeat().type, () => { + this.view.html.addEventListener(events.RemoveRepeat().type, () => { this.progress.update(); - } ); + }); - this.view.html.addEventListener( events.ChangeLanguage().type, () => { + this.view.html.addEventListener(events.ChangeLanguage().type, () => { this.output.update(); - } ); + }); - this.view.$.find( '.or-group > h4' ).on( 'click', function() { + this.view.$.find('.or-group > h4').on('click', function () { // The resize trigger is to make sure canvas widgets start working. - $( this ).closest( '.or-group' ).toggleClass( 'or-appearance-compact' ).trigger( 'resize' ); - } ); + $(this) + .closest('.or-group') + .toggleClass('or-appearance-compact') + .trigger('resize'); + }); }; /** @@ -902,16 +1056,18 @@

js/form.js

* @param {Element} control - form control HTML element * @param {string} [type] - One of "constraint", "required" and "relevant". */ -Form.prototype.setValid = function( control, type ) { - const wrap = this.input.getWrapNode( control ); +Form.prototype.setValid = function (control, type) { + const wrap = this.input.getWrapNode(control); - if ( !wrap ){ + if (!wrap) { // TODO: this condition occurs, at least in tests for itemsets, but we need find out how. return; } - const classes = type ? [ `invalid-${type}` ] : [ ...wrap.classList ].filter( cl => cl.indexOf( 'invalid-' ) === 0 ); - wrap.classList.remove( ...classes ); + const classes = type + ? [`invalid-${type}`] + : [...wrap.classList].filter((cl) => cl.indexOf('invalid-') === 0); + wrap.classList.remove(...classes); }; /** @@ -920,19 +1076,19 @@

js/form.js

* @param {Element} control - form control HTML element * @param {string} [type] - One of "constraint", "required" and "relevant". */ -Form.prototype.setInvalid = function( control, type = 'constraint' ) { - const wrap = this.input.getWrapNode( control ); +Form.prototype.setInvalid = function (control, type = 'constraint') { + const wrap = this.input.getWrapNode(control); - if ( !wrap ){ + if (!wrap) { // TODO: this condition occurs, at least in tests for itemsets, but we need find out how. return; } - if ( config.validatePage === false && this.isValid( control ) ) { + if (config.validatePage === false && this.isValid(control)) { this.blockPageNavigation(); } - wrap.classList.add( `invalid-${type}` ); + wrap.classList.add(`invalid-${type}`); }; /** @@ -940,23 +1096,24 @@

js/form.js

* @param {*} control - form control HTML element * @param {*} result - result object obtained from Nodeset.validate */ -Form.prototype.updateValidityInUi = function( control, result ){ - const passed = result.requiredValid !== false && result.constraintValid !== false; +Form.prototype.updateValidityInUi = function (control, result) { + const passed = + result.requiredValid !== false && result.constraintValid !== false; // Update UI - if ( result.requiredValid === false ) { - this.setValid( control, 'constraint' ); - this.setInvalid( control, 'required' ); - } else if ( result.constraintValid === false ) { - this.setValid( control, 'required' ); - this.setInvalid( control, 'constraint' ); + if (result.requiredValid === false) { + this.setValid(control, 'constraint'); + this.setInvalid(control, 'required'); + } else if (result.constraintValid === false) { + this.setValid(control, 'required'); + this.setInvalid(control, 'constraint'); } else { - this.setValid( control, 'constraint' ); - this.setValid( control, 'required' ); + this.setValid(control, 'constraint'); + this.setValid(control, 'required'); } - if ( !passed ){ - control.dispatchEvent( events.Invalidated() ); + if (!passed) { + control.dispatchEvent(events.Invalidated()); } }; @@ -964,13 +1121,13 @@

js/form.js

* Blocks page navigation for a short period. * This can be used to ensure that the user sees a new error message before moving to another page. */ -Form.prototype.blockPageNavigation = function() { +Form.prototype.blockPageNavigation = function () { const that = this; this.pageNavigationBlocked = true; - window.clearTimeout( this.blockPageNavigationTimeout ); - this.blockPageNavigationTimeout = window.setTimeout( () => { + window.clearTimeout(this.blockPageNavigationTimeout); + this.blockPageNavigationTimeout = window.setTimeout(() => { that.pageNavigationBlocked = false; - }, 600 ); + }, 600); }; /** @@ -979,23 +1136,25 @@

js/form.js

* @param {Element} node - form control HTML element * @return {!boolean} Whether the question/form is not marked as invalid. */ -Form.prototype.isValid = function( node ) { - const invalidSelectors = [ '.invalid-required', '.invalid-relevant' ].concat( this.constraintClassesInvalid.map( cls => `.${cls}` ) ); - if ( node ) { - const question = this.input.getWrapNode( node ); +Form.prototype.isValid = function (node) { + const invalidSelectors = ['.invalid-required', '.invalid-relevant'].concat( + this.constraintClassesInvalid.map((cls) => `.${cls}`) + ); + if (node) { + const question = this.input.getWrapNode(node); const cls = question.classList; - return !invalidSelectors.some( selector => cls.contains( selector ) ); + return !invalidSelectors.some((selector) => cls.contains(selector)); } - return !this.view.html.querySelector( invalidSelectors.join( ', ' ) ); + return !this.view.html.querySelector(invalidSelectors.join(', ')); }; /** * Clears non-relevant values. */ -Form.prototype.clearNonRelevant = function() { - this.relevant.update( null, true ); +Form.prototype.clearNonRelevant = function () { + this.relevant.update(null, true); }; /** @@ -1004,17 +1163,16 @@

js/form.js

* * @return {Promise} wrapping {boolean} whether the form contains any errors */ -Form.prototype.validateAll = function() { +Form.prototype.validateAll = function () { const that = this; // to not delay validation unnecessarily we only clear non-relevants if necessary this.clearNonRelevant(); - return this.validateContent( this.view.$ ) - .then( valid => { - that.view.html.dispatchEvent( events.ValidationComplete() ); + return this.validateContent(this.view.$).then((valid) => { + that.view.html.dispatchEvent(events.ValidationComplete()); - return valid; - } ); + return valid; + }); }; /** @@ -1030,41 +1188,57 @@

js/form.js

* @param {jQuery} $container - HTML container element inside which to validate form controls * @return {Promise} wrapping {boolean} whether the container contains any errors */ -Form.prototype.validateContent = function( $container ) { +Form.prototype.validateContent = function ($container) { const that = this; - const invalidSelector = [ '.invalid-required', '.invalid-relevant' ].concat( this.constraintClassesInvalid.map( cls => `.${cls}` ) ).join( ', ' ); - - //can't fire custom events on disabled elements therefore we set them all as valid - $container.find( 'fieldset:disabled input, fieldset:disabled select, fieldset:disabled textarea, ' + - 'input:disabled, select:disabled, textarea:disabled' ).each( function() { - that.setValid( this ); - } ); - - const validations = $container.find( '.question' ).addBack( '.question' ).map( function() { - // only trigger validate on first input and use a **pure CSS** selector (huge performance impact) - const elem = this - .querySelector( 'input:not(.ignore):not(:disabled), select:not(.ignore):not(:disabled), textarea:not(.ignore):not(:disabled)' ); - if ( !elem ) { - return Promise.resolve(); - } + const invalidSelector = ['.invalid-required', '.invalid-relevant'] + .concat(this.constraintClassesInvalid.map((cls) => `.${cls}`)) + .join(', '); + + // can't fire custom events on disabled elements therefore we set them all as valid + $container + .find( + 'fieldset:disabled input, fieldset:disabled select, fieldset:disabled textarea, ' + + 'input:disabled, select:disabled, textarea:disabled' + ) + .each(function () { + that.setValid(this); + }); + + const validations = $container + .find('.question') + .addBack('.question') + .map(function () { + // only trigger validate on first input and use a **pure CSS** selector (huge performance impact) + const elem = this.querySelector( + 'input:not(.ignore):not(:disabled), select:not(.ignore):not(:disabled), textarea:not(.ignore):not(:disabled)' + ); + if (!elem) { + return Promise.resolve(); + } - return that.validateInput( elem ); - } ).toArray(); + return that.validateInput(elem); + }) + .toArray(); - return Promise.all( validations ) - .then( () => { - const container = $container[ 0 ]; - const firstError = container.matches( invalidSelector ) ? container : container.querySelector( invalidSelector ); + return Promise.all(validations) + .then(() => { + const container = $container[0]; + const firstError = container.matches(invalidSelector) + ? container + : container.querySelector(invalidSelector); - if ( firstError ) { - that.goToTarget( firstError ); + if (firstError) { + that.goToTarget(firstError); } return !firstError; - } ) - .catch( () => // fail whole-form validation if any of the question - // validations threw. - false ); + }) + .catch( + () => + // fail whole-form validation if any of the question + // validations threw. + false + ); }; /** @@ -1072,17 +1246,17 @@

js/form.js

* @param {string} contextPath - absolute context path * @return {string} absolute path */ -Form.prototype.pathToAbsolute = function( targetPath, contextPath ) { +Form.prototype.pathToAbsolute = function (targetPath, contextPath) { let target; - if ( targetPath.indexOf( '/' ) === 0 ) { + if (targetPath.indexOf('/') === 0) { return targetPath; } // index is non-relevant (no positions in returned path) - target = this.model.evaluate( targetPath, 'node', contextPath, 0, true ); + target = this.model.evaluate(targetPath, 'node', contextPath, 0, true); - return getXPath( target, 'instance', false ); + return getXPath(target, 'instance', false); }; /** @@ -1097,8 +1271,8 @@

js/form.js

* @param {Element} control - form control HTML element * @return {Promise<undefined|ValidateInputResolution>} resolves with validation result */ -Form.prototype.validateInput = function( control ) { - if ( !this.initialized ) { +Form.prototype.validateInput = function (control) { + if (!this.initialized) { return Promise.resolve(); } const that = this; @@ -1107,54 +1281,62 @@

js/form.js

// There is some scope for performance improvement by determining other properties when they // are needed, but that may not be so significant. const n = { - path: this.input.getName( control ), - inputType: this.input.getInputType( control ), - xmlType: this.input.getXmlType( control ), - enabled: this.input.isEnabled( control ), - constraint: this.input.getConstraint( control ), - calculation: this.input.getCalculation( control ), - required: this.input.getRequired( control ), - readonly: this.input.getReadonly( control ), - val: this.input.getVal( control ) + path: this.input.getName(control), + inputType: this.input.getInputType(control), + xmlType: this.input.getXmlType(control), + enabled: this.input.isEnabled(control), + constraint: this.input.getConstraint(control), + calculation: this.input.getCalculation(control), + required: this.input.getRequired(control), + readonly: this.input.getReadonly(control), + val: this.input.getVal(control), }; // No need to validate, **nor send validation events**. Meant for simple empty "notes" only. - if ( n.readonly && !n.val && !n.required && !n.constraint && !n.calculation ) { + if ( + n.readonly && + !n.val && + !n.required && + !n.constraint && + !n.calculation + ) { return Promise.resolve(); } // The enabled check serves a purpose only when an input field itself is marked as enabled but its parent fieldset is not. // If an element is disabled mark it as valid (to undo a previously shown branch with fields marked as invalid). - if ( n.enabled && n.inputType !== 'hidden' ) { + if (n.enabled && n.inputType !== 'hidden') { // Only now, will we determine the index. - n.ind = this.input.getIndex( control ); - getValidationResult = this.model.node( n.path, n.ind ).validate( n.constraint, n.required, n.xmlType ); + n.ind = this.input.getIndex(control); + getValidationResult = this.model + .node(n.path, n.ind) + .validate(n.constraint, n.required, n.xmlType); } else { - getValidationResult = Promise.resolve( { + getValidationResult = Promise.resolve({ requiredValid: true, - constraintValid: true - } ); + constraintValid: true, + }); } return getValidationResult - .then( result => { - if ( n.inputType !== 'hidden' ) { - this.updateValidityInUi( control, result ); + .then((result) => { + if (n.inputType !== 'hidden') { + this.updateValidityInUi(control, result); } return result; - } ) - .catch( e => { - console.error( 'validation error', e ); - that.setInvalid( control, 'constraint' ); + }) + .catch((e) => { + console.error('validation error', e); + that.setInvalid(control, 'constraint'); throw e; - } ); + }); }; /** * @param {string} path - path to HTML form control * @return {null|Element} HTML question element */ -Form.prototype.getGoToTarget = function( path ) { +Form.prototype.getGoToTarget = function (path) { let hits; let modelNode; let target; @@ -1162,39 +1344,41 @@

js/form.js

let selector = ''; const repeatRegEx = /([^[]+)\[(\d+)\]([^[]*$)?/g; - if ( !path ) { + if (!path) { return; } - modelNode = this.model.node( path ).getElement(); + modelNode = this.model.node(path).getElement(); - if ( !modelNode ) { + if (!modelNode) { return; } // Convert to absolute path, while maintaining positions. - path = getXPath( modelNode, 'instance', true ); + path = getXPath(modelNode, 'instance', true); // Not inside a cloned repeat. - target = this.view.html.querySelector( `[name="${path}"]` ); + target = this.view.html.querySelector(`[name="${path}"]`); // If inside a cloned repeat (i.e. a repeat that is not first-in-series) - if ( !target ) { + if (!target) { intermediateTarget = this.view.html; - while ( ( hits = repeatRegEx.exec( path ) ) !== null && intermediateTarget ) { - selector += hits[ 1 ]; - intermediateTarget = intermediateTarget - .querySelectorAll( `[name="${selector}"], [data-name="${selector}"]` )[ hits[ 2 ] ]; - if ( intermediateTarget && hits[ 3 ] ) { - selector += hits[ 3 ]; - intermediateTarget = intermediateTarget - .querySelector( `[name="${selector}"],[data-name="${selector}"]` ); + while ((hits = repeatRegEx.exec(path)) !== null && intermediateTarget) { + selector += hits[1]; + intermediateTarget = intermediateTarget.querySelectorAll( + `[name="${selector}"], [data-name="${selector}"]` + )[hits[2]]; + if (intermediateTarget && hits[3]) { + selector += hits[3]; + intermediateTarget = intermediateTarget.querySelector( + `[name="${selector}"],[data-name="${selector}"]` + ); } target = intermediateTarget; } } - return target ? this.input.getWrapNode( target ) : target; + return target ? this.input.getWrapNode(target) : target; }; /** @@ -1203,30 +1387,32 @@

js/form.js

* @param {HTMLElement} target - An HTML question or group element to scroll to * @return {boolean} whether target found */ -Form.prototype.goToTarget = function( target ) { - if ( target ) { - if ( this.pages.active ) { +Form.prototype.goToTarget = function (target) { + if (target) { + if (this.pages.active) { // Flip to page - this.pages.flipToPageContaining( $( target ) ); + this.pages.flipToPageContaining($(target)); } // check if the target has a form control - if ( target.closest( '.calculation, .setvalue, .setgeopoint' ) ) { + if (target.closest('.calculation, .setvalue, .setgeopoint')) { // It is up to the apps to decide what to do with this event. - target.dispatchEvent( events.GoToInvisible() ); + target.dispatchEvent(events.GoToInvisible()); } // check if the nearest question or group is irrelevant after page flip - if ( target.closest( '.or-branch.disabled' ) ) { + if (target.closest('.or-branch.disabled')) { // It is up to the apps to decide what to do with this event. - target.dispatchEvent( events.GoToIrrelevant() ); + target.dispatchEvent(events.GoToIrrelevant()); } // Scroll to element target.scrollIntoView(); // Focus on the first non .ignore form control // If the element is hidden (e.g. because it's been replaced by a widget), // the focus event will not fire, so we also trigger an applyfocus event that widgets can listen for. - const input = target.querySelector( 'input:not(.ignore), textarea:not(.ignore), select:not(.ignore)' ); + const input = target.querySelector( + 'input:not(.ignore), textarea:not(.ignore), select:not(.ignore)' + ); input.focus(); - input.dispatchEvent( events.ApplyFocus() ); + input.dispatchEvent(events.ApplyFocus()); } return !!target; diff --git a/docs/js_format.js.html b/docs/js_format.js.html index c61e4c4a3..1608a1449 100644 --- a/docs/js_format.js.html +++ b/docs/js_format.js.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -54,7 +54,9 @@

js/format.js

const NUMBER = '0-9\u0660-\u0669'; const TIME_PART = `[:${NUMBER}]+`; const MERIDIAN_PART = `[^: ${NUMBER}]+`; -const HAS_MERIDIAN = new RegExp( `^(${TIME_PART} ?(${MERIDIAN_PART}))|((${MERIDIAN_PART}) ?${TIME_PART})$` ); +const HAS_MERIDIAN = new RegExp( + `^(${TIME_PART} ?(${MERIDIAN_PART}))|((${MERIDIAN_PART}) ?${TIME_PART})$` +); /** * Transforms time to a cleaned-up localized time. @@ -62,10 +64,10 @@

js/format.js

* @param {Date} dt - date object * @return {string} cleaned-up localized time */ -function _getCleanLocalTime( dt ) { - dt = typeof dt == 'undefined' ? new Date() : dt; +function _getCleanLocalTime(dt) { + dt = typeof dt === 'undefined' ? new Date() : dt; - return _cleanSpecialChars( dt.toLocaleTimeString( format.locale ) ); + return _cleanSpecialChars(dt.toLocaleTimeString(format.locale)); } /** @@ -74,8 +76,8 @@

js/format.js

* @param {string} timeStr - (date)time string to clean up * @return {string} transformed (date)time string with removed unneeded special characters that cause issues */ -function _cleanSpecialChars( timeStr ) { - return timeStr.replace( /[\u200E\u200F]/g, '' ); +function _cleanSpecialChars(timeStr) { + return timeStr.replace(/[\u200E\u200F]/g, ''); } /** @@ -87,30 +89,30 @@

js/format.js

* @type {string} */ get hour12() { - return this.hasMeridian( _getCleanLocalTime() ); + return this.hasMeridian(_getCleanLocalTime()); }, /** * @type {string} */ get pmNotation() { - return this.meridianNotation( new Date( 2000, 1, 1, 23, 0, 0 ) ); + return this.meridianNotation(new Date(2000, 1, 1, 23, 0, 0)); }, /** * @type {string} */ get amNotation() { - return this.meridianNotation( new Date( 2000, 1, 1, 1, 0, 0 ) ); + return this.meridianNotation(new Date(2000, 1, 1, 1, 0, 0)); }, /** * @type {Function} * @param {Date} dt - datetime string */ - meridianNotation( dt ) { - let matches = _getCleanLocalTime( dt ).match( HAS_MERIDIAN ); - if ( matches && matches.length ) { - matches = matches.filter( item => !!item ); + meridianNotation(dt) { + let matches = _getCleanLocalTime(dt).match(HAS_MERIDIAN); + if (matches && matches.length) { + matches = matches.filter((item) => !!item); - return matches[ matches.length - 1 ].trim(); + return matches[matches.length - 1].trim(); } return null; @@ -121,9 +123,9 @@

js/format.js

* @type {Function} * @param {string} time - Time string */ - hasMeridian( time ) { - return HAS_MERIDIAN.test( _cleanSpecialChars( time ) ); - } + hasMeridian(time) { + return HAS_MERIDIAN.test(_cleanSpecialChars(time)); + }, }; /** diff --git a/docs/js_geolocation.js.html b/docs/js_geolocation.js.html index 74b190f43..4ba909951 100644 --- a/docs/js_geolocation.js.html +++ b/docs/js_geolocation.js.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -59,25 +59,31 @@

js/geolocation.js

* @param {window.PositionOptions} [options] - lookup options * @return {Promise<GeolocationPosition>} - coordinates */ -export const getCurrentPosition = ( options ) => { - return new Promise( ( resolve, reject ) => { - navigator.geolocation.getCurrentPosition( ( position ) => { - const { latitude, longitude, altitude, accuracy } = position.coords; - - const lat = Math.round( latitude * 1000000 ) / 1000000; - const lng = Math.round( longitude * 1000000 ) / 1000000; - - const geopoint = `${lat} ${lng} ${altitude || '0.0'} ${accuracy || '0.0'}`; - - resolve( { - geopoint, - lat, - lng, - position, - } ); - }, reject, options ); - } ); -}; +export const getCurrentPosition = (options) => + new Promise((resolve, reject) => { + navigator.geolocation.getCurrentPosition( + (position) => { + const { latitude, longitude, altitude, accuracy } = + position.coords; + + const lat = Math.round(latitude * 1000000) / 1000000; + const lng = Math.round(longitude * 1000000) / 1000000; + + const geopoint = `${lat} ${lng} ${altitude || '0.0'} ${ + accuracy || '0.0' + }`; + + resolve({ + geopoint, + lat, + lng, + position, + }); + }, + reject, + options + ); + });
diff --git a/docs/js_input.js.html b/docs/js_input.js.html index 30feaa389..946dd4f60 100644 --- a/docs/js_input.js.html +++ b/docs/js_input.js.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -53,7 +53,10 @@

js/input.js

* @module input */ -import { getTimezoneOffsetAsTime, toISOLocalString } from 'openrosa-xpath-evaluator/src/date-extensions'; +import { + getTimezoneOffsetAsTime, + toISOLocalString, +} from 'openrosa-xpath-evaluator/src/date-extensions'; import types from './types'; import events from './event'; import { closestAncestorUntil } from './dom-utils'; @@ -63,21 +66,23 @@

js/input.js

* @param {Element} control - form control HTML element * @return {Element} Wrap node */ - getWrapNode( control ) { - return control.closest( '.question, .calculation, .setvalue, .setgeopoint' ); + getWrapNode(control) { + return control.closest( + '.question, .calculation, .setvalue, .setgeopoint' + ); }, /** * @param {Array<Element>} controls - form controls HTML elements * @return {Array<Element>} Wrap nodes */ - getWrapNodes( controls ) { + getWrapNodes(controls) { const result = []; - controls.forEach( control => { - const question = this.getWrapNode( control ); - if ( !result.includes( question ) ) { - result.push( question ); + controls.forEach((control) => { + const question = this.getWrapNode(control); + if (!result.includes(question)) { + result.push(question); } - } ); + }); return result; }, @@ -85,70 +90,77 @@

js/input.js

* @param {Element} control - form control HTML element * @return {object} control element properties */ - getProps( control ) { + getProps(control) { return { - path: this.getName( control ), - ind: this.getIndex( control ), - inputType: this.getInputType( control ), - xmlType: this.getXmlType( control ), - constraint: this.getConstraint( control ), - calculation: this.getCalculation( control ), - relevant: this.getRelevant( control ), - readonly: this.getReadonly( control ), - val: this.getVal( control ), - required: this.getRequired( control ), - enabled: this.isEnabled( control ), - multiple: this.isMultiple( control ) + path: this.getName(control), + ind: this.getIndex(control), + inputType: this.getInputType(control), + xmlType: this.getXmlType(control), + constraint: this.getConstraint(control), + calculation: this.getCalculation(control), + relevant: this.getRelevant(control), + readonly: this.getReadonly(control), + val: this.getVal(control), + required: this.getRequired(control), + enabled: this.isEnabled(control), + multiple: this.isMultiple(control), }; }, /** * @param {Element} control - form control HTML element * @return {string} input type */ - getInputType( control ) { + getInputType(control) { const nodeName = control.nodeName.toLowerCase(); - if ( nodeName === 'input' ) { - if ( control.dataset.drawing ) { + if (nodeName === 'input') { + if (control.dataset.drawing) { return 'drawing'; } - if ( control.type ) { - if ( control.type === 'text' && this.getXmlType( control ) === 'date' ) { + if (control.type) { + if ( + control.type === 'text' && + this.getXmlType(control) === 'date' + ) { // for browsers that don't support type='date' and return 'text' (e.g. Safari Desktop) return 'date'; - } else if ( control.type === 'text' && this.getXmlType( control ) === 'datetime' ) { + } + if ( + control.type === 'text' && + this.getXmlType(control) === 'datetime' + ) { // for browsers that don't support type='datetime-local' and return 'text' (e.g. Safari and Firefox Desktop) return 'datetime-local'; - } else { - return control.type.toLowerCase(); } + return control.type.toLowerCase(); } - return console.error( '<input> node has no type' ); - - } else if ( nodeName === 'select' ) { + return console.error('<input> node has no type'); + } + if (nodeName === 'select') { return 'select'; - } else if ( nodeName === 'textarea' ) { + } + if (nodeName === 'textarea') { return 'textarea'; - } else if ( nodeName === 'fieldset' || nodeName === 'section' ) { + } + if (nodeName === 'fieldset' || nodeName === 'section') { return 'fieldset'; - } else { - return console.error( 'unexpected input node type provided' ); } + return console.error('unexpected input node type provided'); }, /** * @param {Element} control - form control HTML element * @return {string} constraint expression */ - getConstraint( control ) { + getConstraint(control) { return control.dataset.constraint; }, /** * @param {Element} control - form control HTML element * @return {string|undefined} required expression */ - getRequired( control ) { + getRequired(control) { // only return value if input is not a table heading input - if ( !closestAncestorUntil( control, '.or-appearance-label', '.or' ) ) { + if (!closestAncestorUntil(control, '.or-appearance-label', '.or')) { return control.dataset.required; } }, @@ -156,38 +168,38 @@

js/input.js

* @param {Element} control - form control HTML element * @return {string} relevant expression */ - getRelevant( control ) { + getRelevant(control) { return control.dataset.relevant; }, /** * @param {Element} control - form control HTML element * @return {boolean} whether element is read only */ - getReadonly( control ) { - return control.matches( '[readonly]' ); + getReadonly(control) { + return control.matches('[readonly]'); }, /** * @param {Element} control - form control HTML element * @return {string} calculate expression */ - getCalculation( control ) { + getCalculation(control) { return control.dataset.calculate; }, /** * @param {Element} control - form control HTML element * @return {string} XML type */ - getXmlType( control ) { - return ( control.dataset.typeXml || 'string' ).toLowerCase(); + getXmlType(control) { + return (control.dataset.typeXml || 'string').toLowerCase(); }, /** * @param {Element} control - form control HTML element * @return {string} name */ - getName( control ) { - const name = control.dataset.name || control.getAttribute( 'name' ); - if ( !name ) { - console.error( 'input node has no name' ); + getName(control) { + const name = control.dataset.name || control.getAttribute('name'); + if (!name) { + console.error('input node has no name'); } return name; @@ -196,58 +208,72 @@

js/input.js

* @param {Element} control - form control HTML element * @return {number} - the repeat index of the form control */ - getIndex( control ) { - return this.form.repeats.getIndex( control.closest( '.or-repeat' ) ); + getIndex(control) { + return this.form.repeats.getIndex(control.closest('.or-repeat')); }, /** * @param {Element} control - form control HTML element * @return {boolean} whether element is multiple */ - isMultiple( control ) { - return this.getInputType( control ) === 'checkbox' || control.multiple; + isMultiple(control) { + return this.getInputType(control) === 'checkbox' || control.multiple; }, /** * @param {Element} control - form control HTML element * @return {boolean} whether element is enabled */ - isEnabled( control ) { - return !( control.disabled || closestAncestorUntil( control, '.disabled', '.or' ) ); + isEnabled(control) { + return !( + control.disabled || + closestAncestorUntil(control, '.disabled', '.or') + ); }, /** * @param {Element} control - form control HTML element * @return {string} element value */ - getVal( control ) { + getVal(control) { let value = ''; - const inputType = this.getInputType( control ); - const name = this.getName( control ); + const inputType = this.getInputType(control); + const name = this.getName(control); - switch ( inputType ) { + switch (inputType) { case 'radio': { - const checked = this.getWrapNode( control ).querySelector( `input[type="radio"][data-name="${name}"]:checked` ); + const checked = this.getWrapNode(control).querySelector( + `input[type="radio"][data-name="${name}"]:checked` + ); value = checked ? checked.value : ''; break; } case 'checkbox': { - value = [ ...this.getWrapNode( control ).querySelectorAll( `input[type="checkbox"][name="${name}"]:checked` ) ].map( input => input.value ); + value = [ + ...this.getWrapNode(control).querySelectorAll( + `input[type="checkbox"][name="${name}"]:checked` + ), + ].map((input) => input.value); break; } case 'select': { - if ( this.isMultiple( control ) ) { - value = [ ...control.querySelectorAll( 'option:checked' ) ].map( option => option.value ); + if (this.isMultiple(control)) { + value = [...control.querySelectorAll('option:checked')].map( + (option) => option.value + ); } else { - const selected = control.querySelector( 'option:checked' ); + const selected = control.querySelector('option:checked'); value = selected ? selected.value : ''; } break; } case 'datetime-local': { - if ( control.value ) { - const dt = control.value.split( 'T' )[ 1 ].length === 5 ? control.value + ':00' : control.value; + if (control.value) { + const dt = + control.value.split('T')[1].length === 5 + ? `${control.value}:00` + : control.value; // Add local timezone offset // do not use toISOLocalString because new Date("2019-10-17T16:34:23.048") works differently in iOS/Safari // Take care to get DST offsets right for the date value. - value = dt + getTimezoneOffsetAsTime( new Date( dt ) ); + value = dt + getTimezoneOffsetAsTime(new Date(dt)); } break; } @@ -265,15 +291,23 @@

js/input.js

* @param {number} index - repeat index * @return {Element} found element */ - find( name, index = 0 ) { + find(name, index = 0) { let attr = 'name'; - if ( this.form.view.html.querySelector( `input[type="radio"][data-name="${name}"]:not(.ignore)` ) ) { + if ( + this.form.view.html.querySelector( + `input[type="radio"][data-name="${name}"]:not(.ignore)` + ) + ) { attr = 'data-name'; } const selector = `[${attr}="${name}"]:not([data-event="xforms-value-changed"])`; - const question = this.getWrapNodes( this.form.view.html.querySelectorAll( selector ) )[ index ]; + const question = this.getWrapNodes( + this.form.view.html.querySelectorAll(selector) + )[index]; - return question ? question.querySelector( `[${attr}="${name}"]:not(.ignore)` ) : null; + return question + ? question.querySelector(`[${attr}="${name}"]:not(.ignore)`) + : null; }, /** * Sets the value of a form control (or group like radiobuttons) @@ -283,24 +317,26 @@

js/input.js

* @param {Event} [event] - event to fire after setting value * @return {Element} first control whose value was set */ - setVal( control, value, event = events.InputUpdate() ) { + setVal(control, value, event = events.InputUpdate()) { let inputs; - const type = this.getInputType( control ); - const xmlType = this.getXmlType( control ); - const question = this.getWrapNode( control ); - const name = this.getName( control ); + const type = this.getInputType(control); + const xmlType = this.getXmlType(control); + const question = this.getWrapNode(control); + const name = this.getName(control); - if ( type === 'radio' ) { + if (type === 'radio') { // data-name is always present on radiobuttons - inputs = question.querySelectorAll( `[data-name="${name}"]:not(.ignore)` ); + inputs = question.querySelectorAll( + `[data-name="${name}"]:not(.ignore)` + ); } else { // why not use this.getIndex? - inputs = question.querySelectorAll( `[name="${name}"]:not(.ignore)` ); + inputs = question.querySelectorAll(`[name="${name}"]:not(.ignore)`); - if ( type === 'file' ) { + if (type === 'file') { // value of file input can be reset to empty but not to a non-empty value - if ( value ) { - control.setAttribute( 'data-loaded-file-name', value ); + if (value) { + control.setAttribute('data-loaded-file-name', value); // console.error('Cannot set value of file input field (value: '+value+'). If trying to load '+ // 'this record for editing this file input field will remain unchanged.'); @@ -308,83 +344,124 @@

js/input.js

} } - if ( xmlType === 'date' || xmlType === 'datetime' ) { - if ( value ) { + if (xmlType === 'date' || xmlType === 'datetime') { + if (value) { // convert current value (loaded from instance) to a value that a native datepicker understands // TODO: test for IE, FF, Safari when those browsers start including native datepickers - value = types[ xmlType.toLowerCase() ].convert( value ); + value = types[xmlType.toLowerCase()].convert(value); - if ( xmlType === 'datetime' ) { + if (xmlType === 'datetime') { // convert to local time zone - value = toISOLocalString( new Date( value ) ); + value = toISOLocalString(new Date(value)); // chop off local timezone offset to display properly in (native datetime-local) widget - const parts = value.split( 'T' ); - const date = parts[ 0 ]; - const time = ( parts && parts[ 1 ] ) ? parts[ 1 ].split( /[Z\-+]/ )[ 0 ] : '00:00'; + const parts = value.split('T'); + const date = parts[0]; + const time = + parts && parts[1] + ? parts[1].split(/[Z\-+]/)[0] + : '00:00'; value = `${date}T${time}`; } } } - if ( type === 'time' ) { + if (type === 'time') { // convert to a local time value that HTML time inputs and the JS widget understand (01:02) - if ( /(\+|-)/.test( value ) ) { + if (/(\+|-)/.test(value)) { // Use today's date to incorporate daylight savings changes, // Strip the thousands of a second, because most browsers fail to parse such a time. // Add a space before the timezone offset to satisfy some browsers. // For IE11, we also need to strip the Left-to-Right marks \u200E... - const ds = `${new Date().toLocaleDateString( 'en', { - month: 'short', - day: 'numeric', - year: 'numeric' - } ).replace( /\u200E/g, '' )} ${value.replace( /(\d\d:\d\d:\d\d)(\.\d{1,3})(\s?((\+|-)\d\d))(:)?(\d\d)?/, '$1 GMT$3$7' )}`; - const d = new Date( ds ); - if ( d.toString() !== 'Invalid Date' ) { - value = `${d.getHours().toString().padStart( 2, '0' )}:${d.getMinutes().toString().padStart( 2, '0' )}`; + const ds = `${new Date() + .toLocaleDateString('en', { + month: 'short', + day: 'numeric', + year: 'numeric', + }) + .replace(/\u200E/g, '')} ${value.replace( + /(\d\d:\d\d:\d\d)(\.\d{1,3})(\s?((\+|-)\d\d))(:)?(\d\d)?/, + '$1 GMT$3$7' + )}`; + const d = new Date(ds); + if (d.toString() !== 'Invalid Date') { + value = `${d.getHours().toString().padStart(2, '0')}:${d + .getMinutes() + .toString() + .padStart(2, '0')}`; } else { - console.error( 'could not parse time:', value ); + console.error('could not parse time:', value); } } } } - if ( this.isMultiple( control ) === true ) { + if (this.isMultiple(control) === true) { // TODO: It's weird that setVal does not take an array value but getVal returns an array value for multiple selects! - value = value.split( ' ' ); - } else if ( type === 'radio' ) { - value = [ value ]; + value = value.split(' '); + } else if (type === 'radio') { + value = [value]; } - if ( inputs.length ) { - const curVal = this.getVal( control ); - if ( curVal === undefined || curVal.toString() !== value.toString() ) { - switch ( type ) { + if (inputs.length) { + const curVal = this.getVal(control); + if ( + curVal === undefined || + curVal.toString() !== value.toString() + ) { + switch (type) { case 'radio': { - if ( value.toString() === '' ){ - inputs.forEach( input => input.checked = false ); + if (value.toString() === '') { + inputs.forEach((input) => (input.checked = false)); } else { - const input = this.getWrapNode( control ).querySelector( `input[type="radio"][data-name="${name}"][value="${value}"]` ); - if ( input ) { + const input = this.getWrapNode( + control + ).querySelector( + `input[type="radio"][data-name="${name}"][value="${CSS.escape( + value + )}"]` + ); + if (input) { input.checked = true; } } break; } case 'checkbox': { - this.getWrapNode( control ).querySelectorAll( `input[type="checkbox"][name="${name}"]` ) - .forEach( input => input.checked = value.includes( input.value ) ); + this.getWrapNode(control) + .querySelectorAll( + `input[type="checkbox"][name="${name}"]` + ) + .forEach( + (input) => + (input.checked = value.includes( + input.value + )) + ); break; } case 'select': { - if ( this.isMultiple( control ) ) { - control.querySelectorAll( 'option' ).forEach( option => option.selected = value.includes( option.value ) ); + if (this.isMultiple(control)) { + control + .querySelectorAll('option') + .forEach( + (option) => + (option.selected = value.includes( + option.value + )) + ); } else { - const option = control.querySelector( `option[value="${value}"]` ); - if ( option ) { + const option = control.querySelector( + `option[value="${CSS.escape(value)}"]` + ); + if (option) { option.selected = true; } else { - control.querySelectorAll( 'option' ).forEach( option => option.selected = false ); + control + .querySelectorAll('option') + .forEach( + (option) => (option.selected = false) + ); } } break; @@ -395,18 +472,18 @@

js/input.js

} // don't trigger on all radiobuttons/checkboxes - if ( event ) { - inputs[ 0 ].dispatchEvent( event ); + if (event) { + inputs[0].dispatchEvent(event); // Ensure that any calculations with form controls that serve as action triggers // the action. - if ( event.type === events.InputUpdate().type ){ - inputs[0].dispatchEvent( events.XFormsValueChanged() ); + if (event.type === events.InputUpdate().type) { + inputs[0].dispatchEvent(events.XFormsValueChanged()); } } } } - return inputs[ 0 ]; + return inputs[0]; }, /** * Clears form input fields and triggers events when doing this. @@ -415,28 +492,32 @@

js/input.js

* @param event1 - first event to trigger * @param event2 - second event to trigger */ - clear( grp, event1, event2 ){ + clear(grp, event1, event2) { // See original pre-December 2020 plugin.js for some additional stuff with file-preview, loadedFileName and selectedIndex // which I think was no longer necessary, or should be moved to the widgets instead // Note, issue https://github.com/enketo/enketo-core/issues/773, wrt to querySelectorAll use here. - const questions = grp.matches( '.question' ) ? [ grp ] : grp.querySelectorAll( '.question' ); - questions.forEach( question => { - const control = question.querySelector( 'input:not(.ignore), select:not(.ignore), textarea:not(.ignore)' ); - if ( control ){ - this.setVal( control, '', event1 ); - if ( event2 ){ - control.dispatchEvent( event2 ); + const questions = grp.matches('.question') + ? [grp] + : grp.querySelectorAll('.question'); + questions.forEach((question) => { + const control = question.querySelector( + 'input:not(.ignore), select:not(.ignore), textarea:not(.ignore)' + ); + if (control) { + this.setVal(control, '', event1); + if (event2) { + control.dispatchEvent(event2); } } - } ); + }); }, /** * @param {Element} control - form control HTML element * @return {Promise<undefined|ValidateInputResolution>} Promise that resolves */ - validate( control ) { - return this.form.validateInput( control ); - } + validate(control) { + return this.form.validateInput(control); + }, };
diff --git a/docs/js_itemset.js.html b/docs/js_itemset.js.html index 1d0928517..ec372447e 100644 --- a/docs/js_itemset.js.html +++ b/docs/js_itemset.js.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -53,11 +53,16 @@

js/itemset.js

* @module itemset */ -import { parseFunctionFromExpression } from './utils'; import dialog from 'enketo/dialog'; -import { closestAncestorUntil, getChild, getSiblingElement, elementDataStore as data } from './dom-utils'; -import events from './event'; import { t } from 'enketo/translator'; +import { parseFunctionFromExpression } from './utils'; +import { + closestAncestorUntil, + getChild, + getSiblingElement, + elementDataStore as data, +} from './dom-utils'; +import events from './event'; /** * This function tries to determine whether an XPath expression for a nodeset from an external instance is static. @@ -69,16 +74,16 @@

js/itemset.js

* @param {string} expr - XPath expression to analyze * @return {boolean} Whether expression contains a predicate */ -function isStaticItemsetFromSecondaryInstance( expr ) { - const refersToInstance = /^\s*instance\(.+\)/.test( expr ); - if ( !refersToInstance ) { +function isStaticItemsetFromSecondaryInstance(expr) { + const refersToInstance = /^\s*instance\(.+\)/.test(expr); + if (!refersToInstance) { return false; } - const containsPredicate = /\[.+\]/.test( expr ); - if ( !containsPredicate ) { + const containsPredicate = /\[.+\]/.test(expr); + if (!containsPredicate) { return true; } - const containsNumericPredicate = /\[\d+\]/.test( expr ); + const containsNumericPredicate = /\[\d+\]/.test(expr); return containsNumericPredicate; } @@ -89,210 +94,344 @@

js/itemset.js

/** * @param {UpdatedDataNodes} [updated] - The object containing info on updated data nodes. */ - update( updated = {} ) { + update(updated = {}) { const that = this; const fragmentsCache = {}; let nodes; - if ( !this.form ) { - throw new Error( 'Output module not correctly instantiated with form property.' ); + if (!this.form) { + throw new Error( + 'Output module not correctly instantiated with form property.' + ); } - if ( updated.relevantPath ) { + if (updated.relevantPath) { // Questions that are descendants of a group: - nodes = this.form.getRelatedNodes( 'data-items-path', '.itemset-template' ).get() - .filter( template => { - return template.querySelector( `[type="checkbox"][name^="${updated.relevantPath}/"]` ) // checkboxes, ancestor relevant - || template.querySelector( `[type="radio"][data-name^="${updated.relevantPath}/"]` ) // radiobuttons, ancestor relevant - || template.parentElement.matches( `select[name^="${updated.relevantPath}/"]` ) // select minimal, ancestor relevant - || template.parentElement.parentElement.querySelector( `input[list][name^="${updated.relevantPath}/"]` ) // autocomplete, ancestor relevant - || template.querySelector( `[type="checkbox"][name="${updated.relevantPath}"]` ) // checkboxes, self relevant - || template.querySelector( `[type="radio"][data-name="${updated.relevantPath}"]` ) // radiobuttons, self relevant - || template.parentElement.matches( `select[name="${updated.relevantPath}"]` ) // select minimal, self relevant - || template.parentElement.parentElement.querySelector( `input[list][name="${updated.relevantPath}"]` ); // autocomplete, self relevant - } ); + nodes = this.form + .getRelatedNodes('data-items-path', '.itemset-template') + .get() + .filter( + (template) => + template.querySelector( + `[type="checkbox"][name^="${updated.relevantPath}/"]` + ) || // checkboxes, ancestor relevant + template.querySelector( + `[type="radio"][data-name^="${updated.relevantPath}/"]` + ) || // radiobuttons, ancestor relevant + template.parentElement.matches( + `select[name^="${updated.relevantPath}/"]` + ) || // select minimal, ancestor relevant + template.parentElement.parentElement.querySelector( + `input[list][name^="${updated.relevantPath}/"]` + ) || // autocomplete, ancestor relevant + template.querySelector( + `[type="checkbox"][name="${updated.relevantPath}"]` + ) || // checkboxes, self relevant + template.querySelector( + `[type="radio"][data-name="${updated.relevantPath}"]` + ) || // radiobuttons, self relevant + template.parentElement.matches( + `select[name="${updated.relevantPath}"]` + ) || // select minimal, self relevant + template.parentElement.parentElement.querySelector( + `input[list][name="${updated.relevantPath}"]` + ) // autocomplete, self relevant + ); // TODO: missing case: static shared itemlist in repeat - } else { - nodes = this.form.getRelatedNodes( 'data-items-path', '.itemset-template', updated ) + nodes = this.form + .getRelatedNodes( + 'data-items-path', + '.itemset-template', + updated + ) .get(); } - const clonedRepeatsPresent = this.form.repeatsPresent && this.form.view.html.querySelector( '.or-repeat.clone' ); + const clonedRepeatsPresent = + this.form.repeatsPresent && + this.form.view.html.querySelector('.or-repeat.clone'); const alerts = []; - nodes.forEach( template => { - const shared = template.parentElement.parentElement.matches( '.or-repeat-info' ); + nodes.forEach((template) => { + const shared = + template.parentElement.parentElement.matches('.or-repeat-info'); const inputAttributes = {}; // Nodes are in document order, so we discard any nodes in questions/groups that have a disabled parent - if ( closestAncestorUntil( template, '.disabled', '.or' ) ) { + if (closestAncestorUntil(template, '.disabled', '.or')) { return; } const newItems = {}; - const prevItems = data.get( template, 'items' ) || {}; + const prevItems = data.get(template, 'items') || {}; const templateNodeName = template.nodeName.toLowerCase(); - const list = template.parentElement.matches( 'select, datalist' ) ? template.parentElement : null; + const list = template.parentElement.matches('select, datalist') + ? template.parentElement + : null; let input; - if ( templateNodeName === 'label' ) { - const optionInput = getChild( template, 'input' ); - [].slice.call( optionInput.attributes ).forEach( attr => { - inputAttributes[ attr.name ] = attr.value; - } ); + if (templateNodeName === 'label') { + const optionInput = getChild(template, 'input'); + [].slice.call(optionInput.attributes).forEach((attr) => { + inputAttributes[attr.name] = attr.value; + }); // If this is a ranking widget: - input = optionInput.classList.contains( 'ignore' ) ? getSiblingElement( optionInput.closest( '.option-wrapper' ), 'input.rank' ) : optionInput; - } else if ( list && list.nodeName.toLowerCase() === 'select' ) { + input = optionInput.classList.contains('ignore') + ? getSiblingElement( + optionInput.closest('.option-wrapper'), + 'input.rank' + ) + : optionInput; + } else if (list && list.nodeName.toLowerCase() === 'select') { input = list; - } else if ( list && list.nodeName.toLowerCase() === 'datalist' ) { - if ( shared ) { + if (input.matches('[readonly]')) { + inputAttributes.disabled = 'disabled'; + } + } else if (list && list.nodeName.toLowerCase() === 'datalist') { + if (shared) { // only the first input, is that okay? - input = that.form.view.html.querySelector( `input[name="${list.dataset.name}"]` ); + input = that.form.view.html.querySelector( + `input[name="${list.dataset.name}"]` + ); } else { - input = getSiblingElement( list, 'input:not(.widget)' ); + input = getSiblingElement(list, 'input:not(.widget)'); } } - const labelsContainer = getSiblingElement( template.closest( 'label, select, datalist' ), '.itemset-labels' ); + const labelsContainer = getSiblingElement( + template.closest('label, select, datalist'), + '.itemset-labels' + ); const itemsXpath = template.dataset.itemsPath; - let labelType = labelsContainer.dataset.labelType; - let labelRef = labelsContainer.dataset.labelRef; + let { labelType } = labelsContainer.dataset; + let { labelRef } = labelsContainer.dataset; // TODO: if translate() becomes official, move determination of labelType to enketo-xslt // and set labelRef correct in enketo-xslt - const matches = parseFunctionFromExpression( labelRef, 'translate' ); - if ( matches.length ) { - labelRef = matches[ 0 ][ 1 ][ 0 ]; + const matches = parseFunctionFromExpression(labelRef, 'translate'); + if (matches.length) { + labelRef = matches[0][1][0]; labelType = 'langs'; } - const valueRef = labelsContainer.dataset.valueRef; + const { valueRef } = labelsContainer.dataset; // Shared datalists are under .or-repeat-info. Context is not relevant as these are static lists (without relative nodes). - const context = that.form.input.getName( input ); + const context = that.form.input.getName(input); /* * Determining the index is expensive, so we only do this when the itemset is inside a cloned repeat and not shared. * It can be safely set to 0 for other branches. */ - const index = ( !shared && clonedRepeatsPresent && closestAncestorUntil( input, '.or-repeat.clone', '.or' ) ) ? that.form.input.getIndex( input ) : 0; + const index = + !shared && + clonedRepeatsPresent && + closestAncestorUntil(input, '.or-repeat.clone', '.or') + ? that.form.input.getIndex(input) + : 0; const safeToTryNative = true; // Caching has no advantage here. This is a very quick query (natively). - const instanceItems = this.form.model.evaluate( itemsXpath, 'nodes', context, index, safeToTryNative ); + const instanceItems = this.form.model.evaluate( + itemsXpath, + 'nodes', + context, + index, + safeToTryNative + ); // This property allows for more efficient 'itemschanged' detection newItems.length = instanceItems.length; // TODO: This may cause problems for large itemsets. Use md5 instead? - newItems.text = instanceItems.map( item => item.textContent ).join( '' ); - - if ( newItems.length === prevItems.length && newItems.text === prevItems.text ) { + newItems.text = instanceItems + .map((item) => item.textContent) + .join(''); + + if ( + newItems.length === prevItems.length && + newItems.text === prevItems.text + ) { return; } - data.put( template, 'items', newItems ); + data.put(template, 'items', newItems); /** * Remove current items before rebuilding a new itemset from scratch. */ // the current <option> and <input> elements // datalist will catch the shared datalists inside .or-repeat-info - const question = template.closest( '.question, datalist' ); - [ ...question.querySelectorAll( templateNodeName ) ].filter( el => el !== template ).forEach( el => el.remove() ); + const question = template.closest('.question, datalist'); + [...question.querySelectorAll(templateNodeName)] + .filter((el) => el !== template) + .forEach((el) => el.remove()); // labels for current <option> elements const next = question.nextElementSibling; // next is a somewhat fragile match for option-translations belonging to a shared datalist in // .or-repeat-info if there are multiple shared datalists. - const optionsTranslations = next && next.matches( '.or-option-translations' ) ? next : question.querySelector( '.or-option-translations' ); - if ( optionsTranslations ) { - [ ...optionsTranslations.children ].forEach( child => child.remove() ); + const optionsTranslations = + next && next.matches('.or-option-translations') + ? next + : question.querySelector('.or-option-translations'); + if (optionsTranslations) { + [...optionsTranslations.children].forEach((child) => + child.remove() + ); } let optionsFragment = document.createDocumentFragment(); let optionsTranslationsFragment = document.createDocumentFragment(); let translations = []; const cacheKey = `${context}:${itemsXpath}`; - if ( fragmentsCache[ cacheKey ] ) { + if (fragmentsCache[cacheKey]) { // important: leave cache intact by cloning - optionsFragment = fragmentsCache[ cacheKey ].optionsFragment.cloneNode( true ); - optionsTranslationsFragment = fragmentsCache[ cacheKey ].optionsTranslationsFragment.cloneNode( true ); + optionsFragment = + fragmentsCache[cacheKey].optionsFragment.cloneNode(true); + optionsTranslationsFragment = + fragmentsCache[ + cacheKey + ].optionsTranslationsFragment.cloneNode(true); } else { - instanceItems.forEach( item => { + instanceItems.forEach((item) => { /* * Note: $labelRefs could either be * - a single itext reference * - a collection of labels with different lang attributes * - a single label */ - const labels = that.getNodesFromItem( labelRef, item ); - if ( !labels || !labels.length ) { - translations = [ { language: '', label: 'error', active: true } ]; + const labels = that.getNodesFromItem(labelRef, item); + if (!labels || !labels.length) { + translations = [ + { language: '', label: 'error', active: true }, + ]; } else { - switch ( labelType ) { + switch (labelType) { case 'itext': // Search in the special .itemset-labels created in enketo-transformer for labels with itext ref. - translations = [ ...labelsContainer.querySelectorAll( `[data-itext-id="${labels[ 0 ].textContent}"]` ) ].map( label => { - const language = label.getAttribute( 'lang' ); + translations = [ + ...labelsContainer.querySelectorAll( + `[data-itext-id="${labels[0].textContent}"]` + ), + ].map((label) => { + const language = label.getAttribute('lang'); const type = label.nodeName; - const src = label.src; - const contentNodes = [ ...label.childNodes ]; - const active = label.classList.contains( 'active' ); - const alt = label.alt; - - return { language, type, contentNodes, active, src, alt }; - } ); + const { src } = label; + const contentNodes = [...label.childNodes]; + const active = + label.classList.contains('active'); + const { alt } = label; + + return { + language, + type, + contentNodes, + active, + src, + alt, + }; + }); break; case 'langs': - translations = labels.map( label => { - const lang = label.getAttribute( 'lang' ); + translations = labels.map((label) => { + const lang = label.getAttribute('lang'); // Two falsy values should set active to true. - const active = ( !lang && !that.form.langs.currentLanguage ) || ( lang === that.form.langs.currentLanguage ); - - return { language: lang, type: 'span', contentNodes: [ ...label.childNodes ], active }; - } ); + const active = + (!lang && + !that.form.langs.currentLanguage) || + lang === + that.form.langs.currentLanguage; + + return { + language: lang, + type: 'span', + contentNodes: [...label.childNodes], + active, + }; + }); break; default: - translations = [ { language: '', type: 'span', contentNodes: labels && labels.length ? [ ...labels[ 0 ].childNodes ] : [], active: true } ]; + translations = [ + { + language: '', + type: 'span', + contentNodes: + labels && labels.length + ? [...labels[0].childNodes] + : [], + active: true, + }, + ]; } } // Obtain the value of the secondary instance item found. - const value = that.getNodeFromItem( valueRef, item ).textContent; + const value = that.getNodeFromItem( + valueRef, + item + ).textContent; /** * #510 Show warning if select_multiple value has spaces */ - const multiple = ( inputAttributes[ 'data-type-xml' ] == 'select' ) && ( inputAttributes[ 'type' ] == 'checkbox' ) || ( list && list.multiple ); - if ( multiple && ( value.indexOf( ' ' ) > -1 ) ) { - alerts[ alerts.length ] = t( 'alert.valuehasspaces.multiple', { value: value } ); + const multiple = + (inputAttributes['data-type-xml'] === 'select' && + inputAttributes.type === 'checkbox') || + (list && list.multiple); + if (multiple && value.indexOf(' ') > -1) { + alerts[alerts.length] = t( + 'alert.valuehasspaces.multiple', + { value } + ); } - if ( templateNodeName === 'label' ) { - optionsFragment.appendChild( that.createInput( inputAttributes, translations, value ) ); - } else if ( templateNodeName === 'option' ) { + if (templateNodeName === 'label') { + optionsFragment.appendChild( + that.createInput( + inputAttributes, + translations, + value + ) + ); + } else if (templateNodeName === 'option') { let activeLabelContentNodes = []; - if ( translations.length > 1 ) { - translations.forEach( translation => { - if ( translation.active ) { - activeLabelContentNodes = translation.contentNodes; + if (translations.length > 1) { + translations.forEach((translation) => { + if (translation.active) { + activeLabelContentNodes = + translation.contentNodes; } - optionsTranslationsFragment.appendChild( that.createOptionTranslation( translation, value ) ); - } ); + optionsTranslationsFragment.appendChild( + that.createOptionTranslation( + translation, + value + ) + ); + }); } else { - activeLabelContentNodes = translations[ 0 ].contentNodes; + activeLabelContentNodes = + translations[0].contentNodes; } - optionsFragment.appendChild( that.createOption( activeLabelContentNodes, value ) ); + optionsFragment.appendChild( + that.createOption( + inputAttributes, + activeLabelContentNodes, + value + ) + ); } - - } ); + }); // Do not cache radio button questions inside a repeat because each set (in each repeat) should maintain unique name attribute - if ( isStaticItemsetFromSecondaryInstance( itemsXpath ) && !( input.type === 'radio' && input.closest( '.or-repeat' ) ) ) { - fragmentsCache[ cacheKey ] = { - optionsFragment: optionsFragment.cloneNode( true ), - optionsTranslationsFragment: optionsTranslationsFragment.cloneNode( true ) + if ( + isStaticItemsetFromSecondaryInstance(itemsXpath) && + !(input.type === 'radio' && input.closest('.or-repeat')) + ) { + fragmentsCache[cacheKey] = { + optionsFragment: optionsFragment.cloneNode(true), + optionsTranslationsFragment: + optionsTranslationsFragment.cloneNode(true), }; } } - template.parentNode.appendChild( optionsFragment ); - if ( optionsTranslations ) { - optionsTranslations.appendChild( optionsTranslationsFragment ); + template.parentNode.appendChild(optionsFragment); + if (optionsTranslations) { + optionsTranslations.appendChild(optionsTranslationsFragment); } /** @@ -303,25 +442,23 @@

js/itemset.js

*/ // It is not necessary to do this for default values in static itemsets because setAllVals takes care of this. - let currentValue = that.form.model.node( context, index ).getVal(); - if ( currentValue !== '' ) { - - if ( input.classList.contains( 'rank' ) ) { + let currentValue = that.form.model.node(context, index).getVal(); + if (currentValue !== '') { + if (input.classList.contains('rank')) { currentValue = ''; } - that.form.input.setVal( input, currentValue, events.Change() ); + that.form.input.setVal(input, currentValue, events.Change()); } - if ( list || input.classList.contains( 'rank' ) ) { - input.dispatchEvent( events.ChangeOption() ); + if (list || input.classList.contains('rank')) { + input.dispatchEvent(events.ChangeOption()); } - - } ); - if ( alerts.length > 0 ) { + }); + if (alerts.length > 0) { /** * We're assuming the enketo-core-consuming app has a dialog that supports some basic HTML rendering */ - dialog.alert( alerts.join( '<br>' ) ); + dialog.alert(alerts.join('<br>')); } }, @@ -333,20 +470,31 @@

js/itemset.js

* @param {boolean} single - whether to only return a single (first) node * @return {Array<Element>} found nodes */ - getNodesFromItem( expr, context, single ) { - if ( !expr ) { - throw new Error( 'Error: could not query instance item, no expression provided' ); + getNodesFromItem(expr, context, single) { + if (!expr) { + throw new Error( + 'Error: could not query instance item, no expression provided' + ); } const type = single ? 9 : 7; - const evaluateFnName = typeof this.form.model.xml.evaluate !== 'undefined' ? 'evaluate' : 'jsEvaluate'; - const result = this.form.model.xml[ evaluateFnName ]( expr, context, this.form.model.getNsResolver(), type, null ); + const evaluateFnName = + typeof this.form.model.xml.evaluate !== 'undefined' + ? 'evaluate' + : 'jsEvaluate'; + const result = this.form.model.xml[evaluateFnName]( + expr, + context, + this.form.model.getNsResolver(), + type, + null + ); const response = []; - if ( !single ) { - for ( let j = 0; j < result.snapshotLength; j++ ) { - response.push( result.snapshotItem( j ) ); + if (!single) { + for (let j = 0; j < result.snapshotLength; j++) { + response.push(result.snapshotItem(j)); } } else { - response.push( result.singleNodeValue ); + response.push(result.singleNodeValue); } return response; @@ -357,22 +505,28 @@

js/itemset.js

* @param {string} context - evalation context path * @return {Element|null} found nodes */ - getNodeFromItem( expr, context ) { - const nodes = this.getNodesFromItem( expr, context, true ); + getNodeFromItem(expr, context) { + const nodes = this.getNodesFromItem(expr, context, true); - return nodes.length ? nodes[ 0 ] : null; + return nodes.length ? nodes[0] : null; }, /** * Creates a HTML option element * + * @param {object} attributes - attributes to add to option * @param {Array<Element>} labelContentNodes - label content nodes * @param {string} value - option value * @return {Element} created option */ - createOption( labelContentNodes, value ) { - const option = document.createElement( 'option' ); - option.textContent = labelContentNodes.map( node => node.textContent ).join( '' ); + createOption(attributes, labelContentNodes, value) { + const option = document.createElement('option'); + Object.getOwnPropertyNames(attributes).forEach((attr) => { + option.setAttribute(attr, attributes[attr]); + }); + option.textContent = labelContentNodes + .map((node) => node.textContent) + .join(''); option.value = value; return option; @@ -387,18 +541,20 @@

js/itemset.js

* @param {string} value - option value * @return {Element} created element */ - createOptionTranslation( translation, value ) { - const el = document.createElement( translation.type || 'span' ); - if ( translation.contentNodes ) { - el.classList.add( 'option-label' ); - translation.contentNodes.forEach( node => el.appendChild( node.cloneNode( true ) ) ); + createOptionTranslation(translation, value) { + const el = document.createElement(translation.type || 'span'); + if (translation.contentNodes) { + el.classList.add('option-label'); + translation.contentNodes.forEach((node) => + el.appendChild(node.cloneNode(true)) + ); } - el.classList.toggle( 'active', translation.active ); - if ( translation.language ) { + el.classList.toggle('active', translation.active); + if (translation.language) { el.lang = translation.language; } el.dataset.optionValue = value; - if ( translation.src ) { + if (translation.src) { el.src = translation.src; el.alt = translation.alt; } @@ -409,26 +565,26 @@

js/itemset.js

/** * Creates an input HTML element * - * @param {Array<object>} attributes - attributes to add to input + * @param {object} attributes - attributes to add to input * @param {Array<object>} translations - translation to add * @param {string} value - option value * @return {Element} label element (wrapper) */ - createInput( attributes, translations, value ) { + createInput(attributes, translations, value) { const that = this; - const label = document.createElement( 'label' ); - const input = document.createElement( 'input' ); - Object.getOwnPropertyNames( attributes ).forEach( attr => { - input.setAttribute( attr, attributes[ attr ] ); - } ); + const label = document.createElement('label'); + const input = document.createElement('input'); + Object.getOwnPropertyNames(attributes).forEach((attr) => { + input.setAttribute(attr, attributes[attr]); + }); input.value = value; - label.appendChild( input ); - translations.forEach( translation => { - label.appendChild( that.createOptionTranslation( translation, value ) ); - } ); + label.appendChild(input); + translations.forEach((translation) => { + label.appendChild(that.createOptionTranslation(translation, value)); + }); return label; - } + }, };
diff --git a/docs/js_language.js.html b/docs/js_language.js.html index 971976399..7d611f0ec 100644 --- a/docs/js_language.js.html +++ b/docs/js_language.js.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -60,57 +60,73 @@

js/language.js

/** * @param {string} overrideLang - override language IANA subtag */ - init( overrideLang ) { - if ( !this.form ) { - throw new Error( 'Language module not correctly instantiated with form property.' ); + init(overrideLang) { + if (!this.form) { + throw new Error( + 'Language module not correctly instantiated with form property.' + ); } - const root = this.form.view.html.closest( 'body' ) || this.form.view.html.parentNode; - if ( !root ) { + const root = + this.form.view.html.closest('body') || + this.form.view.html.parentNode; + if (!root) { return; } - const langSelector = root.querySelector( '.form-language-selector' ); - const formLanguages = this.form.view.html.querySelector( '#form-languages' ); + const langSelector = root.querySelector('.form-language-selector'); + const formLanguages = + this.form.view.html.querySelector('#form-languages'); - if ( !formLanguages ) { + if (!formLanguages) { return; } - this.languages = [ ...formLanguages.querySelectorAll( 'option' ) ].map( option => option.value ); - if ( langSelector ) { - langSelector - .append( formLanguages ); - if ( this.languages.length > 1 ) { - langSelector.classList.remove( 'hide' ); + this.languages = [...formLanguages.querySelectorAll('option')].map( + (option) => option.value + ); + if (langSelector) { + langSelector.append(formLanguages); + if (this.languages.length > 1) { + langSelector.classList.remove('hide'); } } - this.formLanguages = root.querySelector( '#form-languages' ); - this.defaultLanguage = this.formLanguages.dataset.defaultLang || undefined; - - if ( overrideLang && this.languages.includes( overrideLang ) && this.languages.length > 1 ) { + this.formLanguages = root.querySelector('#form-languages'); + this.defaultLanguage = + this.formLanguages.dataset.defaultLang || undefined; + + if ( + overrideLang && + this.languages.includes(overrideLang) && + this.languages.length > 1 + ) { this._currentLang = overrideLang; - this.setFormUi( this._currentLang ); + this.setFormUi(this._currentLang); } else { - this._currentLang = this.defaultLanguage || this.languages[ 0 ] || ''; + this._currentLang = this.defaultLanguage || this.languages[0] || ''; } - const langOption = this.formLanguages.querySelector( `[value="${this._currentLang}"]` ); - const currentDirectionality = langOption && langOption.dataset.dir || 'ltr'; + const langOption = this.formLanguages.querySelector( + `[value="${this._currentLang}"]` + ); + const currentDirectionality = + (langOption && langOption.dataset.dir) || 'ltr'; this.formLanguages.value = this._currentLang; - this.form.view.html.setAttribute( 'dir', currentDirectionality ); + this.form.view.html.setAttribute('dir', currentDirectionality); - if ( this.languages.length < 2 ) { + if (this.languages.length < 2) { return; } - this.formLanguages.addEventListener( events.Change().type, event => { + this.formLanguages.addEventListener(events.Change().type, (event) => { event.preventDefault(); this._currentLang = event.target.value; - this.setFormUi( this._currentLang ); - } ); + this.setFormUi(this._currentLang); + }); - this.form.view.html.addEventListener( events.AddRepeat().type, event => this.setFormUi( this._currentLang, event.target ) ); + this.form.view.html.addEventListener(events.AddRepeat().type, (event) => + this.setFormUi(this._currentLang, event.target) + ); }, /** * @type {string} @@ -122,7 +138,9 @@

js/language.js

* @type {string} */ get currentLangDesc() { - const langOption = this.formLanguages.querySelector( `[value="${this._currentLang}"]` ); + const langOption = this.formLanguages.querySelector( + `[value="${this._currentLang}"]` + ); return langOption ? langOption.textContent : null; }, @@ -132,56 +150,76 @@

js/language.js

get languagesUsed() { return this.languages || []; }, - setFormUi( lang, group = this.form.view.html ) { - const dir = this.formLanguages.querySelector( `[value="${lang}"]` ).dataset.dir || 'ltr'; - const translations = [ ...group.querySelectorAll( '[lang]' ) ]; - - this.form.view.html.setAttribute( 'dir', dir ); - translations.forEach( el => el.classList.remove( 'active' ) ); + setFormUi(lang, group = this.form.view.html) { + const dir = + this.formLanguages.querySelector(`[value="${lang}"]`).dataset.dir || + 'ltr'; + const translations = [...group.querySelectorAll('[lang]')]; + + this.form.view.html.setAttribute('dir', dir); + translations.forEach((el) => el.classList.remove('active')); translations - .filter( el => el.matches( `[lang="${lang}"], [lang=""]` ) && - ( !el.classList.contains( 'or-form-short' ) || ( el.classList.contains( 'or-form-short' ) && !getSiblingElement( el, '.or-form-long' ) ) ) ) - .forEach( el => el.classList.add( - 'active' - ) ); + .filter( + (el) => + el.matches(`[lang="${lang}"], [lang=""]`) && + (!el.classList.contains('or-form-short') || + (el.classList.contains('or-form-short') && + !getSiblingElement(el, '.or-form-long'))) + ) + .forEach((el) => el.classList.add('active')); // For use in locale-sensitive XPath functions. // Don't even check whether it's a proper subtag or not. It will revert to client locale if it is not recognized. window.enketoFormLocale = lang; - this.form.view.html.querySelectorAll( 'select, datalist' ).forEach( el => this.setSelect( el ) ); - this.form.view.html.dispatchEvent( events.ChangeLanguage() ); + this.form.view.html + .querySelectorAll('select, datalist') + .forEach((el) => this.setSelect(el)); + this.form.view.html.dispatchEvent(events.ChangeLanguage()); }, /** * swap language of <select> and <datalist> <option>s * * @param {Element} select - select or datalist HTML element */ - setSelect( select ) { + setSelect(select) { const type = select.nodeName.toLowerCase(); - const question = select.closest( '.question, .or-repeat-info' ); - const translations = question ? question.querySelector( '.or-option-translations' ) : null; + const question = select.closest('.question, .or-repeat-info'); + const translations = question + ? question.querySelector('.or-option-translations') + : null; - if ( !translations ) { + if (!translations) { return; } - [ ...select.children ].filter( el => el.matches( 'option' ) && !el.matches( '[value=""], [data-value=""]' ) ) - .forEach( option => { - const curLabel = type === 'datalist' ? option.value : option.textContent; + [...select.children] + .filter( + (el) => + el.matches('option') && + !el.matches('[value=""], [data-value=""]') + ) + .forEach((option) => { + const curLabel = + type === 'datalist' ? option.value : option.textContent; // Datalist will not have initialized when init function is called upon form load, so it is option.value until it has initialized. That is not great. - const value = type === 'datalist' ? option.dataset.value || option.value : option.value; - const translatedOption = translations.querySelector( `.active[data-option-value="${value}"]` ); - if ( translatedOption ) { + const value = + type === 'datalist' + ? option.dataset.value || option.value + : option.value; + const translatedOption = translations.querySelector( + `.active[data-option-value="${CSS.escape(value)}"]` + ); + if (translatedOption) { let newLabel = curLabel; - if ( translatedOption && translatedOption.textContent ) { + if (translatedOption && translatedOption.textContent) { newLabel = translatedOption.textContent; } option.value = value; option.textContent = newLabel; } - } ); - } + }); + }, }; diff --git a/docs/js_mask.js.html b/docs/js_mask.js.html index 8bfb3f940..dd5b8bd33 100644 --- a/docs/js_mask.js.html +++ b/docs/js_mask.js.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -53,17 +53,25 @@

js/mask.js

import { getPasteData } from './utils'; import events from './event'; + const KEYBOARD_CUT_PASTE = 'xvc'; export default { - init() { - /* - * These are hardcoded number input masks. The approach will be different if we - * ever add complex user-defined input masks. - */ - this._setNumberMask( '[data-type-xml="int"]', /^(-?[0-9]+$)/, '-0123456789' ); - this._setNumberMask( '[data-type-xml="decimal"]', /^(-?[0-9]+[.,]?[0-9]*$)/, '-0123456789.,' ); + /* + * These are hardcoded number input masks. The approach will be different if we + * ever add complex user-defined input masks. + */ + this._setNumberMask( + '[data-type-xml="int"]', + /^(-?[0-9]+$)/, + '-0123456789' + ); + this._setNumberMask( + '[data-type-xml="decimal"]', + /^(-?[0-9]+[.,]?[0-9]*$)/, + '-0123456789.,' + ); }, /** @@ -71,61 +79,70 @@

js/mask.js

* @param {string} validRegex - regular expression for valid values * @param {string} allowedChars - string of allowed characters */ - _setNumberMask( selector, validRegex, allowedChars ) { + _setNumberMask(selector, validRegex, allowedChars) { const form = this.form.view.html; - form.addEventListener( 'keydown', event => { - if ( event.target.matches( selector ) ){ - // The "key" property is the correct standards-compliant property to use - // but needs some corrections for non-standard-compliant IE behavior. - if ( this._isNotPrintableKey( event ) || this._isKeyboardCutPaste( event ) || allowedChars.indexOf( event.key ) !== -1 ) { + form.addEventListener('keydown', (event) => { + if (event.target.matches(selector)) { + // The "key" property is the correct standards-compliant property to use + // but needs some corrections for non-standard-compliant IE behavior. + if ( + this._isNotPrintableKey(event) || + this._isKeyboardCutPaste(event) || + allowedChars.indexOf(event.key) !== -1 + ) { return true; } event.preventDefault(); event.stopPropagation(); } + }); - } ); - - form.addEventListener( 'paste', event => { - if ( event.target.matches( selector ) ){ - const val = getPasteData( event ); + form.addEventListener('paste', (event) => { + if (event.target.matches(selector)) { + const val = getPasteData(event); // HTML number input fields will trim the pasted value automatically. - if ( val && validRegex.test( val.trim() ) ) { - // Note that event.target.value will be empty if the pasted value is not a valid number (except in IE11). - // In that case the paste action has the same result as pasting an empty value, ie - // clearing any existing value. + if (val && validRegex.test(val.trim())) { + // Note that event.target.value will be empty if the pasted value is not a valid number (except in IE11). + // In that case the paste action has the same result as pasting an empty value, ie + // clearing any existing value. return true; } - event.target.value = ''; - event.target.dispatchEvent( events.Change() ); + event.target.value = ''; + event.target.dispatchEvent(events.Change()); event.preventDefault(); event.stopPropagation(); } - - } ); + }); /* - * Workaround for most browsers keeping invalid numbers visible in the input without a means to access the invalid value. - * E.g. see https://bugs.chromium.org/p/chromium/issues/detail?id=178437&can=2&q=178437&colspec=ID%20Pri%20M%20Stars%20ReleaseBlock%20Component%20Status%20Owner%20Summary%20OS%20Modified - * - * A much more intelligent way to solve the problem would be to add a feedback loop from the Model to the input that would - * correct (a converted number) or empty (an invalid number). https://github.com/enketo/enketo-core/issues/407 - */ - form.addEventListener( 'blur', event => { - if ( event.target.matches( selector ) ){ - // proper browsers: - if ( typeof event.target.validity !== 'undefined' && typeof event.target.validity.badInput !== 'undefined' && event.target.validity.badInput ) { + * Workaround for most browsers keeping invalid numbers visible in the input without a means to access the invalid value. + * E.g. see https://bugs.chromium.org/p/chromium/issues/detail?id=178437&can=2&q=178437&colspec=ID%20Pri%20M%20Stars%20ReleaseBlock%20Component%20Status%20Owner%20Summary%20OS%20Modified + * + * A much more intelligent way to solve the problem would be to add a feedback loop from the Model to the input that would + * correct (a converted number) or empty (an invalid number). https://github.com/enketo/enketo-core/issues/407 + */ + form.addEventListener('blur', (event) => { + if (event.target.matches(selector)) { + // proper browsers: + if ( + typeof event.target.validity !== 'undefined' && + typeof event.target.validity.badInput !== 'undefined' && + event.target.validity.badInput + ) { event.target.value = ''; } // IE11 (no validity.badInput support, but does give access to invalid number with event.target.value) - else if ( typeof event.target.validity.badInput === 'undefined' && event.target.value && !validRegex.test( event.target.value.trim() ) ) { + else if ( + typeof event.target.validity.badInput === 'undefined' && + event.target.value && + !validRegex.test(event.target.value.trim()) + ) { event.target.value = ''; } } - } ); - + }); }, // Using the (assumed) fact that a non-printable character key always has length > 1 @@ -134,7 +151,7 @@

js/mask.js

* @param {Event} e - event * @return {boolean} whether key is printable */ - _isNotPrintableKey( e ) { + _isNotPrintableKey(e) { return e.key.length > 1 && e.key !== 'Spacebar'; }, @@ -142,10 +159,11 @@

js/mask.js

* @param {Event} e - event * @return {boolean} whether event is a paste event */ - _isKeyboardCutPaste( e ) { - return KEYBOARD_CUT_PASTE.indexOf( e.key ) !== -1 && ( e.metaKey || e.ctrlKey ); - } - + _isKeyboardCutPaste(e) { + return ( + KEYBOARD_CUT_PASTE.indexOf(e.key) !== -1 && (e.metaKey || e.ctrlKey) + ); + }, }; diff --git a/docs/js_nodeset.js.html b/docs/js_nodeset.js.html index a6d054d1f..6b3f97fea 100644 --- a/docs/js_nodeset.js.html +++ b/docs/js_nodeset.js.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -67,56 +67,68 @@

js/nodeset.js

* @param {NodesetFilter} [filter] - Filter object for the result nodeset * @param {FormModel} model - Instance of FormModel */ -const Nodeset = function( selector, index, filter, model ) { +const Nodeset = function (selector, index, filter, model) { const defaultSelector = model.hasInstance ? '/model/instance[1]//*' : '//*'; this.model = model; this.originalSelector = selector; - this.selector = ( typeof selector === 'string' && selector.length > 0 ) ? selector : defaultSelector; - filter = ( typeof filter !== 'undefined' && filter !== null ) ? filter : {}; + this.selector = + typeof selector === 'string' && selector.length > 0 + ? selector + : defaultSelector; + filter = typeof filter !== 'undefined' && filter !== null ? filter : {}; this.filter = filter; - this.filter.onlyLeaf = ( typeof filter.onlyLeaf !== 'undefined' ) ? filter.onlyLeaf : false; - this.filter.noEmpty = ( typeof filter.noEmpty !== 'undefined' ) ? filter.noEmpty : false; + this.filter.onlyLeaf = + typeof filter.onlyLeaf !== 'undefined' ? filter.onlyLeaf : false; + this.filter.noEmpty = + typeof filter.noEmpty !== 'undefined' ? filter.noEmpty : false; this.index = index; }; /** * @return {Element} Single node */ -Nodeset.prototype.getElement = function() { - return this.getElements()[ 0 ]; +Nodeset.prototype.getElement = function () { + return this.getElements()[0]; }; /** * @return {Array<Element>} List of nodes */ -Nodeset.prototype.getElements = function() { +Nodeset.prototype.getElements = function () { let nodes; let /** @type {string} */ val; // cache evaluation result - if ( !this._nodes ) { - this._nodes = this.model.evaluate( this.selector, 'nodes-ordered', null, null, true ); + if (!this._nodes) { + this._nodes = this.model.evaluate( + this.selector, + 'nodes-ordered', + null, + null, + true + ); // noEmpty automatically excludes non-leaf nodes - if ( this.filter.noEmpty === true ) { - this._nodes = this._nodes - .filter( node => { - val = node.textContent; + if (this.filter.noEmpty === true) { + this._nodes = this._nodes.filter((node) => { + val = node.textContent; - return node.children.length === 0 && val.trim().length > 0; - } ); + return node.children.length === 0 && val.trim().length > 0; + }); } // this may still contain empty leaf nodes - else if ( this.filter.onlyLeaf === true ) { - this._nodes = this._nodes - .filter( node => node.children.length === 0 ); + else if (this.filter.onlyLeaf === true) { + this._nodes = this._nodes.filter( + (node) => node.children.length === 0 + ); } } nodes = this._nodes; - if ( typeof this.index !== 'undefined' && this.index !== null ) { - nodes = typeof nodes[ this.index ] === 'undefined' ? [] : [ nodes[ this.index ] ]; + if (typeof this.index !== 'undefined' && this.index !== null) { + nodes = + typeof nodes[this.index] === 'undefined' ? [] : [nodes[this.index]]; } return nodes; @@ -127,7 +139,7 @@

js/nodeset.js

* * @param {number} [index] - The 0-based index */ -Nodeset.prototype.setIndex = function( index ) { +Nodeset.prototype.setIndex = function (index) { this.index = index; }; @@ -140,56 +152,62 @@

js/nodeset.js

* @return {null|UpdatedDataNodes} `null` is returned when the node is not found or multiple nodes were selected, * otherwise an object with update information is returned. */ -Nodeset.prototype.setVal = function( newVals, xmlDataType ) { - let /**@type {string}*/ newVal; +Nodeset.prototype.setVal = function (newVals, xmlDataType) { + let /** @type {string} */ newVal; let updated; let customData; const curVal = this.getVal(); - if ( typeof newVals !== 'undefined' && newVals !== null ) { - newVal = ( Array.isArray( newVals ) ) ? newVals.join( ' ' ) : newVals.toString(); + if (typeof newVals !== 'undefined' && newVals !== null) { + newVal = Array.isArray(newVals) + ? newVals.join(' ') + : newVals.toString(); } else { newVal = ''; } - newVal = this.convert( newVal, xmlDataType ); + newVal = this.convert(newVal, xmlDataType); const targets = this.getElements(); - if ( targets.length === 1 && newVal.toString() !== curVal.toString() ) { - const target = targets[ 0 ]; + if (targets.length === 1 && newVal.toString() !== curVal.toString()) { + const target = targets[0]; // first change the value so that it can be evaluated in XPath (validated) target.textContent = newVal.toString(); // then return validation result updated = this.getClosestRepeat(); - updated.nodes = [ target.nodeName ]; + updated.nodes = [target.nodeName]; - customData = this.model.getUpdateEventData( target, xmlDataType ); - updated = ( customData ) ? $.extend( {}, updated, customData ) : updated; + customData = this.model.getUpdateEventData(target, xmlDataType); + updated = customData ? $.extend({}, updated, customData) : updated; - this.model.events.dispatchEvent( event.DataUpdate( updated ) ); + this.model.events.dispatchEvent(event.DataUpdate(updated)); - //add type="file" attribute for file references - if ( xmlDataType === 'binary' ) { - if ( newVal.length > 0 ) { - target.setAttribute( 'type', 'file' ); + // add type="file" attribute for file references + if (xmlDataType === 'binary') { + if (newVal.length > 0) { + target.setAttribute('type', 'file'); // The src attribute if for default binary values (added by enketo-transformer) // As soon as the value changes this attribute can be removed to clean up. - target.removeAttribute( 'src' ); + target.removeAttribute('src'); } else { - target.removeAttribute( 'type' ); + target.removeAttribute('type'); } } return updated; } - if ( targets.length > 1 ) { - console.error( 'nodeset.setVal expected nodeset with one node, but received multiple' ); + if (targets.length > 1) { + console.error( + 'nodeset.setVal expected nodeset with one node, but received multiple' + ); return null; } - if ( targets.length === 0 ) { - console.warn( `Data node: ${this.selector} with null-based index: ${this.index} not found. Ignored.` ); + if (targets.length === 0) { + console.warn( + `Data node: ${this.selector} with null-based index: ${this.index} not found. Ignored.` + ); return null; } @@ -202,10 +220,10 @@

js/nodeset.js

* * @return {string|undefined} data value of first node or `undefined` if zero nodes */ -Nodeset.prototype.getVal = function() { +Nodeset.prototype.getVal = function () { const nodes = this.getElements(); - return nodes.length ? nodes[ 0 ].textContent : undefined; + return nodes.length ? nodes[0].textContent : undefined; }; /** @@ -213,37 +231,49 @@

js/nodeset.js

* * @return {{repeatPath: string, repeatIndex: number}|{}} Empty object for nothing found */ -Nodeset.prototype.getClosestRepeat = function() { +Nodeset.prototype.getClosestRepeat = function () { let el = this.getElement(); - let nodeName = el.nodeName; - - while ( nodeName && nodeName !== 'instance' && !( el.nextElementSibling && el.nextElementSibling.nodeName === nodeName ) && !( el.previousElementSibling && el.previousElementSibling.nodeName === nodeName ) ) { + let { nodeName } = el; + + while ( + nodeName && + nodeName !== 'instance' && + !( + el.nextElementSibling && el.nextElementSibling.nodeName === nodeName + ) && + !( + el.previousElementSibling && + el.previousElementSibling.nodeName === nodeName + ) + ) { el = el.parentElement; nodeName = el ? el.nodeName : null; } - return ( !nodeName || nodeName === 'instance' ) ? {} : { - repeatPath: getXPath( el, 'instance' ), - repeatIndex: this.model.determineIndex( el ) - }; + return !nodeName || nodeName === 'instance' + ? {} + : { + repeatPath: getXPath(el, 'instance'), + repeatIndex: this.model.determineIndex(el), + }; }; /** * Remove a repeat node */ -Nodeset.prototype.remove = function() { +Nodeset.prototype.remove = function () { const dataNode = this.getElement(); - if ( dataNode ) { - const nodeName = dataNode.nodeName; - const repeatPath = getXPath( dataNode, 'instance' ); - let repeatIndex = this.model.determineIndex( dataNode ); - const removalEventData = this.model.getRemovalEventData( dataNode ); + if (dataNode) { + const { nodeName } = dataNode; + const repeatPath = getXPath(dataNode, 'instance'); + let repeatIndex = this.model.determineIndex(dataNode); + const removalEventData = this.model.getRemovalEventData(dataNode); - if ( !this.model.templates[ repeatPath ] ) { + if (!this.model.templates[repeatPath]) { // This allows the model itseldataNodeout requiring the controller to call .extractFakeTemplates() // to extract non-jr:templates by assuming that node.remove() would only called for a repeat. - this.model.extractFakeTemplates( [ repeatPath ] ); + this.model.extractFakeTemplates([repeatPath]); } // warning: jQuery.next() to be avoided to support dots in the nodename let nextNode = dataNode.nextElementSibling; @@ -252,29 +282,34 @@

js/nodeset.js

this._nodes = null; // For internal use - this.model.events.dispatchEvent( event.DataUpdate( { - nodes: null, - repeatPath, - repeatIndex - } ) ); + this.model.events.dispatchEvent( + event.DataUpdate({ + nodes: null, + repeatPath, + repeatIndex, + }) + ); // For all next sibling repeats to update formulas that use e.g. position(..) // For internal use - while ( nextNode && nextNode.nodeName == nodeName ) { + while (nextNode && nextNode.nodeName === nodeName) { nextNode = nextNode.nextElementSibling; - this.model.events.dispatchEvent( event.DataUpdate( { - nodes: null, - repeatPath, - repeatIndex: repeatIndex++ - } ) ); + this.model.events.dispatchEvent( + event.DataUpdate({ + nodes: null, + repeatPath, + repeatIndex: repeatIndex++, + }) + ); } // For external use, if required with custom data. - this.model.events.dispatchEvent( event.Removed( removalEventData ) ); - + this.model.events.dispatchEvent(event.Removed(removalEventData)); } else { - console.error( `could not find node ${this.selector} with index ${this.index} to remove ` ); + console.error( + `could not find node ${this.selector} with index ${this.index} to remove ` + ); } }; @@ -285,14 +320,17 @@

js/nodeset.js

* @param {string} [xmlDataType] - XML data type * @return {string} - String representation of converted value */ -Nodeset.prototype.convert = ( x, xmlDataType ) => { - if ( x.toString() === '' ) { +Nodeset.prototype.convert = (x, xmlDataType) => { + if (x.toString() === '') { return x; } - if ( typeof xmlDataType !== 'undefined' && xmlDataType !== null && - typeof types[ xmlDataType.toLowerCase() ] !== 'undefined' && - typeof types[ xmlDataType.toLowerCase() ].convert !== 'undefined' ) { - return types[ xmlDataType.toLowerCase() ].convert( x ); + if ( + typeof xmlDataType !== 'undefined' && + xmlDataType !== null && + typeof types[xmlDataType.toLowerCase()] !== 'undefined' && + typeof types[xmlDataType.toLowerCase()].convert !== 'undefined' + ) { + return types[xmlDataType.toLowerCase()].convert(x); } return x; @@ -304,22 +342,28 @@

js/nodeset.js

* @param {string} xmlDataType - XML data type * @return {Promise} promise that resolves with a ValidateInputResolution object */ -Nodeset.prototype.validate = function( constraintExpr, requiredExpr, xmlDataType ) { +Nodeset.prototype.validate = function ( + constraintExpr, + requiredExpr, + xmlDataType +) { const that = this; const result = {}; // Avoid checking constraint if required is invalid - return this.validateRequired( requiredExpr ) - .then( passed => { + return this.validateRequired(requiredExpr) + .then((passed) => { result.requiredValid = passed; - return ( passed === false ) ? null : that.validateConstraintAndType( constraintExpr, xmlDataType ); - } ) - .then( passed => { + return passed === false + ? null + : that.validateConstraintAndType(constraintExpr, xmlDataType); + }) + .then((passed) => { result.constraintValid = passed; return result; - } ); + }); }; /** @@ -329,36 +373,52 @@

js/nodeset.js

* @param {string} [xmlDataType] - XML data type * @return {Promise} wrapping a boolean indicating if the value is valid or not; error also indicates invalid field, or problem validating it */ -Nodeset.prototype.validateConstraintAndType = function( expr, xmlDataType ) { +Nodeset.prototype.validateConstraintAndType = function (expr, xmlDataType) { const that = this; let value; - if ( !xmlDataType || typeof types[ xmlDataType.toLowerCase() ] === 'undefined' ) { + if ( + !xmlDataType || + typeof types[xmlDataType.toLowerCase()] === 'undefined' + ) { xmlDataType = 'string'; } // This one weird trick results in a small validation performance increase. // Do not obtain *the value* if the expr is empty and data type is string, select, select1, binary knowing that this will always return true. - if ( !expr && ( xmlDataType === 'string' || xmlDataType === 'select' || xmlDataType === 'select1' || xmlDataType === 'binary' ) ) { - return Promise.resolve( true ); + if ( + !expr && + (xmlDataType === 'string' || + xmlDataType === 'select' || + xmlDataType === 'select1' || + xmlDataType === 'binary') + ) { + return Promise.resolve(true); } value = that.getVal(); - if ( value.toString() === '' ) { - return Promise.resolve( true ); + if (value.toString() === '') { + return Promise.resolve(true); } return Promise.resolve() - .then( () => types[ xmlDataType.toLowerCase() ].validate( value ) ) - .then( typeValid => { - if ( !typeValid ){ + .then(() => types[xmlDataType.toLowerCase()].validate(value)) + .then((typeValid) => { + if (!typeValid) { return false; } - const exprValid = expr ? that.model.evaluate( expr, 'boolean', that.originalSelector, that.index ) : true; + const exprValid = expr + ? that.model.evaluate( + expr, + 'boolean', + that.originalSelector, + that.index + ) + : true; return exprValid; - } ); + }); }; // TODO: rename to isTrue? @@ -366,8 +426,16 @@

js/nodeset.js

* @param {string} [expr] - The XPath expression * @return {boolean} Whether node is required */ -Nodeset.prototype.isRequired = function( expr ) { - return !expr || expr.trim() === 'false()' ? false : expr.trim() === 'true()' || this.model.evaluate( expr, 'boolean', this.originalSelector, this.index ); +Nodeset.prototype.isRequired = function (expr) { + return !expr || expr.trim() === 'false()' + ? false + : expr.trim() === 'true()' || + this.model.evaluate( + expr, + 'boolean', + this.originalSelector, + this.index + ); }; /** @@ -376,18 +444,20 @@

js/nodeset.js

* @param {string} [expr] - The XPath expression * @return {Promise<boolean>} Promise that resolves with a boolean */ -Nodeset.prototype.validateRequired = function( expr ) { +Nodeset.prototype.validateRequired = function (expr) { const that = this; // if the node has a value or there is no required expression - if ( !expr || this.getVal() ) { - return Promise.resolve( true ); + if (!expr || this.getVal()) { + return Promise.resolve(true); } // if the node does not have a value and there is a required expression - return Promise.resolve() - .then( () => // if the expression evaluates to true, the field is required, and the function returns false. - !that.isRequired( expr ) ); + return Promise.resolve().then( + () => + // if the expression evaluates to true, the field is required, and the function returns false. + !that.isRequired(expr) + ); }; export { Nodeset }; diff --git a/docs/js_output.js.html b/docs/js_output.js.html index 4533d09d7..37600097b 100644 --- a/docs/js_output.js.html +++ b/docs/js_output.js.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -59,25 +59,36 @@

js/output.js

* * @param {UpdatedDataNodes} [updated] - The object containing info on updated data nodes. */ - update( updated ) { + update(updated) { const outputCache = {}; let val = ''; const that = this; - if ( !this.form ) { - throw new Error( 'Output module not correctly instantiated with form property.' ); + if (!this.form) { + throw new Error( + 'Output module not correctly instantiated with form property.' + ); } - const $nodes = this.form.getRelatedNodes( 'data-value', '.or-output', updated ); + const $nodes = this.form.getRelatedNodes( + 'data-value', + '.or-output', + updated + ); - const clonedRepeatsPresent = this.form.repeatsPresent && this.form.view.html.querySelector( '.or-repeat.clone' ); + const clonedRepeatsPresent = + this.form.repeatsPresent && + this.form.view.html.querySelector('.or-repeat.clone'); - $nodes.each( function() { - const $output = $( this ); + $nodes.each(function () { + const $output = $(this); const output = this; // nodes are in document order, so we discard any nodes in questions/groups that have a disabled parent - if ( $output.closest( '.or-branch' ).parent().closest( '.disabled' ).length ) { + if ( + $output.closest('.or-branch').parent().closest('.disabled') + .length + ) { return; } @@ -89,41 +100,58 @@

js/output.js

* or the parent with a name attribute * or the whole document */ - let context = output.closest( '.question, .or-group' ); + let context = output.closest('.question, .or-group'); - - if ( !context.matches( '.or-group' ) ) { - context = context.querySelector( '[name]' ); + if (!context.matches('.or-group')) { + context = context.querySelector('[name]'); } - let contextPath = that.form.input.getName( context ); + let contextPath = that.form.input.getName(context); /* * If the output is part of a group label and that group contains repeats with the same name, * but currently has 0 repeats, the context will not be available. See issue 502. * This same logic is applied in branch.js. */ - if ( $( context ).children( `.or-repeat-info[data-name="${contextPath}"]` ).length && !$( context ).children( `.or-repeat[name="${contextPath}"]` ).length ) { + if ( + $(context).children( + `.or-repeat-info[data-name="${contextPath}"]` + ).length && + !$(context).children(`.or-repeat[name="${contextPath}"]`).length + ) { contextPath = null; } - const insideRepeat = ( clonedRepeatsPresent && $output.parentsUntil( '.or', '.or-repeat' ).length > 0 ); - const insideRepeatClone = ( insideRepeat && $output.parentsUntil( '.or', '.or-repeat.clone' ).length > 0 ); - const index = ( insideRepeatClone && contextPath ) ? that.form.input.getIndex( context ) : 0; - - if ( typeof outputCache[ expr ] !== 'undefined' ) { - val = outputCache[ expr ]; + const insideRepeat = + clonedRepeatsPresent && + $output.parentsUntil('.or', '.or-repeat').length > 0; + const insideRepeatClone = + insideRepeat && + $output.parentsUntil('.or', '.or-repeat.clone').length > 0; + const index = + insideRepeatClone && contextPath + ? that.form.input.getIndex(context) + : 0; + + if (typeof outputCache[expr] !== 'undefined') { + val = outputCache[expr]; } else { - val = that.form.model.evaluate( expr, 'string', contextPath, index, true ); - if ( !insideRepeat ) { - outputCache[ expr ] = val; + val = that.form.model.evaluate( + expr, + 'string', + contextPath, + index, + true + ); + if (!insideRepeat) { + outputCache[expr] = val; } } - if ( $output.text() !== val ) { - $output.text( val ); + if ($output.text() !== val) { + $output.text(val); } - } ); - } + }); + }, }; diff --git a/docs/js_page.js.html b/docs/js_page.js.html index 19ae585b1..49adbfa9d 100644 --- a/docs/js_page.js.html +++ b/docs/js_page.js.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -54,8 +54,8 @@

js/page.js

*/ import $ from 'jquery'; -import events from './event'; import config from 'enketo/config'; +import events from './event'; import { getSiblingElement, getAncestors } from './dom-utils'; import 'jquery-touchswipe'; @@ -78,35 +78,55 @@

js/page.js

* @type {Function} */ init() { - if ( !this.form ) { - throw new Error( 'Repeats module not correctly instantiated with form property.' ); + if (!this.form) { + throw new Error( + 'Repeats module not correctly instantiated with form property.' + ); } - if ( this.form.view.html.classList.contains( 'pages' ) ) { - const allPages = [ ...this.form.view.html.querySelectorAll( '.question, .or-appearance-field-list' ) ] - .concat( [ ...this.form.view.html.querySelectorAll( '.or-repeat.or-appearance-field-list + .or-repeat-info' ) ] ) - .filter( el => { - // something tells me there is a more efficient way to doing this - // e.g. by selecting the descendants of the .or-appearance-field-list and removing those - return !el.parentElement.closest( '.or-appearance-field-list' ) && !( el.matches( '.question' ) && el.querySelector( '[data-for]' ) ); - } ) - .map( el => { - el.setAttribute( 'role', 'page' ); + if (this.form.view.html.classList.contains('pages')) { + const allPages = [ + ...this.form.view.html.querySelectorAll( + '.question, .or-appearance-field-list' + ), + ] + .concat([ + ...this.form.view.html.querySelectorAll( + '.or-repeat.or-appearance-field-list + .or-repeat-info' + ), + ]) + .filter( + (el) => + // something tells me there is a more efficient way to doing this + // e.g. by selecting the descendants of the .or-appearance-field-list and removing those + !el.parentElement.closest( + '.or-appearance-field-list' + ) && + !( + el.matches('.question') && + el.querySelector('[data-for]') + ) + ) + .map((el) => { + el.setAttribute('role', 'page'); return el; - } ); + }); - if ( allPages.length > 0 || allPages[ 0 ].classList.contains( 'or-repeat' ) ) { + if ( + allPages.length > 0 || + allPages[0].classList.contains('or-repeat') + ) { const formWrapper = this.form.view.html.parentNode; - this.$formFooter = $( formWrapper.querySelector( '.form-footer' ) ); - this.$btnFirst = this.$formFooter.find( '.first-page' ); - this.$btnPrev = this.$formFooter.find( '.previous-page' ); - this.$btnNext = this.$formFooter.find( '.next-page' ); - this.$btnNext.attr( 'tabindex', 2 ); - this.$btnLast = this.$formFooter.find( '.last-page' ); - this.$toc = $( formWrapper.querySelector( '.pages-toc__list' ) ); - this._updateAllActive( allPages ); + this.$formFooter = $(formWrapper.querySelector('.form-footer')); + this.$btnFirst = this.$formFooter.find('.first-page'); + this.$btnPrev = this.$formFooter.find('.previous-page'); + this.$btnNext = this.$formFooter.find('.next-page'); + this.$btnNext.attr('tabindex', 2); + this.$btnLast = this.$formFooter.find('.last-page'); + this.$toc = $(formWrapper.querySelector('.pages-toc__list')); + this._updateAllActive(allPages); this._updateToc(); - this._toggleButtons( 0 ); + this._toggleButtons(0); this._setButtonHandlers(); this._setRepeatHandlers(); this._setBranchHandlers(); @@ -116,9 +136,9 @@

js/page.js

this.active = true; this._flipToFirst(); } - /*else { + /* else { form.view.$.removeClass( 'pages' ); - }*/ + } */ } }, /** @@ -129,24 +149,26 @@

js/page.js

* * @param {jQuery} $e - Element on page to flip to */ - flipToPageContaining( $e ) { - const e = $e[ 0 ]; - const closest = e.closest( '[role="page"]' ); + flipToPageContaining($e) { + const e = $e[0]; + const closest = e.closest('[role="page"]'); - if ( closest ) { - this._flipTo( closest ); + if (closest) { + this._flipTo(closest); } else { // If $e is a comment question, and it is not inside a group, there will be no closest. - const referer = e.querySelector( '[data-for]' ); - const ancestor = e.closest( '.or-repeat, form.or' ); - if ( referer && ancestor ) { - const linkedQuestion = ancestor.querySelector( `[name="${referer.dataset.for}"]` ); - if ( linkedQuestion ) { - this._flipTo( linkedQuestion.closest( '[role="page"]' ) ); + const referer = e.querySelector('[data-for]'); + const ancestor = e.closest('.or-repeat, form.or'); + if (referer && ancestor) { + const linkedQuestion = ancestor.querySelector( + `[name="${referer.dataset.for}"]` + ); + if (linkedQuestion) { + this._flipTo(linkedQuestion.closest('[role="page"]')); } } } - this.$toc.parent().find( '.pages-toc__overlay' ).click(); + this.$toc.parent().find('.pages-toc__overlay').click(); }, /** * sets button handlers @@ -154,47 +176,47 @@

js/page.js

_setButtonHandlers() { const that = this; // Make sure eventhandlers are not duplicated after resetting form. - this.$btnFirst.off( '.pagemode' ).on( 'click.pagemode', () => { - if ( !that.form.pageNavigationBlocked ) { + this.$btnFirst.off('.pagemode').on('click.pagemode', () => { + if (!that.form.pageNavigationBlocked) { that._flipToFirst(); } return false; - } ); - this.$btnPrev.off( '.pagemode' ).on( 'click.pagemode', () => { - if ( !that.form.pageNavigationBlocked ) { + }); + this.$btnPrev.off('.pagemode').on('click.pagemode', () => { + if (!that.form.pageNavigationBlocked) { that._prev(); } return false; - } ); - this.$btnNext.off( '.pagemode' ).on( 'click.pagemode', () => { - if ( !that.form.pageNavigationBlocked ) { + }); + this.$btnNext.off('.pagemode').on('click.pagemode', () => { + if (!that.form.pageNavigationBlocked) { that._next(); } return false; - } ); - this.$btnLast.off( '.pagemode' ).on( 'click.pagemode', () => { - if ( !that.form.pageNavigationBlocked ) { + }); + this.$btnLast.off('.pagemode').on('click.pagemode', () => { + if (!that.form.pageNavigationBlocked) { that._flipToLast(); } return false; - } ); + }); }, /** * sets swipe handlers */ _setSwipeHandlers() { - if ( config.swipePage === false ) { + if (config.swipePage === false) { return; } const that = this; - const $main = this.form.view.$.closest( '.main' ); + const $main = this.form.view.$.closest('.main'); - $main.swipe( 'destroy' ); - $main.swipe( { + $main.swipe('destroy'); + $main.swipe({ allowPageScroll: 'vertical', threshold: 250, preventDefaultEvents: false, @@ -204,8 +226,8 @@

js/page.js

swipeRight() { that.$btnPrev.click(); }, - swipeStatus( evt, phase ) { - if ( phase === 'start' ) { + swipeStatus(evt, phase) { + if (phase === 'start') { /* * Triggering blur will fire a change event on the currently focused form control * This will trigger validation and is required to block page navigation on swipe @@ -215,13 +237,15 @@

js/page.js

* set form.pageNavigationBlocked to true. The edge case will be very slow devices * and/or amazingly complex constraint expressions. */ - const focused = that._getCurrent() ? that._getCurrent().querySelector( ':focus' ) : null; - if ( focused ) { + const focused = that._getCurrent() + ? that._getCurrent().querySelector(':focus') + : null; + if (focused) { focused.blur(); } } - } - } ); + }, + }); }, /** * sets toc handlers @@ -229,53 +253,75 @@

js/page.js

_setTocHandlers() { const that = this; this.$toc - .on( 'click', 'a', function() { - if ( !that.form.pageNavigationBlocked ) { - if ( this.parentElement && this.parentElement.getAttribute( 'tocId' ) ) { - const tocId = parseInt( this.parentElement.getAttribute( 'tocId' ), 10 ); - const destItem = that.form.toc.tocItems.find( item => item.tocId === tocId ); - if ( destItem && destItem.element ) { + .on('click', 'a', function () { + if (!that.form.pageNavigationBlocked) { + if ( + this.parentElement && + this.parentElement.getAttribute('tocId') + ) { + const tocId = parseInt( + this.parentElement.getAttribute('tocId'), + 10 + ); + const destItem = that.form.toc.tocItems.find( + (item) => item.tocId === tocId + ); + if (destItem && destItem.element) { const destEl = destItem.element; - that.form.goToTarget( destEl ); + that.form.goToTarget(destEl); } } } return false; - } ) - .parent().find( '.pages-toc__overlay' ).on( 'click', () => { - that.$toc.parent().find( '#toc-toggle' ).prop( 'checked', false ); - } ); + }) + .parent() + .find('.pages-toc__overlay') + .on('click', () => { + that.$toc.parent().find('#toc-toggle').prop('checked', false); + }); }, /** * sets repeat handlers */ _setRepeatHandlers() { // TODO: can be optimized by smartly updating the active pages - this.form.view.html.addEventListener( events.AddRepeat().type, event => { - this._updateAllActive(); - // Don't flip if the user didn't create the repeat with the + button. - // or if is the default first instance created during loading. - // except if the new repeat is actually the first page in the form, or contains the first page - if ( event.detail.trigger === 'user' || this.activePages[ 0 ] === event.target || getAncestors( this.activePages[ 0 ], '.or-repeat' ).includes( event.target ) ) { - this.flipToPageContaining( $( event.target ) ); - } else { - this._toggleButtons(); - } - } ); - this.form.view.html.addEventListener( events.RemoveRepeat().type, event => { - // if the current page is removed - // note that that.current will have length 1 even if it was removed from DOM! - if ( this.current && !this.current.closest( 'html' ) ) { + this.form.view.html.addEventListener( + events.AddRepeat().type, + (event) => { this._updateAllActive(); - let $target = $( event.target ).prev(); - if ( $target.length === 0 ) { - $target = $( event.target ); + // Don't flip if the user didn't create the repeat with the + button. + // or if is the default first instance created during loading. + // except if the new repeat is actually the first page in the form, or contains the first page + if ( + event.detail.trigger === 'user' || + this.activePages[0] === event.target || + getAncestors(this.activePages[0], '.or-repeat').includes( + event.target + ) + ) { + this.flipToPageContaining($(event.target)); + } else { + this._toggleButtons(); + } + } + ); + this.form.view.html.addEventListener( + events.RemoveRepeat().type, + (event) => { + // if the current page is removed + // note that that.current will have length 1 even if it was removed from DOM! + if (this.current && !this.current.closest('html')) { + this._updateAllActive(); + let $target = $(event.target).prev(); + if ($target.length === 0) { + $target = $(event.target); + } + // is it best to go to previous page always? + this.flipToPageContaining($target); } - // is it best to go to previous page always? - this.flipToPageContaining( $target ); } - } ); + ); }, /** * sets branch handlers @@ -284,24 +330,26 @@

js/page.js

const that = this; // TODO: can be optimized by smartly updating the active pages this.form.view.$ - //.off( 'changebranch.pagemode' ) - .on( 'changebranch.pagemode', () => { + // .off( 'changebranch.pagemode' ) + .on('changebranch.pagemode', () => { that._updateAllActive(); // If the current page has become inactive (e.g. a form whose first page during load becomes non-relevant) - if ( !that.activePages.includes( that.current ) ) { + if (!that.activePages.includes(that.current)) { that._next(); } that._toggleButtons(); - } ); + }); }, /** * sets language change handlers */ _setLangChangeHandlers() { - this.form.view.html - .addEventListener( events.ChangeLanguage().type, () => { + this.form.view.html.addEventListener( + events.ChangeLanguage().type, + () => { this._updateToc(); - } ); + } + ); }, /** * @return {Element} current page @@ -312,37 +360,40 @@

js/page.js

/** * @param {Array<Node>} all - all elements that represent a page */ - _updateAllActive( all ) { - all = all || [ ...this.form.view.html.querySelectorAll( '[role="page"]' ) ]; - this.activePages = all.filter( el => { - return !el.closest( '.disabled' ) && - ( el.matches( '.question' ) || el.querySelector( '.question:not(.disabled)' ) || + _updateAllActive(all) { + all = all || [...this.form.view.html.querySelectorAll('[role="page"]')]; + this.activePages = all.filter( + (el) => + !el.closest('.disabled') && + (el.matches('.question') || + el.querySelector('.question:not(.disabled)') || // or-repeat-info is only considered a page by itself if it has no sibling repeats // When there are siblings repeats, we use CSS trickery to show the + button underneath the last // repeat. - ( el.matches( '.or-repeat-info' ) && !getSiblingElement( el, '.or-repeat' ) ) ); - } ); + (el.matches('.or-repeat-info') && + !getSiblingElement(el, '.or-repeat'))) + ); this._updateToc(); }, /** * @param {number} currentIndex - current index * @return {jQuery} Previous page */ - _getPrev( currentIndex ) { - return this.activePages[ currentIndex - 1 ]; + _getPrev(currentIndex) { + return this.activePages[currentIndex - 1]; }, /** * @param {number} currentIndex - current index * @return {jQuery} Next page */ - _getNext( currentIndex ) { - return this.activePages[ currentIndex + 1 ]; + _getNext(currentIndex) { + return this.activePages[currentIndex + 1]; }, /** * @return {number} Current page index */ _getCurrentIndex() { - return this.activePages.findIndex( el => el === this.current ); + return this.activePages.findIndex((el) => el === this.current); }, /** * Changes the `pages.next()` function to return a `Promise`, wrapping one of the following values: @@ -357,47 +408,53 @@

js/page.js

let validate; currentIndex = this._getCurrentIndex(); - validate = ( config.validatePage === false || !this.current ) ? Promise.resolve( true ) : this.form.validateContent( $( this.current ) ); + validate = + config.validatePage === false || !this.current + ? Promise.resolve(true) + : this.form.validateContent($(this.current)); - return validate - .then( valid => { - let next, newIndex; + return validate.then((valid) => { + let next; + let newIndex; - if ( !valid ) { - return false; - } + if (!valid) { + return false; + } - next = that._getNext( currentIndex ); + next = that._getNext(currentIndex); - if ( next ) { - newIndex = currentIndex + 1; - that._flipTo( next, newIndex ); - //return newIndex; - } + if (next) { + newIndex = currentIndex + 1; + that._flipTo(next, newIndex); + // return newIndex; + } - return true; - } ); + return true; + }); }, /** * Switches to previous page */ _prev() { const currentIndex = this._getCurrentIndex(); - const prev = this._getPrev( currentIndex ); + const prev = this._getPrev(currentIndex); - if ( prev ) { - this._flipTo( prev, currentIndex - 1 ); + if (prev) { + this._flipTo(prev, currentIndex - 1); } }, /** * @param {Element} pageEl - page element */ - _setToCurrent( pageEl ) { - pageEl.classList.add( 'current', 'hidden' ); + _setToCurrent(pageEl) { + pageEl.classList.add('current', 'hidden'); // Was just added, for animation? - pageEl.classList.remove( 'hidden' ); - getAncestors( pageEl, '.or-group, .or-group-data, .or-repeat', '.or' ) - .forEach( el => el.classList.add( 'contains-current' ) ); + pageEl.classList.remove('hidden'); + getAncestors( + pageEl, + '.or-group, .or-group-data, .or-repeat', + '.or' + ).forEach((el) => el.classList.add('contains-current')); this.current = pageEl; }, /** @@ -406,57 +463,60 @@

js/page.js

* @param {Element} pageEl - page element * @param {number} newIndex - new index */ - _flipTo( pageEl, newIndex ) { + _flipTo(pageEl, newIndex) { // if there is a current page (note: if current page was removed it is not null, hence the .closest('html') check) - if ( this.current && this.current.closest( 'html' ) ) { + if (this.current && this.current.closest('html')) { // if current page is not same as pageEl - if ( this.current !== pageEl ) { - this.current.classList.remove( 'current', 'fade-out' ); - getAncestors( this.current, '.or-group, .or-group-data, .or-repeat', '.or' ) - .forEach( el => el.classList.remove( 'contains-current' ) ); - this._pauseMultimedia( this.current ); - this._setToCurrent( pageEl ); - this._focusOnFirstQuestion( pageEl ); - this._toggleButtons( newIndex ); - pageEl.dispatchEvent( events.PageFlip() ); + if (this.current !== pageEl) { + this.current.classList.remove('current', 'fade-out'); + getAncestors( + this.current, + '.or-group, .or-group-data, .or-repeat', + '.or' + ).forEach((el) => el.classList.remove('contains-current')); + this._pauseMultimedia(this.current); + this._setToCurrent(pageEl); + this._focusOnFirstQuestion(pageEl); + this._toggleButtons(newIndex); + pageEl.dispatchEvent(events.PageFlip()); } - } else if ( pageEl ) { - this._setToCurrent( pageEl ); - this._focusOnFirstQuestion( pageEl ); - this._toggleButtons( newIndex ); - pageEl.dispatchEvent( events.PageFlip() ); - pageEl.setAttribute( 'tabindex', 1 ); + } else if (pageEl) { + this._setToCurrent(pageEl); + this._focusOnFirstQuestion(pageEl); + this._toggleButtons(newIndex); + pageEl.dispatchEvent(events.PageFlip()); + pageEl.setAttribute('tabindex', 1); } }, /** * Switches to first page */ _flipToFirst() { - this._flipTo( this.activePages[ 0 ] ); + this._flipTo(this.activePages[0]); }, /** * Switches to last page */ _flipToLast() { - this._flipTo( this.activePages[ this.activePages.length - 1 ] ); + this._flipTo(this.activePages[this.activePages.length - 1]); }, /** * Focuses on first question and scrolls it into view * * @param {Element} pageEl - page element */ - _focusOnFirstQuestion( pageEl ) { - //triggering fake focus in case element cannot be focused (if hidden by widget) - $( pageEl ) - .find( '.question:not(.disabled)' ) - .addBack( '.question:not(.disabled)' ) - .filter( function() { - return $( this ).parentsUntil( '.or', '.disabled' ).length === 0; - } ) - .eq( 0 ) - .find( 'input, select, textarea' ) - .eq( 0 ) - .trigger( 'fakefocus' ); + _focusOnFirstQuestion(pageEl) { + // triggering fake focus in case element cannot be focused (if hidden by widget) + $(pageEl) + .find('.question:not(.disabled)') + .addBack('.question:not(.disabled)') + .filter(function () { + return $(this).parentsUntil('.or', '.disabled').length === 0; + }) + .eq(0) + .find('input, select, textarea') + .eq(0) + .trigger('fakefocus'); // focus on element pageEl.focus(); @@ -468,33 +528,33 @@

js/page.js

* * @param {number} [index] - index of current page */ - _toggleButtons( index = this._getCurrentIndex() ) { - const next = this._getNext( index ); - const prev = this._getPrev( index ); - this.$btnNext.add( this.$btnLast ).toggleClass( 'disabled', !next ); - this.$btnPrev.add( this.$btnFirst ).toggleClass( 'disabled', !prev ); - this.$formFooter.toggleClass( 'end', !next ); + _toggleButtons(index = this._getCurrentIndex()) { + const next = this._getNext(index); + const prev = this._getPrev(index); + this.$btnNext.add(this.$btnLast).toggleClass('disabled', !next); + this.$btnPrev.add(this.$btnFirst).toggleClass('disabled', !prev); + this.$formFooter.toggleClass('end', !next); }, /** * Pauses video and audio from playing when switching to a page. * * @param {Element} pageEl - page element */ - _pauseMultimedia( pageEl ) { - $( pageEl ) - .find( 'audio, video' ) - .each( ( idx, element ) => element.pause() ); + _pauseMultimedia(pageEl) { + $(pageEl) + .find('audio, video') + .each((idx, element) => element.pause()); }, /** * Updates Table of Contents */ _updateToc() { - if ( this.$toc.length ) { + if (this.$toc.length) { // regenerate complete ToC from first enabled question/group label of each page - this.$toc.empty()[ 0 ].append( this.form.toc.getHtmlFragment() ); - this.$toc.closest( '.pages-toc' ).removeClass( 'hide' ); + this.$toc.empty()[0].append(this.form.toc.getHtmlFragment()); + this.$toc.closest('.pages-toc').removeClass('hide'); } - } + }, }; diff --git a/docs/js_plugins.js.html b/docs/js_plugins.js.html index e53c2d573..d51fbf462 100644 --- a/docs/js_plugins.js.html +++ b/docs/js_plugins.js.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -52,7 +52,6 @@

js/plugins.js

*/ import $ from 'jquery'; - /** * Reverses a jQuery collection * diff --git a/docs/js_preload.js.html b/docs/js_preload.js.html index 329e94dff..99c64c939 100644 --- a/docs/js_preload.js.html +++ b/docs/js_preload.js.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -53,61 +53,84 @@

js/preload.js

* @module preloader */ -import events from '../../src/js/event'; +import events from './event'; + export default { /** * Initializes preloader */ init() { - if ( !this.form ) { - throw new Error( 'Preload module not correctly instantiated with form property.' ); + if (!this.form) { + throw new Error( + 'Preload module not correctly instantiated with form property.' + ); } - //these initialize actual preload items - this.form.view.html.querySelectorAll( 'input[data-preload], select[data-preload], textarea[data-preload]' ).forEach( preloadEl => { - const props = this.form.input.getProps( preloadEl ); - const item = preloadEl.dataset && preloadEl.dataset.preload ? preloadEl.dataset.preload.toLowerCase() : undefined; - const param = preloadEl.dataset && preloadEl.dataset.preloadParams ? preloadEl.dataset.preloadParams.toLowerCase() : undefined; - - if ( typeof this[ item ] !== 'undefined' ) { - const dataNode = this.form.model.node( props.path, props.index ); - // If a preload item is placed inside a repeat with repeat-count 0, the node - // doesn't exist and will never get a value (which is correct behavior) - if ( dataNode.getElements().length ) { - const curVal = dataNode.getVal(); - const newVal = this[ item ]( { - param, - curVal, - dataNode - } ); - - dataNode.setVal( newVal, props.xmlType ); + // these initialize actual preload items + this.form.view.html + .querySelectorAll( + 'input[data-preload], select[data-preload], textarea[data-preload]' + ) + .forEach((preloadEl) => { + const props = this.form.input.getProps(preloadEl); + const item = + preloadEl.dataset && preloadEl.dataset.preload + ? preloadEl.dataset.preload.toLowerCase() + : undefined; + const param = + preloadEl.dataset && preloadEl.dataset.preloadParams + ? preloadEl.dataset.preloadParams.toLowerCase() + : undefined; + + if (typeof this[item] !== 'undefined') { + const dataNode = this.form.model.node( + props.path, + props.index + ); + // If a preload item is placed inside a repeat with repeat-count 0, the node + // doesn't exist and will never get a value (which is correct behavior) + if (dataNode.getElements().length) { + const curVal = dataNode.getVal(); + const newVal = this[item]({ + param, + curVal, + dataNode, + }); + + dataNode.setVal(newVal, props.xmlType); + } + } else { + console.warn( + `Preload "${item}" not supported. May or may not be a big deal.` + ); } - } else { - console.warn( `Preload "${item}" not supported. May or may not be a big deal.` ); - } - } ); + }); }, /** * @param {object} o - parameter object * @return {string} evaluated value or error message */ - 'timestamp': function( o ) { + timestamp(o) { let value; const that = this; // when is 'start' or 'end' - if ( o.param === 'start' ) { - return ( o.curVal.length > 0 ) ? o.curVal : this.form.model.evaluate( 'now()', 'string' ); + if (o.param === 'start') { + return o.curVal.length > 0 + ? o.curVal + : this.form.model.evaluate('now()', 'string'); } - if ( o.param === 'end' ) { - //set event handler for each save event (needs to be triggered!) - this.form.view.html.addEventListener( events.BeforeSave().type, () => { - value = that.form.model.evaluate( 'now()', 'string' ); - o.dataNode.setVal( value, 'datetime' ); - } ); - - //TODO: why populate this upon load? - return this.form.model.evaluate( 'now()', 'string' ); + if (o.param === 'end') { + // set event handler for each save event (needs to be triggered!) + this.form.view.html.addEventListener( + events.BeforeSave().type, + () => { + value = that.form.model.evaluate('now()', 'string'); + o.dataNode.setVal(value, 'datetime'); + } + ); + + // TODO: why populate this upon load? + return this.form.model.evaluate('now()', 'string'); } return 'error - unknown timestamp parameter'; @@ -116,17 +139,17 @@

js/preload.js

* @param {object} o - parameter object * @return {string} current value or evaluated value */ - 'date': function( o ) { + date(o) { let today; let year; let month; let day; - if ( o.curVal.length === 0 ) { - today = new Date( this.form.model.evaluate( 'today()', 'string' ) ); - year = today.getFullYear().toString().padStart( 4, '0' ); - month = ( today.getMonth() + 1 ).toString().padStart( 2, '0' ); - day = today.getDate().toString().padStart( 2, '0' ); + if (o.curVal.length === 0) { + today = new Date(this.form.model.evaluate('today()', 'string')); + year = today.getFullYear().toString().padStart(4, '0'); + month = (today.getMonth() + 1).toString().padStart(2, '0'); + day = today.getDate().toString().padStart(2, '0'); return `${year}-${month}-${day}`; } @@ -137,17 +160,18 @@

js/preload.js

* @param {object} o - parameter object * @return {string} current value or evaluated value */ - 'property': function( o ) { + property(o) { let node; // 'deviceid', 'subscriberid', 'simserial', 'phonenumber' - if ( o.curVal.length === 0 ) { - node = this.form.model.node( `instance("__session")/session/context/${o.param}` ); - if ( node.getElements().length ) { + if (o.curVal.length === 0) { + node = this.form.model.node( + `instance("__session")/session/context/${o.param}` + ); + if (node.getElements().length) { return node.getVal(); - } else { - return `no ${o.param} property in enketo`; } + return `no ${o.param} property in enketo`; } return o.curVal; @@ -156,10 +180,12 @@

js/preload.js

* @param {object} o - parameter object * @return {string} current value or evaluated value */ - 'context': function( o ) { + context(o) { // 'application', 'user'?? - if ( o.curVal.length === 0 ) { - return ( o.param === 'application' ) ? 'enketo' : `${o.param} not supported in enketo`; + if (o.curVal.length === 0) { + return o.param === 'application' + ? 'enketo' + : `${o.param} not supported in enketo`; } return o.curVal; @@ -168,8 +194,8 @@

js/preload.js

* @param {object} o - parameter object * @return {string} current value or error message */ - 'patient': function( o ) { - if ( o.curVal.length === 0 ) { + patient(o) { + if (o.curVal.length === 0) { return 'patient preload item not supported in enketo'; } @@ -179,8 +205,8 @@

js/preload.js

* @param {object} o - parameter object * @return {string} current value or error message */ - 'user': function( o ) { - if ( o.curVal.length === 0 ) { + user(o) { + if (o.curVal.length === 0) { return 'user preload item not supported in enketo yet'; } @@ -190,13 +216,16 @@

js/preload.js

* @param {object} o - parameter object * @return {string} current value or evaluated value */ - 'uid': function( o ) { - if ( o.curVal.length === 0 ) { - return this.form.model.evaluate( 'concat("uuid:", uuid())', 'string' ); + uid(o) { + if (o.curVal.length === 0) { + return this.form.model.evaluate( + 'concat("uuid:", uuid())', + 'string' + ); } return o.curVal; - } + }, }; diff --git a/docs/js_print.js.html b/docs/js_print.js.html index 38089d86a..770bbe3b8 100644 --- a/docs/js_print.js.html +++ b/docs/js_print.js.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -54,11 +54,12 @@

js/print.js

*/ import $ from 'jquery'; +import dialog from 'enketo/dialog'; import { MutationsTracker } from './dom-utils'; -let dpi, printStyleSheet; +let dpi; +let printStyleSheet; let printStyleSheetLink; -import dialog from 'enketo/dialog'; /** * @typedef PaperObj @@ -70,19 +71,18 @@

js/print.js

*/ // make sure setDpi is not called until DOM is ready -document.addEventListener( 'DOMContentLoaded', () => setDpi() ); - +document.addEventListener('DOMContentLoaded', () => setDpi()); /** * Calculates the dots per inch and sets the dpi property */ function setDpi() { const dpiO = {}; - const e = document.body.appendChild( document.createElement( 'DIV' ) ); + const e = document.body.appendChild(document.createElement('DIV')); e.style.width = '1in'; e.style.padding = '0'; dpiO.v = e.offsetWidth; - e.parentNode.removeChild( e ); + e.parentNode.removeChild(e); dpi = dpiO.v; } @@ -93,10 +93,10 @@

js/print.js

*/ function getPrintStyleSheet() { // document.styleSheets is an Object not an Array! - for ( const i in document.styleSheets ) { - if ( Object.prototype.hasOwnProperty.call( document.styleSheets, i ) ) { - const sheet = document.styleSheets[ i ]; - if ( sheet.media.mediaText === 'print' ) { + for (const i in document.styleSheets) { + if (Object.prototype.hasOwnProperty.call(document.styleSheets, i)) { + const sheet = document.styleSheets[i]; + if (sheet.media.mediaText === 'print') { return sheet; } } @@ -111,7 +111,7 @@

js/print.js

* @return {Element} stylesheet link HTML element */ function getPrintStyleSheetLink() { - return document.querySelector( 'link[media="print"]' ); + return document.querySelector('link[media="print"]'); } /** @@ -127,7 +127,7 @@

js/print.js

// Chrome: printStyleSheet.media.mediaText = 'all'; // Firefox: - printStyleSheetLink.setAttribute( 'media', 'all' ); + printStyleSheetLink.setAttribute('media', 'all'); return !!printStyleSheet; } @@ -139,13 +139,19 @@

js/print.js

*/ function styleReset() { printStyleSheet.media.mediaText = 'print'; - printStyleSheetLink.setAttribute( 'media', 'print' ); - document.querySelectorAll( '.print-height-adjusted, .print-width-adjusted, .main' ) - .forEach( el => { - el.removeAttribute( 'style' ); - el.classList.remove( 'print-height-adjusted', 'print-width-adjusted' ); - } ); - $( '.back-to-screen-view' ).off( 'click' ).remove(); + printStyleSheetLink.setAttribute('media', 'print'); + document + .querySelectorAll( + '.print-height-adjusted, .print-width-adjusted, .main' + ) + .forEach((el) => { + el.removeAttribute('style'); + el.classList.remove( + 'print-height-adjusted', + 'print-width-adjusted' + ); + }); + $('.back-to-screen-view').off('click').remove(); } /** @@ -155,7 +161,9 @@

js/print.js

* @return {boolean} whether the form definition was defined to use the Grid theme */ function isGrid() { - return /theme-.*grid.*/.test( document.querySelector( 'form.or' ).getAttribute( 'class' ) ); + return /theme-.*grid.*/.test( + document.querySelector('form.or').getAttribute('class') + ); } /** @@ -166,66 +174,75 @@

js/print.js

* @param {number} [delay] - delay in milliseconds, to wait for re-painting to finish. * @return {Promise} Promise that resolves with undefined */ -function fixGrid( paper, delay = 500 ) { +function fixGrid(paper, delay = 500) { const mutationsTracker = new MutationsTracker(); // to ensure cells grow correctly with text-wrapping before fixing heights and widths. - const main = document.querySelector( '.main' ); + const main = document.querySelector('.main'); const cls = 'print-width-adjusted'; - const classChange = mutationsTracker.waitForClassChange( main, cls ); - main.style.width = getPaperPixelWidth( paper ); - main.classList.add( cls ); + const classChange = mutationsTracker.waitForClassChange(main, cls); + main.style.width = getPaperPixelWidth(paper); + main.classList.add(cls); // wait for browser repainting after width change // TODO: may not work, may need to add delay - return classChange - .then( () => { - let row = []; - let rowTop; - const title = document.querySelector( '#form-title' ); - // the -1px adjustment is necessary because the h3 element width is calc(100% + 1px) - const maxWidth = title ? title.offsetWidth - 1 : null; - const els = document.querySelectorAll( '.question:not(.draft), .trigger:not(.draft)' ); - - els.forEach( ( el, index ) => { - const lastElement = index === els.length - 1; - const top = $( el ).offset().top; - rowTop = ( rowTop || rowTop === 0 ) ? rowTop : top; - - if ( top === rowTop ) { - row = row.concat( el ); - } - - // If an element is hidden, top = 0. We still need to trigger a resize on the very last row - // if the last element is hidden, so this is placed outside of the previous if statement - if ( lastElement ) { - _resizeRowElements( row, maxWidth ); - } + return classChange.then(() => { + let row = []; + let rowTop; + const title = document.querySelector('#form-title'); + // the -1px adjustment is necessary because the h3 element width is calc(100% + 1px) + const maxWidth = title ? title.offsetWidth - 1 : null; + const els = document.querySelectorAll( + '.question:not(.draft), .trigger:not(.draft)' + ); + + els.forEach((el, index) => { + const lastElement = index === els.length - 1; + const { top } = $(el).offset(); + rowTop = rowTop || rowTop === 0 ? rowTop : top; + + if (top === rowTop) { + row = row.concat(el); + } - // process row, and start a new row - if ( top > rowTop ) { - _resizeRowElements( row, maxWidth ); + // If an element is hidden, top = 0. We still need to trigger a resize on the very last row + // if the last element is hidden, so this is placed outside of the previous if statement + if (lastElement) { + _resizeRowElements(row, maxWidth); + } - if ( lastElement && !row.includes( el ) ) { - _resizeRowElements( [ el ], maxWidth ); - } else { - // start a new row - row = [ el ]; - rowTop = $( el ).offset().top; - } + // process row, and start a new row + if (top > rowTop) { + _resizeRowElements(row, maxWidth); - } else if ( rowTop < top ) { - console.error( 'unexpected question top position: ', top, 'for element:', el, 'expected >=', rowTop ); + if (lastElement && !row.includes(el)) { + _resizeRowElements([el], maxWidth); + } else { + // start a new row + row = [el]; + rowTop = $(el).offset().top; } - } ); - - return mutationsTracker.waitForQuietness() - .then( () => { - // The need for this 'dumb' delay is unfortunate, but at least the mutationTracker will smartly increase - // the waiting time for larger forms (more mutations). - return new Promise( resolve => setTimeout( resolve, delay ) ); - } ); - } ); + } else if (rowTop < top) { + console.error( + 'unexpected question top position: ', + top, + 'for element:', + el, + 'expected >=', + rowTop + ); + } + }); + + return mutationsTracker.waitForQuietness().then( + () => + // The need for this 'dumb' delay is unfortunate, but at least the mutationTracker will smartly increase + // the waiting time for larger forms (more mutations). + new Promise((resolve) => { + setTimeout(resolve, delay); + }) + ); + }); } /** @@ -233,41 +250,44 @@

js/print.js

* @param {Element} row - row elements * @param {number} maxWidth - maximum width of row */ -function _resizeRowElements( row, maxWidth ) { +function _resizeRowElements(row, maxWidth) { const widths = []; let cumulativeWidth = 0; let maxHeight = 0; - row.forEach( el => { - const width = Number( $( el ).css( 'width' ).replace( 'px', '' ) ); - widths.push( width ); + row.forEach((el) => { + const width = Number($(el).css('width').replace('px', '')); + widths.push(width); cumulativeWidth += width; - } ); + }); // adjusts widths if w-values don't add up to 100% - if ( cumulativeWidth < maxWidth ) { + if (cumulativeWidth < maxWidth) { const diff = maxWidth - cumulativeWidth; - row.forEach( ( el, index ) => { - const width = widths[ index ] + ( widths[ index ] / cumulativeWidth ) * diff; + row.forEach((el, index) => { + const width = + widths[index] + (widths[index] / cumulativeWidth) * diff; // round down to 2 decimals to avoid 100.001% totals - el.style.width = `${Math.floor( ( width * 100 / maxWidth ) * 100 ) / 100}%`; - el.classList.add( 'print-width-adjusted' ); - } ); + el.style.width = `${ + Math.floor(((width * 100) / maxWidth) * 100) / 100 + }%`; + el.classList.add('print-width-adjusted'); + }); } - row.forEach( el => { + row.forEach((el) => { const height = el.offsetHeight; - maxHeight = ( height > maxHeight ) ? height : maxHeight; - } ); + maxHeight = height > maxHeight ? height : maxHeight; + }); - row.forEach( el => { - // unset max height for image-map widget + row.forEach((el) => { + // unset max height for image-map widget // (https://github.com/OpenClinica/enketo-express-oc/issues/363) - if ( !el.classList.contains( 'or-appearance-image-map' ) ) { - el.classList.add( 'print-height-adjusted' ); + if (!el.classList.contains('or-appearance-image-map')) { + el.classList.add('print-height-adjusted'); el.style.height = `${maxHeight}px`; } - } ); + }); } /** @@ -276,66 +296,78 @@

js/print.js

* @param {PaperObj} paper - paper format * @return {string} pixel width string */ -function getPaperPixelWidth( paper ) { +function getPaperPixelWidth(paper) { let printWidth; const FORMATS = { - Letter: [ 8.5, 11 ], - Legal: [ 8.5, 14 ], - Tabloid: [ 11, 17 ], - Ledger: [ 17, 11 ], - A0: [ 33.1, 46.8 ], - A1: [ 23.4, 33.1 ], - A2: [ 16.5, 23.4 ], - A3: [ 11.7, 16.5 ], - A4: [ 8.27, 11.7 ], - A5: [ 5.83, 8.27 ], - A6: [ 4.13, 5.83 ], + Letter: [8.5, 11], + Legal: [8.5, 14], + Tabloid: [11, 17], + Ledger: [17, 11], + A0: [33.1, 46.8], + A1: [23.4, 33.1], + A2: [16.5, 23.4], + A3: [11.7, 16.5], + A4: [8.27, 11.7], + A5: [5.83, 8.27], + A6: [4.13, 5.83], }; - paper.landscape = typeof paper.landscape === 'boolean' ? paper.landscape : paper.orientation === 'landscape'; + paper.landscape = + typeof paper.landscape === 'boolean' + ? paper.landscape + : paper.orientation === 'landscape'; delete paper.orientation; - if ( typeof paper.margin === 'undefined' ) { + if (typeof paper.margin === 'undefined') { paper.margin = 0.4; - } else if ( /^[\d.]+in$/.test( paper.margin.trim() ) ) { - paper.margin = parseFloat( paper.margin, 10 ); - } else if ( /^[\d.]+cm$/.test( paper.margin.trim() ) ) { - paper.margin = parseFloat( paper.margin, 10 ) / 2.54; - } else if ( /^[\d.]+mm$/.test( paper.margin.trim() ) ) { - paper.margin = parseFloat( paper.margin, 10 ) / 25.4; + } else if (/^[\d.]+in$/.test(paper.margin.trim())) { + paper.margin = parseFloat(paper.margin, 10); + } else if (/^[\d.]+cm$/.test(paper.margin.trim())) { + paper.margin = parseFloat(paper.margin, 10) / 2.54; + } else if (/^[\d.]+mm$/.test(paper.margin.trim())) { + paper.margin = parseFloat(paper.margin, 10) / 25.4; } - paper.format = typeof paper.format === 'string' && typeof FORMATS[ paper.format ] !== 'undefined' ? paper.format : 'A4'; - printWidth = ( paper.landscape === true ) ? FORMATS[ paper.format ][ 1 ] : FORMATS[ paper.format ][ 0 ]; - - return `${( printWidth - ( 2 * paper.margin ) ) * dpi}px`; + paper.format = + typeof paper.format === 'string' && + typeof FORMATS[paper.format] !== 'undefined' + ? paper.format + : 'A4'; + printWidth = + paper.landscape === true + ? FORMATS[paper.format][1] + : FORMATS[paper.format][0]; + + return `${(printWidth - 2 * paper.margin) * dpi}px`; } /** * @static */ function openAllDetails() { - document.querySelectorAll( 'details.or-form-guidance.active' ) - .forEach( details => { - if ( details.open ) { + document + .querySelectorAll('details.or-form-guidance.active') + .forEach((details) => { + if (details.open) { details.dataset.previousOpen = true; } else { details.open = true; } - } ); + }); } /** * @static */ function closeAllDetails() { - document.querySelectorAll( 'details.or-form-guidance.active' ) - .forEach( details => { - if ( details.dataset.previousOpen ) { + document + .querySelectorAll('details.or-form-guidance.active') + .forEach((details) => { + if (details.dataset.previousOpen) { delete details.dataset.previousOpen; } else { details.open = false; } - } ); + }); } /** @@ -347,33 +379,42 @@

js/print.js

* @static * @param {string} theme - theme name */ -function print( theme ) { - if ( theme === 'grid' || ( !theme && isGrid() ) ) { +function print(theme) { + if (theme === 'grid' || (!theme && isGrid())) { let swapped = false; - dialog.prompt( 'Enter valid paper format', 'A4' ) - .then( format => { - if ( !format ) { - throw new Error( 'Print cancelled by user.' ); + dialog + .prompt('Enter valid paper format', 'A4') + .then((format) => { + if (!format) { + throw new Error('Print cancelled by user.'); } swapped = styleToAll(); - return fixGrid( { - format - } ); - } ) - .then( window.print ) - .catch( console.error ) - .then( () => { - if ( swapped ) { - setTimeout( styleReset, 500 ); + return fixGrid({ + format, + }); + }) + .then(window.print) + .catch(console.error) + .then(() => { + if (swapped) { + setTimeout(styleReset, 500); } - } ); + }); } else { window.print(); } } -export { print, fixGrid, styleToAll, styleReset, isGrid, openAllDetails, closeAllDetails }; +export { + print, + fixGrid, + styleToAll, + styleReset, + isGrid, + openAllDetails, + closeAllDetails, +};
diff --git a/docs/js_progress.js.html b/docs/js_progress.js.html index 0a343a1ef..5e2cb43f3 100644 --- a/docs/js_progress.js.html +++ b/docs/js_progress.js.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -76,30 +76,37 @@

js/progress.js

* Updates total */ updateTotal() { - this.all = [ ...this.form.view.html.querySelectorAll( '.question:not(.disabled):not(.or-appearance-comment):not(.or-appearance-dn):not(.readonly)' ) ] - .filter( question => !question.closest( '.disabled' ) ); + this.all = [ + ...this.form.view.html.querySelectorAll( + '.question:not(.disabled):not(.or-appearance-comment):not(.or-appearance-dn):not(.readonly)' + ), + ].filter((question) => !question.closest('.disabled')); }, /** * Updates rounded % value of progress and triggers event if changed. * * @param {Element} el - the element that represent the current state of progress */ - update( el ) { + update(el) { let status; - if ( !this.all || !el ) { + if (!this.all || !el) { this.updateTotal(); } this.lastChanged = el || this.lastChanged; - if ( this.lastChanged ) { - status = Math.round( ( ( this.all.indexOf( this.lastChanged.closest( '.question' ) ) + 1 ) * 100 ) / this.all.length ); + if (this.lastChanged) { + status = Math.round( + ((this.all.indexOf(this.lastChanged.closest('.question')) + 1) * + 100) / + this.all.length + ); } // if the current el was removed (inside removed repeat), the status will be 0 - leave unchanged - if ( status > 0 && status !== this.status ) { + if (status > 0 && status !== this.status) { this.status = status; - this.form.view.html.dispatchEvent( events.ProgressUpdate( status ) ); + this.form.view.html.dispatchEvent(events.ProgressUpdate(status)); } }, /** @@ -107,7 +114,7 @@

js/progress.js

*/ get() { return this.status; - } + }, }; diff --git a/docs/js_readonly.js.html b/docs/js_readonly.js.html index 3b1d6cef4..7315bd4fb 100644 --- a/docs/js_readonly.js.html +++ b/docs/js_readonly.js.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -57,26 +57,32 @@

js/readonly.js

* * @param {UpdatedDataNodes} [updated] - The object containing info on updated data nodes. */ - update( updated ) { - const nodes = this.form.getRelatedNodes( 'readonly', '', updated ).get(); - nodes.forEach( node => { - node.closest( '.question' ).classList.add( 'readonly' ); + update(updated) { + const nodes = this.form.getRelatedNodes('readonly', '', updated).get(); + nodes.forEach((node) => { + node.closest('.question').classList.add('readonly'); - const path = this.form.input.getName( node ); - const action = this.form.view.html.querySelector( `[data-setvalue][data-event="xforms-value-changed"][name="${path}"], [data-setgeopoint][data-event="xforms-value-changed"][name="${path}"]` ); + const path = this.form.input.getName(node); + const action = this.form.view.html.querySelector( + `[data-setvalue][data-event="xforms-value-changed"][name="${path}"], [data-setgeopoint][data-event="xforms-value-changed"][name="${path}"]` + ); // Note: the readonly-forced class is added for special readonly views of a form. - const empty = !node.value && !node.dataset.calculate && !action && !node.classList.contains( 'readonly-forced' ); - - node.classList.toggle( 'empty', empty ); - - if( empty ){ - node.setAttribute( 'aria-hidden', 'true' ); - }else{ - node.removeAttribute( 'aria-hidden' ); + const empty = + !node.value && + !node.dataset.calculate && + !action && + !node.classList.contains('readonly-forced'); + + node.classList.toggle('empty', empty); + + if (empty) { + node.setAttribute('aria-hidden', 'true'); + } else { + node.removeAttribute('aria-hidden'); } - } ); - } + }); + }, }; diff --git a/docs/js_relevant.js.html b/docs/js_relevant.js.html index 22144443a..30847e741 100644 --- a/docs/js_relevant.js.html +++ b/docs/js_relevant.js.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -61,45 +61,53 @@

js/relevant.js

* @param {UpdatedDataNodes} [updated] - The object containing info on updated data nodes. * @param {boolean} forceClearNonRelevant - whether to empty the values of non-relevant nodes */ - update( updated, forceClearNonRelevant = false ) { - - if ( !this.form ) { - throw new Error( 'Branch module not correctly instantiated with form property.' ); + update(updated, forceClearNonRelevant = false) { + if (!this.form) { + throw new Error( + 'Branch module not correctly instantiated with form property.' + ); } - const nodes = this.form.getRelatedNodes( 'data-relevant', '', updated ).get(); + const nodes = this.form + .getRelatedNodes('data-relevant', '', updated) + .get(); - this.updateNodes( nodes, forceClearNonRelevant ); + this.updateNodes(nodes, forceClearNonRelevant); }, /** * @param {Array<Element>} nodes - Nodes to update * @param {boolean} forceClearNonRelevant - whether to empty the values of non-relevant nodes */ - updateNodes( nodes, forceClearNonRelevant = false ) { + updateNodes(nodes, forceClearNonRelevant = false) { let branchChange = false; const relevantCache = {}; const alreadyCovered = []; - const clonedRepeatsPresent = this.form.repeatsPresent && this.form.view.html.querySelector( '.or-repeat.clone' ); - - nodes.forEach( node => { + const clonedRepeatsPresent = + this.form.repeatsPresent && + this.form.view.html.querySelector('.or-repeat.clone'); + nodes.forEach((node) => { // Note that node.getAttribute('name') is not the same as p.path for repeated radiobuttons! - if ( alreadyCovered.indexOf( node.getAttribute( 'name' ) ) !== -1 ) { + if (alreadyCovered.indexOf(node.getAttribute('name')) !== -1) { return; } // Since this result is almost certainly not empty, closest() is the most efficient - const branchNode = node.closest( '.or-branch' ); + const branchNode = node.closest('.or-branch'); const p = {}; let cacheIndex = null; - p.relevant = this.form.input.getRelevant( node ); - p.path = this.form.input.getName( node ); + p.relevant = this.form.input.getRelevant(node); + p.path = this.form.input.getName(node); - if ( !branchNode ) { - if ( !closestAncestorUntil( node.parentsUntil( node, '#or-calculated-items', '.or' ) ) ) { - console.error( 'could not find branch node for ', node ); + if (!branchNode) { + if ( + !closestAncestorUntil( + node.parentsUntil(node, '#or-calculated-items', '.or') + ) + ) { + console.error('could not find branch node for ', node); } return; @@ -108,15 +116,25 @@

js/relevant.js

/* * Check if the (calculate without form control) node is part of a repeat that has no instances */ - const pathParts = p.path.split( '/' ); - if ( pathParts.length > 3 ) { - const parentPath = pathParts.splice( 0, pathParts.length - 1 ).join( '/' ); - const parentGroups = [ ...this.form.view.html.querySelectorAll( `.or-group[name="${parentPath}"],.or-group-data[name="${parentPath}"]` ) ] + const pathParts = p.path.split('/'); + if (pathParts.length > 3) { + const parentPath = pathParts + .splice(0, pathParts.length - 1) + .join('/'); + const parentGroups = [ + ...this.form.view.html.querySelectorAll( + `.or-group[name="${parentPath}"],.or-group-data[name="${parentPath}"]` + ), + ] // now remove the groups that have a repeat-info child without repeat instance siblings - .filter( group => getChild( group, '.or-repeat' ) || !getChild( group, '.or-repeat-info' ) ); + .filter( + (group) => + getChild(group, '.or-repeat') || + !getChild(group, '.or-repeat-info') + ); // If the parent doesn't exist in the DOM it means there is a repeat ancestor and there are no instances of that repeat. // Hence that relevant does not need to be evaluated (and would fail otherwise because the context doesn't exist). - if ( parentGroups.length === 0 ) { + if (parentGroups.length === 0) { return; } } @@ -128,15 +146,22 @@

js/relevant.js

* (6-7 seconds of loading time on the bench6 form) */ // TODO: these checks fail miserably for calculated items that do not have a form control - const insideRepeat = clonedRepeatsPresent && closestAncestorUntil( branchNode, '.or-repeat', '.or', ); - const insideRepeatClone = clonedRepeatsPresent && closestAncestorUntil( branchNode, '.or-repeat.clone', '.or' ); + const insideRepeat = + clonedRepeatsPresent && + closestAncestorUntil(branchNode, '.or-repeat', '.or'); + const insideRepeatClone = + clonedRepeatsPresent && + closestAncestorUntil(branchNode, '.or-repeat.clone', '.or'); /* * If the relevant is placed on a group and that group contains repeats with the same name, * but currently has 0 repeats, the context will not be available. This same logic is applied in output.js. */ let context = p.path; - if ( getChild( node, `.or-repeat-info[data-name="${p.path}"]` ) && !getChild( node, `.or-repeat[name="${p.path}"]` ) ) { + if ( + getChild(node, `.or-repeat-info[data-name="${p.path}"]`) && + !getChild(node, `.or-repeat[name="${p.path}"]`) + ) { context = null; } @@ -144,42 +169,58 @@

js/relevant.js

* Determining the index is expensive, so we only do this when the branch is inside a cloned repeat. * It can be safely set to 0 for other branches. */ - p.ind = ( context && insideRepeatClone ) ? this.form.input.getIndex( node ) : 0; + p.ind = + context && insideRepeatClone + ? this.form.input.getIndex(node) + : 0; /* * Caching is only possible for expressions that do not contain relative paths to nodes. * So, first do a *very* aggresive check to see if the expression contains a relative path. * This check assumes that child nodes (e.g. "mychild = 'bob'") are NEVER used in a relevant * expression, which may prove to be incorrect. */ - if ( p.relevant.indexOf( '..' ) === -1 ) { - if ( !insideRepeat ) { + if (p.relevant.indexOf('..') === -1) { + if (!insideRepeat) { cacheIndex = p.relevant; } else { // The path is stripped of the last nodeName to record the context. // This might be dangerous, but until we find a bug, it helps in those forms where one group contains // many sibling questions that each have the same relevant. - cacheIndex = `${p.relevant}__${p.path.substring( 0, p.path.lastIndexOf( '/' ) )}__${p.ind}`; + cacheIndex = `${p.relevant}__${p.path.substring( + 0, + p.path.lastIndexOf('/') + )}__${p.ind}`; } } let result; - if ( cacheIndex && typeof relevantCache[ cacheIndex ] !== 'undefined' ) { - result = relevantCache[ cacheIndex ]; + if ( + cacheIndex && + typeof relevantCache[cacheIndex] !== 'undefined' + ) { + result = relevantCache[cacheIndex]; } else { - result = this.evaluate( p.relevant, context, p.ind ); - relevantCache[ cacheIndex ] = result; + result = this.evaluate(p.relevant, context, p.ind); + relevantCache[cacheIndex] = result; } - if ( !insideRepeat ) { - alreadyCovered.push( node.getAttribute( 'name' ) ); + if (!insideRepeat) { + alreadyCovered.push(node.getAttribute('name')); } - if ( this.process( branchNode, p.path, result, forceClearNonRelevant ) === true ) { + if ( + this.process( + branchNode, + p.path, + result, + forceClearNonRelevant + ) === true + ) { branchChange = true; } - } ); + }); - if ( branchChange ) { - this.form.view.$.trigger( 'changebranch' ); + if (branchChange) { + this.form.view.$.trigger('changebranch'); } }, /** @@ -190,8 +231,13 @@

js/relevant.js

* @param {number} index - index of context node * @return {boolean} result of evaluation */ - evaluate( expr, contextPath, index ) { - const result = this.form.model.evaluate( expr, 'boolean', contextPath, index ); + evaluate(expr, contextPath, index) { + const result = this.form.model.evaluate( + expr, + 'boolean', + contextPath, + index + ); return result; }, @@ -203,12 +249,11 @@

js/relevant.js

* @param {boolean} result - result of relevant evaluation * @param {boolean} forceClearNonRelevant - whether to empty the values of non-relevant nodes */ - process( branchNode, path, result, forceClearNonRelevant = false ) { - if ( result === true ) { - return this.enable( branchNode, path ); - } else { - return this.disable( branchNode, path, forceClearNonRelevant ); + process(branchNode, path, result, forceClearNonRelevant = false) { + if (result === true) { + return this.enable(branchNode, path); } + return this.disable(branchNode, path, forceClearNonRelevant); }, /** @@ -217,8 +262,11 @@

js/relevant.js

* @param {Element} branchNode - branch node * @return {boolean} whether branch is currently relevant */ - selfRelevant( branchNode ) { - return !branchNode.classList.contains( 'disabled' ) && !branchNode.classList.contains( 'pre-init' ); + selfRelevant(branchNode) { + return ( + !branchNode.classList.contains('disabled') && + !branchNode.classList.contains('pre-init') + ); }, /** @@ -228,24 +276,24 @@

js/relevant.js

* @param {string} path - path of branch node * @return {boolean} whether the relevant changed as a result of this action */ - enable( branchNode, path ) { + enable(branchNode, path) { let change = false; - if ( !this.selfRelevant( branchNode ) ) { + if (!this.selfRelevant(branchNode)) { change = true; - branchNode.classList.remove( 'disabled', 'pre-init' ); + branchNode.classList.remove('disabled', 'pre-init'); // Update calculated items, both individual question or descendants of group - this.form.calc.update( { - relevantPath: path - } ); - this.form.itemset.update( { - relevantPath: path - } ); + this.form.calc.update({ + relevantPath: path, + }); + this.form.itemset.update({ + relevantPath: path, + }); // Update outputs that are children of branch // TODO this re-evaluates all outputs in the form which is not efficient! this.form.output.update(); - this.form.widgets.enable( branchNode ); - this.activate( branchNode ); + this.form.widgets.enable(branchNode); + this.activate(branchNode); } return change; @@ -259,17 +307,21 @@

js/relevant.js

* @param {boolean} forceClearNonRelevant - whether to empty the values of non-relevant nodes * @return {boolean} whether the relevancy changed as a result of this action */ - disable( branchNode, path, forceClearNonRelevant ) { - const neverEnabled = branchNode.classList.contains( 'pre-init' ); + disable(branchNode, path, forceClearNonRelevant) { + const neverEnabled = branchNode.classList.contains('pre-init'); let changed = false; - if ( neverEnabled || this.selfRelevant( branchNode ) || forceClearNonRelevant ) { + if ( + neverEnabled || + this.selfRelevant(branchNode) || + forceClearNonRelevant + ) { changed = true; - if ( forceClearNonRelevant ) { - this.clear( branchNode, path ); + if (forceClearNonRelevant) { + this.clear(branchNode, path); } - this.deactivate( branchNode ); + this.deactivate(branchNode); } return changed; @@ -281,32 +333,44 @@

js/relevant.js

* @param {Element} branchNode - branch node * @param {string} path - path of branch node */ - clear( branchNode, path ) { + clear(branchNode, path) { // A change event ensures the model is updated // An inputupdate event is required to update widgets - this.form.input.clear( branchNode, events.Change(), events.InputUpdate() ); + this.form.input.clear( + branchNode, + events.Change(), + events.InputUpdate() + ); // Update calculated items if branch is a group // We exclude question branches here because those will have been cleared already in the previous line. - if ( branchNode.matches( '.or-group, .or-group-data' ) ) { - this.form.calc.update( { - relevantPath: path - }, '', true ); + if (branchNode.matches('.or-group, .or-group-data')) { + this.form.calc.update( + { + relevantPath: path, + }, + '', + true + ); } }, /** * @param {Element} branchNode - branch node * @param {boolean} bool - value to set disabled property to */ - setDisabledProperty( branchNode, bool ) { + setDisabledProperty(branchNode, bool) { const type = branchNode.nodeName.toLowerCase(); - if ( type === 'label' ) { - getChildren( branchNode, 'input, select, textarea' ).forEach( el => el.disabled = bool ); - } else if ( type === 'fieldset' || type === 'section' ) { + if (type === 'label') { + getChildren(branchNode, 'input, select, textarea').forEach( + (el) => (el.disabled = bool) + ); + } else if (type === 'fieldset' || type === 'section') { // TODO: a <section> cannot be disabled like this branchNode.disabled = bool; } else { - branchNode.querySelectorAll( 'fieldset, input, select, textarea' ).forEach( el => el.disabled = bool ); + branchNode + .querySelectorAll('fieldset, input, select, textarea') + .forEach((el) => (el.disabled = bool)); } }, /** @@ -315,8 +379,8 @@

js/relevant.js

* * @param {Element} branchNode - branch node */ - activate( branchNode ) { - this.setDisabledProperty( branchNode, false ); + activate(branchNode) { + this.setDisabledProperty(branchNode, false); }, /** * Deactivates form controls. @@ -324,11 +388,11 @@

js/relevant.js

* * @param {Element} branchNode - branch node */ - deactivate( branchNode ) { - branchNode.classList.add( 'disabled' ); - this.form.widgets.disable( branchNode ); - this.setDisabledProperty( branchNode, true ); - } + deactivate(branchNode) { + branchNode.classList.add('disabled'); + this.form.widgets.disable(branchNode); + this.setDisabledProperty(branchNode, true); + }, }; diff --git a/docs/js_repeat.js.html b/docs/js_repeat.js.html index cb1275cb2..fba7b387b 100644 --- a/docs/js_repeat.js.html +++ b/docs/js_repeat.js.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -61,12 +61,18 @@

js/repeat.js

*/ import $ from 'jquery'; -import events from './event'; import { t } from 'enketo/translator'; import dialog from 'enketo/dialog'; -import { getSiblingElements, getSiblingElement, getChildren, getSiblingElementsAndSelf } from './dom-utils'; -import { isStaticItemsetFromSecondaryInstance } from './itemset'; import config from 'enketo/config'; +import events from './event'; +import { + getSiblingElements, + getSiblingElement, + getChildren, + getSiblingElementsAndSelf, +} from './dom-utils'; +import { isStaticItemsetFromSecondaryInstance } from './itemset'; + const disableFirstRepeatRemoval = config.repeatOrdinals === true; export default { @@ -79,39 +85,52 @@

js/repeat.js

this.staticLists = []; - if ( !this.form ) { - throw new Error( 'Repeat module not correctly instantiated with form property.' ); + if (!this.form) { + throw new Error( + 'Repeat module not correctly instantiated with form property.' + ); } - $repeatInfos = this.form.view.$.find( '.or-repeat-info' ); + $repeatInfos = this.form.view.$.find('.or-repeat-info'); this.templates = {}; // Add repeat numbering elements $repeatInfos - .siblings( '.or-repeat' ) - .prepend( '<span class="repeat-number"></span>' ) + .siblings('.or-repeat') + .prepend('<span class="repeat-number"></span>') // add empty class for calculation-only repeats .addBack() - .filter( function() { + .filter(function () { // remove whitespace - if ( this.firstChild && this.firstChild.nodeType === 3 ) { + if (this.firstChild && this.firstChild.nodeType === 3) { this.firstChild.textContent = ''; } - return !this.querySelector( '.question' ); - } ) - .addClass( 'empty' ); + return !this.querySelector('.question'); + }) + .addClass('empty'); // Add repeat buttons - $repeatInfos.filter( '*:not([data-repeat-fixed]):not([data-repeat-count])' ) - .append( '<button type="button" class="btn btn-default add-repeat-btn"><i class="icon icon-plus"> </i></button>' ) - .siblings( '.or-repeat' ) - .append( `<div class="repeat-buttons"><button type="button" ${disableFirstRepeatRemoval ? ' disabled ' : ' '}class="btn btn-default remove"><i class="icon icon-minus"> </i></button></div>` ); + $repeatInfos + .filter('*:not([data-repeat-fixed]):not([data-repeat-count])') + .append( + '<button type="button" class="btn btn-default add-repeat-btn"><i class="icon icon-plus"> </i></button>' + ) + .siblings('.or-repeat') + .append( + `<div class="repeat-buttons"><button type="button" ${ + disableFirstRepeatRemoval ? ' disabled ' : ' ' + }class="btn btn-default remove"><i class="icon icon-minus"> </i></button></div>` + ); /** * The model also requires storing repeat templates for repeats that do not have a jr:template. * Since the model has no knowledge of which node is a repeat, we direct this here. */ - this.form.model.extractFakeTemplates( $repeatInfos.map( function() { - return this.dataset.name; - } ).get() ); + this.form.model.extractFakeTemplates( + $repeatInfos + .map(function () { + return this.dataset.name; + }) + .get() + ); /** * Clone all repeats to serve as templates @@ -119,59 +138,74 @@

js/repeat.js

* * Widgets not yet initialized. Values not yet set. */ - $repeatInfos.siblings( '.or-repeat' ).reverse().each( function() { - const templateEl = this.cloneNode( true ); - const xPath = templateEl.getAttribute( 'name' ); - this.remove(); - $( templateEl ).removeClass( 'contains-current current' ).find( '.current' ).removeClass( 'current' ); - // Clear all values (this is required for setvalue/odk-instance-first-load populated values) - // The default values will be added anyway in the repeats.add function. - that.form.input.clear( templateEl ); - that.templates[ xPath ] = templateEl; - } ); - - $repeatInfos.reverse() - .each( function() { + $repeatInfos + .siblings('.or-repeat') + .reverse() + .each(function () { + const templateEl = this.cloneNode(true); + const xPath = templateEl.getAttribute('name'); + this.remove(); + $(templateEl) + .removeClass('contains-current current') + .find('.current') + .removeClass('current'); + // Clear all values (this is required for setvalue/odk-instance-first-load populated values) + // The default values will be added anyway in the repeats.add function. + that.form.input.clear(templateEl); + that.templates[xPath] = templateEl; + }); + + $repeatInfos + .reverse() + .each(function () { // don't do nested repeats here, they will be dealt with recursively - if ( !$( this ).closest( '.or-repeat' ).length ) { - that.updateDefaultFirstRepeatInstance( this ); + if (!$(this).closest('.or-repeat').length) { + that.updateDefaultFirstRepeatInstance(this); } - } ) + }) // If there is no repeat-count attribute, check how many repeat instances // are in the model, and update view if necessary. .get() - .forEach( that.updateViewInstancesFromModel.bind( this ) ); + .forEach(that.updateViewInstancesFromModel.bind(this)); // delegated handlers (strictly speaking not required, but checked for doubling of events -> OK) - this.form.view.$.on( 'click', 'button.add-repeat-btn:enabled', function() { - // Create a clone - that.add( $( this ).closest( '.or-repeat-info' )[ 0 ] ); - - // Prevent default - return false; - } ); - this.form.view.$.on( 'click', 'button.remove:enabled', function() { - that.confirmDelete( this.closest( '.or-repeat' ) ); + this.form.view.$.on( + 'click', + 'button.add-repeat-btn:enabled', + function () { + // Create a clone + that.add($(this).closest('.or-repeat-info')[0]); + + // Prevent default + return false; + } + ); + this.form.view.$.on('click', 'button.remove:enabled', function () { + that.confirmDelete(this.closest('.or-repeat')); - //prevent default + // prevent default return false; - } ); + }); this.countUpdate(); return true; }, // Make this function overwritable - confirmDelete( repeatEl ) { + confirmDelete(repeatEl) { const that = this; - dialog.confirm( { heading: t( 'confirm.repeatremove.heading' ), msg: t( 'confirm.repeatremove.msg' ) } ) - .then( confirmed => { - if ( confirmed ) { - //remove clone - that.remove( $( repeatEl ) ); + dialog + .confirm({ + heading: t('confirm.repeatremove.heading'), + msg: t('confirm.repeatremove.msg'), + }) + .then((confirmed) => { + if (confirmed) { + // remove clone + that.remove($(repeatEl)); } - } ) - .catch( console.error ); + }) + .catch(console.error); }, /* * Obtains the 0-based absolute index of the provided repeat or repeat-info element @@ -185,24 +219,32 @@

js/repeat.js

* The repeat-info concept was added in the context of supporting zero instances of a repeat. It would be good * to expand on its documentation. */ - getIndex( el ) { - if ( !el || !this.form.repeatsPresent ) { + getIndex(el) { + if (!el || !this.form.repeatsPresent) { return 0; } - const isInfoElement = el.classList.contains( 'or-repeat-info' ); + const isInfoElement = el.classList.contains('or-repeat-info'); - const toCountSelector = isInfoElement ? `.or-repeat-info[data-name="${el.dataset.name}"]` : `.or-repeat[name="${el.getAttribute( 'name' )}"]`; - let predecessorCount = isInfoElement ? 0 : Number( el.querySelector( '.repeat-number' ).textContent ) - 1; + const toCountSelector = isInfoElement + ? `.or-repeat-info[data-name="${el.dataset.name}"]` + : `.or-repeat[name="${el.getAttribute('name')}"]`; + let predecessorCount = isInfoElement + ? 0 + : Number(el.querySelector('.repeat-number').textContent) - 1; let checkEl = el; - while ( checkEl ) { - while ( checkEl.previousElementSibling && checkEl.previousElementSibling.matches( '.or-repeat' ) ) { + while (checkEl) { + while ( + checkEl.previousElementSibling && + checkEl.previousElementSibling.matches('.or-repeat') + ) { checkEl = checkEl.previousElementSibling; - predecessorCount += checkEl.querySelectorAll( toCountSelector ).length; + predecessorCount += + checkEl.querySelectorAll(toCountSelector).length; } const parent = checkEl.parentElement; - checkEl = parent ? parent.closest( '.or-repeat' ) : null; + checkEl = parent ? parent.closest('.or-repeat') : null; } return predecessorCount; @@ -213,21 +255,36 @@

js/repeat.js

* @param {Element} repeatInfo - repeatInfo element * @return {number} length of repeat series in model */ - updateViewInstancesFromModel( repeatInfo ) { + updateViewInstancesFromModel(repeatInfo) { const repeatPath = repeatInfo.dataset.name; // All we need is to find out in which series we are. - const repeatSeriesIndex = this.getIndex( repeatInfo ); - const repInModelSeries = this.form.model.getRepeatSeries( repeatPath, repeatSeriesIndex ); - const repInViewSeries = getSiblingElements( repeatInfo, '.or-repeat' ); + const repeatSeriesIndex = this.getIndex(repeatInfo); + const repInModelSeries = this.form.model.getRepeatSeries( + repeatPath, + repeatSeriesIndex + ); + const repInViewSeries = getSiblingElements(repeatInfo, '.or-repeat'); // First rep is already included (by XSLT transformation) - if ( repInModelSeries.length > repInViewSeries.length ) { - this.add( repeatInfo, repInModelSeries.length - repInViewSeries.length, 'model' ); + if (repInModelSeries.length > repInViewSeries.length) { + this.add( + repeatInfo, + repInModelSeries.length - repInViewSeries.length, + 'model' + ); // Now check the repeat counts of all the descendants of this repeat and its new siblings // Note: not tested with triple-nested repeats, but probably taking the better safe-than-sorry approach, // so should be okay except for performance. - getSiblingElements( repeatInfo, '.or-repeat' ) - .reduce( ( acc, current ) => acc.concat( [ ...current.querySelectorAll( '.or-repeat-info:not([data-repeat-count])' ) ] ), [] ) - .forEach( this.updateViewInstancesFromModel.bind( this ) ); + getSiblingElements(repeatInfo, '.or-repeat') + .reduce( + (acc, current) => + acc.concat([ + ...current.querySelectorAll( + '.or-repeat-info:not([data-repeat-count])' + ), + ]), + [] + ) + .forEach(this.updateViewInstancesFromModel.bind(this)); } return repInModelSeries.length; @@ -237,18 +294,34 @@

js/repeat.js

* * @param {Element} repeatInfo - repeatInfo element */ - updateDefaultFirstRepeatInstance( repeatInfo ) { + updateDefaultFirstRepeatInstance(repeatInfo) { const repeatPath = repeatInfo.dataset.name; - if ( !this.form.model.data.instanceStr && !this.templates[ repeatPath ].classList.contains( 'or-appearance-minimal' ) ) { - const repeatSeriesIndex = this.getIndex( repeatInfo ); - const repeatSeriesInModel = this.form.model.getRepeatSeries( repeatPath, repeatSeriesIndex ); - if ( repeatSeriesInModel.length === 0 ) { - this.add( repeatInfo, 1, 'magic' ); + if ( + !this.form.model.data.instanceStr && + !this.templates[repeatPath].classList.contains( + 'or-appearance-minimal' + ) + ) { + const repeatSeriesIndex = this.getIndex(repeatInfo); + const repeatSeriesInModel = this.form.model.getRepeatSeries( + repeatPath, + repeatSeriesIndex + ); + if (repeatSeriesInModel.length === 0) { + this.add(repeatInfo, 1, 'magic'); } - getSiblingElements( repeatInfo, '.or-repeat' ) - .reduce( ( acc, current ) => acc.concat( [ ...current.querySelectorAll( '.or-repeat-info:not([data-repeat-count])' ) ] ), [] ) - .forEach( this.updateDefaultFirstRepeatInstance.bind( this ) ); + getSiblingElements(repeatInfo, '.or-repeat') + .reduce( + (acc, current) => + acc.concat([ + ...current.querySelectorAll( + '.or-repeat-info:not([data-repeat-count])' + ), + ]), + [] + ) + .forEach(this.updateDefaultFirstRepeatInstance.bind(this)); } }, /** @@ -256,10 +329,10 @@

js/repeat.js

* * @param {Element} repeatInfo - repeatInfo element */ - updateRepeatInstancesFromCount( repeatInfo ) { + updateRepeatInstancesFromCount(repeatInfo) { const repCountPath = repeatInfo.dataset.repeatCount || ''; - if ( !repCountPath ) { + if (!repCountPath) { return; } @@ -269,26 +342,53 @@

js/repeat.js

* is determined in a node inside the parent repeat. To do so we use the repeat comment in model as context. */ const repPath = repeatInfo.dataset.name; - let numRepsInCount = this.form.model.evaluate( repCountPath, 'number', this.form.model.getRepeatCommentSelector( repPath ), this.getIndex( repeatInfo ), true ); - numRepsInCount = isNaN( numRepsInCount ) ? 0 : numRepsInCount; - const numRepsInView = getSiblingElements( repeatInfo, `.or-repeat[name="${repPath}"]` ).length; + let numRepsInCount = this.form.model.evaluate( + repCountPath, + 'number', + this.form.model.getRepeatCommentSelector(repPath), + this.getIndex(repeatInfo), + true + ); + numRepsInCount = isNaN(numRepsInCount) ? 0 : numRepsInCount; + const numRepsInView = getSiblingElements( + repeatInfo, + `.or-repeat[name="${repPath}"]` + ).length; let toCreate = numRepsInCount - numRepsInView; - if ( toCreate > 0 ) { - this.add( repeatInfo, toCreate, 'count' ); - } else if ( toCreate < 0 ) { - toCreate = Math.abs( toCreate ) >= numRepsInView ? -numRepsInView + ( disableFirstRepeatRemoval ? 1 : 0 ) : toCreate; - for ( ; toCreate < 0; toCreate++ ) { - const $last = $( repeatInfo ).siblings( '.or-repeat' ).last(); - this.remove( $last ); + if (toCreate > 0) { + this.add(repeatInfo, toCreate, 'count'); + } else if (toCreate < 0) { + toCreate = + Math.abs(toCreate) >= numRepsInView + ? -numRepsInView + (disableFirstRepeatRemoval ? 1 : 0) + : toCreate; + for (; toCreate < 0; toCreate++) { + const $last = $(repeatInfo).siblings('.or-repeat').last(); + this.remove($last); } } // Now check the repeat counts of all the descendants of this repeat and its new siblings, level-by-level. // TODO: this does not find .or-repeat > .or-repeat (= unusual syntax) - getSiblingElementsAndSelf( repeatInfo, '.or-repeat' ) - .reduce( ( acc, current ) => acc.concat( getChildren( current, '.or-group, .or-group-data' ) ), [] ) - .reduce( ( acc, current ) => acc.concat( getChildren( current, '.or-repeat-info[data-repeat-count]' ) ), [] ) - .forEach( this.updateRepeatInstancesFromCount.bind( this ) ); + getSiblingElementsAndSelf(repeatInfo, '.or-repeat') + .reduce( + (acc, current) => + acc.concat( + getChildren(current, '.or-group, .or-group-data') + ), + [] + ) + .reduce( + (acc, current) => + acc.concat( + getChildren( + current, + '.or-repeat-info[data-repeat-count]' + ) + ), + [] + ) + .forEach(this.updateRepeatInstancesFromCount.bind(this)); }, /** * Checks whether repeat count value has been updated and updates repeat instances @@ -296,9 +396,11 @@

js/repeat.js

* * @param {UpdatedDataNodes} updated - The object containing info on updated data nodes. */ - countUpdate( updated = {} ) { - const repeatInfos = this.form.getRelatedNodes( 'data-repeat-count', '.or-repeat-info', updated ).get(); - repeatInfos.forEach( this.updateRepeatInstancesFromCount.bind( this ) ); + countUpdate(updated = {}) { + const repeatInfos = this.form + .getRelatedNodes('data-repeat-count', '.or-repeat-info', updated) + .get(); + repeatInfos.forEach(this.updateRepeatInstancesFromCount.bind(this)); }, /** * Clone a repeat group/node. @@ -308,9 +410,9 @@

js/repeat.js

* @param {string=} trigger - The trigger ('magic', 'user', 'count', 'model') * @return {boolean} Cloning success/failure outcome. */ - add( repeatInfo, toCreate = 1, trigger = 'user' ) { - if ( !repeatInfo ) { - console.error( 'Nothing to clone' ); + add(repeatInfo, toCreate = 1, trigger = 'user') { + if (!repeatInfo) { + console.error('Nothing to clone'); return false; } @@ -318,171 +420,192 @@

js/repeat.js

let repeatIndex; const repeatPath = repeatInfo.dataset.name; - let repeats = getSiblingElements( repeatInfo, '.or-repeat' ); - let clone = this.templates[ repeatPath ].cloneNode( true ); + const repeats = getSiblingElements(repeatInfo, '.or-repeat'); + let clone = this.templates[repeatPath].cloneNode(true); // Determine the index of the repeat series. - let repeatSeriesIndex = this.getIndex( repeatInfo ); - let modelRepeatSeriesLength = this.form.model.getRepeatSeries( repeatPath, repeatSeriesIndex ).length; + const repeatSeriesIndex = this.getIndex(repeatInfo); + let modelRepeatSeriesLength = this.form.model.getRepeatSeries( + repeatPath, + repeatSeriesIndex + ).length; // Determine the index of the repeat inside its series const prevSibling = repeatInfo.previousElementSibling; - let repeatIndexInSeries = prevSibling && prevSibling.classList.contains( 'or-repeat' ) ? - Number( prevSibling.querySelector( '.repeat-number' ).textContent ) : 0; + let repeatIndexInSeries = + prevSibling && prevSibling.classList.contains('or-repeat') + ? Number( + prevSibling.querySelector('.repeat-number').textContent + ) + : 0; // Add required number of repeats - for ( let i = 0; i < toCreate; i++ ) { + for (let i = 0; i < toCreate; i++) { // Fix names of radio button groups - clone.querySelectorAll( '.option-wrapper' ).forEach( this.fixRadioName ); - this.processDatalists( clone.querySelectorAll( 'datalist' ), repeatInfo ); + clone + .querySelectorAll('.option-wrapper') + .forEach(this.fixRadioName); + this.processDatalists( + clone.querySelectorAll('datalist'), + repeatInfo + ); // Insert the clone - repeatInfo.parentElement.insertBefore( clone, repeatInfo ); + repeatInfo.parentElement.insertBefore(clone, repeatInfo); - if ( repeatIndexInSeries > 0 ) { + if (repeatIndexInSeries > 0) { // Also add the clone class for all 2+ numbers as this is // used for performance optimization in several places. - clone.classList.add( 'clone' ); + clone.classList.add('clone'); } // Update the repeat number - clone.querySelector( '.repeat-number' ).textContent = repeatIndexInSeries + 1; + clone.querySelector('.repeat-number').textContent = + repeatIndexInSeries + 1; // Update the variable containing the view repeats in the current series. - repeats.push( clone ); + repeats.push(clone); // Create a repeat in the model if it doesn't already exist - if ( repeats.length > modelRepeatSeriesLength ) { - this.form.model.addRepeat( repeatPath, repeatSeriesIndex ); + if (repeats.length > modelRepeatSeriesLength) { + this.form.model.addRepeat(repeatPath, repeatSeriesIndex); modelRepeatSeriesLength++; } // This is the index of the new repeat in relation to all other repeats of the same name, // even if they are in different series. - repeatIndex = repeatIndex || this.getIndex( clone ); + repeatIndex = repeatIndex || this.getIndex(clone); const updated = { repeatIndex, repeatPath, trigger, - cloned: true + cloned: true, }; // The odk-new-repeat event (before the event that triggers re-calculations etc) - if ( trigger === 'user' || trigger === 'count' ) { - clone.dispatchEvent( events.NewRepeat( updated ) ); + if (trigger === 'user' || trigger === 'count') { + clone.dispatchEvent(events.NewRepeat(updated)); } // This will trigger setting default values, calculations, readonly, relevancy, language updates, and automatic page flips. - clone.dispatchEvent( events.AddRepeat( updated ) ); + clone.dispatchEvent(events.AddRepeat(updated)); // Initialize widgets in clone after default values have been set - if ( this.form.widgetsInitialized ) { - this.form.widgets.init( $( clone ), this.form.options ); + if (this.form.widgetsInitialized) { + this.form.widgets.init($(clone), this.form.options); } // now create the first instance of any nested repeats if necessary - clone.querySelectorAll( '.or-repeat-info:not([data-repeat-count])' ) - .forEach( this.updateDefaultFirstRepeatInstance.bind( this ) ); + clone + .querySelectorAll('.or-repeat-info:not([data-repeat-count])') + .forEach(this.updateDefaultFirstRepeatInstance.bind(this)); - clone = this.templates[ repeatPath ].cloneNode( true ); + clone = this.templates[repeatPath].cloneNode(true); repeatIndex++; repeatIndexInSeries++; } // enable or disable + and - buttons - this.toggleButtons( repeatInfo ); + this.toggleButtons(repeatInfo); return true; }, - remove( $repeat ) { + remove($repeat) { const that = this; - const $next = $repeat.next( '.or-repeat, .or-repeat-info' ); - const repeatPath = $repeat.attr( 'name' ); - const repeatIndex = this.getIndex( $repeat[ 0 ] ); - const repeatInfo = $repeat.siblings( '.or-repeat-info' )[ 0 ]; + const $next = $repeat.next('.or-repeat, .or-repeat-info'); + const repeatPath = $repeat.attr('name'); + const repeatIndex = this.getIndex($repeat[0]); + const repeatInfo = $repeat.siblings('.or-repeat-info')[0]; $repeat.remove(); - that.numberRepeats( repeatInfo ); - that.toggleButtons( repeatInfo ); + that.numberRepeats(repeatInfo); + that.toggleButtons(repeatInfo); // Trigger the removerepeat on the next repeat or repeat-info(always present) // so that removerepeat handlers know where the repeat was removed - $next[ 0 ].dispatchEvent( events.RemoveRepeat() ); + $next[0].dispatchEvent(events.RemoveRepeat()); // Now remove the data node - that.form.model.node( repeatPath, repeatIndex ).remove(); - + that.form.model.node(repeatPath, repeatIndex).remove(); }, - fixRadioName( element ) { - const random = Math.floor( ( Math.random() * 10000000 ) + 1 ); - element.querySelectorAll( 'input[type="radio"]' ) - .forEach( el => { - el.setAttribute( 'name', random ); - } ); + fixRadioName(element) { + const random = Math.floor(Math.random() * 10000000 + 1); + element.querySelectorAll('input[type="radio"]').forEach((el) => { + el.setAttribute('name', random); + }); }, - fixDatalistId( element ) { - const newId = element.id + Math.floor( ( Math.random() * 10000000 ) + 1 ); - element.parentNode.querySelector( `input[list="${element.id}"]` ).setAttribute( 'list', newId ); + fixDatalistId(element) { + const newId = element.id + Math.floor(Math.random() * 10000000 + 1); + element.parentNode + .querySelector(`input[list="${element.id}"]`) + .setAttribute('list', newId); element.id = newId; }, - processDatalists( datalists, repeatInfo ) { - datalists.forEach( datalist => { - const template = datalist.querySelector( '.itemset-template[data-items-path]' ); + processDatalists(datalists, repeatInfo) { + datalists.forEach((datalist) => { + const template = datalist.querySelector( + '.itemset-template[data-items-path]' + ); const expr = template ? template.dataset.itemsPath : null; - if ( !isStaticItemsetFromSecondaryInstance( expr ) ) { - this.fixDatalistId( datalist ); + if (!isStaticItemsetFromSecondaryInstance(expr)) { + this.fixDatalistId(datalist); } else { - const id = datalist.id; - const input = getSiblingElement( datalist, 'input[list]' ); + const { id } = datalist; + const input = getSiblingElement(datalist, 'input[list]'); - if ( input ) { + if (input) { // For very long static datalists, a huge performance improvement can be achieved, by using the // same datalist for all repeat instances that use it. - if ( this.staticLists.includes( id ) ) { + if (this.staticLists.includes(id)) { datalist.remove(); } else { // Let all identical input[list] questions amongst all repeat instances use the same // datalist by moving it under repeatInfo. // It will survive removal of all repeat instances. const parent = datalist.parentElement; - const name = input.name; - - const dl = parent.querySelector( 'datalist' ); - const detachedList = parent.removeChild( dl ); - detachedList.setAttribute( 'data-name', name ); - repeatInfo.appendChild( detachedList ); - - const translations = parent.querySelector( '.or-option-translations' ); - const detachedTranslations = parent.removeChild( translations ); - detachedTranslations.setAttribute( 'data-name', name ); - repeatInfo.appendChild( detachedTranslations ); - - const labels = parent.querySelector( '.itemset-labels' ); - const detachedLabels = parent.removeChild( labels ); - detachedLabels.setAttribute( 'data-name', name ); - repeatInfo.appendChild( detachedLabels ); - - this.staticLists.push( id ); - //input.classList.add( 'shared' ); + const { name } = input; + + const dl = parent.querySelector('datalist'); + const detachedList = parent.removeChild(dl); + detachedList.setAttribute('data-name', name); + repeatInfo.appendChild(detachedList); + + const translations = parent.querySelector( + '.or-option-translations' + ); + const detachedTranslations = + parent.removeChild(translations); + detachedTranslations.setAttribute('data-name', name); + repeatInfo.appendChild(detachedTranslations); + + const labels = parent.querySelector('.itemset-labels'); + const detachedLabels = parent.removeChild(labels); + detachedLabels.setAttribute('data-name', name); + repeatInfo.appendChild(detachedLabels); + + this.staticLists.push(id); + // input.classList.add( 'shared' ); } } } - } ); + }); }, - toggleButtons( repeatInfo ) { - $( repeatInfo ) - .siblings( '.or-repeat' ) - .children( '.repeat-buttons' ) - .find( 'button.remove' ) - .prop( 'disabled', false ) + toggleButtons(repeatInfo) { + $(repeatInfo) + .siblings('.or-repeat') + .children('.repeat-buttons') + .find('button.remove') + .prop('disabled', false) .first() - .prop( 'disabled', disableFirstRepeatRemoval ); + .prop('disabled', disableFirstRepeatRemoval); + }, + numberRepeats(repeatInfo) { + $(repeatInfo) + .siblings('.or-repeat') + .each((idx, repeat) => { + $(repeat) + .children('.repeat-number') + .text(idx + 1); + }); }, - numberRepeats( repeatInfo ) { - $( repeatInfo ) - .siblings( '.or-repeat' ) - .each( ( idx, repeat ) => { - $( repeat ).children( '.repeat-number' ).text( idx + 1 ); - } ); - } }; diff --git a/docs/js_required.js.html b/docs/js_required.js.html index 8af717520..2a95bd6eb 100644 --- a/docs/js_required.js.html +++ b/docs/js_required.js.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -61,37 +61,51 @@

js/required.js

* * @param {UpdatedDataNodes} [updated] - The object containing info on updated data nodes. */ - update( updated /*, filter*/ ) { + update(updated /* , filter */) { const that = this; // A "required" update will never result in a node value change so the expression evaluation result can be cached fairly aggressively. const requiredCache = {}; - if ( !this.form ) { - throw new Error( 'Required module not correctly instantiated with form property.' ); + if (!this.form) { + throw new Error( + 'Required module not correctly instantiated with form property.' + ); } - const $nodes = this.form.getRelatedNodes( 'data-required', '', updated ); - const repeatClonesPresent = this.form.repeatsPresent && this.form.view.html.querySelector( '.or-repeat.clone' ); + const $nodes = this.form.getRelatedNodes('data-required', '', updated); + const repeatClonesPresent = + this.form.repeatsPresent && + this.form.view.html.querySelector('.or-repeat.clone'); - $nodes.each( function() { - const $input = $( this ); + $nodes.each(function () { + const $input = $(this); const input = this; - const requiredExpr = that.form.input.getRequired( input ); - const path = that.form.input.getName( input ); + const requiredExpr = that.form.input.getRequired(input); + const path = that.form.input.getName(input); // Minimize index determination because it is expensive. - const index = repeatClonesPresent ? that.form.input.getIndex( input ) : 0; + const index = repeatClonesPresent + ? that.form.input.getIndex(input) + : 0; // The path is stripped of the last nodeName to record the context. // This might be dangerous, but until we find a bug, it improves performance a lot in those forms where one group contains // many sibling questions that each have the same required expression. - const cacheIndex = `${requiredExpr}__${path.substring( 0, path.lastIndexOf( '/' ) )}__${index}`; - - if ( typeof requiredCache[ cacheIndex ] === 'undefined' ) { - requiredCache[ cacheIndex ] = that.form.model.node( path, index ).isRequired( requiredExpr ); + const cacheIndex = `${requiredExpr}__${path.substring( + 0, + path.lastIndexOf('/') + )}__${index}`; + + if (typeof requiredCache[cacheIndex] === 'undefined') { + requiredCache[cacheIndex] = that.form.model + .node(path, index) + .isRequired(requiredExpr); } - $input.closest( '.question' ).find( '.required' ).toggleClass( 'hide', !requiredCache[ cacheIndex ] ); - } ); - } + $input + .closest('.question') + .find('.required') + .toggleClass('hide', !requiredCache[cacheIndex]); + }); + }, }; diff --git a/docs/js_sniffer.js.html b/docs/js_sniffer.js.html index ff397394e..833cc7e51 100644 --- a/docs/js_sniffer.js.html +++ b/docs/js_sniffer.js.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -49,7 +49,7 @@

js/sniffer.js

/**
  * @module sniffer
- **/
+ * */
 
 const ua = navigator.userAgent;
 const pf = navigator.platform;
@@ -59,42 +59,45 @@ 

js/sniffer.js

/** * @namespace os - **/ + * */ const os = { /** * @type {string} - **/ + * */ get ios() { // in iOS13, the default Safari setting is 'Request Desktop Site' to be On. // The platform and useragent no longer show iPad/iPhone/iPod // so we use a trick that will work for a while until MacOs gets touchscreen support. - return /iPad|iPhone|iPod/i.test( pf ) || ( /Mac/i.test( pf ) && document.documentElement.ontouchstart !== undefined ); + return ( + /iPad|iPhone|iPod/i.test(pf) || + (/Mac/i.test(pf) && + document.documentElement.ontouchstart !== undefined) + ); }, /** * @type {string} - **/ + * */ get android() { - return /android/i.test( ua ); + return /android/i.test(ua); }, /** * @type {string} - **/ + * */ get macos() { - return /Mac/.test( ua ); + return /Mac/.test(ua); }, }; - /** * @namespace browser - **/ + * */ const browser = { - /**ua + /** ua * @type {string} - **/ + * */ get safari() { - return /^((?!chrome|android).)*safari/i.test( ua ); - } + return /^((?!chrome|android).)*safari/i.test(ua); + }, }; export { os, browser }; diff --git a/docs/js_support.js.html b/docs/js_support.js.html index d1ff90fad..dbb260b6b 100644 --- a/docs/js_support.js.html +++ b/docs/js_support.js.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -59,34 +59,34 @@

js/support.js

let mobile = false; // test input types -[ 'date', 'datetime-local', 'time', 'month' ].forEach( inputType => { - const input = document.createElement( 'input' ); - input.setAttribute( 'type', inputType ); - inputTypes[ inputType ] = input.type !== 'text'; -} ); +['date', 'datetime-local', 'time', 'month'].forEach((inputType) => { + const input = document.createElement('input'); + input.setAttribute('type', inputType); + inputTypes[inputType] = input.type !== 'text'; +}); // The word 'touch' has become misleading. It should be considered 'small mobile' including tablets. -if ( os.ios || os.android ) { +if (os.ios || os.android) { mobile = true; - document.documentElement.classList.add( 'touch' ); + document.documentElement.classList.add('touch'); } export default { /** * @type {Array<string>} - **/ + * */ get inputTypes() { return inputTypes; }, /** * @type {boolean} - **/ + * */ get touch() { return mobile; }, - set touch( val ) { + set touch(val) { mobile = val; - } + }, };
diff --git a/docs/js_toc.js.html b/docs/js_toc.js.html index 40c9ca832..9f116be70 100644 --- a/docs/js_toc.js.html +++ b/docs/js_toc.js.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -71,36 +71,52 @@

js/toc.js

*/ generateTocItems() { this.tocItems = []; - const tocElements = [ ...this.form.view.$[ 0 ].querySelectorAll( '.question:not([role="comment"]), .or-group' ) ] - .filter( tocEl => { - return !tocEl.closest( '.disabled' ) && - ( tocEl.matches( '.question' ) || tocEl.querySelector( '.question:not(.disabled)' ) || + const tocElements = [ + ...this.form.view.$[0].querySelectorAll( + '.question:not([role="comment"]), .or-group' + ), + ] + .filter( + (tocEl) => + !tocEl.closest('.disabled') && + (tocEl.matches('.question') || + tocEl.querySelector('.question:not(.disabled)') || // or-repeat-info is only considered a page by itself if it has no sibling repeats // When there are siblings repeats, we use CSS trickery to show the + button underneath the last // repeat. - ( tocEl.matches( '.or-repeat-info' ) && !getSiblingElement( tocEl, '.or-repeat' ) ) ); - } ) - .filter( tocEl => !tocEl.classList.contains( 'or-repeat-info' ) ); - tocElements.forEach( ( element, index ) => { - const groupParents = getAncestors( element, '.or-group' ); - this.tocItems.push( { - element: element, + (tocEl.matches('.or-repeat-info') && + !getSiblingElement(tocEl, '.or-repeat'))) + ) + .filter((tocEl) => !tocEl.classList.contains('or-repeat-info')); + tocElements.forEach((element, index) => { + const groupParents = getAncestors(element, '.or-group'); + this.tocItems.push({ + element, level: groupParents.length, - parent: groupParents.length > 0 ? groupParents[ groupParents.length - 1 ] : null, + parent: + groupParents.length > 0 + ? groupParents[groupParents.length - 1] + : null, tocId: index, - tocParentId: null - } ); - } ); - - this._maxTocLevel = Math.max.apply( Math, this.tocItems.map( el => el.level ) ); - const newTocParents = this.tocItems.filter( item => item.level < this._maxTocLevel && item.element.classList.contains( 'or-group' ) ); - - this.tocItems.forEach( item => { - const parentItem = newTocParents.find( parent => item.parent === parent.element ); - if ( parentItem ) { + tocParentId: null, + }); + }); + + this._maxTocLevel = Math.max(...this.tocItems.map((el) => el.level)); + const newTocParents = this.tocItems.filter( + (item) => + item.level < this._maxTocLevel && + item.element.classList.contains('or-group') + ); + + this.tocItems.forEach((item) => { + const parentItem = newTocParents.find( + (parent) => item.parent === parent.element + ); + if (parentItem) { item.tocParentId = parentItem.tocId; } - } ); + }); }, /** * Generate ToC Html Fragment @@ -114,26 +130,40 @@

js/toc.js

let currentTocLevel = 0; do { - const currentTocLevelItems = this.tocItems.filter( item => item.level === currentTocLevel ); + const currentTocLevelItems = this.tocItems.filter( + (item) => item.level === currentTocLevel + ); - if ( currentTocLevel === 0 ) { - this._buildTocHtmlList( currentTocLevelItems, toc ); + if (currentTocLevel === 0) { + this._buildTocHtmlList(currentTocLevelItems, toc); } else { - const currentLevelParentIds = [ ...new Set( currentTocLevelItems.map( item => item.tocParentId ) ) ]; - - currentLevelParentIds.forEach( parentId => { - const tocList = document.createElement( 'ul' ); - const currentLTocevelItemsWithSameIds = currentTocLevelItems.filter( item => item.tocParentId === parentId ); - - this._buildTocHtmlList( currentLTocevelItemsWithSameIds, tocList ); - - const tocParent = toc.querySelectorAll( '[tocId="' + parentId + '"]' )[ 0 ]; - tocParent.appendChild( tocList ); - } ); + const currentLevelParentIds = [ + ...new Set( + currentTocLevelItems.map((item) => item.tocParentId) + ), + ]; + + currentLevelParentIds.forEach((parentId) => { + const tocList = document.createElement('ul'); + const currentLTocevelItemsWithSameIds = + currentTocLevelItems.filter( + (item) => item.tocParentId === parentId + ); + + this._buildTocHtmlList( + currentLTocevelItemsWithSameIds, + tocList + ); + + const tocParent = toc.querySelectorAll( + `[tocId="${parentId}"]` + )[0]; + tocParent.appendChild(tocList); + }); } currentTocLevel++; - } while ( currentTocLevel <= this._maxTocLevel ); + } while (currentTocLevel <= this._maxTocLevel); return toc; }, @@ -142,18 +172,21 @@

js/toc.js

* * @param {Element} el - HTML element that serves as page */ - _getTitle( el ) { + _getTitle(el) { let tocItemText; - const labelEl = el.querySelector( '.question-label.active' ); - if ( labelEl ) { + const labelEl = el.querySelector('.question-label.active'); + if (labelEl) { tocItemText = labelEl.textContent; } else { - const hintEl = el.querySelector( '.or-hint.active' ); - if ( hintEl ) { + const hintEl = el.querySelector('.or-hint.active'); + if (hintEl) { tocItemText = hintEl.textContent; } } - tocItemText = tocItemText && tocItemText.length > 20 ? `${tocItemText.substring( 0,20 )}...` : tocItemText; + tocItemText = + tocItemText && tocItemText.length > 20 + ? `${tocItemText.substring(0, 20)}...` + : tocItemText; return tocItemText; }, @@ -163,37 +196,42 @@

js/toc.js

* @param {Array<object>} items - ToC list of items * @param {Element} appendTo - HTML Element to append ToC list to */ - _buildTocHtmlList( items, appendTo ) { - if ( items.length > 0 ) { - items.forEach( item => { - const tocListItem = document.createElement( 'li' ); - if ( item.element.classList.contains( 'or-group' ) ) { - const groupTocTitle = document.createElement( 'summary' ); - groupTocTitle.textContent = this._getTitle( item.element ) || `[${item.tocId + 1}]`; - - const groupToc = document.createElement( 'details' ); - groupToc.setAttribute( 'tocId', item.tocId ); - if ( item.tocParentId !== null ) { - groupToc.setAttribute( 'tocParentId', item.tocParentId ); + _buildTocHtmlList(items, appendTo) { + if (items.length > 0) { + items.forEach((item) => { + const tocListItem = document.createElement('li'); + if (item.element.classList.contains('or-group')) { + const groupTocTitle = document.createElement('summary'); + groupTocTitle.textContent = + this._getTitle(item.element) || `[${item.tocId + 1}]`; + + const groupToc = document.createElement('details'); + groupToc.setAttribute('tocId', item.tocId); + if (item.tocParentId !== null) { + groupToc.setAttribute('tocParentId', item.tocParentId); } - groupToc.append( groupTocTitle ); + groupToc.append(groupTocTitle); - tocListItem.append( groupToc ); + tocListItem.append(groupToc); } else { - const a = document.createElement( 'a' ); - a.textContent = this._getTitle( item.element ) || `[${item.tocId + 1}]`; - - tocListItem.setAttribute( 'tocId', item.tocId ); - tocListItem.setAttribute( 'role', 'pageLink' ); - if ( item.tocParentId !== null ) { - tocListItem.setAttribute( 'tocParentId', item.tocParentId ); + const a = document.createElement('a'); + a.textContent = + this._getTitle(item.element) || `[${item.tocId + 1}]`; + + tocListItem.setAttribute('tocId', item.tocId); + tocListItem.setAttribute('role', 'pageLink'); + if (item.tocParentId !== null) { + tocListItem.setAttribute( + 'tocParentId', + item.tocParentId + ); } - tocListItem.append( a ); + tocListItem.append(a); } - appendTo.append( tocListItem ); - } ); + appendTo.append(tocListItem); + }); } - } + }, }; diff --git a/docs/js_translated-error.js.html b/docs/js_translated-error.js.html index 3371364b9..809a6fca6 100644 --- a/docs/js_translated-error.js.html +++ b/docs/js_translated-error.js.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -56,12 +56,12 @@

js/translated-error.js

* @param {string} translationKey - translation key * @param {*} translationOptions - translation options */ -function TranslatedError( message, translationKey, translationOptions ) { +function TranslatedError(message, translationKey, translationOptions) { this.message = message; this.translationKey = translationKey; this.translationOptions = translationOptions; } -TranslatedError.prototype = Object.create( Error.prototype ); +TranslatedError.prototype = Object.create(Error.prototype); TranslatedError.prototype.name = 'TranslatedError'; export default TranslatedError; diff --git a/docs/js_type-def.js.html b/docs/js_type-def.js.html index b54cfe26a..109b47905 100644 --- a/docs/js_type-def.js.html +++ b/docs/js_type-def.js.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -48,11 +48,18 @@

js/type-def.js

/**
+ * @typedef ExternalInstance
+ * @property {string} id
+ * @property {string} src
+ * @property {string | Document} xml
+ */
+
+/**
  * @typedef FormDataObj
  * @property {string} modelStr -  XML Model as string
  * @property {string} [instanceStr] - (partial) XML instance to load
  * @property {boolean} [submitted] - Flag to indicate whether data was submitted before
- * @property {object} [external] - Array of external data objects, required for each external data instance in the XForm
+ * @property {Array<ExternalInstance | null | undefined>} [external] - Array of external data objects, required for each external data instance in the XForm
  * @property {string} [external.id] - ID of external instance
  * @property {string} [external.xmlStr] - XML string of external instance content
  */
@@ -70,7 +77,6 @@ 

js/type-def.js

* @typedef {object} jQuery * @description jQuery collection */ -
diff --git a/docs/js_types.js.html b/docs/js_types.js.html index 10724d9ed..24b14044a 100644 --- a/docs/js_types.js.html +++ b/docs/js_types.js.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -53,7 +53,10 @@

js/types.js

* @module types */ -import { getTimezoneOffsetAsTime, toISOLocalString, } from 'openrosa-xpath-evaluator/src/date-extensions'; +import { + getTimezoneOffsetAsTime, + toISOLocalString, +} from 'openrosa-xpath-evaluator/src/date-extensions'; import { isNumber } from './utils'; import { time } from './format'; @@ -64,55 +67,59 @@

js/types.js

/** * @namespace */ - 'string': { + string: { /** * @param {string} x - value * @return {string} converted value */ - convert( x ) { - return x.replace( /^\s+$/, '' ); + convert(x) { + return x.replace(/^\s+$/, ''); }, - //max length of type string is 255 chars.Convert( truncate ) silently ? + // max length of type string is 255 chars.Convert( truncate ) silently ? /** * @return {boolean} always `true` */ validate() { return true; - } + }, }, /** * @namespace */ - 'select': { + select: { /** * @return {boolean} always `true` */ validate() { return true; - } + }, }, /** * @namespace */ - 'select1': { + select1: { /** * @return {boolean} always `true` */ validate() { return true; - } + }, }, /** * @namespace */ - 'decimal': { + decimal: { /** * @param {number|string} x - value * @return {number} converted value */ - convert( x ) { - const num = Number( x ); - if ( isNaN( num ) || num === Number.POSITIVE_INFINITY || num === Number.NEGATIVE_INFINITY ) { + convert(x) { + const num = Number(x); + if ( + isNaN(num) || + num === Number.POSITIVE_INFINITY || + num === Number.NEGATIVE_INFINITY + ) { // Comply with XML schema decimal type that has no special values. '' is our only option. return ''; } @@ -123,58 +130,76 @@

js/types.js

* @param {number|string} x - value * @return {boolean} whether value is valid */ - validate( x ) { - const num = Number( x ); - - return !isNaN( num ) && num !== Number.POSITIVE_INFINITY && num !== Number.NEGATIVE_INFINITY; - } + validate(x) { + const num = Number(x); + + return ( + !isNaN(num) && + num !== Number.POSITIVE_INFINITY && + num !== Number.NEGATIVE_INFINITY + ); + }, }, /** * @namespace */ - 'int': { + int: { /** * @param {number|string} x - value * @return {number} converted value */ - convert( x ) { - const num = Number( x ); - if ( isNaN( num ) || num === Number.POSITIVE_INFINITY || num === Number.NEGATIVE_INFINITY ) { + convert(x) { + const num = Number(x); + if ( + isNaN(num) || + num === Number.POSITIVE_INFINITY || + num === Number.NEGATIVE_INFINITY + ) { // Comply with XML schema int type that has no special values. '' is our only option. return ''; } - return ( num >= 0 ) ? Math.floor( num ) : -Math.floor( Math.abs( num ) ); + return num >= 0 ? Math.floor(num) : -Math.floor(Math.abs(num)); }, /** * @param {number|string} x - value * @return {boolean} whether value is valid */ - validate( x ) { - const num = Number( x ); - - return !isNaN( num ) && num !== Number.POSITIVE_INFINITY && num !== Number.NEGATIVE_INFINITY && Math.round( num ) === num && num.toString() === x.toString(); - } + validate(x) { + const num = Number(x); + + return ( + !isNaN(num) && + num !== Number.POSITIVE_INFINITY && + num !== Number.NEGATIVE_INFINITY && + Math.round(num) === num && + num.toString() === x.toString() + ); + }, }, /** * @namespace */ - 'date': { + date: { /** * @param {string} x - value * @return {boolean} whether value is valid */ - validate( x ) { + validate(x) { const pattern = /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/; - const segments = pattern.exec( x ); - if ( segments && segments.length === 4 ) { - const year = Number( segments[ 1 ] ); - const month = Number( segments[ 2 ] ) - 1; - const day = Number( segments[ 3 ] ); - const date = new Date( year, month, day ); + const segments = pattern.exec(x); + if (segments && segments.length === 4) { + const year = Number(segments[1]); + const month = Number(segments[2]) - 1; + const day = Number(segments[3]); + const date = new Date(year, month, day); // Do not approve automatic JavaScript conversion of invalid dates such as 2017-12-32 - return date.getFullYear() === year && date.getMonth() === month && date.getDate() === day; + return ( + date.getFullYear() === year && + date.getMonth() === month && + date.getDate() === day + ); } return false; @@ -183,72 +208,86 @@

js/types.js

* @param {number|string} x - value * @return {string} converted value */ - convert( x ) { - if ( isNumber( x ) ) { + convert(x) { + if (isNumber(x)) { // The XPath expression "2012-01-01" + 2 returns a number of days in XPath. - const date = new Date( x * 24 * 60 * 60 * 1000 ); - - return date.toString() === 'Invalid Date' ? - '' : `${date.getFullYear().toString().padStart( 4, '0' )}-${( date.getMonth() + 1 ).toString().padStart( 2, '0' )}-${date.getDate().toString().padStart( 2, '0' )}`; - } else { - // For both dates and datetimes - // If it's a datetime, we can quite safely assume it's in the local timezone, and therefore we can simply chop off - // the time component. - if ( /[0-9]T[0-9]/.test( x ) ) { - x = x.split( 'T' )[ 0 ]; - } - - return this.validate( x ) ? x : ''; + const date = new Date(x * 24 * 60 * 60 * 1000); + + return date.toString() === 'Invalid Date' + ? '' + : `${date.getFullYear().toString().padStart(4, '0')}-${( + date.getMonth() + 1 + ) + .toString() + .padStart(2, '0')}-${date + .getDate() + .toString() + .padStart(2, '0')}`; + } + // For both dates and datetimes + // If it's a datetime, we can quite safely assume it's in the local timezone, and therefore we can simply chop off + // the time component. + if (/[0-9]T[0-9]/.test(x)) { + x = x.split('T')[0]; } - } + + return this.validate(x) ? x : ''; + }, }, /** * @namespace */ - 'datetime': { + datetime: { /** * @param {string} x - value * @return {boolean} whether value is valid */ - validate( x ) { - const parts = x.split( 'T' ); - if ( parts.length === 2 ) { - return types.date.validate( parts[ 0 ] ) && types.time.validate( parts[ 1 ], false ); + validate(x) { + const parts = x.split('T'); + if (parts.length === 2) { + return ( + types.date.validate(parts[0]) && + types.time.validate(parts[1], false) + ); } - return types.date.validate( parts[ 0 ] ); + return types.date.validate(parts[0]); }, /** * @param {number|string} x - value * @return {string} converted value */ - convert( x ) { + convert(x) { let date = 'Invalid Date'; - const parts = x.split( 'T' ); - if ( isNumber( x ) ) { + const parts = x.split('T'); + if (isNumber(x)) { // The XPath expression "2012-01-01T01:02:03+01:00" + 2 returns a number of days in XPath. - date = new Date( x * 24 * 60 * 60 * 1000 ); - } else if ( /[0-9]T[0-9]/.test( x ) && parts.length === 2 ) { - const convertedDate = types.date.convert( parts[ 0 ] ); + date = new Date(x * 24 * 60 * 60 * 1000); + } else if (/[0-9]T[0-9]/.test(x) && parts.length === 2) { + const convertedDate = types.date.convert(parts[0]); // The milliseconds are optional for datetime (and shouldn't be added) - const convertedTime = types.time.convert( parts[ 1 ], false ); - if ( convertedDate && convertedTime ) { + const convertedTime = types.time.convert(parts[1], false); + if (convertedDate && convertedTime) { return `${convertedDate}T${convertedTime}`; } } else { - const convertedDate = types.date.convert( parts[ 0 ] ); - if ( convertedDate ) { - return `${convertedDate}T00:00:00.000${getTimezoneOffsetAsTime( new Date() )}`; + const convertedDate = types.date.convert(parts[0]); + if (convertedDate) { + return `${convertedDate}T00:00:00.000${getTimezoneOffsetAsTime( + new Date() + )}`; } } - return date.toString() !== 'Invalid Date' ? toISOLocalString( date ) : ''; - } + return date.toString() !== 'Invalid Date' + ? toISOLocalString(date) + : ''; + }, }, /** * @namespace */ - 'time': { + time: { // Note that it's okay if the validate function is stricter than the spec, // (for timezone offset), as long as the convertor automatically converts // to a valid time. @@ -257,32 +296,42 @@

js/types.js

* @param {boolean} [requireMillis] - whether milliseconds are required * @return {boolean} whether value is valid */ - validate( x, requireMillis ) { - let m = x.match( /^(\d\d):(\d\d):(\d\d)\.\d\d\d(\+|-)(\d\d):(\d\d)$/ ); + validate(x, requireMillis) { + let m = x.match( + /^(\d\d):(\d\d):(\d\d)\.\d\d\d(\+|-)(\d\d):(\d\d)$/ + ); - requireMillis = typeof requireMillis !== 'boolean' ? true : requireMillis; + requireMillis = + typeof requireMillis !== 'boolean' ? true : requireMillis; - if ( !m && !requireMillis ) { - m = x.match( /^(\d\d):(\d\d):(\d\d)(\+|-)(\d\d):(\d\d)$/ ); + if (!m && !requireMillis) { + m = x.match(/^(\d\d):(\d\d):(\d\d)(\+|-)(\d\d):(\d\d)$/); } - if ( !m ) { + if (!m) { return false; } // no need to convert to numbers since we know they are number strings - return m[ 1 ] < 24 && m[ 1 ] >= 0 && - m[ 2 ] < 60 && m[ 2 ] >= 0 && - m[ 3 ] < 60 && m[ 3 ] >= 0 && - m[ 5 ] < 24 && m[ 5 ] >= 0 && // this could be tighter - m[ 6 ] < 60 && m[ 6 ] >= 0; // this is probably either 0 or 30 + return ( + m[1] < 24 && + m[1] >= 0 && + m[2] < 60 && + m[2] >= 0 && + m[3] < 60 && + m[3] >= 0 && + m[5] < 24 && + m[5] >= 0 && // this could be tighter + m[6] < 60 && + m[6] >= 0 + ); // this is probably either 0 or 30 }, /** * @param {string} x - value * @param {boolean} [requireMillis] - whether milliseconds are required * @return {string} converted value */ - convert( x, requireMillis ) { + convert(x, requireMillis) { let date; const o = {}; let parts; @@ -292,45 +341,56 @@

js/types.js

let offset; const timeAppearsCorrect = /^[0-9]{1,2}:[0-9]{1,2}(:[0-9.]*)?/; - requireMillis = typeof requireMillis !== 'boolean' ? true : requireMillis; + requireMillis = + typeof requireMillis !== 'boolean' ? true : requireMillis; - if ( !timeAppearsCorrect.test( x ) ) { + if (!timeAppearsCorrect.test(x)) { // An XPath expression would return a datetime string since there is no way to request a timeValue. // We can test this by trying to convert to a date. - date = new Date( x ); - if ( date.toString() !== 'Invalid Date' ) { - x = `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}.${date.getMilliseconds()}${getTimezoneOffsetAsTime( date )}`; + date = new Date(x); + if (date.toString() !== 'Invalid Date') { + x = `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}.${date.getMilliseconds()}${getTimezoneOffsetAsTime( + date + )}`; } else { return ''; } } - parts = x.toString().split( /(\+|-|Z)/ ); + parts = x.toString().split(/(\+|-|Z)/); // We're using a 'capturing group' here, so the + or - is included!. - if ( parts.length < 1 ) { + if (parts.length < 1) { return ''; } - time = parts[ 0 ].split( ':' ); - tz = parts[ 2 ] ? [ parts[ 1 ] ].concat( parts[ 2 ].split( ':' ) ) : ( parts[ 1 ] === 'Z' ? [ '+', '00', '00' ] : [] ); + time = parts[0].split(':'); + tz = parts[2] + ? [parts[1]].concat(parts[2].split(':')) + : parts[1] === 'Z' + ? ['+', '00', '00'] + : []; - o.hours = time[ 0 ].padStart( 2, '0' ); - o.minutes = time[ 1 ].padStart( 2, '0' ); + o.hours = time[0].padStart(2, '0'); + o.minutes = time[1].padStart(2, '0'); - secs = time[ 2 ] ? time[ 2 ].split( '.' ) : [ '00' ]; + secs = time[2] ? time[2].split('.') : ['00']; - o.seconds = secs[ 0 ]; - o.milliseconds = secs[ 1 ] || ( requireMillis ? '000' : undefined ); + o.seconds = secs[0]; + o.milliseconds = secs[1] || (requireMillis ? '000' : undefined); - if ( tz.length === 0 ) { - offset = getTimezoneOffsetAsTime( new Date() ); + if (tz.length === 0) { + offset = getTimezoneOffsetAsTime(new Date()); } else { - offset = `${tz[0] + tz[1].padStart( 2, '0' )}:${tz[2] ? tz[2].padStart( 2, '0' ) : '00'}`; + offset = `${tz[0] + tz[1].padStart(2, '0')}:${ + tz[2] ? tz[2].padStart(2, '0') : '00' + }`; } - x = `${o.hours}:${o.minutes}:${o.seconds}${o.milliseconds ? `.${o.milliseconds}` : ''}${offset}`; + x = `${o.hours}:${o.minutes}:${o.seconds}${ + o.milliseconds ? `.${o.milliseconds}` : '' + }${offset}`; - return this.validate( x, requireMillis ) ? x : ''; + return this.validate(x, requireMillis) ? x : ''; }, /** * converts "11:30 AM", and "11:30 ", and "11:30 上午" to: "11:30" @@ -339,116 +399,134 @@

js/types.js

* @param {string} x - value * @return {string} converted value */ - convertMeridian( x ) { + convertMeridian(x) { x = x.trim(); - if ( time.hasMeridian( x ) ) { - const parts = x.split( ' ' ); - const timeParts = parts[ 0 ].split( ':' ); - if ( parts.length > 0 ) { + if (time.hasMeridian(x)) { + const parts = x.split(' '); + const timeParts = parts[0].split(':'); + if (parts.length > 0) { // This will only work for latin numbers but that should be fine because that's what the widget supports. - if ( parts[ 1 ] === time.pmNotation ) { - timeParts[ 0 ] = ( ( Number( timeParts[ 0 ] ) % 12 ) + 12 ).toString().padStart( 2, '0' ); - } else if ( parts[ 1 ] === time.amNotation ) { - timeParts[ 0 ] = ( Number( timeParts[ 0 ] ) % 12 ).toString().padStart( 2, '0' ); + if (parts[1] === time.pmNotation) { + timeParts[0] = ((Number(timeParts[0]) % 12) + 12) + .toString() + .padStart(2, '0'); + } else if (parts[1] === time.amNotation) { + timeParts[0] = (Number(timeParts[0]) % 12) + .toString() + .padStart(2, '0'); } - x = timeParts.join( ':' ); + x = timeParts.join(':'); } } return x; - } + }, }, /** * @namespace */ - 'barcode': { + barcode: { /** * @return {boolean} always `true` */ validate() { return true; - } + }, }, /** * @namespace */ - 'geopoint': { + geopoint: { /** * @param {string} x - value * @return {boolean} whether value is valid */ - validate( x ) { - const coords = x.toString().trim().split( ' ' ); + validate(x) { + const coords = x.toString().trim().split(' '); // Note that longitudes from -180 to 180 are problematic when recording points close to the international // dateline. They are therefore set from -360 to 360 (circumventing Earth twice, I think) which is // an arbitrary limit. https://github.com/kobotoolbox/enketo-express/issues/1033 - return ( coords[ 0 ] !== '' && coords[ 0 ] >= -90 && coords[ 0 ] <= 90 ) && - ( coords[ 1 ] !== '' && coords[ 1 ] >= -360 && coords[ 1 ] <= 360 ) && - ( typeof coords[ 2 ] === 'undefined' || !isNaN( coords[ 2 ] ) ) && - ( typeof coords[ 3 ] === 'undefined' || ( !isNaN( coords[ 3 ] ) && coords[ 3 ] >= 0 ) ); + return ( + coords[0] !== '' && + coords[0] >= -90 && + coords[0] <= 90 && + coords[1] !== '' && + coords[1] >= -360 && + coords[1] <= 360 && + (typeof coords[2] === 'undefined' || !isNaN(coords[2])) && + (typeof coords[3] === 'undefined' || + (!isNaN(coords[3]) && coords[3] >= 0)) + ); }, /** * @param {string} x - value * @return {string} converted value */ - convert( x ) { + convert(x) { return x.toString().trim(); - } + }, }, /** * @namespace */ - 'geotrace': { + geotrace: { /** * @param {string} x - value * @return {boolean} whether value is valid */ - validate( x ) { - const geopoints = x.toString().split( ';' ); + validate(x) { + const geopoints = x.toString().split(';'); - return geopoints.length >= 2 && geopoints.every( geopoint => types.geopoint.validate( geopoint ) ); + return ( + geopoints.length >= 2 && + geopoints.every((geopoint) => types.geopoint.validate(geopoint)) + ); }, /** * @param {string} x - value * @return {string} converted value */ - convert( x ) { + convert(x) { return x.toString().trim(); - } + }, }, /** * @namespace */ - 'geoshape': { + geoshape: { /** * @param {string} x - value * @return {boolean} whether value is valid */ - validate( x ) { - const geopoints = x.toString().split( ';' ); - - return geopoints.length >= 4 && ( geopoints[ 0 ] === geopoints[ geopoints.length - 1 ] ) && geopoints.every( geopoint => types.geopoint.validate( geopoint ) ); + validate(x) { + const geopoints = x.toString().split(';'); + + return ( + geopoints.length >= 4 && + geopoints[0] === geopoints[geopoints.length - 1] && + geopoints.every((geopoint) => types.geopoint.validate(geopoint)) + ); }, /** * @param {string} x - value * @return {string} converted value */ - convert( x ) { + convert(x) { return x.toString().trim(); - } + }, }, /** * @namespace */ - 'binary': { + binary: { /** * @return {boolean} always `true` */ validate() { return true; - } - } + }, + }, }; export default types; diff --git a/docs/js_utils.js.html b/docs/js_utils.js.html index 61bd57519..9b1009c34 100644 --- a/docs/js_utils.js.html +++ b/docs/js_utils.js.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -63,39 +63,39 @@

js/utils.js

* @param {string} func - The function name to search for * @return {Array<Array<string, any>>} The result array, where each result is an array containing the function call and array of arguments. */ -function parseFunctionFromExpression( expr, func ) { +function parseFunctionFromExpression(expr, func) { let result; - const findFunc = new RegExp( `${func}\\s*\\(`, 'g' ); + const findFunc = new RegExp(`${func}\\s*\\(`, 'g'); const results = []; - if ( !expr || !func ) { + if (!expr || !func) { return results; } - while ( ( result = findFunc.exec( expr ) ) !== null ) { + while ((result = findFunc.exec(expr)) !== null) { const args = []; let openBrackets = 1; - let start = result.index; + const start = result.index; let argStart = findFunc.lastIndex; let index = argStart - 1; - while ( openBrackets !== 0 && index < expr.length ) { + while (openBrackets !== 0 && index < expr.length) { index++; - if ( expr[ index ] === '(' ) { + if (expr[index] === '(') { openBrackets++; - } else if ( expr[ index ] === ')' ) { + } else if (expr[index] === ')') { openBrackets--; - } else if ( expr[ index ] === ',' && openBrackets === 1 ) { - args.push( expr.substring( argStart, index ).trim() ); + } else if (expr[index] === ',' && openBrackets === 1) { + args.push(expr.substring(argStart, index).trim()); argStart = index + 1; } } // add last argument - if ( argStart < index ) { - args.push( expr.substring( argStart, index ).trim() ); + if (argStart < index) { + args.push(expr.substring(argStart, index).trim()); } // add [ 'function( a ,b)', ['a','b'] ] to result array - results.push( [ expr.substring( start, index + 1 ), args ] ); + results.push([expr.substring(start, index + 1), args]); } return results; @@ -106,9 +106,9 @@

js/utils.js

* @param {string} str - original string * @return {string} stripped string */ -function stripQuotes( str ) { - if ( /^".+"$/.test( str ) || /^'.+'$/.test( str ) ) { - return str.substring( 1, str.length - 1 ); +function stripQuotes(str) { + if (/^".+"$/.test(str) || /^'.+'$/.test(str)) { + return str.substring(1, str.length - 1); } return str; @@ -124,17 +124,17 @@

js/utils.js

* @param {string} postfix - postfix for filename * @return {string} new filename */ -function getFilename( file, postfix ) { - if ( typeof file === 'object' && file !== null && file.name ) { +function getFilename(file, postfix) { + if (typeof file === 'object' && file !== null && file.name) { postfix = postfix || ''; - const filenameParts = file.name.split( '.' ); - if ( filenameParts.length > 1 ) { - filenameParts[ filenameParts.length - 2 ] += postfix; - } else if ( filenameParts.length === 1 ) { - filenameParts[ 0 ] += postfix; + const filenameParts = file.name.split('.'); + if (filenameParts.length > 1) { + filenameParts[filenameParts.length - 2] += postfix; + } else if (filenameParts.length === 1) { + filenameParts[0] += postfix; } - return filenameParts.join( '.' ); + return filenameParts.join('.'); } return ''; @@ -145,8 +145,8 @@

js/utils.js

* @param {*} n - value * @return {boolean} whether it is a number value */ -function isNumber( n ) { - return !isNaN( parseFloat( n ) ) && isFinite( n ); +function isNumber(n) { + return !isNaN(parseFloat(n)) && isFinite(n); } /** @@ -154,33 +154,32 @@

js/utils.js

* @param {string} name - a cookie to look for * @return {string|undefined} the value of the cookie */ -function readCookie( name ) { - if ( cookies ) { - return cookies[ name ]; +function readCookie(name) { + if (cookies) { + return cookies[name]; } // In enketo-validate and perhaps other contexts, enketo-core is used in an empty page in a headless browser // In such a context document.cookie throws an 'Access is denied for this document' error. try { - const parts = document.cookie.split( '; ' ); + const parts = document.cookie.split('; '); cookies = {}; - for ( let i = parts.length - 1; i >= 0; i-- ) { - const ck = parts[ i ].split( '=' ); + for (let i = parts.length - 1; i >= 0; i--) { + const ck = parts[i].split('='); // decode URI - ck[ 1 ] = decodeURIComponent( ck[ 1 ] ); + ck[1] = decodeURIComponent(ck[1]); // if cookie is signed (using expressjs/cookie-parser/), extract value - if ( ck[ 1 ].substr( 0, 2 ) === 's:' ) { - ck[ 1 ] = ck[ 1 ].slice( 2 ); - ck[ 1 ] = ck[ 1 ].slice( 0, ck[ 1 ].lastIndexOf( '.' ) ); + if (ck[1].substr(0, 2) === 's:') { + ck[1] = ck[1].slice(2); + ck[1] = ck[1].slice(0, ck[1].lastIndexOf('.')); } - cookies[ ck[ 0 ] ] = decodeURIComponent( ck[ 1 ] ); + cookies[ck[0]] = decodeURIComponent(ck[1]); } - return cookies[ name ]; - - } catch( e ){ - console.error( 'Cookie error', e ); + return cookies[name]; + } catch (e) { + console.error('Cookie error', e); return null; } @@ -191,25 +190,25 @@

js/utils.js

* @param {string} dataURI - dataURI * @return {Blob} dataURI converted to a Blob */ -function dataUriToBlobSync( dataURI ) { +function dataUriToBlobSync(dataURI) { // convert base64 to raw binary data held in a string // doesn't handle URLEncoded DataURIs - see SO answer #6850276 for code that does this - const byteString = atob( dataURI.split( ',' )[ 1 ] ); + const byteString = atob(dataURI.split(',')[1]); // separate out the mime component - const mimeString = dataURI.split( ',' )[ 0 ].split( ':' )[ 1 ].split( ';' )[ 0 ]; + const mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; // write the bytes of the string to an ArrayBuffer - const buffer = new ArrayBuffer( byteString.length ); - const array = new Uint8Array( buffer ); + const buffer = new ArrayBuffer(byteString.length); + const array = new Uint8Array(buffer); - for ( let i = 0; i < byteString.length; i++ ) { - array[ i ] = byteString.charCodeAt( i ); + for (let i = 0; i < byteString.length; i++) { + array[i] = byteString.charCodeAt(i); } // write the ArrayBuffer to a blob - return new Blob( [ array ], { - type: mimeString - } ); + return new Blob([array], { + type: mimeString, + }); } /** @@ -217,10 +216,10 @@

js/utils.js

* @param {Event} event - a paste event * @return {string|null} clipboard data text value contained in event or null */ -function getPasteData( event ) { +function getPasteData(event) { const clipboardData = event.clipboardData || window.clipboardData; // modern || IE11 - return ( clipboardData ) ? clipboardData.getData( 'text' ) : null; + return clipboardData ? clipboardData.getData('text') : null; } /** @@ -229,41 +228,41 @@

js/utils.js

* @param {number} maxPixels - Maximum pixels of resized image * @return {Promise<Blob>} Promise of resized image blob */ -function resizeImage( file, maxPixels ) { - return new Promise( ( resolve, reject ) => { +function resizeImage(file, maxPixels) { + return new Promise((resolve, reject) => { const image = new Image(); - image.src = URL.createObjectURL( file ); + image.src = URL.createObjectURL(file); image.onload = () => { - const width = image.width; - const height = image.height; + const { width } = image; + const { height } = image; - if ( width <= maxPixels && height <= maxPixels ) { - resolve( file ); + if (width <= maxPixels && height <= maxPixels) { + resolve(file); } let newWidth; let newHeight; - if ( width > height ) { - newHeight = height * ( maxPixels / width ); + if (width > height) { + newHeight = height * (maxPixels / width); newWidth = maxPixels; } else { - newWidth = width * ( maxPixels / height ); + newWidth = width * (maxPixels / height); newHeight = maxPixels; } - const canvas = document.createElement( 'canvas' ); + const canvas = document.createElement('canvas'); canvas.width = newWidth; canvas.height = newHeight; - const context = canvas.getContext( '2d' ); + const context = canvas.getContext('2d'); - context.drawImage( image, 0, 0, newWidth, newHeight ); + context.drawImage(image, 0, 0, newWidth, newHeight); - canvas.toBlob( resolve, file.type ); + canvas.toBlob(resolve, file.type); }; image.onerror = reject; - } ); + }); } /** @@ -272,45 +271,44 @@

js/utils.js

* Does not support using ".." to go above/outside the root. * This means that join("foo", "../../bar") will not resolve to "../bar" */ -function joinPath( /* path segments */ ) { +function joinPath(...args) { // Split the inputs into a list of path commands. let parts = []; - for ( var i = 0, l = arguments.length; i < l; i++ ) { - parts = parts.concat( arguments[i].split( '/' ) ); + for (let i = 0, l = args.length; i < l; i++) { + parts = parts.concat(args[i].split('/')); } // Interpret the path commands to get the new resolved path. - let newParts = []; - for ( i = 0, l = parts.length; i < l; i++ ) { - var part = parts[i]; + const newParts = []; + for (let i = 0, l = parts.length; i < l; i++) { + const part = parts[i]; // Remove leading and trailing slashes // Also remove "." segments - if ( !part || part === '.' ) continue; + if (!part || part === '.') continue; // Interpret ".." to pop the last segment - if ( part === '..' ) newParts.pop(); + if (part === '..') newParts.pop(); // Push new path segments. - else newParts.push( part ); + else newParts.push(part); } // Preserve the initial slash if there was one. - if ( parts[0] === '' ) newParts.unshift( '' ); + if (parts[0] === '') newParts.unshift(''); // Turn back into a single string path. - return newParts.join( '/' ) || ( newParts.length ? '/' : '.' ); + return newParts.join('/') || (newParts.length ? '/' : '.'); } - -function getScript( url ) { - const scriptTag = document.createElement( 'script' ); - const firstScriptTag = document.getElementsByTagName( 'script' )[0]; +function getScript(url) { + const scriptTag = document.createElement('script'); + const firstScriptTag = document.getElementsByTagName('script')[0]; scriptTag.src = url; - firstScriptTag.parentNode.insertBefore( scriptTag, firstScriptTag ); + firstScriptTag.parentNode.insertBefore(scriptTag, firstScriptTag); } -function encodeHtmlEntities( text ){ +function encodeHtmlEntities(text) { return text - .replace( /&/g, '&amp;' ) - .replace( /</g, '&lt;' ) - .replace( />/g, '&gt;' ) - .replace( /"/g, '&quot;' ); + .replace(/&/g, '&amp;') + .replace(/</g, '&lt;') + .replace(/>/g, '&gt;') + .replace(/"/g, '&quot;'); } export { @@ -324,7 +322,7 @@

js/utils.js

resizeImage, joinPath, getScript, - encodeHtmlEntities + encodeHtmlEntities, }; diff --git a/docs/js_widget.js.html b/docs/js_widget.js.html index 76868b013..c75055df8 100644 --- a/docs/js_widget.js.html +++ b/docs/js_widget.js.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -49,6 +49,7 @@

js/widget.js

import input from './input';
 import event from './event';
+
 const range = document.createRange();
 
 /**
@@ -60,10 +61,10 @@ 

js/widget.js

* @param {Element} element - The DOM element the widget is applied on * @param {(boolean|{touch: boolean})} [options] - Options passed to the widget during instantiation */ - constructor( element, options ) { + constructor(element, options) { this.element = element; this.options = options || {}; - this.question = element.closest( '.question' ); + this.question = element.closest('.question'); this._props = this._getProps(); // Some widgets (e.g. ImageMap) initialize asynchronously and init returns a promise. @@ -89,13 +90,19 @@

js/widget.js

const that = this; return { - get readonly() { return that.element.nodeName.toLowerCase() === 'select' ? that.element.hasAttribute( 'readonly' ) : !!that.element.readOnly; }, - appearances: [ ...this.element.closest( '.question, form.or' ).classList ] - .filter( cls => cls.indexOf( 'or-appearance-' ) === 0 ) - .map( cls => cls.substring( 14 ) ), + get readonly() { + return that.element.nodeName.toLowerCase() === 'select' + ? that.element.hasAttribute('readonly') + : !!that.element.readOnly; + }, + appearances: [ + ...this.element.closest('.question, form.or').classList, + ] + .filter((cls) => cls.indexOf('or-appearance-') === 0) + .map((cls) => cls.substring(14)), multiple: !!this.element.multiple, disabled: !!this.element.disabled, - type: this.element.getAttribute( 'data-type-xml' ), + type: this.element.getAttribute('data-type-xml'), }; } @@ -178,7 +185,7 @@

js/widget.js

* @param {*} value - value to set * @type {*} */ - set value( value ) {} + set value(value) {} // eslint-disable-line no-empty-function -- this is defining the API /** * Obtains the value from the original form control the widget is instantiated on. @@ -188,7 +195,7 @@

js/widget.js

* @type {*} */ get originalInputValue() { - return input.getVal( this.element ); + return input.getVal(this.element); } /** @@ -198,12 +205,12 @@

js/widget.js

* @param {*} value - value to set * @type {*} */ - set originalInputValue( value ) { + set originalInputValue(value) { // Avoid unnecessary change events as they could have significant negative consequences! // However, to add a check for this.originalInputValue !== value here would affect performance too much, // so we rely on widget code to only use this setter when the value changes. - input.setVal( this.element, value, null ); - this.element.dispatchEvent( event.Change() ); + input.setVal(this.element, value, null); + this.element.dispatchEvent(event.Change()); } /** diff --git a/docs/js_widgets-controller.js.html b/docs/js_widgets-controller.js.html index 3240c43f4..097374559 100644 --- a/docs/js_widgets-controller.js.html +++ b/docs/js_widgets-controller.js.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -55,7 +55,8 @@

js/widgets-controller.js

import _widgets from 'enketo/widgets'; import { elementDataStore as data } from './dom-utils'; import events from './event'; -const widgets = _widgets.filter( widget => widget.selector ); + +const widgets = _widgets.filter((widget) => widget.selector); let options; let formHtml; @@ -67,19 +68,21 @@

js/widgets-controller.js

* @param {*} [opts] - Options (e.g. helper function of Form.js passed) * @return {boolean} `true` when initialized successfuly */ -function init( $group, opts = {} ) { - if ( !this.form ) { - throw new Error( 'Widgets module not correctly instantiated with form property.' ); +function init($group, opts = {}) { + if (!this.form) { + throw new Error( + 'Widgets module not correctly instantiated with form property.' + ); } options = opts; formHtml = this.form.view.html; // not sure why this is only available in init - const group = $group && $group.length ? $group[ 0 ] : formHtml; + const group = $group && $group.length ? $group[0] : formHtml; - widgets.forEach( Widget => { - _instantiate( Widget, group ); - } ); + widgets.forEach((Widget) => { + _instantiate(Widget, group); + }); return true; } @@ -95,12 +98,15 @@

js/widgets-controller.js

* @static * @param {Element} group - HTML element */ -function enable( group ) { - widgets.forEach( Widget => { - const els = _getElements( group, Widget.selector ) - .filter( el => el.nodeName.toLowerCase() === 'select' ? !el.hasAttribute( 'readonly' ) : !el.readOnly ); - new Collection( els ).enable( Widget ); - } ); +function enable(group) { + widgets.forEach((Widget) => { + const els = _getElements(group, Widget.selector).filter((el) => + el.nodeName.toLowerCase() === 'select' + ? !el.hasAttribute('readonly') + : !el.readOnly + ); + new Collection(els).enable(Widget); + }); } /** @@ -111,11 +117,11 @@

js/widgets-controller.js

* @static * @param {Element} group - The element inside which all widgets need to be disabled. */ -function disable( group ) { - widgets.forEach( Widget => { - const els = _getElements( group, Widget.selector ); - new Collection( els ).disable( Widget ); - } ); +function disable(group) { + widgets.forEach((Widget) => { + const els = _getElements(group, Widget.selector); + new Collection(els).disable(Widget); + }); } /** @@ -125,18 +131,21 @@

js/widgets-controller.js

* @param {string|null} selector - If the selector is `null`, the form element will be returned * @return {jQuery} A jQuery collection */ -function _getElements( group, selector ) { - if ( selector ) { - if ( selector === 'form' ) { - return [ formHtml ]; +function _getElements(group, selector) { + if (selector) { + if (selector === 'form') { + return [formHtml]; } // e.g. if the widget selector starts at .question level (e.g. ".or-appearance-draw input") - if ( group.classList.contains( 'question' ) ) { - return [ ...group.querySelectorAll( 'input:not(.ignore), select:not(.ignore), textarea:not(.ignore)' ) ] - .filter( el => el.matches( selector ) ); + if (group.classList.contains('question')) { + return [ + ...group.querySelectorAll( + 'input:not(.ignore), select:not(.ignore), textarea:not(.ignore)' + ), + ].filter((el) => el.matches(selector)); } - return [ ...group.querySelectorAll( selector ) ]; + return [...group.querySelectorAll(selector)]; } return []; @@ -148,34 +157,33 @@

js/widgets-controller.js

* @param {object} Widget - The widget to instantiate * @param {Element} group - The element inside which widgets need to be created. */ -function _instantiate( Widget, group ) { - let opts = {}; +function _instantiate(Widget, group) { + const opts = {}; - if ( !Widget.name ) { - return console.error( 'widget doesn\'t have a name' ); + if (!Widget.name) { + return console.error("widget doesn't have a name"); } - if ( Widget.helpersRequired && Widget.helpersRequired.length > 0 ) { + if (Widget.helpersRequired && Widget.helpersRequired.length > 0) { opts.helpers = {}; - Widget.helpersRequired.forEach( helper => { - opts.helpers[ helper ] = options[ helper ]; - } ); + Widget.helpersRequired.forEach((helper) => { + opts.helpers[helper] = options[helper]; + }); } - const elements = _getElements( group, Widget.selector ); + const elements = _getElements(group, Widget.selector); - if ( !elements.length ) { + if (!elements.length) { return; } - new Collection( elements ).instantiate( Widget, opts ); + new Collection(elements).instantiate(Widget, opts); - _setLangChangeListener( Widget, elements ); - _setOptionChangeListener( Widget, elements ); - _setValChangeListener( Widget, elements ); + _setLangChangeListener(Widget, elements); + _setOptionChangeListener(Widget, elements); + _setValChangeListener(Widget, elements); } - /** * Calls widget('update') when the language changes. This function is called upon initialization, * and whenever a new repeat is created. In the latter case, since the widget('update') is called upon @@ -184,12 +192,12 @@

js/widgets-controller.js

* @param {{name: string}} Widget - The widget configuration object * @param {Array<Element>} els - Array of elements that the widget has been instantiated on. */ -function _setLangChangeListener( Widget, els ) { +function _setLangChangeListener(Widget, els) { // call update for all widgets when language changes - if ( els.length > 0 ) { - formHtml.addEventListener( events.ChangeLanguage().type, () => { - new Collection( els ).update( Widget ); - } ); + if (els.length > 0) { + formHtml.addEventListener(events.ChangeLanguage().type, () => { + new Collection(els).update(Widget); + }); } } @@ -201,12 +209,12 @@

js/widgets-controller.js

* @param {{name: string}} Widget - The widget configuration object * @param {Array<Element>} els - The array of elements that the widget has been instantiated on. */ -function _setOptionChangeListener( Widget, els ) { - if ( els.length > 0 && Widget.list ) { - $( els ).on( events.ChangeOption().type, function() { +function _setOptionChangeListener(Widget, els) { + if (els.length > 0 && Widget.list) { + $(els).on(events.ChangeOption().type, function () { // update (itemselect) picker on which event was triggered because the options changed - new Collection( this ).update( Widget ); - } ); + new Collection(this).update(Widget); + }); } } @@ -217,12 +225,14 @@

js/widgets-controller.js

* @param {{name: string}} Widget - The widget configuration object. * @param {Array<Element>} els - The array of elements that the widget has been instantiated on. */ -function _setValChangeListener( Widget, els ) { +function _setValChangeListener(Widget, els) { // avoid adding eventhandlers on widgets that apply to the <form> or <label> element - if ( els.length > 0 && els[ 0 ].matches( 'input, select, textarea' ) ) { - els.forEach( el => el.addEventListener( events.InputUpdate().type, event => { - new Collection( event.target ).update( Widget ); - } ) ); + if (els.length > 0 && els[0].matches('input, select, textarea')) { + els.forEach((el) => + el.addEventListener(events.InputUpdate().type, (event) => { + new Collection(event.target).update(Widget); + }) + ); } } @@ -231,74 +241,82 @@

js/widgets-controller.js

* @class * @param {Array<Element>} elements - HTML elements */ - constructor( elements ) { - if ( !Array.isArray( elements ) ) { - elements = [ elements ]; + constructor(elements) { + if (!Array.isArray(elements)) { + elements = [elements]; } this.elements = elements; } + /** * @param {Element} element - HTML element * @param {object} Widget - widget to instantiate * @param {object} [options] - widget options */ - _instantiateSingleWidget( element, Widget, options = {} ) { - if ( !Widget.condition( element ) ) { + _instantiateSingleWidget(element, Widget, options = {}) { + if (!Widget.condition(element)) { return; } - if ( data.has( element, Widget ) ) { + if (data.has(element, Widget)) { return; } - const w = new Widget( element, options ); - if ( w instanceof Promise ) { - w.then( wr => data.put( element, Widget.name, wr ) ); + const w = new Widget(element, options); + if (w instanceof Promise) { + w.then((wr) => data.put(element, Widget.name, wr)); } else { - data.put( element, Widget.name, w ); + data.put(element, Widget.name, w); } } + /** * @param {object} Widget - widget to instantiate * @param {Function} method - widget function to call */ - _methodCall( Widget, method ) { - this.elements.forEach( element => { - const w = data.get( element, Widget.name ); - if ( w ) { - w[ method ](); + _methodCall(Widget, method) { + this.elements.forEach((element) => { + const w = data.get(element, Widget.name); + if (w) { + w[method](); } - } ); + }); } + /** * @param {object} Widget - widget to instantiate * @param {object} [options] - widget options */ - instantiate( Widget, options ) { - this.elements.forEach( el => this._instantiateSingleWidget( el, Widget, options ) ); + instantiate(Widget, options) { + this.elements.forEach((el) => + this._instantiateSingleWidget(el, Widget, options) + ); } + /** * @param {object} Widget - widget to instantiate */ - update( Widget ) { - this._methodCall( Widget, 'update' ); + update(Widget) { + this._methodCall(Widget, 'update'); } + /** * @param {object} Widget - widget to instantiate */ - disable( Widget ) { - this._methodCall( Widget, 'disable' ); + disable(Widget) { + this._methodCall(Widget, 'disable'); } + /** * @param {object} Widget - The widget to instantiate */ - enable( Widget ) { - this._methodCall( Widget, 'enable' ); + enable(Widget) { + this._methodCall(Widget, 'enable'); } } export default { init, enable, - disable + disable, };
diff --git a/docs/js_widgets.js.html b/docs/js_widgets.js.html index 7177e1be5..47fe421fe 100644 --- a/docs/js_widgets.js.html +++ b/docs/js_widgets.js.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -83,9 +83,40 @@

js/widgets.js

import ac from '../widget/rating/rating'; import ad from '../widget/text-print/text-print'; import ae from '../widget/thousands-sep/thousands-sep'; -//import zz from '../widget/example/my-widget'; - -export default [ a, b, c, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, ab, ac, ad, ae ]; +// import zz from '../widget/example/my-widget'; + +export default [ + a, + b, + c, + e, + f, + g, + h, + i, + j, + k, + l, + m, + n, + o, + p, + q, + r, + s, + t, + u, + v, + w, + x, + y, + z, + aa, + ab, + ac, + ad, + ae, +]; diff --git a/docs/js_xpath-evaluator-binding.js.html b/docs/js_xpath-evaluator-binding.js.html index e4e32fa68..f7caebc32 100644 --- a/docs/js_xpath-evaluator-binding.js.html +++ b/docs/js_xpath-evaluator-binding.js.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -54,7 +54,7 @@

js/xpath-evaluator-binding.js

/** * @function xpath-evaluator-binding */ -export default function( ) { +export default function () { const evaluator = OpenRosaXPath(); this.xml.jsEvaluate = evaluator.evaluate; } diff --git a/docs/module-calculate.html b/docs/module-calculate.html index d2e1e299c..15eff2601 100644 --- a/docs/module-calculate.html +++ b/docs/module-calculate.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -98,7 +98,7 @@

(static)
Source:
@@ -276,7 +276,7 @@

(static)
Source:
@@ -434,7 +434,7 @@

(static)
Source:
@@ -644,7 +644,7 @@

(static) Source:
@@ -826,7 +826,7 @@

(static) Source:
diff --git a/docs/module-dialog.html b/docs/module-dialog.html index a86b97f96..c6795c268 100644 --- a/docs/module-dialog.html +++ b/docs/module-dialog.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

diff --git a/docs/module-dom-utils-elementDataStore.html b/docs/module-dom-utils-elementDataStore.html index 266e51abe..9a0aa2a37 100644 --- a/docs/module-dom-utils-elementDataStore.html +++ b/docs/module-dom-utils-elementDataStore.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -69,7 +69,7 @@

Source:
@@ -147,7 +147,7 @@

(static) _st
Source:
@@ -226,7 +226,7 @@

(static) getSource:
@@ -407,7 +407,7 @@

(static) hasSource:
@@ -588,7 +588,7 @@

(static) putSource:
@@ -770,7 +770,7 @@

(static) remov
Source:
diff --git a/docs/module-dom-utils.html b/docs/module-dom-utils.html index d6ec90897..4679e4c8f 100644 --- a/docs/module-dom-utils.html +++ b/docs/module-dom-utils.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -99,7 +99,7 @@

(static) Source:
@@ -175,7 +175,7 @@

(static
Source:
@@ -356,6 +356,8 @@
Parameters:
+ null + @@ -423,7 +425,7 @@

(static) empty<
Source:
@@ -577,7 +579,7 @@

(static) Source:
@@ -760,6 +762,8 @@

Parameters:
+ null + @@ -1481,7 +1485,7 @@

(inner) getC
Source:
@@ -1676,7 +1680,7 @@

(inner) g
Source:
@@ -1869,7 +1873,7 @@

(inner) Source:
@@ -2240,7 +2244,7 @@

(inner) getX
Source:
@@ -2492,7 +2496,7 @@

Source:
@@ -2646,7 +2650,7 @@

Source:
@@ -2823,7 +2827,7 @@

Source:
@@ -2977,7 +2981,7 @@

(i
Source:
diff --git a/docs/module-event.html b/docs/module-event.html index e4ae97232..cfe4196bd 100644 --- a/docs/module-event.html +++ b/docs/module-event.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -523,7 +523,7 @@

(inner) Be
Source:
@@ -850,7 +850,7 @@

(inner) De
Source:
@@ -1177,7 +1177,7 @@

(inner) Source:
@@ -1287,7 +1287,7 @@

(inner) Source:
@@ -1730,7 +1730,7 @@

(inner) I
Source:
@@ -2113,7 +2113,7 @@

(inner) Prin
Source:
@@ -2222,7 +2222,7 @@

(inner) Source:
@@ -2647,7 +2647,7 @@

(inner) <
Source:
diff --git a/docs/module-fake-translator.html b/docs/module-fake-translator.html index 8706eeed2..a939c6215 100644 --- a/docs/module-fake-translator.html +++ b/docs/module-fake-translator.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -179,7 +179,7 @@

(static) tSource:
diff --git a/docs/module-fileManager.html b/docs/module-fileManager.html index 4c55896d4..e220537fc 100644 --- a/docs/module-fileManager.html +++ b/docs/module-fileManager.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -183,7 +183,7 @@

(static) Source:
@@ -292,7 +292,7 @@

(static) g
Source:
@@ -461,7 +461,7 @@

(static)
Source:
@@ -570,7 +570,7 @@

(static) Source:
@@ -738,7 +738,7 @@

(static) initSource:
@@ -853,7 +853,7 @@

(static) i
Source:
@@ -962,7 +962,7 @@

(sta
Source:
@@ -1071,7 +1071,7 @@

(static) ur
Source:
diff --git a/docs/module-format-format.html b/docs/module-format-format.html index 774eae1a0..6165fc7c4 100644 --- a/docs/module-format-format.html +++ b/docs/module-format-format.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -69,7 +69,7 @@

Source:
diff --git a/docs/module-format-time.html b/docs/module-format-time.html index 553b65636..0235d031f 100644 --- a/docs/module-format-time.html +++ b/docs/module-format-time.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -69,7 +69,7 @@

Source:
@@ -142,7 +142,7 @@

(static) a
Source:
@@ -211,7 +211,7 @@

(static) hour1
Source:
@@ -280,7 +280,7 @@

(static) p
Source:
@@ -359,7 +359,7 @@

(static)
Source:
@@ -495,7 +495,7 @@

(static) Source:
diff --git a/docs/module-format.html b/docs/module-format.html index 8d346b7fb..538c8ea0b 100644 --- a/docs/module-format.html +++ b/docs/module-format.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -108,7 +108,7 @@

(inner) <
Source:
@@ -266,7 +266,7 @@

(inner) <
Source:
diff --git a/docs/module-input.html b/docs/module-input.html index 1ff18ff14..0fdbdad6c 100644 --- a/docs/module-input.html +++ b/docs/module-input.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -179,7 +179,7 @@

(static) clear<
Source:
@@ -346,7 +346,7 @@

(static) findSource:
@@ -527,7 +527,7 @@

(static) Source:
@@ -681,7 +681,7 @@

(static) Source:
@@ -835,7 +835,7 @@

(static) get
Source:
@@ -991,7 +991,7 @@

(static) Source:
@@ -1145,7 +1145,7 @@

(static) getN
Source:
@@ -1299,7 +1299,7 @@

(static) get
Source:
@@ -1453,7 +1453,7 @@

(static)
Source:
@@ -1607,7 +1607,7 @@

(static)
Source:
@@ -1761,7 +1761,7 @@

(static)
Source:
@@ -1918,7 +1918,7 @@

(static) getVa
Source:
@@ -2072,7 +2072,7 @@

(static)
Source:
@@ -2226,7 +2226,7 @@

(static) Source:
@@ -2380,7 +2380,7 @@

(static) g
Source:
@@ -2534,7 +2534,7 @@

(static) is
Source:
@@ -2688,7 +2688,7 @@

(static) i
Source:
@@ -2842,7 +2842,7 @@

(static) setVa
Source:
@@ -3077,7 +3077,7 @@

(static) val
Source:
diff --git a/docs/module-itemset.html b/docs/module-itemset.html index 46fd7e43c..a285fe15d 100644 --- a/docs/module-itemset.html +++ b/docs/module-itemset.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -179,7 +179,7 @@

(static)
Source:
@@ -263,7 +263,7 @@

Parameters:
-Array.<object> +object @@ -371,7 +371,7 @@
Returns:
-

(static) createOption(labelContentNodes, value) → {Element}

+

(static) createOption(attributes, labelContentNodes, value) → {Element}

@@ -383,7 +383,7 @@

(static) Source:
@@ -459,6 +459,29 @@

Parameters:
+ + + attributes + + + + + +object + + + + + + + + + +

attributes to add to option

+ + + + labelContentNodes @@ -564,7 +587,7 @@

(sta
Source:
@@ -839,7 +862,7 @@

(static) Source:
@@ -1019,7 +1042,7 @@

(static) Source:
@@ -1223,7 +1246,7 @@

Source:
@@ -1384,7 +1407,7 @@

(static) updat
Source:
diff --git a/docs/module-language.html b/docs/module-language.html index b545ba264..f77ee31fd 100644 --- a/docs/module-language.html +++ b/docs/module-language.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -173,7 +173,7 @@

(static) Source:
@@ -242,7 +242,7 @@

(static) Source:
@@ -311,7 +311,7 @@

(static) Source:
@@ -522,7 +522,7 @@

(static) se
Source:
diff --git a/docs/module-mask.html b/docs/module-mask.html index 882293806..4900660cf 100644 --- a/docs/module-mask.html +++ b/docs/module-mask.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -98,7 +98,7 @@

(static)
Source:
@@ -252,7 +252,7 @@

(static)
Source:
@@ -406,7 +406,7 @@

(static) Source:
diff --git a/docs/module-output.html b/docs/module-output.html index 5d56ebc94..835f343e9 100644 --- a/docs/module-output.html +++ b/docs/module-output.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

diff --git a/docs/module-pages.html b/docs/module-pages.html index ab8264efd..fa6c72348 100644 --- a/docs/module-pages.html +++ b/docs/module-pages.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -393,7 +393,7 @@

(static) _fli
Source:
@@ -552,7 +552,7 @@

(static) Source:
@@ -639,7 +639,7 @@

(static)
Source:
@@ -726,7 +726,7 @@

(stati
Source:
@@ -862,7 +862,7 @@

(static)
Source:
@@ -967,7 +967,7 @@

(static) Source:
@@ -1072,7 +1072,7 @@

(static) _ge
Source:
@@ -1226,7 +1226,7 @@

(static) _ge
Source:
@@ -1380,7 +1380,7 @@

(static) _next<
Source:
@@ -1491,7 +1491,7 @@

(static) Source:
@@ -1627,7 +1627,7 @@

(static) _prev<
Source:
@@ -1714,7 +1714,7 @@

(static)
Source:
@@ -1801,7 +1801,7 @@

(static)
Source:
@@ -1888,7 +1888,7 @@

(stat
Source:
@@ -1975,7 +1975,7 @@

(static)
Source:
@@ -2062,7 +2062,7 @@

(static) <
Source:
@@ -2149,7 +2149,7 @@

(static) Source:
@@ -2236,7 +2236,7 @@

(static) Source:
@@ -2368,7 +2368,7 @@

(static) Source:
@@ -2516,7 +2516,7 @@

(static) Source:
@@ -2648,7 +2648,7 @@

(static) _
Source:
@@ -2735,7 +2735,7 @@

(static
Source:
diff --git a/docs/module-preloader.html b/docs/module-preloader.html index ef689253c..c06d512f7 100644 --- a/docs/module-preloader.html +++ b/docs/module-preloader.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -179,7 +179,7 @@

(static) cont
Source:
@@ -333,7 +333,7 @@

(static) dateSource:
@@ -487,7 +487,7 @@

(static) initSource:
@@ -574,7 +574,7 @@

(static) pati
Source:
@@ -728,7 +728,7 @@

(static) pro
Source:
@@ -882,7 +882,7 @@

(static) ti
Source:
@@ -1036,7 +1036,7 @@

(static) uidSource:
@@ -1190,7 +1190,7 @@

(static) userSource:
diff --git a/docs/module-print.html b/docs/module-print.html index 6eeff1a76..9c621b7f1 100644 --- a/docs/module-print.html +++ b/docs/module-print.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -179,7 +179,7 @@

(static) Source:
@@ -262,7 +262,7 @@

(static) fixG
Source:
@@ -475,7 +475,7 @@

(static) isGri
Source:
@@ -584,7 +584,7 @@

(static) Source:
@@ -667,7 +667,7 @@

(static) print<
Source:
@@ -1001,7 +1001,7 @@

(inner) <
Source:
@@ -1156,7 +1156,7 @@

(inner) <
Source:
@@ -1624,7 +1624,7 @@

PaperObj

Source:
diff --git a/docs/module-progress.html b/docs/module-progress.html index 7aa6131d3..7938e76a0 100644 --- a/docs/module-progress.html +++ b/docs/module-progress.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -480,7 +480,7 @@

(static) getSource:
@@ -585,7 +585,7 @@

(static) updat
Source:
diff --git a/docs/module-readonly.html b/docs/module-readonly.html index 6b549565b..8d24bf2e7 100644 --- a/docs/module-readonly.html +++ b/docs/module-readonly.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

diff --git a/docs/module-relevant.html b/docs/module-relevant.html index ad7b179f7..549dc6632 100644 --- a/docs/module-relevant.html +++ b/docs/module-relevant.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -179,7 +179,7 @@

(static) act
Source:
@@ -316,7 +316,7 @@

(static) clear<
Source:
@@ -476,7 +476,7 @@

(static) d
Source:
@@ -613,7 +613,7 @@

(static) disa
Source:
@@ -817,7 +817,7 @@

(static) enabl
Source:
@@ -998,7 +998,7 @@

(static) eva
Source:
@@ -1202,7 +1202,7 @@

(static) proc
Source:
@@ -1407,7 +1407,7 @@

(static) Source:
@@ -1565,7 +1565,7 @@

(static)
Source:
@@ -1895,7 +1895,7 @@

(static)
Source:
diff --git a/docs/module-repeat.html b/docs/module-repeat.html index ec5c2ea48..2469c257d 100644 --- a/docs/module-repeat.html +++ b/docs/module-repeat.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -186,7 +186,7 @@

(static) addSource:
@@ -420,7 +420,7 @@

(static)
Source:
@@ -557,7 +557,7 @@

(static) initSource:
@@ -644,7 +644,7 @@

Source:
@@ -780,7 +780,7 @@

Source:
@@ -916,7 +916,7 @@

Source:
diff --git a/docs/module-required.html b/docs/module-required.html index 58feefe7d..239dc33fc 100644 --- a/docs/module-required.html +++ b/docs/module-required.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

diff --git a/docs/module-sniffer-browser.html b/docs/module-sniffer-browser.html index 57e308851..948c0e976 100644 --- a/docs/module-sniffer-browser.html +++ b/docs/module-sniffer-browser.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -69,7 +69,7 @@

Source:
@@ -142,7 +142,7 @@

(static) safar
Source:
diff --git a/docs/module-sniffer-os.html b/docs/module-sniffer-os.html index 8553bbfe2..27019f0ff 100644 --- a/docs/module-sniffer-os.html +++ b/docs/module-sniffer-os.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -142,7 +142,7 @@

(static) andr
Source:
@@ -280,7 +280,7 @@

(static) macos<
Source:
diff --git a/docs/module-sniffer.html b/docs/module-sniffer.html index 956681da0..639344b3e 100644 --- a/docs/module-sniffer.html +++ b/docs/module-sniffer.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

diff --git a/docs/module-support.html b/docs/module-support.html index c3627e147..92d81fd67 100644 --- a/docs/module-support.html +++ b/docs/module-support.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

diff --git a/docs/module-toc.html b/docs/module-toc.html index b42d362e4..fcd0dd04d 100644 --- a/docs/module-toc.html +++ b/docs/module-toc.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -331,7 +331,7 @@

(static) <
Source:
@@ -490,7 +490,7 @@

(static) _g
Source:
@@ -713,7 +713,7 @@

(static) Source:
diff --git a/docs/module-types-types.barcode.html b/docs/module-types-types.barcode.html index 79eac1da6..199864c42 100644 --- a/docs/module-types-types.barcode.html +++ b/docs/module-types-types.barcode.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -69,7 +69,7 @@

Source:
@@ -148,7 +148,7 @@

(static) val
Source:
diff --git a/docs/module-types-types.binary.html b/docs/module-types-types.binary.html index b897922c5..d9cb3ad89 100644 --- a/docs/module-types-types.binary.html +++ b/docs/module-types-types.binary.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -69,7 +69,7 @@

Source:
@@ -148,7 +148,7 @@

(static) val
Source:
diff --git a/docs/module-types-types.date.html b/docs/module-types-types.date.html index 7ef081994..4c8cf6cd0 100644 --- a/docs/module-types-types.date.html +++ b/docs/module-types-types.date.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -69,7 +69,7 @@

Source:
@@ -148,7 +148,7 @@

(static) conv
Source:
@@ -305,7 +305,7 @@

(static) val
Source:
diff --git a/docs/module-types-types.datetime.html b/docs/module-types-types.datetime.html index d3ad3c0f3..e3e64561f 100644 --- a/docs/module-types-types.datetime.html +++ b/docs/module-types-types.datetime.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -69,7 +69,7 @@

Source:
@@ -148,7 +148,7 @@

(static) conv
Source:
@@ -305,7 +305,7 @@

(static) val
Source:
diff --git a/docs/module-types-types.decimal.html b/docs/module-types-types.decimal.html index eb85608b4..6bd433191 100644 --- a/docs/module-types-types.decimal.html +++ b/docs/module-types-types.decimal.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -69,7 +69,7 @@

Source:
@@ -148,7 +148,7 @@

(static) conv
Source:
@@ -305,7 +305,7 @@

(static) val
Source:
diff --git a/docs/module-types-types.geopoint.html b/docs/module-types-types.geopoint.html index 6d1437dc2..ea534eafc 100644 --- a/docs/module-types-types.geopoint.html +++ b/docs/module-types-types.geopoint.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -69,7 +69,7 @@

Source:
@@ -148,7 +148,7 @@

(static) conv
Source:
@@ -302,7 +302,7 @@

(static) val
Source:
diff --git a/docs/module-types-types.geoshape.html b/docs/module-types-types.geoshape.html index ac46fa0d0..eb3cac7b1 100644 --- a/docs/module-types-types.geoshape.html +++ b/docs/module-types-types.geoshape.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -69,7 +69,7 @@

Source:
@@ -148,7 +148,7 @@

(static) conv
Source:
@@ -302,7 +302,7 @@

(static) val
Source:
diff --git a/docs/module-types-types.geotrace.html b/docs/module-types-types.geotrace.html index 3d070bf38..0d0074550 100644 --- a/docs/module-types-types.geotrace.html +++ b/docs/module-types-types.geotrace.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -69,7 +69,7 @@

Source:
@@ -148,7 +148,7 @@

(static) conv
Source:
@@ -302,7 +302,7 @@

(static) val
Source:
diff --git a/docs/module-types-types.html b/docs/module-types-types.html index 1860981c9..941c1b517 100644 --- a/docs/module-types-types.html +++ b/docs/module-types-types.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -69,7 +69,7 @@

Source:
diff --git a/docs/module-types-types.int.html b/docs/module-types-types.int.html index b1599abf5..da5e548cb 100644 --- a/docs/module-types-types.int.html +++ b/docs/module-types-types.int.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -69,7 +69,7 @@

Source:
@@ -148,7 +148,7 @@

(static) conv
Source:
@@ -305,7 +305,7 @@

(static) val
Source:
diff --git a/docs/module-types-types.select.html b/docs/module-types-types.select.html index 89ef0de48..756f116c4 100644 --- a/docs/module-types-types.select.html +++ b/docs/module-types-types.select.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -69,7 +69,7 @@

Source:
@@ -148,7 +148,7 @@

(static) val
Source:
diff --git a/docs/module-types-types.select1.html b/docs/module-types-types.select1.html index 6fd1f7aa1..573de1054 100644 --- a/docs/module-types-types.select1.html +++ b/docs/module-types-types.select1.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -69,7 +69,7 @@

Source:
@@ -148,7 +148,7 @@

(static) val
Source:
diff --git a/docs/module-types-types.string.html b/docs/module-types-types.string.html index a05ca80b7..3cc89786a 100644 --- a/docs/module-types-types.string.html +++ b/docs/module-types-types.string.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -69,7 +69,7 @@

Source:
@@ -148,7 +148,7 @@

(static) conv
Source:
@@ -302,7 +302,7 @@

(static) val
Source:
diff --git a/docs/module-types-types.time.html b/docs/module-types-types.time.html index c28e6426a..43a847f7f 100644 --- a/docs/module-types-types.time.html +++ b/docs/module-types-types.time.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -69,7 +69,7 @@

Source:
@@ -148,7 +148,7 @@

(static) conv
Source:
@@ -345,7 +345,7 @@

(static) Source:
@@ -504,7 +504,7 @@

(static) val
Source:
diff --git a/docs/module-types.html b/docs/module-types.html index 1e205b02d..05a72fc92 100644 --- a/docs/module-types.html +++ b/docs/module-types.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

diff --git a/docs/module-utils.html b/docs/module-utils.html index cad4c7afa..ea9698518 100644 --- a/docs/module-utils.html +++ b/docs/module-utils.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -179,7 +179,7 @@

(static) <
Source:
@@ -510,7 +510,7 @@

(static) Source:
@@ -1159,7 +1159,7 @@

(static)
Source:
@@ -1490,7 +1490,7 @@

(inner) join
Source:
diff --git a/docs/module-widgets-controller-Collection.html b/docs/module-widgets-controller-Collection.html index e52adaafb..399b06c2e 100644 --- a/docs/module-widgets-controller-Collection.html +++ b/docs/module-widgets-controller-Collection.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -78,7 +78,7 @@

new Collect
Source:
@@ -231,7 +231,7 @@

Source:
@@ -437,7 +437,7 @@

_methodCal
Source:
@@ -592,7 +592,7 @@

disableSource:
@@ -724,7 +724,7 @@

enableSource:
@@ -856,7 +856,7 @@

instantiat
Source:
@@ -1031,7 +1031,7 @@

updateSource:
diff --git a/docs/module-widgets-controller.html b/docs/module-widgets-controller.html index b42327d83..ac639d02e 100644 --- a/docs/module-widgets-controller.html +++ b/docs/module-widgets-controller.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -105,7 +105,7 @@

(static) disa
Source:
@@ -243,7 +243,7 @@

(static) enabl
Source:
@@ -384,7 +384,7 @@

(static) initSource:
@@ -585,7 +585,7 @@

(inner)
Source:
@@ -769,7 +769,7 @@

(inner)
Source:
@@ -928,7 +928,7 @@

(inne
Source:
@@ -1089,7 +1089,7 @@

(in
Source:
@@ -1250,7 +1250,7 @@

(inner
Source:
diff --git a/docs/module-widgets.html b/docs/module-widgets.html index 4fef799f2..437628f49 100644 --- a/docs/module-widgets.html +++ b/docs/module-widgets.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

diff --git a/docs/tutorial-00-getting-started.html b/docs/tutorial-00-getting-started.html index d120c15fb..a91a0cf70 100644 --- a/docs/tutorial-00-getting-started.html +++ b/docs/tutorial-00-getting-started.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -69,56 +69,54 @@

Usage as a library

const formEl = document.querySelector('form.or'); // required object containing data for the form - const data = { - // required string of the default instance defined in the XForm - modelStr: globalXMLInstance, - // optional string of an existing instance to be edited - instanceStr: null, - // optional boolean whether this instance has ever been submitted before - submitted: false, - // optional array of external data objects containing: - // {id: 'someInstanceId', xml: XMLDocument} - external: [], - // optional object of session properties - // 'deviceid', 'username', 'email', 'phonenumber', 'simserial', 'subscriberid' - session: {} +const data = { + // required string of the default instance defined in the XForm + modelStr: globalXMLInstance, + // optional string of an existing instance to be edited + instanceStr: null, + // optional boolean whether this instance has ever been submitted before + submitted: false, + // optional array of external data objects containing: + // {id: 'someInstanceId', xml: XMLDocument} + external: [], + // optional object of session properties + // 'deviceid', 'username', 'email', 'phonenumber', 'simserial', 'subscriberid' + session: {}, }; // Form-specific configuration -const options = {} +const options = {}; // Instantiate a form, with 2 parameters -const form = new Form( formEl, data, options); +const form = new Form(formEl, data, options); // Initialize the form and capture any load errors let loadErrors = form.init(); // If desired, scroll to a specific question with any XPath location expression, // and aggregate any loadErrors. -loadErrors = loadErrors.concat( form.goTo( '//repeat[3]/node' ) ); +loadErrors = loadErrors.concat(form.goTo('//repeat[3]/node')); // submit button handler for validate button -$( '#submit' ).on( 'click', function() { - // clear non-relevant questions and validate - form.validate() - .then(function (valid){ - if ( !valid ) { - alert( 'Form contains errors. Please see fields marked in red.' ); - } else { - // Record is valid! - const record = form.getDataStr(); - - // reset the form view - form.resetView(); - - // reinstantiate a new form with the default model and no options - form = new Form( formSelector, { modelStr: modelStr }, {} ); - - // do what you want with the record - } +$('#submit').on('click', function () { + // clear non-relevant questions and validate + form.validate().then(function (valid) { + if (!valid) { + alert('Form contains errors. Please see fields marked in red.'); + } else { + // Record is valid! + const record = form.getDataStr(); + + // reset the form view + form.resetView(); + + // reinstantiate a new form with the default model and no options + form = new Form(formSelector, { modelStr: modelStr }, {}); + + // do what you want with the record + } }); -} ); - +}); diff --git a/docs/tutorial-10-configuration.html b/docs/tutorial-10-configuration.html index d616b1c5d..c9c660b20 100644 --- a/docs/tutorial-10-configuration.html +++ b/docs/tutorial-10-configuration.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -52,7 +52,7 @@

Configuration

Global Configuration

Global configuration (per app) is done in config.json which is meant to be overridden by a config file in your own application (e.g. by using rollup).

maps

-

The maps configuration can include an array of Mapbox TileJSON objects (or a subset of these with at least a name, tiles (array) and an attribution property, and optionally maxzoom and minzoom). You can also mix and match Google Maps layers. Below is an example of a mix of two map layers provided by OSM (in TileJSON format) and Google maps.

+

The maps configuration can include an array of Mapbox TileJSON objects (or a subset of these with at least a name, tiles (array) and an attribution property, and optionally maxzoom and minzoom). You can also mix and match Google Maps layers. Below is an example of a mix of two map layers provided by OSM (in TileJSON format) and Google maps.

[
   {
     "name": "street",
diff --git a/docs/tutorial-20-development.html b/docs/tutorial-20-development.html
index 4af8934d1..dec94280e 100644
--- a/docs/tutorial-20-development.html
+++ b/docs/tutorial-20-development.html
@@ -32,7 +32,7 @@
     
     
     
-    

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

diff --git a/docs/tutorial-30-events.html b/docs/tutorial-30-events.html index 122ff6956..aa85278af 100644 --- a/docs/tutorial-30-events.html +++ b/docs/tutorial-30-events.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

diff --git a/docs/tutorial-40-widgets.html b/docs/tutorial-40-widgets.html index 55372c6f0..0cff49cfb 100644 --- a/docs/tutorial-40-widgets.html +++ b/docs/tutorial-40-widgets.html @@ -32,7 +32,7 @@ -

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

+

Home

Github repo

Change log

Tutorials

Classes

Modules

Externals

Namespaces

Global

@@ -58,7 +58,6 @@

Widgets in Enketo Core

* Make sure to give the widget a unique widget class name and extend Widget. */ class MyWidget extends Widget { - /* * The selector that determines on which form control the widget is instantiated. * Make sure that any other widgets that target the same from control are not interfering with this widget by disabling @@ -76,7 +75,7 @@

Widgets in Enketo Core

*/ _init() { // Hide the original input - this.element.classList.add( 'hide' ); + this.element.classList.add('hide'); // Create the widget's DOM fragment. const fragment = document.createRange().createContextualFragment( @@ -84,20 +83,22 @@

Widgets in Enketo Core

<input class="ignore" type="range" min="0" max="100" step="1"/> <div>` ); - fragment.querySelector( '.widget' ).appendChild( this.resetButtonHtml ); + fragment.querySelector('.widget').appendChild(this.resetButtonHtml); // Only when the new DOM has been fully created as a HTML fragment, we append it. - this.element.after( fragment ); + this.element.after(fragment); - const widget = this.element.parentElement.querySelector( '.widget' ); - this.range = widget.querySelector( 'input' ); + const widget = this.element.parentElement.querySelector('.widget'); + this.range = widget.querySelector('input'); // Set the current loaded value into the widget this.value = this.originalInputValue; // Set event handlers for the widget - this.range.addEventListener( 'change', this._change.bind( this ) ); - widget.querySelector( '.btn-reset' ).addEventListener( 'click', this._reset.bind( this ) ); + this.range.addEventListener('change', this._change.bind(this)); + widget + .querySelector('.btn-reset') + .addEventListener('click', this._reset.bind(this)); // This widget initializes synchronously so we don't return anything. // If the widget initializes asynchronously return a promise that resolves to `this`. @@ -106,13 +107,13 @@

Widgets in Enketo Core

_reset() { this.value = ''; this.originalInputValue = ''; - this.element.classList.add( 'empty' ); + this.element.classList.add('empty'); } - _change( ev ) { + _change(ev) { // propagate value changes to original input and make sure a change event is fired this.originalInputValue = ev.target.value; - this.element.classList.remove( 'empty' ); + this.element.classList.remove('empty'); } /* @@ -140,16 +141,15 @@

Widgets in Enketo Core

* Obtain the current value from the widget. Usually required. */ get value() { - return this.element.classList.contains( 'empty' ) ? '' : this.range.value; + return this.element.classList.contains('empty') ? '' : this.range.value; } /* * Set a value in the widget. Usually required. */ - set value( value ) { + set value(value) { this.range.value = value; } - } export default MyWidget; @@ -159,13 +159,12 @@

Widgets in Enketo Core

import ExampleWidget from '../../src/widget/example/my-widget';
 import { runAllCommonWidgetTests } from '../helpers/testWidget';
 
-const FORM =
-    `<label class="question or-appearance-my-widget">
+const FORM = `<label class="question or-appearance-my-widget">
         <input type="number" name="/data/node">
     </label>`;
 const VALUE = '2';
 
-runAllCommonWidgetTests( ExampleWidget, FORM, VALUE );
+runAllCommonWidgetTests(ExampleWidget, FORM, VALUE);
 

DO