diff --git a/ckanext/scheming/assets/js/scheming-suggestions.js b/ckanext/scheming/assets/js/scheming-suggestions.js new file mode 100644 index 00000000..a9bdc20b --- /dev/null +++ b/ckanext/scheming/assets/js/scheming-suggestions.js @@ -0,0 +1,229 @@ +ckan.module('scheming-suggestions', function($) { + return { + initialize: function() { + console.log("Initializing scheming-suggestions module"); + + var el = this.el; + var content = $(el).attr('data-content'); + + var $popoverDiv = $(''); + $popoverDiv.html(content); + $('body').append($popoverDiv); + + $(el).closest('.control-group').addClass('has-suggestion'); + + $popoverDiv.find('.suggestion-apply-btn').each(function() { + var isValid = $(this).data('is-valid') !== 'false'; + if (!isValid) { + $(this).addClass('suggestion-apply-btn-disabled'); + } + }); + + $(el).on('click', function(e) { + e.preventDefault(); + e.stopPropagation(); + + $('.custom-suggestion-popover').hide(); + + var buttonPos = $(el).offset(); + var parentWidth = $(window).width(); + var popoverWidth = Math.min(350, parentWidth - 40); + + var leftPos = buttonPos.left; + if (leftPos + popoverWidth > parentWidth - 20) { + leftPos = Math.max(20, parentWidth - popoverWidth - 20); + } + + $popoverDiv.css({ + position: 'absolute', + top: buttonPos.top + $(el).outerHeight() + 10, + left: leftPos, + maxWidth: popoverWidth + 'px', + zIndex: 1000 + }); + + $popoverDiv.toggle(); + }); + + // Close popover when clicking outside + $(document).on('click', function(e) { + if (!$(e.target).closest('.suggestion-btn').length && + !$(e.target).closest('.custom-suggestion-popover').length) { + $popoverDiv.hide(); + } + }); + + // Handle formula toggle button + $popoverDiv.on('click', '.formula-toggle-btn', function(e) { + e.preventDefault(); + e.stopPropagation(); + + var $formulaSection = $(this).closest('.suggestion-popover-content').find('.suggestion-formula'); + var $toggleIcon = $(this).find('.formula-toggle-icon'); + var $toggleText = $(this).find('.formula-toggle-text'); + + if ($formulaSection.is(':visible')) { + // Hide formula + $formulaSection.slideUp(200); + $toggleIcon.html('▼'); // Down arrow + $toggleText.text('Show formula'); + $(this).removeClass('formula-toggle-active'); + } else { + // Show formula + $formulaSection.slideDown(200); + $toggleIcon.html('▲'); // Up arrow + $toggleText.text('Hide formula'); + $(this).addClass('formula-toggle-active'); + } + }); + + // Handle copy formula button + $popoverDiv.on('click', '.copy-formula-btn', function(e) { + e.preventDefault(); + e.stopPropagation(); + + var formula = $(this).data('formula'); + var $copyBtn = $(this); + + // Use Clipboard API to copy formula + navigator.clipboard.writeText(formula).then(function() { + // Show success state + $copyBtn.addClass('copy-success'); + + // Store original content + var $icon = $copyBtn.find('svg'); + var originalHtml = $icon.html(); + + // Show checkmark + $icon.html(''); + + // Reset after 2 seconds + setTimeout(function() { + $copyBtn.removeClass('copy-success'); + $icon.html(originalHtml); + }, 2000); + }).catch(function(err) { + console.error('Could not copy formula: ', err); + }); + }); + + // Handle apply suggestion button + $popoverDiv.on('click', '.suggestion-apply-btn', function(e) { + e.preventDefault(); + e.stopPropagation(); + + // Skip if button is disabled + if ($(this).hasClass('suggestion-apply-btn-disabled')) { + console.log("Ignoring click on disabled apply button"); + return; + } + + var targetId = $(this).data('target'); + var suggestionValue = $(this).data('value'); + var isSelectField = $(this).data('is-select') === 'true'; + var isValid = $(this).data('is-valid') !== 'false'; // Default to true if not specified + var $target = $('#' + targetId); + + if ($target.length === 0) { + console.error("Target not found:", targetId); + return; + } + + console.log("Applying suggestion to", targetId); + + // Handle different input types + if ($target.is('textarea')) { + $target.val(suggestionValue); + // If it's a markdown editor, also update the preview + if ($target.hasClass('markdown-editor')) { + $target.trigger('change'); + } + } else if ($target.is('input[type="text"]')) { + $target.val(suggestionValue); + } else if ($target.is('select')) { + if (isValid) { + $target.val(suggestionValue); + $target.trigger('change'); + } else { + var $warningMsg = $('
The suggested value is not a valid option
'); + $warningMsg.css({ + position: 'absolute', + top: $target.offset().top - 25, + left: $target.offset().left + $target.outerWidth() / 2, + transform: 'translateX(-50%)', + backgroundColor: '#e67e22', + color: 'white', + padding: '4px 10px', + borderRadius: '4px', + fontSize: '12px', + fontWeight: 'bold', + zIndex: 1010, + opacity: 0, + transition: 'opacity 0.3s ease' + }); + $('body').append($warningMsg); + + setTimeout(function() { + $warningMsg.css('opacity', '1'); + }, 10); + + setTimeout(function() { + $warningMsg.css('opacity', '0'); + setTimeout(function() { + $warningMsg.remove(); + }, 300); + }, 3000); + + $target.addClass('suggestion-invalid'); + setTimeout(function() { + $target.removeClass('suggestion-invalid'); + }, 3000); + + // Hide the popover + $popoverDiv.hide(); + return; + } + } + + // Add a success class for animation + $target.addClass('suggestion-applied'); + setTimeout(function() { + $target.removeClass('suggestion-applied'); + }, 1000); + + // Show a brief success message + var $successMsg = $('
Suggestion applied!
'); + $successMsg.css({ + position: 'absolute', + top: $target.offset().top - 25, + left: $target.offset().left + $target.outerWidth() / 2, + transform: 'translateX(-50%)', + backgroundColor: 'rgba(42, 145, 52, 0.9)', + color: 'white', + padding: '4px 10px', + borderRadius: '4px', + fontSize: '12px', + fontWeight: 'bold', + zIndex: 1010, + opacity: 0, + transition: 'opacity 0.3s ease' + }); + $('body').append($successMsg); + + setTimeout(function() { + $successMsg.css('opacity', '1'); + }, 10); + + setTimeout(function() { + $successMsg.css('opacity', '0'); + setTimeout(function() { + $successMsg.remove(); + }, 300); + }, 1500); + + // Hide the popover + $popoverDiv.hide(); + }); + } + }; + }); \ No newline at end of file diff --git a/ckanext/scheming/assets/resource.config b/ckanext/scheming/assets/resource.config index 50d1622f..38996f51 100644 --- a/ckanext/scheming/assets/resource.config +++ b/ckanext/scheming/assets/resource.config @@ -5,3 +5,4 @@ main = base/main main = js/scheming-multiple-text.js js/scheming-repeating-subfields.js + js/scheming-suggestions.js \ No newline at end of file diff --git a/ckanext/scheming/assets/styles/scheming.css b/ckanext/scheming/assets/styles/scheming.css index 9e30967a..dc18ee26 100644 --- a/ckanext/scheming/assets/styles/scheming.css +++ b/ckanext/scheming/assets/styles/scheming.css @@ -39,3 +39,447 @@ a.btn.btn-multiple-remove { .radio-group label { font-weight: normal; } + + +.form-label label::after { + content: none !important; +} + +.control-label-with-suggestion label:after { + content: none !important; +} + +label:after { + content: none !important; +} + + + +.suggestion-btn { + display: inline-flex; + align-items: center; + justify-content: center; + vertical-align: middle; + border: none; + background-color: transparent; + color: #2a9134; /* darker green */ + width: 20px; + height: 20px; + padding: 0; + cursor: pointer; + transition: all 0.2s ease; + position: relative; + top: -1px; +} + +.suggestion-btn:hover, .suggestion-btn:focus { + transform: scale(1.2); + outline: none; +} + +.suggestion-btn svg { + width: 20px; + height: 20px; + fill: #2a9134; /* darker green */ +} + +.suggestion-field-wrapper { + position: relative; +} + +/* Label with suggestion styling */ +.control-label-with-suggestion { + display: flex; + flex-wrap: nowrap; /* Prevent wrapping */ +} + +/* FIX for colon issue - hide the default colon */ +.control-label-with-suggestion .form-label::after { + content: none !important; +} + +/* Custom popover styles */ +.custom-suggestion-popover { + max-width: 350px; + position: absolute; + background-color: #fff; + border: none; + border-radius: 12px; + box-shadow: 0 8px 32px rgba(0,0,0,0.12), 0 4px 8px rgba(0,0,0,0.08); + padding: 16px; + z-index: 1000; +} + +.custom-suggestion-popover:before { + content: ''; + display: block; + position: absolute; + width: 12px; + height: 12px; + background: #fff; + transform: rotate(45deg); + top: -6px; + left: 15px; + box-shadow: -2px -2px 5px rgba(0,0,0,0.05); +} + +.suggestion-popover-content { + padding: 0; +} + +.suggestion-popover-content strong { + display: block; + margin-bottom: 10px; + color: #333; + font-size: 16px; + font-weight: 600; +} + +/* Warning for invalid dropdown values */ +.suggestion-warning { + display: flex; + align-items: flex-start; + margin-top: 8px; + padding: 10px; + background-color: #fff7ed; + border-radius: 6px; + border-left: 3px solid #e67e22; + font-size: 12px; + line-height: 1.4; + color: #8d4004; +} + +.suggestion-warning svg { + flex-shrink: 0; + margin-right: 8px; + stroke: #e67e22; + margin-top: 2px; +} + +.suggestion-warning span { + display: block; +} + +/* Invalid selection animation */ +@keyframes invalidShake { + 0%, 100% { transform: translateX(0); } + 20%, 60% { transform: translateX(-5px); } + 40%, 80% { transform: translateX(5px); } +} + +select.suggestion-invalid { + animation: invalidShake 0.5s ease; + border-color: #e67e22 !important; + box-shadow: 0 0 0 3px rgba(230, 126, 34, 0.2) !important; +} + +.suggestion-warning-message { + animation: fadeInOut 1.5s ease-in-out; + box-shadow: 0 2px 10px rgba(0,0,0,0.2); + background-color: #e67e22 !important; +} + +.suggestion-value { + margin: 12px 0; + padding: 12px; + background-color: #f8f9fa; + border-radius: 6px; + font-family: monospace; + word-break: break-all; + border-left: 3px solid #2a9134; /* darker green */ + border-top: none; + border-right: none; + border-bottom: none; + max-height: 200px; + overflow-y: auto; + font-size: 13px; + color: #333; + box-shadow: inset 0 1px 3px rgba(0,0,0,0.05); +} + +/* Formula toggle button */ +.formula-toggle { + margin: 8px 0 0 0; +} + +.formula-toggle-btn { + background: none; + border: none; + color: #2a9134; + font-size: 12px; + font-weight: 600; + cursor: pointer; + padding: 0; + display: flex; + align-items: center; + opacity: 0.8; + transition: all 0.2s ease; +} + +.formula-toggle-btn:hover { + opacity: 1; + text-decoration: underline; +} + +.formula-toggle-active { + opacity: 1; +} + +.formula-toggle-icon { + font-size: 10px; + margin-right: 4px; + transition: transform 0.2s ease; +} + +/* Formula display */ +.suggestion-formula { + margin: 8px 0; + padding: 10px; + background-color: #f1f1f1; + border-radius: 4px; + font-size: 12px; + line-height: 1.4; + color: #555; + border-left: 2px solid #2a9134; +} + +.formula-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 5px; + font-weight: 600; + color: #444; + font-size: 11px; + text-transform: uppercase; +} + +.copy-formula-btn { + background: none; + border: none; + color: #2a9134; + cursor: pointer; + padding: 3px; + display: flex; + align-items: center; + justify-content: center; + opacity: 0.7; + transition: all 0.2s ease; + border-radius: 4px; +} + +.copy-formula-btn:hover { + opacity: 1; + background-color: rgba(42, 145, 52, 0.1); +} + +.copy-formula-btn.copy-success { + color: #2a9134; + opacity: 1; + background-color: rgba(42, 145, 52, 0.15); +} + +.suggestion-formula code { + white-space: pre-wrap; + word-break: break-all; + font-family: monospace; + display: block; +} + +.suggestion-apply-btn { + margin-top: 12px; + padding: 10px; + font-size: 14px; + background-color: #2a9134; /* darker green */ + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + display: block; + width: 100%; + font-weight: 600; + transition: all 0.2s ease; + text-transform: uppercase; + letter-spacing: 0.5px; + box-shadow: 0 2px 5px rgba(42, 145, 52, 0.3); +} + +/* Disabled apply button for invalid suggestions */ +.suggestion-apply-btn-disabled { + background-color: #cccccc !important; + color: #888888 !important; + cursor: not-allowed !important; + box-shadow: none !important; + opacity: 0.7; +} + +.suggestion-apply-btn-disabled:hover { + transform: none !important; + background-color: #cccccc !important; + box-shadow: none !important; +} + +.suggestion-apply-btn:hover { + background-color: #217a2b; /* even darker on hover */ + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(42, 145, 52, 0.4); +} + +/* Button pressed effect */ +.suggestion-apply-btn:active { + transform: translateY(0); + box-shadow: 0 1px 2px rgba(42, 145, 52, 0.4); +} + +/* Custom tooltip styling */ +.suggestion-btn { + position: relative; +} + +.suggestion-btn:hover::before { + content: attr(title); + position: absolute; + bottom: 100%; + left: 50%; + transform: translateX(-50%); + padding: 6px 12px; + border-radius: 4px; + background-color: rgba(42, 145, 52, 0.9); /* darker green */ + color: white; + font-size: 12px; + white-space: nowrap; + z-index: 1001; + pointer-events: none; + font-weight: normal; + margin-bottom: 5px; + box-shadow: 0 2px 10px rgba(0,0,0,0.1); +} + +.suggestion-btn:hover::after { + content: ''; + position: absolute; + top: -5px; + left: 50%; + transform: translateX(-50%); + border-width: 5px 5px 0; + border-style: solid; + border-color: rgba(42, 145, 52, 0.9) transparent transparent; /* darker green */ + z-index: 1001; + pointer-events: none; +} + +/* Animation for suggestion button */ +@keyframes pulse { + 0% { transform: scale(1); } + 50% { transform: scale(1.1); } + 100% { transform: scale(1); } +} + +.suggestion-btn { + animation: pulse 2s infinite; +} + +.suggestion-btn:hover { + animation: none; +} + +/* Improved popover styling */ +.custom-suggestion-popover { + animation: fadeIn 0.2s ease-in-out; +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(-10px); } + to { opacity: 1; transform: translateY(0); } +} + +/* Suggestion button in field designs with icon */ +.control-group.has-suggestion .control-label::before { + content: ''; + display: inline-block; + width: 4px; + height: 16px; + background-color: #2a9134; + margin-right: 6px; + border-radius: 2px; + vertical-align: middle; +} + +/* Fade-in effect for suggestion application */ +@keyframes highlightSuccess { + 0% { background-color: rgba(42, 145, 52, 0); } + 50% { background-color: rgba(42, 145, 52, 0.12); } + 100% { background-color: rgba(42, 145, 52, 0); } +} + +.suggestion-applied { + animation: highlightSuccess 1s ease-in-out; +} + +/* Success message animation */ +.suggestion-success-message { + animation: fadeInOut 1.5s ease-in-out; + box-shadow: 0 2px 10px rgba(0,0,0,0.2); +} + +@keyframes fadeInOut { + 0% { opacity: 0; transform: translateY(10px); } + 20% { opacity: 1; transform: translateY(0); } + 80% { opacity: 1; transform: translateY(0); } + 100% { opacity: 0; transform: translateY(-10px); } +} + +/* Add styles for mobile devices */ +@media (max-width: 767px) { + .custom-suggestion-popover { + width: 90%; + max-width: none; + left: 5% !important; + right: 5% !important; + } + + .suggestion-btn { + width: 24px; + height: 24px; + } + + .suggestion-btn svg { + width: 24px; + height: 24px; + } + + .formula-toggle-btn { + padding: 8px 0; + } +} + +.preformulated-field { + position: relative; + background-color: #f9f9f9; + border-left: 3px solid #2a9134; + padding-left: 10px; +} + +.preformulated-field input, +.preformulated-field textarea, +.preformulated-field select { + background-color: #f9f9f9 !important; + border-color: #e0e0e0 !important; + cursor: not-allowed !important; + color: #555 !important; +} + +.preformulated-field-notice { + display: block; + margin-top: 5px; + font-size: 12px; + color: #555; + font-style: italic; + background-color: #f3f3f3; + padding: 5px 10px; + border-radius: 3px; +} + +#dataset-edit > div:nth-child(8) > label > div > span.preformulated-icon > svg { +vertical-align: text-top; +} \ No newline at end of file diff --git a/ckanext/scheming/assets/webassets.yml b/ckanext/scheming/assets/webassets.yml index b3710959..a6f0450b 100644 --- a/ckanext/scheming/assets/webassets.yml +++ b/ckanext/scheming/assets/webassets.yml @@ -20,3 +20,12 @@ multiple_text: - base/main contents: - js/scheming-multiple-text.js + +suggestions: + filters: rjsmin + output: ckanext-scheming/%(version)s_scheming_suggestions.js + extra: + preload: + - base/main + contents: + - js/scheming-suggestions.js \ No newline at end of file diff --git a/ckanext/scheming/helpers.py b/ckanext/scheming/helpers.py index 80166d24..b9fd08c7 100644 --- a/ckanext/scheming/helpers.py +++ b/ckanext/scheming/helpers.py @@ -5,6 +5,7 @@ import pytz import json import six +import logging from jinja2 import Environment from ckan.plugins.toolkit import config, _, h @@ -12,6 +13,7 @@ from ckanapi import LocalCKAN, NotFound, NotAuthorized all_helpers = {} +logger = logging.getLogger(__name__) def helper(fn): """ @@ -459,3 +461,79 @@ def scheming_missing_required_fields(pages, data=None, package_id=None): if f.get('required') and not data.get(f['field_name']) ]) return missing + +@helper +def scheming_field_suggestion(field): + """ + Returns suggestion data for a field if it exists + """ + suggestion_label = field.get('suggestion_label', field.get('label', '')) + suggestion_formula = field.get('suggestion_formula', field.get('suggest_jinja2', None)) + + if suggestion_formula: + return { + 'label': suggestion_label, + 'formula': suggestion_formula + } + return None + + + +@helper +def scheming_get_suggestion_value(field_name, data=None, errors=None, lang=None): + if not data: + return '' + + try: + # Log the field name + logger.info(f"Field name extracted: {field_name}") + + # Get package data (where dpp_suggestions is stored) + package_data = data + logger.info(f"Data passed to scheming_get_suggestion_value: {data}") + + # Check if dpp_suggestions exists and has the package section + if (package_data and 'dpp_suggestions' in package_data and + isinstance(package_data['dpp_suggestions'], dict) and + 'package' in package_data['dpp_suggestions']): + + # Get the suggestion value if it exists + if field_name in package_data['dpp_suggestions']['package']: + logger.info(f"Suggestion value found for field '{field_name}': {package_data['dpp_suggestions']['package'][field_name]}") + return package_data['dpp_suggestions']['package'][field_name] + + # No suggestion value found + return '' + except Exception as e: + # Log the error but don't crash + logger.warning(f"Error getting suggestion value: {e}") + return '' + +@helper +def scheming_is_valid_suggestion(field, value): + """ + Check if a suggested value is valid for a field, particularly for select fields + """ + # If not a select/choice field, always valid + if not field.get('choices') and not field.get('choices_helper'): + return True + + # Get all valid choices for this field + choices = scheming_field_choices(field) + if not choices: + return True + + # Check if the value is in the list of valid choices + for choice in choices: + if choice['value'] == value: + return True + + return False + +@helper +def is_preformulated_field(field): + """ + Check if a field is preformulated (has formula attribute) + This helper returns True only if the field has a 'formula' key with a non-empty value + """ + return bool(field.get('formula', False)) diff --git a/ckanext/scheming/suggestion_test_schema.yaml b/ckanext/scheming/suggestion_test_schema.yaml new file mode 100644 index 00000000..ba8a53df --- /dev/null +++ b/ckanext/scheming/suggestion_test_schema.yaml @@ -0,0 +1,110 @@ +scheming_version: 2 +dataset_type: dataset +about: A reimplementation of the default CKAN dataset schema +about_url: http://github.com/ckan/ckanext-scheming + + +dataset_fields: + +- field_name: title + label: Title + preset: title + form_placeholder: eg. A descriptive title + +- field_name: name + label: URL + preset: dataset_slug + form_placeholder: eg. my-dataset + +- field_name: notes + label: Description + form_snippet: markdown.html + form_placeholder: eg. Some useful notes/blurb about the data + suggestion_formula: Latitudinal range {{resource.LATITUDE.stats.max|float - resource.LATITUDE.stats.min|float }} {{"the quick brown fox"|truncate_with_ellipsis(5)}} + +- field_name: spatial_extent + label: Spatial Extent + form_snippet: markdown.html + suggestion_formula: '{{ spatial_extent_wkt(resource.LONGITUDE.stats.min, resource.LATITUDE.stats.min, resource.LONGITUDE.stats.max, resource.LATITUDE.stats.max) }}' + +- field_name: test_field + label: Test Field + formula: 'Test formula field value:{{package.author}}: {{package.author_email}}' + +- field_name: tag_string + label: Tags + preset: tag_string_autocomplete + form_placeholder: eg. economy, mental health, government + +- field_name: license_id + label: License + form_snippet: license.html + help_text: License definitions and additional information can be found at http://opendefinition.org/ + +- field_name: owner_org + label: Organization + preset: dataset_organization + +- field_name: url + label: Source + form_placeholder: http://example.com/dataset.json + display_property: foaf:homepage + display_snippet: link.html + +- field_name: version + label: Version + validators: ignore_missing unicode_safe package_version_validator + form_placeholder: '1.0' + +- field_name: author + label: Author + form_placeholder: Joe Bloggs + display_property: dc:creator + +- field_name: author_email + label: Author Email + form_placeholder: joe@example.com + display_property: dc:creator + display_snippet: email.html + display_email_name_field: author + +- field_name: maintainer + label: Maintainer + form_placeholder: Joe Bloggs + display_property: dc:contributor + +- field_name: maintainer_email + label: Maintainer Email + form_placeholder: joe@example.com + display_property: dc:contributor + display_snippet: email.html + display_email_name_field: maintainer + +- field_name: dpp_suggestions + label: DPP Suggestion + preset: json_object + +resource_fields: + +- field_name: url + label: URL + preset: resource_url_upload + +- field_name: name + label: Name + form_placeholder: eg. January 2011 Gold Prices + +- field_name: description + label: Description + form_snippet: markdown.html + form_placeholder: Some useful notes about the data + +- field_name: spatial_extent + label: Spatial Extent + form_snippet: markdown.html + suggestion_formula: POLYGON(({{resource.LATITUDE.stats.min|float}}, {{resource.LONGITUDE.stats.min|float}}, {{resource.LATITUDE.stats.max|float}}, {{resource.LONGITUDE.stats.min|float}}, {{resource.LATITUDE.stats.max|float}}, {{resource.LONGITUDE.stats.max|float}}, {{resource.LATITUDE.stats.min|float}}, {{resource.LONGITUDE.stats.max|float}}, {{resource.LATITUDE.stats.min|float}}, {{resource.LONGITUDE.stats.min|float}})) + + +- field_name: format + label: Format + preset: resource_format_autocomplete \ No newline at end of file diff --git a/ckanext/scheming/templates/scheming/form_snippets/markdown.html b/ckanext/scheming/templates/scheming/form_snippets/markdown.html index 798ae96e..a20c7790 100644 --- a/ckanext/scheming/templates/scheming/form_snippets/markdown.html +++ b/ckanext/scheming/templates/scheming/form_snippets/markdown.html @@ -1,9 +1,30 @@ {% import 'macros/form.html' as form %} +{% include 'scheming/snippets/suggestions_asset.html' %} + +{# Check if this is a preformulated field #} +{% set is_preformulated = h.is_preformulated_field(field) %} + +{% set suggestion = h.scheming_field_suggestion(field) %} +{% set label_text = h.scheming_language_text(field.label) %} +{% set label_with_suggestion %} + {{ label_text }}: + {%- if suggestion -%} + {%- snippet 'scheming/snippets/suggestion_button.html', field=field, data=data -%} + {%- endif -%} + {%- if is_preformulated -%} + + + + + + {%- endif -%} +{% endset %} + {% call form.markdown( field.field_name, id='field-' + field.field_name, - label=h.scheming_language_text(field.label), + label=label_with_suggestion|safe, placeholder=h.scheming_language_text(field.form_placeholder), value=data[field.field_name], error=errors[field.field_name], @@ -11,5 +32,12 @@ is_required=h.scheming_field_required(field) ) %} - {%- snippet 'scheming/form_snippets/help_text.html', field=field -%} -{% endcall %} +
+ {%- if is_preformulated -%} +
+ Automated field: This field is automatically populated and cannot be edited manually. +
+ {%- endif -%} + {%- snippet 'scheming/form_snippets/help_text.html', field=field -%} +
+{% endcall %} \ No newline at end of file diff --git a/ckanext/scheming/templates/scheming/form_snippets/select.html b/ckanext/scheming/templates/scheming/form_snippets/select.html index 619e8fec..cae25aea 100644 --- a/ckanext/scheming/templates/scheming/form_snippets/select.html +++ b/ckanext/scheming/templates/scheming/form_snippets/select.html @@ -1,4 +1,5 @@ {% import 'macros/form.html' as form %} +{% include 'scheming/snippets/suggestions_asset.html' %} {%- set options=[] -%} {%- set form_restrict_choices_to=field.get('form_restrict_choices_to') -%} @@ -16,18 +17,25 @@ {%- if field.get('sorted_choices') -%} {%- set options = options|sort(case_sensitive=false, attribute='text') -%} {%- endif -%} -{%- if data[field.field_name] is defined -%} +{%- if data[field.field_name] -%} {%- set option_selected = data[field.field_name]|string -%} -{%- elif field.default is defined -%} - {%- set option_selected = field.default|string -%} {%- else -%} {%- set option_selected = None -%} {%- endif -%} +{% set suggestion = h.scheming_field_suggestion(field) %} +{% set label_text = h.scheming_language_text(field.label) %} +{% set label_with_suggestion %} + {{ label_text }}: + {%- if suggestion -%} + {%- snippet 'scheming/snippets/suggestion_button.html', field=field, data=data -%} + {%- endif -%} +{% endset %} + {% call form.select( field.field_name, id='field-' + field.field_name, - label=h.scheming_language_text(field.label), + label=label_with_suggestion|safe, options=options, selected=option_selected, error=errors[field.field_name], @@ -36,5 +44,7 @@ is_required=h.scheming_field_required(field) ) %} - {%- snippet 'scheming/form_snippets/help_text.html', field=field -%} -{% endcall %} +
+ {%- snippet 'scheming/form_snippets/help_text.html', field=field -%} +
+{% endcall %} \ No newline at end of file diff --git a/ckanext/scheming/templates/scheming/form_snippets/text.html b/ckanext/scheming/templates/scheming/form_snippets/text.html index 9d276da0..faff95e5 100644 --- a/ckanext/scheming/templates/scheming/form_snippets/text.html +++ b/ckanext/scheming/templates/scheming/form_snippets/text.html @@ -1,9 +1,28 @@ {% import 'macros/form.html' as form %} +{% include 'scheming/snippets/suggestions_asset.html' %} + +{% set is_preformulated = h.is_preformulated_field(field) %} + +{% set suggestion = h.scheming_field_suggestion(field) %} +{% set label_text = h.scheming_language_text(field.label) %} +{% set label_with_suggestion %} + {{ label_text }} + {%- if suggestion -%} + {%- snippet 'scheming/snippets/suggestion_button.html', field=field, data=data -%} + {%- endif -%} + {%- if is_preformulated -%} + + + + + + {%- endif -%} +{% endset %} {% call form.input( field.field_name, id='field-' + field.field_name, - label=h.scheming_language_text(field.label), + label=label_with_suggestion|safe, placeholder=h.scheming_language_text(field.form_placeholder), value=data[field.field_name], error=errors[field.field_name], @@ -12,5 +31,12 @@ is_required=h.scheming_field_required(field) ) %} - {%- snippet 'scheming/form_snippets/help_text.html', field=field -%} -{% endcall %} +
+ {%- if is_preformulated -%} +
+ Automated field: This field is automatically populated and cannot be edited manually. +
+ {%- endif -%} + {%- snippet 'scheming/form_snippets/help_text.html', field=field -%} +
+{% endcall %} \ No newline at end of file diff --git a/ckanext/scheming/templates/scheming/snippets/suggestion_button.html b/ckanext/scheming/templates/scheming/snippets/suggestion_button.html new file mode 100644 index 00000000..0adc57cc --- /dev/null +++ b/ckanext/scheming/templates/scheming/snippets/suggestion_button.html @@ -0,0 +1,60 @@ +{% set suggestion = h.scheming_field_suggestion(field) %} +{% if suggestion %} + {% set suggestion_value = h.scheming_get_suggestion_value(field.field_name, data) %} + {% set field_id = 'field-' + field.field_name %} + {% set popup_id = 'suggestion-popup-' + field.field_name %} + + {# Check if this is a select field and if the suggestion is valid #} + {% set is_select = field.choices is defined or field.choices_helper is defined %} + {% set is_valid_suggestion = h.scheming_is_valid_suggestion(field, suggestion_value) %} + + + "> + + + + + + +{% endif %} \ No newline at end of file diff --git a/ckanext/scheming/templates/scheming/snippets/suggestions_asset.html b/ckanext/scheming/templates/scheming/snippets/suggestions_asset.html new file mode 100644 index 00000000..6254967b --- /dev/null +++ b/ckanext/scheming/templates/scheming/snippets/suggestions_asset.html @@ -0,0 +1 @@ +{% asset 'ckanext-scheming/suggestions' %} \ No newline at end of file