fdbq-rails 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f9195b6626e0cf218de00b495a3617f1cb460f8a50fbee7add7c93b5e44ebccc
4
+ data.tar.gz: 2d8c3c1606629fa1ed0e934168edd8fba4a699fcc7dfd3a4cd5af0f203014784
5
+ SHA512:
6
+ metadata.gz: 5c98ef23951c9f362f4da0c4a8eb0922615bae697f1b2cc6281277da879dc33ca016c2f1258ab3ae68f1d2c40805810ef881769f74cdc7bf96ef3466d73e0770
7
+ data.tar.gz: 69cc776c15caaa9acc52b9286be0706870c36fe4711f19e2c2acdc0cf443773c60dd7f00c7e81d7299673e215b59b2d842b5c5e397c25aeeb9379a571a62fa89
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2020 Roman Solomud
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,49 @@
1
+ # Fdbq::Rails
2
+
3
+ A Rails integration of <TBD> with an ORM recording of submits.
4
+
5
+ ## Installation
6
+
7
+ ##### Terminal
8
+
9
+ `gem install fdbq-rails`
10
+
11
+ ##### Gemfile
12
+
13
+ `gem 'fdbq-rails'`
14
+
15
+ ##### Rails
16
+
17
+ Generate configuration files
18
+ + `rails g fdbq:rails:install`
19
+
20
+ Mount into your application
21
+ + update `config/routes.rb` with `mount Fdbq::Rails::Engine, at: '<your path>'`
22
+
23
+ Update configuration for fton-end
24
+ + update `config/fdbq.yml` configuration file
25
+
26
+ ## Usage
27
+
28
+
29
+ Aadd to your layout or view plugin
30
+ + `fdbq_render`
31
+
32
+ To fetch list of submitted responses use
33
+ + `fdbq_responses` helper that works within controller, helper or view.
34
+ or
35
+ + `Fdbq::Feedback.all` to query directly thru ActiveRecord model.
36
+
37
+ ## Contributing
38
+
39
+ - fork it
40
+ - commit
41
+ - submit PR
42
+
43
+ ## TODO
44
+
45
+ - [ ] Add JS plugin
46
+ - [ ] Add MongoDB support
47
+
48
+ ## License
49
+ 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,22 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Fdbq::Rails'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("spec/dummy/Rakefile", __dir__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+ load 'rails/tasks/statistics.rake'
21
+
22
+ require 'bundler/gem_tasks'
@@ -0,0 +1,2 @@
1
+ //= link_directory ../stylesheets .css
2
+ //= link_directory ../javascripts .js
@@ -0,0 +1 @@
1
+ //= require @softserveopensource/fdbq
@@ -0,0 +1,3 @@
1
+ /*
2
+ *= require @softserveopensource/fdbq/theme
3
+ */
@@ -0,0 +1,20 @@
1
+ module Fdbq
2
+ class FeedbackController < Fdbq::Rails.controller_parent.constantize
3
+ skip_before_action :verify_authenticity_token, only: :create
4
+
5
+ def create
6
+ @feedback = Fdbq::Feedback.new(fields: permitted_params.to_h)
7
+ @feedback.save
8
+
9
+ respond_to do |format|
10
+ format.json { render(status: @feedback.persisted? ? 201 : 422) }
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def permitted_params
17
+ params.require(Fdbq.param_key).permit(*params[Fdbq.param_key]&.keys)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ module Fdbq
2
+ class Feedback < Rails.model_parent.constantize
3
+ self.table_name = "fdbq_feedback"
4
+
5
+ serialize :fields, Hash
6
+
7
+
8
+ validate :required_fields
9
+
10
+ private
11
+
12
+ def required_fields
13
+ Fdbq.questions.select(&:required?).each do |question|
14
+ next if question.answered?(fields.to_h)
15
+
16
+ self.errors.add(:fields, :invalid)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,7 @@
1
+ json.call(@feedback, :id, :fields)
2
+ json.created_at @feedback.created_at&.iso8601
3
+ json.updated_at @feedback.updated_at&.iso8601
4
+
5
+ if @feedback.errors.present?
6
+ json.errors @feedback.errors.messages
7
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,3 @@
1
+ Fdbq::Rails::Engine.routes.draw do
2
+ resource :feedback, only: :create, controller: 'feedback'
3
+ end
data/lib/fdbq/fdbq.rb ADDED
@@ -0,0 +1,19 @@
1
+ require 'fdbq/plugin'
2
+
3
+ module Fdbq
4
+ def self.param_key
5
+ Plugin.instance.param_key
6
+ end
7
+
8
+ def self.questions
9
+ Plugin.instance.questions
10
+ end
11
+
12
+ def self.configure(&block)
13
+ Plugin.instance.instance_eval(&block)
14
+ end
15
+
16
+ def self.current_instance
17
+ Plugin.instance.settings
18
+ end
19
+ end
@@ -0,0 +1,42 @@
1
+ require 'yaml'
2
+ require 'singleton'
3
+
4
+ require 'fdbq/question'
5
+
6
+ module Fdbq
7
+ class Plugin
8
+ DEFAULT_PARAM_KEY = :feedback
9
+
10
+ attr_accessor :config_file_path
11
+ attr_accessor :param_key
12
+
13
+ include ::Singleton
14
+
15
+ instance.param_key = DEFAULT_PARAM_KEY
16
+
17
+ def reset!
18
+ self.param_key = DEFAULT_PARAM_KEY
19
+ self.config_file_path = nil
20
+ end
21
+
22
+ def questions
23
+ config.dig('questions').to_a.map(&Question.method(:new))
24
+ end
25
+
26
+ def settings
27
+ config.tap do |c|
28
+ c['questions']&.map! { |el| el.merge('name' => build_param_key(el['name'])) }
29
+
30
+ c['submit'] = { url: Rails::Engine.routes.url_helpers.feedback_path }
31
+ end
32
+ end
33
+
34
+ def config
35
+ YAML.load_file(config_file_path).to_h
36
+ end
37
+
38
+ def build_param_key(question_name)
39
+ "#{param_key}[#{question_name}]"
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,23 @@
1
+ module Fdbq
2
+ class Question
3
+ attr_accessor :label, :name, :value, :placeholder, :type, :required, :hint
4
+
5
+ def initialize(attrs = {})
6
+ attrs.to_h.each_pair do |k, v|
7
+ self.send("#{k}=", v)
8
+ end
9
+ end
10
+
11
+ def param_key
12
+ name.to_s
13
+ end
14
+
15
+ def answered?(answers)
16
+ answers.to_h.stringify_keys[param_key].present?
17
+ end
18
+
19
+ def required?
20
+ !!required
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,17 @@
1
+ module Fdbq
2
+ module Rails
3
+ class Engine < ::Rails::Engine
4
+ isolate_namespace Fdbq
5
+ engine_name 'fdbq'
6
+
7
+ initializer "fdbq.assets.precompile" do |app|
8
+ app.config.assets.precompile += %w( fdbq.* )
9
+ end
10
+
11
+ initializer "fdbq.engine" do |app|
12
+ ActionController::Base.send :include, Fdbq::Rails::Helpers
13
+ ActionView::Base.send :include, Fdbq::Rails::Helpers
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ module Fdbq
2
+ module Rails
3
+ module Helpers
4
+ def fdbq_responses
5
+ Fdbq::Feedback.all
6
+ end
7
+
8
+ def fdbq_render
9
+ javascript_tag "(new Fdbq(#{Fdbq.current_instance.to_json})).init();"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,5 @@
1
+ module Fdbq
2
+ module Rails
3
+ VERSION = '0.1.0'
4
+ end
5
+ end
data/lib/fdbq/rails.rb ADDED
@@ -0,0 +1,29 @@
1
+ require 'jbuilder'
2
+
3
+ require "fdbq/fdbq"
4
+ require "fdbq/rails/engine"
5
+ require 'fdbq/rails/helpers'
6
+
7
+ module Fdbq
8
+ module Rails
9
+ def self.controller_parent
10
+ if latest?
11
+ 'ApplicationController'
12
+ else
13
+ 'ActionController::Base'
14
+ end
15
+ end
16
+
17
+ def self.model_parent
18
+ if latest?
19
+ 'ApplicationRecord'
20
+ else
21
+ 'ActiveRecord::Base'
22
+ end
23
+ end
24
+
25
+ def self.latest?
26
+ ::Rails.version.to_i >= 5
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,4 @@
1
+ module Fdbq
2
+ class Railtie < ::Rails::Railtie
3
+ end
4
+ end
@@ -0,0 +1,3 @@
1
+ module Fdbq
2
+ VERSION = '0.1.0'
3
+ end
data/lib/fdbq-rails.rb ADDED
@@ -0,0 +1,3 @@
1
+ require "fdbq/railtie"
2
+ require "fdbq/rails"
3
+
@@ -0,0 +1,37 @@
1
+ require 'rails/generators/migration'
2
+
3
+ module Fdbq
4
+ module Rails
5
+ module Generators
6
+ class InstallGenerator < ::Rails::Generators::Base
7
+ include ::Rails::Generators::Migration
8
+
9
+ source_root File.expand_path("../../../templates", __FILE__)
10
+
11
+ desc "This generator creates a plugin configuration files and a migration"
12
+
13
+ class << self
14
+ def next_migration_number(dirname)
15
+ if ActiveRecord::Base.timestamped_migrations
16
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
17
+ else
18
+ "%.3d" % (current_migration_number(dirname) + 1)
19
+ end
20
+ end
21
+ end
22
+
23
+ def copy_config
24
+ template "fdbq.yml", "config/fdbq.yml"
25
+ end
26
+
27
+ def copy_migration
28
+ migration_template "create_fdbq_feedback.rb", "db/migrate/create_fdbq_feedback.rb"
29
+ end
30
+
31
+ def copy_initializer
32
+ template "fdbq.rb", "config/initializers/fdbq.rb"
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,9 @@
1
+ class CreateFdbqFeedback < ActiveRecord::Migration[4.2]
2
+ def change
3
+ create_table :fdbq_feedback do |t|
4
+ t.text :fields
5
+
6
+ t.timestamps
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ Fdbq.configure do |config|
2
+ config.config_file_path = Rails.root.join('config', 'fdbq.yml').to_s
3
+
4
+ config.param_key = :feedback
5
+ end
@@ -0,0 +1,23 @@
1
+ placement: bottom right
2
+ modal:
3
+ title:
4
+ subHeader:
5
+ title:
6
+ description:
7
+ questions:
8
+ # -
9
+ # name: # [String]
10
+ # label: # [String]
11
+ # value: # [String]
12
+ # placeholder: # [String]
13
+ # type: # [String]
14
+ # required: # [Boolean]
15
+ # hint: # [String]
16
+ # -
17
+ # name: # [String]
18
+ # label: # [String]
19
+ # value: # [String]
20
+ # placeholder: # [String]
21
+ # type: # [String]
22
+ # required: # [Boolean]
23
+ # hint: # [String]
@@ -0,0 +1,120 @@
1
+ ## Table of contents
2
+
3
+ - [Installation](#installation)
4
+ - [Configuration](#configuration)
5
+
6
+ ## Installation
7
+
8
+ * Add package to a project
9
+
10
+ * using Yarn
11
+
12
+ `yarn add fdbq`
13
+
14
+ * using NPM
15
+
16
+ `npm i -S fdbq`
17
+
18
+ * Add javascript file
19
+
20
+ `import "fdbq"`
21
+
22
+ * Include default theme (optional)
23
+
24
+ `import "fdbq/theme.css"`
25
+
26
+ * Enable on a page
27
+
28
+ ```
29
+ var instance = new Fdbq(config);
30
+ instance.init();
31
+ ```
32
+
33
+ ## Configuration
34
+
35
+ ```
36
+ {
37
+ mountNode: "body",
38
+ placement: "bottom right",
39
+ modal: {
40
+ title: "Help us get better",
41
+ },
42
+ submit: {
43
+ url: "http://localhost:3000/api/test-feedback"
44
+ },
45
+ subHeader: {
46
+ title: "Feedback",
47
+ description: "Please take a short survey"
48
+ },
49
+ questions: [
50
+ {
51
+ name: "feedback[usage]",
52
+ label: "Please share your feedback upon usage of Practice Dashboard",
53
+ value: "",
54
+ placeholder: "Type here",
55
+ type: "text",
56
+ required: true,
57
+ hint: "Please, describe your opinion about the service we provide."
58
+ }
59
+ ]
60
+ }
61
+ ```
62
+
63
+ ##### Instance configuration
64
+
65
+ | Setting | Required | Type | Options | Default | Description |
66
+ | ------------------------ | -------- | ------------------- | -------------------------------- | --------- | ----------------------------- |
67
+ | `mountNode` | No | String | |`"body" ` | Root node to mount lugin into |
68
+ | `placement` | Yes | String | `bottom`, `top`, `left`, `right` | | Button placement on a screen |
69
+ | `modal` | No | Object | | | Modal settings |
70
+ | `submit` | Yes | Object | | `func` | Modal submit handler. Default handler submit on `url` |
71
+ | `subHeader` | No | String | | | Modal body sub-header |
72
+ | `questions` | No | Array[Object] | | `[]` | List of questions for a modal |
73
+
74
+ ##### Modal configuration `modal`
75
+
76
+ | Setting | Required | Type | Options | Default | Description |
77
+ | -------- | -------- | ------------------- | -------------------------------- | --------- | ----------- |
78
+ | `title` | No | String | |`"" ` | Modal title |
79
+
80
+ ##### Modal configuration `subHeader`
81
+
82
+ | Setting | Required | Type | Options | Default | Description |
83
+ | ------------- | -------- | ------------------- | -------------------------------- | --------- | ---------------------- |
84
+ | `title` | No | String | |`"" ` | Modal body title |
85
+ | `description` | No | String | |`"" ` | Modal body description |
86
+
87
+ ##### Modal configuration `submit`
88
+
89
+ * as `Function`
90
+
91
+ ```
92
+ {
93
+ // ...
94
+ submit: function(fields, instance) {
95
+ console.log(fields, instance);
96
+ },
97
+ // ...
98
+ }
99
+ ```
100
+
101
+ * as `Object`
102
+
103
+ | Setting | Required | Type | Options | Default | Description |
104
+ | ------- | -------- | ------------------- | -------------------------------- | --------- | ---------------------------------------------- |
105
+ | `url` | Yes | String | |`"" ` | Submit url for the form. (Uses `POST` request) |
106
+
107
+ ##### Modal configuration `questions`
108
+
109
+ | Setting | Required | Type | Options | Default | Description |
110
+ | ------------- | -------- | ------- | ---------------- | ------- | ---------------------------------------------- |
111
+ | `name` | Yes | String | | | Name for a input field. Used as submit param |
112
+ | `label` | Yes | String | | | Input field label |
113
+ | `value` | No | String | | | Default value for the input |
114
+ | `placeholder` | No | String | | | Input placeholder |
115
+ | `type` | Yes | String | `Text`, `String` | | Input field type. Rendered in different style |
116
+ | `required` | No | Boolean | | `false` | Input required flag |
117
+ | `hint` | No | String | | | Hint text for input field |
118
+
119
+
120
+ > Check out a configuration example in `examples/basic/index.html`
@@ -0,0 +1,393 @@
1
+ (function() {
2
+ var TOGGLE_REGEX = /(^show | show | show$)/i;
3
+ var SUCCESS_STATUS_CODE = /^20\d$/;
4
+
5
+ function parameterize(object) {
6
+ var parts = [];
7
+
8
+ for(var prop in object) {
9
+ parts.push(encodeURIComponent(prop)+"="+encodeURIComponent(object[prop]));
10
+ }
11
+
12
+ return parts.join("&");
13
+ }
14
+
15
+ function questionID(config) { return (config.name+"").replace(/[^\w\-]+/ig, ""); }
16
+
17
+ function queryField(fieldID, instance) {
18
+ return document.querySelector('#'+instance.modalSelector+' [data-id="'+fieldID+'"][data-fdbq="field-container"]');
19
+ }
20
+
21
+ function arrayFind(array, prop, value) {
22
+ for(var i = 0; i < array.length; i++) {
23
+ if(array[i] && array[i][prop] === value) return array[i];
24
+ }
25
+ }
26
+
27
+ function validField(field, config) {
28
+ var isValid = true;
29
+
30
+ if(!field) return false;
31
+
32
+ if(config.required && !field.value) isValid = false;
33
+
34
+ return isValid;
35
+ }
36
+
37
+ function buildField(type, options) {
38
+ var node = document.createElement("div");
39
+ node.className = FdbqClassName("fdbq-field", "-"+type, options.error ? "has-errors" : "");
40
+ node.dataset.fdbq = "field-container";
41
+ node.dataset.id = questionID(options);
42
+
43
+ return node;
44
+ }
45
+
46
+ function buildInput(type, props, options) {
47
+ var node = document.createElement(type);
48
+
49
+ for(var prop in props) {
50
+ node[prop] = props[prop];
51
+ }
52
+
53
+ node.className = FdbqClassName("fdbq-input", node.className);
54
+ node.dataset.fdbq = "field";
55
+
56
+ return node;
57
+ }
58
+
59
+ function httpSubmit(params, instance) {
60
+ if(!instance.config.submit.url) return;
61
+
62
+ var xmlHttp = new XMLHttpRequest();
63
+
64
+ xmlHttp.onreadystatechange = function() {
65
+ if(xmlHttp.readyState == 4 && SUCCESS_STATUS_CODE.test(xmlHttp.status)) {
66
+ instance.onCloseClick();
67
+ }
68
+ };
69
+
70
+ xmlHttp.open("POST", instance.config.submit.url, true);
71
+ xmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
72
+ xmlHttp.send(parameterize(params));
73
+ }
74
+
75
+ function renderModalBody(node, instance) {
76
+ node.appendChild(FdbqModalHead(instance));
77
+ node.appendChild(FdbqModalBody(instance));
78
+ }
79
+
80
+ function renderModalFields(container, instance) {
81
+ for(var i = 0; i < instance.config.questions.length; i++) {
82
+ var field = FdbqField(instance.config.questions[i], instance);
83
+
84
+ if(!field) continue;
85
+
86
+ container.appendChild(field);
87
+ }
88
+ }
89
+
90
+ function renderFields(node, instance) {
91
+ if(!Array.isArray(instance.config.questions) || !instance.config.questions.length) return;
92
+
93
+ child = document.createElement("div");
94
+ child.className = "fdbq-modal-fields";
95
+ child.id = instance.fieldsContainerSelector;
96
+
97
+ renderModalFields(child, instance);
98
+
99
+ node.appendChild(child);
100
+ }
101
+
102
+ function FdbqFieldsReplaceInvalid(instance) {
103
+ if(!instance.errors || !instance.config || !instance.config.questions) return;
104
+
105
+ var fields = document.querySelector('#'+instance.modalSelector+' .fdbq-modal-fields');
106
+
107
+ if(!fields) return;
108
+
109
+ for(var i = 0; i < instance.errors.length; i++) {
110
+ var fieldID = instance.errors[i];
111
+ var config = arrayFind(instance.config.questions, function(el) { return questionID(el) === fieldID; });
112
+ var field = queryField(fieldID, instance);
113
+
114
+ if(!config || !field) continue;
115
+
116
+ fields.replaceChild(FdbqField(config, instance), field);
117
+ }
118
+ }
119
+
120
+ function FdbqUUID(id) { return id+"-"+Math.random().toString(36).substr(2, 9); };
121
+ function FdbqClassName() {
122
+ var classList = [];
123
+
124
+ for(var i = 0; i < arguments.length; i++) {
125
+ if(arguments[i]) classList.push(arguments[i]);
126
+ }
127
+
128
+ return classList.join(" ");
129
+ }
130
+
131
+ function FdbqFieldsReset(instance) {
132
+ var fields = document.getElementById(instance.fieldsContainerSelector);
133
+
134
+ if(!fields) return;
135
+
136
+ fields.innerHTML = "";
137
+ renderModalFields(fields, instance);
138
+ }
139
+
140
+ function FdbqTextField(config, instance) {
141
+ var child;
142
+ var inputID = questionID(config);
143
+ var node = buildField("text", Object.assign({}, config, { error: instance.errors.indexOf(inputID) >= 0 }));
144
+ var fieldID = FdbqUUID("fdbq-field-text-"+inputID);
145
+
146
+ if(config.label) {
147
+ child = document.createElement("label");
148
+ child.innerHTML = (config.required ? '<abbr title="required">*</abbr>' : "")+config.label;
149
+ child.htmlFor = fieldID;
150
+
151
+ node.appendChild(child);
152
+ }
153
+
154
+ node.appendChild(buildInput("textarea", {
155
+ id: fieldID,
156
+ rows: config.rows ? parseInt(config.rows, 10) : 3,
157
+ value: config.value || "",
158
+ placeholder: config.placeholder
159
+ }, { type: "text", id: inputID }));
160
+
161
+ if(config.hint) {
162
+ child = document.createElement("div");
163
+ child.className = "fdbq-field-hint";
164
+ child.innerHTML = config.hint;
165
+
166
+ node.appendChild(child);
167
+ }
168
+
169
+ return node;
170
+ }
171
+
172
+ function FdbqStringField(config, instance) {
173
+ var child;
174
+ var inputID = questionID(config);
175
+ var node = buildField("string", Object.assign({}, config, { error: instance.errors.indexOf(inputID) >= 0 }));
176
+ var fieldID = FdbqUUID("fdbq-field-string-"+inputID);
177
+
178
+ if(config.label) {
179
+ child = document.createElement("label");
180
+ child.innerHTML = (config.required ? '<abbr title="required">*</abbr>' : "")+config.label;
181
+ child.htmlFor = fieldID;
182
+
183
+ node.appendChild(child);
184
+ }
185
+
186
+ node.appendChild(buildInput("input", {
187
+ id: fieldID,
188
+ type: "text",
189
+ rows: config.rows ? parseInt(config.rows, 10) : 3,
190
+ value: config.value || "",
191
+ placeholder: config.placeholder
192
+ }, { error: instance.errors.indexOf(config.id) >= 0, type: "string", id: config.id }));
193
+
194
+ if(config.hint) {
195
+ child = document.createElement("div");
196
+ child.className = "fdbq-field-hint";
197
+ child.innerHTML = config.hint;
198
+
199
+ node.appendChild(child);
200
+ }
201
+
202
+ return node;
203
+ }
204
+
205
+ function FdbqField(config, instance) {
206
+ switch(config.type) {
207
+ case "text":
208
+ return FdbqTextField(config, instance);
209
+ default:
210
+ return FdbqStringField(config, instance);
211
+ }
212
+ }
213
+
214
+ function FdbqModalHead(instance) {
215
+ var node = document.createElement("div");
216
+ node.className = "fdbq-modal-head";
217
+
218
+ // heading;
219
+ var heading = document.createElement("h4");
220
+ heading.innerText = instance.config.modal && instance.config.modal.title || "";
221
+
222
+ // close button;
223
+ var closeButton = document.createElement("button");
224
+ closeButton.innerHTML = "&times;";
225
+ closeButton.className = "close";
226
+ closeButton.id = instance.modalCloseSelector;
227
+
228
+ node.appendChild(heading);
229
+ node.appendChild(closeButton);
230
+
231
+ return node;
232
+ }
233
+
234
+ function FdbqModalBodyHeading(instance) {
235
+ if(!instance.config.subHeader) return null;
236
+
237
+ var node = document.createElement("div");
238
+ node.className = "fdbq-modal-heading";
239
+
240
+ // heading;
241
+ if(instance.config.subHeader.title) {
242
+ var child = document.createElement("h5");
243
+ child.className = "sub-heading";
244
+ child.innerText = instance.config.subHeader.title;
245
+
246
+ node.appendChild(child);
247
+ }
248
+
249
+ // sub-title;
250
+ if(instance.config.subHeader.description) {
251
+ child = document.createElement("div");
252
+ child.className = "sub-heading-description";
253
+ child.innerHTML = instance.config.subHeader.description;
254
+
255
+ node.appendChild(child);
256
+ }
257
+
258
+ return node;
259
+ }
260
+
261
+ function FdbqActions(instance) {
262
+ var node = document.createElement("div");
263
+ node.className = "fdbq-modal-actions";
264
+
265
+ var child = document.createElement("button");
266
+ child.className = "submit";
267
+ child.id = instance.modalSubmitSelector;
268
+ child.innerText = "Submit";
269
+
270
+ node.appendChild(child);
271
+
272
+ return node;
273
+ }
274
+
275
+ function FdbqModalBody(instance) {
276
+ var node = document.createElement("div");
277
+ node.className = "fdbq-modal-body";
278
+
279
+ var child = FdbqModalBodyHeading(instance);
280
+
281
+ if(child) node.appendChild(child);
282
+
283
+ renderFields(node, instance);
284
+
285
+ child = FdbqActions(instance);
286
+
287
+ if(child) node.appendChild(child);
288
+
289
+ return node;
290
+ }
291
+
292
+ function FdbqModal(instance) {
293
+ var overlay = document.createElement("div");
294
+ var node = document.createElement("div");
295
+ node.className = "fdbq-modal";
296
+ node.id = instance.modalContainerSelector;
297
+
298
+ renderModalBody(node, instance);
299
+
300
+ overlay.className = "fdbq-modal-overlay";
301
+ overlay.appendChild(node);
302
+ overlay.id = instance.modalSelector;
303
+
304
+ return overlay;
305
+ }
306
+
307
+ function FdbqAction(instance) {
308
+ var node = document.createElement("button");
309
+ node.className = FdbqClassName("fdbq-toggle", instance.config.placement);
310
+ node.id = instance.buttonSelector;
311
+ node.innerHTML = instance.config.actionHTML || instance.config.actionText || '<i class="fdbq-icon-action"></i>';
312
+
313
+ return node;
314
+ }
315
+
316
+ window.Fdbq = function(config) {
317
+ this.init = function() {
318
+ var modal = FdbqModal(this);
319
+ var action = FdbqAction(this);
320
+
321
+ this.mountNode().appendChild(modal);
322
+ this.mountNode().appendChild(action);
323
+
324
+ var button = document.getElementById(this.buttonSelector);
325
+ var closeButton = document.getElementById(this.modalCloseSelector);
326
+ var submitButton = document.getElementById(this.modalSubmitSelector);
327
+
328
+ if(button) button.addEventListener("click", this.onActionClick.bind(this));
329
+ if(closeButton) closeButton.addEventListener("click", this.onCloseClick.bind(this));
330
+ if(submitButton) submitButton.addEventListener("click", this.onSubmitClick.bind(this));
331
+
332
+ };
333
+
334
+ this.mountNode = function() { return document.querySelector(this.config.mountNode) || document.body; };
335
+ this.onActionClick = function(event) {
336
+ var modal = document.getElementById(this.modalSelector);
337
+
338
+ if(!modal || TOGGLE_REGEX.test(modal.className)) return;
339
+
340
+ modal.className = modal.className+" show";
341
+ };
342
+ this.onCloseClick = function() {
343
+ var modal = document.getElementById(this.modalSelector);
344
+
345
+ if(!modal || !TOGGLE_REGEX.test(modal.className)) return;
346
+ modal.className = modal.className.replace(TOGGLE_REGEX, "").trim();
347
+ this.errors = [];
348
+ FdbqFieldsReset(this);
349
+ };
350
+ this.onSubmitClick = function(event) {
351
+ this.errors = [];
352
+ if(!this.config.submit || !this.config.questions) return;
353
+
354
+ var callback = typeof(this.config.submit) === "function" ? this.config.submit : httpSubmit;
355
+ var params = {};
356
+
357
+ for(var i = 0; i < this.config.questions.length; i++) {
358
+ var q = this.config.questions[i];
359
+ var id = questionID(q);
360
+ var field = document.querySelector('.fdbq-modal-fields [data-id="'+id+'"] [data-fdbq="field"]');
361
+
362
+ if(!field) continue;
363
+
364
+ if(validField(field, q)) {
365
+ params[q.name] = field.value;
366
+ } else {
367
+ this.errors.push(id);
368
+ }
369
+ }
370
+
371
+ if(this.errors.length) {
372
+ FdbqFieldsReplaceInvalid(this);
373
+
374
+ return false;
375
+ }
376
+
377
+ callback(params, this);
378
+
379
+ return true;
380
+ };
381
+
382
+ // props;
383
+ this.config = config || {};
384
+ this.errors = [];
385
+ this.identifier = FdbqUUID("fdbq");
386
+ this.buttonSelector = this.identifier+"-button";
387
+ this.modalSelector = this.identifier+"-modal";
388
+ this.modalContainerSelector = this.identifier+"-modal-container";
389
+ this.fieldsContainerSelector = this.identifier+"-fields";
390
+ this.modalCloseSelector = this.modalSelector+"-close";
391
+ this.modalSubmitSelector = this.modalSelector+"-submit";
392
+ };
393
+ })();
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@softserveopensource/fdbq",
3
+ "version": "0.1.0",
4
+ "description": "A simple lightweight survey for your website",
5
+ "main": "index.js",
6
+ "author": "SoftServe OpenSource",
7
+ "license": "MIT",
8
+ "homepage": "https://github.com/SoftServeInc/fdbq#readme",
9
+ "scripts": {
10
+ "test": "jest"
11
+ },
12
+ "devDependencies": {
13
+ "jest": "^24.9.0",
14
+ "serve": "^11.3.0"
15
+ },
16
+ "files": [
17
+ "index.js",
18
+ "theme.css"
19
+ ],
20
+ "repository": {
21
+ "url": "git+https://github.com/SoftServeInc/fdbq.git",
22
+ "type": "git"
23
+ },
24
+ "bugs": {
25
+ "url": "https://github.com/SoftServeInc/fdbq/issues"
26
+ },
27
+ "directories": {
28
+ "example": "examples"
29
+ },
30
+ "keywords": [
31
+ "Feedback"
32
+ ]
33
+ }
@@ -0,0 +1,228 @@
1
+
2
+ .fdbq-modal-overlay {
3
+ position: fixed;
4
+ z-index: 10000;
5
+ top: 0;
6
+ left: 0;
7
+ right: 0;
8
+ bottom: 0;
9
+ background-color: rgba(0, 0, 0, 0.3);
10
+ overflow-y: scroll;
11
+ display: none;
12
+ }
13
+
14
+ .fdbq-modal-overlay.show {
15
+ display: block;
16
+ }
17
+
18
+ .fdbq-modal {
19
+ width: 40%;
20
+ margin: 40px auto;
21
+ background-color: #ffffff;
22
+ }
23
+
24
+ .fdbq-modal .fdbq-modal-head {
25
+ display: flex;
26
+ margin: 0;
27
+ padding: 0;
28
+ align-items: center;
29
+ background-color: #000000;
30
+ }
31
+
32
+ .fdbq-modal .fdbq-modal-head h4 {
33
+ width: 100%;
34
+ color: #ffffff;
35
+ margin-top: 10px;
36
+ margin-bottom: 10px;
37
+ margin-left: 15px;
38
+ margin-right: 15px;
39
+ font-size: 18px;
40
+ text-transform: uppercase;
41
+ }
42
+
43
+ .fdbq-modal .fdbq-modal-head .close {
44
+ padding: 0 10px;
45
+ margin: 0 5px 0 0;
46
+ border: none;
47
+ background-color: transparent;
48
+ color: #ffffff;
49
+ font-size: 33px;
50
+ line-height: 100%;
51
+ }
52
+
53
+ .fdbq-modal .fdbq-modal-head .close:focus {
54
+ outline: none;
55
+ }
56
+
57
+ .fdbq-modal .fdbq-modal-head .close:hover {
58
+ cursor: pointer;
59
+ }
60
+
61
+ .fdbq-modal .fdbq-modal-body {
62
+ padding: 30px 15px;
63
+ }
64
+
65
+ .fdbq-modal .fdbq-modal-body .fdbq-modal-actions {
66
+ margin-top: 30px;
67
+ padding: 0;
68
+ }
69
+
70
+ .fdbq-modal .fdbq-modal-body .fdbq-modal-heading {
71
+ margin-bottom: 30px;
72
+ }
73
+
74
+ .fdbq-modal .fdbq-modal-body .fdbq-modal-heading h5 {
75
+ margin: 0;
76
+ font-size: 22px;
77
+ font-weight: bold;
78
+ text-transform: uppercase;
79
+ }
80
+
81
+ .fdbq-modal .fdbq-modal-body .fdbq-modal-heading .sub-heading-description {
82
+ padding-top: 10px;
83
+ color: #000000;
84
+ }
85
+
86
+ .fdbq-modal .fdbq-modal-body .fdbq-modal-heading h5:after {
87
+ display: block;
88
+ margin-top: 10px;
89
+ content: "";
90
+ width: 23%;
91
+ height: 2px;
92
+ background-color: #000000;
93
+ }
94
+
95
+ .fdbq-modal .fdbq-modal-body .fdbq-modal-actions .submit {
96
+ background-color: #ffffff;
97
+ border: 2px solid #000000;
98
+ color: #000000;
99
+ font-size: 16px;
100
+ text-transform: uppercase;
101
+ padding: 5px 30px;
102
+ font-weight: bold;
103
+ }
104
+
105
+ .fdbq-modal .fdbq-modal-body .fdbq-modal-actions .submit:hover,
106
+ .fdbq-modal .fdbq-modal-body .fdbq-modal-actions .submit:focus {
107
+ background-color: #000000;
108
+ color: #ffffff;
109
+ cursor: pointer;
110
+ transition: background-color .5s;
111
+ }
112
+
113
+ .fdbq-modal .fdbq-field {
114
+ display: flex;
115
+ flex-direction: column;
116
+ }
117
+
118
+ .fdbq-modal .fdbq-field abbr {
119
+ color: red;
120
+ padding-right: 3px;
121
+ }
122
+
123
+ .fdbq-modal .fdbq-field label {
124
+ display: block;
125
+ margin-bottom: 5px;
126
+ color: #909090;
127
+ font-weight: bold;
128
+ }
129
+
130
+ .fdbq-modal .fdbq-field.has-errors label {
131
+ color: red !important;
132
+ }
133
+
134
+ .fdbq-modal .fdbq-field.has-errors .fdbq-input {
135
+ border-color: red !important;
136
+ }
137
+
138
+ .fdbq-modal .fdbq-field .fdbq-input {
139
+ padding: 15px;
140
+ border: none;
141
+ display: flex;
142
+ border: 2px solid #000000;
143
+ font-size: 14px;
144
+ color: #000000;
145
+ line-height: 15px;
146
+ }
147
+
148
+ .fdbq-modal .fdbq-field .fdbq-input:focus {
149
+ outline: none;
150
+ }
151
+
152
+ .fdbq-modal .fdbq-field + .fdbq-field {
153
+ margin-top: 15px;
154
+ }
155
+
156
+ .fdbq-modal .fdbq-field .fdbq-field-hint {
157
+ padding-top: 5px;
158
+ font-size: 14px;
159
+ color: #909090;
160
+ }
161
+
162
+ .fdbq-toggle {
163
+ padding: 5px;
164
+ margin: 0;
165
+ background-color: #000000;
166
+ border: none;
167
+ }
168
+
169
+ .fdbq-toggle:hover, .fdbq-toggle:focus {
170
+ cursor: pointer;
171
+ outline: none;
172
+ background-color: #303030;
173
+ transition: background-color .5s;
174
+ }
175
+
176
+ .fdbq-toggle .fdbq-icon-action {
177
+ display: inline-block;
178
+ width: 45px;
179
+ height: 45px;
180
+ background: url("data:image/png;base64,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") center top no-repeat;
181
+ background-size: contain;
182
+ }
183
+
184
+ .fdbq-toggle.bottom {
185
+ position: fixed;
186
+ bottom: 5%;
187
+ }
188
+
189
+ .fdbq-toggle.top {
190
+ position: fixed;
191
+ top: 5%;
192
+ }
193
+
194
+ .fdbq-toggle.center {
195
+ position: fixed;
196
+ left: 50%;
197
+ transform: translateX(-50%);
198
+ }
199
+
200
+
201
+ .fdbq-toggle.middle {
202
+ position: fixed;
203
+ top: 50%;
204
+ transform: translate(0, -50%);
205
+ }
206
+
207
+ .fdbq-toggle.right {
208
+ position: fixed;
209
+ right: 5%;
210
+ }
211
+
212
+ .fdbq-toggle.left {
213
+ position: fixed;
214
+ left: 5%;
215
+ }
216
+
217
+
218
+ .fdbq-toggle.right.bottom,
219
+ .fdbq-toggle.left.bottom {
220
+ position: fixed;
221
+ bottom: 5%;
222
+ }
223
+
224
+ .fdbq-toggle.right.top,
225
+ .fdbq-toggle.left.top {
226
+ top: 5%;
227
+ position: fixed;
228
+ }
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fdbq-rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - SoftServe OpenSource
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-02-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '3'
20
+ - - "~>"
21
+ - !ruby/object:Gem::Version
22
+ version: 6.0.2
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '3'
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: 6.0.2
33
+ - !ruby/object:Gem::Dependency
34
+ name: jbuilder
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: sqlite3
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ description: Fdbq::Rails. A Rails integration for Fdbq JS plugin.
62
+ email:
63
+ - opensource@softserveinc.com
64
+ executables: []
65
+ extensions: []
66
+ extra_rdoc_files: []
67
+ files:
68
+ - MIT-LICENSE
69
+ - README.md
70
+ - Rakefile
71
+ - app/assets/config/fdbq_rails_manifest.js
72
+ - app/assets/javascripts/fdbq.js
73
+ - app/assets/stylesheets/fdbq.css
74
+ - app/controllers/fdbq/feedback_controller.rb
75
+ - app/models/fdbq/feedback.rb
76
+ - app/views/fdbq/feedback/create.json.jbuilder
77
+ - config/routes.rb
78
+ - lib/fdbq-rails.rb
79
+ - lib/fdbq/fdbq.rb
80
+ - lib/fdbq/plugin.rb
81
+ - lib/fdbq/question.rb
82
+ - lib/fdbq/rails.rb
83
+ - lib/fdbq/rails/engine.rb
84
+ - lib/fdbq/rails/helpers.rb
85
+ - lib/fdbq/rails/version.rb
86
+ - lib/fdbq/railtie.rb
87
+ - lib/fdbq/version.rb
88
+ - lib/generators/fdbq/rails/install_generator.rb
89
+ - lib/generators/templates/create_fdbq_feedback.rb
90
+ - lib/generators/templates/fdbq.rb
91
+ - lib/generators/templates/fdbq.yml
92
+ - vendor/assets/javascripts/@softserveopensource/fdbq/README.md
93
+ - vendor/assets/javascripts/@softserveopensource/fdbq/index.js
94
+ - vendor/assets/javascripts/@softserveopensource/fdbq/package.json
95
+ - vendor/assets/javascripts/@softserveopensource/fdbq/theme.css
96
+ homepage: https://github.com/SoftServeInc/fdbq-rails
97
+ licenses:
98
+ - MIT
99
+ metadata:
100
+ allowed_push_host: https://rubygems.org
101
+ post_install_message:
102
+ rdoc_options: []
103
+ require_paths:
104
+ - lib
105
+ required_ruby_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ required_rubygems_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ requirements: []
116
+ rubygems_version: 3.0.6
117
+ signing_key:
118
+ specification_version: 4
119
+ summary: Fdbq::Rails. A Rails integration for Fdbq JS plugin.
120
+ test_files: []