splitting settings form into field sets

This commit is contained in:
Sacha Greif 2014-09-28 16:31:12 +09:00
parent b6becdab3a
commit 51de4d79db
14 changed files with 486 additions and 47 deletions

View file

@ -1,7 +1,7 @@
<template name="settings"> <template name="settings">
<div class="grid-small grid-module dialog settings"> <div class="grid-small grid-module dialog settings">
{{#if this.hasSettings}} {{#if this.hasSettings}}
{{> quickForm collection="Settings" id="updateSettingsForm" type="update" doc=this.settings template="bootstrap3-horizontal" label-class="control-label" input-col-class="controls"}} {{> quickForm collection="Settings" id="updateSettingsForm" type="update" doc=this.settings label-class="control-label" input-col-class="controls" template="test"}}
{{else}} {{else}}
{{> quickForm collection="Settings" id="updateSettingsForm" type="insert" template="bootstrap3-horizontal" label-class="control-label" input-col-class="controls"}} {{> quickForm collection="Settings" id="updateSettingsForm" type="insert" template="bootstrap3-horizontal" label-class="control-label" input-col-class="controls"}}
{{/if}} {{/if}}

View file

@ -0,0 +1,89 @@
<template name="quickForm_test">
{{#autoForm qfAutoFormContext}}
{{#each afFieldsets}}
<fieldset>
<h3 class="fieldset-heading">{{this}}</h3>
{{> afQuickFields fields=fieldsForFieldset omitFields=../atts.omitFields template="bootstrap3-horizontal" input-col-class=inputClass label-class=labelClass}}
</fieldset>
{{/each}}
{{#if qfShouldRenderButton}}
<div class="form-group">
<div class="{{labelClass}}"></div>
<div class="{{inputClass}}">
<button {{submitButtonAtts}}>
{{#with ../atts.buttonContent}}
{{this}}
{{else}}
Submit
{{/with}}
</button>
</div>
</div>
{{/if}}
{{/autoForm}}
</template>
<template name="afFormGroup_test">
<div class="form-group {{#if afFieldIsInvalid name=this.atts.name}}has-error{{/if}}">
{{#if this.skipLabel}}
{{! We include the empty label as the easiest way to keep proper field alignment}}
<label {{afEmptyFieldLabelAtts}}></label>
{{else}}
{{> afFieldLabel afFieldLabelAtts}}
{{/if}}
<div class="{{rightColumnClass}}">
{{> afFieldInput afFieldInputAtts}}
<span class="help-block">{{{afFieldMessage name=this.atts.name}}}</span>
</div>
</div>
</template>
<template name="afObjectField_test">
<div class="form-group {{#if afFieldIsInvalid name=this.atts.name}}has-error{{/if}}">
{{> afFieldLabel afFieldLabelAtts}}
<div class="{{rightColumnClass}}">
<div class="panel panel-default autoform-padding-fix">
<div class="panel-body">
{{> afQuickFields name=this.atts.name fields=this.atts.fields omitFields=this.atts.omitFields template="bootstrap3"}}
</div>
</div>
</div>
</div>
</template>
<template name="afArrayField_test">
<div class="form-group {{#if afFieldIsInvalid name=this.atts.name}}has-error{{/if}}">
{{> afFieldLabel afFieldLabelAtts}}
<div class="{{rightColumnClass}}">
<div class="panel panel-default autoform-padding-fix">
{{#if afFieldIsInvalid name=this.atts.name}}
<div class="panel-body has-error">
<span class="help-block">{{{afFieldMessage name=this.atts.name}}}</span>
</div>
{{/if}}
<ul class="list-group">
{{#afEachArrayItem name=this.atts.name minCount=this.atts.minCount maxCount=this.atts.maxCount}}
<li class="list-group-item autoform-array-item">
<div class="media">
{{#if afArrayFieldHasMoreThanMinimum name=../atts.name minCount=../atts.minCount maxCount=../atts.maxCount}}
<button class="btn btn-primary autoform-remove-item pull-left"><span class="glyphicon glyphicon-minus"></span></button>
{{/if}}
<div class="media-body">
{{> afQuickField name=this.name label=false}}
</div>
</div>
</li>
{{/afEachArrayItem}}
{{#if afArrayFieldHasLessThanMaximum name=this.atts.name minCount=this.atts.minCount maxCount=this.atts.maxCount}}
<li class="list-group-item">
<button class="btn btn-primary autoform-add-item" data-autoform-field="{{this.atts.name}}" data-autoform-minCount="{{this.atts.minCount}}" data-autoform-maxCount="{{this.atts.maxCount}}"><span class="glyphicon glyphicon-plus"></span></button>
</li>
{{/if}}
</ul>
</div>
</div>
</div>
</template>

View file

@ -0,0 +1,130 @@
function findAtts() {
var c, n = 0;
do {
c = UI._parentData(n++);
} while (c && !c.atts);
return c && c.atts;
}
Template[getTemplate('quickForm_test')].helpers({
afFieldsets: function () {
var schema = this._af.ss._schema;
var groups = _.compact(_.uniq(_.pluckDeep(schema, 'autoform.group')));
groups = groups.map(function (group) {
return capitalise(group);
});
return groups;
},
fieldsForFieldset: function () {
var fieldset = this.toLowerCase();
var schema = Template.parentData(1)._af.ss._schema;
// decorate schema with key names
schema = _.map(schema, function (field, key) {
field.name = key;
return field;
});
// get names of fields whose group match the current fieldset
var fields = _.pluck(_.filter(schema, function (field, key) {
return field.autoform && field.autoform.group == fieldset;
}), 'name');
return fields;
},
inputClass: function inputClassHelper() {
var atts = findAtts();
if (atts) {
return atts["input-col-class"];
}
},
labelClass: function inputClassHelper() {
var atts = findAtts();
if (atts) {
return atts["label-class"];
}
},
submitButtonAtts: function bsQuickFormSubmitButtonAtts() {
var qfAtts = this.atts;
var atts = {type: "submit"};
if (typeof qfAtts.buttonClasses === "string") {
atts['class'] = qfAtts.buttonClasses;
} else {
atts['class'] = 'btn btn-primary';
}
return atts;
},
qfAutoFormContext: function () {
var ctx = _.clone(this.qfAutoFormContext || {});
if (typeof ctx["class"] === "string") {
ctx["class"] += " form-horizontal";
} else {
ctx["class"] = "form-horizontal";
}
if (ctx["input-col-class"])
delete ctx["input-col-class"];
if (ctx["label-class"])
delete ctx["label-class"];
return ctx;
}
});
Template["afFormGroup_test"].afFieldInputAtts = function () {
var atts = _.clone(this.afFieldInputAtts || {});
if ('input-col-class' in atts) {
delete atts['input-col-class'];
}
atts.template = "bootstrap3";
return atts;
};
Template["afFormGroup_test"].afFieldLabelAtts = function () {
var atts = _.clone(this.afFieldLabelAtts || {});
atts.template = "bootstrap3";
return atts;
};
Template["afFormGroup_test"].afEmptyFieldLabelAtts = function () {
var atts = _.clone(this.afFieldLabelAtts || {});
var labelAtts = _.omit(atts, 'name', 'autoform', 'template');
// Add bootstrap class if necessary
if (typeof labelAtts['class'] === "string") {
labelAtts['class'] += " control-label"; //might be added twice but that shouldn't hurt anything
} else {
labelAtts['class'] = "control-label";
}
return labelAtts;
};
Template["afFormGroup_test"].rightColumnClass = function () {
var atts = this.afFieldInputAtts || {};
return atts['input-col-class'] || "";
};
Template["afObjectField_test"].rightColumnClass = function () {
var atts = this.atts || {};
return atts['input-col-class'] || "";
};
Template["afObjectField_test"].afFieldLabelAtts = function () {
var atts = this.atts;
return {
template: "bootstrap3",
"class": atts["label-class"],
"name": atts.name
};
};
Template["afArrayField_test"].rightColumnClass = function () {
var atts = this.atts || {};
return atts['input-col-class'] || "";
};
Template["afArrayField_test"].afFieldLabelAtts = function () {
var atts = this.atts || {};
return {
template: "bootstrap3",
"class": atts["label-class"],
"name": atts.name
};
};

View file

@ -2,60 +2,94 @@ settingsSchemaObject = {
title: { title: {
type: String, type: String,
label: "Title", label: "Title",
optional: true optional: true,
autoform: {
group: 'general'
}
}, },
siteUrl: { siteUrl: {
type: String, type: String,
optional: true, optional: true,
label: 'Site URL (with trailing "/")' label: 'Site URL (with trailing "/")',
autoform: {
group: 'general'
}
}, },
tagline: { tagline: {
type: String, type: String,
label: "Tagline", label: "Tagline",
optional: true optional: true,
autoform: {
group: 'general'
}
}, },
requireViewInvite: { requireViewInvite: {
type: Boolean, type: Boolean,
label: "Require invite to view", label: "Require invite to view",
optional: true optional: true,
autoform: {
group: 'access'
}
}, },
requirePostInvite: { requirePostInvite: {
type: Boolean, type: Boolean,
label: "Require invite to post", label: "Require invite to post",
optional: true optional: true,
autoform: {
group: 'access'
}
}, },
requirePostsApproval: { requirePostsApproval: {
type: Boolean, type: Boolean,
label: "Posts must be approved by admin", label: "Posts must be approved by admin",
optional: true optional: true,
autoform: {
group: 'access'
}
}, },
emailNotifications: { emailNotifications: {
type: Boolean, type: Boolean,
label: "Enable email notifications", label: "Enable email notifications",
optional: true optional: true,
autoform: {
group: 'email'
}
}, },
nestedComments: { nestedComments: {
type: Boolean, type: Boolean,
label: "Enable nested comments", label: "Enable nested comments",
optional: true optional: true,
autoform: {
group: 'comments'
}
}, },
redistributeKarma: { redistributeKarma: {
type: Boolean, type: Boolean,
label: "Enable redistributed karma", label: "Enable redistributed karma",
optional: true optional: true,
autoform: {
group: 'general'
}
}, },
defaultEmail: { defaultEmail: {
type: String, type: String,
optional: true optional: true,
autoform: {
group: 'email'
}
}, },
scoreUpdateInterval: { scoreUpdateInterval: {
type: Number, type: Number,
optional: true optional: true,
autoform: {
group: 'scoring'
}
}, },
defaultView: { defaultView: {
type: String, type: String,
optional: true, optional: true,
autoform: { autoform: {
group: 'posts',
options: _.map(viewNav, function (view) { options: _.map(viewNav, function (view) {
return { return {
value: camelCaseify(view.label), value: camelCaseify(view.label),
@ -66,47 +100,77 @@ settingsSchemaObject = {
}, },
postInterval: { postInterval: {
type: Number, type: Number,
optional: true optional: true,
autoform: {
group: 'posts'
}
}, },
commentInterval: { commentInterval: {
type: Number, type: Number,
optional: true optional: true,
autoform: {
group: 'comments'
}
}, },
maxPostsPerDay: { maxPostsPerDay: {
type: Number, type: Number,
optional: true optional: true,
autoform: {
group: 'posts'
}
}, },
startInvitesCount: { startInvitesCount: {
type: Number, type: Number,
defaultValue: 3, defaultValue: 3,
optional: true optional: true,
autoform: {
group: 'general'
}
}, },
postsPerPage: { postsPerPage: {
type: Number, type: Number,
defaultValue: 10, defaultValue: 10,
optional: true optional: true,
autoform: {
group: 'posts'
}
}, },
logoUrl: { logoUrl: {
type: String, type: String,
optional: true optional: true,
autoform: {
group: 'logo'
}
}, },
logoHeight: { logoHeight: {
type: Number, type: Number,
optional: true optional: true,
autoform: {
group: 'logo'
}
}, },
logoWidth: { logoWidth: {
type: Number, type: Number,
optional: true optional: true,
autoform: {
group: 'logo'
}
}, },
language: { language: {
type: String, type: String,
defaultValue: 'en', defaultValue: 'en',
optional: true optional: true,
autoform: {
group: 'general'
}
}, },
backgroundCSS: { backgroundCSS: {
type: String, type: String,
optional: true, optional: true,
label: "Background CSS: color, image, etc." label: "Background CSS: color, image, etc.",
autoform: {
group: 'extras'
}
}, },
// secondaryColor: { // secondaryColor: {
// type: String, // type: String,
@ -114,51 +178,87 @@ settingsSchemaObject = {
// }, // },
buttonColor: { buttonColor: {
type: String, type: String,
optional: true optional: true,
autoform: {
group: 'colors'
}
}, },
buttonTextColor: { buttonTextColor: {
type: String, type: String,
optional: true optional: true,
autoform: {
group: 'colors'
}
}, },
headerColor: { headerColor: {
type: String, type: String,
optional: true optional: true,
autoform: {
group: 'colors'
}
}, },
headerTextColor: { headerTextColor: {
type: String, type: String,
optional: true optional: true,
autoform: {
group: 'colors'
}
}, },
twitterAccount: { twitterAccount: {
type: String, type: String,
optional: true optional: true,
autoform: {
group: 'integrations'
}
}, },
googleAnalyticsId: { googleAnalyticsId: {
type: String, type: String,
optional: true optional: true,
autoform: {
group: 'integrations'
}
}, },
mixpanelId: { mixpanelId: {
type: String, type: String,
optional: true optional: true,
autoform: {
group: 'integrations'
}
}, },
clickyId: { clickyId: {
type: String, type: String,
optional: true optional: true,
autoform: {
group: 'integrations'
}
}, },
footerCode: { footerCode: {
type: String, type: String,
optional: true optional: true,
autoform: {
group: 'extras'
}
}, },
extraCode: { extraCode: {
type: String, type: String,
optional: true optional: true,
autoform: {
group: 'extras'
}
}, },
emailFooter: { emailFooter: {
type: String, type: String,
optional: true optional: true,
autoform: {
group: 'email'
}
}, },
notes: { notes: {
type: String, type: String,
optional: true optional: true,
autoform: {
group: 'extras'
}
}, },
}; };

View file

@ -3,7 +3,10 @@ var kadiraAppIdProperty = {
propertyGroup: 'kadira', propertyGroup: 'kadira',
propertySchema: { propertySchema: {
type: String, type: String,
optional: true optional: true,
autoform: {
group: 'kadira'
}
} }
} }
addToSettingsSchema.push(kadiraAppIdProperty); addToSettingsSchema.push(kadiraAppIdProperty);
@ -13,7 +16,10 @@ var kadiraAppSecretProperty = {
propertyGroup: 'kadira', propertyGroup: 'kadira',
propertySchema: { propertySchema: {
type: String, type: String,
optional: true optional: true,
autoform: {
group: 'kadira'
}
} }
} }
addToSettingsSchema.push(kadiraAppSecretProperty); addToSettingsSchema.push(kadiraAppSecretProperty);

View file

@ -0,0 +1,75 @@
// see https://gist.github.com/furf/3208381
_.mixin({
// Get/set the value of a nested property
deep: function (obj, key, value) {
var keys = key.replace(/\[(["']?)([^\1]+?)\1?\]/g, '.$2').replace(/^\./, '').split('.'),
root,
i = 0,
n = keys.length;
// Set deep value
if (arguments.length > 2) {
root = obj;
n--;
while (i < n) {
key = keys[i++];
obj = obj[key] = _.isObject(obj[key]) ? obj[key] : {};
}
obj[keys[i]] = value;
value = root;
// Get deep value
} else {
while ((obj = obj[keys[i++]]) != null && i < n) {};
value = i < n ? void 0 : obj;
}
return value;
}
});
// Usage:
//
// var obj = {
// a: {
// b: {
// c: {
// d: ['e', 'f', 'g']
// }
// }
// }
// };
//
// Get deep value
// _.deep(obj, 'a.b.c.d[2]'); // 'g'
//
// Set deep value
// _.deep(obj, 'a.b.c.d[2]', 'george');
//
// _.deep(obj, 'a.b.c.d[2]'); // 'george'
_.mixin({
pluckDeep: function (obj, key) {
return _.map(obj, function (value) { return _.deep(value, key); });
}
});
_.mixin({
// Return a copy of an object containing all but the blacklisted properties.
unpick: function (obj) {
obj || (obj = {});
return _.pick(obj, _.difference(_.keys(obj), _.flatten(Array.prototype.slice.call(arguments, 1))));
}
});

View file

@ -37,3 +37,7 @@ trimWords = function(s, numWords) {
return expString.join(" ")+"…"; return expString.join(" ")+"…";
return s; return s;
}; };
capitalise = function (string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}

View file

@ -16,6 +16,7 @@ Package.onUse(function (api) {
api.add_files([ api.add_files([
'lib/lib.js', 'lib/lib.js',
'lib/deep.js',
'lib/deep_extend.js', 'lib/deep_extend.js',
'lib/autolink.js', 'lib/autolink.js',
'lib/permissions.js' 'lib/permissions.js'
@ -32,6 +33,8 @@ Package.onUse(function (api) {
'getThemeSetting', 'getThemeSetting',
'getSiteUrl', 'getSiteUrl',
'trimWords', 'trimWords',
'can' 'can',
'_',
'capitalise'
]); ]);
}); });

View file

@ -27,7 +27,10 @@ var embedlyKeyProperty = {
propertyName: 'embedlyKey', propertyName: 'embedlyKey',
propertySchema: { propertySchema: {
type: String, type: String,
optional: true optional: true,
autoform: {
group: 'embedly'
}
} }
} }
addToSettingsSchema.push(embedlyKeyProperty); addToSettingsSchema.push(embedlyKeyProperty);

View file

@ -48,7 +48,10 @@ var showBanner = {
propertySchema: { propertySchema: {
type: Boolean, type: Boolean,
optional: true, optional: true,
label: 'Show newsletter sign-up banner' label: 'Show newsletter sign-up banner',
autoform: {
group: 'newsletter'
}
} }
} }
addToSettingsSchema.push(showBanner); addToSettingsSchema.push(showBanner);
@ -58,6 +61,9 @@ var mailChimpAPIKey = {
propertySchema: { propertySchema: {
type: String, type: String,
optional: true, optional: true,
autoform: {
group: 'newsletter'
}
} }
} }
addToSettingsSchema.push(mailChimpAPIKey); addToSettingsSchema.push(mailChimpAPIKey);
@ -67,6 +73,9 @@ var mailChimpListId = {
propertySchema: { propertySchema: {
type: String, type: String,
optional: true, optional: true,
autoform: {
group: 'newsletter'
}
} }
} }
addToSettingsSchema.push(mailChimpListId); addToSettingsSchema.push(mailChimpListId);
@ -75,7 +84,10 @@ var postsPerNewsletter = {
propertyName: 'postsPerNewsletter', propertyName: 'postsPerNewsletter',
propertySchema: { propertySchema: {
type: Number, type: Number,
optional: true optional: true,
autoform: {
group: 'newsletter'
}
} }
} }
addToSettingsSchema.push(postsPerNewsletter); addToSettingsSchema.push(postsPerNewsletter);
@ -86,6 +98,7 @@ var newsletterFrequency = {
type: Number, type: Number,
optional: true, optional: true,
autoform: { autoform: {
group: 'newsletter',
options: [ options: [
{ {
value: 1, value: 1,

View file

@ -74,7 +74,6 @@ scheduleNextCampaign = function (isTest) {
return scheduleCampaign(buildCampaign(posts), isTest); return scheduleCampaign(buildCampaign(posts), isTest);
}else{ }else{
var result = 'No posts to schedule today…'; var result = 'No posts to schedule today…';
console.log(result)
return result return result
} }
} }

View file

@ -312,6 +312,16 @@ input[type="search"] {
.comment-field { .comment-field {
margin-bottom: 10px; } margin-bottom: 10px; }
/* line 173, ../scss/global/_forms.scss */
.fieldset-heading {
padding-bottom: 5px;
margin-bottom: 15px;
border-bottom: 1px solid #B5B0B0; }
/* line 178, ../scss/global/_forms.scss */
fieldset {
margin-bottom: 30px; }
/* line 1, ../scss/global/_links.scss */ /* line 1, ../scss/global/_links.scss */
a { a {
text-decoration: none; } text-decoration: none; }

View file

@ -170,3 +170,11 @@ input[type="search"]{
.comment-field{ .comment-field{
margin-bottom: 10px; margin-bottom: 10px;
} }
.fieldset-heading{
padding-bottom: 5px;
margin-bottom: 15px;
border-bottom: 1px solid #B5B0B0;
}
fieldset{
margin-bottom: 30px;
}

View file

@ -20,7 +20,6 @@
// Specific Styles (header, posts, etc.) // Specific Styles (header, posts, etc.)
@import "specific/accounts";
@import "specific/dropdown"; @import "specific/dropdown";
@import "specific/errors"; @import "specific/errors";
@import "specific/header"; @import "specific/header";