
vs.


+

Hot Module Replacement
server.js
var webpack = require('webpack');
var WebpackDevServer = require('webpack-dev-server');
var config = require('./webpack.config');
new WebpackDevServer(webpack(config), {
publicPath: config.output.publicPath,
hot: true,
historyApiFallback: true
}).listen(3000, 'localhost', function (err, result) {
if (err) {
return console.log(err);
}
console.log('Listening at http://localhost:3000/');
});
Hot Module Replacement
$base_path = 'ui/dist/master.js';
$script_path = plugins_url( $base_path, __FILE__ );
if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
$script_path = apply_filters( 'wcmia_js_dev_path', $script_path, $base_path );
}
wp_register_script( 'wcmia-plugin-ui', $script_path, $dependencies, $version, true );
mu-plugins/hmr.php
add_filter( 'wcmia_js_dev_path', function( $script_path, $base_path ) {
return 'http://localhost:3000/' . $base_path;
}, 10, 2 );
Data Persistence
Data Persistence: Ajax
const App = () => {
const updateDatabase = () => {
console.log('Updating database');
api.put(store.getState().manager);
};
return (
<Provider store={store}>
<MenuManager
updateDatabase={updateDatabase} />
</Provider>
);
};
handleSaveClick() {
this.props.dayMenuUpdated({
date: this.props.date.iso,
content: {
exceptions: { $set: this.state.exceptions },
menu: { $set: this.state.content },
},
});
this.setState({ editing: false });
this.props.updateDatabase();
}
import request from 'superagent';
import * as wpPublish from './dom/wp-publish';
export const put = (managerData = {}) => {
wpPublish.disable();
return request
.post(managerData.api.url)
.set('Content-Type', 'application/json')
.set('X-WP-Nonce', managerData.api.nonce)
.send(JSON.stringify(managerData))
.end((err) => {
wpPublish.enable();
if (err) {
console.error(`There was an error saving menu data: ${err}`);
} else {
console.log('Successfully updated menu manager data in the database');
}
});
};
class Menu_Controller extends Menu_Management_REST_Controller {
private $base = '/menu';
public function register_routes() {
register_rest_route( $this->get_namespace(), $this->base . '/(?P<id>\d+)', [
[
'methods' => \WP_REST_Server::READABLE,
'callback' => [ $this, 'get_item' ],
// and other args
],
[
'methods' => \WP_REST_Server::EDITABLE,
'callback' => [ $this, 'update_item' ],
// and other args
],
] );
}
public function get_item( \WP_REST_Request $request ) {
$post_id = $request->get_param( 'id' );
$calendar = new Calendar_Data( $post_id );
return rest_ensure_response( $calendar->get_data() );
}
public function update_item( \WP_REST_Request $request ) {
$post_id = $request->get_param( 'id' );
$updater = new Calendar_Updater( $post_id, $request->get_json_params() );
$updater->apply_update();
return $this->get_item( $request );
}
}
<input type="hidden" />
<input type="hidden" />
runDataHeartbeat() {
const dataInput = this.dataInput;
this.heartbeat = setInterval(() => {
const panels = cloneDeep(this.props.panels);
const newData = JSON.stringify({ panels });
dataInput.value = newData;
}, 1000);
}
Autosave WP Globals
const wp = window.wp || {};
export const wpHeartbeat = wp.heartbeat || null;
export const wpAutosave = wp.autosave || null;
Autosave Hooks
const bindEvents = () => {
$(document).on(`before-autosave.${settings.namespace}`, (e, postdata) => autosaveDrafts(e, postdata));
$(document).on(`after-autosave.${settings.namespace}`, (e, data) => handleAutosaveSuccess(e, data));
};
const autosaveDrafts = (e, postdata) => {
postdata.post_content_filtered = MODULAR_CONTENT.autosave;
};
Trigger Autosave
export const triggerAutosave = () => {
if (!wpAutosave) {
return;
}
const timestamp = new Date().getTime();
MODULAR_CONTENT.needs_save = false;
titleText = title.value;
title.value = `${titleText}${timestamp}`;
triggeredSave = true;
wpAutosave.server.triggerSave();
title.value = titleText;
};
wp_localize_script()
public function enqueue_resources() {
if ( $this->is_menu_post_admin() ) {
wp_enqueue_script( 'menu-manager-admin-ui', $this->script_url(), [ ], tribe_get_version(), true );
wp_localize_script( 'menu-manager-admin-ui', 'TribeMenuManager', $this->get_script_data() );
wp_localize_script( 'menu-manager-admin-ui', 'TribeMenuManageri18n', $this->get_i18n_data() );
}
}
private function get_script_data() {
$data = new Calendar_Data( $GLOBALS[ 'post' ]->ID );
$data = $data->get_data();
$data[ 'api' ] = [
'url' => Menu_Controller::get_url( $GLOBALS[ 'post' ]->ID ),
'nonce' => wp_create_nonce( 'wp_rest' ),
];
return $data;
}
<script>
var ExampleData = <?php echo json_encode( $meta_box_data ); ?>;
<?php do_action( 'example_metabox_js_init' ); ?>
</script>
Media WP Globals
wp_register_script(
'example-admin-ui',
$script_url,
[ 'wp-util', 'media-upload', 'media-views' ],
$version,
true
);
const wp = window.wp || {};
export const wpMedia = wp.media || null;
Media Frames
const frame = wpMedia({
multiple: false,
library: {
type: 'image',
},
});
frame.on('select', () => {
const attachment = frame.state().get('selection').first().toJSON();
// do something with the attachment object
});
frame.open();
Image Galleries
selectImages() {
const ids = _.map(this.state.gallery, attachment => attachment.id);
// Set frame object:
this.frame = wpMedia({
frame: 'post',
state: 'gallery-edit',
title: 'Gallery',
editing: true,
multiple: true,
selection: this.buildSelection(ids),
});
this.frame.open();
// but wait, there's more!
}
Image Galleries
buildSelection(attachmentIds) {
const shortcode = new WPShortcode({
tag: 'gallery',
attrs: { ids: attachmentIds.join(',') },
type: 'single',
});
const attachments = wpMedia.gallery.attachments(shortcode);
const selection = new wpMedia.model.Selection(attachments.models, {
multiple: true, props: attachments.props.toJSON(),
});
selection.gallery = attachments.gallery;
selection.more().done(() => {
selection.props.set({ query: false });
selection.unmirror();
selection.props.unset('orderby');
});
return selection;
}
Image Galleries
selectImages() {
// the aforementioned code to open the frame, then...
const GallerySidebarHider = {};
_.extend(GallerySidebarHider, panelBackbone.Events);
GallerySidebarHider.hideGallerySidebar = this.hideGallerySidebar;
GallerySidebarHider.listenTo(
this.frame.state('gallery-edit'),
'activate',
GallerySidebarHider.hideGallerySidebar
);
GallerySidebarHider.hideGallerySidebar();
this.frame.on('toolbar:render:gallery-edit', this.overrideGalleryInsert);
this.overrideGalleryInsert();
}
Image Galleries
hideGallerySidebar() {
if (this.frame) {
// Hide Gallery Settings in sidebar
this.frame.content.get('view').sidebar.unset('gallery');
}
}
Image Galleries
this.frame.on('toolbar:render:gallery-edit', this.overrideGalleryInsert);
this.overrideGalleryInsert();
overrideGalleryInsert() {
this.frame.toolbar.get('view').set({
insert: {
style: 'primary',
text: 'Save Gallery', // Change the "Insert Gallery" text
click: this.handleFrameInsertClick, // Set our handler
},
});
}
Image Galleries
handleFrameInsertClick() {
const models = this.frame.state().get('library');
const gallery = models.map((attachment) => {
const att = attachment.toJSON();
return {
id: att.id,
thumbnail: att.sizes.thumbnail.url,
};
});
this.setState({ gallery });
this.frame.close();
this.frame = null;
}
wp_editor()
- Settings
ob_start();
wp_editor(
'',
$editor_id,
$settings
);
ob_get_clean();
return $editor_id;
wp_editor()
- Media Buttons
wp_print_styles('editor-buttons');
ob_start();
do_action( 'media_buttons', '%EDITOR_ID%' );
$html = ob_get_clean();
wp_editor()
- Media Buttons
const getMediaButtons = () => {
return props.buttons ? (
<div
id={`wp-${props.fid}-media-buttons`}
className="wp-media-buttons"
dangerouslySetInnerHTML={{ __html: mediaButtonsHTML.replace('%EDITOR_ID%', props.fid) }}
/>
) : null;
};
wp_editor()
- Render
<div className="wp-editor-tabs">
<button
type="button"
id={`${props.fid}-tmce`}
className="wp-switch-editor switch-tmce"
data-wp-editor-id={props.fid}
>
{props.strings['tab.visual']}
</button>
<button
type="button"
id={`${props.fid}-html`}
className="wp-switch-editor switch-html"
data-wp-editor-id={props.fid}
>
{props.strings['tab.text']}
</button>
</div>
wp_editor()
- Render
<div
data-settings_id={props.fid}
id={`wp-${props.fid}-editor-container`}
className="wp-editor-container"
>
<div
data-settings_id={props.fid}
id={`qt_${props.fid}_toolbar`}
className="quicktags-toolbar"
/>
<textarea
className={`wysiwyg-${props.fid} wp-editor-area`}
rows="15"
cols="40"
value={props.data}
name={props.name}
id={props.fid}
onChange={props.onUpdate}
/>
</div>
wp_editor()
- Initialize TinyMCE
componentDidMount() {
// delay for smooth animations
_.delay(() => {
this.initTinyMCE();
}, 100);
}
wp_editor()
- TinyMCE Globals
export const tinyMCE = window.tinyMCE || null;
export const tinyMCEPreInit = window.tinyMCEPreInit || null;
export const switchEditors = window.switchEditors || null;
export const quicktags = window.quicktags || null;
export const QTags = window.QTags || null;
wp_editor()
- Initialize TinyMCE
let settings = tinyMCEPreInit.mceInit[options.editor_settings];
const qtSettings = {
id: options.fid,
buttons: tinyMCEPreInit.qtInit[options.editor_settings].buttons,
};
settings.selector = `#${options.fid}`;
settings = tinyMCE.extend({}, tinyMCEPreInit.ref, settings);
tinyMCEPreInit.mceInit[options.fid] = settings;
tinyMCEPreInit.qtInit[options.fid] = qtSettings;
wp_editor()
- Initialize TinyMCE
quicktags(tinyMCEPreInit.qtInit[options.fid]);
QTags._buttonsInit();
if (!window.wpActiveEditor) {
window.wpActiveEditor = options.fid;
}
options.editor.addEventListener('click', () => {
window.wpActiveEditor = options.fid;
});
if (options.editor.classList.contains('tmce-active')) {
_.delay(() => {
switchEditors.go(options.fid, 'tmce');
}, 100);
}