Skip to content

Instantly share code, notes, and snippets.

@say2joe
Last active October 11, 2015 09:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save say2joe/3841515 to your computer and use it in GitHub Desktop.
Save say2joe/3841515 to your computer and use it in GitHub Desktop.
Sample JS App Template (JavaScript)
/**
* SLP.Modules.Codes extends the SLP.Modules object on DOM-ready.
* @module Codes
*/
SLP.Modules.Codes = {
/**
* First validate for empty fields, then check the campaign codes,
* making sure that they begin with T(masked), L(masked), or S(masked).
* @param {object} event jQuery Event object (form submit)
* @return {boolean} true if all validation passes.
*/
validateCampaignCodes: function(event){
var $input, isValid = SLP.validateFormInputsNotEmpty(event);
if (isValid) {// Continue validation if all fields are non-empty.
$input = $("input").filter("[name=campaign_code]").each(function(){
if (!(isValid = /^[TLS]/i.test(this.value))) {
return !SLP.DOM.$modalPrompt.find(".modal-body").html( // Error msg.
"Campaign Codes must begin with T(masked), S(masked), or L(masked)."
).end().modal("show");// On error, immediately exit (the each method).
}
});
}
// Convert campaign codes to uppercase for submission.
$input.val(function(i,v){ return v.toUpperCase(); });
// Enable all checkboxes to submit values with form.
$("input[type=checkbox]").attr("disabled", false);
return isValid;
},
/**
* Display the Edit button on editable rows in the table on mouseover (enter).
* If the Cancel (and Save) buttons are showing, do not show the Edit button.
* @param {object} event jQuery Event object (mouseenter or mouseleave)
*/
toggleEditButtonHover: function(event){
var $t = $(this), $c = $t.find(".btn-cancel"), isEnter = (event.type=="mouseenter");
$t.find(".btn-edit").toggle(isEnter && $c.is(":hidden"));
$t.toggleClass("hover", isEnter);
},
/**
* If the user clicks the cancel button, remove the form controls.
* Hide the Save and Cancel buttons. Add a line-break after values.
* @param {object} event jQuery Event object (click)
*/
viewCampaignCode: function(event){
$(this).next(".btn-save").andSelf().hide()
.closest("td").siblings().replaceWith(function(){
var $controls = $(this).find(":input").attr("disabled",true);
return $("<td/>").append(
$controls.map(function(i,c){
return ((i)? "<br/>" : '') + ((c.type=="checkbox")? c.outerHTML : c.value);
}).get().join('')
);
});
},
/**
* When the user clicks the Edit button, display form controls for editing.
* We need a special case for destinations since we want values to be simple.
* Also, show the Save and Cancel buttons; hide the Edit button.
* @param {object} event jQuery Event object (click)
* @function
*/
editCampaignCode: function(event){
var controls = $(SLP.Templates.tmplCampainCode).filter("td"),
newHTMLforTD = function newHTMLforTD(i,html){
var $contents = (i)?// First iteration is false.
// Set the value of a control using the text.
$(controls[i]).find(":input").val(html).end() :
// Enable the activate / deactivate checkbox.
$(controls[i]).append($(html).attr("disabled",false));
if (i == controls.length-1) {// Set destinations.
$contents.prepend(// Prepend to Add Destination
$.map(html.split(/<\w+>/),function(dest,i){
var oTmplVars = { i: i, value: dest },
input = SLP.tmpl(SLP.Templates.tmplDestination,oTmplVars);
return input + '<i class="icon-minus blue"></i>';
}).join('')
);
}
return $contents;
};
$(this).hide().siblings().show().closest("td")
.siblings().replaceWith(newHTMLforTD);
},
/**
* Add a new destination as an empty form input control.
* @param {object} event jQuery Event object (click)
*/
addDestination: function(event){
var $t = $(this), oTmplVars = { i: $t.siblings().length };
$t.before($(SLP.tmpl(SLP.Templates.tmplDestination,oTmplVars)));
},
/**
* Remove destination from the DOM, if the user clicks the - (minus) icon.
* @param {object} event jQuery Event object (click)
*/
delDestination: function(event){
var $btnMinusIcon = $(this);
SLP.DOM.$modalConfirm.find(".modal-body").html(
"Are you sure you want to delete this destination?"
).end().modal("show").one("click",".btn-primary",function(){
$btnMinusIcon.prev().andSelf().remove();
SLP.DOM.$modalConfirm.modal("hide");
});
},
/**
* Use a template to display a new row with form controls for adding a campaign code.
* Create a TR with TDs from a template (adding action buttons as the last TD).
* Remove the Edit button (not necessary for new campaign codes). Show others.
* @param {object} event jQuery Event object (click)
*/
addNewCampaignCode: function(event){
var $newrow = $('<tr class="edit new"/>'),
$buttons = $("<td/>").html(SLP.Templates.tmplRowActions);
$buttons.find("button").first().addClass("delNewCampaign");
$newrow.html(SLP.Templates.tmplCampainCode).append($buttons);
$buttons.find(".btn-edit").remove();
SLP.DOM.$rowAddNew.after($newrow);
$buttons.find("button").show();
},
/**
* Remove new campaign code row from the DOM, if the user clicks the Cancel button.
* @param {object} event jQuery Event object (click)
*/
delNewCampaignCode: function(event){
$(this).closest("tr").remove();
},
/**
* Capture events which would normally submit the form... then cancel the submit.
* For example, if the user is editing a destination and presses the enter key.
* @param {object} event jQuery Event object (click)
* @return {boolean} Continue or stop form submission.
*/
cancelFormSubmit: function(event){
if (event.which === 13) {
event.stopPropagation();
event.preventDefault();
}
},
init: function(){// Add (hidden) buttons to each row.
SLP.DOM.$tblCC.find("tbody>tr>td:last-child")
.not(":first").html(SLP.Templates.tmplRowActions);
return this;
}
};
$(function(){// On DOM Ready...
var SMC = SLP.init({// DOM (Cache)
$tblCC: $("#tblCampaignCodes"),
$frmCC: $("#frmCampaignCodes"),
$rowAddNew: $("#newCampaignCode"),
$modalPrompt: $("#ValidationPrompt"),
$modalConfirm: $("#ConfirmationPrompt")
}).Modules.Codes.init();
SLP.DOM.$frmCC
.on("submit", SMC.validateCampaignCodes)
.on("keypress", ":input", SMC.cancelFormSubmit);
SLP.DOM.$tblCC
.on("click", ".icon-minus", SMC.delDestination)
.on("click", ".btn-edit", SMC.editCampaignCode)
.on("click", ".btn-cancel", SMC.viewCampaignCode)
.on("click", ".add-destination", SMC.addDestination)
.on("click", ".delNewCampaign", SMC.delNewCampaignCode)
.find("tbody>tr").not(":first").hover(SMC.toggleEditButtonHover);
SLP.DOM.$rowAddNew.on("click", ".addNewCampaign", SMC.addNewCampaignCode);
});
/**
* @fileOverview This file (slp.js) contains core functionality for SLP 2.0.
* @author <a href="mailto:joe.johnson@icrossing.com">Joe Johnson</a>.
* @version 1.0.1
*/
/**
* @namespace
* Contains global functionality for this application.
* @requires jQuery 1.7, Bootstrap 2.0.4. Leads need jQuery++ and DataTables.
*/
var SLP = {
// Core Properties:
description: "Sales Lead Processor", version: "2.0",
css: "css/slp.css",
// Core Objects:
/**
* Object containing properties of HTML from templates.
* @type {Object}
*/
Templates: { /* HTML templates (per page) after SLP.init(). */ },
/**
* Object containing specific (page) functionality for SLP.
* @type {Object}
*/
Modules: { /* slp.[leads].js contains SLP.Modules.[Leads]. */ },
/**
* Object containing DOM elements with (cached) references.
* @type {Object}
*/
DOM: { /* Properties available after SLP.init(). */ },
/**
* A wrapper for browsers missing native support for console.
* @type {Object}
*/
Console: window.console || { log: function(msg){ alert(msg); } },
/**
* Object containing DatePicker properties specific to the SLP project. This object / plugin is
* utilized (loaded dynamically) if and only if the browser does not support the HTML5 input type
* of "date". If the browser supports DATE input and has a built-in widgit, this is not used.
* @type {Object} Synonymous STRUCT. Specifies plugin paths.
*/
DatePicker: {
js: "js/lib/bootstrap-datepicker.js",
css: "css/datepicker.css",
initialized: false
},
/**
* Object containing XML specific methods. Extends String object.
* @type {Object}
*/
XML: {
trimXML: function(){ // Remove text nodes.
return this.replace(/^\s+|\>\s+\</gm,'');
},
HTMLtoXML: function(){ // Replace escaped HTML with text.
return this.replace("&lt;",'<').replace("&gt;",'>');
},
XMLtoHTML: function(){ // Replace illegal HTML control characters in XML string.
return this.replace(/\</gm,"&lt;").replace(/\>/gm,"&gt;").replace(/\n|\r/gm,"<br />");
}
},
// Core Methods:
/**
* SLP.tmpl: returns a block of HTML based on a template and data object.
* @param {String} tmplHTML A string of HTML containing replaceable data in the form, {{prop}}.
* @param {Object} obj An object containing properties matching template variables ({{var}}).
* @return {String} An HTML string to be added / replaced in the DOM.
*/
tmpl: function(tmplHTML,obj){
var i = 0, properties = tmplHTML.match(/\{\{(\w+)\}\}/gmi), l = properties.length;
for (;i<l;i++) tmplHTML = tmplHTML.replace(properties[i],obj[properties[i].replace(/[\{\}]/g,'')]);
return tmplHTML.replace(/null|undefined/gm,"");// Replace all errors with an empty string.
},
/**
* SLP.print: instantiate a window to print an HTML DOM Ojbect.
* @param {DOMElement} DOMobj A jQuery or DOM object with innerHTML.
*/
print: function(DOMobj){
var winPrinter = window.open('', "SLPPrinter", "width=900,height=525,top=150,left=150,toolbars=no,scrollbars=yes,status=no,resizable=yes"),
html = (DOMobj.jquery)? DOMobj.parent().html() : (DOMobj.parentNode)? DOMobj.parentNode.innerHTML : (DOMobj.innerHTML || '');
if (document.createStyleSheet) document.createStyleSheet(this.css); else // Dynamically added CSS is different for IE.
$("head",winPrinter.document).append($("<link/>").attr({ rel: "stylesheet", type: "text/css", href: this.css }));
$("body",winPrinter.document).append(html); winPrinter.focus(); winPrinter.print(); winPrinter.close();
return DOMobj;
},
/**
* SLP.datepicker: wrapper for Datepicker plugin for Bootstrap conditionally
* loaded based on feature detection (HTML5 input type attribute != date).
* @param {String} selector A jQuery selector for datepicker DOM elements.
* @return {Object} Either a passed argument or THIS namespace.
*/
datepicker: function(selector){
var $datepickers = $(selector);
if (SLP.DatePicker.initialized) return this;
if ($datepickers[0] && $datepickers.get(0).type !== "date") {
$.get(SLP.DatePicker.css, function(css){ // Load CSS and JS.
if (document.createStyleSheet) document.createStyleSheet(SLP.DatePicker.css);
else $('<style type="text/css"/>').html(css).appendTo("head");
$.getScript(SLP.DatePicker.js, function(data, status, jqxhr){
$datepickers.val(function(i,v){ // Set the date value.
var d = new Date(); d.setDate(d.getDate() -1 + i);
return (v==="")? (d.getMonth()+1) +'/'+ d.getDate() +'/'+ d.getFullYear() : v;
}).datepicker({ format: "m/d/yyyy" });
SLP.DatePicker.initialized = true;
});
});
}
return this;
},
/**
* Invoke with submit event of a form. Finds all input elements
* and returns true if at least one character has been entered.
* The this keyword assumes this method has been bound to a form.
* @param {object} event jQuery Event object
* @return {boolean} false stops submission
*/
validateFormInputsNotEmpty: function(event){
var $f = $(event.target), $i = $f.find("input");
return ($i.length == $i.filter('[value!=""]').length);
},
/**
* SLP.getLead: returns an SLP lead javascript object.
* @param {String|Number} lid The id for the lead.
* @param {String|Number} did The (optional) id for the destination.
* @return {Object} The (JSON) SLP Lead Data from the server.
*/
getLead: function(lid,did){
var lead = this.Leads[lid] || {};
if (did) lead = lead.destinations[did];
return lead; // If non-existent, blank object.
},
// Core Initialization. Call via a module.
/* Initialize functionality for the SLP namespace. */
init: function(DOMCache){
$.extend( this.DOM, DOMCache );
$("form").attr("method","post");
$('script[type="text/html"]').each(function(){
SLP.Templates[this.id] = this.innerHTML;
});// Render templates using SLP.tmpl method.
for (var method in this.XML) {// Extend String.
String.prototype[method] = this.XML[method];
}// SLP.XML methods can now be used on any string.
return this;
}
};
$(function(){
// If the user clicks on a row within a table with class "tbl-results",
// send them to the page defined by the table's data attributes, and
// include a query string with parameters based on the row data.
$(".tbl-results").find("tbody tr").click(function(event){
var $row = $(this), $tbl = $row.closest("table"), d = $tbl.data(), loc = d.page;
if (!loc || loc==="") return false;
if (d.filter) {
loc += '?'+ d.filter;
if ($row.data("query")) loc += '='+ $row.data("query");
}
location.href = loc;
});
});
/**
* SLP.Modules.Leads extends the SLP.Modules object on DOM-ready.
* @module Leads
*/
SLP.Modules.Leads = {
/**
* When a window resize event occurs, redraw sized objects.
* @param {object} event Native browser Event object.
*/
redrawElements: function(event){
SLP.DOM.$slpTblData.fnDraw(); // DT method to redraw Leads table.
if (SLP.DOM.$details.is(":visible")) { // Redraw details overlay.
var $detailsLink = SLP.Modules.Leads.closeDetailsOverlay();
if ($detailsLink) $detailsLink.trigger("click");
}
},
/**
* Performs automatic UI adjustments when clicking a checkbox for a lead.
* @param {object} event jQuery extended Event object.
*/
selectLead: function(event){
var $checked = SLP.DOM.$checkboxes.filter(":checked"),
$enabled = SLP.DOM.$checkboxes.filter(":enabled");
if ($checked.length == $enabled.length) $(".select-all").attr("checked",true);
else if (!this.checked) $(".select-all").attr("checked",false);
$(this).closest("tr").toggleClass("selected",this.checked);
$(".resubmit-selected").html(function(i,html){
return html.replace(/\d+/,$checked.length);
}).toggleClass("leaded",!!$checked.length);
},
/**
* Event handler for checkbox to select all resubmittable leads.
* Stops propagation of event bubbling to avoid column sorting.
* @param {object} event jQuery extended Event object.
*/
toggleEditAllSelection: function(event){ // (De-)Select all checkboxes.
SLP.DOM.$checkboxes.filter(":enabled").each(function(){
this.checked = event.target.checked;
SLP.Modules.Leads.selectLead.call(this);
});
event.stopPropagation();
},
/**
* Implements Details link overlay. When the user clicks on the Details link on a lead,
* this method handles the class change, arrow change, and display of details overlay.
* @param {object} event jQuery extended Event object (triggered by click event).
*/
showDetailsOverlay: function(event){ // Bugs present only with test data.
var $thisRow = $(this).closest("tr"), // this refers to Details link.
lid = $thisRow[0].id.replace("lead-",''); // Each row has a lead id.
try { // Use the TR id (instead of a data attribute) for faster DOM lookups.
if (!lid || !SLP.Leads[lid]) throw new Error("No lead data!");
$('i',SLP.DOM.$slpTblData).filter(".icon-chevron-up").add($('i',this))
.toggleClass("icon-chevron-down icon-chevron-up") // Change arrow icons.
.closest(".details-link").toggleClass("tab"); // Change text to a "tab".
// If the overlay is not showing (or it is showing but for a different lead id),
if (SLP.DOM.$details.is(":hidden") || SLP.DOM.$leadid.html().indexOf(lid) < 0) {
var dest, d, o, destinations = SLP.Leads[lid].destinations, counter = 0,
htmlDestinations = '', rpos = $thisRow.position(), dpos = $(this).position();
for (d in destinations) { // get each destination, add to details overlay...
dest = destinations[d]; // destinations property from JSON leads object.
o = { id: d, n: ++counter, disabled: (!!dest.xml ? '' : "disabled") };
htmlDestinations += SLP.tmpl(SLP.Templates.tmplDestination,$.extend(o,dest));
}
SLP.DOM.$leadid.html(SLP.DOM.$leadid[0].title +' '+ lid);// Update Lead ID text.
SLP.DOM.$destinations.html(htmlDestinations);// Set HTML for destinations.
SLP.DOM.$details.data("leadid",lid).css({ // Position relative to Details.
width: $thisRow.width() - 5, // Sets the overlay width based on table.
top: dpos.top + 20, // Drop the overlay 20 pixels (below Details link).
left: rpos.left // Position overlay to left side of Leads table.
}).show();
} else {
SLP.DOM.$details.hide();
}
} catch(e) {
// Show modal with error related to details not working.
SLP.Console.log(e); // Temporarily using console output.
}
},
/**
* Close the Details overlay via "X Close" link, column sorting, or window resize.
* @param {object} event jQuery extended Event object (optional and not used)
* @return {object} If visible, returns a DOM object otherwise undefined.
*/
closeDetailsOverlay: function(event){
if (SLP.DOM.$details.is(":visible")) { // Invokes showDetailsOverlay event handler.
return $("#lead-" + SLP.DOM.$details.data("leadid")).find(".details-link").click();
}
},
/**
* When Details overlay is visible, this is invoked by clicking Edit on a destination.
* Retrieves the lead XML (optionally based on destination) and sets the modal text.
* The modal functionality is handled via data attributes and the Bootstrap plug-in.
* @param {object} event jQuery extended Event object (optional and not used)
*/
launchXMLEditor: function(event){
var oLead = { // Get the Lead from Leads JSON.
lid: SLP.DOM.$details.data("leadid"),
did: $(this).data("id").toString()
}, // Create strings for display in UI.
lead = SLP.getLead(oLead.lid,oLead.did),
str = oLead.lid +' ('+ oLead.did +')';
SLP.DOM.$xmlModal.find(".leadid").html(str);
SLP.DOM.$xmlEditor.data("lead",oLead).text(lead.xml||'');
},
/**
* Updates the raw JSON lead data with the raw text from the XML modal editor.
* This should probably just send the data to the server using a form...
* @param {object} event jQuery extended Event object (optional and not used)
*/
saveXMLEditorData: function(event){
var data = SLP.DOM.$xmlEditor.data("lead"),
lead = SLP.Leads[data.lid].destinations[data.did];
lead.xml = $.text(SLP.DOM.$xmlEditor);
$("#lead-"+data.lid).find(".resubmit").click();
SLP.DOM.$xmlModal.modal("hide");
},
/**
* If the user press Enter or Tab keys while in the XML editor, we need to capture.
* This method will keep the user in the xml editor and insert appropriate characters.
* jQuery++ is used here to put the cursor back in the correct place after tab or enter.
* @param {object} event jQuery extended Event object (required)
*/
catchXMLEditorInput: function(event){
if (/^9$|^13$/.test(event.which)) { // jQuery++
event.preventDefault(); // Catch TAB and ENTER.
var $t=$(this), t=$.text($t), s=$t.selection(),
nc = (event.which==13) ? '\n' : '\t',
newCursorPos = s.end + nc.length;
$t.text(t.substring(0,s.start) +nc+ t.substring(s.end));
$t.selection(newCursorPos,newCursorPos);
}
},
/**
* Using the Lead template, create input elements with lead ids which should be re-submitted
* to the correct destination. This will need work as the full back-end implementation is not known.
* @param {array} leads For each lead checked in the table, pass in those leads.
*/
createFormLeads: function(leads){
var str = '', l = (typeof leads === "object")? leads.length : 0;
while (leads[--l]) str += SLP.tmpl(SLP.Templates.tmplResubmitLead,leads[l]);
SLP.DOM.$frmLeads.html(str);// Add the input elements to the form before submit.
},
/**
* Form submission event handler. Invoked when the user clicks a Resubmit button.
* Retrieves lead ids to be re-submitted, invokes input element creation, and submits.
* Form submit method will invoke any event handlers bound to the "submit" event.
* @param {object} event jQuery extended Event object (button click event)
*/
resubmit: function(event){
if ($(event.target).hasClass("resubmit-selected")) { // Re-submit selected leads.
var leads = [], $selectedLeads = SLP.DOM.$checkboxes.filter(":checked");
if (!$selectedLeads.length) return false;
$selectedLeads.closest("tr").each(function(){
leads.push(SLP.Leads[this.id.replace("lead-",'')]||{});
});
SLP.Modules.Leads.createFormLeads(leads);
SLP.DOM.$frmLeads.submit();
}
},
/**
* Invoked once, by init(), to attach event handlers (via Bootstrap plug-in) for Tooltips.
* Basically, this is just a wrapper to tell the Bootstrap plug-in what tooltip text to use.
* @return {object} (SML) Returns the SLP.Modules.Leads object for chaining.
*/
tooltips: function(){
SLP.DOM.$selectAll.parent().tooltip({ title: SLP.DOM.$selectAll.data("tooltip"), placement: "right" });
SLP.DOM.$comments.tooltip({ title: $("tbody",SLP.DOM.$slpTblData).data("tooltip") });
return this;
},
init: function(){
this.isScrollingTable = /scroll/.test(location.search);
SLP.datepicker(".datepicker");
return this.tooltips();
}
};
$(function(){
// On DOM Ready ...
SLP.Leads = slpLeads;
var dtColumns = [null],
SML = SLP.init({
// DOM (Cache) ...
$leadid: $("#leadid"),
$details: $("#details"),
$frmLeads: $("#frmLeads"),
$xmlModal: $("#XML-Modal"),
$xmlEditor: $("#XML-Editor"),
$selectAll: $(".select-all"),
$comments: $(".icon-comment"),
$slpTblData: $("#slpDataTable"),
$searchType: $("#slpSearchType"),
$checkboxes: $("input.resubmit"),
$destinations: $("#destinations"),
$ReportActions: $("#ReportActions")
}).Modules.Leads.init();
SLP.DOM.$slpTblData.find("th").each(function(i){
// We must build an array of sort options for each column.
// However, only the first column is customized for checkboxes.
dtColumns[i] = (i)? null : { "sSortDataType": "dom-checkbox" };
});
// Create options object for checkbox sorting (above) and (optionally) scroller.
var optsDataTable = { bInfo: false, bPaginate: false, aoColumns: dtColumns };
if (SML.isScrollingTable) optsDataTable.sScrollY = "570px";
SLP.DOM.$slpTblData.dataTable(optsDataTable);
SLP.DOM.$ReportActions.on("click", ".export-report", function(){});
SLP.DOM.$ReportActions.on("click", ".email-report", function(){});
SLP.DOM.$ReportActions.on("click", ".print-report", function(){
SLP.DOM.$slpTblData.siblings().remove();
SLP.print(SLP.DOM.$slpTblData);
});
SLP.DOM.$xmlModal.on("click", ".save-xml", SML.saveXMLEditorData);
SLP.DOM.$xmlEditor.on("keydown", SML.catchXMLEditorInput);
SLP.DOM.$checkboxes.on("click", SML.selectLead);
SLP.DOM.$details
.on("click", ".close", SML.closeDetailsOverlay)
.on("click", ".lead-edit", SML.launchXMLEditor)
.on("click", ".lead-resubmit", SML.resubmit);
SLP.DOM.$slpTblData
.on("click", "th", SML.closeDetailsOverlay)
.on("click", ".details-link", SML.showDetailsOverlay)
.find("td").filter(".name").each(function(){ // Hyphenate long names.
var n = this.innerHTML; if (n.length > 12) {
this.innerHTML = n.substring(12,0)+"- "+n.substring(12);
}
});
SLP.DOM.$searchType.on("change", function(){ SLP.DOM.$searchType.next("input").focus(); });
SLP.DOM.$selectAll.on("click", SML.toggleEditAllSelection);
$(".resubmit-selected").on("click", SML.resubmit);
window.onresize = SML.redrawElements;
});
(function($){
/* DataTables modifications for SLP /w BS */
$.extend( $.fn.dataTableExt.oStdClasses, {
"sWrapper": "dataTables_wrapper form-inline"
});
$.extend( $.fn.dataTableExt.afnSortData, {
"dom-checkbox": function(oSettings,iColumn) {
var aData = []; // This method allows for column sorting based on checkbox selections.
$('td:eq('+iColumn+') input', oSettings.oApi._fnGetTrNodes(oSettings)).each(function(){
aData.push( this.checked ? "1" : "0" );
}); // Requires aoColumns (and dtColumns).
return aData;
}
});
})(jQuery);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment