ReactJS + jQuery Nestable = Drag & Drop Menu Manager

tl;dr

Final revision of the JSFiddle for this demo.

The Intro Heartbreak

I was an AngularJS developer & I loved it.

I thought it was the best thing since learning jQuery, I thought it could solve most of my problems effortlessly and that nothing could ruin it. I had read about how the Angular team was implementing Angular 2 as "a whole rewrite!" and I remember getting a little mad about it since it was going to force me to forget some of the lessons I had learned as a novice developer, and to break something that lots of people, myself included, considered to be a stable, all in one stop shop for web devs' problems.

I didn't know there were so many things wrong with Angular until I meet ReactJS.

Sure I had my issues, my breakdowns, my head banging on the wall sometimes but that's simply a job requirement with learning something new. I never experienced the dreaded performance issues so, actually, I wasn't even aware of half of the problems React aims to fix.

Also, that's not to say it's fair to give both the same level of criticism. It feels a little bit like comparing oranges to tangerines clementines since Angular is a full-fledged framework and React...is not. However, one often tends to place them in the same category and you wouldn't be (entirely) wrong for doing it.

With that, I started reading up on React. The first thing I heard in the midst of all the React hype was, unsurprisingly, that it was breaking speed records much like Usain Bolt. Fortunately, one of my colleagues pointed out that the people at 500Tech exposed very clearly that's not the case (also, another tech blog "Proudly published with Ghost"!). Naturally, I wasn't impressed.

But everything changed when the fire nation attacked I read about React's Virtual DOM and one-way data binding.

My first react(ion) was something like this:

Why do we want React's one-way data binding when Angular gives us a perfectly synced model with two-way data binding?

I believe most of you may have the same question. And when I told Joana, the co-author of this blog, I swear that's what she asked me too.

Angular always had one-way data binding with its fancy curly braces or ng-bind, for those who don't remember, but it was ng-model and $watch that always gathered the most attention. If you're interested on the subject, I gathered lots of good information on this Programmer's Stack Exchange Question.

I still love you Angular...just not as much.

On to the good bits!


The Good bits

As advertised, I'll be using Nestable, an excellent yet originally deprecated jQuery plugin. There are other active forks, and I've made my own since I'm using it at work - I may feel like giving it wings or something - and ReactJS, Facebook's own interface JS library.

The first thing you should do to get to know React is play around with their fiddles, particularly the one with JSX.

What on earth is 'JSX'?

«JSX is a XML-like syntax extension to ECMAScript», it will help you to better understand your code, it may help you to adapt to the upcoming syntax changes and it will make you do the macarena. But it's all subjective of course.

Redirect yourself to a whole new mentality with this 5-step guide of Thinking in React. It really helped me get up and running pretty quickly considering I hadn't tried anything before and was moving away from the ng-way-of-thinking.

Start with a mock & break the UI into a component hierarchy

The article itself mentions working alongside a designer, and that might be case for some of you, but I couldn't really relate. Instead, I found out that doing that little exercise really helped me to build the interface quicker, as it cleaned up my thought process when writing code for each component.

How do you decide a component? Look no further than what's already mentioned in the article - the single responsibility principle - and make your components do one thing and one thing only. Ideally, of course. Find their real purpose in life.

Menus Mockup

  • MenusContainer - Light blue
  • MenuContainer - Red
  • MenuTitle - Orange
  • MenuBody - Purple
  • MenuDropdownItem - Blue
  • MenuItem - Green

Did I overdo it? I'll leave that up to you. (Yes, that's Balsamiq Mockups)

//JSON initial data
var menus = [  
  { menu_id: "navigation_menu1", title: "Main Menu", items: [{ id: 1, title: "Item1", link_to: "Products", url: "/product1" }, { id: 2, title: "Item2", link_to: "", url: "", children: [{ id: 3, title: "Item3", link_to: "Products", url: "/product2", children: [{ 

 (...)
];

Next, I had to decide what would my static data look like, so I came up with something like the object above. And so it started (JSFiddle - Revision 1).

You should also notice how the first element is being created and rendered to the document body.

var MenusContainer = React.createClass({  
    render: function() {
        return (
            <div>Hello, world!</div>
        );
    }
});

React.render(<MenusContainer />,  document.body);  
UI components

Notice anything different? Maybe you should copy & paste the code above to the JSX compiler and see the changes. The lines <div>Hello, world!</div> & <MenusContainer /> are the first use of JSX in the process and are much more cleaner than their native counterparts React.createElement("div", null, "Hello, world!") & React.createElement(MenusContainer, null). "That's in your opinion, counselor?" "In my opinion, your honor."

It's not interesting to render a component to the document.body itself, but rather we should create a div that will accommodate our elements. Let's add the following HTML and change our React.render command.

<div id="content"></div>

React.render(<MenusContainer />,  document.getElementById('content'));  

While we're at it, we should mimic the MenusContainer component code and add the rest of the pack from the mockup, while nesting them inside each other to match the colored boxes logic. For example, for the MenuItem and MenuDropdownItem:

var MenuItem = React.createClass({  
    render: function() {
        return (
            <div>Hello, MenuItem!</div>
        );
    }
});

var MenuDropdownItem = React.createClass({  
    render: function() {
        return (
            <div>
                <h1>MenuDropdownItem</h1>
                <MenuItem /> // this is how you nest nest nest
            </div>
        );
    }
});

You can see the results @Revision 4

I decided to make it clear which elements were being displayed compared to the previous mockup, so I added some classes to each element with the components' own names, like so:

var MenuContainer = React.createClass({  
    render: function() {
        return (
            <div className="menuContainer"> // notice the use of 'className'
                <h1>MenuContainer</h1>
            </div>
        );
    }
});

Notice the use of className there? Before moving on, I have to warn you to the following fact, present in this article.

Since JSX is JavaScript, identifiers such as class and for are discouraged as XML attribute names. Instead, React DOM components expect DOM property names like className and htmlFor, respectively.

Got it? Let's move on.

Nestable & Bootstrap

Borrowing the elements & classes from the Nestable Demo, it was also time to startup the jQuery plugin and start shaping up our menu manager. With new external resources in place, using the rawgit trick to include the necessary JS directly from Github, I was able to reach Level Revision 8.

That doesn't look quite like the mockup does it? Let's take care of that by adding another external resource, good ol' Bootstrap and style our components juuuuuust a little bit, adding a row in our top cat MenusContainer and col-xs-4 class in MenuContainer. Advance to Revision 14 (and wait 3 turns).

Finally, we can also take care of styling the MenuContainer since it's pretty obvious from the mockup that we'll be using Bootstrap Panels to achieve such a layout, along with some buttons. Let's be done with the CSS and analyze Revision 20.

One way data flow - Props

It's time to talk a little about properties, or simply put, props. Props is how you'll pass data from parent to children, and is how you'll be able to fully take advantage of the fact that we are reusing & nesting components. React components also have state but if you're smart about it, you don't need (and shouldn't need!) to use state at all for this application! You'll be able to dig deeper into the differences between props & state right here.

I'm not sure where I read it first, so just keep this in mind for future reference: everything that changes goes in state, everything that doesn't goes in props. Also, don't set state with props (with exceptions).

Now let's look at the following piece of code:

//JSON data
var menus = [  
  { menu_id:1, title: "Main Menu", items: [{ (...)

// render Root node
React.render(<MenusContainer data={menus} />, document.getElementById('content'));  

The menus var is there like a little reminder for those who missed it. With that extra bit of code data={menus} we're basically saying that all JSON data is now available inside the MenusContainer component, which can be accessed by this.props.data.

var MenusContainer = React.createClass({  
    render: function() {
        return (
            <div className="row menusContainer">
                <h1>Menus</h1>
                {this.props.data.map(function(menu){return <MenuContainer key={menu.menu_id} data={menu} />})}
            </div>
        );
    }
});

The assumption here is that our JSON data would be dynamic, perhaps served by our NodeJS backend or fed by some farfetched API from galaxies away. And maybe, just maybe, you'll want to add new menus on the go, maybe edit some properties...everything is dynamic!

That's why we're using map to go through our array of menus and, more importantly, why we're assigning each children an unique key, so React can keep the state of each child instead of reusing it or reordering it or even destroying it.

With that brand new knowledge in place, take another look at Revision 20 and be sure to identify how data is being passed around. When you're ready, peek at Revision 21 to see a near complete data flow.

Give me back my JSON!

Still borrowing extra stuff from the Nestable Demo, let's add a serialized output textarea to check that our data is being changed in real time when we move our menu items around. Firstly, add the corresponding HTML.

<textarea id="nestable-output" readonly></textarea>  

Then prepare some update function taking advantage of Nestable's own serializer (along with whatever extra variables you'll need - in my case, menu_title & menu_id.

function updateJSON(){  
    var menusJSON = [];

    $('.dd').each(function(menu){
        var menu_list = $(this).nestable('serialize');
        var menu_title = $(this).parent().siblings('.panel-heading').children('h3').html();
        var menu_id = this.id;

        menusJSON.push({menu_id: menu_id, title: menu_title, items: menu_list});
    })

    // show JSON at textarea
    $('#nestable-output').val(JSON.stringify(menusJSON));
}

Lastly, add the change listener to the Nestable initializer with our newly added function and also run it when the page has finished loading so it doesn't show up emtpy.

// Init Nestable
$('.dd').nestable(/* config options */).on('change', updateJSON);

$(document).ready(function(){
    updateJSON();
});

And check the results at Revision 38! (I'm not sure what I was doing saving all these revisions, probably some...stuff...)

Click me! (No, not literally)

All this time we left our buttons in the MenuTitle component just chillin', watching us, probably even playing darts at some point revision. Well no more! Let's give them a pair of simple handlers.

And by simple I mean really bare bones alerts and logs, nothing too fancy. Move over to the culprits in question and set them like this

<button type="button" className="btn btn-default" onClick={this.handleEditClick}>

(...)

<button type="button" className="btn btn-default" onClick={this.handleTrashClick}>  

Notice how the handler functions are referred to, always having a hand to this component. This is crucial because you'll want to save a reference to these functions, or the component itself, in case you want to bind them to a child when using the map function, for example. You'll thank me later...

However, since this won't be the case, let's add the handlers right on top of our component's return block

var MenuTitle = React.createClass({  
    handleEditClick: function (e) {
      alert("EDIT " + this.props.menu_id);
      console.log(this.props);
    },
    handleTrashClick: function (e) {
      alert("TRASH " + this.props.menu_id);
      console.log(this.props);
    },
    render: function() {
        return (
            <div className="panel-heading clearfix">
                <h3 className="panel-title pull-left">{this.props.title}</h3>
                <div className="btn-group btn-group-xs pull-right">
                    <button type="button" className="btn btn-default" onClick={this.handleEditClick}><span className="glyphicon glyphicon-edit" aria-hidden="true"></span></button>
                    <button type="button" className="btn btn-default" onClick={this.handleTrashClick}><span className="glyphicon glyphicon-trash" aria-hidden="true"></span></button>
                </div>
            </div>
        );
    }
});

Click the buttons at will and check all of this code working together @ the final revision.

Before I let you go...

I cannot eagerly say that ReactJS is or will be the solution to all your problems, nor that it's better to use it instead of AngularJS since I could easily argue for both (form-heavy app VS a large web data table).

But it did help me understand the reasons behind Angular 2, why EmberJS will be moving forward with these ideas, and wrap my head around some new and interesting concepts like the Virtual DOM.

Be sure to check out React's official documentation and find some interesting extra information @ the following StackExchange topic.

Tiago Casanova

Developer @ Jumpseller. Currently working with Ruby & JavaScript. Curious and creative on a daily basis and works like a chameleon. Loves hoops and cooks.

Subscribe to Binary Lies

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!