diff --git a/README.md b/README.md index c0afc713..ebdae484 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ In order to minimize the dependencies depending on your needs, we provide these - `servant-server`, which lets you *implement* an HTTP server with handlers for each endpoint of an API. - `servant-client`, which lets you derive automatically Haskell functions that let you query each endpoint of a `servant` webservice. - `servant-docs`, which lets you generate API docs for your webservice. -- `servant-jquery`, which lets you derive Javascript functions (based on jquery) to query your API's endpoints, in the same spirit as `servant-client`. +- `servant-js`, which lets you derive Javascript functions (using vanilla JS ajax requests, angular or jquery) to query your API's endpoints, in the same spirit as `servant-client`. - `servant-blaze` and `servant-lucid` provide easy HTML rendering of your data as an `HTML` content-type "combinator". ## Tutorial diff --git a/scripts/shell.nix b/scripts/shell.nix index 61dda6c8..615506cc 100644 --- a/scripts/shell.nix +++ b/scripts/shell.nix @@ -9,12 +9,12 @@ let modifiedHaskellPackages = haskellngPackages.override { ../servant-server {}) "--ghc-options=-Werror"; servant-client = appendConfigureFlag (self.callPackage ../servant-client {}) "--ghc-options=-Werror"; - servant-jquery = appendConfigureFlag (self.callPackage - ../servant-jquery {}) "--ghc-options=-Werror"; + servant-js = appendConfigureFlag (self.callPackage + ../servant-js {}) "--ghc-options=-Werror"; servant-docs = appendConfigureFlag (self.callPackage ../servant-docs {}) "--ghc-options=-Werror"; }; }; in modifiedHaskellPackages.ghcWithPackages ( p : with p ; [ - servant servant-server servant-client servant-jquery servant-docs + servant servant-server servant-client servant-js servant-docs ]) diff --git a/servant-examples/servant-examples.cabal b/servant-examples/servant-examples.cabal index 0e8f096c..d91c585e 100644 --- a/servant-examples/servant-examples.cabal +++ b/servant-examples/servant-examples.cabal @@ -32,7 +32,7 @@ executable tutorial , random , servant == 0.4.* , servant-docs == 0.4.* - , servant-jquery == 0.4.* + , servant-js == 0.4.* , servant-lucid == 0.4.* , servant-server == 0.4.* , text diff --git a/servant-examples/tutorial/T9.hs b/servant-examples/tutorial/T9.hs index 1b0633f0..140f48d6 100644 --- a/servant-examples/tutorial/T9.hs +++ b/servant-examples/tutorial/T9.hs @@ -12,7 +12,8 @@ import Data.Text (Text) import GHC.Generics import Network.Wai import Servant -import Servant.JQuery +import Servant.JS +import Servant.JS.JQuery import System.Random import qualified Data.Text as T @@ -92,7 +93,7 @@ server' = server :<|> serveDirectory "tutorial/t9" apiJS :: String -apiJS = jsForAPI api +apiJS = jsForAPI api jquery writeJSFiles :: IO () writeJSFiles = do diff --git a/servant-jquery/examples/www/api.js b/servant-jquery/examples/www/api.js deleted file mode 100644 index 0adbd89a..00000000 --- a/servant-jquery/examples/www/api.js +++ /dev/null @@ -1,20 +0,0 @@ - -function postcounter(onSuccess, onError) -{ - $.ajax( - { url: '/counter' - , success: onSuccess - , error: onError - , type: 'POST' - }); -} - -function getcounter(onSuccess, onError) -{ - $.ajax( - { url: '/counter' - , success: onSuccess - , error: onError - , type: 'GET' - }); -} diff --git a/servant-jquery/src/Servant/JQuery.hs b/servant-jquery/src/Servant/JQuery.hs deleted file mode 100644 index 974b3925..00000000 --- a/servant-jquery/src/Servant/JQuery.hs +++ /dev/null @@ -1,117 +0,0 @@ -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE TypeOperators #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE FlexibleInstances #-} ------------------------------------------------------------------------------ --- | --- Module : Servant.JQuery --- Copyright : (C) 2014 Alp Mestanogullari --- License : BSD3 --- Maintainer : Alp Mestanogullari --- Stability : experimental --- Portability : non-portable -module Servant.JQuery - ( jquery - , generateJS - , jsForAPI - , printJS - , module Servant.JQuery.Internal - , GenerateCode(..) - ) where - -import Control.Lens -import Data.List -import Data.Monoid -import Data.Proxy -import Servant.API -import Servant.JQuery.Internal - -jquery :: HasJQ layout => Proxy layout -> JQ layout -jquery p = jqueryFor p defReq - --- js codegen -generateJS :: AjaxReq -> String -generateJS req = "\n" <> - "function " <> fname <> "(" <> argsStr <> ")\n" - <> "{\n" - <> " $.ajax(\n" - <> " { url: " <> url <> "\n" - <> " , success: onSuccess\n" - <> dataBody - <> reqheaders - <> " , error: onError\n" - <> " , type: '" <> method <> "'\n" - <> " });\n" - <> "}\n" - - where argsStr = intercalate ", " args - args = captures - ++ map (view argName) queryparams - ++ body - ++ map (toValidFunctionName . (<>) "header" . headerArgName) hs - ++ ["onSuccess", "onError"] - - captures = map captureArg - . filter isCapture - $ req ^. reqUrl.path - - hs = req ^. reqHeaders - - queryparams = req ^.. reqUrl.queryStr.traverse - - body = if req ^. reqBody - then ["body"] - else [] - - dataBody = - if req ^. reqBody - then " , data: JSON.stringify(body)\n" <> - " , contentType: 'application/json'\n" - else "" - - reqheaders = - if null hs - then "" - else " , headers: { " ++ headersStr ++ " }\n" - - where headersStr = intercalate ", " $ map headerStr hs - headerStr header = "\"" ++ - headerArgName header ++ - "\": " ++ show header - - fname = req ^. funcName - method = req ^. reqMethod - url = if url' == "'" then "'/'" else url' - url' = "'" - ++ urlArgs - ++ queryArgs - - urlArgs = jsSegments - $ req ^.. reqUrl.path.traverse - - queryArgs = if null queryparams - then "" - else " + '?" ++ jsParams queryparams - -printJS :: AjaxReq -> IO () -printJS = putStrLn . generateJS - --- | Utility class used by 'jsForAPI' which will --- directly hand you all the Javascript code --- instead of handing you a ':<|>'-separated list --- of 'AjaxReq' like 'jquery' and then having to --- use 'generateJS' on each 'AjaxReq'. -class GenerateCode reqs where - jsFor :: reqs -> String - -instance GenerateCode AjaxReq where - jsFor = generateJS - -instance GenerateCode rest => GenerateCode (AjaxReq :<|> rest) where - jsFor (req :<|> rest) = jsFor req ++ jsFor rest - --- | Directly generate all the javascript functions for your API --- from a 'Proxy' for your API type. You can then write it to --- a file or integrate it in a page, for example. -jsForAPI :: (HasJQ api, GenerateCode (JQ api)) => Proxy api -> String -jsForAPI p = jsFor (jquery p) diff --git a/servant-jquery/test/Servant/JQuerySpec.hs b/servant-jquery/test/Servant/JQuerySpec.hs deleted file mode 100644 index b8bc5152..00000000 --- a/servant-jquery/test/Servant/JQuerySpec.hs +++ /dev/null @@ -1,96 +0,0 @@ -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE QuasiQuotes #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE TypeOperators #-} -{-# OPTIONS_GHC -fno-warn-orphans #-} -module Servant.JQuerySpec where - -import Data.Either (isRight) -import Data.Proxy -import Language.ECMAScript3.Parser (parseFromString) -import Test.Hspec - -import Servant.API -import Servant.JQuery -import Servant.JQuerySpec.CustomHeaders - -type TestAPI = "simple" :> ReqBody '[JSON,FormUrlEncoded] String :> Post '[JSON] Bool - :<|> "has.extension" :> Get '[FormUrlEncoded,JSON] Bool - -type TopLevelRawAPI = "something" :> Get '[JSON] Int - :<|> Raw - -type HeaderHandlingAPI = "something" :> Header "Foo" String - :> Get '[JSON] Int - -type CustomAuthAPI = "something" :> Authorization "Basic" String - :> Get '[JSON] Int - -type CustomHeaderAPI = "something" :> MyLovelyHorse String - :> Get '[JSON] Int - -type CustomHeaderAPI2 = "something" :> WhatsForDinner String - :> Get '[JSON] Int - -headerHandlingProxy :: Proxy HeaderHandlingAPI -headerHandlingProxy = Proxy - -customAuthProxy :: Proxy CustomAuthAPI -customAuthProxy = Proxy - -customHeaderProxy :: Proxy CustomHeaderAPI -customHeaderProxy = Proxy - -customHeaderProxy2 :: Proxy CustomHeaderAPI2 -customHeaderProxy2 = Proxy - -spec :: Spec -spec = describe "Servant.JQuery" - generateJSSpec - -generateJSSpec :: Spec -generateJSSpec = describe "generateJS" $ do - it "should generate valid javascript" $ do - let (postSimple :<|> getHasExtension ) = jquery (Proxy :: Proxy TestAPI) - parseFromString (generateJS postSimple) `shouldSatisfy` isRight - parseFromString (generateJS getHasExtension) `shouldSatisfy` isRight - print $ generateJS getHasExtension - - it "should use non-empty function names" $ do - let (_ :<|> topLevel) = jquery (Proxy :: Proxy TopLevelRawAPI) - print $ generateJS $ topLevel "GET" - parseFromString (generateJS $ topLevel "GET") `shouldSatisfy` isRight - - it "should handle simple HTTP headers" $ do - let jsText = generateJS $ jquery headerHandlingProxy - print jsText - parseFromString jsText `shouldSatisfy` isRight - jsText `shouldContain` "headerFoo" - jsText `shouldContain` "headers: { \"Foo\": headerFoo }\n" - - it "should handle complex HTTP headers" $ do - let jsText = generateJS $ jquery customAuthProxy - print jsText - parseFromString jsText `shouldSatisfy` isRight - jsText `shouldContain` "headerAuthorization" - jsText `shouldContain` "headers: { \"Authorization\": \"Basic \" + headerAuthorization }\n" - - it "should handle complex, custom HTTP headers" $ do - let jsText = generateJS $ jquery customHeaderProxy - print jsText - parseFromString jsText `shouldSatisfy` isRight - jsText `shouldContain` "headerXMyLovelyHorse" - jsText `shouldContain` "headers: { \"X-MyLovelyHorse\": \"I am good friends with \" + headerXMyLovelyHorse }\n" - - it "should handle complex, custom HTTP headers (template replacement)" $ do - let jsText = generateJS $ jquery customHeaderProxy2 - print jsText - parseFromString jsText `shouldSatisfy` isRight - jsText `shouldContain` "headerXWhatsForDinner" - jsText `shouldContain` "headers: { \"X-WhatsForDinner\": \"I would like \" + headerXWhatsForDinner + \" with a cherry on top.\" }\n" - - it "can generate the whole javascript code string at once with jsForAPI" $ do - let jsStr = jsForAPI (Proxy :: Proxy TestAPI) - parseFromString jsStr `shouldSatisfy` isRight diff --git a/servant-jquery/CHANGELOG.md b/servant-js/CHANGELOG.md similarity index 77% rename from servant-jquery/CHANGELOG.md rename to servant-js/CHANGELOG.md index 3ef30bef..2c115a02 100644 --- a/servant-jquery/CHANGELOG.md +++ b/servant-js/CHANGELOG.md @@ -1,6 +1,8 @@ HEAD ---- +* Provide new targets for code generation along with the old jQuery one: vanilla Javascript and Angular.js +* Greatly simplify usage of this library by reducing down the API to just 2 functions: `jsForAPI` and `writeJSForAPI` + the choice of a code generator * Support for the `HttpVersion`, `IsSecure`, `RemoteHost` and `Vault` combinators 0.4 diff --git a/servant-jquery/LICENSE b/servant-js/LICENSE similarity index 100% rename from servant-jquery/LICENSE rename to servant-js/LICENSE diff --git a/servant-jquery/README.md b/servant-js/README.md similarity index 99% rename from servant-jquery/README.md rename to servant-js/README.md index f6c6c7b8..53d09880 100644 --- a/servant-jquery/README.md +++ b/servant-js/README.md @@ -1,4 +1,4 @@ -# servant-jquery +# servant-js ![servant](https://raw.githubusercontent.com/haskell-servant/servant/master/servant.png) diff --git a/servant-jquery/Setup.hs b/servant-js/Setup.hs similarity index 100% rename from servant-jquery/Setup.hs rename to servant-js/Setup.hs diff --git a/servant-jquery/TODO.md b/servant-js/TODO.md similarity index 100% rename from servant-jquery/TODO.md rename to servant-js/TODO.md diff --git a/servant-jquery/default.nix b/servant-js/default.nix similarity index 81% rename from servant-jquery/default.nix rename to servant-js/default.nix index 59f1da4e..f84d977e 100644 --- a/servant-jquery/default.nix +++ b/servant-js/default.nix @@ -3,7 +3,7 @@ , servant-server, stdenv, stm, text, transformers, warp }: mkDerivation { - pname = "servant-jquery"; + pname = "servant-js"; version = "0.4.0"; src = ./.; isLibrary = true; @@ -16,6 +16,6 @@ mkDerivation { base hspec hspec-expectations language-ecmascript lens servant ]; homepage = "http://haskell-servant.github.io/"; - description = "Automatically derive (jquery) javascript functions to query servant webservices"; + description = "Automatically derive javascript functions to query servant webservices"; license = stdenv.lib.licenses.bsd3; } diff --git a/servant-jquery/docs.sh b/servant-js/docs.sh similarity index 100% rename from servant-jquery/docs.sh rename to servant-js/docs.sh diff --git a/servant-jquery/examples/README.md b/servant-js/examples/README.md similarity index 100% rename from servant-jquery/examples/README.md rename to servant-js/examples/README.md diff --git a/servant-jquery/examples/counter.hs b/servant-js/examples/counter.hs similarity index 53% rename from servant-jquery/examples/counter.hs rename to servant-js/examples/counter.hs index d5b6b156..86f274dd 100644 --- a/servant-jquery/examples/counter.hs +++ b/servant-js/examples/counter.hs @@ -10,7 +10,9 @@ import Data.Proxy import GHC.Generics import Network.Wai.Handler.Warp (run) import Servant -import Servant.JQuery +import Servant.JS +import qualified Servant.JS as SJS +import qualified Servant.JS.Angular as NG import System.FilePath -- * A simple Counter data type @@ -37,43 +39,60 @@ currentValue :: MonadIO m => TVar Counter -> m Counter currentValue counter = liftIO $ readTVarIO counter -- * Our API type -type TestApi = "counter" :> Post Counter -- endpoint for increasing the counter - :<|> "counter" :> Get Counter -- endpoint to get the current value - :<|> Raw -- used for serving static files +type TestApi = "counter" :> Post '[JSON] Counter -- endpoint for increasing the counter + :<|> "counter" :> Get '[JSON] Counter -- endpoint to get the current value +type TestApi' = TestApi + :<|> Raw -- used for serving static files + +-- this proxy only targets the proper endpoints of our API, +-- not the static file serving bit testApi :: Proxy TestApi testApi = Proxy +-- this proxy targets everything +testApi' :: Proxy TestApi' +testApi' = Proxy + -- * Server-side handler -- where our static files reside www :: FilePath www = "examples/www" --- defining handlers +-- defining handlers of our endpoints server :: TVar Counter -> Server TestApi server counter = counterPlusOne counter -- (+1) on the TVar :<|> currentValue counter -- read the TVar - :<|> serveDirectory www -- serve static files + +-- the whole server, including static file serving +server' :: TVar Counter -> Server TestApi' +server' counter = server counter + :<|> serveDirectory www -- serve static files runServer :: TVar Counter -- ^ shared variable for the counter -> Int -- ^ port the server should listen on -> IO () -runServer var port = run port (serve testApi $ server var) - --- * Generating the JQuery code - -incCounterJS :<|> currentValueJS :<|> _ = jquery testApi - -writeJS :: FilePath -> [AjaxReq] -> IO () -writeJS fp functions = writeFile fp $ - concatMap generateJS functions +runServer var port = run port (serve testApi' $ server' var) +writeServiceJS :: FilePath -> IO () +writeServiceJS fp = + writeJSForAPI testApi + (angularServiceWith (NG.defAngularOptions { NG.serviceName = "counterSvc" }) + (defCommonGeneratorOptions { SJS.moduleName = "counterApp" }) + ) + fp + main :: IO () main = do -- write the JS code to www/api.js at startup - writeJS (www "api.js") - [ incCounterJS, currentValueJS ] + writeJSForAPI testApi jquery (www "jquery" "api.js") + + writeJSForAPI testApi vanillaJS (www "vanilla" "api.js") + + writeJSForAPI testApi (angular defAngularOptions) (www "angular" "api.js") + + writeServiceJS (www "angular" "api.service.js") -- setup a shared counter cnt <- newCounter diff --git a/servant-jquery/examples/counter.md b/servant-js/examples/counter.md similarity index 100% rename from servant-jquery/examples/counter.md rename to servant-js/examples/counter.md diff --git a/servant-js/examples/www/angular/angular.min.js b/servant-js/examples/www/angular/angular.min.js new file mode 100644 index 00000000..b72d29a9 --- /dev/null +++ b/servant-js/examples/www/angular/angular.min.js @@ -0,0 +1,290 @@ +/* + AngularJS v1.4.2 + (c) 2010-2015 Google, Inc. http://angularjs.org + License: MIT +*/ +(function(O,U,t){'use strict';function J(b){return function(){var a=arguments[0],c;c="["+(b?b+":":"")+a+"] http://errors.angularjs.org/1.4.2/"+(b?b+"/":"")+a;for(a=1;a").append(b).html();try{return b[0].nodeType===Na?M(c):c.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,function(a,b){return"<"+M(b)})}catch(d){return M(c)}}function yc(b){try{return decodeURIComponent(b)}catch(a){}}function zc(b){var a={},c,d;m((b||"").split("&"),function(b){b&&(c=b.replace(/\+/g, +"%20").split("="),d=yc(c[0]),w(d)&&(b=w(c[1])?yc(c[1]):!0,Xa.call(a,d)?G(a[d])?a[d].push(b):a[d]=[a[d],b]:a[d]=b))});return a}function Qb(b){var a=[];m(b,function(b,d){G(b)?m(b,function(b){a.push(ma(d,!0)+(!0===b?"":"="+ma(b,!0)))}):a.push(ma(d,!0)+(!0===b?"":"="+ma(b,!0)))});return a.length?a.join("&"):""}function ob(b){return ma(b,!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function ma(b,a){return encodeURIComponent(b).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g, +"$").replace(/%2C/gi,",").replace(/%3B/gi,";").replace(/%20/g,a?"%20":"+")}function Yd(b,a){var c,d,e=Oa.length;for(d=0;d/,">"));}a=a||[];a.unshift(["$provide",function(a){a.value("$rootElement",b)}]);c.debugInfoEnabled&&a.push(["$compileProvider",function(a){a.debugInfoEnabled(!0)}]);a.unshift("ng");d=eb(a,c.strictDi);d.invoke(["$rootScope","$rootElement","$compile","$injector",function(a,b,c,d){a.$apply(function(){b.data("$injector",d);c(b)(a)})}]);return d},e= +/^NG_ENABLE_DEBUG_INFO!/,f=/^NG_DEFER_BOOTSTRAP!/;O&&e.test(O.name)&&(c.debugInfoEnabled=!0,O.name=O.name.replace(e,""));if(O&&!f.test(O.name))return d();O.name=O.name.replace(f,"");ca.resumeBootstrap=function(b){m(b,function(b){a.push(b)});return d()};z(ca.resumeDeferredBootstrap)&&ca.resumeDeferredBootstrap()}function $d(){O.name="NG_ENABLE_DEBUG_INFO!"+O.name;O.location.reload()}function ae(b){b=ca.element(b).injector();if(!b)throw Fa("test");return b.get("$$testability")}function Bc(b,a){a=a|| +"_";return b.replace(be,function(b,d){return(d?a:"")+b.toLowerCase()})}function ce(){var b;if(!Cc){var a=pb();la=O.jQuery;w(a)&&(la=null===a?t:O[a]);la&&la.fn.on?(y=la,P(la.fn,{scope:Pa.scope,isolateScope:Pa.isolateScope,controller:Pa.controller,injector:Pa.injector,inheritedData:Pa.inheritedData}),b=la.cleanData,la.cleanData=function(a){var d;if(Rb)Rb=!1;else for(var e=0,f;null!=(f=a[e]);e++)(d=la._data(f,"events"))&&d.$destroy&&la(f).triggerHandler("$destroy");b(a)}):y=Q;ca.element=y;Cc=!0}}function Sb(b, +a,c){if(!b)throw Fa("areq",a||"?",c||"required");return b}function Qa(b,a,c){c&&G(b)&&(b=b[b.length-1]);Sb(z(b),a,"not a function, got "+(b&&"object"===typeof b?b.constructor.name||"Object":typeof b));return b}function Ra(b,a){if("hasOwnProperty"===b)throw Fa("badname",a);}function Dc(b,a,c){if(!a)return b;a=a.split(".");for(var d,e=b,f=a.length,g=0;g")+d[2];for(d=d[0];d--;)c=c.lastChild;f=cb(f,c.childNodes);c=e.firstChild;c.textContent=""}else f.push(a.createTextNode(b));e.textContent="";e.innerHTML="";m(f,function(a){e.appendChild(a)});return e}function Q(b){if(b instanceof Q)return b;var a;L(b)&&(b=R(b),a=!0);if(!(this instanceof Q)){if(a&&"<"!=b.charAt(0))throw Ub("nosel");return new Q(b)}if(a){a= +U;var c;b=(c=Df.exec(b))?[a.createElement(c[1])]:(c=Nc(b,a))?c.childNodes:[]}Oc(this,b)}function Vb(b){return b.cloneNode(!0)}function tb(b,a){a||ub(b);if(b.querySelectorAll)for(var c=b.querySelectorAll("*"),d=0,e=c.length;dk&&this.remove(s.key);return b}},get:function(a){if(k").parent()[0])});var f=S(a,b,a,c,d,e);Z.$$addScopeClass(a);var g=null;return function(b,c,d){Sb(b,"scope");d=d||{};var e=d.parentBoundTranscludeFn,h=d.transcludeControllers;d=d.futureParentElement;e&&e.$$boundTransclude&&(e=e.$$boundTransclude);g||(g=(d=d&&d[0])?"foreignobject"!==ta(d)&&d.toString().match(/SVG/)?"svg":"html":"html");d="html"!==g?y(Yb(g,y("
").append(a).html())):c?Pa.clone.call(a):a;if(h)for(var k in h)d.data("$"+k+"Controller", +h[k].instance);Z.$$addScopeInfo(d,b);c&&c(d,b);f&&f(b,d,d,e);return d}}function S(a,b,c,d,e,f){function g(a,c,d,e){var f,k,l,s,n,B,C;if(p)for(C=Array(c.length),s=0;sE.priority)break;if(v=E.scope)E.templateUrl||(H(v)?(O("new/isolated scope",u||$,E,ba),u=E):O("new/isolated scope",u,E,ba)),$=$||E;w=E.name;!E.templateUrl&&E.controller&&(v=E.controller,N=N||ga(),O("'"+w+"' controller",N[w],E,ba),N[w]=E);if(v=E.transclude)K=!0,E.$$tlb||(O("transclusion",m,E,ba),m=E),"element"==v?(q=!0,F=E.priority,v=ba,ba=d.$$element=y(U.createComment(" "+w+": "+d[w]+" ")),b=ba[0],T(f,za.call(v,0),b),A=Z(v,e,F,g&&g.name,{nonTlbTranscludeDirective:m})):(v= +y(Vb(b)).contents(),ba.empty(),A=Z(v,e));if(E.template)if(I=!0,O("template",D,E,ba),D=E,v=z(E.template)?E.template(ba,d):E.template,v=fa(v),E.replace){g=E;v=Tb.test(v)?$c(Yb(E.templateNamespace,R(v))):[];b=v[0];if(1!=v.length||b.nodeType!==qa)throw ea("tplrt",w,"");T(f,ba,b);Ta={$attr:{}};v=ha(b,[],Ta);var Q=a.splice(xa+1,a.length-(xa+1));u&&ad(v);a=a.concat(v).concat(Q);J(d,Ta);Ta=a.length}else ba.html(v);if(E.templateUrl)I=!0,O("template",D,E,ba),D=E,E.replace&&(g=E),S=Mf(a.splice(xa,a.length-xa), +ba,d,f,K&&A,h,k,{controllerDirectives:N,newScopeDirective:$!==E&&$,newIsolateScopeDirective:u,templateDirective:D,nonTlbTranscludeDirective:m}),Ta=a.length;else if(E.compile)try{Aa=E.compile(ba,d,A),z(Aa)?n(null,Aa,M,P):Aa&&n(Aa.pre,Aa.post,M,P)}catch(Lf){c(Lf,ua(ba))}E.terminal&&(S.terminal=!0,F=Math.max(F,E.priority))}S.scope=$&&!0===$.scope;S.transcludeOnThisElement=K;S.templateOnThisElement=I;S.transclude=A;s.hasElementTranscludeDirective=q;return S}function ad(a){for(var b=0,c=a.length;bn.priority)&&-1!=n.restrict.indexOf(f)&&(k&&(n=Ob(n,{$$start:k,$$end:l})),b.push(n),h=n)}catch(x){c(x)}}return h}function A(b){if(e.hasOwnProperty(b))for(var c=a.get(b+"Directive"),d=0,f=c.length;d"+b+"";return c.childNodes[0].childNodes;default:return b}}function Q(a,b){if("srcdoc"==b)return I.HTML; +var c=ta(a);if("xlinkHref"==b||"form"==c&&"action"==b||"img"!=c&&("src"==b||"ngSrc"==b))return I.RESOURCE_URL}function V(a,c,d,e,f){var g=Q(a,e);f=h[e]||f;var l=b(d,!0,g,f);if(l){if("multiple"===e&&"select"===ta(a))throw ea("selmulti",ua(a));c.push({priority:100,compile:function(){return{pre:function(a,c,h){c=h.$$observers||(h.$$observers={});if(k.test(e))throw ea("nodomevents");var s=h[e];s!==d&&(l=s&&b(s,!0,g,f),d=s);l&&(h[e]=l(a),(c[e]||(c[e]=[])).$$inter=!0,(h.$$observers&&h.$$observers[e].$$scope|| +a).$watch(l,function(a,b){"class"===e&&a!=b?h.$updateClass(a,b):h.$set(e,a)}))}}}})}}function T(a,b,c){var d=b[0],e=b.length,f=d.parentNode,g,h;if(a)for(g=0,h=a.length;g=a)return b;for(;a--;)8===b[a].nodeType&&Nf.call(b,a,1);return b}function Xe(){var b={},a=!1;this.register=function(a,d){Ra(a,"controller");H(a)?P(b,a):b[a]=d};this.allowGlobals=function(){a=!0};this.$get=["$injector","$window",function(c,d){function e(a,b,c,d){if(!a||!H(a.$scope))throw J("$controller")("noscp",d,b);a.$scope[b]=c}return function(f,g,h,l){var k,n,r;h=!0===h;l&&L(l)&&(r=l);if(L(f)){l=f.match(Xc);if(!l)throw Of("ctrlfmt", +f);n=l[1];r=r||l[3];f=b.hasOwnProperty(n)?b[n]:Dc(g.$scope,n,!0)||(a?Dc(d,n,!0):t);Qa(f,n,!0)}if(h)return h=(G(f)?f[f.length-1]:f).prototype,k=Object.create(h||null),r&&e(g,r,k,n||f.name),P(function(){var a=c.invoke(f,k,g,n);a!==k&&(H(a)||z(a))&&(k=a,r&&e(g,r,k,n||f.name));return k},{instance:k,identifier:r});k=c.instantiate(f,g,n);r&&e(g,r,k,n||f.name);return k}}]}function Ye(){this.$get=["$window",function(b){return y(b.document)}]}function Ze(){this.$get=["$log",function(b){return function(a,c){b.error.apply(b, +arguments)}}]}function Zb(b){return H(b)?aa(b)?b.toISOString():db(b):b}function cf(){this.$get=function(){return function(b){if(!b)return"";var a=[];oc(b,function(b,d){null===b||A(b)||(G(b)?m(b,function(b,c){a.push(ma(d)+"="+ma(Zb(b)))}):a.push(ma(d)+"="+ma(Zb(b))))});return a.join("&")}}}function df(){this.$get=function(){return function(b){function a(b,e,f){null===b||A(b)||(G(b)?m(b,function(b){a(b,e+"[]")}):H(b)&&!aa(b)?oc(b,function(b,c){a(b,e+(f?"":"[")+c+(f?"":"]"))}):c.push(ma(e)+"="+ma(Zb(b))))} +if(!b)return"";var c=[];a(b,"",!0);return c.join("&")}}}function $b(b,a){if(L(b)){var c=b.replace(Pf,"").trim();if(c){var d=a("Content-Type");(d=d&&0===d.indexOf(cd))||(d=(d=c.match(Qf))&&Rf[d[0]].test(c));d&&(b=wc(c))}}return b}function dd(b){var a=ga(),c;L(b)?m(b.split("\n"),function(b){c=b.indexOf(":");var e=M(R(b.substr(0,c)));b=R(b.substr(c+1));e&&(a[e]=a[e]?a[e]+", "+b:b)}):H(b)&&m(b,function(b,c){var f=M(c),g=R(b);f&&(a[f]=a[f]?a[f]+", "+g:g)});return a}function ed(b){var a;return function(c){a|| +(a=dd(b));return c?(c=a[M(c)],void 0===c&&(c=null),c):a}}function fd(b,a,c,d){if(z(d))return d(b,a,c);m(d,function(d){b=d(b,a,c)});return b}function bf(){var b=this.defaults={transformResponse:[$b],transformRequest:[function(a){return H(a)&&"[object File]"!==sa.call(a)&&"[object Blob]"!==sa.call(a)&&"[object FormData]"!==sa.call(a)?db(a):a}],headers:{common:{Accept:"application/json, text/plain, */*"},post:ia(ac),put:ia(ac),patch:ia(ac)},xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",paramSerializer:"$httpParamSerializer"}, +a=!1;this.useApplyAsync=function(b){return w(b)?(a=!!b,this):a};var c=this.interceptors=[];this.$get=["$httpBackend","$$cookieReader","$cacheFactory","$rootScope","$q","$injector",function(d,e,f,g,h,l){function k(a){function c(a){var b=P({},a);b.data=a.data?fd(a.data,a.headers,a.status,e.transformResponse):a.data;a=a.status;return 200<=a&&300>a?b:h.reject(b)}function d(a,b){var c,e={};m(a,function(a,d){z(a)?(c=a(b),null!=c&&(e[d]=c)):e[d]=a});return e}if(!ca.isObject(a))throw J("$http")("badreq", +a);var e=P({method:"get",transformRequest:b.transformRequest,transformResponse:b.transformResponse,paramSerializer:b.paramSerializer},a);e.headers=function(a){var c=b.headers,e=P({},a.headers),f,g,h,c=P({},c.common,c[M(a.method)]);a:for(f in c){g=M(f);for(h in e)if(M(h)===g)continue a;e[f]=c[f]}return d(e,ia(a))}(a);e.method=rb(e.method);e.paramSerializer=L(e.paramSerializer)?l.get(e.paramSerializer):e.paramSerializer;var f=[function(a){var d=a.headers,e=fd(a.data,ed(d),t,a.transformRequest);A(e)&& +m(d,function(a,b){"content-type"===M(b)&&delete d[b]});A(a.withCredentials)&&!A(b.withCredentials)&&(a.withCredentials=b.withCredentials);return n(a,e).then(c,c)},t],g=h.when(e);for(m(x,function(a){(a.request||a.requestError)&&f.unshift(a.request,a.requestError);(a.response||a.responseError)&&f.push(a.response,a.responseError)});f.length;){a=f.shift();var k=f.shift(),g=g.then(a,k)}g.success=function(a){Qa(a,"fn");g.then(function(b){a(b.data,b.status,b.headers,e)});return g};g.error=function(a){Qa(a, +"fn");g.then(null,function(b){a(b.data,b.status,b.headers,e)});return g};return g}function n(c,f){function l(b,c,d,e){function f(){n(c,b,d,e)}N&&(200<=b&&300>b?N.put(S,[b,c,dd(d),e]):N.remove(S));a?g.$applyAsync(f):(f(),g.$$phase||g.$apply())}function n(a,b,d,e){b=Math.max(b,0);(200<=b&&300>b?I.resolve:I.reject)({data:a,status:b,headers:ed(d),config:c,statusText:e})}function x(a){n(a.data,a.status,ia(a.headers()),a.statusText)}function m(){var a=k.pendingRequests.indexOf(c);-1!==a&&k.pendingRequests.splice(a, +1)}var I=h.defer(),B=I.promise,N,D,q=c.headers,S=r(c.url,c.paramSerializer(c.params));k.pendingRequests.push(c);B.then(m,m);!c.cache&&!b.cache||!1===c.cache||"GET"!==c.method&&"JSONP"!==c.method||(N=H(c.cache)?c.cache:H(b.cache)?b.cache:s);N&&(D=N.get(S),w(D)?D&&z(D.then)?D.then(x,x):G(D)?n(D[1],D[0],ia(D[2]),D[3]):n(D,200,{},"OK"):N.put(S,B));A(D)&&((D=gd(c.url)?e()[c.xsrfCookieName||b.xsrfCookieName]:t)&&(q[c.xsrfHeaderName||b.xsrfHeaderName]=D),d(c.method,S,f,l,q,c.timeout,c.withCredentials,c.responseType)); +return B}function r(a,b){0=l&&(u.resolve(C),x(p.$$intervalId),delete f[p.$$intervalId]);F||b.$apply()},h);f[p.$$intervalId]=u;return p}var f={};e.cancel=function(b){return b&&b.$$intervalId in f?(f[b.$$intervalId].reject("canceled"),a.clearInterval(b.$$intervalId),delete f[b.$$intervalId], +!0):!1};return e}]}function ge(){this.$get=function(){return{id:"en-us",NUMBER_FORMATS:{DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{minInt:1,minFrac:0,maxFrac:3,posPre:"",posSuf:"",negPre:"-",negSuf:"",gSize:3,lgSize:3},{minInt:1,minFrac:2,maxFrac:2,posPre:"\u00a4",posSuf:"",negPre:"(\u00a4",negSuf:")",gSize:3,lgSize:3}],CURRENCY_SYM:"$"},DATETIME_FORMATS:{MONTH:"January February March April May June July August September October November December".split(" "),SHORTMONTH:"Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "), +DAY:"Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),SHORTDAY:"Sun Mon Tue Wed Thu Fri Sat".split(" "),AMPMS:["AM","PM"],medium:"MMM d, y h:mm:ss a","short":"M/d/yy h:mm a",fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",mediumDate:"MMM d, y",shortDate:"M/d/yy",mediumTime:"h:mm:ss a",shortTime:"h:mm a",ERANAMES:["Before Christ","Anno Domini"],ERAS:["BC","AD"]},pluralCat:function(b){return 1===b?"one":"other"}}}}function bc(b){b=b.split("/");for(var a=b.length;a--;)b[a]=ob(b[a]); +return b.join("/")}function hd(b,a){var c=Ba(b);a.$$protocol=c.protocol;a.$$host=c.hostname;a.$$port=W(c.port)||Uf[c.protocol]||null}function id(b,a){var c="/"!==b.charAt(0);c&&(b="/"+b);var d=Ba(b);a.$$path=decodeURIComponent(c&&"/"===d.pathname.charAt(0)?d.pathname.substring(1):d.pathname);a.$$search=zc(d.search);a.$$hash=decodeURIComponent(d.hash);a.$$path&&"/"!=a.$$path.charAt(0)&&(a.$$path="/"+a.$$path)}function ya(b,a){if(0===a.indexOf(b))return a.substr(b.length)}function Ja(b){var a=b.indexOf("#"); +return-1==a?b:b.substr(0,a)}function Bb(b){return b.replace(/(#.+)|#$/,"$1")}function cc(b){return b.substr(0,Ja(b).lastIndexOf("/")+1)}function dc(b,a){this.$$html5=!0;a=a||"";var c=cc(b);hd(b,this);this.$$parse=function(a){var b=ya(c,a);if(!L(b))throw Cb("ipthprfx",a,c);id(b,this);this.$$path||(this.$$path="/");this.$$compose()};this.$$compose=function(){var a=Qb(this.$$search),b=this.$$hash?"#"+ob(this.$$hash):"";this.$$url=bc(this.$$path)+(a?"?"+a:"")+b;this.$$absUrl=c+this.$$url.substr(1)};this.$$parseLinkUrl= +function(d,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;(f=ya(b,d))!==t?(g=f,g=(f=ya(a,f))!==t?c+(ya("/",f)||f):b+g):(f=ya(c,d))!==t?g=c+f:c==d+"/"&&(g=c);g&&this.$$parse(g);return!!g}}function ec(b,a){var c=cc(b);hd(b,this);this.$$parse=function(d){var e=ya(b,d)||ya(c,d),f;A(e)||"#"!==e.charAt(0)?this.$$html5?f=e:(f="",A(e)&&(b=d,this.replace())):(f=ya(a,e),A(f)&&(f=e));id(f,this);d=this.$$path;var e=b,g=/^\/[A-Z]:(\/.*)/;0===f.indexOf(e)&&(f=f.replace(e,""));g.exec(f)||(d=(f=g.exec(d))? +f[1]:d);this.$$path=d;this.$$compose()};this.$$compose=function(){var c=Qb(this.$$search),e=this.$$hash?"#"+ob(this.$$hash):"";this.$$url=bc(this.$$path)+(c?"?"+c:"")+e;this.$$absUrl=b+(this.$$url?a+this.$$url:"")};this.$$parseLinkUrl=function(a,c){return Ja(b)==Ja(a)?(this.$$parse(a),!0):!1}}function jd(b,a){this.$$html5=!0;ec.apply(this,arguments);var c=cc(b);this.$$parseLinkUrl=function(d,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;b==Ja(d)?f=d:(g=ya(c,d))?f=b+a+g:c===d+"/"&&(f= +c);f&&this.$$parse(f);return!!f};this.$$compose=function(){var c=Qb(this.$$search),e=this.$$hash?"#"+ob(this.$$hash):"";this.$$url=bc(this.$$path)+(c?"?"+c:"")+e;this.$$absUrl=b+a+this.$$url}}function Db(b){return function(){return this[b]}}function kd(b,a){return function(c){if(A(c))return this[b];this[b]=a(c);this.$$compose();return this}}function ff(){var b="",a={enabled:!1,requireBase:!0,rewriteLinks:!0};this.hashPrefix=function(a){return w(a)?(b=a,this):b};this.html5Mode=function(b){return ab(b)? +(a.enabled=b,this):H(b)?(ab(b.enabled)&&(a.enabled=b.enabled),ab(b.requireBase)&&(a.requireBase=b.requireBase),ab(b.rewriteLinks)&&(a.rewriteLinks=b.rewriteLinks),this):a};this.$get=["$rootScope","$browser","$sniffer","$rootElement","$window",function(c,d,e,f,g){function h(a,b,c){var e=k.url(),f=k.$$state;try{d.url(a,b,c),k.$$state=d.state()}catch(g){throw k.url(e),k.$$state=f,g;}}function l(a,b){c.$broadcast("$locationChangeSuccess",k.absUrl(),a,k.$$state,b)}var k,n;n=d.baseHref();var r=d.url(), +s;if(a.enabled){if(!n&&a.requireBase)throw Cb("nobase");s=r.substring(0,r.indexOf("/",r.indexOf("//")+2))+(n||"/");n=e.history?dc:jd}else s=Ja(r),n=ec;k=new n(s,"#"+b);k.$$parseLinkUrl(r,r);k.$$state=d.state();var x=/^\s*(javascript|mailto):/i;f.on("click",function(b){if(a.rewriteLinks&&!b.ctrlKey&&!b.metaKey&&!b.shiftKey&&2!=b.which&&2!=b.button){for(var e=y(b.target);"a"!==ta(e[0]);)if(e[0]===f[0]||!(e=e.parent())[0])return;var h=e.prop("href"),l=e.attr("href")||e.attr("xlink:href");H(h)&&"[object SVGAnimatedString]"=== +h.toString()&&(h=Ba(h.animVal).href);x.test(h)||!h||e.attr("target")||b.isDefaultPrevented()||!k.$$parseLinkUrl(h,l)||(b.preventDefault(),k.absUrl()!=d.url()&&(c.$apply(),g.angular["ff-684208-preventDefault"]=!0))}});Bb(k.absUrl())!=Bb(r)&&d.url(k.absUrl(),!0);var C=!0;d.onUrlChange(function(a,b){c.$evalAsync(function(){var d=k.absUrl(),e=k.$$state,f;k.$$parse(a);k.$$state=b;f=c.$broadcast("$locationChangeStart",a,d,b,e).defaultPrevented;k.absUrl()===a&&(f?(k.$$parse(d),k.$$state=e,h(d,!1,e)):(C= +!1,l(d,e)))});c.$$phase||c.$digest()});c.$watch(function(){var a=Bb(d.url()),b=Bb(k.absUrl()),f=d.state(),g=k.$$replace,n=a!==b||k.$$html5&&e.history&&f!==k.$$state;if(C||n)C=!1,c.$evalAsync(function(){var b=k.absUrl(),d=c.$broadcast("$locationChangeStart",b,a,k.$$state,f).defaultPrevented;k.absUrl()===b&&(d?(k.$$parse(a),k.$$state=f):(n&&h(b,g,f===k.$$state?null:k.$$state),l(a,f)))});k.$$replace=!1});return k}]}function gf(){var b=!0,a=this;this.debugEnabled=function(a){return w(a)?(b=a,this):b}; +this.$get=["$window",function(c){function d(a){a instanceof Error&&(a.stack?a=a.message&&-1===a.stack.indexOf(a.message)?"Error: "+a.message+"\n"+a.stack:a.stack:a.sourceURL&&(a=a.message+"\n"+a.sourceURL+":"+a.line));return a}function e(a){var b=c.console||{},e=b[a]||b.log||v;a=!1;try{a=!!e.apply}catch(l){}return a?function(){var a=[];m(arguments,function(b){a.push(d(b))});return e.apply(b,a)}:function(a,b){e(a,null==b?"":b)}}return{log:e("log"),info:e("info"),warn:e("warn"),error:e("error"),debug:function(){var c= +e("debug");return function(){b&&c.apply(a,arguments)}}()}}]}function Ca(b,a){if("__defineGetter__"===b||"__defineSetter__"===b||"__lookupGetter__"===b||"__lookupSetter__"===b||"__proto__"===b)throw da("isecfld",a);return b}function oa(b,a){if(b){if(b.constructor===b)throw da("isecfn",a);if(b.window===b)throw da("isecwindow",a);if(b.children&&(b.nodeName||b.prop&&b.attr&&b.find))throw da("isecdom",a);if(b===Object)throw da("isecobj",a);}return b}function ld(b,a){if(b){if(b.constructor===b)throw da("isecfn", +a);if(b===Vf||b===Wf||b===Xf)throw da("isecff",a);}}function Yf(b,a){return"undefined"!==typeof b?b:a}function md(b,a){return"undefined"===typeof b?a:"undefined"===typeof a?b:b+a}function T(b,a){var c,d;switch(b.type){case q.Program:c=!0;m(b.body,function(b){T(b.expression,a);c=c&&b.expression.constant});b.constant=c;break;case q.Literal:b.constant=!0;b.toWatch=[];break;case q.UnaryExpression:T(b.argument,a);b.constant=b.argument.constant;b.toWatch=b.argument.toWatch;break;case q.BinaryExpression:T(b.left, +a);T(b.right,a);b.constant=b.left.constant&&b.right.constant;b.toWatch=b.left.toWatch.concat(b.right.toWatch);break;case q.LogicalExpression:T(b.left,a);T(b.right,a);b.constant=b.left.constant&&b.right.constant;b.toWatch=b.constant?[]:[b];break;case q.ConditionalExpression:T(b.test,a);T(b.alternate,a);T(b.consequent,a);b.constant=b.test.constant&&b.alternate.constant&&b.consequent.constant;b.toWatch=b.constant?[]:[b];break;case q.Identifier:b.constant=!1;b.toWatch=[b];break;case q.MemberExpression:T(b.object, +a);b.computed&&T(b.property,a);b.constant=b.object.constant&&(!b.computed||b.property.constant);b.toWatch=[b];break;case q.CallExpression:c=b.filter?!a(b.callee.name).$stateful:!1;d=[];m(b.arguments,function(b){T(b,a);c=c&&b.constant;b.constant||d.push.apply(d,b.toWatch)});b.constant=c;b.toWatch=b.filter&&!a(b.callee.name).$stateful?d:[b];break;case q.AssignmentExpression:T(b.left,a);T(b.right,a);b.constant=b.left.constant&&b.right.constant;b.toWatch=[b];break;case q.ArrayExpression:c=!0;d=[];m(b.elements, +function(b){T(b,a);c=c&&b.constant;b.constant||d.push.apply(d,b.toWatch)});b.constant=c;b.toWatch=d;break;case q.ObjectExpression:c=!0;d=[];m(b.properties,function(b){T(b.value,a);c=c&&b.value.constant;b.value.constant||d.push.apply(d,b.value.toWatch)});b.constant=c;b.toWatch=d;break;case q.ThisExpression:b.constant=!1,b.toWatch=[]}}function nd(b){if(1==b.length){b=b[0].expression;var a=b.toWatch;return 1!==a.length?a:a[0]!==b?a:t}}function od(b){return b.type===q.Identifier||b.type===q.MemberExpression} +function pd(b){if(1===b.body.length&&od(b.body[0].expression))return{type:q.AssignmentExpression,left:b.body[0].expression,right:{type:q.NGValueParameter},operator:"="}}function qd(b){return 0===b.body.length||1===b.body.length&&(b.body[0].expression.type===q.Literal||b.body[0].expression.type===q.ArrayExpression||b.body[0].expression.type===q.ObjectExpression)}function rd(b,a){this.astBuilder=b;this.$filter=a}function sd(b,a){this.astBuilder=b;this.$filter=a}function Eb(b,a,c,d){oa(b,d);a=a.split("."); +for(var e,f=0;1=this.promise.$$state.status&&d&&d.length&&b(function(){for(var b,e,f=0,g=d.length;fa)for(b in l++,f)e.hasOwnProperty(b)|| +(m--,delete f[b])}else f!==e&&(f=e,l++);return l}}c.$stateful=!0;var d=this,e,f,g,k=1m&&(E=4-m,u[E]||(u[E]=[]),u[E].push({msg:z(b.exp)?"fn: "+(b.exp.name||b.exp.toString()):b.exp,newVal:f,oldVal:h}));else if(b===d){s=!1;break a}}catch(A){g(A)}if(!(k=x.$$watchersCount&&x.$$childHead||x!==this&&x.$$nextSibling))for(;x!== +this&&!(k=x.$$nextSibling);)x=x.$parent}while(x=k);if((s||t.length)&&!m--)throw p.$$phase=null,c("infdig",a,u);}while(s||t.length);for(p.$$phase=null;w.length;)try{w.shift()()}catch(y){g(y)}},$destroy:function(){if(!this.$$destroyed){var a=this.$parent;this.$broadcast("$destroy");this.$$destroyed=!0;this===p&&l.$$applicationDestroyed();s(this,-this.$$watchersCount);for(var b in this.$$listenerCount)x(this,this.$$listenerCount[b],b);a&&a.$$childHead==this&&(a.$$childHead=this.$$nextSibling);a&&a.$$childTail== +this&&(a.$$childTail=this.$$prevSibling);this.$$prevSibling&&(this.$$prevSibling.$$nextSibling=this.$$nextSibling);this.$$nextSibling&&(this.$$nextSibling.$$prevSibling=this.$$prevSibling);this.$destroy=this.$digest=this.$apply=this.$evalAsync=this.$applyAsync=v;this.$on=this.$watch=this.$watchGroup=function(){return v};this.$$listeners={};this.$parent=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=this.$root=this.$$watchers=null}},$eval:function(a,b){return h(a)(this,b)}, +$evalAsync:function(a,b){p.$$phase||t.length||l.defer(function(){t.length&&p.$digest()});t.push({scope:this,expression:a,locals:b})},$$postDigest:function(a){w.push(a)},$apply:function(a){try{return r("$apply"),this.$eval(a)}catch(b){g(b)}finally{p.$$phase=null;try{p.$digest()}catch(c){throw g(c),c;}}},$applyAsync:function(a){function b(){c.$eval(a)}var c=this;a&&I.push(b);u()},$on:function(a,b){var c=this.$$listeners[a];c||(this.$$listeners[a]=c=[]);c.push(b);var d=this;do d.$$listenerCount[a]|| +(d.$$listenerCount[a]=0),d.$$listenerCount[a]++;while(d=d.$parent);var e=this;return function(){var d=c.indexOf(b);-1!==d&&(c[d]=null,x(e,1,a))}},$emit:function(a,b){var c=[],d,e=this,f=!1,h={name:a,targetScope:e,stopPropagation:function(){f=!0},preventDefault:function(){h.defaultPrevented=!0},defaultPrevented:!1},k=cb([h],arguments,1),l,n;do{d=e.$$listeners[a]||c;h.currentScope=e;l=0;for(n=d.length;lUa)throw Da("iequirks");var d=ia(pa);d.isEnabled=function(){return b};d.trustAs=c.trustAs;d.getTrusted=c.getTrusted;d.valueOf=c.valueOf;b||(d.trustAs= +d.getTrusted=function(a,b){return b},d.valueOf=Ya);d.parseAs=function(b,c){var e=a(c);return e.literal&&e.constant?e:a(c,function(a){return d.getTrusted(b,a)})};var e=d.parseAs,f=d.getTrusted,g=d.trustAs;m(pa,function(a,b){var c=M(b);d[hb("parse_as_"+c)]=function(b){return e(a,b)};d[hb("get_trusted_"+c)]=function(b){return f(a,b)};d[hb("trust_as_"+c)]=function(b){return g(a,b)}});return d}]}function of(){this.$get=["$window","$document",function(b,a){var c={},d=W((/android (\d+)/.exec(M((b.navigator|| +{}).userAgent))||[])[1]),e=/Boxee/i.test((b.navigator||{}).userAgent),f=a[0]||{},g,h=/^(Moz|webkit|ms)(?=[A-Z])/,l=f.body&&f.body.style,k=!1,n=!1;if(l){for(var r in l)if(k=h.exec(r)){g=k[0];g=g.substr(0,1).toUpperCase()+g.substr(1);break}g||(g="WebkitOpacity"in l&&"webkit");k=!!("transition"in l||g+"Transition"in l);n=!!("animation"in l||g+"Animation"in l);!d||k&&n||(k=L(l.webkitTransition),n=L(l.webkitAnimation))}return{history:!(!b.history||!b.history.pushState||4>d||e),hasEvent:function(a){if("input"=== +a&&11>=Ua)return!1;if(A(c[a])){var b=f.createElement("div");c[a]="on"+a in b}return c[a]},csp:fb(),vendorPrefix:g,transitions:k,animations:n,android:d}}]}function qf(){this.$get=["$templateCache","$http","$q","$sce",function(b,a,c,d){function e(f,g){e.totalPendingRequests++;L(f)&&b.get(f)||(f=d.getTrustedResourceUrl(f));var h=a.defaults&&a.defaults.transformResponse;G(h)?h=h.filter(function(a){return a!==$b}):h===$b&&(h=null);return a.get(f,{cache:b,transformResponse:h})["finally"](function(){e.totalPendingRequests--}).then(function(a){b.put(f, +a.data);return a.data},function(a){if(!g)throw ea("tpload",f,a.status,a.statusText);return c.reject(a)})}e.totalPendingRequests=0;return e}]}function rf(){this.$get=["$rootScope","$browser","$location",function(b,a,c){return{findBindings:function(a,b,c){a=a.getElementsByClassName("ng-binding");var g=[];m(a,function(a){var d=ca.element(a).data("$binding");d&&m(d,function(d){c?(new RegExp("(^|\\s)"+ud(b)+"(\\s|\\||$)")).test(d)&&g.push(a):-1!=d.indexOf(b)&&g.push(a)})});return g},findModels:function(a, +b,c){for(var g=["ng-","data-ng-","ng\\:"],h=0;hb;b=Math.abs(b);var g=Infinity===b;if(!g&&!isFinite(b))return"";var h=b+"",l="",k=!1,n=[];g&&(l="\u221e"); +if(!g&&-1!==h.indexOf("e")){var r=h.match(/([\d\.]+)e(-?)(\d+)/);r&&"-"==r[2]&&r[3]>e+1?b=0:(l=h,k=!0)}if(g||k)0b&&(l=b.toFixed(e),b=parseFloat(l));else{g=(h.split(Dd)[1]||"").length;A(e)&&(e=Math.min(Math.max(a.minFrac,g),a.maxFrac));b=+(Math.round(+(b.toString()+"e"+e)).toString()+"e"+-e);var g=(""+b).split(Dd),h=g[0],g=g[1]||"",r=0,s=a.lgSize,m=a.gSize;if(h.length>=s+m)for(r=h.length-s,k=0;kb&&(d="-",b=-b);for(b=""+b;b.length-c)e+=c;0===e&&-12==c&&(e=12);return Gb(e,a,d)}}function Hb(b,a){return function(c,d){var e=c["get"+b](),f=rb(a?"SHORT"+b:b);return d[f][e]}}function Ed(b){var a= +(new Date(b,0,1)).getDay();return new Date(b,0,(4>=a?5:12)-a)}function Fd(b){return function(a){var c=Ed(a.getFullYear());a=+new Date(a.getFullYear(),a.getMonth(),a.getDate()+(4-a.getDay()))-+c;a=1+Math.round(a/6048E5);return Gb(a,b)}}function jc(b,a){return 0>=b.getFullYear()?a.ERAS[0]:a.ERAS[1]}function zd(b){function a(a){var b;if(b=a.match(c)){a=new Date(0);var f=0,g=0,h=b[8]?a.setUTCFullYear:a.setFullYear,l=b[8]?a.setUTCHours:a.setHours;b[9]&&(f=W(b[9]+b[10]),g=W(b[9]+b[11]));h.call(a,W(b[1]), +W(b[2])-1,W(b[3]));f=W(b[4]||0)-f;g=W(b[5]||0)-g;h=W(b[6]||0);b=Math.round(1E3*parseFloat("0."+(b[7]||0)));l.call(a,f,g,h,b)}return a}var c=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;return function(c,e,f){var g="",h=[],l,k;e=e||"mediumDate";e=b.DATETIME_FORMATS[e]||e;L(c)&&(c=gg.test(c)?W(c):a(c));V(c)&&(c=new Date(c));if(!aa(c)||!isFinite(c.getTime()))return c;for(;e;)(k=hg.exec(e))?(h=cb(h,k,1),e=h.pop()):(h.push(e),e=null);var n=c.getTimezoneOffset(); +f&&(n=xc(f,c.getTimezoneOffset()),c=Pb(c,f,!0));m(h,function(a){l=ig[a];g+=l?l(c,b.DATETIME_FORMATS,n):a.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}}function bg(){return function(b,a){A(a)&&(a=2);return db(b,a)}}function cg(){return function(b,a,c){a=Infinity===Math.abs(Number(a))?Number(a):W(a);if(isNaN(a))return b;V(b)&&(b=b.toString());if(!G(b)&&!L(b))return b;c=!c||isNaN(c)?0:W(c);c=0>c&&c>=-b.length?b.length+c:c;return 0<=a?b.slice(c,c+a):0===c?b.slice(a,b.length):b.slice(Math.max(0, +c+a),c)}}function Bd(b){function a(a,c){c=c?-1:1;return a.map(function(a){var d=1,h=Ya;if(z(a))h=a;else if(L(a)){if("+"==a.charAt(0)||"-"==a.charAt(0))d="-"==a.charAt(0)?-1:1,a=a.substring(1);if(""!==a&&(h=b(a),h.constant))var l=h(),h=function(a){return a[l]}}return{get:h,descending:d*c}})}function c(a){switch(typeof a){case "number":case "boolean":case "string":return!0;default:return!1}}return function(b,e,f){if(!Ea(b))return b;G(e)||(e=[e]);0===e.length&&(e=["+"]);var g=a(e,f);b=Array.prototype.map.call(b, +function(a,b){return{value:a,predicateValues:g.map(function(d){var e=d.get(a);d=typeof e;if(null===e)d="string",e="null";else if("string"===d)e=e.toLowerCase();else if("object"===d)a:{if("function"===typeof e.valueOf&&(e=e.valueOf(),c(e)))break a;if(rc(e)&&(e=e.toString(),c(e)))break a;e=b}return{value:e,type:d}})}});b.sort(function(a,b){for(var c=0,d=0,e=g.length;db||37<=b&&40>=b||n(a,this,this.value)});if(e.hasEvent("paste"))a.on("paste cut",n)}a.on("change",l);d.$render=function(){a.val(d.$isEmpty(d.$viewValue)?"":d.$viewValue)}}function Kb(b,a){return function(c,d){var e,f;if(aa(c))return c;if(L(c)){'"'==c.charAt(0)&&'"'==c.charAt(c.length-1)&&(c=c.substring(1,c.length-1)); +if(jg.test(c))return new Date(c);b.lastIndex=0;if(e=b.exec(c))return e.shift(),f=d?{yyyy:d.getFullYear(),MM:d.getMonth()+1,dd:d.getDate(),HH:d.getHours(),mm:d.getMinutes(),ss:d.getSeconds(),sss:d.getMilliseconds()/1E3}:{yyyy:1970,MM:1,dd:1,HH:0,mm:0,ss:0,sss:0},m(e,function(b,c){c=F};g.$observe("min",function(a){F=s(a);h.$validate()})}if(w(g.max)||g.ngMax){var u; +h.$validators.max=function(a){return!r(a)||A(u)||c(a)<=u};g.$observe("max",function(a){u=s(a);h.$validate()})}}}function Id(b,a,c,d){(d.$$hasNativeValidators=H(a[0].validity))&&d.$parsers.push(function(b){var c=a.prop("validity")||{};return c.badInput&&!c.typeMismatch?t:b})}function Jd(b,a,c,d,e){if(w(d)){b=b(d);if(!b.constant)throw J("ngModel")("constexpr",c,d);return b(a)}return e}function lc(b,a){b="ngClass"+b;return["$animate",function(c){function d(a,b){var c=[],d=0;a:for(;d(?:<\/\1>|)$/,Tb=/<|&#?\w+;/,Bf=/<([\w:]+)/,Cf=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,na={option:[1,'"],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};na.optgroup=na.option;na.tbody=na.tfoot=na.colgroup=na.caption=na.thead; +na.th=na.td;var Pa=Q.prototype={ready:function(b){function a(){c||(c=!0,b())}var c=!1;"complete"===U.readyState?setTimeout(a):(this.on("DOMContentLoaded",a),Q(O).on("load",a))},toString:function(){var b=[];m(this,function(a){b.push(""+a)});return"["+b.join(", ")+"]"},eq:function(b){return 0<=b?y(this[b]):y(this[this.length+b])},length:0,push:lg,sort:[].sort,splice:[].splice},Ab={};m("multiple selected checked disabled readOnly required open".split(" "),function(b){Ab[M(b)]=b});var Tc={};m("input select option textarea button form details".split(" "), +function(b){Tc[b]=!0});var Uc={ngMinlength:"minlength",ngMaxlength:"maxlength",ngMin:"min",ngMax:"max",ngPattern:"pattern"};m({data:Wb,removeData:ub,hasData:function(b){for(var a in ib[b.ng339])return!0;return!1}},function(b,a){Q[a]=b});m({data:Wb,inheritedData:zb,scope:function(b){return y.data(b,"$scope")||zb(b.parentNode||b,["$isolateScope","$scope"])},isolateScope:function(b){return y.data(b,"$isolateScope")||y.data(b,"$isolateScopeNoTemplate")},controller:Qc,injector:function(b){return zb(b, +"$injector")},removeAttr:function(b,a){b.removeAttribute(a)},hasClass:wb,css:function(b,a,c){a=hb(a);if(w(c))b.style[a]=c;else return b.style[a]},attr:function(b,a,c){var d=b.nodeType;if(d!==Na&&2!==d&&8!==d)if(d=M(a),Ab[d])if(w(c))c?(b[a]=!0,b.setAttribute(a,d)):(b[a]=!1,b.removeAttribute(d));else return b[a]||(b.attributes.getNamedItem(a)||v).specified?d:t;else if(w(c))b.setAttribute(a,c);else if(b.getAttribute)return b=b.getAttribute(a,2),null===b?t:b},prop:function(b,a,c){if(w(c))b[a]=c;else return b[a]}, +text:function(){function b(a,b){if(A(b)){var d=a.nodeType;return d===qa||d===Na?a.textContent:""}a.textContent=b}b.$dv="";return b}(),val:function(b,a){if(A(a)){if(b.multiple&&"select"===ta(b)){var c=[];m(b.options,function(a){a.selected&&c.push(a.value||a.text)});return 0===c.length?null:c}return b.value}b.value=a},html:function(b,a){if(A(a))return b.innerHTML;tb(b,!0);b.innerHTML=a},empty:Rc},function(b,a){Q.prototype[a]=function(a,d){var e,f,g=this.length;if(b!==Rc&&(2==b.length&&b!==wb&&b!==Qc? +a:d)===t){if(H(a)){for(e=0;e <= >= && || ! = |".split(" "),function(a){Mb[a]=!0});var rg={n:"\n",f:"\f",r:"\r", +t:"\t",v:"\v","'":"'",'"':'"'},gc=function(a){this.options=a};gc.prototype={constructor:gc,lex:function(a){this.text=a;this.index=0;for(this.tokens=[];this.index=a&&"string"===typeof a},isWhitespace:function(a){return" "===a||"\r"===a|| +"\t"===a||"\n"===a||"\v"===a||"\u00a0"===a},isIdent:function(a){return"a"<=a&&"z">=a||"A"<=a&&"Z">=a||"_"===a||"$"===a},isExpOperator:function(a){return"-"===a||"+"===a||this.isNumber(a)},throwError:function(a,c,d){d=d||this.index;c=w(c)?"s "+c+"-"+this.index+" ["+this.text.substring(c,d)+"]":" "+d;throw da("lexerr",a,c,this.text);},readNumber:function(){for(var a="",c=this.index;this.index","<=",">=");)a={type:q.BinaryExpression,operator:c.text,left:a,right:this.additive()};return a},additive:function(){for(var a=this.multiplicative(),c;c=this.expect("+","-");)a={type:q.BinaryExpression,operator:c.text,left:a,right:this.multiplicative()};return a},multiplicative:function(){for(var a=this.unary(),c;c=this.expect("*","/","%");)a={type:q.BinaryExpression,operator:c.text, +left:a,right:this.unary()};return a},unary:function(){var a;return(a=this.expect("+","-","!"))?{type:q.UnaryExpression,operator:a.text,prefix:!0,argument:this.unary()}:this.primary()},primary:function(){var a;this.expect("(")?(a=this.filterChain(),this.consume(")")):this.expect("[")?a=this.arrayDeclaration():this.expect("{")?a=this.object():this.constants.hasOwnProperty(this.peek().text)?a=fa(this.constants[this.consume().text]):this.peek().identifier?a=this.identifier():this.peek().constant?a=this.constant(): +this.throwError("not a primary expression",this.peek());for(var c;c=this.expect("(","[",".");)"("===c.text?(a={type:q.CallExpression,callee:a,arguments:this.parseArguments()},this.consume(")")):"["===c.text?(a={type:q.MemberExpression,object:a,property:this.expression(),computed:!0},this.consume("]")):"."===c.text?a={type:q.MemberExpression,object:a,property:this.identifier(),computed:!1}:this.throwError("IMPOSSIBLE");return a},filter:function(a){a=[a];for(var c={type:q.CallExpression,callee:this.identifier(), +arguments:a,filter:!0};this.expect(":");)a.push(this.expression());return c},parseArguments:function(){var a=[];if(")"!==this.peekToken().text){do a.push(this.expression());while(this.expect(","))}return a},identifier:function(){var a=this.consume();a.identifier||this.throwError("is not a valid identifier",a);return{type:q.Identifier,name:a.text}},constant:function(){return{type:q.Literal,value:this.consume().value}},arrayDeclaration:function(){var a=[];if("]"!==this.peekToken().text){do{if(this.peek("]"))break; +a.push(this.expression())}while(this.expect(","))}this.consume("]");return{type:q.ArrayExpression,elements:a}},object:function(){var a=[],c;if("}"!==this.peekToken().text){do{if(this.peek("}"))break;c={type:q.Property,kind:"init"};this.peek().constant?c.key=this.constant():this.peek().identifier?c.key=this.identifier():this.throwError("invalid key",this.peek());this.consume(":");c.value=this.expression();a.push(c)}while(this.expect(","))}this.consume("}");return{type:q.ObjectExpression,properties:a}}, +throwError:function(a,c){throw da("syntax",c.text,a,c.index+1,this.text,this.text.substring(c.index));},consume:function(a){if(0===this.tokens.length)throw da("ueoe",this.text);var c=this.expect(a);c||this.throwError("is unexpected, expecting ["+a+"]",this.peek());return c},peekToken:function(){if(0===this.tokens.length)throw da("ueoe",this.text);return this.tokens[0]},peek:function(a,c,d,e){return this.peekAhead(0,a,c,d,e)},peekAhead:function(a,c,d,e,f){if(this.tokens.length>a){a=this.tokens[a]; +var g=a.text;if(g===c||g===d||g===e||g===f||!(c||d||e||f))return a}return!1},expect:function(a,c,d,e){return(a=this.peek(a,c,d,e))?(this.tokens.shift(),a):!1},constants:{"true":{type:q.Literal,value:!0},"false":{type:q.Literal,value:!1},"null":{type:q.Literal,value:null},undefined:{type:q.Literal,value:t},"this":{type:q.ThisExpression}}};rd.prototype={compile:function(a,c){var d=this,e=this.astBuilder.ast(a);this.state={nextId:0,filters:{},expensiveChecks:c,fn:{vars:[],body:[],own:{}},assign:{vars:[], +body:[],own:{}},inputs:[]};T(e,d.$filter);var f="",g;this.stage="assign";if(g=pd(e))this.state.computing="assign",f=this.nextId(),this.recurse(g,f),f="fn.assign="+this.generateFunction("assign","s,v,l");g=nd(e.body);d.stage="inputs";m(g,function(a,c){var e="fn"+c;d.state[e]={vars:[],body:[],own:{}};d.state.computing=e;var f=d.nextId();d.recurse(a,f);d.return_(f);d.state.inputs.push(e);a.watchId=c});this.state.computing="fn";this.stage="main";this.recurse(e);f='"'+this.USE+" "+this.STRICT+'";\n'+this.filterPrefix()+ +"var fn="+this.generateFunction("fn","s,l,a,i")+f+this.watchFns()+"return fn;";f=(new Function("$filter","ensureSafeMemberName","ensureSafeObject","ensureSafeFunction","ifDefined","plus","text",f))(this.$filter,Ca,oa,ld,Yf,md,a);this.state=this.stage=t;f.literal=qd(e);f.constant=e.constant;return f},USE:"use",STRICT:"strict",watchFns:function(){var a=[],c=this.state.inputs,d=this;m(c,function(c){a.push("var "+c+"="+d.generateFunction(c,"s"))});c.length&&a.push("fn.inputs=["+c.join(",")+"];");return a.join("")}, +generateFunction:function(a,c){return"function("+c+"){"+this.varsPrefix(a)+this.body(a)+"};"},filterPrefix:function(){var a=[],c=this;m(this.state.filters,function(d,e){a.push(d+"=$filter("+c.escape(e)+")")});return a.length?"var "+a.join(",")+";":""},varsPrefix:function(a){return this.state[a].vars.length?"var "+this.state[a].vars.join(",")+";":""},body:function(a){return this.state[a].body.join("")},recurse:function(a,c,d,e,f,g){var h,l,k=this,n,r;e=e||v;if(!g&&w(a.watchId))c=c||this.nextId(),this.if_("i", +this.lazyAssign(c,this.computedMember("i",a.watchId)),this.lazyRecurse(a,c,d,e,f,!0));else switch(a.type){case q.Program:m(a.body,function(c,d){k.recurse(c.expression,t,t,function(a){l=a});d!==a.body.length-1?k.current().body.push(l,";"):k.return_(l)});break;case q.Literal:r=this.escape(a.value);this.assign(c,r);e(r);break;case q.UnaryExpression:this.recurse(a.argument,t,t,function(a){l=a});r=a.operator+"("+this.ifDefined(l,0)+")";this.assign(c,r);e(r);break;case q.BinaryExpression:this.recurse(a.left, +t,t,function(a){h=a});this.recurse(a.right,t,t,function(a){l=a});r="+"===a.operator?this.plus(h,l):"-"===a.operator?this.ifDefined(h,0)+a.operator+this.ifDefined(l,0):"("+h+")"+a.operator+"("+l+")";this.assign(c,r);e(r);break;case q.LogicalExpression:c=c||this.nextId();k.recurse(a.left,c);k.if_("&&"===a.operator?c:k.not(c),k.lazyRecurse(a.right,c));e(c);break;case q.ConditionalExpression:c=c||this.nextId();k.recurse(a.test,c);k.if_(c,k.lazyRecurse(a.alternate,c),k.lazyRecurse(a.consequent,c));e(c); +break;case q.Identifier:c=c||this.nextId();d&&(d.context="inputs"===k.stage?"s":this.assign(this.nextId(),this.getHasOwnProperty("l",a.name)+"?l:s"),d.computed=!1,d.name=a.name);Ca(a.name);k.if_("inputs"===k.stage||k.not(k.getHasOwnProperty("l",a.name)),function(){k.if_("inputs"===k.stage||"s",function(){f&&1!==f&&k.if_(k.not(k.nonComputedMember("s",a.name)),k.lazyAssign(k.nonComputedMember("s",a.name),"{}"));k.assign(c,k.nonComputedMember("s",a.name))})},c&&k.lazyAssign(c,k.nonComputedMember("l", +a.name)));(k.state.expensiveChecks||Fb(a.name))&&k.addEnsureSafeObject(c);e(c);break;case q.MemberExpression:h=d&&(d.context=this.nextId())||this.nextId();c=c||this.nextId();k.recurse(a.object,h,t,function(){k.if_(k.notNull(h),function(){if(a.computed)l=k.nextId(),k.recurse(a.property,l),k.addEnsureSafeMemberName(l),f&&1!==f&&k.if_(k.not(k.computedMember(h,l)),k.lazyAssign(k.computedMember(h,l),"{}")),r=k.ensureSafeObject(k.computedMember(h,l)),k.assign(c,r),d&&(d.computed=!0,d.name=l);else{Ca(a.property.name); +f&&1!==f&&k.if_(k.not(k.nonComputedMember(h,a.property.name)),k.lazyAssign(k.nonComputedMember(h,a.property.name),"{}"));r=k.nonComputedMember(h,a.property.name);if(k.state.expensiveChecks||Fb(a.property.name))r=k.ensureSafeObject(r);k.assign(c,r);d&&(d.computed=!1,d.name=a.property.name)}},function(){k.assign(c,"undefined")});e(c)},!!f);break;case q.CallExpression:c=c||this.nextId();a.filter?(l=k.filter(a.callee.name),n=[],m(a.arguments,function(a){var c=k.nextId();k.recurse(a,c);n.push(c)}),r=l+ +"("+n.join(",")+")",k.assign(c,r),e(c)):(l=k.nextId(),h={},n=[],k.recurse(a.callee,l,h,function(){k.if_(k.notNull(l),function(){k.addEnsureSafeFunction(l);m(a.arguments,function(a){k.recurse(a,k.nextId(),t,function(a){n.push(k.ensureSafeObject(a))})});h.name?(k.state.expensiveChecks||k.addEnsureSafeObject(h.context),r=k.member(h.context,h.name,h.computed)+"("+n.join(",")+")"):r=l+"("+n.join(",")+")";r=k.ensureSafeObject(r);k.assign(c,r)},function(){k.assign(c,"undefined")});e(c)}));break;case q.AssignmentExpression:l= +this.nextId();h={};if(!od(a.left))throw da("lval");this.recurse(a.left,t,h,function(){k.if_(k.notNull(h.context),function(){k.recurse(a.right,l);k.addEnsureSafeObject(k.member(h.context,h.name,h.computed));r=k.member(h.context,h.name,h.computed)+a.operator+l;k.assign(c,r);e(c||r)})},1);break;case q.ArrayExpression:n=[];m(a.elements,function(a){k.recurse(a,k.nextId(),t,function(a){n.push(a)})});r="["+n.join(",")+"]";this.assign(c,r);e(r);break;case q.ObjectExpression:n=[];m(a.properties,function(a){k.recurse(a.value, +k.nextId(),t,function(c){n.push(k.escape(a.key.type===q.Identifier?a.key.name:""+a.key.value)+":"+c)})});r="{"+n.join(",")+"}";this.assign(c,r);e(r);break;case q.ThisExpression:this.assign(c,"s");e("s");break;case q.NGValueParameter:this.assign(c,"v"),e("v")}},getHasOwnProperty:function(a,c){var d=a+"."+c,e=this.current().own;e.hasOwnProperty(d)||(e[d]=this.nextId(!1,a+"&&("+this.escape(c)+" in "+a+")"));return e[d]},assign:function(a,c){if(a)return this.current().body.push(a,"=",c,";"),a},filter:function(a){this.state.filters.hasOwnProperty(a)|| +(this.state.filters[a]=this.nextId(!0));return this.state.filters[a]},ifDefined:function(a,c){return"ifDefined("+a+","+this.escape(c)+")"},plus:function(a,c){return"plus("+a+","+c+")"},return_:function(a){this.current().body.push("return ",a,";")},if_:function(a,c,d){if(!0===a)c();else{var e=this.current().body;e.push("if(",a,"){");c();e.push("}");d&&(e.push("else{"),d(),e.push("}"))}},not:function(a){return"!("+a+")"},notNull:function(a){return a+"!=null"},nonComputedMember:function(a,c){return a+ +"."+c},computedMember:function(a,c){return a+"["+c+"]"},member:function(a,c,d){return d?this.computedMember(a,c):this.nonComputedMember(a,c)},addEnsureSafeObject:function(a){this.current().body.push(this.ensureSafeObject(a),";")},addEnsureSafeMemberName:function(a){this.current().body.push(this.ensureSafeMemberName(a),";")},addEnsureSafeFunction:function(a){this.current().body.push(this.ensureSafeFunction(a),";")},ensureSafeObject:function(a){return"ensureSafeObject("+a+",text)"},ensureSafeMemberName:function(a){return"ensureSafeMemberName("+ +a+",text)"},ensureSafeFunction:function(a){return"ensureSafeFunction("+a+",text)"},lazyRecurse:function(a,c,d,e,f,g){var h=this;return function(){h.recurse(a,c,d,e,f,g)}},lazyAssign:function(a,c){var d=this;return function(){d.assign(a,c)}},stringEscapeRegex:/[^ a-zA-Z0-9]/g,stringEscapeFn:function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)},escape:function(a){if(L(a))return"'"+a.replace(this.stringEscapeRegex,this.stringEscapeFn)+"'";if(V(a))return a.toString();if(!0===a)return"true"; +if(!1===a)return"false";if(null===a)return"null";if("undefined"===typeof a)return"undefined";throw da("esc");},nextId:function(a,c){var d="v"+this.state.nextId++;a||this.current().vars.push(d+(c?"="+c:""));return d},current:function(){return this.state[this.state.computing]}};sd.prototype={compile:function(a,c){var d=this,e=this.astBuilder.ast(a);this.expression=a;this.expensiveChecks=c;T(e,d.$filter);var f,g;if(f=pd(e))g=this.recurse(f);f=nd(e.body);var h;f&&(h=[],m(f,function(a,c){var e=d.recurse(a); +a.input=e;h.push(e);a.watchId=c}));var l=[];m(e.body,function(a){l.push(d.recurse(a.expression))});f=0===e.body.length?function(){}:1===e.body.length?l[0]:function(a,c){var d;m(l,function(e){d=e(a,c)});return d};g&&(f.assign=function(a,c,d){return g(a,d,c)});h&&(f.inputs=h);f.literal=qd(e);f.constant=e.constant;return f},recurse:function(a,c,d){var e,f,g=this,h;if(a.input)return this.inputs(a.input,a.watchId);switch(a.type){case q.Literal:return this.value(a.value,c);case q.UnaryExpression:return f= +this.recurse(a.argument),this["unary"+a.operator](f,c);case q.BinaryExpression:return e=this.recurse(a.left),f=this.recurse(a.right),this["binary"+a.operator](e,f,c);case q.LogicalExpression:return e=this.recurse(a.left),f=this.recurse(a.right),this["binary"+a.operator](e,f,c);case q.ConditionalExpression:return this["ternary?:"](this.recurse(a.test),this.recurse(a.alternate),this.recurse(a.consequent),c);case q.Identifier:return Ca(a.name,g.expression),g.identifier(a.name,g.expensiveChecks||Fb(a.name), +c,d,g.expression);case q.MemberExpression:return e=this.recurse(a.object,!1,!!d),a.computed||(Ca(a.property.name,g.expression),f=a.property.name),a.computed&&(f=this.recurse(a.property)),a.computed?this.computedMember(e,f,c,d,g.expression):this.nonComputedMember(e,f,g.expensiveChecks,c,d,g.expression);case q.CallExpression:return h=[],m(a.arguments,function(a){h.push(g.recurse(a))}),a.filter&&(f=this.$filter(a.callee.name)),a.filter||(f=this.recurse(a.callee,!0)),a.filter?function(a,d,e,g){for(var m= +[],q=0;q":function(a,c,d){return function(e,f,g,h){e=a(e,f,g,h)>c(e,f,g,h);return d?{value:e}:e}},"binary<=":function(a,c,d){return function(e, +f,g,h){e=a(e,f,g,h)<=c(e,f,g,h);return d?{value:e}:e}},"binary>=":function(a,c,d){return function(e,f,g,h){e=a(e,f,g,h)>=c(e,f,g,h);return d?{value:e}:e}},"binary&&":function(a,c,d){return function(e,f,g,h){e=a(e,f,g,h)&&c(e,f,g,h);return d?{value:e}:e}},"binary||":function(a,c,d){return function(e,f,g,h){e=a(e,f,g,h)||c(e,f,g,h);return d?{value:e}:e}},"ternary?:":function(a,c,d,e){return function(f,g,h,l){f=a(f,g,h,l)?c(f,g,h,l):d(f,g,h,l);return e?{value:f}:f}},value:function(a,c){return function(){return c? +{context:t,name:t,value:a}:a}},identifier:function(a,c,d,e,f){return function(g,h,l,k){g=h&&a in h?h:g;e&&1!==e&&g&&!g[a]&&(g[a]={});h=g?g[a]:t;c&&oa(h,f);return d?{context:g,name:a,value:h}:h}},computedMember:function(a,c,d,e,f){return function(g,h,l,k){var n=a(g,h,l,k),m,s;null!=n&&(m=c(g,h,l,k),Ca(m,f),e&&1!==e&&n&&!n[m]&&(n[m]={}),s=n[m],oa(s,f));return d?{context:n,name:m,value:s}:s}},nonComputedMember:function(a,c,d,e,f,g){return function(h,l,k,n){h=a(h,l,k,n);f&&1!==f&&h&&!h[c]&&(h[c]={}); +l=null!=h?h[c]:t;(d||Fb(c))&&oa(l,g);return e?{context:h,name:c,value:l}:l}},inputs:function(a,c){return function(d,e,f,g){return g?g[c]:a(d,e,f)}}};var hc=function(a,c,d){this.lexer=a;this.$filter=c;this.options=d;this.ast=new q(this.lexer);this.astCompiler=d.csp?new sd(this.ast,c):new rd(this.ast,c)};hc.prototype={constructor:hc,parse:function(a){return this.astCompiler.compile(a,this.options.expensiveChecks)}};ga();ga();var Zf=Object.prototype.valueOf,Da=J("$sce"),pa={HTML:"html",CSS:"css",URL:"url", +RESOURCE_URL:"resourceUrl",JS:"js"},ea=J("$compile"),X=U.createElement("a"),wd=Ba(O.location.href);xd.$inject=["$document"];Lc.$inject=["$provide"];yd.$inject=["$locale"];Ad.$inject=["$locale"];var Dd=".",ig={yyyy:Y("FullYear",4),yy:Y("FullYear",2,0,!0),y:Y("FullYear",1),MMMM:Hb("Month"),MMM:Hb("Month",!0),MM:Y("Month",2,1),M:Y("Month",1,1),dd:Y("Date",2),d:Y("Date",1),HH:Y("Hours",2),H:Y("Hours",1),hh:Y("Hours",2,-12),h:Y("Hours",1,-12),mm:Y("Minutes",2),m:Y("Minutes",1),ss:Y("Seconds",2),s:Y("Seconds", +1),sss:Y("Milliseconds",3),EEEE:Hb("Day"),EEE:Hb("Day",!0),a:function(a,c){return 12>a.getHours()?c.AMPMS[0]:c.AMPMS[1]},Z:function(a,c,d){a=-1*d;return a=(0<=a?"+":"")+(Gb(Math[0=a.getFullYear()?c.ERANAMES[0]:c.ERANAMES[1]}},hg=/((?:[^yMdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/,gg=/^\-?\d+$/;zd.$inject=["$locale"];var dg=ra(M),eg=ra(rb);Bd.$inject= +["$parse"];var ie=ra({restrict:"E",compile:function(a,c){if(!c.href&&!c.xlinkHref)return function(a,c){if("a"===c[0].nodeName.toLowerCase()){var f="[object SVGAnimatedString]"===sa.call(c.prop("href"))?"xlink:href":"href";c.on("click",function(a){c.attr(f)||a.preventDefault()})}}}}),sb={};m(Ab,function(a,c){function d(a,d,f){a.$watch(f[e],function(a){f.$set(c,!!a)})}if("multiple"!=a){var e=wa("ng-"+c),f=d;"checked"===a&&(f=function(a,c,f){f.ngModel!==f[e]&&d(a,c,f)});sb[e]=function(){return{restrict:"A", +priority:100,link:f}}}});m(Uc,function(a,c){sb[c]=function(){return{priority:100,link:function(a,e,f){if("ngPattern"===c&&"/"==f.ngPattern.charAt(0)&&(e=f.ngPattern.match(kg))){f.$set("ngPattern",new RegExp(e[1],e[2]));return}a.$watch(f[c],function(a){f.$set(c,a)})}}}});m(["src","srcset","href"],function(a){var c=wa("ng-"+a);sb[c]=function(){return{priority:99,link:function(d,e,f){var g=a,h=a;"href"===a&&"[object SVGAnimatedString]"===sa.call(e.prop("href"))&&(h="xlinkHref",f.$attr[h]="xlink:href", +g=null);f.$observe(c,function(c){c?(f.$set(h,c),Ua&&g&&e.prop(g,f[h])):"href"===a&&f.$set(h,null)})}}}});var Ib={$addControl:v,$$renameControl:function(a,c){a.$name=c},$removeControl:v,$setValidity:v,$setDirty:v,$setPristine:v,$setSubmitted:v};Gd.$inject=["$element","$attrs","$scope","$animate","$interpolate"];var Od=function(a){return["$timeout",function(c){return{name:"form",restrict:a?"EAC":"E",controller:Gd,compile:function(d,e){d.addClass(Va).addClass(mb);var f=e.name?"name":a&&e.ngForm?"ngForm": +!1;return{pre:function(a,d,e,k){if(!("action"in e)){var n=function(c){a.$apply(function(){k.$commitViewValue();k.$setSubmitted()});c.preventDefault()};d[0].addEventListener("submit",n,!1);d.on("$destroy",function(){c(function(){d[0].removeEventListener("submit",n,!1)},0,!1)})}var m=k.$$parentForm;f&&(Eb(a,k.$name,k,k.$name),e.$observe(f,function(c){k.$name!==c&&(Eb(a,k.$name,t,k.$name),m.$$renameControl(k,c),Eb(a,k.$name,k,k.$name))}));d.on("$destroy",function(){m.$removeControl(k);f&&Eb(a,e[f],t, +k.$name);P(k,Ib)})}}}}}]},je=Od(),we=Od(!0),jg=/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/,sg=/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/,tg=/^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i,ug=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/,Pd=/^(\d{4})-(\d{2})-(\d{2})$/,Qd=/^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,mc=/^(\d{4})-W(\d\d)$/,Rd=/^(\d{4})-(\d\d)$/, +Sd=/^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,Td={text:function(a,c,d,e,f,g){kb(a,c,d,e,f,g);kc(e)},date:lb("date",Pd,Kb(Pd,["yyyy","MM","dd"]),"yyyy-MM-dd"),"datetime-local":lb("datetimelocal",Qd,Kb(Qd,"yyyy MM dd HH mm ss sss".split(" ")),"yyyy-MM-ddTHH:mm:ss.sss"),time:lb("time",Sd,Kb(Sd,["HH","mm","ss","sss"]),"HH:mm:ss.sss"),week:lb("week",mc,function(a,c){if(aa(a))return a;if(L(a)){mc.lastIndex=0;var d=mc.exec(a);if(d){var e=+d[1],f=+d[2],g=d=0,h=0,l=0,k=Ed(e),f=7*(f-1);c&&(d=c.getHours(),g= +c.getMinutes(),h=c.getSeconds(),l=c.getMilliseconds());return new Date(e,0,k.getDate()+f,d,g,h,l)}}return NaN},"yyyy-Www"),month:lb("month",Rd,Kb(Rd,["yyyy","MM"]),"yyyy-MM"),number:function(a,c,d,e,f,g){Id(a,c,d,e);kb(a,c,d,e,f,g);e.$$parserName="number";e.$parsers.push(function(a){return e.$isEmpty(a)?null:ug.test(a)?parseFloat(a):t});e.$formatters.push(function(a){if(!e.$isEmpty(a)){if(!V(a))throw Lb("numfmt",a);a=a.toString()}return a});if(w(d.min)||d.ngMin){var h;e.$validators.min=function(a){return e.$isEmpty(a)|| +A(h)||a>=h};d.$observe("min",function(a){w(a)&&!V(a)&&(a=parseFloat(a,10));h=V(a)&&!isNaN(a)?a:t;e.$validate()})}if(w(d.max)||d.ngMax){var l;e.$validators.max=function(a){return e.$isEmpty(a)||A(l)||a<=l};d.$observe("max",function(a){w(a)&&!V(a)&&(a=parseFloat(a,10));l=V(a)&&!isNaN(a)?a:t;e.$validate()})}},url:function(a,c,d,e,f,g){kb(a,c,d,e,f,g);kc(e);e.$$parserName="url";e.$validators.url=function(a,c){var d=a||c;return e.$isEmpty(d)||sg.test(d)}},email:function(a,c,d,e,f,g){kb(a,c,d,e,f,g);kc(e); +e.$$parserName="email";e.$validators.email=function(a,c){var d=a||c;return e.$isEmpty(d)||tg.test(d)}},radio:function(a,c,d,e){A(d.name)&&c.attr("name",++nb);c.on("click",function(a){c[0].checked&&e.$setViewValue(d.value,a&&a.type)});e.$render=function(){c[0].checked=d.value==e.$viewValue};d.$observe("value",e.$render)},checkbox:function(a,c,d,e,f,g,h,l){var k=Jd(l,a,"ngTrueValue",d.ngTrueValue,!0),n=Jd(l,a,"ngFalseValue",d.ngFalseValue,!1);c.on("click",function(a){e.$setViewValue(c[0].checked,a&& +a.type)});e.$render=function(){c[0].checked=e.$viewValue};e.$isEmpty=function(a){return!1===a};e.$formatters.push(function(a){return ka(a,k)});e.$parsers.push(function(a){return a?k:n})},hidden:v,button:v,submit:v,reset:v,file:v},Fc=["$browser","$sniffer","$filter","$parse",function(a,c,d,e){return{restrict:"E",require:["?ngModel"],link:{pre:function(f,g,h,l){l[0]&&(Td[M(h.type)]||Td.text)(f,g,h,l[0],c,a,d,e)}}}}],vg=/^(true|false|\d+)$/,Oe=function(){return{restrict:"A",priority:100,compile:function(a, +c){return vg.test(c.ngValue)?function(a,c,f){f.$set("value",a.$eval(f.ngValue))}:function(a,c,f){a.$watch(f.ngValue,function(a){f.$set("value",a)})}}}},oe=["$compile",function(a){return{restrict:"AC",compile:function(c){a.$$addBindingClass(c);return function(c,e,f){a.$$addBindingInfo(e,f.ngBind);e=e[0];c.$watch(f.ngBind,function(a){e.textContent=a===t?"":a})}}}}],qe=["$interpolate","$compile",function(a,c){return{compile:function(d){c.$$addBindingClass(d);return function(d,f,g){d=a(f.attr(g.$attr.ngBindTemplate)); +c.$$addBindingInfo(f,d.expressions);f=f[0];g.$observe("ngBindTemplate",function(a){f.textContent=a===t?"":a})}}}}],pe=["$sce","$parse","$compile",function(a,c,d){return{restrict:"A",compile:function(e,f){var g=c(f.ngBindHtml),h=c(f.ngBindHtml,function(a){return(a||"").toString()});d.$$addBindingClass(e);return function(c,e,f){d.$$addBindingInfo(e,f.ngBindHtml);c.$watch(h,function(){e.html(a.getTrustedHtml(g(c))||"")})}}}}],Ne=ra({restrict:"A",require:"ngModel",link:function(a,c,d,e){e.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}), +re=lc("",!0),te=lc("Odd",0),se=lc("Even",1),ue=Ma({compile:function(a,c){c.$set("ngCloak",t);a.removeClass("ng-cloak")}}),ve=[function(){return{restrict:"A",scope:!0,controller:"@",priority:500}}],Kc={},wg={blur:!0,focus:!0};m("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste".split(" "),function(a){var c=wa("ng-"+a);Kc[c]=["$parse","$rootScope",function(d,e){return{restrict:"A",compile:function(f,g){var h= +d(g[c],null,!0);return function(c,d){d.on(a,function(d){var f=function(){h(c,{$event:d})};wg[a]&&e.$$phase?c.$evalAsync(f):c.$apply(f)})}}}}]});var ye=["$animate",function(a){return{multiElement:!0,transclude:"element",priority:600,terminal:!0,restrict:"A",$$tlb:!0,link:function(c,d,e,f,g){var h,l,k;c.$watch(e.ngIf,function(c){c?l||g(function(c,f){l=f;c[c.length++]=U.createComment(" end ngIf: "+e.ngIf+" ");h={clone:c};a.enter(c,d.parent(),d)}):(k&&(k.remove(),k=null),l&&(l.$destroy(),l=null),h&&(k= +qb(h.clone),a.leave(k).then(function(){k=null}),h=null))})}}}],ze=["$templateRequest","$anchorScroll","$animate",function(a,c,d){return{restrict:"ECA",priority:400,terminal:!0,transclude:"element",controller:ca.noop,compile:function(e,f){var g=f.ngInclude||f.src,h=f.onload||"",l=f.autoscroll;return function(e,f,m,s,q){var t=0,F,u,p,v=function(){u&&(u.remove(),u=null);F&&(F.$destroy(),F=null);p&&(d.leave(p).then(function(){u=null}),u=p,p=null)};e.$watch(g,function(g){var m=function(){!w(l)||l&&!e.$eval(l)|| +c()},r=++t;g?(a(g,!0).then(function(a){if(r===t){var c=e.$new();s.template=a;a=q(c,function(a){v();d.enter(a,null,f).then(m)});F=c;p=a;F.$emit("$includeContentLoaded",g);e.$eval(h)}},function(){r===t&&(v(),e.$emit("$includeContentError",g))}),e.$emit("$includeContentRequested",g)):(v(),s.template=null)})}}}}],Qe=["$compile",function(a){return{restrict:"ECA",priority:-400,require:"ngInclude",link:function(c,d,e,f){/SVG/.test(d[0].toString())?(d.empty(),a(Nc(f.template,U).childNodes)(c,function(a){d.append(a)}, +{futureParentElement:d})):(d.html(f.template),a(d.contents())(c))}}}],Ae=Ma({priority:450,compile:function(){return{pre:function(a,c,d){a.$eval(d.ngInit)}}}}),Me=function(){return{restrict:"A",priority:100,require:"ngModel",link:function(a,c,d,e){var f=c.attr(d.$attr.ngList)||", ",g="false"!==d.ngTrim,h=g?R(f):f;e.$parsers.push(function(a){if(!A(a)){var c=[];a&&m(a.split(h),function(a){a&&c.push(g?R(a):a)});return c}});e.$formatters.push(function(a){return G(a)?a.join(f):t});e.$isEmpty=function(a){return!a|| +!a.length}}}},mb="ng-valid",Kd="ng-invalid",Va="ng-pristine",Jb="ng-dirty",Md="ng-pending",Lb=new J("ngModel"),xg=["$scope","$exceptionHandler","$attrs","$element","$parse","$animate","$timeout","$rootScope","$q","$interpolate",function(a,c,d,e,f,g,h,l,k,n){this.$modelValue=this.$viewValue=Number.NaN;this.$$rawModelValue=t;this.$validators={};this.$asyncValidators={};this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$untouched=!0;this.$touched=!1;this.$pristine=!0;this.$dirty= +!1;this.$valid=!0;this.$invalid=!1;this.$error={};this.$$success={};this.$pending=t;this.$name=n(d.name||"",!1)(a);var r=f(d.ngModel),s=r.assign,q=r,C=s,F=null,u,p=this;this.$$setOptions=function(a){if((p.$options=a)&&a.getterSetter){var c=f(d.ngModel+"()"),g=f(d.ngModel+"($$$p)");q=function(a){var d=r(a);z(d)&&(d=c(a));return d};C=function(a,c){z(r(a))?g(a,{$$$p:p.$modelValue}):s(a,p.$modelValue)}}else if(!r.assign)throw Lb("nonassign",d.ngModel,ua(e));};this.$render=v;this.$isEmpty=function(a){return A(a)|| +""===a||null===a||a!==a};var K=e.inheritedData("$formController")||Ib,y=0;Hd({ctrl:this,$element:e,set:function(a,c){a[c]=!0},unset:function(a,c){delete a[c]},parentForm:K,$animate:g});this.$setPristine=function(){p.$dirty=!1;p.$pristine=!0;g.removeClass(e,Jb);g.addClass(e,Va)};this.$setDirty=function(){p.$dirty=!0;p.$pristine=!1;g.removeClass(e,Va);g.addClass(e,Jb);K.$setDirty()};this.$setUntouched=function(){p.$touched=!1;p.$untouched=!0;g.setClass(e,"ng-untouched","ng-touched")};this.$setTouched= +function(){p.$touched=!0;p.$untouched=!1;g.setClass(e,"ng-touched","ng-untouched")};this.$rollbackViewValue=function(){h.cancel(F);p.$viewValue=p.$$lastCommittedViewValue;p.$render()};this.$validate=function(){if(!V(p.$modelValue)||!isNaN(p.$modelValue)){var a=p.$$rawModelValue,c=p.$valid,d=p.$modelValue,e=p.$options&&p.$options.allowInvalid;p.$$runValidators(a,p.$$lastCommittedViewValue,function(f){e||c===f||(p.$modelValue=f?a:t,p.$modelValue!==d&&p.$$writeModelToScope())})}};this.$$runValidators= +function(a,c,d){function e(){var d=!0;m(p.$validators,function(e,f){var h=e(a,c);d=d&&h;g(f,h)});return d?!0:(m(p.$asyncValidators,function(a,c){g(c,null)}),!1)}function f(){var d=[],e=!0;m(p.$asyncValidators,function(f,h){var k=f(a,c);if(!k||!z(k.then))throw Lb("$asyncValidators",k);g(h,t);d.push(k.then(function(){g(h,!0)},function(a){e=!1;g(h,!1)}))});d.length?k.all(d).then(function(){h(e)},v):h(!0)}function g(a,c){l===y&&p.$setValidity(a,c)}function h(a){l===y&&d(a)}y++;var l=y;(function(){var a= +p.$$parserName||"parse";if(u===t)g(a,null);else return u||(m(p.$validators,function(a,c){g(c,null)}),m(p.$asyncValidators,function(a,c){g(c,null)})),g(a,u),u;return!0})()?e()?f():h(!1):h(!1)};this.$commitViewValue=function(){var a=p.$viewValue;h.cancel(F);if(p.$$lastCommittedViewValue!==a||""===a&&p.$$hasNativeValidators)p.$$lastCommittedViewValue=a,p.$pristine&&this.$setDirty(),this.$$parseAndValidate()};this.$$parseAndValidate=function(){var c=p.$$lastCommittedViewValue;if(u=A(c)?t:!0)for(var d= +0;df||e.$isEmpty(c)||c.length<=f}}}}},Ic=function(){return{restrict:"A",require:"?ngModel",link:function(a,c,d,e){if(e){var f=0;d.$observe("minlength",function(a){f=W(a)||0;e.$validate()});e.$validators.minlength=function(a,c){return e.$isEmpty(c)||c.length>=f}}}}};O.angular.bootstrap?console.log("WARNING: Tried to load angular more than once."):(ce(),ee(ca),y(U).ready(function(){Zd(U,Ac)}))})(window,document);!window.angular.$$csp()&&window.angular.element(document).find("head").prepend(''); +//# sourceMappingURL=angular.min.js.map diff --git a/servant-js/examples/www/angular/index.html b/servant-js/examples/www/angular/index.html new file mode 100644 index 00000000..81ce491a --- /dev/null +++ b/servant-js/examples/www/angular/index.html @@ -0,0 +1,39 @@ + + + Servant: counter + + + + + +

Angular version

+{{ 'Counter: ' + counter }} + + + + diff --git a/servant-js/examples/www/angular/service.html b/servant-js/examples/www/angular/service.html new file mode 100644 index 00000000..0131f4bc --- /dev/null +++ b/servant-js/examples/www/angular/service.html @@ -0,0 +1,39 @@ + + + Servant: counter + + + + +

Angular version (Service version)

+{{ 'Counter: ' + counter }} + + + + + diff --git a/servant-js/examples/www/index.html b/servant-js/examples/www/index.html new file mode 100644 index 00000000..4c55aea4 --- /dev/null +++ b/servant-js/examples/www/index.html @@ -0,0 +1,17 @@ + + + Servant: counter + + + + + + + + + + diff --git a/servant-jquery/examples/www/index.html b/servant-js/examples/www/jquery/index.html similarity index 84% rename from servant-jquery/examples/www/index.html rename to servant-js/examples/www/jquery/index.html index 075e6796..48c7d652 100644 --- a/servant-jquery/examples/www/index.html +++ b/servant-js/examples/www/jquery/index.html @@ -8,12 +8,12 @@ +

JQuery version

Counter: 0 -or view the docs - - + + - \ No newline at end of file + diff --git a/servant-jquery/examples/www/jquery.min.js b/servant-js/examples/www/jquery/jquery.min.js similarity index 100% rename from servant-jquery/examples/www/jquery.min.js rename to servant-js/examples/www/jquery/jquery.min.js diff --git a/servant-js/examples/www/vanilla/index.html b/servant-js/examples/www/vanilla/index.html new file mode 100644 index 00000000..245172ab --- /dev/null +++ b/servant-js/examples/www/vanilla/index.html @@ -0,0 +1,39 @@ + + + Servant: counter + + + +

Vanilla version

+Counter: 0 + + + + + + diff --git a/servant-jquery/servant-jquery.cabal b/servant-js/servant-js.cabal similarity index 81% rename from servant-jquery/servant-jquery.cabal rename to servant-js/servant-js.cabal index ebb7ec88..2fa38e0c 100644 --- a/servant-jquery/servant-jquery.cabal +++ b/servant-js/servant-js.cabal @@ -1,9 +1,11 @@ -name: servant-jquery +name: servant-js version: 0.4.1 -synopsis: Automatically derive (jquery) javascript functions to query servant webservices +synopsis: Automatically derive javascript functions to query servant webservices. description: Automatically derive jquery-based javascript functions to query servant webservices. . + Supports deriving functions using vanilla javascript AJAX requests, Angular or JQuery. + . You can find an example which serves the generated javascript to a webpage that allows you to trigger webservice calls. @@ -32,8 +34,11 @@ flag example default: False library - exposed-modules: Servant.JQuery - other-modules: Servant.JQuery.Internal + exposed-modules: Servant.JS + Servant.JS.Angular + Servant.JS.JQuery + Servant.JS.Vanilla + Servant.JS.Internal build-depends: base >=4.5 && <5 , charset , lens >= 4 @@ -59,7 +64,7 @@ executable counter , filepath , servant == 0.4.* , servant-server == 0.4.* - , servant-jquery == 0.4.* + , servant-js == 0.4.* , stm , transformers , warp @@ -73,7 +78,7 @@ test-suite spec build-depends: base == 4.* , lens - , servant-jquery + , servant-js , servant , hspec >= 2.0 , hspec-expectations diff --git a/servant-js/src/Servant/JS.hs b/servant-js/src/Servant/JS.hs new file mode 100644 index 00000000..1367fffd --- /dev/null +++ b/servant-js/src/Servant/JS.hs @@ -0,0 +1,153 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +----------------------------------------------------------------------------- +-- | +-- Module : Servant.JS +-- License : BSD3 +-- Maintainer : Alp Mestanogullari +-- Stability : experimental +-- Portability : non-portable +-- +-- Generating Javascript code to query your APIs using vanilla Javascript, +-- Angular.js or JQuery. +-- +-- Using this package is very simple. Say you have this API type around: +-- +-- > type API = "users" :> Get '[JSON] [Users] +-- > :<|> "messages" :> Get '[JSON] [Message] +-- +-- All you need to do to generate the Javascript code is to write a 'Proxy' +-- for this API type: +-- +-- > api :: Proxy API +-- > api = Proxy +-- +-- And pick one of the generators: +-- +-- - 'vanillaJS' and 'vanillaJSWith' generate functions that use +-- /XMLHttpRequest/ to query your endpoints. The former just calls +-- the latter with default code-generation options. +-- - 'jquery' and 'jqueryWith' follow the same pattern except that they +-- generate functions that use /jQuery/'s AJAX functions. +-- - 'angular' and 'angularWith' do the same but use /Angular.js/'s $http +-- service. In addition, we provide 'angularService' and 'angularServiceWith' +-- which produce functions under an Angular service that your controlers +-- can depend on to query the API. +-- +-- Let's keep it simple and produce vanilla Javascript code with the default options. +-- +-- @ +-- jsCode :: String +-- jsCode = 'jsForAPI' api 'vanillaJS' +-- @ +-- +-- That's it! If you want to write that code to a file: +-- +-- @ +-- writeJSCode :: IO () +-- writeJSCode = 'writeJSForAPI' api 'vanillaJS' "./my_api.js" +-- @ +-- +-- If you want to customize the rendering options, take a look +-- at 'CommonGeneratorOptions' which are generic options common to all the +-- generators. the /xxxWith/ variants all take 'CommonGeneratorOptions' whereas +-- the /xxx/ versions use 'defCommonGeneratorOptions'. Once you have some custom +-- +-- > myOptions :: 'CommonGeneratorOptions' +-- +-- All you need to do to use it is to use 'vanillaJSWith' and pass it @myOptions@. +-- +-- @ +-- jsCodeWithMyOptions :: String +-- jsCodeWithMyOptions = 'jsForAPI' api ('vanillaJSWith' myOptions) +-- @ +-- +-- Follow the same pattern for any other generator. +-- +-- /Note/: The Angular generators take an additional type of options, +-- namely 'AngularOptions', to let you tweak aspects of the code generation +-- that are specific to /Angular.js/. +module Servant.JS + ( -- * Generating javascript code from an API type + jsForAPI + , writeJSForAPI + , JavaScriptGenerator + + , -- * Options common to all generators + CommonGeneratorOptions(..) + , defCommonGeneratorOptions + + , -- * Vanilla Javascript code generation + vanillaJS + , vanillaJSWith + + , -- * JQuery code generation + jquery + , jqueryWith + + , -- * Angular.js code generation + angular + , angularWith + , angularService + , angularServiceWith + , AngularOptions(..) + , defAngularOptions + + , -- * Misc. + listFromAPI + , javascript + , HasJS(..) + , GenerateList(..) + , AjaxReq + ) where + +import Data.Proxy +import Servant.API +import Servant.JS.Angular +import Servant.JS.Internal +import Servant.JS.JQuery +import Servant.JS.Vanilla + +-- | Generate the data necessary to generate javascript code +-- for all the endpoints of an API, as ':<|>'-separated values +-- of type 'AjaxReq'. +javascript :: HasJS layout => Proxy layout -> JS layout +javascript p = javascriptFor p defReq + +-- | Directly generate all the javascript functions for your API +-- from a 'Proxy' for your API type. You can then write it to +-- a file or integrate it in a page, for example. +jsForAPI :: (HasJS api, GenerateList (JS api)) + => Proxy api -- ^ proxy for your API type + -> JavaScriptGenerator -- ^ js code generator to use (angular, vanilla js, jquery, others) + -> String -- ^ a string that you can embed in your pages or write to a file +jsForAPI p gen = gen (listFromAPI p) + +-- | Directly generate all the javascript functions for your API +-- from a 'Proxy' for your API type using the given generator +-- and write the resulting code to a file at the given path. +writeJSForAPI :: (HasJS api, GenerateList (JS api)) + => Proxy api -- ^ proxy for your API type + -> JavaScriptGenerator -- ^ js code generator to use (angular, vanilla js, jquery, others) + -> FilePath -- ^ path to the file you want to write the resulting javascript code into + -> IO () +writeJSForAPI p gen fp = writeFile fp (jsForAPI p gen) + +-- | Utility class used by 'jsForAPI' which computes +-- the data needed to generate a function for each endpoint +-- and hands it all back in a list. +class GenerateList reqs where + generateList :: reqs -> [AjaxReq] + +instance GenerateList AjaxReq where + generateList r = [r] + +instance GenerateList rest => GenerateList (AjaxReq :<|> rest) where + generateList (r :<|> rest) = r : generateList rest + +-- | Generate the necessary data for JS codegen as a list, each 'AjaxReq' +-- describing one endpoint from your API type. +listFromAPI :: (HasJS api, GenerateList (JS api)) => Proxy api -> [AjaxReq] +listFromAPI p = generateList (javascript p) diff --git a/servant-js/src/Servant/JS/Angular.hs b/servant-js/src/Servant/JS/Angular.hs new file mode 100644 index 00000000..68ff0a27 --- /dev/null +++ b/servant-js/src/Servant/JS/Angular.hs @@ -0,0 +1,139 @@ +module Servant.JS.Angular where + +import Servant.JS.Internal +import Control.Lens +import Data.List +import Data.Monoid + +-- | Options specific to the angular code generator +data AngularOptions = AngularOptions + { serviceName :: String -- ^ When generating code with wrapInService, + -- name of the service to generate + , prologue :: String -> String -> String -- ^ beginning of the service definition + , epilogue :: String -- ^ end of the service definition + } + +-- | Default options for the Angular codegen. Used by 'wrapInService'. +defAngularOptions :: AngularOptions +defAngularOptions = AngularOptions + { serviceName = "" + , prologue = \svc m -> m <> "service('" <> svc <> "', function($http) {\n" + <> " return ({" + , epilogue = "});\n});\n" + } + +-- | Instead of simply generating top level functions, generates a service instance +-- on which your controllers can depend to access your API. +-- This variant uses default 'AngularOptions'. +angularService :: AngularOptions -> JavaScriptGenerator +angularService ngOpts = angularServiceWith ngOpts defCommonGeneratorOptions + +-- | Instead of simply generating top level functions, generates a service instance +-- on which your controllers can depend to access your API +angularServiceWith :: AngularOptions -> CommonGeneratorOptions -> JavaScriptGenerator +angularServiceWith ngOpts opts reqs = + prologue ngOpts svc mName + <> intercalate "," (map generator reqs) <> + epilogue ngOpts + where + generator req = generateAngularJSWith ngOpts opts req + svc = serviceName ngOpts + mName = if null (moduleName opts) + then "app." + else moduleName opts <> "." + +-- | Generate regular javacript functions that use +-- the $http service, using default values for 'CommonGeneratorOptions'. +angular :: AngularOptions -> JavaScriptGenerator +angular ngopts = angularWith ngopts defCommonGeneratorOptions + +-- | Generate regular javascript functions that use the $http service. +angularWith :: AngularOptions -> CommonGeneratorOptions -> JavaScriptGenerator +angularWith ngopts opts = intercalate "\n\n" . map (generateAngularJSWith ngopts opts) + +-- | js codegen using $http service from Angular using default options +generateAngularJS :: AngularOptions -> AjaxReq -> String +generateAngularJS ngOpts = generateAngularJSWith ngOpts defCommonGeneratorOptions + +-- | js codegen using $http service from Angular +generateAngularJSWith :: AngularOptions -> CommonGeneratorOptions -> AjaxReq -> String +generateAngularJSWith ngOptions opts req = "\n" <> + fname <> fsep <> " function(" <> argsStr <> ")\n" + <> "{\n" + <> " return $http(\n" + <> " { url: " <> url <> "\n" + <> dataBody + <> reqheaders + <> " , method: '" <> method <> "'\n" + <> " });\n" + <> "}\n" + + where argsStr = intercalate ", " args + args = http + ++ captures + ++ map (view argName) queryparams + ++ body + ++ map (toValidFunctionName . (<>) "header" . headerArgName) hs + + -- If we want to generate Top Level Function, they must depend on + -- the $http service, if we generate a service, the functions will + -- inherit this dependency from the service + http = case length (serviceName ngOptions) of + 0 -> ["$http"] + _ -> [] + + captures = map captureArg + . filter isCapture + $ req ^. reqUrl.path + + hs = req ^. reqHeaders + + queryparams = req ^.. reqUrl.queryStr.traverse + + body = if req ^. reqBody + then [requestBody opts] + else [] + + dataBody = + if req ^. reqBody + then " , data: JSON.stringify(body)\n" <> + " , contentType: 'application/json'\n" + else "" + + reqheaders = + if null hs + then "" + else " , headers: { " ++ headersStr ++ " }\n" + + where headersStr = intercalate ", " $ map headerStr hs + headerStr header = "\"" ++ + headerArgName header ++ + "\": " ++ show header + + namespace = + if hasService + then "" + else if hasNoModule + then "var " + else (moduleName opts) <> "." + where + hasNoModule = null (moduleName opts) + + hasService = not $ null (serviceName ngOptions) + + fsep = if hasService then ":" else " =" + + fname = namespace <> (functionRenamer opts $ req ^. funcName) + + method = req ^. reqMethod + url = if url' == "'" then "'/'" else url' + url' = "'" + ++ urlArgs + ++ queryArgs + + urlArgs = jsSegments + $ req ^.. reqUrl.path.traverse + + queryArgs = if null queryparams + then "" + else " + '?" ++ jsParams queryparams diff --git a/servant-jquery/src/Servant/JQuery/Internal.hs b/servant-js/src/Servant/JS/Internal.hs similarity index 56% rename from servant-jquery/src/Servant/JQuery/Internal.hs rename to servant-js/src/Servant/JS/Internal.hs index 859c1b1b..909df8d2 100644 --- a/servant-jquery/src/Servant/JQuery/Internal.hs +++ b/servant-js/src/Servant/JS/Internal.hs @@ -8,7 +8,7 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} -module Servant.JQuery.Internal where +module Servant.JS.Internal where #if !MIN_VERSION_base(4,8,0) import Control.Applicative @@ -25,8 +25,45 @@ import GHC.Exts (Constraint) import GHC.TypeLits import Servant.API +-- | this structure is used by JavaScriptGenerator implementations to let you +-- customize the output +data CommonGeneratorOptions = CommonGeneratorOptions + { + functionRenamer :: String -> String -- ^ function transforming function names + , requestBody :: String -- ^ name used when a user want to send the request body (to let you redefine it) + , successCallback :: String -- ^ name of the callback parameter when the request was successful + , errorCallback :: String -- ^ name of the callback parameter when the request reported an error + , moduleName :: String -- ^ namespace on which we define the js function (empty mean local var) + } + +-- | Default options. +-- +-- @ +-- > defCommonGeneratorOptions = CommonGeneratorOptions +-- > { functionRenamer = id +-- > , requestBody = "body" +-- > , successCallback = "onSuccess" +-- > , errorCallback = "onError" +-- > , moduleName = "" +-- > } +-- @ +defCommonGeneratorOptions :: CommonGeneratorOptions +defCommonGeneratorOptions = CommonGeneratorOptions + { + functionRenamer = id + , requestBody = "body" + , successCallback = "onSuccess" + , errorCallback = "onError" + , moduleName = "" + } + type Arg = String +-- A 'JavascriptGenerator' just takes the data found in the API type +-- for each endpoint and generates Javascript code in a String. Several +-- generators are available in this package. +type JavaScriptGenerator = [AjaxReq] -> String + data Segment = Segment { _segment :: SegmentType, _matrix :: [MatrixArg] } deriving (Eq, Show) @@ -193,172 +230,172 @@ type family Elem (a :: *) (ls::[*]) :: Constraint where Elem a (a ': list) = () Elem a (b ': list) = Elem a list -class HasJQ (layout :: *) where - type JQ layout :: * - jqueryFor :: Proxy layout -> AjaxReq -> JQ layout +class HasJS (layout :: *) where + type JS layout :: * + javascriptFor :: Proxy layout -> AjaxReq -> JS layout -instance (HasJQ a, HasJQ b) - => HasJQ (a :<|> b) where - type JQ (a :<|> b) = JQ a :<|> JQ b +instance (HasJS a, HasJS b) + => HasJS (a :<|> b) where + type JS (a :<|> b) = JS a :<|> JS b - jqueryFor Proxy req = - jqueryFor (Proxy :: Proxy a) req - :<|> jqueryFor (Proxy :: Proxy b) req + javascriptFor Proxy req = + javascriptFor (Proxy :: Proxy a) req + :<|> javascriptFor (Proxy :: Proxy b) req -instance (KnownSymbol sym, HasJQ sublayout) - => HasJQ (Capture sym a :> sublayout) where - type JQ (Capture sym a :> sublayout) = JQ sublayout +instance (KnownSymbol sym, HasJS sublayout) + => HasJS (Capture sym a :> sublayout) where + type JS (Capture sym a :> sublayout) = JS sublayout - jqueryFor Proxy req = - jqueryFor (Proxy :: Proxy sublayout) $ + javascriptFor Proxy req = + javascriptFor (Proxy :: Proxy sublayout) $ req & reqUrl.path <>~ [Segment (Cap str) []] where str = symbolVal (Proxy :: Proxy sym) -instance Elem JSON list => HasJQ (Delete list a) where - type JQ (Delete list a) = AjaxReq +instance Elem JSON list => HasJS (Delete list a) where + type JS (Delete list a) = AjaxReq - jqueryFor Proxy req = + javascriptFor Proxy req = req & funcName %~ ("delete" <>) & reqMethod .~ "DELETE" -instance Elem JSON list => HasJQ (Get list a) where - type JQ (Get list a) = AjaxReq +instance Elem JSON list => HasJS (Get list a) where + type JS (Get list a) = AjaxReq - jqueryFor Proxy req = + javascriptFor Proxy req = req & funcName %~ ("get" <>) & reqMethod .~ "GET" -instance (KnownSymbol sym, HasJQ sublayout) - => HasJQ (Header sym a :> sublayout) where - type JQ (Header sym a :> sublayout) = JQ sublayout +instance (KnownSymbol sym, HasJS sublayout) + => HasJS (Header sym a :> sublayout) where + type JS (Header sym a :> sublayout) = JS sublayout - jqueryFor Proxy req = - jqueryFor subP (req & reqHeaders <>~ [HeaderArg hname]) + javascriptFor Proxy req = + javascriptFor subP (req & reqHeaders <>~ [HeaderArg hname]) where hname = symbolVal (Proxy :: Proxy sym) subP = Proxy :: Proxy sublayout -instance Elem JSON list => HasJQ (Post list a) where - type JQ (Post list a) = AjaxReq +instance Elem JSON list => HasJS (Post list a) where + type JS (Post list a) = AjaxReq - jqueryFor Proxy req = + javascriptFor Proxy req = req & funcName %~ ("post" <>) & reqMethod .~ "POST" -instance Elem JSON list => HasJQ (Put list a) where - type JQ (Put list a) = AjaxReq +instance Elem JSON list => HasJS (Put list a) where + type JS (Put list a) = AjaxReq - jqueryFor Proxy req = + javascriptFor Proxy req = req & funcName %~ ("put" <>) & reqMethod .~ "PUT" -instance (KnownSymbol sym, HasJQ sublayout) - => HasJQ (QueryParam sym a :> sublayout) where - type JQ (QueryParam sym a :> sublayout) = JQ sublayout +instance (KnownSymbol sym, HasJS sublayout) + => HasJS (QueryParam sym a :> sublayout) where + type JS (QueryParam sym a :> sublayout) = JS sublayout - jqueryFor Proxy req = - jqueryFor (Proxy :: Proxy sublayout) $ + javascriptFor Proxy req = + javascriptFor (Proxy :: Proxy sublayout) $ req & reqUrl.queryStr <>~ [QueryArg str Normal] where str = symbolVal (Proxy :: Proxy sym) -instance (KnownSymbol sym, HasJQ sublayout) - => HasJQ (QueryParams sym a :> sublayout) where - type JQ (QueryParams sym a :> sublayout) = JQ sublayout +instance (KnownSymbol sym, HasJS sublayout) + => HasJS (QueryParams sym a :> sublayout) where + type JS (QueryParams sym a :> sublayout) = JS sublayout - jqueryFor Proxy req = - jqueryFor (Proxy :: Proxy sublayout) $ + javascriptFor Proxy req = + javascriptFor (Proxy :: Proxy sublayout) $ req & reqUrl.queryStr <>~ [QueryArg str List] where str = symbolVal (Proxy :: Proxy sym) -instance (KnownSymbol sym, HasJQ sublayout) - => HasJQ (QueryFlag sym :> sublayout) where - type JQ (QueryFlag sym :> sublayout) = JQ sublayout +instance (KnownSymbol sym, HasJS sublayout) + => HasJS (QueryFlag sym :> sublayout) where + type JS (QueryFlag sym :> sublayout) = JS sublayout - jqueryFor Proxy req = - jqueryFor (Proxy :: Proxy sublayout) $ + javascriptFor Proxy req = + javascriptFor (Proxy :: Proxy sublayout) $ req & reqUrl.queryStr <>~ [QueryArg str Flag] where str = symbolVal (Proxy :: Proxy sym) -instance (KnownSymbol sym, HasJQ sublayout) - => HasJQ (MatrixParam sym a :> sublayout) where - type JQ (MatrixParam sym a :> sublayout) = JQ sublayout +instance (KnownSymbol sym, HasJS sublayout) + => HasJS (MatrixParam sym a :> sublayout) where + type JS (MatrixParam sym a :> sublayout) = JS sublayout - jqueryFor Proxy req = - jqueryFor (Proxy :: Proxy sublayout) $ + javascriptFor Proxy req = + javascriptFor (Proxy :: Proxy sublayout) $ req & reqUrl.path._last.matrix <>~ [QueryArg strArg Normal] where str = symbolVal (Proxy :: Proxy sym) strArg = str ++ "Value" -instance (KnownSymbol sym, HasJQ sublayout) - => HasJQ (MatrixParams sym a :> sublayout) where - type JQ (MatrixParams sym a :> sublayout) = JQ sublayout +instance (KnownSymbol sym, HasJS sublayout) + => HasJS (MatrixParams sym a :> sublayout) where + type JS (MatrixParams sym a :> sublayout) = JS sublayout - jqueryFor Proxy req = - jqueryFor (Proxy :: Proxy sublayout) $ + javascriptFor Proxy req = + javascriptFor (Proxy :: Proxy sublayout) $ req & reqUrl.path._last.matrix <>~ [QueryArg str List] where str = symbolVal (Proxy :: Proxy sym) -instance (KnownSymbol sym, HasJQ sublayout) - => HasJQ (MatrixFlag sym :> sublayout) where - type JQ (MatrixFlag sym :> sublayout) = JQ sublayout +instance (KnownSymbol sym, HasJS sublayout) + => HasJS (MatrixFlag sym :> sublayout) where + type JS (MatrixFlag sym :> sublayout) = JS sublayout - jqueryFor Proxy req = - jqueryFor (Proxy :: Proxy sublayout) $ + javascriptFor Proxy req = + javascriptFor (Proxy :: Proxy sublayout) $ req & reqUrl.path._last.matrix <>~ [QueryArg str Flag] where str = symbolVal (Proxy :: Proxy sym) -instance HasJQ Raw where - type JQ Raw = Method -> AjaxReq +instance HasJS Raw where + type JS Raw = Method -> AjaxReq - jqueryFor Proxy req method = + javascriptFor Proxy req method = req & funcName %~ ((toLower <$> method) <>) & reqMethod .~ method -instance (Elem JSON list, HasJQ sublayout) => HasJQ (ReqBody list a :> sublayout) where - type JQ (ReqBody list a :> sublayout) = JQ sublayout +instance (Elem JSON list, HasJS sublayout) => HasJS (ReqBody list a :> sublayout) where + type JS (ReqBody list a :> sublayout) = JS sublayout - jqueryFor Proxy req = - jqueryFor (Proxy :: Proxy sublayout) $ + javascriptFor Proxy req = + javascriptFor (Proxy :: Proxy sublayout) $ req & reqBody .~ True -instance (KnownSymbol path, HasJQ sublayout) - => HasJQ (path :> sublayout) where - type JQ (path :> sublayout) = JQ sublayout +instance (KnownSymbol path, HasJS sublayout) + => HasJS (path :> sublayout) where + type JS (path :> sublayout) = JS sublayout - jqueryFor Proxy req = - jqueryFor (Proxy :: Proxy sublayout) $ + javascriptFor Proxy req = + javascriptFor (Proxy :: Proxy sublayout) $ req & reqUrl.path <>~ [Segment (Static str) []] & funcName %~ (str <>) where str = map (\c -> if c == '.' then '_' else c) $ symbolVal (Proxy :: Proxy path) -instance HasJQ sublayout => HasJQ (RemoteHost :> sublayout) where - type JQ (RemoteHost :> sublayout) = JQ sublayout +instance HasJS sublayout => HasJS (RemoteHost :> sublayout) where + type JS (RemoteHost :> sublayout) = JS sublayout - jqueryFor Proxy req = - jqueryFor (Proxy :: Proxy sublayout) req + javascriptFor Proxy req = + javascriptFor (Proxy :: Proxy sublayout) req -instance HasJQ sublayout => HasJQ (IsSecure :> sublayout) where - type JQ (IsSecure :> sublayout) = JQ sublayout +instance HasJS sublayout => HasJS (IsSecure :> sublayout) where + type JS (IsSecure :> sublayout) = JS sublayout - jqueryFor Proxy req = - jqueryFor (Proxy :: Proxy sublayout) req + javascriptFor Proxy req = + javascriptFor (Proxy :: Proxy sublayout) req -instance HasJQ sublayout => HasJQ (Vault :> sublayout) where - type JQ (Vault :> sublayout) = JQ sublayout +instance HasJS sublayout => HasJS (Vault :> sublayout) where + type JS (Vault :> sublayout) = JS sublayout - jqueryFor Proxy req = - jqueryFor (Proxy :: Proxy sublayout) req + javascriptFor Proxy req = + javascriptFor (Proxy :: Proxy sublayout) req -instance HasJQ sublayout => HasJQ (HttpVersion :> sublayout) where - type JQ (HttpVersion :> sublayout) = JQ sublayout +instance HasJS sublayout => HasJS (HttpVersion :> sublayout) where + type JS (HttpVersion :> sublayout) = JS sublayout - jqueryFor Proxy req = - jqueryFor (Proxy :: Proxy sublayout) req + javascriptFor Proxy req = + javascriptFor (Proxy :: Proxy sublayout) req diff --git a/servant-js/src/Servant/JS/JQuery.hs b/servant-js/src/Servant/JS/JQuery.hs new file mode 100644 index 00000000..be36792e --- /dev/null +++ b/servant-js/src/Servant/JS/JQuery.hs @@ -0,0 +1,92 @@ +module Servant.JS.JQuery where + +import Servant.JS.Internal +import Control.Lens +import Data.List +import Data.Monoid + +-- | Generate javascript functions that use the /jQuery/ library +-- to make the AJAX calls. Uses 'defCommonGeneratorOptions' +-- for the generator options. +jquery :: JavaScriptGenerator +jquery = concat . map generateJQueryJS + +-- | Generate javascript functions that use the /jQuery/ library +-- to make the AJAX calls. Lets you specify your own 'CommonGeneratorOptions'. +jqueryWith :: CommonGeneratorOptions -> JavaScriptGenerator +jqueryWith opts = concat . map (generateJQueryJSWith opts) + +-- | js codegen using JQuery using default options +generateJQueryJS :: AjaxReq -> String +generateJQueryJS = generateJQueryJSWith defCommonGeneratorOptions + +-- | js codegen using JQuery +generateJQueryJSWith :: CommonGeneratorOptions -> AjaxReq -> String +generateJQueryJSWith opts req = "\n" <> + fname <> " = function(" <> argsStr <> ")\n" + <> "{\n" + <> " $.ajax(\n" + <> " { url: " <> url <> "\n" + <> " , success: " <> onSuccess <> "\n" + <> dataBody + <> reqheaders + <> " , error: " <> onError <> "\n" + <> " , type: '" <> method <> "'\n" + <> " });\n" + <> "}\n" + + where argsStr = intercalate ", " args + args = captures + ++ map (view argName) queryparams + ++ body + ++ map (toValidFunctionName . (<>) "header" . headerArgName) hs + ++ [onSuccess, onError] + + captures = map captureArg + . filter isCapture + $ req ^. reqUrl.path + + hs = req ^. reqHeaders + + queryparams = req ^.. reqUrl.queryStr.traverse + + body = if req ^. reqBody + then [requestBody opts] + else [] + + onSuccess = successCallback opts + onError = errorCallback opts + + dataBody = + if req ^. reqBody + then " , data: JSON.stringify(body)\n" <> + " , contentType: 'application/json'\n" + else "" + + reqheaders = + if null hs + then "" + else " , headers: { " ++ headersStr ++ " }\n" + + where headersStr = intercalate ", " $ map headerStr hs + headerStr header = "\"" ++ + headerArgName header ++ + "\": " ++ show header + + namespace = if null (moduleName opts) + then "var " + else (moduleName opts) <> "." + fname = namespace <> (functionRenamer opts $ req ^. funcName) + + method = req ^. reqMethod + url = if url' == "'" then "'/'" else url' + url' = "'" + ++ urlArgs + ++ queryArgs + + urlArgs = jsSegments + $ req ^.. reqUrl.path.traverse + + queryArgs = if null queryparams + then "" + else " + '?" ++ jsParams queryparams diff --git a/servant-js/src/Servant/JS/Vanilla.hs b/servant-js/src/Servant/JS/Vanilla.hs new file mode 100644 index 00000000..c9b0fb49 --- /dev/null +++ b/servant-js/src/Servant/JS/Vanilla.hs @@ -0,0 +1,99 @@ +module Servant.JS.Vanilla where + +import Servant.JS.Internal +import Control.Lens +import Data.List +import Data.Monoid + +-- | Generate vanilla javascript functions to make AJAX requests +-- to your API, using /XMLHttpRequest/. Uses 'defCommonGeneratorOptions' +-- for the 'CommonGeneratorOptions'. +vanillaJS :: JavaScriptGenerator +vanillaJS = concat . map generateVanillaJS + +-- | Generate vanilla javascript functions to make AJAX requests +-- to your API, using /XMLHttpRequest/. Lets you specify your +-- own options. +vanillaJSWith :: CommonGeneratorOptions -> JavaScriptGenerator +vanillaJSWith opts = concat . map (generateVanillaJSWith opts) + +-- | js codegen using XmlHttpRequest using default generation options +generateVanillaJS :: AjaxReq -> String +generateVanillaJS = generateVanillaJSWith defCommonGeneratorOptions + +-- | js codegen using XmlHttpRequest +generateVanillaJSWith :: CommonGeneratorOptions -> AjaxReq -> String +generateVanillaJSWith opts req = "\n" <> + fname <> " = function(" <> argsStr <> ")\n" + <> "{\n" + <> " var xhr = new XMLHttpRequest();\n" + <> " xhr.open('" <> method <> "', " <> url <> ", true);\n" + <> reqheaders + <> " xhr.onreadystatechange = function (e) {\n" + <> " if (xhr.readyState == 4) {\n" + <> " var value = JSON.parse(xhr.responseText);\n" + <> " if (xhr.status == 200 || xhr.status == 201) {\n" + <> " onSuccess(value);\n" + <> " } else {\n" + <> " onError(value);\n" + <> " }\n" + <> " }\n" + <> " }\n" + <> " xhr.send(" <> dataBody <> ");\n" + <> "}\n" + + where argsStr = intercalate ", " args + args = captures + ++ map (view argName) queryparams + ++ body + ++ map (toValidFunctionName . (<>) "header" . headerArgName) hs + ++ [onSuccess, onError] + + captures = map captureArg + . filter isCapture + $ req ^. reqUrl.path + + hs = req ^. reqHeaders + + queryparams = req ^.. reqUrl.queryStr.traverse + + body = if req ^. reqBody + then [requestBody opts] + else [] + + onSuccess = successCallback opts + onError = errorCallback opts + + dataBody = + if req ^. reqBody + then "JSON.stringify(body)\n" + else "null" + + + reqheaders = + if null hs + then "" + else headersStr ++ "\n" + + where headersStr = intercalate "\n" $ map headerStr hs + headerStr header = " xhr.setRequestHeader(\"" ++ + headerArgName header ++ + "\", " ++ show header ++ ");" + + namespace = if null (moduleName opts) + then "var " + else (moduleName opts) <> "." + fname = namespace <> (functionRenamer opts $ req ^. funcName) + + method = req ^. reqMethod + url = if url' == "'" then "'/'" else url' + url' = "'" + ++ urlArgs + ++ queryArgs + + urlArgs = jsSegments + $ req ^.. reqUrl.path.traverse + + queryArgs = if null queryparams + then "" + else " + '?" ++ jsParams queryparams diff --git a/servant-js/test/Servant/JSSpec.hs b/servant-js/test/Servant/JSSpec.hs new file mode 100644 index 00000000..5ff60f9a --- /dev/null +++ b/servant-js/test/Servant/JSSpec.hs @@ -0,0 +1,151 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} +module Servant.JSSpec where + +import Data.Either (isRight) +import Data.Proxy +import Language.ECMAScript3.Parser (parseFromString) +import Test.Hspec + +import Servant.API +import Servant.JS +import qualified Servant.JS.Vanilla as JS +import qualified Servant.JS.JQuery as JQ +import qualified Servant.JS.Angular as NG +import Servant.JSSpec.CustomHeaders + +type TestAPI = "simple" :> ReqBody '[JSON,FormUrlEncoded] String :> Post '[JSON] Bool + :<|> "has.extension" :> Get '[FormUrlEncoded,JSON] Bool + +type TopLevelRawAPI = "something" :> Get '[JSON] Int + :<|> Raw + +type HeaderHandlingAPI = "something" :> Header "Foo" String + :> Get '[JSON] Int + +type CustomAuthAPI = "something" :> Authorization "Basic" String + :> Get '[JSON] Int + +type CustomHeaderAPI = "something" :> MyLovelyHorse String + :> Get '[JSON] Int + +type CustomHeaderAPI2 = "something" :> WhatsForDinner String + :> Get '[JSON] Int + +headerHandlingProxy :: Proxy HeaderHandlingAPI +headerHandlingProxy = Proxy + +customAuthProxy :: Proxy CustomAuthAPI +customAuthProxy = Proxy + +customHeaderProxy :: Proxy CustomHeaderAPI +customHeaderProxy = Proxy + +customHeaderProxy2 :: Proxy CustomHeaderAPI2 +customHeaderProxy2 = Proxy + +data TestNames = Vanilla + | VanillaCustom + | JQuery + | JQueryCustom + | Angular + | AngularCustom + deriving (Show, Eq) + +customOptions :: CommonGeneratorOptions +customOptions = defCommonGeneratorOptions { + successCallback = "okCallback", + errorCallback = "errorCallback" + } + +spec :: Spec +spec = describe "Servant.JQuery" $ do + generateJSSpec Vanilla JS.generateVanillaJS + generateJSSpec VanillaCustom (JS.generateVanillaJSWith customOptions) + generateJSSpec JQuery JQ.generateJQueryJS + generateJSSpec JQueryCustom (JQ.generateJQueryJSWith customOptions) + generateJSSpec Angular (NG.generateAngularJS NG.defAngularOptions) + generateJSSpec AngularCustom (NG.generateAngularJSWith NG.defAngularOptions customOptions) + + angularSpec Angular + angularSpec AngularCustom + +angularSpec :: TestNames -> Spec +angularSpec test = describe specLabel $ do + it "should implement a service globally" $ do + let jsText = genJS $ listFromAPI (Proxy :: Proxy TestAPI) + output jsText + jsText `shouldContain` (".service('" ++ testName ++ "'") + + it "should depend on $http service globally" $ do + let jsText = genJS $ listFromAPI (Proxy :: Proxy TestAPI) + output jsText + jsText `shouldContain` ("('" ++ testName ++ "', function($http) {") + + it "should not depend on $http service in handlers" $ do + let jsText = genJS $ listFromAPI (Proxy :: Proxy TestAPI) + output jsText + jsText `shouldNotContain` "getsomething($http, " + where + specLabel = "generateJS(" ++ (show test) ++ ")" + --output = putStrLn + output _ = return () + testName = "MyService" + ngOpts = NG.defAngularOptions { NG.serviceName = testName } + genJS req = NG.angularService ngOpts req + +generateJSSpec :: TestNames -> (AjaxReq -> String) -> Spec +generateJSSpec n gen = describe specLabel $ do + it "should generate valid javascript" $ do + let s = jsForAPI (Proxy :: Proxy TestAPI) (concat . map gen) + parseFromString s `shouldSatisfy` isRight + + it "should use non-empty function names" $ do + let (_ :<|> topLevel) = javascript (Proxy :: Proxy TopLevelRawAPI) + output $ genJS (topLevel "GET") + parseFromString (genJS $ topLevel "GET") `shouldSatisfy` isRight + + it "should handle simple HTTP headers" $ do + let jsText = genJS $ javascript headerHandlingProxy + output jsText + parseFromString jsText `shouldSatisfy` isRight + jsText `shouldContain` "headerFoo" + jsText `shouldContain` (header n "Foo" $ "headerFoo") + + it "should handle complex HTTP headers" $ do + let jsText = genJS $ javascript customAuthProxy + output jsText + parseFromString jsText `shouldSatisfy` isRight + jsText `shouldContain` "headerAuthorization" + jsText `shouldContain` (header n "Authorization" $ "\"Basic \" + headerAuthorization") + + it "should handle complex, custom HTTP headers" $ do + let jsText = genJS $ javascript customHeaderProxy + output jsText + parseFromString jsText `shouldSatisfy` isRight + jsText `shouldContain` "headerXMyLovelyHorse" + jsText `shouldContain` (header n "X-MyLovelyHorse" $ "\"I am good friends with \" + headerXMyLovelyHorse") + + it "should handle complex, custom HTTP headers (template replacement)" $ do + let jsText = genJS $ javascript customHeaderProxy2 + output jsText + parseFromString jsText `shouldSatisfy` isRight + jsText `shouldContain` "headerXWhatsForDinner" + jsText `shouldContain` (header n "X-WhatsForDinner" $ "\"I would like \" + headerXWhatsForDinner + \" with a cherry on top.\"") + + it "can generate the whole javascript code string at once with jsForAPI" $ do + let jsStr = jsForAPI (Proxy :: Proxy TestAPI) (concat . map gen) + parseFromString jsStr `shouldSatisfy` isRight + where + specLabel = "generateJS(" ++ (show n) ++ ")" + output _ = return () + genJS req = gen req + header :: TestNames -> String -> String -> String + header v headerName headerValue + | v `elem` [Vanilla, VanillaCustom] = "xhr.setRequestHeader(\"" ++ headerName ++ "\", " ++ headerValue ++ ");\n" + | otherwise = "headers: { \"" ++ headerName ++ "\": " ++ headerValue ++ " }\n" diff --git a/servant-jquery/test/Servant/JQuerySpec/CustomHeaders.hs b/servant-js/test/Servant/JSSpec/CustomHeaders.hs similarity index 63% rename from servant-jquery/test/Servant/JQuerySpec/CustomHeaders.hs rename to servant-js/test/Servant/JSSpec/CustomHeaders.hs index 4480d44c..af89d174 100644 --- a/servant-jquery/test/Servant/JQuerySpec/CustomHeaders.hs +++ b/servant-js/test/Servant/JSSpec/CustomHeaders.hs @@ -6,25 +6,26 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} -module Servant.JQuerySpec.CustomHeaders where +module Servant.JSSpec.CustomHeaders where import Control.Lens import Data.Monoid import Data.Proxy import GHC.TypeLits import Servant.API -import Servant.JQuery +import Servant.JS +import Servant.JS.Internal -- | This is a hypothetical combinator that fetches an Authorization header. -- The symbol in the header denotes what kind of authentication we are -- using -- Basic, Digest, whatever. data Authorization (sym :: Symbol) a -instance (KnownSymbol sym, HasJQ sublayout) - => HasJQ (Authorization sym a :> sublayout) where - type JQ (Authorization sym a :> sublayout) = JQ sublayout +instance (KnownSymbol sym, HasJS sublayout) + => HasJS (Authorization sym a :> sublayout) where + type JS (Authorization sym a :> sublayout) = JS sublayout - jqueryFor Proxy req = jqueryFor (Proxy :: Proxy sublayout) $ + javascriptFor Proxy req = javascriptFor (Proxy :: Proxy sublayout) $ req & reqHeaders <>~ [ ReplaceHeaderArg "Authorization" $ tokenType (symbolVal (Proxy :: Proxy sym)) ] where @@ -33,11 +34,11 @@ instance (KnownSymbol sym, HasJQ sublayout) -- | This is a combinator that fetches an X-MyLovelyHorse header. data MyLovelyHorse a -instance (HasJQ sublayout) - => HasJQ (MyLovelyHorse a :> sublayout) where - type JQ (MyLovelyHorse a :> sublayout) = JQ sublayout +instance (HasJS sublayout) + => HasJS (MyLovelyHorse a :> sublayout) where + type JS (MyLovelyHorse a :> sublayout) = JS sublayout - jqueryFor Proxy req = jqueryFor (Proxy :: Proxy sublayout) $ + javascriptFor Proxy req = javascriptFor (Proxy :: Proxy sublayout) $ req & reqHeaders <>~ [ ReplaceHeaderArg "X-MyLovelyHorse" tpl ] where tpl = "I am good friends with {X-MyLovelyHorse}" @@ -45,11 +46,11 @@ instance (HasJQ sublayout) -- | This is a combinator that fetches an X-WhatsForDinner header. data WhatsForDinner a -instance (HasJQ sublayout) - => HasJQ (WhatsForDinner a :> sublayout) where - type JQ (WhatsForDinner a :> sublayout) = JQ sublayout +instance (HasJS sublayout) + => HasJS (WhatsForDinner a :> sublayout) where + type JS (WhatsForDinner a :> sublayout) = JS sublayout - jqueryFor Proxy req = jqueryFor (Proxy :: Proxy sublayout) $ + javascriptFor Proxy req = javascriptFor (Proxy :: Proxy sublayout) $ req & reqHeaders <>~ [ ReplaceHeaderArg "X-WhatsForDinner" tpl ] where tpl = "I would like {X-WhatsForDinner} with a cherry on top." diff --git a/servant-jquery/test/Spec.hs b/servant-js/test/Spec.hs similarity index 100% rename from servant-jquery/test/Spec.hs rename to servant-js/test/Spec.hs diff --git a/sources.txt b/sources.txt index a36345e5..5854a3d0 100644 --- a/sources.txt +++ b/sources.txt @@ -1,7 +1,7 @@ servant servant-client servant-docs -servant-jquery +servant-js servant-server servant-examples servant-blaze