@kaspernj/api-maker 1.0.230 → 1.0.231
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/base-model/scope.mjs +12 -0
- package/src/base-model.mjs +36 -0
- package/src/bootstrap/card.jsx +11 -16
- package/src/collection-loader.jsx +16 -9
- package/src/collection.mjs +10 -7
- package/src/table/filters/filter-form.jsx +277 -0
- package/src/table/filters/index.jsx +95 -0
- package/src/table/table.jsx +30 -1
package/package.json
CHANGED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {digg} from "diggerize"
|
|
2
|
+
import inflection from "inflection"
|
|
3
|
+
|
|
4
|
+
export default class ApiMakerBaseModelScope {
|
|
5
|
+
constructor(scopeData) {
|
|
6
|
+
this.scopeData = scopeData
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
name() {
|
|
10
|
+
return inflection.camelize(digg(this, "scopeData", "name"), true)
|
|
11
|
+
}
|
|
12
|
+
}
|
package/src/base-model.mjs
CHANGED
|
@@ -11,6 +11,7 @@ import ModelName from "./model-name.mjs"
|
|
|
11
11
|
import NotLoadedError from "./not-loaded-error.mjs"
|
|
12
12
|
import objectToFormData from "object-to-formdata"
|
|
13
13
|
import Reflection from "./base-model/reflection.mjs"
|
|
14
|
+
import Scope from "./base-model/scope.mjs"
|
|
14
15
|
import Services from "./services.mjs"
|
|
15
16
|
import ValidationError from "./validation-error.mjs"
|
|
16
17
|
import {ValidationErrors} from "./validation-errors.mjs"
|
|
@@ -84,6 +85,41 @@ export default class BaseModel {
|
|
|
84
85
|
return new Collection({modelClass: this}, {ransack: query})
|
|
85
86
|
}
|
|
86
87
|
|
|
88
|
+
static ransackableAssociations() {
|
|
89
|
+
const relationships = digg(this.modelClassData(), "ransackable_associations")
|
|
90
|
+
const reflections = []
|
|
91
|
+
|
|
92
|
+
for (const relationshipData of relationships) {
|
|
93
|
+
reflections.push(new Reflection(relationshipData))
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return reflections
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
static ransackableAttributes() {
|
|
100
|
+
const attributes = digg(this.modelClassData(), "ransackable_attributes")
|
|
101
|
+
const result = []
|
|
102
|
+
|
|
103
|
+
for (const attributeData of attributes) {
|
|
104
|
+
result.push(new Attribute(attributeData))
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return result
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
static ransackableScopes() {
|
|
111
|
+
const ransackableScopes = digg(this.modelClassData(), "ransackable_scopes")
|
|
112
|
+
const result = []
|
|
113
|
+
|
|
114
|
+
for (const scopeData of ransackableScopes) {
|
|
115
|
+
const scope = new Scope(scopeData)
|
|
116
|
+
|
|
117
|
+
result.push(scope)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return result
|
|
121
|
+
}
|
|
122
|
+
|
|
87
123
|
static reflections() {
|
|
88
124
|
const relationships = digg(this.modelClassData(), "relationships")
|
|
89
125
|
const reflections = []
|
package/src/bootstrap/card.jsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import classNames from "classnames"
|
|
2
|
+
import {digg, digs} from "diggerize"
|
|
2
3
|
import PropTypes from "prop-types"
|
|
3
4
|
import React from "react"
|
|
4
5
|
|
|
@@ -48,11 +49,14 @@ export default class ApiMakerBootstrapCard extends React.PureComponent {
|
|
|
48
49
|
...restProps
|
|
49
50
|
} = this.props
|
|
50
51
|
const {expanded} = digs(this.state, "expanded")
|
|
52
|
+
const cardHeaderStyle = {display: "flex"}
|
|
53
|
+
|
|
54
|
+
if (!expanded) cardHeaderStyle["borderBottom"] = "0"
|
|
51
55
|
|
|
52
56
|
return (
|
|
53
|
-
<div className={
|
|
57
|
+
<div className={classNames("component-bootstrap-card", "card", "card-default", className)} data-has-footer={Boolean(footer)} ref="card" {...restProps}>
|
|
54
58
|
{(controls || expandable || header) &&
|
|
55
|
-
<div className=
|
|
59
|
+
<div className="card-header" style={cardHeaderStyle}>
|
|
56
60
|
<div style={{alignSelf: "center", marginRight: "auto"}}>
|
|
57
61
|
{header}
|
|
58
62
|
</div>
|
|
@@ -60,13 +64,13 @@ export default class ApiMakerBootstrapCard extends React.PureComponent {
|
|
|
60
64
|
<div style={{alignSelf: "center"}}>
|
|
61
65
|
{controls}
|
|
62
66
|
{expandable && expanded &&
|
|
63
|
-
<a className="collapse-card-button text-muted" href="#" onClick={this
|
|
64
|
-
<i className="
|
|
67
|
+
<a className="collapse-card-button text-muted" href="#" onClick={digg(this, "onCollapseClicked")}>
|
|
68
|
+
<i className="fa fa-angle-up" />
|
|
65
69
|
</a>
|
|
66
70
|
}
|
|
67
71
|
{expandable && !expanded &&
|
|
68
|
-
<a className="expand-card-button text-muted" href="#" onClick={this
|
|
69
|
-
<i className="
|
|
72
|
+
<a className="expand-card-button text-muted" href="#" onClick={digg(this, "onExpandClicked")}>
|
|
73
|
+
<i className="fa fa-angle-down" />
|
|
70
74
|
</a>
|
|
71
75
|
}
|
|
72
76
|
</div>
|
|
@@ -92,15 +96,6 @@ export default class ApiMakerBootstrapCard extends React.PureComponent {
|
|
|
92
96
|
)
|
|
93
97
|
}
|
|
94
98
|
|
|
95
|
-
classNames () {
|
|
96
|
-
const classNames = ["component-bootstrap-card", "card", "card-default"]
|
|
97
|
-
|
|
98
|
-
if (this.props.className)
|
|
99
|
-
classNames.push(this.props.className)
|
|
100
|
-
|
|
101
|
-
return classNames.join(" ")
|
|
102
|
-
}
|
|
103
|
-
|
|
104
99
|
bodyClassNames () {
|
|
105
100
|
const {expandableHide, responsiveTable, table} = digs(this.props, "expandableHide", "responsiveTable", "table")
|
|
106
101
|
const {expanded} = digs(this.state, "expanded")
|
|
@@ -4,7 +4,6 @@ import {digg, digs} from "diggerize"
|
|
|
4
4
|
import EventCreated from "./event-created"
|
|
5
5
|
import EventDestroyed from "./event-destroyed"
|
|
6
6
|
import EventUpdated from "./event-updated"
|
|
7
|
-
import instanceOfClassName from "./instance-of-class-name"
|
|
8
7
|
import {LocationChanged} from "on-location-changed/src/location-changed-component"
|
|
9
8
|
import Params from "./params"
|
|
10
9
|
import PropTypes from "prop-types"
|
|
@@ -54,9 +53,11 @@ export default class CollectionLoader extends React.PureComponent {
|
|
|
54
53
|
query: undefined,
|
|
55
54
|
queryName,
|
|
56
55
|
queryQName: `${queryName}_q`,
|
|
56
|
+
querySName: `${queryName}_s`,
|
|
57
57
|
queryPageName: `${queryName}_page`,
|
|
58
58
|
qParams: undefined,
|
|
59
59
|
result: undefined,
|
|
60
|
+
searchParams: undefined,
|
|
60
61
|
showNoRecordsAvailableContent: false,
|
|
61
62
|
showNoRecordsFoundContent: false
|
|
62
63
|
})
|
|
@@ -83,17 +84,26 @@ export default class CollectionLoader extends React.PureComponent {
|
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
loadQParams () {
|
|
86
|
-
const {queryQName} = digs(this.shape, "queryQName")
|
|
87
|
+
const {queryQName, querySName} = digs(this.shape, "queryQName", "querySName")
|
|
87
88
|
const params = Params.parse()
|
|
88
89
|
const qParams = Object.assign({}, this.props.defaultParams, params[queryQName])
|
|
90
|
+
const searchParams = []
|
|
89
91
|
|
|
90
|
-
|
|
92
|
+
if (params[querySName]) {
|
|
93
|
+
for (const rawSearchParam of params[querySName]) {
|
|
94
|
+
const parsedSearchParam = JSON.parse(rawSearchParam)
|
|
95
|
+
|
|
96
|
+
searchParams.push(parsedSearchParam)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
this.shape.set({qParams, searchParams})
|
|
91
101
|
}
|
|
92
102
|
|
|
93
103
|
loadModels = async () => {
|
|
94
104
|
const params = Params.parse()
|
|
95
105
|
const {abilities, collection, groupBy, modelClass, onModelsLoaded, preloads, select, selectColumns} = this.props
|
|
96
|
-
const {qParams, queryPageName, queryQName} = digs(this.shape, "qParams", "queryPageName", "queryQName")
|
|
106
|
+
const {qParams, queryPageName, queryQName, searchParams} = digs(this.shape, "qParams", "queryPageName", "queryQName", "searchParams")
|
|
97
107
|
|
|
98
108
|
let query = collection?.clone() || modelClass.ransack()
|
|
99
109
|
|
|
@@ -101,6 +111,7 @@ export default class CollectionLoader extends React.PureComponent {
|
|
|
101
111
|
|
|
102
112
|
query = query
|
|
103
113
|
.ransack(qParams)
|
|
114
|
+
.search(searchParams)
|
|
104
115
|
.searchKey(queryQName)
|
|
105
116
|
.page(params[queryPageName])
|
|
106
117
|
.pageKey(queryPageName)
|
|
@@ -151,11 +162,7 @@ export default class CollectionLoader extends React.PureComponent {
|
|
|
151
162
|
}
|
|
152
163
|
|
|
153
164
|
onLocationChanged = () => {
|
|
154
|
-
|
|
155
|
-
const params = Params.parse()
|
|
156
|
-
const qParams = Object.assign({}, this.props.defaultParams, params[queryQName])
|
|
157
|
-
|
|
158
|
-
this.shape.set({qParams})
|
|
165
|
+
this.loadQParams()
|
|
159
166
|
this.loadModels()
|
|
160
167
|
}
|
|
161
168
|
|
package/src/collection.mjs
CHANGED
|
@@ -124,6 +124,7 @@ export default class ApiMakerCollection {
|
|
|
124
124
|
if (this.queryArgs.preload) params.preload = this.queryArgs.preload
|
|
125
125
|
if (this.queryArgs.page) params.page = this.queryArgs.page
|
|
126
126
|
if (this.queryArgs.per) params.per = this.queryArgs.per
|
|
127
|
+
if (this.queryArgs.search) params.search = this.queryArgs.search
|
|
127
128
|
if (this.queryArgs.select) params.select = this.queryArgs.select
|
|
128
129
|
if (this.queryArgs.selectColumns) params.select_columns = this.queryArgs.selectColumns
|
|
129
130
|
|
|
@@ -139,10 +140,7 @@ export default class ApiMakerCollection {
|
|
|
139
140
|
}
|
|
140
141
|
|
|
141
142
|
ransack (params) {
|
|
142
|
-
if (params) {
|
|
143
|
-
this._merge({ransack: params})
|
|
144
|
-
}
|
|
145
|
-
|
|
143
|
+
if (params) this._merge({ransack: params})
|
|
146
144
|
return this
|
|
147
145
|
}
|
|
148
146
|
|
|
@@ -157,11 +155,16 @@ export default class ApiMakerCollection {
|
|
|
157
155
|
return result
|
|
158
156
|
}
|
|
159
157
|
|
|
160
|
-
|
|
158
|
+
search(params) {
|
|
159
|
+
if (params) this._merge({search: params})
|
|
160
|
+
return this
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
searchKey(searchKey) {
|
|
161
164
|
return this._merge({searchKey})
|
|
162
165
|
}
|
|
163
166
|
|
|
164
|
-
select
|
|
167
|
+
select(originalSelect) {
|
|
165
168
|
const newSelect = {}
|
|
166
169
|
|
|
167
170
|
for (const originalModelName in originalSelect) {
|
|
@@ -180,7 +183,7 @@ export default class ApiMakerCollection {
|
|
|
180
183
|
return this._merge({select: newSelect})
|
|
181
184
|
}
|
|
182
185
|
|
|
183
|
-
selectColumns
|
|
186
|
+
selectColumns(originalSelect) {
|
|
184
187
|
const newSelect = {}
|
|
185
188
|
|
|
186
189
|
for (const originalModelName in originalSelect) {
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import Attribute from "../../base-model/attribute"
|
|
2
|
+
import {digs} from "diggerize"
|
|
3
|
+
import Input from "../../inputs/input"
|
|
4
|
+
import PropTypes from "prop-types"
|
|
5
|
+
import PropTypesExact from "prop-types-exact"
|
|
6
|
+
import React from "react"
|
|
7
|
+
import Reflection from "../../base-model/reflection"
|
|
8
|
+
import Select from "../../inputs/select"
|
|
9
|
+
import Services from "../../services.mjs"
|
|
10
|
+
import Shape from "set-state-compare/src/shape"
|
|
11
|
+
|
|
12
|
+
class AttributeElement extends React.PureComponent {
|
|
13
|
+
static propTypes = {
|
|
14
|
+
active: PropTypes.bool.isRequired,
|
|
15
|
+
attribute: PropTypes.instanceOf(Attribute).isRequired,
|
|
16
|
+
currentModelClass: PropTypes.func.isRequired,
|
|
17
|
+
fikter: PropTypes.object,
|
|
18
|
+
onClick: PropTypes.func.isRequired
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
render() {
|
|
22
|
+
const {active, attribute, currentModelClass} = digs(this.props, "active", "attribute", "currentModelClass")
|
|
23
|
+
const style = {}
|
|
24
|
+
|
|
25
|
+
if (active) style.fontWeight = "bold"
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<div onClick={digg(this, "onAttributeClicked")} style={style}>
|
|
29
|
+
{currentModelClass.humanAttributeName(inflection.camelize(attribute.name(), true))}
|
|
30
|
+
</div>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
onAttributeClicked = (e) => {
|
|
35
|
+
e.preventDefault()
|
|
36
|
+
|
|
37
|
+
this.props.onClick({attribute: digg(this, "props", "attribute")})
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
class ReflectionElement extends React.PureComponent {
|
|
42
|
+
static propTypes = {
|
|
43
|
+
currentModelClass: PropTypes.func.isRequired,
|
|
44
|
+
onClick: PropTypes.func.isRequired,
|
|
45
|
+
reflection: PropTypes.instanceOf(Reflection).isRequired
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
render() {
|
|
49
|
+
const {currentModelClass, reflection} = digs(this.props, "currentModelClass", "reflection")
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<div key={reflection.name()} onClick={digg(this, "onReflectionClicked")}>
|
|
53
|
+
{currentModelClass.humanAttributeName(reflection.name())}
|
|
54
|
+
</div>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
onReflectionClicked = (e) => {
|
|
59
|
+
e.preventDefault()
|
|
60
|
+
|
|
61
|
+
this.props.onClick({reflection: digg(this, "props", "reflection")})
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export default class ApiMakerTableFiltersRelationshipSelect extends React.PureComponent {
|
|
66
|
+
static propTypes = PropTypesExact({
|
|
67
|
+
filter: PropTypes.object,
|
|
68
|
+
modelClass: PropTypes.func.isRequired,
|
|
69
|
+
querySearchName: PropTypes.string.isRequired
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
shape = new Shape(this, {
|
|
73
|
+
attribute: this.currentModelClassFromPath(this.props.filter.p || []).ransackableAttributes().find((attribute) => attribute.name() == this.props.filter.a),
|
|
74
|
+
path: this.props.filter.p || [],
|
|
75
|
+
predicate: undefined,
|
|
76
|
+
predicates: undefined,
|
|
77
|
+
value: this.props.filter.v
|
|
78
|
+
})
|
|
79
|
+
valueInputRef = React.createRef()
|
|
80
|
+
|
|
81
|
+
componentDidMount() {
|
|
82
|
+
this.loadRansackPredicates()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async loadRansackPredicates() {
|
|
86
|
+
const response = await Services.current().sendRequest("Ransack::Predicates")
|
|
87
|
+
const predicates = digg(response, "predicates")
|
|
88
|
+
let currentPredicate
|
|
89
|
+
|
|
90
|
+
if (this.props.filter.pre) {
|
|
91
|
+
currentPredicate = predicates.find((predicate) => predicate.name == this.props.filter.pre)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
this.shape.set({
|
|
95
|
+
predicate: currentPredicate,
|
|
96
|
+
predicates
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
render() {
|
|
101
|
+
const {valueInputRef} = digs(this, "valueInputRef")
|
|
102
|
+
const currentModelClass = this.currentModelClass()
|
|
103
|
+
const {attribute, predicate, predicates, value} = digs(this.shape, "attribute", "predicate", "predicates", "value")
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<div className="api-maker--table--filters--relationship-select">
|
|
107
|
+
<form onSubmit={digg(this, "onSubmit")}>
|
|
108
|
+
<div>
|
|
109
|
+
{this.currentPathParts().map(({translation}, pathPartIndex) =>
|
|
110
|
+
<span key={`${pathPartIndex}-${translation}`}>
|
|
111
|
+
{pathPartIndex > 0 &&
|
|
112
|
+
<span style={{marginRight: "5px", marginLeft: "5px"}}>
|
|
113
|
+
-
|
|
114
|
+
</span>
|
|
115
|
+
}
|
|
116
|
+
{translation}
|
|
117
|
+
</span>
|
|
118
|
+
)}
|
|
119
|
+
</div>
|
|
120
|
+
<div style={{display: "flex"}}>
|
|
121
|
+
<div>
|
|
122
|
+
{this.sortedByName(this.reflectionsWithModelClass(currentModelClass.ransackableAssociations()), currentModelClass).map((reflection) =>
|
|
123
|
+
<ReflectionElement
|
|
124
|
+
currentModelClass={currentModelClass}
|
|
125
|
+
key={reflection.name()}
|
|
126
|
+
onClick={digg(this, "onReflectionClicked")}
|
|
127
|
+
reflection={reflection}
|
|
128
|
+
/>
|
|
129
|
+
)}
|
|
130
|
+
</div>
|
|
131
|
+
<div>
|
|
132
|
+
{this.sortedByName(currentModelClass.ransackableAttributes(), currentModelClass).map((attribute) =>
|
|
133
|
+
<AttributeElement
|
|
134
|
+
active={attribute.name() == this.shape.attribute?.name()}
|
|
135
|
+
attribute={attribute}
|
|
136
|
+
currentModelClass={currentModelClass}
|
|
137
|
+
key={attribute.name()}
|
|
138
|
+
onClick={digg(this, "onAttributeClicked")}
|
|
139
|
+
/>
|
|
140
|
+
)}
|
|
141
|
+
{currentModelClass.ransackableScopes().map((scope) =>
|
|
142
|
+
<div>
|
|
143
|
+
{scope.name()}
|
|
144
|
+
</div>
|
|
145
|
+
)}
|
|
146
|
+
</div>
|
|
147
|
+
<div>
|
|
148
|
+
{predicates &&
|
|
149
|
+
<Select
|
|
150
|
+
defaultValue={predicate?.name}
|
|
151
|
+
includeBlank
|
|
152
|
+
onChange={digg(this, "onPredicateChanged")}
|
|
153
|
+
options={predicates.map((predicate) => digg(predicate, "name"))}
|
|
154
|
+
/>
|
|
155
|
+
}
|
|
156
|
+
</div>
|
|
157
|
+
<div>
|
|
158
|
+
{attribute && predicate &&
|
|
159
|
+
<Input defaultValue={value} inputRef={valueInputRef} />
|
|
160
|
+
}
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
<div>
|
|
164
|
+
<Button disabled={!attribute || !predicate}>
|
|
165
|
+
{I18n.t("js.api_maker.table.filters.relationship_select.apply", {defaultValue: "Apply"})}
|
|
166
|
+
</Button>
|
|
167
|
+
</div>
|
|
168
|
+
</form>
|
|
169
|
+
</div>
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
currentModelClass() {
|
|
174
|
+
const {path} = digs(this.shape, "path")
|
|
175
|
+
|
|
176
|
+
return this.currentModelClassFromPath(path)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
currentModelClassFromPath(path) {
|
|
180
|
+
const {modelClass} = digs(this.props, "modelClass")
|
|
181
|
+
let currentModelClass = modelClass
|
|
182
|
+
|
|
183
|
+
for (const pathPart of path) {
|
|
184
|
+
currentModelClass = currentModelClass.ransackableAssociations().find((reflection) => reflection.name() == pathPart).modelClass()
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return currentModelClass
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
currentPathParts() {
|
|
191
|
+
const {modelClass} = digs(this.props, "modelClass")
|
|
192
|
+
const {path} = digs(this.shape, "path")
|
|
193
|
+
const result = []
|
|
194
|
+
let currentModelClass = modelClass
|
|
195
|
+
|
|
196
|
+
result.push({
|
|
197
|
+
modelClass,
|
|
198
|
+
translation: modelClass.modelName().human({count: 2})
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
for (const pathPart of path) {
|
|
202
|
+
const pathPartTranslation = currentModelClass.humanAttributeName(pathPart)
|
|
203
|
+
|
|
204
|
+
currentModelClass = currentModelClass.ransackableAssociations().find((reflection) => reflection.name() == pathPart).modelClass()
|
|
205
|
+
|
|
206
|
+
result.push({
|
|
207
|
+
modelClass: currentModelClass,
|
|
208
|
+
translation: pathPartTranslation
|
|
209
|
+
})
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return result
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
onAttributeClicked = ({attribute}) => {
|
|
216
|
+
this.shape.set({attribute})
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
onPredicateChanged = (e) => {
|
|
220
|
+
const chosenPredicateName = digg(e, "target", "value")
|
|
221
|
+
const predicate = this.shape.predicates.find((predicate) => predicate.name == chosenPredicateName)
|
|
222
|
+
|
|
223
|
+
this.shape.set({predicate})
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
onReflectionClicked = ({reflection}) => {
|
|
227
|
+
const newPath = this.shape.path.concat([reflection.name()])
|
|
228
|
+
|
|
229
|
+
this.shape.set({
|
|
230
|
+
attribute: undefined,
|
|
231
|
+
path: newPath
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
this.props.onPathChanged
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
onSubmit = (e) => {
|
|
238
|
+
e.preventDefault()
|
|
239
|
+
|
|
240
|
+
const {filter, querySearchName} = digs(this.props, "filter", "querySearchName")
|
|
241
|
+
const {attribute, path, predicate} = digs(this.shape, "attribute", "path", "predicate")
|
|
242
|
+
const {filterIndex} = digs(filter, "filterIndex")
|
|
243
|
+
const searchParams = Params.parse()[querySearchName] || {}
|
|
244
|
+
const value = digg(this, "valueInputRef", "current", "value")
|
|
245
|
+
|
|
246
|
+
searchParams[filterIndex] = JSON.stringify({
|
|
247
|
+
a: attribute.name(),
|
|
248
|
+
p: path,
|
|
249
|
+
pre: digg(predicate, "name"),
|
|
250
|
+
v: value
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
const newParams = {}
|
|
254
|
+
|
|
255
|
+
newParams[querySearchName] = searchParams
|
|
256
|
+
|
|
257
|
+
Params.changeParams(newParams)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
reflectionsWithModelClass(reflections) {
|
|
261
|
+
return reflections.filter((reflection) => {
|
|
262
|
+
try {
|
|
263
|
+
reflection.modelClass()
|
|
264
|
+
|
|
265
|
+
return true
|
|
266
|
+
} catch (error) {
|
|
267
|
+
return false
|
|
268
|
+
}
|
|
269
|
+
})
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
sortedByName(reflections, currentModelClass) {
|
|
273
|
+
return reflections.sort((a, b) =>
|
|
274
|
+
currentModelClass.humanAttributeName(a.name()).toLowerCase().localeCompare(currentModelClass.humanAttributeName(b.name()).toLowerCase())
|
|
275
|
+
)
|
|
276
|
+
}
|
|
277
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import PropTypes from "prop-types"
|
|
2
|
+
import React from "react"
|
|
3
|
+
import FilterForm from "./filter-form"
|
|
4
|
+
import Shape from "set-state-compare/src/shape"
|
|
5
|
+
import withQueryParams from "on-location-changed/src/with-query-params"
|
|
6
|
+
|
|
7
|
+
class ApiMakerTableFilter extends React.PureComponent {
|
|
8
|
+
static propTypes = {
|
|
9
|
+
a: PropTypes.string.isRequired,
|
|
10
|
+
filterIndex: PropTypes.number.isRequired,
|
|
11
|
+
onClick: PropTypes.func.isRequired,
|
|
12
|
+
p: PropTypes.array.isRequired,
|
|
13
|
+
pre: PropTypes.string.isRequired,
|
|
14
|
+
v: PropTypes.string.isRequired
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
render() {
|
|
18
|
+
const {a, p, pre, v} = digs(this.props, "a", "p", "pre", "v")
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div onClick={digg(this, "onFilterClicked")} style={{display: "inline-block", backgroundColor: "grey", padding: "10px 6px"}}>
|
|
22
|
+
{p.join(".")}.{a} {pre} {v}
|
|
23
|
+
</div>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
onFilterClicked = (e) => {
|
|
28
|
+
e.preventDefault()
|
|
29
|
+
|
|
30
|
+
const {a, filterIndex, p, pre, v} = digs(this.props, "a", "filterIndex", "p", "pre", "v")
|
|
31
|
+
|
|
32
|
+
this.props.onClick({a, filterIndex, p, pre, v})
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
class ApiMakerTableFilters extends React.PureComponent {
|
|
37
|
+
static propTypes = {
|
|
38
|
+
modelClass: PropTypes.func.isRequired,
|
|
39
|
+
queryName: PropTypes.string.isRequired,
|
|
40
|
+
queryParams: PropTypes.object.isRequired
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
shape = new Shape(this, {
|
|
44
|
+
filter: undefined
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
render() {
|
|
48
|
+
const {modelClass} = this.props
|
|
49
|
+
const {filter} = digs(this.shape, "filter")
|
|
50
|
+
const currentFilters = this.currentFilters()
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<div className="api-maker--table--filters--edit">
|
|
54
|
+
<button onClick={digg(this, "onAddFilterClicked")}>
|
|
55
|
+
{I18n.t("js.api_maker.table.filters.add_new_filter", {defaultValue: "Add new filter"})}
|
|
56
|
+
</button>
|
|
57
|
+
{filter &&
|
|
58
|
+
<FilterForm
|
|
59
|
+
filter={filter}
|
|
60
|
+
key={`filter-${filter.filterIndex}`}
|
|
61
|
+
modelClass={modelClass}
|
|
62
|
+
querySearchName={this.querySearchName()}
|
|
63
|
+
/>
|
|
64
|
+
}
|
|
65
|
+
{currentFilters?.map((filterData, filterIndex) =>
|
|
66
|
+
<ApiMakerTableFilter key={filterIndex} filterIndex={filterIndex} onClick={digg(this, "onFilterClicked")} {...JSON.parse(filterData)} />
|
|
67
|
+
)}
|
|
68
|
+
</div>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
currentFilters() {
|
|
73
|
+
const {queryParams} = this.props
|
|
74
|
+
const currentFilters = queryParams[this.querySearchName()] || []
|
|
75
|
+
|
|
76
|
+
return currentFilters
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
onAddFilterClicked = (e) => {
|
|
80
|
+
e.preventDefault()
|
|
81
|
+
|
|
82
|
+
const newFilterIndex = this.currentFilters().length
|
|
83
|
+
|
|
84
|
+
this.shape.set({
|
|
85
|
+
filter: {
|
|
86
|
+
filterIndex: newFilterIndex
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
onFilterClicked = (args) => this.shape.set({filter: args})
|
|
92
|
+
querySearchName = () => `${this.props.queryName}_s`
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export default withQueryParams(ApiMakerTableFilters)
|
package/src/table/table.jsx
CHANGED
|
@@ -6,6 +6,7 @@ import CollectionLoader from "../collection-loader"
|
|
|
6
6
|
import columnVisible from "./column-visible.mjs"
|
|
7
7
|
import {debounce} from "debounce"
|
|
8
8
|
import {digg, digs} from "diggerize"
|
|
9
|
+
import Filters from "./filters"
|
|
9
10
|
import inflection from "inflection"
|
|
10
11
|
import modelClassRequire from "../model-class-require.mjs"
|
|
11
12
|
import ModelRow from "./model-row"
|
|
@@ -93,6 +94,7 @@ class ApiMakerTable extends React.PureComponent {
|
|
|
93
94
|
queryPageName: `${queryName}_page`,
|
|
94
95
|
qParams: undefined,
|
|
95
96
|
result: undefined,
|
|
97
|
+
showFilters: false,
|
|
96
98
|
showNoRecordsAvailableContent: false,
|
|
97
99
|
showNoRecordsFoundContent: false
|
|
98
100
|
})
|
|
@@ -136,8 +138,10 @@ class ApiMakerTable extends React.PureComponent {
|
|
|
136
138
|
preload,
|
|
137
139
|
qParams,
|
|
138
140
|
query,
|
|
141
|
+
queryName,
|
|
139
142
|
result,
|
|
140
143
|
models,
|
|
144
|
+
showFilters,
|
|
141
145
|
showNoRecordsAvailableContent,
|
|
142
146
|
showNoRecordsFoundContent
|
|
143
147
|
} = digs(
|
|
@@ -146,8 +150,10 @@ class ApiMakerTable extends React.PureComponent {
|
|
|
146
150
|
"preload",
|
|
147
151
|
"qParams",
|
|
148
152
|
"query",
|
|
153
|
+
"queryName",
|
|
149
154
|
"result",
|
|
150
155
|
"models",
|
|
156
|
+
"showFilters",
|
|
151
157
|
"showNoRecordsAvailableContent",
|
|
152
158
|
"showNoRecordsFoundContent"
|
|
153
159
|
)
|
|
@@ -178,6 +184,9 @@ class ApiMakerTable extends React.PureComponent {
|
|
|
178
184
|
{noRecordsFoundContent({models, qParams, overallCount})}
|
|
179
185
|
</div>
|
|
180
186
|
}
|
|
187
|
+
{showFilters &&
|
|
188
|
+
<Filters modelClass={modelClass} queryName={queryName} />
|
|
189
|
+
}
|
|
181
190
|
{qParams && query && result && models && !showNoRecordsAvailableContent && !showNoRecordsFoundContent &&
|
|
182
191
|
this.cardOrTable()
|
|
183
192
|
}
|
|
@@ -270,6 +279,8 @@ class ApiMakerTable extends React.PureComponent {
|
|
|
270
279
|
controlsContent = controls({models, qParams, query, result})
|
|
271
280
|
}
|
|
272
281
|
|
|
282
|
+
controlsContent += this.tableControls()
|
|
283
|
+
|
|
273
284
|
if (typeof header == "function") {
|
|
274
285
|
headerContent = header({models, qParams, query, result})
|
|
275
286
|
} else if (header) {
|
|
@@ -299,7 +310,7 @@ class ApiMakerTable extends React.PureComponent {
|
|
|
299
310
|
this.filterForm()
|
|
300
311
|
}
|
|
301
312
|
{card &&
|
|
302
|
-
<Card className={classNames("mb-4", className)} controls={
|
|
313
|
+
<Card className={classNames("mb-4", className)} controls={this.tableControls()} header={headerContent} footer={this.tableFooter()} table={!this.isSmallScreen()} {...restProps}>
|
|
303
314
|
{this.tableContent()}
|
|
304
315
|
</Card>
|
|
305
316
|
}
|
|
@@ -342,6 +353,24 @@ class ApiMakerTable extends React.PureComponent {
|
|
|
342
353
|
)
|
|
343
354
|
}
|
|
344
355
|
|
|
356
|
+
tableControls() {
|
|
357
|
+
const {controls} = this.props
|
|
358
|
+
|
|
359
|
+
return (
|
|
360
|
+
<>
|
|
361
|
+
{controls && controls({models, qParams, query, result})}
|
|
362
|
+
<a href="#" onClick={digg(this, "onNewFilterClick")}>
|
|
363
|
+
<i className="fa fa-fw fa-magnifying-glass" />
|
|
364
|
+
</a>
|
|
365
|
+
</>
|
|
366
|
+
)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
onNewFilterClick = (e) => {
|
|
370
|
+
e.preventDefault()
|
|
371
|
+
this.shape.set({showFilters: !this.shape.showFilters})
|
|
372
|
+
}
|
|
373
|
+
|
|
345
374
|
tableContent () {
|
|
346
375
|
const {breakPoint} = digs(this.props, "breakPoint")
|
|
347
376
|
const {models, preparedColumns} = digs(this.shape, "models", "preparedColumns")
|