datagrid 1.8.1 → 2.0.0.pre.alpha

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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -7
  3. data/{Readme.markdown → README.md} +46 -29
  4. data/app/assets/stylesheets/datagrid.css +145 -0
  5. data/app/views/datagrid/_enum_checkboxes.html.erb +5 -3
  6. data/app/views/datagrid/_form.html.erb +4 -5
  7. data/app/views/datagrid/_head.html.erb +26 -3
  8. data/app/views/datagrid/_range_filter.html.erb +5 -3
  9. data/app/views/datagrid/_row.html.erb +12 -1
  10. data/app/views/datagrid/_table.html.erb +4 -4
  11. data/datagrid.gemspec +8 -8
  12. data/lib/datagrid/active_model.rb +9 -17
  13. data/lib/datagrid/base.rb +39 -0
  14. data/lib/datagrid/column_names_attribute.rb +12 -12
  15. data/lib/datagrid/columns/column.rb +155 -133
  16. data/lib/datagrid/columns.rb +495 -282
  17. data/lib/datagrid/configuration.rb +23 -10
  18. data/lib/datagrid/core.rb +184 -150
  19. data/lib/datagrid/deprecated_object.rb +20 -0
  20. data/lib/datagrid/drivers/abstract_driver.rb +13 -25
  21. data/lib/datagrid/drivers/active_record.rb +24 -26
  22. data/lib/datagrid/drivers/array.rb +26 -17
  23. data/lib/datagrid/drivers/mongo_mapper.rb +15 -14
  24. data/lib/datagrid/drivers/mongoid.rb +16 -18
  25. data/lib/datagrid/drivers/sequel.rb +14 -19
  26. data/lib/datagrid/drivers.rb +2 -1
  27. data/lib/datagrid/engine.rb +11 -3
  28. data/lib/datagrid/filters/base_filter.rb +166 -142
  29. data/lib/datagrid/filters/boolean_filter.rb +19 -5
  30. data/lib/datagrid/filters/date_filter.rb +33 -35
  31. data/lib/datagrid/filters/date_time_filter.rb +24 -16
  32. data/lib/datagrid/filters/default_filter.rb +9 -3
  33. data/lib/datagrid/filters/dynamic_filter.rb +151 -105
  34. data/lib/datagrid/filters/enum_filter.rb +43 -19
  35. data/lib/datagrid/filters/extended_boolean_filter.rb +39 -27
  36. data/lib/datagrid/filters/float_filter.rb +16 -5
  37. data/lib/datagrid/filters/integer_filter.rb +21 -10
  38. data/lib/datagrid/filters/ranged_filter.rb +66 -45
  39. data/lib/datagrid/filters/select_options.rb +58 -49
  40. data/lib/datagrid/filters/string_filter.rb +9 -4
  41. data/lib/datagrid/filters.rb +234 -106
  42. data/lib/datagrid/form_builder.rb +116 -128
  43. data/lib/datagrid/generators/scaffold.rb +185 -0
  44. data/lib/datagrid/generators/views.rb +20 -0
  45. data/lib/datagrid/helper.rb +397 -22
  46. data/lib/datagrid/ordering.rb +81 -87
  47. data/lib/datagrid/rspec.rb +8 -12
  48. data/lib/datagrid/utils.rb +42 -38
  49. data/lib/datagrid/version.rb +3 -1
  50. data/lib/datagrid.rb +18 -28
  51. data/templates/base.rb.erb +33 -7
  52. data/templates/grid.rb.erb +1 -1
  53. metadata +18 -19
  54. data/app/assets/stylesheets/datagrid.sass +0 -134
  55. data/lib/datagrid/filters/composite_filters.rb +0 -49
  56. data/lib/datagrid/renderer.rb +0 -157
  57. data/lib/datagrid/scaffold.rb +0 -129
  58. data/lib/tasks/datagrid_tasks.rake +0 -15
  59. data/templates/controller.rb.erb +0 -6
  60. data/templates/index.html.erb +0 -5
@@ -1,9 +1,245 @@
1
- require "datagrid/engine"
1
+ # frozen_string_literal: true
2
+
2
3
  require "action_view"
3
4
 
4
5
  module Datagrid
6
+ # # Datagrid Frontend Guide
7
+ #
8
+ # ## Description
9
+ #
10
+ # The easiest way to start with Datagrid frontend is by using the generator:
11
+ #
12
+ # ``` sh
13
+ # rails generate datagrid:scaffold users
14
+ # ```
15
+ #
16
+ # This command builds the controller, view, route, and adds
17
+ # [built-in CSS](https://github.com/bogdan/datagrid/blob/master/app/assets/stylesheets/datagrid.sass).
18
+ #
19
+ # Datagrid includes helpers and a form builder for easy frontend generation.
20
+ # If you need a fully-featured custom GUI, create your templates manually with the help of the {Datagrid::Columns} API.
21
+ #
22
+ # ## Controller and Routing
23
+ #
24
+ # Grids usually implement the `index` action of a Rails REST resource. Here's an example:
25
+ #
26
+ # resources :models, only: [:index]
27
+ #
28
+ # Use the `GET` method in the form, and the controller becomes straightforward:
29
+ #
30
+ # class ModelsController < ApplicationController
31
+ # def index
32
+ # @grid = ModelsGrid.new(params[:my_report]) do |scope|
33
+ # scope.page(params[:page]) # See pagination section
34
+ # end
35
+ # end
36
+ # end
37
+ #
38
+ # To apply additional scoping conditions, such as visibility based on the current user:
39
+ #
40
+ # ModelsGrid.new(params[:my_report]) do |scope|
41
+ # scope.where(owner_id: current_user.id).page(params[:page])
42
+ # end
43
+ #
44
+ # To pass an object to a grid instance, define it as an accessible attribute:
45
+ #
46
+ # class ModelsGrid
47
+ # attr_accessor :current_user
48
+ # end
49
+ #
50
+ # Then pass it when initializing the grid:
51
+ #
52
+ # ModelsGrid.new(params[:models_grid].merge(current_user: current_user))
53
+ #
54
+ # ## Form Builder
55
+ #
56
+ # ### Basic Method
57
+ #
58
+ # Use the built-in partial:
59
+ #
60
+ # = datagrid_form_with model: @grid, url: report_path, other_form_for_option: value
61
+ #
62
+ # {#datagrid_form_with} supports the same options as Rails `form_with`.
63
+ #
64
+ # ### Advanced Method
65
+ #
66
+ # You can use Rails built-in tools to create a form. Additionally, Datagrid provides helpers to generate input/select elements for filters:
67
+ #
68
+ # ``` haml
69
+ # - form_with model: UserGrid.new, method: :get, url: users_path do |f|
70
+ # %div
71
+ # = f.datagrid_label :name
72
+ # = f.datagrid_filter :name # => <input name="grid[name]" type="text"/>
73
+ # %div
74
+ # = f.datagrid_label :category_id
75
+ # = f.datagrid_filter :category_id # => <select name="grid[category_id]">....</select>
76
+ # ```
77
+ #
78
+ # For more flexibility, use Rails default helpers:
79
+ #
80
+ # %div
81
+ # = f.label :name
82
+ # = f.text_field :name
83
+ #
84
+ # See the localization section of {Datagrid::Filters}.
85
+ #
86
+ # ## Datagrid Table
87
+ #
88
+ # Use the helper to display a report:
89
+ #
90
+ # %div== Total #{@grid.assets.total}
91
+ # = datagrid_table(@report)
92
+ # = will_paginate @report.assets
93
+ #
94
+ # Options:
95
+ #
96
+ # - `:html` - Attributes for the `<table>` tag.
97
+ # - `:order` - Set to `false` to disable ordering controls (default: `true`).
98
+ # - `:columns` - Specify an array of column names to display.
99
+ #
100
+ # ## Pagination
101
+ #
102
+ # Datagrid is abstracted from pagination but integrates seamlessly with tools like Kaminari, WillPaginate, or Pagy:
103
+ #
104
+ # # Kaminari
105
+ # @grid = MyGrid.new(params[:grid]) do |scope|
106
+ # scope.page(params[:page]).per(10)
107
+ # end
108
+ #
109
+ # # WillPaginate
110
+ # @grid = MyGrid.new(params[:grid]) do |scope|
111
+ # scope.page(params[:page]).per_page(10)
112
+ # end
113
+ #
114
+ # # Pagy
115
+ # @grid = MyGrid.new(params[:grid])
116
+ # @pagy, @records = pagy(@grid.assets)
117
+ #
118
+ # Render the paginated collection:
119
+ #
120
+ # # WillPaginate or Kaminari
121
+ # <%= datagrid_table(@grid, options) %>
122
+ # # Pagy
123
+ # <%= datagrid_table(@grid, @records, options) %>
124
+ #
125
+ # ## CSV Export
126
+ #
127
+ # Add CSV support to your controller:
128
+ #
129
+ # class UsersController < ApplicationController
130
+ # def index
131
+ # @grid = UsersGrid.new(params[:users_grid])
132
+ # respond_to do |f|
133
+ # f.html { @grid.scope { |scope| scope.page(params[:page]) } }
134
+ # f.csv do
135
+ # send_data @grid.to_csv, type: "text/csv", disposition: 'inline', filename: "grid-#{Time.now.to_s}.csv"
136
+ # end
137
+ # end
138
+ # end
139
+ # end
140
+ #
141
+ # Add a button in your interface:
142
+ #
143
+ # link_to "Get CSV", url_for(format: 'csv', users_grid: params[:users_grid])
144
+ #
145
+ # ## AJAX
146
+ #
147
+ # Datagrid supports asynchronous data loading. Add this to your controller:
148
+ #
149
+ # if request.xhr?
150
+ # render json: {table: view_context.datagrid_table(@grid)}
151
+ # end
152
+ #
153
+ # Modify the form for AJAX:
154
+ #
155
+ # = datagrid_form_with model: @grid, html: {class: 'js-datagrid-form'}
156
+ # .js-datagrid-table
157
+ # = datagrid_table @grid
158
+ # .js-pagination
159
+ # = paginate @grid.assets
160
+ # :javascript
161
+ # $('.js-datagrid-form').submit(function(event) {
162
+ # event.preventDefault();
163
+ # $.get($(this).attr("action"), $(this).serialize(), function (data) {
164
+ # $('.js-datagrid-table').html(data.table);
165
+ # });
166
+ # });
167
+ #
168
+ # ## Modifying Built-In Partials
169
+ #
170
+ # To customize Datagrid views:
171
+ #
172
+ # rake datagrid:copy_partials
173
+ #
174
+ # This creates files in `app/views/datagrid/`, which you can modify to suit your needs:
175
+ #
176
+ # app/views/datagrid/
177
+ # ├── _enum_checkboxes.html.erb # datagrid_filter for filter(name, :enum, checkboxes: true)
178
+ # ├── _form.html.erb # datagrid_form_with
179
+ # ├── _head.html.erb # datagrid_header
180
+ # ├── _range_filter.html.erb # datagrid_filter for filter(name, type, range: true)
181
+ # ├── _row.html.erb # datagrid_rows/datagrid_rows
182
+ # └── _table.html.erb # datagrid_table
183
+ #
184
+ # ## Custom Options
185
+ #
186
+ # You can add custom options to Datagrid columns and filters and implement their support on the frontend.
187
+ # For example, you might want to add a `description` option to a column that appears as a tooltip on mouseover.
188
+ #
189
+ # column(
190
+ # :aov, header: 'AOV',
191
+ # description: 'Average order value: sum of orders subtotal divided by their count'
192
+ # ) do |category|
193
+ # category.orders.sum(:subtotal) / category.orders.count
194
+ # end
195
+ #
196
+ # The `:description` option is not built into Datagrid, but you can implement it
197
+ # by adding the following to partial `app/views/datagrid/_header.html.erb`:
198
+ #
199
+ # <% if column.options[:description] %>
200
+ # <a data-toggle="tooltip" title="<%= column.options[:description] %>">
201
+ # <i class="icon-question-sign"></i>
202
+ # </a>
203
+ # <% end %>
204
+ #
205
+ # This modification allows the `:description` tooltip to work with your chosen UI and JavaScript library.
206
+ # The same technique can be applied to filters by calling `filter.options` in corresponding partials.
207
+ #
208
+ # ## Highlight Rows
209
+ #
210
+ # To add custom HTML classes to each row for styling, modify the `_row.html.erb` partial:
211
+ #
212
+ # ``` diff
213
+ # -<tr>
214
+ # +<tr class="<%= grid.respond_to?(:row_class) ? grid.row_class(asset) : "" %>">
215
+ # <% grid.html_columns(*options[:columns]).each do |column| %>
216
+ # <td class="<%= datagrid_column_classes(grid, column) %>">
217
+ # <%= datagrid_value(grid, column, asset) %>
218
+ # </td>
219
+ # <% end %>
220
+ # ```
221
+ #
222
+ # This allows you to define a custom `row_class` method in your grid class, like this:
223
+ #
224
+ # class IssuesGrid < ApplicationGrid
225
+ # scope { Issue }
226
+ #
227
+ # def row_class(issue)
228
+ # case issue.status
229
+ # when "fixed" then "green"
230
+ # when "rejected" then "red"
231
+ # else "blue"
232
+ # end
233
+ # end
234
+ # end
235
+ #
236
+ # ## Localization
237
+ #
238
+ # You can overwrite Datagrid’s custom localization keys at the application level.
239
+ # See the localization keys here:
240
+ #
241
+ # https://github.com/bogdan/datagrid/blob/master/lib/datagrid/locale/en.yml
5
242
  module Helper
6
-
7
243
  # @param grid [Datagrid] grid object
8
244
  # @param column [Datagrid::Columns::Column, String, Symbol] column name
9
245
  # @param model [Object] an object from grid scope
@@ -15,7 +251,9 @@ module Datagrid
15
251
  # <% end %>
16
252
  # </ul>
17
253
  def datagrid_value(grid, column, model)
18
- datagrid_renderer.format_value(grid, column, model)
254
+ column = grid.column_by_name(column) if column.is_a?(String) || column.is_a?(Symbol)
255
+
256
+ grid.html_value(column, self, model)
19
257
  end
20
258
 
21
259
  # @!visibility private
@@ -38,12 +276,20 @@ module Datagrid
38
276
  # Default: 'datagrid'.
39
277
  # @param grid [Datagrid] grid object
40
278
  # @param assets [Array] objects from grid scope
279
+ # @param [Hash{Symbol => Object}] options HTML attributes to be passed to `<table>` tag
41
280
  # @return [String] table tag HTML markup
42
281
  # @example
43
282
  # assets = grid.assets.page(params[:page])
44
283
  # datagrid_table(grid, assets, options)
45
284
  def datagrid_table(grid, assets = grid.assets, **options)
46
- datagrid_renderer.table(grid, assets, **options)
285
+ _render_partial(
286
+ "table", options[:partials],
287
+ {
288
+ grid: grid,
289
+ options: options,
290
+ assets: assets,
291
+ },
292
+ )
47
293
  end
48
294
 
49
295
  # Renders HTML table header for given grid instance using columns defined in it
@@ -58,11 +304,19 @@ module Datagrid
58
304
  # * <tt>:partials</tt> - Path for partials lookup.
59
305
  # Default: 'datagrid'.
60
306
  # @param grid [Datagrid] grid object
307
+ # @param [Object] opts (deprecated) pass keyword arguments instead
308
+ # @param [Hash] options
61
309
  # @return [String] HTML table header tag markup
62
- def datagrid_header(grid, options = {})
63
- datagrid_renderer.header(grid, options)
64
- end
310
+ def datagrid_header(grid, opts = :__unspecified__, **options)
311
+ unless opts == :__unspecified__
312
+ Datagrid::Utils.warn_once("datagrid_header now requires ** operator when passing options.")
313
+ options.reverse_merge!(opts)
314
+ end
315
+ options[:order] = true unless options.key?(:order)
65
316
 
317
+ _render_partial("head", options[:partials],
318
+ { grid: grid, options: options },)
319
+ end
66
320
 
67
321
  # Renders HTML table rows using given grid definition using columns defined in it.
68
322
  # Allows to provide a custom layout for each for in place with a block
@@ -75,6 +329,7 @@ module Datagrid
75
329
  # * <tt>:partials</tt> - Path for partials lookup.
76
330
  # Default: 'datagrid'.
77
331
  #
332
+ # @return [String]
78
333
  # @example
79
334
  # = datagrid_rows(grid) # Generic table rows Layout
80
335
  #
@@ -83,30 +338,73 @@ module Datagrid
83
338
  # %td= row.project_name
84
339
  # %td.project-status{class: row.status}= row.status
85
340
  def datagrid_rows(grid, assets = grid.assets, **options, &block)
86
- datagrid_renderer.rows(grid, assets, **options, &block)
341
+ safe_join(
342
+ assets.map do |asset|
343
+ datagrid_row(grid, asset, **options, &block)
344
+ end.to_a,
345
+ )
87
346
  end
88
347
 
89
- # Renders ordering controls for the given column name
348
+ # @return [String] renders ordering controls for the given column name
90
349
  #
91
350
  # Supported options:
92
351
  #
93
352
  # * <tt>:partials</tt> - Path for partials lookup.
94
353
  # Default: 'datagrid'.
95
354
  def datagrid_order_for(grid, column, options = {})
96
- datagrid_renderer.order_for(grid, column, options)
355
+ Datagrid::Utils.warn_once(<<~MSG)
356
+ datagrid_order_for is deprecated.
357
+ Put necessary code inline inside datagrid/head partial.
358
+ See built-in partial for example.
359
+ MSG
360
+ _render_partial("order_for", options[:partials],
361
+ { grid: grid, column: column },)
362
+ end
363
+
364
+ # Renders HTML for grid with all filters inputs and labels defined in it
365
+ #
366
+ # Supported options:
367
+ #
368
+ # * <tt>:partials</tt> - Path for form partial lookup.
369
+ # Default: 'datagrid' results in using `app/views/datagrid/` partials.
370
+ # Example: 'datagrid_admin' results in using `app/views/datagrid_admin` partials.
371
+ # * <tt>:model</tt> - Datagrid object to be rendedred.
372
+ # * All options supported by Rails <tt>form_with</tt> helper
373
+ # @param grid [Datagrid] grid object
374
+ # @param [Hash{Symbol => Object}] options
375
+ # @return [String] form HTML tag markup
376
+ def datagrid_form_with(**options)
377
+ raise ArgumentError, "datagrid_form_with block argument is invalid. Use form_with instead." if block_given?
378
+
379
+ grid = options[:model]
380
+ raise ArgumentError, "Grid has no available filters" if grid&.filters&.empty?
381
+
382
+ _render_partial("form", options[:partials], { grid: options[:model], options: options })
97
383
  end
98
384
 
99
- # Renders HTML for for grid with all filters inputs and lables defined in it
385
+ # Renders HTML for grid with all filters inputs and labels defined in it
100
386
  #
101
387
  # Supported options:
102
388
  #
103
389
  # * <tt>:partials</tt> - Path for form partial lookup.
104
390
  # Default: 'datagrid'.
105
- # * All options supported by Rails <tt>form_for</tt> helper
391
+ # * All options supported by Rails <tt>form_with</tt> helper
392
+ # @deprecated Use {#datagrid_form_with} instead.
106
393
  # @param grid [Datagrid] grid object
394
+ # @param [Hash] options
107
395
  # @return [String] form HTML tag markup
108
396
  def datagrid_form_for(grid, options = {})
109
- datagrid_renderer.form_for(grid, options)
397
+ Datagrid::Utils.warn_once("datagrid_form_for is deprecated if favor of datagrid_form_with.")
398
+ _render_partial(
399
+ "form", options[:partials],
400
+ grid: grid,
401
+ options: {
402
+ method: :get,
403
+ as: grid.param_name,
404
+ local: true,
405
+ **options,
406
+ },
407
+ )
110
408
  end
111
409
 
112
410
  # Provides access to datagrid columns data.
@@ -114,6 +412,7 @@ module Datagrid
114
412
  # @param grid [Datagrid] grid object
115
413
  # @param asset [Object] object from grid scope
116
414
  # @param block [Proc] block with Datagrid::Helper::HtmlRow as an argument returning a HTML markup as a String
415
+ # @param [Hash{Symbol => Object}] options
117
416
  # @return [Datagrid::Helper::HtmlRow, String] captured HTML markup if block given otherwise row object
118
417
  # @example
119
418
  # # Suppose that grid has first_name and last_name columns
@@ -130,29 +429,105 @@ module Datagrid
130
429
  # @example
131
430
  # <%= datagrid_row(grid, user, columns: [:first_name, :last_name, :actions]) %>
132
431
  def datagrid_row(grid, asset, **options, &block)
133
- datagrid_renderer.row(grid, asset, **options, &block)
432
+ Datagrid::Helper::HtmlRow.new(self, grid, asset, options).tap do |row|
433
+ return capture(row, &block) if block_given?
434
+ end
134
435
  end
135
436
 
136
437
  # Generates an ascending or descending order url for the given column
137
438
  # @param grid [Datagrid] grid object
138
439
  # @param column [Datagrid::Columns::Column, String, Symbol] column name
139
- # @param descending [Boolean] specifies order direction. Ascending if false, otherwise descending.
440
+ # @param descending [Boolean] order direction, descending if true, otherwise ascending.
140
441
  # @return [String] order layout HTML markup
141
442
  def datagrid_order_path(grid, column, descending)
142
- datagrid_renderer.order_path(grid, column, descending, request)
443
+ column = grid.column_by_name(column)
444
+ query = request&.query_parameters || {}
445
+ ActionDispatch::Http::URL.path_for(
446
+ path: request&.path || "/",
447
+ params: query.merge(grid.query_params(order: column.name, descending: descending)),
448
+ )
143
449
  end
144
450
 
451
+ # @!visibility private
452
+ def datagrid_column_classes(grid, column)
453
+ Datagrid::Utils.warn_once(<<~MSG)
454
+ datagrid_column_classes is deprecated. Assign necessary classes manually.
455
+ Correspond to default datagrid/rows partial for example.)
456
+ MSG
457
+ column = grid.column_by_name(column)
458
+ order_class = if grid.ordered_by?(column)
459
+ ["ordered", grid.descending ? "desc" : "asc"]
460
+ end
461
+ class_names(column.name, order_class, column.options[:class], column.tag_options[:class])
462
+ end
145
463
 
146
464
  protected
147
465
 
148
- def datagrid_renderer
149
- Renderer.for(self)
466
+ def _render_partial(partial_name, partials_path, locals = {})
467
+ render({
468
+ partial: File.join(partials_path || "datagrid", partial_name),
469
+ locals: locals,
470
+ })
150
471
  end
151
472
 
152
- def datagrid_column_classes(grid, column)
153
- order_class = grid.ordered_by?(column) ? ["ordered", grid.descending ? "desc" : "asc"] : nil
154
- [column.name, order_class, column.options[:class]].compact.join(" ")
473
+ # Represents a datagrid row that provides access to column values for the given asset
474
+ # @example
475
+ # row = datagrid_row(grid, user)
476
+ # row.class # => Datagrid::Helper::HtmlRow
477
+ # row.first_name # => "<strong>Bogdan</strong>"
478
+ # row.grid # => Grid object
479
+ # row.asset # => User object
480
+ # row.each do |value|
481
+ # puts value
482
+ # end
483
+ class HtmlRow
484
+ include Enumerable
485
+
486
+ attr_reader :grid, :asset, :options
487
+
488
+ # @!visibility private
489
+ def initialize(renderer, grid, asset, options)
490
+ @renderer = renderer
491
+ @grid = grid
492
+ @asset = asset
493
+ @options = options
494
+ end
495
+
496
+ # @return [Object] a column value for given column name
497
+ def get(column)
498
+ @renderer.datagrid_value(@grid, column, @asset)
499
+ end
500
+
501
+ # Iterates over all column values that are available in the row
502
+ # param block [Proc] column value iterator
503
+ def each(&block)
504
+ (@options[:columns] || @grid.html_columns).each do |column|
505
+ block.call(get(column))
506
+ end
507
+ end
508
+
509
+ # @return [String] HTML row format
510
+ def to_s
511
+ @renderer.send(:_render_partial, "row", options[:partials], {
512
+ grid: grid,
513
+ options: options,
514
+ asset: asset,
515
+ },)
516
+ end
517
+
518
+ protected
519
+
520
+ def method_missing(method, *args, &blk)
521
+ if (column = @grid.column_by_name(method))
522
+ get(column)
523
+ else
524
+ super
525
+ end
526
+ end
527
+
528
+ def respond_to_missing?(method, include_private = false)
529
+ !!@grid.column_by_name(method) || super
530
+ end
155
531
  end
156
532
  end
157
533
  end
158
-