| CARVIEW |
z <substring of folder name> instead of cd relative/or/full/path/to/folder.
Typing Long Paths is Tedious
There’s lots of times per day where I have to switch between various folders in the terminal, and they’re often deep inside different trees with very long paths.
For instance, if I’m currently in ~/vhosts/localhost/wordcamp.test/public_html/wp-content/mu-plugins/blocks/source/blocks/schedule, it’s a pain to type cd ../../../../plugins/gutenberg (including manually counting the number of folders to jump back). I could type the full path to avoid that counting, but that would also be very long. Tab completion helps a bit, but not nearly enough.
z Makes it Easy
z makes that it so that I can just type z camp gut, or z camp sched, or any substring or regex that matches the unique path.
It Also Supports Tab Completion
It also supports tab completion to disambiguate between folders with the same name within different trees. Say I have ~/vhosts/localhost/wordcamp.test/public_html/wp-content/plugins/gutenberg and ~/vhosts/localhost/wp-develop.test/public_html/src/wp-content/plugins/gutenberg; if I type z gut, I’ll get this output:
> z gut
gutenberg/ gutenberg/
> z /Users/iandunn/vhosts/localhost/w
The second line is a new z command with the path for the 2 matches, up until the point where the paths diverge. If I then type p<tab><enter>, it’ll take me to the gutenberg folder within wp-develop.test, instead of the one inside wordcamp.test. If I type c<tab><enter>, then it’d take me to the one inside wordcamp.test.
Here it is in case it’s useful to anyone else.
/**
* Get a list of ISO-3166-2 countries by continent.
*
* Data was sourced from https://dev.maxmind.com/geoip/legacy/codes/country_continent/ on 2020-04-17.
*
* @param string $continent
*
* @return array
*/
function get_iso_3166_2_country_codes( $continent = '' ) {
$codes = array(
'antarctica' => array( 'AQ', 'BV', 'GS', 'HM', 'TF' ),
'africa' => array(
'AO', 'BF', 'BI', 'BJ', 'BW', 'CD', 'CF', 'CG', 'CI', 'CM', 'CV', 'DJ', 'DZ', 'EG', 'EH', 'ER', 'ET',
'GA', 'GH', 'GM', 'GN', 'GQ', 'GW', 'KE', 'KM', 'LR', 'LS', 'LY', 'MA', 'MG', 'ML', 'MR', 'MU', 'MW',
'MZ', 'NA', 'NE', 'NG', 'RE', 'RW', 'SC', 'SD', 'SH', 'SL', 'SN', 'SO', 'ST', 'SZ', 'TD', 'TG', 'TN',
'TZ', 'UG', 'YT', 'ZA', 'ZM', 'ZW',
),
'asia' => array(
// Includes the Middle East, Armenia, Azerbaijan, Cyprus, Georgia.
'AE', 'AF', 'AM', 'AP', 'AZ', 'BD', 'BH', 'BN', 'BT', 'CC', 'CN', 'CX', 'CY', 'GE', 'HK', 'ID', 'IL',
'IN', 'IO', 'IQ', 'IR', 'JO', 'JP', 'KG', 'KH', 'KP', 'KR', 'KW', 'KZ', 'LA', 'LB', 'LK', 'MM', 'MN',
'MO', 'MV', 'MY', 'NP', 'OM', 'PH', 'PK', 'PS', 'QA', 'SA', 'SG', 'SY', 'TH', 'TJ', 'TL', 'TM', 'TW',
'UZ', 'VN', 'YE',
),
'europe' => array(
// Includes Russia, Turkey.
'AD', 'AL', 'AT', 'AX', 'BA', 'BE', 'BG', 'BY', 'CH', 'CZ', 'DE', 'DK', 'EE', 'ES', 'EU', 'FI', 'FO',
'FR', 'FX', 'GB', 'GG', 'GI', 'GR', 'HR', 'HU', 'IE', 'IM', 'IS', 'IT', 'JE', 'LI', 'LT', 'LU', 'LV',
'MC', 'MD', 'ME', 'MK', 'MT', 'NL', 'NO', 'PL', 'PT', 'RO', 'RS', 'RU', 'SE', 'SI', 'SJ', 'SK', 'SM',
'TR', 'UA', 'VA',
),
'north america' => array(
'AG', 'AI', 'AN', 'AW', 'BB', 'BL', 'BM', 'BS', 'BZ', 'CA', 'CR', 'CU', 'DM', 'DO', 'GD', 'GL', 'GP',
'GT', 'HN', 'HT', 'JM', 'KN', 'KY', 'LC', 'MF', 'MQ', 'MS', 'MX', 'NI', 'PA', 'PM', 'PR', 'SV', 'TC',
'TT', 'US', 'VC', 'VG', 'VI',
),
'oceania' => array(
'AS', 'AU', 'CK', 'FJ', 'FM', 'GU', 'KI', 'MH', 'MP', 'NC', 'NF', 'NR', 'NU', 'NZ', 'PF', 'PG', 'PN',
'PW', 'SB', 'TK', 'TO', 'TV', 'UM', 'VU', 'WF', 'WS',
),
'south america' => array(
'AR', 'BO', 'BR', 'CL', 'CO', 'EC', 'FK', 'GF', 'GY', 'PE', 'PY', 'SR', 'UY', 'VE',
),
);
if ( $continent ) {
return $codes[ $continent ];
} else {
return $codes;
}
}]]>
When I’m trying to learn a really deep and complicated topic — something that takes days or weeks to grasp the basics, and years to master — one thing that I find really helpful is the concept of the progressive disclosure of complexity.
A system designed with that in mind lets you make tangible progress with the least amount of time, effort, and friction. And then you’re gradually introduced to more complexity as you have a need for it.
Morten Rand-Hendriksen did a great job applying that concept to building custom Gutenberg blocks in his presentation at WordCamp Vancouver 2019. The video isn’t on WordPress.tv yet, but there’s a GitHub repository with the code examples.
The 01_01 branch is the simplest version of a custom block that you could possible build, and only contains the barest, most essential things. It probably wouldn’t be convenient to build blocks that way, but understanding what’s essential is, well… essential to the process of learning :)
Once you grasp that, then you can move on to the next thing, whether that’s how to add a certain feature, or make your workflow more convenient with a build tool, etc.
The slides are also available:
I often struggle in meetings and other synchronous conversations to form and articulate my thoughts quickly enough to express them coherently before the conversation has moved on.
That’s one of the many reasons that I love distributed work so much; it gives me the time and space that I need to really think through and process a question before giving an answer.
That’s not always possible though, so sometimes I end up feeling like a decision was made without really wrestling with all of the questions, or the nuance of something, because I wasn’t able to bring it to the forefront.
I was reminded of that last night, while listening to an episode of Hurry Slowly. The host was interviewing Mira Jacob, and she said something that I think is really helpful:
I try to tell myself, ‘If you have a funny feeling about it, it’s probably for a reason, so sit with it for a moment before you make any moves. You’re allowed to sit with this, you’re allowed to take your time. You’re allowed to figure out what feels funny, and why.’
Mira Jacob, Timestamp 49:48
It ensures that everyone’s concerns are heard, and strives to arrive at a win-win decision, rather than some creating a situation where some people win and others lose, or arriving at a compromise where everyone is unhappy.
]]>There don’t seem to be a lot of examples of using it outside of a service worker context, though, or with the much cleaner await operator, so I put one together and added it to MDN’s docs.
Minimal Example
// Try to get data from the cache, but fall back to fetching it live.
async function getData() {
const cacheVersion = 1;
const cacheName = `myapp-${ cacheVersion }`;
const url = 'https://jsonplaceholder.typicode.com/todos/1';
let cachedData = await getCachedData( cacheName, url );
if ( cachedData ) {
console.log( 'Retrieved cached data' );
return cachedData;
}
console.log( 'Fetching fresh data' );
const cacheStorage = await caches.open( cacheName );
await cacheStorage.add( url );
cachedData = await getCachedData( cacheName, url );
await deleteOldCaches( cacheName );
return cachedData;
}
// Get data from the cache.
async function getCachedData( cacheName, url ) {
const cacheStorage = await caches.open( cacheName );
const cachedResponse = await cacheStorage.match( url );
if ( ! cachedResponse || ! cachedResponse.ok ) {
return false;
}
return await cachedResponse.json();
}
// Delete any old caches to respect user's disk space.
async function deleteOldCaches( currentCache ) {
const keys = await caches.keys();
for ( const key of keys ) {
const isOurCache = 'myapp-' === key.substr( 0, 6 );
if ( currentCache === key || ! isOurCache ) {
continue;
}
caches.delete( key );
}
}
try {
const data = await getData();
console.log( { data } );
} catch ( error ) {
console.error( { error } );
}
Real World Example
For a bit more involved example, check out how Quick Navigation Interface fetches and caches the user’s content index.
That’s in a WordPress plugin, so it uses apiFetch() to automatically get a fetch() polyfill and add nonces to the XHR request. That has the side-effect of needing to use Cache.put() instead of the simpler Cache.add() from the example above.
Chrome and HTTP
CacheStorage is related to the service worker spec, but can be used independently. Because service workers require HTTPS, though, Chrome and Safari also require HTTPS to use CacheStorage. You can use it without HTTPS in Firefox, though.
Composition and Context are common ways to avoid prop drilling in React apps. Often times people argue for one or the other as an exclusive solution, but I think a more nuanced view is that they should both be understood, and used where they fit best. Composition works better for some things, and Context for others.
Where Composition Fits Best
In one app I’m working on, what seems to work best is using composition for components that are lower in the tree, and Context for ones that are higher. The simple, lower-level components that are just rendering small bits of UI based on props — e.g., ActiveUrlPreview and SearchResults
— tend to be more generic and reusable, and it makes sense to pass their data in as props, so that they can remain uncoupled from the global state.
Composition also makes sense when you want to have specialized versions of generic components, like with Warning and FetchErrorWarning.
Where Context Fits Best
Composition doesn’t seem to make sense, though, for higher-level components that are essentially controller-views — stitching together generic components like Modal and SearchResults. For example, a controller-view like Loaded could be pulled up into it’s parent MainView, which is also a high-level controller-view. That would avoid an extra level in the hierarchy, but it would also result in MainView being very cluttered, and doing too many things. Keeping all of the “loaded” state UI encapsulated inside Loaded makes MainView much easier to understand at a glance.
By their nature of being higher-level components, they’re inherently more aware of the app’s design and business logic, and are less likely to be reused, so I don’t mind coupling them to the global state.
In a larger application, though, it would probably still be best to have separate contexts for separate parts of the app, and only keep the truly universal data in the top-level context.
Results
Using Context and composition in this way removed the prop drilling from my app, while keeping the higher-level components simple, and the lower-level components reusable.
If anyone sees a better way, though, I’d be curious to hear your thoughts.
Side note: Hooks make working with Context so much easier. If you’ve tried using Context in the past and were turned off by render props and all that mess, you might be pleasantly surprised if you give it another shot.
Another side note: Over-using destructuring makes prop drilling even worse, because any time something changes you have to edit it in 2n places instead of just n. Sometimes it’s better to just reference props.foo directly.
wp.customize.Messenger() to send postMessage data to or from the iframe. See below for a minimal example.
Note: This isn’t about previewing custom theme options.
The WordPress Customizer is divided into two panes: the “Controls” pane where you configure options, and the “Preview” pane where you get a real-time preview of the changes you made in the Controls pane. The Preview pane is inside an iframe, so if you want to send custom data (“messages”) back and forth between them, you need to use the postMessage API.
The Customizer is already doing that for previewing theme options, but there are lots of other use cases for sending messages between the frames, like listening for keyboard events in the Preview pane, and then responding to them in the Controls pane.
Writing your own postMessage send/receive functions is fairly straightforward, but there are some security considerations to take into account, and the Customizer API already provides the wp.customize.Messenger class that can do all of that for you, so it’s best to use that, rather than rolling your own solution.
Minimal Example
Save this as wp-content/mu-plugins/post-message-example.php, then browse to the Customizer and open the dev tools console. Then, click inside the Preview frame, and start pressing keys. You should see the events being logged to the console.
<?php
// Print JavaScript for the Preview pane
add_action( 'customize_preview_init', function() {
add_action( 'wp_print_scripts', function() {
?>
<script>
// Wait until `wp.customize.settings`, etc is available, and then listen for keyboard events.
document.addEventListener( 'DOMContentLoaded', () => {
window.addEventListener( 'keyup', sendMessage );
} );
/**
* Send a `postMessage` to the Controls pane.
*
* @param {object} event
*/
const sendMessage = event => {
const messenger = new wp.customize.Messenger( {
channel : 'my-plugin-slug',
targetWindow : window.parent,
url : wp.customize.settings.url.allowed[ 0 ],
} );
// Note that you can't send `event` itself, since its properties aren't enumerable.
// @see https://stackoverflow.com/q/48054951/450127
const message = { key : event.which };
messenger.send( 'my-message-id', message );
}
</script>
<?php
} );
} );
// Print JavaScript for the Controls pane
add_action( 'customize_controls_print_scripts', function() {
?>
<script>
// Wait until `wp.customize.settings` is available and the `iframe` has been added to the DOM.
document.addEventListener( 'DOMContentLoaded', () => {
wp.customize.bind( 'ready', () => {
wp.customize.previewer.bind( 'ready', receiveMessage );
} );
} );
/**
* Listen for messages from the Previewer frame and handle them.
*/
const receiveMessage = () => {
const iframe = document.getElementById( 'customize-preview' ).getElementsByTagName( 'iframe' )[0];
const messenger = new wp.customize.Messenger( {
channel : 'my-plugin-slug',
targetWindow : iframe.contentWindow,
url : wp.customize.settings.url.allowed[ 0 ],
} );
// Handle messages that are received.
messenger.bind( 'my-message-id', event => {
console.log( 'Received message from Previewer iframe:', event );
} );
}
</script>
<?php
} );
Real World Example
If you want to see a real-world example, you can look at how Quick Navigation Interface sends and receives messages.
]]>