tabulatr2 0.9.4 → 0.9.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -0
  3. data/Gemfile +1 -2
  4. data/README.md +155 -95
  5. data/app/assets/javascripts/tabulatr/_storage.js +41 -0
  6. data/app/assets/javascripts/tabulatr/_tabulatr.js +598 -0
  7. data/app/assets/javascripts/tabulatr/application.js +3 -553
  8. data/app/assets/stylesheets/tabulatr/application.css.scss +21 -12
  9. data/app/assets/stylesheets/tabulatr.css +1 -0
  10. data/app/views/tabulatr/_tabulatr_actual_table.html.slim +18 -18
  11. data/app/views/tabulatr/_tabulatr_filter_dialog.html.slim +1 -1
  12. data/app/views/tabulatr/_tabulatr_fuzzy_search_field.html.slim +1 -1
  13. data/app/views/tabulatr/_tabulatr_static_table.html.slim +3 -3
  14. data/app/views/tabulatr/_tabulatr_table.html.slim +17 -12
  15. data/lib/tabulatr/data/data.rb +7 -9
  16. data/lib/tabulatr/data/dsl.rb +36 -21
  17. data/lib/tabulatr/data/filtering.rb +41 -13
  18. data/lib/tabulatr/data/formatting.rb +7 -20
  19. data/lib/tabulatr/data/pagination.rb +1 -2
  20. data/lib/tabulatr/data/proxy.rb +2 -0
  21. data/lib/tabulatr/data/sorting.rb +24 -13
  22. data/lib/tabulatr/engine.rb +1 -0
  23. data/lib/tabulatr/generators/tabulatr/templates/tabulatr.yml +2 -2
  24. data/lib/tabulatr/json_builder.rb +23 -25
  25. data/lib/tabulatr/rails/action_controller.rb +4 -0
  26. data/lib/tabulatr/rails/action_view.rb +3 -2
  27. data/lib/tabulatr/renderer/checkbox.rb +3 -1
  28. data/lib/tabulatr/renderer/column.rb +47 -5
  29. data/lib/tabulatr/renderer/columns_from_block.rb +24 -6
  30. data/lib/tabulatr/renderer/renderer.rb +26 -17
  31. data/lib/tabulatr/utility/unexpected_search_result_error.rb +9 -0
  32. data/lib/tabulatr/utility/utility.rb +4 -0
  33. data/lib/tabulatr/version.rb +1 -1
  34. data/spec/dummy/app/controllers/products_controller.rb +9 -0
  35. data/spec/dummy/app/views/products/local_storage.html.slim +4 -0
  36. data/spec/dummy/app/views/products/simple_index.html.erb +1 -1
  37. data/spec/dummy/app/views/products/stupid_array.html.erb +1 -1
  38. data/spec/dummy/config/application.rb +1 -1
  39. data/spec/dummy/config/locales/tabulatr.yml +2 -2
  40. data/spec/dummy/config/routes.rb +1 -0
  41. data/spec/features/tabulatrs_spec.rb +27 -27
  42. data/spec/lib/tabulatr/data/data_spec.rb +12 -16
  43. data/spec/lib/tabulatr/data/filtering_spec.rb +48 -7
  44. data/spec/lib/tabulatr/data/formatting_spec.rb +32 -0
  45. data/spec/lib/tabulatr/data/sorting_spec.rb +81 -0
  46. data/spec/lib/tabulatr/json_builder_spec.rb +23 -9
  47. data/spec/lib/tabulatr/renderer/checkbox_spec.rb +14 -0
  48. data/spec/lib/tabulatr/renderer/renderer_spec.rb +20 -8
  49. data/tabulatr.gemspec +4 -3
  50. metadata +45 -9
  51. data/lib/tabulatr/data/column_name_builder.rb +0 -86
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0b11a5d5bda9718541550adc016fca03765e3e06
4
- data.tar.gz: 5281e726afe6e44bec05af2a31d72e3546201180
3
+ metadata.gz: 12605d45404e6136d56147d684e8715ec17a46f8
4
+ data.tar.gz: deb114fca5a54182dfeed8aa02b5f0682267ffb2
5
5
  SHA512:
6
- metadata.gz: 1cb47ebf2433da0db9280acb652d833fd4311629d111b03b175ff94ca3e695249222ce841dc7ba94e9e881ac935ebdb095aa1eaf2884670e93ce6284d95596e7
7
- data.tar.gz: 231823bbb5a39e6f95c0991d3debab968f978d9a8ae8fe07203b9266eacb5f9ff729fa8ce9da26fcffd04ecfa7b93f2f5b5d828369c60e63ba8105d50586e5bb
6
+ metadata.gz: b537a626a5255ddb5c513d97c0f651dc99d34345dfda453fd41f9027e00b27972d54d23e477b27021dfad00614cae5c528a5a085994cd042059646f280e77dd4
7
+ data.tar.gz: cf74ff7bc31929f92e5bff94f6779c6e744d6364aa4fb09bff0332a6e1aa1b65bbe7426d5c405f95b7d790c4360d65626af1ddca8c6e22790e2c608687c23330
data/CHANGELOG.md CHANGED
@@ -1,3 +1,32 @@
1
+ ## 0.9.6
2
+ * Adds localStorage persistency for tables. It's automatically turned on for paginated
3
+ tables and you can adjust the setting in your template.
4
+
5
+ Example:
6
+ ```
7
+ table_for Product, persistent: false
8
+ ```
9
+
10
+ * Added `font-awesome-rails`
11
+
12
+ * The DSL now accepts a search block with two block variables. The
13
+ second being the relation. The block needs to return an ActiveRecord::Relation,
14
+ a Hash, a String, nil or an Array.
15
+
16
+ Example:
17
+ ```ruby
18
+ search do |query, relation|
19
+ relation.joins(:vendor).where(["vendors.name = ?", query])
20
+ end
21
+ ```
22
+
23
+ ## 0.9.5
24
+ * Better DOM scopes to enable working with multiple tables per page
25
+ * Added `pagination_position` option to `table_for`.
26
+ Possible values are `:top`, `:bottom` and `:both`.
27
+ * Add 'mark all' checkbox in header row again
28
+ * Added `html_class` option to `table_for`
29
+
1
30
  ## 0.9.4
2
31
 
3
32
  * Fixed date filters
data/Gemfile CHANGED
@@ -3,7 +3,6 @@ source 'https://rubygems.org'
3
3
  gemspec
4
4
 
5
5
  gem 'jquery-rails'
6
- gem 'slim'
7
6
 
8
7
  group :development do
9
8
  gem 'better_errors'
@@ -19,6 +18,6 @@ group :development, :test do
19
18
  gem 'launchy'
20
19
  gem 'database_cleaner', '< 1.1.0'
21
20
  gem 'poltergeist'
22
- gem 'sass-rails', '~> 4.0.0'
21
+ gem 'sass-rails', '~> 4.0.0', '>= 4.0.2'
23
22
  gem 'bootstrap-sass', '~> 3.0.3.0'
24
23
  end
data/README.md CHANGED
@@ -3,6 +3,12 @@
3
3
  [![Code Climate](https://codeclimate.com/github/provideal/tabulatr2.png)](https://codeclimate.com/github/provideal/tabulatr2)
4
4
  [![Travis CI](https://api.travis-ci.org/provideal/tabulatr2.png)](https://travis-ci.org/provideal/tabulatr2)
5
5
 
6
+ ## Requirements
7
+
8
+ * Ruby 2.0.0 or higher
9
+ * Rails 4.0.0 or higher
10
+ * [Bootstrap from Twitter](http://getbootstrap.com)
11
+
6
12
  ## Installation
7
13
 
8
14
  Require tabulatr2 in your Gemfile:
@@ -14,103 +20,186 @@ After that run `bundle install`.
14
20
  Also add `//= require tabulatr` to your application js file and `*= require tabulatr` to your CSS asset
15
21
  pipeline.
16
22
 
17
- Now run the Install generator via
18
- `rails g tabulatr install`
23
+ In order to get the provided `i18n` language files run
24
+ `rails g tabulatr:install`
19
25
 
20
- ## Usage
26
+ ## The DSL
21
27
 
22
- ### Models
28
+ `Tabulatr` provides an easy to use DSL to define the data to be used in your table. It is defined in `TabulatrData`
29
+ classes. If you have a `User` model you would create a `UserTabulatrData` class.
23
30
 
24
- We suppose we have these three models:
25
31
  ```ruby
26
- class Tag < ActiveRecord::Base
27
- has_and_belongs_to_many :products
32
+ class UserTabulatrData < Tabulatr::Data
28
33
  end
29
34
  ```
35
+
36
+ Instead of creating this class by hand you can also generate a `TabulatrData` for a given class by running
37
+ `rails g tabulatr:table User`
38
+
39
+ ### Columns
40
+
41
+ Let's say you want to display each user's `first_name` and `last_name` attribute:
42
+
30
43
  ```ruby
31
- class Product < ActiveRecord::Base
32
- belongs_to :vendor
33
- has_and_belongs_to_many :tags
44
+ class UserTabulatrData < Tabulatr::Data
45
+ column :first_name
46
+ column :last_name
34
47
  end
35
48
  ```
49
+ That's it. It'll work, but let's assume you would like to display the full name in one single column. No worries! `Tabulatr` got you covered:
50
+
51
+ ```ruby
52
+ class UserTabulatrData < Tabulatr::Data
53
+ column :full_name, table_column_options: {header: 'The full name'} do |user|
54
+ link_to "#{user.first_name} #{user.last_name}", user
55
+ end
56
+ end
57
+ ```
58
+ As you can see you just need to provide a block to the `column` call and you can format the cell into whatever you want.
59
+ You can even use all those fancy Rails helpers!
60
+
61
+ Unfortunately, we can't sort and filter this column right now because `Tabulatr` would look for a `full_name` column in
62
+ your DB table but there is no such thing. Bummer! Enter `sort_sql` and `filter_sql`:
63
+
64
+ #### Sorting / Filtering
65
+
36
66
  ```ruby
37
- class Vendor < ActiveRecord::Base
38
- has_many :products
67
+ class UserTabulatrData < Tabulatr::Data
68
+ column :full_name, sort_sql: "users.first_name || '' || users.last_name",
69
+ filter_sql: "users.first_name || '' || users.last_name"
39
70
  end
40
71
  ```
41
72
 
42
- and we want to display information about products in the `ProductsController#index` action.
73
+ With these two options you can provide whatever SQL you would like to have executed when filtering or sorting
74
+ this particular column. If instead you want to disable sorting and filtering at all, you can do that too:
43
75
 
44
- ## ProductTabulatrData
76
+ ```ruby
77
+ class UserTabulatrData < Tabulatr::Data
78
+ column :full_name, table_column_options: {sortable: false, filter: false}
79
+ end
80
+ ```
81
+
82
+ Also you are able to change generated filter form field for this column. Say for example you want to have an `exact`
83
+ filter instead of a `LIKE` filter:
45
84
 
46
- In this class we define which information should be available for the table and how it is formatted.
47
85
  ```ruby
48
- class ProductTabulatrData < Tabulatr::Data
86
+ class UserTabulatrData < Tabulatr::Data
87
+ column :first_name, table_column_options: {filter: :exact}
88
+ end
89
+ ```
90
+
91
+ Following is a table with all the standard mappings for filter fields, which means these filters get created if you
92
+ don't overwrite it:
93
+
94
+ Data type | Generated form field / SQL
95
+ --------- | -------------------------
96
+ integer, float, decimal | String input field, exact match
97
+ string, text | String input field, LIKE match
98
+ date, time, datetime, timestamp | Select field, with options like 'last 30 days', 'today', ...
99
+ boolean | Select field, options are 'Yes', 'No' and 'Both'
49
100
 
50
- search :vendor_address, :title
51
101
 
52
- # search do |query|
53
- # "products.title LIKE '#{query}'"
54
- # end
102
+ ### Associations
55
103
 
56
- column :id
57
- column :title { title.capitalize }
58
- column :price do "#{price} EUR" end
59
- column :vendor_address, sort_sql: "vendors.zipcode || '' || vendors.city",
60
- filter_sql: "vendors.street || '' || vendors.zipcode || '' vendors.city" do
61
- "#{vendor.house_number} #{vendor.street}, #{vendor.zipcode} #{vendor.city}"
104
+ To display associations you would use the `association` method with the association name and the attribute on the
105
+ association as it's first two arguments:
106
+
107
+ ```ruby
108
+ class UserTabulatrData < Tabulatr::Data
109
+ association :citizenship, :name
110
+ end
111
+ ```
112
+
113
+ Associations take the same arguments as the `column` method:
114
+
115
+ ```ruby
116
+ class UserTabulatrData < Tabulatr::Data
117
+ association :citizenship, :name do
118
+ record.citizenship.name.upcase
62
119
  end
63
- column :edit_link do
64
- link_to "edit #{title}", product_path(id)
120
+
121
+ association :posts, :text, table_column_options: {filter: false, sortable: false} do |user|
122
+ user.posts.count
65
123
  end
66
- column :updated_at do
67
- "#{updated_at.strftime('%H:%M %Y/%m/%d')}"
124
+ end
125
+ ```
126
+
127
+ ### Checkbox
128
+
129
+ To provide a checkbox for each row which is needed by batch actions just call `checkbox`:
130
+
131
+ ```ruby
132
+ class UserTabulatrData < Tabulatr::Data
133
+ checkbox
134
+ end
135
+ ```
136
+
137
+ ### Search
138
+
139
+ The DSL provides you with a `search` method to define a custom fuzzy search method which is not bound
140
+ to a specific column.
141
+
142
+ ```ruby
143
+ class UserTabulatrData < Tabulatr::Data
144
+ search do |query|
145
+ "users.first_name LIKE '#{query}' OR users.last_name LIKE '#{query}' OR users.address LIKE '#{query}'"
68
146
  end
69
- association :vendor, :name
70
- association :tags, :title do "'#{tags.map(&:title).map(&:upcase).join(', ')}'" end
71
147
 
148
+ # This call could also be written as:
149
+ # search :first_name, :last_name, :address
72
150
  end
73
151
  ```
74
- The search method is used for a fuzzy search field.
75
152
 
76
- You can automatically generate a new TabulatrData-Class by running
77
- `rails g tabulatr:table MODELNAME`.
153
+ ### Row formatting
78
154
 
79
- This will generate a `MODELNAMETabulatrData` class in `app/tabulatr_data/MODELNAME_data.rb` for you.
155
+ To provide row specific HTML-Attributes call `row`:
80
156
 
81
- This generator also gets executed if you just run the standard Rails `resource` generator.
157
+ ```ruby
158
+ row do |record, row_config|
159
+ if record.super_important?
160
+ row_config[:class] = 'important';
161
+ end
162
+ row_config[:data] = {
163
+ href: edit_user_path(record.id),
164
+ vip: record.super_important?
165
+ }
166
+ end
167
+ ```
168
+
169
+ ## Usage
82
170
 
83
- ## Controller
171
+ Great, we have defined all the required `columns` in the TabulatrData DSL, but how do we display the table now?
84
172
 
85
- In `ProductsController#index` we have:
173
+ In `UsersController#index` we write:
86
174
 
87
175
  ```ruby
88
176
  def index
89
- tabulatr_for Product
177
+ tabulatr_for User
90
178
  end
91
179
  ```
180
+ This call responds to an HTML-Request by rendering the associated `view` and for a JSON-Request by
181
+ fetching the requested records.
182
+
92
183
 
93
184
  _Hint:_ If you want to prefilter your table, you can do that too! Just pass an `ActiveRecord::Relation` to `tabulatr_for`:
94
185
  ```ruby
95
186
  def index
96
- tabulatr_for Product.where(active: true)
187
+ tabulatr_for User.where(active: true)
97
188
  end
98
189
  ```
99
190
 
100
- ### View
101
-
102
- In the view we can use all the attributes which are defined in our `ProductTabulatrData` class.
103
- To display all the columns defined in the `ProductTabulatrData` class we
191
+ In the view we can use all the attributes which are defined in our `UserTabulatrData` class.
192
+ To display all the columns defined in the `UserTabulatrData` class we
104
193
  just need to put the following statement in our view:
105
194
 
106
195
  ```erb
107
- <%= table_for Product %>
196
+ <%= table_for User %>
108
197
  ```
109
198
  If you just want do display a subset of the defined columns or show them in a
110
199
  different order you can provide them as arguments to the `columns` key:
111
200
 
112
201
  ```erb
113
- <%= table_for Product, columns: [:vendor_address, 'vendor:name', {tags: :title}]%>
202
+ <%= table_for User, columns: [:full_name, 'citizenship:name', {posts: :text}]%>
114
203
  ```
115
204
  Note that you can write associations as a string with colon between association
116
205
  name and method or as a hash as you can see above.
@@ -118,13 +207,11 @@ name and method or as a hash as you can see above.
118
207
  An other option is to provide the columns in a block:
119
208
 
120
209
  ```erb
121
- <%= table_for Product do |t|
122
- t.column :title
123
- t.column :price
124
- t.association :vendor, :name
125
- t.column :vendor_address
126
- t.column :updated_at
127
- t.association :tags, :title
210
+ <%= table_for User do |t|
211
+ t.column :full_name
212
+ t.column :active
213
+ t.association :citizenship, :name
214
+ t.association :posts, :text
128
215
  t.column :edit_link
129
216
  end %>
130
217
  ```
@@ -138,20 +225,20 @@ To add a select box with batch-actions (actions that should be performed on all
138
225
  we add an option to the table_for:
139
226
 
140
227
  ```erb
141
- <%= table_for Product, batch_actions: {'foo' => 'Foo', 'delete' => "Delete"} do |t|
228
+ <%= table_for User, batch_actions: {'foo' => 'Foo', 'delete' => "Delete"} do |t|
142
229
  ...
143
230
  end %>
144
231
  ```
145
232
 
146
- To handle the actual batch action, we have to add a block to the `find_for_table` call in the controller:
233
+ To handle the actual batch action, we have to add a block to the `tabulatr_for` call in the controller:
147
234
 
148
235
  ```ruby
149
- tabulatr_for Product do |batch_actions|
236
+ tabulatr_for User do |batch_actions|
150
237
  batch_actions.delete do |ids|
151
238
  ids.each do |id|
152
- Product.find(id).destroy
239
+ User.find(id).destroy
153
240
  end
154
- redirect_to index_select_products_path()
241
+ redirect_to root_path()
155
242
  return
156
243
  end
157
244
  batch_actions.foo do |ids|
@@ -203,12 +290,13 @@ They change the appearance and behaviour of the table.
203
290
  batch_actions: false, # :name => value hash of batch action stuff
204
291
  footer_content: false, # if given, add a <%= content_for <footer_content> %> before the </table>
205
292
  path: '#', # where to send the AJAX-requests to
206
- order_by: nil # default order
293
+ order_by: nil # default order,
294
+ html_class: '' # html classes for the table element
207
295
  ```
208
296
 
209
297
  #### Example:
210
298
  ```erb
211
- <%= table_for Product, {order_by: 'price desc', pagesize: 50} %>
299
+ <%= table_for User, {order_by: 'last_name desc', pagesize: 50} %>
212
300
  ```
213
301
 
214
302
  ### Column Options
@@ -236,30 +324,17 @@ the columns in the block of `table_for`.
236
324
  #### Example:
237
325
  ```erb
238
326
  # in the view
239
- <%= table_for Product do |t|
240
- t.column(:title, header_style: {color: 'red'})
327
+ <%= table_for User do |t|
328
+ t.column(:first_name, header_style: {color: 'red'})
241
329
  # ...
242
330
  %>
243
331
 
244
332
  # or in TabulatrData
245
- class ProductTabulatrData < Tabulatr::Data
246
- column(:title, table_column_options: {header_style: {color: 'red'}})
333
+ class UserTabulatrData < Tabulatr::Data
334
+ column(:first_name, table_column_options: {header_style: {color: 'red'}})
247
335
  end
248
336
  ```
249
337
 
250
- ## Dependencies
251
-
252
- We use [Bootstrap from Twitter](http://getbootstrap.com) in order to make the table look pretty decent.
253
-
254
- ## Known Bugs
255
-
256
- ### Request-URI Too Large error
257
-
258
- This is a problem particulary when using WEBrick, because WEBricks URIs must not exceed 2048 characters.
259
- And this limit is hard-coded IIRC. So – If you run into this limitation –
260
- please consider using another server.
261
- (Thanks to [stepheneb](https://github.com/stepheneb) for calling my attention back to this.)
262
-
263
338
  ## Contributing
264
339
 
265
340
  * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
@@ -274,21 +349,6 @@ please consider using another server.
274
349
  * Feel free to send a pull request if you think others (me, for example) would like to have your change
275
350
  incorporated into future versions of tabulatr.
276
351
 
277
- ## MIT License
278
-
279
- Copyright (c) 2010-2013 Peter Horn, Provideal GmbH</a>
280
-
281
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software
282
- and associated documentation files (the "Software"), to deal in the Software without restriction,
283
- including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
284
- and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
285
- subject to the following conditions:
286
-
287
- The above copyright notice and this permission notice shall be included in all copies or substantial
288
- portions of the Software.
352
+ ## License
289
353
 
290
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
291
- INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
292
- AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
293
- DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
294
- OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
354
+ [MIT](LICENSE)
@@ -0,0 +1,41 @@
1
+ $(function(){
2
+ Tabulatr.prototype.retrieveTableFromLocalStorage = function(response){
3
+ var currentStorage = JSON.parse(localStorage[this.id]);
4
+ if(currentStorage !== undefined){
5
+ $('.pagination[data-table='+ this.id +'] a[data-page='+ response.meta.page +']');
6
+ var $table = $('#' + this.id);
7
+ var tableName = this.id.split('_')[0];
8
+ if(currentStorage[tableName +'_sort'] != ''){
9
+ var sortParam = currentStorage[tableName +'_sort'];
10
+ var header = $table.find('th.tabulatr-sortable[data-tabulatr-column-name="'+ sortParam.split(' ')[0] +'"]');
11
+ header.attr('data-sorted', sortParam.split(' ')[1]);
12
+ header.addClass('sorted');
13
+ $('.tabulatr_filter_form[data-table='+ this.id +'] input[name="'+ tableName +'_sort"]').val(sortParam);
14
+ }
15
+ $('input#'+ this.id +'_fuzzy_search_query').val(currentStorage[tableName +'_search']);
16
+ var objKeys = Object.keys(currentStorage);
17
+ var elem, formParent;
18
+ for(var i = 0; i < objKeys.length; i++){
19
+ elem = $('[name="'+ objKeys[i] +'"]');
20
+ if(elem.length > 0){
21
+ elem.val(currentStorage[objKeys[i]]);
22
+ formParent = elem.parents('.form-group[data-filter-column-name]');
23
+ if(formParent.length > 0){
24
+ formParent.show();
25
+ formParent.siblings('[data-filter-column-name="_submit"]').show();
26
+ }
27
+ }
28
+ }
29
+ }
30
+ };
31
+
32
+ Tabulatr.prototype.resetTable = function(){
33
+ tableName = this.id.split('_')[0];
34
+ localStorage.removeItem(this.id);
35
+ $('table#'+ this.id).find('th.sorted').removeClass('sorted').removeAttr('data-sorted');
36
+ $('form[data-table='+ this.id +'] input.search').val('');
37
+ $('[data-table-id="'+ this.id +'"] [data-filter-column-name]').hide().find('input[type=text], input[type=hidden], select').val('');
38
+ $('.tabulatr_filter_form[data-table='+ this.id +'] input[name="'+ tableName +'_sort"]').val('');
39
+ this.updateTable({page: 1}, true);
40
+ };
41
+ });