Commit b39a3b5b authored by Bas Lijnse's avatar Bas Lijnse

Wrote documentation about different ways of starting tasks.

Also tweaked the API a bit based on the examples in the documentation.
parent 7bc0337b
##### Creating Custom Editors
# Creating Custom Editors #
## Introduction
## Introduction ##
In this guide we assume you are already familiar with writing programs with iTasks and have used the common task definitions for user interaction such as `updateInformation` and `viewInformation`, but find that the automagically generated GUI's are not working for your application.
In this guide we'll walk you through the process of creating an `Editor`. This is the construct that is used by tasks like `updateInformation` to render the GUI and handle user events. Editors are typed GUI building blocks that can be composed to create any GUI you like. Editors abstract away all UI concerns into a single opaque value. These building blocks define how the GUI is rendered, how events are handled and how the GUI is synchronized with the (task) value it represents.
The remainder of this document is structured as follows: First we'll look at how a custom editor can be applied in an interaction task. We'll then look at how we can configure the builtin editors of the iTask framework for basic types. We end by looking at how we can use editor combinators to build editors for more complex data structures such as records and Algebraic data types.
## Using custom editors in tasks
## Using custom editors in tasks ##
The first thing you need to know to work with custom editors is how you can use them in a task. Let's look at a very simple task we'll use as example:
......@@ -40,13 +40,13 @@ The GUI will now be rendered as a nice slider. Let's look a little closer at wha
So to use a custom task UI we need to do two things: We need to specify an editor of the right type, and then pass it in the option list of the interaction task we are using. In the next section we'll look at the builtin editors and how we can use them to replace the generic editor function.
## Using the builtin editors
## Using the builtin editors ##
The iTask framework provides a number of builtin UI components that it can render in a browser. These are the lowest level building blocks with which all iTasks GUI's are constructed.
In the example of the previous section we have seen the `slider` editor. This editor is one of the builtin componentens in the `iTasks.UI.Editor.Controls` module. All builtin editors have no arguments, but can dynamically be configured by setting attributes. For example if we wanted to set the maximum value of the slider, we would write `slider <<@ maxAttr 42`. The tuning combinators `<<@` or `@>>` are used to set attributes on editors. This pattern is used to make it easy to create editors without the need to specify all attributes in advance. In many cases, it is not necessary to deviate from the default values of the configurable attributes. Forcing a programmer to specify them all makes our GUI code too verbose. The price we pay for this convenience is that we lose some type safety. We dynamically set arbitrary attributes on editors, whether the UI rendering code uses them or not.
## Composing editors
## Composing editors ##
Creating editors for basic values is useful, but more often we want to construct editors for composite datastructures such as records. Let's expand the slider example to show how you can compose editors.
......@@ -100,7 +100,7 @@ where
This example is a little more complex, but uses only things we have already seen. By constructing editors from the basic building blocks and transforming the value domain of the editors, we can construct any kind of GUI we like.
## Creating dynamic editors
## Creating dynamic editors ##
One of the nice features of the generic editors is that they work for any type of data structure. You can easily create editors for recursive data types that have values of arbitrary size, not just static forms.
......@@ -129,7 +129,7 @@ The type of the list of possible editors is not `[Editor a]` but `[(a -> a, Edit
This example shows that with these combinators you can also make dynamic editors for recursive types. You can even plug in in the generic editors to create editors for higher order-types.
## Conclusion
## Conclusion ##
In this guide we have shown how you can fully customize the GUI of your iTask tasks by creating editors. We have not covered all builtin editors and combinators, but just enough to get you started. You can look at the documentation of `iTasks.UI.Editor.Controls`, `iTasks.UI.Editor.Containers` and `iTasks.UI.Editor.Modifiers` to find out all possibilities.
......
##### Layouting Task Compositions
# Laying out Task Compositions #
## Introduction
## Introduction ##
In this guide we introduce you to the different task layout modifiers.
We assume you are already familiar with writing programs with iTasks.
......@@ -21,7 +21,7 @@ In those cases you can use the layout directly with the `ApplyLayout` type as fo
task1 <<@ ApplyLayout somelayout
```
## Menu bars
## Menu bars ##
The function signature for the menubar decorator is as follows:
```clean
arrangeAsMenu :: [[Int]] -> Layout
......@@ -48,7 +48,7 @@ task = (programTask
) <<@ ArrangeAsMenu [[0,2]]
```
## Sidebar
## Sidebar ##
The function signature for the sidebar decorator is as follows:
```clean
arrangeWithSideBar :: !Int !UISide !Int !Bool -> Layout
......@@ -72,7 +72,7 @@ where
choices = ["string" +++ toString i\\i<-[0..20]]
```
## Panels
## Panels ##
In some cases, such as when you apply a title, the user interface is automatically lifted to a panel.
However, this can also be done manually with the option of making the panel fullscreenable.
......@@ -84,7 +84,7 @@ toPanel :: Bool -> Layout
If the boolean flag is set to true, the panel includes a small icon (![](Libraries/iTasks/UI/WebPublic/css/icons/fullscreen.png)) on the bottom right that, when clicked, makes the panel full screen.
If the small icon is clicked again, the panel shrinks back to the original size again.
## Conclusion
## Conclusion ##
In this guide we have shown how you how to decorate tasks with layout combinators so that you can change the way they appear in the client.
Not all combinators are covered, but they can be found in `iTasks.UI.Layout.Common`.
# Starting Tasks #
## Introduction ##
For many cases the "standard" way of starting tasks using `doTasks` is all you need.
For example, when you create an iTask program with a single simple task such as this:
```Clean
Start world = doTasks (viewInformation () [] "Hello, world") world
```
your task will make your task available to be started at the URL path `/` of the built-in web-server.
For each client that requests this URL, the server creates an instance of that task for that client.
While this is the common way of starting tasks, it is not the only way.
You can specify different tasks to be started through different URL paths, or you can specify that certain tasks should be started when the iTask program is (re)started.
In this guide we will show you how to do this.
## Starting different tasks in one application ##
If we want to start different tasks by accessing different URLs we need to specify which URLs correspond to which tasks.
The `doTask` function is overloaded. It can be used with everything that is `Startable`.
There is an instance of `Startable` for `Task a` which is used in the simple case.
If we want to use `doTasks` with multiple tasks that can have different types we have to create a value that
wraps those tasks and groups them together.
We can do this as follows:
```Clean
taska = viewInformation () [] "Hello, world"
taskb = viewInformation () [] 42
Start world = doTasks
[onRequest "/ta" (const taska)
,onRequest "/tb" (const taskb)
]
```
The `onRequest` function takes a function that computes a task from an HTTP request (hence the `const` in the example) and wraps it together with the URL path as a `StartableTask` value.
There is an instance of `Startable` for `[StartableTask]`.
This makes it possible to specify a heterogeneous list of tasks with different types that can be started by requesting different URLs. In most cases you don't need the HTTP request, but it can be useful.
## Starting tasks when the application starts ##
Some applications contain tasks that don't need any user interaction and should be started immediately.
For those cases there is the `onStartup` function which also wraps a task as `StartableTask` value.
Tasks that are wrapped this way are created when the iTask program starts and are not directly bound to any user or client. The second argument is a set of `TaskAttributes`.
This is initial meta-data that you can use to make it possible to identify the task instance.
Because these tasks are not directly attached to a client when they are created, this makes it possible to find and attach them later.
The following example shows a combination of an initialization task that is started when the application is started and another that can be started through the web.
```Clean
Start world = doTasks
[onStartup initDatabase defaultValue
,onRequest "/" (const browseDatabase)
]
```
......@@ -22,7 +22,7 @@ import iTasks.WF.Definition
| StartupTask !StartupTask
:: WebTask =
{ url :: !String
{ path :: !String
, task :: !WebTaskWrapper
}
......@@ -35,10 +35,10 @@ import iTasks.WF.Definition
:: TaskWrapper = E.a: TaskWrapper (Task a) & iTask a
//Utility functions for creating collections of startable tasks
atRequest :: String (HTTPRequest -> Task a) -> StartableTask | iTask a
atStartup :: TaskAttributes (Task a) -> StartableTask | iTask a
onRequest :: String (HTTPRequest -> Task a) -> StartableTask | iTask a
onStartup :: TaskAttributes (Task a) -> StartableTask | iTask a
publish :== atRequest //Backwards compatibility
publish :== onRequest //Backwards compatibility
class Startable a
where
......
......@@ -149,7 +149,7 @@ engineWebService webtasks =
[taskUIService webtasks
,documentService
,sdsService
,staticResourceService [url \\ {WebTask|url} <- webtasks]
,staticResourceService [path \\ {WebTask|path} <- webtasks]
]
show :: ![String] !*World -> *World
......@@ -159,11 +159,11 @@ show lines world
# (_,world) = fclose console world
= world
atRequest :: String (HTTPRequest -> Task a) -> StartableTask | iTask a
atRequest url task = WebTask {WebTask|url = url, task = WebTaskWrapper task}
onRequest :: String (HTTPRequest -> Task a) -> StartableTask | iTask a
onRequest path task = WebTask {WebTask|path = path, task = WebTaskWrapper task}
atStartup :: TaskAttributes (Task a) -> StartableTask | iTask a
atStartup attributes task = StartupTask {StartupTask|attributes = attributes, task = TaskWrapper task}
onStartup :: TaskAttributes (Task a) -> StartableTask | iTask a
onStartup attributes task = StartupTask {StartupTask|attributes = attributes, task = TaskWrapper task}
class Startable a
where
......@@ -171,11 +171,11 @@ where
instance Startable (Task a) | iTask a //Default as web task
where
toStartable task = [atRequest "/" (const task)]
toStartable task = [onRequest "/" (const task)]
instance Startable (HTTPRequest -> Task a) | iTask a //As web task
where
toStartable task = [atRequest "/" task]
toStartable task = [onRequest "/" task]
instance Startable StartableTask
where
......
......@@ -80,7 +80,7 @@ where
_ = Error (exception ("Workflow " +++ path +++ " could not be found"))
fromPrj nwf wfs
= Ok (Just [if (wf.path == path) nwf wf \\ wf <- wfs])
= Ok (Just [if (wf.Workflow.path == path) nwf wf \\ wf <- wfs])
allowedWorkflows :: ReadOnlyShared [Workflow]
allowedWorkflows = mapRead filterAllowed (workflows |+| currentUser)
......
......@@ -255,7 +255,7 @@ where
:: ChangeQueues :== Map InstanceNo (Queue UIChange)
taskUIService :: ![WebTask] -> WebService OutputQueues OutputQueues
taskUIService taskUrls = { urlMatchPred = matchFun [url \\ {WebTask|url} <-taskUrls]
taskUIService taskUrls = { urlMatchPred = matchFun [path \\ {WebTask|path} <-taskUrls]
, completeRequest = True
, onNewReq = reqFun taskUrls
, onData = dataFun
......@@ -393,8 +393,8 @@ where
disconnectFun _ _ (clientname,state,instances) iworld = (Nothing, snd (updateInstanceDisconnect (map fst instances) iworld))
disconnectFun _ _ _ iworld = (Nothing, iworld)
createTaskInstance` req [{WebTask|url,task=WebTaskWrapper task}:taskUrls] iworld
| req.HTTPRequest.req_path == uiUrl url = createTaskInstance (task req) 'DM'.newMap iworld
createTaskInstance` req [{WebTask|path,task=WebTaskWrapper task}:taskUrls] iworld
| req.HTTPRequest.req_path == uiUrl path = createTaskInstance (task req) 'DM'.newMap iworld
| otherwise = createTaskInstance` req taskUrls iworld
uiUrl matchUrl = (if (endsWith "/" matchUrl) matchUrl (matchUrl +++ "/")) +++ "gui-wsock"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment