neat-rails 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0917bb29396d40820cd7ea76add02769a75ebf06
4
+ data.tar.gz: 215afdec3ef8a2ea45eed909282ce24b33cb573a
5
+ SHA512:
6
+ metadata.gz: 881a5a3ee3673cf59dcb969c1fb92be12f0cc1851e238f7ab4842191ff28bd60667427c97d7479b5665ecd0b8e540412118e9aab8ee8f8741a843671f875a3c4
7
+ data.tar.gz: a383cfcdfcf454b35d9a5d2d4486a127f2482de763ae076b6472338e05eb3455bc89ece97427f6af4e0fcfe2e7891c753c72a5185608fa179751fce892f66e33
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in neat-rails.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Robert Lail
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Neat::Rails
2
+
3
+ It's like FreightTrain for Backbone. That's pretty neat!
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'neat-rails'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install neat-rails
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,7 @@
1
+ # Configure Rails 3.1 to have assets in the load path
2
+ module Neat
3
+ module Rails
4
+ class Engine < ::Rails::Engine
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ module Neat
2
+ module Rails
3
+ VERSION = "0.0.2"
4
+ end
5
+ end
data/lib/neat-rails.rb ADDED
@@ -0,0 +1,7 @@
1
+ require "neat/rails/version"
2
+ require "neat/rails/engine"
3
+
4
+ module Neat
5
+ module Rails
6
+ end
7
+ end
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'neat/rails/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "neat-rails"
8
+ gem.version = Neat::Rails::VERSION
9
+ gem.authors = ["Bob Lail", "Luke Booth"]
10
+ gem.email = ["bob.lailfamily@gmail.com", "luke.booth@cph.org"]
11
+ gem.description = %q{It allows editing collections and models inline}
12
+ gem.summary = %q{It's like FreightTrain for Backbone. That's pretty neat!}
13
+ gem.homepage = ""
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_development_dependency "rake"
21
+ end
@@ -0,0 +1,215 @@
1
+ KEYS = {
2
+ 13: "RETURN",
3
+ 27: "ESC",
4
+ 38: "UP",
5
+ 40: "DOWN"
6
+ }
7
+
8
+ # Classes that inherit from EditableCollectionView
9
+ # must define one property:
10
+ # resource
11
+ #
12
+ # Can optionally define
13
+ # modelView - by default this is set to {Resource}View
14
+ # template - by default this is set to #{resource}/index
15
+ # viewPath - by default this is set to resource
16
+ #
17
+ class window.Neat.CollectionEditor extends Backbone.View
18
+ sortedBy: 'name'
19
+ sortOrder: 'asc'
20
+ sortAliases: {}
21
+ templateOptions: {}
22
+ pageSize: 30
23
+
24
+ keyDownHandlers:
25
+ ESC: -> @viewInEdit?.cancelEdit()
26
+ RETURN: -> @viewInEdit?.save()
27
+ UP: -> @edit @prevView()
28
+ DOWN: -> @edit @nextView()
29
+
30
+ initialize: ->
31
+ @viewPath = @viewPath ? @resource
32
+ @singular = inflect.singularize(@resource) # e.g. "calendar"
33
+ @modelView = @modelView ? (()=>
34
+ viewName = inflect.camelize(@singular) + "View" # e.g. "CalendarView"
35
+ @debug "expects viewName to be #{viewName}"
36
+ window[viewName])() # e.g. window["CalendarView"]
37
+
38
+ @debug "looking for template at #{"#{@viewPath}/index"}"
39
+ @template = @template ? Neat.template["#{@viewPath}/index"]
40
+
41
+ @paginator = new window.Lail.PaginatedList [],
42
+ page_size: if window.Neat.forPrint then Infinity else @pageSize
43
+ always_show: false
44
+ @paginator.onPageChange _.bind(@renderPage, @)
45
+
46
+ @collection.bind 'reset', @render, @
47
+ @collection.bind 'add', @rerenderPage, @
48
+ @collection.bind 'remove', @rerenderPage, @
49
+
50
+ # We need to rerender the page if a model's attributes
51
+ # have changed just in case this would affect how the
52
+ # models are sorted.
53
+ #
54
+ # !todo: perhaps we can be smarter here and only listen
55
+ # for changes to the attribute the view is sorted on.
56
+ #
57
+ # We don't want to redraw the page every time a model
58
+ # has changed. If an old transaction is deleted, a very
59
+ # large number of more recent transactions' running
60
+ # balances could be updated very rapidly. Redrawing the
61
+ # page will slow things down dramatically.
62
+ #
63
+ # Instead, redraw the page 500ms after a model has changed.
64
+ #
65
+ # This allows us to wait for activity to die down
66
+ # and to redraw the page when it's more likely the system
67
+ # has settled into new state.
68
+ #
69
+ @delayedRerender = new Lail.DelayedAction(_.bind(@rerenderPage, @), delay: 500)
70
+ @collection.bind 'change', @delayedRerender.trigger
71
+
72
+ # If the view's headers are 'a' tags, this view will try
73
+ # to sort the collection using the header tags.
74
+ $(@el).delegate '.header a', 'click', _.bind(@sort, @)
75
+ $(@el).delegate '.editor', 'keydown', _.bind(@onKeyDown, @)
76
+
77
+ @views = []
78
+ @viewInEdit = null
79
+ @templateOptions = {}
80
+
81
+ repaginate: ->
82
+ @rerenderPage(1)
83
+
84
+ rerenderPage: (page)->
85
+ page = @paginator.getCurrentPage() unless _.isNumber(page)
86
+ sortField = @sortField(@sortedBy)
87
+ items = @collection.sortBy (model)->
88
+ val = model.get(sortField) || ''
89
+ if _.isString(val) then val.toLowerCase() else val
90
+ items.reverse() if @sortOrder == 'desc'
91
+ @paginator.init items, page
92
+
93
+
94
+ render: ->
95
+ $el = $(@el)
96
+ $el.html @template(collection: @collection)
97
+ $el.cssHover '.row.interactive'
98
+
99
+ @afterRender()
100
+
101
+ @updateSortStyle() if @sortedBy
102
+
103
+ @paginator.renderPaginationIn($el.find('.pagination'))
104
+ @repaginate()
105
+ @
106
+
107
+ afterRender: ->
108
+ @
109
+
110
+ renderPage: ->
111
+ alt = false
112
+ $ul = $(@el).find("##{@resource}").empty() # e.g. $('#calendars')
113
+ @views = []
114
+ self = @
115
+
116
+ $(@el).find('.extended-pagination').html(@paginator.renderExtendedPagination())
117
+
118
+ for model in @paginator.getCurrentSet()
119
+ view = new @modelView # e.g. window.CalendarView
120
+ resource: @resource
121
+ viewPath: @viewPath
122
+ model: model
123
+ templateOptions: @templateOptions
124
+ view.bind 'edit:begin', -> self.beforeEdit.call(self, @)
125
+ view.bind 'edit:end', -> self.afterEdit.call(self, @)
126
+
127
+ @views.push(view)
128
+
129
+ $el = $(view.render().el)
130
+ $el.toggleClass 'alt', !(alt = !alt)
131
+ $ul.append $el
132
+ @
133
+
134
+
135
+
136
+ beforeEdit: (view)->
137
+ if @viewInEdit
138
+ @debug "cancelling edit for ##{$(@viewInEdit.el).attr('id')} (#{@indexOfViewInEdit()})"
139
+ @viewInEdit.cancelEdit()
140
+ @viewInEdit = view
141
+ @debug "beginning edit for ##{$(@viewInEdit.el).attr('id')} (#{@indexOfViewInEdit()})"
142
+
143
+ afterEdit: (view)->
144
+ @viewInEdit = null if @viewInEdit == view
145
+
146
+
147
+
148
+ sort: (e)->
149
+ e.preventDefault()
150
+ e.stopImmediatePropagation()
151
+ sortBy = $(e.target).closest('a').attr('class').substring(@singular.length + 1)
152
+ @log "sort by #{sortBy} [#{@sortField(sortBy)}]"
153
+ if @sortedBy == sortBy
154
+ @sortOrder = if @sortOrder == 'asc' then 'desc' else 'asc'
155
+ else
156
+ @removeSortStyle @sortedBy
157
+ @sortedBy = sortBy
158
+ @repaginate()
159
+ @updateSortStyle()
160
+ false
161
+
162
+ removeSortStyle: (field)->
163
+
164
+ updateSortStyle: ()->
165
+ @removeSortStyle @sortedBy
166
+
167
+ getHeader: (field)->
168
+ $(@el).find(".header > .#{@singular}-#{field}")
169
+
170
+ sortField: (field)->
171
+ @sortAliases[field] ? field
172
+
173
+
174
+
175
+ onKeyDown: (e)->
176
+ keyName = @identifyKey(e.keyCode)
177
+ handler = @keyDownHandlers[keyName]
178
+ if handler && !@ignoreKeyEventsForTarget(e.target)
179
+ e.preventDefault()
180
+ handler.apply(@)
181
+
182
+ identifyKey: (code)->
183
+ KEYS[code]
184
+
185
+ ignoreKeyEventsForTarget: (target)->
186
+ # i.e. return true if target is in a dropdown control like Chosen
187
+ false
188
+
189
+
190
+
191
+ nextView: ->
192
+ @views[@indexOfViewInEdit() + 1]
193
+
194
+ prevView: ->
195
+ @views[@indexOfViewInEdit() - 1]
196
+
197
+ indexOfViewInEdit: ->
198
+ _.indexOf @views, @viewInEdit
199
+
200
+ edit: (view)->
201
+ if view
202
+ @viewInEdit?.save()
203
+ view.edit()
204
+ @viewInEdit = view
205
+
206
+ cancelEdit: ->
207
+ @viewInEdit?.cancelEdit()
208
+
209
+
210
+
211
+ debug: (o...)->
212
+ @log(o...) if Neat.debug
213
+
214
+ log: (o...)->
215
+ Neat.logger.log "[#{@viewPath}] ", o...
@@ -0,0 +1,49 @@
1
+ var Lail; if(!Lail) Lail={};
2
+ Lail.DelayedAction = function(callback, options) {
3
+ // !todo: paste John Resig's code
4
+
5
+ options = options || {};
6
+ var self = this,
7
+ delay = options.delay || 1000,
8
+ steps = options.steps || 10,
9
+ intervalPeriod = delay / steps,
10
+ counter = 0,
11
+ _params,
12
+ _interval;
13
+
14
+ this.trigger = function(params) {
15
+ _params = params;
16
+ restartCountdown();
17
+ }
18
+
19
+ function restartCountdown() {
20
+ counter = steps;
21
+ if(!_interval) {
22
+ _interval = setInterval(countdown, intervalPeriod);
23
+ }
24
+ }
25
+
26
+ function countdown() {
27
+ if(counter > 0) {
28
+ counter = counter - 1;
29
+ } else {
30
+ stopCountdown()
31
+ fireCallback();
32
+ }
33
+ }
34
+
35
+ function stopCountdown() {
36
+ if(_interval) {
37
+ clearInterval(_interval);
38
+ _interval = null;
39
+ }
40
+ }
41
+
42
+ function fireCallback() {
43
+ try {
44
+ callback(_params);
45
+ } catch(e) {
46
+ App.debug(e);
47
+ }
48
+ }
49
+ }