Javascript

From Bitpost wiki

Basics

See scraps scripts:

~/development/Reusable/JavaScript/scrap

ES6

ES6 is required learning!

Scope

myglobal = 1;       // global scope, defined outside of any scope
var myvar = 1;      // local scope, cacades into closures and blocks; OLDSKOOL, avoid
let myblockvar = 3; // block scope
const myconst = 4;  // const, has block scope

Sorting

You'd think the sort() call's comparison function would be a "less than" check, like in C++. NOPE. You MUST return -1 or 1, not a boolean result. Wtf were they thinking... Working example:

 data.sort(( a, b ) => {
   // DO NOT SIMPLY RETURN a.value < b.value, you will gnash and cry bitter tears...
   return ( a.value < b.value ) ? 1 : -1;
 });

Array find-or-fail

The old stupid anti-pattern that far too many rookies jump right in to:

if find then find else fail.

There are a couple ways to properly find-or-fail, here's one, I have a better one that I can't remember atm, doh...

const targetType = "selected";
myArray.find( item => item.type === targetType )?.description || 'unknown'

Immutability

  • Don't think that const gives you a const variable or object. Only the variable binding is const, ie it can't be reassigned.
  • myVar.freeze() will make your var shallowly immutable - but what's the point of that?
  • Still waiting on a deep immutable standard solution. Ridiculous. As a workaround, use deep copies (also hard to do, see next item).

Deep Copy

Assignment of a new object to an existing one makes the new object a reference to the existing:

var a = { "value": 3 }
var b = a;
a.value = 4; // b.value will also now = 4.

Obviously, often (for better immutability for example), we want a copy. It's hard to believe but deep copies are hard to accomplish out of the box in silly Javascript. According to this guy's benchmark, this works well:

var newAllocation = JSON.parse(JSON.stringify(deepObject))

For futureproofing and convenience, use my deepCopy() function wrapper, in [at.js].

Closures

If you declare a function within another function, then the local variables can remain accessible after returning from the function you called.

The scope of the internal local variables is the lifetime of the function. So if you have a function, you have its local variables too.

You must use the function keyword inside another function to create a closure.

Promises

Libraries

Javascript and JQuery
Formatting examples (numbers dates etc.)
 // Javascript sucks at dates.
 // d3 to the rescue!  
 var parseInitDate = d3.utcParse("%Y-%m-%dT%H:%M:%SZ");
 var initDate = parseInitDate(initial_managed_value_date);
 var initDateString = 
     initDate.getUTCFullYear() 
     +"/"+ (initDate.getUTCMonth()+1) 
     +"/"+ initDate.getUTCDate()
     + " " + initDate.getUTCHours() 
     + ":" + initDate.getUTCMinutes() 
     + ":" + initDate.getUTCSeconds();
 var rightNow = new Date;
 var initToNow_DecimalDays = parseFloat(rightNow - initDate) / 864e5;  // 86,400,000 ms/day
 var initToNow_PercentOfYear = initToNow_DecimalDays / 365.0;
 var change_in_value = (parseFloat(total_managed_value) - parseFloat(initial_managed_value))/parseFloat(initial_managed_value);
 $('#initial_managed_value').val(initial_managed_value.toFixed(2)); 
 $('#initial_managed_value_date').val(initDateString);
 $('#change_in_value').val((change_in_value*100.0).toFixed(1)+ "%");
 $('#equivalent_apr').val((change_in_value/initToNow_PercentOfYear*100.0).toFixed(1)+ "%");
 $('#total_managed_value_readonly').val(total_managed_value.toFixed(2));
Array vs Object lifetime
       // If the JSON data were an array, we would need to slice-to-clone to keep it around:
       //     var dataCopy = data.slice();
       //     updateStockData(run,dataCopy);
       // We have an object so we can pass a reference, and it will keep the object around.
Event handlers
Three basic methods:
  • Attach a function to an event of a specific element; charts.js example:
     var label = jQuery('<label class="btn btn-sm btn-'+color+'"></label>').appendTo(action_button_bar);
     var input = jQuery('<input class="run-'+cmd+'" type="checkbox" autocomplete="off" value="'+cycle.run+'">').appendTo(label)
     .on('change', cycle.owned? function(){
       patchPick(this.value,'{ "action" : "run-hold"       }');
     } : function() {
       patchPick(this.value,'{ "action" : "run-buy"        }');
     });
     var text = jQuery(' ').appendTo(label);
  • Add a function handler once, for a class of elements; picks.js example:
 $("input[type='checkbox'][class='run-top'    ]").change(function() { patchPick(this.value,'{ "action" : "run-move-top"   }');  });
  • Event delegation: Javascript takes events and "bubbles them up" through the chain of parents; set up a handler on a parent to listen for these bubbles.
  • At end of handler, prevent further bubbling with stopPropagation() - still finishes the current event (eg following the href of an <a>):
    var datepicker = jQuery(
      '<div class="input-group date form-inline pull-left" data-provide="datepicker" id="datepicker'+cycle.run+'">'+
        '<input type="text" class="input-sm form-control">'+
        '<div class="input-group-addon">'+
            '<span class="glyphicon glyphicon-th"></span>'+
        '</div>'+
      '</div>'
    )
    .on('changeDate', function(e){
      alert('next '+cycle.run);
      e.stopPropagation();
    })
    ;    
  • Or use preventDefault() to stop completely:
    var datepicker_next = jQuery(
      '<a href="">My javascript action</a>'
    )
    .on('click', function(e){
      alert('next '+cycle.run);
      e.preventDefault();
    })
    ;
Debug clicks on ANY PAGE
  • Press F12 => Sources => Event Listener Breakpoints righthand pane => Mouse => [x] click

B O O M we have it captured and can step into ANYTHING.

JWT flow
Client is easy!
   Client application                                            API
   --------                                              -----------
        |                                                      |
        |                   GET /api/employees                 |
        |----------------------------------------------------->|
        |                     403 Forbidden                    |
        |<-----------------------------------------------------|
        |                                                      |
        |                                                      |
        |                 POST /api/authenticate               |
        |     { login: "john.doe", password: "password" }      |
        |----------------------------------------------------->|
        |                      200 Success                     |
        |             { token: "my.personal.token" }           |
        |<-----------------------------------------------------|
        |                                                      |
        |                                                      |
        |                 GET /api/employees                   |
        | Header { "Authorization: Token "my.personal.token" } |
        |----------------------------------------------------->|
        |                      200 Success                     |
        |<-----------------------------------------------------|
        |                                                      |
Node.js
Bootstrap
Single Page Application
css:
  .mdm-scrollable-div
  {
    height: 100%; 
    overflow: auto;
  }

html:

  <-- header -->

  <div class="frame" id="frame1top">
    <div class="mdm-scrollable-div">
      ...
    </div>
  </div>

  <-- footer -->

js:

  // We need to adjust layout on resize, in ready handler, and programmatically as needed.
  $(window).resize(adjustLayout).resize();

  $( document ).ready(function() {

    // We need to adjust in ready handler, on resize, and programmatically as needed
    adjustLayout();

    /* your other page load code here*/

  });

  function adjustLayout(){

      // ----------------------
      // Fundamental dimensions
      var hh = $('#theheader').outerHeight();
      var fh = $('#thefooter').outerHeight();
      var workspace_height = $(window).height() - hh - fh;
      var workspace_width = $(window).width(); 
      // ----------------------
      
      var cols = 1;
      var col_gap = 16;
      
      // Margin is based on percent of one column width, so it goes to zero before column width does.  :-)
      // Width is based on total width minus margins, then split between columns.
      var margin = ($(window).width() / cols) * .04;
      var w = ($(window).width() - (margin * (cols + 1))) / cols;
      
      var h1 = workspace_height;
      
      $('#frame1top').css({
          display:'block', 
          position:'absolute',
          left: margin * 1 + w * 0,
          top: hh,
          width: w,
          height: h1
      });
  }
Button handlers
Buttons can be represented with labels; in that case, use a click handler:
    jQuery('<label id="'+cycle.apsID+'" class="btn">MahButt</label>')
    .appendTo('#'+chartid+'-apsbar')
    .on('click', function(){
      $(location).attr('href','/v1/parameters/'+this.id+'.html');
    });

You can also get the checked state, and prevent the button from being pressed, among other things:

      jQuery('<label id="'+cycle.run+'" ...
      .on('click', function(e){
        var run = this.id;
        // Look for the (label->input)
        if (this.firstChild.checked)
        ...
        // To prevent checked/pressed state if desired:
        e.stopPropagation();
      }  

Button bars are represented by labels wrapped around inputs:

    <div class="btn-group live-buttons" data-toggle="buttons"><div class="btn-group"><label class="btn"><input type="checkbox">text...

In that case use a change handler on the input:

      var label = jQuery('<label class="btn btn-sm btn-'+color+'"></label>').appendTo(action_button_bar);
      var input = jQuery('<input class="run-'+cmd+'" type="checkbox" autocomplete="off" value="'+cycle.run+'">').appendTo(label)
      .on('change', cycle.owned? function(){
        patchPick(this.value,'{ "action" : "run-hold"       }');
      } : function() {
        patchPick(this.value,'{ "action" : "run-buy"        }');
      });
      var text = jQuery('<span class="glyphicon glyphicon-'+glyph+'"></span> <span class="hidden-xs">'+cmd+'</span>').appendTo(label);

If you just need clicks from the button bar, you do NOT NEED input:

    <div class="btn-group live-buttons" data-toggle="buttons"><div class="btn-group"><label class="btn">text...

Then you can put the change handler right on the label:

    var applybar = jQuery('<div id="applybar-'+cycle.run+'" class="btn-group pull-right" data-toggle="buttons" />');
    var apply_button = jQuery('<label id="apply-'+cycle.run+'" class="btn btn-sm btn-moneygreen"><span class="glyphicon glyphicon-ok"></span><span class="hidden-xs"> Apply</span></input></label>')
    .on('click', function(e) {    
    
        // Do work

        e.stopPropagation();
    })
    .appendTo(applybar)
    ;
Programmatically pressing a button
You MUST DO TWO THINGS:
  • Set the active class on the button/label
  • Set the input to checked
<label class="btn active">
  <input type="checkbox" autocomplete="off" checked>
  Click me
</label>
Handling the UNCHECK event on a pushbutton
Again you MUST SET BOTH active and checked STATES PROPERLY when creating the button (see above). Do not set either if the button is unpressed; set both if it is pressed.

Then you can use a single change event:

  $("input[type='checkbox'][class='run-hold-on-buy'     ]").change(function() { patchPick(this.value,'{ "action" : "'+(this.checked?'hold-on-buy-on'     :'hold-on-buy-off'     )+'"}');  });
Collapsible panel
  • Use a unique id on content div, data-target to connect panel header to content, mdm-panel-collapser so javascript can find icon, abt-scrollable-panel for margin.
  • HTML for each panel (starts collapsed)
    <div class="panel panel-default abt-scrollable-panel">
      <div class="panel-heading collapsed" data-toggle="collapse" data-target="#my-content-block-one">Software development skills<span class="mdm-panel-collapser text-muted glyphicon glyphicon-chevron-down pull-right"></span></div>
      <div class="collapse" id="my-content-block-one">
        <div class="mdm-panel-body">
  
         <!-- CONTENT, can include another nested panel, just add .mdm-nested-panel to class; example: -->
  
          <div class="panel panel-default mdm-scrollable-panel mdm-nested-panel">
            <div class="panel-heading collapsed mdm-job-panel-heading" data-toggle="collapse" data-target="#toshiba-job"><p><strong>Senior Developer and Offshore Manager</strong><i> - Toshiba Global Commerce Solutions, Inc.</i><span class="mdm-panel-collapser text-muted glyphicon glyphicon-chevron-up pull-right"></span></p><p><small>April 2014 – August 2015</small></p></div>
            <div class="collapse in" id="toshiba-job">
              <div class="mdm-panel-body">
                <!-- SUBCONT(IN)ENT im so funny -->
              </div>
            </div>
          </div>
          

        </div>
      </div>
    </div>
  • For an expanded panel, simply change icon from down to up, and add "in" to content div:
    ... glyphicon-chevron-up ...
    <div class="collapse in mdm-panel-body" id="collapseOrderItems1">
  • Define Javascript collapser once on load
 $('.collapse').on('hide.bs.collapse show.bs.collapse', 
   toggleCollapser
 );
  • CSS
   .mdm-nested-panel {
     margin-top: 1em;
     margin-left: 1em;
   }
   collapse.mdm-panel-body collapsing.mdm-panel-body {
       margin: 1em;
   }
  • Common reusable function:
 function toggleCollapser(e) {
     $(e.target)
         .prev('.panel-heading')
         .find('.mdm-panel-collapser')
         .toggleClass('glyphicon-chevron-down glyphicon-chevron-up');
 
   // Prevent bubble up to any parent collapsers.
   // This allows nested collapsers, whoop.
   e.stopPropagation();
 }
D3
ALWAYS initialize incoming data
Hard-earned data-import lessons:
  • tsv() will do complete date formatting, so when you remove it to use JSON directly, you HAVE TO convert JSON timestamps of ANY kind to full Javascript date objects:
 var data = [{"date":"2017-04-01T04:00:00.000Z",...
 // MDM WE MUST MASSAGE DATA HERE
 // JSON VALUES ARE ALWAYS STRINGS, we need to change to Javascript DATE!
 // Incoming datasets need a bit of massaging.
 // We do that in a function so we can reuse it on incoming dataset updates.
 function initializeDataset(dataset) {
     // TIME ON X
     // Convert date strings to actual date values.
     // MDM MAKE SURE this matches the incoming format.
     // Adding date so we can run the chart across days without trouble.
     // var parseDate = d3.time.format("%H:%M:%S %m-%d-%Y").parse;
     // var parseDate = d3.timeParse("%Y %b %d");
     var parseDate = d3.utcParse("%Y-%m-%dT%H:%M:%S.%LZ");
     dataset.forEach(function(d) {
         // there is NO SKIPING THIS STEP, you have to get valid Javascript date objects out of JSON strings
         d.date = parseDate(d.date);
         // we WOULD have to divide all data by zero,
         // but we already grabbed post-data that was already converted
         // This WORKS but makes data / 10000 (very small numbers)
         //for (var i = 1, n = dataset.columns.length; i < n; ++i) d[dataset.columns[i]] = d[dataset.columns[i]] / 100;
     });
 }
 initializeDataset(data);

  • tsv() will create an ARRAY but it also jams in a columns PROPERY; it takes two steps to duplicate that:
  var data = [
    {"date":"2015-06-15T04:00:00.000Z","Google Chrome":0.48090000000000005,...},
    {"date":"2015-06-22T04:00:00.000Z","Google Chrome":0.48979999999999996,...),
 ];
 data.columns = ["date","Google Chrome","Internet Explorer",...  ];
ALWAYS set range and domain properly
You have to know your display size (range) and the min and max of your data (domain), on each axis. From Mike's docs:
       D3’s scales specify a mapping from data space (domain) to display space (range).
       D3’s scales can also be used to interpolate many other 
       types of display-space values, such as paths, color spaces 
       and geometric transforms.
       
         var x = d3.scale.linear()
             .domain([0, d3.max(data)])
             .range([0, 420]);
 
       Although x here looks like an object, it is also a function 
       that returns the scaled display value in the range 
       for a given data value in the domain. 
       For example, an input value of 4 returns 40, and an input value of 16 
       returns 160. To use the new scale, simply replace the 
       hard-coded multiplication by calling the scale function:
       
       d3.select(".chart")
         .selectAll("div")
           .data(data)
         .enter().append("div")
           .style("width", function(d) { return x(d) + "px"; })
           .text(function(d) { return d; });

Watch out for Mike's examples where he (trickily) doesn't bother to set a domain because his data is in the [0..1] set, and that's the default domain, apparently.

Vega-lite
Moment.js
Make sure to specify a format when using a STRING SOURCE, eg:
moment(myinputdate, "HH:mm:ss MM-DD-YYYY")

From that point on, you'll have a JavaScript date object.

Accounting.js
Simple money library used by another bigger one (money.js) that we shouldn't need.
auto AWS
  • npm install -g aws-sdk
  • Add credentials here: C:\Users\Administrator\.aws
  • see existing scripts, anything is possible
install bootstrap
  • npm install -g grunt-cli
  • mkdir mysite && cd mysite
  • npm install bootstrap
  • cd node_modules/bootstrap
  • npm install # to actually pull down dependencies
  • grunt dist # builds and minifies so you're good to go!
yarn alternative to npm