Rescript Capitalised Component - reason

From the Rescript Documentation, it is suggested spread can be used to enable passing a pre-existing list to a component. I am confused what exactly MyComponentis in Rescript as I cannot find a way to initialise a component, which can be done with a function in vanilla React.
<MyComponent>...myChild</MyComponent>
where myChild = list{child1,child2}
After several attempts, the followings do not work:
#JSX div(~children=myChild) , because Rescript asks for wrapping it in a list as in list{myChild}
#JSX div(~children=list{myChild}), which gives a type error
Initialising a module named MyComponent, and do <MyComponent> ...myChild </MyComponent>, but this gives the error The value make can't be found in MyComponent
Initialising a function with a capitalisation escape: let \"MyComponent" = () => ..., but this gives the error The module or file MyComponent can't be found.
What I would love is an example of the initialization of the component MyComponent which can be used as a capitalised tag like <MyComponent>...myChild</MyComponent>. Thank you in advance.

module MyComponent = {
#react.component
let make = (~children: list<React.element>) => {
<div> {Belt.List.toArray(children)->React.array} </div>
}
}
From Rescript Forum.

Related

Open Web Components Testing / Lit - component not being rendered?

I'm trying to test my Lit components with #open-wc/testing. Lit has an example repo here, with this test:
https://github.com/lit/lit-element-starter-ts/blob/main/src/test/my-element_test.ts#L44
When I try to render my element like they do in their example, I get this error:
jtests/components/coding-editor.test.ts:
🚧 Browser logs:
HTMLElement: <coding-editor></coding-editor>
❌ renders
TypeError: Cannot read properties of null (reading 'querySelector')
at o.<anonymous> (jtests/components/coding-editor.test.ts:16:30)
My component works in the browser and uses the name "coding-editor". It's as if this test renderer has no idea that I'm using a custom component though. I don't know why shadowRoot is null in my case.
My code is roughly this:
import { CodingEditor } from '../../app/javascript/components/coding-editor';
import {expect, fixture} from '#open-wc/testing';
import {html} from 'lit/static-html.js';
it('renders', async () => {
const el = await fixture(html`
<coding-editor></coding-editor>
`) as CodingEditor;
console.log(el);
const text = el.shadowRoot!.querySelector('.table-constrainer');
// expect(text).to.not.be.null
});
How can I get my test to render this properly, with the shadowRoot populated?
This is likely due to TypeScript removing the CodingEditor import that's only used as a type so the side effect of defining the custom element is not happening.
You can either set the TS compiler option importsNotUsedAsValues to preserve (See https://www.typescriptlang.org/tsconfig/#importsNotUsedAsValues) or add another import line for the side-effect like
import '../../app/javascript/components/coding-editor';
Additional explanation here too: https://github.com/Microsoft/TypeScript/wiki/FAQ#why-are-imports-being-elided-in-my-emit
As a side-note, in the starter example you linked to, the imported class is used in assert.instanceOf as a value so it does not get elided by TypeScript.

Where is the documentation to write an event handler for input text box?

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 :-)

#JSGlobalScope in scala.js 1.0 (JavaScriptException, ReferenceError, var is not defined)

After migrating from scala.js 0.6.x to 1.0, I've got some code related to #JSGlobalScope broken.
My use case is like this:
there's a 3rd-party library that expects some global var to be set to a function
when loaded and ready, it calls this function (by name)
I set this function in global scope from scala.js
The code looks like this:
#js.native
#JSGlobalScope
object Globals extends js.Object {
var callbackFunctionFor3rdPartyLib: js.Function0[Unit] = js.native
}
then I set this var like this:
Globals.callbackFunctionFor3rdPartyLib = () => {
// do things
}
and then I add the script into the DOM.
This was working with scala.js 0.6.x, but with 1.0 I'm getting an exception like the following:
scala.scalajs.js.JavaScriptException: ReferenceError: callbackFunctionFor3rdPartyLib is not defined
In the changelog for 1.0.0 there's a "Breaking changes" section that mentions this:
Accessing a member that is not declared causes a ReferenceError to be thrown
...
js.Dynamic.global.globalVarThatDoesNotExist = 42
would previously create said global variable. In Scala.js 1.x, it also throws a ReferenceError.
My question is:
what is the right way to do something like this (create a new global var) in scala.js 1.0?
If you know you'll always be in a browser context, you can use #JSGlobal("window") instead of #JSGlobalScope on your Globals, which will then be equivalent to doing window.myGlobalVarFor3rdPartyLib in JS. So that will work.
#js.native
#JSGlobal("window")
object Globals extends js.Object {
var callbackFunctionFor3rdPartyLib: js.Function0[Unit] = js.native
}
If not, but you are using a script (so not a CommonJS nor an ES module), the best thing is actually to use
object Globals {
#JSExportTopLevel("myGlobalVarFor3rdPartyLib")
var foo: js.Function[Unit] = ...
}
Note that Globals is a normal Scala object now, not a JS one.
The #JSExportTopLevel creates a top-level var myGlobalVarFor3rdPartyLib at the top of the script, and then assigning Globals.foo will also assign that top-level var.
If you're not using a script nor know that you're going to always be in a browser, then you need to figure out the global object yourself. Scala.js 0.6.x tried to do that for you, but could fail, so we don't do that anymore. You can at least follow the "instructions" on the documentation of js.special.fileLevelThis to reproduce what Scala.js 0.6.x was doing. I repeat the instructions here:
Using this value should be rare, and mostly limited to writing code
detecting what the global object is. For example, a typical detection
code--in case we do not need to worry of ES modules--looks like:
val globalObject = {
import js.Dynamic.{global => g}
if (js.typeOf(g.global) != "undefined" && (g.global.Object eq g.Object)) {
// Node.js environment detected
g.global
} else {
// In all other well-known environment, we can use the global `this`
js.special.fileLevelThis
}
}
Note that the above code is not comprehensive, as there can be JavaScript
environments where the global object cannot be fetched neither through
global nor this. If your code needs to run in such an environment, it
is up to you to use an appropriate detection procedure.

Form errors i18n Play framework

I'm using the form helper and a custom form template to render my forms in the Play framework like this:
#(lang: Lang)(myForm: Form[MyModel])
#import play.i18n._
#import helper._
#implicitField = #{ FieldConstructor(formTemplate.f) }
#form ( action = routes.Application.index() ){
#inputText(
field = myForm("username"),
'_label -> Messages.get(lang, "username")
)
}
When the template is called with different values for lang, the label is displayed in the respective language.
However, when the form is submitted, error messages are always displayed in the main language. (i.e. for Required fields it's always This field is required.)
As the answer to this post mentioned, I changed the default error messages like so in my language files (currently only 2):
messages.en:
username=Username
error.required=This field is required
messages.nl:
username=Gebruikersnaam
error.required=Dit veld is verplicht
How can I make sure the errors are printed in the correct language?
I've already tried doing the following in my custom template, but without success:
#(elements: helper.FieldElements)
<!-- snipped some HTML code -->
<span class="help">
#(elements.infos(elements.args.get('_lang) match {
case Some(language) => language.asInstanceOf[play.api.i18n.Lang]
case None => new Lang("en","uk")
}).mkString(", "))
</span>
And by adding '_lang -> lang to my #inputText call.
I'm used to programming in Java and have only done some Scala in the Play templates. I'm using Play 2.0.4.
I have found the easiest way of doing this (note: I program in Java) is by defining a static method in one of your models that returns the users language:
public class User{
import play.i18n.Lang;
//simplified
public static Lang getLanguage(){
if(session("language" != null){
return Lang.forCode(session.get("language"));
} else {
return Lang.forCode("en"); //default language
}
}
You can then call this static function in your Scala form template like this:
<span class="errors">#elements.errors(User.getLanguage()).mkString(", ")</span>
to display translated errors based on the default error messages in your messages.xx files.
As a general matter, if your error codes are also found in the messages.xx resource files, they get localized, even if you program a custom validator somewhere else. You don't have to have the Lang in scope or call Messages() yourself. E.g. in Scala Play:
val validPhone = """\+?[0-9_\-\. \(\)]*$""".r
val phoneCheckConstraint: Constraint[String] = Constraint("constraints.phonecheck")({
plainText =>
val errors = plainText match {
case validPhone() => Nil
case _ => Seq(ValidationError("error.phonenumber"))
}
if (errors.isEmpty) {
Valid
} else {
Invalid(errors)
}
})
If you merely have
error.phonenumber=Invalid phone number
in your messages.en file and translated versions in other messages.xx files they will get localized by Play even though no Lang was in scope at the point of declaration. So no need to pass Lang around other than in your templates and elsewhere for explicit Messages() calls.

Cannot access the parameter of a Menu.param from a Lift Snippet

I'm trying to extract the parameter from a Lift Menu.param within a snippet so that I can use it to create a named Comet. However, I get a NullPointerException when I try to pass the parameter to the snippet using SnippetDisptach in my Boot.scala, as suggested here:
http://comments.gmane.org/gmane.comp.web.lift/44299
I've created the Menu item as follows:
object AnItemPage {
// create a parameterized page
def menu = Menu.param[Item]("Item", "Item",
s => fetchItem(s), item => item._id.toString) / "item"
private def fetchItem(s:String) : Box[Item] = synchronized {
ItemDAO.findById(ObjectId.massageToObjectId(s))
}
}
I've added the menu to SiteMap. I've also created a Snippet which I would like to pick up the Item parameter. (I'm using fmpwizard's InsertNamedComet library here):
class AddCometItemPage(boxedItem: Box[Item]) extends InsertNamedComet with DispatchSnippet{
val item : Item = boxedItem.openOr(null)
override lazy val name= "comet_item_" + item._id.toString
override lazy val cometClass= "UserItemCometActor"
def dispatch = null
}
My next step is to crate an instance of this class as demonstrated by David Pollak here:
http://comments.gmane.org/gmane.comp.web.lift/44299
This is what I have added to my Boot.scala:
LiftRules.snippetDispatch.append {
case "item_page" => new AddCometItemPage(AnItemPage.menu.currentValue)
}
My item.html references this snippet:
<div class="lift:item_page">
I get the following null pointer exception when I compile and run this:
Exception occurred while processing /item/5114eb4044ae953cf863b786
Message: java.lang.NullPointerException
net.liftweb.sitemap.Loc$class.siteMap(Loc.scala:147)
net.liftweb.sitemap.Menu$ParamMenuable$$anon$9.siteMap(Menu.scala:170)
net.liftweb.sitemap.Loc$class.allParams(Loc.scala:123)
net.liftweb.sitemap.Menu$ParamMenuable$$anon$9.allParams(Menu.scala:170)
net.liftweb.sitemap.Loc$class.net$liftweb$sitemap$Loc$$staticValue(Loc.scala:87)
net.liftweb.sitemap.Menu$ParamMenuable$$anon$9.net$liftweb$sitemap$Loc$$staticValue(Menu.scala:170)
net.liftweb.sitemap.Loc$$anonfun$paramValue$2.apply(Loc.scala:85)
net.liftweb.sitemap.Loc$$anonfun$paramValue$2.apply(Loc.scala:85)
net.liftweb.common.EmptyBox.or(Box.scala:646)
net.liftweb.sitemap.Loc$class.paramValue(Loc.scala:85)
net.liftweb.sitemap.Menu$ParamMenuable$$anon$9.paramValue(Menu.scala:170)
net.liftweb.sitemap.Loc$$anonfun$currentValue$3.apply(Loc.scala:114)
net.liftweb.sitemap.Loc$$anonfun$currentValue$3.apply(Loc.scala:114)
net.liftweb.common.EmptyBox.or(Box.scala:646)
net.liftweb.sitemap.Loc$class.currentValue(Loc.scala:114)
net.liftweb.sitemap.Menu$ParamMenuable$$anon$9.currentValue(Menu.scala:170)
bootstrap.liftweb.Boot$$anonfun$lift$8.apply(Boot.scala:107)
bootstrap.liftweb.Boot$$anonfun$lift$8.apply(Boot.scala:106)
net.liftweb.util.NamedPF$$anonfun$applyBox$1.apply(NamedPartialFunction.scala:97)
net.liftweb.util.NamedPF$$anonfun$applyBox$1.apply(NamedPartialFunction.scala:97)
net.liftweb.common.Full.map(Box.scala:553)
net.liftweb.util.NamedPF$.applyBox(NamedPartialFunction.scala:97)
net.liftweb.http.LiftRules.snippet(LiftRules.scala:711)
net.liftweb.http.LiftSession$$anonfun$net$liftweb$http$LiftSession$$findSnippetInstance$1.apply(LiftSession.scala:1506)
net.liftweb.http.LiftSession$$anonfun$net$liftweb$http$LiftSession$$findSnippetInstance$1.apply(LiftSession.scala:1506)
net.liftweb.common.EmptyBox.or(Box.scala:646)
net.liftweb.http.LiftSession.net$liftweb$http$LiftSession$$findSnippetInstance(LiftSession.scala:1505)
net.liftweb.http.LiftSession$$anonfun$locateAndCacheSnippet$1$1$$anonfun$apply$88.apply(LiftSession.scala:1670)
net.liftweb.http.LiftSession$$anonfun$locateAndCacheSnippet$1$1$$anonfun$apply$88.apply(LiftSession.scala:1669)
Has anybody any idea where I'm going wrong? I've not been able to find a lot of information on Menu.param.
Thank you very much for your help.
f
I have never tried what you are doing, so I am not sure the best way to accomplish it. The way you are using the Loc Param, you are extracting a variable from a URL pattern. In your case, http://server/item/ITEMID where ITEMID is the string representation of an Item, and which is the value that gets passed to the fetchItem function. The function call will not have a value if you just arbitrarily call it, and from what I can see you are requesting a value that is not initialized.
I would think there are two possible solutions. The first would be to use S.location instead of AnItemPage.menu.currentValue. It will return a Box[Loc[Any]] representing the Loc that is currently being accessed (with the parameters set). You can use that Loc to retrive currentValue and set your parameter.
The other option would be to instantiate the actor in your snippet. Something like this:
item.html
<div data-lift="AnItemPage">
<div id="mycomet"></div>
</div>
And then in your AnItemPage snippet, something like this:
class AnItemPage(item: Item) {
def render = "#mycomet" #> new AddCometItemPage(item).render
}
I haven't tested either of those, so they'll probably need some tweaking. Hopefully it will give you a general idea.