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">
<div class="grid-small grid-module dialog settings">
{{#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}}
{{> quickForm collection="Settings" id="updateSettingsForm" type="insert" template="bootstrap3-horizontal" label-class="control-label" input-col-class="controls"}}
{{/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: {
type: String,
label: "Title",
optional: true
optional: true,
autoform: {
group: 'general'
}
},
siteUrl: {
type: String,
optional: true,
label: 'Site URL (with trailing "/")'
label: 'Site URL (with trailing "/")',
autoform: {
group: 'general'
}
},
tagline: {
type: String,
label: "Tagline",
optional: true
optional: true,
autoform: {
group: 'general'
}
},
requireViewInvite: {
type: Boolean,
label: "Require invite to view",
optional: true
optional: true,
autoform: {
group: 'access'
}
},
requirePostInvite: {
type: Boolean,
label: "Require invite to post",
optional: true
optional: true,
autoform: {
group: 'access'
}
},
requirePostsApproval: {
type: Boolean,
label: "Posts must be approved by admin",
optional: true
optional: true,
autoform: {
group: 'access'
}
},
emailNotifications: {
type: Boolean,
label: "Enable email notifications",
optional: true
optional: true,
autoform: {
group: 'email'
}
},
nestedComments: {
type: Boolean,
label: "Enable nested comments",
optional: true
optional: true,
autoform: {
group: 'comments'
}
},
redistributeKarma: {
type: Boolean,
label: "Enable redistributed karma",
optional: true
optional: true,
autoform: {
group: 'general'
}
},
defaultEmail: {
type: String,
optional: true
optional: true,
autoform: {
group: 'email'
}
},
scoreUpdateInterval: {
type: Number,
optional: true
optional: true,
autoform: {
group: 'scoring'
}
},
defaultView: {
type: String,
optional: true,
autoform: {
group: 'posts',
options: _.map(viewNav, function (view) {
return {
value: camelCaseify(view.label),
@ -66,47 +100,77 @@ settingsSchemaObject = {
},
postInterval: {
type: Number,
optional: true
optional: true,
autoform: {
group: 'posts'
}
},
commentInterval: {
type: Number,
optional: true
optional: true,
autoform: {
group: 'comments'
}
},
maxPostsPerDay: {
type: Number,
optional: true
optional: true,
autoform: {
group: 'posts'
}
},
startInvitesCount: {
type: Number,
defaultValue: 3,
optional: true
optional: true,
autoform: {
group: 'general'
}
},
postsPerPage: {
type: Number,
defaultValue: 10,
optional: true
optional: true,
autoform: {
group: 'posts'
}
},
logoUrl: {
type: String,
optional: true
optional: true,
autoform: {
group: 'logo'
}
},
logoHeight: {
type: Number,
optional: true
type: Number,
optional: true,
autoform: {
group: 'logo'
}
},
logoWidth: {
type: Number,
optional: true
type: Number,
optional: true,
autoform: {
group: 'logo'
}
},
language: {
type: String,
defaultValue: 'en',
optional: true
type: String,
defaultValue: 'en',
optional: true,
autoform: {
group: 'general'
}
},
backgroundCSS: {
type: String,
optional: true,
label: "Background CSS: color, image, etc."
label: "Background CSS: color, image, etc.",
autoform: {
group: 'extras'
}
},
// secondaryColor: {
// type: String,
@ -114,51 +178,87 @@ settingsSchemaObject = {
// },
buttonColor: {
type: String,
optional: true
optional: true,
autoform: {
group: 'colors'
}
},
buttonTextColor: {
type: String,
optional: true
optional: true,
autoform: {
group: 'colors'
}
},
headerColor: {
type: String,
optional: true
optional: true,
autoform: {
group: 'colors'
}
},
headerTextColor: {
type: String,
optional: true
optional: true,
autoform: {
group: 'colors'
}
},
twitterAccount: {
type: String,
optional: true
optional: true,
autoform: {
group: 'integrations'
}
},
googleAnalyticsId: {
type: String,
optional: true
optional: true,
autoform: {
group: 'integrations'
}
},
mixpanelId: {
type: String,
optional: true
optional: true,
autoform: {
group: 'integrations'
}
},
clickyId: {
type: String,
optional: true
optional: true,
autoform: {
group: 'integrations'
}
},
footerCode: {
type: String,
optional: true
optional: true,
autoform: {
group: 'extras'
}
},
extraCode: {
type: String,
optional: true
optional: true,
autoform: {
group: 'extras'
}
},
emailFooter: {
type: String,
optional: true
optional: true,
autoform: {
group: 'email'
}
},
notes: {
type: String,
optional: true
optional: true,
autoform: {
group: 'extras'
}
},
};

View file

@ -3,7 +3,10 @@ var kadiraAppIdProperty = {
propertyGroup: 'kadira',
propertySchema: {
type: String,
optional: true
optional: true,
autoform: {
group: 'kadira'
}
}
}
addToSettingsSchema.push(kadiraAppIdProperty);
@ -13,7 +16,10 @@ var kadiraAppSecretProperty = {
propertyGroup: 'kadira',
propertySchema: {
type: String,
optional: true
optional: true,
autoform: {
group: 'kadira'
}
}
}
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 s;
};
capitalise = function (string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}

View file

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

View file

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

View file

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

View file

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

View file

@ -312,6 +312,16 @@ input[type="search"] {
.comment-field {
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 */
a {
text-decoration: none; }

View file

@ -169,4 +169,12 @@ input[type="search"]{
}
.comment-field{
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.)
@import "specific/accounts";
@import "specific/dropdown";
@import "specific/errors";
@import "specific/header";