Dynamically Modifying Value of an Existing Event Handler - javascript

Quick background: I'm updating existing code to separate event handlers from html objects and, in an onload function I'm then assigning all necessary handlers.
$('input[name^="interactiveElement_"]').each(function() {
var fieldName = "interactiveElement_";
var elementNumber = $(this).attr("name").substring(fieldName.length,$(this).attr("name").indexOf("_",fieldName.length));
var subElementNumber = $(this).attr("name").substring((fieldName+itemNumber+'_').length,$(this).attr("name").lastIndexOf('_'));
var rowNumber = $(this).attr("name").substring($(this).attr("name").lastIndexOf('_')+1);
$(this).on("click.custNamespace", function() {
updateThisElementsMetadata(elementNumber, subElementNumber, rowNumber);
updatePreview(elementNumber);
});
});
Now for the hard part. In this interface, users will be able to trigger clones of existing elements. These clones need to have some of their handler arguments updated to new values.
Before separating out the events, I was doing that with this:
var regex = /([^0-9]+[0-9]+[^0-9a-zA-Z]+[0-9]+[^0-9a-zA-Z]+)([0-9]+)([^0-9]+)?/g;
$(element).attr("onclick",
$(element)
.attr("onclick")
.replace(regex, "$1"+newValue+"$3"));
and so on for each possible event these elements could have.
But, now using jQuery's on(event, handler) I no longer have visibility to what the handler is.
I've tried this (following this question - Get value of current event handler using jQuery):
jQuery._data(element).events.click[0].handler
But, this returns the function with variable names not values.
function () {
updateThisElementsMetadata(elementNumber, subElementNumber, rowNumber);
updatePreview(elementNumber);
}
Where I would have hoped for:
function () {
updateThisElementsMetadata(1, 2, 1);
updatePreview(1);
}
Looking in the console log I see that jQuery._data(element).events.click[0] has the values in handler => < function scope > => Closure but it doesn't seem like there is dot notation to access that, or even an array that I can dynamically cycle through.
If you tell me this isn't possible, I could change all the functions' args to just be $(this) and parse out the necessary values from it in each function, or I guess I could have a helper function... but if I could keep a similar setup to what was there it would ease other dev's learning curve.
Final Solution
To reduce duplicate code, I created a Javascript function/object that parses out necessary info from name/id tag (instead of data- attributes to reduce redundant info). Whenever an event handler is triggered it will first parse out the necessary values and then run the function w/ them.
$('input[name^="interactiveElement_"]').on("click.custNamespace", function() {
var inputField = inputFieldClass(this);
updateThisElementsMetadata(inputField.elementNumber, inputField.subElementNumber, inputField.rowNumber);
updatePreview(inputField.elementNumber);
});
var inputFieldClass = function(field) {
var fieldIdentity = $(field).attr("name") === undefined ? $(field).attr("id") : $(field).attr("name");
var fieldName = fieldIdentity.substring(0,fieldIdentity.indexOf("_")),
elementNumber = fieldIdentity.substring(fieldName.length + 1,fieldIdentity.indexOf("_",fieldName.length + 1)),
subElementNumber = fieldIdentity.substring((fieldName+'_'+elementNumber+'_').length,fieldIdentity.lastIndexOf('_')),
rowNumber = fieldIdentity.substring(fieldIdentity.lastIndexOf('_')+1);
return {
fieldName : fieldName,
elementNumber : elementNumber,
subElementNumber : subElementNumber,
rowNumber : rowNumber,
getInputName : function () {
return this.name + "_" + this.elementNumber + "_" + this.subElementNumber + "_" + this.rowNumber;
}
};
};

What I was suggesting in the comments was something like this:
$('input[name^="interactiveElement_"]').on("click.custNamespace", function() {
var fieldName = "interactiveElement_";
var elementNumber = $(this).attr("name").substring(fieldName.length,$(this).attr("name").indexOf("_",fieldName.length));
var subElementNumber = $(this).attr("name").substring((fieldName+itemNumber+'_').length,$(this).attr("name").lastIndexOf('_'));
var rowNumber = $(this).attr("name").substring($(this).attr("name").lastIndexOf('_')+1);
updateThisElementsMetadata(elementNumber, subElementNumber, rowNumber);
updatePreview(elementNumber);
});
Also, instead of parsing everything from the element's name, you could use data- attributes to make it cleaner (e.g. data-element-number="1", etc.).

Related

Programmatically adding handlers to react form handler

In trying to cut down on boilerplate code I'm trying to add handlers for common form fields to the handler object that will highlight the bad field, show some error, etc. So far I have this:
//FieldHelper.js
exports.withStandardStoreHandlers = function (storeObj, fields)
{
for(var i = 0; i < fields.length; i++)
{
var f = fields[i];
var midName = f.charAt(0).toUpperCase() + f.slice(1);
storeObj.prototype["onUpdate" + midName] = function(event) {
this[""+f] = event.target.value;
this[f+"ValidationState"] = '';
this[f+"HelpBlock"] = '';
};
}
return storeObj;
}
and then eventually I create an Alt store, that is exported thusly:
export default alt.createStore(FieldHelper.withStandardStoreHandlers(AddressStore, "firstName", "lastName", "street"));
And it actually does add the "onUpdateFirstName", "onUpdateLastName", etc. methods, but unfortunately they all use the field that was passed last. So in this example, I type some stuff into first name, but onUpdateFirstName modifies the street text field and its validation state/help block.
Clearly, the solution is to somehow make a copy of the "f" and "midName" variables so each of the dynamically created onUpdateX methods would have it's own value for them, instead of using the last available, but I just can't figure out how to do this in JavaScript. Preferably in as backwards-compatible way as possible, as this is going to be on the front end.
This is due to var in JavaScript being function scoped rather than block scoped as your code is expecting. A better explanation can be found here: https://stackoverflow.com/a/750506/368697
One solution is to make sure your f is declared inside a function scope on each iteration by using forEach:
//FieldHelper.js
exports.withStandardStoreHandlers = function (storeObj, fields)
{
fields.forEach(function (f) {
var midName = f.charAt(0).toUpperCase() + f.slice(1);
storeObj.prototype["onUpdate" + midName] = function(event) {
this[""+f] = event.target.value;
this[f+"ValidationState"] = '';
this[f+"HelpBlock"] = '';
};
});
return storeObj;
}

Getting "This" into a namespace in Javascript

I'm sure this should be a simple question but I'm still learning so here it goes:
I have some code to run a function on click to assign the clicked element's ID to a variable but I don't know how to pass the "this.id" value to the namespace without making a global variable (which I thought was bad).
<script>
fsa = (function() {
function GetTemplateLoc() {
templateId = document.activeElement.id;
alert(templateId + templateId2);
}
return {
GetTemplateLoc: GetTemplateLoc,
}
})();
//call the functions
$(document).on('click', '.template', function () {
fsa.GetTemplateLoc();
});
</script>
and HTML with random picture:
<img id="template-1" class="template" src="http://fc02.deviantart.net/fs70/f/2010/028/c/b/cb21eda885b4cc6ee3f549a417770596.png"/>
<img id="template-2" class="template" src="http://fc02.deviantart.net/fs70/f/2010/028/c/b/cb21eda885b4cc6ee3f549a417770596.png"/>
The following would work:
var fsa = (function() {
function GetTemplateLoc() {
var templateId = this.id;
alert(templateId);
}
return {
GetTemplateLoc: GetTemplateLoc,
}
})();
//call the functions
$(document).on('click', '.template', fsa.GetTemplateLoc);
jQuery generally calls functions you pass as event handlers with this set to the DOM object the event is associated with.
In this case it will call GetTemplateLoc() with this set to either .template element, so you can use this directly in the function and don't need to pass any parameters.
Important tip: Always declare variables using var. JavaScript has no automatic function-local scope for variables, i.e. every variable declared without var is global, no matter where you declare it. In other words, forgetting var counts as a bug.
Try this : You can directly use this.id to pass id of the clicked element where this refers to the instance of clicked element.
<script>
fsa = (function() {
function GetTemplateLoc(templateId ) {
//templateId = document.activeElement.id;
alert(templateId + templateId2);
}
return {
GetTemplateLoc: GetTemplateLoc,
}
})();
//call the functions
$(document).on('click', '.template', function () {
fsa.GetTemplateLoc(this.id);
});
</script>
If you're able to use jQuery within the GetTemplateLoc function, you could do something like this:
var fsa = (function() {
function GetTemplateLoc($trigger) {
var templateId = $trigger.attr('id'),
templateId2 = $($trigger.siblings('.template')[0]).attr('id');
alert(templateId + ' ' + templateId2);
}
return {
GetTemplateLoc: GetTemplateLoc,
}
})();
$(document).on('click', '.template', function () {
fsa.GetTemplateLoc($(this));
});
You can set GetTemplateLoc to expect a jQuery object as a parameter (the dollar sign at the beginning of $trigger can be used to distinguish it as a jQuery object rather than any other data type, it's not necessary but can help clarify things sometimes).
templateId will store the value of the clicked image's ID, and templateId2 will store the value of the other image's ID. I also added a space between the two variables in the alert.
If you can't use jQuery within GetTemplateLoc, you could do something like this:
var fsa = (function() {
function GetTemplateLoc(trigger) {
var templateId = trigger.id;
var templateId2 = trigger.nextElementSibling == null ? trigger.previousElementSibling.id : trigger.nextElementSibling.id;
alert(templateId + ' ' + templateId2);
}
return {
GetTemplateLoc: GetTemplateLoc,
}
})();
This time, the .template that triggered the event is passed into GetTemplateLoc, but this time it's not a jQuery object. templateId is assigned to the trigger's ID and then templateId2 is assigned in a ternary. First, the nextElementSibling of trigger is checked to see if it's null. If it is, we know that trigger is the second of the two .template elements. Therefore we can set templateId2 to the ID of trigger's previous sibling. If trigger's nextElementSibling is not null, then we know that trigger is the first template and we populate templateId2 with the ID of nextElementSibling. This exact method will only work with two .template's, if there are more you'll need some additional/different logic, probably to retrieve all .template IDs and then loop through them to add them to the alert message. Hope this helps.

Create a Reusable HTML Control w/ Javascript

So, I've been searching through some existing questions dealing with re-usable items in HTML and Javascript, and I'm not sure if there's anything that gives me the start I'm looking for. I'm not super well-versed in js, but rather than re-write the same code over and over again and have to perform the upkeep on it, I'd prefer to build a re-usable framework that I can apply in several places.
The basic layout is this: There's an input field with an "Add" button, each time you add a name, it displays below the input with a checkbox. When you uncheck it, the name is removed from the list.
I'm fine with styling and building the HTML, what I'm lost on is developing an object in js that I can apply in multiple places. What I had in mind was this:
function createInputControl(targetElementId) {
var newInputControl = new ItemInputControl();
newInputControl.onItemAdded = customItemAddedCallback;
newInputControl.onItemRemoved = customItemRemovedCallback;
newInputControl.createInElement(targetElementId);
}
That's the start I'm looking for. An object that I can create that has designated callbacks for when an item is added or removed via user interaction, and a way for me to draw it within an existing element on my page.
EDIT: What I'm looking for here is a skeleton of a javascript object (named ItemInputControl above) with these functions / properties that I can re-use throughout my site.
Ok, so If I understand you correctly - you're looking for help on how to make a globally accessible variable that can be used in your entire application, like jQuery. You have two main options for what you are looking to do
First - you could use an Object Literal, which exposes a single global variable and all of your methods are contained within:
(function (window) {
var inputControl = {
onItemAdded: function () {
// do stuff
},
onItemRemoved: function () {
// do stuff
},
createInElement: function (targetElementId) {
// do stuff
}
};
window.ItemInputControl = inputControl;
})(window);
This is used like so:
ItemInputControl.createInElement("elementId");
Your second option is to use Prototype:
(function (window) {
var inputControl = function () {
// Constructor logic
return this;
};
inputControl.prototype.onItemAdded = function () {
// do stuff
return this;
};
inputControl.prototype.onItemRemoved = function () {
// do stuff
return this;
};
inputControl.prototype.createInElement = function (elementId) {
// do stuff
return this;
};
window.ItemInputControl = inputControl;
})(window);
This would be used like so:
var newInputControl = new ItemInputControl();
newInputControl.createInElement("elementId");
For most cases in individual applications - I prefer to use Object Literals for my framework. If I were building a widely distributed javascript framework, I would probably use a prototype pattern. You can read more on prototype patters here: http://www.htmlgoodies.com/beyond/javascript/some-javascript-object-prototyping-patterns.html
Well, I'm not sure if this is exactly helpful, but perhaps it will contain a few ideas for you.
The two HTML elements needed are stored as format strings, and everything is dynamically added/removed in the DOM.
var listid = 0;
$(document).ready(function() {
var controlHtml = + // {0} = mainid
'<div>' +
'<input id="text-{0}" type="text" />' +
'<div id="add-{0}" class="addButton">Add</div>' +
'</div>' +
'<div id="list-{0}"></div>';
var listHtml = + // {0} = mainid, {1} = listid, {2} = suplied name
'<div id="list-{0}-{1}"><input id="checkbox-{0}-{1}" type="checkbox class="checkboxClass" checked />{2}<div>';
$('#content').append(controlHtml.f('0'));
$('.addButton').click(function(e) { addClick(e); });
});
function addClick(e) {
var id = e.currentTarget.id.split('-')[1];
var name = $('text-' + id).val();
$('.list-' + id).append(listHtml.f(id, listid, name));
listid++;
$('.checkboxClass').click(function() { checkboxClick(e); });
}
function checkboxClick(e) {
$('#' + e.currentTarget.id).remove();
}
String.prototype.f = function () { var args = arguments; return this.replace(/\{(\d+)\}/g, function (m, n) { return args[n]; }); };
And of course very minimal HTML to allow a hook for adding your control:
<body>
<div id="content"></div>
</body>

how to make this jquery more reusable?

I would like to be able to pass in "rider" or something else and have add/remove do the same thing but for other items beside rider like subaccounts.
$(function(){
var template = $('#riders-div .rider-new:first').clone(),
ridersCount = 0;
var addRider = function(){
ridersCount++;
var rider = template.clone().removeClass("dontshow").find(':input').each(function(){
var newId = this.id.replace('-', 's['+ ridersCount + '][') + ']';
$(this).prev().attr('for', newId); // update label for (assume prev sib is label)
this.name = this.id = newId; // update id and name (assume the same)
}).end() // back to .rider
.appendTo('#rider-exist'); // add to container
$('#rider-message').html('').removeClass("ui-state-highlight").removeClass("ui-state-error");
};
$('#addButton').click(addRider()); // attach event
$("#removeButton").click(function () {
$('#riders-div .rider-new:last').remove();
$('#rider-message').html('').removeClass("ui-state-highlight").removeClass("ui-state-error");
});
});
https://gist.github.com/1081078
You will want to create a plugin out of this code. The approach is as you need more features, add options to the plugin. I started the plugin below. Also code replacing element IDs (etc) needs to be more generic. Below I added a regex to replace the number in the element id.
Add callbacks where necessary to perform implementation specific actions/UI tweaks. So in your example above add the code to reset the message html ($('#rider-message').html('')) in the after callback.
after: function(i){
$('#rider-message').html(''); //...
}
And so on
$.fn.cloneForm = function(options){
var self = this, count = 0,
opts = $.extend({}, {
after: function(){},
template:'',
prependTo: '',
on: 'click'
}, options),
template = $(opts.template).clone(); // unmodified clone
self.bind(opts.on + '.cloneForm', function(){
count++;
template.clone().find(':input').each(function(){
var newId = this.id.replace(/[0-9]/i, count) // replace number
$(this).prev().attr('for', newId); // update label for
this.name = this.id = newId; // update id and name (assume the same)
}).end().prependTo(opts.prependTo);
opts.after.call(self, count); // run callback
});
}
Usage:
$('#addButton').cloneForm({
template: '#riders-div .rider-new:first',
prependTo: '#riders-div',
after: function(){ console.log('im done'); }
});
It seems like you are defining some sort of management system for this "Rider" module (I dunno, just what I'm seeing). The way I would improve this code to begin with would be to use some OO js. From there when there is the need to make it more generic (such as changeable class names), you make those selectors parameters to the constructor. It'll also have the benefit of making the code much more unit testable!
If you want some specific code examples I'll post something, just let me know.

call function inside a nested jquery plugin

There are many topics related to my question and i have been through most of them, but i haven't got it right. The closest post to my question is the following:
How to call functions that are nested inside a JQuery Plugin?
Below is the jquery plugin i am using. On resize, the element sizes are recalculated. I am now trying to call the function resizeBind() from outside of the jquery plugin and it gives me error
I tried the following combinations to call the function
$.fn.splitter().resizeBind()
$.fn.splitter.resizeBind()
Any ideas, where i am getting wrong?
;(function($){
$.fn.splitter = function(args){
//Other functions ......
$(window).bind("resize", function(){
resizeBind();
});
function resizeBind(){
var top = splitter.offset().top;
var wh = $(window).height();
var ww = $(window).width();
var sh = 0; // scrollbar height
if (ww <0 && !jQuery.browser.msie )
sh = 17;
var footer = parseInt($("#footer").css("height")) || 26;
splitter.css("height", wh-top-footer-sh+"px");
$("#tabsRight").css("height", splitter.height()-30+"px");
$(".contentTabs").css("height", splitter.height()-70+"px");
}
return this.each(function() {
});
};
})(jQuery);
I had the same problem. Those answers on related posts didn't work for my case either. I solved it in a round about way using events.
The example below demonstrates calling a function that multiplies three internal data values by a given multiplier, and returns the result. To call the function, you trigger an event. The handler in turn triggers another event that contains the result. You need to set up a listener for the result event.
Here's the plugin - mostly standard jQuery plugin architecture created by an online wizard:
(function($){
$.foo = function(el, options){
// To avoid scope issues, use 'base' instead of 'this'
var base = this;
// Access to jQuery and DOM versions of element
base.$el = $(el);
base.el = el;
// Add a reverse reference to the DOM object
base.$el.data("foo", base);
base.init = function(){
base.options = $.extend({},$.foo.defaultOptions, options);
// create private data and copy in the options hash
base.private_obj = {};
base.private_obj.value1 = (base.options.opt1);
base.private_obj.value2 = (base.options.opt2);
base.private_obj.value3 = (base.options.opt3);
// make a little element to dump the results into
var ui_element = $('<p>').attr("id","my_paragraph").html(base.private_obj.value1 +" "+ base.private_obj.value2+" " +base.private_obj.value3);
base.$el.append(ui_element);
// this is the handler for the 'get_multiplied_data_please' event.
base.$el.bind('get_multiplied_data_please', function(e,mult) {
bar = {};
bar.v1 = base.private_obj.value1 *mult;
bar.v2 = base.private_obj.value2 *mult;
bar.v3 = base.private_obj.value3 *mult;
base.$el.trigger("here_is_the_multiplied_data", bar);
});
};
base.init();
}
$.foo.defaultOptions = {
opt1: 150,
opt2: 30,
opt3: 100
};
$.fn.foo = function(options){
return this.each(function(){
(new $.foo(this, options));
});
};
})(jQuery);
So, you can attach the object to an element as usual when the document is ready. And at the same time set up a handler for the result event.
$(document).ready(function(){
$('body').foo();
$('body').live('here_is_the_multiplied_data', function(e, data){
console.log("val1:" +data.v1);
console.log("val2:" +data.v2);
console.log("val3:" +data.v3);
$("#my_paragraph").html(data.v1 +" "+ data.v2+" " +data.v3);
});
})
All that's left is to trigger the event and pass it a multiplier value
You could type this into the console - or trigger it from a button that picks out the multiplier from another UI element
$('body').trigger('get_multiplied_data_please', 7);
Disclaimer ;) - I'm quite new to jQuery - sorry if this is using a hammer to crack a nut.
resizeBind function is defined as private so you cannot access it from outside of it's scope. If you want to use it in other scopes you need to define it like that
$.fn.resizeBind = function() { ... }
Then you would call it like that $(selector').resizeBind()
You have defined the resizeBind function in a scope that is different from the global scope. If you dont'use another javascript framework or anything else that uses the $ function (to prevent conflict) you can delete the
(function($){
...
})(jQuery);
statement and in this way the function will be callable everywhere without errors
I didn't test it:
this.resizeBind = function() { .... }

Categories

Resources