diff --git a/ckanext/odsh/fanstatic/odsh_guessformat.js b/ckanext/odsh/fanstatic/odsh_guessformat.js new file mode 100644 index 0000000000000000000000000000000000000000..3efcbff85782b72b22f5dadc325a364ef6022ecf --- /dev/null +++ b/ckanext/odsh/fanstatic/odsh_guessformat.js @@ -0,0 +1,36 @@ + +ckan.module('odsh_guessformat', function ($) +{ + let known_formats = ['pdf', 'rdf', 'txt', 'doc', 'csv'] + + + let c = $('#field-format') + let onChange = function (filename) + { + let ext = filename.slice((filename.lastIndexOf(".") - 1 >>> 0) + 2).toLowerCase(); + if (ext !== undefined && known_formats.indexOf(ext) > -1) + { + c.val(ext.toUpperCase()) + } + } + let onRemoved = function () + { + c.val('') + } + + return { + initialize: function () + { + if (this.options.formats) + known_formats = this.options.formats + + this.sandbox.subscribe('odsh_upload_filename_changed', onChange); + this.sandbox.subscribe('odsh_upload_filename_removed', onRemoved); + }, + teardown: function () + { + this.sandbox.unsubscribe('odsh_upload_filename_changed', onChange); + this.sandbox.unsubscribe('odsh_upload_filename_removed', onRemoved); + }, + }; +}); \ No newline at end of file diff --git a/ckanext/odsh/fanstatic/odsh_image-upload.js b/ckanext/odsh/fanstatic/odsh_image-upload.js new file mode 100644 index 0000000000000000000000000000000000000000..2b63a4250d4087717a85e9299b847c60e09dfaff --- /dev/null +++ b/ckanext/odsh/fanstatic/odsh_image-upload.js @@ -0,0 +1,283 @@ + /* Image Upload + * + */ +this.ckan.module('odsh_image-upload', function($) { + return { + /* options object can be extended using data-module-* attributes */ + options: { + is_url: false, + is_upload: false, + field_upload: 'image_upload', + field_url: 'image_url', + field_clear: 'clear_upload', + field_name: 'name', + upload_label: '' + }, + + /* Should be changed to true if user modifies resource's name + * + * @type {Boolean} + */ + _nameIsDirty: false, + + /* Initialises the module setting up elements and event listeners. + * + * Returns nothing. + */ + initialize: function () { + $.proxyAll(this, /_on/); + var options = this.options; + + // firstly setup the fields + var field_upload = 'input[name="' + options.field_upload + '"]'; + var field_url = 'input[name="' + options.field_url + '"]'; + var field_clear = 'input[name="' + options.field_clear + '"]'; + var field_name = 'input[name="' + options.field_name + '"]'; + + this.input = $(field_upload, this.el); + this.field_url = $(field_url, this.el).parents('.control-group'); + this.field_image = this.input.parents('.control-group'); + this.field_url_input = $('input', this.field_url); + this.field_name = this.el.parents('form').find(field_name); + // this is the location for the upload/link data/image label + this.label_location = $('label[for="field-image-url"]'); + // determines if the resource is a data resource + this.is_data_resource = (this.options.field_url === 'url') && (this.options.field_upload === 'upload'); + + // Is there a clear checkbox on the form already? + var checkbox = $(field_clear, this.el); + if (checkbox.length > 0) { + checkbox.parents('.control-group').remove(); + } + + // Adds the hidden clear input to the form + this.field_clear = $('<input type="hidden" name="' + options.field_clear +'">') + .appendTo(this.el); + + // Button to set the field to be a URL + this.button_url = $('<a href="javascript:;" class="btn">' + + '<i class="fa fa-globe"></i>' + + this._('Link') + '</a>') + .prop('title', this._('Link to a URL on the internet (you can also link to an API)')) + .on('click', this._onFromWeb) + .insertAfter(this.input); + + // Button to attach local file to the form + this.button_upload = $('<a href="javascript:;" class="btn">' + + '<i class="fa fa-cloud-upload"></i>' + + this._('Upload') + '</a>') + .insertAfter(this.input); + + // Button for resetting the form when there is a URL set + var removeText = this._('Remove'); + $('<a href="javascript:;" class="btn btn-danger btn-remove-url">' + + removeText + '</a>') + .prop('title', removeText) + .on('click', this._onRemove) + .insertBefore(this.field_url_input); + + // Update the main label (this is displayed when no data/image has been uploaded/linked) + $('label[for="field-image-upload"]').text(options.upload_label || this._('Image')); + + // Setup the file input + this.input + .on('mouseover', this._onInputMouseOver) + .on('mouseout', this._onInputMouseOut) + .on('change', this._onInputChange) + .prop('title', this._('Upload a file on your computer')) + .css('width', this.button_upload.outerWidth()); + + // Fields storage. Used in this.changeState + this.fields = $('<i />') + .add(this.button_upload) + .add(this.button_url) + .add(this.input) + .add(this.field_url) + .add(this.field_image); + + // Disables autoName if user modifies name field + this.field_name + .on('change', this._onModifyName); + // Disables autoName if resource name already has value, + // i.e. we on edit page + if (this.field_name.val()){ + this._nameIsDirty = true; + } + + if (options.is_url) { + this._showOnlyFieldUrl(); + + this._updateUrlLabel(this._('URL')); + } else if (options.is_upload) { + this._showOnlyFieldUrl(); + + this.field_url_input.prop('readonly', true); + // If the data is an uploaded file, the filename will display rather than whole url of the site + var filename = this._fileNameFromUpload(this.field_url_input.val()); + this.field_url_input.val(filename); + + this._updateUrlLabel(this._('File')); + } else { + this._showOnlyButtons(); + } + }, + + /* Quick way of getting just the filename from the uri of the resource data + * + * url - The url of the uploaded data file + * + * Returns String. + */ + _fileNameFromUpload: function(url) { + // remove fragment (#) + url = url.substring(0, (url.indexOf("#") === -1) ? url.length : url.indexOf("#")); + // remove query string + url = url.substring(0, (url.indexOf("?") === -1) ? url.length : url.indexOf("?")); + // extract the filename + url = url.substring(url.lastIndexOf("/") + 1, url.length); + + return url; // filename + }, + + /* Update the `this.label_location` text + * + * If the upload/link is for a data resource, rather than an image, + * the text for label[for="field-image-url"] will be updated. + * + * label_text - The text for the label of an uploaded/linked resource + * + * Returns nothing. + */ + _updateUrlLabel: function(label_text) { + if (! this.is_data_resource) { + return; + } + + this.label_location.text(label_text); + + }, + + /* Event listener for when someone sets the field to URL mode + * + * Returns nothing. + */ + _onFromWeb: function() { + this._showOnlyFieldUrl(); + + this.field_url_input.focus() + .on('blur', this._onFromWebBlur); + + if (this.options.is_upload) { + this.field_clear.val('true'); + } + + this._updateUrlLabel(this._('URL')); + }, + + /* Event listener for resetting the field back to the blank state + * + * Returns nothing. + */ + _onRemove: function() { + this._showOnlyButtons(); + + this.field_url_input.val(''); + this.field_url_input.prop('readonly', false); + + this.field_clear.val('true'); + + this.sandbox.publish('odsh_upload_filename_removed'); + }, + + /* Event listener for when someone chooses a file to upload + * + * Returns nothing. + */ + _onInputChange: function() { + var file_name = this.input.val().split(/^C:\\fakepath\\/).pop(); + this.field_url_input.val(file_name); + this.field_url_input.prop('readonly', true); + + this.field_clear.val(''); + + this._showOnlyFieldUrl(); + + this._autoName(file_name); + + this._updateUrlLabel(this._('File')); + + this.sandbox.publish('odsh_upload_filename_changed', file_name); + }, + + /* Show only the buttons, hiding all others + * + * Returns nothing. + */ + _showOnlyButtons: function() { + this.fields.hide(); + this.button_upload + .add(this.field_image) + .add(this.button_url) + .add(this.input) + .show(); + }, + + /* Show only the URL field, hiding all others + * + * Returns nothing. + */ + _showOnlyFieldUrl: function() { + this.fields.hide(); + this.field_url.show(); + }, + + /* Event listener for when a user mouseovers the hidden file input + * + * Returns nothing. + */ + _onInputMouseOver: function() { + this.button_upload.addClass('hover'); + }, + + /* Event listener for when a user mouseouts the hidden file input + * + * Returns nothing. + */ + _onInputMouseOut: function() { + this.button_upload.removeClass('hover'); + }, + + /* Event listener for changes in resource's name by direct input from user + * + * Returns nothing + */ + _onModifyName: function() { + this._nameIsDirty = true; + }, + + /* Event listener for when someone loses focus of URL field + * + * Returns nothing + */ + _onFromWebBlur: function() { + var url = this.field_url_input.val().match(/([^\/]+)\/?$/) + if (url) { + this._autoName(url.pop()); + } + let file_name = this.field_url_input.val(); + console.log(file_name) + this.sandbox.publish('odsh_upload_filename_changed', file_name); + }, + + /* Automatically add file name into field Name + * + * Select by attribute [name] to be on the safe side and allow to change field id + * Returns nothing + */ + _autoName: function(name) { + if (!this._nameIsDirty){ + this.field_name.val(name); + } + } + }; +}); diff --git a/ckanext/odsh/helpers.py b/ckanext/odsh/helpers.py index c40980224a522b36388162287e9560000099357e..e467ecc67ec7437596d973ab416f7fb837c65afe 100644 --- a/ckanext/odsh/helpers.py +++ b/ckanext/odsh/helpers.py @@ -8,6 +8,7 @@ import json from ckan.common import c import datetime from dateutil import parser +from ckan.common import config get_action = logic.get_action log = logging.getLogger(__name__) @@ -97,4 +98,9 @@ def odsh_get_spatial_text(pkg_dict): def odsh_render_datetime(datetime_, date_format='%d.%m.%Y'): dt = parser.parse(datetime_) - return dt.strftime(date_format) \ No newline at end of file + return dt.strftime(date_format) + +def odsh_upload_known_formats(): + value = config.get('ckanext.odsh.upload_formats', []) + value = toolkit.aslist(value) + return value \ No newline at end of file diff --git a/ckanext/odsh/plugin.py b/ckanext/odsh/plugin.py index 4dcff1703b219f6a9bc6e910e2a5bf5bc0b8f4e3..93aff5c8d2ce526a2c214584482f417f3c2a154c 100644 --- a/ckanext/odsh/plugin.py +++ b/ckanext/odsh/plugin.py @@ -167,7 +167,8 @@ class OdshPlugin(plugins.SingletonPlugin, DefaultTranslation, DefaultDatasetForm 'odsh_get_resource_views': odsh_helpers.odsh_get_resource_views, 'odsh_get_bounding_box': odsh_helpers.odsh_get_bounding_box, 'odsh_get_spatial_text': odsh_helpers.odsh_get_spatial_text, - 'odsh_render_datetime': odsh_helpers.odsh_render_datetime + 'odsh_render_datetime': odsh_helpers.odsh_render_datetime, + 'odsh_upload_known_formats': odsh_helpers.odsh_upload_known_formats } def before_map(self, map): diff --git a/ckanext/odsh/public/odsh.css b/ckanext/odsh/public/odsh.css index 17a7c1ba404c833abbd2dd431db9964970892e5e..c9a2418c526512f6993953016088150a255ea043 100644 --- a/ckanext/odsh/public/odsh.css +++ b/ckanext/odsh/public/odsh.css @@ -607,6 +607,9 @@ label:after { background-color: rgb(212,0,75); color:white; } +.js .image-upload #field-image-url { + padding-right: 0px; +} .btn { border-bottom-color: none; @@ -1237,4 +1240,4 @@ display: none; /* text-align:center; */ margin-top: 40px; margin-left: 400px; -} \ No newline at end of file +} diff --git a/ckanext/odsh/templates/ajax_snippets/fileformat.html b/ckanext/odsh/templates/ajax_snippets/fileformat.html new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/ckanext/odsh/templates/macros/form.html b/ckanext/odsh/templates/macros/form.html index 2a847d9de6302115edfec0fe2e80cda288c31348..5e8607d825eb05221c950d27d68848f7975314a9 100644 --- a/ckanext/odsh/templates/macros/form.html +++ b/ckanext/odsh/templates/macros/form.html @@ -473,7 +473,7 @@ options - A list/tuple of fields to be used as <options>. {% set upload_label = upload_label or _('Image') %} {% if is_upload_enabled %} - <div class="image-upload" data-module="image-upload" data-module-is_url="{{ 'true' if is_url else 'false' }}" + <div class="image-upload" data-module="odsh_image-upload" data-module-is_url="{{ 'true' if is_url else 'false' }}" data-module-is_upload="{{ 'true' if is_upload else 'false' }}" data-module-field_url="{{ field_url }}" data-module-field_upload="{{ field_upload }}" data-module-field_clear="{{ field_clear }}" data-module-upload_label="{{ upload_label }}" data-module-field_name="{{ field_name }}"> diff --git a/ckanext/odsh/templates/package/snippets/resource_form.html b/ckanext/odsh/templates/package/snippets/resource_form.html index 7c7a963fc11c7598adbd045290e7fe74297cb292..417553c637e3e490b5214a1aeb44d63ddac82529 100644 --- a/ckanext/odsh/templates/package/snippets/resource_form.html +++ b/ckanext/odsh/templates/package/snippets/resource_form.html @@ -1,3 +1,5 @@ + +{% resource 'odsh/odsh_image-upload.js' %} {% import 'macros/form.html' as form %} {% set data = data or {} %} @@ -64,6 +66,20 @@ {{ form.input('mimetype_inner', id='field-mimetype-inner', label=_('MIME Type'), placeholder=_('eg. application/json'), value=data.mimetype_inner, error=errors.mimetype_inner, classes=[]) }} {% endif %} + + + {% block basic_fields_format %} + {% resource 'odsh/odsh_guessformat.js' %} + + {% set format_attrs = {'data-module': 'odsh_guessformat', 'data-module-formats':h.odsh_upload_known_formats()} %} + + {% call form.input('format', id='field-format', label=_('Format'), placeholder=_('eg. CSV, XML or JSON'), value=data.format, error=errors.format, classes=['control-medium'],attrs=format_attrs) %} + <span class="info-block info-block-small"> + <i class="fa fa-info-circle"></i> + {{ _('This will be guessed automatically. Leave blank if you wish') }} + </span> + {% endcall %} + {% endblock %} {% endblock %} <div class='row-fluid'>