diff --git a/app/controllers/templates_controller.rb b/app/controllers/templates_controller.rb
index bbd3201..03c3bc2 100644
--- a/app/controllers/templates_controller.rb
+++ b/app/controllers/templates_controller.rb
@@ -5,7 +5,7 @@
class TemplatesController < ApplicationController
#respond_to :html
after_action :verify_authorized
-
+ helper TemplateHelper
# GET /org/admin/templates/:id/admin_index
# -----------------------------------------------------
def admin_index
@@ -273,6 +273,8 @@
end
@template.description = params["template-desc"]
+ @template.links = JSON.parse(params["template-links"]) if params["template-links"].present?
+
if @template.update_attributes(params[:template])
flash[:notice] = success_message(_('template'), _('saved'))
diff --git a/app/helpers/template_helper.rb b/app/helpers/template_helper.rb
new file mode 100644
index 0000000..85be0d4
--- /dev/null
+++ b/app/helpers/template_helper.rb
@@ -0,0 +1,8 @@
+module TemplateHelper
+ def links_to_a_elements(links)
+ a = links.map do |l|
+ "#{l['text']}"
+ end
+ a.join(", ")
+ end
+end
\ No newline at end of file
diff --git a/app/models/concerns/json_link_validator.rb b/app/models/concerns/json_link_validator.rb
new file mode 100644
index 0000000..8315a7d
--- /dev/null
+++ b/app/models/concerns/json_link_validator.rb
@@ -0,0 +1,32 @@
+module JSONLinkValidator
+ extend ActiveSupport::Concern
+
+ included do
+ # Parses a stringified JSON according to validate_links (e.g. [{ link: String, text: String}, ...])
+ # param {String} the stringified JSON value
+ # Returns an Array of hashes after decoding/validating the stringified JSON passed, otherwise nil
+ def parse_links(value)
+ return nil unless value.is_a?(String)
+ begin
+ parsed_value = JSON.parse(value)
+ return valid_links?(parsed_value) ? parsed_value : nil
+ rescue JSON::ParserError
+ nil
+ end
+ end
+ # Validates whether or not the value passed is conforming to [{ link: String, text: String}, ...]
+ def valid_links?(value)
+ if value.is_a?(Array)
+ r = value.all? do |o|
+ o.is_a?(Hash) &&
+ o.has_key?('link') &&
+ o.has_key?('text') &&
+ o['link'].is_a?(String) &&
+ o['text'].is_a?(String)
+ end
+ return r
+ end
+ false
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/models/template.rb b/app/models/template.rb
index bb69d48..c191858 100644
--- a/app/models/template.rb
+++ b/app/models/template.rb
@@ -1,7 +1,13 @@
class Template < ActiveRecord::Base
include GlobalHelpers
+ include ActiveModel::Validations
+ validates_with TemplateLinksValidator
before_validation :set_creation_defaults
+
+ # Stores links as an JSON object: { funder: [{"link":"www.example.com","text":"foo"}, ...], sample_plan: [{"link":"www.example.com","text":"foo"}, ...]}
+ # The links is validated against custom validator allocated at validators/template_links_validator.rb
+ serialize :links, JSON
##
# Associations
diff --git a/app/validators/template_links_validator.rb b/app/validators/template_links_validator.rb
new file mode 100644
index 0000000..61ef486
--- /dev/null
+++ b/app/validators/template_links_validator.rb
@@ -0,0 +1,18 @@
+class TemplateLinksValidator < ActiveModel::Validator
+ include JSONLinkValidator
+ def validate(record)
+ links = record.links
+ expected_keys = ['funder', 'sample_plan']
+ if links.is_a?(Hash)
+ expected_keys.each do |k|
+ if !links.has_key?(k)
+ record.errors[:links] << _('A key %{key} is expected for links hash') %{ :key => k }
+ else
+ record.errors[:links] << _('The key %{key} does not have a valid set of object links') %{ :key => k } unless valid_links?(links[k])
+ end
+ end
+ else
+ record.errors[:links] << _('A hash is expected for links')
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/views/shared/_links.html.erb b/app/views/shared/_links.html.erb
new file mode 100644
index 0000000..c7d137b
--- /dev/null
+++ b/app/views/shared/_links.html.erb
@@ -0,0 +1,46 @@
+<%# locals: { context, title, links, max_number_links, tooltip } %>
+
+
+
+
<%= title.present? ? title : _('Links') %>
+ (<%= _('Up to ') %>
+
+ <%= max_number_links %>
+ )
+
+
+
+
+
+
+
+
+
+ <% links = [{ "link" => "", "text" => "" }] if links.length == 0 %>
+ <% i = 0 %>
+ <% links.each do |l| %>
+
+
+ <%= label_tag "link_link#{i}", _('URL'), class: "control-label" %>
+ <%= text_field_tag 'link_link', l['link'], class: "form-control", id: "link_link#{i}" %>
+
+
+ <%= label_tag "link_text#{i}", _('Link text'), class: "control-label" %>
+ <%= text_field_tag 'link_text', l['link'], class: "form-control", id: "link_text#{i}" %>
+
+
+
+ <% i += 1 %>
+ <% end %>
+
+
+
+
\ No newline at end of file
diff --git a/app/views/templates/_edit_template.html.erb b/app/views/templates/_edit_template.html.erb
index 7b18016..dca8915 100644
--- a/app/views/templates/_edit_template.html.erb
+++ b/app/views/templates/_edit_template.html.erb
@@ -39,6 +39,28 @@
+ <% if template.org.funder? %>
+
+ <%= render(partial: '/shared/links',
+ locals: {
+ context: 'funder',
+ title: _('Funder Links'),
+ links: template.links['funder'],
+ max_number_links: MAX_NUMBER_LINKS_FUNDER,
+ tooltip: _('Add links to funder websites that provide additional information about the requirements for this template') }) %>
+
+
+ <%= render(partial: '/shared/links',
+ locals: {
+ context: 'sample_plan',
+ title: _('Sample Plan Links'),
+ links: template.links['sample_plan'],
+ max_number_links: MAX_NUMBER_LINKS_SAMPLE_PLAN,
+ tooltip: _('Add links to sample plans if provided by the funder.') }) %>
+
+ <%= hidden_field_tag('template-links', value: template.links) %>
+ <% end %>
+
<%= f.button(_('Save'), class: 'btn btn-default', type: "submit") %>
<%= link_to(_('Cancel'), '#', { class: 'btn btn-default template_show_link', role: "button" }) %>
diff --git a/app/views/templates/admin_index.html.erb b/app/views/templates/admin_index.html.erb
index 92b1fde..41c72a5 100644
--- a/app/views/templates/admin_index.html.erb
+++ b/app/views/templates/admin_index.html.erb
@@ -84,7 +84,7 @@
<%= _('Create a template') %>
<%= render partial: '/templates/list_template', locals: { title: n_('Default template', 'Default templates', @organisation_templates.length), templates: @organisation_templates } %>
-
+
<% if !current_user.org.funder? %>
<%= render partial: '/templates/list_template', locals: { title: _('Funders templates'), templates: @funder_templates } %>
<% end %>
diff --git a/config/initializers/constants.rb b/config/initializers/constants.rb
index c3f0124..51d0993 100644
--- a/config/initializers/constants.rb
+++ b/config/initializers/constants.rb
@@ -1,3 +1,5 @@
LANGUAGES = (ActiveRecord::Base.connection.table_exists? 'languages') ? Language.sorted_by_abbreviation : []
MANY_LANGUAGES = LANGUAGES.length > 1
TABLE_FILTER_MIN_ROWS = 10
+MAX_NUMBER_LINKS_FUNDER = 5
+MAX_NUMBER_LINKS_SAMPLE_PLAN = 5
diff --git a/db/migrate/20171124133802_add_links_to_templates.rb b/db/migrate/20171124133802_add_links_to_templates.rb
new file mode 100644
index 0000000..1ff9799
--- /dev/null
+++ b/db/migrate/20171124133802_add_links_to_templates.rb
@@ -0,0 +1,5 @@
+class AddLinksToTemplates < ActiveRecord::Migration
+ def change
+ add_column :templates, :links, :string, default: '{"funder":[], "sample_plan":[]}'
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index fa4df11..d7b031d 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,75 +11,72 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20171122195828) do
+ActiveRecord::Schema.define(version: 20171124133802) do
+
+ # These are extensions that must be enabled in order to support this database
+ enable_extension "plpgsql"
create_table "annotations", force: :cascade do |t|
- t.integer "question_id", limit: 4
- t.integer "org_id", limit: 4
- t.text "text", limit: 65535
- t.integer "type", limit: 4, default: 0, null: false
+ t.integer "question_id"
+ t.integer "org_id"
+ t.text "text"
+ t.integer "type", default: 0, null: false
t.datetime "created_at"
t.datetime "updated_at"
end
- add_index "annotations", ["org_id"], name: "fk_rails_aca7521f72", using: :btree
add_index "annotations", ["question_id"], name: "index_annotations_on_question_id", using: :btree
create_table "answers", force: :cascade do |t|
- t.text "text", limit: 65535
- t.integer "plan_id", limit: 4
- t.integer "user_id", limit: 4
- t.integer "question_id", limit: 4
+ t.text "text"
+ t.integer "plan_id"
+ t.integer "user_id"
+ t.integer "question_id"
t.datetime "created_at"
t.datetime "updated_at"
- t.integer "lock_version", limit: 4, default: 0
+ t.integer "lock_version", default: 0
end
- add_index "answers", ["plan_id"], name: "fk_rails_84a6005a3e", using: :btree
- add_index "answers", ["question_id"], name: "fk_rails_3d5ed4418f", using: :btree
- add_index "answers", ["user_id"], name: "fk_rails_584be190c2", using: :btree
-
create_table "answers_question_options", id: false, force: :cascade do |t|
- t.integer "answer_id", limit: 4, null: false
- t.integer "question_option_id", limit: 4, null: false
+ t.integer "answer_id", null: false
+ t.integer "question_option_id", null: false
end
add_index "answers_question_options", ["answer_id"], name: "index_answers_question_options_on_answer_id", using: :btree
- add_index "answers_question_options", ["question_option_id"], name: "fk_rails_01ba00b569", using: :btree
create_table "exported_plans", force: :cascade do |t|
- t.integer "plan_id", limit: 4
- t.integer "user_id", limit: 4
- t.string "format", limit: 255
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.integer "phase_id", limit: 4
+ t.integer "plan_id"
+ t.integer "user_id"
+ t.string "format"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.integer "phase_id"
end
create_table "file_types", force: :cascade do |t|
- t.string "name", limit: 255
- t.string "icon_name", limit: 255
- t.integer "icon_size", limit: 4
- t.string "icon_location", limit: 255
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
+ t.string "name"
+ t.string "icon_name"
+ t.integer "icon_size"
+ t.string "icon_location"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
end
create_table "file_uploads", force: :cascade do |t|
- t.string "name", limit: 255
- t.string "title", limit: 255
- t.text "description", limit: 65535
- t.integer "size", limit: 4
+ t.string "name"
+ t.string "title"
+ t.text "description"
+ t.integer "size"
t.boolean "published"
- t.string "location", limit: 255
- t.integer "file_type_id", limit: 4
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
+ t.string "location"
+ t.integer "file_type_id"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
end
create_table "friendly_id_slugs", force: :cascade do |t|
- t.string "slug", limit: 255, null: false
- t.integer "sluggable_id", limit: 4, null: false
+ t.string "slug", null: false
+ t.integer "sluggable_id", null: false
t.string "sluggable_type", limit: 40
t.datetime "created_at"
end
@@ -89,10 +86,10 @@
add_index "friendly_id_slugs", ["sluggable_type"], name: "index_friendly_id_slugs_on_sluggable_type", using: :btree
create_table "guidance_groups", force: :cascade do |t|
- t.string "name", limit: 255
- t.integer "org_id", limit: 4
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
+ t.string "name"
+ t.integer "org_id"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
t.boolean "optional_subset"
t.boolean "published"
end
@@ -100,167 +97,156 @@
add_index "guidance_groups", ["org_id"], name: "index_guidance_groups_on_org_id", using: :btree
create_table "guidances", force: :cascade do |t|
- t.text "text", limit: 65535
- t.integer "guidance_group_id", limit: 4
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.integer "question_id", limit: 4
+ t.text "text"
+ t.integer "guidance_group_id"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.integer "question_id"
t.boolean "published"
end
add_index "guidances", ["guidance_group_id"], name: "index_guidances_on_guidance_group_id", using: :btree
create_table "identifier_schemes", force: :cascade do |t|
- t.string "name", limit: 255
- t.string "description", limit: 255
+ t.string "name"
+ t.string "description"
t.boolean "active"
t.datetime "created_at"
t.datetime "updated_at"
- t.string "logo_url", limit: 255
- t.string "user_landing_url", limit: 255
+ t.string "logo_url"
+ t.string "user_landing_url"
end
create_table "languages", force: :cascade do |t|
- t.string "abbreviation", limit: 255
- t.string "description", limit: 255
- t.string "name", limit: 255
+ t.string "abbreviation"
+ t.string "description"
+ t.string "name"
t.boolean "default_language"
end
create_table "notes", force: :cascade do |t|
- t.integer "user_id", limit: 4
- t.text "text", limit: 65535
+ t.integer "user_id"
+ t.text "text"
t.boolean "archived"
- t.integer "answer_id", limit: 4
- t.integer "archived_by", limit: 4
+ t.integer "answer_id"
+ t.integer "archived_by"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "notes", ["answer_id"], name: "index_notes_on_answer_id", using: :btree
- add_index "notes", ["user_id"], name: "fk_rails_7f2323ad43", using: :btree
create_table "org_identifiers", force: :cascade do |t|
- t.string "identifier", limit: 255
- t.string "attrs", limit: 255
+ t.string "identifier"
+ t.string "attrs"
t.datetime "created_at"
t.datetime "updated_at"
- t.integer "org_id", limit: 4
- t.integer "identifier_scheme_id", limit: 4
+ t.integer "org_id"
+ t.integer "identifier_scheme_id"
end
- add_index "org_identifiers", ["identifier_scheme_id"], name: "fk_rails_189ad2e573", using: :btree
- add_index "org_identifiers", ["org_id"], name: "fk_rails_36323c0674", using: :btree
-
create_table "org_token_permissions", force: :cascade do |t|
- t.integer "org_id", limit: 4
- t.integer "token_permission_type_id", limit: 4
+ t.integer "org_id"
+ t.integer "token_permission_type_id"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "org_token_permissions", ["org_id"], name: "index_org_token_permissions_on_org_id", using: :btree
- add_index "org_token_permissions", ["token_permission_type_id"], name: "fk_rails_2aa265f538", using: :btree
create_table "orgs", force: :cascade do |t|
- t.string "name", limit: 255
- t.string "abbreviation", limit: 255
- t.string "target_url", limit: 255
- t.string "wayfless_entity", limit: 255
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.integer "parent_id", limit: 4
+ t.string "name"
+ t.string "abbreviation"
+ t.string "target_url"
+ t.string "wayfless_entity"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.integer "parent_id"
t.boolean "is_other"
- t.string "sort_name", limit: 255
- t.text "banner_text", limit: 65535
- t.string "logo_file_name", limit: 255
- t.integer "region_id", limit: 4
- t.integer "language_id", limit: 4
- t.string "logo_uid", limit: 255
- t.string "logo_name", limit: 255
- t.string "contact_email", limit: 255
- t.integer "org_type", limit: 4, default: 0, null: false
- t.string "links", limit: 255, default: "[]"
- t.string "contact_name", limit: 255
- t.boolean "feedback_enabled", default: false
- t.string "feedback_email_subject", limit: 255
- t.text "feedback_email_msg", limit: 65535
+ t.string "sort_name"
+ t.text "banner_text"
+ t.string "logo_file_name"
+ t.integer "region_id"
+ t.integer "language_id"
+ t.string "logo_uid"
+ t.string "logo_name"
+ t.string "contact_email"
+ t.integer "org_type", default: 0, null: false
+ t.string "links", default: "[]"
+ t.string "contact_name"
+ t.boolean "feedback_enabled", default: false
+ t.string "feedback_email_subject"
+ t.text "feedback_email_msg"
end
- add_index "orgs", ["language_id"], name: "fk_rails_5640112cab", using: :btree
- add_index "orgs", ["region_id"], name: "fk_rails_5a6adf6bab", using: :btree
-
create_table "perms", force: :cascade do |t|
- t.string "name", limit: 255
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
+ t.string "name"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
end
add_index "perms", ["name"], name: "index_perms_on_name", using: :btree
add_index "perms", ["name"], name: "index_roles_on_name_and_resource_type_and_resource_id", using: :btree
create_table "phases", force: :cascade do |t|
- t.string "title", limit: 255
- t.text "description", limit: 65535
- t.integer "number", limit: 4
- t.integer "template_id", limit: 4
+ t.string "title"
+ t.text "description"
+ t.integer "number"
+ t.integer "template_id"
t.datetime "created_at"
t.datetime "updated_at"
- t.string "slug", limit: 255
+ t.string "slug"
t.boolean "modifiable"
end
add_index "phases", ["template_id"], name: "index_phases_on_template_id", using: :btree
create_table "plans", force: :cascade do |t|
- t.string "title", limit: 255
- t.integer "template_id", limit: 4
+ t.string "title"
+ t.integer "template_id"
t.datetime "created_at"
t.datetime "updated_at"
- t.string "slug", limit: 255
- t.string "grant_number", limit: 255
- t.string "identifier", limit: 255
- t.text "description", limit: 65535
- t.string "principal_investigator", limit: 255
- t.string "principal_investigator_identifier", limit: 255
- t.string "data_contact", limit: 255
- t.string "funder_name", limit: 255
- t.integer "visibility", limit: 4, null: false
- t.string "data_contact_email", limit: 255
- t.string "data_contact_phone", limit: 255
- t.string "principal_investigator_email", limit: 255
- t.string "principal_investigator_phone", limit: 255
- t.boolean "feedback_requested", default: false
+ t.string "slug"
+ t.string "grant_number"
+ t.string "identifier"
+ t.text "description"
+ t.string "principal_investigator"
+ t.string "principal_investigator_identifier"
+ t.string "data_contact"
+ t.string "funder_name"
+ t.integer "visibility", null: false
+ t.string "data_contact_email"
+ t.string "data_contact_phone"
+ t.string "principal_investigator_email"
+ t.string "principal_investigator_phone"
+ t.boolean "feedback_requested", default: false
end
add_index "plans", ["template_id"], name: "index_plans_on_template_id", using: :btree
create_table "plans_guidance_groups", force: :cascade do |t|
- t.integer "guidance_group_id", limit: 4
- t.integer "plan_id", limit: 4
+ t.integer "guidance_group_id"
+ t.integer "plan_id"
end
- add_index "plans_guidance_groups", ["guidance_group_id"], name: "fk_rails_ec1c5524d7", using: :btree
- add_index "plans_guidance_groups", ["plan_id"], name: "fk_rails_13d0671430", using: :btree
-
create_table "prefs", force: :cascade do |t|
- t.string "settings", limit: 255
- t.integer "user_id", limit: 4
+ t.text "settings"
+ t.integer "user_id"
end
create_table "question_formats", force: :cascade do |t|
- t.string "title", limit: 255
- t.text "description", limit: 65535
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.boolean "option_based", default: false
- t.integer "formattype", limit: 4, default: 0
+ t.string "title"
+ t.text "description"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.boolean "option_based", default: false
+ t.integer "formattype", default: 0
end
create_table "question_options", force: :cascade do |t|
- t.integer "question_id", limit: 4
- t.string "text", limit: 255
- t.integer "number", limit: 4
+ t.integer "question_id"
+ t.string "text"
+ t.integer "number"
t.boolean "is_default"
t.datetime "created_at"
t.datetime "updated_at"
@@ -269,213 +255,209 @@
add_index "question_options", ["question_id"], name: "index_question_options_on_question_id", using: :btree
create_table "questions", force: :cascade do |t|
- t.text "text", limit: 65535
- t.text "default_value", limit: 65535
- t.integer "number", limit: 4
- t.integer "section_id", limit: 4
+ t.text "text"
+ t.text "default_value"
+ t.integer "number"
+ t.integer "section_id"
t.datetime "created_at"
t.datetime "updated_at"
- t.integer "question_format_id", limit: 4
- t.boolean "option_comment_display", default: true
+ t.integer "question_format_id"
+ t.boolean "option_comment_display", default: true
t.boolean "modifiable"
end
- add_index "questions", ["question_format_id"], name: "fk_rails_4fbc38c8c7", using: :btree
add_index "questions", ["section_id"], name: "index_questions_on_section_id", using: :btree
create_table "questions_themes", id: false, force: :cascade do |t|
- t.integer "question_id", limit: 4, null: false
- t.integer "theme_id", limit: 4, null: false
+ t.integer "question_id", null: false
+ t.integer "theme_id", null: false
end
add_index "questions_themes", ["question_id"], name: "index_questions_themes_on_question_id", using: :btree
- add_index "questions_themes", ["theme_id"], name: "fk_rails_0489d5eeba", using: :btree
create_table "regions", force: :cascade do |t|
- t.string "abbreviation", limit: 255
- t.string "description", limit: 255
- t.string "name", limit: 255
- t.integer "super_region_id", limit: 4
+ t.string "abbreviation"
+ t.string "description"
+ t.string "name"
+ t.integer "super_region_id"
end
create_table "roles", force: :cascade do |t|
- t.integer "user_id", limit: 4
- t.integer "plan_id", limit: 4
+ t.integer "user_id"
+ t.integer "plan_id"
t.datetime "created_at"
t.datetime "updated_at"
- t.integer "access", limit: 4, default: 0, null: false
- t.boolean "active", default: true
+ t.integer "access", default: 0, null: false
+ t.boolean "active", default: true
end
add_index "roles", ["plan_id"], name: "index_roles_on_plan_id", using: :btree
add_index "roles", ["user_id"], name: "index_roles_on_user_id", using: :btree
create_table "sections", force: :cascade do |t|
- t.string "title", limit: 255
- t.text "description", limit: 65535
- t.integer "number", limit: 4
+ t.string "title"
+ t.text "description"
+ t.integer "number"
t.datetime "created_at"
t.datetime "updated_at"
t.boolean "published"
- t.integer "phase_id", limit: 4
+ t.integer "phase_id"
t.boolean "modifiable"
end
add_index "sections", ["phase_id"], name: "index_sections_on_phase_id", using: :btree
create_table "settings", force: :cascade do |t|
- t.string "var", limit: 255, null: false
- t.text "value", limit: 65535
- t.integer "target_id", limit: 4, null: false
- t.string "target_type", limit: 255, null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
+ t.string "var", null: false
+ t.text "value"
+ t.integer "target_id", null: false
+ t.string "target_type", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
end
add_index "settings", ["target_type", "target_id", "var"], name: "index_settings_on_target_type_and_target_id_and_var", unique: true, using: :btree
create_table "splash_logs", force: :cascade do |t|
- t.string "destination", limit: 255
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
+ t.string "destination"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
end
create_table "templates", force: :cascade do |t|
- t.string "title", limit: 255
- t.text "description", limit: 65535
+ t.string "title"
+ t.text "description"
t.boolean "published"
- t.integer "org_id", limit: 4
- t.string "locale", limit: 255
+ t.integer "org_id"
+ t.string "locale"
t.boolean "is_default"
t.datetime "created_at"
t.datetime "updated_at"
- t.integer "version", limit: 4
- t.integer "visibility", limit: 4
- t.integer "customization_of", limit: 4
- t.integer "dmptemplate_id", limit: 4
+ t.integer "version"
+ t.integer "visibility"
+ t.integer "customization_of"
+ t.integer "dmptemplate_id"
t.boolean "migrated"
- t.boolean "dirty", default: false
+ t.boolean "dirty", default: false
+ t.string "links", default: "{\"funder\":[], \"sample_plan\":[]}"
end
add_index "templates", ["org_id", "dmptemplate_id"], name: "template_organisation_dmptemplate_index", using: :btree
add_index "templates", ["org_id"], name: "index_templates_on_org_id", using: :btree
create_table "themes", force: :cascade do |t|
- t.string "title", limit: 255
- t.text "description", limit: 65535
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.string "locale", limit: 255
+ t.string "title"
+ t.text "description"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.string "locale"
end
create_table "themes_in_guidance", id: false, force: :cascade do |t|
- t.integer "theme_id", limit: 4
- t.integer "guidance_id", limit: 4
+ t.integer "theme_id"
+ t.integer "guidance_id"
end
add_index "themes_in_guidance", ["guidance_id"], name: "index_themes_in_guidance_on_guidance_id", using: :btree
add_index "themes_in_guidance", ["theme_id"], name: "index_themes_in_guidance_on_theme_id", using: :btree
create_table "token_permission_types", force: :cascade do |t|
- t.string "token_type", limit: 255
- t.text "text_description", limit: 65535
+ t.string "token_type"
+ t.text "text_description"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "user_identifiers", force: :cascade do |t|
- t.string "identifier", limit: 255
+ t.string "identifier"
t.datetime "created_at"
t.datetime "updated_at"
- t.integer "user_id", limit: 4
- t.integer "identifier_scheme_id", limit: 4
+ t.integer "user_id"
+ t.integer "identifier_scheme_id"
end
- add_index "user_identifiers", ["identifier_scheme_id"], name: "fk_rails_fe95df7db0", using: :btree
add_index "user_identifiers", ["user_id"], name: "index_user_identifiers_on_user_id", using: :btree
create_table "users", force: :cascade do |t|
- t.string "firstname", limit: 255
- t.string "surname", limit: 255
- t.string "email", limit: 255, default: "", null: false
- t.string "orcid_id", limit: 255
- t.string "shibboleth_id", limit: 255
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.string "encrypted_password", limit: 255, default: ""
- t.string "reset_password_token", limit: 255
+ t.string "firstname"
+ t.string "surname"
+ t.string "email", default: "", null: false
+ t.string "orcid_id"
+ t.string "shibboleth_id"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.string "encrypted_password", default: ""
+ t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
- t.integer "sign_in_count", limit: 4, default: 0
+ t.integer "sign_in_count", default: 0
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
- t.string "current_sign_in_ip", limit: 255
- t.string "last_sign_in_ip", limit: 255
- t.string "confirmation_token", limit: 255
+ t.string "current_sign_in_ip"
+ t.string "last_sign_in_ip"
+ t.string "confirmation_token"
t.datetime "confirmed_at"
t.datetime "confirmation_sent_at"
- t.string "invitation_token", limit: 255
+ t.string "invitation_token"
t.datetime "invitation_created_at"
t.datetime "invitation_sent_at"
t.datetime "invitation_accepted_at"
- t.string "other_organisation", limit: 255
+ t.string "other_organisation"
t.boolean "accept_terms"
- t.integer "org_id", limit: 4
- t.string "api_token", limit: 255
- t.integer "invited_by_id", limit: 4
- t.string "invited_by_type", limit: 255
- t.integer "language_id", limit: 4
- t.string "recovery_email", limit: 255
+ t.integer "org_id"
+ t.string "api_token"
+ t.integer "invited_by_id"
+ t.string "invited_by_type"
+ t.integer "language_id"
+ t.string "recovery_email"
end
add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
- add_index "users", ["language_id"], name: "fk_rails_45f4f12508", using: :btree
add_index "users", ["org_id"], name: "index_users_on_org_id", using: :btree
create_table "users_perms", id: false, force: :cascade do |t|
- t.integer "user_id", limit: 4
- t.integer "perm_id", limit: 4
+ t.integer "user_id"
+ t.integer "perm_id"
end
- add_index "users_perms", ["perm_id"], name: "fk_rails_457217c31c", using: :btree
add_index "users_perms", ["user_id"], name: "index_users_perms_on_user_id", using: :btree
- add_foreign_key "annotations", "orgs"
- add_foreign_key "annotations", "questions"
- add_foreign_key "answers", "plans"
- add_foreign_key "answers", "questions"
- add_foreign_key "answers", "users"
- add_foreign_key "answers_question_options", "answers"
- add_foreign_key "answers_question_options", "question_options"
- add_foreign_key "guidance_groups", "orgs"
- add_foreign_key "guidances", "guidance_groups"
- add_foreign_key "notes", "answers"
- add_foreign_key "notes", "users"
- add_foreign_key "org_identifiers", "identifier_schemes"
- add_foreign_key "org_identifiers", "orgs"
- add_foreign_key "org_token_permissions", "orgs"
- add_foreign_key "org_token_permissions", "token_permission_types"
- add_foreign_key "orgs", "languages"
- add_foreign_key "orgs", "regions"
- add_foreign_key "phases", "templates"
- add_foreign_key "plans", "templates"
- add_foreign_key "plans_guidance_groups", "guidance_groups"
- add_foreign_key "plans_guidance_groups", "plans"
- add_foreign_key "question_options", "questions"
- add_foreign_key "questions", "question_formats"
- add_foreign_key "questions", "sections"
- add_foreign_key "questions_themes", "questions"
- add_foreign_key "questions_themes", "themes"
- add_foreign_key "roles", "plans"
- add_foreign_key "roles", "users"
- add_foreign_key "sections", "phases"
- add_foreign_key "templates", "orgs"
- add_foreign_key "themes_in_guidance", "guidances"
- add_foreign_key "themes_in_guidance", "themes"
- add_foreign_key "user_identifiers", "identifier_schemes"
- add_foreign_key "user_identifiers", "users"
- add_foreign_key "users", "languages"
- add_foreign_key "users", "orgs"
- add_foreign_key "users_perms", "perms"
+ add_foreign_key "annotations", "orgs"
+ add_foreign_key "annotations", "questions"
+ add_foreign_key "answers", "plans"
+ add_foreign_key "answers", "questions"
+ add_foreign_key "answers", "users"
+ add_foreign_key "answers_question_options", "answers"
+ add_foreign_key "answers_question_options", "question_options"
+ add_foreign_key "guidance_groups", "orgs"
+ add_foreign_key "guidances", "guidance_groups"
+ add_foreign_key "notes", "answers"
+ add_foreign_key "notes", "users"
+ add_foreign_key "org_identifiers", "identifier_schemes"
+ add_foreign_key "org_identifiers", "orgs"
+ add_foreign_key "org_token_permissions", "orgs"
+ add_foreign_key "org_token_permissions", "token_permission_types"
+ add_foreign_key "orgs", "languages"
+ add_foreign_key "orgs", "regions"
+ add_foreign_key "phases", "templates"
+ add_foreign_key "plans", "templates"
+ add_foreign_key "plans_guidance_groups", "guidance_groups"
+ add_foreign_key "plans_guidance_groups", "plans"
+ add_foreign_key "question_options", "questions"
+ add_foreign_key "questions", "question_formats"
+ add_foreign_key "questions", "sections"
+ add_foreign_key "questions_themes", "questions"
+ add_foreign_key "questions_themes", "themes"
+ add_foreign_key "roles", "plans"
+ add_foreign_key "roles", "users"
+ add_foreign_key "sections", "phases"
+ add_foreign_key "templates", "orgs"
+ add_foreign_key "themes_in_guidance", "guidances"
+ add_foreign_key "themes_in_guidance", "themes"
+ add_foreign_key "user_identifiers", "identifier_schemes"
+ add_foreign_key "user_identifiers", "users"
+ add_foreign_key "users", "languages"
+ add_foreign_key "users", "orgs"
+ add_foreign_key "users_perms", "perms"
add_foreign_key "users_perms", "users"
-end
\ No newline at end of file
+end
diff --git a/lib/assets/javascripts/application.js b/lib/assets/javascripts/application.js
index d5868f6..8be3644 100644
--- a/lib/assets/javascripts/application.js
+++ b/lib/assets/javascripts/application.js
@@ -1,6 +1,7 @@
// Generic JS that is applicable across multiple pages
import './utils/paginable';
import './utils/linkHelper';
+import './utils/links';
import './utils/tabHelper';
import './utils/tableHelper';
import './utils/tooltipHelper';
diff --git a/lib/assets/javascripts/utils/links.js b/lib/assets/javascripts/utils/links.js
new file mode 100644
index 0000000..41999a0
--- /dev/null
+++ b/lib/assets/javascripts/utils/links.js
@@ -0,0 +1,80 @@
+import 'number-to-text/converters/en-us';
+import { convertToText } from 'number-to-text/index';
+import { isFunction } from './isType';
+
+const getLinks = elem =>
+ $(elem).find('.link').map((i, el) => {
+ const linkVal = $(el).find('input[name="link_link"]').val();
+ const textVal = $(el).find('input[name="link_text"]').val();
+ if (linkVal.length > 0 && textVal.length > 0) {
+ return { link: linkVal, text: textVal };
+ }
+ return undefined;
+ }).get();
+
+/*
+ * Iterates through any links class found and for each one found,
+ * executes the function cb by passing two parameters: the data-context value
+ * and value param being an Array of link objects (e.g. [{ link: String, text: String }, ...])
+ * that represent the values for the input texts introduced.
+*/
+export const eachLinks = (cb) => {
+ if (isFunction(cb)) {
+ $('.links').each((i, el) => {
+ cb($(el).attr('data-context'), getLinks(el));
+ });
+ eachLinks.done();
+ }
+ return eachLinks;
+};
+eachLinks.done = (cb) => {
+ if (isFunction(cb)) {
+ cb();
+ }
+};
+
+$(() => {
+ const regExp = /([^\d]*)(\d)+/;
+ const replacer = (match, p1, p2) => `${p1}${(p2 * 1) + 1}`;
+ const replacerFor = (i, el) => $(el).attr('for', $(el).attr('for').replace(regExp, replacer));
+ const replacerId = (i, el) => $(el).attr('id', $(el).attr('id').replace(regExp, replacer));
+ const changeIds = (jqueryElem) => {
+ jqueryElem.find('label').each(replacerFor);
+ jqueryElem.find('input[type="text"]').each(replacerId);
+ };
+ const clearVals = (jqueryElem) => {
+ jqueryElem.find('input[type="text"]').each((i, el) => {
+ $(el).val('');
+ });
+ };
+ const linksLength = jqueryElem => jqueryElem.closest('.links').find('.link').length;
+ const maxNumberLinks = jqueryElem => jqueryElem.closest('.links').attr('data-max-number-links') * 1;
+
+ $('.links').on('click', '.new', (e) => {
+ e.preventDefault();
+ const target = $(e.target);
+ const max = maxNumberLinks(target);
+ if (linksLength(target) < max) {
+ const lastLink = target.closest('.links').find('.link').last();
+ const clonedLink = lastLink.clone();
+ changeIds(clonedLink);
+ clearVals(clonedLink);
+ lastLink.after(clonedLink);
+ }
+ });
+ $('.links').on('click', '.delete', (e) => {
+ e.preventDefault();
+ const target = $(e.target);
+ if (linksLength(target) > 1) {
+ target.closest('.link').remove();
+ }
+ });
+
+ $('.links').find('.max-number-links').each((i, el) => {
+ const target = $(el);
+ const max = target.closest('.links').attr('data-max-number-links');
+ target.text(convertToText(max).toLowerCase());
+ });
+});
+
+export { eachLinks as default };
diff --git a/lib/assets/javascripts/views/templates/edit.js b/lib/assets/javascripts/views/templates/edit.js
index eb4de0d..448a8c7 100644
--- a/lib/assets/javascripts/views/templates/edit.js
+++ b/lib/assets/javascripts/views/templates/edit.js
@@ -1,4 +1,5 @@
import { Tinymce } from '../../utils/tinymce';
+import { eachLinks } from '../../utils/links';
$(() => {
Tinymce.init({ selector: '.template' });
@@ -7,4 +8,12 @@
$(e.target).closest('.template_edit').hide();
$(e.target).closest('.tab-pane').find('.template_show').show();
});
+ $('.edit_template').on('submit', () => {
+ const links = {};
+ eachLinks((ctx, value) => {
+ links[ctx] = value;
+ }).done(() => {
+ $('#template-links').val(JSON.stringify(links));
+ });
+ });
});
diff --git a/test/unit/concerns/json_link_validator_test.rb b/test/unit/concerns/json_link_validator_test.rb
new file mode 100644
index 0000000..5148133
--- /dev/null
+++ b/test/unit/concerns/json_link_validator_test.rb
@@ -0,0 +1,50 @@
+require 'test_helper'
+
+class JSONLinkValidatorTest < ActiveSupport::TestCase
+ include JSONLinkValidator
+
+ test 'returns nil for a non-string value passed' do
+ assert_nil(parse_links(nil))
+ assert_nil(parse_links(true))
+ assert_nil(parse_links(false))
+ assert_nil(parse_links([]))
+ assert_nil(parse_links({}))
+ assert_nil(parse_links(1))
+ assert_nil(parse_links(1.1))
+ end
+
+ test 'returns nil for a non-array object parsed' do
+ assert_nil(parse_links("string"))
+ assert_nil(parse_links("1"))
+ assert_nil(parse_links("{}"))
+ assert_nil(parse_links("true"))
+ assert_nil(parse_links("false"))
+ assert_nil(parse_links("null"))
+ assert(parse_links("[]"))
+ end
+
+ test 'returns nil for a non-hash item within the array parsed' do
+ assert_nil(parse_links("[{}, \"\"]"))
+ end
+
+ test 'returns nil for a non-valid link key at array item' do
+ assert_nil(parse_links("[{\"link\": \"\", \"text\": \"\"}, {}]"))
+ end
+
+ test 'returns nil for a non-valid text key at array item' do
+ assert_nil(parse_links("[{\"link\": \"\", \"text\": \"\"}, {\"link\": \"\"}]"))
+ end
+
+ test 'returns nil for a non-string value for link key at array item' do
+ assert_nil(parse_links("[{\"link\": \"\", \"text\": \"\"}, {\"link\": [], \"text\": \"\"}]"))
+ end
+
+ test 'returns nil for a non-string value for a text key at array item' do
+ assert_nil(parse_links("[{\"link\": \"\", \"text\": \"\"}, {\"link\": \"\", \"text\": []}]"))
+ end
+
+ test 'returns Array for a valid array of link objects' do
+ assert_equal([{ "link" => "", "text" => ""}, { "link" => "", "text" => ""}],
+ parse_links("[{\"link\": \"\", \"text\": \"\"}, {\"link\": \"\", \"text\": \"\"}]"))
+ end
+end
\ No newline at end of file
diff --git a/test/unit/template_test.rb b/test/unit/template_test.rb
index b9ccb42..562b319 100644
--- a/test/unit/template_test.rb
+++ b/test/unit/template_test.rb
@@ -120,4 +120,31 @@
verify_belongs_to_relationship(tmplt, @org)
end
+ test 'should be invalid when links is not a hash' do
+ t = Template.new(title: 'My test', version: 1, org: @org)
+ t.links = []
+ refute(t.valid?)
+ assert_equal(['A hash is expected for links'], t.errors.messages[:links])
+ end
+
+ test 'should be invalid when links hash does not have the expected keys' do
+ t = Template.new(title: 'My test', version: 1, org: @org)
+ t.links = { "foo" => [], "bar" => [] }
+ refute(t.valid?)
+ assert_equal(['A key funder is expected for links hash', 'A key sample_plan is expected for links hash'], t.errors.messages[:links])
+ end
+
+ test 'should be invalid when links hash keys are not compliant to object links format' do
+ t = Template.new(title: 'My test', version: 1, org: @org)
+ t.links = { "funder" => [{}], "sample_plan" => [{}] }
+ refute(t.valid?)
+ assert_equal(['The key funder does not have a valid set of object links', 'The key sample_plan does not have a valid set of object links'], t.errors.messages[:links])
+ end
+
+ test 'should be valid when links hash keys are compliant to object links format' do
+ t = Template.new(title: 'My test', version: 1, org: @org)
+ t.links = { "funder" => [{ "link" => "foo", "text" => "bar" }], "sample_plan" => [] }
+ assert(t.valid?)
+ assert_equal(nil, t.errors.messages[:links])
+ end
end