From dc0c64825058c31a914f35a46bafe0bb2f81dbbf Mon Sep 17 00:00:00 2001 From: Anusha Ranganathan Date: Tue, 7 Jul 2026 14:17:17 +0530 Subject: [PATCH 1/4] Extract all identifiers --- portality/crosswalks/article_crossref_xml.py | 32 ++++++++++++++++++++ portality/models/v1/bibjson.py | 1 + 2 files changed, 33 insertions(+) diff --git a/portality/crosswalks/article_crossref_xml.py b/portality/crosswalks/article_crossref_xml.py index e59340be48..790fa6aa4e 100644 --- a/portality/crosswalks/article_crossref_xml.py +++ b/portality/crosswalks/article_crossref_xml.py @@ -171,6 +171,7 @@ def crosswalk_article(self, record, journal): self.extract_issue(journal, bibjson) self.extract_pages(record, journal, bibjson) self.extract_doi(record, journal, bibjson) + self.extract_identifiers(record, journal, bibjson) self.extract_fulltext(record, journal, bibjson) self.extract_article_title(record, journal, bibjson) self.extract_authors(record, journal, bibjson) @@ -284,6 +285,37 @@ def extract_doi(self, record, journal, bibjson): if doi is not None: bibjson.add_identifier(bibjson.DOI, doi) + def extract_identifiers(self, record, journal, bibjson): + # this is used to extract item_number such as article number, eLocator, or e-location + # and capture other identifiers such as PII, DOI, DAI, report number + publisher_item = record.find("x:publisher_item", self.NS) + if publisher_item is not None: + ans = [] + ans1 = publisher_item.findall("x:item_number", self.NS) + ans2 = publisher_item.findall("x:identifier", self.NS) + if ans1 is not None: + ans = ans + ans1 + if ans2 is not None: + ans = ans + ans2 + for an in ans: + typ = None + typ1 = an.attrib["item_number_type"] + typ2 = an.attrib["id_type"] + if typ1: + typ = typ1 + elif typ2: + typ = typ2 + if typ and typ.lower() in ["eLocator", "e-location"]: + bibjson.add_identifier('e-location', an[0].text.upper()) + elif typ and typ.lower() in bibjson.IDENTIFIER_TYPES: + bibjson.add_identifier(typ.lower(), an[0].text.upper()) + elif typ: + # TODO - Do we capture identifiers of other types? + bibjson.add_identifier(typ.lower(), an[0].text.upper()) + else: + # TODO - Do we capture identifiers of unknown type? + bibjson.add_identifier(typ, an[0].text.upper()) + def extract_fulltext(self, record, journal, bibjson): d = record.find("x:doi_data", self.NS) ftel = _element(d, "x:resource", self.NS) diff --git a/portality/models/v1/bibjson.py b/portality/models/v1/bibjson.py index ba97fc67d9..418df35187 100644 --- a/portality/models/v1/bibjson.py +++ b/portality/models/v1/bibjson.py @@ -5,6 +5,7 @@ class GenericBibJSON(dataobj.DataObj): # vocab of known identifier types P_ISSN = "pissn" E_ISSN = "eissn" + IDENTIFIER_TYPES = ['handle', 'ark', 'doi', 'urn', 'dor', 'purl', 'e-location'] DOI = "doi" # allowable values for the url types From 33cdcbe422fc342f8493882a7cd69b8d2c040cc4 Mon Sep 17 00:00:00 2001 From: Anusha Ranganathan Date: Fri, 10 Jul 2026 14:28:29 +0530 Subject: [PATCH 2/4] Add elocation and support for other identifiers --- portality/crosswalks/article_crossref_xml.py | 10 +-- portality/crosswalks/article_form.py | 30 +++++++++ portality/forms/article_forms.py | 66 ++++++++++++++++++- portality/models/article.py | 16 +++++ portality/models/v1/bibjson.py | 4 +- .../includes/_article_metadata_form.html | 34 ++++++++++ portality/view/admin.py | 1 + portality/view/publisher.py | 1 + 8 files changed, 154 insertions(+), 8 deletions(-) diff --git a/portality/crosswalks/article_crossref_xml.py b/portality/crosswalks/article_crossref_xml.py index 790fa6aa4e..6ed6f53f33 100644 --- a/portality/crosswalks/article_crossref_xml.py +++ b/portality/crosswalks/article_crossref_xml.py @@ -286,8 +286,10 @@ def extract_doi(self, record, journal, bibjson): bibjson.add_identifier(bibjson.DOI, doi) def extract_identifiers(self, record, journal, bibjson): - # this is used to extract item_number such as article number, eLocator, or e-location - # and capture other identifiers such as PII, DOI, DAI, report number + # This is used to extract item_number such as article number, eLocator, or e-location + # and capture other identifiers such as handle, ark, doi, urn, dor and purl + # Both item_number and identifier have a type attribute which is free text. + # ToDO - Do we need to differentiate between item_number and other identifiers? publisher_item = record.find("x:publisher_item", self.NS) if publisher_item is not None: ans = [] @@ -306,7 +308,7 @@ def extract_identifiers(self, record, journal, bibjson): elif typ2: typ = typ2 if typ and typ.lower() in ["eLocator", "e-location"]: - bibjson.add_identifier('e-location', an[0].text.upper()) + bibjson.add_identifier(bibjson.ELOCATION, an[0].text.upper()) elif typ and typ.lower() in bibjson.IDENTIFIER_TYPES: bibjson.add_identifier(typ.lower(), an[0].text.upper()) elif typ: @@ -314,7 +316,7 @@ def extract_identifiers(self, record, journal, bibjson): bibjson.add_identifier(typ.lower(), an[0].text.upper()) else: # TODO - Do we capture identifiers of unknown type? - bibjson.add_identifier(typ, an[0].text.upper()) + bibjson.add_identifier('other', an[0].text.upper()) def extract_fulltext(self, record, journal, bibjson): d = record.find("x:doi_data", self.NS) diff --git a/portality/crosswalks/article_form.py b/portality/crosswalks/article_form.py index 9ab82cdef9..367cf9aaa5 100644 --- a/portality/crosswalks/article_form.py +++ b/portality/crosswalks/article_form.py @@ -91,6 +91,15 @@ def form2obj(cls, form, add_journal_info=True): end = form.end.data if end is not None and end != "": bibjson.end_page = end + elocation_id = form.elocation_id.data + if elocation_id is not None and elocation_id != "": + bibjson.add_identifier(bibjson.ELOCATION, elocation_id) + for subfield in form.other_identifiers: + if (subfield.form.type.data is not None and subfield.form.type.data != "" and + subfield.form.id.data is not None and subfield.form.id.data != ""): + id_typ = subfield.form.type.data + id_val = subfield.form.id.data + bibjson.add_identifier(id_typ, id_val) # add the journal info if requested if add_journal_info: @@ -153,4 +162,25 @@ def obj2form(cls, form, article): form.end.data = bibjson.end_page if bibjson.abstract: form.abstract.data = bibjson.abstract + elocation_id = bibjson.get_one_identifier("elocation") + if elocation_id: + form.elocation_id.data = elocation_id + other_identifiers = [] + for each_id in bibjson.get_identifiers(): + if not each_id['type'] in bibjson.NAMED_IDENTIFIERS: + other_identifiers.append(each_id) + if other_identifiers: + for i in range(len(form.other_identifiers)): + form.other_identifiers.pop_entry() + for i in other_identifiers: + id_form = OtherIdentifierForm() + if "type" in i: + id_form.type = i["type"] + else: + id_form.type = "" + if "id" in i: + id_form.id = i["id"] + else: + id_form.id = "" + form.other_identifiers.append_entry(id_form) diff --git a/portality/forms/article_forms.py b/portality/forms/article_forms.py index 28a84a6c62..077af0fbf6 100644 --- a/portality/forms/article_forms.py +++ b/portality/forms/article_forms.py @@ -14,6 +14,7 @@ from portality.forms.fields import DOAJSelectField, TagListField from portality.forms.validate import OptionalIf, ThisOrThat, NoScriptTag, DifferentTo from portality.lib import dates +from portality.models.v1 import bibjson from portality.ui.messages import Messages from portality.ui import templates @@ -561,6 +562,15 @@ class AuthorForm(Form): orcid_id = StringField("ORCID iD", [validators.Optional(), validators.Regexp(regex=regex.ORCID_COMPILED, message=ORCID_ERROR)]) +class OtherIdentifierForm(Form): + """ + ~~->$ OtherIdentifiers:Form~~ + """ + id_types = type = DOAJSelectField("Type of identifier", [validators.Optional()], choices=bibjson.GenericBibJSON.IDENTIFIER_TYPES, default="" ) + type = StringField("type", [validators.Optional(), NoScriptTag()]) + id = StringField("id", [validators.Optional(), NoScriptTag()]) + + class ArticleForm(Form): title = StringField("Article title (required)", [validators.DataRequired(), NoScriptTag()]) doi = StringField("DOI", [OptionalIf("fulltext", "You must provide the DOI or the Full-Text URL"), validators.Regexp(regex=regex.DOI_COMPILED, message=DOI_ERROR)], description="(You must provide a DOI and/or a Full-Text URL)") @@ -583,6 +593,8 @@ class ArticleForm(Form): number = StringField("Issue", [validators.Optional(), NoScriptTag()]) start = StringField("Start", [validators.Optional(), NoScriptTag()]) end = StringField("End", [validators.Optional(), NoScriptTag()]) + elocation_id = StringField("eLocation ID", [validators.Optional(), NoScriptTag()]) + other_identifiers = FieldList(FormField(OtherIdentifierForm), min_entries=1) def __init__(self, *args, **kwargs): super(ArticleForm, self).__init__(*args, **kwargs) @@ -599,7 +611,6 @@ def set_choices(self, user=None, article_id=None): app.logger.exception(str(e)) - ######################################### # Formcontexts and factory @@ -626,6 +637,7 @@ class MetadataForm(FormContext): def __init__(self, source, form_data, user): self.user = user self.author_error = False + self.other_identifier_error = False super(MetadataForm, self).__init__(source=source, form_data=form_data) def _set_choices(self): @@ -671,6 +683,48 @@ def _validate_authors(self): counted += 1 return counted >= 1 + def modify_other_identifiers_if_required(self, request_data): + + more_ids = request_data.get("more_other_identifiers") + remove_id = None + for v in list(request.values.keys()): + if v.startswith("remove_other_identifiers"): + remove_id = v.split("-")[1] + + # if the user wants more ids, add an extra entry + if more_ids: + return self.render_template(more_other_identifiers=True) + + # if the user wants to remove an author, do the various back-flips required + if remove_id is not None: + return self.render_template(remove_other_identifiers=remove_id) + + def _check_for_other_identifier_errors(self, **kwargs): + + if "more_other_identifiers" in kwargs and kwargs["more_other_identifiers"] == True: + self.form.other_identifiers.append_entry() + if "remove_other_identifiers" in kwargs: + keep = [] + while len(self.form.other_identifiers.entries) > 0: + entry = self.form.other_identifiers.pop_entry() + if entry.short_name == "other_identifiers-" + kwargs["remove_other_identifier"]: + break + else: + keep.append(entry) + while len(keep) > 0: + self.form.other_identifiers.append_entry(keep.pop().data) + + def _validate_other_identifiers(self): + errors = 0 + for entry in self.form.other_identifiers.entries: + typ = entry.data.get("type") + id = entry.data.get("id") + if typ is not None and typ != "" and (id is None or id == "") : + errors += 1 + elif id is not None and id != "" and (typ is None or typ == ""): + errors += 1 + return errors == 0 + def blank_form(self): self.form = ArticleForm() self._set_choices() @@ -690,6 +744,8 @@ def form2target(self): def validate(self): if not self._validate_authors(): self.author_error = True + if not self._validate_other_identifiers(): + self.other_identifier_error = True if not self.form.validate(): return False return True @@ -720,9 +776,11 @@ def set_template(self): def render_template(self, **kwargs): self._check_for_author_errors(**kwargs) + self._check_for_other_identifier_errors(**kwargs) if "validated" in kwargs and kwargs["validated"] == True: self.blank_form() - return render_template(self.template, form=self.form, form_context=self, author_error=self.author_error) + return render_template(self.template, form=self.form, form_context=self, author_error=self.author_error, + other_identifier_error=self.other_identifier_error) class AdminMetadataArticleForm(MetadataForm): @@ -737,4 +795,6 @@ def set_template(self): def render_template(self, **kwargs): self._check_for_author_errors(**kwargs) - return render_template(self.template, form=self.form, form_context=self, author_error=self.author_error) + self._check_for_other_identifier_errors() + return render_template(self.template, form=self.form, form_context=self, author_error=self.author_error, + other_identifier_error=self.other_identifier_error) diff --git a/portality/models/article.py b/portality/models/article.py index 7e46bc4f9f..d9b2db93cb 100644 --- a/portality/models/article.py +++ b/portality/models/article.py @@ -749,6 +749,22 @@ def end_page(self): def end_page(self, val): self._set_with_struct("end_page", val) + @property + def elocation(self): + self.elocation += self.get_identifiers(self.ELOCATION) + + @elocation.setter + def elocation(self, val): + self.add_identifier(self.ELOCATION, val) + + @property + def other_identifiers(self): + identifiers = [] + for each_id in self.get_identifiers(): + if not each_id['type'] in self.NAMED_IDENTIFIERS: + identifiers.append(each_id) + return identifiers + @property def abstract(self): return self._get_single("abstract") diff --git a/portality/models/v1/bibjson.py b/portality/models/v1/bibjson.py index 418df35187..fa48fc4472 100644 --- a/portality/models/v1/bibjson.py +++ b/portality/models/v1/bibjson.py @@ -5,8 +5,10 @@ class GenericBibJSON(dataobj.DataObj): # vocab of known identifier types P_ISSN = "pissn" E_ISSN = "eissn" - IDENTIFIER_TYPES = ['handle', 'ark', 'doi', 'urn', 'dor', 'purl', 'e-location'] DOI = "doi" + IDENTIFIER_TYPES = ['handle', 'ark', 'doi', 'urn', 'dor', 'purl'] + ELOCATION = 'e-location' + NAMED_IDENTIFIERS = [P_ISSN, E_ISSN, DOI, ELOCATION] # allowable values for the url types HOMEPAGE = "homepage" diff --git a/portality/templates-v2/_articles/includes/_article_metadata_form.html b/portality/templates-v2/_articles/includes/_article_metadata_form.html index 09ec2c9f13..1a54b2bb7e 100644 --- a/portality/templates-v2/_articles/includes/_article_metadata_form.html +++ b/portality/templates-v2/_articles/includes/_article_metadata_form.html @@ -4,6 +4,40 @@

Article

{{ render_field_horizontal(form.fulltext, placeholder="https://www.example.com") }} {{ render_field_horizontal(form.doi, placeholder="10.1234/article-32") }} +
+ Identifiers +
    + {% for subfield in form.other_identifiers %} +
  • + {% for field in subfield.form %} + {{ field }} + {% endfor %} + {% for error in subfield.errors %} +
    +
      +
    • {{ subfield.errors.get(error)[0] }}
    • +
    +
    + {% endfor %} + +
  • + {% endfor %} +

    + +

    + {% if other_identifier_error %} +
    +
      +
    • Please provide both the type and the identifier valuer
    • +
    +
    + {% endif %} +
+
diff --git a/portality/view/admin.py b/portality/view/admin.py index eb6da12ec1..a5ec3ed14f 100644 --- a/portality/view/admin.py +++ b/portality/view/admin.py @@ -185,6 +185,7 @@ def article_page(article_id): fc = ArticleFormFactory.get_from_context(role="admin", source=ap, user=user, form_data=request.form) fc.modify_authors_if_required(request.values) + fc.modify_other_identifiers_if_required(request.values) if fc.validate(): try: diff --git a/portality/view/publisher.py b/portality/view/publisher.py index f5d23d062d..a99a55fa34 100644 --- a/portality/view/publisher.py +++ b/portality/view/publisher.py @@ -361,6 +361,7 @@ def metadata(): # the user might request by pressing the add/remove authors buttons fc.modify_authors_if_required(request.values) + fc.modify_other_identifiers_if_required(request.values) validated = False if fc.validate(): From 9090c8caa137af448c614fe11c7da03c3a40f7f1 Mon Sep 17 00:00:00 2001 From: Anusha Ranganathan Date: Wed, 15 Jul 2026 11:26:52 +0530 Subject: [PATCH 3/4] Display choices and hide other id type field --- portality/crosswalks/article_form.py | 12 +++--- portality/forms/article_forms.py | 11 ++++-- portality/static/js/article_metadata_form.js | 15 ++++++++ .../includes/_article_metadata_form.html | 38 +++++++++++++++---- 4 files changed, 59 insertions(+), 17 deletions(-) diff --git a/portality/crosswalks/article_form.py b/portality/crosswalks/article_form.py index 367cf9aaa5..5458ebd41d 100644 --- a/portality/crosswalks/article_form.py +++ b/portality/crosswalks/article_form.py @@ -91,9 +91,9 @@ def form2obj(cls, form, add_journal_info=True): end = form.end.data if end is not None and end != "": bibjson.end_page = end - elocation_id = form.elocation_id.data - if elocation_id is not None and elocation_id != "": - bibjson.add_identifier(bibjson.ELOCATION, elocation_id) + elocation = form.elocation.data + if elocation is not None and elocation != "": + bibjson.add_identifier(bibjson.ELOCATION, elocation) for subfield in form.other_identifiers: if (subfield.form.type.data is not None and subfield.form.type.data != "" and subfield.form.id.data is not None and subfield.form.id.data != ""): @@ -162,9 +162,9 @@ def obj2form(cls, form, article): form.end.data = bibjson.end_page if bibjson.abstract: form.abstract.data = bibjson.abstract - elocation_id = bibjson.get_one_identifier("elocation") - if elocation_id: - form.elocation_id.data = elocation_id + elocation = bibjson.get_one_identifier(bibjson.ELOCATION) + if elocation: + form.elocation.data = elocation other_identifiers = [] for each_id in bibjson.get_identifiers(): if not each_id['type'] in bibjson.NAMED_IDENTIFIERS: diff --git a/portality/forms/article_forms.py b/portality/forms/article_forms.py index 077af0fbf6..c7263b8961 100644 --- a/portality/forms/article_forms.py +++ b/portality/forms/article_forms.py @@ -566,9 +566,12 @@ class OtherIdentifierForm(Form): """ ~~->$ OtherIdentifiers:Form~~ """ - id_types = type = DOAJSelectField("Type of identifier", [validators.Optional()], choices=bibjson.GenericBibJSON.IDENTIFIER_TYPES, default="" ) - type = StringField("type", [validators.Optional(), NoScriptTag()]) - id = StringField("id", [validators.Optional(), NoScriptTag()]) + choices = [('Select type', '')] + choices += [(i.upper(), i) for i in bibjson.GenericBibJSON.IDENTIFIER_TYPES] + choices += [('Other', 'other')] + id_types = DOAJSelectField("Type of identifier", [validators.Optional()], choices=choices, default="" ) + type = StringField("Type", [validators.Optional(), NoScriptTag()]) + id = StringField("ID", [validators.Optional(), NoScriptTag()]) class ArticleForm(Form): @@ -593,7 +596,7 @@ class ArticleForm(Form): number = StringField("Issue", [validators.Optional(), NoScriptTag()]) start = StringField("Start", [validators.Optional(), NoScriptTag()]) end = StringField("End", [validators.Optional(), NoScriptTag()]) - elocation_id = StringField("eLocation ID", [validators.Optional(), NoScriptTag()]) + elocation = StringField("eLocation ID", [validators.Optional(), NoScriptTag()]) other_identifiers = FieldList(FormField(OtherIdentifierForm), min_entries=1) def __init__(self, *args, **kwargs): diff --git a/portality/static/js/article_metadata_form.js b/portality/static/js/article_metadata_form.js index 9e187e3f3f..cbfb2b21f1 100644 --- a/portality/static/js/article_metadata_form.js +++ b/portality/static/js/article_metadata_form.js @@ -132,4 +132,19 @@ $(document).ready(function() { $("#article_metadata_form").on("submit", function(event) { $("button[type=submit]").prop("disabled", true); }) + + $(".id_types").on("change", (e) => { + e.preventDefault(); + var selected_val = $(this).text(); + console.log(selected_val); + if (selected_val === "Other") { + console.log("Parent: ", $(this).parents(".identifier-item").class); + $(this).parents(".identifier-item").css({"color": "red", "border": "2px solid red"}) + // $(".id_type",parent(".identifier-item")).show(); + } else { + console.log("Parent: ", $(this).parents(".identifier-item").class); + $(this).parents(".identifier-item").css({"color": "blue", "border": "2px solid red"}) + // $(".id_type",parent(".identifier-item")).hide(); + } + }); }) \ No newline at end of file diff --git a/portality/templates-v2/_articles/includes/_article_metadata_form.html b/portality/templates-v2/_articles/includes/_article_metadata_form.html index 1a54b2bb7e..a718435edc 100644 --- a/portality/templates-v2/_articles/includes/_article_metadata_form.html +++ b/portality/templates-v2/_articles/includes/_article_metadata_form.html @@ -6,11 +6,32 @@

Article

Identifiers -
    +
    + {% for subfield in form.other_identifiers %} -
  • +
    + {% set typ = None %} + {% for field in subfield.form %} + {% if field.label.text == "Type of identifier" %} + {% set typ = field.data %} + {% endif %} + {% endfor %} {% for field in subfield.form %} - {{ field }} + {% if field.label.text == "Type" %} +
    + {{ field(placeholder="Type of identifier") }} +
    + {% elif field.label.text == "ID" %} + {{ field(placeholder="Identifier value") }} + {% else %} + + {% endif %} {% endfor %} {% for error in subfield.errors %}
    @@ -22,7 +43,7 @@

    Article

    -
  • +
    {% endfor %}

+
@@ -124,7 +145,7 @@

Journal

-

Page(s)

+

Location(s)

@@ -133,6 +154,9 @@

Page(s)

{{ render_field_horizontal(form.end) }}
+
+ {{ render_field_horizontal(form.elocation) }} +
From ca641e83fe4c58f0774ec7d47f956e07cee80176 Mon Sep 17 00:00:00 2001 From: "kush.varade.19" Date: Wed, 15 Jul 2026 11:42:53 +0530 Subject: [PATCH 4/4] Add/Remove for identifier functionality is added --- portality/forms/article_forms.py | 2 +- portality/static/js/article_metadata_form.js | 235 ++++++++++++------ .../includes/_article_metadata_form.html | 12 +- 3 files changed, 163 insertions(+), 86 deletions(-) diff --git a/portality/forms/article_forms.py b/portality/forms/article_forms.py index 077af0fbf6..99c442bf79 100644 --- a/portality/forms/article_forms.py +++ b/portality/forms/article_forms.py @@ -795,6 +795,6 @@ def set_template(self): def render_template(self, **kwargs): self._check_for_author_errors(**kwargs) - self._check_for_other_identifier_errors() + self._check_for_other_identifier_errors(**kwargs) return render_template(self.template, form=self.form, form_context=self, author_error=self.author_error, other_identifier_error=self.other_identifier_error) diff --git a/portality/static/js/article_metadata_form.js b/portality/static/js/article_metadata_form.js index 9e187e3f3f..f6071d984e 100644 --- a/portality/static/js/article_metadata_form.js +++ b/portality/static/js/article_metadata_form.js @@ -2,112 +2,189 @@ $(document).ready(function() { - function prepAuthorContainer(params) { - var ne = params.element; - var reset = params.reset_value; - var number = params.number; + function initRepeatableFieldList(options) { + var prefix = options.prefix; + var removeButtonSelector = options.removeButtonSelector; + var addButtonSelector = options.addButtonSelector; + var onAdd = options.onAdd; + + function prepContainer(params) { + var ne = params.element; + var reset = params.reset_value; + var number = params.number; + + ne.id = prefix + '-' + number + '-container'; + + ne = $(ne); + ne.find('[id^=' + prefix + '-]').each(function () { + var ce = $(this); + + // reset the value + if (reset) { + if (ce.is('select')) { + // selects may have no blank option, so val('') can leave nothing selected; + // default to the first option instead + ce.prop('selectedIndex', 0); + } else { + ce.val(''); + } + } + + // set the id as requested + var items = ce.attr('id').split('-'); + var id = prefix + '-' + number + '-' + items[2]; + + // set both the id and the name to the new id, as per wtforms requirements + ce.attr('id', id); + ce.attr('name', id); + }); - ne.id = 'authors-' + number + '-container'; + // we also need to update the remove button + ne.find(removeButtonSelector).each(function () { + var ce = $(this); + var id = 'remove_' + prefix + '-' + number; - ne = $(ne); - ne.find('[id^=authors-]').each(function () { - var ce = $(this); + // set both the id and the name to the new id + ce.attr('id', id); + ce.attr('name', id); + }); + } - // reset the value - if (reset) { - ce.val('') + function showHideFirstRemoveButton() { + // Hide delete button when there's just one entry + if ($('[id^=' + prefix + '-][id$="container"]').length === 1) { + $('#remove_' + prefix + '-0').css('display', 'none'); + } else { + $('#remove_' + prefix + '-0').css('display', 'inherit'); } + } - // set the id as requestsed - items = ce.attr('id').split('-'); - var id = 'authors-' + number + '-' + items[2]; - - // set both the id and the name to the new id, as per wtforms requirements - ce.attr('id', id); - ce.attr('name', id); - }); + function removeItem(event) { + event.preventDefault(); - // we also need to update the remove button - ne.find("[id^=remove_authors-]").each(function () { - var ce = $(this); + var id = $(this).attr("id"); + // strip the leading "remove_" to get the container's short_name (e.g. other_identifiers-0) + var short_name = id.replace(/^remove_/, ''); + var container = short_name + "-container"; - // update the id as above - saving us a closure again - items = ce.attr('id').split('-'); - var id = 'remove_authors-' + number; + $("#" + container).remove(); - // set both the id and the name to the new id - ce.attr('id', id); - ce.attr('name', id); - }) - } + var count = 0; + $('[id^=' + prefix + '-][id$="container"]').each(function () { + prepContainer({ + element: this, + number: count, + reset_value: false + }); + count++; + }); - function showHideFirstRemoveButton() { - // Hide delete button when there's just one author - if ($('[id^=authors-][id$="container"]').length === 1) { - $('#remove_authors-0').css('display', 'none') - } else { - $('#remove_authors-0').css('display', 'inherit') + showHideFirstRemoveButton(); } - } - function removeAuthor() { - event.preventDefault(); + $(addButtonSelector).click(function (event) { + event.preventDefault(); - var id = $(this).attr("id"); - var short_name = id.split("_")[1]; - var container = short_name + "-container"; + // get the last entry in the list + var all_e = $('[id^=' + prefix + '-][id$="container"]'); + var e = all_e.last(); - $("#" + container).remove(); + // make a clone of the last entry + var ne = e.clone()[0]; - var count = 0; - $('[id^=authors-][id$="container"]').each(function () { - prepAuthorContainer({ - element: this, - number: count, - reset_value: false + // extract the last entry's number from the container id and increment it + var items = ne.id.split('-'); + var number = parseInt(items[1]); + number = number + 1; + + // increment all the numbers + prepContainer({ + element: ne, + number: number, + reset_value: true }); - count++; + + e.after(ne); + + var rem_b = $(removeButtonSelector); + rem_b.unbind("click"); + rem_b.click(removeItem); + if (all_e.length === 1) { + $('#remove_' + prefix + '-1').css('display', 'inherit'); + } + + showHideFirstRemoveButton(); + + if (onAdd) { + onAdd(ne); + } }); - showHideFirstRemoveButton() + showHideFirstRemoveButton(); + $(removeButtonSelector).click(removeItem); } - $('button[name=more_authors]').click(function (event) { - event.preventDefault(); - - // get the last author div in the list - var all_e = $('[id^=authors-][id$="container"]'); - var e = all_e.last(); + initRepeatableFieldList({ + prefix: 'authors', + removeButtonSelector: '.remove_author__button', + addButtonSelector: 'button[name=more_authors]' + }); - // make a clone of the last author div - var ne = e.clone()[0]; + initRepeatableFieldList({ + prefix: 'other_identifiers', + removeButtonSelector: '.remove_identifier__button', + addButtonSelector: 'button[name=more_other_identifiers]', + onAdd: function (container) { + syncIdentifierTypeVisibility($(container).find('.identifier-type-select')); + } + }); - // extract the last author's number from the div id and increment it - var items = ne.id.split('-'); - var number = parseInt(items[1]); - number = number + 1; + // The "Type of identifier" dropdown offers the known identifier types plus an + // "other" option. The free-text type field is only needed (and shown) when + // "other" is selected - the rest of the time the dropdown value *is* the type. + function syncIdentifierTypeVisibility(select) { + var typeInput = select.closest('.identifier-item').find('.identifier-type-other'); + if (select.val() === 'other') { + typeInput.show(); + } else { + typeInput.hide(); + } + } - // increment all the numbers - prepAuthorContainer({ - element: ne, - number: number, - reset_value: true - }); + $('#identifier-list').on('change', '.identifier-type-select', function () { + syncIdentifierTypeVisibility($(this)); + }); - e.after(ne); + $('.identifier-type-select').each(function () { + var select = $(this); + var typeInput = select.closest('.identifier-item').find('.identifier-type-other'); + var existingType = typeInput.val(); - var rem_b = $(".remove_field__button"); - rem_b.unbind("click"); - rem_b.click(removeAuthor); - if (all_e.length === 1) { - $('#remove_authors-1').css('display', 'inherit') + // an existing type that isn't one of the dropdown's known options must have + // been freely typed in previously, so select "other" and reveal the field + if (existingType && select.find('option[value="' + existingType + '"]').length === 0) { + select.val('other'); } - showHideFirstRemoveButton(); + syncIdentifierTypeVisibility(select); }); - showHideFirstRemoveButton(); - $(".remove_field__button").click(removeAuthor); + $("#article_metadata_form").on("submit", function () { + // whenever a known type is selected, that dropdown value is the type - + // copy it into the (possibly hidden) type field so it gets submitted. + // Rows the user hasn't engaged with (no identifier value entered) are left + // alone so they don't get flagged as incomplete. + $('#identifier-list .identifier-item').each(function () { + var item = $(this); + var select = item.find('.identifier-type-select'); + var idInput = item.find('input[id$="-id"]'); + var typeInput = item.find('.identifier-type-other'); + + if (select.val() !== 'other' && idInput.val() !== '') { + typeInput.val(select.val()); + } + }); + }); $("#pissn").select2({ allowClear: false, @@ -132,4 +209,4 @@ $(document).ready(function() { $("#article_metadata_form").on("submit", function(event) { $("button[type=submit]").prop("disabled", true); }) - }) \ No newline at end of file + }) diff --git a/portality/templates-v2/_articles/includes/_article_metadata_form.html b/portality/templates-v2/_articles/includes/_article_metadata_form.html index 1a54b2bb7e..2d1d9d9d5b 100644 --- a/portality/templates-v2/_articles/includes/_article_metadata_form.html +++ b/portality/templates-v2/_articles/includes/_article_metadata_form.html @@ -8,10 +8,10 @@

Article

Identifiers
    {% for subfield in form.other_identifiers %} -
  • - {% for field in subfield.form %} - {{ field }} - {% endfor %} +
  • + {{ subfield.form.id_types(class_="identifier-type-select") }} + {{ subfield.form.type(class_="identifier-type-other", placeholder="Type", style="display:none;") }} + {{ subfield.form.id }} {% for error in subfield.errors %}
      @@ -19,7 +19,7 @@

      Article

    {% endfor %} -
  • @@ -61,7 +61,7 @@

    Author (required)

{% endfor %} -