clearing out all child elements from a div with PureScript - dom

There are lots of ways to empty out a element of a DOM node with JavaScript. I would like to do this with PureScript (with the intent of replacing static content with a Halogen widget). It seems like among all the functions of purescript-web-html and purescript-web-dom there ought to be an obvious way to to this, but the huge number of conflicting ways to describe a element are defeating me.
Is there a one or two line easy way to do what seems like an obvious operation before calling runUI?

I'm not sure if this is the best way to do this as it seems like there ought to be a nice functional way to map over an HTMLCollection, but at least it works:
import Data.Maybe (Maybe(..))
import Effect (Effect)
import Halogen.Aff as HA
import Halogen.VDom.Driver (runUI)
import Web.DOM.ParentNode
import Web.DOM.ChildNode (remove)
import Web.HTML.HTMLElement (HTMLElement, toParentNode)
import Web.DOM.Element (toChildNode)
import Effect.Class
selector :: QuerySelector
selector = QuerySelector "#halogen"
main :: Effect Unit
main = HA.runHalogenAff do
body <- HA.awaitBody
elem <- HA.selectElement selector
case elem of
Nothing -> runUI component unit body
Just e -> do
liftEffect $ clearChildren e
runUI component unit e
clearChildren :: HTMLElement -> Effect Unit
clearChildren e = clearNextChild (toParentNode e)
where
clearNextChild :: ParentNode -> Effect Unit
clearNextChild n = do
last <- lastElementChild n
case (last) of
Nothing -> pure unit
Just elem -> do
remove $ toChildNode elem
clearNextChild n

Related

Semigroup typeclass (Either) with slightly altered combine

Using cats.Semigroup one can write this:
import cats.Semigroup
import cats.implicits._
val l1: String Either Int = Left("error")
val r1: String Either Int = Right(1)
val r2: String Either Int = Right(2)
l1 |+| r1 // Left("error")
r1 |+| r2 // Right(3)
I would like to have an equally idiomatic operator (combine-like) that works like this:
if there is (at least) one Right in my computation, return a Right
if there are only Lefts, return a Left
E.g.:
Right(1) |+| Right(2) // Right(3)
Right(1) |+| Left("2") // Right(1)
Left("1") |+| Left("2") // Left("12") // in my particular case the wrapped value here does not really matter (could also be e.g. Left("1") or Left("2")), but I guess Left("12") would be the must logical result
Is there something like this already defined in e.g. cats on Either?
There are a bunch of lawful semigroup instances for Either, and which of them should be included in Cats was a matter of some debate. Cats, Scalaz, and Haskell all make different choices in this respect, and the instance you're describing (flipped but with both lefts and right combining) is different from all three of those, it doesn't have a specific name that I'm aware of, and it isn't provided under any name or in any form by Cats.
That's of course not a problem in itself, since as we'll see below it's pretty easy to verify that this instance is lawful, but there is one potential issue you should be aware of. You don't really explain your intended semantics, but if you ever want to promote this to a Monoid, the fact that you pick the Right when you have both a Left and a Right means that your zero will have to be Left. This might be kind of weird if you're thinking of rights as successes and lefts as errors that are safe to ignore when combining values.
You're asking about Semigroup, though, not Monoid, so let's just ignore that for now and show that this thing is lawful. First for the definition:
import cats.kernel.Semigroup
implicit def eitherSemigroup[A, B](implicit
A: Semigroup[A],
B: Semigroup[B]
): Semigroup[Either[A, B]] = Semigroup.instance {
case (Right(x), Right(y)) => Right(B.combine(x, y))
case (r # Right(_), Left(_)) => r
case (Left(_), r # Right(_)) => r
case (Left(x), Left(y)) => Left(A.combine(x, y))
}
And then the checking part:
import cats.instances.int._
import cats.instances.string._
import cats.kernel.instances.either.catsStdEqForEither
import cats.kernel.laws.discipline.SemigroupTests
import org.scalacheck.Test.Parameters
SemigroupTests(eitherSemigroup[String, Int]).semigroup.all.check(Parameters.default)
And yeah, it's fine:
+ semigroup.associative: OK, passed 100 tests.
+ semigroup.combineAllOption: OK, passed 100 tests.
+ semigroup.repeat1: OK, passed 100 tests.
+ semigroup.repeat2: OK, passed 100 tests.
Personally if I wanted something like this I'd probably use a wrapper to avoid confusing future readers of my code (including myself), but given that nobody really knows what the semigroup of Either should do, I don't think using a custom instance is as big of a problem as it is for most other types from the standard library.

Unwrap Maybe values from nested records

I have a series of nested records of config information in PureScript that I want to pass to a JavaScript function. These records predominantly consist of Maybe-typed values. Is there a way I can serialize this to JavaScript objects which omit the Nothing-valued methods and unwrap the Just-valued methods?
I've taken a shot at writing this in JavaScript using semi-hacky instanceof checks, but it's been very painful, e.g. because there is no easy way to stop the recursion (I can't differentiate my records from random other JavaScript objects). Is there a better way?
One option is to use the purescript-nullable package. You can turn Maybe values into Nullable values using toNullable :: forall a. Maybe a -> Nullable a. The resulting runtime representation is appropriate for passing to JavaScript functions, since toNullable (Just value) becomes value during runtime and toNullable Nothing becomes null during runtime.
Another option is to use the purescript-simple-json package. You can use the write :: forall a. WriteForeign a => a -> Foreign function to turn a record with Maybe values into a record where Just value is replaced with value and Nothing is replaced with undefined. This approach should more straightforward for your use case of nested records with Maybe values.
You can use genericEncodeJson from Data.Argonaut and set the omitNothingFields flag to true. This flag does exactly what you expect.
module Main where
import Prelude
import Control.Monad.Eff.Console (logShow)
import Data.Argonaut.Generic.Aeson (options)
import Data.Argonaut.Generic.Encode (Options(..), genericEncodeJson)
import Data.Generic (class Generic)
import Data.Maybe (Maybe(..))
data TheRecord = TheRecord { a :: Maybe Int, b :: Maybe String, c :: String }
derive instance gRecord :: Generic TheRecord
main =
-- Prints {"c":"Always here","a":42}
logShow $ genericEncodeJson (Options o { omitNothingFields = true }) rec
where
rec = TheRecord { a: Just 42, b: Nothing, c: "Always here" }
Options o = options

Purescript cannot get keycode from keyboard

I am new to functional programming and to Purescript. I am trying to get keycode from keys pressed from keyboard. I have made a eventListener which fires when keydown event is fired and triggers event listener function test. I am having problem in converting event to keyboardevent and getting keycode from keyboardevent. I am attaching my code and error it producing.
import Control.Monad.Eff (Eff)
import Control.Monad.Eff.Class
import Control.Monad.Eff.Console (CONSOLE, log)
import DOM (DOM)
import DOM.Event.EventTarget (addEventListener, eventListener)
import DOM.HTML.Types as DHT
import DOM.Event.KeyboardEvent as KE
import DOM.Event.Types (EventType(..), EventTarget)
import DOM.Event.Event
import DOM.HTML (window)
import DOM.HTML.Window (document)
import Prelude (Unit)
test :: forall e. Event -> Eff ( console :: CONSOLE, dom :: DOM | e) Unit
test a = do
ke <- KE.eventToKeyboardEvent a
co <- KE.key ke
log "Key Pressed : "
main :: forall e. Eff (console :: CONSOLE, dom :: DOM | e) Unit
main = do
documenttarget <- liftEff $ window >>= document <#> DHT.htmlDocumentToEventTarget
addEventListener (EventType "keydown") (eventListener test) true (documenttarget)
Error :
Error found:
in module Main
at src/Main.purs line 30, column 10 - line 30, column 19
Could not match type
String
with type
t0 t1
while checking that type String
is at least as general as type t0 t1
while checking that expression key ke
has type t0 t1
in value declaration test
where t0 is an unknown type
t1 is an unknown type
See https://github.com/purescript/documentation/blob/master/errors/TypesDoNotUnify.md for more information,
or to contribute content related to this error.
* ERROR: Subcommand terminated with exit code 1
Your test function needs tweaking a bit:
import Prelude
import Control.Monad.Eff (Eff)
import Control.Monad.Eff.Class (liftEff)
import Control.Monad.Eff.Console (CONSOLE, log)
import Control.Monad.Except (runExcept)
import DOM (DOM)
import DOM.Event.Event (Event)
import DOM.Event.EventTarget (addEventListener, eventListener)
import DOM.Event.KeyboardEvent as KE
import DOM.Event.Types (EventType(..))
import DOM.HTML (window)
import DOM.HTML.Types as DHT
import DOM.HTML.Window (document)
import Data.Either (Either(..))
test :: forall e. Event -> Eff ( console :: CONSOLE, dom :: DOM | e) Unit
test a =
case runExcept (KE.eventToKeyboardEvent a) of
Left err ->
log "Event was not a keyboard event"
Right ke -> do
let co = KE.key ke
log "Key Pressed : "
main :: forall e. Eff (console :: CONSOLE, dom :: DOM | e) Unit
main = do
documenttarget <- liftEff $ window >>= document <#> DHT.htmlDocumentToEventTarget
addEventListener (EventType "keydown") (eventListener test) true (documenttarget)
First, KE.key and KE.eventToKeyboardEvent are not effectful functions, which means you don't need to bind to get the values from them (this is what the t0 t1 error was about: it expects the resulting value to be like m String rather than just String, for some m).
Next, when we use eventToKeyboardEvent, it can fail, since we have no way of guaranteeing that it's safe to coerce any Event to a KeyboardEvent. The result is wrapped in F, which is a synonym for Except MultipleErrors, so we need to do something to get the result of that. We "run" the Except wrapper that gives us an Either back, where the Left side is the error message and Right is the success result.
In this example I made it log when the cast fails, but you probably don't care about that, and generally don't even expect that case to be hit, in which case using pure unit is probably good enough instead.
There's actually another way of writing this if you're happy to let the failure case do nothing:
import Data.Foldable (for_)
test :: forall e. Event -> Eff ( console :: CONSOLE, dom :: DOM | e) Unit
test a =
for_ (runExcept (KE.eventToKeyboardEvent a)) \ke -> do
let co = KE.key ke
log "Key Pressed : "
for_ is actually a highly generic function that can be used for a variety of stuff, but this usage of it to absorb failures silently is pretty handy when writing code against the ugly DOM API.

How do you set the document title using Purescript?

After searching for some time I found in Pursuit the module DOM.HTML.History which has the data type DocumentTitle. This type could probably be used together with the function
replaceState ::
∀ e. Foreign -> DocumentTitle -> URL -> History -> Eff (history :: HISTORY | e) Unit
To change the document.title property of the page, however, I can't find examples showing how to call this function (e.g., where do I get the external Foreign data type?). Also, I'm not even sure if this function would do what I expect it to do...
In the unfortunate case that the Purescript team didn't include in their core API a way to change the document title, it's still possible to do so by making use of purescript's handy FFI mechanism.
Add these two files into your project:
Document.js
exports.setDocumentTitle =
function (title)
{
return function ()
{
window.document.title = title;
};
};
Document.purs
module Document
where
import Control.Monad.Eff (kind Effect, Eff)
import Data.Unit (Unit)
foreign import data DOCUMENT :: Effect
foreign import setDocumentTitle ::
∀ fx . String -> Eff (document :: DOCUMENT | fx) Unit
Now you can call setDocumentTitle as you would call Console's log function, except the effect would be DOCUMENT instead of CONSOLE, of course.
kazouas answer would look like this (in PS 0.12)
import Effect (Effect)
import Data.Unit (Unit)
foreign import setDocumentTitle :: String -> Effect Unit
Javascript remains the same.

How do you present any data type to the user with PureScript?

I want to make a very human-friendly development environment, and I'm considering using PureScript to provide the language part. I see that out of the box, Show doesn't work on records of things which are instances of Show:
log (show {a:5})
The 'Try PureScript!' (http://try.purescript.org/) compiler says:
No type class instance was found for
Prelude.Show { a :: Int
}
Is there a tool for generically printing any data structure, especially one containing records? Is there some type trickery that would support generically walking over the record to support my own class like present :: Present a => a -> Presentation? The problem is that I don't know what the types will be ahead of time. The user enters a record and I want to be able to present it. It seems that I'll have to patch the compiler to support this.
Records are disallowed in instance heads. For discussion and reasons, see this thread.They must be wrapped in data or newtype if we want to write instances for them.
However, there is a generics library and a deriving mechanism that lets us generate Show instances.
import Data.Generic
data Foo = Foo {a :: Int} | Bar {b :: String}
derive instance genericFoo :: Generic Foo
instance showFoo :: Show Foo where
show = gShow
Working with untyped data in PureScript is done using the purescript-foreign or the purescript-argonaut libraries. I'd suggest argonaut.
The representation of a record with unknown fields and unknown types for these fields would be: StrMap Json from the purescript-maps package. I'd suggest you take a look at the (not yet merged) documentation over here: https://github.com/hdgarrood/purescript-argonaut-core/blob/565c7e650c51c45570663cf1838ec9cfa307a9c7/README.md. I've also put together a little example, showing how to match on a heterogeneous array from JavaScript:
-- src/Main.purs
module Main where
import Prelude
import Control.Monad.Eff (Eff)
import Control.Monad.Eff.Console (CONSOLE, log)
import Data.Argonaut (foldJson, Json)
import Data.Foldable (traverse_)
newtype Presentation = Presentation String
unPresentation :: Presentation -> String
unPresentation (Presentation p) = p
instance showPresentation :: Show Presentation where
show = unPresentation
class Present a where
present :: a -> Presentation
instance presentInt :: Present Int where
present = Presentation <<< show
instance presentNumber :: Present Number where
present = Presentation <<< show
instance presentBoolean :: Present Boolean where
present = Presentation <<< show
instance presentString :: Present String where
present = Presentation
presentJson :: Json -> Presentation
presentJson =
foldJson
(const (Presentation "null"))
present
present
present
(const (Presentation "array"))
(const (Presentation "record"))
foreign import vals :: Array Json
main :: forall e. Eff ( console :: CONSOLE | e) Unit
main = traverse_ (log <<< show <<< presentJson) vals
And the corresponding js file:
// src/Main.js
// module Main
exports.vals = [1, 1.2, "hello", true, [1,2,3], {a: 3, b: "hi"}];
Running this program gives you:
> pulp run
* Building project in/home/creek/Documents/so-christopher-done
* Build successful.
1.0
1.2
hello
true
array
record
Yes, traceAny and related functions from purescript-debug. Here are a few examples: test/Main.purs#L22. I'd post the links to Pursuit, but it doesn't seem to have purescript-debug at the moment.