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