diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dfc038..1c7fb2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,12 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). -[1.6.0] +[1.6.1] - 2019-07-24 +--------------------- +##### Changed +- Updated form engine and other dependencies. + +[1.6.0] - 2019-02-21 --------------------- ##### Removed - Badly broken self-reference check. diff --git a/build/FormModel-bundle.js b/build/FormModel-bundle.js index a5cc193..7a57adf 100644 --- a/build/FormModel-bundle.js +++ b/build/FormModel-bundle.js @@ -1,7 +1,7 @@ (function () { 'use strict'; - var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; + var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; function createCommonjsModule(fn, module) { return module = { exports: {} }, fn(module, module.exports), module.exports; @@ -555,15 +555,20 @@ })); }); - /* global ArrayBuffer, Uint8Array */ + /** + * Various utilities. + * + * @module utils + */ + let cookies; /** * Parses an Expression to extract all function calls and theirs argument arrays. * - * @param {String} expr The expression to search - * @param {String} func The function name to search for - * @return {>} The result array, where each result is an array containing the function call and array of arguments. + * @param {string} expr - The expression to search + * @param {string} func - The function name to search for + * @return {Array.Array.} The result array, where each result is an array containing the function call and array of arguments. */ function parseFunctionFromExpression( expr, func ) { let index; @@ -615,9 +620,9 @@ return str; } - // Because iOS gives any camera-provided file the same filename, we need to a + // Because iOS gives any camera-provided file the same filename, we need to a // unique-ified filename. - // + // // See https://github.com/kobotoolbox/enketo-express/issues/374 function getFilename( file, postfix ) { let filenameParts; @@ -635,9 +640,10 @@ } /** - * Converts NodeLists or DOMtokenLists to an array - * @param {[type]} list [description] - * @return {[type]} [description] + * Converts NodeLists or DOMtokenLists to an array. + * + * @param {NodeList|DOMTokenList} list + * @return {Array} */ function toArray( list ) { const array = []; @@ -712,7 +718,7 @@ /** * Update a HTML anchor to serve as a download or reset it if an empty objectUrl is provided. - * + * * @param {HTMLElement} anchor the anchor element * @param {*} objectUrl the objectUrl to download * @param {*} fileName the filename of the file @@ -725,6 +731,51 @@ anchor.setAttribute( 'download', fileName || '' ); } + /** + * @function resizeImage + * + * @param {File} file - image file to be resized + * @param {number} maxPixels - maximum pixels of resized image + * + * @return {Promise} promise of resized image blob + */ + function resizeImage( file, maxPixels ) { + return new Promise( ( resolve, reject ) => { + let image = new Image(); + image.src = URL.createObjectURL( file ); + image.onload = () => { + let width = image.width; + let height = image.height; + + if ( width <= maxPixels && height <= maxPixels ) { + resolve( file ); + } + + let newWidth; + let newHeight; + + if ( width > height ) { + newHeight = height * ( maxPixels / width ); + newWidth = maxPixels; + } else { + newWidth = width * ( maxPixels / height ); + newHeight = maxPixels; + } + + let canvas = document.createElement( 'canvas' ); + canvas.width = newWidth; + canvas.height = newHeight; + + let context = canvas.getContext( '2d' ); + + context.drawImage( image, 0, 0, newWidth, newHeight ); + + canvas.toBlob( resolve, file.type ); + }; + image.onerror = reject; + } ); + } + var jquery = createCommonjsModule(function (module) { /*! * jQuery JavaScript Library v3.3.1 @@ -11059,26 +11110,37 @@ } ); }); + /** + * @module dom-utils + */ + /** * Gets siblings that match selector and self _in DOM order_. - * @param {} element - * @param {*} selector + * + * @param {Node} element - Target element. + * @param {*} selector - A CSS selector. + * @return {Array} Array of sibling nodes plus target element. */ function getSiblingElementsAndSelf( element, selector ) { return _getSiblingElements( element, selector, [ element ] ); } + /** + * Gets siblings that match selector _in DOM order_. + * + * @param {Node} element - Target element. + * @param {*} selector - A CSS selector. + * @return {Array} Array of sibling nodes. + */ function getSiblingElements( element, selector ) { return _getSiblingElements( element, selector ); } - function _getSiblingElements( element, selector, startArray = [] ) { + function _getSiblingElements( element, selector = '*', startArray = [] ) { const siblings = startArray; let prev = element.previousElementSibling; let next = element.nextElementSibling; - selector = typeof selector === 'undefined' ? '*' : selector; - while ( prev ) { if ( prev.matches( selector ) ) { siblings.unshift( prev ); @@ -11095,13 +11157,48 @@ return siblings; } + function getAncestors( element, selector = '*' ) { + const ancestors = []; + let parent = element.parentElement; + + while ( parent ) { + if ( parent.matches( selector ) ) { + // document order + ancestors.unshift( parent ); + } + parent = parent.parentElement; + } + + return ancestors; + } + + function closestAncestorUntil( element, filterSelector, endSelector ) { + let parent = element.parentElement; + let found = null; + + while ( parent && !found ) { + if ( parent.matches( filterSelector ) ) { + found = parent; + } + parent = endSelector && parent.matches( endSelector ) ? null : parent.parentElement; + } + + return found; + } + + /** + * Removes all children elements. + * + * @param {Node} element - Target element. + * @return {undefined} + */ function empty( element ) { [ ...element.children ].forEach( el => el.remove() ); } - /** + /** * Adapted from https://stackoverflow.com/a/46522991/3071529 - * + * * A storage solution aimed at replacing jQuerys data function. * Implementation Note: Elements are stored in a (WeakMap)[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap]. * This makes sure the data is garbage collected when the node is removed. @@ -11131,6 +11228,13 @@ } }; + /** + * A custom error type for form logic + * + * @class + * @extends Error + * @param {string} message - Optional message. + */ function FormLogicError( message ) { this.message = message || 'unknown'; this.name = 'FormLogicError'; @@ -11160,9 +11264,14 @@ 'webMapId': '', 'hasZ': true, 'basemaps': [ 'streets' ] - } + }, + 'textMaxChars': 2000 }; + /** + * @module format + */ + let _locale = navigator.language; const NUMBER = '0-9\u0660-\u0669'; const TIME_PART = `[:${NUMBER}]+`; @@ -11178,30 +11287,54 @@ return timeStr.replace( /[\u200E\u200F]/g, '' ); } + /** + * @namespace time + */ const time = { // For now we just look at a subset of numbers in Arabic and Latin. There are actually over 20 number scripts and :digit: doesn't work in browsers + /** + * @type {string} + */ get hour12() { return this.hasMeridian( _getCleanLocalTime() ); }, + /** + * @type {string} + */ get pmNotation() { 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 ) ); }, + /** + * @param dt + */ meridianNotation( dt ) { let matches = _getCleanLocalTime( dt ).match( HAS_MERIDIAN ); if ( matches && matches.length ) { matches = matches.filter( item => !!item ); - return matches[ matches.length - 1 ]; + return matches[ matches.length - 1 ].trim(); } return null; }, + /** + * Whether time string has meridian parts + * + * @param {string} time - time string + */ hasMeridian( time ) { return HAS_MERIDIAN.test( _cleanSpecialChars( time ) ); } }; + /** + * @module types + **/ + const types = { 'string': { convert( x ) { @@ -11252,7 +11385,7 @@ }, 'date': { validate( x ) { - const pattern = /([0-9]{4})-([0-9]{2})-([0-9]{2})/; + 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 ] ); @@ -11269,7 +11402,7 @@ // 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().pad( 4 )}-${( date.getMonth() + 1 ).toString().pad( 2 )}-${date.getDate().toString().pad( 2 )}`; + '' : `${date.getFullYear().toString().pad(4)}-${(date.getMonth() + 1).toString().pad(2)}-${date.getDate().toString().pad(2)}`; } 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 @@ -11380,7 +11513,7 @@ if ( tz.length === 0 ) { offset = new Date().getTimezoneOffsetAsTime(); } else { - offset = `${tz[ 0 ] + tz[ 1 ].pad( 2 )}:${tz[ 2 ] ? tz[ 2 ].pad( 2 ) : '00'}`; + offset = `${tz[0] + tz[1].pad(2)}:${tz[2] ? tz[2].pad(2) : '00'}`; } x = `${o.hours}:${o.minutes}:${o.seconds}${o.milliseconds ? `.${o.milliseconds}` : ''}${offset}`; @@ -11452,53 +11585,160 @@ } }; + /** + * @module event + */ // TODO: add second "propagate" parameter to constructors to add .enketo namespace to event. + /** + * Data update event + * + * @param detail + * @return {CustomEvent} + */ function DataUpdate( detail ) { return new CustomEvent( 'dataupdate', { detail } ); } + /** + * Fake focus event. + * + * @return {CustomEvent} + */ function FakeFocus() { - return new CustomEvent( 'fakefocus' ); + return new CustomEvent( 'fakefocus', { bubbles: true } ); } + /** + * Apply focus event. + * + * @return {CustomEvent} + */ function ApplyFocus() { return new CustomEvent( 'applyfocus' ); } + /** + * Page flip event. + * + * @return {CustomEvent} + */ function PageFlip() { - return new CustomEvent( 'pageflip' ); + return new CustomEvent( 'pageflip', { bubbles: true } ); } + /** + * Removed event. + * + * @param detail + * @return {CustomEvent} + */ function Removed( detail ) { - return new CustomEvent( 'removed', { detail } ); + return new CustomEvent( 'removed', { detail, bubbles: true } ); } + /** + * Add repeat event. + * + * @param detail + * @return {CustomEvent} + */ function AddRepeat( detail ) { return new CustomEvent( 'addrepeat', { detail, bubbles: true } ); } + /** + * Remove repeat event. + * + * @return {CustomEvent} + */ function RemoveRepeat() { return new CustomEvent( 'removerepeat', { bubbles: true } ); } + /** + * Change language event. + * + * @return {CustomEvent} + */ function ChangeLanguage() { return new CustomEvent( 'changelanguage', { bubbles: true } ); } + /** + * Change event. + * + * @return {Event} + */ function Change() { return new Event( 'change', { bubbles: true } ); } + /** + * Input event. + * + * @return {Event} + */ function Input() { return new Event( 'input', { bubbles: true } ); } + /** + * Input update event + * + * @return {CustomEvent} + */ function InputUpdate() { return new CustomEvent( 'inputupdate', { bubbles: true } ); } - var event = { + /** + * Edited event. + * + * @return {CustomEvent} + */ + function Edited() { + return new CustomEvent( 'edited', { bubbles: true } ); + } + + /** + * Validation complete event. + * + * @return {CustomEvent} + */ + function ValidationComplete() { + return new CustomEvent( 'validationcomplete', { bubbles: true } ); + } + + /** + * Invalidated event. + * + * @return {CustomEvent} + */ + function Invalidated() { + return new CustomEvent( 'invalidated', { bubbles: true } ); + } + + /** + * Progress update event + * + * @param detail + * @return {CustomEvent} + */ + function ProgressUpdate( detail ) { + return new CustomEvent( 'progressupdate', { detail, bubbles: true } ); + } + + /** + * Go to hidden event. + * + * @return {CustomEvent} + */ + function GoToHidden() { + return new CustomEvent( 'gotohidden', { bubbles: true } ); + } + + var events = { DataUpdate, FakeFocus, ApplyFocus, @@ -11509,7 +11749,12 @@ ChangeLanguage, Change, Input, - InputUpdate + InputUpdate, + Edited, + ValidationComplete, + Invalidated, + ProgressUpdate, + GoToHidden }; /** @@ -11560,8 +11805,9 @@ /** * Pads a string with prefixed zeros until the requested string length is achieved. - * @param {number} digits [description] - * @return {String|string} [description] + * + * @param {number} digits - The desired string length. + * @return {string} - Padded string. */ String.prototype.pad = function( digits ) { let x = this; @@ -14265,7 +14511,7 @@ s0 = peg$currPos; s1 = peg$parseQName(); if (s1 !== peg$FAILED) { - s2 = peg$c114(s1); + s2 = peg$c114(); if (s2) { s2 = void 0; } else { @@ -16920,9 +17166,10 @@ * 2. We could assume local time. * Since a date widget would return the format '2012-02-03' and we'd like the constraint ". < today()" to work, * we have to choose option 2. + * Note: the timezone offset has to be obtained from the actual date in order to account for DST! */ if (/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/.test(value)) { - value += 'T00:00:00.000' + (new Date()).getTimezoneOffsetAsTime(); + value += 'T00:00:00.000' + (new Date(value)).getTimezoneOffsetAsTime(); } BaseType.call(this, value, 'date', [ @@ -21009,13 +21256,9 @@ /** * Class dealing with the XML Model of a form * - * @constructor - * @param {{modelStr: string, ?instanceStr: string, ?external: <{id: string, xml: xmlDocument }>, ?submitted: boolean }} data: - * data object containing XML model, - * (partial) XML instance to load, - * external data array - * flag to indicate whether data was submitted before - * @param {?{?full:boolean}} options Whether to initialize the full model or only the primary instance + * @class + * @param {{modelStr: string, instanceStr: string=, external: Array.<{id: string, xml: XMLDocument}>=, submitted: boolean= }} data - data object containing XML model, (partial) XML instance to load, external data array, flag to indicate whether data was submitted before + * @param {{full:boolean=}=} options - Whether to initialize the full model or only the primary instance */ FormModel = function( data, options ) { @@ -21104,7 +21347,7 @@ // the default model this.xml = parser$1.parseFromString( this.data.modelStr, 'text/xml' ); this.throwParserErrors( this.xml, this.data.modelStr ); - // add external data to model + // add external data to model this.data.external.forEach( instance => { id = `instance "${instance.id}"` || 'instance unknown'; instanceDoc = that.getSecondaryInstance( instance.id ); @@ -21129,7 +21372,7 @@ } } ); - // TODO: in the future, we should search for jr://instance/session and + // 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 ) { @@ -21217,9 +21460,9 @@ /** * For some unknown reason we cannot use doc.getElementById(id) or doc.querySelector('#'+id) * in IE11. This function is a replacement for this specifically to find a secondary instance. - * - * @param {string} id [description] - * @return {Element} [description] + * + * @param {string} id - DOM element id. + * @return {Element} */ FormModel.prototype.getSecondaryInstance = function( id ) { let instanceEl; @@ -21254,6 +21497,9 @@ /** * Alternative adoptNode on IE11 (http://stackoverflow.com/questions/1811116/ie-support-for-dom-importnode) * TODO: remove to be replaced by separate IE11-only polyfill file/service + * + * @param node + * @param allChildren */ FormModel.prototype.importNode = function( node, allChildren ) { let i; @@ -21315,13 +21561,13 @@ 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, '' ); /** - * Comments aren't merging in document order (which would be impossible also). + * 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). */ @@ -21330,12 +21576,12 @@ /** * Normally records will not contain the special "jr:template" attribute. However, we should still be able to deal with - * this if they do, including the old hacked non-namespaced "template" attribute. + * this if they do, including the old hacked non-namespaced "template" attribute. * https://github.com/enketo/enketo-core/issues/376 - * + * * The solution if these are found is to delete the node. - * - * Since the record is not a FormModel instance we revert to a very aggressive querySelectorAll that selects all + * + * Since the record is not a FormModel instance we revert to a very aggressive querySelectorAll that selects all * nodes with a template attribute name IN ANY NAMESPACE. */ @@ -21348,9 +21594,9 @@ /** * To comply with quirky behaviour of repeats in XForms, we manually create the correct number of repeat instances * before merging. This resolves these two issues: - * a) Multiple repeat instances in record are added out of order when merged into a record that contains fewer + * a) Multiple repeat instances in record are added out of order when merged into a record that contains fewer * repeat instances, see https://github.com/kobotoolbox/enketo-express/issues/223 - * b) If a repeat node is missing from a repeat instance (e.g. the 2nd) in a record, and that repeat instance is not + * b) If a repeat node is missing from a repeat instance (e.g. the 2nd) in a record, and that repeat instance is not * in the model, that node will be missing in the result. */ // TODO: ES6 for (var node of record.querySelectorAll('*')){} @@ -21382,7 +21628,7 @@ } } ); - /** + /** * 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. */ @@ -21429,12 +21675,12 @@ } /** - * Beware: merge.Get(0) returns an ActiveXObject in IE11. We turn this + * 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$1.parseFromString( merger.Get( 1 ), 'text/xml' ); - /** + /** * To properly show 0 repeats, if the form definition contains multiple default instances * and the record contains none, we have to iterate trough the templates object, and * 1. check for each template path, whether the record contained more than 0 of these nodes @@ -21463,10 +21709,11 @@ /** * Creates an XPath from a node - * @param { XMLElement} node XML node - * @param {string=} rootNodeName if absent the root is #document - * @param {boolean=} includePosition whether or not to include the positions /path/to/repeat[2]/node - * @return {string} XPath + * + * @param { XMLElement} node - XML node + * @param {string=} rootNodeName - if absent the root is #document + * @param {boolean=} includePosition - whether or not to include the positions /path/to/repeat[2]/node + * @return {string} XPath */ FormModel.prototype.getXPath = function( node, rootNodeName, includePosition ) { let index; @@ -21504,11 +21751,11 @@ return `/${steps.reverse().join( '/' )}`; }; - /** + /** * Obtains the index of a repeat instance within its own series. - * - * @param {[type]} node [description] - * @return {[type]} [description] + * + * @param {Node} node + * @return {number} index */ FormModel.prototype.getRepeatIndex = node => { let index = 0; @@ -21528,7 +21775,7 @@ /** * Trims values - * + * */ FormModel.prototype.trimValues = function() { this.node( null, null, { @@ -21540,7 +21787,6 @@ /** * [deprecateId description] - * @return {[type]} [description] */ FormModel.prototype.setInstanceIdAndDeprecatedId = function() { let instanceIdObj; @@ -21608,9 +21854,9 @@ /** * Adds a able instance node in a particular series of a repeat. * - * @param {string} repeatPath absolute path of a repeat - * @param {number} repeatSeriesIndex index of the repeat series that gets a new repeat (this is always 0 for non-nested repeats) - * @param {boolean} merge whether this operation is part of a merge operation (won't send dataupdate event, clears all values and + * @param {string} repeatPath - absolute path of a repeat + * @param {number} repeatSeriesIndex - index of the repeat series that gets a new repeat (this is always 0 for non-nested repeats) + * @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 ) { @@ -21635,14 +21881,14 @@ } /** - * If templatenodes and insertAfterNode(s) have been identified + * If templatenodes and insertAfterNode(s) have been identified */ if ( template && insertAfterNode ) { templateClone = template.cloneNode( true ); insertAfterNode.after( templateClone ); this.removeOrdinalAttributes( templateClone ); - // We should not automatically add ordinal attributes for an existing record as the ordinal values cannot be determined. + // 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 ] ); @@ -21663,7 +21909,6 @@ FormModel.prototype.addOrdinalAttribute = function( repeat, firstRepeatInSeries ) { const enkNs = this.getNamespacePrefix( ENKETO_XFORMS_NS ); - firstRepeatInSeries = firstRepeatInSeries || repeat; }; FormModel.prototype.removeOrdinalAttributes = el => { @@ -21671,10 +21916,10 @@ /** * Obtains a single series of repeat element; - * - * @param {string} repeatPath The absolute path of the repeat. - * @param {number} repeatSeriesIndex The index of the series of that repeat. - * @return {} Array of all repeat elements in a series. + * + * @param {string} repeatPath - The absolute path of the repeat. + * @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 ) { let pathSegments; @@ -21738,8 +21983,8 @@ /** * Determines the index of a repeated node amongst all nodes with the same XPath selector * - * @param {Element} element element - * @return {number} [description] + * @param {Element} element + * @return {number} determined index. */ FormModel.prototype.determineIndex = function( element ) { const that = this; @@ -21757,8 +22002,7 @@ }; /** - * Extracts all templates from the model and stores them in a Javascript object poperties as Jquery collections - * @return {[type]} [description] + * Extracts all templates from the model and stores them in a Javascript object poperties as Jquery collections. */ FormModel.prototype.extractTemplates = function() { const that = this; @@ -21824,7 +22068,7 @@ FormModel.prototype.getTemplateNodes = function() { const jrPrefix = this.getNamespacePrefix( JAVAROSA_XFORMS_NS ); // For now we support both the official namespaced template and the hacked non-namespaced template attributes - // Note: due to an MS Edge bug, we use the slow JS XPath evaluator here. It would be VERY GOOD for performance + // Note: due to an MS Edge bug, we use the slow JS XPath evaluator here. It would be VERY GOOD for performance // to switch back once the Edge bug is fixed. The bug results in not finding any templates. // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/9544701/ return this.evaluate( `/model/instance[1]/*//*[@template] | /model/instance[1]/*//*[@${jrPrefix}:template]`, 'nodes', null, null, false ); @@ -21862,7 +22106,7 @@ }; /** - * There is a huge historic issue (stemming from JavaRosa) that has resulted in the usage of incorrect formulae + * There is a huge historic issue (stemming from JavaRosa) that has resulted in the usage of incorrect formulae * on nodes inside repeat nodes. * Those formulae use absolute paths when relative paths should have been used. See more here: * http://opendatakit.github.io/odk-xform-spec/#a-big-deviation-with-xforms @@ -21877,7 +22121,7 @@ * the second rep_a repeat. * * This function should be removed when we can reasonbly expect not many 'old XForms' to be in use any more. - * + * * Already it should leave proper XPaths untouched. * * @param {string} expr the XPath expression @@ -21920,9 +22164,9 @@ 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. + * 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). - * + * * For now it has therefore been restricted to only look at the top-level node in the primary instance and in the secondary instances. * We can always expand that later. */ @@ -22005,7 +22249,7 @@ return expr; }; - /** + /** * Replace instance('id') with an absolute path * Doing this here instead of adding an instance() function to the XPath evaluator, means we can keep using * the much faster native evaluator in most cases! @@ -22029,16 +22273,16 @@ } ); }; - /** + /** * Replaces current() with /absolute/path/to/node to ensure the context is shifted to the primary instance - * + * * Doing this here instead of adding a current() function to the XPath evaluator, means we can keep using * the much faster native evaluator in most cases! * * Root will be shifted, and repeat positions injected, **later on**, so it's not included here. * * @param {string} expr original expression - * @param {string} contextSelector context selector + * @param {string} contextSelector context selector * @return {string} new expression */ FormModel.prototype.replaceCurrentFn = ( expr, contextSelector ) => { @@ -22056,8 +22300,10 @@ * Replaces indexed-repeat(node, path, position, path, position, etc) substrings by converting them * to their native XPath equivalents using [position() = x] predicates * - * @param {string} expr the XPath expression - * @return {string} converted XPath expression + * @param {string} expr - the XPath expression. + * @param {string} selector + * @param {string} index + * @return {string} converted XPath expression */ FormModel.prototype.replaceIndexedRepeatFn = function( expr, selector, index ) { const that = this; @@ -22074,7 +22320,7 @@ 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 + // 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}]` ); @@ -22110,7 +22356,7 @@ const replacements = this.convertPullDataFn( expr, selector, index ); for ( const pullData in replacements ) { - if ( replacements.hasOwnProperty( pullData ) ) { + 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}"` ); @@ -22198,26 +22444,24 @@ console.error( 'no context element found', selector, index ); } - // cache key includes the number of repeated context nodes, + // 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( '|' ); // These functions need to come before makeBugCompliant. - // An expression transformation with indexed-repeat or pulldata cannot be cached because in + // 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 ); - let intermediate = ''; // if no cached conversion exists if ( !this.convertedExpressions[ cacheKey ] ) { expr = expr.trim(); expr = this.replaceInstanceFn( expr ); expr = this.replaceVersionFn( expr ); expr = this.replaceCurrentFn( expr, this.getXPath( context, 'instance', true ) ); - intermediate = expr; // shiftRoot should come after replaceCurrentFn expr = this.shiftRoot( expr ); // path corrections for repeated nodes: http://opendatakit.github.io/odk-xform-spec/#a-big-deviation-with-xforms @@ -22225,9 +22469,9 @@ expr = this.makeBugCompliant( expr, selector, index ); } // decode - // expr = expr.replace( /</g, '<' ); - //expr = expr.replace( />/g, '>' ); - // expr = expr.replace( /"/g, '"' ); + expr = expr.replace( /</g, '<' ); + expr = expr.replace( />/g, '>' ); + expr = expr.replace( /"/g, '"' ); if ( cacheable ) { this.convertedExpressions[ cacheKey ] = expr; } @@ -22246,7 +22490,7 @@ // translate typeStr to number according to DOM level 3 XPath constants for ( resTypeNum in resultTypes ) { - if ( resultTypes.hasOwnProperty( resTypeNum ) ) { + if ( Object.prototype.hasOwnProperty.call( resultTypes, resTypeNum ) ) { resTypeNum = Number( resTypeNum ); if ( resultTypes[ resTypeNum ][ 0 ] === resTypeStr ) { break; @@ -22267,7 +22511,7 @@ } } - // if that didn't work, try the slow XPathJS evaluator + // if that didn't work, try the slow XPathJS evaluator if ( !result ) { try { if ( typeof doc.jsEvaluate === 'undefined' ) { @@ -22276,8 +22520,7 @@ // console.log( 'trying the slow enketo-xpathjs "openrosa" evaluator for', expr, index ); result = doc.jsEvaluate( expr, context, this.getNsResolver(), resTypeNum, null ); } catch ( e ) { - console.error( e ); - throw new FormLogicError( `Could not evaluate: "${expr}", message: "${e.message}", original:"${original}", intermediate:"${intermediate}"` ); + throw new FormLogicError( `Could not evaluate: ${expr}, message: ${e.message}` ); } } @@ -22286,7 +22529,7 @@ // for type = any, see if a valid string, number or boolean is returned if ( resTypeNum === 0 ) { for ( resTypeNum in resultTypes ) { - if ( resultTypes.hasOwnProperty( resTypeNum ) ) { + if ( Object.prototype.hasOwnProperty.call( resultTypes, resTypeNum ) ) { resTypeNum = Number( resTypeNum ); if ( resTypeNum === Number( result.resultType ) && resTypeNum > 0 && resTypeNum < 4 ) { response = result[ resultTypes[ resTypeNum ][ 2 ] ]; @@ -22311,11 +22554,11 @@ /** * Class dealing with nodes and nodesets of the XML instance * - * @constructor - * @param {string=} selector simpleXPath or jQuery selectedor - * @param {number=} index the index of the target node with that selector - * @param {?{onlyLeaf: boolean, noEmpty: boolean}=} filter filter object for the result nodeset - * @param { FormModel } model instance of FormModel + * @class + * @param {string=} selector - simpleXPath or jQuery selectedor + * @param {number=} index - the index of the target node with that selector + * @param {?{onlyLeaf: boolean, noEmpty: boolean}=} filter - filter object for the result nodeset + * @param { FormModel } model - instance of FormModel */ Nodeset = function( selector, index, filter, model ) { const defaultSelector = model.hasInstance ? '/model/instance[1]//*' : '//*'; @@ -22368,7 +22611,7 @@ /** * Sets the index of the Nodeset instance * - * @param {=number?} index The 0-based index + * @param {number=} index - The 0-based index */ Nodeset.prototype.setIndex = function( index ) { this.index = index; @@ -22377,8 +22620,8 @@ /** * Sets data node values. * - * @param {(string|Array.)=} newVals The new value of the node. - * @param {?string=} xmlDataType XML data type of the node + * @param {(string|Array.)=} newVals - The new value of the node. + * @param {string=} xmlDataType - XML data type of the node * * @return {?*} wrapping {?boolean}; null is returned when the node is not found or multiple nodes were selected, * otherwise an object with update information is returned. @@ -22410,7 +22653,7 @@ customData = this.model.getUpdateEventData( target, xmlDataType ); updated = ( customData ) ? jquery.extend( {}, updated, customData ) : updated; - this.model.events.dispatchEvent( event.DataUpdate( updated ) ); + this.model.events.dispatchEvent( events.DataUpdate( updated ) ); //add type="file" attribute for file references if ( xmlDataType === 'binary' ) { @@ -22484,7 +22727,7 @@ this._nodes = null; // For internal use - this.model.events.dispatchEvent( event.DataUpdate( { + this.model.events.dispatchEvent( events.DataUpdate( { nodes: null, repeatPath, repeatIndex @@ -22495,7 +22738,7 @@ while ( nextNode && nextNode.nodeName == nodeName ) { nextNode = nextNode.nextElementSibling; - this.model.events.dispatchEvent( event.DataUpdate( { + this.model.events.dispatchEvent( events.DataUpdate( { nodes: null, repeatPath, repeatIndex: repeatIndex++ @@ -22503,7 +22746,7 @@ } // For external use, if required with custom data. - this.model.events.dispatchEvent( event.Removed( removalEventData ) ); + this.model.events.dispatchEvent( events.Removed( removalEventData ) ); } else { console.error( `could not find node ${this.selector} with index ${this.index} to remove ` ); @@ -22512,9 +22755,10 @@ /** * Convert a value to a specified data type( though always stringified ) - * @param {?string=} x value to convert - * @param {?string=} xmlDataType XML data type - * @return {string} string representation of converted value + * + * @param {?string=} x - value to convert + * @param {?string=} xmlDataType - XML data type + * @return {string} - string representation of converted value */ Nodeset.prototype.convert = ( x, xmlDataType ) => { if ( x.toString() === '' ) { @@ -22546,8 +22790,9 @@ /** * Validate a value with an XPath Expression and /or xml data type - * @param {?string=} expr XPath expression - * @param {?string=} xmlDataType XML datatype + * + * @param {?string=} expr - XPath expression + * @param {?string=} xmlDataType - XML datatype * @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 ) { @@ -22607,7 +22852,13 @@ // Expose types to facilitate extending with custom types FormModel.prototype.types = types; - // This is NOT a complete list of all enketo-core UI strings. Use a parser to find + /** + * Placeholder module for translator. It is meant to be overwritten by a translator used in your app. + * + * @module fake-translator + */ + + // 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': { @@ -22697,9 +22948,9 @@ /** * Meant to be replaced by a real translator in the app that consumes enketo-core * - * @param {String} key translation key - * @param {*} key translation options - * @return {String} translation output + * @param {string} key - translation key + * @param {*} options - translation options + * @return {string} translation output */ function t( key, options ) { let str = ''; @@ -22721,46 +22972,48 @@ /** * Form control (input, select, textarea) helper functions. + * + * @module input */ - var input = { - // Multiple nodes are limited to ones of the same input type (better implemented as JQuery plugin actually) - getWrapNodes( $inputs ) { - const type = this.getInputType( $inputs.eq( 0 ) ); - return ( type === 'fieldset' ) ? $inputs : $inputs.closest( '.question, .calculation' ); + var inputHelper = { + getWrapNode( control ) { + return control.closest( '.question, .calculation' ); }, - /** very inefficient, should actually not be used **/ - getProps( $input ) { - if ( $input.length !== 1 ) { - return console.error( 'getProps(): no input node provided or multiple' ); - } + getWrapNodes( controls ) { + const result = []; + controls.forEach( control => { + const question = this.getWrapNode( control ); + if ( !result.includes( question ) ) { + result.push( question ); + } + } ); + return result; + }, + getProps( control ) { return { - path: this.getName( $input ), - ind: this.getIndex( $input ), - inputType: this.getInputType( $input ), - xmlType: this.getXmlType( $input ), - constraint: this.getConstraint( $input ), - calculation: this.getCalculation( $input ), - relevant: this.getRelevant( $input ), - readonly: this.getReadonly( $input ), - val: this.getVal( $input ), - required: this.getRequired( $input ), - enabled: this.isEnabled( $input ), - multiple: this.isMultiple( $input ) + 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 ) }; }, - getInputType( $input ) { - let nodeName; - if ( $input.length !== 1 ) { - return ''; //console.error('getInputType(): no input node provided or multiple'); - } - nodeName = $input.prop( 'nodeName' ).toLowerCase(); + getInputType( control ) { + const nodeName = control.nodeName.toLowerCase(); if ( nodeName === 'input' ) { - if ( $input.data( 'drawing' ) ) { + if ( control.dataset.drawing ) { return 'drawing'; } - if ( $input.attr( 'type' ).length > 0 ) { - return $input.attr( 'type' ).toLowerCase(); + if ( control.type ) { + return control.type.toLowerCase(); } return console.error( ' node has no type' ); @@ -22774,102 +23027,104 @@ return console.error( 'unexpected input node type provided' ); } }, - getConstraint( $input ) { - return $input.attr( 'data-constraint' ); + getConstraint( control ) { + return control.dataset.constraint; }, - getRequired( $input ) { + getRequired( control ) { // only return value if input is not a table heading input - if ( $input.parentsUntil( '.or', '.or-appearance-label' ).length === 0 ) { - return $input.attr( 'data-required' ); + if ( !closestAncestorUntil( control, '.or-appearance-label', '.or' ) ) { + return control.dataset.required; } }, - getRelevant( $input ) { - return $input.attr( 'data-relevant' ); + getRelevant( control ) { + return control.dataset.relevant; }, - getReadonly( $input ) { - return $input.is( '[readonly]' ); + getReadonly( control ) { + return control.matches( '[readonly]' ); }, - getCalculation( $input ) { - return $input.attr( 'data-calculate' ); + getCalculation( control ) { + return control.dataset.calculate; }, - getXmlType( $input ) { - if ( $input.length !== 1 ) { - return console.error( 'getXMLType(): no input node provided or multiple' ); - } - return $input.attr( 'data-type-xml' ); + getXmlType( control ) { + return control.dataset.typeXml; }, - getName( $input ) { - let name; - if ( $input.length !== 1 ) { - return console.error( 'getName(): no input node provided or multiple' ); + getName( control ) { + const name = control.dataset.name || control.getAttribute( 'name' ); + if ( !name ) { + console.error( 'input node has no name' ); } - name = $input.attr( 'data-name' ) || $input.attr( 'name' ); - return name || console.error( 'input node has no name' ); + return name; }, - /** - * Used to retrieve the index of a question amidst all questions with the same name. - * The index that can be used to find the corresponding node in the model. - * NOTE: this function should be used sparingly, as it is CPU intensive! - */ - getIndex( $input ) { - if ( $input.length !== 1 ) { - return console.error( 'getIndex(): no input node provided or multiple' ); - } - return this.form.repeats.getIndex( $input[ 0 ].closest( '.or-repeat' ) ); + getIndex( control ) { + return this.form.repeats.getIndex( control.closest( '.or-repeat' ) ); }, - isMultiple( $input ) { - return ( this.getInputType( $input ) === 'checkbox' || $input.attr( 'multiple' ) !== undefined ) ? true : false; + isMultiple( control ) { + return this.getInputType( control ) === 'checkbox' || control.multiple; }, - isEnabled( $input ) { - return !( $input.prop( 'disabled' ) || $input.parentsUntil( '.or', '.disabled' ).length > 0 ); + isEnabled( control ) { + return !( control.disabled || closestAncestorUntil( control, '.disabled', '.or' ) ); }, - getVal( $input ) { - let inputType; - const values = []; - let name; - - if ( $input.length !== 1 ) { - return console.error( 'getVal(): no inputNode provided or multiple' ); + getVal( control ) { + let value = ''; + const inputType = this.getInputType( control ); + const name = this.getName( control ); + + switch ( inputType ) { + case 'radio': + { + 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 ); + break; + } + case 'select': + { + if ( this.isMultiple( control ) ) { + value = [ ...control.querySelectorAll( 'option:checked' ) ].map( option => option.value ); + } else { + const selected = control.querySelector( 'option:checked' ); + value = selected ? selected.value : ''; + } + break; + } + default: + { + value = control.value; + } } - inputType = this.getInputType( $input ); - name = this.getName( $input ); - if ( inputType === 'radio' ) { - return this.getWrapNodes( $input ).find( 'input:checked' ).val() || ''; - } - // checkbox values bug in jQuery as (node.val() should work) - if ( inputType === 'checkbox' ) { - this.getWrapNodes( $input ).find( `input[name="${name}"]:checked` ).each( function() { - values.push( this.value ); - } ); - return values; - } - return $input.val() || ''; + return value || ''; }, find( name, index ) { let attr = 'name'; - if ( this.getInputType( this.form.view.$.find( `[data-name="${name}"]:not(.ignore)` ).eq( 0 ) ) === 'radio' ) { + if ( this.form.view.html.querySelector( `input[type="radio"][data-name="${name}"]:not(.ignore)` ) ) { attr = 'data-name'; } - return this.getWrapNodes( this.form.view.$.find( `[${attr}="${name}"]` ) ).eq( index ).find( `[${attr}="${name}"]:not(.ignore)` ).eq( 0 ); + const question = this.getWrapNodes( this.form.view.html.querySelectorAll( `[${attr}="${name}"]` ) )[ index ]; + + return question ? question.querySelector( `[${attr}="${name}"]:not(.ignore)` ) : null; }, - setVal( $input, value, event$1 = event.InputUpdate() ) { - let $inputs; - const type = this.getInputType( $input ); - const $question = this.getWrapNodes( $input ); - const name = this.getName( $input ); + setVal( control, value, event = events.InputUpdate() ) { + let inputs; + const type = this.getInputType( control ); + const question = this.getWrapNode( control ); + const name = this.getName( control ); if ( type === 'radio' ) { - // TODO: should this revert to name if data-name is not present. Is data-name always present on radiobuttons? - $inputs = $question.find( `[data-name="${name}"]:not(.ignore)` ); + // data-name is always present on radiobuttons + inputs = question.querySelectorAll( `[data-name="${name}"]:not(.ignore)` ); } else { // why not use this.getIndex? - $inputs = $question.find( `[name="${name}"]:not(.ignore)` ); + inputs = question.querySelectorAll( `[name="${name}"]:not(.ignore)` ); if ( type === 'file' ) { // value of file input can be reset to empty but not to a non-empty value if ( value ) { - $input.attr( 'data-loaded-file-name', 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.'); return false; @@ -22904,39 +23159,76 @@ } } - if ( this.isMultiple( $input ) === 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 ]; } - // Trigger an 'inputupdate' event which can be used in widgets to update the widget when the value of its + // Trigger an 'inputupdate' event which can be used in widgets to update the widget when the value of its // original input element has changed **programmatically**. - if ( $inputs.length ) { - const curVal = this.getVal( $input ); + if ( inputs.length ) { + const curVal = this.getVal( control ); if ( curVal === undefined || curVal.toString() !== value.toString() ) { - $inputs.val( value ); + switch ( type ) { + case 'radio': + { + const input = this.getWrapNode( control ).querySelector( `input[type="radio"][data-name="${name}"][value="${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 ) ); + break; + } + case 'select': + { + 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 ) { + option.selected = true; + } else { + control.querySelectorAll( 'option' ).forEach( option => option.selected = false ); + } + } + break; + } + default: + { + control.value = value; + } + } + + // don't trigger on all radiobuttons/checkboxes - if ( event$1 ) { - $inputs[ 0 ].dispatchEvent( event$1 ); + if ( event ) { + inputs[ 0 ].dispatchEvent( event ); } } } - return $inputs[ 0 ]; + return inputs[ 0 ]; }, - validate( $input ) { - return this.form.validateInput( $input ); + validate( control ) { + return this.form.validateInput( control ); } }; - /* - * This file is meant to be overidden with one that uses the app's dialogs. + /** + * This placeholder module is meant to be overwritten with one that uses the app's own dialogs. + * + * @module dialog */ /** - * @param {String | {message: String, heading: String}} content Dialog content + * @param {string | {message: string, heading: string}} content - Dialog content */ function alert( content ) { window.alert( content ); @@ -22944,7 +23236,7 @@ } /** - * @param {String | {message: String, heading: String}} content Dialog content + * @param {string | {message: string, heading: string}} content - Dialog content */ function confirm( content ) { const msg = content.message ? content.message : content; @@ -22962,14 +23254,16 @@ }; /** - * Repeats module. - * + * Repeat module. + * * Two important concepts are used: * 1. The first XLST-added repeat view is cloned to serve as a template of that repeat. * 2. Each repeat series has a sibling .or-repeat-info element that stores info that is relevant to that series. * * Note that with nested repeats you may have many more series of repeats than templates, because a nested repeat * may have multiple series. + * + * @module repeat */ const disableFirstRepeatRemoval = config.repeatOrdinals === true; @@ -22988,21 +23282,25 @@ $repeatInfos = this.form.view.$.find( '.or-repeat-info' ); this.templates = {}; - // Add repeat numbering elements, if repeat has form controls (not just calculations) - $repeatInfos.siblings( '.or-repeat' ) + // Add repeat numbering elements + $repeatInfos + .siblings( '.or-repeat' ) + .prepend( '' ) + // add empty class for calculation-only repeats + .addBack() .filter( function() { - // remove whitespace so we can use :empty css selector + // remove whitespace if ( this.firstChild && this.firstChild.nodeType === 3 ) { this.firstChild.textContent = ''; } - return !!this.querySelector( '.question' ); + return !this.querySelector( '.question' ); } ) - .prepend( '' ); + .addClass( 'empty' ); // Add repeat buttons $repeatInfos.filter( '*:not([data-repeat-fixed]):not([data-repeat-count])' ) .append( '' ) .siblings( '.or-repeat' ) - .append( `
` ); + .append( `
` ); /** * 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. @@ -23032,7 +23330,7 @@ that.updateDefaultFirstRepeatInstance( null, this ); } } ) - // If there is no repeat-count attribute, check how many repeat instances + // If there is no repeat-count attribute, check how many repeat instances // are in the model, and update view if necessary. .each( that.updateViewInstancesFromModel.bind( this ) ); @@ -23101,13 +23399,13 @@ return null; } const name = repeatInfo.dataset.name; - return [ ...repeatInfo.closest( 'form.or' ).querySelectorAll( `.or-repeat-info[data-name="${name}"` ) ].indexOf( repeatInfo ); + return [ ...repeatInfo.closest( 'form.or' ).querySelectorAll( `.or-repeat-info[data-name="${name}"]` ) ].indexOf( repeatInfo ); }, /** * [updateViewInstancesFromModel description] - * @param {[type]} idx not used but part of jQuery.each - * @param {Element} repeatInfo repeatInfo element - * @return {[type]} [description] + * @param {number} idx - not used but part of jQuery.each + * @param {Element} repeatInfo - repeatInfo element + * @return {number} */ updateViewInstancesFromModel( idx, repeatInfo ) { const that = this; @@ -23133,9 +23431,8 @@ }, /** * [updateDefaultFirstRepeatInstance description] - * @param {[type]} idx not use but part of jQeury.each - * @param {Element} repeatInfo repeatInfo element - * @return {[type]} [description] + * @param {number} idx - not used but part of jQuery.each + * @param {Element} repeatInfo - repeatInfo element */ updateDefaultFirstRepeatInstance( idx, repeatInfo ) { let repeatSeriesIndex; @@ -23157,9 +23454,9 @@ }, /** * [updateRepeatInstancesFromCount description] - * @param {[type]} idx not use but part of jQeury.each - * @param {Element} repeatInfo repeatInfo element - * @return {[type]} [description] + * + * @param {number} idx - not used but part of jQuery.each + * @param {Element} repeatInfo - repeatInfo element */ updateRepeatInstancesFromCount( idx, repeatInfo ) { const that = this; @@ -23193,7 +23490,7 @@ if ( toCreate > 0 ) { that.add( repeatInfo, toCreate ); } else if ( toCreate < 0 ) { - toCreate = Math.abs( toCreate ) >= numRepsInView ? -numRepsInView + ( 0 ) : toCreate; + toCreate = Math.abs( toCreate ) >= numRepsInView ? -numRepsInView + ( 0 ) : toCreate; for ( ; toCreate < 0; toCreate++ ) { $last = $repeatInfo.siblings( '.or-repeat' ).last(); this.remove( $last, 0 ); @@ -23209,9 +23506,8 @@ /** * Checks whether repeat count value has been updated and updates repeat instances * accordingly. - * - * @param {[type]} updated [description] - * @return {[type]} [description] + * + * @param {Object} updated */ countUpdate( updated ) { let $repeatInfos; @@ -23219,11 +23515,12 @@ $repeatInfos = this.form.getRelatedNodes( 'data-repeat-count', '.or-repeat-info', updated ); $repeatInfos.each( this.updateRepeatInstancesFromCount.bind( this ) ); }, - /**s - * clone a repeat group/node - * @param {Element} repeatInfo repeatInfo element - * @param {number=} count number of clones to create - * @return {boolean} [description] + /** + * Clone a repeat group/node. + * + * @param {Element} repeatInfo - A repeatInfo element. + * @param {number=} count - Number of clones to create. + * @return {boolean} Cloning success/failure outcome. */ add( repeatInfo, count ) { let $repeats; @@ -23287,7 +23584,7 @@ // even if they are in different series. repeatIndex = repeatIndex || this.getIndex( $clone[ 0 ] ); // This will trigger setting default values, calculations, readonly, relevancy, language updates, and automatic page flips. - $clone[ 0 ].dispatchEvent( event.AddRepeat( [ repeatIndex, byCountUpdate ] ) ); + $clone[ 0 ].dispatchEvent( events.AddRepeat( [ repeatIndex, byCountUpdate ] ) ); // Initialize widgets in clone after default values have been set if ( this.form.widgetsInitialized ) { this.form.widgets.init( $clone, this.form.options ); @@ -23331,7 +23628,7 @@ 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( event.RemoveRepeat() ); + $next[ 0 ].dispatchEvent( events.RemoveRepeat() ); // Now remove the data node that.form.model.node( repeatPath, repeatIndex ).remove(); } ); @@ -23377,11 +23674,13 @@ * Dual licensed under the MIT or GPL Version 2 licenses. * */ - !function(factory){factory(module.exports?jquery:jQuery);}(function($){function init(options){return !options||void 0!==options.allowPageScroll||void 0===options.swipe&&void 0===options.swipeStatus||(options.allowPageScroll=NONE),void 0!==options.click&&void 0===options.tap&&(options.tap=options.click),options||(options={}),options=$.extend({},$.fn.swipe.defaults,options),this.each(function(){var $this=$(this),plugin=$this.data(PLUGIN_NS);plugin||(plugin=new TouchSwipe(this,options),$this.data(PLUGIN_NS,plugin));})}function TouchSwipe(element,options){function touchStart(jqEvent){if(!(getTouchInProgress()||$(jqEvent.target).closest(options.excludedElements,$element).length>0)){var event=jqEvent.originalEvent?jqEvent.originalEvent:jqEvent;if(!event.pointerType||"mouse"!=event.pointerType||0!=options.fallbackToMouseEvents){var ret,touches=event.touches,evt=touches?touches[0]:event;return phase=PHASE_START,touches?fingerCount=touches.length:options.preventDefaultEvents!==!1&&jqEvent.preventDefault(),distance=0,direction=null,currentDirection=null,pinchDirection=null,duration=0,startTouchesDistance=0,endTouchesDistance=0,pinchZoom=1,pinchDistance=0,maximumsMap=createMaximumsData(),cancelMultiFingerRelease(),createFingerData(0,evt),!touches||fingerCount===options.fingers||options.fingers===ALL_FINGERS||hasPinches()?(startTime=getTimeStamp(),2==fingerCount&&(createFingerData(1,touches[1]),startTouchesDistance=endTouchesDistance=calculateTouchesDistance(fingerData[0].start,fingerData[1].start)),(options.swipeStatus||options.pinchStatus)&&(ret=triggerHandler(event,phase))):ret=!1,ret===!1?(phase=PHASE_CANCEL,triggerHandler(event,phase),ret):(options.hold&&(holdTimeout=setTimeout($.proxy(function(){$element.trigger("hold",[event.target]),options.hold&&(ret=options.hold.call($element,event,event.target));},this),options.longTapThreshold)),setTouchInProgress(!0),null)}}}function touchMove(jqEvent){var event=jqEvent.originalEvent?jqEvent.originalEvent:jqEvent;if(phase!==PHASE_END&&phase!==PHASE_CANCEL&&!inMultiFingerRelease()){var ret,touches=event.touches,evt=touches?touches[0]:event,currentFinger=updateFingerData(evt);if(endTime=getTimeStamp(),touches&&(fingerCount=touches.length),options.hold&&clearTimeout(holdTimeout),phase=PHASE_MOVE,2==fingerCount&&(0==startTouchesDistance?(createFingerData(1,touches[1]),startTouchesDistance=endTouchesDistance=calculateTouchesDistance(fingerData[0].start,fingerData[1].start)):(updateFingerData(touches[1]),endTouchesDistance=calculateTouchesDistance(fingerData[0].end,fingerData[1].end),pinchDirection=calculatePinchDirection(fingerData[0].end,fingerData[1].end)),pinchZoom=calculatePinchZoom(startTouchesDistance,endTouchesDistance),pinchDistance=Math.abs(startTouchesDistance-endTouchesDistance)),fingerCount===options.fingers||options.fingers===ALL_FINGERS||!touches||hasPinches()){if(direction=calculateDirection(currentFinger.start,currentFinger.end),currentDirection=calculateDirection(currentFinger.last,currentFinger.end),validateDefaultEvent(jqEvent,currentDirection),distance=calculateDistance(currentFinger.start,currentFinger.end),duration=calculateDuration(),setMaxDistance(direction,distance),ret=triggerHandler(event,phase),!options.triggerOnTouchEnd||options.triggerOnTouchLeave){var inBounds=!0;if(options.triggerOnTouchLeave){var bounds=getbounds(this);inBounds=isInBounds(currentFinger.end,bounds);}!options.triggerOnTouchEnd&&inBounds?phase=getNextPhase(PHASE_MOVE):options.triggerOnTouchLeave&&!inBounds&&(phase=getNextPhase(PHASE_END)),phase!=PHASE_CANCEL&&phase!=PHASE_END||triggerHandler(event,phase);}}else phase=PHASE_CANCEL,triggerHandler(event,phase);ret===!1&&(phase=PHASE_CANCEL,triggerHandler(event,phase));}}function touchEnd(jqEvent){var event=jqEvent.originalEvent?jqEvent.originalEvent:jqEvent,touches=event.touches;if(touches){if(touches.length&&!inMultiFingerRelease())return startMultiFingerRelease(event),!0;if(touches.length&&inMultiFingerRelease())return !0}return inMultiFingerRelease()&&(fingerCount=fingerCountAtRelease),endTime=getTimeStamp(),duration=calculateDuration(),didSwipeBackToCancel()||!validateSwipeDistance()?(phase=PHASE_CANCEL,triggerHandler(event,phase)):options.triggerOnTouchEnd||options.triggerOnTouchEnd===!1&&phase===PHASE_MOVE?(options.preventDefaultEvents!==!1&&jqEvent.cancelable!==!1&&jqEvent.preventDefault(),phase=PHASE_END,triggerHandler(event,phase)):!options.triggerOnTouchEnd&&hasTap()?(phase=PHASE_END,triggerHandlerForGesture(event,phase,TAP)):phase===PHASE_MOVE&&(phase=PHASE_CANCEL,triggerHandler(event,phase)),setTouchInProgress(!1),null}function touchCancel(){fingerCount=0,endTime=0,startTime=0,startTouchesDistance=0,endTouchesDistance=0,pinchZoom=1,cancelMultiFingerRelease(),setTouchInProgress(!1);}function touchLeave(jqEvent){var event=jqEvent.originalEvent?jqEvent.originalEvent:jqEvent;options.triggerOnTouchLeave&&(phase=getNextPhase(PHASE_END),triggerHandler(event,phase));}function removeListeners(){$element.off(START_EV,touchStart),$element.off(CANCEL_EV,touchCancel),$element.off(MOVE_EV,touchMove),$element.off(END_EV,touchEnd),LEAVE_EV&&$element.off(LEAVE_EV,touchLeave),setTouchInProgress(!1);}function getNextPhase(currentPhase){var nextPhase=currentPhase,validTime=validateSwipeTime(),validDistance=validateSwipeDistance(),didCancel=didSwipeBackToCancel();return !validTime||didCancel?nextPhase=PHASE_CANCEL:!validDistance||currentPhase!=PHASE_MOVE||options.triggerOnTouchEnd&&!options.triggerOnTouchLeave?!validDistance&¤tPhase==PHASE_END&&options.triggerOnTouchLeave&&(nextPhase=PHASE_CANCEL):nextPhase=PHASE_END,nextPhase}function triggerHandler(event,phase){var ret,touches=event.touches;return (didSwipe()||hasSwipes())&&(ret=triggerHandlerForGesture(event,phase,SWIPE)),(didPinch()||hasPinches())&&ret!==!1&&(ret=triggerHandlerForGesture(event,phase,PINCH)),didDoubleTap()&&ret!==!1?ret=triggerHandlerForGesture(event,phase,DOUBLE_TAP):didLongTap()&&ret!==!1?ret=triggerHandlerForGesture(event,phase,LONG_TAP):didTap()&&ret!==!1&&(ret=triggerHandlerForGesture(event,phase,TAP)),phase===PHASE_CANCEL&&touchCancel(event),phase===PHASE_END&&(touches?touches.length||touchCancel(event):touchCancel(event)),ret}function triggerHandlerForGesture(event,phase,gesture){var ret;if(gesture==SWIPE){if($element.trigger("swipeStatus",[phase,direction||null,distance||0,duration||0,fingerCount,fingerData,currentDirection]),options.swipeStatus&&(ret=options.swipeStatus.call($element,event,phase,direction||null,distance||0,duration||0,fingerCount,fingerData,currentDirection),ret===!1))return !1;if(phase==PHASE_END&&validateSwipe()){if(clearTimeout(singleTapTimeout),clearTimeout(holdTimeout),$element.trigger("swipe",[direction,distance,duration,fingerCount,fingerData,currentDirection]),options.swipe&&(ret=options.swipe.call($element,event,direction,distance,duration,fingerCount,fingerData,currentDirection),ret===!1))return !1;switch(direction){case LEFT:$element.trigger("swipeLeft",[direction,distance,duration,fingerCount,fingerData,currentDirection]),options.swipeLeft&&(ret=options.swipeLeft.call($element,event,direction,distance,duration,fingerCount,fingerData,currentDirection));break;case RIGHT:$element.trigger("swipeRight",[direction,distance,duration,fingerCount,fingerData,currentDirection]),options.swipeRight&&(ret=options.swipeRight.call($element,event,direction,distance,duration,fingerCount,fingerData,currentDirection));break;case UP:$element.trigger("swipeUp",[direction,distance,duration,fingerCount,fingerData,currentDirection]),options.swipeUp&&(ret=options.swipeUp.call($element,event,direction,distance,duration,fingerCount,fingerData,currentDirection));break;case DOWN:$element.trigger("swipeDown",[direction,distance,duration,fingerCount,fingerData,currentDirection]),options.swipeDown&&(ret=options.swipeDown.call($element,event,direction,distance,duration,fingerCount,fingerData,currentDirection));}}}if(gesture==PINCH){if($element.trigger("pinchStatus",[phase,pinchDirection||null,pinchDistance||0,duration||0,fingerCount,pinchZoom,fingerData]),options.pinchStatus&&(ret=options.pinchStatus.call($element,event,phase,pinchDirection||null,pinchDistance||0,duration||0,fingerCount,pinchZoom,fingerData),ret===!1))return !1;if(phase==PHASE_END&&validatePinch())switch(pinchDirection){case IN:$element.trigger("pinchIn",[pinchDirection||null,pinchDistance||0,duration||0,fingerCount,pinchZoom,fingerData]),options.pinchIn&&(ret=options.pinchIn.call($element,event,pinchDirection||null,pinchDistance||0,duration||0,fingerCount,pinchZoom,fingerData));break;case OUT:$element.trigger("pinchOut",[pinchDirection||null,pinchDistance||0,duration||0,fingerCount,pinchZoom,fingerData]),options.pinchOut&&(ret=options.pinchOut.call($element,event,pinchDirection||null,pinchDistance||0,duration||0,fingerCount,pinchZoom,fingerData));}}return gesture==TAP?phase!==PHASE_CANCEL&&phase!==PHASE_END||(clearTimeout(singleTapTimeout),clearTimeout(holdTimeout),hasDoubleTap()&&!inDoubleTap()?(doubleTapStartTime=getTimeStamp(),singleTapTimeout=setTimeout($.proxy(function(){doubleTapStartTime=null,$element.trigger("tap",[event.target]),options.tap&&(ret=options.tap.call($element,event,event.target));},this),options.doubleTapThreshold)):(doubleTapStartTime=null,$element.trigger("tap",[event.target]),options.tap&&(ret=options.tap.call($element,event,event.target)))):gesture==DOUBLE_TAP?phase!==PHASE_CANCEL&&phase!==PHASE_END||(clearTimeout(singleTapTimeout),clearTimeout(holdTimeout),doubleTapStartTime=null,$element.trigger("doubletap",[event.target]),options.doubleTap&&(ret=options.doubleTap.call($element,event,event.target))):gesture==LONG_TAP&&(phase!==PHASE_CANCEL&&phase!==PHASE_END||(clearTimeout(singleTapTimeout),doubleTapStartTime=null,$element.trigger("longtap",[event.target]),options.longTap&&(ret=options.longTap.call($element,event,event.target)))),ret}function validateSwipeDistance(){var valid=!0;return null!==options.threshold&&(valid=distance>=options.threshold),valid}function didSwipeBackToCancel(){var cancelled=!1;return null!==options.cancelThreshold&&null!==direction&&(cancelled=getMaxDistance(direction)-distance>=options.cancelThreshold),cancelled}function validatePinchDistance(){return null!==options.pinchThreshold?pinchDistance>=options.pinchThreshold:!0}function validateSwipeTime(){var result;return result=options.maxTimeThreshold?!(duration>=options.maxTimeThreshold):!0}function validateDefaultEvent(jqEvent,direction){if(options.preventDefaultEvents!==!1)if(options.allowPageScroll===NONE)jqEvent.preventDefault();else{var auto=options.allowPageScroll===AUTO;switch(direction){case LEFT:(options.swipeLeft&&auto||!auto&&options.allowPageScroll!=HORIZONTAL)&&jqEvent.preventDefault();break;case RIGHT:(options.swipeRight&&auto||!auto&&options.allowPageScroll!=HORIZONTAL)&&jqEvent.preventDefault();break;case UP:(options.swipeUp&&auto||!auto&&options.allowPageScroll!=VERTICAL)&&jqEvent.preventDefault();break;case DOWN:(options.swipeDown&&auto||!auto&&options.allowPageScroll!=VERTICAL)&&jqEvent.preventDefault();break;case NONE:}}}function validatePinch(){var hasCorrectFingerCount=validateFingers(),hasEndPoint=validateEndPoint(),hasCorrectDistance=validatePinchDistance();return hasCorrectFingerCount&&hasEndPoint&&hasCorrectDistance}function hasPinches(){return !!(options.pinchStatus||options.pinchIn||options.pinchOut)}function didPinch(){return !(!validatePinch()||!hasPinches())}function validateSwipe(){var hasValidTime=validateSwipeTime(),hasValidDistance=validateSwipeDistance(),hasCorrectFingerCount=validateFingers(),hasEndPoint=validateEndPoint(),didCancel=didSwipeBackToCancel(),valid=!didCancel&&hasEndPoint&&hasCorrectFingerCount&&hasValidDistance&&hasValidTime;return valid}function hasSwipes(){return !!(options.swipe||options.swipeStatus||options.swipeLeft||options.swipeRight||options.swipeUp||options.swipeDown)}function didSwipe(){return !(!validateSwipe()||!hasSwipes())}function validateFingers(){return fingerCount===options.fingers||options.fingers===ALL_FINGERS||!SUPPORTS_TOUCH}function validateEndPoint(){return 0!==fingerData[0].end.x}function hasTap(){return !!options.tap}function hasDoubleTap(){return !!options.doubleTap}function hasLongTap(){return !!options.longTap}function validateDoubleTap(){if(null==doubleTapStartTime)return !1;var now=getTimeStamp();return hasDoubleTap()&&now-doubleTapStartTime<=options.doubleTapThreshold}function inDoubleTap(){return validateDoubleTap()}function validateTap(){return (1===fingerCount||!SUPPORTS_TOUCH)&&(isNaN(distance)||distanceoptions.longTapThreshold&&DOUBLE_TAP_THRESHOLD>distance}function didTap(){return !(!validateTap()||!hasTap())}function didDoubleTap(){return !(!validateDoubleTap()||!hasDoubleTap())}function didLongTap(){return !(!validateLongTap()||!hasLongTap())}function startMultiFingerRelease(event){previousTouchEndTime=getTimeStamp(),fingerCountAtRelease=event.touches.length+1;}function cancelMultiFingerRelease(){previousTouchEndTime=0,fingerCountAtRelease=0;}function inMultiFingerRelease(){var withinThreshold=!1;if(previousTouchEndTime){var diff=getTimeStamp()-previousTouchEndTime;diff<=options.fingerReleaseThreshold&&(withinThreshold=!0);}return withinThreshold}function getTouchInProgress(){return !($element.data(PLUGIN_NS+"_intouch")!==!0)}function setTouchInProgress(val){$element&&(val===!0?($element.on(MOVE_EV,touchMove),$element.on(END_EV,touchEnd),LEAVE_EV&&$element.on(LEAVE_EV,touchLeave)):($element.off(MOVE_EV,touchMove,!1),$element.off(END_EV,touchEnd,!1),LEAVE_EV&&$element.off(LEAVE_EV,touchLeave,!1)),$element.data(PLUGIN_NS+"_intouch",val===!0));}function createFingerData(id,evt){var f={start:{x:0,y:0},last:{x:0,y:0},end:{x:0,y:0}};return f.start.x=f.last.x=f.end.x=evt.pageX||evt.clientX,f.start.y=f.last.y=f.end.y=evt.pageY||evt.clientY,fingerData[id]=f,f}function updateFingerData(evt){var id=void 0!==evt.identifier?evt.identifier:0,f=getFingerData(id);return null===f&&(f=createFingerData(id,evt)),f.last.x=f.end.x,f.last.y=f.end.y,f.end.x=evt.pageX||evt.clientX,f.end.y=evt.pageY||evt.clientY,f}function getFingerData(id){return fingerData[id]||null}function setMaxDistance(direction,distance){direction!=NONE&&(distance=Math.max(distance,getMaxDistance(direction)),maximumsMap[direction].distance=distance);}function getMaxDistance(direction){return maximumsMap[direction]?maximumsMap[direction].distance:void 0}function createMaximumsData(){var maxData={};return maxData[LEFT]=createMaximumVO(LEFT),maxData[RIGHT]=createMaximumVO(RIGHT),maxData[UP]=createMaximumVO(UP),maxData[DOWN]=createMaximumVO(DOWN),maxData}function createMaximumVO(dir){return {direction:dir,distance:0}}function calculateDuration(){return endTime-startTime}function calculateTouchesDistance(startPoint,endPoint){var diffX=Math.abs(startPoint.x-endPoint.x),diffY=Math.abs(startPoint.y-endPoint.y);return Math.round(Math.sqrt(diffX*diffX+diffY*diffY))}function calculatePinchZoom(startDistance,endDistance){var percent=endDistance/startDistance*1;return percent.toFixed(2)}function calculatePinchDirection(){return 1>pinchZoom?OUT:IN}function calculateDistance(startPoint,endPoint){return Math.round(Math.sqrt(Math.pow(endPoint.x-startPoint.x,2)+Math.pow(endPoint.y-startPoint.y,2)))}function calculateAngle(startPoint,endPoint){var x=startPoint.x-endPoint.x,y=endPoint.y-startPoint.y,r=Math.atan2(y,x),angle=Math.round(180*r/Math.PI);return 0>angle&&(angle=360-Math.abs(angle)),angle}function calculateDirection(startPoint,endPoint){if(comparePoints(startPoint,endPoint))return NONE;var angle=calculateAngle(startPoint,endPoint);return 45>=angle&&angle>=0?LEFT:360>=angle&&angle>=315?LEFT:angle>=135&&225>=angle?RIGHT:angle>45&&135>angle?DOWN:UP}function getTimeStamp(){var now=new Date;return now.getTime()}function getbounds(el){el=$(el);var offset=el.offset(),bounds={left:offset.left,right:offset.left+el.outerWidth(),top:offset.top,bottom:offset.top+el.outerHeight()};return bounds}function isInBounds(point,bounds){return point.x>bounds.left&&point.xbounds.top&&point.y0)){var event=jqEvent.originalEvent?jqEvent.originalEvent:jqEvent;if(!event.pointerType||"mouse"!=event.pointerType||0!=options.fallbackToMouseEvents){var ret,touches=event.touches,evt=touches?touches[0]:event;return phase=PHASE_START,touches?fingerCount=touches.length:options.preventDefaultEvents!==!1&&jqEvent.preventDefault(),distance=0,direction=null,currentDirection=null,pinchDirection=null,duration=0,startTouchesDistance=0,endTouchesDistance=0,pinchZoom=1,pinchDistance=0,maximumsMap=createMaximumsData(),cancelMultiFingerRelease(),createFingerData(0,evt),!touches||fingerCount===options.fingers||options.fingers===ALL_FINGERS||hasPinches()?(startTime=getTimeStamp(),2==fingerCount&&(createFingerData(1,touches[1]),startTouchesDistance=endTouchesDistance=calculateTouchesDistance(fingerData[0].start,fingerData[1].start)),(options.swipeStatus||options.pinchStatus)&&(ret=triggerHandler(event,phase))):ret=!1,ret===!1?(phase=PHASE_CANCEL,triggerHandler(event,phase),ret):(options.hold&&(holdTimeout=setTimeout($.proxy(function(){$element.trigger("hold",[event.target]),options.hold&&(ret=options.hold.call($element,event,event.target));},this),options.longTapThreshold)),setTouchInProgress(!0),null)}}}function touchMove(jqEvent){var event=jqEvent.originalEvent?jqEvent.originalEvent:jqEvent;if(phase!==PHASE_END&&phase!==PHASE_CANCEL&&!inMultiFingerRelease()){var ret,touches=event.touches,evt=touches?touches[0]:event,currentFinger=updateFingerData(evt);if(endTime=getTimeStamp(),touches&&(fingerCount=touches.length),options.hold&&clearTimeout(holdTimeout),phase=PHASE_MOVE,2==fingerCount&&(0==startTouchesDistance?(createFingerData(1,touches[1]),startTouchesDistance=endTouchesDistance=calculateTouchesDistance(fingerData[0].start,fingerData[1].start)):(updateFingerData(touches[1]),endTouchesDistance=calculateTouchesDistance(fingerData[0].end,fingerData[1].end),pinchDirection=calculatePinchDirection(fingerData[0].end,fingerData[1].end)),pinchZoom=calculatePinchZoom(startTouchesDistance,endTouchesDistance),pinchDistance=Math.abs(startTouchesDistance-endTouchesDistance)),fingerCount===options.fingers||options.fingers===ALL_FINGERS||!touches||hasPinches()){if(direction=calculateDirection(currentFinger.start,currentFinger.end),currentDirection=calculateDirection(currentFinger.last,currentFinger.end),validateDefaultEvent(jqEvent,currentDirection),distance=calculateDistance(currentFinger.start,currentFinger.end),duration=calculateDuration(),setMaxDistance(direction,distance),ret=triggerHandler(event,phase),!options.triggerOnTouchEnd||options.triggerOnTouchLeave){var inBounds=!0;if(options.triggerOnTouchLeave){var bounds=getbounds(this);inBounds=isInBounds(currentFinger.end,bounds);}!options.triggerOnTouchEnd&&inBounds?phase=getNextPhase(PHASE_MOVE):options.triggerOnTouchLeave&&!inBounds&&(phase=getNextPhase(PHASE_END)),phase!=PHASE_CANCEL&&phase!=PHASE_END||triggerHandler(event,phase);}}else phase=PHASE_CANCEL,triggerHandler(event,phase);ret===!1&&(phase=PHASE_CANCEL,triggerHandler(event,phase));}}function touchEnd(jqEvent){var event=jqEvent.originalEvent?jqEvent.originalEvent:jqEvent,touches=event.touches;if(touches){if(touches.length&&!inMultiFingerRelease())return startMultiFingerRelease(event),!0;if(touches.length&&inMultiFingerRelease())return !0}return inMultiFingerRelease()&&(fingerCount=fingerCountAtRelease),endTime=getTimeStamp(),duration=calculateDuration(),didSwipeBackToCancel()||!validateSwipeDistance()?(phase=PHASE_CANCEL,triggerHandler(event,phase)):options.triggerOnTouchEnd||options.triggerOnTouchEnd===!1&&phase===PHASE_MOVE?(options.preventDefaultEvents!==!1&&jqEvent.cancelable!==!1&&jqEvent.preventDefault(),phase=PHASE_END,triggerHandler(event,phase)):!options.triggerOnTouchEnd&&hasTap()?(phase=PHASE_END,triggerHandlerForGesture(event,phase,TAP)):phase===PHASE_MOVE&&(phase=PHASE_CANCEL,triggerHandler(event,phase)),setTouchInProgress(!1),null}function touchCancel(){fingerCount=0,endTime=0,startTime=0,startTouchesDistance=0,endTouchesDistance=0,pinchZoom=1,cancelMultiFingerRelease(),setTouchInProgress(!1);}function touchLeave(jqEvent){var event=jqEvent.originalEvent?jqEvent.originalEvent:jqEvent;options.triggerOnTouchLeave&&(phase=getNextPhase(PHASE_END),triggerHandler(event,phase));}function removeListeners(){$element.off(START_EV,touchStart),$element.off(CANCEL_EV,touchCancel),$element.off(MOVE_EV,touchMove),$element.off(END_EV,touchEnd),LEAVE_EV&&$element.off(LEAVE_EV,touchLeave),setTouchInProgress(!1);}function getNextPhase(currentPhase){var nextPhase=currentPhase,validTime=validateSwipeTime(),validDistance=validateSwipeDistance(),didCancel=didSwipeBackToCancel();return !validTime||didCancel?nextPhase=PHASE_CANCEL:!validDistance||currentPhase!=PHASE_MOVE||options.triggerOnTouchEnd&&!options.triggerOnTouchLeave?!validDistance&¤tPhase==PHASE_END&&options.triggerOnTouchLeave&&(nextPhase=PHASE_CANCEL):nextPhase=PHASE_END,nextPhase}function triggerHandler(event,phase){var ret,touches=event.touches;return (didSwipe()||hasSwipes())&&(ret=triggerHandlerForGesture(event,phase,SWIPE)),(didPinch()||hasPinches())&&ret!==!1&&(ret=triggerHandlerForGesture(event,phase,PINCH)),didDoubleTap()&&ret!==!1?ret=triggerHandlerForGesture(event,phase,DOUBLE_TAP):didLongTap()&&ret!==!1?ret=triggerHandlerForGesture(event,phase,LONG_TAP):didTap()&&ret!==!1&&(ret=triggerHandlerForGesture(event,phase,TAP)),phase===PHASE_CANCEL&&touchCancel(),phase===PHASE_END&&(touches?touches.length||touchCancel():touchCancel()),ret}function triggerHandlerForGesture(event,phase,gesture){var ret;if(gesture==SWIPE){if($element.trigger("swipeStatus",[phase,direction||null,distance||0,duration||0,fingerCount,fingerData,currentDirection]),options.swipeStatus&&(ret=options.swipeStatus.call($element,event,phase,direction||null,distance||0,duration||0,fingerCount,fingerData,currentDirection),ret===!1))return !1;if(phase==PHASE_END&&validateSwipe()){if(clearTimeout(singleTapTimeout),clearTimeout(holdTimeout),$element.trigger("swipe",[direction,distance,duration,fingerCount,fingerData,currentDirection]),options.swipe&&(ret=options.swipe.call($element,event,direction,distance,duration,fingerCount,fingerData,currentDirection),ret===!1))return !1;switch(direction){case LEFT:$element.trigger("swipeLeft",[direction,distance,duration,fingerCount,fingerData,currentDirection]),options.swipeLeft&&(ret=options.swipeLeft.call($element,event,direction,distance,duration,fingerCount,fingerData,currentDirection));break;case RIGHT:$element.trigger("swipeRight",[direction,distance,duration,fingerCount,fingerData,currentDirection]),options.swipeRight&&(ret=options.swipeRight.call($element,event,direction,distance,duration,fingerCount,fingerData,currentDirection));break;case UP:$element.trigger("swipeUp",[direction,distance,duration,fingerCount,fingerData,currentDirection]),options.swipeUp&&(ret=options.swipeUp.call($element,event,direction,distance,duration,fingerCount,fingerData,currentDirection));break;case DOWN:$element.trigger("swipeDown",[direction,distance,duration,fingerCount,fingerData,currentDirection]),options.swipeDown&&(ret=options.swipeDown.call($element,event,direction,distance,duration,fingerCount,fingerData,currentDirection));}}}if(gesture==PINCH){if($element.trigger("pinchStatus",[phase,pinchDirection||null,pinchDistance||0,duration||0,fingerCount,pinchZoom,fingerData]),options.pinchStatus&&(ret=options.pinchStatus.call($element,event,phase,pinchDirection||null,pinchDistance||0,duration||0,fingerCount,pinchZoom,fingerData),ret===!1))return !1;if(phase==PHASE_END&&validatePinch())switch(pinchDirection){case IN:$element.trigger("pinchIn",[pinchDirection||null,pinchDistance||0,duration||0,fingerCount,pinchZoom,fingerData]),options.pinchIn&&(ret=options.pinchIn.call($element,event,pinchDirection||null,pinchDistance||0,duration||0,fingerCount,pinchZoom,fingerData));break;case OUT:$element.trigger("pinchOut",[pinchDirection||null,pinchDistance||0,duration||0,fingerCount,pinchZoom,fingerData]),options.pinchOut&&(ret=options.pinchOut.call($element,event,pinchDirection||null,pinchDistance||0,duration||0,fingerCount,pinchZoom,fingerData));}}return gesture==TAP?phase!==PHASE_CANCEL&&phase!==PHASE_END||(clearTimeout(singleTapTimeout),clearTimeout(holdTimeout),hasDoubleTap()&&!inDoubleTap()?(doubleTapStartTime=getTimeStamp(),singleTapTimeout=setTimeout($.proxy(function(){doubleTapStartTime=null,$element.trigger("tap",[event.target]),options.tap&&(ret=options.tap.call($element,event,event.target));},this),options.doubleTapThreshold)):(doubleTapStartTime=null,$element.trigger("tap",[event.target]),options.tap&&(ret=options.tap.call($element,event,event.target)))):gesture==DOUBLE_TAP?phase!==PHASE_CANCEL&&phase!==PHASE_END||(clearTimeout(singleTapTimeout),clearTimeout(holdTimeout),doubleTapStartTime=null,$element.trigger("doubletap",[event.target]),options.doubleTap&&(ret=options.doubleTap.call($element,event,event.target))):gesture==LONG_TAP&&(phase!==PHASE_CANCEL&&phase!==PHASE_END||(clearTimeout(singleTapTimeout),doubleTapStartTime=null,$element.trigger("longtap",[event.target]),options.longTap&&(ret=options.longTap.call($element,event,event.target)))),ret}function validateSwipeDistance(){var valid=!0;return null!==options.threshold&&(valid=distance>=options.threshold),valid}function didSwipeBackToCancel(){var cancelled=!1;return null!==options.cancelThreshold&&null!==direction&&(cancelled=getMaxDistance(direction)-distance>=options.cancelThreshold),cancelled}function validatePinchDistance(){return null!==options.pinchThreshold?pinchDistance>=options.pinchThreshold:!0}function validateSwipeTime(){var result;return result=options.maxTimeThreshold?!(duration>=options.maxTimeThreshold):!0}function validateDefaultEvent(jqEvent,direction){if(options.preventDefaultEvents!==!1)if(options.allowPageScroll===NONE)jqEvent.preventDefault();else{var auto=options.allowPageScroll===AUTO;switch(direction){case LEFT:(options.swipeLeft&&auto||!auto&&options.allowPageScroll!=HORIZONTAL)&&jqEvent.preventDefault();break;case RIGHT:(options.swipeRight&&auto||!auto&&options.allowPageScroll!=HORIZONTAL)&&jqEvent.preventDefault();break;case UP:(options.swipeUp&&auto||!auto&&options.allowPageScroll!=VERTICAL)&&jqEvent.preventDefault();break;case DOWN:(options.swipeDown&&auto||!auto&&options.allowPageScroll!=VERTICAL)&&jqEvent.preventDefault();break;case NONE:}}}function validatePinch(){var hasCorrectFingerCount=validateFingers(),hasEndPoint=validateEndPoint(),hasCorrectDistance=validatePinchDistance();return hasCorrectFingerCount&&hasEndPoint&&hasCorrectDistance}function hasPinches(){return !!(options.pinchStatus||options.pinchIn||options.pinchOut)}function didPinch(){return !(!validatePinch()||!hasPinches())}function validateSwipe(){var hasValidTime=validateSwipeTime(),hasValidDistance=validateSwipeDistance(),hasCorrectFingerCount=validateFingers(),hasEndPoint=validateEndPoint(),didCancel=didSwipeBackToCancel(),valid=!didCancel&&hasEndPoint&&hasCorrectFingerCount&&hasValidDistance&&hasValidTime;return valid}function hasSwipes(){return !!(options.swipe||options.swipeStatus||options.swipeLeft||options.swipeRight||options.swipeUp||options.swipeDown)}function didSwipe(){return !(!validateSwipe()||!hasSwipes())}function validateFingers(){return fingerCount===options.fingers||options.fingers===ALL_FINGERS||!SUPPORTS_TOUCH}function validateEndPoint(){return 0!==fingerData[0].end.x}function hasTap(){return !!options.tap}function hasDoubleTap(){return !!options.doubleTap}function hasLongTap(){return !!options.longTap}function validateDoubleTap(){if(null==doubleTapStartTime)return !1;var now=getTimeStamp();return hasDoubleTap()&&now-doubleTapStartTime<=options.doubleTapThreshold}function inDoubleTap(){return validateDoubleTap()}function validateTap(){return (1===fingerCount||!SUPPORTS_TOUCH)&&(isNaN(distance)||distanceoptions.longTapThreshold&&DOUBLE_TAP_THRESHOLD>distance}function didTap(){return !(!validateTap()||!hasTap())}function didDoubleTap(){return !(!validateDoubleTap()||!hasDoubleTap())}function didLongTap(){return !(!validateLongTap()||!hasLongTap())}function startMultiFingerRelease(event){previousTouchEndTime=getTimeStamp(),fingerCountAtRelease=event.touches.length+1;}function cancelMultiFingerRelease(){previousTouchEndTime=0,fingerCountAtRelease=0;}function inMultiFingerRelease(){var withinThreshold=!1;if(previousTouchEndTime){var diff=getTimeStamp()-previousTouchEndTime;diff<=options.fingerReleaseThreshold&&(withinThreshold=!0);}return withinThreshold}function getTouchInProgress(){return !($element.data(PLUGIN_NS+"_intouch")!==!0)}function setTouchInProgress(val){$element&&(val===!0?($element.on(MOVE_EV,touchMove),$element.on(END_EV,touchEnd),LEAVE_EV&&$element.on(LEAVE_EV,touchLeave)):($element.off(MOVE_EV,touchMove,!1),$element.off(END_EV,touchEnd,!1),LEAVE_EV&&$element.off(LEAVE_EV,touchLeave,!1)),$element.data(PLUGIN_NS+"_intouch",val===!0));}function createFingerData(id,evt){var f={start:{x:0,y:0},last:{x:0,y:0},end:{x:0,y:0}};return f.start.x=f.last.x=f.end.x=evt.pageX||evt.clientX,f.start.y=f.last.y=f.end.y=evt.pageY||evt.clientY,fingerData[id]=f,f}function updateFingerData(evt){var id=void 0!==evt.identifier?evt.identifier:0,f=getFingerData(id);return null===f&&(f=createFingerData(id,evt)),f.last.x=f.end.x,f.last.y=f.end.y,f.end.x=evt.pageX||evt.clientX,f.end.y=evt.pageY||evt.clientY,f}function getFingerData(id){return fingerData[id]||null}function setMaxDistance(direction,distance){direction!=NONE&&(distance=Math.max(distance,getMaxDistance(direction)),maximumsMap[direction].distance=distance);}function getMaxDistance(direction){return maximumsMap[direction]?maximumsMap[direction].distance:void 0}function createMaximumsData(){var maxData={};return maxData[LEFT]=createMaximumVO(LEFT),maxData[RIGHT]=createMaximumVO(RIGHT),maxData[UP]=createMaximumVO(UP),maxData[DOWN]=createMaximumVO(DOWN),maxData}function createMaximumVO(dir){return {direction:dir,distance:0}}function calculateDuration(){return endTime-startTime}function calculateTouchesDistance(startPoint,endPoint){var diffX=Math.abs(startPoint.x-endPoint.x),diffY=Math.abs(startPoint.y-endPoint.y);return Math.round(Math.sqrt(diffX*diffX+diffY*diffY))}function calculatePinchZoom(startDistance,endDistance){var percent=endDistance/startDistance*1;return percent.toFixed(2)}function calculatePinchDirection(){return 1>pinchZoom?OUT:IN}function calculateDistance(startPoint,endPoint){return Math.round(Math.sqrt(Math.pow(endPoint.x-startPoint.x,2)+Math.pow(endPoint.y-startPoint.y,2)))}function calculateAngle(startPoint,endPoint){var x=startPoint.x-endPoint.x,y=endPoint.y-startPoint.y,r=Math.atan2(y,x),angle=Math.round(180*r/Math.PI);return 0>angle&&(angle=360-Math.abs(angle)),angle}function calculateDirection(startPoint,endPoint){if(comparePoints(startPoint,endPoint))return NONE;var angle=calculateAngle(startPoint,endPoint);return 45>=angle&&angle>=0?LEFT:360>=angle&&angle>=315?LEFT:angle>=135&&225>=angle?RIGHT:angle>45&&135>angle?DOWN:UP}function getTimeStamp(){var now=new Date;return now.getTime()}function getbounds(el){el=$(el);var offset=el.offset(),bounds={left:offset.left,right:offset.left+el.outerWidth(),top:offset.top,bottom:offset.top+el.outerHeight()};return bounds}function isInBounds(point,bounds){return point.x>bounds.left&&point.xbounds.top&&point.y { + this.form.view.html.addEventListener( events.AddRepeat().type, event => { const byCountUpdate = event.detail ? event.detail[ 1 ] : undefined; this._updateAllActive(); // Don't flip if the user didn't create the repeat with the + button. @@ -23528,7 +23827,7 @@ this.flipToPageContaining( jquery( event.target ) ); } } ); - this.form.view.html.addEventListener( event.RemoveRepeat().type, event => { + 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.closest( 'html' ).length === 0 ) { @@ -23558,7 +23857,7 @@ }, _setLangChangeHandlers() { this.form.view.html - .addEventListener( event.ChangeLanguage().type, () => { + .addEventListener( events.ChangeLanguage().type, () => { this._updateToc(); } ); }, @@ -23600,7 +23899,7 @@ let validate; currentIndex = this._getCurrentIndex(); - validate = this.form.validateContent( this.$current ); + validate = this.form.validateContent( this.$current ); return validate .then( valid => { @@ -23644,13 +23943,13 @@ this._setToCurrent( pageEl ); this._focusOnFirstQuestion( pageEl ); this._toggleButtons( newIndex ); - pageEl.dispatchEvent( event.PageFlip() ); + pageEl.dispatchEvent( events.PageFlip() ); } } else { this._setToCurrent( pageEl ); this._focusOnFirstQuestion( pageEl ); this._toggleButtons( newIndex ); - pageEl.dispatchEvent( event.PageFlip() ); + pageEl.dispatchEvent( events.PageFlip() ); } }, _flipToFirst() { @@ -23721,7 +24020,9 @@ }; /** - * Updates branches + * @module relevant + * + * @description Updates branches * * @param {{nodes:Array=, repeatPath: string=, repeatIndex: number=}=} updated The object containing info on updated data nodes */ @@ -23753,6 +24054,7 @@ $nodes.each( function() { const $node = jquery( this ); + const node = this; let context; let $parentGroups; let pathParts; @@ -23769,8 +24071,8 @@ p = {}; cacheIndex = null; - p.relevant = that.form.input.getRelevant( $node ); - p.path = that.form.input.getName( $node ); + p.relevant = that.form.input.getRelevant( node ); + p.path = that.form.input.getName( node ); if ( $branchNode.length !== 1 ) { if ( $node.parentsUntil( '.or', '#or-calculated-items' ).length === 0 ) { @@ -23808,7 +24110,7 @@ insideRepeat = clonedRepeatsPresent && $branchNode.parentsUntil( '.or', '.or-repeat' ).length > 0; insideRepeatClone = clonedRepeatsPresent && $branchNode.parentsUntil( '.or', '.or-repeat.clone' ).length > 0; - /* + /* * 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. */ @@ -23821,7 +24123,7 @@ * 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 ) ? that.form.input.getIndex( $node ) : 0; + p.ind = ( context && insideRepeatClone ) ? that.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. @@ -23861,10 +24163,10 @@ /** * Evaluates a relevant expression (for future fancy stuff this is placed in a separate function) * - * @param {string} expr [description] - * @param {string} contextPath [description] - * @param {number} index [description] - * @return {boolean} [description] + * @param {string} expr + * @param {string} contextPath + * @param {number} index + * @return {boolean} */ evaluate( expr, contextPath, index ) { const result = this.form.model.evaluate( expr, 'boolean', contextPath, index ); @@ -23873,10 +24175,10 @@ /** * Processes the evaluation result for a branch * - * @param { jQuery } $branchNode [description] - * @param { string } path Path of branch node - * @param { boolean } result result of relevant evaluation - * @param { =boolean } forceClearIrrelevant Whether to force clearing of irrelevant nodes and descendants + * @param {jQuery} $branchNode + * @param {string} path - Path of branch node + * @param {boolean} result - result of relevant evaluation + * @param {boolean} forceClearIrrelevant - Whether to force clearing of irrelevant nodes and descendants */ process( $branchNode, path, result, forceClearIrrelevant ) { if ( result === true ) { @@ -23889,8 +24191,8 @@ /** * Checks whether branch currently has 'relevant' state * - * @param {jQuery} $branchNode [description] - * @return {boolean} [description] + * @param {jQuery} $branchNode + * @return {boolean} */ selfRelevant( $branchNode ) { return !$branchNode.hasClass( 'disabled' ) && !$branchNode.hasClass( 'pre-init' ); @@ -23899,7 +24201,7 @@ /** * Enables and reveals a branch node/group * - * @param {jQuery} $branchNode The jQuery object to reveal and enable + * @param {jQuery} $branchNode - The jQuery object to reveal and enable */ enable( $branchNode, path ) { let change = false; @@ -23948,16 +24250,16 @@ return change; }, /** - * Clears values from branchnode. + * Clears values from branchnode. * This function is separated so it can be overridden in custom apps. - * - * @param {[type]} $branchNode [description] - * @return {boolean} [description] + * + * @param {jQuery} $branchNode + * @param {string} path */ clear( $branchNode, path ) { // A change event ensures the model is updated // An inputupdate event is required to update widgets - $branchNode.clearInputs( 'change', event.InputUpdate().type ); + $branchNode.clearInputs( 'change', events.InputUpdate().type ); // 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.is( '.or-group, .or-group-data' ) ) { @@ -23981,9 +24283,8 @@ /** * Activates form controls. * This function is separated so it can be overridden in custom apps. - * - * @param {[type]} $branchNode [description] - * @return {[type]} [description] + * + * @param {jQuery} $branchNode */ activate( $branchNode ) { this.setDisabledProperty( $branchNode, false ); @@ -23991,9 +24292,8 @@ /** * Deactivates form controls. * This function is separated so it can be overridden in custom apps. - * - * @param {[type]} $branchNode [description] - * @return {[type]} [description] + * + * @param {jQuery} $branchNode */ deactivate( $branchNode ) { $branchNode.addClass( 'disabled' ); @@ -24003,12 +24303,15 @@ }; /** - * Updates itemsets - * - * @param {{nodes:Array=, repeatPath: string=, repeatIndex: number=}=} updated The object containing info on updated data nodes + * Updates itemsets. + * + * @module itemset */ var itemsetModule = { + /** + * @param {{nodes:Array=, repeatPath: string=, repeatIndex: number=}=} updated The object containing info on updated data nodes + */ update( updated = {} ) { const that = this; const itemsCache = {}; @@ -24084,7 +24387,7 @@ * I am not sure what is correct, but for now for XLSForm-style secondary instances with only one level underneath the s that * the nodeset retrieves, Enketo's aproach works well. */ - const context = that.form.input.getName( $input ); + const context = that.form.input.getName( $input[ 0 ] ); /* * Determining the index is expensive, so we only do this when the itemset is inside a cloned repeat. @@ -24092,7 +24395,7 @@ */ const insideRepeat = ( clonedRepeatsPresent && $input.parentsUntil( '.or', '.or-repeat' ).length > 0 ) ? true : false; const insideRepeatClone = ( clonedRepeatsPresent && $input.parentsUntil( '.or', '.or-repeat.clone' ).length > 0 ) ? true : false; - const index = ( insideRepeatClone ) ? that.form.input.getIndex( $input ) : 0; + const index = ( insideRepeatClone ) ? that.form.input.getIndex( $input[ 0 ] ) : 0; if ( typeof itemsCache[ itemsXpath ] !== 'undefined' ) { $instanceItems = itemsCache[ itemsXpath ]; @@ -24201,7 +24504,7 @@ if ( $input.hasClass( 'rank' ) ) { currentValue = ''; } - that.form.input.setVal( $input, currentValue ); + that.form.input.setVal( $input[ 0 ], currentValue ); $input.trigger( 'change' ); } @@ -24281,38 +24584,48 @@ /** * Progress module. + * + * @module progress */ /** * Maintains progress state of user traversing through form, using - * currently focused input || last changed input as current location. + * currently focused input or the last changed input as the indicator for the current location. */ var progressModule = { status: 0, lastChanged: null, - $all: null, + all: null, updateTotal() { - this.$all = this.form.view.$.find( '.question' ).not( '.disabled, .or-appearance-comment, .or-appearance-dn' ).filter( function() { - return jquery( this ).parentsUntil( '.or', '.disabled' ).length === 0; - } ); + 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 + /** + * Updates rounded % value of progress and triggers event if changed. + * + * @param {Element} el + */ update( el ) { let status; - if ( !this.$all || !el ) { + if ( !this.all || !el ) { this.updateTotal(); } this.lastChanged = el || this.lastChanged; - status = Math.round( ( ( this.$all.index( jquery( 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 ) { this.status = status; - this.form.view.$.trigger( 'progressupdate.enketo', status ); + this.form.view.html.dispatchEvent( events.ProgressUpdate( status ) ); } }, + /** + * @returns string + */ get() { return this.status; } @@ -24351,8 +24664,9 @@ // Not meant to be overridden, but could be. Recommend to extend `get props()` instead. _getProps() { + const that = this; return { - readonly: this.element.nodeName.toLowerCase === 'select' ? !!this.element.getAttribute( 'readonly' ) : !!this.element.readOnly, + get readonly() { return that.element.nodeName.toLowerCase() === 'select' ? !!that.element.getAttribute( 'readonly' ) : !!that.element.readOnly; }, appearances: [ ...this.element.closest( '.question, form.or' ).classList ] .filter( cls => cls.indexOf( 'or-appearance-' ) === 0 ) .map( cls => cls.substring( 14 ) ), @@ -24377,7 +24691,7 @@ } /** - * Updates languages,