working on demo; extracting forms logic into package

This commit is contained in:
Sacha Greif 2016-02-28 18:56:58 +09:00
parent f9c0faba89
commit 5b8c8d92a9
16 changed files with 56 additions and 404 deletions

View file

@ -143,6 +143,7 @@ twitter@1.1.6-beta.11
ui@1.0.8
underscore@1.0.5-beta.11
url@1.0.6-beta.11
utilities:react-form-containers@0.1.0
utilities:react-list-container@0.1.0
utilities:smart-methods@0.1.0
utilities:smart-publications@0.1.1

View file

@ -55,4 +55,8 @@ body{
.comment-body{
border: 1px solid #ddd;
padding: 10px;
}
.new-post-button{
margin-bottom: 15px;
}

View file

@ -1,4 +1,5 @@
import SmartContainers from "meteor/utilities:react-list-container";
import FormContainers from "meteor/utilities:react-form-containers";
Telescope.registerComponent("AppContainer", require('./containers/AppContainer.jsx'));
// Telescope.registerComponent("ItemContainer", require('./containers/ItemContainer.jsx'));
@ -12,7 +13,11 @@ Telescope.registerComponent("ListContainer", SmartContainers.ListContainer);
Telescope.registerComponent("FlashContainer", require('./containers/FlashContainer.jsx'));
Telescope.registerComponent("CurrentUserContainer", require('./containers/CurrentUserContainer.jsx'));
Telescope.registerComponent("NewDocContainer", require('./containers/NewDocContainer.jsx'));
Telescope.registerComponent("EditDocContainer", require('./containers/EditDocContainer.jsx'));
// Telescope.registerComponent("NewDocContainer", require('./containers/NewDocContainer.jsx'));
// Telescope.registerComponent("EditDocContainer", require('./containers/EditDocContainer.jsx'));
Telescope.registerComponent("NewDocContainer", FormContainers.NewDocContainer);
Telescope.registerComponent("EditDocContainer", FormContainers.EditDocContainer);
Telescope.registerComponent("ModalButton", require('./components/ModalButton.jsx'));

View file

@ -51,7 +51,7 @@ const ModalButton = React.createClass({
callback = this.closeModal;
}
return React.cloneElement(child, { callback: callback });
return React.cloneElement(child, { successCallback: callback });
});
@ -71,4 +71,5 @@ const ModalButton = React.createClass({
}
});
module.exports = ModalButton;
module.exports = ModalButton;
export default ModalButton;

View file

@ -34,4 +34,5 @@ const AppContainer = React.createClass({
});
module.exports = AppContainer;
module.exports = AppContainer;
export default AppContainer;

View file

@ -15,4 +15,5 @@ const CurrentUserContainer = React.createClass({
});
module.exports = CurrentUserContainer;
module.exports = CurrentUserContainer;
export default CurrentUserContainer;

View file

@ -1,63 +0,0 @@
import Messages from "../messages.js";
import NovaForms from "../forms.jsx";
import Formsy from 'formsy-react';
const EditDocContainer = React.createClass({
propTypes: {
document: React.PropTypes.object, // required but might be passed later on
collection: React.PropTypes.object, // required but might be passed later on
label: React.PropTypes.string,
callback: React.PropTypes.func,
methodName: React.PropTypes.string
},
mixins: [ReactMeteorData],
getMeteorData() {
console.log(this)
return {
currentUser: Meteor.user()
};
},
submitForm(data) {
const document = this.props.document;
const modifier = {$set: _.compactObject(data)};
const collection = this.props.collection;
const methodName = this.props.methodName ? this.props.methodName : collection._name+'.edit';
Meteor.call(methodName, document._id, modifier, (error, document) => {
if (error) {
console.log(error)
Messages.flash(error.message, "error")
} else {
Messages.flash("Document edited.", "success");
if (this.props.callback) {
this.props.callback(document);
}
}
});
},
render() {
const document = this.props.document;
const collection = this.props.collection;
const fields = collection.getInsertableFields(this.data.currentUser);
return (
<div className="document-edit">
<h3>{this.props.label}</h3>
<Formsy.Form onSubmit={this.submitForm}>
{fields.map(fieldName => NovaForms.getComponent(fieldName, collection.simpleSchema()._schema[fieldName], document))}
<button type="submit" className="button button--primary">Submit</button>
</Formsy.Form>
</div>
)
}
});
module.exports = EditDocContainer;

View file

@ -24,4 +24,5 @@ const FlashContainer = React.createClass({
});
module.exports = FlashContainer;
module.exports = FlashContainer;
export default FlashContainer;

View file

@ -1,61 +0,0 @@
// const ItemContainer = React.createClass({
// propTypes: {
// collection: React.PropTypes.object.isRequired,
// selector: React.PropTypes.object.isRequired,
// publication: React.PropTypes.string,
// joins: React.PropTypes.array,
// loading: React.PropTypes.func
// },
// mixins: [ReactMeteorData],
// getMeteorData() {
// // subscribe if necessary
// if (this.props.publication) {
// const subscription = Meteor.subscribe(this.props.publication, this.props.selector);
// }
// const collection = this.props.collection;
// const document = collection.findOne(this.props.selector);
// // look for any specified joins
// if (document && this.props.joins) {
// // loop over each join
// this.props.joins.forEach(join => {
// // get the property containing the id or ids
// const joinProperty = document[join.property];
// const collection = Meteor.isClient ? window[join.collection] : global[join.collection];
// // perform the join
// if (Array.isArray(joinProperty)) { // join property is an array of ids
// document[join.joinAs] = collection.find({_id: {$in: joinProperty}}).fetch();
// } else { // join property is a single id
// document[join.joinAs] = collection.findOne({_id: joinProperty});
// }
// });
// }
// return {
// document: document,
// currentUser: Meteor.user()
// };
// },
// render() {
// const loadingComponent = this.props.loading ? this.props.loading : <p>Loading</p>
// if (this.data.document) {
// return React.cloneElement(this.props.children, { ...this.data, collection: this.props.collection });
// } else {
// return loadingComponent;
// }
// }
// });
// module.exports = ItemContainer;

View file

@ -1,144 +0,0 @@
// /*
// Example code:
// <ListContainer
// collection={Posts}
// publication="posts.list"
// terms={queryParams}
// component={PostList}
// joins={[
// {
// property: "categories",
// joinAs: "categoriesArray",
// collection: "Categories"
// },
// {
// property: "userId",
// joinAs: "user",
// collection: "Users"
// }
// ]}
// />
// Note that the joins are client-side only, and expect you to
// make the relevant data available separately.
// */
// const ListContainer = React.createClass({
// propTypes: {
// collection: React.PropTypes.object.isRequired, // the collection to paginate
// selector: React.PropTypes.object, // the selector used in collection.find()
// options: React.PropTypes.object, // the options used in collection.find()
// publication: React.PropTypes.string, // the publication to subscribe to
// terms: React.PropTypes.object, // an object passed to the publication
// limit: React.PropTypes.number, // the limit used to increase pagination
// joins: React.PropTypes.array, // joins to apply to the results
// parentProperty: React.PropTypes.string // if provided, use to generate tree
// },
// getDefaultProps: function() {
// return {
// limit: 5
// };
// },
// getInitialState() {
// return {
// limit: this.props.limit
// };
// },
// mixins: [ReactMeteorData],
// getMeteorData() {
// // initialize data object with current user, and default to data being ready
// let data = {
// currentUser: Meteor.user(),
// ready: true
// };
// // subscribe if needed. Note: always subscribe first, otherwise
// // it won't work when server-side rendering with FlowRouter SSR
// if (this.props.publication) {
// let terms = {...this.props.terms, limit: this.state.limit};
// const subscription = Meteor.subscribe(this.props.publication, terms);
// data.ready = subscription.ready();
// }
// const selector = this.props.selector || {};
// const options = {...this.props.options, limit: this.state.limit};
// const cursor = this.props.collection.find(selector, options);
// const count = cursor.count();
// // when rendering on the server, we want to get a count without the limit
// const optionsNoLimit = {...this.props.options, limit: 0};
// const cursorNoLimit = this.props.collection.find(selector, optionsNoLimit);
// const totalCount = Meteor.isClient ? Counts.get(this.props.publication) : cursorNoLimit.count();
// let results = cursor.fetch();
// // look for any specified joins
// if (this.props.joins) {
// // loop over each document in the results
// results.forEach(doc => {
// // loop over each join
// this.props.joins.forEach(join => {
// // get the property containing the id or ids
// const joinProperty = doc[join.property];
// const collection = join.collection();
// const joinLimit = join.limit ? join.limit : 0;
// // perform the join
// if (Array.isArray(joinProperty)) { // join property is an array of ids
// doc[join.joinAs] = collection.find({_id: {$in: joinProperty}}, {limit: joinLimit}).fetch();
// } else { // join property is a single id
// doc[join.joinAs] = collection.findOne({_id: joinProperty});
// }
// });
// // return the updated document
// return doc;
// });
// }
// // transform list into tree
// if (this.props.parentProperty) {
// results = Telescope.utils.unflatten(results, "_id", this.props.parentProperty);
// }
// data = {
// ...data,
// results: results,
// count: count,
// totalCount: totalCount,
// hasMore: count < totalCount
// };
// return data;
// },
// loadMore(event) {
// event.preventDefault();
// this.setState({
// limit: this.state.limit+this.props.limit
// });
// },
// render() {
// return React.cloneElement(this.props.children, { ...this.data, loadMore: this.loadMore});
// }
// });
// // export default PostListContainer;
// module.exports = ListContainer;

View file

@ -1,59 +0,0 @@
import Messages from "../messages.js";
import NovaForms from "../forms.jsx";
import Formsy from 'formsy-react';
const NewDocContainer = React.createClass({
propTypes: {
collection: React.PropTypes.object.isRequired,
label: React.PropTypes.string,
callback: React.PropTypes.func,
methodName: React.PropTypes.string
},
mixins: [ReactMeteorData],
getMeteorData() {
return {
currentUser: Meteor.user()
};
},
submitForm(data) {
// remove any empty properties
const document = _.compactObject(data);
const collection = this.props.collection;
const methodName = this.props.methodName ? this.props.methodName : collection._name+'.create';
Meteor.call(methodName, document, (error, document) => {
if (error) {
console.log(error)
Messages.flash(error.message, "error")
} else {
Messages.flash("Document created.", "success");
if (this.props.callback) {
this.props.callback(document);
}
}
});
},
render() {
const collection = this.props.collection;
const fields = collection.getInsertableFields(this.data.currentUser);
return (
<div className="new-document">
<h3>{this.props.label}</h3>
<Formsy.Form onSubmit={this.submitForm}>
{fields.map(fieldName => NovaForms.getComponent(fieldName, collection.simpleSchema()._schema[fieldName]))}
<button type="submit" className="button button--primary">Submit</button>
</Formsy.Form>
</div>
)
}
});
module.exports = NewDocContainer;

View file

@ -1,4 +1,7 @@
import Messages from "./messages.js";
import NovaForms from "./forms.jsx";
export default {Messages, NovaForms};
import FlashContainer from "./containers/FlashContainer.jsx";
import ModalButton from "./components/ModalButton.jsx";
export default {Messages, FlashContainer, ModalButton};

View file

@ -1,41 +0,0 @@
import Formsy from 'formsy-react';
import FRC from 'formsy-react-components';
const Checkbox = FRC.Checkbox;
const CheckboxGroup = FRC.CheckboxGroup;
const Input = FRC.Input;
const RadioGroup = FRC.RadioGroup;
const Select = FRC.Select;
const Textarea = FRC.Textarea;
const NovaForms = {};
NovaForms.getComponent = (fieldName, field, document) => {
let options = [];
if (field.autoform && field.autoform.options) {
options = typeof field.autoform.options === "function" ? field.autoform.options() : field.autoform.options;
}
const value = document && document[fieldName] ? document[fieldName] : "";
switch (field.control) {
case "text":
return <Input key={fieldName} name={fieldName} value={value} label={fieldName} type="text" className="text-input"/>;
case "textarea":
return <Textarea key={fieldName} name={fieldName} value={value} label={fieldName} className="textarea"/>;
case "checkbox":
return <Checkbox key={fieldName} name={fieldName} value={value} label={fieldName}/>;
case "checkboxgroup":
return <CheckboxGroup key={fieldName} name={fieldName} value={value} label={fieldName} options={options} />;
case "radiogroup":
return <RadioGroup key={fieldName} name={fieldName} value={value} label={fieldName} options={options} />;
case "select":
return <Select key={fieldName} name={fieldName} value={value} label={fieldName} options={options} />;
default:
return <Input key={fieldName} name={fieldName} value={value} label={fieldName} type="text" className="text-input"/>;
}
}
export default NovaForms;

View file

@ -15,7 +15,8 @@ Package.onUse(function(api) {
'nova:i18n@0.25.7', // lib
'nova:events@0.25.7', // lib, i18n
'nova:settings@0.25.7', // lib, i18n
'utilities:react-list-container'
'utilities:react-list-container',
'utilities:react-form-containers'
];
api.use(packages);

View file

@ -1,16 +1,5 @@
import {mount} from 'react-mounter';
//////////////////////////////////////////////////////
// Route //
//////////////////////////////////////////////////////
FlowRouter.route('/demo', {
name: 'demo',
action(params, queryParams) {
mount(MoviesWrapper);
}
});
//////////////////////////////////////////////////////
// Collection & Schema //
//////////////////////////////////////////////////////
@ -18,7 +7,7 @@ FlowRouter.route('/demo', {
Movies = new Mongo.Collection("movies");
const isLoggedIn = user => !!user;
const isOwner = (user, document) => {user._id === document.userId};
const isOwner = (user, document) => user._id === document.userId;
const schema = new SimpleSchema({
name: {
@ -60,12 +49,23 @@ const schema = new SimpleSchema({
Movies.attachSchema(schema);
//////////////////////////////////////////////////////
// Route //
//////////////////////////////////////////////////////
FlowRouter.route('/demo', {
name: 'demo',
action() {
mount(MoviesWrapper);
}
});
//////////////////////////////////////////////////////
// Methods //
//////////////////////////////////////////////////////
Movies.smartMethods({
createCallback: function (document) {
createCallback: function (user, document) {
document = _.extend(document, {
createdAt: new Date(),
userId: Meteor.userId()

View file

@ -1,5 +1,15 @@
import NoSSR from 'react-no-ssr';
import Core from 'meteor/nova:core';
import SmartContainers from "meteor/utilities:react-list-container";
import FormContainers from "meteor/utilities:react-form-containers";
FlashContainer = Core.FlashContainer;
ModalButton = Core.ModalButton;
NewDocContainer = FormContainers.NewDocContainer;
EditDocContainer = FormContainers.EditDocContainer;
ListContainer = SmartContainers.ListContainer;
//////////////////////////////////////////////////////
// MoviesWrapper //
//////////////////////////////////////////////////////
@ -8,10 +18,8 @@ MoviesWrapper = React.createClass({
render() {
({ListContainer, FlashContainer} = Telescope.components);
return (
<div>
<div className="wrapper">
<NoSSR onSSR={<p>Loading</p>}>
<LogInButtons />
@ -26,7 +34,6 @@ MoviesWrapper = React.createClass({
terms={{options: {sort: {createdAt: -1}}}}
options={{sort: {createdAt: -1}}}
joins={Movies.getJoins()}
limit={4}
>
<MoviesList/>
</ListContainer>
@ -35,7 +42,7 @@ MoviesWrapper = React.createClass({
</div>
)
}
})
});
//////////////////////////////////////////////////////
// MoviesList //
@ -45,8 +52,6 @@ MoviesList = React.createClass({
renderNew() {
({ModalButton, NewDocContainer} = Telescope.components);
const component = (
<ModalButton label="Add Movie" className="button button--primary">
<NewDocContainer collection={Movies} label="Add Movie" methodName="movies.create"/>
@ -57,7 +62,6 @@ MoviesList = React.createClass({
},
render() {
({LoadMore} = Telescope.components);
return (
<div className="movies">
@ -68,18 +72,14 @@ MoviesList = React.createClass({
)
}
});
//////////////////////////////////////////////////////
// Movie //
//////////////////////////////////////////////////////
Movie = React.createClass({
renderEdit() {
({ModalButton, EditDocContainer} = Telescope.components);
const movie = this.props;
const component = (
@ -108,4 +108,6 @@ Movie = React.createClass({
)
}
});
});
const LoadMore = props => <a href="#" className="load-more button button--primary" onClick={props.loadMore}>Load More ({props.count}/{props.totalCount})</a>