Skip to content

Instantly share code, notes, and snippets.

@gordonbrander
Last active August 29, 2015 14:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gordonbrander/652bd1df516c2c676246 to your computer and use it in GitHub Desktop.
Save gordonbrander/652bd1df516c2c676246 to your computer and use it in GitHub Desktop.
Foldable -- fold elements based on textContent matches

Basic stuff

Add an element to an indexed object, mutating it. Returns object.

add = (indexed, item) ->
  Array.prototype.push.call(indexed, item)
  indexed

Generic reduction over anything. This will come in handy for iterating over dom collections and having generic manipulation functions.

reduce = (thing, step, reduction) ->
  if not thing? then return reduction
  if thing? and not thing.length? then return step(reduction, thing)
  reduction = step(reduction, item) for item in thing
  return reduction

Generic filter function over anything. Returns a new true array of things that passed the test.

filter = (thing, predicate) ->
  step = (array, item) -> if predicate(item) then add(array, item) else array
  reduce(thing, step, [])

Generic reject function over anything. Returns a new true array of things that failed the test.

reject = (thing, predicate) ->
  step = (array, item) -> if !predicate(item) then add(array, item) else array
  reduce(thing, step, [])

Escape special RegExp characters in a string.

escapeRegExp = (string) -> string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1")

Same as String.prototype.match, but always guarantees an array as return value.

match = (string, pattern) -> string.match(pattern) || []

Make a RegExp objects from an escaped string.

pattern = (string, flags) -> new RegExp(escapeRegExp(string), flags)

Create case insensitive patterns from strings. This is useful for mapping strings.

patterni = (string) -> pattern(string, 'i')

DOM helpers

Add or remove an attribute from a single element. If key and value are provided, then attribute will be set. Otherwise attribute will be removed. Returns the element after mutating.

elAttr = (element, key, value) ->
  if key and value then element.setAttribute(key, value) else element.removeAttribute(key)
  element

Add or remove an attribute from a collection of elements. Returns the element collection after mutating.

attr = (elements, key, value) ->
  reduce(elements, (_, element) -> elAttr(element, key, value))
  elements

URL helpers

Get the hash fragment from a URL:

extractHashFragment = (url) -> url.substring(url.indexOf('#'))

Creates an array of case-insensitive RegExp patterns from all search terms. Extract search words. Spaces are considered "and" relationship. double-quoted search literals are not supported right now.

parseSearchWordPatterns = (search) ->
  # First remove all double-quoted phrases, then split on one or more
  # contiguous space characters.
  search.trim().split(/\s+|(?:\%20)+/g).map(patterni)

Determine if url contains a search fragment.

hasSearchFragment = (url) ->
  extractHashFragment(url).indexOf('#?') is 0

Get search string from URL. Returns string without any garbage.

extractSearchString = (url) ->
  if hasSearchFragment(url) then extractHashFragment(url).replace(/^#\?/, '')
  else ''

Extract all search patterns from a URL.

parseUrlToSearchPatterns = (url) ->
  if hasSearchFragment(url) then parseSearchWordPatterns(extractSearchString(url))
  else []

The business

A function that will hide all elements not having match for pattern and show all elements that do match. If nothing matches, everything is shown. Returns the hidden elements.

showElementsMatching = (elements, patterns) ->
  hasPattern = (element) ->
    stepHasPattern = (isPassing, pattern) ->
      if isPassing then element.textContent.search(pattern) isnt -1 else false
    patterns.reduce(stepHasPattern, true)

  attr(filter(elements, hasPattern), 'hidden')
  attr(reject(elements, hasPattern), 'hidden', 'hidden')

Show everything. A simple wrapper around attr for now. May get fancier if we go toward a fancy model.

showAll = (elements) -> attr(elements, 'hidden')

Ok, so here's the goal: a link of form #?some text will hide everything that doesn't match, replacing hidden nodes with a ....

What we've got now: it hides stuff.

foldable = (elements) ->
  patterns = parseUrlToSearchPatterns(window.location.hash)
  showElementsMatching(elements, patterns)

  onkeydown = (event) ->
    # 27 = esc
    if event.keyCode ==  27 then window.location.hash = ''

  onhashchange = (event) ->
    patterns = parseUrlToSearchPatterns(event.newURL)
    showElementsMatching(elements, patterns)

  window.addEventListener('hashchange', onhashchange)
  window.addEventListener('keydown', onkeydown)

  () ->
    window.removeEventListener('hashchange', onhashchange)
    window.removeEventListener('keydown', onkeydown)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment