How can I achieve view-model separation in a Javascript component for editing HTML? How can I achieve view-model separation in a Javascript component for editing HTML? reactjs reactjs

How can I achieve view-model separation in a Javascript component for editing HTML?


If I could understand your question correctly, you want to edit several elements in single page?

In such case I would recommend writing this "module" using angularjs directives (wired by attribute so your semantic to html is same as it is not applied at all to document). This directive (when angular app is loaded) should change elements "editable" attribute which would allow user to edit content of element. So lets say template is like this:

<div id="title-editor" my-editor>...</div>

it could be scenario when user blur this div it then fires event in directive and sends content to backend.

Here is working directive which could be candidate to review it https://github.com/TerryMooreII/angular-wysiwyg

Bottom line, is that you can have multiple "placeholders" in page, so you define layout and what web parts are placed where in page, other things are in hands of page moderator.

One more note, name each placeholder, so you can identify them on backend and fill data if your case is like that, or you could use jquery/angular to fill them on page load. It is question what is your preferred way :)

On backend side, you could use redis or mongo to store this place holder logical structure rather then trying to represent complete DOM structure and recreate it each time one reads it from backend. So it would be much quicker to load this way.

For parts which you want to drag and drop on page, again you will need some place holders, but this is different kind of "editor" so you would need additional angular directive. Here is one more which could help gaining knowladge on how to. http://codef0rmer.github.io/angular-dragdrop.

Combining this two, you could have something close to what you need. Again. You have to (as developer) be responsible for page layout, since you have to define at least where top level (drag and drop) placeholders are.


From what I understand, it is not a question of which library to use or which pattern. It is more a role based architecture. Lets say one of the elements you can create and edit in your editor is a <strong> tag. The end users will see

<strong class="some-class">Some content</strong>

While your editors will see the same element (hence WYSIWYG) but with additional styles or controls, lets say it will appear red and have a 'remove' button.

<strong class="some-class">Some content <sup>Remove</sup></strong>

We can leverage CSS to achieve these differences regarding the visual aspect of the elements just by using an additional class on the editor canvas.

.canvas strong{    font-weight: bold;}.canvas strong sup{    display:none;}.editing.canvas strong{    color:red;}.editing.canvas strong sup{    display:inline;}// your end users will see<div class="canvas">    // but the <sup> element will be display:none; by default    <strong class="some-class">Some content <sup>Remove</sup></strong></div>// your editors will see<div class="canvas editing">    // here, the 'editing' class will make the <sup> visible and change the color to red    <strong class="some-class">Some content <sup>Remove</sup></strong></div>

Granted, this is a reeeally simple example but you can see how more complex elements fit into this architecture. Now for the js part.

Lets say that our strong element will throw an alert for our end users on hover, and for our editors it will handle clicks on the 'remove' element.

function StrongElement(){    var id,        el,        data = {           content: 'default content',           classname: 'default classname'        };    function setId(id){        id = id;        render();    }    function setContent(content){        data.content = content;        render();    }    (function create(){        // this is done only once!        el = document.createElement('strong'); // this in more complex elements would be a template        var remove = document.createElement('sup');        remove.innerHTML = 'remove';        el.appendChild(remove);    })();    function render(){        el.id = id;        el.className = data.classname;        el.innerHTML = data.content;        return el;    }    function onHover(){        alert('Im strong and I like it');    }    function remove(){        // more on editor later*        if (editor.isInEditingMode()){            editor.remove(id);        }    }    // expose the public methods    return {        render: render    };}

We have defined a js 'class' to handle any interaction of this element. It can be way more complex and the element could have nested elements. What's additional to a common html plugin is that these elements have an id, and some methods will only work if the variable editor exists. Lets take a look at the editor:

function Editor(){    var canvas = document.getElementById('canvas'), // note that the canvas can be a text area, a div, or even a html5 canvas where your elements are rendered graphically instead of the DOM        elements = [],        editingMode = true;    function addElement(element){        // keep track of the js instance of the element        elements.push(element);        // give it an id that both the element and the editor know        element.setId(elements.length);        // append the graphic representation of the instance (some html tags) into the graphic representation of the editor        canvas.appendChild(element.render());        // we return the id so that anyone who added an element can keep track of it        return elements.length; // the id, will be the index in the array of elements    }    function removeElement(id){        // this could be way better but its just for demo purposes        elements[id] = null;    }    function refresh(){        // elements are already in the canvas, so we iterate over all of them         for (var i in elements){            // and re render them            elements[i].render();        }    }    function setEditingMode(mode){        editingMode = mode;        if (editingMode){             canvas.className = 'editing';        }else{             canvas.className = '';        }    }    function isInEditingMode(){        return editingMode;    }    return {        isInEditingMode: isInEditingMode,        addElement: addElement,        removeElement: removeElement,        refresh: refresh    }}

This is a very simple editor, to get this to a production ready level you'll need to deal with positions of each element, in this case we could assume elements are shown in the same order as they were added. But this gets more interesting when representing a tree structure or x, y and z positions for elements drawn on a pixel canvas. That position data would be added to each element and the render function in each should be able to handle it with reference to the parent editor.

This uber simple editor would work something like this:

<div id="canvas"></div>var editor = new Editor(),    element = new Strong();element.setContent("I'm Stronger");editor.addElement( element );editor.refresh();editor.setEditingMode(true); // this should add the 'editing' class and you should now see the remove button and red colored contenteditor.setEditingMode(false); // this should return the element to a end user state and if you hover you should get an alert

Demo: http://jsfiddle.net/pzef52gh/1/

Basically the way I'd go is design and implement the elements for the end user, including styles and functionality. After that, I'd build on top of those elements the additional styles and functionality for when you are in edit mode. You can do this in any language and any framework. The example might seem familiar for backbonejs users but if I had to do something like this today, I'd use angular and each 'element' would be a directive with a editing/non-editing state. Hope it helps!


I would recommend looking into a Flux implementation with ReactJS. With that, your models can easily be abstracted to services called with Flux actions, which in turn can be listened to with stores where your in-app data can be mutated. That way, your view logic is in the components, but your business logic is abstracted elsewhere.