custom_inputs 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +35 -0
- data/LICENSE.txt +21 -0
- data/README.md +129 -0
- data/Rakefile +6 -0
- data/app/assets/javascripts/custom_inputs/array-input.js +67 -0
- data/app/assets/javascripts/custom_inputs/custom_inputs.js +12 -0
- data/app/assets/javascripts/custom_inputs/list-input.js +79 -0
- data/app/assets/stylesheets/custom_inputs/array_input.scss +57 -0
- data/app/assets/stylesheets/custom_inputs/list_input.scss +41 -0
- data/app/assets/stylesheets/custom_inputs/variables.scss +4 -0
- data/app/views/custom_inputs/_list.html.erb +22 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/custom_inputs.gemspec +53 -0
- data/lib/custom_inputs.rb +9 -0
- data/lib/custom_inputs/version.rb +3 -0
- data/lib/formtastic/input_helpers.rb +22 -0
- data/lib/formtastic/inputs/array_input.rb +50 -0
- data/lib/formtastic/inputs/list_input.rb +126 -0
- metadata +113 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f9096f6fc66e82395b149079a7a2d4ecbcb7cca373642fa1004d70b24faed964
|
4
|
+
data.tar.gz: 65566a633b1e6d5081797a66142a6819adf79d6732e10f01c21893b91350574b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c941894463a216ff2d2c256923b114531bff25724eb293b677bee915724c96e00ea5f31b27b5c75ff22cca1c5533bd3a1512f2403d454d731bf0868ce37cb120
|
7
|
+
data.tar.gz: a27ffbcd6432b1b1a0b2d1e5767c5e943678c9f69653555fe266019970bf24a9a5023cc84c167a73443e55166b967e1531b0293ab8ffcda02b8a08202c31e5fb
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
In the interest of fostering an open and welcoming environment, we as
|
6
|
+
contributors and maintainers pledge to making participation in our project and
|
7
|
+
our community a harassment-free experience for everyone, regardless of age, body
|
8
|
+
size, disability, ethnicity, gender identity and expression, level of experience,
|
9
|
+
nationality, personal appearance, race, religion, or sexual identity and
|
10
|
+
orientation.
|
11
|
+
|
12
|
+
## Our Standards
|
13
|
+
|
14
|
+
Examples of behavior that contributes to creating a positive environment
|
15
|
+
include:
|
16
|
+
|
17
|
+
* Using welcoming and inclusive language
|
18
|
+
* Being respectful of differing viewpoints and experiences
|
19
|
+
* Gracefully accepting constructive criticism
|
20
|
+
* Focusing on what is best for the community
|
21
|
+
* Showing empathy towards other community members
|
22
|
+
|
23
|
+
Examples of unacceptable behavior by participants include:
|
24
|
+
|
25
|
+
* The use of sexualized language or imagery and unwelcome sexual attention or
|
26
|
+
advances
|
27
|
+
* Trolling, insulting/derogatory comments, and personal or political attacks
|
28
|
+
* Public or private harassment
|
29
|
+
* Publishing others' private information, such as a physical or electronic
|
30
|
+
address, without explicit permission
|
31
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
32
|
+
professional setting
|
33
|
+
|
34
|
+
## Our Responsibilities
|
35
|
+
|
36
|
+
Project maintainers are responsible for clarifying the standards of acceptable
|
37
|
+
behavior and are expected to take appropriate and fair corrective action in
|
38
|
+
response to any instances of unacceptable behavior.
|
39
|
+
|
40
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
41
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
42
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
43
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
44
|
+
threatening, offensive, or harmful.
|
45
|
+
|
46
|
+
## Scope
|
47
|
+
|
48
|
+
This Code of Conduct applies both within project spaces and in public spaces
|
49
|
+
when an individual is representing the project or its community. Examples of
|
50
|
+
representing a project or community include using an official project e-mail
|
51
|
+
address, posting via an official social media account, or acting as an appointed
|
52
|
+
representative at an online or offline event. Representation of a project may be
|
53
|
+
further defined and clarified by project maintainers.
|
54
|
+
|
55
|
+
## Enforcement
|
56
|
+
|
57
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
58
|
+
reported by contacting the project team at hasan.a.ayar@gmail.com. All
|
59
|
+
complaints will be reviewed and investigated and will result in a response that
|
60
|
+
is deemed necessary and appropriate to the circumstances. The project team is
|
61
|
+
obligated to maintain confidentiality with regard to the reporter of an incident.
|
62
|
+
Further details of specific enforcement policies may be posted separately.
|
63
|
+
|
64
|
+
Project maintainers who do not follow or enforce the Code of Conduct in good
|
65
|
+
faith may face temporary or permanent repercussions as determined by other
|
66
|
+
members of the project's leadership.
|
67
|
+
|
68
|
+
## Attribution
|
69
|
+
|
70
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
71
|
+
available at [http://contributor-covenant.org/version/1/4][version]
|
72
|
+
|
73
|
+
[homepage]: http://contributor-covenant.org
|
74
|
+
[version]: http://contributor-covenant.org/version/1/4/
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
custom_inputs (0.1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
diff-lcs (1.3)
|
10
|
+
rake (13.0.1)
|
11
|
+
rspec (3.9.0)
|
12
|
+
rspec-core (~> 3.9.0)
|
13
|
+
rspec-expectations (~> 3.9.0)
|
14
|
+
rspec-mocks (~> 3.9.0)
|
15
|
+
rspec-core (3.9.1)
|
16
|
+
rspec-support (~> 3.9.1)
|
17
|
+
rspec-expectations (3.9.0)
|
18
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
19
|
+
rspec-support (~> 3.9.0)
|
20
|
+
rspec-mocks (3.9.1)
|
21
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
22
|
+
rspec-support (~> 3.9.0)
|
23
|
+
rspec-support (3.9.2)
|
24
|
+
|
25
|
+
PLATFORMS
|
26
|
+
ruby
|
27
|
+
|
28
|
+
DEPENDENCIES
|
29
|
+
bundler (~> 2)
|
30
|
+
custom_inputs!
|
31
|
+
rake (~> 13.0)
|
32
|
+
rspec (~> 3.0)
|
33
|
+
|
34
|
+
BUNDLED WITH
|
35
|
+
2.1.4
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2020 Hasan Ali Ayar
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
# Custom Inputs for Formtastic (Active Admin)
|
2
|
+
|
3
|
+
I've been using [Active Admin](https://github.com/activeadmin/activeadmin) for developing admin panels for my clients.
|
4
|
+
There are number of custom input components accumulated along the way.
|
5
|
+
|
6
|
+
I've decided to make a gem and share all these custom input components that I think may be very useful.
|
7
|
+
|
8
|
+
|
9
|
+
These inputs are optimised for [Arctic Admin](https://github.com/cprodhomme/arctic_admin) theme.
|
10
|
+
If you are using any other theme you can ignore the css files of this gem and implement your own css.
|
11
|
+
|
12
|
+
*Note:* Although these inputs are being actively used in a production app you should use them at your own risk.
|
13
|
+
|
14
|
+
- [Array input](#array-input)
|
15
|
+
- [List input](#list-input)
|
16
|
+
|
17
|
+
|
18
|
+
(More to come...)
|
19
|
+
- Code Editor input (...)
|
20
|
+
- Query Builder input (...)
|
21
|
+
- Remote Image Select input (...)
|
22
|
+
- Rich Text input (...)
|
23
|
+
- Upload To S3 input (...)
|
24
|
+
|
25
|
+
## Installation
|
26
|
+
|
27
|
+
Add this line to your application's Gemfile:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
gem 'custom_inputs'
|
31
|
+
```
|
32
|
+
|
33
|
+
And then execute:
|
34
|
+
|
35
|
+
$ bundle
|
36
|
+
|
37
|
+
Or install it yourself as:
|
38
|
+
|
39
|
+
$ gem install custom_inputs
|
40
|
+
|
41
|
+
## Usage
|
42
|
+
|
43
|
+
All inputs comes with a javascript and scss file. You need to include these files wherever and whenever you want them to be included.
|
44
|
+
|
45
|
+
For a quick start you can add them to the base active admin asset files like so:
|
46
|
+
|
47
|
+
|
48
|
+
Append @ app/assets/javascripts/active_admin.js:
|
49
|
+
```javascript
|
50
|
+
//= require custom_inputs/array-input
|
51
|
+
//= require custom_inputs/list-input
|
52
|
+
```
|
53
|
+
|
54
|
+
Append @ app/assets/stylesheets/active_admin.scss :
|
55
|
+
```scss
|
56
|
+
@import "custom_inputs/array_input";
|
57
|
+
@import "custom_inputs/list_input";
|
58
|
+
```
|
59
|
+
|
60
|
+
### Array Input
|
61
|
+
![Array input sample](examples/images/array-input-sample.png)
|
62
|
+
Array input is specially made for PostgreSQL array fields.
|
63
|
+
You can add/remove string items to the input and they'll be packed in an array field.
|
64
|
+
|
65
|
+
Add the field to the permitted parameter as an array.
|
66
|
+
```ruby
|
67
|
+
permit_params colors: []
|
68
|
+
```
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
form do |f|
|
72
|
+
f.inputs do
|
73
|
+
f.input :colors, as: :array, undoable: true
|
74
|
+
end
|
75
|
+
f.actions
|
76
|
+
end
|
77
|
+
```
|
78
|
+
|
79
|
+
#### Available Options
|
80
|
+
|
81
|
+
- **`undoable`:** deleted array items can be un-deleted if this flag set to true.
|
82
|
+
|
83
|
+
### List Input
|
84
|
+
![List input sample](examples/images/list-input-sample.png)
|
85
|
+
|
86
|
+
List input helps you generate a selection of items (array of hashes) from the supplied `collection_object` option.
|
87
|
+
|
88
|
+
You can select items from a drop down list, with an optional quantity.
|
89
|
+
|
90
|
+
The selected items are displayed in an html table.
|
91
|
+
|
92
|
+
You can determine which fields of the items are to be used by `fields` option.
|
93
|
+
|
94
|
+
This input will eventually generate an array of hashes with specified fields to be submitted.
|
95
|
+
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
form do |f|
|
99
|
+
f.inputs do
|
100
|
+
f.input :order_items, as: :list,
|
101
|
+
has_quantity: true,
|
102
|
+
collection_object: Product.all,
|
103
|
+
to_s_method: :name,
|
104
|
+
unique_field: :id,
|
105
|
+
fields: [
|
106
|
+
{'Product number': :id},
|
107
|
+
{'Name': :name}
|
108
|
+
]
|
109
|
+
end
|
110
|
+
f.actions
|
111
|
+
end
|
112
|
+
```
|
113
|
+
#### Available Options
|
114
|
+
|
115
|
+
- **`has_quantity`:** append quantity field to the items in the list.
|
116
|
+
- **`collection_object*`:** the list of items to choose from.
|
117
|
+
- **`to_s_method`:** (default `to_s`) the method to call for each item in the `collection_object` to display it in the dropdown list.
|
118
|
+
- **`unique_field`:** (default `id`) the field that uniquely identifies the items in the `collection_object`.
|
119
|
+
- **`fields*`:** only the supplied fields of the items will be used in the input.
|
120
|
+
The keys are used as table column headers and the values ara the actual field names.
|
121
|
+
|
122
|
+
|
123
|
+
## Contributing
|
124
|
+
|
125
|
+
Bug reports and pull requests are very welcome.
|
126
|
+
|
127
|
+
## License
|
128
|
+
|
129
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
class ArrayModifier {
|
2
|
+
static removeFromArrayInput(e) {
|
3
|
+
let button = this;
|
4
|
+
let input = button.previousSibling;
|
5
|
+
let undoable = button.classList.contains('undoable');
|
6
|
+
|
7
|
+
if (undoable) {
|
8
|
+
if (input.disabled) {
|
9
|
+
button.innerHTML = `<i class="fa fa-minus-circle"></i>`;
|
10
|
+
input.disabled = false;
|
11
|
+
return input.classList.remove('text--strike');
|
12
|
+
} else {
|
13
|
+
button.innerHTML = `<i class="fa fa-undo"></i>`;
|
14
|
+
input.disabled = true;
|
15
|
+
input.classList.add('text--strike');
|
16
|
+
}
|
17
|
+
} else {
|
18
|
+
button.parentElement.remove();
|
19
|
+
}
|
20
|
+
}
|
21
|
+
|
22
|
+
static createNewInput(value) {
|
23
|
+
let template = document.querySelector('.input-group--array__item.template');
|
24
|
+
let clone = template.cloneNode(true);
|
25
|
+
|
26
|
+
clone.classList.remove('template', 'hidden')
|
27
|
+
|
28
|
+
let input = clone.querySelector('input');
|
29
|
+
input.removeAttribute('disabled');
|
30
|
+
input.value = value;
|
31
|
+
input.focus();
|
32
|
+
|
33
|
+
return clone;
|
34
|
+
}
|
35
|
+
|
36
|
+
static handleKeyDown(event) {
|
37
|
+
if (event.key === 'Enter') {
|
38
|
+
ArrayModifier.handleAddClick(event, this.nextSibling);
|
39
|
+
return false;
|
40
|
+
}
|
41
|
+
}
|
42
|
+
|
43
|
+
static handleAddClick(e, b) {
|
44
|
+
e.preventDefault();
|
45
|
+
|
46
|
+
let button = b || this;
|
47
|
+
let newItemField = button.parentNode;
|
48
|
+
let input = button.previousSibling;
|
49
|
+
|
50
|
+
let value = input.value;
|
51
|
+
if (value !== '') {
|
52
|
+
input.value = '';
|
53
|
+
let clone = ArrayModifier.createNewInput(value);
|
54
|
+
newItemField.parentNode.insertBefore(clone, newItemField);
|
55
|
+
} else {
|
56
|
+
return input.focus();
|
57
|
+
}
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
$(document).ready(function () {
|
62
|
+
$('.js-add-to-array-input').click(ArrayModifier.handleAddClick);
|
63
|
+
|
64
|
+
let arrayInputContainer = $('.input-group--array');
|
65
|
+
$(arrayInputContainer).on('click', '.js-remove-from-array-input', ArrayModifier.removeFromArrayInput);
|
66
|
+
$(arrayInputContainer).on('keydown', '.input-group--array__item input', ArrayModifier.handleKeyDown)
|
67
|
+
});
|
@@ -0,0 +1,12 @@
|
|
1
|
+
function clearEmptyValues() {
|
2
|
+
console.log('custom_inputs.js : clearEmptyValues');
|
3
|
+
document.querySelectorAll('.custom-inputs--input-field').forEach((input) => {
|
4
|
+
if (!input.value) {
|
5
|
+
input.setAttribute('disabled', 'disabled');
|
6
|
+
}
|
7
|
+
});
|
8
|
+
}
|
9
|
+
|
10
|
+
$(document).ready(function () {
|
11
|
+
$('form').submit(clearEmptyValues);
|
12
|
+
});
|
@@ -0,0 +1,79 @@
|
|
1
|
+
//= require ./custom_inputs
|
2
|
+
|
3
|
+
class ListModifier {
|
4
|
+
static removeFromArrayInput(button) {
|
5
|
+
button.parentElement.parentElement.remove();
|
6
|
+
}
|
7
|
+
|
8
|
+
// Add item button click
|
9
|
+
static addToItems(e) {
|
10
|
+
e.preventDefault;
|
11
|
+
|
12
|
+
let add_button = this;
|
13
|
+
if (add_button.classList.contains('array-action--add')) {
|
14
|
+
let container = add_button.parentElement.parentElement;
|
15
|
+
let item_table = container.querySelector('.item-table tbody');
|
16
|
+
let template_row = container.querySelector('.item-row');
|
17
|
+
|
18
|
+
//Get selected values
|
19
|
+
let quantity_val = container.querySelector('input').value;
|
20
|
+
let item_val = JSON.parse(container.querySelector('select').value);
|
21
|
+
let row_id = 'row_' + item_val['id'];
|
22
|
+
|
23
|
+
//If this item is selected before, we'll update it's quantity instead of inserting a new row.
|
24
|
+
let row_to_be_updated = item_table.querySelector('#' + row_id);
|
25
|
+
|
26
|
+
let new_row = false;
|
27
|
+
if (!row_to_be_updated) {
|
28
|
+
//Clone the item row and show it.
|
29
|
+
row_to_be_updated = template_row.cloneNode(true);
|
30
|
+
row_to_be_updated.classList.remove('hidden');
|
31
|
+
row_to_be_updated.setAttribute('id', row_id);
|
32
|
+
new_row = true;
|
33
|
+
}
|
34
|
+
|
35
|
+
// Find elements to manipulate.
|
36
|
+
// Html td cells contain field attributes with the key of contained value.
|
37
|
+
let fields = row_to_be_updated.querySelectorAll('td[field]');
|
38
|
+
fields.forEach(field => {
|
39
|
+
let fieldKey = field.getAttribute('field');
|
40
|
+
let fieldValue = item_val[fieldKey];
|
41
|
+
|
42
|
+
// Put value in table cell
|
43
|
+
let cell = row_to_be_updated.querySelector('.' + fieldKey + '-cell');
|
44
|
+
cell.innerHTML = fieldValue;
|
45
|
+
|
46
|
+
// put value in hidden input
|
47
|
+
let hidden_input = row_to_be_updated.querySelector('.hidden-' + fieldKey);
|
48
|
+
hidden_input.value = fieldValue;
|
49
|
+
});
|
50
|
+
|
51
|
+
//Assign selected quantity to hidden inputs#
|
52
|
+
let hidden_quantity = row_to_be_updated.querySelector('.hidden-quantity');
|
53
|
+
let prev_quantity = hidden_quantity.value || 0;
|
54
|
+
|
55
|
+
quantity_val = Number(quantity_val) + Number(prev_quantity);
|
56
|
+
hidden_quantity.value = quantity_val;
|
57
|
+
|
58
|
+
//Show selected quantity on table
|
59
|
+
let quantity_cell = row_to_be_updated.querySelector('.quantity-cell');
|
60
|
+
quantity_cell.innerHTML = quantity_val;
|
61
|
+
|
62
|
+
if (new_row) {
|
63
|
+
//Add cloned row to table
|
64
|
+
return item_table.appendChild(row_to_be_updated);
|
65
|
+
}
|
66
|
+
} else {
|
67
|
+
return removeFromArrayInput(this);
|
68
|
+
}
|
69
|
+
}
|
70
|
+
}
|
71
|
+
|
72
|
+
$(document).ready(function () {
|
73
|
+
$('.item-list-container').on('click', '.js-remove-from-items-input', function (e) {
|
74
|
+
e.preventDefault();
|
75
|
+
ListModifier.removeFromArrayInput(this);
|
76
|
+
});
|
77
|
+
|
78
|
+
$('.js-add-to-items-input').click(ListModifier.addToItems);
|
79
|
+
});
|
@@ -0,0 +1,57 @@
|
|
1
|
+
//array input
|
2
|
+
|
3
|
+
@mixin reset-aa-button {
|
4
|
+
background: none;
|
5
|
+
border-radius: 0;
|
6
|
+
border: 0;
|
7
|
+
box-shadow: none;
|
8
|
+
line-height: 1;
|
9
|
+
padding: 0;
|
10
|
+
text-shadow: none;
|
11
|
+
vertical-align: middle;
|
12
|
+
|
13
|
+
&:not(.disabled) {
|
14
|
+
&:hover,
|
15
|
+
&:active,
|
16
|
+
&:focus {
|
17
|
+
background: none;
|
18
|
+
border: 0;
|
19
|
+
box-shadow: none;
|
20
|
+
}
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
.formtastic li.array.input {
|
25
|
+
.input-group--array {
|
26
|
+
width: 75%;
|
27
|
+
margin: 0 0 0 25%;
|
28
|
+
.input-group--array__item {
|
29
|
+
padding: 4px 0;
|
30
|
+
input {
|
31
|
+
display: inline-block;
|
32
|
+
margin: 0 10px 0 0;
|
33
|
+
&.text--strike {
|
34
|
+
text-decoration: line-through;
|
35
|
+
}
|
36
|
+
}
|
37
|
+
&.hidden {
|
38
|
+
display: none;
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
.array-action--add,
|
43
|
+
.array-action--remove {
|
44
|
+
@include reset-aa-button;
|
45
|
+
display: inline-block;
|
46
|
+
font-size: rem(15);
|
47
|
+
}
|
48
|
+
|
49
|
+
.array-action--add {
|
50
|
+
color: green;
|
51
|
+
}
|
52
|
+
|
53
|
+
.array-action--remove {
|
54
|
+
color: red;
|
55
|
+
}
|
56
|
+
}
|
57
|
+
}
|
@@ -0,0 +1,41 @@
|
|
1
|
+
//item_with_quantity input
|
2
|
+
@import "./variables";
|
3
|
+
|
4
|
+
.list{
|
5
|
+
&.view{
|
6
|
+
width: auto;
|
7
|
+
}
|
8
|
+
|
9
|
+
th{
|
10
|
+
text-align: left;
|
11
|
+
}
|
12
|
+
&.error {
|
13
|
+
table tr {
|
14
|
+
border-color: $danger-color
|
15
|
+
}
|
16
|
+
}
|
17
|
+
.item-list-container {
|
18
|
+
width: 100%;
|
19
|
+
padding-left: 25%;
|
20
|
+
padding-right: 25%;
|
21
|
+
}
|
22
|
+
.hidden {
|
23
|
+
display: none;
|
24
|
+
}
|
25
|
+
.item-quantity-group:first-child {
|
26
|
+
margin-left: 0;
|
27
|
+
}
|
28
|
+
.item-quantity-group {
|
29
|
+
display: flex;
|
30
|
+
max-width: 50%;
|
31
|
+
margin-left: 25%;
|
32
|
+
justify-content: space-between;
|
33
|
+
.item-item, .select2 {
|
34
|
+
flex: 3;
|
35
|
+
}
|
36
|
+
.item-quantity {
|
37
|
+
flex: 1;
|
38
|
+
margin-left: 10px;
|
39
|
+
}
|
40
|
+
}
|
41
|
+
}
|
@@ -0,0 +1,22 @@
|
|
1
|
+
<div class="item_with_quantity view">
|
2
|
+
|
3
|
+
<table class="item-table">
|
4
|
+
<tbody>
|
5
|
+
<tr>
|
6
|
+
<% items[0].each do |key, value| %>
|
7
|
+
<th><%= key %></th>
|
8
|
+
<% end %>
|
9
|
+
</tr>
|
10
|
+
<% items.each do |item| %>
|
11
|
+
<tr class="item-row">
|
12
|
+
<% item.each do |key, value| %>
|
13
|
+
|
14
|
+
<td><%= value %></td>
|
15
|
+
|
16
|
+
<% end %>
|
17
|
+
</tr>
|
18
|
+
<% end %>
|
19
|
+
</tbody>
|
20
|
+
</table>
|
21
|
+
|
22
|
+
</div>
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "custom_inputs"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
lib = File.expand_path("../lib", __FILE__)
|
2
|
+
app = File.expand_path("../app", __FILE__)
|
3
|
+
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
$LOAD_PATH.unshift(app) unless $LOAD_PATH.include?(app)
|
6
|
+
require "custom_inputs/version"
|
7
|
+
|
8
|
+
Gem::Specification.new do |spec|
|
9
|
+
spec.name = "custom_inputs"
|
10
|
+
spec.version = CustomInputs::VERSION
|
11
|
+
spec.authors = ["Hasan Ali Ayar"]
|
12
|
+
spec.email = ["hasan.a.ayar@gmail.com"]
|
13
|
+
|
14
|
+
spec.summary = "Custom form input fields for active admin + formtastic."
|
15
|
+
spec.description = `I've been using Active Admin for developing admin panels for my clients.
|
16
|
+
There are number of custom input components accumulated along the way.
|
17
|
+
|
18
|
+
I've decided to make a gem and share all these custom input components that I think may be very useful.
|
19
|
+
|
20
|
+
These inputs are optimised for Arctic Admin theme.
|
21
|
+
If you are using any other theme you can ignore the css files of this gem and implement your own css.
|
22
|
+
|
23
|
+
Note: Although these inputs are being actively used in a production app you should use them at your own risk.
|
24
|
+
`
|
25
|
+
spec.homepage = "https://github.com/hasan-aa/custom_inputs"
|
26
|
+
spec.license = "MIT"
|
27
|
+
|
28
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
29
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
30
|
+
if spec.respond_to?(:metadata)
|
31
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org/"
|
32
|
+
|
33
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
34
|
+
spec.metadata["source_code_uri"] = "https://github.com/hasan-aa/custom_inputs"
|
35
|
+
# spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
|
36
|
+
else
|
37
|
+
raise "RubyGems 2.0 or newer is required to protect against " \
|
38
|
+
"public gem pushes."
|
39
|
+
end
|
40
|
+
|
41
|
+
# Specify which files should be added to the gem when it is released.
|
42
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
43
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
44
|
+
`git ls-files -z`.split("\x0").reject {|f| f.match(%r{^(test|spec|features|examples)/})}
|
45
|
+
end
|
46
|
+
spec.bindir = "exe"
|
47
|
+
spec.executables = spec.files.grep(%r{^exe/}) {|f| File.basename(f)}
|
48
|
+
spec.require_paths = ["lib", "app"]
|
49
|
+
|
50
|
+
spec.add_development_dependency "bundler", "~> 2"
|
51
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
52
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
53
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Formtastic
|
2
|
+
module InputHelpers
|
3
|
+
def icon_tag(icon_name)
|
4
|
+
template.content_tag(:i, '', class: "fa fa-#{icon_name}")
|
5
|
+
end
|
6
|
+
|
7
|
+
def input_classes(options)
|
8
|
+
classes = 'custom-inputs--input-field'
|
9
|
+
classes += ' ' + options[:class] if options[:class]
|
10
|
+
classes
|
11
|
+
end
|
12
|
+
|
13
|
+
def ci_hidden_field(name, value = nil, options = {})
|
14
|
+
template.hidden_field_tag(name, value, options.merge(class: input_classes(options)))
|
15
|
+
end
|
16
|
+
|
17
|
+
def ci_text_field(name, value = nil, options = {})
|
18
|
+
template.text_field_tag(name, value, options.merge(class: input_classes(options)))
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Formtastic
|
2
|
+
module Inputs
|
3
|
+
class ArrayInput
|
4
|
+
include Base
|
5
|
+
include InputHelpers
|
6
|
+
|
7
|
+
def to_html
|
8
|
+
@undoable = options[:undoable]
|
9
|
+
input_wrapping do
|
10
|
+
inputs = []
|
11
|
+
|
12
|
+
values = @object.send(method)
|
13
|
+
if values
|
14
|
+
values.each_with_index do |v, x|
|
15
|
+
inputs << array_input_html(v)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
label_html <<
|
20
|
+
template.content_tag(:div, class: 'input-group--array') do
|
21
|
+
inputs.join.html_safe <<
|
22
|
+
array_input_html('', false) <<
|
23
|
+
array_input_html('', true, true) # template for creating new array input field
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def array_input_html(value, remove = true, hidden = false)
|
31
|
+
icon_name = remove ? 'minus-circle' : 'plus-circle'
|
32
|
+
button_classes = remove ? 'array-action--remove js-remove-from-array-input' : 'array-action--add js-add-to-array-input'
|
33
|
+
button_classes += ' undoable' if @undoable
|
34
|
+
|
35
|
+
button = template.content_tag(:button,
|
36
|
+
icon_tag(icon_name),
|
37
|
+
class: button_classes,
|
38
|
+
type: 'button')
|
39
|
+
|
40
|
+
wrapper_classes = 'input-group--array__item'
|
41
|
+
wrapper_classes += ' hidden template' if hidden
|
42
|
+
|
43
|
+
template.content_tag(:div, class: wrapper_classes) do
|
44
|
+
ci_text_field("#{object_name}[#{method}][]", value, id: nil, disabled: hidden) << button
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
module Formtastic
|
2
|
+
module Inputs
|
3
|
+
class ListInput
|
4
|
+
include Base
|
5
|
+
include InputHelpers
|
6
|
+
|
7
|
+
def has_quantity?
|
8
|
+
if @has_quantity.nil?
|
9
|
+
@has_quantity = options[:has_quantity]
|
10
|
+
@has_quantity = true if @has_quantity.nil?
|
11
|
+
end
|
12
|
+
@has_quantity
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_html
|
16
|
+
fields = options[:fields] # [{'Header Text': :key}... ]
|
17
|
+
values = @object ? @object.send(method) : nil
|
18
|
+
|
19
|
+
input_wrapping do
|
20
|
+
label_html <<
|
21
|
+
template.content_tag(:div, class: 'item-quantity-group') do
|
22
|
+
tags = select_html
|
23
|
+
tags << quantity_html if has_quantity?
|
24
|
+
tags << buttons(false)
|
25
|
+
end <<
|
26
|
+
template.content_tag(:div, nil, class: 'item-list-container') do
|
27
|
+
# Table
|
28
|
+
template.content_tag(:table, class: 'item-table') do
|
29
|
+
# Headers
|
30
|
+
template.content_tag(:tr) do
|
31
|
+
headers = fields.map {|field|
|
32
|
+
header = field.first[0]
|
33
|
+
template.content_tag(:th, header.to_s)
|
34
|
+
}.join.html_safe
|
35
|
+
# Quantity header
|
36
|
+
headers << template.content_tag(:th, 'Quantity') if has_quantity?
|
37
|
+
# Button header
|
38
|
+
headers << template.content_tag(:th)
|
39
|
+
end <<
|
40
|
+
table_row << #hidden template row for js use.
|
41
|
+
if values
|
42
|
+
values.map {|value| table_row(false, value)}.join.html_safe
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def table_row(hidden = true, item = nil)
|
52
|
+
# Generating sample hidden row
|
53
|
+
unique_field = options[:unique_field] || 'id'
|
54
|
+
unique_value = item
|
55
|
+
quantity = nil
|
56
|
+
if item and has_quantity?
|
57
|
+
unique_value = item[unique_field.to_s]
|
58
|
+
quantity = item['quantity']
|
59
|
+
end
|
60
|
+
|
61
|
+
item_data = options[:collection_object].find_by("#{unique_field}": unique_value)
|
62
|
+
item_data ||= {}
|
63
|
+
fields = options[:fields]
|
64
|
+
|
65
|
+
css_classes = 'item-row'
|
66
|
+
css_classes += ' hidden' if hidden
|
67
|
+
template.content_tag(:tr, class: css_classes, id: "row_#{item_data[:id]}") do
|
68
|
+
cells = fields.map {|field|
|
69
|
+
key = field.first[1]
|
70
|
+
cell = template.content_tag(:td, "", class: "#{key}-cell", field: key.to_s) do
|
71
|
+
item_data[key].to_s
|
72
|
+
end
|
73
|
+
cell << hidden_field(key, true, item_data[key]) if has_quantity?
|
74
|
+
cell
|
75
|
+
}.join.html_safe
|
76
|
+
|
77
|
+
if has_quantity?
|
78
|
+
# Quantity cell
|
79
|
+
cells << template.content_tag(:td, class: 'quantity-cell') do
|
80
|
+
quantity
|
81
|
+
end
|
82
|
+
cells << hidden_field('quantity', false, quantity)
|
83
|
+
else
|
84
|
+
cells << hidden_field(unique_field, false, unique_value)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Button cell
|
88
|
+
cells << template.content_tag(:td, width: '26px') do
|
89
|
+
buttons(true)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def select_html
|
95
|
+
template.select_tag('', template.options_from_collection_for_select(options[:collection_object], 'to_json', options[:to_s_method] || 'to_s'), input_html_options.merge({class: 'item-item', name: ''}))
|
96
|
+
end
|
97
|
+
|
98
|
+
def quantity_html
|
99
|
+
builder.number_field('', input_html_options.merge({class: 'item-quantity', value: 1, name: ''}))
|
100
|
+
end
|
101
|
+
|
102
|
+
def hidden_field(key, insert_field_attr = true, value = nil)
|
103
|
+
options = {class: "hidden-#{key}"}
|
104
|
+
options['field'] = key.to_s if insert_field_attr
|
105
|
+
|
106
|
+
if has_quantity?
|
107
|
+
# When with quantity build an array of objects with fields given in options.
|
108
|
+
# template.hidden_field_tag("#{object_name}[#{method}][][#{key}]", value = value, options = options)
|
109
|
+
ci_hidden_field("#{object_name}[#{method}][][#{key}]", value = value, options = options)
|
110
|
+
else
|
111
|
+
# When without quantity build an array of IDs
|
112
|
+
ci_hidden_field("#{object_name}[#{method}][]", value = value, options = options)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def buttons(remove = true)
|
117
|
+
if remove
|
118
|
+
button = template.content_tag(:button, icon_tag('minus-circle'), class: 'array-action--remove js-remove-from-items-input', type: 'button')
|
119
|
+
else
|
120
|
+
button = template.content_tag(:button, icon_tag('plus-circle'), class: 'array-action--add js-add-to-items-input', type: 'button')
|
121
|
+
end
|
122
|
+
button
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
metadata
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: custom_inputs
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Hasan Ali Ayar
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-03-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '13.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '13.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
description: ''
|
56
|
+
email:
|
57
|
+
- hasan.a.ayar@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- ".rspec"
|
64
|
+
- ".travis.yml"
|
65
|
+
- CODE_OF_CONDUCT.md
|
66
|
+
- Gemfile
|
67
|
+
- Gemfile.lock
|
68
|
+
- LICENSE.txt
|
69
|
+
- README.md
|
70
|
+
- Rakefile
|
71
|
+
- app/assets/javascripts/custom_inputs/array-input.js
|
72
|
+
- app/assets/javascripts/custom_inputs/custom_inputs.js
|
73
|
+
- app/assets/javascripts/custom_inputs/list-input.js
|
74
|
+
- app/assets/stylesheets/custom_inputs/array_input.scss
|
75
|
+
- app/assets/stylesheets/custom_inputs/list_input.scss
|
76
|
+
- app/assets/stylesheets/custom_inputs/variables.scss
|
77
|
+
- app/views/custom_inputs/_list.html.erb
|
78
|
+
- bin/console
|
79
|
+
- bin/setup
|
80
|
+
- custom_inputs.gemspec
|
81
|
+
- lib/custom_inputs.rb
|
82
|
+
- lib/custom_inputs/version.rb
|
83
|
+
- lib/formtastic/input_helpers.rb
|
84
|
+
- lib/formtastic/inputs/array_input.rb
|
85
|
+
- lib/formtastic/inputs/list_input.rb
|
86
|
+
homepage: https://github.com/hasan-aa/custom_inputs
|
87
|
+
licenses:
|
88
|
+
- MIT
|
89
|
+
metadata:
|
90
|
+
allowed_push_host: https://rubygems.org/
|
91
|
+
homepage_uri: https://github.com/hasan-aa/custom_inputs
|
92
|
+
source_code_uri: https://github.com/hasan-aa/custom_inputs
|
93
|
+
post_install_message:
|
94
|
+
rdoc_options: []
|
95
|
+
require_paths:
|
96
|
+
- lib
|
97
|
+
- app
|
98
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
105
|
+
- - ">="
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '0'
|
108
|
+
requirements: []
|
109
|
+
rubygems_version: 3.0.3
|
110
|
+
signing_key:
|
111
|
+
specification_version: 4
|
112
|
+
summary: Custom form input fields for active admin + formtastic.
|
113
|
+
test_files: []
|