This article is extracted from the book JavaScript for Rails Developers. Get your copy today. ✌️
Ruby (and Rails) are known for great Developer Experience. Not in the least because of the many little helpers available. JavaScript doesn’t have most of these unfortunately, but luckily, as a developer, many are easily replicated. Let’s look at the API I am looking for:
class Editor {
async #update(content) {
const sanitizedContent = sanitize(content, { trimTrailingWhitespace: true })
const response = await fetch(
this.updateUrlValue,
{
// …
body: JSON.stringify({
content: sanitizedContent
})
}
)
// …
}
}
sanitize(content, { trimTrailingWhitespace: true })
is what I want to implement. You could extend the options with whatever you need:
sanitize(content, {
trimTrailingWhitespace: true,
trimLeadingWhitespace: true,
maxConsecutiveNewlines: 2,
maxConsecutiveSpaces: 1
// … etc.
})
The code for it is quite simple really! Let’s create it at: app/javascript/helpers/textSanitizers.js.
// app/javascript/helpers/textSanitizers.js
class TextSanitizers {
}
export const sanitize = (content, options = {}) => {
return new TextSanitizers(content).process(options)
}
Above shows that the content
is passed as an argument and then very likely set as an instance variable. It also needs a process
method.
class TextSanitizers {
+ constructor(content) {
+ this.content = content
+ }
+ process(options = {})
+ return this.content.replace(/[ \t]+$/gm, "")
+ }
}
Above is all that is needed to sanitize trailing white-spaces from the content. But I want this class to be responsible for many more sanitizations! Let’s set it up to be ready for that.
class TextSanitizers {
+ availableCleaners = [
+ "trimTrailingWhitespace"
+ ]
constructor(content) {
this.content = content
}
process(options = {}) {
- return this.content.replace(/[ \t]+$/gm, "")
+ return Object.entries(options).reduce((result, [option, value]) => {
+ return this.availableCleaners.includes(option) ? this[option](result, value) : result
+ }, this.content)
}
// private
+ trimTrailingWhitespace(text) {
+ return text.replace(/[ \t]+$/gm, "")
+ }
}
The process method determines which cleaning operations need to run. When no options are passed, it defaults to an empty object ({}
). Then inside the method Object.entries
converts the options into an array of key-value pairs: {trimTrailingWhitespace: true}
would become: [["trimTrailingWhitespace", true]]
. If this seems like a lot, the book JavaScript for Rails Developers has you covered (more sanitization options included)! 😊
Okay, with that done, it is now easy to extend TextSanitizers
class with more options:
class TextSanitizers {
availableCleaners = [
"trimTrailingWhitespace",
+ "trimLeadingWhitespace",
+ "maxConsecutiveNewlines",
+ "maxConsecutiveSpaces"
]
constructor(content) {
this.content = content
}
process(options = {}) {
return Object.entries(options).reduce((result, [option, value]) => {
return this.availableCleaners.includes(option) ? this[option](result, value) : result
}, this.content)
}
// private
trimTrailingWhitespace(text) {
return text.replace(/[ \t]+$/gm, "")
}
+ trimLeadingWhitespace(text) {
+ return text.replace(/^[ \t]+/gm, "")
+ }
+ maxConsecutiveNewlines(text, maxNewlines) {
+ return text.replace(/\n{3,}/g, "\n".repeat(maxNewlines))
+ }
+ maxConsecutiveSpaces(text, maxSpaces) {
+ return text.replace(/ {2,}/g, " ".repeat(maxSpaces))
+ }
}
For every new sanitization option, you add it to availableCleaners
and then add the method with a matching name.
And that is how you added a little bit of Rails into JavaScript. Super clean! 🛀