Skip to content
This repository was archived by the owner on Nov 1, 2017. It is now read-only.

Commit 3db430a

Browse files
committed
Merge pull request #1 from github/caffeinated-encapsulation
Reinvent the checkbox
2 parents ca0ab90 + d38962b commit 3db430a

26 files changed

+708
-395
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,5 @@ spec/reports
1515
test/tmp
1616
test/version_tmp
1717
tmp
18+
components
19+
node_modules

README.md

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,40 @@ And then execute:
2626

2727
### Rails 3+: Rails Engine?
2828

29+
TBD
30+
2931
### Rails 2.3: Manual.
3032

33+
TBD
34+
35+
### CoffeeScript Requirements
36+
37+
The following Bower packages are required:
38+
39+
* jquery
40+
* https://github.com/github/crema -- `$.pageUpdate`
41+
* rails-behavior -- `data-remote`
42+
3143
## Usage
3244

33-
TODO: Write usage instructions here
45+
TBD
46+
47+
## Testing and Development
48+
49+
To run the functional tests in the browser, install the necessary components
50+
with `script/bootstrap`:
51+
52+
```
53+
script/bootstrap
54+
```
55+
56+
Then run the server:
57+
58+
```
59+
rackup -p 4011
60+
```
61+
62+
Navigate to http://localhost:4011/test/functional/test_task_lists_behavior.html
3463

3564
## Contributing
3665

Rakefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ Rake::TestTask.new do |t|
66
t.libs << "lib"
77
t.test_files = FileList['test/**/*_test.rb']
88
t.verbose = true
9-
end
9+
end
Lines changed: 145 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,154 @@
1-
# Task list checkboxes are rendered as disabled by default
2-
# because rendered user content is cached without regard
3-
# for the viewer. Enables the checkboxes and applies the
4-
# correct list style, if the viewer is able to edit the comment.
5-
enableTaskList = (comment) ->
6-
if comment.find('.js-comment-edit-button').length > 0
7-
comment.addClass('context-loader-container')
8-
comment.find('.js-comment-body').addClass('context-loader-overlay')
9-
comment.
1+
# TaskList Behavior
2+
#
3+
#= provides tasklist:enabled
4+
#= provides tasklist:disabled
5+
#= provides tasklist:change
6+
#
7+
#= require crema/events/pageupdate
8+
#
9+
# Enables Task List update behavior.
10+
#
11+
# ### Example Markup
12+
#
13+
# <div class="js-task-list-container">
14+
# <ul class="task-list">
15+
# <li class="task-list-item">
16+
# <label>
17+
# <input type="checkbox" class="js-task-list-item-checkbox" disabled />
18+
# text
19+
# </label>
20+
# </li>
21+
# </ul>
22+
# <form>
23+
# <textarea class="js-task-list-field">- [ ] text</textarea>
24+
# </form>
25+
# </div>
26+
#
27+
# ### Specification
28+
#
29+
# TaskLists MUST be contained in a `div.js-task-list-container`.
30+
#
31+
# TaskList Items SHOULD be an a list (`UL`/`OL`) element.
32+
#
33+
# Task list items MUST match `(input).task-list-item-checkbox` and MUST be
34+
# `disabled` by default. The Item's contents SHOULD be wrapped in a `LABEL`.
35+
#
36+
# TaskLists MUST have a `(textarea).js-task-list-field` form element whose
37+
# `value` attribute is the source (Markdown) to be udpated. The source MUST
38+
# follow the syntax guidelines.
39+
#
40+
# TaskList updates trigger `tasklist:change` events.
41+
#
42+
# jQuery is required and the `$.pageUpdate` behavior MUST be available.
43+
#
44+
# ### Events
45+
#
46+
# When the TaskList field has been changed, a `tasklist:change` event is fired.
47+
#
48+
# ### NOTE
49+
#
50+
# Task list checkboxes are rendered as disabled by default because rendered
51+
# user content is cached without regard for the viewer. We enable checkboxes
52+
# on `pageUpdate` if the container has a `(textarea).js-task-list-field`.
53+
54+
incomplete = "[ ]"
55+
complete = "[x]"
56+
57+
# Pattern used to identify all task list items.
58+
# Useful when you need iterate over all items.
59+
itemPattern = ///
60+
^
61+
(?:\s*[-+*]|(?:\d+\.))? # optional list prefix
62+
\s* # optional whitespace prefix
63+
( # checkbox
64+
#{complete. replace(/([\[\]])/g, "\\$1")}|
65+
#{incomplete.replace(/([\[\]])/g, "\\$1")}
66+
)
67+
(?=\s) # followed by whitespace
68+
///
69+
70+
# Used to filter out code fences from the source for comparison only.
71+
# http://rubular.com/r/x5EwZVrloI
72+
# Modified slightly due to issues with JS
73+
codeFencesPattern = ///
74+
^`{3} # ```
75+
(?:\s*\w+)? # followed by optional language
76+
[\S\s] # whitespace
77+
.* # code
78+
[\S\s] # whitespace
79+
^`{3}$ # ```
80+
///mg
81+
82+
# Used to filter out potential mismatches (items not in lists).
83+
# http://rubular.com/r/OInl6CiePy
84+
itemsInParasPattern = ///
85+
^
86+
(
87+
#{complete. replace(/[\[\]]/g, "\\$1")}|
88+
#{incomplete.replace(/[\[\]]/g, "\\$1")}
89+
)
90+
.+
91+
$
92+
///g
93+
94+
# Given the source text, updates the appropriate task list item to match the
95+
# given checked value.
96+
#
97+
# Returns the updated String text.
98+
updateTaskListItem = (source, itemIndex, checked) ->
99+
clean = source.replace(/\r/g, '').replace(codeFencesPattern, '').
100+
replace(itemsInParasPattern, '').split("\n")
101+
index = 0
102+
result = for line in source.split("\n")
103+
if line in clean && line.match(itemPattern)
104+
index += 1
105+
if index == itemIndex
106+
line =
107+
if checked
108+
line.replace(incomplete, complete)
109+
else
110+
line.replace(complete, incomplete)
111+
line
112+
result.join("\n")
113+
114+
# Enables task list items to trigger updates.
115+
enableTaskList = ($container) ->
116+
if $container.find('.js-task-list-field').length > 0
117+
$container.
10118
find('.task-list-item').addClass('enabled').
11119
find('.task-list-item-checkbox').attr('disabled', null)
12-
comment.closest('.context-loader-container').find('.context-loader:first').
13-
removeClass 'is-context-loading'
120+
$container.trigger 'tasklist:enabled'
14121

15-
disableTaskList = (comment) ->
16-
comment.
122+
disableTaskList = ($container) ->
123+
$container.
17124
find('.task-list-item').removeClass('enabled').
18125
find('.task-list-item-checkbox').attr('disabled', 'disabled')
19-
comment.closest('.context-loader-container').find('.context-loader:first').
20-
addClass 'is-context-loading'
21-
22-
# Submit updates to task list items asynchronously.
23-
# Successful updates won't require re-rendering to represent reality.
24-
updateTaskListItem = (item) ->
25-
comment = item.parents('.js-comment')
26-
form = comment.find('form.js-comment-update')
27-
body = comment.find('.js-comment-body')
28-
data =
29-
item: item.attr('data-item-index')
30-
checked: if item.prop('checked') then 1 else 0
31-
body_version: body.attr('data-body-version')
32-
33-
disableTaskList(comment)
34-
35-
$.ajax
36-
type: 'post'
37-
url: form.attr('action')+'/task'
38-
data: data
39-
dataType: 'json'
40-
success: (data) ->
41-
console.log 'success', data
42-
if data.stale
43-
window.location.href = window.location.href
44-
else
45-
body.attr('data-body-version', data.new_body_version)
46-
comment.find('.form-content .js-comment-field').val(data.new_body)
47-
error: (e) ->
48-
console.log 'error', e
49-
complete: ->
50-
enableTaskList(comment)
126+
$container.trigger 'tasklist:disabled'
127+
128+
# Updates the $field value to reflect the state of $item.
129+
# Triggers the `tasklist:change` event when the value has changed.
130+
updateTaskList = ($item) ->
131+
$container = $item.closest '.js-task-list-container'
132+
$field = $container.find '.js-task-list-field'
133+
index = 1 + $container.find('.task-list-item-checkbox').index($item)
134+
checked = $item.prop 'checked'
135+
136+
disableTaskList $container
137+
138+
$field.val updateTaskListItem($field.val(), index, checked)
139+
$field.trigger 'change'
140+
$field.trigger 'tasklist:change', [index, checked]
51141

52142
# When the task list item checkbox is updated, submit the change
53-
$(document).on 'change', 'input.task-list-item-checkbox[type=checkbox]', ->
54-
updateTaskListItem $(this)
143+
$(document).on 'change', '.task-list-item-checkbox', ->
144+
updateTaskList $(this)
145+
146+
$(document).on 'tasklist:disable', '.js-task-list-container', (event) ->
147+
disableTaskList $(this)
148+
149+
$(document).on 'tasklist:enable', '.js-task-list-container', (event) ->
150+
enableTaskList $(this)
55151

56152
$.pageUpdate ->
57-
$('.js-comment').each ->
58-
enableTaskList($(this))
153+
$('.js-task-list-container').each ->
154+
enableTaskList $(this)

config.ru

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Rack environment for testing purposes
2+
3+
require 'coffee-script'
4+
require 'json'
5+
require 'sprockets'
6+
7+
Root = File.expand_path("..", __FILE__)
8+
9+
Assets = Sprockets::Environment.new(Root) do |env|
10+
env.append_path "components" # bower
11+
env.append_path "app/assets/javascripts"
12+
env.append_path "app/assets/stylesheets"
13+
env.append_path "test"
14+
end
15+
16+
map "/assets" do
17+
run Assets
18+
end
19+
20+
map "/update" do
21+
run lambda { |env|
22+
req = Rack::Request.new(env)
23+
[200, {'Content-Type' => 'application/json'}, [req.params.to_json]]
24+
}
25+
end
26+
27+
map "/" do
28+
run Rack::Directory.new(Root)
29+
end

0 commit comments

Comments
 (0)