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)
.css('width',this.options.width)
.on('mouseenter', function() {
// Bind actions on first mouseover for faster creation
if(event._need_actions_linked)
{
event._copy_parent_actions();
}
// Tooltip
if(!event._tooltipElem)
{
@ -108,6 +113,8 @@ var et2_calendar_event = (function(){ "use strict"; return et2_valueWidget.exten
.appendTo(this.title);
this.setDOMNode(this.div[0]);
this._need_actions_linked = false;
},
doLoadingFinished: function() {
@ -243,23 +250,7 @@ var et2_calendar_event = (function(){ "use strict"; return et2_valueWidget.exten
this._actionObject.id = 'calendar::' + id;
}
// 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||{});
} catch (e) {
// something went wrong, but keep quiet about it
debugger;
}
}
this._need_actions_linked = true;
// Make sure category stuff is there
// 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;
// Skip for planner view, it's always small
if(this._parent && this._parent.instanceOf(et2_calendar_planner_row)) return;
// Pre-calculation reset
this.div.removeClass('calendar_calEventSmall');
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);
},
/**
* 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.
*

View File

@ -64,6 +64,8 @@ var et2_calendar_planner = (function(){ "use strict"; return et2_calendar_view.e
}
},
DEFERRED_ROW_TIME: 100,
/**
* Constructor
*
@ -108,6 +110,8 @@ var et2_calendar_planner = (function(){ "use strict"; return et2_calendar_view.e
this.setDOMNode(this.div[0]);
this.registeredCallbacks = [];
this.cache = {};
this._deferred_row_updates = {};
},
destroy: function() {
@ -133,6 +137,9 @@ var et2_calendar_planner = (function(){ "use strict"; return et2_calendar_view.e
// - no action system -
var planner = this;
this.cache = {};
this._deferred_row_updates = {};
/**
* 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
@ -502,7 +509,20 @@ var et2_calendar_planner = (function(){ "use strict"; return et2_calendar_view.e
draw_row: function(sort_key, label, events) {
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
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();
@ -849,7 +872,7 @@ var et2_calendar_planner = (function(){ "use strict"; return et2_calendar_view.e
.append(this.grid);
this.grid.empty();
var grouper = this.groupers[isNaN(this.options.group_by) ? this.options.group_by : 'category'];
var grouper = this.grouper;
if(!grouper) return;
// 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');
}
// 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 = [];
},
@ -932,11 +963,6 @@ var et2_calendar_planner = (function(){ "use strict"; return et2_calendar_view.e
row.doLoadingFinished();
}
// Add actual events
window.setTimeout(jQuery.proxy(function() {
this.row._update_events(this.events);
}, {row: row, events: events} ),0)
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);
do
{
// 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, 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);
value = value.concat(this._cache_register(t, this.options.owner, last_data));
t.setUTCDate(t.getUTCDate() + 1);
}
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;
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.
* 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.
*
*/
set_value: function(events)
set_value: function set_value(events)
{
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
* @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')
{
@ -1858,6 +1975,8 @@ var et2_calendar_planner = (function(){ "use strict"; return et2_calendar_view.e
var old = this.options.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())
{
this.invalidate(true);
@ -1869,7 +1988,7 @@ var et2_calendar_planner = (function(){ "use strict"; return et2_calendar_view.e
*
* @param {boolean} weekends
*/
set_show_weekend: function(weekends)
set_show_weekend: function set_show_weekend(weekends)
{
weekends = weekends ? true : false;
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._cached_rows = [];
this._row_height = 20;
},
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;
if(!parent)
{
debugger;
egw.debug('error','No parent objectManager found');
return;
}
@ -436,6 +435,39 @@ var et2_calendar_planner_row = (function(){ "use strict"; return et2_valueWidget
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.
*
@ -446,10 +478,11 @@ var et2_calendar_planner_row = (function(){ "use strict"; return et2_valueWidget
_update_events: function(events)
{
// Remove all events
while(this._children.length)
while(this._children.length > 0)
{
this._children[this._children.length-1].free();
this.removeChild(this._children[this._children.length-1]);
var node = this._children[this._children.length-1];
this.removeChild(node);
node.free();
}
this._cached_rows = [];
@ -483,10 +516,8 @@ var et2_calendar_planner_row = (function(){ "use strict"; return et2_valueWidget
position_event: function(event)
{
var rows = this._spread_events();
var row = jQuery('<div class="calendar_plannerEventRowWidget"></div>').appendTo(this.rows);
var height = rows.length * (parseInt(window.getComputedStyle(row[0]).getPropertyValue("height")) || 20);
var height = rows.length * this._row_height;
var row_width = this.rows.width();
row.remove();
for(var c = 0; c < rows.length; c++)
{
@ -725,7 +756,9 @@ var et2_calendar_planner_row = (function(){ "use strict"; return et2_valueWidget
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);

View File

@ -246,7 +246,13 @@ var et2_calendar_view = (function(){ "use strict"; return et2_valueWidget.extend
_owner = jQuery.extend([],_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);
}