jQuery: Find a selector in both descendants and the element itself

jQuery's find looks in the element's descendants. It will never return the current element itself, even if the element matches the given selector.

Require the attached file and you can now say:

$('.container').findWithSelf('.selector')

This is sort of like closest, but it looks in descendants instead of ancestors.

Preloading images with CSS

Sometimes you want to preload images that you will be using later. E.g. if hovering over a an area changes its background image, the new image should be preloaded. If you only load it once the user starts hovering, there will be a delay until the background image flips.

The attached article explains how to preload images with only CSS. No Javascript required.

The gist is:

.element:after {
  content: url(img01.jpg) url(img02.jpg) url(img03.jpg);
  display: none;
}

Delay your Jasmine tests until the document is ready

To delay your entire Jasmine test suite until the DOM is ready, add the following:

beforeAll(function(done) {
  $(done);
});

jQuery Core 3.0 Upgrade Guide | jQuery

Since jQuery 3 saw it's first release candidate today, the links has a list of (breaking) changes.

How to: Client-side language detection

When you have a localized website, you may want to redirect users to their preferred language when they visit the root path.
Here is how to do it without a server-side component (like a Rails application).

  • Use JavaScript's navigator.language (real browsers and IE11+) and navigator.userLanguage (old IEs).
  • Use a <meta> refresh as fallback
  • Provide buttons for paranoid users that disabled JavaScript and meta refreshs.

JavaScript

The following JavaScript will try to auto-detect a user's preferred language.

It understands string...

thoughtbot/fake_stripe: A Stripe fake so that you can avoid hitting Stripe servers in tests.

fake_stripe spins up a local server that acts like Stripe’s and also serves a fake version of Stripe.js, Stripe’s JavaScript library that allows you to collect your customers’ payment information without ever having it touch your servers. It spins up when you run your feature specs, so that you can test your purchase flow without hitting Stripe’s servers or making any external HTTP requests.

We've also had tests actually hitting the testing sandbox of Stripe, which worked OK most of the time (can be flakey).

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...

Reading and writing cookies in JavaScript

You can use JavaScript to get or set cookie values on the client.

Using the vanilla JavaScript API

In JavaScript, document.cookie is an accessor to all cookies on the current site. It looks like a String, but its setter is actually more powerful.

When setting cookies this way, remember to set the path=/ option.

Reading cookies

A result may look like this:

hello=universe; foo=bar

This means that there are 2 cookies: "hello" with value "universe", and "foo" with value "bar...

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...

How to quickly inspect an Angular scope in your webkit browser

Current webkit browsers like Chrome and Safari have a special variable in their consoles that refers to the selected DOM node in the elements panel. This lets us easily inspect Angular scopes.

  1. Right click in the page and click "Inspect" to open the Dev Tools

  2. Select the element you're interested in from the elements panel

  3. Focus the console (in Chrome, hit ESC)

  4. Get the scope object and store it

    s=$($0).scope()
    
    // That is:
    element = $0 // Store element
    $element = $(element) // Wrap with j...
    

Infinitely nested hashes in Javascript (Coffeescript)

The NestedHash class allows you to read and write hashes of any depth. Examples:

hash = {}
NestedHash.write hash, 'a', 'b', 'c', 'value' # => { a: { b: { c: 'value' } } }
NestedHash.read hash, 'a', 'b', 'c' # => 'value'
NestedHash.read hash, 'a' # => { b: { c: 'value' } }
NestedHash.read hash, 'foo', 'bar' # => undefined

Inspired by victusfate.

Code

class @NestedHash

  @read: (objekt, keys...) ->
    if objekt and keys.length
      @read objekt[keys[0]], keys[1...]...
    ...

IE11: Trigger native mouse events with Javascript

The attached Coffeescript helper will let you create mouse events:

$element = $('div')
Trigger.mouseover($element)
Trigger.mouseenter($element)
Trigger.mousedown($element)
Trigger.mouseup($element)
Trigger.mouseout($element)
Trigger.mouseleave($element)
Trigger.click($element)

The dispatched events are real DOM events, which will trigger both native and jQuery handlers.
jQuery's .trigger is simpler, but will only trigger event handlers that were bound by jQuery's .on.

Real user actions t...

Looping through iterators in Coffeescript

Some modern Javascript APIs return iterators instead of arrays.

In plain Javascript you can loop through an iterator using
for...of:

var iterator = ...;

for (var value of iterator) {
   console.log(value); 
}

While there is a for...of construct in Coffeescript, it iterates through property/value pairs and **...

Manually uploading files via AJAX

To upload a file via AJAX (e.g. from an <input type='file'>) you need to wrap your params in a FormData object.

You can initialize a FormData using the contents of a form:

var form = document.querySelector('form.my-form') // Find the <form> element
var formData = new FormData(form); // Wrap form contents

Or you can construct it manually, param by param:

var fileInput = document.querySelector('form input[type=file]');
var attachment = fileInput.files[0];

var f...

Heads up: Angular may break links to the current URL (e.g. when using ngInclude)

Angular's location provider stalls links to the current URL, i.e. window.location. As soon as the $location service is activated in an Angular app, it will intercept links. The click event handler is registered in $LocationProvider.$get().

The motivation is reasonable, as they want to keep the Browser history clean when Angular is controlling it. However, when Angular is NOT controlling your interaction with the browser history (i.e. you're just using Angular as JS sugar on your page), Angular will create the above issue as soon as you u...

Don't open user-supplied links with target="_blank"

This will give the target site full access to your Javascript environment through window.opener, if the target is on the same domain.

Even if the target site is on another domain, it still has some access and can for example manipulate window.location to perform a phishing attack.

You may use a rel="noopener" attribute to avoid this in modern browsers, except IE or Edge.

Capybara: Find the innermost DOM element that contains a given string

Let's say you want to find the element with the text hello in the following DOM tree:

<html>
  <body>
    <article>
      <strong>hello</strong>
      <strong>world</strong>
    </article>
  </body>
</html>

You might think of XPath's contain() function:

page.find(:xpath, ".//*[contains(text(), 'hello')")

Unfortunately that returns a lot more elements than you expect:

[ <html>...<html>,
  <body>...</body>,
  <article>...</article>,
  <strong>hello</strong> ]

What you need to do instead is to *find all...

HTTP 302 redirects for PATCH or DELETE will not redirect with GET

A HTTP 302 Found redirect to PATCH and DELETE requests will be followed with PATCH or DELETE. Redirect responses to GET and POST will be followed with a GET. The Rails form_for helper will use a workaround to send POST requests with a _method param to avoid this issue for PATCH/DELETE.

If you make requests yourself, watch out for the following behavior.

When you make an AJAX request PATCH /foo and the /foo action redirects to /bar, browsers will request PATCH /bar. You probably expected the second requ...

IIFEs in Coffeescript

In JavaScript we often use Immediately Invoked Function Expessions (or IIFEs) to prevent local variables from bleeding into an outside scope:

(function() {
  var foo = "value"; // foo is scoped to this IIFE
})();

In Coffeescript an IIFE looks like this:

(->
  foo = "value" # foo is scoped to this IIFE
)()

There is also a shorthand syntax with do:

do ->
  foo = "value" # foo is scoped to this IIFE

You can also use do with arguments t...

Useful tricks for logging debugging information to the browser console

During debugging you might pepper your code with lines like these:

console.log('foo = ' + foo + ', bar = ' + bar)

I recommend to use variable interpolation instead:

console.log('foo = %o, bar = %o', foo, bar)

This has some advantages:

  1. It's easier to write
  2. Variables are colored in the console output
  3. You don't need to stringify arguments so the + operator doesn't explode
  4. If the variable is a structured object like a D...

How to generate a Rails-compatible query string

From Rails 3.0.9, there is a method Hash#to_query that will turn a Hash into a query string:

>> {:a => "a", :b => ["c", "d", "e"]}.to_query
=> "a=a&b%5B%5D=c&b%5B%5D=d&b%5B%5D=e"
>> CGI.unescape _
=> "a=a&b[]=c&b[]=d&b[]=e"

If you're on the browser side, you can serialize nestd objects to query strings using jQuery's $.param.

Use jQuery's selector engine on vanilla DOM nodes

There are cases when you need to select DOM elements without jQuery, such as:

  • when jQuery is not available
  • when your code is is extremely performance-sensitive
  • when you want to operate on an entire HTML document (which is hard to represent as a jQuery collection).

To select descendants of a vanilla DOM element (i.e. not a jQuery collection), one option is to use your browser's native querySelector and [querySelectorAll](https://developer.mozilla.org/de/docs/We...

How to open a new tab with Selenium

Until recently, you could open a new tab via window.open when using execute_script in Selenium tests. It no longer works in Chrome (will show a "popup blocked" notification).

This is because browsers usually block window.open unless the user interacted with an element for security reasons. I am not sure why it did work via Selenium before.

Here is an approach that will insert a link into the page, and have Selenium click it:

path = "/your/path/here"
id = "helper_#{SecureRandom.hex(8)}"
execute_script <<-JAVASCRIPT
  ...

CSS: Select elements that contain another selector

CSS4 comes with :has. E.g. h1:has(b) would select all <h1> tags that contain a <b> tag.

This is implemented in no browser but the jQuery query engine already supports it as a custom extension.