| Safe Haskell | None | 
|---|---|
| Language | Haskell2010 | 
Myxine
Contents
Description
This library implements typed bindings to the Myxine server for creating local interactive GUIs in the web browser. For more details on Myxine-the-program, see the package description of this library, or its own homepage.
This module defines a higher level interface which abstracts over the direct calls to the Myxine server to allow a more declarative style of programming.
For a one-to-one set of bindings directly to the corresponding calls to the Myxine API see the module Myxine.Direct. This is straightforward for small examples and tests, but can become cumbersome for building full interactive applications.
Synopsis
- data Page model
- runPage :: forall model. PageLocation -> (WithinPage => IO model) -> (WithinPage => model -> (PageContent, Handlers model)) -> IO (Page model)
- waitPage :: Page model -> IO model
- stopPage :: Page model -> IO ()
- data PageLocation
- pagePath :: PagePath -> PageLocation
- pagePort :: PagePort -> PageLocation
- type Reactive model = ReactiveM model ()
- data ReactiveM model a
- reactive :: Reactive model -> model -> (PageContent, Handlers model)
- title :: Text -> Reactive model
- markup :: ToMarkup h => h -> Reactive model
- on' :: EventType props -> (props -> StateT model IO Propagation) -> Reactive model
- on :: EventType props -> (props -> StateT model IO ()) -> Reactive model
- data Propagation
- (@@) :: (Html -> Html) -> ReactiveM model a -> ReactiveM model a
- (##) :: Traversal' model model' -> Reactive model' -> Reactive model
- target :: ReactiveM model a -> ReactiveM model a
- this :: ReactiveM model Text
- class WithinPage
- eval :: (WithinPage, FromJSON a, MonadIO m) => Text -> m a
- evalBlock :: (WithinPage, FromJSON a, MonadIO m) => Text -> m a
- modifyPage :: Page model -> (model -> model) -> IO ()
- modifyPageIO :: Page model -> (WithinPage => model -> IO model) -> IO ()
- setPage :: Page model -> model -> IO ()
- getPage :: Page model -> IO model
- module Myxine.Event
Required Extensions
This library relies on the extension OverloadedRecordFields, since a
variety of browser event interfaces share field names/types. Without enabling
it, you'll see many bewildering errors about ambiguous names.
You may also find useful for concision the extensions NamedFieldPuns and
RecordWildCards.
Creating Interactive Pages
To create an interactive page, we need to build a Page. A Page is a handle
to a running page in the browser, providing a stateful typed mapping between the
view and interactions in the browser page and the types and actions available
within Haskell. To create a Page, we use runPage:
runPage::PageLocation-> model -> (WithinPage=> model -> (PageContent,Handlersmodel)) -> IO (Pagemodel)
The beginning of a typical myxine-client app looks something like:
data Model = ... -- the model that defines the page's current state do page <-runPagelocation (pure initialModel) (reactive. component) finalModel <-waitPagepage where location ::PageLocationlocation =pagePort1123 <>pagePath'/' -- where to connect to the server initialModel :: Model initialModel = ... -- the initial state of the model component ::WithinPage=>ReactiveModel component = ... -- how to render the model and listen for events that would update it
To describe the interactive behavior of the page, we need to define:
- location: the- pagePathand- pagePortto connect to the Myxine server. Use- memptyto use the default port and the root path. See the section on page locations.
- initialModel: the starting value for the model of the page, which can be any Haskell data type of your choice.
- component: an interleaved description in the- Reactivemonad explaining both how to render the current state of the model as HTML, and how to handle events that occur within that selfsame HTML by updating the model and performing IO.
See also the sections on building reactive pages and manipulating pages.
 Using Page without Reactive
Although the Reactive abstraction is typically the most convenient way to
build a Page, the runPage abstraction is not bound to a specific way of
formatting HTML PageContent or gathering a set of Handlers. Instead,
as noted above, runPage takes as input any function of type
WithinPage => model -> (PageContent, Handlers model)Reactive-built pages to runPage by evaluating them using:
reactive::Reactivemodel -> model -> (PageContent,Handlersmodel)
This might not always suit your desires, though, and that's precisely why it's
not baked in. You are free to construct PageContent using pageTitle and
pageBody, and to construct Handlers using onEvent and
<>, avoiding the Reactive abstraction altogether if you so choose.
Creating and Waiting For Pages
A handle to a running Page. Create this using runPage, and wait for its
 eventual result using waitPage. In between, you can interact with it using
 the rest of the functions in this module, such as modifyPage, stopPage,
 etc.
Arguments
| :: PageLocation | The location of the  | 
| -> (WithinPage => IO model) | An IO action to return the initial  | 
| -> (WithinPage => model -> (PageContent, Handlers model)) | A function to draw the  | 
| -> IO (Page model) | A  | 
Run an interactive page, returning a handle Page through which it can be
 interacted further, via the functions in this module (e.g. waitPage,
 modifyPage, etc.).
This function takes as input a PageLocation, an initial model, and a pure
 function from the current state of the page's model to a rendered HTML view
 of the page in its entirety, and the new set of Handlers for page events. A
 handler can modify the model of the page, and perform arbitrary IO actions,
 including evaluating JavaScript in the page using eval. After each all
 pertinent handlers for an event are dispatched, the page is re-rendered to
 the browser.
This function itself is non-blocking: it immediately kicks off threads to
 start running the page. It will not throw exceptions by itself. All
 exceptions thrown by page threads (such as issues with connecting to the
 server) are deferred until a call to waitPage.
Important: Because the GHC runtime does not wait for all threads to
 finish when ending the main thread, you probably need to use waitPage to
 make sure your program stays alive to keep processing events.
Typical use of this function embeds a Reactive page by using the reactive
 function to adapt it as the last argument (but this is not the only way to
 use it, see Using Page without Reactive):
runPagelocation (pure initialModel) (reactivecomponent)
waitPage :: Page model -> IO model Source #
Wait for a Page to finish executing and return its resultant model, or
 re-throw any exception the page encountered.
This function may throw HttpException if it cannot connect to a running
 instance of the server. Additionally, it will also re-throw any exception
 that was raised by user code running within an event handler or
 model-modifying action.
Specifying Page Locations
If you are building a single-page application using Myxine, and you don't
      intend to share its address space, you don't need to change the default
      settings for the PageLocation: mempty will do. However, the Myxine
      server will gladly host your page at any path you desire; just use
      pagePath to specify. Similarly, use pagePort to specify if the Myxine
      server is running on a different port than its default of 1123.
data PageLocation Source #
The options for connecting to the Myxine server. This is an opaque
 Monoid: set options by combining pagePort and/or pagePath using their
 Semigroup instance.
Instances
pagePath :: PagePath -> PageLocation Source #
Set the path to something other than the default of /.
pagePort :: PagePort -> PageLocation Source #
Set the port to a non-default port. This is only necessary when Myxine is running on a non-default port also.
Building Reactive Pages
When using the Reactive DSL for building pages, it's intended that you
      import it alongside the Html5 and
      Attributes modules from the
      blaze-html, which
      provide the HTML combinators required for building markup.
While nothing about this module hard-codes the use of
      lenses, it is often useful in
      the body of handlers given to on to manipulate the model state using
      lens combinators, in particular the stateful .= and
      friends. This library is designed to play well with the following import
      structure:
import Text.Blaze.Html5 ((!)) import qualified Text.Blaze.Html5 as H import qualified Text.Blaze.Html5.Attributes as A import Control.Monad.IO.Class import Control.Monad.State import Control.Monad.Reader import Control.Lens import Myxine
A small example:
Here's a simple Reactive page that uses some of the combinators in this
      library, as well as the Lens combinators _1,
      _2, ^., .=,
      +=, and *=.
In this demonstration, you can see how the @@ and on functions work
      together to allow you to define event handlers scoped to specific regions
      of the page: handlers defined via on receive only events from within the
      region delineated by the enclosing @@. As such, clicking on the
      background does not increment the counter, but clicking on the button
      does.
main :: IO () main = do page <-runPagemempty(0, False) (reactivecomponent) print =<<waitPagepage component :: Reactive (Integer, Bool) component = do model <-askH.div ! A.style ("background: "<>if model^._2then "red" else "green")@@doonMouseOver$ \_ ->_2.=TrueonMouseOut$ \_ ->_2.=False H.button ! A.style "margin: 20pt"@@doonClick$ \MouseEvent{shiftKey = False} ->_1+=1onClick$ \MouseEvent{shiftKey = True} ->_1*=2markup$ do H.span ! A.style "font-size: 20pt" $ H.string (show (model^._1))
type Reactive model = ReactiveM model () Source #
The Reactive type interleaves the description of page markup with the
 specification of event handlers for the page.
It is a Monoid and its underlying type ReactiveM is a Monad, which
 means that just like the Blaze templating library, it can be (and is designed
 to be!) used in do-notation. Importantly, it is also a MonadReader
 model, where the current model is returned by ask.
data ReactiveM model a Source #
The underlying builder monad for the Reactive type.
This is almost always used with a return type of (), hence you will usually
 see it aliased as Reactive model.
Instances
| MonadReader model (ReactiveM model) Source # | |
| Monad (ReactiveM model) Source # | |
| Functor (ReactiveM model) Source # | |
| Applicative (ReactiveM model) Source # | |
| Defined in Myxine.Reactive Methods pure :: a -> ReactiveM model a # (<*>) :: ReactiveM model (a -> b) -> ReactiveM model a -> ReactiveM model b # liftA2 :: (a -> b -> c) -> ReactiveM model a -> ReactiveM model b -> ReactiveM model c # (*>) :: ReactiveM model a -> ReactiveM model b -> ReactiveM model b # (<*) :: ReactiveM model a -> ReactiveM model b -> ReactiveM model a # | |
| a ~ () => IsString (ReactiveM model a) Source # | A string literal is a  | 
| Defined in Myxine.Reactive Methods fromString :: String -> ReactiveM model a # | |
| Semigroup a => Semigroup (ReactiveM model a) Source # | 
 | 
| Monoid a => Monoid (ReactiveM model a) Source # | The empty  | 
| Attributable (ReactiveM model a -> ReactiveM model a) Source # | You can apply an HTML attribute to any function between  | 
| Attributable (ReactiveM model a) Source # | You can apply an HTML attribute to any  | 
reactive :: Reactive model -> model -> (PageContent, Handlers model) Source #
Evaluate a reactive component to produce a pair of PageContent and
 Handlers. This is the bridge between the runPage abstraction and
 the Reactive abstraction: use this to run a reactive component in a
 Page.
title :: Text -> Reactive model Source #
Set the title for the page. If this function is called multiple times in one update, the most recent call is used.
markup :: ToMarkup h => h -> Reactive model Source #
Write an atomic piece of HTML (or anything that can be converted to it) to
 the page in this location. Event listeners for its enclosing scope can be
 added by sequential use of on. If you need sub-pieces of this HTML to have
 their own scoped event listeners, use @@ to build a composite component.
>>>markup h === const (toMarkup h) @@ pure ()
on' :: EventType props -> (props -> StateT model IO Propagation) -> Reactive model Source #
Listen to a particular event and react to it by modifying the model for the
 page. This function's returned Propagation value specifies whether or not
 to propagate the event outwards to other enclosing contexts. The event target
 is scoped to the enclosing @@, or the whole page if at the top level.
When the specified EventType occurs, the event handler will be called with
 that event type's corresponding property record, e.g. a Click event's
 handler will receive a MouseEvent record. A handler can modify the page's
 model via Stateful actions and perform arbitrary IO using liftIO. In the
 context of a running page, a handler also has access to the eval and
 evalBlock functions to evaluate JavaScript in that page.
Exception behavior: This function catches PatternMatchFail exceptions
 thrown by the passed function. That is, if there is a partial pattern match
 in the pure function from event properties to stateful update, the stateful
 update will be silently skipped. This is useful as a shorthand to select only
 events of a certain sort, for instance:
on'Click\MouseEvent{shiftKey = True} -> do putStrLn "Shift + Click!" pureBubble
data Propagation Source #
Indicator for whether an event should continue to be triggered on parent
 elements in the path. An event handler can signal that it wishes the event to
 stop propagating by returning Stop.
Constructors
| Bubble | Continue to trigger the event on parent elements | 
| Stop | Continue to trigger the event for all handlers of this element, but stop before triggering it on any parent elements | 
Instances
(@@) :: (Html -> Html) -> ReactiveM model a -> ReactiveM model a infixr 5 Source #
Wrap an inner reactive component in some enclosing HTML. Any listeners
 created via on in the wrapped component will be scoped to only events that
 occur within this chunk of HTML.
In the following example, we install a Click handler for the whole page,
 then build a div with another Click handler inside that page, which
 returns Stop from on' to stop the Click event from bubbling out to the
 outer handler when the inner div is clicked.
doonClick$ \_ -> liftIO $ putStrLn "Clicked outside!" div ! style "background: lightblue;"@@do "Click here, or elsewhere..."on'Click$ \_ -> do liftIO $ putStrLn "Clicked inside!" pureStop
(##) :: Traversal' model model' -> Reactive model' -> Reactive model infixr 5 Source #
Focus a reactive page fragment to manipulate a piece of a larger model,
 using a Traversal' to specify what part(s) of the larger model to
 manipulate.
This is especially useful when creating generic components which can be re-used in the context of many different models. For instance, we can define a toggle button and specify separately which part of a model it toggles:
toggle ::ReactiveBool toggle = button@@do active <-askif active then "ON" else "OFF"onClick\_ ->modifynot twoToggles ::Reactive(Bool, Bool) twoToggles = do _1##toggle _2##toggle
This function takes a Traversal', which is strictly more general than a
 Lens'. This means you can use traversals with zero or more than one target,
 and this many replicas of the given Reactive fragment will be generated,
 each separately controlling its corresponding portion of the model. This
 means the above example could also be phrased:
twoToggles ::Reactive(Bool, Bool) twoToggles =each##toggle
target :: ReactiveM model a -> ReactiveM model a Source #
Create a scope for event listeners without wrapping any enclosing HTML. Any
 listeners created via on will apply only to HTML that is written inside
 this block, rather than to the enclosing broader scope.
>>>target === (id @@)
this :: ReactiveM model Text Source #
Return a piece of JavaScript code which looks up the object corresponding
 to the current scope's location in the page. This is suitable to be used in
 eval, for instance, to retrieve properties of a particular element.
If there is no enclosing @@, then this is the window object; otherwise,
 it is the outermost HTML element object created by the first argument to the
 enclosing @@. If there are multiple elements at the root of the enclosing
 @@, then the first of these is selected.
For example, here's an input which reports its own contents:
textbox :: Reactive Text
textbox = input @@ do
  e <- this
  on Input _ -> do
    value eval $ this < ".value"
    put value
Evaluating JavaScript
The functions eval and evalBlock evaluate some JavaScript in the
      context of the current Page and return a deserialized Haskell type
      (inferred from the use site), or throw a JsException containing a
      human-readable string describing any error that occurred.
For instance, here's how we would query the width of the browser window on
      every Resize event:
windowWidth ::WithinPage=>ReactiveInt windowWidth = do currentWidth <- askmarkupcurrentWidthonResize$ \_ -> do width <-eval"window.innerWidth"putwidth
Possible errors (which manifest as JsExceptions):
- Any exception in the given JavaScript
- Invalid JSON response for the result type inferred (use Valueif you don't know what shape of data you're waiting to receive).
Further caveats:
- JavaScript undefinedis translated tonullin the results
- Return types are limited to those which can be serialized via
        JSON.stringify,
        which does not work for cyclic objects (like window,document, and all DOM nodes), and may fail to serialize some properties for other non-scalar values. If you want to return a non-scalar value like a list or dictionary, construct it explicitly yourself by copying from the fields of the object you're interested in.
- You're evaluating an arbitrary string as JavaScript, which means there are no guarantees about type safety or purity.
- It is possible that you could break the Myxine server code running in the page that makes it update properly, or hang the page by passing a non-terminating piece of code.
- Any modifications you make to the DOM will be immediately overwritten on the next re-draw of the page. Don't do this.
- If there are multiple browser windows pointed at the same page, and the result of your query differs between them, it's nondeterministic which result you get back.
class WithinPage Source #
The WithinPage constraint, when it is present, enables the use of the
 eval and evalBlock functions. Only in the body of a call to runPage or
 modifyPageIO is there a canonical current page, and it's a type error to
 use these functions anywhere else.
Minimal complete definition
withinPageContext
eval :: (WithinPage, FromJSON a, MonadIO m) => Text -> m a Source #
Evaluate a JavaScript expression in the context of the current page.
 The given text is automatically wrapped in a return statement before being
 evaluated in the browser.
If you try to call eval outside of a call to runPage or modifyPageIO,
  you'll get a type error like the following:
• No instance for WithinPage arising from a use of ‘eval’ • In a stmt of a 'do' block: x <- eval @Int "1 + 1"
This means that you called it in some non-Page context, like in the
 main function of your program.
evalBlock :: (WithinPage, FromJSON a, MonadIO m) => Text -> m a Source #
Evaluate a JavaScript block in the context of the current page. Unlike
 eval, the given text is not automatically wrapped in a return
 statement, which means that you can evaluate multi-line statements, but you
 must provide your own return.
If you try to call evalBlock outside of a call to runPage or
  modifyPageIO, you'll get a type error like the following:
• No instance for WithinPage arising from a use of ‘evalBlock’ • In a stmt of a 'do' block: x <- evalBlock @Int "return 1;"
This means that you called it in some non-Page context, like in the
 main function of your program.
Manipulating Running Pages
Once a page is running, the only way to interact with its contents is via its
      Page handle (unless you use the methods in Direct, but it is
      strongly discouraged to mix the two different abstractions: you will
      almost certainly confuse yourself a lot).
A Page whose behavior relies solely on user interactions within the
      browser doesn't need any of these functions: these are the mechanism by
      which external information can be used to modify the model of a page, and
      thereby update the GUI to reflect the model.
Keep in mind that Myxine, like most GUIs, is an inherently concurrent
      system. This interface reflects that: in between direct modifications
      of the model with modifyPage and its friends, the model may change
      arbitrarily due to event handlers (or other threads) taking actions upon
      it. However, it's guaranteed that any single modification is atomic, and
      that sequences of modifications are not re-ordered (although there may be
      things that happen in between them).
modifyPage :: Page model -> (model -> model) -> IO () Source #
Modify the model of the page with a pure function, and update the view in the browser to reflect the new model.
This function is non-blocking; the page view may not yet have been updated by the time it returns.
modifyPageIO :: Page model -> (WithinPage => model -> IO model) -> IO () Source #
Modify the model of the page, potentially doing arbitrary other effects in
 the IO monad, then re-draw the page to the browser. The functions eval
 and evalBlock are available for evaluating JavaScript within the context of
 the current page.
This function is non-blocking; the page view may not yet have been updated by the time it returns.
setPage :: Page model -> model -> IO () Source #
Set the model of the page to a particular value, and update the view in the browser to reflect the new model.
This function is non-blocking; the page view may not yet have been updated by the time it returns.
getPage :: Page model -> IO model Source #
Get the current model of the page, blocking until it is retrieved.
Note: it is not guaranteed that the model returned by this function is "fresh" by the time you act upon it. That is:
getPagepage >>=setPagepage
is not the same as
modifyPage id
This is because some other thread (notably, an event handler thread) could
 have changed the page in between the call to getPage and setPage. As a
 result, you probably don't want to use this function, except perhaps as a way
 to extract intermediate reports on the value of the page.
module Myxine.Event