High frequency updates to PouchDB (Document update conflict) - javascript

I have a method for getting/updating state that's stored within PouchDB. This method gets called by a constructor of an element to assign a user-friendly unique tag to the element. The simplified version of the code looks like this:
var tagList = [ /* set of dictionary words to cycle through */ ];
function generateTag(id) {
return db.get('tags').then(function (tagData) {
var tag = '', remainder = tagData.tagCount, quotient;
while (remainder >= tagList.length) {
quotient = Math.floor(remainder / tagList.length);
tag += tagList[quotient - 1];
remainder -= tagList.length * quotient;
}
tag += tagList[remainder];
tag = tag.charAt(0).toLowerCase() + tag.slice(1);
tagData.tagCount++;
tagData.tags[tag] = id;
db.put(tagData);
return tag;
}).catch(function (err) {
console.error(err);
});
}
class Element {
constructor() {
var self = this;
generateTag('element' + Date.now()).then(function (tag) {
self.tag = tag;
});
}
}
This logic works as expected when there is a delay between creating elements. But when elements are created in a rapid burst (i.e. a for loop), the db.get call for 2nd, 3rd, and consecutive elements gets called before the db.put operation for first element finishes, resulting in "Document update conflict" messages for consecutive elements. At first I thought PouchDB's conflict resolution would automatically handle this for me, but I was wrong.
Maybe I'm not understanding proper way of handling such cases, or is there a better way of writing this? What I need is for consecutive db.get calls to effectively block until the ongoing db.put from previous operation finishes. I was thinking perhaps even keeping a static link to the promise corresponding to last PouchDB operation on 'tags' object, such that instead of db.get('tags') I'd run tagsPromise.then(function () { return db.get('tags'); }), but I'm still a rookie with promises and don't know if that would be a desirable way of addressing this issue or if this issue is even a real issue or something I imposed on myself by not sticking with a better approach?
UPDATE:
It looks like modifying the logic to always return a promise and always start with a "singleton" promise instead of db.get('tags') in generateTag function as I mentioned does fix the issue, still want to understand if there is a better approach.

To others interested, this is how I rewrote the above logic using the tagPromise approach I mentioned in the update (if there is a better answer from PouchDB experts, I'll accept that instead):
var tagList = [ /* set of dictionary words to cycle through */ ];
var tagPromise = db.get('tags');
function generateTag(id, callback) {
tagPromise = tagPromise.then(function() {
return db.get('tags');
}).then(function (tagData) {
var tag = '', remainder = tagData.tagCount, quotient;
while (remainder >= tagList.length) {
quotient = Math.floor(remainder / tagList.length);
tag += tagList[quotient - 1];
remainder -= tagList.length * quotient;
}
tag += tagList[remainder];
tag = tag.charAt(0).toLowerCase() + tag.slice(1);
tagData.tagCount++;
tagData.tags[tag] = id;
callback(tag);
return db.put(tagData);
}).catch(function (err) {
console.error(err);
});
}
class Element {
constructor() {
var self = this;
generateTag('element' + Date.now(), function (tag) {
self.tag = tag;
});
}
}

I find it hard to follow what you are trying to achieve. Could you post the tags document and examples of the elements and tags?
At first glance, getting and updating a single 'tags' document looks quite smelly, I think it would make much more sense to use a view. But for an informed response, I'd need more details please! Thanks!

Related

Getting lost understanding the role of this callback function (BFS, width level for trees)

I'm currently working with and learning trees and have been dealing with different implementations of traversals.
class Node {
constructor(data) {
this.data = data;
this.children = [];
}
add(data) {
this.children.push(new Node(data));
}
remove(data) {
this.children = this.children.filter(node => {
return node.data !== data;
})
}
}
traverseBF(fn) {
const arr = [this.root];
while (arr.length) {
const node = arr.shift();
arr.push(...node.children);
fn(node); //what role does this play?
}
return count;
}
traverseDF(fn) {
const arr = [this.root];
while (arr.length) {
const node = arr.shift();
arr.unshift(...node.children);
fn(node); //what role does this play???
}
}
I thought I had come to understand that the callback has the context where it's declared in and is able to access variables in the outer function and what I thought was the reason why arr stays up to date and that the callback function was integral for BFS/DFS to work in this instance. However, learning about calculating width levels broke my understanding.
function levelWidth(root) {
const arr = [root, 's'];
const counters = [0];
while (arr.length > 1) {
const node = arr.shift();
if (node === 's') {
counters.push(0);
arr.push('s');
} else {
arr.push(...node.children);
counters[counters.length - 1]++;
}
}
return counters;
}
There's no callback here yet this BFS search and the traversal works fine. Can anyone help me understand better why it was needed in the first instance and not this instance??
What exactly happens when I call the traversal like so?
const letters = [];
const t = new Tree();
t.root = new Node('a');
t.root.add('b');
t.root.add('d');
t.root.children[0].add('c');
t.root.children[1].add('e');
t.traverseBF(node => {
letters.push(node.data);
});
console.log(letters);
There is no wrong or right here.
The callback version differs in two ways:
It does not apply any logic using the visited nodes. It only takes care of the traversal, not of any other logic. Any specific logic is left to the caller who can pass a callback exactly for that purpose. In your final example, that specific logic consists of collecting the node's data values into an array. But note that the traversal function is unaware of this logic, which is a nice separation of concern.
NB: the return count at the end of traverseBF(fn) should not be there (there is no count)
It does not keep the caller waiting until all nodes are visited.
The non-callback version, not only visits the nodes, but also takes care of a specific processing on those nodes (i.e some counting), and it returns the result of that processing only. This is much less generic. If you want a traversal for a completely different purpose, you cannot use this function, as it really does not tell the caller about the nodes it has visited, nor the order in which this happened.
You could also imagine a kind of "in-between" traversal implementation: one that does not use a callback, but just collects all the visited nodes in an array, and then returns that complete array of nodes in the order that they were visited. This is more generic, but the caller must wait until all nodes have been visited before it can start applying its own algorithm on that returned array of nodes.
So, I would say the callback version is more flexible and generic.
However, the more modern way to implement such a generic traversal is not via a callback system, but as a generator.
Here is how that would look (notice the initial *)
* traverseBF() {
const arr = [this.root];
while (arr.length) {
const node = arr.shift();
arr.push(...node.children);
yield node; // <---
}
}
* traverseDF() {
const arr = [this.root];
while (arr.length) {
const node = arr.shift();
arr.unshift(...node.children);
yield node; // <---
}
}
The caller must be aware of the fact that these methods are generators, but you can use a for loop like this:
let letters = [];
for (let node of t.traverseDF()) {
// do something with this node before continuing the traversal
letters.push(node.data);
}
console.log(letters);
The additional advantage here is that the caller can always decide to discontinue the traversal. In the above code, an early break from the loop would really mean that the traversal would not be completed any further. For all the other methods mentioned earlier, you would have to trigger an exception to make that possible; in all other cases the traversal would have to run until completion.

Is it possible to check if a CSS Transition is finished WITHOUT using events?

Is it possible to somehow via JavaScript verify that a CSS-Transition is finished without previsouly registering for the transition events?
The problem is:
I have a Web Application that uses CSS transitions for fading in some elements on page load
I cannot modify this Web Applications JavaScript code
When I access this page I can execute JavaScript in the browser console
I wanna ensure that the CSS transition is 100% finished before I continue with my custom java script code
in browser console I could hook to the transition event, but this would fail in a lot of cases because:
transitioning element is not there yet
animation is already finished when I set up the hook
Is there any possibility to check via JavaScript if the CSS transition for the element is done? At any time?
I cannot make use of javascript events (like e.g: https://jonsuh.com/blog/detect-the-end-of-css-animations-and-transitions-with-javascript/)
No.
The best you can do is looking at the CSS to see the transition duration.
Not an Answer but a quick POC to build upon:
element.onclick = function() {
const color = 0x1000 | (Math.random() * 0x1000);
const prop = Math.random() < .5? "background-color": "color";
element.style[prop] = "#" + color.toString(16).slice(-3);
}
let curr = {};
requestAnimationFrame(function check() {
const prev = curr;
curr = Object.assign({}, getComputedStyle(element));
const changed = Object.keys(curr).filter(key => curr[key] !== prev[key]);
out.innerHTML = changed.map(key => `<li>${key}</li>`).join("\n");
requestAnimationFrame(check);
});
html,
body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
#element {
cursor: pointer;
width: 100%;
height: 100%;
padding: 20px;
transition: all 1s;
}
<div id="element">
<p>Click somewhere</p>
currently transitioning:
<ul id="out"></ul>
</div>
you'll notice the flickering, that's because two adjacent frames might not differ during that interpolation. You'd want to cache more frames and compare something like 5-10 frames apart; depends on the interpolation method you use and the duration of a transition.
Besides that, depending on the properties you're checking you could also compare getComputedStyle(element)[key] against element.style[key] instead of storing the values for multiple frames. But this doesn't work out for color (and others), as there are so many ways to describe the same color.
IT IS POSSIBLE
TIMED CHAINS;
Sorry for the delay, was in a meeting. Searched for one of my old projects but could not find. I'll sketch the idea here, I initially thought perhaps we could go for Mutation Observer, however we have to do periodic checks there as well. So I guess this one will do. First you should avoid some things:
calling getComputedStyle every frame, it is a bad idea, because that is a very expensive func to call and triggers layout, so you should throttle instead.
hardcopying style object, that is a hefty object to copy, so instead you should pass an argument for a specific property
working with node reference, if the node is not there as you said, this will throw reference error, instead use a function to return the node.
The first thing is to write a helper function, a function that would periodically run some test function and return if it is successful:
function watchman(obj,test,action,options){
var currentFrame = {value:undefined};
function watch(){
if(test.call(obj,options)){
action.call(obj,options);
return;
} else {
currentFrame.value = window.requestAnimationFrame(watch);
}
};
currentFrame.value = window.requestAnimationFrame(watch);
return currentFrame;
};
Next is the actual function, I'd say no need to create new object, we can make a function with 3 (2 optional) arguments, the node "functor", the style property to check and lastly the function to call.
function onTransitionEnd(fNode,prop,f,precision,timeout){
precision = precision || 5;
timeout = timeout || Infinity;
return new Promise(function(res){
var node = fNode(),
oValue = node && getComputedStyle(node)[prop],
currentFrame = watchman(
fNode,
function(counter){
if(counter.counter * 17 >= timeout){
window.cancelAnimationFrame(currentFrame.value);
}
if(++counter.counter % precision === 0) {
if(!this()){return}
var nValue = getComputedStyle(this())[prop];
if(nValue === oValue) {
return true;
}
oValue = nValue;
}
},
function(counter){
res(f.call(fNode(),prop));
},
{counter:0}
);
});
};
Default precision of 5 means the function will check every 5 ticks, 5 * 17 milliseconds the values to determine if transition ended. Timeout is optional too, it will cancel running after a certain period.
It is not a problem if the node is NOT there, as we are passing a function that returns the node or null, if the node is not there, it will not execute.
Above is a promise and would return a "thenable" object which you can chain as you like.
Simple use case, for instance right after you change a style or class:
document.getElementById("someDiv").className = "def c1";
onTransitionEnd(
function(){return document.getElementById("someDiv");},
"transform",
function(){alert("heyy!!");}
);
it would now alert you "heyy". To chain this:
document.getElementById("someDiv").className = "def c1";
onTransitionEnd(
function(){return document.getElementById("someDiv");},
"transform",
function(prop){alert("heyy!!"); return this;}
).then(function(node){
node.className = "def";
return onTransitionEnd(
function(){return document.getElementById("someDiv");},
"transform",
function(){alert("heyy-2!!"); return this;}
);
}).then(function(node){
alert("id is " + node.id);
});
Here are some examples:
basic;
chain;
node is not there yet;
for the last one to work, open the developer console, select the blue div and change its id to "someDiv", the function will execute.
You might wonder whether to call onTransitionEnd each time you change the style, in that case you can write a wrapper. If you don't have an idea let me know I'll write that too.
Obviously you did not use a wrapper, so here is a helper wrapper:
function Select(node){
this.node = node;
};
Select.prototype.style = function(prop,value,f){
var node = this.node;
this.node.style[prop] = value;
f && onTransitionEnd(
function(){return node;},
prop,
f
);
return this;
};
Here is how you would use it:
var selection = new Select(document.getElementById("someDiv"));
selection
.style("width","100px",function(propName){alert(propName + " changed!");})
.style("height","100px",function(propName){alert(propName + " changed!");})
.style("transform","scale(0.5,0.5)",function(propName){alert(propName + " changed!");});
And here is the EXAMPLE;
TIMED CHAINS;

RxJS, how to poll an API to continuously check for updated records using a dynamic timestamp

I am new to RxJS and I am trying to write an app that will accomplish the following things:
On load, make an AJAX request (faked as fetchItems() for simplicity) to fetch a list of items.
Every second after that, make an AJAX request to get the items.
When checking for new items, ONLY items changed after the most recent timestamp should be returned.
There shouldn't be any state external to the observables.
My first attempt was very straight forward and met goals 1, 2 and 4.
var data$ = Rx.Observable.interval(1000)
.startWith('run right away')
.map(function() {
// `fetchItems(modifiedSince)` returns an array of items modified after `modifiedSince`, but
// I am not yet tracking the `modifiedSince` timestamp yet so all items will always be returned
return fetchItems();
});
Now I'm excited, that was easy, it can't be that much harder to meet goal 3...several hours later this is where I am at:
var modifiedSince = null;
var data$ = Rx.Observable.interval(1000)
.startWith('run right away')
.flatMap(function() {
// `fetchItems(modifiedSince)` returns an array of items modified after `modifiedSince`
return fetchItems(modifiedSince);
})
.do(function(item) {
if(item.updatedAt > modifiedSince) {
modifiedSince = item.updatedAt;
}
})
.scan(function(previous, current) {
previous.push(current);
return previous;
}, []);
This solves goal 3, but regresses on goal 4. I am now storing state outside of the observable.
I'm assuming that global modifiedSince and the .do() block aren't the best way of accomplishing this. Any guidance would be greatly appreciated.
EDIT: hopefully clarified what I am looking for with this question.
Here is another solution which does not use closure or 'external state'.
I made the following hypothesis :
fetchItems returns a Rx.Observable of items, i.e. not an array of items
It makes use of the expand operator which allows to emit values which follow a recursive relationship of the type x_n+1 = f(x_n). You pass x_n+1 by returning an observable which emits that value, for instance Rx.Observable.return(x_n+1) and you can finish the recursion by returning Rx.Observable.empty(). Here it seems that you don't have an ending condition so this will run forever.
scan also allows to emit values following a recursive relationship (x_n+1 = f(x_n, y_n)). The difference is that scan forces you to use a syncronous function (so x_n+1 is synchronized with y_n), while with expand you can use an asynchronous function in the form of an observable.
Code is not tested, so keep me updated if this works or not.
Relevant documentation : expand, combineLatest
var modifiedSinceInitValue = // put your date here
var polling_frequency = // put your value here
var initial_state = {modifiedSince: modifiedSinceInitValue, itemArray : []}
function max(property) {
return function (acc, current) {
acc = current[property] > acc ? current[property] : acc;
}
}
var data$ = Rx.Observable.return(initial_state)
.expand (function(state){
return fetchItem(state.modifiedSince)
.toArray()
.combineLatest(Rx.Observable.interval(polling_frequency).take(1),
function (itemArray, _) {
return {
modifiedSince : itemArray.reduce(max('updatedAt'), modifiedSinceInitValue),
itemArray : itemArray
}
}
})
You seem to mean that modifiedSince is part of the state you carry, so it should appear in the scan. Why don-t you move the action in do into the scan too?. Your seed would then be {modifiedSince: null, itemArray: []}.
Errr, I just thought that this might not work, as you need to feed modifiedSince back to the fetchItem function which is upstream. Don't you have a cycle here? That means you would have to use a subject to break that cycle. Alternatively you can try to keep modifiedSince encapsulated in a closure. Something like
function pollItems (fetchItems, polling_frequency) {
var modifiedSince = null;
var data$ = Rx.Observable.interval(polling_frequency)
.startWith('run right away')
.flatMap(function() {
// `fetchItems(modifiedSince)` returns an array of items modified after `modifiedSince`
return fetchItems(modifiedSince);
})
.do(function(item) {
if(item.updatedAt > modifiedSince) {
modifiedSince = item.updatedAt;
}
})
.scan(function(previous, current) {
previous.push(current);
return previous;
}, []);
return data$;
}
I have to run out to celebrate the new year, if that does not work, I can give another try later (maybe using the expand operator, the other version of scan).
How about this:
var interval = 1000;
function fetchItems() {
return items;
}
var data$ = Rx.Observable.interval(interval)
.map(function() { return fetchItems(); })
.filter(function(x) {return x.lastModified > Date.now() - interval}
.skip(1)
.startWith(fetchItems());
That should filter the source only for new items, plus start you off with the full collection. Just write the filter function to be appropriate for your data source.
Or by passing an argument to fetchItems:
var interval = 1000;
function fetchItems(modifiedSince) {
var retVal = modifiedSince ? items.filter( function(x) {return x.lastModified > modifiedSince}) : items
return retVal;
}
var data$ = Rx.Observable.interval(interval)
.map(function() { return fetchItems(Date.now() - interval); })
.skip(1)
.startWith(fetchItems());

Object to 'hook' into jQuery function, possible?

I am currently running into the following issue, which I'd like to solve more elegantly:
My script works as follows:
Takes an element
Puts the element into a container (defined as var container = $('<div></div>') by using .append()
Keeps track of how far the container is 'filled'
If the container is full, clone a new container and continue there
Repeat this until every element is processed
Right now, this requires me to keep track of a 'fill' (and a 'max') variable to determine how far the container has been filled. So each time I do an append(), I have to increment these counters.
Now, what to me would be more elegant is making the container object smart, and enabling it to 'hook' into the append() event: whenever something is appended, the container object itself executes some code (incrementing its own counter, deciding if it is full, and if it is, returning a new, empty container).
I thought of solving it this way, by creating a function that returns a container:
var container = {
template : $('<div class="container"></div>'),
containers : [],
get : function (i) {
if (!this.containers[i]) {
this.containers[i] = this.template.clone()
.addClass('container-'+i)
.data('max', 500); //this determines the maximum (px) the container can hold
}
return this.containers[i];
}
};
This works, as I can now iterate over all the elements, and call container.get(i).append(element) for each one (while keeping count of height seperately and comparing that to container().get(i).data().max) and later in the script, when I need the output, I can return the container.containers object.
But I can't get it to work having the container.get function to 'watch' for an append() and act on it. I know this is not the way jQuery is meant to work, but I am sure there is another way of doing it, other than keeping local counters in the element iterator.
One other thing I tried is trying to set .on($.append, function() { //do stuff }); on the container, but that was a pipe dream..
I hope I have explained everything clearly, and would love to know if someone has a solution to this.
See this fiddle for a 'working' example (I highly doubt my programming skills)
Maybe you need something like this:
(function($)
{
var oldappend = $.fn.append;
var count = 0;
$.fn.newAppend = function()
{
var ret = oldappend.apply(this, arguments);
//your logic goes here
// count++;
return ret;
};
})(jQuery);
Or you need to store the count variable per container:
(function($)
{
var oldappend = $.fn.append;
$.fn.newAppend = function()
{
var ret = oldappend.apply(this, arguments);
//your logic goes here
if (!this.count){
this.count = 0;
}
this.count++;
return ret;
};
})(jQuery);
Use it:
$('<div class="container"></div>').newAppend(yourElement);

Javascript function objects

I edited the question so it would make more sense.
I have a function that needs a couple arguments - let's call it fc(). I am passing that function as an argument through other functions (lets call them fa() and fb()). Each of the functions that fc() passes through add an argument to fc(). How do I pass fc() to each function without having to pass fc()'s arguments separately? Below is how I want it to work.
function fa(fc){
fc.myvar=something
fb(fc)
}
function fb(fc){
fc.myothervar=something
fc()
}
function fc(){
doessomething with myvar and myothervar
}
Below is how I do it now. As I add arguments, it's getting confusing because I have to add them to preceding function(s) as well. fb() and fc() get used elsewhere and I am loosing some flexibility.
function fa(fc){
myvar=something
fb(fc,myvar)
}
function fb(fc,myvar){
myothervar=something
fc(myvar,myothervar)
}
function fc(myvar,myothervar){
doessomething with myvar and myothervar
}
Thanks for your help
Edit 3 - The code
I updated my code using JimmyP's solution. I'd be interested in Jason Bunting's non-hack solution. Remember that each of these functions are also called from other functions and events.
From the HTML page
<input type="text" class="right" dynamicSelect="../selectLists/otherchargetype.aspx,null,calcSalesTax"/>
Set event handlers when section is loaded
function setDynamicSelectElements(oSet) {
/**************************************************************************************
* Sets the event handlers for inputs with dynamic selects
**************************************************************************************/
if (oSet.dynamicSelect) {
var ySelectArgs = oSet.dynamicSelect.split(',');
with (oSet) {
onkeyup = function() { findListItem(this); };
onclick = function() { selectList(ySelectArgs[0], ySelectArgs[1], ySelectArgs[2]) }
}
}
}
onclick event builds list
function selectList(sListName, sQuery, fnFollowing) {
/**************************************************************************************
* Build a dynamic select list and set each of the events for the table elements
**************************************************************************************/
if (fnFollowing) {
fnFollowing = eval(fnFollowing)//sent text function name, eval to a function
configureSelectList.clickEvent = fnFollowing
}
var oDiv = setDiv(sListName, sQuery, 'dynamicSelect', configureSelectList); //create the div in the right place
var oSelected = event.srcElement;
if (oSelected.value) findListItem(oSelected)//highlight the selected item
}
Create the list
function setDiv(sPageName, sQuery, sClassName, fnBeforeAppend) {
/**************************************************************************************
* Creates a div and places a page in it.
**************************************************************************************/
var oSelected = event.srcElement;
var sCursor = oSelected.style.cursor; //remember this for later
var coords = getElementCoords(oSelected);
var iBorder = makeNumeric(getStyle(oSelected, 'border-width'))
var oParent = oSelected.parentNode
if (!oParent.id) oParent.id = sAutoGenIdPrefix + randomNumber()//create an ID
var oDiv = document.getElementById(oParent.id + sWindowIdSuffix)//see if the div already exists
if (!oDiv) {//if not create it and set an id we can use to find it later
oDiv = document.createElement('DIV')
oDiv.id = oParent.id + sWindowIdSuffix//give the child an id so we can reference it later
oSelected.style.cursor = 'wait'//until the thing is loaded
oDiv.className = sClassName
oDiv.style.pixelLeft = coords.x + (iBorder * 2)
oDiv.style.pixelTop = (coords.y + coords.h + (iBorder * 2))
XmlHttpPage(sPageName, oDiv, sQuery)
if (fnBeforeAppend) {
fnBeforeAppend(oDiv)
}
oParent.appendChild(oDiv)
oSelected.style.cursor = ''//until the thing is loaded//once it's loaded, set the cursor back
oDiv.style.cursor = ''
}
return oDiv;
}
Position and size the list
function configureSelectList(oDiv, fnOnClick) {
/**************************************************************************************
* Build a dynamic select list and set each of the events for the table elements
* Created in one place and moved to another so that sizing based on the cell width can
* occur without being affected by stylesheet cascades
**************************************************************************************/
if(!fnOnClick) fnOnClick=configureSelectList.clickEvent
if (!oDiv) oDiv = configureSelectList.Container;
var oTable = getDecendant('TABLE', oDiv)
document.getElementsByTagName('TABLE')[0].rows[0].cells[0].appendChild(oDiv)//append to the doc so we are style free, then move it later
if (oTable) {
for (iRow = 0; iRow < oTable.rows.length; iRow++) {
var oRow = oTable.rows[iRow]
oRow.onmouseover = function() { highlightSelection(this) };
oRow.onmouseout = function() { highlightSelection(this) };
oRow.style.cursor = 'hand';
oRow.onclick = function() { closeSelectList(0); fnOnClick ? fnOnClick() : null };
oRow.cells[0].style.whiteSpace = 'nowrap'
}
} else {
//show some kind of error
}
oDiv.style.width = (oTable.offsetWidth + 20) + "px"; //no horiz scroll bars please
oTable.mouseout = function() { closeSelectList(500) };
if (oDiv.firstChild.offsetHeight < oDiv.offsetHeight) oDiv.style.height = oDiv.firstChild.offsetHeight//make sure the list is not too big for a few of items
}
Okay, so - where to start? :) Here is the partial function to begin with, you will need this (now and in the future, if you spend a lot of time hacking JavaScript):
function partial(func /*, 0..n args */) {
var args = Array.prototype.slice.call(arguments, 1);
return function() {
var allArguments = args.concat(Array.prototype.slice.call(arguments));
return func.apply(this, allArguments);
};
}
I see a lot of things about your code that make me cringe, but since I don't have time to really critique it, and you didn't ask for it, I will suggest the following if you want to rid yourself of the hack you are currently using, and a few other things:
The setDynamicSelectElements() function
In this function, you can change this line:
onclick = function() { selectList(ySelectArgs[0], ySelectArgs[1], ySelectArgs[2]) }
To this:
onclick = function() { selectList.apply(null, ySelectArgs); }
The selectList() function
In this function, you can get rid of this code where you are using eval - don't ever use eval unless you have a good reason to do so, it is very risky (go read up on it):
if (fnFollowing) {
fnFollowing = eval(fnFollowing)
configureSelectList.clickEvent = fnFollowing
}
And use this instead:
if(fnFollowing) {
fnFollowing = window[fnFollowing]; //this will find the function in the global scope
}
Then, change this line:
var oDiv = setDiv(sListName, sQuery, 'dynamicSelect', configureSelectList);
To this:
var oDiv = setDiv(sListName, sQuery, 'dynamicSelect', partial(configureSelectListAlternate, fnFollowing));
Now, in that code I provided, I have "configureSelectListAlternate" - that is a function that is the same as "configureSelectList" but has the parameters in the reverse order - if you can reverse the order of the parameters to "configureSelectList" instead, do that, otherwise here is my version:
function configureSelectListAlternate(fnOnClick, oDiv) {
configureSelectList(oDiv, fnOnClick);
}
The configureSelectList() function
In this function, you can eliminate this line:
if(!fnOnClick) fnOnClick=configureSelectList.clickEvent
That isn't needed any longer. Now, I see something I don't understand:
if (!oDiv) oDiv = configureSelectList.Container;
I didn't see you hook that Container property on in any of the other code. Unless you need this line, you should be able to get rid of it.
The setDiv() function can stay the same.
Not too exciting, but you get the idea - your code really could use some cleanup - are you avoiding the use of a library like jQuery or MochiKit for a good reason? It would make your life a lot easier...
A function's properties are not available as variables in the local scope. You must access them as properties. So, within 'fc' you could access 'myvar' in one of two ways:
// #1
arguments.callee.myvar;
// #2
fc.myvar;
Either's fine...
Try inheritance - by passing your whatever object as an argument, you gain access to whatever variables inside, like:
function Obj (iString) { // Base object
this.string = iString;
}
var myObj = new Obj ("text");
function InheritedObj (objInstance) { // Object with Obj vars
this.subObj = objInstance;
}
var myInheritedObj = new InheritedObj (myObj);
var myVar = myInheritedObj.subObj.string;
document.write (myVar);
subObj will take the form of myObj, so you can access the variables inside.
Maybe you are looking for Partial Function Application, or possibly currying?
Here is a quote from a blog post on the difference:
Where partial application takes a function and from it builds a function which takes fewer arguments, currying builds functions which take multiple arguments by composition of functions which each take a single argument.
If possible, it would help us help you if you could simplify your example and/or provide actual JS code instead of pseudocode.

Categories