webworka/Tagedit

improvements suggestions

Opened this issue · 6 comments

I've tweaked Tagedit a little bit for my own needs, here is what I came up with:

New features:

  • tag number cap
  • tag length limit
  • input validation through a regex
  • optional class to .tagedit-list when the input text field is focused (so you can mimic an input field with a css :focus selector)

Bug fix:

  • deep extend prevents breakKeyCodes to be overriden

What could be done:

  • make the tag cap / length limit optional
  • add a way of sending error/info messages (by passing a selector in options for instance)
  • indentation needs some clean-up

I have other improvements in my mind, but it needs more thinking. Tell me what you think!

Here is the diff with the version I'm using on my project:

1c1
< /*

---
> /*
52c52
<       options = $.extend(true, {

---
>       options = $.extend({
62a63,67
>           // additions
>           maxTags: 10,
>           maxLength: 30,
>           focusClass: null,
>           validationRule: '^[a-zA-Z0-9-_]+$',
98a104,105
>       var tagNumber = 0;
> 
99a107
>       var tagRegexp = new RegExp(options.validationRule);
126a135,140
>                       if (tagNumber >= options.maxTags) {
>                           alert("Maximum element number reached (" + options.maxTags + ")");
>                       } else {
>                           tagNumber++;
>                       }
> 
152c166
<           html += '<input type="text" name="'+baseName+'[]" value="" id="tagedit-input" disabled="disabled" class="tagedit-input-disabled" dir="'+options.direction+'"/>';

---
>           html += '<input type="text" name="'+baseName+'[]" value="" id="tagedit-input" maxlength="' +options.maxLength+ '" disabled="disabled" class="tagedit-input-disabled" dir="'+options.direction+'"/>';
163c177
<                       // Event ist triggert in case of choosing an item from the autocomplete, or finish the input

---
>                       // Event triggered when picking an item from the autocomplete list, or finishing the input
177a192,203
>                                   var tagString = $(this).val().replace(/\s{2,}/g, ' ');
> 
>                                   if ((tagNumber >= options.maxTags) ||
>                                       (tagString === ' ') ||
>                                       (tagRegexp.test(tagString) === false))
>                                   {
>                                       $(this).val('');
>                                       return;
>                                   }
> 
>                                   tagNumber++;
> 
180c206
<                                   html += '<span dir="'+options.direction+'">' + $(this).val() + '</span>';

---
>                                   html += '<span dir="'+options.direction+'">' + tagString + '</span>';
182c208
<                                   html += '<input type="hidden" name="'+name+'" value="'+$(this).val()+'" />';

---
>                                   html += '<input type="hidden" name="'+name+'" value="'+tagString+'" />';
202a229,230
>                                       if (tagNumber > 0) tagNumber--;
> 
251a280,289
>                       })
>                       .focusin(function() {
>                           if (options.focusClass != null) {
>                               $('.tagedit-list').addClass(options.focusClass);
>                           }
>                       })
>                       .focusout(function() {
>                           if (options.focusClass != null) {
>                               $('.tagedit-list').removeClass(options.focusClass);
>                           }
253d290
< 
261a299,300
>                           tagNumber--;
>                   
412c451
<               if(elementValue == compareValue) {

---
>               if(elementValue === compareValue) {

This is helpful, thanks. I was looking for a way to do input validation, and this gets me off to a good start. Your code doesn't seem to handle the case of editing a tag, however. I added the following to the doEdit() function (immediately after the line where textfield is defined in the finishEdit function) so that edits are validated, too:

var tagString = textfield.val().replace(/\s{2,}/g, ' ');
if ((tagString === ' ') ||
    (tagRegexp.test(tagString) === false)) {
  textfield.val('');
}

Thanks!

I added the following to disallow the user from entering characters which don't match the regex:

function isIllegalCharacter(characterCode) {
  return tagRegexp.test(String.fromCharCode(characterCode)) === false
}

I then call that function in the two keypress event handlers:

.keypress(function(event) {
            var code = event.keyCode > 0 ? event.keyCode : event.which;

            if ($.inArray(code, options.breakKeyCodes) > -1) {
              if ($(this).val().length > 0 && $('ul.ui-autocomplete #ui-active-menuitem').length == 0) {
                $(this).trigger('transformToTag');
              }
              event.preventDefault();
              return false;
            } else if (isIllegalCharacter(code)) {
              event.preventDefault();
              return false;
            }
            return true;
          })
.keypress(function(event) {
            switch (event.keyCode) {
              case 13: // RETURN
                event.preventDefault();
                $(this).parent().trigger('finishEdit');
                return false;
              case 27: // ESC
                event.preventDefault();
                $(this).parent().trigger('finishEdit', [true]);
                return false;
              default:
                // swallow invalid characters
                var code = event.keyCode > 0 ? event.keyCode : event.which;
                if (isIllegalCharacter(code)) {
                  event.preventDefault();
                  return false;
                }
            }
            return true;
          })

You'll of course want to do the regex check on the server side as well since this is purely UI gravy that any sufficiently savvy user could circumvent.

Chris

Yeah I don't allow tag editing in my project so that's why I missed it!

I tried to do what you suggest before but I haven't been able to prevent carets (^) from being typed or being used as diacritics (âô). I haven't been looking thoroughly into this issue though.

If you find a way to do this, I would be interested :)

Some other things that could be done to improve this plugin:

  • Follow JQuery plugin best practices listed there : http://docs.jquery.com/Plugins/Authoring. I did this in the version I'm using but since I've stripped out some features I don't know if it's worth something that I post it here.
  • The last part of the isNew function is awkward because it does an ajax call before validating a tag, which may cause some lag if the server is slow. Maybe this check could be optional?
else if (typeof options.autocompleteOptions.source === "string") {
  // Check also autocomplete values
  var autocompleteURL = options.autocompleteOptions.source;
  if (autocompleteURL.match(/\?/)) {
    autocompleteURL += '&';
  } else {
    autocompleteURL += '?';
  }

  autocompleteURL += 'term=' + value;
  $.ajax({
    async: false,
    url: autocompleteURL,
    dataType: 'json',
    complete: function (XMLHttpRequest, textStatus) {
    result = $.parseJSON(XMLHttpRequest.responseText);
  });
}
  • There is an issue with this part of code:
// close autocomplete
if(options.autocompleteOptions.source) {
  $(this).autocomplete( "close" );
}

The purpose of this, I guess, is to close the autocomplete list when you input a tag so, when you start typing again after the input has been cleared it shows up at the new input position.
The issue is hard to describe but I'll give it a try: let's suppose that if you type "ag" it triggers a list of choices with "agenda". You press enter really fast, creating a tag named "ag", then you retype "ag". Well sometimes the autocomplete list doesn't show up. Commenting $(this).autocomplete( "close" ); prevents this behavior but in this case the autocomplete list sometimes stays at the old input position. If someone finds out why that would be cool :)

  • It would be great to use a different selector like the element that contains the inputs with the '.tag' class. This way you would be able to pass the form selector directly, and most importantly to use many instances of this plugin in the same form (by enclosing groups of inputs in a div), which could be useful.

drenty said:

I tried to do what you suggest before but I haven't been able to prevent carets (^) from being typed or being used as diacritics (âô).

They key press event handler modifications I posted earlier prevent the caret from being typed, as long as your regex doesn't allow them. I'm using ^[a-zA-Z0-9-_ ]+$ so it "just works" for me. You can still type carets as diacritics, but as soon as you enter the tag (using one of the break codes) then the tag is rejected because it doesn't pass the regex.

In short, it all just works.

Chris

Does anyone of you can do a pull request with the changes?

I missed your reply. If it's still relevant I can do that.