Ruby: Retrieving and processing files via Selenium and JavaScript
This card shows an uncommon way to retrieve a file using selenium where JavaScript is used to return a binary data array to Ruby code.
The following code example retrieves a PDF but the approach also works for other file types.
require "selenium-webdriver"
selenium_driver = Selenium::WebDriver.for :chrome
selenium_driver.navigate.to('https://example.com')
link_to_pdf = 'https://blobs.example.com/random-pdf'
binary_data_array = selenium_driver.execute_script(<<-JS, link_to_pdf)
const response = await fetch(arguments[0])
if (!r...
Terser is good at minifying JavaScript
Terser is a really good minifier ("compressor") for JavaScript code. I'm often surprised by the thoughtfulness of its compressed output.
Let's take this function:
function fn() {
if (a) {
return 'foo'
} else if (b) {
return 'foo'
} else {
return c()
}
}
console.log(fn())
Terser will reduce this to the following code:
console.log(a||b?"foo":c())
Note how:
- The
if
statement has been replaced by a tertiary expression. This is often less readable, but it doesn't matter in c...
Getter and setter functions for JavaScript properties
JavaScript objects can have getter and setter functions that are called when a property is read from or written to.
For example, if you'd like an object that has a virtual person.fullName
attribute that dynamically composes person.firstName
and person.lastName
:
var person = {
firstName: 'Guybrush',
lastName: 'Threepwood',
get fullName() {
return this.firstName + " " + this.lastName;
},
set fullName(name) {
var parts = name.split(" ");
this.firstName = parts[0];
this.lastName = parts[1];
}
};
`...
Shorthand function properties in ES6
Here is an ES5 object literal with two string properties and a function property:
let user = {
firstName: 'Max',
lastName: 'Muster',
fullName: function() { return this.firstName + ' ' + this.lastName }
}
user.fullName() // => 'Max Muster'
In ES6 we can define a function property using the following shorthand syntax:
let user = {
firstName: 'Max',
lastName: 'Muster',
fullName() { return this.firstName + ' ' + this.lastName }
}
user.fullName() // => 'Max Muster'
We can also define a gette...
Firefox cancels any JavaScript events at a fieldset[disabled]
If you try to listen to events on elements that are nested inside a <fieldset disabled>
, Firefox will stop event propagation once the event reaches the fieldset. Chrome and IE/Edge will propagate events.
Since we often bind event listeners to document
this can be annoying.
You could solve it by...
- ...adding event listeners on elements themselves. Note that this is terrible when you have many elements that you'd register events for.
- ...adding event listeners on a container inside the
<fieldset>
, around your eleme...
Using regular expressions in JavaScript
Regular expressions in Javascript are represented by a RegExp
object. There also is a regex literal as in many other languages: /regex/
. However, they are used slightly differently.
Regex literal
- Usage:
/foo+/
- Shorthand for creating a regular expression object
RegExp() object
- Usage:
RegExp("foo+")
ornew RegExp("foo+")
- No surrounding slashes required (they're the literal markers)
- Since the argument is a string, backslashes need to be escaped as well:
RegExp("\\d+")
Gotchas
- Regex objects [never eq...
JavaScript: Testing whether the browser is online or offline
You can use the code below to check whether the browser can make connections to the current site:
await isOnline() // resolves to true or false
The code
The isOnline()
function below checks if you can make real requests by re-fetching your site's favicon. If the favicon cannot be downloaded within 6 seconds, it considers your connection to be offline.
async function isOnline({ path, timeout } = {}) {
if (!navigator.onLine) return false
path ||= document.querySelect...
Simple debounce in vanilla JavaScript
Debouncing a method call delays its execution until after a specified time has passed.
If it's called again before that time has passed, its execution is delayed again.
This technique is commonly used to improve performance when code would be run more often than it needs to.
One example for that are scroll
event handlers in JavaScript: You want to react to a user scrolling, but it's enough to do that when they have stopped scrolling.
Here is a small JavaScript function that you can use for that:
function debounce(callback...
JavaScript: Calling a function with a variable number of arguments
This card describes how to pass an array with multiple element to a JavaScript function, so that the first array element becomes the first function argument, the second element becomes the second argument, etc.
Note how this is different from passing the entire array as the first argument. Compare these two different ways of calling fun()
in Ruby:
# Ruby
array = [1, 2, 3]
fun(array) # same as fun([1, 2, 3]) (1 argument)
fun(*array) # same as fun(1, 2, 3) (3 arguments)
Depending on your culture the spreading of array eleme...
esbuild: Compressing JavaScript harder with Terser
esbuild comes with a minifier that is good enough for most cases. If you're looking to squeeze out as many bytes as possible, you can consider compressing with Terser instead.
Using Terser will increase your build times significantly, but produce the smallest output:
| | Terser (3 pass) | Terser (1 pass) | esbuild |
|----------------------------|-----------------------|------------------|-------...
Operators "in" and "of" are very inconsistent between CoffeeScript and JavaScript
CoffeeScript and JavaScript (ECMAScript) both have operators in
and of
. Each language use them for more than one purpose. There is not a single case where the same operator can be used for the same purpose in both languages.
Check if an object (or its prototype) has a property
CoffeeScript
var hasFoo = 'foo' of object
JavaScript
var hasFoo = 'foo' in object;
Iterate through all properties of an object
================================...
JavaScript: Detecting the end of native smooth scrolling
When you use native smooth scrolling there is no built-in method to detect the end of the scrolling animation. Methods like scrollTo()
don't return a promise. We may eventually get a scrollend
event, but that is still some time away.
Until then I'm using the ...
tesseract.js: Pure Javascript OCR for 62 Languages
This might be relevant for us since we're often managing customer documents in our apps.
I played around with the library and this is what I found:
- A 200 DPI scan of an English letter (500 KB JPEG) was processed in ~6 seconds on my desktop PC. It does the heavy lifting in a Web worker so you're rendering thread isn't blocked.
- It detected maybe 95% of the text flawlessly. It has difficulties with underlined text or tight table borders.
- When you feed ...
How to accept or deny JavaScript confirmation dialogs in Capybara/Selenium
These methods are available to you:
page.driver.browser.switch_to.alert.accept
page.driver.browser.switch_to.alert.dismiss
page.driver.browser.switch_to.alert.text # the confirmation text
Spreewald gives you steps like these:
When I confirm the browser dialog
When I cancel the browser dialog
Note that recent versions of Selenium will automatically dismiss confirmation dialogs. [Here is how to fix that](https://makandracards.com/makandra/617366-configure-selenium-webdriv...
How to find child nodes that match a selector with JavaScript
Using querySelector
or querySelectorAll
in JavaScript, you can easily find descendants of a node that match a given selector.
But what if you want to find only children (i.e. direct descendants) of an element?
Easy: use :scope
. It references the element on which DOM API methods are being called:
element.querySelectorAll(':scope > .your-selector')
Example
Consider this HTML
<body>
<div id="container1">
<div id="container1a">foo</div>
<div id="container1b">bar</div>
<div id="container1c">baz</...
Setting expiry dates for images, JavaScript and CSS
When deploying Rails applications you might have noticed that JS and CSS are not cached by all browsers.
In order to force Apache to add expiry dates to its response, add the attached .htaccess
to the public directory. This will add a header such as Expires: Thu, 07 Oct 2010 07:21:45 GMT
to the httpd response.
Configuring Apache
Check that you have mod_expires
enabled. You need it for the attached .htaccess
to work:
sudo a2enmod expires
Configuring Nginx
You can add this:
Javascript: Avoid using innerHTML for unsafe arguments
Make sure that you use the correct property when editing an HTML attribute. Using innerHTML
with unsafe arguments makes your application vulnerable to XSS.
-
textContent
: Sets the content of aNode
(arguments are HTML-safe escaped) -
innerHTML
: Sets the HTML of anElement
(arguments are not escaped and may not contain user content)
Hierarchy
This hierarchy gives you a better understanding, where the textContent
and the innerHTML
properties are defined. It also includes (just for completeness) the innerText
property, whi...
JavaScript: Listening to a class getting added
Reacting on a class getting added can be done with a mutation observer. Example:
const items = document.querySelectorAll('.item')
const expectedClass = 'active'
const activeObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.target.classList.contains(expectedClass) {
// Do something
}
})
})
items.forEach(item => activeObserver.observe(item, { attributes: true, attributeFilter: ['class'] }))
Note that this is not a generic solution – it makes a few assumptions to simplif...
Managing vendor libraries with the Rails asset pipeline
The benefit of the Rails asset pipeline is that it compiles your stylesheets and javascripts to a single file, respectively. However, the consequences are startling if you don't understand them. Among others, the raw asset pipeline requires you to have all your asset libraries in the same folder, which quickly becomes confusing as your set of assets grows. To overcome this, we have two different solutions.
Custom solution
We are using a custom workaround to keep library files apart in their own directories. To avoid b...
JavaScript: New Features in ES2021
tl;dr
With ES2021 you now can use
str.replaceAll()
,Promise.any()
, logical assignment operators, numeric separators andWeakRef
on all major browsers except IE11.
replaceAll
JavaScript's replace(searchValue, replaceValueOrFn)
by default replaces only the first match of a given String or RegExp.
When supplying a RegExp as the searchValue argument, you can specify the g
("global") modifier, but you have to remember doing that, hence using replace
when you expect global replacement is prone to errors.
When supplying st...
How to read the current breakpoint tier(s) in JavaScript
To read the current breakpoint tier in JavaScript, employ this CSS:
:root {
--current-breakpoint-tier: xs;
@media (min-width: $screen-sm-min) {
--current-breakpoint-tier: sm;
}
@media (min-width: $screen-md-min) {
--current-breakpoint-tier: md;
}
@media (min-width: $screen-lg-min) {
--current-breakpoint-tier: lg;
}
@media (min-width: $screen-xl-min) {
--current-breakpoint-tier: xl;
}
@media (min-width: $screen-xxl-min) {
--current-breakpoint-tier: xxl;
}
}
Then use this JavaScript:
Adding Jasmine JavaScript specs to a Webpack(er) project
The goal is to get Jasmine specs running in a Rails project using Webpacker, with the browser based test runner. Should be easily adaptable to a pure Webpack setup.
Step 1: Install Jasmine
yarn add jasmine-core
Step 2: Add two separate packs
Since we do not want to mix Jasmine into our regular Javascript, we will create two additional packs. The first only contains Jasmine and the test runner. The second will contain our normal application code and the specs themselves.
We cannot...
Jasmine: Expecting objects as method invocation arguments
To check if a method has been called in Jasmine, you first need to spy on it:
let spy = spyOn(window, 'alert')
codeThatAlerts()
expect(window.alert).toHaveBeenCalledWith('Important message')
To expect an object of a given type, pass the constructor function to jasmine.any()
:
expect(spy).toHaveBeenCalledWith(jasmine.any(Object))
expect(spy).toHaveBeenCalledWith(jasmine.any(String))
expect(spy).toHaveBeenCalledWith(jasmine.any(Number))
To expect an object with given key/value properties, use `jasmine.objectContaining(...
JavaScript has a native event emitter
Suppose you want to implement a publish/subscribe pattern in your Frontend application to react to data changes and events. First, you might be looking for an event emitter library.
Today I learned that vanilla JavaScript comes with a native event emitter, which I've been indirectly using forever. There's no need for extra code!
const emitter = new EventTarget()
emitter.addEventListener('YOLO', () => {
console.log('YOLO');
})
// invoke attached event listeners
emitter.dispatchEvent(new Event('YOLO'));