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/
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 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).
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/
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?
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.
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/
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):
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/
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):
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.
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 ).
Somebody provided a solution on stackoverflow right after I posted here. https://stackoverflow.com/questions...in-textarea-doesnt-work-on-cell-phones-jquery http://jsfiddle.net/26fms3bo/
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.
Here's a pen: https://codepen.io/jason-knight/pen/xxEdrqP If I have time later I'll write up an explanation of the how/what/why.
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.
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>