var Author = function(form, initial, name, affiliations){
var self = this;
// self.form = form; // これをすると依存関係の検出時に無限ループに落ち込む
// self.form = function(){ return form; } // これならOK
self.initial = ko.observable(initial).extend({
required: true,
pattern: {
message: 'First and middle names should be typed as initial(s) '+
'that is expected to have a period character "." at the end.',
params: '\\.$'
}
});
self.name = ko.observable(name).extend({ required: true });
self.affiliations = ko.observable(affiliations).extend({
required: true,
pattern: {
message: 'Affiliation must be specified by comma separated numbers.',
params: '^ *[1-9][0-9]*( *, *[1-9][0-9]*)* *$'
},
validation: [ {
validator: function(val) {
var ok = true;
_.each(val.split(/ *, */), function(a) {
if(Number(a) > form.affiliations().length ||
(Number(a) == 0) )
ok = false;
});
return ok;
},
message: 'Specified affiliation number does not exist.'
}, {
validator: function(val) {
var as = _.sortBy(
_.map( val.split(/ *, */),
function(a) { return Number(a); }),
function(a) {return a});
for(var i=0; i<as.length-1; i++)
if(as[i]==as[i+1])
return false;
return true;
},
message: 'Duplicated affiliation number exist.'
}]
});
self.errors = ko.validation.group(self, { deep: true, observable: false });
};
var Affiliation = function(institute, country) {
var self = this;
self.institute = ko.observable(institute).extend({ required: true});
self.country = ko.observable(country).extend({ required: true});
self.errors = ko.validation.group(self, { deep: true, observable: false });
};
var TopicList
var AbstractForm = function(abstract) {
var self = this;
if(!abstract) {
abstract = { id: 0, title: '', presenter: '0', stylePrefered: 'Whichever',
topic1index: 0, topic2index: 0,
authors: [{}, {}], affiliations: [{}],
applyPosterAward: false, dateOfBirth:undefined, recommendation:'' };
}
self.id = abstract.id;
self.title = ko.observable(abstract.title).extend({
required: true
});
self.presenter = ko.observable(abstract.presenter).extend({
required: true
});
self.stylePrefered = ko.observable(abstract.stylePrefered);
self.topics = [
"-- select one --",
"Bio- & macromolecules and polymers",
"Semiconductor and metal surface structure",
"Adsorbates and reactions on semiconductor and metal surfaces",
"Magnetic systems",
"Nanotubes and 1-D wires",
"Catalysis & electrochemistry",
"Nano-optical phenomena",
"Interaction within and between single molecules",
"Atom and molecular manipulation",
"Nano-patterning",
"Oxides",
"Cells and biological systems",
"Theory of probe-matter interactions",
"Tip preparation and functionalisation",
"Bottom-up process",
"Molecular electronics "
];
self.topic1 = ko.observable(self.topics[0]).extend({
validation: {
validator: function (val) {
return _.indexOf(self.topics, val) >= 1;
},
message: 'Select a topic.',
params: undefined
}
});
self.topic2 = ko.observable(self.topics[0]);
self.topic1index = ko.computed({
read: function() { return _.indexOf(self.topics, self.topic1()); },
write: function(v) { self.topic1(self.topics[v]); }
});
self.topic1index(abstract.topic1index);
self.topic2index = ko.computed({
read: function() { return _.indexOf(self.topics, self.topic2()); },
write: function(v) { self.topic2(self.topics[v]); }
});
self.topic2index(abstract.topic2index);
self.affiliations = ko.observableArray(
ko.utils.arrayMap(abstract.affiliations, function(entry){
return new Affiliation(entry.institute, entry.country);})
);
self.affiliationAdd = function() {
self.affiliations.push(new Affiliation())
}
self.affiliationRemove = function(affiliation) {
if(self.affiliations().length>1)
self.affiliations.remove(affiliation)
};
self.affiliationPlaceHolder = function(index) {
return index==0 ? ['IBM Zurich Research Laboratory','Switzerland'] : ['',''];
};
self.authors = ko.observableArray(
ko.utils.arrayMap(abstract.authors, function(entry){
return new Author(self, entry.initial, entry.name, entry.affiliations);})
);
self.authorAdd = function() {
self.authors.push(new Author(self))
};
self.authorRemove = function(author) {
if(self.authors().length>1)
self.authors.remove(author);
};
self.authorUp = function(author) {
for(var i=0; i<self.authors().length; i++)
self.authors()[i].newIndex = i;
i = self.authors().indexOf(author);
self.authors()[i].newIndex = i-1;
self.authors()[i-1].newIndex = i;
self.authors.sort(function(left, right) {
return left.newIndex == right.newIndex ? 0 : (left.newIndex < right.newIndex ? -1 : 1) ;
});
self.presenter.valueHasMutated(); // なぜこれが必要か?
};
self.authorDown = function(author) {
for(var i=0; i<self.authors().length; i++)
self.authors()[i].newIndex = i;
i = self.authors().indexOf(author);
self.authors()[i].newIndex = i+1;
self.authors()[i+1].newIndex = i;
self.authors.sort(function(left, right) {
return left.newIndex == right.newIndex ? 0 : (left.newIndex < right.newIndex ? -1 : 1) ;
});
self.presenter.valueHasMutated(); // なぜこれが必要か?
};
self.authorPlaceHolder
= function(index) {
return index>1 ? ['','',''] : [['G.', 'Binnig', '1'], ['H.', 'Rohrer', '1']][index];
};
self.applyPosterAward = ko.observable(abstract.applyPosterAward);
self.dateOfBirth = ko.observable(abstract.dateOfBirth).extend({
required: {
onlyIf: function () {
return self.applyPosterAward();
}
},
pattern: {
params: '^(20[01][0-9]|19[0-9][0-9])-(0?[1-9]|1[0-2])-(0?[1-9]|[12][0-9]|3[01])$',
message: 'Input the date in a form "yyyy-mm-dd".'
}
});
self.recommendation = ko.observable(abstract.recommendation).extend({
required: {
onlyIf: function () {
return self.applyPosterAward();
}
},
validation: {
validator: function(val) {
return val.split(/[ \n\t]+/).length < 210;
},
message: "Too long."
}
});
self.previewErrors = ko.validation.group(
[self.authors, self.affiliations, self.title], { deep: true, observable: false });
self.errors = ko.validation.group(self, { deep: true, observable: false });
self.onSave = function() {
var js = ko.toJS(self);
js.topics = undefined;
js.topic1 = undefined;
js.topic2 = undefined;
alert(ko.toJSON(js));
};
self.onSubmit = function(formElement) {
if(self.errors().length > 0) {
self.errors.showAllMessages();
alert("There are some errors. Please correct them before submission.");
return false;
}
return true;
};
}
abstract = null;
ko.validation.init( {
decorateElement: true,
errorElementClass: 'validationError',
messageTemplate: 'validationErrorMessage'
} );
ko.applyBindings(new AbstractForm(abstract));
External resources loaded into this fiddle:
<script type="text/html" id="validationErrorMessage">
<p data-bind="visible: field.isModified() && !field.isValid(),
text: field.error" class="validationMessage"></p>
</script>
<h1>Abstract Submission Form</h1>
<form id='AbstractForm' data-bind="submit: onSubmit">
<section>
<h3>Registration-Id:</h3>
<!--ko text: id --><!--/ko-->
<input type="hidden" name="id" data-bind="value: id">
</section>
<section>
<h3>Presentation Title:</h3>
<textarea data-bind="value: title" name="title"></textarea>
</section>
<section>
<h3>Author List:</h3>
<table id="authors">
<tr><th>Presenter</th><th>Initial</th><th>Name</th><th>Affiliations</th></tr>
<!--ko foreach: authors-->
<tr data-bind='validationOptions: { insertMessages: false }'>
<td> <!--ko text: $index() + 1 + '.' --><!--/ko-->
<input type="radio" name="presenter" data-bind="checked: $parent.presenter, value: $index()"></td>
<td><input data-bind="value: initial, attr: {name:'author['+$index()+'].initial', placeholder: $parent.authorPlaceHolder($index())[0]}"></td>
<td><input data-bind="value: name, attr:{name:'author['+$index()+'].name', placeholder: $parent.authorPlaceHolder($index())[1]}"></td>
<td><input data-bind="value: affiliations, attr:{name:'author['+$index()+'].affiliations', placeholder: $parent.authorPlaceHolder($index())[2]}"></td>
<td>
<a href="" data-bind="click: $parent.authorRemove, if:$parent.authors().length>1">remove</a>
<a href="" data-bind="click: $parent.authorUp, if:$index()>0">up</a>
<a href="" data-bind="click: $parent.authorDown, if:$index()<$parent.authors().length-1">down</a>
</td>
</tr>
<tr data-bind="visible: errors().length>0">
<td>
<td colspan="3" class="validationMessage" style="width:310px">
<p data-bind="validationMessage: initial" fieldName="Initial"></p>
<p data-bind="validationMessage: name" fieldName="Name"></p>
<p data-bind="validationMessage: affiliations" fieldName="Affiliations"></p>
</tr>
<!--/ko-->
</table>
<div style="padding:5px 0 0 20px;"><a href="" data-bind="click: authorAdd">add a new line</a></div>
</div>
</section>
<section>
<h3>Affiliation List:</h3>
<table id="affiliations">
<tr><th><th>Institute</th><th>Country</th></tr>
<!--ko foreach: affiliations-->
<tr data-bind='validationOptions: { insertMessages: false }'>
<td data-bind="text: $index() + 1 + '.'"></td>
<td><input data-bind="value: institute, attr:{name:'affiliation['+$index()+'].institute', placeholder: $parent.affiliationPlaceHolder($index())[0] }"></td>
<td><input data-bind="value: country, attr:{name:'affiliation['+$index()+'].country', placeholder: $parent.affiliationPlaceHolder($index())[1] }"></td>
<td><a href="" data-bind="click: $parent.affiliationRemove, if: $parent.affiliations().length>1">remove</a></td>
</tr>
<tr data-bind="visible: errors().length>0">
<td>
<td colspan="2" class="validationMessage" style="width:400px">
<p data-bind="validationMessage: institute" fieldName="Institute"></p>
<p data-bind="validationMessage: country" fieldName="Country"></p>
</tr>
<!--/ko-->
</table>
<div style="padding:5px 0 0 20px;"><a href="" data-bind="click: affiliationAdd">add a new line</a></div>
</section>
<section id="preview">
<h3>Preview:</h3>
<!--ko if: errors().length==0 -->
<div class="title" data-bind="html: title()==='' ? '<span class=\'alert\'>-- title is missing --</span>' : title()"></div>
<div class="authors" data-bind="foreach: authors">
<span data-bind="css: $index()==$parent.presenter() ? 'author presenter' : 'author'"><!--ko text: initial--><!--/ko-->
<!--ko text: name--><!--/ko--><span class="affiliations"
data-bind="text: affiliations"></span></span><!--ko text:
$index()===($parent.authors().length-2) ? ' and ' :
$index()===($parent.authors().length-1) ? '' : ', ' --><!--/ko-->
</div>
<div class="affiliations" data-bind="foreach: affiliations">
<span class="affiliation"><span class="number" data-bind="text: $index()+1"></span><!--ko text: institute--><!--/ko-->,
<!--ko text: country--><!--/ko--></span><!--ko text: $index()===($parent.affiliations().length-1) ? '' : ', ' --><!--/ko-->
</div>
<!--/ko-->
<!--ko if: errors().length>0 -->
<p>There are some errors in the fields.</p>
<!--/ko-->
</section>
<section>
<h3>Preferential Presentation Style:</h3>
<div>
<label><input type="radio" name="stylePrefered" data-bind="checked: stylePrefered" value="Oral">Oral</label>
<label><input type="radio" name="stylePrefered" data-bind="checked: stylePrefered" value="Poster">Poster</label>
<label><input type="radio" name="stylePrefered" data-bind="checked: stylePrefered" value="Whichever">Whichever</label>
(
<label><input type="radio" name="stylePrefered" data-bind="checked: stylePrefered" value="Invited">Invited</label>
<label><input type="radio" name="stylePrefered" data-bind="checked: stylePrefered" value="Plenary">Plenary</label>
)
</p>
<span data-bind="if: stylePrefered()=='Invited' || stylePrefered()=='Plenary'">
<br>
* Select 'Invited' or 'Plenary' only if you have received an invitation letter in advance.
</span>
</section>
<section>
<h3>Topics:</h3>
<p>Topic1:</p>
<div><select data-bind="options: topics, value: topic1"></select>
<input type="hidden" name="topic1" data-bind="value: topic1index"></div>
<br>
<p>Topic2 (optional):</p>
<div><select data-bind="options: topics, value: topic2"></select>
<input type="hidden" name="topic2" data-bind="value: topic2index"></div>
</section>
<section id="PosterAward">
<h3>Poster Award:</h3>
<label><input name="applyPosterAward" type="checkbox" data-bind="checked: applyPosterAward">
Apply for Poster Award</label>
<p>(You must be younger than or equal to 35 years old on 2013-01-01)</p>
<br>
<div data-bind="css: applyPosterAward() ? '' : 'disable'">
Date of Birth:<br>
<div> <input name="dateOfBirth" placeHolder="yyyy-mm-dd" data-bind="value: dateOfBirth, enable: applyPosterAward()"></div>
<br>
Self recommendation of your paper (less than 200 words):<br>
<div> <textarea name="recommendation" data-bind="value: recommendation, enable: applyPosterAward()"></textarea></div>
</div>
</section>
<br>
<br>
<input type="button" data-bind="click: onSave" value="Save This Form Temporarily">
<input type="submit" value="Submit"></input><br>
<br>
<br>
<br>
<br>
</form>
/* リセットしてしまった見出しのサイズ調整 */
h1 {
font-size: 160%;
}
h2 {
font-size: 130%;
}
h3 {
font-size: 100%;
}
/* ここからが AbstractForm 関連 */
form#AbstractForm h3 {
margin: 20px 0 10px 0;
}
form#AbstractForm textarea[name='title'] {
width: 450px;
height: 3em;
}
input.validationError,
select.validationError,
textarea.validationError {
background-color: #fcc;
}
form#AbstractForm {
line-height: 140%;
}
form#AbstractForm input,
form#AbstractForm select,
form#AbstractForm textarea {
font-size: 100%;
}
form#AbstractForm section > :not(h3) {
margin-left: 20px;
}
form#AbstractForm table#authors tr th,
form#AbstractForm table#authors tr td,
form#AbstractForm table#affiliations tr th,
form#AbstractForm table#affiliations tr td {
padding: 0 5px 0 5px;
font-weight: normal;
}
form#AbstractForm table#authors tr th,
form#AbstractForm table#affiliations tr th {
vertical-align: top;
}
form#AbstractForm table#authors tr td:nth-child(2) input {
width: 60px;
}
form#AbstractForm table#authors tr td:nth-child(3) input {
width: 150px;
}
form#AbstractForm table#authors tr td:nth-child(4) input {
width: 80px;
}
form#AbstractForm table#affiliations tr td:nth-child(2) input {
width: 300px;
}
form#AbstractForm table#affiliations tr td:nth-child(3) input {
width: 100px;
}
form#AbstractForm section#preview div.title {
font-weight: bold;
}
form#AbstractForm section#preview div.authors,
form#AbstractForm section#preview div.affiliations {
font-style: italic;
}
form#AbstractForm section#preview > div.authors > span.presenter {
text-decoration: underline;
}
form#AbstractForm section#preview > div.authors > span.author > span.affiliations {
vertical-align: super;
font-size: small;
}
form#AbstractForm section#preview > div.authors > span.presenter > span.affiliations {
text-decoration: none !important;
}
form#AbstractForm section#preview div.affiliations span.affiliation span.number {
vertical-align: super;
font-size: small;
}
form#AbstractForm section#PosterAward div.disable {
color: #888;
}
form#AbstractForm section#PosterAward div textarea {
width: 450px;
height: 5em;
}
p[data-bind^="validationMessage:"], p.validationMessage {
color: #f33;
}
p[data-bind^="validationMessage:"][fieldName]:before {
content: attr(fieldName) " : "
}