Skip to content

Commit 9dbf45a

Browse files
committedMay 8, 2020
Selectable includes working
1 parent 0bd74ac commit 9dbf45a

22 files changed

+398
-197
lines changed
 

‎.idea/.generators

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎.idea/codeStyles/codeStyleConfig.xml

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎.idea/dictionaries/dazmin.xml

+8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎.idea/jsLibraryMappings.xml

+6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎.idea/json_api_experiment.iml

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎.idea/misc.xml

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎.idea/vcs.xml

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎app/controllers/home_controller.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@ class HomeController < ApplicationController
22
before_action :authenticate_user!
33

44
def index
5-
render component: 'Home', props: { path: params[:path] || '/' }, tag: 'div', prerender: false
5+
render component: 'App', props: { path: params[:path] || '/' }, tag: 'div', prerender: false
66
end
77
end

‎app/javascript/components/App.js

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import {Router, ServerLocation} from "@reach/router";
2+
import React from "react";
3+
import {Provider} from "react-redux";
4+
import store from "../store";
5+
import Home from "./Home";
6+
7+
const App = ({ path }) => (
8+
<Provider store={store} >
9+
<ServerLocation url={path}>
10+
<Router>
11+
<Home path="/" />
12+
</Router>
13+
</ServerLocation>
14+
</Provider>
15+
);
16+
17+
export default App
+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import React from "react";
2+
3+
export const concatRelationshipAttribute = (
4+
data,
5+
relationship,
6+
attribute,
7+
options = {}
8+
) => {
9+
const { separator = ", ", fallback = "" } = options;
10+
const relationshipData = data.relationships[relationship].data.map(
11+
(element) => element.attributes[attribute]
12+
);
13+
return relationshipData.length > 0
14+
? relationshipData.join(separator)
15+
: fallback;
16+
};
17+
18+
const ColHeading = ({ label, disabled }) => !disabled ? <th key={label}>{label}</th> : null;
19+
20+
const ColContent = ({ data, colDefinition }) => {
21+
const { attribute, dataHandler } = colDefinition;
22+
if (dataHandler) {
23+
return <td>{dataHandler(data)}</td>;
24+
} else {
25+
return <td>{data.attributes[attribute]}</td>;
26+
}
27+
};
28+
29+
const Row = ({ data, definition }) => {
30+
return (
31+
<tr key={data.id}>
32+
{definition.map((colDefinition) => !colDefinition.disabled ? (
33+
<ColContent key={`${colDefinition.label}-${data.id}`} data={data} colDefinition={colDefinition} />
34+
) : null)}
35+
</tr>
36+
);
37+
};
38+
39+
const DataTable = ({ data = [], definition, loading = false }) => {
40+
return loading ? null : (
41+
<table>
42+
<thead>
43+
<tr>{definition.map(ColHeading)}</tr>
44+
</thead>
45+
<tbody>
46+
{data.map((row) => (
47+
<Row key={row.id} data={row} definition={definition} />
48+
))}
49+
</tbody>
50+
</table>
51+
);
52+
};
53+
54+
export default DataTable;

‎app/javascript/components/Home.js

+36-164
Original file line numberDiff line numberDiff line change
@@ -1,176 +1,48 @@
11
import React, { useState, useEffect } from "react";
2-
import axios from "axios";
3-
import { Router, Link, ServerLocation } from "@reach/router";
4-
import { Dialog } from "@reach/dialog";
5-
import "@reach/dialog/styles.css";
6-
7-
const Wow = () => (
8-
<div>
9-
<Link to={"/dang"}>Dang</Link>
10-
</div>
11-
);
12-
13-
const mapJsonApiElement = (el, included) => {
14-
const relationships = {};
15-
for (const key in el.relationships) {
16-
let relationship = el.relationships[key];
17-
if (typeof relationship.data === "undefined") {
18-
return el;
19-
}
20-
const mappedRelationship = relationship.data.map((relationEl) => {
21-
const target = included.find((include) => {
22-
return include.id === relationEl.id && include.type === relationEl.type;
23-
});
24-
return target;
25-
});
26-
relationships[key] = { ...relationship, data: mappedRelationship };
27-
}
28-
return { ...el, relationships };
29-
};
30-
31-
async function fetchJsonApi(endpoint, params = {}) {
32-
const {
33-
data: { data, included },
34-
} = await axios(endpoint, { params });
35-
if (Array.isArray(data)) {
36-
return data.map((el) => mapJsonApiElement(el, included));
37-
} else {
38-
return mapJsonApiElement(data, included);
39-
}
40-
}
41-
42-
const Dang = () => {
43-
const [users, setUsers] = useState([]);
2+
import DataTable, { concatRelationshipAttribute } from "./DataTable";
3+
import UserDetailModal from "./UserDetailModal";
4+
import {useFetchHellaRecords} from "../utils/jsonApiStuff";
5+
6+
const Home = () => {
7+
const [flavorsEnabled, setFlavorsEnabled] = useState(false);
8+
const [users, loading, setLoading] = useFetchHellaRecords("/api/users", {
9+
include: [
10+
flavorsEnabled ? 'flavors' : null,
11+
].filter(Boolean).join(',')
12+
})
4413
const [detailUserPath, setDetailUserPath] = useState(null);
4514

46-
useEffect(() => {
47-
async function fetchUsers() {
48-
const response = await fetchJsonApi("/api/users", {
49-
"fields[user]": "email",
50-
});
51-
return response;
52-
}
53-
fetchUsers().then(setUsers);
54-
}, []);
55-
5615
const closeDetailModal = () => setDetailUserPath(null);
57-
16+
17+
const toggleFlavors = () => {
18+
setLoading(true);
19+
setFlavorsEnabled(!flavorsEnabled);
20+
}
5821
return (
5922
<React.Fragment>
60-
<table>
61-
<thead>
62-
<tr>
63-
<th>Email</th>
64-
<th>Detail</th>
65-
</tr>
66-
</thead>
67-
<tbody>
68-
{users.map((user) => (
69-
<UserRow
70-
key={user.id}
71-
user={user}
72-
openUserDetail={setDetailUserPath}
73-
/>
74-
))}
75-
</tbody>
76-
</table>
23+
<fieldset>
24+
<legend>Options</legend>
25+
<label htmlFor="flavorsEnable"><input type="checkbox" checked={flavorsEnabled} onChange={toggleFlavors}/>Show flavors?</label>
26+
</fieldset>
27+
<DataTable
28+
data={users}
29+
loading={loading}
30+
definition={[
31+
{label: "Email", attribute: "email"},
32+
{
33+
label: "Flavors",
34+
disabled: !flavorsEnabled,
35+
dataHandler: data => concatRelationshipAttribute(data, "flavors", "description")
36+
},
37+
{
38+
label: "View User",
39+
dataHandler: data => <button onClick={() => setDetailUserPath(data.links.self)}>Dang </button>,
40+
}
41+
]}
42+
/>
7743
<UserDetailModal userPath={detailUserPath} onClose={closeDetailModal} />
7844
</React.Fragment>
7945
);
8046
};
8147

82-
const UserRow = ({ user, openUserDetail }) => {
83-
const {
84-
attributes: { email },
85-
links: { self },
86-
} = user;
87-
return (
88-
<tr>
89-
<td>{email}</td>
90-
<td>
91-
<button onClick={() => openUserDetail(self)}>!!!</button>
92-
</td>
93-
</tr>
94-
);
95-
};
96-
97-
const useFetchSingleRecord = (recordPath, params = {}) => {
98-
const [record, setRecord] = useState(null);
99-
useEffect(() => {
100-
async function fetchRecord() {
101-
const response = await fetchJsonApi(recordPath, params);
102-
return response;
103-
}
104-
if (recordPath !== null) {
105-
fetchRecord().then(setRecord);
106-
} else {
107-
setRecord(null);
108-
}
109-
}, [recordPath]);
110-
111-
return record;
112-
};
113-
114-
const UserDetailModal = ({ userPath, onClose }) => {
115-
const params = {
116-
include: "things,flavors",
117-
"fields[user]": "email,role,secret",
118-
"fields[things]": "kinda",
119-
"fields[flavors]": "description",
120-
};
121-
const user = useFetchSingleRecord(userPath, params);
122-
123-
const close = () => {
124-
onClose();
125-
setUser(null);
126-
};
127-
128-
if (user !== null) {
129-
const {
130-
attributes: { email, role, secret },
131-
relationships: { things, flavors },
132-
} = user;
133-
134-
const kindaThings = things.data.map((thing) => thing.attributes.kinda);
135-
const flavorDescriptions = flavors.data.map(
136-
(flavor) => flavor.attributes.description
137-
);
138-
139-
return (
140-
<Dialog onDismiss={close}>
141-
<p>Email:</p>
142-
<p>{email}</p>
143-
<p>Role:</p>
144-
<p>{role}</p>
145-
<p>Kinda Things:</p>
146-
<p>
147-
{kindaThings.length > 0
148-
? kindaThings.join(", ")
149-
: "Ain't got no things."}
150-
</p>
151-
<p>Flavors:</p>
152-
<p>{flavorDescriptions.join(", ")}</p>
153-
{typeof secret !== "undefined" ? (
154-
<React.Fragment>
155-
<p>Secret:</p>
156-
<p>{secret}</p>
157-
</React.Fragment>
158-
) : null}
159-
<button onClick={close}>Kay.</button>
160-
</Dialog>
161-
);
162-
} else {
163-
return null;
164-
}
165-
};
166-
167-
const Home = ({ path }) => (
168-
<ServerLocation url={path}>
169-
<Router>
170-
<Wow path="/" />
171-
<Dang path="/dang" />
172-
</Router>
173-
</ServerLocation>
174-
);
175-
17648
export default Home;

‎app/javascript/components/RR.js

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import React from "react";
2+
3+
const User = ReactiveRecord.model("User")
4+
function Users() {
5+
return (
6+
<Collection for={User}>
7+
{ users => {
8+
console.log(users)
9+
return (
10+
<div>
11+
{users.map(user => <span>{user.id}</span>)}
12+
</div>
13+
);
14+
}}
15+
</Collection>
16+
)
17+
}

0 commit comments

Comments
 (0)
Please sign in to comment.