materio_flag.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710
  1. // @codekit-prepend "gui.js"
  2. (function($) {
  3. MaterioFlag = function(){
  4. var _isLoadingList = false ;
  5. /**
  6. * init()
  7. */
  8. function init(){
  9. //trace('MaterioFlag :: init MaterioFlag');
  10. buildBlocks();
  11. $(document)
  12. .bind('flagGlobalAfterLinkUpdate', onFlaging)
  13. .bind('resultscompleted resultschanged', onResultsUpdated)
  14. .bind('init-scroller-pager', onInitScrollerPager)
  15. .bind('load-scroller-pager', onLoadScrollerPager)
  16. .bind('view-mode-changed', onViewModeChanged)
  17. .bind('history-state-change', onHistoryStateChange);
  18. // ajaxifyLinks();
  19. // trigger updated event for direct html loading
  20. if(isList()){
  21. setTimeout(function(){
  22. triggerContentChanged();
  23. }, 10);
  24. }
  25. };
  26. function onFlaging(event){
  27. //trace('MaterioFlag :: onFlaging', event);
  28. refreshBlocks();
  29. };
  30. function onResultsUpdated(event){
  31. //trace('MaterioFlag :: onResultsUpdated', event);
  32. ajaxifyLinks(event.container);
  33. };
  34. function buildBlocks(activename){
  35. //trace('MaterioFlag :: buildBlocks | activename', activename);
  36. if($('#block-materio-flag-materio-flag-mybookmarks').length){
  37. var type = 'bookmarks';
  38. var block = '#block-materio-flag-materio-flag-mybookmarks';
  39. }else if($('#block-materio-flag-materio-flag-mylists').length){
  40. var type = 'lists';
  41. var block = '#block-materio-flag-materio-flag-mylists';
  42. }
  43. switch(type){
  44. case 'bookmarks':
  45. var name = type;
  46. $('h2 .listname', block).attr('name', name).bind('click', onClickShowPreview);
  47. $('<i class="icon-remove"></i>').appendTo($('h2', block)).attr('name', name).bind('click', onClickClosePreview);
  48. // $('<span class="preview"><i class="icon-eye-open"></i></span>').appendTo($('h2', block)).bind('click', onClickShowPreview);
  49. // if(!readCookie('materiobookmarkspreviewopened')){
  50. // showPreview('bookmarks', block);
  51. // }else{
  52. // }
  53. break;
  54. case 'lists':
  55. // nav block
  56. $('a.open-list:not(.ajax-processed)', '#block-materio-flag-materio-flag-mylists-nav').each(function(index){
  57. //trace('nav block a.open-list this', this);
  58. $this = $(this)
  59. .bind('click', onClickOpenLink)
  60. .addClass('ajax-processed');
  61. var name = $this.attr('class').match(/flag_lists_[^_]+_[0-9]+/);
  62. // trace('MaterioFlag :: name', name);
  63. $('<span class="preview"><i class="icon-eye-open"></i></span>').attr('name', name).insertAfter($this).bind('click', onClickShowPreview);
  64. });
  65. $('a.edit-list:not(.ajax-processed)', '#block-materio-flag-materio-flag-mylists-nav')
  66. .bind('click', onCLickEditList)
  67. .addClass('ajax-processed');
  68. $('a.flag-lists-create:not(.ajax-processed)', '#block-materio-flag-materio-flag-mylists-nav')
  69. .bind('click', onClickCreatLink)
  70. .addClass('ajax-processed');
  71. // preview block
  72. $('section.flag-list:not(.ajax-processed)', '#block-materio-flag-materio-flag-mylists').each(function(index){
  73. var name = $(this).attr('class').match(/flag_lists_[^_]+_[0-9]+/);
  74. $('<i class="icon-remove"></i>').appendTo($('h2.listname', this)).attr('name', name).bind('click', onClickClosePreview);
  75. $('a.open-list', this).bind('click', onClickOpenLink);
  76. }).addClass('ajax-processed');
  77. break;
  78. }
  79. // trigger refresh block event for enabling lazyload images
  80. setTimeout(function(){
  81. $.event.trigger({
  82. type : 'my'+type+'-block-builded',
  83. block : block,
  84. name : name
  85. });
  86. },10);
  87. // trace('MaterioFlag :: activename', activename);
  88. if(activename == undefined)
  89. activename = readCookie('materiomyflaglistsopened');
  90. // trace('MaterioFlag :: activename', activename);
  91. if(activename)
  92. showPreview(activename, block);
  93. };
  94. function refreshBlocks(name){
  95. //trace('MaterioFlag :: refreshBlocks | name', name);
  96. if($('#block-materio-flag-materio-flag-mybookmarks').length){
  97. var type = 'bookmarks';
  98. }else if($('#block-materio-flag-materio-flag-mylists').length){
  99. var type = 'lists';
  100. }
  101. if(type != undefined){
  102. var id = '#block-materio-flag-materio-flag-my'+type;
  103. var url = Drupal.settings.basePath+Drupal.settings.pathPrefix+'materioflag/refresh/block/'+type;
  104. $.getJSON(url, function(json){
  105. //trace('MaterioFlag :: block refreshed '+type, json);
  106. $(id).replaceWith(json.block);
  107. $('#block-materio-flag-materio-flag-mylists-nav').replaceWith(json.block_nav);
  108. buildBlocks(name);
  109. $.event.trigger({
  110. type : 'my'+type+'-block-updated',
  111. listname : name
  112. });
  113. });
  114. }
  115. };
  116. function ajaxifyLinks(container){
  117. //trace('MaterioFlag :: ajaxifyLinks', container);
  118. container = ((container != null) ? container : 'body');
  119. // trace('MaterioFlag :: typeof Drupal.flagLink', typeof Drupal.flagLink);
  120. if (typeof Drupal.flagLink != 'undefined')
  121. Drupal.flagLink(container);
  122. if(isList()){
  123. var fid = $('.materio-flags-list', '#content').attr('fid');
  124. $('li.unflag-action.fid-'+fid+' a:not(.ajax-processed), li.flag-bookmarks a.unflag-action:not(.ajax-processed)')
  125. .bind('click', onUnflagList)
  126. .addClass('ajax-processed');
  127. }
  128. $('a.flag-lists-create:not(.ajax-processed)', container)
  129. .bind('click', onClickCreatLink)
  130. .addClass('ajax-processed');
  131. };
  132. /**
  133. * show hide preview
  134. */
  135. function onClickShowPreview(event){
  136. //trace('MaterioFlag :: onClickShowPreview', event);
  137. showPreview($(this).attr('name'), $(this).parent('.block').attr('id'));
  138. };
  139. function showPreview(name, block){
  140. //trace('MaterioFlag :: showPreview', name);
  141. $('section.'+name, block).addClass('active')
  142. .siblings('section').removeClass('active');
  143. createCookie('materiomyflaglistsopened', name, 1);
  144. $.event.trigger('init-layout');
  145. };
  146. function onClickClosePreview(event){
  147. //trace('MaterioFlag :: onClickClosePreview', event);
  148. eraseCookie('materiomyflaglistsopened');
  149. if($(this).attr('name') == 'bookmarks'){
  150. $(this).parents('.block').find('section.bookmarks').removeClass('active');
  151. }else{
  152. $(this).parents('section.flag-list').removeClass('active');
  153. }
  154. $.event.trigger('init-layout');
  155. };
  156. /**
  157. * onClickOpenLink
  158. */
  159. function onClickOpenLink(event){
  160. event.preventDefault();
  161. var $link = $(event.currentTarget);
  162. var fid = $link.attr('href').match(/lists\/([0-9]+)$/);
  163. // trace('MaterioFlag :: type', type);
  164. loadList(fid[1]);
  165. return false;
  166. };
  167. function loadList(fid){
  168. //trace('MaterioFlag :: loadList | fid', fid);
  169. var url = Drupal.settings.basePath+Drupal.settings.pathPrefix+'materioflag/ajax/list/'+fid;
  170. $.event.trigger('loading-content');
  171. $.getJSON(url, {'current_path':document.location.href},function(json){
  172. //trace('MaterioFlag :: json', json);
  173. if(json.redirect){
  174. window.location = json.redirect;
  175. }else{
  176. changeContent(json);
  177. }
  178. });
  179. };
  180. function changeContent(json){
  181. if(json.return){
  182. $('.inner-content','#content').html(json.return);
  183. $.event.trigger('loaded-content');
  184. // no need of ajaxifylinks because it's triggered with resultschanged
  185. // ajaxifyLinks('#content');
  186. var path = Drupal.settings.basePath + Drupal.settings.pathPrefix + json.path;
  187. $.event.trigger({
  188. type : 'new-history-page',
  189. path : path,
  190. title : json.title,
  191. content : json.return
  192. });
  193. // TODO: change language links for folders
  194. // for (language in Drupal.settings.materio_search_api_ajax.languages) {
  195. // var l = Drupal.settings.materio_search_api_ajax.languages[language];
  196. // $('#block-locale-language li.'+language+' a').attr('href', Drupal.settings.basePath + l.prefix+'/' + json.search_path + '/' + json.keys)
  197. // };
  198. triggerContentChanged();
  199. }else{
  200. //trace('MaterioFlag :: no results');
  201. }
  202. };
  203. function triggerContentChanged(){
  204. $.event.trigger({
  205. type: 'resultschanged',
  206. container : '#content .flaglist-items'
  207. });
  208. };
  209. /**
  210. * onClickCreatLink(event)
  211. */
  212. function onClickCreatLink(event){
  213. //trace('MaterioFlag :: onClickCreatLink | event', event);
  214. event.preventDefault();
  215. var $link = $(event.currentTarget);
  216. var type = $link.attr('href').match(/[^\/]*$/);
  217. // trace('MaterioFlag :: type', type);
  218. var url = Drupal.settings.basePath+Drupal.settings.pathPrefix+'materioflag/createlist/form/'+type[0];
  219. $.getJSON(url, function(json){
  220. //trace('MaterioFlag :: creat list : json', json);
  221. showCreateListForm(json, $link);
  222. });
  223. return false;
  224. };
  225. function showCreateListForm(json, $link){
  226. //trace('MaterioFlag :: showCreateListForm | json', json);
  227. // google analytics
  228. $.event.trigger({
  229. type:"record-stat",
  230. categorie:"flagLists",
  231. action: 'show create form'
  232. });
  233. var $modal = $('<div id="modal" class="modal"/>').appendTo('body');
  234. $modal
  235. .css({
  236. position:'absolute',
  237. top:'40%', left:'50%',
  238. marginLeft:'-150px', width:'300px',
  239. zIndex:"99999"
  240. })
  241. .append(json.return)
  242. .find('input[type="submit"]', '#materio-flag-create-list-form').bind('click', function(event) {
  243. event.preventDefault();
  244. switch($(this).attr('name')){
  245. case 'cancel':
  246. //trace('MaterioFlag :: cancel',event);
  247. $(this).parents('#modal').remove();
  248. // google analytics
  249. $.event.trigger({
  250. type:"record-stat",
  251. categorie:"flagLists",
  252. action: 'cancel create form'
  253. });
  254. break;
  255. case 'create':
  256. //trace('MaterioFlag :: create',event);
  257. var title = $(this).parents('form').find('input[name*="flag-lists-name"]').val();
  258. var type = $(this).parents('form').find('input[name*="type"]').val();
  259. // google analytics
  260. $.event.trigger({
  261. type : "record-stat",
  262. categorie : "flagLists",
  263. action : "submit create form",
  264. label : 'title : '+title
  265. });
  266. createList($modal, type, title, $link);
  267. break;
  268. }
  269. return false;
  270. })
  271. .parents('form').find('input[type="text"]').focus();
  272. // TODO: esc keypressed close the form
  273. };
  274. function createList($modal, type, title, $link){
  275. //trace('materioflag :: createList | title', title);
  276. $('.flag-lists-create').addClass('loading');
  277. var url = Drupal.settings.basePath+Drupal.settings.pathPrefix+'flag-lists/add/'+type+'/js';
  278. $.getJSON(url, {name:title}, function(data) {
  279. if (data.error) {
  280. //trace(data.error);
  281. }
  282. else {
  283. // select.append('<option value="'+data.flag.fid+'">'+data.flag.title+'</option>');
  284. // $('input.name', $(this)).val('');
  285. // dialog.dialog('close');
  286. //trace('MaterioFlag :: created list : data', data);
  287. if($link.attr('nid') && $link.attr('token')){
  288. flagEntityWithList(data.flag.name, $link.attr('nid'), $link.attr('token'));
  289. }else{
  290. refreshBlocks(data.flag.name);
  291. refreshNodeLinks();
  292. }
  293. $modal.remove();
  294. }
  295. });
  296. };
  297. function flagEntityWithList(name, nid, token){
  298. //trace('MaterioFlag :: flagEntityWithList | name', name);
  299. // var ret;
  300. // Send POST request
  301. $.ajax({
  302. type: 'POST',
  303. url: Drupal.settings.basePath+Drupal.settings.pathPrefix+'flag-lists/flag/'+name+'/'+nid,
  304. data: { js: true, token: token },
  305. dataType: 'json',
  306. success: function (data2) {
  307. //trace('MaterioFlag :: node taged with newly created list : data2', data2)
  308. if (data2.status) {
  309. // google analytics
  310. $.event.trigger({
  311. type : "record-stat",
  312. categorie : 'FlagLists',
  313. action : 'node flaged',
  314. label : 'nid : '+nid+' | flag : '+name
  315. });
  316. refreshBlocks(name);
  317. refreshNodeLinks();
  318. }else {
  319. // Failure.
  320. alert(data2.errorMessage);
  321. }
  322. },
  323. error: function (xmlhttp) {
  324. alert('An HTTP error '+ xmlhttp.status +' occurred.\n'+ element.href);
  325. }
  326. });
  327. };
  328. function refreshNodeLinks(){
  329. //trace('MaterioFlag :: refreshNodeLinks');
  330. var nids = new Array();
  331. $('.flag-lists-entity-links')
  332. // .addClass('loading')
  333. .parents('.node')
  334. .each(function(index) {
  335. nids.push($(this).attr('class').match(/node-([0-9]+)/)[1]);
  336. });
  337. // trace('MaterioFlag :: nids', nids);
  338. var url = Drupal.settings.basePath+Drupal.settings.pathPrefix+'materioflag/nodelinks';
  339. $.getJSON(url, {nids:nids.join(";")}, function(data) {
  340. // trace('MaterioFlag :: data', data);
  341. for(nid in data.links){
  342. // trace('MaterioFlag :: nid', nid);
  343. // trace('MaterioFlag :: data.links[nid]', data.links[nid]);
  344. $('.node-'+nid+' .flag-lists-entity-links').replaceWith(data.links[nid]);
  345. // trace('MaterioFlag :: typeof Drupal.flagLink', typeof Drupal.flagLink);
  346. // if (typeof Drupal.flagLink != 'undefined')
  347. // Drupal.flagLink($('.node-'+nid+' .flag-lists-entity-links'));
  348. // TODO: sortir ajaxifyLinks de la boucle, je pense que ça prend trop de ressources
  349. ajaxifyLinks('.node-'+nid+' .flag-lists-entity-links');
  350. }
  351. });
  352. $.event.trigger({
  353. type : 'materioflag-nodelinks-updated',
  354. nids : nids
  355. });
  356. };
  357. /**
  358. * onCLickEditList(event)
  359. */
  360. function onCLickEditList(event){
  361. //trace('MaterioFlag :: onCLickEditList | event', event);
  362. // TODO: empécher le double formulaire
  363. event.preventDefault();
  364. var $link = $(event.currentTarget);
  365. var lid = $link.attr('href').match(/[^\/]*$/);
  366. var type = 'materiau'; // this is cheap
  367. var url = Drupal.settings.basePath+Drupal.settings.pathPrefix+'materioflag/editlistform/'+type+'/'+lid[0];
  368. $.getJSON(url, function(json){
  369. //trace('MaterioFlag :: editlist : json', json);
  370. showEditListForm(json, $link);
  371. });
  372. return false;
  373. };
  374. function showEditListForm(json, $link){
  375. //trace('MaterioFlag :: showEditListForm | json', json);
  376. // google analytics
  377. $.event.trigger({
  378. type:"record-stat",
  379. categorie:"flagLists",
  380. action: 'show edit form'
  381. });
  382. var $modal = $('<div id="modal" class="modal"/>').appendTo('body');
  383. $modal
  384. .css({
  385. position:'absolute',
  386. top:'40%', left:'50%',
  387. marginLeft:'-150px', width:'300px',
  388. zIndex:"99999"
  389. })
  390. .append(json.return)
  391. .find('input[type="submit"]', '#materio-flag-edit-list-form').bind('click', function(event) {
  392. event.preventDefault();
  393. var $form = $(this).parents('form');
  394. var title = $form.find('input[name*="flag-lists-title"]').val();
  395. var fid = $form.find('input[name*="fid"]').val();
  396. var name = $form.find('input[name*="name"]').val();
  397. switch($(this).attr('name')){
  398. case 'cancel':
  399. //trace('MaterioFlag :: cancel',event);
  400. $(this).parents('#modal').remove();
  401. // google analytics
  402. var action = 'cancel edit form';
  403. break;
  404. case 'save':
  405. //trace('MaterioFlag :: create',event);
  406. // google analytics
  407. var action = "submit edit form";
  408. saveList($modal, fid, name, title);
  409. break;
  410. case 'delete':
  411. //trace('MaterioFlag :: delete',event);
  412. if(confirm('Do you realy want to delete your '+title+' folder ?')){
  413. var action = "submit delete form";
  414. deleteList($modal, fid);
  415. }else{
  416. var action = "cancel delete form";
  417. }
  418. break;
  419. }
  420. // google analytics
  421. $.event.trigger({
  422. type:"record-stat",
  423. categorie:"flagLists",
  424. action: action
  425. });
  426. return false;
  427. })
  428. .parents('form').find('input[type="text"]').focus();
  429. // TODO: esc keypressed close the form
  430. };
  431. function saveList($modal, fid, name, title){
  432. //trace('MaterioFlag :: saveList | fid : '+fid+'| name', name);
  433. $('.flag-lists-link.fid-'+fid).addClass('loading');
  434. var url = Drupal.settings.basePath+Drupal.settings.pathPrefix+'materioflag/editlist/'+fid+'/'+name+'/'+title;
  435. $.getJSON(url, function(data) {
  436. if (data.error) {
  437. // trace(data.error);
  438. if(data.message)
  439. alert(data.message);
  440. }
  441. else {
  442. //trace('MaterioFlag :: saved list : data', data);
  443. $.event.trigger({
  444. type : 'list-edited',
  445. name : data.listname,
  446. title : data.title,
  447. });
  448. refreshBlocks();
  449. refreshNodeLinks();
  450. $modal.remove();
  451. }
  452. });
  453. };
  454. function deleteList($modal, fid){
  455. //trace('MaterioFlag :: deletelist | fid', fid);
  456. $('.flag-lists-link.fid-'+fid).hide();
  457. var url = Drupal.settings.basePath+Drupal.settings.pathPrefix+'materioflag/deletelist/'+fid;
  458. $.getJSON(url, function(data) {
  459. if (data.error) {
  460. // trace(data.error);
  461. if(data.message)
  462. alert(data.message);
  463. }
  464. else {
  465. //trace('MaterioFlag :: deleted list : data', data);
  466. refreshBlocks();
  467. refreshNodeLinks();
  468. // TODO: if the deleted list was the current displayed list ??
  469. $modal.remove();
  470. }
  471. });
  472. };
  473. /**
  474. * onUnflagList()
  475. */
  476. function onUnflagList(event){
  477. //trace('onUnflagList', event);
  478. $(this).parents('article.node').addClass('removed');
  479. };
  480. /**
  481. *
  482. */
  483. function onInitScrollerPager(event){
  484. // trace('MaterioFlag :: MaterioFlag :: onInitScrollerPager');
  485. if (isList()){
  486. // trace('MaterioFlag :: event.pager', event);
  487. event.pager.hide();
  488. }
  489. };
  490. function onLoadScrollerPager(event){
  491. if (isList())
  492. loadNextListPage(event.href);
  493. };
  494. function loadNextListPage(href){
  495. // trace('MaterioFlag :: loadNextListPage', href);
  496. if(!_isLoadingList){
  497. var fid = href.match(/lists\/([^\/|\?]+)/);
  498. var page = href.match(/\?page=([0-9]+)/);
  499. var url = Drupal.settings.basePath+Drupal.settings.pathPrefix+'materioflag/ajax/list/'+fid[1]+'/'+page[1];
  500. // trace('MaterioFlag :: url', url);
  501. loadNextPage(url, $('.materio-flags-list', '#content'), '.flaglist-items');
  502. }
  503. };
  504. function loadNextPage(url, $container, target){
  505. //trace('MaterioFlag :: loadNextPage');
  506. _isLoadingList = true;
  507. $container.addClass('loading');
  508. $.getJSON(url, function(json){
  509. //trace('json', json);
  510. _isLoadingList = false;
  511. $container.removeClass('loading');
  512. addNextpage(json, target);
  513. });
  514. };
  515. function addNextpage(json, container_class){
  516. var $newcontent = $(json.return),
  517. $newitems = $(container_class, $newcontent).children('article').addClass('just-added'),
  518. $newpager = $('ul.pager', $newcontent);
  519. $(container_class, '#content').append($newitems);
  520. $('ul.pager', '#content').replaceWith($newpager.hide());
  521. // TODO: animation, this should be on theme side
  522. $(container_class, '#content').children('.just-added').each(function(i){
  523. // $(this).delay(5000*i).removeClass('just-added');
  524. var $this = $(this);
  525. setTimeout(function(){
  526. $this.removeClass('just-added');
  527. }, 150*i);
  528. });
  529. $.event.trigger({
  530. type : 'resultscompleted',
  531. container : $(container_class, '#content')
  532. });
  533. };
  534. function onViewModeChanged(event){
  535. if (isList())
  536. loadList(getFid());
  537. };
  538. /**
  539. * history
  540. */
  541. function onHistoryStateChange(event){
  542. if(isList())
  543. triggerContentChanged();
  544. };
  545. /**
  546. * Helpers
  547. */
  548. function getFid(){
  549. return $('.materio-flags-list', '#content').attr('fid');;
  550. };
  551. function isList(){
  552. return $('.materio-flags-list', '#content').length;
  553. };
  554. /**
  555. * cookies
  556. */
  557. function createCookie(name,value,days) {
  558. if (days) {
  559. var date = new Date();
  560. date.setTime(date.getTime()+(days*24*60*60*1000));
  561. var expires = "; expires="+date.toGMTString();
  562. }
  563. else var expires = "";
  564. document.cookie = name+"="+value+expires+"; path=/";
  565. }
  566. function readCookie(name) {
  567. var nameEQ = name + "=";
  568. var ca = document.cookie.split(';');
  569. for(var i=0;i < ca.length;i++) {
  570. var c = ca[i];
  571. while (c.charAt(0)==' ') c = c.substring(1,c.length);
  572. if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
  573. }
  574. return null;
  575. }
  576. function eraseCookie(name) {
  577. createCookie(name,"",-1);
  578. }
  579. init();
  580. };
  581. var materioflag = new MaterioFlag();
  582. })(jQuery);