mirror of
https://github.com/vale981/Vulcan
synced 2025-03-05 09:31:43 -05:00
working on demo; extracting forms logic into package
This commit is contained in:
parent
f9c0faba89
commit
5b8c8d92a9
16 changed files with 56 additions and 404 deletions
|
@ -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
|
||||
|
|
|
@ -55,4 +55,8 @@ body{
|
|||
.comment-body{
|
||||
border: 1px solid #ddd;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.new-post-button{
|
||||
margin-bottom: 15px;
|
||||
}
|
|
@ -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'));
|
||||
|
|
|
@ -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;
|
|
@ -34,4 +34,5 @@ const AppContainer = React.createClass({
|
|||
|
||||
});
|
||||
|
||||
module.exports = AppContainer;
|
||||
module.exports = AppContainer;
|
||||
export default AppContainer;
|
|
@ -15,4 +15,5 @@ const CurrentUserContainer = React.createClass({
|
|||
|
||||
});
|
||||
|
||||
module.exports = CurrentUserContainer;
|
||||
module.exports = CurrentUserContainer;
|
||||
export default CurrentUserContainer;
|
|
@ -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;
|
|
@ -24,4 +24,5 @@ const FlashContainer = React.createClass({
|
|||
|
||||
});
|
||||
|
||||
module.exports = FlashContainer;
|
||||
module.exports = FlashContainer;
|
||||
export default FlashContainer;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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};
|
|
@ -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;
|
|
@ -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);
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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>
|
Loading…
Add table
Reference in a new issue