| | |
| | | this.rowcount = 0; |
| | | this.colcount = 0; |
| | | |
| | | this.subject_col = -1; |
| | | this.subject_col = 0; |
| | | this.modkey = 0; |
| | | this.multiselect = false; |
| | | this.multiexpand = false; |
| | |
| | | this.column_movable = false; |
| | | this.keyboard = false; |
| | | this.toggleselect = false; |
| | | this.aria_listbox = false; |
| | | this.parent_focus = true; |
| | | |
| | | this.drag_active = false; |
| | | this.col_drag_active = false; |
| | | this.column_fixed = null; |
| | | this.last_selected = 0; |
| | | this.shift_start = 0; |
| | | this.last_selected = null; |
| | | this.shift_start = null; |
| | | this.focused = false; |
| | | this.drag_mouse_start = null; |
| | | this.dblclick_time = 500; // default value on MS Windows is 500 |
| | |
| | | if (p && typeof p === 'object') |
| | | for (var n in p) |
| | | this[n] = p[n]; |
| | | |
| | | // register this instance |
| | | rcube_list_widget._instances.push(this); |
| | | }; |
| | | |
| | | |
| | |
| | | this.tbody = this.list; |
| | | } |
| | | |
| | | if ($(this.list).attr('role') == 'listbox') { |
| | | this.aria_listbox = true; |
| | | if (this.multiselect) |
| | | $(this.list).attr('aria-multiselectable', 'true'); |
| | | } |
| | | |
| | | var me = this; |
| | | |
| | | if (this.tbody) { |
| | | this.rows = {}; |
| | | this.rowcount = 0; |
| | | |
| | | var r, len, rows = this.tbody.childNodes, me = this; |
| | | var r, len, rows = this.tbody.childNodes; |
| | | |
| | | for (r=0, len=rows.length; r<len; r++) { |
| | | this.rowcount += this.init_row(rows[r]) ? 1 : 0; |
| | | if (rows[r].nodeType == 1) |
| | | this.rowcount += this.init_row(rows[r]) ? 1 : 0; |
| | | } |
| | | |
| | | this.init_header(); |
| | |
| | | if (this.keyboard) { |
| | | rcube_event.add_listener({event:'keydown', object:this, method:'key_press'}); |
| | | |
| | | // install a link element to receive focus. |
| | | // this helps to maintain the natural tab order when moving focus with keyboard |
| | | this.focus_elem = $('<a>') |
| | | .attr('tabindex', '0') |
| | | .attr('style', 'display:block; width:1px; height:1px; line-height:1px; overflow:hidden; position:absolute; top:-1000px') |
| | | .html('Select List') |
| | | .insertAfter(this.list) |
| | | .on('focus', function(e){ me.focus(e); }) |
| | | .on('blur', function(e){ me.blur(e); }); |
| | | // allow the table element to receive focus. |
| | | $(this.list).attr('tabindex', '0') |
| | | .on('focus', function(e){ me.focus(e); }); |
| | | } |
| | | } |
| | | |
| | | if (this.parent_focus) { |
| | | this.list.parentNode.onclick = function(e) { me.focus(); }; |
| | | } |
| | | |
| | | return this; |
| | |
| | | var self = this, uid = row.uid; |
| | | this.rows[uid] = {uid:uid, id:row.id, obj:row}; |
| | | |
| | | // set eventhandlers to table row |
| | | row.onmousedown = function(e){ return self.drag_row(e, this.uid); }; |
| | | row.onmouseup = function(e){ return self.click_row(e, this.uid); }; |
| | | $(row).data('uid', uid) |
| | | // set eventhandlers to table row (only left-button-clicks in mouseup) |
| | | .mousedown(function(e) { return self.drag_row(e, this.uid); }) |
| | | .mouseup(function(e) { |
| | | if (e.which == 1 && !self.drag_active) |
| | | return self.click_row(e, this.uid); |
| | | else |
| | | return true; |
| | | }); |
| | | |
| | | if (bw.touch) { |
| | | if (bw.touch && row.addEventListener) { |
| | | row.addEventListener('touchstart', function(e) { |
| | | if (e.touches.length == 1) { |
| | | self.touchmoved = false; |
| | |
| | | e.preventDefault(); |
| | | } |
| | | }, false); |
| | | } |
| | | |
| | | // label the list row with the subject col as descriptive label |
| | | if (this.aria_listbox) { |
| | | var lbl_id = 'l:' + row.id; |
| | | $(row) |
| | | .attr('role', 'option') |
| | | .attr('aria-labelledby', lbl_id) |
| | | .find(this.col_tagname()).eq(this.subject_col).attr('id', lbl_id); |
| | | } |
| | | |
| | | if (document.all) |
| | |
| | | |
| | | if (this.fixed_header) { // copy (modified) fixed header back to the actual table |
| | | $(this.list.tHead).replaceWith($(this.fixed_header).find('thead').clone()); |
| | | $(this.list.tHead).find('tr td').attr('style', '').find('a.sortcol').attr('tabindex', '-1'); // remove fixed widths |
| | | $(this.list.tHead).find('th,td').attr('style', '').find('a').attr('tabindex', '-1'); // remove fixed widths |
| | | } |
| | | else if (!bw.touch && this.list.className.indexOf('fixedheader') >= 0 && 0) { |
| | | else if (!bw.touch && this.list.className.indexOf('fixedheader') >= 0) { |
| | | this.init_fixed_header(); |
| | | } |
| | | |
| | |
| | | if (this.column_fixed == r) |
| | | continue; |
| | | col = this.thead.rows[0].cells[r]; |
| | | col.onmousedown = function(e){ return p.drag_column(e, this); }; |
| | | col.onmousedown = function(e) { return p.drag_column(e, this); }; |
| | | this.colcount++; |
| | | } |
| | | } |
| | |
| | | if (!this.fixed_header) { |
| | | this.fixed_header = $('<table>') |
| | | .attr('class', this.list.className + ' fixedcopy') |
| | | .attr('role', 'presentation') |
| | | .css({ position:'fixed' }) |
| | | .append(clone) |
| | | .append('<tbody></tbody>'); |
| | | $(this.list).before(this.fixed_header); |
| | | |
| | | var me = this; |
| | | $(window).resize(function(){ me.resize() }); |
| | | $(window).scroll(function(){ |
| | | $(window).resize(function() { me.resize(); }); |
| | | $(window).scroll(function() { |
| | | var w = $(window); |
| | | me.fixed_header.css('marginLeft', (-w.scrollLeft()) + 'px'); |
| | | if (!bw.webkit) |
| | | me.fixed_header.css('marginTop', (-w.scrollTop()) + 'px'); |
| | | me.fixed_header.css({ |
| | | marginLeft: -w.scrollLeft() + 'px', |
| | | marginTop: -w.scrollTop() + 'px' |
| | | }); |
| | | }); |
| | | } |
| | | else { |
| | |
| | | var column_widths = []; |
| | | |
| | | // get column widths from original thead |
| | | $(this.tbody).parent().find('thead tr td').each(function(index) { |
| | | $(this.tbody).parent().find('thead th,thead td').each(function(index) { |
| | | column_widths[index] = $(this).width(); |
| | | }); |
| | | |
| | | // apply fixed widths to fixed table header |
| | | $(this.thead).parent().width($(this.tbody).parent().width()); |
| | | $(this.thead).find('tr td').each(function(index) { |
| | | $(this).css('width', column_widths[index]); |
| | | $(this.thead).find('th,td').each(function(index) { |
| | | $(this).width(column_widths[index]); |
| | | }); |
| | | |
| | | $(window).scroll(); |
| | |
| | | |
| | | this.rows = {}; |
| | | this.rowcount = 0; |
| | | this.last_selected = null; |
| | | |
| | | if (sel) |
| | | this.clear_selection(); |
| | | else |
| | | this.last_selected = 0; |
| | | |
| | | // reset scroll position (in Opera) |
| | | if (this.frame) |
| | |
| | | if (row.nodeName === undefined) { |
| | | // for performance reasons use DOM instead of jQuery here |
| | | var domrow = document.createElement(this.row_tagname()); |
| | | |
| | | if (row.id) domrow.id = row.id; |
| | | if (row.uid) domrow.uid = row.uid; |
| | | if (row.className) domrow.className = row.className; |
| | | if (row.style) $.extend(domrow.style, row.style); |
| | | if (row.uid) $(domrow).data('uid', row.uid); |
| | | |
| | | for (var e, domcell, col, i=0; row.cols && i < row.cols.length; i++) { |
| | | col = row.cols[i]; |
| | |
| | | var row = this.rows[id]; |
| | | if (!row) return false; |
| | | |
| | | var domrow = row.obj; |
| | | for (var domcell, col, i=0; cols && i < cols.length; i++) { |
| | | var i, domrow = row.obj; |
| | | for (i = 0; cols && i < cols.length; i++) { |
| | | this.get_cell(domrow, i).html(cols[i]); |
| | | } |
| | | |
| | | if (newid) { |
| | | delete this.rows[id]; |
| | | domrow.uid = newid; |
| | | domrow.id = 'rcmrow' + newid; |
| | | this.init_row(domrow); |
| | | |
| | | if (select) |
| | | this.selection[0] = newid; |
| | | |
| | | if (this.last_selected == id) |
| | | this.last_selected = newid; |
| | | } |
| | | }, |
| | | |
| | |
| | | if (this.focused) |
| | | return; |
| | | |
| | | var n, id; |
| | | this.focused = true; |
| | | |
| | | for (n in this.selection) { |
| | | id = this.selection[n]; |
| | | if (this.rows[id] && this.rows[id].obj) { |
| | | $(this.rows[id].obj).addClass('selected').removeClass('unfocused'); |
| | | } |
| | | } |
| | | |
| | | if (e) |
| | | rcube_event.cancel(e); |
| | | |
| | | // Un-focus already focused elements (#1487123, #1487316, #1488600, #1488620) |
| | | // It looks that window.focus() does the job for all browsers, but not Firefox (#1489058) |
| | | // We now fix this by explicitly assigning focus to a dedicated link element |
| | | this.focus_elem.focus(); |
| | | var focus_elem = null; |
| | | |
| | | $(this.list).addClass('focus'); |
| | | if (this.last_selected && this.rows[this.last_selected]) { |
| | | focus_elem = $(this.rows[this.last_selected].obj).find(this.col_tagname()).eq(this.subject_col).attr('tabindex', '0'); |
| | | } |
| | | |
| | | // Un-focus already focused elements (#1487123, #1487316, #1488600, #1488620) |
| | | if (focus_elem && focus_elem.length) { |
| | | // We now fix this by explicitly assigning focus to a dedicated link element |
| | | this.focus_noscroll(focus_elem); |
| | | } |
| | | else { |
| | | // It looks that window.focus() does the job for all browsers, but not Firefox (#1489058) |
| | | $('iframe,:focus:not(body)').blur(); |
| | | window.focus(); |
| | | } |
| | | |
| | | $(this.list).addClass('focus').removeAttr('tabindex'); |
| | | |
| | | // set internal focus pointer to first row |
| | | if (!this.last_selected) |
| | |
| | | */ |
| | | blur: function(e) |
| | | { |
| | | var n, id; |
| | | this.focused = false; |
| | | for (n in this.selection) { |
| | | id = this.selection[n]; |
| | | if (this.rows[id] && this.rows[id].obj) { |
| | | $(this.rows[id].obj).removeClass('selected focused').addClass('unfocused'); |
| | | } |
| | | |
| | | // avoid the table getting focus right again |
| | | var me = this; |
| | | setTimeout(function(){ |
| | | $(me.list).removeClass('focus').attr('tabindex', '0'); |
| | | }, 20); |
| | | |
| | | if (this.last_selected && this.rows[this.last_selected]) { |
| | | $(this.rows[this.last_selected].obj) |
| | | .find(this.col_tagname()).eq(this.subject_col).removeAttr('tabindex'); |
| | | } |
| | | |
| | | $(this.list).removeClass('focus'); |
| | | }, |
| | | |
| | | /** |
| | | * Focus the given element without scrolling the list container |
| | | */ |
| | | focus_noscroll: function(elem) |
| | | { |
| | | var y = this.frame.scrollTop || this.frame.scrollY; |
| | | elem.focus(); |
| | | this.frame.scrollTop = y; |
| | | }, |
| | | |
| | | |
| | |
| | | */ |
| | | click_row: function(e, id) |
| | | { |
| | | // sanity check |
| | | if (!id || !this.rows[id]) |
| | | return false; |
| | | |
| | | // don't do anything (another action processed before) |
| | | if (!this.is_event_target(e)) |
| | | return true; |
| | |
| | | } |
| | | |
| | | this.rows[id].clicked = now; |
| | | this.focus(); |
| | | |
| | | return false; |
| | | }, |
| | | |
| | |
| | | |
| | | get_row_uid: function(row) |
| | | { |
| | | if (row && row.uid) |
| | | return row.uid; |
| | | if (!row) |
| | | return; |
| | | |
| | | var uid; |
| | | if (row && (uid = $(row).data('uid'))) |
| | | row.uid = uid; |
| | | else if (row && String(row.id).match(this.id_regexp)) |
| | | row.uid = RegExp.$1; |
| | | if (!row.uid) { |
| | | var uid = $(row).data('uid'); |
| | | if (uid) |
| | | row.uid = uid; |
| | | else if (String(row.id).match(this.id_regexp)) |
| | | row.uid = RegExp.$1; |
| | | } |
| | | |
| | | return row.uid; |
| | | }, |
| | |
| | | get_first_row: function() |
| | | { |
| | | if (this.rowcount) { |
| | | var i, len, uid, rows = this.tbody.childNodes; |
| | | var i, uid, rows = this.tbody.childNodes; |
| | | |
| | | for (i=0, len=rows.length-1; i<len; i++) |
| | | for (i=0; i<rows.length; i++) |
| | | if (rows[i].id && (uid = this.get_row_uid(rows[i]))) |
| | | return uid; |
| | | } |
| | |
| | | */ |
| | | select_row: function(id, mod_key, with_mouse) |
| | | { |
| | | var select_before = this.selection.join(','); |
| | | var select_before = this.selection.join(','), |
| | | in_selection_before = this.in_selection(id); |
| | | |
| | | if (!this.multiselect) |
| | | if (!this.multiselect && with_mouse) |
| | | mod_key = 0; |
| | | |
| | | if (!this.shift_start) |
| | |
| | | this.multi_selecting = true; |
| | | } |
| | | |
| | | // trigger event if selection changed |
| | | if (this.selection.join(',') != select_before) |
| | | this.triggerEvent('select'); |
| | | |
| | | if (this.last_selected != 0 && this.rows[this.last_selected]) |
| | | $(this.rows[this.last_selected].obj).removeClass('focused'); |
| | | if (this.last_selected && this.rows[this.last_selected]) { |
| | | $(this.rows[this.last_selected].obj).removeClass('focused') |
| | | .find(this.col_tagname()).eq(this.subject_col).removeAttr('tabindex'); |
| | | } |
| | | |
| | | // unselect if toggleselect is active and the same row was clicked again |
| | | if (this.toggleselect && this.last_selected == id) { |
| | | if (this.toggleselect && in_selection_before && !mod_key) { |
| | | this.clear_selection(); |
| | | id = null; |
| | | } |
| | | else |
| | | // trigger event if selection changed |
| | | else if (this.selection.join(',') != select_before) { |
| | | this.triggerEvent('select'); |
| | | } |
| | | |
| | | if (this.rows[id]) { |
| | | $(this.rows[id].obj).addClass('focused'); |
| | | // set cursor focus to link inside selected row |
| | | if (this.focused) |
| | | this.focus_noscroll($(this.rows[id].obj).find(this.col_tagname()).eq(this.subject_col).attr('tabindex', '0')); |
| | | } |
| | | |
| | | if (!this.selection.length) |
| | | this.shift_start = null; |
| | |
| | | /** |
| | | * Check if given id is part of the current selection |
| | | */ |
| | | in_selection: function(id) |
| | | in_selection: function(id, index) |
| | | { |
| | | for (var n in this.selection) |
| | | if (this.selection[n] == id) |
| | | return true; |
| | | return index ? parseInt(n) : true; |
| | | |
| | | return false; |
| | | }, |
| | |
| | | this.highlight_row(n, true, true); |
| | | } |
| | | else { |
| | | $(this.rows[n].obj).removeClass('selected').removeClass('unfocused'); |
| | | $(this.rows[n].obj).removeClass('selected').removeAttr('aria-selected'); |
| | | } |
| | | } |
| | | |
| | |
| | | else { |
| | | for (n in this.selection) |
| | | if (this.rows[this.selection[n]]) { |
| | | $(this.rows[this.selection[n]].obj).removeClass('selected').removeClass('unfocused'); |
| | | $(this.rows[this.selection[n]].obj).removeClass('selected').removeAttr('aria-selected'); |
| | | } |
| | | |
| | | this.selection = []; |
| | |
| | | |
| | | if (num_select && !this.selection.length && !no_event) { |
| | | this.triggerEvent('select'); |
| | | this.last_selected = 0; |
| | | this.last_selected = null; |
| | | } |
| | | }, |
| | | |
| | |
| | | if (this.selection.length > 1 || !this.in_selection(id)) { |
| | | this.clear_selection(null, true); |
| | | this.selection[0] = id; |
| | | $(this.rows[id].obj).addClass('selected'); |
| | | $(this.rows[id].obj).addClass('selected').attr('aria-selected', 'true'); |
| | | } |
| | | } |
| | | else { |
| | | if (!this.in_selection(id)) { // select row |
| | | var pre, post, p = this.in_selection(id, true); |
| | | |
| | | if (p === false) { // select row |
| | | this.selection.push(id); |
| | | $(this.rows[id].obj).addClass('selected'); |
| | | $(this.rows[id].obj).addClass('selected').attr('aria-selected', 'true'); |
| | | if (!norecur && !this.rows[id].expanded) |
| | | this.highlight_children(id, true); |
| | | } |
| | | else { // unselect row |
| | | var p = $.inArray(id, this.selection), |
| | | a_pre = this.selection.slice(0, p), |
| | | a_post = this.selection.slice(p+1, this.selection.length); |
| | | pre = this.selection.slice(0, p); |
| | | post = this.selection.slice(p+1, this.selection.length); |
| | | |
| | | this.selection = a_pre.concat(a_post); |
| | | $(this.rows[id].obj).removeClass('selected').removeClass('unfocused'); |
| | | this.selection = pre.concat(post); |
| | | $(this.rows[id].obj).removeClass('selected').removeAttr('aria-selected'); |
| | | if (!norecur && !this.rows[id].expanded) |
| | | this.highlight_children(id, false); |
| | | } |
| | |
| | | } |
| | | |
| | | return rcube_event.cancel(e); |
| | | |
| | | case 9: // Tab |
| | | this.blur(); |
| | | break; |
| | | |
| | | case 13: // Enter |
| | | if (!this.selection.length) |
| | | this.select_row(this.last_selected, mod_key, false); |
| | | |
| | | default: |
| | | this.key_pressed = keyCode; |
| | |
| | | */ |
| | | scrollto: function(id) |
| | | { |
| | | var row = this.rows[id].obj; |
| | | var row = this.rows[id] ? this.rows[id].obj : null; |
| | | |
| | | if (row && this.frame) { |
| | | var scroll_to = Number(row.offsetTop), |
| | | head_offset = 0; |
| | |
| | | |
| | | $('> ' + self.col_tagname(), self.rows[uid].obj).each(function(n, cell) { |
| | | if (self.subject_col < 0 || (self.subject_col >= 0 && self.subject_col == n)) { |
| | | var subject = $(cell).text(); |
| | | // remove elements marked with "skip-on-drag" class |
| | | cell = $(cell).clone(); |
| | | $(cell).find('.skip-on-drag').remove(); |
| | | |
| | | var subject = cell.text(); |
| | | |
| | | if (subject) { |
| | | // remove leading spaces |
| | |
| | | this.col_draglayer = null; |
| | | } |
| | | |
| | | if (this.col_drag_active) |
| | | this.focus(); |
| | | this.col_drag_active = false; |
| | | |
| | | rcube_event.remove_listener({event:'mousemove', object:this, method:'column_drag_mouse_move'}); |
| | | rcube_event.remove_listener({event:'mouseup', object:this, method:'column_drag_mouse_up'}); |
| | | |
| | | // remove temp divs |
| | | this.del_dragfix(); |
| | | |
| | | if (this.selected_column !== null && this.cols && this.cols.length) { |
| | | var i, cpos = 0, pos = rcube_event.get_mouse_pos(e); |
| | | if (this.col_drag_active) { |
| | | this.col_drag_active = false; |
| | | this.focus(); |
| | | this.triggerEvent('column_dragend', e); |
| | | |
| | | // find destination position |
| | | for (i=0; i<this.cols.length; i++) { |
| | | if (pos.x >= this.cols[i]/2 + this.list_pos + cpos) |
| | | cpos += this.cols[i]; |
| | | else |
| | | break; |
| | | } |
| | | if (this.selected_column !== null && this.cols && this.cols.length) { |
| | | var i, cpos = 0, pos = rcube_event.get_mouse_pos(e); |
| | | |
| | | if (i != this.selected_column && i != this.selected_column+1) { |
| | | this.column_replace(this.selected_column, i); |
| | | // find destination position |
| | | for (i=0; i<this.cols.length; i++) { |
| | | if (pos.x >= this.cols[i]/2 + this.list_pos + cpos) |
| | | cpos += this.cols[i]; |
| | | else |
| | | break; |
| | | } |
| | | |
| | | if (i != this.selected_column && i != this.selected_column+1) { |
| | | this.column_replace(this.selected_column, i); |
| | | } |
| | | } |
| | | } |
| | | |
| | | this.triggerEvent('column_dragend', e); |
| | | |
| | | return rcube_event.cancel(e); |
| | | }, |
| | |
| | | rcube_list_widget.prototype.addEventListener = rcube_event_engine.prototype.addEventListener; |
| | | rcube_list_widget.prototype.removeEventListener = rcube_event_engine.prototype.removeEventListener; |
| | | rcube_list_widget.prototype.triggerEvent = rcube_event_engine.prototype.triggerEvent; |
| | | |
| | | // static |
| | | rcube_list_widget._instances = []; |