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).
Related
I have a list view present in the home screen widget of my android app. I am using a flutter plugin called home widget to enable communication between kotlin and dart code. There are two elements in every item of the listview: a checkbox and text. When the checkbox is clicked, I want to execute HomeWidgetBackgroundIntent.getBroadcast method to trigger a background callback in my dart code(this makes some changes in the database) and when the text is clicked I want to use HomeWidgetLaunchIntent.getActivity method to open my app with a specific URI. Both these methods are pending intents. The problem is that android doesn't allow you to add different pending intents for one single list item. They want developers to use fillInIntent for individual items and then set common pending intent template. How I am supposed to detect which element is clicked(checkbox or text) and set a different pending intent template for both cases? I have also thought of letting the pending intent template to trigger the receiver of the widget and then call the home_widget plugin methods here(based on data set in fillInIntent). But since the methods are pending intents, how can I trigger pending intents here(usually pending intents are triggered on click event but its not possible to trigger them in onReceive method right)?
Code pertaining to my widget:
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray, widgetData: SharedPreferences) {
Log.d("debugging", "widget loaded")
val sharedPref: SharedPreferences = context.getSharedPreferences(
"ApplicationListener", Context.MODE_PRIVATE)
appWidgetIds.forEach { widgetId ->
val views = RemoteViews(context.packageName, R.layout.widget_layout).apply {
val todosStr = widgetData.getString("todos", "null")
val todos = ArrayList<HashMap<String, Any>>()
val todosRemoteView = RemoteViews.RemoteCollectionItems.Builder()
if(todosStr != "null"){
val jObj = JSONObject(todosStr)
val jsonArry = jObj.getJSONArray("todos")
for (i in 0 until jsonArry.length()) {
val todo = HashMap<String, Any>()
val obj = jsonArry.getJSONObject(i)
todo["id"] = obj.getInt("id")
todo["taskName"] = obj.getString("taskName")
todos.add(todo)
val view = RemoteViews(context.packageName, R.layout.each_todo).apply {
setTextViewText(R.id.each_todo_container_text, todo["taskName"].toString())
setCompoundButtonChecked(R.id.each_todo_container_checkbox, todo["finished"].toString().toBoolean())
//i want to have a separate pending intent for the text view and the check box
val backgroundIntent = HomeWidgetBackgroundIntent.getBroadcast(context,
Uri.parse("myAppWidget://todo_checked/${todo["id"].toString()}"))
setOnCheckedChangeResponse(
R.id.each_todo_container_checkbox,
RemoteViews.RemoteResponse.fromPendingIntent(backgroundIntent)
)
//this pending intent doesn't get triggered
val fillInIntent = Intent().apply {
Bundle().also { extras ->
extras.putInt(EXTRA_ITEM, todo["id"].toString().toInt())
putExtras(extras)
}
}
setOnClickFillInIntent(R.id.each_todo_container_text, fillInIntent)
}
todosRemoteView.addItem(todo["id"].toString().toInt().toLong(), view)
}
}
setRemoteAdapter(
R.id.todos_list,
todosRemoteView
.build()
)
val editTodoIntent = HomeWidgetLaunchIntent.getActivity(
context,
MainActivity::class.java,
Uri.parse("http://edit_todo/$timeType"))
setPendingIntentTemplate(R.id.todos_list, editTodoIntent)
//how to set two different pending intent templates here for the text view and checkbox?
}
appWidgetManager.updateAppWidget(widgetId, views)
}
}
override fun onReceive(context: Context?, intent: Intent?) {
val viewIndex: Int = intent!!.getIntExtra(EXTRA_ITEM, 0)
Log.d("debugging", "an item is clicked $viewIndex")
//i can make the pending intent template of the list view trigger this onReceive function and I can use the data inside intent(set by fillInIntent) to find out which element of the two was clicked
//but how can I trigger the pending intent here?
super.onReceive(context, intent)
}
}
So What I want to do is to press a button and inside the ButtonClicked Event I want to wait completing the event, until I press a specific button/one of the specified buttons.
I also know that there are some similar questions to this topic but I wasnt able to find a fix out of the answers
So basically this:
reactions += {
case event.ButtonClicked(`rollDice`) =>
some code ...
wait for one of the specified buttons to be pressed and then continue
some code ...
Is there an easy way to solve this problem without the use of threads?
There are certainly some abstractions you could set up around the event layer, but you asked for "easy", so my suggestion is to set a flag/state when the dice are rolled, and then check for that flag in the other buttons' event handler.
private var postDiceRollState: Option[InfoRelatedToRollDice] = None
reactions += {
case event.ButtonClicked(`rollDice`) =>
// capture whatever info you need to pass along to the later button handler
val relevantInfo = /* some code... */
// store that info in the "state"
postDiceRollState = Some(relevantInfo)
case event.ButtonClicked(other) if isPostDiceRollButton(other) =>
// when the other button gets clicked, pull the relevant info from your "state"
postDiceRollState match {
case Some(relevantInfo) =>
postDiceRollState = None // clear state
doInterestingStuff(relevantInfo) // "some code..."
case None =>
// nothing happens if you didn't roll the dice first
}
}
Note: I represented the "flag" as an Option, under the assumption that you might have some information you want to capture about the rollDice event. If you don't actually have anything to put in there, you could represent your state as private var didRollDice: Boolean = false and set/clear would be setting it to true/false respectively.
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
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.
I am still trying to wrap my head around rxjs and observables and BehaviorSubject. What I would like to do is, combine BehaviorSubject and LocalStorage so that all components get notified when a particular LocalStorage variable changes.
For example, consider the following scenario.
There are two components Component1 and Component2.
Both these components look for a variable in LocalStorage called Component1 and Component2, which contain a color and they display a square of that color.
Component1 needs to subscribe to "Component1Color" key in LocalStorage and Component2 needs to subscribe to "Component2Color" in LocalStorage.
One way to do this is have a BehaviorSubject that maintains the state of LocalStorage and anytime a change is made to any variable, broadcast this message to all the components that have subscribed to the BehaviorSubject.
The problem with this approach is that when Component2Color is updated, component1 gets notified as well, and will do nothing about it.
What would be nice is, Component1 gets notified only when Component1Color is updated, and Component2 gets notified only when Component2Color is updated. Is there a way this can be done using a single BehaviorSubject?
You can listen to the storage event via the StorageEvent
const storage = Rx.Observable.fromEvent(window, 'storage').groupBy(ev => ev.key);
Then you can access each stream by doing:
let component1Stream = storage.filter(x => x.key === 'Component1Color').merge();
//Or using flatMap
let component2Stream = storage.flatMap(x =>
x.key === 'Component2Color' ? x : Rx.Observable.empty()
);
Edit
As was pointed out in the comments, the storage event is only useful for cross page updates it won't update if you are on the same page. Fortunately, the solution remains pretty much the same, you just need to provide a decorator object for the localStorage object so that it can emit events.
class RxLocalStorage {
private _subject: Rx.Subject<any>;
private _updates: Observable<any>;
constructor() {
this._subject = new Rx.Subject();
this._updates = this._subject.groupBy(ev => ev.key)
.share();
}
getItem(key) { return this._updates.filter(x => x.key === key).merge(); }
setItem(key, newValue) {
const oldValue = localStorage.getItem(key);
const event = {key, newValue, oldValue};
localStorage.setItem(newValue);
this._subject.next(event);
}
}
let myLocalStorage = new RxLocalStorage();
myLocalStorage.getItem('Component1Color')
.subscribe(x => console.log('Component1 Color changed'));
myLocalStorage.setItem('Component1Color', 'blue');
//-> Component1 Color changed
Note the above assumes that all of your subscriptions are made before you begin making changes.