AudioContext is not setting the gain properly in JavaScript - javascript

I am trying to set volume over 1 on an audio element, following this article.
https://cwestblog.com/2017/08/17/html5-getting-more-volume-from-the-web-audio-api/
I need to be able to set it more than once, so i've set a global array to store the different results in so that I can adjust based on participants.
This does not seem to be working though, to me I can't tell any difference when the gain is set, am I doing something wrong?
window.audioGainParticipants = [];
function amplifyMedia(participant_id, mediaElem, multiplier) {
const exists = window.audioGainParticipants.find(
(x) => x.participant_id === participant_id
);
let result = null;
if (exists) {
result = exists.result;
} else {
var context = new (window.AudioContext || window.webkitAudioContext)();
result = {
context: context,
source: context.createMediaElementSource(mediaElem),
gain: context.createGain(),
media: mediaElem,
amplify: function (multiplier) {
result.gain.gain.value = multiplier;
},
getAmpLevel: function () {
return result.gain.gain.value;
},
};
result.source.connect(result.gain);
result.gain.connect(context.destination);
window.audioGainParticipants.push({ participant_id, result });
}
result.amplify(multiplier);
}
I call this like this...
const audioElement = document.getElementById(
`audio-${participantId}`
);
amplifyMedia(
`audio-${participantId}`,
audioElement,
volume // number between 0-2
);

That article might be outdated. You're not supposed to directly assign to the gain's value, but instead use a setter method.
setValueAtTime is pretty simple, and should meet your needs. The docs show an example of calling this method on a gain node.
https://developer.mozilla.org/en-US/docs/Web/API/AudioParam/setValueAtTime
There's also setTargetAtTime which is a tiny bit more complex, but should sound better if you need to change settings on something that is currently playing.
https://developer.mozilla.org/en-US/docs/Web/API/AudioParam/setTargetAtTime

Related

Polling with RxJS that can recover missed events

I am trying to use RxJS to poll for events. However, I only have access to one function, which is getEvent(). I can do 2 things with the getEvent function:
getEvent("latest") — this will give me the latest event object
getEvent(eventId) - I pass in an integer and it will give me the event object corresponding to the eventId.
Event IDs always increment from 0, but the problem is, if my polling interval isn't small enough, I might miss events.
For example, if I do a getEvent("latest") and I get an event that has an ID of 1, that's great. But if the next time I call it, I get an ID of 3, I know that I missed an event.
In this case, I want to use a higher-order observable to call getEvent(2) and getEvent(3) so that the consumer of the stream I am creating won't have to worry about missing an event.
Right now, all I have is something like this:
timer(0, 500).pipe(
concatMap(() => from(getEvent("latest"))
)
For some context, I'm working off of this blogpost: https://itnext.io/polling-using-rxjs-b56cd3531815
Using expand to recursively call GET fits here perfectly. Here is an example with DEMO:
const source = timer(0, 2000)
const _stream = new Subject();
const stream = _stream.asObservable();
const s1 = source.pipe(tap(random)).subscribe()
const sub = stream.pipe(
startWith(0),
pairwise(),
concatMap((v: Array<number>) => {
let missing = v[1] - v[0];
return missing ? getMissing(v[0], missing) : EMPTY
})
).subscribe(console.log)
function getMissing(start, count) {
return getById(start).pipe(
expand(id => getById(id+1)),
take(count)
)
}
// helper functions for DEMO
let i = 1;
function random() {. // THIS IS YOUR getEvent('latest')
if (i < 10) {
i+=2;
_stream.next(i
// (Math.floor(Math.random() * 8))
)
}
}
function getById(id) {. // THIS IS YOUR getEvent(eventId)
return of(id).pipe(delay(1000)) // delay to mimic network
}

How to show percent done in terminal

Good day all.
Today i'm working in this complex script that makes request's to a site with server-side rendering, get's the HTML, breaks and grabs some data. The script has 4 phases: phaseOne, phaseTwo, phaseThree and phaseFour.
Which phases has a similar interface:
class PhaseOne {
constructor(MAP) {
this.MAP = MAP || MAP;
}
// All code related with the phase here.
process() {}
}
So i'm working upon this MAP object in all phases, and i'm calling each phase in a stack, like this:
let phases = require('./phases');
[
// 'Initial',
'PhaseOne',
'PhaseTwo',
'PhaseThree',
'PhaseFour',
].reduce((m, fn) => {
return new phases[fn](m).process();
}, MAP);
Everything is working fine. My problem is: Some phases are REALLY slow.. all the program will take 30 minutes to finish.. and i would like to see in my terminal the percentage of each phase.
Like:
PhaseOne: 10%
PhaseOne: 11%
PhaseOne: 12%
But i don't have any idea and i can't find a good tutorial to do that..
Currently inside my process functions i have for loops, if statements.. in general i'm using imperative style..
An example of PhaseOne:
// In this phase we need the property string in MAP.aguia01 to
// assign the first context and extract the data with regex.
if (typeof this.MAP.aguia01.string === 'undefined') {
cli.fatal(
'MAP doesn\'t have the necessary properties to run in Aguia01 phase. Exiting...'
);
}
for (let month of months) {
this.MAP.aguia01.string += requests.aguia01.apply(this, [month]);
}
for (let info of patterns.aguia01.infos) {
this.MAP.aguia01.infos[info.name] = (
this.MAP.aguia01.string.match(info.pattern)
)[1];
}
for (let line of patterns.aguia01.lines) {
this.MAP.aguia01.lines[line.name] = (
this.MAP.aguia01.string.match(line.pattern)
)[1];
}
So.. Is it possible to do what i want with imperative style?
Thanks.
There is the progress package but it's only up to you how you define "progress". You define a number of ticks corresponding to the completed state and then, you just call a method on the progress bar to make it "progress". An example:
var ProgressBar = require('progress');
// 10 ticks to complete the task
var bar = new ProgressBar(':bar', { total: 10 });
var timer = setInterval(function () {
// make the bar tick(), each call will make a 10% progress
bar.tick();
if (bar.complete) {
console.log('\ncomplete\n');
clearInterval(timer);
}
}, 100);
How about keeping a context object for progress outside of your reduce call? You could make it an event emitter, and then pass it in to your process function. Inside your process function you could emit progress events, which could then be logged. Perhaps something like this:
let phases = require('./phases');
//set up
let progressMonitor = new require('events')
progressMonitor.on('progress', percentDone => {
console.log(percentDone + '% done')
})
// your existing code
[
// 'Initial',
'PhaseOne',
'PhaseTwo',
'PhaseThree',
'PhaseFour',
].reduce((m, fn) => {
return new phases[fn](m).process(progressMonitor);
}, MAP);
and then inside your process functions:
class PhaseOne {
constructor(MAP) {
this.MAP = MAP || MAP;
}
// All code related with the phase here.
process(progressMonitor) {
//do work
progressMonitor.emit('progress', 10)
//more work
progressMonitor.emit('progress', 15)
//much work
progressMonitor.emit('progress', 75)
}
}

Object not updating after localstorage get

In the following I try to test whether an object has been stored in localstorage, and if not to fill it with initial variables.
var TimerData = $localstorage.getObject("TimerData", "{}");
if(!TimerData.hasOwnProperty("timerState")) {
TimerData["timerState"] = "run";
TimerData["timeOutMode"] = false;
TimerData["timeOutStartDate"] = null;
console.log("test line", TimerData)
};
However, running the console at line "test line" returns {} despite that I filled TimerData with variables lines before.
$localstorage.getObject looks as follows:
getObject: function(key, fallBack) {
return JSON.parse($window.localStorage[key] || fallBack);
},
My guess is that the operation is dealing with async problems (taking data from localstorage takes longer).
How can this be overcome?
Jakee1 has the right idea but but you asked about angular...
Instead of
var TimerData = $localstorage.getObject("TimerData", "{}");
I would create local storage first, then assign a var to it.
$localStorage.$default({TimerData: {}});
var TimerData = $localStorage.TimerData;
This will only setup localStorage to {} if it doesn't exist ety
It looks like you are setting the value of "TimerData" to an empty object.
I think you can simplify this using standard js notation (you definitely dont need a special $localstorage adapter because you are using ionic). Ionic should respect standard js notation (although purely speculation on my part)
var TimerData = localStorage.get("TimerData");
if (!TimerData.timerState) {
TimerData["timerState"] = "run";
TimerData["timeOutMode"] = false;
TimerData["timeOutStartDate"] = null;
console.log("test line", TimerData)
}

"this" doesn't work when binding functions to events inside a Javascript class

First off, I know I can copy "this" on instantiation, but that doesn't work here.
Basically I'm writing something to track people interacting with Youtube videos.
I got this working fine for one video at a time. But I want it to work on pages with multiple Youtube videos as well, so I converted the code to a class so I can create a new instance of it for each video on the page.
The problem is when trying to bind to the Youtube event listener for state changes. For "non-class" code, it looks like this:
var o = document.getElementById( id );
o.addEventListener("onStateChange", "onPlayerStateChange" );
(onPlayerStateChange being the function I wrote to track state changes in the video)
(I'm also aware that addEventListener won't work with MSIE but I'm not worrying about that yet)
But when I'm inside a class, I have to use "this" to refer to another function in that class. Here's what the code looks like:
this.o = document.getElementById( id );
this.o.addEventListener("onStateChange", "this.onPlayerStateChange" );
When it's written like this, this.onPlayerStateChange is never called. I've tried copying "this" into another variable, e.g. "me", but that doesn't work either. The onPlayerStateChange function is defined within the "this" scope before I do this:
var me = this;
this.o = document.getElementById( id );
this.o.addEventListener("onStateChange", "me.onPlayerStateChange" );
Any insights?
Looking through other similar questions here, all of them are using jQuery, and I think doing it that way might work if I did it that way. But I don't want to use jQuery, because this is going to be deployed on random third party sites. I love jQuery but I don't want it to be a requirement to use this.
You need a global way to access the onPlayerStateChange method of your object. When you assign me as var me = this;, the variable me is only valid inside the object method where it is created. However, the Youtube player API requires a function that is accessible globally, since the actual call is coming from Flash and it has no direct reference to your JavaScript function.
I found a very helpful blog post by James Coglan in which he discussed a nice way to communicate with the Youtube's JavaScript API and manage events for multiple videos.
I have released a JavaScript wrapper library using his ideas at http://github.com/AnuragMishra/YoutubePlayer. Feel free to checkout the code. The underlying idea is simple - store all instances of the player object on the constructor. For example:
function Player(id) {
// id of the placeholder div that gets replaced
// the <object> element in which the flash video resides will
// replace the placeholder div and take over its id
this.id = id;
Player.instances.push(this);
}
Player.instances = [];
When passing a string as a callback, use a string of the form:
"Player.dispatchEvent('playerId')"
When the flash player evals this string, it should return a function. That function is the callback that will ultimately receive the playback event id.
Player.dispatchEvent = function(id) {
var player = ..; // search player object using id in "instances"
return function(eventId) { // this is the callback that Flash talks to
player.notify(eventId);
};
};
When the flash player has loaded the video, the global onYoutubePlayerReady function is called. Inside that method, setup the event handlers for listening to playback events.
function onYouTubePlayerReady(id) {
var player = ..; // find player in "instances"
// replace <id> with player.id
var callback = "YoutubePlayer.dispatchEvent({id})";
callback = callback.replace("{id}", player.id);
player.addEventListener('onStateChange', callback);
}
See a working example here..
You can use a technique called currying to achieve this. For that you need a currying function. Here's one I wrote some time back
/**
* Changes the scope of function "fn" to the "scope" parameter specified or
* if not, defaults to window scope. The scope of the function determines what
* "this" inside "fn" evaluates to, inside the function "fn". Any additional arguments
* specified in this are passed to the underlying "curried" function. If the underlying
* function is already passed some arguments, the optional arguments are appended
* to the argument array of the underlying function. To explain this lets take
* the example below:
*
* You can pass any number of arguments that are passed to the underlying (curried)
* function
* #param {Function} fn The function to curry
* #param {Object} scope The scope to be set inside the curried function, if
* not specified, defaults to window
* #param arguments {...} Any other optional arguments ot be passed to the curried function
*
*/
var curry = function(fn, scope /*, arguments */) {
scope = scope || window;
var actualArgs = arguments;
return function() {
var args = [];
for(var j = 0; j < arguments.length; j++) {
args.push(arguments[j]);
}
for(var i = 2; i < actualArgs.length; i++) {
args.push(actualArgs[i]);
}
return fn.apply(scope, args);
};
};
You can use it to curry other functions and maintain the 'this' scope inside the functions.
Check out this article on currying
this.o.addEventListener("onStateChange", curry(onPlayerStateChange, this));
Edit:
var curriedFunc = curry(onPlayerStateChange, this);
this.o.addEventListener("onStateChange", "curriedFunc");
Edit:
Okay lets say this is your custom class you create:
function MyCustomClass() {
var privateVar = "x"; // some variables;
this.onPlayerStateChange = function() { //instance method on your custom class
// do something important
}
}
On a global level you create an instance of MyCustomClass
var myCustom = new MyCustomClass(); // create a new instance of your custom class
var curriedFunc = curry(myCustom.onplayerStageChange, myCustom); // curry its onplayerstateChange
// now add it to your event handler
o.addEventListener("onStateChange", "curriedFunc");
You should be using the following to attach an event:
this.o.addEventListener("statechange", this.onPlayerStateChange);
For addEventListener, you don't need to add the on prefix.
What I posted above is correct for standard javascript, but because this passes it to the YT flash object, it's expecting onStateChange which is correct.
HTH
EDIT: Try the method in this post to help.
TheCloudlessSky was partly right and Sean was partly right. You can continue to use "onStateChange" as the event name, but don't put this.onPlayerStateChange in quotations - doing so removes the special meaning of this and javascript will look for a function named "this.onPlayerStateChange" rather than looking for a function "onPlayerStateChange" within this object.
this.o.addEventListener("onStateChange", this.onPlayerStateChange);
After looking at the Youtube Api, it looks like the addEventListener only accepts a String for the event handler function. That means there's no clean way to register a unique event handler for each object.
An alternative is to register a global handler for all youtube state changes, and then let that handler pass the state change onto all your objects. Assuming you have an array of "tracker" objects:
function globalOnPlayerStateChange() {
for (tracker in myTrackerObjects) {
tracker.playerStateChange();
}
}
Each tracker object can then figure out by itself whether or not a state change actually occured (using the API's getPlayerState function):
function MyYoutubeTracker() {
this.currentState = ...
// Determine if state changed happened or not
this.playerStateChange = function() {
var newState = this.o.getPlayerState();
if (newState != this.currentState) {
// State has changed
this.currentState = newState;
}
}
// Register global event handler for this youtube object
this.o.addEventListener("onStateChange", "globalOnPlayerStateChange");
}
Ok, I got this all working. It's a bit of an ugly hack but it works. Basically I'm storing each new instance of the class in an array, and I'm passing the array key (1, 2, etc) into the class, so it can refer to itself externally as needed in a few key places.
The places I need the class to refer to itself externally are the string I pass to addEventListener, and within a few setTimeout functions, where "this" apparently loses its context (as far as I can tell anyways, because the only way I could them working was changing "this" to use external references instead.
Here's the full code.
On the page that has Youtube videos, they are injected using swfobject. The _ytmeta object stores the titles for each video. It's optional, but it's the only way to log the title of a video, because Youtube's API does not give it to you. This means you have to know the title up front, but the point is simply that if you want the title to show up in our reports, you have to create this object:
<div id='yt1'></div>
<script src='youtube.js'></script>
<script src='swfobject.js'></script>
<script>
var _ytmeta = {}
_ytmeta.yt1 = { 'title': 'Moonwalking in Walmart' };
var params = { allowScriptAccess: "always" };
swfobject.embedSWF("http://www.youtube.com/v/gE1ZvCnwkYk?enablejsapi=1&playerapiid=yt1", "yt1", "425", "356", "8", null, null, params );
</script>
So we're including the swfobject javascript code, as well as the youtube.js file, which will be hosted on our server and included on the pages you want to track videos.
Here are the contents of youtube.js:
// we're storing each youtube object (video) in an array, and passing the array key into the class, so the class instance can refer to itself externally
// this is necessary for two reasons
// first, the event listener function we pass to Youtube has to be globally accessible, so passing "this.blah" doesn't work
// it has to be passed as a string also, so putting "this" in quotes makes it lose its special meaning
// second, when we create timeout functions, the meaning of "this" inside that function loses its scope, so we have to refer to the class externally from there too.
// _yt is the global youtube array that stores each youtube object. yti is the array key, incremented automatically for each new object created
var _yt = [], _yti = 0;
// this is the function the youtube player calls once it's loaded.
// each time it's called, it creates a new object in the global array, and passes the array key into the class so the class can refer to itself externally
function onYouTubePlayerReady( id ) {
_yti++;
_yt[ _yti ] = new _yta( id, _yti );
}
function _yta( id, i ) {
if( !id || !i ) return;
this.id = id;
this.mytime;
this.scrubTimer;
this.startTimer;
this.last = 'none';
this.scrubbing = false;
this.o = document.getElementById( this.id );
this.o.addEventListener("onStateChange", "_yt["+i+"].onPlayerStateChange" );
this.onPlayerStateChange = function( newState ) {
// some events rely on a timer to determine what action was performed, we clear it on every state change.
if( this.myTime != undefined ) clearTimeout( this.myTime );
// pause - happens when clicking pause, or seeking
// that's why a timeout is used, so if we're seeking, once it starts playing again, we log it as a seek and kill the timer that would have logged the pause
// we're only giving it 2 seconds to start playing again though. that should be enough for most users.
// if we happen to log a pause during the seek - so be it.
if( newState == '2' ) {
this.myTime = setTimeout( function() {
_yt[i].videoLog('pause');
_yt[i].last = 'pause';
_yt[i].scrubbing = false;
}, 2000 );
if( this.scrubbing == false ){
this.last = 'pre-scrub';
this.scrubbing = true;
}
}
// play
else if( newState == '1' ) {
switch( this.last ) {
case 'none':
this.killTimers();
this.startTimer = setInterval( this.startRun, 200 );
break;
case 'pause':
this.myTime = setTimeout( function() {
_yt[i].videoLog('play');
_yt[i].last = 'play';
}, 2000 );
break;
case 'pre-scrub':
this.killTimers();
this.scrubTimer = setInterval( this.scrubRun, 200 );
break;
}
}
// end
else if( newState == '0' ) {
this.last = 'none';
this.videoLog('end');
}
}
// have to use external calls here because these are set as timeouts, which makes "this" change context (apparently)
this.scrubRun = function() {
_yt[i].videoLog('seek');
_yt[i].killTimers();
_yt[i].last = 'scrub';
_yt[i].scrubbing = false;
}
this.startRun = function() {
_yt[i].videoLog('play');
_yt[i].killTimers();
_yt[i].last = 'start';
}
this.killTimers = function() {
if( this.startTimer ) {
clearInterval( this.startTimer );
this.startTimer = null;
}
if( this.scrubTimer ){
clearInterval( this.scrubTimer );
this.scrubTimer = null;
}
}
this.videoLog = function( action ) {
clicky.video( action, this.videoTime(), this.videoURL(), this.videoTitle());
}
this.videoTime = function() {
return Math.round( this.o.getCurrentTime() );
}
this.videoURL = function() {
return this.o.getVideoUrl().split('&')[0]; // remove any extra parameters - we just want the first one, which is the video ID.
}
this.videoTitle = function() {
// titles have to be defined in an external object
if( window['_ytmeta'] ) return window['_ytmeta'][ this.id ].title || '';
}
}
Hopefully, someone in the future will find this helpful, because it was a serious pain in the ass to get it working!
Thank you everyone who posted their ideas here. :)

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