The script in detail

Dealer widget scripts

Uncompressed dealer widget code

Please note that this example of the script may not be absolutely up-to-date.

The CSS used by the widget to display the modal is first minified and then included in the JS below, which is itself then minified. The original CSS is detailed below.

// wrapped up this way to minimise namespace pollution
(function(win, doc) {

  // ======================================================================
  // CONFIG
  // ======================================================================

  var _Proto = 'https://';
  var _Host = 'www.classicandsportsfinance.com';

  var _HostPath = _Host + '/finance-enquiry-form';
  var _Buttons = _Proto + _Host + '/dealer-widget/buttons/';
  var _WidgetClass = 'csf-enquiry-widget';
  var _WidgetTooltip = "Finance options are available for this vehicle";
  var _ScriptWidgetType = 'CSF/Widget';
  var _FrameURL = _Proto + _Host + '/dealer-widget-form/'; 
  var _Params = ['make-model', 'price', 'dealer', 'image'];
  var _WidgetMinHeight = 500;
  var _WidgetMinWidth = 600;
  var _WidgetMaxHeight = 500;
  var _WidgetMaxWidth = 600;
  var _WidgetFrameID = "csf_widget_frame";
  var _WidgetContainerID = "csf_widget_container";

  var _CSS = ""; // put minified CSS in here
  
  // ======================================================================
  // FUNCTIONS
  // ======================================================================
  
  // create a stylesheet tag based on our minified CSS, and add it to the page
  function loadWidgetStyles() {
    // create a stylesheet tag element
    var ss = doc.createElement('style');
    var hh = doc.getElementsByTagName('head')[0];
    ss.setAttribute('type', 'text/css');
    // add the tag to the document head
    // (this odd order helps with older IE)
    hh.appendChild(ss);
    // now append the CSS source
    if (ss.styleSheet) {
      ss.styleSheet.cssText = _CSS;
    } else {
      ss.appendChild(doc.createTextNode(_CSS));
    }
  }
  
  // safe log file wrapper
  function log(msg) {
    if(console) console.log(msg);
  }

  // calculate a popup size 
  // returns a boolean indicating if there's enough room
  function getPopupDimensions(wdims) {
    var pdims = { 
      w: (_WidgetMaxWidth > wdims.w ) ? Math.floor(wdims.w * 0.9) : _WidgetMaxWidth,
      h: (_WidgetMaxHeight > wdims.h ) ? Math.floor(wdims.h * 0.9) : _WidgetMaxHeight
    };
    return ((pdims.w >= _WidgetMinWidth) && (pdims.h >= _WidgetMinHeight)) ? pdims : null;
  }
  
  // get the dimensions of the current window
  function getWindowDimensions() {
    return {
      w: win.innerWidth || doc.documentElement.clientWidth || doc.body.clientWidth,
       h: win.innerHeight || doc.documentElement.clientHeight || doc.body.clientHeight
    };
  }
  
  // this is really just for tidy code!
  function px(x) {
    return '' + x + 'px';
  }
  
  // cross-platform add and remove event handlers
  function addEventHandler(e,ev,h) {
    // standards-compliant
    if (e.addEventListener)
      e.addEventListener(ev,h,false);
    // IE 8
    else if (e.attachEvent)
      e.attachEvent('on'+ev,h); 
  }
  
  function removeEventHandler(e,ev,h) {
    // standards-compliant
    if (e.removeEventListener) 
      e.removeEventListener(ev,h,false);
    // IE 8
    if (e.detachEvent)
      e.detachEvent('on'+ev,h); 
  }
  
  // pressing the escape key clears the popup
  function escapeKeyHandler(e) {
    if(e.keyCode==27) widgetFormClose();
  }  

  // extract enquiry form parameters from the data attributes
  function buildEnquiryParamsFromAttributes(el, ps) {
    for(pi =0; pi < _Params.length; pi++) {
      var param = _Params[pi];
      
      // if the attribute is specified (e.g. data-price), extract it
      var val  = el.getAttribute('data-' + param);
      if((typeof(val) != 'undefined') && val) {
        ps[param] = val;
      }
      else {
        // if an extract attribute is set (e.g. data-extract-price)
        // locate the element it refers to, and calculate its plain text content
        var sel = el.getAttribute('data-extract-' + param);
        if((typeof(sel) != 'undefined') && sel) {
          var selected = doc.querySelector(sel);
          var selected_val = (selected.innerText || selected.textContent || '').replace(/[\s\n\t\r]+/, ' ');
          ps[param] = selected_val;
        }
      }
      
    }
    return ps;
  }

  // extract enquiry form parameters from the query string
  function buildEnquiryParamsFromQueryString(el, ps) {
    var qs = el.search.replace(/^\?/, '').split('&');
    for( qi = 0; qi < qs.length; qi++ ) {
        var p = qs[qi].split('=');
        ps[p[0]] = decodeURIComponent(p[1].replace(/\+/g, '%20'));
    }
    return ps;
  }
  
  // create a new URL with our sanitised/combined parameters
  function getURLWithParameters(url, el) {
    var pairs = [];
    for(pi =0; pi < _Params.length; pi++) {
      var param = _Params[pi];
      if(param == 'image') { continue; }
      var val;
      if(val = el.csf_enquiry_params[param]) {
        pairs[pairs.length] = param + '=' + encodeURIComponent(val);
      }
    }
    
    if(url.indexOf('/', url.length - 1) === -1) {
      url = url + '/';
    }
    
    return url + '?' + pairs.join('&');
  }
  
  // find all image links
  function findAllFormLinks() {
    var ws = [];
    // find all the CSF links in the page
    var links = doc.querySelectorAll(
      'a[href^="http://' + _HostPath + '"], ' +
      'a[href^="https://' + _HostPath + '"]');
                    
    var num_links = links.length;
    // loop through, annotating the script tags with the enquiry params
    for(var i=0;i<num_links;i++) {
      var l = links[i];
      // if this isn't an upgraded button, we need to pull the enquiry params
      if(!l.csf_enquiry_params) {
        // enquiry params can come from the query string or from data attributes
        l.csf_enquiry_params = 
          buildEnquiryParamsFromAttributes(l,
            buildEnquiryParamsFromQueryString(l, {}));
      }
      // we can use it if we have a dealer id
      if(l.csf_enquiry_params.dealer) {
        ws[ws.length] = l;
      }
    }
    return ws;
  }

  // find all our pure-javascript buttons
  function findAllScriptWidgets() {
    var ws = [];
    // find all the script tags in the page
    var ss = doc.querySelectorAll('script[type="' + _ScriptWidgetType + '"]');
    var num_ss = ss.length;

    // loop through, annotating the script tags with the enquiry params
    for(var i=0;i<num_ss;i++) {
      var scr = ss[i];
      scr.csf_enquiry_params = buildEnquiryParamsFromAttributes(scr, {});
      // just needs a dealer ID to be useful
      if(scr.csf_enquiry_params.dealer) {
        ws[ws.length] = scr;
      }
    }
    return ws;
  }
  
  // close the widget form and clear up
  function widgetFormClose() {
    
    // clear any modal wrappers
    var modals = doc.querySelectorAll('.' + _WidgetClass + '-modal');
    for (var i = 0; i < modals.length; i++) {
      modals[i].parentNode.removeChild(modals[i]);
    }

    // clear any containers
    var widgets = doc.querySelectorAll('.' + _WidgetClass + '-form-container');
    for (var i = 0; i < widgets.length; i++) {
      widgets[i].parentNode.removeChild(widgets[i]);
    }

    // tidying up: remove the event handler we added
    removeEventHandler(win, 'keypress', escapeKeyHandler);
    removeEventHandler(win, 'resize', windowResizeHandler);
    return false;  
  }
  
  function resizeContainer(c, pdims, wdims) {
    c.style.height = px(pdims.h);
    c.style.width =  px(pdims.w);
    c.style.top = px(Math.floor((wdims.h - pdims.h) / 2)); 
    c.style.left = px(Math.floor((wdims.w - pdims.w) / 2));
  }
  
  function resizeFrame(f, pdims) {
    f.style.height = px(pdims.h);
    f.style.width =  px(pdims.w);
    f.height = pdims.h;
    f.width = pdims.w;
  }
  
  function windowResizeHandler() {
    var wdims = getWindowDimensions();
    var pdims = getPopupDimensions(wdims);
    if(pdims) {
      resizeContainer(doc.getElementById(_WidgetContainerID), pdims, wdims);
      resizeFrame(doc.getElementById(_WidgetFrameID), pdims);
    }
  }
  
  function widgetClick() {

    // close anything that is open now
    widgetFormClose();
    
    // window dimensions
    var wdims = getWindowDimensions();
    
    // check browser size against requirements
    var pdims = getPopupDimensions(wdims);
    if(!pdims) {
      log('The browser window is too small to display the popup.');
      this.target="_blank";
      return true;
    }

    // modal overlay
    var modal = doc.createElement('div');
    modal.className = _WidgetClass + '-modal';
    modal.onclick = widgetFormClose;
    
    // the popup container
    var container = doc.createElement('div');
    container.className = _WidgetClass + '-form-container';
    container.id= _WidgetContainerID;
    container.style.display = 'block';
    resizeContainer(container, pdims, wdims);
    
    // close button for the popup
    var button = doc.createElement('span');
    button.innerHTML = 'close window';
    button.onclick = widgetFormClose;

    // inner div for the popup
    var inner = doc.createElement('div');
    inner.className = _WidgetClass + '-form-container-inner';
    
    // the iframe where the enquiry actually happens
    var frame = doc.createElement('iframe');
    frame.src = getURLWithParameters(_FrameURL, this);
    frame.scrolling = "no";
    frame.frameBorder = "0";
    frame.id= _WidgetFrameID;
    resizeFrame(frame, pdims);

    // add the button and iframe to the inner
    inner.appendChild(button);
    inner.appendChild(frame);

    // add the inner part to the container
    container.appendChild(inner);
    
    // add the modal overlay to the page
    modal.style.display = 'block';
    doc.body.appendChild(modal);
    
    // add the popup on top
    doc.body.appendChild(container);
    
    // add the escape key handler
    addEventHandler(win, 'keypress', escapeKeyHandler);

    // and the resize handler
    addEventHandler(win, 'resize', windowResizeHandler);
  
    return false;
  }
  
  // this function converts the script-type widget to a clickable image
  // before the main function upgrades it
  function convertScriptWidgets() {
    var ss = findAllScriptWidgets();
    for(i=0; i < ss.length; i++) {
      var s = ss[i];

      // create an image
      var im = doc.createElement('img');
      im.src = _Buttons + (s.csf_enquiry_params.image || 'black_fe_orange_grey_120') + '.png';

      // create A link
      var a = doc.createElement('a');
      a.csf_enquiry_params = s.csf_enquiry_params;
      a.href= _Proto + _HostPath; 
      a.appendChild(im);
      
      // make a note of where the script tag is
      var sp  = s.parentNode;
      var sn = s.nextSibling;
      // now remove it
      s.parentNode.removeChild(s);

      // insert the A link where the script was
      if(sn) {
        sp.insertBefore(a, sn);
      } 
      else {
        sp.appendChild(a);
      }
    }    
  }
  
  // this function upgrades the links so they launch a popup instead
  function upgradeFormLinks() {
    var fls = findAllFormLinks();
    for(i=0; i < fls.length; i++) {
      var fl = fls[i];
      // add the click action
      fl.removeAttribute('target');
      fl.onclick = widgetClick;
      // add a simple tooltip
      fl.title = _WidgetTooltip;
      // update the link with the fully-configured URL, for right-click friendliness
      fl.href = getURLWithParameters(_Proto + _HostPath, fl);
    }
  }
  
  // this kicks it all off
  // run directly or as a result of an onload handler
  function run() {

    // load our stylesheet
    loadWidgetStyles();
    
    // convert buttons to image links
    convertScriptWidgets();
    
    // upgrade form links
    upgradeFormLinks();

  }  

  // ======================================================================
  // HERE'S WHERE IT STARTS
  // ======================================================================
    
  // this script requires querySelector/querySelectorAll
  // which is supported by pretty much everything except IE7 and IE6
  // (Note that this also rules out IE8 in compatibility mode)
  if(!doc.querySelector) {
    log("This browser does not support the CSF enquiry widget.");
    return;
  }
  
  // examine the document's state
  switch (doc.readyState) {
    // DOM not ready so we add an onload handler to do our stuff
    case "uninitialized":
    case "loading":
    case "loaded":
      addEventHandler(win, "load", function() { run(); });
      break;
    // in these cases, the DOM is ready so we can continue
    case "interactive":
    case "complete":
      run();
  }
  
}(window, document));

Uncompressed widget CSS

The CSS, unminified:

.csf-enquiry-widget-modal {
    display:        none;
    position:       fixed;
    left:           0;
    top:            0;
    width:          100%;
    height:         100%;
    background:     #000;
    filter:         alpha(Opacity = 50);
    opacity:        0.5;
    -moz-opacity:   0.5;
    -khtml-opacity: 0.5;
    z-index: 999999;
}

.csf-enquiry-widget-form-container {
    display: none;
  position: fixed;
    border: 3px solid #999;
    
    -webkit-box-shadow: 10px 10px 12px 1px rgba(0,0,0,0.27);
    -moz-box-shadow: 10px 10px 12px 1px rgba(0,0,0,0.27);
    box-shadow: 10px 10px 12px 1px rgba(0,0,0,0.27);
    z-index: 999999;
}

.csf-enquiry-widget-form-container-inner {
    background-color: #fff;
    position: relative;
    top: 0;
}

.csf-enquiry-widget-form-container-inner span {
    cursor: pointer;
    position: absolute;
    display: inline-block;
    font-family: Arial, Helvetica, sans-serif;
    top: 8px;
    right: 8px;
    border: 0;
    font-size: 12px;
    text-align: center;
    color: #fff;
    background: #999;
    border-radius: 5px;
    padding: 0.25em 0.5em;
    text-decoration: none;
}

.csf-enquiry-widget-form-container-inner span:hover {
    text-decoration: none;
    background-color: #444;
}

.csf-enquiry-widget-form-container iframe {
    display: block;
    border: none;
}