1. Advertising
    y u no do it?

    Advertising (learn more)

    Advertise virtually anything here, with CPM banner ads, CPM email ads and CPC contextual links. You can target relevant areas of the site and show ads based on geographical location of the user if you wish.

    Starts at just $1 per CPM or $0.10 per CPC.

Limit how many words can be entered into an input

Discussion in 'JavaScript' started by qwikad.com, Dec 9, 2020.

  1. #1
    I'd like to use this little keyword function, the problem is I want to limit how many keywords can be entered. Adding maxlength="10" to the input doesn't do it. It limits how many characters can be entered, but not how many keywords.

    http://jsfiddle.net/bjBQZ/50/
    SEMrush
     
    Solved! View solution.
    qwikad.com, Dec 9, 2020 IP
    SEMrush
  2. hdewantara

    hdewantara Well-Known Member

    Messages:
    519
    Likes Received:
    47
    Best Answers:
    23
    Trophy Points:
    130
    #2
    Hi. Following may not be properly coded as I haven't used jQuery for long,

    First, I'll add one element to the fiddle:
    <div id="box">
        <ul>
            <li><input type="text" id="type"/></li>
        </ul>
    </div>
    <small>*put something in the input above, and press enter.</small>
    <br>count: <input type="text" id="count">
    HTML:
    Then let's modify js to something like:
    
    $('#box').on('click', function(ev) {
      var el = ev.target;
      if(el.nodeName == 'A'){
        $(el).parent().parent().remove();
        count--;
        $('#count').val(count);
      }
    });
    $('#type').keypress(function(e) {
            if(count > 5)
            e.preventDefault();
        else{
          if(e.which == 32) {//change to 32 for spacebar instead of enter
              var tx = $(this).val();
              if (tx) {
                  count++;
                  $('#count').val(count);
                  $(this).val('').parent().before('<li><span>'+tx+'<a href="javascript:void(0);">x</a></span></li>');
                  closer();
              }
          }
        }
    });
    Code (JavaScript):
     
    hdewantara, Dec 9, 2020 IP
  3. qwikad.com

    qwikad.com Illustrious Member Affiliate Manager

    Messages:
    7,022
    Likes Received:
    1,598
    Best Answers:
    27
    Trophy Points:
    475
    #3
    @hdewantara not sure if we are talking about the same thing. Here's your code:

    https://jsfiddle.net/0xu876jy/

    I do not need the count option and your code allows me to enter more than 10 keywords (it could be any number, but let's say it's 10).
     
    qwikad.com, Dec 9, 2020 IP
  4. sarahk

    sarahk iTamer Staff

    Messages:
    27,948
    Likes Received:
    4,333
    Best Answers:
    123
    Trophy Points:
    665
    #4
    this works

    $('#type').keypress(function(e) {
      if (e.which == 32) { //change to 32 for spacebar instead of enter
        var tx = $(this).val();
        if ($('#box ul li').length > 10) {
          alert('remove a tag before you add another');
        } else {
          if (tx) {
            $(this).val('').parent().before('<li><span>' + tx + '<a href="javascript:void(0);">x</a></span></li>');
            closer();
          }
        }
      }
    });
    Code (JavaScript):
    http://jsfiddle.net/bjBQZ/50/
     
    sarahk, Dec 9, 2020 IP
  5. qwikad.com

    qwikad.com Illustrious Member Affiliate Manager

    Messages:
    7,022
    Likes Received:
    1,598
    Best Answers:
    27
    Trophy Points:
    475
    #5
    But I can still enter words after I close that alert (or without the alert). I don't want a user to be able to enter anything after the 10th keyword. Is it possible?
     
    qwikad.com, Dec 9, 2020 IP
  6. sarahk

    sarahk iTamer Staff

    Messages:
    27,948
    Likes Received:
    4,333
    Best Answers:
    123
    Trophy Points:
    665
    #6
    Just add this

    if ($('#box ul li').length > 3) {
          alert('remove a tag before you add another');
          $('#type').attr('disabled', 'disabled');
        }
    Code (JavaScript):
    but you then need to listen for a tag delete event and removed the disabled bit.
     
    sarahk, Dec 9, 2020 IP
  7. qwikad.com

    qwikad.com Illustrious Member Affiliate Manager

    Messages:
    7,022
    Likes Received:
    1,598
    Best Answers:
    27
    Trophy Points:
    475
    #7
    But then everything stops working. Enter 5+ words and you'll se what I mean. I can't remove the entered keywords, everything is disabled.

    http://jsfiddle.net/0jc9zwm8/1/
     
    qwikad.com, Dec 9, 2020 IP
  8. sarahk

    sarahk iTamer Staff

    Messages:
    27,948
    Likes Received:
    4,333
    Best Answers:
    123
    Trophy Points:
    665
    #8
    That's because you've removed the closer() function.

    You could also do this

    $('#words').keypress(function(e) {
      if (e.which == 32) { //change to 32 for spacebar instead of enter
        var tx = $(this).val();
        if ($('#box ul li').length > 3) {
         $(this).val('')
          $('#words').attr('disabled', 'disabled');
        } else {
          if (tx) {
            $(this).val('').parent().before('<li><span>' + tx + '<a href="javascript:void(0);">x</a></span></li>');
            closer();
          }
        }
      }
    });
    function closer(){
        $('#box a').on('click', function() {
            $(this).parent().parent().remove();
        });
    }
    Code (JavaScript):
     
    sarahk, Dec 9, 2020 IP
  9. qwikad.com

    qwikad.com Illustrious Member Affiliate Manager

    Messages:
    7,022
    Likes Received:
    1,598
    Best Answers:
    27
    Trophy Points:
    475
    #9
    Ok, now it's almost working. You can add and remove keywords, but after reaching the limit and then removing the words, you can't enter anything again, it stays disabled: http://jsfiddle.net/c260gtop/
     
    qwikad.com, Dec 9, 2020 IP
  10. sarahk

    sarahk iTamer Staff

    Messages:
    27,948
    Likes Received:
    4,333
    Best Answers:
    123
    Trophy Points:
    665
    #10
    add the listener for the delete event
     
    sarahk, Dec 9, 2020 IP
  11. qwikad.com

    qwikad.com Illustrious Member Affiliate Manager

    Messages:
    7,022
    Likes Received:
    1,598
    Best Answers:
    27
    Trophy Points:
    475
    #11
    I tried this and that nothing is working. Jquery is not my forte.
     
    qwikad.com, Dec 10, 2020 IP
  12. #12
    try this

    function closer() {
      $('#box a').on('click', function() {
        $(this).parent().parent().remove();
        console.log('remove disabled');
        if ($('#box ul li').length <= 3) {
          $('#type').prop('disabled', false);
        }
      });
    }
    $('#type').keypress(function(e) {
      if (e.which == 32) { //change to 32 for spacebar instead of enter
        var tx = $(this).val();
        if ($('#box ul li').length > 3) {
          $(this).val('');
          console.log('add disabled');
          $('#type').prop('disabled', 'disabled');
        } else {
          if (tx) {
            $(this).val('').parent().before('<li><span>' + tx + '<a href="javascript:void(0);">x</a></span></li>');
            closer();
          }
        }
      }
    });
    Code (JavaScript):
     
    sarahk, Dec 10, 2020 IP
    qwikad.com likes this.
  13. qwikad.com

    qwikad.com Illustrious Member Affiliate Manager

    Messages:
    7,022
    Likes Received:
    1,598
    Best Answers:
    27
    Trophy Points:
    475
  14. deathshadow

    deathshadow Acclaimed Member

    Messages:
    9,635
    Likes Received:
    1,971
    Best Answers:
    249
    Trophy Points:
    515
    #14
    Laughing because:

    1) wrong forum area because

    2) none of JavaScript's flipping business in 2020.

    <input type="text" pattern="^(\s*\b\S+\b\s*){0,10}$">
    Code (markup):
    See my response to your asking the same thing on my site.
    https://forums.cutcodedown.com/index.php?topic=430.msg2385#msg2385

    This is 2020, stop using JS for things that don't need it.
     
    deathshadow, Dec 15, 2020 IP
  15. qwikad.com

    qwikad.com Illustrious Member Affiliate Manager

    Messages:
    7,022
    Likes Received:
    1,598
    Best Answers:
    27
    Trophy Points:
    475
    #15
    I did see your and coothead's replies, but it's not what I want the function to do. (I will however use the regex you suggested to ensure that only an X amount of words are recorded in the DB in case javascript is disabled).
    I like the JS version of it. The problem I have is it doesn't work on Samsung cell phones. Any key in Samsung and I assume in some other cell phones produces 229 and so does the space bar. So it's completely useless in some cell phones and from what I read there's no real way to address that. ( See my post about it here: https://stackoverflow.com/questions...in-textarea-doesnt-work-on-cell-phones-jquery ).
     
    qwikad.com, Dec 15, 2020 IP
  16. qwikad.com

    qwikad.com Illustrious Member Affiliate Manager

    Messages:
    7,022
    Likes Received:
    1,598
    Best Answers:
    27
    Trophy Points:
    475
    #16
    qwikad.com, Dec 16, 2020 IP
  17. deathshadow

    deathshadow Acclaimed Member

    Messages:
    9,635
    Likes Received:
    1,971
    Best Answers:
    249
    Trophy Points:
    515
    #17
    Yeah, I was about to suggest onInput before I refreshed the tab (left it open).

    Now that I can see what you're after, yeah JS is the better choice -- though as always the mind-numbing idiotic trash that is jQuery should be kicked to the curb.

    I see a number of potential "issues" that make this a bit of a usability headache. Nothing insurmountable though.

    1) you're copying the TEXT to the LI, but how are you going to extract that to send it to the server and/or operate on it? I'd suggest putting the values into a hidden input as name=something[] array. I'd use hidden input in ADDITION to the text since input don't dynamically size. (pain in the ass to style)

    2) if you navigate away from the input (blur) the copy should probably be made then as well.

    3) It should probably check BOTH enter and space... and using oninput won't trap enter.

    4) if you put this in an actual form, "enter" becomes a submit. As such to trap enter one will also want to event.preventDefault();

    5) When a word is removed the source input loses focus here in Vivaldi, but not in FF. I'd suggest doing a .focus() to be 100% sure that flies right.

    6) It's hardcoded to just one div/input. Would be nice if it could be coded to work with multiple inputs on one page. (something jQ sucks at)

    7) It also does WAY too many "querySelector" style junk inside the events, instead of walking the DOM. Again where jQ goes full Gungan promoting bad practices.

    8) I'd put the pattern on it anyways for scripting off graceful degradation. To that end the scripting only elements should NOT be in the markup either.

    Gimme a few minutes and I'll toss together something that fixes all that.
     
    Last edited: Dec 16, 2020
    deathshadow, Dec 16, 2020 IP
    qwikad.com likes this.
  18. deathshadow

    deathshadow Acclaimed Member

    Messages:
    9,635
    Likes Received:
    1,971
    Best Answers:
    249
    Trophy Points:
    515
    #18
    deathshadow, Dec 16, 2020 IP
    qwikad.com likes this.
  19. deathshadow

    deathshadow Acclaimed Member

    Messages:
    9,635
    Likes Received:
    1,971
    Best Answers:
    249
    Trophy Points:
    515
    #19
    Alright, lemme 'splain.

    First up I put it into a form so the output could be tested and to make sure the behavior didn't conflict:

    
    <form action="#" method="get">
    	<fieldset>
    		<label for="inputLimit_test">Describe our INPUT here</label><br>
    		<div class="inputLimit">
    			<input
    				data-input-limit="5"
    				id="inputLimit_test"
    				name="test"
    				pattern="^(\s*\b\S+\b\s*){0,5}$"
    				type="text"
    			>
    		<!-- .inputLimit --></div>
    		<p>
    			<em>Enter up to five words above. Space or Enter will separate words automatically.</em>
    		</p>
    		<button>submit</button>
    	</fieldset>
    </form>
    
    Code (markup):
    All we're really interested in though is the div.inputLimit and the input inside it. The DIV is our bordered container for both input and the list. The list will be added from the scripting as it makes no sense scripting off for a UL to be there. The data-input-limit variable sets the max number of words so you can configure the script from the markup. Pattern is added so that we have a fallback for when scripting is not present.

    With the script, as always I put it all in a IIFE to isolate scope, and pass "document" as "d" the same way crazy Goog does. Good enough for google, it's good enough for me. Gimme that, old time IIFE, gimme that old time IIFE, gimme that old time IIFE, it's good enough for me.

    (function(d) {
    
    })(document);
    Code (markup):
    Inside that IIFE I start out with some helper functions to make the creation of elements on the DOM a bit less of a headache. First up is "make' which just creates DOM elements and content structures of elements and textnodes.

    
    	function make(tagName, attr, forceParent) {
    		var e = d.createElement(tagName);
    		if (forceParent) forceParent.appendChild(e);
    		for (var key in attr) {
    			var value = attr[key];
    			switch (key) {
    				case 'after':
    					value.parentNode.insertBefore(e, value.nextSibling);
    					continue;
    				case 'before':
    					value.parentNode.insertBefore(e, value);
    					continue;
    				case 'first':
    					value.insertBefore(e, value.firstChild);
    					continue;
    				case 'last':
    					attr[key].appendChild(e);
    					continue;
    				case 'content':
    					if (value instanceof Array) {
    						for (var row of value) {
    							if (row instanceof Array) make(row[0], row[1], e);
    							else nodeAppend(e, row);
    						}
    					} else nodeAppend(e, row);
    					continue;
    				default:
    					e[key] = attr[key];
    			}
    		}
    		return e;
    	} // make
    
    Code (markup):
    Nothing too complex. If you were to:

    
    make('p', {
      content : "This is a test",
      last : document.body
    });
    
    Code (markup):
    It makes a new P, with "This is a test" inside it, as the last element inside the document body. "before", "after", "first", and "last" correspond to insertBefore, insertBefore the nextSibling (aka the nonexistent insertAfter), insertBefore the first-child, and appendChild. The object you pass can also set any normal attribute... kind of. This is a gutted and simplified version.

    Laughably under the hood React does something very much like this, it just hides it from the normies with pointless bloated string processing crap.

    The make function calls nodeAppend:

    
    	function nodeAppend(e, value) {
    		e.appendChild(value instanceof Node ? value : d.createTextNode(value));
    	} // nodeAppend
    
    Code (markup):
    Which is just a simple routine to auto-detect if a Node or non-node is to be appended. Non-nodes are forced converted to text, which can be fun if you pass a function or object to it as it will in fact stringify it for you.

    The event handlers I create as methods of an object so they can be looped for application. It's silly to make them unique functions in the namespace and then manually apply them. This way we can easily add or remove any from outside the loop.

    
    	var
    		inputCallbacks = {
    			blur : function(event) {
    				if (event.currentTarget.value.trim()) wordAdd(event.currentTarget);
    			},
    			input : function(event) {
    				if (event.data === " ") wordAdd(event.currentTarget);
    			},
    			keydown : function(event) {
    				if (event.key === "Enter") {
    					wordAdd(event.currentTarget);
    					event.preventDefault();
    				}
    			}
    		},
    
    Code (markup):
    Notice they all call "wordAdd" just under different rules. They all trigger the same behavior, it's the "what and why" that's different.

    Next we need all the input we're going to hook for events. Again I coded this so that you can run multiple input of this type on the same page. Rather than spend time on classes or trying to grab them by the parent, querySelectorAll can be used to grab if they have our data-input-limit attribute on them.

    
    		targets = d.querySelectorAll("input[data-input-limit]");
    		
    	for (var input of targets) {
    		for (var event in inputCallbacks) {
    			input.addEventListener(event, inputCallbacks[event], false);
    		}
    		make('ul', { before : input } );
    	}
    
    Code (markup):
    We loop through all the target input, we add the event callbacks, and we 'make' a UL before each input.

    The wordAdd routine passed by our callbacks isn't too complex:

    
    	function wordAdd(input) {
    		var
    			count = (
    				input.dataset.inputLimit -
    				input.previousElementSibling.childElementCount
    			);
    		input.value = input.value.trim(); 
    		if (!count || !input.value) return;
    		make('li', {
    			last : input.previousElementSibling,
    			content : [
    				[ 
    					'input', {
    						name : input.name + "Words[]",
    						type : 'hidden',
    						value : input.value
    					}
    				],
    				input.value,
    				[
    					'button', {
    						onclick : wordRemove,
    						textContent : "\uD83D\uDDD9",
    						title : "Remove",
    						type : 'button'
    					}
    				]
    			]
    		});
    		input.value = "";
    		if (count === 1) input.disabled = true;
    	} // wordAdd
    
    Code (markup):
    First thing we do is pull up the count. "childElementCount" being another example of jQuery's "For **** sake, LEARN JAVASCRIPT" idiocy -- replicating things that already exist. All we need do is subtract the childElementCount of the UL (input.previousElementSibling) from data-input-limit (input.dataset.inputLimit). This tells us how many are "remaining".

    Input.value needs to be trimmed to make sure any whitespace doesn't get added. onInput will include the space at the end, so we trim that off.

    Because 0 is loose false, we can !count as a "abort early" trigger. Same for empty string. Hence if !count or !input.value we return. No sense doing anything else if those are "invalid". This is the ADVANAGE of loose typecasting that the "typescript" whackjobs waste time fighting, which is how they end up taking twice as long to code anything and vomit up two to ten times the code needed..

    We then make our LI with its contents. Because of how my "make" handles "content" this is pretty clean and easy. The LI is added to the end of the UL (input.previousElementSibling) via "last", it contains a hidden 'input', a textNode of the input.value, and the removal button with a UTF-8 cancellation X inside it and the title "remove" set as a tooltip.

    Notice the generated input sets its name as
    name : input.name + "Words[]"
    Code (markup):
    Thus each of the sub-words using our example form will become testWords[] array in your $_GET or $_POST server-side in languages like PHP. The ability to make array results by just appending [] is very handy. Now a form submit or JavaScript's FormData object can access those separate words without any extra work.

    The button is a BUTTON, not an <a>nchor, because it's not a navigational link, it's an action. 90%+ of the time people use anchors for JavaScript hooks that don't override the href, they're doing it all wrong! A button is the correct semantics, a button if set to type="button" (the default is submit) doesn't need that "void javascript" BS or event.preventDefault, a button doesn't need a BS href to be keyboard navigable!

    We then empty out the input, and if the count was one at the start, we disable the input. The disable should be done AFTER the input is emptied, as it will trigger an undesired blur();

    Note, in the CSS set input[disabled] to display:none, it just looks cleaner.

    That button calls wordRemove

    
    	function wordRemove(event) {
    		var
    			li = event.currentTarget.parentNode,
    			ul = li.parentNode,
    			input = ul.nextElementSibling;
    		ul.removeChild(li);
    		input.disabled = false;
    		input.focus();
    	} // wordRemove
    
    Code (markup):
    Normally I rail against "variables for nothing" but in this case the deep object lookups can be slow for anything we call more than once, and saying "event.currentTarget.parentNode.parentNode.nextElementSibling" just to get to the input gets a bit... unwieldy. It's ok to do it when the values are used more than once, shortcut the creation of other values, and improves code clarity.

    Pretty simple stuff though, we remove the button's parent LI from the UL, we make sure the input is enabled, and we focus the input since clicking on the button blurs everything else.

    That in a nutshell is how the JavaScript works. I think you can probably can figure out the CSS side, but if you have questions go ahead and ask.
     
    Last edited: Dec 17, 2020
    deathshadow, Dec 17, 2020 IP
    malky66 and qwikad.com like this.
  20. JEET

    JEET Notable Member

    Messages:
    3,758
    Likes Received:
    493
    Best Answers:
    19
    Trophy Points:
    235
    #20
    Try this basic javascript function.
    Should work on all browsers which have JS, its just basic js.


    <script type="text/javascript">

    function checkWords( id, max ){

    w= document.getElementById(id).value;

    //copy original in another variable to operate
    n=w;

    //replace some delimiters to white space
    n= n.replace(", ", " ");
    n= n.replace(",", " ");
    //add more delimiters here if needed


    arr= n.split(" ");
    c=0;
    for(x=0; x<arr.length; ++x){
    if(arr[x] != ""){ ++c; }
    }//for ends

    if( c > max ){
    w= w.substring(0, w.length-1);
    w= w.trim()+" ";
    }

    document.getElementById(id).value = w;
    }//func

    </script>

    <p>
    <label> 3 words: <br />
    <input type="text" id="ww" value="" onkeydown=" checkWords('ww', 3); " />
    </label>
    </p>

    <p>
    <label> 10 words: <br />
    <input type="text" id="ww2" value="" onkeydown=" checkWords('ww2', 10); " />
    </label>
    </p>
     
    JEET, Dec 18, 2020 IP