neat-rails 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/lib/neat/rails/engine.rb +7 -0
- data/lib/neat/rails/version.rb +5 -0
- data/lib/neat-rails.rb +7 -0
- data/neat-rails.gemspec +21 -0
- data/vendor/assets/javascripts/neat/collection_editor.coffee +215 -0
- data/vendor/assets/javascripts/neat/lib/delayed_action.js +49 -0
- data/vendor/assets/javascripts/neat/lib/inflect.js +825 -0
- data/vendor/assets/javascripts/neat/lib/jquery_extensions.coffee +15 -0
- data/vendor/assets/javascripts/neat/lib/paginated_list.js +204 -0
- data/vendor/assets/javascripts/neat/model_editor.coffee +107 -0
- data/vendor/assets/javascripts/neat.js +23 -0
- metadata +75 -0
@@ -0,0 +1,15 @@
|
|
1
|
+
$.fn.extend
|
2
|
+
cssHover: (selector)->
|
3
|
+
if arguments.length == 0
|
4
|
+
@hover(
|
5
|
+
-> $(@).addClass('hovered'),
|
6
|
+
-> $(@).removeClass('hovered'))
|
7
|
+
else
|
8
|
+
@delegate selector, 'hover', (e)->
|
9
|
+
if e.type == 'mouseenter'
|
10
|
+
$(@).addClass('hovered')
|
11
|
+
else
|
12
|
+
$(@).removeClass('hovered')
|
13
|
+
|
14
|
+
isIn: (selector)->
|
15
|
+
@is(selector) or (@parents(selector).length > 0)
|
@@ -0,0 +1,204 @@
|
|
1
|
+
var Lail; if(!Lail) Lail={};
|
2
|
+
Lail.PaginatedList = function(list, options) {
|
3
|
+
// todo: paste John Resig's code
|
4
|
+
|
5
|
+
options = options || {};
|
6
|
+
var self = this;
|
7
|
+
|
8
|
+
this.set = [];
|
9
|
+
this.set_length = 0;
|
10
|
+
this.current_page = 1;
|
11
|
+
this.page_size = options.page_size || 20;
|
12
|
+
this.always_show = (typeof options.always_show == 'undefined') ? true : options.always_show;
|
13
|
+
this.page_count = 1;
|
14
|
+
this.observer = new Observer();
|
15
|
+
this.pagination_container = null;
|
16
|
+
this.during_init = false;
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
this.count = function() {
|
21
|
+
return self.set_length;
|
22
|
+
}
|
23
|
+
|
24
|
+
|
25
|
+
|
26
|
+
this.init = function(list, initialPage, options) {
|
27
|
+
if((options || {}).page_size) {
|
28
|
+
self.page_size = options.page_size;
|
29
|
+
}
|
30
|
+
self.during_init = true;
|
31
|
+
self.set = list;
|
32
|
+
self.set_length = self.set.length;
|
33
|
+
self.page_count = parseInt(Math.ceil(self.set_length / self.page_size));
|
34
|
+
if(self.page_count < 1) {
|
35
|
+
self.page_count = 1;
|
36
|
+
}
|
37
|
+
self.current_page = 0;
|
38
|
+
self.gotoPage(initialPage || 1);
|
39
|
+
self.during_init = false;
|
40
|
+
}
|
41
|
+
|
42
|
+
this.gotoPage = function(page_number) {
|
43
|
+
page_number = +page_number; // needs to be an integer
|
44
|
+
if(page_number < 1) page_number = 1;
|
45
|
+
if(page_number > self.page_count) page_number = self.page_count;
|
46
|
+
if(page_number != self.current_page) __setCurrentPage(page_number);
|
47
|
+
}
|
48
|
+
|
49
|
+
function __setCurrentPage(page_number) {
|
50
|
+
self.current_page = page_number;
|
51
|
+
self.current_set = self.set.slice(self.firstItemIndex(), self.lastItemIndex());
|
52
|
+
renderPagination();
|
53
|
+
notifyOfPageChange();
|
54
|
+
}
|
55
|
+
|
56
|
+
this.firstItemIndex = function() {
|
57
|
+
return (self.current_page - 1) * self.page_size || 0;
|
58
|
+
}
|
59
|
+
|
60
|
+
this.lastItemIndex = function() {
|
61
|
+
var end = self.current_page * self.page_size;
|
62
|
+
return (end > self.set_length) ? self.set_length : end;
|
63
|
+
}
|
64
|
+
|
65
|
+
this.getCurrentPage = function() {
|
66
|
+
return self.current_page;
|
67
|
+
}
|
68
|
+
|
69
|
+
this.getCurrentSet = function() {
|
70
|
+
return self.current_set;
|
71
|
+
}
|
72
|
+
|
73
|
+
this.getEntireSet = function() {
|
74
|
+
return self.set;
|
75
|
+
}
|
76
|
+
|
77
|
+
function renderPagination() {
|
78
|
+
if(self.pagination_container) {
|
79
|
+
self.pagination_container.html(self.renderPagination());
|
80
|
+
}
|
81
|
+
}
|
82
|
+
|
83
|
+
function notifyOfPageChange() {
|
84
|
+
self.observer.fire('changed', {onInit: self.during_init});
|
85
|
+
}
|
86
|
+
|
87
|
+
|
88
|
+
|
89
|
+
this.onPageChange = function(callback) {
|
90
|
+
self.observer.observe('changed', callback);
|
91
|
+
}
|
92
|
+
|
93
|
+
|
94
|
+
|
95
|
+
this.gotoNextPage = function() {
|
96
|
+
if(self.isLastPage()) {
|
97
|
+
return false;
|
98
|
+
} else {
|
99
|
+
self.gotoPage(self.current_page + 1);
|
100
|
+
return true;
|
101
|
+
}
|
102
|
+
}
|
103
|
+
|
104
|
+
this.getNextPageNumber = function() {
|
105
|
+
return self.isLastPage() ? self.current_page : (self.current_page + 1);
|
106
|
+
}
|
107
|
+
|
108
|
+
this.isLastPage = function() {
|
109
|
+
return (self.current_page == self.page_count);
|
110
|
+
}
|
111
|
+
|
112
|
+
|
113
|
+
|
114
|
+
this.gotoPreviousPage = function() {
|
115
|
+
if(self.isFirstPage()) {
|
116
|
+
return false;
|
117
|
+
} else {
|
118
|
+
self.gotoPage(self.current_page - 1);
|
119
|
+
return true;
|
120
|
+
}
|
121
|
+
}
|
122
|
+
|
123
|
+
this.getPreviousPageNumber = function() {
|
124
|
+
return self.isFirstPage() ? self.current_page : (self.current_page - 1);
|
125
|
+
}
|
126
|
+
|
127
|
+
this.isFirstPage = function() {
|
128
|
+
return (self.current_page == 1);
|
129
|
+
}
|
130
|
+
|
131
|
+
|
132
|
+
|
133
|
+
// !nb: uses jQuery!!!
|
134
|
+
this.renderPaginationIn = function(selector) {
|
135
|
+
self.pagination_container = jQuery(selector);
|
136
|
+
self.pagination_container.delegate('a', 'click', function(e) {
|
137
|
+
e.preventDefault();
|
138
|
+
e.stopImmediatePropagation();
|
139
|
+
var a = jQuery(this);
|
140
|
+
if(a.hasClass('prev_page')) {
|
141
|
+
self.gotoPreviousPage();
|
142
|
+
} else if(a.hasClass('next_page')) {
|
143
|
+
self.gotoNextPage();
|
144
|
+
} else if(a.hasClass('goto_page')) {
|
145
|
+
self.gotoPage(parseInt(a.attr('id').substring(5)));
|
146
|
+
}
|
147
|
+
return false;
|
148
|
+
});
|
149
|
+
}
|
150
|
+
|
151
|
+
// !todo: use Handlebars
|
152
|
+
this.renderPagination = function() {
|
153
|
+
var html = '',
|
154
|
+
current = self.current_page,
|
155
|
+
count = self.page_count,
|
156
|
+
min = 1,
|
157
|
+
max = count;
|
158
|
+
|
159
|
+
if(this.always_show || count > 1) {
|
160
|
+
if(self.isFirstPage()) {
|
161
|
+
html += '<span class="prev_page disabled">« Previous</span> ';
|
162
|
+
} else {
|
163
|
+
html += '<a class="prev_page" href="#" rel="previous">« Previous</a> ';
|
164
|
+
}
|
165
|
+
|
166
|
+
// list no more than 7 page numbers
|
167
|
+
if(self.page_count > 7) {
|
168
|
+
min = current - 3;
|
169
|
+
max = current + 3;
|
170
|
+
var shift = (min < 1) ? (1 - min) : ((max > count) ? (count - max) : 0);
|
171
|
+
min += shift;
|
172
|
+
max += shift;
|
173
|
+
}
|
174
|
+
for(var i=min; i<=max; i++) {
|
175
|
+
if(i == self.current_page) {
|
176
|
+
html += ' <span class="currentPage">' + i + '</span> ';
|
177
|
+
} else {
|
178
|
+
html += ' <a class="goto_page" href="#" id="page_' + i + '">' + i + '</a> ';
|
179
|
+
}
|
180
|
+
}
|
181
|
+
|
182
|
+
if(self.isLastPage()) {
|
183
|
+
html += ' <span class="next_page disabled">Next »</span>';
|
184
|
+
} else {
|
185
|
+
html += ' <a class="next_page" href="#" rel="next">Next »</a>';
|
186
|
+
}
|
187
|
+
}
|
188
|
+
|
189
|
+
return html;
|
190
|
+
}
|
191
|
+
|
192
|
+
// !todo: use handlebars
|
193
|
+
this.renderExtendedPagination = function() {
|
194
|
+
var html = 'Listing <strong>';
|
195
|
+
if(self.page_count > 1) {
|
196
|
+
html += (self.firstItemIndex()+1) + '–' + self.lastItemIndex() + '</strong> of <strong>';
|
197
|
+
}
|
198
|
+
return html + self.count() + '</strong>';
|
199
|
+
}
|
200
|
+
|
201
|
+
|
202
|
+
|
203
|
+
this.init(list || []);
|
204
|
+
}
|
@@ -0,0 +1,107 @@
|
|
1
|
+
class window.Neat.ModelEditor extends Backbone.View
|
2
|
+
tagName: 'li'
|
3
|
+
className: 'row interactive editable'
|
4
|
+
|
5
|
+
initialize: (options)->
|
6
|
+
options = options ? {}
|
7
|
+
@templateOptions = options.templateOptions ? {}
|
8
|
+
@viewPath = @viewPath ? options.viewPath
|
9
|
+
@resource = @resource ? window.inflect.singularize(options.resource)
|
10
|
+
$(@el).addClass(@resource)
|
11
|
+
|
12
|
+
# Renders the 'show' template normally,
|
13
|
+
# renders 'edit' when in edit mode.
|
14
|
+
@showTemplate = JST["#{@viewPath}/show"]
|
15
|
+
@editTemplate = JST["#{@viewPath}/edit"]
|
16
|
+
|
17
|
+
# Wire up events.
|
18
|
+
# Don't use Backbone's events hash because if subclasses
|
19
|
+
# use that more familiar syntax, they'll blow away events
|
20
|
+
# defined in this class.
|
21
|
+
$(@el).delegate('.save-button', 'click', _.bind(@save, @))
|
22
|
+
$(@el).delegate('.delete-button', 'click', _.bind(@delete, @))
|
23
|
+
$(@el).delegate('.cancel-button', 'click', _.bind(@cancelEdit, @))
|
24
|
+
|
25
|
+
# Begin editing when this resource is clicked
|
26
|
+
# unless the user clicked a link or button.
|
27
|
+
$(@el).click (e)=>
|
28
|
+
@edit() if @canEdit() and !$(e.target).isIn('input, button, a, label')
|
29
|
+
|
30
|
+
render: ->
|
31
|
+
json = _.extend(@model.toJSON(), {options: @templateOptions})
|
32
|
+
$(@el).html @template()(json)
|
33
|
+
$(@el).attr('id', "#{@resource}_#{@model.get('id')}") # e.g. "calendar_5"
|
34
|
+
@
|
35
|
+
|
36
|
+
inEdit: -> $(@el).hasClass('editor')
|
37
|
+
canEdit: -> $(@el).hasClass('editable') and !@inEdit()
|
38
|
+
template: -> if @inEdit() then @editTemplate else @showTemplate
|
39
|
+
|
40
|
+
cancelEdit: (e)->
|
41
|
+
e?.preventDefault()
|
42
|
+
e?.stopImmediatePropagation()
|
43
|
+
if @inEdit()
|
44
|
+
$(@el).removeClass('editor').addClass('editable')
|
45
|
+
@render()
|
46
|
+
@trigger('edit:end')
|
47
|
+
@
|
48
|
+
|
49
|
+
edit: ->
|
50
|
+
unless @inEdit()
|
51
|
+
$el = $(@el)
|
52
|
+
$el.addClass('editor').removeClass('editable hovered')
|
53
|
+
@trigger('edit:begin')
|
54
|
+
@render()
|
55
|
+
$el.find(':input:visible').first().focus()
|
56
|
+
@
|
57
|
+
|
58
|
+
save: (e)->
|
59
|
+
e?.preventDefault()
|
60
|
+
$form = $(@el).closest('form')
|
61
|
+
newAttributes = $form.serializeObject()
|
62
|
+
@debug 'saving: ', newAttributes
|
63
|
+
attributes = @model.changedAttributes(newAttributes)
|
64
|
+
|
65
|
+
if attributes
|
66
|
+
previousAttributes = @model.toJSON()
|
67
|
+
|
68
|
+
@model.save attributes,
|
69
|
+
wait: true
|
70
|
+
success: =>
|
71
|
+
for attribute, newValue of attributes
|
72
|
+
@debug " . #{attribute} changed from ", previousAttributes[attribute], " to ", @model.get(attribute)
|
73
|
+
@onSaveSuccess()
|
74
|
+
error: _.bind(@onSaveError, @)
|
75
|
+
|
76
|
+
@cancelEdit()
|
77
|
+
|
78
|
+
delete: (e)->
|
79
|
+
e?.preventDefault()
|
80
|
+
if @confirmDelete(@resource)
|
81
|
+
$(@el).removeClass('editable').addClass('deleted')
|
82
|
+
|
83
|
+
@model.destroy
|
84
|
+
wait: true
|
85
|
+
success: =>
|
86
|
+
@model.collection.remove(@model) if @model.collection
|
87
|
+
@onDeleteSuccess
|
88
|
+
error: _.bind(@onSaveError, @)
|
89
|
+
@cancelEdit()
|
90
|
+
|
91
|
+
confirmDelete: (resource)->
|
92
|
+
confirm("Delete this #{resource}?")
|
93
|
+
|
94
|
+
|
95
|
+
|
96
|
+
onSaveSuccess: ->
|
97
|
+
onSaveError: ->
|
98
|
+
onDeleteSuccess: ->
|
99
|
+
onDeleteError: ->
|
100
|
+
|
101
|
+
|
102
|
+
|
103
|
+
debug: (o...)->
|
104
|
+
@log(o...) if Neat.debug
|
105
|
+
|
106
|
+
log: (o...)->
|
107
|
+
Neat.logger.log "[#{@resource}] ", o...
|
@@ -0,0 +1,23 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into including all the files listed below.
|
2
|
+
// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
|
3
|
+
// be included in the compiled file accessible from http://example.com/assets/application.js
|
4
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
5
|
+
// the compiled file.
|
6
|
+
//
|
7
|
+
//= require_self
|
8
|
+
//= require ./neat/lib/inflect
|
9
|
+
//= require ./neat/lib/paginated_list
|
10
|
+
//= require ./neat/lib/delayed_action
|
11
|
+
//= require ./neat/lib/jquery_extensions
|
12
|
+
//= require ./neat/collection_editor
|
13
|
+
//= require ./neat/model_editor
|
14
|
+
|
15
|
+
window.Neat = window.Neat || {}
|
16
|
+
window.Neat.debug = true;
|
17
|
+
window.Neat.logger = {
|
18
|
+
log: function() {
|
19
|
+
var log, __slice = [].slice, o, _ref;
|
20
|
+
o = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
|
21
|
+
return (_ref = window.console).log.apply(_ref, o);
|
22
|
+
}
|
23
|
+
};
|
metadata
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: neat-rails
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Bob Lail
|
8
|
+
- Luke Booth
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-08-07 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rake
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '0'
|
21
|
+
type: :development
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '0'
|
28
|
+
description: It allows editing collections and models inline
|
29
|
+
email:
|
30
|
+
- bob.lailfamily@gmail.com
|
31
|
+
- luke.booth@cph.org
|
32
|
+
executables: []
|
33
|
+
extensions: []
|
34
|
+
extra_rdoc_files: []
|
35
|
+
files:
|
36
|
+
- ".gitignore"
|
37
|
+
- Gemfile
|
38
|
+
- LICENSE.txt
|
39
|
+
- README.md
|
40
|
+
- Rakefile
|
41
|
+
- lib/neat-rails.rb
|
42
|
+
- lib/neat/rails/engine.rb
|
43
|
+
- lib/neat/rails/version.rb
|
44
|
+
- neat-rails.gemspec
|
45
|
+
- vendor/assets/javascripts/neat.js
|
46
|
+
- vendor/assets/javascripts/neat/collection_editor.coffee
|
47
|
+
- vendor/assets/javascripts/neat/lib/delayed_action.js
|
48
|
+
- vendor/assets/javascripts/neat/lib/inflect.js
|
49
|
+
- vendor/assets/javascripts/neat/lib/jquery_extensions.coffee
|
50
|
+
- vendor/assets/javascripts/neat/lib/paginated_list.js
|
51
|
+
- vendor/assets/javascripts/neat/model_editor.coffee
|
52
|
+
homepage: ''
|
53
|
+
licenses: []
|
54
|
+
metadata: {}
|
55
|
+
post_install_message:
|
56
|
+
rdoc_options: []
|
57
|
+
require_paths:
|
58
|
+
- lib
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
requirements: []
|
70
|
+
rubyforge_project:
|
71
|
+
rubygems_version: 2.2.0
|
72
|
+
signing_key:
|
73
|
+
specification_version: 4
|
74
|
+
summary: It's like FreightTrain for Backbone. That's pretty neat!
|
75
|
+
test_files: []
|