MODx (missing) preview of unsaved changes
<?php
/**
* Plugin: Previewer
* Delivers a preview of the current input.
* It uses the basic saving-process, but the old data is saved into an cachefile and resaved after the preview was regenerated.
*/
$event = $modx->event->name;
// Set cache options
$cacheOptions = array(
xPDO::OPT_CACHE_KEY => '',
xPDO::OPT_CACHE_HANDLER => 'xPDOFileCache',
xPDO::OPT_CACHE_EXPIRES => 0,
);
switch($event) {
case 'OnDocFormRender':
$parents = array_reverse($modx->getParentIds($id, 10, array('context' => 'web')));
if(in_array(33, $parents)) {
$js =<<<JS
<script type="text/javascript">
Ext.onReady(function() {
Ext.getCmp('modx-abtn-preview').getEl().hide();
})
</script>
JS;
$modx->regClientStartupHTMLBlock($js);
return;
}
// Only when resource exists already!
$has_duplicate = false;
if($mode == 'upd') {
$prevUrl = $modx->makeUrl($id, 'web', array('is_preview' => 1), 'full');
$allParents = json_encode($parents);
$_POST['id'] = $prev;
$modx_version = $modx->getVersionData();
$update_processor = $modx_version['major_version'] > 2 ? 'resource/update' : 'update';
$js =<<<JS
<script type="text/javascript">
var Previewr = function(config) {
config = config || {};
Previewr.superclass.constructor.call(this,config);
};
Ext.extend(Previewr, Ext.Component, {
page:{},window:{},grid:{},tree:{},panel:{},combo:{},config: {}
});
var Previewr = new Previewr();
Previewr.config = {
prev_url: '$prevUrl',
all_parents: '$allParents',
update_processor: '$update_processor'
}
</script>
JS;
$modx->regClientStartupHTMLBlock($js);
// Change this to the path to the JS-file in your installation
$modx->regClientStartupScript($modx->getOption('assets_url'). 'js/plugins/previewr.plugin.js');
}
$btn_js =<<<BTN_JS
<script type="text/javascript">
Ext.onReady(function () {
window.setInterval(function() {
// Always enable the save button
if(!Ext.getCmp('modx-abtn-save'))
return;
Ext.getCmp('modx-abtn-save').enable().setDisabled(false);
}, 2000);
});
</script>
BTN_JS;
$modx->regClientStartupHTMLBlock($btn_js);
break;
case 'OnBeforeDocFormSave':
// Check if it's a preview-process
if($_POST['preview_check'] != 1)
return;
// Get the resource
$resource = $modx->getObject('modResource', $id);
if(!$resource)
return;
// Add resource to the currently previewed resources
$current_res = $modx->cacheManager->get('preview/current_resources', $cacheOptions);
$current_res['resources'][] = $id;
$modx->cacheManager->set('preview/current_resources', $current_res, 60, $cacheOptions);
// Get all resource-data
$temp['resource'] = $resource->toArray();
// Get all TV-data
if ($tvs = $resource->getMany('TemplateVars', 'all')) {
foreach ($tvs as $tv) {
$temp_tvs[] = $tv->toArray();
}
}
$temp['tvs'] = $temp_tvs;
// Get all resource-groups
$groups = $resource->getMany('ResourceGroupResources');
foreach($groups as $name => $grpObject) {
$temp_groups[] = $grpObject->toArray();
}
$temp['groups'] = $temp_groups;
// Write all data (resource,tv,groups) to a cachefile
$modx->cacheManager->set('preview/'.$id.'.temp', $temp, 0, $cacheOptions);
// Log preview-process
$modx->logManagerAction('resource_preview', 'modResource', $id);
return;
break;
case 'OnDocFormSave':
// Exit if it's a preview process
if($_POST['preview_check'] == 1)
return;
function generateRandomString($length = 10) {
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$randomString = '';
for ($i = 0; $i < $length; $i++) {
$randomString .= $characters[rand(0, strlen($characters) - 1)];
}
return $randomString;
}
if($id) {
$url = $modx->makeUrl($id, 'web', null, 'full');
//use parameter 'nc' to prevent retrieving a cached resource
$opts = array(
'http'=>array(
'method'=>"POST",
'content'=>http_build_query(array('t'=> generateRandomString())),
'timeout' => 0.1
)
);
$context = stream_context_create($opts);
$fp = @fopen($url, 'r', false, $context);
}
return;
break;
case 'OnWebPageComplete':
// Return if it is not a preview-process
if(!$_GET['is_preview'])
return;
// Get old resource cache
$data = $modx->cacheManager->get('preview/'.$modx->resource->get('id').'.temp', $cacheOptions);
// Remove cache file
$modx->cacheManager->clearCache(array('preview/'), array('objects' => null, 'extensions' => array('.temp.cache.php')));
// Set resource data
$modx->resource->fromArray($data['resource']);
// Set TVs
foreach($data['tvs'] as $tv) {
$modx->resource->setTVValue($tv['id'], $tv['value']);
}
// Set resource groups
foreach($data['groups'] as $group) {
$modx->resource->joinGroup($group['document_group']);
}
// Save the resource
$modx->resource->save();
// Remove the page from current preview-resources
$current_res = $modx->cacheManager->get('preview/current_resources', $cacheOptions);
foreach (array_keys($current_res['resources'], $modx->resource->get('id'), true) as $key) {
unset($current_res['resources'][$key]);
}
$modx->cacheManager->set('preview/current_resources', $current_res, 60, $cacheOptions);
return;
break;
}
return;
Ext.onReady(function () {
var ResourcePanel = Ext.getCmp('modx-panel-resource');
var ResourceTree = Ext.getCmp('modx-resource-tree');
var save_btn = Ext.getCmp('modx-abtn-save');
Previewr.window.Preview = function(config) {
config = config || {};
Ext.applyIf(config,{
title: 'Vorschau'
,closeAction: 'hide'
,width: 500
,height: 500
,maximized: true
,maximizable: false
,fields: [{
xtype : "component",
autoEl : {
tag : "iframe"
,width: '100%'
,height: '750px'
,border: '0'
,style: 'border:1px solid #ccc'
,src : Previewr.config.prev_url
}
}]
,buttons: [{
text: 'Schliessen'
,scope: this
,handler: function() { this.hide(); }
}]
});
Previewr.window.Preview.superclass.constructor.call(this,config);
}
Ext.extend(Previewr.window.Preview,MODx.Window);
Ext.reg('previewr-window-preview',Previewr.window.Preview);
/**
* [description]
* @param {[type]} form [description]
* @param {[type]} opt [description]
* @param {[type]} config [description]
* @return {[type]} [description]
*/
ResourcePanel.on('beforeSubmit', function(form, opt, config) {
if(ResourcePanel.getForm().findField("preview_check").getValue() == 0)
return;
var tp = Ext.getCmp('modx-leftbar-tabpanel');
var t = ResourceTree;
tp.activate('modx-resource-tree');
Ext.each(Previewr.config.all_parents, function(parent, index) {
var n = t.getNodeById('web_' + parent);
if(n)
n.expand();
});
if (save_btn)
save_btn.disable();
});
/**
* [description]
* @param {[type]} o [description]
* @return {[type]} [description]
*/
ResourcePanel.on('success', function(o) {
if(ResourcePanel.getForm().findField("preview_check").getValue() == 0)
return;
var g = Ext.getCmp('modx-grid-resource-security');
var t = ResourceTree;
// var save_btn = Ext.getCmp('modx-abtn-save');
if (g) {
g.getStore().commitChanges();
}
if (t) {
Ext.each(Previewr.config.all_parents, function(parent, index) {
var n = t.getNodeById('web_' + parent);
if(n)
n.expand();
});
var ctx = Ext.getCmp('modx-resource-context-key').getValue();
var pa = Ext.getCmp('modx-resource-parent-hidden').getValue();
var pao = Ext.getCmp('modx-resource-parent-old-hidden').getValue();
var n = t.getNodeById(ctx+'_'+pa);
if(pa !== pao) {
Ext.getCmp('modx-resource-parent-old-hidden').setValue(pa);
} else {
if(typeof n !== 'undefined')
n.leaf = false;
}
}
var object = o.result.object;
// object.parent is undefined on template changing.
if (this.config.resource && object.parent !== undefined && (object.class_key != this.defaultClassKey || object.parent != this.defaultValues.parent)) {
MODx.loadPage(location.href);
} else {
this.getForm().setValues(object);
Ext.getCmp('modx-page-update-resource').config.preview_url = object.preview_url;
}
ResourcePanel.fireEvent('fieldChange');
ResourcePanel.markDirty();
if (save_btn) {
save_btn.enable();
}
// Display a MODx Window with the preview
if(o.result.object.preview_check == 1) {
var updateWindow = MODx.load({
xtype: 'previewr-window-preview'
,title: 'Vorschau: ' + ResourcePanel.getForm().findField('pagetitle').getValue()
});
updateWindow.show();
setTimeout(function() { ResourcePanel.markDirty(); }, 2000);
}
if(o.result.object.action == 'update')
return;
this.getForm().setValues(o.result.object);
});
var uri = ResourcePanel.getForm().findField("uri").getValue();
var alias = ResourcePanel.getForm().findField("alias").getValue();
var id = ResourcePanel.getForm().findField("id").getValue();
// add hidden field to check if it is a preview-process on save
ResourcePanel.add({
xtype: 'hidden'
,name: 'preview_check'
,id: 'preview_check'
},{
xtype: 'hidden'
,name: 'preview_url'
,id: 'preview_url'
});
if(!save_btn)
return;
// If the resource hasn't been created yet it's not possible to generate a preview
if(save_btn.process == 'create')
return;
//reset preview_check when clicking the save-button
save_btn.on('click', function() {
ResourcePanel.getForm().findField("preview_check").setValue('0');
});
var modab = Ext.getCmp("modx-action-buttons");
// modab.add('-');
modab.add('-',{
xtype: 'button'
,text: 'Vorschau'
,id: 'modx-abtn-real-preview'
,method: 'remote'
,process: Previewr.config.update_processor
// ,checkDirty: true
,listeners: {
click: function(btn) {
//set prview_check to 1 when clicking the preview-button
ResourcePanel.getForm().findField("preview_check").setValue('1');
}
}
});
modab.doLayout();
});
MODx Revolution lacks a preview of unsaved changes. For editors this is a rather important feature to check if their changes are correctly displayed and it's easy to do. No need to unpublish, save and view the resource. Just click 'Preview' and a MODx Window will show you all the changes.
The way it works is pretty easy: When clicking the 'Preview' button and OnBeforeDocFormSave
is triggered all current (saved) data will be stored in a cache file, the resource will be saved with the new data. If OnWebPageComplete
is fired, the saved data will be replaced with the previously cached data.
So for a short period the actual preview will be live!
preview.plugin.php
in to a plugin with these events:
OnDocFormRender
OnBeforeDocFormSave
OnDocFormSave
OnWebPageComplete
preview.plugin.js
somewhere (I prefer something like assets/js/plugins
) in your MODx directory and replace it in preview.plugin.php
Please test this on your local development before deploying it on any production server. It might not work as expected!
This is only tested in Chrome 38.0.2125.111 on MAC OS X 10.10 (Yosemite).
There's an issue with MODx 2.3+: When previewing it will ask if you want to leave or stay on this page after clicking the button. Just stay and the MODx Window will pop up immediately after.
Please let me know if this is working on your MODx installation: http://herooutoftime.com/modx-real-preview/