├── .gitignore ├── README.md ├── build.sbt ├── config ├── akka.conf ├── akka_cluster.conf ├── application.conf ├── flash_socket_policy.xml ├── logback.xml ├── ssl_example_fullchain.pem ├── ssl_example_privkey.pem └── xitrum.conf ├── project ├── build.properties └── plugins.sbt ├── public ├── 404.html ├── 500.html ├── app.css ├── favicon.ico ├── robots.txt ├── run_prettify.js └── whale.png ├── sbt ├── sbt ├── sbt-launch-1.7.3.jar └── sbt.bat ├── screenshot.png ├── script ├── runner ├── runner.bat ├── scalive ├── scalive-1.7.0.jar └── scalive.bat └── src └── main ├── scala └── demos │ ├── Boot.scala │ └── action │ ├── ActorActionDemo.scala │ ├── AppAction.scala │ ├── Articles.scala │ ├── CacheDemo.scala │ ├── Chat.scala │ ├── Compo.scala │ ├── Dot.scala │ ├── Errors.scala │ ├── FileMonitorEvents.scala │ ├── Filter.scala │ ├── Forward.scala │ ├── GetPost.scala │ ├── JsonPost.scala │ ├── OpenId.scala │ ├── ScalateAction.scala │ ├── SiteIndex.scala │ ├── SourceCode.scala │ ├── Swagger.scala │ ├── Todos.scala │ └── Upload.scala └── scalate └── demos └── action ├── AppAction.jade ├── ArticlesEdit.jade ├── ArticlesIndex.jade ├── ArticlesNew.jade ├── ArticlesShow.jade ├── CompoMain.jade ├── CompoWithView.jade ├── FileMonitorEvents.jade ├── GetPost.jade ├── JsonPost.jade ├── NotFoundError.jade ├── OpenIdLogin.jade ├── ScalateMustache.mustache ├── ServerError.jade ├── SiteIndex.jade ├── SockJsChat.jade ├── SwaggerDemo.jade ├── TodosIndex.jade ├── Upload.jade ├── WebSocketChat.jade └── _ArticlesForm.jade /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | log 3 | project/project 4 | project/target 5 | target 6 | tmp 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Xitrum projects require Java 8+. 2 | 3 | This project contains various demos for [Xitrum](http://xitrum-framework.github.io/). 4 | 5 | ``` 6 | git clone https://github.com/xitrum-framework/xitrum-demos.git 7 | cd xitrum-demos 8 | sbt/sbt fgRun 9 | ``` 10 | 11 | Now you have a sample project running at http://localhost:8000/ 12 | and https://localhost:4430/ 13 | 14 | ![ScreenShot](screenshot.png) 15 | 16 | To generate Eclipse project: 17 | 18 | ``` 19 | sbt/sbt eclipse 20 | ``` 21 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | organization := "tv.cntt" 2 | name := "xitrum-demos" 3 | version := "1.0-SNAPSHOT" 4 | 5 | scalaVersion := "2.13.4" 6 | 7 | scalacOptions ++= Seq("-deprecation", "-feature", "-unchecked") 8 | 9 | // Xitrum requires Java 8 10 | javacOptions ++= Seq("-source", "1.8", "-target", "1.8") 11 | 12 | //------------------------------------------------------------------------------ 13 | 14 | libraryDependencies += "tv.cntt" %% "xitrum" % "3.31.0" 15 | 16 | // Xitrum uses SLF4J, an implementation of SLF4J is needed 17 | libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.3" 18 | 19 | // For writing condition in logback.xml 20 | libraryDependencies += "org.codehaus.janino" % "janino" % "3.1.2" 21 | 22 | // For Knockout.js demo 23 | libraryDependencies += "tv.cntt" %% "xitrum-ko" % "1.8.1" 24 | 25 | // For OpenID demo 26 | libraryDependencies += "org.openid4java" % "openid4java" % "1.0.0" 27 | 28 | libraryDependencies += "org.webjars.bower" % "bootstrap-css" % "3.3.6" 29 | 30 | // Scalate template engine config for Xitrum ----------------------------------- 31 | 32 | libraryDependencies += "tv.cntt" %% "xitrum-scalate" % "2.9.2" 33 | 34 | // Precompile Scalate templates 35 | import org.fusesource.scalate.ScalatePlugin._ 36 | scalateSettings 37 | Compile / ScalateKeys.scalateTemplateConfig := Seq(TemplateConfig( 38 | (Compile / sourceDirectory).value / "scalate", 39 | Seq(), 40 | Seq(Binding("helper", "xitrum.Action", importMembers = true)) 41 | )) 42 | 43 | // xgettext i18n translation key string extractor is a compiler plugin --------- 44 | 45 | autoCompilerPlugins := true 46 | addCompilerPlugin("tv.cntt" %% "xgettext" % "1.5.4") 47 | scalacOptions += "-P:xgettext:xitrum.I18n" 48 | 49 | // Put config directory in classpath for easier development -------------------- 50 | 51 | // For "sbt console" 52 | Compile / unmanagedClasspath += baseDirectory.value / "config" 53 | 54 | // For "sbt fgRun" 55 | Runtime / unmanagedClasspath += baseDirectory.value / "config" 56 | 57 | // Copy these to target/xitrum when sbt xitrum-package is run 58 | XitrumPackage.copy("config", "public", "script", "src") 59 | 60 | fork := true 61 | -------------------------------------------------------------------------------- /config/akka.conf: -------------------------------------------------------------------------------- 1 | # Config Akka cluster if you want distributed SockJS 2 | # include "akka_cluster" 3 | 4 | akka { 5 | loggers = ["akka.event.slf4j.Slf4jLogger"] 6 | logger-startup-timeout = 30s 7 | } 8 | -------------------------------------------------------------------------------- /config/akka_cluster.conf: -------------------------------------------------------------------------------- 1 | # Config Akka cluster if you want distributed SockJS 2 | # https://doc.akka.io/docs/akka/current/cluster-usage.html 3 | akka { 4 | actor { 5 | provider = "cluster" 6 | } 7 | 8 | # This node 9 | remote.artery { 10 | canonical { 11 | hostname = "127.0.0.1" 12 | port = 2551 13 | } 14 | } 15 | 16 | cluster { 17 | seed-nodes = [ 18 | "akka://xitrum@127.0.0.1:2551", 19 | "akka://xitrum@127.0.0.1:2552" 20 | ] 21 | 22 | downing-provider-class = "akka.cluster.sbr.SplitBrainResolverProvider" 23 | 24 | metrics.enabled = off 25 | } 26 | 27 | extensions = ["akka.cluster.metrics.ClusterMetricsExtension"] 28 | } 29 | -------------------------------------------------------------------------------- /config/application.conf: -------------------------------------------------------------------------------- 1 | include "akka" 2 | include "xitrum" 3 | -------------------------------------------------------------------------------- /config/flash_socket_policy.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /config/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | [%level] %m%n 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | ./log/xitrum.log 31 | 32 | [%level] [%d{yy-MM-dd HH:mm:ss}] %c{1}: %m%n 33 | 34 | 35 | ./log/xitrum.log.%d{yy-MM} 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /config/ssl_example_fullchain.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIBqjCCAROgAwIBAgIJAMB54+xlplaXMA0GCSqGSIb3DQEBBQUAMBYxFDASBgNVBAMTC2V4YW1w 3 | bGUuY29tMCAXDTEzMDYxNDEyNTE0MVoYDzk5OTkxMjMxMjM1OTU5WjAWMRQwEgYDVQQDEwtleGFt 4 | cGxlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAl6s8nYNM7GDx/XEhypNNbA5aG9GT 5 | ER4XZoGJOjntVPFzZqrDlyTMpxOklpGz9L5NsmHF1D/2Ubk1Whk2BXD1y42dYWZkm1rydk20+P8f 6 | h8IYxQK+8glYaouYbqCh1wNHGScNoyohYI+3rzcnu9QiUikSnVVXuWDONxLWs6oZW1cCAwEAATAN 7 | BgkqhkiG9w0BAQUFAAOBgQBwGCMv8lVnQ1QM6TExltmm3U0DM4sz3hElGmvpB+84R3yTxcOWKuQS 8 | u5lIaZ/rEnZCyuTZJWTivI2zKqY4DIkJOoezqshh9JuzcNvWL/gVKX+Z7LGE3+4P6pXj1mycYje2 9 | SSJneN/UznQ/r2imeshoPtiR1Jzo64pgI8eKD0b8FA== 10 | -----END CERTIFICATE----- 11 | -------------------------------------------------------------------------------- /config/ssl_example_privkey.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAJerPJ2DTOxg8f1xIcqTTWwOWhvR 3 | kxEeF2aBiTo57VTxc2aqw5ckzKcTpJaRs/S+TbJhxdQ/9lG5NVoZNgVw9cuNnWFmZJta8nZNtPj/ 4 | H4fCGMUCvvIJWGqLmG6godcDRxknDaMqIWCPt683J7vUIlIpEp1VV7lgzjcS1rOqGVtXAgMBAAEC 5 | gYBD+C9v/3mnrUKFa//SwvS9aikjsmYQE8Y03/RZrcAYgyRObFS/FXTJo1ntSFq3Ydl8CakYl5mR 6 | wkiQmh/FjHv6Dhka+wlqCSz/JdbVW1es1bJVRhxgjuKy/8yh3uMc5NBL5Z4ELPE/YY2k1SVeoM9N 7 | l14+sFprC6Q34G4n0v1Z0QJBANA7RajKPqBGrlbznKOxVI8YoTrNCs+b3LlXmLCUzfFYT19en6ap 8 | qyoa+L1ywQjpdANq6WvzbLfw8UG5JdEMdFUCQQC6djvZvGqeMb++6xoPEOX6Q6GFBgy2XqmEgaLY 9 | syvmvdA7G07MGPVwGQW8OoQ7a4GknohcBRq7UIrT1a03ghz7AkEAvoPMWv8XR1sDvUhMIzRmkjnN 10 | odfhsUsAKo8rgzvSJKNQk4gmd7y6ft6IgASS+o1leI8Dm6Hu8Wg/w4sbP2WutQJAeBCaCWJh5Otz 11 | 5KPOa9UdwUC7SMTUeroJveEb0x3MLxAafXTgEFwh7sSuBL5JV1mqaS4L7/T5eZQrUKxOlWea8QJB 12 | AMqCBAONyw/Zmuk0vuDFDrcq1anL1tmKDhmGdNwh/fJ+n6xFqtzJBFwQJOHjEGmjxUmmSTIoApVI 13 | PQDUSx33Tm4= 14 | -----END PRIVATE KEY----- 15 | -------------------------------------------------------------------------------- /config/xitrum.conf: -------------------------------------------------------------------------------- 1 | xitrum { 2 | # Comment out if you don't want to protect the whole site with basic authentication. 3 | #basicAuth { 4 | #realm = xitrum 5 | #username = xitrum 6 | #password = xitrum 7 | #} 8 | 9 | # Hostname or IP. 10 | # Comment out to listen on all network interfaces. 11 | #interface = localhost 12 | 13 | # Comment out the one you don't want to start. 14 | port { 15 | http = 8000 16 | https = 4430 17 | 18 | # May use same port with HTTP server. 19 | # flash_socket_policy.xml will be returned. 20 | #flashSocketPolicy = 8430 21 | } 22 | 23 | # On Linux, you can use native epoll based transport that uses edge-triggered 24 | # mode for maximal performance and low latency. 25 | edgeTriggeredEpoll = false 26 | 27 | # Not used if port.https above is disabled 28 | https { 29 | # If useOpenSSL is true (HTTPS will be faster), Apache Portable Runtime (APR) 30 | # and OpenSSL must be in the library load path such as system library directories, 31 | # $LD_LIBRARY_PATH, and %PATH%. 32 | openSSL = false 33 | certChainFile = config/ssl_example_fullchain.pem 34 | keyFile = config/ssl_example_privkey.pem 35 | } 36 | 37 | # Comment out if you don't run Xitrum behind a reverse proxy, like Nginx. 38 | # If you do, you should: 39 | # - Configure the proxy to serve static files 40 | # - Set response.autoGzip below to false and let the proxy do the response compressing 41 | #reverseProxy { 42 | # If you run Xitrum behind a proxy, for Xitrum to determine the origin's IP, 43 | # the absolute URL etc., set IP of the proxies here. For security, only proxies 44 | # with IPs set here are allowed. Remember to config the proxy to set the 45 | # following headers properly (see your proxy documentation): 46 | # X-Forwarded-Host 47 | # X-Forwarded-For 48 | # X-Forwarded-Proto, or X-Forwarded-Scheme, or X-Forwarded-Ssl 49 | #ips = ["127.0.0.1"] 50 | 51 | # Set baseUrl to "/my_site" if you want the URL to be http:///my_site/... 52 | # Otherwise set it to empty string 53 | #baseUrl = /my_site 54 | #proxyProtocol = false 55 | #} 56 | 57 | # Comment out to specify the system temporary directory. 58 | tmpDir = ./tmp 59 | 60 | # Comment out if you don't use template engine. 61 | template { 62 | "xitrum.view.Scalate" { 63 | defaultType = jade # jade, mustache, scaml, or ssp 64 | } 65 | } 66 | 67 | cache { 68 | # Simple in-memory cache 69 | "xitrum.local.LruCache" { 70 | maxElems = 10000 71 | } 72 | 73 | # Commented out: Cache is automatically disabled in development mode, 74 | # and enabled in production mode. 75 | # enabled = true: Force cache to be enabled even in development mode. 76 | # enabled = false: Force cache to be disabled even in production mode. 77 | #enabled = true 78 | } 79 | 80 | session { 81 | # Store sessions on client side. 82 | #store = xitrum.scope.session.CookieSessionStore 83 | 84 | # Simple in-memory server side session store. 85 | store { 86 | "xitrum.local.LruSessionStore" { 87 | maxElems = 10000 88 | } 89 | } 90 | 91 | # You can use xitrum-hazelcast if you want clustered server side session store: 92 | # https://github.com/xitrum-framework/xitrum-hazelcast 93 | 94 | # If you run multiple sites on the same domain, make sure that there's no 95 | # cookie name conflict between sites. 96 | cookieName = _session 97 | 98 | # Seconds to live from last access. 99 | # Comment out to delete when browser closes windows. 100 | #cookieMaxAge = 3600 101 | 102 | # Key to encrypt session cookie etc. 103 | # If you deploy your application to several instances be sure to use the same key! 104 | # Do not use the example below! Use your own! 105 | secureKey = "ajconghoaofuxahoi92chunghiaujivietnamlasdoclapjfltudoil98hanhphucup8" 106 | } 107 | 108 | # Static files are put in "public" directory. 109 | staticFile { 110 | # This regex is to optimize static file serving speed by avoiding unnecessary 111 | # file existence check. Ex: 112 | # - "\\.(ico|txt)$": files should end with .txt or .ico extension 113 | # - ".*": file existence will be checked for all requests (not recommended) 114 | pathRegex = "\\.(ico|jpg|jpeg|gif|png|svg|html|htm|txt|css|js|map)$" 115 | 116 | # Small static files are cached in memory. 117 | # Files bigger than maxSizeInKBOfCachedFiles will not be cached. 118 | maxSizeInKBOfCachedFiles = 512 119 | maxNumberOfCachedFiles = 1024 120 | 121 | # true: ETag response header is set for static files. 122 | # Before reusing the files, clients must send requests to server 123 | # to revalidate if the files have been changed. Use this when you 124 | # create HTML directly with static files. 125 | # false: Response headers are set so that clients will cache static files 126 | # for one year. Use this when you create HTML from templates and use 127 | # publicUrl("path/to/static/file") in templates. 128 | revalidate = false 129 | } 130 | 131 | request { 132 | charset = UTF-8 133 | 134 | # Initial line example: "GET / HTTP/1.1". 135 | # Adjust this when you use very long URL, e.g. send a lot of data with GET method. 136 | maxInitialLineLength = 4096 137 | 138 | maxHeaderSize = 81920 139 | 140 | # Increase if you want to allow bigger file upload. 141 | # (Google App Engine's limit: 32 MB) 142 | maxSizeInMB = 32 143 | 144 | # Upload files bigger than maxSizeInKBOfUploadMem will be saved to tmpDir/upload 145 | # instead of memory. 146 | maxSizeInKBOfUploadMem = 16 147 | 148 | # Sensitive parameters that should not be logged to access log. 149 | filteredParams = ["password", "passwordConfirm"] 150 | } 151 | 152 | response { 153 | # Set to true to tell Xitrum to gzip big textual response when 154 | # request header Accept-Encoding contains "gzip": 155 | # http://en.wikipedia.org/wiki/HTTP_compression 156 | autoGzip = true 157 | 158 | sockJsCookieNeeded = false 159 | 160 | # Comment out if you don't use CORS and SockJS (SockJS needs CORS): 161 | # https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS 162 | #corsAllowOrigins = ["*"] 163 | #corsAllowOrigins = ["http://example.com"] 164 | } 165 | 166 | # Version of your app's API, displayed at Swagger Doc (URL: /xitrum/swagger). 167 | # Comment out if you want to disable Swagger Doc (for security reason etc.). 168 | swaggerApiVersion = "1.0-SNAPSHOT" 169 | 170 | # Comment out if you don't want metrics feature. 171 | metrics { 172 | # Key to access /xitrum/metrics/viewer?api_key= 173 | # Do not use the example below! Use your own! 174 | apiKey = "kgreankbeplawfr7934jv2nr0bsbas0" 175 | 176 | # Collect JMX metrics. 177 | jmx = true 178 | 179 | # Collect Xitrum actions metrics. 180 | actions = true 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.7.3 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | // Run sbt eclipse to create Eclipse project file 2 | addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "5.2.4") 3 | 4 | // Run sbt xitrum-package to prepare for deploying to production environment 5 | addSbtPlugin("tv.cntt" % "xitrum-package" % "2.0.0") 6 | 7 | // For precompiling Scalate templates in the compile phase of SBT 8 | addSbtPlugin("org.scalatra.scalate" % "sbt-scalate-precompiler" % "1.9.6.0") 9 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The page you were looking for doesn't exist.

23 | 24 |

You may have mistyped the address or the page may have moved.

25 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

We're sorry, but something went wrong.

23 |

We've been notified about this issue and we'll take a look at it shortly.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /public/app.css: -------------------------------------------------------------------------------- 1 | a { 2 | color: #3B5998; 3 | text-decoration: none; 4 | } 5 | 6 | #chatOutput { 7 | border: 1px solid; 8 | height: 200px; 9 | overflow-x: hidden; 10 | overflow-y: auto; 11 | } 12 | 13 | #chatInput { 14 | width: 98%; 15 | } 16 | 17 | pre.prettyprint{ 18 | overflow-x:scroll; 19 | padding-left:1%; 20 | font-size:12px; 21 | } -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xitrum-framework/xitrum-demos/75629893fdc082ea613d4090e408161530095c2c/public/favicon.ico -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-Agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /public/run_prettify.js: -------------------------------------------------------------------------------- 1 | !function(){/* 2 | 3 | Copyright (C) 2013 Google Inc. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | 17 | Copyright (C) 2006 Google Inc. 18 | 19 | Licensed under the Apache License, Version 2.0 (the "License"); 20 | you may not use this file except in compliance with the License. 21 | You may obtain a copy of the License at 22 | 23 | http://www.apache.org/licenses/LICENSE-2.0 24 | 25 | Unless required by applicable law or agreed to in writing, software 26 | distributed under the License is distributed on an "AS IS" BASIS, 27 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 28 | See the License for the specific language governing permissions and 29 | limitations under the License. 30 | */ 31 | (function(){function ba(g){function k(){try{M.doScroll("left")}catch(g){t.setTimeout(k,50);return}z("poll")}function z(k){if("readystatechange"!=k.type||"complete"==A.readyState)("load"==k.type?t:A)[B](p+k.type,z,!1),!q&&(q=!0)&&g.call(t,k.type||k)}var Y=A.addEventListener,q=!1,C=!0,x=Y?"addEventListener":"attachEvent",B=Y?"removeEventListener":"detachEvent",p=Y?"":"on";if("complete"==A.readyState)g.call(t,"lazy");else{if(A.createEventObject&&M.doScroll){try{C=!t.frameElement}catch(da){}C&&k()}A[x](p+ 32 | "DOMContentLoaded",z,!1);A[x](p+"readystatechange",z,!1);t[x](p+"load",z,!1)}}function U(){V&&ba(function(){var g=N.length;ca(g?function(){for(var k=0;k=a?parseInt(e.substring(1),8):"u"===a||"x"===a?parseInt(e.substring(2),16):e.charCodeAt(1)}function f(e){if(32>e)return(16>e?"\\x0":"\\x")+e.toString(16);e=String.fromCharCode(e);return"\\"===e||"-"=== 36 | e||"]"===e||"^"===e?"\\"+e:e}function b(e){var b=e.substring(1,e.length-1).match(/\\u[0-9A-Fa-f]{4}|\\x[0-9A-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\s\S]|-|[^-\\]/g);e=[];var a="^"===b[0],c=["["];a&&c.push("^");for(var a=a?1:0,h=b.length;an||122n||90n||122l[0]&&(l[1]+1>l[0]&&c.push("-"),c.push(f(l[1])));c.push("]");return c.join("")}function g(e){for(var a=e.source.match(/(?:\[(?:[^\x5C\x5D]|\\[\s\S])*\]|\\u[A-Fa-f0-9]{4}|\\x[A-Fa-f0-9]{2}|\\[0-9]+|\\[^ux0-9]|\(\?[:!=]|[\(\)\^]|[^\x5B\x5C\(\)\^]+)/g),c=a.length,d=[],h=0,l=0;h/,null])):d.push(["com",/^#[^\r\n]*/,null,"#"]));a.cStyleComments&&(f.push(["com",/^\/\/[^\r\n]*/,null]),f.push(["com",/^\/\*[\s\S]*?(?:\*\/|$)/,null]));if(b=a.regexLiterals){var g=(b=1|\\/=?|::?|<>?>?=?|,|;|\\?|@|\\[|~|{|\\^\\^?=?|\\|\\|?=?|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*("+ 45 | ("/(?=[^/*"+b+"])(?:[^/\\x5B\\x5C"+b+"]|\\x5C"+g+"|\\x5B(?:[^\\x5C\\x5D"+b+"]|\\x5C"+g+")*(?:\\x5D|$))+/")+")")])}(b=a.types)&&f.push(["typ",b]);b=(""+a.keywords).replace(/^ | $/g,"");b.length&&f.push(["kwd",new RegExp("^(?:"+b.replace(/[\s,]+/g,"|")+")\\b"),null]);d.push(["pln",/^\s+/,null," \r\n\t\u00a0"]);b="^.[^\\s\\w.$@'\"`/\\\\]*";a.regexLiterals&&(b+="(?!s*/)");f.push(["lit",/^@[a-z_$][a-z_$@0-9]*/i,null],["typ",/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],["pln",/^[a-z_$][a-z_$@0-9]*/i, 46 | null],["lit",/^(?:0x[a-f0-9]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+\-]?\d+)?)[a-z]*/i,null,"0123456789"],["pln",/^\\[\s\S]?/,null],["pun",new RegExp(b),null]);return C(d,f)}function B(a,d,f){function b(a){var c=a.nodeType;if(1==c&&!k.test(a.className))if("br"===a.nodeName)g(a),a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)b(a);else if((3==c||4==c)&&f){var d=a.nodeValue,p=d.match(q);p&&(c=d.substring(0,p.index),a.nodeValue=c,(d=d.substring(p.index+p[0].length))&& 47 | a.parentNode.insertBefore(m.createTextNode(d),a.nextSibling),g(a),c||a.parentNode.removeChild(a))}}function g(a){function b(a,c){var d=c?a.cloneNode(!1):a,n=a.parentNode;if(n){var n=b(n,1),e=a.nextSibling;n.appendChild(d);for(var f=e;f;f=e)e=f.nextSibling,n.appendChild(f)}return d}for(;!a.nextSibling;)if(a=a.parentNode,!a)return;a=b(a.nextSibling,0);for(var d;(d=a.parentNode)&&1===d.nodeType;)a=d;c.push(a)}for(var k=/(?:^|\s)nocode(?:\s|$)/,q=/\r\n?|\n/,m=a.ownerDocument,p=m.createElement("li");a.firstChild;)p.appendChild(a.firstChild); 48 | for(var c=[p],r=0;r=+g[1],d=/\n/g,p=a.a,k=p.length,f=0,m=a.c,t=m.length,b=0,c=a.g,r=c.length,x=0;c[r]=k;var u,e;for(e=u=0;e=l&&(b+=2);f>=n&&(x+=2)}}finally{h&&(h.style.display=a)}}catch(y){R.console&&console.log(y&&y.stack||y)}}var R=window,K=["break,continue,do,else,for,if,return,while"], 51 | L=[[K,"auto,case,char,const,default,double,enum,extern,float,goto,inline,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"],"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],S=[L,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,delegate,dynamic_cast,explicit,export,friend,generic,late_check,mutable,namespace,nullptr,property,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"], 52 | M=[L,"abstract,assert,boolean,byte,extends,finally,final,implements,import,instanceof,interface,null,native,package,strictfp,super,synchronized,throws,transient"],N=[L,"abstract,as,base,bool,by,byte,checked,decimal,delegate,descending,dynamic,event,finally,fixed,foreach,from,group,implicit,in,interface,internal,into,is,let,lock,null,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var,virtual,where"],L=[L,"debugger,eval,export,function,get,instanceof,null,set,undefined,var,with,Infinity,NaN"], 53 | O=[K,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],P=[K,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],K=[K,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],Q=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)\b/, 54 | T=/\S/,U=x({keywords:[S,N,M,L,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",O,P,K],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),X={};p(U,["default-code"]);p(C([],[["pln",/^[^]*(?:>|$)/],["com",/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-", 55 | /^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),"default-markup htm html mxml xhtml xml xsl".split(" "));p(C([["pln",/^[\s]+/,null," \t\r\n"],["atv",/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/], 56 | ["pun",/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);p(C([],[["atv",/^[\s\S]+/]]),["uq.val"]);p(x({keywords:S,hashComments:!0,cStyleComments:!0,types:Q}),"c cc cpp cxx cyc m".split(" "));p(x({keywords:"null,true,false"}),["json"]);p(x({keywords:N,hashComments:!0,cStyleComments:!0, 57 | verbatimStrings:!0,types:Q}),["cs"]);p(x({keywords:M,cStyleComments:!0}),["java"]);p(x({keywords:K,hashComments:!0,multiLineStrings:!0}),["bash","bsh","csh","sh"]);p(x({keywords:O,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),["cv","py","python"]);p(x({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:2}), 58 | ["perl","pl","pm"]);p(x({keywords:P,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb","ruby"]);p(x({keywords:L,cStyleComments:!0,regexLiterals:!0}),["javascript","js"]);p(x({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,throw,true,try,unless,until,when,while,yes",hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);p(C([],[["str",/^[\s\S]+/]]),["regex"]); 59 | var V=R.PR={createSimpleLexer:C,registerLangHandler:p,sourceDecorator:x,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:"com",PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:"lit",PR_NOCODE:"nocode",PR_PLAIN:"pln",PR_PUNCTUATION:"pun",PR_SOURCE:"src",PR_STRING:"str",PR_TAG:"tag",PR_TYPE:"typ",prettyPrintOne:function(a,d,f){f=f||!1;d=d||null;var b=document.createElement("div");b.innerHTML="
"+a+"
";b=b.firstChild;f&&B(b,f,!0);H({j:d,m:f,h:b,l:1,a:null,i:null,c:null,g:null});return b.innerHTML}, 60 | prettyPrint:g=g=function(a,d){function f(){for(var b=R.PR_SHOULD_USE_CONTINUATION?c.now()+250:Infinity;r 17 | respondInlineView("It's " + pastTime + " Unix ms 3s ago.") 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/scala/demos/action/AppAction.scala: -------------------------------------------------------------------------------- 1 | package demos.action 2 | 3 | import xitrum.Action 4 | import xitrum.Config.xitrum.metrics 5 | 6 | trait AppAction extends Action { 7 | override def layout: String = { 8 | at("metricsViewerApiKey") = metrics.get.apiKey 9 | renderViewNoLayout[AppAction]() 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/scala/demos/action/Articles.scala: -------------------------------------------------------------------------------- 1 | package demos.action 2 | 3 | import xitrum.RequestVar 4 | import xitrum.annotation.{First, GET, POST, PATCH, DELETE} 5 | import xitrum.validator.Required 6 | 7 | // Actions --------------------------------------------------------------------- 8 | 9 | // Request vars for passing data from action to Scalate view 10 | object RVArticle extends RequestVar[Article] 11 | object RVArticles extends RequestVar[Iterable[Article]] 12 | 13 | @GET("articles") 14 | class ArticlesIndex extends AppAction { 15 | def execute(): Unit = { 16 | val articles = Article.findAll() 17 | RVArticles.set(articles) 18 | respondView() 19 | } 20 | } 21 | 22 | @GET("articles/:id<[0-9]+>") 23 | class ArticlesShow extends AppAction { 24 | def execute(): Unit = { 25 | val id = param[Int]("id") 26 | Article.find(id) match { 27 | case Some(article) => 28 | RVArticle.set(article) 29 | respondView() 30 | case None => 31 | flash("Article not found") 32 | respond404Page() 33 | } 34 | } 35 | } 36 | 37 | @First // This route has higher priority than "ArticlesShow" above 38 | @GET("articles/new") 39 | class ArticlesNew extends AppAction { 40 | def execute(): Unit = { 41 | val article = new Article() 42 | RVArticle.set(article) 43 | respondView() 44 | } 45 | } 46 | 47 | @POST("articles") 48 | class ArticlesCreate extends AppAction { 49 | def execute(): Unit = { 50 | val title = param("title") 51 | val content = param("content") 52 | val article = Article(title = title, content = content) 53 | article.validationMessage match { 54 | case None => 55 | val id = Article.insert(article) 56 | flash("Article has been saved") 57 | redirectTo[ArticlesShow]("id" -> id) 58 | case Some(msg) => 59 | RVArticle.set(article) 60 | flash(msg) 61 | respondView[ArticlesNew]() 62 | } 63 | } 64 | } 65 | 66 | @GET("articles/:id/edit") 67 | class ArticlesEdit extends AppAction { 68 | def execute(): Unit = { 69 | val id = param[Int]("id") 70 | Article.find(id) match { 71 | case Some(article) => 72 | RVArticle.set(article) 73 | respondView() 74 | case None => 75 | flash("Article not found") 76 | respond404Page() 77 | } 78 | } 79 | } 80 | 81 | @PATCH("articles/:id") 82 | class ArticlesUpdate extends AppAction { 83 | def execute(): Unit = { 84 | val id = param[Int]("id") 85 | val title = param("title") 86 | val content = param("content") 87 | val article = Article(id, title, content) 88 | article.validationMessage match { 89 | case None => 90 | Article.update(article) 91 | flash("Article has been saved") 92 | redirectTo[ArticlesShow]("id" -> id) 93 | case Some(msg) => 94 | RVArticle.set(article) 95 | flash(msg) 96 | respondView[ArticlesEdit]() 97 | } 98 | } 99 | } 100 | 101 | @DELETE("articles/:id") 102 | class ArticlesDestroy extends AppAction { 103 | def execute(): Unit = { 104 | val id = param[Int]("id") 105 | if (id == 1) { 106 | flash("This article is for demo, can't be deleted") 107 | redirectTo[ArticlesIndex]() 108 | } else { 109 | Article.delete(id) 110 | flash("Article has been deleted") 111 | redirectTo[ArticlesIndex]() 112 | } 113 | } 114 | } 115 | 116 | // Model ----------------------------------------------------------------------- 117 | 118 | case class Article(id: Int = 0, title: String = "", content: String = "") { 119 | // Returns Some(error message) or None 120 | def validationMessage: Option[String] = 121 | Required.message("Title", title) orElse 122 | Required.message("Content", content) 123 | } 124 | 125 | object Article { 126 | private var storage = Map[Int, Article]() 127 | private var nextId = 1 128 | 129 | insert(Article(1, "Title 1", "Body 1")) 130 | insert(Article(2, "Title 2", "Body 2")) 131 | 132 | def findAll(): Iterable[Article] = storage.values 133 | 134 | def find(id: Int): Option[Article] = storage.get(id) 135 | 136 | def insert(article: Article): Int = synchronized { 137 | val article2 = Article(nextId, article.title, article.content) 138 | storage = storage + (nextId -> article2) 139 | nextId += 1 140 | article2.id 141 | } 142 | 143 | def update(article: Article): Unit = synchronized { 144 | storage = storage + (article.id -> article) 145 | } 146 | 147 | def delete(id: Int): Unit = synchronized { 148 | storage = storage - id 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/main/scala/demos/action/CacheDemo.scala: -------------------------------------------------------------------------------- 1 | package demos.action 2 | 3 | import xitrum.annotation.{CacheActionSecond, CachePageSecond, GET} 4 | 5 | @GET("cache/action") 6 | @CacheActionSecond(10) 7 | class ActionCacheDemo extends AppAction { 8 | beforeFilter { 9 | log.info("Filter is run") 10 | } 11 | 12 | def execute(): Unit = { 13 | respondInlineView( 14 |
    15 |
  • 16 | In production mode, this action is cached 10 seconds. 17 | (System.currentTimeMillis() below will not change in 10 seconds.) 18 |
  • 19 |
  • For action cache, filters are run. Please try refreshing and see the log.
  • 20 |
  • System.currentTimeMillis(): {System.currentTimeMillis()}
  • 21 |
22 | ) 23 | } 24 | } 25 | 26 | @GET("cache/page") 27 | @CachePageSecond(10) 28 | class PageCacheDemo extends AppAction { 29 | beforeFilter { 30 | log.info("Filter is run") 31 | } 32 | 33 | def execute(): Unit = { 34 | respondInlineView( 35 |
    36 |
  • 37 | In production mode, this page is cached 10 seconds. 38 | (System.currentTimeMillis() below will not change in 10 seconds). 39 |
  • 40 |
  • For page cache, filters are NOT run. Please try refreshing and see the log.
  • 41 |
  • System.currentTimeMillis(): {System.currentTimeMillis()}
  • 42 |
43 | ) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/scala/demos/action/Chat.scala: -------------------------------------------------------------------------------- 1 | package demos.action 2 | 3 | import akka.actor.{Actor, ActorRef, Props, Terminated} 4 | import glokka.Registry 5 | 6 | import xitrum.{Config, Log, SockJsAction, SockJsText, WebSocketAction, WebSocketText, WebSocketBinary, WebSocketPing, WebSocketPong} 7 | import xitrum.annotation.{GET, SOCKJS, WEBSOCKET} 8 | 9 | @GET("sockJsChatDemo") 10 | class SockJsChat extends AppAction { 11 | def execute(): Unit = { 12 | respondView() 13 | } 14 | } 15 | 16 | @GET("websocketChatDemo") 17 | class WebSocketChat extends AppAction { 18 | def execute(): Unit = { 19 | respondView() 20 | } 21 | } 22 | 23 | @SOCKJS("sockJsChat") 24 | class SockJsChatActor extends SockJsAction with LookupOrCreateChatRoom { 25 | def execute(): Unit = { 26 | joinChatRoom() 27 | } 28 | 29 | def onJoinChatRoom(chatRoom: ActorRef): Receive = { 30 | case SockJsText(msg) => 31 | chatRoom ! ChatRoom.Msg(msg) 32 | 33 | case ChatRoom.Msg(body) => 34 | respondSockJsText(body) 35 | } 36 | } 37 | 38 | @WEBSOCKET("websocketChat") 39 | class WebSocketChatActor extends WebSocketAction with LookupOrCreateChatRoom { 40 | def execute(): Unit = { 41 | joinChatRoom() 42 | } 43 | 44 | def onJoinChatRoom(chatRoom: ActorRef): Receive = { 45 | case WebSocketText(msg) => 46 | chatRoom ! ChatRoom.Msg(msg) 47 | 48 | case ChatRoom.Msg(body) => 49 | respondWebSocketText(body) 50 | 51 | case WebSocketBinary(bytes) => 52 | case WebSocketPing => 53 | case WebSocketPong => 54 | } 55 | } 56 | 57 | //------------------------------------------------------------------------------ 58 | // See https://github.com/xitrum-framework/glokka 59 | 60 | object ChatRoom { 61 | val MAX_MSGS = 20 62 | val ROOM_NAME = "chatRoom" 63 | val PROXY_NAME = "chatRoomProxy" 64 | 65 | // Subscribers: 66 | // * To join a chat room, send Join 67 | // * When there's a chat message, receive Msg 68 | case object Join 69 | case class Msg(body: String) 70 | 71 | // Registry is used for looking up chat room actor by name. 72 | // For simplicity, this demo uses only one chat room (lobby chat room). 73 | // If you want many chat rooms, create more chat rooms with different names. 74 | val registry: ActorRef = Registry.start(Config.actorSystem, PROXY_NAME) 75 | } 76 | 77 | /** Subclasses should implement onJoinChatRoom and call joinChatRoom. */ 78 | trait LookupOrCreateChatRoom { 79 | this: Actor => 80 | 81 | import ChatRoom._ 82 | 83 | def onJoinChatRoom(chatRoom: ActorRef): Actor.Receive 84 | 85 | def joinChatRoom(): Unit = { 86 | registry ! Registry.Register(ROOM_NAME, Props[ChatRoom]()) 87 | context.become(waitForRegisterResult) 88 | } 89 | 90 | private def waitForRegisterResult: Actor.Receive = { 91 | case msg: Registry.FoundOrCreated => 92 | val chatRoom = msg.ref 93 | chatRoom ! Join 94 | context.become(onJoinChatRoom(chatRoom)) 95 | } 96 | } 97 | 98 | class ChatRoom extends Actor with Log { 99 | import ChatRoom._ 100 | 101 | private var subscribers = Seq.empty[ActorRef] 102 | private var msgs = Seq.empty[String] 103 | 104 | def receive: Receive = { 105 | case Join => 106 | val subscriber = sender() 107 | subscribers = subscribers :+ subscriber 108 | context.watch(subscriber) 109 | msgs foreach (subscriber ! Msg(_)) 110 | log.debug("Joined chat room: " + subscriber) 111 | 112 | case m @ Msg(body) => 113 | msgs = msgs :+ body 114 | if (msgs.length > MAX_MSGS) msgs = msgs.drop(1) 115 | subscribers foreach (_ ! m) 116 | 117 | case Terminated(subscriber) => 118 | subscribers = subscribers.filterNot(_ == subscriber) 119 | log.debug("Left chat room: " + subscriber) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/main/scala/demos/action/Compo.scala: -------------------------------------------------------------------------------- 1 | package demos.action 2 | 3 | import xitrum.Component 4 | import xitrum.annotation.GET 5 | 6 | import scala.xml.Node 7 | 8 | @GET("compo") 9 | class CompoMain extends AppAction { 10 | def execute(): Unit = { 11 | // In the view template, CompoWithView and CompoWithoutView will be displayed 12 | respondView() 13 | } 14 | } 15 | 16 | class CompoWithView extends Component { 17 | def render(): String = { 18 | renderView() 19 | } 20 | } 21 | 22 | class CompoWithoutView extends Component { 23 | def render(): Node = { 24 | 25 |

Params:

26 |
{textParams}
27 |
28 | } 29 | } 30 | 31 | class CompoNested extends Component { 32 | def render(): Node = { 33 | val compo1 = newComponent[CompoWithView]() 34 | val compo2 = newComponent[CompoWithoutView]() 35 | 36 |

Component1 output:

37 |
{compo1.render()}
38 | 39 |

Component2 output:

40 |
{compo2.render().toString}
41 |
42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/scala/demos/action/Dot.scala: -------------------------------------------------------------------------------- 1 | package demos.action 2 | 3 | import xitrum.annotation.GET 4 | 5 | @GET("articles/:id<[0-9]+>.:format") 6 | class ArticlesDotShow extends AppAction { 7 | def execute(): Unit = { 8 | val id = param[Int]("id") 9 | val format = param("format") 10 | respondInlineView( 11 | 12 | URL = {absUrl[ArticlesDotShow]("id" -> 1, "format" -> "foo")}
13 | id = {id}
14 | format = {format} 15 |
16 | ) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/scala/demos/action/Errors.scala: -------------------------------------------------------------------------------- 1 | package demos.action 2 | 3 | import xitrum.annotation.{Error404, Error500} 4 | 5 | @Error404 6 | class NotFoundError extends AppAction { 7 | def execute(): Unit = { 8 | respondView() 9 | } 10 | } 11 | 12 | @Error500 13 | class ServerError extends AppAction { 14 | def execute(): Unit = { 15 | respondView() 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/scala/demos/action/FileMonitorEvents.scala: -------------------------------------------------------------------------------- 1 | package demos.action 2 | 3 | import java.io.{BufferedWriter, FileWriter} 4 | import java.nio.file.{Path, Paths} 5 | 6 | import xitrum.annotation.{GET, SOCKJS} 7 | import xitrum.Config.xitrum.tmpDir 8 | import xitrum.SockJsAction 9 | import xitrum.util.FileMonitor 10 | 11 | @GET("fileMonitor") 12 | class FileMonitorEvents extends AppAction { 13 | def execute(): Unit = { 14 | respondView() 15 | } 16 | } 17 | 18 | @SOCKJS("fileMonitorSocket") 19 | class FileMonitorEventsSocket extends SockJsAction { 20 | def execute(): Unit = { 21 | val targetDirPath = tmpDir.toPath.toAbsolutePath 22 | val targetFilePath = Paths.get(targetDirPath.toString + "/created_" + System.currentTimeMillis()).toAbsolutePath 23 | 24 | // Start monitoring create event in config dir 25 | FileMonitor.monitor(targetDirPath) { (event, path, stop) => 26 | event match { 27 | case FileMonitor.CREATE => onCreate(path) 28 | case FileMonitor.MODIFY => onModify(path) 29 | case FileMonitor.DELETE => onDelete(path, stop) 30 | } 31 | } 32 | 33 | respondSockJsText(s"[Registered]: File monitor is registered to: $targetDirPath") 34 | 35 | // Kick off file create event 36 | Thread.sleep(1000) 37 | targetFilePath.toFile.createNewFile() 38 | 39 | def onCreate(path: Path): Unit = { 40 | respondSockJsText(s"[Created]: $path") 41 | 42 | // Kick off file modify event 43 | Thread.sleep(1000) 44 | val writer = new BufferedWriter(new FileWriter(path.toFile)) 45 | writer.write("There's text in here wee!!") 46 | writer.close() 47 | } 48 | 49 | def onModify(path: Path): Unit = { 50 | respondSockJsText(s"[Modified]: $path") 51 | 52 | // Kick off file delete event 53 | Thread.sleep(1000) 54 | path.toFile.delete() 55 | } 56 | 57 | def onDelete(path: Path, stop: FileMonitor.Stop): Unit = { 58 | respondSockJsText(s"[Deleted]: $path") 59 | stop() 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/scala/demos/action/Filter.scala: -------------------------------------------------------------------------------- 1 | package demos.action 2 | 3 | import xitrum.annotation.GET 4 | 5 | @GET("filter/before") 6 | class BeforeFilter extends AppAction { 7 | beforeFilter { 8 | log.info("I run therefore I am") 9 | } 10 | 11 | // This method is run after the above filters 12 | def execute(): Unit = { 13 | respondInlineView("Before filters should have been run, please check the log") 14 | } 15 | } 16 | 17 | @GET("filter/after") 18 | class AfterFilter extends AppAction { 19 | afterFilter { 20 | log.info("Run at " + System.currentTimeMillis()) 21 | } 22 | 23 | def execute(): Unit = { 24 | respondInlineView("After filter should have been run, please check the log") 25 | } 26 | } 27 | 28 | @GET("filter/around") 29 | class AroundFilter extends AppAction { 30 | aroundFilter { action => 31 | val begin = System.currentTimeMillis() 32 | action() 33 | val end = System.currentTimeMillis() 34 | log.info("The action took " + (end - begin) + " [ms]") 35 | } 36 | 37 | def execute(): Unit = { 38 | respondInlineView("Around filter should have been run, please check the log") 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/scala/demos/action/Forward.scala: -------------------------------------------------------------------------------- 1 | package demos.action 2 | 3 | import xitrum.annotation.GET 4 | 5 | @GET("forward") 6 | class ForwardDemo extends AppAction { 7 | def execute(): Unit = { 8 | forwardTo[ForwardedDemo]() 9 | } 10 | } 11 | 12 | @GET("forwarded") 13 | class ForwardedDemo extends AppAction { 14 | def execute(): Unit = { 15 | respondInlineView("This action could have been forwarded from ForwardDemo") 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/scala/demos/action/GetPost.scala: -------------------------------------------------------------------------------- 1 | package demos.action 2 | 3 | import xitrum.annotation.{GET, POST} 4 | 5 | @GET("get_post", "get_post_another_route") 6 | @POST("get_post") 7 | class GetPost extends AppAction { 8 | def execute(): Unit = { 9 | respondView() 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/scala/demos/action/JsonPost.scala: -------------------------------------------------------------------------------- 1 | package demos.action 2 | 3 | import io.netty.handler.codec.http.HttpMethod 4 | import xitrum.annotation.{GET, POST} 5 | import xitrum.util.SeriDeseri 6 | 7 | case class MyClass(x: Int, y: Int) 8 | 9 | @GET("json") 10 | @POST("json/:demo") 11 | class JsonPost extends AppAction { 12 | def execute(): Unit = { 13 | if (request.method == HttpMethod.POST) { 14 | param("demo") match { 15 | case "1" => 16 | // You can parse the JSON as a Map like this: 17 | val map = SeriDeseri.fromJValue[Map[String, Int]](requestContentJValue) 18 | respondText(map) 19 | 20 | case _ => 21 | // Or convert it to a (case) class instance 22 | val myClass = SeriDeseri.fromJValue[MyClass](requestContentJValue) 23 | respondText(myClass) 24 | } 25 | } else { 26 | respondView() 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/scala/demos/action/OpenId.scala: -------------------------------------------------------------------------------- 1 | package demos.action 2 | 3 | import scala.util.control.NonFatal 4 | 5 | import org.openid4java.consumer.ConsumerManager 6 | import org.openid4java.discovery.DiscoveryInformation 7 | import org.openid4java.message.ParameterList 8 | 9 | import xitrum.annotation.{GET, POST} 10 | 11 | // See https://code.google.com/p/openid4java/wiki/QuickStart 12 | // 13 | // OpenID4Java dependency has been added to build.sbt: 14 | // libraryDependencies += "org.openid4java" % "openid4java" % "0.9.8" 15 | 16 | object OpenId { 17 | val manager = new ConsumerManager 18 | val SESSION_KEY = "openIdDiscovered" 19 | } 20 | 21 | @GET("openid/login") 22 | class OpenIdLogin extends AppAction { 23 | def execute(): Unit = { 24 | respondView() 25 | } 26 | } 27 | 28 | @POST("openid/redirect") 29 | class OpenIdRedirect extends AppAction { 30 | def execute(): Unit = { 31 | try { 32 | val openId = param("openId") 33 | val discoveries = OpenId.manager.discover(openId) 34 | val discovered = OpenId.manager.associate(discoveries) 35 | val returnUrl = absUrl[OpenIdVerify] // Must be http(s)://... 36 | val authReq = OpenId.manager.authenticate(discovered, returnUrl) 37 | val destinationUrl = authReq.getDestinationUrl(true) 38 | 39 | session(OpenId.SESSION_KEY) = discovered 40 | redirectTo(destinationUrl) 41 | } catch { 42 | case NonFatal(e) => 43 | log.warn("OpenID error", e) 44 | flash("Could not redirect to remote OpenID provider. Your OpenID may be wrong.") 45 | redirectTo[OpenIdLogin]() 46 | } 47 | } 48 | } 49 | 50 | @GET("openid/verify") 51 | class OpenIdVerify extends AppAction { 52 | def execute(): Unit = { 53 | try { 54 | val queryString = request.uri.substring(request.uri.indexOf("?") + 1) 55 | val openIdResp = ParameterList.createFromQueryString(queryString) 56 | val discovered = session(OpenId.SESSION_KEY).asInstanceOf[DiscoveryInformation] 57 | val receivingUrl = absUrl[OpenIdVerify] + "?" + queryString 58 | val verification = OpenId.manager.verify(receivingUrl, openIdResp, discovered) 59 | val verifiedId = verification.getVerifiedId 60 | 61 | session.clear() 62 | if (verifiedId != null) 63 | flash("OpenID login success: " + verifiedId) 64 | else 65 | flash("OpenID login failed") 66 | } catch { 67 | case NonFatal(e) => 68 | log.warn("OpenID error", e) 69 | flash("OpenID login failed") 70 | } 71 | redirectTo[OpenIdLogin]() 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/scala/demos/action/ScalateAction.scala: -------------------------------------------------------------------------------- 1 | package demos.action 2 | 3 | import xitrum.Config 4 | import xitrum.annotation.GET 5 | import xitrum.view.Scalate 6 | 7 | @GET("scalate/notFile") 8 | class ScalateJadeString extends AppAction { 9 | def execute(): Unit = { 10 | val scalate = Config.xitrum.template.get.asInstanceOf[Scalate] 11 | val template = "p This Jade template is from a string, not from a file." 12 | val string = scalate.renderJadeString(template) 13 | respondInlineView(string) 14 | } 15 | } 16 | 17 | @GET("scalate/mustache") 18 | class ScalateMustache extends AppAction { 19 | def execute(): Unit = { 20 | at("name") = "Chris" 21 | at("value") = 10000 22 | at("taxed_value") = 10000 - (10000 * 0.4) 23 | at("in_ca") = true 24 | respondView(Map("type" -> "mustache")) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/scala/demos/action/SiteIndex.scala: -------------------------------------------------------------------------------- 1 | package demos.action 2 | 3 | import xitrum.annotation.GET 4 | 5 | @GET("") 6 | class SiteIndex extends AppAction { 7 | def execute(): Unit = { 8 | respondView() 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/scala/demos/action/SourceCode.scala: -------------------------------------------------------------------------------- 1 | package demos.action 2 | 3 | import scala.util.control.NonFatal 4 | 5 | import javassist.{ClassPool, ClassClassPath} 6 | import xitrum.Config.xitrum.cache 7 | import xitrum.util.Loader 8 | 9 | object SourceCode { 10 | private val classPool = { 11 | val ret = ClassPool.getDefault 12 | val classPath = new ClassClassPath(getClass) 13 | ret.insertClassPath(classPath) 14 | ret 15 | } 16 | 17 | def getSourceFileName(className: String): String = { 18 | cache.getAs[String](className) match { 19 | case Some(fileName) => 20 | fileName 21 | 22 | case None => 23 | val ctClass = classPool.get(className) 24 | val classFile = ctClass.getClassFile 25 | val fileName = classFile.getSourceFile 26 | cache.put(className, fileName) 27 | fileName 28 | } 29 | } 30 | 31 | def getFileContent(path: String): String = { 32 | cache.get(path) match { 33 | case Some(content) => 34 | content.toString 35 | 36 | case None => 37 | try { 38 | val content = Loader.stringFromFile(path) 39 | cache.put(path, content) 40 | content 41 | } catch { 42 | case NonFatal(_) => 43 | "Could not load file: " + path 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/scala/demos/action/Swagger.scala: -------------------------------------------------------------------------------- 1 | package demos.action 2 | 3 | import io.netty.handler.codec.http.HttpResponseStatus 4 | import xitrum.{Action, SkipCsrfCheck} 5 | import xitrum.annotation.{GET, POST, PATCH, DELETE, Swagger} 6 | 7 | @Swagger( 8 | Swagger.Tags("Operations about articles"), 9 | Swagger.Produces("application/json") 10 | ) 11 | trait Api extends Action with SkipCsrfCheck 12 | 13 | @GET("api/articles") 14 | @Swagger( 15 | Swagger.Summary("List all articles") 16 | ) 17 | class ApiArticlesIndex extends Api { 18 | def execute(): Unit = { 19 | val articles = Article.findAll() 20 | respondJson(articles) 21 | } 22 | } 23 | 24 | @GET("api/articles/:id<[0-9]+>") 25 | @Swagger( 26 | Swagger.Summary("Show an article"), 27 | Swagger.IntPath("id", "ID of the article") 28 | ) 29 | class ApiArticlesShow extends Api { 30 | def execute(): Unit = { 31 | val id = param[Int]("id") 32 | Article.find(id) match { 33 | case None => 34 | response.setStatus(HttpResponseStatus.NOT_FOUND) 35 | respondText("Article not found") 36 | 37 | case Some(article) => 38 | respondJson(article) 39 | } 40 | } 41 | } 42 | 43 | @POST("api/articles") 44 | @Swagger( 45 | Swagger.Summary("Create a new article"), 46 | Swagger.Consumes("application/x-www-form-urlencoded"), 47 | Swagger.StringForm("title"), 48 | Swagger.StringForm("content"), 49 | Swagger.Response(200, "ID of the newly created article will be returned") 50 | ) 51 | class ApiArticlesCreate extends Api { 52 | def execute(): Unit = { 53 | val title = param("title") 54 | val content = param("content") 55 | val article = Article(title = title, content = content) 56 | article.validationMessage match { 57 | case None => 58 | val id = Article.insert(article) 59 | respondJson(Map("id" -> id)) 60 | 61 | case Some(msg) => 62 | respondJson(Map("error" -> msg)) 63 | } 64 | } 65 | } 66 | 67 | @PATCH("api/articles/:id") 68 | @Swagger( 69 | Swagger.Summary("Modify an article"), 70 | Swagger.IntPath("id", "ID of the article to modify"), 71 | Swagger.Consumes("application/x-www-form-urlencoded"), 72 | Swagger.StringForm("title", "New title"), 73 | Swagger.StringForm("content", "New content") 74 | ) 75 | class ApiArticlesUpdate extends Api { 76 | def execute(): Unit = { 77 | val id = param[Int]("id") 78 | val title = param("title") 79 | val content = param("content") 80 | val article = Article(id, title, content) 81 | article.validationMessage match { 82 | case None => 83 | Article.update(article) 84 | respondJson(Map("id" -> id)) 85 | 86 | case Some(msg) => 87 | response.setStatus(HttpResponseStatus.BAD_REQUEST) 88 | respondJson(Map("error" -> msg)) 89 | } 90 | } 91 | } 92 | 93 | @DELETE("api/articles/:id") 94 | @Swagger( 95 | Swagger.Summary("Delete an article"), 96 | Swagger.IntPath("id") 97 | ) 98 | class ApiArticlesDestroy extends Api { 99 | def execute(): Unit = { 100 | val id = param[Int]("id") 101 | if (id == 1) { 102 | response.setStatus(HttpResponseStatus.BAD_REQUEST) 103 | respondJson(Map("error" -> "This article is for demo, can't be deleted")) 104 | } else { 105 | Article.delete(id) 106 | respondJson(Map("id" -> id)) 107 | } 108 | } 109 | } 110 | 111 | @GET("swagger") 112 | class SwaggerDemo extends AppAction { 113 | def execute(): Unit = { 114 | respondView() 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/scala/demos/action/Todos.scala: -------------------------------------------------------------------------------- 1 | package demos.action 2 | 3 | import xitrum.RequestVar 4 | import xitrum.annotation.{GET, POST} 5 | import xitrum.util.SeriDeseri 6 | 7 | // Actions --------------------------------------------------------------------- 8 | 9 | // Request var for passing data from action to Scalate view 10 | object RVTodoList extends RequestVar[TodoList] 11 | 12 | @GET("todos") 13 | class TodosIndex extends AppAction { 14 | def execute(): Unit = { 15 | val todoList = TodoList.get() 16 | RVTodoList.set(todoList) 17 | respondView() 18 | } 19 | } 20 | 21 | @POST("todos") 22 | class TodosSave extends AppAction { 23 | def execute(): Unit = { 24 | val json = param("model") 25 | val todoList = SeriDeseri.fromJson[TodoList](json).get 26 | 27 | // You can update the model on the browser like this: 28 | // val newTodoList = ... 29 | // respondJson(newTodoList) 30 | // 31 | // Whenever the model on the browser is updated, Knockout.js will automagically 32 | // update the UI! 33 | 34 | TodoList.update(todoList) 35 | jsRespondFlash("Todo list has been saved") 36 | } 37 | } 38 | 39 | // Model ----------------------------------------------------------------------- 40 | 41 | case class Todo(done: Boolean, desc: String) 42 | case class TodoList(todos: Seq[Todo]) 43 | 44 | object TodoList { 45 | private var storage = TodoList(Seq(Todo(done = true, "Task1"), Todo(done = false, "Task2"))) 46 | 47 | def get(): TodoList = storage 48 | def update(todoList: TodoList): Unit = { storage = todoList } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/scala/demos/action/Upload.scala: -------------------------------------------------------------------------------- 1 | package demos.action 2 | 3 | import io.netty.handler.codec.http.multipart.FileUpload 4 | import xitrum.annotation.{GET, POST} 5 | 6 | @GET("upload") 7 | class Upload extends AppAction { 8 | def execute(): Unit = { 9 | respondView() 10 | } 11 | } 12 | 13 | @POST("upload") 14 | class DoUpload extends AppAction { 15 | def execute(): Unit = { 16 | paramo[FileUpload]("file") match { 17 | case Some(file) => 18 | flash("Uploaded " + file) 19 | 20 | case None => 21 | flash("Please upload a nonempty file") 22 | } 23 | 24 | respondView[Upload]() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/scalate/demos/action/AppAction.jade: -------------------------------------------------------------------------------- 1 | - import demos.action._ 2 | 3 | - val metricsApiKey = at[String]("metricsViewerApiKey") 4 | 5 | !!! 5 6 | html 7 | head 8 | != antiCsrfMeta 9 | != xitrumCss 10 | 11 | meta(content="text/html; charset=utf-8" http-equiv="content-type") 12 | title Welcome to Xitrum 13 | 14 | link(rel="shortcut icon" href={publicUrl("favicon.ico")}) 15 | 16 | link(type="text/css" rel="stylesheet" media="all" href={webJarsUrl("bootstrap-css/3.3.6/css", "bootstrap.css", "bootstrap.min.css")}) 17 | link(type="text/css" rel="stylesheet" media="all" href={publicUrl("app.css")}) 18 | 19 | body 20 | .container 21 | .navbar.navbar-inverse 22 | .navbar-brand 23 | a(href={url[SiteIndex]}) Xitrum Demos 24 | ul.nav.navbar-nav 25 | li 26 | a(href="https://github.com/xitrum-framework/xitrum-demos/tree/master/src/main") Source code 27 | li 28 | a(href="http://xitrum-framework.github.io/") Xitrum Homepage 29 | li 30 | a(href="http://xitrum-framework.github.io/guide.html") Xitrum Guide 31 | li 32 | a(href="http://xitrum-framework.github.io/api.html") API Doc 33 | li 34 | a(href="https://github.com/xitrum-framework/xitrum-new") New Project Skeleton 35 | li 36 | a(href="https://groups.google.com/group/xitrum-framework") Google Group 37 | 38 | .container 39 | .col-md-9 40 | #flash 41 | !~ jsRenderFlash() 42 | 43 | != renderedView 44 | 45 | hr 46 | h3 Source code of current action 47 | div 48 | -# fileName is only, for example, Articles.scala, not /full/path/to/Articles.scala 49 | - val fileName = SourceCode.getSourceFileName(currentAction.getClass.getName) 50 | b= "demos/action/" + fileName 51 | pre(id="actionSrc" class="prettyprint linenums:1 lang-scala") 52 | ~~ SourceCode.getFileContent("src/main/scala/demos/action/" + fileName) 53 | 54 | .col-md-3 55 | p REST 56 | ul 57 | li 58 | a(href={url[ArticlesIndex]}) index 59 | li 60 | a(href={url[ArticlesShow]("id" -> 1)}) show 61 | li 62 | a(href={url[ArticlesEdit]("id" -> 1)}) edit 63 | li 64 | a(href={url[ArticlesNew]}) new 65 | 66 | p Async 67 | ul 68 | li 69 | a(href={url[ActorActionDemo]}) ActorAction (delayed 3s) 70 | li 71 | a(href={url[WebSocketChat]}) WebSocket chat 72 | li 73 | a(href={url[SockJsChat]}) SockJS chat 74 | 75 | p Filters 76 | ul 77 | li 78 | a(href={url[BeforeFilter]}) Before filter 79 | li 80 | a(href={url[AfterFilter]}) After filter 81 | li 82 | a(href={url[AroundFilter]}) Around filter 83 | 84 | p Cache 85 | ul 86 | li 87 | a(href={url[ActionCacheDemo]}) Action cache 88 | li 89 | a(href={url[PageCacheDemo]}) Page cache 90 | 91 | p Scalate 92 | ul 93 | li 94 | a(href={url[ScalateJadeString]}) Template from string 95 | li 96 | a(href={url[ScalateMustache]}) Mustache template 97 | 98 | p Various demos 99 | ul 100 | li 101 | a(href={url[Upload]}) File upload 102 | li 103 | a(href={url[CompoMain]("param1" -> "value1", "param" -> "value2")}) Component 104 | li 105 | a(href={url[ArticlesDotShow]("id" -> 1, "format" -> "foo")}) Dot in path 106 | li 107 | a(href={url[ForwardDemo]}) forwardTo 108 | li 109 | a(href={url[GetPost]}) Multiple routes to an action 110 | li 111 | a(href={url[JsonPost]}) Json POST parse action 112 | li 113 | a(href={url[TodosIndex]}) Knockout.js 114 | li 115 | a(href={url[OpenIdLogin]}) OpenID login 116 | li 117 | a(href={url[FileMonitorEvents]}) File monitor 118 | 119 | p Special demos 120 | ul 121 | li 122 | a(href={url[SwaggerDemo]}) Swagger demo 123 | li 124 | a(href={url[xitrum.metrics.XitrumMetricsViewer]("api_key" -> metricsApiKey)}) Metrics 125 | 126 | != jsDefaults 127 | script(src={publicUrl("run_prettify.js")}) 128 | != jsForView 129 | -------------------------------------------------------------------------------- /src/main/scalate/demos/action/ArticlesEdit.jade: -------------------------------------------------------------------------------- 1 | - import demos.action._ 2 | - val article = RVArticle.get 3 | 4 | h1 Edit article 5 | 6 | form(action={url[ArticlesUpdate]("id" -> article.id)} method="post") 7 | != antiCsrfInput 8 | input(type="hidden" name="_method" value="patch") 9 | 10 | - at("article") = article 11 | != renderFragment("ArticlesForm") 12 | 13 | p 14 | a(href={url[ArticlesShow]("id" -> article.id)}) Cancel | 15 | a(href={url[ArticlesIndex]}) List of articles 16 | -------------------------------------------------------------------------------- /src/main/scalate/demos/action/ArticlesIndex.jade: -------------------------------------------------------------------------------- 1 | - import demos.action._ 2 | 3 | h1 Articles 4 | 5 | ul 6 | - for (article <- RVArticles.get) 7 | li 8 | a(href={url[ArticlesShow]("id" -> article.id)})= article.title 9 | 10 | p 11 | a(href={url[ArticlesNew]}) Create new -------------------------------------------------------------------------------- /src/main/scalate/demos/action/ArticlesNew.jade: -------------------------------------------------------------------------------- 1 | - import demos.action._ 2 | - val article = RVArticle.get 3 | 4 | h1 Create new article 5 | 6 | form(action={url[ArticlesCreate]} method="post") 7 | != antiCsrfInput 8 | 9 | - at("article") = article 10 | != renderFragment("ArticlesForm") 11 | 12 | p 13 | a(href={url[ArticlesIndex]}) List of articles 14 | -------------------------------------------------------------------------------- /src/main/scalate/demos/action/ArticlesShow.jade: -------------------------------------------------------------------------------- 1 | - import demos.action._ 2 | - val article = RVArticle.get 3 | 4 | h1= article.title 5 | 6 | p= article.content 7 | 8 | p 9 | a(href={url[ArticlesEdit]("id" -> article.id)}) Edit | 10 | a(href={url[ArticlesIndex]}) List of articles 11 | -------------------------------------------------------------------------------- /src/main/scalate/demos/action/CompoMain.jade: -------------------------------------------------------------------------------- 1 | - import demos.action._ 2 | 3 | p 4 | | You can create reusable components. A component is similar to an action, but without the 5 | code execute 6 | | method. A component can have or don't have associated views. 7 | 8 | h4 Component that has associated view 9 | 10 | != newComponent[CompoWithView]().render() 11 | 12 | h4 Component that renders directly 13 | 14 | != newComponent[CompoWithoutView]().render() 15 | 16 | h4 Component that contains other components 17 | 18 | != newComponent[CompoNested]().render() 19 | -------------------------------------------------------------------------------- /src/main/scalate/demos/action/CompoWithView.jade: -------------------------------------------------------------------------------- 1 | p Headers: 2 | 3 | pre< 4 | code= request.headers.names 5 | -------------------------------------------------------------------------------- /src/main/scalate/demos/action/FileMonitorEvents.jade: -------------------------------------------------------------------------------- 1 | - import demos.action._ 2 | 3 | h1 FileMonitor demo 4 | 5 | p In this demo: 6 | 7 | ol 8 | li A file will be created 9 | li Then edited 10 | li Then deleted 11 | 12 | #chatOutput 13 | 14 | - 15 | jsAddToView( 16 | "var url = '" + sockJsUrl[FileMonitorEventsSocket] + "';" + 17 | """ 18 | var socket; 19 | 20 | 21 | var initSocket = function() { 22 | socket = new SockJS(url); 23 | 24 | socket.onopen = function(event) { 25 | }; 26 | 27 | socket.onclose = function(event) { 28 | // Reconnect 29 | setTimeout(initSocket, 1000); 30 | }; 31 | 32 | socket.onmessage = function(event) { 33 | var text = event.data + '
'; 34 | xitrum.appendAndScroll('#chatOutput', text); 35 | }; 36 | }; 37 | initSocket(); 38 | """ 39 | ) 40 | -------------------------------------------------------------------------------- /src/main/scalate/demos/action/GetPost.jade: -------------------------------------------------------------------------------- 1 | - import demos.action.GetPost 2 | 3 | p 4 | | Request method: 5 | b= request.method 6 | 7 | textarea(cols="80" rows="20")= request 8 | 9 | br 10 | 11 | a(href={url[GetPost]}) Send GET request to this URL 12 | 13 | br 14 | 15 | form(method="post" action={url[GetPost]}) 16 | != antiCsrfInput 17 | input(type="submit" value="Send POST request to this URL") 18 | -------------------------------------------------------------------------------- /src/main/scalate/demos/action/JsonPost.jade: -------------------------------------------------------------------------------- 1 | - import demos.action.{JsonPost, MyClass} 2 | 3 | p In this demo, you can send a POST request like this: 4 | 5 | ul 6 | li Content-Type: application/json 7 | li Content: {"x": 1, "y": 2} 8 | 9 | table 10 | tr 11 | td(style="width:300px;") 12 | b At server side, parse JSON request content by... 13 | td(style="width:60px;") 14 | b x 15 | td(style="width:60px;") 16 | b y 17 | td(style="width:30px;") 18 | tr 19 | td 20 | span Map[String, Int] 21 | td 22 | span 1 23 | td 24 | span 2 25 | td 26 | button(id="button1" value="Send POST request to this URL") post 27 | tr 28 | td 29 | span case MyClass(x: Int, y: Int) 30 | td 31 | span 3 32 | td 33 | span 4 34 | td 35 | button(id="button2" value="Send POST request to this URL") post 36 | 37 | p POST response: 38 | 39 | pre(id="res") 40 | 41 | 42 | script 43 | = "var action = '" + {url[JsonPost]} + "';" 44 | 45 | :javascript 46 | function post(payload) { 47 | $.ajax({ 48 | type: 'POST', 49 | url: payload.url, 50 | contentType: 'application/json', 51 | dataType: 'text', 52 | data: JSON.stringify(payload.input) 53 | }) 54 | .done(function(data) { 55 | $('#res').text(data) 56 | }); 57 | } 58 | 59 | function loaded() { 60 | $('table tr td').css({'border':'solid 1px gray', 'text-align':'center'}); 61 | $('#button1').on('click',function(e){ 62 | e.preventDefault(); 63 | var payload = { 64 | url: action + '/1', 65 | input: {'x':1, 'y':2} 66 | }; 67 | post(payload); 68 | }); 69 | $('#button2').on('click', function(e) { 70 | e.preventDefault(); 71 | var payload = { 72 | url: action + '/2', 73 | input: {'x':3, 'y':4} 74 | }; 75 | post(payload); 76 | }); 77 | } 78 | 79 | - 80 | jsAddToView("loaded();") 81 | 82 | -------------------------------------------------------------------------------- /src/main/scalate/demos/action/NotFoundError.jade: -------------------------------------------------------------------------------- 1 | p This is custom 404 page 2 | -------------------------------------------------------------------------------- /src/main/scalate/demos/action/OpenIdLogin.jade: -------------------------------------------------------------------------------- 1 | - import demos.action.OpenIdRedirect 2 | 3 | form(method="post" action={url[OpenIdRedirect]}) 4 | != antiCsrfInput 5 | 6 | p 7 | | Your OpenID URL: 8 | input(type="text" name="openId" size="60") 9 | input(type="submit" value="Login") 10 | br 11 | | (Ex: openid.aol.com/example) 12 | 13 | p OpenID login works like this: 14 | 15 | ol 16 | li The user inputs his OpenID URL. 17 | li You redirect the user to the remote OpenID provider (another site). 18 | li At the remote site, the user may cancel the login or proceed the login. 19 | li The remote site redirects the user back to your site. 20 | li You verify the above request. 21 | 22 | p Most OpenID libraries allow you to manually handle step 2 and 5. 23 | 24 | p Notes: 25 | 26 | ul 27 | li 28 | | This login demo is based on 29 | a(href="https://code.google.com/p/openid4java/wiki/QuickStart") OpenID4Java 30 | li 31 | | To securely verify the login, some data must be saved in session. This data 32 | | is bigger than cookie limit (4KB), so you can't use cookie to store session 33 | | (see xitrum.scope.session.CookieSessionStore in xitrum.conf). 34 | -------------------------------------------------------------------------------- /src/main/scalate/demos/action/ScalateMustache.mustache: -------------------------------------------------------------------------------- 1 | Hello {{name}} 2 | You have just won ${{value}}! 3 | {{#in_ca}} 4 | Well, ${{taxed_value}}, after taxes. 5 | {{/in_ca}} 6 | -------------------------------------------------------------------------------- /src/main/scalate/demos/action/ServerError.jade: -------------------------------------------------------------------------------- 1 | p This is custom 500 page 2 | -------------------------------------------------------------------------------- /src/main/scalate/demos/action/SiteIndex.jade: -------------------------------------------------------------------------------- 1 | img(src={publicUrl("whale.png")}) 2 | -------------------------------------------------------------------------------- /src/main/scalate/demos/action/SockJsChat.jade: -------------------------------------------------------------------------------- 1 | - import demos.action._ 2 | 3 | h1 SockJS chat 4 | 5 | #chatOutput 6 | input(type="text" id="chatInput" name="chatInput" class="required" disabled="disabled") 7 | 8 | - 9 | jsAddToView( 10 | "var url = '" + sockJsUrl[SockJsChatActor] + "';" + 11 | """ 12 | var socket; 13 | 14 | // Do not put in initSocket so that this event handler is registered only once 15 | $('#chatInput').keypress(function(event) { 16 | var keycode = (event.keyCode ? event.keyCode : event.which); 17 | if (keycode == '13') { 18 | socket.send($('#chatInput').val()); 19 | $('#chatInput').val(''); 20 | } 21 | }); 22 | 23 | var initSocket = function() { 24 | socket = new SockJS(url); 25 | 26 | socket.onopen = function(event) { 27 | var text = '[Connected]
'; 28 | xitrum.appendAndScroll('#chatOutput', text); 29 | $('#chatInput').removeAttr('disabled'); 30 | }; 31 | 32 | socket.onclose = function(event) { 33 | var text = '[Disconnected]
'; 34 | xitrum.appendAndScroll('#chatOutput', text); 35 | $('#chatInput').attr('disabled', 'disabled'); 36 | 37 | // Reconnect 38 | setTimeout(initSocket, 1000); 39 | }; 40 | 41 | socket.onmessage = function(event) { 42 | var text = '- ' + xitrum.escapeHtml(event.data) + '
'; 43 | xitrum.appendAndScroll('#chatOutput', text); 44 | }; 45 | }; 46 | initSocket(); 47 | """ 48 | ) 49 | -------------------------------------------------------------------------------- /src/main/scalate/demos/action/SwaggerDemo.jade: -------------------------------------------------------------------------------- 1 | h1 Swagger demo 2 | 3 | p 4 | | Add Swagger doc to your APIs like the source code below. 5 | 6 | p 7 | | Then you can test the APIs with the 8 | a(href={url[xitrum.routing.SwaggerUi]}) Swagger UI 9 | | tool. 10 | 11 | p 12 | | You can use 13 | a(href="https://github.com/wordnik/swagger-codegen") Swagger Codegen 14 | | to automatically generate client side source code for Java (for use in Android apps), 15 | | Objective-C (for use in iOS apps) etc. 16 | -------------------------------------------------------------------------------- /src/main/scalate/demos/action/TodosIndex.jade: -------------------------------------------------------------------------------- 1 | - import xitrum.Knockout 2 | - import demos.action._ 3 | 4 | != Knockout.js 5 | 6 | h1 Todo list 7 | 8 | #todos(data-bind="template: {name: 'todo-template', foreach: todos}") 9 | 10 | form#new_todo_form 11 | input#new_todo_desc(type="text" class="required") 12 | input#new_todo_add(type="submit" value="Add") 13 | 14 | br 15 | input#save(type="button" value="Save") 16 | 17 | script#todo-template(type="text/html") 18 | div 19 | input(type="checkbox" data-bind="checked: done") 20 | / ko if: done 21 | strike 22 | span(data-bind="text: desc") 23 | / /ko 24 | / ko ifnot: done 25 | span(data-bind="text: desc") 26 | / /ko 27 | 28 | - 29 | Knockout.applyBindingsCs(RVTodoList.get, classOf[TodosSave], """ 30 | $('#new_todo_form').submit -> 31 | if $('#new_todo_form').valid() 32 | desc = $('#new_todo_desc').val() 33 | $('#new_todo_desc').val('') 34 | todo = {done: false, desc: desc} 35 | model.todos.push(ko.mapping.fromJS(todo)) 36 | false 37 | 38 | $('#save').click(sync) 39 | """) 40 | 41 | div 42 | br 43 | hr 44 | p In this sample, the server side only has to work with Scala data structures: 45 | pre< 46 | code< 47 | :~~escaped 48 | case class Todo(done: Boolean, desc: String) 49 | case class TodoList(todos: Seq[Todo]) 50 | 51 | p 52 | | More info: 53 | a(href="http://todomvc.com/") todomvc.com 54 | -------------------------------------------------------------------------------- /src/main/scalate/demos/action/Upload.jade: -------------------------------------------------------------------------------- 1 | - import demos.action.DoUpload 2 | 3 | form(enctype="multipart/form-data" method="post" action={url[DoUpload]}) 4 | != antiCsrfInput 5 | input(type="file" name="file") 6 | input(type="submit" value="Upload") 7 | -------------------------------------------------------------------------------- /src/main/scalate/demos/action/WebSocketChat.jade: -------------------------------------------------------------------------------- 1 | - import demos.action._ 2 | 3 | h1 WebSocket chat 4 | 5 | #chatOutput 6 | input(type="text" id="chatInput" name="chatInput" class="required" disabled="disabled") 7 | 8 | - 9 | jsAddToView( 10 | "var url = '" + absWebSocketUrl[WebSocketChatActor] + "';" + 11 | """ 12 | var socket; 13 | 14 | // Do not put in initSocket so that this event handler is registered only once 15 | $('#chatInput').keypress(function(event) { 16 | var keycode = (event.keyCode ? event.keyCode : event.which); 17 | if (keycode == '13') { 18 | socket.send($('#chatInput').val()); 19 | $('#chatInput').val(''); 20 | } 21 | }); 22 | 23 | var initSocket = function() { 24 | if (!window.WebSocket) window.WebSocket = window.MozWebSocket; 25 | if (!window.WebSocket) { 26 | alert("Your browser does not support WebSocket."); 27 | } else { 28 | socket = new WebSocket(url); 29 | 30 | socket.onopen = function(event) { 31 | var text = '[Connected]
'; 32 | xitrum.appendAndScroll('#chatOutput', text); 33 | $('#chatInput').removeAttr('disabled'); 34 | }; 35 | 36 | socket.onclose = function(event) { 37 | var text = '[Disconnected]
'; 38 | xitrum.appendAndScroll('#chatOutput', text); 39 | $('#chatInput').attr('disabled', 'disabled'); 40 | 41 | // Reconnect 42 | setTimeout(initSocket, 1000); 43 | }; 44 | 45 | socket.onmessage = function(event) { 46 | var text = '- ' + xitrum.escapeHtml(event.data) + '
'; 47 | xitrum.appendAndScroll('#chatOutput', text); 48 | }; 49 | } 50 | }; 51 | initSocket(); 52 | """ 53 | ) 54 | -------------------------------------------------------------------------------- /src/main/scalate/demos/action/_ArticlesForm.jade: -------------------------------------------------------------------------------- 1 | - import demos.action._ 2 | - val article = at("article").asInstanceOf[Article] 3 | 4 | label Title: 5 | br 6 | input(type="text" name="title" value={article.title}) 7 | br 8 | 9 | label Content: 10 | br 11 | textarea(name="content" rows=20 cols=60)= article.content 12 | br 13 | 14 | p 15 | input(type="submit" value="Save") 16 | --------------------------------------------------------------------------------