api_wrapper.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714
  1. /**
  2. * Wrapper to the SCORM API provided by Chamilo
  3. * The complete set of functions and variables are in this file to avoid unnecessary file
  4. * accesses.
  5. * Only event triggers and answer data are inserted into the final document.
  6. * @author Yannick Warnier - inspired by the ADLNet documentation on SCORM content-side API
  7. * @package scorm.js
  8. */
  9. /**
  10. * Initialisation of the SCORM API section.
  11. * Find the SCO functions (startTimer, computeTime, etc in the second section)
  12. * Find the Chamilo-proper functions (checkAnswers, etc in the third section)
  13. */
  14. var _debug = true;
  15. var findAPITries = 0;
  16. var _apiHandle = null; //private variable
  17. var errMsgLocate = "Unable to locate the LMS's API implementation";
  18. var _NoError = 0;
  19. var _GeneralException = 101;
  20. var _ServerBusy = 102;
  21. var _InvalidArgumentError = 201;
  22. var _ElementCannotHaveChildren = 202;
  23. var _ElementIsNotAnArray = 203;
  24. var _NotInitialized = 301;
  25. var _NotImplementedError = 401;
  26. var _InvalidSetValue = 402;
  27. var _ElementIsReadOnly = 403;
  28. var _ElementIsWriteOnly = 404;
  29. var _IncorrectDataType = 405;
  30. var startTime;
  31. var exitPageStatus;
  32. /**
  33. * Gets the API handle right into the local API object and ensure there is only one.
  34. * Using the singleton pattern to ensure there's only one API object.
  35. * @return object The API object as given by the LMS
  36. */
  37. var API = new function()
  38. {
  39. if (_apiHandle == null) {
  40. _apiHandle = getAPI();
  41. }
  42. return _apiHandle;
  43. }
  44. /**
  45. * Finds the API on the LMS side or gives up giving an error message
  46. * @param object The window/frame object in which we are searching for the SCORM API
  47. * @return object The API object recovered from the LMS's implementation of the SCORM API
  48. */
  49. function findAPI(win)
  50. {
  51. while((win.API == null) && (win.parent != null) && (win.parent != win)) {
  52. findAPITries++;
  53. if (findAPITries>10) {
  54. alert("Error finding API - too deeply nested");
  55. return null;
  56. }
  57. win = win.parent
  58. }
  59. return win.API;
  60. }
  61. /**
  62. * Gets the API from the current window/frame or from parent objects if not found
  63. * @return object The API object recovered from the LMS's implementation of the SCORM API
  64. */
  65. function getAPI()
  66. {
  67. //window is the global/root object of the current window/frame
  68. var MyAPI = findAPI(window);
  69. //look through parents if any
  70. if ((MyAPI == null) && (window.opener != null) && (typeof(window.opener) != "undefined")) {
  71. MyAPI = findAPI(window.opener);
  72. }
  73. //still not found? error message
  74. if (MyAPI == null) {
  75. alert("Unable to find SCORM API adapter.\nPlease check your LMS is considering this page as SCORM and providing the right JavaScript interface.")
  76. }
  77. return MyAPI;
  78. }
  79. /**
  80. * Handles error codes (prints the error if it has a description)
  81. * @return int Error code from LMS's API
  82. */
  83. function errorHandler()
  84. {
  85. if (API == null) {
  86. alert("Unable to locate the LMS's API. Cannot determine LMS error code");
  87. return;
  88. }
  89. var errCode = API.LMSGetLastError().toString();
  90. if (errCode != _NoError) {
  91. if (errCode == _NotImplementedError) {
  92. var errDescription = "The LMS doesn't support this feature";
  93. if (_debug) {
  94. errDescription += "\n";
  95. errDescription += api.LMSGetDiagnostic(null);
  96. }
  97. addDebug(errDescription);
  98. } else {
  99. var errDescription = API.LMSGetErrorString(errCode);
  100. if (_debug) {
  101. errDescription += "\n";
  102. errDescription += api.LMSGetDiagnostic(null);
  103. }
  104. addDebug(errDescription);
  105. }
  106. }
  107. return errCode;
  108. }
  109. function addDebug(message) {
  110. if (_debug && window.console) {
  111. console.log(message);
  112. }
  113. }
  114. function addDebugTable(message) {
  115. if (_debug && window.console) {
  116. console.table(message);
  117. }
  118. }
  119. /**
  120. * Calls the LMSInitialize method of the LMS's API object
  121. * @return string The string value of the LMS returned value or false if error (should be "true" otherwise)
  122. */
  123. function doLMSInitialize()
  124. {
  125. if (API == null) {
  126. alert(errMsgLocate + "\nLMSInitialize failed");
  127. return false;
  128. }
  129. var result = API.LMSInitialize("");
  130. if (result.toString() != "true") {
  131. var err = errorHandler();
  132. }
  133. return result.toString();
  134. }
  135. /**
  136. * Calls the LMSFinish method of the LMS's API object
  137. * @return string The string value of the LMS return value, or false if error (should be "true" otherwise)
  138. */
  139. function doLMSFinish()
  140. {
  141. if (API == null) {
  142. alert(errMsgLocate + "\nLMSFinish failed");
  143. return false;
  144. } else {
  145. var result = API.LMSFinish('');
  146. if (result.toString() != "true") {
  147. var err = errorHandler();
  148. }
  149. }
  150. return result.toString();
  151. }
  152. /**
  153. * Calls the LMSGetValue method
  154. * @param string The name of the SCORM parameter to get
  155. * @return string The value returned by the LMS
  156. */
  157. function doLMSGetValue(name)
  158. {
  159. if (API == null) {
  160. alert(errMsgLocate + "\nLMSGetValue was not successful.");
  161. return '';
  162. } else {
  163. var value = API.LMSGetValue(name);
  164. var errCode = API.LMSGetLastError().toString();
  165. if (errCode != _NoError) {
  166. // an error was encountered so display the error description
  167. var errDescription = API.LMSGetErrorString(errCode);
  168. addDebug("LMSGetValue(" + name + ") failed. \n" + errDescription)
  169. return '';
  170. }
  171. return value.toString();
  172. }
  173. }
  174. /**
  175. * Calls the LMSSetValue method of the API object
  176. * @param string The name of the SCORM parameter to set
  177. * @param string The value to set the parameter to
  178. * @return void
  179. */
  180. function doLMSSetValue(name, value)
  181. {
  182. if (API == null) {
  183. alert("Unable to locate the LMS's API Implementation.\nLMSSetValue was not successful.");
  184. return;
  185. } else {
  186. var result = API.LMSSetValue(name, value);
  187. if (result.toString() != "true") {
  188. var err = errorHandler();
  189. }
  190. }
  191. return;
  192. }
  193. /**
  194. * Calls the LMSCommit method
  195. */
  196. function doLMSCommit()
  197. {
  198. if (API == null) {
  199. alert(errMsgLocate + "\nLMSCommit was not successful.");
  200. return "false";
  201. } else {
  202. var result = API.LMSCommit("");
  203. if (result != "true") {
  204. var err = errorHandler();
  205. }
  206. }
  207. return result.toString();
  208. }
  209. /**
  210. * Calls GetLastError()
  211. */
  212. function doLMSGetLastError()
  213. {
  214. if (API == null) {
  215. alert(errMsgLocate + "\nLMSGetLastError was not successful.");
  216. //since we can't get the error code from the LMS, return a general error
  217. return _GeneralError;
  218. }
  219. return API.LMSGetLastError().toString();
  220. }
  221. /**
  222. * Calls LMSGetErrorString()
  223. */
  224. function doLMSGetErrorString(errorCode)
  225. {
  226. if (API == null) {
  227. alert(errMsgLocate + "\nLMSGetErrorString was not successful.");
  228. }
  229. return API.LMSGetErrorString(errorCode).toString();
  230. }
  231. /**
  232. * Calls LMSGetDiagnostic()
  233. */
  234. function doLMSGetDiagnostic(errorCode)
  235. {
  236. if (API == null) {
  237. alert(errMsgLocate + "\nLMSGetDiagnostic was not successful.");
  238. }
  239. return API.LMSGetDiagnostic(errorCode).toString();
  240. }
  241. /**
  242. * Initialise page values
  243. */
  244. function loadPage()
  245. {
  246. var result = doLMSInitialize();
  247. if (result) {
  248. var status = doLMSGetValue("cmi.core.lesson_status");
  249. if (status == "not attempted") {
  250. doLMSSetValue("cmi.core.lesson_status", "incomplete");
  251. }
  252. exitPageStatus = false;
  253. startTimer();
  254. }
  255. }
  256. /**
  257. * Starts the local timer
  258. */
  259. function startTimer()
  260. {
  261. startTime = new Date().getTime();
  262. }
  263. /**
  264. * Calculates the total time and sends the result to the LMS
  265. */
  266. function computeTime()
  267. {
  268. if (startTime != 0) {
  269. var currentDate = new Date().getTime();
  270. var elapsedSeconds = ( (currentDate - startTime) / 1000 );
  271. var formattedTime = convertTotalSeconds(elapsedSeconds);
  272. } else {
  273. formattedTime = "00:00:00.0";
  274. }
  275. doLMSSetValue( "cmi.core.session_time", formattedTime );
  276. }
  277. /**
  278. * Formats the time in a SCORM time format
  279. */
  280. function convertTotalSeconds(ts)
  281. {
  282. var sec = (ts % 60);
  283. ts -= sec;
  284. var tmp = (ts % 3600); //# of seconds in the total # of minutes
  285. ts -= tmp; //# of seconds in the total # of hours
  286. // convert seconds to conform to CMITimespan type (e.g. SS.00)
  287. sec = Math.round(sec*100)/100;
  288. var strSec = new String(sec);
  289. var strWholeSec = strSec;
  290. var strFractionSec = "";
  291. if (strSec.indexOf(".") != -1) {
  292. strWholeSec = strSec.substring(0, strSec.indexOf("."));
  293. strFractionSec = strSec.substring(strSec.indexOf(".") + 1, strSec.length);
  294. }
  295. if (strWholeSec.length < 2) {
  296. strWholeSec = "0" + strWholeSec;
  297. }
  298. strSec = strWholeSec;
  299. if (strFractionSec.length) {
  300. strSec = strSec + "." + strFractionSec;
  301. }
  302. if ((ts % 3600) != 0)
  303. var hour = 0;
  304. else var hour = (ts / 3600);
  305. if ((tmp % 60) != 0)
  306. var min = 0;
  307. else var min = (tmp / 60);
  308. if ((new String(hour)).length < 2)
  309. hour = "0" + hour;
  310. if ((new String(min)).length < 2)
  311. min = "0" + min;
  312. var rtnVal = hour + ":" + min + ":" + strSec;
  313. return rtnVal
  314. }
  315. /**
  316. * Handles the use of the back button (saves data and closes SCO)
  317. */
  318. function doBack()
  319. {
  320. checkAnswers(true);
  321. doLMSSetValue( "cmi.core.exit", "suspend" );
  322. computeTime();
  323. exitPageStatus = true;
  324. var result;
  325. result = doLMSCommit();
  326. result = doLMSFinish();
  327. }
  328. /**
  329. * Handles the closure of the current SCO before an interruption. This is only useful if the LMS
  330. * deals with the cmi.core.exit, cmi.core.lesson_status and cmi.core.lesson_mode *and* the SCO
  331. * sends some kind of value for cmi.core.exit, which is not the case here (yet).
  332. */
  333. function doContinue(status)
  334. {
  335. // Reinitialize Exit to blank
  336. doLMSSetValue( "cmi.core.exit", "" );
  337. var mode = doLMSGetValue( "cmi.core.lesson_mode" );
  338. if ( mode != "review" && mode != "browse" )
  339. {
  340. doLMSSetValue( "cmi.core.lesson_status", status );
  341. }
  342. computeTime();
  343. exitPageStatus = true;
  344. var result;
  345. result = doLMSCommit();
  346. result = doLMSFinish();
  347. }
  348. /**
  349. * handles the recording of everything on a normal shutdown
  350. */
  351. function doQuit()
  352. {
  353. checkAnswers();
  354. computeTime();
  355. exitPageStatus = true;
  356. var result;
  357. result = doLMSCommit();
  358. result = doLMSFinish();
  359. }
  360. /**
  361. * Called upon unload event from body element
  362. */
  363. function unloadPage(status)
  364. {
  365. if (!exitPageStatus)
  366. {
  367. // doQuit( status );
  368. }
  369. }
  370. /**
  371. * Checks the answers on the test formular page
  372. */
  373. function checkAnswers(interrupted)
  374. {
  375. var tmpScore = 0;
  376. var status = 'not attempted';
  377. var scoreMax = 0;
  378. addDebug('Number of questions: '+ questions.length);
  379. for (var i=0; i < questions.length; i++) {
  380. if (questions[i] != undefined && questions[i] != null) {
  381. var idQuestion = questions[i];
  382. var type = questions_types[idQuestion];
  383. var interactionScore = 0;
  384. var interactionAnswers = '';
  385. var interactionCorrectResponses = '';
  386. var interactionType = '';
  387. addDebug('idQuestion: ' +idQuestion + ', Type: ' +type);
  388. addDebug('questions_answers: ');
  389. addDebugTable(questions_answers[idQuestion]);
  390. addDebug('questions_answers_ponderation: ');
  391. addDebugTable(questions_answers_ponderation[idQuestion]);
  392. addDebug('questions_answers_correct: ');
  393. addDebugTable(questions_answers_correct[idQuestion]);
  394. switch (type) {
  395. case 'mcma':
  396. interactionType = 'choice';
  397. var myScore = 0;
  398. for(var j = 0; j< questions_answers[idQuestion].length;j++) {
  399. var idAnswer = questions_answers[idQuestion][j];
  400. var answer = document.getElementById('question_'+(idQuestion)+'_multiple_'+(idAnswer));
  401. if (answer.checked) {
  402. interactionAnswers += idAnswer+'__|';// changed by isaac flores
  403. myScore += questions_answers_ponderation[idQuestion][idAnswer];
  404. }
  405. }
  406. interactionScore = myScore;
  407. scoreMax += questions_score_max[idQuestion];
  408. addDebug("Score: "+myScore);
  409. break;
  410. case 'mcua':
  411. interactionType = 'choice';
  412. var myScore = 0;
  413. for (var j = 0; j<questions_answers[idQuestion].length; j++) {
  414. var idAnswer = questions_answers[idQuestion][j];
  415. var elementId = 'question_'+(idQuestion)+'_unique_'+(idAnswer);
  416. var answer = document.getElementById(elementId);
  417. if (answer.checked) {
  418. addDebug('Element id # "'+ elementId +'" was checked');
  419. interactionAnswers += idAnswer;
  420. addDebug("List of correct answers: "+questions_answers_correct[idQuestion]);
  421. addDebug('Score for this answer: ' + questions_answers_ponderation[idQuestion][idAnswer]);
  422. addDebug("idAnswer: "+idAnswer);
  423. addDebug("Option selected: "+questions_answers_correct[idQuestion][idAnswer]);
  424. if (questions_answers_correct[idQuestion][idAnswer] == 1) {
  425. if (questions_answers_ponderation[idQuestion][idAnswer]) {
  426. myScore += questions_answers_ponderation[idQuestion][idAnswer];
  427. } else {
  428. myScore++;
  429. }
  430. }
  431. }
  432. }
  433. addDebug("Score: "+myScore);
  434. interactionScore = myScore;
  435. scoreMax += questions_score_max[idQuestion];
  436. break;
  437. case 'tf':
  438. interactionType = 'true-false';
  439. var myScore = 0;
  440. for (var j = 0; j < questions_answers[idQuestion].length; j++) {
  441. var idAnswer = questions_answers[idQuestion][j];
  442. var answer = document.getElementById('question_' + idQuestion + '_tf_' + (idAnswer));
  443. if (answer.checked.value) {
  444. interactionAnswers += idAnswer;
  445. for (k = 0; k < questions_answers_correct[idQuestion].length; k++) {
  446. if (questions_answers_correct[idQuestion][k] == idAnswer) {
  447. if (questions_answers_ponderation[idQuestion][idAnswer]) {
  448. myScore += questions_answers_ponderation[idQuestion][idAnswer];
  449. } else {
  450. myScore++;
  451. }
  452. }
  453. }
  454. }
  455. }
  456. addDebug("Score: "+ myScore);
  457. interactionScore = myScore;
  458. scoreMax += questions_score_max[idQuestion];
  459. break;
  460. case 'fib':
  461. interactionType = 'fill-in';
  462. var myScore = 0;
  463. for (var j = 0; j < questions_answers[idQuestion].length; j++) {
  464. var idAnswer = questions_answers[idQuestion][j];
  465. var answer = document.getElementById('question_'+(idQuestion)+'_fib_'+(idAnswer));
  466. if (answer.value) {
  467. interactionAnswers += answer.value + '__|';//changed by isaac flores
  468. for (k = 0; k < questions_answers_correct[idQuestion].length; k++) {
  469. if (questions_answers_correct[idQuestion][k] == answer.value) {
  470. if (questions_answers_ponderation[idQuestion][idAnswer]) {
  471. myScore += questions_answers_ponderation[idQuestion][idAnswer];
  472. } else {
  473. myScore++;
  474. }
  475. }
  476. }
  477. }
  478. }
  479. addDebug("Score: "+myScore);
  480. interactionScore = myScore;
  481. scoreMax += questions_score_max[idQuestion];
  482. break;
  483. case 'matching':
  484. interactionType = 'matching';
  485. var myScore = 0;
  486. addDebug("List of correct answers: ");
  487. console.log(questions_answers_correct[idQuestion]);
  488. for (var j = 0; j < questions_answers[idQuestion].length; j++) {
  489. var idAnswer = questions_answers[idQuestion][j];
  490. var elementId = 'question_' + (idQuestion) + '_matching_' + (idAnswer);
  491. addDebug("---------idAnswer #"+idAnswer+'------------------');
  492. addDebug("Checking element #"+elementId);
  493. var answer = document.getElementById(elementId);
  494. if (answer && answer.value) {
  495. interactionAnswers += answer.value + '__|';//changed by isaac flores
  496. for (k = 0; k < questions_answers_correct[idQuestion].length; k++) {
  497. var left = questions_answers_correct[idQuestion][k][0];
  498. var right = questions_answers_correct[idQuestion][k][1];
  499. addDebug('Left ' + left);
  500. addDebug('Right ' + right);
  501. addDebug('answer.value ' + answer.value);
  502. if (right == idAnswer && left == answer.value) {
  503. addDebug('Score for this answer: ' + questions_answers_ponderation[idQuestion][idAnswer]);
  504. if (questions_answers_ponderation[idQuestion][idAnswer]) {
  505. myScore += questions_answers_ponderation[idQuestion][idAnswer];
  506. } else {
  507. // myScore++;
  508. }
  509. }
  510. }
  511. }
  512. addDebug("Partial score: "+myScore);
  513. addDebug("--------- end --- idAnswer #"+idAnswer+'------------------');
  514. }
  515. addDebug("Score: "+myScore);
  516. interactionScore = myScore;
  517. scoreMax += questions_score_max[idQuestion];
  518. break;
  519. case 'free':
  520. //ignore for now as a score cannot be given
  521. interactionType = 'free';
  522. var answer = document.getElementById('question_'+(idQuestion)+'_free');
  523. if (answer && answer.value) {
  524. interactionAnswers += answer.value
  525. }
  526. //interactionScore = questions_score_max[idQuestion];
  527. interactionScore = 0;
  528. scoreMax += questions_score_max[idQuestion];
  529. //interactionAnswers = document.getElementById('question_'+(idQuestion)+'_free').value;
  530. //correct responses work by pattern, see SCORM Runtime Env Doc
  531. //interactionCorrectResponses += questions_answers_correct[idQuestion].toString();
  532. break;
  533. case 'hotspot':
  534. interactionType = 'sequencing';
  535. interactionScore = 0;
  536. //if(question_score && question_score[idQuestion]){
  537. // interactionScore = question_score[idQuestion];
  538. //} //else, 0
  539. //interactionAnswers = document.getElementById('question_'+(idQuestion)+'_free').innerHTML;
  540. //correct responses work by pattern, see SCORM Runtime Env Doc
  541. //for(k=0;k<questions_answers_correct[idQuestion].length;k++)
  542. //{
  543. // interactionCorrectResponses += questions_answers_correct[idQuestion][k].toString()+',';
  544. //}
  545. break;
  546. case 'exact':
  547. interactionType = 'exact';
  548. interactionScore = 0;
  549. var real_answers = new Array();
  550. for (var j = 0; j < questions_answers[idQuestion].length; j++) {
  551. var idAnswer = questions_answers[idQuestion][j];
  552. var answer = document.getElementById('question_' + (idQuestion) + '_exact_' + (idAnswer));
  553. if (answer.checked == true) {
  554. interactionAnswers += idAnswer+', ';
  555. if (questions_answers_correct[idQuestion][idAnswer] != 0) {
  556. real_answers[j] = true;
  557. } else {
  558. real_answers[j] = false;
  559. }
  560. } else {
  561. if (questions_answers_correct[idQuestion][idAnswer] != 0) {
  562. real_answers[j] = false;
  563. } else {
  564. real_answers[j] = true;
  565. }
  566. }
  567. }
  568. var final_answer = true;
  569. for (var z = 0; z < real_answers.length; z++) {
  570. if (real_answers[z] == false) {
  571. final_answer = false;
  572. }
  573. }
  574. interactionScore = 0;
  575. addDebug(real_answers);
  576. if (final_answer) {
  577. //getting only the first score where we save the weight of all the question
  578. interactionScore = questions_answers_ponderation[idQuestion][1];
  579. }
  580. addDebug("Score: "+interactionScore);
  581. scoreMax += questions_score_max[idQuestion];
  582. break;
  583. }
  584. tmpScore += interactionScore;
  585. doLMSSetValue('cmi.interactions.'+idQuestion+'.id', 'Q'+idQuestion);
  586. doLMSSetValue('cmi.interactions.'+idQuestion+'.type', interactionType);
  587. doLMSSetValue('cmi.interactions.'+idQuestion+'.student_response', interactionAnswers);
  588. doLMSSetValue('cmi.interactions.'+idQuestion+'.result', interactionScore);
  589. }
  590. }
  591. doLMSSetValue('cmi.core.score.min', 0);
  592. doLMSSetValue('cmi.core.score.max', scoreMax);
  593. doLMSSetValue('cmi.core.score.raw', tmpScore);
  594. // Get status
  595. var mastery_score = doLMSGetValue('cmi.student_data.mastery_score');
  596. if (mastery_score <= 0) {
  597. mastery_score = (scoreMax * 0.80);
  598. }
  599. if (tmpScore >= mastery_score) {
  600. status = 'passed';
  601. } else {
  602. status = 'failed';
  603. }
  604. addDebug('student_score: ' + tmpScore);
  605. addDebug('mastery_score: ' + mastery_score);
  606. addDebug('cmi.core.score.max: ' + scoreMax);
  607. addDebug('cmi.core.lesson_status: ' + status);
  608. doLMSSetValue('cmi.core.lesson_status', status);
  609. if (interrupted && (status != 'completed') && (status != 'passed')) {
  610. doLMSSetValue('cmi.core.exit', 'suspended');
  611. }
  612. return false; //do not submit the form
  613. }
  614. (function($){
  615. //Shuffle all rows, while keeping the first column
  616. //Requires: Shuffle
  617. $.fn.shuffleRows = function(){
  618. return this.each(function(){
  619. var main = $(/table/i.test(this.tagName) ? this.tBodies[0] : this);
  620. var firstElem = [], counter=0;
  621. main.children().each(function(){
  622. firstElem.push(this.firstChild);
  623. });
  624. main.shuffle();
  625. main.children().each(function(){
  626. this.insertBefore(firstElem[counter++], this.firstChild);
  627. });
  628. });
  629. }
  630. /* Shuffle is required */
  631. $.fn.shuffle = function() {
  632. return this.each(function(){
  633. var items = $(this).children();
  634. return (items.length)
  635. ? $(this).html($.shuffle(items))
  636. : this;
  637. });
  638. }
  639. $.shuffle = function(arr) {
  640. for(
  641. var j, x, i = arr.length; i;
  642. j = parseInt(Math.random() * i),
  643. x = arr[--i], arr[i] = arr[j], arr[j] = x
  644. );
  645. return arr;
  646. }
  647. })(jQuery);
  648. /*
  649. * Assigns any event handler to any element
  650. * @param object Element on which the event is added
  651. * @param string Name of event
  652. * @param string Function to trigger on event
  653. * @param boolean Capture the event and prevent
  654. */
  655. function addEvent(elm, evType, fn, useCapture)
  656. {
  657. if (elm.addEventListener) {
  658. elm.addEventListener(evType, fn, useCapture);
  659. return true;
  660. } else if(elm.attachEvent) {
  661. var r = elm.attachEvent('on' + evType, fn);
  662. return r;
  663. } else {
  664. elm['on' + evType] = fn;
  665. }
  666. }