From 51383d563c1488afee238203d38d22408dd2f4d2 Mon Sep 17 00:00:00 2001 From: decaffeinate Date: Fri, 4 Apr 2025 10:11:36 -0300 Subject: [PATCH 01/69] decaffeinate: Rename ajax.coffee and 27 other files from .coffee to .js --- .../META-INF/modules/t5/core/{ajax.coffee => ajax.js} | 0 .../modules/t5/core/{ajaxformloop.coffee => ajaxformloop.js} | 0 .../META-INF/modules/t5/core/{alert.coffee => alert.js} | 0 .../modules/t5/core/{autocomplete.coffee => autocomplete.js} | 0 .../META-INF/modules/t5/core/{bootstrap.coffee => bootstrap.js} | 0 .../modules/t5/core/{confirm-click.coffee => confirm-click.js} | 0 .../META-INF/modules/t5/core/{console.coffee => console.js} | 0 .../META-INF/modules/t5/core/{datefield.coffee => datefield.js} | 0 .../META-INF/modules/t5/core/{events.coffee => events.js} | 0 .../t5/core/{exception-display.coffee => exception-display.js} | 0 .../t5/core/{exception-frame.coffee => exception-frame.js} | 0 .../META-INF/modules/t5/core/{fields.coffee => fields.js} | 0 .../modules/t5/core/{form-fragment.coffee => form-fragment.js} | 0 .../META-INF/modules/t5/core/{forms.coffee => forms.js} | 0 .../META-INF/modules/t5/core/{graphviz.coffee => graphviz.js} | 0 .../META-INF/modules/t5/core/{init.coffee => init.js} | 0 .../META-INF/modules/t5/core/{localdate.coffee => localdate.js} | 0 .../META-INF/modules/t5/core/{messages.coffee => messages.js} | 0 .../META-INF/modules/t5/core/{moment.coffee => moment.js} | 0 .../META-INF/modules/t5/core/{pageinit.coffee => pageinit.js} | 0 .../META-INF/modules/t5/core/{palette.coffee => palette.js} | 0 .../META-INF/modules/t5/core/{select.coffee => select.js} | 0 .../modules/t5/core/{time-interval.coffee => time-interval.js} | 0 .../META-INF/modules/t5/core/{tree.coffee => tree.js} | 0 .../META-INF/modules/t5/core/{utils.coffee => utils.js} | 0 .../META-INF/modules/t5/core/{validation.coffee => validation.js} | 0 .../modules/t5/core/{zone-refresh.coffee => zone-refresh.js} | 0 .../META-INF/modules/t5/core/{zone.coffee => zone.js} | 0 28 files changed, 0 insertions(+), 0 deletions(-) rename tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/{ajax.coffee => ajax.js} (100%) rename tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/{ajaxformloop.coffee => ajaxformloop.js} (100%) rename tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/{alert.coffee => alert.js} (100%) rename tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/{autocomplete.coffee => autocomplete.js} (100%) rename tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/{bootstrap.coffee => bootstrap.js} (100%) rename tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/{confirm-click.coffee => confirm-click.js} (100%) rename tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/{console.coffee => console.js} (100%) rename tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/{datefield.coffee => datefield.js} (100%) rename tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/{events.coffee => events.js} (100%) rename tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/{exception-display.coffee => exception-display.js} (100%) rename tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/{exception-frame.coffee => exception-frame.js} (100%) rename tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/{fields.coffee => fields.js} (100%) rename tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/{form-fragment.coffee => form-fragment.js} (100%) rename tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/{forms.coffee => forms.js} (100%) rename tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/{graphviz.coffee => graphviz.js} (100%) rename tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/{init.coffee => init.js} (100%) rename tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/{localdate.coffee => localdate.js} (100%) rename tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/{messages.coffee => messages.js} (100%) rename tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/{moment.coffee => moment.js} (100%) rename tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/{pageinit.coffee => pageinit.js} (100%) rename tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/{palette.coffee => palette.js} (100%) rename tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/{select.coffee => select.js} (100%) rename tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/{time-interval.coffee => time-interval.js} (100%) rename tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/{tree.coffee => tree.js} (100%) rename tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/{utils.coffee => utils.js} (100%) rename tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/{validation.coffee => validation.js} (100%) rename tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/{zone-refresh.coffee => zone-refresh.js} (100%) rename tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/{zone.coffee => zone.js} (100%) diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/ajax.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/ajax.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/ajax.coffee rename to tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/ajax.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/ajaxformloop.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/ajaxformloop.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/ajaxformloop.coffee rename to tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/ajaxformloop.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/alert.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/alert.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/alert.coffee rename to tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/alert.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/autocomplete.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/autocomplete.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/autocomplete.coffee rename to tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/autocomplete.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/bootstrap.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/bootstrap.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/bootstrap.coffee rename to tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/bootstrap.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/confirm-click.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/confirm-click.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/confirm-click.coffee rename to tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/confirm-click.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/console.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/console.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/console.coffee rename to tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/console.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/datefield.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/datefield.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/datefield.coffee rename to tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/datefield.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/events.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/events.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/events.coffee rename to tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/events.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/exception-display.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/exception-display.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/exception-display.coffee rename to tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/exception-display.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/exception-frame.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/exception-frame.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/exception-frame.coffee rename to tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/exception-frame.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/fields.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/fields.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/fields.coffee rename to tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/fields.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/form-fragment.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/form-fragment.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/form-fragment.coffee rename to tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/form-fragment.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/forms.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/forms.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/forms.coffee rename to tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/forms.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/graphviz.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/graphviz.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/graphviz.coffee rename to tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/graphviz.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/init.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/init.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/init.coffee rename to tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/init.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/localdate.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/localdate.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/localdate.coffee rename to tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/localdate.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/messages.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/messages.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/messages.coffee rename to tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/messages.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/moment.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/moment.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/moment.coffee rename to tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/moment.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/pageinit.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/pageinit.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/pageinit.coffee rename to tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/pageinit.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/palette.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/palette.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/palette.coffee rename to tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/palette.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/select.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/select.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/select.coffee rename to tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/select.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/time-interval.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/time-interval.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/time-interval.coffee rename to tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/time-interval.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/tree.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/tree.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/tree.coffee rename to tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/tree.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/utils.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/utils.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/utils.coffee rename to tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/utils.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/validation.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/validation.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/validation.coffee rename to tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/validation.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/zone-refresh.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/zone-refresh.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/zone-refresh.coffee rename to tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/zone-refresh.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/zone.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/zone.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/zone.coffee rename to tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/zone.js From 63124239d044be87630fc1555ab7ae5cd1c053cc Mon Sep 17 00:00:00 2001 From: decaffeinate Date: Fri, 4 Apr 2025 10:11:41 -0300 Subject: [PATCH 02/69] decaffeinate: Convert ajax.coffee and 27 other files to JS --- .../META-INF/modules/t5/core/ajax.js | 202 ++++--- .../META-INF/modules/t5/core/ajaxformloop.js | 139 +++-- .../META-INF/modules/t5/core/alert.js | 304 +++++----- .../META-INF/modules/t5/core/autocomplete.js | 96 +-- .../META-INF/modules/t5/core/bootstrap.js | 49 +- .../META-INF/modules/t5/core/confirm-click.js | 205 ++++--- .../META-INF/modules/t5/core/console.js | 374 ++++++------ .../META-INF/modules/t5/core/datefield.js | 366 ++++++----- .../META-INF/modules/t5/core/events.js | 367 +++++------ .../modules/t5/core/exception-display.js | 59 +- .../modules/t5/core/exception-frame.js | 115 ++-- .../META-INF/modules/t5/core/fields.js | 333 +++++----- .../META-INF/modules/t5/core/form-fragment.js | 219 ++++--- .../META-INF/modules/t5/core/forms.js | 341 ++++++----- .../META-INF/modules/t5/core/graphviz.js | 63 +- .../META-INF/modules/t5/core/init.js | 61 +- .../META-INF/modules/t5/core/localdate.js | 80 +-- .../META-INF/modules/t5/core/messages.js | 113 ++-- .../META-INF/modules/t5/core/moment.js | 45 +- .../META-INF/modules/t5/core/pageinit.js | 512 +++++++++------- .../META-INF/modules/t5/core/palette.js | 568 ++++++++++-------- .../META-INF/modules/t5/core/select.js | 56 +- .../META-INF/modules/t5/core/time-interval.js | 117 ++-- .../META-INF/modules/t5/core/tree.js | 237 ++++---- .../META-INF/modules/t5/core/utils.js | 127 ++-- .../META-INF/modules/t5/core/validation.js | 380 ++++++------ .../META-INF/modules/t5/core/zone-refresh.js | 146 ++--- .../META-INF/modules/t5/core/zone.js | 259 ++++---- 28 files changed, 3234 insertions(+), 2699 deletions(-) diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/ajax.js b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/ajax.js index 8e57943246..4d27daba1a 100644 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/ajax.js +++ b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/ajax.js @@ -1,95 +1,107 @@ -# Copyright 2012-2014 The Apache Software Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http:#www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# ## t5/core/ajax -# -# Exports a single function, that invokes `t5/core/dom:ajaxRequest()` with the provided `url` and a modified version of the -# `options`. -# -# * options.method - "post", "get", etc., default: "post". -# * options.element - if provided, the URL will be treated as a server-side event name -# and the actual URL to be used will be obtained from dom.getEventUrl(url, element) -# * options.contentType - request content, defaults to "application/x-www-form-urlencoded" -# * options.data - optional, additional key/value pairs (for the default content type) -# * options.success - handler to invoke on success. Passed the ResponseWrapper object. -# Default does nothing. -# * options.failure - handler to invoke on failure (server responds with a non-2xx code). -# Passed the response. Default will throw the exception -# * options.exception - handler to invoke when an exception occurs (often means the server is unavailable). -# Passed the exception. Default will generate an exception message and throw an `Error`. -# Note: not really supported under jQuery, a hold-over from Prototype. -# * options.complete - handler to invoke after success, falure, or exception. The handler is passed no -# parameters. -# -# It wraps (or provides) `success`, `exception`, and `failure` handlers, extended to handle a partial page render -# response (for success), or properly log a server-side failure or client-side exception, including using the -# `t5/core/exception-frame` module to display a server-side processing exception. -define ["t5/core/pageinit", "t5/core/dom", "t5/core/exception-frame", "t5/core/console", "underscore"], - (pageinit, dom, exceptionframe, console, _) -> - (url, options) -> - - complete = -> - if options.complete - options.complete() - - return - - if options.hasOwnProperty 'element' - url = dom.getEventUrl(url, options.element) - - newOptions = _.extend {}, options, - - # Logs the exception to the console before passing it to the - # provided exception handler or throwing the exception. - exception: (exception) -> - console.error "Request to #{url} failed with #{exception}" - - if options.exception - options.exception exception - else - throw exception - - complete() - - return - - failure: (response, failureMessage) -> - raw = response.header "X-Tapestry-ErrorMessage" - unless _.isEmpty raw - message = window.unescape raw - console.error "Request to #{url} failed with '#{message}'." - - contentType = response.header "content-type" - - isHTML = contentType and (contentType.split(';')[0] is "text/html") - - if isHTML - exceptionframe response.text - else - console.error failureMessage - - options.failure and options.failure(response) - - complete() - - return - - success: (response) -> - pageinit.handlePartialPageRenderResponse response, options.success - - complete() - - return - - dom.ajaxRequest url, newOptions \ No newline at end of file +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md + */ +// Copyright 2012-2014 The Apache Software Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http:#www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ## t5/core/ajax +// +// Exports a single function, that invokes `t5/core/dom:ajaxRequest()` with the provided `url` and a modified version of the +// `options`. +// +// * options.method - "post", "get", etc., default: "post". +// * options.element - if provided, the URL will be treated as a server-side event name +// and the actual URL to be used will be obtained from dom.getEventUrl(url, element) +// * options.contentType - request content, defaults to "application/x-www-form-urlencoded" +// * options.data - optional, additional key/value pairs (for the default content type) +// * options.success - handler to invoke on success. Passed the ResponseWrapper object. +// Default does nothing. +// * options.failure - handler to invoke on failure (server responds with a non-2xx code). +// Passed the response. Default will throw the exception +// * options.exception - handler to invoke when an exception occurs (often means the server is unavailable). +// Passed the exception. Default will generate an exception message and throw an `Error`. +// Note: not really supported under jQuery, a hold-over from Prototype. +// * options.complete - handler to invoke after success, falure, or exception. The handler is passed no +// parameters. +// +// It wraps (or provides) `success`, `exception`, and `failure` handlers, extended to handle a partial page render +// response (for success), or properly log a server-side failure or client-side exception, including using the +// `t5/core/exception-frame` module to display a server-side processing exception. +define(["t5/core/pageinit", "t5/core/dom", "t5/core/exception-frame", "t5/core/console", "underscore"], + (pageinit, dom, exceptionframe, console, _) => (function(url, options) { + + const complete = function() { + if (options.complete) { + options.complete(); + } + + }; + + if (options.hasOwnProperty('element')) { + url = dom.getEventUrl(url, options.element); + } + + const newOptions = _.extend({}, options, { + + // Logs the exception to the console before passing it to the + // provided exception handler or throwing the exception. + exception(exception) { + console.error(`Request to ${url} failed with ${exception}`); + + if (options.exception) { + options.exception(exception); + } else { + throw exception; + } + + complete(); + + }, + + failure(response, failureMessage) { + const raw = response.header("X-Tapestry-ErrorMessage"); + if (!_.isEmpty(raw)) { + const message = window.unescape(raw); + console.error(`Request to ${url} failed with '${message}'.`); + + const contentType = response.header("content-type"); + + const isHTML = contentType && (contentType.split(';')[0] === "text/html"); + + if (isHTML) { + exceptionframe(response.text); + } + } else { + console.error(failureMessage); + } + + options.failure && options.failure(response); + + complete(); + + }, + + success(response) { + pageinit.handlePartialPageRenderResponse(response, options.success); + + complete(); + + } + } + ); + + return dom.ajaxRequest(url, newOptions); + })); \ No newline at end of file diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/ajaxformloop.js b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/ajaxformloop.js index 0dca35d2bc..465a47f567 100644 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/ajaxformloop.js +++ b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/ajaxformloop.js @@ -1,87 +1,102 @@ -# Copyright 2012, 2013 The Apache Software Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http:#www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md + */ +// Copyright 2012, 2013 The Apache Software Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http:#www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -# ## t5/core/ajaxformloop -# -# Provides handlers related to the core/AjaxFormLoop component (as well as core/AddRowLink and -# core/RemoveRowLink). -define ["t5/core/dom", "t5/core/events", "t5/core/console", "t5/core/ajax"], - (dom, events, console, ajax) -> +// ## t5/core/ajaxformloop +// +// Provides handlers related to the core/AjaxFormLoop component (as well as core/AddRowLink and +// core/RemoveRowLink). +define(["t5/core/dom", "t5/core/events", "t5/core/console", "t5/core/ajax"], + function(dom, events, console, ajax) { - # "afl" is short for "AjaxFormLoop". - AFL_SELECTOR = "[data-container-type='core/AjaxFormLoop']" - FRAGMENT_TYPE = "core/ajaxformloop-fragment" + // "afl" is short for "AjaxFormLoop". + const AFL_SELECTOR = "[data-container-type='core/AjaxFormLoop']"; + const FRAGMENT_TYPE = "core/ajaxformloop-fragment"; - dom.onDocument "click", "#{AFL_SELECTOR} [data-afl-behavior=remove]", -> + dom.onDocument("click", `${AFL_SELECTOR} [data-afl-behavior=remove]`, function() { - afl = @findParent AFL_SELECTOR + const afl = this.findParent(AFL_SELECTOR); - unless afl - console.error "Enclosing element for AjaxFormLoop remove row link not found." - return false + if (!afl) { + console.error("Enclosing element for AjaxFormLoop remove row link not found."); + return false; + } - url = afl.attr "data-remove-row-url" + const url = afl.attr("data-remove-row-url"); - ajax url, - data: - "t:rowvalue": (@closest "[data-afl-row-value]").attr "data-afl-row-value" - success: => - # The server has removed the row from persistent storage, lets - # do the same on the UI. + ajax(url, { + data: { + "t:rowvalue": (this.closest("[data-afl-row-value]")).attr("data-afl-row-value") + }, + success: () => { + // The server has removed the row from persistent storage, lets + // do the same on the UI. - fragment = @findParent "[data-container-type='#{FRAGMENT_TYPE}']" + const fragment = this.findParent(`[data-container-type='${FRAGMENT_TYPE}']`); - # TODO: Fire some before & after events, to allow for animation. + // TODO: Fire some before & after events, to allow for animation. - # The fragment takes with it the hidden fields that control form submission - # for its portion of the form. - fragment.remove() + // The fragment takes with it the hidden fields that control form submission + // for its portion of the form. + return fragment.remove(); + } + } + ); - return false + return false; + }); - dom.onDocument "click", "#{AFL_SELECTOR} [data-afl-behavior=insert-before] [data-afl-trigger=add]", -> + dom.onDocument("click", `${AFL_SELECTOR} [data-afl-behavior=insert-before] [data-afl-trigger=add]`, function() { - afl = @findParent AFL_SELECTOR + const afl = this.findParent(AFL_SELECTOR); - insertionPoint = @findParent "[data-afl-behavior=insert-before]" + const insertionPoint = this.findParent("[data-afl-behavior=insert-before]"); - url = afl.attr "data-inject-row-url" + const url = afl.attr("data-inject-row-url"); - ajax url, - success: (response) -> - content = response.json?.content or "" + ajax(url, { + success(response) { + const content = (response.json != null ? response.json.content : undefined) || ""; - # Create a new element with the same type (usually "div") and class as this element. - # It will contain the new content. + // Create a new element with the same type (usually "div") and class as this element. + // It will contain the new content. - newElement = dom.create insertionPoint.element.tagName, - 'class': insertionPoint.element.className, 'data-container-type': FRAGMENT_TYPE, - content + const newElement = dom.create(insertionPoint.element.tagName, + {'class': insertionPoint.element.className, 'data-container-type': FRAGMENT_TYPE}, + content); - insertionPoint.insertBefore newElement + insertionPoint.insertBefore(newElement); - # Initialize components inside the new row - newElement.trigger events.initializeComponents + // Initialize components inside the new row + newElement.trigger(events.initializeComponents); - # Trigger this event, to inform the world that the zone-like new element has been updated - # with content. - insertionPoint.trigger events.zone.didUpdate + // Trigger this event, to inform the world that the zone-like new element has been updated + // with content. + insertionPoint.trigger(events.zone.didUpdate); - return + } + } + ); - return false + return false; + }); - # This module is all event handlers, and no exported functions. - return \ No newline at end of file + // This module is all event handlers, and no exported functions. +}); \ No newline at end of file diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/alert.js b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/alert.js index 778515020f..0dddb6189b 100644 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/alert.js +++ b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/alert.js @@ -1,142 +1,172 @@ -# Copyright 2012-2013 The Apache Software Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http:#www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# ## t5/core/alert -# -# Support for the core/Alerts components. -# -define ["t5/core/dom", "t5/core/console", "t5/core/messages", "t5/core/ajax", "underscore", "t5/core/bootstrap"], - (dom, console, messages, ajax, _, {glyph}) -> - - severityToClass = - info: "alert-info" - success: "alert-success" - warn: "alert-warning" +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md + */ +// Copyright 2012-2013 The Apache Software Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http:#www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ## t5/core/alert +// +// Support for the core/Alerts components. +// +define(["t5/core/dom", "t5/core/console", "t5/core/messages", "t5/core/ajax", "underscore", "t5/core/bootstrap"], + function(dom, console, messages, ajax, _, {glyph}) { + + let exports; + const severityToClass = { + info: "alert-info", + success: "alert-success", + warn: "alert-warning", error: "alert-danger" + }; - getURL = (container) -> container.attr "data-dismiss-url" + const getURL = container => container.attr("data-dismiss-url"); - removeAlert = (container, alert) -> - alert.remove() + const removeAlert = function(container, alert) { + alert.remove(); - if container.find(".alert").length is 0 - container.update null - - dismissAll = (container) -> - - alerts = container.find "[data-alert-id]" - - if alerts.length is 0 - container.update null - return - - ajax (getURL container), - success: -> container.update null - - dismissOne = (container, button) -> - - alert = button.parent() - - id = alert.attr "data-alert-id" - - unless id - removeAlert container, alert - return - - ajax (getURL container), - data: { id } - success: -> removeAlert container, alert - - setupUI = (outer) -> - - outer.update """ -
- """ - - if (outer.attr "data-show-dismiss-all") is "true" - outer.append """ -
- -
- """ - - outer.on "click", "[data-action=dismiss-all]", -> - dismissAll outer - return false - - outer.on "click", "button.close", -> - dismissOne outer, this - return false - - findInnerContainer = -> - outer = dom.body.findFirst "[data-container-type=alerts]" - - unless outer - console.error "Unable to locate alert container element to present an alert." - return null - - # Set up the inner content when needed - unless outer.element.firstChild - setupUI outer - - return outer?.findFirst "[data-container-type=inner]" - - # The `data` for the alert has a number of keys to control its behavior: - # - # * severity - used to determine the CSS class, may be "warn", "error", or "info" (the default) - # * message - message to display to as te alert's body - # * markup - if true, then the message contains markup that should not be HTML escaped - alert = (data) -> - - container = findInnerContainer() - - return unless container - - # Map from severity name to a CSS class; using alert-info if no severity, or unknown severity - className = severityToClass[data.severity] or "alert-info" - - content = if data.markup then data.message else _.escape data.message - - # Note that `data-dismiss=alert` is purposely excluded - # - we want to handle closes w/ notifications to the server if not transient - # - we don't want to rely on bootstrap.js, as that will drag jQuery into the application - # Also, the tag makes it easier to pull out just the content when doing tests, - # but we only add this add if the alert doesn't have a message that contains markup (TAP5-1863) - element = dom.create "div", - "data-alert-id": data.id + if (container.find(".alert").length === 0) { + return container.update(null); + } + }; + + const dismissAll = function(container) { + + const alerts = container.find("[data-alert-id]"); + + if (alerts.length === 0) { + container.update(null); + return; + } + + return ajax((getURL(container)), + {success() { return container.update(null); }}); + }; + + const dismissOne = function(container, button) { + + const alert = button.parent(); + + const id = alert.attr("data-alert-id"); + + if (!id) { + removeAlert(container, alert); + return; + } + + return ajax((getURL(container)), { + data: { id }, + success() { return removeAlert(container, alert); } + } + ); + }; + + const setupUI = function(outer) { + + outer.update(`\ +
\ +` + ); + + if ((outer.attr("data-show-dismiss-all")) === "true") { + outer.append(`\ +
+ +
\ +` + ); + } + + outer.on("click", "[data-action=dismiss-all]", function() { + dismissAll(outer); + return false; + }); + + return outer.on("click", "button.close", function() { + dismissOne(outer, this); + return false; + }); + }; + + const findInnerContainer = function() { + const outer = dom.body.findFirst("[data-container-type=alerts]"); + + if (!outer) { + console.error("Unable to locate alert container element to present an alert."); + return null; + } + + // Set up the inner content when needed + if (!outer.element.firstChild) { + setupUI(outer); + } + + return (outer != null ? outer.findFirst("[data-container-type=inner]") : undefined); + }; + + // The `data` for the alert has a number of keys to control its behavior: + // + // * severity - used to determine the CSS class, may be "warn", "error", or "info" (the default) + // * message - message to display to as te alert's body + // * markup - if true, then the message contains markup that should not be HTML escaped + const alert = function(data) { + + const container = findInnerContainer(); + + if (!container) { return; } + + // Map from severity name to a CSS class; using alert-info if no severity, or unknown severity + const className = severityToClass[data.severity] || "alert-info"; + + const content = data.markup ? data.message : _.escape(data.message); + + // Note that `data-dismiss=alert` is purposely excluded + // - we want to handle closes w/ notifications to the server if not transient + // - we don't want to rely on bootstrap.js, as that will drag jQuery into the application + // Also, the tag makes it easier to pull out just the content when doing tests, + // but we only add this add if the alert doesn't have a message that contains markup (TAP5-1863) + const element = dom.create("div", { + "data-alert-id": data.id, class: "alert alert-dismissable " + className - if data.markup - """ - - #{content} - """ - else - """ - - #{content} - """ - - container.append element - - if data['transient'] - outerContainer = container.findParent '[data-container-type=alerts]' - _.delay removeAlert, exports.TRANSIENT_DURATION, outerContainer, element - - alert.TRANSIENT_DURATION = 5000 - - # Export the alert function - exports = alert + }, + data.markup ? + `\ + +${content}\ +` + : + `\ + +${content}\ +` + ); + + container.append(element); + + if (data['transient']) { + const outerContainer = container.findParent('[data-container-type=alerts]'); + return _.delay(removeAlert, exports.TRANSIENT_DURATION, outerContainer, element); + } + }; + + alert.TRANSIENT_DURATION = 5000; + + // Export the alert function + return exports = alert; +}); diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/autocomplete.js b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/autocomplete.js index fe09a564a8..a1146f2f72 100644 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/autocomplete.js +++ b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/autocomplete.js @@ -1,48 +1,58 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http:#www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# ## t5/core/autocomplete -# -# Support for the core/Autocomplete Tapestry mixin, a wrapper around -# the Twitter autocomplete.js library. -define ["t5/core/dom", "t5/core/ajax", "underscore", "jquery", "t5/core/utils", "t5/core/typeahead"], - (dom, ajax, _, $, {extendURL}) -> - - init = (spec) -> - $field = $ document.getElementById spec.id - - engine = new Bloodhound - datumTokenizer: Bloodhound.tokenizers.whitespace - queryTokenizer: Bloodhound.tokenizers.whitespace - limit: spec.limit - remote: - url: spec.url - replace: (uri, query) -> extendURL uri, "t:input": query - filter: (response) -> response.matches - - engine.initialize() - - dataset = - name: spec.id - displayKey: _.identity +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md + */ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http:#www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ## t5/core/autocomplete +// +// Support for the core/Autocomplete Tapestry mixin, a wrapper around +// the Twitter autocomplete.js library. +define(["t5/core/dom", "t5/core/ajax", "underscore", "jquery", "t5/core/utils", "t5/core/typeahead"], + function(dom, ajax, _, $, {extendURL}) { + + let exports; + const init = function(spec) { + const $field = $(document.getElementById(spec.id)); + + const engine = new Bloodhound({ + datumTokenizer: Bloodhound.tokenizers.whitespace, + queryTokenizer: Bloodhound.tokenizers.whitespace, + limit: spec.limit, + remote: { + url: spec.url, + replace(uri, query) { return extendURL(uri, {"t:input": query}); }, + filter(response) { return response.matches; } + } + }); + + engine.initialize(); + + const dataset = { + name: spec.id, + displayKey: _.identity, source: engine.ttAdapter() + }; - $field.typeahead - minLength: spec.minChars - dataset + $field.typeahead( + {minLength: spec.minChars}, + dataset); - # don't validate the "tt-hint" input field created by Typeahead (fix for TAP5-2440) - $field.prev(".tt-hint").removeAttr("data-validation data-optionality data-required-message") + // don't validate the "tt-hint" input field created by Typeahead (fix for TAP5-2440) + $field.prev(".tt-hint").removeAttr("data-validation data-optionality data-required-message"); - return + }; - exports = init + return exports = init; +}); diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/bootstrap.js b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/bootstrap.js index fd9fcbe1ac..58fbd9c2ef 100644 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/bootstrap.js +++ b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/bootstrap.js @@ -1,29 +1,30 @@ -# Copyright 2013 The Apache Software Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http:#www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +// Copyright 2013 The Apache Software Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http:#www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -# ## t5/core/bootstrap -# -# Utilities for leveraging Bootstrap -define ["t5/core/bootstrap"], - -> +// ## t5/core/bootstrap +// +// Utilities for leveraging Bootstrap +define(["t5/core/bootstrap"], + function() { - exports = - # Generates the CSS class name for an icon. - # - # * name - of icon, e.g., "arrow-left" - glyph: (name) -> """""" + const exports = + // Generates the CSS class name for an icon. + // + // * name - of icon, e.g., "arrow-left" + {glyph(name) { return ``; }}; - return exports + return exports; +}); diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/confirm-click.js b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/confirm-click.js index 1d79e849c3..0d7bb7c2c5 100644 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/confirm-click.js +++ b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/confirm-click.js @@ -1,96 +1,109 @@ -# ## t5/core/confirm-click -# -# Support for the Tapestry Confirm mixin, and for running confirmation dialogs programmatically. - -define ["jquery", "t5/core/events", "bootstrap/modal"], - - ($, events) -> - - # Runs a modal dialog, invoking a callback if the user selects the OK option. On any form of cancel, - # there is no callback. - # - # options.title - default "Confirm" - # options.message - required - # options.okClass - default "btn-warning" - # options.okLabel - default "OK" - # options.cancelLabel - default "Cancel" - # options.ok - callback function, required - runDialog = (options) -> - - confirmed = false - - content = """ - - """ - - $dialog = $ content - - $dialog.on "click", ".modal-footer button:first", -> - confirmed = true - return - - # Let the animation run before (perhaps) invoking the callback. - $dialog.modal().on "hidden.bs.modal", -> - $dialog.remove() - if confirmed - options.ok() - - $dialog.appendTo $ "body" - - # Focus on the first button (the "OK") button. - $dialog.on "shown.bs.modal", -> - $dialog.find(".modal-footer .btn").first().focus() - - # Support for the Confirm mixin - $("body").on "click", "[data-confirm-message]:not(.disabled)", (event)-> - - $this = $(this) - - # We use a data- attribute as a flag, to indicate that the user confirmed the behavior. - - if ($this.attr "data-confirm-state") is "confirmed" - $this.attr "data-confirm-state", null - return # allow default behavior to continue - - runDialog - title: $this.attr "data-confirm-title" - message: $this.attr "data-confirm-message" - okClass: $this.attr "data-confirm-class-ok" - okLabel: $this.attr "data-confirm-label-ok" - cancelLabel: $this.attr "data-confirm-label-cancel" - ok: -> - $this.attr "data-confirm-state", "confirmed" - # In the case of an Ajax update, or a button, this is enough. In the case of a simple link, - # the default behavior when triggering click() is to do nothing, and our document event handler - # (just below) picks up the slack. - $this.click() - - # Cancel the original click event - return false - - ($ document).on "click", "a[data-confirm-message]:not(.disabled, [data-update-zone], [data-async-trigger])", (event) -> - - target = $ event.target - - # See note above; this replicates the default behavior of a link element that is lost because - # of the - window.location.href = target.attr "href" - return false - - # Exports: - - { runDialog } +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md + */ +// ## t5/core/confirm-click +// +// Support for the Tapestry Confirm mixin, and for running confirmation dialogs programmatically. + +define(["jquery", "t5/core/events", "bootstrap/modal"], + + function($, events) { + + // Runs a modal dialog, invoking a callback if the user selects the OK option. On any form of cancel, + // there is no callback. + // + // options.title - default "Confirm" + // options.message - required + // options.okClass - default "btn-warning" + // options.okLabel - default "OK" + // options.cancelLabel - default "Cancel" + // options.ok - callback function, required + const runDialog = function(options) { + + let confirmed = false; + + const content = `\ +\ +`; + + const $dialog = $(content); + + $dialog.on("click", ".modal-footer button:first", function() { + confirmed = true; + }); + + // Let the animation run before (perhaps) invoking the callback. + $dialog.modal().on("hidden.bs.modal", function() { + $dialog.remove(); + if (confirmed) { + return options.ok(); + } + }); + + $dialog.appendTo($("body")); + + // Focus on the first button (the "OK") button. + return $dialog.on("shown.bs.modal", () => $dialog.find(".modal-footer .btn").first().focus()); + }; + + // Support for the Confirm mixin + $("body").on("click", "[data-confirm-message]:not(.disabled)", function(event){ + + const $this = $(this); + + // We use a data- attribute as a flag, to indicate that the user confirmed the behavior. + + if (($this.attr("data-confirm-state")) === "confirmed") { + $this.attr("data-confirm-state", null); + return; // allow default behavior to continue + } + + runDialog({ + title: $this.attr("data-confirm-title"), + message: $this.attr("data-confirm-message"), + okClass: $this.attr("data-confirm-class-ok"), + okLabel: $this.attr("data-confirm-label-ok"), + cancelLabel: $this.attr("data-confirm-label-cancel"), + ok() { + $this.attr("data-confirm-state", "confirmed"); + // In the case of an Ajax update, or a button, this is enough. In the case of a simple link, + // the default behavior when triggering click() is to do nothing, and our document event handler + // (just below) picks up the slack. + return $this.click(); + } + }); + + // Cancel the original click event + return false; + }); + + ($(document)).on("click", "a[data-confirm-message]:not(.disabled, [data-update-zone], [data-async-trigger])", function(event) { + + const target = $(event.target); + + // See note above; this replicates the default behavior of a link element that is lost because + // of the + window.location.href = target.attr("href"); + return false; + }); + + // Exports: + + return { runDialog }; +}); diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/console.js b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/console.js index 1f09456978..a65bb3c2cf 100644 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/console.js +++ b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/console.js @@ -1,218 +1,250 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http:#www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md + */ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http:#www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ## t5/core/console +// +// A wrapper around the native console, when it exists. +define(["t5/core/dom", "underscore", "t5/core/bootstrap"], + function(dom, _, { glyph }) { + + let e; + let nativeConsole = null; + + try { + // FireFox will throw an exception if you even access the console object and it does + // not exist. Wow! + nativeConsole = window.console || {}; + } catch (error) { e = error; } + + let floatingConsole = null; + let messages = null; + + const noFilter = () => true; + + let filter = noFilter; + + const updateFilter = function(text) { + if (text === "") { + filter = noFilter; + return; + } + + const words = text.toLowerCase().split(/\s+/); + + filter = function(e) { + const content = e.text().toLowerCase(); + + for (var word of Array.from(words)) { + if (content.indexOf(word) < 0) { return false; } + } + + return true; + }; -# ## t5/core/console -# -# A wrapper around the native console, when it exists. -define ["t5/core/dom", "underscore", "t5/core/bootstrap"], - (dom, _, { glyph }) -> + }; - nativeConsole = null + const consoleAttribute = dom.body.attr("data-floating-console"); - try - # FireFox will throw an exception if you even access the console object and it does - # not exist. Wow! - nativeConsole = window.console or {} - catch e + const forceFloating = (consoleAttribute === "enabled") || (consoleAttribute === "invisible"); - floatingConsole = null - messages = null + const button = function(action, icon, label, disabled) { if (disabled == null) { disabled = false; } return `\ +\ +`; }; - noFilter = -> true + // _internal_: displays the message inside the floating console, creating the floating + // console as needed. + const display = function(className, message) { - filter = noFilter + if (!floatingConsole) { + floatingConsole = dom.create( + {class: "tapestry-console"}, + `\ +
+
+
+ ${button("clear", "remove", "Clear Console")} + ${button("enable", "play", "Enable Console")} + ${button("disable", "pause", "Disable Console")} +
+
+ +
+
\ +` + ); + + dom.body.prepend(floatingConsole); + + // Basically, any non-blank value will enable the floating console. In addition, the special + // value "invisible" will enable it but then hide it ... this is useful in tests, since + // the console output is captured in the markup, but the visible console can have unwanted interactions + // (such as obscuring elements that make them unclickable). + if (consoleAttribute === "invisible") { + floatingConsole.hide(); + } + } + + messages = floatingConsole.findFirst(".message-container"); - updateFilter = (text) -> - if text is "" - filter = noFilter - return + floatingConsole.findFirst("[data-action=enable]").attr("disabled", true); - words = text.toLowerCase().split /\s+/ + floatingConsole.on("click", "[data-action=clear]", function() { + floatingConsole.hide(); + return messages.update(""); + }); - filter = (e) -> - content = e.text().toLowerCase() + floatingConsole.on("click", "[data-action=disable]", function() { - for word in words - return false if content.indexOf(word) < 0 + this.attr("disabled", true); + floatingConsole.findFirst("[data-action=enable]").attr("disabled", false); - return true + messages.hide(); - return + return false; + }); - consoleAttribute = dom.body.attr "data-floating-console" + floatingConsole.on("click", "[data-action=enable]", function() { - forceFloating = (consoleAttribute is "enabled") or (consoleAttribute is "invisible") + this.attr("disabled", true); + floatingConsole.findFirst("[data-action=disable]").attr("disabled", false); - button = (action, icon, label, disabled = false) -> """ - - """ + messages.show(); - # _internal_: displays the message inside the floating console, creating the floating - # console as needed. - display = (className, message) -> + return false; + }); - unless floatingConsole - floatingConsole = dom.create - class: "tapestry-console", - """ -
-
-
- #{button "clear", "remove", "Clear Console"} - #{button "enable", "play", "Enable Console"} - #{button "disable", "pause", "Disable Console"} -
-
- -
-
- """ + floatingConsole.on("change keyup", "input", function() { + updateFilter(this.value()); - dom.body.prepend floatingConsole + for (e of Array.from(messages.children())) { + var visible = filter(e); - # Basically, any non-blank value will enable the floating console. In addition, the special - # value "invisible" will enable it but then hide it ... this is useful in tests, since - # the console output is captured in the markup, but the visible console can have unwanted interactions - # (such as obscuring elements that make them unclickable). - if consoleAttribute is "invisible" - floatingConsole.hide() + e[visible ? "show" : "hide"](); + } - messages = floatingConsole.findFirst ".message-container" + return false; + }); - floatingConsole.findFirst("[data-action=enable]").attr "disabled", true + const div = dom.create( + {class: className}, + _.escape(message)); - floatingConsole.on "click", "[data-action=clear]", -> - floatingConsole.hide() - messages.update "" + // Should really filter on original message, not escaped. - floatingConsole.on "click", "[data-action=disable]", -> + if (!filter(div)) { + div.hide(); + } - @attr "disabled", true - floatingConsole.findFirst("[data-action=enable]").attr "disabled", false + messages.append(div); - messages.hide() + // A slightly clumsy way to ensure that the container is scrolled to the bottom. + return _.delay(() => messages.element.scrollTop = messages.element.scrollHeight); + }; - return false + const level = (className, consolefn) => (function(message) { + // consolefn may be null if there's no console; under IE it may be non-null, but not a function. + // For some testing, it is nice to force the floating console to always display. - floatingConsole.on "click", "[data-action=enable]", -> + if (forceFloating || (!consolefn)) { + // Display it floating. If there's a real problem, such as a failed Ajax request, then the + // client-side code should be alerting the user in some other way, and not rely on them + // being able to see the logged console output. + display(className, message); - @attr "disabled", true - floatingConsole.findFirst("[data-action=disable]").attr "disabled", false + if (!forceFloating) { return; } + } - messages.show() + if (window.console && (_.isFunction(consolefn))) { + // Use the available native console, calling it like an instance method + consolefn.call(window.console, message); + return; + } - return false + // And IE just has to be different. The properties of console are callable, like functions, + // but aren't proper functions that work with `call()` either. + // On IE8, the console object is undefined unless debugging tools are enabled. + // In that case, nativeConsole will be an empty object. + if (consolefn) { + consolefn(message); + } - floatingConsole.on "change keyup", "input", -> - updateFilter @value() + }); - for e in messages.children() - visible = filter e + const exports = { + info: level("info", nativeConsole.info), + warn: level("warn", nativeConsole.warn), + error: level("error", nativeConsole.error), - e[if visible then "show" else "hide"]() + // Determine whether debug is enabled by checking for the necessary attribute (which is missing + // in production mode). + debugEnabled: ((document.documentElement.getAttribute("data-debug-enabled")) != null) + }; - return false + const noop = function() {}; - div = dom.create - class: className, - _.escape message - - # Should really filter on original message, not escaped. - - unless filter div - div.hide() - - messages.append div - - # A slightly clumsy way to ensure that the container is scrolled to the bottom. - _.delay -> messages.element.scrollTop = messages.element.scrollHeight - - level = (className, consolefn) -> - (message) -> - # consolefn may be null if there's no console; under IE it may be non-null, but not a function. - # For some testing, it is nice to force the floating console to always display. - - if forceFloating or (not consolefn) - # Display it floating. If there's a real problem, such as a failed Ajax request, then the - # client-side code should be alerting the user in some other way, and not rely on them - # being able to see the logged console output. - display className, message - - return unless forceFloating - - if window.console and (_.isFunction consolefn) - # Use the available native console, calling it like an instance method - consolefn.call window.console, message - return - - # And IE just has to be different. The properties of console are callable, like functions, - # but aren't proper functions that work with `call()` either. - # On IE8, the console object is undefined unless debugging tools are enabled. - # In that case, nativeConsole will be an empty object. - if consolefn - consolefn message - - return - - exports = - info: level "info", nativeConsole.info - warn: level "warn", nativeConsole.warn - error: level "error", nativeConsole.error - - # Determine whether debug is enabled by checking for the necessary attribute (which is missing - # in production mode). - debugEnabled: (document.documentElement.getAttribute "data-debug-enabled")? - - noop = -> - - # When debugging is not enabled, then the debug function becomes a no-op. + // When debugging is not enabled, then the debug function becomes a no-op. exports.debug = - if exports.debugEnabled - # If native console available, go for it. IE doesn't have debug, so we use log instead. - # Add a special noop case for IE8, since IE8 is just crazy. - level "debug", (nativeConsole.debug or nativeConsole.log or noop) - else - noop + exports.debugEnabled ? + // If native console available, go for it. IE doesn't have debug, so we use log instead. + // Add a special noop case for IE8, since IE8 is just crazy. + level("debug", (nativeConsole.debug || nativeConsole.log || noop)) + : + noop; - # This is also an aid to debugging; it allows arbitrary scripts to present on the console; when using Geb - # and/or Selenium, it is very useful to present debugging data right on the page. - window.t5console = exports + // This is also an aid to debugging; it allows arbitrary scripts to present on the console; when using Geb + // and/or Selenium, it is very useful to present debugging data right on the page. + window.t5console = exports; - requirejs.onError = (err) -> + requirejs.onError = function(err) { - message = "RequireJS error: #{err?.requireType or 'unknown'}" + let message = `RequireJS error: ${(err != null ? err.requireType : undefined) || 'unknown'}`; - if err.message - message += """: #{err.message}""" + if (err.message) { + message += `: ${err.message}`; + } - if err.requireType - modules = err?.requireModules - if modules and modules.length > 0 - message += """, modules #{modules.join(", ")}""" + if (err.requireType) { + const modules = err != null ? err.requireModules : undefined; + if (modules && (modules.length > 0)) { + message += `, modules ${modules.join(", ")}`; + } + } - if err.fileName - message += """, #{err.fileName}""" + if (err.fileName) { + message += `, ${err.fileName}`; + } - if err.lineNumber - message += """, line #{err.lineNumber}""" + if (err.lineNumber) { + message += `, line ${err.lineNumber}`; + } - if err.columnNumber - message += """, line #{err.columnNumber}""" + if (err.columnNumber) { + message += `, line ${err.columnNumber}`; + } - exports.error message + return exports.error(message); + }; - # Return the exports; we keep a reference to it, so we can see exports.DURATION, even - # if some other module imports this one and modifies that property. - return exports \ No newline at end of file + // Return the exports; we keep a reference to it, so we can see exports.DURATION, even + // if some other module imports this one and modifies that property. + return exports; +}); \ No newline at end of file diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/datefield.js b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/datefield.js index e4ea18f087..e0c01a6e3d 100644 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/datefield.js +++ b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/datefield.js @@ -1,180 +1,224 @@ -# Copyright 2012, 2013 The Apache Software Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http:#www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# ## t5/core/datefield -# -# Provides support for the `core/DateField` component. -define ["t5/core/dom", "t5/core/events", "t5/core/messages", "t5/core/ajax", "underscore", "t5/core/datepicker", "t5/core/fields"], - (dom, events, messages, ajax, _, DatePicker) -> - - - # Translate from the provided order (SUNDAY = 0, MONDAY = 1), to - # the order needed by the DatePicker component (MONDAY = 0 ... SUNDAY = 6) - serverFirstDay = parseInt messages "date-symbols.first-day" - datePickerFirstDay = if serverFirstDay is 0 then 6 else serverFirstDay - 1 - - # Localize a few other things. - days = (messages "date-symbols.days").split "," - - # Shuffle sunday to the end, so that monday is first. - - days.push days.shift() - - monthsLabels = (messages "date-symbols.months").split "," - abbreviateWeekDay = (name) -> name.substr(0, 1).toLowerCase() - locale = (document.documentElement.getAttribute("data-locale")) || "en" - if (locale.indexOf 'zh') is 0 - # TAP5-1886, Chinese weekdays cannot be abbreviated using the first character - abbreviateWeekDay = (name) -> name.substr(name.length-1) - daysLabels = (abbreviateWeekDay name for name in days) - todayLabel = messages "core-datefield-today" - noneLabel = messages "core-datefield-none" - - - # Track the active popup; only one allowed at a time. May look to rework this - # later so that there's just one popup and it is moved around the viewport, or - # around the DOM. - activePopup = null - - - isPartOfPopup = (element) -> - element.findParent(".labelPopup")? or element.findParent(".datefield-popup")? - - dom.body.on "click", -> - if activePopup and not isPartOfPopup @ - activePopup.hide() - activePopup = null - return - - - class Controller - constructor: (@container) -> - @field = @container.findFirst 'input:not([name="t:formdata"])' - @trigger = @container.findFirst "button" - - @trigger.on "click", => - @doTogglePopup() - false - - showPopup: -> - if activePopup and activePopup isnt @popup - activePopup.hide() - - @popup.show() - activePopup = @popup - - hidePopup: -> - @popup.hide() - activePopup = null - - doTogglePopup: -> - return if @field.element.disabled - - unless @popup - @createPopup() - activePopup?.hide() - else if @popup.visible() - @hidePopup() - return - - value = @field.value() - - if value is "" - @datePicker.setDate null - @showPopup() - return - - @field.addClass "ajax-wait" - - ajax (@container.attr "data-parse-url"), - data: +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS205: Consider reworking code to avoid use of IIFEs + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md + */ +// Copyright 2012, 2013 The Apache Software Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http:#www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ## t5/core/datefield +// +// Provides support for the `core/DateField` component. +define(["t5/core/dom", "t5/core/events", "t5/core/messages", "t5/core/ajax", "underscore", "t5/core/datepicker", "t5/core/fields"], + function(dom, events, messages, ajax, _, DatePicker) { + + + // Translate from the provided order (SUNDAY = 0, MONDAY = 1), to + // the order needed by the DatePicker component (MONDAY = 0 ... SUNDAY = 6) + let name; + const serverFirstDay = parseInt(messages("date-symbols.first-day")); + const datePickerFirstDay = serverFirstDay === 0 ? 6 : serverFirstDay - 1; + + // Localize a few other things. + const days = (messages("date-symbols.days")).split(","); + + // Shuffle sunday to the end, so that monday is first. + + days.push(days.shift()); + + const monthsLabels = (messages("date-symbols.months")).split(","); + let abbreviateWeekDay = name => name.substr(0, 1).toLowerCase(); + const locale = (document.documentElement.getAttribute("data-locale")) || "en"; + if ((locale.indexOf('zh')) === 0) { + // TAP5-1886, Chinese weekdays cannot be abbreviated using the first character + abbreviateWeekDay = name => name.substr(name.length-1); + } + const daysLabels = ((() => { + const result = []; + for (name of Array.from(days)) { result.push(abbreviateWeekDay(name)); + } + return result; + })()); + const todayLabel = messages("core-datefield-today"); + const noneLabel = messages("core-datefield-none"); + + + // Track the active popup; only one allowed at a time. May look to rework this + // later so that there's just one popup and it is moved around the viewport, or + // around the DOM. + let activePopup = null; + + + const isPartOfPopup = element => (element.findParent(".labelPopup") != null) || (element.findParent(".datefield-popup") != null); + + dom.body.on("click", function() { + if (activePopup && !isPartOfPopup(this)) { + activePopup.hide(); + activePopup = null; + } + }); + + + class Controller { + constructor(container) { + this.container = container; + this.field = this.container.findFirst('input:not([name="t:formdata"])'); + this.trigger = this.container.findFirst("button"); + + this.trigger.on("click", () => { + this.doTogglePopup(); + return false; + }); + } + + showPopup() { + if (activePopup && (activePopup !== this.popup)) { + activePopup.hide(); + } + + this.popup.show(); + return activePopup = this.popup; + } + + hidePopup() { + this.popup.hide(); + return activePopup = null; + } + + doTogglePopup() { + if (this.field.element.disabled) { return; } + + if (!this.popup) { + this.createPopup(); + if (activePopup != null) { + activePopup.hide(); + } + } else if (this.popup.visible()) { + this.hidePopup(); + return; + } + + const value = this.field.value(); + + if (value === "") { + this.datePicker.setDate(null); + this.showPopup(); + return; + } + + this.field.addClass("ajax-wait"); + + return ajax((this.container.attr("data-parse-url")), { + data: { input: value - onerror: (message) => - @field.removeClass "ajax-wait" - @fieldError message + }, + onerror: message => { + this.field.removeClass("ajax-wait"); + this.fieldError(message); - @showPopup() - return + this.showPopup(); + }, - success: (response) => - @field.removeClass "ajax-wait" - reply = response.json + success: response => { + this.field.removeClass("ajax-wait"); + const reply = response.json; - if reply.result - @clearFieldError() - [year, month, day] = reply.result.split '-' + if (reply.result) { + this.clearFieldError(); + const [year, month, day] = Array.from(reply.result.split('-')); - date = new Date year, month-1, day - @datePicker.setDate date + const date = new Date(year, month-1, day); + this.datePicker.setDate(date); + } - if reply.error - @fieldError (_.escape reply.error) + if (reply.error) { + this.fieldError((_.escape(reply.error))); - @datePicker.setDate null + this.datePicker.setDate(null); + } - @showPopup() - return + this.showPopup(); + } + } + ); + } - fieldError: (message) -> - @field.focus().trigger events.field.showValidationError, { message } + fieldError(message) { + return this.field.focus().trigger(events.field.showValidationError, { message }); + } - clearFieldError: -> - @field.trigger events.field.clearValidationError + clearFieldError() { + return this.field.trigger(events.field.clearValidationError); + } - createPopup: -> - @datePicker = new DatePicker() - @datePicker.setFirstWeekDay datePickerFirstDay + createPopup() { + this.datePicker = new DatePicker(); + this.datePicker.setFirstWeekDay(datePickerFirstDay); - @datePicker.setLocalizations monthsLabels, daysLabels, todayLabel, noneLabel + this.datePicker.setLocalizations(monthsLabels, daysLabels, todayLabel, noneLabel); - @popup = dom.create("div", { class: "datefield-popup well"}).append @datePicker.create() - @container.insertAfter @popup + this.popup = dom.create("div", { class: "datefield-popup well"}).append(this.datePicker.create()); + this.container.insertAfter(this.popup); - @datePicker.onselect = _.bind @onSelect, this + return this.datePicker.onselect = _.bind(this.onSelect, this); + } - onSelect: -> - date = @datePicker.getDate() + onSelect() { + const date = this.datePicker.getDate(); - if date is null - @hidePopup() - @clearFieldError() - @field.value "" - return + if (date === null) { + this.hidePopup(); + this.clearFieldError(); + this.field.value(""); + return; + } - @field.addClass "ajax-wait" + this.field.addClass("ajax-wait"); - normalizedFormat = "#{date.getFullYear()}-#{date.getMonth()+1}-#{date.getDate()}" - ajax (@container.attr "data-format-url"), - data: + const normalizedFormat = `${date.getFullYear()}-${date.getMonth()+1}-${date.getDate()}`; + return ajax((this.container.attr("data-format-url")), { + data: { input: normalizedFormat - failure: (response, message) => - @field.removeClass "ajax-wait" - @fieldError message - success: (response) => - @field.removeClass "ajax-wait" - @clearFieldError() - @field.value response.json.result - @hidePopup() - - - # Initialization: - - dom.scanner "[data-component-type='core/DateField']", (container) -> - # Hide it from later scans - container.attr "data-component-type", null - - new Controller(container) - - # Exports nothing. - return null + }, + failure: (response, message) => { + this.field.removeClass("ajax-wait"); + return this.fieldError(message); + }, + success: response => { + this.field.removeClass("ajax-wait"); + this.clearFieldError(); + this.field.value(response.json.result); + return this.hidePopup(); + } + } + ); + } + } + + + // Initialization: + + dom.scanner("[data-component-type='core/DateField']", function(container) { + // Hide it from later scans + container.attr("data-component-type", null); + + return new Controller(container); + }); + + // Exports nothing. + return null; +}); diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/events.js b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/events.js index ec7da0bf8f..9a2588a468 100644 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/events.js +++ b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/events.js @@ -1,187 +1,194 @@ -# Copyright 2012-2014 The Apache Software Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http:#www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# ## t5/core/events -# -# This module defines logical names for all events that Tapestry-controlled elements -# trigger or listener for. Prototype requires that all custom events have a namespace prefix; jQuery appears to -# allow it without issue. -define - - # Defines events related to the validation and submission of forms. See module `t5/core/forms` for further details. - # All events are triggered on a specific HTML `
` element, and top-level handlers take it from there. - form: - # Triggered after fields have been validated, when there are no field validation exceptions, to allow for - # cross-form validation. Passed a memo object: the handler should set the `error` property of the memo - # to true to indicate a validation exception occured, and the form submission should be prevented. - validate: "t5:form:validate" - - # Triggered after fields and form have been validated, when there are field or form validation exceptions. - validateInError: "t5:form:validateInError" - - # Triggered after `validate` (when there are no prior validation exceptions), to allow certain elements - # to configure themselves immediately before the form is submitted. This exists primarily for components such - # as FormFragment, which will enable or disable a hidden field to match the visibility of the fragment. - # There is no event memo. +// Copyright 2012-2014 The Apache Software Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http:#www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ## t5/core/events +// +// This module defines logical names for all events that Tapestry-controlled elements +// trigger or listener for. Prototype requires that all custom events have a namespace prefix; jQuery appears to +// allow it without issue. +define({ + + // Defines events related to the validation and submission of forms. See module `t5/core/forms` for further details. + // All events are triggered on a specific HTML `` element, and top-level handlers take it from there. + form: { + // Triggered after fields have been validated, when there are no field validation exceptions, to allow for + // cross-form validation. Passed a memo object: the handler should set the `error` property of the memo + // to true to indicate a validation exception occured, and the form submission should be prevented. + validate: "t5:form:validate", + + // Triggered after fields and form have been validated, when there are field or form validation exceptions. + validateInError: "t5:form:validateInError", + + // Triggered after `validate` (when there are no prior validation exceptions), to allow certain elements + // to configure themselves immediately before the form is submitted. This exists primarily for components such + // as FormFragment, which will enable or disable a hidden field to match the visibility of the fragment. + // There is no event memo. prepareForSubmit: "t5:form:prepare-for-submit" - - # Events releated to form input fields. Primarily, these events are related to form input validation. - # Validating a field involves three major steps: - # - # * optional - check for a required field that has no value - # * translate - translate a string to another representation, such as `Date`, or a number - # * validate - validate the field against any number of other constraints (such as ranges) - # - # A field that is blank but not required is considered valid: the translate and validate steps are skipped. - # - # When a validation error occurs, the event handler should present the validation error (see below), but also - # return `false`. This will prevent the event from propogating to other event handlers (Tapestry only supports - # a single validation exception per field). - # - # Presenting validation error: The event handler has two options for indicating a validation failure - # at any of the three steps: - # - # * set the `error` property of the memo to true, and trigger the `showValidationError` event (or otherwise - # make the validation error visible) - # * set the `error` property of the memo to the message to display; this will indicate a failure, and the - # `showValidationError` event will be triggered automatically. - # * In addition, return `false` to prevent the event bubbling (see note above). - field: - - # Perform the optionality check. The event memo includes a `value` property. If the field is required - # but the value is blank, then a validation error should be presented (as described above). The `value` - # property of the memo is as described for the `translate` event. - optional: "t5:field:optional" - - # Trigged by the field if there is a field value (a non-empty string, or a non-empty array in the case - # of a select element). The event memo includes the field's value as the `value` property. - # For text fields, the value is the text inside the field. For select elements, it is an array of the values - # of selected options. If the element has the attribute `data-value-mode` set to 'options', then the - # value will be the array of all options (selected or not; this is provided for the core/Palette Tapestry - # component). - # - # An event handler may update the event, setting the `translated` property to an alternate formatting, or - # alternate representation (e.g., `Date`, or a number) for the input value. If the input can not be translated, - # then a validation error should be presented (as described above). - translate: "t5:field:translate" - - # Triggered by the field if there is a field value, and the `translate` event succeeded. The event memo - # includes a `value' property, and a `translated` property. If any constraints on the field are invalid, - # then the event handler should be presented (as described above). - validate: "t5:field:validate" - - # Triggered by the form on all enclosed elements with the `data-validation` attribute (indicating they are - # interested in participating with user input validation). The default implementation fires a series of - # events: `optional`, `translate`, `validate`. The latter two are always skipped if the input is blank, or if - # a preceding event set the memo's `error` property to true. If all events complete without setting an error, - # then the `clearValidationError` event is triggered, to remove any validation errors from previous - # validation cycles. - # - # This event is passed a memo object; it should set the memo's `error` property to true if validation failed - # for the field. - inputValidation: "t5:field:input-validation" - - # Clears and hides the element used to display validation error messages. There is no memo for - # this event. The p.help-block for the field is located (if it exists) and emptied and hidden. - # The containing .form-group element (if it exists) has its "has-error" class name removed. - clearValidationError: "t5:field:clear-validation-error" - - # Presents a validation error for a field. The event memo should have a `message` key; the message to present - # (as a string, or even as a detached DOM element). The help block for the field will be located or created, - # made visible, and have its content updated to `memo.message`. If a containing element has the class ".form-group", - # then the class "has-error" will be added; otherwise, the immediately containing element will have class "has-error" - # added. The latter handles the case where, for layout reasons, the error container can not be inside the same - # .form-group as the form control (this often happens when constructing horizontal forms). - # - # The rules for locating the help block: - # - # * Search for element with attribute `data-error-block-for` set to the field's `id` attribute - # * If not found, find the enclosing .form-group element - # * Search the form group for an element with attribute `data-presentation="error"` - # * Otherwise, it is not found (but may be created dynamically) - # * If found, set the `data-error-block-for` attribute to the field's `id` (assigning a unique id to the field - # if not already present) - # - # The rules for creating the help block: - # - # * The element is created as `p.help-block` with `data-error-block-for` attribute set to the - # field's id. The field will be assigned an id if necesary. - # * Normally, the block is inserted immediately after the field - # * If the field's immediate container has class "input-group", then the block is inserted after the container + }, + + // Events releated to form input fields. Primarily, these events are related to form input validation. + // Validating a field involves three major steps: + // + // * optional - check for a required field that has no value + // * translate - translate a string to another representation, such as `Date`, or a number + // * validate - validate the field against any number of other constraints (such as ranges) + // + // A field that is blank but not required is considered valid: the translate and validate steps are skipped. + // + // When a validation error occurs, the event handler should present the validation error (see below), but also + // return `false`. This will prevent the event from propogating to other event handlers (Tapestry only supports + // a single validation exception per field). + // + // Presenting validation error: The event handler has two options for indicating a validation failure + // at any of the three steps: + // + // * set the `error` property of the memo to true, and trigger the `showValidationError` event (or otherwise + // make the validation error visible) + // * set the `error` property of the memo to the message to display; this will indicate a failure, and the + // `showValidationError` event will be triggered automatically. + // * In addition, return `false` to prevent the event bubbling (see note above). + field: { + + // Perform the optionality check. The event memo includes a `value` property. If the field is required + // but the value is blank, then a validation error should be presented (as described above). The `value` + // property of the memo is as described for the `translate` event. + optional: "t5:field:optional", + + // Trigged by the field if there is a field value (a non-empty string, or a non-empty array in the case + // of a select element). The event memo includes the field's value as the `value` property. + // For text fields, the value is the text inside the field. For select elements, it is an array of the values + // of selected options. If the element has the attribute `data-value-mode` set to 'options', then the + // value will be the array of all options (selected or not; this is provided for the core/Palette Tapestry + // component). + // + // An event handler may update the event, setting the `translated` property to an alternate formatting, or + // alternate representation (e.g., `Date`, or a number) for the input value. If the input can not be translated, + // then a validation error should be presented (as described above). + translate: "t5:field:translate", + + // Triggered by the field if there is a field value, and the `translate` event succeeded. The event memo + // includes a `value' property, and a `translated` property. If any constraints on the field are invalid, + // then the event handler should be presented (as described above). + validate: "t5:field:validate", + + // Triggered by the form on all enclosed elements with the `data-validation` attribute (indicating they are + // interested in participating with user input validation). The default implementation fires a series of + // events: `optional`, `translate`, `validate`. The latter two are always skipped if the input is blank, or if + // a preceding event set the memo's `error` property to true. If all events complete without setting an error, + // then the `clearValidationError` event is triggered, to remove any validation errors from previous + // validation cycles. + // + // This event is passed a memo object; it should set the memo's `error` property to true if validation failed + // for the field. + inputValidation: "t5:field:input-validation", + + // Clears and hides the element used to display validation error messages. There is no memo for + // this event. The p.help-block for the field is located (if it exists) and emptied and hidden. + // The containing .form-group element (if it exists) has its "has-error" class name removed. + clearValidationError: "t5:field:clear-validation-error", + + // Presents a validation error for a field. The event memo should have a `message` key; the message to present + // (as a string, or even as a detached DOM element). The help block for the field will be located or created, + // made visible, and have its content updated to `memo.message`. If a containing element has the class ".form-group", + // then the class "has-error" will be added; otherwise, the immediately containing element will have class "has-error" + // added. The latter handles the case where, for layout reasons, the error container can not be inside the same + // .form-group as the form control (this often happens when constructing horizontal forms). + // + // The rules for locating the help block: + // + // * Search for element with attribute `data-error-block-for` set to the field's `id` attribute + // * If not found, find the enclosing .form-group element + // * Search the form group for an element with attribute `data-presentation="error"` + // * Otherwise, it is not found (but may be created dynamically) + // * If found, set the `data-error-block-for` attribute to the field's `id` (assigning a unique id to the field + // if not already present) + // + // The rules for creating the help block: + // + // * The element is created as `p.help-block` with `data-error-block-for` attribute set to the + // field's id. The field will be assigned an id if necesary. + // * Normally, the block is inserted immediately after the field + // * If the field's immediate container has class "input-group", then the block is inserted after the container showValidationError: "t5:field:show-validation-error" - - # Events triggered by the Palette component. - palette: - # Event triggered when the selection is about to change. - # - # * memo.selectedOptions - array of selected options (e.g., HTMLOptionElement) representing which options - # will be selected in the Palette, should the change be allowed. - # * memo.reorder - if true, then the event represents changing the order of the selections only - # * memo.cancel - function to invoke to prevent the change to the Palette from occurring - # * memo.defer - like cancel, but returns a no-arguments function that will perform the update at a later date (e.g., - # after a confirmation panel) - willChange: "t5:palette:willChange" - # Event triggered after the Palette selection has changed. - # - # * memo.selectedOptions - array of selected options (e.g., HTMLOptionElement) - # * memo.reorder - if true, the event represents a change in the order of selections only + }, + + // Events triggered by the Palette component. + palette: { + // Event triggered when the selection is about to change. + // + // * memo.selectedOptions - array of selected options (e.g., HTMLOptionElement) representing which options + // will be selected in the Palette, should the change be allowed. + // * memo.reorder - if true, then the event represents changing the order of the selections only + // * memo.cancel - function to invoke to prevent the change to the Palette from occurring + // * memo.defer - like cancel, but returns a no-arguments function that will perform the update at a later date (e.g., + // after a confirmation panel) + willChange: "t5:palette:willChange", + // Event triggered after the Palette selection has changed. + // + // * memo.selectedOptions - array of selected options (e.g., HTMLOptionElement) + // * memo.reorder - if true, the event represents a change in the order of selections only didChange: "t5:palette:didChange" - - # Defines a number of event names specific to Tapestry Zones. Zones are Tapestry components that are structured - # to correctly support dynamic updates from the server via an Ajax request, and a standard response - # (the partial page render reponse). More details are available in the `t5/core/zone` module. - zone: - # Invoked on a zone element to force an update to its content. The event memo should contain a `content` key (an - # Element, or a `t5/core/dom:ElementWrapper`, or more typically, a string containing HTML markup). A standard top-level - # handler is defined by module `t5/core/zone`, and is responsible for the actual update; it triggers the - # `events.zone.willUpdate` and `events.zone.didUpdate` events just before and just after changing the element's - # content. - update: "t5:zone:update" - - # Triggered (by the standard `events.zone.update` event handler) just before the content in a Zone will be updated. - willUpdate: "t5:zone:will-update" - - # Triggered (by the standard `events.zone.update` event handle) just after the content in a Zone has updated. - # If the zone was not visible, it is made visible after its content is changed, and before this event is triggered. - # Some number of other components that also perform Ajax updates of the page also trigger this event. - # - # Certain components bind this event to scan new additions to the page to see if certain structures exist and - # create client-side support in the form of controllers and event handlers. DateField is one such example - # (see `t5/core/datefield` module). - didUpdate: "t5:zone:did-update" - - # Triggered on (or within) a zone element, the default handler will peform an Ajax request and, when the response is available, - # update the zone (via `events.zone.update`). The request should provide a partial page render response. If the - # response includes a `content` key, its value will be the markup to replace the zone element's body. - # - # * memo.url - URL to use for the Ajax request - # * memo.parameters - (optional) additional query parameters for the request + }, + + // Defines a number of event names specific to Tapestry Zones. Zones are Tapestry components that are structured + // to correctly support dynamic updates from the server via an Ajax request, and a standard response + // (the partial page render reponse). More details are available in the `t5/core/zone` module. + zone: { + // Invoked on a zone element to force an update to its content. The event memo should contain a `content` key (an + // Element, or a `t5/core/dom:ElementWrapper`, or more typically, a string containing HTML markup). A standard top-level + // handler is defined by module `t5/core/zone`, and is responsible for the actual update; it triggers the + // `events.zone.willUpdate` and `events.zone.didUpdate` events just before and just after changing the element's + // content. + update: "t5:zone:update", + + // Triggered (by the standard `events.zone.update` event handler) just before the content in a Zone will be updated. + willUpdate: "t5:zone:will-update", + + // Triggered (by the standard `events.zone.update` event handle) just after the content in a Zone has updated. + // If the zone was not visible, it is made visible after its content is changed, and before this event is triggered. + // Some number of other components that also perform Ajax updates of the page also trigger this event. + // + // Certain components bind this event to scan new additions to the page to see if certain structures exist and + // create client-side support in the form of controllers and event handlers. DateField is one such example + // (see `t5/core/datefield` module). + didUpdate: "t5:zone:did-update", + + // Triggered on (or within) a zone element, the default handler will peform an Ajax request and, when the response is available, + // update the zone (via `events.zone.update`). The request should provide a partial page render response. If the + // response includes a `content` key, its value will be the markup to replace the zone element's body. + // + // * memo.url - URL to use for the Ajax request + // * memo.parameters - (optional) additional query parameters for the request refresh: "t5:zone:refresh" - - # Event names for arbitrary elements. These notifications exist primarily to allow for customizations in how - # certain behaviors are presented, for example, to add animation when certain elements are hidden or revealed. - element: - # Triggered when a hidden element has just been displayed. - didShow: "t5:element:did-show" - # Trigered when a visible element has just been hidden. + }, + + // Event names for arbitrary elements. These notifications exist primarily to allow for customizations in how + // certain behaviors are presented, for example, to add animation when certain elements are hidden or revealed. + element: { + // Triggered when a hidden element has just been displayed. + didShow: "t5:element:did-show", + // Trigered when a visible element has just been hidden. didHide: "t5:element:did-hide" - # Event names specific to client-side element associated with the FormFragment component. These events exist to allow - # client code to cleanly adjust the visibility of the fragment, or remove it. - formfragment: - # Requests that the fragment change its visibility. The event memo is an object with a single key, visible, a - # boolean. The fragment will show or hide itself if necessary (triggering the `element.didShow` or - # `element.didHide` event). + }, + // Event names specific to client-side element associated with the FormFragment component. These events exist to allow + // client code to cleanly adjust the visibility of the fragment, or remove it. + formfragment: { + // Requests that the fragment change its visibility. The event memo is an object with a single key, visible, a + // boolean. The fragment will show or hide itself if necessary (triggering the `element.didShow` or + // `element.didHide` event). changeVisibility: "t5:fragment:change-visibility" - # Event to scan inserted DOM content for components to initialize (see t5/core/dom:scanner) + }, + // Event to scan inserted DOM content for components to initialize (see t5/core/dom:scanner) initializeComponents : "t5:initializeComponents" +}); diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/exception-display.js b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/exception-display.js index 5559c07d95..1cb89f044c 100644 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/exception-display.js +++ b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/exception-display.js @@ -1,30 +1,39 @@ -# Copyright 2012-2013 The Apache Software Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http:#www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md + */ +// Copyright 2012-2013 The Apache Software Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http:#www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -# ## t5/core/exception-display -# -# Provides dynamic behavior for the t5/core/ExceptionDisplay component; specifically, -# filtering the stack trace. -define ["t5/core/dom"], - (dom) -> +// ## t5/core/exception-display +// +// Provides dynamic behavior for the t5/core/ExceptionDisplay component; specifically, +// filtering the stack trace. +define(["t5/core/dom"], + function(dom) { - dom.onDocument "click", "[data-behavior=stack-trace-filter-toggle]", -> - checked = @element.checked + dom.onDocument("click", "[data-behavior=stack-trace-filter-toggle]", function() { + const { + checked + } = this.element; - for traceList in dom.body.find ".stack-trace" - traceList[if checked then "addClass" else "removeClass"] "filtered" + for (var traceList of Array.from(dom.body.find(".stack-trace"))) { + traceList[checked ? "addClass" : "removeClass"]("filtered"); + } - return + }); - return null + return null; +}); diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/exception-frame.js b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/exception-frame.js index f587ecc4e5..25c73a4593 100644 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/exception-frame.js +++ b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/exception-frame.js @@ -1,64 +1,75 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http:#www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md + */ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http:#www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -# ## t5/core/exception-frame -# -# Manages a special element used to present an HTML exception report from an Ajax request (where a non-markup response -# was expected, including a partial page render response). -define ["t5/core/dom"], (dom) -> +// ## t5/core/exception-frame +// +// Manages a special element used to present an HTML exception report from an Ajax request (where a non-markup response +// was expected, including a partial page render response). +define(["t5/core/dom"], function(dom) { - write = (container, content) -> - iframe = (container.findFirst "iframe").element + const write = function(container, content) { + const iframe = (container.findFirst("iframe")).element; - # See http://xkr.us/articles/dom/iframe-document/ + // See http://xkr.us/articles/dom/iframe-document/ - iframeDocument = iframe.contentWindow or iframe.contentDocument - if iframeDocument.document - iframeDocument = iframeDocument.document + let iframeDocument = iframe.contentWindow || iframe.contentDocument; + if (iframeDocument.document) { + iframeDocument = iframeDocument.document; + } - # Clear current content: - iframeDocument.open() - # Write new content: - iframeDocument.write content - iframeDocument.close() + // Clear current content: + iframeDocument.open(); + // Write new content: + iframeDocument.write(content); + return iframeDocument.close(); + }; - clear = -> - container = @closest '.exception-container' - container.remove() - return false + const clear = function() { + const container = this.closest('.exception-container'); + container.remove(); + return false; + }; - create = -> + const create = function() { - container = dom.create - class: "exception-container" - """ - -
- -
- """ + const container = dom.create( + {class: "exception-container"}, + `\ + +
+ +
\ +` + ); - dom.body.append container.hide() + dom.body.append(container.hide()); - container.on "click", "button", clear - container + container.on("click", "button", clear); + return container; + }; - # Export single function: + // Export single function: - (exceptionContent) -> - container = create() - write container, exceptionContent - container.show() - return \ No newline at end of file + return function(exceptionContent) { + const container = create(); + write(container, exceptionContent); + container.show(); + }; +}); \ No newline at end of file diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/fields.js b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/fields.js index a43dc61f79..2c9d085a6e 100644 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/fields.js +++ b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/fields.js @@ -1,195 +1,220 @@ -# Copyright 2012-2013 The Apache Software Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http:#www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# ## t5/core/fields -# -# Module for logic relating to form input fields (input, select, textarea); specifically -# presenting validation errors and perfoming input validation when necessary. -define ["underscore", "t5/core/events", "t5/core/dom", "t5/core/utils", "t5/core/forms"], - (_, events, dom, utils) -> - - ensureFieldId = (field) -> - fieldId = field.attr "id" - - unless fieldId - fieldId = _.uniqueId "field" - field.attr "id", fieldId - - return fieldId - - # Finds any `.help-block` used for presenting errors for the provided field. - # Returns the found block(s) as an array of ElementWrapper. Returns null - # if no blocks can be found. - # - # Normally, you would expect just a single help block for a field, but in some cases, - # such as to support responsive layout, there will be multiple help blocks for a single field. - # - # * field - element wrapper for the field - findHelpBlocks = (field) -> - fieldId = field.attr "id" - - # When the field has an id (the normal case!), search the body for - # the matching help blocks. - if fieldId - blocks = dom.body.find "[data-error-block-for='#{fieldId}']" - - return blocks if blocks.length > 0 - else - # Assign a unique (hopefully!) client id for the field, which will be - # used to link the field and the new help-block together. - fieldId = ensureFieldId field - - # Not found by id, but see if an empty placeholder was provided within - # the same .form-group. - - group = field.findParent ".form-group" - - return null unless group - - # This happens less often, now that the Errors component ensures (at render time) - # a fieldId and a data-error-block-for element. Even so, sometimes a template - # will just contain a div.help-block[data-presentation=error] - block = group.findFirst "[data-presentation=error]" - - if block - block.attr "data-error-block-for", fieldId - return [block] - - # Not found, so perhaps it will be created dynamically. - return null - - createHelpBlock = (field) -> - fieldId = ensureFieldId field - - # No containing group ... this is a problem, probably an old 5.3 application upgraded to 5.4 - # or beyond. Place the block just after the field. - - container = field.parent() - - block = dom.create "p", - class: "help-block form-text text-danger" - "data-error-block-for": fieldId +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md + */ +// Copyright 2012-2013 The Apache Software Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http:#www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ## t5/core/fields +// +// Module for logic relating to form input fields (input, select, textarea); specifically +// presenting validation errors and perfoming input validation when necessary. +define(["underscore", "t5/core/events", "t5/core/dom", "t5/core/utils", "t5/core/forms"], + function(_, events, dom, utils) { + + let exports; + const ensureFieldId = function(field) { + let fieldId = field.attr("id"); + + if (!fieldId) { + fieldId = _.uniqueId("field"); + field.attr("id", fieldId); + } + + return fieldId; + }; + + // Finds any `.help-block` used for presenting errors for the provided field. + // Returns the found block(s) as an array of ElementWrapper. Returns null + // if no blocks can be found. + // + // Normally, you would expect just a single help block for a field, but in some cases, + // such as to support responsive layout, there will be multiple help blocks for a single field. + // + // * field - element wrapper for the field + const findHelpBlocks = function(field) { + let fieldId = field.attr("id"); + + // When the field has an id (the normal case!), search the body for + // the matching help blocks. + if (fieldId) { + const blocks = dom.body.find(`[data-error-block-for='${fieldId}']`); + + if (blocks.length > 0) { return blocks; } + } else { + // Assign a unique (hopefully!) client id for the field, which will be + // used to link the field and the new help-block together. + fieldId = ensureFieldId(field); + } + + // Not found by id, but see if an empty placeholder was provided within + // the same .form-group. + + const group = field.findParent(".form-group"); + + if (!group) { return null; } + + // This happens less often, now that the Errors component ensures (at render time) + // a fieldId and a data-error-block-for element. Even so, sometimes a template + // will just contain a div.help-block[data-presentation=error] + const block = group.findFirst("[data-presentation=error]"); + + if (block) { + block.attr("data-error-block-for", fieldId); + return [block]; + } + + // Not found, so perhaps it will be created dynamically. + return null; + }; + + const createHelpBlock = function(field) { + const fieldId = ensureFieldId(field); + + // No containing group ... this is a problem, probably an old 5.3 application upgraded to 5.4 + // or beyond. Place the block just after the field. + + const container = field.parent(); + + const block = dom.create("p", { + class: "help-block form-text text-danger", + "data-error-block-for": fieldId, "id": fieldId + "-help-block" + } + ); - # The .input-group selectors are used to attach buttons or markers to the field. - # In which case, the help block can go after the group instead. - if container.hasClass("input-group") - container.insertAfter block - else - field.insertAfter block + // The .input-group selectors are used to attach buttons or markers to the field. + // In which case, the help block can go after the group instead. + if (container.hasClass("input-group")) { + container.insertAfter(block); + } else { + field.insertAfter(block); + } - return block + return block; + }; - showValidationError = (id, message) -> - dom.wrap(id).trigger events.field.showValidationError, { message } + const showValidationError = (id, message) => dom.wrap(id).trigger(events.field.showValidationError, { message }); - collectOptionValues = (wrapper) -> - _.pluck wrapper.element.options, "value" + const collectOptionValues = wrapper => _.pluck(wrapper.element.options, "value"); - # Default registrations: + // Default registrations: - dom.onDocument events.field.inputValidation, (event, formMemo) -> + dom.onDocument(events.field.inputValidation, function(event, formMemo) { - # Fields that are disabled, or not visible to the user are not subject to - # validation. Typically, a field will only be invisible due to the - # core/FormFragment component. - return if @element.disabled or (not @deepVisible()) + // Fields that are disabled, or not visible to the user are not subject to + // validation. Typically, a field will only be invisible due to the + // core/FormFragment component. + if (this.element.disabled || (!this.deepVisible())) { return; } - failure = false + let failure = false; - fieldValue = - if (@attr "data-value-mode") is "options" - collectOptionValues this - else if @element.type is "checkbox" - @checked() - else - @value() + const fieldValue = + (this.attr("data-value-mode")) === "options" ? + collectOptionValues(this) + : this.element.type === "checkbox" ? + this.checked() + : + this.value(); - memo = value: fieldValue + const memo = {value: fieldValue}; - postEventTrigger = => - if memo.error - # Assume the event handler displayed the message. - failure = true + const postEventTrigger = () => { + if (memo.error) { + // Assume the event handler displayed the message. + failure = true; - if _.isString memo.error + if (_.isString(memo.error)) { - @trigger events.field.showValidationError, { message: memo.error } + return this.trigger(events.field.showValidationError, { message: memo.error }); + } + } + }; - @trigger events.field.optional, memo + this.trigger(events.field.optional, memo); - postEventTrigger() + postEventTrigger(); - unless failure or (utils.isBlank memo.value) + if (!failure && (!utils.isBlank(memo.value))) { - @trigger events.field.translate, memo + this.trigger(events.field.translate, memo); - postEventTrigger() + postEventTrigger(); - unless failure - if _.isUndefined memo.translated - memo.translated = memo.value + if (!failure) { + if (_.isUndefined(memo.translated)) { + memo.translated = memo.value; + } - @trigger events.field.validate, memo + this.trigger(events.field.validate, memo); - postEventTrigger() + postEventTrigger(); + } + } - if failure - formMemo.error = true + if (failure) { + formMemo.error = true; this.attr('aria-invalid', 'true'); this.attr('aria-describedby', this.attr('id') + "-help-block"); - else + } else { this.attr('aria-invalid', 'false'); this.attr('aria-describedby ', null); - @trigger events.field.clearValidationError + this.trigger(events.field.clearValidationError); + } - return + }); - dom.onDocument events.field.clearValidationError, -> - blocks = exports.findHelpBlocks this + dom.onDocument(events.field.clearValidationError, function() { + const blocks = exports.findHelpBlocks(this); - for block in blocks or [] - block.hide().update("") - block.parent().removeClass "has-error" - block.attr("role", null) + for (var block of Array.from(blocks || [])) { + block.hide().update(""); + block.parent().removeClass("has-error"); + block.attr("role", null); + } - group = @findParent ".form-group" + const group = this.findParent(".form-group"); - group and group.removeClass "has-error" + group && group.removeClass("has-error"); - return + }); - dom.onDocument events.field.showValidationError, (event, memo) -> - blocks = exports.findHelpBlocks this + dom.onDocument(events.field.showValidationError, function(event, memo) { + let blocks = exports.findHelpBlocks(this); - unless blocks - blocks = [exports.createHelpBlock this] + if (!blocks) { + blocks = [exports.createHelpBlock(this)]; + } - for block in blocks - block.removeClass("invisible").show().update(memo.message) - # Add "has-error" to the help-block's immediate container; this assist with some layout issues - # where the help block can't be under the same .form-group element as the field (more common - # with a horizontal form layout). - block.parent().addClass("has-error") - block.attr("role", "alert") + for (var block of Array.from(blocks)) { + block.removeClass("invisible").show().update(memo.message); + // Add "has-error" to the help-block's immediate container; this assist with some layout issues + // where the help block can't be under the same .form-group element as the field (more common + // with a horizontal form layout). + block.parent().addClass("has-error"); + block.attr("role", "alert"); + } - group = @findParent ".form-group" + const group = this.findParent(".form-group"); - container = group or @parent().closest(":not(.input-group)") + const container = group || this.parent().closest(":not(.input-group)"); - container.addClass "has-error" + container.addClass("has-error"); - return + }); - exports = {findHelpBlocks, createHelpBlock, showValidationError} \ No newline at end of file + return exports = {findHelpBlocks, createHelpBlock, showValidationError}; +}); \ No newline at end of file diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/form-fragment.js b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/form-fragment.js index ce4e1138cf..ce03724ae0 100644 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/form-fragment.js +++ b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/form-fragment.js @@ -1,98 +1,121 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http:#www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -# ## t5/core/form-fragment -# -define ["underscore", "t5/core/dom", "t5/core/events", "t5/core/forms"], - (_, dom, events) -> - - SELECTOR = "[data-component-type='core/FormFragment']" - - REENABLE = "data-re-enable-when-fragment-visible" - - disableInputFields = (fragment) -> - - # This is an example of where the t5/core/dom abstraction label is problematic, - # as this is very inefficient vs. the native jQuery approach. - for field in fragment.find "input:not(:disabled)" - field.attr "disabled", true - field.attr REENABLE, true - - renableInputFields = (fragment) -> - - for field in fragment.find "input[#{REENABLE}]" - field.attr "disabled", null - field.attr REENABLE, null - - updateFields = (fragment, makeVisible) -> - - # This is a server side option that says the content of the fragment should always be submitted, - # even if the fragment is not currently visible. - return if fragment.attr "data-always-submit" - - f = if makeVisible then renableInputFields else disableInputFields - - f fragment - - # Again, a DOM event to make the FormFragment visible or invisible; this is useful - # because of the didShow/didHide events ... but we're really just seeing the evolution - # from the old style (the FormFragment class as controller) to the new style (DOM events and - # top-level event handlers). - dom.onDocument events.formfragment.changeVisibility, SELECTOR, (event) -> - makeVisible = event.memo.visible - - this[if makeVisible then "show" else "hide"]() - - updateFields this, makeVisible - - @trigger events.element[if makeVisible then "didShow" else "didHide"] - - return false - - # When a FormFragment is initially rendered as hidden, then we need to do some - # book-keeping on the client side. - hide = (id) -> - field = dom(id) - - updateFields field, false - - # Initializes a trigger for a FormFragment - # - # * spec.triggerId - id of checkbox or radio button - # * spec.fragmentId - id of FormFragment element - # * spec.invert - (optional) if true, then checked trigger hides (not shows) the fragment - linkTrigger = (spec) -> - unless spec.triggerId? then throw new Error "Incomplete parameters, triggerId is null" - unless spec.fragmentId? then throw new Error "Incomplete parameters, fragmentId is null" - trigger = dom spec.triggerId - fragment = dom spec.fragmentId - if fragment is null - throw new Error "Invalid configuration, fragment with id #{spec.fragmentId} not found" - - invert = spec.invert or false - - update = -> - checked = trigger.element.checked - makeVisible = checked isnt invert - - fragment.trigger events.formfragment.changeVisibility, visible: makeVisible - - return - - if trigger.element.type is "radio" - dom.on trigger.element.form, "click", update - else - trigger.on "click", update - - # Module exports: - { linkTrigger, hide } +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS205: Consider reworking code to avoid use of IIFEs + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md + */ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http:#www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +// ## t5/core/form-fragment +// +define(["underscore", "t5/core/dom", "t5/core/events", "t5/core/forms"], + function(_, dom, events) { + + const SELECTOR = "[data-component-type='core/FormFragment']"; + + const REENABLE = "data-re-enable-when-fragment-visible"; + + const disableInputFields = fragment => // This is an example of where the t5/core/dom abstraction label is problematic, + // as this is very inefficient vs. the native jQuery approach. + (() => { + const result = []; + for (var field of Array.from(fragment.find("input:not(:disabled)"))) { + field.attr("disabled", true); + result.push(field.attr(REENABLE, true)); + } + return result; + })(); + + const renableInputFields = fragment => (() => { + const result = []; + for (var field of Array.from(fragment.find(`input[${REENABLE}]`))) { + field.attr("disabled", null); + result.push(field.attr(REENABLE, null)); + } + return result; + })(); + + const updateFields = function(fragment, makeVisible) { + + // This is a server side option that says the content of the fragment should always be submitted, + // even if the fragment is not currently visible. + if (fragment.attr("data-always-submit")) { return; } + + const f = makeVisible ? renableInputFields : disableInputFields; + + return f(fragment); + }; + + // Again, a DOM event to make the FormFragment visible or invisible; this is useful + // because of the didShow/didHide events ... but we're really just seeing the evolution + // from the old style (the FormFragment class as controller) to the new style (DOM events and + // top-level event handlers). + dom.onDocument(events.formfragment.changeVisibility, SELECTOR, function(event) { + const makeVisible = event.memo.visible; + + this[makeVisible ? "show" : "hide"](); + + updateFields(this, makeVisible); + + this.trigger(events.element[makeVisible ? "didShow" : "didHide"]); + + return false; + }); + + // When a FormFragment is initially rendered as hidden, then we need to do some + // book-keeping on the client side. + const hide = function(id) { + const field = dom(id); + + return updateFields(field, false); + }; + + // Initializes a trigger for a FormFragment + // + // * spec.triggerId - id of checkbox or radio button + // * spec.fragmentId - id of FormFragment element + // * spec.invert - (optional) if true, then checked trigger hides (not shows) the fragment + const linkTrigger = function(spec) { + if (spec.triggerId == null) { throw new Error("Incomplete parameters, triggerId is null"); } + if (spec.fragmentId == null) { throw new Error("Incomplete parameters, fragmentId is null"); } + const trigger = dom(spec.triggerId); + const fragment = dom(spec.fragmentId); + if (fragment === null) { + throw new Error(`Invalid configuration, fragment with id ${spec.fragmentId} not found`); + } + + const invert = spec.invert || false; + + const update = function() { + const { + checked + } = trigger.element; + const makeVisible = checked !== invert; + + fragment.trigger(events.formfragment.changeVisibility, {visible: makeVisible}); + + }; + + if (trigger.element.type === "radio") { + return dom.on(trigger.element.form, "click", update); + } else { + return trigger.on("click", update); + } + }; + + // Module exports: + return { linkTrigger, hide }; +}); diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/forms.js b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/forms.js index 00a8e6a341..985333c442 100644 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/forms.js +++ b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/forms.js @@ -1,211 +1,240 @@ -# Copyright 2012, 2013 The Apache Software Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http:#www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# ## t5/core/forms -# -# Defines handlers for HTML forms and HTML field elements, specifically to control input validation. - -define ["t5/core/events", "t5/core/dom", "underscore"], - (events, dom, _) -> - - # Meta-data name that indicates the next submission should skip validation (typically, because - # the form was submitted by a "cancel" button). - SKIP_VALIDATION = "t5:skip-validation" +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md + */ +// Copyright 2012, 2013 The Apache Software Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http:#www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ## t5/core/forms +// +// Defines handlers for HTML forms and HTML field elements, specifically to control input validation. + +define(["t5/core/events", "t5/core/dom", "underscore"], + function(events, dom, _) { + + // Meta-data name that indicates the next submission should skip validation (typically, because + // the form was submitted by a "cancel" button). + let exports; + const SKIP_VALIDATION = "t5:skip-validation"; - # Data attribute and value added to HTML forms generated by the - # Tapestry's Form component from the core library. - DATA_ATTRIBUTE = "data-generator" - DATA_ATTRIBUTE_VALUE = "tapestry/core/form" - TAPESTRY_CORE_FORM_SELECTOR = "form[" + DATA_ATTRIBUTE + "='" + DATA_ATTRIBUTE_VALUE + "']" + // Data attribute and value added to HTML forms generated by the + // Tapestry's Form component from the core library. + const DATA_ATTRIBUTE = "data-generator"; + const DATA_ATTRIBUTE_VALUE = "tapestry/core/form"; + const TAPESTRY_CORE_FORM_SELECTOR = "form[" + DATA_ATTRIBUTE + "='" + DATA_ATTRIBUTE_VALUE + "']"; - clearSubmittingHidden = (form) -> - hidden = form.findFirst "[name='t:submit']" + const clearSubmittingHidden = function(form) { + const hidden = form.findFirst("[name='t:submit']"); - # Clear if found - hidden and hidden.value null + // Clear if found + hidden && hidden.value(null); - form.meta SKIP_VALIDATION, null + form.meta(SKIP_VALIDATION, null); - return + }; - setSubmittingHidden = (form, submitter) -> + const setSubmittingHidden = function(form, submitter) { - mode = submitter.attr "data-submit-mode" - isCancel = mode is "cancel" - if mode and mode isnt "normal" - form.meta SKIP_VALIDATION, true + const mode = submitter.attr("data-submit-mode"); + const isCancel = mode === "cancel"; + if (mode && (mode !== "normal")) { + form.meta(SKIP_VALIDATION, true); + } - hidden = form.findFirst "[name='t:submit']" + let hidden = form.findFirst("[name='t:submit']"); - unless hidden - firstHidden = form.findFirst "input[type=hidden]" - hidden = dom.create "input", type: "hidden", name: "t:submit" - firstHidden.insertBefore hidden + if (!hidden) { + const firstHidden = form.findFirst("input[type=hidden]"); + hidden = dom.create("input", {type: "hidden", name: "t:submit"}); + firstHidden.insertBefore(hidden); + } - # TODO: Research why we need id and name and get rid of one if possible. - name = if isCancel then "cancel" else submitter.element.name - # Not going to drag in all of json2 just for this one purpose, but even - # so, I'd like to get rid of this. Prototype includes Object.toJSON(), but jQuery - # is curiously absent an equivalent. - hidden.value "[\"#{submitter.element.id}\",\"#{name}\"]" + // TODO: Research why we need id and name and get rid of one if possible. + const name = isCancel ? "cancel" : submitter.element.name; + // Not going to drag in all of json2 just for this one purpose, but even + // so, I'd like to get rid of this. Prototype includes Object.toJSON(), but jQuery + // is curiously absent an equivalent. + hidden.value(`[\"${submitter.element.id}\",\"${name}\"]`); - return + }; - # Passed the element wrapper for a form element, returns a map of all the values - # for all non-disabled fields (including hidden fields, select, textarea). This is primarily - # used when assembling an Ajax request for a form submission. - gatherParameters = (form) -> - result = {} + // Passed the element wrapper for a form element, returns a map of all the values + // for all non-disabled fields (including hidden fields, select, textarea). This is primarily + // used when assembling an Ajax request for a form submission. + const gatherParameters = function(form) { + const result = {}; - fields = form.find "input, select, textarea" + const fields = form.find("input, select, textarea"); - _.each fields, (field) -> - return if field.attr "disabled" + _.each(fields, function(field) { + if (field.attr("disabled")) { return; } - type = field.element.type + const { + type + } = field.element; - # Ignore types file and submit; file doesn't make sense for Ajax, and submit - # is handled by keeping a hidden field active with the data Tapestry needs - # on the server. - return if type is "file" or type is "submit" + // Ignore types file and submit; file doesn't make sense for Ajax, and submit + // is handled by keeping a hidden field active with the data Tapestry needs + // on the server. + if ((type === "file") || (type === "submit")) { return; } - return if (type is "checkbox" or type is "radio") and field.checked() is false + if (((type === "checkbox") || (type === "radio")) && (field.checked() === false)) { return; } - value = field.value() + const value = field.value(); - return if value is null + if (value === null) { return; } - name = field.element.name + const { + name + } = field.element; - # Many modern UIs create name-less elements on the fly (e.g., Backbone); these may be mixed - # in with normal elements managed by Tapestry but should be ignored (not sent to the server in a POST - # or Ajax update). - return if name is "" + // Many modern UIs create name-less elements on the fly (e.g., Backbone); these may be mixed + // in with normal elements managed by Tapestry but should be ignored (not sent to the server in a POST + // or Ajax update). + if (name === "") { return; } - existing = result[name] + const existing = result[name]; - if _.isArray existing - existing.push value - return + if (_.isArray(existing)) { + existing.push(value); + return; + } - if existing - result[name] = [existing, value] - return + if (existing) { + result[name] = [existing, value]; + return; + } - result[name] = value + return result[name] = value; + }); - return result + return result; + }; - defaultValidateAndSubmit = -> + const defaultValidateAndSubmit = function() { - where = -> "processing form submission" + let where = () => "processing form submission"; - try + try { - if ((@attr "data-validate") is "submit") and - (not @meta SKIP_VALIDATION) + if (((this.attr("data-validate")) === "submit") && + (!this.meta(SKIP_VALIDATION))) { - @meta SKIP_VALIDATION, null + let memo; + this.meta(SKIP_VALIDATION, null); - hasError = false - focusField = null + let hasError = false; + let focusField = null; - for field in @find "[data-validation]" - memo = {} - where = -> "triggering #{events.field.inputValidation} event on #{field.toString()}" - field.trigger events.field.inputValidation, memo + for (var field of Array.from(this.find("[data-validation]"))) { + memo = {}; + where = () => `triggering ${events.field.inputValidation} event on ${field.toString()}`; + field.trigger(events.field.inputValidation, memo); - if memo.error - hasError = true - focusField = field unless focusField + if (memo.error) { + hasError = true; + if (!focusField) { focusField = field; } + } + } - # Only do form validation if all individual field validation - # was successful. - unless hasError - memo = {} - where = -> "trigging cross-form validation event" - @trigger events.form.validate, memo + // Only do form validation if all individual field validation + // was successful. + if (!hasError) { + memo = {}; + where = () => "trigging cross-form validation event"; + this.trigger(events.form.validate, memo); - hasError = memo.error + hasError = memo.error; + } - if hasError - clearSubmittingHidden this + if (hasError) { + clearSubmittingHidden(this); - # If a specific field has been identified as the source of the validation error, then - # focus on it. - focusField.focus() if focusField + // If a specific field has been identified as the source of the validation error, then + // focus on it. + if (focusField) { focusField.focus(); } - # Trigger an event to inform that form validation results in error - where = -> "triggering validation in error event" - @trigger events.form.validateInError + // Trigger an event to inform that form validation results in error + where = () => "triggering validation in error event"; + this.trigger(events.form.validateInError); - # Cancel the original submit event when there's an error - return false + // Cancel the original submit event when there's an error + return false; + } + } - # Allow certain types of elements to do last-moment set up. Basically, this is for - # FormFragment, or similar, to make their hidden field enabled or disabled to match - # their UI's visible/hidden status. This is assumed to work or throw an exception; there - # is no memo. - where = -> "triggering #{events.form.prepareForSubmit} event (after validation)" + // Allow certain types of elements to do last-moment set up. Basically, this is for + // FormFragment, or similar, to make their hidden field enabled or disabled to match + // their UI's visible/hidden status. This is assumed to work or throw an exception; there + // is no memo. + where = () => `triggering ${events.form.prepareForSubmit} event (after validation)`; - @trigger events.form.prepareForSubmit + this.trigger(events.form.prepareForSubmit); - catch error - console.error "Form validiation/submit error `#{error.toString()}', in form #{this.toString()}, #{where()}" - console.error error - return false + } catch (error) { + console.error(`Form validiation/submit error \`${error.toString()}', in form ${this.toString()}, ${where()}`); + console.error(error); + return false; + } - # Otherwise, the event is good, there are no validation problems, let the normal processing commence. - # Possibly, the document event handler provided by the t5/core/zone module will intercept form submission if this - # is an Ajax submission. - return + // Otherwise, the event is good, there are no validation problems, let the normal processing commence. + // Possibly, the document event handler provided by the t5/core/zone module will intercept form submission if this + // is an Ajax submission. + }; - dom.onDocument "submit", TAPESTRY_CORE_FORM_SELECTOR, defaultValidateAndSubmit + dom.onDocument("submit", TAPESTRY_CORE_FORM_SELECTOR, defaultValidateAndSubmit); - # On any click on a submit or image, update the containing form to indicate that the element - # was responsible for the eventual submit; this is very important to Ajax updates, otherwise the - # information about which control triggered the submit gets lost. - dom.onDocument "click", TAPESTRY_CORE_FORM_SELECTOR + " input[type=submit], " + TAPESTRY_CORE_FORM_SELECTOR + " input[type=image]", -> - setSubmittingHidden (dom @element.form), this - return + // On any click on a submit or image, update the containing form to indicate that the element + // was responsible for the eventual submit; this is very important to Ajax updates, otherwise the + // information about which control triggered the submit gets lost. + dom.onDocument("click", TAPESTRY_CORE_FORM_SELECTOR + " input[type=submit], " + TAPESTRY_CORE_FORM_SELECTOR + " input[type=image]", function() { + setSubmittingHidden((dom(this.element.form)), this); + }); - # Support for link submits. `data-submit-mode` will be non-null, possibly "cancel". - # Update the hidden field, but also cancel the default behavior for the click. - dom.onDocument "click", "a[data-submit-mode]", -> - form = @findParent "form" + // Support for link submits. `data-submit-mode` will be non-null, possibly "cancel". + // Update the hidden field, but also cancel the default behavior for the click. + dom.onDocument("click", "a[data-submit-mode]", function() { + const form = this.findParent("form"); - unless form - console.error "Submitting link element not contained inside a form element." - return false + if (!form) { + console.error("Submitting link element not contained inside a form element."); + return false; + } - setSubmittingHidden form, @closest "a[data-submit-mode]" + setSubmittingHidden(form, this.closest("a[data-submit-mode]")); - # Now the ugly part; if we just invoke submit() on the form, it does not trigger - # the form's "submit" event, which we need. + // Now the ugly part; if we just invoke submit() on the form, it does not trigger + // the form's "submit" event, which we need. - form.trigger "submit" + form.trigger("submit"); - # And cancel the default behavior for the original click event - return false + // And cancel the default behavior for the original click event + return false; + }); - exports = - gatherParameters: gatherParameters + return exports = { + gatherParameters, - setSubmittingElement: setSubmittingHidden - - # Sets a flag on the form to indicate that client-side validation should be bypassed. - # This is typically associated with submit buttons that "cancel" the form. - skipValidation: (form) -> - form.meta SKIP_VALIDATION, true + setSubmittingElement: setSubmittingHidden, + + // Sets a flag on the form to indicate that client-side validation should be bypassed. + // This is typically associated with submit buttons that "cancel" the form. + skipValidation(form) { + return form.meta(SKIP_VALIDATION, true); + } + }; +}); diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/graphviz.js b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/graphviz.js index e1fbe3ee2f..80391a2412 100644 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/graphviz.js +++ b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/graphviz.js @@ -1,29 +1,36 @@ -# Copyright 2023 The Apache Software Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http:#www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md + */ +// Copyright 2023 The Apache Software Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http:#www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -# ## t5/core/graphviz -# -# Support to the core/Graphviz Tapestry component. -define ["https://cdn.jsdelivr.net/npm/@hpcc-js/wasm/dist/graphviz.umd.js"], - (hpccWasm) -> - render = (value, id, showDownloadLink) -> - hpccWasm.Graphviz.load().then (graphviz) -> - svg = graphviz.dot value - div = document.getElementById id - layout = graphviz.layout(value, "svg", "dot") - div.innerHTML = layout - if showDownloadLink - link = document.getElementById (id + "-download") - link.setAttribute "href", "data:image/svg+xml;charset=utf-8," + encodeURIComponent(layout) - return render +// ## t5/core/graphviz +// +// Support to the core/Graphviz Tapestry component. +define(["https://cdn.jsdelivr.net/npm/@hpcc-js/wasm/dist/graphviz.umd.js"], + function(hpccWasm) { + const render = (value, id, showDownloadLink) => hpccWasm.Graphviz.load().then(function(graphviz) { + const svg = graphviz.dot(value); + const div = document.getElementById(id); + const layout = graphviz.layout(value, "svg", "dot"); + div.innerHTML = layout; + if (showDownloadLink) { + const link = document.getElementById((id + "-download")); + return link.setAttribute("href", "data:image/svg+xml;charset=utf-8," + encodeURIComponent(layout)); + } + }); + return render; +}); diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/init.js b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/init.js index 67aa07bc38..372e5a12d6 100644 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/init.js +++ b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/init.js @@ -1,30 +1,35 @@ -# Copyright 2012 The Apache Software Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http:#www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md + */ +// Copyright 2012 The Apache Software Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http:#www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -# ## t5/core/init -# -# Compatibility module, invokes functions on the T5.initializers namespace. -# -# Introduced in 5.4, to be removed at some point in the future, when T5.initializers is itself no more. -define ["t5/core/console"], +// ## t5/core/init +// +// Compatibility module, invokes functions on the T5.initializers namespace. +// +// Introduced in 5.4, to be removed at some point in the future, when T5.initializers is itself no more. +define(["t5/core/console"], - (console) -> - - # Exports a single function that finds an initializer in `T5.initializers` and invokes it. - (initName, args...) -> - fn = T5.initializers[initName] - if not fn - console.error "Initialization function '#{initName}' not found in T5.initializers namespace." - else - fn.apply null, args + console => // Exports a single function that finds an initializer in `T5.initializers` and invokes it. + (function(initName, ...args) { + const fn = T5.initializers[initName]; + if (!fn) { + return console.error(`Initialization function '${initName}' not found in T5.initializers namespace.`); + } else { + return fn.apply(null, args); + } + })); diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/localdate.js b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/localdate.js index 3ec7ca28a5..9eaa732361 100644 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/localdate.js +++ b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/localdate.js @@ -1,37 +1,43 @@ -# Copyright 2013 The Apache Software Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http:#www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# ## t5/core/localdate -# -# Used with the LocalDate component to present a Date in a particular format, in the -# browser's time zone. - -define ["t5/core/dom", "t5/core/moment"], -(dom, moment) -> - - ATTR = "data-localdate-format" - - dom.scanner "[#{ATTR}]", (el) -> - format = el.attr ATTR - - isoString = el.text() - - m = moment isoString - - el.update m.format format - - # A good scanner callback always removes itself from future scans. - el.attr ATTR, null - - return +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md + */ +// Copyright 2013 The Apache Software Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http:#www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ## t5/core/localdate +// +// Used with the LocalDate component to present a Date in a particular format, in the +// browser's time zone. + +define(["t5/core/dom", "t5/core/moment"], +function(dom, moment) { + + const ATTR = "data-localdate-format"; + + return dom.scanner(`[${ATTR}]`, function(el) { + const format = el.attr(ATTR); + + const isoString = el.text(); + + const m = moment(isoString); + + el.update(m.format(format)); + + // A good scanner callback always removes itself from future scans. + el.attr(ATTR, null); + + }); +}); diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/messages.js b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/messages.js index 957cccea48..5e045c4b3c 100644 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/messages.js +++ b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/messages.js @@ -1,55 +1,58 @@ -# Copyright 2012-2013 The Apache Software Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http:#www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# ## t5/core/messages -# -# For all of these modules, we've turned off CoffeeScript's normal outer function -# wrapper, as each module is just a call to `define()` with a function that fulfills -# the same purpose. This one is different, as it is necessary to compute one of the dependencies. -# On the server `t5/core/messages/` is actually generated dynamically, as is a simple -# mapping of message keys to message values, from the global application message catalog. -# -# This module provides access to localized messages from the Tapestry applications' server-side -# application message catalog (which is, itself, built from multiple resources, some provided by -# the framework, others provided by the application, or third-party libraries). -# -# Messages in the catalog that contain Java-style format specifiers are not included, as there -# is no facility for formatting those on the client. This is actually done as a simple test for the -# presence of the `%` character. In addition, any message key that begins with "private-" is -# assumed to contain sensitive data (such as database URLs or passwords) and will not be -# exposed to the client. - -# In the unexpected case that the data-locale attribute is missing, assume English -locale = (document.documentElement.getAttribute "data-locale") or "en" - -define ["t5/core/messages/#{locale}", "underscore", "t5/core/console"], - (messages, _, console) -> - - # Returns the application message catalog message for the given key. Returns - # a placeholder if the key is not found. - get = (key) -> - value = messages[key] - - if value - return value - else - console.error "No value for message catalog key '#{key}' exists." - return "[[Missing Key: '#{key}']]" - - # Returns all keys that are defined by the underlying catalog, in no specific order. - get.keys = -> _.keys messages - - - # Export get as the main function. - return get +// Copyright 2012-2013 The Apache Software Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http:#www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ## t5/core/messages +// +// For all of these modules, we've turned off CoffeeScript's normal outer function +// wrapper, as each module is just a call to `define()` with a function that fulfills +// the same purpose. This one is different, as it is necessary to compute one of the dependencies. +// On the server `t5/core/messages/` is actually generated dynamically, as is a simple +// mapping of message keys to message values, from the global application message catalog. +// +// This module provides access to localized messages from the Tapestry applications' server-side +// application message catalog (which is, itself, built from multiple resources, some provided by +// the framework, others provided by the application, or third-party libraries). +// +// Messages in the catalog that contain Java-style format specifiers are not included, as there +// is no facility for formatting those on the client. This is actually done as a simple test for the +// presence of the `%` character. In addition, any message key that begins with "private-" is +// assumed to contain sensitive data (such as database URLs or passwords) and will not be +// exposed to the client. + +// In the unexpected case that the data-locale attribute is missing, assume English +const locale = (document.documentElement.getAttribute("data-locale")) || "en"; + +define([`t5/core/messages/${locale}`, "underscore", "t5/core/console"], + function(messages, _, console) { + + // Returns the application message catalog message for the given key. Returns + // a placeholder if the key is not found. + const get = function(key) { + const value = messages[key]; + + if (value) { + return value; + } else { + console.error(`No value for message catalog key '${key}' exists.`); + return `[[Missing Key: '${key}']]`; + } + }; + + // Returns all keys that are defined by the underlying catalog, in no specific order. + get.keys = () => _.keys(messages); + + + // Export get as the main function. + return get; +}); diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/moment.js b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/moment.js index 4b11254106..8bb24633bd 100644 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/moment.js +++ b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/moment.js @@ -1,25 +1,26 @@ -# Copyright 2013 The Apache Software Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http:#www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +// Copyright 2013 The Apache Software Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http:#www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -# ## t5/core/moment -# -# A wrapper around Moment.js; this simply initializes Moment to -# use the correct locale (as per the data-locale attribute of the document element). -define ["moment"], -(moment) -> - locale = (document.documentElement.getAttribute "data-locale") or "en" +// ## t5/core/moment +// +// A wrapper around Moment.js; this simply initializes Moment to +// use the correct locale (as per the data-locale attribute of the document element). +define(["moment"], +function(moment) { + const locale = (document.documentElement.getAttribute("data-locale")) || "en"; - moment.locale(locale) + moment.locale(locale); - return moment \ No newline at end of file + return moment; +}); \ No newline at end of file diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/pageinit.js b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/pageinit.js index 34f3364494..de47602b88 100644 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/pageinit.js +++ b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/pageinit.js @@ -1,241 +1,287 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http:#www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# ## t5/core/pageinit -# -# Module that defines functions used for page initialization. -# The initialize function is passed an array of initializers; each initializer is itself -# an array. The first value in the initializer defines the name of the module to invoke. -# The module name may also indicate the function exported by the module, as a suffix following a colon: -# e.g., "my/module:myfunc". -# Any additional values in the initializer are passed to the function. The context of the function (this) is null. -define ["underscore", "t5/core/console", "t5/core/dom", "t5/core/events"], - (_, console, dom, events) -> - pathPrefix = null - - # Borrowed from Prototype: - isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]' - isIE = !!window.attachEvent && !isOpera - - rebuildURL = (path) -> - return path if path.match /^https?:/ - - # See Tapestry.rebuildURL() for an error about the path not starting with a leading '/' - # We'll assume that doesn't happen. - - if !pathPrefix - l = window.location - pathPrefix = "#{l.protocol}//#{l.host}" - - return pathPrefix + path - - rebuildURLOnIE = - if isIE then rebuildURL else _.identity - - addStylesheets = (newStylesheets) -> - return unless newStylesheets - - # Figure out which stylesheets are loaded; adjust for IE, and especially, IE 9 - # which can report a stylesheet's URL as null (not empty string). - loaded = _.chain(document.styleSheets) +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS201: Simplify complex destructure assignments + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md + */ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http:#www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ## t5/core/pageinit +// +// Module that defines functions used for page initialization. +// The initialize function is passed an array of initializers; each initializer is itself +// an array. The first value in the initializer defines the name of the module to invoke. +// The module name may also indicate the function exported by the module, as a suffix following a colon: +// e.g., "my/module:myfunc". +// Any additional values in the initializer are passed to the function. The context of the function (this) is null. +define(["underscore", "t5/core/console", "t5/core/dom", "t5/core/events"], + function(_, console, dom, events) { + let exports; + let pathPrefix = null; + + // Borrowed from Prototype: + const isOpera = Object.prototype.toString.call(window.opera) === '[object Opera]'; + const isIE = !!window.attachEvent && !isOpera; + + const rebuildURL = function(path) { + if (path.match(/^https?:/)) { return path; } + + // See Tapestry.rebuildURL() for an error about the path not starting with a leading '/' + // We'll assume that doesn't happen. + + if (!pathPrefix) { + const l = window.location; + pathPrefix = `${l.protocol}//${l.host}`; + } + + return pathPrefix + path; + }; + + const rebuildURLOnIE = + isIE ? rebuildURL : _.identity; + + const addStylesheets = function(newStylesheets) { + if (!newStylesheets) { return; } + + // Figure out which stylesheets are loaded; adjust for IE, and especially, IE 9 + // which can report a stylesheet's URL as null (not empty string). + const loaded = _.chain(document.styleSheets) .pluck("href") .without("") .without(null) - .map(rebuildURLOnIE) + .map(rebuildURLOnIE); - insertionPoint = _.find(document.styleSheets, (ss) -> - parent = ss.ownerNode || ss.owningElement - parent.rel is "stylesheet ajax-insertion-point") + const insertionPoint = _.find(document.styleSheets, function(ss) { + const parent = ss.ownerNode || ss.owningElement; + return parent.rel === "stylesheet ajax-insertion-point"; + }); - # Most browsers support document.head, but older IE doesn't: - head = document.head or document.getElementsByTagName("head")[0] + // Most browsers support document.head, but older IE doesn't: + const head = document.head || document.getElementsByTagName("head")[0]; _.chain(newStylesheets) - .map((ss) -> { href: rebuildURL(ss.href), media: ss.media }) - .reject((ss) -> loaded.contains(ss.href).value()) - .each((ss) -> - element = document.createElement "link" - element.setAttribute "type", "text/css" - element.setAttribute "rel", "stylesheet" - element.setAttribute "href", ss.href - if ss.media - element.setAttribute "media", ss.media - - if insertionPoint - head.insertBefore element, insertionPoint.ownerNode - else - head.appendChild element - - console.debug "Added stylesheet #{ss.href}" - ) - - return - - invokeInitializer = (tracker, qualifiedName, initArguments) -> - [moduleName, functionName] = qualifiedName.split ':' - - require [moduleName], (moduleLib) -> + .map(ss => ({ + href: rebuildURL(ss.href), + media: ss.media + })) + .reject(ss => loaded.contains(ss.href).value()) + .each(function(ss) { + const element = document.createElement("link"); + element.setAttribute("type", "text/css"); + element.setAttribute("rel", "stylesheet"); + element.setAttribute("href", ss.href); + if (ss.media) { + element.setAttribute("media", ss.media); + } + + if (insertionPoint) { + head.insertBefore(element, insertionPoint.ownerNode); + } else { + head.appendChild(element); + } + + return console.debug(`Added stylesheet ${ss.href}`); + }); + + }; + + const invokeInitializer = function(tracker, qualifiedName, initArguments) { + const [moduleName, functionName] = Array.from(qualifiedName.split(':')); + + return require([moduleName], function(moduleLib) { - try - # Some modules export nothing but do some full-page initialization, such as adding - # event handlers to the body. - if not functionName and - initArguments.length is 0 and - not _.isFunction moduleLib - console.debug "Loaded module #{moduleName}" - return - - unless moduleLib - throw new Error "require('#{moduleName}') returned #{moduleLib} when not expected" - - fn = if functionName? then moduleLib[functionName] else moduleLib - - unless fn? - if functionName - console.error "Could not locate function `#{qualifiedName}' in #{JSON.stringify(moduleLib)}" - console.error moduleLib - throw new Error "Could not locate function `#{qualifiedName}'." - - if console.debugEnabled - argsString = (JSON.stringify arg for arg in initArguments).join(", ") - console.debug "Invoking #{qualifiedName}(#{argsString})" - - fn.apply null, initArguments - finally - tracker() - - # Loads all specified libraries in order (this includes the core stack, other stacks, and - # any free-standing libraries). It then executes the initializations. Once all initializations have - # completed (which is usually an asynchronous operation, as initializations may require the loading - # of further modules), then the `data-page-initialized` attribute of the `` element is set to - # 'true'. - # - # This is the main export of the module; other functions are attached as properties. - loadLibrariesAndInitialize = (libraries, inits) -> - console.debug "Loading #{libraries?.length or 0} libraries" - exports.loadLibraries libraries, - -> exports.initialize inits, - -> - # At this point, all libraries have been loaded, and all inits should have executed. Unless some of - # the inits triggered Ajax updates (such as a core/ProgressiveDisplay component), then the page should - # be ready to go. We set a flag, mostly used by test suites, to ensure that all is ready. - # Later Ajax requests will cause the data-ajax-active attribute to be incremented (from 0) - # and decremented (when the requests complete). - - dom.body.attr "data-page-initialized", "true" - - for mask in dom.body.find ".pageloading-mask" - mask.remove() - - exports = _.extend loadLibrariesAndInitialize, - # Passed a list of initializers, executes each initializer in order. Due to asynchronous loading - # of modules, the exact order in which initializer functions are invoked is not predictable. - initialize: (inits = [], callback) -> - console.debug "Executing #{inits.length} inits" - callbackCountdown = inits.length + 1 - - # tracker gets invoked once after each require/callback, plus once extra - # (to handle the case where there are no inits). When the count hits zero, - # it invokes the callback (if there is one). - tracker = -> - callbackCountdown-- - - if callbackCountdown is 0 - console.debug "All inits executed" - callback() if callback - - # First value in each init is the qualified module name; anything after - # that are arguments to be passed to the identified function. A string - # is the name of a module to load, or function to invoke, that - # takes no parameters. - for init in inits - if _.isString init - invokeInitializer tracker, init, [] - else - [qualifiedName, initArguments...] = init - invokeInitializer tracker, qualifiedName, initArguments - - tracker() - - # Pre-loads a number of libraries in order. When the last library is loaded, - # invokes the callback (with no parameters). - loadLibraries: (libraries, callback) -> - reducer = (callback, library) -> -> - console.debug "Loading library #{library}" - require [library], callback - - finalCallback = _.reduceRight libraries, reducer, callback - - finalCallback.call null - - evalJavaScript: (js) -> - console.debug "Evaluating: #{js}" - eval js - - # Triggers the focus event on the field, if the field exist. Focus occurs delayed 1/8th of a - # second, which helps ensure that other initializions on the page are in place. - # - # * fieldId - element id of field to focus on - focus: (fieldId) -> - field = dom fieldId - - if field - _.delay (-> field.focus()), 125 - - # Passed the response from an Ajax request, when the request is successful. - # This is used for any request that attaches partial-page-render data to the main JSON object - # response. If no such data is attached, the callback is simply invoked immediately. - # Otherwise, Tapestry processes the partial-page-render data. This may involve loading some number - # of JavaScript libraries and CSS style sheets, and a number of direct updates to the DOM. After DOM updates, - # the callback is invoked, passed the response (with any Tapestry-specific data removed). - # After the callback is invoked, page initializations occur. This method returns null. - - # * response - the Ajax response object - # * callback - invoked after scripts are loaded, but before page initializations occur (may be null) - handlePartialPageRenderResponse: (response, callback) -> - - # Capture the partial page response portion of the overall response, and - # then remove it so it doesn't interfere elsewhere. - responseJSON = response.json or {} - partial = responseJSON._tapestry - delete responseJSON._tapestry - - # Extreme case: the data has a redirectURL which forces an immediate redirect to the URL. - # No other initialization or callback invocation occurs. - if partial?.redirectURL - if window.location.href is partial.redirectURL - window.location.reload true - else - window.location.href = partial.redirectURL - return - - addStylesheets partial?.stylesheets - - # Make sure all libraries are loaded - exports.loadLibraries partial?.libraries, -> - - # After libraries are loaded, update each zone: - _(partial?.content).each ([id, content]) -> - console.debug "Updating content for zone #{id}" - - zone = dom.wrap id - - if zone - zone.trigger events.zone.update, { content } - - # Invoke the callback, if present. The callback may do its own content updates. - callback and callback(response) - - # Lastly, perform initializations from the partial page render response. - exports.initialize partial?.inits - - return + try { + // Some modules export nothing but do some full-page initialization, such as adding + // event handlers to the body. + if (!functionName && + (initArguments.length === 0) && + !_.isFunction(moduleLib)) { + console.debug(`Loaded module ${moduleName}`); + return; + } + + if (!moduleLib) { + throw new Error(`require('${moduleName}') returned ${moduleLib} when not expected`); + } + + const fn = (functionName != null) ? moduleLib[functionName] : moduleLib; + + if (fn == null) { + if (functionName) { + console.error(`Could not locate function \`${qualifiedName}' in ${JSON.stringify(moduleLib)}`); + console.error(moduleLib); + } + throw new Error(`Could not locate function \`${qualifiedName}'.`); + } + + if (console.debugEnabled) { + const argsString = (Array.from(initArguments).map((arg) => JSON.stringify(arg))).join(", "); + console.debug(`Invoking ${qualifiedName}(${argsString})`); + } + + return fn.apply(null, initArguments); + } finally { + tracker(); + } + }); + }; + + // Loads all specified libraries in order (this includes the core stack, other stacks, and + // any free-standing libraries). It then executes the initializations. Once all initializations have + // completed (which is usually an asynchronous operation, as initializations may require the loading + // of further modules), then the `data-page-initialized` attribute of the `` element is set to + // 'true'. + // + // This is the main export of the module; other functions are attached as properties. + const loadLibrariesAndInitialize = function(libraries, inits) { + console.debug(`Loading ${(libraries != null ? libraries.length : undefined) || 0} libraries`); + return exports.loadLibraries(libraries, + () => exports.initialize(inits, + function() { + // At this point, all libraries have been loaded, and all inits should have executed. Unless some of + // the inits triggered Ajax updates (such as a core/ProgressiveDisplay component), then the page should + // be ready to go. We set a flag, mostly used by test suites, to ensure that all is ready. + // Later Ajax requests will cause the data-ajax-active attribute to be incremented (from 0) + // and decremented (when the requests complete). + + dom.body.attr("data-page-initialized", "true"); + + return Array.from(dom.body.find(".pageloading-mask")).map((mask) => + mask.remove()); + })); + }; + + return exports = _.extend(loadLibrariesAndInitialize, { + // Passed a list of initializers, executes each initializer in order. Due to asynchronous loading + // of modules, the exact order in which initializer functions are invoked is not predictable. + initialize(inits, callback) { + if (inits == null) { inits = []; } + console.debug(`Executing ${inits.length} inits`); + let callbackCountdown = inits.length + 1; + + // tracker gets invoked once after each require/callback, plus once extra + // (to handle the case where there are no inits). When the count hits zero, + // it invokes the callback (if there is one). + const tracker = function() { + callbackCountdown--; + + if (callbackCountdown === 0) { + console.debug("All inits executed"); + if (callback) { return callback(); } + } + }; + + // First value in each init is the qualified module name; anything after + // that are arguments to be passed to the identified function. A string + // is the name of a module to load, or function to invoke, that + // takes no parameters. + for (var init of Array.from(inits)) { + if (_.isString(init)) { + invokeInitializer(tracker, init, []); + } else { + var [qualifiedName, ...initArguments] = Array.from(init); + invokeInitializer(tracker, qualifiedName, initArguments); + } + } + + return tracker(); + }, + + // Pre-loads a number of libraries in order. When the last library is loaded, + // invokes the callback (with no parameters). + loadLibraries(libraries, callback) { + const reducer = (callback, library) => (function() { + console.debug(`Loading library ${library}`); + return require([library], callback); + }); + + const finalCallback = _.reduceRight(libraries, reducer, callback); + + return finalCallback.call(null); + }, + + evalJavaScript(js) { + console.debug(`Evaluating: ${js}`); + return eval(js); + }, + + // Triggers the focus event on the field, if the field exist. Focus occurs delayed 1/8th of a + // second, which helps ensure that other initializions on the page are in place. + // + // * fieldId - element id of field to focus on + focus(fieldId) { + const field = dom(fieldId); + + if (field) { + return _.delay((() => field.focus()), 125); + } + }, + + // Passed the response from an Ajax request, when the request is successful. + // This is used for any request that attaches partial-page-render data to the main JSON object + // response. If no such data is attached, the callback is simply invoked immediately. + // Otherwise, Tapestry processes the partial-page-render data. This may involve loading some number + // of JavaScript libraries and CSS style sheets, and a number of direct updates to the DOM. After DOM updates, + // the callback is invoked, passed the response (with any Tapestry-specific data removed). + // After the callback is invoked, page initializations occur. This method returns null. + + // * response - the Ajax response object + // * callback - invoked after scripts are loaded, but before page initializations occur (may be null) + handlePartialPageRenderResponse(response, callback) { + + // Capture the partial page response portion of the overall response, and + // then remove it so it doesn't interfere elsewhere. + const responseJSON = response.json || {}; + const partial = responseJSON._tapestry; + delete responseJSON._tapestry; + + // Extreme case: the data has a redirectURL which forces an immediate redirect to the URL. + // No other initialization or callback invocation occurs. + if (partial != null ? partial.redirectURL : undefined) { + if (window.location.href === partial.redirectURL) { + window.location.reload(true); + } else { + window.location.href = partial.redirectURL; + } + return; + } + + addStylesheets(partial != null ? partial.stylesheets : undefined); + + // Make sure all libraries are loaded + exports.loadLibraries(partial != null ? partial.libraries : undefined, function() { + + // After libraries are loaded, update each zone: + _(partial != null ? partial.content : undefined).each(function(...args) { + const [id, content] = Array.from(args[0]); + console.debug(`Updating content for zone ${id}`); + + const zone = dom.wrap(id); + + if (zone) { + return zone.trigger(events.zone.update, { content }); + }}); + + // Invoke the callback, if present. The callback may do its own content updates. + callback && callback(response); + + // Lastly, perform initializations from the partial page render response. + return exports.initialize(partial != null ? partial.inits : undefined); + }); + + } + } + ); +}); diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/palette.js b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/palette.js index 52fa984044..68d1b4da84 100644 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/palette.js +++ b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/palette.js @@ -1,300 +1,380 @@ -# Copyright 2012-2024 The Apache Software Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http:#www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# ## t5/core/palette -# -# Support for the `core/Palette` component. -define ["t5/core/dom", "underscore", "t5/core/events"], - (dom, _, events) -> - - isSelected = (option) -> option.selected - - class PaletteController - - constructor: (id) -> - @selected = (dom id) - @container = @selected.findParent ".palette" - @available = @container.findFirst ".palette-available select" - @hidden = @container.findFirst "input[type=hidden]" +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS205: Consider reworking code to avoid use of IIFEs + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md + */ +// Copyright 2012-2024 The Apache Software Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http:#www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ## t5/core/palette +// +// Support for the `core/Palette` component. +define(["t5/core/dom", "underscore", "t5/core/events"], + function(dom, _, events) { + + const isSelected = option => option.selected; + + class PaletteController { + + constructor(id) { + this.selected = (dom(id)); + this.container = this.selected.findParent(".palette"); + this.available = this.container.findFirst(".palette-available select"); + this.hidden = this.container.findFirst("input[type=hidden]"); + + this.select = this.container.findFirst("[data-action=select]"); + this.deselect = this.container.findFirst("[data-action=deselect]"); + + this.moveUp = this.container.findFirst("[data-action=move-up]"); + this.moveDown = this.container.findFirst("[data-action=move-down]"); + + // Track where reorder is allowed based on whether the buttons actually exist + this.reorder = this.moveUp !== null; + + this.valueToOrderIndex = {}; + + for (let i = 0; i < this.available.element.options.length; i++) { + var option = this.available.element.options[i]; + this.valueToOrderIndex[option.value] = i; + } + + // This occurs even when the palette is disabled, to present the + // values correctly. Otherwise it looks like nothing is selected. + this.initialTransfer(); + + if (!this.selected.element.disabled) { + this.updateButtons(); + this.bindEvents(); + } + } + + initialTransfer() { + // Get the values for options that should move over + let i, option; + const values = JSON.parse(this.hidden.value()); + const valueToPosition = {}; + + for (i = 0; i < values.length; i++) { + var v = values[i]; + valueToPosition[v] = i; + } + + const e = this.available.element; + + const movers = []; + + for (i = e.options.length - 1; i >= 0; i--) { + option = e.options[i]; + var { + value + } = option; + var pos = valueToPosition[value]; + if (pos !== undefined) { + movers[pos] = option; + e.remove(i); + } + } + + return (() => { + const result = []; + for (option of Array.from(movers)) { + result.push(this.selected.element.add(option)); + } + return result; + })(); + } + + // Invoked after any change to the selections list to update the hidden field as well as the + // buttons' state. + updateAfterChange() { + this.updateHidden(); + return this.updateButtons(); + } + + updateHidden() { + const values = (Array.from(this.selected.element.options).map((option) => option.value)); + return this.hidden.value(JSON.stringify(values)); + } + + bindEvents() { + this.container.on("change", "select", () => { + this.updateButtons(); + return false; + }); - @select = @container.findFirst "[data-action=select]" - @deselect = @container.findFirst "[data-action=deselect]" + this.select.on("click", () => { + this.doSelect(); + return false; + }); - @moveUp = @container.findFirst "[data-action=move-up]" - @moveDown = @container.findFirst "[data-action=move-down]" + this.available.on("dblclick", () => { + this.doSelect(); + return false; + }); - # Track where reorder is allowed based on whether the buttons actually exist - @reorder = @moveUp isnt null + this.deselect.on("click", () => { + this.doDeselect(); + return false; + }); - @valueToOrderIndex = {} + this.selected.on("dblclick", () => { + this.doDeselect(); + return false; + }); - for option,i in @available.element.options - @valueToOrderIndex[option.value] = i + if (this.reorder) { + this.moveUp.on("click", () => { + this.doMoveUp(); + return false; + }); - # This occurs even when the palette is disabled, to present the - # values correctly. Otherwise it looks like nothing is selected. - @initialTransfer() + return this.moveDown.on("click", () => { + this.doMoveDown(); + return false; + }); + } + } - unless @selected.element.disabled - @updateButtons() - @bindEvents() + // Invoked whenever the selections in either list changes or after an updates; figures out which buttons + // should be enabled and which disabled. + updateButtons() { + this.select.element.disabled = this.available.element.selectedIndex < 0; - initialTransfer: -> - # Get the values for options that should move over - values = JSON.parse @hidden.value() - valueToPosition = {} + const nothingSelected = this.selected.element.selectedIndex < 0; - for v, i in values - valueToPosition[v] = i + this.deselect.element.disabled = nothingSelected; - e = @available.element + if (this.reorder) { + this.moveUp.element.disabled = nothingSelected || this.allSelectionsAtTop(); + return this.moveDown.element.disabled = nothingSelected || this.allSelectionsAtBottom(); + } + } - movers = [] + doSelect() { return this.transferOptions(this.available, this.selected, this.reorder); } - for i in [(e.options.length - 1)..0] by -1 - option = e.options[i] - value = option.value - pos = valueToPosition[value] - unless pos is undefined - movers[pos] = option - e.remove i + doDeselect() { return this.transferOptions(this.selected, this.available, false); } - for option in movers - @selected.element.add option + doMoveUp() { + let options = _.toArray(this.selected.element.options); - # Invoked after any change to the selections list to update the hidden field as well as the - # buttons' state. - updateAfterChange: -> - @updateHidden() - @updateButtons() + const groups = _.partition(options, isSelected); - updateHidden: -> - values = (option.value for option in @selected.element.options) - @hidden.value JSON.stringify values + const movers = groups[0]; - bindEvents: -> - @container.on "change", "select", => - @updateButtons() - return false + // The element before the first selected element is the pivot; all the selected elements will + // move before the pivot. If there is no pivot, the elements are shifted to the front of the list. + const firstMoverIndex = _.first(movers).index; + const pivot = options[firstMoverIndex - 1]; - @select.on "click", => - @doSelect() - return false + options = groups[1]; - @available.on "dblclick", => - @doSelect() - return false + const splicePos = pivot ? _.indexOf(options, pivot) : 0; - @deselect.on "click", => - @doDeselect() - return false + movers.reverse(); - @selected.on "dblclick", => - @doDeselect() - return false + for (var o of Array.from(movers)) { + options.splice(splicePos, 0, o); + } - if @reorder - @moveUp.on "click", => - @doMoveUp() - return false + return this.reorderSelected(options); + } - @moveDown.on "click", => - @doMoveDown() - return false - # Invoked whenever the selections in either list changes or after an updates; figures out which buttons - # should be enabled and which disabled. - updateButtons: -> - @select.element.disabled = @available.element.selectedIndex < 0 + doMoveDown() { + let options = _.toArray(this.selected.element.options); - nothingSelected = @selected.element.selectedIndex < 0 + const groups = _.partition(options, isSelected); - @deselect.element.disabled = nothingSelected + const movers = groups[0]; - if @reorder - @moveUp.element.disabled = nothingSelected or @allSelectionsAtTop() - @moveDown.element.disabled = nothingSelected or @allSelectionsAtBottom() + // The element after the last selected element is the pivot; all the selected elements will + // move after the pivot. If there is no pivot, the elements are shifted to the end of the list. + const lastMoverIndex = movers.slice(-1)[0].index; + const pivot = options[lastMoverIndex + 1]; - doSelect: -> @transferOptions @available, @selected, @reorder + options = groups[1]; - doDeselect: -> @transferOptions @selected, @available, false + const splicePos = pivot ? _.indexOf(options, pivot) + 1 : options.length; - doMoveUp: -> - options = _.toArray @selected.element.options + movers.reverse(); - groups = _.partition options, isSelected + for (var o of Array.from(movers)) { + options.splice(splicePos, 0, o); + } - movers = groups[0] + return this.reorderSelected(options); + } - # The element before the first selected element is the pivot; all the selected elements will - # move before the pivot. If there is no pivot, the elements are shifted to the front of the list. - firstMoverIndex = _.first(movers).index - pivot = options[firstMoverIndex - 1] + // Reorders the selected options to the provided list of options; handles triggering the willUpdate and + // didUpdate events. + reorderSelected(options) { - options = groups[1] + return this.performUpdate(true, options, () => { - splicePos = if pivot then _.indexOf options, pivot else 0 + this.deleteOptions(this.selected); - movers.reverse() + return Array.from(options).map((o) => + this.selected.element.add(o, null)); + }); + } - for o in movers - options.splice splicePos, 0, o + // Performs the update, which includes the willChange and didChange events. + performUpdate(reorder, selectedOptions, updateCallback) { - @reorderSelected options + let canceled = false; + const doUpdate = () => { + updateCallback(); - doMoveDown: -> - options = _.toArray @selected.element.options + this.selected.trigger(events.palette.didChange, { selectedOptions, reorder }); - groups = _.partition options, isSelected + return this.updateAfterChange(); + }; - movers = groups[0] + const memo = { + selectedOptions, + reorder, + cancel() { return canceled = true; }, + defer() { + canceled = true; + return doUpdate; + } + }; - # The element after the last selected element is the pivot; all the selected elements will - # move after the pivot. If there is no pivot, the elements are shifted to the end of the list. - lastMoverIndex = movers[-1..-1][0].index - pivot = options[lastMoverIndex + 1] + this.selected.trigger(events.palette.willChange, memo); - options = groups[1] + if (!canceled) { return doUpdate(); } + } - splicePos = if pivot then _.indexOf(options, pivot) + 1 else options.length + // Deletes all options from a select (an ElementWrapper), prior to new options being populated in. + deleteOptions(select) { - movers.reverse() + const e = select.element; - for o in movers - options.splice splicePos, 0, o + return (() => { + const result = []; + for (let i = e.length - 1; i >= 0; i--) { + result.push(e.remove(i)); + } + return result; + })(); + } - @reorderSelected options + // Moves options between the available and selected lists, including event notifiations before and after. + transferOptions(from, to, atEnd) { - # Reorders the selected options to the provided list of options; handles triggering the willUpdate and - # didUpdate events. - reorderSelected: (options) -> + let o; + if (from.element.selectedIndex === -1) { + return; + } - @performUpdate true, options, => + // This could be done in a single pass, but: + const movers = _.filter(from.element.options, isSelected); + const fromOptions = _.reject(from.element.options, isSelected); - @deleteOptions @selected + const toOptions = _.toArray(to.element.options); - for o in options - @selected.element.add o, null + for (o of Array.from(movers)) { + this.insertOption(toOptions, o, atEnd); + } - # Performs the update, which includes the willChange and didChange events. - performUpdate: (reorder, selectedOptions, updateCallback) -> + const isSelectedSelect = to === this.selected; + const selectedOptions = isSelectedSelect ? toOptions : fromOptions; - canceled = false + return this.performUpdate(false, selectedOptions, () => { + let i; + for (i = from.element.length - 1; i >= 0; i--) { + if (from.element.options[i].selected) { + from.element.remove(i); + } + } - doUpdate = => - updateCallback() + // A bit ugly: update the to select by removing all, then adding back in. - @selected.trigger events.palette.didChange, { selectedOptions, reorder } + for (i = to.element.length - 1; i >= 0; i--) { + to.element.options[i].selected = false; + to.element.remove(i); + } - @updateAfterChange() - - memo = - selectedOptions: selectedOptions - reorder: reorder - cancel: -> canceled = true - defer: -> - canceled = true - return doUpdate - - @selected.trigger events.palette.willChange, memo - - doUpdate() unless canceled - - # Deletes all options from a select (an ElementWrapper), prior to new options being populated in. - deleteOptions: (select) -> - - e = select.element - - for i in [(e.length - 1)..0] by -1 - e.remove i - - # Moves options between the available and selected lists, including event notifiations before and after. - transferOptions: (from, to, atEnd) -> - - if from.element.selectedIndex is -1 - return - - # This could be done in a single pass, but: - movers = _.filter from.element.options, isSelected - fromOptions = _.reject from.element.options, isSelected - - toOptions = _.toArray to.element.options - - for o in movers - @insertOption toOptions, o, atEnd - - isSelectedSelect = to is @selected - selectedOptions = if isSelectedSelect then toOptions else fromOptions - - @performUpdate false, selectedOptions, => - for i in [(from.element.length - 1)..0] by -1 - if from.element.options[i].selected - from.element.remove i - - # A bit ugly: update the to select by removing all, then adding back in. - - for i in [(to.element.length - 1)..0] by -1 - to.element.options[i].selected = false - to.element.remove i - - for o in toOptions - groupIdx = o.getAttribute('data-optgroup-idx') - if isSelectedSelect or !groupIdx or groupIdx == '' - to.element.add o, null - else - group = to.element.children[parseInt(groupIdx)] - group.appendChild o - - - insertOption: (options, option, atEnd) -> - - unless atEnd - optionOrder = @valueToOrderIndex[option.value] - before = _.find options, (o) => @valueToOrderIndex[o.value] > optionOrder - - if before - i = _.indexOf options, before - options.splice i, 0, option - else - options.push option - - - indexOfLastSelection: (select) -> - e = select.element - if e.selectedIndex < 0 - return -1 - - for i in [(e.options.length - 1)..(e.selectedIndex)] by -1 - if e.options[i].selected - return i - - return -1 - - allSelectionsAtTop: -> - last = @indexOfLastSelection @selected - options = _.toArray @selected.element.options - - _(options[0..last]).all (o) -> o.selected - - allSelectionsAtBottom: -> - e = @selected.element - last = e.selectedIndex - options = _.toArray e.options - - _(options[last..]).all (o) -> o.selected - - - # Export just the initializer function - (id) -> new PaletteController(id) \ No newline at end of file + return (() => { + const result = []; + for (o of Array.from(toOptions)) { + var groupIdx = o.getAttribute('data-optgroup-idx'); + if (isSelectedSelect || !groupIdx || (groupIdx === '')) { + result.push(to.element.add(o, null)); + } else { + var group = to.element.children[parseInt(groupIdx)]; + result.push(group.appendChild(o)); + } + } + return result; + })(); + }); + } + + + insertOption(options, option, atEnd) { + + let before; + if (!atEnd) { + const optionOrder = this.valueToOrderIndex[option.value]; + before = _.find(options, o => this.valueToOrderIndex[o.value] > optionOrder); + } + + if (before) { + const i = _.indexOf(options, before); + return options.splice(i, 0, option); + } else { + return options.push(option); + } + } + + + indexOfLastSelection(select) { + const e = select.element; + if (e.selectedIndex < 0) { + return -1; + } + + for (let i = e.options.length - 1, end = e.selectedIndex; i >= end; i--) { + if (e.options[i].selected) { + return i; + } + } + + return -1; + } + + allSelectionsAtTop() { + const last = this.indexOfLastSelection(this.selected); + const options = _.toArray(this.selected.element.options); + + return _(options.slice(0, +last + 1 || undefined)).all(o => o.selected); + } + + allSelectionsAtBottom() { + const e = this.selected.element; + const last = e.selectedIndex; + const options = _.toArray(e.options); + + return _(options.slice(last)).all(o => o.selected); + } + } + + + // Export just the initializer function + return id => new PaletteController(id); +}); \ No newline at end of file diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/select.js b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/select.js index f006c70735..3d74fd9691 100644 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/select.js +++ b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/select.js @@ -1,32 +1,36 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http:#www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http:#www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -# ## t5/core/select -# -# Provides a document event handler that triggers an update a zone when the value -# of a select element within the zone changes. -define ["t5/core/events", "t5/core/dom", "t5/core/zone"], +// ## t5/core/select +// +// Provides a document event handler that triggers an update a zone when the value +// of a select element within the zone changes. +define(["t5/core/events", "t5/core/dom", "t5/core/zone"], - (events, dom, zone) -> + function(events, dom, zone) { - dom.onDocument "change", "select[data-update-zone]", -> + dom.onDocument("change", "select[data-update-zone]", function() { - containingZone = zone.findZone this + const containingZone = zone.findZone(this); - if containingZone - containingZone.trigger events.zone.refresh, - url: @attr "data-update-url" - parameters: - "t:selectvalue": @value() + if (containingZone) { + containingZone.trigger(events.zone.refresh, { + url: this.attr("data-update-url"), + parameters: { + "t:selectvalue": this.value() + } + } + ); + } - return - return + }); +}); diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/time-interval.js b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/time-interval.js index 7221e76eff..2bd17f5be1 100644 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/time-interval.js +++ b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/time-interval.js @@ -1,55 +1,62 @@ -# Copyright 2013 The Apache Software Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http:#www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# ## t5/core/time-interval -# -# Used with the Interval component to express the interval between two timestamps, -# or the dynamic difference between now and an end point in the past or future. -define ["t5/core/dom", "t5/core/moment"], -(dom, moment) -> - - ATTR = "data-timeinterval" - - DEFAULT_FORMAT = 'YYYY-MM-DDTHH:mm:ss.SSSZ' - - toMoment = (s) -> if s then (moment s, DEFAULT_FORMAT) else moment() - - updateElement = (el) -> - start = toMoment el.attr "data-timeinterval-start" - end = toMoment el.attr "data-timeinterval-end" - plain = el.attr "data-timeinterval-plain" - - el.update end.from start, plain - return - - updateDynamics = -> - for el in dom.body.find "[#{ATTR}=dynamic]" - updateElement el - return - - # Update any dynamic intervals (the ones without a specific start date) about once a second - setInterval updateDynamics, 1000 - - dom.scanner "[#{ATTR}=true]", (el) -> - - updateElement el - - if (el.attr "data-timeinterval-start") and (el.attr "data-timeinterval-end") - el.attr ATTR, null - else - el.attr ATTR, "dynamic" - - return - - return # no exports +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md + */ +// Copyright 2013 The Apache Software Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http:#www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ## t5/core/time-interval +// +// Used with the Interval component to express the interval between two timestamps, +// or the dynamic difference between now and an end point in the past or future. +define(["t5/core/dom", "t5/core/moment"], +function(dom, moment) { + + const ATTR = "data-timeinterval"; + + const DEFAULT_FORMAT = 'YYYY-MM-DDTHH:mm:ss.SSSZ'; + + const toMoment = function(s) { if (s) { return (moment(s, DEFAULT_FORMAT)); } else { return moment(); } }; + + const updateElement = function(el) { + const start = toMoment(el.attr("data-timeinterval-start")); + const end = toMoment(el.attr("data-timeinterval-end")); + const plain = el.attr("data-timeinterval-plain"); + + el.update(end.from(start, plain)); + }; + + const updateDynamics = function() { + for (var el of Array.from(dom.body.find(`[${ATTR}=dynamic]`))) { + updateElement(el); + } + }; + + // Update any dynamic intervals (the ones without a specific start date) about once a second + setInterval(updateDynamics, 1000); + + dom.scanner(`[${ATTR}=true]`, function(el) { + + updateElement(el); + + if ((el.attr("data-timeinterval-start")) && (el.attr("data-timeinterval-end"))) { + el.attr(ATTR, null); + } else { + el.attr(ATTR, "dynamic"); + } + + }); + +}); // no exports diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/tree.js b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/tree.js index 556dcf1bef..78f71dae2e 100644 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/tree.js +++ b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/tree.js @@ -1,114 +1,133 @@ -# Copyright 2012, 2013 The Apache Software Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http:#www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md + */ +// Copyright 2012, 2013 The Apache Software Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http:#www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ## t5/core/tree +// +// Handlers to support to the core/Tree Tapestry component. +define(["t5/core/dom", "t5/core/ajax", "t5/core/zone"], + function(dom, ajax) { + const TREE = "[data-component-type='core/Tree']"; + const NODE_ID = "data-node-id"; + const SELECTOR = `${TREE} [${NODE_ID}]`; + + const LOADING = "tree-children-loading"; + const LOADED = "tree-children-loaded"; + const EXPANDED = "tree-expanded"; + const SELECTED = "selected-leaf-node"; + + const send = function(node, action, success) { + const container = node.findParent(TREE); + const url = container.attr("data-tree-action-url"); + + return ajax(url, { + data: { + "t:action": action, + "t:nodeid": node.attr(NODE_ID) + }, + success + } + ); + }; + + const loadChildren = function(node) { + + // Ignore duplicate requests to load the children. + if (node.meta(LOADING)) { return; } + + node.meta(LOADING, true); + + node.addClass("empty-node"); + node.update(""); + + return send(node, "expand", function(response) { + // Remove the Ajax spinner and mark the node as expanded (it will have a "-" + // icon instead of a "+" icon) + node.update("").addClass(EXPANDED).removeClass("empty-node"); + + const label = node.findParent("li").findFirst(".tree-label"); + + label.insertAfter(response.json.content); + + node.meta(LOADING, false); + return node.meta(LOADED, true); + }); + }; + + // toggles a folder in the tree between expanded and collapsed (once data for the folder + // has been loaded). + const toggle = function(node) { + const sublist = node.findParent("li").findFirst("ul"); + + if (node.hasClass(EXPANDED)) { + node.removeClass(EXPANDED); + sublist.hide(); + send(node, "markCollapsed"); + return; + } + + node.addClass(EXPANDED); + sublist.show(); + return send(node, "markExpanded"); + }; + + // The handler is triggered on the `` directly inside the `
  • `. + const clickHandler = function() { + + // Ignore clicks on leaf nodes, and on folders that are known to be empty. + if ((this.parent().hasClass("leaf-node")) || (this.hasClass("empty-node"))) { + return false; + } + + // If not already loaded then fire off the Ajax request to load the content. + if ((this.meta(LOADED)) || (this.hasClass(EXPANDED))) { + toggle(this); + } else { + loadChildren(this); + } + + return false; + }; + + const toggleSelection = function() { + + const selected = this.hasClass(SELECTED); + + const node = this.findParent("li").findFirst(`[${NODE_ID}]`); + + if (selected) { + this.removeClass(SELECTED); + send(node, "deselect"); + } else { + this.addClass(SELECTED); + send(node, "select"); + } + + return false; + }; -# ## t5/core/tree -# -# Handlers to support to the core/Tree Tapestry component. -define ["t5/core/dom", "t5/core/ajax", "t5/core/zone"], - (dom, ajax) -> - TREE = "[data-component-type='core/Tree']" - NODE_ID = "data-node-id" - SELECTOR = "#{TREE} [#{NODE_ID}]" + dom.onDocument("click", SELECTOR, clickHandler); - LOADING = "tree-children-loading" - LOADED = "tree-children-loaded" - EXPANDED = "tree-expanded" - SELECTED = "selected-leaf-node" + dom.onDocument("click", + `${TREE}[data-tree-node-selection-enabled] LI.leaf-node > .tree-label`, + toggleSelection); - send = (node, action, success) -> - container = node.findParent TREE - url = container.attr "data-tree-action-url" - ajax url, - data: - "t:action": action - "t:nodeid": node.attr NODE_ID - success: success - - loadChildren = (node) -> - - # Ignore duplicate requests to load the children. - return if node.meta LOADING - - node.meta LOADING, true - - node.addClass "empty-node" - node.update "" - - send node, "expand", (response) -> - # Remove the Ajax spinner and mark the node as expanded (it will have a "-" - # icon instead of a "+" icon) - node.update("").addClass(EXPANDED).removeClass("empty-node") - - label = node.findParent("li").findFirst(".tree-label") - - label.insertAfter response.json.content - - node.meta LOADING, false - node.meta LOADED, true - - # toggles a folder in the tree between expanded and collapsed (once data for the folder - # has been loaded). - toggle = (node) -> - sublist = node.findParent("li").findFirst("ul") - - if node.hasClass EXPANDED - node.removeClass EXPANDED - sublist.hide() - send node, "markCollapsed" - return - - node.addClass EXPANDED - sublist.show() - send node, "markExpanded" - - # The handler is triggered on the `` directly inside the `
  • `. - clickHandler = -> - - # Ignore clicks on leaf nodes, and on folders that are known to be empty. - if (@parent().hasClass "leaf-node") or (@hasClass "empty-node") - return false - - # If not already loaded then fire off the Ajax request to load the content. - if (@meta LOADED) or (@hasClass EXPANDED) - toggle this - else - loadChildren this - - return false - - toggleSelection = -> - - selected = @hasClass SELECTED - - node = @findParent("li").findFirst("[#{NODE_ID}]") - - if selected - @removeClass SELECTED - send node, "deselect" - else - @addClass SELECTED - send node, "select" - - return false - - dom.onDocument "click", SELECTOR, clickHandler - - dom.onDocument "click", - "#{TREE}[data-tree-node-selection-enabled] LI.leaf-node > .tree-label", - toggleSelection - - - return null + return null; +}); diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/utils.js b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/utils.js index c89b2941a3..b556d58e71 100644 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/utils.js +++ b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/utils.js @@ -1,59 +1,74 @@ -# Copyright 2012, 2013 The Apache Software Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http:#www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -## t5/core/utils -# -# A few handy functions. -define ["underscore"], - - (_) -> - - trim = (input) -> - if String.prototype.trim - input.trim() - else - input.replace(/^\s+/, '').replace(/\s+$/, '') - - # Extends a URL, adding parameters and values from the params object. The values must already - # be URI encoded. - extendURL = (url, params) -> - - sep = if url.indexOf("?") >= 0 then "&" else "?" - - for name, value of params - url = url + sep + name + "=" + value - sep = "&" - - return url - - exports = - trim: trim - extendURL: extendURL - - startsWith: (string, pattern) -> (string.indexOf pattern) is 0 - # Trims leading and trailing whitespace from a string. Delegates to String.prototype.trim if present. - # Determines if the input is a blank string, or null, or an empty array. - isBlank: (input) -> - - return true if input is null - - if _.isArray input - return input.length is 0 +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md + */ +// Copyright 2012, 2013 The Apache Software Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http:#www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//# t5/core/utils +// +// A few handy functions. +define(["underscore"], + + function(_) { + + let exports; + const trim = function(input) { + if (String.prototype.trim) { + return input.trim(); + } else { + return input.replace(/^\s+/, '').replace(/\s+$/, ''); + } + }; + + // Extends a URL, adding parameters and values from the params object. The values must already + // be URI encoded. + const extendURL = function(url, params) { + + let sep = url.indexOf("?") >= 0 ? "&" : "?"; + + for (var name in params) { + var value = params[name]; + url = url + sep + name + "=" + value; + sep = "&"; + } + + return url; + }; + + return exports = { + trim, + extendURL, + + startsWith(string, pattern) { return (string.indexOf(pattern)) === 0; }, + // Trims leading and trailing whitespace from a string. Delegates to String.prototype.trim if present. + // Determines if the input is a blank string, or null, or an empty array. + isBlank(input) { + + if (input === null) { return true; } + + if (_.isArray(input)) { + return input.length === 0; + } - return false if typeof input is "boolean" + if (typeof input === "boolean") { return false; } - return (exports.trim input).length is 0 + return (exports.trim(input)).length === 0; + }, - # Splits the input string into words separated by whitespace - split: (str) -> _(str.split " ").reject((s) -> s is "") + // Splits the input string into words separated by whitespace + split(str) { return _(str.split(" ")).reject(s => s === ""); } + }; +}); diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/validation.js b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/validation.js index b140f0b035..d54bcd0243 100644 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/validation.js +++ b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/validation.js @@ -1,178 +1,220 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http:#www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# ## t5/core/validation -# -# Support for Tapestry's built-in set of translators and validators. -# -define ["underscore", "t5/core/dom", "t5/core/events", "t5/core/utils", "t5/core/messages", "t5/core/fields"], - (_, dom, events, utils, messages) -> +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md + */ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http:#www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ## t5/core/validation +// +// Support for Tapestry's built-in set of translators and validators. +// +define(["underscore", "t5/core/dom", "t5/core/events", "t5/core/utils", "t5/core/messages", "t5/core/fields"], + function(_, dom, events, utils, messages) { + + const REGEXP_META = "t5:regular-expression"; + + const minus = messages("decimal-symbols.minus"); + const grouping = messages("decimal-symbols.group"); + const decimal = messages("decimal-symbols.decimal"); + + // Formats a string for a localized number into a simple number. This uses localization + // information provided by `t5/core/messages` to remove grouping seperators and convert the + // minus sign and decimal seperator into english norms ("-" and ".") that are compatible + // with the `Number` constructor. May throw `Error` if the input can not be parsed. + // + // A little state machine does the parsing; the input may have a leading minus sign. + // It then consists primarily of numeric digits. At least one digit must occur + // between each grouping character. Grouping characters are not allowed + // after the decimal point. + // + // * input - input string to be converted + // * isInteger - restrict to integer values (decimal point not allowed) + const parseNumber = function(input, isInteger) { + + let canonical = ""; + + const accept = ch => canonical += ch; + + const acceptDigitOnly = function(ch) { + if ((ch < "0") || (ch > "9")) { + throw new Error(messages("core-input-not-numeric")); + } + + accept(ch); + }; + + const mustBeDigit = function(ch) { + acceptDigitOnly(ch); + return any; + }; + + var decimalPortion = function(ch) { + acceptDigitOnly(ch); + return decimalPortion; + }; + + var any = function(ch) { + switch (ch) { + case grouping: return mustBeDigit; + case decimal: + if (isInteger) { + throw new Error(messages("core-input-not-integer")); + } + + accept("."); + return decimalPortion; + default: + return mustBeDigit(ch); + } + }; + + const leadingMinus = function(ch) { + if (ch === minus) { + accept("-"); + return mustBeDigit; + } else { + return any(ch); + } + }; + + let state = leadingMinus; + + for (var ch of Array.from(utils.trim(input))) { + state = (state(ch)); + } + + return Number(canonical); + }; + + const matches = function(input, re) { + const groups = input.match(re); + + // Unlike Java, there isn't an easy way to match the entire string. This + // gets the job done. + + if (groups === null) { return false; } + + return groups[0] === input; + }; + + const emailRE = new RegExp("[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?\\.)+[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?"); + + const translate = function(field, memo, isInteger) { + try { + const result = parseNumber(memo.value, isInteger); + + if (_.isNaN(result)) { + throw messages("core-input-not-numeric"); + } + + return memo.translated = result; + } catch (e) { + memo.error = (field.attr("data-translation-message")) || e.message || "ERROR"; + return false; + } + }; + + dom.onDocument(events.field.optional, "[data-optionality=required]", function(event, memo) { + + if (utils.isBlank(memo.value)) { + return memo.error = (this.attr("data-required-message")) || "REQUIRED"; + } + }); + + dom.onDocument(events.field.translate, "[data-translation=numeric]", function(event, memo) { + return translate(this, memo, false); + }); + + dom.onDocument(events.field.translate, "[data-translation=integer]", function(event, memo) { + return translate(this, memo, true); + }); + + dom.onDocument(events.field.validate, "[data-validate-min-length]", function(event, memo) { + const min = parseInt(this.attr("data-validate-min-length")); + + if (memo.translated.length < min) { + memo.error = (this.attr("data-min-length-message")) || "TOO SHORT"; + return false; + } + }); + + dom.onDocument(events.field.validate, "[data-validate-max-length]", function(event, memo) { + const max = parseInt(this.attr("data-validate-max-length")); + + if (memo.translated.length > max) { + memo.error = (this.attr("data-max-length-message")) || "TOO LONG"; + return false; + } + }); + + dom.onDocument(events.field.validate, "[data-validate-max]", function(event, memo) { + const max = parseInt(this.attr("data-validate-max")); + + if (memo.translated > max) { + memo.error = (this.attr("data-max-message")) || "TOO LARGE"; + return false; + } + }); + + dom.onDocument(events.field.validate, "[data-validate-min]", function(event, memo) { + const min = parseInt(this.attr("data-validate-min")); + + if (memo.translated < min) { + memo.error = (this.attr("data-min-message")) || "TOO SMALL"; + return false; + } + }); - REGEXP_META = "t5:regular-expression" - - minus = messages "decimal-symbols.minus" - grouping = messages "decimal-symbols.group" - decimal = messages "decimal-symbols.decimal" - - # Formats a string for a localized number into a simple number. This uses localization - # information provided by `t5/core/messages` to remove grouping seperators and convert the - # minus sign and decimal seperator into english norms ("-" and ".") that are compatible - # with the `Number` constructor. May throw `Error` if the input can not be parsed. - # - # A little state machine does the parsing; the input may have a leading minus sign. - # It then consists primarily of numeric digits. At least one digit must occur - # between each grouping character. Grouping characters are not allowed - # after the decimal point. - # - # * input - input string to be converted - # * isInteger - restrict to integer values (decimal point not allowed) - parseNumber = (input, isInteger) -> + dom.onDocument(events.field.validate, "[data-validate-email]", function(event, memo) { - canonical = "" + if (!matches(memo.translated, emailRE)) { + memo.error = (this.attr("data-email-message")) || "INVALID EMAIL"; + return false; + } + }); - accept = (ch) -> canonical += ch + dom.onDocument(events.field.validate, "[data-validate-regexp]", function(event, memo) { - acceptDigitOnly = (ch) -> - if ch < "0" or ch > "9" - throw new Error messages "core-input-not-numeric" + // Cache the compiled regular expression. + let re = this.meta(REGEXP_META); - accept ch - return + if (!re) { + re = new RegExp(this.attr("data-validate-regexp")); + this.meta(REGEXP_META, re); + } - mustBeDigit = (ch) -> - acceptDigitOnly ch - return any + if (!matches(memo.translated, re)) { + memo.error = (this.attr("data-regexp-message")) || "INVALID"; + return false; + } + }); - decimalPortion = (ch) -> - acceptDigitOnly ch - return decimalPortion + dom.onDocument(events.field.validate, "[data-expected-status=checked]", function(event, memo) { - any = (ch) -> - switch ch - when grouping then return mustBeDigit - when decimal - if isInteger - throw new Error messages "core-input-not-integer" + if (!memo.value) { + return memo.error = (this.attr("data-checked-message")) || "MUST BE CHECKED"; + } + }); - accept "." - return decimalPortion - else - mustBeDigit ch - - leadingMinus = (ch) -> - if ch is minus - accept "-" - return mustBeDigit - else - any ch - - state = leadingMinus - - for ch in utils.trim input - state = (state ch) - - return Number canonical - - matches = (input, re) -> - groups = input.match re - - # Unlike Java, there isn't an easy way to match the entire string. This - # gets the job done. - - return false if groups is null - - groups[0] is input - - emailRE = new RegExp("[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?\\.)+[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?") - - translate = (field, memo, isInteger) -> - try - result = parseNumber memo.value, isInteger - - if _.isNaN result - throw messages "core-input-not-numeric" - - memo.translated = result - catch e - memo.error = (field.attr "data-translation-message") or e.message or "ERROR" - return false - - dom.onDocument events.field.optional, "[data-optionality=required]", (event, memo) -> - - if utils.isBlank memo.value - memo.error = (@attr "data-required-message") or "REQUIRED" - - dom.onDocument events.field.translate, "[data-translation=numeric]", (event, memo) -> - translate this, memo, false - - dom.onDocument events.field.translate, "[data-translation=integer]", (event, memo) -> - translate this, memo, true - - dom.onDocument events.field.validate, "[data-validate-min-length]", (event, memo) -> - min = parseInt @attr "data-validate-min-length" - - if memo.translated.length < min - memo.error = (@attr "data-min-length-message") or "TOO SHORT" - return false - - dom.onDocument events.field.validate, "[data-validate-max-length]", (event, memo) -> - max = parseInt @attr "data-validate-max-length" - - if memo.translated.length > max - memo.error = (@attr "data-max-length-message") or "TOO LONG" - return false - - dom.onDocument events.field.validate, "[data-validate-max]", (event, memo) -> - max = parseInt @attr "data-validate-max" - - if memo.translated > max - memo.error = (@attr "data-max-message") or "TOO LARGE" - return false - - dom.onDocument events.field.validate, "[data-validate-min]", (event, memo) -> - min = parseInt @attr "data-validate-min" - - if memo.translated < min - memo.error = (@attr "data-min-message") or "TOO SMALL" - return false - - dom.onDocument events.field.validate, "[data-validate-email]", (event, memo) -> - - unless (matches memo.translated, emailRE) - memo.error = (@attr "data-email-message") or "INVALID EMAIL" - return false - - dom.onDocument events.field.validate, "[data-validate-regexp]", (event, memo) -> - - # Cache the compiled regular expression. - re = @meta REGEXP_META - - unless re - re = new RegExp(@attr "data-validate-regexp") - @meta REGEXP_META, re - - unless (matches memo.translated, re) - memo.error = (@attr "data-regexp-message") or "INVALID" - return false - - dom.onDocument events.field.validate, "[data-expected-status=checked]", (event, memo) -> - - unless memo.value - memo.error = (@attr "data-checked-message") or "MUST BE CHECKED" - - dom.onDocument events.field.validate, "[data-expected-status=unchecked]", (event, memo) -> - - if memo.value - memo.error = (@attr "data-checked-message") or "MUST NOT BE CHECKED" - - # Export the number parser, just to be nice (and to support some testing). - return { parseNumber } \ No newline at end of file + dom.onDocument(events.field.validate, "[data-expected-status=unchecked]", function(event, memo) { + + if (memo.value) { + return memo.error = (this.attr("data-checked-message")) || "MUST NOT BE CHECKED"; + } + }); + + // Export the number parser, just to be nice (and to support some testing). + return { parseNumber }; +}); \ No newline at end of file diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/zone-refresh.js b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/zone-refresh.js index a6b743b9c4..8082bdd486 100644 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/zone-refresh.js +++ b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/zone-refresh.js @@ -1,68 +1,78 @@ -# Copyright 2012 The Apache Software Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http:#www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# ## t5/core/zone-refresh -define ["t5/core/events", "t5/core/dom", "t5/core/console"], - (events, dom, console) -> - - # Initialize a timer for the zone at the specified period (in seconds). The zone will be - # refreshed with the provided URL. - initialize = (zoneId, period, url) -> - zone = dom zoneId - - unless zone - console.err "Zone #{zoneId} not found for periodic refresh." - return - - # Only one periodic refresh per zone. - return if zone.meta "periodic-refresh" - - zone.meta "periodic-refresh", true - - executing = false - - # Whenever the zone updates, we can clear the executing flag. - - zone.on events.zone.didUpdate, -> - executing = false - return - - cleanUp = -> - window.clearInterval intervalId - zone = null - return - - handler = -> - # Don't clog things up if the response rate is too slow - return if executing - - # If the zone element is no longer part of the DOM, stop the - # timer - - unless (zone.closest 'body') - cleanUp() - return - - # Set the flag now, it will clear when the zone updates. - executing = true - - zone.trigger events.zone.refresh, { url } - - intervalId = window.setInterval handler, period * 1000 - - # Not sure if this is needed except for IE: - (dom window).on "beforeunload", cleanUp - - # export the single function: - return initialize +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md + */ +// Copyright 2012 The Apache Software Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http:#www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ## t5/core/zone-refresh +define(["t5/core/events", "t5/core/dom", "t5/core/console"], + function(events, dom, console) { + + // Initialize a timer for the zone at the specified period (in seconds). The zone will be + // refreshed with the provided URL. + const initialize = function(zoneId, period, url) { + let zone = dom(zoneId); + + if (!zone) { + console.err(`Zone ${zoneId} not found for periodic refresh.`); + return; + } + + // Only one periodic refresh per zone. + if (zone.meta("periodic-refresh")) { return; } + + zone.meta("periodic-refresh", true); + + let executing = false; + + // Whenever the zone updates, we can clear the executing flag. + + zone.on(events.zone.didUpdate, function() { + executing = false; + }); + + const cleanUp = function() { + window.clearInterval(intervalId); + zone = null; + }; + + const handler = function() { + // Don't clog things up if the response rate is too slow + if (executing) { return; } + + // If the zone element is no longer part of the DOM, stop the + // timer + + if (!zone.closest('body')) { + cleanUp(); + return; + } + + // Set the flag now, it will clear when the zone updates. + executing = true; + + return zone.trigger(events.zone.refresh, { url }); + }; + + var intervalId = window.setInterval(handler, period * 1000); + + // Not sure if this is needed except for IE: + return (dom(window)).on("beforeunload", cleanUp); + }; + + // export the single function: + return initialize; +}); diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/zone.js b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/zone.js index dbcbd55188..639053fb59 100644 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/zone.js +++ b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/zone.js @@ -1,160 +1,189 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http:#www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# ## t5/core/zone -# -# Provides a default handler for events related to zones. A zone is any kind of -# client-side element that can be updated; a zone will normally have a unique id. -# Typically, a client-side zone element is rendered by, and corresponds to, a server-side -# core/Zone component; however, certain other components (such as core/ProgressiveDisplay) may -# also be treated as zones. -# -# Most often, a zone is any element with attribute `data-container-type=zone` and corresponds -# to a core/Zone server-side component. -define ["t5/core/dom", "t5/core/events", "t5/core/ajax", "t5/core/console", "t5/core/forms", "underscore"], - - (dom, events, ajax, console, forms, _) -> +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md + */ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http:#www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ## t5/core/zone +// +// Provides a default handler for events related to zones. A zone is any kind of +// client-side element that can be updated; a zone will normally have a unique id. +// Typically, a client-side zone element is rendered by, and corresponds to, a server-side +// core/Zone component; however, certain other components (such as core/ProgressiveDisplay) may +// also be treated as zones. +// +// Most often, a zone is any element with attribute `data-container-type=zone` and corresponds +// to a core/Zone server-side component. +define(["t5/core/dom", "t5/core/events", "t5/core/ajax", "t5/core/console", "t5/core/forms", "underscore"], + + function(dom, events, ajax, console, forms, _) { - unless (typeof ajax) == "function" - console.error "ajax variable is not a function, but instead it is " + JSON.stringify(ajax) - console.error ajax - throw new Error "ajax variable is not a function" + if ((typeof ajax) !== "function") { + console.error("ajax variable is not a function, but instead it is " + JSON.stringify(ajax)); + console.error(ajax); + throw new Error("ajax variable is not a function"); + } - # For a given element that may have the `data-update-zone` attribute, locates the - # zone element. May return null if the zone can not be found (after logging an error - # to the console). - # - # * element - starting point for determining zone - findZone = (element) -> - zoneId = element.attr "data-update-zone" + // For a given element that may have the `data-update-zone` attribute, locates the + // zone element. May return null if the zone can not be found (after logging an error + // to the console). + // + // * element - starting point for determining zone + const findZone = function(element) { + let zone; + const zoneId = element.attr("data-update-zone"); - if zoneId is "^" - zone = element.findParent "[data-container-type=zone]" + if (zoneId === "^") { + zone = element.findParent("[data-container-type=zone]"); - if zone is null - throw new Error "Unable to locate containing zone for #{element}." + if (zone === null) { + throw new Error(`Unable to locate containing zone for ${element}.`); + } - return zone + return zone; + } - zone = dom zoneId + zone = dom(zoneId); - if zone is null - throw new Error "Unable to locate zone '#{zoneId}'." + if (zone === null) { + throw new Error(`Unable to locate zone '${zoneId}'.`); + } - return zone + return zone; + }; - dom.onDocument "click", "a[data-update-zone]", (event) -> + dom.onDocument("click", "a[data-update-zone]", function(event) { - element = @closest "[data-update-zone]" + const element = this.closest("[data-update-zone]"); - unless element - throw new Error "Could not locate containing element with data-update-zone attribute." + if (!element) { + throw new Error("Could not locate containing element with data-update-zone attribute."); + } - zone = findZone element + const zone = findZone(element); - if zone - zone.trigger events.zone.refresh, url: element.attr "href" + if (zone) { + zone.trigger(events.zone.refresh, {url: element.attr("href")}); + } - event.nativeEvent.preventDefault() - return + event.nativeEvent.preventDefault(); + }); - dom.onDocument "submit", "form[data-update-zone]", -> + dom.onDocument("submit", "form[data-update-zone]", function() { - zone = findZone this + const zone = findZone(this); - if zone - formParameters = forms.gatherParameters this + if (zone) { + const formParameters = forms.gatherParameters(this); - zone.trigger events.zone.refresh, - url: (@attr "action") + zone.trigger(events.zone.refresh, { + url: (this.attr("action")), parameters: formParameters + } + ); + } - return false + return false; + }); - dom.onDocument "submit", "form[data-async-trigger]", -> + dom.onDocument("submit", "form[data-async-trigger]", function() { - formParameters = forms.gatherParameters this + const formParameters = forms.gatherParameters(this); - @addClass "ajax-update" + this.addClass("ajax-update"); - ajax (@attr "action"), - data: formParameters - complete: => @removeClass "ajax-update" + ajax((this.attr("action")), { + data: formParameters, + complete: () => this.removeClass("ajax-update") + } + ); - return false + return false; + }); - dom.onDocument events.zone.update, (event) -> + dom.onDocument(events.zone.update, function(event) { - @trigger events.zone.willUpdate + this.trigger(events.zone.willUpdate); - content = event.memo.content + const { + content + } = event.memo; - # The server may have passed down the empty string for the content; that removes the existing content. - # On the other hand, the server may have not provided a content key; in that case, content is undefined - # which means to leave the existing content alone. - # - # Note that currently, the willUpdate and didUpdate events are triggered even when the zone is not actually - # updated. That may be a bug. - unless content is undefined - @update content + // The server may have passed down the empty string for the content; that removes the existing content. + // On the other hand, the server may have not provided a content key; in that case, content is undefined + // which means to leave the existing content alone. + // + // Note that currently, the willUpdate and didUpdate events are triggered even when the zone is not actually + // updated. That may be a bug. + if (content !== undefined) { + this.update(content); + } - @trigger events.initializeComponents - @trigger events.zone.didUpdate + this.trigger(events.initializeComponents); + return this.trigger(events.zone.didUpdate); + }); - dom.onDocument events.zone.refresh, (event) -> + dom.onDocument(events.zone.refresh, function(event) { - # This event may be triggered on an element inside the zone, rather than on the zone itself. Scan upwards - # to find the actual zone. - zone = @closest "[data-container-type=zone]" + // This event may be triggered on an element inside the zone, rather than on the zone itself. Scan upwards + // to find the actual zone. + const zone = this.closest("[data-container-type=zone]"); - # A Zone inside a form will render some additional parameters to coordinate updates with the Form on the server. - attr = zone.attr "data-zone-parameters" + // A Zone inside a form will render some additional parameters to coordinate updates with the Form on the server. + const attr = zone.attr("data-zone-parameters"); - parameters = attr and JSON.parse attr + const parameters = attr && JSON.parse(attr); - simpleIdParams = if zone.attr "data-simple-ids" then {"t:suppress-namespaced-ids": true} + const simpleIdParams = zone.attr("data-simple-ids") ? {"t:suppress-namespaced-ids": true} : undefined; - ajax event.memo.url, - data: _.extend { "t:zoneid": zone.element.id }, simpleIdParams, parameters, event.memo.parameters - success: (response) -> - zone.trigger events.zone.update, content: response.json?.content + return ajax(event.memo.url, { + data: _.extend({ "t:zoneid": zone.element.id }, simpleIdParams, parameters, event.memo.parameters), + success(response) { + return zone.trigger(events.zone.update, {content: (response.json != null ? response.json.content : undefined)}); + } + } + ); + }); - dom.onDocument "click", "a[data-async-trigger]", (event)-> - link = @closest 'a[data-async-trigger]' + dom.onDocument("click", "a[data-async-trigger]", function(event){ + const link = this.closest('a[data-async-trigger]'); - link.addClass "ajax-update" + link.addClass("ajax-update"); - ajax (link.attr "href"), - complete: -> link.removeClass "ajax-update" + ajax((link.attr("href")), + {complete() { return link.removeClass("ajax-update"); }}); - event.nativeEvent.preventDefault() + event.nativeEvent.preventDefault(); - return + }); - # Locates a zone element by its unique id attribute, and (deferred, to a later event loop cycle), - # performs a standard refresh of the zone. This is primarily used by the core/ProgressiveDisplay component. - # - # * id - client id of the element - # * url - URL to use to refresh the element. - deferredZoneUpdate = (id, url) -> + // Locates a zone element by its unique id attribute, and (deferred, to a later event loop cycle), + // performs a standard refresh of the zone. This is primarily used by the core/ProgressiveDisplay component. + // + // * id - client id of the element + // * url - URL to use to refresh the element. + const deferredZoneUpdate = (id, url) => _.defer(function() { + const zone = dom(id); - _.defer -> - zone = dom id + if (zone === null) { + console.error(`Could not locate element '${id}' to update.`); + return; + } - if zone is null - console.error "Could not locate element '#{id}' to update." - return + return zone.trigger(events.zone.refresh, { url });}); - zone.trigger events.zone.refresh, { url } - - # Most of this module is document-level event handlers, but there's also some exports: - return { deferredZoneUpdate, findZone } + // Most of this module is document-level event handlers, but there's also some exports: + return { deferredZoneUpdate, findZone }; +}); From cf8a6cab1c3ae9daccd121cfe31268f9908d9d61 Mon Sep 17 00:00:00 2001 From: "Thiago H. de Paula Figueiredo" Date: Fri, 4 Apr 2025 14:07:36 -0300 Subject: [PATCH 03/69] TAP5-2804: moving converted CoffeeScript sources to TS project --- .../META-INF/modules/t5/core => typescript/src}/ajax.js | 0 .../META-INF/modules/t5/core => typescript/src}/ajaxformloop.js | 0 .../META-INF/modules/t5/core => typescript/src}/alert.js | 0 .../META-INF/modules/t5/core => typescript/src}/autocomplete.js | 0 .../META-INF/modules/t5/core => typescript/src}/bootstrap.js | 0 .../META-INF/modules/t5/core => typescript/src}/confirm-click.js | 0 .../META-INF/modules/t5/core => typescript/src}/console.js | 0 .../META-INF/modules/t5/core => typescript/src}/datefield.js | 0 .../META-INF/modules/t5/core => typescript/src}/events.js | 0 .../modules/t5/core => typescript/src}/exception-display.js | 0 .../modules/t5/core => typescript/src}/exception-frame.js | 0 .../META-INF/modules/t5/core => typescript/src}/fields.js | 0 .../META-INF/modules/t5/core => typescript/src}/form-fragment.js | 0 .../META-INF/modules/t5/core => typescript/src}/forms.js | 0 .../META-INF/modules/t5/core => typescript/src}/graphviz.js | 0 .../META-INF/modules/t5/core => typescript/src}/init.js | 0 .../META-INF/modules/t5/core => typescript/src}/localdate.js | 0 .../META-INF/modules/t5/core => typescript/src}/messages.js | 0 .../META-INF/modules/t5/core => typescript/src}/moment.js | 0 .../META-INF/modules/t5/core => typescript/src}/pageinit.js | 0 .../META-INF/modules/t5/core => typescript/src}/palette.js | 0 .../META-INF/modules/t5/core => typescript/src}/select.js | 0 .../META-INF/modules/t5/core => typescript/src}/time-interval.js | 0 .../META-INF/modules/t5/core => typescript/src}/tree.js | 0 .../META-INF/modules/t5/core => typescript/src}/utils.js | 0 .../META-INF/modules/t5/core => typescript/src}/validation.js | 0 .../META-INF/modules/t5/core => typescript/src}/zone-refresh.js | 0 .../META-INF/modules/t5/core => typescript/src}/zone.js | 0 28 files changed, 0 insertions(+), 0 deletions(-) rename tapestry-core/src/main/{coffeescript/META-INF/modules/t5/core => typescript/src}/ajax.js (100%) rename tapestry-core/src/main/{coffeescript/META-INF/modules/t5/core => typescript/src}/ajaxformloop.js (100%) rename tapestry-core/src/main/{coffeescript/META-INF/modules/t5/core => typescript/src}/alert.js (100%) rename tapestry-core/src/main/{coffeescript/META-INF/modules/t5/core => typescript/src}/autocomplete.js (100%) rename tapestry-core/src/main/{coffeescript/META-INF/modules/t5/core => typescript/src}/bootstrap.js (100%) rename tapestry-core/src/main/{coffeescript/META-INF/modules/t5/core => typescript/src}/confirm-click.js (100%) rename tapestry-core/src/main/{coffeescript/META-INF/modules/t5/core => typescript/src}/console.js (100%) rename tapestry-core/src/main/{coffeescript/META-INF/modules/t5/core => typescript/src}/datefield.js (100%) rename tapestry-core/src/main/{coffeescript/META-INF/modules/t5/core => typescript/src}/events.js (100%) rename tapestry-core/src/main/{coffeescript/META-INF/modules/t5/core => typescript/src}/exception-display.js (100%) rename tapestry-core/src/main/{coffeescript/META-INF/modules/t5/core => typescript/src}/exception-frame.js (100%) rename tapestry-core/src/main/{coffeescript/META-INF/modules/t5/core => typescript/src}/fields.js (100%) rename tapestry-core/src/main/{coffeescript/META-INF/modules/t5/core => typescript/src}/form-fragment.js (100%) rename tapestry-core/src/main/{coffeescript/META-INF/modules/t5/core => typescript/src}/forms.js (100%) rename tapestry-core/src/main/{coffeescript/META-INF/modules/t5/core => typescript/src}/graphviz.js (100%) rename tapestry-core/src/main/{coffeescript/META-INF/modules/t5/core => typescript/src}/init.js (100%) rename tapestry-core/src/main/{coffeescript/META-INF/modules/t5/core => typescript/src}/localdate.js (100%) rename tapestry-core/src/main/{coffeescript/META-INF/modules/t5/core => typescript/src}/messages.js (100%) rename tapestry-core/src/main/{coffeescript/META-INF/modules/t5/core => typescript/src}/moment.js (100%) rename tapestry-core/src/main/{coffeescript/META-INF/modules/t5/core => typescript/src}/pageinit.js (100%) rename tapestry-core/src/main/{coffeescript/META-INF/modules/t5/core => typescript/src}/palette.js (100%) rename tapestry-core/src/main/{coffeescript/META-INF/modules/t5/core => typescript/src}/select.js (100%) rename tapestry-core/src/main/{coffeescript/META-INF/modules/t5/core => typescript/src}/time-interval.js (100%) rename tapestry-core/src/main/{coffeescript/META-INF/modules/t5/core => typescript/src}/tree.js (100%) rename tapestry-core/src/main/{coffeescript/META-INF/modules/t5/core => typescript/src}/utils.js (100%) rename tapestry-core/src/main/{coffeescript/META-INF/modules/t5/core => typescript/src}/validation.js (100%) rename tapestry-core/src/main/{coffeescript/META-INF/modules/t5/core => typescript/src}/zone-refresh.js (100%) rename tapestry-core/src/main/{coffeescript/META-INF/modules/t5/core => typescript/src}/zone.js (100%) diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/ajax.js b/tapestry-core/src/main/typescript/src/ajax.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/ajax.js rename to tapestry-core/src/main/typescript/src/ajax.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/ajaxformloop.js b/tapestry-core/src/main/typescript/src/ajaxformloop.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/ajaxformloop.js rename to tapestry-core/src/main/typescript/src/ajaxformloop.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/alert.js b/tapestry-core/src/main/typescript/src/alert.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/alert.js rename to tapestry-core/src/main/typescript/src/alert.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/autocomplete.js b/tapestry-core/src/main/typescript/src/autocomplete.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/autocomplete.js rename to tapestry-core/src/main/typescript/src/autocomplete.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/bootstrap.js b/tapestry-core/src/main/typescript/src/bootstrap.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/bootstrap.js rename to tapestry-core/src/main/typescript/src/bootstrap.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/confirm-click.js b/tapestry-core/src/main/typescript/src/confirm-click.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/confirm-click.js rename to tapestry-core/src/main/typescript/src/confirm-click.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/console.js b/tapestry-core/src/main/typescript/src/console.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/console.js rename to tapestry-core/src/main/typescript/src/console.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/datefield.js b/tapestry-core/src/main/typescript/src/datefield.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/datefield.js rename to tapestry-core/src/main/typescript/src/datefield.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/events.js b/tapestry-core/src/main/typescript/src/events.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/events.js rename to tapestry-core/src/main/typescript/src/events.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/exception-display.js b/tapestry-core/src/main/typescript/src/exception-display.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/exception-display.js rename to tapestry-core/src/main/typescript/src/exception-display.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/exception-frame.js b/tapestry-core/src/main/typescript/src/exception-frame.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/exception-frame.js rename to tapestry-core/src/main/typescript/src/exception-frame.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/fields.js b/tapestry-core/src/main/typescript/src/fields.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/fields.js rename to tapestry-core/src/main/typescript/src/fields.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/form-fragment.js b/tapestry-core/src/main/typescript/src/form-fragment.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/form-fragment.js rename to tapestry-core/src/main/typescript/src/form-fragment.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/forms.js b/tapestry-core/src/main/typescript/src/forms.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/forms.js rename to tapestry-core/src/main/typescript/src/forms.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/graphviz.js b/tapestry-core/src/main/typescript/src/graphviz.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/graphviz.js rename to tapestry-core/src/main/typescript/src/graphviz.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/init.js b/tapestry-core/src/main/typescript/src/init.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/init.js rename to tapestry-core/src/main/typescript/src/init.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/localdate.js b/tapestry-core/src/main/typescript/src/localdate.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/localdate.js rename to tapestry-core/src/main/typescript/src/localdate.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/messages.js b/tapestry-core/src/main/typescript/src/messages.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/messages.js rename to tapestry-core/src/main/typescript/src/messages.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/moment.js b/tapestry-core/src/main/typescript/src/moment.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/moment.js rename to tapestry-core/src/main/typescript/src/moment.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/pageinit.js b/tapestry-core/src/main/typescript/src/pageinit.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/pageinit.js rename to tapestry-core/src/main/typescript/src/pageinit.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/palette.js b/tapestry-core/src/main/typescript/src/palette.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/palette.js rename to tapestry-core/src/main/typescript/src/palette.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/select.js b/tapestry-core/src/main/typescript/src/select.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/select.js rename to tapestry-core/src/main/typescript/src/select.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/time-interval.js b/tapestry-core/src/main/typescript/src/time-interval.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/time-interval.js rename to tapestry-core/src/main/typescript/src/time-interval.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/tree.js b/tapestry-core/src/main/typescript/src/tree.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/tree.js rename to tapestry-core/src/main/typescript/src/tree.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/utils.js b/tapestry-core/src/main/typescript/src/utils.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/utils.js rename to tapestry-core/src/main/typescript/src/utils.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/validation.js b/tapestry-core/src/main/typescript/src/validation.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/validation.js rename to tapestry-core/src/main/typescript/src/validation.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/zone-refresh.js b/tapestry-core/src/main/typescript/src/zone-refresh.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/zone-refresh.js rename to tapestry-core/src/main/typescript/src/zone-refresh.js diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/zone.js b/tapestry-core/src/main/typescript/src/zone.js similarity index 100% rename from tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/zone.js rename to tapestry-core/src/main/typescript/src/zone.js From 80152bc8a6d0f979a15a3995fdf59a30cec6c540 Mon Sep 17 00:00:00 2001 From: "Thiago H. de Paula Figueiredo" Date: Fri, 4 Apr 2025 14:08:05 -0300 Subject: [PATCH 04/69] TAP5-2804: creating TypeScript project --- settings.gradle | 2 +- tapestry-core/src/main/typescript/.gitignore | 1 + .../src/main/typescript/package-lock.json | 25 +++++++++++++++++++ .../src/main/typescript/package.json | 5 ++++ .../src/main/typescript/tsconfig.json | 21 ++++++++++++++++ 5 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 tapestry-core/src/main/typescript/.gitignore create mode 100644 tapestry-core/src/main/typescript/package-lock.json create mode 100644 tapestry-core/src/main/typescript/package.json create mode 100644 tapestry-core/src/main/typescript/tsconfig.json diff --git a/settings.gradle b/settings.gradle index f7400a1505..7cdaa06322 100644 --- a/settings.gradle +++ b/settings.gradle @@ -31,7 +31,7 @@ buildCache { } } -rootProject.name = "tapestry" +rootProject.name = "tapestry-5" include "plastic", "tapestry5-annotations", "tapestry-test", "tapestry-func", "tapestry-ioc", "tapestry-json", "tapestry-http", "tapestry-core" include "tapestry-hibernate-core", "tapestry-hibernate", "tapestry-jmx", "tapestry-upload" diff --git a/tapestry-core/src/main/typescript/.gitignore b/tapestry-core/src/main/typescript/.gitignore new file mode 100644 index 0000000000..2ccbe4656c --- /dev/null +++ b/tapestry-core/src/main/typescript/.gitignore @@ -0,0 +1 @@ +/node_modules/ diff --git a/tapestry-core/src/main/typescript/package-lock.json b/tapestry-core/src/main/typescript/package-lock.json new file mode 100644 index 0000000000..24f53662fc --- /dev/null +++ b/tapestry-core/src/main/typescript/package-lock.json @@ -0,0 +1,25 @@ +{ + "name": "typescript", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "typescript": "^5.8.2" + } + }, + "node_modules/typescript": { + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + } + } +} diff --git a/tapestry-core/src/main/typescript/package.json b/tapestry-core/src/main/typescript/package.json new file mode 100644 index 0000000000..3ba47b7ad8 --- /dev/null +++ b/tapestry-core/src/main/typescript/package.json @@ -0,0 +1,5 @@ +{ + "devDependencies": { + "typescript": "^5.0.0" + } +} diff --git a/tapestry-core/src/main/typescript/tsconfig.json b/tapestry-core/src/main/typescript/tsconfig.json new file mode 100644 index 0000000000..5fa70e7617 --- /dev/null +++ b/tapestry-core/src/main/typescript/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Language and Environment */ + "target": "ES2015", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + + /* Modules */ + "module": "ES2015", /* Specify what module code is generated. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +} From 47699c617172d7406141e763dab1de3096005af6 Mon Sep 17 00:00:00 2001 From: "Thiago H. de Paula Figueiredo" Date: Fri, 4 Apr 2025 14:54:13 -0300 Subject: [PATCH 05/69] TAP5-2804: changing file extensions from .js to .ts --- tapestry-core/src/main/typescript/src/{ajax.js => ajax.ts} | 0 .../src/main/typescript/src/{ajaxformloop.js => ajaxformloop.ts} | 0 tapestry-core/src/main/typescript/src/{alert.js => alert.ts} | 0 .../src/main/typescript/src/{autocomplete.js => autocomplete.ts} | 0 .../src/main/typescript/src/{bootstrap.js => bootstrap.ts} | 0 .../main/typescript/src/{confirm-click.js => confirm-click.ts} | 0 tapestry-core/src/main/typescript/src/{console.js => console.ts} | 0 .../src/main/typescript/src/{datefield.js => datefield.ts} | 0 tapestry-core/src/main/typescript/src/{events.js => events.ts} | 0 .../typescript/src/{exception-display.js => exception-display.ts} | 0 .../typescript/src/{exception-frame.js => exception-frame.ts} | 0 tapestry-core/src/main/typescript/src/{fields.js => fields.ts} | 0 .../main/typescript/src/{form-fragment.js => form-fragment.ts} | 0 tapestry-core/src/main/typescript/src/{forms.js => forms.ts} | 0 .../src/main/typescript/src/{graphviz.js => graphviz.ts} | 0 tapestry-core/src/main/typescript/src/{init.js => init.ts} | 0 .../src/main/typescript/src/{localdate.js => localdate.ts} | 0 .../src/main/typescript/src/{messages.js => messages.ts} | 0 tapestry-core/src/main/typescript/src/{moment.js => moment.ts} | 0 .../src/main/typescript/src/{pageinit.js => pageinit.ts} | 0 tapestry-core/src/main/typescript/src/{palette.js => palette.ts} | 0 tapestry-core/src/main/typescript/src/{select.js => select.ts} | 0 .../main/typescript/src/{time-interval.js => time-interval.ts} | 0 tapestry-core/src/main/typescript/src/{tree.js => tree.ts} | 0 tapestry-core/src/main/typescript/src/{utils.js => utils.ts} | 0 .../src/main/typescript/src/{validation.js => validation.ts} | 0 .../src/main/typescript/src/{zone-refresh.js => zone-refresh.ts} | 0 tapestry-core/src/main/typescript/src/{zone.js => zone.ts} | 0 28 files changed, 0 insertions(+), 0 deletions(-) rename tapestry-core/src/main/typescript/src/{ajax.js => ajax.ts} (100%) rename tapestry-core/src/main/typescript/src/{ajaxformloop.js => ajaxformloop.ts} (100%) rename tapestry-core/src/main/typescript/src/{alert.js => alert.ts} (100%) rename tapestry-core/src/main/typescript/src/{autocomplete.js => autocomplete.ts} (100%) rename tapestry-core/src/main/typescript/src/{bootstrap.js => bootstrap.ts} (100%) rename tapestry-core/src/main/typescript/src/{confirm-click.js => confirm-click.ts} (100%) rename tapestry-core/src/main/typescript/src/{console.js => console.ts} (100%) rename tapestry-core/src/main/typescript/src/{datefield.js => datefield.ts} (100%) rename tapestry-core/src/main/typescript/src/{events.js => events.ts} (100%) rename tapestry-core/src/main/typescript/src/{exception-display.js => exception-display.ts} (100%) rename tapestry-core/src/main/typescript/src/{exception-frame.js => exception-frame.ts} (100%) rename tapestry-core/src/main/typescript/src/{fields.js => fields.ts} (100%) rename tapestry-core/src/main/typescript/src/{form-fragment.js => form-fragment.ts} (100%) rename tapestry-core/src/main/typescript/src/{forms.js => forms.ts} (100%) rename tapestry-core/src/main/typescript/src/{graphviz.js => graphviz.ts} (100%) rename tapestry-core/src/main/typescript/src/{init.js => init.ts} (100%) rename tapestry-core/src/main/typescript/src/{localdate.js => localdate.ts} (100%) rename tapestry-core/src/main/typescript/src/{messages.js => messages.ts} (100%) rename tapestry-core/src/main/typescript/src/{moment.js => moment.ts} (100%) rename tapestry-core/src/main/typescript/src/{pageinit.js => pageinit.ts} (100%) rename tapestry-core/src/main/typescript/src/{palette.js => palette.ts} (100%) rename tapestry-core/src/main/typescript/src/{select.js => select.ts} (100%) rename tapestry-core/src/main/typescript/src/{time-interval.js => time-interval.ts} (100%) rename tapestry-core/src/main/typescript/src/{tree.js => tree.ts} (100%) rename tapestry-core/src/main/typescript/src/{utils.js => utils.ts} (100%) rename tapestry-core/src/main/typescript/src/{validation.js => validation.ts} (100%) rename tapestry-core/src/main/typescript/src/{zone-refresh.js => zone-refresh.ts} (100%) rename tapestry-core/src/main/typescript/src/{zone.js => zone.ts} (100%) diff --git a/tapestry-core/src/main/typescript/src/ajax.js b/tapestry-core/src/main/typescript/src/ajax.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/ajax.js rename to tapestry-core/src/main/typescript/src/ajax.ts diff --git a/tapestry-core/src/main/typescript/src/ajaxformloop.js b/tapestry-core/src/main/typescript/src/ajaxformloop.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/ajaxformloop.js rename to tapestry-core/src/main/typescript/src/ajaxformloop.ts diff --git a/tapestry-core/src/main/typescript/src/alert.js b/tapestry-core/src/main/typescript/src/alert.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/alert.js rename to tapestry-core/src/main/typescript/src/alert.ts diff --git a/tapestry-core/src/main/typescript/src/autocomplete.js b/tapestry-core/src/main/typescript/src/autocomplete.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/autocomplete.js rename to tapestry-core/src/main/typescript/src/autocomplete.ts diff --git a/tapestry-core/src/main/typescript/src/bootstrap.js b/tapestry-core/src/main/typescript/src/bootstrap.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/bootstrap.js rename to tapestry-core/src/main/typescript/src/bootstrap.ts diff --git a/tapestry-core/src/main/typescript/src/confirm-click.js b/tapestry-core/src/main/typescript/src/confirm-click.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/confirm-click.js rename to tapestry-core/src/main/typescript/src/confirm-click.ts diff --git a/tapestry-core/src/main/typescript/src/console.js b/tapestry-core/src/main/typescript/src/console.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/console.js rename to tapestry-core/src/main/typescript/src/console.ts diff --git a/tapestry-core/src/main/typescript/src/datefield.js b/tapestry-core/src/main/typescript/src/datefield.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/datefield.js rename to tapestry-core/src/main/typescript/src/datefield.ts diff --git a/tapestry-core/src/main/typescript/src/events.js b/tapestry-core/src/main/typescript/src/events.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/events.js rename to tapestry-core/src/main/typescript/src/events.ts diff --git a/tapestry-core/src/main/typescript/src/exception-display.js b/tapestry-core/src/main/typescript/src/exception-display.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/exception-display.js rename to tapestry-core/src/main/typescript/src/exception-display.ts diff --git a/tapestry-core/src/main/typescript/src/exception-frame.js b/tapestry-core/src/main/typescript/src/exception-frame.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/exception-frame.js rename to tapestry-core/src/main/typescript/src/exception-frame.ts diff --git a/tapestry-core/src/main/typescript/src/fields.js b/tapestry-core/src/main/typescript/src/fields.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/fields.js rename to tapestry-core/src/main/typescript/src/fields.ts diff --git a/tapestry-core/src/main/typescript/src/form-fragment.js b/tapestry-core/src/main/typescript/src/form-fragment.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/form-fragment.js rename to tapestry-core/src/main/typescript/src/form-fragment.ts diff --git a/tapestry-core/src/main/typescript/src/forms.js b/tapestry-core/src/main/typescript/src/forms.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/forms.js rename to tapestry-core/src/main/typescript/src/forms.ts diff --git a/tapestry-core/src/main/typescript/src/graphviz.js b/tapestry-core/src/main/typescript/src/graphviz.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/graphviz.js rename to tapestry-core/src/main/typescript/src/graphviz.ts diff --git a/tapestry-core/src/main/typescript/src/init.js b/tapestry-core/src/main/typescript/src/init.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/init.js rename to tapestry-core/src/main/typescript/src/init.ts diff --git a/tapestry-core/src/main/typescript/src/localdate.js b/tapestry-core/src/main/typescript/src/localdate.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/localdate.js rename to tapestry-core/src/main/typescript/src/localdate.ts diff --git a/tapestry-core/src/main/typescript/src/messages.js b/tapestry-core/src/main/typescript/src/messages.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/messages.js rename to tapestry-core/src/main/typescript/src/messages.ts diff --git a/tapestry-core/src/main/typescript/src/moment.js b/tapestry-core/src/main/typescript/src/moment.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/moment.js rename to tapestry-core/src/main/typescript/src/moment.ts diff --git a/tapestry-core/src/main/typescript/src/pageinit.js b/tapestry-core/src/main/typescript/src/pageinit.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/pageinit.js rename to tapestry-core/src/main/typescript/src/pageinit.ts diff --git a/tapestry-core/src/main/typescript/src/palette.js b/tapestry-core/src/main/typescript/src/palette.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/palette.js rename to tapestry-core/src/main/typescript/src/palette.ts diff --git a/tapestry-core/src/main/typescript/src/select.js b/tapestry-core/src/main/typescript/src/select.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/select.js rename to tapestry-core/src/main/typescript/src/select.ts diff --git a/tapestry-core/src/main/typescript/src/time-interval.js b/tapestry-core/src/main/typescript/src/time-interval.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/time-interval.js rename to tapestry-core/src/main/typescript/src/time-interval.ts diff --git a/tapestry-core/src/main/typescript/src/tree.js b/tapestry-core/src/main/typescript/src/tree.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/tree.js rename to tapestry-core/src/main/typescript/src/tree.ts diff --git a/tapestry-core/src/main/typescript/src/utils.js b/tapestry-core/src/main/typescript/src/utils.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/utils.js rename to tapestry-core/src/main/typescript/src/utils.ts diff --git a/tapestry-core/src/main/typescript/src/validation.js b/tapestry-core/src/main/typescript/src/validation.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/validation.js rename to tapestry-core/src/main/typescript/src/validation.ts diff --git a/tapestry-core/src/main/typescript/src/zone-refresh.js b/tapestry-core/src/main/typescript/src/zone-refresh.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/zone-refresh.js rename to tapestry-core/src/main/typescript/src/zone-refresh.ts diff --git a/tapestry-core/src/main/typescript/src/zone.js b/tapestry-core/src/main/typescript/src/zone.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/zone.js rename to tapestry-core/src/main/typescript/src/zone.ts From 353b4f0c60113a1ec2626a29058d38f5021008f4 Mon Sep 17 00:00:00 2001 From: "Thiago H. de Paula Figueiredo" Date: Fri, 4 Apr 2025 15:21:31 -0300 Subject: [PATCH 06/69] TAP5-2804: moving TypeScript files to correct folder --- tapestry-core/src/main/typescript/src/{ => t5/core}/ajax.ts | 0 .../src/main/typescript/src/{ => t5/core}/ajaxformloop.ts | 0 tapestry-core/src/main/typescript/src/{ => t5/core}/alert.ts | 0 .../src/main/typescript/src/{ => t5/core}/autocomplete.ts | 0 tapestry-core/src/main/typescript/src/{ => t5/core}/bootstrap.ts | 0 .../src/main/typescript/src/{ => t5/core}/confirm-click.ts | 0 tapestry-core/src/main/typescript/src/{ => t5/core}/console.ts | 0 tapestry-core/src/main/typescript/src/{ => t5/core}/datefield.ts | 0 tapestry-core/src/main/typescript/src/{ => t5/core}/events.ts | 0 .../src/main/typescript/src/{ => t5/core}/exception-display.ts | 0 .../src/main/typescript/src/{ => t5/core}/exception-frame.ts | 0 tapestry-core/src/main/typescript/src/{ => t5/core}/fields.ts | 0 .../src/main/typescript/src/{ => t5/core}/form-fragment.ts | 0 tapestry-core/src/main/typescript/src/{ => t5/core}/forms.ts | 0 tapestry-core/src/main/typescript/src/{ => t5/core}/graphviz.ts | 0 tapestry-core/src/main/typescript/src/{ => t5/core}/init.ts | 0 tapestry-core/src/main/typescript/src/{ => t5/core}/localdate.ts | 0 tapestry-core/src/main/typescript/src/{ => t5/core}/messages.ts | 0 tapestry-core/src/main/typescript/src/{ => t5/core}/moment.ts | 0 tapestry-core/src/main/typescript/src/{ => t5/core}/pageinit.ts | 0 tapestry-core/src/main/typescript/src/{ => t5/core}/palette.ts | 0 tapestry-core/src/main/typescript/src/{ => t5/core}/select.ts | 0 .../src/main/typescript/src/{ => t5/core}/time-interval.ts | 0 tapestry-core/src/main/typescript/src/{ => t5/core}/tree.ts | 0 tapestry-core/src/main/typescript/src/{ => t5/core}/utils.ts | 0 tapestry-core/src/main/typescript/src/{ => t5/core}/validation.ts | 0 .../src/main/typescript/src/{ => t5/core}/zone-refresh.ts | 0 tapestry-core/src/main/typescript/src/{ => t5/core}/zone.ts | 0 28 files changed, 0 insertions(+), 0 deletions(-) rename tapestry-core/src/main/typescript/src/{ => t5/core}/ajax.ts (100%) rename tapestry-core/src/main/typescript/src/{ => t5/core}/ajaxformloop.ts (100%) rename tapestry-core/src/main/typescript/src/{ => t5/core}/alert.ts (100%) rename tapestry-core/src/main/typescript/src/{ => t5/core}/autocomplete.ts (100%) rename tapestry-core/src/main/typescript/src/{ => t5/core}/bootstrap.ts (100%) rename tapestry-core/src/main/typescript/src/{ => t5/core}/confirm-click.ts (100%) rename tapestry-core/src/main/typescript/src/{ => t5/core}/console.ts (100%) rename tapestry-core/src/main/typescript/src/{ => t5/core}/datefield.ts (100%) rename tapestry-core/src/main/typescript/src/{ => t5/core}/events.ts (100%) rename tapestry-core/src/main/typescript/src/{ => t5/core}/exception-display.ts (100%) rename tapestry-core/src/main/typescript/src/{ => t5/core}/exception-frame.ts (100%) rename tapestry-core/src/main/typescript/src/{ => t5/core}/fields.ts (100%) rename tapestry-core/src/main/typescript/src/{ => t5/core}/form-fragment.ts (100%) rename tapestry-core/src/main/typescript/src/{ => t5/core}/forms.ts (100%) rename tapestry-core/src/main/typescript/src/{ => t5/core}/graphviz.ts (100%) rename tapestry-core/src/main/typescript/src/{ => t5/core}/init.ts (100%) rename tapestry-core/src/main/typescript/src/{ => t5/core}/localdate.ts (100%) rename tapestry-core/src/main/typescript/src/{ => t5/core}/messages.ts (100%) rename tapestry-core/src/main/typescript/src/{ => t5/core}/moment.ts (100%) rename tapestry-core/src/main/typescript/src/{ => t5/core}/pageinit.ts (100%) rename tapestry-core/src/main/typescript/src/{ => t5/core}/palette.ts (100%) rename tapestry-core/src/main/typescript/src/{ => t5/core}/select.ts (100%) rename tapestry-core/src/main/typescript/src/{ => t5/core}/time-interval.ts (100%) rename tapestry-core/src/main/typescript/src/{ => t5/core}/tree.ts (100%) rename tapestry-core/src/main/typescript/src/{ => t5/core}/utils.ts (100%) rename tapestry-core/src/main/typescript/src/{ => t5/core}/validation.ts (100%) rename tapestry-core/src/main/typescript/src/{ => t5/core}/zone-refresh.ts (100%) rename tapestry-core/src/main/typescript/src/{ => t5/core}/zone.ts (100%) diff --git a/tapestry-core/src/main/typescript/src/ajax.ts b/tapestry-core/src/main/typescript/src/t5/core/ajax.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/ajax.ts rename to tapestry-core/src/main/typescript/src/t5/core/ajax.ts diff --git a/tapestry-core/src/main/typescript/src/ajaxformloop.ts b/tapestry-core/src/main/typescript/src/t5/core/ajaxformloop.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/ajaxformloop.ts rename to tapestry-core/src/main/typescript/src/t5/core/ajaxformloop.ts diff --git a/tapestry-core/src/main/typescript/src/alert.ts b/tapestry-core/src/main/typescript/src/t5/core/alert.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/alert.ts rename to tapestry-core/src/main/typescript/src/t5/core/alert.ts diff --git a/tapestry-core/src/main/typescript/src/autocomplete.ts b/tapestry-core/src/main/typescript/src/t5/core/autocomplete.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/autocomplete.ts rename to tapestry-core/src/main/typescript/src/t5/core/autocomplete.ts diff --git a/tapestry-core/src/main/typescript/src/bootstrap.ts b/tapestry-core/src/main/typescript/src/t5/core/bootstrap.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/bootstrap.ts rename to tapestry-core/src/main/typescript/src/t5/core/bootstrap.ts diff --git a/tapestry-core/src/main/typescript/src/confirm-click.ts b/tapestry-core/src/main/typescript/src/t5/core/confirm-click.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/confirm-click.ts rename to tapestry-core/src/main/typescript/src/t5/core/confirm-click.ts diff --git a/tapestry-core/src/main/typescript/src/console.ts b/tapestry-core/src/main/typescript/src/t5/core/console.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/console.ts rename to tapestry-core/src/main/typescript/src/t5/core/console.ts diff --git a/tapestry-core/src/main/typescript/src/datefield.ts b/tapestry-core/src/main/typescript/src/t5/core/datefield.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/datefield.ts rename to tapestry-core/src/main/typescript/src/t5/core/datefield.ts diff --git a/tapestry-core/src/main/typescript/src/events.ts b/tapestry-core/src/main/typescript/src/t5/core/events.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/events.ts rename to tapestry-core/src/main/typescript/src/t5/core/events.ts diff --git a/tapestry-core/src/main/typescript/src/exception-display.ts b/tapestry-core/src/main/typescript/src/t5/core/exception-display.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/exception-display.ts rename to tapestry-core/src/main/typescript/src/t5/core/exception-display.ts diff --git a/tapestry-core/src/main/typescript/src/exception-frame.ts b/tapestry-core/src/main/typescript/src/t5/core/exception-frame.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/exception-frame.ts rename to tapestry-core/src/main/typescript/src/t5/core/exception-frame.ts diff --git a/tapestry-core/src/main/typescript/src/fields.ts b/tapestry-core/src/main/typescript/src/t5/core/fields.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/fields.ts rename to tapestry-core/src/main/typescript/src/t5/core/fields.ts diff --git a/tapestry-core/src/main/typescript/src/form-fragment.ts b/tapestry-core/src/main/typescript/src/t5/core/form-fragment.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/form-fragment.ts rename to tapestry-core/src/main/typescript/src/t5/core/form-fragment.ts diff --git a/tapestry-core/src/main/typescript/src/forms.ts b/tapestry-core/src/main/typescript/src/t5/core/forms.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/forms.ts rename to tapestry-core/src/main/typescript/src/t5/core/forms.ts diff --git a/tapestry-core/src/main/typescript/src/graphviz.ts b/tapestry-core/src/main/typescript/src/t5/core/graphviz.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/graphviz.ts rename to tapestry-core/src/main/typescript/src/t5/core/graphviz.ts diff --git a/tapestry-core/src/main/typescript/src/init.ts b/tapestry-core/src/main/typescript/src/t5/core/init.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/init.ts rename to tapestry-core/src/main/typescript/src/t5/core/init.ts diff --git a/tapestry-core/src/main/typescript/src/localdate.ts b/tapestry-core/src/main/typescript/src/t5/core/localdate.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/localdate.ts rename to tapestry-core/src/main/typescript/src/t5/core/localdate.ts diff --git a/tapestry-core/src/main/typescript/src/messages.ts b/tapestry-core/src/main/typescript/src/t5/core/messages.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/messages.ts rename to tapestry-core/src/main/typescript/src/t5/core/messages.ts diff --git a/tapestry-core/src/main/typescript/src/moment.ts b/tapestry-core/src/main/typescript/src/t5/core/moment.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/moment.ts rename to tapestry-core/src/main/typescript/src/t5/core/moment.ts diff --git a/tapestry-core/src/main/typescript/src/pageinit.ts b/tapestry-core/src/main/typescript/src/t5/core/pageinit.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/pageinit.ts rename to tapestry-core/src/main/typescript/src/t5/core/pageinit.ts diff --git a/tapestry-core/src/main/typescript/src/palette.ts b/tapestry-core/src/main/typescript/src/t5/core/palette.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/palette.ts rename to tapestry-core/src/main/typescript/src/t5/core/palette.ts diff --git a/tapestry-core/src/main/typescript/src/select.ts b/tapestry-core/src/main/typescript/src/t5/core/select.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/select.ts rename to tapestry-core/src/main/typescript/src/t5/core/select.ts diff --git a/tapestry-core/src/main/typescript/src/time-interval.ts b/tapestry-core/src/main/typescript/src/t5/core/time-interval.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/time-interval.ts rename to tapestry-core/src/main/typescript/src/t5/core/time-interval.ts diff --git a/tapestry-core/src/main/typescript/src/tree.ts b/tapestry-core/src/main/typescript/src/t5/core/tree.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/tree.ts rename to tapestry-core/src/main/typescript/src/t5/core/tree.ts diff --git a/tapestry-core/src/main/typescript/src/utils.ts b/tapestry-core/src/main/typescript/src/t5/core/utils.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/utils.ts rename to tapestry-core/src/main/typescript/src/t5/core/utils.ts diff --git a/tapestry-core/src/main/typescript/src/validation.ts b/tapestry-core/src/main/typescript/src/t5/core/validation.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/validation.ts rename to tapestry-core/src/main/typescript/src/t5/core/validation.ts diff --git a/tapestry-core/src/main/typescript/src/zone-refresh.ts b/tapestry-core/src/main/typescript/src/t5/core/zone-refresh.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/zone-refresh.ts rename to tapestry-core/src/main/typescript/src/t5/core/zone-refresh.ts diff --git a/tapestry-core/src/main/typescript/src/zone.ts b/tapestry-core/src/main/typescript/src/t5/core/zone.ts similarity index 100% rename from tapestry-core/src/main/typescript/src/zone.ts rename to tapestry-core/src/main/typescript/src/t5/core/zone.ts From 3ca15b6f1279b5c1160f80a556f7e357243c4afc Mon Sep 17 00:00:00 2001 From: "Thiago H. de Paula Figueiredo" Date: Fri, 4 Apr 2025 19:24:10 -0300 Subject: [PATCH 07/69] TAP5-2805: Upgrade underscore.js to 1.36.7 --- .../tapestry5/modules/JavaScriptModule.java | 2 +- .../assets/tapestry5/underscore-1.13.6.js | 2042 ----------------- .../assets/tapestry5/underscore-1.13.7.js | 6 + 3 files changed, 7 insertions(+), 2043 deletions(-) delete mode 100644 tapestry-core/src/main/resources/META-INF/assets/tapestry5/underscore-1.13.6.js create mode 100644 tapestry-core/src/main/resources/META-INF/assets/tapestry5/underscore-1.13.7.js diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/modules/JavaScriptModule.java b/tapestry-core/src/main/java/org/apache/tapestry5/modules/JavaScriptModule.java index b452932f53..560ba0d127 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/modules/JavaScriptModule.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/modules/JavaScriptModule.java @@ -151,7 +151,7 @@ public static void setupCoreJavaScriptStack(OrderedConfiguration String provider) { configuration.add("requirejs", StackExtension.library(ROOT + "/require.js")); - configuration.add("underscore-library", StackExtension.library(ROOT + "/underscore-1.13.6.js")); + configuration.add("underscore-library", StackExtension.library(ROOT + "/underscore-1.13.7.js")); if (provider.equals("prototype")) { diff --git a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/underscore-1.13.6.js b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/underscore-1.13.6.js deleted file mode 100644 index c81e732e90..0000000000 --- a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/underscore-1.13.6.js +++ /dev/null @@ -1,2042 +0,0 @@ -(function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : - typeof define === 'function' && define.amd ? define('underscore', factory) : - (global = typeof globalThis !== 'undefined' ? globalThis : global || self, (function () { - var current = global._; - var exports = global._ = factory(); - exports.noConflict = function () { global._ = current; return exports; }; - }())); -}(this, (function () { - // Underscore.js 1.13.6 - // https://underscorejs.org - // (c) 2009-2022 Jeremy Ashkenas, Julian Gonggrijp, and DocumentCloud and Investigative Reporters & Editors - // Underscore may be freely distributed under the MIT license. - - // Current version. - var VERSION = '1.13.6'; - - // Establish the root object, `window` (`self`) in the browser, `global` - // on the server, or `this` in some virtual machines. We use `self` - // instead of `window` for `WebWorker` support. - var root = (typeof self == 'object' && self.self === self && self) || - (typeof global == 'object' && global.global === global && global) || - Function('return this')() || - {}; - - // Save bytes in the minified (but not gzipped) version: - var ArrayProto = Array.prototype, ObjProto = Object.prototype; - var SymbolProto = typeof Symbol !== 'undefined' ? Symbol.prototype : null; - - // Create quick reference variables for speed access to core prototypes. - var push = ArrayProto.push, - slice = ArrayProto.slice, - toString = ObjProto.toString, - hasOwnProperty = ObjProto.hasOwnProperty; - - // Modern feature detection. - var supportsArrayBuffer = typeof ArrayBuffer !== 'undefined', - supportsDataView = typeof DataView !== 'undefined'; - - // All **ECMAScript 5+** native function implementations that we hope to use - // are declared here. - var nativeIsArray = Array.isArray, - nativeKeys = Object.keys, - nativeCreate = Object.create, - nativeIsView = supportsArrayBuffer && ArrayBuffer.isView; - - // Create references to these builtin functions because we override them. - var _isNaN = isNaN, - _isFinite = isFinite; - - // Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed. - var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString'); - var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString', - 'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString']; - - // The largest integer that can be represented exactly. - var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1; - - // Some functions take a variable number of arguments, or a few expected - // arguments at the beginning and then a variable number of values to operate - // on. This helper accumulates all remaining arguments past the function’s - // argument length (or an explicit `startIndex`), into an array that becomes - // the last argument. Similar to ES6’s "rest parameter". - function restArguments(func, startIndex) { - startIndex = startIndex == null ? func.length - 1 : +startIndex; - return function() { - var length = Math.max(arguments.length - startIndex, 0), - rest = Array(length), - index = 0; - for (; index < length; index++) { - rest[index] = arguments[index + startIndex]; - } - switch (startIndex) { - case 0: return func.call(this, rest); - case 1: return func.call(this, arguments[0], rest); - case 2: return func.call(this, arguments[0], arguments[1], rest); - } - var args = Array(startIndex + 1); - for (index = 0; index < startIndex; index++) { - args[index] = arguments[index]; - } - args[startIndex] = rest; - return func.apply(this, args); - }; - } - - // Is a given variable an object? - function isObject(obj) { - var type = typeof obj; - return type === 'function' || (type === 'object' && !!obj); - } - - // Is a given value equal to null? - function isNull(obj) { - return obj === null; - } - - // Is a given variable undefined? - function isUndefined(obj) { - return obj === void 0; - } - - // Is a given value a boolean? - function isBoolean(obj) { - return obj === true || obj === false || toString.call(obj) === '[object Boolean]'; - } - - // Is a given value a DOM element? - function isElement(obj) { - return !!(obj && obj.nodeType === 1); - } - - // Internal function for creating a `toString`-based type tester. - function tagTester(name) { - var tag = '[object ' + name + ']'; - return function(obj) { - return toString.call(obj) === tag; - }; - } - - var isString = tagTester('String'); - - var isNumber = tagTester('Number'); - - var isDate = tagTester('Date'); - - var isRegExp = tagTester('RegExp'); - - var isError = tagTester('Error'); - - var isSymbol = tagTester('Symbol'); - - var isArrayBuffer = tagTester('ArrayBuffer'); - - var isFunction = tagTester('Function'); - - // Optimize `isFunction` if appropriate. Work around some `typeof` bugs in old - // v8, IE 11 (#1621), Safari 8 (#1929), and PhantomJS (#2236). - var nodelist = root.document && root.document.childNodes; - if (typeof /./ != 'function' && typeof Int8Array != 'object' && typeof nodelist != 'function') { - isFunction = function(obj) { - return typeof obj == 'function' || false; - }; - } - - var isFunction$1 = isFunction; - - var hasObjectTag = tagTester('Object'); - - // In IE 10 - Edge 13, `DataView` has string tag `'[object Object]'`. - // In IE 11, the most common among them, this problem also applies to - // `Map`, `WeakMap` and `Set`. - var hasStringTagBug = ( - supportsDataView && hasObjectTag(new DataView(new ArrayBuffer(8))) - ), - isIE11 = (typeof Map !== 'undefined' && hasObjectTag(new Map)); - - var isDataView = tagTester('DataView'); - - // In IE 10 - Edge 13, we need a different heuristic - // to determine whether an object is a `DataView`. - function ie10IsDataView(obj) { - return obj != null && isFunction$1(obj.getInt8) && isArrayBuffer(obj.buffer); - } - - var isDataView$1 = (hasStringTagBug ? ie10IsDataView : isDataView); - - // Is a given value an array? - // Delegates to ECMA5's native `Array.isArray`. - var isArray = nativeIsArray || tagTester('Array'); - - // Internal function to check whether `key` is an own property name of `obj`. - function has$1(obj, key) { - return obj != null && hasOwnProperty.call(obj, key); - } - - var isArguments = tagTester('Arguments'); - - // Define a fallback version of the method in browsers (ahem, IE < 9), where - // there isn't any inspectable "Arguments" type. - (function() { - if (!isArguments(arguments)) { - isArguments = function(obj) { - return has$1(obj, 'callee'); - }; - } - }()); - - var isArguments$1 = isArguments; - - // Is a given object a finite number? - function isFinite$1(obj) { - return !isSymbol(obj) && _isFinite(obj) && !isNaN(parseFloat(obj)); - } - - // Is the given value `NaN`? - function isNaN$1(obj) { - return isNumber(obj) && _isNaN(obj); - } - - // Predicate-generating function. Often useful outside of Underscore. - function constant(value) { - return function() { - return value; - }; - } - - // Common internal logic for `isArrayLike` and `isBufferLike`. - function createSizePropertyCheck(getSizeProperty) { - return function(collection) { - var sizeProperty = getSizeProperty(collection); - return typeof sizeProperty == 'number' && sizeProperty >= 0 && sizeProperty <= MAX_ARRAY_INDEX; - } - } - - // Internal helper to generate a function to obtain property `key` from `obj`. - function shallowProperty(key) { - return function(obj) { - return obj == null ? void 0 : obj[key]; - }; - } - - // Internal helper to obtain the `byteLength` property of an object. - var getByteLength = shallowProperty('byteLength'); - - // Internal helper to determine whether we should spend extensive checks against - // `ArrayBuffer` et al. - var isBufferLike = createSizePropertyCheck(getByteLength); - - // Is a given value a typed array? - var typedArrayPattern = /\[object ((I|Ui)nt(8|16|32)|Float(32|64)|Uint8Clamped|Big(I|Ui)nt64)Array\]/; - function isTypedArray(obj) { - // `ArrayBuffer.isView` is the most future-proof, so use it when available. - // Otherwise, fall back on the above regular expression. - return nativeIsView ? (nativeIsView(obj) && !isDataView$1(obj)) : - isBufferLike(obj) && typedArrayPattern.test(toString.call(obj)); - } - - var isTypedArray$1 = supportsArrayBuffer ? isTypedArray : constant(false); - - // Internal helper to obtain the `length` property of an object. - var getLength = shallowProperty('length'); - - // Internal helper to create a simple lookup structure. - // `collectNonEnumProps` used to depend on `_.contains`, but this led to - // circular imports. `emulatedSet` is a one-off solution that only works for - // arrays of strings. - function emulatedSet(keys) { - var hash = {}; - for (var l = keys.length, i = 0; i < l; ++i) hash[keys[i]] = true; - return { - contains: function(key) { return hash[key] === true; }, - push: function(key) { - hash[key] = true; - return keys.push(key); - } - }; - } - - // Internal helper. Checks `keys` for the presence of keys in IE < 9 that won't - // be iterated by `for key in ...` and thus missed. Extends `keys` in place if - // needed. - function collectNonEnumProps(obj, keys) { - keys = emulatedSet(keys); - var nonEnumIdx = nonEnumerableProps.length; - var constructor = obj.constructor; - var proto = (isFunction$1(constructor) && constructor.prototype) || ObjProto; - - // Constructor is a special case. - var prop = 'constructor'; - if (has$1(obj, prop) && !keys.contains(prop)) keys.push(prop); - - while (nonEnumIdx--) { - prop = nonEnumerableProps[nonEnumIdx]; - if (prop in obj && obj[prop] !== proto[prop] && !keys.contains(prop)) { - keys.push(prop); - } - } - } - - // Retrieve the names of an object's own properties. - // Delegates to **ECMAScript 5**'s native `Object.keys`. - function keys(obj) { - if (!isObject(obj)) return []; - if (nativeKeys) return nativeKeys(obj); - var keys = []; - for (var key in obj) if (has$1(obj, key)) keys.push(key); - // Ahem, IE < 9. - if (hasEnumBug) collectNonEnumProps(obj, keys); - return keys; - } - - // Is a given array, string, or object empty? - // An "empty" object has no enumerable own-properties. - function isEmpty(obj) { - if (obj == null) return true; - // Skip the more expensive `toString`-based type checks if `obj` has no - // `.length`. - var length = getLength(obj); - if (typeof length == 'number' && ( - isArray(obj) || isString(obj) || isArguments$1(obj) - )) return length === 0; - return getLength(keys(obj)) === 0; - } - - // Returns whether an object has a given set of `key:value` pairs. - function isMatch(object, attrs) { - var _keys = keys(attrs), length = _keys.length; - if (object == null) return !length; - var obj = Object(object); - for (var i = 0; i < length; i++) { - var key = _keys[i]; - if (attrs[key] !== obj[key] || !(key in obj)) return false; - } - return true; - } - - // If Underscore is called as a function, it returns a wrapped object that can - // be used OO-style. This wrapper holds altered versions of all functions added - // through `_.mixin`. Wrapped objects may be chained. - function _$1(obj) { - if (obj instanceof _$1) return obj; - if (!(this instanceof _$1)) return new _$1(obj); - this._wrapped = obj; - } - - _$1.VERSION = VERSION; - - // Extracts the result from a wrapped and chained object. - _$1.prototype.value = function() { - return this._wrapped; - }; - - // Provide unwrapping proxies for some methods used in engine operations - // such as arithmetic and JSON stringification. - _$1.prototype.valueOf = _$1.prototype.toJSON = _$1.prototype.value; - - _$1.prototype.toString = function() { - return String(this._wrapped); - }; - - // Internal function to wrap or shallow-copy an ArrayBuffer, - // typed array or DataView to a new view, reusing the buffer. - function toBufferView(bufferSource) { - return new Uint8Array( - bufferSource.buffer || bufferSource, - bufferSource.byteOffset || 0, - getByteLength(bufferSource) - ); - } - - // We use this string twice, so give it a name for minification. - var tagDataView = '[object DataView]'; - - // Internal recursive comparison function for `_.isEqual`. - function eq(a, b, aStack, bStack) { - // Identical objects are equal. `0 === -0`, but they aren't identical. - // See the [Harmony `egal` proposal](https://wiki.ecmascript.org/doku.php?id=harmony:egal). - if (a === b) return a !== 0 || 1 / a === 1 / b; - // `null` or `undefined` only equal to itself (strict comparison). - if (a == null || b == null) return false; - // `NaN`s are equivalent, but non-reflexive. - if (a !== a) return b !== b; - // Exhaust primitive checks - var type = typeof a; - if (type !== 'function' && type !== 'object' && typeof b != 'object') return false; - return deepEq(a, b, aStack, bStack); - } - - // Internal recursive comparison function for `_.isEqual`. - function deepEq(a, b, aStack, bStack) { - // Unwrap any wrapped objects. - if (a instanceof _$1) a = a._wrapped; - if (b instanceof _$1) b = b._wrapped; - // Compare `[[Class]]` names. - var className = toString.call(a); - if (className !== toString.call(b)) return false; - // Work around a bug in IE 10 - Edge 13. - if (hasStringTagBug && className == '[object Object]' && isDataView$1(a)) { - if (!isDataView$1(b)) return false; - className = tagDataView; - } - switch (className) { - // These types are compared by value. - case '[object RegExp]': - // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i') - case '[object String]': - // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is - // equivalent to `new String("5")`. - return '' + a === '' + b; - case '[object Number]': - // `NaN`s are equivalent, but non-reflexive. - // Object(NaN) is equivalent to NaN. - if (+a !== +a) return +b !== +b; - // An `egal` comparison is performed for other numeric values. - return +a === 0 ? 1 / +a === 1 / b : +a === +b; - case '[object Date]': - case '[object Boolean]': - // Coerce dates and booleans to numeric primitive values. Dates are compared by their - // millisecond representations. Note that invalid dates with millisecond representations - // of `NaN` are not equivalent. - return +a === +b; - case '[object Symbol]': - return SymbolProto.valueOf.call(a) === SymbolProto.valueOf.call(b); - case '[object ArrayBuffer]': - case tagDataView: - // Coerce to typed array so we can fall through. - return deepEq(toBufferView(a), toBufferView(b), aStack, bStack); - } - - var areArrays = className === '[object Array]'; - if (!areArrays && isTypedArray$1(a)) { - var byteLength = getByteLength(a); - if (byteLength !== getByteLength(b)) return false; - if (a.buffer === b.buffer && a.byteOffset === b.byteOffset) return true; - areArrays = true; - } - if (!areArrays) { - if (typeof a != 'object' || typeof b != 'object') return false; - - // Objects with different constructors are not equivalent, but `Object`s or `Array`s - // from different frames are. - var aCtor = a.constructor, bCtor = b.constructor; - if (aCtor !== bCtor && !(isFunction$1(aCtor) && aCtor instanceof aCtor && - isFunction$1(bCtor) && bCtor instanceof bCtor) - && ('constructor' in a && 'constructor' in b)) { - return false; - } - } - // Assume equality for cyclic structures. The algorithm for detecting cyclic - // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. - - // Initializing stack of traversed objects. - // It's done here since we only need them for objects and arrays comparison. - aStack = aStack || []; - bStack = bStack || []; - var length = aStack.length; - while (length--) { - // Linear search. Performance is inversely proportional to the number of - // unique nested structures. - if (aStack[length] === a) return bStack[length] === b; - } - - // Add the first object to the stack of traversed objects. - aStack.push(a); - bStack.push(b); - - // Recursively compare objects and arrays. - if (areArrays) { - // Compare array lengths to determine if a deep comparison is necessary. - length = a.length; - if (length !== b.length) return false; - // Deep compare the contents, ignoring non-numeric properties. - while (length--) { - if (!eq(a[length], b[length], aStack, bStack)) return false; - } - } else { - // Deep compare objects. - var _keys = keys(a), key; - length = _keys.length; - // Ensure that both objects contain the same number of properties before comparing deep equality. - if (keys(b).length !== length) return false; - while (length--) { - // Deep compare each member - key = _keys[length]; - if (!(has$1(b, key) && eq(a[key], b[key], aStack, bStack))) return false; - } - } - // Remove the first object from the stack of traversed objects. - aStack.pop(); - bStack.pop(); - return true; - } - - // Perform a deep comparison to check if two objects are equal. - function isEqual(a, b) { - return eq(a, b); - } - - // Retrieve all the enumerable property names of an object. - function allKeys(obj) { - if (!isObject(obj)) return []; - var keys = []; - for (var key in obj) keys.push(key); - // Ahem, IE < 9. - if (hasEnumBug) collectNonEnumProps(obj, keys); - return keys; - } - - // Since the regular `Object.prototype.toString` type tests don't work for - // some types in IE 11, we use a fingerprinting heuristic instead, based - // on the methods. It's not great, but it's the best we got. - // The fingerprint method lists are defined below. - function ie11fingerprint(methods) { - var length = getLength(methods); - return function(obj) { - if (obj == null) return false; - // `Map`, `WeakMap` and `Set` have no enumerable keys. - var keys = allKeys(obj); - if (getLength(keys)) return false; - for (var i = 0; i < length; i++) { - if (!isFunction$1(obj[methods[i]])) return false; - } - // If we are testing against `WeakMap`, we need to ensure that - // `obj` doesn't have a `forEach` method in order to distinguish - // it from a regular `Map`. - return methods !== weakMapMethods || !isFunction$1(obj[forEachName]); - }; - } - - // In the interest of compact minification, we write - // each string in the fingerprints only once. - var forEachName = 'forEach', - hasName = 'has', - commonInit = ['clear', 'delete'], - mapTail = ['get', hasName, 'set']; - - // `Map`, `WeakMap` and `Set` each have slightly different - // combinations of the above sublists. - var mapMethods = commonInit.concat(forEachName, mapTail), - weakMapMethods = commonInit.concat(mapTail), - setMethods = ['add'].concat(commonInit, forEachName, hasName); - - var isMap = isIE11 ? ie11fingerprint(mapMethods) : tagTester('Map'); - - var isWeakMap = isIE11 ? ie11fingerprint(weakMapMethods) : tagTester('WeakMap'); - - var isSet = isIE11 ? ie11fingerprint(setMethods) : tagTester('Set'); - - var isWeakSet = tagTester('WeakSet'); - - // Retrieve the values of an object's properties. - function values(obj) { - var _keys = keys(obj); - var length = _keys.length; - var values = Array(length); - for (var i = 0; i < length; i++) { - values[i] = obj[_keys[i]]; - } - return values; - } - - // Convert an object into a list of `[key, value]` pairs. - // The opposite of `_.object` with one argument. - function pairs(obj) { - var _keys = keys(obj); - var length = _keys.length; - var pairs = Array(length); - for (var i = 0; i < length; i++) { - pairs[i] = [_keys[i], obj[_keys[i]]]; - } - return pairs; - } - - // Invert the keys and values of an object. The values must be serializable. - function invert(obj) { - var result = {}; - var _keys = keys(obj); - for (var i = 0, length = _keys.length; i < length; i++) { - result[obj[_keys[i]]] = _keys[i]; - } - return result; - } - - // Return a sorted list of the function names available on the object. - function functions(obj) { - var names = []; - for (var key in obj) { - if (isFunction$1(obj[key])) names.push(key); - } - return names.sort(); - } - - // An internal function for creating assigner functions. - function createAssigner(keysFunc, defaults) { - return function(obj) { - var length = arguments.length; - if (defaults) obj = Object(obj); - if (length < 2 || obj == null) return obj; - for (var index = 1; index < length; index++) { - var source = arguments[index], - keys = keysFunc(source), - l = keys.length; - for (var i = 0; i < l; i++) { - var key = keys[i]; - if (!defaults || obj[key] === void 0) obj[key] = source[key]; - } - } - return obj; - }; - } - - // Extend a given object with all the properties in passed-in object(s). - var extend = createAssigner(allKeys); - - // Assigns a given object with all the own properties in the passed-in - // object(s). - // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) - var extendOwn = createAssigner(keys); - - // Fill in a given object with default properties. - var defaults = createAssigner(allKeys, true); - - // Create a naked function reference for surrogate-prototype-swapping. - function ctor() { - return function(){}; - } - - // An internal function for creating a new object that inherits from another. - function baseCreate(prototype) { - if (!isObject(prototype)) return {}; - if (nativeCreate) return nativeCreate(prototype); - var Ctor = ctor(); - Ctor.prototype = prototype; - var result = new Ctor; - Ctor.prototype = null; - return result; - } - - // Creates an object that inherits from the given prototype object. - // If additional properties are provided then they will be added to the - // created object. - function create(prototype, props) { - var result = baseCreate(prototype); - if (props) extendOwn(result, props); - return result; - } - - // Create a (shallow-cloned) duplicate of an object. - function clone(obj) { - if (!isObject(obj)) return obj; - return isArray(obj) ? obj.slice() : extend({}, obj); - } - - // Invokes `interceptor` with the `obj` and then returns `obj`. - // The primary purpose of this method is to "tap into" a method chain, in - // order to perform operations on intermediate results within the chain. - function tap(obj, interceptor) { - interceptor(obj); - return obj; - } - - // Normalize a (deep) property `path` to array. - // Like `_.iteratee`, this function can be customized. - function toPath$1(path) { - return isArray(path) ? path : [path]; - } - _$1.toPath = toPath$1; - - // Internal wrapper for `_.toPath` to enable minification. - // Similar to `cb` for `_.iteratee`. - function toPath(path) { - return _$1.toPath(path); - } - - // Internal function to obtain a nested property in `obj` along `path`. - function deepGet(obj, path) { - var length = path.length; - for (var i = 0; i < length; i++) { - if (obj == null) return void 0; - obj = obj[path[i]]; - } - return length ? obj : void 0; - } - - // Get the value of the (deep) property on `path` from `object`. - // If any property in `path` does not exist or if the value is - // `undefined`, return `defaultValue` instead. - // The `path` is normalized through `_.toPath`. - function get(object, path, defaultValue) { - var value = deepGet(object, toPath(path)); - return isUndefined(value) ? defaultValue : value; - } - - // Shortcut function for checking if an object has a given property directly on - // itself (in other words, not on a prototype). Unlike the internal `has` - // function, this public version can also traverse nested properties. - function has(obj, path) { - path = toPath(path); - var length = path.length; - for (var i = 0; i < length; i++) { - var key = path[i]; - if (!has$1(obj, key)) return false; - obj = obj[key]; - } - return !!length; - } - - // Keep the identity function around for default iteratees. - function identity(value) { - return value; - } - - // Returns a predicate for checking whether an object has a given set of - // `key:value` pairs. - function matcher(attrs) { - attrs = extendOwn({}, attrs); - return function(obj) { - return isMatch(obj, attrs); - }; - } - - // Creates a function that, when passed an object, will traverse that object’s - // properties down the given `path`, specified as an array of keys or indices. - function property(path) { - path = toPath(path); - return function(obj) { - return deepGet(obj, path); - }; - } - - // Internal function that returns an efficient (for current engines) version - // of the passed-in callback, to be repeatedly applied in other Underscore - // functions. - function optimizeCb(func, context, argCount) { - if (context === void 0) return func; - switch (argCount == null ? 3 : argCount) { - case 1: return function(value) { - return func.call(context, value); - }; - // The 2-argument case is omitted because we’re not using it. - case 3: return function(value, index, collection) { - return func.call(context, value, index, collection); - }; - case 4: return function(accumulator, value, index, collection) { - return func.call(context, accumulator, value, index, collection); - }; - } - return function() { - return func.apply(context, arguments); - }; - } - - // An internal function to generate callbacks that can be applied to each - // element in a collection, returning the desired result — either `_.identity`, - // an arbitrary callback, a property matcher, or a property accessor. - function baseIteratee(value, context, argCount) { - if (value == null) return identity; - if (isFunction$1(value)) return optimizeCb(value, context, argCount); - if (isObject(value) && !isArray(value)) return matcher(value); - return property(value); - } - - // External wrapper for our callback generator. Users may customize - // `_.iteratee` if they want additional predicate/iteratee shorthand styles. - // This abstraction hides the internal-only `argCount` argument. - function iteratee(value, context) { - return baseIteratee(value, context, Infinity); - } - _$1.iteratee = iteratee; - - // The function we call internally to generate a callback. It invokes - // `_.iteratee` if overridden, otherwise `baseIteratee`. - function cb(value, context, argCount) { - if (_$1.iteratee !== iteratee) return _$1.iteratee(value, context); - return baseIteratee(value, context, argCount); - } - - // Returns the results of applying the `iteratee` to each element of `obj`. - // In contrast to `_.map` it returns an object. - function mapObject(obj, iteratee, context) { - iteratee = cb(iteratee, context); - var _keys = keys(obj), - length = _keys.length, - results = {}; - for (var index = 0; index < length; index++) { - var currentKey = _keys[index]; - results[currentKey] = iteratee(obj[currentKey], currentKey, obj); - } - return results; - } - - // Predicate-generating function. Often useful outside of Underscore. - function noop(){} - - // Generates a function for a given object that returns a given property. - function propertyOf(obj) { - if (obj == null) return noop; - return function(path) { - return get(obj, path); - }; - } - - // Run a function **n** times. - function times(n, iteratee, context) { - var accum = Array(Math.max(0, n)); - iteratee = optimizeCb(iteratee, context, 1); - for (var i = 0; i < n; i++) accum[i] = iteratee(i); - return accum; - } - - // Return a random integer between `min` and `max` (inclusive). - function random(min, max) { - if (max == null) { - max = min; - min = 0; - } - return min + Math.floor(Math.random() * (max - min + 1)); - } - - // A (possibly faster) way to get the current timestamp as an integer. - var now = Date.now || function() { - return new Date().getTime(); - }; - - // Internal helper to generate functions for escaping and unescaping strings - // to/from HTML interpolation. - function createEscaper(map) { - var escaper = function(match) { - return map[match]; - }; - // Regexes for identifying a key that needs to be escaped. - var source = '(?:' + keys(map).join('|') + ')'; - var testRegexp = RegExp(source); - var replaceRegexp = RegExp(source, 'g'); - return function(string) { - string = string == null ? '' : '' + string; - return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string; - }; - } - - // Internal list of HTML entities for escaping. - var escapeMap = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''', - '`': '`' - }; - - // Function for escaping strings to HTML interpolation. - var _escape = createEscaper(escapeMap); - - // Internal list of HTML entities for unescaping. - var unescapeMap = invert(escapeMap); - - // Function for unescaping strings from HTML interpolation. - var _unescape = createEscaper(unescapeMap); - - // By default, Underscore uses ERB-style template delimiters. Change the - // following template settings to use alternative delimiters. - var templateSettings = _$1.templateSettings = { - evaluate: /<%([\s\S]+?)%>/g, - interpolate: /<%=([\s\S]+?)%>/g, - escape: /<%-([\s\S]+?)%>/g - }; - - // When customizing `_.templateSettings`, if you don't want to define an - // interpolation, evaluation or escaping regex, we need one that is - // guaranteed not to match. - var noMatch = /(.)^/; - - // Certain characters need to be escaped so that they can be put into a - // string literal. - var escapes = { - "'": "'", - '\\': '\\', - '\r': 'r', - '\n': 'n', - '\u2028': 'u2028', - '\u2029': 'u2029' - }; - - var escapeRegExp = /\\|'|\r|\n|\u2028|\u2029/g; - - function escapeChar(match) { - return '\\' + escapes[match]; - } - - // In order to prevent third-party code injection through - // `_.templateSettings.variable`, we test it against the following regular - // expression. It is intentionally a bit more liberal than just matching valid - // identifiers, but still prevents possible loopholes through defaults or - // destructuring assignment. - var bareIdentifier = /^\s*(\w|\$)+\s*$/; - - // JavaScript micro-templating, similar to John Resig's implementation. - // Underscore templating handles arbitrary delimiters, preserves whitespace, - // and correctly escapes quotes within interpolated code. - // NB: `oldSettings` only exists for backwards compatibility. - function template(text, settings, oldSettings) { - if (!settings && oldSettings) settings = oldSettings; - settings = defaults({}, settings, _$1.templateSettings); - - // Combine delimiters into one regular expression via alternation. - var matcher = RegExp([ - (settings.escape || noMatch).source, - (settings.interpolate || noMatch).source, - (settings.evaluate || noMatch).source - ].join('|') + '|$', 'g'); - - // Compile the template source, escaping string literals appropriately. - var index = 0; - var source = "__p+='"; - text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { - source += text.slice(index, offset).replace(escapeRegExp, escapeChar); - index = offset + match.length; - - if (escape) { - source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; - } else if (interpolate) { - source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; - } else if (evaluate) { - source += "';\n" + evaluate + "\n__p+='"; - } - - // Adobe VMs need the match returned to produce the correct offset. - return match; - }); - source += "';\n"; - - var argument = settings.variable; - if (argument) { - // Insure against third-party code injection. (CVE-2021-23358) - if (!bareIdentifier.test(argument)) throw new Error( - 'variable is not a bare identifier: ' + argument - ); - } else { - // If a variable is not specified, place data values in local scope. - source = 'with(obj||{}){\n' + source + '}\n'; - argument = 'obj'; - } - - source = "var __t,__p='',__j=Array.prototype.join," + - "print=function(){__p+=__j.call(arguments,'');};\n" + - source + 'return __p;\n'; - - var render; - try { - render = new Function(argument, '_', source); - } catch (e) { - e.source = source; - throw e; - } - - var template = function(data) { - return render.call(this, data, _$1); - }; - - // Provide the compiled source as a convenience for precompilation. - template.source = 'function(' + argument + '){\n' + source + '}'; - - return template; - } - - // Traverses the children of `obj` along `path`. If a child is a function, it - // is invoked with its parent as context. Returns the value of the final - // child, or `fallback` if any child is undefined. - function result(obj, path, fallback) { - path = toPath(path); - var length = path.length; - if (!length) { - return isFunction$1(fallback) ? fallback.call(obj) : fallback; - } - for (var i = 0; i < length; i++) { - var prop = obj == null ? void 0 : obj[path[i]]; - if (prop === void 0) { - prop = fallback; - i = length; // Ensure we don't continue iterating. - } - obj = isFunction$1(prop) ? prop.call(obj) : prop; - } - return obj; - } - - // Generate a unique integer id (unique within the entire client session). - // Useful for temporary DOM ids. - var idCounter = 0; - function uniqueId(prefix) { - var id = ++idCounter + ''; - return prefix ? prefix + id : id; - } - - // Start chaining a wrapped Underscore object. - function chain(obj) { - var instance = _$1(obj); - instance._chain = true; - return instance; - } - - // Internal function to execute `sourceFunc` bound to `context` with optional - // `args`. Determines whether to execute a function as a constructor or as a - // normal function. - function executeBound(sourceFunc, boundFunc, context, callingContext, args) { - if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args); - var self = baseCreate(sourceFunc.prototype); - var result = sourceFunc.apply(self, args); - if (isObject(result)) return result; - return self; - } - - // Partially apply a function by creating a version that has had some of its - // arguments pre-filled, without changing its dynamic `this` context. `_` acts - // as a placeholder by default, allowing any combination of arguments to be - // pre-filled. Set `_.partial.placeholder` for a custom placeholder argument. - var partial = restArguments(function(func, boundArgs) { - var placeholder = partial.placeholder; - var bound = function() { - var position = 0, length = boundArgs.length; - var args = Array(length); - for (var i = 0; i < length; i++) { - args[i] = boundArgs[i] === placeholder ? arguments[position++] : boundArgs[i]; - } - while (position < arguments.length) args.push(arguments[position++]); - return executeBound(func, bound, this, this, args); - }; - return bound; - }); - - partial.placeholder = _$1; - - // Create a function bound to a given object (assigning `this`, and arguments, - // optionally). - var bind = restArguments(function(func, context, args) { - if (!isFunction$1(func)) throw new TypeError('Bind must be called on a function'); - var bound = restArguments(function(callArgs) { - return executeBound(func, bound, context, this, args.concat(callArgs)); - }); - return bound; - }); - - // Internal helper for collection methods to determine whether a collection - // should be iterated as an array or as an object. - // Related: https://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength - // Avoids a very nasty iOS 8 JIT bug on ARM-64. #2094 - var isArrayLike = createSizePropertyCheck(getLength); - - // Internal implementation of a recursive `flatten` function. - function flatten$1(input, depth, strict, output) { - output = output || []; - if (!depth && depth !== 0) { - depth = Infinity; - } else if (depth <= 0) { - return output.concat(input); - } - var idx = output.length; - for (var i = 0, length = getLength(input); i < length; i++) { - var value = input[i]; - if (isArrayLike(value) && (isArray(value) || isArguments$1(value))) { - // Flatten current level of array or arguments object. - if (depth > 1) { - flatten$1(value, depth - 1, strict, output); - idx = output.length; - } else { - var j = 0, len = value.length; - while (j < len) output[idx++] = value[j++]; - } - } else if (!strict) { - output[idx++] = value; - } - } - return output; - } - - // Bind a number of an object's methods to that object. Remaining arguments - // are the method names to be bound. Useful for ensuring that all callbacks - // defined on an object belong to it. - var bindAll = restArguments(function(obj, keys) { - keys = flatten$1(keys, false, false); - var index = keys.length; - if (index < 1) throw new Error('bindAll must be passed function names'); - while (index--) { - var key = keys[index]; - obj[key] = bind(obj[key], obj); - } - return obj; - }); - - // Memoize an expensive function by storing its results. - function memoize(func, hasher) { - var memoize = function(key) { - var cache = memoize.cache; - var address = '' + (hasher ? hasher.apply(this, arguments) : key); - if (!has$1(cache, address)) cache[address] = func.apply(this, arguments); - return cache[address]; - }; - memoize.cache = {}; - return memoize; - } - - // Delays a function for the given number of milliseconds, and then calls - // it with the arguments supplied. - var delay = restArguments(function(func, wait, args) { - return setTimeout(function() { - return func.apply(null, args); - }, wait); - }); - - // Defers a function, scheduling it to run after the current call stack has - // cleared. - var defer = partial(delay, _$1, 1); - - // Returns a function, that, when invoked, will only be triggered at most once - // during a given window of time. Normally, the throttled function will run - // as much as it can, without ever going more than once per `wait` duration; - // but if you'd like to disable the execution on the leading edge, pass - // `{leading: false}`. To disable execution on the trailing edge, ditto. - function throttle(func, wait, options) { - var timeout, context, args, result; - var previous = 0; - if (!options) options = {}; - - var later = function() { - previous = options.leading === false ? 0 : now(); - timeout = null; - result = func.apply(context, args); - if (!timeout) context = args = null; - }; - - var throttled = function() { - var _now = now(); - if (!previous && options.leading === false) previous = _now; - var remaining = wait - (_now - previous); - context = this; - args = arguments; - if (remaining <= 0 || remaining > wait) { - if (timeout) { - clearTimeout(timeout); - timeout = null; - } - previous = _now; - result = func.apply(context, args); - if (!timeout) context = args = null; - } else if (!timeout && options.trailing !== false) { - timeout = setTimeout(later, remaining); - } - return result; - }; - - throttled.cancel = function() { - clearTimeout(timeout); - previous = 0; - timeout = context = args = null; - }; - - return throttled; - } - - // When a sequence of calls of the returned function ends, the argument - // function is triggered. The end of a sequence is defined by the `wait` - // parameter. If `immediate` is passed, the argument function will be - // triggered at the beginning of the sequence instead of at the end. - function debounce(func, wait, immediate) { - var timeout, previous, args, result, context; - - var later = function() { - var passed = now() - previous; - if (wait > passed) { - timeout = setTimeout(later, wait - passed); - } else { - timeout = null; - if (!immediate) result = func.apply(context, args); - // This check is needed because `func` can recursively invoke `debounced`. - if (!timeout) args = context = null; - } - }; - - var debounced = restArguments(function(_args) { - context = this; - args = _args; - previous = now(); - if (!timeout) { - timeout = setTimeout(later, wait); - if (immediate) result = func.apply(context, args); - } - return result; - }); - - debounced.cancel = function() { - clearTimeout(timeout); - timeout = args = context = null; - }; - - return debounced; - } - - // Returns the first function passed as an argument to the second, - // allowing you to adjust arguments, run code before and after, and - // conditionally execute the original function. - function wrap(func, wrapper) { - return partial(wrapper, func); - } - - // Returns a negated version of the passed-in predicate. - function negate(predicate) { - return function() { - return !predicate.apply(this, arguments); - }; - } - - // Returns a function that is the composition of a list of functions, each - // consuming the return value of the function that follows. - function compose() { - var args = arguments; - var start = args.length - 1; - return function() { - var i = start; - var result = args[start].apply(this, arguments); - while (i--) result = args[i].call(this, result); - return result; - }; - } - - // Returns a function that will only be executed on and after the Nth call. - function after(times, func) { - return function() { - if (--times < 1) { - return func.apply(this, arguments); - } - }; - } - - // Returns a function that will only be executed up to (but not including) the - // Nth call. - function before(times, func) { - var memo; - return function() { - if (--times > 0) { - memo = func.apply(this, arguments); - } - if (times <= 1) func = null; - return memo; - }; - } - - // Returns a function that will be executed at most one time, no matter how - // often you call it. Useful for lazy initialization. - var once = partial(before, 2); - - // Returns the first key on an object that passes a truth test. - function findKey(obj, predicate, context) { - predicate = cb(predicate, context); - var _keys = keys(obj), key; - for (var i = 0, length = _keys.length; i < length; i++) { - key = _keys[i]; - if (predicate(obj[key], key, obj)) return key; - } - } - - // Internal function to generate `_.findIndex` and `_.findLastIndex`. - function createPredicateIndexFinder(dir) { - return function(array, predicate, context) { - predicate = cb(predicate, context); - var length = getLength(array); - var index = dir > 0 ? 0 : length - 1; - for (; index >= 0 && index < length; index += dir) { - if (predicate(array[index], index, array)) return index; - } - return -1; - }; - } - - // Returns the first index on an array-like that passes a truth test. - var findIndex = createPredicateIndexFinder(1); - - // Returns the last index on an array-like that passes a truth test. - var findLastIndex = createPredicateIndexFinder(-1); - - // Use a comparator function to figure out the smallest index at which - // an object should be inserted so as to maintain order. Uses binary search. - function sortedIndex(array, obj, iteratee, context) { - iteratee = cb(iteratee, context, 1); - var value = iteratee(obj); - var low = 0, high = getLength(array); - while (low < high) { - var mid = Math.floor((low + high) / 2); - if (iteratee(array[mid]) < value) low = mid + 1; else high = mid; - } - return low; - } - - // Internal function to generate the `_.indexOf` and `_.lastIndexOf` functions. - function createIndexFinder(dir, predicateFind, sortedIndex) { - return function(array, item, idx) { - var i = 0, length = getLength(array); - if (typeof idx == 'number') { - if (dir > 0) { - i = idx >= 0 ? idx : Math.max(idx + length, i); - } else { - length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1; - } - } else if (sortedIndex && idx && length) { - idx = sortedIndex(array, item); - return array[idx] === item ? idx : -1; - } - if (item !== item) { - idx = predicateFind(slice.call(array, i, length), isNaN$1); - return idx >= 0 ? idx + i : -1; - } - for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) { - if (array[idx] === item) return idx; - } - return -1; - }; - } - - // Return the position of the first occurrence of an item in an array, - // or -1 if the item is not included in the array. - // If the array is large and already in sort order, pass `true` - // for **isSorted** to use binary search. - var indexOf = createIndexFinder(1, findIndex, sortedIndex); - - // Return the position of the last occurrence of an item in an array, - // or -1 if the item is not included in the array. - var lastIndexOf = createIndexFinder(-1, findLastIndex); - - // Return the first value which passes a truth test. - function find(obj, predicate, context) { - var keyFinder = isArrayLike(obj) ? findIndex : findKey; - var key = keyFinder(obj, predicate, context); - if (key !== void 0 && key !== -1) return obj[key]; - } - - // Convenience version of a common use case of `_.find`: getting the first - // object containing specific `key:value` pairs. - function findWhere(obj, attrs) { - return find(obj, matcher(attrs)); - } - - // The cornerstone for collection functions, an `each` - // implementation, aka `forEach`. - // Handles raw objects in addition to array-likes. Treats all - // sparse array-likes as if they were dense. - function each(obj, iteratee, context) { - iteratee = optimizeCb(iteratee, context); - var i, length; - if (isArrayLike(obj)) { - for (i = 0, length = obj.length; i < length; i++) { - iteratee(obj[i], i, obj); - } - } else { - var _keys = keys(obj); - for (i = 0, length = _keys.length; i < length; i++) { - iteratee(obj[_keys[i]], _keys[i], obj); - } - } - return obj; - } - - // Return the results of applying the iteratee to each element. - function map(obj, iteratee, context) { - iteratee = cb(iteratee, context); - var _keys = !isArrayLike(obj) && keys(obj), - length = (_keys || obj).length, - results = Array(length); - for (var index = 0; index < length; index++) { - var currentKey = _keys ? _keys[index] : index; - results[index] = iteratee(obj[currentKey], currentKey, obj); - } - return results; - } - - // Internal helper to create a reducing function, iterating left or right. - function createReduce(dir) { - // Wrap code that reassigns argument variables in a separate function than - // the one that accesses `arguments.length` to avoid a perf hit. (#1991) - var reducer = function(obj, iteratee, memo, initial) { - var _keys = !isArrayLike(obj) && keys(obj), - length = (_keys || obj).length, - index = dir > 0 ? 0 : length - 1; - if (!initial) { - memo = obj[_keys ? _keys[index] : index]; - index += dir; - } - for (; index >= 0 && index < length; index += dir) { - var currentKey = _keys ? _keys[index] : index; - memo = iteratee(memo, obj[currentKey], currentKey, obj); - } - return memo; - }; - - return function(obj, iteratee, memo, context) { - var initial = arguments.length >= 3; - return reducer(obj, optimizeCb(iteratee, context, 4), memo, initial); - }; - } - - // **Reduce** builds up a single result from a list of values, aka `inject`, - // or `foldl`. - var reduce = createReduce(1); - - // The right-associative version of reduce, also known as `foldr`. - var reduceRight = createReduce(-1); - - // Return all the elements that pass a truth test. - function filter(obj, predicate, context) { - var results = []; - predicate = cb(predicate, context); - each(obj, function(value, index, list) { - if (predicate(value, index, list)) results.push(value); - }); - return results; - } - - // Return all the elements for which a truth test fails. - function reject(obj, predicate, context) { - return filter(obj, negate(cb(predicate)), context); - } - - // Determine whether all of the elements pass a truth test. - function every(obj, predicate, context) { - predicate = cb(predicate, context); - var _keys = !isArrayLike(obj) && keys(obj), - length = (_keys || obj).length; - for (var index = 0; index < length; index++) { - var currentKey = _keys ? _keys[index] : index; - if (!predicate(obj[currentKey], currentKey, obj)) return false; - } - return true; - } - - // Determine if at least one element in the object passes a truth test. - function some(obj, predicate, context) { - predicate = cb(predicate, context); - var _keys = !isArrayLike(obj) && keys(obj), - length = (_keys || obj).length; - for (var index = 0; index < length; index++) { - var currentKey = _keys ? _keys[index] : index; - if (predicate(obj[currentKey], currentKey, obj)) return true; - } - return false; - } - - // Determine if the array or object contains a given item (using `===`). - function contains(obj, item, fromIndex, guard) { - if (!isArrayLike(obj)) obj = values(obj); - if (typeof fromIndex != 'number' || guard) fromIndex = 0; - return indexOf(obj, item, fromIndex) >= 0; - } - - // Invoke a method (with arguments) on every item in a collection. - var invoke = restArguments(function(obj, path, args) { - var contextPath, func; - if (isFunction$1(path)) { - func = path; - } else { - path = toPath(path); - contextPath = path.slice(0, -1); - path = path[path.length - 1]; - } - return map(obj, function(context) { - var method = func; - if (!method) { - if (contextPath && contextPath.length) { - context = deepGet(context, contextPath); - } - if (context == null) return void 0; - method = context[path]; - } - return method == null ? method : method.apply(context, args); - }); - }); - - // Convenience version of a common use case of `_.map`: fetching a property. - function pluck(obj, key) { - return map(obj, property(key)); - } - - // Convenience version of a common use case of `_.filter`: selecting only - // objects containing specific `key:value` pairs. - function where(obj, attrs) { - return filter(obj, matcher(attrs)); - } - - // Return the maximum element (or element-based computation). - function max(obj, iteratee, context) { - var result = -Infinity, lastComputed = -Infinity, - value, computed; - if (iteratee == null || (typeof iteratee == 'number' && typeof obj[0] != 'object' && obj != null)) { - obj = isArrayLike(obj) ? obj : values(obj); - for (var i = 0, length = obj.length; i < length; i++) { - value = obj[i]; - if (value != null && value > result) { - result = value; - } - } - } else { - iteratee = cb(iteratee, context); - each(obj, function(v, index, list) { - computed = iteratee(v, index, list); - if (computed > lastComputed || (computed === -Infinity && result === -Infinity)) { - result = v; - lastComputed = computed; - } - }); - } - return result; - } - - // Return the minimum element (or element-based computation). - function min(obj, iteratee, context) { - var result = Infinity, lastComputed = Infinity, - value, computed; - if (iteratee == null || (typeof iteratee == 'number' && typeof obj[0] != 'object' && obj != null)) { - obj = isArrayLike(obj) ? obj : values(obj); - for (var i = 0, length = obj.length; i < length; i++) { - value = obj[i]; - if (value != null && value < result) { - result = value; - } - } - } else { - iteratee = cb(iteratee, context); - each(obj, function(v, index, list) { - computed = iteratee(v, index, list); - if (computed < lastComputed || (computed === Infinity && result === Infinity)) { - result = v; - lastComputed = computed; - } - }); - } - return result; - } - - // Safely create a real, live array from anything iterable. - var reStrSymbol = /[^\ud800-\udfff]|[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff]/g; - function toArray(obj) { - if (!obj) return []; - if (isArray(obj)) return slice.call(obj); - if (isString(obj)) { - // Keep surrogate pair characters together. - return obj.match(reStrSymbol); - } - if (isArrayLike(obj)) return map(obj, identity); - return values(obj); - } - - // Sample **n** random values from a collection using the modern version of the - // [Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher–Yates_shuffle). - // If **n** is not specified, returns a single random element. - // The internal `guard` argument allows it to work with `_.map`. - function sample(obj, n, guard) { - if (n == null || guard) { - if (!isArrayLike(obj)) obj = values(obj); - return obj[random(obj.length - 1)]; - } - var sample = toArray(obj); - var length = getLength(sample); - n = Math.max(Math.min(n, length), 0); - var last = length - 1; - for (var index = 0; index < n; index++) { - var rand = random(index, last); - var temp = sample[index]; - sample[index] = sample[rand]; - sample[rand] = temp; - } - return sample.slice(0, n); - } - - // Shuffle a collection. - function shuffle(obj) { - return sample(obj, Infinity); - } - - // Sort the object's values by a criterion produced by an iteratee. - function sortBy(obj, iteratee, context) { - var index = 0; - iteratee = cb(iteratee, context); - return pluck(map(obj, function(value, key, list) { - return { - value: value, - index: index++, - criteria: iteratee(value, key, list) - }; - }).sort(function(left, right) { - var a = left.criteria; - var b = right.criteria; - if (a !== b) { - if (a > b || a === void 0) return 1; - if (a < b || b === void 0) return -1; - } - return left.index - right.index; - }), 'value'); - } - - // An internal function used for aggregate "group by" operations. - function group(behavior, partition) { - return function(obj, iteratee, context) { - var result = partition ? [[], []] : {}; - iteratee = cb(iteratee, context); - each(obj, function(value, index) { - var key = iteratee(value, index, obj); - behavior(result, value, key); - }); - return result; - }; - } - - // Groups the object's values by a criterion. Pass either a string attribute - // to group by, or a function that returns the criterion. - var groupBy = group(function(result, value, key) { - if (has$1(result, key)) result[key].push(value); else result[key] = [value]; - }); - - // Indexes the object's values by a criterion, similar to `_.groupBy`, but for - // when you know that your index values will be unique. - var indexBy = group(function(result, value, key) { - result[key] = value; - }); - - // Counts instances of an object that group by a certain criterion. Pass - // either a string attribute to count by, or a function that returns the - // criterion. - var countBy = group(function(result, value, key) { - if (has$1(result, key)) result[key]++; else result[key] = 1; - }); - - // Split a collection into two arrays: one whose elements all pass the given - // truth test, and one whose elements all do not pass the truth test. - var partition = group(function(result, value, pass) { - result[pass ? 0 : 1].push(value); - }, true); - - // Return the number of elements in a collection. - function size(obj) { - if (obj == null) return 0; - return isArrayLike(obj) ? obj.length : keys(obj).length; - } - - // Internal `_.pick` helper function to determine whether `key` is an enumerable - // property name of `obj`. - function keyInObj(value, key, obj) { - return key in obj; - } - - // Return a copy of the object only containing the allowed properties. - var pick = restArguments(function(obj, keys) { - var result = {}, iteratee = keys[0]; - if (obj == null) return result; - if (isFunction$1(iteratee)) { - if (keys.length > 1) iteratee = optimizeCb(iteratee, keys[1]); - keys = allKeys(obj); - } else { - iteratee = keyInObj; - keys = flatten$1(keys, false, false); - obj = Object(obj); - } - for (var i = 0, length = keys.length; i < length; i++) { - var key = keys[i]; - var value = obj[key]; - if (iteratee(value, key, obj)) result[key] = value; - } - return result; - }); - - // Return a copy of the object without the disallowed properties. - var omit = restArguments(function(obj, keys) { - var iteratee = keys[0], context; - if (isFunction$1(iteratee)) { - iteratee = negate(iteratee); - if (keys.length > 1) context = keys[1]; - } else { - keys = map(flatten$1(keys, false, false), String); - iteratee = function(value, key) { - return !contains(keys, key); - }; - } - return pick(obj, iteratee, context); - }); - - // Returns everything but the last entry of the array. Especially useful on - // the arguments object. Passing **n** will return all the values in - // the array, excluding the last N. - function initial(array, n, guard) { - return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n))); - } - - // Get the first element of an array. Passing **n** will return the first N - // values in the array. The **guard** check allows it to work with `_.map`. - function first(array, n, guard) { - if (array == null || array.length < 1) return n == null || guard ? void 0 : []; - if (n == null || guard) return array[0]; - return initial(array, array.length - n); - } - - // Returns everything but the first entry of the `array`. Especially useful on - // the `arguments` object. Passing an **n** will return the rest N values in the - // `array`. - function rest(array, n, guard) { - return slice.call(array, n == null || guard ? 1 : n); - } - - // Get the last element of an array. Passing **n** will return the last N - // values in the array. - function last(array, n, guard) { - if (array == null || array.length < 1) return n == null || guard ? void 0 : []; - if (n == null || guard) return array[array.length - 1]; - return rest(array, Math.max(0, array.length - n)); - } - - // Trim out all falsy values from an array. - function compact(array) { - return filter(array, Boolean); - } - - // Flatten out an array, either recursively (by default), or up to `depth`. - // Passing `true` or `false` as `depth` means `1` or `Infinity`, respectively. - function flatten(array, depth) { - return flatten$1(array, depth, false); - } - - // Take the difference between one array and a number of other arrays. - // Only the elements present in just the first array will remain. - var difference = restArguments(function(array, rest) { - rest = flatten$1(rest, true, true); - return filter(array, function(value){ - return !contains(rest, value); - }); - }); - - // Return a version of the array that does not contain the specified value(s). - var without = restArguments(function(array, otherArrays) { - return difference(array, otherArrays); - }); - - // Produce a duplicate-free version of the array. If the array has already - // been sorted, you have the option of using a faster algorithm. - // The faster algorithm will not work with an iteratee if the iteratee - // is not a one-to-one function, so providing an iteratee will disable - // the faster algorithm. - function uniq(array, isSorted, iteratee, context) { - if (!isBoolean(isSorted)) { - context = iteratee; - iteratee = isSorted; - isSorted = false; - } - if (iteratee != null) iteratee = cb(iteratee, context); - var result = []; - var seen = []; - for (var i = 0, length = getLength(array); i < length; i++) { - var value = array[i], - computed = iteratee ? iteratee(value, i, array) : value; - if (isSorted && !iteratee) { - if (!i || seen !== computed) result.push(value); - seen = computed; - } else if (iteratee) { - if (!contains(seen, computed)) { - seen.push(computed); - result.push(value); - } - } else if (!contains(result, value)) { - result.push(value); - } - } - return result; - } - - // Produce an array that contains the union: each distinct element from all of - // the passed-in arrays. - var union = restArguments(function(arrays) { - return uniq(flatten$1(arrays, true, true)); - }); - - // Produce an array that contains every item shared between all the - // passed-in arrays. - function intersection(array) { - var result = []; - var argsLength = arguments.length; - for (var i = 0, length = getLength(array); i < length; i++) { - var item = array[i]; - if (contains(result, item)) continue; - var j; - for (j = 1; j < argsLength; j++) { - if (!contains(arguments[j], item)) break; - } - if (j === argsLength) result.push(item); - } - return result; - } - - // Complement of zip. Unzip accepts an array of arrays and groups - // each array's elements on shared indices. - function unzip(array) { - var length = (array && max(array, getLength).length) || 0; - var result = Array(length); - - for (var index = 0; index < length; index++) { - result[index] = pluck(array, index); - } - return result; - } - - // Zip together multiple lists into a single array -- elements that share - // an index go together. - var zip = restArguments(unzip); - - // Converts lists into objects. Pass either a single array of `[key, value]` - // pairs, or two parallel arrays of the same length -- one of keys, and one of - // the corresponding values. Passing by pairs is the reverse of `_.pairs`. - function object(list, values) { - var result = {}; - for (var i = 0, length = getLength(list); i < length; i++) { - if (values) { - result[list[i]] = values[i]; - } else { - result[list[i][0]] = list[i][1]; - } - } - return result; - } - - // Generate an integer Array containing an arithmetic progression. A port of - // the native Python `range()` function. See - // [the Python documentation](https://docs.python.org/library/functions.html#range). - function range(start, stop, step) { - if (stop == null) { - stop = start || 0; - start = 0; - } - if (!step) { - step = stop < start ? -1 : 1; - } - - var length = Math.max(Math.ceil((stop - start) / step), 0); - var range = Array(length); - - for (var idx = 0; idx < length; idx++, start += step) { - range[idx] = start; - } - - return range; - } - - // Chunk a single array into multiple arrays, each containing `count` or fewer - // items. - function chunk(array, count) { - if (count == null || count < 1) return []; - var result = []; - var i = 0, length = array.length; - while (i < length) { - result.push(slice.call(array, i, i += count)); - } - return result; - } - - // Helper function to continue chaining intermediate results. - function chainResult(instance, obj) { - return instance._chain ? _$1(obj).chain() : obj; - } - - // Add your own custom functions to the Underscore object. - function mixin(obj) { - each(functions(obj), function(name) { - var func = _$1[name] = obj[name]; - _$1.prototype[name] = function() { - var args = [this._wrapped]; - push.apply(args, arguments); - return chainResult(this, func.apply(_$1, args)); - }; - }); - return _$1; - } - - // Add all mutator `Array` functions to the wrapper. - each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { - var method = ArrayProto[name]; - _$1.prototype[name] = function() { - var obj = this._wrapped; - if (obj != null) { - method.apply(obj, arguments); - if ((name === 'shift' || name === 'splice') && obj.length === 0) { - delete obj[0]; - } - } - return chainResult(this, obj); - }; - }); - - // Add all accessor `Array` functions to the wrapper. - each(['concat', 'join', 'slice'], function(name) { - var method = ArrayProto[name]; - _$1.prototype[name] = function() { - var obj = this._wrapped; - if (obj != null) obj = method.apply(obj, arguments); - return chainResult(this, obj); - }; - }); - - // Named Exports - - var allExports = { - __proto__: null, - VERSION: VERSION, - restArguments: restArguments, - isObject: isObject, - isNull: isNull, - isUndefined: isUndefined, - isBoolean: isBoolean, - isElement: isElement, - isString: isString, - isNumber: isNumber, - isDate: isDate, - isRegExp: isRegExp, - isError: isError, - isSymbol: isSymbol, - isArrayBuffer: isArrayBuffer, - isDataView: isDataView$1, - isArray: isArray, - isFunction: isFunction$1, - isArguments: isArguments$1, - isFinite: isFinite$1, - isNaN: isNaN$1, - isTypedArray: isTypedArray$1, - isEmpty: isEmpty, - isMatch: isMatch, - isEqual: isEqual, - isMap: isMap, - isWeakMap: isWeakMap, - isSet: isSet, - isWeakSet: isWeakSet, - keys: keys, - allKeys: allKeys, - values: values, - pairs: pairs, - invert: invert, - functions: functions, - methods: functions, - extend: extend, - extendOwn: extendOwn, - assign: extendOwn, - defaults: defaults, - create: create, - clone: clone, - tap: tap, - get: get, - has: has, - mapObject: mapObject, - identity: identity, - constant: constant, - noop: noop, - toPath: toPath$1, - property: property, - propertyOf: propertyOf, - matcher: matcher, - matches: matcher, - times: times, - random: random, - now: now, - escape: _escape, - unescape: _unescape, - templateSettings: templateSettings, - template: template, - result: result, - uniqueId: uniqueId, - chain: chain, - iteratee: iteratee, - partial: partial, - bind: bind, - bindAll: bindAll, - memoize: memoize, - delay: delay, - defer: defer, - throttle: throttle, - debounce: debounce, - wrap: wrap, - negate: negate, - compose: compose, - after: after, - before: before, - once: once, - findKey: findKey, - findIndex: findIndex, - findLastIndex: findLastIndex, - sortedIndex: sortedIndex, - indexOf: indexOf, - lastIndexOf: lastIndexOf, - find: find, - detect: find, - findWhere: findWhere, - each: each, - forEach: each, - map: map, - collect: map, - reduce: reduce, - foldl: reduce, - inject: reduce, - reduceRight: reduceRight, - foldr: reduceRight, - filter: filter, - select: filter, - reject: reject, - every: every, - all: every, - some: some, - any: some, - contains: contains, - includes: contains, - include: contains, - invoke: invoke, - pluck: pluck, - where: where, - max: max, - min: min, - shuffle: shuffle, - sample: sample, - sortBy: sortBy, - groupBy: groupBy, - indexBy: indexBy, - countBy: countBy, - partition: partition, - toArray: toArray, - size: size, - pick: pick, - omit: omit, - first: first, - head: first, - take: first, - initial: initial, - last: last, - rest: rest, - tail: rest, - drop: rest, - compact: compact, - flatten: flatten, - without: without, - uniq: uniq, - unique: uniq, - union: union, - intersection: intersection, - difference: difference, - unzip: unzip, - transpose: unzip, - zip: zip, - object: object, - range: range, - chunk: chunk, - mixin: mixin, - 'default': _$1 - }; - - // Default Export - - // Add all of the Underscore functions to the wrapper object. - var _ = mixin(allExports); - // Legacy Node.js API. - _._ = _; - - return _; - -}))); -//# sourceMappingURL=underscore-umd.js.map diff --git a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/underscore-1.13.7.js b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/underscore-1.13.7.js new file mode 100644 index 0000000000..03277d15dd --- /dev/null +++ b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/underscore-1.13.7.js @@ -0,0 +1,6 @@ +!function(n,r){"object"==typeof exports&&"undefined"!=typeof module?module.exports=r():"function"==typeof define&&define.amd?define("underscore",r):(n="undefined"!=typeof globalThis?globalThis:n||self,function(){var t=n._,e=n._=r();e.noConflict=function(){return n._=t,e}}())}(this,(function(){ +// Underscore.js 1.13.7 +// https://underscorejs.org +// (c) 2009-2024 Jeremy Ashkenas, Julian Gonggrijp, and DocumentCloud and Investigative Reporters & Editors +// Underscore may be freely distributed under the MIT license. +var n="1.13.7",r="object"==typeof self&&self.self===self&&self||"object"==typeof global&&global.global===global&&global||Function("return this")()||{},t=Array.prototype,e=Object.prototype,u="undefined"!=typeof Symbol?Symbol.prototype:null,i=t.push,o=t.slice,a=e.toString,f=e.hasOwnProperty,c="undefined"!=typeof ArrayBuffer,l="undefined"!=typeof DataView,s=Array.isArray,p=Object.keys,v=Object.create,h=c&&ArrayBuffer.isView,y=isNaN,d=isFinite,g=!{toString:null}.propertyIsEnumerable("toString"),b=["valueOf","isPrototypeOf","toString","propertyIsEnumerable","hasOwnProperty","toLocaleString"],m=Math.pow(2,53)-1;function j(n,r){return r=null==r?n.length-1:+r,function(){for(var t=Math.max(arguments.length-r,0),e=Array(t),u=0;u=0&&t<=m}}function J(n){return function(r){return null==r?void 0:r[n]}}var G=J("byteLength"),H=K(G),Q=/\[object ((I|Ui)nt(8|16|32)|Float(32|64)|Uint8Clamped|Big(I|Ui)nt64)Array\]/;var X=c?function(n){return h?h(n)&&!q(n):H(n)&&Q.test(a.call(n))}:C(!1),Y=J("length");function Z(n,r){r=function(n){for(var r={},t=n.length,e=0;e":">",'"':""","'":"'","`":"`"},$n=zn(Ln),Cn=zn(wn(Ln)),Kn=tn.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g},Jn=/(.)^/,Gn={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},Hn=/\\|'|\r|\n|\u2028|\u2029/g;function Qn(n){return"\\"+Gn[n]}var Xn=/^\s*(\w|\$)+\s*$/;var Yn=0;function Zn(n,r,t,e,u){if(!(e instanceof r))return n.apply(t,u);var i=Mn(n.prototype),o=n.apply(i,u);return w(o)?o:i}var nr=j((function(n,r){var t=nr.placeholder,e=function(){for(var u=0,i=r.length,o=Array(i),a=0;a1)er(a,r-1,t,e),u=e.length;else for(var f=0,c=a.length;f0&&(t=r.apply(this,arguments)),n<=1&&(r=null),t}}var cr=nr(fr,2);function lr(n,r,t){r=Pn(r,t);for(var e,u=nn(n),i=0,o=u.length;i0?0:u-1;i>=0&&i0?a=i>=0?i:Math.max(i+f,a):f=i>=0?Math.min(i+1,f):i+f+1;else if(t&&i&&f)return e[i=t(e,u)]===u?i:-1;if(u!=u)return(i=r(o.call(e,a,f),$))>=0?i+a:-1;for(i=n>0?a:f-1;i>=0&&i0?0:o-1;for(u||(e=r[i?i[a]:a],a+=n);a>=0&&a=3;return r(n,Rn(t,u,4),e,i)}}var _r=wr(1),Ar=wr(-1);function xr(n,r,t){var e=[];return r=Pn(r,t),mr(n,(function(n,t,u){r(n,t,u)&&e.push(n)})),e}function Sr(n,r,t){r=Pn(r,t);for(var e=!tr(n)&&nn(n),u=(e||n).length,i=0;i=0}var Er=j((function(n,r,t){var e,u;return D(r)?u=r:(r=Bn(r),e=r.slice(0,-1),r=r[r.length-1]),jr(n,(function(n){var i=u;if(!i){if(e&&e.length&&(n=Nn(n,e)),null==n)return;i=n[r]}return null==i?i:i.apply(n,t)}))}));function Br(n,r){return jr(n,Dn(r))}function Nr(n,r,t){var e,u,i=-1/0,o=-1/0;if(null==r||"number"==typeof r&&"object"!=typeof n[0]&&null!=n)for(var a=0,f=(n=tr(n)?n:jn(n)).length;ai&&(i=e);else r=Pn(r,t),mr(n,(function(n,t,e){((u=r(n,t,e))>o||u===-1/0&&i===-1/0)&&(i=n,o=u)}));return i}var Ir=/[^\ud800-\udfff]|[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff]/g;function Tr(n){return n?U(n)?o.call(n):S(n)?n.match(Ir):tr(n)?jr(n,Tn):jn(n):[]}function kr(n,r,t){if(null==r||t)return tr(n)||(n=jn(n)),n[Un(n.length-1)];var e=Tr(n),u=Y(e);r=Math.max(Math.min(r,u),0);for(var i=u-1,o=0;o1&&(e=Rn(e,r[1])),r=an(n)):(e=qr,r=er(r,!1,!1),n=Object(n));for(var u=0,i=r.length;u1&&(t=r[1])):(r=jr(er(r,!1,!1),String),e=function(n,t){return!Mr(r,t)}),Ur(n,e,t)}));function zr(n,r,t){return o.call(n,0,Math.max(0,n.length-(null==r||t?1:r)))}function Lr(n,r,t){return null==n||n.length<1?null==r||t?void 0:[]:null==r||t?n[0]:zr(n,n.length-r)}function $r(n,r,t){return o.call(n,null==r||t?1:r)}var Cr=j((function(n,r){return r=er(r,!0,!0),xr(n,(function(n){return!Mr(r,n)}))})),Kr=j((function(n,r){return Cr(n,r)}));function Jr(n,r,t,e){A(r)||(e=t,t=r,r=!1),null!=t&&(t=Pn(t,e));for(var u=[],i=[],o=0,a=Y(n);or?(e&&(clearTimeout(e),e=null),a=c,o=n.apply(u,i),e||(u=i=null)):e||!1===t.trailing||(e=setTimeout(f,l)),o};return c.cancel=function(){clearTimeout(e),a=0,e=u=i=null},c},debounce:function(n,r,t){var e,u,i,o,a,f=function(){var c=Wn()-u;r>c?e=setTimeout(f,r-c):(e=null,t||(o=n.apply(a,i)),e||(i=a=null))},c=j((function(c){return a=this,i=c,u=Wn(),e||(e=setTimeout(f,r),t&&(o=n.apply(a,i))),o}));return c.cancel=function(){clearTimeout(e),e=i=a=null},c},wrap:function(n,r){return nr(r,n)},negate:ar,compose:function(){var n=arguments,r=n.length-1;return function(){for(var t=r,e=n[r].apply(this,arguments);t--;)e=n[t].call(this,e);return e}},after:function(n,r){return function(){if(--n<1)return r.apply(this,arguments)}},before:fr,once:cr,findKey:lr,findIndex:pr,findLastIndex:vr,sortedIndex:hr,indexOf:dr,lastIndexOf:gr,find:br,detect:br,findWhere:function(n,r){return br(n,kn(r))},each:mr,forEach:mr,map:jr,collect:jr,reduce:_r,foldl:_r,inject:_r,reduceRight:Ar,foldr:Ar,filter:xr,select:xr,reject:function(n,r,t){return xr(n,ar(Pn(r)),t)},every:Sr,all:Sr,some:Or,any:Or,contains:Mr,includes:Mr,include:Mr,invoke:Er,pluck:Br,where:function(n,r){return xr(n,kn(r))},max:Nr,min:function(n,r,t){var e,u,i=1/0,o=1/0;if(null==r||"number"==typeof r&&"object"!=typeof n[0]&&null!=n)for(var a=0,f=(n=tr(n)?n:jn(n)).length;ae||void 0===t)return 1;if(t Date: Tue, 8 Apr 2025 13:46:25 -0300 Subject: [PATCH 08/69] TAP5-2804: another pass at the CoffeeScript to TypeScript conversion --- .../corelib/components/Graphviz.java | 4 + tapestry-core/src/main/typescript/.gitignore | 1 + .../src/main/typescript/package-lock.json | 652 +++++++++++++++++- .../src/main/typescript/package.json | 24 +- .../src/main/typescript/src/t5/core/ajax.ts | 147 ++-- .../typescript/src/t5/core/ajaxformloop.ts | 117 ++-- .../src/main/typescript/src/t5/core/alert.ts | 8 +- .../typescript/src/t5/core/autocomplete.ts | 78 +-- .../main/typescript/src/t5/core/bootstrap.ts | 17 +- .../typescript/src/t5/core/confirm-click.ts | 160 ++--- .../main/typescript/src/t5/core/console.ts | 309 ++++----- .../main/typescript/src/t5/core/datefield.ts | 344 +++++---- .../main/typescript/src/t5/core/dom-jquery.ts | 536 ++++++++++++++ .../src/main/typescript/src/t5/core/dom.ts | 27 + .../src/main/typescript/src/t5/core/events.ts | 17 +- .../src/t5/core/exception-display.ts | 29 +- .../typescript/src/t5/core/exception-frame.ts | 69 +- .../src/main/typescript/src/t5/core/fields.ts | 298 ++++---- .../typescript/src/t5/core/form-fragment.ts | 203 +++--- .../src/main/typescript/src/t5/core/forms.ts | 334 +++++---- .../main/typescript/src/t5/core/graphviz.ts | 37 +- .../src/main/typescript/src/t5/core/init.ts | 22 +- .../main/typescript/src/t5/core/localdate.ts | 23 +- .../main/typescript/src/t5/core/messages.ts | 39 +- .../src/main/typescript/src/t5/core/moment.ts | 11 +- .../main/typescript/src/t5/core/pageinit.ts | 452 ++++++------ .../main/typescript/src/t5/core/palette.ts | 552 ++++++++------- .../src/main/typescript/src/t5/core/select.ts | 27 +- .../typescript/src/t5/core/time-interval.ts | 62 +- .../src/main/typescript/src/t5/core/tree.ts | 175 +++-- .../src/main/typescript/src/t5/core/types.ts | 484 +++++++++++++ .../src/main/typescript/src/t5/core/utils.ts | 68 +- .../main/typescript/src/t5/core/validation.ts | 322 ++++----- .../typescript/src/t5/core/zone-refresh.ts | 90 ++- .../src/main/typescript/src/t5/core/zone.ts | 245 ++++--- .../src/main/typescript/tsconfig.json | 32 +- 36 files changed, 3835 insertions(+), 2180 deletions(-) create mode 100644 tapestry-core/src/main/typescript/src/t5/core/dom-jquery.ts create mode 100644 tapestry-core/src/main/typescript/src/t5/core/dom.ts create mode 100644 tapestry-core/src/main/typescript/src/t5/core/types.ts diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Graphviz.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Graphviz.java index f0647bab72..0f34cd3ec9 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Graphviz.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Graphviz.java @@ -83,6 +83,10 @@ void setupRender(MarkupWriter writer) final String id = javaScriptSupport.allocateClientId(resources); writer.element(elementName, "id", id); writer.end(); + + + // TODO import https://cdn.jsdelivr.net/npm/@hpcc-js/wasm/dist/graphviz.js + // if Require.js is disabled @hpcc-js/wasm javaScriptSupport.require("t5/core/graphviz").with(cachedValue, id, showDownloadLink); diff --git a/tapestry-core/src/main/typescript/.gitignore b/tapestry-core/src/main/typescript/.gitignore index 2ccbe4656c..ea683cd2fe 100644 --- a/tapestry-core/src/main/typescript/.gitignore +++ b/tapestry-core/src/main/typescript/.gitignore @@ -1 +1,2 @@ /node_modules/ +/output/ diff --git a/tapestry-core/src/main/typescript/package-lock.json b/tapestry-core/src/main/typescript/package-lock.json index 24f53662fc..2719117b49 100644 --- a/tapestry-core/src/main/typescript/package-lock.json +++ b/tapestry-core/src/main/typescript/package-lock.json @@ -4,8 +4,576 @@ "requires": true, "packages": { "": { + "license": "Apache-2.0", + "dependencies": { + "underscore": "^1.13.7" + }, "devDependencies": { - "typescript": "^5.8.2" + "@hpcc-js/wasm": "^2.22.4", + "@types/jquery": "^1.10.45", + "@types/underscore": "^1.13.0", + "jsdoc": "^4.0.4", + "typedoc": "^0.28.1", + "typescript": "^5.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", + "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.27.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", + "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@gerrit0/mini-shiki": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.2.2.tgz", + "integrity": "sha512-vaZNGhGLKMY14HbF53xxHNgFO9Wz+t5lTlGNpl2N9xFiKQ0I5oIe0vKjU9dh7Nb3Dw6lZ7wqUE0ri+zcdpnK+Q==", + "dev": true, + "dependencies": { + "@shikijs/engine-oniguruma": "^3.2.1", + "@shikijs/langs": "^3.2.1", + "@shikijs/themes": "^3.2.1", + "@shikijs/types": "^3.2.1", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@hpcc-js/wasm": { + "version": "2.22.4", + "resolved": "https://registry.npmjs.org/@hpcc-js/wasm/-/wasm-2.22.4.tgz", + "integrity": "sha512-58JkRkxZffiBAbZhc7z+9iaaAOmn1cyxLL3rRwsUvco/I0Wwb7uVAlHM9HiU6XASe2k11jrIjCFff1t9QKjlqg==", + "dev": true, + "dependencies": { + "yargs": "17.7.2" + }, + "bin": { + "dot-wasm": "node ./node_modules/@hpcc-js/wasm-graphviz-cli/bin/index.js" + } + }, + "node_modules/@jsdoc/salty": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.9.tgz", + "integrity": "sha512-yYxMVH7Dqw6nO0d5NIV8OQWnitU8k6vXH8NtgqAfIa/IUqRMxRv/NUJJ08VEKbAakwxlgBl5PJdrU0dMPStsnw==", + "dev": true, + "dependencies": { + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=v12.0.0" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.2.1.tgz", + "integrity": "sha512-wZZAkayEn6qu2+YjenEoFqj0OyQI64EWsNR6/71d1EkG4sxEOFooowKivsWPpaWNBu3sxAG+zPz5kzBL/SsreQ==", + "dev": true, + "dependencies": { + "@shikijs/types": "3.2.1", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/langs": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.2.1.tgz", + "integrity": "sha512-If0iDHYRSGbihiA8+7uRsgb1er1Yj11pwpX1c6HLYnizDsKAw5iaT3JXj5ZpaimXSWky/IhxTm7C6nkiYVym+A==", + "dev": true, + "dependencies": { + "@shikijs/types": "3.2.1" + } + }, + "node_modules/@shikijs/themes": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.2.1.tgz", + "integrity": "sha512-k5DKJUT8IldBvAm8WcrDT5+7GA7se6lLksR+2E3SvyqGTyFMzU2F9Gb7rmD+t+Pga1MKrYFxDIeyWjMZWM6uBQ==", + "dev": true, + "dependencies": { + "@shikijs/types": "3.2.1" + } + }, + "node_modules/@shikijs/types": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.2.1.tgz", + "integrity": "sha512-/NTWAk4KE2M8uac0RhOsIhYQf4pdU0OywQuYDGIGAJ6Mjunxl2cGiuLkvu4HLCMn+OTTLRWkjZITp+aYJv60yA==", + "dev": true, + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "dev": true + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dev": true, + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/jquery": { + "version": "1.10.45", + "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-1.10.45.tgz", + "integrity": "sha512-JvoVtPbu1wrcOldn5gvuyJqr7DLX6I657N68Fqj5S7AUutdYZqabQjfP7dY6Gee7/Afo1uajwcORVlQfnsjkLQ==", + "dev": true + }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true + }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "dev": true, + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true + }, + "node_modules/@types/underscore": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@types/underscore/-/underscore-1.13.0.tgz", + "integrity": "sha512-L6LBgy1f0EFQZ+7uSA57+n2g/s4Qs5r06Vwrwn0/nuK1de+adz00NWaztRQ30aEqw5qOaWbPI8u2cGQ52lj6VA==", + "dev": true + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/catharsis": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", + "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", + "dev": true, + "dependencies": { + "lodash": "^4.17.15" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", + "dev": true, + "dependencies": { + "xmlcreate": "^2.0.4" + } + }, + "node_modules/jsdoc": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.4.tgz", + "integrity": "sha512-zeFezwyXeG4syyYHbvh1A967IAqq/67yXtXvuL5wnqCkFZe8I0vKfm+EO+YEvLguo6w9CDUbrAXVtJSHh2E8rw==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.15", + "@jsdoc/salty": "^0.2.1", + "@types/markdown-it": "^14.1.1", + "bluebird": "^3.7.2", + "catharsis": "^0.9.0", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.2", + "klaw": "^3.0.0", + "markdown-it": "^14.1.0", + "markdown-it-anchor": "^8.6.7", + "marked": "^4.0.10", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "underscore": "~1.13.2" + }, + "bin": { + "jsdoc": "jsdoc.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.9" + } + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dev": true, + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true + }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdown-it-anchor": { + "version": "8.6.7", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz", + "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==", + "dev": true, + "peerDependencies": { + "@types/markdown-it": "*", + "markdown-it": "*" + } + }, + "node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "dev": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", + "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", + "dev": true, + "dependencies": { + "lodash": "^4.17.21" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typedoc": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.1.tgz", + "integrity": "sha512-Mn2VPNMaxoe/hlBiLriG4U55oyAa3Xo+8HbtEwV7F5WEOPXqtxzGuMZhJYHaqFJpajeQ6ZDUC2c990NAtTbdgw==", + "dev": true, + "dependencies": { + "@gerrit0/mini-shiki": "^3.2.1", + "lunr": "^2.3.9", + "markdown-it": "^14.1.0", + "minimatch": "^9.0.5", + "yaml": "^2.7.0 " + }, + "bin": { + "typedoc": "bin/typedoc" + }, + "engines": { + "node": ">= 18", + "pnpm": ">= 10" + }, + "peerDependencies": { + "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x" } }, "node_modules/typescript": { @@ -20,6 +588,88 @@ "engines": { "node": ">=14.17" } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true + }, + "node_modules/underscore": { + "version": "1.13.7", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", + "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", + "dev": true + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yaml": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz", + "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==", + "dev": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } } } } diff --git a/tapestry-core/src/main/typescript/package.json b/tapestry-core/src/main/typescript/package.json index 3ba47b7ad8..c3f09ea087 100644 --- a/tapestry-core/src/main/typescript/package.json +++ b/tapestry-core/src/main/typescript/package.json @@ -1,5 +1,27 @@ { + "type": "module", "devDependencies": { + "@hpcc-js/wasm": "^2.22.4", + "@types/jquery": "^1.10.45", + "@types/underscore": "^1.13.0", + "jsdoc": "^4.0.4", + "typedoc": "^0.28.1", "typescript": "^5.0.0" - } + }, + "dependencies": { + "underscore": "^1.13.7" + }, + "scripts": { + "build": "npx tsc", + "docs": "typedoc", + "docs-old": "node_modules/.bin/jsdoc --destination ./docs -r --verbose --pedantic ./output", + "docs-config": "node_modules/.bin/jsdoc -c jsdoc.json", + "clean-docs": "rm -rf out" + }, + "license": "Apache-2.0", + "description": "Apache Tapestry's TypeScript/JavaScript code", + "homepage": "https://tapestry.apache.org", + "keywords": [ + "tapestry" + ] } diff --git a/tapestry-core/src/main/typescript/src/t5/core/ajax.ts b/tapestry-core/src/main/typescript/src/t5/core/ajax.ts index 4d27daba1a..1d543189fc 100644 --- a/tapestry-core/src/main/typescript/src/t5/core/ajax.ts +++ b/tapestry-core/src/main/typescript/src/t5/core/ajax.ts @@ -1,9 +1,4 @@ -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md - */ -// Copyright 2012-2014 The Apache Software Foundation +// Copyright 2012-2025 The Apache Software Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -17,91 +12,93 @@ // See the License for the specific language governing permissions and // limitations under the License. -// ## t5/core/ajax -// -// Exports a single function, that invokes `t5/core/dom:ajaxRequest()` with the provided `url` and a modified version of the -// `options`. -// -// * options.method - "post", "get", etc., default: "post". -// * options.element - if provided, the URL will be treated as a server-side event name -// and the actual URL to be used will be obtained from dom.getEventUrl(url, element) -// * options.contentType - request content, defaults to "application/x-www-form-urlencoded" -// * options.data - optional, additional key/value pairs (for the default content type) -// * options.success - handler to invoke on success. Passed the ResponseWrapper object. -// Default does nothing. -// * options.failure - handler to invoke on failure (server responds with a non-2xx code). -// Passed the response. Default will throw the exception -// * options.exception - handler to invoke when an exception occurs (often means the server is unavailable). -// Passed the exception. Default will generate an exception message and throw an `Error`. -// Note: not really supported under jQuery, a hold-over from Prototype. -// * options.complete - handler to invoke after success, falure, or exception. The handler is passed no -// parameters. -// -// It wraps (or provides) `success`, `exception`, and `failure` handlers, extended to handle a partial page render -// response (for success), or properly log a server-side failure or client-side exception, including using the -// `t5/core/exception-frame` module to display a server-side processing exception. -define(["t5/core/pageinit", "t5/core/dom", "t5/core/exception-frame", "t5/core/console", "underscore"], - (pageinit, dom, exceptionframe, console, _) => (function(url, options) { - - const complete = function() { - if (options.complete) { - options.complete(); - } - - }; - - if (options.hasOwnProperty('element')) { - url = dom.getEventUrl(url, options.element); +/** t5/core/ajax: exports a single function, that invokes `t5/core/dom:ajaxRequest()` with the provided `url` and a modified version of the + * `options`. + * + * * options.method - "post", "get", etc., default: "post". + * * options.element - if provided, the URL will be treated as a server-side event name + * and the actual URL to be used will be obtained from dom.getEventUrl(url, element) + * * options.contentType - request content, defaults to "application/x-www-form-urlencoded" + * * options.data - optional, additional key/value pairs (for the default content type) + * * options.success - handler to invoke on success. Passed the ResponseWrapper object. + * Default does nothing. + * * options.failure - handler to invoke on failure (server responds with a non-2xx code). + * Passed the response. Default will throw the exception + * * options.exception - handler to invoke when an exception occurs (often means the server is unavailable). + * Passed the exception. Default will generate an exception message and throw an `Error`. + * Note: not really supported under jQuery, a hold-over from Prototype. + * * options.complete - handler to invoke after success, falure, or exception. The handler is passed no + * parameters. + * It wraps (or provides) `success`, `exception`, and `failure` handlers, extended to handle a partial page render + * response (for success), or properly log a server-side failure or client-side exception, including using the + * `t5/core/exception-frame` module to display a server-side processing exception. + */ +import pageinit from "t5/core/pageinit.js"; +import dom from "t5/core/dom.js"; +import exceptionframe from "t5/core/exception-frame.js"; +import console from "t5/core/console.js"; +import _ from "underscore"; + +export default function(url: String, options: any) { + const complete = function() { + if (options.complete) { + options.complete(); } - const newOptions = _.extend({}, options, { + }; + + if (options.hasOwnProperty('element')) { + url = dom.getEventUrl(url, options.element); + } - // Logs the exception to the console before passing it to the - // provided exception handler or throwing the exception. - exception(exception) { - console.error(`Request to ${url} failed with ${exception}`); + const newOptions = _.extend({}, options, { - if (options.exception) { - options.exception(exception); - } else { - throw exception; - } + // Logs the exception to the console before passing it to the + // provided exception handler or throwing the exception. + exception(exception: any) { + console.error(`Request to ${url} failed with ${exception}`); + + if (options.exception) { + options.exception(exception); + } else { + throw exception; + } - complete(); + complete(); - }, + }, - failure(response, failureMessage) { - const raw = response.header("X-Tapestry-ErrorMessage"); - if (!_.isEmpty(raw)) { - const message = window.unescape(raw); - console.error(`Request to ${url} failed with '${message}'.`); + failure(response: any, failureMessage: string) { + const raw = response.header("X-Tapestry-ErrorMessage"); + if (!_.isEmpty(raw)) { + const message = window.unescape(raw); + console.error(`Request to ${url} failed with '${message}'.`); - const contentType = response.header("content-type"); + const contentType = response.header("content-type"); - const isHTML = contentType && (contentType.split(';')[0] === "text/html"); + const isHTML = contentType && (contentType.split(';')[0] === "text/html"); - if (isHTML) { - exceptionframe(response.text); - } - } else { - console.error(failureMessage); + if (isHTML) { + exceptionframe(response.text); } + } else { + console.error(failureMessage); + } - options.failure && options.failure(response); + options.failure && options.failure(response); - complete(); + complete(); - }, + }, - success(response) { - pageinit.handlePartialPageRenderResponse(response, options.success); + success(response: any) { + pageinit.handlePartialPageRenderResponse(response, options.success); - complete(); + complete(); - } } - ); + } + ); - return dom.ajaxRequest(url, newOptions); - })); \ No newline at end of file + return dom.ajaxRequest(url, newOptions); +}; \ No newline at end of file diff --git a/tapestry-core/src/main/typescript/src/t5/core/ajaxformloop.ts b/tapestry-core/src/main/typescript/src/t5/core/ajaxformloop.ts index 465a47f567..7e3cd256d5 100644 --- a/tapestry-core/src/main/typescript/src/t5/core/ajaxformloop.ts +++ b/tapestry-core/src/main/typescript/src/t5/core/ajaxformloop.ts @@ -1,10 +1,4 @@ -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md - */ -// Copyright 2012, 2013 The Apache Software Foundation +// Copyright 2012, 2013, 2025 The Apache Software Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -22,81 +16,82 @@ // // Provides handlers related to the core/AjaxFormLoop component (as well as core/AddRowLink and // core/RemoveRowLink). -define(["t5/core/dom", "t5/core/events", "t5/core/console", "t5/core/ajax"], - function(dom, events, console, ajax) { +import dom from "t5/core/dom"; +import events from "t5/core/events"; +import console from "t5/core/console" +import ajax from "t5/core/ajax"; - // "afl" is short for "AjaxFormLoop". - const AFL_SELECTOR = "[data-container-type='core/AjaxFormLoop']"; - const FRAGMENT_TYPE = "core/ajaxformloop-fragment"; +// "afl" is short for "AjaxFormLoop". +const AFL_SELECTOR = "[data-container-type='core/AjaxFormLoop']"; +const FRAGMENT_TYPE = "core/ajaxformloop-fragment"; - dom.onDocument("click", `${AFL_SELECTOR} [data-afl-behavior=remove]`, function() { +dom.onDocument("click", `${AFL_SELECTOR} [data-afl-behavior=remove]`, function() { - const afl = this.findParent(AFL_SELECTOR); + const afl = this.findParent(AFL_SELECTOR); - if (!afl) { - console.error("Enclosing element for AjaxFormLoop remove row link not found."); - return false; - } + if (!afl) { + console.error("Enclosing element for AjaxFormLoop remove row link not found."); + return false; + } - const url = afl.attr("data-remove-row-url"); + const url = afl.attr("data-remove-row-url"); - ajax(url, { - data: { - "t:rowvalue": (this.closest("[data-afl-row-value]")).attr("data-afl-row-value") - }, - success: () => { - // The server has removed the row from persistent storage, lets - // do the same on the UI. + ajax(url, { + data: { + "t:rowvalue": (this.closest("[data-afl-row-value]")).attr("data-afl-row-value") + }, + success: () => { + // The server has removed the row from persistent storage, lets + // do the same on the UI. - const fragment = this.findParent(`[data-container-type='${FRAGMENT_TYPE}']`); + const fragment = this.findParent(`[data-container-type='${FRAGMENT_TYPE}']`); - // TODO: Fire some before & after events, to allow for animation. + // TODO: Fire some before & after events, to allow for animation. - // The fragment takes with it the hidden fields that control form submission - // for its portion of the form. - return fragment.remove(); - } - } - ); + // The fragment takes with it the hidden fields that control form submission + // for its portion of the form. + return fragment.remove(); + } + } + ); - return false; - }); + return false; +}); - dom.onDocument("click", `${AFL_SELECTOR} [data-afl-behavior=insert-before] [data-afl-trigger=add]`, function() { +dom.onDocument("click", `${AFL_SELECTOR} [data-afl-behavior=insert-before] [data-afl-trigger=add]`, function() { - const afl = this.findParent(AFL_SELECTOR); + const afl = this.findParent(AFL_SELECTOR); - const insertionPoint = this.findParent("[data-afl-behavior=insert-before]"); + const insertionPoint = this.findParent("[data-afl-behavior=insert-before]"); - const url = afl.attr("data-inject-row-url"); + const url = afl.attr("data-inject-row-url"); - ajax(url, { - success(response) { - const content = (response.json != null ? response.json.content : undefined) || ""; + ajax(url, { + success(response) { + const content = (response.json != null ? response.json.content : undefined) || ""; - // Create a new element with the same type (usually "div") and class as this element. - // It will contain the new content. + // Create a new element with the same type (usually "div") and class as this element. + // It will contain the new content. - const newElement = dom.create(insertionPoint.element.tagName, - {'class': insertionPoint.element.className, 'data-container-type': FRAGMENT_TYPE}, - content); + const newElement = dom.create(insertionPoint.element.tagName, + {'class': insertionPoint.element.className, 'data-container-type': FRAGMENT_TYPE}, + content); - insertionPoint.insertBefore(newElement); + insertionPoint.insertBefore(newElement); - // Initialize components inside the new row - newElement.trigger(events.initializeComponents); + // Initialize components inside the new row + newElement.trigger(events.initializeComponents); - // Trigger this event, to inform the world that the zone-like new element has been updated - // with content. - insertionPoint.trigger(events.zone.didUpdate); + // Trigger this event, to inform the world that the zone-like new element has been updated + // with content. + insertionPoint.trigger(events.zone.didUpdate); - } - } - ); + } + } + ); - return false; - }); + return false; +}); - // This module is all event handlers, and no exported functions. -}); \ No newline at end of file +// This module is all event handlers, and no exported functions. \ No newline at end of file diff --git a/tapestry-core/src/main/typescript/src/t5/core/alert.ts b/tapestry-core/src/main/typescript/src/t5/core/alert.ts index 0dddb6189b..74ea546c7c 100644 --- a/tapestry-core/src/main/typescript/src/t5/core/alert.ts +++ b/tapestry-core/src/main/typescript/src/t5/core/alert.ts @@ -1,10 +1,4 @@ -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md - */ -// Copyright 2012-2013 The Apache Software Foundation +// Copyright 2012-2025 The Apache Software Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/tapestry-core/src/main/typescript/src/t5/core/autocomplete.ts b/tapestry-core/src/main/typescript/src/t5/core/autocomplete.ts index a1146f2f72..e0acd35821 100644 --- a/tapestry-core/src/main/typescript/src/t5/core/autocomplete.ts +++ b/tapestry-core/src/main/typescript/src/t5/core/autocomplete.ts @@ -1,8 +1,3 @@ -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md - */ // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -19,40 +14,39 @@ // // Support for the core/Autocomplete Tapestry mixin, a wrapper around // the Twitter autocomplete.js library. -define(["t5/core/dom", "t5/core/ajax", "underscore", "jquery", "t5/core/utils", "t5/core/typeahead"], - function(dom, ajax, _, $, {extendURL}) { - - let exports; - const init = function(spec) { - const $field = $(document.getElementById(spec.id)); - - const engine = new Bloodhound({ - datumTokenizer: Bloodhound.tokenizers.whitespace, - queryTokenizer: Bloodhound.tokenizers.whitespace, - limit: spec.limit, - remote: { - url: spec.url, - replace(uri, query) { return extendURL(uri, {"t:input": query}); }, - filter(response) { return response.matches; } - } - }); - - engine.initialize(); - - const dataset = { - name: spec.id, - displayKey: _.identity, - source: engine.ttAdapter() - }; - - $field.typeahead( - {minLength: spec.minChars}, - dataset); - - // don't validate the "tt-hint" input field created by Typeahead (fix for TAP5-2440) - $field.prev(".tt-hint").removeAttr("data-validation data-optionality data-required-message"); - - }; - - return exports = init; -}); +import dom from "t5/core/dom"; +import underscore from "underscore"; +import $ from "jquery" +import utils from "t5/core/utils"; +import typeahead from "t5/core/typeahead"; + +export const init = function(spec) { + const $field = $(document.getElementById(spec.id)); + + const engine = new Bloodhound({ + datumTokenizer: Bloodhound.tokenizers.whitespace, + queryTokenizer: Bloodhound.tokenizers.whitespace, + limit: spec.limit, + remote: { + url: spec.url, + replace(uri, query) { return extendURL(uri, {"t:input": query}); }, + filter(response) { return response.matches; } + } + }); + + engine.initialize(); + + const dataset = { + name: spec.id, + displayKey: _.identity, + source: engine.ttAdapter() + }; + + $field.typeahead( + {minLength: spec.minChars}, + dataset); + + // don't validate the "tt-hint" input field created by Typeahead (fix for TAP5-2440) + $field.prev(".tt-hint").removeAttr("data-validation data-optionality data-required-message"); + +}; \ No newline at end of file diff --git a/tapestry-core/src/main/typescript/src/t5/core/bootstrap.ts b/tapestry-core/src/main/typescript/src/t5/core/bootstrap.ts index 58fbd9c2ef..d75cb7a5a3 100644 --- a/tapestry-core/src/main/typescript/src/t5/core/bootstrap.ts +++ b/tapestry-core/src/main/typescript/src/t5/core/bootstrap.ts @@ -15,16 +15,13 @@ // ## t5/core/bootstrap // // Utilities for leveraging Bootstrap -define(["t5/core/bootstrap"], - function() { +import "t5/core/bootstrap" - const exports = - // Generates the CSS class name for an icon. - // - // * name - of icon, e.g., "arrow-left" - {glyph(name) { return ``; }}; - - return exports; -}); +let a = ""; +export default + // Generates the CSS class name for an icon. + // + // * name - of icon, e.g., "arrow-left" + {glyph: function(name: string) { return ``; }}; diff --git a/tapestry-core/src/main/typescript/src/t5/core/confirm-click.ts b/tapestry-core/src/main/typescript/src/t5/core/confirm-click.ts index 0d7bb7c2c5..fe9589e58b 100644 --- a/tapestry-core/src/main/typescript/src/t5/core/confirm-click.ts +++ b/tapestry-core/src/main/typescript/src/t5/core/confirm-click.ts @@ -1,30 +1,33 @@ -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md - */ // ## t5/core/confirm-click // // Support for the Tapestry Confirm mixin, and for running confirmation dialogs programmatically. -define(["jquery", "t5/core/events", "bootstrap/modal"], +import $ from "jquery"; +import "bootstrap/modal"; - function($, events) { +type DialogOptions = { + title: any; + message: any; + okClass: any; + okLabel: any; + cancelLabel: any; + ok: any; +}; - // Runs a modal dialog, invoking a callback if the user selects the OK option. On any form of cancel, - // there is no callback. - // - // options.title - default "Confirm" - // options.message - required - // options.okClass - default "btn-warning" - // options.okLabel - default "OK" - // options.cancelLabel - default "Cancel" - // options.ok - callback function, required - const runDialog = function(options) { +// Runs a modal dialog, invoking a callback if the user selects the OK option. On any form of cancel, +// there is no callback. +// +// options.title - default "Confirm" +// options.message - required +// options.okClass - default "btn-warning" +// options.okLabel - default "OK" +// options.cancelLabel - default "Cancel" +// options.ok - callback function, required +const runDialog = function(options: DialogOptions) { - let confirmed = false; + let confirmed = false; - const content = `\ + const content = `\
  • `. - const clickHandler = function() { +// toggles a folder in the tree between expanded and collapsed (once data for the folder +// has been loaded). +const toggle = function(node) { + const sublist = node.findParent("li").findFirst("ul"); - // Ignore clicks on leaf nodes, and on folders that are known to be empty. - if ((this.parent().hasClass("leaf-node")) || (this.hasClass("empty-node"))) { - return false; - } + if (node.hasClass(EXPANDED)) { + node.removeClass(EXPANDED); + sublist.hide(); + send(node, "markCollapsed"); + return; + } - // If not already loaded then fire off the Ajax request to load the content. - if ((this.meta(LOADED)) || (this.hasClass(EXPANDED))) { - toggle(this); - } else { - loadChildren(this); - } + node.addClass(EXPANDED); + sublist.show(); + return send(node, "markExpanded"); +}; - return false; - }; +// The handler is triggered on the `` directly inside the `
  • `. +const clickHandler = function() { - const toggleSelection = function() { + // Ignore clicks on leaf nodes, and on folders that are known to be empty. + if ((this.parent().hasClass("leaf-node")) || (this.hasClass("empty-node"))) { + return false; + } - const selected = this.hasClass(SELECTED); + // If not already loaded then fire off the Ajax request to load the content. + if ((this.meta(LOADED)) || (this.hasClass(EXPANDED))) { + toggle(this); + } else { + loadChildren(this); + } - const node = this.findParent("li").findFirst(`[${NODE_ID}]`); + return false; +}; - if (selected) { - this.removeClass(SELECTED); - send(node, "deselect"); - } else { - this.addClass(SELECTED); - send(node, "select"); - } +const toggleSelection = function() { - return false; - }; + const selected = this.hasClass(SELECTED); - dom.onDocument("click", SELECTOR, clickHandler); + const node = this.findParent("li").findFirst(`[${NODE_ID}]`); - dom.onDocument("click", - `${TREE}[data-tree-node-selection-enabled] LI.leaf-node > .tree-label`, - toggleSelection); + if (selected) { + this.removeClass(SELECTED); + send(node, "deselect"); + } else { + this.addClass(SELECTED); + send(node, "select"); + } + return false; +}; - return null; -}); - +dom.onDocument("click", SELECTOR, clickHandler); + +dom.onDocument("click", + `${TREE}[data-tree-node-selection-enabled] LI.leaf-node > .tree-label`, + toggleSelection); diff --git a/tapestry-core/src/main/typescript/src/t5/core/types.ts b/tapestry-core/src/main/typescript/src/t5/core/types.ts new file mode 100644 index 0000000000..98b1d2fc49 --- /dev/null +++ b/tapestry-core/src/main/typescript/src/t5/core/types.ts @@ -0,0 +1,484 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http:#www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * Superinterface of event wrapper types. Exists just to keep the compiler happy. + */ +export interface IEventWrapper { +} + +/** + * Type of event handlers functions. + */ +export type OnEventHandler = (element: ElementWrapper, event: EventWrapper, memo: any) => any; + +/** + * Class defining the offset of an element. + */ +export interface ElementOffset { + top: Number; + left: Number; +} + +/** + * Type defining the types of content + */ +export type AddableContent = string | ElementWrapper | HTMLElement; + +/** + * Type defining the options used in an AJAX request. + */ +export interface AjaxRequestOptions { + + /** + * HTTP method to be used ("post", "get", etc.). Default: "post". + */ + method?: string; + + /** + * Request content type. Defaults to "application/x-www-form-urlencoded". + */ + contentType?: string; + + /** + * Additional key/value pairs (for the default content type). Optional. + */ + data?: [key: string, value: string]; + + /** + * Additional key/value pairs to be added to the request headers. Optional. + */ + headers?: [key: string, value: string]; + + /** + * Handler to invoke on success. Passed the ResponseWrapper object. Default: does nothing. + */ + success?: (r: ResponseWrapper) => void; + + /** + * Handler to invoke on failure (server responds with a non-2xx code). + * Passed the response. Default will throw the exception. + */ + failure?: (r: ResponseWrapper, message?: string) => void; + + /** + * Handler to invoke when an exception occurs (often means the server is unavailable). + * Passed the exception. Default will generate an exception message and throw an `Error`. + */ + exception?: (e: any) => void; +} + +/** + * Wraps a DOM element, providing some common behaviors. + * Exposes the DOM element as property `element`. + */ +export interface ElementWrapper { + + /** + * Hides the wrapped element, setting its display to 'none'. + * + * @returns {T} this. + */ + hide(): ElementWrapper; + + /** + * Displays teh wrapped element if hidden. + * + * @returns {T} this. + */ + show(): ElementWrapper; + + /** + * Gets or sets a CSS property. + * + * @param {string} string name the name of the property. + * @param {string} value the value of the property. + * @returns {T} this. + */ + css(name: string, property: string | undefined): ElementWrapper | string; + + /** + * Returns the offset of the object relative to the document. The returned object has + * keys top and left'. + */ + offset(): ElementOffset; + + /** + * Removes the wrapped element from the DOM. It can later be re-attached. + * + * @returns {T} this. + */ + remove(): ElementWrapper; + + /** + * Reads or updates an attribute. With one argument, returns the current value + * of the attribute. With two arguments, updates the attribute's value, and returns + * the previous value. Setting an attribute to null is the same as removing it. + * + * Alternately, the first attribute can be an object in which case all the keys + * and values of the object are applied as attributes, and this `ElementWrapper` is returned. + * + * @param {string | [key: string, value: string]} name the attribute to read or update, or an object of keys and values + * @param {string} value (optional) the new value for the attribute + */ + attr(name: string | [key: string, value: string], value?: string): ElementWrapper | string | null; + + /** + * Moves the cursor to the field. + */ + focus(): ElementWrapper; + + /** + * Returns true if the element has the indicated class name, false otherwise. + * + * @param {string} name the class name + * @returns {boolean} true of false + */ + hasClass(name: string): boolean; + + /** + * Removes the class name from the element. + * + * @param {string} name the class name + * @returns {T} this. + */ + removeClass(name: string): ElementWrapper; + + /** + * Adds the class name to the element. + * + * @param {string} name the class name + * @returns {T} this. + */ + addClass(name: string) : ElementWrapper; + + /** + * Updates this element with new content, replacing any old content. The new content may be HTML text, or a DOM + * element, or an ElementWrapper, or null (to remove the body of the element). + * + * @param {AddableContent} content the content to be added + * @returns {T} this. + */ + update(content: AddableContent) : ElementWrapper; + + /** + * Appends new content (Element, ElementWrapper, or HTML markup string) to the body of the element. + * + * @param {AddableContent} content the content to be added + * @returns {T} this. + */ + append(content: AddableContent): ElementWrapper; + + /** + * Prepends new content (Element, ElementWrapper, or HTML markup string) to the body of the element. + * + * @param {AddableContent} content the content to be added + * @returns {T} this. + */ + prepend(content: AddableContent): ElementWrapper; + + /** + * Inserts new content (Element, ElementWrapper, or HTML markup string) into the DOM immediately before + * this ElementWrapper's element. + * + * @param {AddableContent} content the content to be added + * @returns {T} this. + */ + insertBefore(content: AddableContent): ElementWrapper; + + /** + * Inserts new content (Element, ElementWrapper, or HTML markup string) into the DOM immediately before + * this ElementWrapper's element. + * + * @param {AddableContent} content the content to be added + * @returns {T} this. + */ + insertAfter(content: AddableContent): ElementWrapper; + + /** + * Finds all child elements matching the CSS selector, returning them + * as an array of ElementWrappers. + * @param {string} selector a CSS selector. + * @returns {ElementWrapper[]} the matching child elements as an array of ElementWrapper instances + */ + find(selector: string): ElementWrapper[]; + + /** + * Finds the first child element that matches the CSS selector, wrapped as an ElementWrapper. + * Returns null if not found. + * + * @param {string} selector a CSS selector + */ + findFirst(selector: string): ElementWrapper | null; + + /** + * Finds the first container element that matches the CSS selector, wrapped as an ElementWrapper. + * Returns null if not found. + * + * @param {string} selector a CSS selector + * @returns {ElementWrapper | null} an element wrapper or null. * + */ + findParent(selector: string): ElementWrapper | null ; + + /** + * Returns this ElementWrapper if it matches the selector; otherwise, returns the first container element (as an ElementWrapper) + * that matches the selector. Returns null if no container element matches. + * + * @param {string} selector a CSS selector + * @returns {ElementWrapper | null} an element wrapper or null. + */ + closest(selector: string): ElementWrapper | null ; + + /** + * Returns an ElementWrapper for this element's containing element. + * Returns null if this element has no parent (either because this element is the document object, or + * because this element is not yet attached to the DOM). + * + * @returns {ElementWrapper | null} an element wrapper or null. + */ + parent(): ElementWrapper | null; + + /** + * Returns an array of all the immediate child elements of this element, as ElementWrappers. + * + * @returns {ElementWrapper[]} the children as ElementWrapper[] + */ + children(): ElementWrapper[]; + + /** + * Returns true if this element is visible, false otherwise. This does not check to see if all containers of the + * element are visible. + * + * @returns {boolean} true or false + */ + visible(): boolean; + + /** + * Returns true if this element is visible, and all parent elements are also visible, up to the document body. + * + * @returns {boolean} true or false + */ + deepVisible(): boolean; + + /** + * Fires a named event, passing an optional _memo_ object to event handler functions. This must support + * common native events (exact list TBD), as well as custom events (in Prototype, custom events must have + * a prefix that ends with a colon). + * + * @param {string} eventName name of event to trigger on the wrapped Element + * @param {Object | null} memo optional value assocated with the event; available as WrappedeEvent.memo in event handler functions (must + * be null for native events). The memo, when provided, should be an object; it is an error if it is a string or other + * non-object type.. + * @returns {boolean} true if the event fully executed, or false if the event was canceled. + */ + trigger(eventName: string, memo: Object | null): void; + + /** + * With no parameters, returns the current value of the element (which must be a form control element, such as `` or + * `