We still have work to do to turn this into a real Kanban as pictured above. The application is still missing some logic and styling. That's what we'll focus on here.
The Editable
component we implemented earlier will come in handy. We can use it to make it possible to alter Lane
names. The idea is exactly the same as for notes.
We should also make it possible to remove lanes. For that to work we'll need to add an UI control and attach logic to it. Again, it's a similar idea as earlier.
Lane
names#To edit a Lane
name, we need a little bit of logic and UI hooks. Editable
can handle the UI part. Logic will take more work. To get started, tweak LaneHeader
as follows:
app/components/LaneHeader.jsx
import React from 'react';
import uuid from 'uuid';
import connect from '../libs/connect';
import NoteActions from '../actions/NoteActions';
import LaneActions from '../actions/LaneActions';
import Editable from './Editable';
export default connect(() => ({}), {
NoteActions,
LaneActions
})(({lane, LaneActions, NoteActions, ...props}) => {
const addNote = e => {
...
};
const activateLaneEdit = () => {
LaneActions.update({
id: lane.id,
editing: true
});
};
const editName = name => {
LaneActions.update({
id: lane.id,
name,
editing: false
});
};
return (
<div className="lane-header" {...props}>
<div className="lane-header" onClick={activateLaneEdit} {...props}>
<div className="lane-add-note">
<button onClick={addNote}>+</button>
</div>
<div className="lane-name">{lane.name}</div>
<Editable className="lane-name" editing={lane.editing}
value={lane.name} onEdit={editName} />
</div>
);
})
The user interface should look exactly the same after this change. We still need to implement LaneActions.update
to make our setup work.
Just like before, we have to tweak two places, the action definition and LaneStore
. Here's the action part:
app/actions/LaneActions.js
import alt from '../libs/alt';
export default alt.generateActions(
'create', 'update', 'attachToLane', 'detachFromLane'
);
To add the missing logic, tweak LaneStore
like this. It's the same idea as for NoteStore
:
app/stores/LaneStore.js
import LaneActions from '../actions/LaneActions';
export default class LaneStore {
constructor() {
this.bindActions(LaneActions);
this.lanes = [];
}
create(lane) {
...
}
update(updatedLane) {
this.setState({
lanes: this.lanes.map(lane => {
if(lane.id === updatedLane.id) {
return Object.assign({}, lane, updatedLane);
}
return lane;
})
});
}
...
}
After these changes you should be able to edit lane names. Lane deletion is a good feature to sort out next.
Lane
Deletion#Deleting lanes is a similar problem. We need to extend the user interface, add an action, and attach logic associated to it.
The user interface is a natural place to start. Often it's a good idea to add some console.log
s in place to make sure your event handlers get triggered as your expect. It would be even better to write tests for those. That way you'll end up with a runnable specification. Here's how to add a stub for deleting lanes:
app/components/LaneHeader.jsx
...
export default connect(() => ({}), {
NoteActions,
LaneActions
})(({lane, LaneActions, NoteActions, ...props}) => {
...
const deleteLane = e => {
// Avoid bubbling to edit
e.stopPropagation();
LaneActions.delete(lane.id);
};
return (
<div className="lane-header" onClick={activateLaneEdit} {...props}>
<div className="lane-add-note">
<button onClick={addNote}>+</button>
</div>
<Editable className="lane-name" editing={lane.editing}
value={lane.name} onEdit={editName} />
<div className="lane-delete">
<button onClick={deleteLane}>x</button>
</div>
</div>
);
});
Again, we need to expand our action definition:
app/actions/LaneActions.js
import alt from '../libs/alt';
export default alt.generateActions(
'create', 'update', 'delete', 'attachToLane', 'detachFromLane'
);
And to finalize the implementation, let's add logic:
app/stores/LaneStore.js
import LaneActions from '../actions/LaneActions';
export default class LaneStore {
constructor() {
this.bindActions(LaneActions);
this.lanes = [];
}
create(lane) {
...
}
update(updatedLane) {
...
}
delete(id) {
this.setState({
lanes: this.lanes.filter(lane => lane.id !== id)
});
}
...
}
Assuming everything went correctly, you should be able to delete entire lanes now.
The current implementation contains one gotcha. Even though we are removing references to lanes, the notes they point remain. This is something that could be turned into a rubbish bin feature. Or we could perform cleanup as well. For the purposes of this application, we can leave the situation as is. It is something good to be aware of, though.
As we added Lanes
to the application, the styling went a bit off. Adjust as follows to make it a little nicer:
app/main.css
body {
background-color: cornsilk;
font-family: sans-serif;
}
.add-note {
background-color: #fdfdfd;
border: 1px solid #ccc;
}
.lane {
display: inline-block;
margin: 1em;
background-color: #efefef;
border: 1px solid #ccc;
border-radius: 0.5em;
min-width: 10em;
vertical-align: top;
}
.lane-header {
overflow: auto;
padding: 1em;
color: #efefef;
background-color: #333;
border-top-left-radius: 0.5em;
border-top-right-radius: 0.5em;
}
.lane-name {
float: left;
}
.lane-add-note {
float: left;
margin-right: 0.5em;
}
.lane-delete {
float: right;
margin-left: 0.5em;
visibility: hidden;
}
.lane-header:hover .lane-delete {
visibility: visible;
}
.add-lane, .lane-add-note button {
cursor: pointer;
background-color: #fdfdfd;
border: 1px solid #ccc;
}
.lane-delete button {
padding: 0;
cursor: pointer;
color: white;
background-color: rgba(0, 0, 0, 0);
border: 0;
}
...
You should end up with a result like this:
As this is a small project, we can leave the CSS in a single file like this. In case it starts growing, consider separating it to multiple files. One way to do this is to extract CSS per component and then refer to it there (e.g., require('./lane.css')
at Lane.jsx
). You could even consider using CSS Modules to make your CSS default to local scope. See the Styling React chapter for further ideas.
Even though our application is starting to look good and has basic functionality in it, it's still missing perhaps the most vital feature. We still cannot move notes between lanes. This is something we will resolve in the next chapter as we implement drag and drop.
This book is available through Leanpub. By purchasing the book you support the development of further content.