Project

General

Profile

Feature #17607 » context_menu.js

Context Menu javascript - Jan Strnádek, 2014-08-04 13:07

 
1
/**
2
 * Redmine - ContextMenu (JQuery plugin)
3
 *
4
 * @author: Jan Strnadek <jan.strnadek@gmail.com, jan.strnadek@netbrick.eu>
5
 */
6
(function($) {
7
  "use strict"; // jshint
8

    
9
  /** Context Menu Manager (SINGLETON) */
10
  var ContextMenuManager = function() {
11
    if ( ContextMenuManager.prototype._singletonInstance )
12
      return ContextMenuManager.prototype._singletonInstance;
13

    
14
    ContextMenuManager.prototype._singletonInstance = this;
15

    
16
    this.init();
17
  };
18

    
19
  ContextMenuManager.prototype = {
20

    
21
    init: function() {
22
      var that = this;
23
      this.stack = [];
24
      this.$el   = $("<div></div>").attr('id', 'context-menu').hide();
25
      $("#content").append(this.$el);
26

    
27
      /** Click event handler */
28
      $(document).click(function(evt) {
29
        that.click(evt);
30
      });
31

    
32
      /** Escape event handler */
33
      $(document).keyup(function(evt) {
34
        if (evt.keyCode == 27)
35
          that.hideContexMenu();
36
      });
37
    },
38

    
39
    click: function(evt) {
40
      var target = $(evt.target);
41
      if (target.is('a') && target.hasClass('submenu')) {
42
        event.preventDefault();
43
        return;
44
      }
45
      this.hideContexMenu();
46
    },
47

    
48
    hideContexMenu: function() {
49
      this.$el.hide();
50
    },
51

    
52
    add: function(contextMenu) {
53
      this.stack.push(contextMenu);
54
    },
55

    
56
    getElement: function() {
57
      return this.$el;
58
    },
59

    
60
    showContextMenu: function(evt, data) {
61
      var mouse_x = evt.pageX;
62
      var mouse_y = evt.pageY;
63
      this.$el.css('left', (mouse_x + 'px'));
64
      this.$el.css('top',  (mouse_y + 'px'));
65
      this.$el.html(data);
66
      this.resizeContextMenu(mouse_x, mouse_y);
67
      this.$el.show();
68
    },
69

    
70
    resizeContextMenu: function(mouse_x, mouse_y) {
71
      var dims;
72
      var menu_width;
73
      var menu_height;
74
      var window_width;
75
      var window_height;
76
      var max_width;
77
      var max_height;
78

    
79
      menu_width  = this.$el.width();
80
      menu_height = this.$el.height();
81
      max_width   = mouse_x + 2 * menu_width;
82
      max_height  = mouse_y + menu_height;
83

    
84
      var ws = this.windowSize();
85
      window_width = ws.width;
86
      window_height = ws.height;
87

    
88
      /* display the menu above and/or to the left of the click if needed */
89
      if (max_width > window_width) {
90
       mouse_x -= menu_width;
91
       this.$el.addClass('reverse-x');
92
      } else {
93
       this.$el.removeClass('reverse-x');
94
      }
95
      if (max_height > window_height) {
96
       mouse_y -= menu_height;
97
       this.$el.addClass('reverse-y');
98
      } else {
99
       this.$el.removeClass('reverse-y');
100
      }
101
      if (mouse_x <= 0) mouse_x = 1;
102
      if (mouse_y <= 0) mouse_y = 1;
103
      this.$el.css('left', (mouse_x + 'px'));
104
      this.$el.css('top', (mouse_y + 'px'));
105
    },
106

    
107
    windowSize: function() {
108
      var w;
109
      var h;
110
      if (window.innerWidth) {
111
        w = window.innerWidth;
112
        h = window.innerHeight;
113
      } else if (document.documentElement) {
114
        w = document.documentElement.clientWidth;
115
        h = document.documentElement.clientHeight;
116
      } else {
117
        w = document.body.clientWidth;
118
        h = document.body.clientHeight;
119
      }
120
      return {width: w, height: h};
121
    },
122
  };
123

    
124
  /* ContextMenu class definition */
125
  var ContextMenu = function (element, options) {
126
    this.init (element, options);
127
  };
128

    
129
  ContextMenu.prototype = {
130
    constructor: ContextMenu,
131

    
132
    init: function (element, options) {
133
	    var that     = this;
134
    	this.options = options;
135

    
136
      /** Add menu to manager */
137
      this.manager = new ContextMenuManager();
138
      this.manager.add ( this );
139

    
140
      /** Start observing element */
141
      this.$el   = $(element);
142
      this.$el.contextmenu(function(evt) {
143
        that.contextMenuClick(evt);
144
      });
145
      this.$el.click(function(evt) {
146
        that.click(evt);
147
      });
148
    },
149

    
150
    click: function(evt) {
151
      var target = $(evt.target);
152

    
153
      if (target.is('a') && target.hasClass('submenu')) {
154
        event.preventDefault();
155
        return;
156
      }
157
      if (target.is('a') || target.is('img')) { return; }
158

    
159
      /** Check if row was clicked */
160
      var tr = target.parents('tr').first();
161
      if (tr.length && tr.hasClass('hascontextmenu')) {
162
        /** Input was clicked - do not remove selection from all */
163
        if (target.is('input')) {
164
          if (target.is(':checked')) {
165
            tr.addClass('context-menu-selection');
166
          } else {
167
            tr.removeClass('context-menu-selection');
168
          }
169
        } else {
170
          /** Disable all selected items */
171
          this.$el.find('tr.hascontextmenu').each(function() {
172
            var element = $(this);
173
            var input = element.find('td.checkbox input[type="checkbox"]').first();
174
            if ( input ) input.prop('checked', false);
175
            element.removeClass('context-menu-selection');
176
          });
177

    
178
          /** Set checked to true and add tr context class */
179
          tr.addClass('context-menu-selection');
180
          var input = tr.find('td.checkbox input[type="checkbox"]').first();
181
          if ( input ) input.prop('checked', true);
182
        }
183
      }
184
    },
185

    
186
    contextMenuClick: function(evt) {
187
      var target = $(evt.target);
188

    
189
      if (target.is('a')) { return; }
190

    
191
      var tr = target.parents('tr').first();
192
      if (!tr.hasClass('hascontextmenu')) { return; }
193

    
194
      /** Clicked on tr - ensure it is checked */
195
      var input = tr.find('td.checkbox input[type="checkbox"]');
196
      if ( input ) {
197
        if ( !input.is(':checked') ) {
198
          /** Input is not checked - remove selection of other elements */
199
          this.$el.find('tr.hascontextmenu').each(function() {
200
            var element = $(this);
201
            var input = element.find('td.checkbox input[type="checkbox"]').first();
202
            if ( input ) input.prop('checked', false);
203
            element.removeClass('context-menu-selection');
204
          });
205
        }
206
        input.prop('checked', true);
207
        tr.addClass('context-menu-selection');
208
      }
209

    
210
      evt.preventDefault();
211
      this.show(evt);
212
    },
213

    
214
    show: function(evt) {
215
      var manager = this.manager;
216
      var form    = this.$el.children('form').first();
217

    
218
      $.ajax({
219
        url: this.options.url,
220
        data: form.serialize(),
221
        success: function(data, textStatus, jqXHR) {
222
          manager.showContextMenu(evt, data);
223
        }
224
      });
225
    }
226
  };
227

    
228
  $.fn.contextMenu = function(options) {
229
    var defaults = {
230
      url: null
231
    };
232
    var settings = $.extend( {}, defaults, options );
233

    
234
    /* Element for bind is table / parent DIV */
235
    return this.each(function() {
236
      new ContextMenu(this, settings);
237
    });
238
  };
239

    
240
})( jQuery );
    (1-1/1)