Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117752608
list.js
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
49 KB
Referenced Files
None
Subscribers
None
list.js
View Options
/**
* Roundcube List Widget
*
* This file is part of the Roundcube Webmail client
*
* @licstart The following is the entire license notice for the
* JavaScript code in this file.
*
* Copyright (c) The Roundcube Dev Team
*
* The JavaScript code in this page is free software: you can
* redistribute it and/or modify it under the terms of the GNU
* General Public License (GNU GPL) as published by the Free Software
* Foundation, either version 3 of the License, or (at your option)
* any later version. The code is distributed WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
*
* As additional permission under GNU GPL version 3 section 7, you
* may distribute non-source (e.g., minimized or compacted) forms of
* that code without the copy of the GNU GPL normally required by
* section 4, provided you include this license notice and a URL
* through which recipients can access the Corresponding Source.
*
* @licend The above is the entire license notice
* for the JavaScript code in this file.
*
* @author Thomas Bruederli <roundcube@gmail.com>
* @author Charles McNulty <charles@charlesmcnulty.com>
*
* @requires jquery.js, common.js
*/
/**
* Roundcube List Widget class
* @constructor
*/
function
rcube_list_widget
(
list
,
p
)
{
// static constants
this
.
ENTER_KEY
=
13
;
this
.
DELETE_KEY
=
46
;
this
.
BACKSPACE_KEY
=
8
;
this
.
list
=
list
?
list
:
null
;
this
.
tagname
=
this
.
list
?
this
.
list
.
nodeName
.
toLowerCase
()
:
'table'
;
this
.
id_regexp
=
/^rcmrow([a-z0-9\-_=\+\/]+)/i
;
this
.
rows
=
{};
this
.
selection
=
[];
this
.
rowcount
=
0
;
this
.
colcount
=
0
;
this
.
subject_col
=
0
;
this
.
modkey
=
0
;
this
.
multiselect
=
false
;
this
.
multiexpand
=
false
;
this
.
multi_selecting
=
false
;
this
.
draggable
=
false
;
this
.
column_movable
=
false
;
this
.
keyboard
=
false
;
this
.
toggleselect
=
false
;
this
.
aria_listbox
=
false
;
this
.
parent_focus
=
true
;
this
.
checkbox_selection
=
false
;
this
.
drag_active
=
false
;
this
.
col_drag_active
=
false
;
this
.
column_fixed
=
null
;
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
this
.
row_init
=
function
(){};
// @deprecated; use list.addEventListener('initrow') instead
this
.
touch_start_time
=
0
;
// start time of the touch event
this
.
touch_event_time
=
500
;
// maximum time a touch should be considered a left mouse button event, after this its something else (eg contextmenu event)
// overwrite default parameters
if
(
p
&&
typeof
p
===
'object'
)
for
(
var
n
in
p
)
this
[
n
]
=
p
[
n
];
// register this instance
rcube_list_widget
.
_instances
.
push
(
this
);
};
rcube_list_widget
.
prototype
=
{
/**
* get all message rows from HTML table and init each row
*/
init
:
function
()
{
if
(
this
.
tagname
==
'table'
&&
this
.
list
&&
this
.
list
.
tBodies
[
0
])
{
this
.
thead
=
this
.
list
.
tHead
;
this
.
tbody
=
this
.
list
.
tBodies
[
0
];
}
else
if
(
this
.
tagname
!=
'table'
&&
this
.
list
)
{
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
;
for
(
r
=
0
,
len
=
rows
.
length
;
r
<
len
;
r
++
)
{
if
(
rows
[
r
].
nodeType
==
1
)
this
.
rowcount
+=
this
.
init_row
(
rows
[
r
])
?
1
:
0
;
}
this
.
init_header
();
this
.
frame
=
this
.
list
.
parentNode
;
// set body events
if
(
this
.
keyboard
)
{
rcube_event
.
add_listener
({
event
:
'keydown'
,
object
:
this
,
method
:
'key_press'
});
// 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
();
};
}
rcmail
.
triggerEvent
(
'initlist'
,
{
obj
:
this
.
list
});
return
this
;
},
/**
* Init list row and set mouse events on it
*/
init_row
:
function
(
row
)
{
row
.
uid
=
this
.
get_row_uid
(
row
);
// make references in internal array and set event handlers
if
(
row
&&
row
.
uid
)
{
var
self
=
this
,
uid
=
row
.
uid
;
this
.
rows
[
uid
]
=
{
uid
:
uid
,
id
:
row
.
id
,
obj
:
row
};
$
(
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
&&
!
$
(
e
.
currentTarget
).
is
(
'.ui-droppable-active'
))
return
self
.
click_row
(
e
,
this
.
uid
);
else
return
true
;
});
// for IE and Edge (Trident) differentiate between touch, touch+hold using pointer events rather than touch
if
((
bw
.
ie
||
(
bw
.
edge
&&
bw
.
vendver
<
75
))
&&
bw
.
pointer
)
{
$
(
row
).
on
(
'pointerdown'
,
function
(
e
)
{
if
(
e
.
pointerType
==
'touch'
)
{
self
.
touch_start_time
=
new
Date
().
getTime
();
return
false
;
}
})
.
on
(
'pointerup'
,
function
(
e
)
{
if
(
e
.
pointerType
==
'touch'
)
{
var
duration
=
(
new
Date
().
getTime
()
-
self
.
touch_start_time
);
if
(
duration
<=
self
.
touch_event_time
)
{
self
.
drag_row
(
e
,
this
.
uid
);
return
self
.
click_row
(
e
,
this
.
uid
);
}
}
});
}
else
if
(
bw
.
touch
&&
row
.
addEventListener
)
{
row
.
addEventListener
(
'touchstart'
,
function
(
e
)
{
if
(
e
.
touches
.
length
==
1
)
{
self
.
touchmoved
=
false
;
self
.
drag_row
(
rcube_event
.
touchevent
(
e
.
touches
[
0
]),
this
.
uid
);
self
.
touch_start_time
=
new
Date
().
getTime
();
}
},
false
);
row
.
addEventListener
(
'touchend'
,
function
(
e
)
{
if
(
e
.
changedTouches
.
length
==
1
)
{
var
duration
=
(
new
Date
().
getTime
()
-
self
.
touch_start_time
);
if
(
!
self
.
touchmoved
&&
duration
<=
self
.
touch_event_time
&&
!
self
.
click_row
(
rcube_event
.
touchevent
(
e
.
changedTouches
[
0
]),
this
.
uid
))
e
.
preventDefault
();
}
},
false
);
row
.
addEventListener
(
'touchmove'
,
function
(
e
)
{
if
(
e
.
changedTouches
.
length
==
1
)
{
self
.
touchmoved
=
true
;
if
(
self
.
drag_active
)
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_column
()).
attr
(
'id'
,
lbl_id
);
}
if
(
document
.
all
)
row
.
onselectstart
=
function
()
{
return
false
;
};
this
.
row_init
(
this
.
rows
[
uid
]);
// legacy support
this
.
triggerEvent
(
'initrow'
,
this
.
rows
[
uid
]);
return
true
;
}
},
/**
* Init list column headers and set mouse events on them
*/
init_header
:
function
()
{
if
(
this
.
thead
)
{
this
.
colcount
=
0
;
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
(
'th,td'
).
attr
(
'style'
,
''
).
find
(
'a'
).
attr
(
'tabindex'
,
'-1'
);
// remove fixed widths
}
else
if
(
!
bw
.
touch
&&
this
.
list
.
className
.
indexOf
(
'fixedheader'
)
>=
0
)
{
this
.
init_fixed_header
();
}
var
col
,
r
,
p
=
this
;
// add events for list columns moving
if
(
this
.
column_movable
&&
this
.
thead
&&
this
.
thead
.
rows
)
{
for
(
r
=
0
;
r
<
this
.
thead
.
rows
[
0
].
cells
.
length
;
r
++
)
{
if
(
this
.
column_fixed
==
r
)
continue
;
col
=
this
.
thead
.
rows
[
0
].
cells
[
r
];
col
.
onmousedown
=
function
(
e
)
{
return
p
.
drag_column
(
e
,
this
);
};
this
.
colcount
++
;
}
}
}
},
/**
* Set the scrollable parent object for the table's fixed header
*/
container
:
window
,
init_fixed_header
:
function
()
{
var
clone
=
$
(
this
.
list
.
tHead
).
clone
();
if
(
!
this
.
fixed_header
)
{
this
.
fixed_header
=
$
(
'<table>'
)
.
attr
(
'id'
,
this
.
list
.
id
+
'-fixedcopy'
)
.
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
).
on
(
'resize'
,
function
()
{
me
.
resize
();
});
$
(
this
.
container
).
on
(
'scroll'
,
function
()
{
var
w
=
$
(
this
);
me
.
fixed_header
.
css
({
marginLeft
:
-
w
.
scrollLeft
()
+
'px'
,
marginTop
:
-
w
.
scrollTop
()
+
'px'
});
});
}
else
{
$
(
this
.
fixed_header
).
find
(
'thead'
).
replaceWith
(
clone
);
}
// avoid scrolling header links being focused
$
(
this
.
list
.
tHead
).
find
(
'a.sortcol'
).
attr
(
'tabindex'
,
'-1'
);
// set tabindex to fixed header sort links
clone
.
find
(
'a.sortcol'
).
attr
(
'tabindex'
,
'0'
);
this
.
thead
=
clone
.
get
(
0
);
this
.
resize
();
},
resize
:
function
()
{
if
(
!
this
.
fixed_header
)
return
;
var
column_widths
=
[];
// get column widths from original thead
$
(
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
(
'th,td'
).
each
(
function
(
index
)
{
$
(
this
).
width
(
column_widths
[
index
]);
});
$
(
window
).
scroll
();
},
/**
* Remove all list rows
*/
clear
:
function
(
sel
)
{
if
(
this
.
tagname
==
'table'
)
{
var
tbody
=
document
.
createElement
(
'tbody'
);
this
.
list
.
insertBefore
(
tbody
,
this
.
tbody
);
this
.
list
.
removeChild
(
this
.
list
.
tBodies
[
1
]);
this
.
tbody
=
tbody
;
}
else
{
$
(
this
.
row_tagname
()
+
':not(.thead)'
,
this
.
tbody
).
remove
();
}
this
.
rows
=
{};
this
.
rowcount
=
0
;
this
.
last_selected
=
null
;
if
(
sel
)
this
.
clear_selection
();
// reset scroll position (in Opera)
if
(
this
.
frame
)
this
.
frame
.
scrollTop
=
0
;
// fix list header after removing any rows
this
.
resize
();
},
/**
* 'remove' message row from list (just hide it)
*/
remove_row
:
function
(
uid
,
sel_next
)
{
var
self
=
this
,
node
=
this
.
rows
[
uid
]
?
this
.
rows
[
uid
].
obj
:
null
;
if
(
!
node
)
return
;
node
.
style
.
display
=
'none'
;
// Select next row before deletion, because we need the reference
if
(
sel_next
)
this
.
select_next
(
uid
);
delete
this
.
rows
[
uid
];
this
.
rowcount
--
;
// fix list header after removing any rows
clearTimeout
(
this
.
resize_timeout
)
this
.
resize_timeout
=
setTimeout
(
function
()
{
self
.
resize
();
},
50
);
},
/**
* Add row to the list and initialize it
*/
insert_row
:
function
(
row
,
before
)
{
var
self
=
this
,
tbody
=
this
.
tbody
;
// create a real dom node first
if
(
row
.
nodeName
===
undefined
)
{
// for performance reasons use DOM instead of jQuery here
var
i
,
e
,
domcell
,
col
,
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
);
for
(
i
=
0
;
row
.
cols
&&
i
<
row
.
cols
.
length
;
i
++
)
{
col
=
row
.
cols
[
i
];
domcell
=
col
.
dom
;
if
(
!
domcell
)
{
domcell
=
document
.
createElement
(
this
.
col_tagname
());
if
(
col
.
className
)
domcell
.
className
=
col
.
className
;
if
(
col
.
innerHTML
)
domcell
.
innerHTML
=
col
.
innerHTML
;
for
(
e
in
col
.
events
)
domcell
[
'on'
+
e
]
=
col
.
events
[
e
];
}
domrow
.
appendChild
(
domcell
);
}
row
=
domrow
;
}
if
(
this
.
checkbox_selection
)
{
this
.
insert_checkbox
(
row
);
}
if
(
before
&&
tbody
.
childNodes
.
length
)
tbody
.
insertBefore
(
row
,
(
typeof
before
==
'object'
&&
before
.
parentNode
==
tbody
)
?
before
:
tbody
.
firstChild
);
else
tbody
.
appendChild
(
row
);
this
.
init_row
(
row
);
this
.
rowcount
++
;
// fix list header after adding any rows
clearTimeout
(
this
.
resize_timeout
)
this
.
resize_timeout
=
setTimeout
(
function
()
{
self
.
resize
();
},
50
);
},
/**
* Update existing record
*/
update_row
:
function
(
id
,
cols
,
newid
,
select
)
{
var
row
=
this
.
rows
[
id
];
if
(
!
row
)
return
false
;
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
;
}
},
/**
* Add selection checkbox to the list record
*/
insert_checkbox
:
function
(
row
,
tag_name
)
{
var
key
,
self
=
this
,
cell
=
document
.
createElement
(
this
.
col_tagname
(
tag_name
)),
chbox
=
document
.
createElement
(
'input'
);
chbox
.
type
=
'checkbox'
;
chbox
.
tabIndex
=
-
1
;
chbox
.
onchange
=
function
(
e
)
{
self
.
select_row
(
row
.
uid
,
key
||
CONTROL_KEY
,
true
);
e
.
stopPropagation
();
key
=
null
;
};
chbox
.
onmousedown
=
function
(
e
)
{
key
=
rcube_event
.
get_modifier
(
e
);
};
cell
.
className
=
'selection'
;
// make the whole cell "touchable" for touch devices
cell
.
onclick
=
function
(
e
)
{
if
(
!
$
(
e
.
target
).
is
(
'input'
))
{
key
=
rcube_event
.
get_modifier
(
e
);
$
(
chbox
).
prop
(
'checked'
,
!
chbox
.
checked
).
change
();
}
e
.
stopPropagation
();
};
cell
.
appendChild
(
chbox
);
row
.
insertBefore
(
cell
,
row
.
firstChild
);
},
/**
* Enable checkbox selection
*/
enable_checkbox_selection
:
function
()
{
this
.
checkbox_selection
=
true
;
// Add checkbox to existing records if any
var
r
,
len
,
cell
,
rows
,
row_tag
=
this
.
row_tagname
().
toUpperCase
();
if
(
this
.
thead
)
{
rows
=
this
.
thead
.
childNodes
;
for
(
r
=
0
,
len
=
rows
.
length
;
r
<
len
;
r
++
)
{
if
(
rows
[
r
].
nodeName
==
row_tag
&&
(
cell
=
rows
[
r
].
firstChild
))
{
if
(
cell
.
className
==
'selection'
)
break
;
this
.
insert_checkbox
(
rows
[
r
],
'thead'
);
}
}
}
rows
=
this
.
tbody
.
childNodes
;
for
(
r
=
0
,
len
=
rows
.
length
;
r
<
len
;
r
++
)
{
if
(
rows
[
r
].
nodeName
==
row_tag
&&
(
cell
=
rows
[
r
].
firstChild
))
{
if
(
cell
.
className
==
'selection'
)
break
;
this
.
insert_checkbox
(
rows
[
r
],
'tbody'
);
}
}
},
/**
* Set focus to the list
*/
focus
:
function
(
e
)
{
if
(
this
.
focused
)
return
;
this
.
focused
=
true
;
if
(
e
)
rcube_event
.
cancel
(
e
);
var
focus_elem
=
null
;
if
(
this
.
last_selected
&&
this
.
rows
[
this
.
last_selected
])
{
focus_elem
=
$
(
this
.
rows
[
this
.
last_selected
].
obj
).
find
(
this
.
col_tagname
()).
eq
(
this
.
subject_column
()).
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
)
this
.
select_first
(
CONTROL_KEY
);
},
/**
* remove focus from the list
*/
blur
:
function
(
e
)
{
this
.
focused
=
false
;
// avoid the table getting focus right again (on Shift+Tab)
var
me
=
this
;
setTimeout
(
function
()
{
$
(
me
.
list
).
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_column
()).
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
;
},
/**
* Set/unset the given column as hidden
*/
hide_column
:
function
(
col
,
hide
)
{
var
method
=
hide
?
'addClass'
:
'removeClass'
;
if
(
this
.
fixed_header
)
$
(
this
.
row_tagname
()
+
' '
+
this
.
col_tagname
()
+
'.'
+
col
,
this
.
fixed_header
)[
method
](
'hidden'
);
$
(
this
.
row_tagname
()
+
' '
+
this
.
col_tagname
()
+
'.'
+
col
,
this
.
list
)[
method
](
'hidden'
);
},
/**
* onmousedown-handler of message list column
*/
drag_column
:
function
(
e
,
col
)
{
if
(
this
.
colcount
>
1
)
{
this
.
drag_start
=
true
;
this
.
drag_mouse_start
=
rcube_event
.
get_mouse_pos
(
e
);
rcube_event
.
add_listener
({
event
:
'mousemove'
,
object
:
this
,
method
:
'column_drag_mouse_move'
});
rcube_event
.
add_listener
({
event
:
'mouseup'
,
object
:
this
,
method
:
'column_drag_mouse_up'
});
// enable dragging over iframes
this
.
add_dragfix
();
// find selected column number
for
(
var
i
=
0
;
i
<
this
.
thead
.
rows
[
0
].
cells
.
length
;
i
++
)
{
if
(
col
==
this
.
thead
.
rows
[
0
].
cells
[
i
])
{
this
.
selected_column
=
i
;
break
;
}
}
}
return
false
;
},
/**
* onmousedown-handler of message list row
*/
drag_row
:
function
(
e
,
id
)
{
// don't do anything (another action processed before)
if
(
!
this
.
is_event_target
(
e
))
return
true
;
// handle only left-clicks
if
(
rcube_event
.
get_button
(
e
)
!=
0
)
return
true
;
this
.
in_selection_before
=
e
&&
e
.
istouch
||
this
.
in_selection
(
id
)
?
id
:
false
;
// selects currently unselected row
if
(
!
this
.
in_selection_before
)
{
var
mod_key
=
rcube_event
.
get_modifier
(
e
);
this
.
select_row
(
id
,
mod_key
,
true
);
}
if
(
this
.
draggable
&&
this
.
selection
.
length
&&
this
.
in_selection
(
id
))
{
this
.
drag_start
=
true
;
this
.
drag_mouse_start
=
rcube_event
.
get_mouse_pos
(
e
);
rcube_event
.
add_listener
({
event
:
'mousemove'
,
object
:
this
,
method
:
'drag_mouse_move'
});
rcube_event
.
add_listener
({
event
:
'mouseup'
,
object
:
this
,
method
:
'drag_mouse_up'
});
if
(
bw
.
touch
)
{
rcube_event
.
add_listener
({
event
:
'touchmove'
,
object
:
this
,
method
:
'drag_mouse_move'
});
rcube_event
.
add_listener
({
event
:
'touchend'
,
object
:
this
,
method
:
'drag_mouse_up'
});
}
// enable dragging over iframes
this
.
add_dragfix
();
this
.
focus
();
}
return
false
;
},
/**
* onmouseup-handler of message list row
*/
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
;
var
now
=
new
Date
().
getTime
(),
dblclicked
=
now
-
this
.
rows
[
id
].
clicked
<
this
.
dblclick_time
;
// unselects currently selected row
if
(
!
this
.
drag_active
&&
!
dblclicked
&&
this
.
in_selection_before
==
id
)
this
.
select_row
(
id
,
rcube_event
.
get_modifier
(
e
),
true
);
this
.
drag_start
=
false
;
this
.
in_selection_before
=
false
;
// row was double clicked
if
(
this
.
rowcount
&&
dblclicked
&&
this
.
in_selection
(
id
))
{
this
.
triggerEvent
(
'dblclick'
);
now
=
0
;
}
else
this
.
triggerEvent
(
'click'
);
if
(
!
this
.
drag_active
)
{
// remove temp divs
this
.
del_dragfix
();
rcube_event
.
cancel
(
e
);
}
this
.
rows
[
id
].
clicked
=
now
;
this
.
focus
();
return
false
;
},
/**
* Check target of the current event
*/
is_event_target
:
function
(
e
)
{
var
target
=
rcube_event
.
get_target
(
e
),
tagname
=
target
.
tagName
.
toLowerCase
();
return
!
(
target
&&
(
tagname
==
'input'
||
tagname
==
'img'
||
(
tagname
!=
'a'
&&
target
.
onclick
)
||
$
(
target
).
data
(
'action-link'
)));
},
/*
* Returns thread root ID for specified row ID
*/
find_root
:
function
(
uid
)
{
var
r
=
this
.
rows
[
uid
];
if
(
r
&&
r
.
parent_uid
)
return
this
.
find_root
(
r
.
parent_uid
);
else
return
uid
;
},
expand_row
:
function
(
e
,
id
)
{
var
row
=
this
.
rows
[
id
],
evtarget
=
rcube_event
.
get_target
(
e
),
mod_key
=
rcube_event
.
get_modifier
(
e
),
action
=
(
row
.
expanded
?
'collapse'
:
'expand'
)
+
(
mod_key
==
CONTROL_KEY
||
this
.
multiexpand
?
'_all'
:
''
);
// Don't treat double click on the expando as double click on the message.
row
.
clicked
=
0
;
this
[
action
](
row
);
},
collapse
:
function
(
row
)
{
var
r
,
depth
=
row
.
depth
,
new_row
=
row
?
row
.
obj
.
nextSibling
:
null
;
row
.
expanded
=
false
;
this
.
update_expando
(
row
.
id
);
this
.
triggerEvent
(
'expandcollapse'
,
{
uid
:
row
.
uid
,
expanded
:
row
.
expanded
,
obj
:
row
.
obj
});
while
(
new_row
)
{
if
(
new_row
.
nodeType
==
1
)
{
r
=
this
.
rows
[
new_row
.
uid
];
if
(
r
&&
r
.
depth
<=
depth
)
break
;
$
(
new_row
).
css
(
'display'
,
'none'
);
if
(
r
.
expanded
)
{
r
.
expanded
=
false
;
this
.
triggerEvent
(
'expandcollapse'
,
{
uid
:
r
.
uid
,
expanded
:
r
.
expanded
,
obj
:
new_row
});
}
}
new_row
=
new_row
.
nextSibling
;
}
this
.
resize
();
this
.
triggerEvent
(
'listupdate'
);
return
false
;
},
expand
:
function
(
row
)
{
var
r
,
p
,
depth
,
new_row
,
last_expanded_parent_depth
;
if
(
row
)
{
row
.
expanded
=
true
;
depth
=
row
.
depth
;
new_row
=
row
.
obj
.
nextSibling
;
this
.
update_expando
(
row
.
id
,
true
);
this
.
triggerEvent
(
'expandcollapse'
,
{
uid
:
row
.
uid
,
expanded
:
row
.
expanded
,
obj
:
row
.
obj
});
}
else
{
var
tbody
=
this
.
tbody
;
new_row
=
tbody
.
firstChild
;
depth
=
0
;
last_expanded_parent_depth
=
0
;
}
while
(
new_row
)
{
if
(
new_row
.
nodeType
==
1
)
{
r
=
this
.
rows
[
new_row
.
uid
];
if
(
r
)
{
if
(
row
&&
(
!
r
.
depth
||
r
.
depth
<=
depth
))
break
;
if
(
r
.
parent_uid
)
{
p
=
this
.
rows
[
r
.
parent_uid
];
if
(
p
&&
p
.
expanded
)
{
if
((
row
&&
p
==
row
)
||
last_expanded_parent_depth
>=
p
.
depth
-
1
)
{
last_expanded_parent_depth
=
p
.
depth
;
$
(
new_row
).
css
(
'display'
,
''
);
r
.
expanded
=
true
;
this
.
update_expando
(
r
.
id
,
true
);
this
.
triggerEvent
(
'expandcollapse'
,
{
uid
:
r
.
uid
,
expanded
:
r
.
expanded
,
obj
:
new_row
});
}
}
else
if
(
row
&&
(
!
p
||
p
.
depth
<=
depth
))
break
;
}
}
}
new_row
=
new_row
.
nextSibling
;
}
this
.
resize
();
this
.
triggerEvent
(
'listupdate'
);
return
false
;
},
collapse_all
:
function
(
row
)
{
var
depth
,
new_row
,
r
;
if
(
row
)
{
row
.
expanded
=
false
;
depth
=
row
.
depth
;
new_row
=
row
.
obj
.
nextSibling
;
this
.
update_expando
(
row
.
id
);
this
.
triggerEvent
(
'expandcollapse'
,
{
uid
:
row
.
uid
,
expanded
:
row
.
expanded
,
obj
:
row
.
obj
});
// don't collapse sub-root tree in multiexpand mode
if
(
depth
&&
this
.
multiexpand
)
return
false
;
}
else
{
new_row
=
this
.
tbody
.
firstChild
;
depth
=
0
;
}
while
(
new_row
)
{
if
(
new_row
.
nodeType
==
1
)
{
if
(
r
=
this
.
rows
[
new_row
.
uid
])
{
if
(
row
&&
(
!
r
.
depth
||
r
.
depth
<=
depth
))
break
;
if
(
row
||
r
.
depth
)
$
(
new_row
).
css
(
'display'
,
'none'
);
if
(
r
.
expanded
)
{
r
.
expanded
=
false
;
if
(
r
.
has_children
)
{
this
.
update_expando
(
r
.
id
);
this
.
triggerEvent
(
'expandcollapse'
,
{
uid
:
r
.
uid
,
expanded
:
r
.
expanded
,
obj
:
new_row
});
}
}
}
}
new_row
=
new_row
.
nextSibling
;
}
this
.
resize
();
this
.
triggerEvent
(
'listupdate'
);
return
false
;
},
expand_all
:
function
(
row
)
{
var
depth
,
new_row
,
r
;
if
(
row
)
{
row
.
expanded
=
true
;
depth
=
row
.
depth
;
new_row
=
row
.
obj
.
nextSibling
;
this
.
update_expando
(
row
.
id
,
true
);
this
.
triggerEvent
(
'expandcollapse'
,
{
uid
:
row
.
uid
,
expanded
:
row
.
expanded
,
obj
:
row
.
obj
});
}
else
{
new_row
=
this
.
tbody
.
firstChild
;
depth
=
0
;
}
while
(
new_row
)
{
if
(
new_row
.
nodeType
==
1
)
{
if
(
r
=
this
.
rows
[
new_row
.
uid
])
{
if
(
row
&&
r
.
depth
<=
depth
)
break
;
$
(
new_row
).
css
(
'display'
,
''
);
if
(
!
r
.
expanded
)
{
r
.
expanded
=
true
;
if
(
r
.
has_children
)
{
this
.
update_expando
(
r
.
id
,
true
);
this
.
triggerEvent
(
'expandcollapse'
,
{
uid
:
r
.
uid
,
expanded
:
r
.
expanded
,
obj
:
new_row
});
}
}
}
}
new_row
=
new_row
.
nextSibling
;
}
this
.
resize
();
this
.
triggerEvent
(
'listupdate'
);
return
false
;
},
update_expando
:
function
(
id
,
expanded
)
{
var
expando
=
document
.
getElementById
(
'rcmexpando'
+
id
);
if
(
expando
)
expando
.
className
=
expanded
?
'expanded'
:
'collapsed'
;
},
get_row_uid
:
function
(
row
)
{
if
(
!
row
)
return
;
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/next/previous/last rows that are not hidden
*/
get_next_row
:
function
(
uid
)
{
if
(
!
this
.
rowcount
)
return
false
;
var
last_selected_row
=
this
.
rows
[
uid
||
this
.
last_selected
],
new_row
=
last_selected_row
?
last_selected_row
.
obj
.
nextSibling
:
null
;
while
(
new_row
&&
(
new_row
.
nodeType
!=
1
||
new_row
.
style
.
display
==
'none'
))
new_row
=
new_row
.
nextSibling
;
return
new_row
;
},
get_prev_row
:
function
(
uid
)
{
if
(
!
this
.
rowcount
)
return
false
;
var
last_selected_row
=
this
.
rows
[
uid
||
this
.
last_selected
],
new_row
=
last_selected_row
?
last_selected_row
.
obj
.
previousSibling
:
null
;
while
(
new_row
&&
(
new_row
.
nodeType
!=
1
||
new_row
.
style
.
display
==
'none'
))
new_row
=
new_row
.
previousSibling
;
return
new_row
;
},
get_first_row
:
function
()
{
if
(
this
.
rowcount
)
{
var
i
,
uid
,
rows
=
this
.
tbody
.
childNodes
;
for
(
i
=
0
;
i
<
rows
.
length
;
i
++
)
if
(
rows
[
i
].
id
&&
(
uid
=
this
.
get_row_uid
(
rows
[
i
]))
&&
this
.
rows
[
uid
])
return
uid
;
}
return
null
;
},
get_last_row
:
function
()
{
if
(
this
.
rowcount
)
{
var
i
,
uid
,
rows
=
this
.
tbody
.
childNodes
;
for
(
i
=
rows
.
length
-
1
;
i
>=
0
;
i
--
)
if
(
rows
[
i
].
id
&&
(
uid
=
this
.
get_row_uid
(
rows
[
i
]))
&&
this
.
rows
[
uid
])
return
uid
;
}
return
null
;
},
get_next
:
function
()
{
var
row
;
if
(
row
=
this
.
get_next_row
())
{
return
row
.
uid
;
}
},
get_prev
:
function
()
{
var
row
;
if
(
row
=
this
.
get_prev_row
())
{
return
row
.
uid
;
}
},
row_tagname
:
function
()
{
var
row_tagnames
=
{
table
:
'tr'
,
ul
:
'li'
,
'*'
:
'div'
};
return
row_tagnames
[
this
.
tagname
]
||
row_tagnames
[
'*'
];
},
col_tagname
:
function
(
tagname
)
{
var
col_tagnames
=
{
table
:
'td'
,
thead
:
'th'
,
tbody
:
'td'
,
'*'
:
'span'
};
return
col_tagnames
[
tagname
||
this
.
tagname
]
||
col_tagnames
[
'*'
];
},
get_cell
:
function
(
row
,
index
)
{
return
$
(
this
.
col_tagname
(),
row
).
eq
(
index
+
(
this
.
checkbox_selection
?
1
:
0
));
},
/**
* selects or unselects the proper row depending on the modifier key pressed
*/
select_row
:
function
(
id
,
mod_key
,
with_mouse
)
{
var
select_before
=
this
.
selection
.
join
(
','
),
in_selection_before
=
this
.
in_selection
(
id
);
if
(
!
this
.
multiselect
&&
with_mouse
)
mod_key
=
0
;
if
(
!
this
.
shift_start
)
this
.
shift_start
=
id
if
(
!
mod_key
)
{
this
.
shift_start
=
id
;
this
.
highlight_row
(
id
,
false
);
this
.
multi_selecting
=
false
;
}
else
{
switch
(
mod_key
)
{
case
SHIFT_KEY
:
this
.
shift_select
(
id
,
false
);
break
;
case
CONTROL_KEY
:
if
(
with_mouse
)
{
this
.
shift_start
=
id
;
this
.
highlight_row
(
id
,
true
);
}
break
;
case
CONTROL_SHIFT_KEY
:
this
.
shift_select
(
id
,
true
);
break
;
default
:
this
.
highlight_row
(
id
,
false
);
break
;
}
this
.
multi_selecting
=
true
;
}
if
(
this
.
last_selected
&&
this
.
rows
[
this
.
last_selected
])
{
$
(
this
.
rows
[
this
.
last_selected
].
obj
).
removeClass
(
'focused'
)
.
find
(
this
.
col_tagname
()).
eq
(
this
.
subject_column
()).
removeAttr
(
'tabindex'
);
}
// unselect if toggleselect is active and the same row was clicked again
if
(
this
.
toggleselect
&&
in_selection_before
&&
!
mod_key
)
{
this
.
clear_selection
();
}
// 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_column
()).
attr
(
'tabindex'
,
'0'
));
}
if
(
!
this
.
selection
.
length
)
this
.
shift_start
=
null
;
this
.
last_selected
=
id
;
},
/**
* Alias method for select_row
*/
select
:
function
(
id
)
{
this
.
select_row
(
id
,
false
);
this
.
scrollto
(
id
);
},
/**
* Select row next to the specified or last selected one
* Either below or above.
*/
select_next
:
function
(
uid
)
{
var
new_row
=
this
.
get_next_row
(
uid
)
||
this
.
get_prev_row
(
uid
);
if
(
new_row
)
this
.
select_row
(
new_row
.
uid
,
false
,
false
);
},
/**
* Select first row
*/
select_first
:
function
(
mod_key
,
noscroll
)
{
var
row
=
this
.
get_first_row
();
if
(
row
)
{
this
.
select_row
(
row
,
mod_key
,
false
);
if
(
!
noscroll
)
this
.
scrollto
(
row
);
}
},
/**
* Select last row
*/
select_last
:
function
(
mod_key
,
noscroll
)
{
var
row
=
this
.
get_last_row
();
if
(
row
)
{
this
.
select_row
(
row
,
mod_key
,
false
);
if
(
!
noscroll
)
this
.
scrollto
(
row
);
}
},
/**
* Add all children of the given row to selection
*/
select_children
:
function
(
uid
)
{
var
i
,
children
=
this
.
row_children
(
uid
),
len
=
children
.
length
;
for
(
i
=
0
;
i
<
len
;
i
++
)
if
(
!
this
.
in_selection
(
children
[
i
]))
this
.
select_row
(
children
[
i
],
CONTROL_KEY
,
true
);
},
/**
* Perform selection when shift key is pressed
*/
shift_select
:
function
(
id
,
control
)
{
if
(
!
this
.
rows
[
this
.
shift_start
]
||
!
this
.
selection
.
length
)
this
.
shift_start
=
id
;
var
n
,
i
,
j
,
to_row
=
this
.
rows
[
id
],
from_rowIndex
=
this
.
_rowIndex
(
this
.
rows
[
this
.
shift_start
].
obj
),
to_rowIndex
=
this
.
_rowIndex
(
to_row
.
obj
);
// if we're going down the list, and we hit a thread, and it's closed, select the whole thread
if
(
from_rowIndex
<
to_rowIndex
&&
!
to_row
.
expanded
&&
to_row
.
has_children
)
if
(
to_row
=
this
.
rows
[(
this
.
row_children
(
id
)).
pop
()])
to_rowIndex
=
this
.
_rowIndex
(
to_row
.
obj
);
i
=
((
from_rowIndex
<
to_rowIndex
)
?
from_rowIndex
:
to_rowIndex
),
j
=
((
from_rowIndex
>
to_rowIndex
)
?
from_rowIndex
:
to_rowIndex
);
// iterate through the entire message list
for
(
n
in
this
.
rows
)
{
if
(
this
.
_rowIndex
(
this
.
rows
[
n
].
obj
)
>=
i
&&
this
.
_rowIndex
(
this
.
rows
[
n
].
obj
)
<=
j
)
{
if
(
!
this
.
in_selection
(
n
))
{
this
.
highlight_row
(
n
,
true
);
}
}
else
{
if
(
this
.
in_selection
(
n
)
&&
!
control
)
{
this
.
highlight_row
(
n
,
true
);
}
}
}
},
/**
* Helper method to emulate the rowIndex property of non-tr elements
*/
_rowIndex
:
function
(
obj
)
{
return
(
obj
.
rowIndex
!==
undefined
)
?
obj
.
rowIndex
:
$
(
obj
).
prevAll
().
length
;
},
/**
* Check if given id is part of the current selection
*/
in_selection
:
function
(
id
,
index
)
{
for
(
var
n
in
this
.
selection
)
if
(
this
.
selection
[
n
]
==
id
)
return
index
?
parseInt
(
n
)
:
true
;
return
false
;
},
/**
* Select each row in list
*/
select_all
:
function
(
filter
)
{
if
(
!
this
.
rowcount
)
return
false
;
// reset but remember selection first
var
n
,
select_before
=
this
.
selection
.
join
(
','
);
this
.
selection
=
[];
for
(
n
in
this
.
rows
)
{
if
(
!
filter
||
this
.
rows
[
n
][
filter
]
==
true
)
{
this
.
last_selected
=
n
;
this
.
highlight_row
(
n
,
true
,
true
);
}
else
{
$
(
this
.
rows
[
n
].
obj
).
removeClass
(
'selected'
).
removeAttr
(
'aria-selected'
);
}
}
// trigger event if selection changed
if
(
this
.
selection
.
join
(
','
)
!=
select_before
)
this
.
triggerEvent
(
'select'
);
this
.
focus
();
return
true
;
},
/**
* Invert selection
*/
invert_selection
:
function
()
{
if
(
!
this
.
rowcount
)
return
false
;
// remember old selection
var
n
,
select_before
=
this
.
selection
.
join
(
','
);
for
(
n
in
this
.
rows
)
this
.
highlight_row
(
n
,
true
);
// trigger event if selection changed
if
(
this
.
selection
.
join
(
','
)
!=
select_before
)
this
.
triggerEvent
(
'select'
);
this
.
focus
();
return
true
;
},
/**
* Unselect selected row(s)
*/
clear_selection
:
function
(
id
,
no_event
)
{
var
n
,
num_select
=
this
.
selection
.
length
;
// one row
if
(
id
)
{
for
(
n
in
this
.
selection
)
if
(
this
.
selection
[
n
]
==
id
)
{
this
.
selection
.
splice
(
n
,
1
);
break
;
}
}
// all rows
else
{
for
(
n
in
this
.
selection
)
if
(
this
.
rows
[
this
.
selection
[
n
]])
{
$
(
this
.
rows
[
this
.
selection
[
n
]].
obj
).
removeClass
(
'selected'
).
removeAttr
(
'aria-selected'
);
}
this
.
selection
=
[];
}
if
(
this
.
checkbox_selection
)
$
(
this
.
row_tagname
()
+
':not(.selected) > .selection > input:checked'
,
this
.
list
).
prop
(
'checked'
,
false
);
if
(
num_select
&&
!
this
.
selection
.
length
&&
!
no_event
)
{
this
.
triggerEvent
(
'select'
);
this
.
last_selected
=
null
;
}
},
/**
* Getter for the selection array
*/
get_selection
:
function
(
deep
)
{
var
res
=
$
.
merge
([],
this
.
selection
);
var
props
=
{
deep
:
deep
,
res
:
res
};
if
(
this
.
triggerEvent
(
'getselection'
,
props
)
===
false
)
return
props
.
res
;
// return children of selected threads even if only root is selected
if
(
deep
!==
false
&&
res
.
length
)
{
for
(
var
uid
,
uids
,
i
=
0
,
len
=
res
.
length
;
i
<
len
;
i
++
)
{
uid
=
res
[
i
];
if
(
this
.
rows
[
uid
]
&&
this
.
rows
[
uid
].
has_children
&&
!
this
.
rows
[
uid
].
expanded
)
{
uids
=
this
.
row_children
(
uid
);
for
(
var
j
=
0
,
uids_len
=
uids
.
length
;
j
<
uids_len
;
j
++
)
{
uid
=
uids
[
j
];
if
(
!
this
.
in_selection
(
uid
))
res
.
push
(
uid
);
}
}
}
}
return
res
;
},
/**
* Return the ID if only one row is selected
*/
get_single_selection
:
function
()
{
var
selection
=
this
.
get_selection
(
false
);
if
(
selection
.
length
==
1
)
return
selection
[
0
];
else
return
null
;
},
/**
* Highlight/unhighlight a row
*/
highlight_row
:
function
(
id
,
multiple
,
norecur
)
{
if
(
!
this
.
rows
[
id
])
return
;
if
(
!
multiple
)
{
if
(
this
.
selection
.
length
>
1
||
!
this
.
in_selection
(
id
))
{
this
.
clear_selection
(
null
,
true
);
this
.
selection
[
0
]
=
id
;
$
(
this
.
rows
[
id
].
obj
).
addClass
(
'selected'
).
attr
(
'aria-selected'
,
'true'
);
if
(
this
.
checkbox_selection
)
$
(
'.selection > input'
,
this
.
rows
[
id
].
obj
).
prop
(
'checked'
,
true
);
}
}
else
{
var
pre
,
post
,
p
=
this
.
in_selection
(
id
,
true
);
if
(
p
===
false
)
{
// select row
this
.
selection
.
push
(
id
);
$
(
this
.
rows
[
id
].
obj
).
addClass
(
'selected'
).
attr
(
'aria-selected'
,
'true'
);
if
(
this
.
checkbox_selection
)
$
(
'.selection > input'
,
this
.
rows
[
id
].
obj
).
prop
(
'checked'
,
true
);
if
(
!
norecur
&&
!
this
.
rows
[
id
].
expanded
)
this
.
highlight_children
(
id
,
true
);
}
else
{
// unselect row
pre
=
this
.
selection
.
slice
(
0
,
p
);
post
=
this
.
selection
.
slice
(
p
+
1
,
this
.
selection
.
length
);
this
.
selection
=
pre
.
concat
(
post
);
$
(
this
.
rows
[
id
].
obj
).
removeClass
(
'selected'
).
removeAttr
(
'aria-selected'
);
if
(
this
.
checkbox_selection
)
$
(
'.selection > input'
,
this
.
rows
[
id
].
obj
).
prop
(
'checked'
,
false
);
if
(
!
norecur
&&
!
this
.
rows
[
id
].
expanded
)
this
.
highlight_children
(
id
,
false
);
}
}
},
/**
* Highlight/unhighlight all children of the given row
*/
highlight_children
:
function
(
id
,
status
)
{
var
i
,
selected
,
children
=
this
.
row_children
(
id
),
len
=
children
.
length
;
for
(
i
=
0
;
i
<
len
;
i
++
)
{
selected
=
this
.
in_selection
(
children
[
i
]);
if
((
status
&&
!
selected
)
||
(
!
status
&&
selected
))
this
.
highlight_row
(
children
[
i
],
true
,
true
);
}
},
/**
* Handler for keyboard events
*/
key_press
:
function
(
e
)
{
if
(
!
this
.
focused
||
$
(
e
.
target
).
is
(
'input,textarea,select'
))
return
true
;
var
keyCode
=
rcube_event
.
get_keycode
(
e
),
mod_key
=
rcube_event
.
get_modifier
(
e
);
switch
(
keyCode
)
{
case
37
:
// Left arrow
case
39
:
// Right arrow
case
40
:
// Up arrow
case
38
:
// Down arrow
case
63233
:
// "down" in Safari keypress
case
63232
:
// "up" in Safari keypress
// Stop propagation so that the browser doesn't scroll
rcube_event
.
cancel
(
e
);
return
this
.
use_arrow_key
(
keyCode
,
mod_key
);
case
32
:
// Space
rcube_event
.
cancel
(
e
);
return
this
.
select_row
(
this
.
last_selected
,
mod_key
,
true
);
case
36
:
// Home
this
.
select_first
(
mod_key
);
return
rcube_event
.
cancel
(
e
);
case
35
:
// End
this
.
select_last
(
mod_key
);
return
rcube_event
.
cancel
(
e
);
case
65
:
// Ctrl + A
if
(
mod_key
==
CONTROL_KEY
&&
this
.
multiselect
)
{
this
.
select_first
(
null
,
true
);
this
.
select_last
(
SHIFT_KEY
,
true
);
return
rcube_event
.
cancel
(
e
);
}
break
;
case
27
:
// Esc
if
(
this
.
drag_active
)
return
this
.
drag_mouse_up
(
e
);
if
(
this
.
col_drag_active
)
{
this
.
selected_column
=
null
;
return
this
.
column_drag_mouse_up
(
e
);
}
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
;
this
.
modkey
=
mod_key
;
this
.
triggerEvent
(
'keypress'
);
this
.
modkey
=
0
;
if
(
this
.
key_pressed
==
this
.
BACKSPACE_KEY
)
return
rcube_event
.
cancel
(
e
);
}
return
true
;
},
/**
* Special handling method for arrow keys
*/
use_arrow_key
:
function
(
keyCode
,
mod_key
)
{
var
new_row
,
selected_row
=
this
.
rows
[
this
.
last_selected
];
if
(
!
selected_row
)
{
// select the first row if none selected yet
this
.
select_first
(
CONTROL_KEY
);
}
// Safari uses the non-standard keycodes 63232/63233 for up/down, if we're
// using the keypress event (but not the keydown or keyup event).
else
if
(
keyCode
==
40
||
keyCode
==
63233
)
// Down arrow
new_row
=
this
.
get_next_row
();
else
if
(
keyCode
==
38
||
keyCode
==
63232
)
// Up arrow
new_row
=
this
.
get_prev_row
();
else
if
(
keyCode
==
39
&&
selected_row
.
has_children
)
{
// Right arrow
if
(
!
selected_row
.
expanded
)
this
.
expand_all
(
selected_row
);
else
{
// jump to the first child
new_row
=
this
.
get_next_row
();
mod_key
=
null
;
}
}
else
if
(
keyCode
==
37
)
{
// Left arrow
if
(
selected_row
.
expanded
&&
selected_row
.
has_children
&&
(
!
selected_row
.
parent_uid
||
!
this
.
multiexpand
))
this
.
collapse_all
(
selected_row
);
else
if
(
selected_row
.
parent_uid
)
{
// jump to the top-most or closest parent
if
(
mod_key
==
CONTROL_KEY
)
new_row
=
this
.
rows
[
this
.
find_root
(
selected_row
.
uid
)];
else
new_row
=
this
.
rows
[
selected_row
.
parent_uid
];
mod_key
=
null
;
}
}
if
(
new_row
)
{
// simulate ctr-key if no rows are selected
if
(
!
mod_key
&&
!
this
.
selection
.
length
)
mod_key
=
CONTROL_KEY
;
this
.
select_row
(
new_row
.
uid
,
mod_key
,
false
);
this
.
scrollto
(
new_row
.
uid
);
}
return
false
;
},
/**
* Try to scroll the list to make the specified row visible
*/
scrollto
:
function
(
id
)
{
var
row
=
this
.
rows
[
id
]
?
this
.
rows
[
id
].
obj
:
null
;
if
(
row
&&
this
.
frame
)
{
var
scroll_to
=
Number
(
row
.
offsetTop
),
head_offset
=
0
;
// expand thread if target row is hidden (collapsed)
if
(
!
scroll_to
&&
this
.
rows
[
id
].
parent_uid
)
{
var
parent
=
this
.
find_root
(
this
.
rows
[
id
].
uid
);
this
.
expand_all
(
this
.
rows
[
parent
]);
scroll_to
=
Number
(
row
.
offsetTop
);
}
if
(
this
.
fixed_header
)
head_offset
=
Number
(
this
.
thead
.
offsetHeight
);
// if row is above the frame (or behind header)
if
(
scroll_to
<
Number
(
this
.
frame
.
scrollTop
)
+
head_offset
)
{
// scroll window so that row isn't behind header
this
.
frame
.
scrollTop
=
scroll_to
-
head_offset
;
}
else
if
(
scroll_to
+
Number
(
row
.
offsetHeight
)
>
Number
(
this
.
frame
.
scrollTop
)
+
Number
(
this
.
frame
.
offsetHeight
))
this
.
frame
.
scrollTop
=
(
scroll_to
+
Number
(
row
.
offsetHeight
))
-
Number
(
this
.
frame
.
offsetHeight
);
}
},
/**
* Handler for mouse move events
*/
drag_mouse_move
:
function
(
e
)
{
// convert touch event
if
(
e
.
type
==
'touchmove'
)
{
if
(
e
.
touches
.
length
==
1
&&
e
.
changedTouches
.
length
==
1
)
e
=
rcube_event
.
touchevent
(
e
.
changedTouches
[
0
]);
else
return
rcube_event
.
cancel
(
e
);
}
if
(
this
.
drag_start
)
{
// check mouse movement, of less than 3 pixels, don't start dragging
var
m
=
rcube_event
.
get_mouse_pos
(
e
),
limit
=
10
,
selection
=
[],
self
=
this
;
if
(
!
this
.
drag_mouse_start
||
(
Math
.
abs
(
m
.
x
-
this
.
drag_mouse_start
.
x
)
<
3
&&
Math
.
abs
(
m
.
y
-
this
.
drag_mouse_start
.
y
)
<
3
))
return
false
;
// remember dragging start position
this
.
drag_start_pos
=
{
left
:
m
.
x
,
top
:
m
.
y
};
// initialize drag layer
if
(
!
this
.
draglayer
)
this
.
draglayer
=
$
(
'<div>'
).
attr
(
'id'
,
'rcmdraglayer'
)
.
css
({
position
:
'absolute'
,
display
:
'none'
,
'z-index'
:
2000
})
.
appendTo
(
document
.
body
);
else
this
.
draglayer
.
html
(
''
);
// get selected rows (in display order), don't use this.selection here
$
(
this
.
row_tagname
()
+
'.selected'
,
this
.
tbody
).
each
(
function
()
{
var
uid
=
self
.
get_row_uid
(
this
),
row
=
self
.
rows
[
uid
];
if
(
!
row
||
$
.
inArray
(
uid
,
selection
)
>
-
1
)
return
;
selection
.
push
(
uid
);
// also handle children of (collapsed) trees for dragging (they might be not selected)
if
(
row
.
has_children
&&
!
row
.
expanded
)
$
.
each
(
self
.
row_children
(
uid
),
function
()
{
if
(
$
.
inArray
(
this
,
selection
)
>
-
1
)
return
;
selection
.
push
(
this
);
});
// break the loop asap
if
(
selection
.
length
>
limit
+
1
)
return
false
;
});
var
row
,
subject
,
subject_col
=
self
.
subject_column
(),
subject_func
=
function
(
cell
)
{
if
(
cell
)
{
// remove elements marked with "skip-on-drag" class
cell
=
$
(
cell
).
clone
();
$
(
cell
).
find
(
'.skip-on-drag'
).
remove
();
}
return
cell
?
cell
.
text
()
:
''
;
};
// append subject (of every row up to the limit) to the drag layer
$
.
each
(
selection
,
function
(
i
,
uid
)
{
if
(
i
>
limit
)
{
self
.
draglayer
.
append
(
$
(
'<div>'
).
text
(
'...'
));
return
false
;
}
row
=
self
.
rows
[
uid
].
obj
;
subject
=
''
;
$
(
row
).
children
(
self
.
col_tagname
()).
each
(
function
(
n
,
cell
)
{
if
(
subject_col
<
0
||
(
subject_col
>=
0
&&
subject_col
==
n
))
{
if
(
subject
=
subject_func
(
cell
))
{
return
false
;
}
}
});
// Subject column might be wrong, fallback to .subject
if
(
!
subject
.
length
)
{
subject
=
subject_func
(
$
(
row
).
children
(
'.subject'
).
first
());
}
if
(
subject
.
length
)
{
// remove leading spaces
subject
=
subject
.
trim
();
// truncate line to 50 characters
subject
=
(
subject
.
length
>
50
?
subject
.
substring
(
0
,
50
)
+
'...'
:
subject
);
self
.
draglayer
.
append
(
$
(
'<div>'
).
text
(
subject
));
}
});
this
.
draglayer
.
show
();
this
.
drag_active
=
true
;
this
.
triggerEvent
(
'dragstart'
);
}
if
(
this
.
drag_active
&&
this
.
draglayer
)
{
var
pos
=
rcube_event
.
get_mouse_pos
(
e
);
this
.
draglayer
.
css
({
left
:
(
pos
.
x
+
20
)
+
'px'
,
top
:
(
pos
.
y
-
5
+
(
bw
.
ie
?
document
.
documentElement
.
scrollTop
:
0
))
+
'px'
});
this
.
triggerEvent
(
'dragmove'
,
e
?
e
:
window
.
event
);
}
this
.
drag_start
=
false
;
return
false
;
},
/**
* Handler for mouse up events
*/
drag_mouse_up
:
function
(
e
)
{
document
.
onmousemove
=
null
;
if
(
e
.
type
==
'touchend'
)
{
if
(
e
.
changedTouches
.
length
!=
1
)
return
rcube_event
.
cancel
(
e
);
}
if
(
this
.
draglayer
&&
this
.
draglayer
.
is
(
':visible'
))
{
if
(
this
.
drag_start_pos
)
this
.
draglayer
.
animate
(
this
.
drag_start_pos
,
300
,
'swing'
).
hide
(
20
);
else
this
.
draglayer
.
hide
();
}
if
(
this
.
drag_active
)
this
.
focus
();
this
.
drag_active
=
false
;
rcube_event
.
remove_listener
({
event
:
'mousemove'
,
object
:
this
,
method
:
'drag_mouse_move'
});
rcube_event
.
remove_listener
({
event
:
'mouseup'
,
object
:
this
,
method
:
'drag_mouse_up'
});
if
(
bw
.
touch
)
{
rcube_event
.
remove_listener
({
event
:
'touchmove'
,
object
:
this
,
method
:
'drag_mouse_move'
});
rcube_event
.
remove_listener
({
event
:
'touchend'
,
object
:
this
,
method
:
'drag_mouse_up'
});
}
// remove temp divs
this
.
del_dragfix
();
this
.
triggerEvent
(
'dragend'
,
e
);
return
rcube_event
.
cancel
(
e
);
},
/**
* Handler for mouse move events for dragging list column
*/
column_drag_mouse_move
:
function
(
e
)
{
if
(
this
.
drag_start
)
{
// check mouse movement, of less than 3 pixels, don't start dragging
var
i
,
m
=
rcube_event
.
get_mouse_pos
(
e
);
if
(
!
this
.
drag_mouse_start
||
(
Math
.
abs
(
m
.
x
-
this
.
drag_mouse_start
.
x
)
<
3
&&
Math
.
abs
(
m
.
y
-
this
.
drag_mouse_start
.
y
)
<
3
))
return
false
;
if
(
!
this
.
col_draglayer
)
{
var
lpos
=
$
(
this
.
list
).
offset
(),
cells
=
this
.
thead
.
rows
[
0
].
cells
;
// fix layer position when list is scrolled
lpos
.
top
+=
this
.
list
.
scrollTop
+
this
.
list
.
parentNode
.
scrollTop
;
// create dragging layer
this
.
col_draglayer
=
$
(
'<div>'
).
attr
(
'id'
,
'rcmcoldraglayer'
)
.
css
(
lpos
).
css
({
position
:
'absolute'
,
'z-index'
:
2001
,
'background-color'
:
'white'
,
opacity
:
0.75
,
height
:
(
this
.
frame
.
offsetHeight
-
2
)
+
'px'
,
width
:
(
this
.
frame
.
offsetWidth
-
2
)
+
'px'
})
.
appendTo
(
document
.
body
)
// ... and column position indicator
.
append
(
$
(
'<div>'
).
attr
(
'id'
,
'rcmcolumnindicator'
)
.
css
({
position
:
'absolute'
,
'border-right'
:
'2px dotted #555'
,
'z-index'
:
2002
,
height
:
(
this
.
frame
.
offsetHeight
-
2
)
+
'px'
}));
this
.
cols
=
[];
this
.
list_pos
=
this
.
list_min_pos
=
lpos
.
left
;
// save columns positions
for
(
i
=
0
;
i
<
cells
.
length
;
i
++
)
{
this
.
cols
[
i
]
=
cells
[
i
].
offsetWidth
;
if
(
this
.
column_fixed
!==
null
&&
i
<=
this
.
column_fixed
)
{
this
.
list_min_pos
+=
this
.
cols
[
i
];
}
}
}
this
.
col_draglayer
.
show
();
this
.
col_drag_active
=
true
;
this
.
triggerEvent
(
'column_dragstart'
);
}
// set column indicator position
if
(
this
.
col_drag_active
&&
this
.
col_draglayer
)
{
var
i
,
cpos
=
0
,
pos
=
rcube_event
.
get_mouse_pos
(
e
);
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
;
}
// handle fixed columns on left
if
(
i
==
0
&&
this
.
list_min_pos
>
pos
.
x
)
cpos
=
this
.
list_min_pos
-
this
.
list_pos
;
// empty list needs some assignment
else
if
(
!
this
.
list
.
rowcount
&&
i
==
this
.
cols
.
length
)
cpos
-=
2
;
$
(
'#rcmcolumnindicator'
).
css
({
width
:
cpos
+
'px'
});
this
.
triggerEvent
(
'column_dragmove'
,
e
?
e
:
window
.
event
);
}
this
.
drag_start
=
false
;
return
false
;
},
/**
* Handler for mouse up events for dragging list columns
*/
column_drag_mouse_up
:
function
(
e
)
{
document
.
onmousemove
=
null
;
if
(
this
.
col_draglayer
)
{
(
this
.
col_draglayer
).
remove
();
this
.
col_draglayer
=
null
;
}
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
.
col_drag_active
)
{
this
.
col_drag_active
=
false
;
this
.
focus
();
this
.
triggerEvent
(
'column_dragend'
,
e
);
if
(
this
.
selected_column
!==
null
&&
this
.
cols
&&
this
.
cols
.
length
)
{
var
i
,
cpos
=
0
,
pos
=
rcube_event
.
get_mouse_pos
(
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
(
i
!=
this
.
selected_column
&&
i
!=
this
.
selected_column
+
1
)
{
this
.
column_replace
(
this
.
selected_column
,
i
);
}
}
}
return
rcube_event
.
cancel
(
e
);
},
/**
* Returns IDs of all rows in a thread (except root) for specified root
*/
row_children
:
function
(
uid
)
{
if
(
!
this
.
rows
[
uid
]
||
!
this
.
rows
[
uid
].
has_children
)
return
[];
var
res
=
[],
depth
=
this
.
rows
[
uid
].
depth
,
row
=
this
.
rows
[
uid
].
obj
.
nextSibling
;
while
(
row
)
{
if
(
row
.
nodeType
==
1
)
{
if
(
r
=
this
.
rows
[
row
.
uid
])
{
if
(
!
r
.
depth
||
r
.
depth
<=
depth
)
break
;
res
.
push
(
r
.
uid
);
}
}
row
=
row
.
nextSibling
;
}
return
res
;
},
/**
* Creates a layer for drag&drop over iframes
*/
add_dragfix
:
function
()
{
$
(
'iframe'
).
each
(
function
()
{
$
(
'<div class="iframe-dragdrop-fix"></div>'
)
.
css
({
background
:
'#fff'
,
width
:
this
.
offsetWidth
+
'px'
,
height
:
this
.
offsetHeight
+
'px'
,
position
:
'absolute'
,
opacity
:
'0.001'
,
zIndex
:
1000
})
.
css
(
$
(
this
).
offset
())
.
appendTo
(
document
.
body
);
});
},
/**
* Removes the layer for drag&drop over iframes
*/
del_dragfix
:
function
()
{
$
(
'div.iframe-dragdrop-fix'
).
remove
();
},
/**
* Replaces two columns
*/
column_replace
:
function
(
from
,
to
)
{
// only supported for <table> lists
if
(
!
this
.
thead
||
!
this
.
thead
.
rows
)
return
;
var
len
,
cells
=
this
.
thead
.
rows
[
0
].
cells
,
elem
=
cells
[
from
],
before
=
cells
[
to
],
td
=
document
.
createElement
(
'td'
);
// replace header cells
if
(
before
)
cells
[
0
].
parentNode
.
insertBefore
(
td
,
before
);
else
cells
[
0
].
parentNode
.
appendChild
(
td
);
cells
[
0
].
parentNode
.
replaceChild
(
elem
,
td
);
// replace list cells
for
(
r
=
0
,
len
=
this
.
tbody
.
rows
.
length
;
r
<
len
;
r
++
)
{
row
=
this
.
tbody
.
rows
[
r
];
elem
=
row
.
cells
[
from
];
before
=
row
.
cells
[
to
];
td
=
document
.
createElement
(
'td'
);
if
(
before
)
row
.
insertBefore
(
td
,
before
);
else
row
.
appendChild
(
td
);
row
.
replaceChild
(
elem
,
td
);
}
// update subject column position
if
(
this
.
subject_col
==
from
)
this
.
subject_col
=
to
>
from
?
to
-
1
:
to
;
else
if
(
this
.
subject_col
<
from
&&
to
<=
this
.
subject_col
)
this
.
subject_col
++
;
else
if
(
this
.
subject_col
>
from
&&
to
>=
this
.
subject_col
)
this
.
subject_col
--
;
if
(
this
.
fixed_header
)
this
.
init_header
();
this
.
triggerEvent
(
'column_replace'
);
},
subject_column
:
function
()
{
return
this
.
subject_col
+
(
this
.
checkbox_selection
?
1
:
0
);
}
};
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
=
[];
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Apr 4, 4:24 AM (42 m, 3 s ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
dd/58/88e7d2328f0cd221aadcbd005683
Default Alt Text
list.js (49 KB)
Attached To
Mode
R113 roundcubemail
Attached
Detach File
Event Timeline