diff --git a/.meteor/versions b/.meteor/versions index 2a41d6f75..6ee9f0fe1 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -137,7 +137,6 @@ 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 diff --git a/packages/nova-forms/lib/EditDocument.jsx b/packages/nova-forms/lib/EditDocument.jsx index f60bac4a1..69bfebf98 100644 --- a/packages/nova-forms/lib/EditDocument.jsx +++ b/packages/nova-forms/lib/EditDocument.jsx @@ -17,17 +17,25 @@ const EditDocument = React.createClass({ labelFunction: React.PropTypes.func }, + getFields() { + const collection = this.props.collection; + const fields = collection.getInsertableFields(this.props.currentUser); + return fields; + }, + submitForm(data) { - console.log(data) - + const fields = this.getFields(); const document = this.props.document; - const modifier = {$set: _.compactObject(Utils.flatten(data))}; + // put all keys with data on $set + const set = _.compactObject(Utils.flatten(data)); + // put all keys without data on $unset + const unsetKeys = _.difference(fields, _.keys(set)); + const unset = _.object(unsetKeys, unsetKeys.map(()=>true)); + const modifier = {$set: set, $unset: unset}; 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) @@ -49,7 +57,7 @@ const EditDocument = React.createClass({ const document = this.props.document; const collection = this.props.collection; - const fields = collection.getInsertableFields(this.props.currentUser); + const fields = this.getFields(); const style = { maxWidth: "800px", @@ -59,7 +67,11 @@ const EditDocument = React.createClass({ return (
- {fields.map(fieldName =>
{SmartForms.getComponent(fieldName, collection.simpleSchema()._schema[fieldName], this.props.labelFunction, document)}
)} + {fields.map(fieldName => +
+ {SmartForms.getComponent(fieldName, collection.simpleSchema()._schema[fieldName], this.props.labelFunction, document)} +
+ )}
diff --git a/packages/nova-forms/lib/component.js b/packages/nova-forms/lib/component.js new file mode 100644 index 000000000..69d1f1573 --- /dev/null +++ b/packages/nova-forms/lib/component.js @@ -0,0 +1,159 @@ +'use strict'; + +var React = require('react'); + +module.exports = { + + propTypes: { + layout: React.PropTypes.string, + validatePristine: React.PropTypes.bool, + rowClassName: React.PropTypes.oneOfType([ + React.PropTypes.string, + React.PropTypes.array, + React.PropTypes.object + ]), + labelClassName: React.PropTypes.oneOfType([ + React.PropTypes.string, + React.PropTypes.array, + React.PropTypes.object + ]), + elementWrapperClassName: React.PropTypes.oneOfType([ + React.PropTypes.string, + React.PropTypes.array, + React.PropTypes.object + ]) + }, + + contextTypes: { + layout: React.PropTypes.string, + validatePristine: React.PropTypes.bool, + rowClassName: React.PropTypes.oneOfType([ + React.PropTypes.string, + React.PropTypes.array, + React.PropTypes.object + ]), + labelClassName: React.PropTypes.oneOfType([ + React.PropTypes.string, + React.PropTypes.array, + React.PropTypes.object + ]), + elementWrapperClassName: React.PropTypes.oneOfType([ + React.PropTypes.string, + React.PropTypes.array, + React.PropTypes.object + ]) + }, + + getDefaultProps: function() { + return { + disabled: false, + validatePristine: false, + onChange: function() {}, + onFocus: function() {}, + onBlur: function() {} + }; + }, + + /** + * Accessors for "special" properties. + * + * The following methods are used to merge master default properties that + * are optionally set on the parent form. This to to allow customising these + * properties 'as a whole' for the form, while retaining the ability to + * override the properties on a component basis. + * + * Also see the parent-context mixin. + */ + getLayout: function() { + var defaultProperty = this.context.layout || 'horizontal'; + return this.props.layout ? this.props.layout : defaultProperty; + }, + + getValidatePristine: function() { + var defaultProperty = this.context.validatePristine || false; + return this.props.validatePristine ? this.props.validatePristine : defaultProperty; + }, + + getRowClassName: function() { + return [this.context.rowClassName, this.props.rowClassName]; + }, + + getLabelClassName: function() { + return [this.context.labelClassName, this.props.labelClassName]; + }, + + getElementWrapperClassName: function() { + return [this.context.elementWrapperClassName, this.props.elementWrapperClassName]; + }, + + getRowProperties: function() { + return { + label: this.props.label, + rowClassName: this.getRowClassName(), + labelClassName: this.getLabelClassName(), + elementWrapperClassName: this.getElementWrapperClassName(), + layout: this.getLayout(), + required: this.isRequired(), + hasErrors: this.showErrors() + }; + }, + + hashString: function(string) { + var hash = 0; + for (var i = 0; i < string.length; i++) { + hash = (((hash << 5) - hash) + string.charCodeAt(i)) & 0xFFFFFFFF; + } + return hash; + }, + + /** + * getId + * + * The ID is used as an attribute on the form control, and is used to allow + * associating the label element with the form control. + * + * If we don't explicitly pass an `id` prop, we generate one based on the + * `name` and `label` properties. + */ + getId: function() { + if (this.props.id) { + return this.props.id; + } + var label = (typeof this.props.label === 'undefined' ? '' : this.props.label); + return [ + 'frc', + this.props.name.split('[').join('_').replace(']', ''), + this.hashString(JSON.stringify(label)) + ].join('-'); + }, + + renderHelp: function() { + if (!this.props.help) { + return ''; + } + return ( + {this.props.help} + ); + }, + + renderErrorMessage: function() { + if (!this.showErrors()) { + return ''; + } + var errorMessages = this.getErrorMessages() || []; + return errorMessages.map((message, key) => { + return ( + {message} + ); + }); + }, + + showErrors: function() { + if (this.isPristine() === true) { + if (this.getValidatePristine() === false) { + return false; + } + } + return (this.isValid() === false); + } +}; \ No newline at end of file diff --git a/packages/nova-forms/lib/row.js b/packages/nova-forms/lib/row.js new file mode 100644 index 000000000..f685e45f3 --- /dev/null +++ b/packages/nova-forms/lib/row.js @@ -0,0 +1,126 @@ +/*jshint node:true */ + +'use strict'; + +var React = require('react'); +var classNames = require('classnames/dedupe'); + +var Row = React.createClass({ + + propTypes: { + label: React.PropTypes.node, + rowClassName: React.PropTypes.oneOfType([ + React.PropTypes.string, + React.PropTypes.array, + React.PropTypes.object + ]), + labelClassName: React.PropTypes.oneOfType([ + React.PropTypes.string, + React.PropTypes.array, + React.PropTypes.object + ]), + elementWrapperClassName: React.PropTypes.oneOfType([ + React.PropTypes.string, + React.PropTypes.array, + React.PropTypes.object + ]), + required: React.PropTypes.bool, + hasErrors: React.PropTypes.bool, + fakeLabel: React.PropTypes.bool, + layout: React.PropTypes.oneOf(['horizontal', 'vertical', 'elementOnly']), + htmlFor: React.PropTypes.string + }, + + getDefaultProps: function() { + return { + label: '', + rowClassName: '', + labelClassName: '', + elementWrapperClassName: '', + required: false, + hasErrors: false, + fakeLabel: false + }; + }, + + renderLabel: function() { + + if (this.props.layout === 'elementOnly') { + return ''; + } + + var labelClassNames = []; + labelClassNames.push('control-label'); + + if (this.props.layout === 'horizontal') { + labelClassNames.push('col-sm-3'); + } + + labelClassNames.push(this.props.labelClassName); + + if (this.props.fakeLabel) { + return ( +
+ + {this.props.label} + {this.props.required ? ' *' : null} + +
+ ); + } + return ( + + ); + }, + + render: function() { + + if (this.props.layout === 'elementOnly') { + return ( + + {this.props.children} + + ); + } + + var cssClasses = { + row: ['form-group'], + elementWrapper: [] + }; + + if (this.props.hasErrors) { + cssClasses.row.push('has-error'); + cssClasses.row.push('has-feedback'); + } + + var element = this.props.children; + if (this.props.layout === 'horizontal') { + + // Horizontal layout needs a 'row' class for Bootstrap 4 + cssClasses.row.push('row'); + + cssClasses.elementWrapper.push('col-sm-9'); + cssClasses.elementWrapper.push(this.props.elementWrapperClassName); + + element = ( +
+ {this.props.children} +
+ ); + } + + cssClasses.row.push(this.props.rowClassName); + return ( +
+ {this.renderLabel()} + {element} +
+ ); + } + +}); + +module.exports = Row; \ No newline at end of file diff --git a/packages/nova-forms/lib/smart-forms.jsx b/packages/nova-forms/lib/smart-forms.jsx index f3aaf8ffa..f140771b6 100644 --- a/packages/nova-forms/lib/smart-forms.jsx +++ b/packages/nova-forms/lib/smart-forms.jsx @@ -5,7 +5,7 @@ import FRC from 'formsy-react-components'; import Utils from './utils.js'; const Checkbox = FRC.Checkbox; -const CheckboxGroup = FRC.CheckboxGroup; +// const CheckboxGroup = FRC.CheckboxGroup; const Input = FRC.Input; const RadioGroup = FRC.RadioGroup; const Select = FRC.Select; @@ -38,7 +38,7 @@ SmartForms.getComponent = (fieldName, field, labelFunction, document) => { return ; // note: checkboxgroup cause React refs error, so use RadioGroup for now case "checkboxgroup": - return ; + return ; case "radiogroup": return ; case "select": @@ -48,4 +48,80 @@ SmartForms.getComponent = (fieldName, field, labelFunction, document) => { } } -export default SmartForms; \ No newline at end of file +export default SmartForms; + + +import ComponentMixin from './component'; +import Row from './row'; + +const CheckboxGroup = React.createClass({ + + mixins: [Formsy.Mixin, ComponentMixin], + + propTypes: { + name: React.PropTypes.string.isRequired, + options: React.PropTypes.array.isRequired + }, + + getDefaultProps: function() { + return { + label: '', + help: null + }; + }, + + changeCheckbox: function() { + var value = []; + this.props.options.forEach(function(option, key) { + if (this.refs['element-' + key].checked) { + value.push(option.value); + } + + }.bind(this)); + this.setValue(value); + this.props.onChange(this.props.name, value); + }, + + renderElement: function() { + var _this = this; + var controls = this.props.options.map(function(checkbox, key) { + var checked = (_this.getValue().indexOf(checkbox.value) !== -1); + var disabled = _this.isFormDisabled() || checkbox.disabled || _this.props.disabled; + return ( +
+ +
+ ); + }); + return controls; + }, + + render: function() { + + if (this.getLayout() === 'elementOnly') { + return ( +
{this.renderElement()}
+ ); + } + + return ( + + {this.renderElement()} + {this.renderHelp()} + {this.renderErrorMessage()} + + ); + } +}); diff --git a/sample_settings.json b/sample_settings.json index 2958064e5..7e02f3cc2 100644 --- a/sample_settings.json +++ b/sample_settings.json @@ -17,7 +17,7 @@ "postInterval": 20, "RSSLinksPointTo": "link", "commentInterval": 20, - "maxPostsPerDay": 10 + "maxPostsPerDay": 10, "startInvitesCount": 5, "postsPerPage": 10, @@ -27,7 +27,7 @@ "facebookPage": "http://facebook.com/foo", "googleAnalyticsId":"123foo", - "embedlyKey":"123foo", + "embedlyKey":"123foo" },