dynaspan 0.1.5 → 1.0.0

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
- SHA1:
3
- metadata.gz: 6ccd1c1a71062d8b93a5339c4f083e7bedbe3d8c
4
- data.tar.gz: 643942167cfb812c700812eccfdfaeb2799693e6
2
+ SHA256:
3
+ metadata.gz: 047acad1b508d8c2fbdc2a9950051996829266886c98f4f81e0c32294aa7bc95
4
+ data.tar.gz: ea52a09c58dcc0f959f1ac6b6c947a7aafbb2b079e0223c8b3513eaf1a339a5d
5
5
  SHA512:
6
- metadata.gz: 144be3e34ffcdea3e7e09f550e6d5c398138653b6bba32dfe578592e73876549a440aeb8c71a2b2ba07f89c43754347894339ba4deda7c2c8c1c3e6a19a1b1e2
7
- data.tar.gz: e6235297868f385432e9bafbc3528d3356f83619347cc8a20b19cb561dc73eb963c9ba51f43625f6c4fe578cd4d780325afe233711f2e422167d4673c0956b34
6
+ metadata.gz: 31f57f891c11a653217bd72ef0fcd37227826ac53cf967ce69cb97a57c1d29af8515e217399795c2b0882c7e6dfeeb91ebfc863051c2d34b2bdeb91dccaa8d07
7
+ data.tar.gz: 83aa3a3b9be60f735e6d2e449844754b2b50a3de8a8b6e46980e88a042d0d447262320e386e14d4482645248eb5c382124eb0011858326ebabc4e4a812f90eea
data/CHANGELOG.md ADDED
@@ -0,0 +1,105 @@
1
+ # Changelog
2
+
3
+ ## 1.0.0
4
+
5
+ Dynaspan is now a modern Rails engine for Rails 7.1 through 8.x. The helper API
6
+ (`dynaspan_text_field`, `dynaspan_text_area`, `dynaspan_select`) and its options
7
+ are unchanged.
8
+
9
+ ### Added
10
+
11
+ - Dependency-free JavaScript. jQuery, rails-ujs and jquery_ujs are no longer
12
+ required. Updates are sent with `fetch` and include the CSRF token.
13
+ - Works with importmap-rails (`import "dynaspan"`, pinned automatically),
14
+ Propshaft and Sprockets.
15
+ - Keyboard support. The text can be reached with <kbd>Tab</kbd> and opened with
16
+ <kbd>Enter</kbd> or <kbd>Space</kbd>. <kbd>Enter</kbd> saves a text field,
17
+ <kbd>Ctrl</kbd>/<kbd>⌘</kbd>+<kbd>Enter</kbd> saves a text area and
18
+ <kbd>Esc</kbd> cancels the edit.
19
+ - DOM events: `dynaspan:open`, `dynaspan:close`, `dynaspan:update`
20
+ (cancelable), `dynaspan:success` and `dynaspan:error`.
21
+ - `ds-saving` and `ds-error` CSS classes on the Dynaspan block.
22
+ - Turbo Stream (`update.turbo_stream.erb`) and JavaScript (`update.js.erb`)
23
+ responses are applied to the page.
24
+ - An optional stylesheet: `dynaspan/dynaspan.css`.
25
+ - `window.Dynaspan.show(id)`, `.hide(id)` and `.cancel(id)` for scripting.
26
+ - A test suite (helper and browser tests) and CI across Rails 7.1–8.1.
27
+
28
+ ### Changed
29
+
30
+ - Requires Ruby 3.1+ and Rails 7.1+.
31
+ - Helpers are available in every view automatically. You no longer need to
32
+ `include Dynaspan::ApplicationHelper` (keeping it does no harm).
33
+ - Inline `onclick`/`onblur`/`onfocus` handlers were replaced by delegated event
34
+ listeners, so Dynaspan works with a `script-src` Content Security Policy
35
+ that disallows inline scripts (`callback_on_update` and
36
+ `callback_with_values` still need `unsafe-eval`).
37
+ - A request is sent only when the value actually changed.
38
+ - The three partials were merged into `dynaspan/_dynaspan.html.erb`.
39
+ - Forms get `data-turbo="false"` and are no longer marked `remote: true`.
40
+ Dynaspan submits them itself.
41
+ - For a `has_many` nested record the parameters are now sent as
42
+ `parent[children_attributes][0][attribute]`, which also works for new
43
+ (unsaved) nested records.
44
+ - The `[edit]` element is rendered only when edit text is given.
45
+
46
+ ### Fixed
47
+
48
+ - User input is displayed as text, never as HTML.
49
+ - `callback_with_values` works when the entered text contains quotes.
50
+ - Callback strings are HTML escaped in the data attributes.
51
+ - Reserved `html_options` (`:id`, `:onblur`, `:onfocus`) are really removed.
52
+ - Ids generated for namespaced models (`Admin::User`) are valid selectors.
53
+ - A blank nested value no longer displays the parent record's attribute of
54
+ the same name.
55
+ - `dynaspan_select` finds the label for hash, grouped and pre-rendered
56
+ (`options_for_select`) choices without building a regular expression from
57
+ user data.
58
+ - A nested record without `accepts_nested_attributes_for` raises a helpful
59
+ `ArgumentError` instead of silently editing the parent record.
60
+ - `helper` is no longer called on `ActionController::API` controllers.
61
+
62
+ ### Removed
63
+
64
+ - Rails 3/4 specific code, the unused `basepath.js.erb`, `dynaspan-jquery.js`
65
+ and the `assets:precompile` non-digest rake task.
66
+
67
+ ## 0.1.5 / 0.1.4
68
+
69
+ - `dynaspan_select` displays the option label rather than its value.
70
+ - Safeguard for enum values.
71
+
72
+ ## 0.1.3
73
+
74
+ - `:unique_id` defaults to an id based on the record plus random characters.
75
+ - Added `:html_options`.
76
+ - Added `dynaspan_select` with `:choices`, `:options` and block support.
77
+
78
+ ## 0.1.2
79
+
80
+ - Added `:unique_id` and `:form_for` options.
81
+
82
+ ## 0.1.1
83
+
84
+ - Added `:callback_with_values`.
85
+
86
+ ## 0.1.0
87
+
88
+ - `:hidden_fields` work for non-nested records.
89
+
90
+ ## 0.0.9
91
+
92
+ - Added `:callback_on_update`.
93
+
94
+ ## 0.0.8
95
+
96
+ - Options hash with `:hidden_fields` for nested records.
97
+ - The nested record id is only sent when it exists, allowing new nested records.
98
+
99
+ ## 0.0.7
100
+
101
+ - `ds-dialog-open` class while the field is open.
102
+
103
+ ## 0.0.6
104
+
105
+ - `ds-content-present` class when the field has content.
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (C) 2014-2016 by Daniel P. Clark
3
+ Copyright (C) 2014-2026 by Daniel P. Clark
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,215 +1,300 @@
1
- ##Dynaspan - The magic AJAX just happens!
2
- [![Gem Version](https://badge.fury.io/rb/dynaspan.svg)](http://badge.fury.io/rb/dynaspan)
3
- [![Code Climate](https://codeclimate.com/github/danielpclark/dynaspan/badges/gpa.svg)](https://codeclimate.com/github/danielpclark/dynaspan)
4
- #####[JSFiddle Demo](http://jsfiddle.net/680v09y8/)
1
+ # Dynaspan
5
2
 
6
- Dynaspan is an AJAX tool for Rails to update one field of any object without interfering with your website experience. The user will see the web page as normal text. Where ever you've placed a Dynaspan field people can click on the text and it transforms into text entry. As soon as the person moves away from that entry it sends the update to the server.
3
+ [![Gem Version](https://badge.fury.io/rb/dynaspan.svg)](https://rubygems.org/gems/dynaspan)
4
+ [![CI](https://github.com/danielpclark/dynaspan/actions/workflows/ci.yml/badge.svg)](https://github.com/danielpclark/dynaspan/actions/workflows/ci.yml)
7
5
 
8
- Dynaspan also accepts updating an attribute for a nested object, but only 1 level deep.
6
+ **Click-to-edit, in-place AJAX editing for Rails.**
9
7
 
10
- ###Installation
8
+ Dynaspan shows a record's attribute as ordinary text on your page. Click the
9
+ text and it turns into a text field, text area or select. Click away (or press
10
+ <kbd>Enter</kbd>) and the change is sent to your controller's `update` action
11
+ over AJAX, and the field turns back into plain text.
11
12
 
12
- - [ ] Add `gem 'dynaspan'` to your Gemfile
13
- - [ ] Run `bundle`
14
- - [ ] Next add `include Dynaspan::ApplicationHelper` inside your **ApplicationHelper** module
15
- - [ ] Add `//= require dynaspan/dynaspan` to your **application.js** file
16
-
17
- And it's installed!
18
-
19
- ###Usage
20
-
21
- Simple example:
22
- ```ruby
23
- dynaspan_text_field(user, :name)
13
+ ```erb
14
+ <%= dynaspan_text_field(@user, :name) %>
24
15
  ```
25
- And that's it. As long as you have a User object with a name field, this will update through
26
- the UserController's update method. **user** is an User Object instance eg: `user = User.first`.
27
16
 
28
- ---
17
+ ![Dynaspan demo: clicking text turns it into an input; clicking away saves it and turns it back into text](docs/images/dynaspan-demo.gif)
29
18
 
30
- Polymorphic/Nested Example #1:
31
- ```ruby
32
- dynaspan_text_field(@article, comment, :note, '[edit]')
33
- ```
34
- Polymorphic/Nested Example #2:
35
- ```ruby
36
- dynaspan_text_field(profile, profile.websites, :url, '[edit]',
37
- {
38
- hidden_fields: {page_name: 'page2'},
39
- callback_on_update: "alert('Awesome!');"
40
- }
41
- )
42
- ```
43
- This will show the value of note in the comment object as plain text. It can be clicked on to instantly become a text field input. And once unselected the `@article` object will update with its nested attribute object `comment` and its new value in the `note` attribute.
19
+ | Before: plain text on the page | After a click: an input, ready to type |
20
+ | --- | --- |
21
+ | ![A profile card showing plain text values](docs/images/dynaspan-text.png) | ![The same card with the title turned into a text field](docs/images/dynaspan-editing.png) |
44
22
 
45
- You can use either `dynaspan_text_field` or `dynaspan_text_area` in any of your views. There are two mandatory parameters. The first is a the main Object model instance you will be updating. And the other mandatory field is the symbol of the attribute to update. There are two optional fields. The first is the nested attribute object which will have its field updated. And the last is the optional text for `[edit]`-ing (clicking on to edit which is useful for blank fields).
46
- ```ruby
47
- dynaspan_text_field(Object,OptionalNestedObject,SymField,OptionalEditText,OptionalOptionsHash)
48
- dynaspan_text_area(Object,OptionalNestedObject,SymField,OptionalEditText,OptionalOptionsHash)
49
- dynaspan_select(Object,OptionalNestedObject,SymField,OptionalEditText,OptionsHash)
50
- ```
51
- The order is important. And yes it does NOT change even if you just do:
52
- ```ruby
53
- dynaspan_text_field(Object,SymField)
54
- ```
55
- It is unconventional but the order remains the same despite the optional fields.
23
+ - Text fields, text areas and selects
24
+ - Nested attributes (`accepts_nested_attributes_for`), one level deep
25
+ - No jQuery, rails-ujs or Turbo required, but it works alongside all three
26
+ - Works with importmap-rails, Propshaft and Sprockets
27
+ - Keyboard accessible: <kbd>Tab</kbd> to the text, <kbd>Enter</kbd> to edit,
28
+ <kbd>Enter</kbd> to save, <kbd>Esc</kbd> to cancel
29
+ - DOM events and CSS state classes for styling and custom behaviour
56
30
 
57
- ###Parameters
31
+ ## Requirements
58
32
 
59
- The **first** parameter will always be the Object that will have its update method called. It must be an instance of the Object.
60
- For example current_user being an instance of User.
33
+ - Ruby 3.1+
34
+ - Rails 7.1, 7.2, 8.0 or 8.1
61
35
 
62
- The **second** parameter can be a symbol of the field you want to update on the main Object from the first parameter.
36
+ ## Installation
63
37
 
64
- The **second** field can also be a has_one or has_many subset of the first argument moving the symbol to modify to the **third** argument.
65
- For example **dynaspan_text_field(author, author.stories, :title)**. This works as a nested attribute so it includes Polymorphic Objects.
38
+ Add the gem to your Gemfile and run `bundle install`:
66
39
 
67
- The last two parameters can be edit text, and then additional options (in that order). Both are optional. The edit text
68
- is a way to be able to click somewhere to open up the input to initially enter text.
40
+ ```ruby
41
+ gem 'dynaspan'
42
+ ```
69
43
 
70
- The options Hash currently has these options.
44
+ Then load the JavaScript using whichever asset setup your app uses.
71
45
 
72
- - **:hidden_fields** will put in as many hidden fields as you include in a Hash with key->value matching to name->value
73
- - **:callback_on_update** is a no frills callback. It runs whatever command you give it whenever Dynaspan submits an update to the server
74
- - **:callback_with_values** will allow you to put a JavaScript command you want called on update and include as many parameters as you'd like. It will dynamically append a last parameter which is a Hash of two values. The first is the CSS selector id of the Dynaspan block that just performed the action, the second value is the actual text that was entered. The keys in this Hash are **ds_selector** and **ds_input**
75
- - **:unique_id** allows custom ID labelling. This is no longer recommended to be used as the in-built method is thorough in its uniqeness.
76
- - **:form_for** allows adding or over-writing any form_for parameter (besides the object being written to). This takes a Hash of parameters just like you would give in a view for your form_for form. If you have a namespaced object to update use the **url:** option in the hash for the path to use in updating your object.
77
- - **:html_options** add your own html options to the input field. Includes ability to add additional classes with `html_options: {class: "example"}`. **:id**, **:onfocus**, and **:onblur** are reserved.
78
- - **:choices** used for **dynaspan_select** for the choices of the select box.
79
- - **:options** used for **dynaspan_select** for the options of the select box; such as **:disabled**, **:prompt**, or **:include_blank**.
80
- - **&block** used only with **dynaspan_select** for passing a block to Rails' form select method.
46
+ **importmap-rails** (the Rails 7+ default). The pin is added for you, so just
47
+ import it in `app/javascript/application.js`:
81
48
 
82
- ###How it updates
49
+ ```js
50
+ import "dynaspan"
51
+ ```
83
52
 
84
- The AJAX call will call the update method on your first Object parameter via PATCH. The optional nested attribute
85
- and the symbol for the field are all part of the main Object being updated. There is no expected AJAX reply. It's
86
- a silent set it and forget it method. If you don't have your update method configured with a `.js` response then it
87
- will successfully perform the update on the object, and then send a complaint about a response but no one will notice
88
- (unless maybe you look at the server logs). In other words the client experience is only good, and the server
89
- won't hiccup over it.
53
+ **Sprockets.** Add this to `app/assets/javascripts/application.js`:
90
54
 
91
- ###It's too easy!
55
+ ```js
56
+ //= require dynaspan/dynaspan
57
+ ```
92
58
 
93
- **You're welcome!**
59
+ **Propshaft, jsbundling-rails or anything else.** Add a script tag to your
60
+ layout:
94
61
 
95
- -- Daniel P. Clark
62
+ ```erb
63
+ <%= javascript_include_tag "dynaspan/dynaspan", defer: true %>
64
+ ```
96
65
 
97
- ###Styles
66
+ Optionally, add the default styles (hover highlight, full-width inputs, saving
67
+ and error states):
98
68
 
99
- As of version 0.0.6 a class will be dynamically added/removed to a div tag containing the class "dyna-span".
100
- That class is "ds-content-present". The purpose of this class is to allow CSS content styles depending on
101
- whether your text exists or not. The '[edit]' text you can use as a parameter normally drops below the input
102
- box. If you don't want it to drop you can style it with the proper CSS selector for content present. E.G.
103
- `.ds-content-present > dyna-span-edit-text { margin-top:-18px; }` You can set the height to whatever your input
104
- field height is to maintain the position of the edit text.
69
+ ```erb
70
+ <%= stylesheet_link_tag "dynaspan/dynaspan" %>
71
+ ```
105
72
 
106
- In version 0.0.7 I've added a class to the parent div object for when the text field dialog is open. The class
107
- is "ds-dialog-open". This is also to use in CSS styles. This feature was added since CSS doesn't support
108
- calling parents with selectors. Example usage:
73
+ The view helpers are available in every view automatically.
109
74
 
110
- ```css
111
- .ds-content-present > .dyna-span-edit-text {
112
- margin-top:-18px;
113
- }
75
+ ## Usage
114
76
 
115
- .ds-dialog-open > .dyna-span-edit-text {
116
- margin-top:-24px;
117
- }
77
+ ```erb
78
+ <%= dynaspan_text_field(@user, :name) %>
79
+ <%= dynaspan_text_area(@user, :bio) %>
80
+ <%= dynaspan_select(@user, :role, choices: [["Administrator", "admin"], ["Editor", "editor"]]) %>
118
81
  ```
119
82
 
120
- ###What's New
121
-
83
+ Each helper renders the current value as text, together with a hidden form for
84
+ the record. When the value changes, the form is submitted to the record's
85
+ `update` route (`PATCH /users/:id`), just as a normal `form_with(model: @user)`
86
+ form would be. Your controller only needs to permit the attribute:
122
87
 
123
- ####Version 0.1.4 & 0.1.5
124
-
125
- Use display name rather than value from option. And enum behavior may be prone to change so added safeguard scenario.
88
+ ```ruby
89
+ class UsersController < ApplicationController
90
+ def update
91
+ @user = User.find(params[:id])
92
+
93
+ respond_to do |format|
94
+ if @user.update(user_params)
95
+ format.html { redirect_to @user }
96
+ format.json { render json: @user }
97
+ else
98
+ format.html { render :edit, status: :unprocessable_entity }
99
+ format.json { render json: @user.errors, status: :unprocessable_entity }
100
+ end
101
+ end
102
+ end
103
+
104
+ private
105
+
106
+ def user_params
107
+ params.require(:user).permit(:name, :bio, :role)
108
+ end
109
+ end
110
+ ```
126
111
 
127
- ####Version 0.1.3
112
+ The request asks for Turbo Stream, JavaScript, JSON or HTML, in that order, so
113
+ a standard scaffold controller works as it is. Turbo Stream
114
+ (`update.turbo_stream.erb`) and JavaScript (`update.js.erb`) responses are
115
+ applied to the page. A response with an error status (such as a failed
116
+ validation) adds the `ds-error` class to the field and fires `dynaspan:error`.
128
117
 
129
- Changed **:unique_id** to work based on the object being rendered and some additional random characters in case the same object will be used more than once.
118
+ ### Edit text
130
119
 
131
- Added **:html_options** add your own html options to the input field. Includes ability to add additional classes with `html_options: {class: "example"}`. **:id**, **:onfocus**, and **:onblur** are reserved.
120
+ Blank values have nothing to click on. Pass some edit text as the argument
121
+ after the attribute to give people something to click:
132
122
 
133
- Added **dynaspan_select** for having a select box dynamically appear.
134
- - Added **:choices** used for **dynaspan_select** for the choices of the select box.
135
- - Added **:options** used for **dynaspan_select** for the options of the select box; such as **:disabled**, **:prompt**, or **:include_blank**.
136
- - Added **&block** used only with **dynaspan_select** for passing a block to Rails' form select method.
123
+ ```erb
124
+ <%= dynaspan_text_field(@user, :nickname, "[edit]") %>
125
+ ```
137
126
 
127
+ ### Nested records
138
128
 
129
+ Pass the nested record before the attribute to edit it through its parent. The
130
+ parent must have `accepts_nested_attributes_for` for the association:
139
131
 
140
- ####Version 0.1.2
132
+ ```ruby
133
+ class Article < ApplicationRecord
134
+ has_many :comments
135
+ accepts_nested_attributes_for :comments
136
+ end
137
+ ```
141
138
 
142
- Added **unique_id** parameter to the options Hash allowing custom ID labelling which is ideal for JavaScript generated usage.
139
+ ```erb
140
+ <%= dynaspan_text_field(@article, comment, :note, "[edit]") %>
141
+ ```
143
142
 
144
- Added **form_for** parameter to allow adding or over-writing any form_for parameter (besides the object being written to).
145
- If you have a namespaced object to update use the **url:** option in the hash for the path to use in updating your object.
143
+ This submits `article[comments_attributes][0][id]` and
144
+ `article[comments_attributes][0][note]` to `ArticlesController#update`.
145
+ Remember to permit `comments_attributes: [:id, :note]`.
146
146
 
147
- ####Version 0.1.1
147
+ ### Arguments
148
148
 
149
- Added a JavaScript callback that will **append** a Hash/Dictionary of the updated Dynaspan Object to the end of your
150
- functions parameters. The method is named **callback_with_values**.
151
149
  ```ruby
152
- {
153
- callback_with_values: "console.log();"
154
- }
150
+ dynaspan_text_field(record, nested_record = nil, :attribute, edit_text = nil, options = {})
151
+ dynaspan_text_area(record, nested_record = nil, :attribute, edit_text = nil, options = {})
152
+ dynaspan_select(record, nested_record = nil, :attribute, edit_text = nil, options = {}, &block)
155
153
  ```
156
- This will be called everytime the Dynaspan field submits and it will **inject** the following result **as the last parameter**:
157
- ```ruby
158
- {
159
- ds_selector: "dyna_span_unique_label<#>",
160
- ds_input: "the entered text from the input field"
161
- }
162
- ```
163
- ####Version 0.1.0
164
154
 
165
- Added the same hidden_fields from version 0.0.8 to support non-nested Objects. You can use them now on anything.
155
+ 1. **record**: the model instance whose `update` action is called.
156
+ 2. **nested_record** (optional): a record from one of `record`'s nested
157
+ attribute associations.
158
+ 3. **:attribute**: a Symbol naming the attribute to edit.
159
+ 4. **edit_text** (optional): a String shown next to the value that can also be
160
+ clicked to start editing.
161
+ 5. **options** (optional): a Hash, see below.
162
+
163
+ ### Options
164
+
165
+ | Option | Description |
166
+ | --- | --- |
167
+ | `:choices` | `dynaspan_select` only. The choices for the select: an array, a hash, grouped choices or `options_for_select` output. |
168
+ | `:options` | `dynaspan_select` only. Options for Rails' `select`, such as `include_blank:` or `prompt:`. |
169
+ | `&block` | `dynaspan_select` only. Passed through to Rails' `select`. |
170
+ | `:html_options` | HTML attributes for the input, e.g. `{ class: "wide", rows: 4, placeholder: "Name" }`. Classes are added to Dynaspan's own. `:id`, `:onblur` and `:onfocus` are reserved. |
171
+ | `:hidden_fields` | A Hash of extra values to submit, rendered as hidden fields: `{ page_name: "profile" }`. |
172
+ | `:form_for` | Options for the underlying `form_for`, e.g. `{ url: admin_user_path(@user) }` for namespaced routes. |
173
+ | `:unique_id` | Sets the id suffix used for the elements. By default an id is built from the record, the attribute and some random characters. |
174
+ | `:callback_on_update` | A string of JavaScript run each time a change is sent, e.g. `"refreshTotals();"`. |
175
+ | `:callback_with_values` | A JavaScript function call as a string, e.g. `"saved();"`. A Hash is appended as its last argument: `{ ds_selector: "#dyna_span_block…", ds_input: "entered text" }`. |
176
+
177
+ For new code, prefer the [JavaScript events](#javascript-events) over the two
178
+ callback options. The callbacks are evaluated as strings, so they need
179
+ `unsafe-eval` if you use a Content Security Policy.
180
+
181
+ ## Keyboard
182
+
183
+ | Key | Where | Action |
184
+ | --- | --- | --- |
185
+ | <kbd>Tab</kbd> | Page | Move focus to a Dynaspan value |
186
+ | <kbd>Enter</kbd> / <kbd>Space</kbd> | Value | Start editing |
187
+ | <kbd>Enter</kbd> | Text field | Save |
188
+ | <kbd>Ctrl</kbd>/<kbd>⌘</kbd> + <kbd>Enter</kbd> | Text area | Save |
189
+ | <kbd>Esc</kbd> | Field | Cancel and restore the previous value |
190
+
191
+ Clicking anywhere outside the field also saves it. Nothing is sent to the
192
+ server when the value did not change.
193
+
194
+ ## JavaScript events
195
+
196
+ Events are dispatched on the Dynaspan block (`div.dyna-span`) and bubble up
197
+ to `document`:
198
+
199
+ | Event | When | `event.detail` |
200
+ | --- | --- | --- |
201
+ | `dynaspan:open` | The field is shown | `selector`, `value` |
202
+ | `dynaspan:close` | The field is hidden | `selector`, `value`, `changed` |
203
+ | `dynaspan:update` | A change is about to be sent. Call `preventDefault()` to skip the request. | `selector`, `input` (display text), `value`, `form` |
204
+ | `dynaspan:success` | The server responded successfully | same as `update`, plus `response` |
205
+ | `dynaspan:error` | The request failed or returned an error status | same as `update`, plus `error` and `response` |
206
+
207
+ ```js
208
+ document.addEventListener("dynaspan:success", (event) => {
209
+ console.log("Saved", event.detail.value)
210
+ })
211
+ ```
166
212
 
167
- ####Version 0.0.9
213
+ You can also control a field from JavaScript by its id (set it with the
214
+ `:unique_id` option):
168
215
 
169
- JavaScript callback option now available. Whenever the Dynaspan field is submitted you can have Dynaspan call
170
- your own JavaScript method.
171
- ```ruby
172
- {
173
- callback_on_update: "someMethod('some-relative-instance-value');"
174
- }
216
+ ```js
217
+ Dynaspan.show("user-name") // open the field
218
+ Dynaspan.hide("user-name") // close it and save if changed
219
+ Dynaspan.cancel("user-name") // close it and discard the change
175
220
  ```
176
- ####Version 0.0.8
177
221
 
178
- You can now provide an option hash as a last parameter. Current
179
- valid options only include:
180
- ```ruby
181
- {
182
- hidden_fields: { label: "value" }
183
- }
222
+ If jQuery is on the page, the 0.x API (`$().dynaspan.upShow(id)`,
223
+ `upHide(id)` and `upLast(id)`) still works.
224
+
225
+ ## Styling
226
+
227
+ The markup looks like this:
228
+
229
+ ```html
230
+ <div id="dyna_span_block{id}" class="dyna-span ds-content-present" data-dynaspan="{id}">
231
+ <div id="dyna_span_div{id}" class="dyna-span-form"><form>…the input…</form></div>
232
+ <span id="dyna_span_span{id}" class="dyna-span dyna-span-text">The value</span>
233
+ <div class="dyna-span-edit-text pull-right">[edit]</div>
234
+ </div>
184
235
  ```
185
- You can add as many hidden fields to your Dynaspan objects as you'd like.
186
236
 
187
- >NOTE: In this version hidden fields only applies to nested attributes.
237
+ These classes are toggled on the outer `div.dyna-span`:
188
238
 
189
- Also the id parameter will only be passed to the server if it exists. (No more empty
190
- string for id.) This allows you to create "new" polymorphic child objects with Dynaspan.
239
+ | Class | Present when |
240
+ | --- | --- |
241
+ | `ds-content-present` | The value isn't blank |
242
+ | `ds-dialog-open` | The input is showing |
243
+ | `ds-saving` | A request is in flight |
244
+ | `ds-error` | The last request failed |
191
245
 
192
- ###License
246
+ The input always has the classes `dyna-span-input` and `form-control` (so it
247
+ fits in with Bootstrap), plus any you add with `html_options`. For example, to
248
+ keep the edit text beside a value instead of below it:
193
249
 
194
- The MIT License (MIT)
250
+ ```css
251
+ .ds-content-present > .dyna-span-edit-text { margin-top: -18px; }
252
+ .ds-dialog-open > .dyna-span-edit-text { margin-top: -24px; }
253
+ ```
195
254
 
196
- Copyright (C) 2014-2016 by Daniel P. Clark
255
+ To change the markup itself, copy
256
+ [`app/views/dynaspan/_dynaspan.html.erb`](app/views/dynaspan/_dynaspan.html.erb)
257
+ into your application at the same path.
258
+
259
+ ## Upgrading from 0.x
260
+
261
+ - Rails 7.1+ and Ruby 3.1+ are required.
262
+ - jQuery and rails-ujs are no longer needed. Load `dynaspan/dynaspan` as shown in
263
+ [Installation](#installation). If you had `//= require dynaspan/dynaspan`
264
+ already, it keeps working.
265
+ - `include Dynaspan::ApplicationHelper` is no longer necessary.
266
+ - A request is sent only when the value changed, so `callback_on_update` and
267
+ `callback_with_values` only run for real changes.
268
+ - If you overrode `_dynaspan_text_field`, `_dynaspan_text_area` or
269
+ `_dynaspan_text_select`, move your changes to `_dynaspan.html.erb`.
270
+ - Passing a nested record whose association lacks
271
+ `accepts_nested_attributes_for` now raises an `ArgumentError`.
272
+
273
+ See the [CHANGELOG](CHANGELOG.md) for everything else.
274
+
275
+ ## Development
276
+
277
+ ```sh
278
+ bundle install
279
+ bundle exec rake test # helper and browser tests (needs Chrome/Chromium)
280
+ RAILS_VERSION=7.2 bundle update # test against another Rails version
281
+ bin/demo # the demo app at http://localhost:3000
282
+ ```
197
283
 
198
- Permission is hereby granted, free of charge, to any person obtaining a copy
199
- of this software and associated documentation files (the "Software"), to deal
200
- in the Software without restriction, including without limitation the rights
201
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
202
- copies of the Software, and to permit persons to whom the Software is
203
- furnished to do so, subject to the following conditions:
284
+ The browser tests use [Cuprite](https://github.com/rubycdp/cuprite). Set
285
+ `BROWSER_PATH` if Chrome isn't found automatically.
204
286
 
205
- The above copyright notice and this permission notice shall be included in
206
- all copies or substantial portions of the Software.
287
+ The demo GIF and screenshots above are recorded from `bin/demo` with
288
+ [Playwright](https://playwright.dev) and assembled with
289
+ [Pillow](https://python-pillow.org):
290
+
291
+ ```sh
292
+ PORT=3999 bin/demo &
293
+ node script/demo/record.js # captures frames and the screenshots
294
+ python3 script/demo/build_gif.py # writes docs/images/dynaspan-demo.gif
295
+ ```
207
296
 
208
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
209
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
210
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
211
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
212
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
213
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
214
- THE SOFTWARE.
297
+ ## License
215
298
 
299
+ The MIT License (MIT). Copyright (C) 2014-2026 by Daniel P. Clark. See
300
+ [LICENSE](LICENSE).