https://github.com/rootinc/crudable
https://github.com/rootinc/crudable
Last synced: 2 months ago
JSON representation
- Host: GitHub
- URL: https://github.com/rootinc/crudable
- Owner: rootinc
- Created: 2018-05-15T17:46:01.000Z (about 8 years ago)
- Default Branch: master
- Last Pushed: 2026-04-02T13:32:16.000Z (3 months ago)
- Last Synced: 2026-04-21T03:55:15.912Z (2 months ago)
- Language: JavaScript
- Size: 256 KB
- Stars: 0
- Watchers: 4
- Forks: 0
- Open Issues: 4
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# CRUDable
Provides CRUDable interfaces with React.
## Installation
1. `npm i github:rootinc/CRUDable`
2. :shipit:
## Usage
### User Example
**User.js**
```
import React, { Component } from 'react';
import {CRUDable, defaultPlaceholderId} from 'CRUDable';
export default class User extends CRUDable {
renderReadOnly = () => {
return (
{
const key = e.target.getAttribute('data-key');
this.switchToWriteOnly(key);
}}
>
{this.props.data.email}
{this.props.data.first_name}
{this.props.data.last_name}
);
}
renderWriteOnly = (e) => {
return (
{this.handleChange(e, 'email'); }}
onBlur={(e) => {this.handleBlur(e, 'email'); }}
onClick={this.handleClick}
/>
{this.handleChange(e, 'first_name'); }}
onBlur={(e) => {this.handleBlur(e, 'first_name'); }}
onClick={this.handleClick}
/>
{this.handleChange(e, 'last_name'); }}
onBlur={(e) => {this.handleBlur(e, 'last_name'); }}
onClick={this.handleClick}
/>
{
this.props.data.id < defaultPlaceholderId ? null : this.renderCreateButton("Create User")
}
{
this.props.data.id < defaultPlaceholderId ? this.renderDeleteButton("Delete User") : null
}
);
}
}
```
**UserManagement.js**
```
import React, { Component } from 'react';
import User from './User';
import {CRUDableManagement} from 'CRUDable';
export default class UserManagement extends CRUDableManagement {
renderList = () => {
return this.props.list.map((user)=>{
return (
);
});
}
renderContainer = () => {
return (
Email
First Name
Last Name
{this.renderList()}
);
}
render() {
return (
{this.renderAddButton("Add User")}
{this.renderContainer()}
);
}
}
```
**UserManagementContainer.js**
```
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import update from 'immutability-helper';
import UserManagement from './UserManagement';
class UserManagementContainer extends Component {
constructor(props) {
super(props);
this.state = {
users: []
};
}
componentWillMount() {
this.fetchUsers();
}
fetchUsers = () => {
window.axios.get("/api/users")
.then((response) => {
this.setState({
users: response.data.payload.users
});
})
.catch((error) => {
console.error(error);
});
}
revertUsers = (index) => {
return update(this.state.users, {
$splice: [[index, 1]]
});
}
modifyList = (type, users, user, index) => {
switch (type)
{
case 'POST':
this.postData(user, index);
break;
case 'PUT':
this.putData(user);
break;
case 'DELETE':
this.deleteData(user);
break;
}
this.setState({users: users});
}
postData = (postData, index) => {
window.axios.post("/api/users", postData)
.then((response) => {
if (response.data.status === "error")
{
console.error(response);
this.setState({
users: this.revertUsers(index)
});
}
else
{
const usersWithNewUser = update(this.state.users, {
[index]: {
$merge: response.data.payload.user
}
});
this.setState({users: usersWithNewUser});
}
})
.catch((error) => {
console.error(error);
this.setState({
users: this.revertUsers(index)
});
});
}
putData = (putData) => {
window.axios.put("/api/users/" + putData.id, putData)
.then((response) => {
if (response.data.status === "error")
{
console.error(response);
}
})
.catch((error) => {
console.error(error);
});
}
deleteData = (deleteData) => {
window.axios.delete("/api/users/" + deleteData.id, deleteData)
.then((response) => {
if (response.data.status === "error")
{
console.error(response);
}
})
.catch((error) => {
console.error(error);
});
}
render() {
return (
);
}
}
export default UserManagementContainer;
const usersElement = document.getElementById('users');
if (usersElement) {
ReactDOM.render(, usersElement);
}
```
### Another (More Complicated) Example
**Commitment.js**
```
import React, { Component } from 'react';
import {CRUDable, defaultPlaceholderId} from 'CRUDable';
export default class Commitment extends CRUDable {
renderReadOnly = () => {
let className = "";
if (!this.props.data.favorite) {
className = "-o";
}
return (
{
this.handleChange({target: {value: !this.props.data.favorite}}, 'favorite', true)
}}
style={{
color: "goldenrod",
fontSize: "1rem"
}}
/>
{
if (this.props.canEdit)
{
const key = e.target.getAttribute('data-key');
this.switchToWriteOnly(key);
}
}}
style={{
flex: "1",
backgroundColor: this.props.color || this.props.data.compass_module.color,
color: "white",
borderRadius: "0.25rem",
marginLeft: "0.25rem",
padding: "0.5rem"
}}
title="Click to edit"
data-key="text"
>
{this.props.data.text}
);
}
renderWriteOnly = () => {
return (
{this.handleChange(e, 'text'); }}
onBlur={(e) => {this.handleBlur(e, 'text'); }}
onClick={this.handleClick}
onKeyDown={(e) => {
if (e.keyCode === 13)
{
e.preventDefault();
if (this.props.data.text != "")
{
this.handleCreate();
}
}
}}
/>
{
this.props.data.id < defaultPlaceholderId ? (
{this.renderDeleteButton("Delete")}
) : null
}
Hit Enter to Submit
);
}
}
```
**CommitmentManagement.js**
```
import React, { Component } from 'react';
import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd';
import Commitment from './Commitment';
import {CRUDableManagement} from 'CRUDable';
const reorder = (list, startIndex, endIndex) => {
const result = Array.from(list);
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
};
export default class CommitmentManagement extends CRUDableManagement {
droppedOutsideOfList = (result) => {
return !result.destination;
}
onDragEnd = (result) => {
if (this.droppedOutsideOfList(result)) {
return;
}
let list = reorder(this.props.list, result.source.index, result.destination.index);
for (let i=0; i {
return (
);
}
renderDraggableList = () => {
return this.props.list.map((commitment, index)=>{
return (
{(provided, snapshot) => (
{this.renderCommitment(commitment, false)}
)}
);
});
}
renderDnDContainer = () => {
return (
{(provided, snapshot) => (
{this.renderDraggableList()}
)}
);
}
renderList = () => {
return this.props.list.map((commitment)=>{
return this.renderCommitment(commitment, true);
});
}
renderContainer = () => {
return (
{this.renderList()}
);
}
render() {
return (
{this.props.orderable ? this.renderDnDContainer() : this.renderContainer()}
);
}
}
```
**Commitments.js**
```
import React, { Component } from 'react';
import update from 'immutability-helper';
import CommitmentManagement from './CommitmentManagement';
import {defaultPlaceholderId, getKey} from 'CRUDable';
export default class Commitments extends Component {
modifyList = (type, commitments, commitment, index) => {
this.props.modifyCommitments(commitments);
switch (type)
{
case 'POST':
this.postData(commitment, index);
break;
case 'PUT':
this.putData(commitment);
break;
case 'DELETE':
this.deleteData(commitment);
break;
}
}
postData = (postData, index) => {
window.axios.post("/api/commitments", postData)
.then((response) => {
let type;
if (this.props.addPlaceholderTo == "top")
{
type = "$unshift";
}
else if (this.props.addPlaceholderTo == "bottom")
{
type = "$push";
}
else
{
type = "$unshift";
}
let commitmentsWithNewCommitmentAndPlaceholder = update(this.props.commitments, {
[index]: {
$merge: response.data.payload.commitment,
},
[type]: [{ //prepend or append placeholder commitments since there is no add button
id: getKey(),
compass_module_id: this.props.compass_module_id,
text: ""
}]
});
this.props.modifyCommitments(commitmentsWithNewCommitmentAndPlaceholder);
})
.catch(function (error) {
console.error(error);
});
}
putData = (putData) => {
window.axios.put("/api/commitments/" + putData.id, putData)
.then(() => {
this.props.refreshFavorites && this.props.refreshFavorites();
})
.catch(function (error) {
console.error(error);
});
}
deleteData = (deleteData) => {
window.axios.delete("/api/commitments/" + deleteData.id)
.catch(function (error) {
console.error(error);
});
}
render() {
return (
{this.props.title}
);
}
}
```
**CommitmentsContainer.js**
```
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import update from 'immutability-helper';
import styled from 'styled-components';
import Commitments from './commitments/Commitments';
import CommitmentManagement from './commitments/CommitmentManagement';
import {defaultPlaceholderId, getKey} from 'CRUDable';
class CommitmentsContainer extends Component {
constructor(props) {
super(props);
this.state = {
compassModulesWithCommitments: [],
favoriteCommitments: [],
};
}
componentWillMount() {
this.fetchCompassModulesWithCommitments();
this.fetchFavoriteCommitments();
}
fetchCompassModulesWithCommitments = () => {
window.axios.get("/api/commitments")
.then((response) => {
this.prependPlacholderCommitments(response.data.payload.compass_modules);
this.setState({
compassModulesWithCommitments: response.data.payload.compass_modules,
});
})
.catch((error) => {
console.error(error);
});
}
fetchFavoriteCommitments = () => {
window.axios.get("/api/commitments/favorites")
.then((response) => {
this.setState({
favoriteCommitments: response.data.payload.commitments,
});
})
.catch((error) => {
console.error(error);
});
}
prependPlacholderCommitments = (compassModules) => {
for (let i=0; i {
return this.state.compassModulesWithCommitments.findIndex((cm) => {
return moduleId === cm.id;
});
}
modifyCommitments = (moduleId, commitments) => {
var index = this.findCompassModuleIndex(moduleId);
let commitmentsMerged = update(this.state.compassModulesWithCommitments, {
[index]: {
commitments: {
$set: commitments
}
}
});
this.setState({compassModulesWithCommitments: commitmentsMerged});
}
modifyFavoriteCommitments = (type, favoriteCommitments, favoriteCommitment, index) => {
window.axios.put("/api/commitments/" + favoriteCommitment.id, favoriteCommitment)
.then(() => {
this.fetchCompassModulesWithCommitments();
})
.catch(function (error) {
alert(window._genericErrorMessage);
});
const listRemovedObject = update(favoriteCommitments, {
$splice: [[index, 1]]
});
this.setState({favoriteCommitments: listRemovedObject});
}
updateFavoriteCommitmentsOrder = (list) => {
window.axios.post("/api/commitments/favorites/reorder", {list: list})
.catch(function (error) {
alert(window._genericErrorMessage);
});
this.setState({favoriteCommitments: list});
}
renderFavoriteCommitments = () => {
return (
Favorites
);
}
renderCommitments = () => {
return this.state.compassModulesWithCommitments.map((compassModule) => {
return (
{
this.modifyCommitments(compassModule.id, commitments);
}}
refreshFavorites={this.fetchFavoriteCommitments}
addPlaceholderTo="top"
className="className"
color={compassModule.color}
/>
);
});
}
render() {
return (
Commitments
{this.renderFavoriteCommitments()}
{this.renderCommitments()}
);
}
}
export default CommitmentsContainer;
const commitmentElement = document.getElementById('commitments');
if (commitmentElement) {
ReactDOM.render(, commitmentElement);
}
```
## Contributing
Thank you for considering contributing to CRUDable! To encourage active collaboration, we encourage pull requests, not just issues.
If you file an issue, the issue should contain a title and a clear description of the issue. You should also include as much relevant information as possible and a code sample that demonstrates the issue. The goal of a issue is to make it easy for yourself - and others - to replicate the bug and develop a fix.
## License
CRUDable is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT).