You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

application.js 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  1. /* Redmine - project management software
  2. Copyright (C) 2006-2015 Jean-Philippe Lang */
  3. function checkAll(id, checked) {
  4. $('#'+id).find('input[type=checkbox]:enabled').prop('checked', checked);
  5. }
  6. function toggleCheckboxesBySelector(selector) {
  7. var all_checked = true;
  8. $(selector).each(function(index) {
  9. if (!$(this).is(':checked')) { all_checked = false; }
  10. });
  11. $(selector).prop('checked', !all_checked);
  12. }
  13. function showAndScrollTo(id, focus) {
  14. $('#'+id).show();
  15. if (focus !== null) {
  16. $('#'+focus).focus();
  17. }
  18. $('html, body').animate({scrollTop: $('#'+id).offset().top}, 100);
  19. }
  20. function toggleRowGroup(el) {
  21. var tr = $(el).parents('tr').first();
  22. var n = tr.next();
  23. tr.toggleClass('open');
  24. while (n.length && !n.hasClass('group')) {
  25. n.toggle();
  26. n = n.next('tr');
  27. }
  28. }
  29. function collapseAllRowGroups(el) {
  30. var tbody = $(el).parents('tbody').first();
  31. tbody.children('tr').each(function(index) {
  32. if ($(this).hasClass('group')) {
  33. $(this).removeClass('open');
  34. } else {
  35. $(this).hide();
  36. }
  37. });
  38. }
  39. function expandAllRowGroups(el) {
  40. var tbody = $(el).parents('tbody').first();
  41. tbody.children('tr').each(function(index) {
  42. if ($(this).hasClass('group')) {
  43. $(this).addClass('open');
  44. } else {
  45. $(this).show();
  46. }
  47. });
  48. }
  49. function toggleAllRowGroups(el) {
  50. var tr = $(el).parents('tr').first();
  51. if (tr.hasClass('open')) {
  52. collapseAllRowGroups(el);
  53. } else {
  54. expandAllRowGroups(el);
  55. }
  56. }
  57. function toggleFieldset(el) {
  58. var fieldset = $(el).parents('fieldset').first();
  59. fieldset.toggleClass('collapsed');
  60. fieldset.children('div').toggle();
  61. }
  62. function hideFieldset(el) {
  63. var fieldset = $(el).parents('fieldset').first();
  64. fieldset.toggleClass('collapsed');
  65. fieldset.children('div').hide();
  66. }
  67. // columns selection
  68. function moveOptions(theSelFrom, theSelTo) {
  69. $(theSelFrom).find('option:selected').detach().prop("selected", false).appendTo($(theSelTo));
  70. }
  71. function moveOptionUp(theSel) {
  72. $(theSel).find('option:selected').each(function(){
  73. $(this).prev(':not(:selected)').detach().insertAfter($(this));
  74. });
  75. }
  76. function moveOptionTop(theSel) {
  77. $(theSel).find('option:selected').detach().prependTo($(theSel));
  78. }
  79. function moveOptionDown(theSel) {
  80. $($(theSel).find('option:selected').get().reverse()).each(function(){
  81. $(this).next(':not(:selected)').detach().insertBefore($(this));
  82. });
  83. }
  84. function moveOptionBottom(theSel) {
  85. $(theSel).find('option:selected').detach().appendTo($(theSel));
  86. }
  87. function initFilters() {
  88. $('#add_filter_select').change(function() {
  89. addFilter($(this).val(), '', []);
  90. });
  91. $('#filters-table td.field input[type=checkbox]').each(function() {
  92. toggleFilter($(this).val());
  93. });
  94. $('#filters-table').on('click', 'td.field input[type=checkbox]', function() {
  95. toggleFilter($(this).val());
  96. });
  97. $('#filters-table').on('click', '.toggle-multiselect', function() {
  98. toggleMultiSelect($(this).siblings('select'));
  99. });
  100. $('#filters-table').on('keypress', 'input[type=text]', function(e) {
  101. if (e.keyCode == 13) $(this).closest('form').submit();
  102. });
  103. }
  104. function addFilter(field, operator, values) {
  105. var fieldId = field.replace('.', '_');
  106. var tr = $('#tr_'+fieldId);
  107. if (tr.length > 0) {
  108. tr.show();
  109. } else {
  110. buildFilterRow(field, operator, values);
  111. }
  112. $('#cb_'+fieldId).prop('checked', true);
  113. toggleFilter(field);
  114. $('#add_filter_select').val('').find('option').each(function() {
  115. if ($(this).attr('value') == field) {
  116. $(this).attr('disabled', true);
  117. }
  118. });
  119. }
  120. function buildFilterRow(field, operator, values) {
  121. var fieldId = field.replace('.', '_');
  122. var filterTable = $("#filters-table");
  123. var filterOptions = availableFilters[field];
  124. if (!filterOptions) return;
  125. var operators = operatorByType[filterOptions['type']];
  126. var filterValues = filterOptions['values'];
  127. var i, select;
  128. var tr = $('<tr class="filter">').attr('id', 'tr_'+fieldId).html(
  129. '<td class="field"><input checked="checked" id="cb_'+fieldId+'" name="f[]" value="'+field+'" type="checkbox"><label for="cb_'+fieldId+'"> '+filterOptions['name']+'</label></td>' +
  130. '<td class="operator"><select id="operators_'+fieldId+'" name="op['+field+']"></td>' +
  131. '<td class="values"></td>'
  132. );
  133. filterTable.append(tr);
  134. select = tr.find('td.operator select');
  135. for (i = 0; i < operators.length; i++) {
  136. var option = $('<option>').val(operators[i]).text(operatorLabels[operators[i]]);
  137. if (operators[i] == operator) { option.attr('selected', true); }
  138. select.append(option);
  139. }
  140. select.change(function(){ toggleOperator(field); });
  141. switch (filterOptions['type']) {
  142. case "list":
  143. case "list_optional":
  144. case "list_status":
  145. case "list_subprojects":
  146. tr.find('td.values').append(
  147. '<span style="display:none;"><select class="value" id="values_'+fieldId+'_1" name="v['+field+'][]"></select>' +
  148. ' <span class="toggle-multiselect">&nbsp;</span></span>'
  149. );
  150. select = tr.find('td.values select');
  151. if (values.length > 1) { select.attr('multiple', true); }
  152. for (i = 0; i < filterValues.length; i++) {
  153. var filterValue = filterValues[i];
  154. var option = $('<option>');
  155. if ($.isArray(filterValue)) {
  156. option.val(filterValue[1]).text(filterValue[0]);
  157. if ($.inArray(filterValue[1], values) > -1) {option.attr('selected', true);}
  158. } else {
  159. option.val(filterValue).text(filterValue);
  160. if ($.inArray(filterValue, values) > -1) {option.attr('selected', true);}
  161. }
  162. select.append(option);
  163. }
  164. break;
  165. case "date":
  166. case "date_past":
  167. tr.find('td.values').append(
  168. '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_1" size="10" class="value date_value" /></span>' +
  169. ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_2" size="10" class="value date_value" /></span>' +
  170. ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="3" class="value" /> '+labelDayPlural+'</span>'
  171. );
  172. $('#values_'+fieldId+'_1').val(values[0]).datepicker(datepickerOptions);
  173. $('#values_'+fieldId+'_2').val(values[1]).datepicker(datepickerOptions);
  174. $('#values_'+fieldId).val(values[0]);
  175. break;
  176. case "string":
  177. case "text":
  178. tr.find('td.values').append(
  179. '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="30" class="value" /></span>'
  180. );
  181. $('#values_'+fieldId).val(values[0]);
  182. break;
  183. case "relation":
  184. tr.find('td.values').append(
  185. '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="6" class="value" /></span>' +
  186. '<span style="display:none;"><select class="value" name="v['+field+'][]" id="values_'+fieldId+'_1"></select></span>'
  187. );
  188. $('#values_'+fieldId).val(values[0]);
  189. select = tr.find('td.values select');
  190. for (i = 0; i < allProjects.length; i++) {
  191. var filterValue = allProjects[i];
  192. var option = $('<option>');
  193. option.val(filterValue[1]).text(filterValue[0]);
  194. if (values[0] == filterValue[1]) { option.attr('selected', true); }
  195. select.append(option);
  196. }
  197. break;
  198. case "integer":
  199. case "float":
  200. case "tree":
  201. tr.find('td.values').append(
  202. '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_1" size="6" class="value" /></span>' +
  203. ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_2" size="6" class="value" /></span>'
  204. );
  205. $('#values_'+fieldId+'_1').val(values[0]);
  206. $('#values_'+fieldId+'_2').val(values[1]);
  207. break;
  208. }
  209. }
  210. function toggleFilter(field) {
  211. var fieldId = field.replace('.', '_');
  212. if ($('#cb_' + fieldId).is(':checked')) {
  213. $("#operators_" + fieldId).show().removeAttr('disabled');
  214. toggleOperator(field);
  215. } else {
  216. $("#operators_" + fieldId).hide().attr('disabled', true);
  217. enableValues(field, []);
  218. }
  219. }
  220. function enableValues(field, indexes) {
  221. var fieldId = field.replace('.', '_');
  222. $('#tr_'+fieldId+' td.values .value').each(function(index) {
  223. if ($.inArray(index, indexes) >= 0) {
  224. $(this).removeAttr('disabled');
  225. $(this).parents('span').first().show();
  226. } else {
  227. $(this).val('');
  228. $(this).attr('disabled', true);
  229. $(this).parents('span').first().hide();
  230. }
  231. if ($(this).hasClass('group')) {
  232. $(this).addClass('open');
  233. } else {
  234. $(this).show();
  235. }
  236. });
  237. }
  238. function toggleOperator(field) {
  239. var fieldId = field.replace('.', '_');
  240. var operator = $("#operators_" + fieldId);
  241. switch (operator.val()) {
  242. case "!*":
  243. case "*":
  244. case "t":
  245. case "ld":
  246. case "w":
  247. case "lw":
  248. case "l2w":
  249. case "m":
  250. case "lm":
  251. case "y":
  252. case "o":
  253. case "c":
  254. enableValues(field, []);
  255. break;
  256. case "><":
  257. enableValues(field, [0,1]);
  258. break;
  259. case "<t+":
  260. case ">t+":
  261. case "><t+":
  262. case "t+":
  263. case ">t-":
  264. case "<t-":
  265. case "><t-":
  266. case "t-":
  267. enableValues(field, [2]);
  268. break;
  269. case "=p":
  270. case "=!p":
  271. case "!p":
  272. enableValues(field, [1]);
  273. break;
  274. default:
  275. enableValues(field, [0]);
  276. break;
  277. }
  278. }
  279. function toggleMultiSelect(el) {
  280. if (el.attr('multiple')) {
  281. el.removeAttr('multiple');
  282. el.attr('size', 1);
  283. } else {
  284. el.attr('multiple', true);
  285. if (el.children().length > 10)
  286. el.attr('size', 10);
  287. else
  288. el.attr('size', 4);
  289. }
  290. }
  291. function showTab(name, url) {
  292. $('div#content .tab-content').hide();
  293. $('div.tabs a').removeClass('selected');
  294. $('#tab-content-' + name).show();
  295. $('#tab-' + name).addClass('selected');
  296. //replaces current URL with the "href" attribute of the current link
  297. //(only triggered if supported by browser)
  298. if ("replaceState" in window.history) {
  299. window.history.replaceState(null, document.title, url);
  300. }
  301. return false;
  302. }
  303. function moveTabRight(el) {
  304. var lis = $(el).parents('div.tabs').first().find('ul').children();
  305. var tabsWidth = 0;
  306. var i = 0;
  307. lis.each(function() {
  308. if ($(this).is(':visible')) {
  309. tabsWidth += $(this).width() + 6;
  310. }
  311. });
  312. if (tabsWidth < $(el).parents('div.tabs').first().width() - 60) { return; }
  313. while (i<lis.length && !lis.eq(i).is(':visible')) { i++; }
  314. lis.eq(i).hide();
  315. }
  316. function moveTabLeft(el) {
  317. var lis = $(el).parents('div.tabs').first().find('ul').children();
  318. var i = 0;
  319. while (i < lis.length && !lis.eq(i).is(':visible')) { i++; }
  320. if (i > 0) {
  321. lis.eq(i-1).show();
  322. }
  323. }
  324. function displayTabsButtons() {
  325. var lis;
  326. var tabsWidth;
  327. var el;
  328. $('div.tabs').each(function() {
  329. el = $(this);
  330. lis = el.find('ul').children();
  331. tabsWidth = 0;
  332. lis.each(function(){
  333. if ($(this).is(':visible')) {
  334. tabsWidth += $(this).width() + 6;
  335. }
  336. });
  337. if ((tabsWidth < el.width() - 60) && (lis.first().is(':visible'))) {
  338. el.find('div.tabs-buttons').hide();
  339. } else {
  340. el.find('div.tabs-buttons').show();
  341. }
  342. });
  343. }
  344. function setPredecessorFieldsVisibility() {
  345. var relationType = $('#relation_relation_type');
  346. if (relationType.val() == "precedes" || relationType.val() == "follows") {
  347. $('#predecessor_fields').show();
  348. } else {
  349. $('#predecessor_fields').hide();
  350. }
  351. }
  352. function showModal(id, width, title) {
  353. var el = $('#'+id).first();
  354. if (el.length === 0 || el.is(':visible')) {return;}
  355. if (!title) title = el.find('h3.title').text();
  356. // moves existing modals behind the transparent background
  357. $(".modal").zIndex(99);
  358. el.dialog({
  359. width: width,
  360. modal: true,
  361. resizable: false,
  362. dialogClass: 'modal',
  363. title: title
  364. }).on('dialogclose', function(){
  365. $(".modal").zIndex(101);
  366. });
  367. el.find("input[type=text], input[type=submit]").first().focus();
  368. }
  369. function hideModal(el) {
  370. var modal;
  371. if (el) {
  372. modal = $(el).parents('.ui-dialog-content');
  373. } else {
  374. modal = $('#ajax-modal');
  375. }
  376. modal.dialog("close");
  377. }
  378. function submitPreview(url, form, target) {
  379. $.ajax({
  380. url: url,
  381. type: 'post',
  382. data: $('#'+form).serialize(),
  383. success: function(data){
  384. $('#'+target).html(data);
  385. }
  386. });
  387. }
  388. function collapseScmEntry(id) {
  389. $('.'+id).each(function() {
  390. if ($(this).hasClass('open')) {
  391. collapseScmEntry($(this).attr('id'));
  392. }
  393. $(this).hide();
  394. });
  395. $('#'+id).removeClass('open');
  396. }
  397. function expandScmEntry(id) {
  398. $('.'+id).each(function() {
  399. $(this).show();
  400. if ($(this).hasClass('loaded') && !$(this).hasClass('collapsed')) {
  401. expandScmEntry($(this).attr('id'));
  402. }
  403. });
  404. $('#'+id).addClass('open');
  405. }
  406. function scmEntryClick(id, url) {
  407. var el = $('#'+id);
  408. if (el.hasClass('open')) {
  409. collapseScmEntry(id);
  410. el.addClass('collapsed');
  411. return false;
  412. } else if (el.hasClass('loaded')) {
  413. expandScmEntry(id);
  414. el.removeClass('collapsed');
  415. return false;
  416. }
  417. if (el.hasClass('loading')) {
  418. return false;
  419. }
  420. el.addClass('loading');
  421. $.ajax({
  422. url: url,
  423. success: function(data) {
  424. el.after(data);
  425. el.addClass('open').addClass('loaded').removeClass('loading');
  426. }
  427. });
  428. return true;
  429. }
  430. function randomKey(size) {
  431. var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
  432. var key = '';
  433. for (var i = 0; i < size; i++) {
  434. key += chars.charAt(Math.floor(Math.random() * chars.length));
  435. }
  436. return key;
  437. }
  438. function updateIssueFrom(url) {
  439. $('#all_attributes input, #all_attributes textarea, #all_attributes select').each(function(){
  440. $(this).data('valuebeforeupdate', $(this).val());
  441. });
  442. return $.ajax({
  443. url: url,
  444. type: 'post',
  445. data: $('#issue-form').serialize()
  446. });
  447. }
  448. function replaceIssueFormWith(html){
  449. var replacement = $(html);
  450. $('#all_attributes input, #all_attributes textarea, #all_attributes select').each(function(){
  451. var object_id = $(this).attr('id');
  452. if (object_id && $(this).data('valuebeforeupdate')!=$(this).val()) {
  453. replacement.find('#'+object_id).val($(this).val());
  454. }
  455. });
  456. $('#all_attributes').empty();
  457. $('#all_attributes').prepend(replacement);
  458. }
  459. function updateBulkEditFrom(url) {
  460. $.ajax({
  461. url: url,
  462. type: 'post',
  463. data: $('#bulk_edit_form').serialize()
  464. });
  465. }
  466. function observeAutocompleteField(fieldId, url, options) {
  467. $(document).ready(function() {
  468. $('#'+fieldId).autocomplete($.extend({
  469. source: url,
  470. minLength: 2,
  471. search: function(){$('#'+fieldId).addClass('ajax-loading');},
  472. response: function(){$('#'+fieldId).removeClass('ajax-loading');}
  473. }, options));
  474. $('#'+fieldId).addClass('autocomplete');
  475. });
  476. }
  477. function observeSearchfield(fieldId, targetId, url) {
  478. $('#'+fieldId).each(function() {
  479. var $this = $(this);
  480. $this.addClass('autocomplete');
  481. $this.attr('data-value-was', $this.val());
  482. var check = function() {
  483. var val = $this.val();
  484. if ($this.attr('data-value-was') != val){
  485. $this.attr('data-value-was', val);
  486. $.ajax({
  487. url: url,
  488. type: 'get',
  489. data: {q: $this.val()},
  490. success: function(data){ if(targetId) $('#'+targetId).html(data); },
  491. beforeSend: function(){ $this.addClass('ajax-loading'); },
  492. complete: function(){ $this.removeClass('ajax-loading'); }
  493. });
  494. }
  495. };
  496. var reset = function() {
  497. if (timer) {
  498. clearInterval(timer);
  499. timer = setInterval(check, 300);
  500. }
  501. };
  502. var timer = setInterval(check, 300);
  503. $this.bind('keyup click mousemove', reset);
  504. });
  505. }
  506. function beforeShowDatePicker(input, inst) {
  507. var default_date = null;
  508. switch ($(input).attr("id")) {
  509. case "issue_start_date" :
  510. if ($("#issue_due_date").size() > 0) {
  511. default_date = $("#issue_due_date").val();
  512. }
  513. break;
  514. case "issue_due_date" :
  515. if ($("#issue_start_date").size() > 0) {
  516. default_date = $("#issue_start_date").val();
  517. }
  518. break;
  519. }
  520. $(input).datepicker("option", "defaultDate", default_date);
  521. }
  522. function initMyPageSortable(list, url) {
  523. $('#list-'+list).sortable({
  524. connectWith: '.block-receiver',
  525. tolerance: 'pointer',
  526. update: function(){
  527. $.ajax({
  528. url: url,
  529. type: 'post',
  530. data: {'blocks': $.map($('#list-'+list).children(), function(el){return $(el).attr('id');})}
  531. });
  532. }
  533. });
  534. $("#list-top, #list-left, #list-right").disableSelection();
  535. }
  536. var warnLeavingUnsavedMessage;
  537. function warnLeavingUnsaved(message) {
  538. warnLeavingUnsavedMessage = message;
  539. $(document).on('submit', 'form', function(){
  540. $('textarea').removeData('changed');
  541. });
  542. $(document).on('change', 'textarea', function(){
  543. $(this).data('changed', 'changed');
  544. });
  545. window.onbeforeunload = function(){
  546. var warn = false;
  547. $('textarea').blur().each(function(){
  548. if ($(this).data('changed')) {
  549. warn = true;
  550. }
  551. });
  552. if (warn) {return warnLeavingUnsavedMessage;}
  553. };
  554. }
  555. function setupAjaxIndicator() {
  556. $(document).bind('ajaxSend', function(event, xhr, settings) {
  557. if ($('.ajax-loading').length === 0 && settings.contentType != 'application/octet-stream') {
  558. $('#ajax-indicator').show();
  559. }
  560. });
  561. $(document).bind('ajaxStop', function() {
  562. $('#ajax-indicator').hide();
  563. });
  564. }
  565. function hideOnLoad() {
  566. $('.hol').hide();
  567. }
  568. function addFormObserversForDoubleSubmit() {
  569. $('form[method=post]').each(function() {
  570. if (!$(this).hasClass('multiple-submit')) {
  571. $(this).submit(function(form_submission) {
  572. if ($(form_submission.target).attr('data-submitted')) {
  573. form_submission.preventDefault();
  574. } else {
  575. $(form_submission.target).attr('data-submitted', true);
  576. }
  577. });
  578. }
  579. });
  580. }
  581. function defaultFocus(){
  582. if (($('#content :focus').length == 0) && (window.location.hash == '')) {
  583. $('#content input[type=text], #content textarea').first().focus();
  584. }
  585. }
  586. function blockEventPropagation(event) {
  587. event.stopPropagation();
  588. event.preventDefault();
  589. }
  590. function toggleDisabledOnChange() {
  591. var checked = $(this).is(':checked');
  592. $($(this).data('disables')).attr('disabled', checked);
  593. $($(this).data('enables')).attr('disabled', !checked);
  594. }
  595. function toggleDisabledInit() {
  596. $('input[data-disables], input[data-enables]').each(toggleDisabledOnChange);
  597. }
  598. $(document).ready(function(){
  599. $('#content').on('change', 'input[data-disables], input[data-enables]', toggleDisabledOnChange);
  600. toggleDisabledInit();
  601. });
  602. $(document).ready(setupAjaxIndicator);
  603. $(document).ready(hideOnLoad);
  604. $(document).ready(addFormObserversForDoubleSubmit);
  605. $(document).ready(defaultFocus);