2
\$\begingroup\$

Here's my attempt at a script that will take user input and produce a custom interactive eBay listing. This is definitely still in progress, but I wanted to post the code I have since I'm sure there will be awesome suggestions on how I can improve functionality and write less.

Here's a Fiddle. The default window size triggers the mobile styles, so just view full screen to see the desktop version.

$(document).ready(function(){
    localStorage.clear();
//***********************************************************
//                        PUBLIC VARIABLES
//***************************************************************

    // Local Storage
        var storage = localStorage;

    // Opening tags
        var templateOpen = '<tableid="mblParentContainer"><tbody><tr><td><divclass="mblWrpr">';

    // Closing tags
        var templateClose =  '</div></td></tr></tbody></table>';

    // Get rid of commas in our opening tags and add whitespace to classnames and ids
    var x = templateOpen.split(' ').toString()
            .replace(/\,/g, '')
            .replace(/class/g, ' class')
            .replace(/id/g, ' id');

    // Get rid of commas in our closing tags
    var y = templateClose.split(' ').toString().replace(/\,/g, '');

    // Create an object to be used as an inline stylesheet
    var styleSheet =  {
        sOpen: '<style>',
        table: '#mblParentContainer{width:100%;}',
        wrapper: '.mblWrpr{position:relative;width:90%;margin:0 auto;height:auto;border-radius:5px;' + 
                  'box-shadow:inset 0 0 10px #383838,inset 0 0 10px #383838,inset 0 0 10px #383838;' + 
                  'background:Linen;padding:10px;text-align:center;font-family:Baskerville;}',
        header: '.mblHdr{text-align:left;padding:10px;border-bottom:1px solid #778899;}',
        menuTrigger: '.menu-trigger{display:none;position:relative;margin:auto;height:50px;width:80px;z-index:9999;}',
        bars: '.bar{position:absolute;left:0;height:5px;width:100%;background:#fff;}',
        barOne: '.bar:first-child{top:13px;}',
        barTwo: '.bar:nth-child(2){top:23px;}',
        barThree: '.bar:nth-child(3){top:33px;}',
        nav: '.mblNav{background:#383838;height:50px;line-height:50px;width:100%;border-radius:3px;}',
        ul: '.mblNav ul{text-align:center}',
        li: '.mblNav ul li {position:relative;list-style-type: none; display: inline-block;width:20%}',
        a: '.mblNav ul li a{display:block;padding:0 8px 0 8px;color:#fff;text-decoration:none;}',
        aHov: '.mblNav ul li:hover a {background:#000;color:#66e2e3;}',
        liHov: '.mblNav ul li:hover{border-bottom:2px solid #66e2e3;}',
        triangle: '.mblNav ul li .triangle{display:none;position:absolute;top:50px;left:48%;margin-top:-10px;margin-left:-5px;' + 
                  'height:0;width:0;border-right:10px solid transparent;border-left:10px solid transparent;border-bottom:10px solid #66e2e3}',
        hidden: '.mblHide{display:none;}',
        fadeWrapper: '.mblFadeWrapper{display:none;position:fixed;top:0;left:0;height:100%;width:100%;background:rgba(0,0,0,.5);z-index:5000;}',
        mobileContainer: '#mblMobile{display:none;position:absolute;top:150px;left:50%;margin:-top:-125px;margin-left:-100px;' + 
                         'height:250px;width:200px;background:#383838;z-index:9999;}',
        menuTriggerHov: '.menu-trigger:hover{cursor:pointer;}',
        mobileLi: '.mblMM li{list-style-type:none;height:49px;line-height:49px;border-bottom:1px solid #000;}',
        mobileA: '.mblMM li a{display:block;color:#66e2e3;text-decoration:none;}',
        mobileAHov: '.mblMM li:hover{background:rgba(0,0,0,.3);}',
        sectionHead: 'h1.mblDescHdr{font-size:36px;text-align:left;text-decoration:underline;color:#888888;}',
        desc: 'p.mblParaDesc{font-size:18px;text-align:left;color:#888888;}',
        divider: '.divider{margin:10px 0}',
        divider2: '.divider:last-child{margin:10px 0 20px}',
        dividerImg: '.divider img{height:1px;width:100%}',
        footer: '.mblFtr{position:absolute;bottom:0;left:0;width:100%;height:22px;line-height:22px;background:#383838;text-align:center;}',
        footerP: '.mblFtr p{color:#66e2e3;}',
        footerA: '.mblFtr a {color:#66e2e3;text-decoration:none;}',
        footerAHov: '.mblFtr a:hover{color:#fff;}',
        mediaQueries: '@media only screen and (max-width: 768px) {.mblNav ul{display:none}.menu-trigger{display:block}}',
        sClose: '</style>'
    };

    // Convert stylesheet object to string and get rid of quotation marks
    var styleObj = JSON.stringify(styleSheet.sOpen + styleSheet.table + styleSheet.wrapper + styleSheet.header + 
    styleSheet.menuTrigger + styleSheet.bars + styleSheet.barOne + styleSheet.barTwo + styleSheet.barThree +  
    styleSheet.nav + styleSheet.ul + styleSheet.li + styleSheet.a + styleSheet.aHov + styleSheet.liHov + 
    styleSheet.triangle + styleSheet.sectionHead + styleSheet.desc + styleSheet.divider + styleSheet.divider2 + 
    styleSheet.dividerImg + styleSheet.hidden + styleSheet.fadeWrapper + styleSheet.mobileContainer + styleSheet.menuTriggerHov +  
    styleSheet.footer + styleSheet.footerP + styleSheet.footerA + styleSheet.footerAHov + 
    styleSheet.mobileLi + styleSheet.mobileA + styleSheet.mobileAHov + styleSheet.mediaQueries + styleSheet.sClose)
    .replace(/"/g, '');


//*************************************************************
//                        INLINE SCRIPT
//*************************************************************

    // Store the whole script as a string
    // iQuery is a function which returns a set of commonly used methods
    // These are simply to make writing behaviors a little simpler
    var iQuery = '<script>var iq=function(){return{tag:function(e){return document.getElementsByTagName(e)},' + 
             'sel:function(e){return document.querySelector(e)},selAll:function(e){return document.querySelectorAll(e)},' + 
             'show:function(e){return e.style.display="block"},hide:function(e){return e.style.display="none"},' + 
             'fadeOut:function(e,t){function s(){n-=i;t.style.opacity=n;if(n<=0){window.clearInterval(o);' + 
             't.style.display="none"}}var n=1,r=50,i=r/e;var o=window.setInterval(s,r)},' + 
             'fadeIn:function(e,t){function s(){n+=i;t.style.opacity=n;if(n>=1){window.clearInterval(o)}}var n=0,r=50,i=r/e;' + 
             't.style.display="block";t.style.opacity=n;var o=window.setInterval(s,r)},' + 
             'click:function(e,t){return[].forEach.call(iq.selAll(e),function(e){e.addEventListener("click",t,false)})},' + 
             'hasClass:function(e,t){return e.classList.contains(t)},addClass:function(e,t){return e.classList.add(t)},' + 
             'removeClass:function(e,t){return e.classList.remove(t)},' + 
             'addOrRemoveClass:function(e,t){if(iq.hasClass(e,t)){return e.classList.remove(t)}else{return e.classList.add(t)}},' + 
             'map:function(e,t){var n=[];e=iq.tag(e);for(var r=0;r<e.length;r++){var i=e[r].getAttribute(t);n.push(i)}return n},' + 
             'scrollWindow:function(e){function t(e){var t=0;if(e.offsetParent){do{t+=e.offsetTop}while(e=e.offsetParent);' + 
             'return[t]}}e=iq.sel(e);window.scrollTo(0,t(e))}}}();';

    // Breaking this up into two variables so that we can distinguish textually 
    // between the framework and its integration when this gets rendered as text
    // in the output container, which the user can copy and paste into an eBay listing
    var funcs =  'iq.show(iq.sel(".mblHide.one"));' + 
             'iq.sel(".mblNav ul li:first-child").style.background = "#000";' + 
             'iq.sel(".mblNav ul li:first-child .triangle").style.display = "block";' + 
             'iq.sel(".mblNav ul li:first-child").style.borderBottom = "2px solid #66e2e3";' + 
             'iq.sel(".mblNav ul li:first-child a").style.color = "#66e2e3";' + 
             'iq.sel(".mblNav ul li:first-child a").style.textTransform = "uppercase";' +
             'iq.click("a", function(e){' +
                'if (iq.hasClass(iq.sel("#mblMobile"), "open") && this.parentNode.parentNode.getAttribute("class") === "mblMM") {' +
                    'iq.fadeOut(500, iq.sel(".mblFadeWrapper"));' + 
                    'iq.fadeOut(500, iq.sel("#mblMobile"));' +
                    'iq.removeClass(iq.sel("#mblMobile"), "open");' +
                '} else if (!iq.hasClass(iq.sel("#mblMobile"), "open") && this.parentNode.parentNode.getAttribute("class") === "mblMM") {' +
                    'iq.fadeIn(500, iq.sel("#mblMobile"));' + 
                    'iq.fadeIn(500, iq.sel(".mblFadeWrapper"));' +
                    'iq.addClass(iq.sel("#mblMobile"), "open");' +
                '} else {' + 
                    'e.preventDefault();' +
                '}' +
                'var cls = iq.map("a", "class");' +  
                'for (var i = 0; i < cls.length; i++) {' +  
                    'if (this.getAttribute("class") === cls[i]) {' + 
                        'iq.show(iq.sel(".mblHide." + cls[i]));' + 
                        'iq.sel("a."+cls[i]).style.color = "#66e2e3";' + 
                        'iq.sel("a."+cls[i]).style.textTransform = "uppercase";' + 
                        'iq.sel("a."+cls[i]).parentNode.style.background = "#000";' + 
                        'iq.sel("a."+cls[i]).parentNode.style.borderBottom = "2px solid #66e2e3";' +  
                        'iq.sel("a."+cls[i]).parentNode.children[1].style.display = "block";' +  
                        'var toHide = iq.selAll(".mblHide:not(." + cls[i] + ")");' +
                        'var a = iq.selAll(".mblMainNav a:not(."+cls[i]+")");' +
                        'for (var j = 0; j < toHide.length; j++) {' + 
                            'iq.hide(toHide[j]);' +
                            'for (var k = 0; k < a.length; k++) {' + 
                                'a[k].style.color = "";' + 
                                'a[k].style.textTransform = "";' + 
                                'a[k].parentNode.style.background = "";' + 
                                'a[k].parentNode.style.borderBottom = "";' +  
                                'a[k].parentNode.children[1].style.display = "";' +
                            '}' + 
                        '}' + 
                    '}' + 
                '}' + 
            '});' +
            'iq.click(".menu-trigger", function(){' +
                'iq.addOrRemoveClass(iq.sel("#mblMobile"), "open");' +
                'if (iq.hasClass(iq.sel("#mblMobile"), "open")) {' +
                    'iq.fadeIn(500, iq.sel(".mblFadeWrapper"));' + 
                '   iq.fadeIn(500, iq.sel("#mblMobile"));' + 
                '} else {' +
                    'iq.fadeOut(500, iq.sel("#mblMobile"));' + 
                    'iq.fadeOut(500, iq.sel(".mblFadeWrapper"));' +
                '}' + 
            '});' + 
             '</script>';

    // Combine the two script variables for easier access (this gets added to the code block generated on form submit)
    var scriptObj = iQuery + funcs;


//*****************************************************************
//                     OUTPUT GENERATION FUNCTIONS
//*****************************************************************

    // Create inner html based on values entered in inputs
    $('#form1 input, #form1 textarea').change(function(){

        // Get input values
        var menu1 = $('.menu1').val(),
            menu2 = $('.menu2').val(),
            menu3 = $('.menu3').val(),
            menu4 = $('.menu4').val(),
            menu5 = $('.menu5').val();

        var itemDesc1 = $('.itemDescription1 textarea').val(),
            itemDesc2 = $('.itemDescription2 textarea').val(),
            itemDesc3 = $('.itemDescription3 textarea').val(),
            itemDesc4 = $('.itemDescription4 textarea').val(),
            itemDesc5 = $('.itemDescription5 textarea').val();

        var src = $('#logo').val();

        var head1 = $('.section-head1'),
            head1Val = head1.val(),
            head2 = $('.section-head2'),
            head2Val = head2.val(),
            head3 = $('.section-head3'),
            head3Val = head3.val(),
            head4 = $('.section-head4'),
            head4Val = head4.val(),
            head5 = $('.section-head5'),
            head5Val = head5.val();

            head1.val(menu1);
            head2.val(menu2);
            head3.val(menu3);
            head4.val(menu4);
            head5.val(menu5);


            // All the HTML markup that goes inside the template tags
            var innerMarkup = '<div class="mblFadeWrapper"></div><header class="mblHdr"><img alt="" src="'+src+'">' + 
            '<nav class="mblNav"><div class="menu-trigger">' + 
            '<div class="bar"></div><div class="bar"></div>' + 
            '<div class="bar"></div></div><ul class="mblMainNav"><li><a href="javascript:;" class="one">' + menu1 + 
            '</a><div class="triangle"></div></li><li><a href="javascript:;" class="two">' + menu2 +
            '</a><div class="triangle"></div></li><li><a href="javascript:;" class="three">' + menu3 +
            '</a><div class="triangle"></div></li><li><a href="javascript:;" class="four">' + menu4 + 
            '</a><div class="triangle"></div></li><li><a href="javascript:;" class="five">' + menu5 + 
            '</a><div class="triangle"></div></li></ul></nav></header>' + 
            '<div id="mblMobile"><ul class="mblMM"><li><a href="javascript:;" class="one">'+menu1+'</a></li>' +
            '<li><a href="javascript:;" class="two">'+menu2+'</a></li><li><a href="javascript:;" class="three">'+menu3+'</a></li>' + 
            '<li><a href="javascript:;" class="four">'+menu4+'</a></li><li><a href="javascript:;" class="five">'+menu5+'</a></li></ul></div>' + 
            '<div class="mblHide one"><h1 class="mblDescHdr"></h1><p class="mblParaDesc">'+itemDesc1+'</p>' + 
            '<div class="divider"><img alt="" src="http://www.moblah.com/img/divider-line3.png"></div></div>' + 
            '<div class="mblHide two"><h1 class="mblDescHdr"></h1><p class="mblParaDesc">'+itemDesc2+'</p>' + 
            '<div class="divider"><img alt="" src="http://www.moblah.com/img/divider-line3.png"></div></div>' + 
            '<div class="mblHide three"><h1 class="mblDescHdr"></h1><p class="mblParaDesc">'+itemDesc3+'</p>' + 
            '<div class="divider"><img alt="" src="http://www.moblah.com/img/divider-line3.png"></div></div>' + 
            '<div class="mblHide four"><h1 class="mblDescHdr"></h1><p class="mblParaDesc">'+itemDesc4+'</p>' + 
            '<div class="divider"><img alt="" src="http://www.moblah.com/img/divider-line3.png"></div></div>' + 
            '<div class="mblHide five"><h1 class="mblDescHdr"></h1><p class="mblParaDesc">'+itemDesc5+'</p>' + 
            '<div class="divider"><img alt="" src="http://www.moblah.com/img/divider-line3.png"></div></div>' +
            '<div class="mblFtr"><p>Listing by <a href="http://www.noticeeverythingcreative.com" target="blank">' +
            'Notice Everything Creative</a></p></div>';

        // Save code block in storage
        // This gets rendered both as a string and as HTML markup
        storage.output = x + styleObj + innerMarkup + scriptObj + y;
    });

    // On form submit, populate code output container and preview container
    $('#form1').on('submit', function(e) {
        e.preventDefault();
        var inputs = $('textarea, input[type="text"').map(function(){
        return $(this);
        });

        // Hide inputs whose value hasn't changed
        for (var i = 0; i < inputs.length; i++) {
            if (inputs[i].val() === '') {
                inputs[i].hide();
                inputs[i].parent().find('h1').hide();
            } else {
                inputs[i].attr('disabled', true);
            }
        }

        // Populate code container and preview container, with raw code, and rendered HTML/CSS, respectively
        $('.submit').fadeOut();
        $('#output').text(storage.output);
        $('#preview').fadeIn().append(storage.output);
        $('.continue-editing, .get-code, #form-clear').fadeIn(); 
    });

    // Close the preview but keep all the input values and storage
    $('.continue-editing').click(function(){
        $('.continue-editing, .get-code, #form-clear').fadeOut();
        $('.submit').fadeIn();
        $('#output').fadeOut();
        $('#preview').fadeOut().empty();
        $('form textarea, form input, form h1').show();
        $('form textarea, form input').attr('disabled', false);

    });

    // Same as above, but clear form and storage
    $('#form-clear').click(function(){
        document.forms.form1.reset();
        $('.submit').fadeIn();
        $('#output').text('');
        $('#preview').fadeOut().empty();
        $('form textarea, form input, form h1').show();
        $('form textarea, form input').attr('disabled', false);
        $('.continue-editing, .get-code, #form-clear').fadeOut();
    });

    // View the code block output
    $('.get-code').click(function(){
        $('#output').toggle();
    });
});

I should point out that although it's possible to use jQuery in eBay listings, it's also explicitly against their policy, which is why I've got that jQuery variable and its methods for simplifying vanilla JS.

\$\endgroup\$
4
\$\begingroup\$

TL;DR: Take a look at Grunt


Important points when writing code

Take a vacation for 2 weeks and after that, try editing that code. I bet you wouldn't feel as good as you did when you originally wrote that. Here's 3 things to note:

  • Maintainability - When things go wrong, can you fix it easily?
  • Scalability - When I want a modal inside that code, can you put one quickly?
  • Separation of Concerns - When there's a styling issue, are you sure it's we'll only modify the CSS?

What now?

Now, I'll not review how the code is at the moment. A better way to deal with messy code is to do it the right way before fixing it. Otherwise, it's called patch-work, and believe me, it's not a great solution. If I were you, you'd build that code into separate files into manageable bits of HTML, CSS and JS.

Then you'll tell me "Hey! I'm building a standalone, droppable JS widget. I can't tell the user to put N files in N different directories!". Yes, true, you can make a widget in one file, but that doesn't mean you'll write it all in one file.

How to do it the right way

To start off, write the code in separate files. Have an index.html to dump everything there, scripts, HTML and all. I suppose you did the same thing when writing the HTML before you packed it in the script.

Then I suggest you take a look at Grunt. It's an automation tool with lots of plugins available already. It's like make but written in JS. You can do all sorts of stuff, like minifying the JS, optimizing the HTML, concatenate.

In the end, with the right plugins, you end up with your one-file widget, but not a one-file project.

\$\endgroup\$
  • \$\begingroup\$ Thanks for the suggestions. I spent a bunch of time trying to load all the HTML/CSS separately via AJAX, but I just couldn't get it to work properly. As I mentioned, this might be a tad above my skill level. However, I did clean up the original code, and it should be much easier to make sense of even if it still contains all the HTML/CSS markup. \$\endgroup\$ – MyCompassSpins Jun 29 '14 at 23:11
  • \$\begingroup\$ @MyCompassSpins The key to large scale JS is to never make it large in the first place. Small but manageable code is better than one big mess. \$\endgroup\$ – Joseph Jun 30 '14 at 14:09
  • \$\begingroup\$ Of course I agree. However, the above is still the only way I've been able to come up with that produces the proper result. I'll be looking into templating engines today. \$\endgroup\$ – MyCompassSpins Jun 30 '14 at 15:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.