Introduction to Google Tag Manager (for web developers who know Google Analytics)
As a web developer, you know Google Analytics (GA). Probably you've dropped the GA snippet into more than one website, maybe you've even used its Javascript API to implement tracking at the event level.
Google Tag Manager (GTM) is a related tool, but on a higher level and thus with much more power. GTM is not a replacement for GA. Rather, it can make GA configurable without changing anything in the application's code base (and much more beyond, see below).
Only prefer GTM if the customer requests it, or if he is updating his tracking r...
Rails: Flagging all cookies as secure-only to pass a security audit
Why secure-only cookies used to be necessary
Cookies have an optional secure
flag. It tells the browser to not send the cookie for a non-https request.
It used to be important to activate the secure
flag even on sites that automatically redirect users from http://
to https://
. The reason was that most users will only enter a scheme-less domain like makandra.de
into their location bar, which will default to `http://m...
JavaScript “Stale Practices” | benmccormick.org
The linked article lists a number of techniques that were best practices with ES5, but have better alternatives in modern JavaScript.
Best practices don’t last forever. This is especially true when a field is changing fast, and JavaScript development has changed a lot over the past 10 years. The old best practices go stale, and new ones take their place. Here are 5 JavaScript best practices that have gone stale recently.
Minimal JavaScript function to detect version of Internet Explorer or Edge
If possible your code should detect features, not browsers. But sometimes you just need to sniff the browser. And when you do, you're probably fighting a Microsoft product.
The following function returns a Number
like 10, 11, 12, 13 for Internet Explorer or Edge (anything above 11 is Edge). It returns undefined
for any other browser.
function ieVersion(uaString) {
uaString = uaString || navigator.userAgent;
var match = /\...
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
================================...
screenfull.js: Simple wrapper for cross-browser usage of the JavaScript Fullscreen API
Using the JS fullscreen API is painful because all browers use different methods and events and you need to use lots of boilerplate code to make your application work in all browsers.
The "screenfull" library wraps that for you, including events.
Examples
The linked GitHub repo contains some information. You basically use the library like this:
// Make an element go fullscreen
screenfull.request(element)
// Leave fullscreen
screenfull.exit()
...
Unpoly: Loading large libraries on-demand
When your JavaScript bundle is so massive that you cannot load it all up front, I would recommend to load large libraries from the compilers that need it.
Compilers are also a good place to track whether the library has been loaded before. Note that including same <script>
tag more than once will cause the browser to fetch and execute the script more than once. This can lead to memory leaks or cause duplicate event handlers being registered.
In our work we mostly load all JavaScript up front, since our bundles are small enough. We recent...
jQuery: How to replace DOM nodes with their contents
You know that you can use jQuery's text()
to get an element's contents without any tags.
If you want to remove only some tags, but keep others, use contents()
and unwrap()
. Here is how.
Consider the following example element.
$container = $('<div><strong>Hello</strong> <em>World</em></div>')
Let's say we want to discard any <em>
tags, but keep their contents.
Simply find
them, then dive into their child nodes via contents
, and use unwrap
replace their ...
Webpacker: Configuring browser compatibility
Webpacker uses Babel and Webpack to transpile modern JavaScript down to EcmaScript 5. Depending on what browser a project needs to support, the final Webpack output needs to be different. E.g. when we need to support IE11 we can rely on fewer JavaScript features. Hence our output will be more verbose than when we only need support modern browsers.
Rails 5.1+ projects often use Webpacker to preconfigure the Webpack pipeline for us. The default configuration works something like this:
- Webpack checks w...
Event order when clicking on touch devices
Touch devices have their own set of events like touchstart
or touchmove
. Because mobile browsers should also work with with web applications that were build for mouse devices, touch devices also fire classic mouse events like mousedown
or click
.
When a user follows a link on a touch device, the following events will be fired in sequence:
touchstart
touchend
mousemove
mousedown
mouseup
click
Canceling the event sequence
-------------------...
Generating test images on the fly via JavaScript or Ruby
When you need test images, instead of using services like lorempixel or placehold.it you may generate test images yourself.
Here we build a simple SVG image and wrap it into a data:
URI. All browsers support SVG, and you can easily adjust it yourself.
Simply set it as an image's src
attribute.
JavaScript
Simple solution in modern JavaScript, e.g. for use in the client's browser:
function svgUri(text) {
let svg = `
<svg wid...
Upgrading a Rails app to Cucumber 3
Upgrade gems
You need to update a lof gems. Make sure you don't have any version constraints in your Gemfile
or your bundle update
won't do anything!
Upgrade cucumber_priority
:
bundle update cucumber_priority
Upgrade spreewald
:
bundle update spreewald
Upgrade cucumber_factory
:
bundle update cucumber_factory
Upgrade parallel_tests
:
bundle update parallel_tests
Even on the latest version, parallel_tests
will print some deprecation warnings due to using an older formatter A...
Nested Spreewald patiently blocks are now patient
In Spreewald 1.10.4+, nested patiently
blocks are now patient.
Here is an example:
patiently do
outer_code
patiently do
inner_code
end
end
On spreewald 1.11.2+ the inner block will wait for the full configured wait time (by default 5 seconds). The outer patiently
block would now be out of time, but it will always be retried at least a second time. This behavior allows with_scope
to be patient, and it must be patient, as explained below.
In versions 1.10.4 - 1.11.1, inner blocks would keep giving the ou...
HTML5: disabled vs. readonly form fields
Form fields can be rendered as noneditable by setting the disabled
or the readonly
attribute. Be aware of the differences:
disabled fields
- don’t post to the server
- don’t get focus
- are skipped while tab navigation
- available for
button
,fieldset
,input
,select
,textarea
,command
,keygen
,optgroup
,option
Browser specific behavior:
- IE 11: text inputs that are descendants of a disabled fieldset appear disabled but the user can still interact with them
- Firefox: selecting text in a disabled text field is no...
Font Awesome 5 migration guide
Font Awesome version 5 changed some icon names, and introduces new prefixes fab
, far
, and fas
.
There is a JavaScript shim that you can use as a "quick fix". It allows you to keep v4 icon names on v5.
The linked page includes details on using that, and a migration guide including a list of icon renames.
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(...
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...
HTML: Making browsers wrap long words
By default, browsers will not wrap text at syllable boundaries. Text is wrapped at word boundaries only.
This card explains some options to make browsers wrap inside a long word like "Donaudampfschifffahrt"
.
Option 1: hyphens CSS property (preferred)
Modern browsers can hyphenate natively. Use the hyphens CSS property:
hyphens: auto
There is also hyphens: none
(disable hyphenations even at ­
entities) and hyphens: manual
(hy...
JavaScript: Testing the type of a value
Checking if a JavaScript value is of a given type can be very confusing:
- There are two operators
typeof
andinstanceof
which work very differently. - JavaScript has some primitive types, like string literals, that are not objects (as opposed to Ruby, where every value is an object).
- Some values are sometimes a primitive value (e.g.
"foo"
) and sometimes an object (new String("foo")
) and each form requires different checks - There are three different types for
null
(null
,undefined
andNaN
) and each has different rules for...
Spreewald: Content-Disposition not set when testing a download's filename
Precondition
- You are not using javascript tests
- The file is served from a public folder (not via controller)
Problem description
If you deliver files from a public folder it might be that the Content-Disposition
header is not set. That's why the following spreewald step might raise an error:
Then I should get a download with filename "..."
expected: /filename="some.pdf"$/
got: nil (using =~) (RSpec::Expectations::ExpectationNotMetError)
Solution
One solution...
Fixing flaky E2E tests
An end-to-end test (E2E test) is a script that remote-controls a web browser with tools like Selenium WebDriver. This card shows basic techniques for fixing a flaky E2E test suite that sometimes passes and sometimes fails.
Although many examples in this card use Ruby, Cucumber and Selenium, the techniques are applicable to all languages and testing tools.
Why tests are flaky
Your tests probably look like this:
When I click on A
And I click on B
And I click on C
Then I should see effects of C
A test like this works fine...
Async control flow in JavaScript: Promises, Microtasks, async/await
Slides for Henning's talk on Sep 21st 2017.
Understanding sync vs. async control flow
Talking to synchronous (or "blocking") API
print('script start')
html = get('/foo')
print(html)
print('script end')
Script outputs 'script start'
, (long delay), '<html>...</html>'
, 'script end'
.
Talking to asynchronous (or "evented") API
print('script start')
get('foo', done: function(html) {
print(html)
})
print('script end')
Script outputs 'script start'
, 'script end'
, (long ...
How to exploit websites that include user input in their CSS
The linked article shows how to exploit websites that include unsanitized user input in their CSS.
Although the article often mentions React and CSS-in-JS libraries, the methods are applicable to any web app that injects user input into style
tags or properties.
Also, sanitizing user input for CSS injection is much harder than sanitizing HTML.
JavaScript bookmarklet to click an element and copy its text contents
Here is some JavaScript code that allows you to click the screen and get the clicked element's text contents (or value, in case of inputs).
The approach is simple: we place an overlay so you don't really click the target element. When you click the overlay, we look up the element underneath it and show its text in a browser dialog. You can then copy it from there.
While moving the mouse, the detected element is highlighted.
Here is the one-liner URL that you can store as a bookmark. Place it in your bookmarks bar and click it to activate....