I'm using purescript-halogen to build a spreadsheet-like table (similar to Handsontable). If you double-click a cell, an html input element is rendered as a child of the respective table cell (and no such element is rendered for all the other cells).
This works really well with halogen, except that I don't know how to automatically set the focus to the newly created input element.
I tried the autofocus attribute, but this only works for the first cell that is double-clicked. The JavaScript way to do it is by calling the focus() method on the new element, but I don't know how to call it after the DOM has been updated in halogen. Any ideas?
Ok, here is how I did it using Phil's Initializer hint:
Write a JavaScript function that actually focuses the element.
exports.setFocusImpl = function(elemId) {
return function() {
document.getElementById(elemId).focus();
};
};
FFI it.
foreign import data FOCUS :: !
foreign import setFocusImpl :: forall e. Fn1 String (Eff (focus :: FOCUS | e) Unit)
setFocus :: forall e. String -> Eff (focus :: FOCUS | e) Unit
setFocus = runFn1 setFocusImpl
And then use the setFocus function in the initializer.
H.input
[ A.id_ "inputField"
, A.Initializer do
liftEff $ setFocus "inputField"
pure DoNothing
] [ ]
Note that I'm using an old version of halogen where the signature is still the old one (definition of Initializer in 30e8b2c7).
Related
Originally I wanted to know:
How do I write a handler for this?
type state = string;
type action = | ChangeName(string)
let reducer = (_state, action) => switch action { | ChangeName(text) => text }
[#react.component]
let make = () => {
let (state, dispatch) = React.usefReducer(reducer, "");
/* What is the parameter type and how do I decode it? */
let onChange = ??? => dispatch(ChangeText(????));
<input value=state onChange/>
}
Specifically, what is the parameter type for the punned onChange handler and how do I decode it?
Every reference I come across is for JS, which I'm having difficulty translating to Re.
EDIT
The answer I found by scraping github:
let onChange = event => dispatch(ChangeName(ReactEvent.Form.target(event)##value));
Say I'd like to use another JSX element, where's the documentation? OR, is their a supposition that people coming to this from elsewhere have knowledge apriori? (I'm mostly comfortable with 'c').
You can get the types of all the DOM attributes from https://github.com/rescript-lang/rescript-react/blob/v0.10.1/src/ReactDOM.res
This file contains bindings to ReScript-React's subset of DOM attributes. It has:
onChange: ReactEvent.Form.t => unit
ReactEvent.Form module is declared at https://github.com/rescript-lang/rescript-react/blob/v0.10.1/src/ReactEvent.resi#L168
When looking for anything specific to ReScript-React, search that repo.
Looks like you have the correct code to handle the event now. Btw, you have in some places the variant constructor ChangeName and in others ChangeText, I assume the correct one is one of those. The compiler will of course catch this too :-)
I have the following code:
workWithImportantField :: forall fields. { importantField :: Int | fields } -> Input
workWithImportantField = ...
workWithImportantField $
maybe { importantField: 1 } identity (Just { importantField: 1, fieldIDontCareAbout: "whatever" })
This doesn't compile because the first record doesn't have the fieldIDontCareAbout. However, I'm perfectly fine if it unifies into forall fields. { importantField :: Int | fields } so it gets passed into workWithImportantField. How would I go about doing that?
I've tried adding type annotations to various places (first record, second record, the whole expression) without success. I can always replace identity with unsafeCoerce, but I would like a type-safe solution. I can also manually pick out the fields I need by replacing identity with \{ importantField } -> { importantField }, but that doesn't look nice.
Replacing identity with pick found at Record.Extra "throws away" fieldIDontCareAbout from the second record so the types unify.
I have the following PureScript snippets; note parseXMLFromString is partially applied:
parseXMLFromString ∷ String → DOMParser → Effect Document
parseXMLFromString s d =
parseFromString "application/xml" s d
parseNoteDoc :: DOMParser -> Effect Document
parseNoteDoc = parseXMLFromString TD.noteXml
note <- parseNoteDoc domParser
The following code is generated:
// Generated by purs version 0.12.4
"use strict";
var Effect_Console = require("../Effect.Console/index.js");
var Test_Data = require("../Test.Data/index.js");
var Web_DOM_DOMParser = require("../Web.DOM.DOMParser/index.js");
var parseNoteDoc = Web_DOM_DOMParser.parseXMLFromString(Test_Data.noteXml);
var main = function __do() {
var v = Web_DOM_DOMParser.makeDOMParser();
var v1 = parseNoteDoc(v)();
return Effect_Console.log("TODO: You should add some tests.")();
};
module.exports = {
parseNoteDoc: parseNoteDoc,
main: main
};
The line var v1 = parseNoteDoc(v)(); gives the error TypeError: parseNoteDoc(...) is not a function.
I'm not sure where the extra () is coming from on parseNoteDoc but that is the issue. When I manually remove the () in the generated source, it works works as expected.
Update: Added the code to reproduce this on this branch. After the usual formalities, npm run testbrowser and open dist/index.html in a browser.
TL;DR: your FFI code is incorrect, you need to add an extra function().
Longer explanation:
The extra empty parens come from Effect.
This is how effectful computations are modeled in PureScript: an effectful computation is not a value, but a "promise" of a value that you can evaluate and get the value as a result. A "promise" of a value may be modeled as a function that returns a value, and this is exactly how it's modeled in PureScript.
For example, this:
a :: Effect Unit
is compiled to JavaScript as:
function a() { return {}; }
and similarly, this:
f :: String -> Effect Unit
is compiled to JavaScript as:
function f(s) { return function() { return {}; } }
So it takes a string as a parameter, and then returns Effect Unit, which is itself a parameterless function in JS.
In your FFI module, however, you are defining parseFromString as:
exports.parseFromString = function (documentType) {
return function (sourceString) {
return function (domParser) {
return domParser.parseFromString(sourceString, documentType);
};
};
};
Which would be equivalent to parseFromString :: String -> String -> DOMParser -> Document - i.e. it takes three parameters, one by one, and returns a parsed document.
But on the PureScript side you're defining it as parseFromString :: String -> String -> DOMParser -> Effect Document - which means that it should take three parameters, one by one, and then return an Effect Document - which should be, as described above, a parameterless function. And it is exactly this extra parameterless call that fails when you try to evaluate that Effect Unit, which in reality is not an Effect at all, but a Document.
So, in order to fix your FFI, you just need to insert an extra parameterless function, which will model the returned Effect:
exports.parseFromString = function (documentType) {
return function (sourceString) {
return function (domParser) {
return function() {
return domParser.parseFromString(sourceString, documentType);
}
};
};
};
(it is interesting to note that makeDOMParser :: Effect DOMParser is correctly modeled in your FFI module as a parameterless function)
But there is a better way
These pyramids of nested functions in JS do look quite ugly, you have to agree. So it's no surprise that there is an app for that - EffectFn1, runEffectFn1, and friends. These are wrappers that "translate" JavaScript-style functions (i.e. taking all parameters at once) into PureScript-style curried effectful functions (i.e. taking parameters one by one and returning effects).
You can declare your JS side as a normal JS function, then import it into PureScript as EffectFnX, and call it using runEffectFnX where needed:
// JavaScript:
exports.parseFromString = function (documentType, sourceString, domParser) {
return domParser.parseFromString(sourceString, documentType);
};
-- PureScript:
foreign import parseFromString ∷ EffectFn3 String String DOMParser Document
parseHTMLFromString ∷ String → DOMParser → Effect Document
parseHTMLFromString s d =
runEffectFn3 parseFromString "text/html" s d
P.S. People who purchased EffectFn1 also liked Fn1 and friends - same thing, but for pure (non-effectful) functions.
In my custom Halogen/Purescript project I follow the pattern from the AJAX Example where I split my actions up into pure Inputs and effectful Requests.
I want to change my event handler to use the preventDefault behavior, but don't understand what consequences for the type of the UI function this entails.
I made the same changes to the AJAX Example by changing the event handler the following way:
Before:
H.button [ A.classes [B.btn, B.btnPrimary]
, A.disabled busy
, A.onclick (\_ -> pure (handler code))
] [ H.text "Compile" ]
After:
H.a [ A.classes [B.btn, B.btnPrimary]
, A.href "#compile"
, A.disabled busy
, A.onclick (\_ -> E.preventDefault $> pure (handler code))
] [ H.text "Compile" ]
(Full diff available here)
I end up with this type error:
Cannot unify type
Example.Ajax.Input
with type
Halogen.HTML.Events.Monad.Event Halogen.HalogenEffects<(http ::
Example.Ajax.HTTP | u32519)> Example.Ajax.Input
At this point, I'm a bit lost whether I would need to adjust the type signature of the UI function or I apply the preventDefault modifier the wrong way.
The type of $> looks like:
($>) :: forall a. EventHandler a -> b -> EventHandler b
The type of pure looks like:
pure :: forall a. a -> EventHandler a
So the problem is by using both together, you're making a type which looks like this:
EventHandler a -> EventHandler b -> EventHandler (EventHandler b)
But you don't want that, you just want an EventHandler b, where b is the E.Event type of handler code.
The best solution is to just not use pure:
E.preventDefault $> handler code
If sometime you do have two EventHandler values you want to use together like this, the function to use is *> instead of $>.
I am new to Lift, I want to use hyperlink instead of submit button. I am able to bind my submit button with a server-side method use CSS Selector. for example:
def render = {
// define some variables to put our values into
// process the form
def process() {
do something....
}
}
"type=submit" #> SHtml.onSubmitUnit(process)
}
I want to use hyperlink to submit my form instead of submit button. How can I bind hyperlink with process()(server-side) method.
Thanks,
Puneet
In this instance you would probably want to use SHtml.ajaxCall and supply the form information as the JsonContext (i.e. not bound with CSS selectors):
def ajaxCall (jsCalcValue: JsExp, jsContext: JsContext, func: (String) ⇒ JsCmd) : (String, JsExp)
Alternativly you could use SHtml.a:
def a (func: () ⇒ JsObj, jsonContext: JsonContext, body: NodeSeq, attrs: ElemAttr*) : Elem
Failing that you should look at the available methods in SHtml (jsonForm would be another one to look at) and see which one best fits your use case. I would encourage you to pick up a copy of Lift in Action which discusses how the function binding works, as I think you have miss-understood it in relation to the request/response cycle.