fdbq-rails 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []