需求
根据提供的试卷题目(是一个干净的只有“数据”的HTML网页)生成一份多页的试卷,用户能执行翻页、具有答题时间限制,展示给用户的试卷中题目需要占用尽量少的空间(比如选择题中把两条较短的选项由两行合并到一行)、同一道题目不要跨页面显示以方便答题者,管理员能够改变试卷的样式(字体、颜色、行距、页面边距,像字处理软件一样……),题目之间可以插入一些说明性的文字(比如告知答题者作答的须知等等)。题目提干、选择题的选项、说明文字可以包含多媒体信息(文字、图片、列表、表格、视频等等……)。选择题选项数目不限、单选多选不限。翻页要有可订制的动画效果
提供的试卷样板类似如下(Input):
02 | < div class = "Desc" >选择题:说明文字。说明文字。说明文字。说明文字。说明文字。说明文字。说明文字。说明文字。说明文字。说明文字。说明文字。说明文字。说明文字。说明文字。</ div > |
03 | < div class = "Problem" id = "1" > |
04 | < li >1.你认为怎样的老师是好老师?</ li > |
06 | < label >< input type = "radio" name = "prob1" value = "A" />和学生平等相处,能全面满足学生各种需要</ label > |
07 | < label >< input type = "radio" name = "prob1" value = "B" />所在教学班的成绩优于其他平行班</ label > |
08 | < label >< input type = "radio" name = "prob1" value = "C" />严格管理学生、所带的班级班风良好</ label > |
09 | < label >< input type = "radio" name = "prob1" value = "D" />父母般地关心学生的生活和情绪状态</ label > |
12 | < div class = "Problem" id = "2" > |
13 | < li >2.一位有15年教龄的英语教师,教了多年高三,可谓学校的核心骨干。一次接受邀请到外校介绍教学经验,台下有老师发表了观点并问到几个英语教法发面的问题,一下子把她给卡住了。这是因为</ li > |
15 | < label >< input type = "radio" name = "prob2" value = "A" />她最近工作太累,注意力不够集中。</ label > |
16 | < label >< input type = "radio" name = "prob2" value = "B" />提问老师的观点和她的有很大不同。</ label > |
17 | < label >< input type = "radio" name = "prob2" value = "C" />由于长时间在教学一线拼搏,她对教学理论问题的关注度不高。</ label > |
18 | < label >< input type = "radio" name = "prob2" value = "D" />对学科教学的归纳和思考少,一时加工不过来。</ label > |
21 | < div class = "Problem" id = "3" > |
24 | < label >< input type = "radio" name = "prob3" value = "A" />这一张< img src = "img1.png" height = "300px" width = "400px" alt = "img1" />好看。</ label > |
25 | < label >< input type = "radio" name = "prob3" value = "B" />这一张< img src = "img2.png" height = "300px" width = "400px" alt = "img2" />好看。</ label > |
26 | < label >< input type = "radio" name = "prob3" value = "C" />这一张< img src = "img3.png" height = "300px" width = "400px" alt = "img3" />好看。</ label > |
27 | < label >< input type = "radio" name = "prob3" value = "D" />这一张< img src = "img4.png" height = "300px" width = "400px" alt = "img4" />好看。</ label > |
28 | < label >< input type = "radio" name = "prob3" value = "E" />不知道。</ label > |
31 | < div class = "Desc" >填空题和选择题:一大堆的说明文字。一大堆的说明文字。一大堆的说明文字。一大堆的说明文字。一大堆的说明文字。</ div > |
32 | < div class = "Problem" id = "4" > |
33 | < li >4.床前明月光,< input type = "text" name = "prob4" /></ li > |
35 | < div class = "Problem" id = "5" > |
36 | < li >5.你认为怎样的老师是好老师?</ li > |
38 | < label >< input type = "checkbox" name = "prob6" value = "D" />和</ label > |
39 | < label >< input type = "checkbox" name = "prob6" value = "A" />所</ label > |
40 | < label >< input type = "checkbox" name = "prob6" value = "B" />严</ label > |
41 | < label >< input type = "checkbox" name = "prob6" value = "C" />父</ label > |
42 | < label >< input type = "checkbox" name = "prob6" value = "E" />和班的成绩班的成绩班的成绩班的成绩班的成绩</ label > |
43 | < label >< input type = "checkbox" name = "prob6" value = "F" />所班的成绩班的成绩班的成绩</ label > |
44 | < label >< input type = "checkbox" name = "prob6" value = "G" />严班的班的成绩班的成绩班的成绩班的成绩</ label > |
45 | < label >< input type = "checkbox" name = "prob6" value = "H" />啊</ label > |
思路
面对这种需求该怎么办呢?使用JavaScript了,看来。后来决定用jQuery,Aptana作IDE(虽然jQuery支持库在Windows上死活装不上去,换了个OS就好了,奇怪),格式么就用CSS了。
具体步骤:
- 导入试卷题目HTML
- 对所有选择题进行排版,把一行划分为四个位置,使选项尽量适应一个位置、两个位置或四个位置(也就是一行四项、一行两项或者一行一项的效果)
- 对所有题目进行分页
思路还是清晰的,但是由于浏览器众多,还是比较麻烦的,并且我是新手,没接触过jQuery之前……
实现
页面文件(和例子不同,但是格式一样的)
004 | < title >No title...</ title > |
005 | < meta http-equiv = "Content-Type" content = "text/html; charset=utf-8" /> |
006 | < script language = "JavaScript" src = "lib/jquery/jquery-1.4.2.js" type = "text/javascript" ></ script > |
007 | < script language = "JavaScript" src = "lib/countdown/jquery.countdown.pack.js" type = "text/javascript" ></ script > |
008 | < script language = "JavaScript" src = "TestPaperProcessor.js" type = "text/javascript" ></ script > |
009 | < link href = "style.css" rel = "stylesheet" type = "text/css" /> |
012 | < div id = "divToolbar" > |
013 | < div id = "divPrev" >PrevPage</ div > |
014 | < div id = "divNext" >NextPage</ div > |
015 | < div id = "divPageInfo" >Loading the test...</ div > |
016 | < div id = "divTimer" ></ div > |
018 | < form id = "formPaper" action = "demo.html" method = "post" accept-charset = "utf-8" > |
020 | < div class = "Display" id = "divLeft" > |
021 | left <!--the left page--> |
023 | < div class = "Display" id = "divRight" > |
024 | right <!--the right page--> |
028 | < div class = "Desc" >选择题:说明文字。说明文字。说明文字。说明文字。说明文字。说明文字。说明文字。说明文字。说明文字。说明文字。说明文字。说明文字。说明文字。说明文字。</ div > |
029 | < div class = "Problem" id = "1" > |
030 | < li >1你认为怎样的老师是好老师?</ li > |
031 | < div class = "Choices" > |
033 | < input type = "radio" name = "prob1" value = "D" />和学生平等相处,能全面满足学生各种需要 |
036 | < input type = "radio" name = "prob1" value = "A" />所在教学班的成绩优于其他平行班 |
039 | < input type = "radio" name = "prob1" value = "B" />严格管理学生/所带的班级班风良好 |
042 | < input type = "radio" name = "prob1" value = "C" />父母般地关心学生的生活和情绪状态 |
046 | < div class = "Problem" id = "2" > |
047 | < li >2你认为怎样的老师是好老师?</ li > |
048 | < div class = "Choices" > |
050 | < input type = "radio" name = "prob2" value = "D" />和学生平jlsdjklsdf生各种需要 |
053 | < input type = "radio" name = "prob2" value = "A" />所 |
056 | < input type = "radio" name = "prob2" value = "B" />严格好 |
059 | < input type = "radio" name = "prob2" value = "C" />父母关心学生的生活和情绪状态 |
063 | < div class = "Problem" id = "3" > |
064 | < li >3你认为怎样的老师是好老师?</ li > |
065 | < div class = "Choices" > |
067 | < input type = "radio" name = "prob3" value = "D" />和学生平等相处,能全面满足学生各种需要 |
070 | < input type = "radio" name = "prob3" value = "A" />所在教学班的成绩优于其他平行班 |
073 | < input type = "radio" name = "prob3" value = "B" />严格管理学生/所带的班级班风良好 |
076 | < input type = "radio" name = "prob3" value = "C" />父母般地关心学生的生活和情绪状态 |
080 | < div class = "Problem" id = "4" > |
081 | < li >4你认为怎样的老师是好老师?</ li > |
082 | < div class = "Choices" > |
084 | < input type = "radio" name = "prob4" value = "D" />和学生平等相处,能全面满足学和学生平等相处,能全面满足学和学生平等相处,能全面满足学和学生平等相处,能全面满足学和学生平等相处,能全面满足学和学生平等相处,能全面满足学和学生平等相处,能全面满足学和学生平等相处,能全面满足学和学生平等相处,能全面满足学生各种需要 |
087 | < input type = "radio" name = "prob4" value = "A" />所在教学班的成绩优于其他平行班 |
090 | < input type = "radio" name = "prob4" value = "B" />严格管理学生/所带的班级班风良好 |
093 | < input type = "radio" name = "prob4" value = "C" />父母般地关心学生的生活和情绪状态 |
097 | < div class = "Desc" >还是选择题:一大堆的说明文字。一大堆的说明文字。一大堆的说明文字。一大堆的说明文字。一大堆的说明文字。</ div > |
098 | < div class = "Problem" id = "10" > |
099 | < li >5你认为怎样的老师是好老师?</ li > |
100 | < div class = "Choices" > |
102 | < input type = "radio" name = "prob5" value = "D" />和10学生平等相处,能全面满足学生各种需要 |
105 | < input type = "radio" name = "prob5" value = "A" />所10在教学班的成绩优于其他平行班 |
108 | < input type = "radio" name = "prob5" value = "B" />严10jhjhjhjhkljlkjjkljjkjjkllkjlkjljkjljlkj格管文字 |
111 | < input type = "radio" name = "prob5" value = "C" />父10母般地关心学生的生活和情绪状态 |
115 | < div class = "Problem" id = "5" > |
116 | < li >5你认为怎样的老师是好老师?</ li > |
117 | < div class = "Choices" > |
119 | < input type = "radio" name = "prob5" value = "D" />和学生平等相处,能全面满足学生各种需要 |
122 | < input type = "radio" name = "prob5" value = "A" />所在教学班的成绩优于其他平行班 |
125 | < input type = "radio" name = "prob5" value = "B" />严jhjhjhjhkljlkjjkljjkjjkllkjlkjljkjljlkj格管< img src = "aaaa9.jpg" height = "300px" width = "400px" alt = "pic" />文字 |
128 | < input type = "radio" name = "prob5" value = "C" />父母般地关心学生的生活和情绪状态 |
132 | < div class = "Problem" id = "6" > |
133 | < li >6你认为怎样的老师是好老师?</ li > |
134 | < div class = "Choices" > |
136 | < input type = "radio" name = "prob6" value = "D" />和 |
139 | < input type = "radio" name = "prob6" value = "A" />所 |
142 | < input type = "radio" name = "prob6" value = "B" />严 |
145 | < input type = "radio" name = "prob6" value = "C" />父 |
148 | < input type = "radio" name = "prob6" value = "E" />和班的成绩班的成绩班的成绩班的成绩班的成绩 |
151 | < input type = "radio" name = "prob6" value = "F" />所班的成绩班的成绩班的成绩 |
154 | < input type = "radio" name = "prob6" value = "G" />严班的班的成绩班的成绩班的成绩班的成绩 |
157 | < input type = "radio" name = "prob6" value = "H" />父 |
样式文件(CSS)
01 | /* YahooUI CSS Reset */ |
02 | body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,textarea,p,blockquote,th,td { padding: 0; margin: 0; } |
03 | table { border-collapse: collapse; border-spacing: 0; } |
04 | fieldset,img { border: 0; } |
05 | address,caption,cite,code,dfn,em,strong,th,var { font-weight: normal; font-style: normal; } |
06 | ol,ul { list-style: none; } |
07 | caption,th { text-align: left; } |
08 | h1,h2,h3,h4,h5,h6 { font-weight: normal; font-size: 100%; } |
09 | q:before,q:after { content:''; } |
10 | abbr,acronym { border: 0;} |
12 | label { padding: 0; margin: 0; } |
15 | .Choices { line-height: 150%; margin: 5px 0; } |
16 | .Page { height: 500px; border: solid 1px gray; } |
17 | #olThePaper, .Display { padding: 0; width: 500px; } |
18 | /* NOTICE: the width of .Display and #olThePaper should be the SAME. */ |
19 | .Display { float: left; } |
21 | #divToolbar { height: 35px; } |
22 | #divPrev, #divNext { float: left; width: 100px; height: 30px; border: solid 1px green; background-color: #999999; } |
23 | #divPageInfo { float: left; width: 100px; height: 30px; } |
24 | #divTimer { float: left; width: 500px; height: 30px; } |
27 | /*for debugging... perhaps for non-IE only*/ |
28 | /**label { outline: dotted 1px red; background-color: gray; }**/ |
29 | /**div {outline: dashed 1px blue;}**/ |
下面是重点,TTestPaperProcessor.js:
003 | * @param {String} PaperOlId the id value of the ol tags indicating pages. |
004 | * @param {String} ProblemClass the css class name for problem area. |
005 | * @param {String} DescClass the css class name for description area. |
006 | * @param {String} ChoicesClass the css class name for choices area. |
007 | * @param {String} LeftPageId the id of the left page. |
008 | * @param {String} RightPageId the id of the right page. |
009 | * @author ExSystem<exsystemchina@gmail.com> |
011 | function TTestPaperProcessor(PaperOlId, ProblemClass, DescClass, ChoicesClass, LeftPageId, RightPageId) { |
012 | this .FPaperOlId = PaperOlId; |
013 | this .FProblemClass = ProblemClass; |
014 | this .FDescClass = DescClass; |
015 | this .FChoicesClass = ChoicesClass; |
016 | this .FLeftPageId = LeftPageId; |
017 | this .FRightPageId =RightPageId; |
018 | $( '#' + this .FLeftPageId).html( '' ); |
019 | $( '#' + this .FRightPageId).html( '' ); |
020 | this ._FormatProblemOptions(); |
021 | this ._DivideIntoPages(); |
025 | TTestPaperProcessor.prototype = { |
026 | FPaperOlId: '' , //the id property of the ol tag contains the whole test paper. |
027 | FProblemClass: '' , //the css class name for problem area. |
028 | FDescClass: '' , //the css class name for description area. |
029 | FChoicesClass: '' , //the css class name for choices area. |
030 | FLeftPageId: '' , //the left page. |
031 | FRightPageId: '' , //the right page. |
033 | FIsDisplayTableSupported: null , //whether the browser is the EVIL M$IE6,7 that does not support display: table(-cell). |
034 | FCurrPage: 0, //start from 1, 0 for no page has been displayed yet. |
035 | FPageCount: 0, //page count. |
037 | // * Get external css stylesheet info. |
038 | // * @param {String} Selector The selector in the css style sheet. |
039 | // * @param {String} Property The property name. |
040 | // * @return {String} The value of the property, or null for undefined property. |
042 | // _GetCssInfo: function(Selector, Property) { |
043 | // var mCss = document.styleSheets[0].cssRules || document.styleSheets[0].rules; |
044 | // for (var mIndex = 0; mIndex < mCss.length; ++mIndex) { |
045 | // if (mCss[mIndex].selectorText.toLowerCase() == Selector) { |
046 | // return mCss[mIndex].style[Property]; |
055 | _IsDisplayTableSupported: function () { |
056 | if ( this .FIsDisplayTableSupported != null ) { |
057 | return this .FIsDisplayTableSupported; |
060 | this .FIsDisplayTableSupported = !(jQuery.browser.msie && jQuery.browser.version < 8.0); |
061 | return this .FIsDisplayTableSupported; |
065 | * Formats radios and checkboxes for the Choices quiz. |
067 | _FormatProblemOptions: function () { |
069 | var mSelector = '.' + this .FProblemClass + ' .' + this .FChoicesClass; |
070 | $(mSelector).each( function () { |
071 | //Rearrange the options for each problem ordered by offsetWidth of the label tag. |
072 | var mLabels = new Array(); |
073 | mLabels = jQuery.makeArray($( 'label' , this )); |
074 | mLabels.sort( function (First, Second) { |
075 | return $(Second).outerWidth( true ) > $(First).outerWidth( true ); |
077 | $(mLabels).appendTo( this ); |
079 | //Layout the options into the appropreate form. |
080 | var mSlots = -1; //Force to create a new row, inside the while() loop. |
081 | var mSlotWidth = $(mSelector).width() / 4.0; |
083 | if (mThis._IsDisplayTableSupported()) { |
084 | while (mLabels.length > 0) { |
085 | //alert($(mLabels[0]).outerWidth(true) + '::' + $(mLabels[0]).outerHeight(true) + '::' + $(mLabels[0]).html()); |
086 | if (mSlots <= 0) { //If no empty slot, create a new row. |
087 | mCurrRow = $( '<div class="___table" style="display: table;"></div>' ); |
088 | mCurrRow.appendTo( this ); |
092 | var mRealCellWidth = $(mLabels[0]).outerWidth( true ); |
093 | if (mRealCellWidth < mSlotWidth) { |
096 | if (mRealCellWidth >= mSlotWidth && mRealCellWidth < mSlotWidth * 2) { |
099 | if (mRealCellWidth >= mSlotWidth * 2) { |
104 | if (mSlots >= 0) { //If empty slots exists, put the cell into the row. |
105 | mLabel = mLabels.shift(); |
106 | $(mLabel).addClass( '___cell' ); |
107 | $(mLabel).css( 'display' , 'table-cell' ); |
108 | $(mLabel).appendTo(mCurrRow); |
111 | $( '.___table' ).each( function () { //Align all the tables and cells. |
112 | $( this ).css( 'width' , '100%' ); |
113 | var mCellWidth = 100 / $( '.___cell' , this ).length; |
114 | $( '.___cell' , this ).css( 'width' , mCellWidth + '%' ); |
117 | else { // for the evil M$IE6, use table, tr, td tags. |
118 | while (mLabels.length > 0) { |
119 | if (mSlots <= 0) { //If no empty slot, create a new row. |
120 | mCurrRow = $( '<table class="___table" cellspacing="0" cellpadding="0"></table>' ); |
121 | mRow = $( '<tr></tr>' ); |
122 | mRow.appendTo(mCurrRow); |
123 | mCurrRow.appendTo( this ); |
127 | var mRealCellWidth = $(mLabels[0]).attr( 'offsetWidth' ); |
129 | //be sure to use this css reset: table { border-collapse: collapse; border-spacing: 0; } |
130 | //otherwise, 2 lines will be occupied by some long problem options instead of 1. |
131 | //or use this code instead: var mRealCellWidth = $(mLabels[0]).attr('offsetWidth') * 1.3; |
132 | if (mRealCellWidth <= mSlotWidth) { |
135 | if (mRealCellWidth > mSlotWidth && mRealCellWidth <= mSlotWidth * 2) { |
138 | if (mRealCellWidth > mSlotWidth * 2) { |
143 | if (mSlots >= 0) { //If empty slots exists, put the cell into the row. |
144 | mLabel = mLabels.shift(); |
145 | mCell = $( '<td class="___cell"></td>' ); |
146 | $(mLabel).appendTo(mCell); |
147 | mCell.appendTo($( 'tr' , mCurrRow)[0]); |
150 | $( '.___table' ).each( function () { //Align all the tables and cells. |
151 | $( this ).css( 'width' , '100%' ); |
152 | var mCellWidth = 100 / $( 'tbody tr .___cell' , this ).length; |
153 | $( 'tbody tr .___cell' , this ).css( 'width' , mCellWidth + '%' ); |
160 | * Create a new page, and add it to the paper. |
161 | * @return {jQuery} the new page. |
163 | _CreateNewPage: function () { |
166 | mPage = $( '<div class="' + this .CPageClass + '" id="___page_' + this .FPageCount + '"></div>' ); |
167 | mPage.appendTo($( '#' + this .FPaperOlId)); |
174 | * @param {Number} PageNumber |
177 | _GetPage: function (PageNumber) { |
178 | if (PageNumber < 1 || PageNumber > this .FPageCount) { |
179 | throw new Error( 'invalid page number: ' + PageNumber + '.' ); |
181 | return $( '#___page_' + PageNumber); |
187 | _DivideIntoPages: function () { |
188 | var mProblems = $( '.' + this .FProblemClass + ', .' + this .FDescClass); |
189 | var mProblemsCount = mProblems.length; |
190 | var mCurrPage = this ._CreateNewPage(); |
191 | //var mPageHeight = mCurrPage.attr('offsetHeight'); chrome: sometimes 0. safari: always 0, IF PUTTED IN $(window).ready(). |
192 | var mPageHeight = mCurrPage.outerHeight( true ); //the same as the code above. FIX: PUT IT INTO $(window).load(). |
193 | var mUsedPageHeight = 0; |
194 | for ( var mCurrProblem = 0; mCurrProblem < mProblemsCount; ++mCurrProblem) { |
195 | if (mUsedPageHeight + $(mProblems[mCurrProblem]).outerHeight( true ) > mPageHeight) { |
197 | mCurrPage = this ._CreateNewPage(); |
198 | mPageHeight = mCurrPage.outerHeight( true ); |
201 | $(mProblems[mCurrProblem]).appendTo(mCurrPage); |
202 | mUsedPageHeight += $(mProblems[mCurrProblem]).outerHeight( true ); |
207 | * Get the current page of the left side, started from 1. |
208 | * @return {Number} The current page. |
210 | getCurrPage: function () { |
211 | if ( this .FPageCount == 0) { |
212 | throw new Error( 'No page has been created yet.' ); |
214 | return this .FCurrPage; |
217 | * Trun to a specific page in the left side. |
218 | * @param {Number} Value The page number. |
220 | setCurrPage: function (Value) { |
221 | if (Value < 1 || Value > this .FPageCount) { |
222 | throw new Error( 'No such page: ' + Value + '.' ); |
224 | this .FCurrPage = parseInt(Value / 2) * 2 + 1; // to get an odd number. |
225 | $( '#' + this .FLeftPageId + ' .' + this .CPageClass).hide(); |
226 | $( '#' + this .FRightPageId + ' .' + this .CPageClass).hide(); |
227 | if ( this .FCurrPage >= 0) { |
228 | $( '#___page_' + this .FCurrPage).appendTo($( '#' + this .FLeftPageId)); |
229 | $( '#___page_' + this .FCurrPage).show( 'fast' ); |
230 | if ( this .FCurrPage < this .FPageCount) { |
232 | $( '#___page_' + this .FCurrPage).appendTo($( '#' + this .FRightPageId)); |
233 | $( '#___page_' + this .FCurrPage).show( 'fast' ); |
241 | getPageCount: function () { |
242 | return this .FPageCount; |
248 | this .setCurrPage( this .FCurrPage - 2); |
254 | this .setCurrPage( this .FCurrPage + 2); |
258 | //client code goes here... |
259 | $(window).load( function () { |
260 | var obj = new TTestPaperProcessor( 'olThePaper' , 'Problem' , 'Desc' , 'Choices' , 'divLeft' , 'divRight' ); |
261 | $( '#divPrev' ).click( function () { |
264 | $( '#divPageInfo' ).text(obj.getCurrPage() + ' of ' + obj.getPageCount()); |
267 | alert( 'No such page!' ); |
270 | $( '#divNext' ).click( function () { |
273 | $( '#divPageInfo' ).text(obj.getCurrPage() + ' of ' + obj.getPageCount()); |
276 | alert( 'No such page!' ); |
281 | $( '#formPaper' ).submit(); |
283 | $( '#divTimer' ).countdown({ |
290 | $( '#divPageInfo' ).text(obj.getCurrPage() + ' of ' + obj.getPageCount()); |