How would I write the following JavaScript:
var element = document.querySelector('.element')
element.style.color = 'red'
in Reason?
So far I have:
[###bs.config {no_export: no_export}];
external document : Dom.document = "document" [##bs.val];
external query_selector : Dom.document => string => Dom.element = "querySelector" [##bs.send];
let element = query_selector document ".element";
And that compiles just fine.
But how would I be able to set an attribute (i.e. style) on element?
So first of all, this is available in bs-webapi already. But if you want to recreate (and simplify) it, here's how:
external document : Dom.document = "document" [##bs.val];
external querySelector : string => option Dom.element = "" [##bs.send.pipe: Dom.document] [##bs.return null_to_opt];
external style : Dom.element => Dom.cssStyleDeclaration = "" [##bs.get];
external setColor : Dom.cssStyleDeclaration => string => unit = "color" [##bs.set];
let () =
switch (document |> querySelector ".element") {
| Some element => setColor (style element) "red";
| None => ()
};
You can also throw type-safety out the window and just do it like this:
external document : Js.t {..} = "document" [##bs.val];
let () = {
let element = document##querySelector ".element";
element##style##color #= "red"
};
But then I'm guessing you're doing this to learn, in which case the latter would be a terrible idea.
One way to do it is:
[###bs.config {no_export: no_export}];
external document : Dom.document = "document" [##bs.val];
external query_selector : Dom.document => string => Dom.element = "querySelector" [##bs.send];
external set_attribute : Dom.element => string => string => unit = "setAttribute" [##bs.send];
let element = query_selector document ".element";
set_attribute element "style" "color: red";
However, I'm not sure if there is a better way.
Notes:
[###bs.config {no_export: no_export}]; prevents Bucklescript from exporting ES6 modules.
The Dom module provides a bunch of types.
Unanswered Questions:
How can I take advantage of the Dom module's attr type instead of using string?
How can I take advantage of the Dom module's cssStyleDeclaration type instead of using string?
Related
I am having trouble figuring out how to get or filter a bunch of childNodes by their style class name inside my useEffect. Using ReactJs v18.
Straight after the line with: const circleElements = launcherCircle!.childNodes; I would like to get/filter the div's with the class name 'launcherPos' so I can position them in a circle formation.
const LauncherComponent = () => {
const launcherCircleRef = useRef<HTMLDivElement>(null);
let modules: Module[] | null = GetModules();
const enableLauncher = (module: Module) => {
return !module.IsEnabled ? styles['not-active'] : null;
};
useEffect(() => {
const launcherCircle = launcherCircleRef.current;
const circleElements = launcherCircle!.childNodes;
let angle = 360 - 190;
let dangle = 360 / circleElements.length;
for (let i = 0; i < circleElements.length; i++) {
let circle = circleElements[i] as HTMLElement;
angle += dangle;
circle.style.transform = `rotate(${angle}deg) translate(${launcherCircle!.clientWidth / 2}px) rotate(-${angle}deg)`;
}
}, []);
if (modules == null){
return <Navigate replace to={'/noaccess'} />
} else {
return (
<div data-testid="Launcher" className={styles['launcherContainer']} >
<div className={styles['launcherCircle']} ref={launcherCircleRef}>
{modules.map(function (module: Module, idx) {
return (
<div key={idx} className={styles['launcherPos']} ><div className={`${styles['launcherButton']} ${enableLauncher(module)}`}><img src={module.ImagePath} alt={module.Prefix} /></div></div>
)
})}
<div className={styles['launcherTextDiv']}>
<span>TEST</span>
</div>
</div>
</div>
)
}
};
export default LauncherComponent;
From what I've read getElementsByClassName() is not advisable practise because of the nature of ReactJs and it's virtual DOM.
I tried the following filter but I think with React garburling the class name I didn't get anything back.
const launcherChildren = launcherCircle!.children;
const circleElements = [...launcherChildren].filter(element => element.classList.contains('launcherPos'));
Maybe there's a way to ref an array of the just the children with the launcherPos class???
There must be a couple of different ways, but, they are eluding me.
When you filter/map an array of HTMLElements, the results are in the form of objects, which contains properties like, props, ref etc.
Since className is a prop on the element, you should try looking for the class name by digging into the props key.
Simply put, all the props that you pass to the element, like onClick, onChange, value, className are stored under the props property.
You can filter the results by converting the class name into an array and further checking if it contains the target string (launcherPos in this case).
Your code should look something like this:
const circleElements = [...launcherChildren].filter(element=>element.props.className.split(' ').includes('launcherPos'))
The above method could be used when an array directly holds elements. E.g: [<div></div>,<div></div>...].
The approach that you've followed is correct, except for the way you are selecting the elements by their class names. I can see that you are using CSS modules in this component, meaning all the class names exist as properties on the imported object(styles in this case), so when you use contains('launcherPos') you are essentially checking for the presence of a string, but when using CSS modules, class names are available only as object properties, that's the reason you are getting an empty array. Simply update launcherPos to styles.launcherPos and that shall fix the issue.
All-in-all your useEffect function should look something like this:
useEffect(() => {
const launcherCircle = launcherCircleRef.current;
const launcherChildren = launcherCircle!.children;
const circleElements = [...launcherChildren].filter(element => element.classList.contains(styles.launcherPos)); //change launcherPos to styles.launcherPos
let angle = 360 - 190;
let dangle = 360 / circleElements.length;
for (let i = 0; i < circleElements.length; i++) {
let circle = circleElements[i] as HTMLElement;
angle += dangle;
circle.style.transform = `rotate(${angle}deg) translate(${launcherCircle!.clientWidth / 2}px) rotate(-${angle}deg)`;
}
}, []);
I want to manage the content of the page from a content editor where I am getting page content from the API.
Check this screenshot.
I used two different react modules for this react-html-parser and react-string-replace but it is still not working.
Here is my code.
let pageData = '';
pageData = ReactHtmlParser(page.content);
// replacing contact us form with a contact us form component
pageData = reactStringReplace(pageData, '{CONTACT_US_FORM}', (match, i) => (
<ContactUsForm />
));
return <div>{pageData}</div>;
react-html-parser -> It is used to parse HTML tags which are in string format into tree of elements.
react-string-replace -> It is used to replace a string into react a component.
Note: If I use react-html-parser or react-string-replace individually then it works fine but it does not work together.
Any suggestion?
Depends on the expected structure of page.content. If it contains HTML you are right in using react-html-parser, which has a replace option.
import parse from 'html-react-parser';
const macro = '{CONTACT_US_FORM}';
const replaceContactUsForm = (domhandlerNode) => {
if (domhandlerNode.type === 'text' && domhandlerNode.data.includes(macro))
return <ContactUsForm />;
};
// ...
const elements = parse(page.content, { replace: replaceContactUsForm });
return <div>{elements}</div>;
Additionally, If the string {CONTACT_US_FORM} is embedded in text you could use react-string-replace to keep the rest of the text intact:
const replaceContactUsForm = (domhandlerNode) => {
if (domhandlerNode.type === 'text' && domhandlerNode.data.includes(macro))
return <>{reactStringReplace(domhandlerNode.data, macro, () => (<ContactUsForm />))}</>;
};
If page.content does not contain HTML you do not need react-html-parser. But judging from your screenshot some markup is probably contained.
I'm trying to write my own MineSweeper in F#.
I create a type of Button :
type myButton(pos : Point, boum : bool, wnd : Form) =
inherit Button(Parent = wnd,
Bounds = Rectangle(Point((pos.X+1) * size,(pos.Y+1) * size), Size(size,size)),
Visible = true,
Text = if boum = true then "B" else "")
let (bomb : bool) = boum
member this.Bomb with get() = bomb
The buttons are in a Form and I can get these buttons with GethildAtPoint. But I get a Control not a myButton so I can't access to the the field Bomb.
How can I get it ? I tried with the Tag property, but without success.
Thank you.
When you call GetChildAtPoint, you will get back a Control, which you can then type test with a pattern match to see if it's your custom button:
let ctrl = form.GetChildAtPoint pt
match ctrl with
| :? myButton as myb ->
let isBomb = myb.Bomb ()
// Do something here
()
| _ -> () // ignore anything else
I'm trying to get an idea how can I force Play Scala framework form mapper to save null values in array property.
Example. Request body (print out of snippet below):
AnyContentAsJson({
"entities":["ENI","GDF Suez","Procter & Gamble"],
"entityValues":[null,"42",null]
})
Resulting value of entityValues property after binding:
List(Some(42.0))
But I want to see:
List(None, Some(42.0), None)
Code snippet of controller:
def actionX = Action {implicit request =>
println(request.body)
TaskForm.form.bindFromRequest.fold(
formWithErrors => {
BadRequest("error")
},
taskData => {
println(taskData.entityValues)
}
)
}
Form class with mapping:
case class TaskForm(entities: List[String],
entityValues: List[Option[Double]]) { }
object TaskForm {
val map = mapping(
"entities" -> list(text),
"entityValues" -> list(optional(of(doubleFormat)))
)(TaskForm.apply)(TaskForm.unapply)
val form = Form(
map
)
}
I also tried some combinations of optional and default mapping parameters, but a result is still the same.
Using 0 or any another numeric value instead of null is not a case.
Does anyone have any ideas how to implement such form behaviour?
Thanks in advance for your time and attention.
It looks like you're sending JSON to a form endpoint. While this will work for simple JSON structures, you get no control over how it is done and hence get problems like the one you're seeing.
I'd be explicit about being a JSON-endpoint, and then you can define your own Reads[Option[Double]] that works precisely how you want it to:
First, define the implicits at the Controller level; here's where we get to control the null-handling; it ends up being pretty easy:
implicit val optionalDoubleReads = new Reads[Option[Double]] {
def reads(json: JsValue) = json match {
case JsNumber(n) => JsSuccess(Some(n.toDouble))
case JsString(n) => JsSuccess(Some(n.toDouble))
case JsNull => JsSuccess(None) // The important one
case _ => JsError("error.expected.jsnumber")
}
}
implicit val taskReads = Json.reads[TaskForm]
With that done, we modify your Action to require JSON (using parse.json). The function itself remains remarkably similar to the original form-binding fold:
def actionX = Action(parse.json) { implicit request =>
println(request.body)
request.body.validate[TaskForm].fold(
jsonErrors => {
BadRequest(s"Error: $jsonErrors")
},
taskData => {
println(taskData.entityValues)
Ok(taskData.entityValues.toString)
}
)
}
i'm using openlayers and geoExt.
what i have is this:
var options = {
hover : true,
box : true,
onSelect : saveToJ
};
var select = new OpenLayers.Control.SelectFeature(vecLayer, options);
map.addControl(select);
select.activate();
now in saveToJ function i want to get length of selected feature (let's say feature = lineString):
function saveToJ(feature) {
feature.getLength()
...
}
gives an error TypeError: Object #<Object> has no method 'getLength', but from this
i thought i can use it.
So: how can i get a length of selected feature?
getLength is a method of Geometry, not Feature.
So you should write feature.geometry.getLength(), see http://dev.openlayers.org/docs/files/OpenLayers/Geometry-js.html#OpenLayers.Geometry.getLength