$50 - Javascript - WYSIWYG Drag and Drop Live Editor for Webpages Prototype
New here? Learn about Bountify and follow @bountify to get notified of new bounties! x

Bounty will be changed to $50 if any work in progress is submitted and anyone commits to finishing it

The objective of this mini project is to create a Live Editor or WYSIWYG page builder, like many that exists today (e.g: http://www.bootply.com/). The idea is to make our live editor very flexible, and let the developer (me)
easily create new modules and let the end user easily create new content via a drag and drop interface.

The backend:

  • The modules will be defined via JSON.
  • The content that will be created by combining modules and parametrizing their properties, will also be coded via JSON.

The frontend:

  • The idea is that the end user never has to touch a line of JSON, so you will have to create a WYSISWYG Live Editor that lets the end user drag
    and drop modules. The Live Editor on the backed will create the JSON content, which will be parsed to create the final output shown to the user.

  • The properties of each content module added by the user will be easily modifiable through an easy UI showing input forms to the user via modals as explained in detail below.

Let's start with examples. The variable "modules" will hold the definition of available module types. On the left pane of the Live Editor these modules should be listed as available,
and user should be able to drag and drop them on any module rendered that holds a property of type "container". The "container" properties will help to create sections, columns or layouts.

As shown below, each module has an "output" (content to be generated),
and properties which are enclosed in %% inside the output and meant to be replaced with the property value. Each property has at least "default value", and a "type".

var modules = [
    "section": {
            output: "<div style='padding: 25px; border: %color% 1px solid;'>%content%</div>",
            properties: [
                            "color": {
                                default :"black",
                                type: "color" /* show color picker */
                                },
                            "content": {
                                default: "", /* Empty content. Show caption "drag and drop modules here" */
                                type: "container" /* show drag and drop area where user can drag and drop other modules (including a section module inside a section module) */
                                }
                        ]
    }
    "3-column": {
            output: "<div>
                     <div style='padding: 25px; border: %color% 1px solid;'>%content1%</div>
                     <div style='padding: 25px; border: %color% 1px solid;'>%content2%</div>
                     <div style='padding: 25px; border: %color% 1px solid;'>%content3%</div>
                     </div>",
            properties: [
                            "color": {
                                default :"black",
                                type: "color" /* show color picker */
                                },
                            "content1": {
                                default: "", /* Empty content. Show caption "drag and drop modules here" */
                                type: "container" /* show drag and drop area where user can drag and drop modules */
                                },
                            "content2": {
                                default: "", /* Empty content. Show caption "drag and drop modules here" */
                                type: "container" /* show drag and drop area where user can drag and drop modules */
                                },
                            "content3": {
                                default: "", /* Empty content. Show caption "drag and drop modules here" */
                                type: "container" /* show drag and drop area where user can drag and drop modules */
                                }
                        ]
    },
    "p" : {
        "output": "<p span="color: %color%;">%text%</p>",
        "properties": 
            [
            "text": {
                default :"default value",
                type: "text" /* show textarea */
            }
            "color": {
                default :"black",
                type: "color" /* show color picker */
                }
            ]
        },
    "image: {
        "output": "<img src='%source%' alt='%alt%'>",
        "properties": 
            [
            "source": {
                default :"default value",
                type: "image" /* show text input to write remote image src with an image preview */
            }
            "alt": {
                default :"[Image]",
                type: "text" /* show textarea */
                }
            ]
        }
    }

]

To begin the initial data when the user opens the Live Editor will be:

content = [
        "section": {
            color: "black",
            content: ""
            }
        ];

This means that initially, the user will be able to drag and drop any modules available since the "section" module contains a "container" type property which is dropppable.
If the user drags a "p" module or "image" module, they will be added
in order, and user should be able to change order by drag and drop, pushing one element down or up.

The previous content should generate the following html content (by looking at the property "output" on module definition and replacement of properties enclosed in %%):

<div style='padding: 25px; border: black 1px solid;'></div>

If the JSON content were for example:

content = [
        "section": {
            color: "black",
            content: [
                        "p" : {
                            "text": "Hellow World!",
                            "color: "green"
                        }
                        "image" : {
                            "url": "Path to image",
                            "alt: "[The Image]"
                        }
            }
]

Then the generated HTML content should be the following (given the "output" property and property value replacements on the module definition):

    <div style='padding: 25px; border: black 1px solid;'>
        <p span="color: green;">Hellow World!</p>
        <img src='Path to image' alt='[The Image]'>
    </div>

If the user inserts a "section" module inside another "section" module this will create recursive action were the user can insert new modules inside the nested section module,
or inside the original section module. This becomes more clear in the following example:

content = [
        "section": {
            color: "black",
            content: [
                        "p" : {
                            "text": "Hellow World!",
                            "color: "green"
                        }
                        "image" : {
                            "url": "Path to image",
                            "alt: "[The Image]"
                        }
                        "3-column": {
                            color: "black",
                            content1: [
                                        "p" : {
                                            "text": "Column 1",
                                            "color: "green"
                                        }
                                    ],
                            content2: [
                                        "p" : {
                                            "text": "Column 2",
                                            "color: "red"
                                        }
                                    ],
                            content3: [
                                        "p" : {
                                            "text": "Column 3",
                                            "color: "blue"
                                        }
                                    ]
                        },
                        "p" : {
                            "text": "Hellow World again!",
                            "color: "blue"
                        }
                        "p" : {
                            "text": "Some more text" /* Omitting any property will make it default to its default value */
                        }

                ]
            }
        ];

The WYSIWGY editor (or "Live Editor") should be able to create this JSON on the fly via a drag and drop interface.

Live Editor Interface:

  • On hover of any module, you should show a pencil icon (edit), cross icon (delete), and a duplicate icon (copy), AND show a dashed colored border around it to make it very clear that the element is being selected.

  • Upon clicking on any module's edit icon dialog should appear (use Bootstrap Modal) and ask for user entry to define the properties of the module.

  • Upon clicking on any module's delete icon, a confirmation dialog should appear, and if user clicks yes, the module should be deleted.

  • Upon clicking on any module's duplicate icon, module should be instantly duplicated without confirmation and appear below current module.

Modal Dialog Editor Interface

By using the modules definitions, the modal dialog should programatically have different fields.
The field types will be defined by the module "type" property.

Example:

"p" : {
    "output": "<p span="color: %color%;">%text%</p>",
    "properties": 
        [
        "text": {
            default :"default value",
            type: "text" /* show textarea */
        }
        "color": {
            default :"black",
            type: "color" /* show color picker */
            }
        ]
    }

Clicking on this module's edit icon, should open a modal dialog with two inputs, the first should be a textarea (type "text") with default value "default value",
and the second should be a color picker (type "color") with default color black.

Another example:

"image: {
        "output": "<img src='%source%' alt='%alt%'>",
        "properties": 
            [
            "source": {
                default :"default value",
                type: "image" /* show text input to write remote image src with an image preview */
            }
            "alt": {
                default :"[Image]",
                type: "text" /* show textarea */
                }
            ]
        }

Click on any module of type "image"'s edit icon should open a modal dialog with two inputs, the first a input text box with a preview pane (for "source"),
and second a textarea for "alt", with default value "[image]".

Final example:

    "section": {
            output: "<div style='padding: 25px; border: %color% 1px solid;'>%content%</div>",
            properties: [
                            "color": {
                                default :"black",
                                type: "color" /* show color picker */
                                },
                            "content": {
                                default: "", /* Empty content. Show caption "drag and drop modules here" */
                                type: "container" /* show drag and drop area where user can drag and drop other modules (including a section module inside a section module) */
                                }
                        ]
    }

In the case of type "container" clicking on the pencil icon should open a dialog with all the properties defined that are not of type "container".
So in this case it would only include a "color" input color picker. The "content" property does not appear on the dialog,
as it is of a "container" type, and so it is a drag and droppable area for dragging other modules.

And as mentioned before, recursive action should be supported (meaning that a container can be inside a container, and be inside another container).

More bounties related to this project will be asked soon, so if you are able to solve this, you are in for a treat

I am happy to increase this to $50 if anyone can commit to finishing it. Thanks!
georgefountain 3 months ago
..And? Any update? Is anyone willing to work on this bounty? If so let me know and I will increase the bounty's price. Thanks
georgefountain 3 months ago
awarded to Kuanysh
Tags
javascript

Crowdsource coding tasks.

2 Solutions

Winning solution

Hi, try this solution, it is able to render the modules, as you specified.
I needed to change your content JSON to have it as an array with "name": "section" - like syntax for identifying modules. Other features like drag & drop and modal windows would require more time.

<!DOCTYPE html>
<html>
<head>
    <title>Drag &amp; Drop Editor</title>
</head>
<body>

<script type="text/javascript">
    var modules = {
        "p": {
            "output": "<p style='color:%color%'>%text%</p>",
            "properties": {
                "color": {
                    "default": "black",
                    "type": "color",
                },
                "text": {
                    "default": "Your text here",
                    "type": "text",
                },
            },
        },
        "section": {
            "output": "<div style='padding: 25px; border: %color% 1px solid;'>%content%</div>",
            "properties": {
                "color": {
                    "default": "black",
                    "type": "color",
                },
                "content": {
                    "default": "",
                    "type": "container",
                }
            }
        }
    };  

    var content = [
        {
            "name": "p",
            "color": "red",
            "text": "Hello world",
        },
        {
            "name": "p",
            "text": "How are you doing? :)",
        },
        {
            "name": "p",
            "color": "#0f0",
        },
        {
            "name": "section",
            "color": "orange",
            "content": [
                {
                    "name": "p",
                    "text": "hello",
                    "color": "purple",
                },
                {
                    "name": "section",
                    "color": "red",
                    "content": "salem world"
                },
                {
                    "name": "p",
                    "text": "hello",
                    "color": "green",
                },
            ],
        }
    ];

    function getModule(name) {
        return modules[name];
    }

    function render(content) {
        if (Array.isArray(content)) {
            var output = '';
            for (var i = 0; i < content.length; ++ i)
                output += renderModule(content[i]);
            return output;
        }

        return renderModule(content);
    }

    function renderModule(instance) {
        if (typeof instance != 'object')
            return instance;

        var module = getModule(instance.name);
        if (!module) {
            console.error("cannot find module definition for ", instance);
            return '';
        }

        if (!module.hasOwnProperty("output")) {
            console.error("no output defined for module ", instance.name);
            return '';
        }

        var output = module.output;

        if (!module.properties) {
            console.info("no properties in the module ", instance.name, " defined");
            return output;
        }

        for (var property in module.properties)
            output = output.replace('%' + property + '%', getPropertyValue(property, instance, module));

        return output;
    }

    function getPropertyValue(property, instance, module) {
        var value = '';
        var moduleProperty = module.properties ? module.properties[property] : null;

        if (!moduleProperty) {
            console.error("module ", instance.name, " doesn't have a property ", property);
            return value;
        }

        if (instance.hasOwnProperty(property))
            value = instance[property];
        else if (moduleProperty.hasOwnProperty("default"))
            value = moduleProperty.default;
        else console.error("property ", property, " in module ", instance.name, " doesn't have a default value");

        return moduleProperty.type == "container" ? render(value) : value;
    }

    document.write(render(content, modules));

</script>
</body>
</html>
Thank you Kuanysh, could you please post this on a codepen so it can be tested? Thanks
georgefountain 3 months ago
Sure, http://codepen.io/anon/pen/OpjwKa. You can change the modules and content objects and see what happens.
Kuanysh 3 months ago
This looks fantastic! I have raised the bounty to $50 as promised. I like that you and farolanf have solved different things. You solved the render part, he is solving the drag and drop. Maybe you can use each others' code.
georgefountain 3 months ago
Hey, I implemented the drag & drop functionality, you can see it here: https://jsfiddle.net/ojnrwh4g/
Kuanysh 3 months ago
Hi Kuanysh, wow I am impressed! Question: does reordering the elements on drag and drop actually modify the content JSON array? I mean is the content JSON saved as I edit on the drag and drop view?
georgefountain 3 months ago
Also, would it be possible for you to add a "shadow" of the element being about to be dropped? Right now you are showing a green line, but it would be better if you actually showed the element to be dropped, semi-transparent. Like this sample http://codepen.io/rodrix/pen/aJGzGg . See what happens when you drag an element how it previews how it will look before being dropped. Could you add this? Thanks!!! Amazing work again
georgefountain 3 months ago
Hi, good questions. The greatest thing about this solution that it was designed to keep the JSON array in sync with the drag & drop interface. I anticipated that you ask for that and it was the most challenging thing about this task. So, yes, dragging and dropping elements changes the JSON array in the first place! And the updated JSON array is rendered to show that change to the user.
Kuanysh 3 months ago
Regarding the element shadow - I will try to do it, maybe using Dragula. But I am not sure if it works here, will let you know. Btw, this bounty has expired, what happens after that?
Kuanysh 3 months ago
Amazing work Kuanysh! I am so happy to hear that the JSON array is in sync with the drag and drop interface. Could you display the JSON array on the console after every drag and drop change so I can see it? Thanks so much for the amazing work!
georgefountain 3 months ago
Regarding the shadow, it would be great if you can add it. You may use Dragula if you like, like the sample I provided you. The bounty expired, I have 36 hours from now to decide to whom I give the bounty... so we got some "extra time". Thanks so much looking forward to your update! :) :) :)
georgefountain 3 months ago
P.S: Dragula script has a nice response and feel when dragging and dropping. If you can implement it, it would be great.
georgefountain 3 months ago
Ok, I'll try it and let you know
Kuanysh 3 months ago
Thanks Kuanysh, I will be waiting. Hoping you can implement it!
georgefountain 3 months ago
Kuanysh I need to award the bounty today (deadline cannot be extended). Please don't forget to submit the corrections so I can award it to you. Thanks
georgefountain 3 months ago
Any update?
georgefountain 3 months ago
Sorry, still not working as expected with Dragula, still in progress..
Kuanysh 3 months ago
Thanks for your update! Looking forward to hearing from you later. Thank you :)
georgefountain 3 months ago
Any update?
georgefountain 3 months ago
I have awarded the bounty to you trusting that you will solve the final details asked. Please update me asap with your progress and deliver in the next 24hs. Thank you!
georgefountain 3 months ago
Also, could you please tell me how to modify a single property value from a module inside the content array? Let's say I want to modify the "text" property of module "drag-n-drop-component-29" from the content array, how can I do that? I want to update the property value on the content JSON, and re-render. You can post the answer here. But my email is this value http://pastebin.com/yTSNCPfg @gmail.com . Please send me an email so I have your contact too. Thank you!
georgefountain 3 months ago
Any update? Please reply. Thanks
georgefountain 3 months ago
Hi, sorry for silence, George, thanks for awarding this to me. I've made progress on dragula integration, it looks promising, but need to finalize it and clean some bugs.
Kuanysh 3 months ago
As per modifying the properties of the module, there is no UI for this currently, but I wrote the code with this feature in mind, so there should not be problems with implementing this.
Kuanysh 3 months ago
Also elements with "drag-n-drop-component-" id are internal modules, that are needed for drag-n-drop feature, so you shouldn't need to change any of their properties.
Kuanysh 3 months ago

Here's the current state of my work https://tokovopulsa.firebaseapp.com. It's not much to look at, but it involves many components under the hood.

The Stack

  • React
  • Redux
  • Babel
  • Webpack
  • Currently hosted at firebase, and when the backend is ready I planned to host it on heroku

Roadmap

  • Snippet instance controls
  • Snippet instance editor
  • Property editor
  • Array of children
  • Backend
  • Polished UI
  • Should be many more I can't think of right now

About

I did this mainly for learning to develop this type of app. It took too much of my time as I expected and it's just a small part of the finished app. Finishing this app needs many more hours so I expect other deal instead of this bounty, specifically fee per hour or salary based deal.

Development Screenshot

Code

Farolanf, amazing work! This looks very promising.
georgefountain 3 months ago
Farolanf, I have raised the bounty to $50 as promised. I like that you and Kuanysh have solved different things. You solved the drag and drop and interface, he is solving the render part. Maybe you can use each others' code. In your example could you add an option to see the JSON of the defined modules, and the content JSON? Thanks. Also I tried to contact you with no avail, through Bountify.
georgefountain 3 months ago
Did you test using this script which provides nested drag and drop targets? http://react-dnd.github.io/react-dnd/examples-nesting-drag-sources.html
georgefountain 3 months ago
And can you please explain why you are having so many dependencies? thanks
georgefountain 3 months ago
From what I tested on your demo, your demo only allows dropping just one module. I can't drop any more.
georgefountain 3 months ago
Hi George, I think the project is too complex for this bounty so I offered you another fair deal via email, but I didn't receive answer. The demo that you can access from the link of this solution is redesigned and doesn't reflect the screenshot anymore. It doesn't use react-dnd, and it design lean toward contentbuilder.js. I'm still working on it, but not for this bounty.
farolanf 3 months ago
View Timeline