Class: AspaceFormHelper::FormContext

Inherits:
Object
  • Object
show all
Defined in:
frontend/app/helpers/aspace_form_helper.rb

Direct Known Subclasses

ReadOnlyContext

Defined Under Namespace

Classes: BaseDefinition, JSONModelDefinition, ReadOnlyContext

Constant Summary collapse

PROPERTIES_TO_EXCLUDE_FROM_READ_ONLY_VIEW =
["jsonmodel_type", "lock_version", "_resolved", "uri", "ref", "create_time", "system_mtime", "user_mtime", "created_by", "last_modified_by", "sort_name_auto_generate", "suppressed", "display_string", "file_uri", "agent_person_id", "agent_software_id", "agent_family_id", "agent_corporate_entity_id", "id"]

Instance Method Summary collapse

Constructor Details

#initialize(name, values_from, parent) ⇒ FormContext

Returns a new instance of FormContext.



16
17
18
19
20
21
22
# File 'frontend/app/helpers/aspace_form_helper.rb', line 16

def initialize(name, values_from, parent)
  values = values_from.is_a?(JSONModelType) ? values_from.to_hash(:raw) : values_from

  @forms = FormHelpers.new
  @parent = parent
  @context = [[name, values]]
end

Instance Method Details

#[](key) ⇒ Object



218
219
220
# File 'frontend/app/helpers/aspace_form_helper.rb', line 218

def [](key)
  obj[key]
end

#add_tooltip_options(tooltip, options) ⇒ Object



547
548
549
550
551
552
553
554
555
556
557
558
# File 'frontend/app/helpers/aspace_form_helper.rb', line 547

def add_tooltip_options(tooltip, options)
  options[:title] = tooltip
  options['data-placement'] = 'bottom'
  options['data-boundary'] = 'viewport'
  options['data-html'] = true
  options['data-delay'] = 500
  options['data-trigger'] = 'manual'
  options['data-template'] = '<div class="tooltip archivesspace-help"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
  options[:class] ||= ''
  options[:class] += ' has-tooltip'
  options
end

#allowable_types_for(name) ⇒ Object



726
727
728
729
730
731
732
# File 'frontend/app/helpers/aspace_form_helper.rb', line 726

def allowable_types_for(name)
  if @active_template && @parent.templates[@active_template]
    @parent.templates[@active_template][:definition].allowable_types_for(name)
  else
    []
  end
end

#array_for_textarea(values) ⇒ Object



1363
1364
1365
1366
# File 'frontend/app/helpers/aspace_form_helper.rb', line 1363

def array_for_textarea(values)
  return values unless values.is_a?(Array)
  values.join("\n")
end

#button_with_tooltip(tooltip, content, div_classes = [], button_classes = [], use_default_btn_classes = true) ⇒ Object



560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
# File 'frontend/app/helpers/aspace_form_helper.rb', line 560

def button_with_tooltip(tooltip, content, div_classes = [], button_classes = [], use_default_btn_classes = true)
  div_classes = div_classes + ["btn-with-tooltip"]

  button_classes = use_default_btn_classes ? button_classes + ["btn", "btn-sm"] : button_classes

  div_options = {:class => div_classes.join(' ')}
  add_tooltip_options(tooltip, div_options)

  button_options = {:class => button_classes.join(' ')}

  div_attr_string = div_options.map {|k, v| '%s="%s"' % [CGI::escapeHTML(k.to_s),
                                                 CGI::escapeHTML(v.to_s)]}
                       .join(' ')

  button_attr_string = button_options.map {|k, v| '%s="%s"' % [CGI::escapeHTML(k.to_s),
                                                 CGI::escapeHTML(v.to_s)]}
                       .join(' ')

  "<div #{div_attr_string}><button #{button_attr_string}>#{content}</button></div>".html_safe
end

#checkbox(name, opts = {}, default = true, force_checked = false) ⇒ Object



585
586
587
588
589
590
# File 'frontend/app/helpers/aspace_form_helper.rb', line 585

def checkbox(name, opts = {}, default = true, force_checked = false)
  options = {:id => "#{id_for(name)}", :type => "checkbox", :name => path(name), :value => 1}
  options[:checked] = "checked" if force_checked or (obj[name] === true) or (obj[name].is_a? String and obj[name].start_with?("true")) or (obj[name] === "1") or (obj[name].nil? and default)

  @forms.tag("input", options.merge(opts), false, false)
end

#checkboxes_for_oai_sets(set_json, value_list) ⇒ Object

takes a JSON representation of the current options selected and the list of archival_record_level enums returns HTML for a set of checkboxes representing current selected and deselected sets for OAI export



594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
# File 'frontend/app/helpers/aspace_form_helper.rb', line 594

def checkboxes_for_oai_sets(set_json, value_list)
  return "" if value_list == nil
  # when called by #new, set_json will be nil.
  if set_json
    set_arry = JSON::parse(set_json)
  else
    set_arry = []
  end

  html = ""

  html << "<div class='form-group'>"
  html << label("oai_sets_available", {}, ["control-label", "col-sm-2"])
  html << "<div class='col-sm-9'>"
  html << "<ul class='checkbox-list'>"
  value_list['enumeration_values'].each do |v|
    # if we have an empty list of checkboxes, assume all sets are enabled.
    # otherwise, a checkbox is on if it's the in the list we get from the backend.
    checked = set_arry.include?(v['id'].to_s) || set_arry.length == 0

    html << "<li class='list-group-item'>"
    html << "<div class='checkbox'>"
    html << "<label>"
    html << "<input id=\"#{v['id']}\" name=\"sets[#{v['id']}]\" type=\"checkbox\" "
    if checked
      html << "checked=\"checked\" "
    end

    if readonly?
      html << "disabled />"
    else
      html << "/>"
    end # of checkbox tag

    html << "#{v['value']}"
    html << "</label>"
    html << "</div>"
    html << "</li>"
  end
  html << "</ul>"
  html << "</div>" #col-sm-9
  html << "</div>" #form-group

  return html.html_safe
end

#clean_mixed_content(content, root_url) ⇒ Object



30
31
32
33
34
35
# File 'frontend/app/helpers/aspace_form_helper.rb', line 30

def clean_mixed_content(content, root_url)
  content = content.to_s
  return content if content.blank?

  MixedContentParser::parse(content, root_url, { :wrap_blocks => false } ).to_s.html_safe
end

#combobox(name, options, opts = {}) ⇒ Object



408
409
410
411
# File 'frontend/app/helpers/aspace_form_helper.rb', line 408

def combobox(name, options, opts = {})
  select(name, options, opts.merge({ :"data-combobox" => true,
                                     :id => id_for(name) }))
end

#current_contextObject



208
209
210
# File 'frontend/app/helpers/aspace_form_helper.rb', line 208

def current_context
  @context.last
end

#current_idObject



262
263
264
# File 'frontend/app/helpers/aspace_form_helper.rb', line 262

def current_id
  path(nil).gsub(/[\[\]]/, '_')
end

#custom_report_template_limit_optionsObject



1359
1360
1361
# File 'frontend/app/helpers/aspace_form_helper.rb', line 1359

def custom_report_template_limit_options
  [100, 500, 1000, 5000, 10000, 50000]
end

#default_for(name) ⇒ Object



717
718
719
720
721
722
723
# File 'frontend/app/helpers/aspace_form_helper.rb', line 717

def default_for(name)
  if @active_template && @parent.templates[@active_template]
    @parent.templates[@active_template][:definition].default_for(name)
  else
    nil
  end
end

#define_template(name, definition = nil, &block) ⇒ Object

we expect the template to be defined in a view context that will have the @required_fields object if applicable. We add it to the template hash because the object will be out of scope when the JS templates are emitted.



1199
1200
1201
1202
1203
1204
1205
1206
# File 'frontend/app/helpers/aspace_form_helper.rb', line 1199

def define_template(name, definition = nil, &block)
  @templates ||= {}
  @templates[name] = {
    :block => block,
    :definition => (definition || BaseDefinition.new),
    :requirements => @required_fields
  }
end

#emit_template(name, *args) ⇒ Object



505
506
507
508
509
510
511
512
513
514
# File 'frontend/app/helpers/aspace_form_helper.rb', line 505

def emit_template(name, *args)
  if !@parent.templates[name]
    raise "No such template: #{name.inspect}"
  end

  old = @active_template
  @active_template = name
  @parent.templates[name][:block].call(self, *args)
  @active_template = old
end

#error_params(exceptions) ⇒ Object



1353
1354
1355
1356
1357
# File 'frontend/app/helpers/aspace_form_helper.rb', line 1353

def error_params(exceptions)
  {
    :"data-form-errors" => (exceptions && exceptions.keys[0])
  }
end

#exceptions_for_js(exceptions) ⇒ Object



242
243
244
245
246
247
248
249
250
251
252
253
# File 'frontend/app/helpers/aspace_form_helper.rb', line 242

def exceptions_for_js(exceptions)
  result = {}
  [:errors, :warnings].each do |condition|
    if exceptions[condition]
      result[condition] = exceptions[condition].keys.map {|property|
        id_for_javascript(property)
      }
    end
  end

  result.to_json.html_safe
end

#fields_for(object, context_name, &block) ⇒ Object

renders a single template containing form elements.



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'frontend/app/helpers/aspace_form_helper.rb', line 141

def fields_for(object, context_name, &block)
  result = ""

  push(context_name, object) do
    result << hidden_input("lock_version", object["lock_version"]) if object
    result << @parent.capture(object, &block)
  end

  extra_classes = ""

  # ANW-429: Add class to top level div so element can be switched out by JS based on user form input
  # TODO: refactor
  extra_classes += "sdl-subrecord-form" if context_name == "structured_date_range" || context_name == "structured_date_single"


  ("<div data-name-path=\"#{set_index(self.path(context_name), '${index}')}\" " +
    " data-id-path=\"#{id_for(set_index(self.path(context_name), '${index}'), false)}\" " +
    " class=\"subrecord-form-fields-for mt-1 #{extra_classes}\">#{result}</div>").html_safe
end

#form_context(name, values_from = {}, &body) ⇒ Object



1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
# File 'frontend/app/helpers/aspace_form_helper.rb', line 1018

def form_context(name, values_from = {}, &body)
  context = FormContext.new(name, values_from, self)

  env = self.request.env
  env['form_context_depth'] ||= 0
  context.instance_variable_set(:@form_context_depth, env['form_context_depth'])
  # Only fetch required values at the top-level
  if env['form_context_depth'] == 0
    begin
      required_fields = RequiredFields.get(values_from.jsonmodel_type)
      context.instance_variable_set(:@required_fields, required_fields)
    rescue
    end
  end

  s = "<div class=\"form-context\" id=\"form_#{name}\">".html_safe
  s << context.hidden_input("lock_version", values_from["lock_version"])

  env['form_context_depth'] += 1
  s << capture(context, &body)
  env['form_context_depth'] -= 1

  if env['form_context_depth'] == 0
    # Only emit the JS templates at the top-level
    s << templates_for_js(values_from["jsonmodel_type"])
  end
  s << "</div>".html_safe

  s
rescue
  Rails.logger.error("Failure generating templates for JS: #{$!}")
  Rails.logger.error("Stacktrace:\n%s" % [$@.join("\n")])

  raise $!
end

#form_topObject



162
163
164
# File 'frontend/app/helpers/aspace_form_helper.rb', line 162

def form_top
  @context[0].first
end

#h(str) ⇒ Object



25
26
27
# File 'frontend/app/helpers/aspace_form_helper.rb', line 25

def h(str)
  ERB::Util.html_escape(str)
end

#help_path_for(name) ⇒ Object



196
197
198
199
200
# File 'frontend/app/helpers/aspace_form_helper.rb', line 196

def help_path_for(name)
  names = @context.map(&:first)
  return "#{names[-1].to_s.gsub(/\[.*\]/, "").singularize}_#{name}" if names.length > 0
  name
end

#hidden_input(name, value = nil, field_opts = {}) ⇒ Object



491
492
493
494
495
496
497
498
499
500
501
502
503
# File 'frontend/app/helpers/aspace_form_helper.rb', line 491

def hidden_input(name, value = nil, field_opts = {})
  value = obj[name] if value.nil?

  full_name = path(name)

  if value && value.is_a?(Hash) && value.has_key?('ref')
    full_name += '[ref]'
    value = value['ref']
  end

  @forms.tag("input", {:id => id_for(name), :type => "hidden", :value => h(value), :name => full_name}.merge(field_opts),
             false, false)
end

#i18n_for(name, ignore_form_context = false) ⇒ Object

ignore_form_context will return a translation divorced from the active template or form in which it appears.



233
234
235
236
237
238
239
# File 'frontend/app/helpers/aspace_form_helper.rb', line 233

def i18n_for(name, ignore_form_context = false)
  if ignore_form_context
    "#{name.to_s.gsub(/\[\]$/, "")}"
  else
    "#{@active_template or form_top}.#{name.to_s.gsub(/\[\]$/, "")}"
  end
end

#idObject



167
168
169
# File 'frontend/app/helpers/aspace_form_helper.rb', line 167

def id
  "form_#{form_top}"
end

#id_for(name, qualify = true) ⇒ Object



266
267
268
269
270
# File 'frontend/app/helpers/aspace_form_helper.rb', line 266

def id_for(name, qualify = true)
  name = path(name) if qualify

  name.gsub(/[\[\]]/, '_')
end

#id_for_javascript(name) ⇒ Object



256
257
258
259
# File 'frontend/app/helpers/aspace_form_helper.rb', line 256

def id_for_javascript(name)
  path = name.split("/").collect {|a| "[#{a}]"}.join
  "#{form_top}#{path}".gsub(/[\[\]\/]/, "_")
end

#jsonmodel_definition(type, root = nil) ⇒ Object



1072
1073
1074
# File 'frontend/app/helpers/aspace_form_helper.rb', line 1072

def jsonmodel_definition(type, root = nil)
  JSONModelDefinition.new(JSONModel(type), root)
end

#label(name, opts = {}, classes = []) ⇒ Object



526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
# File 'frontend/app/helpers/aspace_form_helper.rb', line 526

def label(name, opts = {}, classes = [])
  prefix = ''
  prefix << "#{opts[:contextual]}." if opts[:contextual]
  prefix << 'plugins.' if opts[:plugin]

  classes << 'control-label text-right'

  options = {:class => classes.join(' '), :for => id_for(name)}

  unless (tooltip = tooltip(name, prefix)).empty?
    add_tooltip_options(tooltip, options)
  end

  attr_string = options.merge(opts || {})
                  .map {|k, v| '%s="%s"' % [CGI::escapeHTML(k.to_s),
                                            CGI::escapeHTML(v.to_s)]}
                  .join(' ')
  content = CGI::escapeHTML(I18n.t(prefix + i18n_for(name, opts[:ignore_form_context])))
  "<label #{attr_string}>#{content}</label>".html_safe
end

#label_and_boolean(name, opts = {}, default = false, force_checked = false) ⇒ Object



333
334
335
336
337
# File 'frontend/app/helpers/aspace_form_helper.rb', line 333

def label_and_boolean(name, opts = {}, default = false, force_checked = false)
  opts[:col_size] = 1
  opts[:controls_class] = "checkbox"
  label_with_field(name, checkbox(name, opts.except(:label_opts), default, force_checked), opts)
end

#label_and_date(name, opts = {}) ⇒ Object



276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
# File 'frontend/app/helpers/aspace_form_helper.rb', line 276

def label_and_date(name, opts = {})
  field_opts = (opts[:field_opts] || {}).merge({
      :class => "date-field form-control",
      :"data-format" => "yyyy-mm-dd",
      :"data-date" => Date.today.strftime('%Y-%m-%d'),
      :"data-autoclose" => true,
      :"data-force-parse" => false,
      :"data-label" => I18n.t("actions.date_picker_toggle")
  })

  if obj[name].blank? && opts[:default]
    value = opts[:default]
  else
    value = obj[name]
  end

  opts[:col_size] = 4

  date_input = textfield(name, value, field_opts)

  label_with_field(name, date_input, opts)
end

#label_and_disabled_checkbox(name) ⇒ Object



299
300
301
302
303
304
305
306
307
308
309
310
311
# File 'frontend/app/helpers/aspace_form_helper.rb', line 299

def label_and_disabled_checkbox(name)
  html = ""

  html << "<div class='form-group'>"

  html << "<label class='col-sm-2 control-label'>#{name}</label>"
  html << "<div class='col-sm-1'>"
  html << "<input type='checkbox' name='disabled' disabled>"
  html << "</div>"
  html << "</div>"

  return html.html_safe
end

#label_and_fourpartidObject



516
517
518
519
520
521
522
523
# File 'frontend/app/helpers/aspace_form_helper.rb', line 516

def label_and_fourpartid
  field_html =  textfield("id_0", obj["id_0"], :class => "id_0 form-control", :size => 10)
  field_html << textfield("id_1", obj["id_1"], :class => "id_1 form-control", :size => 10, :disabled => obj["id_0"].blank? && obj["id_1"].blank?, :'aria-label' => "id_1")
  field_html << textfield("id_2", obj["id_2"], :class => "id_2 form-control", :size => 10, :disabled => obj["id_1"].blank? && obj["id_2"].blank?, :'aria-label' => "id_2")
  field_html << textfield("id_3", obj["id_3"], :class => "id_3 form-control", :size => 10, :disabled => obj["id_2"].blank? && obj["id_3"].blank?, :'aria-label' => "id_3")
  @forms.(:div, (I18n.t(i18n_for("id_0")) + field_html).html_safe, :class=> "identifier-fields")
  label_with_field("id_0", field_html, :control_class => "identifier-fields")
end

#label_and_merge_select(name, default = "", opts = {}) ⇒ Object



367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
# File 'frontend/app/helpers/aspace_form_helper.rb', line 367

def label_and_merge_select(name, default = "", opts = {})
  value = obj[name]
  begin
    jsonmodel_type = obj["jsonmodel_type"]
    prefix = opts[:plugin] ? 'plugins.' : ''
    schema = JSONModel(jsonmodel_type).schema
    if (schema["properties"][name].has_key?('dynamic_enum'))
      value = I18n.t("enumerations.#{schema["properties"][name]["dynamic_enum"]}.#{value}", :default => value)
    elsif schema["properties"][name].has_key?("enum")
      value = I18n.t("#{prefix}#{jsonmodel_type.to_s}.#{property}_#{value}", :default => value)
    end
    value = "" unless value.is_a?(String)
  rescue
  end
  if opts.has_key? :controls_class
    opts[:controls_class] << " label-only"
  else
    opts[:controls_class] = " label-only"
  end
  if value.blank?
    label_with_field(name, value.blank? ? default : value , opts)
  else
    label_with_field(name, merge_select(name, value, opts), opts)
  end
end

#label_and_password(name, opts = {}) ⇒ Object



328
329
330
# File 'frontend/app/helpers/aspace_form_helper.rb', line 328

def label_and_password(name, opts = {})
  label_with_field(name, password(name, obj[name], opts[:field_opts] || {}), opts)
end

#label_and_readonly(name, default = "", opts = {}) ⇒ Object



339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
# File 'frontend/app/helpers/aspace_form_helper.rb', line 339

def label_and_readonly(name, default = "", opts = {})
  value = obj[name]
  if !(value.is_a? String)
    value = value.to_s
  end

  begin
    jsonmodel_type = obj["jsonmodel_type"]
    prefix = opts[:plugin] ? 'plugins.' : ''
    schema = JSONModel(jsonmodel_type).schema
    if (schema["properties"][name]&.has_key?('dynamic_enum'))
      value = I18n.t("enumerations.#{schema["properties"][name]["dynamic_enum"]}.#{value}", :default => value)
    elsif schema["properties"][name]&.has_key?("enum")
      property = schema["properties"][name]["enum"]
      value = I18n.t("#{prefix}#{jsonmodel_type.to_s}.#{name}_#{value}", :default => value)
    end
    value = "" unless value.is_a?(String)
  rescue
  end
  if opts.has_key? :controls_class
    opts[:controls_class] << " label-only"
  else
    opts[:controls_class] = " label-only"
  end

  label_with_field(name, value.blank? ? default : value , opts)
end

#label_and_select(name, options, opts = {}) ⇒ Object



318
319
320
321
322
323
324
325
# File 'frontend/app/helpers/aspace_form_helper.rb', line 318

def label_and_select(name, options, opts = {})
  options = ([""] + options) if opts[:nodefault]
  opts[:field_opts] ||= {}

  opts[:col_size] = 9
  widget = options.length < COMBOBOX_MIN_LIMIT ? select(name, options, opts[:field_opts] || {}) : combobox(name, options, opts[:field_opts] || {})
  label_with_field(name, widget, opts)
end

#label_and_textarea(name, opts = {}) ⇒ Object



313
314
315
# File 'frontend/app/helpers/aspace_form_helper.rb', line 313

def label_and_textarea(name, opts = {})
  label_with_field(name, textarea(name, obj[name] || opts[:default], opts[:field_opts] || {}), opts)
end

#label_and_textfield(name, opts = {}) ⇒ Object



272
273
274
# File 'frontend/app/helpers/aspace_form_helper.rb', line 272

def label_and_textfield(name, opts = {})
  label_with_field(name, textfield(name, obj[name], opts[:field_opts] || {}), opts)
end

#label_with_field(name, field_html, opts = {}) ⇒ Object



744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
# File 'frontend/app/helpers/aspace_form_helper.rb', line 744

def label_with_field(name, field_html, opts = {})
  opts[:label_opts] ||= {}
  opts[:label_opts][:plugin] = opts[:plugin]
  opts[:label_opts][:classes] ||= ''
  opts[:col_size] ||= 9

  control_group_classes,
  label_classes,
  controls_classes = %w(form-group w-100), [], []

  unless opts[:layout] && opts[:layout] == 'stacked'
    control_group_classes.insert(-1, 'row')
    label_classes << "col-sm-#{opts[:label_opts].fetch(:col_size, 2)} #{opts[:label_opts][:classes]}"
    controls_classes << "col-sm-#{opts[:col_size]}"
  end
  # There must be a better way to say this...
  # The value of the 'required' option wins out if set to either true or false
  # if not specified, we take the value of required?
  required = [:required, 'required'].map {|r| opts[r]}.compact.first
  if required.nil?
    required = required?(name)
  end

  # additional admin-defined requirements
  unless required || @required_fields.nil?
    type = @record_type || @context.last[1]["jsonmodel_type"]
    # ideally we would send along the property as well,
    # and be really sure that this field is required on
    # this type of record in such and such context. A possible
    # refactor would be to have all or some of  the marking up
    # of required fields happen on demand (in JavaScript).
    required = @required_fields.required?(nil, type, name)
  end

  control_group_classes << "required" if required == true

  control_group_classes << "conditionally-required" if required == :conditionally

  control_group_classes << "#{opts[:control_class]}" if opts.has_key? :control_class

  #TODO: refactor this. We don't need a separate method for each extra special class to be added below. Probably the thing to is to use the opts param.
  # ANW-617: add JS classes to slug fields
  control_group_classes << "js-slug_textfield" if name == "slug"
  control_group_classes << "js-slug_auto_checkbox" if name == "is_slug_auto"

  # ANW-429: add JS classes to structured date fields
  control_group_classes << "js-structured_date_select" if name == "date_type_structured"

  controls_classes << "#{opts[:controls_class]}" if opts.has_key? :controls_class

  control_group = "<div class=\"#{control_group_classes.join(' ')}\">"
  control_group << label(name, opts[:label_opts], label_classes)
  control_group << "<div class=\"#{controls_classes.join(' ')}\">"
  control_group << field_html
  control_group << "</div>"
  control_group << "</div>"

  # ANW-429
  # TODO: Refactor to the JS files, ideally so this is run when the "Add Date" button is clicked. This is a tricky one since the select field this JS needs to be run on doesn't exist until the callbacks that run after the button is clicked run. Putting it here means that it runs as part of the html, and is always included in the right context.
  control_group << "<script>selectStructuredDateSubform();</script>" if name == "date_type_structured"

  control_group.html_safe
end

#list_for(objects, context_name, &block) ⇒ Object

renders a list of form element sets from a template. Each item will be re-orderable. Objects should be an array.



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'frontend/app/helpers/aspace_form_helper.rb', line 121

def list_for(objects, context_name, &block)
  objects ||= []
  result = ""

  objects.each_with_index do |object, idx|
    push(set_index(context_name, idx), object) do
      result << "<li id=\"#{current_id}\" class=\"subrecord-form-wrapper\" data-index=\"#{idx}\" data-object-name=\"#{context_name.gsub(/\[\]/, "").singularize}\">"
      result << hidden_input("lock_version") if obj.respond_to?(:has_key?) && obj.has_key?("lock_version")
      result << @parent.capture(object, idx, &block)
      result << "</li>"
    end
  end

  ("<ul data-name-path=\"#{set_index(self.path(context_name), '${index}')}\" " +
   " data-id-path=\"#{id_for(set_index(self.path(context_name), '${index}'), false)}\" " +
   " class=\"subrecord-form-list\">#{result}</ul>").html_safe
end

#merge_checkbox(name, opts = {}, default = false, force_checked = false) ⇒ Object



693
694
695
696
697
698
# File 'frontend/app/helpers/aspace_form_helper.rb', line 693

def merge_checkbox(name, opts = {}, default = false, force_checked = false)
  options = {:id => "#{id_for(name)}", :type => "checkbox", :name => path(name), :value => "REPLACE"}
  options[:checked] = "checked" if force_checked or (obj[name] === true) or (obj[name].is_a? String and obj[name].start_with?("true")) or (obj[name] === "REPLACE") or (obj[name].nil? and default)

  @forms.tag("input", options.merge(opts), false, false)
end

#merge_select(name, value, opts) ⇒ Object

ANW-429: Modified this method so that a disable_replace can be passed in, which will skip the creation of the “replace” checkboxes. This can be used to not render them in case a replace in inappropriate, e.g., the target record has nothing to replace with.



395
396
397
398
399
400
401
402
403
404
405
406
# File 'frontend/app/helpers/aspace_form_helper.rb', line 395

def merge_select(name, value, opts)
  unless opts[:disable_replace] == true
    value += "<label class='subreplace-control'>".html_safe
    value += merge_checkbox("#{name}", {
      :class => "merge-toggle"}, false, false)
    value += "&#160;<small>".html_safe
    value += I18n.t("actions.merge_replace")
    value += "</small></label>".html_safe
  else
    value += ""
  end
end

#merge_victim_view(hash, opts = {}) ⇒ Object



914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
# File 'frontend/app/helpers/aspace_form_helper.rb', line 914

def merge_victim_view(hash, opts = {})
  jsonmodel_type = hash["jsonmodel_type"]
  schema = JSONModel(jsonmodel_type).schema
  prefix = opts[:plugin] ? 'plugins.' : ''
  html = "<div class='form-horizontal'>"

  hash.reject {|k, v| PROPERTIES_TO_EXCLUDE_FROM_READ_ONLY_VIEW.include?(k)}.each do |property, value|
    if schema and schema["properties"].has_key?(property)
      if (schema["properties"][property].has_key?('dynamic_enum'))
        value = I18n.t("enumerations.#{schema["properties"][property]["dynamic_enum"]}.#{value}", :default => value)
      elsif schema["properties"][property].has_key?("enum")
        value = I18n.t("#{prefix}#{jsonmodel_type.to_s}.#{property}_#{value}", :default => value)
      elsif schema["properties"][property]["type"] === "boolean"
        value = value === true ? "True" : "False"
      elsif schema["properties"][property]["type"] === "date"
        value = value.blank? ? "" : Date.strptime(value, "%Y-%m-%d")
      elsif schema["properties"][property]["type"] === "array"
        # this view doesn't support arrays
        next
      elsif value.is_a? Hash
        # can't display an object either
        next
      end
    end
    value = "" unless value.is_a?(String)
    html << "<div class='form-group'>"
    html << "<div class='control-label col-sm-2'>"
    html << I18n.t("#{prefix}#{jsonmodel_type.to_s}.#{property}")
    html << "</div>"
    html << "<div class='col-sm-8'>#{value}</div>"
    html << "</div>"
  end
  html << "</div>"
  html.html_safe
end

#name_to_json_path(name) ⇒ Object

Turn a name like my[nested][object][0][title] into the equivalent JSON path (my/nested/object/0/title)



174
175
176
# File 'frontend/app/helpers/aspace_form_helper.rb', line 174

def name_to_json_path(name)
  name.gsub(/[\[\]]+/, "/").gsub(/\/+$/, "").gsub(/^\/+/, "")
end

#notes_preview(notes_index = "notes", content_index = "content") ⇒ Object

ANW-429 Generates HTML for a very stripped down summary of a note for use in the agents merge preview. TODO: Eventually we’ll want to use the notes partials in place of this code. This code was created because the current notes show takes up a lot of space, and work needs to be done to figure out the exact setup/context needed to get those views to render properly. T



812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
# File 'frontend/app/helpers/aspace_form_helper.rb', line 812

def notes_preview(notes_index = "notes", content_index = "content")
   = I18n.t("note._frontend.preview.content")
  html = ""

  if obj[notes_index] && obj[notes_index].length > 0

    html << "<div class='subrecord-form-container'>"
    html << "<h4 class='subrecord-form-heading'>#{I18n.t("subsections.notes")}</h4>"

    obj[notes_index].each_with_index do |o, i|
      notes_heading = I18n.t("note.#{o['jsonmodel_type'].to_s}")

      if o[content_index].is_a?(Array)
        notes_content = o[content_index].join(" : ")
      else
        notes_content = o[content_index]
      end

      html << "<section>"
      html << "<h5>#{notes_heading}</h5>"
      html << "<div class='panel panel-default'>"
      html << "<div class=\"form-group\">"
      html << "<label class='control-label col-sm-2'>#{content_label}</label>"
      html << "<div class='col-sm-9 label-only'>"
      html << "#{notes_content}"
      html << "</div>"
      html << "</div>"
      html << "</div>"
      html << "</section>"
    end

    html << "</div>"

    html.html_safe

  end
end

#notes_preview_single(obj) ⇒ Object

Same as above, but intended for use with an agents top level notes record instead of a subrecord, in the merge selector form. Needed because emitting templates as usual breaks the merge selector interface



852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
# File 'frontend/app/helpers/aspace_form_helper.rb', line 852

def notes_preview_single(obj)
   = I18n.t("note._frontend.preview.content")
  html = ""

  notes_content = ""

  obj['subnotes'].each do |subnote|
    if subnote['jsonmodel_type'] == "note_text"
      notes_content << subnote["content"] if subnote["content"]
    end
  end

  html << "<br />"
  html << "<section>"
  html << "<div class='panel panel-default'>"
  html << "<div class=\"form-group\">"
  html << "<label class='control-label col-sm-2'>#{content_label}</label>"
  html << "<div class='col-sm-9 label-only'>"
  html << "#{notes_content}"
  html << "</div>"
  html << "</div>"
  html << "</div>"
  html << "</section>"

  html.html_safe
end

#oai_config_repo_set_codes_field(set_json, repositories) ⇒ Object



640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
# File 'frontend/app/helpers/aspace_form_helper.rb', line 640

def oai_config_repo_set_codes_field(set_json, repositories)
  #label_and_textfield(name, opts)
  set_arry = JSON::parse(set_json)

  html = ""

  html << "<div class='form-group row'>"
  html << label("repo_set_section", {}, ["control-label", "col-sm-2"])
  html << "<div class='col-sm-9'>"
  html << "<ul class='checkbox-list'>"
  repositories.each do |r|
    # a checkbox is on if it's the in the list we get from the backend.
    checked = set_arry.include?(r['repo_code'].to_s)

    html << "<li class='list-group-item'>"
    html << "<div class='checkbox'>"
    html << "<label>"
    html << "<input id=\"#{r['repo_code']}\" name=\"repo_set_codes[#{r['repo_code']}]\" type=\"checkbox\" "
    if checked
      html << "checked=\"checked\" "
    end

    html << "/>"

    html << "#{r['repo_code']}"
    html << "</label>"
    html << "</div>"
    html << "</li>"
  end
  html << "</ul>"
  html << "</div>" #col-sm-9
  html << "</div>" #form-group

  return html.html_safe
end

#oai_config_sponsor_set_names_field(set_json, opts = {}) ⇒ Object



676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
# File 'frontend/app/helpers/aspace_form_helper.rb', line 676

def oai_config_sponsor_set_names_field(set_json, opts = {})
  # turn array from DB into a comma delimited list for UI
  set_arry = JSON::parse(set_json)
  value = set_arry.join("|")

  html = ""

  html << "<div class='form-group row'>"
  html << label("sponsor_set_names", {}, ["control-label", "col-sm-2"])
  html << "<div class='col-sm-9'>"
  html << "<input id='oai_config_sponsor_set_names_' type='text' value='#{value}' name='oai_config[sponsor_set_names]' class='form-control js-taggable' datarole='tagsinput'>"
  html << "</div>"
  html << "</div>"

  return html.html_safe
end

#objObject



213
214
215
# File 'frontend/app/helpers/aspace_form_helper.rb', line 213

def obj
  @context.last.second
end

#parent_contextObject



203
204
205
# File 'frontend/app/helpers/aspace_form_helper.rb', line 203

def parent_context
  form_top
end

#password(name = nil, value = "", opts = {}) ⇒ Object



486
487
488
489
# File 'frontend/app/helpers/aspace_form_helper.rb', line 486

def password(name = nil, value = "", opts = {})
  @forms.tag("input", {:id => id_for(name), :type => "password", :value => h(value), :name => path(name)}.merge(opts),
             false, false)
end

#path(name = nil) ⇒ Object



179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'frontend/app/helpers/aspace_form_helper.rb', line 179

def path(name = nil)
  names = @context.map(&:first)
  tail = names.drop(1)
  tail += [name] if name

  path = tail.map {|e|
    if e =~ /(.*?)\[([0-9]+)?\]$/
      "[#{$1}][#{$2}]"
    else
      "[#{e}]"
    end
  }.join("")

  "#{names.first}#{path}"
end

#possible_options_for(name, add_empty_options = false, opts = {}) ⇒ Object



735
736
737
738
739
740
741
# File 'frontend/app/helpers/aspace_form_helper.rb', line 735

def possible_options_for(name, add_empty_options = false, opts = {})
  if @active_template && @parent.templates[@active_template]
    @parent.templates[@active_template][:definition].options_for(self, name, add_empty_options, opts)
  else
    []
  end
end

#preserve_newlines(string) ⇒ Object



1337
1338
1339
# File 'frontend/app/helpers/aspace_form_helper.rb', line 1337

def preserve_newlines(string)
  string.gsub(/\n/, '<br>')
end

#push(name, values_from = {}) {|_self| ... } ⇒ Object

Yields:

  • (_self)

Yield Parameters:



223
224
225
226
227
228
# File 'frontend/app/helpers/aspace_form_helper.rb', line 223

def push(name, values_from = {})
  path(name) # populate the i18n mapping
  @context.push([name, values_from])
  yield(self)
  @context.pop
end

#radio(name, value, opts = {}) ⇒ Object



700
701
702
703
704
705
# File 'frontend/app/helpers/aspace_form_helper.rb', line 700

def radio(name, value, opts = {})
  options = {:id => "#{id_for(name)}", :type => "radio", :name => path(name), :value => value}
  options[:checked] = "checked" if obj[name] == value

  @forms.tag("input", options.merge(opts), false, false)
end

#read_only_view(hash, opts = {}) ⇒ Object



1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
# File 'frontend/app/helpers/aspace_form_helper.rb', line 1286

def read_only_view(hash, opts = {})
  jsonmodel_type = hash["jsonmodel_type"]
  schema = JSONModel(jsonmodel_type).schema
  prefix = opts[:plugin] ? 'plugins.' : ''
  html = "<div class='form-horizontal'>"

  # in some cases, we want to not display certain fields for some records, but not for others.
  # e.g., we don't want to display published for subjects (they are always published), but we do for other records types.
  if opts[:exclude]
    props_to_exclude = PROPERTIES_TO_EXCLUDE_FROM_READ_ONLY_VIEW + opts[:exclude]
  else
    props_to_exclude = PROPERTIES_TO_EXCLUDE_FROM_READ_ONLY_VIEW

  end

  hash.reject {|k, v| props_to_exclude.include?(k)}.each do |property, value|

    if schema and schema["properties"].has_key?(property)
      if (schema["properties"][property].has_key?('dynamic_enum'))
        value = I18n.t("enumerations.#{schema["properties"][property]["dynamic_enum"]}.#{value}", :default => value)
      elsif schema["properties"][property].has_key?("enum")
        value = I18n.t("#{prefix}#{jsonmodel_type.to_s}.#{property}_#{value}", :default => value)
      elsif schema["properties"][property]["type"] === "boolean"
        value = value === true ? I18n.t('boolean.true') : I18n.t('boolean.false')
      elsif schema["properties"][property]["type"] === "date"
        value = value.blank? ? "" : Date.strptime(value, "%Y-%m-%d")
      elsif schema["properties"][property]["type"] === "integer"
        value = value.blank? ? "" : value.to_s
      elsif schema["properties"][property]["type"] === "array"
        # this view doesn't support arrays
        next
      elsif value.is_a? Hash
        # can't display an object either
        next
      end
      next unless value.is_a?(String)
    end

    html << "<div class='form-group d-flex'>"
    html << "<div class='control-label col-sm-2 text-right'>"
    html << I18n.t("#{prefix}#{jsonmodel_type.to_s}.#{property}")
    html << "</div>"
    html << "<div class='col-sm-8'>#{value}</div>"
    html << "</div>"
  end

  html << "</div>"

  html.html_safe
end

#readonly?Boolean

Returns:

  • (Boolean)


38
39
40
# File 'frontend/app/helpers/aspace_form_helper.rb', line 38

def readonly?
  false
end

#readonly_context(name, values_from = {}, &body) ⇒ Object



1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
# File 'frontend/app/helpers/aspace_form_helper.rb', line 1269

def readonly_context(name, values_from = {}, &body)
  context = ReadOnlyContext.new(name, values_from, self)

  # Not feeling great about this, but we render the form twice: the first pass
  # sets up the mapping from form input names to i18n keys, while the second
  # actually uses that map to set the labels correctly.
  capture(context, &body)

  s = "<div class=\"readonly-context form-horizontal\">".html_safe
  s << capture(context, &body)
  s << "</div>".html_safe

  s
end

#record_level_merge_controls(form, name = "undefined", controls = true, replace = true, append = true) ⇒ Object

ANW-429 outputs HTML for checkboxes for record-level add and replace for agents merge



881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
# File 'frontend/app/helpers/aspace_form_helper.rb', line 881

def record_level_merge_controls(form, name = "undefined", controls = true, replace = true, append = true)
  html = ""

  html << '<h4 class="subrecord-form-heading">'
  html << I18n.t("#{name}._singular").to_s

  if controls
    if replace
      html << '<label class="replace-control">'
      html << form.merge_checkbox('replace')
      html << '<small class="fs-14px">'
      html << I18n.t("actions.merge_replace").to_s
      html << '</small>'
      html << '</label>'
    end

    if append
      html << '<label class="append-control">'
      html << form.merge_checkbox('append')
      html << '<small class="fs-14px">'
      html << I18n.t("actions.merge_add").to_s
      html << '</small>'
      html << '</label>'
    end
  end

  html << '</h4>'

  return html.html_safe
end

#required?(name) ⇒ Boolean

Returns:

  • (Boolean)


708
709
710
711
712
713
714
# File 'frontend/app/helpers/aspace_form_helper.rb', line 708

def required?(name)
  if @active_template && @parent.templates[@active_template]
    @parent.templates[@active_template][:definition].required?(name)
  else
    false
  end
end

#select(name, options, opts = {}) ⇒ Object



414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
# File 'frontend/app/helpers/aspace_form_helper.rb', line 414

def select(name, options, opts = {})
    if opts.has_key? :class
      opts[:class] << " form-control"
    else
      opts[:class] = "form-control"
    end
    if opts.has_key? :"data-combobox"
      opts[:role] = "listbox"
      opts[:"aria-label"] = I18n.t(i18n_for(name))
    end
    selection = obj[name]
    selection = selection[0...-4] if selection.is_a? String and selection.end_with?("_REQ")
    @forms.select_tag(path(name), @forms.options_for_select(options, selection || default_for(name) || opts[:default]), {:id => id_for(name)}.merge!(opts))
  end

  def textarea(name = nil, value = "", opts = {})
    value = value[0...-4] if value.is_a? String and value.end_with?("_REQ")
    value = nil if value === "REQ"
    options = {:id => id_for(name), :rows => 3}

    placeholder = I18n.t("#{i18n_for(name)}_placeholder", :default => '')
    options[:placeholder] = placeholder if not placeholder.empty?
    options[:class] = "form-control"

    @forms.text_area_tag(path(name), h(value), options.merge(opts))
  end

  def textarea_ro(name = nil, value = "", opts = {})
    return "" if value.blank?
    opts[:escape] = true unless opts[:escape] == false
    opts[:base_url] ||= "/"
    value = clean_mixed_content(value, opts[:base_url]) if opts[:clean] == true
    value = @parent.preserve_newlines(value) if opts[:clean] == true
    value = CGI::escapeHTML(value) if opts[:escape]
    value.html_safe
  end

  def textfield(name = nil, value = nil, opts = {})
    value ||= obj[name] if !name.nil?

    value = value[0...-4] if value.is_a? String and value.end_with?("_REQ")
    value = nil if value === "REQ"

    options = {:id => id_for(name), :type => "text", :value => h(value), :name => path(name)}

    placeholder = I18n.t("#{i18n_for(name)}_placeholder", :default => '')
    options[:placeholder] = placeholder if not placeholder.empty?
    options[:class] = "form-control"

    value = @forms.tag("input", options.merge(opts),
               false, false)

    if opts[:automatable]
      by_default = default_for("#{name}_auto_generate") || false
      value << "<label>".html_safe
      value << checkbox("#{name}_auto_generate", {
        :class => "automate-field-toggle", :display_text_when_checked => I18n.t("states.auto_generated")
        }, by_default, false)
      value << "&#160;<small>".html_safe
      value << I18n.t("actions.automate")
      value << "</small></label>".html_safe
    end

    inline_help = I18n.t("#{i18n_for(name)}_inline_help", :default => '')
    if !inline_help.empty?
      value << "<span class=\"help-inline\">#{inline_help}</span>".html_safe
    end

    value
  end


  def password(name = nil, value = "", opts = {})
    @forms.tag("input", {:id => id_for(name), :type => "password", :value => h(value), :name => path(name)}.merge(opts),
               false, false)
  end

  def hidden_input(name, value = nil, field_opts = {})
    value = obj[name] if value.nil?

    full_name = path(name)

    if value && value.is_a?(Hash) && value.has_key?('ref')
      full_name += '[ref]'
      value = value['ref']
    end

    @forms.tag("input", {:id => id_for(name), :type => "hidden", :value => h(value), :name => full_name}.merge(field_opts),
               false, false)
  end

  def emit_template(name, *args)
    if !@parent.templates[name]
      raise "No such template: #{name.inspect}"
    end

    old = @active_template
    @active_template = name
    @parent.templates[name][:block].call(self, *args)
    @active_template = old
  end

  def label_and_fourpartid
    field_html =  textfield("id_0", obj["id_0"], :class => "id_0 form-control", :size => 10)
    field_html << textfield("id_1", obj["id_1"], :class => "id_1 form-control", :size => 10, :disabled => obj["id_0"].blank? && obj["id_1"].blank?, :'aria-label' => "id_1")
    field_html << textfield("id_2", obj["id_2"], :class => "id_2 form-control", :size => 10, :disabled => obj["id_1"].blank? && obj["id_2"].blank?, :'aria-label' => "id_2")
    field_html << textfield("id_3", obj["id_3"], :class => "id_3 form-control", :size => 10, :disabled => obj["id_2"].blank? && obj["id_3"].blank?, :'aria-label' => "id_3")
    @forms.(:div, (I18n.t(i18n_for("id_0")) + field_html).html_safe, :class=> "identifier-fields")
    label_with_field("id_0", field_html, :control_class => "identifier-fields")
  end


  def label(name, opts = {}, classes = [])
    prefix = ''
    prefix << "#{opts[:contextual]}." if opts[:contextual]
    prefix << 'plugins.' if opts[:plugin]

    classes << 'control-label text-right'

    options = {:class => classes.join(' '), :for => id_for(name)}

    unless (tooltip = tooltip(name, prefix)).empty?
      add_tooltip_options(tooltip, options)
    end

    attr_string = options.merge(opts || {})
                    .map {|k, v| '%s="%s"' % [CGI::escapeHTML(k.to_s),
                                              CGI::escapeHTML(v.to_s)]}
                    .join(' ')
    content = CGI::escapeHTML(I18n.t(prefix + i18n_for(name, opts[:ignore_form_context])))
    "<label #{attr_string}>#{content}</label>".html_safe
  end

  def add_tooltip_options(tooltip, options)
    options[:title] = tooltip
    options['data-placement'] = 'bottom'
    options['data-boundary'] = 'viewport'
    options['data-html'] = true
    options['data-delay'] = 500
    options['data-trigger'] = 'manual'
    options['data-template'] = '<div class="tooltip archivesspace-help"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
    options[:class] ||= ''
    options[:class] += ' has-tooltip'
    options
  end

  def button_with_tooltip(tooltip, content, div_classes = [], button_classes = [], use_default_btn_classes = true)
    div_classes = div_classes + ["btn-with-tooltip"]

    button_classes = use_default_btn_classes ? button_classes + ["btn", "btn-sm"] : button_classes

    div_options = {:class => div_classes.join(' ')}
    add_tooltip_options(tooltip, div_options)

    button_options = {:class => button_classes.join(' ')}

    div_attr_string = div_options.map {|k, v| '%s="%s"' % [CGI::escapeHTML(k.to_s),
                                                   CGI::escapeHTML(v.to_s)]}
                         .join(' ')

    button_attr_string = button_options.map {|k, v| '%s="%s"' % [CGI::escapeHTML(k.to_s),
                                                   CGI::escapeHTML(v.to_s)]}
                         .join(' ')

    "<div #{div_attr_string}><button #{button_attr_string}>#{content}</button></div>".html_safe
  end

  def tooltip(name, prefix = '')
    I18n.t_raw("#{prefix}#{i18n_for(name)}_tooltip", :default => '')
  end

  def checkbox(name, opts = {}, default = true, force_checked = false)
    options = {:id => "#{id_for(name)}", :type => "checkbox", :name => path(name), :value => 1}
    options[:checked] = "checked" if force_checked or (obj[name] === true) or (obj[name].is_a? String and obj[name].start_with?("true")) or (obj[name] === "1") or (obj[name].nil? and default)

    @forms.tag("input", options.merge(opts), false, false)
  end

  # takes a JSON representation of the current options selected and the list of archival_record_level enums
  # returns HTML for a set of checkboxes representing current selected and deselected sets for OAI export
  def checkboxes_for_oai_sets(set_json, value_list)
    return "" if value_list == nil
    # when called by #new, set_json will be nil.
    if set_json
      set_arry = JSON::parse(set_json)
    else
      set_arry = []
    end

    html = ""

    html << "<div class='form-group'>"
    html << label("oai_sets_available", {}, ["control-label", "col-sm-2"])
    html << "<div class='col-sm-9'>"
    html << "<ul class='checkbox-list'>"
    value_list['enumeration_values'].each do |v|
      # if we have an empty list of checkboxes, assume all sets are enabled.
      # otherwise, a checkbox is on if it's the in the list we get from the backend.
      checked = set_arry.include?(v['id'].to_s) || set_arry.length == 0

      html << "<li class='list-group-item'>"
      html << "<div class='checkbox'>"
      html << "<label>"
      html << "<input id=\"#{v['id']}\" name=\"sets[#{v['id']}]\" type=\"checkbox\" "
      if checked
        html << "checked=\"checked\" "
      end

      if readonly?
        html << "disabled />"
      else
        html << "/>"
      end # of checkbox tag

      html << "#{v['value']}"
      html << "</label>"
      html << "</div>"
      html << "</li>"
    end
    html << "</ul>"
    html << "</div>" #col-sm-9
    html << "</div>" #form-group

    return html.html_safe
  end

  def oai_config_repo_set_codes_field(set_json, repositories)
    #label_and_textfield(name, opts)
    set_arry = JSON::parse(set_json)

    html = ""

    html << "<div class='form-group row'>"
    html << label("repo_set_section", {}, ["control-label", "col-sm-2"])
    html << "<div class='col-sm-9'>"
    html << "<ul class='checkbox-list'>"
    repositories.each do |r|
      # a checkbox is on if it's the in the list we get from the backend.
      checked = set_arry.include?(r['repo_code'].to_s)

      html << "<li class='list-group-item'>"
      html << "<div class='checkbox'>"
      html << "<label>"
      html << "<input id=\"#{r['repo_code']}\" name=\"repo_set_codes[#{r['repo_code']}]\" type=\"checkbox\" "
      if checked
        html << "checked=\"checked\" "
      end

      html << "/>"

      html << "#{r['repo_code']}"
      html << "</label>"
      html << "</div>"
      html << "</li>"
    end
    html << "</ul>"
    html << "</div>" #col-sm-9
    html << "</div>" #form-group

    return html.html_safe
  end

  def oai_config_sponsor_set_names_field(set_json, opts = {})
    # turn array from DB into a comma delimited list for UI
    set_arry = JSON::parse(set_json)
    value = set_arry.join("|")

    html = ""

    html << "<div class='form-group row'>"
    html << label("sponsor_set_names", {}, ["control-label", "col-sm-2"])
    html << "<div class='col-sm-9'>"
    html << "<input id='oai_config_sponsor_set_names_' type='text' value='#{value}' name='oai_config[sponsor_set_names]' class='form-control js-taggable' datarole='tagsinput'>"
    html << "</div>"
    html << "</div>"

    return html.html_safe
  end

  def merge_checkbox(name, opts = {}, default = false, force_checked = false)
    options = {:id => "#{id_for(name)}", :type => "checkbox", :name => path(name), :value => "REPLACE"}
    options[:checked] = "checked" if force_checked or (obj[name] === true) or (obj[name].is_a? String and obj[name].start_with?("true")) or (obj[name] === "REPLACE") or (obj[name].nil? and default)

    @forms.tag("input", options.merge(opts), false, false)
  end

  def radio(name, value, opts = {})
    options = {:id => "#{id_for(name)}", :type => "radio", :name => path(name), :value => value}
    options[:checked] = "checked" if obj[name] == value

    @forms.tag("input", options.merge(opts), false, false)
  end


  def required?(name)
    if @active_template && @parent.templates[@active_template]
      @parent.templates[@active_template][:definition].required?(name)
    else
      false
    end
  end


  def default_for(name)
    if @active_template && @parent.templates[@active_template]
      @parent.templates[@active_template][:definition].default_for(name)
    else
      nil
    end
  end


  def allowable_types_for(name)
    if @active_template && @parent.templates[@active_template]
      @parent.templates[@active_template][:definition].allowable_types_for(name)
    else
      []
    end
  end


  def possible_options_for(name, add_empty_options = false, opts = {})
    if @active_template && @parent.templates[@active_template]
      @parent.templates[@active_template][:definition].options_for(self, name, add_empty_options, opts)
    else
      []
    end
  end


  def label_with_field(name, field_html, opts = {})
    opts[:label_opts] ||= {}
    opts[:label_opts][:plugin] = opts[:plugin]
    opts[:label_opts][:classes] ||= ''
    opts[:col_size] ||= 9

    control_group_classes,
    label_classes,
    controls_classes = %w(form-group w-100), [], []

    unless opts[:layout] && opts[:layout] == 'stacked'
      control_group_classes.insert(-1, 'row')
      label_classes << "col-sm-#{opts[:label_opts].fetch(:col_size, 2)} #{opts[:label_opts][:classes]}"
      controls_classes << "col-sm-#{opts[:col_size]}"
    end
    # There must be a better way to say this...
    # The value of the 'required' option wins out if set to either true or false
    # if not specified, we take the value of required?
    required = [:required, 'required'].map {|r| opts[r]}.compact.first
    if required.nil?
      required = required?(name)
    end

    # additional admin-defined requirements
    unless required || @required_fields.nil?
      type = @record_type || @context.last[1]["jsonmodel_type"]
      # ideally we would send along the property as well,
      # and be really sure that this field is required on
      # this type of record in such and such context. A possible
      # refactor would be to have all or some of  the marking up
      # of required fields happen on demand (in JavaScript).
      required = @required_fields.required?(nil, type, name)
    end

    control_group_classes << "required" if required == true

    control_group_classes << "conditionally-required" if required == :conditionally

    control_group_classes << "#{opts[:control_class]}" if opts.has_key? :control_class

    #TODO: refactor this. We don't need a separate method for each extra special class to be added below. Probably the thing to is to use the opts param.
    # ANW-617: add JS classes to slug fields
    control_group_classes << "js-slug_textfield" if name == "slug"
    control_group_classes << "js-slug_auto_checkbox" if name == "is_slug_auto"

    # ANW-429: add JS classes to structured date fields
    control_group_classes << "js-structured_date_select" if name == "date_type_structured"

    controls_classes << "#{opts[:controls_class]}" if opts.has_key? :controls_class

    control_group = "<div class=\"#{control_group_classes.join(' ')}\">"
    control_group << label(name, opts[:label_opts], label_classes)
    control_group << "<div class=\"#{controls_classes.join(' ')}\">"
    control_group << field_html
    control_group << "</div>"
    control_group << "</div>"

    # ANW-429
    # TODO: Refactor to the JS files, ideally so this is run when the "Add Date" button is clicked. This is a tricky one since the select field this JS needs to be run on doesn't exist until the callbacks that run after the button is clicked run. Putting it here means that it runs as part of the html, and is always included in the right context.
    control_group << "<script>selectStructuredDateSubform();</script>" if name == "date_type_structured"

    control_group.html_safe
  end

  # ANW-429
  # Generates HTML for a very stripped down summary of a note for use in the agents merge preview.
  # TODO: Eventually we'll want to use the notes partials in place of this code. This code was created because the current notes show takes up a lot of space, and work needs to be done to figure out the exact setup/context needed to get those views to render properly.
  # T
  def notes_preview(notes_index = "notes", content_index = "content")
     = I18n.t("note._frontend.preview.content")
    html = ""

    if obj[notes_index] && obj[notes_index].length > 0

      html << "<div class='subrecord-form-container'>"
      html << "<h4 class='subrecord-form-heading'>#{I18n.t("subsections.notes")}</h4>"

      obj[notes_index].each_with_index do |o, i|
        notes_heading = I18n.t("note.#{o['jsonmodel_type'].to_s}")

        if o[content_index].is_a?(Array)
          notes_content = o[content_index].join(" : ")
        else
          notes_content = o[content_index]
        end

        html << "<section>"
        html << "<h5>#{notes_heading}</h5>"
        html << "<div class='panel panel-default'>"
        html << "<div class=\"form-group\">"
        html << "<label class='control-label col-sm-2'>#{content_label}</label>"
        html << "<div class='col-sm-9 label-only'>"
        html << "#{notes_content}"
        html << "</div>"
        html << "</div>"
        html << "</div>"
        html << "</section>"
      end

      html << "</div>"

      html.html_safe

    end
  end

  # Same as above, but intended for use with an agents top level notes record instead of a subrecord, in the merge selector form.
  # Needed because emitting templates as usual breaks the merge selector interface
  def notes_preview_single(obj)
     = I18n.t("note._frontend.preview.content")
    html = ""

    notes_content = ""

    obj['subnotes'].each do |subnote|
      if subnote['jsonmodel_type'] == "note_text"
        notes_content << subnote["content"] if subnote["content"]
      end
    end

    html << "<br />"
    html << "<section>"
    html << "<div class='panel panel-default'>"
    html << "<div class=\"form-group\">"
    html << "<label class='control-label col-sm-2'>#{content_label}</label>"
    html << "<div class='col-sm-9 label-only'>"
    html << "#{notes_content}"
    html << "</div>"
    html << "</div>"
    html << "</div>"
    html << "</section>"

    html.html_safe
  end

  # ANW-429
  # outputs HTML for checkboxes for record-level add and replace for agents merge
  def record_level_merge_controls(form, name = "undefined", controls = true, replace = true, append = true)
    html = ""

    html << '<h4 class="subrecord-form-heading">'
    html << I18n.t("#{name}._singular").to_s

    if controls
      if replace
        html << '<label class="replace-control">'
        html << form.merge_checkbox('replace')
        html << '<small class="fs-14px">'
        html << I18n.t("actions.merge_replace").to_s
        html << '</small>'
        html << '</label>'
      end

      if append
        html << '<label class="append-control">'
        html << form.merge_checkbox('append')
        html << '<small class="fs-14px">'
        html << I18n.t("actions.merge_add").to_s
        html << '</small>'
        html << '</label>'
      end
    end

    html << '</h4>'

    return html.html_safe
  end

end

#set_index(template, idx) ⇒ Object



43
44
45
# File 'frontend/app/helpers/aspace_form_helper.rb', line 43

def set_index(template, idx)
  template.gsub(/\[\]$/, "[#{idx}]")
end

#slug_url_field(name, repo_slug = nil, generate_url_with_repo_slug = nil) ⇒ Object

ANW-617: TODO: Ideally, this method should generate a full URL, with the value from AppConfig[:public_url], so the link is fully actionable. Ran into a a bug (with AS? or deeper?) where the value of AppConfig[:public_url] was being changed at runtime simply by getting it’s value, with code like base_url = AppConfig[:public_url]. For now, this method generates a relative URL, like ‘\resources\Resource A’ to avoid this.



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'frontend/app/helpers/aspace_form_helper.rb', line 51

def slug_url_field(name, repo_slug = nil, generate_url_with_repo_slug = nil)
  url = ""
  html = ""

  case obj['jsonmodel_type']
  when 'resource'
    scope = :repo
    route = "resources"
  when 'accession'
    scope = :repo
    route = "accessions"
  when 'classification'
    scope = :repo
    route = "classifications"
  when 'classification_term'
    scope = :repo
    route = "classification_terms"
  when 'digital_object'
    scope = :repo
    route = "digital_objects"
  when 'repository'
    scope = :global
    route = "repositories"
  when 'agent_person'
    scope = :global
    route = "agents"
  when 'agent_family'
    scope = :global
    route = "agents"
  when 'agent_software'
    scope = :global
    route = "agents"
  when 'agent_corporate_entity'
    scope = :global
    route = "agents"
  when 'subject'
    scope = :global
    route = "subjects"
  when 'archival_object'
    scope = :repo
    route = "archival_objects"
  when 'digital_object_component'
    scope = :repo
    route = "digital_object_components"
  end

  # For repo scoped objects,
  # if we have access to the repo slug in the session and the repo scoped URLs are enabled
  # generate link with repo slug
  if !obj['slug'].nil? &&
     !obj['slug'].empty? &&
     AppConfig[:use_human_readable_urls]

    if scope == :repo
      if generate_url_with_repo_slug && repo_slug
        url << "/" + "repositories" + "/"
        url << repo_slug
      end
    end

    url << "/" + route + "/" + obj['slug']
  else
    url = obj['uri']
  end

  url.to_s
end

#templatesObject



1055
1056
1057
1058
# File 'frontend/app/helpers/aspace_form_helper.rb', line 1055

def templates
  @templates ||= {}
  @templates
end

#templates_for_js(jsonmodel_type = nil) ⇒ Object



1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
# File 'frontend/app/helpers/aspace_form_helper.rb', line 1209

def templates_for_js(jsonmodel_type = nil)
  @delivering_js_templates = true

  result = ""
  return result if @templates.blank?

  obj = {}
  obj['jsonmodel_type'] = jsonmodel_type if jsonmodel_type

  templates_to_process = @templates.clone
  templates_processed = []
  # As processing a template may register further templates that hadn't been
  # registered previously, keep looping until we have no more templates to
  # process.
  #
  # Because infinite loops are terrifying and a pain to debug, let us reign
  # in the fear with a 100-loop-count-get-out-of-here-alive limit.
  i = 0
  while (true)
    templates_to_process.each do |name, template|
      context = FormContext.new("${path}", obj, self)

      def context.id_for(name, qualify = true)
        name = path(name) if qualify

        name.gsub(/[\[\]]/, '_').gsub('${path}', '${id_path}')
      end

      context.instance_eval do
        @active_template = name
        @record_type = template[:definition].record_type
        @required_fields = template[:requirements]
      end

      result << "<div id=\"template_#{name}\"><!--"
      result << capture(context, &template[:block])
      result << "--></div>"

      templates_processed << name
    end

    if templates_processed.length < @templates.length
      # some new templates were defined while outputing the js templates
      templates_to_process = @templates.reject {|name, _| templates_processed.include?(name)}
    else
      # we've got them all
      break
    end

    i += 1

    if i > 100
      Rails.logger.error("templates_for_js has looped out more that 100 times")
      break
    end
  end

  result.html_safe
end

#textarea(name = nil, value = "", opts = {}) ⇒ Object



429
430
431
432
433
434
435
436
437
438
439
# File 'frontend/app/helpers/aspace_form_helper.rb', line 429

def textarea(name = nil, value = "", opts = {})
  value = value[0...-4] if value.is_a? String and value.end_with?("_REQ")
  value = nil if value === "REQ"
  options = {:id => id_for(name), :rows => 3}

  placeholder = I18n.t("#{i18n_for(name)}_placeholder", :default => '')
  options[:placeholder] = placeholder if not placeholder.empty?
  options[:class] = "form-control"

  @forms.text_area_tag(path(name), h(value), options.merge(opts))
end

#textarea_ro(name = nil, value = "", opts = {}) ⇒ Object



441
442
443
444
445
446
447
448
449
# File 'frontend/app/helpers/aspace_form_helper.rb', line 441

def textarea_ro(name = nil, value = "", opts = {})
  return "" if value.blank?
  opts[:escape] = true unless opts[:escape] == false
  opts[:base_url] ||= "/"
  value = clean_mixed_content(value, opts[:base_url]) if opts[:clean] == true
  value = @parent.preserve_newlines(value) if opts[:clean] == true
  value = CGI::escapeHTML(value) if opts[:escape]
  value.html_safe
end

#textfield(name = nil, value = nil, opts = {}) ⇒ Object



451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
# File 'frontend/app/helpers/aspace_form_helper.rb', line 451

def textfield(name = nil, value = nil, opts = {})
  value ||= obj[name] if !name.nil?

  value = value[0...-4] if value.is_a? String and value.end_with?("_REQ")
  value = nil if value === "REQ"

  options = {:id => id_for(name), :type => "text", :value => h(value), :name => path(name)}

  placeholder = I18n.t("#{i18n_for(name)}_placeholder", :default => '')
  options[:placeholder] = placeholder if not placeholder.empty?
  options[:class] = "form-control"

  value = @forms.tag("input", options.merge(opts),
             false, false)

  if opts[:automatable]
    by_default = default_for("#{name}_auto_generate") || false
    value << "<label>".html_safe
    value << checkbox("#{name}_auto_generate", {
      :class => "automate-field-toggle", :display_text_when_checked => I18n.t("states.auto_generated")
      }, by_default, false)
    value << "&#160;<small>".html_safe
    value << I18n.t("actions.automate")
    value << "</small></label>".html_safe
  end

  inline_help = I18n.t("#{i18n_for(name)}_inline_help", :default => '')
  if !inline_help.empty?
    value << "<span class=\"help-inline\">#{inline_help}</span>".html_safe
  end

  value
end

#tooltip(name, prefix = '') ⇒ Object



581
582
583
# File 'frontend/app/helpers/aspace_form_helper.rb', line 581

def tooltip(name, prefix = '')
  I18n.t_raw("#{prefix}#{i18n_for(name)}_tooltip", :default => '')
end

#update_monitor_params(record) ⇒ Object



1342
1343
1344
1345
1346
1347
1348
1349
1350
# File 'frontend/app/helpers/aspace_form_helper.rb', line 1342

def update_monitor_params(record)
  {
    :"data-update-monitor" => true,
    :"data-update-monitor-url" => url_for(:controller => :update_monitor, :action => :poll),
    :"data-update-monitor-record-uri" => record.uri,
    :"data-update-monitor-record-is-stale" => !!@record_is_stale,
    :"data-update-monitor-lock_version" => record.lock_version
  }
end