Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

MDL-30023: rubric editor usability - automatically set scores for add…

…ed levels
  • Loading branch information...
commit ffbf4370a278ccf4c0a1bf783c96a1137f9a8b86 1 parent 2d41a91
Marina Glancy marinaglancy authored
134 grade/grading/form/rubric/js/rubriceditor.js
@@ -23,6 +23,7 @@ M.gradingform_rubriceditor.addhandlers = function() {
23 23 M.gradingform_rubriceditor.eventhandler = Y.on('click', M.gradingform_rubriceditor.buttonclick, '#rubric-'+name+' input[type=submit]', null);
24 24 }
25 25
  26 +// switches all input text elements to non-edit mode
26 27 M.gradingform_rubriceditor.disablealleditors = function() {
27 28 var Y = M.gradingform_rubriceditor.Y
28 29 var name = M.gradingform_rubriceditor.name
@@ -30,6 +31,9 @@ M.gradingform_rubriceditor.disablealleditors = function() {
30 31 Y.all('#rubric-'+name+' .description').each( function(node) {M.gradingform_rubriceditor.editmode(node, false)} );
31 32 }
32 33
  34 +// function invoked on each click on the page. If level and/or criterion description is clicked
  35 +// it switches this element to edit mode. If rubric button is clicked it does nothing so the 'buttonclick'
  36 +// function is invoked
33 37 M.gradingform_rubriceditor.clickanywhere = function(e) {
34 38 var el = e.target
35 39 // if clicked on button - disablecurrenteditor, continue
@@ -54,56 +58,55 @@ M.gradingform_rubriceditor.clickanywhere = function(e) {
54 58 M.gradingform_rubriceditor.disablealleditors()
55 59 }
56 60
  61 +// switch the criterion description or level to edit mode or switch back
57 62 M.gradingform_rubriceditor.editmode = function(el, editmode, focustb) {
58 63 var ta = el.one('textarea')
59   - if (!ta.get('parentNode').one('.plainvalue')) {
60   - ta.get('parentNode').append('<div class="plainvalue"></div>')
61   - }
62   - var tb = el.one('input[type=text]')
63   - if (tb && !tb.get('parentNode').one('.plainvalue')) {
64   - tb.get('parentNode').append('<div class="plainvalue"></div>')
  64 + if (!editmode && ta.getStyle('display') == 'none') return;
  65 + if (editmode && ta.getStyle('display') == 'block') return;
  66 + var pseudotablink = '<a href="#" class="pseudotablink"> </a>',
  67 + taplain = ta.get('parentNode').one('.plainvalue'),
  68 + tbplain = null,
  69 + tb = el.one('input[type=text]')
  70 + // add 'plainvalue' next to textarea for description/definition and next to input text field for score (if applicable)
  71 + if (!taplain) {
  72 + ta.get('parentNode').append('<div class="plainvalue"><span></span>'+pseudotablink+'</div>')
  73 + taplain = ta.get('parentNode').one('.plainvalue')
  74 + taplain.one('.pseudotablink').on('focus', M.gradingform_rubriceditor.clickanywhere)
  75 + if (tb) {
  76 + tb.get('parentNode').append('<div class="plainvalue"><span></span>'+pseudotablink+'</div>')
  77 + tbplain = tb.get('parentNode').one('.plainvalue')
  78 + tbplain.one('.pseudotablink').on('focus', M.gradingform_rubriceditor.clickanywhere)
  79 + }
65 80 }
  81 + if (tb && !tbplain) tbplain = tb.get('parentNode').one('.plainvalue')
66 82 if (!editmode) {
67   - if (ta.getStyle('display') == 'none') return;
  83 + // if we need to hide the input fields, copy their contents to plainvalue(s). If description/definition
  84 + // is empty, display the default text ('Click to edit ...') and add/remove 'empty' CSS class to element
68 85 var value = ta.get('value')
69   - if (value.length) ta.get('parentNode').one('.plainvalue').removeClass('empty')
  86 + if (value.length) taplain.removeClass('empty')
70 87 else {
71 88 value = (el.hasClass('level')) ? M.str.gradingform_rubric.levelempty : M.str.gradingform_rubric.criterionempty
72   - ta.get('parentNode').one('.plainvalue').addClass('empty')
73   - }
74   - ta.get('parentNode').one('.plainvalue').set('innerHTML', value)
75   - ta.get('parentNode').one('.plainvalue').setStyle('display', 'block')
76   - ta.setStyle('display', 'none')
77   - if (tb) {
78   - tb.get('parentNode').one('.plainvalue').set('innerHTML', tb.get('value'))
79   - tb.get('parentNode').one('.plainvalue').setStyle('display', 'inline-block')
80   - tb.setStyle('display', 'none')
  89 + taplain.addClass('empty')
81 90 }
  91 + taplain.one('span').set('innerHTML', value)
  92 + if (tb) tbplain.one('span').set('innerHTML', tb.get('value'))
82 93 } else {
83   - if (tb) {
84   - tb.get('parentNode').one('.plainvalue').setStyle('display', 'none')
85   - tb.setStyle('display', 'inline-block')
86   - }
87   - var width = ta.get('parentNode').getComputedStyle('width') // TODO min width
88   - var height = ta.get('parentNode').getComputedStyle('height') // TODO min height
89   - if (el.hasClass('level')) {
90   - height = el.getComputedStyle('height') - el.one('.score').getComputedStyle('height')
91   - } else if (el.hasClass('description')) {
92   - height = el.get('parentNode').getComputedStyle('height')
93   - }
94   - ta.get('parentNode').one('.plainvalue').setStyle('display', 'none')
95   - ta.setStyle('display', 'block').setStyle('width', width).setStyle('height', height)
96   - if (tb && focustb) tb.focus(); else ta.focus()
  94 + // if we need to show the input fields, set the width/height for textarea so it fills the cell
  95 + var width = parseFloat(ta.get('parentNode').getComputedStyle('width')),
  96 + height
  97 + if (el.hasClass('level')) height = parseFloat(el.getComputedStyle('height')) - parseFloat(el.one('.score').getComputedStyle('height'))
  98 + else height = parseFloat(ta.get('parentNode').getComputedStyle('height'))
  99 + ta.setStyle('width', Math.max(width,50)+'px').setStyle('height', Math.max(height,20)+'px')
97 100 }
98   - if (!ta.get('parentNode').one('.plainvalue').one('.pseudotablink')) {
99   - var pseudotablink = '<a href="#" class="pseudotablink"> </a>'
100   - ta.get('parentNode').one('.plainvalue').append(pseudotablink)
101   - ta.get('parentNode').one('.plainvalue').one('.pseudotablink').on('focus', M.gradingform_rubriceditor.clickanywhere)
102   - if (tb) {
103   - tb.get('parentNode').one('.plainvalue').append(pseudotablink)
104   - tb.get('parentNode').one('.plainvalue').one('.pseudotablink').on('focus', M.gradingform_rubriceditor.clickanywhere)
105   - }
  101 + // hide/display textarea, textbox and plaintexts
  102 + taplain.setStyle('display', editmode ? 'none' : 'block')
  103 + ta.setStyle('display', editmode ? 'block' : 'none')
  104 + if (tb) {
  105 + tbplain.setStyle('display', editmode ? 'none' : 'inline-block')
  106 + tb.setStyle('display', editmode ? 'inline-block' : 'none')
106 107 }
  108 + // focus the proper input field in edit mode
  109 + if (editmode) { if (tb && focustb) tb.focus(); else ta.focus() }
107 110 }
108 111
109 112 // handler for clicking on submit buttons within rubriceditor element. Adds/deletes/rearranges criteria and/or levels on client side
@@ -133,43 +136,52 @@ M.gradingform_rubriceditor.buttonclick = function(e, confirmed) {
133 136 };
134 137 if (chunks.length == 3 && action == 'addcriterion') {
135 138 // ADD NEW CRITERION
136   - var nlevels = 3
137   - var criteria = Y.all('#'+name+'-criteria .criterion')
138   - if (criteria.size()) nlevels = Math.max(nlevels, criteria.item(criteria.size()-1).all('.level').size())
139   - var levelsstr = '';
140   - for (var levidx=0;levidx<nlevels;levidx++) {
141   - levelsstr += M.gradingform_rubriceditor.templates[name]['level'].
142   - replace(/\{CRITERION-id\}/g, 'NEWID'+newid).replace(/\{LEVEL-id\}/g, 'NEWID'+(newlevid+levidx)).replace(/\{.+?\}/g, '')
143   - }
144   - var newcriterion = M.gradingform_rubriceditor.templates[name]['criterion'].
145   - replace(/\{CRITERION-id\}/g, 'NEWID'+newid).replace(/\{LEVELS\}/, levelsstr).replace(/\{.+?\}/g, '')
  139 + var levelsscores = [0], levidx = 1
146 140 var parentel = Y.one('#'+name+'-criteria')
147 141 if (parentel.one('>tbody')) parentel = parentel.one('>tbody')
148   - parentel.append(newcriterion)
149   - M.gradingform_rubriceditor.addhandlers();
  142 + if (parentel.all('.criterion').size()) {
  143 + var lastcriterion = parentel.all('.criterion').item(parentel.all('.criterion').size()-1).all('.level')
  144 + for (levidx=0;levidx<lastcriterion.size();levidx++) levelsscores[levidx] = lastcriterion.item(levidx).one('.score input[type=text]').get('value')
  145 + }
  146 + for (levidx;levidx<3;levidx++) levelsscores[levidx] = parseFloat(levelsscores[levidx-1])+1
  147 + var levelsstr = '';
  148 + for (levidx=0;levidx<levelsscores.length;levidx++) {
  149 + levelsstr += M.gradingform_rubriceditor.templates[name]['level'].replace(/\{LEVEL-id\}/g, 'NEWID'+(newlevid+levidx)).replace(/\{LEVEL-score\}/g, levelsscores[levidx])
  150 + }
  151 + var newcriterion = M.gradingform_rubriceditor.templates[name]['criterion'].replace(/\{LEVELS\}/, levelsstr)
  152 + parentel.append(newcriterion.replace(/\{CRITERION-id\}/g, 'NEWID'+newid).replace(/\{.+?\}/g, ''))
150 153 M.gradingform_rubriceditor.assignclasses('#rubric-'+name+' #'+name+'-criteria-NEWID'+newid+'-levels .level')
  154 + M.gradingform_rubriceditor.addhandlers();
  155 + M.gradingform_rubriceditor.disablealleditors()
  156 + M.gradingform_rubriceditor.assignclasses(elements_str)
151 157 M.gradingform_rubriceditor.editmode(Y.one('#rubric-'+name+' #'+name+'-criteria-NEWID'+newid+'-description'),true)
152 158 } else if (chunks.length == 5 && action == 'addlevel') {
153 159 // ADD NEW LEVEL
  160 + var newscore = 0;
  161 + parent = Y.one('#'+name+'-criteria-'+chunks[2]+'-levels')
  162 + parent.all('.level').each(function (node) { newscore = Math.max(newscore, parseFloat(node.one('.score input[type=text]').get('value'))+1) })
154 163 var newlevel = M.gradingform_rubriceditor.templates[name]['level'].
155   - replace(/\{CRITERION-id\}/g, chunks[2]).replace(/\{LEVEL-id\}/g, 'NEWID'+newlevid).replace(/\{.+?\}/g, '')
156   - Y.one('#'+name+'-criteria-'+chunks[2]+'-levels').append(newlevel)
157   - var levels = Y.all('#'+name+'-criteria-'+chunks[2]+'-levels .level')
158   - if (levels.size()) levels.set('width', Math.round(100/levels.size())+'%')
  164 + replace(/\{CRITERION-id\}/g, chunks[2]).replace(/\{LEVEL-id\}/g, 'NEWID'+newlevid).replace(/\{LEVEL-score\}/g, newscore).replace(/\{.+?\}/g, '')
  165 + parent.append(newlevel)
159 166 M.gradingform_rubriceditor.addhandlers();
160   - M.gradingform_rubriceditor.editmode(levels.item(levels.size()-1),true)
  167 + M.gradingform_rubriceditor.disablealleditors()
  168 + M.gradingform_rubriceditor.assignclasses(elements_str)
  169 + M.gradingform_rubriceditor.editmode(parent.all('.level').item(parent.all('.level').size()-1), true)
161 170 } else if (chunks.length == 4 && action == 'moveup') {
162 171 // MOVE CRITERION UP
163 172 el = Y.one('#'+name+'-criteria-'+chunks[2])
164 173 if (el.previous()) el.get('parentNode').insertBefore(el, el.previous())
  174 + M.gradingform_rubriceditor.assignclasses(elements_str)
165 175 } else if (chunks.length == 4 && action == 'movedown') {
166 176 // MOVE CRITERION DOWN
167 177 el = Y.one('#'+name+'-criteria-'+chunks[2])
168 178 if (el.next()) el.get('parentNode').insertBefore(el.next(), el)
  179 + M.gradingform_rubriceditor.assignclasses(elements_str)
169 180 } else if (chunks.length == 4 && action == 'delete') {
170 181 // DELETE CRITERION
171 182 if (confirmed) {
172 183 Y.one('#'+name+'-criteria-'+chunks[2]).remove()
  184 + M.gradingform_rubriceditor.assignclasses(elements_str)
173 185 } else {
174 186 dialog_options['message'] = M.str.gradingform_rubric.confirmdeletecriterion
175 187 M.util.show_confirm_dialog(e, dialog_options);
@@ -178,8 +190,7 @@ M.gradingform_rubriceditor.buttonclick = function(e, confirmed) {
178 190 // DELETE LEVEL
179 191 if (confirmed) {
180 192 Y.one('#'+name+'-criteria-'+chunks[2]+'-'+chunks[3]+'-'+chunks[4]).remove()
181   - levels = Y.all('#'+name+'-criteria-'+chunks[2]+'-levels .level')
182   - if (levels.size()) levels.set('width', Math.round(100/levels.size())+'%')
  193 + M.gradingform_rubriceditor.assignclasses(elements_str)
183 194 } else {
184 195 dialog_options['message'] = M.str.gradingform_rubric.confirmdeletelevel
185 196 M.util.show_confirm_dialog(e, dialog_options);
@@ -189,21 +200,22 @@ M.gradingform_rubriceditor.buttonclick = function(e, confirmed) {
189 200 return;
190 201 }
191 202 e.preventDefault();
192   - // properly set classes and sortorder
193   - M.gradingform_rubriceditor.assignclasses(elements_str)
194 203 }
195 204
  205 +// properly set classes (first/last/odd/even), level width and/or criterion sortorder for elements Y.all(elements_str)
196 206 M.gradingform_rubriceditor.assignclasses = function (elements_str) {
197 207 var elements = M.gradingform_rubriceditor.Y.all(elements_str)
198 208 for (var i=0;i<elements.size();i++) {
199 209 elements.item(i).removeClass('first').removeClass('last').removeClass('even').removeClass('odd').
200 210 addClass(((i%2)?'odd':'even') + ((i==0)?' first':'') + ((i==elements.size()-1)?' last':''))
201 211 elements.item(i).all('input[type=hidden]').each(
202   - function(node) { if (node.get('name').match(/sortorder/)) node.set('value', i) }
  212 + function(node) {if (node.get('name').match(/sortorder/)) node.set('value', i)}
203 213 );
  214 + if (elements.item(i).hasClass('level')) elements.item(i).set('width', Math.round(100/elements.size())+'%')
204 215 }
205 216 }
206 217
  218 +// returns unique id for the next added element, it should not be equal to any of Y.all(elements_str) ids
207 219 M.gradingform_rubriceditor.calculatenewid = function (elements_str) {
208 220 var newid = 1
209 221 M.gradingform_rubriceditor.Y.all(elements_str).each( function(node) {
24 grade/grading/form/rubric/rubriceditor.php
@@ -152,7 +152,24 @@ function prepare_data($value = null, $withvalidation = false) {
152 152 foreach ($value['criteria'] as $id => $criterion) {
153 153 if ($id == 'addcriterion') {
154 154 $id = $this->get_next_id(array_keys($value['criteria']));
155   - $criterion = array('description' => '');
  155 + $criterion = array('description' => '', 'levels' => array());
  156 + $i = 0;
  157 + // when adding new criterion copy the number of levels and their scores from the last criterion
  158 + if (!empty($value['criteria'][$lastid]['levels'])) {
  159 + foreach ($value['criteria'][$lastid]['levels'] as $lastlevel) {
  160 + $criterion['levels']['NEWID'+($i++)]['score'] = $lastlevel['score'];
  161 + }
  162 + } else {
  163 + $criterion['levels']['NEWID'+($i++)]['score'] = 0;
  164 + }
  165 + // add more levels so there are at least 3 in the new criterion. Increment by 1 the score for each next one
  166 + for ($i; $i<3; $i++) {
  167 + $criterion['levels']['NEWID'+$i]['score'] = $criterion['levels']['NEWID'+($i-1)]['score'] + 1;
  168 + }
  169 + // set other necessary fields (definition) for the levels in the new criterion
  170 + foreach (array_keys($criterion['levels']) as $i) {
  171 + $criterion['levels'][$i]['definition'] = '';
  172 + }
156 173 $this->nonjsbuttonpressed = true;
157 174 }
158 175 $levels = array();
@@ -165,6 +182,11 @@ function prepare_data($value = null, $withvalidation = false) {
165 182 'definition' => '',
166 183 'score' => 0,
167 184 );
  185 + foreach ($criterion['levels'] as $lastlevel) {
  186 + if ($level['score'] < $lastlevel['score'] + 1) {
  187 + $level['score'] = $lastlevel['score'] + 1;
  188 + }
  189 + }
168 190 $this->nonjsbuttonpressed = true;
169 191 }
170 192 if (!array_key_exists('delete', $level)) {

0 comments on commit ffbf437

Please sign in to comment.
Something went wrong with that request. Please try again.