|
|
Line 1: |
Line 1: |
| === Basics === | | === Basics === |
|
| |
|
| | ==== Closures ==== |
|
| |
|
| | In JavaScript, if you declare a function within another function, then the local variables can remain accessible after returning from the function you called. |
|
| |
|
| | ==== Scope ==== |
| | |
| | local scope: var myvar = 1; |
| | global scope: myglobal = 2; // defined outside of any scope |
| | |
|
| |
|
| {| class="mw-collapsible mw-collapsed wikitable" | | {| class="mw-collapsible mw-collapsed wikitable" |
Revision as of 12:57, 7 March 2018
Basics
Closures
In JavaScript, if you declare a function within another function, then the local variables can remain accessible after returning from the function you called.
Scope
local scope: var myvar = 1;
global scope: myglobal = 2; // defined outside of any scope
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 |
|<-----------------------------------------------------|
| |
|
|
Node.js
|
Register with npm
|
A one-time registration is required on a new machine if you want to publish from it:
npm adduser
Username: moodboom
Password: (see private)
Email: (this IS public) moodboom@gmail.com
|
Publish a node module
|
sudo npm install -g # keep doing this until you are happy with local install
# update version in package.json
# this creates a FULL "annotated" tag, not a "lightweight" tag that doesn't show up for [git describe] - it also removes the need for a separate commit
git tag -a 1.0.5 -m "changes include..."
git push && git push --tags # NOTE: bitpost has a git hook to push changes all the way up to github
npm publish
|
Update a node module's dependencies
|
# make sure dependency in package.json has a carat at the beginning of its version (^x means "at least" version x)
# make sure the dependency has a new version available - completely publish it first if it is your own
# then you can simply reinstall from within the module folder to get all dependencies upgraded
sudo npm install -g
|
Develop several node modules at once
|
Convert dependencies to use local packages instead of published versions, eg:
cd ~/development/mah-haus
npm install -S /home/m/development/thedigitalage/rad-scripts
Then reinstall everything (local dependent modules, then parent modules, pita - consider links if doing longer-term dev)
sudo npm install -g
Then convert back to published versions as they become available (it's up to me to stabilize and publish new module versions):
cd ~/development/mah-haus
npm install -S rad-scripts
|
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!
|
|
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.
|
|