I have some code that's part of my music-js library, and most of it is very organized, using object-oriented design for my AudioAnalyser and Visualizer classes (this code has been updated since publishing but I haven't pushed the changes for several months). There's one static function though, for generating the HTML and event listeners for the actual audio player, it's a bit long and I'm not sure how to better organize it, considering most of the functions have shared variables.// accepts instance of AudioAnalyser and DOM element to insert player intofunction makeControls(audioanalyser, container) {    var link = document.createElement('link'),        controls = document.createElement('div'),        back = document.createElement('div'),        toggle = document.createElement('div'),        skip = document.createElement('div'),        seekbar = document.createElement('div'),        seekinner = document.createElement('div'),        buffered = document.createElement('div'),        played = document.createElement('div'),        seekbtn = document.createElement('div'),        time = document.createElement('div'),        speaker = document.createElement('div'),        volbar = document.createElement('div'),        volinner = document.createElement('div'),        volume = document.createElement('div'),        volbtn = document.createElement('div'),        // contains information about bar and button being dragged        dragbar = null,        dragbtn = null,        dragging = false,        muted = false,        // settings is global variable to library with structure found in index.html        lastVol = settings.volume,        // determines whether to autoplay after seeking        // based on whether audio was paused or not        resume;    // currentTime in seconds of audio playing    function setTime(currentTime) {        var seconds = Math.floor(currentTime),            minutes = Math.floor(seconds / 60),            timeStr = '';        timeStr += minutes + ':';        seconds -= minutes * 60;        timeStr += ('0' + seconds).slice(-2);        time.textContent = timeStr;    }    // between 0.0 and 1.0 inclusive    function setVolume(percent) {        // if gainNode is initialized, use it        if (audioanalyser.gainNode) {            audioanalyser.audio.volume = 1;            audioanalyser.gainNode.gain.value = percent;        // otherwise fallback to set volume directly, which affects visualization        } else {            audioanalyser.audio.volume = percent;        }    }    // get position of event relative to top-left of specified element    function getPos(event, element) {        var x = event.clientX,            y = event.clientY,            currentElement = element;        while (currentElement) {            x -= currentElement.offsetLeft - currentElement.scrollLeft;            y -= currentElement.offsetTop - currentElement.scrollTop;            currentElement = currentElement.offsetParent;        }        return {            x: x,            y: y        };    }    // xPos in pixels, DOM element bar, DOM element button    function updatePos(xPos, bar, button) {        var x = Math.max(Math.min(xPos, bar.offsetWidth - button.offsetWidth - 1), -1);        button.style.left = x + 'px';    }    // start and end are between 0.0 and 1.0 inclusive    // DOM element bar, DOM element range    function updateRange(start, end, bar, range) {        var left = Math.round(bar.clientWidth * start),            right = Math.round(bar.clientWidth * end);        range.style.left = left + 'px';        range.style.width = (right - left) + 'px';    }    // seekbar dragging event handler (not called by listener directly)    function seekMousemove(percent) {        updateRange(0, (seekbtn.offsetLeft + seekbtn.offsetWidth / 2) / seekbar.clientWidth, seekinner, played);        audioanalyser.emit('seeking', {            currentTime: Math.floor(Math.max(Math.min(percent, 1), 0) * audioanalyser.audio.duration)        });    }    // volumebar dragging event handler (not called by listener directly)    function volumeMousemove(percent) {        updateRange(0, (volbtn.offsetLeft + volbtn.offsetWidth / 2) / volbar.clientWidth, volinner, volume);        setVolume(percent);        muted = false;        if (percent > 0.5) {            speaker.classList.remove('icon-volume-off', 'icon-volume-down');            speaker.classList.add('icon-volume-up');        } else if (percent > 0) {            speaker.classList.remove('icon-volume-off', 'icon-volume-up');            speaker.classList.add('icon-volume-down');        } else {            speaker.classList.remove('icon-volume-down', 'icon-volume-up');            speaker.classList.add('icon-volume-off');            muted = true;        }        // if percent is 0, allow volume toggle to revert to full volume        // this is how youtube volume controls behave        lastVol = percent || 1;    }    // generic bar dragging event handler    function barMousemove(event) {        if (dragging) {            var percent = (dragbtn.offsetLeft + 1) / (dragbar.offsetWidth - dragbtn.offsetWidth);            updatePos(Math.round(getPos(event, dragbar).x - dragbtn.offsetWidth / 2 - 2), dragbar, dragbtn);            if (dragbtn === seekbtn) {                seekMousemove(percent);            }            if (dragbtn === volbtn) {                volumeMousemove(percent);            }        }    }    // generic bar mousedown event handler    function barMousedown(event) {        dragging = true;        dragbar = this;        dragbtn = this.lastElementChild;        if (dragbtn === seekbtn) {            // resume playing after seeking if not paused            resume = !audioanalyser.audio.paused;        }        barMousemove(event);        event.preventDefault();    }    // generic bar mouseup event handler    function barMouseup() {        if (dragbtn === seekbtn) {            audioanalyser.emit('seeked', {                resume: resume            });        }        dragging = false;        dragbar = null;        dragbtn = null;    }    // toggle playing audio    toggle.addEventListener('click', function () {        if (audioanalyser.audio.paused) {            audioanalyser.play();        } else {            audioanalyser.pause();        }    });    // seek back in playlist    back.addEventListener('click', function () {        audioanalyser.last();    });    // seek forward in playlist    skip.addEventListener('click', function () {        audioanalyser.next();    });    // handle volume toggling based on last volume    // that was explicitly set on volume bar    speaker.addEventListener('click', function () {        // if muted, unmute        if (muted) {            updatePos(lastVol * (volbar.offsetWidth - volbtn.offsetWidth) - 1, volbar, volbtn);            setVolume(lastVol);            // handle icon changes            if (lastVol > 0.5) {                speaker.classList.remove('icon-volume-off', 'icon-volume-down');                speaker.classList.add('icon-volume-up');            } else {                speaker.classList.remove('icon-volume-off', 'icon-volume-up');                speaker.classList.add('icon-volume-down');            }        // else mute        } else {            updatePos(-1, volbar, volbtn);            setVolume(0);            // handle icon changes            speaker.classList.remove('icon-volume-down', 'icon-volume-up');            speaker.classList.add('icon-volume-off');        }        updateRange(0, (volbtn.offsetLeft + volbtn.offsetWidth / 2) / volbar.clientWidth, volinner, volume);        // toggle mute flag        muted = !muted;    });    // handle icon changes    audioanalyser.on('playing', function () {        toggle.classList.add('icon-pause');        toggle.classList.remove('icon-play');    });    // handle icon changes    audioanalyser.on('pause', function () {        toggle.classList.add('icon-play');        toggle.classList.remove('icon-pause');    });    // handle time display    audioanalyser.on('timeupdate', function () {        var percent = audioanalyser.audio.currentTime / audioanalyser.audio.duration,            xPos = Math.round((seekbar.offsetWidth - seekbtn.offsetWidth) * percent - 1);        // if audio is playing, update seek button and played range        if (!audioanalyser.audio.paused) {            updatePos(xPos, seekbar, seekbtn);            updateRange(0, (seekbtn.offsetLeft + seekbtn.offsetWidth / 2) / seekbar.clientWidth, seekinner, played);        }        // if audio is paused, do not update these since drag handler does this already        setTime(audioanalyser.audio.currentTime);    });    // handle buffered range    audioanalyser.on('progress', function () {        if (audioanalyser.audio.buffered.length > 0) {            var percentStart = audioanalyser.audio.buffered.start(0) / audioanalyser.audio.duration,                percentEnd = audioanalyser.audio.buffered.end(0) / audioanalyser.audio.duration;            updateRange(percentStart, percentEnd, seekinner, buffered);        }    });    // add event listeners    seekbar.addEventListener('mousedown', barMousedown);    volbar.addEventListener('mousedown', barMousedown);    document.addEventListener('mousemove', barMousemove);    document.addEventListener('mouseup', barMouseup);    // preload styling    link.setAttribute('type', 'text/css');    link.setAttribute('rel', 'stylesheet');    // Change this value to whatever is needed    link.setAttribute('href', '//html5music.herokuapp.com/css/style.css');    link.addEventListener('load', function () {        setTime(0);        updatePos(lastVol * (volbar.offsetWidth - volbtn.offsetWidth) - 1, volbar, volbtn);        if (volbar.offsetWidth) {            volumeMousemove();        } else {            setVolume(settings.volume);        }        audioanalyser.initialize();    });    // insert styling from player settings    controls.setAttribute('style', settings.controls);    // insert CSS classes    controls.classList.add('audio');    back.classList.add('back', 'icon-step-backward');    toggle.classList.add('toggle', 'icon-play');    skip.classList.add('skip', 'icon-step-forward');    seekbar.classList.add('seekbar');    seekinner.classList.add('innerbar');    buffered.classList.add('buffered');    played.classList.add('played');    seekbtn.classList.add('seekbtn');    time.classList.add('time');    speaker.classList.add('speaker', 'icon-volume-up');    volbar.classList.add('volbar');    volinner.classList.add('innerbar');    volume.classList.add('volume');    volbtn.classList.add('volbtn');    // insert elements into container element    document.head.appendChild(link);    controls.appendChild(back);    controls.appendChild(toggle);    controls.appendChild(skip);    controls.appendChild(seekbar);    controls.appendChild(time);    controls.appendChild(speaker);    controls.appendChild(volbar);    seekbar.appendChild(seekinner);    seekbar.appendChild(seekbtn);    seekinner.appendChild(buffered);    seekinner.appendChild(played);    volbar.appendChild(volinner);    volbar.appendChild(volbtn);    volinner.appendChild(volume);    container.appendChild(controls);}
Organize dynamic HTML generation function for audio player
javascript;dom;audio
You may consider to make the elements building process more generic. Not sure it ends up with very much less code, but IMO it becomes clearer.I imagined a solution, and applied it on a part of your code, as an example:function makeControls(audioanalyser, container) {    var $ = {}, // will contain the set of controls once built        elements = {            link: {                tag: 'link',                attrs: {'type': 'text/css', 'rel': 'stylesheet'},                bind: {'load': linkLoad},                parent: document.head            },            controls: {                tag: 'div',                attrs: {'style': settings.controls},                classes: ['audio'],                parent: container            },            back: {                tag: 'div',                bind: {'click': backClick},                classes: ['back', 'icon-step-backward'],                parent: 'controls'            },            toggle: {                tag: 'div',                bind: {'click': toggleClick},                classes: ['toggle', 'icon-play'],                parent: 'controls'            },            // and so on...        },        // contains information about bar and button being dragged        dragbar = null,        dragbtn = null,        dragging = false,        muted = false,        // settings is global variable to library with structure found in index.html        lastVol = settings.volume,        // determines whether to autoplay after seeking        // based on whether audio was paused or not        resume;    function linkLoad() {        setTime(0);        // note the $. prefixes added to controls elements        updatePos(lastVol * ($.volbar.offsetWidth - $.volbtn.offsetWidth) - 1,          $.volbar, $.volbtn);        if ($.volbar.offsetWidth) {            volumeMousemove();        } else {            setVolume(settings.volume);        }        audioanalyser.initialize();    }    function backClick() {        audioanalyser.last();    }    function toggleClick() {        if (audioanalyser.audio.paused) {            audioanalyser.play();        } else {            audioanalyser.pause();        }    }    // And so on for each migrated bind functions...    // Then all the other (previously defined) functions...    // Here the whole stuff for building controls.    for (var e in elements) {      var element = elements[e];      $[e] = document.createElement(element.tag);      if (element.attrs) {        for (var a in element.attrs) {          $[e].setAttribute(a, element.attrs[a]);        }      }      if (element.classes) {        for (var c in element.classes) {          $[e].classList.add(element.classes[c]);        }      }      if (element.bind) {        for (var b in element.bind) {          $[e].addEventListener(b, element.bind[b]);        }      }      if (typeof element.parent == 'string') {        $[element.parent].appendChild($[e]);      } else {        element.parent.appendChild($[e]);      }    }}I tested this code and it works.Here are some points of interest:obviously, since the leading idea was to define properties for the whole set of elements and loop to build them, we must have an object for the entire set --> pitfall: once built the elements don't have an associated variable referencing them.To create those variables while building the elements I couldn't think to better than eval(): not a good thing.Then I choosed to create the $ object to collect elements, so each reference to these elements, in your functions, must be prefixed by $.: hopefully this is not so painful, and doesn't harm readability.regarding event binding, I merely reported your original anonymous functions to named, independant functionswhen attaching element to its parent, linkand controls target an element, while all other ones target controls --> the tip is to define parent as a string when it is control, and merely as DOM or JS element otherwise.