threepenny-gui 0.2.0.1 → 0.9.4.2
raw patch · 98 files changed
Files
- CHANGELOG.md +278/−0
- LICENSE +10/−0
- README.md +65/−0
- js/comm.js +163/−0
- js/ffi.js +135/−0
- js/haskell.css +3/−0
- js/index.html +21/−0
- js/lib.js +111/−0
- js/lib/jquery-cookie.js +56/−0
- js/lib/jquery.js +4/−0
- js/log.js +32/−0
- samples/BarTab.hs +77/−0
- samples/Buttons.hs +75/−0
- samples/CRUD.hs +135/−0
- samples/Canvas.hs +108/−0
- samples/Chat.hs +104/−0
- samples/CurrencyConverter.hs +39/−0
- samples/Data/List/Extra.hs +8/−0
- samples/DragNDropExample.hs +61/−0
- samples/DrumMachine.hs +92/−0
- samples/Paths.hs +24/−0
- samples/README.md +47/−0
- samples/Svg.hs +44/−0
- samples/static/chat.html +16/−0
- samples/static/css/DragNDropExample.css +18/−0
- samples/static/css/buttons.css +52/−0
- samples/static/css/chat.css +106/−0
- samples/static/css/fade.png binary
- samples/static/css/stripes-bg.png binary
- samples/static/css/wood-bg.png binary
- samples/static/haskell-logo.png binary
- samples/static/hihat.wav binary
- samples/static/kick.wav binary
- samples/static/snare.wav binary
- src/BarTab.hs +0/−87
- src/Buttons.hs +0/−85
- src/Chat.hs +0/−108
- src/Control/Concurrent/Chan/Extra.hs +0/−22
- src/Control/Concurrent/Delay.hs +0/−17
- src/Control/Event.hs +0/−111
- src/Control/Monad/Extra.hs +0/−30
- src/Control/Monad/IO.hs +0/−8
- src/Data/List/Extra.hs +0/−8
- src/DragNDropExample.hs +0/−73
- src/DrumMachine.hs +0/−108
- src/Foreign/JavaScript.hs +111/−0
- src/Foreign/JavaScript/CallBuffer.hs +53/−0
- src/Foreign/JavaScript/EventLoop.hs +202/−0
- src/Foreign/JavaScript/Include.hs +17/−0
- src/Foreign/JavaScript/Marshal.hs +251/−0
- src/Foreign/JavaScript/Resources.hs +23/−0
- src/Foreign/JavaScript/Server.hs +192/−0
- src/Foreign/JavaScript/Types.hs +395/−0
- src/Foreign/RemotePtr.hs +208/−0
- src/Graphics/UI/Threepenny.hs +96/−8
- src/Graphics/UI/Threepenny/Attributes.hs +19/−16
- src/Graphics/UI/Threepenny/Canvas.hs +250/−16
- src/Graphics/UI/Threepenny/Core.hs +204/−300
- src/Graphics/UI/Threepenny/DragNDrop.hs +4/−4
- src/Graphics/UI/Threepenny/Elements.hs +10/−9
- src/Graphics/UI/Threepenny/Events.hs +100/−13
- src/Graphics/UI/Threepenny/Internal.hs +382/−0
- src/Graphics/UI/Threepenny/Internal/Core.hs +0/−630
- src/Graphics/UI/Threepenny/Internal/Resources.hs +0/−54
- src/Graphics/UI/Threepenny/Internal/Types.hs +0/−260
- src/Graphics/UI/Threepenny/JQuery.hs +22/−34
- src/Graphics/UI/Threepenny/SVG.hs +18/−0
- src/Graphics/UI/Threepenny/SVG/Attributes.hs +367/−0
- src/Graphics/UI/Threepenny/SVG/Elements.hs +125/−0
- src/Graphics/UI/Threepenny/Timer.hs +39/−25
- src/Graphics/UI/Threepenny/Widgets.hs +121/−0
- src/Graphics/UI/driver.css +0/−3
- src/Graphics/UI/driver.js +0/−461
- src/Graphics/UI/index.html +0/−10
- src/Graphics/UI/jquery-cookie.js +0/−56
- src/Graphics/UI/jquery.js +0/−4
- src/MissingDollars.hs +0/−142
- src/Paths.hs +0/−20
- src/Reactive/Threepenny.hs +398/−0
- src/Reactive/Threepenny/Memo.hs +42/−0
- src/Reactive/Threepenny/Monads.hs +12/−0
- src/Reactive/Threepenny/PulseLatch.hs +203/−0
- src/Reactive/Threepenny/Types.hs +41/−0
- src/UseWords.hs +0/−110
- threepenny-gui.cabal +175/−105
- wwwroot/and-then-haskell.txt +0/−77
- wwwroot/chat.html +0/−13
- wwwroot/css/DragNDropExample.css +0/−18
- wwwroot/css/buttons.css +0/−52
- wwwroot/css/chat.css +0/−106
- wwwroot/css/fade.png binary
- wwwroot/css/missing-dollars.css +0/−52
- wwwroot/css/stripes-bg.png binary
- wwwroot/css/use-words.css +0/−70
- wwwroot/css/wood-bg.png binary
- wwwroot/hihat.wav binary
- wwwroot/kick.wav binary
- wwwroot/snare.wav binary
+ CHANGELOG.md view
@@ -0,0 +1,278 @@+## Changelog for the `threepenny-gui` package++**0.9.4.2** – Maintenance and snapshot release++* Bump dependencies for compatibility with GHC-9.12.++**0.9.4.1** – Maintenance and snapshot release++* Bump dependencies for compatibility with GHC-9.8.++**0.9.4.0** – Maintenance and snapshot release++* Fix support for SSL: Export `ConfigSSL` constructor.++**0.9.2.0** – Maintenance and snapshot release++* Add support for SSL.++ To start the server as an HTTPS server, use the `jsUseSSL` field with appropriate parameters. For security reasons, no information is read from the environment in this case.++* Bump dependencies for compatibility with GHC-9.4.+* Bump dependencies for compatibility with GHC-9.2.++**0.9.1.0** – Maintenance and snapshot release++* Add support for websockets over SSL.+* Bump dependencies for compatibility with GHC-9.0.++**0.9.0.0** – Maintenance and snapshot release++* The events `contextmenu`, `mousedown`, `mousemove` and `mouseup` now return+ `Double` coordinates instead of `Int`s. This change reflects updates to the+ underlying browser APIs and the jQuery library. [#238][]++ Users who prefer to keep working with `Int` coordinates may use the added+ `roundCoordinates` compatibility function.++* Bump dependencies to allow `aeson` 1.5.++[#238]: https://github.com/HeinrichApfelmus/threepenny-gui/issues/238++**0.8.3.2** – Maintenance release++* Bump dependencies for compatibility with GHC-8.10.++**0.8.3.1** – Maintenance release++* Bump dependencies for compatibility with GHC-8.8.+* Bump dependencies to allow `hashable` 1.3.0.0.+* Remove support for GHC 7.6 and 7.8.++**0.8.3.0** – Maintenance and snapshot release++* Export `keypress` event.+* Fix the spelling of the `refX` and `refY` SVG attributes.+ `refX` and `refY` are added to `Graphics.UI.Threepenny.SVG.Attributes`,+ the old lowercase versions are deprecated.+* Invoke compatibility mode of IE11 for jQuery v3.2.1.+* Compatibility with GHC-8.6.1++**0.8.2.4** – Maintenance release++* Remove redundant dependencies on `network-uri` and `network`+* Bump dependencies to allow `aeson` 1.4.0.0+* Exclude `websockets` 0.12.5.0 from dependencies.++**0.8.2.3** – Maintenance release++* Compatibility with GHC-8.4.1+* Bump dependencies to allow `aeson` 1.3.0.0+* Bump dependencies to allow `exceptions` 0.10.0+* Bump dependencies to allow `snap-server` 1.1.0.0++**0.8.2.2** – Maintenance release++* Bump dependencies to allow `exceptions` 0.9.0++**0.8.2.1** — Maintenance release++* Bump dependencies to allow `async` 2.2+* Fix a compatibility issue with Cabal-2.0++**0.8.2.0** — Snapshot release++* Add `getCookies` function that retrieves the cookies sent with the HTTP request when the browser window connects (to the websocket). [#137][]+* Allow Electron process to be accessed from JavaScript FFI. [#200][] This means that Threepenny is now more useful when used with the [Electron][] framework, see [doc/electron.md](doc/electron.md) for more information on that.+* Bump dependencies to allow `file-embed` 0.0.10.1++ [#137]: https://github.com/HeinrichApfelmus/threepenny-gui/issues/137+ [#200]: https://github.com/HeinrichApfelmus/threepenny-gui/issues/200+ [electron]: https://electron.atom.io++**0.8.1.0** — Snapshot release++* Improve documentation and handling of call buffering (`CallBufferMode`). The default call buffer mode was documented incorrectly, it was `BufferRun` and is now `FlushOften`. [#163][], [#191][], [#192][]+* Add new default `CallBufferMode`: `FlushOften`. This mode is like `BufferRun`, but will flush the buffer at every `onEvent` as well, leading to less confusion when using the library in most circumstances. [#191][]+* Add new `CallBufferMode`: `FlushPeriodically`. This mode is like `BufferRun`, but will flush the call buffer every 300ms if nonempty. [#192][]+* Add support for [custom DOM events][customevent] (`CustomEvent`). [#196][]+* Expose JavaScript FFI functions `toJSObject` and `liftJSWindow` in `Graphics.UI.Threepenny`. This is useful for linking the lifetime of JavaScript objects to the lifetime of `Element`. [#181][]+* Use `jsLog` parameter to log exceptions. [#185][]+* Update bundled jQuery to version 3.2.1. [#186][]++ [customevent]: https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent+ [#163]: https://github.com/HeinrichApfelmus/threepenny-gui/issues/163+ [#181]: https://github.com/HeinrichApfelmus/threepenny-gui/issues/181+ [#185]: https://github.com/HeinrichApfelmus/threepenny-gui/issues/185+ [#186]: https://github.com/HeinrichApfelmus/threepenny-gui/issues/186+ [#191]: https://github.com/HeinrichApfelmus/threepenny-gui/issues/191+ [#192]: https://github.com/HeinrichApfelmus/threepenny-gui/issues/192+ [#196]: https://github.com/HeinrichApfelmus/threepenny-gui/issues/196+++**0.8.0.1** — Maintenance release++* Bump dependencies for compatibility with GHC-8.2.1+* Bump dependencies to allow `websockets` 0.12++**0.8.0.0** — Snapshot release++Graphics.UI.Threepenny++* Fix `getElementById` to properly return `Nothing` when no element with the given `id` attribute is present. [#129][].+* Bring back `loadFile` and `loadDirectory`. [#110][]+* Add `MonadUI` typeclass for easier lifting in custom monad stacks. [#173][]++JavaScript FFI++* Implement batching / buffering for FFI calls. [#131][]. Several calls to the `runFunction` function may now buffer the JavaScript code and send it to the browser window in a single message at a later time. This improves performance considerably.+* Clarify semantics for exceptions. See the file [`doc/javascript-ffi.md`](doc/javascript-ffi.md) for more.+ * The `UI` monad now supports exception handling by being an instance of the type classes `MonadThrow` and `MonadCatch`.+ * The function `callFunction` can now throw a `JavaScriptException` exception to the Haskell side.+ * The function `runFunction` now terminates the connection to the browser window whenever the JavaScript code within throws an exception.+* Exceptions in the `UI` monad that are not handled are now printed properly for better debugging. [#145][]+* Clarify semantics of the `disconnect` event. It is now triggered more reliably. [#133][].+* Remove unnecessary client response when exporting event handlers. [#131][].+* Add option `jsWindowReloadOnDisconnect` to reload the browser window [#130][] whenever the WebSocket connection is broken. This is useful for e.g. mobile devices, which tend to disconnect WebSocket connections very often.++ [#110]: https://github.com/HeinrichApfelmus/threepenny-gui/issues/110+ [#129]: https://github.com/HeinrichApfelmus/threepenny-gui/issues/129+ [#130]: https://github.com/HeinrichApfelmus/threepenny-gui/issues/130+ [#133]: https://github.com/HeinrichApfelmus/threepenny-gui/issues/133+ [#145]: https://github.com/HeinrichApfelmus/threepenny-gui/issues/145+ [#173]: https://github.com/HeinrichApfelmus/threepenny-gui/issues/173++Dependencies++* Add dependency on `exceptions`++**0.7.0.2** — Maintenance release++* Bump dependencies to allow `aeson` 1.2+* Bump dependencies to allow `websockets` 0.11++**0.7.0.1** — Maintenance release++* Bump dependencies to allow `aeson` 1.1+* Bump dependencies to allow `vector` 0.12+* Bump dependencies to allow `websockets` 0.10++**0.7.0.0** — Maintenance and snapshot release++* JavaScript FFI: Reduce communication from browser to server when creating `Element`s.+ New function `unsafeCreateJSObject` to create JavaScript objects without+ waiting for a client response. [#131][]+* JavaScript FFI: Implement escape sequence '%%' when calling JavaScript functions. [#132][].+* Change type of `onEvent` function to allow unregistering events.+* Add function `timestamp` for simple performance measurements.+* Update JavaScript dependencies to jQuery 2.2.3+* Adapt to GHC 8.0.1. [#138][]+* Bump dependencies to allow `aeson` 1.0+* Bump dependencies to allow `data-default` 0.7+* Bump dependencies to allow `snap-core` 1.0 and `snap-server` 1.0+* Bump dependencies to allow `template-haskell` 2.11+* Bump dependencies to allow `websockets-snap` 0.10++ [#131]: https://github.com/HeinrichApfelmus/threepenny-gui/issues/131+ [#132]: https://github.com/HeinrichApfelmus/threepenny-gui/issues/132+ [#138]: https://github.com/HeinrichApfelmus/threepenny-gui/issues/138++**0.6.0.6** — Maintenance release++* Bump dependencies to allow `base` 4.9+* Bump dependencies to allow `aeson` 0.11++**0.6.0.5** — Maintenance release++* Bump dependencies to allow `async` 2.1+* Bump dependencies to allow `transformers` 0.5++**0.6.0.4** — Maintenance release.++* Elements that have become unreachable, for instance because they have been removed from the DOM and are no longer reachable in the Haskell code, will be garbage collected again. Fix [#109][], [#113][].+* Adjust dependencies.+* Add `<meta>` tag to indicate UTF8 encoding in html file. [#116][]++ [#113]: https://github.com/HeinrichApfelmus/threepenny-gui/issues/113+ [#109]: https://github.com/HeinrichApfelmus/threepenny-gui/issues/109+ [#116]: https://github.com/HeinrichApfelmus/threepenny-gui/issues/116++**0.6.0.3** — Maintenance release.++* Temporary fix for #109, which was causing event handlers to be discarded. Unfortunately, this means that elements are currently not garbage collected after they have been removed from the DOM tree.++**0.6.0.2** — Maintenance release.++* Remove unused direct dependencies, in particular+** attoparsec-enumerator+** utf8-string+** MonadCatchIO-transformers+** time++**0.6.0.1** — Maintenance release.++* The `ADDR` environment variable is now parsed correctly.+* Now builds on GHC 7.8 and GHC 7.10+* The example source code in the `samples` folder has been reorganized and consolidated.++**0.6.0.0** — Snapshot release.++* The internals of the JavaScript FFI has been reimplemented completely. A new module `Foreign.JavaScript` exports a bare JavaScript FFI in case you want to write a custom GUI framework. However, the module `Graphics.UI.Threepenny` is *not* compatible with it, even though it builds on top of it.+* The fields of `Config` type for server configuration are now prefixed with `js` instead of `tp`. Example: `jsPort`, `jsStatic`.+* The functions `loadFile` and `loadDirectory` have been *removed*, as I felt that the `jsStatic` option is sufficient for most use cases.++**0.5.0.0** — Snapshot release.++* Possibility to specify IP address to bind the server to.+* FFI now supports callbacks into Haskell. Remove `callDeferredFunction` function.+* `Graphics.UI.Threepenny.Canvas.SVG` for creating SVG elements and attributes.+* 2D graphics API in `Graphics.UI.Threepenny.Canvas` is beginning to grow.+* `Bool` is now correctly marshalled to JavaScript.+* `Text` can now be marshalled to JavaScrtip.++**0.4.2.0** — Maintenance release.++* Dependency `bytestring >=0.9.2` is now implemented correctly.+* Allow newer versions of `aeson` dependency.+* Allow newer versions of `network`, `transformers` and `template-haskell` dependencies.+* Helper scripts in the `samples` directory now assume that you use a cabal sandbox for development.+* The `UI` monad is now an instance of the `Applicative` class.++**0.4.1.0** — Maintenance release.++* Dependency on `text` package now from version 0.11 to 1.1.*.+* Dependency on `aeson` package replaces the former dependency on the `json` package.+* Unicode characters are now transmitted correctly to the browser. #75, #62.+* Change default port number to 8023. #64++**0.4.0.2** — Bugfix release.++* Fix CSS bug for `grid` function.++**0.4.0.1** — Maintenance release.++* Adjust package dependencies.++**0.4.0.0** — Snapshot release.++* New `UI` monad for easier JavaScript FFI and recursion in FRP.+* Garbage collection for DOM elements. (Unfortunately, this doesn't support using custom HTML files anymore, see [issue #60][#60].)+* First stab at widgets.+* Bump dependencies to allow `websockets` 0.8++ [#60]: https://github.com/HeinrichApfelmus/threepenny-gui/issues/60++**0.3.0.0** — Snapshot release.++* Browser communication with WebSockets.+* First stab at FRP integration.++**0.2.0.0** — Snapshot release.++* First stab at easy JavaScript FFI.++**0.1.0.0**++* Initial release.++
LICENSE view
@@ -1,3 +1,13 @@+The following license ("BSD3") applies to almost all files in this project,+in particular the source code indicated in the `threepenny-gui.cabal` file.+Exceptions are audio sample (*.wav) files,+and files whose content explicitely indicates a different license.++--++Copyright (c) 2012-2016, Threepenny-GUI authors (see CONTRIBUTORS)+Copyright (c) 2012, Jeremy Bowers <https://github.com/thejerf>+Copyright (c) 2011-2012, Chris Done <https://github.com/chrisdone> All rights reserved. Redistribution and use in source and binary forms, with or without
+ README.md view
@@ -0,0 +1,65 @@+[](https://ci.appveyor.com/project/HeinrichApfelmus/threepenny-gui)+[](https://hackage.haskell.org/package/threepenny-gui)+[](http://stackage.org/lts/package/threepenny-gui)+[](http://stackage.org/nightly/package/threepenny-gui)++# Threepenny-GUI++### What's this?++Threepenny is a GUI framework written in Haskell that uses the web browser as a display. It's very easy to install. See the++ [**Project homepage**](https://heinrichapfelmus.github.io/threepenny-gui/)++for more information on what it does and can do for you as a library user.++### Examples++The library comes with many examples, which can be found in the [samples](samples#readme) folder. Follow the link for more information on how to run them.++### Desktop Apps++Threepenny is mainly intended for writing GUI applications that run on the local network, and it relies on a web browser being installed. You can drop the latter requirement and integrate it a bit more tightly with you desktop environment by using the Electron framework. There is no fully automated support for this yet, but the documentation includes a [tutorial on how to use Electron with Threepenny](doc/electron.md).++# Technical overview++### JavaScript FFI++A program written with Threepenny is essentially a small web server that displays the user interface as a web page to any browser that connects to it.++The web server displays a HTML page, which in turn establishes WebSocket connection with the server. The server uses this connection to send JavaScript code that is executed in the client. In the library, this appears as a JavaScript Foreign Function Interface (FFI). The documentation includes [more information on the design of the JavaScript FFI](doc/javascript-ffi.md).++### Latency++The frequent communication between browser and server+means that Threepenny is best used as a GUI server running on localhost. You can use it on your local network as well.++If you want to reduce latency, the best option is to generate larger blocks of JavaScript+code and run them on the client. Consider this approach similar to [a+shading language](http://en.wikipedia.org/wiki/Shading_language).+You can import any JavaScript library and use it from the JavaScript FFI.++If you don't want to write JavaScript, then you could choose a Haskell-like language like [PureScript](http://www.purescript.org), [Fay](https://github.com/faylang/fay/wiki). You can also directly compile Haskell to JavaScript with [Haste](https://github.com/valderman/haste-compiler) or [GHCJS](https://github.com/ghcjs/ghcjs).++# Future ideas++### HTML rendering mode++It might be nice in the case of search engines to merely generate a DOM and render it, so that search engines can read the pages.++### UI libraries++[qooxdoo](http://qooxdoo.org/demo) — provides a feature-complete widget set. One could wrap this in a type-safe API from Threepenny and get a complete, stable UI framework for free. Most of the "immediate feedback" like dragging things here, switching tabs there, are taken care of by the framework. All that would be left would be to provide the domain configuration and business/presentation logic.++There are plenty more like this, but this is the first that springs to+mind that is good.++# Contributors++Many thanks to everyone who contributed, provided feedback or simply wrote an application using Threepenny! In particular, many thanks to:++Heinrich Apfelmus, Daniel Austin, Jeremy Barisch-Rooney, Steve Bigham, Simon Jakobi, Ken Friis Larsen, Daniel Mlot, Tim C. Schröder [*and many others*](CONTRIBUTORS)++Special thanks to *Simon Jakobi* for co-maintaining this project.++Special thanks to *Chris Done* for starting the precursor project Ji.
+ js/comm.js view
@@ -0,0 +1,163 @@+/* *********************************************************************+ Client-server communication+ using WebSockets+ + The function createWebSocket(receive) creates a client-server+ connection. The argument function `receive` is called whenever the client+ receives a message. The connection object has a single member `send`+ which can be used to send messages to the server.+ + In Haskell types:+ + createWebSocket :: (JSON -> IO ()) -> IO { send :: JSON -> IO () }+ +********************************************************************* */+Haskell.createWebSocket = function (url0, receive) {+ var that = {};+ var optReloadOnDisconnect = false;+ var url = new URL(url0);+ if (url.protocol === 'http:') { url.protocol = 'ws:' }+ if (url.protocol === 'https:') { url.protocol = 'wss:' }+ url.href = url.href + 'websocket/';+ var ws = new WebSocket(url);+ + // Close WebSocket when the browser window is closed.+ $(window).on('unload', function () {+ ws.close();+ });++ // Send ping message in regular intervals.+ // We expect pong messages in return to keep the connection alive.+ var ping = function () {+ // Only send a ping when it has a chance to reach the server.+ if (ws.readyState !== WebSocket.CLOSING && ws.readyState !== WebSocket.CLOSED) {+ ws.send("ping");+ window.setTimeout(ping,2000);+ }+ };+ + // Start communication when the WebSocket is opened.+ ws.onopen = function (e) {+ ping();+ ws.onmessage = function (msg) {+ // Haskell.log("WebSocket message: %o",msg);+ if (msg.data !== "pong") {+ receive(JSON.parse(msg.data));+ }+ };+ ws.onclose = function (e) {+ Haskell.log("WebSocket closed: %o", e);+ if (optReloadOnDisconnect) { window.location.reload(true); }+ };+ ws.onerror = function (e) {+ Haskell.log("WebSocket error: %o", e);+ };+ };+ + // Send a JSON message to the server+ that.send = function (json) { ws.send(JSON.stringify(json)); };+ // Close the connection+ that.close = function () { ws.send("quit"); };+ // Set option: Reload window when the websocket connection is broken?+ that.setReloadOnDisconnect = function (b) { optReloadOnDisconnect = b };+ + return that;+};+++/* *********************************************************************+ Client-server communication+ using HTTP+ + WARNING: The following code is untested legacy code.+ It's only there because there may be a chance we want to resurrect it.+ +********************************************************************* */++/* *********************************************************************+Threepenny.createHTTP = function (receive) {+ var that;+ var signal_count = 0;+ var sessionToken = {};+ + window.setTimeout(function () {+ waitForEvents();+ });+ + // Poll instruction from the server.+ var waitForEvents = function () {+ console.log("Polling… (%d signals so far)",signal_count);+ var data = { token: sessionToken };+ var cmd = sessionToken != null? 'poll' : 'init';+ if (cmd === 'init') {+ data.info = window.location.href;+ }+ var req = $.ajax({+ dataType: 'json',+ url : cmd,+ data: data,+ success: function (events) {+ if (sessionToken == null) {+ sessionToken = req.getResponseHeader('Set-Token').match(/[0-9]+/)*1;+ }+ console.log("Running event" +(events.length>1?'s':'') +"…")+ if (events.length) {+ console.log('Event list:');+ runMultipleEvents(events);+ } else {+ runEvent(events, signalEvent, function(response){+ maybeReply(response, waitForEvents);+ });+ }+ },+ error: function(reply) {+ console.log("Error, waiting...");+ window.setTimeout(function(){+ waitForEvents();+ },5000);+ }+ });+ }++ var runMultipleEvents = function (events){+ if(events.length == 0) {+ return waitForEvents();+ }+ runEvent(events.shift(), signalEvent, function(response){+ maybeReply(response, function(){+ runMultipleEvents(events);+ });+ });+ }+ + // Send an event to the server.+ var signalEvent = function (value) {+ signal({ Event: value}, function (){});+ }+ + // Send a reply to the server if necessary.+ var maybeReply = function (response, continuation) {+ if (response != undefined) { signal(response, continuation); }+ else { continuation(); }+ }+ + // Send response back to the server.+ var signal = function signal(signal,continuation) {+ signal_count++;+ console.log('Signal: %s',JSON.stringify(signal));+ $.ajax({+ dataType: 'json',+ url:'signal',+ data: { token: sessionToken, signal: JSON.stringify(signal) },+ success: function(){+ continuation();+ },+ error: function(reply){+ console.log("Error: %o",reply);+ }+ });+ }+ + return that;+};+********************************************************************* */
+ js/ffi.js view
@@ -0,0 +1,135 @@+/* *********************************************************************+ Foreign Function Interface (FFI)+ JavaScript <-> Haskell++ This module implements everything necessary to call JS functions+ from Haskell and vice versa.++ * Listen to server and call JS functions on request.+ * Present Haskell functions as objects that can be called from JS.+ * StablePtr for JS objects.++********************************************************************* */++// Connect to the Haskell server and initialize the FFI.+// An optional string argument can be used to specify the server URL.+Haskell.initFFI = function () {+ var connection;+ var url = window.location.origin.toString();++ if (arguments[0]) {+ url = arguments[0]; // take server url from argument+ }++ /////////////////////////////////////////////////////////////////////+ // Listen to server and execute JS functions+ var reply = function (response) {+ if (response !== undefined) {+ connection.send(response);+ }+ };++ var receive = function (msg) {+ Haskell.log("Server message: %o", msg);++ switch (msg.tag) {++ case "RunEval":+ try {+ eval(msg.contents);+ reply();+ } catch (err) {+ connection.close();+ throw(err);+ }+ break;++ case "CallEval":+ try {+ var result = eval(msg.contents);+ reply({ tag : "Result" , contents : result });+ } catch (err) {+ reply({ tag : "Exception", contents : err.toString() });+ }+ break;++ case "Debug":+ Haskell.log("Server debug: %o", msg.contents);+ reply();+ break;++ case "Timestamp":+ Haskell.log("Timestamp: %f ms", Haskell.performance.now());+ Haskell.log("Elapsed since last timestamp: %f ms",+ Haskell.performance.diff());+ reply();+ break;+ }+ };++ // Initialize connection to server.+ connection = Haskell.createWebSocket(url, receive);++ /////////////////////////////////////////////////////////////////////+ // Calling Haskell functions++ // An event is a function on the server side that can be called anytime,+ // but whose execution will be queued.+ Haskell.newEvent = function (name, args) {+ var that = function () {+ var theargs = [];+ if (args) {+ theargs = eval(args);+ } else {+ for (var i=0; i<arguments.length; i++) {+ theargs[i] = arguments[i];+ }+ }++ reply({+ tag : "Event",+ name : name,+ arguments : theargs+ });+ // 'args' is a string that contains the name 'arguments'+ // Evaluating it will perform appropriate marshalling.+ };+ return that;+ };++ /////////////////////////////////////////////////////////////////////+ // Stable Pointers on JavaScript objects+ //+ // Warning: We assume that each object can have at most one StablePtr+ // associated to it. We have to pay attention that we don't+ // free a stable pointer twice.+ var stablePtrs = {};+ var counter = 0;++ var newStablePtr = function (object) {+ if (object.stablePtr === undefined) {+ object.stablePtr = 'js-' + counter.toString();+ stablePtrs[object.stablePtr] = object;+ counter++;+ }+ return object.stablePtr.toString();+ };++ Haskell.imposeStablePtr = function (object, ptr) {+ object.stablePtr = ptr;+ stablePtrs[object.stablePtr] = object;+ };++ Haskell.getStablePtr = function (object) {+ return (object.stablePtr || newStablePtr(object));+ };++ Haskell.deRefStablePtr = function (ptr) {+ return stablePtrs[ptr];+ };++ Haskell.freeStablePtr = function (ptr) {+ delete stablePtrs[ptr].stablePtr;+ delete stablePtrs[ptr];+ };+};
+ js/haskell.css view
@@ -0,0 +1,3 @@+.table { display: table; }+.table-row { display: table-row; }+.table-cell { display: table-cell; }
+ js/index.html view
@@ -0,0 +1,21 @@+<!doctype html>+<head>+ <meta charset="UTF-8">+ <meta http-equiv="X-UA-Compatible" content="IE=edge"/>++ <title></title>+ <link rel="stylesheet" type="text/css" href="haskell.css"/>++ <!-- See https://stackoverflow.com/a/37480521 -->+ <script>if (typeof module === 'object') {window.module = module; module = undefined;}</script>+ <script src="haskell.js"></script>+ <script>if (window.module) module = window.module;</script>++ <script type="text/javascript" charset="utf-8">+ Haskell.initFFI();+ </script>+</head>+<body>+ <noscript>Please enable JavaScript.</noscript>+</body>+</html>
+ js/lib.js view
@@ -0,0 +1,111 @@+/* *********************************************************************+ Threepenny - JavaScript library++ Additional JavaScript functions that are available+ for Threepenny.+********************************************************************* */+window.jquery_scrollToBottom = function(el){+ $(el).scrollTop(el.scrollHeight);+};++Haskell.map = function (fun, array) {+ var result = [];+ for (i=0; i<array.length; i++) {+ result[i] = fun(array[i]);+ }+ return result;+};++/////////////////////////////////////////////////////////////////////+// Attach an event handler for a particular event type to an element+Haskell.on = function (el, eventType, fun) {+ if(eventType === 'livechange') {+ $(el).livechange(300,function(e){+ fun([ $(el).val() ]);+ return true;+ });+ } else if(eventType === 'sendvalue') {+ $(el).sendvalue(function(x){+ fun(x.toString());+ });+ } else if (eventType.match('dragstart|dragenter|dragover|dragleave|drag|drop|dragend')) {+ $(el).on(eventType, function(e) {+ fun( e.originalEvent.dataTransfer+ ? [e.originalEvent.dataTransfer.getData("dragData")]+ : [] );+ });+ } else if(eventType.match('contextmenu|mousemove|mousedown|mouseup')) {+ $(el).on(eventType, function(e) {+ var offset = $(this).offset();+ var x = e.pageX - offset.left;+ var y = e.pageY - offset.top;+ fun([x, y]);+ });+ } else if(eventType.match('keydown|keyup')) {+ $(el).on(eventType, function(e) {+ fun(e.keyCode);+ return true;+ });+ } else {+ $(el).on(eventType, function(e) {+ fun(e.which ? [e.which.toString()] : e.detail || []);+ return true;+ });+ }+};+++/////////////////////////////////////////////////////////////////////+// Canvas API additions.+// See http://stackoverflow.com/a/9722502/403805 .+CanvasRenderingContext2D.prototype.clear =+ CanvasRenderingContext2D.prototype.clear || function (preserveTransform) {+ if (preserveTransform) {+ this.save();+ this.setTransform(1, 0, 0, 1, 0, 0);+ }+ this.clearRect(0, 0, this.canvas.width, this.canvas.height);+ if (preserveTransform) {+ this.restore();+ }+};+++// Sendvalue+$.fn.sendvalue = function(trigger){+ $(this).each(function(){+ var self = this;+ var el = $(self);+ el.keydown(function(e){+ if(e.which == 13) {+ trigger.call(self,el.val());+ return false;+ }+ else+ return true;+ });+ });+};++// Livechange+$.fn.livechange = function(ms,trigger){+ $(this).each(function(){+ var self = this;+ var el = $(self);+ var last_val;+ var check = function(){+ var val = el.val();+ if(val != last_val)+ trigger.call(self);+ last_val = val;+ };+ var checker;+ var restart = function(){+ clearTimeout(checker);+ checker = setInterval(check,ms);+ };+ restart();+ el.keypress(restart).change(restart);+ });+};+
+ js/lib/jquery-cookie.js view
@@ -0,0 +1,56 @@+/**+ * jQuery Cookie plugin+ *+ * Copyright (c) 2010 Klaus Hartl (stilbuero.de)+ * Dual licensed under the MIT and GPL licenses:+ * http://www.opensource.org/licenses/mit-license.php+ * http://www.gnu.org/licenses/gpl.html+ *+ */+(function($) {+ $.cookie = function(key, value, options) {++ // key and at least value given, set cookie...+ if (arguments.length > 1 && (!/Object/.test(Object.prototype.toString.call(value)) || value === null || value === undefined)) {+ options = $.extend({}, options);++ if (value === null || value === undefined) {+ options.expires = -1;+ }++ if (typeof options.expires === 'number') {+ var days = options.expires, t = options.expires = new Date();+ t.setDate(t.getDate() + days);+ }++ value = String(value);++ return (document.cookie = [+ encodeURIComponent(key), '=', options.raw ? value : encodeURIComponent(value),+ options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE+ options.path ? '; path=' + options.path : '',+ options.domain ? '; domain=' + options.domain : '',+ options.secure ? '; secure' : ''+ ].join(''));+ }++ // key and possibly options given, get cookie...+ options = value || {};+ var decode = options.raw ? function(s) { return s; } : decodeURIComponent;++ var pairs = document.cookie.split('; ');+ for (var i = 0, pair; pair = pairs[i] && pairs[i].split('='); i++) {+ if (decode(pair[0]) === key) return decode(pair[1] || ''); // IE saves cookies with empty string as "c; ", e.g. without "=" as opposed to EOMB, thus pair[1] may be undefined+ }+ return null;+ };++ $.setSimpleCookie = function(name,value,f) {+ $.cookie(name,value,{+ path: '/',+ expire: 999 // TODO: + });+ f();+ };+ +})(jQuery);
+ js/lib/jquery.js view
@@ -0,0 +1,4 @@+/*! jQuery v3.2.1 | (c) JS Foundation and other contributors | jquery.org/license */+!function(a,b){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){"use strict";var c=[],d=a.document,e=Object.getPrototypeOf,f=c.slice,g=c.concat,h=c.push,i=c.indexOf,j={},k=j.toString,l=j.hasOwnProperty,m=l.toString,n=m.call(Object),o={};function p(a,b){b=b||d;var c=b.createElement("script");c.text=a,b.head.appendChild(c).parentNode.removeChild(c)}var q="3.2.1",r=function(a,b){return new r.fn.init(a,b)},s=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,t=/^-ms-/,u=/-([a-z])/g,v=function(a,b){return b.toUpperCase()};r.fn=r.prototype={jquery:q,constructor:r,length:0,toArray:function(){return f.call(this)},get:function(a){return null==a?f.call(this):a<0?this[a+this.length]:this[a]},pushStack:function(a){var b=r.merge(this.constructor(),a);return b.prevObject=this,b},each:function(a){return r.each(this,a)},map:function(a){return this.pushStack(r.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(f.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(a<0?b:0);return this.pushStack(c>=0&&c<b?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:h,sort:c.sort,splice:c.splice},r.extend=r.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||r.isFunction(g)||(g={}),h===i&&(g=this,h--);h<i;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(r.isPlainObject(d)||(e=Array.isArray(d)))?(e?(e=!1,f=c&&Array.isArray(c)?c:[]):f=c&&r.isPlainObject(c)?c:{},g[b]=r.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},r.extend({expando:"jQuery"+(q+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===r.type(a)},isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){var b=r.type(a);return("number"===b||"string"===b)&&!isNaN(a-parseFloat(a))},isPlainObject:function(a){var b,c;return!(!a||"[object Object]"!==k.call(a))&&(!(b=e(a))||(c=l.call(b,"constructor")&&b.constructor,"function"==typeof c&&m.call(c)===n))},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?j[k.call(a)]||"object":typeof a},globalEval:function(a){p(a)},camelCase:function(a){return a.replace(t,"ms-").replace(u,v)},each:function(a,b){var c,d=0;if(w(a)){for(c=a.length;d<c;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(s,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(w(Object(a))?r.merge(c,"string"==typeof a?[a]:a):h.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:i.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;d<c;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;f<g;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,f=0,h=[];if(w(a))for(d=a.length;f<d;f++)e=b(a[f],f,c),null!=e&&h.push(e);else for(f in a)e=b(a[f],f,c),null!=e&&h.push(e);return g.apply([],h)},guid:1,proxy:function(a,b){var c,d,e;if("string"==typeof b&&(c=a[b],b=a,a=c),r.isFunction(a))return d=f.call(arguments,2),e=function(){return a.apply(b||this,d.concat(f.call(arguments)))},e.guid=a.guid=a.guid||r.guid++,e},now:Date.now,support:o}),"function"==typeof Symbol&&(r.fn[Symbol.iterator]=c[Symbol.iterator]),r.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){j["[object "+b+"]"]=b.toLowerCase()});function w(a){var b=!!a&&"length"in a&&a.length,c=r.type(a);return"function"!==c&&!r.isWindow(a)&&("array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a)}var x=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=function(a,b){for(var c=0,d=a.length;c<d;c++)if(a[c]===b)return c;return-1},J="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",K="[\\x20\\t\\r\\n\\f]",L="(?:\\\\.|[\\w-]|[^\0-\\xa0])+",M="\\["+K+"*("+L+")(?:"+K+"*([*^$|!~]?=)"+K+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+L+"))|)"+K+"*\\]",N=":("+L+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+M+")*)|.*)\\)|)",O=new RegExp(K+"+","g"),P=new RegExp("^"+K+"+|((?:^|[^\\\\])(?:\\\\.)*)"+K+"+$","g"),Q=new RegExp("^"+K+"*,"+K+"*"),R=new RegExp("^"+K+"*([>+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(N),U=new RegExp("^"+L+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+N),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),aa=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:d<0?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ba=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ca=function(a,b){return b?"\0"===a?"\ufffd":a.slice(0,-1)+"\\"+a.charCodeAt(a.length-1).toString(16)+" ":"\\"+a},da=function(){m()},ea=ta(function(a){return a.disabled===!0&&("form"in a||"label"in a)},{dir:"parentNode",next:"legend"});try{G.apply(D=H.call(v.childNodes),v.childNodes),D[v.childNodes.length].nodeType}catch(fa){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s=b&&b.ownerDocument,w=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==w&&9!==w&&11!==w)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==w&&(l=Z.exec(a)))if(f=l[1]){if(9===w){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(s&&(j=s.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(l[2])return G.apply(d,b.getElementsByTagName(a)),d;if((f=l[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==w)s=b,r=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(ba,ca):b.setAttribute("id",k=u),o=g(a),h=o.length;while(h--)o[h]="#"+k+" "+sa(o[h]);r=o.join(","),s=$.test(a)&&qa(b.parentNode)||b}if(r)try{return G.apply(d,s.querySelectorAll(r)),d}catch(x){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(P,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("fieldset");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&a.sourceIndex-b.sourceIndex;if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return function(b){return"form"in b?b.parentNode&&b.disabled===!1?"label"in b?"label"in b.parentNode?b.parentNode.disabled===a:b.disabled===a:b.isDisabled===a||b.isDisabled!==!a&&ea(b)===a:b.disabled===a:"label"in b&&b.disabled===a}}function pa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function qa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return!!b&&"HTML"!==b.nodeName},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),v!==n&&(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(n.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){return a.getAttribute("id")===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}}):(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c,d,e,f=b.getElementById(a);if(f){if(c=f.getAttributeNode("id"),c&&c.value===a)return[f];e=b.getElementsByName(a),d=0;while(f=e[d++])if(c=f.getAttributeNode("id"),c&&c.value===a)return[f]}return[]}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){if("undefined"!=typeof b.getElementsByClassName&&p)return b.getElementsByClassName(a)},r=[],q=[],(c.qsa=Y.test(n.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="<a id='"+u+"'></a><select id='"+u+"-\r\\' msallowcapture=''><option selected=''></option></select>",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){a.innerHTML="<a href='' disabled='disabled'></a><select disabled='disabled'><option/></select>";var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+K+"*[*^$|!~]?="),2!==a.querySelectorAll(":enabled").length&&q.push(":enabled",":disabled"),o.appendChild(a).disabled=!0,2!==a.querySelectorAll(":disabled").length&&q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Y.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"*"),s.call(a,"[s!='']:x"),r.push("!=",N)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Y.test(o.compareDocumentPosition),t=b||Y.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?I(k,a)-I(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?I(k,a)-I(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?la(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(S,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.escape=function(a){return(a+"").replace(ba,ca)},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(_,aa),a[3]=(a[3]||a[4]||a[5]||"").replace(_,aa),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return V.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&T.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(_,aa).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:!b||(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(O," ")+" ").indexOf(c)>-1:"|="===b&&(e===c||e.slice(0,c.length+1)===c+"-"))}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(P,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(_,aa),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return U.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(_,aa).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:oa(!1),disabled:oa(!0),checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:pa(function(){return[0]}),last:pa(function(a,b){return[b-1]}),eq:pa(function(a,b,c){return[c<0?c+b:c]}),even:pa(function(a,b){for(var c=0;c<b;c+=2)a.push(c);return a}),odd:pa(function(a,b){for(var c=1;c<b;c+=2)a.push(c);return a}),lt:pa(function(a,b,c){for(var d=c<0?c+b:c;--d>=0;)a.push(d);return a}),gt:pa(function(a,b,c){for(var d=c<0?c+b:c;++d<b;)a.push(d);return a})}},d.pseudos.nth=d.pseudos.eq;for(b in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})d.pseudos[b]=ma(b);for(b in{submit:!0,reset:!0})d.pseudos[b]=na(b);function ra(){}ra.prototype=d.filters=d.pseudos,d.setFilters=new ra,g=ga.tokenize=function(a,b){var c,e,f,g,h,i,j,k=z[a+" "];if(k)return b?0:k.slice(0);h=a,i=[],j=d.preFilter;while(h){c&&!(e=Q.exec(h))||(e&&(h=h.slice(e[0].length)||h),i.push(f=[])),c=!1,(e=R.exec(h))&&(c=e.shift(),f.push({value:c,type:e[0].replace(P," ")}),h=h.slice(c.length));for(g in d.filter)!(e=V[g].exec(h))||j[g]&&!(e=j[g](e))||(c=e.shift(),f.push({value:c,type:g,matches:e}),h=h.slice(c.length));if(!c)break}return b?h.length:h?ga.error(a):z(a,i).slice(0)};function sa(a){for(var b=0,c=a.length,d="";b<c;b++)d+=a[b].value;return d}function ta(a,b,c){var d=b.dir,e=b.next,f=e||d,g=c&&"parentNode"===f,h=x++;return b.first?function(b,c,e){while(b=b[d])if(1===b.nodeType||g)return a(b,c,e);return!1}:function(b,c,i){var j,k,l,m=[w,h];if(i){while(b=b[d])if((1===b.nodeType||g)&&a(b,c,i))return!0}else while(b=b[d])if(1===b.nodeType||g)if(l=b[u]||(b[u]={}),k=l[b.uniqueID]||(l[b.uniqueID]={}),e&&e===b.nodeName.toLowerCase())b=b[d]||b;else{if((j=k[f])&&j[0]===w&&j[1]===h)return m[2]=j[2];if(k[f]=m,m[2]=a(b,c,i))return!0}return!1}}function ua(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function va(a,b,c){for(var d=0,e=b.length;d<e;d++)ga(a,b[d],c);return c}function wa(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;h<i;h++)(f=a[h])&&(c&&!c(f,d,e)||(g.push(f),j&&b.push(h)));return g}function xa(a,b,c,d,e,f){return d&&!d[u]&&(d=xa(d)),e&&!e[u]&&(e=xa(e,f)),ia(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||va(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:wa(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=wa(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?I(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=wa(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ya(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ta(function(a){return a===b},h,!0),l=ta(function(a){return I(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];i<f;i++)if(c=d.relative[a[i].type])m=[ta(ua(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;e<f;e++)if(d.relative[a[e].type])break;return xa(i>1&&ua(m),i>1&&sa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(P,"$1"),c,i<e&&ya(a.slice(i,e)),e<f&&ya(a=a.slice(e)),e<f&&sa(a))}m.push(c)}return ua(m)}function za(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=E.call(i));u=wa(u)}G.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&ga.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=ya(b[c]),f[u]?d.push(f):e.push(f);f=A(a,za(e,d)),f.selector=a}return f},i=ga.select=function(a,b,c,e){var f,i,j,k,l,m="function"==typeof a&&a,n=!e&&g(a=m.selector||a);if(c=c||[],1===n.length){if(i=n[0]=n[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&9===b.nodeType&&p&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(_,aa),b)||[])[0],!b)return c;m&&(b=b.parentNode),a=a.slice(i.shift().value.length)}f=V.needsContext.test(a)?0:i.length;while(f--){if(j=i[f],d.relative[k=j.type])break;if((l=d.find[k])&&(e=l(j.matches[0].replace(_,aa),$.test(i[0].type)&&qa(b.parentNode)||b))){if(i.splice(f,1),a=e.length&&sa(i),!a)return G.apply(c,e),c;break}}}return(m||h(a,n))(e,b,!p,c,!b||$.test(a)&&qa(b.parentNode)||b),c},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("fieldset"))}),ja(function(a){return a.innerHTML="<a href='#'></a>","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){if(!c)return a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="<input/>",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){if(!c&&"input"===a.nodeName.toLowerCase())return a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(J,function(a,b,c){var d;if(!c)return a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);r.find=x,r.expr=x.selectors,r.expr[":"]=r.expr.pseudos,r.uniqueSort=r.unique=x.uniqueSort,r.text=x.getText,r.isXMLDoc=x.isXML,r.contains=x.contains,r.escapeSelector=x.escape;var y=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&r(a).is(c))break;d.push(a)}return d},z=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},A=r.expr.match.needsContext;function B(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()}var C=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i,D=/^.[^:#\[\.,]*$/;function E(a,b,c){return r.isFunction(b)?r.grep(a,function(a,d){return!!b.call(a,d,a)!==c}):b.nodeType?r.grep(a,function(a){return a===b!==c}):"string"!=typeof b?r.grep(a,function(a){return i.call(b,a)>-1!==c}):D.test(b)?r.filter(b,a,c):(b=r.filter(b,a),r.grep(a,function(a){return i.call(b,a)>-1!==c&&1===a.nodeType}))}r.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?r.find.matchesSelector(d,a)?[d]:[]:r.find.matches(a,r.grep(b,function(a){return 1===a.nodeType}))},r.fn.extend({find:function(a){var b,c,d=this.length,e=this;if("string"!=typeof a)return this.pushStack(r(a).filter(function(){for(b=0;b<d;b++)if(r.contains(e[b],this))return!0}));for(c=this.pushStack([]),b=0;b<d;b++)r.find(a,e[b],c);return d>1?r.uniqueSort(c):c},filter:function(a){return this.pushStack(E(this,a||[],!1))},not:function(a){return this.pushStack(E(this,a||[],!0))},is:function(a){return!!E(this,"string"==typeof a&&A.test(a)?r(a):a||[],!1).length}});var F,G=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,H=r.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||F,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:G.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof r?b[0]:b,r.merge(this,r.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),C.test(e[1])&&r.isPlainObject(b))for(e in b)r.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&(this[0]=f,this.length=1),this}return a.nodeType?(this[0]=a,this.length=1,this):r.isFunction(a)?void 0!==c.ready?c.ready(a):a(r):r.makeArray(a,this)};H.prototype=r.fn,F=r(d);var I=/^(?:parents|prev(?:Until|All))/,J={children:!0,contents:!0,next:!0,prev:!0};r.fn.extend({has:function(a){var b=r(a,this),c=b.length;return this.filter(function(){for(var a=0;a<c;a++)if(r.contains(this,b[a]))return!0})},closest:function(a,b){var c,d=0,e=this.length,f=[],g="string"!=typeof a&&r(a);if(!A.test(a))for(;d<e;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&r.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?r.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?i.call(r(a),this[0]):i.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(r.uniqueSort(r.merge(this.get(),r(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function K(a,b){while((a=a[b])&&1!==a.nodeType);return a}r.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return y(a,"parentNode")},parentsUntil:function(a,b,c){return y(a,"parentNode",c)},next:function(a){return K(a,"nextSibling")},prev:function(a){return K(a,"previousSibling")},nextAll:function(a){return y(a,"nextSibling")},prevAll:function(a){return y(a,"previousSibling")},nextUntil:function(a,b,c){return y(a,"nextSibling",c)},prevUntil:function(a,b,c){return y(a,"previousSibling",c)},siblings:function(a){return z((a.parentNode||{}).firstChild,a)},children:function(a){return z(a.firstChild)},contents:function(a){return B(a,"iframe")?a.contentDocument:(B(a,"template")&&(a=a.content||a),r.merge([],a.childNodes))}},function(a,b){r.fn[a]=function(c,d){var e=r.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=r.filter(d,e)),this.length>1&&(J[a]||r.uniqueSort(e),I.test(a)&&e.reverse()),this.pushStack(e)}});var L=/[^\x20\t\r\n\f]+/g;function M(a){var b={};return r.each(a.match(L)||[],function(a,c){b[c]=!0}),b}r.Callbacks=function(a){a="string"==typeof a?M(a):r.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=e||a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h<f.length)f[h].apply(c[0],c[1])===!1&&a.stopOnFalse&&(h=f.length,c=!1)}a.memory||(c=!1),b=!1,e&&(f=c?[]:"")},j={add:function(){return f&&(c&&!b&&(h=f.length-1,g.push(c)),function d(b){r.each(b,function(b,c){r.isFunction(c)?a.unique&&j.has(c)||f.push(c):c&&c.length&&"string"!==r.type(c)&&d(c)})}(arguments),c&&!b&&i()),this},remove:function(){return r.each(arguments,function(a,b){var c;while((c=r.inArray(b,f,c))>-1)f.splice(c,1),c<=h&&h--}),this},has:function(a){return a?r.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||b||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j};function N(a){return a}function O(a){throw a}function P(a,b,c,d){var e;try{a&&r.isFunction(e=a.promise)?e.call(a).done(b).fail(c):a&&r.isFunction(e=a.then)?e.call(a,b,c):b.apply(void 0,[a].slice(d))}catch(a){c.apply(void 0,[a])}}r.extend({Deferred:function(b){var c=[["notify","progress",r.Callbacks("memory"),r.Callbacks("memory"),2],["resolve","done",r.Callbacks("once memory"),r.Callbacks("once memory"),0,"resolved"],["reject","fail",r.Callbacks("once memory"),r.Callbacks("once memory"),1,"rejected"]],d="pending",e={state:function(){return d},always:function(){return f.done(arguments).fail(arguments),this},"catch":function(a){return e.then(null,a)},pipe:function(){var a=arguments;return r.Deferred(function(b){r.each(c,function(c,d){var e=r.isFunction(a[d[4]])&&a[d[4]];f[d[1]](function(){var a=e&&e.apply(this,arguments);a&&r.isFunction(a.promise)?a.promise().progress(b.notify).done(b.resolve).fail(b.reject):b[d[0]+"With"](this,e?[a]:arguments)})}),a=null}).promise()},then:function(b,d,e){var f=0;function g(b,c,d,e){return function(){var h=this,i=arguments,j=function(){var a,j;if(!(b<f)){if(a=d.apply(h,i),a===c.promise())throw new TypeError("Thenable self-resolution");j=a&&("object"==typeof a||"function"==typeof a)&&a.then,r.isFunction(j)?e?j.call(a,g(f,c,N,e),g(f,c,O,e)):(f++,j.call(a,g(f,c,N,e),g(f,c,O,e),g(f,c,N,c.notifyWith))):(d!==N&&(h=void 0,i=[a]),(e||c.resolveWith)(h,i))}},k=e?j:function(){try{j()}catch(a){r.Deferred.exceptionHook&&r.Deferred.exceptionHook(a,k.stackTrace),b+1>=f&&(d!==O&&(h=void 0,i=[a]),c.rejectWith(h,i))}};b?k():(r.Deferred.getStackHook&&(k.stackTrace=r.Deferred.getStackHook()),a.setTimeout(k))}}return r.Deferred(function(a){c[0][3].add(g(0,a,r.isFunction(e)?e:N,a.notifyWith)),c[1][3].add(g(0,a,r.isFunction(b)?b:N)),c[2][3].add(g(0,a,r.isFunction(d)?d:O))}).promise()},promise:function(a){return null!=a?r.extend(a,e):e}},f={};return r.each(c,function(a,b){var g=b[2],h=b[5];e[b[1]]=g.add,h&&g.add(function(){d=h},c[3-a][2].disable,c[0][2].lock),g.add(b[3].fire),f[b[0]]=function(){return f[b[0]+"With"](this===f?void 0:this,arguments),this},f[b[0]+"With"]=g.fireWith}),e.promise(f),b&&b.call(f,f),f},when:function(a){var b=arguments.length,c=b,d=Array(c),e=f.call(arguments),g=r.Deferred(),h=function(a){return function(c){d[a]=this,e[a]=arguments.length>1?f.call(arguments):c,--b||g.resolveWith(d,e)}};if(b<=1&&(P(a,g.done(h(c)).resolve,g.reject,!b),"pending"===g.state()||r.isFunction(e[c]&&e[c].then)))return g.then();while(c--)P(e[c],h(c),g.reject);return g.promise()}});var Q=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;r.Deferred.exceptionHook=function(b,c){a.console&&a.console.warn&&b&&Q.test(b.name)&&a.console.warn("jQuery.Deferred exception: "+b.message,b.stack,c)},r.readyException=function(b){a.setTimeout(function(){throw b})};var R=r.Deferred();r.fn.ready=function(a){return R.then(a)["catch"](function(a){r.readyException(a)}),this},r.extend({isReady:!1,readyWait:1,ready:function(a){(a===!0?--r.readyWait:r.isReady)||(r.isReady=!0,a!==!0&&--r.readyWait>0||R.resolveWith(d,[r]))}}),r.ready.then=R.then;function S(){d.removeEventListener("DOMContentLoaded",S),+a.removeEventListener("load",S),r.ready()}"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(r.ready):(d.addEventListener("DOMContentLoaded",S),a.addEventListener("load",S));var T=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===r.type(c)){e=!0;for(h in c)T(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,r.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(r(a),c)})),b))for(;h<i;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},U=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function V(){this.expando=r.expando+V.uid++}V.uid=1,V.prototype={cache:function(a){var b=a[this.expando];return b||(b={},U(a)&&(a.nodeType?a[this.expando]=b:Object.defineProperty(a,this.expando,{value:b,configurable:!0}))),b},set:function(a,b,c){var d,e=this.cache(a);if("string"==typeof b)e[r.camelCase(b)]=c;else for(d in b)e[r.camelCase(d)]=b[d];return e},get:function(a,b){return void 0===b?this.cache(a):a[this.expando]&&a[this.expando][r.camelCase(b)]},access:function(a,b,c){return void 0===b||b&&"string"==typeof b&&void 0===c?this.get(a,b):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d=a[this.expando];if(void 0!==d){if(void 0!==b){Array.isArray(b)?b=b.map(r.camelCase):(b=r.camelCase(b),b=b in d?[b]:b.match(L)||[]),c=b.length;while(c--)delete d[b[c]]}(void 0===b||r.isEmptyObject(d))&&(a.nodeType?a[this.expando]=void 0:delete a[this.expando])}},hasData:function(a){var b=a[this.expando];return void 0!==b&&!r.isEmptyObject(b)}};var W=new V,X=new V,Y=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,Z=/[A-Z]/g;function $(a){return"true"===a||"false"!==a&&("null"===a?null:a===+a+""?+a:Y.test(a)?JSON.parse(a):a)}function _(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(Z,"-$&").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c=$(c)}catch(e){}X.set(a,b,c)}else c=void 0;return c}r.extend({hasData:function(a){return X.hasData(a)||W.hasData(a)},data:function(a,b,c){return X.access(a,b,c)},removeData:function(a,b){X.remove(a,b)},_data:function(a,b,c){return W.access(a,b,c)},_removeData:function(a,b){W.remove(a,b)}}),r.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=X.get(f),1===f.nodeType&&!W.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=r.camelCase(d.slice(5)),_(f,d,e[d])));W.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){X.set(this,a)}):T(this,function(b){var c;if(f&&void 0===b){if(c=X.get(f,a),void 0!==c)return c;if(c=_(f,a),void 0!==c)return c}else this.each(function(){X.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){X.remove(this,a)})}}),r.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=W.get(a,b),c&&(!d||Array.isArray(c)?d=W.access(a,b,r.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=r.queue(a,b),d=c.length,e=c.shift(),f=r._queueHooks(a,b),g=function(){r.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return W.get(a,c)||W.access(a,c,{empty:r.Callbacks("once memory").add(function(){W.remove(a,[b+"queue",c])})})}}),r.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?r.queue(this[0],a):void 0===b?this:this.each(function(){var c=r.queue(this,a,b);r._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&r.dequeue(this,a)})},dequeue:function(a){return this.each(function(){r.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=r.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=W.get(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var aa=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,ba=new RegExp("^(?:([+-])=|)("+aa+")([a-z%]*)$","i"),ca=["Top","Right","Bottom","Left"],da=function(a,b){return a=b||a,"none"===a.style.display||""===a.style.display&&r.contains(a.ownerDocument,a)&&"none"===r.css(a,"display")},ea=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e};function fa(a,b,c,d){var e,f=1,g=20,h=d?function(){return d.cur()}:function(){return r.css(a,b,"")},i=h(),j=c&&c[3]||(r.cssNumber[b]?"":"px"),k=(r.cssNumber[b]||"px"!==j&&+i)&&ba.exec(r.css(a,b));if(k&&k[3]!==j){j=j||k[3],c=c||[],k=+i||1;do f=f||".5",k/=f,r.style(a,b,k+j);while(f!==(f=h()/i)&&1!==f&&--g)}return c&&(k=+k||+i||0,e=c[1]?k+(c[1]+1)*c[2]:+c[2],d&&(d.unit=j,d.start=k,d.end=e)),e}var ga={};function ha(a){var b,c=a.ownerDocument,d=a.nodeName,e=ga[d];return e?e:(b=c.body.appendChild(c.createElement(d)),e=r.css(b,"display"),b.parentNode.removeChild(b),"none"===e&&(e="block"),ga[d]=e,e)}function ia(a,b){for(var c,d,e=[],f=0,g=a.length;f<g;f++)d=a[f],d.style&&(c=d.style.display,b?("none"===c&&(e[f]=W.get(d,"display")||null,e[f]||(d.style.display="")),""===d.style.display&&da(d)&&(e[f]=ha(d))):"none"!==c&&(e[f]="none",W.set(d,"display",c)));for(f=0;f<g;f++)null!=e[f]&&(a[f].style.display=e[f]);return a}r.fn.extend({show:function(){return ia(this,!0)},hide:function(){return ia(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){da(this)?r(this).show():r(this).hide()})}});var ja=/^(?:checkbox|radio)$/i,ka=/<([a-z][^\/\0>\x20\t\r\n\f]+)/i,la=/^$|\/(?:java|ecma)script/i,ma={option:[1,"<select multiple='multiple'>","</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};ma.optgroup=ma.option,ma.tbody=ma.tfoot=ma.colgroup=ma.caption=ma.thead,ma.th=ma.td;function na(a,b){var c;return c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[],void 0===b||b&&B(a,b)?r.merge([a],c):c}function oa(a,b){for(var c=0,d=a.length;c<d;c++)W.set(a[c],"globalEval",!b||W.get(b[c],"globalEval"))}var pa=/<|&#?\w+;/;function qa(a,b,c,d,e){for(var f,g,h,i,j,k,l=b.createDocumentFragment(),m=[],n=0,o=a.length;n<o;n++)if(f=a[n],f||0===f)if("object"===r.type(f))r.merge(m,f.nodeType?[f]:f);else if(pa.test(f)){g=g||l.appendChild(b.createElement("div")),h=(ka.exec(f)||["",""])[1].toLowerCase(),i=ma[h]||ma._default,g.innerHTML=i[1]+r.htmlPrefilter(f)+i[2],k=i[0];while(k--)g=g.lastChild;r.merge(m,g.childNodes),g=l.firstChild,g.textContent=""}else m.push(b.createTextNode(f));l.textContent="",n=0;while(f=m[n++])if(d&&r.inArray(f,d)>-1)e&&e.push(f);else if(j=r.contains(f.ownerDocument,f),g=na(l.appendChild(f),"script"),j&&oa(g),c){k=0;while(f=g[k++])la.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),o.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="<textarea>x</textarea>",o.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var ra=d.documentElement,sa=/^key/,ta=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,ua=/^([^.]*)(?:\.(.+)|)/;function va(){return!0}function wa(){return!1}function xa(){try{return d.activeElement}catch(a){}}function ya(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)ya(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=wa;else if(!e)return a;return 1===f&&(g=e,e=function(a){return r().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=r.guid++)),a.each(function(){r.event.add(this,b,e,d,c)})}r.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=W.get(a);if(q){c.handler&&(f=c,c=f.handler,e=f.selector),e&&r.find.matchesSelector(ra,e),c.guid||(c.guid=r.guid++),(i=q.events)||(i=q.events={}),(g=q.handle)||(g=q.handle=function(b){return"undefined"!=typeof r&&r.event.triggered!==b.type?r.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(L)||[""],j=b.length;while(j--)h=ua.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n&&(l=r.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=r.event.special[n]||{},k=r.extend({type:n,origType:p,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&r.expr.match.needsContext.test(e),namespace:o.join(".")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,o,g)!==!1||a.addEventListener&&a.addEventListener(n,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),r.event.global[n]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=W.hasData(a)&&W.get(a);if(q&&(i=q.events)){b=(b||"").match(L)||[""],j=b.length;while(j--)if(h=ua.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n){l=r.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp("(^|\\.)"+o.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&p!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,o,q.handle)!==!1||r.removeEvent(a,n,q.handle),delete i[n])}else for(n in i)r.event.remove(a,n+b[j],c,d,!0);r.isEmptyObject(i)&&W.remove(a,"handle events")}},dispatch:function(a){var b=r.event.fix(a),c,d,e,f,g,h,i=new Array(arguments.length),j=(W.get(this,"events")||{})[b.type]||[],k=r.event.special[b.type]||{};for(i[0]=b,c=1;c<arguments.length;c++)i[c]=arguments[c];if(b.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,b)!==!1){h=r.event.handlers.call(this,b,j),c=0;while((f=h[c++])&&!b.isPropagationStopped()){b.currentTarget=f.elem,d=0;while((g=f.handlers[d++])&&!b.isImmediatePropagationStopped())b.rnamespace&&!b.rnamespace.test(g.namespace)||(b.handleObj=g,b.data=g.data,e=((r.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(b.result=e)===!1&&(b.preventDefault(),b.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,b),b.result}},handlers:function(a,b){var c,d,e,f,g,h=[],i=b.delegateCount,j=a.target;if(i&&j.nodeType&&!("click"===a.type&&a.button>=1))for(;j!==this;j=j.parentNode||this)if(1===j.nodeType&&("click"!==a.type||j.disabled!==!0)){for(f=[],g={},c=0;c<i;c++)d=b[c],e=d.selector+" ",void 0===g[e]&&(g[e]=d.needsContext?r(e,this).index(j)>-1:r.find(e,this,null,[j]).length),g[e]&&f.push(d);f.length&&h.push({elem:j,handlers:f})}return j=this,i<b.length&&h.push({elem:j,handlers:b.slice(i)}),h},addProp:function(a,b){Object.defineProperty(r.Event.prototype,a,{enumerable:!0,configurable:!0,get:r.isFunction(b)?function(){if(this.originalEvent)return b(this.originalEvent)}:function(){if(this.originalEvent)return this.originalEvent[a]},set:function(b){Object.defineProperty(this,a,{enumerable:!0,configurable:!0,writable:!0,value:b})}})},fix:function(a){return a[r.expando]?a:new r.Event(a)},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==xa()&&this.focus)return this.focus(),!1},delegateType:"focusin"},blur:{trigger:function(){if(this===xa()&&this.blur)return this.blur(),!1},delegateType:"focusout"},click:{trigger:function(){if("checkbox"===this.type&&this.click&&B(this,"input"))return this.click(),!1},_default:function(a){return B(a.target,"a")}},beforeunload:{postDispatch:function(a){void 0!==a.result&&a.originalEvent&&(a.originalEvent.returnValue=a.result)}}}},r.removeEvent=function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c)},r.Event=function(a,b){return this instanceof r.Event?(a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||void 0===a.defaultPrevented&&a.returnValue===!1?va:wa,this.target=a.target&&3===a.target.nodeType?a.target.parentNode:a.target,this.currentTarget=a.currentTarget,this.relatedTarget=a.relatedTarget):this.type=a,b&&r.extend(this,b),this.timeStamp=a&&a.timeStamp||r.now(),void(this[r.expando]=!0)):new r.Event(a,b)},r.Event.prototype={constructor:r.Event,isDefaultPrevented:wa,isPropagationStopped:wa,isImmediatePropagationStopped:wa,isSimulated:!1,preventDefault:function(){var a=this.originalEvent;this.isDefaultPrevented=va,a&&!this.isSimulated&&a.preventDefault()},stopPropagation:function(){var a=this.originalEvent;this.isPropagationStopped=va,a&&!this.isSimulated&&a.stopPropagation()},stopImmediatePropagation:function(){var a=this.originalEvent;this.isImmediatePropagationStopped=va,a&&!this.isSimulated&&a.stopImmediatePropagation(),this.stopPropagation()}},r.each({altKey:!0,bubbles:!0,cancelable:!0,changedTouches:!0,ctrlKey:!0,detail:!0,eventPhase:!0,metaKey:!0,pageX:!0,pageY:!0,shiftKey:!0,view:!0,"char":!0,charCode:!0,key:!0,keyCode:!0,button:!0,buttons:!0,clientX:!0,clientY:!0,offsetX:!0,offsetY:!0,pointerId:!0,pointerType:!0,screenX:!0,screenY:!0,targetTouches:!0,toElement:!0,touches:!0,which:function(a){var b=a.button;return null==a.which&&sa.test(a.type)?null!=a.charCode?a.charCode:a.keyCode:!a.which&&void 0!==b&&ta.test(a.type)?1&b?1:2&b?3:4&b?2:0:a.which}},r.event.addProp),r.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(a,b){r.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj;return e&&(e===d||r.contains(d,e))||(a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b),c}}}),r.fn.extend({on:function(a,b,c,d){return ya(this,a,b,c,d)},one:function(a,b,c,d){return ya(this,a,b,c,d,1)},off:function(a,b,c){var d,e;if(a&&a.preventDefault&&a.handleObj)return d=a.handleObj,r(a.delegateTarget).off(d.namespace?d.origType+"."+d.namespace:d.origType,d.selector,d.handler),this;if("object"==typeof a){for(e in a)this.off(e,b,a[e]);return this}return b!==!1&&"function"!=typeof b||(c=b,b=void 0),c===!1&&(c=wa),this.each(function(){r.event.remove(this,a,c,b)})}});var za=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi,Aa=/<script|<style|<link/i,Ba=/checked\s*(?:[^=]|=\s*.checked.)/i,Ca=/^true\/(.*)/,Da=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;function Ea(a,b){return B(a,"table")&&B(11!==b.nodeType?b:b.firstChild,"tr")?r(">tbody",a)[0]||a:a}function Fa(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function Ga(a){var b=Ca.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Ha(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(W.hasData(a)&&(f=W.access(a),g=W.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;c<d;c++)r.event.add(b,e,j[e][c])}X.hasData(a)&&(h=X.access(a),i=r.extend({},h),X.set(b,i))}}function Ia(a,b){var c=b.nodeName.toLowerCase();"input"===c&&ja.test(a.type)?b.checked=a.checked:"input"!==c&&"textarea"!==c||(b.defaultValue=a.defaultValue)}function Ja(a,b,c,d){b=g.apply([],b);var e,f,h,i,j,k,l=0,m=a.length,n=m-1,q=b[0],s=r.isFunction(q);if(s||m>1&&"string"==typeof q&&!o.checkClone&&Ba.test(q))return a.each(function(e){var f=a.eq(e);s&&(b[0]=q.call(this,e,f.html())),Ja(f,b,c,d)});if(m&&(e=qa(b,a[0].ownerDocument,!1,a,d),f=e.firstChild,1===e.childNodes.length&&(e=f),f||d)){for(h=r.map(na(e,"script"),Fa),i=h.length;l<m;l++)j=e,l!==n&&(j=r.clone(j,!0,!0),i&&r.merge(h,na(j,"script"))),c.call(a[l],j,l);if(i)for(k=h[h.length-1].ownerDocument,r.map(h,Ga),l=0;l<i;l++)j=h[l],la.test(j.type||"")&&!W.access(j,"globalEval")&&r.contains(k,j)&&(j.src?r._evalUrl&&r._evalUrl(j.src):p(j.textContent.replace(Da,""),k))}return a}function Ka(a,b,c){for(var d,e=b?r.filter(b,a):a,f=0;null!=(d=e[f]);f++)c||1!==d.nodeType||r.cleanData(na(d)),d.parentNode&&(c&&r.contains(d.ownerDocument,d)&&oa(na(d,"script")),d.parentNode.removeChild(d));return a}r.extend({htmlPrefilter:function(a){return a.replace(za,"<$1></$2>")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=r.contains(a.ownerDocument,a);if(!(o.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||r.isXMLDoc(a)))for(g=na(h),f=na(a),d=0,e=f.length;d<e;d++)Ia(f[d],g[d]);if(b)if(c)for(f=f||na(a),g=g||na(h),d=0,e=f.length;d<e;d++)Ha(f[d],g[d]);else Ha(a,h);return g=na(h,"script"),g.length>0&&oa(g,!i&&na(a,"script")),h},cleanData:function(a){for(var b,c,d,e=r.event.special,f=0;void 0!==(c=a[f]);f++)if(U(c)){if(b=c[W.expando]){if(b.events)for(d in b.events)e[d]?r.event.remove(c,d):r.removeEvent(c,d,b.handle);c[W.expando]=void 0}c[X.expando]&&(c[X.expando]=void 0)}}}),r.fn.extend({detach:function(a){return Ka(this,a,!0)},remove:function(a){return Ka(this,a)},text:function(a){return T(this,function(a){return void 0===a?r.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return Ja(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ea(this,a);b.appendChild(a)}})},prepend:function(){return Ja(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ea(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ja(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ja(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(r.cleanData(na(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null!=a&&a,b=null==b?a:b,this.map(function(){return r.clone(this,a,b)})},html:function(a){return T(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!Aa.test(a)&&!ma[(ka.exec(a)||["",""])[1].toLowerCase()]){a=r.htmlPrefilter(a);try{for(;c<d;c++)b=this[c]||{},1===b.nodeType&&(r.cleanData(na(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=[];return Ja(this,arguments,function(b){var c=this.parentNode;r.inArray(this,a)<0&&(r.cleanData(na(this)),c&&c.replaceChild(b,this))},a)}}),r.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){r.fn[a]=function(a){for(var c,d=[],e=r(a),f=e.length-1,g=0;g<=f;g++)c=g===f?this:this.clone(!0),r(e[g])[b](c),h.apply(d,c.get());return this.pushStack(d)}});var La=/^margin/,Ma=new RegExp("^("+aa+")(?!px)[a-z%]+$","i"),Na=function(b){var c=b.ownerDocument.defaultView;return c&&c.opener||(c=a),c.getComputedStyle(b)};!function(){function b(){if(i){i.style.cssText="box-sizing:border-box;position:relative;display:block;margin:auto;border:1px;padding:1px;top:1%;width:50%",i.innerHTML="",ra.appendChild(h);var b=a.getComputedStyle(i);c="1%"!==b.top,g="2px"===b.marginLeft,e="4px"===b.width,i.style.marginRight="50%",f="4px"===b.marginRight,ra.removeChild(h),i=null}}var c,e,f,g,h=d.createElement("div"),i=d.createElement("div");i.style&&(i.style.backgroundClip="content-box",i.cloneNode(!0).style.backgroundClip="",o.clearCloneStyle="content-box"===i.style.backgroundClip,h.style.cssText="border:0;width:8px;height:0;top:0;left:-9999px;padding:0;margin-top:1px;position:absolute",h.appendChild(i),r.extend(o,{pixelPosition:function(){return b(),c},boxSizingReliable:function(){return b(),e},pixelMarginRight:function(){return b(),f},reliableMarginLeft:function(){return b(),g}}))}();function Oa(a,b,c){var d,e,f,g,h=a.style;return c=c||Na(a),c&&(g=c.getPropertyValue(b)||c[b],""!==g||r.contains(a.ownerDocument,a)||(g=r.style(a,b)),!o.pixelMarginRight()&&Ma.test(g)&&La.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f)),void 0!==g?g+"":g}function Pa(a,b){return{get:function(){return a()?void delete this.get:(this.get=b).apply(this,arguments)}}}var Qa=/^(none|table(?!-c[ea]).+)/,Ra=/^--/,Sa={position:"absolute",visibility:"hidden",display:"block"},Ta={letterSpacing:"0",fontWeight:"400"},Ua=["Webkit","Moz","ms"],Va=d.createElement("div").style;function Wa(a){if(a in Va)return a;var b=a[0].toUpperCase()+a.slice(1),c=Ua.length;while(c--)if(a=Ua[c]+b,a in Va)return a}function Xa(a){var b=r.cssProps[a];return b||(b=r.cssProps[a]=Wa(a)||a),b}function Ya(a,b,c){var d=ba.exec(b);return d?Math.max(0,d[2]-(c||0))+(d[3]||"px"):b}function Za(a,b,c,d,e){var f,g=0;for(f=c===(d?"border":"content")?4:"width"===b?1:0;f<4;f+=2)"margin"===c&&(g+=r.css(a,c+ca[f],!0,e)),d?("content"===c&&(g-=r.css(a,"padding"+ca[f],!0,e)),"margin"!==c&&(g-=r.css(a,"border"+ca[f]+"Width",!0,e))):(g+=r.css(a,"padding"+ca[f],!0,e),"padding"!==c&&(g+=r.css(a,"border"+ca[f]+"Width",!0,e)));return g}function $a(a,b,c){var d,e=Na(a),f=Oa(a,b,e),g="border-box"===r.css(a,"boxSizing",!1,e);return Ma.test(f)?f:(d=g&&(o.boxSizingReliable()||f===a.style[b]),"auto"===f&&(f=a["offset"+b[0].toUpperCase()+b.slice(1)]),f=parseFloat(f)||0,f+Za(a,b,c||(g?"border":"content"),d,e)+"px")}r.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=Oa(a,"opacity");return""===c?"1":c}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":"cssFloat"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=r.camelCase(b),i=Ra.test(b),j=a.style;return i||(b=Xa(h)),g=r.cssHooks[b]||r.cssHooks[h],void 0===c?g&&"get"in g&&void 0!==(e=g.get(a,!1,d))?e:j[b]:(f=typeof c,"string"===f&&(e=ba.exec(c))&&e[1]&&(c=fa(a,b,e),f="number"),null!=c&&c===c&&("number"===f&&(c+=e&&e[3]||(r.cssNumber[h]?"":"px")),o.clearCloneStyle||""!==c||0!==b.indexOf("background")||(j[b]="inherit"),g&&"set"in g&&void 0===(c=g.set(a,c,d))||(i?j.setProperty(b,c):j[b]=c)),void 0)}},css:function(a,b,c,d){var e,f,g,h=r.camelCase(b),i=Ra.test(b);return i||(b=Xa(h)),g=r.cssHooks[b]||r.cssHooks[h],g&&"get"in g&&(e=g.get(a,!0,c)),void 0===e&&(e=Oa(a,b,d)),"normal"===e&&b in Ta&&(e=Ta[b]),""===c||c?(f=parseFloat(e),c===!0||isFinite(f)?f||0:e):e}}),r.each(["height","width"],function(a,b){r.cssHooks[b]={get:function(a,c,d){if(c)return!Qa.test(r.css(a,"display"))||a.getClientRects().length&&a.getBoundingClientRect().width?$a(a,b,d):ea(a,Sa,function(){return $a(a,b,d)})},set:function(a,c,d){var e,f=d&&Na(a),g=d&&Za(a,b,d,"border-box"===r.css(a,"boxSizing",!1,f),f);return g&&(e=ba.exec(c))&&"px"!==(e[3]||"px")&&(a.style[b]=c,c=r.css(a,b)),Ya(a,c,g)}}}),r.cssHooks.marginLeft=Pa(o.reliableMarginLeft,function(a,b){if(b)return(parseFloat(Oa(a,"marginLeft"))||a.getBoundingClientRect().left-ea(a,{marginLeft:0},function(){return a.getBoundingClientRect().left}))+"px"}),r.each({margin:"",padding:"",border:"Width"},function(a,b){r.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];d<4;d++)e[a+ca[d]+b]=f[d]||f[d-2]||f[0];return e}},La.test(a)||(r.cssHooks[a+b].set=Ya)}),r.fn.extend({css:function(a,b){return T(this,function(a,b,c){var d,e,f={},g=0;if(Array.isArray(b)){for(d=Na(a),e=b.length;g<e;g++)f[b[g]]=r.css(a,b[g],!1,d);return f}return void 0!==c?r.style(a,b,c):r.css(a,b)},a,b,arguments.length>1)}});function _a(a,b,c,d,e){return new _a.prototype.init(a,b,c,d,e)}r.Tween=_a,_a.prototype={constructor:_a,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||r.easing._default,this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(r.cssNumber[c]?"":"px")},cur:function(){var a=_a.propHooks[this.prop];return a&&a.get?a.get(this):_a.propHooks._default.get(this)},run:function(a){var b,c=_a.propHooks[this.prop];return this.options.duration?this.pos=b=r.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):_a.propHooks._default.set(this),this}},_a.prototype.init.prototype=_a.prototype,_a.propHooks={_default:{get:function(a){var b;return 1!==a.elem.nodeType||null!=a.elem[a.prop]&&null==a.elem.style[a.prop]?a.elem[a.prop]:(b=r.css(a.elem,a.prop,""),b&&"auto"!==b?b:0)},set:function(a){r.fx.step[a.prop]?r.fx.step[a.prop](a):1!==a.elem.nodeType||null==a.elem.style[r.cssProps[a.prop]]&&!r.cssHooks[a.prop]?a.elem[a.prop]=a.now:r.style(a.elem,a.prop,a.now+a.unit)}}},_a.propHooks.scrollTop=_a.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},r.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},_default:"swing"},r.fx=_a.prototype.init,r.fx.step={};var ab,bb,cb=/^(?:toggle|show|hide)$/,db=/queueHooks$/;function eb(){bb&&(d.hidden===!1&&a.requestAnimationFrame?a.requestAnimationFrame(eb):a.setTimeout(eb,r.fx.interval),r.fx.tick())}function fb(){return a.setTimeout(function(){ab=void 0}),ab=r.now()}function gb(a,b){var c,d=0,e={height:a};for(b=b?1:0;d<4;d+=2-b)c=ca[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function hb(a,b,c){for(var d,e=(kb.tweeners[b]||[]).concat(kb.tweeners["*"]),f=0,g=e.length;f<g;f++)if(d=e[f].call(c,b,a))return d}function ib(a,b,c){var d,e,f,g,h,i,j,k,l="width"in b||"height"in b,m=this,n={},o=a.style,p=a.nodeType&&da(a),q=W.get(a,"fxshow");c.queue||(g=r._queueHooks(a,"fx"),null==g.unqueued&&(g.unqueued=0,h=g.empty.fire,g.empty.fire=function(){g.unqueued||h()}),g.unqueued++,m.always(function(){m.always(function(){g.unqueued--,r.queue(a,"fx").length||g.empty.fire()})}));for(d in b)if(e=b[d],cb.test(e)){if(delete b[d],f=f||"toggle"===e,e===(p?"hide":"show")){if("show"!==e||!q||void 0===q[d])continue;p=!0}n[d]=q&&q[d]||r.style(a,d)}if(i=!r.isEmptyObject(b),i||!r.isEmptyObject(n)){l&&1===a.nodeType&&(c.overflow=[o.overflow,o.overflowX,o.overflowY],j=q&&q.display,null==j&&(j=W.get(a,"display")),k=r.css(a,"display"),"none"===k&&(j?k=j:(ia([a],!0),j=a.style.display||j,k=r.css(a,"display"),ia([a]))),("inline"===k||"inline-block"===k&&null!=j)&&"none"===r.css(a,"float")&&(i||(m.done(function(){o.display=j}),null==j&&(k=o.display,j="none"===k?"":k)),o.display="inline-block")),c.overflow&&(o.overflow="hidden",m.always(function(){o.overflow=c.overflow[0],o.overflowX=c.overflow[1],o.overflowY=c.overflow[2]})),i=!1;for(d in n)i||(q?"hidden"in q&&(p=q.hidden):q=W.access(a,"fxshow",{display:j}),f&&(q.hidden=!p),p&&ia([a],!0),m.done(function(){p||ia([a]),W.remove(a,"fxshow");for(d in n)r.style(a,d,n[d])})),i=hb(p?q[d]:0,d,m),d in q||(q[d]=i.start,p&&(i.end=i.start,i.start=0))}}function jb(a,b){var c,d,e,f,g;for(c in a)if(d=r.camelCase(c),e=b[d],f=a[c],Array.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=r.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function kb(a,b,c){var d,e,f=0,g=kb.prefilters.length,h=r.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=ab||fb(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;g<i;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),f<1&&i?c:(i||h.notifyWith(a,[j,1,0]),h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:r.extend({},b),opts:r.extend(!0,{specialEasing:{},easing:r.easing._default},c),originalProperties:b,originalOptions:c,startTime:ab||fb(),duration:c.duration,tweens:[],createTween:function(b,c){var d=r.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;c<d;c++)j.tweens[c].run(1);return b?(h.notifyWith(a,[j,1,0]),h.resolveWith(a,[j,b])):h.rejectWith(a,[j,b]),this}}),k=j.props;for(jb(k,j.opts.specialEasing);f<g;f++)if(d=kb.prefilters[f].call(j,a,k,j.opts))return r.isFunction(d.stop)&&(r._queueHooks(j.elem,j.opts.queue).stop=r.proxy(d.stop,d)),d;return r.map(k,hb,j),r.isFunction(j.opts.start)&&j.opts.start.call(a,j),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always),r.fx.timer(r.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j}r.Animation=r.extend(kb,{tweeners:{"*":[function(a,b){var c=this.createTween(a,b);return fa(c.elem,a,ba.exec(b),c),c}]},tweener:function(a,b){r.isFunction(a)?(b=a,a=["*"]):a=a.match(L);for(var c,d=0,e=a.length;d<e;d++)c=a[d],kb.tweeners[c]=kb.tweeners[c]||[],kb.tweeners[c].unshift(b)},prefilters:[ib],prefilter:function(a,b){b?kb.prefilters.unshift(a):kb.prefilters.push(a)}}),r.speed=function(a,b,c){var d=a&&"object"==typeof a?r.extend({},a):{complete:c||!c&&b||r.isFunction(a)&&a,duration:a,easing:c&&b||b&&!r.isFunction(b)&&b};return r.fx.off?d.duration=0:"number"!=typeof d.duration&&(d.duration in r.fx.speeds?d.duration=r.fx.speeds[d.duration]:d.duration=r.fx.speeds._default),null!=d.queue&&d.queue!==!0||(d.queue="fx"),d.old=d.complete,d.complete=function(){r.isFunction(d.old)&&d.old.call(this),d.queue&&r.dequeue(this,d.queue)},d},r.fn.extend({fadeTo:function(a,b,c,d){return this.filter(da).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=r.isEmptyObject(a),f=r.speed(b,c,d),g=function(){var b=kb(this,r.extend({},a),f);(e||W.get(this,"finish"))&&b.stop(!0)};return g.finish=g,e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,b,c){var d=function(a){var b=a.stop;delete a.stop,b(c)};return"string"!=typeof a&&(c=b,b=a,a=void 0),b&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,e=null!=a&&a+"queueHooks",f=r.timers,g=W.get(this);if(e)g[e]&&g[e].stop&&d(g[e]);else for(e in g)g[e]&&g[e].stop&&db.test(e)&&d(g[e]);for(e=f.length;e--;)f[e].elem!==this||null!=a&&f[e].queue!==a||(f[e].anim.stop(c),b=!1,f.splice(e,1));!b&&c||r.dequeue(this,a)})},finish:function(a){return a!==!1&&(a=a||"fx"),this.each(function(){var b,c=W.get(this),d=c[a+"queue"],e=c[a+"queueHooks"],f=r.timers,g=d?d.length:0;for(c.finish=!0,r.queue(this,a,[]),e&&e.stop&&e.stop.call(this,!0),b=f.length;b--;)f[b].elem===this&&f[b].queue===a&&(f[b].anim.stop(!0),f.splice(b,1));for(b=0;b<g;b++)d[b]&&d[b].finish&&d[b].finish.call(this);delete c.finish})}}),r.each(["toggle","show","hide"],function(a,b){var c=r.fn[b];r.fn[b]=function(a,d,e){return null==a||"boolean"==typeof a?c.apply(this,arguments):this.animate(gb(b,!0),a,d,e)}}),r.each({slideDown:gb("show"),slideUp:gb("hide"),slideToggle:gb("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){r.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),r.timers=[],r.fx.tick=function(){var a,b=0,c=r.timers;for(ab=r.now();b<c.length;b++)a=c[b],a()||c[b]!==a||c.splice(b--,1);c.length||r.fx.stop(),ab=void 0},r.fx.timer=function(a){r.timers.push(a),r.fx.start()},r.fx.interval=13,r.fx.start=function(){bb||(bb=!0,eb())},r.fx.stop=function(){bb=null},r.fx.speeds={slow:600,fast:200,_default:400},r.fn.delay=function(b,c){return b=r.fx?r.fx.speeds[b]||b:b,c=c||"fx",this.queue(c,function(c,d){var e=a.setTimeout(c,b);d.stop=function(){a.clearTimeout(e)}})},function(){var a=d.createElement("input"),b=d.createElement("select"),c=b.appendChild(d.createElement("option"));a.type="checkbox",o.checkOn=""!==a.value,o.optSelected=c.selected,a=d.createElement("input"),a.value="t",a.type="radio",o.radioValue="t"===a.value}();var lb,mb=r.expr.attrHandle;r.fn.extend({attr:function(a,b){return T(this,r.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){r.removeAttr(this,a)})}}),r.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return"undefined"==typeof a.getAttribute?r.prop(a,b,c):(1===f&&r.isXMLDoc(a)||(e=r.attrHooks[b.toLowerCase()]||(r.expr.match.bool.test(b)?lb:void 0)),void 0!==c?null===c?void r.removeAttr(a,b):e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:(a.setAttribute(b,c+""),c):e&&"get"in e&&null!==(d=e.get(a,b))?d:(d=r.find.attr(a,b),+null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!o.radioValue&&"radio"===b&&B(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d=0,e=b&&b.match(L);if(e&&1===a.nodeType)while(c=e[d++])a.removeAttribute(c)}}),lb={set:function(a,b,c){return b===!1?r.removeAttr(a,c):a.setAttribute(c,c),c}},r.each(r.expr.match.bool.source.match(/\w+/g),function(a,b){var c=mb[b]||r.find.attr;mb[b]=function(a,b,d){var e,f,g=b.toLowerCase();return d||(f=mb[g],mb[g]=e,e=null!=c(a,b,d)?g:null,mb[g]=f),e}});var nb=/^(?:input|select|textarea|button)$/i,ob=/^(?:a|area)$/i;r.fn.extend({prop:function(a,b){return T(this,r.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[r.propFix[a]||a]})}}),r.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&r.isXMLDoc(a)||(b=r.propFix[b]||b,e=r.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=r.find.attr(a,"tabindex");return b?parseInt(b,10):nb.test(a.nodeName)||ob.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),o.optSelected||(r.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),r.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){r.propFix[this.toLowerCase()]=this});function pb(a){var b=a.match(L)||[];return b.join(" ")}function qb(a){return a.getAttribute&&a.getAttribute("class")||""}r.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).addClass(a.call(this,b,qb(this)))});if("string"==typeof a&&a){b=a.match(L)||[];while(c=this[i++])if(e=qb(c),d=1===c.nodeType&&" "+pb(e)+" "){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=pb(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).removeClass(a.call(this,b,qb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(L)||[];while(c=this[i++])if(e=qb(c),d=1===c.nodeType&&" "+pb(e)+" "){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=pb(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):r.isFunction(a)?this.each(function(c){r(this).toggleClass(a.call(this,c,qb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=r(this),f=a.match(L)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=qb(this),b&&W.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":W.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+pb(qb(c))+" ").indexOf(b)>-1)return!0;return!1}});var rb=/\r/g;r.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=r.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,r(this).val()):a,null==e?e="":"number"==typeof e?e+="":Array.isArray(e)&&(e=r.map(e,function(a){return null==a?"":a+""})),b=r.valHooks[this.type]||r.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=r.valHooks[e.type]||r.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(rb,""):null==c?"":c)}}}),r.extend({valHooks:{option:{get:function(a){var b=r.find.attr(a,"value");return null!=b?b:pb(r.text(a))}},select:{get:function(a){var b,c,d,e=a.options,f=a.selectedIndex,g="select-one"===a.type,h=g?null:[],i=g?f+1:e.length;for(d=f<0?i:g?f:0;d<i;d++)if(c=e[d],(c.selected||d===f)&&!c.disabled&&(!c.parentNode.disabled||!B(c.parentNode,"optgroup"))){if(b=r(c).val(),g)return b;h.push(b)}return h},set:function(a,b){var c,d,e=a.options,f=r.makeArray(b),g=e.length;while(g--)d=e[g],(d.selected=r.inArray(r.valHooks.option.get(d),f)>-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),r.each(["radio","checkbox"],function(){r.valHooks[this]={set:function(a,b){if(Array.isArray(b))return a.checked=r.inArray(r(a).val(),b)>-1}},o.checkOn||(r.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var sb=/^(?:focusinfocus|focusoutblur)$/;r.extend(r.event,{trigger:function(b,c,e,f){var g,h,i,j,k,m,n,o=[e||d],p=l.call(b,"type")?b.type:b,q=l.call(b,"namespace")?b.namespace.split("."):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!sb.test(p+r.event.triggered)&&(p.indexOf(".")>-1&&(q=p.split("."),p=q.shift(),q.sort()),k=p.indexOf(":")<0&&"on"+p,b=b[r.expando]?b:new r.Event(p,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=q.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:r.makeArray(c,[b]),n=r.event.special[p]||{},f||!n.trigger||n.trigger.apply(e,c)!==!1)){if(!f&&!n.noBubble&&!r.isWindow(e)){for(j=n.delegateType||p,sb.test(j+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),i=h;i===(e.ownerDocument||d)&&o.push(i.defaultView||i.parentWindow||a)}g=0;while((h=o[g++])&&!b.isPropagationStopped())b.type=g>1?j:n.bindType||p,m=(W.get(h,"events")||{})[b.type]&&W.get(h,"handle"),m&&m.apply(h,c),m=k&&h[k],m&&m.apply&&U(h)&&(b.result=m.apply(h,c),b.result===!1&&b.preventDefault());return b.type=p,f||b.isDefaultPrevented()||n._default&&n._default.apply(o.pop(),c)!==!1||!U(e)||k&&r.isFunction(e[p])&&!r.isWindow(e)&&(i=e[k],i&&(e[k]=null),r.event.triggered=p,e[p](),r.event.triggered=void 0,i&&(e[k]=i)),b.result}},simulate:function(a,b,c){var d=r.extend(new r.Event,c,{type:a,isSimulated:!0});r.event.trigger(d,null,b)}}),r.fn.extend({trigger:function(a,b){return this.each(function(){r.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];if(c)return r.event.trigger(a,b,c,!0)}}),r.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(a,b){r.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),r.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),o.focusin="onfocusin"in a,o.focusin||r.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){r.event.simulate(b,a.target,r.event.fix(a))};r.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=W.access(d,b);e||d.addEventListener(a,c,!0),W.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=W.access(d,b)-1;e?W.access(d,b,e):(d.removeEventListener(a,c,!0),W.remove(d,b))}}});var tb=a.location,ub=r.now(),vb=/\?/;r.parseXML=function(b){var c;if(!b||"string"!=typeof b)return null;try{c=(new a.DOMParser).parseFromString(b,"text/xml")}catch(d){c=void 0}return c&&!c.getElementsByTagName("parsererror").length||r.error("Invalid XML: "+b),c};var wb=/\[\]$/,xb=/\r?\n/g,yb=/^(?:submit|button|image|reset|file)$/i,zb=/^(?:input|select|textarea|keygen)/i;function Ab(a,b,c,d){var e;if(Array.isArray(b))r.each(b,function(b,e){c||wb.test(a)?d(a,e):Ab(a+"["+("object"==typeof e&&null!=e?b:"")+"]",e,c,d)});else if(c||"object"!==r.type(b))d(a,b);else for(e in b)Ab(a+"["+e+"]",b[e],c,d)}r.param=function(a,b){var c,d=[],e=function(a,b){var c=r.isFunction(b)?b():b;d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(null==c?"":c)};if(Array.isArray(a)||a.jquery&&!r.isPlainObject(a))r.each(a,function(){e(this.name,this.value)});else for(c in a)Ab(c,a[c],b,e);return d.join("&")},r.fn.extend({serialize:function(){return r.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=r.prop(this,"elements");return a?r.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!r(this).is(":disabled")&&zb.test(this.nodeName)&&!yb.test(a)&&(this.checked||!ja.test(a))}).map(function(a,b){var c=r(this).val();return null==c?null:Array.isArray(c)?r.map(c,function(a){return{name:b.name,value:a.replace(xb,"\r\n")}}):{name:b.name,value:c.replace(xb,"\r\n")}}).get()}});var Bb=/%20/g,Cb=/#.*$/,Db=/([?&])_=[^&]*/,Eb=/^(.*?):[ \t]*([^\r\n]*)$/gm,Fb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Gb=/^(?:GET|HEAD)$/,Hb=/^\/\//,Ib={},Jb={},Kb="*/".concat("*"),Lb=d.createElement("a");Lb.href=tb.href;function Mb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(L)||[];if(r.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Nb(a,b,c,d){var e={},f=a===Jb;function g(h){var i;return e[h]=!0,r.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Ob(a,b){var c,d,e=r.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&r.extend(!0,a,d),a}function Pb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}if(f)return f!==i[0]&&i.unshift(f),c[f]}function Qb(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}r.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:tb.href,type:"GET",isLocal:Fb.test(tb.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Kb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":r.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Ob(Ob(a,r.ajaxSettings),b):Ob(r.ajaxSettings,a)},ajaxPrefilter:Mb(Ib),ajaxTransport:Mb(Jb),ajax:function(b,c){"object"==typeof b&&(c=b,b=void 0),c=c||{};var e,f,g,h,i,j,k,l,m,n,o=r.ajaxSetup({},c),p=o.context||o,q=o.context&&(p.nodeType||p.jquery)?r(p):r.event,s=r.Deferred(),t=r.Callbacks("once memory"),u=o.statusCode||{},v={},w={},x="canceled",y={readyState:0,getResponseHeader:function(a){var b;if(k){if(!h){h={};while(b=Eb.exec(g))h[b[1].toLowerCase()]=b[2]}b=h[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return k?g:null},setRequestHeader:function(a,b){return null==k&&(a=w[a.toLowerCase()]=w[a.toLowerCase()]||a,v[a]=b),this},overrideMimeType:function(a){return null==k&&(o.mimeType=a),this},statusCode:function(a){var b;if(a)if(k)y.always(a[y.status]);else for(b in a)u[b]=[u[b],a[b]];return this},abort:function(a){var b=a||x;return e&&e.abort(b),A(0,b),this}};if(s.promise(y),o.url=((b||o.url||tb.href)+"").replace(Hb,tb.protocol+"//"),o.type=c.method||c.type||o.method||o.type,o.dataTypes=(o.dataType||"*").toLowerCase().match(L)||[""],null==o.crossDomain){j=d.createElement("a");try{j.href=o.url,j.href=j.href,o.crossDomain=Lb.protocol+"//"+Lb.host!=j.protocol+"//"+j.host}catch(z){o.crossDomain=!0}}if(o.data&&o.processData&&"string"!=typeof o.data&&(o.data=r.param(o.data,o.traditional)),Nb(Ib,o,c,y),k)return y;l=r.event&&o.global,l&&0===r.active++&&r.event.trigger("ajaxStart"),o.type=o.type.toUpperCase(),o.hasContent=!Gb.test(o.type),f=o.url.replace(Cb,""),o.hasContent?o.data&&o.processData&&0===(o.contentType||"").indexOf("application/x-www-form-urlencoded")&&(o.data=o.data.replace(Bb,"+")):(n=o.url.slice(f.length),o.data&&(f+=(vb.test(f)?"&":"?")+o.data,delete o.data),o.cache===!1&&(f=f.replace(Db,"$1"),n=(vb.test(f)?"&":"?")+"_="+ub++ +n),o.url=f+n),o.ifModified&&(r.lastModified[f]&&y.setRequestHeader("If-Modified-Since",r.lastModified[f]),r.etag[f]&&y.setRequestHeader("If-None-Match",r.etag[f])),(o.data&&o.hasContent&&o.contentType!==!1||c.contentType)&&y.setRequestHeader("Content-Type",o.contentType),y.setRequestHeader("Accept",o.dataTypes[0]&&o.accepts[o.dataTypes[0]]?o.accepts[o.dataTypes[0]]+("*"!==o.dataTypes[0]?", "+Kb+"; q=0.01":""):o.accepts["*"]);for(m in o.headers)y.setRequestHeader(m,o.headers[m]);if(o.beforeSend&&(o.beforeSend.call(p,y,o)===!1||k))return y.abort();if(x="abort",t.add(o.complete),y.done(o.success),y.fail(o.error),e=Nb(Jb,o,c,y)){if(y.readyState=1,l&&q.trigger("ajaxSend",[y,o]),k)return y;o.async&&o.timeout>0&&(i=a.setTimeout(function(){y.abort("timeout")},o.timeout));try{k=!1,e.send(v,A)}catch(z){if(k)throw z;A(-1,z)}}else A(-1,"No Transport");function A(b,c,d,h){var j,m,n,v,w,x=c;k||(k=!0,i&&a.clearTimeout(i),e=void 0,g=h||"",y.readyState=b>0?4:0,j=b>=200&&b<300||304===b,d&&(v=Pb(o,y,d)),v=Qb(o,v,y,j),j?(o.ifModified&&(w=y.getResponseHeader("Last-Modified"),w&&(r.lastModified[f]=w),w=y.getResponseHeader("etag"),w&&(r.etag[f]=w)),204===b||"HEAD"===o.type?x="nocontent":304===b?x="notmodified":(x=v.state,m=v.data,n=v.error,j=!n)):(n=x,!b&&x||(x="error",b<0&&(b=0))),y.status=b,y.statusText=(c||x)+"",j?s.resolveWith(p,[m,x,y]):s.rejectWith(p,[y,x,n]),y.statusCode(u),u=void 0,l&&q.trigger(j?"ajaxSuccess":"ajaxError",[y,o,j?m:n]),t.fireWith(p,[y,x]),l&&(q.trigger("ajaxComplete",[y,o]),--r.active||r.event.trigger("ajaxStop")))}return y},getJSON:function(a,b,c){return r.get(a,b,c,"json")},getScript:function(a,b){return r.get(a,void 0,b,"script")}}),r.each(["get","post"],function(a,b){r[b]=function(a,c,d,e){return r.isFunction(c)&&(e=e||d,d=c,c=void 0),r.ajax(r.extend({url:a,type:b,dataType:e,data:c,success:d},r.isPlainObject(a)&&a))}}),r._evalUrl=function(a){return r.ajax({url:a,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},r.fn.extend({wrapAll:function(a){var b;return this[0]&&(r.isFunction(a)&&(a=a.call(this[0])),b=r(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this},wrapInner:function(a){return r.isFunction(a)?this.each(function(b){r(this).wrapInner(a.call(this,b))}):this.each(function(){var b=r(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=r.isFunction(a);return this.each(function(c){r(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(a){return this.parent(a).not("body").each(function(){r(this).replaceWith(this.childNodes)}),this}}),r.expr.pseudos.hidden=function(a){return!r.expr.pseudos.visible(a)},r.expr.pseudos.visible=function(a){return!!(a.offsetWidth||a.offsetHeight||a.getClientRects().length)},r.ajaxSettings.xhr=function(){try{return new a.XMLHttpRequest}catch(b){}};var Rb={0:200,1223:204},Sb=r.ajaxSettings.xhr();o.cors=!!Sb&&"withCredentials"in Sb,o.ajax=Sb=!!Sb,r.ajaxTransport(function(b){var c,d;if(o.cors||Sb&&!b.crossDomain)return{send:function(e,f){var g,h=b.xhr();if(h.open(b.type,b.url,b.async,b.username,b.password),b.xhrFields)for(g in b.xhrFields)h[g]=b.xhrFields[g];b.mimeType&&h.overrideMimeType&&h.overrideMimeType(b.mimeType),b.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest");for(g in e)h.setRequestHeader(g,e[g]);c=function(a){return function(){c&&(c=d=h.onload=h.onerror=h.onabort=h.onreadystatechange=null,"abort"===a?h.abort():"error"===a?"number"!=typeof h.status?f(0,"error"):f(h.status,h.statusText):f(Rb[h.status]||h.status,h.statusText,"text"!==(h.responseType||"text")||"string"!=typeof h.responseText?{binary:h.response}:{text:h.responseText},h.getAllResponseHeaders()))}},h.onload=c(),d=h.onerror=c("error"),void 0!==h.onabort?h.onabort=d:h.onreadystatechange=function(){4===h.readyState&&a.setTimeout(function(){c&&d()})},c=c("abort");try{h.send(b.hasContent&&b.data||null)}catch(i){if(c)throw i}},abort:function(){c&&c()}}}),r.ajaxPrefilter(function(a){a.crossDomain&&(a.contents.script=!1)}),r.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(a){return r.globalEval(a),a}}}),r.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),r.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(e,f){b=r("<script>").prop({charset:a.scriptCharset,src:a.url}).on("load error",c=function(a){b.remove(),c=null,a&&f("error"===a.type?404:200,a.type)}),d.head.appendChild(b[0])},abort:function(){c&&c()}}}});var Tb=[],Ub=/(=)\?(?=&|$)|\?\?/;r.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=Tb.pop()||r.expando+"_"+ub++;return this[a]=!0,a}}),r.ajaxPrefilter("json jsonp",function(b,c,d){var e,f,g,h=b.jsonp!==!1&&(Ub.test(b.url)?"url":"string"==typeof b.data&&0===(b.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ub.test(b.data)&&"data");if(h||"jsonp"===b.dataTypes[0])return e=b.jsonpCallback=r.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h?b[h]=b[h].replace(Ub,"$1"+e):b.jsonp!==!1&&(b.url+=(vb.test(b.url)?"&":"?")+b.jsonp+"="+e),b.converters["script json"]=function(){return g||r.error(e+" was not called"),g[0]},b.dataTypes[0]="json",f=a[e],a[e]=function(){g=arguments},d.always(function(){void 0===f?r(a).removeProp(e):a[e]=f,b[e]&&(b.jsonpCallback=c.jsonpCallback,Tb.push(e)),g&&r.isFunction(f)&&f(g[0]),g=f=void 0}),"script"}),o.createHTMLDocument=function(){var a=d.implementation.createHTMLDocument("").body;return a.innerHTML="<form></form><form></form>",2===a.childNodes.length}(),r.parseHTML=function(a,b,c){if("string"!=typeof a)return[];"boolean"==typeof b&&(c=b,b=!1);var e,f,g;return b||(o.createHTMLDocument?(b=d.implementation.createHTMLDocument(""),e=b.createElement("base"),e.href=d.location.href,b.head.appendChild(e)):b=d),f=C.exec(a),g=!c&&[],f?[b.createElement(f[1])]:(f=qa([a],b,g),g&&g.length&&r(g).remove(),r.merge([],f.childNodes))},r.fn.load=function(a,b,c){var d,e,f,g=this,h=a.indexOf(" ");return h>-1&&(d=pb(a.slice(h)),a=a.slice(0,h)),r.isFunction(b)?(c=b,b=void 0):b&&"object"==typeof b&&(e="POST"),g.length>0&&r.ajax({url:a,type:e||"GET",dataType:"html",data:b}).done(function(a){f=arguments,g.html(d?r("<div>").append(r.parseHTML(a)).find(d):a)}).always(c&&function(a,b){g.each(function(){c.apply(this,f||[a.responseText,b,a])})}),this},r.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(a,b){r.fn[b]=function(a){return this.on(b,a)}}),r.expr.pseudos.animated=function(a){return r.grep(r.timers,function(b){return a===b.elem}).length},r.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=r.css(a,"position"),l=r(a),m={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=r.css(a,"top"),i=r.css(a,"left"),j=("absolute"===k||"fixed"===k)&&(f+i).indexOf("auto")>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),r.isFunction(b)&&(b=b.call(a,c,r.extend({},h))),null!=b.top&&(m.top=b.top-h.top+g),null!=b.left&&(m.left=b.left-h.left+e),"using"in b?b.using.call(a,m):l.css(m)}},r.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){r.offset.setOffset(this,a,b)});var b,c,d,e,f=this[0];if(f)return f.getClientRects().length?(d=f.getBoundingClientRect(),b=f.ownerDocument,c=b.documentElement,e=b.defaultView,{top:d.top+e.pageYOffset-c.clientTop,left:d.left+e.pageXOffset-c.clientLeft}):{top:0,left:0}},position:function(){if(this[0]){var a,b,c=this[0],d={top:0,left:0};return"fixed"===r.css(c,"position")?b=c.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),B(a[0],"html")||(d=a.offset()),d={top:d.top+r.css(a[0],"borderTopWidth",!0),left:d.left+r.css(a[0],"borderLeftWidth",!0)}),{top:b.top-d.top-r.css(c,"marginTop",!0),left:b.left-d.left-r.css(c,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent;while(a&&"static"===r.css(a,"position"))a=a.offsetParent;return a||ra})}}),r.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,b){var c="pageYOffset"===b;r.fn[a]=function(d){return T(this,function(a,d,e){var f;return r.isWindow(a)?f=a:9===a.nodeType&&(f=a.defaultView),void 0===e?f?f[b]:a[d]:void(f?f.scrollTo(c?f.pageXOffset:e,c?e:f.pageYOffset):a[d]=e)},a,d,arguments.length)}}),r.each(["top","left"],function(a,b){r.cssHooks[b]=Pa(o.pixelPosition,function(a,c){if(c)return c=Oa(a,b),Ma.test(c)?r(a).position()[b]+"px":c})}),r.each({Height:"height",Width:"width"},function(a,b){r.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){r.fn[d]=function(e,f){var g=arguments.length&&(c||"boolean"!=typeof e),h=c||(e===!0||f===!0?"margin":"border");return T(this,function(b,c,e){var f;return r.isWindow(b)?0===d.indexOf("outer")?b["inner"+a]:b.document.documentElement["client"+a]:9===b.nodeType?(f=b.documentElement,Math.max(b.body["scroll"+a],f["scroll"+a],b.body["offset"+a],f["offset"+a],f["client"+a])):void 0===e?r.css(b,c,h):r.style(b,c,e,h)},b,g?e:void 0,g)}})}),r.fn.extend({bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)}}),r.holdReady=function(a){a?r.readyWait++:r.ready(!0)},r.isArray=Array.isArray,r.parseJSON=JSON.parse,r.nodeName=B,"function"==typeof define&&define.amd&&define("jquery",[],function(){return r});var Vb=a.jQuery,Wb=a.$;return r.noConflict=function(b){return a.$===r&&(a.$=Wb),b&&a.jQuery===r&&(a.jQuery=Vb),r},b||(a.jQuery=a.$=r),r});
+ js/log.js view
@@ -0,0 +1,32 @@+/* *********************************************************************+ Logging utilities+********************************************************************* */++// simple logging+Haskell.log = function () {+ if ($.cookie('Haskell.log.enabled') === "true") {+ window.console.log.apply(window.console, arguments);+ }+};++Haskell.log.enable = function (bool) {+ $.cookie('Haskell.log.enabled', bool.toString());+};+++// very simple performance counter+Haskell.performance = function () {+ var t1,t2 = 0.0;+ var that = {}++ that.now = function () {+ t1 = t2;+ t2 = window.performance.now();+ return t2;+ }+ that.diff = function () {+ return t2 - t1;+ }++ return that;+}();
+ samples/BarTab.hs view
@@ -0,0 +1,77 @@+{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}++import Control.Applicative+import Control.Monad+import Data.IORef+import Data.Maybe++import qualified Graphics.UI.Threepenny as UI+import Graphics.UI.Threepenny.Core+++-- | Main entry point.+main :: IO ()+main = startGUI defaultConfig setup++setup :: Window -> UI ()+setup w = do+ -- active elements+ return w # set title "BarTab"++ elAdd <- UI.button # set UI.text "Add"+ elRemove <- UI.button # set UI.text "Remove"+ elResult <- UI.span++ inputs <- liftIO $ newIORef []++ -- functionality+ let+ displayTotal = void $ do+ xs <- mapM (get value) =<< liftIO (readIORef inputs)+ element elResult # set UI.text (showNumber . sum $ map readNumber xs)++ redoLayout :: UI ()+ redoLayout = void $ do+ layout <- mkLayout =<< liftIO (readIORef inputs)+ getBody w # set children [layout]+ displayTotal++ mkLayout :: [Element] -> UI Element+ mkLayout xs = column $+ [row [element elAdd, element elRemove]+ ,UI.hr]+ ++ map element xs +++ [UI.hr+ ,row [UI.span # set text "Sum: ", element elResult]+ ]++ addInput :: UI ()+ addInput = do+ elInput <- UI.input # set value "0"+ on (domEvent "livechange") elInput $ \_ -> displayTotal+ liftIO $ modifyIORef inputs (elInput:)++ removeInput :: UI ()+ removeInput = liftIO $ modifyIORef inputs (drop 1)++ on UI.click elAdd $ \_ -> addInput >> redoLayout+ on UI.click elRemove $ \_ -> removeInput >> redoLayout+ addInput >> redoLayout+++{-----------------------------------------------------------------------------+ Functionality+------------------------------------------------------------------------------}+type Number = Maybe Double++instance Num Number where+ (+) = liftA2 (+)+ (-) = liftA2 (-)+ (*) = liftA2 (*)+ abs = fmap abs+ signum = fmap signum+ fromInteger = pure . fromInteger++readNumber :: String -> Number+readNumber s = listToMaybe [x | (x,"") <- reads s]+showNumber = maybe "--" show
+ samples/Buttons.hs view
@@ -0,0 +1,75 @@+import Control.Monad+import Control.Concurrent (threadDelay)++import Paths++import qualified Graphics.UI.Threepenny as UI+import Graphics.UI.Threepenny.Core++{-----------------------------------------------------------------------------+ Buttons+------------------------------------------------------------------------------}++main :: IO ()+main = do+ static <- getStaticDir+ startGUI defaultConfig { jsStatic = Just static } setup++setup :: Window -> UI ()+setup w = void $ do+ return w # set title "Buttons"+ UI.addStyleSheet w "buttons.css"++ buttons <- mkButtons+ getBody w #++ [UI.div #. "wrap" #+ (greet ++ map element buttons ++ [viewSource])]++greet :: [UI Element]+greet =+ [ UI.h1 #+ [string "Hello, Haskell!"]+ , UI.div #+ [string "Try the buttons below, they hover and click."]+ ]+++mkButton :: String -> UI (Element, Element)+mkButton title = do+ button <- UI.button #. "button" #+ [string title]+ view <- UI.p #+ [element button]+ return (button, view)++mkButtons :: UI [Element]+mkButtons = do+ list <- UI.ul #. "buttons-list"+ + (button1, view1) <- mkButton button1Title+ + on UI.hover button1 $ \_ -> do+ element button1 # set text (button1Title ++ " [hover]")+ on UI.leave button1 $ \_ -> do+ element button1 # set text button1Title+ on UI.click button1 $ \_ -> do+ element button1 # set text (button1Title ++ " [pressed]")+ liftIO $ threadDelay $ 1000 * 1000 * 1+ element list #+ [UI.li # set html "<b>Delayed</b> result!"]+ + (button2, view2) <- mkButton button2Title++ on UI.hover button2 $ \_ -> do+ element button2 # set text (button2Title ++ " [hover]")+ on UI.leave button2 $ \_ -> do+ element button2 # set text button2Title+ on UI.click button2 $ \_ -> do+ element button2 # set text (button2Title ++ " [pressed]")+ element list #+ [UI.li # set html "Zap! Quick result!"]+ + return [list, view1, view2]++ where button1Title = "Click me, I delay a bit"+ button2Title = "Click me, I work immediately"++viewSource :: UI Element+viewSource = UI.p #++ [UI.anchor #. "view-source" # set UI.href url #+ [string "View source code"]]+ where+ url = samplesURL ++ "Buttons.hs"+
+ samples/CRUD.hs view
@@ -0,0 +1,135 @@+{-----------------------------------------------------------------------------+ threepenny-gui+ + Example:+ Small database with CRUD operations and filtering.+ To keep things simple, the list box is rebuild every time+ that the database is updated. This is perfectly fine for rapid prototyping.+ A more sophisticated approach would use incremental updates.+------------------------------------------------------------------------------}+{-# LANGUAGE RecursiveDo #-}++import Prelude hiding (lookup)+import Control.Monad (void)+import Data.List (isPrefixOf)+import qualified Data.Map as Map++import qualified Graphics.UI.Threepenny as UI+import Graphics.UI.Threepenny.Core hiding (delete)++{-----------------------------------------------------------------------------+ Main+------------------------------------------------------------------------------}+main :: IO ()+main = startGUI defaultConfig setup++setup :: Window -> UI ()+setup window = void $ mdo+ return window # set title "CRUD Example (Simple)"++ -- GUI elements+ createBtn <- UI.button #+ [string "Create"]+ deleteBtn <- UI.button #+ [string "Delete"]+ listBox <- UI.listBox bListBoxItems bSelection bDisplayDataItem+ filterEntry <- UI.entry bFilterString+ ((firstname, lastname), tDataItem)+ <- dataItem bSelectionDataItem++ -- GUI layout+ element listBox # set (attr "size") "10" # set style [("width","200px")]+ + let uiDataItem = grid [[string "First Name:", element firstname]+ ,[string "Last Name:" , element lastname]]+ let glue = string " "+ getBody window #+ [grid+ [[row [string "Filter prefix:", element filterEntry], glue]+ ,[element listBox, uiDataItem]+ ,[row [element createBtn, element deleteBtn], glue]+ ]]++ -- events and behaviors+ bFilterString <- stepper "" . rumors $ UI.userText filterEntry+ let tFilter = isPrefixOf <$> UI.userText filterEntry+ bFilter = facts tFilter+ eFilter = rumors tFilter++ let eSelection = rumors $ UI.userSelection listBox+ eDataItemIn = rumors $ tDataItem+ eCreate = UI.click createBtn+ eDelete = UI.click deleteBtn++ -- database+ -- bDatabase :: Behavior (Database DataItem)+ let update' mkey x = flip update x <$> mkey+ bDatabase <- accumB emptydb $ concatenate <$> unions+ [ create ("Emil","Example") <$ eCreate+ , filterJust $ update' <$> bSelection <@> eDataItemIn+ , delete <$> filterJust (bSelection <@ eDelete)+ ]++ -- selection+ -- bSelection :: Behavior (Maybe DatabaseKey)+ bSelection <- stepper Nothing $ head <$> unions+ [ eSelection+ , Nothing <$ eDelete+ , Just . nextKey <$> bDatabase <@ eCreate+ , (\b s p -> b >>= \a -> if p (s a) then Just a else Nothing)+ <$> bSelection <*> bShowDataItem <@> eFilter+ ]+ + let bLookup :: Behavior (DatabaseKey -> Maybe DataItem)+ bLookup = flip lookup <$> bDatabase+ + bShowDataItem :: Behavior (DatabaseKey -> String)+ bShowDataItem = (maybe "" showDataItem .) <$> bLookup++ bDisplayDataItem = (UI.string .) <$> bShowDataItem+ + bListBoxItems :: Behavior [DatabaseKey]+ bListBoxItems = (\p show -> filter (p. show) . keys)+ <$> bFilter <*> bShowDataItem <*> bDatabase++ bSelectionDataItem :: Behavior (Maybe DataItem)+ bSelectionDataItem = (=<<) <$> bLookup <*> bSelection++ -- automatically enable / disable editing+ let+ bDisplayItem :: Behavior Bool+ bDisplayItem = maybe False (const True) <$> bSelection+ + element deleteBtn # sink UI.enabled bDisplayItem+ element firstname # sink UI.enabled bDisplayItem+ element lastname # sink UI.enabled bDisplayItem+++{-----------------------------------------------------------------------------+ Database Model+------------------------------------------------------------------------------}+type DatabaseKey = Int+data Database a = Database { nextKey :: !Int, db :: Map.Map DatabaseKey a }++emptydb = Database 0 Map.empty+keys = Map.keys . db++create x (Database newkey db) = Database (newkey+1) $ Map.insert newkey x db+update key x (Database newkey db) = Database newkey $ Map.insert key x db+delete key (Database newkey db) = Database newkey $ Map.delete key db+lookup key (Database _ db) = Map.lookup key db++{-----------------------------------------------------------------------------+ Data items that are stored in the data base+------------------------------------------------------------------------------}+type DataItem = (String, String)+showDataItem (firstname, lastname) = lastname ++ ", " ++ firstname++-- | Data item widget, consisting of two text entries+dataItem+ :: Behavior (Maybe DataItem)+ -> UI ((Element, Element), Tidings DataItem)+dataItem bItem = do+ entry1 <- UI.entry $ fst . maybe ("","") id <$> bItem+ entry2 <- UI.entry $ snd . maybe ("","") id <$> bItem+ + return ( (getElement entry1, getElement entry2)+ , (,) <$> UI.userText entry1 <*> UI.userText entry2+ )
+ samples/Canvas.hs view
@@ -0,0 +1,108 @@+import Control.Monad++import Paths++import qualified Graphics.UI.Threepenny as UI+import Graphics.UI.Threepenny.Core+++{-----------------------------------------------------------------------------+ Main+------------------------------------------------------------------------------}+main :: IO ()+main = do+ static <- getStaticDir+ startGUI defaultConfig setup++canvasSize = 400++setup :: Window -> UI ()+setup window = do+ return window # set title "Canvas - Examples"++ canvas <- UI.canvas+ # set UI.height canvasSize+ # set UI.width canvasSize+ # set style [("border", "solid black 1px"), ("background", "#eee")]++ drawRects <- UI.button #+ [string "Add some rectangles."]+ drawText <- UI.button #+ [string "Add text."]+ drawImage <- UI.button #+ [string "Add image."]+ drawPie <- UI.button #+ [string "Must have pie!"]+ clear <- UI.button #+ [string "Clear the canvas."]++ getBody window #++ [ column [element canvas]+ , element drawRects, element drawText, element drawImage+ , element drawPie , element clear+ ]++ on UI.click clear $ const $+ canvas # UI.clearCanvas++ -- draw a pie chart+ on UI.click drawPie $ const $ do+ let+ center = (fromIntegral canvasSize / 2, fromIntegral (canvasSize+30) / 2)+ radius = 100++ drawSlice start end color = do+ canvas # set' UI.fillStyle (UI.htmlColor color)+ canvas # set' UI.strokeStyle "white"+ canvas # UI.beginPath+ canvas # UI.arc center radius start end+ canvas # UI.lineTo center+ canvas # UI.closePath+ canvas # UI.fill+ canvas # UI.stroke++ radian angle = angle * pi / 180++ normalizeAngles xs = map (\(x,y) -> (360 * x/total,y)) xs+ where total = sum $ map fst xs++ pieData = normalizeAngles+ [ (100, "#1f77b4")+ , (45, "#ff7f0e")+ , (80, "#2ca02c")+ , (10, "#d62728")+ , (105,"#9467bd")+ , (20, "#8c564b")+ ]++ UI.timestamp -- measure drawing performance for fun+ foldM (\start (delta, col) -> do+ let end = start+delta+ drawSlice (radian start) (radian end) col+ return end) 0 pieData+ UI.timestamp+++ -- draw some rectangles+ on UI.click drawRects $ const $ do+ let rects = [ (20 , 130, 15, 120, "teal")+ , (345, 110, 15, 90, "lightblue")+ , (220, 360, 95, 15, "teal")+ ]++ forM_ rects $ \(x,y,w,h,color) -> do+ canvas # set' UI.fillStyle (UI.htmlColor color)+ canvas # UI.fillRect (x,y) w h++ -- draw some text+ on UI.click drawText $ const $ do+ return canvas+ # set UI.textFont "30px sans-serif"+ # set UI.strokeStyle "gray"+ # set UI.fillStyle (UI.htmlColor "black")++ canvas # UI.strokeText "is awesome" (141,61)+ canvas # UI.fillText "is awesome" (140,60)++ -- draw the haskell logo+ url <- UI.loadFile "image/png" "static/haskell-logo.png"+ img <- UI.img # set UI.src url++ on UI.click drawImage $ const $ do+ canvas # UI.drawImage img (60,20)+
+ samples/Chat.hs view
@@ -0,0 +1,104 @@+import Control.Concurrent+import qualified Control.Concurrent.Chan as Chan+import Control.Monad+import Data.List.Extra+import Data.Time+import Data.IORef+import Prelude hiding (catch)++import Paths++import qualified Graphics.UI.Threepenny as UI+import Graphics.UI.Threepenny.Core hiding (text)++{-----------------------------------------------------------------------------+ Chat+------------------------------------------------------------------------------}++main :: IO ()+main = do+ static <- getStaticDir+ messages <- Chan.newChan+ startGUI defaultConfig+ { jsCustomHTML = Just "chat.html"+ , jsStatic = Just static+ , jsCallBufferMode = BufferRun+ } $ setup messages++type Message = (UTCTime, String, String)++setup :: Chan Message -> Window -> UI ()+setup globalMsgs window = do+ msgs <- liftIO $ Chan.dupChan globalMsgs++ return window # set title "Chat"+ + (nickRef, nickname) <- mkNickname+ messageArea <- mkMessageArea msgs nickRef++ getBody window #++ [ UI.div #. "header" #+ [string "Threepenny Chat"]+ , UI.div #. "gradient"+ , viewSource+ , element nickname+ , element messageArea+ ]+ + messageReceiver <- liftIO $ forkIO $ receiveMessages window msgs messageArea++ on UI.disconnect window $ const $ liftIO $ do+ killThread messageReceiver+ now <- getCurrentTime+ nick <- readIORef nickRef+ Chan.writeChan msgs (now,nick,"( left the conversation )")+++receiveMessages w msgs messageArea = do+ messages <- Chan.getChanContents msgs+ forM_ messages $ \msg -> do+ runUI w $ do+ element messageArea #+ [mkMessage msg]+ UI.scrollToBottom messageArea+ flushCallBuffer -- make sure that JavaScript functions are executed++mkMessageArea :: Chan Message -> IORef String -> UI Element+mkMessageArea msgs nickname = do+ input <- UI.textarea #. "send-textarea"+ + on UI.sendValue input $ (. trim) $ \content -> do+ element input # set value ""+ when (not (null content)) $ liftIO $ do+ now <- getCurrentTime+ nick <- readIORef nickname+ when (not (null nick)) $+ Chan.writeChan msgs (now,nick,content)++ UI.div #. "message-area" #+ [UI.div #. "send-area" #+ [element input]]+++mkNickname :: UI (IORef String, Element)+mkNickname = do+ input <- UI.input #. "name-input"+ el <- UI.div #. "name-area" #++ [ UI.span #. "name-label" #+ [string "Your name "]+ , element input+ ]+ UI.setFocus input+ + nick <- liftIO $ newIORef ""+ on UI.keyup input $ \_ -> liftIO . writeIORef nick . trim =<< get value input+ return (nick,el)++mkMessage :: Message -> UI Element+mkMessage (timestamp, nick, content) =+ UI.div #. "message" #++ [ UI.div #. "timestamp" #+ [string $ show timestamp]+ , UI.div #. "name" #+ [string $ nick ++ " says:"]+ , UI.div #. "content" #+ [string content]+ ]++viewSource :: UI Element+viewSource =+ UI.anchor #. "view-source" # set UI.href url #+ [string "View source code"]+ where+ url = samplesURL ++ "Chat.hs"
+ samples/CurrencyConverter.hs view
@@ -0,0 +1,39 @@+import Control.Monad (void)+import Text.Printf+import Safe (readMay)++import qualified Graphics.UI.Threepenny as UI+import Graphics.UI.Threepenny.Core++{-----------------------------------------------------------------------------+ Main+------------------------------------------------------------------------------}+main :: IO ()+main = startGUI defaultConfig setup++setup :: Window -> UI ()+setup window = void $ do+ return window # set title "Currency Converter"++ dollar <- UI.input+ euro <- UI.input+ + getBody window #+ [+ column [+ grid [[string "Dollar:", element dollar]+ ,[string "Euro:" , element euro ]]+ , string "Amounts update while typing."+ ]]++ euroIn <- stepper "0" $ UI.valueChange euro+ dollarIn <- stepper "0" $ UI.valueChange dollar+ let+ rate = 0.7 :: Double+ withString f = maybe "-" (printf "%.2f") . fmap f . readMay+ + dollarOut = withString (/ rate) <$> euroIn+ euroOut = withString (* rate) <$> dollarIn+ + element euro # sink value euroOut+ element dollar # sink value dollarOut+
+ samples/Data/List/Extra.hs view
@@ -0,0 +1,8 @@+module Data.List.Extra where++import Data.Char+ ( isSpace+ )++trim :: String -> String+trim = reverse . dropWhile isSpace . reverse . dropWhile isSpace
+ samples/DragNDropExample.hs view
@@ -0,0 +1,61 @@+import Control.Monad+import Data.IORef++import Paths++import qualified Graphics.UI.Threepenny as UI+import Graphics.UI.Threepenny.Core++{-----------------------------------------------------------------------------+ Drag'N'Drop example+------------------------------------------------------------------------------}+main :: IO ()+main = do+ static <- getStaticDir+ startGUI defaultConfig { jsStatic = Just static } setup++setup :: Window -> UI ()+setup w = void $ do+ return w # set title "Drag 'N' Drop Example"+ UI.addStyleSheet w "DragNDropExample.css"+ + pairs <- sequence $+ zipWith mkDragPair (words "red green blue") (map (150*) [0..2])+ getBody w #+ concat [[element i, element o] | (i,o) <- pairs]++type Color = String++mkDragPair :: Color -> Int -> UI (Element, Element)+mkDragPair color position = do+ elDrag <- UI.new #. "box-drag"+ # set UI.style [("left", show position ++ "px"), ("color",color)]+ # set text "Drag me!"+ # set UI.draggable True+ # set UI.dragData color++ elDrop <- UI.new #. "box-drop"+ # set UI.style [("border","2px solid " ++ color), ("left", show position ++ "px")]+++ dropSuccess <- liftIO $ newIORef False++ on UI.dragStart elDrag $ \_ -> void $+ element elDrop+ # set text "Drop here!"+ # set UI.droppable True+ on UI.dragEnd elDrag $ \_ -> void $ do+ dropped <- liftIO $ readIORef dropSuccess+ when (not dropped) $ void $+ element elDrop+ # set text ""+ # set UI.droppable False++ on UI.drop elDrop $ \color' -> when (color == color') $ void $ do+ liftIO $ writeIORef dropSuccess True+ delete elDrag+ element elDrop+ # set text "Dropped!"+ # set UI.droppable False+ + return (elDrag, elDrop)+
+ samples/DrumMachine.hs view
@@ -0,0 +1,92 @@+import Control.Monad++import Paths++import qualified Graphics.UI.Threepenny as UI+import Graphics.UI.Threepenny.Core++{-----------------------------------------------------------------------------+ Configuration+------------------------------------------------------------------------------}+bars, beats, defaultBpm :: Int+bars = 4+beats = 4+defaultBpm = 120++bpm2ms :: Int -> Int+bpm2ms bpm = ceiling $ 1000*60 / (fromIntegral bpm :: Double)++-- NOTE: Samples taken from "conductive-examples"++instruments = words "kick snare hihat"++loadInstrumentSample name = return $ "static/" ++ name ++ ".wav"++{-----------------------------------------------------------------------------+ Main+------------------------------------------------------------------------------}+main :: IO ()+main = do+ static <- getStaticDir+ startGUI defaultConfig { jsStatic = Just static } setup++setup :: Window -> UI ()+setup w = void $ do+ return w # set title "Ha-ha-ha-ks-ks-ks-ha-ha-ha-ell-ell-ell"++ elBpm <- UI.input # set value (show defaultBpm)+ elTick <- UI.span+ (kit, elInstruments) <- mkDrumKit+ let status = grid [[UI.string "BPM:" , element elBpm]+ ,[UI.string "Beat:", element elTick]]+ getBody w #+ [UI.div #. "wrap" #+ (status : map element elInstruments)]++ timer <- UI.timer # set UI.interval (bpm2ms defaultBpm)+ eBeat <- accumE (0::Int) $+ (\beat -> (beat + 1) `mod` (beats * bars)) <$ UI.tick timer+ void . onEvent eBeat $ \beat -> do+ -- display beat count+ element elTick # set text (show $ beat + 1)+ -- play corresponding sounds+ sequence_ $ map (!! beat) kit++ -- allow user to set BPM+ on UI.keydown elBpm $ \keycode -> when (keycode == 13) $ void $ do+ bpm <- read <$> get value elBpm+ return timer # set UI.interval (bpm2ms bpm)++ -- start the timer+ UI.start timer+++type Kit = [Instrument]+type Instrument = [Beat]+type Beat = UI () -- play the corresponding sound++mkDrumKit :: UI (Kit, [Element])+mkDrumKit = unzip <$> mapM mkInstrument instruments++mkInstrument :: String -> UI (Instrument, Element)+mkInstrument name = do+ elCheckboxes <-+ sequence $ replicate bars $+ sequence $ replicate beats $+ UI.input # set UI.type_ "checkbox"++ url <- loadInstrumentSample name+ elAudio <- UI.audio # set (attr "preload") "1" # set UI.src url++ let play box = do+ checked <- get UI.checked box+ when checked $ do+ runFunction $ ffi "%1.pause()" elAudio+ runFunction $ ffi "%1.currentTime = 0" elAudio+ runFunction $ ffi "%1.play()" elAudio+ beats = map play . concat $ elCheckboxes+ elGroups = [UI.span #. "bar" #+ map element bar | bar <- elCheckboxes]++ elInstrument <- UI.div #. "instrument"+ #+ (element elAudio : UI.string name : elGroups)++ return (beats, elInstrument)+
+ samples/Paths.hs view
@@ -0,0 +1,24 @@+{-# LANGUAGE CPP#-}+module Paths (getStaticDir, samplesURL) where++import Control.Monad+import System.FilePath++#if defined(CABAL)+-- using cabal+import qualified Paths_threepenny_gui (getDataDir)++getStaticDir :: IO FilePath+getStaticDir = (</> "samples/static") `liftM` Paths_threepenny_gui.getDataDir++#else+-- using GHCi++getStaticDir :: IO FilePath+getStaticDir = return "static"++#endif++-- | Base URL for the example source code.+samplesURL :: String+samplesURL = "https://github.com/HeinrichApfelmus/threepenny-gui/blob/master/samples/"
+ samples/README.md view
@@ -0,0 +1,47 @@+## Examples++This folder contains the code examples for the Threepenny GUI library.++To run the examples, you can (but don't have to) use the provided `runhaskell` and `ghci` scripts:++ cd samples+ ./runhaskell Chat.hs++These scripts check whether you have set up a [cabal sandbox][] or, alternatively, the [stack utility][stack] for use with this library, and uses these instead of the global package database; this is very useful for me as a library author.++(To set up the examples with `stack`, do the following++ git clone https://github.com/HeinrichApfelmus/threepenny-gui.git+ cd threepenny-gui/+ stack init+ stack setup+ stack build+ cd samples+ ./runhaskell Mouse.hs++)++After you have started an example, open your web browser and point it to the address [http://localhost:8023](http://localhost:8023). Enjoy!++ [stack]: http://haskellstack.org+ [cabal sandbox]: http://coldwa.st/e/blog/2013-08-20-Cabal-sandbox.html++Here a description of the currently maintained examples.++* [BarTab.hs](BarTab.hs) — Dynamic creation of widgets.+* [Buttons.hs](Buttons.hs) — Simple buttons to click on.+* [Canvas.hs](Canvas.hs) — Graphics using the HTML 5 canvas element.+* [Chat.hs](Chat.hs) — Multi-user chat.+* [CRUD.hs](CRUD.hs) — A CRUD example showcasing functional reactice programming (FRP).+* [CurrencyConverter.hs](CurrencyConverter.hs) — Simple demonstration of two reactive input elements.+* [DragNDropExample.hs](DragNDropExample.hs) — Simple drag'N'drop demo.+* [DrumMachine.hs](DrumMachine.hs) — Specify rhythm by activating and deactivating checkboxes.+* [FadeInFadeOut.hs](FadeInFadeOut.hs) — Calling Haskell functions from the browser.+* [GetElementsBy.hs](GetElementsBy.hs) — Get elements by tag name or ID.+* [Mouse.hs](Mouse.hs) — Test of the `mousemove` event.+* [Svg.hs](Svg.hs) — Test of the SVG elements.++There are also a few other examples that are used for testing or that demonstrate library internals. They won't be maintained.++* [WorkaroundFastEvent.hs](WorkaroundFastEvent.hs) — Demonstrates how to register an event handler without requiring a response from the browser window.+
+ samples/Svg.hs view
@@ -0,0 +1,44 @@+import Control.Monad (void)++import qualified Graphics.UI.Threepenny as UI+import Graphics.UI.Threepenny.Core+import qualified Graphics.UI.Threepenny.SVG as SVG++{-----------------------------------------------------------------------------+ SVG+------------------------------------------------------------------------------}+main :: IO ()+main = startGUI defaultConfig setup++setup :: Window -> UI ()+setup w = void $ do+ return w # set title "SVG"++ heading <- UI.h1 # set text "SVG Two Ways"++ getBody w #+ [element heading+ , UI.div #+ [svgElems, UI.h3 # set text "Individual SVG elements in Haskell"]+ , UI.div # set html strCircle #+ [UI.h3 # set text "SVG block as a Haskell string"]+ ]+++svgElems :: UI Element+svgElems = do+ context <- SVG.svg+ # set SVG.width "150"+ # set SVG.height "100"+ elemCircle <- SVG.circle+ # set SVG.cx "100"+ # set SVG.cy "50"+ # set SVG.r "40"+ # set SVG.stroke "green"+ # set SVG.stroke_width "4"+ # set SVG.fill "yellow"+ return context #+ [element elemCircle]+++strCircle :: String+strCircle = "<svg width=\"150\" height=\"100\">"+ ++ " <circle cx=\"100\" cy=\"50\" r=\"40\" stroke=\"gray\" stroke-width=\"4\" fill=\"orange\" />"+ ++ "</svg>"+
+ samples/static/chat.html view
@@ -0,0 +1,16 @@+<!doctype html>+<head>+ <title>Chat</title>+ <link rel="stylesheet" type="text/css" href="static/css/chat.css"/>+ <link href='http://fonts.googleapis.com/css?family=Fredericka+the+Great'+ rel='stylesheet' type='text/css'>+ <link href='http://fonts.googleapis.com/css?family=Annie+Use+Your+Telescope' rel='stylesheet' type='text/css'>+ <script src="haskell.js"></script>+ <script type="text/javascript" charset="utf-8">+ Haskell.initFFI();+ </script>+</head>+<body>+ <noscript>Please enable JavaScript.</noscript>+</body>+</html>
+ samples/static/css/DragNDropExample.css view
@@ -0,0 +1,18 @@+.box-drag {+ position: absolute;+ top : 20px;+ border: 1px solid gray;+ width : 90px;+ height: 2ex;+ text-align: center;+ padding: 1ex;+}++.box-drop {+ position: absolute;+ top : 100px;+ width : 100px;+ height: 2.2ex;+ text-align: center;+ padding: 1ex;+}
+ samples/static/css/buttons.css view
@@ -0,0 +1,52 @@+body {+ background: #333;+ color: #ccc;+ font-family: ubuntu, sans-serif;+ font-size: 14px;+ background: url(stripes-bg.png);+ padding-top: 1em;+ text-shadow: #000 3px 1px 3px;+}+.wrap {+ width: 40em;+ margin: auto;+}+.buttons-list {+ color: #aaa;+}+.button {+ cursor: pointer;+ color: #acc2a1;+ text-decoration: underline;+}+a {+ color: #acc2a1;+}+button, input {+ border: 0;+ color: #ccc;+ padding: 0.3em;+ border-radius:0.3em;+ box-shadow: #191919 2px 1px 2px;+}+input {+ background: #292929;+ width: 2em;+}+button:focus,input:focus {+ background: #222;+}+button {+ background: #444;+}+h1 {+ font-size: 4em;+ margin: 0;+ padding-bottom: 0.2em;+}+h2 {+ margin-top: 1.3em;+ padding-bottom: 0em;+ margin-bottom:0;+}+.view-source { float: right }
+ samples/static/css/chat.css view
@@ -0,0 +1,106 @@+body {+ background: #333;+ color: #ccc;+ font-family: 'Annie Use Your Telescope';+ font-size: 26px;+ text-shadow: #000 3px 1px 3px;+ padding:0;+ margin:0;+ background: url(wood-bg.png) #333;+}+.header {+ font-family: 'Fredericka the Great', cursive;+ font-size: 50px;+ color: #ad1c43;+ float: left;+ padding: 10px 20px;+ position:absolute;+ z-index:2;+}+.message-area {+ clear:both;+ padding: 20px;+ color: #fff;+ position: absolute;+ top: 1em;+ right:0;+ left:0;+ bottom:10%;+ overflow: auto;+}+.gradient {+ background: url(fade.png) repeat-x;+ position:absolute;+ top:0;+ height: 150px;+ left:0;+ right:0;+ z-index:1;+}+.send-area {+ z-index:0;+ position:fixed;+ top:90%;+ left:0;+ right:0;+ bottom:0;+ background:#fff;+}+.send-textarea {+ padding: 0.25em;+ border:0;+ position: absolute;+ left:0;+ width:100%;+ bottom:0;+ top:-1px;+ font-family: 'Annie Use Your Telescope';+ font-size: 25px;+ background: #111;+ color: #999;+}+.message {+ margin-top: 1em;+ border-top: 0.1em dotted #333;+}+.message .timestamp {+ color: #888;+ font-size: 15px;+ display: inline;+ float: right;+}+.message .name {+ display: inline-block;+ font-size: 18px;+ font-style: italic;+ float:left;+}+.message .content {+ display:block;+ clear:both;+}+.name-area {+ position: absolute;+ top: 20px;+ right: 20px;+ z-index: 3;+}+.name-input {+ background: #111;+ border: 0;+ border-radius: 0.3em;+ font-family: 'Annie Use Your Telescope';+ color: #777;+ font-size: 20px;+ padding-left: 0.2em;+}+.name-input:focus,.send-textarea:focus {+ background: #333;+}+.view-source {+ position: absolute;+ z-index: 4;+ right: 20px;+ color: #555;+ top: 100px;+}
+ samples/static/css/fade.png view
binary file changed (absent → 4690 bytes)
+ samples/static/css/stripes-bg.png view
binary file changed (absent → 162 bytes)
+ samples/static/css/wood-bg.png view
binary file changed (absent → 628939 bytes)
+ samples/static/haskell-logo.png view
binary file changed (absent → 4094 bytes)
+ samples/static/hihat.wav view
binary file changed (absent → 28556 bytes)
+ samples/static/kick.wav view
binary file changed (absent → 31622 bytes)
+ samples/static/snare.wav view
binary file changed (absent → 76332 bytes)
− src/BarTab.hs
@@ -1,87 +0,0 @@-{-# LANGUAGE CPP, PackageImports #-}-{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}--import Control.Applicative-import Control.Monad-import Data.IORef-import Data.Maybe--import Paths--#ifdef CABAL-import qualified "threepenny-gui" Graphics.UI.Threepenny as UI-import "threepenny-gui" Graphics.UI.Threepenny.Core-#else-import qualified Graphics.UI.Threepenny as UI-import Graphics.UI.Threepenny.Core-#endif---- | Main entry point. Starts a TP server.-main :: IO ()-main = do- static <- getStaticDir- startGUI Config- { tpPort = 10000- , tpCustomHTML = Nothing- , tpStatic = static- } setup--setup :: Window -> IO ()-setup w = do- -- active elements- return w # set title "BarTab"-- elAdd <- UI.button # set UI.text "Add"- elRemove <- UI.button # set UI.text "Remove"- elResult <- UI.span-- inputs <- newIORef []- - -- functionality- let- displayTotal = void $ do- xs <- getValuesList =<< readIORef inputs- element elResult # set text (showNumber . sum $ map readNumber xs)- - redoLayout :: IO ()- redoLayout = void $ do- layout <- mkLayout =<< readIORef inputs- getBody w # set children [layout]- displayTotal-- mkLayout :: [Element] -> IO Element- mkLayout xs = column $- [row [element elAdd, element elRemove]- ,UI.hr]- ++ map element xs ++- [UI.hr- ,row [UI.span # set text "Sum: ", element elResult]- ]- - addInput :: IO ()- addInput = do- elInput <- UI.input # set value "0"- on (domEvent "livechange") elInput $ \_ -> displayTotal- modifyIORef inputs (elInput:)- - on UI.click elAdd $ \_ -> addInput >> redoLayout- on UI.click elRemove $ \_ -> modifyIORef inputs (drop 1) >> redoLayout- addInput >> redoLayout---{------------------------------------------------------------------------------ Functionality-------------------------------------------------------------------------------}-type Number = Maybe Double--instance Num Number where- (+) = liftA2 (+)- (-) = liftA2 (-)- (*) = liftA2 (*)- abs = fmap abs- signum = fmap signum- fromInteger = pure . fromInteger--readNumber :: String -> Number-readNumber s = listToMaybe [x | (x,"") <- reads s] -showNumber = maybe "--" show
− src/Buttons.hs
@@ -1,85 +0,0 @@-{-# LANGUAGE CPP, PackageImports #-}--import Control.Monad-import Control.Concurrent (threadDelay)--#ifdef CABAL-import qualified "threepenny-gui" Graphics.UI.Threepenny as UI-import "threepenny-gui" Graphics.UI.Threepenny.Core-#else-import qualified Graphics.UI.Threepenny as UI-import Graphics.UI.Threepenny.Core-#endif-import Paths--{------------------------------------------------------------------------------ Buttons-------------------------------------------------------------------------------}--main :: IO ()-main = do- static <- getStaticDir- startGUI Config- { tpPort = 10000- , tpCustomHTML = Nothing- , tpStatic = static- } setup--setup :: Window -> IO ()-setup w = void $ do- return w # set title "Buttons"- UI.addStyleSheet w "buttons.css"-- buttons <- mkButtons- getBody w #+- [UI.div #. "wrap" #+ (greet ++ map element buttons ++ [viewSource])]--greet :: [IO Element]-greet =- [ UI.h1 #+ [string "Hello, Haskell!"]- , UI.div #+ [string "Try the buttons below, they hover and click."]- ]---mkButton :: String -> IO (Element, Element)-mkButton title = do- button <- UI.button #. "button" #+ [string title]- view <- UI.p #+ [element button]- return (button, view)--mkButtons :: IO [Element]-mkButtons = do- list <- UI.ul #. "buttons-list"- - (button1, view1) <- mkButton button1Title- - on UI.hover button1 $ \_ -> do- element button1 # set text (button1Title ++ " [hover]")- on UI.leave button1 $ \_ -> do- element button1 # set text button1Title- on UI.click button1 $ \_ -> do- element button1 # set text (button1Title ++ " [pressed]")- threadDelay $ 1000 * 1000 * 1- element list #+ [UI.li # set html "<b>Delayed</b> result!"]- - (button2, view2) <- mkButton button2Title-- on UI.hover button2 $ \_ -> do- element button2 # set text (button2Title ++ " [hover]")- on UI.leave button2 $ \_ -> do- element button2 # set text button2Title- on UI.click button2 $ \_ -> do- element button2 # set text (button2Title ++ " [pressed]")- element list #+ [UI.li # set html "Zap! Quick result!"]- - return [list, view1, view2]-- where button1Title = "Click me, I delay a bit"- button2Title = "Click me, I work immediately"--viewSource :: IO Element-viewSource = UI.p #+- [UI.anchor #. "view-source" # set UI.href url #+ [string "View source code"]]- where- url = "https://github.com/HeinrichApfelmus/threepenny-gui/blob/master/src/Buttons.hs"-
− src/Chat.hs
@@ -1,108 +0,0 @@-{-# LANGUAGE CPP, PackageImports #-}-{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}--import Control.Concurrent-import qualified Control.Concurrent.Chan as Chan-import Control.Exception-import Control.Monad-import Data.Functor-import Data.List.Extra-import Data.Time-import Prelude hiding (catch)--import Control.Monad.Trans.Reader as Reader-import Control.Monad.IO.Class--#ifdef CABAL-import qualified "threepenny-gui" Graphics.UI.Threepenny as UI-import "threepenny-gui" Graphics.UI.Threepenny.Core hiding (text)-#else-import qualified Graphics.UI.Threepenny as UI-import Graphics.UI.Threepenny.Core hiding (text)-#endif-import Paths--{------------------------------------------------------------------------------ Chat-------------------------------------------------------------------------------}--main :: IO ()-main = do- static <- getStaticDir- messages <- Chan.newChan- startGUI Config- { tpPort = 10000- , tpCustomHTML = Just "chat.html"- , tpStatic = static- } $ setup messages--type Message = (UTCTime, String, String)--setup :: Chan Message -> Window -> IO ()-setup globalMsgs w = do- msgs <- Chan.dupChan globalMsgs-- return w # set title "Chat"- - (nick, nickname) <- mkNickname- messageArea <- mkMessageArea msgs nick-- getBody w #+- [ UI.div #. "header" #+ [string "Threepenny Chat"]- , UI.div #. "gradient"- , viewSource- , element nickname- , element messageArea- ]- - void $ forkIO $ receiveMessages w msgs messageArea---- io $ catch (runTP session handleEvents)--- (\e -> do killThread messageReceiver--- throw (e :: SomeException))--receiveMessages w msgs messageArea = do- messages <- Chan.getChanContents msgs- forM_ messages $ \msg -> do- atomic w $ do- element messageArea #+ [mkMessage msg]- UI.scrollToBottom messageArea--mkMessageArea :: Chan Message -> Element -> IO Element-mkMessageArea msgs nickname = do- input <- UI.textarea #. "send-textarea"- - on UI.sendValue input $ (. trim) $ \content -> do- when (not (null content)) $ do- now <- getCurrentTime- nick <- trim <$> get value nickname- element input # set value ""- when (not (null nick)) $- Chan.writeChan msgs (now,nick,content)-- UI.div #. "message-area" #+ [UI.div #. "send-area" #+ [element input]]---mkNickname :: IO (Element, Element)-mkNickname = do- i <- UI.input #. "name-input"- el <- UI.div #. "name-area" #+- [ UI.span #. "name-label" #+ [string "Your name "]- , element i- ]- UI.setFocus i- return (i,el)--mkMessage :: Message -> IO Element-mkMessage (timestamp, nick, content) =- UI.div #. "message" #+- [ UI.div #. "timestamp" #+ [string $ show timestamp]- , UI.div #. "name" #+ [string $ nick ++ " says:"]- , UI.div #. "content" #+ [string content]- ]--viewSource :: IO Element-viewSource =- UI.anchor #. "view-source" # set UI.href url #+ [string "View source code"]- where- url = "https://github.com/HeinrichApfelmus/threepenny-gui/blob/master/src/Chat.hs"
− src/Control/Concurrent/Chan/Extra.hs
@@ -1,22 +0,0 @@--- | Extra utilities for chans.--module Control.Concurrent.Chan.Extra- (readAvailableChan)- where--import Control.Concurrent.Chan--readAvailableChan :: Chan a -> IO [a]-readAvailableChan chan = do- v <- readChan chan- vs <- rest- return (v:vs)-- where- rest = do- empty <- isEmptyChan chan- if empty- then return []- else do v <- readChan chan- vs <- rest- return (v:vs)
− src/Control/Concurrent/Delay.hs
@@ -1,17 +0,0 @@--- | Delay a thread for n seconds or minutes.--module Control.Concurrent.Delay- (delaySeconds- ,delayMinutes)- where--import Control.Concurrent---- | Delay the current thread for at least n seconds.-delaySeconds :: Integer -> IO ()-delaySeconds 0 = return ()-delaySeconds n = do threadDelay (1000 * 1000); delaySeconds (n-1)---- | Delay the current thread for at least n minutes.-delayMinutes :: Integer -> IO ()-delayMinutes = delaySeconds . (*60)
− src/Control/Event.hs
@@ -1,111 +0,0 @@-module Control.Event (- -- * Synopsis- -- | Event-driven programming in the imperative style.- - -- * Documentation- Handler, Event(..),- mapIO, filterIO, filterJust,- newEvent, newEventsTagged- ) where---import Data.IORef-import qualified Data.Unique -- ordinary uniques here, because they are Ord--import qualified Data.Map as Map--type Map = Map.Map--{------------------------------------------------------------------------------ Types-------------------------------------------------------------------------------}--- | An /event handler/ is a function that takes an--- /event value/ and performs some computation.-type Handler a = a -> IO ()----- | An /event/ is a facility for registering--- event handlers. These will be called whenever the event occurs.--- --- When registering an event handler, you will also be given an action--- that unregisters this handler again.--- --- > do unregisterMyHandler <- register event myHandler----newtype Event a = Event { register :: Handler a -> IO (IO ()) }--{------------------------------------------------------------------------------ Combinators-------------------------------------------------------------------------------}-instance Functor Event where- fmap f = mapIO (return . f)---- | Map the event value with an 'IO' action.-mapIO :: (a -> IO b) -> Event a -> Event b-mapIO f e = Event $ \h -> register e $ \x -> f x >>= h ---- | Filter event values that don't return 'True'.-filterIO :: (a -> IO Bool) -> Event a -> Event a-filterIO f e = Event $ \h ->- register e $ \x -> f x >>= \b -> if b then h x else return ()---- | Keep only those event values that are of the form 'Just'.-filterJust :: Event (Maybe a) -> Event a-filterJust e = Event $ \g -> register e (maybe (return ()) g)---{------------------------------------------------------------------------------ Construction-------------------------------------------------------------------------------}--- | Build a facility to register and unregister event handlers.--- Also yields a function that takes an event handler and runs all the registered--- handlers.------ Example:------ > do--- > (event, fire) <- newEvent--- > register event (putStrLn)--- > fire "Hello!"-newEvent :: IO (Event a, a -> IO ())-newEvent = do- handlers <- newIORef Map.empty- let register handler = do- key <- Data.Unique.newUnique- atomicModifyIORef_ handlers $ Map.insert key handler- return $ atomicModifyIORef_ handlers $ Map.delete key- runHandlers a =- mapM_ ($ a) . map snd . Map.toList =<< readIORef handlers- return (Event register, runHandlers)---- | Build several 'Event's from case analysis on a tag.--- Generalization of 'newEvent'.-newEventsTagged :: Ord tag => IO (tag -> Event a, (tag, a) -> IO ())-newEventsTagged = do- handlersRef <- newIORef Map.empty -- :: Map key (Map Unique (Handler a))-- let register tag handler = do- -- new identifier for this handler- uid <- Data.Unique.newUnique- -- add handler to map at key- atomicModifyIORef_ handlersRef $- Map.alter (Just . Map.insert uid handler . maybe Map.empty id) tag- -- remove handler from map at key- return $ atomicModifyIORef_ handlersRef $- Map.adjust (Map.delete uid) tag-- let runHandlers (tag,a) = do- handlers <- readIORef handlersRef- case Map.lookup tag handlers of- Just hs -> mapM_ ($ a) (Map.elems hs)- Nothing -> return ()-- return (\tag -> Event (register tag), runHandlers)- -{------------------------------------------------------------------------------ Utilities-------------------------------------------------------------------------------}-atomicModifyIORef_ ref f = atomicModifyIORef ref $ \x -> (f x, ())---
− src/Control/Monad/Extra.hs
@@ -1,30 +0,0 @@--- | Extra utilities for monads.--module Control.Monad.Extra where--import Data.Maybe---- | Ignore the given action's return.-ig :: (Monad m) => m a -> m ()-ig m = m >> return ()---- | A non-operator version of (=<<).-bind :: (Monad m) => (a -> m b) -> m a -> m b-bind = flip (>>=)---- | When the value is Just, run the action.-whenJust :: Monad m => Maybe a -> (a -> m ()) -> m ()-whenJust (Just a) m = m a-whenJust Nothing _ = return ()---- | Wrap up a functor in a Maybe.-just :: Functor m => m a -> m (Maybe a)-just = fmap Just---- | Flip mapMaybe.-forMaybe :: [a] -> (a -> Maybe b) -> [b]-forMaybe = flip mapMaybe---- | Monadic version of maybe.-maybeM :: (Monad m) => a -> (a1 -> m a) -> Maybe a1 -> m a-maybeM nil cons a = maybe (return nil) cons a
− src/Control/Monad/IO.hs
@@ -1,8 +0,0 @@-{-# OPTIONS -Wall #-}--module Control.Monad.IO where--import Control.Monad.Trans--io :: MonadIO m => IO a -> m a-io = liftIO
− src/Data/List/Extra.hs
@@ -1,8 +0,0 @@-module Data.List.Extra where- -import Data.List-import Data.Char-import Data.Maybe--trim :: String -> String-trim = reverse . dropWhile isSpace . reverse . dropWhile isSpace
− src/DragNDropExample.hs
@@ -1,73 +0,0 @@-{-# LANGUAGE CPP, PackageImports #-}--import Control.Applicative-import Control.Monad-import Data.IORef-import Data.Maybe--#ifdef CABAL-import qualified "threepenny-gui" Graphics.UI.Threepenny as UI-import "threepenny-gui" Graphics.UI.Threepenny.Core-#else-import qualified Graphics.UI.Threepenny as UI-import Graphics.UI.Threepenny.Core-#endif-import Paths--{------------------------------------------------------------------------------ Drag'N'Drop example-------------------------------------------------------------------------------}-main :: IO ()-main = do- static <- getStaticDir- startGUI Config- { tpPort = 10000- , tpCustomHTML = Nothing- , tpStatic = static- } setup--setup :: Window -> IO ()-setup w = void $ do- return w # set title "Drag 'N' Drop Example"- UI.addStyleSheet w "DragNDropExample.css"- - pairs <- sequence $- zipWith (mkDragPair w) (words "red green blue") (map (150*) [0..2])- getBody w #+ concat [[element i, element o] | (i,o) <- pairs]--type Color = String--mkDragPair :: Window -> Color -> Int -> IO (Element, Element)-mkDragPair w color position = do- elDrag <- UI.new #. "box-drag"- # set UI.style [("left", show position ++ "px"), ("color",color)]- # set text "Drag me!"- # set UI.draggable True- # set UI.dragData color-- elDrop <- UI.new #. "box-drop"- # set UI.style [("border","2px solid " ++ color), ("left", show position ++ "px")]--- dropSuccess <- newIORef False-- on UI.dragStart elDrag $ \_ -> void $- element elDrop- # set text "Drop here!"- # set UI.droppable True- on UI.dragEnd elDrag $ \_ -> void $ do- dropped <- readIORef dropSuccess- when (not dropped) $ void $- element elDrop- # set text ""- # set UI.droppable False-- on UI.drop elDrop $ \color' -> when (color == color') $ void $ do- writeIORef dropSuccess True- delete elDrag- element elDrop- # set text "Dropped!"- # set UI.droppable False- - return (elDrag, elDrop)-
− src/DrumMachine.hs
@@ -1,108 +0,0 @@-{-# LANGUAGE CPP, PackageImports #-}--import Control.Monad-import Data.IORef-import Data.Functor--#ifdef CABAL-import qualified "threepenny-gui" Graphics.UI.Threepenny as UI-import "threepenny-gui" Graphics.UI.Threepenny.Core-#else-import qualified Graphics.UI.Threepenny as UI-import Graphics.UI.Threepenny.Core-#endif-import Paths-import System.FilePath--{------------------------------------------------------------------------------ Configuration-------------------------------------------------------------------------------}-bars = 4-beats = 4-defaultBpm = 120--bpm2ms :: Int -> Int-bpm2ms bpm = ceiling $ 1000*60 / fromIntegral bpm---- NOTE: Samples taken from "conductive-examples"--instruments = words "kick snare hihat"--loadInstrumentSample w name = do- static <- getStaticDir- loadFile w "audio/wav" (static </> name <.> "wav")--{------------------------------------------------------------------------------ Main-------------------------------------------------------------------------------}-main :: IO ()-main = do- static <- getStaticDir- startGUI Config- { tpPort = 10000- , tpCustomHTML = Nothing- , tpStatic = static- } setup--setup :: Window -> IO ()-setup w = void $ do- return w # set title "Ha-ha-ha-ks-ks-ks-ha-ha-ha-ell-ell-ell"-- elBpm <- UI.input # set value (show defaultBpm)- elTick <- UI.span- (kit, elInstruments) <- mkDrumKit w- let status = grid [[UI.string "BPM:" , element elBpm]- ,[UI.string "Beat:", element elTick]]- getBody w #+ [UI.div #. "wrap" #+ (status : map element elInstruments)]- - timer <- UI.timer # set UI.interval (bpm2ms defaultBpm)- refBeat <- newIORef 0- - -- play sounds on timer events- on UI.tick timer $ const $ void $ do- -- get and increase beat count- beat <- readIORef refBeat- writeIORef refBeat $ (beat + 1) `mod` (beats * bars)- element elTick # set text (show $ beat + 1)- - -- play corresponding sounds- sequence_ $ map (!! beat) kit- - -- allow user to set BPM- on (domEvent "livechange") elBpm $ const $ void $ do- bpm <- read <$> get value elBpm- return timer # set UI.interval (bpm2ms bpm)- - -- star the timer- UI.start timer---type Kit = [Instrument]-type Instrument = [Beat]-type Beat = IO () -- play the corresponding sound--mkDrumKit :: Window -> IO (Kit, [Element])-mkDrumKit w = unzip <$> mapM (mkInstrument w) instruments--mkInstrument :: Window -> String -> IO (Instrument, Element)-mkInstrument window name = do- elCheckboxes <-- sequence $ replicate bars $- sequence $ replicate beats $- UI.input # set UI.type_ "checkbox"-- url <- loadInstrumentSample window name- elAudio <- UI.audio # set (attr "preload") "1" # set UI.src url-- let- play box = do- checked <- get UI.checked box- when checked $ audioPlay elAudio- beats = map play . concat $ elCheckboxes- elGroups = [UI.span #. "bar" #+ map element bar | bar <- elCheckboxes]- - elInstrument <- UI.div #. "instrument"- #+ (element elAudio : UI.string name : elGroups)- - return (beats, elInstrument)-
+ src/Foreign/JavaScript.hs view
@@ -0,0 +1,111 @@+{-# LANGUAGE RecordWildCards #-}+module Foreign.JavaScript (+ -- * Synopsis+ -- | A JavaScript foreign function interface (FFI).+ --+ -- This module implements a web server that communicates with+ -- a web browser and allows you to execute arbitrary JavaScript code on it.+ --+ -- NOTE: This module is used internally by the "Graphics.UI.Threepenny"+ -- library, but the types are /not/ compatible directly+ -- (although some escape hatches are provided).+ -- Use "Foreign.JavaScript" only if you want to roll your own+ -- interface to the web browser.++ -- * Server+ serve, defaultConfig, Config(+ jsPort, jsAddr+ , jsCustomHTML, jsStatic, jsLog+ , jsWindowReloadOnDisconnect, jsCallBufferMode+ , jsUseSSL),+ ConfigSSL (..),+ Server, MimeType, URI, loadFile, loadDirectory,+ Window, getServer, getCookies, root,++ -- * JavaScript FFI+ ToJS(..), FromJS, JSFunction, JSObject, JavaScriptException,+ FFI, ffi, runFunction, callFunction,+ NewJSObject, unsafeCreateJSObject,+ CallBufferMode(..), setCallBufferMode, getCallBufferMode, flushCallBuffer,+ IsHandler, exportHandler, onDisconnect,+ debug, timestamp,+ ) where++import Foreign.JavaScript.CallBuffer+import Foreign.JavaScript.EventLoop+import Foreign.JavaScript.Marshal+import Foreign.JavaScript.Server+import Foreign.JavaScript.Types+import Foreign.RemotePtr as Foreign++{-----------------------------------------------------------------------------+ Server+------------------------------------------------------------------------------}+-- | Run a "Foreign.JavaScript" server.+serve+ :: Config -- ^ Configuration options.+ -> (Window -> IO ()) -- ^ Initialization whenever a client connects.+ -> IO ()+serve config initialize = httpComm config $ eventLoop $ \w -> do+ setCallBufferMode w (jsCallBufferMode config)+ runFunction w $+ ffi "connection.setReloadOnDisconnect(%1)" $ jsWindowReloadOnDisconnect config+ flushCallBuffer w -- make sure that all `runEval` commands are executed+ initialize w+ flushCallBuffer w -- make sure that all `runEval` commands are executed++{-----------------------------------------------------------------------------+ JavaScript+------------------------------------------------------------------------------}+-- | Run a JavaScript function, but do not wait for a result.+--+-- NOTE: The JavaScript function need not be executed immediately,+-- it can be buffered and sent to the browser window at a later time.+-- See 'setCallBufferMode' and 'flushCallBuffer' for more.+runFunction :: Window -> JSFunction () -> IO ()+runFunction w f = bufferRunEval w =<< toCode f++-- | Run a JavaScript function that creates a new object.+-- Return a corresponding 'JSObject' without waiting for the browser+-- to send a result.+--+-- WARNING: This function assumes that the supplied JavaScript code does,+-- in fact, create an object that is new.+unsafeCreateJSObject :: Window -> JSFunction NewJSObject -> IO JSObject+unsafeCreateJSObject w f = do+ g <- wrapImposeStablePtr w f+ bufferRunEval w =<< toCode g+ marshalResult g w err+ where+ err = error "unsafeCreateJSObject: marshal does not take arguments"++-- | Call a JavaScript function and wait for the result.+callFunction :: Window -> JSFunction a -> IO a+callFunction w f = do+ -- FIXME: Add the code of f to the buffer as well!+ -- However, we have to pay attention to the semantics of exceptions in this case.+ flushCallBuffer w++ resultJS <- callEval w =<< toCode f+ marshalResult f w resultJS++-- | Export a Haskell function as an event handler.+--+-- The result is a JavaScript @Function@ object that can be called+-- from JavaScript like a regular function. However,+-- the corresponding Haskell function will /not/ be run immediately,+-- rather it will be added to the event queue and processed+-- like an event. In other words, this the Haskell code is only called+-- asynchronously.+--+-- WARNING: The event handler will be garbage collected unless you+-- keep a reference to it /on the Haskell side/!+-- Registering it with a JavaScript function will generally /not/+-- keep it alive.+exportHandler :: IsHandler a => Window -> a -> IO JSObject+exportHandler w f = do+ g <- newHandler w (\args -> handle f w args >> flushCallBuffer w)+ h <- unsafeCreateJSObject w $+ ffi "Haskell.newEvent(%1,%2)" g (convertArguments f)+ Foreign.addReachable h g+ return h
+ src/Foreign/JavaScript/CallBuffer.hs view
@@ -0,0 +1,53 @@+{-# LANGUAGE RecordWildCards #-}+module Foreign.JavaScript.CallBuffer where++import Control.Concurrent.STM as STM+import Control.Monad++import Foreign.JavaScript.Types++{-----------------------------------------------------------------------------+ Call Buffer+------------------------------------------------------------------------------}+-- | Set the call buffering mode for the given browser window.+setCallBufferMode :: Window -> CallBufferMode -> IO ()+setCallBufferMode w new =+ flushCallBufferWithAtomic w $ writeTVar (wCallBufferMode w) new++-- | Get the call buffering mode for the given browser window.+getCallBufferMode :: Window -> IO CallBufferMode+getCallBufferMode Window{..} = atomically $ readTVar wCallBufferMode++-- | Flush the call buffer,+-- i.e. send all outstanding JavaScript to the client in one single message.+flushCallBuffer :: Window -> IO ()+flushCallBuffer w = flushCallBufferWithAtomic w $ return ()++-- | Flush the call buffer, and atomically perform an additional action+flushCallBufferWithAtomic :: Window -> STM a -> IO a+flushCallBufferWithAtomic Window{..} action = do+ -- by taking the call buffer, we ensure that no further code+ -- is added to the buffer while we execute the current buffer's code.+ code' <- atomically $ takeTMVar wCallBuffer+ let code = code' ""+ unless (null code) $ runEval code+ atomically $ do+ putTMVar wCallBuffer id+ action++-- | Schedule a piece of JavaScript code to be run with `runEval`,+-- depending on the buffering mode+bufferRunEval :: Window -> String -> IO ()+bufferRunEval Window{..} code = do+ action <- atomically $ do+ mode <- readTVar wCallBufferMode+ case mode of+ NoBuffering -> do+ return $ Just code+ _ -> do+ msg <- takeTMVar wCallBuffer+ putTMVar wCallBuffer (msg . (\s -> ";" ++ code ++ s))+ return Nothing+ case action of+ Nothing -> return ()+ Just code1 -> runEval code1
+ src/Foreign/JavaScript/EventLoop.hs view
@@ -0,0 +1,202 @@+{-# LANGUAGE RecordWildCards, CPP #-}+{-# LANGUAGE RecursiveDo #-}+module Foreign.JavaScript.EventLoop (+ eventLoop,+ runEval, callEval, debug, onDisconnect,+ newHandler, fromJSStablePtr, newJSObjectFromCoupon+ ) where++import Control.Concurrent+import Control.Concurrent.Async+import Control.Concurrent.STM as STM+import Control.DeepSeq (deepseq)+import Control.Exception as E+import Control.Monad+import qualified Data.Aeson as JSON+import qualified Data.ByteString.Char8 as BS+import qualified Data.Text as T++import Foreign.RemotePtr as Foreign+import Foreign.JavaScript.CallBuffer+import Foreign.JavaScript.Types++rebug :: IO ()+#ifdef REBUG+rebug = System.Mem.performGC+#else+rebug = return ()+#endif++{-----------------------------------------------------------------------------+ Event Loop+------------------------------------------------------------------------------}+-- | Handle a single event+handleEvent :: Window -> (Coupon, JSON.Value) -> IO ()+handleEvent Window{..} (name, args) = do+ mhandler <- Foreign.lookup name wEventHandlers+ case mhandler of+ Nothing -> return ()+ Just f -> withRemotePtr f (\_ g -> g args)+++type Result = Either String JSON.Value++-- | Event loop for a browser window.+-- Supports concurrent invocations of `runEval` and `callEval`.+eventLoop :: (Window -> IO void) -> EventLoop+eventLoop initialize server info comm = void $ do+ -- To support concurrent FFI calls, we need three threads.+ -- A fourth thread supports + --+ -- The thread `multiplexer` reads from the client and+ -- sorts the messages into the appropriate queue.+ events <- newTQueueIO+ results <- newTQueueIO :: IO (TQueue Result)+ -- The thread `handleCalls` executes FFI calls+ -- from the Haskell side in order.+ -- The corresponding queue records `TMVar`s in which to put the results.+ calls <- newTQueueIO :: IO (TQueue (Maybe (TMVar Result), ServerMsg))+ -- The thread `handleEvents` handles client Events in order.++ -- We only want to make an FFI call when the connection browser<->server is open+ -- Otherwise, throw an exception.+ let atomicallyIfOpen stm = do+ r <- atomically $ do+ b <- readTVar $ commOpen comm+ if b then fmap Right stm else return (Left ())+ case r of+ Right a -> return a+ Left _ -> throwIO $ ErrorCall "Foreign.JavaScript: Browser <-> Server communication broken."++ -- FFI calls are made by writing to the `calls` queue.+ let run msg = msg `deepseq` do -- see [ServerMsg strictness]+ atomicallyIfOpen $ writeTQueue calls (Nothing , msg)+ call msg = msg `deepseq` do -- see [ServerMsg strictness]+ ref <- newEmptyTMVarIO+ atomicallyIfOpen $ writeTQueue calls (Just ref, msg)+ er <- atomicallyIfOpen $ takeTMVar ref+ case er of+ Left e -> E.throwIO $ JavaScriptException e+ Right x -> return x+ debug s = s `deepseq` do -- see [ServerMsg strictness]+ atomicallyIfOpen $ writeServer comm $ Debug s++ -- We also send a separate event when the client disconnects.+ disconnect <- newTVarIO $ return ()+ -- FIXME: Make it possible to store *multiple* event handlers+ let onDisconnect m = atomically $ writeTVar disconnect m++ w0 <- newPartialWindow+ let w = w0 { getServer = server+ , getCookies = info+ , runEval = run . RunEval+ , callEval = call . CallEval+ , debug = debug+ , timestamp = run Timestamp+ , onDisconnect = onDisconnect+ }++ -- The individual threads are as follows:+ --+ -- Read client messages and send them to the+ -- thread that handles events or the thread that handles FFI calls.+ let multiplexer = forever $ atomically $ do+ msg <- readClient comm+ case msg of+ Event x y -> writeTQueue events (x,y)+ Result x -> writeTQueue results (Right x)+ Exception e -> writeTQueue results (Left e)++ -- Send FFI calls to client and collect results+ let handleCalls = forever $ do+ mref <- atomically $ do+ (mref, msg) <- readTQueue calls+ writeServer comm msg+ return mref+ atomically $+ case mref of+ Just ref -> do+ result <- readTQueue results+ putTMVar ref result+ Nothing -> return ()++ -- Receive events from client and handle them in order.+ let handleEvents = do+ me <- atomically $ do+ open <- readTVar $ commOpen comm+ if open+ then Just <$> readTQueue events+ else return Nothing -- channel is closed+ case me of+ Nothing -> return () -- channel is closed, we're done+ Just e -> do+ handleEvent w e+ `E.onException` commClose comm -- close channel in case of exception+ rebug+ handleEvents++ -- Execute an IO action, but also print any exceptions that it may throw.+ -- (The exception is rethrown.)+ let+ printException :: IO a -> IO a+ printException = E.handle $ \e -> do+ sLog server . BS.pack $ show (e :: E.SomeException)+ E.throwIO e++ -- NOTE: Due to an issue with `snap-server` library,+ -- we print the exception ourselves.+ printException $+ -- Wrap the main loop into `withRemotePtr` in order to keep the root alive.+ Foreign.withRemotePtr (wRoot w) $ \_ _ ->+ -- run `multiplexer` and `handleCalls` concurrently+ withAsync multiplexer $ \_ ->+ withAsync handleCalls $ \_ ->+ withAsync (flushCallBufferPeriodically w) $ \_ ->+ E.finally (initialize w >> handleEvents) $ do+ putStrLn "Foreign.JavaScript: Browser window disconnected."+ -- close communication channel if still necessary+ commClose comm+ -- trigger the `disconnect` event+ -- FIXME: Asynchronous exceptions should not be masked during the disconnect handler+ m <- atomically $ readTVar disconnect+ m++-- | Thread that periodically flushes the call buffer+flushCallBufferPeriodically :: Window -> IO ()+flushCallBufferPeriodically w =+ forever $ threadDelay (flushPeriod*1000) >> flushCallBuffer w+++{-----------------------------------------------------------------------------+ Exports, Imports and garbage collection+------------------------------------------------------------------------------}+-- | Turn a Haskell function into an event handler.+newHandler :: Window -> ([JSON.Value] -> IO ()) -> IO HsEvent+newHandler Window{..} handler = do+ coupon <- newCoupon wEventHandlers+ newRemotePtr coupon (handler . parseArgs) wEventHandlers+ where+ fromSuccess (JSON.Success x) = x+ -- parse a genuine JavaScript array+ parseArgs x = fromSuccess (JSON.fromJSON x) :: [JSON.Value]+ -- parse a JavaScript arguments object+ -- parseArgs x = Map.elems (fromSuccess (JSON.fromJSON x) :: Map.Map String JSON.Value)+++-- | Retrieve 'JSObject' associated with a JavaScript stable pointer.+fromJSStablePtr :: JSON.Value -> Window -> IO JSObject+fromJSStablePtr js w@(Window{..}) = do+ let JSON.Success coupon = JSON.fromJSON js+ mhs <- Foreign.lookup coupon wJSObjects+ case mhs of+ Just hs -> return hs+ Nothing -> newJSObjectFromCoupon w coupon++-- | Create a new JSObject by registering a new coupon.+newJSObjectFromCoupon :: Window -> Foreign.Coupon -> IO JSObject+newJSObjectFromCoupon w@(Window{..}) coupon = do+ ptr <- newRemotePtr coupon (JSPtr coupon) wJSObjects+ addFinalizer ptr $+ bufferRunEval w ("Haskell.freeStablePtr('" ++ T.unpack coupon ++ "')")+ return ptr+
+ src/Foreign/JavaScript/Include.hs view
@@ -0,0 +1,17 @@+{-# LANGUAGE TemplateHaskell, CPP #-}+module Foreign.JavaScript.Include (include) where++import Data.FileEmbed (makeRelativeToProject)+import Language.Haskell.TH+import System.IO++include :: FilePath -> Q Exp+include path = do+ relativePath <- makeRelativeToProject path+ LitE . StringL <$> runIO (readFileUTF8 relativePath)++readFileUTF8 :: FilePath -> IO String+readFileUTF8 path = do+ h <- openFile path ReadMode+ hSetEncoding h utf8+ hGetContents h
+ src/Foreign/JavaScript/Marshal.hs view
@@ -0,0 +1,251 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE FlexibleInstances, TypeSynonymInstances, ScopedTypeVariables #-}+{-# LANGUAGE RecordWildCards #-}+module Foreign.JavaScript.Marshal (+ ToJS(..), FromJS,+ FFI, JSFunction, toCode, marshalResult, ffi,+ IsHandler, convertArguments, handle,++ NewJSObject, wrapImposeStablePtr,+ ) where++import Data.Aeson as JSON+#if defined(CABAL)+#if MIN_VERSION_aeson(1,0,0)+import qualified Data.Aeson.Text as JSON (encodeToTextBuilder)+#else+import qualified Data.Aeson.Encode as JSON (encodeToTextBuilder)+#endif+#else+import qualified Data.Aeson.Text as JSON (encodeToTextBuilder)+#endif+import Data.List (intercalate)+import qualified Data.Text as T+import qualified Data.Text.Lazy+import qualified Data.Text.Lazy.Builder+import qualified Data.Vector as Vector+import Safe (atMay)++import Foreign.JavaScript.EventLoop (fromJSStablePtr, newJSObjectFromCoupon )+import Foreign.JavaScript.Types+import Foreign.RemotePtr++{-----------------------------------------------------------------------------+ Convert Haskell values to JavaScript values+------------------------------------------------------------------------------}+-- | JavaScript code snippet.+newtype JSCode = JSCode { unJSCode :: String }+ deriving (Eq, Ord, Show)++-- | Helper class for rendering Haskell values as JavaScript expressions.+class ToJS a where+ render :: a -> IO JSCode+ renderList :: [a] -> IO JSCode++ renderList xs = do+ ys <- mapM render xs+ jsCode $ "[" ++ intercalate "," (map unJSCode ys) ++ "]"++jsCode :: String -> IO JSCode+jsCode = return . JSCode++instance ToJS Float where render = render . JSON.toJSON+instance ToJS Double where render = render . JSON.toJSON+instance ToJS Int where render = jsCode . show+instance ToJS Bool where render b = jsCode $ if b then "true" else "false"+instance ToJS JSON.Value where render = jsCode . showJSON+instance ToJS T.Text where render = render . JSON.String+instance ToJS Char where+ render x = renderList [x]+ renderList = render . JSON.String . T.pack++instance ToJS a => ToJS [a] where+ render = renderList++instance ToJS HsEvent where+ render x = render =<< unprotectedGetCoupon x+instance ToJS JSObject where+ render x = apply1 "Haskell.deRefStablePtr(%1)"+ <$> (render =<< unprotectedGetCoupon x)++-- | Show a type in a JSON compatible way.+showJSON :: ToJSON a => a -> String+showJSON+ = Data.Text.Lazy.unpack+ . Data.Text.Lazy.Builder.toLazyText+ . JSON.encodeToTextBuilder . JSON.toJSON++{-----------------------------------------------------------------------------+ Convert JavaScript values to Haskell values+------------------------------------------------------------------------------}+data FromJS' a = FromJS'+ { wrapCode :: (JSCode -> JSCode)+ , marshal :: Window -> JSON.Value -> IO a+ }++-- | Helper class for converting JavaScript values to Haskell values.+class FromJS a where+ fromJS :: FromJS' a++-- | Marshal a simple type to Haskell.+simple :: FromJSON a => (JSCode -> JSCode) -> FromJS' a+simple f =+ FromJS' { wrapCode = f , marshal = \_ -> fromSuccessIO . JSON.fromJSON }+ where+ fromSuccessIO (JSON.Success a) = return a++instance FromJS String where fromJS = simple $ apply1 "%1.toString()"+instance FromJS T.Text where fromJS = simple $ apply1 "%1.toString()"+instance FromJS Int where fromJS = simple id+instance FromJS Double where fromJS = simple id+instance FromJS Float where fromJS = simple id+instance FromJS JSON.Value where fromJS = simple id++instance FromJS () where+ fromJS = FromJS' { wrapCode = id, marshal = \_ _ -> return () }++instance FromJS JSObject where+ fromJS = FromJS'+ { wrapCode = apply1 "Haskell.getStablePtr(%1)"+ , marshal = \w v -> fromJSStablePtr v w+ }++-- FIXME: Not sure whether this instance is really a good idea.+instance FromJS [JSObject] where+ fromJS = FromJS'+ { wrapCode = apply1 "Haskell.map(Haskell.getStablePtr, %1)"+ , marshal = \w (JSON.Array vs) -> do+ mapM (\v -> fromJSStablePtr v w) (Vector.toList vs)+ }++instance FromJS NewJSObject where+ fromJS = FromJS' { wrapCode = id, marshal = \_ _ -> return NewJSObject }++-- | Impose a JS stable pointer upon a newly created JavaScript object.+-- In this way, JSObject can be created without waiting for the browser+-- to return a result.+wrapImposeStablePtr :: Window -> JSFunction NewJSObject -> IO (JSFunction JSObject)+wrapImposeStablePtr (Window{..}) f = do+ coupon <- newCoupon wJSObjects+ rcoupon <- render coupon+ rcode <- code f+ return $ JSFunction+ { code = return $ apply "Haskell.imposeStablePtr(%1,%2)" [rcode, rcoupon]+ , marshalResult = \w _ -> newJSObjectFromCoupon w coupon+ }++{-----------------------------------------------------------------------------+ Variable argument JavaScript functions+------------------------------------------------------------------------------}+-- | A JavaScript function with a given output type @a@.+data JSFunction a = JSFunction+ { code :: IO JSCode+ -- ^ Code snippet that implements the function.+ , marshalResult :: Window -> JSON.Value -> IO a+ -- ^ Marshal the function result to a Haskell value.+ }++-- | Change the output type of a 'JSFunction'.+instance Functor JSFunction where+ fmap f (JSFunction c m) = JSFunction c (\w v -> fmap f $ m w v)++-- | Render function to a textual representation using JavaScript syntax.+toCode :: JSFunction a -> IO String+toCode = fmap unJSCode . code+++-- | Helper class for making 'ffi' a variable argument function.+class FFI a where+ fancy :: ([JSCode] -> IO JSCode) -> a++instance (ToJS a, FFI b) => FFI (a -> b) where+ fancy f a = fancy $ \xs -> do+ x <- render a+ f (x:xs)++instance FromJS b => FFI (JSFunction b) where+ fancy f = JSFunction+ { code = wrapCode b <$> f []+ , marshalResult = marshal b+ }+ where b = fromJS++-- | Simple JavaScript FFI with string substitution.+--+-- Inspired by the Fay language. <https://github.com/faylang/fay/wiki>+--+-- > example :: String -> Int -> JSFunction String+-- > example = ffi "$(%1).prop('checked',%2)"+--+-- The 'ffi' function takes a string argument representing the JavaScript+-- code to be executed on the client.+-- Occurrences of the substrings @%1@ to @%9@ will be replaced by+-- subequent arguments.+-- The substring @%%@ in the original will be replaced by @%@ (character escape).+--+-- Note: Always specify a type signature! The types automate+-- how values are marshalled between Haskell and JavaScript.+-- The class instances for the 'FFI' class show which conversions are supported.+--+ffi :: FFI a => String -> a+ffi macro = fancy (return . apply macro)++testFFI :: String -> Int -> JSFunction String+testFFI = ffi "$(%1).prop('checked',%2)"++{-----------------------------------------------------------------------------+ Type classes+------------------------------------------------------------------------------}+-- | Helper class for exporting Haskell functions to JavaScript+-- as event handlers.+class IsHandler a where+ convertArgs :: a -> Int -> [JSCode]+ handle :: a -> Window -> [JSON.Value] -> IO ()++instance (FromJS a, IsHandler b) => IsHandler (a -> b) where+ convertArgs = convertArgs'+ handle f = \w (a:as) -> do+ x <- marshal fromJS w a+ handle (f x) w as++convertArgs' :: forall a b. (FromJS a, IsHandler b) => (a -> b) -> Int -> [JSCode]+convertArgs' f n = wrap arg : convertArgs (f x) (n+1)+ where+ x = undefined :: a+ wrap = wrapCode (fromJS :: FromJS' a)+ arg = JSCode $ "arguments[" ++ show n ++ "]"++instance IsHandler (IO ()) where+ convertArgs _ _ = []+ handle m = \_ _ -> m++-- | Code needed to preconvert arguments on the JavaScript side.+convertArguments :: IsHandler a => a -> String+convertArguments f =+ "[" ++ intercalate "," (map unJSCode $ convertArgs f 0) ++ "]"+++{-----------------------------------------------------------------------------+ String utilities+------------------------------------------------------------------------------}+-- | String substitution.+-- Substitute occurences of %1, %2 up to %9 with the argument strings.+-- The types ensure that the % character has no meaning in the generated output.+--+-- > apply "%1 and %2" [x,y] = x ++ " and " ++ y+apply :: String -> [JSCode] -> JSCode+apply code args = JSCode $ go code+ where+ at xs i = maybe (error err) id $ atMay xs i+ err = "Graphics.UI.Threepenny.FFI: Too few arguments in FFI call!"+ argument i = unJSCode (args `at` i)++ go [] = []+ go ('%':'%':cs) = '%' : go cs+ go ('%':c :cs) = argument index ++ go cs+ where index = fromEnum c - fromEnum '1'+ go (c:cs) = c : go cs++-- | Apply string substitution that expects a single argument.+apply1 :: String -> JSCode -> JSCode+apply1 s x = apply s [x]
+ src/Foreign/JavaScript/Resources.hs view
@@ -0,0 +1,23 @@+{-# LANGUAGE TemplateHaskell #-}+module Foreign.JavaScript.Resources where++import Data.Text (Text)+import qualified Data.Text as Text+import Foreign.JavaScript.Include++jsDriverCode :: Text+jsDriverCode = Text.unlines $ map Text.pack+ [ $(include "js/lib/jquery.js")+ , $(include "js/lib/jquery-cookie.js")+ , "var Haskell = {};"+ , $(include "js/comm.js")+ , $(include "js/ffi.js")+ , $(include "js/lib.js")+ , $(include "js/log.js")+ ]++cssDriverCode :: Text+cssDriverCode = Text.pack $(include "js/haskell.css")++defaultHtmlFile :: Text+defaultHtmlFile = Text.pack $(include "js/index.html")
+ src/Foreign/JavaScript/Server.hs view
@@ -0,0 +1,192 @@+{-# LANGUAGE RecordWildCards, OverloadedStrings #-}+module Foreign.JavaScript.Server (+ httpComm, loadFile, loadDirectory,+ ) where++-- import general libraries+import Control.Concurrent+import Control.Concurrent.Async+import Control.Concurrent.STM as STM+import qualified Control.Exception as E+import Control.Monad+import Control.Monad.IO.Class+import Data.ByteString (ByteString)+import qualified Data.ByteString.Char8 as BS+import qualified Data.ByteString.Lazy.Char8 as LBS+import qualified Data.Map as M+import Data.Text (Text)+import qualified Safe as Safe+import System.Environment+import System.FilePath++-- import web libraries+import qualified Data.Aeson as JSON+import qualified Network.WebSockets as WS+import qualified Network.WebSockets.Snap as WS+import Snap.Core as Snap hiding (path, dir)+import qualified Snap.Http.Server as Snap+import Snap.Util.FileServe++-- import internal modules+import Foreign.JavaScript.Resources+import Foreign.JavaScript.Types++{-----------------------------------------------------------------------------+ HTTP Server using WebSockets+------------------------------------------------------------------------------}+-- | Run a HTTP server that creates a 'Comm' channel.+httpComm :: Config -> EventLoop -> IO ()+httpComm Config{..} worker = do+ env <- getEnvironment++ let config = Snap.setErrorLog (Snap.ConfigIoLog jsLog)+ $ Snap.setAccessLog (Snap.ConfigIoLog jsLog)+ $ maybe (configureHTTP env) configureSSL jsUseSSL+ $ Snap.defaultConfig++ server <- Server <$> newMVar newFilepaths <*> newMVar newFilepaths <*> return jsLog++ Snap.httpServe config . route $+ routeResources server jsCustomHTML jsStatic+ ++ routeWebsockets (worker server)++ where+ configureHTTP :: [(String, String)] -> Snap.Config m a -> Snap.Config m a+ configureHTTP env config =+ let portEnv = Safe.readMay =<< Prelude.lookup "PORT" env+ addrEnv = fmap BS.pack $ Prelude.lookup "ADDR" env+ in Snap.setPort (maybe defaultPort id (jsPort `mplus` portEnv))+ $ Snap.setBind (maybe defaultAddr id (jsAddr `mplus` addrEnv)) config++ configureSSL :: ConfigSSL -> Snap.Config m a -> Snap.Config m a+ configureSSL cfgSsl config =+ Snap.setSSLBind (jsSSLBind cfgSsl)+ . Snap.setSSLPort (jsSSLPort cfgSsl)+ . Snap.setSSLCert (jsSSLCert cfgSsl)+ . Snap.setSSLKey (jsSSLKey cfgSsl)+ $ Snap.setSSLChainCert (jsSSLChainCert cfgSsl) config++-- | Route the communication between JavaScript and the server+routeWebsockets :: (RequestInfo -> Comm -> IO void) -> Routes+routeWebsockets worker = [("websocket", response)]+ where+ response = do+ requestInfo <- Snap.getRequest+ WS.runWebSocketsSnap $ \ws -> void $ do+ comm <- communicationFromWebSocket ws+ worker (rqCookies requestInfo) comm+ -- error "Foreign.JavaScript: unreachable code path."++-- | Create 'Comm' channel from WebSocket request.+communicationFromWebSocket :: WS.PendingConnection -> IO Comm+communicationFromWebSocket request = do+ connection <- WS.acceptRequest request+ commIn <- STM.newTQueueIO -- outgoing communication+ commOut <- STM.newTQueueIO -- incoming communication+ commOpen <- STM.newTVarIO True++ -- write data to browser+ let sendData = forever $ do+ x <- atomically $ STM.readTQueue commOut+ -- see note [ServerMsg strictness]+ WS.sendTextData connection . JSON.encode $ x++ -- read data from browser+ let readData = forever $ do+ input <- WS.receiveData connection+ case input of+ "ping" -> WS.sendTextData connection . LBS.pack $ "pong"+ "quit" -> E.throwIO WS.ConnectionClosed+ input -> case JSON.decode input of+ Just x -> atomically $ STM.writeTQueue commIn x+ Nothing -> error $+ "Foreign.JavaScript: Couldn't parse JSON input"+ ++ show input++ -- block until the channel is closed+ let sentry = atomically $ do+ open <- STM.readTVar commOpen+ when open retry++ -- explicitly close the Comm chanenl+ let commClose = atomically $ STM.writeTVar commOpen False++ -- read/write data until an exception occurs or the channel is no longer open+ _ <- forkFinally (sendData `race_` readData `race_` sentry) $ \_ -> void $ do+ -- close the communication channel explicitly if that didn't happen yet+ commClose++ -- attempt to close websocket if still necessary/possible+ -- ignore any exceptions that may happen if it's already closed+ let allExceptions :: E.SomeException -> Maybe ()+ allExceptions _ = Just ()+ E.tryJust allExceptions $ WS.sendClose connection $ LBS.pack "close"++ return $ Comm {..}++{-----------------------------------------------------------------------------+ Resources+------------------------------------------------------------------------------}+type Routes = [(ByteString, Snap ())]++routeResources :: Server -> Maybe FilePath -> Maybe FilePath -> Routes+routeResources server customHTML staticDir =+ fixHandlers noCache $+ static +++ [("/" , root)+ ,("/haskell.js" , writeTextMime jsDriverCode "application/javascript")+ ,("/haskell.css" , writeTextMime cssDriverCode "text/css")+ ,("/file/:name" ,+ withFilepath (sFiles server) (flip serveFileAs))+ ,("/dir/:name" ,+ withFilepath (sDirs server) (\path _ -> serveDirectory path))+ ]+ where+ fixHandlers f routes = [(a,f b) | (a,b) <- routes]+ noCache h = modifyResponse (setHeader "Cache-Control" "no-cache") >> h++ static = maybe [] (\dir -> [("/static", serveDirectory dir)]) staticDir++ root = case customHTML of+ Just file -> case staticDir of+ Just dir -> serveFile (dir </> file)+ Nothing -> logError "Foreign.JavaScript: Cannot use jsCustomHTML file without jsStatic"+ Nothing -> writeTextMime defaultHtmlFile "text/html"++writeTextMime :: MonadSnap m => Text -> ByteString -> m ()+writeTextMime text mime = do+ modifyResponse (setHeader "Content-type" mime)+ writeText text++-- | Extract from a URI+withFilepath :: MVar Filepaths -> (FilePath -> ByteString -> Snap a) -> Snap a+withFilepath rDict cont = do+ mName <- getParam "name"+ (_,dict) <- liftIO $ withMVar rDict return+ case (\key -> M.lookup key dict) =<< mName of+ Just (path,mimetype) -> cont path (BS.pack mimetype)+ Nothing -> error $ "File not loaded: " ++ show mName++-- FIXME: Serving large files fails with the exception+-- System.SendFile.Darwin: invalid argument (Socket is not connected)++-- | Associate an URL to a FilePath+newAssociation :: MVar Filepaths -> (FilePath, MimeType) -> IO String+newAssociation rDict (path,mimetype) = do+ (old, dict) <- takeMVar rDict+ let new = old + 1; key = show new ++ takeFileName path+ putMVar rDict $ (new, M.insert (BS.pack key) (path,mimetype) dict)+ return key++-- | Begin to serve a local file with a given 'MimeType' under a URI.+loadFile :: Server -> MimeType -> FilePath -> IO String+loadFile server mimetype path = do+ key <- newAssociation (sFiles server) (path, mimetype)+ return $ "/file/" ++ key++-- | Begin to serve a local directory under a URI.+loadDirectory :: Server -> FilePath -> IO String+loadDirectory server path = do+ key <- newAssociation (sDirs server) (path,"")+ return $ "/dir/" ++ key+
+ src/Foreign/JavaScript/Types.hs view
@@ -0,0 +1,395 @@+{-# LANGUAGE OverloadedStrings, DeriveDataTypeable #-}+module Foreign.JavaScript.Types where++import Control.Concurrent.STM+ ( STM+ , TMVar+ , TQueue+ , TVar+ )+import Control.DeepSeq+ ( NFData (..)+ , force+ )+import Data.Aeson+ ( toJSON+ , (.=)+ , (.:)+ )+import Data.ByteString.Char8+ ( ByteString+ )+import Data.Map+ ( Map+ )+import Data.String+ ( fromString+ )+import Data.Text+ ( Text+ )+import Data.Typeable+ ( Typeable+ )+import Snap.Core+ ( Cookie(..)+ )+import System.IO+ ( stderr+ )++import qualified Control.Concurrent.STM as STM+import qualified Control.Exception as E+import qualified Data.Aeson as JSON+import qualified Data.ByteString.Char8 as BS (hPutStrLn)+import qualified Data.Map as Map++import Control.Concurrent.MVar+import Foreign.RemotePtr++{-----------------------------------------------------------------------------+ Server Configuration -- Static+------------------------------------------------------------------------------}+-- NOTE: Unfortunately, Haddock currently does not create documentation for+-- record fields when the constructor is not exported.+-- That's why we copy & paste it in the documentation for the data type.+{- | Static configuration for a "Foreign.JavaScript" server.++This is a record type which has the following fields:++* @jsPort :: Maybe Int@++ Port number.+ @Nothing@ means that the port number is read from the environment variable @PORT@.+ Alternatively, port @8023@ is used if this variable is not set.++* @jsAddr :: Maybe ByteString@++ Bind address.+ @Nothing@ means that the bind address is read from the environment variable @ADDR@.+ Alternatively, address @127.0.0.1@ is used if this variable is not set.++* @jsCustomHTML :: Maybe FilePath@++ Custom HTML file to replace the default one.++* @jsStatic :: Maybe FilePath@++ Directory that is served under @/static@.++* @jsLog :: ByteString -> IO ()@++ Function to print a single log message.++* @jsWindowReloadOnDisconnect :: Bool@++ Reload the browser window if the connection to the server was dropped accidentally,+ for instance because the computer was put to sleep and awoken again.++* @jsCallBufferMode :: CallBufferMode@++ The initial 'CallBufferMode' to use for 'runFunction'.+ It can be changed at any time with 'setCallBufferMode'.++* @jsUseSSLBind :: Maybe ConfigSSL@++ Whether to serve on a HTTPS connection instead of HTTP for improved security.++ * 'Just' with a 'ConfigSSL' to serve on HTTPS.+ Note that this will fail silently unless the @snap-server@ package+ has been compiled with the @openssl@ flag enabled.++ * 'Nothing' to serve on HTTP.++(For reasons of forward compatibility, the constructor is not exported.)++-}+data Config = Config+ { jsPort :: Maybe Int+ , jsAddr :: Maybe ByteString+ , jsCustomHTML :: Maybe FilePath+ , jsStatic :: Maybe FilePath+ , jsLog :: ByteString -> IO ()+ , jsWindowReloadOnDisconnect :: Bool+ , jsCallBufferMode :: CallBufferMode+ , jsUseSSL :: Maybe ConfigSSL+ }++{- | Static configuration for the SSL version of the "Foreign.JavaScript" server.++This is a record type which has the following fields:++* @jsSSLBind :: ByteString@++ Bind address.++* @jsSSLCert :: FilePath@++ Path to SSL certificate file. Example: @cert.pem@.++* @jsSSLChainCert :: Bool@++ If it is SSL chain certificate file.++* @jsSSLKey :: FilePath@++ Path to SSL key file. Example: @key.pem@.++* @jsSSLPort :: ByteString@++ Port number. Example: 443.++-}++data ConfigSSL = ConfigSSL+ { jsSSLBind :: ByteString+ , jsSSLCert :: FilePath+ , jsSSLChainCert :: Bool+ , jsSSLKey :: FilePath+ , jsSSLPort :: Int+ }++defaultPort :: Int+defaultPort = 8023++defaultAddr :: ByteString+defaultAddr = "127.0.0.1"++-- | Default configuration.+--+-- Port from environment variable or @8023@, listening on @localhost@,+-- no custom HTML, no static directory,+-- logging to stderr,+-- do reload on disconnect,+-- __buffer FFI calls__.+defaultConfig :: Config+defaultConfig = Config+ { jsPort = Nothing+ , jsAddr = Nothing+ , jsWindowReloadOnDisconnect = True+ , jsCustomHTML = Nothing+ , jsStatic = Nothing+ , jsLog = BS.hPutStrLn stderr+ , jsCallBufferMode = FlushOften+ , jsUseSSL = Nothing+ }++{-----------------------------------------------------------------------------+ Server Configuration -- Dynamic+------------------------------------------------------------------------------}+-- | URI type.+--+-- FIXME: Use the correct type from "Network.URI"+type URI = String++-- | MIME type.+type MimeType = String++-- | Representation of a "Foreign.JavaScript" server.+--+-- Can be used for dynamic configuration, e.g. serving additional files.+data Server = Server+ { sFiles :: MVar Filepaths+ , sDirs :: MVar Filepaths+ , sLog :: ByteString -> IO () -- function for logging+ }+type Filepaths = (Integer, Map ByteString (FilePath, MimeType))++newFilepaths :: Filepaths+newFilepaths = (0, Map.empty)++{-----------------------------------------------------------------------------+ Communication channel+------------------------------------------------------------------------------}+-- | Bidirectional communication channel.+data Comm = Comm+ { commIn :: TQueue JSON.Value -- ^ Read from channel.+ , commOut :: TQueue JSON.Value -- ^ Write into channel.+ , commOpen :: TVar Bool -- ^ Indicate whether the channel is still open.+ , commClose :: IO () -- ^ Close the channel.+ }++writeComm :: Comm -> JSON.Value -> STM ()+writeComm c = STM.writeTQueue (commOut c)++readComm :: Comm -> STM JSON.Value+readComm c = STM.readTQueue (commIn c)++{-----------------------------------------------------------------------------+ Communication protocol+------------------------------------------------------------------------------}+-- | Messages received from the JavaScript client.+data ClientMsg+ = Event Coupon JSON.Value+ | Result JSON.Value+ | Exception String+ | Quit+ deriving (Eq, Show)++instance JSON.FromJSON ClientMsg where+ parseJSON (JSON.Object msg) = do+ tag <- msg .: "tag"+ case (tag :: Text) of+ "Event" -> Event <$> (msg .: "name") <*> (msg .: "arguments")+ "Result" -> Result <$> (msg .: "contents")+ "Exception" -> Exception <$> (msg .: "contents")+ "Quit" -> return Quit++readClient :: Comm -> STM ClientMsg+readClient c = do+ msg <- readComm c+ case JSON.fromJSON msg of+ JSON.Error s ->+ error $ "Foreign.JavaScript: Error parsing client message " ++ show s+ JSON.Success x+ -> pure x++-- | Messages sent by the Haskell server.+data ServerMsg+ = RunEval String+ | CallEval String+ | Debug String+ | Timestamp+ deriving (Eq,Show)++instance NFData ServerMsg where+ rnf (RunEval x) = rnf x+ rnf (CallEval x) = rnf x+ rnf (Debug x) = rnf x+ rnf (Timestamp ) = ()++instance JSON.ToJSON ServerMsg where+ toJSON (Debug x) =+ JSON.object [ "tag" .= t "Debug", "contents" .= toJSON x]+ toJSON Timestamp =+ JSON.object [ "tag" .= t "Timestamp" ]+ toJSON (RunEval x) =+ JSON.object [ "tag" .= t "RunEval", "contents" .= toJSON x]+ toJSON (CallEval x) =+ JSON.object [ "tag" .= t "CallEval", "contents" .= toJSON x]++t :: String -> Text+t s = fromString s++writeServer :: Comm -> ServerMsg -> STM ()+writeServer c = writeComm c . toJSON . force++{- Note [ServerMsg strictness]++The type `ServerMsg` may contain components that evalute to _|_, and+an exception will be thrown when we try to send one of those to the browser.++However, we have to make sure that the exception is thrown+in the thread that constructed the message, not in the thread that+handles the actual communication with the client.++That's why we have to use the function+`Control.DeepSeq.deepseq` to make sure that any exception+is thrown before handing the message over to another thread.++Since exceptions in pure code do not have a precise ordering relative+to exceptions in IO code, evaluating the pure value+also helps with ensuring that the exception is raised before+any subsequent IO exception; this makes it easier to pinpoint+the root cause for library users.++-}+++data JavaScriptException = JavaScriptException String deriving Typeable++instance E.Exception JavaScriptException++instance Show JavaScriptException where+ showsPrec _ (JavaScriptException err) = showString $ "JavaScript error: " ++ err++{-----------------------------------------------------------------------------+ Window & Event Loop+------------------------------------------------------------------------------}+-- | An event sent from the browser window to the server.+type Event = (Coupon, JSON.Value)++-- | An event handler that can be passed to the JavaScript client.+type HsEvent = RemotePtr (JSON.Value -> IO ())++quit :: Event+quit = ("quit", JSON.Null)++-- | Specification of how JavaScript functions should be called.+data CallBufferMode+ = NoBuffering+ -- ^ When 'runFunction' is used to call a JavaScript function,+ -- immediately send a message to the browser window to execute+ -- said function.+ | BufferRun+ -- ^ When 'runFunction' is used to call a JavaScript function,+ -- hold back any message to the server.+ -- All JavaScript functions that are held back in this way+ -- are combined into a single message,+ -- which is finally sent whenever 'callFunction' or+ -- 'flushCallBuffer' are used, or an exported Haskell function is called.+ | FlushOften+ -- ^ The same as 'BufferRun', but this mode indicates+ -- client libraries and programs are encouraged to flush the buffer more often+ -- to simplify usage. Users may choose 'BufferRun' instead if they want more control+ -- over flushing the buffer.+ | FlushPeriodically+ -- ^ The same as 'BufferRun', except that the buffer will also be flushed+ -- every 300ms.++flushPeriod = 300 :: Int++-- | Action that the server will run when a browser window connects.+type EventLoop = Server -> RequestInfo -> Comm -> IO ()+type RequestInfo = [Cookie]++-- | Representation of a browser window.+data Window = Window+ { getServer :: Server+ -- ^ Server that the browser window communicates with.+ , getCookies :: [Cookie]+ -- ^ Cookies that the browser window has sent to the server when connecting.++ , runEval :: String -> IO ()+ , callEval :: String -> IO JSON.Value++ , wCallBuffer :: TMVar (String -> String)+ , wCallBufferMode :: TVar CallBufferMode++ , timestamp :: IO ()+ -- ^ Print a timestamp and the time difference to the previous one+ -- in the JavaScript console.+ , debug :: String -> IO ()+ -- ^ Send a debug message to the JavaScript console.+ , onDisconnect :: IO () -> IO ()+ -- ^ Register an action to be performed when the client disconnects.+ , wRoot :: RemotePtr ()+ , wEventHandlers :: Vendor (JSON.Value -> IO ())+ , wJSObjects :: Vendor JSPtr+ }++newPartialWindow :: IO Window+newPartialWindow = do+ ptr <- newRemotePtr "" () =<< newVendor+ b1 <- STM.newTMVarIO id+ b2 <- STM.newTVarIO NoBuffering+ let nop = const $ return ()+ Window undefined [] nop undefined b1 b2 (return ()) nop nop ptr <$> newVendor <*> newVendor++-- | For the purpose of controlling garbage collection,+-- every 'Window' as an associated 'RemotePtr' that is alive+-- as long as the external JavaScript connection is alive.+root :: Window -> RemotePtr ()+root = wRoot++{-----------------------------------------------------------------------------+ Marshalling+------------------------------------------------------------------------------}+newtype JSPtr = JSPtr { unsJSPtr :: Coupon }++-- | A mutable JavaScript object.+type JSObject = RemotePtr JSPtr++-- | A mutable JavaScript object that has just been created.+-- This a dummy type used for additional type safety.+data NewJSObject = NewJSObject
+ src/Foreign/RemotePtr.hs view
@@ -0,0 +1,208 @@+{-# LANGUAGE RecordWildCards, CPP, ExistentialQuantification #-}+{-# LANGUAGE MagicHash, UnboxedTuples #-}+module Foreign.RemotePtr (+ -- * Synopsis+ -- | Toolbox for managing remote objects in Haskell.+ + -- * RemotePtr+ RemotePtr,+ withRemotePtr, addFinalizer, destroy, addReachable, clearReachable,+ unprotectedGetCoupon,++ -- * Coupons and Vendors+ Coupon, newCoupon,+ Vendor, newVendor, lookup,+ newRemotePtr,+ ) where++import Prelude hiding (lookup)+import Control.Monad+import qualified Data.Text as T+import qualified Data.HashMap.Strict as Map+import Data.IORef++import System.Mem.Weak hiding (addFinalizer)++import qualified GHC.Base as GHC+import qualified GHC.Weak as GHC+import qualified GHC.IORef as GHC+import qualified GHC.STRef as GHC++#if CABAL+#if MIN_VERSION_base(4,6,0)+#else+atomicModifyIORef' = atomicModifyIORef+#endif+#endif++mkWeakIORefValue :: IORef a -> value -> IO () -> IO (Weak value)+#if CABAL+#if MIN_VERSION_base(4,9,0)+mkWeakIORefValue (GHC.IORef (GHC.STRef r#)) v (GHC.IO f) = GHC.IO $ \s ->+ case GHC.mkWeak# r# v f s of (# s1, w #) -> (# s1, GHC.Weak w #)+#else+mkWeakIORefValue (GHC.IORef (GHC.STRef r#)) v f = GHC.IO $ \s ->+ case GHC.mkWeak# r# v f s of (# s1, w #) -> (# s1, GHC.Weak w #)+#endif+#else+mkWeakIORefValue (GHC.IORef (GHC.STRef r#)) v (GHC.IO f) = GHC.IO $ \s ->+ case GHC.mkWeak# r# v f s of (# s1, w #) -> (# s1, GHC.Weak w #)+#endif++type Map = Map.HashMap++{-----------------------------------------------------------------------------+ Types+------------------------------------------------------------------------------}+-- | A 'Coupon' is a unique identifier.+-- +-- It is a string of alphanumeric ASCII characters and it is intended to+-- be sent to or received from a remote program.+--+-- The data structure 'Vendor' associates 'Coupon's to 'RemotPtr' objects.+type Coupon = T.Text+++-- | A 'RemotePtr' is a pointer to a foreign object.+-- +-- Like a 'ForeignPtr', it refers to an object managed by an environment+-- external to the Haskell runtime.+-- Likewise, you can assign finalizers to a 'RemotePtr'. The finalizers+-- will be run when the Haskell runtime garbage collects this value.+-- They can perform some cleanup operations, like freeing memory.+--+-- Unlike a 'ForeignPtr', the object referenced by a 'RemotePtr' is not+-- necessarily a block of RAM. Instead, it can refer to things like an object+-- managed by a remote program.++type RemotePtr a = IORef (RemoteData a)++data RemoteData a = RemoteData+ { self :: Weak (RemotePtr a)+ , coupon :: Coupon+ , value :: a+ , children :: IORef [SomeWeak]+ }++-- Existentially quantified weak pointer. We only care about its finalizer.+data SomeWeak = forall a. SomeWeak (Weak a)++-- | A 'Vendor' is a bijective mapping from 'Coupon' to 'RemotePtr'.+--+-- Every 'Coupon' has at most one 'RemotePtr' associated to it.+-- A single 'RemotePtr' will always be associated with the same 'Coupon'.++data Vendor a = Vendor+ { coupons :: IORef (Map Coupon (Weak (RemotePtr a)))+ , counter :: IORef Integer+ }++{-----------------------------------------------------------------------------+ Vendor and Coupons+------------------------------------------------------------------------------}+-- | Create a new 'Vendor' for trading 'Coupon's and 'RemotePtr's.+newVendor :: IO (Vendor a)+newVendor = do+ counter <- newIORef 0+ coupons <- newIORef Map.empty+ return $ Vendor {..}++-- | Take a 'Coupon' to a 'Vendor' and maybe you'll get a 'RemotePtr' for it.+lookup :: Coupon -> Vendor a -> IO (Maybe (RemotePtr a))+lookup coupon Vendor{..} = do+ w <- Map.lookup coupon <$> readIORef coupons+ maybe (return Nothing) deRefWeak w++-- | Create a new 'Coupon'.+--+-- WARNING: This coupon is only unique relative to this 'Vendor'.+-- There is no guarantee that this 'Coupon' is globally unique,+-- certainly not on a remote machine.+newCoupon :: Vendor a -> IO Coupon+newCoupon Vendor{..} =+ T.pack . show <$> atomicModifyIORef' counter (\n -> (n+1,n))++-- | Create a new 'RemotePtr' from a 'Coupon' and register it with a 'Vendor'.+newRemotePtr :: Coupon -> a -> Vendor a -> IO (RemotePtr a)+newRemotePtr coupon value Vendor{..} = do+ children <- newIORef []+ let self = undefined+ ptr <- newIORef RemoteData{..}+ + let doFinalize =+ atomicModifyIORef' coupons $ \m -> (Map.delete coupon m, ())+ w <- mkWeakIORef ptr doFinalize+ atomicModifyIORef' coupons $ \m -> (Map.insert coupon w m, ())+ atomicModifyIORef' ptr $ \itemdata -> (itemdata { self = w }, ())+ return ptr++{-----------------------------------------------------------------------------+ RemotePtr+------------------------------------------------------------------------------}+-- | Access the data of the 'RemotePtr'.+-- +-- While the action is being performed, it is ensured that the 'RemotePtr'+-- will not be garbage collected+-- and its 'Coupon' can be successfully redeemed at the 'Vendor'.+withRemotePtr :: RemotePtr a -> (Coupon -> a -> IO b) -> IO b+withRemotePtr ptr0 f = do+ RemoteData{..} <- readIORef ptr0+ b <- f coupon value+ touch ptr0+ return b+ where+ -- make sure that the pointer is alive at this point in the code+ touch ptr = void $ readIORef ptr++-- | Unprotected access the 'Coupon' of a 'RemotePtr'.+--+-- Note: There is no guarantee that the 'RemotePtr' is alive+-- after this operation and that the 'Coupon' can be redeemed at a 'Vendor'.+-- Most of the time, you should use 'withRemotePtr' instead.+--+-- Note: In particular, if you use this with @unsafePerformIO@,+-- the risk is high that you only refer to the 'RemotePtr' argument via+-- the result just obtained, and the pointer will be garbage collected.+unprotectedGetCoupon :: RemotePtr a -> IO Coupon+unprotectedGetCoupon ptr = coupon <$> readIORef ptr+++-- | Add a finalizer that is run when the 'RemotePtr' is garbage collected.+--+-- The associated coupon cannot be redeemed anymore while the finalizer runs.+addFinalizer :: RemotePtr a -> IO () -> IO ()+addFinalizer ptr = void . mkWeakIORef ptr+-- | FIXME: Is this finalizer really run when 'destroy' is called?++-- | Destroy a 'RemotePtr' and run all finalizers for it.+-- 'Coupon's for this pointer can no longer be redeemed.+destroy :: RemotePtr a -> IO ()+destroy ptr = finalize =<< self <$> readIORef ptr+++-- | When dealing with several foreign objects,+-- it is useful to model dependencies between them.+--+-- After this operation, the second 'RemotePtr' will be reachable+-- whenever the first one is reachable.+-- For instance, you should call this function when the second foreign object+-- is actually a subobject of the first one.+--+-- Note: It is possible to model dependencies in the @parent@ data,+-- but the 'addReachable' method is preferrable,+-- as it allows all child object to be garbage collected at once.+addReachable :: RemotePtr a -> RemotePtr b -> IO ()+addReachable parent child = do+ w <- mkWeakIORefValue parent child $ return ()+ ref <- children <$> readIORef parent+ atomicModifyIORef' ref $ \ws -> (SomeWeak w:ws, ())++-- | Clear all dependencies.+-- +-- Reachability of this 'RemotePtr' no longer implies reachability+-- of other items, as formerly implied by calls to 'addReachable'.+clearReachable :: RemotePtr a -> IO ()+clearReachable parent = do+ ref <- children <$> readIORef parent+ xs <- atomicModifyIORef' ref $ \xs -> ([], xs)+ sequence_ [finalize x | SomeWeak x <- xs]
src/Graphics/UI/Threepenny.hs view
@@ -1,4 +1,11 @@ module Graphics.UI.Threepenny (+ -- * Introduction+ -- $intro++ -- * Example+ -- $example++ -- * Modules module Graphics.UI.Threepenny.Attributes, module Graphics.UI.Threepenny.Core, module Graphics.UI.Threepenny.Canvas,@@ -7,13 +14,94 @@ module Graphics.UI.Threepenny.Events, module Graphics.UI.Threepenny.JQuery, module Graphics.UI.Threepenny.Timer,+ module Graphics.UI.Threepenny.Widgets, ) where -import Graphics.UI.Threepenny.Attributes-import Graphics.UI.Threepenny.Core-import Graphics.UI.Threepenny.Canvas-import Graphics.UI.Threepenny.DragNDrop-import Graphics.UI.Threepenny.Elements-import Graphics.UI.Threepenny.Events-import Graphics.UI.Threepenny.JQuery-import Graphics.UI.Threepenny.Timer+import Graphics.UI.Threepenny.Attributes+import Graphics.UI.Threepenny.Canvas+import Graphics.UI.Threepenny.Core+import Graphics.UI.Threepenny.DragNDrop+import Graphics.UI.Threepenny.Elements+import Graphics.UI.Threepenny.Events+import Graphics.UI.Threepenny.JQuery+import Graphics.UI.Threepenny.Timer+import Graphics.UI.Threepenny.Widgets++{- $intro++Welcome to the Threepenny library for graphical user interfaces.++A program written with Threepenny is essentially a small web server+that displays the user interface as a web page to any browser that connects to it.++For an introduction, see the example below.+The module "Graphics.UI.Threepenny.Core" contains the main functions.++-}+++{- $example++The following example should help to get you started with Threepenny.+(The lines of code below are meant to be concatenated into a single file.)++> module Main where++First, we have to import the library.+It is a good idea to import the core module verbatim+and import all other functions with a mandatory @UI@ prefix.++> import qualified Graphics.UI.Threepenny as UI+> import Graphics.UI.Threepenny.Core++We begin by starting a server on port @8023@ using the 'startGUI' function.+Additional static content is served from the @../wwwroot@ directory.++> main :: IO ()+> main = do+> startGUI defaultConfig+> { jsPort = Just 8023+> , jsStatic = Just "../wwwroot"+> } setup++Whenever a browser connects to the server,+the following function will be executed to start the GUI interaction.+It builds the initial HTML page.++> setup :: Window -> UI ()+> setup window = do++First, set the title of the HTML document++> return window # set UI.title "Hello World!"++Then create a button element++> button <- UI.button # set UI.text "Click me!"++DOM elements can be accessed much in the same way they are+accessed from JavaScript; they can be searched, updated, moved and+inspected. In the line above, we set the 'text' contents.++To actually display the button, we have to attach it to the body of the HTML element.+The '#+' combinator allows you to nest elements quickly+in the style of a HTML combinator library.++> getBody window #+ [element button]++Finally, we register an event handler for the 'click' event,+which occurs whenever the user clicks on the button.+When that happens, we change the text of the button.++> on UI.click button $ const $ do+> element button # set UI.text "I have been clicked!"++That's it! Now, run the program and visit the URL <http://localhost:8023/>+in your browser to interact with the user interface.++The library comes with a+<https://github.com/HeinrichApfelmus/threepenny-gui/tree/master/samples#readme plethora of additional example code>.+++-}+
src/Graphics/UI/Threepenny/Attributes.hs view
@@ -1,65 +1,67 @@+{-# OPTIONS_GHC -Wno-missing-signatures #-} module Graphics.UI.Threepenny.Attributes ( -- * Synopsis -- | Element attributes.- + -- * Input elements checked, selection, enabled,- + -- * HTML attributes action, align, alink, alt, altcode, archive, background, base, bgcolor, border, bordercolor, cellpadding, cellspacing, checked_, class_, clear_, code_, codebase, color, cols, colspan, compact, content, coords,- enctype, face, frameborder, height, href, hspace, httpequiv,+ enctype, face, for, frameborder, height, href, hspace, httpequiv, id_, ismap, lang, marginheight, marginwidth, maxlength, method, multiple, name, nohref, noresize, noshade, nowrap, rel, rev, rows, rowspan, rules, scrolling, selected, shape, size, src,- target, text_, type_, usemap, valign, version, vlink, vspace, width,+ target, text_, title__, type_, usemap, valign, version, vlink, vspace, width, ) where -import Text.JSON-import Graphics.UI.Threepenny.Core+import qualified Data.Aeson as JSON+import Graphics.UI.Threepenny.Core {----------------------------------------------------------------------------- Attributes ------------------------------------------------------------------------------} -- | The @checked@ status of an input element of type checkbox. checked :: Attr Element Bool-checked = fromProp "checked" (== JSBool True) JSBool+checked = fromJQueryProp "checked" (== JSON.Bool True) JSON.Bool -- | The @enabled@ status of an input element enabled :: Attr Element Bool-enabled = fromProp "disabled" (== JSBool False) (JSBool . not)+enabled = fromJQueryProp "disabled" (== JSON.Bool False) (JSON.Bool . not) -- | Index of the currently selected option of a @<select>@ element. -- -- The index starts at @0@. -- If no option is selected, then the selection is 'Nothing'. selection :: Attr Element (Maybe Int)-selection = fromProp "selectedIndex" from (showJSON . maybe (-1) id)+selection = fromJQueryProp "selectedIndex" from (JSON.toJSON . maybe (-1) id) where- from s = let Ok x = readJSON s in if x == -1 then Nothing else Just x+ from s = let JSON.Success x = JSON.fromJSON s in+ if x == -1 then Nothing else Just x {----------------------------------------------------------------------------- HTML atributes- + Taken from the HTML library (BSD3 license) http://hackage.haskell.org/package/html ------------------------------------------------------------------------------} strAttr :: String -> WriteAttr Element String-strAttr name = mkWriteAttr (set' (attr name))+strAttr attrname = mkWriteAttr (set' (attr attrname)) intAttr :: String -> WriteAttr Element Int-intAttr name = mkWriteAttr (set' (attr name) . show)+intAttr attrname = mkWriteAttr (set' (attr attrname) . show) emptyAttr :: String -> WriteAttr Element Bool-emptyAttr name = mkWriteAttr (set' (attr name) . f)+emptyAttr attrname = mkWriteAttr (set' (attr attrname) . f) where f True = "1" f False = "0"- + action = strAttr "action" align = strAttr "align" alink = strAttr "alink"@@ -85,6 +87,7 @@ coords = strAttr "coords" enctype = strAttr "enctype" face = strAttr "face"+for = strAttr "for" frameborder = intAttr "frameborder" height = intAttr "height" href = strAttr "href"@@ -117,7 +120,7 @@ text_ = strAttr "text" class_ = strAttr "class" type_ = strAttr "type"-title = strAttr "title"+title__ = strAttr "title" -- ugly, but necessary to avoid conflicts with the window title and the title element usemap = strAttr "usemap" valign = strAttr "valign" version = strAttr "version"
src/Graphics/UI/Threepenny/Canvas.hs view
@@ -1,35 +1,269 @@ module Graphics.UI.Threepenny.Canvas ( -- * Synopsis -- | Partial binding to the HTML5 canvas API.- + -- * Documentation- Canvas,- drawImage, clearCanvas,+ Canvas+ , Vector, Point+ , Color(..), ColorStop, Gradient, FillStyle+ , drawImage, clearCanvas+ , solidColor, htmlColor+ , linearGradient, horizontalLinearGradient, verticalLinearGradient+ , fillRect, fillStyle, strokeStyle, lineWidth, textFont+ , TextAlign(..), textAlign+ , beginPath, moveTo, lineTo, closePath, arc, arc'+ , fill, stroke, fillText, strokeText ) where -import Control.Event+import Data.Char (toUpper)+import Data.List(intercalate)+import Numeric (showHex)+ import Graphics.UI.Threepenny.Core-import qualified Graphics.UI.Threepenny.Internal.Core as Core-import qualified Graphics.UI.Threepenny.Internal.Types as Core {----------------------------------------------------------------------------- Canvas ------------------------------------------------------------------------------} type Canvas = Element -type Vector = (Int,Int)+type Vector = Point+type Point = (Double, Double)+data Color = RGB { red :: Int, green :: Int, blue :: Int }+ | RGBA { red :: Int, green :: Int, blue :: Int, alpha :: Double }+ deriving (Eq, Show) +type ColorStop = (Double, Color)++data Gradient + -- | defines a linear gradient + -- see <http://www.w3schools.com/tags/canvas_createlineargradient.asp> + = LinearGradient + { upperLeft :: Vector -- ^ the left-upper point where the gradient should begin+ , gradWidth :: Double -- ^ the width of the gradient+ , gradHeight :: Double -- ^ the height of the gradient+ , colorStops :: [ColorStop] -- ^ the gradients color stops+ } deriving (Show, Eq)++data FillStyle+ = SolidColor Color+ | HtmlColor String -- Html representation of a color+ | Gradient Gradient+ deriving (Show, Eq) +++{-----------------------------------------------------------------------------+ Image drawing+------------------------------------------------------------------------------}+ -- | Draw the image of an image element onto the canvas at a specified position.-drawImage :: Element -> Vector -> Canvas -> IO ()-drawImage eimage (x,y) = updateElement $ \(Core.Element canvas window) -> do- image <- manifestElement window eimage- runFunction window $- ffi "%1.getContext('2d').drawImage(%2,%3,%4)" canvas image x y+drawImage :: Element -> Vector -> Canvas -> UI ()+drawImage image (x,y) canvas =+ runFunction $ ffi "%1.getContext('2d').drawImage(%2,%3,%4)" canvas image x y +{-----------------------------------------------------------------------------+ Fill Styles+------------------------------------------------------------------------------}++-- | creates a solid-color fillstyle+solidColor :: Color -> FillStyle+solidColor rgb = SolidColor rgb++-- | Solid color represented as a HTML string.+htmlColor :: String -> FillStyle+htmlColor = HtmlColor++-- | creates a linear gradient fill style+linearGradient :: Point -- ^ The upper-left coordinate of the gradient+ -> Double -- ^ The width of the gradient+ -> Double -- ^ The height of the gradient+ -> [ColorStop] -- ^ the color-stops for the gradient+ -> FillStyle+linearGradient (x0, y0) w h sts = Gradient $ LinearGradient (x0,y0) w h sts++-- | creates a simple horizontal gradient+horizontalLinearGradient:: Point -- ^ The upper-left coordinate of the gradient+ -> Double -- ^ The width of the gradient+ -> Color -- ^ The starting color of the gradient+ -> Color -- ^ The ending color of the gradient+ -> FillStyle+horizontalLinearGradient pt w c0 c1 = linearGradient pt w 0 [(0, c0), (1, c1)]++-- | creates a simple vertical gradient+verticalLinearGradient:: Point -- ^ The upper-left coordinate of the gradient+ -> Double -- ^ The height of the gradient+ -> Color -- ^ The starting color of the gradient+ -> Color -- ^ The ending color of the gradient+ -> FillStyle+verticalLinearGradient pt h c0 c1 = linearGradient pt 0 h [(0, c0), (1, c1)]++{-----------------------------------------------------------------------------+ general+------------------------------------------------------------------------------}+ -- | Clear the canvas-clearCanvas :: Canvas -> IO ()-clearCanvas = updateElement $ \(Core.Element canvas window) -> do- runFunction window $- ffi "%1.getContext('2d').clear()" canvas+clearCanvas :: Canvas -> UI ()+clearCanvas = runFunction . ffi "%1.getContext('2d').clear()" +{-----------------------------------------------------------------------------+ fill primitives+------------------------------------------------------------------------------}+++-- | Draw a filled rectangle.+--+-- The 'fillStyle' attribute determines the color.+fillRect+ :: Point -- ^ upper left corner+ -> Double -- ^ width in pixels+ -> Double -- ^ height in pixels+ -> Canvas -> UI ()+fillRect (x,y) w h canvas =+ runFunction $ ffi "%1.getContext('2d').fillRect(%2, %3, %4, %5)" canvas x y w h++-- | The Fillstyle to use inside shapes.+-- write-only as I could not find how to consistently read the fillstyle+fillStyle :: WriteAttr Canvas FillStyle+fillStyle = mkWriteAttr assignFillStyle++-- | sets the current fill style of the canvas context+assignFillStyle :: FillStyle -> Canvas -> UI ()+assignFillStyle (Gradient fs) canvas =+ runFunction $ ffi cmd canvas+ where cmd = "var ctx=%1.getContext('2d'); var grd=" ++ fsStr fs ++ cStops fs ++ "ctx.fillStyle=grd;"+ fsStr (LinearGradient (x0, y0) w h _) + = "ctx.createLinearGradient(" ++ pStr [x0, y0, x0+w, y0+h] ++ ");"+ cStops (LinearGradient _ _ _ sts) = concatMap addStop sts+ addStop (p,c) = "grd.addColorStop(" ++ show p ++ ",'" ++ rgbString c ++ "');"+ pStr = intercalate "," . map show+assignFillStyle (SolidColor color) canvas =+ runFunction $ ffi "%1.getContext('2d').fillStyle=%2" canvas (rgbString color)+assignFillStyle (HtmlColor color) canvas =+ runFunction $ ffi "%1.getContext('2d').fillStyle=%2" canvas color++-- | The color or style to use for the lines around shapes.+-- Default is @#000@ (black).+strokeStyle :: Attr Canvas String+strokeStyle = fromObjectProperty "getContext('2d').strokeStyle"++-- | The width of lines. Default is @1@.+lineWidth :: Attr Canvas Double+lineWidth = fromObjectProperty "getContext('2d').lineWidth"++-- | The font used for 'fillText' and 'strokeText'.+-- Default is @10px sans-serif@.+textFont :: Attr Canvas String+textFont = fromObjectProperty "getContext('2d').font"++data TextAlign = Start | End | LeftAligned | RightAligned | Center+ deriving (Eq, Show, Read)++aToS :: TextAlign -> String+aToS algn =+ case algn of+ Start -> "start"+ End -> "end"+ LeftAligned -> "left"+ RightAligned -> "right"+ Center -> "center"++sToA :: String -> TextAlign+sToA algn =+ case algn of+ "start" -> Start+ "end" -> End+ "left" -> LeftAligned+ "right" -> RightAligned+ "center" -> Center+ _ -> Start++-- | The alignment for 'fillText' and 'strokeText'. Default is 'Start'.+textAlign :: Attr Canvas TextAlign+textAlign = bimapAttr aToS sToA $ textAlignStr+ where+ textAlignStr :: Attr Canvas String+ textAlignStr = fromObjectProperty "getContext('2d').textAlign"++-- | Starts a new path by resetting the list of sub-paths.+-- Call this function when you want to create a new path.+beginPath :: Canvas -> UI()+beginPath = runFunction . ffi "%1.getContext('2d').beginPath()"++-- | Moves the starting point of a new subpath to the @(x,y)@ coordinate.+moveTo :: Point -> Canvas -> UI()+moveTo (x,y) canvas =+ runFunction $ ffi "%1.getContext('2d').moveTo(%2, %3)" canvas x y++-- | Connects the last point in the subpath to the @(x,y)@ coordinates+-- with a straight line.+lineTo :: Point -> Canvas -> UI()+lineTo (x,y) canvas =+ runFunction $ ffi "%1.getContext('2d').lineTo(%2, %3)" canvas x y++-- | Draw a straight line from the current point to the start of the+-- path. If the shape has already been closed or has only one point,+-- this function does nothing.+closePath :: Canvas -> UI()+closePath = runFunction . ffi "%1.getContext('2d').closePath()"++-- | Add a circular arc to the current path.+arc+ :: Point -- ^ Center of the circle of which the arc is a part.+ -> Double -- ^ Radius of the circle of which the arc is a part.+ -> Double -- ^ Starting angle, in radians.+ -> Double -- ^ Ending angle, in radians.+ -> Canvas -> UI ()+arc (x,y) radius startAngle endAngle canvas =+ runFunction $ ffi "%1.getContext('2d').arc(%2, %3, %4, %5, %6)"+ canvas x y radius startAngle endAngle++-- | Like 'arc', but with an extra argument that indicates whether+-- we go in counter-clockwise ('True') or clockwise ('False') direction.+arc' :: Point -> Double -> Double -> Double -> Bool -> Canvas -> UI ()+arc' (x,y) radius startAngle endAngle anti canvas =+ runFunction $ ffi "%1.getContext('2d').arc(%2, %3, %4, %5, %6, %7)"+ canvas x y radius startAngle endAngle anti++-- | Fills the subpaths with the current fill style.+fill :: Canvas -> UI ()+fill = runFunction . ffi "%1.getContext('2d').fill()"++-- | Strokes the subpaths with the current stroke style.+stroke :: Canvas -> UI ()+stroke = runFunction . ffi "%1.getContext('2d').stroke()"++-- | Render a text in solid color at a certain point on the canvas.+-- +-- The 'fillStyle' attribute determines the color.+-- The 'textFont' attribute determines the font used.+-- The 'textAlign' attributes determines the position of the text+-- relative to the point.+fillText :: String -> Point -> Canvas -> UI ()+fillText t (x,y) canvas =+ runFunction $ ffi "%1.getContext('2d').fillText(%2, %3, %4)" canvas t x y++-- | Render the outline of a text at a certain point on the canvas.+-- +-- The 'strokeStyle' attribute determines the color of the outline.+-- The 'textFont' attribute determines the font used.+-- The 'textAlign' attributes determines the position of the text+-- relative to the point.+strokeText :: String -> Point -> Canvas -> UI ()+strokeText t (x,y) canvas =+ runFunction $ ffi "%1.getContext('2d').strokeText(%2, %3, %4)" canvas t x y++{-----------------------------------------------------------------------------+ helper functions+------------------------------------------------------------------------------}++rgbString :: Color -> String+rgbString color =+ case color of+ (RGB r g b) -> "#" ++ sh r ++ sh g ++ sh b+ (RGBA r g b a) -> "rgba(" ++ show r ++ "," ++ show g ++ "," ++ show b ++ "," ++ show a ++ ")"+ where sh i = pad . map toUpper $ showHex i ""+ pad s+ | length s == 0 = "00"+ | length s == 1 = '0' : s+ | length s == 2 = s+ | otherwise = take 2 s
src/Graphics/UI/Threepenny/Core.hs view
@@ -1,99 +1,78 @@+{-# LANGUAGE FlexibleContexts, ScopedTypeVariables #-} module Graphics.UI.Threepenny.Core (- -- * Guide- -- $guide- + -- * Synopsis+ -- | Core functionality of the Threepenny GUI library.+ -- * Server -- $server- Config(..), startGUI,+ Config(..), ConfigSSL (..), defaultConfig, startGUI, loadFile, loadDirectory,- ++ -- * UI monad+ -- $ui+ UI, runUI, MonadUI(..), askWindow, liftIOLater,+ module Control.Monad.IO.Class,+ module Control.Monad.Fix,+ -- * Browser Window- Window, title, cookies, getRequestLocation,- + Window, title,+ -- * DOM elements -- | Create and manipulate DOM elements.- Element, mkElement, getWindow, delete, (#+), string,+ Element, getWindow, mkElement, mkElementNamespace, delete,+ string, getHead, getBody,- children, text, html, attr, style, value,- getValuesList,- getElementsByTagName, getElementByTagName, getElementsById, getElementById,- + (#+), children, text, html, attr, style, value,+ getElementsByTagName, getElementById, getElementsByClassName,+ -- * Layout -- | Combinators for quickly creating layouts. -- They can be adjusted with CSS later on. grid, row, column,- + -- * Events -- | For a list of predefined events, see "Graphics.UI.Threepenny.Events".- EventData(..), domEvent, on,- module Control.Event,- + EventData, domEvent, unsafeFromJSON, disconnect, on, onEvent, onChanges,+ module Reactive.Threepenny,+ -- * Attributes -- | For a list of predefined attributes, see "Graphics.UI.Threepenny.Attributes".- (#), (#.), element,+ (#), (#.), Attr, WriteAttr, ReadAttr, ReadWriteAttr(..),- set, get, mkReadWriteAttr, mkWriteAttr, mkReadAttr,- + set, sink, get, mkReadWriteAttr, mkWriteAttr, mkReadAttr,+ bimapAttr, fromObjectProperty,++ -- * Widgets+ Widget(..), element, widget,+ -- * JavaScript FFI -- | Direct interface to JavaScript in the browser window.- debug, clear,- ToJS, FFI, ffi, JSFunction, runFunction, callFunction,- callDeferredFunction, atomic,- + debug, timestamp,+ ToJS, FFI,+ JSFunction, ffi, runFunction, callFunction,+ CallBufferMode(..), setCallBufferMode, flushCallBuffer,+ ffiExport,+ -- ** Internals+ toJSObject, liftJSWindow, -- * Internal and oddball functions- updateElement, manifestElement, audioPlay, fromProp,- + fromJQueryProp,+ ) where -import Data.IORef-import Data.Maybe (listToMaybe)-import Data.Functor-import Data.String (fromString)-import Control.Concurrent.MVar-import Control.Event-import Control.Monad+import Control.Monad (forM_, forM, void)+import Control.Monad.Fix import Control.Monad.IO.Class-import Control.Monad.Trans.Reader as Reader-import Network.URI-import Text.JSON -import qualified Graphics.UI.Threepenny.Internal.Core as Core-import Graphics.UI.Threepenny.Internal.Core- (getRequestLocation,- ToJS, FFI, ffi, JSFunction,- debug, clear, callFunction, runFunction, callDeferredFunction, atomic, )-import qualified Graphics.UI.Threepenny.Internal.Types as Core-import Graphics.UI.Threepenny.Internal.Types (Window, Config, EventData)--{------------------------------------------------------------------------------ Guide-------------------------------------------------------------------------------}-{- $guide--Threepenny runs a small web server that displays the user interface-as a web page to any browser that connects to it.-To start the web server, use the 'startGUI' function.--Creating of DOM elements is easy,-the '(#+)' combinator allows a style similar to HTML combinator libraries.--Existing DOM elements can be accessed much in the same way they are-accessed from JavaScript; they can be searched, updated, moved and-inspected. Events can be bound to DOM elements and handled.---Applications written in Threepenny are multithreaded. Each client (user)-has a separate thread which runs with no awareness of the asynchronous-protocol below. Each session should only be accessed from one-thread. There is not yet any clever architecture for accessing the-(single threaded) web browser from multi-threaded Haskell. That's-my recommendation. You can choose to ignore it, but don't blame me-when you run an element search and you get a click event as a-result.--This project was originally called Ji.+import qualified Control.Monad.Catch as E+import qualified Data.Aeson as JSON+import qualified Foreign.JavaScript as JS+import qualified Graphics.UI.Threepenny.Internal as Core+import qualified Reactive.Threepenny as Reactive --}+-- exports+import Foreign.JavaScript (Config(..), ConfigSSL (..), defaultConfig)+import Graphics.UI.Threepenny.Internal+import Reactive.Threepenny hiding (onChange) {-----------------------------------------------------------------------------@@ -102,125 +81,37 @@ {- $server To display the user interface, you have to start a server using 'startGUI'.-Then, visit the URL <http://localhost:10000/> in your browser-(assuming that you have set the port number to @tpPort=10000@-in the server configuration).---}---- | Start server for GUI sessions.-startGUI- :: Config -- ^ Server configuration.- -> (Window -> IO ()) -- ^ Action to run whenever a client browser connects.- -> IO ()-startGUI config handler =- Core.serve config $ \w -> handler w >> Core.handleEvents w+Then, visit the URL <http://localhost:8023/> in your browser+(assuming that you use the default server configuration 'defaultConfig',+or have set the port number to @jsPort=Just 8023@.) +The server is multithreaded.+FFI calls can be made concurrently, but events are handled sequentially. --- | Make a local file available as a relative URI.-loadFile- :: Window -- ^ Browser window- -> String -- ^ MIME type- -> FilePath -- ^ Local path to the file- -> IO String -- ^ Generated URI-loadFile w mime path = Core.loadFile w (fromString mime) path+FFI calls can be __buffered__,+so in some circumstances, it may happen that you manipulate the browser window,+but the effect is not immediately visible.+See 'CallBufferMode' for more information. --- | Make a local directory available as a relative URI.-loadDirectory :: Window -> FilePath -> IO String-loadDirectory = Core.loadDirectory+-} {----------------------------------------------------------------------------- Browser window ------------------------------------------------------------------------------} -- | Title of the client window. title :: WriteAttr Window String-title = mkWriteAttr Core.setTitle---- | Cookies on the client.-cookies :: ReadAttr Window [(String,String)]-cookies = mkReadAttr Core.getRequestCookies+title = mkWriteAttr $ \s _ ->+ runFunction $ ffi "document.title = %1;" s {------------------------------------------------------------------------------ Elements+ DOM Elements ------------------------------------------------------------------------------}-type Value = String---- | Reference to an element in the DOM of the client window.-newtype Element = Element (MVar Elem)-data Elem- = Alive Core.Element -- element exists in a window- | Limbo Value (Window -> IO Core.Element) -- still needs to be created---- Turn a live reference into an 'Element'.--- Note that multiple MVars may now point to the same live reference,--- but this is ok since live references never change.-fromAlive :: Core.Element -> IO Element-fromAlive e = Element <$> newMVar (Alive e)---- Update an element that may be in Limbo.-updateElement :: (Core.Element -> IO ()) -> Element -> IO ()-updateElement f (Element me) = do- e <- takeMVar me- case e of- Alive e -> do -- update immediately- f e- putMVar me $ Alive e- Limbo value create -> -- update on creation- putMVar me $ Limbo value $ \w -> create w >>= \e -> f e >> return e---- Given a browser window, make sure that the element exists there.--- TODO: 1. Throw exception if the element exists in another window.--- 2. Don't throw exception, but move the element across windows.-manifestElement :: Window -> Element -> IO Core.Element-manifestElement w (Element me) = do- e1 <- takeMVar me- e2 <- case e1 of- Alive e -> return e- Limbo v create -> do { e2 <- create w; Core.setAttr "value" v e2; return e2 }- putMVar me $ Alive e2- return e2---- Append a child element to a parent element. Non-blocking.-appendTo- :: Element -- ^ Parent.- -> Element -- ^ Child.- -> IO ()-appendTo parent child = do- flip updateElement parent $ \x -> do- y <- manifestElement (Core.getWindow x) child- Core.appendElementTo x y---- | Make a new DOM element.-mkElement- :: String -- ^ Tag name- -> IO Element-mkElement tag = Element <$> newMVar (Limbo "" $ \w -> Core.newElement w tag)---- | Retreive the browser 'Window' in which the element resides.--- --- Note that elements do not reside in any browser window when they are first created.--- To move the element to a particular browser window,--- you have to append it to a parent, for instance with the `(#+)` operator.------ WARNING: The ability to move elements from one browser window to another--- is currently not implemented yet.-getWindow :: Element -> IO (Maybe Window)-getWindow (Element ref) = do- e1 <- readMVar ref- return $ case e1 of- Alive e -> Just $ Core.getWindow e- Limbo _ _ -> Nothing---- | Delete the given element.-delete :: Element -> IO ()-delete = updateElement (Core.delete)- -- | Append DOM elements as children to a given element.-(#+) :: IO Element -> [IO Element] -> IO Element+(#+) :: UI Element -> [UI Element] -> UI Element (#+) mx mys = do x <- mx ys <- sequence mys- mapM_ (appendTo x) ys+ mapM_ (Core.appendChild x) ys return x -- | Child elements of a given element.@@ -228,119 +119,85 @@ children = mkWriteAttr set where set xs x = do- updateElement Core.emptyEl x- mapM_ (appendTo x) xs+ Core.clearChildren x+ mapM_ (Core.appendChild x) xs -- | Child elements of a given element as a HTML string. html :: WriteAttr Element String-html = mkWriteAttr (updateElement . Core.setHtml)+html = mkWriteAttr $ \s el ->+ runFunction $ ffi "$(%1).html(%2)" el s -- | HTML attributes of an element. attr :: String -> WriteAttr Element String-attr name = mkWriteAttr (updateElement . Core.setAttr name)+attr name = mkWriteAttr $ \s el ->+ runFunction $ ffi "$(%1).attr(%2,%3)" el name s -- | Set CSS style of an Element style :: WriteAttr Element [(String,String)]-style = mkWriteAttr (updateElement . Core.setStyle)+style = mkWriteAttr $ \xs el -> forM_ xs $ \(name,val) ->+ runFunction $ ffi "%1.style[%2] = %3" el name val -- | Value attribute of an element. -- Particularly relevant for control widgets like 'input'. value :: Attr Element String value = mkReadWriteAttr get set where- get (Element ref) = getValue =<< readMVar ref- set v (Element ref) = updateMVar (setValue v) ref- - getValue (Limbo v _) = return v- getValue (Alive e ) = Core.getValue e- - setValue v (Limbo _ f) = return $ Limbo v f- setValue v (Alive e ) = Core.setAttr "value" v e >> return (Alive e)- - updateMVar f ref = do- x <- takeMVar ref- y <- f x- putMVar ref y---- | Get values from inputs. Blocks. This is faster than many 'getValue' invocations.-getValuesList- :: [Element] -- ^ A list of elements to get the values of.- -> IO [String] -- ^ The list of plain text values.-getValuesList = mapM (get value)- -- TODO: improve this to use Core.getValuesList+ get el = callFunction $ ffi "$(%1).val()" el+ set v el = runFunction $ ffi "$(%1).val(%2)" el v -- | Text content of an element. text :: WriteAttr Element String-text = mkWriteAttr (updateElement . Core.setText)+text = mkWriteAttr $ \s el ->+ runFunction $ ffi "$(%1).text(%2)" el s -- | Make a @span@ element with a given text content.-string :: String -> IO Element+string :: String -> UI Element string s = mkElement "span" # set text s - -- | Get the head of the page.-getHead :: Window -> IO Element-getHead = fromAlive <=< Core.getHead+getHead :: Window -> UI Element+getHead _ = fromJSObject =<< callFunction (ffi "document.head") -- | Get the body of the page.-getBody :: Window -> IO Element-getBody = fromAlive <=< Core.getBody---- | Get an element by its tag name. Blocks.-getElementByTagName- :: Window -- ^ Browser window- -> String -- ^ The tag name.- -> IO (Maybe Element) -- ^ An element (if any) with that tag name.-getElementByTagName window = liftM listToMaybe . getElementsByTagName window+getBody :: Window -> UI Element+getBody _ = fromJSObject =<< callFunction (ffi "document.body") --- | Get all elements of the given tag name. Blocks.+-- | Get all elements of the given tag name. getElementsByTagName :: Window -- ^ Browser window -> String -- ^ The tag name.- -> IO [Element] -- ^ All elements with that tag name.-getElementsByTagName window name =- mapM fromAlive =<< Core.getElementsByTagName window name+ -> UI [Element] -- ^ All elements with that tag name.+getElementsByTagName _ tag =+ mapM fromJSObject =<< callFunction (ffi "document.getElementsByTagName(%1)" tag) --- | Get an element by a particular ID. Blocks.+-- | Get an element by a particular ID. getElementById :: Window -- ^ Browser window -> String -- ^ The ID string.- -> IO (Maybe Element) -- ^ Element (if any) with given ID.-getElementById window id = listToMaybe `fmap` getElementsById window [id]+ -> UI (Maybe Element) -- ^ Element (if any) with given ID.+getElementById _ ident =+ E.handle (\(_ :: JS.JavaScriptException) -> return Nothing) $+ fmap Just . fromJSObject+ =<< callFunction (ffi "document.getElementById(%1)" ident) --- | Get a list of elements by particular IDs. Blocks.-getElementsById+-- | Get a list of elements by particular class.+getElementsByClassName :: Window -- ^ Browser window- -> [String] -- ^ The ID string.- -> IO [Element] -- ^ Elements with given ID.-getElementsById window name =- mapM fromAlive =<< Core.getElementsById window name--{------------------------------------------------------------------------------ Oddball-------------------------------------------------------------------------------}-audioPlay = updateElement Core.audioPlay---- Turn a jQuery property @.prop()@ into an attribute.-fromProp :: String -> (JSValue -> a) -> (a -> JSValue) -> Attr Element a-fromProp name from to = mkReadWriteAttr get set- where- set x = updateElement (Core.setProp name $ to x)- get (Element ref) = do- me <- readMVar ref- case me of- Limbo _ _ -> error "'checked' attribute: element must be in a browser window"- Alive e -> from <$> Core.getProp name e+ -> String -- ^ The class string.+ -> UI [Element] -- ^ Elements with given class.+getElementsByClassName _ s =+ mapM fromJSObject+ =<< callFunction (ffi "document.getElementsByClassName(%1)" s) {----------------------------------------------------------------------------- Layout ------------------------------------------------------------------------------} -- | Align given elements in a row. Special case of 'grid'.-row :: [IO Element] -> IO Element+row :: [UI Element] -> UI Element row xs = grid [xs] -- | Align given elements in a column. Special case of 'grid'.-column :: [IO Element] -> IO Element+column :: [UI Element] -> UI Element column = grid . map (:[]) -- | Align given elements in a rectangular grid.@@ -362,14 +219,14 @@ -- You can customatize the actual layout by assigning an @id@ to the element -- and changing the @.table@, @.table-row@ and @table-column@ -- classes in a custom CSS file.-grid :: [[IO Element]] -> IO Element+grid :: [[UI Element]] -> UI Element grid mrows = do rows0 <- mapM (sequence) mrows- + rows <- forM rows0 $ \row0 -> do- row <- forM row0 $ \entry ->+ row1 <- forM row0 $ \entry -> wrap "table-cell" [entry]- wrap "table-row" row+ wrap "table-row" row1 wrap "table" rows where@@ -378,39 +235,35 @@ {----------------------------------------------------------------------------- Events ------------------------------------------------------------------------------}--- | Obtain DOM event for a given element.-domEvent- :: String- -- ^ Event name. A full list can be found at- -- <http://www.w3schools.com/jsref/dom_obj_event.asp>.- -- Note that the @on@-prefix is not included,- -- the name is @click@ and so on.- -> Element -- ^ Element where the event is to occur.- -> Event EventData-domEvent name element = Control.Event.Event $ \handler -> do- ref <- newIORef $ return ()- let- -- register handler and remember unregister function- register' = flip updateElement element $ \e -> do- unregister <- register (Core.bind name e) handler- writeIORef ref unregister- - -- update element to unregister the event handler- unregister' = flip updateElement element $ \_ -> do- join $ readIORef ref- - register'- return unregister'- -- | Convenience function to register 'Event's for 'Element's. -- -- Example usage. -- -- > on click element $ \_ -> ...-on :: (element -> Event a) -> element -> (a -> IO void) -> IO ()-on f x h = register (f x) (void . h) >> return ()+on :: (element -> Event a) -> element -> (a -> UI void) -> UI ()+on f x = void . onEvent (f x) +-- | Register an 'UI' action to be executed whenever the 'Event' happens.+--+-- FIXME: Should be unified with 'on'?+onEvent :: Event a -> (a -> UI void) -> UI (UI ())+onEvent e h = do+ window <- askWindow+ let flush = liftJSWindow $ \w -> do+ mode <- JS.getCallBufferMode w+ case mode of+ FlushOften -> JS.flushCallBuffer w+ _ -> return ()+ unregister <- liftIO $ register e (void . runUI window . (>> flush) . h)+ return (liftIO unregister) +-- | Execute a 'UI' action whenever a 'Behavior' changes.+-- Use sparingly, it is recommended that you use 'sink' instead.+onChanges :: Behavior a -> (a -> UI void) -> UI ()+onChanges b f = do+ window <- askWindow+ liftIO $ Reactive.onChange b (void . runUI window . f)+ {----------------------------------------------------------------------------- Attributes ------------------------------------------------------------------------------}@@ -431,20 +284,9 @@ (#) = flip ($) -- | Convenient combinator for setting the CSS class on element creation.-(#.) :: IO Element -> String -> IO Element+(#.) :: UI Element -> String -> UI Element (#.) mx s = mx # set (attr "class") s ---- | Convience synonym for 'return' to make elements work well with 'set'.------ Example usage.------ > e <- mkElement "button"--- > element e # set text "Ok"-element :: Element -> IO Element-element = return-- -- | Attributes can be 'set' and 'get'. type Attr x a = ReadWriteAttr x a a @@ -456,31 +298,93 @@ -- | Generalized attribute with different types for getting and setting. data ReadWriteAttr x i o = ReadWriteAttr- { get' :: x -> IO o- , set' :: i -> x -> IO ()+ { get' :: x -> UI o+ , set' :: i -> x -> UI () } --- | Set value of an attribute in the 'IO' monad.+instance Functor (ReadWriteAttr x i) where+ fmap f = bimapAttr id f++-- | Map input and output type of an attribute.+bimapAttr :: (i' -> i) -> (o -> o')+ -> ReadWriteAttr x i o -> ReadWriteAttr x i' o'+bimapAttr from to attribute = attribute+ { get' = fmap to . get' attribute+ , set' = \i' -> set' attribute (from i')+ }++-- | Set value of an attribute in the 'UI' monad. -- Best used in conjunction with '#'.-set :: MonadIO m => ReadWriteAttr x i o -> i -> m x -> m x-set attr i mx = do { x <- mx; liftIO (set' attr i x); return x; }+set :: ReadWriteAttr x i o -> i -> UI x -> UI x+set attr i mx = do { x <- mx; set' attr i x; return x; } +-- | Set the value of an attribute to a 'Behavior', that is a time-varying value.+--+-- Note: For reasons of efficiency, the attribute is only+-- updated when the value changes.+sink :: ReadWriteAttr x i o -> Behavior i -> UI x -> UI x+sink attribute bi mx = do+ x <- mx+ window <- askWindow+ liftIOLater $ do+ i0 <- currentValue bi+ runUI window $ set' attribute i0 x+ Reactive.onChange bi $ \i -> runUI window $ set' attribute i x+ return x+ -- | Get attribute value.-get :: ReadWriteAttr x i o -> x -> IO o-get = get'+get :: ReadWriteAttr x i o -> x -> UI o+get attribute = get' attribute -- | Build an attribute from a getter and a setter. mkReadWriteAttr- :: (x -> IO o) -- ^ Getter.- -> (i -> x -> IO ()) -- ^ Setter.+ :: (x -> UI o) -- ^ Getter.+ -> (i -> x -> UI ()) -- ^ Setter. -> ReadWriteAttr x i o-mkReadWriteAttr get set = ReadWriteAttr { get' = get, set' = set }+mkReadWriteAttr geti seto = ReadWriteAttr { get' = geti, set' = seto } -- | Build attribute from a getter.-mkReadAttr :: (x -> IO o) -> ReadAttr x o-mkReadAttr get = mkReadWriteAttr get (\_ _ -> return ())+mkReadAttr :: (x -> UI o) -> ReadAttr x o+mkReadAttr geti = mkReadWriteAttr geti (\_ _ -> return ()) -- | Build attribute from a setter.-mkWriteAttr :: (i -> x -> IO ()) -> WriteAttr x i-mkWriteAttr set = mkReadWriteAttr (\_ -> return ()) set+mkWriteAttr :: (i -> x -> UI ()) -> WriteAttr x i+mkWriteAttr seto = mkReadWriteAttr (\_ -> return ()) seto +-- | Turn a jQuery property @.prop()@ into an attribute.+fromJQueryProp :: String -> (JSON.Value -> a) -> (a -> JSON.Value) -> Attr Element a+fromJQueryProp name from to = mkReadWriteAttr geti seto+ where+ seto v el = runFunction $ ffi "$(%1).prop(%2,%3)" el name (to v)+ geti el = fmap from $ callFunction $ ffi "$(%1).prop(%2)" el name++-- | Turn a JavaScript object property @.prop = ...@ into an attribute.+fromObjectProperty :: (FromJS a, ToJS a) => String -> Attr Element a+fromObjectProperty name = mkReadWriteAttr geti seto+ where+ seto v el = runFunction $ ffi ("%1." ++ name ++ " = %2") el v+ geti el = callFunction $ ffi ("%1." ++ name) el++{-----------------------------------------------------------------------------+ Widget class+------------------------------------------------------------------------------}+-- | Widgets are data types that have a visual representation.+class Widget w where+ getElement :: w -> Element++instance Widget Element where+ getElement = id++-- | Convenience synonym for 'return' to make elements work well with 'set'.+-- Also works on 'Widget's.+--+-- Example usage.+--+-- > e <- mkElement "button"+-- > element e # set text "Ok"+element :: MonadIO m => Widget w => w -> m Element+element = return . getElement++-- | Convenience synonym for 'return' to make widgets work well with 'set'.+widget :: Widget w => w -> UI w+widget = return
src/Graphics/UI/Threepenny/DragNDrop.hs view
@@ -70,11 +70,11 @@ -- Change this to 'Maybe String' instead. type DragData = String --withDragData = fmap extract+withDragData :: Event EventData -> Event String+withDragData = fmap (extract . unsafeFromJSON) where- extract (EventData [Just s]) = s- extract _ = ""+ extract [s] = s+ extract _ = "" -- | Occurs periodically while the element is being dragged around. drag :: Element -> Event DragData
src/Graphics/UI/Threepenny/Elements.hs view
@@ -1,10 +1,11 @@+{-# OPTIONS_GHC -Wno-missing-signatures #-} -- | Predefined DOM elements, for convenience. module Graphics.UI.Threepenny.Elements ( -- * Combinations and utilities addStyleSheet, -- text, new,- + -- * Primitive HTML elements address, a, anchor, applet, area, audio, basefont, big, blockquote, body, bold, br, button,@@ -13,7 +14,7 @@ dterm, emphasize, fieldset, font, form, frame, frameset, h1, h2, h3, h4, h5, h6, header, hr, img, image, input, italics,- keyboard, legend, li, link, map, meta, noframes, olist, option,+ keyboard, label, legend, li, link, map, meta, noframes, olist, option, p, paragraph, param, pre, sample, select, small, source, span, strong, sub, sup, table, td, textarea, th, thebase,@@ -21,10 +22,9 @@ underline, variable, video, ) where -import Control.Monad-import Control.Monad.Trans.Reader-import Prelude hiding (span,div,map)-import Graphics.UI.Threepenny.Core+import Control.Monad+import Graphics.UI.Threepenny.Core+import Prelude hiding (div, map, span) {----------------------------------------------------------------------------- Combinations@@ -38,7 +38,7 @@ addStyleSheet :: Window -> FilePath- -> IO ()+ -> UI () addStyleSheet w filename = void $ do el <- mkElement "link" # set (attr "rel" ) "stylesheet"@@ -47,12 +47,12 @@ getHead w #+ [element el] -- | Make a new @div@ element, synonym for 'div'.-new :: IO Element+new :: UI Element new = div {----------------------------------------------------------------------------- Primitives- + Taken from the HTML library (BSD3 license) http://hackage.haskell.org/package/html ------------------------------------------------------------------------------}@@ -101,6 +101,7 @@ input = itag "input" italics = tag "i" keyboard = tag "kbd"+label = tag "label" legend = tag "legend" li = tag "li" link = tag "link"
src/Graphics/UI/Threepenny/Events.hs view
@@ -1,40 +1,122 @@ module Graphics.UI.Threepenny.Events ( -- * Synopsis- -- | Common DOM events, for convenience.- - -- * Documentation- click, mousemove, hover, blur, leave,- keyup, keydown,+ -- | Events on DOM elements.++ -- * Convenience events+ valueChange, selectionChange, checkedChange,++ -- * Standard DOM events+ click, contextmenu, mousemove, mousedown, mouseup,+ hover, leave,+ focus, blur,+ KeyCode, keyup, keydown, keypress,++ -- * Migration+ roundCoordinates ) where +import Graphics.UI.Threepenny.Attributes import Graphics.UI.Threepenny.Core +silence :: Event a -> Event () silence = fmap (const ()) +{-----------------------------------------------------------------------------+ Events+------------------------------------------------------------------------------}+-- | Event that occurs when the /user/ changes the value of the input element.+valueChange :: Element -> Event String+valueChange el = unsafeMapUI el (const $ get value el) (domEvent "keydown" el)++unsafeMapUI :: Element -> (t -> UI b) -> Event t -> Event b+unsafeMapUI el f = unsafeMapIO (\a -> getWindow el >>= \w -> runUI w (f a))++-- | Event that occurs when the /user/ changes the selection of a @<select>@ element.+selectionChange :: Element -> Event (Maybe Int)+selectionChange el = unsafeMapUI el (const $ get selection el) (click el)++-- | Event that occurs when the /user/ changes the checked status of an input+-- element of type checkbox.+checkedChange :: Element -> Event Bool+checkedChange el = unsafeMapUI el (const $ get checked el) (click el)++{-----------------------------------------------------------------------------+ DOM Events+------------------------------------------------------------------------------} -- | Mouse click. click :: Element -> Event () click = silence . domEvent "click" --- | Mouse hovering over an element.+-- | Context menu event.+--+-- The mouse coordinates are relative to the upper left corner of the element.+contextmenu :: Element -> Event (Double,Double)+contextmenu = fmap readCoordinates . domEvent "contextmenu"++-- | Mouse enters an element. hover :: Element -> Event () hover = silence . domEvent "mouseenter" -- | Event that periodically occurs while the mouse is moving over an element. -- -- The event value represents the mouse coordinates--- relative to the upper left corner of the entire page.+-- relative to the upper left corner of the element. -- -- Note: The @<body>@ element responds to mouse move events, -- but only in the area occupied by actual content, -- not the whole browser window.-mousemove :: Element -> Event (Int,Int)-mousemove = fmap readPair . domEvent "mousemove"- where readPair (EventData (Just x:Just y:_)) = (read x, read y)+mousemove :: Element -> Event (Double,Double)+mousemove = fmap readCoordinates . domEvent "mousemove" +-- NB:+-- The return types of mouse events have been redefined from @long@+-- to @double@ in the CSS Object Model View Model working draft,+-- which browsers have begun to adopt.+-- See https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent#Specifications+-- and https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/pageX+--+-- Similarly, we rely on jQuery's @.offset()@ to return+-- coordinates relative to the upper left corner of the+-- element, and this may return fractional data.+-- https://api.jquery.com/offset/+readCoordinates :: EventData -> (Double,Double)+readCoordinates json = (x,y)+ where [x,y] = unsafeFromJSON json++-- | Round a pair of `Double` to the next integers.+-- This function helps you migrate from previous versions of Threepenny-GUI.+--+-- The return types of mouse events (`mousedown`, `mouseup`, `mousemove`, `contextmenu`) +-- have been redefined from @long@+-- to @double@ in the CSS Object Model View Model working draft,+-- which browsers have begun to adopt.+--+-- See https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent#Specifications+--+-- and https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/pageX+roundCoordinates :: (Double,Double) -> (Int,Int)+roundCoordinates (x,y) = (round x, round y)++-- | Mouse down event.+--+-- The mouse coordinates are relative to the upper left corner of the element.+mousedown :: Element -> Event (Double,Double)+mousedown = fmap readCoordinates . domEvent "mousedown"++-- | Mouse up event.+--+-- The mouse coordinates are relative to the upper left corner of the element.+mouseup :: Element -> Event (Double,Double)+mouseup = fmap readCoordinates . domEvent "mouseup"+ -- | Mouse leaving an element. leave :: Element -> Event () leave = silence . domEvent "mouseleave" +-- | Element receives focus.+focus :: Element -> Event ()+focus = silence . domEvent "focus"+ -- | Element loses focus. blur :: Element -> Event () blur = silence . domEvent "blur"@@ -43,11 +125,16 @@ type KeyCode = Int -- | Key pressed while element has focus.+-- Returns the keycode (as opposed to the ASCII value) of any key, including+-- SHIFT, CTRL and arrow keys. keydown :: Element -> Event KeyCode-keydown = fmap read1 . domEvent "keydown"+keydown = fmap unsafeFromJSON . domEvent "keydown" -- | Key released while element has focus. keyup :: Element -> Event KeyCode-keyup = fmap read1 . domEvent "keyup"+keyup = fmap unsafeFromJSON . domEvent "keyup" -read1 (EventData (Just s:_)) = read s+-- | Key pressed while element has focus.+-- Returns the actual character, taking into account SHIFT or CAPS LOCK.+keypress :: Element -> Event Char+keypress = fmap (toEnum . read . head . unsafeFromJSON) . domEvent "keypress"
+ src/Graphics/UI/Threepenny/Internal.hs view
@@ -0,0 +1,382 @@+{-# LANGUAGE DeriveDataTypeable #-}+module Graphics.UI.Threepenny.Internal (+ -- * Synopsis+ -- | Internal core:+ -- 'UI' monad, integrating FRP and JavaScript FFI. garbage collection++ -- * Documentation+ Window, disconnect,+ startGUI, loadFile, loadDirectory,++ UI, runUI, MonadUI(..), liftIOLater, askWindow, liftJSWindow,++ FFI, FromJS, ToJS, JSFunction, JSObject, ffi,+ runFunction, callFunction,+ CallBufferMode(..), setCallBufferMode, flushCallBuffer,+ ffiExport, debug, timestamp,++ Element(toJSObject), fromJSObject, getWindow,+ mkElementNamespace, mkElement, delete, appendChild, clearChildren,++ EventData, domEvent, unsafeFromJSON,+ ) where++import Control.Monad+import Control.Monad.Catch+import Control.Monad.Fix+import Control.Monad.IO.Class+import qualified Control.Monad.Trans.RWS.Lazy as Monad+import Data.Dynamic (Typeable)++import qualified Data.Aeson as JSON+import qualified Foreign.JavaScript as JS+import qualified Foreign.RemotePtr as Foreign++import qualified Reactive.Threepenny as RB++import Foreign.JavaScript hiding+ (runFunction, callFunction, setCallBufferMode, flushCallBuffer+ ,debug, timestamp, Window, loadFile, loadDirectory)++{-----------------------------------------------------------------------------+ Custom Window type+------------------------------------------------------------------------------}+-- | The type 'Window' represents a browser window.+data Window = Window+ { jsWindow :: JS.Window -- JavaScript window+ , eDisconnect :: RB.Event () -- event that happens when client disconnects+ , wEvents :: Foreign.Vendor Events+ -- events associated to 'Element's+ , wChildren :: Foreign.Vendor ()+ -- children reachable from 'Element's+ }++-- | Start server for GUI sessions.+startGUI+ :: Config -- ^ Server configuration.+ -> (Window -> UI ()) -- ^ Action to run whenever a client browser connects.+ -> IO ()+startGUI config initialize = JS.serve config $ \w -> do+ -- set up disconnect event+ (eDisconnect, handleDisconnect) <- RB.newEvent+ JS.onDisconnect w $ handleDisconnect ()++ -- make window+ wEvents <- Foreign.newVendor+ wChildren <- Foreign.newVendor+ let window = Window+ { jsWindow = w+ , eDisconnect = eDisconnect+ , wEvents = wEvents+ , wChildren = wChildren+ }++ -- run initialization+ runUI window $ initialize window++-- | Event that occurs whenever the client has disconnected,+-- be it by closing the browser window or by exception.+--+-- Note: DOM Elements in a browser window that has been closed+-- can no longer be manipulated.+disconnect :: Window -> RB.Event ()+disconnect = eDisconnect++-- | Begin to serve a local file with a given 'MimeType' under a relative URI.+loadFile+ :: String -- ^ MIME type+ -> FilePath -- ^ Local path to the file+ -> UI String -- ^ Relative URI under which this file is now accessible+loadFile x y = liftJSWindow $ \w -> JS.loadFile (JS.getServer w) x y++-- | Make a local directory available under a relative URI.+loadDirectory :: FilePath -> UI String+loadDirectory x = liftJSWindow $ \w -> JS.loadDirectory (JS.getServer w) x++{-----------------------------------------------------------------------------+ Elements+------------------------------------------------------------------------------}+type Events = String -> RB.Event JSON.Value++-- Reachability information for children of an 'Element'.+-- The children of an element are always reachable from this RemotePtr.+type Children = Foreign.RemotePtr ()++data Element = Element+ { toJSObject :: JS.JSObject -- ^ Access to the primitive 'JS.JSObject' for roll-your-own foreign calls.+ , elEvents :: Events -- ^ FRP event mapping+ , elChildren :: Children -- ^ The children of this element+ , elWindow :: Window -- ^ Window in which the element was created+ } deriving (Typeable)++instance ToJS Element where+ render = render . toJSObject++getWindow :: Element -> IO Window+getWindow = return . elWindow++-- | Lookup or create reachability information for the children of+-- an element that is represented by a JavaScript object.+getChildren :: JS.JSObject -> Window -> IO Children+getChildren el Window{ wChildren = wChildren } =+ Foreign.withRemotePtr el $ \coupon _ -> do+ mptr <- Foreign.lookup coupon wChildren+ case mptr of+ Nothing -> do+ -- Create new pointer for reachability information.+ ptr <- Foreign.newRemotePtr coupon () wChildren+ Foreign.addReachable el ptr+ return ptr+ Just p ->+ -- Return existing information+ return p++-- | Convert JavaScript object into an Element by attaching relevant information.+-- The JavaScript object may still be subject to garbage collection.+fromJSObject0 :: JS.JSObject -> Window -> IO Element+fromJSObject0 el window = do+ events <- getEvents el window+ children <- getChildren el window+ return $ Element el events children window++-- | Convert JavaScript object into an element.+--+-- FIXME: For the purpose of garbage collection, this element+-- will always be reachable from the root.+fromJSObject :: JS.JSObject -> UI Element+fromJSObject el = do+ window <- askWindow+ liftIO $ do+ Foreign.addReachable (JS.root $ jsWindow window) el+ fromJSObject0 el window++-- | Add lazy FRP events to a JavaScript object.+addEvents :: JS.JSObject -> Window -> IO Events+addEvents el Window{ jsWindow = w, wEvents = wEvents } = do+ -- Lazily create FRP events whenever they are needed.+ let initializeEvent (name,_,handler) = do+ handlerPtr <- JS.exportHandler w handler+ -- make handler reachable from element+ Foreign.addReachable el handlerPtr+ JS.runFunction w $+ ffi "Haskell.on(%1,%2,%3)" el name handlerPtr++ events <- RB.newEventsNamed initializeEvent++ -- Create new pointer and add reachability.+ Foreign.withRemotePtr el $ \coupon _ -> do+ ptr <- Foreign.newRemotePtr coupon events wEvents+ Foreign.addReachable el ptr++ return events++-- | Lookup or create lazy events for a JavaScript object.+getEvents :: JS.JSObject -> Window -> IO Events+getEvents el window@Window{ wEvents = wEvents } = do+ Foreign.withRemotePtr el $ \coupon _ -> do+ mptr <- Foreign.lookup coupon wEvents+ case mptr of+ Nothing -> addEvents el window+ Just p -> Foreign.withRemotePtr p $ \_ -> return++-- | Events may carry data. At the moment, they may return+-- a single JSON value, as defined in the "Data.Aeson" module.+type EventData = JSON.Value++-- | Convert event data to a Haskell value.+-- Throws an exception when the data cannot be converted.+unsafeFromJSON :: JSON.FromJSON a => EventData -> a+unsafeFromJSON x = let JSON.Success y = JSON.fromJSON x in y++-- | Obtain DOM event for a given element.+domEvent+ :: String+ -- ^ Event name. A full list can be found at+ -- <http://www.w3schools.com/jsref/dom_obj_event.asp>.+ -- Note that the @on@-prefix is not included,+ -- the name is @click@ and so on.+ -> Element -- ^ Element where the event is to occur.+ -> RB.Event EventData+domEvent name el = elEvents el name++-- | Make a new DOM element with a given tag name.+mkElement :: String -> UI Element+mkElement = mkElementNamespace Nothing++-- | Make a new DOM element with a namespace and a given tag name.+--+-- A namespace 'Nothing' corresponds to the default HTML namespace.+mkElementNamespace :: Maybe String -> String -> UI Element+mkElementNamespace namespace tag = do+ window <- askWindow+ let w = jsWindow window+ liftIO $ do+ el <- JS.unsafeCreateJSObject w $ case namespace of+ Nothing -> ffi "document.createElement(%1)" tag+ Just ns -> ffi "document.createElementNS(%1,%2)" ns tag+ fromJSObject0 el window++-- | Delete the given element.+--+-- This operation removes the element from the browser window DOM+-- and marks it for garbage collection on the Haskell side.+-- The element is unusable afterwards.+--+-- NOTE: If you wish to temporarily remove an element from the DOM tree,+-- change the 'children' property of its parent element instead.+delete :: Element -> UI ()+delete el = liftJSWindow $ \w -> do+ JS.runFunction w $ ffi "$(%1).detach()" el+ Foreign.destroy $ toJSObject el++-- | Remove all child elements.+clearChildren :: Element -> UI ()+clearChildren element = liftJSWindow $ \w -> do+ let el = toJSObject element+ Foreign.withRemotePtr el $ \_ _ -> do+ -- Previous children are no longer reachable from this element+ JS.runFunction w $ ffi "$(%1).contents().detach()" el+ Foreign.clearReachable (elChildren element)++-- | Append a child element.+appendChild :: Element -> Element -> UI ()+appendChild parent child = liftJSWindow $ \w -> do+ -- FIXME: We have to stop the child being reachable from its+ -- /previous/ parent.+ Foreign.addReachable (elChildren parent) (toJSObject child)+ JS.runFunction w $ ffi "$(%1).append($(%2))" (toJSObject parent) (toJSObject child)+++{-----------------------------------------------------------------------------+ UI monad+------------------------------------------------------------------------------}+{- |++User interface elements are created and manipulated in the 'UI' monad.++This monad is essentially just a thin wrapper around the familiar 'IO' monad.+Use the 'liftIO' function to access 'IO' operations like reading+and writing from files.++There are several subtle reasons why Threepenny+uses a custom 'UI' monad instead of the standard 'IO' monad:++* More convenience when calling JavaScript.+The monad keeps track of a browser 'Window' context+in which JavaScript function calls are executed.++* Recursion for functional reactive programming.++-}+newtype UI a = UI { unUI :: Monad.RWST Window [IO ()] () IO a }+ deriving (Typeable)++class (Monad m) => MonadUI m where+ -- | Lift a computation from the 'UI' monad.+ liftUI :: UI a -> m a++instance MonadUI UI where+ liftUI = id++-- | Access to the primitive 'JS.Window' object,+-- for roll-your-own JS foreign calls.+liftJSWindow :: (JS.Window -> IO a) -> UI a+liftJSWindow f = askWindow >>= liftIO . f . jsWindow++instance Functor UI where+ fmap f = UI . fmap f . unUI++instance Applicative UI where+ pure = UI . pure+ (<*>) = ap++instance Monad UI where+ return = pure+ m >>= k = UI $ unUI m >>= unUI . k++instance MonadIO UI where+ liftIO = UI . liftIO++instance MonadFix UI where+ mfix f = UI $ mfix (unUI . f)++instance MonadThrow UI where+ throwM = UI . throwM++instance MonadCatch UI where+ catch m f = UI $ catch (unUI m) (unUI . f)++-- | Execute an 'UI' action in a particular browser window.+-- Also runs all scheduled 'IO' actions.+runUI :: Window -> UI a -> IO a+runUI window m = do+ (a, _, actions) <- Monad.runRWST (unUI m) window ()+ sequence_ actions+ return a++-- | Retrieve current 'Window' context in the 'UI' monad.+askWindow :: UI Window+askWindow = UI Monad.ask++-- | Schedule an 'IO' action to be run later.+liftIOLater :: IO () -> UI ()+liftIOLater x = UI $ Monad.tell [x]++{-----------------------------------------------------------------------------+ FFI+------------------------------------------------------------------------------}+-- | Run a JavaScript function, but do not wait for a result.+--+-- The client window uses JavaScript's @eval()@ function to run the code.+--+-- NOTE: The JavaScript function need not be executed immediately,+-- it can be buffered and sent to the browser window at a later time.+-- See 'setCallBufferMode' and 'flushCallBuffer' for more.+runFunction :: JSFunction () -> UI ()+runFunction fun = liftJSWindow $ \w -> JS.runFunction w fun++-- | Call a JavaScript function and wait for the result.+--+-- The client window uses JavaScript's @eval()@ function to run the code.+callFunction :: JSFunction a -> UI a+callFunction fun = liftJSWindow $ \w -> JS.callFunction w fun++-- | Set the call buffering mode for the browser window.+setCallBufferMode :: CallBufferMode -> UI ()+setCallBufferMode x = liftJSWindow $ \w -> JS.setCallBufferMode w x++-- | Flush the call buffer,+-- i.e. send all outstanding JavaScript to the client in one single message.+flushCallBuffer :: UI ()+flushCallBuffer = liftJSWindow $ \w -> JS.flushCallBuffer w++-- | Export the given Haskell function so that it can be called+-- from JavaScript code.+--+-- NOTE: At the moment, the 'JSObject' representing the exported function+-- will be referenced by the browser 'Window' in which it was created,+-- preventing garbage collection until this browser 'Window' is disconnected.+--+-- This makes it possible to use it as an event handler on the JavaScript side,+-- but it also means that the Haskell runtime has no way to detect+-- early when it is no longer needed.+--+-- In contrast, if you use the function 'domEvent' to register an+-- event handler to an 'Element',+-- then the handler will be garbage collected+-- as soon as the associated 'Element' is garbage collected.+ffiExport :: JS.IsHandler a => a -> UI JSObject+ffiExport fun = liftJSWindow $ \w -> do+ handlerPtr <- JS.exportHandler w fun+ Foreign.addReachable (JS.root w) handlerPtr+ return handlerPtr++-- | Print a message on the client console if the client has debugging enabled.+debug :: String -> UI ()+debug s = liftJSWindow $ \w -> JS.debug w s++-- | Print a timestamp and the difference to the previous timestamp+-- on the client console if the client has debugging enabled.+timestamp :: UI ()+timestamp = liftJSWindow JS.timestamp+
− src/Graphics/UI/Threepenny/Internal/Core.hs
@@ -1,630 +0,0 @@-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE OverloadedStrings #-}-{-# OPTIONS -fno-warn-name-shadowing #-}---- | The main Threepenny module.-------------------------------------------------------------------------------------- Exports--module Graphics.UI.Threepenny.Internal.Core- (- -- * Server running- serve- ,loadFile- ,loadDirectory- - -- * Event handling- -- $eventhandling- ,bind- ,handleEvent- ,handleEvents- ,module Control.Event- - -- * Setting attributes- -- $settingattributes- ,setStyle- ,setAttr- ,setProp- ,setText- ,setHtml- ,setTitle- ,emptyEl- ,delete- - -- * Manipulating tree structure- -- $treestructure- ,newElement- ,appendElementTo- - -- * Querying- -- $querying- ,getHead- ,getBody- ,getElementsByTagName- ,getElementsById- ,getWindow- ,getProp- ,getValue- ,getValuesList- ,readValue- ,readValuesList- ,getRequestCookies- ,getRequestLocation- - -- * Utilities- ,debug- ,clear- ,callDeferredFunction- ,atomic-- -- * JavaScript FFI- ,ToJS, FFI, ffi, JSFunction- ,runFunction, callFunction- - -- * Oddball- ,audioPlay- - -- * Types- ,Window- ,Element- ,Config(..)- ,EventData(..)- ) where-------------------------------------------------------------------------------------- Imports--import Graphics.UI.Threepenny.Internal.Types as Threepenny-import Graphics.UI.Threepenny.Internal.Resources--import Control.Applicative-import Control.Concurrent-import Control.Concurrent.Chan.Extra-import Control.Concurrent.Delay-import qualified Control.Exception as E-import Control.Event-import Control.Monad.IO-import Control.Monad.Reader-import Data.ByteString (ByteString)-import Data.ByteString.UTF8 (toString,fromString)-import Data.Map (Map)-import qualified Data.Map as M-import Data.Maybe-import Data.Text (Text,pack,unpack)-import qualified Data.Text as Text-import Data.Text.Encoding-import Data.Time-import Network.URI-import Prelude hiding (init)-import Safe-import Snap.Core-import Snap.Http.Server hiding (Config)-import Snap.Util.FileServe-import System.FilePath-import Text.JSON.Generic---{------------------------------------------------------------------------------ Server running-------------------------------------------------------------------------------}-newServerState :: IO ServerState-newServerState = ServerState - <$> newMVar M.empty- <*> newMVar (0,M.empty)- <*> newMVar (0,M.empty)---- | Run a TP server with Snap on the specified port and the given--- worker action.-serve :: Config -> (Session -> IO ()) -> IO () -- ^ A TP server.-serve Config{..} worker = do- server <- newServerState- _ <- forkIO $ custodian 30 (sSessions server)- httpServe config (router tpCustomHTML tpStatic worker server)- where config = setPort tpPort defaultConfig---- | Kill sessions after at least n seconds of disconnectedness.-custodian :: Integer -> MVar Sessions -> IO ()-custodian seconds sessions = forever $ do- delaySeconds seconds- modifyMVar_ sessions $ \sessions -> do- killed <- fmap catMaybes $ forM (M.assocs sessions) $ \(key,Session{..}) -> do- state <- readMVar sConnectedState- case state of- Connected -> return Nothing- Disconnected time -> do- now <- getCurrentTime- let dcSeconds = diffUTCTime now time- if (dcSeconds > fromIntegral seconds)- then do killThread sThreadId- return (Just key)- else return Nothing- - return (M.filterWithKey (\k _ -> not (k `elem` killed)) sessions)---- Route requests. If the initFile is Nothing, then a default--- file will be served at /.-router- :: Maybe FilePath- -> FilePath- -> (Session -> IO a)- -> ServerState- -> Snap ()-router customHTML wwwroot worker server =- route [("/static" , serveDirectory wwwroot)- ,("/" , root)- ,("/driver/threepenny-gui.js" , writeText jsDriverCode )- ,("/driver/threepenny-gui.css" , writeText cssDriverCode)- ,("/init" , init worker server)- ,("/poll" , withSession server poll )- ,("/signal" , withSession server signal)- ,("/file/:name" ,- withFilepath (sFiles server) (flip serveFileAs))- ,("/dir/:name" ,- withFilepath (sDirs server) (\path _ -> serveDirectory path))- ]- where- root = case customHTML of- Just file -> serveFile (wwwroot </> file)- Nothing -> writeText defaultHtmlFile---- Get a filename from a URI-withFilepath :: MVar Filepaths -> (FilePath -> MimeType -> Snap a) -> Snap a-withFilepath rDict cont = do- mName <- getParam "name"- (_,dict) <- io $ withMVar rDict return- case (\key -> M.lookup key dict) =<< mName of- Just (path,mimetype) -> cont path mimetype- Nothing -> error $ "File not loaded: " ++ show mName---- FIXME: Serving large files fails with the exception--- System.SendFile.Darwin: invalid argument (Socket is not connected)---newAssociation :: MVar Filepaths -> (FilePath, MimeType) -> IO String-newAssociation rDict (path,mimetype) = do- (old, dict) <- takeMVar rDict- let new = old + 1; key = show new ++ takeFileName path- putMVar rDict $ (new, M.insert (fromString key) (path,mimetype) dict)- return key---- | Begin to serve a local file under an URI.-loadFile :: Session -> MimeType -> FilePath -> IO String-loadFile Session{..} mimetype path = do- key <- newAssociation (sFiles sServerState) (path,mimetype)- return $ "/file/" ++ key---- | Begin to serve a local directory under an URI.-loadDirectory :: Session -> FilePath -> IO String-loadDirectory Session{..} path = do- key <- newAssociation (sDirs sServerState) (path,"")- return $ "/dir/" ++ key---- Initialize the session.-init :: (Session -> IO void) -> ServerState -> Snap ()-init sessionThread server = do- uri <- getRequestURI- params <- getRequestCookies- key <- io $ modifyMVar (sSessions server) $ \sessions -> do- let newKey = maybe 0 (+1) (lastMay (M.keys sessions))- session <- newSession server (uri,params) newKey- _ <- forkIO $ do _ <- sessionThread session; return ()- return (M.insert newKey session sessions,newKey)- modifyResponse $ setHeader "Set-Token" (fromString (show key))- withGivenSession key server poll- - where getRequestURI = do- uri <- getInput "info"- maybe (error ("Unable to parse request URI: " ++ show uri)) return (uri >>= parseURI)- getRequestCookies = do- cookies <- getsRequest rqCookies- return $ flip map cookies $ \Cookie{..} -> (toString cookieName,toString cookieValue)---- Make a new session.-newSession :: ServerState -> (URI,[(String, String)]) -> Integer -> IO Session-newSession server info token = do- signals <- newChan- instructions <- newChan- (event, handler) <- newEventsTagged- ids <- newMVar [0..]- mutex <- newMVar ()- now <- getCurrentTime- conState <- newMVar (Disconnected now)- threadId <- myThreadId- closures <- newMVar [0..]- return $ Session- { sSignals = signals- , sInstructions = instructions- , sEvent = event- , sEventHandler = handler- , sElementIds = ids- , sToken = token- , sMutex = mutex- , sConnectedState = conState- , sThreadId = threadId- , sClosures = closures- , sStartInfo = info- , sServerState = server- }- --- Respond to poll requests.-poll :: Session -> Snap ()-poll Session{..} = do- let setDisconnected = do- now <- getCurrentTime- modifyMVar_ sConnectedState (const (return (Disconnected now)))- io $ modifyMVar_ sConnectedState (const (return Connected))- threadId <- io $ myThreadId- _ <- io $ forkIO $ do- delaySeconds $ 60 * 5 -- Force kill after 5 minutes.- killThread threadId- instructions <- io $ E.catch (readAvailableChan sInstructions) $ \e -> do- when (e == E.ThreadKilled) $ do- setDisconnected- E.throw e- writeJson instructions---- Write JSON to output.-writeJson :: (MonadSnap m, JSON a) => a -> m ()-writeJson json = do- modifyResponse $ setContentType "application/json"- (writeString . (\x -> showJSValue x "") . showJSON) json---- Write a string to output.-writeString :: (MonadSnap m) => String -> m ()-writeString = writeText . pack---- Handle signals sent from the client.-signal :: Session -> Snap ()-signal Session{..} = do- input <- getInput "signal"- case input of- Just signalJson -> do- let signal = decode signalJson- case signal of- Ok signal -> io $ writeChan sSignals signal- Error err -> error err- Nothing -> error $ "Unable to parse " ++ show input---- Get a text input from snap.-getInput :: (MonadSnap f) => ByteString -> f (Maybe String)-getInput = fmap (fmap (unpack . decodeUtf8)) . getParam---- Run a snap action with the given session.-withSession :: ServerState -> (Session -> Snap a) -> Snap a-withSession server cont = do- token <- readInput "token"- case token of- Nothing -> error $ "Invalid session token format."- Just token -> withGivenSession token server cont- --- Do something with the session given by its token id.-withGivenSession :: Integer -> ServerState -> (Session -> Snap a) -> Snap a-withGivenSession token ServerState{..} cont = do- sessions <- io $ withMVar sSessions return- case M.lookup token sessions of- Nothing -> error $ "Nonexistant token: " ++ show token- Just session -> cont session---- Read an input from snap.-readInput :: (MonadSnap f,Read a) => ByteString -> f (Maybe a)-readInput = fmap (>>= readMay) . getInput---{------------------------------------------------------------------------------ Event handling-------------------------------------------------------------------------------}-{- $eventhandling-- To bind events to elements, use the 'bind' function.-- To handle DOM events, use the 'handleEvent' function, or the- 'handleEvents' function which will block forever.-- See the rest of this section for some helpful functions that do- common binding, such as clicks, hovers, etc.--}---- | Handle events signalled from the client.-handleEvents :: Window -> IO ()-handleEvents window = forever $ handleEvent window---- | Handle one event.-handleEvent :: Window -> IO ()-handleEvent window@(Session{..}) = do- signal <- getSignal window- case signal of- Threepenny.Event (elid,eventType,params) -> do- sEventHandler ((elid,eventType), EventData params)- _ -> return ()---- Get the latest signal sent from the client.-getSignal :: Window -> IO Signal-getSignal (Session{..}) = readChan sSignals---- | Return an 'Event' associated to an 'Element'.-bind- :: String -- ^ The eventType, see any DOM documentation for a list of these.- -> Element -- ^ The element to bind to.- -> Event EventData -- ^ The event handler.-bind eventType (Element el@(ElementId elid) session) =- Control.Event.Event register- where- key = (elid, eventType)- register h = do- -- register with server- unregister <- Control.Event.register (sEvent session key) h- -- register with client- run session $ Bind eventType el (Closure key)- return unregister---- Make a uniquely numbered event handler.-newClosure :: Window -> String -> String -> ([Maybe String] -> IO ()) -> IO Closure-newClosure window@(Session{..}) eventType elid thunk = do- let key = (elid, eventType)- _ <- register (sEvent key) $ \(EventData xs) -> thunk xs- return (Closure key)--{------------------------------------------------------------------------------ Setting attributes-------------------------------------------------------------------------------}-{- $settingattributes- - Text, HTML and attributes of DOM nodes can be set using the- functions in this section. --}---- | Set the style of the given element.-setStyle :: [(String, String)] -- ^ Pairs of CSS (property,value).- -> Element -- ^ The element to update.- -> IO ()-setStyle props e@(Element el session) = run session $ SetStyle el props---- | Set the attribute of the given element.-setAttr :: String -- ^ The attribute name.- -> String -- ^ The attribute value.- -> Element -- ^ The element to update.- -> IO ()-setAttr key value e@(Element el session) = run session $ SetAttr el key value---- | Set the property of the given element.-setProp :: String -- ^ The property name.- -> JSValue -- ^ The property value.- -> Element -- ^ The element to update.- -> IO ()-setProp key value e@(Element el session) =- runFunction session $ ffi "$(%1).prop(%2,%3);" el key value---- | Set the text of the given element.-setText :: String -- ^ The plain text.- -> Element -- ^ The element to update.- -> IO ()-setText props e@(Element el session) = run session $ SetText el props---- | Set the HTML of the given element.-setHtml :: String -- ^ The HTML.- -> Element -- ^ The element to update.- -> IO ()-setHtml props e@(Element el session) = run session $ SetHtml el props---- | Set the title of the document.-setTitle- :: String -- ^ The title.- -> Window -- ^ The document window- -> IO ()-setTitle title session = run session $ SetTitle title---- | Empty the given element.-emptyEl :: Element -> IO ()-emptyEl e@(Element el session) = run session $ EmptyEl el---- | Delete the given element.-delete :: Element -> IO ()-delete e@(Element el session) = run session $ Delete el---{------------------------------------------------------------------------------ Manipulating tree structure-------------------------------------------------------------------------------}--- $treestructure------ Functions for creating, deleting, moving, appending, prepending, DOM nodes.---- | Create a new element of the given tag name.-newElement :: Window -- ^ Browser window in which context to create the element- -> String -- ^ The tag name.- -> IO Element -- ^ A tag reference. Non-blocking.-newElement session@(Session{..}) tagName = do- -- TODO: Remove the need to specify in which browser window is to be created- elid <- modifyMVar sElementIds $ \elids ->- return (tail elids,"*" ++ show (head elids) ++ ":" ++ tagName)- return (Element (ElementId elid) session)---- | Append a child element to a parent element. Non-blocking.-appendElementTo- :: Element -- ^ Parent.- -> Element -- ^ Child.- -> IO () -appendElementTo (Element parent session) e@(Element child _) =- -- TODO: Right now, parent and child need to be from the same session/browser window- -- Implement transfer of elements across browser windows- run session $ Append parent child---{------------------------------------------------------------------------------ Oddball-------------------------------------------------------------------------------}--- | Invoke the JavaScript expression @audioElement.play();@.-audioPlay :: Element -> IO ()-audioPlay (Element el session) = runFunction session $ ffi "%1.play();" el--{------------------------------------------------------------------------------ Querying the DOM-------------------------------------------------------------------------------}--- $querying--- --- The DOM can be searched for elements of a given name, and nodes can--- be inspected for their values.---- | Get all elements of the given tag name. Blocks.-getElementsByTagName- :: Window -- ^ Browser window- -> String -- ^ The tag name.- -> IO [Element] -- ^ All elements with that tag name.-getElementsByTagName window tagName =- call window (GetElementsByTagName tagName) $ \signal ->- case signal of- Elements els -> return $ Just $ [Element el window | el <- els]- _ -> return Nothing---- | Get a list of elements by particular IDs. Blocks.-getElementsById- :: Window -- ^ Browser window- -> [String] -- ^ The ID string.- -> IO [Element] -- ^ Elements with given ID.-getElementsById window ids =- call window (GetElementsById ids) $ \signal ->- case signal of- Elements els -> return $ Just [Element el window | el <- els]- _ -> return Nothing---- | Get the value of an input. Blocks.-getValue- :: Element -- ^ The element to get the value of.- -> IO String -- ^ The plain text value.-getValue e@(Element el window) =- call window (GetValue el) $ \signal ->- case signal of- Value str -> return (Just str)- _ -> return Nothing---- | Get the property of an element. Blocks.-getProp- :: String -- ^ The property name.- -> Element -- ^ The element to get the value of.- -> IO JSValue -- ^ The plain text value.-getProp prop e@(Element el window) =- callFunction window (ffi "$(%1).prop(%2)" el prop)---- | Get 'Window' associated to an 'Element'.-getWindow :: Element -> Window-getWindow (Element _ window) = window---- | Get values from inputs. Blocks. This is faster than many 'getValue' invocations.-getValuesList- :: [Element] -- ^ A list of elements to get the values of.- -> IO [String] -- ^ The list of plain text values.-getValuesList [] = return []-getValuesList es@(Element _ window : _) =- let elids = [elid | Element elid _ <- es] in- call window (GetValues elids) $ \signal ->- case signal of- Values strs -> return $ Just strs- _ -> return Nothing---- | Read a value from an input. Blocks.-readValue- :: Read a- => Element -- ^ The element to read a value from.- -> IO (Maybe a) -- ^ Maybe the read value.-readValue = liftM readMay . getValue---- | Read values from inputs. Blocks. This is faster than many 'readValue' invocations.-readValuesList- :: Read a- => [Element] -- ^ The element to read a value from.- -> IO (Maybe [a]) -- ^ Maybe the read values. All or none.-readValuesList = liftM (sequence . map readMay) . getValuesList---- | Atomically execute the given computation in the context of a browser window-atomic :: Window -> IO a -> IO a-atomic window@(Session{..}) m = do- takeMVar sMutex- ret <- m- putMVar sMutex ()- return ret----- Send an instruction and read the signal response.-call :: Session -> Instruction -> (Signal -> IO (Maybe a)) -> IO a-call session@(Session{..}) instruction withSignal = do- takeMVar sMutex- run session $ instruction- newChan <- dupChan sSignals- go sMutex newChan-- where- go mutex newChan = do- signal <- readChan newChan- result <- withSignal signal- case result of- Just signal -> do putMVar mutex ()- return signal- Nothing -> go mutex newChan---- Run the given instruction.-run :: Session -> Instruction -> IO ()-run (Session{..}) i = writeChan sInstructions i---- | Get the head of the page.-getHead :: Window -> IO Element-getHead session = return $ Element (ElementId "head") session---- | Get the body of the page.-getBody :: Window -> IO Element-getBody session = return $ Element (ElementId "body") session---- | Get the request location.-getRequestLocation :: Window -> IO URI-getRequestLocation = return . fst . sStartInfo---- | Get the request cookies.-getRequestCookies :: Window -> IO [(String,String)]-getRequestCookies = return . snd . sStartInfo--{------------------------------------------------------------------------------ Utilities-------------------------------------------------------------------------------}--- | Send a debug message to the client. The behaviour of the client--- is unspecified.-debug- :: Window -- ^ Client window- -> String -- ^ Some plain text to send to the client.- -> IO ()-debug window = run window . Debug---- | Clear the client's DOM.-clear :: Window -> IO ()-clear window = run window $ Clear ()---- | Run the given JavaScript function and carry on. Doesn't block.------ The client window uses JavaScript's @eval()@ function to run the code.-runFunction :: Window -> JSFunction () -> IO ()-runFunction session = run session . RunJSFunction . unJSCode . code---- | Run the given JavaScript function and wait for results. Blocks.------ The client window uses JavaScript's @eval()@ function to run the code.-callFunction :: Window -> JSFunction a -> IO a-callFunction window (JSFunction code marshal) = - call window (CallJSFunction . unJSCode $ code) $ \signal ->- case signal of- FunctionResult v -> case marshal window v of- Ok a -> return $ Just a- Error _ -> return Nothing- _ -> return Nothing---- | Call the given function with the given continuation. Doesn't block.-callDeferredFunction- :: Window -- ^ Browser window- -> String -- ^ The function name.- -> [String] -- ^ Parameters.- -> ([Maybe String] -> IO ()) -- ^ The continuation to call if/when the function completes.- -> IO ()-callDeferredFunction session@(Session{..}) func params closure = do- cid <- modifyMVar sClosures (\(x:xs) -> return (xs,x))- closure' <- newClosure session func (show cid) closure- run session $ CallDeferredFunction (closure',func,params)
− src/Graphics/UI/Threepenny/Internal/Resources.hs
@@ -1,54 +0,0 @@-{-# LANGUAGE QuasiQuotes, CPP #-}-module Graphics.UI.Threepenny.Internal.Resources where--import Data.Text (Text)-import qualified Data.Text as Text-import qualified Data.Text.IO as Text-import System.FilePath-import System.IO.Unsafe--#ifdef CABAL-import qualified Paths_threepenny_gui-getDataDir = fmap (</> "src" </> "Graphics" </> "UI") Paths_threepenny_gui.getDataDir--#else-getDataDir = return $ "Graphics" </> "UI"-#endif---jsDriverCode :: Text-cssDriverCode :: Text-defaultHtmlFile :: Text---jsDriverCode = unsafePerformIO $- readFiles $ words "jquery.js jquery-cookie.js driver.js" --cssDriverCode = unsafePerformIO $- readFiles ["driver.css"]--defaultHtmlFile = unsafePerformIO $- readFiles ["index.html"]--readFiles files = do- ys <- mapM (Text.readFile . getDataFile) files- return $ Text.unlines ys--getDataFile x = unsafePerformIO $ fmap (</> x) getDataDir---{---- Temporarily disabled code.--- Use quasiquotation to bundle resource files with executable.--jsDriverCode = Text.unlines $ map Text.pack- [ [include|Graphics/UI/jquery.js|]- , [include|Graphics/UI/jquery-cookie.js|]- , [include|Graphics/UI/driver.js|]- ]--cssDriverCode = Text.pack [include|Graphics/UI/driver.css|]--defaultHtmlFile = Text.pack [include|Graphics/UI/index.html|]---}
− src/Graphics/UI/Threepenny/Internal/Types.hs
@@ -1,260 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}--module Graphics.UI.Threepenny.Internal.Types where--import Prelude hiding (init)--import Control.Applicative-import Control.Concurrent-import qualified Control.Event as E-import Control.Monad.Reader-import Data.ByteString (ByteString)-import Data.Map (Map)-import Data.Time--import Network.URI-import Text.JSON.Generic--{------------------------------------------------------------------------------ Public types-------------------------------------------------------------------------------}---- | Reference to an element in the DOM of the client window.-data Element = Element- { elId :: ElementId- , elSession :: Session- }--instance Show Element where- show = show . elId---- | An opaque reference to an element in the DOM.-data ElementId = ElementId String- deriving (Data,Typeable,Show)--instance JSON ElementId where- showJSON (ElementId o) = showJSON o- readJSON obj = do- obj <- readJSON obj- ElementId <$> valFromObj "Element" obj-- --- | A client session. This type is opaque, you don't need to inspect it.-data Session = Session- { sSignals :: Chan Signal- , sInstructions :: Chan Instruction- , sEvent :: EventKey -> E.Event EventData- , sEventHandler :: E.Handler (EventKey, EventData)- , sClosures :: MVar [Integer]- , sElementIds :: MVar [Integer]- , sToken :: Integer- , sMutex :: MVar ()- , sConnectedState :: MVar ConnectedState- , sThreadId :: ThreadId- , sStartInfo :: (URI,[(String,String)])- , sServerState :: ServerState- }--type Sessions = Map Integer Session-type MimeType = ByteString-type Filepaths = (Integer, Map ByteString (FilePath, MimeType))--data ServerState = ServerState- { sSessions :: MVar Sessions- , sFiles :: MVar Filepaths- , sDirs :: MVar Filepaths- }--type EventKey = (String, String)---- | The client browser window.-type Window = Session--data ConnectedState- = Disconnected UTCTime -- ^ The time that the poll disconnected, or- -- the first initial connection time.- | Connected -- ^ The client is connected, we don't care- -- since when.- deriving (Show)---- | Data from an event. At the moment it is empty.-data EventData = EventData [Maybe String]---- | Record for configuring the Threepenny GUI server.-data Config = Config- { tpPort :: Int -- ^ Port number.- , tpCustomHTML :: Maybe FilePath -- ^ Custom HTML file to replace the default one.- , tpStatic :: FilePath -- ^ Directory that is served under @/static@.- }---{------------------------------------------------------------------------------ Communication between client and server-------------------------------------------------------------------------------}---- | An instruction that is sent to the client as JSON.-data Instruction- = Debug String- | Begin ()- | End ()- | SetToken Integer- | Clear ()- | GetElementsById [String]- | GetElementsByTagName String- | SetStyle ElementId [(String,String)]- | SetAttr ElementId String String- | Append ElementId ElementId- | SetText ElementId String- | SetHtml ElementId String- | Bind String ElementId Closure- | GetValue ElementId- | GetValues [ElementId]- | SetTitle String- | GetLocation ()- | RunJSFunction String- | CallJSFunction String- | CallDeferredFunction (Closure,String,[String])- | EmptyEl ElementId- | Delete ElementId- deriving (Typeable,Data,Show)--instance JSON Instruction where- readJSON _ = error "JSON.Instruction.readJSON: No method implemented."- showJSON x = toJSON x ---- | A signal (mostly events) that are sent from the client to the server.-data Signal- = Init ()- | Elements [ElementId]- | Event (String,String,[Maybe String])- | Value String- | Values [String]- | Location String- | FunctionCallValues [Maybe String]- | FunctionResult JSValue- deriving (Show)--instance JSON Signal where- showJSON _ = error "JSON.Signal.showJSON: No method implemented."- readJSON obj = do- obj <- readJSON obj- let init = Init <$> valFromObj "Init" obj- elements = Elements <$> valFromObj "Elements" obj- event = do- (cid,typ,arguments) <- valFromObj "Event" obj- args <- mapM nullable arguments- return $ Event (cid,typ,args)- value = Value <$> valFromObj "Value" obj- location = Location <$> valFromObj "Location" obj- values = Values <$> valFromObj "Values" obj- fcallvalues = do- FunctionCallValues <$> (valFromObj "FunctionCallValues" obj >>= mapM nullable)- fresult = FunctionResult <$> valFromObj "FunctionResult" obj- init <|> elements <|> event <|> value <|> values <|> location- <|> fcallvalues <|> fresult---- | Read a JSValue that may be null.-nullable :: JSON a => JSValue -> Result (Maybe a)-nullable JSNull = return Nothing-nullable v = Just <$> readJSON v---- | An opaque reference to a closure that the event manager uses to--- trigger events signalled by the client.-data Closure = Closure (String,String)- deriving (Typeable,Data,Show)--{------------------------------------------------------------------------------ JavaScript Code and Foreign Function Interface-------------------------------------------------------------------------------}--- | JavaScript code snippet.-newtype JSCode = JSCode { unJSCode :: String }- deriving (Eq, Ord, Show, Data, Typeable)---- | Class for rendering Haskell types as JavaScript code.-class ToJS a where- render :: a -> JSCode--instance ToJS String where render = JSCode . show-instance ToJS Int where render = JSCode . show-instance ToJS Bool where render b = JSCode $ if b then "false" else "true"-instance ToJS JSValue where render x = JSCode $ showJSValue x ""-instance ToJS ElementId where- render (ElementId x) = apply "elidToElement(%1)" [render x]-instance ToJS Element where render (Element e _) = render e----- | Representation of a JavaScript expression--- with a girven output type.-data JSFunction a = JSFunction- { code :: JSCode -- code snippet- , marshal :: Window -> JSValue -> Result a -- convert to Haskell value- }--instance Functor JSFunction where- fmap f = fmapWindow (const f)--fmapWindow :: (Window -> a -> b) -> JSFunction a -> JSFunction b-fmapWindow f (JSFunction c m) = JSFunction c (\w v -> f w <$> m w v)--fromJSCode :: JSCode -> JSFunction ()-fromJSCode c = JSFunction { code = c, marshal = \_ _ -> Ok () }---- | Helper class for making a simple JavaScript FFI-class FFI a where- fancy :: ([JSCode] -> JSCode) -> a--instance (ToJS a, FFI b) => FFI (a -> b) where- fancy f a = fancy $ f . (render a:)--instance FFI (JSFunction ()) where fancy f = fromJSCode $ f []-instance FFI (JSFunction String) where fancy = mkResult "%1.toString()"-instance FFI (JSFunction JSValue) where fancy = mkResult "%1"-instance FFI (JSFunction ElementId) where- fancy = mkResult "{ Element: elementToElid(%1) }"-instance FFI (JSFunction Element) where- fancy = fmapWindow (\w elid -> Element elid w) . fancy--mkResult :: JSON a => String -> ([JSCode] -> JSCode) -> JSFunction a-mkResult client f = JSFunction- { code = apply client [f []]- , marshal = \w -> readJSON- }---- | Simple JavaScript FFI with string substitution.------ Inspired by the Fay language. <http://fay-lang.org/>------ > example :: String -> Int -> JSFunction String--- > example = ffi "$(%1).prop('checked',%2)"------ The 'ffi' function takes a string argument representing the JavaScript--- code to be executed on the client.--- Occurrences of the substrings @%1@ to @%9@ will be replaced by--- subequent arguments.------ Note: Always specify a type signature! The types automate--- how values are marshalled between Haskell and JavaScript.--- The class instances for the 'FFI' class show which conversions are supported.----ffi :: FFI a => String -> a-ffi macro = fancy (apply macro)--testFFI :: String -> Int -> JSFunction String-testFFI = ffi "$(%1).prop('checked',%2)"---- | String substitution.--- Substitute occurences of %1, %2 up to %9 with the argument strings.--- The types ensure that the % character has no meaning in the generated output.--- --- > apply "%1 and %2" [x,y] = x ++ " and " ++ y-apply :: String -> [JSCode] -> JSCode-apply code args = JSCode $ go code- where- argument i = unJSCode (args !! i)- - go [] = []- go ('%':c:cs) = argument index ++ go cs- where index = fromEnum c - fromEnum '1'- go (c:cs) = c : go cs-
src/Graphics/UI/Threepenny/JQuery.hs view
@@ -1,53 +1,41 @@-{-# OPTIONS -fno-warn-wrong-do-bind #-} module Graphics.UI.Threepenny.JQuery where -import Control.Event-import Control.Arrow import Data.Char import Data.Default-import Data.Maybe+ import Graphics.UI.Threepenny.Core-import qualified Graphics.UI.Threepenny.Internal.Core as Core-import qualified Graphics.UI.Threepenny.Internal.Types as Core-import Text.JSON +{-----------------------------------------------------------------------------+ jQuery utilities+------------------------------------------------------------------------------} data Easing = Swing | Linear deriving (Eq,Enum,Show) -instance Default Easing where- def = Linear---- | Animate property changes of a function.-animate :: Element -> [(String,String)] -> Int -> Easing -> IO () -> IO ()-animate el props duration easing complete =- flip updateElement el $ \(Core.Element el window) ->- callDeferredFunction window- "jquery_animate"- [encode el,encode (makeObj (map (second showJSON) props)),show duration,map toLower (show easing)]- (const complete)+instance Default Easing where def = Linear -- | Fade in an element.-fadeIn :: Element -> Int -> Easing -> IO () -> IO ()-fadeIn el duration easing complete =- animate el [("opacity","1")] duration easing complete+fadeIn :: Element -> Int -> Easing -> IO () -> UI ()+fadeIn el duration easing complete = do+ callback <- ffiExport complete+ runFunction $ ffi "$(%1).animate({opacity: 1}, %2 * 1, %3, %4)"+ el duration (map toLower (show easing)) callback -- | Fade out an element.-fadeOut :: Element -> Int -> Easing -> IO () -> IO ()-fadeOut el duration easing complete =- animate el [("opacity","0")] duration easing complete+fadeOut :: Element -> Int -> Easing -> IO () -> UI ()+fadeOut el duration easing complete = do+ callback <- ffiExport complete+ runFunction $ ffi "$(%1).animate({opacity: 0}, %2 * 1, %3, %4)"+ el duration (map toLower (show easing)) callback --- | Do something on return.+-- | The 'sendValue' event happens whenever the return key is pressed+-- while the element has focus. Its data is the event value. sendValue :: Element -> Event String-sendValue el = fmap f (domEvent "sendvalue" el)- where- f (EventData x) = concat . catMaybes $ x+sendValue = fmap unsafeFromJSON . domEvent "sendvalue" -- | Focus an element.-setFocus :: Element -> IO ()-setFocus = updateElement $ \(Core.Element el window) ->- runFunction window (ffi "$(%1).focus()" el)+setFocus :: Element -> UI ()+setFocus = runFunction . ffi "$(%1).focus()" -- | Scroll to the bottom of an element.-scrollToBottom :: Element -> IO ()-scrollToBottom = updateElement $ \(Core.Element area window) ->- runFunction window (ffi "jquery_scrollToBottom(%1)" area)+scrollToBottom :: Element -> UI ()+scrollToBottom = runFunction . ffi "jquery_scrollToBottom(%1)"
+ src/Graphics/UI/Threepenny/SVG.hs view
@@ -0,0 +1,18 @@+module Graphics.UI.Threepenny.SVG (+ -- * Synopsis+ -- | SVG elements and attributes.+ --+ -- It is recommended that you import this module qualified, i.e.+ --+ -- > import qualified Graphics.UI.Threepenny.SVG as SVG+ --+ -- Note that some SVG attributes and elements have the same name.+ -- In this case, only the most common of either has been imported.+ -- If you need the other version, you have to import the individual modules.+ + module Graphics.UI.Threepenny.SVG.Attributes,+ module Graphics.UI.Threepenny.SVG.Elements+ ) where++import Graphics.UI.Threepenny.SVG.Attributes hiding (path, title, glyphRef)+import Graphics.UI.Threepenny.SVG.Elements hiding (cursor, filter, mask, style)
+ src/Graphics/UI/Threepenny/SVG/Attributes.hs view
@@ -0,0 +1,367 @@+{-# OPTIONS_GHC -Wno-missing-signatures #-}+module Graphics.UI.Threepenny.SVG.Attributes (+ -- * Synopsis+ -- | SVG attributes as defined by W3C, Scalable Vector Graphics (SVG) 1.1+ -- (Second Edition) Appendix M <http://www.w3.org/TR/2011/REC-SVG11-20110816/>.+ -- + -- Whenever possible, the Haskell identifier for an attribute is the + -- same as the attribute name. However, sometimes changes are necessary:+ --+ -- 1. Hyphens @-@ are replaced by underscores @_@, e.g. 'stroke_width'.+ --+ -- 2. An underscore is used whenever the attribute name conflicts with + -- a reserved word in Haskell, e.g. 'class_'.+ + -- * Regular attributes+ accent_height, accumulate, additive, alphabetic, amplitude,+ arabic_form, ascent, attributeName, attributeType, azimuth,+ baseFrequency, baseProfile, bbox, begin, bias, by,+ calcMode, cap_height, class_, clipPathUnits, contentScriptType,+ contentStyleType, cx, cy,+ d, descent, diffuseConstant, divisor, dur, dx, dy,+ edgeMode, elevation, end, exponent, externalResourcesRequired,+ filterRes, filterUnits, format, from, fx, fy,+ g1, g2, glyph_name, glyphRef, gradientTransform, gradientUnits,+ hanging, height, horiz_adv_x, horiz_origin_x, horiz_origin_y,+ id, ideographic, in_, in2, intercept,+ k, k1, k2, k3, k4, kernelMatrix, kernelUnitLength,+ keyPoints, keySplines, keyTimes,+ lang, lengthAdjust, limitingConeAngle, local,+ markerHeight, markerUnits, markerWidth, maskContentUnits, maskUnits,+ mathematical, max, media, method, min, mode,+ name, numOctaves,+ offset,+ onabort, onactivate, onbegin, onclick, onend, onerror, onfocusin, onfocusout,+ onload, onmousedown, onmousemove, onmouseout, onmouseover, onmouseup,+ onrepeat, onresize, onscroll, onunload, onzoom,+ operator, order, orient, orientation, origin,+ overline_position, overline_thickness,+ panose_1, path, pathLength, patternContentUnits,+ patternTransform, patternUnits, points, pointsAtX, pointsAtY, pointsAtZ,+ preserveAlpha, preserveAspectRatio, primitiveUnits,+ r, radius, refX, refY, rendering_intent, repeatCount, repeatDur,+ requiredExtensions, requiredFeatures, restart, result, rotate, rx, ry,+ scale, seed, slope, spacing, specularConstant, specularExponent, spreadMethod,+ startOffset, stdDeviation, stemh, stemv, stitchTiles,+ strikethrough_position, strikethrough_thickness,+ string, style, surfaceScale, systemLanguage,+ tableValues, target, targetX, targetY, textLength, title, to, transform, type_,+ u1, u2, underline_position, underline_thickness, unicode, unicode_range, units_per_em,+ v_alphabetic, v_hanging, v_ideographic, v_mathematical, values, version,+ vert_adv_y, vert_origin_x, vert_origin_y, viewBox, viewTarget,+ width, widths,+ x, x_height, x1, x2, xChannelSelector,+ xlink_actuate, xlink_arcrole, xlink_href, xlink_role, xlink_show, xlink_title, xlink_type,+ xml_base, xml_lang, xml_space,+ y, y1, y2, yChannelSelector,+ z, zoomAndPan,++ -- * Regular attributes (alternative spellings, deprecated)+ refx, refy,++ -- * Presentation attributes+ alignment_baseline, baseline_shift,+ clip_path, clip_rule, clip, color_interpolation_filters, color_interpolation,+ color_profile, color_rendering, color, cursor,+ direction, display, dominant_baseline,+ enable_background,+ fill_opacity, fill_rule, fill, filter, flood_color,flood_opacity,+ font_family, font_size_adjust, font_size, font_stretch, font_style, font_variant,+ font_weight,+ glyph_orientation_horizontal, glyph_orientation_vertical,+ image_rendering, kerning,+ letter_spacing, lighting_color,+ marker_end, marker_mid, marker_start, mask,+ opacity, overflow, pointer_events,+ shape_rendering, stop_color, stop_opacity,+ stroke_dasharray, stroke_dashoffset, stroke_linecap, stroke_linejoin,+ stroke_miterlimit, stroke_opacity, stroke_width, stroke,+ text_anchor, text_decoration, text_rendering,+ unicode_bidi, visibility, word_spacing, writing_mode+ ) where+++import Graphics.UI.Threepenny.Core (Element, WriteAttr, attr,+ mkWriteAttr, set')+import Prelude hiding (exponent, filter, id, max,+ min)++strAttr :: String -> WriteAttr Element String+strAttr n = mkWriteAttr (set' (attr n))++intAttr :: String -> WriteAttr Element Int+intAttr n = mkWriteAttr (set' (attr n) . show)++fltAttr :: String -> WriteAttr Element Float+fltAttr n = mkWriteAttr (set' (attr n) . show)++accent_height = fltAttr "accent-height"+accumulate = strAttr "accumulate"+additive = strAttr "additive"+alignment_baseline = strAttr "alignment-baseline"+alphabetic = fltAttr "alphabetic"+amplitude = fltAttr "amplitude"+arabic_form = strAttr "arabic-form"+ascent = fltAttr "ascent"+attributeName = strAttr "attributeName"+attributeType = strAttr "attributeType"+azimuth = fltAttr "azimuth"+baseFrequency = strAttr "baseFrequency"+baseProfile = strAttr "baseProfile"+baseline_shift = strAttr "baseline-shift"+bbox = strAttr "bbox"+begin = strAttr "begin"+bias = fltAttr "bias"+by = fltAttr "by"+calcMode = strAttr "calcMode"+cap_height = fltAttr "cap-height"+class_ = strAttr "class"+clip = strAttr "clip"+clip_path = strAttr "clip-path"+clip_rule = strAttr "clip-rule"+clipPathUnits = strAttr "clipPathUnits"+color = strAttr "color"+color_interpolation = strAttr "color-interpolation"+color_interpolation_filters = strAttr "color-interpolation-filters"+color_profile = strAttr "color-profile"+color_rendering = strAttr "color-rendering"+contentScriptType = strAttr "contentScriptType"+contentStyleType = strAttr "contentStyleType"+cursor = strAttr "cursor"+cx = strAttr "cx"+cy = strAttr "cy"+d = strAttr "d"+descent = fltAttr "descent"+diffuseConstant = fltAttr "diffuseConstant"+direction = strAttr "direction"+display = strAttr "display"+divisor = fltAttr "divisor"+dominant_baseline = strAttr "dominant-baseline"+dur = strAttr "dur"+dx = strAttr "dx"+dy = strAttr "dy"+edgeMode = strAttr "edgeMode"+elevation = fltAttr "elevation"+enable_background = strAttr "enable-background"+end = strAttr "end"+exponent = fltAttr "exponent"+externalResourcesRequired = strAttr "externalResourcesRequired"+fill = strAttr "fill"+fill_opacity = strAttr "fill-opacity"+fill_rule = strAttr "fill-rule"+filter = strAttr "filter"+filterRes = strAttr "filterRes"+filterUnits = strAttr "filterUnits"+flood_color = strAttr "flood-color"+flood_opacity = strAttr "flood-opacity"+font_family = strAttr "font-family"+font_size = strAttr "font-size"+font_size_adjust = strAttr "font-size-adjust"+font_stretch = strAttr "font-stretch"+font_style = strAttr "font-style"+font_variant = strAttr "font-variant"+font_weight = strAttr "font-weight"+format = strAttr "format"+from = fltAttr "from"+fx = strAttr "fx"+fy = strAttr "fy"+g1 = strAttr "g1"+g2 = strAttr "g2"+glyph_name = strAttr "glyph-name"+glyph_orientation_horizontal = strAttr "glyph-orientation-horizontal"+glyph_orientation_vertical = strAttr "glyph-orientation-vertical"+glyphRef = strAttr "glyphRef"+gradientTransform = strAttr "gradientTransform"+gradientUnits = strAttr "gradientUnits"+hanging = fltAttr "hanging"+height = strAttr "height"+horiz_adv_x = fltAttr "horiz-adv-x"+horiz_origin_x = fltAttr "horiz-origin-x"+horiz_origin_y = fltAttr "horiz-origin-y"+id = strAttr "id"+ideographic = fltAttr "ideographic"+image_rendering = strAttr "image-rendering"+in_ = strAttr "in"+in2 = strAttr "in2"+intercept = fltAttr "intercept"+k = fltAttr "k"+k1 = fltAttr "k1"+k2 = fltAttr "k2"+k3 = fltAttr "k3"+k4 = fltAttr "k4"+kernelMatrix = strAttr "kernelMatrix"+kernelUnitLength = strAttr "kernelUnitLength"+kerning = strAttr "kerning"+keyPoints = strAttr "keyPoints"+keySplines = strAttr "keySplines"+keyTimes = strAttr "keyTimes"+lang = strAttr "lang"+lengthAdjust = strAttr "lengthAdjust"+letter_spacing = strAttr "letter-spacing"+lighting_color = strAttr "lighting-color"+limitingConeAngle = fltAttr "limitingConeAngle"+local = strAttr "local"+marker_end = strAttr "marker-end"+marker_mid = strAttr "marker-mid"+marker_start = strAttr "marker-start"+markerHeight = strAttr "markerHeight"+markerUnits = strAttr "markerUnits"+markerWidth = strAttr "markerWidth"+mask = strAttr "mask"+maskContentUnits = strAttr "maskContentUnits"+maskUnits = strAttr "maskUnits"+mathematical = fltAttr "mathematical"+max = strAttr "max"+media = strAttr "media"+method = strAttr "method"+min = strAttr "min"+mode = strAttr "mode"+name = strAttr "name"+numOctaves = intAttr "numOctaves"+offset = fltAttr "offset"+onabort = strAttr "onabort"+onactivate = strAttr "onactivate"+onbegin = strAttr "onbegin"+onclick = strAttr "onclick"+onend = strAttr "onend"+onerror = strAttr "onerror"+onfocusin = strAttr "onfocusin"+onfocusout = strAttr "onfocusout"+onload = strAttr "onload"+onmousedown = strAttr "onmousedown"+onmousemove = strAttr "onmousemove"+onmouseout = strAttr "onmouseout"+onmouseover = strAttr "onmouseover"+onmouseup = strAttr "onmouseup"+onrepeat = strAttr "onrepeat"+onresize = strAttr "onresize"+onscroll = strAttr "onscroll"+onunload = strAttr "onunload"+onzoom = strAttr "onzoom"+opacity = strAttr "opacity"+operator = strAttr "operator"+order = strAttr "order"+orient = strAttr "orient"+orientation = strAttr "orientation"+origin = strAttr "origin"+overflow = strAttr "overflow"+overline_position = fltAttr "overline-position"+overline_thickness = fltAttr "overline-thickness"+panose_1 = intAttr "panose-1"+path = "path"+-- path = strAttr "path"+pathLength = fltAttr "pathLength"+patternContentUnits = strAttr "patternContentUnits"+patternTransform = strAttr "patternTransform"+patternUnits = strAttr "patternUnits"+pointer_events = strAttr "pointer-events"+points = strAttr "points"+pointsAtX = fltAttr "pointsAtX"+pointsAtY = fltAttr "pointsAtY"+pointsAtZ = fltAttr "pointsAtZ"+preserveAlpha = strAttr "preserveAlpha"+preserveAspectRatio = strAttr "preserveAspectRatio"+primitiveUnits = strAttr "primitiveUnits"+r = strAttr "r"+radius = strAttr "radius"+refX = strAttr "refX"+refY = strAttr "refY"+refx = refX+refy = refY+{-# DEPRECATED refx, refy "Use 'refX' and 'refY' instead" #-}+rendering_intent = strAttr "rendering-intent"+repeatCount = strAttr "repeatCount"+repeatDur = strAttr "repeatDur"+requiredExtensions = strAttr "requiredExtensions"+requiredFeatures = strAttr "requiredFeatures"+restart = strAttr "restart"+result = strAttr "result"+rotate = strAttr "rotate"+rx = strAttr "rx"+ry = strAttr "ry"+scale = fltAttr "scale"+seed = fltAttr "seed"+shape_rendering = strAttr "shape-rendering"+slope = fltAttr "slope"+spacing = strAttr "spacing"+specularConstant = fltAttr "specularConstant"+specularExponent = fltAttr "specularExponent"+spreadMethod = strAttr "spreadMethod"+startOffset = strAttr "startOffset"+stdDeviation = strAttr "stdDeviation"+stemh = fltAttr "stemh"+stemv = fltAttr "stemv"+stitchTiles = strAttr "stitchTiles"+stop_color = strAttr "stop-color"+stop_opacity = strAttr "stop-opacity"+strikethrough_position = fltAttr "strikethrough-position"+strikethrough_thickness = fltAttr "strikethrough-thickness"+string = strAttr "string"+stroke = strAttr "stroke"+stroke_dasharray = strAttr "stroke-dasharray"+stroke_dashoffset = strAttr "stroke-dashoffset"+stroke_linecap = strAttr "stroke-linecap"+stroke_linejoin = strAttr "stroke-linejoin"+stroke_miterlimit = strAttr "stroke-miterlimit"+stroke_opacity = strAttr "stroke-opacity"+stroke_width = strAttr "stroke-width"+style = strAttr "style"+surfaceScale = fltAttr "surfaceScale"+systemLanguage = strAttr "systemLanguage"+tableValues = strAttr "tableValues"+target = strAttr "target"+targetX = fltAttr "targetX"+targetY = fltAttr "targetY"+text_anchor = strAttr "text-anchor"+text_decoration = strAttr "text-decoration"+text_rendering = strAttr "text-rendering"+textLength = strAttr "textLength"+title = strAttr "title"+to = fltAttr "to"+transform = strAttr "transform"+type_ = strAttr "type"+u1 = strAttr "u1"+u2 = strAttr "u2"+underline_position = fltAttr "underline-position"+underline_thickness = fltAttr "underline-thickness"+unicode = strAttr "unicode"+unicode_bidi = strAttr "unicode-bidi"+unicode_range = strAttr "unicode-range"+units_per_em = fltAttr "units-per-em"+v_alphabetic = fltAttr "v-alphabetic"+v_hanging = fltAttr "v-hanging"+v_ideographic = fltAttr "v-ideographic"+v_mathematical = fltAttr "v-mathematical"+values = strAttr "values"+version = fltAttr "version"+vert_adv_y = fltAttr "vert-adv-y"+vert_origin_x = fltAttr "vert-origin-x"+vert_origin_y = fltAttr "vert-origin-y"+viewBox = strAttr "viewBox"+viewTarget = strAttr "viewTarget"+visibility = strAttr "visibility"+width = strAttr "width"+widths = strAttr "widths"+word_spacing = strAttr "word-spacing"+writing_mode = strAttr "writing-mode"+x = strAttr "x"+x_height = fltAttr "x-height"+x1 = strAttr "x1"+x2 = strAttr "x2"+xChannelSelector = strAttr "xChannelSelector"+xlink_actuate = strAttr "xlink:actuate"+xlink_arcrole = strAttr "xlink:acrole"+xlink_href = strAttr "xlink:href"+xlink_role = strAttr "xlink:role"+xlink_show = strAttr "xlink:show"+xlink_title = strAttr "xlink:title"+xlink_type = strAttr "xlink:type"+xml_base = strAttr "xml:base"+xml_lang = strAttr "xml:lang"+xml_space = strAttr "xml:space"+y = strAttr "y"+y1 = strAttr "y1"+y2 = strAttr "y2"+yChannelSelector = strAttr "yChannelSelector"+z = fltAttr "z"+zoomAndPan = strAttr "zoomAndPan"
+ src/Graphics/UI/Threepenny/SVG/Elements.hs view
@@ -0,0 +1,125 @@+{-# OPTIONS_GHC -Wno-missing-signatures #-}+module Graphics.UI.Threepenny.SVG.Elements (+ -- * Synopsis+ -- | SVG elements as defined by W3C, Scalable Vector Graphics (SVG) 1.1+ -- (Second Edition) Appendix M <http://www.w3.org/TR/2011/REC-SVG11-20110816/>.+ -- + -- Whenever possible, the Haskell identifier for an element is the + -- same as the attribute name. However, sometimes changes are necessary:+ --+ -- 1. Hyphens @-@ are replaced by underscores @_@, e.g. 'font_face'.++ -- * SVG Elements+ a, altGlyph, altGlyphDef, altGlyphItem,+ animate, animateColor, animateMotion, animateTransform,+ circle, clipPath, colorProfile, cursor,+ defs, desc,+ ellipse,+ feBlend, feColorMatrix, feComponentTransfer, feComposite, feConvolveMatrix,+ feDiffuseLighting, feDisplacementMap, feDistantLight, feFlood,+ feFuncA, feFuncB, feFuncG, feFuncR,+ feGaussianBlur, feImage, feMerge, feMergeNode, feMorphology, feOffset,+ fePointLight, feSpecularLighting, feSpotLight, feTile, feTurbulence,+ filter, font,+ font_face, font_face_format, font_face_name, font_face_srv, font_face_uri,+ foreignObject,+ g, glyph, glyphRef,+ hkern,+ image,+ line, linearGradient,+ marker, mask, metadata, missing_glyph, mpath,+ path, pattern, polygon, polyline,+ radialGradient, rect,+ script, set, stop, style, svg, switch, symbol,+ text, textPath, title, tref, tspan,+ use,+ view, vkern,+ ) where++import Prelude (Maybe(..))+import Graphics.UI.Threepenny.Core (mkElementNamespace)+++tag = mkElementNamespace (Just "http://www.w3.org/2000/svg")+itag = tag++a = tag "a"+altGlyph = tag "altGlyph"+altGlyphDef = itag "altGlyphDef"+altGlyphItem = itag "altGlyphItem"+animate = tag "animate"+animateColor = tag "animateColor"+animateMotion = tag "animateMotion"+animateTransform = itag "animateTransform"+circle = itag "circle"+clipPath = tag "clipPath"+colorProfile = itag "colorProfile"+cursor = itag "cursor"+defs = tag "defs"+desc = itag "desc"+ellipse = tag "ellipse"+feBlend = tag "feBlend"+feColorMatrix = tag "feColorMatrix"+feComponentTransfer = tag "feComponentTransfer"+feComposite = tag "feComposite"+feConvolveMatrix = tag "feConvolveMatrix"+feDiffuseLighting = tag "feDiffuseLighting"+feDisplacementMap = tag "feDisplacementMap"+feDistantLight = itag "feDistantLight"+feFlood = tag "feFlood"+feFuncA = itag "feFuncA"+feFuncB = itag "feFuncB"+feFuncG = itag "feFuncG"+feFuncR = itag "feFuncR"+feGaussianBlur = tag "feGaussianBlur"+feImage = tag "feImage"+feMerge = tag "feMerge"+feMergeNode = itag "feMergeNode"+feMorphology = tag "feMorphology"+feOffset = tag "feOffset"+fePointLight = itag "fePointLight"+feSpecularLighting = tag "feSpecularLighting"+feSpotLight = itag "feSpotLight"+feTile = tag "feTile"+feTurbulence = tag "feTurbulence"+filter = tag "filter"+font = tag "font"+font_face = itag "font-face"+font_face_format = itag "font-face-format"+font_face_name = itag "font-face-name"+font_face_srv = itag "font-face-src"+font_face_uri = itag "font-face-uri"+foreignObject = tag "foreignObject"+g = tag "g"+glyph = tag "glyph"+glyphRef = tag "glyphRef"+hkern = itag "hkern"+image = tag "image"+line = tag "line"+linearGradient = tag "linearGradient"+marker = tag "marker"+mask = tag "mask"+metadata = itag "metadata"+missing_glyph = tag "missing-glyph"+mpath = itag "mpath"+path = itag "path"+pattern = tag "pattern"+polygon = tag "polygon"+polyline = tag "polyline"+radialGradient = tag "radialGradient"+rect = tag "rect"+script = itag "script"+set = itag "set"+stop = tag "stop"+style = itag "style"+svg = tag "svg"+switch = tag "switch"+symbol = tag "symbol"+text = tag "text"+textPath = tag "textPath"+title = itag "title"+tref = tag "tref"+tspan = tag "tspan"+use = tag "use"+view = itag "view"+vkern = itag "vkern"
src/Graphics/UI/Threepenny/Timer.hs view
@@ -1,76 +1,90 @@-{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE RecordWildCards, DeriveDataTypeable #-} module Graphics.UI.Threepenny.Timer ( -- * Synopsis -- | Implementation of a simple timer which runs on the server-side.+ --+ -- NOTE: The timer may be rather wobbly unless you compile+ -- with the @-threaded@ option. -- * Documentation Timer, timer, interval, running, tick, start, stop, ) where --import Control.Monad (when, forever, void)-import Control.Event+import Data.Typeable+import Control.Monad (when, forever) import Control.Concurrent import Control.Concurrent.STM+import Reactive.Threepenny import Graphics.UI.Threepenny.Core data Timer = Timer- { tRunning :: TVar Bool- , tInterval :: TVar Int -- in ms+ { tRunning :: GetSet Bool Bool+ , tInterval :: GetSet Int Int -- in ms , tTick :: Event ()- }+ } deriving (Typeable) -- | Create a new timer-timer :: IO Timer-timer = do- tRunning <- newTVarIO False- tInterval <- newTVarIO 1000+timer :: UI Timer+timer = liftIO $ do+ tvRunning <- newTVarIO False+ tvInterval <- newTVarIO 1000 (tTick, fire) <- newEvent - forkIO $ forever $ do- wait <- atomically $ do- b <- readTVar tRunning+ _ <- forkIO $ forever $ do+ atomically $ do+ b <- readTVar tvRunning when (not b) retry- readTVar tInterval- threadDelay (wait * 1000)+ wait <- atomically $ readTVar tvInterval fire ()- + threadDelay (wait * 1000)+ + let tRunning = fromTVar tvRunning+ tInterval = fromTVar tvInterval + return $ Timer {..} +-- | Timer event.+tick :: Timer -> Event () tick = tTick -- | Timer interval in milliseconds. interval :: Attr Timer Int-interval = fromTVar tInterval+interval = fromGetSet tInterval -- | Whether the timer is running or not. running :: Attr Timer Bool-running = fromTVar tRunning+running = fromGetSet tRunning -- | Start the timer.-start :: Timer -> IO ()+start :: Timer -> UI () start = set' running True -- | Stop the timer.-stop :: Timer -> IO ()+stop :: Timer -> UI () stop = set' running False -fromTVar :: (x -> TVar a) -> Attr x a-fromTVar f = mkReadWriteAttr- (atomically . readTVar . f)- (\i x -> atomically $ writeTVar (f x) i)+fromTVar :: TVar a -> GetSet a a+fromTVar var = (atomically $ readTVar var, atomically . writeTVar var) +type GetSet i o = (IO o, i -> IO ()) +fromGetSet :: (x -> GetSet i o) -> ReadWriteAttr x i o+fromGetSet f = mkReadWriteAttr (liftIO . fst . f) (\i x -> liftIO $ snd (f x) i)++ {----------------------------------------------------------------------------- Small test ------------------------------------------------------------------------------}+{-+ testTimer = do t <- timer void $ register (tick t) $ const $ putStr "Hello" return t # set interval 1000 # set running True+-}
+ src/Graphics/UI/Threepenny/Widgets.hs view
@@ -0,0 +1,121 @@+{-# LANGUAGE RecordWildCards, ScopedTypeVariables #-}+{-# OPTIONS_GHC -Wno-unused-do-bind #-}+module Graphics.UI.Threepenny.Widgets (+ -- * Synopsis+ -- | Widgets are reusable building blocks for a graphical user interface.+ -- This module collects useful widgets that are designed to work+ -- with functional reactive programming (FRP).+ -- + -- For more details and information on how to write your own widgets, see the+ -- <https://github.com/HeinrichApfelmus/threepenny-gui/blob/master/doc/design-widgets.md widget design guide>.+ + -- * Tidings+ Tidings, rumors, facts, tidings,+ -- * Widgets+ -- ** Input widgets+ TextEntry, entry, userText,+ -- ** ListBox+ ListBox, listBox, userSelection,+ ) where++import Control.Monad (void, when)+import qualified Data.Map as Map++import qualified Graphics.UI.Threepenny.Attributes as UI+import qualified Graphics.UI.Threepenny.Events as UI+import qualified Graphics.UI.Threepenny.Elements as UI+import Graphics.UI.Threepenny.Core+import Reactive.Threepenny++{-----------------------------------------------------------------------------+ Input widgets+------------------------------------------------------------------------------}+-- | A single-line text entry.+data TextEntry = TextEntry+ { _elementTE :: Element+ , _userTE :: Tidings String+ }++instance Widget TextEntry where getElement = _elementTE++-- | User changes to the text value.+userText :: TextEntry -> Tidings String+userText = _userTE++-- | Create a single-line text entry.+entry+ :: Behavior String -- ^ Display value when the element does not have focus.+ -> UI TextEntry+entry bValue = do -- single text entry+ input <- UI.input++ bEditing <- stepper False $ and <$>+ unions [True <$ UI.focus input, False <$ UI.blur input]+ + window <- askWindow+ liftIOLater $ onChange bValue $ \s -> runUI window $ do+ editing <- liftIO $ currentValue bEditing+ when (not editing) $ void $ element input # set value s++ let _elementTE = input+ _userTE = tidings bValue $ UI.valueChange input + return TextEntry {..}++{-----------------------------------------------------------------------------+ List box+------------------------------------------------------------------------------}+-- | A list of values. The user can select entries.+data ListBox a = ListBox+ { _elementLB :: Element+ , _selectionLB :: Tidings (Maybe a)+ }++instance Widget (ListBox a) where getElement = _elementLB++-- | User changes to the current selection (possibly empty).+userSelection :: ListBox a -> Tidings (Maybe a)+userSelection = _selectionLB++-- | Create a 'ListBox'.+listBox :: forall a. Ord a+ => Behavior [a] -- ^ list of items+ -> Behavior (Maybe a) -- ^ selected item+ -> Behavior (a -> UI Element) -- ^ display for an item+ -> UI (ListBox a)+listBox bitems bsel bdisplay = do+ list <- UI.select++ -- animate output items+ element list # sink items (map <$> bdisplay <*> bitems)++ -- animate output selection+ let bindices :: Behavior (Map.Map a Int)+ bindices = (Map.fromList . flip zip [0..]) <$> bitems+ bindex = lookupIndex <$> bindices <*> bsel++ lookupIndex _indices Nothing = Nothing+ lookupIndex indices (Just sel) = Map.lookup sel indices++ element list # sink UI.selection bindex++ -- changing the display won't change the current selection+ -- eDisplay <- changes display+ -- sink listBox [ selection :== stepper (-1) $ bSelection <@ eDisplay ]++ -- user selection+ let bindices2 :: Behavior (Map.Map Int a)+ bindices2 = Map.fromList . zip [0..] <$> bitems++ _selectionLB = tidings bsel $+ lookupIndex <$> bindices2 <@> UI.selectionChange list+ _elementLB = list++ return ListBox {..}++items :: WriteAttr Element [UI Element]+items = mkWriteAttr $ \i x -> void $ do+ return x # set children [] #+ map (\j -> UI.option #+ [j]) i++++
− src/Graphics/UI/driver.css
@@ -1,3 +0,0 @@-.tabel { display: table; }-.table-row { display: table-row; }-.table-cell { display: table-cell; }
− src/Graphics/UI/driver.js
@@ -1,461 +0,0 @@-$.fn.sendvalue = function(trigger){- $(this).each(function(){- var self = this;- var el = $(self);- el.keydown(function(e){- if(e.which == 13) {- trigger.call(self,el.val());- return false;- }- else- return true;- });- });-};--$.fn.livechange = function(ms,trigger){- $(this).each(function(){- var self = this;- var el = $(self);- var last_val;- var check = function(){- var val = el.val();- if(val != last_val)- trigger.call(self);- last_val = val;- };- var checker;- var restart = function(){- clearTimeout(checker);- checker = setInterval(check,ms);- };- restart();- el.keypress(restart).change(restart);- });-};--(function(){-- ////////////////////////////////////////////////////////////////////////////////- // State- var sessionToken = null;- var element_count = 0, el_table = {};- var tp_enable_log = $.cookie('tp_log') == "true";- var signal_count = 0;-- document.head = document.head || document.getElementsByTagName('head')[0];-- ////////////////////////////////////////////////////////////////////////////////- // Main entry point- $(document).ready(function(){- setTimeout(function(){- waitForEvents();- })- });-- ////////////////////////////////////////////////////////////////////////////////- // Running instructions-- window.do_logging = function(x){- $.cookie('tp_log',x.toString());- };- - function waitForEvents(){- console_log("Polling… (%d signals so far)",signal_count);- var data = { token: sessionToken };- var cmd = sessionToken != null? 'poll' : 'init';- if(cmd == 'init')- data.info = window.location.href;- var req = $.ajax({- dataType: 'json',- url: cmd,- data: data,- success: function(events){- if(sessionToken == null) {- sessionToken = req.getResponseHeader('Set-Token').match(/[0-9]+/)*1;- }- console_log("Running event" +(events.length>1?'s':'') +"…")- if(events.length){- console_log('Event list:');- runMultipleEvents(events);- } else {- runEvent(events,function(){- waitForEvents();- });- }- },- error: function(reply){- console_log("Error, waiting…");- setTimeout(function(){- waitForEvents();- },5000);- }- });- }-- function runMultipleEvents(events){- if(events.length == 0) {- return waitForEvents();- }- runEvent(events.shift(),function(){- runMultipleEvents(events);- });- }-- function runEvent(event,continuation){- console_log("Event: %s",JSON.stringify(event));- for(var key in event){- switch(key){- - case "EmptyEl": {- var id = event.EmptyEl;- var el = elidToElement(id);- // Detach child elements without deleting associated event handlers and data.- // It is not correct to remove the child elements from the el_table- // because they may still be present on the server side.- $(el).contents().detach();- continuation();- break;- }- case "CallDeferredFunction": {- var call = event.CallDeferredFunction;- var closure = call[0];- var theFunction = eval(call[1]);- var params = call[2];- theFunction.apply(window, params.concat(function(){- console_log(this);- var args = Array.prototype.slice.call(arguments,0);- signal({- Event: closure.concat([args])- },function(){- // No action.- });- }));- continuation();- break;- }- case "RunJSFunction": {- eval(event.RunJSFunction);- continuation();- break;- }- case "CallJSFunction": {- var result = eval(event.CallJSFunction);- signal({FunctionResult : result}, continuation);- break;- }- case "Delete": {- event_delete(event);- continuation();- break;- }- case "Debug": {- if(window.console)- console.log("Server debug: %o",event.Debug);- continuation();- break;- }- case "Clear": {- $('body').contents().detach();- continuation();- break;- }- case "GetElementsByTagName": {- var elements = document.getElementsByTagName(event.GetElementsByTagName);- var els = [];- var len = elements.length;- for(var i = 0; i < len; i++) {- els.push({- Element: elementToElid(elements[i])- });- }- signal({- Elements: els- },function(){- continuation();- });- break;- }- case "GetElementsById": {- // Note that this is the html ID, not the elid that is the key of the el_table.- var ids = event.GetElementsById;- var els = [];- for(var i = 0; i < ids.length; i++) {- var match = document.getElementById(ids[i]);- if (match != null) {- els.push({- Element: elementToElid(match)- });- }- }- signal({- Elements: els- },function(){- continuation();- });- break;- }- case "SetStyle": {- var set = event.SetStyle;- var id = set[0];- var style = set[1];- var el = elidToElement(id);- var len = style.length;- for(var i = 0; i < len; i++){- el.style[style[i][0]] = style[i][1];- }- continuation();- break;- }- case "SetAttr": {- var set = event.SetAttr;- var id = set[0];- var key = set[1];- var value = set[2];- var el = elidToElement(id);- $(el).attr(key,value);- continuation();- break;- }- case "GetValue": {- var id = event.GetValue;- var el = elidToElement(id);- var value = $(el).val();- signal({- Value: value- },function(){- continuation();- });- break;- }- case "GetLocation": {- signal({- Location: window.location.href- },function(){- continuation();- });- break;- }- case "GetValues": {- var ids = event.GetValues;- var len = ids.length;- var values = [];- for(var i = 0; i < len; i++) {- values.push($(elidToElement(ids[i])).val());- }- signal({- Values: values- },function(){- continuation();- });- break;- }- case "Append": {- var append = event.Append;- $(elidToElement(append[0])).append($(elidToElement(append[1])));- continuation();- break;- }- case "SetText": {- var set = event.SetText;- $(elidToElement(set[0])).text(set[1]);- continuation();- break;- }- case "SetTitle": {- document.title = event.SetTitle;- continuation();- break;- }- case "SetHtml": {- var set = event.SetHtml;- $(elidToElement(set[0])).html(set[1]);- continuation();- break;- }- case "Bind": {- var bind = event.Bind;- var eventType = bind[0];- var handlerGuid = bind[2];- var el = elidToElement(bind[1]);- console_log('event type: ' + eventType);- if(eventType == 'livechange') {- $(el).livechange(300,function(e){- signal({- Event: handlerGuid.concat([[$(el).val()]])- },function(){- // no action- });- return true;- });- } else if(eventType == 'sendvalue') {- $(el).sendvalue(function(x){- signal({- Event: handlerGuid.concat([[x]])- },function(){});- });- } else if(eventType.match('dragstart|dragenter|dragover|dragleave|drag|drop|dragend')) {- $(el).bind(eventType,function(e){- signal({- Event: handlerGuid.concat([- e.originalEvent.dataTransfer- ?[e.originalEvent.dataTransfer.getData("dragData")]- :[]])- },function(){- // no action- });- return true;- });- } else if(eventType.match('mousemove')) {- $(el).bind(eventType,function(e){- signal({- Event: handlerGuid.concat([[e.pageX.toString(), e.pageY.toString()]])- },function(){- // no action- });- return true;- });- } else if(eventType.match('keydown|keyup')) {- $(el).bind(eventType,function(e){- signal({- Event: handlerGuid.concat([[e.keyCode.toString()]])- },function(){- // no action- });- return true;- });- } else {- $(el).bind(eventType,function(e){- signal({- Event: handlerGuid.concat([e.which?[e.which.toString()]:[]])- },function(){- // no action- });- return true;- });- }- continuation();- break;- }- default: continuation();- }- }- }-- function event_delete(event){- var id = event.Delete;- var el = elidToElement(id);- // TODO: Think whether it is correct to remove element ids- $(el).detach();- deleteElementTable(id);- }-- ////////////////////////////////////////////////////////////////////////////////- // Signalling events-- function signal(signal,continuation){- signal_count++;- console_log('Signal: %s',JSON.stringify(signal));- $.ajax({- dataType: 'json',- url:'signal',- data: { token: sessionToken, signal: JSON.stringify(signal) },- success: function(){- continuation();- },- error: function(reply){- console_log("Error: %o",reply);- }- });- }-- // When the server creates elements, it assigns them a string "elid". - // This elidToElement function is used to sync the elids on the server with the - // elids on this client code. Lookups on elids that do not already exist in the client- // table are created and added automatically.- function elidToElement(elid){- if(elid == 'body')- return document.body;- else if(elid == 'head')- return document.head;- else if(el_table[elid]){- return el_table[elid];- } else {- if(elid[0] == '*'){- var create = elid.split(':');- var element = document.createElement(create[1]);- element.elid = elid;- el_table[elid] = element;- return element;- } else {- throw "Unknown element: " + elid;- }- }- }- function deleteElementTable(elid){- delete el_table[elid];- }- - // Get/generate a elid for an element. This function is used for cases in which the- // element is accessed without knowing an elid from the server, such as when the - // element is retrieved by type or html ID attribute. The element is then added to - // elid lookup table using the new elid.- // Note: The mapping between elids and DOM elements must be bijective.- function elementToElid(element){- if(element.elid) {- return element.elid;- }- else if (element === document.body) {- return "body";- }- else if (element === document.head) {- return "head";- }- else {- var elid = "!" + element_count.toString();- element_count++;- element.elid = elid;- el_table[elid] = element;- return elid;- }- }-- // A log- function console_log(){- if (tp_enable_log) {- window.console.log.apply(window.console,arguments);- }- };--- ////////////////////////////////////////////////////////////////////////////////- // Additional functions- - window.jquery_animate = function(el_id,props,duration,easing,complete){- var el = elidToElement(JSON.parse(el_id));- $(el).animate(JSON.parse(props),- duration * 1,- easing * 1,- complete);- }-- window.jquery_scrollToBottom = function(el){- $(el).scrollTop(el.scrollHeight);- };-- // see http://stackoverflow.com/a/9722502/403805- CanvasRenderingContext2D.prototype.clear = - CanvasRenderingContext2D.prototype.clear || function (preserveTransform) {- if (preserveTransform) {- this.save();- this.setTransform(1, 0, 0, 1, 0, 0);- }-- this.clearRect(0, 0, this.canvas.width, this.canvas.height);-- if (preserveTransform) {- this.restore();- } - };--})();
− src/Graphics/UI/index.html
@@ -1,10 +0,0 @@-<!doctype html>-<head>- <title></title>- <script src="driver/threepenny-gui.js"></script>- <link rel="stylesheet" type="text/css" href="driver/threepenny-gui.css"/>-</head>-<body>- <noscript>Please enable JavaScript.</noscript>-</body>-</html>
− src/Graphics/UI/jquery-cookie.js
@@ -1,56 +0,0 @@-/**- * jQuery Cookie plugin- *- * Copyright (c) 2010 Klaus Hartl (stilbuero.de)- * Dual licensed under the MIT and GPL licenses:- * http://www.opensource.org/licenses/mit-license.php- * http://www.gnu.org/licenses/gpl.html- *- */-(function($) {- $.cookie = function(key, value, options) {-- // key and at least value given, set cookie...- if (arguments.length > 1 && (!/Object/.test(Object.prototype.toString.call(value)) || value === null || value === undefined)) {- options = $.extend({}, options);-- if (value === null || value === undefined) {- options.expires = -1;- }-- if (typeof options.expires === 'number') {- var days = options.expires, t = options.expires = new Date();- t.setDate(t.getDate() + days);- }-- value = String(value);-- return (document.cookie = [- encodeURIComponent(key), '=', options.raw ? value : encodeURIComponent(value),- options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE- options.path ? '; path=' + options.path : '',- options.domain ? '; domain=' + options.domain : '',- options.secure ? '; secure' : ''- ].join(''));- }-- // key and possibly options given, get cookie...- options = value || {};- var decode = options.raw ? function(s) { return s; } : decodeURIComponent;-- var pairs = document.cookie.split('; ');- for (var i = 0, pair; pair = pairs[i] && pairs[i].split('='); i++) {- if (decode(pair[0]) === key) return decode(pair[1] || ''); // IE saves cookies with empty string as "c; ", e.g. without "=" as opposed to EOMB, thus pair[1] may be undefined- }- return null;- };-- $.setSimpleCookie = function(name,value,f) {- $.cookie(name,value,{- path: '/',- expire: 999 // TODO: - });- f();- };- -})(jQuery);
− src/Graphics/UI/jquery.js
@@ -1,4 +0,0 @@-/*! jQuery v1.7.1 jquery.com | jquery.org/license */-(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cv(a){if(!ck[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){cl||(cl=c.createElement("iframe"),cl.frameBorder=cl.width=cl.height=0),b.appendChild(cl);if(!cm||!cl.createElement)cm=(cl.contentWindow||cl.contentDocument).document,cm.write((c.compatMode==="CSS1Compat"?"<!doctype html>":"")+"<html><body>"),cm.close();d=cm.createElement(a),cm.body.appendChild(d),e=f.css(d,"display"),b.removeChild(cl)}ck[a]=e}return ck[a]}function cu(a,b){var c={};f.each(cq.concat.apply([],cq.slice(0,b)),function(){c[this]=a});return c}function ct(){cr=b}function cs(){setTimeout(ct,0);return cr=f.now()}function cj(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ci(){try{return new a.XMLHttpRequest}catch(b){}}function cc(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g<i;g++){if(g===1)for(h in a.converters)typeof h=="string"&&(e[h.toLowerCase()]=a.converters[h]);l=k,k=d[g];if(k==="*")k=l;else if(l!=="*"&&l!==k){m=l+" "+k,n=e[m]||e["* "+k];if(!n){p=b;for(o in e){j=o.split(" ");if(j[0]===l||j[0]==="*"){p=e[j[1]+" "+k];if(p){o=e[o],o===!0?n=p:p===!0&&(n=o);break}}}}!n&&!p&&f.error("No conversion from "+m.replace(" "," to ")),n!==!0&&(c=n?n(c):p(o(c)))}}return c}function cb(a,c,d){var e=a.contents,f=a.dataTypes,g=a.responseFields,h,i,j,k;for(i in g)i in d&&(c[g[i]]=d[i]);while(f[0]==="*")f.shift(),h===b&&(h=a.mimeType||c.getResponseHeader("content-type"));if(h)for(i in e)if(e[i]&&e[i].test(h)){f.unshift(i);break}if(f[0]in d)j=f[0];else{for(i in d){if(!f[0]||a.converters[i+" "+f[0]]){j=i;break}k||(k=i)}j=j||k}if(j){j!==f[0]&&f.unshift(j);return d[j]}}function ca(a,b,c,d){if(f.isArray(b))f.each(b,function(b,e){c||bE.test(a)?d(a,e):ca(a+"["+(typeof e=="object"||f.isArray(e)?b:"")+"]",e,c,d)});else if(!c&&b!=null&&typeof b=="object")for(var e in b)ca(a+"["+e+"]",b[e],c,d);else d(a,b)}function b_(a,c){var d,e,g=f.ajaxSettings.flatOptions||{};for(d in c)c[d]!==b&&((g[d]?a:e||(e={}))[d]=c[d]);e&&f.extend(!0,a,e)}function b$(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h=a[f],i=0,j=h?h.length:0,k=a===bT,l;for(;i<j&&(k||!l);i++)l=h[i](c,d,e),typeof l=="string"&&(!k||g[l]?l=b:(c.dataTypes.unshift(l),l=b$(a,c,d,e,l,g)));(k||!l)&&!g["*"]&&(l=b$(a,c,d,e,"*",g));return l}function bZ(a){return function(b,c){typeof b!="string"&&(c=b,b="*");if(f.isFunction(c)){var d=b.toLowerCase().split(bP),e=0,g=d.length,h,i,j;for(;e<g;e++)h=d[e],j=/^\+/.test(h),j&&(h=h.substr(1)||"*"),i=a[h]=a[h]||[],i[j?"unshift":"push"](c)}}}function bC(a,b,c){var d=b==="width"?a.offsetWidth:a.offsetHeight,e=b==="width"?bx:by,g=0,h=e.length;if(d>0){if(c!=="border")for(;g<h;g++)c||(d-=parseFloat(f.css(a,"padding"+e[g]))||0),c==="margin"?d+=parseFloat(f.css(a,c+e[g]))||0:d-=parseFloat(f.css(a,"border"+e[g]+"Width"))||0;return d+"px"}d=bz(a,b,b);if(d<0||d==null)d=a.style[b]||0;d=parseFloat(d)||0;if(c)for(;g<h;g++)d+=parseFloat(f.css(a,"padding"+e[g]))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+e[g]+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+e[g]))||0);return d+"px"}function bp(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(bf,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bo(a){var b=c.createElement("div");bh.appendChild(b),b.innerHTML=a.outerHTML;return b.firstChild}function bn(a){var b=(a.nodeName||"").toLowerCase();b==="input"?bm(a):b!=="script"&&typeof a.getElementsByTagName!="undefined"&&f.grep(a.getElementsByTagName("input"),bm)}function bm(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bl(a){return typeof a.getElementsByTagName!="undefined"?a.getElementsByTagName("*"):typeof a.querySelectorAll!="undefined"?a.querySelectorAll("*"):[]}function bk(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bj(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c,d,e,g=f._data(a),h=f._data(b,g),i=g.events;if(i){delete h.handle,h.events={};for(c in i)for(d=0,e=i[c].length;d<e;d++)f.event.add(b,c+(i[c][d].namespace?".":"")+i[c][d].namespace,i[c][d],i[c][d].data)}h.data&&(h.data=f.extend({},h.data))}}function bi(a,b){return f.nodeName(a,"table")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function U(a){var b=V.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}function T(a,b,c){b=b||0;if(f.isFunction(b))return f.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return f.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=f.grep(a,function(a){return a.nodeType===1});if(O.test(b))return f.filter(b,d,!c);b=f.filter(b,d)}return f.grep(a,function(a,d){return f.inArray(a,b)>=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?parseFloat(d):j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c<d;c++)b[a[c]]=!0;return b}var c=a.document,d=a.navigator,e=a.location,f=function(){function J(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(J,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.1",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j<k;j++)if((a=arguments[j])!=null)for(c in a){d=i[c],f=a[c];if(i===f)continue;l&&f&&(e.isPlainObject(f)||(g=e.isArray(f)))?(g?(g=!1,h=d&&e.isArray(d)?d:[]):h=d&&e.isPlainObject(d)?d:{},i[c]=e.extend(l,h,f)):f!==b&&(i[c]=f)}return i},e.extend({noConflict:function(b){a.$===e&&(a.$=g),b&&a.jQuery===e&&(a.jQuery=f);return e},isReady:!1,readyWait:1,holdReady:function(a){a?e.readyWait++:e.ready(!0)},ready:function(a){if(a===!0&&!--e.readyWait||a!==!0&&!e.isReady){if(!c.body)return setTimeout(e.ready,1);e.isReady=!0;if(a!==!0&&--e.readyWait>0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g<h;)if(c.apply(a[g++],d)===!1)break}else if(i){for(f in a)if(c.call(a[f],f,a[f])===!1)break}else for(;g<h;)if(c.call(a[g],g,a[g++])===!1)break;return a},trim:G?function(a){return a==null?"":G.call(a)}:function(a){return a==null?"":(a+"").replace(k,"").replace(l,"")},makeArray:function(a,b){var c=b||[];if(a!=null){var d=e.type(a);a.length==null||d==="string"||d==="function"||d==="regexp"||e.isWindow(a)?E.call(c,a):e.merge(c,a)}return c},inArray:function(a,b,c){var d;if(b){if(H)return H.call(b,a,c);d=b.length,c=c?c<0?Math.max(0,d+c):c:0;for(;c<d;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,c){var d=a.length,e=0;if(typeof c.length=="number")for(var f=c.length;e<f;e++)a[d++]=c[e];else while(c[e]!==b)a[d++]=c[e++];a.length=d;return a},grep:function(a,b,c){var d=[],e;c=!!c;for(var f=0,g=a.length;f<g;f++)e=!!b(a[f],f),c!==e&&d.push(a[f]);return d},map:function(a,c,d){var f,g,h=[],i=0,j=a.length,k=a instanceof e||j!==b&&typeof j=="number"&&(j>0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i<j;i++)f=c(a[i],i,d),f!=null&&(h[h.length]=f);else for(g in a)f=c(a[g],g,d),f!=null&&(h[h.length]=f);return h.concat.apply([],h)},guid:1,proxy:function(a,c){if(typeof c=="string"){var d=a[c];c=a,a=d}if(!e.isFunction(a))return b;var f=F.call(arguments,2),g=function(){return a.apply(c,f.concat(F.call(arguments)))};g.guid=a.guid=a.guid||g.guid||e.guid++;return g},access:function(a,c,d,f,g,h){var i=a.length;if(typeof c=="object"){for(var j in c)e.access(a,j,c[j],f,g,d);return a}if(d!==b){f=!h&&f&&e.isFunction(d);for(var k=0;k<i;k++)g(a[k],c,f?d.call(a[k],k,g(a[k],c)):d,h);return a}return i?g(a[0],c):b},now:function(){return(new Date).getTime()},uaMatch:function(a){a=a.toLowerCase();var b=r.exec(a)||s.exec(a)||t.exec(a)||a.indexOf("compatible")<0&&u.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},sub:function(){function a(b,c){return new a.fn.init(b,c)}e.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function(d,f){f&&f instanceof e&&!(f instanceof a)&&(f=a(f));return e.fn.init.call(this,d,f,b)},a.fn.init.prototype=a.fn;var b=a(c);return a},browser:{}}),e.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){I["[object "+b+"]"]=b.toLowerCase()}),z=e.uaMatch(y),z.browser&&(e.browser[z.browser]=!0,e.browser.version=z.version),e.browser.webkit&&(e.browser.safari=!0),j.test(" ")&&(k=/^[\s\xA0]+/,l=/[\s\xA0]+$/),h=e(c),c.addEventListener?B=function(){c.removeEventListener("DOMContentLoaded",B,!1),e.ready()}:c.attachEvent&&(B=function(){c.readyState==="complete"&&(c.detachEvent("onreadystatechange",B),e.ready())});return e}(),g={};f.Callbacks=function(a){a=a?g[a]||h(a):{};var c=[],d=[],e,i,j,k,l,m=function(b){var d,e,g,h,i;for(d=0,e=b.length;d<e;d++)g=b[d],h=f.type(g),h==="array"?m(g):h==="function"&&(!a.unique||!o.has(g))&&c.push(g)},n=function(b,f){f=f||[],e=!a.memory||[b,f],i=!0,l=j||0,j=0,k=c.length;for(;c&&l<k;l++)if(c[l].apply(b,f)===!1&&a.stopOnFalse){e=!0;break}i=!1,c&&(a.once?e===!0?o.disable():c=[]:d&&d.length&&(e=d.shift(),o.fireWith(e[0],e[1])))},o={add:function(){if(c){var a=c.length;m(arguments),i?k=c.length:e&&e!==!0&&(j=a,n(e[0],e[1]))}return this},remove:function(){if(c){var b=arguments,d=0,e=b.length;for(;d<e;d++)for(var f=0;f<c.length;f++)if(b[d]===c[f]){i&&f<=k&&(k--,f<=l&&l--),c.splice(f--,1);if(a.unique)break}}return this},has:function(a){if(c){var b=0,d=c.length;for(;b<d;b++)if(a===c[b])return!0}return!1},empty:function(){c=[];return this},disable:function(){c=d=e=b;return this},disabled:function(){return!c},lock:function(){d=b,(!e||e===!0)&&o.disable();return this},locked:function(){return!d},fireWith:function(b,c){d&&(i?a.once||d.push([b,c]):(!a.once||!e)&&n(b,c));return this},fire:function(){o.fireWith(this,arguments);return this},fired:function(){return!!e}};return o};var i=[].slice;f.extend({Deferred:function(a){var b=f.Callbacks("once memory"),c=f.Callbacks("once memory"),d=f.Callbacks("memory"),e="pending",g={resolve:b,reject:c,notify:d},h={done:b.add,fail:c.add,progress:d.add,state:function(){return e},isResolved:b.fired,isRejected:c.fired,then:function(a,b,c){i.done(a).fail(b).progress(c);return this},always:function(){i.done.apply(i,arguments).fail.apply(i,arguments);return this},pipe:function(a,b,c){return f.Deferred(function(d){f.each({done:[a,"resolve"],fail:[b,"reject"],progress:[c,"notify"]},function(a,b){var c=b[0],e=b[1],g;f.isFunction(c)?i[a](function(){g=c.apply(this,arguments),g&&f.isFunction(g.promise)?g.promise().then(d.resolve,d.reject,d.notify):d[e+"With"](this===i?d:this,[g])}):i[a](d[e])})}).promise()},promise:function(a){if(a==null)a=h;else for(var b in h)a[b]=h[b];return a}},i=h.promise({}),j;for(j in g)i[j]=g[j].fire,i[j+"With"]=g[j].fireWith;i.done(function(){e="resolved"},c.disable,d.lock).fail(function(){e="rejected"},b.disable,d.lock),a&&a.call(i,i);return i},when:function(a){function m(a){return function(b){e[a]=arguments.length>1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c<d;c++)b[c]&&b[c].promise&&f.isFunction(b[c].promise)?b[c].promise().then(l(c),j.reject,m(c)):--g;g||j.resolveWith(j,b)}else j!==a&&j.resolveWith(j,d?[a]:[]);return k}}),f.support=function(){var b,d,e,g,h,i,j,k,l,m,n,o,p,q=c.createElement("div"),r=c.documentElement;q.setAttribute("className","t"),q.innerHTML=" <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>",d=q.getElementsByTagName("*"),e=q.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=q.getElementsByTagName("input")[0],b={leadingWhitespace:q.firstChild.nodeType===3,tbody:!q.getElementsByTagName("tbody").length,htmlSerialize:!!q.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:q.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav></:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete q.test}catch(s){b.deleteExpando=!1}!q.addEventListener&&q.attachEvent&&q.fireEvent&&(q.attachEvent("onclick",function(){b.noCloneEvent=!1}),q.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),q.appendChild(i),k=c.createDocumentFragment(),k.appendChild(q.lastChild),b.checkClone=k.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,k.removeChild(i),k.appendChild(q),q.innerHTML="",a.getComputedStyle&&(j=c.createElement("div"),j.style.width="0",j.style.marginRight="0",q.style.width="2px",q.appendChild(j),b.reliableMarginRight=(parseInt((a.getComputedStyle(j,null)||{marginRight:0}).marginRight,10)||0)===0);if(q.attachEvent)for(o in{submit:1,change:1,focusin:1})n="on"+o,p=n in q,p||(q.setAttribute(n,"return;"),p=typeof q[n]=="function"),b[o+"Bubbles"]=p;k.removeChild(q),k=g=h=j=q=i=null,f(function(){var a,d,e,g,h,i,j,k,m,n,o,r=c.getElementsByTagName("body")[0];!r||(j=1,k="position:absolute;top:0;left:0;width:1px;height:1px;margin:0;",m="visibility:hidden;border:0;",n="style='"+k+"border:5px solid #000;padding:0;'",o="<div "+n+"><div></div></div>"+"<table "+n+" cellpadding='0' cellspacing='0'>"+"<tr><td></td></tr></table>",a=c.createElement("div"),a.style.cssText=m+"width:0;height:0;position:static;top:0;margin-top:"+j+"px",r.insertBefore(a,r.firstChild),q=c.createElement("div"),a.appendChild(q),q.innerHTML="<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>",l=q.getElementsByTagName("td"),p=l[0].offsetHeight===0,l[0].style.display="",l[1].style.display="none",b.reliableHiddenOffsets=p&&l[0].offsetHeight===0,q.innerHTML="",q.style.width=q.style.paddingLeft="1px",f.boxModel=b.boxModel=q.offsetWidth===2,typeof q.style.zoom!="undefined"&&(q.style.display="inline",q.style.zoom=1,b.inlineBlockNeedsLayout=q.offsetWidth===2,q.style.display="",q.innerHTML="<div style='width:4px;'></div>",b.shrinkWrapBlocks=q.offsetWidth!==2),q.style.cssText=k+m,q.innerHTML=o,d=q.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,i={doesNotAddBorder:e.offsetTop!==5,doesAddBorderForTableAndCells:h.offsetTop===5},e.style.position="fixed",e.style.top="20px",i.fixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",i.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,i.doesNotIncludeMarginInBodyOffset=r.offsetTop!==j,r.removeChild(a),q=a=null,f.extend(b,i))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e<g;e++)delete d[b[e]];if(!(c?m:f.isEmptyObject)(d))return}}if(!c){delete j[k].data;if(!m(j[k]))return}f.support.deleteExpando||!j.setInterval?delete j[k]:j[k]=null,i&&(f.support.deleteExpando?delete a[h]:a.removeAttribute?a.removeAttribute(h):a[h]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d,e,g,h=null;if(typeof a=="undefined"){if(this.length){h=f.data(this[0]);if(this[0].nodeType===1&&!f._data(this[0],"parsedAttrs")){e=this[0].attributes;for(var i=0,j=e.length;i<j;i++)g=e[i].name,g.indexOf("data-")===0&&(g=f.camelCase(g.substring(5)),l(this[0],g,h[g]));f._data(this[0],"parsedAttrs",!0)}}return h}if(typeof a=="object")return this.each(function(){f.data(this,a)});d=a.split("."),d[1]=d[1]?"."+d[1]:"";if(c===b){h=this.triggerHandler("getData"+d[1]+"!",[d[0]]),h===b&&this.length&&(h=f.data(this[0],a),h=l(this[0],a,h));return h===b&&d[1]?this.data(d[0]):h}return this.each(function(){var b=f(this),e=[d[0],c];b.triggerHandler("setData"+d[1]+"!",e),f.data(this,a,c),b.triggerHandler("changeData"+d[1]+"!",e)})},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,b){a&&(b=(b||"fx")+"mark",f._data(a,b,(f._data(a,b)||0)+1))},_unmark:function(a,b,c){a!==!0&&(c=b,b=a,a=!1);if(b){c=c||"fx";var d=c+"mark",e=a?0:(f._data(b,d)||1)-1;e?f._data(b,d,e):(f.removeData(b,d,!0),n(b,c,"mark"))}},queue:function(a,b,c){var d;if(a){b=(b||"fx")+"queue",d=f._data(a,b),c&&(!d||f.isArray(c)?d=f._data(a,b,f.makeArray(c)):d.push(c));return d||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e={};d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),f._data(a,b+".run",e),d.call(a,function(){f.dequeue(a,b)},e)),c.length||(f.removeData(a,b+"queue "+b+".run",!0),n(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){typeof a!="string"&&(c=a,a="fx");if(c===b)return f.queue(this[0],a);return this.each(function(){var b=f.queue(this,a,c);a==="fx"&&b[0]!=="inprogress"&&f.dequeue(this,a)})},dequeue:function(a){return this.each(function(){f.dequeue(this,a)})},delay:function(a,b){a=f.fx?f.fx.speeds[a]||a:a,b=b||"fx";return this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){function m(){--h||d.resolveWith(e,[e])}typeof a!="string"&&(c=a,a=b),a=a||"fx";var d=f.Deferred(),e=this,g=e.length,h=1,i=a+"defer",j=a+"queue",k=a+"mark",l;while(g--)if(l=f.data(e[g],i,b,!0)||(f.data(e[g],j,b,!0)||f.data(e[g],k,b,!0))&&f.data(e[g],i,f.Callbacks("once memory"),!0))h++,l.add(m);m();return d.promise()}});var o=/[\n\t\r]/g,p=/\s+/,q=/\r/g,r=/^(?:button|input)$/i,s=/^(?:button|input|object|select|textarea)$/i,t=/^a(?:rea)?$/i,u=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,v=f.support.getSetAttribute,w,x,y;f.fn.extend({attr:function(a,b){return f.access(this,a,b,!0,f.attr)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,a,b,!0,f.prop)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,g,h,i;if(f.isFunction(a))return this.each(function(b){f(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(p);for(c=0,d=this.length;c<d;c++){e=this[c];if(e.nodeType===1)if(!e.className&&b.length===1)e.className=a;else{g=" "+e.className+" ";for(h=0,i=b.length;h<i;h++)~g.indexOf(" "+b[h]+" ")||(g+=b[h]+" ");e.className=f.trim(g)}}}return this},removeClass:function(a){var c,d,e,g,h,i,j;if(f.isFunction(a))return this.each(function(b){f(this).removeClass(a.call(this,b,this.className))});if(a&&typeof a=="string"||a===b){c=(a||"").split(p);for(d=0,e=this.length;d<e;d++){g=this[d];if(g.nodeType===1&&g.className)if(a){h=(" "+g.className+" ").replace(o," ");for(i=0,j=c.length;i<j;i++)h=h.replace(" "+c[i]+" "," ");g.className=f.trim(h)}else g.className=""}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";if(f.isFunction(a))return this.each(function(c){f(this).toggleClass(a.call(this,c,this.className,b),b)});return this.each(function(){if(c==="string"){var e,g=0,h=f(this),i=b,j=a.split(p);while(e=j[g++])i=d?i:!h.hasClass(e),h[i?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&f._data(this,"__className__",this.className),this.className=this.className||a===!1?"":f._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c<d;c++)if(this[c].nodeType===1&&(" "+this[c].className+" ").replace(o," ").indexOf(b)>-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.valHooks[g.nodeName.toLowerCase()]||f.valHooks[g.type];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c<d;c++){e=i[c];if(e.selected&&(f.support.optDisabled?!e.disabled:e.getAttribute("disabled")===null)&&(!e.parentNode.disabled||!f.nodeName(e.parentNode,"optgroup"))){b=f(e).val();if(j)return b;h.push(b)}}if(j&&!h.length&&i.length)return f(i[g]).val();return h},set:function(a,b){var c=f.makeArray(b);f(a).find("option").each(function(){this.selected=f.inArray(f(this).val(),c)>=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;h<g;h++)e=d[h],e&&(c=f.propFix[e]||e,f.attr(a,e,""),a.removeAttribute(v?e:c),u.test(e)&&c in a&&(a[c]=!1))}},attrHooks:{type:{set:function(a,b){if(r.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},value:{get:function(a,b){if(w&&f.nodeName(a,"button"))return w.get(a,b);return b in a?a.value:null},set:function(a,b,c){if(w&&f.nodeName(a,"button"))return w.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e,g,h,i=a.nodeType;if(!!a&&i!==3&&i!==8&&i!==2){h=i!==1||!f.isXMLDoc(a),h&&(c=f.propFix[c]||c,g=f.propHooks[c]);return d!==b?g&&"set"in g&&(e=g.set(a,d,c))!==b?e:a[c]=d:g&&"get"in g&&(e=g.get(a,c))!==null?e:a[c]}},propHooks:{tabIndex:{get:function(a){var c=a.getAttributeNode("tabindex");return c&&c.specified?parseInt(c.value,10):s.test(a.nodeName)||t.test(a.nodeName)&&a.href?0:b}}}}),f.attrHooks.tabindex=f.propHooks.tabIndex,x={get:function(a,c){var d,e=f.prop(a,c);return e===!0||typeof e!="boolean"&&(d=a.getAttributeNode(c))&&d.nodeValue!==!1?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase()));return c}},v||(y={name:!0,id:!0},w=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&(y[c]?d.nodeValue!=="":d.specified)?d.nodeValue:b},set:function(a,b,d){var e=a.getAttributeNode(d);e||(e=c.createAttribute(d),a.setAttributeNode(e));return e.nodeValue=b+""}},f.attrHooks.tabindex.set=w.set,f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})}),f.attrHooks.contenteditable={get:w.get,set:function(a,b,c){b===""&&(b="false"),w.set(a,b,c)}}),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex);return null}})),f.support.enctype||(f.propFix.enctype="encoding"),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/\bhover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function(a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")};-f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k<c.length;k++){l=A.exec(c[k])||[],m=l[1],n=(l[2]||"").split(".").sort(),s=f.event.special[m]||{},m=(g?s.delegateType:s.bindType)||m,s=f.event.special[m]||{},o=f.extend({type:m,origType:l[1],data:e,handler:d,guid:d.guid,selector:g,quick:G(g),namespace:n.join(".")},p),r=j[m];if(!r){r=j[m]=[],r.delegateCount=0;if(!s.setup||s.setup.call(a,e,n,i)===!1)a.addEventListener?a.addEventListener(m,i,!1):a.attachEvent&&a.attachEvent("on"+m,i)}s.add&&(s.add.call(a,o),o.handler.guid||(o.handler.guid=d.guid)),g?r.splice(r.delegateCount++,0,o):r.push(o),f.event.global[m]=!0}a=null}},global:{},remove:function(a,b,c,d,e){var g=f.hasData(a)&&f._data(a),h,i,j,k,l,m,n,o,p,q,r,s;if(!!g&&!!(o=g.events)){b=f.trim(I(b||"")).split(" ");for(h=0;h<b.length;h++){i=A.exec(b[h])||[],j=k=i[1],l=i[2];if(!j){for(j in o)f.event.remove(a,j+b[h],c,d,!0);continue}p=f.event.special[j]||{},j=(d?p.delegateType:p.bindType)||j,r=o[j]||[],m=r.length,l=l?new RegExp("(^|\\.)"+l.split(".").sort().join("\\.(?:.*\\.)?")+"(\\.|$)"):null;for(n=0;n<r.length;n++)s=r[n],(e||k===s.origType)&&(!c||c.guid===s.guid)&&(!l||l.test(s.namespace))&&(!d||d===s.selector||d==="**"&&s.selector)&&(r.splice(n--,1),s.selector&&r.delegateCount--,p.remove&&p.remove.call(a,s));r.length===0&&m!==r.length&&((!p.teardown||p.teardown.call(a,l)===!1)&&f.removeEvent(a,j,g.handle),delete o[j])}f.isEmptyObject(o)&&(q=g.handle,q&&(q.elem=null),f.removeData(a,["events","handle"],!0))}},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,e,g){if(!e||e.nodeType!==3&&e.nodeType!==8){var h=c.type||c,i=[],j,k,l,m,n,o,p,q,r,s;if(E.test(h+f.event.triggered))return;h.indexOf("!")>=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;l<r.length&&!c.isPropagationStopped();l++)m=r[l][0],c.type=r[l][1],q=(f._data(m,"events")||{})[c.type]&&f._data(m,"handle"),q&&q.apply(m,d),q=o&&m[o],q&&f.acceptData(m)&&q.apply(m,d)===!1&&c.preventDefault();c.type=h,!g&&!c.isDefaultPrevented()&&(!p._default||p._default.apply(e.ownerDocument,d)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)&&o&&e[h]&&(h!=="focus"&&h!=="blur"||c.target.offsetWidth!==0)&&!f.isWindow(e)&&(n=e[o],n&&(e[o]=null),f.event.triggered=h,e[h](),f.event.triggered=b,n&&(e[o]=n));return c.result}},dispatch:function(c){c=f.event.fix(c||a.event);var d=(f._data(this,"events")||{})[c.type]||[],e=d.delegateCount,g=[].slice.call(arguments,0),h=!c.exclusive&&!c.namespace,i=[],j,k,l,m,n,o,p,q,r,s,t;g[0]=c,c.delegateTarget=this;if(e&&!c.target.disabled&&(!c.button||c.type!=="click")){m=f(this),m.context=this.ownerDocument||this;for(l=c.target;l!=this;l=l.parentNode||this){o={},q=[],m[0]=l;for(j=0;j<e;j++)r=d[j],s=r.selector,o[s]===b&&(o[s]=r.quick?H(l,r.quick):m.is(s)),o[s]&&q.push(r);q.length&&i.push({elem:l,matches:q})}}d.length>e&&i.push({elem:this,matches:d.slice(e)});for(j=0;j<i.length&&!c.isPropagationStopped();j++){p=i[j],c.currentTarget=p.elem;for(k=0;k<p.matches.length&&!c.isImmediatePropagationStopped();k++){r=p.matches[k];if(h||!c.namespace&&!r.namespace||c.namespace_re&&c.namespace_re.test(r.namespace))c.data=r.data,c.handleObj=r,n=((f.event.special[r.origType]||{}).handle||r.handler).apply(p.elem,g),n!==b&&(c.result=n,n===!1&&(c.preventDefault(),c.stopPropagation()))}}return c.result},props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){a.which==null&&(a.which=b.charCode!=null?b.charCode:b.keyCode);return a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,d){var e,f,g,h=d.button,i=d.fromElement;a.pageX==null&&d.clientX!=null&&(e=a.target.ownerDocument||c,f=e.documentElement,g=e.body,a.pageX=d.clientX+(f&&f.scrollLeft||g&&g.scrollLeft||0)-(f&&f.clientLeft||g&&g.clientLeft||0),a.pageY=d.clientY+(f&&f.scrollTop||g&&g.scrollTop||0)-(f&&f.clientTop||g&&g.clientTop||0)),!a.relatedTarget&&i&&(a.relatedTarget=i===a.target?d.toElement:i),!a.which&&h!==b&&(a.which=h&1?1:h&2?3:h&4?2:0);return a}},fix:function(a){if(a[f.expando])return a;var d,e,g=a,h=f.event.fixHooks[a.type]||{},i=h.props?this.props.concat(h.props):this.props;a=f.Event(g);for(d=i.length;d;)e=i[--d],a[e]=g[e];a.target||(a.target=g.srcElement||c),a.target.nodeType===3&&(a.target=a.target.parentNode),a.metaKey===b&&(a.metaKey=a.ctrlKey);return h.filter?h.filter(a,g):a},special:{ready:{setup:f.bindReady},load:{noBubble:!0},focus:{delegateType:"focusin"},blur:{delegateType:"focusout"},beforeunload:{setup:function(a,b,c){f.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}},simulate:function(a,b,c,d){var e=f.extend(new f.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?f.event.trigger(e,null,b):f.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},f.event.handle=f.event.dispatch,f.removeEvent=c.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){a.detachEvent&&a.detachEvent("on"+b,c)},f.Event=function(a,b){if(!(this instanceof f.Event))return new f.Event(a,b);a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?K:J):this.type=a,b&&f.extend(this,b),this.timeStamp=a&&a.timeStamp||f.now(),this[f.expando]=!0},f.Event.prototype={preventDefault:function(){this.isDefaultPrevented=K;var a=this.originalEvent;!a||(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){this.isPropagationStopped=K;var a=this.originalEvent;!a||(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=K,this.stopPropagation()},isDefaultPrevented:J,isPropagationStopped:J,isImmediatePropagationStopped:J},f.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){f.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c=this,d=a.relatedTarget,e=a.handleObj,g=e.selector,h;if(!d||d!==c&&!f.contains(c,d))a.type=e.origType,h=e.handler.apply(this,arguments),a.type=b;return h}}}),f.support.submitBubbles||(f.event.special.submit={setup:function(){if(f.nodeName(this,"form"))return!1;f.event.add(this,"click._submit keypress._submit",function(a){var c=a.target,d=f.nodeName(c,"input")||f.nodeName(c,"button")?c.form:b;d&&!d._submit_attached&&(f.event.add(d,"submit._submit",function(a){this.parentNode&&!a.isTrigger&&f.event.simulate("submit",this.parentNode,a,!0)}),d._submit_attached=!0)})},teardown:function(){if(f.nodeName(this,"form"))return!1;f.event.remove(this,"._submit")}}),f.support.changeBubbles||(f.event.special.change={setup:function(){if(z.test(this.nodeName)){if(this.type==="checkbox"||this.type==="radio")f.event.add(this,"propertychange._change",function(a){a.originalEvent.propertyName==="checked"&&(this._just_changed=!0)}),f.event.add(this,"click._change",function(a){this._just_changed&&!a.isTrigger&&(this._just_changed=!1,f.event.simulate("change",this,a,!0))});return!1}f.event.add(this,"beforeactivate._change",function(a){var b=a.target;z.test(b.nodeName)&&!b._change_attached&&(f.event.add(b,"change._change",function(a){this.parentNode&&!a.isSimulated&&!a.isTrigger&&f.event.simulate("change",this.parentNode,a,!0)}),b._change_attached=!0)})},handle:function(a){var b=a.target;if(this!==b||a.isSimulated||a.isTrigger||b.type!=="radio"&&b.type!=="checkbox")return a.handleObj.handler.apply(this,arguments)},teardown:function(){f.event.remove(this,"._change");return z.test(this.nodeName)}}),f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){var d=0,e=function(a){f.event.simulate(b,a.target,f.event.fix(a),!0)};f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.fn.extend({on:function(a,c,d,e,g){var h,i;if(typeof a=="object"){typeof c!="string"&&(d=c,c=b);for(i in a)this.on(i,c,d,a[i],g);return this}d==null&&e==null?(e=c,d=c=b):e==null&&(typeof c=="string"?(e=d,d=b):(e=d,d=c,c=b));if(e===!1)e=J;else if(!e)return this;g===1&&(h=e,e=function(a){f().off(a);return h.apply(this,arguments)},e.guid=h.guid||(h.guid=f.guid++));return this.each(function(){f.event.add(this,a,e,d,c)})},one:function(a,b,c,d){return this.on.call(this,a,b,c,d,1)},off:function(a,c,d){if(a&&a.preventDefault&&a.handleObj){var e=a.handleObj;f(a.delegateTarget).off(e.namespace?e.type+"."+e.namespace:e.type,e.selector,e.handler);return this}if(typeof a=="object"){for(var g in a)this.off(g,c,a[g]);return this}if(c===!1||typeof c=="function")d=c,c=b;d===!1&&(d=J);return this.each(function(){f.event.remove(this,a,d,c)})},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},live:function(a,b,c){f(this.context).on(a,this.selector,b,c);return this},die:function(a,b){f(this.context).off(a,this.selector||"**",b);return this},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return arguments.length==1?this.off(a,"**"):this.off(b,a,c)},trigger:function(a,b){return this.each(function(){f.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return f.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||f.guid++,d=0,e=function(c){var e=(f._data(this,"lastToggle"+a.guid)||0)%d;f._data(this,"lastToggle"+a.guid,e+1),c.preventDefault();return b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),f.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){f.fn[b]=function(a,c){c==null&&(c=a,a=null);return arguments.length>0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h<i;h++){var j=e[h];if(j){var k=!1;j=j[a];while(j){if(j[d]===c){k=e[j.sizset];break}if(j.nodeType===1){g||(j[d]=c,j.sizset=h);if(typeof b!="string"){if(j===b){k=!0;break}}else if(m.filter(b,[j]).length>0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h<i;h++){var j=e[h];if(j){var k=!1;j=j[a];while(j){if(j[d]===c){k=e[j.sizset];break}j.nodeType===1&&!g&&(j[d]=c,j.sizset=h);if(j.nodeName.toLowerCase()===b){k=j;break}j=j[a]}e[h]=k}}}var a=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b<a.length;b++)a[b]===a[b-1]&&a.splice(b--,1)}return a},m.matches=function(a,b){return m(a,null,null,b)},m.matchesSelector=function(a,b){return m(b,null,null,[a]).length>0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e<f;e++){h=o.order[e];if(g=o.leftMatch[h].exec(a)){i=g[1],g.splice(1,1);if(i.substr(i.length-1)!=="\\"){g[1]=(g[1]||"").replace(j,""),d=o.find[h](g,b,c);if(d!=null){a=a.replace(o.match[h],"");break}}}}d||(d=typeof b.getElementsByTagName!="undefined"?b.getElementsByTagName("*"):[]);return{set:d,expr:a}},m.filter=function(a,c,d,e){var f,g,h,i,j,k,l,n,p,q=a,r=[],s=c,t=c&&c[0]&&m.isXML(c[0]);while(a&&c.length){for(h in o.filter)if((f=o.leftMatch[h].exec(a))!=null&&f[2]){k=o.filter[h],l=f[1],g=!1,f.splice(1,1);if(l.substr(l.length-1)==="\\")continue;s===r&&(r=[]);if(o.preFilter[h]){f=o.preFilter[h](f,s,d,r,e,t);if(!f)g=i=!0;else if(f===!0)continue}if(f)for(n=0;(j=s[n])!=null;n++)j&&(i=k(j,f,n,s),p=e^i,d&&i!=null?p?g=!0:s[n]=!1:p&&(r.push(j),g=!0));if(i!==b){d||(s=r),a=a.replace(o.match[h],"");if(!g)return[];break}}if(a===q)if(g==null)m.error(a);else break;q=a}return s},m.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)};var n=m.getText=function(a){var b,c,d=a.nodeType,e="";if(d){if(d===1||d===9){if(typeof a.textContent=="string")return a.textContent;if(typeof a.innerText=="string")return a.innerText.replace(k,"");for(a=a.firstChild;a;a=a.nextSibling)e+=n(a)}else if(d===3||d===4)return a.nodeValue}else for(b=0;c=a[b];b++)c.nodeType!==8&&(e+=n(c));return e},o=m.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(a){return a.getAttribute("href")},type:function(a){return a.getAttribute("type")}},relative:{"+":function(a,b){var c=typeof b=="string",d=c&&!l.test(b),e=c&&!d;d&&(b=b.toLowerCase());for(var f=0,g=a.length,h;f<g;f++)if(h=a[f]){while((h=h.previousSibling)&&h.nodeType!==1);a[f]=e||h&&h.nodeName.toLowerCase()===b?h||!1:h===b}e&&m.filter(b,a,!0)},">":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e<f;e++){c=a[e];if(c){var g=c.parentNode;a[e]=g.nodeName.toLowerCase()===b?g:!1}}}else{for(;e<f;e++)c=a[e],c&&(a[e]=d?c.parentNode:c.parentNode===b);d&&m.filter(b,a,!0)}},"":function(a,b,c){var d,f=e++,g=x;typeof b=="string"&&!l.test(b)&&(b=b.toLowerCase(),d=b,g=w),g("parentNode",b,f,a,d,c)},"~":function(a,b,c){var d,f=e++,g=x;typeof b=="string"&&!l.test(b)&&(b=b.toLowerCase(),d=b,g=w),g("previousSibling",b,f,a,d,c)}},find:{ID:function(a,b,c){if(typeof b.getElementById!="undefined"&&!c){var d=b.getElementById(a[1]);return d&&d.parentNode?[d]:[]}},NAME:function(a,b){if(typeof b.getElementsByName!="undefined"){var c=[],d=b.getElementsByName(a[1]);for(var e=0,f=d.length;e<f;e++)d[e].getAttribute("name")===a[1]&&c.push(d[e]);return c.length===0?null:c}},TAG:function(a,b){if(typeof b.getElementsByTagName!="undefined")return b.getElementsByTagName(a[1])}},preFilter:{CLASS:function(a,b,c,d,e,f){a=" "+a[1].replace(j,"")+" ";if(f)return a;for(var g=0,h;(h=b[g])!=null;g++)h&&(e^(h.className&&(" "+h.className+" ").replace(/[\t\n\r]/g," ").indexOf(a)>=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return b<c[3]-0},gt:function(a,b,c){return b>c[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h<i;h++)if(g[h]===a)return!1;return!0}m.error(e)},CHILD:function(a,b){var c,e,f,g,h,i,j,k=b[1],l=a;switch(k){case"only":case"first":while(l=l.previousSibling)if(l.nodeType===1)return!1;if(k==="first")return!0;l=a;case"last":while(l=l.nextSibling)if(l.nodeType===1)return!1;return!0;case"nth":c=b[2],e=b[3];if(c===1&&e===0)return!0;f=b[0],g=a.parentNode;if(g&&(g[d]!==f||!a.nodeIndex)){i=0;for(l=g.firstChild;l;l=l.nextSibling)l.nodeType===1&&(l.nodeIndex=++i);g[d]=f}j=a.nodeIndex-e;return c===0?j===0:j%c===0&&j/c>=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c<e;c++)d.push(a[c]);else for(;a[c];c++)d.push(a[c]);return d}}var u,v;c.documentElement.compareDocumentPosition?u=function(a,b){if(a===b){h=!0;return 0}if(!a.compareDocumentPosition||!b.compareDocumentPosition)return a.compareDocumentPosition?-1:1;return a.compareDocumentPosition(b)&4?-1:1}:(u=function(a,b){if(a===b){h=!0;return 0}if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],g=a.parentNode,i=b.parentNode,j=g;if(g===i)return v(a,b);if(!g)return-1;if(!i)return 1;while(j)e.unshift(j),j=j.parentNode;j=i;while(j)f.unshift(j),j=j.parentNode;c=e.length,d=f.length;for(var k=0;k<c&&k<d;k++)if(e[k]!==f[k])return v(e[k],f[k]);return k===c?v(a,f[k],-1):v(e[k],b,1)},v=function(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}),function(){var a=c.createElement("div"),d="script"+(new Date).getTime(),e=c.documentElement;a.innerHTML="<a name='"+d+"'/>",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="<p class='TEST'></p>";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="<div class='test e'></div><div class='test'></div>";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h<i;h++)m(a,g[h],e,c);return m.filter(f,e)};m.attr=f.attr,m.selectors.attrMap={},f.find=m,f.expr=m.selectors,f.expr[":"]=f.expr.filters,f.unique=m.uniqueSort,f.text=m.getText,f.isXMLDoc=m.isXML,f.contains=m.contains}();var L=/Until$/,M=/^(?:parents|prevUntil|prevAll)/,N=/,/,O=/^.[^:#\[\.,]*$/,P=Array.prototype.slice,Q=f.expr.match.POS,R={children:!0,contents:!0,next:!0,prev:!0};f.fn.extend({find:function(a){var b=this,c,d;if(typeof a!="string")return f(a).filter(function(){for(c=0,d=b.length;c<d;c++)if(f.contains(b[c],this))return!0});var e=this.pushStack("","find",a),g,h,i;for(c=0,d=this.length;c<d;c++){g=e.length,f.find(a,this[c],e);if(c>0)for(h=g;h<e.length;h++)for(i=0;i<g;i++)if(e[i]===e[h]){e.splice(h--,1);break}}return e},has:function(a){var b=f(a);return this.filter(function(){for(var a=0,c=b.length;a<c;a++)if(f.contains(this,b[a]))return!0})},not:function(a){return this.pushStack(T(this,a,!1),"not",a)},filter:function(a){return this.pushStack(T(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?Q.test(a)?f(a,this.context).index(this[0])>=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d<a.length;d++)f(g).is(a[d])&&c.push({selector:a[d],elem:g,level:h});g=g.parentNode,h++}return c}var i=Q.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d<e;d++){g=this[d];while(g){if(i?i.index(g)>-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/<tbody/i,_=/<|&#?\w+;/,ba=/<(?:script|style)/i,bb=/<(?:script|object|embed|option|style)/i,bc=new RegExp("<(?:"+V+")","i"),bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*<!(?:\[CDATA\[|\-\-)/,bg={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div<div>","</div>"]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function()-{for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1></$2>");try{for(var c=0,d=this.length;c<d;c++)this[c].nodeType===1&&(f.cleanData(this[c].getElementsByTagName("*")),this[c].innerHTML=a)}catch(e){this.empty().append(a)}}else f.isFunction(a)?this.each(function(b){var c=f(this);c.html(a.call(this,b,c.html()))}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(f.isFunction(a))return this.each(function(b){var c=f(this),d=c.html();c.replaceWith(a.call(this,b,d))});typeof a!="string"&&(a=f(a).detach());return this.each(function(){var b=this.nextSibling,c=this.parentNode;f(this).remove(),b?f(b).before(a):f(c).append(a)})}return this.length?this.pushStack(f(f.isFunction(a)?a():a),"replaceWith",a):this},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){var e,g,h,i,j=a[0],k=[];if(!f.support.checkClone&&arguments.length===3&&typeof j=="string"&&bd.test(j))return this.each(function(){f(this).domManip(a,c,d,!0)});if(f.isFunction(j))return this.each(function(e){var g=f(this);a[0]=j.call(this,e,c?g.html():b),g.domManip(a,c,d)});if(this[0]){i=j&&j.parentNode,f.support.parentNode&&i&&i.nodeType===11&&i.childNodes.length===this.length?e={fragment:i}:e=f.buildFragment(a,this,k),h=e.fragment,h.childNodes.length===1?g=h=h.firstChild:g=h.firstChild;if(g){c=c&&f.nodeName(g,"tr");for(var l=0,m=this.length,n=m-1;l<m;l++)d.call(c?bi(this[l],g):this[l],e.cacheable||m>1&&l<n?f.clone(h,!0,!0):h)}k.length&&f.each(k,bp)}return this}}),f.buildFragment=function(a,b,d){var e,g,h,i,j=a[0];b&&b[0]&&(i=b[0].ownerDocument||b[0]),i.createDocumentFragment||(i=c),a.length===1&&typeof j=="string"&&j.length<512&&i===c&&j.charAt(0)==="<"&&!bb.test(j)&&(f.support.checkClone||!bd.test(j))&&(f.support.html5Clone||!bc.test(j))&&(g=!0,h=f.fragments[j],h&&h!==1&&(e=h)),e||(e=i.createDocumentFragment(),f.clean(a,i,e,d)),g&&(f.fragments[j]=h?e:1);return{fragment:e,cacheable:g}},f.fragments={},f.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){f.fn[a]=function(c){var d=[],e=f(c),g=this.length===1&&this[0].parentNode;if(g&&g.nodeType===11&&g.childNodes.length===1&&e.length===1){e[b](this[0]);return this}for(var h=0,i=e.length;h<i;h++){var j=(h>0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||!bc.test("<"+a.nodeName)?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!_.test(k))k=b.createTextNode(k);else{k=k.replace(Y,"<$1></$2>");var l=(Z.exec(k)||["",""])[1].toLowerCase(),m=bg[l]||bg._default,n=m[0],o=b.createElement("div");b===c?bh.appendChild(o):U(b).appendChild(o),o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=$.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]==="<table>"&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&X.test(k)&&o.insertBefore(b.createTextNode(X.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i<r;i++)bn(k[i]);else bn(k);k.nodeType?h.push(k):h=f.merge(h,k)}if(d){g=function(a){return!a.type||be.test(a.type)};for(j=0;h[j];j++)if(e&&f.nodeName(h[j],"script")&&(!h[j].type||h[j].type.toLowerCase()==="text/javascript"))e.push(h[j].parentNode?h[j].parentNode.removeChild(h[j]):h[j]);else{if(h[j].nodeType===1){var s=f.grep(h[j].getElementsByTagName("script"),g);h.splice.apply(h,[j+1,0].concat(s))}d.appendChild(h[j])}}return h},cleanData:function(a){var b,c,d=f.cache,e=f.event.special,g=f.support.deleteExpando;for(var h=0,i;(i=a[h])!=null;h++){if(i.nodeName&&f.noData[i.nodeName.toLowerCase()])continue;c=i[f.expando];if(c){b=d[c];if(b&&b.events){for(var j in b.events)e[j]?f.event.remove(i,j):f.removeEvent(i,j,b.handle);b.handle&&(b.handle.elem=null)}g?delete i[f.expando]:i.removeAttribute&&i.removeAttribute(f.expando),delete d[c]}}}});var bq=/alpha\([^)]*\)/i,br=/opacity=([^)]*)/,bs=/([A-Z]|^ms)/g,bt=/^-?\d+(?:px)?$/i,bu=/^-?\d/,bv=/^([\-+])=([\-+.\de]+)/,bw={position:"absolute",visibility:"hidden",display:"block"},bx=["Left","Right"],by=["Top","Bottom"],bz,bA,bB;f.fn.css=function(a,c){if(arguments.length===2&&c===b)return this;return f.access(this,a,c,!0,function(a,c,d){return d!==b?f.style(a,c,d):f.css(a,c)})},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bz(a,"opacity","opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d,h==="string"&&(g=bv.exec(d))&&(d=+(g[1]+1)*+g[2]+parseFloat(f.css(a,c)),h="number");if(d==null||h==="number"&&isNaN(d))return;h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(bz)return bz(a,c)},swap:function(a,b,c){var d={};for(var e in b)d[e]=a.style[e],a.style[e]=b[e];c.call(a);for(e in b)a.style[e]=d[e]}}),f.curCSS=f.css,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){var e;if(c){if(a.offsetWidth!==0)return bC(a,b,d);f.swap(a,bw,function(){e=bC(a,b,d)});return e}},set:function(a,b){if(!bt.test(b))return b;b=parseFloat(b);if(b>=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return br.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bq,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bq.test(g)?g.replace(bq,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bz(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(bA=function(a,b){var c,d,e;b=b.replace(bs,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b)));return c}),c.documentElement.currentStyle&&(bB=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f===null&&g&&(e=g[b])&&(f=e),!bt.test(f)&&bu.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f||0,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),bz=bA||bB,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bD=/%20/g,bE=/\[\]$/,bF=/\r?\n/g,bG=/#.*$/,bH=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bI=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bJ=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bK=/^(?:GET|HEAD)$/,bL=/^\/\//,bM=/\?/,bN=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bO=/^(?:select|textarea)/i,bP=/\s+/,bQ=/([?&])_=[^&]*/,bR=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bS=f.fn.load,bT={},bU={},bV,bW,bX=["*/"]+["*"];try{bV=e.href}catch(bY){bV=c.createElement("a"),bV.href="",bV=bV.href}bW=bR.exec(bV.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bS)return bS.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("<div>").append(c.replace(bN,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bO.test(this.nodeName)||bI.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bF,"\r\n")}}):{name:b.name,value:c.replace(bF,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b_(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b_(a,b);return a},ajaxSettings:{url:bV,isLocal:bJ.test(bW[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bX},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bZ(bT),ajaxTransport:bZ(bU),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?cb(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cc(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bH.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bG,"").replace(bL,bW[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bP),d.crossDomain==null&&(r=bR.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bW[1]&&r[2]==bW[2]&&(r[3]||(r[1]==="http:"?80:443))==(bW[3]||(bW[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),b$(bT,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bK.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bM.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bQ,"$1_="+x);d.url=y+(y===d.url?(bM.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bX+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=b$(bU,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)ca(g,a[g],c,e);return d.join("&").replace(bD,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cd=f.now(),ce=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cd++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(ce.test(b.url)||e&&ce.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(ce,l),b.url===j&&(e&&(k=k.replace(ce,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var cf=a.ActiveXObject?function(){for(var a in ch)ch[a](0,1)}:!1,cg=0,ch;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ci()||cj()}:ci,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,cf&&delete ch[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cg,cf&&(ch||(ch={},f(a).unload(cf)),ch[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var ck={},cl,cm,cn=/^(?:toggle|show|hide)$/,co=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cp,cq=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cr;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cu("show",3),a,b,c);for(var g=0,h=this.length;g<h;g++)d=this[g],d.style&&(e=d.style.display,!f._data(d,"olddisplay")&&e==="none"&&(e=d.style.display=""),e===""&&f.css(d,"display")==="none"&&f._data(d,"olddisplay",cv(d.nodeName)));for(g=0;g<h;g++){d=this[g];if(d.style){e=d.style.display;if(e===""||e==="none")d.style.display=f._data(d,"olddisplay")||""}}return this},hide:function(a,b,c){if(a||a===0)return this.animate(cu("hide",3),a,b,c);var d,e,g=0,h=this.length;for(;g<h;g++)d=this[g],d.style&&(e=f.css(d,"display"),e!=="none"&&!f._data(d,"olddisplay")&&f._data(d,"olddisplay",e));for(g=0;g<h;g++)this[g].style&&(this[g].style.display="none");return this},_toggle:f.fn.toggle,toggle:function(a,b,c){var d=typeof a=="boolean";f.isFunction(a)&&f.isFunction(b)?this._toggle.apply(this,arguments):a==null||d?this.each(function(){var b=d?a:f(this).is(":hidden");f(this)[b?"show":"hide"]()}):this.animate(cu("toggle",3),a,b,c);return this},fadeTo:function(a,b,c,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){function g(){e.queue===!1&&f._mark(this);var b=f.extend({},e),c=this.nodeType===1,d=c&&f(this).is(":hidden"),g,h,i,j,k,l,m,n,o;b.animatedProperties={};for(i in a){g=f.camelCase(i),i!==g&&(a[g]=a[i],delete a[i]),h=a[g],f.isArray(h)?(b.animatedProperties[g]=h[1],h=a[g]=h[0]):b.animatedProperties[g]=b.specialEasing&&b.specialEasing[g]||b.easing||"swing";if(h==="hide"&&d||h==="show"&&!d)return b.complete.call(this);c&&(g==="height"||g==="width")&&(b.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY],f.css(this,"display")==="inline"&&f.css(this,"float")==="none"&&(!f.support.inlineBlockNeedsLayout||cv(this.nodeName)==="inline"?this.style.display="inline-block":this.style.zoom=1))}b.overflow!=null&&(this.style.overflow="hidden");for(i in a)j=new f.fx(this,b,i),h=a[i],cn.test(h)?(o=f._data(this,"toggle"+i)||(h==="toggle"?d?"show":"hide":0),o?(f._data(this,"toggle"+i,o==="show"?"hide":"show"),j[o]()):j[h]()):(k=co.exec(h),l=j.cur(),k?(m=parseFloat(k[2]),n=k[3]||(f.cssNumber[i]?"":"px"),n!=="px"&&(f.style(this,i,(m||1)+n),l=(m||1)/j.cur()*l,f.style(this,i,l+n)),k[1]&&(m=(k[1]==="-="?-1:1)*m+l),j.custom(l,m,n)):j.custom(l,h,""));return!0}var e=f.speed(b,c,d);if(f.isEmptyObject(a))return this.each(e.complete,[!1]);a=f.extend({},a);return e.queue===!1?this.each(g):this.queue(e.queue,g)},stop:function(a,c,d){typeof a!="string"&&(d=c,c=a,a=b),c&&a!==!1&&this.queue(a||"fx",[]);return this.each(function(){function h(a,b,c){var e=b[c];f.removeData(a,c,!0),e.stop(d)}var b,c=!1,e=f.timers,g=f._data(this);d||f._unmark(!0,this);if(a==null)for(b in g)g[b]&&g[b].stop&&b.indexOf(".run")===b.length-4&&h(this,g,b);else g[b=a+".run"]&&g[b].stop&&h(this,g,b);for(b=e.length;b--;)e[b].elem===this&&(a==null||e[b].queue===a)&&(d?e[b](!0):e[b].saveState(),c=!0,e.splice(b,1));(!d||!c)&&f.dequeue(this,a)})}}),f.each({slideDown:cu("show",1),slideUp:cu("hide",1),slideToggle:cu("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){f.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),f.extend({speed:function(a,b,c){var d=a&&typeof a=="object"?f.extend({},a):{complete:c||!c&&b||f.isFunction(a)&&a,duration:a,easing:c&&b||b&&!f.isFunction(b)&&b};d.duration=f.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in f.fx.speeds?f.fx.speeds[d.duration]:f.fx.speeds._default;if(d.queue==null||d.queue===!0)d.queue="fx";d.old=d.complete,d.complete=function(a){f.isFunction(d.old)&&d.old.call(this),d.queue?f.dequeue(this,d.queue):a!==!1&&f._unmark(this)};return d},easing:{linear:function(a,b,c,d){return c+d*a},swing:function(a,b,c,d){return(-Math.cos(a*Math.PI)/2+.5)*d+c}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig=b.orig||{}}}),f.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(f.fx.step[this.prop]||f.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=f.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,c,d){function h(a){return e.step(a)}var e=this,g=f.fx;this.startTime=cr||cs(),this.end=c,this.now=this.start=a,this.pos=this.state=0,this.unit=d||this.unit||(f.cssNumber[this.prop]?"":"px"),h.queue=this.options.queue,h.elem=this.elem,h.saveState=function(){e.options.hide&&f._data(e.elem,"fxshow"+e.prop)===b&&f._data(e.elem,"fxshow"+e.prop,e.start)},h()&&f.timers.push(h)&&!cp&&(cp=setInterval(g.tick,g.interval))},show:function(){var a=f._data(this.elem,"fxshow"+this.prop);this.options.orig[this.prop]=a||f.style(this.elem,this.prop),this.options.show=!0,a!==b?this.custom(this.cur(),a):this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),f(this.elem).show()},hide:function(){this.options.orig[this.prop]=f._data(this.elem,"fxshow"+this.prop)||f.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b,c,d,e=cr||cs(),g=!0,h=this.elem,i=this.options;if(a||e>=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c<b.length;c++)a=b[c],!a()&&b[c]===a&&b.splice(c--,1);b.length||f.fx.stop()},interval:13,stop:function(){clearInterval(cp),cp=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){f.style(a.elem,"opacity",a.now)},_default:function(a){a.elem.style&&a.elem.style[a.prop]!=null?a.elem.style[a.prop]=a.now+a.unit:a.elem[a.prop]=a.now}}}),f.each(["width","height"],function(a,b){f.fx.step[b]=function(a){f.style(a.elem,b,Math.max(0,a.now)+a.unit)}}),f.expr&&f.expr.filters&&(f.expr.filters.animated=function(a){return f.grep(f.timers,function(b){return a===b.elem}).length});var cw=/^t(?:able|d|h)$/i,cx=/^(?:body|html)$/i;"getBoundingClientRect"in c.documentElement?f.fn.offset=function(a){var b=this[0],c;if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);try{c=b.getBoundingClientRect()}catch(d){}var e=b.ownerDocument,g=e.documentElement;if(!c||!f.contains(g,b))return c?{top:c.top,left:c.left}:{top:0,left:0};var h=e.body,i=cy(e),j=g.clientTop||h.clientTop||0,k=g.clientLeft||h.clientLeft||0,l=i.pageYOffset||f.support.boxModel&&g.scrollTop||h.scrollTop,m=i.pageXOffset||f.support.boxModel&&g.scrollLeft||h.scrollLeft,n=c.top+l-j,o=c.left+m-k;return{top:n,left:o}}:f.fn.offset=function(a){var b=this[0];if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);var c,d=b.offsetParent,e=b,g=b.ownerDocument,h=g.documentElement,i=g.body,j=g.defaultView,k=j?j.getComputedStyle(b,null):b.currentStyle,l=b.offsetTop,m=b.offsetLeft;while((b=b.parentNode)&&b!==i&&b!==h){if(f.support.fixedPosition&&k.position==="fixed")break;c=j?j.getComputedStyle(b,null):b.currentStyle,l-=b.scrollTop,m-=b.scrollLeft,b===d&&(l+=b.offsetTop,m+=b.offsetLeft,f.support.doesNotAddBorder&&(!f.support.doesAddBorderForTableAndCells||!cw.test(b.nodeName))&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),e=d,d=b.offsetParent),f.support.subtractsBorderForOverflowNotVisible&&c.overflow!=="visible"&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),k=c}if(k.position==="relative"||k.position==="static")l+=i.offsetTop,m+=i.offsetLeft;f.support.fixedPosition&&k.position==="fixed"&&(l+=Math.max(h.scrollTop,i.scrollTop),m+=Math.max(h.scrollLeft,i.scrollLeft));return{top:l,left:m}},f.offset={bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.support.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cy(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cy(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,d,"padding")):this[d]():null},f.fn["outer"+c]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,d,a?"margin":"border")):this[d]():null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c],h=e.document.body;return e.document.compatMode==="CSS1Compat"&&g||h&&h["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var i=f.css(e,d),j=parseFloat(i);return f.isNumeric(j)?j:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window);
− src/MissingDollars.hs
@@ -1,142 +0,0 @@-{-# LANGUAGE CPP, PackageImports #-}--import Control.Monad-import Control.Monad.Extra-import Safe--#ifdef CABAL-import qualified "threepenny-gui" Graphics.UI.Threepenny as UI-import "threepenny-gui" Graphics.UI.Threepenny.Core-#else-import qualified Graphics.UI.Threepenny as UI-import Graphics.UI.Threepenny.Core-#endif-import Paths--{------------------------------------------------------------------------------ Missing Dollars-------------------------------------------------------------------------------}-main :: IO ()-main = do- static <- getStaticDir- startGUI Config- { tpPort = 10000- , tpCustomHTML = Nothing- , tpStatic = static- } setup---setup :: Window -> IO ()-setup w = void $ do- return w # set title "Missing Dollars"- UI.addStyleSheet w "missing-dollars.css"- - (headerView,headerMe) <- mkHeader- riddle <- mkMissingDollarRiddle headerMe- let layout = [element headerView] ++ riddle ++ attributionSource - getBody w #+ [UI.div #. "wrap" #+ layout]---mkHeader :: IO (Element, Element)-mkHeader = do- headerMe <- string "..."- view <- UI.h1 #+ [string "The ", element headerMe, string " Dollars"]- return (view, headerMe)--attributionSource :: [IO Element]-attributionSource =- [ UI.p #+- [ UI.anchor #. "view-source" # set UI.href urlSource- #+ [string "View source code"]- ]- , UI.p #+- [ string "Originally by "- , UI.anchor # set UI.href urlAttribution #+ [string "Albert Lai"]- ]- ]- where- urlSource = "https://github.com/HeinrichApfelmus/threepenny-gui/blob/master/src/MissingDollars.hs"- urlAttribution = "http://www.vex.net/~trebla/humour/missing_dollar.html"---mkMissingDollarRiddle :: Element -> IO [IO Element]-mkMissingDollarRiddle headerMe = do- -- declare input and display values- (hotelOut : hotelCost : hotelHold : _)- <- sequence . replicate 3 $- UI.input # set (attr "size") "3" # set (attr "type") "text"-- (hotelChange : hotelRet : hotelBal : hotelPocket :- hotelBal2 : hotelPocket2 : hotelSum : hotelMe : _)- <- sequence . replicate 8 $ UI.span- - -- update procedure- let updateDisplay out cost hold = do- let change = out - cost- ret = change - hold- bal = out - ret- sum = bal + hold- diff = sum - out- element hotelOut # set value (show out)- element hotelCost # set value (show cost)- element hotelHold # set value (show hold)- element hotelChange # set text (show change)- element hotelRet # set text (show ret)- element hotelBal # set text (show bal)- element hotelPocket # set text (show hold)- element hotelBal2 # set text (show bal)- element hotelPocket2 # set text (show hold)- element hotelSum # set text (show sum)- - if diff >= 0- then do element hotelMe # set text ("extra $" ++ show diff ++ " come from")- element headerMe # set text "Extra"- else do element hotelMe # set text ("missing $" ++ show (-diff) ++ " go")- element headerMe # set text "Missing"- return ()- - -- initialize values- updateDisplay 30 25 2- - -- calculate button- calculate <- UI.button #+ [string "Calculate"]- on UI.click calculate $ \_ -> do- result <- mapM readMay `liftM` getValuesList [hotelOut,hotelCost,hotelHold]- case result of- Just [getout,getcost,gethold] -> updateDisplay getout getcost gethold- _ -> return ()- - return $- [ UI.h2 #+ [string "The Guests, The Bellhop, And The Pizza"]- , UI.p #+- [ string "Three guests went to a hotel and gave $"- , element hotelOut- , string " to the bellhop to buy pizza. The pizza cost only $"- , element hotelCost- , string ". Of the $"- , element hotelChange- , string " change, the bellhop kept $"- , element hotelHold- , string " to himself and returned $"- , element hotelRet- , string " to the guests."- ]- , UI.p #+- [ string "So the guests spent $"- , element hotelBal- , string ", and the bellhop pocketed $"- , element hotelPocket- , string ". Now "- , string "$"- , element hotelBal2- , string "+$"- , element hotelPocket2- , string "=$"- , element hotelSum- , string ". Where did the "- , element hotelMe- , string "?"- ]- , element calculate- ] -
− src/Paths.hs
@@ -1,20 +0,0 @@-{-# LANGUAGE CPP#-}-module Paths (getStaticDir) where--import Control.Monad-import System.FilePath--#if CABAL--- using cabal-import qualified Paths_threepenny_gui (getDataDir)--getStaticDir :: IO FilePath-getStaticDir = (</> "wwwroot") `liftM` Paths_threepenny_gui.getDataDir--#else--- using GHCi--getStaticDir :: IO FilePath-getStaticDir = return "../wwwroot/"--#endif
+ src/Reactive/Threepenny.hs view
@@ -0,0 +1,398 @@+{-# LANGUAGE RecursiveDo #-}+module Reactive.Threepenny (+ -- * Synopsis+ -- | Functional reactive programming.++ -- * Types+ -- $intro+ Event, Behavior,++ -- * IO+ -- | Functions to connect events to the outside world.+ Handler, newEvent, register,+ currentValue,++ -- * Core Combinators+ -- | Minimal set of combinators for programming with 'Event' and 'Behavior'.+ module Control.Applicative,+ never, filterJust, unionWith,+ accumE, apply, stepper,+ -- $classes++ -- * Derived Combinators+ -- | Additional combinators that make programming+ -- with 'Event' and 'Behavior' convenient.+ -- ** Application+ (<@>), (<@),+ -- ** Filtering+ filterE, filterApply, whenE, split,+ -- ** Union+ unions, concatenate,+ -- ** Accumulation+ -- $accumulation+ accumB, mapAccum,++ -- * Additional Notes+ -- $recursion++ -- * Tidings+ Tidings, tidings, facts, rumors,++ -- * Internal+ -- | Functions reserved for special circumstances.+ -- Do not use unless you know what you're doing.+ onChange, unsafeMapIO, newEventsNamed,+ + -- * Testing+ test, test_recursion1+ ) where++import Control.Applicative+import Control.Monad (void)+import Control.Monad.IO.Class+import Data.IORef+import qualified Data.Map as Map++import Reactive.Threepenny.Memo as Memo+import qualified Reactive.Threepenny.PulseLatch as Prim++type Pulse = Prim.Pulse+type Latch = Prim.Latch++{-----------------------------------------------------------------------------+ Types+------------------------------------------------------------------------------}+{- $intro++At its core, Functional Reactive Programming (FRP) is about two+data types 'Event' and 'Behavior' and the various ways to combine them.++-}++{-| @Event a@ represents a stream of events as they occur in time.+Semantically, you can think of @Event a@ as an infinite list of values+that are tagged with their corresponding time of occurence,++> type Event a = [(Time,a)]+-}+newtype Event a = E { unE :: Memo (Pulse a) }++{-| @Behavior a@ represents a value that varies in time. Think of it as++> type Behavior a = Time -> a+-}+data Behavior a = B { latch :: Latch a, changes :: Event () }++{-----------------------------------------------------------------------------+ IO+------------------------------------------------------------------------------}+-- | An /event handler/ is a function that takes an+-- /event value/ and performs some computation.+type Handler a = a -> IO ()++-- | Create a new event.+-- Also returns a function that triggers an event occurrence.+newEvent :: IO (Event a, Handler a)+newEvent = do+ (p, fire) <- Prim.newPulse+ return (E $ fromPure p, fire)+++-- | Create a series of events with delayed initialization.+--+-- For each name, the initialization handler will be called+-- exactly once when the event is first "brought to life",+-- e.g. when an event handler is registered to it.+newEventsNamed :: Ord name+ => Handler (name, Event a, Handler a) -- ^ Initialization procedure.+ -> IO (name -> Event a) -- ^ Series of events.+newEventsNamed initialize = do+ eventsRef <- newIORef Map.empty+ return $ \name -> E $ memoize $ do+ events <- readIORef eventsRef+ case Map.lookup name events of+ Just p -> return p+ Nothing -> do+ (p, fire) <- Prim.newPulse+ writeIORef eventsRef $ Map.insert name p events+ initialize (name, E $ fromPure p, fire)+ return p+++-- | Register an event 'Handler' for an 'Event'.+-- All registered handlers will be called whenever the event occurs.+--+-- When registering an event handler, you will also be given an action+-- that unregisters this handler again.+--+-- > do unregisterMyHandler <- register event myHandler+--+-- FIXME: Unregistering event handlers does not work yet.+register :: Event a -> Handler a -> IO (IO ())+register e h = do+ p <- at (unE e) -- evaluate the memoized action+ Prim.addHandler p h++-- | Register an event 'Handler' for a 'Behavior'.+-- All registered handlers will be called whenever the behavior changes.+--+-- However, note that this is only an approximation,+-- as behaviors may change continuously.+-- Consequently, handlers should be idempotent.+onChange :: Behavior a -> Handler a -> IO ()+onChange (B l e) h = void $ do+ -- This works because latches are updated before the handlers are being called.+ register e (\_ -> h =<< Prim.readLatch l)++-- | Read the current value of a 'Behavior'.+currentValue :: MonadIO m => Behavior a -> m a+currentValue (B l _) = liftIO $ Prim.readLatch l+++{-----------------------------------------------------------------------------+ Core Combinators+------------------------------------------------------------------------------}+instance Functor Event where+ fmap f e = E $ liftMemo1 (Prim.mapP f) (unE e)++unsafeMapIO :: (a -> IO b) -> Event a -> Event b+unsafeMapIO f e = E $ liftMemo1 (Prim.unsafeMapIOP f) (unE e)++-- | Event that never occurs.+-- Think of it as @never = []@.+never :: Event a+never = E $ fromPure Prim.neverP++-- | Return all event occurrences that are 'Just' values, discard the rest.+-- Think of it as+--+-- > filterJust es = [(time,a) | (time,Just a) <- es]+filterJust :: Event (Maybe a) -> Event a+filterJust e = E $ liftMemo1 Prim.filterJustP (unE e)++-- | Merge two event streams of the same type.+-- In case of simultaneous occurrences, the event values are combined+-- with the binary function.+-- Think of it as+--+-- > unionWith f ((timex,x):xs) ((timey,y):ys)+-- > | timex == timey = (timex,f x y) : unionWith f xs ys+-- > | timex < timey = (timex,x) : unionWith f xs ((timey,y):ys)+-- > | timex > timey = (timey,y) : unionWith f ((timex,x):xs) ys+unionWith :: (a -> a -> a) -> Event a -> Event a -> Event a+unionWith f e1 e2 = E $ liftMemo2 (Prim.unionWithP f) (unE e1) (unE e2)++-- | Apply a time-varying function to a stream of events.+-- Think of it as+--+-- > apply bf ex = [(time, bf time x) | (time, x) <- ex]+apply :: Behavior (a -> b) -> Event a -> Event b+apply f x = E $ liftMemo1 (\p -> Prim.applyP (latch f) p) (unE x)++infixl 4 <@>, <@++-- | Infix synonym for 'apply', similar to '<*>'.+(<@>) :: Behavior (a -> b) -> Event a -> Event b+(<@>) = apply++-- | Variant of 'apply' similar to '<*'+(<@) :: Behavior a -> Event b -> Event a+b <@ e = (const <$> b) <@> e++-- | The 'accumB' function is similar to a /strict/ left fold, 'foldl''.+-- It starts with an initial value and combines it with incoming events.+-- For example, think+--+-- > accumB "x" [(time1,(++"y")),(time2,(++"z"))]+-- > = stepper "x" [(time1,"xy"),(time2,"xyz")]+--+-- Note that the value of the behavior changes \"slightly after\"+-- the events occur. This allows for recursive definitions.+accumB :: MonadIO m => a -> Event (a -> a) -> m (Behavior a)+accumB a e = liftIO $ do+ (l1,p1) <- Prim.accumL a =<< at (unE e)+ p2 <- Prim.mapP (const ()) p1+ return $ B l1 (E $ fromPure p2)+++-- | Construct a time-varying function from an initial value and+-- a stream of new values. Think of it as+--+-- > stepper x0 ex = return $ \time ->+-- > last (x0 : [x | (timex,x) <- ex, timex < time])+--+-- Note that the smaller-than-sign in the comparison @timex < time@ means+-- that the value of the behavior changes \"slightly after\"+-- the event occurrences. This allows for recursive definitions.+stepper :: MonadIO m => a -> Event a -> m (Behavior a)+stepper a e = accumB a (const <$> e)++-- | The 'accumE' function accumulates a stream of events.+-- Example:+--+-- > accumE "x" [(time1,(++"y")),(time2,(++"z"))]+-- > = return [(time1,"xy"),(time2,"xyz")]+--+-- Note that the output events are simultaneous with the input events,+-- there is no \"delay\" like in the case of 'accumB'.+accumE :: MonadIO m => a -> Event (a -> a) -> m (Event a)+accumE a e = liftIO $ do+ p <- fmap snd . Prim.accumL a =<< at (unE e)+ return $ E $ fromPure p++instance Functor Behavior where+ fmap f ~(B l e) = B (Prim.mapL f l) e++instance Applicative Behavior where+ pure a = B (Prim.pureL a) never+ ~(B lf ef) <*> ~(B lx ex) =+ B (Prim.applyL lf lx) (unionWith const ef ex)++{- $classes++/Further combinators that Haddock can't document properly./++> instance Applicative Behavior++'Behavior' is an applicative functor. In particular, we have the following functions.++> pure :: a -> Behavior a++The constant time-varying value. Think of it as @pure x = \\time -> x@.++> (<*>) :: Behavior (a -> b) -> Behavior a -> Behavior b++Combine behaviors in applicative style.+Think of it as @bf \<*\> bx = \\time -> bf time $ bx time@.++-}++{- $recursion++Recursion in the 'IO' monad is possible, but somewhat limited.+The main rule is that the sequence of IO actions must be known+in advance, only the values may be recursive.++Good:++> mdo+> let e2 = apply (const <$> b) e1 -- applying a behavior is not an IO action+> b <- accumB $ (+1) <$ e2++Bad:++> mdo+> b <- accumB $ (+1) <$ e2 -- actions executed here could depend ...+> let e2 = apply (const <$> b) e1 -- ... on this value++-}++{-----------------------------------------------------------------------------+ Derived Combinators+------------------------------------------------------------------------------}+-- | Return all event occurrences that fulfill the predicate, discard the rest.+filterE :: (a -> Bool) -> Event a -> Event a+filterE p = filterJust . fmap (\a -> if p a then Just a else Nothing)++-- | Return all event occurrences that fulfill the time-varying predicate,+-- discard the rest. Generalization of 'filterE'.+filterApply :: Behavior (a -> Bool) -> Event a -> Event a+filterApply bp = fmap snd . filterE fst . apply ((\p a -> (p a,a)) <$> bp)++-- | Return event occurrences only when the behavior is 'True'.+-- Variant of 'filterApply'.+whenE :: Behavior Bool -> Event a -> Event a+whenE bf = filterApply (const <$> bf)++-- | Split event occurrences according to a tag.+-- The 'Left' values go into the left component while the 'Right' values+-- go into the right component of the result.+split :: Event (Either a b) -> (Event a, Event b)+split e = (filterJust $ fromLeft <$> e, filterJust $ fromRight <$> e)+ where+ fromLeft (Left a) = Just a+ fromLeft (Right _) = Nothing+ fromRight (Left _) = Nothing+ fromRight (Right b) = Just b++-- | Collect simultaneous event occurrences in a list.+unions :: [Event a] -> Event [a]+unions = foldr (unionWith (++)) never . map (fmap (:[]))++-- | Apply a list of functions in succession.+-- Useful in conjunction with 'unions'.+--+-- > concatenate [f,g,h] = f . g . h+concatenate :: [a -> a] -> (a -> a)+concatenate = foldr (.) id++{- $accumulation++Note: All accumulation functions are strict in the accumulated value!+acc -> (x,acc) is the order used by 'unfoldr' and 'State'.++-}++-- | Efficient combination of 'accumE' and 'accumB'.+mapAccum :: MonadIO m => acc -> Event (acc -> (x,acc)) -> m (Event x, Behavior acc)+mapAccum acc ef = do+ e <- accumE (undefined,acc) ((. snd) <$> ef)+ b <- stepper acc (snd <$> e)+ return (fst <$> e, b)+++{-----------------------------------------------------------------------------+ Tidings++ Data type for combining user events.+ See <http://apfelmus.nfshost.com/blog/2012/03/29-frp-three-principles-bidirectional-gui.html>+ for more information.+------------------------------------------------------------------------------}+-- | Data type representing a behavior ('facts')+-- and suggestions to change it ('rumors').+data Tidings a = T { facts :: Behavior a, rumors :: Event a }++-- | Smart constructor. Combine facts and rumors into 'Tidings'.+tidings :: Behavior a -> Event a -> Tidings a+tidings b e = T b e++instance Functor Tidings where+ fmap f (T b e) = T (fmap f b) (fmap f e)++-- | The applicative instance combines 'rumors'+-- and uses 'facts' when some of the 'rumors' are not available.+instance Applicative Tidings where+ pure x = T (pure x) never+ f <*> x = uncurry ($) <$> pair f x++pair :: Tidings a -> Tidings b -> Tidings (a,b)+pair (T bx ex) (T by ey) = T b e+ where+ b = (,) <$> bx <*> by+ ex' = flip (,) <$> by <@> ex+ ey' = (,) <$> bx <@> ey+ e = unionWith (\(x,_) (_,y) -> (x,y)) ex' ey'+++{-----------------------------------------------------------------------------+ Test+------------------------------------------------------------------------------}+test :: IO (Int -> IO ())+test = do+ (e1,fire) <- newEvent+ e2 <- accumE 0 $ (+) <$> e1+ _ <- register e2 print++ return fire++test_recursion1 :: IO (IO ())+test_recursion1 = mdo+ (e1, fire) <- newEvent+ let e2 :: Event Int+ e2 = apply (const <$> b) e1+ b <- accumB 0 $ (+1) <$ e2+ _ <- register e2 print++ return $ fire ()++
+ src/Reactive/Threepenny/Memo.hs view
@@ -0,0 +1,42 @@+{-# LANGUAGE RecursiveDo #-}+module Reactive.Threepenny.Memo (+ Memo, fromPure, memoize, at, liftMemo1, liftMemo2,+ ) where++import Data.IORef+import System.IO.Unsafe++{-----------------------------------------------------------------------------+ Memoize time-varying values / computations+------------------------------------------------------------------------------}+data Memo a+ = Const a+ | Memoized (IORef (MemoD a))++type MemoD a = Either (IO a) a++fromPure :: a -> Memo a+fromPure = Const++at :: Memo a -> IO a+at (Const a) = return a+at (Memoized r) = do+ memo <- readIORef r+ case memo of+ Right a -> return a+ Left ma -> mdo+ writeIORef r $ Right a+ a <- ma -- allow some recursion+ return a++memoize :: IO a -> Memo a+memoize m = unsafePerformIO $ Memoized <$> newIORef (Left m)++liftMemo1 :: (a -> IO b) -> Memo a -> Memo b+liftMemo1 f ma = memoize $ f =<< at ma++liftMemo2 :: (a -> b -> IO c) -> Memo a -> Memo b -> Memo c+liftMemo2 f ma mb = memoize $ do+ a <- at ma+ b <- at mb+ f a b
+ src/Reactive/Threepenny/Monads.hs view
@@ -0,0 +1,12 @@+module Reactive.Threepenny.Monads where++import Control.Monad.Trans.RWS.Lazy+import Reactive.Threepenny.Types++{-----------------------------------------------------------------------------+ EvalP - evaluate pulses+------------------------------------------------------------------------------}+runEvalP :: Values -> EvalP a -> IO (a, Values)+runEvalP pulses m = do+ (a, s, _) <- runRWST m () pulses+ return (a, s)
+ src/Reactive/Threepenny/PulseLatch.hs view
@@ -0,0 +1,203 @@+{-# LANGUAGE RecordWildCards, RecursiveDo #-}+module Reactive.Threepenny.PulseLatch (+ Pulse, newPulse, addHandler,+ neverP, mapP, filterJustP, unionWithP, unsafeMapIOP,+ + Latch,+ pureL, mapL, applyL, accumL, applyP,+ readLatch,+ + -- * Internal+ test, test_recursion1+ ) where+++import Control.Monad+import Control.Monad.Trans.Class+import Control.Monad.Trans.RWS as Monad++import Data.IORef++import qualified Data.HashMap.Strict as Map+import qualified Data.Vault.Strict as Vault+import Data.Unique.Really++import Reactive.Threepenny.Monads+import Reactive.Threepenny.Types++{-----------------------------------------------------------------------------+ Pulse+------------------------------------------------------------------------------}+-- Turn evaluation action into pulse that caches the value.+cacheEval :: EvalP (Maybe a) -> Build (Pulse a)+cacheEval e = do+ key <- Vault.newKey+ return $ Pulse+ { addHandlerP = \_ -> return (return ())+ , evalP = do+ vault <- Monad.get+ case Vault.lookup key vault of+ Just a -> return a+ Nothing -> do+ a <- e+ Monad.put $ Vault.insert key a vault+ return a+ }++-- Add a dependency to a pulse, for the sake of keeping track of dependencies.+dependOn :: Pulse a -> Pulse b -> Pulse a+dependOn p q = p { addHandlerP = \h -> (>>) <$> addHandlerP p h <*> addHandlerP q h }++-- Execute an action when the pulse occurs+whenPulse :: Pulse a -> (a -> IO ()) -> Handler+whenPulse p f = do+ ma <- evalP p+ case ma of+ Just a -> return (f a)+ Nothing -> return $ return ()++{-----------------------------------------------------------------------------+ Interface to the outside world.+------------------------------------------------------------------------------}+-- | Create a new pulse and a function to trigger it.+newPulse :: Build (Pulse a, a -> IO ())+newPulse = do+ key <- Vault.newKey+ handlersRef <- newIORef Map.empty -- map of handlers+ + let+ -- add handler to map+ addHandlerP :: ((Unique, Priority), Handler) -> Build (IO ())+ addHandlerP (uid,m) = do+ modifyIORef' handlersRef (Map.insert uid m)+ return $ modifyIORef' handlersRef (Map.delete uid)+ + -- evaluate all handlers attached to this input pulse+ fireP a = do+ let pulses = Vault.insert key (Just a) $ Vault.empty+ handlers <- readIORef handlersRef+ (ms, _) <- runEvalP pulses $ sequence $ + [m | ((_,DoLatch),m) <- Map.toList handlers]+ ++ [m | ((_,DoIO ),m) <- Map.toList handlers] + sequence_ ms+ + evalP = join . Vault.lookup key <$> Monad.get++ return (Pulse {..}, fireP)++-- | Register a handler to be executed whenever a pulse occurs.+addHandler :: Pulse a -> (a -> IO ()) -> Build (IO ())+addHandler p f = do+ uid <- newUnique+ addHandlerP p ((uid, DoIO), whenPulse p f)++-- | Read the value of a 'Latch' at a particular moment in Build.+readLatch :: Latch a -> Build a+readLatch = readL++{-----------------------------------------------------------------------------+ Pulse and Latch+ Public API+------------------------------------------------------------------------------}+-- | Create a new pulse that never occurs.+neverP :: Pulse a+neverP = Pulse+ { addHandlerP = const $ return (return ())+ , evalP = return Nothing+ }++-- | Map a function over pulses.+mapP :: (a -> b) -> Pulse a -> Build (Pulse b)+mapP f p = (`dependOn` p) <$> cacheEval (return . fmap f =<< evalP p)++-- | Map an IO function over pulses. Is only executed once.+unsafeMapIOP :: (a -> IO b) -> Pulse a -> Build (Pulse b)+unsafeMapIOP f p = (`dependOn` p) <$> cacheEval (traverse' . fmap f =<< evalP p)+ where+ traverse' :: Maybe (IO a) -> EvalP (Maybe a)+ traverse' Nothing = return Nothing+ traverse' (Just m) = Just <$> lift m++-- | Filter occurrences. Only keep those of the form 'Just'.+filterJustP :: Pulse (Maybe a) -> Build (Pulse a)+filterJustP p = (`dependOn` p) <$> cacheEval (return . join =<< evalP p)++-- | Pulse that occurs when either of the pulses occur.+-- Combines values with the indicated function when both occur.+unionWithP :: (a -> a -> a) -> Pulse a -> Pulse a -> Build (Pulse a)+unionWithP f p q = (`dependOn` q) . (`dependOn` p) <$> cacheEval eval+ where+ eval = do+ x <- evalP p+ y <- evalP q+ return $ case (x,y) of+ (Nothing, Nothing) -> Nothing+ (Just a , Nothing) -> Just a+ (Nothing, Just a ) -> Just a+ (Just a1, Just a2) -> Just $ f a1 a2++-- | Apply the current latch value whenever the pulse occurs.+applyP :: Latch (a -> b) -> Pulse a -> Build (Pulse b)+applyP l p = (`dependOn` p) <$> cacheEval eval+ where+ eval = do+ f <- lift $ readL l+ a <- evalP p+ return $ f <$> a++-- | Accumulate values in a latch.+accumL :: a -> Pulse (a -> a) -> Build (Latch a, Pulse a)+accumL a p1 = do+ -- IORef to hold the current latch value+ latch <- newIORef a+ let l1 = Latch { readL = readIORef latch }++ -- calculate new pulse from old value+ let l2 = mapL (flip ($)) l1+ p2 <- applyP l2 p1++ -- register handler to update latch+ uid <- newUnique+ let handler = whenPulse p2 $ (writeIORef latch $!)+ void $ addHandlerP p2 ((uid, DoLatch), handler)+ + return (l1,p2)++-- | Latch whose value stays constant.+pureL :: a -> Latch a+pureL a = Latch { readL = return a }++-- | Map a function over latches.+--+-- Evaluated only when needed, result is not cached.+mapL :: (a -> b) -> Latch a -> Latch b+mapL f l = Latch { readL = f <$> readL l } ++-- | Apply two current latch values+--+-- Evaluated only when needed, result is not cached.+applyL :: Latch (a -> b) -> Latch a -> Latch b+applyL l1 l2 = Latch { readL = readL l1 <*> readL l2 }++{-----------------------------------------------------------------------------+ Test+------------------------------------------------------------------------------}+test :: IO (Int -> IO ())+test = do+ (p1, fire) <- newPulse+ p2 <- mapP (+) p1+ (l1,_) <- accumL 0 p2+ let l2 = mapL const l1+ p3 <- applyP l2 p1+ void $ addHandler p3 print+ return fire++test_recursion1 :: IO (IO ())+test_recursion1 = mdo+ (p1, fire) <- newPulse+ p2 <- applyP l2 p1+ p3 <- mapP (const (+1)) p2+ ~(l1,_) <- accumL (0::Int) p3+ let l2 = mapL const l1+ void $ addHandler p2 print+ return $ fire ()
+ src/Reactive/Threepenny/Types.hs view
@@ -0,0 +1,41 @@+module Reactive.Threepenny.Types where++import Control.Monad.Trans.RWS.Lazy+import Data.Functor.Identity ()++import Data.Hashable+import qualified Data.Vault.Strict as Vault.Strict+import Data.Unique.Really++{-----------------------------------------------------------------------------+ Pulse and Latch+------------------------------------------------------------------------------}+type Values = Vault.Strict.Vault++type Handler = EvalP (IO ())+data Priority = DoLatch | DoIO deriving (Eq,Show,Ord,Enum)++data Pulse a = Pulse+ { addHandlerP :: ((Unique, Priority), Handler) -> Build (IO ())+ , evalP :: EvalP (Maybe a)+ }++instance Hashable Priority where+ hashWithSalt = hashUsing fromEnum+ hash = fromEnum++data Latch a = Latch { readL :: EvalL a }++{-----------------------------------------------------------------------------+ Monads+------------------------------------------------------------------------------}+-- | The 'EvalP' monad is used to evaluate pulses.+type EvalP = RWST () () Values BuildIO+ -- state: current pulse values++-- | The 'EvalL' monad is used to evaluate latches.+type EvalL = IO++-- | The 'Build' monad is used to add pulses and latches to the graph.+type Build = IO+type BuildIO = Build
− src/UseWords.hs
@@ -1,110 +0,0 @@-{-# LANGUAGE CPP, PackageImports #-}-{-# LANGUAGE ViewPatterns #-}--module Main where--import Control.Applicative hiding ((<|>),many)-import Control.Monad-import Control.Arrow (second)-import Data.Maybe-import Text.Parsec--#ifdef CABAL-import qualified "threepenny-gui" Graphics.UI.Threepenny as UI-import "threepenny-gui" Graphics.UI.Threepenny.Core hiding (string)-#else-import qualified Graphics.UI.Threepenny as UI-import Graphics.UI.Threepenny.Core hiding (string)-#endif-import Paths-import System.FilePath ((</>))--{------------------------------------------------------------------------------ GUI-------------------------------------------------------------------------------}-main :: IO ()-main = do- static <- getStaticDir- startGUI Config- { tpPort = 10000- , tpCustomHTML = Nothing- , tpStatic = static- } setup---setup :: Window -> IO ()-setup w = do- filename <- fmap (</> "and-then-haskell.txt") getStaticDir - andthen <- readFile filename- case parts filename andthen of- Left parseerror -> debug w $ show parseerror- Right parts -> void $ do- UI.addStyleSheet w "use-words.css"-- let (header, Prelude.drop 2 -> rest) = splitAt 3 parts - - (views1, vars1) <- renderParts header- (views2, vars2) <- renderParts rest- varChoices <- mapM (renderVarChoice (vars1 ++ vars2)) vars- - getBody w #+- [ viewSource- , UI.div #. "wrap" #+ (- [ UI.div #. "header" #+ map element views1- , UI.ul #. "vars" #+ map element varChoices- ]- ++ map element views2 )- ]- -type VariableViews = [(Name, Element)]--renderVarChoice :: VariableViews -> Variable -> IO Element-renderVarChoice views (label,(name,def)) = do- input <- UI.input #. "var-value" # set value def- - on (domEvent "livechange") input $ \(EventData xs) -> do- let s = concat $ catMaybes xs- forM_ (filter ((==name).fst) views) $ \(_,el) -> do- element el # set text s- - UI.li #+ [UI.string (label ++ ":"), element input]--renderParts :: [Part] -> IO ([Element], VariableViews)-renderParts parts = do- views <- mapM renderPart parts- let variables = [(var, view) | (Ref var, view) <- zip parts views]- return (views, variables)--renderPart :: Part -> IO Element-renderPart (Text str) = UI.div #. "text" #+ [UI.string str]-renderPart (Ref var) = UI.div #. "var"- # maybe (set text $ "{" ++ var ++ "}") (either (set html) (set text))- (lookup var templatevars)--viewSource :: IO Element-viewSource = UI.p #+- [UI.anchor #. "view-source" # set UI.href url #+ [UI.string "View source code"]]- where- url = "https://github.com/HeinrichApfelmus/threepenny-gui/blob/master/src/UseWords.hs"--{------------------------------------------------------------------------------ Parsing-------------------------------------------------------------------------------}-type Name = String-type Variable = (String, (Name, String))--templatevars = map (second Right . snd) vars ++ map (second Left) exts--vars :: [Variable]-vars = [("Favourite technology",("favourite-language","Haskell"))- ,("Technology used at work",("work-language","Python"))- ,("Cool forum",("bar","LtU"))- ,("Particular to technology",("particular-stuff","monads"))]-exts = [("br","<br><br>")]--data Part = Text String | Ref String deriving Show--parts :: SourceName -> String -> Either ParseError [Part]-parts = parse (many (ref <|> text)) where- text = Text <$> many1 (notFollowedBy (string "{") *> anyChar)- ref = Ref <$> (string "{" *> many1 (noneOf "}") <* (string "}"))
threepenny-gui.cabal view
@@ -1,114 +1,159 @@-Name: threepenny-gui-Version: 0.2.0.1-Synopsis: Small GUI framework that uses the web browser as a display.-Description:- Threepenny-GUI is a small GUI framework that uses the web browser as a display.+name: threepenny-gui+version: 0.9.4.2+synopsis: GUI framework that uses the web browser as a display.+description:+ Threepenny-GUI is a GUI framework that uses the web browser as a display. .- It's cheap and easy to install because everyone has a web browser installed.+ It's very easy to install because everyone has a web browser installed. .- Internally, the library implements a small web server- that serves a HTML/JavaScript page with the GUI elements.- You can freely manipulate the HTML DOM and handle browser events+ A program written with Threepenny is essentially a small web server that+ displays the user interface as a web page to any browser that connects to it.+ You can freely manipulate the HTML DOM and handle JavaScript events from your Haskell code. .- Stability forecast: This is an experimental release! Send us your feedback!- Basic functionality should work.+ Stability forecast: This is an experimental release! Send me your feedback! Significant API changes are likely in future versions. . NOTE: This library contains examples, but they are not built by default. To build and install the example, use the @buildExamples@ flag like this . @cabal install threepenny-gui -fbuildExamples@- .- Changelog:- .- * 0.2.0.0 - Snapshot release. First stab at easy JavaScript FFI.- .- * 0.1.0.0 - Initial release. -License: BSD3-License-file: LICENSE-Author: Chris Done, Heinrich Apfelmus-Maintainer: Heinrich Apfelmus <apfelmus at quantentunnel dot de>-Homepage: http://github.com/HeinrichApfelmus/threepenny-gui-Category: Web, GUI-Build-type: Simple-Cabal-version: >=1.8+license: BSD3+license-file: LICENSE+author: Heinrich Apfelmus+maintainer: Heinrich Apfelmus <apfelmus at quantentunnel dot de>+homepage: http://wiki.haskell.org/Threepenny-gui+bug-reports: https://github.com/HeinrichApfelmus/threepenny-gui/issues+category: Web, GUI+build-type: Simple+cabal-version: >=1.10+tested-with:+ GHC == 8.0.2+ , GHC == 8.2.2+ , GHC == 8.4.4+ , GHC == 8.6.5+ , GHC == 8.8.4+ , GHC == 8.10.7+ , GHC == 9.2.8+ , GHC == 9.4.7+ , GHC == 9.6.6+ , GHC == 9.8.4+ , GHC == 9.10.1+ , GHC == 9.12.1 -Extra-Source-Files: src/Graphics/UI/*.html- ,src/Graphics/UI/*.js+extra-source-Files:+ CHANGELOG.md+ , README.md+ , samples/README.md+ , js/*.html+ , js/*.css+ , js/*.js+ , js/lib/*.js -Data-dir: .-Data-files: src/Graphics/UI/*.js- ,src/Graphics/UI/*.html- ,src/Graphics/UI/*.css- ,wwwroot/css/*.css- ,wwwroot/css/*.png- ,wwwroot/*.html- ,wwwroot/*.txt- ,wwwroot/*.wav+data-dir: .+data-files:+ samples/static/css/*.css+ , samples/static/css/*.png+ , samples/static/*.html+ , samples/static/*.png+ , samples/static/*.wav flag buildExamples description: Build example executables.- default: True+ default: False+ manual: True -Source-repository head+flag rebug+ description: The library uses some techniques that are highly+ non-deterministic, for example garbage collection+ and concurrency.+ Bugs in these subsystems are harder to find.+ Activating this flag will expose more of them.+ default: False+ manual: True++source-repository head type: git location: git://github.com/HeinrichApfelmus/threepenny-gui.git +library+ hs-source-dirs: src+ exposed-modules:+ Foreign.JavaScript+ Foreign.RemotePtr+ Graphics.UI.Threepenny+ Graphics.UI.Threepenny.Attributes+ Graphics.UI.Threepenny.Canvas+ Graphics.UI.Threepenny.Core+ Graphics.UI.Threepenny.DragNDrop+ Graphics.UI.Threepenny.Elements+ Graphics.UI.Threepenny.Events+ Graphics.UI.Threepenny.JQuery+ Graphics.UI.Threepenny.SVG+ Graphics.UI.Threepenny.SVG.Attributes+ Graphics.UI.Threepenny.SVG.Elements+ Graphics.UI.Threepenny.Timer+ Graphics.UI.Threepenny.Widgets+ Reactive.Threepenny+ other-modules:+ Foreign.JavaScript.CallBuffer+ Foreign.JavaScript.EventLoop+ Foreign.JavaScript.Include+ Foreign.JavaScript.Marshal+ Foreign.JavaScript.Resources+ Foreign.JavaScript.Server+ Foreign.JavaScript.Types+ Graphics.UI.Threepenny.Internal+ Reactive.Threepenny.Memo+ Reactive.Threepenny.Monads+ Reactive.Threepenny.PulseLatch+ Reactive.Threepenny.Types+ Paths_threepenny_gui+ other-extensions: CPP+ cpp-options: -DCABAL+ if flag(rebug)+ cpp-options: -DREBUG+ ghc-options: -O2+ build-depends:+ base >= 4.8 && < 4.22+ , aeson (>= 0.7 && < 0.10) || == 0.11.* || (>= 1.0 && < 2.3)+ , async >= 2.0 && < 2.3+ , bytestring >= 0.9.2 && < 0.13+ , containers >= 0.4.2 && < 0.8+ , data-default >= 0.5.0 && < 0.8+ , deepseq >= 1.3.0 && < 1.6+ , exceptions >= 0.6 && < 0.11+ , filepath >= 1.3.0 && < 1.6.0+ , file-embed >= 0.0.10 && < 0.1+ , hashable >= 1.2.0 && < 1.6+ , safe == 0.3.*+ , snap-server >= 0.9.0 && < 1.2+ , snap-core >= 0.9.0 && < 1.1+ , stm >= 2.2 && < 2.6+ , template-haskell >= 2.7.0 && < 2.24+ , text >= 0.11 && < 2.2+ , transformers >= 0.3.0 && < 0.7+ , unordered-containers == 0.2.*+ , websockets (>= 0.8 && < 0.12.5) || (> 0.12.5.0 && < 0.14)+ , websockets-snap >= 0.8 && < 0.11+ , vault >= 0.3 && < 0.4+ , vector >= 0.10 && < 0.14+ if impl(ghc >= 8.0)+ ghc-options: -Wcompat -Wnoncanonical-monad-instances+ default-language: Haskell2010 -Library- Hs-source-dirs: src- Exposed-modules:- Control.Event- ,Graphics.UI.Threepenny- ,Graphics.UI.Threepenny.Attributes- ,Graphics.UI.Threepenny.Core- ,Graphics.UI.Threepenny.Canvas- ,Graphics.UI.Threepenny.DragNDrop- ,Graphics.UI.Threepenny.Elements- ,Graphics.UI.Threepenny.Events- ,Graphics.UI.Threepenny.JQuery- ,Graphics.UI.Threepenny.Timer- Other-modules:- Control.Concurrent.Chan.Extra- ,Control.Monad.Extra- ,Control.Monad.IO- ,Control.Concurrent.Delay- ,Graphics.UI.Threepenny.Internal.Core- ,Graphics.UI.Threepenny.Internal.Resources- ,Graphics.UI.Threepenny.Internal.Types- ,Paths_threepenny_gui- CPP-Options: -DCABAL- Build-depends: base >= 4 && < 5- ,snap-server- ,snap-core- ,mtl- ,text- ,safe- ,containers- ,bytestring- ,json >= 0.4.4 && < 0.6- ,time- ,utf8-string- ,network- ,filepath- ,data-default- ,transformers- ,stm- Executable threepenny-examples-bartab if flag(buildExamples) cpp-options: -DCABAL build-depends: base >= 4 && < 5 ,threepenny-gui- ,filepath else buildable: False main-is: BarTab.hs- other-modules: Paths_threepenny_gui, Paths- hs-source-dirs: src+ hs-source-dirs: samples+ default-language: Haskell2010 Executable threepenny-examples-buttons if flag(buildExamples)@@ -120,68 +165,93 @@ buildable: False main-is: Buttons.hs other-modules: Paths_threepenny_gui, Paths- hs-source-dirs: src+ hs-source-dirs: samples+ default-language: Haskell2010 -Executable threepenny-examples-dragndropexample+Executable threepenny-examples-canvas if flag(buildExamples) cpp-options: -DCABAL build-depends: base >= 4 && < 5- ,threepenny-gui ,filepath+ ,threepenny-gui else buildable: False- main-is: DragNDropExample.hs+ main-is: Canvas.hs other-modules: Paths_threepenny_gui, Paths- hs-source-dirs: src+ hs-source-dirs: samples+ default-language: Haskell2010 -Executable threepenny-examples-drummachine+Executable threepenny-examples-chat if flag(buildExamples) cpp-options: -DCABAL build-depends: base >= 4 && < 5 ,threepenny-gui ,filepath+ ,time else buildable: False- main-is: DrumMachine.hs- other-modules: Paths_threepenny_gui, Paths- hs-source-dirs: src+ main-is: Chat.hs+ other-modules: Paths_threepenny_gui, Paths, Data.List.Extra+ hs-source-dirs: samples+ default-language: Haskell2010 -Executable threepenny-examples-missing-dollars+Executable threepenny-examples-crud if flag(buildExamples) cpp-options: -DCABAL build-depends: base >= 4 && < 5- ,filepath+ ,containers ,threepenny-gui+ else+ buildable: False+ main-is: CRUD.hs+ hs-source-dirs: samples+ default-language: Haskell2010++Executable threepenny-examples-currencyconverter+ if flag(buildExamples)+ cpp-options: -DCABAL+ build-depends: base >= 4 && < 5+ ,threepenny-gui ,safe else buildable: False- main-is: MissingDollars.hs- other-modules: Paths_threepenny_gui, Paths- hs-source-dirs: src+ main-is: CurrencyConverter.hs+ hs-source-dirs: samples+ default-language: Haskell2010 -Executable threepenny-examples-use-words+Executable threepenny-examples-dragndropexample if flag(buildExamples) cpp-options: -DCABAL build-depends: base >= 4 && < 5 ,threepenny-gui ,filepath- ,parsec else buildable: False- main-is: UseWords.hs+ main-is: DragNDropExample.hs other-modules: Paths_threepenny_gui, Paths- hs-source-dirs: src+ hs-source-dirs: samples+ default-language: Haskell2010 -Executable threepenny-examples-chat+Executable threepenny-examples-drummachine if flag(buildExamples) cpp-options: -DCABAL build-depends: base >= 4 && < 5 ,threepenny-gui- ,transformers ,filepath- ,time else buildable: False- main-is: Chat.hs- other-modules: Paths_threepenny_gui, Paths, Data.List.Extra- hs-source-dirs: src+ main-is: DrumMachine.hs+ other-modules: Paths_threepenny_gui, Paths+ hs-source-dirs: samples+ default-language: Haskell2010++Executable threepenny-examples-svg+ if flag(buildExamples)+ cpp-options: -DCABAL+ build-depends: base >= 4 && < 5+ ,threepenny-gui+ else+ buildable: False+ main-is: Svg.hs+ hs-source-dirs: samples+ default-language: Haskell2010
− wwwroot/and-then-haskell.txt
@@ -1,77 +0,0 @@-And then there's {favourite-language}...-{br}-{favourite-language} is like "that girl." You know the one...-{br}-You never really went steady, but you'd run into her from time to time-while knocking around in disreputable joints, usually late at night,-every several months or so. She looked so hot, so sleek, so sexy, so-expressive, so exotic. You'd end up back at her place and the night-would just... take off. A complete blur of hot, sweaty, feverish,-delirious, fumbling passion. You'd do things to each other... you'd-do things to her, she'd do things to you... things that you're not-even sure have names, that you're pretty sure are illegal almost-anywhere. Even her kinks have kinks --- and after one of these-nights, you'd realize that you yourself had a lot more kinks than you.-And it wasn't just physical, it was --- cerebral. Ethereal.-Transcendent. But it would all whiz by in a blur, and by morning-you'd find yourself lightheaded, a bit confused, and stumbling-homeward to your regular gal.-{br}-Over the next few days and weeks you'd find yourself occasionally-drifting away, thinking about her. {favourite-language}. You'd be-there, banging away at your regular girl, and find yourself thinking-"you know, if I was with {favourite-language}, I'd be doing this-completely differently." You'd think "I could be doing so much bigger-and better stuff with {favourite-language}." Now, your regular girl,-she's not as exotic as {favourite-language}. Pretty, maybe, if you're-lucky. (Perhaps your regular girlfriend's name is-{work-language}. ;-) But not nearly as --- weird. Wild. Cool.-Exciting. Don't get me wrong --- your girl, she's wonderful. You've-got a wonderful relationship. She's --- comfortable. You can bang-away at her all day and night. She's accommodating. Easy going. You-work well together. But --- confidentially --- she's, well, maybe-just a little bit boring. You'd catch yourself thinking these things,-and the guilty pangs would get to you... You'd quash the thoughts,-buckle down, and get back to banging away. Comfortable... there's a-lot to be said for that, ya know? Comfortable... just keep telling-yourself that.-{br}-Months would go by. Late some night you'd find yourself out,-disreputable places again. Maybe that hacker bar, {bar}. Somebody'd-slip you an URL for some renegade paper, you know, one of *those*-papers. You'd run into {favourite-language} again. And the whole-thing starts over.-{br}-Eventually, you're going to get the ultimatum. {favourite-language}'s-ultimately just like any other girl on some level; she needs-commitment. Eventually, after one night of wild, feverish, kinky,-abstract passion, she's going to say to you: "All these times, and you-don't understand me at all! You know, you're going to have to get-serious, mister! I've got needs, too. You're going to have to get-serious about my {particular-stuff}, or that's the last time you're-going to play with them! Got it?"-{br}-...and then, you've got to make The Choice.-{br}-Chances are, you're going to go back to your regular gal.-{favourite-language}'s just too much for any one man, probably. She-leaves a trail of broken, brainy, embittered PhDs and former-programmers behind her. She ruins you for the RealWorld. You can-ride a while, but you probably can't go the distance with her. Go-back to your regular gal and try not to think too much about what-you've seen. Done. Felt. Thought.-{br}-Maybe you can salvage a little happiness; but it'll be hard. After-all... you've tasted {favourite-language}.-{br}-She's not like anything else.-{br}-Original post: http://www.xent.com/pipermail/fork/Week-of-Mon-20070219/044101.html-{br}-{br}-(It's intended in good humour (and just a demonstration of the Threepenny library). Haskell is my favourite programming language, and I like-this mailing list message, but anyone with a brain can realise it's-easy to replace the word "Haskell" with "Lisp" or "Vim" or whatever-you prefer and suddenly it's stroking your sense of identity, the post-is lacking in any content… when you're next reading evangelism, try-swapping the names with other technologies in your head, it's funny.)
− wwwroot/chat.html
@@ -1,13 +0,0 @@-<!doctype html>-<head>- <title>Chat</title>- <link rel="stylesheet" type="text/css" href="static/css/chat.css"/>-<link href='http://fonts.googleapis.com/css?family=Fredericka+the+Great'- rel='stylesheet' type='text/css'>-<link href='http://fonts.googleapis.com/css?family=Annie+Use+Your+Telescope' rel='stylesheet' type='text/css'>- <script src="driver/threepenny-gui.js"></script>-</head>-<body>- <noscript>Please enable JavaScript.</noscript>-</body>-</html>
− wwwroot/css/DragNDropExample.css
@@ -1,18 +0,0 @@-.box-drag {- position: absolute;- top : 20px;- border: 1px solid gray;- width : 90px;- height: 2ex;- text-align: center;- padding: 1ex;-}--.box-drop {- position: absolute;- top : 100px;- width : 100px;- height: 2.2ex;- text-align: center;- padding: 1ex;-}
− wwwroot/css/buttons.css
@@ -1,52 +0,0 @@-body {- background: #333;- color: #ccc;- font-family: ubuntu, sans-serif;- font-size: 14px;- background: url(stripes-bg.png);- padding-top: 1em;- text-shadow: #000 3px 1px 3px;-}-.wrap {- width: 40em;- margin: auto;-}-.buttons-list {- color: #aaa;-}-.button {- cursor: pointer;- color: #acc2a1;- text-decoration: underline;-}-a {- color: #acc2a1;-}-button, input {- border: 0;- color: #ccc;- padding: 0.3em;- border-radius:0.3em;- box-shadow: #191919 2px 1px 2px;-}-input {- background: #292929;- width: 2em;-}-button:focus,input:focus {- background: #222;-}-button {- background: #444;-}-h1 {- font-size: 4em;- margin: 0;- padding-bottom: 0.2em;-}-h2 {- margin-top: 1.3em;- padding-bottom: 0em;- margin-bottom:0;-}-.view-source { float: right }
− wwwroot/css/chat.css
@@ -1,106 +0,0 @@-body {- background: #333;- color: #ccc;- font-family: 'Annie Use Your Telescope';- font-size: 26px;- text-shadow: #000 3px 1px 3px;- padding:0;- margin:0;- background: url(wood-bg.png) #333;-}-.header {- font-family: 'Fredericka the Great', cursive;- font-size: 50px;- color: #ad1c43;- float: left;- padding: 10px 20px;- position:absolute;- z-index:2;-}-.message-area {- clear:both;- padding: 20px;- color: #fff;- position: absolute;- top: 1em;- right:0;- left:0;- bottom:10%;- overflow: auto;-}-.gradient {- background: url(fade.png) repeat-x;- position:absolute;- top:0;- height: 150px;- left:0;- right:0;- z-index:1;-}-.send-area {- z-index:0;- position:fixed;- top:90%;- left:0;- right:0;- bottom:0;- background:#fff;-}-.send-textarea {- padding: 0.25em;- border:0;- position: absolute;- left:0;- width:100%;- bottom:0;- top:-1px;- font-family: 'Annie Use Your Telescope';- font-size: 25px;- background: #111;- color: #999;-}-.message {- margin-top: 1em;- border-top: 0.1em dotted #333;-}-.message .timestamp {- color: #888;- font-size: 15px;- display: inline;- float: right;-}-.message .name {- display: inline-block;- font-size: 18px;- font-style: italic;- float:left;-}-.message .content {- display:block;- clear:both;-}-.name-area {- position: absolute;- top: 20px;- right: 20px;- z-index: 3;-}-.name-input {- background: #111;- border: 0;- border-radius: 0.3em;- font-family: 'Annie Use Your Telescope';- color: #777;- font-size: 20px;- padding-left: 0.2em;-}-.name-input:focus,.send-textarea:focus {- background: #333;-}-.view-source {- position: absolute;- z-index: 4;- right: 20px;- color: #555;- top: 100px;-}
− wwwroot/css/fade.png
binary file changed (4690 → absent bytes)
− wwwroot/css/missing-dollars.css
@@ -1,52 +0,0 @@-body {- background: #333;- color: #ccc;- font-family: ubuntu, sans-serif;- font-size: 14px;- background: url(stripes-bg.png);- padding-top: 1em;- text-shadow: #000 3px 1px 3px;-}-.wrap {- width: 40em;- margin: auto;-}-.buttons-list {- color: #aaa;-}-.button {- cursor: pointer;- color: #acc2a1;- text-decoration: underline;-}-a {- color: #acc2a1;-}-button, input {- border: 0;- color: #ccc;- padding: 0.3em;- border-radius:0.3em;- box-shadow: #191919 2px 1px 2px;-}-input {- background: #292929;- width: 2em;-}-button:focus,input:focus {- background: #222;-}-button {- background: #444;-}-h1 {- font-size: 4em;- margin: 0;- padding: 0;-}-h2 {- margin-top: 1.3em;- padding-bottom: 0em;- margin-bottom:0;-}-.view-source { float: right }
− wwwroot/css/stripes-bg.png
binary file changed (162 → absent bytes)
− wwwroot/css/use-words.css
@@ -1,70 +0,0 @@-.text,.var {- display: inline;-}-.var {- color: #fff;-}-body {- background: #333;- color: #ccc;- font-family: ubuntu, sans-serif;- font-size: 14px;- background: url(stripes-bg.png);- padding-top: 1em;- text-shadow: #000 3px 1px 3px;-}-.wrap {- width: 50em;- margin: auto;- padding-bottom: 5em;- line-height: 1.5em;-}-.buttons-list {- color: #aaa;-}-.button {- cursor: pointer;- color: #acc2a1;- text-decoration: underline;-}-a {- color: #acc2a1;-}-button, input {- border: 0;- color: #ccc;- padding: 0.3em;- border-radius:0.3em;- box-shadow: #191919 2px 1px 2px;-}-label { display: block; padding-bottom: 0.4em; padding-top: 0.8em;}-input {- background: #292929;- width: 20em;-}-button:focus,input:focus {- background: #222;-}-button {- background: #444;-}-h1 {- font-size: 4em;- margin: 0;- padding-bottom: 0.2em;-}-h2 {- margin-top: 1.3em;- padding-bottom: 0em;- margin-bottom:0;-}-.view-source { float: right }-.vars { margin-bottom: 3em; margin-top: 2em; }-.header {- font-size: 3em;- padding-top: 0.5em;-}-.code-link {- float: right;- display: block;-}
− wwwroot/css/wood-bg.png
binary file changed (628939 → absent bytes)
− wwwroot/hihat.wav
binary file changed (28556 → absent bytes)
− wwwroot/kick.wav
binary file changed (31622 → absent bytes)
− wwwroot/snare.wav
binary file changed (76332 → absent bytes)