widget_list 2.0.0 → 2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4a4a7f836ad410d0cedd3471ebd645479d96eba8933e82be357fbf0bbc140ea0
4
- data.tar.gz: 7fe3cdbe6e2aecae40f6192fcd4f9e1ac87a83ec8db70b3790bd67c46392d6ab
3
+ metadata.gz: 65e24eab11f0979fb9b5345f2abc750120245e1a3170c1c3cf4314650e01b42b
4
+ data.tar.gz: 49994c8e04a8fed60a7605cf896e78f79d6bdcabd5407a780ba8ceaeae9142fd
5
5
  SHA512:
6
- metadata.gz: f3658252db5d9e1e9b3665f7d38a2853a55427d5a73e57871e8e9649dc49b6eca71ccf5296f73a586baf8a6f011a25d4577630519fb3f7a1ad88ecc44d840221
7
- data.tar.gz: f59f51c7b0338638a0a27fa674517ccee1f5367cc24aeecbcf59cb577b4732d842da7fad47f2a1c072000b5c8d215de6a790fa430c7ccaa73af2a7ab28d96f34
6
+ metadata.gz: 4876ec92b2d1cf146f96d9b777575ff44ece4f3e707bed4098fef565025fc2a321246dbf200b084ef9ae847bd6822262ab39a68979213d9a2b3f8044f330399d
7
+ data.tar.gz: bbbeaf1232b1db0c48452af7dbd42b980e403ec3f3fb843aace3df2b9364af425256a945cc9dc3dea08e985853c2d4ec2d881e3d61b161b8236d2a6bc60d8cd9
data/README.md CHANGED
@@ -1,9 +1,15 @@
1
1
  # widget_list
2
2
 
3
- `widget_list` renders sortable, searchable Rails data grids with Ajax paging and CSV export. Version 2.0.0 has been exercised with **Rails 8.1.4**, **Ruby 3.4.11**, **Sequel 5.109.0**, **Ransack 5.0.2**, and SQLite. Rails 8.1 requires Ruby 3.2 or newer. The Rails 3 era guide remains in [docs/legacy-readme.md](docs/legacy-readme.md) for reference.
3
+ `widget_list` renders sortable, searchable Rails data grids with Ajax paging and CSV export. Version 2.0.0 has been exercised with **Rails 8.1.4**, **Ruby 3.4.11**, **Sequel 5.109.0**, **Ransack 5.0.2**, and SQLite. The restored administration console is in the 2.0.1 source on `main`; publish 2.0.1 before installing it from RubyGems. Rails 8.1 requires Ruby 3.2 or newer. The Rails 3 era guide remains in [docs/legacy-readme.md](docs/legacy-readme.md) for reference.
4
4
 
5
5
  The runnable [widget_list_example_rails8](https://github.com/davidrenne/widget_list_example_rails8/) contains both a Sequel SQL list and a Ransack/Active Record list. Its README and single commit diff show every caller change needed in a fresh Rails 8 app.
6
6
 
7
+ ## Video demo
8
+
9
+ [![Play the original widget_list demo](docs/widget-list-demo-thumbnail.png)](https://www.youtube.com/watch?v=A6mZa8Ge2Rk)
10
+
11
+ This video shows the original 1.x interface. For the Rails 8 integration, use the current example app below.
12
+
7
13
  ## Add it to a Rails 8 app
8
14
 
9
15
  `widget_list` uses jQuery and Sprockets assets. In your Gemfile:
@@ -14,12 +20,6 @@ gem 'jquery-rails'
14
20
  gem 'widget_list', '~> 2.0'
15
21
  ```
16
22
 
17
- For local development before publishing version 2.0.0, replace the last line with:
18
-
19
- ```ruby
20
- gem 'widget_list', path: '../widget_list'
21
- ```
22
-
23
23
  Run `bundle install`. Add `app/assets/config/manifest.js`:
24
24
 
25
25
  ```javascript
@@ -44,10 +44,28 @@ Load the assets in the application layout's `<head>`:
44
44
  ```erb
45
45
  <%= csrf_meta_tags %>
46
46
  <%= stylesheet_link_tag 'application', 'widget_list', 'widgets' %>
47
- <%= javascript_include_tag 'application', defer: true %>
47
+ <%= javascript_include_tag 'application' %>
48
48
  ```
49
49
 
50
- The gem resolves its image URLs through Sprockets when compiling CSS. No manual image copy or static middleware is needed. An app using the default Rails 8 Propshaft pipeline should replace `propshaft` with `sprockets-rails` for this integration. Run `bin/rails assets:precompile` to check production assets.
50
+ Load this script in the layout's `<head>` without `defer`: the administration wizard's inline script needs jQuery as it is parsed. The gem resolves its image URLs through Sprockets when compiling CSS. No manual image copy or static middleware is needed. An app using the default Rails 8 Propshaft pipeline should replace `propshaft` with `sprockets-rails` for this integration. Run `bin/rails assets:precompile` to check production assets.
51
+
52
+ ## Themes
53
+
54
+ `widget_list` lets a theme provide default colors, type, borders, and button styles through `WidgetListThemeHelper::ThemeDefaults`. Settings passed to an individual `WidgetList.go!` call override theme defaults. A theme can also ship a stylesheet for finer control.
55
+
56
+ | Theme | Appearance | Compatibility |
57
+ | --- | --- | --- |
58
+ | [AI theme](https://github.com/davidrenne/widget_list_theme_ai) | Midnight surfaces, cyan and violet accents, modern grid controls | Built for widget_list 2.x and Rails 8 |
59
+ | [Cruddy](https://github.com/davidrenne/widget_list_theme_cruddy) | Warm, textured classic style | Legacy 1.x theme; requires migration for 2.x |
60
+ | [Blue Sky Basin](https://github.com/davidrenne/widget_list_theme_blue_sky_basin) | Blue mountain inspired palette | Legacy 1.x theme; requires migration for 2.x |
61
+
62
+ The AI theme is under development. To try it in your own Rails app, check out `widget_list_theme_ai` next to the app and add this line to its Gemfile:
63
+
64
+ ```ruby
65
+ gem 'widget_list_theme_ai', path: '../widget_list_theme_ai'
66
+ ```
67
+
68
+ Then run `bundle install`, add `//= link widget_list_theme_ai.css` to `app/assets/config/manifest.js`, and load `widget_list_theme_ai` after `widget_list` and `widgets` in the layout's `stylesheet_link_tag`. The theme uses a local system font stack and makes no external font request. See the [theme README on the preview branch](https://github.com/davidrenne/widget_list_theme_ai/tree/codex/ai-theme) for the complete setup and the example's `codex/ai-theme-example` branch for a working caller. That branch also uses the local core checkout for a pagination fix pending release.
51
69
 
52
70
  ## Choose a database source
53
71
 
@@ -118,10 +136,11 @@ A standard GET URL such as `/items?q[name_cont]=Apple` applies the filter. The g
118
136
 
119
137
  ## Try the example
120
138
 
121
- With `widget_list` and `widget_list_example_rails8` checked out as sibling directories:
139
+ The example installs the published 2.0.0 gem from RubyGems. Clone and run it anywhere:
122
140
 
123
141
  ```sh
124
- cd ../widget_list_example_rails8
142
+ git clone https://github.com/davidrenne/widget_list_example_rails8.git
143
+ cd widget_list_example_rails8
125
144
  bundle install
126
145
  bin/rails db:prepare
127
146
  bin/rails db:seed
@@ -129,16 +148,75 @@ bin/rails test
129
148
  bin/rails server
130
149
  ```
131
150
 
132
- Open `http://127.0.0.1:3000/` for Sequel or `/ransack` for Active Record. Search SKU `1001`, change pages, export CSV, or open the filter arrow and set **Name contains Apple**. The example uses a relative Bundler `path:` dependency, so edits to this gem are picked up after restarting the server.
151
+ Open `http://127.0.0.1:3000/` for Sequel or `/ransack` for Active Record. Search SKU `1001`, change pages, export CSV, or open the filter arrow and set **Name contains Apple**.
152
+
153
+ ## Administration console
154
+
155
+ The original administration wizard is restored in the [Rails 8 migration example](https://github.com/davidrenne/widget_list_example). Open `/administration` there to choose an Active Record model and fields, configure sorting, search, row buttons and export, preview the list, then generate starter controller code. Hidden fields, field functions, grouping fields, and the drill-down value column use dropdowns populated from the selected model's table columns. The drill-down display column remains editable for custom expressions. The example routes the wizard only in development and test. It uses the sibling `widget_list` checkout, so the console fixes require the 2.0.1 source until that version is published.
156
+
157
+ To add the wizard to your own app, first complete the asset setup above. Then add a GET/POST route, a controller action, and a view:
158
+
159
+ ```ruby
160
+ # config/routes.rb
161
+ if Rails.env.development? || Rails.env.test?
162
+ match '/administration', to: 'widget_list_examples#administration',
163
+ via: [:get, :post], as: :administration
164
+ end
165
+
166
+ # app/controllers/widget_list_examples_controller.rb
167
+ class WidgetListExamplesController < ApplicationController
168
+ def administration
169
+ @output = WidgetList.go!
170
+ render json: JSON.parse(@output) if params.key?(:ajax)
171
+ end
172
+ end
173
+ ```
174
+
175
+ ```erb
176
+ <%# app/views/widget_list_examples/administration.html.erb %>
177
+ <div style="margin: 50px"><%= raw @output %></div>
178
+
179
+ <%# Link from a page in development %>
180
+ <%= link_to 'Administration Console', administration_path if Rails.env.development? %>
181
+ ```
182
+
183
+ If `config/widget-list.yml` points `primary` at Sequel and `secondary` at Active Record, leave **Primary Connection?** unchecked when selecting a model. The wizard now defaults to that choice. Ransack requires the model's `ransackable_attributes` allowlist shown above. The generated action needs a GET/POST route and a view that renders `@output`, just like a hand-written list. Review the generated fields, links and controller code before using it. The wizard writes draft configuration to `config/widget-list-administration.json` and saved configurations to `config/widget-list-administration-all.json`; the example ignores both files in Git. Keep the route accessible only to trusted developers: the preview evaluates generated Ruby and the wizard writes files.
184
+
185
+ These are the original screenshots from the Rails 3 era. The Rails 8 wizard uses the same steps, while its generated code and preview have been updated for Rails 8 and Ransack 5.
186
+
187
+ | Start with a model | Configure search and export |
188
+ | --- | --- |
189
+ | ![Original administration start screen](docs/screenshots/admin1.jpg) | ![Original search and export settings](docs/screenshots/admin2.jpg) |
190
+
191
+ | Select fields | Configure row buttons |
192
+ | --- | --- |
193
+ | ![Original field selection](docs/screenshots/admin3.jpg) | ![Original row buttons](docs/screenshots/admin4.jpg) |
194
+
195
+ | Preview the list | Generate controller code |
196
+ | --- | --- |
197
+ | ![Original list preview](docs/screenshots/admin5.png) | ![Original generated code](docs/screenshots/admin6.png) |
198
+
199
+ <details>
200
+ <summary>More original widget_list screenshots</summary>
201
+
202
+ ![Original widget_list table](docs/screenshots/main.png)
203
+ ![Original filtered list](docs/screenshots/filtered.png)
204
+ ![Original search](docs/screenshots/search.png)
205
+ ![Original Ransack search](docs/screenshots/ransack1.png)
206
+ ![Original Ransack filters](docs/screenshots/ransack2.png)
207
+ ![Original combined Ransack filters](docs/screenshots/ransack3.png)
208
+ ![Original Blue Sky Basin theme](docs/screenshots/theme_blue_sky_basin.png)
209
+
210
+ </details>
133
211
 
134
212
  ## Release
135
213
 
136
- The RubyGems page takes its short summary and longer description from `gem.summary` and `gem.description` in [widget_list.gemspec](widget_list.gemspec). Edit those fields before building and publishing a new version; changing GitHub or the RubyGems web page does not update an already published gem version.
214
+ Version 2.0.0 is published; the administration console fixes are prepared as 2.0.1 on `main` and have not been published. The RubyGems page takes its short summary and longer description from `gem.summary` and `gem.description` in [widget_list.gemspec](widget_list.gemspec). To change them in a future release, bump `WidgetList::VERSION` in `lib/widget_list/version.rb` and publish a new version; published versions cannot be overwritten.
137
215
 
138
- `./checkin_gem.sh` validates and builds the gem, and runs the sibling Rails 8 example tests when present. Review and commit your changes on `main`. Sign in to the command-line publisher with `gem signin` if needed, using an API key with the `push_rubygem` scope. A RubyGems website session does not sign in the `gem` command. Then run `./publish_gem.sh`; it checks that the tree is clean, builds the package, and pushes it to RubyGems.org. RubyGems may prompt for MFA in the terminal or open a browser for WebAuthn. No credential is stored in these scripts. Publishing is a separate, intentional step from committing.
216
+ `./checkin_gem.sh` validates and builds the gem, and runs the sibling local-source Rails 8 example tests when present. Review and commit your changes on `main`. Sign in to the command-line publisher with `gem signin` if needed, using an API key with the `push_rubygem` scope. A RubyGems website session does not sign in the `gem` command. Then run `./publish_gem.sh`; it checks that the tree is clean, builds the package, and pushes it to RubyGems.org. RubyGems may prompt for MFA in the terminal or open a browser for WebAuthn. No credential is stored in these scripts. Publishing is a separate, intentional step from committing.
139
217
 
140
218
  See [RubyGems publishing](https://guides.rubygems.org/publishing/) and [MFA setup](https://guides.rubygems.org/setting-up-otp-mfa/) for account setup.
141
219
 
142
220
  ## Upgrade notes
143
221
 
144
- The migration replaced Rails 3 callbacks and asset middleware, Ruby's removed `URI.decode`, unsafe SQL `eval`, and process-wide request globals. Ransack 5 requires explicit `ransackable_attributes` in each searchable model. The original 2013 administration builder and non-SQLite adapters are still historical code paths; the Rails 8 example verifies the list, Ajax search, pagination, advanced Ransack filtering, CSV export, and asset compilation.
222
+ The migration replaced Rails 3 callbacks and asset middleware, Ruby's removed `URI.decode`, unsafe SQL `eval`, and process-wide request globals. Ransack 5 requires explicit `ransackable_attributes` in each searchable model. The Rails 8 examples verify the list, Ajax search, pagination, advanced Ransack filtering, CSV export, and asset compilation. The migration example also exercises the administration wizard, its preview, and generated code. Non-SQLite adapters remain unverified.
@@ -3,7 +3,7 @@
3
3
  <!--TEXT_DESC-->:&#160;<input type="text" class="<!--SUBJECT-->_<!--TEXT_KEY-->" style="width:170px" onblur="jQuery(this).attr('value',jQuery(this).val().trim());<!--ONBLUR1-->" name="<!--SUBJECT-->[<!--TEXT_KEY-->][]" id="<!--SUBJECT-->[<!--TEXT_KEY-->][]" value="<!--BUTTON_TEXT-->"/><%= raw WidgetList::Widgets::widget_button('?', {'onclick' => "alert('<!--TEXT_HELP-->')", 'innerClass' => "default <!--SUBJECT-->_<!--TEXT_KEY-->_button" }, true ) %>
4
4
  </td>
5
5
  <td>
6
- <!--URL_DESC-->:&#160;<input type="text" class="<!--SUBJECT-->_<!--URL_KEY-->" style="width:170px" onblur="jQuery(this).attr('value',jQuery(this).val().trim());<!--ONBLUR2-->" name="<!--SUBJECT-->[<!--URL_KEY-->][]" id="<!--SUBJECT-->[<!--URL_KEY-->][]" value="<!--BUTTON_URL-->"/><%= raw WidgetList::Widgets::widget_button('?', {'onclick' => "alert('<!--2_HELP->')", 'innerClass' => "default <!--SUBJECT-->_<!--URL_KEY-->_button" }, true ) %>
6
+ <!--URL_DESC-->:&#160;<% if local_assigns[:middle_column_select] %><select class="<!--SUBJECT-->_<!--URL_KEY--> widget-list-column-select" style="width:170px" name="<!--SUBJECT-->[<!--URL_KEY-->][]" data-selected="<!--BUTTON_URL-->" onchange="<!--ONBLUR2-->"><option value="">Select a column</option></select><% else %><input type="text" class="<!--SUBJECT-->_<!--URL_KEY-->" style="width:170px" onblur="jQuery(this).attr('value',jQuery(this).val().trim());<!--ONBLUR2-->" name="<!--SUBJECT-->[<!--URL_KEY-->][]" id="<!--SUBJECT-->[<!--URL_KEY-->][]" value="<!--BUTTON_URL-->"/><% end %><%= raw WidgetList::Widgets::widget_button('?', {'onclick' => "alert('<!--2_HELP->')", 'innerClass' => "default <!--SUBJECT-->_<!--URL_KEY-->_button" }, true ) %>
7
7
  </td>
8
8
  <td>
9
9
  <!--COLOR_DESC-->:&#160;<input type="text" class="<!--SUBJECT-->_<!--COLOR_KEY-->" style="width:170px" onblur="jQuery(this).attr('value',jQuery(this).val().trim());<!--ONBLUR3-->" name="<!--SUBJECT-->[<!--COLOR_KEY-->][]" id="<!--SUBJECT-->[<!--COLOR_KEY-->][]" value="<!--BUTTON_CLASS-->"/><%= raw WidgetList::Widgets::widget_button('?', {'onclick' => "alert('<!--COLOR_HELP-->')", 'innerClass' => "default <!--SUBJECT-->_<!--COLOR_KEY-->_button" }, true ) %>
@@ -11,4 +11,4 @@
11
11
  <td>
12
12
  <!--REMOVE_FIELD_BUTTON-->
13
13
  </td>
14
- </tr>
14
+ </tr>
@@ -1,6 +1,6 @@
1
1
  <tr style="<!--TR_STYLE-->">
2
2
  <td style="<!--TR1_STYLE-->">
3
- <!--FIELD-->:&#160;<input type="text" style="width:350px" class="<!--SUBJECT-->_keys" onblur="jQuery(this).attr('value',jQuery(this).val().trim());<!--ONBLUR1-->" name="<!--SUBJECT-->[key][]" id="<!--SUBJECT-->[key][]" value="<!--FIELD_VALUE-->"/>
3
+ <!--FIELD-->:&#160;<% if local_assigns[:key_select] %><select style="width:350px" class="<!--SUBJECT-->_keys widget-list-column-select" name="<!--SUBJECT-->[key][]" data-selected="<!--FIELD_VALUE-->" onchange="<!--ONBLUR1-->"><option value="">Select a column</option></select><% else %><input type="text" style="width:350px" class="<!--SUBJECT-->_keys" onblur="jQuery(this).attr('value',jQuery(this).val().trim());<!--ONBLUR1-->" name="<!--SUBJECT-->[key][]" id="<!--SUBJECT-->[key][]" value="<!--FIELD_VALUE-->"/><% end %>
4
4
  </td>
5
5
  <td>
6
6
  <!--DESC-->:&#160;<input <!--DISABLED--> type="text" style="width:350px" class="<!--SUBJECT-->_descriptions" onblur="jQuery(this).attr('value',jQuery(this).val().trim());<!--ONBLUR2-->" name="<!--SUBJECT-->[description][]" id="<!--SUBJECT-->[description][]" value="<!--FIELD_DESC-->"/>
@@ -9,4 +9,4 @@
9
9
  <!--REMOVE_FIELD_BUTTON-->
10
10
  </td>
11
11
  </tr>
12
- <!--EXTRA-->
12
+ <!--EXTRA-->
@@ -39,6 +39,10 @@
39
39
  var all = '.init,.start,.fields,.rows,.search,.footer,.misc_submit';
40
40
  var all_btns = '#start,#fields,#rows,#search,#footer,#misc_submit';
41
41
  jQuery(document).ready(function () {
42
+ PopulateColumnSelects();
43
+ jQuery(document).on('change', 'select.widget-list-column-select', function () {
44
+ jQuery(this).attr('data-selected', this.value);
45
+ });
42
46
  if (jQuery('#view').val() != '') {
43
47
  jQuery(all_btns).removeClass('disabled');
44
48
  }
@@ -49,6 +53,9 @@
49
53
  }
50
54
 
51
55
  window.onbeforeunload = function (evt) {
56
+ if (isSubmitting) {
57
+ return;
58
+ }
52
59
  var message = 'Are you sure you want to abandon this widget_list implementation? Doing so will lose all your work?';
53
60
  if (typeof evt == 'undefined') {
54
61
  evt = window.event;
@@ -57,9 +64,7 @@
57
64
  if (evt) {
58
65
  evt.returnValue = message;
59
66
  }
60
- if (!isSubmitting) {
61
- return message;
62
- }
67
+ return message;
63
68
  }
64
69
 
65
70
  });
@@ -82,33 +87,29 @@
82
87
  var by_pass = false;
83
88
  }
84
89
  if (by_pass || VerifyOK(func_name)) {
85
- jQuery('.search_dependent').hide(0,function() {
86
- jQuery(all).hide(0,function() {
87
- jQuery(which_class).fadeIn(0,function() {
88
- if (func_name == 'ShowSearch') {
89
- ShowSearchOptions(function() {
90
- ShowGroupingOptions();
91
- ShowExportOptions();
92
-
93
- });
94
- }
95
- if (func_name == 'ShowFields') {
96
- ShowHiddenOptions();
97
- ShowFunctionOptions();
98
- }
99
- if (func_name == 'ShowSubmit') {
100
- ShowCheckboxOptions();
101
- }
102
- if (func_name == 'ShowRows') {
103
- ShowButtonOptions();
104
- ShowDrillDownOptions();
105
- }
106
- if (func_name == 'ShowFooter') {
107
- ShowFooterOptions();
108
- }
109
- });
90
+ jQuery('.search_dependent').hide();
91
+ jQuery(all).hide();
92
+ jQuery(which_class).show();
93
+ if (func_name == 'ShowSearch') {
94
+ ShowSearchOptions(function() {
95
+ ShowGroupingOptions();
96
+ ShowExportOptions();
110
97
  });
111
- });
98
+ }
99
+ if (func_name == 'ShowFields') {
100
+ ShowHiddenOptions();
101
+ ShowFunctionOptions();
102
+ }
103
+ if (func_name == 'ShowSubmit') {
104
+ ShowCheckboxOptions();
105
+ }
106
+ if (func_name == 'ShowRows') {
107
+ ShowButtonOptions();
108
+ ShowDrillDownOptions();
109
+ }
110
+ if (func_name == 'ShowFooter') {
111
+ ShowFooterOptions();
112
+ }
112
113
 
113
114
  if (!by_pass) {
114
115
  SaveChanges();
@@ -141,31 +142,60 @@
141
142
  ClickTab('.misc_submit',arguments.callee.name);
142
143
  }
143
144
 
145
+ function SetColumnValue(control, value) {
146
+ var field = jQuery(control);
147
+ if (field.is('select') && !field.find('option').filter(function () { return this.value == value; }).length) {
148
+ field.append(jQuery('<option>').val(value).text(value));
149
+ }
150
+ field.val(value);
151
+ field.attr(field.is('select') ? 'data-selected' : 'value', value);
152
+ }
153
+
154
+ function PopulateColumnSelects() {
155
+ var columns = jQuery('#checkboxField option').map(function () { return this.value; }).get().filter(Boolean);
156
+ if (!columns.length) return;
157
+
158
+ jQuery('select.widget-list-column-select').each(function () {
159
+ var field = jQuery(this);
160
+ var selected = field.val() || field.attr('data-selected') || '';
161
+ if (['column_name', 'col_name', 'field_name'].indexOf(selected) !== -1) selected = '';
162
+
163
+ field.empty().append(jQuery('<option>').val('').text(field.hasClass('group_by_keys') ? 'All items / select a column' : 'Select a column'));
164
+ columns.forEach(function (column) {
165
+ field.append(jQuery('<option>').val(column).text(column));
166
+ });
167
+ if (selected && columns.indexOf(selected) === -1) {
168
+ field.append(jQuery('<option>').val(selected).text(selected + ' (saved value)'));
169
+ }
170
+ SetColumnValue(field, selected);
171
+ });
172
+ }
173
+
144
174
  function ReplaceColumnsToLinked(obj) {
175
+ var isPassColumn = jQuery(obj).hasClass('drill_downs_data_to_pass_from_view');
176
+ var displayColumn = jQuery(obj).closest('tr').find('.drill_downs_column_to_show').val() || '';
177
+ if (isPassColumn && displayColumn.endsWith('_linked')) return;
145
178
  found = false;
146
- var className = obj.className;
147
179
  jQuery(obj).css('background-color','white');
148
180
  jQuery('.fields_keys, .fields_hidden_keys, .fields_function_keys').each(
149
181
  function(key, value) {
150
182
  if(jQuery(value).val() == jQuery(obj).val().trim() && jQuery(obj).val().search(/_linked/i) == -1) {
151
183
  found = true;
152
184
  var id_original = jQuery(obj).val().trim();
153
- jQuery(value).val(jQuery(obj).val().trim() + '_linked');
154
- jQuery(obj).val(jQuery(obj).val().trim() + '_linked');
155
- jQuery(obj).attr('value',jQuery(this).val().trim());
156
- if (className == 'drill_downs_data_to_pass_from_view') {
185
+ SetColumnValue(value, jQuery(obj).val().trim() + '_linked');
186
+ SetColumnValue(obj, isPassColumn ? id_original : id_original + '_linked');
187
+ if (isPassColumn) {
157
188
  jQuery('.drill_downs_column_to_show[value="col_name"]').each(
158
189
  function(key1, value1) {
159
- jQuery(value1).val(jQuery(obj).val().trim());
160
- jQuery(value1).attr('value',jQuery(value1).val().trim());
190
+ jQuery(value1).val(id_original + '_linked');
191
+ jQuery(value1).attr('value',id_original + '_linked');
161
192
  return false;
162
193
  }
163
194
  )
164
195
  } else {
165
- jQuery('.drill_downs_data_to_pass_from_view[value="col_name"]').each(
196
+ jQuery('.drill_downs_data_to_pass_from_view').filter(function () { return !this.value || this.value == 'col_name'; }).each(
166
197
  function(key1, value1) {
167
- jQuery(value1).val(jQuery(obj).val().trim());
168
- jQuery(value1).attr('value',jQuery(value1).val().trim());
198
+ SetColumnValue(value1, id_original);
169
199
  return false;
170
200
  }
171
201
  )
@@ -182,10 +212,9 @@
182
212
 
183
213
  jQuery('#showHidden').attr('checked',true);
184
214
  AddHiddenField();
185
- jQuery('.fields_hidden_keys[value=""]').each(
215
+ jQuery('.fields_hidden_keys').filter(function () { return !this.value; }).each(
186
216
  function(key1, value1) {
187
- jQuery(value1).val(id_original);
188
- jQuery(value1).attr('value',id_original);
217
+ SetColumnValue(value1, id_original);
189
218
  return false;
190
219
  }
191
220
  );
@@ -200,6 +229,7 @@
200
229
  }
201
230
 
202
231
  function InvalidField(obj) {
232
+ if (!jQuery(obj).val()) return;
203
233
  found = false;
204
234
  jQuery(obj).css('background-color','white');
205
235
  jQuery('.fields_keys, .fields_hidden_keys, .fields_function_keys').each(
@@ -330,6 +360,7 @@
330
360
 
331
361
  function AddGroupBy() {
332
362
  jQuery('#grouping_table').html(jQuery('#grouping_table').html() + jQuery('#default_grouping_template').html());
363
+ PopulateColumnSelects();
333
364
  }
334
365
 
335
366
  function AddButton() {
@@ -339,6 +370,7 @@
339
370
 
340
371
  function AddDrillDown() {
341
372
  jQuery('#drill_down_table').html(jQuery('#drill_down_table').html() + jQuery('#default_drill_down_template').html());
373
+ PopulateColumnSelects();
342
374
  }
343
375
 
344
376
  function AddFooterButton() {
@@ -348,10 +380,12 @@
348
380
 
349
381
  function AddHiddenField() {
350
382
  jQuery('#hidden_field_table').html(jQuery('#hidden_field_table').html() + jQuery('#default_hidden_field_template').html());
383
+ PopulateColumnSelects();
351
384
  }
352
385
 
353
386
  function AddFieldFunction() {
354
387
  jQuery('#field_function_table').html(jQuery('#field_function_table').html() + jQuery('#default_field_function_template').html());
388
+ PopulateColumnSelects();
355
389
  }
356
390
 
357
391
  function FakeCheck(obj) {
@@ -360,11 +394,11 @@
360
394
 
361
395
  function RemoveField(obj) {
362
396
  var isHiddenRemoval = false;
363
- if (jQuery(obj).parent().parent().find('input.fields_hidden_keys').length > 0) {
397
+ if (jQuery(obj).parent().parent().find('.fields_hidden_keys').length > 0) {
364
398
  isHiddenRemoval = true;
365
399
  }
366
400
  var isFunctionRemoval = false;
367
- if (jQuery(obj).parent().parent().find('input.fields_function_keys').length > 0) {
401
+ if (jQuery(obj).parent().parent().find('.fields_function_keys').length > 0) {
368
402
  isFunctionRemoval = true;
369
403
  }
370
404
 
@@ -406,10 +440,9 @@
406
440
  }
407
441
  AddHiddenField();
408
442
  jQuery('.hidden_fields_dependent').show();
409
- jQuery('.fields_hidden_keys[value=""]').each(
443
+ jQuery('.fields_hidden_keys').filter(function () { return !this.value; }).each(
410
444
  function(key1, value1) {
411
- jQuery(value1).val(field_name);
412
- jQuery(value1).attr('value',field_name);
445
+ SetColumnValue(value1, field_name);
413
446
  return false;
414
447
  }
415
448
  );
@@ -439,17 +472,16 @@
439
472
  if (jQuery('#fieldFunctionOn').not(':checked')) {
440
473
  jQuery('#fieldFunctionOn').attr('checked',true);
441
474
  }
442
- var objExists = '.fields_function_keys[value="' + field_name + '"]';
443
- if (jQuery(objExists).length > 0) {
475
+ var objExists = jQuery('.fields_function_keys').filter(function () { return this.value == field_name; });
476
+ if (objExists.length > 0) {
444
477
  alert('Field Function for ' + field_name + ' already exists, please modify');
445
478
  jQuery(objExists).css('background-color','pink');
446
479
  } else {
447
480
  AddFieldFunction();
448
481
  jQuery('.function_dependent').show();
449
- jQuery('.fields_function_keys[value=""]').each(
482
+ jQuery('.fields_function_keys').filter(function () { return !this.value; }).each(
450
483
  function(key1, value1) {
451
- jQuery(value1).val(field_name);
452
- jQuery(value1).attr('value',field_name);
484
+ SetColumnValue(value1, field_name);
453
485
  return false;
454
486
  }
455
487
  );
@@ -466,7 +498,7 @@
466
498
 
467
499
  function ShowField(obj) {
468
500
 
469
- var field_name = jQuery(obj).parent().parent().find('input.fields_hidden_keys').val();
501
+ var field_name = jQuery(obj).parent().parent().find('.fields_hidden_keys').val();
470
502
  RemoveField(obj);
471
503
  AddField();
472
504
  jQuery('.fields_keys[value=""]').each(
@@ -504,6 +536,7 @@
504
536
  jQuery('#checkboxField').html(response['checked_fields']);
505
537
  jQuery('#footer_table').html(response['footer_buttons']);
506
538
  jQuery('#grouping_table').html(response['group_by']);
539
+ PopulateColumnSelects();
507
540
  SaveChanges();
508
541
  if (jQuery(obj).val() != '') {
509
542
  jQuery(all_btns).removeClass('disabled');
@@ -521,7 +554,7 @@
521
554
  if (jQuery(obj).val() != '' && jQuery('#showExport').is(':checked')) {
522
555
  jQuery('#exportButtonTitle').val('Export All ' + jQuery(obj).val() + 's');
523
556
  }
524
- }, "json").error(function(response) {
557
+ }, "json").fail(function(response) {
525
558
  alert('An error occurred instantiating this model. Check the response for errors.');
526
559
  });
527
560
  }
@@ -568,6 +601,7 @@
568
601
 
569
602
  </script>
570
603
  <form action="<!--POST_URL-->" method="post" id="widget_list_administration">
604
+ <input type="hidden" name="authenticity_token" value="<!--AUTH_TOKEN-->"/>
571
605
  <h1 style="font-size:24;"><!--TITLE--></h1><div class="horizontal_rule"></div>
572
606
  <h3>Get started quickly by selecting the features you need for this list</h3>
573
607
  <table>
@@ -1098,4 +1132,4 @@
1098
1132
  </tr>
1099
1133
 
1100
1134
  </table>
1101
- </form>
1135
+ </form>
data/checkin_gem.sh CHANGED
@@ -5,7 +5,7 @@ cd "$(dirname "$0")"
5
5
  version=$(ruby -Ilib -rwidget_list/version -e 'print WidgetList::VERSION')
6
6
  gem build widget_list.gemspec --output "widget_list-${version}.gem"
7
7
 
8
- example_dir="../widget_list_example_rails8"
8
+ example_dir="../widget_list_example"
9
9
  if [[ -f "${example_dir}/bin/rails" ]]; then
10
10
  (cd "$example_dir" && bundle install && bin/rails test)
11
11
  fi
@@ -65,31 +65,31 @@ In rails you have will_paginate and other ones like it using the ActiveRecord ap
65
65
 
66
66
  ### Main Example Loaded:
67
67
 
68
- ![](http://davidrenne.com/github/widget_list/main.png)
68
+ ![](screenshots/main.png)
69
69
 
70
70
  ###Filter Drop Downs:
71
71
 
72
- ![](http://davidrenne.com/github/widget_list/filtered.png)
72
+ ![](screenshots/filtered.png)
73
73
 
74
74
  ###Searching a row (with wild card search):
75
75
 
76
- ![](http://davidrenne.com/github/widget_list/search.png)
76
+ ![](screenshots/search.png)
77
77
 
78
78
  ###Searching "name=asdf_18" (With ActiveRecord and Ransack hook):
79
79
 
80
- ![](http://davidrenne.com/github/widget_list/ransack1.png)
80
+ ![](screenshots/ransack1.png)
81
81
 
82
82
  ###Searching "name=asdf_18" and "sku<9000"(With ActiveRecord and Ransack hook):
83
83
 
84
- ![](http://davidrenne.com/github/widget_list/ransack2.png)
84
+ ![](screenshots/ransack2.png)
85
85
 
86
86
  ###Searching "name=asdf_18" and "sku < 9000" and "price > 67" (With ActiveRecord and Ransack hook):
87
87
 
88
- ![](http://davidrenne.com/github/widget_list/ransack3.png)
88
+ ![](screenshots/ransack3.png)
89
89
 
90
90
  ###Theme plugins/gem support ([The Blue Sky Basin Theme][2]):
91
91
 
92
- ![](http://davidrenne.com/github/widget_list/theme_blue_sky_basin.png)
92
+ ![](screenshots/theme_blue_sky_basin.png)
93
93
 
94
94
  ****
95
95
 
@@ -114,21 +114,21 @@ And then add this to a clean controller. If you have code below, please return
114
114
 
115
115
  Next you will see this interface which will build some starter code for you:
116
116
 
117
- ![](http://davidrenne.com/github/widget_list/admin1.jpg)
117
+ ![](screenshots/admin1.jpg)
118
118
 
119
- ![](http://davidrenne.com/github/widget_list/admin2.jpg)
119
+ ![](screenshots/admin2.jpg)
120
120
 
121
- ![](http://davidrenne.com/github/widget_list/admin3.jpg)
121
+ ![](screenshots/admin3.jpg)
122
122
 
123
- ![](http://davidrenne.com/github/widget_list/admin4.jpg)
123
+ ![](screenshots/admin4.jpg)
124
124
 
125
125
  ###As you can see below, there is a preview iframe of what you are configuring:
126
126
 
127
- ![](http://davidrenne.com/github/widget_list/admin5.png)
127
+ ![](screenshots/admin5.png)
128
128
 
129
129
  ###And finally, the code is output to get you started
130
130
 
131
- ![](http://davidrenne.com/github/widget_list/admin6.png)
131
+ ![](screenshots/admin6.png)
132
132
 
133
133
  ****
134
134
 
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -8,6 +8,7 @@ module WidgetList
8
8
  RequestContext.session = session.to_h.deep_dup
9
9
  RequestContext.server = request.env
10
10
  RequestContext.request = request.filtered_parameters
11
+ RequestContext.csrf_token = form_authenticity_token
11
12
 
12
13
  yield
13
14
  ensure
@@ -1,5 +1,5 @@
1
1
  module WidgetList
2
2
  class RequestContext < ActiveSupport::CurrentAttributes
3
- attribute :request, :server, :session, :used
3
+ attribute :request, :server, :session, :used, :csrf_token
4
4
  end
5
5
  end
@@ -1,3 +1,3 @@
1
1
  module WidgetList
2
- VERSION = "2.0.0"
2
+ VERSION = "2.0.2"
3
3
  end
data/lib/widget_list.rb CHANGED
@@ -43,10 +43,12 @@ module WidgetList
43
43
 
44
44
 
45
45
  #Loop models
46
- models = Dir[ Rails.root.join("app", "models").to_s + '**/*'].reject {|fn| File.directory?(fn) }
46
+ models = Dir[Rails.root.join('app', 'models', '**', '*.rb').to_s].reject do |path|
47
+ File.basename(path) == 'application_record.rb'
48
+ end
47
49
  if !models.empty?
48
50
  model_options = '<option value="">Select a Model</option>' + models.collect { |model|
49
- model_name = model.split('/').last.to_s.camelize.gsub(/.rb/,'')
51
+ model_name = File.basename(model, '.rb').camelize
50
52
  "<option value='#{model_name}' #{((@isEditing && page_config['view'] == model_name) ? 'selected' : '')}>#{model_name}</option>"
51
53
  }.sort.uniq.join('')
52
54
  else
@@ -119,7 +121,10 @@ module WidgetList
119
121
  @fill['<!--VIEW_OPTIONS-->'] = model_options
120
122
  @fill['<!--TITLE_VALUE-->'] = (!@isEditing) ? '' : page_config['title']
121
123
  @fill['<!--DESC_VALUE-->'] = (!@isEditing) ? '' : page_config['listDescription']
122
- @fill['<!--PRIMARY_CHECKED-->'] = (!@isEditing) ? 'checked' : (page_config['primaryDatabase'] == "1") ? 'checked' : ''
124
+ WidgetList::List.load_widget_list_yml
125
+ primary_is_active_record = !$widget_list_conf[:primary].to_s.include?(':/')
126
+ @fill['<!--PRIMARY_CHECKED-->'] = (!@isEditing) ? (primary_is_active_record ? 'checked' : '') : (page_config['primaryDatabase'] == "1") ? 'checked' : ''
127
+ @fill['<!--AUTH_TOKEN-->'] = ERB::Util.html_escape(WidgetList::RequestContext.csrf_token.to_s)
123
128
 
124
129
  #
125
130
  # FIELD LEVEL
@@ -188,7 +193,8 @@ module WidgetList
188
193
  @fieldFill['<!--DESC-->'] = 'Desc'
189
194
  @fieldFill['<!--DISABLED-->'] = 'disabled'
190
195
  @fieldFill['<!--TR_STYLE-->'] = ''
191
- @fill['<!--HIDDEN_FIELD_TEMPLATE-->'] = WidgetList::Utils::fill(@fieldFill , ac.render_to_string(:partial => 'widget_list/administration/field_row') )
196
+ @fieldFill['<!--ONBLUR1-->'] = ''
197
+ @fill['<!--HIDDEN_FIELD_TEMPLATE-->'] = WidgetList::Utils::fill(@fieldFill , ac.render_to_string(:partial => 'widget_list/administration/field_row', :locals => { :key_select => true }) )
192
198
  @fill['<!--ADD_HIDDEN_FIELD_BUTTON-->'] = WidgetList::Widgets::widget_button('Add Hidden Field', {'onclick' => "AddHiddenField();", 'innerClass' => "success" } )
193
199
  @fill['<!--ALL_HIDDEN_FIELDS-->'] = (!@isEditing) ? '' : page_json['fields_hidden']
194
200
 
@@ -203,7 +209,8 @@ module WidgetList
203
209
  @fieldFill['<!--DESC-->'] = 'Database Function'
204
210
  @fieldFill['<!--DISABLED-->'] = ''
205
211
  @fieldFill['<!--TR_STYLE-->'] = ''
206
- @fill['<!--FIELD_FUNCTION_TEMPLATE-->'] = WidgetList::Utils::fill(@fieldFill , ac.render_to_string(:partial => 'widget_list/administration/field_row') )
212
+ @fieldFill['<!--ONBLUR1-->'] = ''
213
+ @fill['<!--FIELD_FUNCTION_TEMPLATE-->'] = WidgetList::Utils::fill(@fieldFill , ac.render_to_string(:partial => 'widget_list/administration/field_row', :locals => { :key_select => true }) )
207
214
  @fill['<!--ADD_FIELD_FUNCTION_BUTTON-->'] = WidgetList::Widgets::widget_button('Add Function', {'onclick' => "AddFieldFunction();", 'innerClass' => "success" } )
208
215
  @fill['<!--ALL_FIELD_FUNCTIONS-->'] = (!@isEditing) ? '' : page_json['fields_function']
209
216
 
@@ -261,7 +268,7 @@ module WidgetList
261
268
  @fieldFill['<!--COLOR_HELP-->'] = ':column_to_show should either be the column or possibly a formatted column like CONCAT or COUNT'
262
269
  @fieldFill['<!--SUBJECT-->'] = 'drill_downs'
263
270
 
264
- @fill['<!--THE_DRILL_DOWN_TEMPLATE-->'] = WidgetList::Utils::fill(@fieldFill , ac.render_to_string(:partial => 'widget_list/administration/button_row') )
271
+ @fill['<!--THE_DRILL_DOWN_TEMPLATE-->'] = WidgetList::Utils::fill(@fieldFill , ac.render_to_string(:partial => 'widget_list/administration/button_row', :locals => { :middle_column_select => true }) )
265
272
  @fill['<!--ADD_DRILL_DOWN_BUTTON-->'] = WidgetList::Widgets::widget_button('Add New Drill Down', {'onclick' => "AddDrillDown();", 'innerClass' => "success" } )
266
273
  @fill['<!--ALL_DRILL_DOWNS-->'] = page_json['drill_downs']
267
274
 
@@ -278,7 +285,7 @@ module WidgetList
278
285
 
279
286
  @fieldFill = {}
280
287
  @fieldFill['<!--REMOVE_FIELD_BUTTON-->'] = remove_field_button()
281
- @fieldFill['<!--FIELD_VALUE-->'] = 'column_name'
288
+ @fieldFill['<!--FIELD_VALUE-->'] = ''
282
289
  @fieldFill['<!--FIELD_DESC-->'] = 'User Desc'
283
290
  @fieldFill['<!--SUBJECT-->'] = 'group_by'
284
291
  @fieldFill['<!--FIELD-->'] = 'Field'
@@ -286,7 +293,7 @@ module WidgetList
286
293
  @fieldFill['<!--DISABLED-->'] = ''
287
294
  @fieldFill['<!--ONBLUR1-->'] = 'InvalidField(this)'
288
295
  @fieldFill['<!--TR_STYLE-->'] = ''
289
- @fill['<!--DEFAULT_GROUPING-->'] = WidgetList::Utils::fill(@fieldFill , ac.render_to_string(:partial => 'widget_list/administration/field_row') )
296
+ @fill['<!--DEFAULT_GROUPING-->'] = WidgetList::Utils::fill(@fieldFill , ac.render_to_string(:partial => 'widget_list/administration/field_row', :locals => { :key_select => true }) )
290
297
  @fill['<!--ADD_GROUP_BY_BUTTON-->'] = WidgetList::Widgets::widget_button('Add New Group By', {'onclick' => "AddGroupBy();", 'innerClass' => "success" } )
291
298
  @fill['<!--GROUPING_ITEMS-->'] = (!@isEditing) ? '' : page_json['group_by']
292
299
 
@@ -415,6 +422,7 @@ module WidgetList
415
422
  list_parms['searchFieldsOut']#{add_pointer(field[1]['column_to_show'],9)} true"
416
423
 
417
424
  column_to_show = "'#{field[1]['column_to_show'].gsub(/_linked/,'')}'"
425
+ data_to_pass = "'#{field[1]['data_to_pass_from_view'].gsub(/_linked/,'')}'"
418
426
  if using_grouping
419
427
  if database_type == 'oracle'
420
428
  column_to_show = "groupByFilter == 'none' ? '#{escape_code field[1]['column_to_show'].gsub(/_linked/,'')}' : 'MAX(#{escape_code field[1]['column_to_show'].gsub(/_linked/,'')})'"
@@ -425,22 +433,17 @@ module WidgetList
425
433
  list_parms['fieldFunction']#{add_pointer(field[1]['column_to_show'],7)} WidgetList::List::build_drill_down(
426
434
  :list_id => list_parms['name'],
427
435
  :drill_down_name => '#{escape_code field[0]}',
428
- :data_to_pass_from_view => #{column_to_show},
436
+ :data_to_pass_from_view => #{data_to_pass},
429
437
  :column_to_show => #{column_to_show},
430
438
  :column_alias => '#{escape_code field[1]['column_to_show']}',
431
439
  :primary_database => #{page_config['primaryDatabase'] == '1' ? 'true' : 'false'}
432
440
  )"
433
- if $is_mongo
434
- code = "filterValue"
435
- else
436
- code = "#{page_config['view']}.sanitize(filterValue)"
437
- end
438
441
  case_statements += <<-EOD
439
442
 
440
443
  when '#{field[0]}'
441
- list_parms['filter'] << " #{field[1]['column_to_show'].gsub(/_linked/,'')} = ? "
442
- list_parms['bindVars'] << #{code}
443
- list_parms['listDescription'] = drillDownBackLink + ' Filtered by #{escape_code field[1]['column_to_show'].gsub(/_linked/,'').camelize} (' + filterValue + ')'
444
+ list_parms['filter'] << " #{field[1]['data_to_pass_from_view'].gsub(/_linked/,'')} = ? "
445
+ list_parms['bindVars'] << filterValue
446
+ list_parms['listDescription'] = drillDownBackLink + ' Filtered by #{escape_code field[1]['data_to_pass_from_view'].gsub(/_linked/,'').camelize} (' + filterValue + ')'
444
447
  EOD
445
448
  }
446
449
 
@@ -595,7 +598,7 @@ module WidgetList
595
598
 
596
599
  if page_config['useRansack'] == '1' && page_config['showSearch'] == '1' && $is_mongo == false
597
600
  view_code = "
598
- list_parms#{add_pointer('ransackSearch',-10)} #{page_config['view']}.search(#{(WidgetList::RequestContext.request.key?('iframe')) ? 'WidgetList::RequestContext.request' : 'params'}[:q])
601
+ list_parms#{add_pointer('ransackSearch',-10)} #{page_config['view']}.ransack(#{(WidgetList::RequestContext.request.key?('iframe')) ? 'WidgetList::RequestContext.request' : 'params'}[:q])
599
602
  list_parms#{add_pointer('view',-10)} list_parms['ransackSearch'].result
600
603
  "
601
604
  if page_config['ransackAdvancedForm'] == '1'
@@ -622,7 +625,7 @@ module WidgetList
622
625
 
623
626
  else
624
627
  view_code = "
625
- list_parms#{add_pointer('view',-10)} #{page_config['view']}
628
+ list_parms#{add_pointer('view',-10)} #{page_config['view']}.all
626
629
  "
627
630
  end
628
631
 
@@ -807,8 +810,6 @@ module WidgetList
807
810
  end
808
811
 
809
812
  <<-EOD
810
- begin
811
-
812
813
  #{variable_code}
813
814
  list_parms = WidgetList::List::init_config()
814
815
  list_parms['name'] = '#{escape_code page_config['name']}'
@@ -849,35 +850,14 @@ module WidgetList
849
850
  when 'html'
850
851
  @output = output
851
852
  when 'json'
852
- return render :inline => output
853
- when 'export'
854
- send_data(output, :filename => list_parms['name'] + '.csv')
855
- return
856
- end
857
-
858
- rescue Exception => e
859
-
860
- #
861
- # Rescue Errors
862
- #
863
- Rails.logger.info e.to_s + "\\n\\n" + $!.backtrace.join("\\n\\n")
864
-
865
- if Rails.env == 'development'
866
- list_parms['errors'] << '<br/><br/><strong style="color:maroon;">(Ruby Exception - Still attempted to render list with given config ' + list_parms.inspect + ') Exception ==> ' + e.to_s + "<br/><br/>Backtrace:<br/><br/>" + $!.backtrace.join("<br/><br/>") + "</strong>"
867
- end
868
-
869
- output_type, output = WidgetList::List.build_list(list_parms)
870
-
871
- case output_type
872
- when 'html'
873
- @output = output
874
- when 'json'
875
- return render :inline => output
853
+ if WidgetList::RequestContext.request.key?('iframe')
854
+ @output = output
855
+ else
856
+ render json: JSON.parse(output)
857
+ end
876
858
  when 'export'
877
- send_data(output, :filename => list_parms['name'] + '.csv')
878
- return
859
+ send_data output, filename: list_parms['name'] + '.csv', type: 'text/csv'
879
860
  end
880
- end
881
861
  EOD
882
862
  end
883
863
 
@@ -1062,7 +1042,6 @@ module WidgetList
1062
1042
  buttons['Edit']['class'] = 'info'
1063
1043
  if WidgetList::RequestContext.request.key?('ajax')
1064
1044
  group_by[''] = 'All ' + model_name + 's'
1065
- group_by['field_name'] = 'This will group by field_name and show Count'
1066
1045
  buttons['Delete']['url'] = '/' + controller + '/delete/' + fields.keys.first + '/'
1067
1046
  buttons['Edit']['url'] = '/' + controller + '/edit/' + fields.keys.first + '/'
1068
1047
  end
@@ -1171,7 +1150,8 @@ module WidgetList
1171
1150
  @fieldFill['<!--FIELD-->'] = 'Field'
1172
1151
  @fieldFill['<!--DISABLED-->'] = 'disabled'
1173
1152
  @fieldFill['<!--TR_STYLE-->'] = ''
1174
- @response['fields_hidden'] += WidgetList::Utils::fill(@fieldFill , ac.render_to_string(:partial => 'widget_list/administration/field_row') )
1153
+ @fieldFill['<!--ONBLUR1-->'] = ''
1154
+ @response['fields_hidden'] += WidgetList::Utils::fill(@fieldFill , ac.render_to_string(:partial => 'widget_list/administration/field_row', :locals => { :key_select => true }) )
1175
1155
  }
1176
1156
 
1177
1157
  group_by.each { |field,description|
@@ -1186,7 +1166,7 @@ module WidgetList
1186
1166
  @fieldFill['<!--DISABLED-->'] = ''
1187
1167
  @fieldFill['<!--ONBLUR1-->'] = 'InvalidField(this)'
1188
1168
  @fieldFill['<!--TR_STYLE-->'] = ''
1189
- @response['group_by'] += WidgetList::Utils::fill(@fieldFill , ac.render_to_string(:partial => 'widget_list/administration/field_row') )
1169
+ @response['group_by'] += WidgetList::Utils::fill(@fieldFill , ac.render_to_string(:partial => 'widget_list/administration/field_row', :locals => { :key_select => true }) )
1190
1170
  }
1191
1171
 
1192
1172
  fields_function.each { |field,description|
@@ -1199,7 +1179,8 @@ module WidgetList
1199
1179
  @fieldFill['<!--FIELD-->'] = 'Field'
1200
1180
  @fieldFill['<!--DISABLED-->'] = ''
1201
1181
  @fieldFill['<!--TR_STYLE-->'] = ''
1202
- @response['fields_function'] += WidgetList::Utils::fill(@fieldFill , ac.render_to_string(:partial => 'widget_list/administration/field_row') )
1182
+ @fieldFill['<!--ONBLUR1-->'] = ''
1183
+ @response['fields_function'] += WidgetList::Utils::fill(@fieldFill , ac.render_to_string(:partial => 'widget_list/administration/field_row', :locals => { :key_select => true }) )
1203
1184
  }
1204
1185
 
1205
1186
  buttons.each { |field|
@@ -1249,7 +1230,7 @@ module WidgetList
1249
1230
  @fieldFill['<!--COLOR_HELP-->'] = ':column_to_show should either be the column or possibly a formatted column like CONCAT or COUNT'
1250
1231
  @fieldFill['<!--SUBJECT-->'] = 'drill_downs'
1251
1232
 
1252
- @response['drill_downs'] += WidgetList::Utils::fill(@fieldFill , ac.render_to_string(:partial => 'widget_list/administration/button_row') )
1233
+ @response['drill_downs'] += WidgetList::Utils::fill(@fieldFill , ac.render_to_string(:partial => 'widget_list/administration/button_row', :locals => { :middle_column_select => true }) )
1253
1234
  }
1254
1235
 
1255
1236
  footer_buttons.each { |field|
@@ -1838,7 +1819,7 @@ module WidgetList
1838
1819
  @items['LIST_SEQUENCE'] = 1
1839
1820
  end
1840
1821
 
1841
- if @items['LIST_SEQUENCE'].class.name == 'Fixnum' && @items['LIST_SEQUENCE'] > 0
1822
+ if @items['LIST_SEQUENCE'].is_a?(Integer) && @items['LIST_SEQUENCE'] > 0
1842
1823
  @sequence = @items['LIST_SEQUENCE'].to_i
1843
1824
  end
1844
1825
 
@@ -2426,6 +2407,16 @@ module WidgetList
2426
2407
  listJumpUrl['LIST_NAME'] = @items['name']
2427
2408
  listJumpUrl['SQL_HASH'] = @sqlHash
2428
2409
 
2410
+ # Initial links inherit request parameters in build_url. Ajax links
2411
+ # need the preview context copied because build_url skips that step.
2412
+ if WidgetList::RequestContext.request.key?('BUTTON_VALUE')
2413
+ %w[iframe desiredController desiredAction].each do |key|
2414
+ if WidgetList::RequestContext.request.key?(key)
2415
+ listJumpUrl[key] = WidgetList::RequestContext.request[key]
2416
+ end
2417
+ end
2418
+ end
2419
+
2429
2420
  if WidgetList::RequestContext.request.key?('switch_grouping')
2430
2421
  listJumpUrl['switch_grouping'] = WidgetList::RequestContext.request['switch_grouping']
2431
2422
  end
@@ -3421,7 +3412,13 @@ module WidgetList
3421
3412
  if WidgetList::List.get_db_type(items[:primary_database]) == 'oracle'
3422
3413
  link = %[q'[<a style='cursor:pointer;color:#{items[:link_color]};' class='#{items[:column_alias]}_drill#{items[:column_class]}' onclick='#{items[:js_function_name]}("#{items[:drill_down_name]}", ListDrillDownGetRowValue(this) ,"#{items[:list_id]}"#{items[:extra_js_func_params]});#{items[:extra_function]}'>]' #{WidgetList::List::concat_string(items[:primary_database])}#{items[:column_to_show]}#{WidgetList::List::concat_string(items[:primary_database])}q'[</a><script class='val-db' type='text'>]' #{WidgetList::List::concat_string(items[:primary_database])} #{items[:data_to_pass_from_view]} #{WidgetList::List::concat_string(items[:primary_database])} q'[</script>]' #{WidgetList::List::concat_outer(items[:primary_database])} #{WidgetList::List::is_sequel(items[:primary_database]) ? " as #{items[:column_alias]} " : ""}]
3423
3414
  else
3424
- if WidgetList::List.get_db_type(items[:primary_database]) == 'postgres'
3415
+ if WidgetList::List.get_db_type(items[:primary_database]) == 'sqlite'
3416
+ sql_literal = ->(value) { "'#{value.gsub("'", "''")}'" }
3417
+ prefix = %(<a style="cursor:pointer;color:#{items[:link_color]};" class="#{items[:column_alias]}_drill#{items[:column_class]}" onclick="#{items[:js_function_name]}('#{items[:drill_down_name]}', ListDrillDownGetRowValue(this) ,'#{items[:list_id]}'#{items[:extra_js_func_params]});#{items[:extra_function]}">)
3418
+ middle = '</a><script class="val-db" type="text">'
3419
+ suffix = '</script>'
3420
+ link = "#{sql_literal.call(prefix)} || #{items[:column_to_show]} || #{sql_literal.call(middle)} || #{items[:data_to_pass_from_view]} || #{sql_literal.call(suffix)}#{WidgetList::List::is_sequel(items[:primary_database]) ? " as #{items[:column_alias]}" : ''}"
3421
+ elsif WidgetList::List.get_db_type(items[:primary_database]) == 'postgres'
3425
3422
  link = %['<a style="cursor:pointer;color:#{items[:link_color]};" class="#{items[:column_alias]}_drill#{items[:column_class]}" onclick="#{items[:js_function_name]}(''#{items[:drill_down_name]}'', ListDrillDownGetRowValue(this) ,''#{items[:list_id]}''#{items[:extra_js_func_params]});#{items[:extra_function]}">"' #{WidgetList::List::concat_string(items[:primary_database])}#{items[:column_to_show]}#{WidgetList::List::concat_string(items[:primary_database])}'</a><script class="val-db" type="text">' #{WidgetList::List::concat_string(items[:primary_database])} #{items[:data_to_pass_from_view]} #{WidgetList::List::concat_string(items[:primary_database])}'</script>' #{WidgetList::List::is_sequel(items[:primary_database]) ? " as #{items[:column_alias]} " : ""}]
3426
3423
  else
3427
3424
  link = %[#{WidgetList::List::concat_inner(items[:primary_database])}"<a style='cursor:pointer;color:#{items[:link_color]};' class='#{items[:column_alias]}_drill#{items[:column_class]}' onclick='#{items[:js_function_name]}(#{WidgetList::List::double_quote(items[:primary_database])}#{items[:drill_down_name]}#{WidgetList::List::double_quote(items[:primary_database])}, ListDrillDownGetRowValue(this) ,#{WidgetList::List::double_quote(items[:primary_database])}#{items[:list_id]}#{WidgetList::List::double_quote(items[:primary_database])}#{items[:extra_js_func_params]});#{items[:extra_function]}'>"#{WidgetList::List::concat_string(items[:primary_database])}#{items[:column_to_show]}#{WidgetList::List::concat_string(items[:primary_database])}"</a><script class='val-db' type='text'>"#{WidgetList::List::concat_string(items[:primary_database])} #{items[:data_to_pass_from_view]} #{WidgetList::List::concat_string(items[:primary_database])}"</script>"#{WidgetList::List::concat_outer(items[:primary_database])} #{WidgetList::List::is_sequel(items[:primary_database]) ? " as #{items[:column_alias]} " : ""}]
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: widget_list
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Renne
@@ -93,6 +93,20 @@ files:
93
93
  - app/views/widget_list/list_partials/_sort_column.html.erb
94
94
  - checkin_gem.sh
95
95
  - docs/legacy-readme.md
96
+ - docs/screenshots/admin1.jpg
97
+ - docs/screenshots/admin2.jpg
98
+ - docs/screenshots/admin3.jpg
99
+ - docs/screenshots/admin4.jpg
100
+ - docs/screenshots/admin5.png
101
+ - docs/screenshots/admin6.png
102
+ - docs/screenshots/filtered.png
103
+ - docs/screenshots/main.png
104
+ - docs/screenshots/ransack1.png
105
+ - docs/screenshots/ransack2.png
106
+ - docs/screenshots/ransack3.png
107
+ - docs/screenshots/search.png
108
+ - docs/screenshots/theme_blue_sky_basin.png
109
+ - docs/widget-list-demo-thumbnail.png
96
110
  - lib/extensions/action_controller_base.rb
97
111
  - lib/widget_list.rb
98
112
  - lib/widget_list/engine.rb