How can I pass data to an element's change() method? - javascript

I wrote an app where multiple users can edit a database in realtime. I am using socket-io to keep all users' pages up to date with any changes to the database.
All input values broadcast a change.
Say I bind a function to an input's change event:
$(".input-field").change(function(ev) {
codeThatChangesOtherInputValuesOnMyPage1;
codeThatChangesOtherInputValuesOnMyPage2;
codeThatChangesOtherInputValuesOnMyPage3;
codeThatChangesOtherInputValuesOnMyPage4;
codeThatChangesOtherInputValuesOnMyPage5;
codeThatChangesOtherInputValuesOnMyPage6;
codeThatChangesOtherInputValuesOnMyPage7;
codeThatChangesOtherInputValuesOnMyPage8;
var tableColumn = $(ev.target).attr('table-col');
var newFieldValue = $(ev.target).val()
broadcastChange(tableColumn, newFieldValue); // this is pseudo code for a socket-io emit()
});
socket.on('runThisWhenReceivingBroadcastFromServer', function(response) {
// response.data has the input element id of element I should update.
// Get the input field i should update
var theInputField = getInputField(response.data)
$(theInputField).val(getNewInputValue(response.data))
$(theInputField).change();
// I call change because I want all my code in the input's change function to run, except for the last line.
});
I have already fixed this problem, but I am repeating myself by just copying all my code from on change function and pasting it in the broadcast receiving and then just omitting the broadcastChange line. But i want to follow DRY (Don't Repeat Yourself).
Also codeThatChangesOtherInputValuesOnMyPage1; is just that, code. Its tons of code. How would you go about restructuring the code?
My first thought was to do something like this (pseudo code):
$(".input-field").change(function(ev) {
codeThatChangesOtherInputValuesOnMyPage1;
codeThatChangesOtherInputValuesOnMyPage2;
codeThatChangesOtherInputValuesOnMyPage3;
codeThatChangesOtherInputValuesOnMyPage4;
codeThatChangesOtherInputValuesOnMyPage5;
codeThatChangesOtherInputValuesOnMyPage6;
codeThatChangesOtherInputValuesOnMyPage7;
codeThatChangesOtherInputValuesOnMyPage8;
var tableColumn = $(ev.target).attr('table-col');
var newFieldValue = $(ev.target).val()
if (!ev.data.comingFromBroadcastReceiverFunction) {
broadcastChange(tableColumn, newFieldValue); // this is pseudo code for a socket-io emit()
}
});
but you can't pass data to change(); only when binding the function.
What do you guys think is the functional programming approach to this?

If you are copying and pasting code over and over, you could avoid that by separating the duplicate code into a new function, and have both the change and the socket functions call it.
For example, it could work like this:
function updateInputValue(inputField) {
codeThatChangesOtherInputValuesOnMyPage1;
codeThatChangesOtherInputValuesOnMyPage2;
codeThatChangesOtherInputValuesOnMyPage3;
codeThatChangesOtherInputValuesOnMyPage4;
codeThatChangesOtherInputValuesOnMyPage5;
codeThatChangesOtherInputValuesOnMyPage6;
codeThatChangesOtherInputValuesOnMyPage7;
codeThatChangesOtherInputValuesOnMyPage8;
}
$(".input-field").change(function(ev) {
updateInputValue($(ev.target));
var tableColumn = $(ev.target).attr('table-col');
var newFieldValue = $(ev.target).val()
broadcastChange(tableColumn, newFieldValue); // this is pseudo code for a socket-io emit()
});
socket.on('runThisWhenReceivingBroadcastFromServer', function(response) {
// response.data has the input element id of element I should update.
// Get the input field i should update
var theInputField = getInputField(response.data)
$(theInputField).val(getNewInputValue(response.data))
updateInputValue($(theInputField));
// I call change because I want all my code in the input's change function to run, except for the last line.
});

Related

JavaScript/Local Storage

Good Afternoon, I'm hoping for some guidance. I am new to JavaScript and struggling with a project. We have been asked to store new entries into a website application, which once added store into the local storage.
This is what I have so far, but I am at a loss where I am going wrong to get this stored.
The first section surrounded by ** works fine (albeit it doesn't store the entires). It is the last bit where I'm trying to add new code to store the message and invoke the event listener which doesn't work.
Any guidance would be greatly appreciated.
**function addTextEntry(itemKey, initialText, isNewEntry) {
// Create a text element to edit the entry
var textElement = document.createElement("textarea");
textElement.rows = 5;
textElement.placeholder = "(new entry)";
// Set the textarea's value to the given text (if any)
textElement.value = initialText;
// Add a section to the page containing the textarea
addSection(itemKey, textElement);
// If this is a new entry (added by the user clicking a button)
// move the focus to the textarea to encourage typing
if (isNewEntry) {
textElement.focus();
}**
// Create an event listener to save the entry when it changes
function saveEntry() {
// A new version of this function is created every time addTextEntry is called,
// so it can access all the variables available in this call to addTextEntry
console.log("saveEntry called with variables in scope:", {
itemKey,
initialText,
isNewEntry,
textElement,
});
document.getElementById(initialText);
item = makeTextItem(initialText);
localStorage.setItem(itemKey);
textElement.addEventListener("initialText", saveEntry);
}
}
setItem method takes 2 parameters:
keyName: The name of the key in the Local Storage
keyValue: The value for a given key
In you code, you are passing only one parameter itemKey. You should also pass the value for that key as a second parameter.
document.getElementById(initialText);
This statement doesn't do anything with the result. You may want to assign it to a value like const textElement = document.getElementById(initialText);
item = makeTextItem(initialText);
You are missing the code for makeTextItem but that's still irrelevant since item isn't being used anywhere in the code below it.
localStorage.setItem(itemKey);
setItem takes TWO params, the key and the thing you want to save storage.setItem(keyName, keyValue);
textElement.addEventListener("initialText", saveEntry);
addEventListener's first argument should be a valid event. addEventListener(type, listener);

How can I avoid repeating this particular code for page load and for DOM changes?

For context, I'm building a JS calculator whereby the values displayed dynamically change in a 'summary box' as the user changes the values in the input fields (using a <form> element).
I have set default values in the <input> elements of the form, but the calculation functions in the summary box only get called as a keyup function when the values in the input fields are changed.
I have managed to get this to work perfectly, however, on page load the summary box values do not change as this is based on the keyup function (which hasn't occurred yet), so even though there are default values in the inputs, the summary box has nothing to calculate from (as it's waiting for the keyup functions to execute).
Now, I have found a workaround by repeating the DOM blocks to replace values on page load, but the code isn't very DRY. I am sure there must be a way to do this. I have tried to put the code block in an array and/or object but I am unable to successfully extract and execute this.
// THESE ARRAY ITEMS REPRESENT THE INPUT FIELDS IN THE HTML
const innerElementsArr = [investment, buying, selling, invFee];
// APPLYING THE DOM CHANGES TO EACH INPUT ELEMENT ABOVE
innerElementsArr.forEach(item => {
item.onkeyup = function() {
invDisplay.innerText = `£${investment.value}`;
netProfit.innerText = `£${grossProfitLoss()}`;
invFeeDisplay.innerText = `£${withInvFee()}`;
netProfitLossDisplay.innerText = `£${netProfitDisplay()}`;
};
});
// DISPLAY ALL CALCULATIONS ON PAGE LOAD
invDisplay.innerText = `£${investment.value}`;
netProfit.innerText = `£${grossProfitLoss()}`;
invFeeDisplay.innerText = `£${withInvFee()}`;
netProfitLossDisplay.innerText = `£${netProfitDisplay()}`;
As you can see, I am repeating myself and would like to know if there is a much more cleaner way to do this.
Thank you in advance.
Thank you to Ouroborus for the answer - it was such a simple workaround.
I created a function including the DOM block and then called that function as a keyup event and a window.onload event.
const valuesToChange = () => {
invDisplay.innerText = `£${investment.value}`;
coinDisplay.innerText = `${coinsOwned()} BTC`;
netProfit.innerText = `£${grossProfitLoss()}`;
invFeeDisplay.innerText = `£${withInvFee()}`;
exitFeeDisplay.innerText = `£${withExitFee()}`;
netProfitLossDisplay.innerText = `£${netProfitDisplay()}`;
};
// CHANGING THE VALUES IN THE SUMMARY AREA
const innerElementsArr = [investment, buying, selling, invFee, exitFee];
innerElementsArr.forEach(item => {
item.onkeyup = function() {
valuesToChange();
};
});
// DISPLAY ALL CALCULATIONS ON PAGE LOAD
window.onload = function() {
valuesToChange();
};

Appending a new file upload button when the previous changes

I have a function that uses jQuery to add in an additional file upload button when a user adds a file. My problem is that I cannot seem to have it either add in proper format or add every time. My current function simply tries added the string directly:
jQuery(document).ready(function($) {
$(function() {
$("input:file").change(function(){
$("input:file").after("</td></tr><tr><td class=\"field_name span4\"><strong></strong></td><td class=\"field_option\"><input type=\"file\" name=\"pictures\">");
});
});
});
You can see a live version of this here: http://1ro.co/salem/?module=insert
The issue with the method shown above is it does not add the first two closing tags: </td></tr>.
I've tried methods such as setting $("input:file"); to a variable, however that doesn't set for every value after the first. For example:
var count = 0;
jQuery(document).ready(function($) {
$(function() {
var input = $("input:file");
input.change(function(){
input.after(input.get(count++));
});
});
});
However this doesn't append at all (in theory would probably append every element), and I cannot use .after() on input.get(count).
In simple terms, I'm looking to improve this and make it so it will append the a new file upload button, and not being formatted improperly. If possible, I would rather use method 2 but at the moment I would like for it to just work.
You'll need to re-add the handler to each new file input, so best to isolate that in a function:
var addfile = function() {
var newRow = $("<tr><td class=\"field_name span4\"><strong></strong></td>" +
"<td class=\"field_option\"><input type=\"file\" name=\"pictures\">").
insertAfter($(this).closest('tr'));
newRow.find('input:file').change(addfile);
};
$("input:file").change( addfile );
I just tried out your live demo and it is inserting something. But it only happens once of course as your event handler is bound only to the first object.
I would try your change function using something like the following:
$('input:file').change(function() {
$(this).closest('tr').after($(this).closest('tr').clone());
});
but your change handler will have to be rebuilt as well.

Creating javascript objects using jQuery's each() method

Im curious about what might be a larger question than I think.
I am using the following code to listen for 'keyup' on a group of text input fields. If the user stops typing for a given amount of time, I send the data to a controller using AJAX.
I decided to try my hand at OOP in javascript to accomplish this. This is because I want a new instance of the timer method for each input field. (To be absolutely clear, Im very new to OOP in javascript so this might be dreadful. Let me know.)
Here is the main class with its methods:
function FieldListener(entity){
t = this;
t.typingTimer; // Timer identifier
t.doneTypingInterval = 1000; // Time in ms. e.g.; 5000 = 5secs
t.entity = entity;
entity.bind("keyup", function(){t.setTimer();});
}
FieldListener.prototype.setTimer = function(){
t = this;
// User is still typing, so clear the timer.
clearTimeout(t.typingTimer);
// Get the field name, e.g.; 'username'
t.entityType = t.entity.attr("name");
// If the value is empty, set it to a single space.
if(!(t.val = t.entity.val())){
t.val = ' ';
}
t.noticeSpan = t.entity.siblings("span");
// Display 'waiting' notice to user.
t.noticeSpan.html('...')
t.typingTimer = setTimeout(function(){t.doneTyping();},t.doneTypingInterval);
}
FieldListener.prototype.doneTyping = function(){
// Encode for passing to ajax route.
t = this;
valueType = encodeURIComponent(t.entityType);
value = encodeURIComponent(t.val);
$.ajax({
url: '/check/'+valueType+'/'+value,
type: 'GET',
processData: false
})
.done(function(validationMessage){
t.noticeSpan.html(validationMessage);
})
.fail(function(){
t.noticeSpan.html("Something went wrong. Please try again.");
});
}
So from here I'd like to be able to create an object of the FieldListener class for every input field.
I know I can do it easily if I have an id for each like so:
var fieldListener = new FieldListener($("#someFieldID"));
But I'd like to iterate over every field with a given class name. Something close to this perhaps?:
i = 0;
$(".info-field").each(function(){
i = new FieldListener($(this));
});
But that doesn't work (and doesn't look very nice).
Any thoughts? (Im also curious about critiques/improvements to the class/methods code as well.)
edit: As per #ChrisHerring's question: The issue is that it seems to create the object but only for the last element in the each() method. So the span associated with the last input field with the class '.info-field' displays the validationMessage returned from AJAX regardless of which field I am typing in.
UPDATE:
It seems like something is wrong with the creation of new objects. For example, if, rather than iterating through the each() method, I simply follow one class initiation with another, like so:
var fieldListener1 = new FieldListener($("#someFieldID"));
var fieldListener2 = new FieldListener($("#someOtherFieldID"));
that fieldListener2 overwrites variables being saved when initiating fieldListener1. This means that when I type into the input field with id "#someFieldID", it behaves as if I am typing into the input field with id "#someOtherFieldID". Thoughts?
UPDATE #2 (solved for now):
It seems that I have solved the issue for now. I needed to add 'var' before 't = this;' in the FieldListener class. Any comments/critiques are still welcome of course. ;)
The t variable is global. The function for the "keyup" event is evaluated dynamically which means it picks up the last value of t.
Change
t = this;
to
var t = this;
I think you want an array of FieldListener objects.
var myListeners = [];
i = 0;
$(".info-field").each(function(){
myListeners[i] = new FieldListener($(this));
i++
});
This'll give you a list of FieldListeners, where myListeners[0] is the listener for the first .info-field on the page, myListeners[1] is the listener for the second, etc.
Edit: It would appear you have solved the problem. This answer may still come in handy later on, though, so I won't delete it. =)
I think you should be using jquery's .on() to handle the binding.
$(body).on({
keyup: function () { HandleKeyUpEvent($(this)); },
keydown: function () { HandleKeyDownEvent($(this)); }
}, ".info-field");
I realize this is a departure from your original coding idea (using prototypes) but it will still be OOP, if that's what you intented to do.

Binding multiple events of the same type?

Firstly, is it possible? Been struggling with this one for hours; I think the reason my events aren't firing is because one event is unbinding/overwriting the other. I want to bind two change events to the same element. How can I do that?
As per request, here's the function I'm struggling with:
(function($) {
$.fn.cascade = function(name, trigger, url) {
var cache = {};
var queue = {};
this.each(function() {
var $input = $(this);
var $trigger = $input.closest('tr').prev('tr').find(trigger);
//$input.hide();
var addOptions = function($select, options) {
$select.append('<option value="">- Select -</option>');
for(var i in options) {
$select.append('<option value="{0}">{1}</option>'.format(options[i][0], options[i][1]));
}
$select.val($input.val()).trigger('change');
}
var $select = $('<select>')
// copy classes
.attr('class', $input.attr('class'))
// update hidden input
.bind('change', function() {
$input.val($(this).val());
})
// save data for chaining
.data('name', name)
.data('trigger', $trigger);
$input.after($select);
$trigger.bind('change', function() {
var value = $(this).val();
$select.empty();
if(value == '' || value == null) {
$select.trigger('change');
return;
}
// TODO: cache should be a jagged multi-dimensional array for nested triggers
if(value in cache) {
addOptions($select, cache[value]);
} else if(value in queue) {
$select.addClass('loading');
queue[value].push($select);
} else {
var getDict = {}
getDict[name] = value;
// TODO: use recursion to chain up more than one level of triggers
if($(this).data('trigger')) {
getDict[$(this).data('name')] = $(this).data('trigger').val();
}
$select.addClass('loading');
queue[value] = [$select];
$.getJSON(url, getDict, function(options) {
cache[value] = options;
while(queue[value].length > 0) {
var $select = queue[value].pop();
$select.removeClass('loading');
addOptions($select, options);
}
});
}
}).trigger('change');
});
return this;
}
})(jQuery);
The relevant chunk of HTML is even longer... but essentially it's a select box with a bunch of years, and then an <input> that gets (visibly) replaced with a <select> showing the vehicle makes for that year, and then another <input> that gets replaced with the models for that make/year.
Actually, it seems to be running pretty well now except for on page load. The initial values are getting wiped.
Solved the issue by pulling out that $select.bind() bit and making it live:
$('select.province').live('change', function() {
$(this).siblings('input.province').val($(this).val());
});
$('select.make').live('change', function() {
$(this).siblings('input.make').val($(this).val());
});
$('select.model').live('change', function() {
$(this).siblings('input.model').val($(this).val());
});
Sucks that it's hard-coded in there for my individual cases though. Ideally, I'd like to encapsulate all the logic in that function. So that I can just have
$('input.province').cascade('country', 'select.country', '/get-provinces.json');
$('input.make').cascade('year', 'select.year', '/get-makes.json');
$('input.model').cascade('make', 'select.make', '/get-models.json');
Yes that is possible.
$(…).change(function () { /* fn1 */ })
.change(function () { /* fn2 */ });
jQuery event binding is additive, calling .change a second time does not remove the original event handler.
Ryan is correct in jQuery being additive, although if you find there are problems because you are chaining the same event, beautiful jQuery allows another approach, and that is calling the second function within the first after completion of the first as shown below.
$('input:checkbox').change(function() {
// Do thing #1.; <-- don't forget your semi-colon here
(function() {
// Do thing #2.
});
});
I use this technique frequently with form validation, one function for checking and replacing disallowed characters input, and the second for running a regex on the results of the parent function.
Update to Post:
OK... You all are quick to beat on me with your negative scores, without understanding the difference in how we each view Mark's request. I will proceed to explain by example why my approach is the better one, as it allows for the greatest flexibility and control. I have thrown up a quick example at the link below. A picture's worth a 1000 words.
Nested Functions on One Event Trigger
This example shows how you can tie in three functions to just one change event, and also how the second and third functions can be controlled independently, even though they are still triggered by the parent change event. This also shows how programmatically the second and third functions can BOTH be tied into the same parent function trigger, yet respond either with or independently (see this by UNCHECKING the checkbox) of the parent function it is nested within.
$('#thecheckbox').change(function() {
$("#doOne").fadeIn();
if ($('#thecheckbox').attr('checked')) { doFunc2() }
else { doFunc3() };
function doFunc2() { $("#doTwo").fadeIn(); return true; }
function doFunc3() { $("#doTwo").fadeOut(); return true; }
$("#doThree").fadeIn();
});
I've included the third 'Do thing #3 in the example, to show how yet another event can follow the two nested functions as described earlier.
Forgive the earlier bad pseudocode originally posted first, as I always use ID's with my jQuery because of their ability to give everything an individual status to address with jQuery. I never use the 'input:checkbox' method in my own coding, as this relies on the 'type' attribute of an input statement, and therefore would require extra processing to isolate any desired checkbox if there is more than one checkbox in the document. Hopefully, the example will succeed at articulating what my comments here have not.
I am actually not sure exactly if you can bind two different change events. But, why not use logic to complete both events? For example...
$('input:checkbox').change(function() {
// Do thing #1.
// Do thing #2.
});
That way, you get the same benefit. Now, if there are two different things you need to do, you may need to use logic so that only one or the other thing happens, but I think you would have to do that anyway, even if you can bind two change events to the same element.

Categories

Resources