Skip to content

Instantly share code, notes, and snippets.

@codenameyau
Last active April 6, 2022 13:07
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save codenameyau/9f0a26a0f1be35a6784a to your computer and use it in GitHub Desktop.
Save codenameyau/9f0a26a0f1be35a6784a to your computer and use it in GitHub Desktop.
Javascript Snippets
/************************************************************************
* CLIENT ONE-LINERS
*************************************************************************/
// Set top level domain cookie.
document.cookie = "token=12345; Max-Age=120; Secure; Domain=mozilla.org; Path=/;"
// Set a subdomain cookie.
document.cookie = "name=hello world; Max-Age=120; Secure; Domain=developer.mozilla.org; Path=/;"
// You can have cookies with the same name but with different paths and domains.
document.cookie = "name=1; Max-Age=60; Secure; Domain=mozilla.org;"
document.cookie = "name=2; Max-Age=60; Secure; Domain=mozilla.org; Path=/"
document.cookie = "name=3; Max-Age=60; Secure; Domain=developer.mozilla.org; Path=/;"
// Copies variable.
copy(temp1)
copy(JSON.stringify(temp1))
// Capture regex groups with variable
const string = 'ayyyy lmao'
const rgx = /(?<firstVowel>[aeiou])/
string.match(rgx).groups.firstVowel
// Copies current selection to clipboard.
// https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand
// https://hackernoon.com/copying-text-to-clipboard-with-javascript-df4d4988697f
document.execCommand('copy')
// Copies text content of click to clipboard.
navigator.clipboard && navigator.clipboard.writeText(event.target.textContent);
// Makes page text editable.
document.designMode = 'on'
// Access third party frames on window.
window.frames
// Create bookmark and set this to URL to skip ads.
javascript:void(document.querySelector('video').currentTime = document.querySelector('video').duration)
// See all event listeners. Remember to remove listener when unmounting.
getEventListeners(document)
// See Slack Emoji authors.
Array.from(document.querySelectorAll('.author_cell > a')).map((a) => a.text.trim()).sort()
// Use this to click all links.
Array.from(document.querySelectorAll('a.block')).forEach(a => a.click())
// Grab all the images from a page (namely instagram)
Array.from(document.images).forEach(img => console.log(img.getAttribute('src')))
// Like all facebook posts.
Array.from(document.body.querySelectorAll('.UFILikeLink')).forEach((a) => a.click())
// Selecting wildcard classes.
Array.from(document.querySelectorAll('div[class^="ManageSection__column-left"] .dp-checkbox')).forEach((a) => a.click())
/************************************************************************
* SERVER ONE-LINERS
*************************************************************************/
// Use Now to deploying static app serving a single page app.
ns ./build --cmd 'list ./content -s'
// Checks if nested property exists.
db.drafts.find({"collection_type": "web_advertising", "email": "jorge@email.com", "rows.222": { $exists: true } });
// Grabs the count.
db.drafts.find({"email": "jorge@email.com"}).count();
/********************************************************************
* UTILS
*********************************************************************/
// https://stackoverflow.com/q/779379
setTimeout(function (callback) {
// Offload Heavy computation.
}, 0);
/********************************************************************
* JQUERY serializeArray
*********************************************************************/
// Converts form into Array of Objects.
// https://plainjs.com/javascript/ajax/serialize-form-data-into-an-array-46/
function serializeArray(form) {
var field, l, s = [];
if (typeof form == 'object' && form.nodeName == "FORM") {
var len = form.elements.length;
for (var i = 0; i < len; i++) {
field = form.elements[i];
if (field.name && !field.disabled && field.type != 'file' && field.type != 'reset' && field.type != 'submit' && field.type != 'button') {
if (field.type == 'select-multiple') {
l = form.elements[i].options.length;
for (j = 0; j < l; j++) {
if (field.options[j].selected)
s[s.length] = {
name: field.name,
value: field.options[j].value
};
}
} else if ((field.type != 'checkbox' && field.type != 'radio') || field.checked) {
s[s.length] = {
name: field.name,
value: field.value
};
}
}
}
}
return s;
}
// Nodejs check if module is called directly
const CALLED_DIRECTLY = require.main === module;
function splitTextarea(text) {
return text.trim().replace(/\s+/, '\n').replace(/\n{2,}/, '').split('\n');
}
// Download using fetch API..
import download from 'downloadjs';
const handleBufferDownload = (filename) => {
res.arrayBuffer()
.then((buffer) => {
download(buffer, filename, contentType);
stopLoading();
});
};
// http://stackoverflow.com/a/23842171
encodeURIComparison = function() {
var arr = [];
for(var i=0; i<256; i++) {
var char=String.fromCharCode(i);
if(encodeURI(char) !== encodeURIComponent(char)) {
arr.push({
character: char,
encodeURI: encodeURI(char),
encodeURIComponent: encodeURIComponent(char)
});
}
}
console.table(arr);
};
function encodeQueryParams (url, queryParams) {
return Object.keys(queryParams).reduce((a, key) => {
a.push(key + '=' + encodeURIComponent(queryParams[key]));
return a;
}, []).join('&');
}
let queryString = encodeQueryParams(queryParams);
url += url.indexOf('?') > -1 ? '&' : '?' + queryString;
/********************************************************************
* JQUERY COLOR CONTRAST
*********************************************************************/
jQuery.Color.fn.contrastColor = function() {
var r = this._rgba[0], g = this._rgba[1], b = this._rgba[2];
return (((r*299)+(g*587)+(b*144))/1000) >= 131.5 ? "black" : "white";
};
// usage examples:
jQuery.Color("#bada55").contrastColor(); // "black"
element.css( "color", jQuery.Color( element, "backgroundColor" ).contrastColor() );
@codenameyau
Copy link
Author

codenameyau commented Jan 10, 2017

Links and Resources

Introduction to File API and Drag and Drop
https://www.html5rocks.com/en/tutorials/file/dndfiles/

JavaScript setTimeout 0 for offloading heavy computation
https://stackoverflow.com/questions/779379/why-is-settimeoutfn-0-sometimes-useful

JavaScript fetch API with await, streams
https://jakearchibald.com/2015/thats-so-fetch/

Div that looks different on every browser
https://codepen.io/MartijnCuppens/pen/MXojmw

Fix htst certificate security warning by adding to Delete domain security policies
chrome://net-internals/#hsts

@codenameyau
Copy link
Author

codenameyau commented Mar 16, 2017

Console scripting

// Create bookmark and set this to URL to skip ads.
javascript:void(document.querySelector('video').currentTime = document.querySelector('video').duration)

// See global variables.
window
keys(window)
dir(window)

// Access React component.
window.FindReact = function(domElement) {
  for (var key in domElement) {
    if (key.startsWith("__reactInternalInstance$")) {
      var compInternals = domElement[key]._currentElement;
      var compWrapper = compInternals._owner;
      var comp = compWrapper._instance;
      return comp;
    }
  }
  return null;
};
// Access component state from render element.
$0.__reactInternalInstance$8d28m1ddneb._currentElement
$0.__reactInternalInstance$8d28m1ddneb._currentElement._owner._instance

@codenameyau
Copy link
Author

codenameyau commented May 16, 2017

Knex and Bookshel ORM

Raw queries with knex and bookshelf.

const knex = require('knex');
const bookshelf = require('bookshelf');
const chalk = require('chalk');

// Setup pooling and connections to Postgres DB
const postgres = exports.client = knex({
  client: 'pg',
  connection: {
    host: process.env.POSTGRES_DB_HOST,
    user: process.env.POSTGRES_DB_USER,
    password: process.env.POSTGRES_DB_PASSWORD,
    database: process.env.POSTGRES_DB_NAME,
    port: process.env.POSTGRES_DB_PORT,
    ssl: true
  },
  pool: {
    min: 0,
    max: 10
  }
});

// Used for applying query params to get requests.
const filterParams = exports.filterParams = (params, paramsList) => {
  const filteredParams = {};
  paramsList.forEach((param) => {
    if (params[param]) {
      filteredParams[param] = params[param];
    }
  });
  return filteredParams;
};

// Enable console output of SQL queries.
const headerMessage = chalk.dim.italic;
const infoMessage = chalk.yellow;

postgres.on('query', (query) => {
  console.log(
    headerMessage('\nQuery: '),
    infoMessage(`"${query.sql}"`),
    headerMessage('\nParams: '),
    infoMessage(`[${query.bindings}]\n`)
  );
});

const errorMessage = chalk.red;
const dbQueryError = exports.dbQueryError = (res, error) => {
  console.error(errorMessage(error));
  res.sendStatus(400);
};

// Setup ORM
const orm = exports.orm = bookshelf(postgres);

// Log connection details
const hostName = (process.env.POSTGRES_DB_HOST || '')
console.log(`[+] Connected to Postgres DB: ${hostName}`);

Knex raw queries

// controller.js
app.get('/api/search/datapoints/', (req, res, next) => {
  const query = `
    select dp.id, dp.name, dp.datasource_id, ds.name as datasource_name
    from ref.datapoint dp
    join ref.datasource ds on ds.id = dp.datasource_id
    where lower(dp.name) ilike (?)
  `;

  const params = [`%${req.query.q}%`];

  orm.knex.raw(query, params).then((result) => {
    res.json(result.rows);
  }).catch(requestError(next));
});

Knex transactions and many-to-many delete and insert with transaction, detach, attach.

const section_id = parseInt(req.params.sectionId, 10);
const timestamp = (new Date()).toJSON();

// Bulk delete all datapoints in the section and then add new datapoints.
orm.knex.transaction((transaction) => {
  return Section
    .forge({ id: section_id })
    .fetch()
    .then((section) => {
      return section.datapoints().detach(null, { transacting: transaction })
      .then(() => {
        return section;
      });
    })
    .then((section) => {
      const newDatapoints = req.body.map((datapoint) => {
        return {
          datapoint_id: datapoint.id,
          section_id: section_id,
          created_by: req.user.email,
          created_date: timestamp,
          ordinal_position: datapoint.ordinal_position
        };
      });
      return section.datapoints().attach(newDatapoints, { transacting: transaction });
    })
    .then((datapointToSectionList) => {
      return res.json(datapointToSectionList);
    });
});

Uploading and downloading excel spreadsheets via AJAX and multer.

const memory_storage = multer.memoryStorage();
const buffer_upload = multer({ storage: memory_storage });
const BATCH_CHUNK_SIZE = 20;

app.post('/api/datapoint/reservation', (req, res, next) => {
  const createSpreadsheet = (ids) => {
    let fields = ['ID', 'Name', 'Description'];

    let data = [fields];
    ids.forEach((datapointID) => {
      data.push([datapointID]); // Add more cols to this row.
    });

    return xlsx.build([{ name: 'custom datapoints', data: data }]);
  };

  const {datasource_id, count} = req.body;

  let batchRows = [];
  for (var i = 0; i < count; i++) {
    batchRows.push({
      datasource_id: datasource_id,
      reserved_by: req.user.email
    });
  }

  // You can also use postgres.batchInsert
  orm.knex.batchInsert('ref.datapoint', batchRows, BATCH_CHUNK_SIZE)
    .returning('id')
    .then((ids) => {
      const xlsxBuffer = createSpreadsheet(ids);
      res.attachment('dp-template.xlsx');
      res.set('Content-Type',
        'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
      res.send(xlsxBuffer);
    })
    .catch(requestError(next));
});

app.post('/api/datapoint/upload', buffer_upload.single('spreadsheet'), (req, res, next) => {
  const timestamp = (new Date()).toJSON();
  const spreadsheet = xlsx.parse(req.file.buffer);
  const templateWorksheet = spreadsheet[0];
  const templateRows = templateWorksheet.data;

  const dataRows = templateRows.slice(1);
  let updateRows = dataRows.map((row) => {
    return {
      id: row[0],
      name: row[1],
      description: row[2],
      data_type: row[3].toLowerCase(),
      display_type: row[4],
      created_by: req.user.email,
      created_date: timestamp,
      is_active: true,
      is_terminated: false
    };
  });

  async.each(updateRows, (row, callback) => {
    Datapoint.forge({ id: row.id })
      .save({ ...row })
      .then((datapoint) => {
        callback(null);
      });
  }, (error) => {
    if (error) { return res.status(500).send(error); }
    return res.sendStatus(200);
  });
});

@codenameyau
Copy link
Author

codenameyau commented May 17, 2017

Image Tool to resize canvas: https://gist.github.com/dcollien/312bce1270a5f511bf4a

var ImageTool = {};

var hasToBlobSupport = typeof HTMLCanvasElement !== "undefined"
  ? HTMLCanvasElement.prototype.toBlob : false;

ImageTool.resize = function(file, maxDimensions, callback) {
  if (typeof maxDimensions === 'function') {
    callback = maxDimensions;
    maxDimensions = {
      width: 600,
      height: 600
    };
  }

  var maxWidth = maxDimensions.width;
  var maxHeight = maxDimensions.height;
  var image = document.createElement('img');

  image.onload = function (imgEvt) {
    var width = image.width;
    var height = image.height;

    if (width > height && width > maxWidth) {
      height *= maxWidth / width;
      width = maxWidth;
    } else if (height > maxHeight) {
      width *= maxHeight / height;
      height = maxHeight;
    }

    var canvas = document.createElement('canvas');
    canvas.width = width;
    canvas.height = height;
    var ctx = canvas.getContext('2d');
    ctx.drawImage(image, 0, 0, width, height);

    if (hasToBlobSupport) {
      canvas.toBlob(function (blob) {
        return callback(blob, true);
      }, file.type);
    }
    else {
      var blob = ImageTool._toBlob(canvas, file.type);
      return callback(blob, true);
    }
  };

  ImageTool._loadImage(image, file);
  return true;
};

ImageTool._toBlob = function(canvas, type) {
  var dataURI = canvas.toDataURL(type);
  var dataURIParts = dataURI.split(',');
  var byteString;

  if (dataURIParts[0].indexOf('base64') >= 0) {
    // Convert base64 to raw binary data held in a string:
    byteString = atob(dataURIParts[1]);
  } else {
    // Convert base64/URLEncoded data component to raw binary data:
    byteString = decodeURIComponent(dataURIParts[1]);
  }

  var arrayBuffer = new ArrayBuffer(byteString.length);
  var intArray = new Uint8Array(arrayBuffer);

  for (var i = 0; i < byteString.length; i += 1) {
    intArray[i] = byteString.charCodeAt(i);
  }

  var mimeString = dataURIParts[0].split(':')[1].split(';')[0];
  var blob = null;

  blob = new Blob([intArray], { type: mimeString });
  return blob;
};

ImageTool._loadImage = function(image, file, callback) {
  if (typeof URL === 'undefined') {
    var reader = new FileReader();

    reader.onload = function (evt) {
      image.src = evt.target.result;
      if (callback) {
        return callback();
      }
    };

    reader.readAsDataURL(file);
  }

  else {
    image.src = URL.createObjectURL(file);
    if (callback) {
      return callback();
    }
  }
};

return ImageTool;

@codenameyau
Copy link
Author

Old Image Module and upload with AJAX. Broken.

/********************************************************************
* PREVIEW, RESIZE, AND UPLOAD IMAGE VIA AJAX 
*********************************************************************/

// https://hacks.mozilla.org/2011/01/how-to-develop-a-html5-image-uploader/
function previewImageUpload ($e) {
  var files = $e.target.files;
  if (files) {
    var imgContainerSelector = $e.target.getAttribute('data-img-preview-container');
    var imgPreviewObj = URL.createObjectURL(files[0]);
    var $imgContainer = $(imgContainerSelector);
    $imgContainer.html('<img class="img-upload-preview">');
    $imgContainer.find('img').attr('src', imgPreviewObj);
  }
}

// http://stackoverflow.com/a/5100158
function dataURItoBlob(dataURI) {
    // convert base64/URLEncoded data component to raw binary data held in a string
    var byteString;
    if (dataURI.split(',')[0].indexOf('base64') >= 0)
        byteString = atob(dataURI.split(',')[1]);
    else
        byteString = window.unescape(dataURI.split(',')[1]);

    // separate out the mime component
    var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];

    // write the bytes of the string to a typed array
    var ia = new Uint8Array(byteString.length);
    for (var i = 0; i < byteString.length; i++) {
        ia[i] = byteString.charCodeAt(i);
    }

    return new Blob([ia], {type:mimeString});
}

function resizeWidth(file, max_width, callback) {
  max_width = max_width || 600;

  // Read image from file system and resize.
  var reader = new FileReader();
  reader.onload = function(e) {
    var img = document.createElement('img');
    img.src = e.target.result;

    // Specify new width and height.
    var width = img.width;
    var height = img.height;
    if (width > max_width) {
      height = parseInt(height * (max_width / width), 10);
      width = max_width;
    }

    // Create canvas which performs the re-sizing.
    var canvas = document.createElement('canvas');
    canvas.width = width;
    canvas.height = height;
    canvas.style.visibility = 'hidden';
    var ctx = canvas.getContext('2d');
    ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
    var resizedFile = dataURItoBlob(canvas.toDataURL(file.type));
    callback(resizedFile);
  };
  reader.readAsDataURL(file);
}

function formatFilename(filename) {
  var extIndex = filename.lastIndexOf('.');
  var basename = filename.slice(0, extIndex).replace(/\s+/g, '-');
  var extension = filename.slice(extIndex);
  return basename + '-' + Date.now() + extension;
}
      
// Callback to ajax. It needs a special handler to upload images.
var $inputImage = $form.find('.input-image-upload');
var imageFile = $inputImage[0].files[0];
var formattedFilename = (imageFile === undefined) ?
  self.model.get('logo_filename') : this.formatFilename(imageFile.name);
  
resizeWidth(imageFile, 115, function(resizedFile) {
  var formData = new FormData();
  formData.append('input-image-upload', resizedFile, formattedFilename);

  $.ajax({
    url: '/api/brand-logo/',
    type: 'POST',
    data: formData,
    cache: false,
    processData: false,
    contentType: false,

    success: function(data, status) {
      console.log(data);
    }
  });
});

@codenameyau
Copy link
Author

/********************************************************************
* RAW MANY TO MANY FOR API
*********************************************************************/
var fake_data_points = [
  { id: 1, name: 'Can its beauty be refurbished?' },
  { id: 2, name: 'Offers online car cleaning?' },
  { id: 3, name: 'Has shopping cart?' }
];

var fake_projects = [
  { id: 1, name: 'Auto' },
  { id: 2, name: 'Beauty' }
];

var fake_data_points_to_project = [
  { id: 1, data_point_id: 1, project_id: 1 },
  { id: 2, data_point_id: 1, project_id: 2 },
  { id: 4, data_point_id: 2, project_id: 1 },
  { id: 5, data_point_id: 2, project_id: 2 }
];

exports.get_many_to_many = function(params) {
  var many_to_many_data = _.where(params.many_to_many, params.lookup);
  var related_table_index = _.indexBy(params.related_table, 'id');

  return _.map(many_to_many_data, function(object) {
    return related_table_index[object[params.related_id]];
  });
};

get_many_to_many({
  many_to_many: fake_data_points_to_project,
  table: fake_data_points,
  related_table: fake_data_points,
  lookup: {'data_point_id': 1},
  related_id: 'project_id'
});

@codenameyau
Copy link
Author

Assignments and reference

// Reference
a = { a1: {a2: 1} }

// Assign by reference.
b = a

// Assign property.
c = a.a1

// Delete pointer.
delete a.a1
console.log(a);
console.log(b);
console.log(c);

@codenameyau
Copy link
Author

codenameyau commented May 29, 2017

Redux React

React CSS

There are three main ways to use css with React. Alternatives:

Styled components

import styled, { css } from 'styled-components';

const CheckValidation = styled.div`
  background: ${({ isValid }) =>
      isValid ? `url(${CheckComplete})` : `url(${CheckIncomplete})`}
    0 center no-repeat;
`;

const CheckValidation = styled.div`
  background: url(${CheckIncomplete}) 0 center no-repeat;

  ${({ isValid }) => isValid && css`
    background-image: url(${CheckComplete});
  `}
`;

Define styles directly inside component

import React from 'react';

const Red = {
  color: 'white';
  backgroundColor: 'red';
};

export class Component extends React.Component {
  render() {
    return (
      <div style={Red}>This is white text on red background</div>
    );
  }
}

export default Component;

Use css loader and import styles

import React from 'react';
import '../styles/styles.css';

export class Component extends React.Component {
  render() {
    return (
      <div className="red">Hello World</div>
    );
  }
}

Use css modules

https://medium.com/nulogy/how-to-use-css-modules-with-create-react-app-9e44bec2b5c2

import React from 'react';
import styles from '../styles/styles.css';

export class Component extends React.Component {
  render() {
    return (
      <div className="styles.red">Hello World</div>
    );
  }
}

Redux Best Practices

You should avoid nesting in store. But here is how you update nested state.

/********************************************************************
* NESTED REDUCERS
*********************************************************************/
var x = {
  name: 'Sabbir',
  same: 'name',
  skillz: { awesomeness: false, dbStuffs: false }
};

var y = {
  name: 'Sabbeez', 
  skillz: { awesomeness: true }
};

// First extend flat props, then extended the nested skillz.
var z = {...x, ...y, skillz: {...x.skillz, ...y.skillz}};

console.log(z);

String replace with components (ex. highlight string): facebook/react#3386

_renderHighlightedName () {
  const { datapoint, highlight } = this.props;
  let datapointName = datapoint.name;
  let highlightI = highlight ? highlight.toLowerCase() : highlight;

  if (highlight && datapointName.toLowerCase().includes(highlightI)) {
    var regex = new RegExp(`(${highlightI})`, 'gi');
    var parts = datapointName.split(regex);

    for (var i = 1; i < parts.length; i += 2) {
      parts[i] = (
        <span key={`highlight-${i}`} className={classes['dp-name-highlight']}>
          {parts[i]}
        </span>
      );
    }

    datapointName = parts;
  }

  return <span className={classes['dp-item-name']}>{datapointName}</span>;
}

@codenameyau
Copy link
Author

codenameyau commented Sep 18, 2017

Tree rendering in React

const DatapointTree = (datapoints) => {
  // Extend each datapoint with these properties: ancestors, children
  // ancestors is array of ids to avoid circular object recursion.
  datapoints.forEach((datapoint) => {
    datapoint.tree = {
      ancestors: datapoint.parent_path.split('.').slice(1),
      children: {},
      children_list: []
    };
  });

  // Create a sorted array based the length of parents to build the tree.
  let sortedDatapoints = datapoints.sort((dpA, dpB) => {
    return dpA.tree.ancestors.length < dpB.tree.ancestors.length ? -1
         : dpA.tree.ancestors.length > dpB.tree.ancestors.length ? 1 : 0;
  });

  // Iterate through the sorted parents path to build tree.
  let tree = { children: {}, children_list: [] };
  sortedDatapoints.forEach((datapoint) => {
    let ancestors = datapoint.tree.ancestors;

    // This node has no ancestors so add it underneath the root node.
    if (!ancestors.length) {
      tree.children[datapoint.id] = datapoint;
      tree.children_list.push(datapoint);
    }

    // Traverse the tree to get the parent subtree, and add the current
    // datapoint as a new node under the parent subtree.
    else {
      let subtree = tree;
      ancestors.forEach((ancestorId) => {
        if (subtree.children[ancestorId]) {
          subtree = subtree.children[ancestorId].tree;
        }
      });
      subtree.children[datapoint.id] = datapoint;
      subtree.children_list.push(datapoint);
    }
  });

  return tree;
};

export default DatapointTree;

touchstart event

class PopoverContainer extends React.Component {
  constructor(props) {
    super(props);

    this.dropdownRef = this.dropdownRef.bind(this);
    this.handleClose = this.handleClose.bind(this);
    this.listeners = ['click', 'touchstart'];
  }
  componentDidMount() {
    this.listeners.forEach((eventType) => {
      document.addEventListener(eventType, this.handleClose, false);
    });
  }
  componentWillUnmount() {
    this.listeners.forEach((eventType) => {
      document.removeEventListener(eventType, this.handleClose, false);
    });
  }
  handleClose(e) {
    if (this.props.settingsDropdown && this.dropdown && !this.dropdown.contains(e.target)) {
      this.props.close();
    }
  }
  dropdownRef(ref) {
    this.dropdown = ref;
  }
  render() {
    return <Popover dropdownRef = { this.dropdownRef } { ...this.props } />
  }
}

@codenameyau
Copy link
Author

codenameyau commented Oct 19, 2017

Event Tracking

We use Google Tag Manager to manage our third-party integrations and trackers. To get started you should create a new Google Tag Manager account and ask for access to "Stash Invest" and "app.stashinvest.com".

Events are fired in our redux middleware in middleware/events.js. Make sure to publish your tag!!!

⚠️ ⚠️ ⚠️ Make sure to disable ad-blocker and Ghostery!!!!!!!! ⚠️ ⚠️ ⚠️

Triggers

In the Tag Manager UI, you can create a trigger to listen for events which you can fire with this code snippet.

const eventName = 'Transaction';
const eventProperties = {
  origin: window.location.pathname
};

window.dataLayer.push(eventName, eventProperties);

Tags

After you create a trigger, you can create a Tag. Choose Custom HTML and insert a code snippet.

Mixpanel Example

<script>
  window.mixpanel && window.mixpanel.track('Transaction', {{DataLayer properties}})
</script>
Marketing Pixel Example
<script>
  (function() {
    function uuidv4() {
      return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
        var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
        return v.toString(16);
      });
    }

    var uuid = uuidv4();
    var iframe = document.createElement("iframe");
    iframe.style.visibility = 'hidden';
    iframe.style.position = 'absolute';
    iframe.style.left = '-9000px';
    iframe.src = "https://example.com" + uuid;
    iframe.width = "1";
    iframe.height = "1";
    document.body.appendChild(iframe);
  })()
</script>

Google Pixel

<script>
  /* <![CDATA[ */
  var google_conversion_id = 0;
  var google_conversion_label = "";
  var google_remarketing_only = false;
  /* ]]> */

  (function() {
    var properties = {{DataLayer properties}};
    if (properties && properties.Action === 'On') {
      var s = document.createElement('script');
      s.async = true;
      s.src = '//www.googleadservices.com/pagead/conversion.js';
      var h = document.getElementsByTagName('head')[0];
      h.appendChild(s);
    }
  })();
</script>

Dynamic Script Example

<script> 
  (function() {
    var s = document.createElement('script');
    s.async = true;
    s.src = '//cdn.nanigans.com/NaN_tracker.js';
    var h = document.getElementsByTagName('head')[0];
    h.appendChild(s);
  })();
</script>

Publish

Click Publish and test your code locally. GTM allows you to push your changes to two "environments":

  • Live will push changes to production (app.stashinvest.com)
  • Latest will push changes to all other environments (Local, Edge and Staging)

Tips

  • ⚠️ Watch out for quotes. Make sure to use actual quotes instead of MS Word quotes.

@codenameyau
Copy link
Author

codenameyau commented Oct 20, 2017

Utils

const isMobileThunk = () => {
  let cachedValue;

  return () => {
    if (cachedValue === undefined) {
      let check = false;
      (function(a) {
        if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55\/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene|gf-5|g-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac( |-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|sdk\/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel(i|m)|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-/i.test(a.substr(0, 4))) check = true;
      })(navigator.userAgent || navigator.vendor || window.opera);
      cachedValue = check;
    }
    return cachedValue;
  };
}

export const isMobile = isMobileThunk();

export const isMacintosh = () => {
  return navigator.platform.indexOf('Mac') > -1
}

export const isWindows = () => {
  return navigator.platform.indexOf('Win') > -1
}

export const serialize = (queryParams) => {
  const params = [];
  for (let param in queryParams) {
    if (queryParams.hasOwnProperty(param)) {
      params.push(encodeURIComponent(param) + "=" + encodeURIComponent(queryParams[param]));
    }
  }
  return params.join("&");
}

export const pathOr = (defaultVal, paths, obj) => {
  let val = obj;
  let index = 0;
  while (index < paths.length) {
    if (val == null) {
      return defaultVal;
    }
    val = val[paths[index]]
    index++;
  }
  return val == null ? defaultVal : val;
}

Code highlights

const GLTR_ID = tickers['GLTR'] && tickers['GLTR'].card_id;
const VNQ_ID = tickers['VNQ'] && tickers['VNQ'].card_id;

const hasInvestmentInAnyAccount = (cardIds) => {
  return Object.values(accounts).some((account) => {
    return account.cards && cardIds.some((cardId) => {
      return account.cards[cardId];
    }); 
  })
}

hasInvestmentInAnyAccount([GLTR_ID, VNQ_ID])

Fetch redux thunk helper

middleware/request.js

export const encodeQuery = (query) => {
  return Object.keys(query).reduce((a, key) => {
    a.push(key + '=' + encodeURIComponent(query[key]));
    return a;
  }, []).join('&');
}

export const httpOptions = {
  method: 'GET',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json'
  }
};

export const request = async (dispatch, getState, config) => {
  const {actions, path, query, options} = config;

  // Parse query string options and add to url.
  const url = query ? '?' + encodeQuery(query) : path;

  // Stringify request body.
  options.body = options.body && JSON.stringify(options.body);

  const fetchOptions = {
    ...httpOptions,
    ...options,
  }

  actions.request && dispatch({
    type: actions.request
  });

  const res = await fetch(url, fetchOptions);

  if (res.status >= 400) {
    const json = await res.json();
    actions.error && dispatch({
      type: actions.error,
      status: res.status,
      body: json,
    })
  }

  const contentType = res.headers.get('Content-Type');

  if (contentType.indexOf('application/json') > -1) {
    const json = await res.json();
    actions.success && dispatch({
      type: actions.success,
      status: res.status,
      body: json
    });
  }

  if (contentType.indexOf('text/html') > -1) {
    await res.text();
    actions.success && dispatch({
      type: actions.success,
      status: res.status,
    });
  }
};

export default request;

middleware/index.js

/**
 * Read more about middleware here:
 * http://redux.js.org/docs/advanced/Middleware.html
 */

import { applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import thunk from './thunk';
import request from './request';

const middleware = [
  thunk.withExtraArgument(request),
];

if (process.env.NODE_ENV === 'development') {
  const { logger } = require('redux-logger');
  middleware.push(logger);
}

export default composeWithDevTools(applyMiddleware(...middleware));
export const PATCH_EXPERIMENT_REQUEST = 'PATCH_EXPERIMENT_REQUEST';
export const PATCH_EXPERIMENT_SUCCESS = 'PATCH_EXPERIMENT_SUCCESS';
export const PATCH_EXPERIMENT_ERROR = 'PATCH_EXPERIMENT_ERROR';

export const patchExperiment = ({ version, active, experiment_urls }) => {
  return (dispatch, getState, request) => {
    return request(dispatch, getState, {
      actions: {
        request: PATCH_EXPERIMENT_REQUEST,
        success: PATCH_EXPERIMENT_SUCCESS,
        error: PATCH_EXPERIMENT_ERROR
      },
      path: `/api/experiment/${version}`,
      options: {
        method: 'PATCH',
        body: {
          active,
          experiment_urls
        }
      }
    });
  }
}

@codenameyau
Copy link
Author

codenameyau commented Jan 26, 2018

@codenameyau
Copy link
Author

codenameyau commented Jun 20, 2018

Tooling and Build

Compiling ES6 with Babel CLI

# Babel CLI should be installed locally rather than globally.
npm install --save-dev babel-cli

# Compile or watch a particular file.
babel public/scripts/snake.js -o public/scripts/snake-es5.js
babel public/scripts/snake.js -o public/scripts/snake-es5.js --watch

@codenameyau
Copy link
Author

White-transparent gradient

const Wrapper = styled.div`
  width: 100%;
  position: relative;

  @media (max-width: 768px) {
    &::before {
      content: '';
      background: linear-gradient(to bottom, white, transparent);
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 50px;
      z-index: 1;
      margin-top: -25px;
    }
  }
`;

@codenameyau
Copy link
Author

codenameyau commented Jul 31, 2019

VSCode debugger with CRA tests

Sell bots
https://botbroker.io/bots

{
  // Use IntelliSense to learn about possible attributes.
  // Hover to view descriptions of existing attributes.
  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Debug CRA Tests",
      "type": "node",
      "request": "launch",
      "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/react-scripts",
      "args": ["test", "${relativeFile}", "--no-cache", "--modulePaths=src"],
      "env": { "CI": "true" },
      "cwd": "${workspaceRoot}",
      "protocol": "inspector",
      "console": "integratedTerminal",
      "internalConsoleOptions": "neverOpen"
  }
  ]
}

Mocking modules

jest.mock('stash/utils/experiments.js', function() {
  return {
    startExperiment: jest.fn(res => {
      return 0;
    }),
  };
});
import * as utils from 'stash/utils';
utils.isMobile = jest.fn().mockReturnValue(true);

@codenameyau
Copy link
Author

Opening chrome with CORS disabled

open -n -a /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --args --user-data-dir="/tmp/chrome_dev_test" --disable-web-security

@codenameyau
Copy link
Author

codenameyau commented Oct 23, 2020

Mongo DB data migration

Binary

# Import database
mongorestore -h ds141168.mlab.com:41168 -d heroku_nl79h610 -u <user> -p <password> <input db directory>

# Export database
mongodump -h ds141168.mlab.com:41168 -d heroku_nl79h610 -u <user> -p <password> -o <output directory>

# Import collection
mongorestore -h ds141168.mlab.com:41168 -d heroku_nl79h610 -u <user> -p <password> <input .bson file>
# Export collection
mongodump -h ds141168.mlab.com:41168 -d heroku_nl79h610 -c <collection> -u <user> -p <password> -o <output directory>

JSON

# Import collection
mongoimport -h ds141168.mlab.com:41168 -d heroku_nl79h610 -c <collection> -u <user> -p <password> --file <input file>

# Export collection
mongoexport -h ds141168.mlab.com:41168 -d heroku_nl79h610 -c <collection> -u <user> -p <password> -o <output file>

CSV

# Import collection
mongoimport -h ds141168.mlab.com:41168 -d heroku_nl79h610 -c <collection> -u <user> -p <password> --file <input .csv file> --type csv --headerline

# Export collection
mongoexport -h ds141168.mlab.com:41168 -d heroku_nl79h610 -c <collection> -u <user> -p <password> -o <output .csv file> --csv -f <comma-separated list of field names>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment