5

I am very new to Purescript so this might be a naive question.

I want to write a Purescript function that reads input from HTML input elements on the browser and writes some output to another HTML input element.

With plain Javascript it's as simple as document.getElementById('output').value = myFun(document.getElementById('input'));. How do I do this with just Purescript?

Random dude
  • 519
  • 2
  • 11

2 Answers2

6

EDIT:

I've noticed that my answer doesn't meet the requirements - I'm only setting up an element value. If I find more time I probably add also reading from element value piece but you should be able to guess how to do this from the hints provided already :-)


In general when using PureScript you want to use some high level framework to manipulate the DOM like: halogen, react-basic, concur, spork, elmish, flare, hedwig, flame (for sure I've missed some others - sorry about that).

But if you really want to mutate the DOM by hand please don't be surprised that it is not as pleasant experience as in imperative JavaScript. It is on purpose - PureScript has the power to separate effects from pure functions and we have to work with Effect in every step here. On the other hand this gives us an unique ability to reason about the code and be sure where side effects can happen and which parts of our program are pure.

So let's use low level purescript-web-html. This library is low level but provides strict types around DOM API so like I said it requires quite a lot of manual value passing:

module Main where

import Prelude

import Data.Maybe (Maybe(..))
import Effect (Effect)
import Web.DOM.Document (toNonElementParentNode)
import Web.DOM.Element (setAttribute)
import Web.DOM.NonElementParentNode (getElementById)
import Web.HTML (window)
import Web.HTML.HTMLDocument (toDocument)
import Web.HTML.Window (document)

main :: Effect Unit
main = do
  w ← window
  d ← document w
  maybeElement ← getElementById "test-input" $ toNonElementParentNode $ toDocument  d
  case maybeElement of
    Nothing → pure unit
    Just elem → do
      setAttribute "value" "new-value" elem

This can be written a bit shorter using point free style so avoiding intermediate variables:

main :: Effect Unit
main = window >>= document >>= toDocument >>> toNonElementParentNode >>> getElementById "test-input" >>= case _ of
  Nothing → pure unit
  Just elem → setAttribute "value" "new-value" elem

Direct DOM manipulation is probably not the best way to start building a larger project or beginning the adventure with this really wonderful language. On the other hand it can be useful from time to time ;-)

paluh
  • 2,171
  • 20
  • 14
  • Thank you for your answer, I will try it. By the way, do you have any recommendations for Purescript frameworks that follow functional paradigm? I looked at Halogen but it seems very object oriented to me with its Component abstraction holding a state and providing methods to render and answer queries. – Random dude May 28 '20 at 00:03
  • I'm a happy user of _react-basic_ (tiny, opinionated layer on top of react so it is component based approach). I'm also using our internal fork of _spork_ ("_Elm_ architecture") framework so I can only recommend these two :-) adapted to be `canvas` rendering layer. – paluh May 28 '20 at 16:21
  • Regarding OO approach I don't think that some form of OO is by definition bad in the context of UI component modeling. Please take into account that there is no "subtyping / inheritance" or anything like that modeled in these frameworks so I consider components more as a state machines. State machines model which is based on a clear transition graph is a really useful and clear approach to model modular application I think :-) – paluh May 28 '20 at 16:23
-1

I used Foreign Function Interface (FFI) feature of Purescript as follows.

Define your Purescript module with imports of foreign functions you want to use. Here we have imported two functions.

-- Main.purs
foreign import getElementById :: String -> Effect String
foreign import setElementById :: String -> String -> Effect Unit 

Now create a Javascript file with the same name but .js extension. We will export JS functions from here for use in Purescript.

// Main.js
"use strict";

exports.getElementById = function(id) {
    return document.getElementById(id).value;
};

exports.setElementById = function(id) {
    return function(value) {
    document.getElementById(id).value = value;
    };
};

Now we can call getElementById and setElementById functions in our Purescript files.

chtenb
  • 14,924
  • 14
  • 78
  • 116
Random dude
  • 519
  • 2
  • 11
  • 1
    I see few issues with the FFI functions and types which you have provided for them. `getElementById` is not a pure function because it depends on the current state of the document and not only on the input `String`. The element could be missing from the DOM, given element can be other then HTML `input` (`value` attribute can be missing). The `value` can be changed between calls so subsequent calls to this functions returns different result which violates referential transparency property. `setElementById` should be wrapped in `function()` which is a representation of an `Effect` on the JS side – paluh May 26 '20 at 15:14
  • 1
    Could you please improve the above answer or add some warning that this snippet is somewhat incorrect? This is displayed as the first answer on stackoverflow for your question and can confuse people. – paluh May 28 '20 at 16:15
  • This method doesn't properly account for situations in which the element doesn't exist. E.g. getElementById should return `Effect (Maybe String)` or something like that. – chtenb May 22 '21 at 17:10
  • @paluh Please provide a more correct answer if you know one. People trying to learn PureScript are doing the best they can with the limited PureScript docs and examples available online. A working example is better than a criticism of someone's reasonable attempt at an answer. – devdanke Oct 18 '21 at 10:44
  • @davdanke you can find my answer just below. Should I extend it and provide more examples? – paluh Oct 18 '21 at 19:57