Skip to content

Commit

Permalink
[#1506][js][l]: UrlEditor is a backbone model; various ux tweaks
Browse files Browse the repository at this point in the history
  • Loading branch information
teajaymars committed Mar 1, 2012
1 parent db865be commit fd77374
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 98 deletions.
204 changes: 110 additions & 94 deletions ckan/public/scripts/application.js
Expand Up @@ -128,109 +128,125 @@ var CKAN = CKAN || {};
/* ============================== */
CKAN.View.UrlEditor = Backbone.View.extend({
initialize: function() {
var slugType = this.options.slugType;
var editMode = this.options.editMode;

if (!editMode) var editMode = false;
// Page elements to hook onto
var titleInput = $('.js-title');
var urlSuffix = $('.js-url-suffix');
var urlInput = $('.js-url-input');
var validMsg = $('.js-url-is-valid');

if (titleInput.length==0) throw "No titleInput found.";
if (urlSuffix.length==0) throw "No urlSuffix found.";
if (urlInput.length==0) throw "No urlInput found.";
if (validMsg.length==0) throw "No validMsg found.";

var api_url = CKAN.SITE_URL + '/api/2/util/is_slug_valid';
// (make length less than max, in case we need a few for '_' chars to de-clash slugs.)
var MAX_SLUG_LENGTH = 90;

var titleChanged = function() {
var lastTitle = "";
var regexToHyphen = [ new RegExp('[ .:/_]', 'g'),
new RegExp('[^a-zA-Z0-9-_]', 'g'),
new RegExp('-+', 'g')];
var regexToDelete = [ new RegExp('^-*', 'g'),
new RegExp('-*$', 'g')];

var titleToSlug = function(title) {
var slug = title;
$.each(regexToHyphen, function(idx,regex) { slug = slug.replace(regex, '-'); });
$.each(regexToDelete, function(idx,regex) { slug = slug.replace(regex, ''); });
slug = slug.toLowerCase();

if (slug.length<MAX_SLUG_LENGTH) {
slug=slug.substring(0,MAX_SLUG_LENGTH);
}
return slug;
};
_.bindAll(this,'titleToSlug','titleChanged','urlChanged','checkSlugIsValid','toEditMode','apiCallback');

// Called when the title changes
return function() {
var title = titleInput.val();
if (title == lastTitle) return;
lastTitle = title;
// Initial state
this.updateTimer = null;
this.titleInput = $('.js-title');
this.urlSuffix = $('.js-url-suffix');
this.urlInput = $('.js-url-input');
this.validMsg = $('.js-url-is-valid');
this.lastTitle = "";
this.disableTitleChanged = false;

// Settings
this.regexToHyphen = [ new RegExp('[ .:/_]', 'g'),
new RegExp('[^a-zA-Z0-9-_]', 'g'),
new RegExp('-+', 'g')];
this.regexToDelete = [ new RegExp('^-*', 'g'),
new RegExp('-*$', 'g')];

// Default options
if (!this.options.apiUrl)
this.options.apiUrl = CKAN.SITE_URL + '/api/2/util/is_slug_valid';
if (!this.options.MAX_SLUG_LENGTH)
this.options.MAX_SLUG_LENGTH = 90;
if (!this.options.editMode)
this.options.editMode = false;

if (this.options.editMode) {
this.originalUrl = this.urlInput.val();
}

slug = titleToSlug(title);
urlInput.val(slug);
urlInput.change();
};
}();
// Hook title changes to the input box
CKAN.Utils.bindInputChanges(this.titleInput, this.titleChanged);
CKAN.Utils.bindInputChanges(this.urlInput, this.urlChanged);
$('.url-edit').click(this.toEditMode);

var urlChanged = function() {
var timer = null;
// Set up the form
this.urlChanged();
if (this.options.editMode) {
this.toEditMode();
this.validMsg.html('');
}
},

var checkSlugValid = function(slug) {
$.ajax({
url: api_url,
data: 'type='+slugType+'&slug=' + slug,
dataType: 'jsonp',
type: 'get',
jsonpCallback: 'callback',
success: function (data) {
if (data.valid) {
validMsg.html('<span style="font-weight: bold; color: #0c0">'+CKAN.Strings.urlIsAvailable+'</span>');
} else {
validMsg.html('<span style="font-weight: bold; color: #c00">'+CKAN.Strings.urlIsNotAvailable+'</span>');
}
}
});
}
toEditMode: function(event) {
$('.js-url-viewmode').hide();
$('.js-url-editmode').show();
if (event) {
// If we clicked a link, highlight the input
event.preventDefault();
this.urlInput.select();
this.urlInput.focus();
}
// Disable automatic slug generation
this.disableTitleChanged = true;
},

return function() {
slug = urlInput.val();
urlSuffix.html('<span>'+slug+'</span>');
if (timer) clearTimeout(timer);
if (slug.length<2) {
validMsg.html('<span style="font-weight: bold; color: #444;">'+CKAN.Strings.urlIsTooShort+'</span>');
}
else {
validMsg.html('<span style="color: #777;">'+CKAN.Strings.checking+'</span>');
timer = setTimeout(function () {
checkSlugValid(slug);
}, 200);
}
};
}();
titleToSlug: function(title) {
var slug = title;
$.each(this.regexToHyphen, function(idx,regex) { slug = slug.replace(regex, '-'); });
$.each(this.regexToDelete, function(idx,regex) { slug = slug.replace(regex, ''); });
slug = slug.toLowerCase();

var editLink = $('.js-url-editlink');
editLink.show();
// Hook title changes to the input box
CKAN.Utils.bindInputChanges(titleInput, titleChanged);
CKAN.Utils.bindInputChanges(urlInput, urlChanged);
// Set up the form
urlChanged();
if (slug.length<this.options.MAX_SLUG_LENGTH) {
slug=slug.substring(0,this.options.MAX_SLUG_LENGTH);
}
return slug;
},

editLink.live('click',function(e) {
e.preventDefault();
$('.js-url-viewmode').hide();
$('.js-url-editmode').show();
urlInput.select();
urlInput.focus();
/* Called when the title changes */
titleChanged: function() {
if (this.disableTitleChanged) return;
var title = this.titleInput.val();
if (title == this.lastTitle) return;
this.lastTitle = title;

slug = this.titleToSlug(title);
this.urlInput.val(slug);
this.urlInput.change();
},

/* Called when the url is changed */
urlChanged: function() {
var slug = this.urlInput.val();
this.urlSuffix.html('<span>'+slug+'</span>');
if (this.updateTimer) clearTimeout(this.updateTimer);
if (slug.length<2) {
this.validMsg.html('<span style="font-weight: bold; color: #444;">'+CKAN.Strings.urlIsTooShort+'</span>');
}
else if (this.options.editMode && slug==this.originalUrl) {
this.validMsg.html('<span style="font-weight: bold; color: #000;">'+CKAN.Strings.urlIsUnchanged+'</span>');
}
else {
this.validMsg.html('<span style="color: #777;">'+CKAN.Strings.checking+'</span>');
var self = this;
this.updateTimer = setTimeout(function () {
self.checkSlugIsValid(slug);
}, 200);
}
},

checkSlugIsValid: function(slug) {
$.ajax({
url: this.options.apiUrl,
data: 'type='+this.options.slugType+'&slug=' + slug,
dataType: 'jsonp',
type: 'get',
jsonpCallback: 'callback',
success: this.apiCallback
});
},

/* Called when the slug-validator gets back to us */
apiCallback: function(data) {
if (data.valid) {
this.validMsg.html('<span style="font-weight: bold; color: #0c0">'+CKAN.Strings.urlIsAvailable+'</span>');
} else {
this.validMsg.html('<span style="font-weight: bold; color: #c00">'+CKAN.Strings.urlIsNotAvailable+'</span>');
}
},
});


Expand Down
4 changes: 2 additions & 2 deletions ckan/templates/group/new_group_form.html
Expand Up @@ -8,7 +8,7 @@
<h2>Errors in form</h2>
<p>The form contains invalid entries:</p>
<ul>
<li py:for="key, error in error_summary.items()">${"%s: %s" % (key, error)}</li>
<li py:for="key, error in error_summary.items()">${"%s: %s" % (key if not key=='Name' else 'URL', error)}</li>
</ul>
</div>

Expand All @@ -19,7 +19,7 @@ <h2>Errors in form</h2>

<dt><label class="field_opt" for="title">Url</label></dt>
<dd class="name-field">
<span class="js-url-text url-text">${h.url(controller='group', action='index')+'/'}<span class="js-url-viewmode js-url-suffix">&nbsp;</span><a style="display: none;" href="#" class="url-edit js-url-editlink js-url-viewmode">(edit)</a></span>
<span class="js-url-text url-text">${h.url(controller='group', action='index')+'/'}<span class="js-url-viewmode js-url-suffix">&nbsp;</span><a href="#" class="url-edit js-url-viewmode">(edit)</a></span>
<input style="display: none;" id="name" maxlength="100" name="name" type="text" class="url-input js-url-editmode js-url-input" value="${data.get('name', '')}" />
<p class="js-url-is-valid">&nbsp;</p>
</dd>
Expand Down
1 change: 1 addition & 0 deletions ckan/templates/js_strings.html
Expand Up @@ -16,6 +16,7 @@
CKAN.Strings.helloWorld = "${_('Hello there, world!')}";
CKAN.Strings.checking = "${_('Checking...')}";
CKAN.Strings.urlIsTooShort = "${_('Type at least two characters...')}";
CKAN.Strings.urlIsUnchanged = "${_('This is the current URL.')}";
CKAN.Strings.urlIsAvailable = "${_('This URL is available!')}";
CKAN.Strings.urlIsNotAvailable = "${_('This URL is already used, please use a different one.')}";
CKAN.Strings.bracketsNone = "${_('(none)')}";
Expand Down
4 changes: 2 additions & 2 deletions ckan/templates/package/new_package_form.html
Expand Up @@ -9,7 +9,7 @@
<h2>Errors in form</h2>
<p>The form contains invalid entries:</p>
<ul>
<li py:for="key, error in error_summary.items()">${"%s: %s" % (key, error)}
<li py:for="key, error in error_summary.items()">${"%s: %s" % (key if not key=='Name' else 'URL', error)}
<py:if test="key=='Resources'">
<ul>
<py:for each="idx, errordict in enumerate(errors.get('resources', []))">
Expand Down Expand Up @@ -42,7 +42,7 @@ <h2>Errors in form</h2>

<dt class="name-label"><label class="field_req" for="name">Url</label></dt>
<dd class="name-field">
<span class="url-text">${h.url(controller='package', action='index')+'/'}<span class="js-url-viewmode js-url-suffix">&nbsp;</span><a href="#" style="display: none;" class="url-edit js-url-editlink js-url-viewmode">(edit)</a></span>
<span class="url-text">${h.url(controller='package', action='index')+'/'}<span class="js-url-viewmode js-url-suffix">&nbsp;</span><a href="#" class="url-edit js-url-viewmode">(edit)</a></span>
<input style="display: none;" id="name" maxlength="100" name="name" type="text" class="url-input js-url-editmode js-url-input" value="${data.get('name', '')}" />
<p class="js-url-is-valid">&nbsp;</p>
</dd>
Expand Down

0 comments on commit fd77374

Please sign in to comment.