Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Checkbox (boolean) field type #147

Merged
merged 10 commits into from
Jul 17, 2018
1 change: 1 addition & 0 deletions packages/fields/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module.exports = {
Password: require('./types/Password'),
Text: require('./types/Text'),
Float: require('./types/Float'),
Checkbox: require('./types/Checkbox'),
Select: require('./types/Select'),
Relationship: require('./types/Relationship'),
File: require('./types/File'),
Expand Down
28 changes: 28 additions & 0 deletions packages/fields/types/Checkbox/Controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import FieldController from '../../Controller';

export default class CheckboxController extends FieldController {
getValue = data => data[this.config.path] || false;
getInitialData = () => this.config.defaultValue || false;
getFilterGraphQL = ({ type, value }) => {
const key = type === 'is' ? `${this.path}` : `${this.path}_${type}`;
return `${key}: ${value}`;
};
getFilterLabel = ({ label }) => {
return `${this.label} ${label.toLowerCase()}`;
};
formatFilter = ({ label, value }) => {
return `${this.getFilterLabel({ label })}: "${value}"`;
};
filterTypes = [
{
type: 'is',
label: 'Is',
getInitialValue: () => 'true',
},
{
type: 'not',
label: 'Is not',
getInitialValue: () => 'true',
},
];
}
58 changes: 58 additions & 0 deletions packages/fields/types/Checkbox/Implementation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
const { Implementation } = require('../../Implementation');
const { MongooseFieldAdapter } = require('@keystonejs/adapter-mongoose');

class Checkbox extends Implementation {
constructor() {
super(...arguments);
this.graphQLType = 'Boolean';
}

getGraphqlQueryArgs() {
return `
${this.path}: Boolean
${this.path}_not: Boolean
`;
}
getGraphqlUpdateArgs() {
return `
${this.path}: Boolean
`;
}
getGraphqlCreateArgs() {
return `
${this.path}: Boolean
`;
}
}

class MongoCheckboxInterface extends MongooseFieldAdapter {
addToMongooseSchema(schema) {
const { mongooseOptions } = this.config;
schema.add({
[this.path]: { type: Boolean, ...mongooseOptions },
});
}

getQueryConditions(args) {
const conditions = [];
if (!args) {
return conditions;
}

const eq = this.path;
if (eq in args) {
conditions.push({ $eq: args[eq] });
}

const not = `${this.path}_not`;
if (not in args) {
conditions.push({ $ne: args[not] });
}
return conditions;
}
}

module.exports = {
Checkbox,
MongoCheckboxInterface,
};
89 changes: 89 additions & 0 deletions packages/fields/types/Checkbox/filterTests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { matchFilter } from '../../tests/fields.test';
import Text from '../Text';
import Checkbox from './';

export const name = 'Checkbox';

export const getTestFields = () => {
return {
name: { type: Text },
enabled: { type: Checkbox },
};
};

export const initItems = () => {
return [
{ name: 'person1', enabled: true },
{ name: 'person2', enabled: false },
{ name: 'person3', enabled: null },
{ name: 'person4', enabled: true },
];
};

export const filterTests = app => {
const match = (filter, targets, done) => {
matchFilter(app, filter, '{ name enabled }', targets, done, 'name');
};

test('No filter', done => {
match(
undefined,
[
{ name: 'person1', enabled: true },
{ name: 'person2', enabled: false },
{ name: 'person3', enabled: null },
{ name: 'person4', enabled: true },
],
done
);
});

test('Empty filter', done => {
match(
'where: { }',
[
{ name: 'person1', enabled: true },
{ name: 'person2', enabled: false },
{ name: 'person3', enabled: null },
{ name: 'person4', enabled: true },
],
done
);
});

test('Filter: enabled true', done => {
match(
'where: { enabled: true }',
[{ name: 'person1', enabled: true }, { name: 'person4', enabled: true }],
done
);
});

test('Filter: enabled false', done => {
match(
'where: { enabled: false }',
[{ name: 'person2', enabled: false }],
done
);
});

test('Filter: enabled_not true', done => {
match(
'where: { enabled_not: true }',
[{ name: 'person2', enabled: false }, { name: 'person3', enabled: null }],
done
);
});

test('Filter: enabled_not false', done => {
match(
'where: { enabled_not: false }',
[
{ name: 'person1', enabled: true },
{ name: 'person3', enabled: null },
{ name: 'person4', enabled: true },
],
done
);
});
};
16 changes: 16 additions & 0 deletions packages/fields/types/Checkbox/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const path = require('path');
const { Checkbox, MongoCheckboxInterface } = require('./Implementation');

module.exports = {
type: 'Checkbox',
implementation: Checkbox,
views: {
Controller: path.resolve(__dirname, './Controller'),
Field: path.resolve(__dirname, './views/Field'),
Filter: path.resolve(__dirname, './views/Filter'),
Cell: path.resolve(__dirname, './views/Cell'),
},
adapters: {
mongoose: MongoCheckboxInterface,
},
};
19 changes: 19 additions & 0 deletions packages/fields/types/Checkbox/views/Cell.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React, { Component } from 'react';

type Props = {
field: Object,
list: Object,
data: Object,
// Link: Component,
};
// NOTE: It looks like this we do not need to handle the Link component.
// Current implementation will wrap the cell in a LinkComponent if it is the first
// field so using it here is not reqired.
// See: admin-ui/client/components/ListTable.js:145,162.

export default class CheckboxCellView extends Component<Props> {
render() {
const { data } = this.props;
return <input type="checkbox" checked={data} disabled />;
}
}
37 changes: 37 additions & 0 deletions packages/fields/types/Checkbox/views/Field.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React, { Component } from 'react';

import {
FieldContainer,
FieldLabel,
FieldInput,
} from '@keystonejs/ui/src/primitives/fields';

// TODO: use pretty checkboxes - these only work in a CheckGroup situation.
// import { Checkbox } from '@keystonejs/ui/src/primitives/forms';

export default class TextField extends Component {
onChange = event => {
const { field, onChange } = this.props;
onChange(field, event.target.checked);
};
render() {
const { autoFocus, field, item } = this.props;
const checked = item[field.path] || false;
const htmlID = `ks-input-${field.path}`;

return (
<FieldContainer>
<FieldLabel htmlFor={htmlID}>{field.label}</FieldLabel>
<FieldInput>
<input
autoFocus={autoFocus}
type="checkbox"
checked={checked}
onChange={this.onChange}
id={htmlID}
/>
</FieldInput>
</FieldContainer>
);
}
}
36 changes: 36 additions & 0 deletions packages/fields/types/Checkbox/views/Filter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// @flow

import React, { Component, type Ref } from 'react';

type Props = {
field: Object,
filter: Object,
innerRef: Ref<*>,
onChange: Event => void,
};

export default class CheckboxFilterView extends Component<Props> {
componentDidUpdate(prevProps) {
const { filter } = this.props;

if (prevProps.filter !== filter) {
this.props.recalcHeight();
}
}
handleChange = ({ target: { value } }) => {
this.props.onChange(value);
};

render() {
const { filter, innerRef, value } = this.props;
if (!filter) return null;

return (
<select onChange={this.handleChange} ref={innerRef} value={value}>
<option value="true">Checked</option>
<option value="false">Unchecked</option>
<option value="null">Not set</option>
</select>
);
}
}
7 changes: 7 additions & 0 deletions projects/basic/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,36 +41,43 @@ module.exports = {
name: 'Boris Bozic',
email: 'boris@keystonejs.com',
company: 'thinkmill',
isAdmin: true,
},
{
name: 'Jed Watson',
email: 'jed@keystonejs.com',
company: 'thinkmill',
isAdmin: true,
},
{
name: 'John Molomby',
email: 'john@keystonejs.com',
company: 'thinkmill',
isAdmin: true,
},
{
name: 'Joss Mackison',
email: 'joss@keystonejs.com',
company: 'thinkmill',
isAdmin: true,
},
{
name: 'Ben Conolly',
email: 'ben@keystonejs.com',
company: 'thinkmill',
isAdmin: true,
},
{
name: 'Luke Batchelor',
email: 'luke@keystonejs.com',
company: 'atlassian',
isAdmin: false,
},
{
name: 'Jared Crowe',
email: 'jared@keystonejs.com',
company: 'atlassian',
isAdmin: false,
},
{
name: 'Tom Walker',
Expand Down
2 changes: 2 additions & 0 deletions projects/basic/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const {
Relationship,
Select,
Password,
Checkbox,
CloudinaryImage,
} = require('@keystonejs/fields');
const { WebServer } = require('@keystonejs/server');
Expand Down Expand Up @@ -56,6 +57,7 @@ keystone.createList('User', {
name: { type: Text },
email: { type: Text },
password: { type: Password },
isAdmin: { type: Checkbox },
company: {
type: Select,
options: [
Expand Down
5 changes: 5 additions & 0 deletions projects/twitter-login/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,26 +41,31 @@ module.exports = {
name: 'Boris Bozic',
email: 'boris@keystonejs.com',
company: 'thinkmill',
isAdmin: true,
},
{
name: 'Jed Watson',
email: 'jed@keystonejs.com',
company: 'thinkmill',
isAdmin: true,
},
{
name: 'John Molomby',
email: 'john@keystonejs.com',
company: 'thinkmill',
isAdmin: true,
},
{
name: 'Joss Mackison',
email: 'joss@keystonejs.com',
company: 'thinkmill',
isAdmin: true,
},
{
name: 'Ben Conolly',
email: 'ben@keystonejs.com',
company: 'thinkmill',
isAdmin: true,
},
{
name: 'Luke Batchelor',
Expand Down
Loading