You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
roundcubemail/skins/elastic/ui.js

455 lines
15 KiB
JavaScript

"use strict";
function rcube_elastic_ui()
{
var mode = 'normal', // one of: wide, normal, tablet, phone
env = {},
content_buttons = [];
var layout = {
menu: $('#layout > .menu')[0],
sidebar: $('#layout > .sidebar')[0],
list: $('#layout > .list')[0],
content: $('#layout > .content')[0],
};
var buttons = {
menu: $('a.menu-button'),
back_sidebar: $('a.back-sidebar-button'),
back_list: $('a.back-list-button'),
};
// public methods
this.register_frame_buttons = register_frame_buttons;
env.last_selected = $('#layout > div.selected')[0];
$(window).on('resize', function() {
clearTimeout(env.resize_timeout);
env.resize_timeout = setTimeout(function() { resize(); }, 25);
}).resize();
$('body').on('click', function() { if (mode == 'phone') $(layout.menu).hide(); });
// when loading content-frame in small-screen mode display it
$('#layout > .content').find('iframe').on('load', function(e) {
var show = !e.target.contentWindow.location.href.endsWith(rcmail.env.blankpage);
if (show && !$(layout.content).is(':visible')) {
env.last_selected = layout.content;
screen_resize();
}
});
// display the list widget after 'list' and 'listgroup' commands
// @TODO: plugins should be able to do the same
var list_handler = function(e) {
if (mode != 'wide') {
if (rcmail.env.task == 'addressbook' || (rcmail.env.task == 'mail' && !rcmail.env.action)) {
show_list();
}
}
// display current folder name in list header
if (rcmail.env.task == 'mail' && !rcmail.env.action) {
var name = $.type(e) == 'string' ? e : rcmail.env.mailbox;
var folder = rcmail.env.mailboxes[name];
$('#folder-name').text(folder ? folder.name : '');
}
};
rcmail.addEventListener('afterlist', list_handler);
rcmail.addEventListener('afterlistgroup', list_handler);
rcmail.addEventListener('message', message_displayed);
// menu/sidebar button
buttons.menu.on('click', function() { show_menu(); return false; });
buttons.back_sidebar.on('click', function() { show_sidebar(); return false; });
buttons.back_list.on('click', function() { hide_content(); return false; });
// Semantic-UI style
$('input.button').addClass('ui');
$('input.button.mainaction').addClass('primary');
// TODO: Most of this style-related code should not be needed
// We should implement some features in the core that would
// allow as to tell the engine to add additional html code/attribs
$('select').dropdown();
// Make forms pretty with semantic-ui's accordion widget
// TODO: Consider using tabs when the page width is big enough
$('form.propform,.tabbed').each(function() {
var form = $(this), fieldsets = form.children('fieldset');
if (fieldsets.length) {
$(this).addClass('ui styled fluid accordion');
fieldsets.each(function(i, fieldset) {
var title = $('<div>').attr('class', 'title' + (i ? '' : ' active'))
.html('<i class="dropdown icon"></i>')
.append($('<span>').text($('legend', fieldset).text()));
var content = $('<div>').attr('class', 'content' + (i ? '' : ' active'))
.append($(fieldset).children().not('legend'));
form.append(title).append(content);
$(fieldset).remove();
});
form.accordion({animateChildren: false});
}
});
// Initialize menu dropdowns
$('*[data-popup]').each(function() {
var popup_id = $(this).data('popup');
$('#' + popup_id).data('button', this);
$(this).attr({
'aria-haspopup': 'true',
'aria-expanded': 'false',
'aria-owns': popup_id + '-menu'
})
.popup({
popup: '#' + popup_id,
exclusive: true,
on: 'click',
8 years ago
position: $(this).data('popup-pos') || 'bottom left',
lastResort: true
});
// TODO: Set aria attributes on menu show/hide
// TODO: Set popup height so it is less that window height
});
$('.ui.popup').attr('aria-hidden', 'true');
// close popups on click in an iframe on the page
var close_all_popups = function() {
$('.ui.popup:visible').each(function() {
$($(this).data('button')).popup('hide');
});
};
rcube_webmail.set_iframe_events({mousedown: close_all_popups});
// Move form buttons from the content frame into the frame header (on parent window)
// TODO: There really should be only one button in most forms, Cancel buttons
// should not be used at all, other buttons will need to be verified
// TODO: Active button state
var form_buttons = [];
$('.formbuttons > .primary').each(function() {
var target = this, button = $(this).clone();
form_buttons.push(
button.attr({'onclick': '', disabled: false, id: button.attr('id') + '-clone'})
.data('target', this)
.on('click', function(e) { $(target).click(); })
);
});
if (form_buttons.length) {
if (rcmail.is_framed())
parent.UI.register_frame_buttons(form_buttons);
else
register_frame_buttons(form_buttons);
}
function register_frame_buttons(buttons)
{
// we need these buttons really only in phone mode
if (/*mode == 'phone' && */layout.content && buttons && buttons.length) {
var header = $(layout.content).children('.header');
if (header.length) {
var toolbar = header.children('.buttons');
if (!toolbar.length)
toolbar = $('<span class="buttons">').appendTo(header);
content_buttons = [];
toolbar.html('');
$.each(buttons, function() {
toolbar.append(this);
content_buttons.push(this.data('target'));
});
resize();
}
}
}
// TODO: In phone mode convert the content toolbar into "hamburger menu"
// Initialize search forms (in list headers)
$('.header > .searchbar').each(function() { searchbar_init(this); });
// Make login form pretty
if (rcmail.env.task == 'login') {
var inputs = [],
icon_map = {user: 'user', pass: 'lock', host: 'home'},
table = $('#login-form table');
$('tr', table).each(function() {
var input = $('input', this).detach(),
input_name = input.attr('name').replace('_', ''),
icon = $('<i>').attr('class', 'icon ' + icon_map[input_name]);
input.attr('placeholder', $('label', this).text());
inputs.push($('<div>').attr('class', 'ui left icon input').append([input, icon]));
});
table.after(inputs);
}
// window resize handler
function resize()
{
var size, width = $(window).width();
if (width <= 480)
size = 'phone';
else if (width > 1200)
size = 'wide';
else if (width <= 768)
size = 'tablet';
else
size = 'normal';
mode = size;
screen_resize(size);
display_screen_size(); // debug info
};
// for development only
function display_screen_size()
{
if ($('body.iframe').length)
return;
var div = $('#screen-size'), win = $(window);
if (!div.length) {
div = $('<div>').attr({
id: 'screen-size',
style: 'position:absolute;display:block;right:0;bottom:0;z-index:100;'
+ 'opacity:0.5;color:white;background-color:black;white-space:nowrap'
}).appendTo(document.body);
}
div.text(win.width() + ' x ' + win.height() + ' (' + mode + ')');
};
function screen_resize()
{
switch (mode) {
case 'phone': screen_resize_phone(); break;
case 'tablet': screen_resize_tablet(); break;
case 'normal': screen_resize_normal(); break;
case 'wide': screen_resize_wide(); break;
}
};
function screen_resize_phone()
{
screen_resize_small();
$(layout.menu).hide();
};
function screen_resize_tablet()
{
screen_resize_small();
$(layout.menu).css('display', 'flex');
};
function screen_resize_small()
{
var show, got_content = false;;
if (layout.content) {
show = got_content = layout.content === env.last_selected;
$(layout.content).css('display', show ? 'flex' : 'none');
}
if (layout.list) {
show = layout.list === env.last_selected && !got_content;
$(layout.list).css('display', show ? 'flex' : 'none');
}
if (layout.sidebar) {
show = (layout.sidebar === env.last_selected || !layout.list) && !got_content;
$(layout.sidebar).css('display', show ? 'flex' : 'none');
}
if (got_content) {
buttons.back_list.show();
}
$.each(content_buttons, function() { $(this).hide(); });
};
function screen_resize_normal()
{
var show;
if (layout.list) {
show = layout.list === env.last_selected || layout.sidebar !== env.last_selected;
$(layout.list).css('display', show ? 'flex' : 'none');
}
if (layout.sidebar) {
show = layout.sidebar === env.last_selected || !layout.list;
$(layout.sidebar).css('display', show ? 'flex' : 'none');
}
$(layout.content).css('display', 'flex');
$(layout.menu).css('display', 'flex');
buttons.back_list.hide();
$.each(content_buttons, function() { $(this).show(); });
};
function screen_resize_wide()
{
$.each(layout, function(name, item) { $(item).css('display', 'flex'); });
buttons.back_list.hide();
$.each(content_buttons, function() { $(this).show(); });
};
function show_sidebar()
{
// show sidebar and hide list
$(layout.list).hide();
$(layout.sidebar).css('display', 'flex');
};
function show_list()
{
// show list and hide sidebar
$(layout.sidebar).hide();
$(layout.list).css('display', 'flex');
};
function hide_content()
{
// show sidebar or list, hide content frame
//$(layout.content).hide();
env.last_selected = layout.list || layout.sidebar;
screen_resize();
// reset content frame, so we can load it again
rcmail.show_contentframe(false);
// or env.content_window.location.href = rcmail.env.blankpage; ?
// now we have to unselect selected row on the list
// TODO: do this with some magic, so it works for plugins UI
// e.g. we could store list widget reference in list container data
if (rcmail.env.task == 'settings' && !rcmail.env.action) {
rcmail.sections_list.clear_selection();
}
else if (rcmail.env.task == 'addressbook' && !rcmail.env.action) {
rcmail.contact_list.clear_selection();
}
};
function show_menu()
{
// show menu widget
var menu = $(layout.menu),
phone_display = menu.is(':visible') && mode == 'phone' ? 'none' : 'block';
menu.css('display', mode == 'phone' ? phone_display : 'flex');
};
/**
* Triggered when a UI message is displayed
*/
function message_displayed(p)
{
var icon, classes = 'ui icon message',
map = {
information: ['success', 'info circle icon'],
confirmation: ['success', 'info circle icon'],
notice: ['', 'info circle icon'],
error: ['negative', 'warning circle icon'],
warning: ['negative', 'warning sign icon'],
loading: ['', 'notched circle loading icon']
};
if (icon = map[p.type]) {
if (icon[0])
classes += ' ' + icon[0];
$('<i>').attr('class', icon[1]).prependTo(p.object);
}
$(p.object).addClass(classes);
/*
var siblings = $(p.object).siblings('div');
if (siblings.length)
$(p.object).insertBefore(siblings.first());
// show a popup dialog on errors
if (p.type == 'error' && rcmail.env.task != 'login') {
// hide original message object, we don't want both
rcmail.hide_message(p.object);
}
*/
};
/**
* Initializes searchbar widget
*/
function searchbar_init(bar)
{
var input = $('input', bar),
button = $('a.button.search', bar),
form = $('form', bar),
all_elements = $('form, a.button.options, a.button.reset', bar),
is_search_pending = function() {
// TODO: This have to be improved to detect real searching state
// There are cases when search is active but the input is empty
return input.val();
},
hide_func = function() {
$(bar).animate({'width': '0'}, 200, 'swing', function() {
all_elements.hide();
button[is_search_pending() ? 'addClass' : 'removeClass']('active')
.css('display', 'inline-block')
.focus();
});
};
if (is_search_pending()) {
button.addClass('active');
}
// Display search form (with animation effect)
button.on('click', function() {
$(bar).animate({'width': '100%'}, 200);
all_elements.css('display', 'table-cell');
button.hide();
input.focus();
});
// Search reset action
$('a.button.reset', bar).on('click', function() {
// for treelist widget's search setting val and keyup.treelist is needed
// in normal search form reset-search command will do the trick
// TODO: This calls for some generalization, what about two searchboxes on a page?
input.val('').change().trigger('keyup.treelist', {keyCode: 27});
hide_func();
});
// These will hide the form, but not reset it
rcube_webmail.set_iframe_events({mousedown: hide_func});
$('body').on('mousedown', function(e) {
if (!button.is(':visible') && $.inArray(bar, $(e.target).parents()) == -1) {
hide_func();
}
});
};
}
var UI = new rcube_elastic_ui();