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 %}
+
+{% 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 %}
+
+{% 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) %}
+
+
+ Apply suggestion
+
+ ">
+
+
+
+
+
+
+{% 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