I have the following module pattern in the javascript for a webpage:
var swf_debugger = swf_debugger || {};
(function($, swf_debugger) {
swf_debugger.pageSetup = (function() {
var
swfType = null,
formData = {},
// ... unimportant code continues ...
initChangeEvents = function() {
$.each(formElements, function(index, $el) {
if ($el.hasClass("swfToLoad")) {
// THIS EVENT IS WHERE I MAKE THE ASSIGNMENT TO 'swfType'
$el.change(function() {
swfType = $("option:selected", this).val();
console.log("swfToLoad has triggered");
console.log(swfType);
});
return;
}
// NO ISSUES HERE WITH THESE EVENTS...
switch($el.prop("tagName")) {
case "SELECT":
$el.change(function() {
formData[$el.attr('id')] = $("option:selected", this).val();
});
break;
case "INPUT":
switch ($el.attr('type')) {
case "text" :
$el.change(function() {
formData[$el.attr('id')] = $(this).val();
});
break;
case "checkbox" :
$el.change(function() {
formData[$el.attr('id')] = $(this).prop("checked");
});
break;
default:
}
break;
default:
}
});
},
init = function() {
$(function() {
addFormComponents();
populateDropdowns();
initCachedData();
initChangeEvents();
});
};
init();
return {
swfType: swfType,
formData: formData
};
}());
}($, swf_debugger));
Essentially I am attaching an event to a series of jquery selected elements, with the callback simply storing the contents of a particular form element (specifically select, text, and checkbox elements) in a variable or an object.
I know my events are attaching properly because when I add console.log statements to them I can see them firing. Also, whenever I call swf_debugger.pageSetup.formData in the console I see valid contents of the object that each of those events are populating, so those events are doing what they're supposed to.
My troubles are happening whenever I try to access swf_debugger.pageSetup.swfType it always returns null and I am not understanding why. I know that the particular event feeding this value, is firing and I know that at least within the function scope of the callback, swfType is valid because of what is returned in my console.log statements. However, whenever I try to access the contents of swfType through the closure, (i.e. typing swf_debugger.pageSetup.swfType in the console), It always returns null.
I am guessing that I am running into the difference between an objects reference being passed and a variables value being passed, but I am not sure. Can someone please help me along here and explain why swfType is always returning null through the closure.
why is swfType always returning null
Because that's the value which you assigned to the property (gotten from the swfType variable which had that value at the time of the assignment). A property is not a reference to the variable assigned to it - you can only assign a value.
What you can do:
make the object property a getter method which returns the value of the local swfType variable whenever it is called
don't use a variable but assign to the property of the swf_debugger.pageSetup object each time
Related
I have a function that creates a new record in the database and returns the ID of the record created. From this I need to assign a function to a select with the value of the returned ID.
.done(function(response) {
//console.log(siblings[1].dataset.contno);
var dbResponse = JSON.parse(response);
document.getElementById(runID).setAttribute('data-runid', dbResponse.id);
var newRunID = dbResponse.id;
var driverSelectID='driverSelectRun'+runCode;
//adding the onchange function with the correct ID to the dropdowns (the assignVehicle FUnction takes the runID which is unknown untill response)
(function(newRunID) {
document.getElementById(driverSelectID).onchange = function () {
assignDriver(newRunID);
}
})(newRunID);
(function(newRunID) {
document.getElementById(vehicleSelectID).onchange = function () {
assignVehicle(newRunID);
}
})(newRunID);
console.log(newRunID);
});
The console.log for the newRunID is 1566 but the onchange function of this select does not contain the new run id it simply shows as assignVehicle(newRunID) instead of actually using the value returned from the database (assignVehicle(1566)). I have used the exact same method on another part of the code which works fine. Can anyone see why this is not working correctly.
Thanks in advance for any replies!
<select id="driverSelectRun6">...</select>
That is the code for the select. It is definately being targeted correctly as the function is being set just without the arguement.
UPDATE
This was a scope issue. The newRunID was declared using var newRunID= but this was delcared in the .done function so was of local scope instead of global. removing the var to make it just newRunID= worked because assigning a value to an undeclared variable implicitly creates it as a global variable (it becomes a property of the global object)
The issue here is you are redefining the variable newRunID as an argument and that argument is actually the event object that is returned from the change event listener.
.onchange = function (newRunID) {
should be
.onchange = function () {
(function(newRunID) {
document.getElementById(driverSelectID).onchange = function () {
assignDriver(newRunID);
}
})(newRunID);
Hy I wrote a quick and dirty list ui in js and html that can be filtered:
https://jsfiddle.net/maxbit89/2jab4fa4/
So the usage of this looks like this (Fiddle line: 96):
var list = new ui_list(document.body, 200, 300, "Test");
var encoder = function(dom, value) {
console.log("begin encoding");
console.log(value)
dom.innerHTML = value.n;
}
list.add({'n': 1}, function() {
this.value.n++;
console.log(this.value.n);
// this.value = this.value;
}, encoder);
So what this basicaly does is create a List and adds a Element to it that has an Object: {'n': 1} as a value and a onClickHandler(second parameter on list.add) wich should increase the value by 1 (fiddle line: 104)
But it won't do this untill you uncomment the line 106 in the fiddle.
(Tested with FireFox 50.1.0, and Edge Browser)
Has any body an idea why js behaves like this?
In a much simplier example this works just fine:
var myObj= {
'onvalueChange' : function() {
console.log('value changed');
},
'print' : function() {
console.log('value:');
console.log(this.value);
console.log(this.value.n);
}
};
Object.defineProperty(myObj, "value", {
get: function() { return this._value; },
set: function(value) {
this.onvalueChange();
this._value = value;
}
});
myObj.value = {'n' : 1};
myObj.value.n++;
myObj.print();
First you have the setter defined like this:
set: function (value) {
this.encoder(this, value);
this._value = value;
}
that means that every time the value is set, the encoder will be called with the new value to update the equivalent DOM element.
But then inside the event listener function you have:
function() {
this.value.n++;
console.log(this.value.n);
//this.value = this.value;
}
where you think that this.value.n++ is setting the value (means it calls the setter which means the encoder will be called to update the DOM element). But it's not true. this.value.n++ is actually calling the getter. To explain more this:
this.value.n++;
is the same as:
var t = this.value; // call the getter
t.n++; // neither call the getter nor the setter. It just uses the reference (to the object) returned by the getter to set n
So, when you uncomment the line this.value = this.value;, the setter gets called, and the encoder gets called to update the DOM element.
So to fix the issue you have to either:
Make a call inside the getter to the encoder as you did for the setter (but this solution is very hacky as it will update the DOM element on every getter call even if nothing is being set).
Change this this.value.n++; to actually call the setter like: this.value = {n: this.value.n + 1}; (but this is hacky too as if value has a lot of key-value pairs then you have to enlist them all here just to set n).
Call the encoder inside the event listener which will be the best way to do it (or if you don't want to pass the parameters to it make another function (for example this.callEncoder()) that will call it and [you] call the new function instead inside the event listener).
I'm creating a custom combobox which uses jQuery validator.
At first they all are gray except the first (it means Country). When I choose 'Slovenská republika' the second combobox is enabled.
They all are instances of a a custom autocomplete combobox widget.
To enable the validation I use this code (which is called within _create: function(){..})
There you can find $.validator.addClassRules(); and $.validator.addMethod(). I also added the appropriate class so it really does something.
_registerCustomValidator: function(){
var uniqueName = this._getUniqueInstanceNameFromThisID(this.id);
var that = this;
console.log(this.id);//this prints 5 unique ids when the page is being loaded
$.validator.addMethod(uniqueName, function(value,element){
if(!that.options.allowOtherValue){
return that.valid;
}
console.log(that.id);//this always prints the ID of the last combobox StreetName
return true;
}, "Error message.");
var o = JSON.parse('{"'+uniqueName+'":"true"}');
$.validator.addClassRules("select-validator", o);
}
//this.id is my own property that I set in _create
Problem: When I change the value of any instance of combobox, it always prints the ID of the last instance StreetName, but it should belong to the one that has been changed.
I thought it might be because of registering $.validator.addMethod("someName",handler) using such a fixed string, so now I pass a uniqueName, but the problem remains.
Therefore the validation of all instances is based on the property allowOtherValue of the last instance.
I don't understand why it behaves so. Does anyone see what might be the problem?
EDIT:
see my comments in the following code
_registerCustomValidator is a custom function within a widget factory.
//somewhere a global var
var InstanceRegistry = [undefined];
//inside a widget factory
_registerCustomValidator: function(){
var i=0;
while(InstanceRegistry[i] !== undefined) ++i;
InstanceRegistry[i] = this.id;
InstanceRegistry[i+1] = undefined;
var ID = i; //here ID,i,InstanceRegistry are correct
$.validator.addMethod(uniqueName, function(value,element){
//here InstanceRegistry contains different values at different positions, so its correct
console.log("ID=="+ID);//ID is always 5 like keeping only the last assiged value.
var that = InstanceRegistry[ID];
if(!that.options.allowOtherValue){
return that.valid;
}
return true;
}, "Error message");
var o = JSON.parse('{"'+uniqueName+'":"true"}');
$.validator.addClassRules("select-validator", o);
}
It looks like a sneaky combination of closure logic and reference logic. The callback in $.validator.addMethod is enclosing a reference to this which will equal the last value of this when $.validator.addMethod. (Or something like that?)
Glancing at your code, it's not clear to me what this is in this context. So I can't really offer a concrete solution. But one solution might be to create some kind of global registry for your thises. Then you could do something along the lines of:
_registerCustomValidator: function(){
var uniqueName = this._getUniqueInstanceNameFromThisID(this.id);
$.validator.addMethod(uniqueName, function(value,element) {
var instance = InstanceRegistry[uniqueName];
if(! instance.options.allowOtherValue){
return instance.valid;
}
return true;
}, "Error message.");
var o = JSON.parse('{"'+uniqueName+'":"true"}');
$.validator.addClassRules("select-validator", o);
}
The registry could be keyed to uniqueName or id, just so long as it is a value getting enclosed in your callback.
Trying to parse through some livestream JSON data and see if an event has a specific tag.
If it doesn't then I'll use that data to output values, etc.
For whatever reason, upcoming_event isn't being assigned the event object (which is the return value of the findPublicEvent function.
The console.log of the event object works fine - but returning it doesn't work :/
// get our NLC data from livestream.
// -> note: need the '?callback=?' to convert to JSONP for cross-domain usage
var $uri = 'http://api.new.livestream.com/accounts/newlifechurchtv/?callback=?';
$.getJSON($uri, function(data) {
parseNLCData(data);
});
parseNLCData = function(nlc_data){
// set our variable to the return first event
// nlc_data.upcoming_events.data is a json array of events
window.upcoming_event = findPublicEvent(nlc_data.upcoming_events.data);
}
// should return single public event
function findPublicEvent (all_events) {
// if we have events
if (all_events) {
// loop through events to find public event
$.each(all_events, function(index,value){
// get all the tags, remove whitespace, and put into array
var $tags = value.tags.replace(/ /g, '').toLowerCase().split(',');
// check for privacy.
var $privacy = $.inArray('private', $tags);
if ($privacy === -1) {
// if the event isn't private -> return it!
console.log(value);
return value;
}
});
// otherwise .... ->
} else {
// we don't have events, sooo, no dice.
return false;
}
};
findPublicEvent isn't returning it. The anonymous function you pass to each is returning it.
Since it is the return value of findPublicEvent that you are capturing, you can't see it.
Define a variable in the scope of findPublicEvent
Assign a value to it from inside your anonymous function (using a regular assignment, not a return)
Return that variable from findPublicEvent
I have a function in testFun.js
var testFunc=function{
var hasCar=false;
return{
checkCar: function(){
for(var i=0; i<5; i++){
MyCar.check(hasCar); //defined in following MyCar.js
}
}
}
}();
Another function in MyCar.js
var MyCar=function(){
var createCar=function(){
//Create a car
//var car = new Car();
}
return{
check: function(hasCar){
console.log(hasCar); //PROBLEM HERE: fierBug console output is "false" always even CHECK_BUTTON has been clicked, why?
var handler=function(){
if(!hasCar)
createCar();
hasCar=true;
}
//CHECK_BUTTON is a button on UI
CHECK_BUTTON.click(handler);
}
}
}();
As you see in MyCar module pattern, when CHECK_BUTTON is clicked, I will call createCar() function, and change hasCar value from false to true.
The hasCar is defined in testFunc module pattern, and pass to MyCar.check(hascar).
I have a fireBug console output inside MyCar.check() function, I expect that when mouse clicked the CHECK_BUTTON, the console output will be true, but I always get false, why? I use hasCar to check is the CHECK_BUTTON has been clicked once or not, but if it always hold false value, I cannot know it. How to get rid of this?
Primitive values are passed by value and not by reference.
Thus assigning true to hasCar inside the click event handler (in MyCar.check) does not change the value of hasCar defined in testFunc.
You could use an object instead:
var testFunc= (function(){
var hasCar = {val: false};
// ...
}());
var MyCar=(function(){
//...
return{
check: function(hasCar){
console.log(hasCar.val);
var handler=function(){
if(!hasCar.val)
createCar();
hasCar.val=true;
}
CHECK_BUTTON.click(handler);
}
}
}());
Or define the hasCar variable in a scope accessible to both functions.
Update:
You will still get false in the console because hasCar.val is only set to true after you clicked the button and you never call console.log when you click the button.
Using a loop here does not tell you anything. More appropriate for testing would be, given your setup:
var hasCar = {val: false};
MyCar.check(hasCar); // will output false
// now click the button
MyCar.check(hasCar); // will output true
Have a look at this DEMO.
Further notes:
For better compatibility, surround your self invoking functions with parenthesis.
You are adding a new click handler every time MyCar.ceck is called. I don't think this is desired.
The structure of your application is quite confusing (at least for me ;)).