Admin.js 18 KB

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