Admin.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  1. /*
  2. This file is part of the Sonata package.
  3. (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
  4. For the full copyright and license information, please view the LICENSE
  5. file that was distributed with this source code.
  6. */
  7. jQuery(document).ready(function() {
  8. jQuery('html').removeClass('no-js');
  9. if (window.SONATA_CONFIG && window.SONATA_CONFIG.CONFIRM_EXIT) {
  10. jQuery('.sonata-ba-form form').each(function () { jQuery(this).confirmExit(); });
  11. }
  12. Admin.setup_per_page_switcher(document);
  13. Admin.shared_setup(document);
  14. });
  15. jQuery(document).on('sonata-admin-append-form-element', function(e) {
  16. Admin.setup_select2(e.target);
  17. Admin.setup_icheck(e.target);
  18. });
  19. var Admin = {
  20. /**
  21. * This function must called when a ajax call is done, to ensure
  22. * retrieve html is properly setup
  23. *
  24. * @param subject
  25. */
  26. shared_setup: function(subject) {
  27. Admin.log("[core|shared_setup] Register services on", subject);
  28. Admin.setup_collection_buttons(subject);
  29. Admin.set_object_field_value(subject);
  30. Admin.setup_select2(subject);
  31. Admin.setup_icheck(subject);
  32. Admin.add_filters(subject);
  33. Admin.setup_xeditable(subject);
  34. Admin.add_pretty_errors(subject);
  35. Admin.setup_form_tabs_for_errors(subject);
  36. Admin.setup_inline_form_errors(subject);
  37. Admin.setup_tree_view(subject);
  38. // Admin.setup_list_modal(subject);
  39. },
  40. setup_list_modal: function(modal) {
  41. Admin.log('[core|setup_list_modal] configure modal on', modal);
  42. // this will force relation modal to open list of entity in a wider modal
  43. // to improve readability
  44. jQuery('div.modal-dialog', modal).css({
  45. width: '90%', //choose your width
  46. height: '85%',
  47. padding: 0
  48. });
  49. jQuery('div.modal-content', modal).css({
  50. 'border-radius':'0',
  51. height: '100%',
  52. padding: 0
  53. });
  54. jQuery('.modal-body', modal).css({
  55. width: 'auto',
  56. height: '90%',
  57. padding: 5,
  58. overflow: 'scroll'
  59. });
  60. },
  61. setup_select2: function(subject) {
  62. if (window.SONATA_CONFIG && window.SONATA_CONFIG.USE_SELECT2 && window.Select2) {
  63. Admin.log('[core|setup_select2] configure Select2 on', subject);
  64. jQuery('select:not([data-sonata-select2="false"])', subject).each(function() {
  65. var select = jQuery(this);
  66. var allowClearEnabled = false;
  67. var popover = select.data('popover');
  68. select.removeClass('form-control');
  69. if (select.find('option[value=""]').length || select.attr('data-sonata-select2-allow-clear')==='true') {
  70. allowClearEnabled = true;
  71. } else if (select.attr('data-sonata-select2-allow-clear')==='false') {
  72. allowClearEnabled = false;
  73. }
  74. select.select2({
  75. width: function(){
  76. return Admin.get_select2_width(this.element);
  77. },
  78. dropdownAutoWidth: true,
  79. minimumResultsForSearch: 10,
  80. allowClear: allowClearEnabled
  81. });
  82. if (undefined !== popover) {
  83. select
  84. .select2('container')
  85. .popover(popover.options)
  86. ;
  87. }
  88. });
  89. }
  90. },
  91. setup_icheck: function(subject) {
  92. if (window.SONATA_CONFIG && window.SONATA_CONFIG.USE_ICHECK) {
  93. Admin.log('[core|setup_icheck] configure iCheck on', subject);
  94. jQuery("input[type='checkbox']:not('label.btn>input'), input[type='radio']:not('label.btn>input')", subject).iCheck({
  95. checkboxClass: 'icheckbox_flat-blue',
  96. radioClass: 'iradio_flat-blue'
  97. });
  98. }
  99. },
  100. setup_xeditable: function(subject) {
  101. Admin.log('[core|setup_xeditable] configure xeditable on', subject);
  102. jQuery('.x-editable', subject).editable({
  103. emptyclass: 'editable-empty btn btn-sm btn-default',
  104. emptytext: '<i class="glyphicon glyphicon-edit"></i>',
  105. container: 'body',
  106. placement: 'auto',
  107. success: function(response) {
  108. if('KO' === response.status) {
  109. return response.message;
  110. }
  111. var html = jQuery(response.content);
  112. Admin.setup_xeditable(html);
  113. jQuery(this)
  114. .closest('td')
  115. .replaceWith(html)
  116. ;
  117. }
  118. });
  119. },
  120. /**
  121. * render log message
  122. * @param mixed
  123. */
  124. log: function() {
  125. var msg = '[Sonata.Admin] ' + Array.prototype.join.call(arguments,', ');
  126. if (window.console && window.console.log) {
  127. window.console.log(msg);
  128. } else if (window.opera && window.opera.postError) {
  129. window.opera.postError(msg);
  130. }
  131. },
  132. /**
  133. * display related errors messages
  134. *
  135. * @param subject
  136. */
  137. add_pretty_errors: function(subject) {
  138. Admin.log('[core|add_pretty_errors] configure pretty errors on', subject);
  139. jQuery('div.sonata-ba-field-error', subject).each(function(index, element) {
  140. var input = jQuery(':input', element);
  141. if (!input.length) {
  142. return;
  143. }
  144. var message = jQuery('div.sonata-ba-field-error-messages', element).html();
  145. jQuery('div.sonata-ba-field-error-messages', element).remove();
  146. if (!message || message.length == 0) {
  147. return;
  148. }
  149. var target = input,
  150. fieldShortDescription = input.closest('.field-container').find('.field-short-description'),
  151. select2 = input.closest('.select2-container')
  152. ;
  153. if (fieldShortDescription.length) {
  154. target = fieldShortDescription;
  155. } else if (select2.length) {
  156. target= select2;
  157. }
  158. target.popover({
  159. content: message,
  160. trigger: 'hover',
  161. html: true,
  162. placement: 'top',
  163. template: '<div class="popover"><div class="arrow"></div><div class="popover-inner"><div class="popover-content alert-error"><p></p></div></div></div>'
  164. });
  165. });
  166. },
  167. stopEvent: function(event) {
  168. // https://github.com/sonata-project/SonataAdminBundle/issues/151
  169. //if it is a standard browser use preventDefault otherwise it is IE then return false
  170. if(event.preventDefault) {
  171. event.preventDefault();
  172. } else {
  173. event.returnValue = false;
  174. }
  175. //if it is a standard browser get target otherwise it is IE then adapt syntax and get target
  176. if (typeof event.target != 'undefined') {
  177. targetElement = event.target;
  178. } else {
  179. targetElement = event.srcElement;
  180. }
  181. return targetElement;
  182. },
  183. add_filters: function(subject) {
  184. Admin.log('[core|add_filters] configure filters on', subject);
  185. jQuery('a.sonata-toggle-filter', subject).on('click', function(e) {
  186. e.preventDefault();
  187. e.stopPropagation();
  188. if (jQuery(e.target).attr('sonata-filter') == 'false') {
  189. return;
  190. }
  191. Admin.log('[core|add_filters] handle filter container: ', jQuery(e.target).attr('filter-container'))
  192. var filters_container = jQuery('#' + jQuery(e.currentTarget).attr('filter-container'));
  193. if (jQuery('div[sonata-filter="true"]:visible', filters_container).length == 0) {
  194. jQuery(filters_container).slideDown();
  195. }
  196. var targetSelector = jQuery(e.currentTarget).attr('filter-target'),
  197. target = jQuery('div[id="' + targetSelector + '"]', filters_container),
  198. filterToggler = jQuery('i', '.sonata-toggle-filter[filter-target="' + targetSelector + '"]')
  199. ;
  200. if (jQuery(target).is(":visible")) {
  201. filterToggler
  202. .removeClass('fa-check-square-o')
  203. .addClass('fa-square-o')
  204. ;
  205. target.hide();
  206. } else {
  207. filterToggler
  208. .removeClass('fa-square-o')
  209. .addClass('fa-check-square-o')
  210. ;
  211. target.show();
  212. }
  213. if (jQuery('div[sonata-filter="true"]:visible', filters_container).length > 0) {
  214. jQuery(filters_container).slideDown();
  215. } else {
  216. jQuery(filters_container).slideUp();
  217. }
  218. });
  219. jQuery('.sonata-filter-form', subject).on('submit', function () {
  220. jQuery(this).find('[sonata-filter="true"]:hidden :input').val('');
  221. });
  222. /* Advanced filters */
  223. if (jQuery('.advanced-filter :input:visible', subject).filter(function () { return jQuery(this).val() }).length === 0) {
  224. jQuery('.advanced-filter').hide();
  225. };
  226. jQuery('[data-toggle="advanced-filter"]', subject).click(function() {
  227. jQuery('.advanced-filter').toggle();
  228. });
  229. },
  230. /**
  231. * Change object field value
  232. * @param subject
  233. */
  234. set_object_field_value: function(subject) {
  235. Admin.log('[core|set_object_field_value] set value field on', subject);
  236. this.log(jQuery('a.sonata-ba-edit-inline', subject));
  237. jQuery('a.sonata-ba-edit-inline', subject).click(function(event) {
  238. Admin.stopEvent(event);
  239. var subject = jQuery(this);
  240. jQuery.ajax({
  241. url: subject.attr('href'),
  242. type: 'POST',
  243. success: function(json) {
  244. if(json.status === "OK") {
  245. var elm = jQuery(subject).parent();
  246. elm.children().remove();
  247. // fix issue with html comment ...
  248. elm.html(jQuery(json.content.replace(/<!--[\s\S]*?-->/g, "")).html());
  249. elm.effect("highlight", {'color' : '#57A957'}, 2000);
  250. Admin.set_object_field_value(elm);
  251. } else {
  252. jQuery(subject).parent().effect("highlight", {'color' : '#C43C35'}, 2000);
  253. }
  254. }
  255. });
  256. });
  257. },
  258. setup_collection_buttons: function(subject) {
  259. Admin.log('[core|setup_collection_buttons] setup collection buttons', subject);
  260. var counters = [];
  261. // Count and save element of each collection
  262. var highestCounterRegexp = new RegExp('_([0-9])+$');
  263. jQuery(subject).find('[data-prototype]').each(function() {
  264. var collection = jQuery(this);
  265. var counter = 0;
  266. collection.children().each(function() {
  267. var matches = highestCounterRegexp.exec(jQuery('[id^="sonata-ba-field-container"]', this).attr('id'));
  268. if (matches && matches[1] && matches[1] > counter) {
  269. counter = parseInt(matches[1], 10);
  270. }
  271. });
  272. counters[collection.attr('id')] = counter;
  273. });
  274. jQuery(subject).on('click', '.sonata-collection-add', function(event) {
  275. Admin.stopEvent(event);
  276. var container = jQuery(this).closest('[data-prototype]');
  277. var counter = ++counters[container.attr('id')];
  278. var proto = container.attr('data-prototype');
  279. var protoName = container.attr('data-prototype-name') || '__name__';
  280. // Set field id
  281. var idRegexp = new RegExp(container.attr('id')+'_'+protoName,'g');
  282. proto = proto.replace(idRegexp, container.attr('id')+'_'+counter);
  283. // Set field name
  284. var parts = container.attr('id').split('_');
  285. var nameRegexp = new RegExp(parts[parts.length-1]+'\\]\\['+protoName,'g');
  286. proto = proto.replace(nameRegexp, parts[parts.length-1]+']['+counter);
  287. jQuery(proto)
  288. .insertBefore(jQuery(this).parent())
  289. .trigger('sonata-admin-append-form-element')
  290. ;
  291. jQuery(this).trigger('sonata-collection-item-added');
  292. });
  293. jQuery(subject).on('click', '.sonata-collection-delete', function(event) {
  294. Admin.stopEvent(event);
  295. jQuery(this).trigger('sonata-collection-item-deleted');
  296. jQuery(this).closest('.sonata-collection-row').remove();
  297. });
  298. },
  299. setup_per_page_switcher: function(subject) {
  300. Admin.log('[core|setup_per_page_switcher] setup page switcher', subject);
  301. jQuery('select.per-page').change(function(event) {
  302. jQuery('input[type=submit]').hide();
  303. window.top.location.href=this.options[this.selectedIndex].value;
  304. });
  305. },
  306. setup_form_tabs_for_errors: function(subject) {
  307. Admin.log('[core|setup_form_tabs_for_errors] setup form tab\'s errors', subject);
  308. // Switch to first tab with server side validation errors on page load
  309. jQuery('form', subject).each(function() {
  310. Admin.show_form_first_tab_with_errors(jQuery(this), '.sonata-ba-field-error');
  311. });
  312. // Switch to first tab with HTML5 errors on form submit
  313. jQuery(subject)
  314. .on('click', 'form [type="submit"]', function() {
  315. Admin.show_form_first_tab_with_errors(jQuery(this).closest('form'), ':invalid');
  316. })
  317. .on('keypress', 'form [type="text"]', function(e) {
  318. if (13 === e.which) {
  319. Admin.show_form_first_tab_with_errors(jQuery(this), ':invalid');
  320. }
  321. })
  322. ;
  323. },
  324. show_form_first_tab_with_errors: function(form, errorSelector) {
  325. Admin.log('[core|show_form_first_tab_with_errors] show first tab with errors', form);
  326. var tabs = form.find('.nav-tabs a'), firstTabWithErrors;
  327. tabs.each(function() {
  328. var id = jQuery(this).attr('href'),
  329. tab = jQuery(this),
  330. icon = tab.find('.has-errors');
  331. if (jQuery(id).find(errorSelector).length > 0) {
  332. // Only show first tab with errors
  333. if (!firstTabWithErrors) {
  334. tab.tab('show');
  335. firstTabWithErrors = tab;
  336. }
  337. icon.removeClass('hide');
  338. } else {
  339. icon.addClass('hide');
  340. }
  341. });
  342. },
  343. setup_inline_form_errors: function(subject) {
  344. Admin.log('[core|setup_inline_form_errors] show first tab with errors', subject);
  345. var deleteCheckboxSelector = '.sonata-ba-field-inline-table [id$="_delete"][type="checkbox"]';
  346. jQuery(deleteCheckboxSelector, subject).each(function() {
  347. Admin.switch_inline_form_errors(jQuery(this));
  348. });
  349. jQuery(subject).on('change', deleteCheckboxSelector, function() {
  350. Admin.switch_inline_form_errors(jQuery(this));
  351. });
  352. },
  353. /**
  354. * Disable inline form errors when the row is marked for deletion
  355. */
  356. switch_inline_form_errors: function(subject) {
  357. Admin.log('[core|switch_inline_form_errors] switch_inline_form_errors', subject);
  358. var row = subject.closest('.sonata-ba-field-inline-table'),
  359. errors = row.find('.sonata-ba-field-error-messages')
  360. ;
  361. if (subject.is(':checked')) {
  362. row
  363. .find('[required]')
  364. .removeAttr('required')
  365. .attr('data-required', 'required')
  366. ;
  367. errors.hide();
  368. } else {
  369. row
  370. .find('[data-required]')
  371. .attr('required', 'required')
  372. ;
  373. errors.show();
  374. }
  375. },
  376. setup_tree_view: function(subject) {
  377. Admin.log('[core|setup_tree_view] setup tree view', subject);
  378. jQuery('ul.js-treeview', subject).treeView();
  379. },
  380. /** Return the width for simple and sortable select2 element **/
  381. get_select2_width: function(element){
  382. var ereg = /width:(auto|(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc)))/i;
  383. // this code is an adaptation of select2 code (initContainerWidth function)
  384. var style = element.attr('style');
  385. //console.log("main style", style);
  386. if (style !== undefined) {
  387. var attrs = style.split(';');
  388. for (i = 0, l = attrs.length; i < l; i = i + 1) {
  389. var matches = attrs[i].replace(/\s/g, '').match(ereg);
  390. if (matches !== null && matches.length >= 1)
  391. return matches[1];
  392. }
  393. }
  394. style = element.css('width');
  395. if (style.indexOf("%") > 0) {
  396. return style;
  397. }
  398. return '100%';
  399. },
  400. setup_sortable_select2: function(subject, data) {
  401. var transformedData = [];
  402. for (var i = 0 ; i < data.length ; i++) {
  403. transformedData[i] = {id: data[i].data, text: data[i].label};
  404. }
  405. subject.select2({
  406. width: function(){
  407. return Admin.get_select2_width(this.element);
  408. },
  409. dropdownAutoWidth: true,
  410. data: transformedData,
  411. multiple: true
  412. });
  413. subject.select2("container").find("ul.select2-choices").sortable({
  414. containment: 'parent',
  415. start: function () {
  416. subject.select2("onSortStart");
  417. },
  418. update: function () {
  419. subject.select2("onSortEnd");
  420. }
  421. });
  422. // On form submit, transform value to match what is expected by server
  423. subject.parents('form:first').submit(function (event) {
  424. var values = subject.val().split(',');
  425. var baseName = subject.attr('name');
  426. baseName = baseName.substring(0, baseName.length-1);
  427. for (var i=0; i<values.length; i++) {
  428. jQuery('<input>')
  429. .attr('type', 'hidden')
  430. .attr('name', baseName+i+']')
  431. .val(values[i])
  432. .appendTo(subject.parents('form:first'));
  433. }
  434. subject.remove();
  435. });
  436. }
  437. };