How to get innerHTML of each element in a node list - dom

Say I have the following html:
<div class="item">one</div>
<div class="item">two</div>
<div class="item">three</div>
I would like to log the innerHTML for each element. I have tried the following:
document
|> Document.querySelectorAll(".item")
|> NodeList.toArray /* not sure if this is needed */
|> Array.map(Document.ofNode) /* not sure if this is needed */
|> Array.map(Element.innerHTML)
|> Array.iter((number) => {
Js.log(number);
});
The error is on line |> Array.map(Element.innerHTML)
And the error is:
This has type:
array(Webapi.Dom.Element.t) => array(string)
But somewhere wanted:
array(Dom.node) => 'a
How can I use the Dom.node so that I access the innerHTML?

Document.ofNode should be Element.ofNode, but the error message indicates that this line isn't present at all in the code producing the error. ofNode also returns an option, since not all nodes are elements, so you have to unwrap that as well. Something like this should work:
document
|> Document.querySelectorAll(".item")
|> NodeList.toArray
|> Array.map(Element.ofNode)
|> Array.map(Js.Option.getExn)
|> Array.map(Element.innerHTML)
|> Array.iter(Js.log);
Or to avoid a bit of unnecessary array allocation and mapping:
document
|> Document.querySelectorAll(".item")
|> NodeList.toArray
|> Array.iter(node =>
node
|> Element.ofNode
|> Js.Option.getExn
|> Element.innerHTML
|> Js.log
);
It also has to be said that direct DOM manipulation isn't a great fit for Reason, or any statically typed functional language for that matter, since the DOM is inherently a very dynamically typed and object-oriented API. NodeList.toArray, for example, is required because a NodeList is not an array but an "array-like", which supports only a few array operations.

Related

Filtering a collection of IO's: List[IO[Page]] scala

I am refactoring a scala http4s application to remove some pesky side effects causing my app to block. I'm replacing .unsafeRunSync with cats.effect.IO. The problem is as follows:
I have 2 lists: alreadyAccessible: IO[List[Page]] and pages: List[Page]
I need to filter out the pages that are not contained in alreadyAccessible.
Then map over the resulting list to "grant Access" in the database to these pages. (e.g. call another method that hits the database and returns an IO[Page].
val addable: List[Page] = pages.filter(p => !alreadyAccessible.contains(p))
val added: List[Page] = addable.map((p: Page) => {
pageModel.grantAccess(roleLst.head.id, p.id) match {
case Right(p) => p
}
})
This is close to what I want; However, it does not work because filter requires a function that returns a Boolean but alreadyAccessible is of type IO[List[Page]] which precludes you from removing anything from the IO monad. I understand you can't remove data from the IO so maybe transform it:
val added: List[IO[Page]] = for(page <- pages) {
val granted = alreadyAccessible.flatMap((aa: List[Page]) => {
if (!aa.contains(page))
pageModel.grantAccess(roleLst.head.id, page.id) match { case Right(p) => p }
else null
})
} yield granted
this unfortunately does not work with the following error:
Error:(62, 7) ';' expected but 'yield' found.
} yield granted
I think because I am somehow mistreating the for comprehension syntax, I just don't understand why I cannot do what I'm doing.
I know there must be a straight forward solution to such a problem, so any input or advice is greatly appreciates. Thank you for your time in reading this!
granted is going to be an IO[List[Page]]. There's no particular point in having IO inside anything else unless you truly are going to treat the actions like values and reorder them/filter them etc.
val granted: IO[List[Page]] = for {
How do you compute it? Well, the first step is to execute alreadyAccessible to get the actual list. In fact, alreadyAccessible is misnamed. It is not the list of accessible pages; it is an action that gets the list of accessible pages. I would recommend you rename it getAlreadyAccessible.
alreadyAccessible <- getAlreadyAccessible
Then you filter pages with it
val required = pages.filterNot(alreadyAccessible.contains)
Now, I cannot decipher what you're doing to these pages. I'm just going to assume you have some kind of function grantAccess: Page => IO[Page]. If you map this function over required, you will get a List[IO[Page]], which is not desirable. Instead, we should traverse with grantAccess, which will produce a IO[List[Page]] that executes each IO[Page] and then assembles all the results into a List[Page].
granted <- required.traverse(grantAccess)
And we're done
} yield granted

How to do a case insensitive match for command line arguments in scala?

I'm working on a command line tool written in Scala which is executed as:
sbt "run --customerAccount 1234567"
Now, I wish to make this flexible to accept "--CUSTOMERACCOUNT" or --cUsToMerAccount or --customerACCOUNT ...you get the drift
Here's what the code looks like:
lazy val OptionsParser: OptionParser[Args] = new scopt.OptionParser[Args]("scopt") {
head(
"XML Generator",
"Creates XML for testing"
)
help("help").text(s"Prints this usage message. $envUsage")
opt[String]('c', "customerAccount")
.text("Required: Please provide customer account number as -c 12334 or --customerAccount 12334")
.required()
.action { (cust, args) =>
assert(cust.nonEmpty, "cust is REQUIRED!!")
args.copy(cust = cust)
}
}
I assume the opt[String]('c', "customerAccount") does the pattern matching from the command line and will match with "customerAccount" - how do I get this to match with "--CUSTOMERACCOUNT" or --cUsToMerAccount or --customerACCOUNT? What exactly does the args.copy (cust = cust) do?
I apologize if the questions seem too basic. I'm incredibly new to Scala, have worked in Java and Python earlier so sometimes I find the syntax a little hard to understand as well.
You'd normally be parsing the args with code like:
OptionsParser.parse(args, Args())
So if you want case-insensitivity, probably the easiest way is to canonicalize the case of args with something like
val canonicalized = args.map(_.toLowerCase)
OptionsParser.parse(canonicalized, Args())
Or, if you for instance wanted to only canonicalize args starting with -- and before a bare --:
val canonicalized =
args.foldLeft(false -> List.empty[String]) { (state, arg) =>
val (afterDashes, result) = state
if (afterDashes) true -> (arg :: result) // pass through unchanged
else {
if (arg == "==") true -> (arg :: result) // move to afterDash state & pass through
else {
if (arg.startsWith("--")) false -> (arg.toLowerCase :: result)
else false -> (arg :: result) // pass through unchanged
}
}
}
._2 // Extract the result
.reverse // Reverse it back into the original order (if building up a sequence, your first choice should be to build a list in reversed order and reverse at the end)
OptionsParser.parse(canonicalized, Args())
Re the second question, since Args is (almost certainly) a case class, it has a copy method which constructs a new object with (most likely, depending on usage) different values for its fields. So
args.copy(cust = cust)
creates a new Args object, where:
the value of the cust field in that object is the value of the cust variable in that block (this is basically a somewhat clever hack that works with named method arguments)
every other field's value is taken from args

How to String matchAll in Reason?

I'm trying to replicate what I would do in javascript with matchAll()
const names = [
...withoutSlashes.matchAll(/(?<=Pos\. \d+ \- )(.*?)(?=","Importe)/g),
];
I see Reason has Js.String.match but I can't find the matchAll. I guess it's because matchAll is a newer ecmascript.
Any hint on which would be a good way to do a performant matchAll? or is there a specific Reason feature that I'm missing?
Based on the accepted answer I wanted to add a version that is following ReScript conventions. [#bs.send.pipe] is discouraged, and the ReScript language officially recommends the pipe-first operator (-> instead of |>).
like this:
[#bs.send]
external matchAll: (string, Js.Re.t) => Js.Array.array_like(array(string)) =
"matchAll";
let matches: array(string) =
matchAll("abc", [%re "/[a-c]/g"])->Js.Array.from;
You can bind to it yourself. The biggest problem with it is that it returns an iterator, which we also don't have bindings for. But we can use Js.Array.array_like('a) and then convert it to an array using Js.Array.from:
[#bs.send.pipe: string]
external matchAll: Js.Re.t => Js.Array.array_like(array(string)) = "matchAll";
let matches = "abc" |> matchAll([%re "/[a-c]/g"]) |> Js.Array.from;

Reason: Error: Unbound value not__ for bs-jest

Trying to test a binding of lit-html method html
open Jest;
let write = () => LitHtml.html("<div></div>");
open Expect;
describe("LitHtml", () =>
test("#html", () =>
expect(() =>
write()
) |> not_ |> toThrow
)
);
I am told this cryptic error:
Error: Unbound value not__
Hint: Did you mean not_?
Jest.Expect.plainPartial('a) => Jest.Expect.invertedPartial('a)
But clearly wrote not_ as it suggests, not not__.
My attempted binding:
[#bs.module "LitHtml"] [#bs.val]
external html: string => Js.nullable(string) = "html";
let html = htmlStr => html(htmlStr) |> Js.Nullable.toOption;
Thanks for any assistance. Just getting started with ReasonML ;)
Seems like this is caused by a largely undocumented change in Reason 3.3.4. I think it might hide in PR #2197.
not is a keyword in OCaml, which is why Expect.not_ is named such as it is in the first place. And this change seems to "mangle" (ie. translate) not in Reason into not_ in OCaml, and then not_ to not__ and so on.
So the solution is simply to replace all instances of not_ in your code with not. OR you can update bs-jest to 0.4.7 where I've added not__ as an alias to not_, so you can use either not or not_.

attach files to email without saving content to local file

I am using elixir Bamboo for sending emails
I have some binary content and would like to attach to email
the simple way is:
content = <<binary-content>>
File.write("/tmp/myfile.pdf", content )
data = new_email()
|> to(email)
|> from( "email.com")
|> subject("subject")
|> put_attachment("/tmp/myfile.pdf")
|> put_header("Reply-To", "email.com")
|> html_body(html_body)
File.rm("/tmp/myfile.pdf", content )
is there a way attach files without saving them locally?
something like:
content = <<binary-content>>
data = new_email()
|> to(email)
|> from( "email.com")
|> subject("subject")
|> put_attachment(content)
|> put_header("Reply-To", "email.com")
|> html_body(html_body)
You bet. It was included prior to the 1.0 release. You can attach data directly to the struct as of this PR.
You just need to pass put_attachment a Bamboo Attachment struct with the data field populated, so:
content = <<binary-content>>
data = new_email()
|> to(email)
|> from( "email.com")
|> subject("subject")
|> put_attachment(%Bamboo.Attachment{filename: "filename.example", data: content})
|> put_header("Reply-To", "email.com")
|> html_body(html_body)
Here's the thread on the feature: https://github.com/thoughtbot/bamboo/issues/286