Difference between revisions of "Yhc/Javascript/Programmers guide/EchoCPS demo source"

From HaskellWiki
Jump to navigation Jump to search
m (EchoCPS demo source moved to Yhc/Javascript/Programmers guide/EchoCPS demo source)
(Added more comments)
 
(One intermediate revision by the same user not shown)
Line 7: Line 7:
 
module EchoCPS where
 
module EchoCPS where
   
  +
-- This module contains useful functions to access properties of underlying
  +
-- Javascript objects in the type-agnostic manner: proceed with caution!
 
import UnsafeJS
 
import UnsafeJS
   
  +
-- This module contains functions to wrap expressions in CPS style
 
import CPS
 
import CPS
  +
  +
-- This module contains Roman-Decimal conversion funcitons
 
import Roman
 
import Roman
  +
  +
-- The modules under DOM are autogenerated from IDL
  +
-- The modules under CDOM are utilities to simplify the use of DOM facilities
 
import DOM.Level1.Dom
 
import DOM.Level1.Dom
 
import DOM.Level1.Html
 
import DOM.Level1.Html
Line 19: Line 27:
 
import DOM.Level1.HTMLDivElement
 
import DOM.Level1.HTMLDivElement
 
import DOM.Level1.HTMLInputElement
 
import DOM.Level1.HTMLInputElement
  +
  +
-- This module contains functions to obtain timestamps
 
import Debug.Profiling
 
import Debug.Profiling
   
  +
-- Line-mode output: create a <div> element, place a text in it,
  +
-- append to the parent element (document body). The mbb argument
  +
-- specifies whether the text will be inserted before a specific
  +
-- element (Just) or just appended to the end of the document (Nothing).
  +
-- The s argument is the string to output, and the c argument is
  +
-- the continuation.
   
 
putLine s mbb c = getHTMLDocument $ \doc ->
 
putLine s mbb c = getHTMLDocument $ \doc ->
 
documentBody doc $ \body ->
 
documentBody doc $ \body ->
  +
  +
-- body contains reference to the HTML document <body> element
  +
-- which is the parent of all <div>'s displaying the lines being output
  +
 
mkDiv doc $ \dv ->
 
mkDiv doc $ \dv ->
 
mkText doc s $ \tx ->
 
mkText doc s $ \tx ->
 
addChild tx dv $ \ch ->
 
addChild tx dv $ \ch ->
  +
  +
-- Functions whose names start with mk (mkDiv, mkText) are autogenerated
  +
-- wrappers over DOM methods which create elements bearing appropriate
  +
-- HTML tags. Above, a <div> and a #text elements were created, and the #text
  +
-- element was added to the <div> as a child.
  +
 
let iac = case mbb of
 
let iac = case mbb of
 
Nothing -> addChild dv
 
Nothing -> addChild dv
 
Just b -> insertChild b dv
 
Just b -> insertChild b dv
  +
  +
-- Based on the mbb argument, decision was made whether to add the child
  +
-- (previously created <div>) to the end of the document (addChild), or to
  +
-- insert the <div> before the specific element (insertChild).
  +
 
in iac body $ \ct ->
 
in iac body $ \ct ->
 
c ct
 
c ct
Line 36: Line 67:
 
main = getHTMLDocument $ \doc ->
 
main = getHTMLDocument $ \doc ->
 
documentBody doc $ \body ->
 
documentBody doc $ \body ->
  +
  +
-- The same sequence as above, to get the reference to the
  +
-- HTML document's <body> element.
  +
 
mkInput doc $ \inp ->
 
mkInput doc $ \inp ->
 
addChild inp body $ \_ ->
 
addChild inp body $ \_ ->
  +
  +
-- The <input> element was created and added to the <body> element
  +
 
set'id "input-echo" inp $ \_ ->
 
set'id "input-echo" inp $ \_ ->
  +
  +
-- The set' family of methods deal with setting nodes' properties.
  +
-- The above expression sets the id attribute of the <input> element
  +
-- to "input-echo", so in plain HTML this would be:
  +
-- <input id="input-echo">
  +
 
set'on "keypress" (inkey inp) inp $ \_ ->
 
set'on "keypress" (inkey inp) inp $ \_ ->
  +
  +
-- The set'on "keypress" is same as specifying
  +
-- <input id="input-echo" onkeypress="javascript:(inkey inp)">
  +
 
focus inp $ id
 
focus inp $ id
   
  +
-- The focus is a DOM method setting input focus on the element.
  +
-- The id call at the very end "closes" the CPS chain of function calls.
  +
  +
  +
-- A function to convert between Roman and Decimal presentations.
  +
-- It also features exception handling that prevents the program
  +
-- from crash if something wrong is entered. Input in error will be
  +
-- converted into an empty string. The fromRoman and toRoman functions
  +
-- call error in the situation when input cannot be processed.
 
romdec :: String -> (String, String)
 
romdec :: String -> (String, String)
   
Line 49: Line 106:
 
in (rom, dec)
 
in (rom, dec)
   
  +
  +
-- The "onkeypress" handler: it does all the job. Note the o argument:
  +
-- it holds reference to the element a handler is attached to. This forms
  +
-- a closure (which may not be very much desired for some browsers), but
  +
-- makes it extremely easy to distinguish between elements that cause
  +
-- the handler to fire.
 
inkey :: THTMLInputElement -> a -> Bool
 
inkey :: THTMLInputElement -> a -> Bool
 
 
 
inkey o e = unsafeGetProperty "keyCode" e $ \kcs ->
 
inkey o e = unsafeGetProperty "keyCode" e $ \kcs ->
 
unsafeToNum kcs $ \kci ->
 
unsafeToNum kcs $ \kci ->
  +
  +
-- Since there is no completed framework on event properties, we have
  +
-- to use the unsafe interface. The two lines above extract the "keyCode"
  +
-- property from the event received, and gets the numeric value of it.
  +
-- In Javascript, this is equivalent to
  +
-- new Number (e.keyCode)
  +
 
if kci == 13
 
if kci == 13
  +
  +
-- We are only interested in the Enter key whose code is 13.
  +
-- Other keys just edit the value in the <input> element, and do not
  +
-- need to be handled.
  +
 
then
 
then
 
get'value o $ \val ->
 
get'value o $ \val ->
 
unsafeToString val $ \v ->
 
unsafeToString val $ \v ->
  +
  +
-- The get' family of methods retrieve properties from nodes (elements).
  +
-- They are basically wrappers over unsafeGetProperty, but IDL definition
  +
-- guarantee that properties not defined per DOM are not accessible through
  +
-- these functions. But we still need to obtain string value of it.
  +
 
if length v > 0
 
if length v > 0
  +
  +
-- If it was not just pressing Enter on an empty input box.
  +
 
then
 
then
 
getTimeStamp $ \t1 ->
 
getTimeStamp $ \t1 ->
  +
  +
-- Obtain the current time stamp
  +
 
toCPE (romdec v) $ \(rom, dec) ->
 
toCPE (romdec v) $ \(rom, dec) ->
  +
  +
-- toCPE wraps an expression in the CPS style expression. See above,
  +
-- the romdec function returns a tuple of two conversion results.
  +
 
rom `seq` dec `seq` getTimeStamp $ \t2 ->
 
rom `seq` dec `seq` getTimeStamp $ \t2 ->
  +
  +
-- Usage of seq's is necessary because seq on the tuple forced by toCPE
  +
-- does not evaluate deep into the structure: only to WHNF.
  +
-- We want to measure the Roman-Deciman conversion time. So we have to force
  +
-- the members of the tuple to evaluate before we get the next time stamp.
  +
 
putLine (v ++ " " ++ rom ++ " " ++ dec ++ " " ++ show (t2 - t1) ++ " ms")
 
putLine (v ++ " " ++ rom ++ " " ++ dec ++ " " ++ show (t2 - t1) ++ " ms")
 
(Just o) $ \_ ->
 
(Just o) $ \_ ->
  +
  +
-- putLine places the formatted output string before the <input> element
  +
-- (Just o). So, as the program works, lines of output start at the top
  +
-- of the browser window, and the <input> element moves downwards as new
  +
-- output apprears.
  +
 
set'value "" o $ \_ ->
 
set'value "" o $ \_ ->
  +
  +
-- Reset the <input> element, erase what was typed in.
  +
-- The final lambda returns True regardless, which means that
  +
-- the browser should take the default action on the user's input
  +
-- (keys pressed).
  +
 
True
 
True
 
else
 
else
 
True
 
True
 
else True
 
else True
 
 
   
 
</haskell>
 
</haskell>

Latest revision as of 15:47, 14 June 2007

-- A program similar to the Echo program, written
-- without monads as the first step to Fudgets adoption.
-- The program also demonstrates use of the DOM Level1 framework
-- also implemented in CPS style.

module  EchoCPS where

-- This module contains useful functions to access properties of underlying
-- Javascript objects in the type-agnostic manner: proceed with caution!
import UnsafeJS

-- This module contains functions to wrap expressions in CPS style
import CPS

-- This module contains Roman-Decimal conversion funcitons
import Roman

-- The modules under DOM are autogenerated from IDL
-- The modules under CDOM are utilities to simplify the use of DOM facilities
import DOM.Level1.Dom
import DOM.Level1.Html
import CDOM.Level1.DomUtils
import CDOM.Level1.Events
import DOM.Level1.Document
import DOM.Level1.HTMLElement
import DOM.Level1.HTMLDivElement
import DOM.Level1.HTMLInputElement

-- This module contains functions to obtain timestamps
import Debug.Profiling

-- Line-mode output: create a <div> element, place a text in it, 
-- append to the parent element (document body). The mbb argument
-- specifies whether the text will be inserted before a specific
-- element (Just) or just appended to the end of the document (Nothing).
-- The s argument is the string to output, and the c argument is
-- the continuation.

putLine s mbb c = getHTMLDocument $ \doc ->
                  documentBody doc $ \body ->

-- body contains reference to the HTML document <body> element
-- which is the parent of all <div>'s displaying the lines being output

                  mkDiv doc $ \dv ->
                  mkText doc s $ \tx ->
                  addChild tx dv $ \ch ->

-- Functions whose names start with mk (mkDiv, mkText) are autogenerated
-- wrappers over DOM methods which create elements bearing appropriate
-- HTML tags. Above, a <div> and a #text elements were created, and the #text
-- element was added to the <div> as a child.

                  let iac = case mbb of
                              Nothing -> addChild dv
                              Just b -> insertChild b dv

-- Based on the mbb argument, decision was made whether to add the child
-- (previously created <div>) to the end of the document (addChild), or to
-- insert the <div> before the specific element (insertChild).

                  in  iac body $ \ct -> 
                  c ct


main = getHTMLDocument $ \doc ->
       documentBody doc $ \body ->

-- The same sequence as above, to get the reference to the
-- HTML document's <body> element.

       mkInput doc $ \inp ->
       addChild inp body $ \_ ->

-- The <input> element was created and added to the <body> element

       set'id "input-echo" inp $ \_ ->

-- The set' family of methods deal with setting nodes' properties.
-- The above expression sets the id attribute of the <input> element
-- to "input-echo", so in plain HTML this would be:
--     <input id="input-echo">

       set'on "keypress" (inkey inp) inp $ \_ ->

-- The set'on "keypress" is same as specifying 
--     <input id="input-echo" onkeypress="javascript:(inkey inp)">

       focus inp $ id

-- The focus is a DOM method setting input focus on the element.
-- The id call at the very end "closes" the CPS chain of function calls.


-- A function to convert between Roman and Decimal presentations.
-- It also features exception handling that prevents the program
-- from crash if something wrong is entered. Input in error will be
-- converted into an empty string. The fromRoman and toRoman functions
-- call error in the situation when input cannot be processed.
romdec :: String -> (String, String)

romdec v =
  let rom = (catchJS ((show . fromRoman) v) (\_ -> ""))
      dec = (catchJS ((toRoman . read) v) (\_ -> ""))
  in (rom, dec)


-- The "onkeypress" handler: it does all the job. Note the o argument:
-- it holds reference to the element a handler is attached to. This forms
-- a closure (which may not be very much desired for some browsers), but
-- makes it extremely easy to distinguish between elements that cause 
-- the handler to fire.
inkey :: THTMLInputElement -> a -> Bool
       
inkey o e = unsafeGetProperty "keyCode" e $ \kcs ->
            unsafeToNum kcs $ \kci ->

-- Since there is no completed framework on event properties, we have
-- to use the unsafe interface. The two lines above extract the "keyCode"
-- property from the event received, and gets the numeric value of it.
-- In Javascript, this is equivalent to
--     new Number (e.keyCode)

            if kci == 13

-- We are only interested in the Enter key whose code is 13.
-- Other keys just edit the value in the <input> element, and do not
-- need to be handled.

              then
                get'value o $ \val ->
                unsafeToString val $ \v ->

-- The get' family of methods retrieve properties from nodes (elements).
-- They are basically wrappers over unsafeGetProperty, but IDL definition
-- guarantee that properties not defined per DOM are not accessible through
-- these functions. But we still need to obtain string value of it.

                if length v > 0

-- If it was not just pressing Enter on an empty input box.

                  then
                    getTimeStamp $ \t1 -> 

-- Obtain the current time stamp

                    toCPE (romdec v) $ \(rom, dec) ->

-- toCPE wraps an expression in the CPS style expression. See above,
-- the romdec function returns a tuple of two conversion results.

                    rom `seq` dec `seq` getTimeStamp $ \t2 ->

-- Usage of seq's is necessary because seq on the tuple forced by toCPE
-- does not evaluate deep into the structure: only to WHNF. 
-- We want to measure the Roman-Deciman conversion time. So we have to force
-- the members of the tuple to evaluate before we get the next time stamp.

                    putLine (v ++ " " ++ rom ++ " " ++ dec ++ " " ++ show (t2 - t1) ++ " ms") 
                            (Just o) $ \_ ->

-- putLine places the formatted output string before the <input> element
-- (Just o). So, as the program works, lines of output start at the top 
-- of the browser window, and the <input> element moves downwards as new
-- output apprears.

                    set'value "" o $ \_ ->

-- Reset the <input> element, erase what was typed in.
-- The final lambda returns True regardless, which means that
-- the browser should take the default action on the user's input
-- (keys pressed).

                    True
                  else  
                    True
              else True