1/* 2 * JavaScript functions in survey taking 3 * 4 * This file is part of LimeSurvey 5 * Copyright (C) 2007-2013 The LimeSurvey Project Team 6 * All rights reserved. 7 * License: GNU/GPL License v2 or later, see LICENSE.php 8 * LimeSurvey is free software. This version may have been modified pursuant 9 * to the GNU General Public License, and as distributed it includes or 10 * is derivative of works licensed under the GNU General Public License or 11 * other free or open source software licenses. 12 * See COPYRIGHT.php for copyright notices and details. 13 */ 14 15// Some function can be launch before document ready (and seems intersting) 16limesurveySubmitHandler(); 17needConfirmHandler(); 18tableCellAdapters(); 19activateLanguageChanger(); 20$(document).ready(function() 21{ 22 navbuttonsJqueryUi(); 23 showStartPopups(); 24 addClassEmpty(); 25 noScrollOnSelect(); 26 doToolTipTable(); 27 28 if (typeof LEMsetTabIndexes === 'function') { LEMsetTabIndexes(); } 29 if (typeof checkconditions!='undefined') checkconditions(); 30 if (typeof template_onload!='undefined') template_onload(); 31 if (typeof(focus_element) != 'undefined') 32 { 33 $(focus_element).focus(); 34 } 35 36 // Keypad functions 37 var kp = $("input.num-keypad"); 38 if(kp.length) 39 { 40 kp.keypad({ 41 showAnim: 'fadeIn', keypadOnly: false, 42 onKeypress: function(key, value, inst) { 43 $(this).trigger('keyup'); 44 } 45 }); 46 } 47 kp = $(".text-keypad"); 48 if(kp.length) 49 { 50 var spacer = $.keypad.HALF_SPACE; 51 for(var i = 0; i != 8; ++i) spacer += $.keypad.SPACE; 52 kp.keypad({ 53 showAnim: 'fadeIn', 54 keypadOnly: false, 55 layout: [ 56 spacer + $.keypad.CLEAR + $.keypad.CLOSE, $.keypad.SPACE, 57 '!@#$%^&*()_=' + $.keypad.HALF_SPACE + $.keypad.BACK, 58 $.keypad.HALF_SPACE + '`~[]{}<>\\|/' + $.keypad.SPACE + $.keypad.SPACE + '789', 59 'qwertyuiop\'"' + $.keypad.HALF_SPACE + $.keypad.SPACE + '456', 60 $.keypad.HALF_SPACE + 'asdfghjkl;:' + $.keypad.SPACE + $.keypad.SPACE + '123', 61 $.keypad.SPACE + 'zxcvbnm,.?' + $.keypad.SPACE + $.keypad.SPACE + $.keypad.HALF_SPACE + '-0+', 62 $.keypad.SHIFT + $.keypad.SPACE_BAR + $.keypad.ENTER], 63 onKeypress: function(key, value, inst) { 64 $(this).trigger('keyup'); 65 } 66 }); 67 } 68 69 // Maxlength for textareas TODO limit to not CSS3 compatible browser 70 maxlengthtextarea(); 71 72}); 73 74/** 75 * setJsVar : Get all global used var 76 */ 77function setJsVar(){ 78 bFixNumAuto=LSvar.bFixNumAuto; 79 bNumRealValue=LSvar.bNumRealValue; 80 LEMradix=LSvar.sLEMradix; 81 numRegex = new RegExp('[^-\.,0-9]','g'); 82 intRegex = new RegExp('[^-0-9]','g'); 83} 84// Deactivate all other button on submit 85function limesurveySubmitHandler(){ 86 // Return false disallow all other system 87 $(document).on("click",".disabled",function(){return false}); 88 $(document).on("click",'.active',function(){return false;});// "[active]" don't seem to work with jquery-1.10.2 89 90 $(document).on('click',"#limesurvey .button", function(event){ 91 $(this).prop('active',true).addClass('active'); 92 $("#limesurvey .button.ui-button" ).not($(this)).button( "option", "disabled", true ); 93 $("#limesurvey .button").not($(this)).prop('disabled',true).addClass('disabled'); 94 }); 95 if (document.all && !document.querySelector) { // IE7 or lower 96 $(function() { 97 $("#defaultbtn").css('display','inline').css('width','0').css('height','0').css('padding','0').css('margin','0').css('overflow','hidden'); 98 $("#limesurvey [type='submit']").not("#defaultbtn").first().before($("#defaultbtn")); 99 }); 100 } 101} 102 103 104// Ask confirmation on click on .needconfirm 105function needConfirmHandler(){ 106 $(document).on('click',"[data-confirmedby]", function(event){ 107 text=$("label[for='"+$(this).data('confirmedby')+"']").text(); 108 if (confirm(text)) { 109 $("#"+$(this).data('confirmedby')).prop('checked',true); 110 return true; 111 } 112 $(".button.ui-button" ).button( "option", "disabled", false ); 113 $(".button").prop('disabled',false).removeClass('disabled'); 114 $(this).prop('active',false).removeClass('active'); 115 return false;
116 }); 117} 118/** 119 * checkconditions : javascript function attach to some element 120 * Launch ExprMgr_process_relevance_and_tailoring with good value 121 */ 122function checkconditions(value, name, type, evt_type) 123{ 124 if (typeof evt_type === 'undefined') 125 { 126 evt_type = 'onchange'; 127 } 128 if (type == 'radio' || type == 'select-one') 129 { 130 $('#java'+name).val(value); 131 } 132 else if (type == 'checkbox') 133 { 134 if ($('#answer'+name).is(':checked')) 135 { 136 $('#java'+name).val('Y'); 137 } else 138 { 139 $('#java'+name).val(''); 140 } 141 } 142 else if (type == 'text' && name.match(/other$/)) 143 { 144 $('#java'+name).val(value); 145 } 146 147 aQuestionsWithDependencies = $('#aQuestionsWithDependencies').data('qids'); 148 149 var result; 150 if(typeof name !== 'undefined') 151 { 152 result = name.split('X'); 153 result = result[2] 154 } 155 156 // $isRelevant = $.inArray(result, aQuestionsWithDependencies); NEED TO ADD THE QUESTIONS WITH CONDITIONS BEFORE WE CAN USE IT !!!! 157 $isRelevant = 1; 158 if($.isFunction(window.ExprMgr_process_relevance_and_tailoring ) && $isRelevant!=-1) 159 ExprMgr_process_relevance_and_tailoring(evt_type,name,type); 160} 161 162/** 163 * fixnum_checkconditions : javascript function attach to some element 164 * Update the answer of the user to be numeric and launch checkconditions 165 * 166 * Also checks if any of the arrow keys is pressed to avoid unecessary hassle. 167 */ 168function fixnum_checkconditions(value, name, type, evt_type, intonly) 169{ 170 if(window.event){ 171 var keyPressed = window.event.keyCode || 0; 172 if( 173 keyPressed == 37 //left arrow 174 || keyPressed == 39 //right arrow 175 ){return false; } 176 } 177 178 var decimalValue; 179 var newval = new String(value); 180 var checkNumericRegex = new RegExp(/^(-)?[0-9]*(,|\.|)[0-9]*$/); 181 var cleansedValue = newval.replace(numRegex,''); 182 /** 183 * If have to use parsed value. 184 */ 185 if(!bNumRealValue) 186 { 187 if(checkNumericRegex.test(value)) { 188 try{ 189 decimalValue = new Decimal(cleansedValue); 190 } catch(e){ 191 try{ 192 decimalValue = new Decimal(cleansedValue.replace(',','.')); 193 } catch(e){ 194 decimalValue = new Decimal(NaN); 195 } 196 } 197 198 if (typeof intonly !=='undefined' && intonly==1) { 199 newval = decimalValue.trunc(); 200 } 201 } else { 202 newval = cleansedValue; 203 } 204 205 } 206 207 /** 208 * If have to fix numbers automatically. 209 */ 210 if(bFixNumAuto && (newval != "")) 211 { 212 213 if(window.correctNumberField!=null) { 214 clearTimeout(window.correctNumberField); 215 window.correctNumberField = null; 216 } 217 218 var addition = ""; 219 if(cleansedValue && cleansedValue.split("").pop().match(/(,)|(\.)/)){ 220 addition = cleansedValue.split("").pop(); 221 } 222 var matchFollowingZeroes = cleansedValue.match(/^-?([0-9])*(,|\.)(0+)$/); 223 if(matchFollowingZeroes){ 224 addition = LEMradix+matchFollowingZeroes[3]; 225 } 226 if(decimalValue == undefined){ 227 try{ 228 decimalValue = new Decimal(cleansedValue); 229 } catch(e){ 230 try{ 231 decimalValue = new Decimal(cleansedValue.replace(',','.')); 232 } catch(e){ 233 decimalValue = new Decimal(NaN); 234 235 } 236 } 237 } 238 239 /** 240 * Work on length of the number 241 * Avoid numbers longer than 20 characters before the decimal separator and 10 after the decimal separator. 242 */ 243 // Treat decimal part, if there is one. 244 // Trim after 10th decimal if larger than 10 decimals. 245 if(decimalValue.dp()>10){ 246 decimalValue.toDecimalPlaces(10); 247 } 248 249 /** 250 * Set display value 251 */ 252 displayVal = decimalValue.toString(); 253 if (displayVal=='NaN') 254 { 255 newval=displayVal; 256 displayVal=value; 257 } 258 else{ 259 if(LEMradix==",") 260 displayVal = displayVal.replace(/\./,','); 261 262 newval = displayVal+addition 263 264 if (name.match(/other$/)) { 265 if($('#answer'+name+'text').val() != newval){ 266 $('#answer'+name+'text').val(newval); 267 } 268 } 269 270 if($('#answer'+name).val() != newval){ 271 window.correctNumberField = setTimeout(function(){$('#answer'+name).val(newval);}, 400); 272 } 273 } 274 } 275 276 /** 277 * Check conditions 278 */ 279 if (typeof evt_type === 'undefined') 280 { 281 evt_type = 'onchange'; 282 } 283 checkconditions(newval, name, type, evt_type); 284} 285 286// Set jquery-ui to LS Button 287function navbuttonsJqueryUi(){ 288 $('[dir!="rtl"] #moveprevbtn').button({ 289 icons: { 290 primary: 'ui-icon-triangle-1-w' 291 } 292 }); 293 $('[dir="rtl"] #moveprevbtn').button({ 294 icons: { 295 secondary: 'ui-icon-triangle-1-e' 296 } 297 }); 298 $('[dir!="rtl"] #movenextbtn').button({ 299 icons: { 300 secondary: 'ui-icon-triangle-1-e' 301 } 302 }); 303 $('[dir="rtl"] #movenextbtn').button({ 304 icons: { 305 primary: 'ui-icon-triangle-1-w' 306 } 307 }); 308 $(".button").button(); 309 // TODO trigger handler activate/deactivate to update ui-button class 310} 311/** 312 * showStartPopups : Take all message in startPopups json array and launch an alert with text 313 */ 314function showStartPopups(){ 315 if(typeof showpopup=="undefined"){showpopup=1;} 316 if(typeof startPopups=="undefined"){startPopups=[];} 317 if(showpopup){ 318 $.each(startPopups,function(key, text){ 319 alert($("<div/>").html(text).text());// Parse HTML because of ' 320 }); 321 } 322} 323/** 324 * Update survey just when select a new language 325 */ 326function activateLanguageChanger(){ 327 $(document).on('change','select.languagechanger', function() { 328 if($(this).hasClass('previewmode')) 329 { 330 var target=$(this).data('targeturl'); 331 $('<form>', { 332 "html": '<input type="hidden" name="lang" value="' + $(this).find('option:selected').val() + '" />', 333 "action": target 334 }).appendTo(document.body).submit(); 335 return false;
336 } 337 if(!$(this).closest('form').length){// If there are no form : we can't use it, we need to create and submit. This break no-js compatibility in some page (token for example). 338 if($('form#limesurvey').length==1){ // The limesurvey form exist in document, move select and button inside and click 339 $("form#limesurvey [name='lang']").remove();// Remove existing lang selector 340 $("<input type='hidden']>").attr('name','lang').val($(this).find('option:selected').val()).appendTo($('form#limesurvey')); 341 $("#changelangbtn").appendTo($('form#limesurvey')); 342 $('#changelangbtn').click(); 343 }else{ 344 if($(this).data('targeturl')){ 345 var target=$(this).data('targeturl'); 346 }else{ 347 var target=document.location.href; 348 } 349 $('<form>', { 350 "html": '<input type="hidden" name="lang" value="' + $(this).find('option:selected').val() + '" />', 351 "action": target, 352 "method": 'post' 353 }).appendTo(document.body).append($("input[name='YII_CSRF_TOKEN']")).submit(); 354 } 355 }else{ 356 $(this).closest('form').find("[name='lang']").not($(this)).remove();// Remove other lang 357 $('#changelangbtn').click(); 358 } 359 }); 360 $(function(){ 361 $(".changelang.jshide").hide(); 362 }); 363} 364/** 365 * Manage the index 366 */ 367function manageIndex(){ 368 $("#index .jshide").hide(); 369 $("#index").on('click','li,.row',function(e){ 370 if(!$(e.target).is('button')){ 371 $(this).children("[name='move']").click(); 372 } 373 }); 374 $(function() { 375 $(".outerframe").addClass("withindex"); 376 var idx = $("#index"); 377 var row = $("#index .row.current"); 378 if(row.length) 379 idx.scrollTop(row.position().top - idx.height() / 2 - row.height() / 2); 380 }); 381} 382/** 383 * Put a empty class on empty answer text item (limit to answers part) 384 * @author Denis Chenu / Shnoulle 385 */ 386function addClassEmpty() 387{ 388 $('.answer-item input.text[value=""]').addClass('empty'); 389 $('.answer-item input[type=text][value=""]').addClass('empty'); 390 $('.answer-item textarea').each(function(index) { 391 if ($(this).val() == ""){ 392 $(this).addClass('empty'); 393 } 394 }); 395 $("body").delegate(".answer-item input.text,.text-item input[type=text],.answer-item textarea","blur focusout",function(){ 396 if ($(this).val() == ""){ 397 $(this).addClass('empty'); 398 }else{ 399 $(this).removeClass('empty'); 400 } 401 }); 402} 403 404/** 405 * Disable scroll on select, put it in function to allow update in template 406 * 407 */ 408function noScrollOnSelect() 409{ 410 $(".question").find("select").each(function () { 411 hookEvent($(this).attr('id'),'mousewheel',noScroll); 412 }); 413} 414/** 415 * Adapt cell to have a click on cell do a click on input:radio or input:checkbox (if unique) 416 * Using delegate the can be outside document.ready (using .on is possible but on $(document) then : less readbale 417 * @author Denis Chenu / Shnoulle 418 */ 419function tableCellAdapters() 420{ 421// $('table.question').delegate('tbody td input:checkbox,tbody td input:radio,tbody td label',"click", function(e) { 422// e.stopPropagation(); 423// }); 424 $(document).on('click','table.question tbody td',function(event) {// 'table.question tbody td' or 'td.radio-item,td.checkbox-item': maybe less js here 425 var eventTarget=$(event.target).prop("tagName");// Alternative us data 426 var eventActivate=$(this).find("input:radio,input:checkbox"); 427 if(eventActivate.length==1 && (eventTarget!='INPUT' && eventTarget!='LABEL' ) ) 428 { 429 $(eventActivate).click(); 430 $(eventActivate).triggerHandler("click"); 431 // Why not use trigger('click'); only ? 432 } 433 }); 434} 435 436Array.prototype.push = function() 437{ 438 var n = this.length >>> 0; 439 for (var i = 0; i < arguments.length; i++) 440 { 441 this[n] = arguments[i]; 442 n = n + 1 >>> 0; 443 } 444 this.length = n; 445 return n; 446}; 447 448Array.prototype.pop = function() { 449 var n = this.length >>> 0, value; 450 if (n) { 451 value = this[--n]; 452 delete this[n]; 453 } 454 this.length = n; 455 return value; 456}; 457 458 459//defined in group.php & question.php & survey.php, but a static function 460function inArray(needle, haystack) 461{ 462 for (h in haystack) 463 { 464 if (haystack[h] == needle) 465 { 466 return true; 467 } 468 } 469 return false;
470} 471 472//defined in group.php & survey.php, but a static function 473function match_regex(testedstring,str_regexp) 474{ 475 // Regular expression test 476 if (str_regexp == '' || testedstring == '') return false; 477 pattern = new RegExp(str_regexp); 478 return pattern.test(testedstring) 479} 480 481function addHiddenField(theform,thename,thevalue) 482{ 483 var myel = document.createElement('input'); 484 myel.type = 'hidden'; 485 myel.name = thename; 486 theform.appendChild(myel); 487 myel.value = thevalue; 488} 489 490function cancelBubbleThis(eventObject) 491{ 492 if (!eventObject) var eventObject = window.event; 493 eventObject.cancelBubble = true; 494 if (eventObject && eventObject.stopPropagation) { 495 eventObject.stopPropagation(); 496 } 497} 498 499function cancelEvent(e) 500{ 501 e = e ? e : window.event; 502 if(e.stopPropagation) 503 e.stopPropagation(); 504 if(e.preventDefault) 505 e.preventDefault(); 506 e.cancelBubble = true; 507 e.cancel = true; 508 e.returnValue = false; 509 return false; 510} 511 512function hookEvent(element, eventName, callback) 513{ 514 if(typeof(element) == "string") 515 element = document.getElementById(element); 516 if(element == null) 517 return; 518 if(element.addEventListener) 519 { 520 if(eventName == 'mousewheel') 521 element.addEventListener('DOMMouseScroll', callback, false); 522 element.addEventListener(eventName, callback, false); 523 } 524 else if(element.attachEvent) 525 element.attachEvent("on" + eventName, callback); 526} 527 528function noScroll(e) 529{ 530 e = e ? e : window.event; 531 cancelEvent(e); 532} 533 534 535function getkey(e) 536{ 537 if (window.event) return window.event.keyCode; 538 else if (e) return e.which; else return null; 539} 540 541function goodchars(e, goods) 542{ 543 var key, keychar; 544 key = getkey(e); 545 if (key == null) return true; 546 547 // get character 548 keychar = String.fromCharCode(key); 549 keychar = keychar.toLowerCase(); 550 goods = goods.toLowerCase(); 551 552 // check goodkeys 553 if (goods.indexOf(keychar) != -1) 554 return true; 555 556 // control keys 557 if ( key==null || key==0 || key==8 || key==9 || key==27 ) 558 return true; 559 560 // else return false 561 return false; 562} 563 564function show_hide_group(group_id) 565{ 566 var questionCount; 567 568 // First let's show the group description, otherwise, all its childs would have the hidden status 569 $("#group-" + group_id).show(); 570 // If all questions in this group are conditionnal 571 // Count visible questions in this group 572 questionCount=$("div#group-" + group_id).find("div[id^='question']:visible").size(); 573 574 if( questionCount == 0 ) 575 { 576 $("#group-" + group_id).hide(); 577 } 578} 579 580// round function from phpjs.org 581function round (value, precision, mode) { 582 // http://kevin.vanzonneveld.net 583 var m, f, isHalf, sgn; // helper variables 584 precision |= 0; // making sure precision is integer 585 m = Math.pow(10, precision); 586 value *= m; 587 sgn = (value > 0) | -(value < 0); // sign of the number 588 isHalf = value % 1 === 0.5 * sgn; 589 f = Math.floor(value); 590 591 if (isHalf) { 592 switch (mode) { 593 case 'PHP_ROUND_HALF_DOWN': 594 value = f + (sgn < 0); // rounds .5 toward zero 595 break; 596 case 'PHP_ROUND_HALF_EVEN': 597 value = f + (f % 2 * sgn); // rouds .5 towards the next even integer 598 break; 599 case 'PHP_ROUND_HALF_ODD': 600 value = f + !(f % 2); // rounds .5 towards the next odd integer 601 break; 602 default: 603 value = f + (sgn > 0); // rounds .5 away from zero 604 } 605 } 606 607 return (isHalf ? value : Math.round(value)) / m; 608} 609 610/* Maxlengt on textarea */ 611function maxlengthtextarea(){ 612 // Calling this function at document.ready : use maxlength attribute on textarea 613 // Can be replaced by inline javascript 614 $("textarea[maxlength]").change(function(){ // global solution 615 var maxlen=$(this).attr("maxlength"); 616 if ($(this).val().length > maxlen) { 617 $(this).val($(this).val().substring(0, maxlen)); 618 } 619 }); 620 $("textarea[maxlength]").keyup(function(){ // For copy/paste (not for all browser) 621 var maxlen=$(this).attr("maxlength"); 622 if ($(this).val().length > maxlen) { 623 $(this).val($(this).val().substring(0, maxlen)); 624 } 625 }); 626 $("textarea[maxlength]").keydown(function(event){ // No new key after maxlength 627 var maxlen=$(this).attr("maxlength"); 628 var k =event.keyCode; 629 if (($(this).val().length >= maxlen) && 630 !(k == null ||k==0||k==8||k==9||k==13||k==27||k==37||k==38||k==39||k==40||k==46)) { 631 // Don't accept new key except NULL,Backspace,Tab,Enter,Esc,arrows,Delete 632 return false;
633 } 634 }); 635} 636/** 637 * Add a title on cell with answer 638 * Title must be updated because label can be updated by expression : mouseover do it only if needed. Accessibility must use aria-labelledby 639 **/ 640function doToolTipTable() 641{ 642 $(document).on("mouseover"," td.answer-item",function() { 643 var text = $(this).find('label').text().trim(); 644 if(text!==""){ 645 $(this).attr('title', text); 646 }else{ 647 $(this).removeAttr('title'); 648 } 649 }); 650} 651//Hide the Answer and the helper field in an 652$(document).ready( 653 function(){ 654 $('.question-container').each(function(){ 655 if($(this).find('div.answer-container').find('input').length == 1) 656 { 657 if($(this).find('div.answer-container').find('input[type=hidden]').length >0 658 && $(this).find('div.answer-container').find('select').length < 1) 659 { 660 $(this).find('div.answer-container').css({display: 'none'}); 661 } 662 if(trim($(this).find('div.question-help-container').find('div').html()) == "") 663 { 664 $(this).find('div.question-help-container').css({display: 'none'}); 665 } 666 } 667 }); 668 } 669);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.