mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-11-17 05:23:13 +01:00
f0ebb448a2
- Client side event caching - Home fixes
1386 lines
37 KiB
JavaScript
1386 lines
37 KiB
JavaScript
/*
|
|
* Egroupware Calendar timegrid
|
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
|
* @package etemplate
|
|
* @subpackage api
|
|
* @link http://www.egroupware.org
|
|
* @author Nathan Gray
|
|
* @version $Id$
|
|
*/
|
|
|
|
|
|
"use strict";
|
|
|
|
/*egw:uses
|
|
/etemplate/js/et2_core_valueWidget;
|
|
/calendar/js/et2_widget_planner_row.js;
|
|
/calendar/js/et2_widget_event.js;
|
|
*/
|
|
|
|
/**
|
|
* Class which implements the "calendar-planner" XET-Tag for displaying a longer
|
|
* ( > 10 days) span of time
|
|
*
|
|
* @augments et2_valueWidget
|
|
* @class
|
|
*/
|
|
var et2_calendar_planner = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResizeable],
|
|
{
|
|
createNamespace: true,
|
|
|
|
attributes: {
|
|
start_date: {
|
|
name: "Start date",
|
|
type: "any"
|
|
},
|
|
end_date: {
|
|
name: "End date",
|
|
type: "any"
|
|
},
|
|
group_by: {
|
|
name: "Group by",
|
|
type: "string", // or category ID
|
|
default: "0",
|
|
description: "Display planner by 'user', 'month', or the given category"
|
|
},
|
|
owner: {
|
|
name: "Owner",
|
|
type: "any", // Integer, or array of integers
|
|
default: 0,
|
|
description: "Account ID number of the calendar owner, if not the current user"
|
|
},
|
|
filter: {
|
|
name: "Filter",
|
|
type: "string",
|
|
default: '',
|
|
description: 'A filter that is used to select events. It is passed along when events are queried.'
|
|
},
|
|
value: {
|
|
type: "any",
|
|
description: "A list of events, optionally you can set start_date, end_date and group_by as keys and events will be fetched"
|
|
},
|
|
"onchange": {
|
|
"name": "onchange",
|
|
"type": "js",
|
|
"default": et2_no_init,
|
|
"description": "JS code which is executed when the date range changes."
|
|
},
|
|
"onevent_change": {
|
|
"name": "onevent_change",
|
|
"type": "js",
|
|
"default": et2_no_init,
|
|
"description": "JS code which is executed when an event changes."
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @memberOf et2_calendar_planner
|
|
* @constructor
|
|
*/
|
|
init: function() {
|
|
this._super.apply(this, arguments);
|
|
|
|
// Main container
|
|
this.div = $j(document.createElement("div"))
|
|
.addClass("calendar_plannerWidget");
|
|
|
|
// Header
|
|
this.gridHeader = $j(document.createElement("div"))
|
|
.addClass("calendar_plannerHeader")
|
|
.appendTo(this.div);
|
|
this.headerTitle = $j(document.createElement("div"))
|
|
.addClass("calendar_plannerHeaderTitle")
|
|
.appendTo(this.gridHeader);
|
|
this.headers = $j(document.createElement("div"))
|
|
.addClass("calendar_plannerHeaderRows")
|
|
.appendTo(this.gridHeader);
|
|
|
|
this.rows = $j(document.createElement("div"))
|
|
.appendTo(this.div);
|
|
|
|
// Used for its date calculations
|
|
this.date_helper = et2_createWidget('date-time',{},null);
|
|
this.date_helper.loadingFinished();
|
|
|
|
this.value = [];
|
|
|
|
// Update timer, to avoid redrawing twice when changing start & end date
|
|
this.update_timer = null;
|
|
|
|
this.setDOMNode(this.div[0]);
|
|
},
|
|
|
|
destroy: function() {
|
|
this._super.apply(this, arguments);
|
|
this.div.off();
|
|
|
|
// date_helper has no parent, so we must explicitly remove it
|
|
this.date_helper.destroy();
|
|
this.date_helper = null;
|
|
|
|
// Stop the invalidate timer
|
|
if(this.update_timer)
|
|
{
|
|
window.clearTimeout(this.update_timer);
|
|
}
|
|
},
|
|
|
|
doLoadingFinished: function() {
|
|
this._super.apply(this, arguments);
|
|
|
|
// Don't bother to draw anything if there's no date yet
|
|
if(this.options.start_date)
|
|
{
|
|
this._drawGrid();
|
|
}
|
|
|
|
// Actions may be set on a parent, so we need to explicitly get in here
|
|
// and get ours
|
|
this._link_actions(this.options.actions || this._parent.options.actions || []);
|
|
|
|
return true;
|
|
},
|
|
|
|
/**
|
|
* These handle the differences between the different group types.
|
|
* They provide the different titles, labels and grouping
|
|
*/
|
|
groupers: {
|
|
// Group by user has one row for each user
|
|
user:
|
|
{
|
|
// Title in top left corner
|
|
title: function() { return this.egw().lang('User');},
|
|
// Column headers
|
|
headers: function() {
|
|
var start = new Date(this.options.start_date);
|
|
var end = new Date(this.options.end_date);
|
|
var start_date = new Date(start.getUTCFullYear(), start.getUTCMonth(),start.getUTCDate());
|
|
var end_date = new Date(end.getUTCFullYear(), end.getUTCMonth(),end.getUTCDate());
|
|
var day_count = Math.round((end_date - start_date) /(1000*3600*24))+1;
|
|
if(day_count >= 28)
|
|
{
|
|
this.headers.append(this._header_months(start, day_count));
|
|
}
|
|
if(day_count >= 5)
|
|
{
|
|
this.headers.append(this._header_weeks(start, day_count));
|
|
}
|
|
this.headers.append(this._header_days(start, day_count));
|
|
if(day_count <= 7)
|
|
{
|
|
this.headers.append(this._header_hours(start, day_count));
|
|
}
|
|
},
|
|
// Labels for the rows
|
|
row_labels: function() {
|
|
var labels = {};
|
|
var accounts = egw.accounts();
|
|
for(var i = 0; i < this.options.owner.length; i++)
|
|
{
|
|
var user = this.options.owner[i];
|
|
if(parseInt(user) === 0)
|
|
{
|
|
// 0 means current user
|
|
user = egw.user('account_id');
|
|
}
|
|
if (isNaN(user)) // resources
|
|
{
|
|
labels[user] = egw.link_title('resources',user.match(/\d+/)[0],function(name) {this[user] = name;},labels);
|
|
}
|
|
else if (user < 0) // groups
|
|
{
|
|
egw.accountData(user,'account_fullname',true,function(result) {
|
|
for(var id in result)
|
|
{
|
|
this[id] = result[id];
|
|
}
|
|
},labels);
|
|
}
|
|
else // users
|
|
{
|
|
user = parseInt(user)
|
|
for(var i = 0; i < accounts.length; i++)
|
|
{
|
|
if(accounts[i].value === user)
|
|
{
|
|
labels[user] = accounts[i].label;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return labels;
|
|
},
|
|
// Group the events into the rows
|
|
group: function(labels, rows, event) {
|
|
// convert filter to allowed status
|
|
var status_to_show = ['U','A','T','D','G'];
|
|
switch(this.options.filter)
|
|
{
|
|
case 'unknown':
|
|
status_to_show = ['U','G']; break;
|
|
case 'accepted':
|
|
status_to_show = ['A']; break;
|
|
case 'tentative':
|
|
status_to_show = ['T']; break;
|
|
case 'rejected':
|
|
status_to_show = ['R']; break;
|
|
case 'delegated':
|
|
status_to_show = ['D']; break;
|
|
case 'all':
|
|
status_to_show = ['U','A','T','D','G','R']; break;
|
|
default:
|
|
status_to_show = ['U','A','T','D','G']; break;
|
|
}
|
|
for(var user in event.participants)
|
|
{
|
|
var participant = event.participants[user];
|
|
if(participant && typeof labels[user] !== 'undefined' && status_to_show.indexOf(participant.substr(0,1)) >= 0 ||
|
|
this.options.filter === 'owner' && event.owner === user)
|
|
{
|
|
if(typeof rows[user] === 'undefined')
|
|
{
|
|
rows[user] = [];
|
|
}
|
|
rows[user].push(event);
|
|
}
|
|
}
|
|
},
|
|
// Draw a single row
|
|
draw_row: function(sort_key, label, events) {
|
|
return this._drawRow(sort_key, label,events,this.options.start_date, this.options.end_date);
|
|
}
|
|
},
|
|
|
|
// Group by month has one row for each month
|
|
month:
|
|
{
|
|
title: function() { return this.egw().lang('Month');},
|
|
headers: function() {
|
|
this.headers.append(this._header_day_of_month());
|
|
},
|
|
row_labels: function() {
|
|
var labels = {};
|
|
var d = new Date(this.options.start_date);
|
|
d = new Date(d.valueOf() + d.getTimezoneOffset() * 60 * 1000);
|
|
for(var i = 0; i < 12; i++)
|
|
{
|
|
labels[d.getUTCFullYear() +'-'+d.getUTCMonth()] = egw.lang(date('F',d))+' '+d.getUTCFullYear();
|
|
d.setUTCMonth(d.getUTCMonth()+1);
|
|
}
|
|
return labels;
|
|
},
|
|
group: function(labels, rows,event) {
|
|
var start = new Date(event.start);
|
|
var key = start.getUTCFullYear() +'-'+start.getUTCMonth();
|
|
if(typeof rows[key] === 'undefined')
|
|
{
|
|
rows[key] = [];
|
|
}
|
|
rows[key].push(event);
|
|
|
|
// end in a different month?
|
|
var end = new Date(event.end);
|
|
var end_key = end.getUTCFullYear() +'-'+end.getUTCMonth();
|
|
while(key !== end_key)
|
|
{
|
|
var year = start.getUTCFullYear();
|
|
var month = start.getUTCMonth();
|
|
if (++month > 12)
|
|
{
|
|
++year;
|
|
month = 1;
|
|
}
|
|
key = sprintf('%04d-%02d',year,month);
|
|
rows[key].push(event);
|
|
}
|
|
},
|
|
// Draw a single row, but split up the dates
|
|
draw_row: function(sort_key, label, events)
|
|
{
|
|
var key = sort_key.split('-');
|
|
this._drawRow(sort_key, label, events, new Date(key[0],key[1],1),new Date(key[0],parseInt(key[1])+1,0));
|
|
}
|
|
},
|
|
// Group by category has one row for each [sub]category
|
|
category:
|
|
{
|
|
title: function() { return this.egw().lang('Category');},
|
|
headers: function() {
|
|
var start = new Date(this.options.start_date);
|
|
var end = new Date(this.options.end_date);
|
|
var start_date = new Date(start.getUTCFullYear(), start.getUTCMonth(),start.getUTCDate());
|
|
var end_date = new Date(end.getUTCFullYear(), end.getUTCMonth(),end.getUTCDate());
|
|
var day_count = Math.round((end_date - start_date) /(1000*3600*24))+1;
|
|
|
|
if(day_count >= 28)
|
|
{
|
|
this.headers.append(this._header_months(start, day_count));
|
|
}
|
|
|
|
if(day_count >= 5)
|
|
{
|
|
this.headers.append(this._header_weeks(start, day_count));
|
|
}
|
|
this.headers.append(this._header_days(start, day_count));
|
|
if(day_count <= 7)
|
|
{
|
|
this.headers.append(this._header_hours(start, day_count));
|
|
}
|
|
},
|
|
row_labels: function() {
|
|
return {'': egw.lang('none')};
|
|
},
|
|
group: function(labels, rows, event) {
|
|
if(typeof rows[event.category] === 'undefined')
|
|
{
|
|
rows[event.category] = [];
|
|
}
|
|
rows[event.category].push(event);
|
|
if(typeof labels[event.category] === 'undefined')
|
|
{
|
|
var categories = et2_selectbox.cat_options({_type:'select-cat'}, {application: 'calendar'});
|
|
for(var i in categories )
|
|
{
|
|
if(parseInt(categories[i].value) === parseInt(event.category))
|
|
{
|
|
labels[event.category] = categories[i].label;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
draw_row: function(sort_key, label, events) {
|
|
return this._drawRow(sort_key, label,events,this.options.start_date, this.options.end_date);
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Something changed, and the planner needs to be re-drawn. We wait a bit to
|
|
* avoid re-drawing twice if start and end date both changed, then recreate.
|
|
*
|
|
* @param {boolean} trigger=false Trigger an event once things are done.
|
|
* Waiting until invalidate completes prevents 2 updates when changing the date range.
|
|
* @returns {undefined}
|
|
*/
|
|
invalidate: function(trigger) {
|
|
|
|
// Wait a bit to see if anything else changes, then re-draw the days
|
|
if(this.update_timer === null)
|
|
{
|
|
this.update_timer = window.setTimeout(jQuery.proxy(function() {
|
|
this.widget.update_timer = null;
|
|
|
|
this.widget.value = this.widget._fetch_data();
|
|
|
|
this.widget._drawGrid();
|
|
|
|
// Update actions
|
|
if(this._actionManager)
|
|
{
|
|
this._link_actions(this._actionManager.children);
|
|
}
|
|
|
|
if(this.trigger)
|
|
{
|
|
this.widget.change();
|
|
}
|
|
},{widget:this,"trigger":trigger}),ET2_GRID_INVALIDATE_TIMEOUT);
|
|
}
|
|
},
|
|
|
|
detachFromDOM: function() {
|
|
// Remove the binding to the change handler
|
|
$j(this.div).off("change.et2_calendar_timegrid");
|
|
|
|
this._super.apply(this, arguments);
|
|
},
|
|
|
|
attachToDOM: function() {
|
|
this._super.apply(this, arguments);
|
|
|
|
// Add the binding for the event change handler
|
|
$j(this.div).on("change.et2_calendar_timegrid", '.calendar_calEvent', this, function(e) {
|
|
// Make sure function gets a reference to the widget
|
|
var args = Array.prototype.slice.call(arguments);
|
|
if(args.indexOf(this) == -1) args.push(this);
|
|
|
|
return e.data.event_change.apply(e.data, args);
|
|
});
|
|
|
|
// Add the binding for the change handler
|
|
$j(this.div).on("change.et2_calendar_timegrid", '*:not(.calendar_calEvent)', this, function(e) {
|
|
return e.data.change.call(e.data, e, this);
|
|
});
|
|
|
|
},
|
|
|
|
getDOMNode: function(_sender)
|
|
{
|
|
if(_sender === this || !_sender)
|
|
{
|
|
return this.div[0];
|
|
}
|
|
if(_sender._parent === this)
|
|
{
|
|
return this.rows[0];
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Creates all the DOM nodes for the planner grid
|
|
*
|
|
* Any existing nodes (& children) are removed, the headers & labels are
|
|
* determined according to the current group_by value, and then the rows
|
|
* are created.
|
|
*
|
|
* @method
|
|
* @private
|
|
*
|
|
*/
|
|
_drawGrid: function()
|
|
{
|
|
|
|
this.div.css('height', this.options.height);
|
|
|
|
// Clear old events
|
|
var delete_index = this._children.length - 1;
|
|
while(this._children.length > 0 && delete_index >= 0)
|
|
{
|
|
this._children[delete_index].free();
|
|
this.removeChild(this._children[delete_index--]);
|
|
}
|
|
|
|
// Clear old rows
|
|
this.rows.empty();
|
|
|
|
var grouper = this.groupers[isNaN(this.options.group_by) ? this.options.group_by : 'category'];
|
|
if(!grouper) return;
|
|
|
|
// Headers
|
|
this.headers.empty();
|
|
this.headerTitle.text(grouper.title.apply(this));
|
|
grouper.headers.apply(this);
|
|
|
|
// Get the rows / labels
|
|
var labels = grouper.row_labels.call(this);
|
|
|
|
// Group the events
|
|
var events = {};
|
|
for(var i = 0; i < this.value.length; i++)
|
|
{
|
|
grouper.group.call(this, labels, events, this.value[i]);
|
|
}
|
|
|
|
// Draw the rows
|
|
for(var key in labels)
|
|
{
|
|
grouper.draw_row.call(this,key, labels[key], events[key] || []);
|
|
}
|
|
|
|
},
|
|
|
|
/**
|
|
* Draw a single row of the planner
|
|
*
|
|
* @param {string} key Index into the grouped labels & events
|
|
* @param {string} label
|
|
* @param {Array} events
|
|
* @param {Date} start
|
|
* @param {Date} end
|
|
*/
|
|
_drawRow: function(key, label, events, start, end)
|
|
{
|
|
var row = et2_createWidget('calendar-planner_row',{
|
|
id: key,
|
|
label: label,
|
|
start_date: start,
|
|
end_date: end,
|
|
value: events
|
|
},this);
|
|
|
|
|
|
if(this.isInTree())
|
|
{
|
|
row.doLoadingFinished();
|
|
}
|
|
|
|
// Add actual events
|
|
row._update_events(events);
|
|
},
|
|
|
|
|
|
_header_day_of_month: function()
|
|
{
|
|
var day_width = 3.23; // 100.0 / 31;
|
|
|
|
// month scale with navigation
|
|
var content = '<div class="calendar_plannerScale">';
|
|
var start = new Date(this.options.start_date);
|
|
start = new Date(start.valueOf() + start.getTimezoneOffset() * 60 * 1000);
|
|
var end = new Date(this.options.end_date);
|
|
end = new Date(end.valueOf() + end.getTimezoneOffset() * 60 * 1000);
|
|
|
|
var title = egw.lang(date('F',start))+' '+date('Y',start)+' - '+
|
|
egw.lang(date('F',end))+' '+date('Y',end);
|
|
|
|
// calculate date for navigation links
|
|
var time = new Date(start);
|
|
time.setUTCFullYear(time.getUTCFullYear()-1);
|
|
var last_year = date('Ymd',time);
|
|
time.setUTCMonth(time.getUTCMonth()+11);
|
|
var last_month = date('Ymd',time);
|
|
time.setUTCMonth(time.getUTCMonth()+2);
|
|
var next_month = date('Ymd',time);
|
|
time.setUTCMonth(time.getUTCMonth()+11);
|
|
var next_year = date('Ymd',time);
|
|
|
|
title = last_year + ' ' + last_month + ' ' + title + ' ' +next_month +' ' +next_year;
|
|
/*
|
|
* TODO: implement these arrows
|
|
title = html::a_href(html::image('phpgwapi','first',lang('back one year'),$options=' alt="<<"'),array(
|
|
'menuaction' => $this->view_menuaction,
|
|
'date' => $last_year,
|
|
)) + ' '+
|
|
html::a_href(html::image('phpgwapi','left',lang('back one month'),$options=' alt="<"'),array(
|
|
'menuaction' => $this->view_menuaction,
|
|
'date' => $last_month,
|
|
)) + ' '+title;
|
|
title += ' '.html::a_href(html::image('phpgwapi','right',lang('forward one month'),$options=' alt=">>"'),array(
|
|
'menuaction' => $this->view_menuaction,
|
|
'date' => $next_month,
|
|
))+ ' '+
|
|
html::a_href(html::image('phpgwapi','last',lang('forward one year'),$options=' alt=">>"'),array(
|
|
'menuaction' => $this->view_menuaction,
|
|
'date' => $next_year,
|
|
));
|
|
*/
|
|
|
|
content += '<div class="calendar_plannerMonthScale th" style="left: 0; width: 100%;">'+
|
|
title+"</div>";
|
|
content += "</div>"; // end of plannerScale
|
|
|
|
// day of month scale
|
|
content +='<div class="calendar_plannerScale">';
|
|
|
|
for(var left = 0, i = 0; i < 31; left += day_width,++i)
|
|
{
|
|
content += '<div class="calendar_plannerDayOfMonthScale " style="left: '+left+'%; width: '+day_width+'%;">'+
|
|
(1+i)+"</div>\n";
|
|
}
|
|
content += "</div>\n";
|
|
|
|
return content;
|
|
},
|
|
|
|
/**
|
|
* Make a header showing the months
|
|
* @param {Date} start
|
|
* @param {number} days
|
|
* @returns {string} HTML snippet
|
|
*/
|
|
_header_months: function(start, days)
|
|
{
|
|
var content = '<div class="calendar_plannerScale">';
|
|
var days_in_month = 0;
|
|
var day_width = 100 / days;
|
|
for(var t = new Date(start),left = 0,i = 0; i < days; t.setUTCDate(t.getUTCDate() + days_in_month),left += days_in_month*day_width,i += days_in_month)
|
|
{
|
|
days_in_month = new Date(t.getUTCFullYear(),t.getUTCMonth()+1,0).getUTCDate() - (t.getUTCDate()-1);
|
|
|
|
if (i + days_in_month > days)
|
|
{
|
|
days_in_month = days - i;
|
|
}
|
|
if (days_in_month > 5)
|
|
{
|
|
var title = egw.lang(date('F',new Date(t.valueOf() + t.getTimezoneOffset() * 60 * 1000)))
|
|
}
|
|
if (days_in_month > 10)
|
|
{
|
|
title += ' '+t.getUTCFullYear();
|
|
|
|
// previous links
|
|
var prev = new Date(t);
|
|
prev.setUTCMonth(prev.getUTCMonth()-1);
|
|
if(prev.valueOf() < start.valueOf() - (20 * 1000*3600*24))
|
|
{
|
|
var full = prev.toJSON();
|
|
prev.setUTCDate(prev.getUTCDate() + 15);
|
|
prev.setUTCDate(start.getUTCDate() < 15 ? 1 : 15);
|
|
var half = prev.toJSON();
|
|
title = this._scroll_button('first',full) + this._scroll_button('left',half) + title;
|
|
}
|
|
|
|
// show next scales, if there are more then 10 days in the next month or there is no next month
|
|
var end = new Date(start);
|
|
end.setUTCDate(end.getUTCDate()+days);
|
|
var days_in_next_month = end.getUTCDate();
|
|
if (days_in_next_month <= 10 || end.getUTCMonth() == t.getUTCMonth())
|
|
{
|
|
// next links
|
|
var next = new Date(t);
|
|
next.setUTCMonth(next.getUTCMonth()+1);
|
|
full = next.toJSON();
|
|
next.setUTCDate(next.getUTCDate() - 15);
|
|
next.setUTCDate(next.getUTCDate() < 15 ? 1 : 15);
|
|
half = next.toJSON();
|
|
|
|
title += this._scroll_button('right',half) + this._scroll_button('last',full);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
title = ' ';
|
|
}
|
|
content += '<div class="calendar_plannerMonthScale et2_clickable" data-date="'+t.toJSON()+ '"'+// data-planner_days='+days_in_month+
|
|
' style="left: '+left+'%; width: '+(day_width*days_in_month)+'%;">'+
|
|
title+"</div>";
|
|
}
|
|
content += "</div>"; // end of plannerScale
|
|
|
|
return content;
|
|
},
|
|
|
|
/**
|
|
* Make a header showing the week numbers
|
|
*
|
|
* @param {Date} start
|
|
* @param {number} days
|
|
* @returns {string} HTML snippet
|
|
*/
|
|
_header_weeks: function(start, days)
|
|
{
|
|
var week_width = 100 / days * (days <= 7 ? days : 7);
|
|
|
|
var content = '<div class="calendar_plannerScale" data-planner_days=7>';
|
|
var state = ''
|
|
|
|
// we're not using UTC so date() formatting function works
|
|
var t = new Date(start.valueOf() + start.getTimezoneOffset() * 60 * 1000);
|
|
for(var left = 0,i = 0; i < days; t.setDate(t.getDate() + 7),left += week_width,i += 7)
|
|
{
|
|
var title = egw.lang('Week')+' '+date('W',t);
|
|
|
|
state = new Date(t.valueOf() - start.getTimezoneOffset() * 60 * 1000).toJSON();
|
|
if (days <= 7)
|
|
{
|
|
// prev. week
|
|
var left = new Date(t);
|
|
left.setUTCDate(left.getUTCDate() - 7);
|
|
|
|
// next week
|
|
var right = new Date(t);
|
|
right.setUTCDate(right.getUTCDate() + 7);
|
|
|
|
title = this._scroll_button('left',left.toJSON()) + title + this._scroll_button('right',right.toJSON());
|
|
}
|
|
|
|
content += '<div class="calendar_plannerWeekScale et2_clickable" data-date=\'' + state + '\' style="left: '+left+'%; width: '+week_width+'%;">'+title+"</div>";
|
|
}
|
|
content += "</div>"; // end of plannerScale
|
|
|
|
return content;
|
|
},
|
|
|
|
/**
|
|
* Make a header for some days
|
|
*
|
|
* @param {Date} start
|
|
* @param {number} days
|
|
* @returns {string} HTML snippet
|
|
*/
|
|
_header_days: function(start, days)
|
|
{
|
|
var day_width = 100 / days;
|
|
var content = '<div class="calendar_plannerScale'+(days > 3 ? 'Day' : '')+'" data-planner_days="1" >';
|
|
|
|
// we're not using UTC so date() formatting function works
|
|
var t = new Date(start.valueOf() + start.getTimezoneOffset() * 60 * 1000);
|
|
for(var left = 0,i = 0; i < days; t.setDate(t.getDate()+1),left += day_width,++i)
|
|
{
|
|
var holidays = [];
|
|
var day_class = this.day_class_holiday(t,holidays);
|
|
var title = '';
|
|
var state = '';
|
|
|
|
if (days <= 3)
|
|
{
|
|
title = egw.lang(date('l',t))+', '+date('j',t)+'. '+egw.lang(date('F',t));
|
|
}
|
|
else if (days <= 7)
|
|
{
|
|
title = egw.lang(date('l',t))+' '+date('j',t);
|
|
}
|
|
else
|
|
{
|
|
title = egw.lang(date('D',t)).substr(0,2)+'<br />'+date('j',t);
|
|
}
|
|
state = new Date(t.valueOf() - start.getTimezoneOffset() * 60 * 1000).toJSON();
|
|
if (days < 5)
|
|
{
|
|
if (!i) // prev. day only for the first day
|
|
{
|
|
var prev = new Date(t);
|
|
prev.setUTCDate(prev.getUTCDate() - 1);
|
|
title = this._scroll_button('left',prev.toJSON()) + title;
|
|
}
|
|
if (i == days-1) // next day only for the last day
|
|
{
|
|
var next = new Date(t);
|
|
next.setUTCDate(next.getUTCDate() + 1);
|
|
title += this._scroll_button('right',next.toJSON());
|
|
}
|
|
}
|
|
content += '<div class="calendar_plannerDayScale et2_clickable '+ day_class+
|
|
'" data-date=\'' + state +'\' style="left: '+left+'%; width: '+day_width+'%;"'+
|
|
(holidays ? ' title="'+holidays.join(',')+'"' : '')+'>'+title+"</div>\n";
|
|
}
|
|
content += "</div>"; // end of plannerScale
|
|
|
|
return content;
|
|
},
|
|
|
|
/**
|
|
* Create a header with hours
|
|
*
|
|
* @param {Date} start
|
|
* @param {number} days
|
|
* @returns {string} HTML snippet for the header
|
|
*/
|
|
_header_hours: function(start,days)
|
|
{
|
|
var divisors = [1,2,3,4,6,8,12];
|
|
var decr = 1;
|
|
for(var i = 0; i < divisors.length; i++) // numbers dividing 24 without rest
|
|
{
|
|
if (divisors[i] > days) break;
|
|
decr = divisors[i];
|
|
}
|
|
var hours = days * 24;
|
|
if (days === 1) // for a single day we calculate the hours of a days, to take into account daylight saving changes (23 or 25 hours)
|
|
{
|
|
var t = new Date(start.getUTCFullYear(),start.getUTCMonth(),start.getUTCDate());
|
|
var s = new Date(start);
|
|
s.setUTCHours(23);
|
|
s.setUTCMinutes(59);
|
|
s.setUTCSeconds(59);
|
|
hours = Math.ceil((s.getTime() - t.getTime()) / 3600000);
|
|
}
|
|
var cell_width = 100 / hours * decr;
|
|
|
|
var content = '<div class="calendar_plannerScale">';
|
|
|
|
// we're not using UTC so date() formatting function works
|
|
var t = new Date(start.valueOf() + start.getTimezoneOffset() * 60 * 1000);
|
|
for(var left = 0,i = 0; i < hours; left += cell_width,i += decr)
|
|
{
|
|
var title = date(egw.preference('timeformat','calendar') == 12 ? 'ha' : 'H',t);
|
|
|
|
content += '<div class="calendar_plannerHourScale" style="left: '+left+'%; width: '+(cell_width)+'%;">'+title+"</div>";
|
|
t.setHours(t.getHours()+decr);
|
|
}
|
|
content += "</div>"; // end of plannerScale
|
|
|
|
return content;
|
|
},
|
|
|
|
/**
|
|
* Create a pagination button, and inserts it
|
|
*
|
|
*/
|
|
_scroll_button: function(image, date)
|
|
{
|
|
return '<img class="et2_clickable" src="' + egw.image(image)+ '" data-date="' + (date.toJSON ? date.toJSON():date) + '"/>';
|
|
},
|
|
|
|
/**
|
|
* Applies class for today, and any holidays for current day
|
|
*
|
|
* @param {Date} date
|
|
* @param {string[]} holiday_list Filled with a list of holidays for that day
|
|
*
|
|
* @return {string} CSS Classes for the day. calendar_calBirthday, calendar_calHoliday, calendar_calToday and calendar_weekend as appropriate
|
|
*/
|
|
day_class_holiday: function(date,holiday_list) {
|
|
|
|
if(!date) return '';
|
|
|
|
var day_class = '';
|
|
|
|
// Holidays and birthdays
|
|
var holidays = et2_calendar_daycol.get_holidays(this,date.getUTCFullYear());
|
|
|
|
// Pass a number rather than the date object, to make sure it doesn't get changed
|
|
this.date_helper.set_value(date.getTime()/1000);
|
|
var date_key = ''+this.date_helper.get_year() + sprintf('%02d',this.date_helper.get_month()) + sprintf('%02d',this.date_helper.get_date());
|
|
if(holidays && holidays[date_key])
|
|
{
|
|
holidays = holidays[date_key];
|
|
for(var i = 0; i < holidays.length; i++)
|
|
{
|
|
if (typeof holidays[i]['birthyear'] !== 'undefined')
|
|
{
|
|
day_class += ' calendar_calBirthday';
|
|
|
|
holiday_list.push(holidays[i]['name']);
|
|
}
|
|
else
|
|
{
|
|
day_class += 'calendar_calHoliday';
|
|
|
|
holiday_list.push(holidays[i]['name']);
|
|
}
|
|
}
|
|
}
|
|
holidays = holiday_list.join(',');
|
|
var today = new Date();
|
|
if(date_key === ''+today.getUTCFullYear()+
|
|
sprintf("%02d",today.getUTCMonth()+1)+
|
|
sprintf("%02d",today.getUTCDate())
|
|
)
|
|
{
|
|
day_class += "calendar_calToday";
|
|
}
|
|
if(date.getUTCDay() == 0 || date.getUTCDay() == 6)
|
|
{
|
|
day_class += "calendar_weekend";
|
|
}
|
|
return day_class;
|
|
},
|
|
|
|
/**
|
|
* Link the actions to the DOM nodes / widget bits.
|
|
*
|
|
* @todo This currently does nothing
|
|
* @param {object} actions {ID: {attributes..}+} map of egw action information
|
|
*/
|
|
_link_actions: function(actions)
|
|
{
|
|
// Get the parent? Might be a grid row, might not. Either way, it is
|
|
// just a container with no valid actions
|
|
var objectManager = egw_getAppObjectManager(true);
|
|
var parent = objectManager.getObjectById(this._parent.id);
|
|
if(!parent) return;
|
|
|
|
for(var i = 0; i < parent.children.length; i++)
|
|
{
|
|
var parent_finder = jQuery(this.div, parent.children[i].iface.doGetDOMNode());
|
|
if(parent_finder.length > 0)
|
|
{
|
|
parent = parent.children[i];
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Automatically add dnd support for linking
|
|
*/
|
|
_init_links_dnd: function(mgr,actionLinks) {
|
|
var self = this;
|
|
|
|
var drop_action = mgr.getActionById('egw_link_drop');
|
|
var drag_action = mgr.getActionById('egw_link_drag');
|
|
|
|
// Check if this app supports linking
|
|
if(!egw.link_get_registry(this.dataStorePrefix || this.egw().appName, 'query') ||
|
|
egw.link_get_registry(this.dataStorePrefix || this.egw().appName, 'title'))
|
|
{
|
|
if(drop_action)
|
|
{
|
|
drop_action.remove();
|
|
if(actionLinks.indexOf(drop_action.id) >= 0)
|
|
{
|
|
actionLinks.splice(actionLinks.indexOf(drop_action.id),1);
|
|
}
|
|
}
|
|
if(drag_action)
|
|
{
|
|
drag_action.remove();
|
|
if(actionLinks.indexOf(drag_action.id) >= 0)
|
|
{
|
|
actionLinks.splice(actionLinks.indexOf(drag_action.id),1);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Don't re-add
|
|
if(drop_action == null)
|
|
{
|
|
// Create the drop action that links entries
|
|
drop_action = mgr.addAction('drop', 'egw_link_drop', egw.lang('Create link'), egw.image('link'), function(action, source, dropped) {
|
|
// Extract link IDs
|
|
var links = [];
|
|
var id = '';
|
|
for(var i = 0; i < source.length; i++)
|
|
{
|
|
if(!source[i].id) continue;
|
|
id = source[i].id.split('::');
|
|
links.push({app: id[0] == 'filemanager' ? 'link' : id[0], id: id[1]});
|
|
}
|
|
if(!links.length)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Link the entries
|
|
egw.json(self.egw().getAppName()+".etemplate_widget_link.ajax_link.etemplate",
|
|
dropped.id.split('::').concat([links]),
|
|
function(result) {
|
|
if(result)
|
|
{
|
|
this.egw().message('Linked');
|
|
}
|
|
},
|
|
self,
|
|
true,
|
|
self
|
|
).sendRequest();
|
|
|
|
},true);
|
|
}
|
|
if(actionLinks.indexOf(drop_action.id) < 0)
|
|
{
|
|
actionLinks.push(drop_action.id);
|
|
}
|
|
// Accept other links, and files dragged from the filemanager
|
|
// This does not handle files dragged from the desktop. They are
|
|
// handled by et2_nextmatch, since it needs DOM stuff
|
|
if(drop_action.acceptedTypes.indexOf('link') == -1)
|
|
{
|
|
drop_action.acceptedTypes.push('link');
|
|
}
|
|
|
|
// Don't re-add
|
|
if(drag_action == null)
|
|
{
|
|
// Create drag action that allows linking
|
|
drag_action = mgr.addAction('drag', 'egw_link_drag', egw.lang('link'), 'link', function(action, selected) {
|
|
// Drag helper - list titles. Arbitrarily limited to 10.
|
|
var helper = $j(document.createElement("div"));
|
|
for(var i = 0; i < selected.length && i < 10; i++)
|
|
{
|
|
var id = selected[i].id.split('::');
|
|
var span = $j(document.createElement('span')).appendTo(helper);
|
|
egw.link_title(id[0],id[1], function(title) {
|
|
this.append(title);
|
|
this.append('<br />');
|
|
}, span);
|
|
}
|
|
// As we wanted to have a general defaul helper interface, we return null here and not using customize helper for links
|
|
// TODO: Need to decide if we need to create a customized helper interface for links anyway
|
|
//return helper;
|
|
return null;
|
|
},true);
|
|
}
|
|
if(actionLinks.indexOf(drag_action.id) < 0)
|
|
{
|
|
actionLinks.push(drag_action.id);
|
|
}
|
|
drag_action.set_dragType('link');
|
|
},
|
|
|
|
/**
|
|
* Get all action-links / id's of 1.-level actions from a given action object
|
|
*
|
|
* Here we are only interested in drop events.
|
|
*
|
|
* @param actions
|
|
* @returns {Array}
|
|
*/
|
|
_get_action_links: function(actions)
|
|
{
|
|
var action_links = [];
|
|
// TODO: determine which actions are allowed without an action (empty actions)
|
|
for(var i in actions)
|
|
{
|
|
var action = actions[i];
|
|
if(action.type === 'drop')
|
|
{
|
|
action_links.push(typeof action.id !== 'undefined' ? action.id : i);
|
|
}
|
|
}
|
|
return action_links;
|
|
},
|
|
|
|
/**
|
|
* Use the egw.data system to get data from the calendar list for the
|
|
* selected time span.
|
|
*
|
|
*/
|
|
_fetch_data: function()
|
|
{
|
|
var value = [];
|
|
// Remember previous day to avoid multi-days duplicating
|
|
var last_data = [];
|
|
|
|
var t = new Date(this.options.start_date);
|
|
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.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;
|
|
}
|
|
t.setUTCDate(t.getUTCDate() + 1);
|
|
}
|
|
while(t < end);
|
|
return value;
|
|
},
|
|
|
|
/**
|
|
* Provide specific data to be displayed.
|
|
* This is a way to set start and end dates, owner and event data in once call.
|
|
*
|
|
* @param {Object[]} events Array of events, indexed by date in Ymd format:
|
|
* {
|
|
* 20150501: [...],
|
|
* 20150502: [...]
|
|
* }
|
|
* Days should be in order.
|
|
*
|
|
*/
|
|
set_value: function(events)
|
|
{
|
|
if(typeof events !== 'object') return false;
|
|
|
|
if(events.owner)
|
|
{
|
|
this.set_owner(events.owner);
|
|
delete events.owner;
|
|
}
|
|
if(events.start_date)
|
|
{
|
|
this.set_start_date(events.start_date);
|
|
delete events.start_date;
|
|
}
|
|
if(events.end_date)
|
|
{
|
|
this.set_end_date(events.end_date);
|
|
delete events.end_date;
|
|
}
|
|
|
|
this.value = events || [];
|
|
},
|
|
|
|
/**
|
|
* Change the start date
|
|
*
|
|
* @param {string|number|Date} new_date New starting date
|
|
* @returns {undefined}
|
|
*/
|
|
set_start_date: function(new_date)
|
|
{
|
|
if(!new_date || new_date === null)
|
|
{
|
|
throw new Error('Invalid start date. ' + new_date.toString());
|
|
}
|
|
|
|
// Use date widget's existing functions to deal
|
|
if(typeof new_date === "object" || typeof new_date === "string" && new_date.length > 8)
|
|
{
|
|
this.date_helper.set_value(new_date);
|
|
}
|
|
else if(typeof new_date === "string")
|
|
{
|
|
this.date_helper.set_year(new_date.substring(0,4));
|
|
this.date_helper.set_month(new_date.substring(4,6));
|
|
this.date_helper.set_date(new_date.substring(6,8));
|
|
}
|
|
|
|
var old_date = this.options.start_date;
|
|
this.options.start_date = this.date_helper.getValue();
|
|
|
|
if(old_date !== this.options.start_date && this.isAttached())
|
|
{
|
|
this.invalidate(true);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Change the end date
|
|
*
|
|
* @param {string|number|Date} new_date New end date
|
|
* @returns {undefined}
|
|
*/
|
|
set_end_date: function(new_date)
|
|
{
|
|
if(!new_date || new_date === null)
|
|
{
|
|
throw new Error('Invalid end date. ' + new_date.toString());
|
|
}
|
|
// Use date widget's existing functions to deal
|
|
if(typeof new_date === "object" || typeof new_date === "string" && new_date.length > 8)
|
|
{
|
|
this.date_helper.set_value(new_date);
|
|
}
|
|
else if(typeof new_date === "string")
|
|
{
|
|
this.date_helper.set_year(new_date.substring(0,4));
|
|
this.date_helper.set_month(new_date.substring(4,6));
|
|
this.date_helper.set_date(new_date.substring(6,8));
|
|
}
|
|
|
|
this.date_helper.set_hours(23);
|
|
this.date_helper.set_minutes(59);
|
|
this.date_helper.date.setSeconds(59);
|
|
var old_date = this.options.end_date;
|
|
this.options.end_date = this.date_helper.getValue();
|
|
|
|
if(old_date !== this.options.end_date && this.isAttached())
|
|
{
|
|
this.invalidate(true);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Change how the planner is grouped
|
|
*
|
|
* @param {string|number} group_by 'user', 'month', or an integer category ID
|
|
* @returns {undefined}
|
|
*/
|
|
set_group_by: function(group_by)
|
|
{
|
|
if(isNaN(group_by) && typeof this.groupers[group_by] === 'undefined')
|
|
{
|
|
throw new Error('Invalid group_by "'+group_by+'"');
|
|
}
|
|
var old = this.options.group_by;
|
|
this.options.group_by = ''+group_by;
|
|
|
|
if(old !== this.options.group_by && this.isAttached())
|
|
{
|
|
this.invalidate(true);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Set which users to display when filtering, and for rows when grouping by user.
|
|
*
|
|
* @param {number|number[]} _owner Account ID
|
|
*/
|
|
set_owner: function(_owner)
|
|
{
|
|
var old = this.options.owner;
|
|
if(!jQuery.isArray(_owner))
|
|
{
|
|
if(typeof _owner === "string")
|
|
{
|
|
_owner = _owner.split(',');
|
|
}
|
|
else
|
|
{
|
|
_owner = [_owner];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_owner = jQuery.extend([],_owner);
|
|
}
|
|
this.options.owner = _owner;
|
|
if(old !== this.options.owner && this.isAttached())
|
|
{
|
|
this.invalidate(true);
|
|
}
|
|
},
|
|
|
|
|
|
/**
|
|
* Call change handler, if set
|
|
*/
|
|
change: function(event) {
|
|
if (this.onchange)
|
|
{
|
|
if(typeof this.onchange == 'function')
|
|
{
|
|
// Make sure function gets a reference to the widget
|
|
var args = Array.prototype.slice.call(arguments);
|
|
if(args.indexOf(this) == -1) args.push(this);
|
|
|
|
return this.onchange.apply(this, args);
|
|
} else {
|
|
return (et2_compileLegacyJS(this.options.onchange, this, _node))();
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Call event change handler, if set
|
|
*/
|
|
event_change: function(event, dom_node) {
|
|
if (this.onevent_change)
|
|
{
|
|
var event_data = this._get_event_info(dom_node);
|
|
var event_widget = this.getWidgetById(event_data.id);
|
|
et2_calendar_event.recur_prompt(event_data, jQuery.proxy(function(button_id, event_data) {
|
|
// No need to continue
|
|
if(button_id === 'cancel') return false;
|
|
|
|
if(typeof this.onevent_change == 'function')
|
|
{
|
|
// Make sure function gets a reference to the widget
|
|
var args = Array.prototype.slice.call(arguments);
|
|
|
|
if(args.indexOf(event_widget) == -1) args.push(event_widget);
|
|
|
|
// Put button ID in event
|
|
event.button_id = button_id;
|
|
|
|
return this.onevent_change.apply(this, [event, event_widget, button_id]);
|
|
} else {
|
|
return (et2_compileLegacyJS(this.options.onevent_change, event_widget, dom_node))();
|
|
}
|
|
},this));
|
|
}
|
|
return false;
|
|
},
|
|
|
|
/**
|
|
* Click handler calling custom handler set via onclick attribute to this.onclick
|
|
*
|
|
* This also handles all its own actions, including navigation. If there is
|
|
* an event associated with the click, it will be found and passed to the
|
|
* onclick function.
|
|
*
|
|
* @param {Event} _ev
|
|
* @returns {boolean}
|
|
*/
|
|
click: function(_ev)
|
|
{
|
|
var result = true;
|
|
|
|
// Is this click in the event stuff, or in the header?
|
|
if(this.gridHeader.has(_ev.target).length === 0 && !$j(_ev.target).hasClass('calendar_plannerRowHeader'))
|
|
{
|
|
// Event came from inside, maybe a calendar event
|
|
var event = this._get_event_info(_ev.originalEvent.target);
|
|
if(typeof this.onclick == 'function')
|
|
{
|
|
// Make sure function gets a reference to the widget, splice it in as 2. argument if not
|
|
var args = Array.prototype.slice.call(arguments);
|
|
if(args.indexOf(this) == -1) args.splice(1, 0, this);
|
|
|
|
result = this.onclick.apply(this, args);
|
|
}
|
|
|
|
if(event.id && result && !this.options.disabled && !this.options.readonly)
|
|
{
|
|
et2_calendar_event.recur_prompt(event);
|
|
|
|
return false;
|
|
}
|
|
return result;
|
|
}
|
|
else if (!jQuery.isEmptyObject(_ev.target.dataset))
|
|
{
|
|
// Click on a header, we can go there
|
|
_ev.data = jQuery.extend({},_ev.target.parentNode.dataset, _ev.target.dataset);
|
|
this.change(_ev);
|
|
}
|
|
else
|
|
{
|
|
// Default handler to open a new event at the selected time
|
|
// TODO: Determine date / time more accurately from position
|
|
this.egw().open(null, 'calendar', 'add', {
|
|
date: _ev.target.dataset.date || this.day_list[0],
|
|
hour: _ev.target.dataset.hour || this.options.day_start,
|
|
minute: _ev.target.dataset.minute || 0
|
|
} , '_blank');
|
|
return false;
|
|
}
|
|
},
|
|
|
|
_get_event_info: function(dom_node)
|
|
{
|
|
// Determine as much relevant info as can be found
|
|
var event_node = $j(dom_node).closest('[data-id]',this.div)[0];
|
|
var day_node = $j(event_node).closest('[data-date]',this.div)[0];
|
|
|
|
return jQuery.extend({
|
|
event_node: event_node,
|
|
day_node: day_node,
|
|
},
|
|
event_node ? event_node.dataset : {},
|
|
day_node ? day_node.dataset : {}
|
|
);
|
|
},
|
|
|
|
|
|
/**
|
|
* Get time from position
|
|
*
|
|
* @param {number} x
|
|
* @param {number} y
|
|
* @returns {DOMNode[]} time node(s) for the given position
|
|
*/
|
|
_get_time_from_position: function(x,y) {
|
|
|
|
x = Math.round(x);
|
|
y = Math.round(y);
|
|
var nodes = $j('.calendar_calAddEvent[data-hour]',this.div).removeClass('drop-hover').filter(function() {
|
|
var offset = $j(this).offset();
|
|
var range={x:[offset.left,offset.left+$j(this).outerWidth()],y:[offset.top,offset.top+$j(this).outerHeight()]};
|
|
|
|
var i = (x >=range.x[0] && x <= range.x[1]) && (y >= range.y[0] && y <= range.y[1]);
|
|
return i;
|
|
}).addClass("drop-hover");
|
|
|
|
return nodes;
|
|
},
|
|
|
|
/**
|
|
* Code for implementing et2_IDetachedDOM
|
|
*
|
|
* @param {array} _attrs array to add further attributes to
|
|
*/
|
|
getDetachedAttributes: function(_attrs) {
|
|
_attrs.push('start_date','end_date');
|
|
},
|
|
|
|
getDetachedNodes: function() {
|
|
return [this.getDOMNode()];
|
|
},
|
|
|
|
setDetachedAttributes: function(_nodes, _values) {
|
|
this.div = $j(_nodes[0]);
|
|
|
|
if(_values.start_date)
|
|
{
|
|
this.set_start_date(_values.start_date);
|
|
}
|
|
if(_values.end_date)
|
|
{
|
|
this.set_end_date(_values.end_date);
|
|
}
|
|
},
|
|
|
|
// Resizable interface
|
|
resize: function (_height)
|
|
{
|
|
this.options.height = _height;
|
|
this.div.css('height', this.options.height);
|
|
}
|
|
});
|
|
et2_register_widget(et2_calendar_planner, ["calendar-planner"]); |