 # Task list checkboxes are rendered as disabled by default because rendered
 # user content is cached without regard for the viewer.
 
-incomplete = "[ ]"
-complete   = "[x]"
-
-# Escapes the String for regular expression matching.
-escapePattern = (str) ->
-  str.
-    replace(/([\[\]])/g, "\\$1"). # escape square brackets
-    replace(/\s/, "\\s").         # match all white space
-    replace("x", "[xX]")          # match all cases
-
-incompletePattern = ///
-  #{escapePattern(incomplete)}
-///
-completePattern = ///
-  #{escapePattern(complete)}
-///
-
-# Pattern used to identify all task list items.
-# Useful when you need iterate over all items.
-itemPattern = ///
-  ^
-  (?:                     # prefix, consisting of
-    \s*                   # optional leading whitespace
-    (?:>\s*)*             # zero or more blockquotes
-    (?:[-+*]|(?:\d+\.))   # list item indicator
-  )
-  \s*                     # optional whitespace prefix
-  (                       # checkbox
-    #{escapePattern(complete)}|
-    #{escapePattern(incomplete)}
-  )
-  \s+                     # is followed by whitespace
-  (?!
-    \(.*?\)               # is not part of a [foo](url) link
-  )
-  (?=                     # and is followed by zero or more links
-    (?:\[.*?\]\s*(?:\[.*?\]|\(.*?\))\s*)*
-    (?:[^\[]|$)           # and either a non-link or the end of the string
-  )
-///
-
-# Used to skip checkbox markup inside of code fences.
-# http://rubular.com/r/TfCDNsy8x4
-startFencesPattern = /^`{3}.*$/
-endFencesPattern = /^`{3}$/
-
-# Used to filter out potential mismatches (items not in lists).
-# http://rubular.com/r/OInl6CiePy
-itemsInParasPattern = ///
-  ^
-  (
-    #{escapePattern(complete)}|
-    #{escapePattern(incomplete)}
-  )
-  .+
-  $
-///g
-
-# Given the source text, updates the appropriate task list item to match the
-# given checked value.
-#
-# Returns the updated String text.
-updateTaskListItem = (source, itemIndex, checked) ->
-  clean = source.replace(/\r/g, '').replace(itemsInParasPattern, '').split("\n")
-  index = 0
-  inCodeBlock = false
-  result = for line in source.split("\n")
-    if inCodeBlock
-      # Lines inside of a code block are ignored.
-      if line.match(endFencesPattern)
-        # Stop ignoring lines once the code block is closed.
-        inCodeBlock = false
-    else if line.match(startFencesPattern)
-      # Start ignoring lines inside a code block.
-      inCodeBlock = true
-    else if line in clean && line.match(itemPattern)
-      index += 1
-      if index == itemIndex
-        line =
-          if checked
-            line.replace(incompletePattern, complete)
-          else
-            line.replace(completePattern, incomplete)
-    line
-  result.join("\n")
-
-# Updates the $field value to reflect the state of $item.
-# Triggers the `tasklist:change` event before the value has changed, and fires
-# a `tasklist:changed` event once the value has changed.
-updateTaskList = ($item) ->
-  $container = $item.closest '.js-task-list-container'
-  $field     = $container.find '.js-task-list-field'
-  index      = 1 + $container.find('.task-list-item-checkbox').index($item)
-  checked    = $item.prop 'checked'
-
-  event = $.Event 'tasklist:change'
-  $field.trigger event, [index, checked]
-
-  unless event.isDefaultPrevented()
-    $field.val updateTaskListItem($field.val(), index, checked)
-    $field.trigger 'change'
-    $field.trigger 'tasklist:changed', [index, checked]
-
-# When the task list item checkbox is updated, submit the change
-$(document).on 'change', '.task-list-item-checkbox', ->
-  updateTaskList $(this)
-
-# Enables TaskList item changes.
-enableTaskList = ($container) ->
-  if $container.find('.js-task-list-field').length > 0
-    $container.
-      find('.task-list-item').addClass('enabled').
-      find('.task-list-item-checkbox').attr('disabled', null)
-    $container.addClass('is-task-list-enabled').
-      trigger 'tasklist:enabled'
-
-# Enables a collection of TaskList containers.
-enableTaskLists = ($containers) ->
-  for container in $containers
-    enableTaskList $(container)
-
-# Disable TaskList item changes.
-disableTaskList = ($container) ->
-  $container.
-    find('.task-list-item').removeClass('enabled').
-    find('.task-list-item-checkbox').attr('disabled', 'disabled')
-  $container.removeClass('is-task-list-enabled').
-    trigger 'tasklist:disabled'
-
-# Disables a collection of TaskList containers.
-disableTaskLists = ($containers) ->
-  for container in $containers
-    disableTaskList $(container)
-
-$.fn.taskList = (method) ->
-  $container = $(this).closest('.js-task-list-container')
-
-  methods =
-    enable: enableTaskLists
-    disable: disableTaskLists
-
-  methods[method || 'enable']($container)
+NodeArray = (nodeList) -> Array.prototype.slice.apply(nodeList)
+
+closest = (el, className) ->
+  while el && !el.classList.contains className
+    el = el.parentNode
+  el
+
+class TaskList
+  constructor: (@el, method) ->
+    @container = closest @el, 'js-task-list-container'
+    @field = @container.querySelector '.js-task-list-field'
+    # When the task list item checkbox is updated, submit the change
+    @container.addEventListener 'change', (event) =>
+      if event.target.classList.contains 'task-list-item-checkbox'
+        @updateTaskList(event.target)
+
+  enable: ->
+    if @container.querySelectorAll('.js-task-list-field').length > 0
+      NodeArray(@container.querySelectorAll('.task-list-item')).
+      forEach (item) ->
+        item.classList.add('enabled')
+      NodeArray(@container.querySelectorAll('.task-list-item-checkbox')).
+      forEach (checkbox) ->
+        checkbox.disabled = false
+      @container.classList.add 'is-task-list-enabled'
+      event = new Event 'tasklist:enabled',
+        cancelable: true
+        bubbles: true
+      @container.dispatchEvent event
+
+  disable: ->
+    NodeArray(@container.querySelectorAll('.task-list-item')).
+    forEach (item) ->
+      item.classList.remove('enabled')
+    NodeArray(@container.querySelectorAll('.task-list-item-checkbox')).
+    forEach (checkbox) ->
+      checkbox.disabled = true
+    @container.classList.remove('is-task-list-enabled')
+    event = new Event 'tasklist:disabled',
+      cancelable: true
+      bubbles: true
+    @container.dispatchEvent event
+
+  # Updates the field value to reflect the state of item.
+  # Triggers the `tasklist:change` event before the value has changed, and fires
+  # a `tasklist:changed` event once the value has changed.
+  updateTaskList: (item) ->
+    checkboxes = @container.querySelectorAll('.task-list-item-checkbox')
+    index = 1 + NodeArray(checkboxes).indexOf item
+
+    changeEvent = new Event 'tasklist:change',
+      cancelable: true
+      bubbles: true
+    changeEvent.detail =
+      index: index
+      checked: item.checked
+    @field.dispatchEvent changeEvent
+
+    unless changeEvent.defaultPrevented
+      @field.value = TaskList.updateSource(@field.value, index, item.checked)
+      changeEvent = new Event 'change',
+        cancelable: true
+        bubbles: true
+      @field.dispatchEvent changeEvent
+      changedEvent = new Event 'tasklist:changed',
+        cancelable: true
+        bubbles: true
+      changedEvent.detail =
+        index: index
+        checked: item.checked
+      @field.dispatchEvent changedEvent
+
+  # Static interface
+
+  @incomplete: "[ ]"
+  @complete: "[x]"
+
+  # Escapes the String for regular expression matching.
+  @escapePattern: (str) ->
+    str.
+      replace(/([\[\]])/g, "\\$1"). # escape square brackets
+      replace(/\s/, "\\s").         # match all white space
+      replace("x", "[xX]")          # match all cases
+
+  @incompletePattern: ///
+    #{@escapePattern(@incomplete)}
+  ///
+  @completePattern: ///
+    #{@escapePattern(@complete)}
+  ///
+
+  # Pattern used to identify all task list items.
+  # Useful when you need iterate over all items.
+  @itemPattern: ///
+    ^
+    (?:                     # prefix, consisting of
+      \s*                   # optional leading whitespace
+      (?:>\s*)*             # zero or more blockquotes
+      (?:[-+*]|(?:\d+\.))   # list item indicator
+    )
+    \s*                     # optional whitespace prefix
+    (                       # checkbox
+      #{@escapePattern(@complete)}|
+      #{@escapePattern(@incomplete)}
+    )
+    \s+                     # is followed by whitespace
+    (?!
+      \(.*?\)               # is not part of a [foo](url) link
+    )
+    (?=                     # and is followed by zero or more links
+      (?:\[.*?\]\s*(?:\[.*?\]|\(.*?\))\s*)*
+      (?:[^\[]|$)           # and either a non-link or the end of the string
+    )
+  ///
+
+  # Used to skip checkbox markup inside of code fences.
+  # http://rubular.com/r/TfCDNsy8x4
+  @startFencesPattern: /^`{3}.*$/
+  @endFencesPattern: /^`{3}$/
+
+  # Used to filter out potential mismatches (items not in lists).
+  # http://rubular.com/r/OInl6CiePy
+  @itemsInParasPattern: ///
+    ^
+    (
+      #{@escapePattern(@complete)}|
+      #{@escapePattern(@incomplete)}
+    )
+    .+
+    $
+  ///g
+
+  # Given the source text, updates the appropriate task list item to match the
+  # given checked value.
+  #
+  # Returns the updated String text.
+  @updateSource: (source, itemIndex, checked) ->
+    clean = source.replace(/\r/g, '').
+      replace(@itemsInParasPattern, '').
+      split("\n")
+    index = 0
+    inCodeBlock = false
+    result = for line in source.split("\n")
+      if inCodeBlock
+        # Lines inside of a code block are ignored.
+        if line.match(@endFencesPattern)
+          # Stop ignoring lines once the code block is closed.
+          inCodeBlock = false
+      else if line.match(@startFencesPattern)
+        # Start ignoring lines inside a code block.
+        inCodeBlock = true
+      else if line in clean && line.match(@itemPattern)
+        index += 1
+        if index == itemIndex
+          line =
+            if checked
+              line.replace(@incompletePattern, @complete)
+            else
+              line.replace(@completePattern, @incomplete)
+      line
+    result.join("\n")
+
+if window.jQuery
+  jQuery.fn.taskList = (method) ->
+    this.each (index, el) ->
+      taskList = jQuery(el).data('task-list')
+      if !taskList
+        taskList = new TaskList el
+        jQuery(el).data 'task-list', taskList
+
+      taskList[method || 'enable']()

# TaskList Behavior
#
#= provides tasklist:enabled
#= provides tasklist:disabled
#= provides tasklist:change
#= provides tasklist:changed
#
#= require jquery
#
# Enables Task List update behavior.
#

# TaskList updates trigger `tasklist:change` events. If the change is
# successful, `tasklist:changed` is fired. The change can be canceled.
#
# ### Methods
#
# `.taskList('enable')` or `.taskList()`
