diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index 2e9a93863c2..6c828f74314 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -1405,6 +1405,18 @@ body.editresources .error-explanation { margin-bottom: 0; } +.popover-title { + margin-bottom: 0; +} + +.popover-inner { + background: #aaa; +} + +.popover.right .arrow { + border-right-color: #aaa; +} + /* Chosen Form Styles */ .chzn-container-single { @@ -1420,10 +1432,27 @@ body.editresources .error-explanation { color: #808080; } +.related-help { + opacity: 0.3; + position: relative; + top: 2px; + cursor: pointer; +} + +.thumbnails li { + z-index: 0; + position: relative; + background-color: #fff; +} + .thumbnails li:nth-of-type(5n) { clear: left; } +.thumbnails li.expanded-description { + z-index: 1; +} + .thumbnail .heading { font-weight: bold; } @@ -1444,6 +1473,19 @@ body.editresources .error-explanation { height: auto; } +.thumbnail .close { + padding-bottom: 7px; + padding-top: 2px; + position: relative; + top: -1px; + right: -1px; + display: none; +} + +.thumbnail:hover .close { + display: block; +} + .thumbnail .empty { color: #ccc; } diff --git a/ckan/public/scripts/application.js b/ckan/public/scripts/application.js index 0d3628ebf76..6e00004542a 100644 --- a/ckan/public/scripts/application.js +++ b/ckan/public/scripts/application.js @@ -133,10 +133,23 @@ CKAN.Utils = CKAN.Utils || {}; jQuery.fn.truncate = function (max, suffix) { return this.each(function () { var element = jQuery(this), - cached = element.text(), - length = max || element.data('truncate') || 30, - text = cached.slice(0, length), - expand = jQuery('').text(suffix || '»'); + isTruncated = element.hasClass('truncated'), + cached, length, text, expand; + + if (isTruncated) { + element.html(element.data('truncate:' + (max === 'expand' ? 'original' : 'truncated'))); + return; + } + + cached = element.text(); + length = max || element.data('truncate') || 30; + text = cached.slice(0, length); + expand = jQuery('').text(suffix || '»'); + + // Bail early if there is nothing to truncate. + if (cached.length < length) { + return; + } // Try to truncate to nearest full word. while ((/\S/).test(text[text.length - 1])) { @@ -144,12 +157,15 @@ jQuery.fn.truncate = function (max, suffix) { } element.html(jQuery.trim(text)); - expand.appendTo(element.append(' ')); expand.click(function (event) { event.preventDefault(); - element.text(cached); + element.html(cached); }); + + element.addClass('truncated'); + element.data('truncate:original', cached); + element.data('truncate:truncated', element.html()); }); }; @@ -1141,13 +1157,30 @@ CKAN.Utils = function($, my) { my.relatedSetup = function(form) { + $('[rel=popover]').popover(); + function addAlert(msg) { $('
').html(msg).hide().prependTo(form).fadeIn(); } + function relatedRequest(action, method, data) { + return $.ajax({ + type: method, + dataType: 'json', + contentType: 'application/json', + url: CKAN.SITE_URL + '/api/3/action/related_' + action, + data: data ? JSON.stringify(data) : undefined, + error: function(err, txt, w) { + // This needs to be far more informative. + addAlert('Error: Unable to ' + action + ' related item'); + } + }); + } + // Center thumbnails vertically. - $('.related-items').each(function () { - var item = $(this); + var relatedItems = $('.related-items'); + relatedItems.find('li').each(function () { + var item = $(this), description = item.find('.description'); function vertiallyAlign() { var img = $(this), @@ -1161,7 +1194,47 @@ CKAN.Utils = function($, my) { } item.find('img').load(vertiallyAlign); - item.find('.description').truncate(); + description.data('height', description.height()).truncate(); + }); + + relatedItems.on('mouseenter mouseleave', '.description.truncated', function (event) { + var isEnter = event.type === 'mouseenter' + description = $(this) + timer = description.data('hover-intent'); + + function update() { + var parent = description.parents('li:first'), + difference = description.data('height') - description.height(); + + description.truncate(isEnter ? 'expand' : 'collapse'); + parent.toggleClass('expanded-description', isEnter); + + // Adjust the bottom margin of the item relative to it's current value + // to allow the description to expand without breaking the grid. + parent.css('margin-bottom', isEnter ? '-=' + difference + 'px' : ''); + description.removeData('hover-intent'); + } + + if (!isEnter && timer) { + // User has moused out in the time set so cancel the action. + description.removeData('hover-intent'); + return clearTimeout(timer); + } else if (!isEnter && !timer) { + update(); + } else { + // Delay the hover action slightly to wait to see if the user mouses + // out again. This prevents unwanted actions. + description.data('hover-intent', setTimeout(update, 200)); + } + }); + + // Add a handler for the delete buttons. + relatedItems.on('click', '[data-action=delete]', function (event) { + var id = $(this).data('relatedId'); + relatedRequest('delete', 'POST', {id: id}).done(function () { + $('#related-item-' + id).remove(); + }); + event.preventDefault(); }); $(form).submit(function (event) { @@ -1186,18 +1259,10 @@ CKAN.Utils = function($, my) { return; } - $.ajax({ - type: this.method, - url: CKAN.SITE_URL + '/api/3/action/related_create', - data: JSON.stringify(data), - success: function (data) { - window.location.reload(); - }, - error: function(err, txt, w) { - // This needs to be far more informative. - addAlert('Error: Unable to add related item'); - } - }); + relatedRequest('create', this.method, data).done(function () { + // TODO: Insert item dynamically. + window.location.reload(); + }); }); }; diff --git a/ckan/templates/_util.html b/ckan/templates/_util.html index 5aa7c077c7e..8c2991877fd 100644 --- a/ckan/templates/_util.html +++ b/ckan/templates/_util.html @@ -126,16 +126,16 @@ -
  • +