neat-rails 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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">&#171; Previous</span> ';
162
+ } else {
163
+ html += '<a class="prev_page" href="#" rel="previous">&#171; 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 &#187;</span>';
184
+ } else {
185
+ html += ' <a class="next_page" href="#" rel="next">Next &#187;</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) + '&ndash;' + 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: []