How can I make this code work better? - javascript

I recently asked a question about manipulating the html select box with jQuery and have gotten this really short and sweet code as an answer.
$("#myselect").change(function (event) {
var o = $("#myselect option:selected"),
v=o.text(),
old = $("#myselect option:contains('name')"),
oldv = old.html();
oldv && old.html( oldv.replace('name: ', '') );
o.text('name: ' + v);
});
I have one problem. This code doesn't work on multiple categories and I can't seem to wrap my mind around how it can be done. So, I made the obvious changes to it:
$("select").change(function (event) {
var foo = $("select option:selected"),
bar = foo.text(),
cat = foo.parent().attr("label"),
old = $("select option:contains('" + cat + "')"),
oldbar = old.html();
oldbar && old.html( oldbar.replace(cat + ': ', '') );
foo.text(cat + ': ' + bar);
});
This now works on multiple optgroups/categories but has led to another bug. A more problematic one, at that.
It happens when you click from one category to another. Check it out on jsFiddle.

The problem with the last snippet is it uses the name of the current category to locate the last selected label to flip back. Instead, how about searching for the ":", (this won't work if you have ":" in one of your options), and then replacing that part of the string.
change line 5 to:
, old = $("select option:contains(':')")
and line 8 to:
oldbar && old.html(oldbar.replace(oldbar.substr(0, oldbar.indexOf(':') + 2),''));
Let me know if that's not working for you!
Edit: as an afterthought, you might consider adding this line
$('select').change();
As well, somewhere in the $(document).ready() event, so that when the page first renders the default value gets the prefix like (I think) you want.

I've renamed the variables since it is really a good habit to get into naming your variables and functions with meaningful names so you can juggle in your memory what is going on.
$("select").change(function () {
// We immediately invoke this function we are in which itself returns a function;
// This lets us keep lastCat private (hidden) from the rest of our script,
// while still giving us access to it below (where we need it to remember the
// last category).
var lastCat;
return function (event) {
var selected = $("select option:selected"),
selectedText = selected.text(),
cat = selected.parent().attr("label");
if (lastCat) { // This won't exist the first time we run this
oldSelection = $("select option:contains('" + lastCat + ":')");
oldHTML = oldSelection.html();
oldHTML && oldSelection.html(oldHTML.replace(lastCat + ': ', ''));
}
selected.text(cat + ': ' + selectedText);
lastCat = cat; // Remember it for next time
};
}()); // Be sure to invoke our outer function (the "closure") immediately,
// so it will produce an event function to give to the change handler,
// which still has access to the variable lastCat inside

Here's a shot in the dark. At first blush, it appears you're not saving the previous selection and you're using the selection which triggered the event in your oldbar && old.html( oldbar.replace(cat + ': ', '') ); line. You need to save the category of the previous selection in a separate var.

Related

How to remove generated node on page

I'm creating a node manually using vanilla JS. The idea is this alert appears after an input field if the maxlength limit has been reached and is removed if it returns below. This is being used on a CMS that users can use to create forms dynamically, so I won't know if the field will have a maxlength or not, or if it will have anything else after it. I'm using the following code:
document.querySelectorAll('input, textarea').forEach(element => {
if (element.hasAttribute('maxlength')) {
let maxChars = element.getAttribute('maxlength');
let elID = element.getAttribute('id');
let charWarning = document.querySelectorAll('#' + elID + ' + .char-limit');
element.addEventListener('input', () => {
let inputLength = element.value.length;
console.log(inputLength);
if (inputLength >= maxChars) {
if (charWarning.length == 0) {
let divAlert = document.createElement('div');
let divAlertText = document.createTextNode(maxChars + ' character limit reached on input');
divAlert.classList.add('text-danger', 'char-limit');
divAlert.setAttribute('aria-live', 'polite');
divAlert.setAttribute('aria-labelledby', elID);
divAlert.appendChild(divAlertText);
element.insertAdjacentElement('afterend', divAlert);
charWarning = document.querySelectorAll('#' + elID + ' + .char-limit');
}
} else {
// console.log(charWarning.length);
if (charWarning.length > 0) {
charWarning.remove(); // This is not working and I have no idea why.
}
}
});
}
});
For whatever reason, the .remove() function isn't working. It's throwing an error:
charWarning.remove is not a function at HTMLTextAreaElement
I don't really understand this. I thought it might be down to the fact that the initial setting of the charWarning prior to the listener was static, so I've added it again at the end of the function creating the warning element. It all works fine, but it's not removing the warning when below the maxlength and throwing that error.
For info, the commented out console.log:
// console.log(charWarning.length);
When uncommented does return 1, when the node has been added.
Can anyone point to what I'm doing wrong?
Ok, so after 2 days of no real responses, I took a late night stab at this again and somehow figured it out through trial and error. I thought the issue was to do with scoping but I couldn't figure out WHAT scope. In the end, I tried changing the "let" to "var" on the warning and that didn't work. So then I changed the selector from document.querySelectorAll to document.querySelector, cleaned up a little of the code and removed the else by using conditional chaining and voilà... result! Much cleaner, much better (and most importantly) functioning code...
However, on researching the properties of aria-live="polite" I discovered that creating and removing the node is the incorrect process. I can create it but when I want to update it for assistive technologies, it's the content I need to change. This kinda goes back to #ZainWilson-WCHStudent's first comment about showing and hiding the content. While I'm still not doing this as I believe it is incorrect for accessibility, it does kind of lean on that idea. I believe that this solution is much more elegant, efficient and (most importantly) accessible:
document.querySelectorAll("input, textarea").forEach((e) => {
if (e.hasAttribute("maxlength")) {
let maxChars = e.getAttribute("maxlength");
let elID = e.getAttribute("id");
let divAlert = document.createElement("div");
let divAlertText = document.createTextNode(
maxChars + " character limit reached on input"
);
divAlert.classList.add("text-danger", "char-limit");
divAlert.setAttribute("aria-live", "polite");
e.insertAdjacentElement("afterend", divAlert);
e.addEventListener("input", () => {
let charWarning = document.querySelector("#" + elID + " + .char-limit");
let inputLength = e.value.length;
if (inputLength >= maxChars) {
charWarning.appendChild(divAlertText);
} else {
if(charWarning.firstChild) {
charWarning.removeChild(charWarning.firstChild);
}
}
});
}
});
Here's a Codepen to test: https://codepen.io/tadywankenobi/pen/OJQXwwR

trouble looping through div ids with newest jquery build

I upgraded my website to the latest jquery build (2.1.4), and I'm trying to debug the many errors that it is throwing.
However, I keep getting the error "unrecognized expression: [id=]" on the following script:
setTimeout(function() {
$(".cab_librovisitas, .cuerpo_librovisitas, .cuerpo_librovisitas_user").each(function () {
var ids = $('[id=' + this.id + ']');
if (ids.length > 1 && ids[0] == this) {
$(ids[1]).remove();
}
});
and I can't wrap my head around it.
Any help will be appreciated.
At first I was going to write a comment advising to put support requests directly on jQuery, but then I saw the code and thought it merits some discussion.
First of all, the id attribute is a special attribute in HTML. It is supposed to hold a unique value throughout the whole document (in other words, no two elements can have the same id), so I'm finding it strange that code would ever work.
Secondly, I don't see any reason why you would use jQuery to select an element by id when a simple document.getElementById() would have done the trick. Let's say you wanted to have a jQuery element. Fine, even in that case, your jQuery selector is far from perfect. A better alternative would be $('#' + this.id);. That said, the best alternative would be a simple $(this)... no need to worry about the id at all.
Perhaps I misunderstand the new jquery build, but normally you would declare your id within the jquery wrapper with
$('#myId')
Your code is assigning
var ids = $('[id=' + this.id + ']');
which translates to this
ids = $('[id=whateverThisIdIs]');
Can you try this instead?
var ids = $('#' + this.id); // assuming this.id does not contain '#'.
Final
setTimeout(function() {
$(".cab_librovisitas, .cuerpo_librovisitas, .cuerpo_librovisitas_user").each(function () {
var ids = $('#' + this.id);
// or this if you have the '#'
// var ids = $(this.id);
if (ids.length > 1 && ids[0] == this) {
$(ids[1]).remove();
}
});
One of the comments mentions
You script implies that there are multiple ids on the page: not good.
This is true if you are using the same id, which I do not think you are.
Somewhere in your HTML is an element with one of the classes .cab_librovisitas, .cuerpo_librovisitas, .cuerpo_librovisitas_user, that either has no id attribute or has an empty one.
Change the line
var ids = $('[id=' + this.id + ']');
to
var ids = $('#' + this.id);
and the error will go away.
Or do a check for an empty id:
setTimeout(function() {
$(".cab_librovisitas, .cuerpo_librovisitas, .cuerpo_librovisitas_user").each(function () {
if (!this.id) return;
var ids = $('[id=' + this.id + ']');
if (ids.length > 1 && ids[0] == this) {
$(ids[1]).remove();
}
});
}
...I can't help but wonder what the purpose of the code snippet is though... trying to remove duplicated elements...? Why are they there in the first place?

JavaScript "if" statement wont run

(Using Javascript)
If I enter text in the textbox or not, the alert will not come up.
I know this is a simple fix but I can not figure it out!
(This is for learning purposes.)
Workout Log Test
<script type="text/javascript">
function myResults() {
myExercise();
myWeight();
mySets();
myReps();
myFunction();
}
function myExercise() {
var txtExercise = document.getElementById("txtExercise");
var txtOutput = document.getElementById("txtOutput1");
var name = txtExercise.value;
txtOutput1.value = "You destroyed, " + name + "!"
if (txtExercise.length === 0) {
alert ('Do you even lift?');
return;
First off, you're checking the "length" property of the element rather than the value of the input.
Second of all, you're checking against an integer value. If you were to simply read the value of the element, you're going to get text.
I'm guessing, what you want is something like:
var exercise = parseInt(txtExercise.value, 10);
if(exercise === 0) {
alert('Do you even lift?');
return;
}
But that's assuming txtExercise is an input element. Without seeing your markup, it's hard to be sure that any given answer will work.
Here you go, all fixed. You need an event handler, and this is a better if/else use case.
http://codepen.io/davidwickman/pen/vOKjqV
// Check the .value.length instead of just the .length
if (txtExercise.value.length === 0) {
alert('Bro, do you even lift?');
} else {
txtOutput1.value = "You destroyed, " + name + "!";
}
Assuming you have closed the function and if statement properly, you are missing a semi colon just before the if statement.
txtOutput1.value = "You destroyed, " + name + "!"; <---- here

JQuery - Using Selector :contains - Weird Results

Story so far.....
I want to learn JQuery, and im also building an MVC ASP.NET Apps which requires a "search as you type" search function - perfect to learn JQuery as well! So far (with the help of stackoverflowers ) I have managed to get the AJAX/JSON bit. Now I want to evaluate each key press and validate it against the JSON Array which was created as an unordered list. What im trying to achieve is to only show the account numbers in the list that contain what is inputted. So, my reasoning was to check against keydown event and validate it. For the time being im just changing the color of the account numbers to red of hiding them, just to prove my logic works.
My JQuery Code so far....
http://jsfiddle.net/garfbradaz/JYdTU/28/
...and for convenience....
$(document).ready(function() {
var $input = $("#livesearchinput"),
filled = false;
$.ajaxSetup({
cache: false
});
$input.keydown(function(key) {
if (!filled) {
filled = true;
$.getJSON("/gh/get/response.json//garfbradaz/MvcLiveSearch/tree/master/JSFiddleAjaxReponses/", function(JSONData) {
var $ul =
$('<ul>').attr({
id: "live-list"
}).appendTo('div#livesearchesults');
$.each(JSONData, function(i, item) {
$.each(item, function(j, val) {
$('<li>').attr({
id: "live-" + val
}).append(val).appendTo($ul);
});
});
});
}
var n = $("li:contains('" + this.value + "')").length;
if (n === 0) {
$("li").removeClass("color-change");
console.log("1. value of n equals " + n + " : " + this.value);
}
else {
$("li:contains('" + this.value + "')").addClass("color-change");
console.log("2. value of n equals " + n + " : " + this.value);
}
});
});​
My Issue.....
My issue is that when i evaluate the key press using the following this.value is empty on the first keydown event and then out of step throughout
var n = $("li:contains('" + this.value + "')").length
Example:
If i input 100004, let me show you my console.log results from Chromefor inputting that result:
The results seem to always be one step behind. Is the keydown event the best one to use or am i missing something.
As always - thanks guys and happy coding.
because this.value on the keydown event does not contain the keystoke which triggered the event. Use keyup instead.
You can access the keystoke via the event object which is available in the function... but the value of the input will not contain it until the key is released.
http://jsfiddle.net/rlemon/TGBUe/1/ in this example you can open your console... and type a character in the input... you will notice the keydown this.value is blank for the first char whereas the keyup contains it.
Maybe the check fires before the AJAX part has completed.
Mind that AJAX is asynchronous and the code continues its flow regardless of the AJAX request, that is completed asynchronously.
Try to move the logic inside function(JSONData).

Can this JavaScript be optimized?

This JS will be executed on pages with a lot of fields. Can you see anyway to improve the speed of this code? If so, can you explain what you found?
var _TextInputs = null;
function GetTextInputs()
{
if (_TextInputs == null)
{
_TextInputs = jq('input[type=text]');
}
return _TextInputs;
}
var _Spans = null;
function GetSpans()
{
if (_Spans == null)
{
_Spans = jq('span');
}
return _Spans;
}
function UpdateRate(ratefield, name)
{
GetTextInputs().filter('[' + name + ']').each(function()
{
this.value = FormatCurrencyAsString(FormatCurrencyAsFloat(ratefield.value));
CalculateCharge(name.replace('Rate', ''), jq(this).attr(name));
});
}
function CalculateCharge(name, activity_id)
{
var inputs = GetTextInputs();
var bill_field = inputs.filter('[' + name + 'Bill=' + activity_id + ']');
var rate_field = inputs.filter('[' + name + 'Rate=' + activity_id + ']');
var charge_field = GetSpans().filter('[' + name + 'Charge=' + activity_id + ']');
charge_field.text(FormatCurrencyAsString(FormatCurrencyAsFloat(bill_field.val()) * FormatCurrencyAsFloat(rate_field.val())));
}
You can:
Replace each with while
Replace val() with .value (should be fine as long as those fields are plain text ones)
Access elements by class instead of by name/type
Replace attr() with plain property access; e.g.: this.attr(name) --> this.name
These are all rather unobtrusive changes which should speed things up mainly due to cutting down on function calls.
Don't query elements on every function call if those elements are static (i.e. are not modified during your app life-cycle). Instead, store them outside the loop.
I can see that you're using attribute filters everywhere, e.g.:
_TextInputs = jq('input[type=text]');
inputs.filter('[' + name + 'Bill=' + activity_id + ']');
Attribute filters are useful, but not especially 'snappy' when compared to more direct class or ID selectors. I can't see any markup so the best I can do is suggest that you use more IDs and classes, e.g.:
jq('input.textInput');
instead of:
jq('input[type=text]');
A little off-topic, but I use and recommend Javascript Rocks. This books contains a TON of awesome JS optimisation advice by the creator of Scriptaculous. Also comes with a tool called DOM Monster which helps track down performance bottlenecks - it's an awesome compliment to Firebug as it actually tracks through the DOM looking for inefficiencies based on heuristics and DOM complexity.

Categories

Resources