Skip to content

Commit

Permalink
TriplePoweredProperties functionality to fetch and cache RDF, fronten…
Browse files Browse the repository at this point in the history
…d and backend URL validation, and frontend UI for viewing labels
  • Loading branch information
Josh Gum committed Aug 12, 2016
1 parent d9a87f8 commit d6cb237
Show file tree
Hide file tree
Showing 24 changed files with 603 additions and 8 deletions.
4 changes: 2 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ GEM
erubis (~> 2.7.0)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
active-fedora (10.2.0)
active-fedora (10.2.1)
active-triples (~> 0.7.1)
activemodel (>= 4.2, < 6)
activesupport (>= 4.2.4, < 6)
Expand Down Expand Up @@ -806,4 +806,4 @@ DEPENDENCIES
webmock

BUNDLED WITH
1.11.2
1.12.1
2 changes: 2 additions & 0 deletions app/assets/javascripts/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,5 @@
//= require blacklight/blacklight
//= require sufia
//= require_tree .
//= require_tree ../../../lib/assets/javascripts/triple_powered_properties/.
//= require triple_powered_properties
1 change: 1 addition & 0 deletions app/assets/stylesheets/application.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@
*
*= require_tree .
*= require_self
*= require triple_powered_properties
*/
4 changes: 1 addition & 3 deletions app/forms/curation_concerns/generic_work_form.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# Generated via
# `rails generate curation_concerns:work GenericWork`
module CurationConcerns
class GenericWorkForm < Sufia::Forms::WorkForm
class GenericWorkForm < ScholarsArchive::TriplePoweredProperties::Forms::WorkForm
self.model_class = ::GenericWork
self.terms += [:resource_type, :spatial, :nested_geo_points, :nested_geo_bbox, :nested_geo_location]

Expand Down
4 changes: 4 additions & 0 deletions app/models/generic_work.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ class GenericWork < ActiveFedora::Base
include ::CurationConcerns::WorkBehavior
include ::CurationConcerns::BasicMetadata
include Sufia::WorkBehavior
include ScholarsArchive::TriplePoweredProperties::WorkBehavior

self.human_readable_type = 'Work'
# Change this to restrict which works can be added as a child.
# self.valid_child_concerns = []
Expand All @@ -17,6 +19,8 @@ class GenericWork < ActiveFedora::Base

validates :title, presence: { message: 'Your work must have a title.' }

self.triple_powered_properties = [ :subject, :based_near, :creator ]

def to_solr(solr_doc = {})
super.tap do |doc|
doc[ActiveFedora.index_field_mapper.solr_name("nested_geo_points_label", :symbol)] = nested_geo_points.flat_map(&:label).select(&:present?)
Expand Down
9 changes: 9 additions & 0 deletions app/views/records/edit_fields/_based_near.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<%= f.input key,
as: :triple_powered_property,
input_html: {
class: 'form-control',
data: { 'autocomplete-url' => "/authorities/search/geonames",
'autocomplete' => key }
},
required: f.object.required?(key),
multi_value: f.object.class.multiple?(key)%>
14 changes: 11 additions & 3 deletions app/views/records/edit_fields/_default.html.erb
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
<% if f.object.class.multiple? key %>
<%= f.input key, as: :multi_value, input_html: { class: 'form-control' }, required: f.object.required?(key) %>
<% if f.object.has_triple_powered_property?(key) %>
<%= f.input key,
as: :triple_powered_property,
input_html: { class: 'form-control' },
required: f.object.required?(key),
multi_value: f.object.class.multiple?(key) %>
<% else %>
<%= f.input key, required: f.object.required?(key) %>
<% if f.object.class.multiple? key %>
<%= f.input key, as: :multi_value, input_html: { class: 'form-control' }, required: f.object.required?(key) %>
<% else %>
<%= f.input key, required: f.object.required?(key) %>
<% end %>
<% end %>
9 changes: 9 additions & 0 deletions app/views/records/edit_fields/_subject.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<%= f.input key,
as: :triple_powered_property,
input_html: {
class: 'form-control',
data: { 'autocomplete-url' => "/authorities/search/local/subjects",
'autocomplete' => key }
},
required: f.object.required?(key),
multi_value: f.object.class.multiple?(key)%>
1 change: 1 addition & 0 deletions config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class Application < Rails::Application
g.test_framework :rspec, :spec => true
end

config.autoload_paths << Rails.root.join('lib')

# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
Expand Down
3 changes: 3 additions & 0 deletions config/initializers/simple_form_triple_powered_properties.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
SimpleForm.setup do |config|
config.custom_inputs_namespaces << "ScholarsArchive::TriplePoweredProperties::Inputs"
end
6 changes: 6 additions & 0 deletions lib/assets/javascripts/triple_powered_properties.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Blacklight.onLoad(function() {
var tpp = require('triple_powered_properties/control');
$('.triple_powered_property').each(function() {
new tpp.TriplePoweredPropertyControl(this);
});
});
16 changes: 16 additions & 0 deletions lib/assets/javascripts/triple_powered_properties/button.es6
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export class TriplePoweredPropertyButton {
constructor(element) {
this.$element = $(element);
this.$list = this.$element.siblings(".list:first");

this.$element.on('click', (e) => {
this.$list.toggle((e) => {
if(this.$list.is(":visible")){
this.$element.text(this.$element.data('hide-all'));
} else {
this.$element.text(this.$element.data('show-all'));
}
})
});
}
}
64 changes: 64 additions & 0 deletions lib/assets/javascripts/triple_powered_properties/control.es6
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { TriplePoweredPropertyButton } from './button'

export class TriplePoweredPropertyControl {
constructor(element) {
// Don't try to make sense of this URL regex, it will fry your brain. See: http://stackoverflow.com/a/9284473
this.urlPattern = new RegExp(/^(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,}))\.?)(?::\d{2,5})?(?:[/?#]\S*)?$/,'i');
this.$element = $(element);

// Any of the triple powered property label lists that were rendered need to have their toggle button
// wired up to provide the collapsable effect
this.$element.find("span.toggle").each((i, el) => {
new TriplePoweredPropertyButton(el);
});

this.bindBlurHandler();
this.bindKeyupHandler();
this.bindManagedFieldAddHandler();
}

// delegate blur events to the form-control that fired them, works with new fields that are added dynamically by way
// of the "+ more" button managed by hydra-editor
bindBlurHandler() {
this.$element.on("blur", "input.form-control", (e) => {
var $input = $(e.currentTarget);

// disallow space characters in triple powered property fields, these will not be valid URIs
$input.val($input.val().replace(' ', ''));

// hide a previously displayed warning to revalidate the field for URLness
var $warning = $(e.currentTarget).siblings('.has-warning');
$warning.text("").addClass("hidden");

// input value has text and doesn't pass the URL regex test
if($input.val() !== '' && !this.urlPattern.test($(e.currentTarget).val())){
$warning.text("Invalid URL").removeClass("hidden");
}
});
}

// Remove the warning message when a user types into the "new" field managed by hydra-editor. This hides the warning
// message "you cannot add a blank field" if it was displayed to the user previously.
bindKeyupHandler() {
this.$element.on("keyup", "input.form-control", (e) => {
var $input = $(e.currentTarget);
var $li = $input.parents("li");

// This warning message is injected by hydra-editor for mult_value fields wrapped in a UL list
if($li){
if($li.find(".field-controls .btn.add").length > 0 && $input.val().length > 0) {
$li.siblings(".has-warning").remove();
}
}
});
}

// After the add button is clicked, hydra-editor clones the LI and clears its fields before injecting the new LI
// to the end of the list.. Make sure this new LI has the URL validation warning field hidden since it is expecting
// the user to enter a new one
bindManagedFieldAddHandler(){
this.$element.on("click", ".btn.add", (e) => {
$(e.delegateTarget).find("ul li:last .has-warning").addClass("hidden");
});
}
}
35 changes: 35 additions & 0 deletions lib/assets/stylesheets/triple_powered_properties.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
.triple_powered_property {
position: relative;
li.field-wrapper {
span.field-controls { vertical-align: top; }
div.message.has-warning { display: inline-block; margin-top: 5px; }
}
.triple_powered_labels {
display: inline-block;
padding: 0 5px;
width: 100%;
margin-top: 2px;
span[itemprop='label'] {
width: 100%;
float: left;
}
span.error { color: #d9534f; }
.toggle {
font-size: 10px;
cursor: pointer;
font-weight: normal;
background-color: #337ab7;
}
.toggle:hover{ background-color: #2e6da4; }
ul.list {
display: none;
margin: 0;
padding: 0;
list-style: none;
li {
float: left;
width: 100%;
}
}
}
}
14 changes: 14 additions & 0 deletions lib/scholars_archive/triple_powered_properties/errors.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module ScholarsArchive::TriplePoweredProperties
module Errors

##
# Base error
class TriplePoweredPropertyError < StandardError
end

##
# Invalid URL
class InvalidUrlError < TriplePoweredPropertyError
end
end
end
12 changes: 12 additions & 0 deletions lib/scholars_archive/triple_powered_properties/forms/work_form.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module ScholarsArchive::TriplePoweredProperties::Forms
class WorkForm < Sufia::Forms::WorkForm

##
# Checks the model of this form to evaluate if a property is triple powered
# @param property [Symbol] the models property to be evaluated
# @return [Boolean] true if the models triple_powered_properties includes the property
def has_triple_powered_property?(property)
self.model.triple_powered_properties.include?(property)
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
require 'uri'

module ScholarsArchive::TriplePoweredProperties
class HasUrlValidator < ActiveModel::Validator

##
# Evaluate each triple powered property value to ensure it is a valid URL
#
# @param record [ActiveModel] the model being validated
def validate(record)
return if record.triple_powered_properties.empty?

record.triple_powered_properties.each do |prop|
record[prop].each do |value|
begin
uri = URI.parse(value)
rescue
record.errors[prop] << "#{value} is not a URL"
else
record.errors[prop] << "#{value} is invalid" unless uri.kind_of?(URI::HTTP)
end
end
end
end
end
end
Loading

0 comments on commit d6cb237

Please sign in to comment.