views:

737

answers:

4

I have some html

<input type="text" name="name" value="" id="name">
<div id="preview"></div>

The rules for entry into the field:

  • Letters A-Z a-z 0-9 space and dash, no other characters allowed
  • Entry of forbidden characters should do nothing

The rules for the div:

  • Show each characters as it is entered into the input field
  • Do not show characters that are forbidden
  • When a space is encountered, show it as a dash

One of my 10 or so efforts: http://tr.im/ydyU

I have had various potions working, not working, or misbehaving. This version seems to work in all cases I can test other than backspace/delete is non functional. Only tested in Safari so far.

There are other "gotcha" areas, like entering in text in-between already entered text, select all, using the arrow keys, all these play a role in this problem.

 $(document).ready(function(){
  $('#name').keypress(function(e) {
   // get key pressed
   var c = String.fromCharCode(e.which);
   // var d = e.keyCode? e.keyCode : e.charCode; // this seems to catch arrow and delete better than jQuery's way (e.which)
   // match against allowed set and fail if no match
   var allowed = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890- ';
   if (e.which != 8 && allowed.indexOf(c) < 0) return false; // d !== 37 && d != 39 && d != 46 && 
   // just replace spaces in the preview
   window.setTimeout(function() {$('#preview').text($('#name').val().replace(/ /g, '-'));}, 1);
  });
 });

If there is a way to put a monetary bounty on this post, let me know. Yes, that is where I am at with this one :)

A: 

Try this: 1. When key down, copy the previous TextField value. 2. When key up, use RegEx to validate the text (something like /^[a-zA-Z0-9 -]*$/), if unmatch, replace the value with the old one.

Here is the code:

var ValidPattern = /^[a-zA-Z0-9\- ]*$/;
$(document).ready(function(){
    $('#name').keydown(function(e) {
     var aValue = $('#name').val();
     $('#name').attr("oldValue", aValue);

     return true;
    });
    $('#name').keyup(function(e) {
     var aValue   = $('#name').val();
     var aIsMatch = aValue.search(ValidPattern) != -1;
     if(aIsMatch) {
      $('#preview').text(aValue);
     } else {
      var aOldValue = $('#name').attr("oldValue");
      $('#name')   .val (aOldValue);
      $('#preview').text(aOldValue);
     }
    });
});

Try it.

NawaMan
Thanks! Very close, I tried something a lot like this earlier today. I see strange behavior on chars that are not part of ValidPattern, you get a glimpse of the char, and then it disappears. Do this in the middle of a string, and the cursor jumps to the end.This is the closest I have come with keydown. Had this idea not been inspired by http://twitter.com/signup for their username field, I would have moved on :) However, seeing they managed perfect execution, I am wondering how, their JS is pretty out there though.
Well the time different is because of keydown and keyup. To eliminate that, you can only use key press and save the old value at the latest press.
NawaMan
I modify the code and it seems like it will have problem but in the lesser degree. Note: It seems like keypress cannot be used so we need to use keyup and there is a small delay from displaying the char until keyup is executed.Here:var ValidPattern = /^[a-zA-Z0-9\- ]*$/;$(document).ready(function() { $('#name').keyup(function(e) { var aValue = $('#name').val(); var aIsMatch = aValue.search(ValidPattern) != -1; if(aIsMatch) $('#preview').text(aValue); else $('#name').val($('#name').attr("oldValue")); // Save as old Value $('#name').attr("oldValue", aValue); });});
NawaMan
Thanks again. Strange behavior on comma char, which is allowed, if you press slowly and repeatedly. A few other chars are the same. I am getting the impression this is just not solvable without a lot of research into key event listening.
A: 

I think the best method will be to keep a button and after entering the text inside the text box and clicking on the button show it in the div. It will be much more easier and user friendly.

It would be better not to try hindering the default actions of a user with the keyboard.

rahul
+2  A: 

I tested the following in Firefox, Safari and Internet Explorer. Unless I didn't fully understand your objective, I believe this should solve your problem.

I ended up writing a jQuery plugin to handle the input caret position. The plugin source is included below, or available on the jQuery plugin site (http://plugins.jquery.com/project/caret-range).

$(document).ready(function () {
    var InvalidPattern = /[^a-z0-9\- ]+/gi;
    var SpacePattern = / /g;

    var name = $("#name");
    var preview = $("#preview");

    var callback = function (e) {
     setTimeout(function () {
      // Get range and length to restore caret position
      var range = name.caret();
      var len = name.val().length;

      // Blur element to minimize visibility of caret jumping
      name.get(0).blur();

      // Remove invalid characters, and update preview
      name.val(name.val().replace(InvalidPattern, ""));
      preview.text(name.val().replace(SpacePattern, "-"));

      // Restore caret position
      var diff = len - name.val().length;
      name.caret(range.start - diff, range.end - diff);
     }, 0);
    };

    name.keypress(callback);
    name.keydown(callback); // Needed by IE to update preview for Delete and Backspace
});

/*
 * jQuery Caret Range plugin
 * Copyright (c) 2009 Matt Zabriskie
 * Released under the MIT and GPL licenses.
 */
(function($) {
    $.extend($.fn, {
     caret: function (start, end) {
      var elem = this[0];

      if (elem) {       
       // get caret range
       if (typeof start == "undefined") {
        if (elem.selectionStart) {
         start = elem.selectionStart;
         end = elem.selectionEnd;
        }
        else if (document.selection) {
         var val = this.val();
         var range = document.selection.createRange().duplicate();
         range.moveEnd("character", val.length)
         start = (range.text == "" ? val.length : val.lastIndexOf(range.text));

         range = document.selection.createRange().duplicate();
         range.moveStart("character", -val.length);
         end = range.text.length;
        }
       }
       // set caret range
       else {
        var val = this.val();

        if (typeof start != "number") start = -1;
        if (typeof end != "number") end = -1;
        if (start < 0) start = 0;
        if (end > val.length) end = val.length;
        if (end < start) end = start;
        if (start > end) start = end;

        elem.focus();

        if (elem.selectionStart) {
         elem.selectionStart = start;
         elem.selectionEnd = end;
        }
        else if (document.selection) {
         var range = elem.createTextRange();
         range.collapse(true);
         range.moveStart("character", start);
         range.moveEnd("character", end - start);
         range.select();
        }
       }

       return {start:start, end:end};
      }
     }
    });
})(jQuery);
mzabriskie
Realy nice, thanks. One of the closest I have seen to making it all work. In Safari, I have the flash of a character that is in the 'InvalidPattern' show up, then it is removed. By all means, not a huge deal, and this is excellent, though I still wonder how others are pulling it off. I see you posted a second example below, let me try that one. Thank you!
+1  A: 

After tinkering around I have refactored my previous solution. This version should behave identical to Twitter. I am keeping my old answer alive simply b/c it is technically valid, and this allows comparing the different approaches.

$(document).ready(function () {
    var SpacePattern = / /g;

    var name = $("#name");
    var preview = $("#preview");

    var updatePreview = function () {
     preview.text(name.val().replace(SpacePattern, "-"));
    };

    name.keypress(function (e) {
     if (e.which > 0 && // check that key code exists
      e.which != 8 && // allow backspace
      e.which != 32 && e.which != 45 && // allow space and dash
      !(e.which >= 48 && e.which <= 57) && // allow 0-9
      !(e.which >= 65 && e.which <= 90) && // allow A-Z
      !(e.which >= 97 && e.which <= 121)   // allow a-z
      ) {
      e.preventDefault();
     }
     else {
      setTimeout(updatePreview, 0);
     }
    });

    name.keyup(updatePreview); // Needed by IE for Delete and Backspace keys
});
mzabriskie
This 100% nails it! Perfect. I had a few suggestions on another forums to look at setTimeout, which I was not aware even existed until the other day. I want to go through this a little more closely, but also wanted to thank you and give you the feedback that it worked. Not many even understood the issue and some of the challenges, thanks so much for looking into this. I will post back if there is something I do not understand, but it looks pretty clear, though I am not sure how the setTimeout() works with this just yet. Thank you again.
Glad to be of help. If you have any further questions feel free to ask.
mzabriskie