context_reaction_block.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. (function($){
  2. Drupal.behaviors.contextReactionBlock = {attach: function(context) {
  3. $('form.context-editor:not(.context-block-processed)')
  4. .addClass('context-block-processed')
  5. .each(function() {
  6. var id = $(this).attr('id');
  7. Drupal.contextBlockEditor = Drupal.contextBlockEditor || {};
  8. $(this).bind('init.pageEditor', function(event) {
  9. Drupal.contextBlockEditor[id] = new DrupalContextBlockEditor($(this));
  10. });
  11. $(this).bind('start.pageEditor', function(event, context) {
  12. // Fallback to first context if param is empty.
  13. if (!context) {
  14. context = $(this).data('defaultContext');
  15. }
  16. Drupal.contextBlockEditor[id].editStart($(this), context);
  17. });
  18. $(this).bind('end.pageEditor', function(event) {
  19. Drupal.contextBlockEditor[id].editFinish();
  20. });
  21. });
  22. //
  23. // Admin Form =======================================================
  24. //
  25. // ContextBlockForm: Init.
  26. $('#context-blockform:not(.processed)').each(function() {
  27. $(this).addClass('processed');
  28. Drupal.contextBlockForm = new DrupalContextBlockForm($(this));
  29. Drupal.contextBlockForm.setState();
  30. });
  31. // ContextBlockForm: Attach block removal handlers.
  32. // Lives in behaviors as it may be required for attachment to new DOM elements.
  33. $('#context-blockform a.remove:not(.processed)').each(function() {
  34. $(this).addClass('processed');
  35. $(this).click(function() {
  36. $(this).parents('tr').eq(0).remove();
  37. Drupal.contextBlockForm.setState();
  38. return false;
  39. });
  40. });
  41. // Conceal Section title, subtitle and class
  42. $('div.context-block-browser', context).nextAll('.form-item').hide();
  43. }};
  44. /**
  45. * Context block form. Default form for editing context block reactions.
  46. */
  47. DrupalContextBlockForm = function(blockForm) {
  48. this.state = {};
  49. this.setState = function() {
  50. $('table.context-blockform-region', blockForm).each(function() {
  51. var region = $(this).attr('id').split('context-blockform-region-')[1];
  52. var blocks = [];
  53. $('tr', $(this)).each(function() {
  54. var bid = $(this).attr('id');
  55. var weight = $(this).find('select,input').first().val();
  56. blocks.push({'bid' : bid, 'weight' : weight});
  57. });
  58. Drupal.contextBlockForm.state[region] = blocks;
  59. });
  60. // Serialize here and set form element value.
  61. $('form input.context-blockform-state').val(JSON.stringify(this.state));
  62. // Hide enabled blocks from selector that are used
  63. $('table.context-blockform-region tr').each(function() {
  64. var bid = Drupal.checkPlain($(this).attr('id'));
  65. $('div.context-blockform-selector input[value="'+bid+'"]').parents('div.form-item').eq(0).hide();
  66. });
  67. // Show blocks in selector that are unused
  68. $('div.context-blockform-selector input').each(function() {
  69. var bid = $(this).val();
  70. if ($('table.context-blockform-region tr#'+bid).size() === 0) {
  71. $(this).parents('div.form-item').eq(0).show();
  72. }
  73. });
  74. };
  75. // make sure we update the state right before submits, this takes care of an
  76. // apparent race condition between saving the state and the weights getting set
  77. // by tabledrag
  78. $('#ctools-export-ui-edit-item-form').submit(function() { Drupal.contextBlockForm.setState(); });
  79. // Tabledrag
  80. // Add additional handlers to update our blocks.
  81. $.each(Drupal.settings.tableDrag, function(base) {
  82. var table = $('#' + base + ':not(.processed)', blockForm);
  83. if (table && table.is('.context-blockform-region')) {
  84. table.addClass('processed');
  85. table.bind('mouseup', function(event) {
  86. Drupal.contextBlockForm.setState();
  87. return;
  88. });
  89. }
  90. });
  91. // Add blocks to a region
  92. $('td.blocks a', blockForm).each(function() {
  93. $(this).click(function() {
  94. var region = $(this).attr('href').split('#')[1];
  95. var base = "context-blockform-region-"+ region;
  96. var selected = $("div.context-blockform-selector input:checked");
  97. if (selected.size() > 0) {
  98. var weight_warn = false;
  99. var min_weight_option = -10;
  100. var max_weight_option = 10;
  101. var max_observed_weight = min_weight_option - 1;
  102. $('table#' + base + ' tr').each(function() {
  103. var weight_input_val = $(this).find('select,input').first().val();
  104. if (+weight_input_val > +max_observed_weight) {
  105. max_observed_weight = weight_input_val;
  106. }
  107. });
  108. selected.each(function() {
  109. // create new block markup
  110. var block = document.createElement('tr');
  111. var text = $(this).parents('div.form-item').eq(0).hide().children('label').text();
  112. var select = '<div class="form-item form-type-select"><select class="tabledrag-hide form-select">';
  113. var i;
  114. weight_warn = true;
  115. var selected_weight = max_weight_option;
  116. if (max_weight_option >= (1 + +max_observed_weight)) {
  117. selected_weight = ++max_observed_weight;
  118. weight_warn = false;
  119. }
  120. for (i = min_weight_option; i <= max_weight_option; ++i) {
  121. select += '<option';
  122. if (i == selected_weight) {
  123. select += ' selected=selected';
  124. }
  125. select += '>' + i + '</option>';
  126. }
  127. select += '</select></div>';
  128. $(block).attr('id', $(this).attr('value')).addClass('draggable');
  129. $(block).html("<td>"+ text + "</td><td>" + select + "</td><td><a href='' class='remove'>X</a></td>");
  130. // add block item to region
  131. //TODO : Fix it so long blocks don't get stuck when added to top regions and dragged towards bottom regions
  132. Drupal.tableDrag[base].makeDraggable(block);
  133. $('table#'+base).append(block);
  134. if ($.cookie('Drupal.tableDrag.showWeight') == 1) {
  135. $('table#'+base).find('.tabledrag-hide').css('display', '');
  136. $('table#'+base).find('.tabledrag-handle').css('display', 'none');
  137. }
  138. else {
  139. $('table#'+base).find('.tabledrag-hide').css('display', 'none');
  140. $('table#'+base).find('.tabledrag-handle').css('display', '');
  141. }
  142. Drupal.attachBehaviors($('table#'+base));
  143. Drupal.contextBlockForm.setState();
  144. $(this).removeAttr('checked');
  145. });
  146. if (weight_warn) {
  147. alert(Drupal.t('Desired block weight exceeds available weight options, please check weights for blocks before saving.'));
  148. }
  149. }
  150. return false;
  151. });
  152. });
  153. };
  154. /**
  155. * Context block editor. AHAH editor for live block reaction editing.
  156. */
  157. DrupalContextBlockEditor = function(editor) {
  158. this.editor = editor;
  159. this.state = {};
  160. this.blocks = {};
  161. this.regions = {};
  162. return this;
  163. };
  164. DrupalContextBlockEditor.prototype = {
  165. initBlocks : function(blocks) {
  166. var self = this;
  167. this.blocks = blocks;
  168. blocks.each(function() {
  169. if($(this).hasClass('context-block-empty')) {
  170. $(this).removeClass('context-block-hidden');
  171. }
  172. $(this).addClass('draggable');
  173. $(this).prepend($('<a class="context-block-handle"></a>'));
  174. $(this).prepend($('<a class="context-block-remove"></a>').click(function() {
  175. $(this).parent ('.block').eq(0).fadeOut('medium', function() {
  176. $(this).remove();
  177. self.updateBlocks();
  178. });
  179. return false;
  180. }));
  181. });
  182. },
  183. initRegions : function(regions) {
  184. this.regions = regions;
  185. var ref = this;
  186. $(regions).not('.context-ui-processed')
  187. .each(function(index, el) {
  188. $('.context-ui-add-link', el).click(function(e){
  189. ref.showBlockBrowser($(this).parent());
  190. }).addClass('context-ui-processed');
  191. });
  192. $('.context-block-browser').hide();
  193. },
  194. showBlockBrowser : function(region) {
  195. var toggled = false;
  196. //figure out the id of the context
  197. var activeId = $('.context-editing', this.editor).attr('id').replace('-trigger', ''),
  198. context = $('#' + activeId)[0];
  199. this.browser = $('.context-block-browser', context).addClass('active');
  200. //add the filter element to the block browser
  201. if (!this.browser.has('input.filter').size()) {
  202. var parent = $('.block-browser-sidebar .filter', this.browser);
  203. var list = $('.blocks', this.browser);
  204. new Drupal.Filter (list, false, '.context-block-addable', parent);
  205. }
  206. //show a dialog for the blocks list
  207. this.browser.show().dialog({
  208. modal : true,
  209. close : function() {
  210. $(this).dialog('destroy');
  211. //reshow all the categories
  212. $('.category', this).show();
  213. $(this).hide().appendTo(context).removeClass('active');
  214. },
  215. height: (.8 * $(window).height()),
  216. minHeight:400,
  217. minWidth:680,
  218. width:680
  219. });
  220. //handle showing / hiding block items when a different category is selected
  221. $('.context-block-browser-categories', this.browser).change(function(e) {
  222. //if no category is selected we want to show all the items
  223. if ($(this).val() == 0) {
  224. $('.category', self.browser).show();
  225. } else {
  226. $('.category', self.browser).hide();
  227. $('.category-' + $(this).val(), self.browser).show();
  228. }
  229. });
  230. //if we already have the function for a different context, rebind it so we don't get dupes
  231. if(this.addToRegion) {
  232. $('.context-block-addable', this.browser).unbind('click.addToRegion')
  233. }
  234. //protected function for adding a clicked block to a region
  235. var self = this;
  236. this.addToRegion = function(e){
  237. var ui = {
  238. 'item' : $(this).clone(),
  239. 'sender' : $(region)
  240. };
  241. $(this).parents('.context-block-browser.active').dialog('close');
  242. $(region).after(ui.item);
  243. self.addBlock(e, ui, this.editor, activeId.replace('context-editable-', ''));
  244. };
  245. $('.context-block-addable', this.browser).bind('click.addToRegion', this.addToRegion);
  246. },
  247. // Update UI to match the current block states.
  248. updateBlocks : function() {
  249. var browser = $('div.context-block-browser');
  250. // For all enabled blocks, mark corresponding addables as having been added.
  251. $('.block, .admin-block').each(function() {
  252. var bid = $(this).attr('id').split('block-')[1]; // Ugh.
  253. });
  254. // For all hidden addables with no corresponding blocks, mark as addable.
  255. $('.context-block-item', browser).each(function() {
  256. var bid = $(this).attr('id').split('context-block-addable-')[1];
  257. });
  258. // Mark empty regions.
  259. $(this.regions).each(function() {
  260. if ($('.block:has(a.context-block)', this).size() > 0) {
  261. $(this).removeClass('context-block-region-empty');
  262. }
  263. else {
  264. $(this).addClass('context-block-region-empty');
  265. }
  266. });
  267. },
  268. // Live update a region
  269. updateRegion : function(event, ui, region, op) {
  270. switch (op) {
  271. case 'over':
  272. $(region).removeClass('context-block-region-empty');
  273. break;
  274. case 'out':
  275. if (
  276. // jQuery UI 1.8
  277. $('.draggable-placeholder', region).size() === 1 &&
  278. $('.block:has(a.context-block)', region).size() == 0
  279. ) {
  280. $(region).addClass('context-block-region-empty');
  281. }
  282. break;
  283. }
  284. },
  285. // Remove script elements while dragging & dropping.
  286. scriptFix : function(event, ui, editor, context) {
  287. if ($('script', ui.item)) {
  288. var placeholder = $(Drupal.settings.contextBlockEditor.scriptPlaceholder);
  289. var label = $('div.handle label', ui.item).text();
  290. placeholder.children('strong').html(label);
  291. $('script', ui.item).parent().empty().append(placeholder);
  292. }
  293. },
  294. // Add a block to a region through an AJAX load of the block contents.
  295. addBlock : function(event, ui, editor, context) {
  296. var self = this;
  297. if (ui.item.is('.context-block-addable')) {
  298. var bid = ui.item.attr('id').split('context-block-addable-')[1];
  299. // Construct query params for our AJAX block request.
  300. var params = Drupal.settings.contextBlockEditor.params;
  301. params.context_block = bid + ',' + context;
  302. // Replace item with loading block.
  303. //ui.sender.append(ui.item);
  304. var blockLoading = $('<div class="context-block-item context-block-loading"><span class="icon"></span></div>');
  305. ui.item.addClass('context-block-added');
  306. ui.item.after(blockLoading);
  307. $.getJSON(Drupal.settings.contextBlockEditor.path, params, function(data) {
  308. if (data.status) {
  309. var newBlock = $(data.block);
  310. if ($('script', newBlock)) {
  311. $('script', newBlock).remove();
  312. }
  313. blockLoading.fadeOut(function() {
  314. $(this).replaceWith(newBlock);
  315. self.initBlocks(newBlock);
  316. self.updateBlocks();
  317. Drupal.attachBehaviors(newBlock);
  318. });
  319. }
  320. else {
  321. blockLoading.fadeOut(function() { $(this).remove(); });
  322. }
  323. });
  324. }
  325. else if (ui.item.is(':has(a.context-block)')) {
  326. self.updateBlocks();
  327. }
  328. },
  329. // Update form hidden field with JSON representation of current block visibility states.
  330. setState : function() {
  331. var self = this;
  332. $(this.regions).each(function() {
  333. var region = $('.context-block-region', this).attr('id').split('context-block-region-')[1];
  334. var blocks = [];
  335. $('a.context-block', $(this)).each(function() {
  336. if ($(this).attr('class').indexOf('edit-') != -1) {
  337. var bid = $(this).attr('id').split('context-block-')[1];
  338. var context = $(this).attr('class').split('edit-')[1].split(' ')[0];
  339. context = context ? context : 0;
  340. var block = {'bid': bid, 'context': context};
  341. blocks.push(block);
  342. }
  343. });
  344. self.state[region] = blocks;
  345. });
  346. // Serialize here and set form element value.
  347. $('input.context-block-editor-state', this.editor).val(JSON.stringify(this.state));
  348. },
  349. //Disable text selection.
  350. disableTextSelect : function() {
  351. if ($.browser.safari) {
  352. $('.block:has(a.context-block):not(:has(input,textarea))').css('WebkitUserSelect','none');
  353. }
  354. else if ($.browser.mozilla) {
  355. $('.block:has(a.context-block):not(:has(input,textarea))').css('MozUserSelect','none');
  356. }
  357. else if ($.browser.msie) {
  358. $('.block:has(a.context-block):not(:has(input,textarea))').bind('selectstart.contextBlockEditor', function() { return false; });
  359. }
  360. else {
  361. $(this).bind('mousedown.contextBlockEditor', function() { return false; });
  362. }
  363. },
  364. //Enable text selection.
  365. enableTextSelect : function() {
  366. if ($.browser.safari) {
  367. $('*').css('WebkitUserSelect','');
  368. }
  369. else if ($.browser.mozilla) {
  370. $('*').css('MozUserSelect','');
  371. }
  372. else if ($.browser.msie) {
  373. $('*').unbind('selectstart.contextBlockEditor');
  374. }
  375. else {
  376. $(this).unbind('mousedown.contextBlockEditor');
  377. }
  378. },
  379. // Start editing. Attach handlers, begin draggable/sortables.
  380. editStart : function(editor, context) {
  381. var self = this;
  382. // This is redundant to the start handler found in context_ui.js.
  383. // However it's necessary that we trigger this class addition before
  384. // we call .sortable() as the empty regions need to be visible.
  385. $(document.body).addClass('context-editing');
  386. this.editor.addClass('context-editing');
  387. this.disableTextSelect();
  388. this.initBlocks($('.block:has(a.context-block.edit-'+context+')'));
  389. this.initRegions($('.context-block-region').parent());
  390. this.updateBlocks();
  391. $('a.context_ui_dialog-stop').hide();
  392. $('.editing-context-label').remove();
  393. var label = $('#context-editable-trigger-'+context+' .label').text();
  394. label = Drupal.t('Now editing: @label', {'@label': label});
  395. editor.parent().parent().prepend('<div class="editing-context-label">' + label + '</div>');
  396. // First pass, enable sortables on all regions.
  397. $(this.regions).each(function() {
  398. var region = $(this);
  399. var params = {
  400. revert: true,
  401. dropOnEmpty: true,
  402. placeholder: 'draggable-placeholder',
  403. forcePlaceholderSize: true,
  404. items: '> *:has(a.context-block.editable)',
  405. handle: 'a.context-block-handle',
  406. start: function(event, ui) { self.scriptFix(event, ui, editor, context); },
  407. stop: function(event, ui) { self.addBlock(event, ui, editor, context); },
  408. receive: function(event, ui) { self.addBlock(event, ui, editor, context); },
  409. over: function(event, ui) { self.updateRegion(event, ui, region, 'over'); },
  410. out: function(event, ui) { self.updateRegion(event, ui, region, 'out'); },
  411. cursorAt: {left: 300, top: 0}
  412. };
  413. region.sortable(params);
  414. });
  415. // Second pass, hook up all regions via connectWith to each other.
  416. $(this.regions).each(function() {
  417. $(this).sortable('option', 'connectWith', ['.ui-sortable']);
  418. });
  419. // Terrible, terrible workaround for parentoffset issue in Safari.
  420. // The proper fix for this issue has been committed to jQuery UI, but was
  421. // not included in the 1.6 release. Therefore, we do a browser agent hack
  422. // to ensure that Safari users are covered by the offset fix found here:
  423. // http://dev.jqueryui.com/changeset/2073.
  424. if ($.ui.version === '1.6' && $.browser.safari) {
  425. $.browser.mozilla = true;
  426. }
  427. },
  428. // Finish editing. Remove handlers.
  429. editFinish : function() {
  430. this.editor.removeClass('context-editing');
  431. this.enableTextSelect();
  432. $('.editing-context-label').remove();
  433. // Remove UI elements.
  434. $(this.blocks).each(function() {
  435. $('a.context-block-handle, a.context-block-remove', this).remove();
  436. if($(this).hasClass('context-block-empty')) {
  437. $(this).addClass('context-block-hidden');
  438. }
  439. $(this).removeClass('draggable');
  440. });
  441. $('a.context_ui_dialog-stop').show();
  442. this.regions.sortable('destroy');
  443. this.setState();
  444. // Unhack the user agent.
  445. if ($.ui.version === '1.6' && $.browser.safari) {
  446. $.browser.mozilla = false;
  447. }
  448. }
  449. }; //End of DrupalContextBlockEditor prototype
  450. })(jQuery);