From 4030d37b87fb11099db35234a1b1ba7e1547f997 Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Fri, 30 Mar 2012 14:51:46 +0100 Subject: [PATCH 01/13] Fixing uploader in IE7; refactoring ckanjs out of the equation. --- ckan/public/css/style.css | 18 +- ckan/public/scripts/application.js | 285 +++++++++++++------ ckan/public/scripts/templates.js | 52 ---- ckan/templates/js_strings.html | 5 - ckan/templates/package/new_package_form.html | 38 ++- 5 files changed, 236 insertions(+), 162 deletions(-) diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index 8e7e7e77c3d..9bbbd99b7d1 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -900,13 +900,6 @@ span.extras-label { display: block; } -.dataset-editresources-form .resource-add li h4 { - display: inline; - padding-right: 20px; -} -.dataset-editresources-form .resource-add .subpane { - margin-top: 10px; -} .dataset-editresources-form .resource-add .fileinfo { margin: 7px 0; } @@ -920,7 +913,18 @@ span.extras-label { width: 400px; height: 70px; } +.resource-add > .nav { + margin-bottom: 0; + width: 522px; +} +.resource-add .tab-pane { + padding: 20px; + background: #fff; + border: 1px solid #DDD; + border-top: none; + width: 480px; +} /* ==================== */ /* = Add Dataset Page = */ diff --git a/ckan/public/scripts/application.js b/ckan/public/scripts/application.js index 160b59e4ad8..877a5232495 100644 --- a/ckan/public/scripts/application.js +++ b/ckan/public/scripts/application.js @@ -269,12 +269,23 @@ CKAN.View.ResourceEditor = Backbone.View.extend({ // Trigger the Add Resource pane this.el.find('.js-resource-add').click(this.openAddPanel); - // Tabbed view for adding resources - var $resourceAdd = this.el.find('.resource-add'); - this.addView=new CKAN.View.ResourceAddTabs({ - collection: this.collection, - el: $resourceAdd + // Tabs for adding resources + new CKAN.View.ResourceAddUrl({ + collection: self.collection, + el: this.el.find('.js-add-url-form'), + mode: 'file' }); + new CKAN.View.ResourceAddUrl({ + collection: self.collection, + el: this.el.find('.js-add-api-form'), + mode: 'api' + }); + new CKAN.View.ResourceAddUpload({ + collection: self.collection + el: this.el.find('.js-add-upload-form'), + client: CKAN.UI.workspace.client, + }); + // Close details button this.el.find('.resource-panel-close').click(this.closePanel); @@ -319,9 +330,9 @@ CKAN.View.ResourceEditor = Backbone.View.extend({ openAddPanel: function(e) { if (e) { e.preventDefault(); } var panel = this.el.find('.resource-panel'); - var addLi = this.el.find('.resource-list-add li'); - this.el.find('.resource-list li').removeClass('active'); - this.el.find('.resource-details').hide(); + var addLi = this.el.find('.resource-list-add > li'); + this.el.find('.resource-list > li').removeClass('active'); + $('.resource-details').hide(); this.el.find('.resource-details.resource-add').show(); addLi.addClass('active'); panel.show(); @@ -332,7 +343,7 @@ CKAN.View.ResourceEditor = Backbone.View.extend({ */ closePanel: function(e) { if (e) { e.preventDefault(); } - this.el.find('.resource-list li').removeClass('active'); + this.el.find('.resource-list > li').removeClass('active'); this.el.find('.resource-panel').hide(); }, /* @@ -536,7 +547,7 @@ CKAN.View.Resource = Backbone.View.extend({ // Close all tables var panel = this.table.parents('.resource-panel'); panel.find('.resource-details').hide(); - this.li.parents('fieldset#resources').find('li').removeClass('active'); + this.li.parents('fieldset#resources').find('.resource-list > li').removeClass('active'); panel.show(); this.table.show(); this.table.find('.js-resource-edit-name').focus(); @@ -652,120 +663,198 @@ CKAN.View.Resource = Backbone.View.extend({ } }); -/* ===================================== */ -/* == Backbone View: ResourceAdd Tabs == */ -/* ===================================== */ -CKAN.View.ResourceAddTabs = Backbone.View.extend({ - initialize: function() { - _.bindAll(this, 'render', 'addNewResource', 'reset'); - }, - render: function() { +/* ================================================ */ +/* == Backbone view: Add resource by file upload == */ +/* ================================================ */ +CKAN.View.ResourceAddUpload = Backbone.View.extend({ + tagName: 'div', + + // expects a client arguments in its options + initialize: function(options) { + this.client = options.client; + _.bindAll(this, 'updateFormData', 'setMessage', 'uploadFile'); + this.$messages = this.el.find('.alert'); + this.$uploader = this.el.find('#fileupload'); + this.setupFileUpload(); }, events: { - 'click button': 'clickButton', - 'click input[name=reset]': 'reset' + 'click button': 'uploadFile' }, - reset: function() { - this.el.find('button').removeClass('depressed'); - if (this.subView) { - this.subView.remove(); - this.subView = null; - } - return false; + setupFileUpload: function() { + var self = this; + this.$uploader.fileupload({ + // needed because we are posting to remote url + forceIframeTransport: true, + replaceFileInput: false, + autoUpload: false, + fail: function(e, data) { + alert('Upload failed'); + }, + add: function(e,data) { + self.fileData = data; + self.fileUploadData = data; + self.key = self.makeUploadKey(data.files[0].name); + self.updateFormData(self.key); + }, + send: function(e, data) { + self.setMessage('Uploading file ... '); + }, + done: function(e, data) { + self.onUploadComplete(self.key); + } + }); }, - clickButton: function(e) { - e.preventDefault(); - var $target = $(e.target); + ISODateString: function(d) { + function pad(n) {return n<10 ? '0'+n : n}; + return d.getUTCFullYear()+'-' + + pad(d.getUTCMonth()+1)+'-' + + pad(d.getUTCDate())+'T' + + pad(d.getUTCHours())+':' + + pad(d.getUTCMinutes())+':' + + pad(d.getUTCSeconds()); + }, - if ($target.is('.depressed')) { - this.reset(); - } - else { - this.reset(); - $target.addClass('depressed'); - - var $subPane = $('
').addClass('subpane'); - this.el.append($subPane); - - var tempResource = new CKAN.Model.Resource({}); - - tempResource.bind('change', this.addNewResource); - // Open sub-pane - if ($target.is('.js-upload-file')) { - this.subView = new CKAN.View.ResourceUpload({ - el: $subPane, - model: tempResource, - // TODO: horrible reverse depedency ... - client: CKAN.UI.workspace.client - }); - } - else if ($target.is('.js-link-file') || $target.is('.js-link-api')) { - this.subView = new CKAN.View.ResourceAddLink({ - el: $subPane, - model: tempResource, - mode: ($target.is('.js-link-file'))? 'file' : 'api', - // TODO: horrible reverse depedency ... - client: CKAN.UI.workspace.client + // Create an upload key/label for this file. + // + // Form: {current-date}/file-name. Do not just use the file name as this + // would lead to collisions. + // (Could add userid/username and/or a small random string to reduce + // collisions but chances seem very low already) + makeUploadKey: function(fileName) { + // google storage replaces ' ' with '+' which breaks things + // See http://trac.ckan.org/ticket/1518 for more. + var corrected = fileName.replace(/ /g, '-'); + // note that we put hh mm ss as hhmmss rather hh:mm:ss (former is 'basic + // format') + var now = new Date(); + // replace ':' with nothing + var str = this.ISODateString(now).replace(':', '').replace(':', ''); + return str + '/' + corrected; + }, + + updateFormData: function(key) { + var self = this; + self.setMessage('Checking upload permissions ... '); + self.el.find('.fileinfo').text(key); + self.client.getStorageAuthForm(key, { + async: false, + success: function(data) { + self.$uploader.attr('data-url', data.action); + _tmpl = ''; + var $hidden = $(self.el.find('div.hidden-inputs')[0]); + $.each(data.fields, function(idx, item) { + $hidden.append($.tmpl(_tmpl, item)); }); + self.hideMessage(); + }, + error: function(jqXHR, textStatus, errorThrown) { + // TODO: more graceful error handling (e.g. of 409) + self.setMessage('Failed to get credentials for storage upload. Upload cannot proceed', 'error'); + self.el.find('button#upload').hide(); } - this.subView.render(); + }); + }, + + uploadFile: function(e) { + e.preventDefault(); + if (!this.fileData) { + alert('No file selected'); + return; } + var jqXHR = this.$uploader.fileupload('send', {files: this.fileData.files, url: this.$uploader.attr('data-url')}); + }, + + onUploadComplete: function(key) { + var self = this; + + self.client.apiCall({ + offset: '/storage/metadata/' + self.key, + success: function(data) { + var name = data._label; + if (name && name.length > 0 && name[0] === '/') { + name = name.slice(1); + } + var d = new Date(data._last_modified); + var lastmod = self.ISODateString(d); + + var newResource = new CKAN.Model.Resource({}); + newResource.set({ + url: data._location + , name: name + , size: data._content_length + , last_modified: lastmod + , format: data._format + , mimetype: data._format + , resource_type: 'file.upload' + , owner: data['uploaded-by'] + , hash: data._checksum + , cache_url: data._location + , cache_url_updated: lastmod + } + , { + error: function(model, error) { + var msg = 'Filed uploaded OK but error adding resource: ' + error + '.'; + msg += 'You may need to create a resource directly. Uploaded file at: ' + data._location; + CKAN.View.flash(msg, 'error'); + } + } + ); + self.collection.add(newResource); + self.setMessage('File uploaded OK and resource added', 'success'); + CKAN.View.flash('File uploaded OK and resource added'); + } + }); }, - addNewResource: function(tempResource) { - // Deep-copy the tempResource we had bound to - var resource=new CKAN.Model.Resource(tempResource.toJSON()); + setMessage: function(msg, category) { + var category = category || 'alert-info'; + this.$messages.removeClass('alert-info alert-success alert-error'); + this.$messages.addClass(category); + this.$messages.show(); + this.$messages.html(msg); + }, - this.collection.add(resource); - this.reset(); + hideMessage: function() { + this.$messages.hide('slow'); } }); -/* ================================================= */ -/* == Backbone View: ResourceAdd Link-To-Resource == */ -/* ================================================= */ -CKAN.View.ResourceAddLink = Backbone.View.extend({ +/* ======================================== */ +/* == Backbone View: Add resource by URL == */ +/* ======================================== */ +CKAN.View.ResourceAddUrl = Backbone.View.extend({ initialize: function(options) { - _.bindAll(this, 'render'); - this.mode = options.mode; + _.bindAll(this, 'clickSubmit'); }, - render: function() { - var tmpl = null; - if (this.mode=='file') { - tmpl = $.tmpl(CKAN.Templates.resourceAddLinkFile); - } - else if (this.mode=='api') { - tmpl = $.tmpl(CKAN.Templates.resourceAddLinkApi); - } - $(this.el).html(tmpl); - return this; - }, + clickSubmit: function(e) { + e.preventDefault(); - setResourceInfo: function(e) { - e.preventDefault(); + var self = this; + var newResource = new CKAN.Model.Resource({}); this.el.find('input[name=save]').addClass("disabled"); - this.el.find('input[name=reset]').addClass("disabled"); - var urlVal=this.el.find('input[name=url]').val(); + var urlVal = this.el.find('input[name=url]').val(); var qaEnabled = $.inArray('qa',CKAN.plugins)>=0; - if(qaEnabled && this.mode=='file') { + if(qaEnabled && this.options.mode=='file') { $.ajax({ url: CKAN.SITE_URL + '/qa/link_checker', - context: this.model, + context: newResource, data: {url: urlVal}, dataType: 'json', error: function(){ - this.set({url: urlVal, resource_type: 'file'}); + newResource.set({url: urlVal, resource_type: 'file'}); + self.collection.add(newResource); + self.resetForm(); }, success: function(data){ data = data[0]; - this.set({ + newResource.set({ url: urlVal, resource_type: 'file', format: data.format, @@ -774,15 +863,25 @@ CKAN.View.ResourceAddLink = Backbone.View.extend({ last_modified: data.last_modified, url_error: (data.url_errors || [""])[0] }); + self.collection.add(newResource); + self.resetForm(); } }); - } else { - this.model.set({url: urlVal, resource_type: this.mode}); + } + else { + newResource.set({url: urlVal, resource_type: this.options.mode}); + this.collection.add(newResource); + this.resetForm(); } }, + resetForm: function() { + this.el.find('input[name=save]').removeClass("disabled"); + this.el.find('input[name=url]').val(''); + }, + events: { - 'submit form': 'setResourceInfo' + 'click .btn': 'clickSubmit' } }); diff --git a/ckan/public/scripts/templates.js b/ckan/public/scripts/templates.js index b1c376f12a3..2289643451f 100644 --- a/ckan/public/scripts/templates.js +++ b/ckan/public/scripts/templates.js @@ -1,55 +1,3 @@ - -CKAN.Templates.resourceAddLinkFile = ' \ -
\ - \ - \ - \ - \ -
\ -'; - -CKAN.Templates.resourceAddLinkApi = ' \ -
\ - \ - \ - \ - \ -
\ -'; - -CKAN.Templates.resourceUpload = ' \ -
\ -
\ - \ -
\ -
\ -
\ - \ -
\ -
\ - \ -
\ -
\ - \ - \ -
\ -
\ -
\ - \ -
\ -
\ -'; - - - CKAN.Templates.resourceEntry = ' \
  • \ \ diff --git a/ckan/templates/js_strings.html b/ckan/templates/js_strings.html index c693e96b2e2..50750b0a071 100644 --- a/ckan/templates/js_strings.html +++ b/ckan/templates/js_strings.html @@ -29,11 +29,6 @@ /* * Used in templates.js. */ - CKAN.Strings.fileUrl = "${_('File URL')}"; - CKAN.Strings.apiUrl = "${_('Api URL')}"; - CKAN.Strings.add = "${_('Add')}"; - CKAN.Strings.cancel = "${_('Cancel')}"; - CKAN.Strings.file = "${_('File')}"; CKAN.Strings.name = "${_('Name')}"; CKAN.Strings.description = "${_('Description')}"; CKAN.Strings.url = "${_('Url')}"; diff --git a/ckan/templates/package/new_package_form.html b/ckan/templates/package/new_package_form.html index 8b39047265d..d62c6bbbbc3 100644 --- a/ckan/templates/package/new_package_form.html +++ b/ckan/templates/package/new_package_form.html @@ -151,12 +151,40 @@

    Errors in form

    From 35b7f656b2f0308e1455cef6ab272aaf70b5c07b Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Fri, 30 Mar 2012 14:56:06 +0100 Subject: [PATCH 02/13] [#1788][xs]: Glitchfix. --- ckan/public/scripts/application.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ckan/public/scripts/application.js b/ckan/public/scripts/application.js index 877a5232495..4ef3e186e2a 100644 --- a/ckan/public/scripts/application.js +++ b/ckan/public/scripts/application.js @@ -281,9 +281,9 @@ CKAN.View.ResourceEditor = Backbone.View.extend({ mode: 'api' }); new CKAN.View.ResourceAddUpload({ - collection: self.collection + collection: self.collection, el: this.el.find('.js-add-upload-form'), - client: CKAN.UI.workspace.client, + client: CKAN.UI.workspace.client }); From 065b9e1a12d6729412fca1305039fa2fe21e0fda Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Fri, 30 Mar 2012 15:00:45 +0100 Subject: [PATCH 03/13] [#1788]: Avoid using HTML5 data- attributes to fix IE7. --- ckan/public/scripts/application.js | 5 +++-- ckan/templates/package/new_package_form.html | 5 +---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/ckan/public/scripts/application.js b/ckan/public/scripts/application.js index 4ef3e186e2a..c84c592ffba 100644 --- a/ckan/public/scripts/application.js +++ b/ckan/public/scripts/application.js @@ -677,6 +677,7 @@ CKAN.View.ResourceAddUpload = Backbone.View.extend({ this.$messages = this.el.find('.alert'); this.$uploader = this.el.find('#fileupload'); this.setupFileUpload(); + this.endpoint = "http://test-ckan-net-storage.commondatastorage.googleapis.com/"; }, events: { @@ -743,7 +744,7 @@ CKAN.View.ResourceAddUpload = Backbone.View.extend({ self.client.getStorageAuthForm(key, { async: false, success: function(data) { - self.$uploader.attr('data-url', data.action); + self.endpoint = data.action; _tmpl = ''; var $hidden = $(self.el.find('div.hidden-inputs')[0]); $.each(data.fields, function(idx, item) { @@ -765,7 +766,7 @@ CKAN.View.ResourceAddUpload = Backbone.View.extend({ alert('No file selected'); return; } - var jqXHR = this.$uploader.fileupload('send', {files: this.fileData.files, url: this.$uploader.attr('data-url')}); + var jqXHR = this.$uploader.fileupload('send', {files: this.fileData.files, url: this.endpoint}); }, onUploadComplete: function(key) { diff --git a/ckan/templates/package/new_package_form.html b/ckan/templates/package/new_package_form.html index d62c6bbbbc3..fc2ee5c1107 100644 --- a/ckan/templates/package/new_package_form.html +++ b/ckan/templates/package/new_package_form.html @@ -175,10 +175,7 @@

    Errors in form

    - +
    From 4b6f271a9e767961db7df6d5555b3544010af89d Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Fri, 30 Mar 2012 15:05:02 +0100 Subject: [PATCH 04/13] [#1788]: Tweaking & testing on staging server. --- ckan/public/scripts/application.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ckan/public/scripts/application.js b/ckan/public/scripts/application.js index c84c592ffba..9cb61a5f8f3 100644 --- a/ckan/public/scripts/application.js +++ b/ckan/public/scripts/application.js @@ -745,6 +745,7 @@ CKAN.View.ResourceAddUpload = Backbone.View.extend({ async: false, success: function(data) { self.endpoint = data.action; + alert('updateFormData :: ' + self.endpoint); _tmpl = ''; var $hidden = $(self.el.find('div.hidden-inputs')[0]); $.each(data.fields, function(idx, item) { @@ -766,6 +767,7 @@ CKAN.View.ResourceAddUpload = Backbone.View.extend({ alert('No file selected'); return; } + alert('uploading :: ' + this.endpoint); var jqXHR = this.$uploader.fileupload('send', {files: this.fileData.files, url: this.endpoint}); }, From cf6e3cd7d8f84984d76a54dcbbc39fd9fa19f750 Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Fri, 30 Mar 2012 16:11:17 +0100 Subject: [PATCH 05/13] [#1788]: Caught JS bug which was killing IE file uploads. --- ckan/public/scripts/application.js | 51 +++++++++++--------- ckan/public/scripts/templates.js | 17 +++++++ ckan/templates/js_strings.html | 2 + ckan/templates/package/new_package_form.html | 5 -- 4 files changed, 47 insertions(+), 28 deletions(-) diff --git a/ckan/public/scripts/application.js b/ckan/public/scripts/application.js index 9cb61a5f8f3..ab9832ed950 100644 --- a/ckan/public/scripts/application.js +++ b/ckan/public/scripts/application.js @@ -271,21 +271,21 @@ CKAN.View.ResourceEditor = Backbone.View.extend({ // Tabs for adding resources new CKAN.View.ResourceAddUrl({ - collection: self.collection, + collection: this.collection, el: this.el.find('.js-add-url-form'), mode: 'file' }); new CKAN.View.ResourceAddUrl({ - collection: self.collection, + collection: this.collection, el: this.el.find('.js-add-api-form'), mode: 'api' }); new CKAN.View.ResourceAddUpload({ - collection: self.collection, + collection: this.collection, el: this.el.find('.js-add-upload-form'), client: CKAN.UI.workspace.client }); - + // Close details button this.el.find('.resource-panel-close').click(this.closePanel); @@ -664,29 +664,38 @@ CKAN.View.Resource = Backbone.View.extend({ }); -/* ================================================ */ -/* == Backbone view: Add resource by file upload == */ -/* ================================================ */ +/* ============================================= */ +/* Backbone View: Add Resource by uploading file */ +/* ============================================= */ CKAN.View.ResourceAddUpload = Backbone.View.extend({ tagName: 'div', // expects a client arguments in its options initialize: function(options) { + this.el = $(this.el); this.client = options.client; - _.bindAll(this, 'updateFormData', 'setMessage', 'uploadFile'); - this.$messages = this.el.find('.alert'); - this.$uploader = this.el.find('#fileupload'); - this.setupFileUpload(); - this.endpoint = "http://test-ckan-net-storage.commondatastorage.googleapis.com/"; + _.bindAll(this, 'render', 'updateFormData', 'setMessage', 'uploadFile'); + this.render(); }, events: { - 'click button': 'uploadFile' + 'click input[type="submit"]': 'uploadFile' + }, + + render: function () { + this.el.empty(); + tmplData = { + } + var tmpl = $.tmpl(CKAN.Templates.resourceUpload, tmplData).appendTo(this.el); + //this.el.append($(tmpl[0])); + this.$messages = this.el.find('.alert'); + this.setupFileUpload(); + return this; }, setupFileUpload: function() { var self = this; - this.$uploader.fileupload({ + this.el.find('.fileupload').fileupload({ // needed because we are posting to remote url forceIframeTransport: true, replaceFileInput: false, @@ -706,7 +715,7 @@ CKAN.View.ResourceAddUpload = Backbone.View.extend({ done: function(e, data) { self.onUploadComplete(self.key); } - }); + }) }, ISODateString: function(d) { @@ -744,10 +753,9 @@ CKAN.View.ResourceAddUpload = Backbone.View.extend({ self.client.getStorageAuthForm(key, { async: false, success: function(data) { - self.endpoint = data.action; - alert('updateFormData :: ' + self.endpoint); + self.el.find('form').attr('action', data.action); _tmpl = ''; - var $hidden = $(self.el.find('div.hidden-inputs')[0]); + var $hidden = $(self.el.find('form div.hidden-inputs')[0]); $.each(data.fields, function(idx, item) { $hidden.append($.tmpl(_tmpl, item)); }); @@ -756,7 +764,7 @@ CKAN.View.ResourceAddUpload = Backbone.View.extend({ error: function(jqXHR, textStatus, errorThrown) { // TODO: more graceful error handling (e.g. of 409) self.setMessage('Failed to get credentials for storage upload. Upload cannot proceed', 'error'); - self.el.find('button#upload').hide(); + self.el.find('input[name="upload"]').hide(); } }); }, @@ -767,13 +775,11 @@ CKAN.View.ResourceAddUpload = Backbone.View.extend({ alert('No file selected'); return; } - alert('uploading :: ' + this.endpoint); - var jqXHR = this.$uploader.fileupload('send', {files: this.fileData.files, url: this.endpoint}); + var jqXHR = this.fileData.submit(); }, onUploadComplete: function(key) { var self = this; - self.client.apiCall({ offset: '/storage/metadata/' + self.key, success: function(data) { @@ -783,7 +789,6 @@ CKAN.View.ResourceAddUpload = Backbone.View.extend({ } var d = new Date(data._last_modified); var lastmod = self.ISODateString(d); - var newResource = new CKAN.Model.Resource({}); newResource.set({ url: data._location diff --git a/ckan/public/scripts/templates.js b/ckan/public/scripts/templates.js index 2289643451f..b56b537a6ad 100644 --- a/ckan/public/scripts/templates.js +++ b/ckan/public/scripts/templates.js @@ -1,3 +1,20 @@ +CKAN.Templates.resourceUpload = ' \ +
    \ +
    \ + \ +
    \ + \ +
    \ +
    \ + \ +
    \ + \ +
    '; + + + CKAN.Templates.resourceEntry = ' \
  • \ \ diff --git a/ckan/templates/js_strings.html b/ckan/templates/js_strings.html index 50750b0a071..e326170bf48 100644 --- a/ckan/templates/js_strings.html +++ b/ckan/templates/js_strings.html @@ -29,6 +29,8 @@ /* * Used in templates.js. */ + CKAN.Strings.upload = "${_('Upload')}"; + CKAN.Strings.cancel = "${_('Cancel')}"; CKAN.Strings.name = "${_('Name')}"; CKAN.Strings.description = "${_('Description')}"; CKAN.Strings.url = "${_('Url')}"; diff --git a/ckan/templates/package/new_package_form.html b/ckan/templates/package/new_package_form.html index fc2ee5c1107..53a6dcf28fa 100644 --- a/ckan/templates/package/new_package_form.html +++ b/ckan/templates/package/new_package_form.html @@ -173,11 +173,6 @@

    Errors in form

    -
    - - -
    -
    From 1f3e243d2494bb9f63d69dcad7e0593ca25d5255 Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Fri, 30 Mar 2012 16:39:35 +0100 Subject: [PATCH 06/13] [#1788]: Refactoring to centralise uploading in application.js; ckanjs.js is not used here now. --- ckan/public/scripts/application.js | 40 +++++++++--------------------- ckan/public/scripts/templates.js | 3 +++ ckan/templates/layout_base.html | 2 +- 3 files changed, 16 insertions(+), 29 deletions(-) diff --git a/ckan/public/scripts/application.js b/ckan/public/scripts/application.js index ab9832ed950..181c0a84f58 100644 --- a/ckan/public/scripts/application.js +++ b/ckan/public/scripts/application.js @@ -1,5 +1,9 @@ var CKAN = CKAN || {}; +CKAN.View = CKAN.View || {}; +CKAN.Model = CKAN.Model || {}; +CKAN.Utils = CKAN.Utils || {}; + /* ================================= */ /* == Initialise CKAN Application == */ /* ================================= */ @@ -17,15 +21,6 @@ var CKAN = CKAN || {}; CKAN.Utils.setupMarkdownEditor($('.markdown-editor')); // bootstrap collapse $('.collapse').collapse({toggle: false}); - // set up ckan js - var config = { - endpoint: CKAN.SITE_URL + '/' - }; - var client = new CKAN.Client(config); - // serious hack to deal with hacky code in ckanjs - CKAN.UI.workspace = { - client: client - }; // Buttons with href-action should navigate when clicked $('input.href-action').click(function(e) { @@ -282,8 +277,7 @@ CKAN.View.ResourceEditor = Backbone.View.extend({ }); new CKAN.View.ResourceAddUpload({ collection: this.collection, - el: this.el.find('.js-add-upload-form'), - client: CKAN.UI.workspace.client + el: this.el.find('.js-add-upload-form') }); @@ -670,29 +664,18 @@ CKAN.View.Resource = Backbone.View.extend({ CKAN.View.ResourceAddUpload = Backbone.View.extend({ tagName: 'div', - // expects a client arguments in its options initialize: function(options) { this.el = $(this.el); - this.client = options.client; _.bindAll(this, 'render', 'updateFormData', 'setMessage', 'uploadFile'); - this.render(); + $(CKAN.Templates.resourceUpload).appendTo(this.el); + this.$messages = this.el.find('.alert'); + this.setupFileUpload(); }, events: { 'click input[type="submit"]': 'uploadFile' }, - render: function () { - this.el.empty(); - tmplData = { - } - var tmpl = $.tmpl(CKAN.Templates.resourceUpload, tmplData).appendTo(this.el); - //this.el.append($(tmpl[0])); - this.$messages = this.el.find('.alert'); - this.setupFileUpload(); - return this; - }, - setupFileUpload: function() { var self = this; this.el.find('.fileupload').fileupload({ @@ -750,7 +733,8 @@ CKAN.View.ResourceAddUpload = Backbone.View.extend({ var self = this; self.setMessage('Checking upload permissions ... '); self.el.find('.fileinfo').text(key); - self.client.getStorageAuthForm(key, { + $.ajax({ + url: CKAN.SITE_URL + '/api/storage/auth/form/' + key, async: false, success: function(data) { self.el.find('form').attr('action', data.action); @@ -780,8 +764,8 @@ CKAN.View.ResourceAddUpload = Backbone.View.extend({ onUploadComplete: function(key) { var self = this; - self.client.apiCall({ - offset: '/storage/metadata/' + self.key, + $.ajax({ + url: CKAN.SITE_URL + '/api/storage/metadata/' + self.key, success: function(data) { var name = data._label; if (name && name.length > 0 && name[0] === '/') { diff --git a/ckan/public/scripts/templates.js b/ckan/public/scripts/templates.js index b56b537a6ad..fdcd4a954de 100644 --- a/ckan/public/scripts/templates.js +++ b/ckan/public/scripts/templates.js @@ -1,3 +1,6 @@ +var CKAN = CKAN || {}; +CKAN.Templates = CKAN.Templates || {}; + CKAN.Templates.resourceUpload = ' \
    \
    Meta - + From 08da289e4e25f9cfca138a48a3db8a750d6788df Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Fri, 30 Mar 2012 16:59:03 +0100 Subject: [PATCH 07/13] [#1788]: Simplifying JS. Don't embed unnecessary JSON into page. --- ckan/controllers/package.py | 6 +++--- ckan/public/scripts/application.js | 12 ++++++------ ckan/templates/package/edit.html | 2 +- ckan/templates/package/editresources.html | 2 +- ckan/templates/package/new.html | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ckan/controllers/package.py b/ckan/controllers/package.py index 90b9a3bff01..cb2e2cf9d64 100644 --- a/ckan/controllers/package.py +++ b/ckan/controllers/package.py @@ -261,7 +261,7 @@ def read(self, id, format='html'): try: c.pkg_dict = get_action('package_show')(context, data_dict) c.pkg = context['package'] - c.pkg_json = json.dumps(c.pkg_dict) + c.resources_json = json.dumps(c.pkg_dict.get('resources',[])) except NotFound: abort(404, _('Dataset not found')) except NotAuthorized: @@ -400,7 +400,7 @@ def new(self, data=None, errors=None, error_summary=None): data = data or clean_dict(unflatten(tuplize_dict(parse_params( request.params, ignore_keys=[CACHE_PARAMETER])))) - c.pkg_json = json.dumps(data) + c.resources_json = json.dumps(data.get('resources',[])) errors = errors or {} error_summary = error_summary or {} @@ -439,7 +439,7 @@ def edit(self, id, data=None, errors=None, error_summary=None): abort(404, _('Dataset not found')) c.pkg = context.get("package") - c.pkg_json = json.dumps(data) + c.resources_json = json.dumps(data.get('resources',[])) try: check_access('package_update',context) diff --git a/ckan/public/scripts/application.js b/ckan/public/scripts/application.js index 181c0a84f58..ff97dd362a9 100644 --- a/ckan/public/scripts/application.js +++ b/ckan/public/scripts/application.js @@ -80,12 +80,12 @@ CKAN.Utils = CKAN.Utils || {}; if (storageEnabled) { $('li.js-upload-file').show(); } - // Backbone model/view - var _dataset = new CKAN.Model.Dataset(preload_dataset); - var $el=$('form#dataset-edit'); - var view=new CKAN.View.ResourceEditor({ - collection: _dataset.get('resources'), - el: $el + // Backbone collection class + var CollectionOfResources = Backbone.Collection.extend({model: CKAN.Model.Resource}); + // 'resources_json' was embedded into the page + var view = new CKAN.View.ResourceEditor({ + collection: new CollectionOfResources(resources_json), + el: $('form#dataset-edit') }); view.render(); diff --git a/ckan/templates/package/edit.html b/ckan/templates/package/edit.html index 030d6ce6ca0..48d5c9ca47b 100644 --- a/ckan/templates/package/edit.html +++ b/ckan/templates/package/edit.html @@ -9,7 +9,7 @@ diff --git a/ckan/templates/package/editresources.html b/ckan/templates/package/editresources.html index 747f4cfd35d..22dd67f8b33 100644 --- a/ckan/templates/package/editresources.html +++ b/ckan/templates/package/editresources.html @@ -9,7 +9,7 @@ diff --git a/ckan/templates/package/new.html b/ckan/templates/package/new.html index 85730c13bc0..1dd77d7aa09 100644 --- a/ckan/templates/package/new.html +++ b/ckan/templates/package/new.html @@ -10,7 +10,7 @@ From 2ee1b48e3332a49a67e99dacc812f67a43992be5 Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Fri, 30 Mar 2012 17:13:29 +0100 Subject: [PATCH 08/13] [#1788]: Removed all dependancy on ckanjs.js. --- ckan/public/scripts/application.js | 15 + .../scripts/vendor/ckanjs/1.0.0/ckanjs.js | 1758 ----------------- ckan/templates/layout_base.html | 1 - 3 files changed, 15 insertions(+), 1759 deletions(-) delete mode 100755 ckan/public/scripts/vendor/ckanjs/1.0.0/ckanjs.js diff --git a/ckan/public/scripts/application.js b/ckan/public/scripts/application.js index ff97dd362a9..ebb289aa113 100644 --- a/ckan/public/scripts/application.js +++ b/ckan/public/scripts/application.js @@ -120,6 +120,21 @@ CKAN.Utils = CKAN.Utils || {}; }(jQuery)); +/* =============================== */ +/* Backbone Model: Resource object */ +/* =============================== */ +CKAN.Model.Resource = Backbone.Model.extend({ + constructor: function Resource() { + Backbone.Model.prototype.constructor.apply(this, arguments); + }, + toTemplateJSON: function() { + var obj = Backbone.Model.prototype.toJSON.apply(this, arguments); + obj.displaytitle = obj.description ? obj.description : 'No description ...'; + return obj; + } +}); + + /* ============================== */ /* == Backbone View: UrlEditor == */ diff --git a/ckan/public/scripts/vendor/ckanjs/1.0.0/ckanjs.js b/ckan/public/scripts/vendor/ckanjs/1.0.0/ckanjs.js deleted file mode 100755 index 08f478f57ac..00000000000 --- a/ckan/public/scripts/vendor/ckanjs/1.0.0/ckanjs.js +++ /dev/null @@ -1,1758 +0,0 @@ -this.CKAN = this.CKAN || {}; - -this.CKAN.Client = (function (CKAN, $, _, Backbone, undefined) { - - // Client constructor. Creates a new client for communicating with - // the CKAN API. - function Client(config) { - this._environment = {}; - this.configure(config || Client.defaults); - - _.bindAll(this, 'syncDataset', '_datasetConverter'); - } - - // Default config parameters for the Client. - Client.defaults = { - apiKey: '', - endpoint: 'http://ckan.net' - }; - - // Extend the Client prototype with Backbone.Events to provide .bind(), - // .unbind() and .trigger() methods. - _.extend(Client.prototype, Backbone.Events, { - - cache: { - dataset: new Backbone.Collection() - }, - - // Allows the implementor to specify an object literal of settings to - // configure the current client. Options include: - // - // - apiKey: The API key for the current user to create/edit datasets. - // - endpoint: The API endpoint to connect to. - configure: function (config) { - config = config || {}; - if (config.endpoint) { - config.endpoint = config.endpoint.replace(/\/$/, ''); - config.restEndpoint = config.endpoint + '/api/2/rest'; - config.searchEndpoint = config.endpoint + '/api/2/search'; - } - return this.environment(config); - }, - - // Client environment getter/setter. Environment variables can be retrieved - // by providing a key string, if the key does not exist the method will - // return `undefined`. To set keys either a key value pair can be provided - // or an object literal containing multiple key/value pairs. - environment: function (key, value) { - if (typeof key === "string") { - if (arguments.length === 1) { - return this._environment[key]; - } - this._environment[key] = value; - } else { - _.extend(this._environment, key); - } - return this; - }, - - // Helper method to fetch datasets from the server. Using this method to - // fetch datasets will ensure that only one instance of a model per server - // resource exists on the page at one time. - // - // The method accepts the dataset `"id"` and an object of `"options"`, these - // can be any options accepted by the `.fetch()` method on `Backbone.Model`. - // If the model already exists it will simply be returned otherwise an empty - // model will be returned and the data requested from the server. - // - // var dataset = client.getDatasetById('my-data-id', { - // success: function () { - // // The model is now populated. - // }, - // error: function (xhr) { - // // Something went wrong check response status etc. - // } - // }); - // - getDatasetById: function (id, options) { - var cache = this.cache.dataset, - dataset = cache.get(id); - var ourOptions = options || {}; - - if (!dataset) { - dataset = this.createDataset({id: id}); - - // Add the stub dataset to the global cache to ensure that only one - // is ever created. - cache.add(dataset); - - // Fetch the dataset from the server passing in any options provided. - // Also set up a callback to remove the model from the cache in - // case of error. - ourOptions.error = function () { - cache.remove(dataset); - }; - // TODO: RP not sure i understand what this does and why it is needed - dataset.fetch(ourOptions); - } - return dataset; - }, - - // Helper method to create a new instance of CKAN.Model.Dataset and - // register a sync listener to update the representation on the server when - // the model is created/updated/deleted. - // - // var myDataset = client.createDataset({ - // title: "My new data set" - // }); - // - // This ensures that the models are always saved with the latest environment - // data. - createDataset: function (attributes) { - return (new CKAN.Model.Dataset(attributes)).bind('sync', this.syncDataset); - }, - - // A wrapper around Backbone.sync() that adds additional ajax options to - // each request. These include the API key and the request url rather than - // using the model to generate it. - syncDataset: function (method, model, options) { - // Get the package url. - var url = this.environment('restEndpoint') + '/package'; - - // Add additional request options. - options = _.extend({}, { - url: model.isNew() ? url : url + '/' + model.id, - headers: { - 'X-CKAN-API-KEY': this.environment('apiKey') - } - }, options); - - Backbone.sync(method, model, options); - return this; - }, - - // Performs a search for datasets against the CKAN API. The `options` - // argument can contain any keys supported by jQuery.ajax(). The query - // parameters should be provided in the `options.query` property. - // - // var query = client.searchDatasets({ - // success: function (datasets) { - // console.log(datasets); // Logs a Backbone.Collection - // } - // }); - // - // The `options.success` method (and any other success callbacks) will - // recieve a `SearchCollection` containing `Dataset` models. The method - // returns a jqXHR object so that additional callbacks can be registered - // with .success() and .error(). - searchDatasets: function (options) { - options = options || {}; - options.data = _.defaults(options.query, {'limit': 10, 'all_fields': 1}); - delete options.query; - - return $.ajax(_.extend({ - url: this.environment('searchEndpoint') + '/package', - converters: { - 'text json': this._datasetConverter - } - }, options)); - }, - - // A "converter" method for jQuery.ajax() this is used to convert the - // results from a search API request into models which in turn will be - // passed into any registered success callbacks. We do this here so that - // _all_ registered success callbacks recieve the same data rather than - // just the callback registered when the search was made. - _datasetConverter: function (raw) { - var json = $.parseJSON(raw), - models = _.map(json.results, function (attributes) { - return this.createDataset(attributes); - }, this); - - return new CKAN.Model.SearchCollection(models, {total: json.count}); - }, - - // Performs a query on CKAN API. - // The `options` argument can contain any keys supported by jQuery.ajax(). - // In addition it should contain either a url or offset variable. If - // offset provided it will be used to construct the full api url by - // prepending the endpoint plus 'api' (i.e. offset of '/2/rest/package' - // will become '{endpoint}/api/2/rest'. - // - // The `options.success` method (and any other success callbacks) will - // recieve a `SearchCollection` containing `Dataset` models. The method - // returns a jqXHR object so that additional callbacks can be registered - // with .success() and .error(). - apiCall: function (options) { - options = options || {}; - // Add additional request options. - options = _.extend({}, { - url: this.environment('endpoint') + '/api' + options.offset, - headers: { - 'X-CKAN-API-KEY': this.environment('apiKey') - } - }, options); - - return $.ajax(options); - }, - - // wrap CKAN /api/storage/auth/form - see http://packages.python.org/ckanext-storage - // params and returns value are as for that API - // key is file label/path - getStorageAuthForm: function(key, options) { - options.offset = '/storage/auth/form/' + key; - this.apiCall(options); - } - }); - - return Client; - -})(this.CKAN, this.$, this._, this.Backbone); -this.CKAN = this.CKAN || {}; - -// Global object that stores all CKAN models. -CKAN.Model = function ($, _, Backbone, undefined) { - - var Model = {}; - - // Simple validator helper returns a `validate()` function that checks - // the provided model keys and returns an error object if these do not - // exist on the model or the attributes object provided.\ - // - // validate: validator('title', 'description', url) - // - function validator() { - var required = arguments; - return function (attrs) { - var errors; - if (attrs) { - _.each(required, function (key) { - if (!attrs[key] && !this.get(key)) { - if (!errors) { - errors = {}; - } - errors[key] = 'The "' + key + '" is required'; - } - }, this); - } - return errors; - }; - } - - // A Base model that all CKAN models inherit from. Methods that should be - // shared across all models should be defined here. - Model.Base = Backbone.Model.extend({ - - // Extend the default Backbone.Model constructor simply to provide a named - // function. This improves debugging in consoles such as the Webkit inspector. - constructor: function Base(attributes, options) { - Backbone.Model.prototype.constructor.apply(this, arguments); - }, - - // Rather than letting the models connect to the server themselves we - // leave this to the implementor to decide how models are saved. This allows - // the API details such as API key and enpoints to change without having - // to update the models. When `.save()` or `.destroy()` is called the - // `"sync"` event will be published with the arguments provided to `.sync()`. - // - // var package = new Package({name: 'My Package Name'}); - // package.bind('sync', Backbone.sync); - // - // This method returns itself for chaining. - sync: function () { - return this.trigger.apply(this, ['sync'].concat(_.toArray(arguments))); - }, - - // Overrides the standard `toJSON()` method to serialise any nested - // Backbone models and collections (or any other object that has a `toJSON()` - // method). - toJSON: function () { - var obj = Backbone.Model.prototype.toJSON.apply(this, arguments); - _.each(obj, function (value, key) { - if (value && typeof value === 'object' && value.toJSON) { - obj[key] = value.toJSON(); - } - }); - return obj; - } - }); - - // Model objects - Model.Dataset = Model.Base.extend({ - constructor: function Dataset() { - // Define an key/model mapping for child relationships. These will be - // managed as a Backbone collection when setting/getting the key. - this.children = { - resources: Model.Resource, - relationships: Model.Relationship - }; - Model.Base.prototype.constructor.apply(this, arguments); - }, - - defaults: { - title: '', - name: '', - notes: '', - resources: [], - tags: [] - }, - - // Override the `set()` method on `Backbone.Model` to handle resources as - // relationships. This will now manually update the `"resouces"` collection - // (using `_updateResources()`) with any `Resource` models provided rather - // than replacing the key. - set: function (attributes, options) { - var children, validated; - - // If not yet defined set the child collections. This will be done when - // set is called for the first time in the constructor. - this._createChildren(); - - // Check to see if any child keys are present in the attributes and - // remove them from the object. Then update them seperately after the - // parent `set()` method has been called. - _.each(this.children, function (Model, key) { - if (attributes && attributes[key]) { - if (!(attributes[key] instanceof Backbone.Collection)) { - if (!children) { - children = {}; - } - children[key] = attributes[key]; - delete attributes[key]; - } - } - }, this); - - validated = Model.Base.prototype.set.call(this, attributes, options); - - // Ensure validation passed before updating child models. - if (validated && children) { - this._updateChildren(children); - } - - return validated; - }, - - // Checks to see if our model instance has Backbone collections defined for - // child keys. If they do not exist it creates them. - _createChildren: function () { - _.each(this.children, function (Model, key) { - if (!this.get(key)) { - var newColl = new Backbone.Collection(); - this.attributes[key] = newColl; - newColl.model = Model; - // bind change events so updating the children trigger change on Dataset - var self = this; - // TODO: do we want to do all or be more selective - newColl.bind('all', function() { - self.trigger('change'); - }); - } - }, this); - return this; - }, - - // Manages the one to many relationship between resources and the dataset. - // Accepts an array of Resources (ideally model instances but will convert - // object literals into resources for you). New models will be added to the - // collection and existing ones updated. Any pre-existing models not found - // in the new array will be removed. - _updateChildren: function (children) { - _.each(children, function (models, key) { - var collection = this.get(key), - ids = {}; - - // Add/Update models. - _.each(models, function (model) { - var existing = collection.get(model.id), - isLiteral = !(model instanceof this.children[key]); - - // Provide the dataset key if not already there and current model is - // not a relationship. - if (isLiteral && key !== 'relationships') { - model.dataset = this; - delete model.package_id; - } - - if (!existing) { - collection.add(model); - } - else if (existing && isLiteral) { - existing.set(model); - } - - ids[model.id] = 1; - }, this); - - // Remove missing models. - collection.remove(collection.select(function (model) { - return !ids[model.id]; - })); - }, this); - return this; - }, - - // NOTE: Returns localised URL. - toTemplateJSON: function () { - var out = this.toJSON(); - var title = this.get('title'); - out.displaytitle = title ? title : 'No title ...'; - var notes = this.get('notes'); - // Don't use a global Showdown; CKAN doesn't need that library - var showdown = new Showdown.converter(); - out.notesHtml = showdown.makeHtml(notes ? notes : ''); - out.snippet = this.makeSnippet(out.notesHtml); - return out; - }, - - makeSnippet: function (notesHtml) { - var out = $(notesHtml).text(); - if (out.length > 190) { - out = out.slice(0, 190) + ' ...'; - } - return out; - } - }); - - // A model for working with resources. Each resource is _required_ to have a - // parent `Dataset`. This must be provided under the `"dataset"` key when the - // `Resource` is created. This is handled for you when creating resources - // via the `Dataset` `set()` method. - // - // The `save()`, `fetch()` and `delete()` methods are mapped to the parent - // dataset and can be used to update a Resource's metadata. - // - // var resource = new Model.Resource({ - // name: 'myresource.csv', - // url: 'http://www.example.com/myresource.csv', - // dataset: dataset - // }); - // - // // Updates the resource name on the server by saving the parent dataset - // resource.set({name: 'Some new name'}); - // - Model.Resource = Model.Base.extend({ - constructor: function Resource() { - Model.Base.prototype.constructor.apply(this, arguments); - }, - - // Override the `save()` method to update the Resource with attributes then - // call the parent dataset and save that. Any `options` provided will be - // passed on to the dataset `save()` method. - save: function (attrs, options) { - var validated = this.set(attrs); - if (validated) { - return this.get('dataset').save({}, options); - } - return validated; - }, - - // Override the `fetch()` method to call `fetch()` on the parent dataset. - fetch: function (options) { - return this.get('dataset').fetch(options); - }, - - // Override the `fetch()` method to trigger the `"destroy"` event which - // will remove it from any collections then save the parent dataset. - destroy: function (options) { - return this.trigger('destroy', this).get('dataset').save({}, options); - }, - - // Override the `toJSON()` method to set the `"package_id"` key required - // by the server. - toJSON: function () { - // Call Backbone.Model rather than Base to break the circular reference. - var obj = Backbone.Model.prototype.toJSON.apply(this, arguments); - if (obj.dataset) { - obj.package_id = obj.dataset.id; - delete obj.dataset; - } else { - obj.package_id = null; - } - return obj; - }, - - toTemplateJSON: function() { - var obj = Backbone.Model.prototype.toJSON.apply(this, arguments); - obj.displaytitle = obj.description ? obj.description : 'No description ...'; - return obj; - }, - - // Validates the provided attributes. Returns an object literal of - // attribute/error pairs if invalid, `undefined` otherwise. - validate: validator('url') - }); - - // Helper function that returns a stub method that warns the devloper that - // this method has not yet been implemented. - function apiPlaceholder(method) { - var console = window.console; - return function () { - if (console && console.warn) { - console.warn('The method "' + method + '" has not yet been implemented'); - } - return this; - }; - } - - // A model for working with relationship objects. These are currently just the - // realtionship objects returned by the server wrapped in a `Base` model - // instance. Currently there is no save or delete functionality. - Model.Relationship = Model.Base.extend({ - constructor: function Relationship() { - Model.Base.prototype.constructor.apply(this, arguments); - }, - - // Add placeholder method that simply returns itself to all methods that - // interact with the server. This will also log a warning message to the - // developer into the console. - save: apiPlaceholder('save'), - fetch: apiPlaceholder('fetch'), - destroy: apiPlaceholder('destroy'), - - // Validates the provided attributes. Returns an object literal of - // attribute/error pairs if invalid, `undefined` otherwise. - validate: validator('object', 'subject', 'type') - }); - - // Collection for managing results from the CKAN search API. An additional - // `options.total` parameter can be provided on initialisation to - // indicate how many models there are on the server in total. This can - // then be accessed via the `total` property. - Model.SearchCollection = Backbone.Collection.extend({ - constructor: function SearchCollection(models, options) { - if (options) { - this.total = options.total; - } - Backbone.Collection.prototype.constructor.apply(this, arguments); - } - }); - - return Model; - -}(this.jQuery, this._, this.Backbone); -var CKAN = CKAN || {}; - -CKAN.Templates = { - minorNavigationDataset: ' \ - \ - ' -}; -var CKAN = CKAN || {}; - -CKAN.View = function($) { - var my = {}; - - // Flash a notification message - // - // Parameters: msg, type. type is set as class on notification and should be one of success, error. - // If type not defined defaults to success - my.flash = function(msg, type) { - if (type === undefined) { - var type = 'success' - } - $.event.trigger('notification', [msg, type]); - }; - - my.NotificationView = Backbone.View.extend({ - initialize: function() { - $.template('notificationTemplate', - '
    ${message}
    '); - - var self = this; - $(document).bind('notification', function(e, msg, type) { - self.render(msg, type) - }); - }, - - events: { - 'click .flash-banner button': 'hide' - }, - - render: function(msg, type) { - var _out = $.tmpl('notificationTemplate', {'message': msg, 'type': type}) - this.el.html(_out); - this.el.slideDown(400); - }, - - hide: function() { - this.el.slideUp(200); - } - }); - - my.ConfigView = Backbone.View.extend({ - initialize: function() { - this.cfg = {}; - this.$ckanUrl = this.el.find('input[name=ckan-url]'); - this.$apikey = this.el.find('input[name=ckan-api-key]'); - - var cfg = this.options.config; - this.$ckanUrl.val(cfg.endpoint); - this.$apikey.val(cfg.apiKey); - }, - - events: { - 'submit #config-form': 'updateConfig' - }, - - updateConfig: function(e) { - e.preventDefault(); - this.saveConfig(); - CKAN.View.flash('Saved configuration'); - }, - - saveConfig: function() { - this.cfg = { - 'endpoint': this.$ckanUrl.val(), - 'apiKey': this.$apikey.val() - }; - $.event.trigger('config:update', this.cfg); - } - }); - - my.DatasetEditView = Backbone.View.extend({ - initialize: function() { - _.bindAll(this, 'saveData', 'render'); - this.model.bind('change', this.render); - }, - - render: function() { - tmplData = { - dataset: this.model.toTemplateJSON() - } - var tmpl = $.tmpl(CKAN.Templates.datasetForm, tmplData); - $(this.el).html(tmpl); - if (tmplData.dataset.id) { // edit not add - $('#minornavigation').html($.tmpl(CKAN.Templates.minorNavigationDataset, tmplData)); - } - return this; - }, - - events: { - 'submit form.dataset': 'saveData', - 'click .previewable-textarea a': 'togglePreview', - 'click .dataset-form-navigation a': 'showFormPart' - }, - - showFormPart: function(e) { - e.preventDefault(); - var action = $(e.target)[0].href.split('#')[1]; - $('.dataset-form-navigation a').removeClass('selected'); - $('.dataset-form-navigation a[href=#' + action + ']').addClass('selected'); - }, - - saveData: function(e) { - e.preventDefault(); - this.model.set(this.getData()); - this.model.save({}, { - success: function(model) { - CKAN.View.flash('Saved dataset'); - window.location.hash = '#dataset/' + model.id + '/view'; - }, - error: function(model, error) { - CKAN.View.flash('Error saving dataset ' + error.responseText, 'error'); - } - }); - }, - - getData: function() { - var _data = $(this.el).find('form.dataset').serializeArray(); - modelData = {}; - $.each(_data, function(idx, value) { - modelData[value.name.split('--')[1]] = value.value - }); - return modelData; - }, - - togglePreview: function(e) { - // set model data as we use it below for notesHtml - this.model.set(this.getData()); - e.preventDefault(); - var el = $(e.target); - var action = el.attr('action'); - var div = el.closest('.previewable-textarea'); - div.find('.tabs a').removeClass('selected'); - div.find('.tabs a[action='+action+']').addClass('selected'); - var textarea = div.find('textarea'); - var preview = div.find('.preview'); - if (action=='preview') { - preview.html(this.model.toTemplateJSON().notesHtml); - textarea.hide(); - preview.show(); - } else { - textarea.show(); - preview.hide(); - } - return false; - } - - }); - - my.DatasetFullView = Backbone.View.extend({ - initialize: function() { - _.bindAll(this, 'render'); - this.model.bind('change', this.render); - - // slightly painful but we have to set this up here so - // it has access to self because when called this will - // be overridden and refer to the element in dom that - // was being saved - var self = this; - this.saveFromEditable = function(value, settings) { - var _attribute = $(this).attr('backbone-attribute'); - var _data = {}; - _data[_attribute] = value; - self.model.set(_data); - self.model.save({}, { - success: function(model) { - CKAN.View.flash('Saved updated notes'); - }, - error: function(model, error) { - CKAN.View.flash('Error saving notes ' + error.responseText, 'error'); - } - }); - // do not worry too much about what we return here - // because update of model will automatically lead to - // re-render - return value; - }; - }, - - events: { - 'click .action-add-resource': 'showResourceAdd' - }, - - render: function() { - var tmplData = { - domain: this.options.domain, - dataset: this.model.toTemplateJSON() - }; - $('.page-heading').html(tmplData.dataset.displaytitle); - $('#minornavigation').html($.tmpl(CKAN.Templates.minorNavigationDataset, tmplData)); - $('#sidebar .widget-list').html($.tmpl(CKAN.Templates.sidebarDatasetView, tmplData)); - this.el.html($.tmpl(CKAN.Templates.datasetView, tmplData)); - this.setupEditable(); - return this; - }, - - setupEditable: function() { - var self = this; - this.el.find('.editable-area').editable( - self.saveFromEditable, { - type : 'textarea', - cancel : 'Cancel', - submit : 'OK', - tooltip : 'Click to edit...', - onblur : 'ignore', - data : function(value, settings) { - var _attribute = $(this).attr('backbone-attribute'); - return self.model.get(_attribute); - } - } - ); - }, - - showResourceAdd: function(e) { - var self = this; - e.preventDefault(); - var $el = $('
    ').addClass('resource-add-dialog'); - $('body').append($el); - var resource = new CKAN.Model.Resource({ - 'dataset': self.model - }); - function handleNewResourceSave(model) { - var res = self.model.get('resources'); - res.add(model); - $el.dialog('close'); - self.model.save({}, { - success: function(model) { - CKAN.View.flash('Saved dataset'); - // TODO: no need to re-render (should happen automatically) - self.render(); - } - , error: function(model, error) { - CKAN.View.flash('Failed to save: ' + error, 'error'); - } - }); - } - resource.bind('change', handleNewResourceSave); - var resourceView = new CKAN.View.ResourceCreate({ - el: $el, - model: resource - }); - resourceView.render(); - dialogOptions = { - autoOpen: false, - // does not seem to work for width ... - position: ['center', 'center'], - buttons: [], - width: 660, - resize: 'auto', - modal: false, - draggable: true, - resizable: true - }; - dialogOptions.title = 'Add Data (File, API, ...)'; - $el.dialog(dialogOptions); - $el.dialog('open'); - $el.bind("dialogbeforeclose", function () { - self.el.find('.resource-add-dialog').remove(); - }); - } - }); - - my.DatasetSearchView = Backbone.View.extend({ - events: { - 'submit #search-form': 'onSearch' - }, - - initialize: function(options) { - var view = this; - - // Temporarily provide the view with access to the client for searching. - this.client = options.client; - this.$results = this.el.find('.results'); - this.$datasetList = this.$results.find('.datasets'); - this.$dialog = this.el.find('.dialog'); - - this.resultView = new CKAN.View.DatasetListing({ - collection: new Backbone.Collection(), - el: this.$datasetList - }); - - _.bindAll(this, "render"); - }, - - render: function() { - this.$('.count').html(this.totalResults); - this.hideSpinner(); - this.$results.show(); - return this; - }, - - onSearch: function (event) { - event.preventDefault(); - var q = $(this.el).find('input.search').val(); - this.doSearch(q); - }, - - doSearch: function (q) { - $(this.el).find('input.search').val(q), - self = this; - - this.showSpinner(); - this.$results.hide(); - this.$results.find('.datasets').empty(); - this.client.searchDatasets({ - query: {q:q}, - success: function (collection) { - self.totalResults = collection.total; - self.resultView.setCollection(collection); - self.render(); - } - }); - }, - - showSpinner: function() { - this.$dialog.empty(); - this.$dialog.html('

    Loading results...

    '); - this.$dialog.show(); - }, - - hideSpinner: function() { - this.$dialog.empty().hide(); - } - }); - - my.ResourceView = Backbone.View.extend({ - render: function() { - var resourceData = this.model.toTemplateJSON(); - var resourceDetails = {}; - var exclude = [ 'resource_group_id', - 'description', - 'url', - 'position', - 'id', - 'webstore', - 'qa', - 'dataset', - 'displaytitle' - ]; - $.each(resourceData, function(key, value) { - if (! _.contains(exclude, key)) { - resourceDetails[key] = value; - } - }); - tmplData = { - dataset: this.model.get('dataset').toTemplateJSON(), - resource: resourceData, - resourceDetails: resourceDetails - }; - $('.page-heading').html(tmplData.dataset.name + ' / ' + tmplData.resource.displaytitle); - var tmpl = $.tmpl(CKAN.Templates.resourceView, tmplData); - $(this.el).html(tmpl); - return this; - }, - - events: { - } - }); - - my.ResourceEditView = Backbone.View.extend({ - render: function() { - var tmpl = $.tmpl(CKAN.Templates.resourceForm, this.model.toJSON()); - $(this.el).html(tmpl); - return this; - }, - - events: { - 'submit form': 'saveData' - }, - - saveData: function() { - // only set rather than save as can only save resources as part of a dataset atm - this.model.set(this.getData(), { - error: function(model, error) { - var msg = 'Failed to save, possibly due to invalid data '; - msg += JSON.stringify(error); - alert(msg); - } - }); - return false; - }, - - getData: function() { - var _data = $(this.el).find('form.resource').serializeArray(); - modelData = {}; - $.each(_data, function(idx, value) { - modelData[value.name.split('--')[1]] = value.value - }); - return modelData; - } - - }); - - return my; -}(jQuery); -var CKAN = CKAN || {}; - -CKAN.UI = function($) { - var my = {}; - - my.Workspace = Backbone.Router.extend({ - routes: { - "": "index", - "search": "search", - "search/:query": "search", - "search/:query/p:page": "search", - "dataset/:id/view": "datasetView", - "dataset/:id/edit": "datasetEdit", - "dataset/:datasetId/resource/:resourceId": "resourceView", - "add-dataset": "datasetAdd", - "add-resource": "resourceAdd", - "config": "config" - }, - - initialize: function(options) { - var self = this; - var defaultConfig = { - endpoint: 'http://ckan.net', - apiKey: '' - }; - - var config = options.config || defaultConfig; - this.client = new CKAN.Client(config); - if (options.fixtures && options.fixtures.datasets) { - $.each(options.fixtures.datasets, function(idx, obj) { - var collection = self.client.cache.dataset; - collection.add(new CKAN.Model.Dataset(obj)); - }); - } - - var newPkg = this.client.createDataset(); - var newCreateView = new CKAN.View.DatasetEditView({model: newPkg, el: $('#dataset-add-page')}); - newCreateView.render(); - - var newResource = new CKAN.Model.Resource({ - dataset: newPkg - }); - var newResourceEditView = new CKAN.View.ResourceEditView({model: newResource, el: $('#add-resource-page')}); - newResourceEditView.render(); - - var searchView = this.searchView = new CKAN.View.DatasetSearchView({ - client: this.client, - el: $('#search-page') - }); - - // set up top bar search - $('#menusearch').find('form').submit(function(e) { - e.preventDefault(); - var _el = $(e.target); - var _q = _el.find('input[name="q"]').val(); - searchView.doSearch(_q); - self.search(_q); - }); - - - var configView = new CKAN.View.ConfigView({ - el: $('#config-page'), - config: config - }); - $(document).bind('config:update', function(e, cfg) { - self.client.configure(cfg); - }); - - this.notificationView = new CKAN.View.NotificationView({ - el: $('.flash-banner-box') - }); - }, - - switchView: function(view) { - $('.page-view').hide(); - $('#sidebar .widget-list').empty(); - $('#minornavigation').empty(); - $('#' + view + '-page').show(); - }, - - index: function(query, page) { - this.search(); - }, - - search: function(query, page) { - this.switchView('search'); - $('.page-heading').html('Search'); - }, - - _findDataset: function(id, callback) { - var pkg = this.client.getDatasetById(id); - - if (pkg===undefined) { - pkg = this.client.createDataset({id: id}); - pkg.fetch({ - success: callback, - error: function() { - alert('There was an error'); - } - }); - } else { - callback(pkg); - } - }, - - datasetView: function(id) { - var self = this; - self.switchView('view'); - var $viewpage = $('#view-page'); - this._findDataset(id, function (model) { - var newView = new CKAN.View.DatasetFullView({ - model: model, - el: $viewpage - }); - newView.render(); - }); - }, - - datasetEdit: function(id) { - this.switchView('dataset-edit'); - $('.page-heading').html('Edit Dataset'); - function _show(model) { - var newView = new CKAN.View.DatasetEditView({model: model}); - $('#dataset-edit-page').html(newView.render().el); - } - this._findDataset(id, _show) - }, - - datasetAdd: function() { - this.switchView('dataset-add'); - $('.page-heading').html('Add Dataset'); - $('#sidebar .widget-list').empty(); - }, - - resourceView: function(datasetId, resourceId) { - this.switchView('resource-view'); - var $viewpage = $('#resource-view-page'); - this._findDataset(datasetId, function (model) { - var resource = model.get('resources').get(resourceId); - var newView = new CKAN.View.ResourceView({ - model: resource, - el: $viewpage - }); - newView.render(); - }); - }, - - resourceAdd: function() { - this.switchView('add-resource'); - }, - - config: function() { - this.switchView('config'); - }, - - url: function(controller, action, id) { - if (id) { - return '#' + controller + '/' + id + '/' + action; - } else { - return '#' + controller + '/' + action; - } - } - }); - - my.initialize = function(options) { - my.workspace = new my.Workspace(options); - Backbone.history.start() - }; - - return my; -}(jQuery); - -CKAN.Templates.datasetForm = ' \ - \ -
    \ -
    \ - \ -
    \ -
    \ - \ -
    \ - \ -
    \ - \ -
    \ -
    \ - \ -
    \ - \ -
    \ - \ -
    \ -
    \ - \ -
    \ - \ -
    \ - \ -
    \ -
    \ -
    \ - \ - \ -
    \ -
    \ - \ -
    \ - \ -
    \ - \ -'; - -CKAN.Templates.datasetFormSidebar = ' \ -
    \ - \ -
    \ -'; -CKAN.Templates.datasetView = ' \ -
    \ -
    \ - ${dataset.snippet} \ - {{if dataset.snippet.length > 50}} \ - Read more \ - {{/if}} \ -
    \ -
    \ - {{if dataset.tags.length}} \ -
      \ - {{each dataset.tags}} \ -
    • ${$value}
    • \ - {{/each}} \ -
    \ - {{/if}} \ -
    \ -
    \ -

    Resources

    \ - \ - \ - \ - \ - \ - \ - {{each dataset.resources}} \ - \ - \ - \ - \ - {{/each}} \ - {{if !dataset.resources.length }} \ - \ - {{/if}} \ -
    DescriptionFormatActions
    \ - \ - {{if $value.description}} \ - ${$value.description} \ - {{else}} \ - (No description) \ - {{/if}} \ - \ - ${$value.format}Download \ -
    No resources.
    \ -
    \ - Add a resource \ -
    \ -
    \ -
    \ -

    Notes

    \ -
    \ - {{html dataset.notesHtml}} \ - {{if !dataset.notes || dataset.notes.length === 0}} \ - No notes yet. Click to add some ... \ - {{/if}} \ -
    \ -
    \ -
    \ -

    Additional Information

    \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - {{each dataset.extras}} \ - \ - \ - \ - \ - {{/each}} \ - \ -
    FieldValue
    Creator${dataset.author}
    Maintainer${dataset.maintainer}
    ${$index}${$value}
    \ -
    \ -
    \ -'; - -CKAN.Templates.sidebarDatasetView = ' \ -
  • \ -

    Connections

    \ - \ - {{if dataset.relationships.length == 0}} \ - No connections to other datasets. \ - {{/if}} \ -
  • \ -'; -CKAN.Templates.resourceForm = ' \ -
    \ -
    \ -
    \ - \ -
    \ -
    \ - \ -
    \ -
    \ - \ -
    \ -
    \ - \ -
    \ -
    \ - \ -
    \ - \ -

    Optional Info

    \ -
    \ -
    \ -
    \ - \ -
    \ -
    \ - \ -
    \ - \ - \ -
    \ - \ -
    \ -
    \ - \ -
    \ -
    \ - \ -
    \ - \ -
    \ -
    \ -'; - -CKAN.Templates.resourceCreate = ' \ -
    \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ -
    \ -

    Link to data already online

    \ -

    or

    Upload data

    \ -
    \ -'; -CKAN.Templates.resourceUpload = ' \ -
    \ -
    \ - \ -
    \ -
    \ - \ - \ - \ - \ -
    \ -
    \ - \ -
    \ -'; - -CKAN.Templates.resourceView = ' \ -
    \ -

    \ - ${resource.url} [${resource.format}] \ -

    \ -
    \ - ${resource.description} \ -
    \ - \ -
    \ -

    Additional Information

    \ - \ - \ - \ - \ - \ - \ - \ - \ - {{each resourceDetails}} \ - \ - \ - \ - \ - {{/each}} \ - \ -
    FieldValue
    ${$index}${$value}
    \ -
    \ -
    \ -'; -this.CKAN || (this.CKAN = {}); -this.CKAN.View || (this.CKAN.View = {}); - -(function (CKAN, $, _, Backbone, undefined) { - CKAN.View.DatasetListing = Backbone.View.extend({ - tagName: 'ul', - - constructor: function DatasetListing() { - Backbone.View.prototype.constructor.apply(this, arguments); - - _.bindAll(this, 'addItem', 'removeItem'); - - this.el = $(this.el); - this.setCollection(this.collection); - }, - - setCollection: function (collection) { - if (this.collection) { - this.collection.unbind('add', this.addItem); - this.collection.unbind('remove', this.removeItem); - } - - this.collection = collection; - if (collection) { - this.collection.bind('add', this.addItem); - this.collection.bind('remove', this.removeItem); - } - return this.render(); - }, - - addItem: function (model) { - var view = new CKAN.View.DatasetListingItem({ - domian: this.options.domain, - model: model - }); - this.el.data(model.cid, view).append(view.render().el); - return this; - }, - - removeItem: function (model) { - var view = this.el.data(model.cid); - if (view) { - view.remove(); - } - return this; - }, - - render: function () { - this.el.empty(); - if (this.collection) { - this.collection.each(this.addItem); - } - return this; - }, - - remove: function () { - this.setCollection(null); - return Backbone.View.prototype.remove.apply(this, arguments); - } - }); - - CKAN.View.DatasetListingItem = Backbone.View.extend({ - tagName: 'li', - - className: 'dataset summary', - - options: { - template: '\ -
    \ - \ - ${displaytitle} \ - \ -
    \ - {{if formats.length > 0}} \ -
      \ - {{each formats}} \ -
    • ${$value}
    • \ - {{/each}} \ -
    \ - {{/if}} \ -
    \ -
    \ -
    \ - {{html snippet}} \ -
    \ -
    \ - {{if tags.length}} \ - \ - {{/if}} \ -
    \ - ' - }, - - constructor: function DatasetListingItem() { - Backbone.View.prototype.constructor.apply(this, arguments); - this.el = $(this.el); - }, - - render: function () { - var dataset = this.model.toTemplateJSON(); - // if 'UI' mode ... - var urls = {}; - if (CKAN.UI && CKAN.UI.workspace) { - urls.datasetView = CKAN.UI.workspace.url('dataset', 'view', this.model.id); - } else { - urls.datasetView = dataset.ckan_url; - } - var data = _.extend(dataset, { - dataset: dataset, - formats: this._availableFormats(), - urls: urls - }); - this.el.html($.tmpl(this.options.template, data)); - return this; - }, - - _availableFormats: function () { - var formats = this.model.get('resources').map(function (resource) { - return resource.get('format'); - }); - return _.uniq(_.compact(formats)); - } - }); -})(CKAN, $, _, Backbone, undefined); -this.CKAN || (this.CKAN = {}); -this.CKAN.View || (this.CKAN.View = {}); - -(function (CKAN, $, _, Backbone, undefined) { - CKAN.View.ResourceCreate = Backbone.View.extend({ - initialize: function() { - this.el = $(this.el); - _.bindAll(this, 'renderMain'); - this.renderMain(); - this.$edit = $(this.el.find('.edit')[0]); - this.$upload = $(this.el.find('.upload')[0]); - this.editView = new CKAN.View.ResourceEditView({ - model: this.model, - el: this.$edit - }); - this.uploadView = new CKAN.View.ResourceUpload({ - el: this.$upload, - model: this.model, - // TODO: horrible reverse depedency ... - client: CKAN.UI.workspace.client - }); - }, - - renderMain: function () { - this.el.empty(); - tmplData = { - }; - var tmpl = $.tmpl(CKAN.Templates.resourceCreate, tmplData); - this.el.html(tmpl); - return this; - }, - - render: function () { - this.editView.render(); - this.uploadView.render(); - } - }); - -})(CKAN, $, _, Backbone, undefined); - -this.CKAN || (this.CKAN = {}); -this.CKAN.View || (this.CKAN.View = {}); - -(function (CKAN, $, _, Backbone, undefined) { - CKAN.View.ResourceUpload = Backbone.View.extend({ - tagName: 'div', - - // expects a client arguments in its options - initialize: function(options) { - this.el = $(this.el); - this.client = options.client; - _.bindAll(this, 'render', 'updateFormData', 'setMessage', 'uploadFile'); - }, - - events: { - 'click input[type="submit"]': 'uploadFile' - }, - - render: function () { - this.el.empty(); - tmplData = { - } - var tmpl = $.tmpl(CKAN.Templates.resourceUpload, tmplData); - this.el.html(tmpl); - this.$messages = this.el.find('.alert'); - this.setupFileUpload(); - return this; - }, - - setupFileUpload: function() { - var self = this; - this.el.find('.fileupload').fileupload({ - // needed because we are posting to remote url - forceIframeTransport: true, - replaceFileInput: false, - autoUpload: false, - fail: function(e, data) { - alert('Upload failed'); - }, - add: function(e,data) { - self.fileData = data; - self.fileUploadData = data; - self.key = self.makeUploadKey(data.files[0].name); - self.updateFormData(self.key); - }, - send: function(e, data) { - self.setMessage('Uploading file ... '); - }, - done: function(e, data) { - self.onUploadComplete(self.key); - } - }) - }, - - ISODateString: function(d) { - function pad(n) {return n<10 ? '0'+n : n}; - return d.getUTCFullYear()+'-' - + pad(d.getUTCMonth()+1)+'-' - + pad(d.getUTCDate())+'T' - + pad(d.getUTCHours())+':' - + pad(d.getUTCMinutes())+':' - + pad(d.getUTCSeconds()); - }, - - // Create an upload key/label for this file. - // - // Form: {current-date}/file-name. Do not just use the file name as this - // would lead to collisions. - // (Could add userid/username and/or a small random string to reduce - // collisions but chances seem very low already) - makeUploadKey: function(fileName) { - // google storage replaces ' ' with '+' which breaks things - // See http://trac.ckan.org/ticket/1518 for more. - var corrected = fileName.replace(/ /g, '-'); - // note that we put hh mm ss as hhmmss rather hh:mm:ss (former is 'basic - // format') - var now = new Date(); - // replace ':' with nothing - var str = this.ISODateString(now).replace(':', '').replace(':', ''); - return str + '/' + corrected; - }, - - updateFormData: function(key) { - var self = this; - self.setMessage('Checking upload permissions ... '); - self.el.find('.fileinfo').text(key); - self.client.getStorageAuthForm(key, { - async: false, - success: function(data) { - self.el.find('form').attr('action', data.action); - _tmpl = ''; - var $hidden = $(self.el.find('form div.hidden-inputs')[0]); - $.each(data.fields, function(idx, item) { - $hidden.append($.tmpl(_tmpl, item)); - }); - self.hideMessage(); - }, - error: function(jqXHR, textStatus, errorThrown) { - // TODO: more graceful error handling (e.g. of 409) - self.setMessage('Failed to get credentials for storage upload. Upload cannot proceed', 'error'); - self.el.find('input[name="upload"]').hide(); - } - }); - }, - - uploadFile: function(e) { - e.preventDefault(); - if (!this.fileData) { - alert('No file selected'); - return; - } - var jqXHR = this.fileData.submit(); - }, - - onUploadComplete: function(key) { - var self = this; - self.client.apiCall({ - offset: '/storage/metadata/' + self.key, - success: function(data) { - var name = data._label; - if (name && name.length > 0 && name[0] === '/') { - name = name.slice(1); - } - var d = new Date(data._last_modified); - var lastmod = self.ISODateString(d); - self.model.set({ - url: data._location - , name: name - , size: data._content_length - , last_modified: lastmod - , format: data._format - , mimetype: data._format - , resource_type: 'file.upload' - , owner: data['uploaded-by'] - , hash: data._checksum - , cache_url: data._location - , cache_url_updated: lastmod - } - , { - error: function(model, error) { - var msg = 'Filed uploaded OK but error adding resource: ' + error + '.'; - msg += 'You may need to create a resource directly. Uploaded file at: ' + data._location; - CKAN.View.flash(msg, 'error'); - } - } - ); - self.setMessage('File uploaded OK and resource added', 'success'); - CKAN.View.flash('File uploaded OK and resource added'); - } - }); - }, - - setMessage: function(msg, category) { - var category = category || 'alert-info'; - this.$messages.removeClass('alert-info alert-success alert-error'); - this.$messages.addClass(category); - this.$messages.show(); - this.$messages.html(msg); - }, - - hideMessage: function() { - this.$messages.hide('slow'); - } - }); - -})(CKAN, $, _, Backbone, undefined); diff --git a/ckan/templates/layout_base.html b/ckan/templates/layout_base.html index 28af818c45c..155862e1dd9 100644 --- a/ckan/templates/layout_base.html +++ b/ckan/templates/layout_base.html @@ -232,7 +232,6 @@

    Meta

    - From 60e1fff8a92fed918827a2089989b046d0a59a97 Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Fri, 30 Mar 2012 17:21:22 +0100 Subject: [PATCH 09/13] [#1788]: Avoid overlapping input names in subform. --- ckan/public/scripts/application.js | 10 +++++----- ckan/public/scripts/templates.js | 2 +- ckan/templates/package/new_package_form.html | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ckan/public/scripts/application.js b/ckan/public/scripts/application.js index ebb289aa113..1b2ed29335a 100644 --- a/ckan/public/scripts/application.js +++ b/ckan/public/scripts/application.js @@ -763,7 +763,7 @@ CKAN.View.ResourceAddUpload = Backbone.View.extend({ error: function(jqXHR, textStatus, errorThrown) { // TODO: more graceful error handling (e.g. of 409) self.setMessage('Failed to get credentials for storage upload. Upload cannot proceed', 'error'); - self.el.find('input[name="upload"]').hide(); + self.el.find('input[name="add-resource-upload"]').hide(); } }); }, @@ -844,8 +844,8 @@ CKAN.View.ResourceAddUrl = Backbone.View.extend({ var self = this; var newResource = new CKAN.Model.Resource({}); - this.el.find('input[name=save]').addClass("disabled"); - var urlVal = this.el.find('input[name=url]').val(); + this.el.find('input[name="add-resource-save"]').addClass("disabled"); + var urlVal = this.el.find('input[name="add-resource-url"]').val(); var qaEnabled = $.inArray('qa',CKAN.plugins)>=0; if(qaEnabled && this.options.mode=='file') { @@ -883,8 +883,8 @@ CKAN.View.ResourceAddUrl = Backbone.View.extend({ }, resetForm: function() { - this.el.find('input[name=save]').removeClass("disabled"); - this.el.find('input[name=url]').val(''); + this.el.find('input[name="add-resource-save"]').removeClass("disabled"); + this.el.find('input[name="add-resource-url"]').val(''); }, events: { diff --git a/ckan/public/scripts/templates.js b/ckan/public/scripts/templates.js index fdcd4a954de..b27cabc3387 100644 --- a/ckan/public/scripts/templates.js +++ b/ckan/public/scripts/templates.js @@ -11,7 +11,7 @@ CKAN.Templates.resourceUpload = ' \ \
    \
    \ - \ + \ \ \ '; diff --git a/ckan/templates/package/new_package_form.html b/ckan/templates/package/new_package_form.html index 53a6dcf28fa..bd5d751c458 100644 --- a/ckan/templates/package/new_package_form.html +++ b/ckan/templates/package/new_package_form.html @@ -160,15 +160,15 @@

    Errors in form

    From ab8c55f6f72679a343d754c408e6411aca956194 Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Fri, 30 Mar 2012 17:26:56 +0100 Subject: [PATCH 10/13] [#1788]: Glitchfixing deprecated method calls. --- ckan/public/scripts/application.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ckan/public/scripts/application.js b/ckan/public/scripts/application.js index 1b2ed29335a..a6a191051ed 100644 --- a/ckan/public/scripts/application.js +++ b/ckan/public/scripts/application.js @@ -806,13 +806,12 @@ CKAN.View.ResourceAddUpload = Backbone.View.extend({ error: function(model, error) { var msg = 'Filed uploaded OK but error adding resource: ' + error + '.'; msg += 'You may need to create a resource directly. Uploaded file at: ' + data._location; - CKAN.View.flash(msg, 'error'); + self.setMessage(msg, 'error'); } } ); self.collection.add(newResource); self.setMessage('File uploaded OK and resource added', 'success'); - CKAN.View.flash('File uploaded OK and resource added'); } }); }, From e1e8cc6b5911b03d4d83f7375e189c06564132ca Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Fri, 30 Mar 2012 19:05:39 +0100 Subject: [PATCH 11/13] [#1788]: Refactored resource editor to be flattened into the page. --- ckan/public/css/style.css | 184 +++++++------------ ckan/public/scripts/application.js | 2 - ckan/templates/package/new_package_form.html | 79 ++++---- 3 files changed, 113 insertions(+), 152 deletions(-) diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index 9bbbd99b7d1..acf78503b7b 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -1228,52 +1228,33 @@ body.authz form button { /* ==================== */ /* = Activity Streams = */ /* ==================== */ - .activity-stream .activity { padding-bottom:1em; - } .activity-stream .activity a { font-weight:bold; - -} -.activity-stream .activity .actor { - } .activity-stream .activity .verb { background-color:PapayaWhip; padding:.25em; margin:.25em; - -} -.activity-stream .activity .object { - -} -.activity-stream .activity .target { - } .activity-stream .activity .date { color:#999; - } - - - - - -/* Dev */ +/* ===================== */ +/* == Resource Editor == */ +/* ===================== */ fieldset#resources { - min-height: 230px; margin-bottom: 40px; - position: relative; padding: 0; } +body.editresources fieldset#resources > legend, body.editresources fieldset#resources > .instructions { display: none; } fieldset#resources > .instructions { - width: 300px; padding: 12px 12px 2px 12px; } .resource-list { @@ -1282,114 +1263,60 @@ fieldset#resources > .instructions { margin: 0; } .resource-list li { - background: #fff; - border: 0; - border: 1px solid #eee; - border-top-color: transparent; - border-left-color: transparent; - border-right: 0; - position: relative; - margin: 0 20px 0 0; - z-index: 1; white-space: nowrap; overflow: hidden; - font-weight: bold; + background: #fff; + border-right: 1px solid transparent; + border-left: 1px solid transparent; + border-top: 1px solid transparent; + border-bottom: 1px solid #eee; + margin-bottom: 0; + border-radius: 5px; +} +.resource-list li:hover { + background-color: #f7f7f7; } .resource-list li:last-child { border-bottom-color: transparent; } +.resource-list li.active { + border-color: #888; + background-color: #f0f0f0; + margin-right: -34px; +} +/**/ .resource-list li a { display: block; padding: 5px 10px; color: #333; + border-right: 0; + z-index: 1; + font-weight: bold; } -.resource-list li:hover a{ +.resource-list li a:hover { color: #B22; -} -.resource-list li:hover { - background-color: #f7f7f7; -} -.resource-list a:hover { text-decoration: none; } -.resource-list li.active { - border-color: #888; - background-color: #f9f9f9; - margin-right: 0; -} - -/* HasErrors */ -.resource-list li.hasErrors { - border-color: #c00; - border-right: 1px solid #c00; -} -.resource-list li.active.hasErrors { - border-right: 0; -} -.resource-list li.hasErrors a { - color: #c00; -} -.resource-errors { - display: none; -} -.resource-errors dl { - margin-bottom: 0; -} -body.editresources .error-explanation { - /* Let JS render the resource errors inline */ - display: none; -} - - -/* While dragging.... */ -.resource-list-edit li.ui-sortable-helper { - box-shadow: 2px 2px 8px rgba(0,0,0,0.1); -} -.resource-list-edit li.ui-sortable-helper.active { - margin-right: 20px; - border-right: 1px solid #888; -} -.resource-list-add, +/* Resource-list-edit */ .resource-list-edit { - width: 350px; + padding-top: 10px; } +/* Resource-list-add */ .resource-list-add { margin-top: 20px; } .resource-list-add li { border-top: 1px solid #eee; -} -.resource-list-add li.active { - border-bottom-color: #888; -} -.resource-list-add li a { padding-left: 43px; } -.resource-list-edit li a { - padding-left: 0; -} - -button.resource-edit-delete { - margin-top: 10px; - float: right; -} - - /* Right-hand-side edit resource panel */ -/* ----------------------------------- */ .resource-panel { background: #f9f9f9; border: 1px solid #888; - box-shadow: 2px 2px 4px #888; - margin-bottom: 20px; - position: absolute; - left: 349px; - top: 0px; padding: 10px 20px; - min-height: 100px; - min-width: 530px; + position: relative; } .resource-panel .resource-panel-close { position: absolute; @@ -1399,28 +1326,28 @@ button.resource-edit-delete { padding: 0; text-align: center; } -.resource-panel .hint { - font-size: 11px; -} -.resource-details input[type="text"] { +.resource-panel input[type="text"] { width: 397px; } -.resource-add input[type="text"] { - width: 280px; +.resource-panel .markdown-editor { + width: 390px; } -.resource-details .markdown-editor { - width: 30em; +.resource-panel textarea { + height: 90px; } -.resource-details .control-group { +.resource-panel .control-group { margin-bottom: 3px; } -.resource-details textarea { - width: 340px; - height: 90px; +.resource-panel .hint { + font-size: 11px; } -.resource-details.resource-add { +.resource-panel .resource-add { + min-height: 140px; margin-bottom: 30px; } +.resource-panel .resource-add input[type="text"] { + width: 280px; +} /* Resource extra fields */ /* --------------------- */ @@ -1438,3 +1365,32 @@ button.resource-edit-delete { width: 164px; } + +button.resource-edit-delete { + margin-top: 10px; + float: right; +} + + + +/* HasErrors */ +.resource-list li.hasErrors { + border-color: #c00; + border-right: 1px solid #c00; +} +.resource-list li.active.hasErrors { + border-right: 0; +} +.resource-list li.hasErrors a { + color: #c00; +} +.resource-errors { + display: none; +} +.resource-errors dl { + margin-bottom: 0; +} +body.editresources .error-explanation { + /* Let JS render the resource errors inline */ + display: none; +} diff --git a/ckan/public/scripts/application.js b/ckan/public/scripts/application.js index a6a191051ed..1906cd52668 100644 --- a/ckan/public/scripts/application.js +++ b/ckan/public/scripts/application.js @@ -345,7 +345,6 @@ CKAN.View.ResourceEditor = Backbone.View.extend({ this.el.find('.resource-details.resource-add').show(); addLi.addClass('active'); panel.show(); - panel.css('top', Math.max(0, addLi.position().top + addLi.height() - panel.height())); }, /* * Close the panel on the right. @@ -561,7 +560,6 @@ CKAN.View.Resource = Backbone.View.extend({ this.table.show(); this.table.find('.js-resource-edit-name').focus(); this.li.addClass('active'); - panel.css('top', Math.max(0, this.li.position().top+this.li.height() - panel.height())); }, /* * Called when my delete button is clicked. Calls back to the parent diff --git a/ckan/templates/package/new_package_form.html b/ckan/templates/package/new_package_form.html index bd5d751c458..091cd4c6644 100644 --- a/ckan/templates/package/new_package_form.html +++ b/ckan/templates/package/new_package_form.html @@ -134,6 +134,49 @@

    Errors in form

    Upload or link data files, APIs and other materials related to your dataset.

    +
    + +
    + +
    +
    +
    @@ -143,42 +186,6 @@

    Errors in form

    -
      -
    - -
    From c2b1089ef26f7777cfa8e7b2cf533c2c1ec2725f Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Fri, 30 Mar 2012 19:11:13 +0100 Subject: [PATCH 12/13] [#1788]: Visually connecting elements on the editresources page. --- ckan/public/css/style.css | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index acf78503b7b..efba8f6c2f3 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -1271,7 +1271,8 @@ fieldset#resources > .instructions { border-top: 1px solid transparent; border-bottom: 1px solid #eee; margin-bottom: 0; - border-radius: 5px; + position: relative; + z-index: 1; } .resource-list li:hover { background-color: #f7f7f7; @@ -1281,8 +1282,9 @@ fieldset#resources > .instructions { } .resource-list li.active { border-color: #888; - background-color: #f0f0f0; - margin-right: -34px; + border-right-color: #f9f9f9; + background-color: #f9f9f9; + margin-right: -21px; } /**/ .resource-list li a { @@ -1290,7 +1292,6 @@ fieldset#resources > .instructions { padding: 5px 10px; color: #333; border-right: 0; - z-index: 1; font-weight: bold; } .resource-list li a:hover { From 9b5564b59c50c4ad46b09136244b4548de8f52bb Mon Sep 17 00:00:00 2001 From: Tom Rees Date: Fri, 30 Mar 2012 19:37:13 +0100 Subject: [PATCH 13/13] [#1788]: minor style fixes. --- ckan/public/css/style.css | 11 +++++++---- ckan/templates/package/new_package_form.html | 5 +---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index efba8f6c2f3..354734e3795 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -415,8 +415,7 @@ ul.no-break li { /* ======================= */ div.form-actions p.hints { width: 50%; - float: right; - margin: 0; + margin: 10px 0 0 0; } @@ -937,8 +936,6 @@ span.extras-label { .dataset-create-form fieldset#resources, .dataset-create-form fieldset#summary { display: block; - padding-bottom: 0; - margin-bottom: 0; } .dataset-create-form .homepage-field, .dataset-create-form .tags-field { @@ -1303,6 +1300,12 @@ fieldset#resources > .instructions { .resource-list-edit { padding-top: 10px; } +/* While dragging.... */ +.resource-list-edit li.ui-sortable-helper { + border-color: #888; + box-shadow: 2px 2px 8px rgba(0,0,0,0.1); +} + /* Resource-list-add */ .resource-list-add { margin-top: 20px; diff --git a/ckan/templates/package/new_package_form.html b/ckan/templates/package/new_package_form.html index efb7b07e799..0323710cf62 100644 --- a/ckan/templates/package/new_package_form.html +++ b/ckan/templates/package/new_package_form.html @@ -305,10 +305,7 @@

    Errors in form

    -

    - Important: By submitting content, you agree to release your contributions under the Open Database License. Please refrain from editing this page if you are not happy to do this. -

    -
    +

    Important: By submitting content, you agree to release your contributions under the Open Database License. Please refrain from editing this page if you are not happy to do this.