├── .gitignore
├── .npmignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE.md
├── MIGRATION.md
├── README.md
├── package.json
├── src
├── __tests__
│ ├── EasyLoadMoreAgent.ts
│ ├── EasyLoaderAgent.ts
│ └── enzyme.config.ts
├── agents
│ ├── EasyLoadMoreAgent.ts
│ ├── EasyLoaderAgent.ts
│ ├── EasyPagerAgent.ts
│ └── index.ts
├── atoms
│ ├── EasyFilters
│ │ ├── EasyFilters.tsx
│ │ ├── index.tsx
│ │ └── tools.ts
│ ├── EasyList
│ │ ├── EasyList.tsx
│ │ ├── index.tsx
│ │ └── registry.tsx
│ ├── EasyLoadMore
│ │ ├── EasyLoadMore.tsx
│ │ ├── index.tsx
│ │ └── registry.tsx
│ ├── EasyPager
│ │ ├── EasyPager.tsx
│ │ ├── index.tsx
│ │ └── registry.tsx
│ ├── EasyTable
│ │ ├── EasyTable.tsx
│ │ ├── EasyTableBody.tsx
│ │ ├── EasyTableHeader.tsx
│ │ ├── defs.ts
│ │ ├── index.ts
│ │ └── registry.tsx
│ └── tools.ts
├── index.ts
└── vendor
│ └── pagination
│ ├── BreakView.tsx
│ ├── LICENSE.md
│ ├── PageView.tsx
│ ├── PaginationBoxView.tsx
│ └── index.ts
├── tsconfig.json
└── tslint.json
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .vscode
3 | coverage
4 | dist
5 | node_modules
6 | npm-debug.log
7 | package-lock.json
8 | typings
9 | yarn-error.log
10 | yarn.lock
11 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | src
2 | node_modules
3 | typings
4 | tsconfig.json
5 | typings.json
6 | tslint.json
7 | dist/test
8 | yarn.lock
9 | coverage
10 | .vscode
11 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '8'
4 |
5 | install:
6 | - npm install -g coveralls
7 | - npm install react-molecule mobx@5 mobx-react # Peer deps
8 | - npm install
9 |
10 | script:
11 | - npm test
12 | - npm run coverage
13 | - coveralls < ./coverage/lcov.info || true # ignore coveralls error
14 |
15 | # Allow Travis tests to run in containers.
16 | # sudo: false
17 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2018 Theodor Diaconu
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/MIGRATION.md:
--------------------------------------------------------------------------------
1 | # 0.2.0
2 |
3 | - In the table model you no longer have `actions`, use a `field` with a custom render
4 | - The pagination options have changed we are now passing: `total`, `currentPage`, `perPage`, `onPagechange`, adapt
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Easify
2 |
3 | [](https://travis-ci.org/cult-of-coders/easify)
4 | [](https://github.com/prettier/prettier)
5 |
6 | Easify is API Agnostic and Extremely Hackable and helps you do the following:
7 |
8 | - Creating simple lists with filters
9 | - Creating tables with sort, pagination, search-bars, custom filters
10 | - Creating load more lists
11 |
12 | ...by being **completely agnostic** to the way you fetch your data.
13 |
14 | Start toying around with it here: https://codesandbox.io/s/2l5lvl1nn
15 |
16 | ## Install
17 |
18 | The peer dependencies:
19 | `npm install --save react-molecule mobx mobx-react simpl-schema`
20 |
21 | The package:
22 | `npm install --save easify`
23 |
24 | ## The Agents
25 |
26 | [Agents](https://github.com/cult-of-coders/react-molecule/blob/master/docs/API.md#agent) are the part where we store logic. Easify brings you the mighty 3:
27 |
28 | - Loader - handles data loading and updates his own internal store
29 | - Pager - modifies options on Loader so that it behaves correctly
30 | - LoadMore - modifies options on Loader so that it behaves correctly
31 |
32 | A simple example is illustrated below, how from a `Promise` we render our data.
33 |
34 | ```jsx
35 | import { molecule } from "react-molecule";
36 | import { EasyLoaderAgent, EasyList } from "easify";
37 |
38 | // We have to pass a way to load the object to our easy loader, and that function needs to return a promise.
39 | const load = ({ filters, options }) =>
40 | // The filters and options here can be from other agents
41 | // Based on these filters you perform the api requests
42 | // For pagination inside options we pass {limit, skip}
43 | new Promise((resolve, reject) => {
44 | setTimeout(() => {
45 | resolve([
46 | {
47 | _id: "XXX",
48 | name: "John Smith"
49 | },
50 | {
51 | _id: "YYY",
52 | name: "John Brown"
53 | }
54 | ]);
55 | }, 500);
56 | });
57 |
58 | const MyList = molecule(
59 | () => {
60 | return {
61 | loader: EasyLoaderAgent.factory({ load })
62 | };
63 | },
64 | () => (
65 |
66 | {({ data, loading, molecule }) => {
67 | return data.map(item => );
68 | }}
69 |
70 | )
71 | );
72 | ```
73 |
74 | ## Pagination
75 |
76 | To add pagination, we need a way of counting the data, based on the same filters that data is being loaded against.
77 |
78 | ```jsx
79 | import { EasyLoaderAgent, EasyPagerAgent, EasyList, EasyPager } from "easify";
80 |
81 | const count = filters =>
82 | new Promise(({ resolve, reject }) => {
83 | setTimeout(() => {
84 | resolve(10);
85 | }, 500);
86 | });
87 |
88 | const MyList = molecule(
89 | () => ({
90 | loader: EasyLoaderAgent.factory({ load }),
91 | pager: EasyPagerAgent.factory({ count, perPage: 10 })
92 | }),
93 | () => {
94 | return (
95 | <>
96 |
97 |
98 | {({ data, loading, molecule }) => {
99 | return data.map(item => );
100 | }}
101 |
102 | >
103 | );
104 | }
105 | );
106 | ```
107 |
108 | ## Load More
109 |
110 | If we want to build a list that contains load more, we simply swap-out Pager, and plug-in LoadMore agents and atoms.
111 |
112 | ```jsx
113 | import {
114 | EasyLoaderAgent,
115 | EasyLoadMoreAgent,
116 | EasyList,
117 | EasyPager
118 | } from "easify";
119 |
120 | const MyList = molecule(
121 | () => {
122 | return {
123 | agents: {
124 | loader: EasyLoaderAgent.factory({ load }),
125 | loadMore: EasyLoadMoreAgent.factory({
126 | count,
127 | initialItemsCount: 20,
128 | loadItemsCount: 10
129 | })
130 | }
131 | };
132 | },
133 | () => {
134 | return (
135 | <>
136 |
137 | {({ data, loading, molecule }) => {
138 | return data.map(item => );
139 | }}
140 |
141 |
142 |
143 | >
144 | );
145 | }
146 | );
147 | ```
148 |
149 | ## EasyTable & Pager
150 |
151 | A place where this package shows the powers of Molecule is when you build a reactive complex table.
152 |
153 | `EasyTable` is a component that accepts a `model` which understands how to render the fields, and it also supports sorting.
154 |
155 | ```jsx
156 | import {
157 | EasyLoaderAgent,
158 | EasyLoadMoreAgent,
159 | EasyList,
160 | EasyPagerAgent
161 | } from "easify";
162 |
163 | const tableModel = {
164 | // React needs to know the key of an array, so we need to uniquely identify an object
165 | key({ object }) {
166 | return object._id;
167 | },
168 |
169 | fields: [
170 | {
171 | label: "First Name",
172 | resolve: "firstName",
173 | sort: "firstName" // The field it should sort on, if not specified it will not have sorting ability
174 | },
175 | {
176 | label: "Id",
177 | // Resolve can also be a function that returns a React renderable
178 | resolve({ object }) {
179 | return {object._id};
180 | }
181 | },
182 | {
183 | label: "Actions",
184 | resolve({ object }) {
185 | return Edit;
186 | }
187 | }
188 | ]
189 | };
190 |
191 | const MyList = molecule(
192 | () => {
193 | return {
194 | agents: {
195 | loader: EasyLoaderAgent.factory({ load }),
196 | pager: EasyPagerAgent.factory({ count })
197 | }
198 | };
199 | },
200 | () => {
201 | return (
202 | <>
203 |
204 |
205 | >
206 | );
207 | }
208 | );
209 | ```
210 |
211 | ## Filtering your data
212 |
213 | Now it's time to filter, whether you want to search stuff or just have a bar with all sorts of filtering, you want this to be done nicely. Note that due to `react-molecule` nature, EasyFilters work with `EasyList` and `EasyTable` and `Pager`
214 |
215 | We'll show first a simple example where we search stuff in an input:
216 |
217 | ```jsx
218 | const MyList = () => {
219 | return (
220 | <>
221 |
222 | {({ doFilter }) => {
223 | return (
224 | doFilter({ name: e.target.value })}
227 | />
228 | );
229 | }}
230 |
231 |
232 |
233 |
234 | >
235 | );
236 | };
237 | ```
238 |
239 | The `doFilter` function will override the current existing filters on the LoaderAgent. And they will end-up in your `load()` function to reload the data again.
240 |
241 | EasyFilters also supports `simpl-schema` definitions. Which can make it work very nicely with [uniforms](https://github.com/vazco/uniforms)
242 |
243 | ```jsx
244 | import SimpleSchema from "simpl-schema";
245 | import { AutoForm } from "uniforms/bootstrap4";
246 |
247 | const FilterSchema = new SimpleSchema({
248 | firstName: {
249 | type: String,
250 | optional: true,
251 | easify: {
252 | toFilter(value) {
253 | return {
254 | firstName: {
255 | $regex: value,
256 | $options: "i"
257 | }
258 | };
259 | }
260 | }
261 | }
262 | });
263 |
264 | // We're passing the schema to EasyFilters as well, because it can read from the `toFilter`
265 | // This makes your life super easy when you have large filter forms, or even when you have simple ones
266 | // No matter how you choose to position them
267 | const MyList = () => {
268 | return (
269 | <>
270 |
271 | {({ onSubmit }) => (
272 | // This form is created from your Schema
273 |
274 | )}
275 |
276 |
277 |
278 |
279 | >
280 | );
281 | };
282 | ```
283 |
284 | ## Hack the views
285 |
286 | Almost all elements can be hackable, thanks to `Molecule registry`. They are stored in the global registry meaning you can override them, for example, you want to override the Table Header ? No problem:
287 |
288 | ```jsx
289 | import { Registry } from "react-molecule";
290 |
291 | Registry.blend({
292 | EasyTableHead: props =>
293 | });
294 | ```
295 |
296 | How did I find out the name ? Well, the easiest way is the use `React DevTools` to check the component name. And almost all components can be hacked. If not, you can look into the `atoms` folder inside this repository and look for `registry.tsx` files.
297 |
298 | ## Conclusion
299 |
300 | Feel free to submit issues if you want to improve anything.
301 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "easify",
3 | "version": "0.2.0",
4 | "description": "Creating lists and complex tables with pagination and filters very fast",
5 | "main": "dist/index.js",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/cult-of-coders/easify.git"
9 | },
10 | "scripts": {
11 | "clean": "rimraf dist coverage",
12 | "compile": "tsc",
13 | "pretest": "npm run compile",
14 | "test": "npm run testonly --",
15 | "lint": "tslint --type-check --project ./tsconfig.json ./src/**/*",
16 | "watch": "tsc -w",
17 | "testonly": "mocha --reporter spec --full-trace ./dist/__tests__/*.js",
18 | "testonly-watch": "mocha --reporter spec --full-trace ./dist/__tests__/*.js --watch",
19 | "coverage": "node ./node_modules/istanbul/lib/cli.js cover _mocha -- --full-trace ./dist/__tests__/*.js",
20 | "postcoverage": "remap-istanbul --input coverage/coverage.json --type lcovonly --output coverage/lcov.info",
21 | "prepublishOnly": "npm run clean && npm run compile"
22 | },
23 | "peerDependencies": {
24 | "react": "16.x",
25 | "react-molecule": "0.2.x",
26 | "simpl-schema": "1.x",
27 | "mobx": ">= 5.x.x",
28 | "mobx-react": ">= 5.x.x"
29 | },
30 | "devDependencies": {
31 | "@types/react": "^16.3.9",
32 | "@types/react-form": "^2.16.0",
33 | "chai": "^4.1.2",
34 | "chai-as-promised": "^7.1.1",
35 | "enzyme": "^3.3.0",
36 | "enzyme-adapter-react-16": "^1.1.1",
37 | "istanbul": "^0.4.5",
38 | "mobx": "^5.8.0",
39 | "mobx-react": "^6.1.3",
40 | "mocha": "^3.3.0",
41 | "react": "16.3.1",
42 | "react-dom": "^16.3.1",
43 | "remap-istanbul": "^0.11.1",
44 | "rimraf": "^2.6.2",
45 | "simpl-schema": "^1.5.0",
46 | "tslint": "^5.2.0",
47 | "typescript": "^3.2.2"
48 | },
49 | "typings": "dist/index.d.ts",
50 | "typescript": {
51 | "definition": "dist/index.d.ts"
52 | },
53 | "license": "MIT",
54 | "dependencies": {
55 | "react-molecule": "^0.2.1"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/__tests__/EasyLoadMoreAgent.ts:
--------------------------------------------------------------------------------
1 | import { MoleculeModel } from 'react-molecule';
2 | import { describe, it } from 'mocha';
3 | import { assert } from 'chai';
4 | import { shallow } from 'enzyme';
5 | import './enzyme.config';
6 | import { EasyLoaderAgent, EasyLoadMoreAgent } from '../agents';
7 | import { LoaderEvents } from './../agents/EasyLoaderAgent';
8 | import { toJS } from 'mobx';
9 |
10 | describe('EasyLoadMore', () => {
11 | it('Should load with propper configuration', done => {
12 | done();
13 |
14 | // To implement
15 | return;
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/src/__tests__/EasyLoaderAgent.ts:
--------------------------------------------------------------------------------
1 | import { MoleculeModel } from 'react-molecule';
2 | import { describe, it } from 'mocha';
3 | import { assert } from 'chai';
4 | import { shallow } from 'enzyme';
5 | import './enzyme.config';
6 | import { EasyLoaderAgent } from '../agents';
7 | import { LoaderEvents } from './../agents/EasyLoaderAgent';
8 | import { toJS } from 'mobx';
9 |
10 | describe('EasyLoaderAgent', () => {
11 | it('Should load with propper configuration', done => {
12 | const agent = new EasyLoaderAgent({
13 | molecule: {} as MoleculeModel,
14 | filters: { test: 1 },
15 | options: { test: 2 },
16 | load: () =>
17 | new Promise((resolve, reject) => {
18 | resolve([{ _id: 1, name: 'Johnas Smith' }]);
19 | }),
20 | });
21 |
22 | agent.prepare();
23 |
24 | assert.equal(1, agent.config.filters.test);
25 | assert.equal(2, agent.config.options.test);
26 |
27 | agent.on(LoaderEvents.LOADED, payload => {
28 | assert.lengthOf(payload.data, 1);
29 | agent.clean();
30 | done();
31 | });
32 |
33 | agent.on(LoaderEvents.ERROR, ({ error }) => {
34 | done(error);
35 | });
36 |
37 | agent.init();
38 | });
39 |
40 | it('Should properly work with override()', async () => {
41 | const dataSet1 = [{ _id: 1, name: 'Brown John' }];
42 |
43 | const dataSet2 = [{ _id: 2, name: 'Brown Smith' }];
44 |
45 | const agent = new EasyLoaderAgent({
46 | molecule: {} as MoleculeModel,
47 | filters: {
48 | context: 1,
49 | },
50 | load: ({ filters, options }) =>
51 | new Promise((resolve, reject) => {
52 | resolve(filters.context === 1 ? dataSet1 : dataSet2);
53 | }),
54 | });
55 |
56 | await agent.init();
57 |
58 | const promise = new Promise((resolve, reject) => {
59 | agent.on(LoaderEvents.LOADED, payload => {
60 | try {
61 | assert.equal('Brown Smith', payload.data[0].name);
62 | } catch (e) {
63 | reject(e);
64 | }
65 |
66 | resolve();
67 | });
68 | });
69 |
70 | agent.override({
71 | filters: {
72 | context: 2,
73 | },
74 | });
75 |
76 | return promise;
77 | });
78 |
79 | it('Should properly work with update()', async () => {
80 | const dataSet1 = [{ _id: 1, name: 'Brown John' }];
81 |
82 | const dataSet2 = [{ _id: 2, name: 'Brown Smith' }];
83 |
84 | const agent = new EasyLoaderAgent({
85 | molecule: {} as MoleculeModel,
86 | filters: {
87 | context: 1,
88 | },
89 | load: ({ filters, options }) =>
90 | new Promise((resolve, reject) => {
91 | resolve(filters.context && !filters.update ? dataSet1 : dataSet2);
92 | }),
93 | });
94 |
95 | await agent.init();
96 |
97 | const promise = new Promise((resolve, reject) => {
98 | agent.on(LoaderEvents.LOADED, payload => {
99 | try {
100 | assert.equal('Brown Smith', payload.data[0].name);
101 | } catch (e) {
102 | reject(e);
103 | }
104 |
105 | resolve();
106 | });
107 | });
108 |
109 | agent.update({
110 | filters: {
111 | update: true,
112 | },
113 | });
114 |
115 | return promise;
116 | });
117 | });
118 |
--------------------------------------------------------------------------------
/src/__tests__/enzyme.config.ts:
--------------------------------------------------------------------------------
1 | import * as enzyme from 'enzyme';
2 | import * as Adapter from 'enzyme-adapter-react-16';
3 |
4 | enzyme.configure({ adapter: new Adapter() });
5 |
--------------------------------------------------------------------------------
/src/agents/EasyLoadMoreAgent.ts:
--------------------------------------------------------------------------------
1 | import { Agent } from 'react-molecule';
2 | // Leave IObservableObject otherwise it will complain in tsc compilation
3 | import { observable, toJS, IObservableObject } from 'mobx';
4 | import EasyLoaderAgent, { LoaderEvents } from './EasyLoaderAgent';
5 |
6 | class EasyLoadMoreAgent extends Agent {
7 | loaderAgent: EasyLoaderAgent;
8 |
9 | // This variable becomes true whenever the selectors inside the molecule change
10 | // It becomes false again after the data has been loaded
11 | duringSelectorsChangedProcess: boolean = false;
12 |
13 | config: {
14 | initialItemsCount: number;
15 | loadItemsCount: number;
16 | agent?: string;
17 | count: (filters) => Promise;
18 | };
19 |
20 | store = observable({
21 | totalLoaded: 0,
22 | hasMore: undefined,
23 | totalCount: 0,
24 | });
25 |
26 | prepare() {
27 | const loaderAgent = this.molecule.getAgent(this.config.agent || 'loader');
28 | this.loaderAgent = loaderAgent;
29 |
30 | loaderAgent.on(LoaderEvents.LOADING, ({ options }) => {
31 | const { initialItemsCount, loadItemsCount } = this.config;
32 |
33 | // If the selectors have changed we basically need to fully reload the initial items count
34 | if (this.duringSelectorsChangedProcess) {
35 | Object.assign(options, {
36 | limit: initialItemsCount,
37 | skip: 0,
38 | });
39 |
40 | return;
41 | }
42 |
43 | const { totalLoaded } = this.store;
44 |
45 | Object.assign(options, {
46 | limit: totalLoaded === 0 ? initialItemsCount : loadItemsCount,
47 | skip: totalLoaded,
48 | });
49 | });
50 |
51 | loaderAgent.on(LoaderEvents.LOADED, payload => {
52 | const { data } = toJS(loaderAgent.store);
53 |
54 | if (!this.duringSelectorsChangedProcess) {
55 | payload.data = [...data, ...payload.data];
56 | } else {
57 | this.duringSelectorsChangedProcess = false;
58 | }
59 |
60 | this.store.totalLoaded = payload.data.length;
61 | this.updateHasMore();
62 | });
63 | }
64 |
65 | init() {
66 | this.loaderAgent.on(LoaderEvents.SELECTORS_CHANGED, () => {
67 | this.duringSelectorsChangedProcess = true;
68 |
69 | Object.assign(this.store, {
70 | totalLoaded: 0,
71 | hasMore: undefined,
72 | totalCount: 0,
73 | });
74 |
75 | this.count();
76 | });
77 |
78 | this.count();
79 | }
80 |
81 | count = () => {
82 | const loaderStore = toJS(this.loaderAgent.store);
83 |
84 | this.isDebug() &&
85 | console.log('Load-more counting for store: ', loaderStore);
86 |
87 | this.config.count(loaderStore.filters).then(count => {
88 | this.store.totalCount = count;
89 | this.updateHasMore();
90 | });
91 | };
92 |
93 | load() {
94 | return this.loaderAgent.load();
95 | }
96 |
97 | isLoading() {
98 | return this.loaderAgent.store.loading;
99 | }
100 |
101 | private updateHasMore() {
102 | this.store.hasMore = this.store.totalLoaded < this.store.totalCount;
103 | }
104 | }
105 |
106 | export default EasyLoadMoreAgent;
107 |
--------------------------------------------------------------------------------
/src/agents/EasyLoaderAgent.ts:
--------------------------------------------------------------------------------
1 | import { observable, toJS } from 'mobx';
2 | import React, { Component } from 'react';
3 | import { withMolecule, Agent } from 'react-molecule';
4 | import PropTypes from 'prop-types';
5 |
6 | export const LoaderEvents = {
7 | INIT: 'easify.loader.init.before',
8 |
9 | UPDATE: 'easify.loader.filters.update',
10 | OVERRIDE: 'easify.loader.filters.override',
11 |
12 | FILTERS_CHANGE: 'easify.loader.filters.change',
13 | OPTIONS_CHANGE: 'easify.loader.options.change',
14 | SELECTORS_CHANGE: 'easify.loader.selectors.change',
15 | SELECTORS_CHANGED: 'easify.loader.selectors.changed',
16 |
17 | LOADING: 'easify.loader.data.loading',
18 | LOADED: 'easify.loader.data.loaded',
19 | ERROR: 'easify.loader.data.error',
20 | };
21 |
22 | export type UpdateFilters = {
23 | filters?: any;
24 | options?: any;
25 | };
26 |
27 | export default class EasyLoaderAgent extends Agent {
28 | static Events = LoaderEvents;
29 |
30 | public store: any = observable({
31 | loading: true,
32 | data: [],
33 | error: null,
34 | filters: {},
35 | options: {},
36 | });
37 |
38 | prepare() {
39 | if (this.config.filters) {
40 | this.store.filters = this.config.filters;
41 | }
42 | if (this.config.options) {
43 | this.store.options = this.config.options;
44 | }
45 | }
46 |
47 | async init() {
48 | return this.load();
49 | }
50 |
51 | async load() {
52 | this.store.loading = true;
53 |
54 | const { filters, options } = toJS(this.store);
55 |
56 | this.emit(LoaderEvents.LOADING, { filters, options });
57 |
58 | // do loading
59 | try {
60 | this.isDebug() &&
61 | console.log(`Loader started loading with: `, { filters, options });
62 |
63 | const data = await this.config.load({
64 | filters,
65 | options,
66 | });
67 |
68 | const payload = { data };
69 |
70 | // Be careful here, we do it like this because we allow to modify the payload
71 | // It can end-up being quite different, like for example append results instead of storing them
72 | this.emit(LoaderEvents.LOADED, payload);
73 |
74 | Object.assign(this.store, {
75 | data: payload.data,
76 | loading: false,
77 | error: null,
78 | });
79 | } catch (error) {
80 | Object.assign(this.store, {
81 | loading: false,
82 | error,
83 | });
84 |
85 | this.emit(LoaderEvents.ERROR, { error });
86 | }
87 | }
88 |
89 | override({ filters, options }: UpdateFilters) {
90 | const store = this.store;
91 |
92 | this.emit(LoaderEvents.SELECTORS_CHANGE, { filters, options });
93 |
94 | if (filters) {
95 | this.emit(LoaderEvents.FILTERS_CHANGE, { filters });
96 | store.filters = filters;
97 | }
98 | if (options) {
99 | this.emit(LoaderEvents.OPTIONS_CHANGE, { options });
100 | store.options = options;
101 | }
102 |
103 | this.load();
104 | }
105 |
106 | update({ filters, options }: UpdateFilters) {
107 | const store = this.store;
108 |
109 | this.emit(LoaderEvents.SELECTORS_CHANGE, { filters, options });
110 |
111 | if (filters) {
112 | this.emit(LoaderEvents.FILTERS_CHANGE, { filters });
113 | store.filters = Object.assign({}, store.filters, filters);
114 | }
115 |
116 | if (options) {
117 | this.emit(LoaderEvents.OPTIONS_CHANGE, { options });
118 | store.options = Object.assign({}, store.options, options);
119 | }
120 |
121 | this.emit(LoaderEvents.SELECTORS_CHANGED, { filters, options });
122 |
123 | this.load();
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/agents/EasyPagerAgent.ts:
--------------------------------------------------------------------------------
1 | import EasyLoaderAgent, { LoaderEvents } from './EasyLoaderAgent';
2 | import { Agent } from 'react-molecule';
3 | import { observable, toJS } from 'mobx';
4 |
5 | export default class EasyPagerAgent extends Agent {
6 | loaderAgent: EasyLoaderAgent;
7 |
8 | config: {
9 | agent: string;
10 | perPage: number;
11 | count(filters: any);
12 | };
13 |
14 | store: { total: number; currentPage: number; perPage: number } = observable({
15 | total: 0,
16 | currentPage: 0,
17 | perPage: null,
18 | });
19 |
20 | prepare() {
21 | const loaderAgent = this.molecule.getAgent(this.config.agent || 'loader');
22 |
23 | if (!loaderAgent) {
24 | throw new Error(`We could not find a loader agent in the molecule`);
25 | }
26 |
27 | this.loaderAgent = loaderAgent;
28 |
29 | this.store.perPage = this.config.perPage || 10;
30 |
31 | loaderAgent.on(LoaderEvents.LOADING, ({ options }) => {
32 | const { perPage, currentPage } = this.store;
33 | Object.assign(options, {
34 | limit: perPage,
35 | skip: currentPage * perPage,
36 | });
37 | });
38 | }
39 |
40 | init() {
41 | this.loaderAgent.on(LoaderEvents.SELECTORS_CHANGED, () => {
42 | this.changePage(0, false);
43 | this.count();
44 | });
45 |
46 | this.count();
47 | }
48 |
49 | count = () => {
50 | const loaderStore = toJS(this.loaderAgent.store);
51 |
52 | this.isDebug() && console.log('Counting with: ', loaderStore);
53 |
54 | this.config.count(loaderStore.filters).then(count => {
55 | this.store.total = count;
56 | });
57 | };
58 |
59 | clean() {
60 | // TODO: propper cleaning
61 | }
62 |
63 | changePage(number, andLoad = true) {
64 | this.store.currentPage = number;
65 | andLoad && this.loaderAgent.load();
66 | }
67 |
68 | changePerPage(number) {
69 | this.store.perPage = number;
70 | this.loaderAgent.load();
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/agents/index.ts:
--------------------------------------------------------------------------------
1 | import EasyLoaderAgent from './EasyLoaderAgent';
2 | import EasyPagerAgent from './EasyPagerAgent';
3 | import EasyLoadMoreAgent from './EasyLoadMoreAgent';
4 |
5 | export { EasyLoaderAgent, EasyPagerAgent, EasyLoadMoreAgent };
6 |
--------------------------------------------------------------------------------
/src/atoms/EasyFilters/EasyFilters.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { MoleculeModel, withMolecule } from 'react-molecule';
3 | import { EasyLoaderAgent } from '../../agents';
4 | import { getMongoFilters } from './tools';
5 |
6 | export interface Props {
7 | molecule: MoleculeModel;
8 | agent?: string;
9 | schema: any;
10 | preFilter?: (filters) => void;
11 | children: ({ onSubmit, doFilter }) => any;
12 | }
13 |
14 | class EasyFilters extends React.Component {
15 | loaderAgent: EasyLoaderAgent;
16 |
17 | static defaultProps = {
18 | agent: 'loader',
19 | };
20 |
21 | constructor(props) {
22 | super(props);
23 |
24 | const { agent, molecule } = props;
25 | this.loaderAgent = molecule.getAgent(agent);
26 | }
27 |
28 | onSubmit = values => {
29 | const { schema, preFilter } = this.props;
30 | const filters = getMongoFilters(schema, values);
31 |
32 | preFilter && preFilter(filters);
33 |
34 | this.loaderAgent.update({
35 | filters,
36 | });
37 | };
38 |
39 | doFilter = filters => {
40 | const { preFilter } = this.props;
41 |
42 | preFilter && preFilter(filters);
43 |
44 | // Filters get overrided
45 | this.loaderAgent.override({
46 | filters,
47 | });
48 | };
49 |
50 | render() {
51 | const { children } = this.props;
52 |
53 | return children({
54 | onSubmit: this.onSubmit,
55 | doFilter: this.doFilter,
56 | });
57 | }
58 | }
59 |
60 | export default withMolecule(EasyFilters);
61 |
--------------------------------------------------------------------------------
/src/atoms/EasyFilters/index.tsx:
--------------------------------------------------------------------------------
1 | import EasyFilters from './EasyFilters';
2 |
3 | export { EasyFilters };
4 |
--------------------------------------------------------------------------------
/src/atoms/EasyFilters/tools.ts:
--------------------------------------------------------------------------------
1 | export function defaultToFilter(value, name) {
2 | if (Array.isArray(value)) {
3 | return {
4 | [name]: { $in: value },
5 | };
6 | }
7 |
8 | if (value instanceof Date) {
9 | let startOfDay = new Date(value.getTime());
10 | let endOfDay = new Date(value.getTime());
11 |
12 | startOfDay.setHours(0, 0, 0, 0);
13 | endOfDay.setHours(23, 59, 59, 999);
14 |
15 | return {
16 | [name]: {
17 | $gte: startOfDay,
18 | $lte: endOfDay,
19 | },
20 | };
21 | }
22 |
23 | // Here we need to carefully prevent null from becoming an actual filter or an empty string
24 | if (value === '' || value === null) {
25 | return null;
26 | }
27 |
28 | return {
29 | [name]: value,
30 | };
31 | }
32 |
33 | export function getMongoFilters(schema, values) {
34 | let filters = {};
35 |
36 | for (let KEY in values) {
37 | const value = values[KEY];
38 | const easifyOptions = schema.get(KEY, 'easify') || {};
39 |
40 | const toFilter = easifyOptions.toFilter || defaultToFilter;
41 | const filter = toFilter(value, KEY, filters);
42 |
43 | // If it's a falsey value we don't assign anything.
44 | if (filter && typeof filter === 'object') {
45 | Object.assign(filters, {
46 | ...filter,
47 | });
48 | }
49 | }
50 |
51 | return filters;
52 | }
53 |
--------------------------------------------------------------------------------
/src/atoms/EasyList/EasyList.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { withMolecule, MoleculeModel } from 'react-molecule';
3 | import EasyLoaderAgent from '../../agents/EasyLoaderAgent';
4 | import { observer } from 'mobx-react';
5 |
6 | export interface Props {
7 | molecule: MoleculeModel;
8 | agent?: string;
9 | children: (items) => any;
10 | autoLoadMore?: boolean;
11 | }
12 |
13 | class EasyList extends React.Component {
14 | loaderAgent: EasyLoaderAgent;
15 |
16 | static defaultProps = {
17 | agent: 'loader',
18 | autoLoadMore: true,
19 | };
20 |
21 | state = {
22 | sort: {},
23 | };
24 |
25 | constructor(props) {
26 | super(props);
27 |
28 | const { molecule, agent } = props;
29 | this.loaderAgent = molecule.getAgent(agent);
30 | }
31 |
32 | render() {
33 | const { molecule, children } = this.props;
34 |
35 | return (
36 |
41 | );
42 | }
43 | }
44 |
45 | const EasyListItems = observer(({ store, molecule, renderer }) => {
46 | const { error, loading, data } = store;
47 | const { EasyListLoading, EasyListError, EasyListWrapper } = molecule.registry;
48 |
49 | if (error) {
50 | return (
51 |
52 |
53 |
54 | );
55 | }
56 |
57 | return (
58 | {renderer({ data, molecule, loading })}
59 | );
60 | });
61 |
62 | export default withMolecule(EasyList);
63 |
--------------------------------------------------------------------------------
/src/atoms/EasyList/index.tsx:
--------------------------------------------------------------------------------
1 | import './registry';
2 | import EasyList from './EasyList';
3 |
4 | export { EasyList };
5 |
--------------------------------------------------------------------------------
/src/atoms/EasyList/registry.tsx:
--------------------------------------------------------------------------------
1 | import { Registry } from 'react-molecule';
2 | import * as React from 'react';
3 |
4 | const EasyListLoading = () => 'Please wait...';
5 | const EasyListError = ({ error }) => 'An error has occured';
6 | const EasyListWrapper = props => (
7 |
8 | );
9 |
10 | Registry.blend({
11 | EasyListLoading,
12 | EasyListError,
13 | EasyListWrapper,
14 | });
15 |
--------------------------------------------------------------------------------
/src/atoms/EasyLoadMore/EasyLoadMore.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { withMolecule, MoleculeModel } from 'react-molecule';
3 | import { Observer } from 'mobx-react';
4 | import { EasyLoadMoreAgent } from '../../agents';
5 |
6 | export interface Props {
7 | molecule: MoleculeModel;
8 | agent?: string;
9 | }
10 |
11 | class EasyLoadMore extends React.Component {
12 | loadMoreAgent: EasyLoadMoreAgent;
13 |
14 | static defaultProps = {
15 | agent: 'loadMore',
16 | };
17 |
18 | state = {
19 | sort: {},
20 | };
21 |
22 | constructor(props) {
23 | super(props);
24 |
25 | const { molecule, agent } = props;
26 | this.loadMoreAgent = molecule.getAgent(agent);
27 | }
28 |
29 | render() {
30 | const { molecule, children } = this.props;
31 |
32 | const { EasyLoadMoreButton } = molecule.registry;
33 |
34 | return (
35 |
36 | {() => {
37 | const { store } = this.loadMoreAgent;
38 |
39 | return (
40 | this.loadMoreAgent.load()}
43 | loading={this.loadMoreAgent.isLoading()}
44 | />
45 | );
46 | }}
47 |
48 | );
49 | }
50 | }
51 |
52 | export default withMolecule(EasyLoadMore);
53 |
--------------------------------------------------------------------------------
/src/atoms/EasyLoadMore/index.tsx:
--------------------------------------------------------------------------------
1 | import './registry';
2 | import EasyLoadMore from './EasyLoadMore';
3 |
4 | export { EasyLoadMore };
5 |
--------------------------------------------------------------------------------
/src/atoms/EasyLoadMore/registry.tsx:
--------------------------------------------------------------------------------
1 | import { Registry } from 'react-molecule';
2 | import * as React from 'react';
3 |
4 | Registry.blend({
5 | EasyLoadMoreButton: ({ loadMore, hasMore }) => {
6 | if (!hasMore) {
7 | return null;
8 | }
9 |
10 | return ;
11 | },
12 | });
13 |
--------------------------------------------------------------------------------
/src/atoms/EasyPager/EasyPager.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { withMolecule, MoleculeModel } from 'react-molecule';
3 | import { observer } from 'mobx-react';
4 |
5 | interface Props {
6 | molecule: MoleculeModel;
7 | agent?: string;
8 | }
9 |
10 | class EasyPager extends React.Component {
11 | pagerAgent: any;
12 |
13 | static defaultProps = {
14 | agent: 'pager',
15 | };
16 |
17 | constructor(props) {
18 | super(props);
19 |
20 | const { agent, molecule } = this.props;
21 | this.pagerAgent = molecule.getAgent(agent || 'pager');
22 | }
23 |
24 | onPageChange = ({ selected }) => {
25 | this.pagerAgent.changePage(selected);
26 | };
27 |
28 | render() {
29 | const { store } = this.pagerAgent;
30 | const { molecule } = this.props;
31 | const { total, currentPage, perPage } = store;
32 |
33 | const { EasyPagination } = molecule.registry;
34 |
35 | return (
36 |
43 | );
44 | }
45 | }
46 |
47 | export default withMolecule(observer(EasyPager));
48 |
--------------------------------------------------------------------------------
/src/atoms/EasyPager/index.tsx:
--------------------------------------------------------------------------------
1 | import './registry';
2 | import EasyPager from './EasyPager';
3 |
4 | export { EasyPager };
5 |
--------------------------------------------------------------------------------
/src/atoms/EasyPager/registry.tsx:
--------------------------------------------------------------------------------
1 | import { Registry } from 'react-molecule';
2 | import Pagination from '../../vendor/pagination';
3 | import * as React from 'react';
4 |
5 | const EasyPagination = props => {
6 | const { total, perPage, currentPage, onPageChange } = props;
7 |
8 | let pageCount = parseInt((total / perPage).toString());
9 | if (total % perPage) {
10 | pageCount++;
11 | }
12 |
13 | return (
14 |
28 | );
29 | };
30 |
31 | Registry.blend({
32 | EasyPagination,
33 | });
34 |
--------------------------------------------------------------------------------
/src/atoms/EasyTable/EasyTable.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { withMolecule, MoleculeModel, mole } from 'react-molecule';
3 | import { EasyTableModel } from './defs';
4 | import EasyLoaderAgent from '../../agents/EasyLoaderAgent';
5 | import EasyTableBody from './EasyTableBody';
6 | import EasyTableHeader from './EasyTableHeader';
7 |
8 | export interface Props {
9 | molecule: MoleculeModel;
10 | model: EasyTableModel;
11 | agent?: string;
12 | }
13 |
14 | class EasyTable extends React.Component {
15 | loaderAgent: EasyLoaderAgent;
16 |
17 | static defaultProps = {
18 | agent: 'loader',
19 | };
20 |
21 | state = {
22 | sort: {},
23 | };
24 |
25 | constructor(props) {
26 | super(props);
27 |
28 | const { molecule, agent } = props;
29 | this.loaderAgent = molecule.getAgent(agent);
30 | }
31 |
32 | onSort(sort) {
33 | const agent = this.loaderAgent;
34 |
35 | let options = agent.store.options;
36 | options.sort = options.sort || {};
37 | Object.assign(options.sort, sort);
38 |
39 | agent.update({
40 | options,
41 | });
42 | }
43 |
44 | render() {
45 | const { molecule, model } = this.props;
46 | const { EasyTable } = molecule.registry;
47 |
48 | return (
49 |
50 |
55 |
60 |
61 | );
62 | }
63 | }
64 |
65 | export default withMolecule(EasyTable);
66 |
--------------------------------------------------------------------------------
/src/atoms/EasyTable/EasyTableBody.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { observer } from 'mobx-react';
4 | import { EasyTableModel } from './defs';
5 | import { MoleculeModel } from 'react-molecule';
6 | import EasyLoaderAgent from '../../agents/EasyLoaderAgent';
7 |
8 | export interface Props {
9 | model: EasyTableModel;
10 | molecule: MoleculeModel;
11 | agent: EasyLoaderAgent;
12 | }
13 |
14 | export class EasyTableBody extends React.Component {
15 | renderRow(item) {
16 | const { model } = this.props;
17 | const { fields } = model;
18 |
19 | return fields.map(field => {
20 | return this.renderElement(field, item);
21 | });
22 | }
23 |
24 | renderElement(field, object) {
25 | const { EasyTableRowElement } = this.props.molecule.registry;
26 |
27 | let value;
28 | if (typeof field.resolve === 'function') {
29 | value = field.resolve({
30 | object,
31 | molecule: this.props.molecule,
32 | });
33 | } else {
34 | value = object[field.resolve];
35 | }
36 |
37 | return (
38 |
39 | {value}
40 |
41 | );
42 | }
43 |
44 | render() {
45 | const { model, molecule, agent } = this.props;
46 | const { error, data, loading } = agent.store;
47 |
48 | const {
49 | EasyTableBody,
50 | EasyTableRow,
51 | EasyTableRowElement,
52 | EasyTableLoading,
53 | EasyTableError,
54 | EasyTableNoFoundData,
55 | } = molecule.registry;
56 |
57 | const canShowData = !error;
58 |
59 | return (
60 |
61 | {loading && (
62 |
63 |
64 |
65 |
66 |
67 | )}
68 | {error && (
69 |
70 |
71 |
72 |
73 |
74 | )}
75 | {canShowData &&
76 | !loading &&
77 | data.length === 0 && (
78 |
79 |
80 |
81 |
82 |
83 | )}
84 | {canShowData &&
85 | data.length > 0 &&
86 | data.map(item => (
87 |
91 | {this.renderRow(item)}
92 |
93 | ))}
94 |
95 | );
96 | }
97 | }
98 |
99 | export default observer(EasyTableBody);
100 |
--------------------------------------------------------------------------------
/src/atoms/EasyTable/EasyTableHeader.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { MoleculeModel } from 'react-molecule';
3 | import EasyLoaderAgent from '../../agents/EasyLoaderAgent';
4 | import { EasyTableModel, EasyTableModelField } from './defs';
5 |
6 | export interface Props {
7 | molecule: MoleculeModel;
8 | agent: EasyLoaderAgent;
9 | model: EasyTableModel;
10 | }
11 | export default class EasyTableHeader extends React.Component {
12 | state = {
13 | sort: {},
14 | };
15 |
16 | renderTableHeaderSort(field) {
17 | const {
18 | EasyTableHeadSortUp,
19 | EasyTableHeadSortDown,
20 | } = this.props.molecule.registry;
21 | const { sort } = this.state;
22 |
23 | const sortValue = sort[field.label];
24 |
25 | if (sortValue === undefined) {
26 | return null;
27 | } else if (sortValue === 1) {
28 | return (
29 | {
31 | this.toggleSort(field);
32 | }}
33 | />
34 | );
35 | } else if (sortValue === -1) {
36 | return (
37 | {
39 | this.toggleSort(field);
40 | }}
41 | />
42 | );
43 | }
44 |
45 | return null;
46 | }
47 |
48 | toggleSort = (field: EasyTableModelField) => {
49 | if (!field.sort) {
50 | return;
51 | }
52 |
53 | const { sort } = this.state;
54 | let newSort = Object.assign({}, sort);
55 |
56 | const sortValue = newSort[field.label];
57 |
58 | if (sortValue === undefined) {
59 | newSort[field.label] = 1;
60 | } else if (sortValue === 1) {
61 | newSort[field.label] = -1;
62 | } else if (sortValue === -1) {
63 | delete newSort[field.label];
64 | }
65 |
66 | this.setState(
67 | {
68 | sort: newSort,
69 | },
70 | () => {
71 | this.dispatchSort();
72 | }
73 | );
74 | };
75 |
76 | dispatchSort() {
77 | const { sort } = this.state;
78 | const { fields } = this.props.model;
79 | let sortOptions = {};
80 | for (let FIELD_LABEL in sort) {
81 | const field = fields.find(f => f.label === FIELD_LABEL);
82 | sortOptions[field.sort] = sort[FIELD_LABEL];
83 | }
84 |
85 | this.props.agent.update({
86 | options: {
87 | sort: sortOptions,
88 | },
89 | });
90 | }
91 |
92 | render() {
93 | const { molecule, model } = this.props;
94 |
95 | const {
96 | EasyTableHead,
97 | EasyTableHeadElement,
98 | EasyTableRow,
99 | } = molecule.registry;
100 |
101 | let ths = [];
102 | model.fields.forEach(field => {
103 | ths.push(
104 | this.toggleSort(field)}
107 | >
108 | {field.label}
109 | {field.sort && this.renderTableHeaderSort(field)}
110 |
111 | );
112 | });
113 |
114 | return (
115 |
116 | {ths}
117 |
118 | );
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/src/atoms/EasyTable/defs.ts:
--------------------------------------------------------------------------------
1 | export type EasyTableModel = {
2 | key?: (object: any) => any;
3 | fields: EasyTableModelField[];
4 | };
5 |
6 | export type EasyTableModelActions = {
7 | label: string;
8 | render: (payload: any) => any;
9 | };
10 |
11 | export type EasyTableModelField = {
12 | label: string;
13 | resolve?: () => any | string;
14 | sort?: string;
15 | };
16 |
--------------------------------------------------------------------------------
/src/atoms/EasyTable/index.ts:
--------------------------------------------------------------------------------
1 | import './registry';
2 | import EasyTable from './EasyTable';
3 |
4 | export { EasyTable };
5 |
--------------------------------------------------------------------------------
/src/atoms/EasyTable/registry.tsx:
--------------------------------------------------------------------------------
1 | import { Registry, withAgent } from 'react-molecule';
2 | import * as React from 'react';
3 |
4 | const EasyTableHead = props => {
5 | return ;
6 | };
7 |
8 | const EasyTableHeadElement = props => {
9 | return | ;
10 | };
11 |
12 | const EasyTableHeadSortUp = ({ onClick }) => ↑;
13 | const EasyTableHeadSortDown = ({ onClick }) => ↓;
14 | const EasyTable = props => {
15 | return ;
16 | };
17 |
18 | const EasyTableBody = props => ;
19 | const EasyTableRow = props =>
;
20 |
21 | const EasyTableRowElement = props => | ;
22 | const EasyTableNoFoundData = () => 'No items were found.';
23 | const EasyTableLoading = () => 'Please wait...';
24 | const EasyTableError = () => 'A weird error occured';
25 |
26 | Registry.blend({
27 | EasyTableHead,
28 | EasyTableHeadElement,
29 | EasyTableHeadSortUp,
30 | EasyTableHeadSortDown,
31 | EasyTable,
32 | EasyTableBody,
33 | EasyTableRow,
34 | EasyTableRowElement,
35 | EasyTableNoFoundData,
36 | EasyTableLoading,
37 | EasyTableError,
38 | });
39 |
--------------------------------------------------------------------------------
/src/atoms/tools.ts:
--------------------------------------------------------------------------------
1 | export function humanize(label) {
2 | return capitalizeFirstLetter(
3 | label.replace(/([A-Z]+)/g, ' $1').replace(/([A-Z][a-z])/g, ' $1')
4 | );
5 | }
6 |
7 | export function capitalizeFirstLetter(string) {
8 | return string.charAt(0).toUpperCase() + string.slice(1);
9 | }
10 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import SimpleSchema from 'simpl-schema';
2 |
3 | SimpleSchema && SimpleSchema.extendOptions(['easify']);
4 |
5 | export * from './atoms/EasyTable';
6 | export * from './atoms/EasyPager';
7 | export * from './atoms/EasyFilters';
8 | export * from './atoms/EasyLoadMore';
9 | export * from './atoms/EasyList';
10 | export * from './agents';
11 |
--------------------------------------------------------------------------------
/src/vendor/pagination/BreakView.tsx:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import * as React from 'react';
4 |
5 | const BreakView = props => {
6 | const label = props.breakLabel;
7 | const className = props.breakClassName || 'break';
8 |
9 | return {label};
10 | };
11 |
12 | export default BreakView;
13 |
--------------------------------------------------------------------------------
/src/vendor/pagination/LICENSE.md:
--------------------------------------------------------------------------------
1 | This code has been taken from:
2 | https://github.com/AdeleD/react-paginate/tree/b684811bb8f88ab9c3ecb531a8f2532968c6329f
3 |
4 | The license of the code is MIT.
5 |
6 | The reason for copy-pasting it, rather than using it as dependency is to have full-control over it.
7 |
--------------------------------------------------------------------------------
/src/vendor/pagination/PageView.tsx:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import * as React from 'react';
4 |
5 | const PageView = props => {
6 | let cssClassName = props.pageClassName;
7 | const linkClassName = props.pageLinkClassName;
8 | const onClick = props.onClick;
9 | const href = props.href;
10 | let ariaLabel =
11 | 'Page ' +
12 | props.page +
13 | (props.extraAriaContext ? ' ' + props.extraAriaContext : '');
14 | let ariaCurrent = null;
15 |
16 | if (props.selected) {
17 | ariaCurrent = 'page';
18 | ariaLabel = 'Page ' + props.page + ' is your current page';
19 | if (typeof cssClassName !== 'undefined') {
20 | cssClassName = cssClassName + ' ' + props.activeClassName;
21 | } else {
22 | cssClassName = props.activeClassName;
23 | }
24 | }
25 |
26 | return (
27 |
28 |
38 | {props.page}
39 |
40 |
41 | );
42 | };
43 |
44 | export default PageView;
45 |
--------------------------------------------------------------------------------
/src/vendor/pagination/PaginationBoxView.tsx:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import * as React from 'react';
4 | import * as PropTypes from 'prop-types';
5 | import PageView from './PageView';
6 | import BreakView from './BreakView';
7 |
8 | export type Props = {
9 | [key: string]: any;
10 | };
11 |
12 | export type State = {
13 | [key: string]: any;
14 | };
15 |
16 | export default class PaginationBoxView extends React.Component {
17 | static propTypes = {
18 | pageCount: PropTypes.number.isRequired,
19 | pageRangeDisplayed: PropTypes.number.isRequired,
20 | marginPagesDisplayed: PropTypes.number.isRequired,
21 | previousLabel: PropTypes.node,
22 | nextLabel: PropTypes.node,
23 | breakLabel: PropTypes.node,
24 | hrefBuilder: PropTypes.func,
25 | onPageChange: PropTypes.func,
26 | initialPage: PropTypes.number,
27 | forcePage: PropTypes.number,
28 | disableInitialCallback: PropTypes.bool,
29 | containerClassName: PropTypes.string,
30 | pageClassName: PropTypes.string,
31 | pageLinkClassName: PropTypes.string,
32 | activeClassName: PropTypes.string,
33 | previousClassName: PropTypes.string,
34 | nextClassName: PropTypes.string,
35 | previousLinkClassName: PropTypes.string,
36 | nextLinkClassName: PropTypes.string,
37 | disabledClassName: PropTypes.string,
38 | breakClassName: PropTypes.string,
39 | };
40 |
41 | static defaultProps = {
42 | pageCount: 10,
43 | pageRangeDisplayed: 2,
44 | marginPagesDisplayed: 3,
45 | activeClassName: 'selected',
46 | previousClassName: 'previous',
47 | nextClassName: 'next',
48 | previousLabel: 'Previous',
49 | nextLabel: 'Next',
50 | breakLabel: '...',
51 | disabledClassName: 'disabled',
52 | disableInitialCallback: false,
53 | };
54 |
55 | constructor(props) {
56 | super(props);
57 |
58 | this.state = {
59 | selected: props.initialPage
60 | ? props.initialPage
61 | : props.forcePage
62 | ? props.forcePage
63 | : 0,
64 | };
65 | }
66 |
67 | componentDidMount() {
68 | const { initialPage, disableInitialCallback } = this.props;
69 | // Call the callback with the initialPage item:
70 | if (typeof initialPage !== 'undefined' && !disableInitialCallback) {
71 | this.callCallback(initialPage);
72 | }
73 | }
74 |
75 | componentWillReceiveProps(nextProps) {
76 | if (
77 | typeof nextProps.forcePage !== 'undefined' &&
78 | this.props.forcePage !== nextProps.forcePage
79 | ) {
80 | this.setState({ selected: nextProps.forcePage });
81 | }
82 | }
83 |
84 | handlePreviousPage = evt => {
85 | const { selected } = this.state;
86 | evt.preventDefault ? evt.preventDefault() : (evt.returnValue = false);
87 | if (selected > 0) {
88 | this.handlePageSelected(selected - 1, evt);
89 | }
90 | };
91 |
92 | handleNextPage = evt => {
93 | const { selected } = this.state;
94 | const { pageCount } = this.props;
95 |
96 | evt.preventDefault ? evt.preventDefault() : (evt.returnValue = false);
97 | if (selected < pageCount - 1) {
98 | this.handlePageSelected(selected + 1, evt);
99 | }
100 | };
101 |
102 | handlePageSelected = (selected, evt) => {
103 | evt.preventDefault ? evt.preventDefault() : (evt.returnValue = false);
104 |
105 | if (this.state.selected === selected) return;
106 |
107 | this.setState({ selected: selected });
108 |
109 | // Call the callback with the new selected item:
110 | this.callCallback(selected);
111 | };
112 |
113 | hrefBuilder(pageIndex) {
114 | const { hrefBuilder, pageCount } = this.props;
115 | if (
116 | hrefBuilder &&
117 | pageIndex !== this.state.selected &&
118 | pageIndex >= 0 &&
119 | pageIndex < pageCount
120 | ) {
121 | return hrefBuilder(pageIndex + 1);
122 | }
123 | }
124 |
125 | callCallback = selectedItem => {
126 | if (
127 | typeof this.props.onPageChange !== 'undefined' &&
128 | typeof this.props.onPageChange === 'function'
129 | ) {
130 | this.props.onPageChange({ selected: selectedItem });
131 | }
132 | };
133 |
134 | getPageElement(index) {
135 | const { selected } = this.state;
136 | const {
137 | pageClassName,
138 | pageLinkClassName,
139 | activeClassName,
140 | extraAriaContext,
141 | } = this.props;
142 |
143 | return (
144 |
155 | );
156 | }
157 |
158 | pagination = () => {
159 | const items = [];
160 | const {
161 | pageRangeDisplayed,
162 | pageCount,
163 | marginPagesDisplayed,
164 | breakLabel,
165 | breakClassName,
166 | } = this.props;
167 |
168 | const { selected } = this.state;
169 |
170 | if (pageCount <= pageRangeDisplayed) {
171 | for (let index = 0; index < pageCount; index++) {
172 | items.push(this.getPageElement(index));
173 | }
174 | } else {
175 | let leftSide = pageRangeDisplayed / 2;
176 | let rightSide = pageRangeDisplayed - leftSide;
177 |
178 | if (selected > pageCount - pageRangeDisplayed / 2) {
179 | rightSide = pageCount - selected;
180 | leftSide = pageRangeDisplayed - rightSide;
181 | } else if (selected < pageRangeDisplayed / 2) {
182 | leftSide = selected;
183 | rightSide = pageRangeDisplayed - leftSide;
184 | }
185 |
186 | let index;
187 | let page;
188 | let breakView;
189 | let createPageView = index => this.getPageElement(index);
190 |
191 | for (index = 0; index < pageCount; index++) {
192 | page = index + 1;
193 |
194 | if (page <= marginPagesDisplayed) {
195 | items.push(createPageView(index));
196 | continue;
197 | }
198 |
199 | if (page > pageCount - marginPagesDisplayed) {
200 | items.push(createPageView(index));
201 | continue;
202 | }
203 |
204 | if (index >= selected - leftSide && index <= selected + rightSide) {
205 | items.push(createPageView(index));
206 | continue;
207 | }
208 |
209 | if (breakLabel && items[items.length - 1] !== breakView) {
210 | breakView = (
211 |
216 | );
217 | items.push(breakView);
218 | }
219 | }
220 | }
221 |
222 | return items;
223 | };
224 |
225 | render() {
226 | const {
227 | disabledClassName,
228 | previousClassName,
229 | nextClassName,
230 | pageCount,
231 | containerClassName,
232 | previousLinkClassName,
233 | previousLabel,
234 | nextLinkClassName,
235 | nextLabel,
236 | } = this.props;
237 |
238 | const { selected } = this.state;
239 |
240 | const previousClasses =
241 | previousClassName + (selected === 0 ? ` ${disabledClassName}` : '');
242 | const nextClasses =
243 | nextClassName +
244 | (selected === pageCount - 1 ? ` ${disabledClassName}` : '');
245 |
246 | return (
247 |
276 | );
277 | }
278 | }
279 |
--------------------------------------------------------------------------------
/src/vendor/pagination/index.ts:
--------------------------------------------------------------------------------
1 | import PaginationBoxView from './PaginationBoxView';
2 |
3 | export default PaginationBoxView;
4 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "commonjs",
5 | "declaration": true,
6 | "moduleResolution": "node",
7 | "sourceMap": true,
8 | "lib": ["es6", "dom", "esnext"],
9 | "noImplicitAny": false,
10 | "rootDir": "./src",
11 | "outDir": "./dist",
12 | "allowSyntheticDefaultImports": true,
13 | "pretty": true,
14 | "jsx": "react",
15 | "removeComments": true,
16 | "typeRoots": ["node_modules/@types"]
17 | },
18 |
19 | "include": ["**/*.ts", "**/*.tsx", "**/*.jsx", "**/*.js"],
20 | "exclude": ["node_modules", "dist"]
21 | }
22 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "align": [
4 | false,
5 | "parameters",
6 | "arguments",
7 | "statements"
8 | ],
9 | "ban": false,
10 | "class-name": true,
11 | "curly": true,
12 | "eofline": true,
13 | "forin": true,
14 | "indent": [
15 | true,
16 | "spaces"
17 | ],
18 | "interface-name": false,
19 | "jsdoc-format": true,
20 | "label-position": true,
21 | "max-line-length": [
22 | true,
23 | 140
24 | ],
25 | "member-access": true,
26 | "member-ordering": [
27 | true,
28 | "public-before-private",
29 | "static-before-instance",
30 | "variables-before-functions"
31 | ],
32 | "no-any": false,
33 | "no-arg": true,
34 | "no-bitwise": true,
35 | "no-conditional-assignment": true,
36 | "no-consecutive-blank-lines": false,
37 | "no-console": [
38 | true,
39 | "log",
40 | "debug",
41 | "info",
42 | "time",
43 | "timeEnd",
44 | "trace"
45 | ],
46 | "no-construct": true,
47 | "no-debugger": true,
48 | "no-duplicate-variable": true,
49 | "no-empty": true,
50 | "no-eval": true,
51 | "no-inferrable-types": false,
52 | "no-internal-module": true,
53 | "no-null-keyword": false,
54 | "no-require-imports": false,
55 | "no-shadowed-variable": true,
56 | "no-switch-case-fall-through": true,
57 | "no-trailing-whitespace": true,
58 | "no-unused-expression": true,
59 | "no-unused-variable": true,
60 | "no-use-before-declare": true,
61 | "no-var-keyword": true,
62 | "no-var-requires": true,
63 | "object-literal-sort-keys": false,
64 | "one-line": [
65 | true,
66 | "check-open-brace",
67 | "check-catch",
68 | "check-else",
69 | "check-finally",
70 | "check-whitespace"
71 | ],
72 | "quotemark": [
73 | true,
74 | "single",
75 | "avoid-escape"
76 | ],
77 | "radix": true,
78 | "semicolon": [
79 | true,
80 | "always"
81 | ],
82 | "switch-default": true,
83 | "trailing-comma": [
84 | true,
85 | {
86 | "multiline": "always",
87 | "singleline": "never"
88 | }
89 | ],
90 | "triple-equals": [
91 | true,
92 | "allow-null-check"
93 | ],
94 | "typedef": [
95 | false,
96 | "call-signature",
97 | "parameter",
98 | "arrow-parameter",
99 | "property-declaration",
100 | "variable-declaration",
101 | "member-variable-declaration"
102 | ],
103 | "typedef-whitespace": [
104 | true,
105 | {
106 | "call-signature": "nospace",
107 | "index-signature": "nospace",
108 | "parameter": "nospace",
109 | "property-declaration": "nospace",
110 | "variable-declaration": "nospace"
111 | },
112 | {
113 | "call-signature": "space",
114 | "index-signature": "space",
115 | "parameter": "space",
116 | "property-declaration": "space",
117 | "variable-declaration": "space"
118 | }
119 | ],
120 | "variable-name": [
121 | true,
122 | "check-format",
123 | "allow-leading-underscore",
124 | "ban-keywords",
125 | "allow-pascal-case"
126 | ],
127 | "whitespace": [
128 | true,
129 | "check-branch",
130 | "check-decl",
131 | "check-operator",
132 | "check-separator",
133 | "check-type"
134 | ]
135 | }
136 | }
--------------------------------------------------------------------------------