diff --git a/5_10_RELEASE_NOTES.md b/5_10_RELEASE_NOTES.md index e7d1c36da7..d62d92163c 100644 --- a/5_10_RELEASE_NOTES.md +++ b/5_10_RELEASE_NOTES.md @@ -2,7 +2,7 @@ Scratch pad for changes destined for the 5.10.0 release notes page. # Added configuration symbols -* `tapestry.es-module-path-prefix` (`SymbolConstants.ES_MODULE_PATH_PREFIX`) +* `tapestry.require-js-enabled`(`SymbolConstants.REQUIRE_JS_ENABLED`) (default value: `code`false). # Added methods @@ -20,5 +20,13 @@ Scratch pad for changes destined for the 5.10.0 release notes page. # Non-backward-compatible changes (but that probably won't cause problems) +# Non-backward-compatible changes + +When using Require.js and AMD modules, from Tapestry 5.10.0 on, +the previously returned objects, functions or values are now +the `default` property of the object received from `require()`. +This is a consequence we couldn't avoid from the CoffeeScript +to JavaScript to TypeScript conversion. + # Overall notes diff --git a/build.gradle b/build.gradle index 94355316c5..a80b696370 100755 --- a/build.gradle +++ b/build.gradle @@ -500,29 +500,12 @@ task aggregateJavadoc(type: Javadoc) { } } -task coffeeScriptDocs(type: Exec) { +task typeScriptDocs() { group "Documentation" - description "Build docco documentation for all CoffeeScript sources" - dependsOn project(":tapestry-core").tasks.preprocessCoffeeScript - - def outputDir = file("$buildDir/documentation/coffeescript") - - def sources = files() - - subprojects.each { sub -> - sources += sub.fileTree("src/main/coffeescript", { include "**/*.coffee" }) - } - - sources += project(":tapestry-core").tasks.preprocessCoffeeScript.outputs.files.asFileTree - - // Needs to be installed via "npm install -g docco@0.6.3" - executable isWindows() ? "docco.cmd" : "docco" - args "--output", outputDir - args sources.files.sort({ a, b -> a.name.compareTo b.name }) + description "Builds typedoc documentation for all TypeScript sources" + dependsOn project(":tapestry-core").tasks.generateTypeScriptDocs } - - dependencies { if (JavaVersion.current() != JavaVersion.VERSION_1_8) { meta aggregateJavadoc.outputs.files @@ -556,7 +539,11 @@ task combinedJacocoReport(type:JacocoReport){ task continuousIntegration { // tapestry-javadoc doesn't work with Java 8 anymore. That's why it's only added if != 8. - def dependants = [subprojects.build, 'tapestry-core:testWithPrototype', combinedJacocoReport] + def dependants = [subprojects.build, // jQuery and Require.js enabled +// 'tapestry-core:testWithJqueryAndRequireJsDisabled', +// 'tapestry-core:testWithPrototypeAndRequireJsEnabled', + 'tapestry-core:testWithPrototypeAndRequireJsDisabled', + combinedJacocoReport] if (JavaVersion.current() != JavaVersion.VERSION_1_8) { dependants << aggregateJavadoc } @@ -568,9 +555,7 @@ task zippedSources(type: Zip) { description "Creates a combined Zip file of all sub-project's sources" group "Release artifact" - dependsOn("tapestry-beanvalidator:compileCoffeeScript") - dependsOn("tapestry-core:compileCoffeeScript") - dependsOn("tapestry-core:compileProcessedCoffeescript") + dependsOn("tapestry-core:compileTypeScript") destinationDirectory = buildDir archiveBaseName = "apache-tapestry" @@ -587,11 +572,14 @@ task zippedSources(type: Zip) { exclude "**/target/**" exclude "**/build/**" exclude "**/test-output/**" // Left around by TestNG sometimes + exclude "**/modules/***.js" + exclude "**/es-modules/***.js" } task zippedApidoc(type: Zip) { + dependsOn typeScriptDocs dependsOn aggregateJavadoc - description "Zip archive of the project's aggregate JavaDoc and CoffeeScript documentation" + description "Zip archive of the project's aggregate JavaDoc and TypeScript documentation" group "Release artifact" destinationDirectory = buildDir @@ -611,7 +599,7 @@ task zippedApidoc(type: Zip) { into "apidocs", { from aggregateJavadoc.outputs.files } - into "coffeescript", { from coffeeScriptDocs.outputs.files } + into "typescript", { from typeScriptDocs.outputs.files } } diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle deleted file mode 100644 index c08ff05d49..0000000000 --- a/buildSrc/build.gradle +++ /dev/null @@ -1,17 +0,0 @@ -apply plugin: "groovy" - -repositories { - mavenCentral() -} - - -dependencies { - implementation ("ro.isdc.wro4j:wro4j-extensions:1.8.0"){ - exclude group: 'org.jruby' - exclude module: 'spring-web' - exclude module: 'closure-compiler' - exclude module: 'gmaven-runtime-1.7' - exclude module: 'less4j' - } - gradleApi() -} diff --git a/buildSrc/src/main/groovy/t5build/CompileCoffeeScript.groovy b/buildSrc/src/main/groovy/t5build/CompileCoffeeScript.groovy deleted file mode 100644 index 19bc8aa6c9..0000000000 --- a/buildSrc/src/main/groovy/t5build/CompileCoffeeScript.groovy +++ /dev/null @@ -1,58 +0,0 @@ -package t5build - -import ro.isdc.wro.model.resource.* -import ro.isdc.wro.extensions.processor.js.* -import ro.isdc.wro.extensions.processor.support.coffeescript.* -import org.gradle.api.* -import org.gradle.api.tasks.* - -class CompileCoffeeScript extends DefaultTask { - - { - description = "Compiles CoffeeScript sources into JavaScript" - group = "build" - } - - def srcDir = "src/main/coffeescript" - - def outputDir = "${project.buildDir}/compiled-coffeescript" - - @InputDirectory - File getSrcDir() { project.file(srcDir) } - - @OutputDirectory - File getOutputDir() { project.file(outputDir) } - - @TaskAction - void doCompile() { - logger.info "Compiling CoffeeScript sources from $srcDir into $outputDir" - - def outputDirFile = getOutputDir() - // Recursively delete output directory if it exists - outputDirFile.deleteDir() - - def tree = project.fileTree srcDir, { - include '**/*.coffee' - } - - def processor = new RhinoCoffeeScriptProcessor() - - tree.visit { visit -> - if (visit.directory) return - - def inputFile = visit.file - def inputPath = visit.path - def outputPath = inputPath.replaceAll(/\.coffee$/, '.js') - def outputFile = new File(outputDirFile, outputPath) - - logger.info "Compiling ${inputPath}" - - outputFile.parentFile.mkdirs() - - def resource = Resource.create(inputFile.absolutePath, ResourceType.JS) - - processor.process(resource, inputFile.newReader(), outputFile.newWriter()) - } - } - -} diff --git a/buildSrc/src/main/groovy/t5build/PreprocessCoffeeScript.groovy b/buildSrc/src/main/groovy/t5build/PreprocessCoffeeScript.groovy deleted file mode 100644 index 10c9481167..0000000000 --- a/buildSrc/src/main/groovy/t5build/PreprocessCoffeeScript.groovy +++ /dev/null @@ -1,103 +0,0 @@ -package t5build - -import org.gradle.api.* -import org.gradle.api.tasks.* - -/** - * Used by the tapestry-core module, to split t5-core-dom into two flavors: one for prototype, one for jQuery. - */ -class PreprocessCoffeeScript extends DefaultTask { - - { - description = "Splits CoffeeScript source files into multiple flavors." - group = "build" - } - - @Internal - def flavors = ["prototype", "jquery"] - def srcDir = "src/main/preprocessed-coffeescript" - def outputDir = "${project.buildDir}/postprocessed-coffeescript" - - @InputDirectory - File getSrcDir() { project.file(srcDir) } - - @OutputDirectory - File getOutputDir() { project.file(outputDir) } - - @TaskAction - void doSplit() { - logger.info "Splitting CoffeeScript sources from $srcDir into $outputDir ($flavors)" - - def outputDirFile = getOutputDir() - // Recursively delete output directory if it exists - outputDirFile.deleteDir() - - def tree = project.fileTree srcDir, { - include '**/*.coffee' - } - - tree.visit { visit -> - if (visit.directory) return - - def inputFile = visit.file - def inputPath = visit.path - - flavors.each { flavor -> - - def dotx = inputPath.lastIndexOf "." - - def outputPath = inputPath.substring(0, dotx) + "-${flavor}.coffee" - - def outputFile = new File(outputDirFile, outputPath) - - logger.info "Generating ${outputPath} from ${inputPath}" - - outputFile.parentFile.mkdirs() - - split inputFile, outputFile, flavor - } - } - } - - // Very sloppy; doesn't actually differentiate between #if and #elseif (nesting is not actually - // supported). Some more C Macro support would be nice, too. - @Internal - def ifPattern = ~/^#(else)?if\s+(\w+)$/ - - void split(File inputFile, File outputFile, String flavor) { - - def ignoring = false - - outputFile.withPrintWriter { pw -> - - inputFile.eachLine { line -> - - def matcher = ifPattern.matcher line - - if (matcher.matches()) { - - // ignore the block unless it matches the flavor - ignoring = matcher[0][2] != flavor - - // And don't copy the "#if" line at all. - return; - } - - // Note that we don't check for nested #if, and those aren't supported. - - if (line == "#endif") { - ignoring = false; - return; - } - - if (ignoring) { - return; - } - - // Copy the line to the output: - - pw.println line - } - } - } -} 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-beanvalidator/build.gradle b/tapestry-beanvalidator/build.gradle index 47f4aeefb3..7c20eb3ea4 100644 --- a/tapestry-beanvalidator/build.gradle +++ b/tapestry-beanvalidator/build.gradle @@ -17,10 +17,6 @@ dependencies { } -task compileCoffeeScript(type: CompileCoffeeScript) { - outputDir "src/main/generated/compiled-coffeescript" -} - // Start up the test app, useful when debugging failing integration tests task runTestApp303(type:JavaExec) { main = 'org.apache.tapestry5.test.JettyRunner' @@ -28,12 +24,4 @@ task runTestApp303(type:JavaExec) { classpath += project.sourceSets.test.runtimeClasspath } -clean.delete 'src/main/generated' - -sourceSets { - main { - output.dir(compileCoffeeScript.outputDir, builtBy: compileCoffeeScript) - } -} - jar.manifest.attributes 'Tapestry-Module-Classes': 'org.apache.tapestry5.beanvalidator.modules.BeanValidatorModule' diff --git a/tapestry-beanvalidator/src/main/coffeescript/META-INF/modules/t5/beanvalidator/beanvalidator-validation.coffee b/tapestry-beanvalidator/src/main/coffeescript/META-INF/modules/t5/beanvalidator/beanvalidator-validation.coffee deleted file mode 100644 index c721088cc5..0000000000 --- a/tapestry-beanvalidator/src/main/coffeescript/META-INF/modules/t5/beanvalidator/beanvalidator-validation.coffee +++ /dev/null @@ -1,70 +0,0 @@ -# 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/beanvalidator/beanvalidator-validation -# -# The awkward name is to accomidate the "docco" documentation tool; it doesn't understand -# having the same named file in multiple folders. See https://github.com/jashkenas/docco/issues/201. -# -# Supports extra validations related to the beanvalidator module. -define ["underscore", "t5/core/dom", "t5/core/events", "t5/core/utils", "t5/core/validation"], - (_, dom, events, utils) -> - - rangeValue = (element, attribute, defaultValue) -> - v = element.attr attribute - if v is null - defaultValue - else - parseInt v - - countOptions = (e) -> - # A select that is used as part of a palette is different; the validation attributes - # are attached to the selected (right side) may have multiple options (the clumsy control-click way) - _.filter(e.element.options, (o) -> o.selected).length - - doRangeValidate = (element, value, memo) -> - min = rangeValue element, "data-range-min", 0 - max = rangeValue element, "data-range-max", Number.MAX_VALUE - - # If the translated value is still a string, and not a number, then the - # size refers to the length of the string, not its numeric value. - if _.isString value - value = value.length - - unless min <= value <= max - memo.error = (element.attr "data-range-message") or "RANGE ERROR" - return false - - return true - - dom.onDocument events.field.optional, "[data-optionality=prohibited]", (event, memo) -> - - unless utils.isBlank memo.value - memo.error = (@attr "data-prohibited-message") or "PROHIBITED" - return false - - return true - - dom.onDocument events.field.validate, "input[data-range-min], input[data-range-max], textarea[data-range-min], textarea[data-range-max]", (event, memo) -> - doRangeValidate this, memo.translated, memo - - dom.onDocument events.field.validate, "select[data-range-min], select[data-range-max]", (event, memo) -> - doRangeValidate this, (countOptions this), memo - - return diff --git a/tapestry-core/build.gradle b/tapestry-core/build.gradle index 328e1e9eb8..1e66536cd1 100644 --- a/tapestry-core/build.gradle +++ b/tapestry-core/build.gradle @@ -1,6 +1,7 @@ import org.gradle.plugins.ide.idea.model.* + import org.apache.tools.ant.filters.ReplaceTokens -import t5build.* +//import t5build.* description = "Central module for Tapestry, containing all core services and components" @@ -22,9 +23,6 @@ dependencies { implementation 'jakarta.annotation:jakarta.annotation-api:2.0.0' implementation 'jakarta.xml.bind:jakarta.xml.bind-api:2.3.2' -// implementation 'org.glassfish.jaxb:jaxb-runtime:2.3.2' -// implementation 'com.sun.xml.ws:jaxws-rt:2.3.2' -// implementation 'javax.xml.ws:jaxws-api:2.3.1' provided project(":tapestry-test") provided project(":tapestry-test-constants") @@ -41,34 +39,55 @@ dependencies { testRuntimeOnly 'com.google.inject:guice:3.0' } -task preprocessCoffeeScript(type: PreprocessCoffeeScript) { +def npmWorkingDir = "src/main/typescript/" + +task npmInstall(type: Exec) { + group "TypeScript" + description "Runs npm install" + + workingDir = npmWorkingDir + commandLine isWindows() ? "npm.cmd" : "npm", 'install' +} + +task compileTypeScriptToAmd(type: Exec) { + dependsOn npmInstall + + workingDir = npmWorkingDir + commandLine isWindows() ? "npm.cmd" : "npm", 'run', 'build-amd' } -task compileCoffeeScript(type: CompileCoffeeScript) { - outputDir "${mainGeneratedDir}/compiled-coffeescript" +task compileTypeScriptToEsModule(type: Exec) { + dependsOn npmInstall + + workingDir = npmWorkingDir + commandLine isWindows() ? "npm.cmd" : "npm", 'run', 'build-es-module' } -task compileProcessedCoffeescript(type: CompileCoffeeScript) { - dependsOn preprocessCoffeeScript - srcDir preprocessCoffeeScript.outputDir - outputDir "${mainGeneratedDir}/compiled-processed-coffeescript" +task compileTypeScript() { + dependsOn compileTypeScriptToAmd + dependsOn compileTypeScriptToEsModule } -task compileTestCoffeeScript(type: CompileCoffeeScript) { - srcDir "src/test/coffeescript" - outputDir "${testGeneratedDir}/compiled-coffeescript" +task generateTypeScriptDocs(type: Exec) { + dependsOn npmInstall + + workingDir = npmWorkingDir + commandLine isWindows() ? "npm.cmd" : "npm", 'run', 'docs' } -sourceSets { - main { - output.dir(compileCoffeeScript.outputDir, builtBy: compileCoffeeScript) - output.dir(compileProcessedCoffeescript.outputDir, builtBy: compileProcessedCoffeescript) +task cleanTypeScriptFiles(type: Delete) { + delete fileTree("src/main/resources/META-INF/assets/es-modules/t5/core") { + include '**.js' } - test { - output.dir(compileTestCoffeeScript.outputDir, builtBy: compileTestCoffeeScript) + delete fileTree("src/main/resources/META-INF/modules/t5/core") { + include '**.js' } + delete "src/main/typescript/docs" } +processResources.dependsOn compileTypeScript +clean.dependsOn cleanTypeScriptFiles + // Not sure why this is necessary: compileTestGroovy.dependsOn compileTestJava @@ -124,6 +143,20 @@ task runTestAppfolder(type:JavaExec) { classpath += project.sourceSets.test.runtimeClasspath } -task testWithPrototype(type:Test) { - systemProperties."tapestry.javascript-infrastructure-provider" = "prototype" +// Default is jQuery and Require.js enabled, so here are the tests for the +// other combinations + +task testWithJqueryAndRequireJsDisabled(type:Test) { + systemProperties."tapestry.javascript-infrastructure-provider" = "jquery" + systemProperties."tapestry.require-js-enabled" = "false" } + +task testWithPrototypeAndRequireJsEnabled(type:Test) { + systemProperties."tapestry.javascript-infrastructure-provider" = "prototype" + systemProperties."tapestry.require-js-enabled" = "true" +} + +task testWithPrototypeAndRequireJsDisabled(type:Test) { + systemProperties."tapestry.javascript-infrastructure-provider" = "prototype" + systemProperties."tapestry.require-js-enabled" = "false" +} \ No newline at end of file diff --git a/tapestry-core/compile-coffeescript b/tapestry-core/compile-coffeescript deleted file mode 100755 index bf99d9950a..0000000000 --- a/tapestry-core/compile-coffeescript +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh -# This is handy when developing as the Gradle build can be very slow. -# Starts coffee in --watch mode, so it will keep compiling files on change. -# Expects coffee to be on the path. -# Should be executed from the tapestry-core folder. -# Does NOT compile the test scripts. - -inputDir=src/main/coffeescript -outputDir=src/main/generated/compiled-coffeescript - -coffee --watch --output $outputDir $inputDir diff --git a/tapestry-core/compile-test-coffeescript b/tapestry-core/compile-test-coffeescript deleted file mode 100755 index 2f58178ef5..0000000000 --- a/tapestry-core/compile-test-coffeescript +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh -# This is handy when developing as the Gradle build can be very slow. -# Starts coffee in --watch mode, so it will keep compiling files on change. -# Expects coffee to be on the path. -# Should be executed from the tapestry-core folder. -# Doe NOT compile the test scripts. - -inputDir=src/test/coffeescript -outputDir=src/test/generated/compiled-coffeescript - -coffee --watch --output $outputDir $inputDir \ No newline at end of file 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.coffee deleted file mode 100644 index 8e57943246..0000000000 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/ajax.coffee +++ /dev/null @@ -1,95 +0,0 @@ -# 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 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.coffee deleted file mode 100644 index 0dca35d2bc..0000000000 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/ajaxformloop.coffee +++ /dev/null @@ -1,87 +0,0 @@ -# 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) -> - - # "afl" is short for "AjaxFormLoop". - AFL_SELECTOR = "[data-container-type='core/AjaxFormLoop']" - FRAGMENT_TYPE = "core/ajaxformloop-fragment" - - dom.onDocument "click", "#{AFL_SELECTOR} [data-afl-behavior=remove]", -> - - afl = @findParent AFL_SELECTOR - - unless afl - console.error "Enclosing element for AjaxFormLoop remove row link not found." - return false - - 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. - - fragment = @findParent "[data-container-type='#{FRAGMENT_TYPE}']" - - # 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() - - return false - - dom.onDocument "click", "#{AFL_SELECTOR} [data-afl-behavior=insert-before] [data-afl-trigger=add]", -> - - afl = @findParent AFL_SELECTOR - - insertionPoint = @findParent "[data-afl-behavior=insert-before]" - - url = afl.attr "data-inject-row-url" - - ajax url, - success: (response) -> - content = response.json?.content or "" - - # 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 - - - insertionPoint.insertBefore newElement - - # 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 - - return - - return false - - # This module is all event handlers, and no exported functions. - return \ No newline at end of file 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.coffee deleted file mode 100644 index 778515020f..0000000000 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/alert.coffee +++ /dev/null @@ -1,142 +0,0 @@ -# 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" - error: "alert-danger" - - getURL = (container) -> container.attr "data-dismiss-url" - - removeAlert = (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 - 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 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.coffee deleted file mode 100644 index fe09a564a8..0000000000 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/autocomplete.coffee +++ /dev/null @@ -1,48 +0,0 @@ -# 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 - 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 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.coffee deleted file mode 100644 index fd9fcbe1ac..0000000000 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/bootstrap.coffee +++ /dev/null @@ -1,29 +0,0 @@ -# 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"], - -> - - exports = - # Generates the CSS class name for an icon. - # - # * name - of icon, e.g., "arrow-left" - glyph: (name) -> """""" - - return exports - - 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.coffee deleted file mode 100644 index 1d79e849c3..0000000000 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/confirm-click.coffee +++ /dev/null @@ -1,96 +0,0 @@ -# ## 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 } 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.coffee deleted file mode 100644 index 1f09456978..0000000000 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/console.coffee +++ /dev/null @@ -1,218 +0,0 @@ -# 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"], - (dom, _, { glyph }) -> - - nativeConsole = null - - 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 - - floatingConsole = null - messages = null - - noFilter = -> true - - filter = noFilter - - updateFilter = (text) -> - if text is "" - filter = noFilter - return - - words = text.toLowerCase().split /\s+/ - - filter = (e) -> - content = e.text().toLowerCase() - - for word in words - return false if content.indexOf(word) < 0 - - return true - - return - - consoleAttribute = dom.body.attr "data-floating-console" - - forceFloating = (consoleAttribute is "enabled") or (consoleAttribute is "invisible") - - button = (action, icon, label, disabled = false) -> """ - - """ - - # _internal_: displays the message inside the floating console, creating the floating - # console as needed. - display = (className, message) -> - - unless 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 is "invisible" - floatingConsole.hide() - - messages = floatingConsole.findFirst ".message-container" - - floatingConsole.findFirst("[data-action=enable]").attr "disabled", true - - floatingConsole.on "click", "[data-action=clear]", -> - floatingConsole.hide() - messages.update "" - - floatingConsole.on "click", "[data-action=disable]", -> - - @attr "disabled", true - floatingConsole.findFirst("[data-action=enable]").attr "disabled", false - - messages.hide() - - return false - - floatingConsole.on "click", "[data-action=enable]", -> - - @attr "disabled", true - floatingConsole.findFirst("[data-action=disable]").attr "disabled", false - - messages.show() - - return false - - floatingConsole.on "change keyup", "input", -> - updateFilter @value() - - for e in messages.children() - visible = filter e - - e[if visible then "show" else "hide"]() - - return false - - 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. - 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 - - # 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) -> - - message = "RequireJS error: #{err?.requireType or 'unknown'}" - - if err.message - message += """: #{err.message}""" - - if err.requireType - modules = err?.requireModules - if modules and modules.length > 0 - message += """, modules #{modules.join(", ")}""" - - if err.fileName - message += """, #{err.fileName}""" - - if err.lineNumber - message += """, line #{err.lineNumber}""" - - if err.columnNumber - message += """, line #{err.columnNumber}""" - - 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 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.coffee deleted file mode 100644 index e4ea18f087..0000000000 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/datefield.coffee +++ /dev/null @@ -1,180 +0,0 @@ -# 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: - input: value - onerror: (message) => - @field.removeClass "ajax-wait" - @fieldError message - - @showPopup() - return - - success: (response) => - @field.removeClass "ajax-wait" - reply = response.json - - if reply.result - @clearFieldError() - [year, month, day] = reply.result.split '-' - - date = new Date year, month-1, day - @datePicker.setDate date - - if reply.error - @fieldError (_.escape reply.error) - - @datePicker.setDate null - - @showPopup() - return - - fieldError: (message) -> - @field.focus().trigger events.field.showValidationError, { message } - - clearFieldError: -> - @field.trigger events.field.clearValidationError - - createPopup: -> - @datePicker = new DatePicker() - @datePicker.setFirstWeekDay datePickerFirstDay - - @datePicker.setLocalizations monthsLabels, daysLabels, todayLabel, noneLabel - - @popup = dom.create("div", { class: "datefield-popup well"}).append @datePicker.create() - @container.insertAfter @popup - - @datePicker.onselect = _.bind @onSelect, this - - onSelect: -> - date = @datePicker.getDate() - - if date is null - @hidePopup() - @clearFieldError() - @field.value "" - return - - @field.addClass "ajax-wait" - - normalizedFormat = "#{date.getFullYear()}-#{date.getMonth()+1}-#{date.getDate()}" - ajax (@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 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.coffee deleted file mode 100644 index ec7da0bf8f..0000000000 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/events.coffee +++ /dev/null @@ -1,187 +0,0 @@ -# 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 - 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 - 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 - 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. - 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). - changeVisibility: "t5:fragment:change-visibility" - # 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.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/exception-display.coffee deleted file mode 100644 index 5559c07d95..0000000000 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/exception-display.coffee +++ /dev/null @@ -1,30 +0,0 @@ -# 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) -> - - dom.onDocument "click", "[data-behavior=stack-trace-filter-toggle]", -> - checked = @element.checked - - for traceList in dom.body.find ".stack-trace" - traceList[if checked then "addClass" else "removeClass"] "filtered" - - return - - return null 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.coffee deleted file mode 100644 index f587ecc4e5..0000000000 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/exception-frame.coffee +++ /dev/null @@ -1,64 +0,0 @@ -# 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) -> - - write = (container, content) -> - iframe = (container.findFirst "iframe").element - - # See http://xkr.us/articles/dom/iframe-document/ - - iframeDocument = iframe.contentWindow or iframe.contentDocument - if iframeDocument.document - iframeDocument = iframeDocument.document - - # Clear current content: - iframeDocument.open() - # Write new content: - iframeDocument.write content - iframeDocument.close() - - clear = -> - container = @closest '.exception-container' - container.remove() - return false - - create = -> - - container = dom.create - class: "exception-container" - """ - -
- -
- """ - - dom.body.append container.hide() - - container.on "click", "button", clear - container - - # Export single function: - - (exceptionContent) -> - container = create() - write container, exceptionContent - container.show() - return \ No newline at end of file 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.coffee deleted file mode 100644 index a43dc61f79..0000000000 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/fields.coffee +++ /dev/null @@ -1,195 +0,0 @@ -# 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 - "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 - - return block - - showValidationError = (id, message) -> - dom.wrap(id).trigger events.field.showValidationError, { message } - - collectOptionValues = (wrapper) -> - _.pluck wrapper.element.options, "value" - - # Default registrations: - - dom.onDocument events.field.inputValidation, (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()) - - failure = false - - fieldValue = - if (@attr "data-value-mode") is "options" - collectOptionValues this - else if @element.type is "checkbox" - @checked() - else - @value() - - memo = value: fieldValue - - postEventTrigger = => - if memo.error - # Assume the event handler displayed the message. - failure = true - - if _.isString memo.error - - @trigger events.field.showValidationError, { message: memo.error } - - @trigger events.field.optional, memo - - postEventTrigger() - - unless failure or (utils.isBlank memo.value) - - @trigger events.field.translate, memo - - postEventTrigger() - - unless failure - if _.isUndefined memo.translated - memo.translated = memo.value - - @trigger events.field.validate, memo - - postEventTrigger() - - if failure - formMemo.error = true - this.attr('aria-invalid', 'true'); - this.attr('aria-describedby', this.attr('id') + "-help-block"); - else - this.attr('aria-invalid', 'false'); - this.attr('aria-describedby ', null); - @trigger events.field.clearValidationError - - return - - dom.onDocument events.field.clearValidationError, -> - blocks = exports.findHelpBlocks this - - for block in blocks or [] - block.hide().update("") - block.parent().removeClass "has-error" - block.attr("role", null) - - group = @findParent ".form-group" - - group and group.removeClass "has-error" - - return - - dom.onDocument events.field.showValidationError, (event, memo) -> - blocks = exports.findHelpBlocks this - - unless 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") - - group = @findParent ".form-group" - - container = group or @parent().closest(":not(.input-group)") - - container.addClass "has-error" - - 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.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/form-fragment.coffee deleted file mode 100644 index ce4e1138cf..0000000000 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/form-fragment.coffee +++ /dev/null @@ -1,98 +0,0 @@ -# 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 } 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.coffee deleted file mode 100644 index 00a8e6a341..0000000000 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/forms.coffee +++ /dev/null @@ -1,211 +0,0 @@ -# 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" - - # 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 + "']" - - clearSubmittingHidden = (form) -> - hidden = form.findFirst "[name='t:submit']" - - # Clear if found - hidden and hidden.value null - - form.meta SKIP_VALIDATION, null - - return - - setSubmittingHidden = (form, submitter) -> - - mode = submitter.attr "data-submit-mode" - isCancel = mode is "cancel" - if mode and mode isnt "normal" - form.meta SKIP_VALIDATION, true - - 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 - - # 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}\"]" - - 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 = {} - - fields = form.find "input, select, textarea" - - _.each fields, (field) -> - return if field.attr "disabled" - - type = field.element.type - - # 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" - - return if (type is "checkbox" or type is "radio") and field.checked() is false - - value = field.value() - - return if value is null - - name = field.element.name - - # 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 "" - - existing = result[name] - - if _.isArray existing - existing.push value - return - - if existing - result[name] = [existing, value] - return - - result[name] = value - - return result - - - defaultValidateAndSubmit = -> - - where = -> "processing form submission" - - try - - if ((@attr "data-validate") is "submit") and - (not @meta SKIP_VALIDATION) - - @meta SKIP_VALIDATION, null - - hasError = false - focusField = null - - for field in @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 - - # 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 - - hasError = memo.error - - 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 - - # Trigger an event to inform that form validation results in error - where = -> "triggering validation in error event" - @trigger events.form.validateInError - - # 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)" - - @trigger events.form.prepareForSubmit - - 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 - - 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 - - # 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" - - unless form - console.error "Submitting link element not contained inside a form element." - return false - - setSubmittingHidden form, @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. - - form.trigger "submit" - - # And cancel the default behavior for the original click event - return false - - exports = - gatherParameters: 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 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.coffee deleted file mode 100644 index e1fbe3ee2f..0000000000 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/graphviz.coffee +++ /dev/null @@ -1,29 +0,0 @@ -# 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 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.coffee deleted file mode 100644 index 67aa07bc38..0000000000 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/init.coffee +++ /dev/null @@ -1,30 +0,0 @@ -# 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"], - - (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 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.coffee deleted file mode 100644 index 3ec7ca28a5..0000000000 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/localdate.coffee +++ /dev/null @@ -1,37 +0,0 @@ -# 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 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.coffee deleted file mode 100644 index 957cccea48..0000000000 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/messages.coffee +++ /dev/null @@ -1,55 +0,0 @@ -# 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 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.coffee deleted file mode 100644 index 4b11254106..0000000000 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/moment.coffee +++ /dev/null @@ -1,25 +0,0 @@ -# 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" - - moment.locale(locale) - - return moment \ No newline at end of file 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.coffee deleted file mode 100644 index 34f3364494..0000000000 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/pageinit.coffee +++ /dev/null @@ -1,241 +0,0 @@ -# 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) - .pluck("href") - .without("") - .without(null) - .map(rebuildURLOnIE) - - insertionPoint = _.find(document.styleSheets, (ss) -> - parent = ss.ownerNode || ss.owningElement - parent.rel is "stylesheet ajax-insertion-point") - - # Most browsers support document.head, but older IE doesn't: - head = document.head or 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) -> - - 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 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.coffee deleted file mode 100644 index 52fa984044..0000000000 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/palette.coffee +++ /dev/null @@ -1,300 +0,0 @@ -# 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]" - - @select = @container.findFirst "[data-action=select]" - @deselect = @container.findFirst "[data-action=deselect]" - - @moveUp = @container.findFirst "[data-action=move-up]" - @moveDown = @container.findFirst "[data-action=move-down]" - - # Track where reorder is allowed based on whether the buttons actually exist - @reorder = @moveUp isnt null - - @valueToOrderIndex = {} - - for option,i in @available.element.options - @valueToOrderIndex[option.value] = i - - # This occurs even when the palette is disabled, to present the - # values correctly. Otherwise it looks like nothing is selected. - @initialTransfer() - - unless @selected.element.disabled - @updateButtons() - @bindEvents() - - initialTransfer: -> - # Get the values for options that should move over - values = JSON.parse @hidden.value() - valueToPosition = {} - - for v, i in values - valueToPosition[v] = i - - e = @available.element - - movers = [] - - 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 - - for option in movers - @selected.element.add option - - # Invoked after any change to the selections list to update the hidden field as well as the - # buttons' state. - updateAfterChange: -> - @updateHidden() - @updateButtons() - - updateHidden: -> - values = (option.value for option in @selected.element.options) - @hidden.value JSON.stringify values - - bindEvents: -> - @container.on "change", "select", => - @updateButtons() - return false - - @select.on "click", => - @doSelect() - return false - - @available.on "dblclick", => - @doSelect() - return false - - @deselect.on "click", => - @doDeselect() - return false - - @selected.on "dblclick", => - @doDeselect() - return false - - if @reorder - @moveUp.on "click", => - @doMoveUp() - return false - - @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 - - nothingSelected = @selected.element.selectedIndex < 0 - - @deselect.element.disabled = nothingSelected - - if @reorder - @moveUp.element.disabled = nothingSelected or @allSelectionsAtTop() - @moveDown.element.disabled = nothingSelected or @allSelectionsAtBottom() - - doSelect: -> @transferOptions @available, @selected, @reorder - - doDeselect: -> @transferOptions @selected, @available, false - - doMoveUp: -> - options = _.toArray @selected.element.options - - groups = _.partition options, isSelected - - movers = groups[0] - - # 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] - - options = groups[1] - - splicePos = if pivot then _.indexOf options, pivot else 0 - - movers.reverse() - - for o in movers - options.splice splicePos, 0, o - - @reorderSelected options - - - doMoveDown: -> - options = _.toArray @selected.element.options - - groups = _.partition options, isSelected - - movers = groups[0] - - # 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] - - options = groups[1] - - splicePos = if pivot then _.indexOf(options, pivot) + 1 else options.length - - movers.reverse() - - for o in movers - options.splice splicePos, 0, o - - @reorderSelected options - - # Reorders the selected options to the provided list of options; handles triggering the willUpdate and - # didUpdate events. - reorderSelected: (options) -> - - @performUpdate true, options, => - - @deleteOptions @selected - - for o in options - @selected.element.add o, null - - # Performs the update, which includes the willChange and didChange events. - performUpdate: (reorder, selectedOptions, updateCallback) -> - - canceled = false - - doUpdate = => - updateCallback() - - @selected.trigger events.palette.didChange, { selectedOptions, reorder } - - @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 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.coffee deleted file mode 100644 index f006c70735..0000000000 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/select.coffee +++ /dev/null @@ -1,32 +0,0 @@ -# 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"], - - (events, dom, zone) -> - - dom.onDocument "change", "select[data-update-zone]", -> - - containingZone = zone.findZone this - - if containingZone - containingZone.trigger events.zone.refresh, - url: @attr "data-update-url" - parameters: - "t:selectvalue": @value() - - return - return 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.coffee deleted file mode 100644 index 7221e76eff..0000000000 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/time-interval.coffee +++ /dev/null @@ -1,55 +0,0 @@ -# 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 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.coffee deleted file mode 100644 index 556dcf1bef..0000000000 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/tree.coffee +++ /dev/null @@ -1,114 +0,0 @@ -# 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"], - (dom, ajax) -> - TREE = "[data-component-type='core/Tree']" - NODE_ID = "data-node-id" - SELECTOR = "#{TREE} [#{NODE_ID}]" - - LOADING = "tree-children-loading" - LOADED = "tree-children-loaded" - EXPANDED = "tree-expanded" - SELECTED = "selected-leaf-node" - - 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 - 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.coffee deleted file mode 100644 index c89b2941a3..0000000000 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/utils.coffee +++ /dev/null @@ -1,59 +0,0 @@ -# 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 - - return false if typeof input is "boolean" - - return (exports.trim input).length is 0 - - # Splits the input string into words separated by whitespace - split: (str) -> _(str.split " ").reject((s) -> s is "") 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.coffee deleted file mode 100644 index b140f0b035..0000000000 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/validation.coffee +++ /dev/null @@ -1,178 +0,0 @@ -# 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) -> - - 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) -> - - canonical = "" - - accept = (ch) -> canonical += ch - - acceptDigitOnly = (ch) -> - if ch < "0" or ch > "9" - throw new Error messages "core-input-not-numeric" - - accept ch - return - - mustBeDigit = (ch) -> - acceptDigitOnly ch - return any - - decimalPortion = (ch) -> - acceptDigitOnly ch - return decimalPortion - - any = (ch) -> - switch ch - when grouping then return mustBeDigit - when decimal - if isInteger - throw new Error messages "core-input-not-integer" - - 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 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.coffee deleted file mode 100644 index a6b743b9c4..0000000000 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/zone-refresh.coffee +++ /dev/null @@ -1,68 +0,0 @@ -# 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 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.coffee deleted file mode 100644 index dbcbd55188..0000000000 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/zone.coffee +++ /dev/null @@ -1,160 +0,0 @@ -# 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, _) -> - - 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" - - # 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" - - if zoneId is "^" - zone = element.findParent "[data-container-type=zone]" - - if zone is null - throw new Error "Unable to locate containing zone for #{element}." - - return zone - - zone = dom zoneId - - if zone is null - throw new Error "Unable to locate zone '#{zoneId}'." - - return zone - - dom.onDocument "click", "a[data-update-zone]", (event) -> - - element = @closest "[data-update-zone]" - - unless element - throw new Error "Could not locate containing element with data-update-zone attribute." - - zone = findZone element - - if zone - zone.trigger events.zone.refresh, url: element.attr "href" - - event.nativeEvent.preventDefault() - return - - dom.onDocument "submit", "form[data-update-zone]", -> - - zone = findZone this - - if zone - formParameters = forms.gatherParameters this - - zone.trigger events.zone.refresh, - url: (@attr "action") - parameters: formParameters - - return false - - dom.onDocument "submit", "form[data-async-trigger]", -> - - formParameters = forms.gatherParameters this - - @addClass "ajax-update" - - ajax (@attr "action"), - data: formParameters - complete: => @removeClass "ajax-update" - - return false - - dom.onDocument events.zone.update, (event) -> - - @trigger events.zone.willUpdate - - content = event.memo.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. - unless content is undefined - @update content - - @trigger events.initializeComponents - @trigger events.zone.didUpdate - - dom.onDocument events.zone.refresh, (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]" - - # 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" - - parameters = attr and JSON.parse attr - - simpleIdParams = if zone.attr "data-simple-ids" then {"t:suppress-namespaced-ids": true} - - 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 - - dom.onDocument "click", "a[data-async-trigger]", (event)-> - link = @closest 'a[data-async-trigger]' - - link.addClass "ajax-update" - - ajax (link.attr "href"), - complete: -> link.removeClass "ajax-update" - - 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) -> - - _.defer -> - zone = dom id - - if zone is null - console.error "Could not locate element '#{id}' to update." - return - - zone.trigger events.zone.refresh, { url } - - # Most of this module is document-level event handlers, but there's also some exports: - return { deferredZoneUpdate, findZone } diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java b/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java index 47a5cadd79..9a5c845b0f 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java @@ -386,18 +386,6 @@ public class SymbolConstants */ public static final String MODULE_PATH_PREFIX = "tapestry.module-path-prefix"; - /** - * Prefix used for automatically configured ES module resources. - * This may contain slashes, but should not being or end with one. - * - * The default is "es-modules". - * - * TODO remove - * - * @since 5.4 - */ - public static final String ES_MODULE_PATH_PREFIX = "tapestry.es-module-path-prefix"; - /** * Identifies the context path of the application, as determined from {@link jakarta.servlet.ServletContext#getContextPath()}. * This is either a blank string or a string that starts with a slash but does not end with one. @@ -821,5 +809,16 @@ public class SymbolConstants * @since 5.8.4 */ public static final String COMPONENT_DEPENDENCY_FILE = "tapestry.component-dependency-file"; + + /** + * Defines whether Require.js will be used to manage AMD modules. + * This also causes Tapestry to use the Require.js/AMD versions of + * its own JavaScript files. When disabled, Require.js will not be included + * and the ES6 module version of its own JavaScript files will be used instead. + * Default value: {@code true}). + * + * @since 5.10.0 + */ + public static final String REQUIRE_JS_ENABLED = "tapestry.require-js-enabled"; } 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/java/org/apache/tapestry5/internal/services/javascript/ConfigureHTMLElementFilter.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/ConfigureHTMLElementFilter.java index aab9c2e945..53a9b36fe3 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/ConfigureHTMLElementFilter.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/ConfigureHTMLElementFilter.java @@ -15,6 +15,7 @@ package org.apache.tapestry5.internal.services.javascript; import org.apache.tapestry5.MarkupWriter; +import org.apache.tapestry5.SymbolConstants; import org.apache.tapestry5.dom.Element; import org.apache.tapestry5.http.TapestryHttpSymbolConstants; import org.apache.tapestry5.ioc.annotations.Symbol; @@ -33,11 +34,18 @@ public class ConfigureHTMLElementFilter implements MarkupRendererFilter private final ThreadLocale threadLocale; private final boolean debugEnabled; + + private final String requireJsEnabled; - public ConfigureHTMLElementFilter(ThreadLocale threadLocale, @Symbol(TapestryHttpSymbolConstants.PRODUCTION_MODE) boolean productionMode) + public ConfigureHTMLElementFilter(ThreadLocale threadLocale, + @Symbol(TapestryHttpSymbolConstants.PRODUCTION_MODE) + boolean productionMode, + @Symbol(SymbolConstants.REQUIRE_JS_ENABLED) + boolean requireJsEnabled) { this.threadLocale = threadLocale; this.debugEnabled = !productionMode; + this.requireJsEnabled = String.valueOf(requireJsEnabled); } public void renderMarkup(MarkupWriter writer, MarkupRenderer renderer) @@ -52,7 +60,8 @@ public void renderMarkup(MarkupWriter writer, MarkupRenderer renderer) // to describe locale, and if debug is enabled. if (html != null) { - html.attributes("data-locale", threadLocale.getLocale().toString()); + html.attributes("data-locale", threadLocale.getLocale().toString(), + "data-require-js-enabled", requireJsEnabled); if (debugEnabled) { diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/ModuleManagerImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/ModuleManagerImpl.java index c56f45a966..7f198b4569 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/ModuleManagerImpl.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/ModuleManagerImpl.java @@ -58,6 +58,10 @@ public class ModuleManagerImpl implements ModuleManager private final JSONObject baseConfig; private final String basePath, compressedBasePath; + + private final String actualDomModule; + + private static final String DOM_MODULE = "t5/core/dom"; public ModuleManagerImpl(ResponseCompressionAnalyzer compressionAnalyzer, AssetSource assetSource, @@ -70,7 +74,9 @@ public ModuleManagerImpl(ResponseCompressionAnalyzer compressionAnalyzer, boolean productionMode, @Symbol(SymbolConstants.MODULE_PATH_PREFIX) String modulePathPrefix, - PathConstructor pathConstructor) + PathConstructor pathConstructor, + @Symbol(SymbolConstants.JAVASCRIPT_INFRASTRUCTURE_PROVIDER) + String infraProvider) { this.compressionAnalyzer = compressionAnalyzer; this.globalMessages = globalMessages; @@ -85,6 +91,9 @@ public ModuleManagerImpl(ResponseCompressionAnalyzer compressionAnalyzer, extensions.addAll(streamableResourceSource.fileExtensionsForContentType(InternalConstants.JAVASCRIPT_CONTENT_TYPE)); baseConfig = buildBaseConfig(configuration, !productionMode); + + actualDomModule = "t5/core/t5-core-dom-" + infraProvider; + } private String buildRequireJSConfig(List callbacks) @@ -228,6 +237,13 @@ private Resource resolveModuleNameToResource(String moduleName) { return resource; } + + // Trick to get the correct t5/core/dom implementation being + // returned when that module is requested. + if (moduleName.equals(DOM_MODULE)) + { + moduleName = actualDomModule; + } // Tack on a fake extension; otherwise modules whose name includes a '.' get mangled // by Resource.withExtension(). diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/util/MessageCatalogResource.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/util/MessageCatalogResource.java index 8992efe59f..c8120833ea 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/util/MessageCatalogResource.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/util/MessageCatalogResource.java @@ -33,12 +33,15 @@ public class MessageCatalogResource extends VirtualResource private final boolean compactJSON; private volatile byte[] bytes; + + private final boolean esModule; - public MessageCatalogResource(Locale locale, ComponentMessagesSource messagesSource, final ResourceChangeTracker changeTracker, boolean compactJSON) + public MessageCatalogResource(boolean esModule, Locale locale, ComponentMessagesSource messagesSource, final ResourceChangeTracker changeTracker, boolean compactJSON) { this.locale = locale; this.messagesSource = messagesSource; this.compactJSON = compactJSON; + this.esModule = esModule; messagesSource.getInvalidationEventHub().addInvalidationCallback(new Runnable() { @@ -127,7 +130,15 @@ private String assembleCatalog() StringBuilder builder = new StringBuilder(2000); - builder.append("define(").append(catalog.toString(compactJSON)).append(");"); + final String json = catalog.toString(compactJSON); + if (esModule) + { + builder.append("export default ").append(json).append(";"); + } + else + { + builder.append("define(").append(json).append(");"); + } return builder.toString(); } 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..8c2c534019 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 @@ -61,6 +61,7 @@ import org.apache.tapestry5.services.compatibility.Compatibility; import org.apache.tapestry5.services.compatibility.Trait; import org.apache.tapestry5.services.javascript.AMDWrapper; +import org.apache.tapestry5.services.javascript.EsModuleConfigurationCallback; import org.apache.tapestry5.services.javascript.EsModuleManager; import org.apache.tapestry5.services.javascript.ExtensibleJavaScriptStack; import org.apache.tapestry5.services.javascript.JavaScriptModuleConfiguration; @@ -151,7 +152,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")) { @@ -493,30 +494,7 @@ public static void setupFactoryDefaults(MappedConfiguration conf { configuration.add(SymbolConstants.JAVASCRIPT_INFRASTRUCTURE_PROVIDER, "prototype"); configuration.add(SymbolConstants.MODULE_PATH_PREFIX, "modules"); - configuration.add(SymbolConstants.ES_MODULE_PATH_PREFIX, "es-modules"); - } - - @Contribute(ModuleManager.class) - public static void setupFoundationFramework(MappedConfiguration configuration, - @Symbol(SymbolConstants.JAVASCRIPT_INFRASTRUCTURE_PROVIDER) - String provider, - @Path("classpath:org/apache/tapestry5/t5-core-dom-prototype.js") - Resource domPrototype, - @Path("classpath:org/apache/tapestry5/t5-core-dom-jquery.js") - Resource domJQuery) - { - if (provider.equals("prototype")) - { - configuration.add("t5/core/dom", new JavaScriptModuleConfiguration(domPrototype)); - } - - if (provider.equals("jquery")) - { - configuration.add("t5/core/dom", new JavaScriptModuleConfiguration(domJQuery)); - } - - // If someone wants to support a different infrastructure, they should set the provider symbol to some other value - // and contribute their own version of the t5/core/dom module. + configuration.add(SymbolConstants.REQUIRE_JS_ENABLED, "true"); } @Contribute(ModuleManager.class) @@ -528,11 +506,35 @@ public static void setupApplicationCatalogModules(MappedConfiguration configuration, + LocalizationSetter localizationSetter, + ComponentMessagesSource messagesSource, + ResourceChangeTracker resourceChangeTracker, + @Symbol(SymbolConstants.COMPACT_JSON) boolean compactJSON) + { + + EsModuleConfigurationCallback callback = jsonObject -> { + + for (Locale locale : localizationSetter.getSupportedLocales()) + { + MessageCatalogResource resource = new MessageCatalogResource(false, locale, messagesSource, resourceChangeTracker, compactJSON); + + jsonObject.put("t5/core/messages/" + locale.toString(), resource.toURL()); + } + + }; + + configuration.add("ApplicationCatalog", callback); + + } + /** * Contributes 'ConfigureHTMLElement', which writes the attributes into the HTML tag to describe locale, etc. diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java b/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java index ff6075d85a..73a69419e7 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java @@ -2212,6 +2212,7 @@ public static void contributeFactoryDefaults(MappedConfiguration configuration.add(SymbolConstants.PUBLISH_OPENAPI_DEFINITON, "false"); configuration.add(SymbolConstants.OPENAPI_DESCRIPTION_PATH, "/openapi.json"); configuration.add(SymbolConstants.OPENAPI_BASE_PATH, "/"); + } /** diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/validator/Email.java b/tapestry-core/src/main/java/org/apache/tapestry5/validator/Email.java index ac3990f465..07d84631c2 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/validator/Email.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/validator/Email.java @@ -32,7 +32,7 @@ public class Email extends AbstractValidator { // The client-side uses a similar RE, but converts the input to lower case before applying the pattern. - // See validation.coffee. + // See validation.ts. private static final Pattern PATTERN = Pattern .compile("[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?", Pattern.CASE_INSENSITIVE); diff --git a/tapestry-core/src/main/preprocessed-coffeescript/org/apache/tapestry5/t5-core-dom.coffee b/tapestry-core/src/main/preprocessed-coffeescript/org/apache/tapestry5/t5-core-dom.coffee deleted file mode 100644 index 07234a83e0..0000000000 --- a/tapestry-core/src/main/preprocessed-coffeescript/org/apache/tapestry5/t5-core-dom.coffee +++ /dev/null @@ -1,993 +0,0 @@ -# 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/dom -# -# This is the abstraction layer that allows the majority of components to operate without caring whether the -# underlying infrastructure framework is Prototype, jQuery, or something else. -# -# The abstraction layer has a number of disadvantages: -# -# * It adds a number of layers of wrapper around the infrastructure framework objects -# * It is leaky; some behaviors will vary slightly based on the active infrastructure framework -# * The abstraction is alien to both Prototype and jQuery developers; it mixes some ideas from both -# * It is much less powerful or expressive than either infrastructure framework used directly -# -# It is quite concievable that some components will require direct access to the infrastructure framework, especially -# those that are wrappers around third party libraries or plugins; however many simple components may need no more than -# the abstract layer and gain the valuable benefit of not caring about the infrastructure framework. - -#if prototype -define ["underscore", "./utils", "./events", "jquery"], -(_, utils, events) -> -#elseif jquery -define ["underscore", "./utils", "./events", "jquery"], -(_, utils, events, $) -> -#endif - -#if prototype - # Save a local reference to Prototype.$ ... see notes about some challenges using Prototype, jQuery, - # and RequireJS together, here: https://github.com/jrburke/requirejs/issues/534 - $ = window.$ - - # Fires a native event; something that Prototype does not normally do. - # Returns true if the event completed normally, false if it was canceled. - fireNativeEvent = (element, eventName) -> - if document.createEventObject - # IE support: - event = document.createEventObject() - return element.fireEvent "on#{eventName}", event - - # Everyone else: - event = document.createEvent "HTMLEvents" - event.initEvent eventName, true, true - element.dispatchEvent event - return not event.defaultPrevented - - # converts a selector to an array of DOM elements - parseSelectorToElements = (selector) -> - if _.isString selector - return $$ selector - - # Array is assumed to be array of DOM elements - if _.isArray selector - return selector - - # Assume its a single DOM element - - [selector] -#endif - - # Converts content (provided to `ElementWrapper.update()` or `append()`) into an appropriate type. This - # primarily exists to validate the value, and to "unpack" an ElementWrapper into a DOM element. - convertContent = (content) -> - if _.isString content - return content - - if _.isElement content - return content - - if content instanceof ElementWrapper -#if jquery - return content.$ -#elseif prototype - return content.element -#endif - - throw new Error "Provided value <#{content}> is not valid as DOM element content." - - # Generic view of an DOM event that is passed to a handler function. - # - # Properties: - # - # * nativeEvent - the native Event object, which may provide additional information. - # * memo - the object passed to `ElementWrapper.trigger()`. - # * type - the name of the event that was triggered. - # * char - the character value of the pressed key, if a printable character, as a string. - # * key -The key value of the pressed key. This is the same as the `char` property for printable keys, - # or a key name for others. - class EventWrapper - -#if jquery - constructor: (event, memo) -> - @nativeEvent = event - @memo = memo -#elseif prototype - constructor: (event) -> - @nativeEvent = event - @memo = event.memo -#endif - # This is to satisfy YUICompressor which doesn't seem to like 'char', even - # though it doesn't appear to be a reserved word. - this[name] = event[name] for name in ["type", "char", "key"] - - # Stops the event which prevents further propagation of the DOM event, - # as well as DOM event bubbling. - stop: -> -#if jquery - @nativeEvent.preventDefault() - @nativeEvent.stopImmediatePropagation() -#elseif prototype - # There's no equivalent to stopImmediatePropagation() unfortunately. - @nativeEvent.stop() -#endif - -#if jquery - # Interface between the dom's event model, and jQuery's. - # - # * jqueryObject - jQuery wrapper around one or more DOM elements - # * eventNames - space-separated list of event names - # * match - selector to match bubbled elements, or null - # * handler - event handler function to invoke; it will be passed an `EventWrapper` instance as the first parameter, - # and the memo as the second parameter. `this` will be the `ElementWrapper` for the matched element. - # - # Event handlers may return false to stop event propogation; this prevents an event from bubbling up, and - # prevents any browser default behavior from triggering. This is often easier than accepting the `EventWrapper` - # object as the first parameter and invoking `stop()`. - # - # Returns a function of no parameters that removes any added handlers. - - onevent = (jqueryObject, eventNames, match, handler) -> - throw new Error "No event handler was provided." unless handler? - - wrapped = (jqueryEvent, memo) -> - # Set `this` to be the matched ElementWrapper, rather than the element on which the event is observed - # (which is often further up the hierarchy). - elementWrapper = new ElementWrapper $(jqueryEvent.target) - eventWrapper = new EventWrapper jqueryEvent, memo - - result = handler.call elementWrapper, eventWrapper, memo - - # If an event handler returns exactly false, then stop the event. - if result is false - eventWrapper.stop() - - return - - jqueryObject.on eventNames, match, wrapped - - # Return a function to stop listening - -> jqueryObject.off eventNames, match, wrapped -#elseif prototype - # Interface between the dom's event model, and Prototype's. - # - # * elements - array of DOM elements (or the document object) - # * eventNames - array of event names - # * match - selector to match bubbled elements, or null - # * handler - event handler function to invoke; it will be passed an `EventWrapper` instance as the first parameter, - # and the memo as the second parameter. `this` will be the `ElementWrapper` for the matched element. - # - # Event handlers may return false to stop event propagation; this prevents an event from bubbling up, and - # prevents any browser default behavior from triggering. This is often easier than accepting the `EventWrapper` - # object as the first parameter and invoking `stop()`. - # - # Returns a function of no parameters that removes any added handlers. - - onevent = (elements, eventNames, match, handler) -> - throw new Error "No event handler was provided." unless handler? - - wrapped = (prototypeEvent) -> - # Set `this` to be the matched ElementWrapper, rather than the element on which the event is observed - # (which is often further up the hierarchy). - elementWrapper = new ElementWrapper prototypeEvent.findElement() - eventWrapper = new EventWrapper prototypeEvent - - # Because there's no stopImmediatePropogation() as with jQuery, we detect if the - # event was stopped and simply stop calling the handler. - result = if prototypeEvent.stopped - false - else handler.call elementWrapper, eventWrapper, eventWrapper.memo - - # If an event handler returns exactly false, then stop the event. - if result is false - prototypeEvent.stop() - - return - - eventHandlers = [] - - for element in elements - for eventName in eventNames - eventHandlers.push (Event.on element, eventName, match, wrapped) - - # Return a function to remove the handler(s) - -> - for eventHandler in eventHandlers - eventHandler.stop() -#endif - - # Wraps a DOM element, providing some common behaviors. - # Exposes the DOM element as property `element`. - class ElementWrapper - -#if jquery - # Passed the jQuery object - constructor: (query) -> - @$ = query - @element = query[0] -#elseif prototype - constructor: (@element) -> -#endif - - # Some coders would use some JavaScript cleverness to automate more of the mapping from the ElementWrapper API - # to the jQuery API, but that eliminates a chance to write some very necessary documentation. - - toString: -> - markup = @element.outerHTML - - "ElementWrapper[#{markup.substring 0, (markup.indexOf ">") + 1}]" - - # Hides the wrapped element, setting its display to 'none'. - hide: -> -#if jquery - @$.hide() -#elseif prototype - @element.hide() -#endif - - return this - - # Displays the wrapped element if hidden. - show: -> -#if jquery - @$.show() -#elseif prototype - @element.show() -#endif - - return this - - # Gets or sets a CSS property. jQuery provides a lot of mapping of names to canonical names. - css: (name, value) -> - - if arguments.length is 1 -#if jquery - return @$.css name -#elseif prototype - return @element.getStyle name -#endif - -#if jquery - @$.css name, value -#elseif prototype - @element.setStyle name: value -#endif - - return this - - # Returns the offset of the object relative to the document. The returned object has - # keys `top`' and `left`'. - offset: -> -#if jquery - @$.offset() -#elseif prototype - @element.viewportOffset() -#endif - - # Removes the wrapped element from the DOM. It can later be re-attached. - remove: -> -#if jquery - @$.detach() -#elseif prototype - @element.remove() -#endif - - return this - - # 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. - # - # * name - the attribute to read or update, or an object of keys and values - # * value - (optional) the new value for the attribute - attr: (name, value) -> - - if _.isObject name - for attributeName, value of name - @attr attributeName, value - - return this - -#if jquery - current = @$.attr name - if arguments.length > 1 - if value is null - @$.removeAttr name - else - @$.attr name, value - if _.isUndefined current - current = null -#elseif prototype - current = @element.readAttribute name - if arguments.length > 1 - # Treat undefined and null the same; Prototype does something slightly odd, - # treating undefined as a special case where the attribute value matches - # the attribute name. - @element.writeAttribute name, if value is undefined then null else value -#endif - - return current - - # Moves the cursor to the field. - focus: -> -#if jquery - @$.focus() -#elseif prototype - @element.focus() -#endif - - return this - - # Returns true if the element has the indicated class name, false otherwise. - hasClass: (name) -> -#if jquery - @$.hasClass name -#elseif prototype - @element.hasClassName name -#endif - - # Removes the class name from the element. - removeClass: (name) -> -#if jquery - @$.removeClass name -#elseif prototype - @element.removeClassName name -#endif - - return this - - # Adds the class name to the element. - addClass: (name) -> -#if jquery - @$.addClass name -#elseif prototype - @element.addClassName name -#endif - - return this - - # 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). - update: (content) -> -#if jquery - @$.empty() - - if content - @$.append (convertContent content) -#elseif prototype - @element.update (content and convertContent content) -#endif - - return this - - # Appends new content (Element, ElementWrapper, or HTML markup string) to the body of the element. - append: (content) -> -#if jquery - @$.append (convertContent content) -#elseif prototype - @element.insert bottom: (convertContent content) -#endif - - return this - - # Prepends new content (Element, ElementWrapper, or HTML markup string) to the body of the element. - prepend: (content) -> -#if jquery - @$.prepend (convertContent content) -#elseif prototype - @element.insert top: (convertContent content) -#endif - - return this - - # Inserts new content (Element, ElementWrapper, or HTML markup string) into the DOM immediately before - # this ElementWrapper's element. - insertBefore: (content) -> -#if jquery - @$.before (convertContent content) -#elseif prototype - @element.insert before: (convertContent content) -#endif - - return this - - # Inserts new content (Element, ElementWrapper, or HTML markup string) into the DOM immediately after - # this ElementWrapper's element. - insertAfter: (content) -> -#if jquery - @$.after (convertContent content) -#elseif prototype - @element.insert after: (convertContent content) -#endif - - return this - - # Finds the first child element that matches the CSS selector, wrapped as an ElementWrapper. - # Returns null if not found. - findFirst: (selector) -> -#if jquery - match = @$.find selector - - if match.length - # At least one element was matched, just keep the first - new ElementWrapper match.first() -#elseif prototype - match = @element.down selector - - # Prototype returns undefined if not found, we want to return null. - if match - new ElementWrapper match -#endif - else - return null - - # Finds _all_ child elements matching the CSS selector, returning them - # as an array of ElementWrappers. - find: (selector) -> -#if jquery - matches = @$.find selector - - for i in [0...matches.length] - new ElementWrapper matches.eq i -#elseif prototype - matches = @element.select selector - - new ElementWrapper(e) for e in matches -#endif - - # Find the first container element that matches the selector (wrapped as an ElementWrapper), - # or returns null. - findParent: (selector) -> -#if jquery - parents = @$.parents selector - - return null unless parents.length - - new ElementWrapper parents.eq(0) -#elseif prototype - parent = @element.up selector - - return null unless parent - - new ElementWrapper parent -#endif - - # 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. - closest: (selector) -> -#if jquery - match = @$.closest selector - - switch - when match.length is 0 then return null - when match[0] is @element then return this - else return new ElementWrapper match -#elseif prototype - if @element.match selector - return this - - return @findParent selector -#endif - - # 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). - parent: -> -#if jquery - parent = @$.parent() - - return null unless parent.length -#elseif prototype - parent = @element.parentNode - - return null unless parent -#endif - new ElementWrapper parent - - # Returns an array of all the immediate child elements of this element, as ElementWrappers. - children: -> -#if jquery - children = @$.children() - - for i in [0...children.length] - new ElementWrapper children.eq i - -#elseif prototype - new ElementWrapper(e) for e in @element.childElements() -#endif - - # Returns true if this element is visible, false otherwise. This does not check to see if all containers of the - # element are visible. - visible: -> -#if jquery - @$.css("display") isnt "none" -#elseif prototype - @element.visible() -#endif - - # Returns true if this element is visible, and all parent elements are also visible, up to the document body. - deepVisible: -> - element = this.element - element.offsetWidth > 0 && element.offsetHeight > 0 - - # 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). - # - # * eventName - name of event to trigger on the wrapped Element - # * 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 true if the event fully executed, or false if the event was canceled. - trigger: (eventName, memo) -> - throw new Error "Attempt to trigger event with null event name" unless eventName? - - unless (_.isNull memo) or (_.isObject memo) or (_.isUndefined memo) - throw new Error "Event memo may be null or an object, but not a simple type." - -#if jquery - jqEvent = $.Event eventName - - @$.trigger jqEvent, memo - - return not jqEvent.isImmediatePropagationStopped() -#elseif prototype - if (eventName.indexOf ':') > 0 - # Custom event is supported directly by Prototype: - event = @element.fire eventName, memo - return not event.defaultPrevented - - # Native events take some extra work: - if memo - throw new Error "Memo must be null when triggering a native event" - - # Hacky solution for TAP5-2602 (5.4 LinkSubmit does not work with Prototype JS) - unless Prototype.Browser.WebKit and eventName == 'submit' and @element instanceof HTMLFormElement - fireNativeEvent @element, eventName - else - @element.requestSubmit() - -#endif - - # With no parameters, returns the current value of the element (which must be a form control element, such as `` or - # `