web: cleanup, enable jquery, use auto-completing combo fields on add form
This commit is contained in:
		
							parent
							
								
									f3ffef2d8a
								
							
						
					
					
						commit
						8c8395778c
					
				| @ -12,6 +12,7 @@ import System.FilePath ((</>), takeFileName) | ||||
| import System.IO.Storage (withStore, putValue, getValue) | ||||
| import Text.ParserCombinators.Parsec (parse) | ||||
| import Yesod | ||||
| import Yesod.Helpers.Static | ||||
| 
 | ||||
| import Hledger.Cli.Commands.Add (journalAddTransaction) | ||||
| import Hledger.Cli.Commands.Balance | ||||
| @ -28,36 +29,43 @@ import Paths_hledger_make (getDataFileName) | ||||
| #else | ||||
| import Paths_hledger (getDataFileName) | ||||
| #endif | ||||
| -- import Hledger.Cli.Commands.Web.Templates | ||||
| 
 | ||||
| 
 | ||||
| defhost           = "localhost" | ||||
| defport           = 5000 | ||||
| defbaseurl = printf "http://%s:%d" defhost defport :: String | ||||
| browserstartdelay = 100000 -- microseconds | ||||
| hledgerurl = "http://hledger.org" | ||||
| manualurl = hledgerurl++"/MANUAL.html" | ||||
| hledgerorgurl     = "http://hledger.org" | ||||
| manualurl         = hledgerorgurl++"/MANUAL.html" | ||||
| 
 | ||||
| data HledgerWebApp = HledgerWebApp { | ||||
|       appRoot    :: String | ||||
|      ,appWebdir  :: FilePath | ||||
|      ,appDir     :: FilePath | ||||
|      ,appOpts    :: [Opt] | ||||
|      ,appArgs    :: [String] | ||||
|      ,appJournal :: Journal | ||||
|      ,appStatic  :: Static | ||||
|      } | ||||
| 
 | ||||
| mkYesod "HledgerWebApp" [$parseRoutes| | ||||
| /             IndexPage            GET | ||||
| /style.css    StyleCss             GET | ||||
| /journalonly  JournalOnlyPage      GET POST | ||||
| /registeronly RegisterOnlyPage     GET | ||||
| /accounts     AccountsPage         GET | ||||
| /journal      AccountsJournalPage  GET POST | ||||
| /register     AccountsRegisterPage GET POST | ||||
| /static          StaticR           Static appStatic | ||||
| /                IndexR            GET | ||||
| /journalonly     JournalOnlyR      GET POST | ||||
| /registeronly    RegisterOnlyR     GET | ||||
| /accounts        AccountsOnlyR     GET | ||||
| /journal         JournalR          GET POST | ||||
| /register        RegisterR         GET POST | ||||
| |] | ||||
| 
 | ||||
| style_css       = StaticRoute ["style.css"] | ||||
| hledger_js      = StaticRoute ["hledger.js"] | ||||
| jquery_js       = StaticRoute ["jquery.js"] | ||||
| dhtmlxcommon_js = StaticRoute ["dhtmlxcommon.js"] | ||||
| dhtmlxcombo_js  = StaticRoute ["dhtmlxcombo.js"] | ||||
| 
 | ||||
| instance Yesod HledgerWebApp where approot = appRoot | ||||
| 
 | ||||
| defaultpage = AccountsJournalPage | ||||
| defaultroute = JournalR | ||||
| 
 | ||||
| -- | A bundle of useful data passed to templates. | ||||
| data TemplateData = TD { | ||||
| @ -71,7 +79,7 @@ data TemplateData = TD { | ||||
|     } | ||||
| 
 | ||||
| mktd = TD { | ||||
|       here = IndexPage | ||||
|       here = IndexR | ||||
|      ,title = "hledger" | ||||
|      ,msg = Nothing | ||||
|      ,a = "" | ||||
| @ -83,8 +91,9 @@ mktd = TD { | ||||
| -- | The web command. | ||||
| web :: [Opt] -> [String] -> Journal -> IO () | ||||
| web opts args j = do | ||||
|   let baseurl = fromMaybe defbaseurl $ baseUrlFromOpts opts | ||||
|   let host    = defhost | ||||
|       port    = fromMaybe defport $ portFromOpts opts | ||||
|       baseurl = fromMaybe (printf "http://%s:%d" host port) $ baseUrlFromOpts opts | ||||
|   unless (Debug `elem` opts) $ forkIO (browser baseurl) >> return () | ||||
|   server baseurl port opts args j | ||||
| 
 | ||||
| @ -98,10 +107,11 @@ browser baseurl = do | ||||
| server :: String -> Int -> [Opt] -> [String] -> Journal -> IO () | ||||
| server baseurl port opts args j = do | ||||
|     printf "starting web server on port %d with base url %s\n" port baseurl | ||||
|     fp <- getDataFileName "web" | ||||
|     dir <- getDataFileName "web" | ||||
|     let app = HledgerWebApp{ | ||||
|                appRoot=baseurl | ||||
|               ,appWebdir=fp | ||||
|               ,appDir=dir | ||||
|               ,appStatic=fileLookupDir (dir </> "static") $ typeByExt -- ++[("hamlet","text/plain")] | ||||
|               ,appOpts=opts | ||||
|               ,appArgs=args | ||||
|               ,appJournal=j | ||||
| @ -156,22 +166,14 @@ getHandlerParameters = do | ||||
| ---------------------------------------------------------------------- | ||||
| -- handlers & templates | ||||
| 
 | ||||
| getStyleCss :: Handler HledgerWebApp () | ||||
| getStyleCss = do | ||||
|     app <- getYesod | ||||
|     let dir = appWebdir app | ||||
|     sendFile "text/css" $ dir </> "style.css" | ||||
| 
 | ||||
| ---------------------------------------------------------------------- | ||||
| 
 | ||||
| getIndexPage :: Handler HledgerWebApp () | ||||
| getIndexPage = redirect RedirectTemporary defaultpage | ||||
| getIndexR :: Handler HledgerWebApp () | ||||
| getIndexR = redirect RedirectTemporary defaultroute | ||||
| 
 | ||||
| ---------------------------------------------------------------------- | ||||
| 
 | ||||
| -- | A combined accounts and journal view. | ||||
| getAccountsJournalPage :: Handler HledgerWebApp RepHtml | ||||
| getAccountsJournalPage = do | ||||
| getJournalR :: Handler HledgerWebApp RepHtml | ||||
| getJournalR = do | ||||
|   (a, p, opts, fspec, j, msg, here) <- getHandlerParameters | ||||
|   today <- liftIO getCurrentDay | ||||
|   -- app <- getYesod | ||||
| @ -183,11 +185,10 @@ getAccountsJournalPage = do | ||||
|       td = mktd{here=here, title="hledger", msg=msg, a=a, p=p, j=j, today=today} | ||||
|       editform' = editform td $ jtext j | ||||
|   hamletToRepHtml $ pageLayout td [$hamlet| | ||||
| ^scripts^ | ||||
| %div.ledger | ||||
|  %div.accounts!style=float:left;  ^br^ | ||||
|  ^navlinks.td^ | ||||
|  ^addform^ | ||||
|  ^addform.td^ | ||||
|  ^editform'^ | ||||
|  ^importform^ | ||||
|  %div#transactions.journal | ||||
| @ -195,14 +196,14 @@ getAccountsJournalPage = do | ||||
|   ^jr^ | ||||
| |] | ||||
| 
 | ||||
| postAccountsJournalPage :: Handler HledgerWebApp RepPlain | ||||
| postAccountsJournalPage = postJournalOnlyPage | ||||
| postJournalR :: Handler HledgerWebApp RepPlain | ||||
| postJournalR = postJournalOnlyR | ||||
| 
 | ||||
| ---------------------------------------------------------------------- | ||||
| 
 | ||||
| -- | A combined accounts and register view. | ||||
| getAccountsRegisterPage :: Handler HledgerWebApp RepHtml | ||||
| getAccountsRegisterPage = do | ||||
| getRegisterR :: Handler HledgerWebApp RepHtml | ||||
| getRegisterR = do | ||||
|   (a, p, opts, fspec, j, msg, here) <- getHandlerParameters | ||||
|   today <- liftIO getCurrentDay | ||||
|   -- app <- getYesod | ||||
| @ -215,11 +216,10 @@ getAccountsRegisterPage = do | ||||
|       td = mktd{here=here, title="hledger", msg=msg, a=a, p=p, j=j, today=today} | ||||
|       editform' = editform td $ jtext j | ||||
|   hamletToRepHtml $ pageLayout td [$hamlet| | ||||
| ^scripts^ | ||||
| %div.ledger | ||||
|  %div.accounts!style=float:left;  ^br^ | ||||
|  ^navlinks.td^ | ||||
|  ^addform^ | ||||
|  ^addform.td^ | ||||
|  ^editform'^ | ||||
|  ^importform^ | ||||
|  %div#transactions.register | ||||
| @ -227,14 +227,14 @@ getAccountsRegisterPage = do | ||||
|   ^rr^ | ||||
| |] | ||||
| 
 | ||||
| postAccountsRegisterPage :: Handler HledgerWebApp RepPlain | ||||
| postAccountsRegisterPage = postJournalOnlyPage | ||||
| postRegisterR :: Handler HledgerWebApp RepPlain | ||||
| postRegisterR = postJournalOnlyR | ||||
| 
 | ||||
| ---------------------------------------------------------------------- | ||||
| 
 | ||||
| -- | A simple accounts and balances view like hledger balance. | ||||
| getAccountsPage :: Handler HledgerWebApp RepHtml | ||||
| getAccountsPage = do | ||||
| getAccountsOnlyR :: Handler HledgerWebApp RepHtml | ||||
| getAccountsOnlyR = do | ||||
|   (a, p, opts, fspec, j, msg, here) <- getHandlerParameters | ||||
|   today <- liftIO getCurrentDay | ||||
|   let td = mktd{here=here, title="hledger", msg=msg, a=a, p=p, j=j, today=today} | ||||
| @ -308,8 +308,8 @@ isAccountRegex s = take 1 s == "^" && (take 5 $ reverse s) == ")$|:(" | ||||
| ---------------------------------------------------------------------- | ||||
| 
 | ||||
| -- | A basic journal view, like hledger print, with editing. | ||||
| getJournalOnlyPage :: Handler HledgerWebApp RepHtml | ||||
| getJournalOnlyPage = do | ||||
| getJournalOnlyR :: Handler HledgerWebApp RepHtml | ||||
| getJournalOnlyR = do | ||||
|   (a, p, opts, fspec, j, msg, here) <- getHandlerParameters | ||||
|   today <- liftIO getCurrentDay | ||||
|   let td = mktd{here=here, title="hledger", msg=msg, a=a, p=p, j=j, today=today} | ||||
| @ -317,12 +317,11 @@ getJournalOnlyPage = do | ||||
|       txns = journalReportAsHtml opts td $ journalReport opts fspec j | ||||
|   hamletToRepHtml $ pageLayout td [$hamlet| | ||||
| %div.journal | ||||
|  ^scripts^ | ||||
|  %div.nav2 | ||||
|   %a#addformlink!href!onclick="return addformToggle()" add one transaction | ||||
|   \ | $ | ||||
|   %a#editformlink!href!onclick="return editformToggle()" edit the whole journal | ||||
|  ^addform^ | ||||
|  ^addform.td^ | ||||
|  ^editform'^ | ||||
|  #transactions ^txns^ | ||||
| |] | ||||
| @ -346,8 +345,24 @@ journalReportAsHtml _ td items = [$hamlet| | ||||
|        evenodd = if even n then "even" else "odd" | ||||
|        txn = trimnl $ showTransaction t where trimnl = reverse . dropWhile (=='\n') . reverse | ||||
| 
 | ||||
| addform :: Hamlet HledgerWebAppRoute | ||||
| addform = [$hamlet| | ||||
| addform :: TemplateData -> Hamlet HledgerWebAppRoute | ||||
| addform td = [$hamlet| | ||||
| %script!type=text/javascript | ||||
|  $$(document).ready(function() { | ||||
|     /* dhtmlxcombo setup */ | ||||
|     window.dhx_globalImgPath="../static/images/"; | ||||
|     var desccombo  = new dhtmlXCombo("description"); | ||||
|     var acct1combo = new dhtmlXCombo("account1"); | ||||
|     var acct2combo = new dhtmlXCombo("account2"); | ||||
|     desccombo.enableFilteringMode(true); | ||||
|     acct1combo.enableFilteringMode(true); | ||||
|     acct2combo.enableFilteringMode(true); | ||||
|     desccombo.setSize(300); | ||||
|     acct1combo.setSize(300); | ||||
|     acct2combo.setSize(300); | ||||
|     /* desccombo.enableOptionAutoHeight(true, 20); */ | ||||
|     /* desccombo.setOptionHeight(200); */ | ||||
|  }); | ||||
| %form#addform!method=POST!style=display:none; | ||||
|   %table.form | ||||
|    %tr | ||||
| @ -361,7 +376,10 @@ addform = [$hamlet| | ||||
|        %td!style=padding-left:1em; | ||||
|         Description: | ||||
|        %td | ||||
|         %input.textinput!size=35!name=description!value=$desc$ | ||||
|         %select!id=description!name=description | ||||
|          %option | ||||
|          $forall descriptions d | ||||
|           %option!value=$d$ $d$ | ||||
|       %tr.helprow | ||||
|        %td | ||||
|        %td | ||||
| @ -369,8 +387,7 @@ addform = [$hamlet| | ||||
|        %td | ||||
|        %td | ||||
|         .help $deschelp$ | ||||
|    ^transactionfields1^ | ||||
|    ^transactionfields2^ | ||||
|    ^postingsfields.td^ | ||||
|    %tr#addbuttonrow | ||||
|     %td!colspan=4 | ||||
|      %input!type=hidden!name=action!value=add | ||||
| @ -378,20 +395,29 @@ addform = [$hamlet| | ||||
| |] | ||||
|  where | ||||
|   -- datehelplink = helplink "dates" "..." | ||||
|   datehelp = "eg: 7/20, 2010/1/1, " | ||||
|   datehelp = "eg: 2010/7/20" | ||||
|   deschelp = "eg: supermarket (optional)" | ||||
|   date = "today" | ||||
|   desc = "" | ||||
|   transactionfields1 = transactionfields 1 | ||||
|   transactionfields2 = transactionfields 2 | ||||
|   descriptions = sort $ nub $ map tdescription $ jtxns $ j td | ||||
| 
 | ||||
| transactionfields :: Int -> Hamlet HledgerWebAppRoute | ||||
| transactionfields n = [$hamlet| | ||||
| postingsfields :: TemplateData -> Hamlet HledgerWebAppRoute | ||||
| postingsfields td = [$hamlet| | ||||
|  ^p1^ | ||||
|  ^p2^ | ||||
| |] | ||||
|   where | ||||
|     p1 = postingfields td 1 | ||||
|     p2 = postingfields td 2 | ||||
| 
 | ||||
| postingfields :: TemplateData -> Int -> Hamlet HledgerWebAppRoute | ||||
| postingfields td n = [$hamlet| | ||||
|  %tr#postingrow | ||||
|   %td!align=right | ||||
|    $label$: | ||||
|   %td!align=right $acctlabel$: | ||||
|   %td | ||||
|    %input.textinput!size=35!name=$acctvar$!value=$acct$ | ||||
|    %select!id=$acctvar$!name=$acctvar$ | ||||
|     %option | ||||
|     $forall acctnames a | ||||
|      %option!value=$a$ $a$ | ||||
|   ^amtfield^ | ||||
|  %tr.helprow | ||||
|   %td | ||||
| @ -402,24 +428,26 @@ transactionfields n = [$hamlet| | ||||
|    .help $amthelp$ | ||||
| |] | ||||
|  where | ||||
|   label | n == 1    = "To account" | ||||
|         | otherwise = "From account" | ||||
|   accthelp | n == 1    = "eg: expenses:food" | ||||
|            | otherwise = "eg: assets:bank:checking" | ||||
|   amtfield | n == 1 = [$hamlet| | ||||
|   numbered = (++ show n) | ||||
|   acctvar = numbered "account" | ||||
|   amtvar = numbered "amount" | ||||
|   acctnames = sort $ journalAccountNamesUsed $ j td | ||||
|   (acctlabel, accthelp, amtfield, amthelp) | ||||
|        | n == 1     = ("To account" | ||||
|                      ,"eg: expenses:food" | ||||
|                      ,[$hamlet| | ||||
|                        %td!style=padding-left:1em; | ||||
|                         Amount: | ||||
|                        %td | ||||
|                         %input.textinput!size=15!name=$amtvar$!value=$amt$ | ||||
|                         %input.textinput!size=15!name=$amtvar$!value="" | ||||
|                        |] | ||||
|            | otherwise = nulltemplate | ||||
|   amthelp | n == 1    = "eg: 5, $6, €7.01" | ||||
|           | otherwise = "" | ||||
|   acct = "" | ||||
|   amt = "" | ||||
|   numbered = (++ show n) | ||||
|   acctvar = numbered "accountname" | ||||
|   amtvar = numbered "amount" | ||||
|                      ,"eg: $6" | ||||
|                      ) | ||||
|        | otherwise = ("From account" | ||||
|                      ,"eg: assets:bank:checking" | ||||
|                      ,nulltemplate | ||||
|                      ,"" | ||||
|                      ) | ||||
| 
 | ||||
| editform :: TemplateData -> String -> Hamlet HledgerWebAppRoute | ||||
| editform _ content = [$hamlet| | ||||
| @ -455,142 +483,8 @@ importform = [$hamlet| | ||||
|      %a!href!onclick="return importformToggle()" cancel | ||||
| |] | ||||
| 
 | ||||
| scripts = [$hamlet| | ||||
| <script type="text/javascript"> | ||||
| 
 | ||||
|  function filterformToggle() { | ||||
|  var a = document.getElementById('addform'); | ||||
|  var e = document.getElementById('editform'); | ||||
|  var f = document.getElementById('filterform'); | ||||
|  var i = document.getElementById('importform'); | ||||
|  var t = document.getElementById('transactions'); | ||||
|  var alink = document.getElementById('addformlink'); | ||||
|  var elink = document.getElementById('editformlink'); | ||||
|  var flink = document.getElementById('filterformlink'); | ||||
|  var ilink = document.getElementById('importformlink'); | ||||
|  var jlink = document.getElementById('journallink'); | ||||
|  var rlink = document.getElementById('registerlink'); | ||||
| 
 | ||||
|   if (f.style.display == 'none') { | ||||
|    flink.style['font-weight'] = 'bold'; | ||||
|    f.style.display = 'block'; | ||||
|   } else { | ||||
|    flink.style['font-weight'] = 'normal'; | ||||
|    f.style.display = 'none'; | ||||
|   } | ||||
|   return false; | ||||
|  } | ||||
| 
 | ||||
|  function addformToggle() { | ||||
|  var a = document.getElementById('addform'); | ||||
|  var e = document.getElementById('editform'); | ||||
|  var f = document.getElementById('filterform'); | ||||
|  var i = document.getElementById('importform'); | ||||
|  var t = document.getElementById('transactions'); | ||||
|  var alink = document.getElementById('addformlink'); | ||||
|  var elink = document.getElementById('editformlink'); | ||||
|  var flink = document.getElementById('filterformlink'); | ||||
|  var ilink = document.getElementById('importformlink'); | ||||
|  var jlink = document.getElementById('journallink'); | ||||
|  var rlink = document.getElementById('registerlink'); | ||||
| 
 | ||||
|   if (a.style.display == 'none') { | ||||
|    alink.style['font-weight'] = 'bold'; | ||||
|    elink.style['font-weight'] = 'normal'; | ||||
|    ilink.style['font-weight'] = 'normal'; | ||||
|    jlink.style['font-weight'] = 'normal'; | ||||
|    rlink.style['font-weight'] = 'normal'; | ||||
|    a.style.display = 'block'; | ||||
|    e.style.display = 'none'; | ||||
|    i.style.display = 'none'; | ||||
|    t.style.display = 'none'; | ||||
|   } else { | ||||
|    alink.style['font-weight'] = 'normal'; | ||||
|    elink.style['font-weight'] = 'normal'; | ||||
|    ilink.style['font-weight'] = 'normal'; | ||||
|    a.style.display = 'none'; | ||||
|    e.style.display = 'none'; | ||||
|    i.style.display = 'none'; | ||||
|    t.style.display = 'block'; | ||||
|   } | ||||
|   return false; | ||||
|  } | ||||
| 
 | ||||
|  function editformToggle() { | ||||
|  var a = document.getElementById('addform'); | ||||
|  var e = document.getElementById('editform'); | ||||
|  var f = document.getElementById('filterform'); | ||||
|  var i = document.getElementById('importform'); | ||||
|  var t = document.getElementById('transactions'); | ||||
|  var alink = document.getElementById('addformlink'); | ||||
|  var elink = document.getElementById('editformlink'); | ||||
|  var flink = document.getElementById('filterformlink'); | ||||
|  var ilink = document.getElementById('importformlink'); | ||||
|  var jlink = document.getElementById('journallink'); | ||||
|  var rlink = document.getElementById('registerlink'); | ||||
| 
 | ||||
|   if (e.style.display == 'none') { | ||||
|    alink.style['font-weight'] = 'normal'; | ||||
|    elink.style['font-weight'] = 'bold'; | ||||
|    ilink.style['font-weight'] = 'normal'; | ||||
|    jlink.style['font-weight'] = 'normal'; | ||||
|    rlink.style['font-weight'] = 'normal'; | ||||
|    a.style.display = 'none'; | ||||
|    e.style.display = 'block'; | ||||
|    i.style.display = 'none'; | ||||
|    t.style.display = 'none'; | ||||
|   } else { | ||||
|    alink.style['font-weight'] = 'normal'; | ||||
|    elink.style['font-weight'] = 'normal'; | ||||
|    ilink.style['font-weight'] = 'normal'; | ||||
|    a.style.display = 'none'; | ||||
|    e.style.display = 'none'; | ||||
|    i.style.display = 'none'; | ||||
|    t.style.display = 'block'; | ||||
|   } | ||||
|   return false; | ||||
|  } | ||||
| 
 | ||||
|  function importformToggle() { | ||||
|  var a = document.getElementById('addform'); | ||||
|  var e = document.getElementById('editform'); | ||||
|  var f = document.getElementById('filterform'); | ||||
|  var i = document.getElementById('importform'); | ||||
|  var t = document.getElementById('transactions'); | ||||
|  var alink = document.getElementById('addformlink'); | ||||
|  var elink = document.getElementById('editformlink'); | ||||
|  var flink = document.getElementById('filterformlink'); | ||||
|  var ilink = document.getElementById('importformlink'); | ||||
|  var jlink = document.getElementById('journallink'); | ||||
|  var rlink = document.getElementById('registerlink'); | ||||
| 
 | ||||
|   if (i.style.display == 'none') { | ||||
|    alink.style['font-weight'] = 'normal'; | ||||
|    elink.style['font-weight'] = 'normal'; | ||||
|    ilink.style['font-weight'] = 'bold'; | ||||
|    jlink.style['font-weight'] = 'normal'; | ||||
|    rlink.style['font-weight'] = 'normal'; | ||||
|    a.style.display = 'none'; | ||||
|    e.style.display = 'none'; | ||||
|    i.style.display = 'block'; | ||||
|    t.style.display = 'none'; | ||||
|   } else { | ||||
|    alink.style['font-weight'] = 'normal'; | ||||
|    elink.style['font-weight'] = 'normal'; | ||||
|    ilink.style['font-weight'] = 'normal'; | ||||
|    a.style.display = 'none'; | ||||
|    e.style.display = 'none'; | ||||
|    i.style.display = 'none'; | ||||
|    t.style.display = 'block'; | ||||
|   } | ||||
|   return false; | ||||
|  } | ||||
| 
 | ||||
| </script> | ||||
| |] | ||||
| 
 | ||||
| postJournalOnlyPage :: Handler HledgerWebApp RepPlain | ||||
| postJournalOnlyPage = do | ||||
| postJournalOnlyR :: Handler HledgerWebApp RepPlain | ||||
| postJournalOnlyR = do | ||||
|   action <- runFormPost' $ maybeStringInput "action" | ||||
|   case action of Just "edit"   -> postEditForm | ||||
|                  Just "import" -> postImportForm | ||||
| @ -606,9 +500,9 @@ postAddForm = do | ||||
|     $ (,,,,,) | ||||
|     <$> maybeStringInput "date" | ||||
|     <*> maybeStringInput "description" | ||||
|     <*> maybeStringInput "accountname1" | ||||
|     <*> maybeStringInput "account1" | ||||
|     <*> maybeStringInput "amount1" | ||||
|     <*> maybeStringInput "accountname2" | ||||
|     <*> maybeStringInput "account2" | ||||
|     <*> maybeStringInput "amount2" | ||||
|   -- supply defaults and parse date and amounts, or get errors. | ||||
|   let dateE = maybe (Left "date required") (either (\e -> Left $ showDateParseError e) Right . fixSmartDateStrEither today) dateM | ||||
| @ -643,14 +537,14 @@ postAddForm = do | ||||
|    Left errs -> do | ||||
|     -- save current form values in session | ||||
|     setMessage $ string $ intercalate "; " errs | ||||
|     redirect RedirectTemporary AccountsRegisterPage | ||||
|     redirect RedirectTemporary RegisterR | ||||
| 
 | ||||
|    Right t -> do | ||||
|     let t' = txnTieKnot t -- XXX move into balanceTransaction | ||||
|     j <- liftIO $ fromJust `fmap` getValue "hledger" "journal" | ||||
|     liftIO $ journalAddTransaction j opts t' | ||||
|     setMessage $ string $ printf "Added transaction:\n%s" (show t') | ||||
|     redirect RedirectTemporary AccountsRegisterPage | ||||
|     redirect RedirectTemporary RegisterR | ||||
| 
 | ||||
| -- | Handle a journal edit form post. | ||||
| postEditForm :: Handler HledgerWebApp RepPlain | ||||
| @ -663,7 +557,7 @@ postEditForm = do | ||||
|    Left errs -> do | ||||
|     -- XXX should save current form values in session | ||||
|     setMessage $ string errs | ||||
|     redirect RedirectTemporary AccountsJournalPage | ||||
|     redirect RedirectTemporary JournalR | ||||
| 
 | ||||
|    Right t' -> do | ||||
|     -- try to avoid unnecessary backups or saving invalid data | ||||
| @ -677,24 +571,24 @@ postEditForm = do | ||||
|     if not changed | ||||
|      then do | ||||
|        setMessage $ string $ "No change" | ||||
|        redirect RedirectTemporary AccountsJournalPage | ||||
|        redirect RedirectTemporary JournalR | ||||
|      else do | ||||
|       jE <- liftIO $ journalFromPathAndString Nothing f tnew | ||||
|       either | ||||
|        (\e -> do | ||||
|           setMessage $ string e | ||||
|           redirect RedirectTemporary AccountsJournalPage) | ||||
|           redirect RedirectTemporary JournalR) | ||||
|        (const $ do | ||||
|           liftIO $ writeFileWithBackup f tnew | ||||
|           setMessage $ string $ printf "Saved journal %s\n" (show f) | ||||
|           redirect RedirectTemporary AccountsJournalPage) | ||||
|           redirect RedirectTemporary JournalR) | ||||
|        jE | ||||
| 
 | ||||
| -- | Handle an import page post. | ||||
| postImportForm :: Handler HledgerWebApp RepPlain | ||||
| postImportForm = do | ||||
|   setMessage $ string $ "can't handle file upload yet" | ||||
|   redirect RedirectTemporary AccountsJournalPage | ||||
|   redirect RedirectTemporary JournalR | ||||
|   -- -- get form input values, or basic validation errors. E means an Either value. | ||||
|   -- fileM <- runFormPost' $ maybeFileInput "file" | ||||
|   -- let fileE = maybe (Left "No file provided") Right fileM | ||||
| @ -702,17 +596,17 @@ postImportForm = do | ||||
|   -- case fileE of | ||||
|   --  Left errs -> do | ||||
|   --   setMessage $ string errs | ||||
|   --   redirect RedirectTemporary AccountsJournalPage | ||||
|   --   redirect RedirectTemporary JournalR | ||||
| 
 | ||||
|   --  Right s -> do | ||||
|   --    setMessage $ string $ s | ||||
|   --    redirect RedirectTemporary AccountsJournalPage | ||||
|   --    redirect RedirectTemporary JournalR | ||||
| 
 | ||||
| ---------------------------------------------------------------------- | ||||
| 
 | ||||
| -- | A simple postings view like hledger register. | ||||
| getRegisterOnlyPage :: Handler HledgerWebApp RepHtml | ||||
| getRegisterOnlyPage = do | ||||
| getRegisterOnlyR :: Handler HledgerWebApp RepHtml | ||||
| getRegisterOnlyR = do | ||||
|   (a, p, opts, fspec, j, msg, here) <- getHandlerParameters | ||||
|   today <- liftIO getCurrentDay | ||||
|   let td = mktd{here=here, title="hledger", msg=msg, a=a, p=p, j=j, today=today} | ||||
| @ -753,8 +647,8 @@ mixedAmountAsHtml b = preEscapedString $ addclass $ intercalate "<br>" $ lines $ | ||||
| ---------------------------------------------------------------------- | ||||
| 
 | ||||
| -- | A standalone journal edit form page. | ||||
| getEditPage :: Handler HledgerWebApp RepHtml | ||||
| getEditPage = do | ||||
| getEditR :: Handler HledgerWebApp RepHtml | ||||
| getEditR = do | ||||
|   (a, p, _, _, _, msg, here) <- getHandlerParameters | ||||
|   today <- liftIO getCurrentDay | ||||
|   -- reload journal's text without parsing, if changed     -- XXX are we doing this right ? | ||||
| @ -774,7 +668,11 @@ pageLayout td@TD{title=title, msg=msg} content = [$hamlet| | ||||
|  %head | ||||
|   %title $title$ | ||||
|   %meta!http-equiv=Content-Type!content=$metacontent$ | ||||
|   %link!rel=stylesheet!type=text/css!href=@StyleCss@!media=all | ||||
|   %script!type=text/javascript!src=@StaticR.jquery_js@ | ||||
|   %script!type=text/javascript!src=@StaticR.dhtmlxcommon_js@ | ||||
|   %script!type=text/javascript!src=@StaticR.dhtmlxcombo_js@ | ||||
|   %script!type=text/javascript!src=@StaticR.hledger_js@ | ||||
|   %link!rel=stylesheet!type=text/css!media=all!href=@StaticR.style_css@ | ||||
|  %body | ||||
|   ^navbar.td^ | ||||
|   #messages $m$ | ||||
| @ -787,7 +685,7 @@ pageLayout td@TD{title=title, msg=msg} content = [$hamlet| | ||||
| navbar :: TemplateData -> Hamlet HledgerWebAppRoute | ||||
| navbar TD{p=p,j=j,today=today} = [$hamlet| | ||||
|  #navbar | ||||
|   %a.topleftlink!href=$hledgerurl$ | ||||
|   %a.topleftlink!href=$hledgerorgurl$ | ||||
|    hledger | ||||
|    <br /> | ||||
|    $version$ | ||||
| @ -817,8 +715,8 @@ navlinks td = [$hamlet| | ||||
| |] | ||||
| --  \ | $ | ||||
|  where | ||||
|    accountsjournallink  = navlink td "journal" AccountsJournalPage | ||||
|    accountsregisterlink = navlink td "register" AccountsRegisterPage | ||||
|    accountsjournallink  = navlink td "journal" JournalR | ||||
|    accountsregisterlink = navlink td "register" RegisterR | ||||
| 
 | ||||
| navlink :: TemplateData -> String -> HledgerWebAppRoute -> Hamlet HledgerWebAppRoute | ||||
| navlink TD{here=here,a=a,p=p} s dest = [$hamlet|%a#$s$link.$style$!href=@?u@ $s$|] | ||||
|  | ||||
							
								
								
									
										135
									
								
								data/web/static/hledger.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								data/web/static/hledger.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,135 @@ | ||||
| /* hledger web ui javascripts */ | ||||
| /* depends on jquery, other support libs, and additional js inserted inline */ | ||||
| 
 | ||||
| $(document).ready(function() { | ||||
| 
 | ||||
| 
 | ||||
| }); | ||||
| 
 | ||||
| function filterformToggle() { | ||||
|  var a = document.getElementById('addform'); | ||||
|  var e = document.getElementById('editform'); | ||||
|  var f = document.getElementById('filterform'); | ||||
|  var i = document.getElementById('importform'); | ||||
|  var t = document.getElementById('transactions'); | ||||
|  var alink = document.getElementById('addformlink'); | ||||
|  var elink = document.getElementById('editformlink'); | ||||
|  var flink = document.getElementById('filterformlink'); | ||||
|  var ilink = document.getElementById('importformlink'); | ||||
|  var jlink = document.getElementById('journallink'); | ||||
|  var rlink = document.getElementById('registerlink'); | ||||
| 
 | ||||
|  if (f.style.display == 'none') { | ||||
|   flink.style['font-weight'] = 'bold'; | ||||
|   f.style.display = 'block'; | ||||
|  } else { | ||||
|   flink.style['font-weight'] = 'normal'; | ||||
|   f.style.display = 'none'; | ||||
|  } | ||||
|  return false; | ||||
| } | ||||
| 
 | ||||
| function addformToggle() { | ||||
|  var a = document.getElementById('addform'); | ||||
|  var e = document.getElementById('editform'); | ||||
|  var f = document.getElementById('filterform'); | ||||
|  var i = document.getElementById('importform'); | ||||
|  var t = document.getElementById('transactions'); | ||||
|  var alink = document.getElementById('addformlink'); | ||||
|  var elink = document.getElementById('editformlink'); | ||||
|  var flink = document.getElementById('filterformlink'); | ||||
|  var ilink = document.getElementById('importformlink'); | ||||
|  var jlink = document.getElementById('journallink'); | ||||
|  var rlink = document.getElementById('registerlink'); | ||||
| 
 | ||||
|  if (a.style.display == 'none') { | ||||
|   alink.style['font-weight'] = 'bold'; | ||||
|   elink.style['font-weight'] = 'normal'; | ||||
|   ilink.style['font-weight'] = 'normal'; | ||||
|   jlink.style['font-weight'] = 'normal'; | ||||
|   rlink.style['font-weight'] = 'normal'; | ||||
|   a.style.display = 'block'; | ||||
|   e.style.display = 'none'; | ||||
|   i.style.display = 'none'; | ||||
|   t.style.display = 'none'; | ||||
|  } else { | ||||
|   alink.style['font-weight'] = 'normal'; | ||||
|   elink.style['font-weight'] = 'normal'; | ||||
|   ilink.style['font-weight'] = 'normal'; | ||||
|   a.style.display = 'none'; | ||||
|   e.style.display = 'none'; | ||||
|   i.style.display = 'none'; | ||||
|   t.style.display = 'block'; | ||||
|  } | ||||
|  return false; | ||||
| } | ||||
| 
 | ||||
| function editformToggle() { | ||||
|  var a = document.getElementById('addform'); | ||||
|  var e = document.getElementById('editform'); | ||||
|  var f = document.getElementById('filterform'); | ||||
|  var i = document.getElementById('importform'); | ||||
|  var t = document.getElementById('transactions'); | ||||
|  var alink = document.getElementById('addformlink'); | ||||
|  var elink = document.getElementById('editformlink'); | ||||
|  var flink = document.getElementById('filterformlink'); | ||||
|  var ilink = document.getElementById('importformlink'); | ||||
|  var jlink = document.getElementById('journallink'); | ||||
|  var rlink = document.getElementById('registerlink'); | ||||
| 
 | ||||
|  if (e.style.display == 'none') { | ||||
|   alink.style['font-weight'] = 'normal'; | ||||
|   elink.style['font-weight'] = 'bold'; | ||||
|   ilink.style['font-weight'] = 'normal'; | ||||
|   jlink.style['font-weight'] = 'normal'; | ||||
|   rlink.style['font-weight'] = 'normal'; | ||||
|   a.style.display = 'none'; | ||||
|   e.style.display = 'block'; | ||||
|   i.style.display = 'none'; | ||||
|   t.style.display = 'none'; | ||||
|  } else { | ||||
|   alink.style['font-weight'] = 'normal'; | ||||
|   elink.style['font-weight'] = 'normal'; | ||||
|   ilink.style['font-weight'] = 'normal'; | ||||
|   a.style.display = 'none'; | ||||
|   e.style.display = 'none'; | ||||
|   i.style.display = 'none'; | ||||
|   t.style.display = 'block'; | ||||
|  } | ||||
|  return false; | ||||
| } | ||||
| 
 | ||||
| function importformToggle() { | ||||
|  var a = document.getElementById('addform'); | ||||
|  var e = document.getElementById('editform'); | ||||
|  var f = document.getElementById('filterform'); | ||||
|  var i = document.getElementById('importform'); | ||||
|  var t = document.getElementById('transactions'); | ||||
|  var alink = document.getElementById('addformlink'); | ||||
|  var elink = document.getElementById('editformlink'); | ||||
|  var flink = document.getElementById('filterformlink'); | ||||
|  var ilink = document.getElementById('importformlink'); | ||||
|  var jlink = document.getElementById('journallink'); | ||||
|  var rlink = document.getElementById('registerlink'); | ||||
| 
 | ||||
|  if (i.style.display == 'none') { | ||||
|   alink.style['font-weight'] = 'normal'; | ||||
|   elink.style['font-weight'] = 'normal'; | ||||
|   ilink.style['font-weight'] = 'bold'; | ||||
|   jlink.style['font-weight'] = 'normal'; | ||||
|   rlink.style['font-weight'] = 'normal'; | ||||
|   a.style.display = 'none'; | ||||
|   e.style.display = 'none'; | ||||
|   i.style.display = 'block'; | ||||
|   t.style.display = 'none'; | ||||
|  } else { | ||||
|   alink.style['font-weight'] = 'normal'; | ||||
|   elink.style['font-weight'] = 'normal'; | ||||
|   ilink.style['font-weight'] = 'normal'; | ||||
|   a.style.display = 'none'; | ||||
|   e.style.display = 'none'; | ||||
|   i.style.display = 'none'; | ||||
|   t.style.display = 'block'; | ||||
|  } | ||||
|  return false; | ||||
| } | ||||
| @ -4,7 +4,7 @@ | ||||
| /* overspecified for cross-browser robustness */ | ||||
| body                        { font-family:helvetica,arial,"sans serif"; } | ||||
| pre                         { font-family:courier,"courier new",monospace; } | ||||
| #addform input.textinput    { font-family:courier,"courier new",monospace; font-size:small; } | ||||
| input.textinput, .dhx_combo_input, .dhx_combo_list    { font-size:small; } | ||||
| #editform textarea          { font-family:courier,"courier new",monospace; font-size:small; } | ||||
| .nav2                       { font-size:small; } | ||||
| #filterform                 { font-size:small; } | ||||
| @ -75,9 +75,152 @@ table.registerreport        { border-spacing:0; } | ||||
| .firstposting td            { } | ||||
| .registerreport .odd        { background-color:#f0f0f0; } | ||||
| 
 | ||||
| #addform input.textinput    { background-color:#eee; padding:4px; } | ||||
| #addform input.textinput, #addform .dhx_combo_input, .dhx_combo_list   { background-color:#eee; padding:4px; } | ||||
| #addform table              { } | ||||
| #addform #addbuttonrow      { text-align:right; } | ||||
| /* #editform                   { width:95%; } */ | ||||
| #editform textarea          { width:100%; background-color:#eee; padding:4px; } | ||||
| #filterform table           { border-spacing:0; padding-left:1em; } | ||||
| 
 | ||||
| 
 | ||||
| /*------------------------------------------------------------------------------------------*/ | ||||
| 
 | ||||
| .dhx_combo_input{ | ||||
| /* color:#333333; */ | ||||
| /* font-family: Arial; */ | ||||
| /* font-size: 9pt; */ | ||||
| /* border:0px; */ | ||||
| /* padding:2px 2px 2px 2px; */ | ||||
| /* position:absolute; */ | ||||
| /* top:0px; */ | ||||
| } | ||||
| 
 | ||||
| /* table {border:thin solid red} */ | ||||
| /* div {border:thin solid yellow} */ | ||||
| 
 | ||||
| .dhx_combo_box{ | ||||
|     position:relative; | ||||
|     display:inline-block; | ||||
|     /* text-align:left; */ | ||||
|     /* height:20px; */ | ||||
|     /* _height:22px; */ | ||||
|     /* overflow:hidden; */ | ||||
|     /* background-color: white; */ | ||||
| } | ||||
| 
 | ||||
| .dhx_combo_list{ | ||||
|     position:absolute; | ||||
|     z-index:230; | ||||
|     overflow-y:auto; | ||||
|     overflow-x:hidden; | ||||
|     white-space:nowrap; | ||||
|     border:1px solid black; | ||||
|     height:50%; | ||||
|     /* background-color: white; */ | ||||
| } | ||||
| 
 | ||||
| .dhx_combo_list div{ | ||||
|     cursor:default; | ||||
|     padding:2px 2px 2px 2px; | ||||
| } | ||||
| 
 | ||||
| .dhx_selected_option{ | ||||
|     background-color:navy; | ||||
|     color:white; | ||||
| } | ||||
| 
 | ||||
| .dhx_combo_img{ | ||||
|     /* display:none; */ | ||||
|     width:18px; | ||||
|     height:20px; | ||||
|     position:absolute; | ||||
|     top:12px; | ||||
|     right:-10px; | ||||
| } | ||||
| 
 | ||||
| .dhx_combo_option_img{ | ||||
| 	position:relative; | ||||
| 	top:1px; | ||||
| 	margin:0px; | ||||
| 	margin-left:2px; | ||||
| 	left:0px; | ||||
| 	width:18px; height:18px; | ||||
| } | ||||
| 
 | ||||
| /* .combo_dhx_sel{ */ | ||||
| /* .dhx_selected_option{ */ | ||||
| /*    background-image: url("../static/images/bg_selection.gif") !important; */ | ||||
| /*    background-position: bottom; */ | ||||
| /*    background-repeat: repeat-x; */ | ||||
| /*    color:black; */ | ||||
| /* } */ | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| /* .dhx_combo_img_rtl{ */ | ||||
| /* 	position:absolute; */ | ||||
| /* 	top:0px; */ | ||||
| /* 	left:1px; */ | ||||
| /* 	width:17px; */ | ||||
| /* 	height:20px; */ | ||||
| /* } */ | ||||
| /* .dhx_combo_option_img_rtl{ */ | ||||
| /* 	float:right; */ | ||||
| /* 	margin-right :0px; */ | ||||
| /* 	width:18px; height:18px; */ | ||||
| /* } */ | ||||
| 
 | ||||
| /* .dhx_combo_list_rtl{ */ | ||||
| /* 	direction: rtl; */ | ||||
| /* 	unicode-bidi : bidi-override; */ | ||||
| /*    position:absolute; */ | ||||
| /*    z-index:230; */ | ||||
| /*    overflow-y:auto; */ | ||||
| /*    overflow-x:hidden; */ | ||||
| /*    border:1px solid black; */ | ||||
| /*    height:100px; */ | ||||
| /*    /\* font-family: Arial; *\/ */ | ||||
| /*    font-size: 9pt; */ | ||||
| /*    background-color: white; */ | ||||
| /* } */ | ||||
| /* .dhx_combo_list_rtl div{ */ | ||||
| /* 	direction: rtl; */ | ||||
| /* 	unicode-bidi : bidi-override; */ | ||||
| /* 	padding:2px 2px 2px 2px; */ | ||||
| /* } */ | ||||
| /* .dhx_combo_list_rtl div div{ */ | ||||
| /* 	float :right !important; */ | ||||
| /* 	cursor:default; */ | ||||
| /* } */ | ||||
| /* .dhx_combo_list_rtl div img{ */ | ||||
| /* 	float :right !important; */ | ||||
| /* } */ | ||||
| /* .dhx_combo_list_rtl div input{ */ | ||||
| /* 	float :right !important; */ | ||||
| /* } */ | ||||
| 
 | ||||
| /* .dhx_combo_box.dhx_skyblue{ */ | ||||
| /* 	border:1px solid #a4bed4; */ | ||||
| /* } */ | ||||
| /* .dhx_combo_box.dhx_skyblue .dhx_combo_input { */ | ||||
| /* 	font-family:Tahoma; */ | ||||
| /* 	font-size: 11px; */ | ||||
| /* 	padding:3px; */ | ||||
| /* } */ | ||||
| /* .dhx_combo_list.dhx_skyblue_list{ */ | ||||
| /* 	background-color: #eaf2fb; */ | ||||
| /* 	border:1px solid #a4bed4; */ | ||||
| /* 	font-family:Tahoma; */ | ||||
| /* 	font-size: 11px; */ | ||||
| /* } */ | ||||
| /* .dhx_combo_list.dhx_skyblue_list div{ */ | ||||
| /* 	cursor:default; */ | ||||
| /* 	padding:3px 4px; */ | ||||
| /* } */ | ||||
| /* .dhx_combo_list_rtl.dhx_skyblue_list{ */ | ||||
| /*    background-color: #eaf2fb; */ | ||||
| /* 	border:1px solid #a4bed4; */ | ||||
| /* 	font-family:Tahoma; */ | ||||
| /* 	font-size: 11px; */ | ||||
| /* } */ | ||||
| 
 | ||||
| @ -23,7 +23,12 @@ cabal-version:  >= 1.2 | ||||
| build-type:     Custom | ||||
| data-dir:       data | ||||
| data-files: | ||||
|                 web/style.css | ||||
|                 web/static/style.css | ||||
|                 web/static/hledger.js | ||||
|                 web/static/jquery.js | ||||
|                 web/static/dhtmlxcommon.js | ||||
|                 web/static/dhtmlxcombo.js | ||||
|                 web/static/images/combo_select.gif | ||||
| extra-tmp-files: | ||||
| extra-source-files: | ||||
|   README.rst | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user