include forms package in repo; add cache for FR-SSR; fix modal window callback issue; fix posts.single publication and subscription bug

This commit is contained in:
Sacha Greif 2016-03-30 19:08:06 +09:00
parent 40d3a737bf
commit b23b7eea73
19 changed files with 321 additions and 30 deletions

View file

@ -23,6 +23,7 @@ nova:getting-started
nova:categories
nova:share
nova:voting
nova:forms
nova:embedly
nova:base-components
nova:base-styles

View file

@ -88,6 +88,7 @@ nova:core@0.25.7
nova:email@0.25.7
nova:embedly@0.25.7
nova:events@0.25.7
nova:forms@0.25.7
nova:getting-started@0.25.7
nova:i18n@0.25.7
nova:lib@0.25.7
@ -136,6 +137,7 @@ tap:i18n@1.7.0
templating@1.1.7
templating-tools@1.0.2
tmeasday:check-npm-versions@0.1.1
tmeasday:healthcheck-handler@0.0.2
tmeasday:publish-counts@0.7.3
tracker@1.0.11
twitter@1.1.7
@ -143,7 +145,6 @@ ui@1.0.9
underscore@1.0.6
url@1.0.7
utilities:avatar@0.9.2
utilities:react-form-containers@0.1.7
utilities:react-list-container@0.1.6
utilities:smart-methods@0.1.2
utilities:smart-publications@0.1.4

View file

@ -30,7 +30,6 @@ const CategoriesList = ({categories}, context) => {
};
CategoriesList.contextTypes = {
currentRoute: React.PropTypes.object
};

View file

@ -1,11 +1,12 @@
import React, { PropTypes, Component } from 'react';
import { Button } from 'react-bootstrap';
import Router from '../router.js'
import Core from "meteor/nova:core";
const Messages = Core.Messages;
const ModalTrigger = Core.ModalTrigger;
import ReactForms from "meteor/utilities:react-form-containers";
import ReactForms from "meteor/nova:forms";
const NewDocument = ReactForms.NewDocument;
const NewPostButton = (props, context) => {

View file

@ -1,5 +1,5 @@
import React, { PropTypes, Component } from 'react';
import ReactForms from "meteor/utilities:react-form-containers";
import ReactForms from "meteor/nova:forms";
const EditDocument = ReactForms.EditDocument;
import Core from "meteor/nova:core";

View file

@ -47,7 +47,7 @@ Router.route('/posts/:_id/:slug?', {
<DocumentContainer
collection={Posts}
publication="posts.single"
selector={params}
selector={{_id: params._id}}
terms={params}
joins={Posts.getJoins()}
component={PostPage}

View file

@ -27,8 +27,7 @@ Package.onUse(function (api) {
// 'studiointeract:react-accounts-ui-basic@1.0.1',
'studiointeract:react-accounts-ui@1.0.7',
'dburles:spacebars-tohtml@1.0.1',
'utilities:react-list-container',
'utilities:react-form-containers'
'utilities:react-list-container'
]);
api.addFiles([

View file

@ -38,27 +38,14 @@ class ModalTrigger extends Component {
this.setState({modalIsOpen: false});
}
getChildContext() {
const component = this;
return {
closeCallback: component.closeModal
};
}
render() {
// see http://stackoverflow.com/a/32371612/649299
const childrenWithProps = React.Children.map(this.props.children, (child) => {
// if child component already has a successCallback, create new callback
// that both calls original callback and also closes modal
let successCallback;
if (child.props.successCallback) {
successCallback = (document) => {
child.props.successCallback(document);
this.closeModal();
}
} else {
successCallback = this.closeModal;
}
return React.cloneElement(child, { successCallback: successCallback });
});
const triggerComponent = React.cloneElement(this.props.component, { onClick: this.openModal });
@ -70,7 +57,7 @@ class ModalTrigger extends Component {
onRequestClose={this.closeModal}
style={customStyles}
>
{childrenWithProps}
{this.props.children}
</Modal>
</div>
)
@ -81,5 +68,9 @@ ModalTrigger.propTypes = {
component: React.PropTypes.object.isRequired
}
ModalTrigger.childContextTypes = {
closeCallback: React.PropTypes.func
}
module.exports = ModalTrigger;
export default ModalTrigger;

View file

@ -24,6 +24,12 @@ FlowRouter.removeFromQueryArray = function (key, value) {
FlowRouter.setQueryParams(params);
}
if(Meteor.isServer) {
var timeInMillis = 1000 * 10; // 10 secs
FlowRouter.setPageCacheTimeout(timeInMillis);
FlowRouter.setDeferScriptLoading(true);
}
// FlowRouter.notFound = {
// action: function() {
// if (Meteor.isClient) {

View file

@ -4,7 +4,7 @@ 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";
import FormContainers from "meteor/nova:forms";
const ModalTrigger = Core.ModalTrigger;
const NewDocument = FormContainers.NewDocument;

View file

@ -0,0 +1,24 @@
# React Forms
This package provides two components (`NewDocument` and `EditDocument`) that work with the schema extension defined in the [smart-methods](https://github.com/meteor-utilities/smart-methods) package to let you easily generate new document and edit document forms.
### Install
`meteor add utilities:react-form-containers`
### `NewDocument`
This component takes the following properties:
- `collection`: the collection in which to insert the new document.
- `currentUser`: the current user.
- `errorCallback`: a function to call on error.
- `successCallback`: a function to call on success.
- `methodName`: the name of the method to submit the form to.
- `labelFunction`: a function that will be called on each field's name to get the label (for example, an internationalization function).
### `EditDocument`
This component takes the same properties as `NewDocument`, plus:
- `document`: the document being edited.

View file

@ -0,0 +1,75 @@
import React, { PropTypes, Component } from 'react';
import Formsy from 'formsy-react';
import { Button } from 'react-bootstrap';
import SmartForms from "./smart-forms.jsx";
import Utils from './utils.js';
const EditDocument = React.createClass({
propTypes: {
document: React.PropTypes.object.isRequired,
collection: React.PropTypes.object.isRequired,
currentUser: React.PropTypes.object,
successCallback: React.PropTypes.func,
errorCallback: React.PropTypes.func,
methodName: React.PropTypes.string,
labelFunction: React.PropTypes.func
},
submitForm(data) {
console.log(data)
const document = this.props.document;
const modifier = {$set: _.compactObject(Utils.flatten(data))};
const collection = this.props.collection;
const methodName = this.props.methodName ? this.props.methodName : collection._name+'.edit';
console.log(modifier)
Meteor.call(methodName, document._id, modifier, (error, document) => {
if (error) {
console.log(error)
if (this.props.errorCallback) {
this.props.errorCallback(document);
}
} else {
if (this.props.successCallback) {
this.props.successCallback(document);
}
if (this.context.closeCallback) {
this.context.closeCallback();
}
}
});
},
render() {
const document = this.props.document;
const collection = this.props.collection;
const fields = collection.getInsertableFields(this.props.currentUser);
const style = {
maxWidth: "800px",
width: "100%"
}
return (
<div className="edit-document" style={style}>
<Formsy.Form onSubmit={this.submitForm}>
{fields.map(fieldName => <div key={fieldName} className={"input-"+fieldName}>{SmartForms.getComponent(fieldName, collection.simpleSchema()._schema[fieldName], this.props.labelFunction, document)}</div>)}
<Button type="submit" bsStyle="primary">Submit</Button>
</Formsy.Form>
</div>
)
}
});
EditDocument.contextTypes = {
closeCallback: React.PropTypes.func
}
module.exports = EditDocument;
export default EditDocument;

View file

@ -0,0 +1,69 @@
import React, { PropTypes, Component } from 'react';
import Formsy from 'formsy-react';
import { Button } from 'react-bootstrap';
import SmartForms from "./smart-forms.jsx";
import Utils from './utils.js';
const NewDocument = React.createClass({
propTypes: {
collection: React.PropTypes.object.isRequired,
currentUser: React.PropTypes.object,
errorCallback: React.PropTypes.func,
successCallback: React.PropTypes.func,
methodName: React.PropTypes.string,
labelFunction: React.PropTypes.func
},
submitForm(data) {
// remove any empty properties
const document = _.compactObject(Utils.flatten(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)
if (this.props.errorCallback) {
this.props.errorCallback(document);
}
} else {
if (this.props.successCallback) {
this.props.successCallback(document);
}
if (this.context.closeCallback) {
this.context.closeCallback();
}
}
});
},
render() {
const collection = this.props.collection;
const fields = collection.getInsertableFields(this.props.currentUser);
const style = {
maxWidth: "800px",
width: "100%"
}
return (
<div className="new-document" style={style}>
<Formsy.Form onSubmit={this.submitForm}>
{fields.map(fieldName => <div key={fieldName} className={"input-"+fieldName}>{SmartForms.getComponent(fieldName, collection.simpleSchema()._schema[fieldName], this.props.labelFunction)}</div>)}
<Button type="submit" bsStyle="primary">Submit</Button>
</Formsy.Form>
</div>
)
}
});
NewDocument.contextTypes = {
closeCallback: React.PropTypes.func
}
module.exports = NewDocument;
export default NewDocument;

View file

@ -0,0 +1,13 @@
import { checkNpmVersions } from 'meteor/tmeasday:check-npm-versions';
checkNpmVersions({
"react": "^0.14.6",
"formsy-react": "^0.17.0",
"formsy-react-components": "^0.6.6",
"react-bootstrap": "^0.28.3"
// 'rebass': '^0.2.4',
});
import NewDocument from "./NewDocument.jsx";
import EditDocument from "./EditDocument.jsx";
export default {NewDocument, EditDocument};

View file

@ -0,0 +1,51 @@
import React, { PropTypes, Component } from 'react';
import Formsy from 'formsy-react';
import FRC from 'formsy-react-components';
import Utils from './utils.js';
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 SmartForms = {};
SimpleSchema.extendOptions({
insertableIf: Match.Optional(Function),
editableIf: Match.Optional(Function)
});
SmartForms.getComponent = (fieldName, field, labelFunction, document) => {
let options = [];
if (field.autoform && field.autoform.options) {
options = typeof field.autoform.options === "function" ? field.autoform.options() : field.autoform.options;
}
const value = document && Utils.deepValue(document, fieldName) ? Utils.deepValue(document, fieldName) : "";
const label = typeof labelFunction === "function" ? labelFunction(fieldName) : fieldName;
switch (field.control) {
case "text":
return <Input key={fieldName} name={fieldName} value={value} label={label} type="text" />;
case "textarea":
return <Textarea key={fieldName} name={fieldName} value={value} label={label} />;
case "checkbox":
return <Checkbox key={fieldName} name={fieldName} value={value} label={label}/>;
// note: checkboxgroup cause React refs error, so use RadioGroup for now
case "checkboxgroup":
return <RadioGroup key={fieldName} name={fieldName} value={value} label={label} options={options} />;
case "radiogroup":
return <RadioGroup key={fieldName} name={fieldName} value={value} label={label} options={options} />;
case "select":
return <Select key={fieldName} name={fieldName} value={value} label={label} options={options} />;
default:
return <Input key={fieldName} name={fieldName} value={value} label={label} type="text" />;
}
}
export default SmartForms;

View file

@ -0,0 +1,37 @@
const Utils = {};
// add support for nested properties
Utils.deepValue = function(obj, path){
for (var i=0, path=path.split('.'), len=path.length; i<len; i++){
obj = obj[path[i]];
};
return obj;
};
// see http://stackoverflow.com/questions/19098797/fastest-way-to-flatten-un-flatten-nested-json-objects
Utils.flatten = function(data) {
var result = {};
function recurse (cur, prop) {
if (Object.prototype.toString.call(cur) !== "[object Object]") {
result[prop] = cur;
} else if (Array.isArray(cur)) {
for(var i=0, l=cur.length; i<l; i++)
recurse(cur[i], prop + "[" + i + "]");
if (l == 0)
result[prop] = [];
} else {
var isEmpty = true;
for (var p in cur) {
isEmpty = false;
recurse(cur[p], prop ? prop+"."+p : p);
}
if (isEmpty && prop)
result[prop] = {};
}
}
recurse(data, "");
return result;
}
export default Utils;

View file

@ -0,0 +1,23 @@
Package.describe({
name: "nova:forms",
summary: "Form containers for React",
version: "0.25.7",
git: "https://github.com/meteor-utilities/react-form-containers.git"
});
Package.onUse(function(api) {
api.versionsFrom("METEOR@1.3");
api.use([
'ecmascript',
'check',
'tmeasday:check-npm-versions@0.1.1',
'aldeed:simple-schema@1.5.3',
'aldeed:collection2@2.8.0',
'utilities:smart-methods@0.1.2'
]);
api.mainModule("lib/export.js", ["client", "server"]);
});

View file

@ -67,6 +67,7 @@ Package.onUse(function (api) {
// 'seba:minifiers-autoprefixer@0.0.1',
'meteorhacks:unblock@1.1.0',
// 'kadira:flow-router@2.10.1',
// 'tmeasday:healthcheck-handler@0.0.2',
'kadira:flow-router-ssr@3.12.2',
'arillo:flow-router-helpers@0.5.0',
// 'peerlibrary:reactive-publish@0.2.0',

View file

@ -94,7 +94,7 @@ Meteor.publish('posts.single', function (terms) {
const currentUser = Meteor.users.findOne(this.userId);
const options = {fields: Posts.publishedFields.single};
const posts = Posts.find(terms, options);
const posts = Posts.find(terms._id, options);
const post = posts.fetch()[0];
const users = getSinglePostUsers(post);