├── .gitignore ├── README.adoc ├── build.gradle ├── build.sbt ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── project ├── Dependencies.scala ├── build.properties └── plugins.sbt ├── sbt ├── sbt-dist └── conf │ ├── sbtconfig.txt │ └── sbtopts ├── sbt.bat └── src └── main ├── resources ├── application.properties ├── import.sql ├── messages.properties ├── static │ ├── css │ │ └── application.css │ └── js │ │ ├── jquery-1.7.2.js │ │ └── jquery.validate.js └── templates │ ├── hotels │ ├── create.html │ ├── edit.html │ └── list.html │ └── layout │ └── sitelayout.html └── scala └── mvctest ├── Application.scala ├── BootConfig.scala ├── DbPopulator.scala ├── domain └── Hotel.scala ├── service └── HotelRepository.scala └── web ├── HotelController.scala └── RootController.scala /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .settings/ 3 | .springBeans 4 | .classpath 5 | .project 6 | bin/ 7 | .cache 8 | .gradle 9 | .idea 10 | build/ 11 | *.iml 12 | *.iws 13 | .DS_Store 14 | *.ipr 15 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = spring-boot-scala-web 2 | A sample web application using spring-boot and Scala 3 | 4 | 5 | === Using gradle as the build tool: 6 | 7 | Build using `./gradlew build` 8 | Run using `./gradlew bootRun` 9 | OR 10 | Run using: `java -jar build/libs/spring-boot-scala-web-0.3.0.jar` 11 | 12 | A url listing the hotels is at http://localhost:8080/hotels[http://localhost:8080/hotels] 13 | 14 | === Using sbt as the build tool 15 | 16 | Start up sbt console 17 | 18 | [source, bash] 19 | ---- 20 | sbt 21 | ---- 22 | 23 | Start App 24 | 25 | [source, bash] 26 | ---- 27 | ~re-start 28 | ---- -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.springframework.boot' version '1.5.3.RELEASE' 3 | } 4 | 5 | apply plugin: 'java' 6 | apply plugin: 'scala' 7 | apply plugin: 'eclipse' 8 | apply plugin: 'idea' 9 | 10 | ext { 11 | sourceCompatibility = 1.8 12 | targetCompatibility = 1.8 13 | } 14 | 15 | 16 | jar { 17 | baseName = 'spring-boot-scala-web' 18 | version = '0.3.0' 19 | } 20 | 21 | repositories { 22 | mavenCentral() 23 | jcenter() 24 | } 25 | 26 | dependencies { 27 | compile("org.springframework.boot:spring-boot-starter-web") 28 | compile("org.springframework.boot:spring-boot-starter-data-jpa") 29 | compile("org.springframework.boot:spring-boot-starter-actuator") 30 | compile("org.thymeleaf:thymeleaf-spring4") 31 | compile("nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect") 32 | testCompile("org.springframework.boot:spring-boot-starter-test") 33 | compile("com.h2database:h2") 34 | compile("org.scala-lang:scala-library:2.12.2") 35 | compile("org.webjars:bootstrap:3.1.1") 36 | } -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | import Dependencies._ 2 | 3 | //testCompile("org.springframework.boot:spring-boot-starter-test") 4 | 5 | lazy val springVersion = "1.5.3.RELEASE" 6 | lazy val thymeleafVersion = "2.1.5.RELEASE" 7 | 8 | lazy val root = (project in file(".")). 9 | settings( 10 | inThisBuild(List( 11 | organization := "org.bk", 12 | scalaVersion := "2.12.2", 13 | version := "0.3.0" 14 | )), 15 | name := "spring-boot-scala-web", 16 | libraryDependencies += scalaTest % Test, 17 | libraryDependencies += "org.springframework.boot" % "spring-boot-starter-web" % springVersion, 18 | libraryDependencies += "org.springframework.boot" % "spring-boot-starter-data-jpa" % springVersion, 19 | libraryDependencies += "org.springframework.boot" % "spring-boot-starter-actuator" % springVersion, 20 | libraryDependencies += "org.thymeleaf" % "thymeleaf-spring4" % thymeleafVersion, 21 | libraryDependencies += "nz.net.ultraq.thymeleaf" % "thymeleaf-layout-dialect" % "1.4.0", 22 | libraryDependencies += "com.h2database" % "h2" % "1.4.195", 23 | libraryDependencies += "org.webjars" % "bootstrap" % "3.1.1" 24 | ) 25 | 26 | 27 | // set the main class for the main 'run' task 28 | // change Compile to Test to set it for 'test:run' 29 | mainClass in (Compile, run) := Some("mvctest.Application") -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bijukunjummen/spring-boot-scala-web/28d92d9ab0f7e4c808a2fc050432a02c7aea59b0/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat May 20 21:37:31 PDT 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.5-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save ( ) { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /project/Dependencies.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | 3 | object Dependencies { 4 | lazy val scalaTest = "org.scalatest" %% "scalatest" % "3.0.1" 5 | } 6 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.15 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("io.spray" % "sbt-revolver" % "0.8.0") -------------------------------------------------------------------------------- /sbt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ./sbt-dist/bin/sbt "$@" -------------------------------------------------------------------------------- /sbt-dist/conf/sbtconfig.txt: -------------------------------------------------------------------------------- 1 | # Set the java args to high 2 | 3 | -Xmx512M 4 | 5 | -XX:MaxPermSize=256m 6 | 7 | -XX:ReservedCodeCacheSize=128m 8 | 9 | 10 | 11 | # Set the extra SBT options 12 | 13 | -Dsbt.log.format=true 14 | 15 | -------------------------------------------------------------------------------- /sbt-dist/conf/sbtopts: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------ # 2 | # The SBT Configuration file. # 3 | # ------------------------------------------------ # 4 | 5 | 6 | # Disable ANSI color codes 7 | # 8 | #-no-colors 9 | 10 | # Starts sbt even if the current directory contains no sbt project. 11 | # 12 | -sbt-create 13 | 14 | # Path to global settings/plugins directory (default: ~/.sbt) 15 | # 16 | #-sbt-dir /etc/sbt 17 | 18 | # Path to shared boot directory (default: ~/.sbt/boot in 0.11 series) 19 | # 20 | #-sbt-boot ~/.sbt/boot 21 | 22 | # Path to local Ivy repository (default: ~/.ivy2) 23 | # 24 | #-ivy ~/.ivy2 25 | 26 | # set memory options 27 | # 28 | #-mem 29 | 30 | # Use local caches for projects, no sharing. 31 | # 32 | #-no-share 33 | 34 | # Put SBT in offline mode. 35 | # 36 | #-offline 37 | 38 | # Sets the SBT version to use. 39 | #-sbt-version 0.11.3 40 | 41 | # Scala version (default: latest release) 42 | # 43 | #-scala-home 44 | #-scala-version 45 | 46 | # java version (default: java from PATH, currently $(java -version |& grep version)) 47 | # 48 | #-java-home 49 | 50 | -------------------------------------------------------------------------------- /sbt.bat: -------------------------------------------------------------------------------- 1 | @REM SBT launcher script 2 | 3 | .\sbt-dist\bin\sbt.bat %* 4 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.jpa.hibernate.ddl-auto: create-drop 2 | spring.datasource.url: jdbc:h2:mem:scratchdb -------------------------------------------------------------------------------- /src/main/resources/import.sql: -------------------------------------------------------------------------------- 1 | -- insert into hotel(id, name, address, zip) values (1, 'Test Hotel 1', 'Test Street 1', '000001'); 2 | -- insert into hotel(id, name, address, zip) values (2, 'Test Hotel 2', 'Test Street 2', '000002'); 3 | -- insert into hotel(id, name, address, zip) values (3, 'Test Hotel 3', 'Test Street 3', '000003'); 4 | -- insert into hotel(id, name, address, zip) values (4, 'Test Hotel 4', 'Test Street 4', '000004'); 5 | -------------------------------------------------------------------------------- /src/main/resources/messages.properties: -------------------------------------------------------------------------------- 1 | app.name=Sample Spring-Boot Scala App 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/main/resources/static/css/application.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: tahoma, verdana, arial, sans-serif; font-size: 12px; 3 | padding-top: 60px; 4 | } -------------------------------------------------------------------------------- /src/main/resources/static/js/jquery.validate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jQuery Validation Plugin @VERSION 3 | * 4 | * http://bassistance.de/jquery-plugins/jquery-plugin-validation/ 5 | * http://docs.jquery.com/Plugins/Validation 6 | * 7 | * Copyright (c) 2012 Jörn Zaefferer 8 | * 9 | * Dual licensed under the MIT and GPL licenses: 10 | * http://www.opensource.org/licenses/mit-license.php 11 | * http://www.gnu.org/licenses/gpl.html 12 | */ 13 | 14 | (function($) { 15 | 16 | $.extend($.fn, { 17 | // http://docs.jquery.com/Plugins/Validation/validate 18 | validate: function( options ) { 19 | 20 | // if nothing is selected, return nothing; can't chain anyway 21 | if (!this.length) { 22 | if (options && options.debug && window.console) { 23 | console.warn( "nothing selected, can't validate, returning nothing" ); 24 | } 25 | return; 26 | } 27 | 28 | // check if a validator for this form was already created 29 | var validator = $.data(this[0], 'validator'); 30 | if ( validator ) { 31 | return validator; 32 | } 33 | 34 | // Add novalidate tag if HTML5. 35 | this.attr('novalidate', 'novalidate'); 36 | 37 | validator = new $.validator( options, this[0] ); 38 | $.data(this[0], 'validator', validator); 39 | 40 | if ( validator.settings.onsubmit ) { 41 | 42 | this.validateDelegate( ":submit", "click", function(ev) { 43 | if ( validator.settings.submitHandler ) { 44 | validator.submitButton = ev.target; 45 | } 46 | // allow suppressing validation by adding a cancel class to the submit button 47 | if ( $(ev.target).hasClass('cancel') ) { 48 | validator.cancelSubmit = true; 49 | } 50 | }); 51 | 52 | // validate the form on submit 53 | this.submit( function( event ) { 54 | if ( validator.settings.debug ) { 55 | // prevent form submit to be able to see console output 56 | event.preventDefault(); 57 | } 58 | function handle() { 59 | var hidden; 60 | if ( validator.settings.submitHandler ) { 61 | if (validator.submitButton) { 62 | // insert a hidden input as a replacement for the missing submit button 63 | hidden = $("").attr("name", validator.submitButton.name).val(validator.submitButton.value).appendTo(validator.currentForm); 64 | } 65 | validator.settings.submitHandler.call( validator, validator.currentForm, event ); 66 | if (validator.submitButton) { 67 | // and clean up afterwards; thanks to no-block-scope, hidden can be referenced 68 | hidden.remove(); 69 | } 70 | return false; 71 | } 72 | return true; 73 | } 74 | 75 | // prevent submit for invalid forms or custom submit handlers 76 | if ( validator.cancelSubmit ) { 77 | validator.cancelSubmit = false; 78 | return handle(); 79 | } 80 | if ( validator.form() ) { 81 | if ( validator.pendingRequest ) { 82 | validator.formSubmitted = true; 83 | return false; 84 | } 85 | return handle(); 86 | } else { 87 | validator.focusInvalid(); 88 | return false; 89 | } 90 | }); 91 | } 92 | 93 | return validator; 94 | }, 95 | // http://docs.jquery.com/Plugins/Validation/valid 96 | valid: function() { 97 | if ( $(this[0]).is('form')) { 98 | return this.validate().form(); 99 | } else { 100 | var valid = true; 101 | var validator = $(this[0].form).validate(); 102 | this.each(function() { 103 | valid &= validator.element(this); 104 | }); 105 | return valid; 106 | } 107 | }, 108 | // attributes: space seperated list of attributes to retrieve and remove 109 | removeAttrs: function(attributes) { 110 | var result = {}, 111 | $element = this; 112 | $.each(attributes.split(/\s/), function(index, value) { 113 | result[value] = $element.attr(value); 114 | $element.removeAttr(value); 115 | }); 116 | return result; 117 | }, 118 | // http://docs.jquery.com/Plugins/Validation/rules 119 | rules: function(command, argument) { 120 | var element = this[0]; 121 | 122 | if (command) { 123 | var settings = $.data(element.form, 'validator').settings; 124 | var staticRules = settings.rules; 125 | var existingRules = $.validator.staticRules(element); 126 | switch(command) { 127 | case "add": 128 | $.extend(existingRules, $.validator.normalizeRule(argument)); 129 | staticRules[element.name] = existingRules; 130 | if (argument.messages) { 131 | settings.messages[element.name] = $.extend( settings.messages[element.name], argument.messages ); 132 | } 133 | break; 134 | case "remove": 135 | if (!argument) { 136 | delete staticRules[element.name]; 137 | return existingRules; 138 | } 139 | var filtered = {}; 140 | $.each(argument.split(/\s/), function(index, method) { 141 | filtered[method] = existingRules[method]; 142 | delete existingRules[method]; 143 | }); 144 | return filtered; 145 | } 146 | } 147 | 148 | var data = $.validator.normalizeRules( 149 | $.extend( 150 | {}, 151 | $.validator.metadataRules(element), 152 | $.validator.classRules(element), 153 | $.validator.attributeRules(element), 154 | $.validator.staticRules(element) 155 | ), element); 156 | 157 | // make sure required is at front 158 | if (data.required) { 159 | var param = data.required; 160 | delete data.required; 161 | data = $.extend({required: param}, data); 162 | } 163 | 164 | return data; 165 | } 166 | }); 167 | 168 | // Custom selectors 169 | $.extend($.expr[":"], { 170 | // http://docs.jquery.com/Plugins/Validation/blank 171 | blank: function(a) {return !$.trim("" + a.value);}, 172 | // http://docs.jquery.com/Plugins/Validation/filled 173 | filled: function(a) {return !!$.trim("" + a.value);}, 174 | // http://docs.jquery.com/Plugins/Validation/unchecked 175 | unchecked: function(a) {return !a.checked;} 176 | }); 177 | 178 | // constructor for validator 179 | $.validator = function( options, form ) { 180 | this.settings = $.extend( true, {}, $.validator.defaults, options ); 181 | this.currentForm = form; 182 | this.init(); 183 | }; 184 | 185 | $.validator.format = function(source, params) { 186 | if ( arguments.length === 1 ) { 187 | return function() { 188 | var args = $.makeArray(arguments); 189 | args.unshift(source); 190 | return $.validator.format.apply( this, args ); 191 | }; 192 | } 193 | if ( arguments.length > 2 && params.constructor !== Array ) { 194 | params = $.makeArray(arguments).slice(1); 195 | } 196 | if ( params.constructor !== Array ) { 197 | params = [ params ]; 198 | } 199 | $.each(params, function(i, n) { 200 | source = source.replace(new RegExp("\\{" + i + "\\}", "g"), n); 201 | }); 202 | return source; 203 | }; 204 | 205 | $.extend($.validator, { 206 | 207 | defaults: { 208 | messages: {}, 209 | groups: {}, 210 | rules: {}, 211 | errorClass: "error", 212 | validClass: "valid", 213 | errorElement: "label", 214 | focusInvalid: true, 215 | errorContainer: $( [] ), 216 | errorLabelContainer: $( [] ), 217 | onsubmit: true, 218 | ignore: ":hidden", 219 | ignoreTitle: false, 220 | onfocusin: function(element, event) { 221 | this.lastActive = element; 222 | 223 | // hide error label and remove error class on focus if enabled 224 | if ( this.settings.focusCleanup && !this.blockFocusCleanup ) { 225 | if ( this.settings.unhighlight ) { 226 | this.settings.unhighlight.call( this, element, this.settings.errorClass, this.settings.validClass ); 227 | } 228 | this.addWrapper(this.errorsFor(element)).hide(); 229 | } 230 | }, 231 | onfocusout: function(element, event) { 232 | if ( !this.checkable(element) && (element.name in this.submitted || !this.optional(element)) ) { 233 | this.element(element); 234 | } 235 | }, 236 | onkeyup: function(element, event) { 237 | if ( element.name in this.submitted || element === this.lastElement ) { 238 | this.element(element); 239 | } 240 | }, 241 | onclick: function(element, event) { 242 | // click on selects, radiobuttons and checkboxes 243 | if ( element.name in this.submitted ) { 244 | this.element(element); 245 | } 246 | // or option elements, check parent select in that case 247 | else if (element.parentNode.name in this.submitted) { 248 | this.element(element.parentNode); 249 | } 250 | }, 251 | highlight: function(element, errorClass, validClass) { 252 | if (element.type === 'radio') { 253 | this.findByName(element.name).addClass(errorClass).removeClass(validClass); 254 | } else { 255 | $(element).addClass(errorClass).removeClass(validClass); 256 | } 257 | }, 258 | unhighlight: function(element, errorClass, validClass) { 259 | if (element.type === 'radio') { 260 | this.findByName(element.name).removeClass(errorClass).addClass(validClass); 261 | } else { 262 | $(element).removeClass(errorClass).addClass(validClass); 263 | } 264 | } 265 | }, 266 | 267 | // http://docs.jquery.com/Plugins/Validation/Validator/setDefaults 268 | setDefaults: function(settings) { 269 | $.extend( $.validator.defaults, settings ); 270 | }, 271 | 272 | messages: { 273 | required: "This field is required.", 274 | remote: "Please fix this field.", 275 | email: "Please enter a valid email address.", 276 | url: "Please enter a valid URL.", 277 | date: "Please enter a valid date.", 278 | dateISO: "Please enter a valid date (ISO).", 279 | number: "Please enter a valid number.", 280 | digits: "Please enter only digits.", 281 | creditcard: "Please enter a valid credit card number.", 282 | equalTo: "Please enter the same value again.", 283 | accept: "Please enter a value with a valid extension.", 284 | maxlength: $.validator.format("Please enter no more than {0} characters."), 285 | minlength: $.validator.format("Please enter at least {0} characters."), 286 | rangelength: $.validator.format("Please enter a value between {0} and {1} characters long."), 287 | range: $.validator.format("Please enter a value between {0} and {1}."), 288 | max: $.validator.format("Please enter a value less than or equal to {0}."), 289 | min: $.validator.format("Please enter a value greater than or equal to {0}.") 290 | }, 291 | 292 | autoCreateRanges: false, 293 | 294 | prototype: { 295 | 296 | init: function() { 297 | this.labelContainer = $(this.settings.errorLabelContainer); 298 | this.errorContext = this.labelContainer.length && this.labelContainer || $(this.currentForm); 299 | this.containers = $(this.settings.errorContainer).add( this.settings.errorLabelContainer ); 300 | this.submitted = {}; 301 | this.valueCache = {}; 302 | this.pendingRequest = 0; 303 | this.pending = {}; 304 | this.invalid = {}; 305 | this.reset(); 306 | 307 | var groups = (this.groups = {}); 308 | $.each(this.settings.groups, function(key, value) { 309 | $.each(value.split(/\s/), function(index, name) { 310 | groups[name] = key; 311 | }); 312 | }); 313 | var rules = this.settings.rules; 314 | $.each(rules, function(key, value) { 315 | rules[key] = $.validator.normalizeRule(value); 316 | }); 317 | 318 | function delegate(event) { 319 | var validator = $.data(this[0].form, "validator"), 320 | eventType = "on" + event.type.replace(/^validate/, ""); 321 | if (validator.settings[eventType]) { 322 | validator.settings[eventType].call(validator, this[0], event); 323 | } 324 | } 325 | $(this.currentForm) 326 | .validateDelegate("[type='text'], [type='password'], [type='file'], select, textarea, " + 327 | "[type='number'], [type='search'] ,[type='tel'], [type='url'], " + 328 | "[type='email'], [type='datetime'], [type='date'], [type='month'], " + 329 | "[type='week'], [type='time'], [type='datetime-local'], " + 330 | "[type='range'], [type='color'] ", 331 | "focusin focusout keyup", delegate) 332 | .validateDelegate("[type='radio'], [type='checkbox'], select, option", "click", delegate); 333 | 334 | if (this.settings.invalidHandler) { 335 | $(this.currentForm).bind("invalid-form.validate", this.settings.invalidHandler); 336 | } 337 | }, 338 | 339 | // http://docs.jquery.com/Plugins/Validation/Validator/form 340 | form: function() { 341 | this.checkForm(); 342 | $.extend(this.submitted, this.errorMap); 343 | this.invalid = $.extend({}, this.errorMap); 344 | if (!this.valid()) { 345 | $(this.currentForm).triggerHandler("invalid-form", [this]); 346 | } 347 | this.showErrors(); 348 | return this.valid(); 349 | }, 350 | 351 | checkForm: function() { 352 | this.prepareForm(); 353 | for ( var i = 0, elements = (this.currentElements = this.elements()); elements[i]; i++ ) { 354 | this.check( elements[i] ); 355 | } 356 | return this.valid(); 357 | }, 358 | 359 | // http://docs.jquery.com/Plugins/Validation/Validator/element 360 | element: function( element ) { 361 | element = this.validationTargetFor( this.clean( element ) ); 362 | this.lastElement = element; 363 | this.prepareElement( element ); 364 | this.currentElements = $(element); 365 | var result = this.check( element ) !== false; 366 | if (result) { 367 | delete this.invalid[element.name]; 368 | } else { 369 | this.invalid[element.name] = true; 370 | } 371 | if ( !this.numberOfInvalids() ) { 372 | // Hide error containers on last error 373 | this.toHide = this.toHide.add( this.containers ); 374 | } 375 | this.showErrors(); 376 | return result; 377 | }, 378 | 379 | // http://docs.jquery.com/Plugins/Validation/Validator/showErrors 380 | showErrors: function(errors) { 381 | if(errors) { 382 | // add items to error list and map 383 | $.extend( this.errorMap, errors ); 384 | this.errorList = []; 385 | for ( var name in errors ) { 386 | this.errorList.push({ 387 | message: errors[name], 388 | element: this.findByName(name)[0] 389 | }); 390 | } 391 | // remove items from success list 392 | this.successList = $.grep( this.successList, function(element) { 393 | return !(element.name in errors); 394 | }); 395 | } 396 | if (this.settings.showErrors) { 397 | this.settings.showErrors.call( this, this.errorMap, this.errorList ); 398 | } else { 399 | this.defaultShowErrors(); 400 | } 401 | }, 402 | 403 | // http://docs.jquery.com/Plugins/Validation/Validator/resetForm 404 | resetForm: function() { 405 | if ( $.fn.resetForm ) { 406 | $( this.currentForm ).resetForm(); 407 | } 408 | this.submitted = {}; 409 | this.lastElement = null; 410 | this.prepareForm(); 411 | this.hideErrors(); 412 | this.elements().removeClass( this.settings.errorClass ); 413 | }, 414 | 415 | numberOfInvalids: function() { 416 | return this.objectLength(this.invalid); 417 | }, 418 | 419 | objectLength: function( obj ) { 420 | var count = 0; 421 | for ( var i in obj ) { 422 | count++; 423 | } 424 | return count; 425 | }, 426 | 427 | hideErrors: function() { 428 | this.addWrapper( this.toHide ).hide(); 429 | }, 430 | 431 | valid: function() { 432 | return this.size() === 0; 433 | }, 434 | 435 | size: function() { 436 | return this.errorList.length; 437 | }, 438 | 439 | focusInvalid: function() { 440 | if( this.settings.focusInvalid ) { 441 | try { 442 | $(this.findLastActive() || this.errorList.length && this.errorList[0].element || []) 443 | .filter(":visible") 444 | .focus() 445 | // manually trigger focusin event; without it, focusin handler isn't called, findLastActive won't have anything to find 446 | .trigger("focusin"); 447 | } catch(e) { 448 | // ignore IE throwing errors when focusing hidden elements 449 | } 450 | } 451 | }, 452 | 453 | findLastActive: function() { 454 | var lastActive = this.lastActive; 455 | return lastActive && $.grep(this.errorList, function(n) { 456 | return n.element.name === lastActive.name; 457 | }).length === 1 && lastActive; 458 | }, 459 | 460 | elements: function() { 461 | var validator = this, 462 | rulesCache = {}; 463 | 464 | // select all valid inputs inside the form (no submit or reset buttons) 465 | return $(this.currentForm) 466 | .find("input, select, textarea") 467 | .not(":submit, :reset, :image, [disabled]") 468 | .not( this.settings.ignore ) 469 | .filter(function() { 470 | if ( !this.name && validator.settings.debug && window.console ) { 471 | console.error( "%o has no name assigned", this); 472 | } 473 | 474 | // select only the first element for each name, and only those with rules specified 475 | if ( this.name in rulesCache || !validator.objectLength($(this).rules()) ) { 476 | return false; 477 | } 478 | 479 | rulesCache[this.name] = true; 480 | return true; 481 | }); 482 | }, 483 | 484 | clean: function( selector ) { 485 | return $( selector )[0]; 486 | }, 487 | 488 | errors: function() { 489 | var errorClass = this.settings.errorClass.replace(' ', '.'); 490 | return $( this.settings.errorElement + "." + errorClass, this.errorContext ); 491 | }, 492 | 493 | reset: function() { 494 | this.successList = []; 495 | this.errorList = []; 496 | this.errorMap = {}; 497 | this.toShow = $([]); 498 | this.toHide = $([]); 499 | this.currentElements = $([]); 500 | }, 501 | 502 | prepareForm: function() { 503 | this.reset(); 504 | this.toHide = this.errors().add( this.containers ); 505 | }, 506 | 507 | prepareElement: function( element ) { 508 | this.reset(); 509 | this.toHide = this.errorsFor(element); 510 | }, 511 | 512 | elementValue: function( element ) { 513 | var val = $(element).val(); 514 | if( typeof val === 'string' ) { 515 | return val.replace(/\r/g, ""); 516 | } 517 | return val; 518 | }, 519 | 520 | check: function( element ) { 521 | element = this.validationTargetFor( this.clean( element ) ); 522 | 523 | var rules = $(element).rules(); 524 | var dependencyMismatch = false; 525 | var val = this.elementValue(element); 526 | var result; 527 | 528 | for (var method in rules ) { 529 | var rule = { method: method, parameters: rules[method] }; 530 | try { 531 | 532 | result = $.validator.methods[method].call( this, val, element, rule.parameters ); 533 | 534 | // if a method indicates that the field is optional and therefore valid, 535 | // don't mark it as valid when there are no other rules 536 | if ( result === "dependency-mismatch" ) { 537 | dependencyMismatch = true; 538 | continue; 539 | } 540 | dependencyMismatch = false; 541 | 542 | if ( result === "pending" ) { 543 | this.toHide = this.toHide.not( this.errorsFor(element) ); 544 | return; 545 | } 546 | 547 | if( !result ) { 548 | this.formatAndAdd( element, rule ); 549 | return false; 550 | } 551 | } catch(e) { 552 | if ( this.settings.debug && window.console ) { 553 | console.log("exception occured when checking element " + element.id + ", check the '" + rule.method + "' method", e); 554 | } 555 | throw e; 556 | } 557 | } 558 | if (dependencyMismatch) { 559 | return; 560 | } 561 | if ( this.objectLength(rules) ) { 562 | this.successList.push(element); 563 | } 564 | return true; 565 | }, 566 | 567 | // return the custom message for the given element and validation method 568 | // specified in the element's "messages" metadata 569 | customMetaMessage: function(element, method) { 570 | if (!$.metadata) { 571 | return; 572 | } 573 | var meta = this.settings.meta ? $(element).metadata()[this.settings.meta] : $(element).metadata(); 574 | return meta && meta.messages && meta.messages[method]; 575 | }, 576 | 577 | // return the custom message for the given element name and validation method 578 | customMessage: function( name, method ) { 579 | var m = this.settings.messages[name]; 580 | return m && (m.constructor === String ? m : m[method]); 581 | }, 582 | 583 | // return the first defined argument, allowing empty strings 584 | findDefined: function() { 585 | for(var i = 0; i < arguments.length; i++) { 586 | if (arguments[i] !== undefined) { 587 | return arguments[i]; 588 | } 589 | } 590 | return undefined; 591 | }, 592 | 593 | defaultMessage: function( element, method) { 594 | return this.findDefined( 595 | this.customMessage( element.name, method ), 596 | this.customMetaMessage( element, method ), 597 | // title is never undefined, so handle empty string as undefined 598 | !this.settings.ignoreTitle && element.title || undefined, 599 | $.validator.messages[method], 600 | "Warning: No message defined for " + element.name + "" 601 | ); 602 | }, 603 | 604 | formatAndAdd: function( element, rule ) { 605 | var message = this.defaultMessage( element, rule.method ), 606 | theregex = /\$?\{(\d+)\}/g; 607 | if ( typeof message === "function" ) { 608 | message = message.call(this, rule.parameters, element); 609 | } else if (theregex.test(message)) { 610 | message = $.validator.format(message.replace(theregex, '{$1}'), rule.parameters); 611 | } 612 | this.errorList.push({ 613 | message: message, 614 | element: element 615 | }); 616 | 617 | this.errorMap[element.name] = message; 618 | this.submitted[element.name] = message; 619 | }, 620 | 621 | addWrapper: function(toToggle) { 622 | if ( this.settings.wrapper ) { 623 | toToggle = toToggle.add( toToggle.parent( this.settings.wrapper ) ); 624 | } 625 | return toToggle; 626 | }, 627 | 628 | defaultShowErrors: function() { 629 | var i, elements; 630 | for ( i = 0; this.errorList[i]; i++ ) { 631 | var error = this.errorList[i]; 632 | if ( this.settings.highlight ) { 633 | this.settings.highlight.call( this, error.element, this.settings.errorClass, this.settings.validClass ); 634 | } 635 | this.showLabel( error.element, error.message ); 636 | } 637 | if( this.errorList.length ) { 638 | this.toShow = this.toShow.add( this.containers ); 639 | } 640 | if (this.settings.success) { 641 | for ( i = 0; this.successList[i]; i++ ) { 642 | this.showLabel( this.successList[i] ); 643 | } 644 | } 645 | if (this.settings.unhighlight) { 646 | for ( i = 0, elements = this.validElements(); elements[i]; i++ ) { 647 | this.settings.unhighlight.call( this, elements[i], this.settings.errorClass, this.settings.validClass ); 648 | } 649 | } 650 | this.toHide = this.toHide.not( this.toShow ); 651 | this.hideErrors(); 652 | this.addWrapper( this.toShow ).show(); 653 | }, 654 | 655 | validElements: function() { 656 | return this.currentElements.not(this.invalidElements()); 657 | }, 658 | 659 | invalidElements: function() { 660 | return $(this.errorList).map(function() { 661 | return this.element; 662 | }); 663 | }, 664 | 665 | showLabel: function(element, message) { 666 | var label = this.errorsFor( element ); 667 | if ( label.length ) { 668 | // refresh error/success class 669 | label.removeClass( this.settings.validClass ).addClass( this.settings.errorClass ); 670 | 671 | // check if we have a generated label, replace the message then 672 | if ( label.attr("generated") ) { 673 | label.html(message); 674 | } 675 | } else { 676 | // create label 677 | label = $("<" + this.settings.errorElement + "/>") 678 | .attr({"for": this.idOrName(element), generated: true}) 679 | .addClass(this.settings.errorClass) 680 | .html(message || ""); 681 | if ( this.settings.wrapper ) { 682 | // make sure the element is visible, even in IE 683 | // actually showing the wrapped element is handled elsewhere 684 | label = label.hide().show().wrap("<" + this.settings.wrapper + "/>").parent(); 685 | } 686 | if ( !this.labelContainer.append(label).length ) { 687 | if ( this.settings.errorPlacement ) { 688 | this.settings.errorPlacement(label, $(element) ); 689 | } else { 690 | label.insertAfter(element); 691 | } 692 | } 693 | } 694 | if ( !message && this.settings.success ) { 695 | label.text(""); 696 | if ( typeof this.settings.success === "string" ) { 697 | label.addClass( this.settings.success ); 698 | } else { 699 | this.settings.success( label ); 700 | } 701 | } 702 | this.toShow = this.toShow.add(label); 703 | }, 704 | 705 | errorsFor: function(element) { 706 | var name = this.idOrName(element); 707 | return this.errors().filter(function() { 708 | return $(this).attr('for') === name; 709 | }); 710 | }, 711 | 712 | idOrName: function(element) { 713 | return this.groups[element.name] || (this.checkable(element) ? element.name : element.id || element.name); 714 | }, 715 | 716 | validationTargetFor: function(element) { 717 | // if radio/checkbox, validate first element in group instead 718 | if (this.checkable(element)) { 719 | element = this.findByName( element.name ).not(this.settings.ignore)[0]; 720 | } 721 | return element; 722 | }, 723 | 724 | checkable: function( element ) { 725 | return (/radio|checkbox/i).test(element.type); 726 | }, 727 | 728 | findByName: function( name ) { 729 | // select by name and filter by form for performance over form.find("[name=...]") 730 | var form = this.currentForm; 731 | return $(document.getElementsByName(name)).map(function(index, element) { 732 | return element.form === form && element.name === name && element || null; 733 | }); 734 | }, 735 | 736 | getLength: function(value, element) { 737 | switch( element.nodeName.toLowerCase() ) { 738 | case 'select': 739 | return $("option:selected", element).length; 740 | case 'input': 741 | if( this.checkable( element) ) { 742 | return this.findByName(element.name).filter(':checked').length; 743 | } 744 | } 745 | return value.length; 746 | }, 747 | 748 | depend: function(param, element) { 749 | return this.dependTypes[typeof param] ? this.dependTypes[typeof param](param, element) : true; 750 | }, 751 | 752 | dependTypes: { 753 | "boolean": function(param, element) { 754 | return param; 755 | }, 756 | "string": function(param, element) { 757 | return !!$(param, element.form).length; 758 | }, 759 | "function": function(param, element) { 760 | return param(element); 761 | } 762 | }, 763 | 764 | optional: function(element) { 765 | var val = this.elementValue(element); 766 | return !$.validator.methods.required.call(this, val, element) && "dependency-mismatch"; 767 | }, 768 | 769 | startRequest: function(element) { 770 | if (!this.pending[element.name]) { 771 | this.pendingRequest++; 772 | this.pending[element.name] = true; 773 | } 774 | }, 775 | 776 | stopRequest: function(element, valid) { 777 | this.pendingRequest--; 778 | // sometimes synchronization fails, make sure pendingRequest is never < 0 779 | if (this.pendingRequest < 0) { 780 | this.pendingRequest = 0; 781 | } 782 | delete this.pending[element.name]; 783 | if ( valid && this.pendingRequest === 0 && this.formSubmitted && this.form() ) { 784 | $(this.currentForm).submit(); 785 | this.formSubmitted = false; 786 | } else if (!valid && this.pendingRequest === 0 && this.formSubmitted) { 787 | $(this.currentForm).triggerHandler("invalid-form", [this]); 788 | this.formSubmitted = false; 789 | } 790 | }, 791 | 792 | previousValue: function(element) { 793 | return $.data(element, "previousValue") || $.data(element, "previousValue", { 794 | old: null, 795 | valid: true, 796 | message: this.defaultMessage( element, "remote" ) 797 | }); 798 | } 799 | 800 | }, 801 | 802 | classRuleSettings: { 803 | required: {required: true}, 804 | email: {email: true}, 805 | url: {url: true}, 806 | date: {date: true}, 807 | dateISO: {dateISO: true}, 808 | number: {number: true}, 809 | digits: {digits: true}, 810 | creditcard: {creditcard: true} 811 | }, 812 | 813 | addClassRules: function(className, rules) { 814 | if ( className.constructor === String ) { 815 | this.classRuleSettings[className] = rules; 816 | } else { 817 | $.extend(this.classRuleSettings, className); 818 | } 819 | }, 820 | 821 | classRules: function(element) { 822 | var rules = {}; 823 | var classes = $(element).attr('class'); 824 | if ( classes ) { 825 | $.each(classes.split(' '), function() { 826 | if (this in $.validator.classRuleSettings) { 827 | $.extend(rules, $.validator.classRuleSettings[this]); 828 | } 829 | }); 830 | } 831 | return rules; 832 | }, 833 | 834 | attributeRules: function(element) { 835 | var rules = {}; 836 | var $element = $(element); 837 | 838 | for (var method in $.validator.methods) { 839 | var value; 840 | 841 | // support for in both html5 and older browsers 842 | if (method === 'required') { 843 | value = $element.get(0).getAttribute(method); 844 | // Some browsers return an empty string for the required attribute 845 | // and non-HTML5 browsers might have required="" markup 846 | if (value === "") { 847 | value = true; 848 | } else if (value === "false") { 849 | value = false; 850 | } 851 | // force non-HTML5 browsers to return bool 852 | value = !!value; 853 | } else { 854 | value = $element.attr(method); 855 | } 856 | 857 | if (value) { 858 | rules[method] = value; 859 | } else if ($element[0].getAttribute("type") === method) { 860 | rules[method] = true; 861 | } 862 | } 863 | 864 | // maxlength may be returned as -1, 2147483647 (IE) and 524288 (safari) for text inputs 865 | if (rules.maxlength && /-1|2147483647|524288/.test(rules.maxlength)) { 866 | delete rules.maxlength; 867 | } 868 | 869 | return rules; 870 | }, 871 | 872 | metadataRules: function(element) { 873 | if (!$.metadata) { 874 | return {}; 875 | } 876 | 877 | var meta = $.data(element.form, 'validator').settings.meta; 878 | return meta ? 879 | $(element).metadata()[meta] : 880 | $(element).metadata(); 881 | }, 882 | 883 | staticRules: function(element) { 884 | var rules = {}; 885 | var validator = $.data(element.form, 'validator'); 886 | if (validator.settings.rules) { 887 | rules = $.validator.normalizeRule(validator.settings.rules[element.name]) || {}; 888 | } 889 | return rules; 890 | }, 891 | 892 | normalizeRules: function(rules, element) { 893 | // handle dependency check 894 | $.each(rules, function(prop, val) { 895 | // ignore rule when param is explicitly false, eg. required:false 896 | if (val === false) { 897 | delete rules[prop]; 898 | return; 899 | } 900 | if (val.param || val.depends) { 901 | var keepRule = true; 902 | switch (typeof val.depends) { 903 | case "string": 904 | keepRule = !!$(val.depends, element.form).length; 905 | break; 906 | case "function": 907 | keepRule = val.depends.call(element, element); 908 | break; 909 | } 910 | if (keepRule) { 911 | rules[prop] = val.param !== undefined ? val.param : true; 912 | } else { 913 | delete rules[prop]; 914 | } 915 | } 916 | }); 917 | 918 | // evaluate parameters 919 | $.each(rules, function(rule, parameter) { 920 | rules[rule] = $.isFunction(parameter) ? parameter(element) : parameter; 921 | }); 922 | 923 | // clean number parameters 924 | $.each(['minlength', 'maxlength', 'min', 'max'], function() { 925 | if (rules[this]) { 926 | rules[this] = Number(rules[this]); 927 | } 928 | }); 929 | $.each(['rangelength', 'range'], function() { 930 | if (rules[this]) { 931 | rules[this] = [Number(rules[this][0]), Number(rules[this][1])]; 932 | } 933 | }); 934 | 935 | if ($.validator.autoCreateRanges) { 936 | // auto-create ranges 937 | if (rules.min && rules.max) { 938 | rules.range = [rules.min, rules.max]; 939 | delete rules.min; 940 | delete rules.max; 941 | } 942 | if (rules.minlength && rules.maxlength) { 943 | rules.rangelength = [rules.minlength, rules.maxlength]; 944 | delete rules.minlength; 945 | delete rules.maxlength; 946 | } 947 | } 948 | 949 | // To support custom messages in metadata ignore rule methods titled "messages" 950 | if (rules.messages) { 951 | delete rules.messages; 952 | } 953 | 954 | return rules; 955 | }, 956 | 957 | // Converts a simple string to a {string: true} rule, e.g., "required" to {required:true} 958 | normalizeRule: function(data) { 959 | if( typeof data === "string" ) { 960 | var transformed = {}; 961 | $.each(data.split(/\s/), function() { 962 | transformed[this] = true; 963 | }); 964 | data = transformed; 965 | } 966 | return data; 967 | }, 968 | 969 | // http://docs.jquery.com/Plugins/Validation/Validator/addMethod 970 | addMethod: function(name, method, message) { 971 | $.validator.methods[name] = method; 972 | $.validator.messages[name] = message !== undefined ? message : $.validator.messages[name]; 973 | if (method.length < 3) { 974 | $.validator.addClassRules(name, $.validator.normalizeRule(name)); 975 | } 976 | }, 977 | 978 | methods: { 979 | 980 | // http://docs.jquery.com/Plugins/Validation/Methods/required 981 | required: function(value, element, param) { 982 | // check if dependency is met 983 | if ( !this.depend(param, element) ) { 984 | return "dependency-mismatch"; 985 | } 986 | if ( element.nodeName.toLowerCase() === "select" ) { 987 | // could be an array for select-multiple or a string, both are fine this way 988 | var val = $(element).val(); 989 | return val && val.length > 0; 990 | } 991 | if ( this.checkable(element) ) { 992 | return this.getLength(value, element) > 0; 993 | } 994 | return $.trim(value).length > 0; 995 | }, 996 | 997 | // http://docs.jquery.com/Plugins/Validation/Methods/remote 998 | remote: function(value, element, param) { 999 | if ( this.optional(element) ) { 1000 | return "dependency-mismatch"; 1001 | } 1002 | 1003 | var previous = this.previousValue(element); 1004 | if (!this.settings.messages[element.name] ) { 1005 | this.settings.messages[element.name] = {}; 1006 | } 1007 | previous.originalMessage = this.settings.messages[element.name].remote; 1008 | this.settings.messages[element.name].remote = previous.message; 1009 | 1010 | param = typeof param === "string" && {url:param} || param; 1011 | 1012 | if ( this.pending[element.name] ) { 1013 | return "pending"; 1014 | } 1015 | if ( previous.old === value ) { 1016 | return previous.valid; 1017 | } 1018 | 1019 | previous.old = value; 1020 | var validator = this; 1021 | this.startRequest(element); 1022 | var data = {}; 1023 | data[element.name] = value; 1024 | $.ajax($.extend(true, { 1025 | url: param, 1026 | mode: "abort", 1027 | port: "validate" + element.name, 1028 | dataType: "json", 1029 | data: data, 1030 | success: function(response) { 1031 | validator.settings.messages[element.name].remote = previous.originalMessage; 1032 | var valid = response === true; 1033 | if ( valid ) { 1034 | var submitted = validator.formSubmitted; 1035 | validator.prepareElement(element); 1036 | validator.formSubmitted = submitted; 1037 | validator.successList.push(element); 1038 | validator.showErrors(); 1039 | } else { 1040 | var errors = {}; 1041 | var message = response || validator.defaultMessage( element, "remote" ); 1042 | errors[element.name] = previous.message = $.isFunction(message) ? message(value) : message; 1043 | validator.showErrors(errors); 1044 | } 1045 | previous.valid = valid; 1046 | validator.stopRequest(element, valid); 1047 | } 1048 | }, param)); 1049 | return "pending"; 1050 | }, 1051 | 1052 | // http://docs.jquery.com/Plugins/Validation/Methods/minlength 1053 | minlength: function(value, element, param) { 1054 | var length = $.isArray( value ) ? value.length : this.getLength($.trim(value), element); 1055 | return this.optional(element) || length >= param; 1056 | }, 1057 | 1058 | // http://docs.jquery.com/Plugins/Validation/Methods/maxlength 1059 | maxlength: function(value, element, param) { 1060 | var length = $.isArray( value ) ? value.length : this.getLength($.trim(value), element); 1061 | return this.optional(element) || length <= param; 1062 | }, 1063 | 1064 | // http://docs.jquery.com/Plugins/Validation/Methods/rangelength 1065 | rangelength: function(value, element, param) { 1066 | var length = $.isArray( value ) ? value.length : this.getLength($.trim(value), element); 1067 | return this.optional(element) || ( length >= param[0] && length <= param[1] ); 1068 | }, 1069 | 1070 | // http://docs.jquery.com/Plugins/Validation/Methods/min 1071 | min: function( value, element, param ) { 1072 | return this.optional(element) || value >= param; 1073 | }, 1074 | 1075 | // http://docs.jquery.com/Plugins/Validation/Methods/max 1076 | max: function( value, element, param ) { 1077 | return this.optional(element) || value <= param; 1078 | }, 1079 | 1080 | // http://docs.jquery.com/Plugins/Validation/Methods/range 1081 | range: function( value, element, param ) { 1082 | return this.optional(element) || ( value >= param[0] && value <= param[1] ); 1083 | }, 1084 | 1085 | // http://docs.jquery.com/Plugins/Validation/Methods/email 1086 | email: function(value, element) { 1087 | // contributed by Scott Gonzalez: http://projects.scottsplayground.com/email_address_validation/ 1088 | return this.optional(element) || /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i.test(value); 1089 | }, 1090 | 1091 | // http://docs.jquery.com/Plugins/Validation/Methods/url 1092 | url: function(value, element) { 1093 | // contributed by Scott Gonzalez: http://projects.scottsplayground.com/iri/ 1094 | return this.optional(element) || /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(value); 1095 | }, 1096 | 1097 | // http://docs.jquery.com/Plugins/Validation/Methods/date 1098 | date: function(value, element) { 1099 | return this.optional(element) || !/Invalid|NaN/.test(new Date(value)); 1100 | }, 1101 | 1102 | // http://docs.jquery.com/Plugins/Validation/Methods/dateISO 1103 | dateISO: function(value, element) { 1104 | return this.optional(element) || /^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}$/.test(value); 1105 | }, 1106 | 1107 | // http://docs.jquery.com/Plugins/Validation/Methods/number 1108 | number: function(value, element) { 1109 | return this.optional(element) || /^-?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(value); 1110 | }, 1111 | 1112 | // http://docs.jquery.com/Plugins/Validation/Methods/digits 1113 | digits: function(value, element) { 1114 | return this.optional(element) || /^\d+$/.test(value); 1115 | }, 1116 | 1117 | // http://docs.jquery.com/Plugins/Validation/Methods/creditcard 1118 | // based on http://en.wikipedia.org/wiki/Luhn 1119 | creditcard: function(value, element) { 1120 | if ( this.optional(element) ) { 1121 | return "dependency-mismatch"; 1122 | } 1123 | // accept only spaces, digits and dashes 1124 | if (/[^0-9 \-]+/.test(value)) { 1125 | return false; 1126 | } 1127 | var nCheck = 0, 1128 | nDigit = 0, 1129 | bEven = false; 1130 | 1131 | value = value.replace(/\D/g, ""); 1132 | 1133 | for (var n = value.length - 1; n >= 0; n--) { 1134 | var cDigit = value.charAt(n); 1135 | nDigit = parseInt(cDigit, 10); 1136 | if (bEven) { 1137 | if ((nDigit *= 2) > 9) { 1138 | nDigit -= 9; 1139 | } 1140 | } 1141 | nCheck += nDigit; 1142 | bEven = !bEven; 1143 | } 1144 | 1145 | return (nCheck % 10) === 0; 1146 | }, 1147 | 1148 | // http://docs.jquery.com/Plugins/Validation/Methods/accept 1149 | accept: function(value, element, param) { 1150 | param = typeof param === "string" ? param.replace(/,/g, '|') : "png|jpe?g|gif"; 1151 | return this.optional(element) || value.match(new RegExp(".(" + param + ")$", "i")); 1152 | }, 1153 | 1154 | // http://docs.jquery.com/Plugins/Validation/Methods/equalTo 1155 | equalTo: function(value, element, param) { 1156 | // bind to the blur event of the target in order to revalidate whenever the target field is updated 1157 | // TODO find a way to bind the event just once, avoiding the unbind-rebind overhead 1158 | var target = $(param).unbind(".validate-equalTo").bind("blur.validate-equalTo", function() { 1159 | $(element).valid(); 1160 | }); 1161 | return value === target.val(); 1162 | } 1163 | 1164 | } 1165 | 1166 | }); 1167 | 1168 | // deprecated, use $.validator.format instead 1169 | $.format = $.validator.format; 1170 | 1171 | }(jQuery)); 1172 | 1173 | // ajax mode: abort 1174 | // usage: $.ajax({ mode: "abort"[, port: "uniqueport"]}); 1175 | // if mode:"abort" is used, the previous request on that port (port can be undefined) is aborted via XMLHttpRequest.abort() 1176 | (function($) { 1177 | var pendingRequests = {}; 1178 | // Use a prefilter if available (1.5+) 1179 | if ( $.ajaxPrefilter ) { 1180 | $.ajaxPrefilter(function(settings, _, xhr) { 1181 | var port = settings.port; 1182 | if (settings.mode === "abort") { 1183 | if ( pendingRequests[port] ) { 1184 | pendingRequests[port].abort(); 1185 | } 1186 | pendingRequests[port] = xhr; 1187 | } 1188 | }); 1189 | } else { 1190 | // Proxy ajax 1191 | var ajax = $.ajax; 1192 | $.ajax = function(settings) { 1193 | var mode = ( "mode" in settings ? settings : $.ajaxSettings ).mode, 1194 | port = ( "port" in settings ? settings : $.ajaxSettings ).port; 1195 | if (mode === "abort") { 1196 | if ( pendingRequests[port] ) { 1197 | pendingRequests[port].abort(); 1198 | } 1199 | return (pendingRequests[port] = ajax.apply(this, arguments)); 1200 | } 1201 | return ajax.apply(this, arguments); 1202 | }; 1203 | } 1204 | }(jQuery)); 1205 | 1206 | // provides cross-browser focusin and focusout events 1207 | // IE has native support, in other browsers, use event caputuring (neither bubbles) 1208 | 1209 | // provides delegate(type: String, delegate: Selector, handler: Callback) plugin for easier event delegation 1210 | // handler is only called when $(event.target).is(delegate), in the scope of the jquery-object for event.target 1211 | (function($) { 1212 | // only implement if not provided by jQuery core (since 1.4) 1213 | // TODO verify if jQuery 1.4's implementation is compatible with older jQuery special-event APIs 1214 | if (!jQuery.event.special.focusin && !jQuery.event.special.focusout && document.addEventListener) { 1215 | $.each({ 1216 | focus: 'focusin', 1217 | blur: 'focusout' 1218 | }, function( original, fix ){ 1219 | $.event.special[fix] = { 1220 | setup:function() { 1221 | this.addEventListener( original, handler, true ); 1222 | }, 1223 | teardown:function() { 1224 | this.removeEventListener( original, handler, true ); 1225 | }, 1226 | handler: function(e) { 1227 | var args = arguments; 1228 | args[0] = $.event.fix(e); 1229 | args[0].type = fix; 1230 | return $.event.handle.apply(this, args); 1231 | } 1232 | }; 1233 | function handler(e) { 1234 | e = $.event.fix(e); 1235 | e.type = fix; 1236 | return $.event.handle.call(this, e); 1237 | } 1238 | }); 1239 | } 1240 | $.extend($.fn, { 1241 | validateDelegate: function(delegate, type, handler) { 1242 | return this.bind(type, function(event) { 1243 | var target = $(event.target); 1244 | if (target.is(delegate)) { 1245 | return handler.apply(target, arguments); 1246 | } 1247 | }); 1248 | } 1249 | }); 1250 | }(jQuery)); -------------------------------------------------------------------------------- /src/main/resources/templates/hotels/create.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | List of Hotels 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 |
13 |

Hotels

14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | 22 |
23 | 24 |
25 |
26 |

Error with Name

27 |
28 |
29 |
30 | 31 |
32 | 33 |
34 |
35 |

Error with Address

36 |
37 |
38 |
39 | 40 |
41 | 42 |
43 |
44 |

Error with Zip

45 |
46 |
47 | 48 |
49 |
50 |
51 |
52 |
53 | 54 | -------------------------------------------------------------------------------- /src/main/resources/templates/hotels/edit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | List of Hotels 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 |
13 |

Hotels

14 |
15 |
16 |
17 |
18 |
19 |
20 | 21 |
22 | 23 |
24 | 25 |
26 |
27 |

Error with Name

28 |
29 |
30 |
31 | 32 |
33 | 34 |
35 |
36 |

Error with Address

37 |
38 |
39 |
40 | 41 |
42 | 43 |
44 |
45 |

Error with Zip

46 |
47 |
48 | 49 |
50 |
51 |
52 |
53 |
54 | 55 | -------------------------------------------------------------------------------- /src/main/resources/templates/hotels/list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | List of Hotels 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 |
13 |

Hotels

14 |
15 |
16 |
17 |
18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 41 | 42 | 43 |
IDNameAddressZipAction
No messages
1Test Name 1Test Address 1Test Zip 1Edit 39 | | Delete 40 |
44 |
45 |
46 |
47 |
48 | New Hotel 49 |
50 |
51 |
52 |
53 | 54 | -------------------------------------------------------------------------------- /src/main/resources/templates/layout/sitelayout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | List of Hotels 5 | 6 | 7 | 8 | 9 | 10 | 30 |
31 |
32 |
33 |
34 | Status message 35 |
36 |
37 |
38 |
39 |

Static content for prototyping purposes only

40 |

41 | This is the layout of the site. The actual content will come from individual views making use of this layout 42 |

43 |
44 |
45 | 54 | 55 | -------------------------------------------------------------------------------- /src/main/scala/mvctest/Application.scala: -------------------------------------------------------------------------------- 1 | package mvctest 2 | 3 | import org.springframework.boot.SpringApplication 4 | 5 | object Application extends App { 6 | SpringApplication.run(classOf[BootConfig]); 7 | } -------------------------------------------------------------------------------- /src/main/scala/mvctest/BootConfig.scala: -------------------------------------------------------------------------------- 1 | package mvctest 2 | 3 | import org.springframework.boot.autoconfigure.SpringBootApplication 4 | 5 | @SpringBootApplication 6 | class BootConfig -------------------------------------------------------------------------------- /src/main/scala/mvctest/DbPopulator.scala: -------------------------------------------------------------------------------- 1 | package mvctest 2 | 3 | import mvctest.domain.Hotel 4 | import mvctest.service.HotelRepository 5 | import org.springframework.beans.factory.annotation.Autowired 6 | import org.springframework.boot.CommandLineRunner 7 | import org.springframework.stereotype.Component 8 | 9 | /** 10 | * Responsible for populating some initial data into the database.. 11 | */ 12 | 13 | @Component 14 | class DbPopulator @Autowired()(val hotelRepository: HotelRepository) extends CommandLineRunner { 15 | override def run(args: String*): Unit = { 16 | (1 to 10).foreach(i => { 17 | hotelRepository.save(new Hotel(id=null, name = s"Hotel $i", address = s"Address $i", zip = s"Zip $i")) 18 | }) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/scala/mvctest/domain/Hotel.scala: -------------------------------------------------------------------------------- 1 | package mvctest.domain 2 | 3 | import javax.persistence.Id 4 | import javax.persistence.GeneratedValue 5 | import java.lang.Long 6 | import javax.persistence.Entity 7 | 8 | import scala.beans.BeanProperty 9 | import org.hibernate.validator.constraints.NotEmpty 10 | 11 | import scala.annotation.meta.field 12 | 13 | @Entity 14 | class Hotel(@(Id @field) @(GeneratedValue @field) @BeanProperty var id: Long, 15 | @BeanProperty @(NotEmpty @field) var name: String, 16 | @BeanProperty @(NotEmpty @field) var address: String, 17 | @BeanProperty @(NotEmpty @field) var zip: String) { 18 | 19 | def this() = this(null, null, null, null) 20 | } -------------------------------------------------------------------------------- /src/main/scala/mvctest/service/HotelRepository.scala: -------------------------------------------------------------------------------- 1 | package mvctest.service 2 | 3 | import org.springframework.data.repository.CrudRepository 4 | import mvctest.domain.Hotel 5 | import java.lang.Long 6 | 7 | trait HotelRepository extends CrudRepository[Hotel, Long] -------------------------------------------------------------------------------- /src/main/scala/mvctest/web/HotelController.scala: -------------------------------------------------------------------------------- 1 | package mvctest.web 2 | 3 | import java.lang.Long 4 | import javax.validation.Valid 5 | 6 | import mvctest.domain.Hotel 7 | import mvctest.service.HotelRepository 8 | import org.springframework.beans.factory.annotation.Autowired 9 | import org.springframework.stereotype.Controller 10 | import org.springframework.ui.Model 11 | import org.springframework.validation.BindingResult 12 | import org.springframework.web.bind.annotation._ 13 | 14 | @Controller 15 | @RequestMapping(Array("/hotels")) 16 | class HotelController @Autowired()(private val hotelRepository: HotelRepository) { 17 | 18 | @GetMapping 19 | def list(model: Model) = { 20 | val hotels = hotelRepository.findAll() 21 | model.addAttribute("hotels", hotels) 22 | "hotels/list" 23 | } 24 | 25 | @GetMapping(Array("/edit/{id}")) 26 | def edit(@PathVariable("id") id: Long, model: Model) = { 27 | model.addAttribute("hotel", hotelRepository.findOne(id)) 28 | "hotels/edit" 29 | } 30 | 31 | @GetMapping(params = Array("form")) 32 | def createForm(model: Model) = { 33 | model.addAttribute("hotel", new Hotel()) 34 | "hotels/create" 35 | } 36 | 37 | @PostMapping 38 | def create(@Valid hotel: Hotel, bindingResult: BindingResult) = 39 | if (bindingResult.hasErrors()) { 40 | "hotels/create" 41 | } else { 42 | hotelRepository.save(hotel) 43 | "redirect:/hotels" 44 | } 45 | 46 | 47 | @PostMapping(value = Array("/update")) 48 | def update(@Valid hotel: Hotel, bindingResult: BindingResult) = 49 | if (bindingResult.hasErrors()) { 50 | "hotels/edit" 51 | } else { 52 | hotelRepository.save(hotel) 53 | "redirect:/hotels" 54 | } 55 | 56 | 57 | @GetMapping(value = Array("/delete/{id}")) 58 | def delete(@PathVariable("id") id: Long) = { 59 | hotelRepository.delete(id) 60 | "redirect:/hotels" 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /src/main/scala/mvctest/web/RootController.scala: -------------------------------------------------------------------------------- 1 | package mvctest.web 2 | 3 | import org.springframework.stereotype.Controller 4 | import org.springframework.web.bind.annotation.RequestMapping 5 | 6 | @Controller 7 | class RootController { 8 | 9 | @RequestMapping(Array("/")) 10 | def handleRootRequest(): String = "redirect:/hotels" 11 | } 12 | --------------------------------------------------------------------------------