An intro to Javascript promises
Promises are the new way™ to express "Do this, and once you're done, do that". In contrast to callbacks, promises are easily chainable. From the readme of Q
, an early implementer of the pattern:
The callback approach is called an “inversion of control”. A function that accepts a callback instead of a return value is saying, “Don’t call me, I’ll call you.”. Promises un-invert the inversion, cleanly separating the input arguments from control flow arguments. This simplifies the use and creation of APIs, p...
Angular directive scope binding with = (equals)
Angular directives with isolate scopes have three different variable binding strategies, of which one is =
. Example:
# HTML
<panel value="parent.someFn() && false" twoway="parent.someProperty"></div>
# Coffeescript
@app.directive 'panel', ->
scope:
evaluated: '=value'
bound: '=twoway'
link: ->
scope.evaluated # = false
scope.bound = 'foo' # Updates parent.someProperty
HTML attributes bound with =
(value
, twoway
) have their value evaluated as Angular expression in the parent scope's context and have the ...
Angular isolate scopes: Calling a parent scope function with externally defined arguments
Isolate scopes offer three kinds of variable binding. One of them is &
, allowing to bind a property of the isolate scope to a function in the parent scope. Example:
# HTML
<panel proxy="parent.someFunction(arg1, arg2)"></div>
# Coffeescript
@app.directive 'panel', ->
scope:
parentFn: '&proxy'
link: (scope) ->
scope.parentFn(arg1: 'first', arg2: 'second')
In this dumb example, the panel
directive will call its scope's parentFn()
function with two arguments, which proxies to parent.someFunction('first', 'second')
...
Your browser might silently change setTimeout(f, 0) to setTimeout(f, 4)
When you're nesting setTimeout(f, 0)
calls, your browser will silently increase the delay to 5 milliseconds after the fourth level of nesting.
This is called "timeout clamping" and defined in the HTML spec:
If nesting level is greater than 5, and timeout is less than 4, then set timeout to 4.
Timeouts are clamped harder in background tabs
On a similar note, all major browsers have implemented throttling rules for setInterval
and setTimeout
calls from tabs...
Middleman does not fingerprint asset paths by default
We're using Middleman for some static sites like our blog.
Despite being very similar to Rails, Middleman does not add a fingerprint hash to its asset paths by default. This means that when you write this:
<%= javascript_include_tag 'all.js' %>
... you always get the same path, regardless of the contents of all.js
:
<script src='/javascripts/all.js'>
Because browsers tend to cache assets for a while, this means that users might not get your changes until their cac...
Represent astral Unicode characters in Javascript, HTML or Ruby
Here is a symbol of an eight note: ♪
Its two-byte hex representation is 0x266A.
This card describes how to create a string with this symbol in various languages.
All languages
Since our tool chain (editors, languages, databases, browsers) is UTF-8 aware (or at least doesn't mangle bytes), you can usually get away with just pasting the symbol verbatim:
note = '♪'
This is great for shapes that are easily recognized by your fellow programmers.
It's not...
Building web applications: Beyond the happy path
When building a web application, one is tempted to claim it "done" too early. Make sure you check this list.
Different screen sizes and browsers
Desktops, tablets and mobile devices have all different screen resolutions. Does your design work on each of them?
- Choose which browsers to support. Make sure the page looks OK, is usable and working in these browsers.
- Use @media queries to build a responsive design
- If you do not suppo...
Google Chrome: How to find out your currently installed theme
So you downloaded a theme for Chrome a while ago and don't remember which one it is?
You can go to chrome://settings/appearance
(on Chrome 61+) to see the theme's name, and click a link to open it in the Chrome Web Store.
If you are on an older version, or if the above no longer works, you have to check Chrome's Preferences
file.
Linux
/home/YOUR_USER_NAME/.config/chromium/Default/Preferences
OSX
/Users/YOUR_USER_NAME/Library/Application Support/Google/Chrome/Default/Preferences
Windows
C:\Users\YOUR_US...
About the HTML and the BODY tag
The <html>
and <body>
tags both come with some non-default behavior that you know from other tags.
Do not try to style html
or body
for positioning, width/heigth, or similar. Every browser has its own caveats and you can not test them all.
Generally speaking:
- Use the
html
tag to define your page's default background color (because on short pages or large screens, yourbody
may not be as tall as the browser window). - Use the
html
tag to define a basefont-size
so you can use [rem
units](https://www.sitepoint.com/underst...
Using Google Analytics with Unpoly
The default Google Analytics might not work as expected with your Unpoly app. This is because your app only has a single page load when the user begins her session. After that only fragments are updated and the <script>
tag that sends the page view to Google Analytics is probably never evaluated again.
Luckily you can fix this.
Simple mode: You just want to track all the page views
Embed your Google Analytics code as always.
Now add the following code snippet:...
object-fit polyfill by lazysizes
All new browsers support the new object-fit
CSS property. It allows to specify how an element behaves within its parent element and is intended for images and videos. The most useful values are contain
(fit-in) and cover
(crop).
Unfortunately, IE does not support this yet. However, if you're already using lazysizes, you can use its object-fit polyfill!
Usage
In your Javascript manifest, require them like this:
#= require plugins/object-fit/ls.obj...
HAML 4+ expands nested element attributes
As you may know, HAML expands data attributes that are given as a hash:
%div{ data: { count: 3 } }
# results in:
<div data-count="3"></div>
However, this also works for any other hash attribute. Consider an Angular directive or an Unpoly compiler that is configured by several attributes. Usually you'd prefix them with the directive/compiler name so it gets clear where the attribute belongs. With HAML, this is easy to build:
%...
About IE's Compatibility mode
IE has a "Compatibility Mode" for old browsers. You can keep IE from offering it (and fix some other things, too) by adding this meta tag to your <head>
:
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
Or in Haml:
%meta(http-equiv="X-UA-Compatible" content="IE=Edge")
However, there are some things you need to bear in mind:
-
X-UA-Compatible is ignored unless it's present inside the first 4k of you page. If you put it somewhere in the bottom of your head section (or in the body) move it to top. The best place for ...
Rails Env Widget
Have you ever mistaken one Rails environment for another? The attached helper will help you to never do it again.
Save the attached file to app/helpers/ and use the widget in your layout like this:
%body
= rails_env_widget unless Rails.env.production?
It'll render a small light gray box in the top left corner of your screen, containing the current Rails environment. On click, it'll disappear. Actually, it has the same UX as our Query Diet widget.
Dynamically uploading files to Rails with jQuery File Upload
Say we want …
- to create a
Gallery
that has a name andhas_many :images
, which in turn have a caption - to offer the user a single form to create a gallery with any number of images
- immediate uploads with a progress bar per image
- a snappy UI
Enter jQuery File Upload. It's a mature library that can do the job frontend-wise. On the server, we'll use Carrierwave, because it's capable of caching images.
(FYI, [here's how to do the u...
UI Sortable on table rows with dynamic height
UI sortable helps reordering items with drag 'n drop. It works quite fine.
Proven configuration for sorting table rows
When invoking the plugin, you may pass several options. This set is working fine with table rows:
$tbody.sortable # Invoke on TBODY when ordering tables
axis: 'y' # Restrict drag direction to "vertically"
cancel: 'tr:first-child:last-child, input' # Disable sorting a single tr to prevent jumpy table headers
containment: 'parent' # Only drag within this container
placehol...
Javascript: Wait until an image has finished loading
The attached ImageLoader
helper will start fetching an image and return an image that is resolved once the image is loaded:
ImageLoader.load('/my/image.png').then(function(image) {
...
});
The image
argument that is yielded to the promise callback is an HTMLImageElement
. This is the kind of object you get when you call new Image()
.
Testing Cookie Limits
TL;DR If you want to support most browsers, then don't exceed 50 cookies per domain, and don't exceed 4093 bytes per domain (i.e. total size of all cookies <= 4093 bytes)
Behind the link, you'll find a simple HTML page that offers some cookie tests (how large, how many etc) and an overview of this data for various browsers.
Fun fact: You cannot delete cookies with a key that hits the size limit and has a small value.
How to inspect RSS feeds with Spreewald, XPath, and Selenium
Spreewald gives you the <step> within <selector>
meta step that will constrain page inspection to a given scope.
Unfortunately, this does not work with RSS feeds, as they're XML documents and not valid when viewed from Capybara's internal browser (e.g. a <link>
tag cannot have content in HTML).
Inspecting XML
If you're inspecting XML that is invalid in HTML, you need to inspect the page source instead of the DOM. You may use Spreewald's "... in the HTML" meta step, or add this proxy step fo...
How to preview an image before uploading it
When building a form with a file select field, you may want to offer your users a live preview before they upload the file to the server.
HTML5 via jQuery
Luckily, HTML5 has simple support for this. Just create an object URL and set it on an <img>
tag's src
attribute:
$('img').attr('src', URL.createObjectURL(this.files[0]))
Unpoly Compiler
As an Unpoly compiler, it looks like this:
up.compiler '[image_p...
Install MySQL 5.6 in Ubuntu 16.04
Instead of using this hack you might want to use MariaDB 10.x which can work with both old and new apps.
An alternative could be to use the MySQL Docker image which is still updated for 5.6.
Ubuntu 16.04 only provides packages for MySQL 5.7 which has a range of backwards compatibility issues with code written against older MySQL versions.
Oracle maintains a list of official APT repositories for MySQL 5.6, but those repositories do...
Using Bumbler to Reduce Runtime Dependencies - The Lean Software Boutique
Tool to show you which gems are slow to load:
➜ git:(master) ✗ bundle exec bumbler
[################################################# ]
(49/65) travis-lint...
Slow requires:
110.21 render_anywhere
147.33 nokogiri
173.83 haml
179.62 sass-rails
205.04 delayed_job_active_record
286.76 rails
289.36 mail
291.98 capistrano
326.05 delayed_job
414.27 pry
852.13 salesforce_bulk_api
An interactive Git shell
Git commands tend to come in groups. Avoid typing git over and over and over by running them in a dedicated git shell.
You might want to run git config --global help.autocorrect true
before using gitsh
. This will silently swallow a muscle-memory "git" prefix to your commands inside the git shell.
jQuery promises: done() and then() are not the same
jQuery's deferred objects behave somewhat like standard promises, but not really.
One of many subtle differences is that there are two ways to chain callbacks to an async functions.
The first one is done
, which only exists in jQuery:
$.ajax('/foo').done(function(html) {
console.debug("The server responded with %s", html);
});
There is also then
, which all promise libraries have:
$.ajax('/foo').then(function(html) {
console.debug("The server resp...