Javascript Programming Structure

t3hl33td4rg0n

New member
Firstly, I will say that I don't really consider myself a programming expert by any means, so I am making an effort to ensure that I am not learning any bad habits. I've been coding in JS using this methodology for a couple years now and wanted to get some opinions on whether this is the way to go or if I should be using different methods.

What I have below is a real basic order entry/scheduling system which has a PHP backend and all the calls will return either JSON or HTML (it's a little messy right now).

The basic layout.


Page Done Loading initial call (I give it a very short delay in execution as sometimes the DOM tree isn't 100% ready to go).....
Code:
$(document).ready(function() { setTimeout("pageInit()", 100); });

function pageInit () {  
.... // Mostly stuff to manipulate a few default events for some elements
}


Below this section is where the bulk of the code goes. Heres the layout of one of these. This is the customer "object".
Code:
/* Current Customer */
var customer = {
    
    cid: false,             // Customer ID
    data: false,            // Customer residential information
    orders: false,          // Associative Array
    appointments: false,    // Associative Array
    invoices: false,        // Associative Array
    reports: false,         // Associative Array
    address: false,         // String of customer's address for header and google
    
    appointmentTotals: false,
    orderTotals: false,
    
    refreshCustomer: function () {
        customer.getCustomerInfo(customer.cid);
    },
    
    
    // Flow for retrieving all customer information (data, appointments, orders) then displaying the information
    // There are better ways of doing this, but this will do for now :)
    getCustomerInfo: function (cid) {
        
        // Paramters for each AJAX request
        var qCustomer =       { url: ajaxFile, data: { q: 'getCustomerByCID', uid: cid }, type: 'POST', success: function (data) { customer.data = jQuery.parseJSON(data) } };
        var qOrders =         { url: ajaxFile, data: { q: 'getOrdersByCID', cid: cid }, type: 'POST', success: function (data) { customer.orders = jQuery.parseJSON(data) }  };
        var qAppointments =   { url: ajaxFile, data: { q: 'getAppointmentsByCID', cid: cid }, type: 'POST', success: function (data) { customer.appointments = jQuery.parseJSON(data) }  };
        var qInvoices =       { url: ajaxFile, data: { q: 'getInvoicesByCID', cid: cid }, type: 'POST', success: function (data) { customer.invoices = jQuery.parseJSON(data) }  };
        var qReports =        { url: ajaxFile, data: { q: 'getReportsByCID', cid: cid }, type: 'POST', success: function (data) { customer.reports = jQuery.parseJSON(data) }  };
        
        // Populate data on-screen once requests are finished
        $.when($.ajax(qCustomer), $.ajax(qAppointments), $.ajax(qOrders), $.ajax(qInvoices)). then ( function () {
            $('#customerData').css('display', 'block');  //Unhide customer info pane
            customer.cid = customer.data[0].uid;
            customer.outputCustomerData();
            customer.outputCustomerOrders();
            customer.outputCustomerAppointments();
            customer.outputCustomerInvoices();
        });
    },
    
    // Depends: customer.data
    // Output: HTML -> Current Customer Information
    outputCustomerData: function () {
        var r = customer.data[0];
        // For GMaps
        var gm  = String('https://www.google.com/maps/place/');
        var add = r.addStreet+ '+' +r.addBldg+ '+' +r.addCity+ '+' +r.addState+ '+' +r.addZip;
        customer.address = add;
        $('#ciAddy').attr('href', gm+add);
        $('#ciAddy').attr('target', '_new');
        $('#ciDirections').attr('href', 'https://www.google.com/maps/dir/2642+Berne+St,+akron,+OH+44312/'+add);
        $('#ciDirections').attr('target', '_new');
        // Refresh Entire Pane
        $('#customerRefreshButton').html ('<input class="stdButtonSm" type="button" onclick="customer.getCustomerInfo('+r.uid+')" value="Refresh" />');
        // Populate HTML
        $('#ciName').html(r.lName+', '+r.fName);
        $('#ciAddy').html(r.addStreet+' '+r.addUnit+' '+r.addBldg+'<br />'+r.addCity+', '+r.addState+' '+r.addZip);
        $('#cid').html(r.uid);
        $('#ciPhone1').html(r.phone1);
        $('#ciPhone2').html(r.phone2);
        $('#ciEntryDate').html(r.newDate);
        // Quick Stats
        var orderTotals = customer.getStatusTotals (customer.orders);
        customer.orderTotals = orderTotals;
        $('#ciOrderTotals').html (orderTotals[0]+' / '+orderTotals[1]+' / '+orderTotals[2]);
        var appointmentTotals = customer.getStatusTotals (customer.appointments);
        customer.appointmentTotals = appointmentTotals;
        $('#ciAppointmentTotals').html (appointmentTotals[0]+' / '+appointmentTotals[1]+' / '+appointmentTotals[2]);
        // Make the create buttons
        $('#createOrderButtonContainer').html(' <input class="stdButtonSm" type="button" onclick="order.formReadyCreate();" value="Create" />');
        $('#createAppointmentButtonContainer').html(' <input class="stdButtonSm" type="button" onclick="appointment.formReadyCreate();" value="Create" />');
        $('#createInvoiceButtonContainer').html(' <input class="stdButtonSm" type="button" onclick="invoice.formReadyCreate();" value="Create" />');
    },
    
    
    // Depends: customer.orders
    // Output: HTML -> Current Customer Information -> Orders
    outputCustomerOrders: function () {
        var r = customer.orders;
        if (customer.orders.length > 0) {
            var table = Array();
                table.push(['ID','5%']);
                table.push(['Status','8%']);
                table.push(['Type','10%']);
                table.push(['Begin Date','12%']);
                table.push(['End Date','12%']);
                table.push(['Services','22%']);
                table.push(['Report(s)','9%']);
                table.push(['Invoice','12%']);
                table.push(['Action','10%']);
            var out = String(createTableHeader('dataTable', 'width:100%;margin:auto;clear:both;', table));
            for (x = 0; x < r.length; x++) {
                out += tr;
                out += td+ r[x].uid +tdc;
                out += td+ r[x].status +tdc;
                out += td+ clrNull(r[x].type) +tdc;
                out += td+ r[x].newDate +tdc;
                out += td+ clrNull(r[x].completeDate) +tdc;
                out += td+ r[x].services +tdc;
                if (r[x].reportID == 0) {
                	out += td+ '<a onclick="report.formReadyCreate();">'+ 'Create...' +'</a>' +tdc;
                } else {
                	out += td+ clrNull(r[x].reportID) +tdc;
                }
                if (r[x].invoiceID == 0) {
                    out += td+ '<a onclick="invoice.formReadyCreate();">'+ 'Create...' +'</a>' +tdc;
                } else {
                    out += td+ '<a onclick="invoice.view();">'+ r[x].invoiceID +'</a>' +tdc;
                }
                out += td;
                out += ct+'<div><img src="../img/icon_edit.png" onclick="order.formReadyUpdate('+r[x].uid+')" title="Edit" alt="Edit" />  ';
                out += '<img src="../img/icon_delete.png" onclick="order.formReadyDelete('+r[x].uid+')" title="Delete" alt="Delete" /></div>'+ctc;
                out += tdc;
                out += trc;
            }
            out += tc;
        } else {
             out = '<p style="text-align:center;">No orders found!</p>';
        }
        $('#ciOrders').html(out);
    },
    
    
    // Depends: customer.appointments
    // Output: HTML -> Current Customer Information -> Appointments
    outputCustomerAppointments: function () {
        var r = customer.appointments;
        if (customer.appointments.length > 0) {
            var table = Array();
            table.push(['ID', '5%']);
            table.push(['Status', '8%']);
            table.push(['Type', '15%']);
            table.push(['Date', '12%']);
            table.push(['Slot', '10%']);
            table.push(['Assist', '15%']);
            table.push(['ETT', '5%']);
            table.push(['Notes', '20%']);
            table.push(['Action', '10%']);
            var out = String(createTableHeader('dataTable', 'width:100%;margin:auto;clear:both;', table));
            for (x = 0; x < r.length; x++) {
                out += tr;
                out += td+ r[x].uid +tdc;
                out += td+ r[x].status +tdc;
                out += td+ r[x].type +tdc;
                out += td+ r[x].date +tdc;
                out += td+ r[x].slot +tdc;
                out += td+ '<a title="'+r[x].assist+'" style"cursor:pointer;>'+r[x].assist.substr(0,30)+'</a>';
                out += td+ r[x].tripTime +tdc;
                out += td;
                if (r[x].notes == null) { out += " "; } else { out += '<a title="'+r[x].notes.substr(0,40)+'" style"cursor:pointer;>'+r[x].assist.substr(0,40)+'...</a>';;  };
                out += tdc;
                out += td;
                out += ct+'<div><img src="../img/icon_edit.png" onclick="appointment.formReadyUpdate('+r[x].uid+')" title="Edit" alt="Edit" />  ';
                out += '<img src="../img/icon_delete.png" onclick="appointment.formReadyDelete('+r[x].uid+')" title="Delete" alt="Delete" /></div>'+ctc;
                out += tdc;
                out += trc;
            }
            out += tc;
        } else {
           out = '<p style="text-align:center;">No appointments found!</p>';
        }
        $('#ciAppointments').html(out);
    },
    
    
    outputCustomerInvoices: function () {
    	var r = customer.invoices;
        if (customer.appointments.length > 0) {
            var table = Array();
            table.push(['I:ID', '5%']);
            table.push(['Date', '10%']);
            table.push(['Total', '7%']);
            table.push(['Paid', '7%']);
            table.push(['Services(Q): Amount', '31%']);
            table.push(['Notes', '20%']);
            table.push(['Action', '20%']);
            var out = String(createTableHeader('dataTable', 'width:100%;margin:auto;clear:both;', table));
        } else {
        	out = '<p style="text-align:center;">No invoices found!</p>';
        }
    	for (x = 0; x < r.length; x++) {
    		out += tr;
    		out += td+ r[x].invoiceID +tdc;
    		out += td+ r[x].invoiceDate +tdc;
    		out += td+ '$'+r[x].totalCharges +tdc;
    		out += td+ '$'+r[x].paidAmount +tdc;
    		out += td;
    		for (n = 0; n < r[x].invoiceData.length; n++) {
    			
    			out += r[x].invoiceData[n].service + '(' + r[x].invoiceData[n].qty + ')' + ': ';
    			out += '$' + r[x].invoiceData[n].charge + '<br />';     			
    		}
    		out += tdc;
    		out += td+ r[x].notes.substr(0.40) +tdc;
    		out += trc;
    	}
    	out += tc;
        
        
    	$('#ciInvoices').html(out);
    },
    
    
    
    outputCustomerReports: function () {
    	
    	
    	
    },
    

    
    /* Misc Functions */
    getCustomerByCID: function (cid) {
        $.post(ajaxFile, 'q=getCustomerByCID&uid='+cid, function(data) { 
            customer.data = jQuery.parseJSON(data);
        });
    },
    
    
    getAppointmentsByCID: function (cid) {
        $.post(ajaxFile, 'q=getAppointmentsByCID&cid='+cid, function(data) { 
            customer.appointments = jQuery.parseJSON(data);
        });
    },
    
    
    getOrdersByCID: function (cid) {
        $.post(ajaxFile, 'q=getOrdersByCID&cid='+cid, function(data) { 
            customer.orders = jQuery.parseJSON(data);
        });
    },
    
    
    getStatusTotals: function (data) {
        var statusNew  = new Number (0);
        var statusWIP  = new Number (0);
        var statusDone = new Number (0);
        for (x = 0; x < data.length; x++) {
            switch (data[x].status) {
                case 'New': statusNew++; break;
                case 'WIP': statusWIP++; break;
                case 'Done': statusDone++; break;
            }
        }
        var out = Array (statusNew, statusWIP, statusDone);
        return out;
    },

    testout: function () {
        console.log('done');
        console.log(customer.data);
        console.log(customer.data.length);
        console.log(customer.appointments);
        console.log(customer.appointments.length);
        console.log(customer.orders);
        console.log(customer.orders.length);
    },
    
    mrDebug: function () { }
};

When in the browser console, the data can be retrieved in the other objects in this way without sending additional queries to the server. It seems fairly organized to me IMO, but there may be better ways of storing this.
2014-09-22%2018_25_18-XnteK%20Admin.png



For orders, appointments, invoices, etc, I have a layout that controls how the forms behave for create/modify/delete. Here is what orders look like. Another thing of note is that since there are multiple forms on one page, each form element usually has a prefix (cco=create customer order, uco=update customer order, etc). I couldn't think of any other way to do this without running into naming conflicts with the different forms when using jQuery.serialize().


Code:
var order = {
    
    // Populate form data to create an order
    formReadyCreate: function () {
        showDialog('createOrder');
        var r = customer.data[0];
        $('#ccoInfo').html(r.lName+', '+r.fName+'<br />'+r.addStreet+' '+r.addUnit+' '+r.addBldg+'<br />'+r.addCity+', '+r.addState+' '+r.addZip);
        $('#ccoDate').datepicker({dateFormat:"yy-mm-dd"}).datepicker("setDate",new Date());
    },
    
    
    create: function (makeAppt) {
        var returnQuery = new String('');
        var formStuff = new String('');
        // Generate URL and append extra stuff
        formStuff += $('#createCustomerOrder').serialize();
        formStuff += '&q=createCustomerOrder';
        formStuff += '&cid='+customer.data[0].uid;
        formStuff += '&makeAppt='+makeAppt;
        // Once PHP is done, return success or fail
        $.post(ajaxFile, formStuff, function(data) {
            var r = jQuery.parseJSON(data);
            $().toastmessage('showNoticeToast', data); 
            $("#createCustomerOrder")[0].reset();
            $('#createOrder').dialog('close');
            customer.refreshCustomer();
        });
    },
    
    
    formReadyUpdate: function (uid) {
        showDialog('updateOrder');
        var formStuff = new String('');
        formStuff += 'q=getOrderById';
        formStuff += '&uid='+uid;
        $('#updateOrder').html('');
        // Create the form inside the dialog window.
        $.post(ajaxFile, formStuff, function(data) {
            var parsed = jQuery.parseJSON(data);
            var r = parsed[0];
            var out = new String('');
            out += '<div>';
            out += '<span style="font-size:125%;">Updating Order #'+uid+'...</span>';
            out += '<div style="clear:both;"> </div>';
            out += '<form id="updateCustomerOrder" name="updateCustomerOrder" action="./">';
            out += '<label class="stdLabel" for="ucoStatus">Status:</label>';
            out += '<select id="ucoStatus" name="ucoStatus" onchange="updateCompleteDate(\'ucoStatus\', \'ucoEndDate\');" >';
            (r.status == 'New') ? out +=  '<option value="New" selected="selected">New</option>' : out += '<option value="New">New</option>';
            (r.status == 'WIP') ? out +=  '<option value="WIP" selected="selected">WIP</option>' : out += '<option value="WIP">WIP</option>';
            (r.status == 'Done') ? out += '<option value="Done" selected="selected">Done</option>' : out += '<option value="Done">Done</option>';
            out += '</select>';
            out += '<br />';
            out += '<input type="hidden" id="ucoUID" name="ucoUID" value="'+r.uid+'" />';
            out += '<label class="stdLabel" for="ucoServices">Services:</label><input type="text" class="stdInputSpaced" id="ucoServices" name="ucoServices" size="28" value="'+r.services+'" />';
            out += '<input class="stdButtonSm" type="button" onclick="showServiceCodes(\'ucoServices\');" value="+" /><br />';
            out += '<label class="stdLabel" for="ucoNewDate">Begin Date:</label><input type="text" class="stdInputSpaced" id="ucoNewDate" name="ucoNewDate" size="11" value="'+r.newDate+'" /><br />';
            out += '<label class="stdLabel" for="ucoWIPDate">WIP Date:</label><input type="text" class="stdInputSpaced" id="ucoWIPDate" name="ucoWIPDate" size="11" value="'+clrNull(r.wipDate)+'" /><br />';
            out += '<label class="stdLabel" for="ucoEndDate">End Date:</label><input type="text" class="stdInputSpaced" id="ucoEndDate" name="ucoEndDate" size="11" value="'+clrNull(r.completeDate)+'" /><br />';
            out += '<label class="stdLabel" for="ucoCheckout">Checkout Item(s):</label><input type="text" class="stdInputSpaced" id="ucoCheckout" name="ucoCheckout" size="24" value="'+r.checkout+'" /><br />';
            out += '<label class="stdLabel" for="ucoCheckIn">Check-in Item(s):</label><input type="text" class="stdInputSpaced" id="ucoCheckIn" name="ucoCheckIn" size="24" value="'+r.checkin+'" /><br />';
            out += '<label class="stdLabel" for="ucoLocalStore">Local Store Item(s):</label><input type="text" class="stdInputSpaced" id="ucoLocalStore" name="ucoLocalStore" size="24" value="'+clrNull(r.localStore)+'" /><br />';
            out += '<label class="stdLabel" for="ucoReportID">Report #:</label><input type="text" class="stdInputSpaced" id="ucoReportID" name="ucoReportID" size="5" value="'+clrNull(r.reportID)+'" /><br />';
            out += '<label class="stdLabel" for="ucoInvoiceID">Invoice #:</label><input type="text" class="stdInputSpaced" id="ucoInvoiceID" name="ucoInvoiceID" size="5" value="'+clrNull(r.invoiceID)+'" /><br />';
            out += '<label class="stdLabel" for="ucoPaidAmount">Paid Amount:</label>$<input type="text" class="stdInputSpaced" id="ucoPaidAmount" name="ucoPaidAmount" size="6" value="'+r.paidAmount+'" placeholder="0.00" /><br />';
            out += '<label class="stdLabel" for="ucoNotes" style="vertical-align:top;">Notes:</label><textarea id="ucoNotes" name="ucoNotes" class="stdInputSpaced" cols="45" rows="2">'+clrNull(r.notes)+'</textarea><br />';
            out += '<div style="clear:both;"> </div>';
            out += '<input class="stdButton" type="button" onclick="order.update();" value="Update" />';
            out += '</form>';
            out += '</div>';
            $('#updateOrder').html(out);
            $('#ucoNewDate').datepicker({ dateFormat: "yy-mm-dd" });
            $('#ucoWIPDate').datepicker({ dateFormat: "yy-mm-dd" });
            $('#ucoEndDate').datepicker({ dateFormat: "yy-mm-dd" });
         });

    },
    
    
    update: function () {
        var sqlDate = $.datepicker.formatDate('yy-mm-dd', new Date());
        var formStuff = new String('');
        // Generate URL and append extra stuff
        formStuff += $('#updateCustomerOrder').serialize();
        formStuff += '&q=updateCustomerOrder';
        formStuff += '&uid='+$('#ucoUID').val();
        $.post(ajaxFile, formStuff, function(data) { 
            $().toastmessage('showNoticeToast', data); 
            $("#updateCustomerOrder")[0].reset();
            $('#updateOrder').dialog('close');
            $('#updateOrder').html('');
            customer.refreshCustomer();
        });
    },
    
    
    formReadyDelete: function (uid) {
        // Just a simple confirmation dialog
        showDialog('deleteOrder');
        $("#deleteOrder").dialog("option", "width", '60%' );
        var out = new String('');
        out += '<h2 align="center">Are you Sure?</h2>';
        out += ct+'<div>';
        out += '<input class="stdButton" type="button" onclick="order.deleteOrder(\''+uid+'\');" value="Yes" />         ';
        out += '<input class="stdButton" type="button" onclick="closeDialog(\'deleteOrder\')" value="No" /> ';
        out += '</div>'+ctc;
        $('#deleteOrder').html(out);
    },
    
    
    deleteOrder: function (uid) {
        var formStuff = new String('');
        formStuff += '&q=deleteOrder';
        formStuff += '&uid='+uid;
        // Send delete request to server
        $.post(ajaxFile, formStuff, function(data) { 
            $().toastmessage('showNoticeToast', data); 
            $('#deleteOrder').dialog('close');
            $('#deleteOrder').html('');
            customer.refreshCustomer();
        });
    },
    
    
    mrDebug: function () {}  
};


Usually at the bottom of the script I will throw in miscellaneous functions that serve a specific pupose and doesn't tie into the objects themselves.

Code:
/* Misc Page Functions */
function showDialog (ele) {
    var winWidth = $(window).width();
    var threshWidth = new Number(1080);
    var width = new Number();
    if (winWidth < threshWidth) {
        width = '99%';
    } else {
        width = '80%';
    }
    $('#'+ele).dialog({
        width: width,
        height: 'auto',
        modal: true,
        show: {
            effect: "fade",
            duration: 500
        },
        hide: {
            effect: "fade",
            duration: 500
        }
    });
}

function closeDialog (ele) {
    $('#'+ele).dialog('close');
}

function day (n) {
    var o = '';
    switch (n) {
        case 0: o = 'Sun'; break;
        case 1: o = 'Mon'; break;
        case 2: o = 'Tue'; break;
        case 3: o = 'Wed'; break;
        case 4: o = 'Thu'; break;
        case 5: o = 'Fri'; break;
        case 6: o = 'Sat'; break;
    }
    return o;
}


function leadZero (n) {
    var x = String('')
    if (n.length == 1 || n < 10) {
        x = '0' + n;
    } else {
        x = n;
    }
    return x;
}

There's a bunch more, but you get the idea :)



I know that some of this code a bit sloppy and could be compartmentalized a bit better. So far scoping hasn't been much of an issue, except in one instance, but I believe the main benefit of writing in this manner without instancing the objects allows for the customer data to be shared across the objects without passing around all over the place. I'm no expert, but I believe these would be considered static objects?

Should I rethink this type of layout and structure, or am I missing something here?
 
I'd look into using the HTML Template tag and then using a good js library like handlebars.js instead of embedding all your UI within the js.

Separating them makes them both easier to maintain.
 
I'd look into using the HTML Template tag and then using a good js library like handlebars.js instead of embedding all your UI within the js.

Separating them makes them both easier to maintain.

Hmm, good points. I've been looking at angular.js recently, I'll have to play around with both of these and try 'em out on my next project. Looking at what they can do with binding elements, this will definitely help with cleaning up the code.

I've been doing it this way for so long, it's going to be a challenge for sure.

Thanks for the advice.
 
Back
Top