frontpack 0.1.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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +51 -0
- data/Rakefile +5 -0
- data/app/assets/config/frontpack_manifest.js +1 -0
- data/app/assets/images/logo.svg +141 -0
- data/app/assets/javascripts/frontpack/auto-complete.js +173 -0
- data/app/assets/javascripts/frontpack/base-webcomponent.js +160 -0
- data/app/assets/javascripts/frontpack/expandable-list.js +99 -0
- data/app/assets/javascripts/frontpack/image-preview.js +160 -0
- data/app/assets/javascripts/frontpack/image-uploader.js +34 -0
- data/app/assets/javascripts/frontpack/localized-time.js +17 -0
- data/app/assets/javascripts/frontpack/modal-dialog.js +206 -0
- data/app/assets/javascripts/frontpack/shortcuts.js +63 -0
- data/app/assets/javascripts/frontpack/tab-control.js +77 -0
- data/app/assets/javascripts/frontpack/text-box.js +70 -0
- data/app/assets/javascripts/frontpack/toggle-button.js +73 -0
- data/app/assets/stylesheets/frontpack/000-reset.scss +15 -0
- data/app/assets/stylesheets/frontpack/010-config.scss +148 -0
- data/app/assets/stylesheets/frontpack/020-mixins.scss +218 -0
- data/app/assets/stylesheets/frontpack/030-consts.scss +13 -0
- data/app/assets/stylesheets/frontpack/031-margins.scss +5 -0
- data/app/assets/stylesheets/frontpack/100-layout.scss +113 -0
- data/app/assets/stylesheets/frontpack/101-container.scss +131 -0
- data/app/assets/stylesheets/frontpack/200-design.scss +75 -0
- data/app/assets/stylesheets/frontpack/201-box.scss +6 -0
- data/app/assets/stylesheets/frontpack/202-borders.scss +5 -0
- data/app/assets/stylesheets/frontpack/300-typography.scss +128 -0
- data/app/assets/stylesheets/frontpack/800-components.scss +85 -0
- data/app/assets/stylesheets/frontpack/801-card.scss +53 -0
- data/app/assets/stylesheets/frontpack/802-buttons.scss +58 -0
- data/app/assets/stylesheets/frontpack/803-form-controls.scss +161 -0
- data/app/assets/stylesheets/frontpack/804-table.scss +60 -0
- data/app/assets/stylesheets/frontpack/805-switch-toggle.scss +43 -0
- data/app/assets/stylesheets/frontpack/frontpack.scss +18 -0
- data/app/controllers/concerns/frontpack/searchable.rb +33 -0
- data/app/controllers/frontpack/autocomplete_controller.rb +33 -0
- data/app/helpers/frontpack/application_helper.rb +7 -0
- data/app/helpers/frontpack/form_builder.rb +130 -0
- data/app/models/concerns/frontpack/auto_completable.rb +35 -0
- data/config/importmap.rb +1 -0
- data/config/routes.rb +3 -0
- data/lib/frontpack/button_to_patch.rb +64 -0
- data/lib/frontpack/engine.rb +28 -0
- data/lib/frontpack/extended_model_translations.rb +31 -0
- data/lib/frontpack/form_builder_options.rb +5 -0
- data/lib/frontpack/version.rb +5 -0
- data/lib/frontpack.rb +9 -0
- data/lib/generators/frontpack/install_generator.rb +37 -0
- data/lib/generators/frontpack/locale_generator.rb +11 -0
- data/lib/generators/templates/initializers/customize_form_with_errors.rb +31 -0
- data/lib/generators/templates/locales/frontpack.en.yml +43 -0
- data/lib/generators/templates/views/layouts/_form-errors.html.slim +3 -0
- data/lib/generators/templates/views/layouts/_navigation.html.slim +1 -0
- data/lib/generators/templates/views/layouts/application.html.slim +37 -0
- data/lib/generators/templates/views/layouts/errors.html.slim +19 -0
- data/lib/generators/templates/views/layouts/mailer.html.slim +6 -0
- data/lib/generators/templates/views/layouts/mailer.text.slim +1 -0
- data/lib/tasks/frontpack_tasks.rake +6 -0
- data/lib/templates/rails/file_utils/searchable.rb +30 -0
- data/lib/templates/rails/scaffold_controller/controller.rb.tt +93 -0
- data/lib/templates/slim/scaffold/_form.html.slim.tt +10 -0
- data/lib/templates/slim/scaffold/edit.html.slim.tt +10 -0
- data/lib/templates/slim/scaffold/index.html.slim.tt +20 -0
- data/lib/templates/slim/scaffold/new.html.slim.tt +8 -0
- data/lib/templates/slim/scaffold/partial.html.slim.tt +6 -0
- data/lib/templates/slim/scaffold/show.html.slim.tt +12 -0
- data/lib/templates/test_unit/model/fixtures.yml.tt +18 -0
- data/lib/templates/test_unit/model/unit_test.rb.tt +41 -0
- metadata +143 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: df0faa6eaf72f4c463429cf921ca5b4787a86a824b495605e2749ef02808099a
|
4
|
+
data.tar.gz: 4196fb91c2dfdf7ffb84cb3fb51e69d6e066e204dc3f216e1f8968ea2deb917b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0a6b40d4a9068b6b320db01776623f4b3fff5366c3926efb301bd5cd2e0e5c7c0c1a39aa9f967393f2a9e27e06ee3703e0719d9cffd1b46eb341189725c7f6c1
|
7
|
+
data.tar.gz: 479e6c6cf85251fb224891e46213a6552d67d4bd074bbd493a952bf6acfd558ce44e3de972886434110887c1f17b92068af7b83cc3ec98e4c02676c05a22bc14
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright Francis Schiavo
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# Frontpack
|
2
|
+
|
3
|
+
This project aims to provide a standard front-end development environment for Ruby on Rails applications.
|
4
|
+
|
5
|
+
Key features:
|
6
|
+
* New form builder with options for enum fields, toggle fields, and autocomplete fields.
|
7
|
+
* New methods for ActiveRecord models to easily translate enum field names and values.
|
8
|
+
* New methods for ActiveRecord models to easily expose autocomplete endpoints.
|
9
|
+
* Integrated sass files for customizing the front end (kinda of a mix including some boostrap and tailwind features).
|
10
|
+
* Integrated javascript files and web components for customizing the front end and form builder.
|
11
|
+
|
12
|
+
## Goals
|
13
|
+
|
14
|
+
Allow even faster development of Rails applications by adding more features for front-end.
|
15
|
+
Some of the features will be simple extensions for Rails form builders.
|
16
|
+
|
17
|
+
Other features will be more complex, like having a standard CSS lib and web components. The idea is to provide a high degree
|
18
|
+
of customization for the front-end, while keeping a standard development process.
|
19
|
+
|
20
|
+
Realistically this project will not replace the need for a project specific front-end for larger projects, but it can
|
21
|
+
be a good alternative for internal use applications where faster development time matters more than a creative and unique UI.
|
22
|
+
|
23
|
+
Also, this project will likely be a lot more open to contributions since the idea is to provide a wide range of options
|
24
|
+
out of the box. As long as a feature is not too specific it will be welcome here.
|
25
|
+
|
26
|
+
Keep in mind this project will favor faster development over a faster runtime. That means we won't bother losing a few
|
27
|
+
milliseconds of page load time if that means we can save a few hours of development time.
|
28
|
+
|
29
|
+
## Setup
|
30
|
+
|
31
|
+
Run `bundle add frontpack`
|
32
|
+
|
33
|
+
Or manually add this line to your application's Gemfile:
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
gem 'frontpack'
|
37
|
+
```
|
38
|
+
|
39
|
+
And then execute:
|
40
|
+
```bash
|
41
|
+
$ bundle
|
42
|
+
```
|
43
|
+
|
44
|
+
## TODO
|
45
|
+
Possibly remove the embedded css files and make it a plugin for this gem. It already works fine with Bootstrap.
|
46
|
+
|
47
|
+
## Contributing
|
48
|
+
WIP
|
49
|
+
|
50
|
+
## License
|
51
|
+
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 @@
|
|
1
|
+
//= link_tree ../javascripts/frontpack .js
|
@@ -0,0 +1,141 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
2
|
+
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
3
|
+
|
4
|
+
<svg
|
5
|
+
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
|
6
|
+
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
7
|
+
xmlns:cc="http://creativecommons.org/ns#"
|
8
|
+
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
9
|
+
xmlns="http://www.w3.org/2000/svg"
|
10
|
+
xmlns:xlink="http://www.w3.org/1999/xlink"
|
11
|
+
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
12
|
+
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
13
|
+
width="200"
|
14
|
+
height="120"
|
15
|
+
viewBox="0 0 52.916665 31.750001"
|
16
|
+
version="1.1"
|
17
|
+
id="svg8"
|
18
|
+
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
|
19
|
+
sodipodi:docname="logo.svg">
|
20
|
+
<defs
|
21
|
+
id="defs2">
|
22
|
+
<linearGradient
|
23
|
+
id="linearGradient899"
|
24
|
+
osb:paint="solid">
|
25
|
+
<stop
|
26
|
+
style="stop-color:#000000;stop-opacity:1;"
|
27
|
+
offset="0"
|
28
|
+
id="stop897" />
|
29
|
+
</linearGradient>
|
30
|
+
<linearGradient
|
31
|
+
inkscape:collect="always"
|
32
|
+
xlink:href="#linearGradient899"
|
33
|
+
id="linearGradient901"
|
34
|
+
x1="3.5352253"
|
35
|
+
y1="276.21649"
|
36
|
+
x2="46.7946"
|
37
|
+
y2="276.21649"
|
38
|
+
gradientUnits="userSpaceOnUse" />
|
39
|
+
</defs>
|
40
|
+
<sodipodi:namedview
|
41
|
+
id="base"
|
42
|
+
pagecolor="#ffffff"
|
43
|
+
bordercolor="#666666"
|
44
|
+
borderopacity="1.0"
|
45
|
+
inkscape:pageopacity="0.0"
|
46
|
+
inkscape:pageshadow="2"
|
47
|
+
inkscape:zoom="3.959798"
|
48
|
+
inkscape:cx="113.07665"
|
49
|
+
inkscape:cy="45.395589"
|
50
|
+
inkscape:document-units="mm"
|
51
|
+
inkscape:current-layer="layer1"
|
52
|
+
showgrid="false"
|
53
|
+
units="px"
|
54
|
+
inkscape:pagecheckerboard="true"
|
55
|
+
inkscape:window-width="1920"
|
56
|
+
inkscape:window-height="1015"
|
57
|
+
inkscape:window-x="0"
|
58
|
+
inkscape:window-y="0"
|
59
|
+
inkscape:window-maximized="1" />
|
60
|
+
<metadata
|
61
|
+
id="metadata5">
|
62
|
+
<rdf:RDF>
|
63
|
+
<cc:Work
|
64
|
+
rdf:about="">
|
65
|
+
<dc:format>image/svg+xml</dc:format>
|
66
|
+
<dc:type
|
67
|
+
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
68
|
+
<dc:title></dc:title>
|
69
|
+
</cc:Work>
|
70
|
+
</rdf:RDF>
|
71
|
+
</metadata>
|
72
|
+
<g
|
73
|
+
inkscape:label="Layer 1"
|
74
|
+
inkscape:groupmode="layer"
|
75
|
+
id="layer1"
|
76
|
+
transform="translate(0,-265.24998)">
|
77
|
+
<path
|
78
|
+
style="clip-rule:evenodd;fill:#d40000;fill-rule:evenodd;stroke-width:0.20672259"
|
79
|
+
d="m 19.732038,290.78116 c 5.677843,0.77521 11.254806,1.53656 16.942571,2.3128 -1.937817,-3.22611 -3.825815,-6.36975 -5.728902,-9.53899 z"
|
80
|
+
id="path927" />
|
81
|
+
<path
|
82
|
+
style="clip-rule:evenodd;fill:#aa0000;fill-rule:evenodd;stroke-width:0.20672259"
|
83
|
+
d="m 38.277123,272.92384 c -0.500476,0.75165 -1.001985,1.50266 -1.50184,2.25472 -1.716624,2.58258 -3.433041,5.16517 -5.148013,7.749 -0.0955,0.14387 -0.219332,0.25799 -0.08475,0.47981 1.657089,2.73637 3.301154,5.48083 4.949353,8.22279 0.260057,0.43308 0.523009,0.86452 0.859347,1.28004 0.333236,-6.65503 0.666266,-13.31025 0.999296,-19.96528 z"
|
84
|
+
id="path925" />
|
85
|
+
<path
|
86
|
+
style="clip-rule:evenodd;fill:#d40000;fill-rule:evenodd;stroke-width:0.20672259"
|
87
|
+
d="m 15.655675,281.4559 c 0.07753,0.0733 0.271013,0.11617 0.361145,0.0717 1.58949,-0.7812 3.189109,-1.54504 4.74387,-2.39054 0.50709,-0.27556 0.896969,-0.76963 1.333773,-1.17026 1.446439,-1.32653 2.890603,-2.65575 4.334147,-3.9852 0.08828,-0.0817 0.192873,-0.16062 0.245587,-0.26357 0.51846,-1.01335 1.028031,-2.03188 1.555587,-3.07975 -0.629057,-0.23649 -1.225451,-0.46781 -1.829288,-0.67825 -0.08187,-0.0286 -0.210857,0.0282 -0.299542,0.0774 -1.39765,0.7808 -2.821556,1.52004 -4.174761,2.37152 -0.677017,0.42606 -1.228554,1.05388 -1.828049,1.60067 -0.966221,0.88187 -1.931204,1.76582 -2.883366,2.66299 -0.252615,0.23815 -0.482904,0.5102 -0.679704,0.79671 -0.651177,0.94824 -1.282921,1.91011 -1.943607,2.89928 0.369207,0.38183 0.704924,0.74688 1.064208,1.08716 z"
|
88
|
+
id="path923" />
|
89
|
+
<path
|
90
|
+
style="clip-rule:evenodd;fill:#aa0000;fill-rule:evenodd;stroke-width:0.20672259"
|
91
|
+
d="m 21.464994,280.15955 c -0.790714,3.34042 -1.576673,6.66203 -2.388886,10.09281 3.80845,-2.45401 7.529043,-4.85137 11.225037,-7.23261 -2.94125,-0.95194 -5.872989,-1.90102 -8.836151,-2.8602 z"
|
92
|
+
id="path921" />
|
93
|
+
<path
|
94
|
+
style="clip-rule:evenodd;fill:#ff8080;fill-rule:evenodd;stroke-width:0.20672259"
|
95
|
+
d="m 37.306354,272.95609 c -3.217845,0.53768 -6.358788,1.06235 -9.540248,1.59383 1.08364,2.63593 2.145573,5.21891 3.237689,7.8753 2.112911,-3.17506 4.184066,-6.28643 6.302559,-9.46913 z"
|
96
|
+
id="path919" />
|
97
|
+
<path
|
98
|
+
style="clip-rule:evenodd;fill:#aa0000;fill-rule:evenodd;stroke-width:0.20672259"
|
99
|
+
d="m 21.59771,279.47633 c 2.89763,0.94058 5.737585,1.86255 8.660024,2.81142 -1.085294,-2.64399 -2.133378,-5.19556 -3.203168,-7.80109 -1.82226,1.66598 -3.617232,3.30736 -5.456856,4.98967 z"
|
100
|
+
id="path917" />
|
101
|
+
<path
|
102
|
+
style="clip-rule:evenodd;fill:#ff2a2a;fill-rule:evenodd;stroke-width:0.20672259"
|
103
|
+
d="m 15.775368,283.40385 c -1.027618,2.45648 -2.0203,4.82944 -3.030968,7.2452 1.942367,-0.0575 3.825195,-0.11301 5.772729,-0.17054 -0.914335,-2.35911 -1.810683,-4.67193 -2.741761,-7.07466 z"
|
104
|
+
id="path915" />
|
105
|
+
<path
|
106
|
+
style="clip-rule:evenodd;fill:#aa0000;fill-rule:evenodd;stroke-width:0.20672259"
|
107
|
+
d="m 18.585347,288.79848 0.07153,-0.0156 c 0.693141,-2.88193 1.396205,-5.74401 2.103816,-8.73858 -1.578535,0.82048 -3.084302,1.60293 -4.631207,2.40707 0.789474,2.05192 1.631249,4.21651 2.455865,6.34722 z"
|
108
|
+
id="path913" />
|
109
|
+
<path
|
110
|
+
style="clip-rule:evenodd;fill:#aa0000;fill-rule:evenodd;stroke-width:0.20672259"
|
111
|
+
d="m 36.141679,272.48868 c -0.921776,-0.24414 -1.843553,-0.4891 -2.765742,-0.73158 -1.325505,-0.34854 -2.652045,-0.69274 -3.976723,-1.04437 -0.165585,-0.0441 -0.285278,-0.0728 -0.382644,0.12674 -0.468227,0.95919 -0.950923,1.91115 -1.426592,2.86683 -0.01468,0.0295 -0.01153,0.0678 -0.02294,0.14202 2.870551,-0.43495 5.721875,-0.86721 8.572786,-1.29926 z"
|
112
|
+
id="path911" />
|
113
|
+
<path
|
114
|
+
style="clip-rule:evenodd;fill:#ff0000;fill-rule:evenodd;stroke-width:0.20672259"
|
115
|
+
d="m 30.793971,270.30693 7.51292,2.0784 c -0.43081,-1.22774 -0.830404,-2.36615 -1.241369,-3.53703 l -6.260593,1.38815 z"
|
116
|
+
id="path909" />
|
117
|
+
<path
|
118
|
+
style="clip-rule:evenodd;fill:#aa0000;fill-rule:evenodd;stroke-width:0.20672259"
|
119
|
+
d="m 19.222261,291.33456 c -0.930458,-0.10725 -1.885725,-0.008 -2.829413,0.008 -0.77521,0.0137 -1.55042,0.0426 -2.32563,0.0669 -0.07979,0.003 -0.159382,0.0185 -0.238971,0.10463 6.409022,0.59247 12.817836,1.18493 19.22665,1.77782 l 0.01304,-0.0855 c -2.054408,-0.28051 -4.109023,-0.56104 -6.163434,-0.84135 -2.560052,-0.34957 -5.115763,-0.73408 -7.682224,-1.0299 z"
|
120
|
+
id="path907" />
|
121
|
+
<path
|
122
|
+
style="clip-rule:evenodd;fill:#aa0000;fill-rule:evenodd;stroke-width:0.20672259"
|
123
|
+
d="m 12.871122,288.43775 c 0.825857,-1.91632 1.648198,-3.83388 2.477364,-5.74874 0.08289,-0.19206 0.07648,-0.3225 -0.08579,-0.47712 -0.346881,-0.33014 -0.669161,-0.68592 -1.048291,-1.08033 -0.512464,2.53028 -1.012321,4.99793 -1.512589,7.46537 l 0.05602,0.0262 c 0.03824,-0.0614 0.08497,-0.11946 0.113283,-0.18543 z"
|
124
|
+
id="path905" />
|
125
|
+
<path
|
126
|
+
style="clip-rule:evenodd;fill:#aa0000;fill-rule:evenodd;stroke-width:0.20672259"
|
127
|
+
d="m 29.14846,269.80108 c 1.392689,-0.35927 2.805639,-0.64021 4.210318,-0.9536 0.09096,-0.0205 0.179849,-0.0505 0.26936,-0.0761 l -0.01386,-0.0687 c -1.978541,0.26606 -3.956876,0.53232 -6.035059,0.81201 0.566627,0.24744 1.003225,0.43226 1.569231,0.28631 z"
|
128
|
+
id="path81" />
|
129
|
+
<text
|
130
|
+
xml:space="preserve"
|
131
|
+
style="font-style:italic;font-variant:normal;font-weight:500;font-stretch:normal;font-size:19.75555611px;line-height:1.25;font-family:'URW Chancery L';-inkscape-font-specification:'URW Chancery L, Medium Italic';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ff5555;fill-opacity:1;stroke:#ffffff;stroke-width:0.26458332;stroke-opacity:1"
|
132
|
+
x="-1.0690777"
|
133
|
+
y="284.57193"
|
134
|
+
id="text12"><tspan
|
135
|
+
sodipodi:role="line"
|
136
|
+
id="tspan10"
|
137
|
+
x="-1.0690777"
|
138
|
+
y="284.57193"
|
139
|
+
style="font-style:italic;font-variant:normal;font-weight:500;font-stretch:normal;font-size:19.75555611px;font-family:'URW Chancery L';-inkscape-font-specification:'URW Chancery L, Medium Italic';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#ff5555;stroke:#ffffff;stroke-width:0.26458332;stroke-opacity:1">Genesis</tspan></text>
|
140
|
+
</g>
|
141
|
+
</svg>
|
@@ -0,0 +1,173 @@
|
|
1
|
+
class AutoComplete extends HTMLInputElement {
|
2
|
+
constructor() {
|
3
|
+
super();
|
4
|
+
}
|
5
|
+
|
6
|
+
connectedCallback() {
|
7
|
+
this.addEventListener('keydown', this.onKeyDown.bind(this));
|
8
|
+
this.addEventListener('keyup', this.onKeyUp.bind(this));
|
9
|
+
this.addEventListener('blur', this.onBlur.bind(this));
|
10
|
+
|
11
|
+
this.hiddenInput = document.createElement('input');
|
12
|
+
this.hiddenInput.type = 'hidden';
|
13
|
+
this.hiddenInput.name = this.name;
|
14
|
+
this.hiddenInput.value = this.value;
|
15
|
+
this.parentElement.appendChild(this.hiddenInput);
|
16
|
+
this.removeAttribute('name');
|
17
|
+
this.value = this.getAttribute('display-value');
|
18
|
+
|
19
|
+
this.items = document.createElement('ul');
|
20
|
+
this.items.style.display = 'none';
|
21
|
+
this.items.style.position = 'absolute';
|
22
|
+
this.items.className = this.autocompleteStyle || 'auto-complete-list';
|
23
|
+
this.items.addEventListener('click', this.onItemClick.bind(this));
|
24
|
+
|
25
|
+
this.parentElement.appendChild(this.items);
|
26
|
+
this.lastUsedTerm = '';
|
27
|
+
}
|
28
|
+
|
29
|
+
set selectedItemIndex(value) {
|
30
|
+
if (value >= this.items.childNodes.length) {
|
31
|
+
this._selectedItemIndex = 0
|
32
|
+
} else if (value < 0) {
|
33
|
+
this._selectedItemIndex = this.items.childNodes.length - 1;
|
34
|
+
} else {
|
35
|
+
this._selectedItemIndex = value;
|
36
|
+
}
|
37
|
+
this.updateSelection();
|
38
|
+
}
|
39
|
+
|
40
|
+
async fetchData() {
|
41
|
+
this.lastUsedTerm = this.value;
|
42
|
+
let response = await fetch(`${this.url}/${encodeURIComponent(this.value)}`, {
|
43
|
+
cache: 'reload',
|
44
|
+
headers: {'Content-Type': 'application/json'},
|
45
|
+
mode: 'cors',
|
46
|
+
credentials: 'same-origin'
|
47
|
+
});
|
48
|
+
let data = await response.json();
|
49
|
+
this.updateList(data);
|
50
|
+
}
|
51
|
+
|
52
|
+
updateListPosition() {
|
53
|
+
let rect = this.getBoundingClientRect();
|
54
|
+
this.items.style.width = rect.width + 'px';
|
55
|
+
this.items.style.left = rect.left + 'px';
|
56
|
+
this.items.style.top = rect.bottom + 'px';
|
57
|
+
|
58
|
+
}
|
59
|
+
|
60
|
+
updateList(data) {
|
61
|
+
this.items.innerHTML = '';
|
62
|
+
for (let item of data) {
|
63
|
+
let li = document.createElement('li');
|
64
|
+
li.dataset.key = item.id;
|
65
|
+
li.dataset.value = item.value;
|
66
|
+
li.innerText = item.label || item.value;
|
67
|
+
this.items.appendChild(li);
|
68
|
+
}
|
69
|
+
this.selectedItemIndex = 0;
|
70
|
+
this.items.style.display = 'inline-block';
|
71
|
+
this.updateListPosition();
|
72
|
+
}
|
73
|
+
|
74
|
+
onBlur() {
|
75
|
+
if (this._timeout) {
|
76
|
+
window.clearTimeout(this._timeout);
|
77
|
+
}
|
78
|
+
|
79
|
+
window.setTimeout(_ => { this.items.style.display = 'none'; }, 100)
|
80
|
+
}
|
81
|
+
|
82
|
+
get url() {
|
83
|
+
return this.dataset.url;
|
84
|
+
}
|
85
|
+
|
86
|
+
get autocompleteStyle() {
|
87
|
+
return this.getAttribute('autocomplete-style');
|
88
|
+
}
|
89
|
+
|
90
|
+
get selectedItemIndex() {
|
91
|
+
return this._selectedItemIndex;
|
92
|
+
}
|
93
|
+
|
94
|
+
previousItem() {
|
95
|
+
this.selectedItemIndex--;
|
96
|
+
}
|
97
|
+
|
98
|
+
nextItem() {
|
99
|
+
this.selectedItemIndex++;
|
100
|
+
}
|
101
|
+
|
102
|
+
updateSelection() {
|
103
|
+
if (this.selectedItem) {
|
104
|
+
this.selectedItem.classList.remove('selected');
|
105
|
+
}
|
106
|
+
this.selectedItem = this.items.childNodes[this.selectedItemIndex];
|
107
|
+
this.selectedItem.classList.add('selected');
|
108
|
+
this.previewItem(this.selectedItem);
|
109
|
+
}
|
110
|
+
|
111
|
+
previewItem(item) {
|
112
|
+
this.value = item.innerText;
|
113
|
+
this.hiddenInput.value = item.dataset.key;
|
114
|
+
}
|
115
|
+
|
116
|
+
selectItem(item) {
|
117
|
+
this.value = item.innerText;
|
118
|
+
this.hiddenInput.value = item.dataset.key;
|
119
|
+
this.items.style.display = 'none';
|
120
|
+
}
|
121
|
+
|
122
|
+
onKeyDown(event) {
|
123
|
+
let key = event.code.toLowerCase();
|
124
|
+
if (['enter'].includes(key)) {
|
125
|
+
event.preventDefault();
|
126
|
+
event.stopPropagation();
|
127
|
+
}
|
128
|
+
}
|
129
|
+
|
130
|
+
onKeyUp(event) {
|
131
|
+
let key = event.code.toLowerCase();
|
132
|
+
if (['arrowdown', 'enter', 'arrowup', 'insert', 'tab'].includes(key)) {
|
133
|
+
event.preventDefault();
|
134
|
+
event.stopPropagation();
|
135
|
+
if (key === 'arrowdown') {
|
136
|
+
this.nextItem();
|
137
|
+
} else if (key === 'arrowup') {
|
138
|
+
this.previousItem();
|
139
|
+
} else if (key === 'enter') {
|
140
|
+
if (this.value !== '') {
|
141
|
+
this.selectItem(this.selectedItem);
|
142
|
+
} else {
|
143
|
+
this.value = '';
|
144
|
+
this.hiddenInput.value = '';
|
145
|
+
}
|
146
|
+
}
|
147
|
+
} else {
|
148
|
+
if (this.value === this.lastUsedTerm) {
|
149
|
+
return
|
150
|
+
}
|
151
|
+
|
152
|
+
this.items.style.display = 'none';
|
153
|
+
|
154
|
+
if (this._timeout) {
|
155
|
+
window.clearTimeout(this._timeout);
|
156
|
+
}
|
157
|
+
|
158
|
+
if (this.value.length < this.minLength) {
|
159
|
+
return;
|
160
|
+
}
|
161
|
+
|
162
|
+
this._timeout = window.setTimeout(this.fetchData.bind(this), 500);
|
163
|
+
}
|
164
|
+
}
|
165
|
+
|
166
|
+
onItemClick(event) {
|
167
|
+
let item = event.target;
|
168
|
+
if (item.tagName.toLowerCase() === 'li') {
|
169
|
+
this.selectItem(item);
|
170
|
+
}
|
171
|
+
}
|
172
|
+
}
|
173
|
+
customElements.define('auto-complete', AutoComplete, { extends: 'input' });
|
@@ -0,0 +1,160 @@
|
|
1
|
+
class BaseWebComponent extends HTMLElement {
|
2
|
+
constructor() {
|
3
|
+
super();
|
4
|
+
this.bindableElements = new Map();
|
5
|
+
this.bindableAttributes = new Map();
|
6
|
+
this.attachShadow({mode: 'open'});
|
7
|
+
this.shadowRoot.innerHTML = this.template();
|
8
|
+
}
|
9
|
+
|
10
|
+
template() {
|
11
|
+
return '<slot></slot>';
|
12
|
+
}
|
13
|
+
|
14
|
+
domTreeLoaded() {
|
15
|
+
// Placeholder
|
16
|
+
}
|
17
|
+
|
18
|
+
raiseEvent(eventName, detail, cancelable = true, bubbles = true) {
|
19
|
+
let event = new CustomEvent(eventName, { bubbles: bubbles, cancelable: cancelable, detail: detail });
|
20
|
+
this.dispatchEvent(event);
|
21
|
+
}
|
22
|
+
|
23
|
+
/**
|
24
|
+
* This function registers "magic events".
|
25
|
+
* Magic events are special methods following a name pattern to auto register event listeners.
|
26
|
+
* The name pattern consists of the event trigger followed by a dollar sign and the method name.
|
27
|
+
* Pattern sample: click$sayHello
|
28
|
+
*/
|
29
|
+
registerEvents() {
|
30
|
+
for (let prop of Object.getOwnPropertyNames(Object.getPrototypeOf(this))) {
|
31
|
+
if (prop.indexOf('$') > 0 && typeof this[prop] === 'function') {
|
32
|
+
let [_, listener] = /^([a-z]+)\$.+/.exec(prop);
|
33
|
+
this.addEventListener(listener, this[prop].bind(this));
|
34
|
+
}
|
35
|
+
}
|
36
|
+
}
|
37
|
+
|
38
|
+
prepareBindableElements() {
|
39
|
+
for (let [attr, _] of this.bindableElements) {
|
40
|
+
// Save a reference to the bindable element
|
41
|
+
let element = this.shadowRoot.getElementById(attr);
|
42
|
+
if (!element) { throw new Error(`Element ${attr} not found.`); }
|
43
|
+
this.bindableElements.set(attr, element);
|
44
|
+
element.innerText = this.getAttribute(attr);
|
45
|
+
|
46
|
+
Object.defineProperty(this, attr, {
|
47
|
+
get() {
|
48
|
+
return this.getAttribute(attr);
|
49
|
+
},
|
50
|
+
set(value) {
|
51
|
+
this.setAttribute(attr, value);
|
52
|
+
let element = this.bindableElements.get(attr);
|
53
|
+
element.innerText = value;
|
54
|
+
}
|
55
|
+
})
|
56
|
+
}
|
57
|
+
}
|
58
|
+
|
59
|
+
prepareBindableAttributes(nodes) {
|
60
|
+
for (let node of nodes) {
|
61
|
+
if (node.nodeType !== 1) { continue; }
|
62
|
+
for (let attr of node.attributes) {
|
63
|
+
let result = /^%([a-z]+)$/.exec(attr.value);
|
64
|
+
if (!result) {
|
65
|
+
continue
|
66
|
+
}
|
67
|
+
let [_, name] = result
|
68
|
+
if (name) {
|
69
|
+
this.bindableAttributes.set(name, attr);
|
70
|
+
attr.value = this.getAttribute(name);
|
71
|
+
|
72
|
+
Object.defineProperty(this, name, {
|
73
|
+
get() {
|
74
|
+
return this.getAttribute(attr);
|
75
|
+
},
|
76
|
+
set(value) {
|
77
|
+
this.setAttribute(name, value);
|
78
|
+
let attribute = this.bindableAttributes.get(name);
|
79
|
+
attribute.value = value;
|
80
|
+
}
|
81
|
+
})
|
82
|
+
}
|
83
|
+
}
|
84
|
+
|
85
|
+
this.prepareBindableAttributes(node.childNodes);
|
86
|
+
}
|
87
|
+
}
|
88
|
+
|
89
|
+
/**
|
90
|
+
* Called by native code when this element is attached to the DOMTree
|
91
|
+
*/
|
92
|
+
connectedCallback() {
|
93
|
+
this.prepareBindableElements();
|
94
|
+
this.prepareBindableAttributes(this.shadowRoot.childNodes);
|
95
|
+
this.registerEvents();
|
96
|
+
if (document.readyState === 'interactive') {
|
97
|
+
this.domTreeLoaded();
|
98
|
+
} else {
|
99
|
+
document.addEventListener('readystatechange', event => {
|
100
|
+
if (event.target.readyState === 'interactive') {
|
101
|
+
this.domTreeLoaded();
|
102
|
+
}
|
103
|
+
})
|
104
|
+
}
|
105
|
+
}
|
106
|
+
|
107
|
+
defineBindableElement(...attrs) {
|
108
|
+
for (let attr of attrs) {
|
109
|
+
this.bindableElements.set(attr, null);
|
110
|
+
}
|
111
|
+
}
|
112
|
+
|
113
|
+
defineBindableAttribute(...attrs) {
|
114
|
+
for (let attr of attrs) {
|
115
|
+
this.bindableAttributes.set(attr, null);
|
116
|
+
}
|
117
|
+
}
|
118
|
+
|
119
|
+
_camelize(text) {
|
120
|
+
let words = text.split('-');
|
121
|
+
for (let i = 1; i < words.length; i++) {
|
122
|
+
words[i] = words[i][0].toUpperCase() + words[i].slice(1);
|
123
|
+
}
|
124
|
+
return words.join('');
|
125
|
+
}
|
126
|
+
|
127
|
+
defineAttribute(...attrs) {
|
128
|
+
for (let prop of attrs) {
|
129
|
+
let name = '';
|
130
|
+
let defaultValue = null;
|
131
|
+
|
132
|
+
if (typeof prop === 'string') {
|
133
|
+
name = prop;
|
134
|
+
} else {
|
135
|
+
name = prop[0]
|
136
|
+
defaultValue = prop[1]
|
137
|
+
}
|
138
|
+
let propName = this._camelize(name);
|
139
|
+
|
140
|
+
Object.defineProperty(this, propName, {
|
141
|
+
get() {
|
142
|
+
return this.getAttribute(name) || defaultValue;
|
143
|
+
},
|
144
|
+
set(value) {
|
145
|
+
this.setAttribute(name, value);
|
146
|
+
}
|
147
|
+
})
|
148
|
+
}
|
149
|
+
}
|
150
|
+
|
151
|
+
findParentElement(tagName, node = this) {
|
152
|
+
if (node.parentNode.tagName.toUpperCase() === tagName.toUpperCase()) {
|
153
|
+
return node.parentNode;
|
154
|
+
} else {
|
155
|
+
this.findParentElement(tagName, node.parentNode);
|
156
|
+
}
|
157
|
+
}
|
158
|
+
}
|
159
|
+
|
160
|
+
module.exports = BaseWebComponent;
|
@@ -0,0 +1,99 @@
|
|
1
|
+
class ExpandableList extends HTMLElement {
|
2
|
+
constructor() {
|
3
|
+
super();
|
4
|
+
const shadowRoot = this.attachShadow({mode: 'open'});
|
5
|
+
shadowRoot.innerHTML = `
|
6
|
+
<style>
|
7
|
+
:host(*) {
|
8
|
+
display: grid;
|
9
|
+
grid-template-columns: 1fr;
|
10
|
+
grid-template-rows: min-content 1fr;
|
11
|
+
}
|
12
|
+
|
13
|
+
#label {
|
14
|
+
display: block;
|
15
|
+
padding: .5rem;
|
16
|
+
font-weight: bold;
|
17
|
+
}
|
18
|
+
|
19
|
+
#area {
|
20
|
+
overflow-x: auto;
|
21
|
+
}
|
22
|
+
|
23
|
+
h3 {
|
24
|
+
margin: 0;
|
25
|
+
}
|
26
|
+
|
27
|
+
</style>
|
28
|
+
<h3 id="label"></h3>
|
29
|
+
<div id="area">
|
30
|
+
<slot></slot>
|
31
|
+
</div>
|
32
|
+
`;
|
33
|
+
}
|
34
|
+
|
35
|
+
connectedCallback() {
|
36
|
+
this.label = this.shadowRoot.getElementById('label');
|
37
|
+
this.label.innerText = this.getAttribute('caption');
|
38
|
+
}
|
39
|
+
}
|
40
|
+
|
41
|
+
customElements.define('expandable-list', ExpandableList);
|
42
|
+
|
43
|
+
class ExpandableItem extends HTMLElement {
|
44
|
+
constructor() {
|
45
|
+
super();
|
46
|
+
const shadowRoot = this.attachShadow({mode: 'open'});
|
47
|
+
shadowRoot.innerHTML = `
|
48
|
+
<style>
|
49
|
+
#label {
|
50
|
+
display: block;
|
51
|
+
padding: .5rem;
|
52
|
+
font-weight: bold;
|
53
|
+
cursor: pointer;
|
54
|
+
}
|
55
|
+
</style>
|
56
|
+
<span id="label"></span>
|
57
|
+
<div id="area">
|
58
|
+
<slot></slot>
|
59
|
+
</div>
|
60
|
+
`;
|
61
|
+
}
|
62
|
+
|
63
|
+
connectedCallback() {
|
64
|
+
this.label = this.shadowRoot.getElementById('label');
|
65
|
+
this.label.innerText = this.getAttribute('caption');
|
66
|
+
this.label.addEventListener('click', this.toggle.bind(this));
|
67
|
+
this.area = this.shadowRoot.getElementById('area');
|
68
|
+
this.expanded = this.hasAttribute('expanded') || this.parentNode.hasAttribute('expanded');
|
69
|
+
}
|
70
|
+
|
71
|
+
set expanded(value) {
|
72
|
+
this._expanded = value;
|
73
|
+
if (value) {
|
74
|
+
this.setAttribute('expanded', 'expanded');
|
75
|
+
} else {
|
76
|
+
this.removeAttribute('expanded');
|
77
|
+
}
|
78
|
+
|
79
|
+
this.area.style.display = this._expanded ? 'block' : 'none';
|
80
|
+
}
|
81
|
+
|
82
|
+
get expanded() {
|
83
|
+
return this._expanded;
|
84
|
+
}
|
85
|
+
|
86
|
+
expand() {
|
87
|
+
this.expanded = true;
|
88
|
+
}
|
89
|
+
|
90
|
+
collapse() {
|
91
|
+
this.expanded = false;
|
92
|
+
}
|
93
|
+
|
94
|
+
toggle() {
|
95
|
+
this.expanded = !this.expanded;
|
96
|
+
}
|
97
|
+
}
|
98
|
+
|
99
|
+
customElements.define('expandable-item', ExpandableItem);
|