better-run.php
<?php
?>
<html>
<head>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/js-beautify/1.10.1/beautify-html.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/js-beautify/1.10.1/beautify.js"></script>
<style>
html,body,table,tr {
min-width:100%;
min-height:100%;
padding:0;
margin:0;
height:100%;
}
td {
width: 33%;
min-height:100%;
}
td > div {
width:100%;
height:100%;
}
#blocks > div {
cursor:pointer;
}
#block_creator {
display:none;
}
</style>
<script>
class WYGUtil {
static nodeLocation(node){
var range = document.createRange();
range.selectNode(node);
var rects = range.getClientRects();
if (rects.length > 0) {
return rects[0]['y'];
}
}
static allowDrop(event) {
event.preventDefault();
}
static isTopHalf(y,node){
if (typeof node !== 'object'
||node.nodeName=='#text')return true;
var range = document.createRange();
range.selectNode(node);
//range.selectNodeContents(node);
var rects = range.getClientRects();
if (typeof rects === undefined
||typeof rects[0] === undefined)return true;
const nodeY = rects[0]['y'];
const nodeEnd = nodeY + node.offsetHeight;
const avg = (nodeY+nodeEnd)/2;
if (y<avg)return true;
else return false;
}
static htmlAsNode(html){
var div = document.createElement('div');
div.innerHTML = html.trim();
return div.firstChild;
}
static placeNearClosestChild(event){
const cursorY = event.clientY;
var contentNode = event.target;
var insertHtml = event.dataTransfer.getData("html");
let relatedChild = this.getClosestVerticalChild(cursorY,contentNode);
let goesAbove = this.isTopHalf(cursorY,relatedChild);
let insertNode = wysiwyg.blockAsContentNode(this.htmlAsNode(insertHtml));
const existingNode = (relatedChild === undefined ||relatedChild === null || typeof relatedChild ===undefined) ? contentNode.firstChild :
(goesAbove ? relatedChild : relatedChild.nextSibling);
this.placeNode(insertNode,existingNode);
}
static placeNode(newNode,existingNode){
const content = document.getElementById("content");
existingNode.parentNode.insertBefore(newNode,existingNode);
const settings = newNode.getAttribute('data-editable');
newNode.removeAttribute('data-editable');
newNode.removeAttribute('draggable');
const id = this.nodeMap.length;
this.nodeMap[id] = {'settings':settings==null ? '' : settings, 'node':newNode};
}
static drop(event) {
event.preventDefault();
const cursorY = event.clientY;
var data = event.dataTransfer.getData("html");
var wrapper = document.createElement('div');
wrapper.innerHTML = data;
var insertNode = wrapper.firstChild;
while (insertNode.nodeName=="#text"){
insertNode = insertNode.nextSibling;
}
if (event.target.getAttribute("id")=="content"){
WYGUtil.placeNearClosestChild(event);
} else {
wysiwyg.placeNearNode(event,insertNode);
insertNode.removeAttribute('draggable');
}
wysiwyg.updateHtml();
}
static getSettingsHtml(node){
let settings = '';
let nodeId = -1;
for (let i=0;i<this.nodeMap.length;i++){
if (this.nodeMap[i]['node']===node){
nodeId = i;
settings = this.nodeMap[i]['settings'];
break;
}
}
let editables = settings.split(',');
editables.push('id');
editables.push('className');
editables.push('style');
function onlyUnique(value, index, self) {
return self.indexOf(value) === index;
}
editables = editables.filter(onlyUnique);
var html = '<div data-nodeid="'+nodeId+'">'
+'<h1>Settings</h1>';
for (let i=0;i<editables.length;i++){
const editable = editables[i].trim();
html = html
+ '<label for="'+editable+'">'+editable+'</label>'
+ '<br>'
+ '<input onkeyup="WYGUtil.saveSetting(this);" type="text" name="'+editable+'" value="'+this.getEditableValue(node,editable)+'">'
+ '<br>';
}
html = html
//+ '<button onclick="updateSettings();">Update</button>'
+ '<br>'
+ '<button onclick="moveUp(getNodeFromSettings(this));">Move Up</button>'
+ ' <button onclick="moveDown(getNodeFromSettings(this));">Move Down</button>'
+ '<br><br>'
+ '<button onclick="deleteNode(getNodeFromSettings(this));">Delete Node</button>'
+ '</div>';
return html;
}
static saveSetting(input){
const editable = input.getAttribute('name');
const value = input.value;
const nodeId = input.parentNode.getAttribute('data-nodeid');
const node = this.nodeMap[nodeId]['node'];
this.setEditableValue(node, editable, value);
}
static setEditableValue(node, editable, value){
if (editable in node){
node[editable] = value;
} else {
node.setAttribute(editable,value);
}
wysiwyg.updateHtml();
}
static getEditableValue(node,editable){
if (editable in node){
return node[editable];
} else {
return node.getAttribute(editable);
}
}
static moveDown(node){
const parent = node.parentNode;
const sibling = node.nextSibling;
if (sibling != null){
const nextSibling = sibling.nextSibling;
parent.removeChild(node);
parent.insertBefore(node,nextSibling);
} else {
console.log('next sibling null');
}
wysiwyg.updateHtml();
}
static moveUp(node){
const parent = node.parentNode;
const sibling = node.previousSibling;
if (sibling != null){
parent.removeChild(node);
parent.insertBefore(node,sibling);
} else {
console.log('prev sibling null');
console.log(node);
}
wysiwyg.updateHtml();
}
static getNodeFromSettings(settingsNode){
const nodeId = settingsNode.parentNode.getAttribute('data-nodeid');
const node = this.nodeMap[nodeId]['node'];
return node;
}
static deleteNode(node){
node.parentNode.removeChild(node);
}
static editBlock(event){
const node = event.target;
const settingsNode = document.getElementById("settings");
settingsNode.innerHTML = this.getSettingsHtml(node);
}
}
WYGUtil.nodeMap = [];
class WYSIWYG {
updateHtml(){
}
placeNearNode(event,newNode){
var lastParent = event.target;
const cursorY = event.clientY;
const content = document.getElementById("content");
while (lastParent!==content
&&lastParent.parentNode!==content){
lastParent = lastParent.parentNode;
}
let goesAbove = WYGUtil.isTopHalf(cursorY,lastParent);
const existingNode = goesAbove ? lastParent : lastParent.nextSibling;
WYGUtil.placeNode(newNode,existingNode);
}
blockAsContentNode(node){
if (node.className=='block'){
node.innerHTML = node.innerHTML.trim();
node = node.firstChild;
}
return node;
}
templatesAreEditable(){
const blockCreator = document.getElementById("block_creator");
return (blockCreator.style.display=='block');
}
setupDrag(){
const blocksNode = document.getElementById("blocks");
const nodeList = Array.from(blocksNode.childNodes);
nodeList.forEach(function(node){
new ElementTemplate(node);
});
}
createTemplate(){
this.enableTemplateEditing();
const blocks = document.getElementById("blocks");
const newBlock = document.createElement('div');
newBlock.className = 'block';
blocks.appendChild(newBlock);
const template = new ElementTemplate(newBlock);
const templateEditor = new ElementEditor(template);
templateEditor.display();
}
toggleTemplateEditing(){
const blockCreator = document.getElementById("block_creator");
const contentArea = document.getElementById("content");
if (!this.templatesAreEditable()){
this.enableTemplateEditing();
} else {
this.disableTemplateEditing();
}
}
enableTemplateEditing(){
const blockCreator = document.getElementById("block_creator");
const contentArea = document.getElementById("content");
const toggleButton = document.getElementById("toggle_edit_button");
toggleButton.innerText = 'Disable Template Editing';
contentArea.style.display = "none";
blockCreator.style.display = "block";
}
disableTemplateEditing(){
const blockCreator = document.getElementById("block_creator");
const contentArea = document.getElementById("content");
const toggleButton = document.getElementById("toggle_edit_button");
toggleButton.innerText = 'Enable Template Editing';
contentArea.style.display = "block";
blockCreator.style.display = "none";
const blockHtml = document.getElementById("block_html");
blockHtml.value = "";
blockCreator.removeAttribute('data-nodeid');
}
refreshElementTemplates(){
var url = "/get_element_templates.php";
var xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.onreadystatechange = function(event){
if (this.readyState===4){
const elementTemplates = document.getElementById("blocks");
elementTemplates.innerHTML = this.response;
wysiwyg.setupDrag();
}
}
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.send();
}
}
var wysiwyg = new WYSIWYG();
window.onload = wysiwyg.setupDrag;
class ElementEditor {
constructor(template){
this.template = template;
this.templateNode = template.node;
this.wysiwyg = template.wysiwyg;
}
display(){
wysiwyg.enableTemplateEditing();
const blockHtml = document.getElementById("block_html");
blockHtml.value = html_beautify(this.templateNode.outerHTML.trim());
const blockCreator = document.getElementById("block_creator");
const nodeId = WYGUtil.nodeMap.length;
WYGUtil.nodeMap[nodeId] = this.templateNode;
blockCreator.setAttribute('data-nodeid',nodeId);
var thisObj = this;
document.getElementById("template_editor_cancel").onclick =
function(event){
thisObj.cancel.call(thisObj,event);
};
document.getElementById("template_editor_save").onclick =
function(event){
thisObj.save.call(thisObj,event);
};
}
save(){
const blockCreator = document.getElementById("block_creator");
const blockHtml = document.getElementById("block_html");
const nodeId = blockCreator.getAttribute('data-nodeid');
const settingsHtml = document.getElementById("settings_html");
const node = WYGUtil.nodeMap[nodeId];
node.innerHTML = blockHtml.value;
var url = "/save_element_template.php";
var params = "element_html="+blockHtml.value
+"&settings_html="+settingsHtml.value;
var xhr = new XMLHttpRequest();
xhr.open("POST", url, true);
xhr.onreadystatechange = function(event){
if (this.readyState===4){
console.log(this.response);
}
}
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.send(params);
this.cancel();
//this.wysiwyg.setupDrag();
this.wysiwyg.refreshElementTemplates();
}
cancel(){
this.wysiwyg.disableTemplateEditing();
}
}
class Template {
constructor(node){
this.node = node;
this.wysiwyg = wysiwyg;
this.activateListeners();
}
activateListeners(){
const node = this.node;
if (typeof node === 'object'
&&node.nodeName!=='#text'){
node.addEventListener('dragstart',this.drag);
node.setAttribute("draggable","true");
var clickedFunc = this.showEditor;
var thisObj = this;
node.addEventListener('click',function(event){clickedFunc.call(thisObj,event);});
}
}
}
class ElementTemplate extends Template{
showEditor(){
if (!this.wysiwyg.templatesAreEditable()){
return;
}
const editor = new ElementEditor(this);
editor.display();
}
drag(event){
event.dataTransfer.setData("html", event.target.outerHTML);
}
}
</script>
</head>
<body>
<table>
<tr>
<td style="background:red;">
<div id="settings">
</div>
</td>
<td>
<div id="content" onclick="WYGUtil.editBlock(event);" ondrop="WYGUtil.drop(event)" ondragover="WYGUtil.allowDrop(event)"> </div>
<div id="block_creator">
<legend for="element_html">Element HTML</legend><br>
<textarea id="block_html" name="element_html" rows="3" cols="70"></textarea>
<br>
<legend for="element_settings_html">Element SettingsHTML</legend><br>
<textarea id="settings_html" name="element_settings_html" rows="3" cols="70"></textarea>
<br>
<button id="template_editor_cancel">Cancel</button>
<button id="template_editor_save">Save Template</button>
</div>
</td>
<td style="background:green;">
<div>
<div>
<div id="blocks">
<?php
$pdo = new PDO('mysql:host=localhost;dbname=test','reed','pass_word');
$statement = $pdo->prepare("SELECT * FROM wysiwyg_elements ORDER BY element_html ASC");
$statement->execute();
$elements = $statement->fetchAll();
foreach ($elements as $tagName=>$data){
echo $data['element_html'];
}
?>
</div>
</div>
<span id="blocks_end"></span>
<br><br>
<button onclick="wysiwyg.createTemplate();">Create New Template</button>
<button id="toggle_edit_button" onclick="wysiwyg.toggleTemplateEditing();">Enable Template Editing</button>
</div>
</td>
</tr>
<tr>
<td></td><td><textarea id="html"></textarea></td><td></td>
</tr>
</table>
</body>
</html>