|
|
Line 14: |
Line 14: |
| let myblockvar = 3; // block scope | | let myblockvar = 3; // block scope |
| const myconst = 4; // const, has 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 ==== | | ==== Array find-or-fail ==== |
Revision as of 16:24, 13 December 2022
Basics
See scraps scripts:
~/development/Reusable/JavaScript/scrap
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.
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(' '+cmd+'').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 |
|<-----------------------------------------------------|
| |
|
|
Libraries
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
);
.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.
|
|
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!
|