Speed improvements for planner view

This commit is contained in:
nathangray 2017-02-24 08:31:39 -07:00
parent ae4eeb6cee
commit 6bccec1e0c
4 changed files with 277 additions and 99 deletions

View File

@ -71,6 +71,11 @@ var et2_calendar_event = (function(){ "use strict"; return et2_valueWidget.exten
.addClass(this.options.class) .addClass(this.options.class)
.css('width',this.options.width) .css('width',this.options.width)
.on('mouseenter', function() { .on('mouseenter', function() {
// Bind actions on first mouseover for faster creation
if(event._need_actions_linked)
{
event._copy_parent_actions();
}
// Tooltip // Tooltip
if(!event._tooltipElem) if(!event._tooltipElem)
{ {
@ -108,6 +113,8 @@ var et2_calendar_event = (function(){ "use strict"; return et2_valueWidget.exten
.appendTo(this.title); .appendTo(this.title);
this.setDOMNode(this.div[0]); this.setDOMNode(this.div[0]);
this._need_actions_linked = false;
}, },
doLoadingFinished: function() { doLoadingFinished: function() {
@ -243,23 +250,7 @@ var et2_calendar_event = (function(){ "use strict"; return et2_valueWidget.exten
this._actionObject.id = 'calendar::' + id; this._actionObject.id = 'calendar::' + id;
} }
// Copy actions set in parent this._need_actions_linked = true;
if(!this.options.readonly && !this._parent.options.readonly)
{
var action_parent = this;
while(action_parent != null && !action_parent.options.actions &&
!action_parent.instanceOf(et2_container)
)
{
action_parent = action_parent.getParent();
}
try {
this._link_actions(action_parent.options.actions||{});
} catch (e) {
// something went wrong, but keep quiet about it
debugger;
}
}
// Make sure category stuff is there // Make sure category stuff is there
// Fake it to use the cache / call - if already there, these will return // Fake it to use the cache / call - if already there, these will return
@ -398,6 +389,9 @@ var et2_calendar_event = (function(){ "use strict"; return et2_valueWidget.exten
if(this.options.value.whole_day_on_top) return; if(this.options.value.whole_day_on_top) return;
// Skip for planner view, it's always small
if(this._parent && this._parent.instanceOf(et2_calendar_planner_row)) return;
// Pre-calculation reset // Pre-calculation reset
this.div.removeClass('calendar_calEventSmall'); this.div.removeClass('calendar_calEventSmall');
this.body.css('height', 'auto'); this.body.css('height', 'auto');
@ -861,6 +855,32 @@ var et2_calendar_event = (function(){ "use strict"; return et2_valueWidget.exten
et2_calendar_event.series_split_prompt(this.options.value,this.options.value.recur_date, callback); et2_calendar_event.series_split_prompt(this.options.value,this.options.value.recur_date, callback);
}, },
/**
* Copy the actions set on the parent, apply them to self
*
* This can take a while to do, so we try to do it only when needed - on mouseover
*/
_copy_parent_actions: function()
{
// Copy actions set in parent
if(!this.options.readonly && !this._parent.options.readonly)
{
var action_parent = this;
while(action_parent != null && !action_parent.options.actions &&
!action_parent.instanceOf(et2_container)
)
{
action_parent = action_parent.getParent();
}
try {
this._link_actions(action_parent.options.actions||{});
this._need_actions_linked = false;
} catch (e) {
// something went wrong, but keep quiet about it
}
}
},
/** /**
* Link the actions to the DOM nodes / widget bits. * Link the actions to the DOM nodes / widget bits.
* *

View File

@ -64,6 +64,8 @@ var et2_calendar_planner = (function(){ "use strict"; return et2_calendar_view.e
} }
}, },
DEFERRED_ROW_TIME: 100,
/** /**
* Constructor * Constructor
* *
@ -108,6 +110,8 @@ var et2_calendar_planner = (function(){ "use strict"; return et2_calendar_view.e
this.setDOMNode(this.div[0]); this.setDOMNode(this.div[0]);
this.registeredCallbacks = []; this.registeredCallbacks = [];
this.cache = {};
this._deferred_row_updates = {};
}, },
destroy: function() { destroy: function() {
@ -133,6 +137,9 @@ var et2_calendar_planner = (function(){ "use strict"; return et2_calendar_view.e
// - no action system - // - no action system -
var planner = this; var planner = this;
this.cache = {};
this._deferred_row_updates = {};
/** /**
* If user puts the mouse over an event, then we'll set up resizing so * If user puts the mouse over an event, then we'll set up resizing so
* they can adjust the length. Should be a little better on resources * they can adjust the length. Should be a little better on resources
@ -502,7 +509,20 @@ var et2_calendar_planner = (function(){ "use strict"; return et2_calendar_view.e
draw_row: function(sort_key, label, events) { draw_row: function(sort_key, label, events) {
if(['user','both'].indexOf(egw.preference('planner_show_empty_rows','calendar')) !== -1 || events.length) if(['user','both'].indexOf(egw.preference('planner_show_empty_rows','calendar')) !== -1 || events.length)
{ {
return this._drawRow(sort_key, label,events,this.options.start_date, this.options.end_date); var row = this._drawRow(sort_key, label,events,this.options.start_date, this.options.end_date);
// Since the daywise cache is by user, we can tap in here
var t = new Date(this.options.start_date);
var end = new Date(this.options.end_date);
do
{
var cache_id = app.classes.calendar._daywise_cache_id(t, sort_key);
egw.dataRegisterUID(cache_id, row._data_callback, row);
t.setUTCDate(t.getUTCDate() + 1);
}
while(t < end);
return row;
} }
} }
}, },
@ -767,7 +787,10 @@ var et2_calendar_planner = (function(){ "use strict"; return et2_calendar_view.e
// Show AJAX loader // Show AJAX loader
this.widget.loader.show(); this.widget.loader.show();
this.widget.value = this.widget._fetch_data(); this.widget.cache = {};
this._deferred_row_updates = {};
this.widget._fetch_data();
this.widget._drawGrid(); this.widget._drawGrid();
@ -849,7 +872,7 @@ var et2_calendar_planner = (function(){ "use strict"; return et2_calendar_view.e
.append(this.grid); .append(this.grid);
this.grid.empty(); this.grid.empty();
var grouper = this.groupers[isNaN(this.options.group_by) ? this.options.group_by : 'category']; var grouper = this.grouper;
if(!grouper) return; if(!grouper) return;
// Headers // Headers
@ -903,6 +926,14 @@ var et2_calendar_planner = (function(){ "use strict"; return et2_calendar_view.e
{ {
this.gridHeader.css('margin-right', (this.rows.width() - this.rows.children().last().width()) + 'px'); this.gridHeader.css('margin-right', (this.rows.width() - this.rows.children().last().width()) + 'px');
} }
// Add actual events
for(var key in this._deferred_row_updates)
{
window.clearTimeout(key);
}
window.setTimeout(jQuery.proxy(function() {
this._deferred_row_update();
}, this ),this.DEFERRED_ROW_TIME)
this.value = []; this.value = [];
}, },
@ -932,11 +963,6 @@ var et2_calendar_planner = (function(){ "use strict"; return et2_calendar_view.e
row.doLoadingFinished(); row.doLoadingFinished();
} }
// Add actual events
window.setTimeout(jQuery.proxy(function() {
this.row._update_events(this.events);
}, {row: row, events: events} ),0)
return row; return row;
}, },
@ -1720,76 +1746,167 @@ var et2_calendar_planner = (function(){ "use strict"; return et2_calendar_view.e
var end = new Date(this.options.end_date); var end = new Date(this.options.end_date);
do do
{ {
// Cache is by date (and owner, if seperate) value = value.concat(this._cache_register(t, this.options.owner, last_data));
var date = t.getUTCFullYear() + sprintf('%02d',t.getUTCMonth()+1) + sprintf('%02d',t.getUTCDate());
var cache_id = app.classes.calendar._daywise_cache_id(date, this.options.owner);
if(egw.dataHasUID(cache_id))
{
var c = egw.dataGetUIDdata(cache_id);
if(c.data && c.data !== null)
{
// There is data, pass it along now
for(var j = 0; j < c.data.length; j++)
{
if(last_data.indexOf(c.data[j]) === -1 && egw.dataHasUID('calendar::'+c.data[j]))
{
value.push(egw.dataGetUIDdata('calendar::'+c.data[j]).data);
}
}
last_data = c.data;
}
}
else
{
fetch = true;
// Assume it's empty, if there is data it will be filled later
egw.dataStoreUID(cache_id, []);
}
this.registeredCallbacks.push(cache_id);
egw.dataRegisterUID(cache_id, function(data) {
if(data && data.length)
{
// If displaying by category, we need the infolog (or other app) categories too
var im = this.getInstanceManager();
for(var i = 0; i < data.length && this.options.group_by == 'category'; i++)
{
var event = egw.dataGetUIDdata('calendar::'+data[i]);
if(event && event.data && event.data.app)
{
// Fake it to use the cache / call
et2_selectbox.cat_options({
_type:'select-cat',
getInstanceManager: function() {return im;}
}, {application:event.data.app||'calendar'});
// Get CSS too
egw.includeCSS('/api/categories.php?app='+event.data.app);
}
}
this.invalidate(false);
}
}, this, this.getInstanceManager().execId,this.id);
t.setUTCDate(t.getUTCDate() + 1); t.setUTCDate(t.getUTCDate() + 1);
} }
while(t < end); while(t < end);
// Need to get some more from the server
if(fetch && app.calendar)
{
app.calendar._fetch_data({
first: this.options.start_date,
last: this.options.end_date,
owner: this.options.owner,
filter: this.options.filter
}, this.getInstanceManager());
}
this.doInvalidate = true; this.doInvalidate = true;
return value; return value;
}, },
/**
* Deal with registering for data cache
*
* @param Date t
* @param String owner Calendar owner
*/
_cache_register: function _cache_register(t, owner, last_data)
{
// Cache is by date (and owner, if seperate)
var date = t.getUTCFullYear() + sprintf('%02d',t.getUTCMonth()+1) + sprintf('%02d',t.getUTCDate());
var cache_id = app.classes.calendar._daywise_cache_id(date, owner);
var value = [];
if(egw.dataHasUID(cache_id))
{
var c = egw.dataGetUIDdata(cache_id);
if(c.data && c.data !== null)
{
// There is data, pass it along now
for(var j = 0; j < c.data.length; j++)
{
if(last_data.indexOf(c.data[j]) === -1 && egw.dataHasUID('calendar::'+c.data[j]))
{
value.push(egw.dataGetUIDdata('calendar::'+c.data[j]).data);
}
}
last_data = c.data;
}
}
else
{
fetch = true;
// Assume it's empty, if there is data it will be filled later
egw.dataStoreUID(cache_id, []);
}
this.registeredCallbacks.push(cache_id);
egw.dataRegisterUID(cache_id, function(data) {
if(data && data.length)
{
var invalidate = true;
// Try to determine rows interested
var labels = [];
var events = {};
if(this.grouper)
{
labels = this.grouper.row_labels.call(this);
invalidate = false;
}
var im = this.getInstanceManager();
for(var i = 0; i < data.length; i++)
{
var event = egw.dataGetUIDdata('calendar::'+data[i]);
// Try to determine rows interested
if(event && event.data && this.grouper)
{
this.grouper.group.call(this, labels, events, event.data);
}
if(Object.keys(events).length > 0 )
{
for(var label_id in events)
{
var id = ""+labels[label_id].id;
if(typeof this.cache[id] === 'undefined')
{
this.cache[id] = [];
}
if(this.cache[id].indexOf(event.data.row_id) === -1 && (
event.data.participants[id] && this.options.group_by === 'user' ||
event.data.category === id && this.options.group_by === 'category'
))
{
this.cache[id].push(event.data.row_id);
}
if (this._deferred_row_updates[id])
{
window.clearTimeout(this._deferred_row_updates[id]);
}
this._deferred_row_updates[id] = window.setTimeout(jQuery.proxy(this._deferred_row_update,this,id),this.DEFERRED_ROW_TIME);
}
}
else
{
// Could be an event no row is interested in, could be a problem.
// Just redraw everything
invalidate = true;
break;
}
// If displaying by category, we need the infolog (or other app) categories too
if(event && event.data && event.data.app && this.options.group_by == 'category')
{
// Fake it to use the cache / call
et2_selectbox.cat_options({
_type:'select-cat',
getInstanceManager: function() {return im;}
}, {application:event.data.app||'calendar'});
// Get CSS too
egw.includeCSS('/api/categories.php?app='+event.data.app);
}
}
if(invalidate)
{
this.invalidate(false);
}
}
}, this, this.getInstanceManager().execId,this.id);
return value;
},
/**
* Because users may be participants in various events and the time it takes
* to create many events, we don't want to update a row too soon - we may have
* to re-draw it if we find the user / category in another event. Pagination
* makes this worse. We wait a bit before updating the row to avoid
* having to re-draw it multiple times.
*
* @param {type} id
* @returns {undefined}
*/
_deferred_row_update: function(id) {
// Something's in progress, skip
if(!this.doInvalidate) return;
var id_list = typeof id === 'undefined' ? Object.keys(this.cache) : [id];
for(var i = 0; i < id_list.length; i++)
{
var cache_id = id_list[i];
var row = this.getWidgetById('planner_row_'+cache_id);
window.clearTimeout(this._deferred_row_updates[cache_id]);
delete this._deferred_row_updates[cache_id];
if(row)
{
row._data_callback(this.cache[cache_id]);
}
else
{
break;
}
}
},
/** /**
* Provide specific data to be displayed. * Provide specific data to be displayed.
* This is a way to set start and end dates, owner and event data in once call. * This is a way to set start and end dates, owner and event data in once call.
@ -1802,7 +1919,7 @@ var et2_calendar_planner = (function(){ "use strict"; return et2_calendar_view.e
* Days should be in order. * Days should be in order.
* *
*/ */
set_value: function(events) set_value: function set_value(events)
{ {
if(typeof events !== 'object') return false; if(typeof events !== 'object') return false;
@ -1849,7 +1966,7 @@ var et2_calendar_planner = (function(){ "use strict"; return et2_calendar_view.e
* @param {string|number} group_by 'user', 'month', or an integer category ID * @param {string|number} group_by 'user', 'month', or an integer category ID
* @returns {undefined} * @returns {undefined}
*/ */
set_group_by: function(group_by) set_group_by: function set_group_by(group_by)
{ {
if(isNaN(group_by) && typeof this.groupers[group_by] === 'undefined') if(isNaN(group_by) && typeof this.groupers[group_by] === 'undefined')
{ {
@ -1858,6 +1975,8 @@ var et2_calendar_planner = (function(){ "use strict"; return et2_calendar_view.e
var old = this.options.group_by; var old = this.options.group_by;
this.options.group_by = ''+group_by; this.options.group_by = ''+group_by;
this.grouper = this.groupers[isNaN(this.options.group_by) ? this.options.group_by : 'category'];
if(old !== this.options.group_by && this.isAttached()) if(old !== this.options.group_by && this.isAttached())
{ {
this.invalidate(true); this.invalidate(true);
@ -1869,7 +1988,7 @@ var et2_calendar_planner = (function(){ "use strict"; return et2_calendar_view.e
* *
* @param {boolean} weekends * @param {boolean} weekends
*/ */
set_show_weekend: function(weekends) set_show_weekend: function set_show_weekend(weekends)
{ {
weekends = weekends ? true : false; weekends = weekends ? true : false;
if(this.options.show_weekend !== weekends) if(this.options.show_weekend !== weekends)

View File

@ -67,7 +67,7 @@ var et2_calendar_planner_row = (function(){ "use strict"; return et2_valueWidget
this.set_end_date(this.options.end_date); this.set_end_date(this.options.end_date);
this._cached_rows = []; this._cached_rows = [];
this._row_height = 20;
}, },
doLoadingFinished: function() { doLoadingFinished: function() {
@ -114,7 +114,6 @@ var et2_calendar_planner_row = (function(){ "use strict"; return et2_valueWidget
var parent = objectManager.getObjectById(this.id,1) || objectManager.getObjectById(this._parent.id,1) || objectManager; var parent = objectManager.getObjectById(this.id,1) || objectManager.getObjectById(this._parent.id,1) || objectManager;
if(!parent) if(!parent)
{ {
debugger;
egw.debug('error','No parent objectManager found'); egw.debug('error','No parent objectManager found');
return; return;
} }
@ -436,6 +435,39 @@ var et2_calendar_planner_row = (function(){ "use strict"; return et2_valueWidget
return content; return content;
}, },
/**
* Callback used when the daywise data changes
*
* Events should update themselves when their data changes, here we are
* dealing with a change in which events are displayed on this row.
*
* @param {String[]} event_ids
* @returns {undefined}
*/
_data_callback: function(event_ids) {
var events = [];
if(event_ids == null || typeof event_ids.length == 'undefined') event_ids = [];
for(var i = 0; i < event_ids.length; i++)
{
var event = egw.dataGetUIDdata('calendar::'+event_ids[i]);
event = event && event.data || false;
if(event && event.date)
{
events.push(event);
}
else if (event)
{
// Got an ID that doesn't belong
event_ids.splice(i--,1);
}
}
if(!this._parent.disabled)
{
this.resize();
this._update_events(events);
}
},
/** /**
* Load the event data for this day and create event widgets for each. * Load the event data for this day and create event widgets for each.
* *
@ -446,10 +478,11 @@ var et2_calendar_planner_row = (function(){ "use strict"; return et2_valueWidget
_update_events: function(events) _update_events: function(events)
{ {
// Remove all events // Remove all events
while(this._children.length) while(this._children.length > 0)
{ {
this._children[this._children.length-1].free(); var node = this._children[this._children.length-1];
this.removeChild(this._children[this._children.length-1]); this.removeChild(node);
node.free();
} }
this._cached_rows = []; this._cached_rows = [];
@ -483,10 +516,8 @@ var et2_calendar_planner_row = (function(){ "use strict"; return et2_valueWidget
position_event: function(event) position_event: function(event)
{ {
var rows = this._spread_events(); var rows = this._spread_events();
var row = jQuery('<div class="calendar_plannerEventRowWidget"></div>').appendTo(this.rows); var height = rows.length * this._row_height;
var height = rows.length * (parseInt(window.getComputedStyle(row[0]).getPropertyValue("height")) || 20);
var row_width = this.rows.width(); var row_width = this.rows.width();
row.remove();
for(var c = 0; c < rows.length; c++) for(var c = 0; c < rows.length; c++)
{ {
@ -725,7 +756,9 @@ var et2_calendar_planner_row = (function(){ "use strict"; return et2_valueWidget
return; return;
} }
this.position_event(); var row = jQuery('<div class="calendar_plannerEventRowWidget"></div>').appendTo(this.rows);
this._row_height = (parseInt(window.getComputedStyle(row[0]).getPropertyValue("height")) || 20);
row.remove();
} }
});}).call(this); });}).call(this);

View File

@ -246,7 +246,13 @@ var et2_calendar_view = (function(){ "use strict"; return et2_valueWidget.extend
_owner = jQuery.extend([],_owner); _owner = jQuery.extend([],_owner);
} }
this.options.owner = _owner; this.options.owner = _owner;
if(old !== this.options.owner && this.isAttached()) if(this.isAttached() && (
typeof old === "number" && typeof _owner === "number" && old !== this.options.owner ||
// Array of ids will not compare as equal
((typeof old === 'object' || typeof _owner === 'object') && old.toString() !== _owner.toString()) ||
// Strings
typeof old === 'string' && ''+old !== ''+this.options.owner
))
{ {
this.invalidate(true); this.invalidate(true);
} }