Scroll action in PureScript Halogen - purescript

I'm using purescript-halogen, and I want to scroll to bottom of div when the child component's message were caught.
However, it seems not present that scroll action control in Halogen.
So, how can I
Scroll to bottom of div?
One solution I think is that call other, not Halogen, process from Main when the event caught.
I'm not sure that this solution is not bad.

Setting the scroll position is just done through using the normal DOM functionality, targeting the rendered node.
To do this, you'll need to add a ref property in the HTML DSL to the node you want to scroll:
-- Define this in the same module as / in the `where` for a component
containerRef ∷ H.RefLabel
containerRef = H.RefLabel "container"
-- Use it with the `ref` property like so:
render =
HH.div
[ HP.ref containerRef ]
[ someContent ]
And then in the eval for the component you can get hold of the actual DOM element created, using getHTMLElementRef, and then update the scroll position on that:
eval (ScrollToBottom next) = do
ref ← H.getHTMLElementRef containerRef
for_ ref \el → H.liftEff do
scrollHeight ← DOM.scrollHeight el
offsetHeight ← DOM.offsetHeight el
let maxScroll ← scrollHeight - offsetHeight
DOM.setScrollTop maxScroll el
pure next
The snippets here are modified from some real world code that does something similar, so should do the trick!

Essentially the same answer as https://stackoverflow.com/a/44543329/1685973, but I had a hard time finding the different imports:
import Halogen as H
import Halogen.HTML as HH
import Data.Foldable (for_)
import DOM.HTML.Types (htmlElementToElement)
import DOM.Node.Element (scrollHeight, setScrollTop)
import DOM.HTML.HTMLElement (offsetHeight)
...
-- Define this in the same module as / in the `where` for a component
containerRef ∷ H.RefLabel
containerRef = H.RefLabel "container"
-- Use it with the `ref` property like so:
render =
HH.div
[ HP.ref containerRef ]
[ someContent ]
...
eval (ScrollToBottom next) = do
ref <- H.getHTMLElementRef containerRef
for_ ref $ \el -> H.liftEff $ do
let hel = htmlElementToElement el
sh <- scrollHeight hel
oh <- offsetHeight el
let maxScroll = sh - oh
setScrollTop maxScroll hel
pure next

Related

How do you access highlighted text in Purescript?

I'm creating an application in Purescript and I want to have a text box displaying some documentation, and then I want to perform some NLP tasks on the server based on the sentences highlighted by the mouse on that text.
How could I extract that text in Purescript?
You can subscribe to the selectionchange event in the initialize handler of your halogen component.
Init -> do
doc <- H.liftEffect $ Web.window >>= Window.document
void $ H.subscribe $
ES.eventListenerEventSource (EventType "selectionchange") (Document.toEventTarget doc)
(const $ Just OnSelectionChange)
Then write an FFI function to get current selected text, something like
foreign import getSelectionString :: Effect String
-- js
const getSelectionString = () => window.getSelection().toString()
Then use getSelectionString in the OnSelectionChange handler.
A not exactly the same example here
https://github.com/nonbili/halogen-contenteditable-example/blob/master/src/Example/Editor.purs#L178-L180

How to create a stateless and static Halogen component?

Consider this snippet from github, https://github.com/slamdata/purescript-halogen/blob/master/examples/basic/src/Button.purs#L42 which tries to render an html button using halogen library.
render :: State -> H.ComponentHTML Query
render state =
let
label = if state then "On" else "Off"
in
HH.button
[ HP.title label
, HE.onClick (HE.input_ Toggle)
]
[ HH.text label ]
eval :: Query ~> H.ComponentDSL State Query Message m
eval = case _ of
Toggle next -> do
state <- H.get
let nextState = not state
H.put nextState
H.raise $ Toggled nextState
pure next
IsOn reply -> do
state <- H.get
pure (reply state)
Is there any possible way to get the most 'barebone' UI control, just to render a static UI component, without states involved?
How about setting type State = Unit? Then your render function would look like
render :: State -> H.ComponentHTML Query
render _ = [...]
i.e. just ignore the parameter (since you can't get any information out of a Unit value anyway).

Purescript: Halogen HTML DSL only Renders "id" tags

I'm using purescript-halogen v0.12.0, and I can't figure out why only the id tag is rendering.
This occurs even with supposedly well supported elements, like div.
Example:
render = div [ id_ "some-id", name "some-name ] []
A div will be created, but only with an id attribute. This occurs with elements in Halogen.HTML as well as Halogen.HTML.Indexed.
Any help in the right direction would be appreciated.
=============================================================
Reproduce the issue with the following.
pulp init
bower i purescript-halogen
npm i virtual-dom
============
module Main where
import Prelude
import Control.Monad.Eff (Eff)
import Control.Monad.Eff.Console (CONSOLE, log)
import Halogen as H
import Halogen.HTML (div, text)
import Halogen.HTML.Properties (id_, name, pixels, height, width)
import Halogen.Util (awaitBody, runHalogenAff)
type State = Int
data Query a = Toggle a
ui :: forall g. (Functor g) => H.Component State Query g
ui = H.component { render, eval }
where
render :: State -> H.ComponentHTML Query
render st = div [ id_ "my-id", name "my-name", height (pixels 3), width (pixels 4) ] [ text "here!" ]
eval :: Query ~> H.ComponentDSL State Query g
eval (Toggle next) = pure next
main :: forall e. Eff (H.HalogenEffects e) Unit
main = runHalogenAff $ do
body <- awaitBody
H.runUI ui 0 body
This is occurring as name is not a valid property to apply to a div, nor are width or height - if you're using the Indexed elements and properties you'll see there's a type error when trying to set width or height. Try changing the div for an input and you'll see the properties applied as they should be.
The indexed elements do allow setting a name on the div however, which is a bug.
The reason these properties do not show in the rendered HTML is they are being set as properties rather than attributes. Properties must exist in the javascript interface for the element otherwise they are ignored. This isn't a Halogen thing so much as the DOM.

adding/changing 'text' to an item in a group

I'm designing a UI with Enthought's TraitsUI, and I can't figure out how to get done what I want...
Here's what I want:
I have Items() in the view that I want to display as either English or SI units. I can change the value in the 'edit' box based on a SI/English button, but I can't figure out how to change the text of the label. For example, if I have an item 'Length, ft [ 3.28]' and convert it to SI, I'd like it to show 'Length, m [ 1.00]'. I can handle the 3.28->1.00 conversion, but can't figure out how to change the 'ft' to 'm'.
Any suggestions?
One thing I've tried is to define a string which holds the units name (like 'm' or 'ft')...then, in the item, I set the label like this:
label = 'Top, '+lengthUnits
This works fine when the view is first built, but it doesn't update the label when I change the units control. Is there some way to force the view to update with all the new values?
Here's a small py program that shows what I'm trying to do (feel free to critique my style :)). I'll also try and add in a couple of images that shows what happens:
# NOTE: This version of the code has been modified so that it works as I want it to :)
# Example trying to change text on a View...
from traits.api \
import HasTraits, Enum, CFloat, String
from traitsui.api \
import View, Group, HGroup, Item, spring
class TestDialog ( HasTraits ):
length = CFloat(1.0)
choose_units = Enum('English', 'SI')
current_units = 'English'
unit_name = String('ft')
ft_to_m = CFloat(3.28)
view = View(
Group(
HGroup(
spring,
Item(name = "length", label = 'Test length'),
Item(name = 'unit_name', style = 'readonly', show_label = False),
spring
),
HGroup(
spring,
Item(name = "choose_units"),
spring
)
),
title = 'Test Changing View Test'
)
def _choose_units_changed(self):
if self.current_units != self.choose_units:
if self.choose_units == 'SI':
self.length /= self.ft_to_m
self.unit_name = 'm'
else:
self.length *= self.ft_to_m
self.unit_name = 'ft'
self.current_units = self.choose_units
# Run the program (if invoked from the command line):
if __name__ == '__main__':
# Create the dialog:
TestIt = TestDialog()
# put the actual dialog up...
TestIt.configure_traits()
Use a notification as described here: http://code.enthought.com/projects/traits/docs/html/traits_user_manual/notification.html
Update in response to updated question:
Right, labels are not dynamically updated. Instead, make a text field that looks like a label, e.g. with:
label_text = String('Test length, English:')
Then display it in your View with something like:
Item("label_text", style='readonly', show_label=False),
You'll probably also want to use an HGroup nested inside your (V)Group, to position it to the left of your "length" display.
Then modify label_text inside your listener.

GXT LayoutContainer with scrollbar reports a client height value which includes the area below the scrollbar

I have this code which sets up a "main" container into which other modules of the application will go.
LayoutContainer c = new LayoutContainer();
c.setScrollMode(Scroll.ALWAYS);
parentContainer.add(c, <...>);
Then later on, I have the following as an event handler
pContainer = c; // pContainer is actually a parameter, but it has c's value
pContainer.removeAll();
pContainer.setLayout(new FitLayout());
LayoutContainer wrapperContainer = new LayoutContainer();
wrapperContainer.setLayout(new BorderLayout());
wrapperContainer.setBorders(false);
pContainer.add(wrapperContainer);
LayoutContainer west = pWestContentContainer;
BorderLayoutData westLayoutData = new BorderLayoutData(LayoutRegion.WEST);
westLayoutData.setSize(pWidth);
westLayoutData.setSplit(true);
wrapperContainer.add(west, westLayoutData);
LayoutContainer center = new LayoutContainer();
wrapperContainer.add(center, new BorderLayoutData(LayoutRegion.CENTER));
pCallback.withSplitContainer(center);
pContainer.layout();
So in effect, the container called 'west' here will be where the module's UI gets displayed. That module UI then does a simple rowlayout with two children. The botton child has RowData(1, 1) so it fills up all the available space.
My problem is that the c (parent) container reports a height and width value which includes the value underneath the scrollbars. What I would like is that the scrollbars show all the space excluding their own space.
This is a screenshot showing what I mean:
alt text http://img265.imageshack.us/img265/9206/scrollbar.png
Try adding padding equivalent to the scroll bars size 14px? on the container where the Scroll.Always is being applied