summaryrefslogtreecommitdiffstats
path: root/sources/ext
diff options
context:
space:
mode:
Diffstat (limited to 'sources/ext')
-rw-r--r--sources/ext/dhtmlxscheduler_active_links.js28
-rw-r--r--sources/ext/dhtmlxscheduler_agenda_view.js120
-rw-r--r--sources/ext/dhtmlxscheduler_all_timed.js123
-rw-r--r--sources/ext/dhtmlxscheduler_collision.js131
-rw-r--r--sources/ext/dhtmlxscheduler_container_autoresize.js161
-rw-r--r--sources/ext/dhtmlxscheduler_cookie.js70
-rw-r--r--sources/ext/dhtmlxscheduler_editors.js158
-rw-r--r--sources/ext/dhtmlxscheduler_expand.js73
-rw-r--r--sources/ext/dhtmlxscheduler_grid_view.js466
-rw-r--r--sources/ext/dhtmlxscheduler_html_templates.js19
-rw-r--r--sources/ext/dhtmlxscheduler_key_nav.js91
-rw-r--r--sources/ext/dhtmlxscheduler_limit.js941
-rw-r--r--sources/ext/dhtmlxscheduler_map_view.js488
-rw-r--r--sources/ext/dhtmlxscheduler_minical.js455
-rw-r--r--sources/ext/dhtmlxscheduler_multiselect.js66
-rw-r--r--sources/ext/dhtmlxscheduler_multisource.js26
-rw-r--r--sources/ext/dhtmlxscheduler_mvc.js82
-rw-r--r--sources/ext/dhtmlxscheduler_offline.js79
-rw-r--r--sources/ext/dhtmlxscheduler_outerdrag.js57
-rw-r--r--sources/ext/dhtmlxscheduler_pdf.js354
-rw-r--r--sources/ext/dhtmlxscheduler_quick_info.js181
-rw-r--r--sources/ext/dhtmlxscheduler_readonly.js163
-rw-r--r--sources/ext/dhtmlxscheduler_recurring.js786
-rw-r--r--sources/ext/dhtmlxscheduler_serialize.js77
-rw-r--r--sources/ext/dhtmlxscheduler_timeline.js1195
-rw-r--r--sources/ext/dhtmlxscheduler_tooltip.js193
-rw-r--r--sources/ext/dhtmlxscheduler_treetimeline.js302
-rw-r--r--sources/ext/dhtmlxscheduler_units.js238
-rw-r--r--sources/ext/dhtmlxscheduler_url.js34
-rw-r--r--sources/ext/dhtmlxscheduler_week_agenda.js256
-rw-r--r--sources/ext/dhtmlxscheduler_year_view.js373
31 files changed, 7786 insertions, 0 deletions
diff --git a/sources/ext/dhtmlxscheduler_active_links.js b/sources/ext/dhtmlxscheduler_active_links.js
new file mode 100644
index 0000000..68786d8
--- /dev/null
+++ b/sources/ext/dhtmlxscheduler_active_links.js
@@ -0,0 +1,28 @@
+/*
+This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
+to use it in non-GPL project. Please contact sales@dhtmlx.com for details
+*/
+scheduler.config.active_link_view = "day";
+scheduler.attachEvent("onTemplatesReady", function() {
+ var s_d = scheduler.date.str_to_date(scheduler.config.api_date);
+ var d_s = scheduler.date.date_to_str(scheduler.config.api_date);
+
+ var month_x = scheduler.templates.month_day;
+ scheduler.templates.month_day = function(date) {
+ return "<a jump_to='" + d_s(date) + "' href='#'>" + month_x(date) + "</a>";
+ };
+ var week_x = scheduler.templates.week_scale_date;
+ scheduler.templates.week_scale_date = function(date) {
+ return "<a jump_to='" + d_s(date) + "' href='#'>" + week_x(date) + "</a>";
+ };
+ dhtmlxEvent(this._obj, "click", function(e) {
+ var start = e.target || event.srcElement;
+ var to = start.getAttribute("jump_to");
+ if (to) {
+ scheduler.setCurrentView(s_d(to), scheduler.config.active_link_view);
+ if (e && e.preventDefault)
+ e.preventDefault();
+ return false;
+ }
+ })
+}); \ No newline at end of file
diff --git a/sources/ext/dhtmlxscheduler_agenda_view.js b/sources/ext/dhtmlxscheduler_agenda_view.js
new file mode 100644
index 0000000..d3845c8
--- /dev/null
+++ b/sources/ext/dhtmlxscheduler_agenda_view.js
@@ -0,0 +1,120 @@
+/*
+This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
+to use it in non-GPL project. Please contact sales@dhtmlx.com for details
+*/
+scheduler.date.add_agenda = function(date){
+ return scheduler.date.add(date, 1, "year");
+};
+
+scheduler.templates.agenda_time = function(start,end,ev){
+ if (ev._timed)
+ return this.day_date(ev.start_date, ev.end_date, ev)+" "+this.event_date(start);
+ else
+ return scheduler.templates.day_date(start)+" &ndash; "+scheduler.templates.day_date(end);
+};
+scheduler.templates.agenda_text = function(start,end,event){
+ return event.text;
+};
+scheduler.templates.agenda_date = function(){ return ""; };
+
+scheduler.date.agenda_start=function(){ return scheduler.date.date_part(scheduler._currentDate()); };
+
+scheduler.attachEvent("onTemplatesReady",function() {
+ var old_dblclick_dhx_cal_data = scheduler.dblclick_dhx_cal_data;
+ scheduler.dblclick_dhx_cal_data = function() {
+ if (this._mode == "agenda") {
+ if (!this.config.readonly && this.config.dblclick_create)
+ this.addEventNow();
+ } else {
+ if (old_dblclick_dhx_cal_data)
+ return old_dblclick_dhx_cal_data.apply(this, arguments);
+ }
+ };
+ scheduler.attachEvent("onSchedulerResize",function(){
+ if (this._mode == "agenda"){
+ this.agenda_view(true);
+ return false;
+ }
+ return true;
+ });
+
+
+ var old = scheduler.render_data;
+ scheduler.render_data=function(evs){
+ if (this._mode == "agenda")
+ fill_agenda_tab();
+ else
+ return old.apply(this,arguments);
+ };
+
+ var old_render_view_data = scheduler.render_view_data;
+ scheduler.render_view_data = function(){
+ if(this._mode == "agenda") {
+ scheduler._agendaScrollTop = scheduler._els["dhx_cal_data"][0].childNodes[0].scrollTop;
+ scheduler._els["dhx_cal_data"][0].childNodes[0].scrollTop = 0;
+ }
+ return old_render_view_data.apply(this,arguments);
+ };
+
+
+ function set_full_view(mode){
+ if (mode){
+ var l = scheduler.locale.labels;
+ scheduler._els["dhx_cal_header"][0].innerHTML="<div class='dhx_agenda_line'><div>"+l.date+"</div><span style='padding-left:25px'>"+l.description+"</span></div>";
+ scheduler._table_view=true;
+ scheduler.set_sizes();
+ }
+ }
+
+ function fill_agenda_tab(){
+ //get current date
+ var date = scheduler._date;
+ //select events for which data need to be printed
+
+ var events = scheduler.get_visible_events();
+ events.sort(function(a,b){ return a.start_date>b.start_date?1:-1});
+
+ //generate html for the view
+ var html="<div class='dhx_agenda_area'>";
+ for (var i=0; i<events.length; i++){
+ var ev = events[i];
+ var bg_color = (ev.color?("background:"+ev.color+";"):"");
+ var color = (ev.textColor?("color:"+ev.textColor+";"):"");
+ var ev_class = scheduler.templates.event_class(ev.start_date, ev.end_date, ev);
+ html+="<div class='dhx_agenda_line"+(ev_class?' '+ev_class:'')+"' event_id='"+ev.id+"' style='"+color+""+bg_color+""+(ev._text_style||"")+"'><div class='dhx_agenda_event_time'>"+scheduler.templates.agenda_time(ev.start_date, ev.end_date,ev)+"</div>";
+ html+="<div class='dhx_event_icon icon_details'>&nbsp</div>";
+ html+="<span>"+scheduler.templates.agenda_text(ev.start_date, ev.end_date, ev)+"</span></div>";
+ }
+ html+="<div class='dhx_v_border'></div></div>";
+
+ //render html
+ scheduler._els["dhx_cal_data"][0].innerHTML = html;
+ scheduler._els["dhx_cal_data"][0].childNodes[0].scrollTop = scheduler._agendaScrollTop||0;
+
+ // setting up dhx_v_border size
+ var agenda_area = scheduler._els["dhx_cal_data"][0].childNodes[0];
+ var v_border = agenda_area.childNodes[agenda_area.childNodes.length-1];
+ v_border.style.height = (agenda_area.offsetHeight < scheduler._els["dhx_cal_data"][0].offsetHeight) ? "100%" : (agenda_area.offsetHeight+"px");
+
+ var t=scheduler._els["dhx_cal_data"][0].firstChild.childNodes;
+ scheduler._els["dhx_cal_date"][0].innerHTML=scheduler.templates.agenda_date(scheduler._min_date, scheduler._max_date, scheduler._mode);
+
+ scheduler._rendered=[];
+ for (var i=0; i < t.length-1; i++)
+ scheduler._rendered[i]=t[i]
+
+ }
+
+ scheduler.agenda_view=function(mode){
+ scheduler._min_date = scheduler.config.agenda_start||scheduler.date.agenda_start(scheduler._date);
+ scheduler._max_date = scheduler.config.agenda_end||scheduler.date.add_agenda(scheduler._min_date, 1);
+ scheduler._table_view = true;
+ set_full_view(mode);
+ if (mode){
+ //agenda tab activated
+ fill_agenda_tab();
+ } else {
+ //agenda tab de-activated
+ }
+ }
+});
diff --git a/sources/ext/dhtmlxscheduler_all_timed.js b/sources/ext/dhtmlxscheduler_all_timed.js
new file mode 100644
index 0000000..cb33477
--- /dev/null
+++ b/sources/ext/dhtmlxscheduler_all_timed.js
@@ -0,0 +1,123 @@
+/*
+This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
+to use it in non-GPL project. Please contact sales@dhtmlx.com for details
+*/
+(function(){
+
+ scheduler.config.all_timed = "short";
+
+ var is_event_short = function (ev) {
+ return !((ev.end_date - ev.start_date)/(1000*60*60) >= 24);
+ };
+
+ var old_prerender_events_line = scheduler._pre_render_events_line;
+ scheduler._pre_render_events_line = function(evs, hold){
+ if (!this.config.all_timed)
+ return old_prerender_events_line.call(this, evs, hold);
+
+ for (var i=0; i < evs.length; i++) {
+ var ev=evs[i];
+
+ if (ev._timed)
+ continue;
+
+ if (this.config.all_timed == "short") {
+ if (!is_event_short(ev)) {
+ evs.splice(i--,1);
+ continue;
+ }
+ }
+
+ var ce = this._lame_copy({}, ev); // current event (event for one specific day) is copy of original with modified dates
+
+ ce.start_date = new Date(ce.start_date); // as lame copy doesn't copy date objects
+
+ if (!isOvernightEvent(ev)) {
+ ce.end_date = new Date(ev.end_date);
+ }
+ else {
+ ce.end_date = getNextDay(ce.start_date);
+ if (this.config.last_hour != 24) { // if specific last_hour was set (e.g. 20)
+ ce.end_date = setDateTime(ce.start_date, this.config.last_hour);
+ }
+ }
+
+ var event_changed = false;
+ if (ce.start_date < this._max_date && ce.end_date > this._min_date && ce.start_date < ce.end_date) {
+ evs[i] = ce; // adding another event in collection
+ event_changed = true;
+ }
+ // if (ce.start_date > ce.end_date) {
+ // evs.splice(i--,1);
+ // }
+
+ var re = this._lame_copy({}, ev); // remaining event, copy of original with modified start_date (making range more narrow)
+ re.end_date = new Date(re.end_date);
+ if (re.start_date < this._min_date)
+ re.start_date = setDateTime(this._min_date, this.config.first_hour);// as we are starting only with whole hours
+ else
+ re.start_date = setDateTime(getNextDay(ev.start_date), this.config.first_hour);
+
+ if (re.start_date < this._max_date && re.start_date < re.end_date) {
+ if (event_changed)
+ evs.splice(i+1,0,re);//insert part
+ else {
+ evs[i--] = re;
+ continue;
+ }
+ }
+
+ }
+ // in case of all_timed pre_render is not applied to the original event
+ // so we need to force redraw in case of dnd
+ var redraw = (this._drag_mode == 'move')?false:hold;
+ return old_prerender_events_line.call(this, evs, redraw);
+
+
+ function isOvernightEvent(ev){
+ var next_day = getNextDay(ev.start_date);
+ return (+ev.end_date > +next_day);
+ }
+ function getNextDay(date){
+ var next_day = scheduler.date.add(date, 1, "day");
+ next_day = scheduler.date.date_part(next_day);
+ return next_day;
+ }
+ function setDateTime(date, hours){
+ var val = scheduler.date.date_part(new Date(date));
+ val.setHours(hours);
+ return val;
+ }
+ };
+ var old_get_visible_events = scheduler.get_visible_events;
+ scheduler.get_visible_events = function(only_timed){
+ if (!(this.config.all_timed && this.config.multi_day))
+ return old_get_visible_events.call(this, only_timed);
+ return old_get_visible_events.call(this, false); // only timed = false
+ };
+ scheduler.attachEvent("onBeforeViewChange", function (old_mode, old_date, mode, date) {
+ scheduler._allow_dnd = (mode == "day" || mode == "week");
+ return true;
+ });
+
+ scheduler._is_main_area_event = function(ev){
+ return !!(ev._timed || this.config.all_timed === true || (this.config.all_timed == "short" && is_event_short(ev)) );
+ };
+
+ var oldUpdate = scheduler.updateEvent;
+ scheduler.updateEvent = function(id){
+ // full redraw(update_render=true) messes events order while dnd.
+ // individual redraw(update_render=false) of multiday event, which happens on select/unselect, expands event to full width of the cell and can be fixes only with full redraw.
+ // so for now full redraw is always enabled for not-dnd updates
+ var fullRedrawNeeded = (scheduler.config.all_timed && !(scheduler.isOneDayEvent(scheduler._events[id]) || scheduler.getState().drag_id));
+ if(fullRedrawNeeded){
+ var initial = scheduler.config.update_render;
+ scheduler.config.update_render = true;
+ }
+ oldUpdate.apply(scheduler, arguments);
+
+ if(fullRedrawNeeded){
+ scheduler.config.update_render = initial;
+ }
+ };
+})(); \ No newline at end of file
diff --git a/sources/ext/dhtmlxscheduler_collision.js b/sources/ext/dhtmlxscheduler_collision.js
new file mode 100644
index 0000000..13bd178
--- /dev/null
+++ b/sources/ext/dhtmlxscheduler_collision.js
@@ -0,0 +1,131 @@
+/*
+This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
+to use it in non-GPL project. Please contact sales@dhtmlx.com for details
+*/
+(function(){
+
+var temp_section;
+var before;
+
+scheduler.config.collision_limit = 1;
+
+function _setTempSection(event_id) { // for custom views (matrix, timeline, units)
+ var pr = scheduler._props?scheduler._props[scheduler._mode]:null;
+ var matrix = scheduler.matrix?scheduler.matrix[scheduler._mode]:null;
+ var checked_mode = pr||matrix; // units or matrix mode
+ if(pr)
+ var map_to = checked_mode.map_to;
+ if(matrix)
+ var map_to = checked_mode.y_property;
+ if ((checked_mode) && event_id){
+ temp_section = scheduler.getEvent(event_id)[map_to];
+ }
+}
+
+scheduler.attachEvent("onBeforeDrag",function(id){
+ _setTempSection(id);
+ return true;
+});
+scheduler.attachEvent("onBeforeLightbox",function(id){
+ var ev = scheduler.getEvent(id);
+ before = [ev.start_date, ev.end_date];
+ _setTempSection(id);
+ return true;
+});
+scheduler.attachEvent("onEventChanged",function(id){
+ if (!id) return true;
+ var ev = scheduler.getEvent(id);
+ if (!scheduler.checkCollision(ev)){
+ if (!before) return false;
+ ev.start_date = before[0];
+ ev.end_date = before[1];
+ ev._timed=this.isOneDayEvent(ev);
+ }
+ return true;
+});
+scheduler.attachEvent("onBeforeEventChanged",function(ev,e,is_new){
+ return scheduler.checkCollision(ev);
+});
+scheduler.attachEvent("onEventAdded",function(id,ev) {
+ var result = scheduler.checkCollision(ev);
+ if (!result)
+ scheduler.deleteEvent(id);
+});
+scheduler.attachEvent("onEventSave",function(id, edited_ev, is_new){
+ edited_ev = scheduler._lame_clone(edited_ev);
+ edited_ev.id = id;
+
+ //lightbox may not have 'time' section
+ if(!(edited_ev.start_date && edited_ev.end_date)){
+ var ev = scheduler.getEvent(id);
+ edited_ev.start_date = new Date(ev.start_date);
+ edited_ev.end_date = new Date(ev.end_date);
+ }
+
+ if(edited_ev.rec_type){
+ scheduler._roll_back_dates(edited_ev);
+ }
+ return scheduler.checkCollision(edited_ev); // in case user creates event on one date but then edited it another
+});
+
+scheduler.checkCollision = function(ev) {
+ var evs = [];
+ var collision_limit = scheduler.config.collision_limit;
+ if (ev.rec_type) {
+ var evs_dates = scheduler.getRecDates(ev);
+ for(var k=0; k<evs_dates.length; k++) {
+ var tevs = scheduler.getEvents(evs_dates[k].start_date, evs_dates[k].end_date);
+ for(var j=0; j<tevs.length; j++) {
+ if ((tevs[j].event_pid || tevs[j].id) != ev.id )
+ evs.push(tevs[j]);
+ }
+ }
+ } else {
+ evs = scheduler.getEvents(ev.start_date, ev.end_date);
+ for (var i=0; i<evs.length; i++) {
+ if (evs[i].id == ev.id) {
+ evs.splice(i,1);
+ break;
+ }
+ }
+ }
+
+ var pr = scheduler._props?scheduler._props[scheduler._mode]:null;
+ var matrix = scheduler.matrix?scheduler.matrix[scheduler._mode]:null;
+
+ var checked_mode = pr||matrix;
+ if(pr)
+ var map_to = checked_mode.map_to;
+ if(matrix)
+ var map_to = checked_mode.y_property;
+
+ var single = true;
+ if (checked_mode) { // custom view
+ var count = 0;
+
+ for (var i = 0; i < evs.length; i++)
+
+ if (evs[i][map_to] == ev[map_to] && evs[i].id != ev.id)
+ count++;
+
+ if (count >= collision_limit) {
+
+ single = false;
+ }
+ }
+ else {
+ if ( evs.length >= collision_limit )
+ single = false;
+ }
+ if (!single) {
+ var res = !scheduler.callEvent("onEventCollision",[ev,evs]);
+ if (!res) {
+ ev[map_to] = temp_section||ev[map_to]; // from _setTempSection for custom views
+ }
+ return res;
+ }
+ return single;
+
+}
+
+})();
diff --git a/sources/ext/dhtmlxscheduler_container_autoresize.js b/sources/ext/dhtmlxscheduler_container_autoresize.js
new file mode 100644
index 0000000..8cb15d2
--- /dev/null
+++ b/sources/ext/dhtmlxscheduler_container_autoresize.js
@@ -0,0 +1,161 @@
+/*
+This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
+to use it in non-GPL project. Please contact sales@dhtmlx.com for details
+*/
+(function() {
+
+ scheduler.config.container_autoresize = true;
+ scheduler.config.month_day_min_height = 90;
+
+ var old_pre_render_event = scheduler._pre_render_events;
+
+ scheduler._pre_render_events = function(evs, hold) {
+ if (!scheduler.config.container_autoresize) {
+ return old_pre_render_event.apply(this, arguments);
+ }
+
+ var hb = this.xy.bar_height;
+ var h_old = this._colsS.heights;
+ var h = this._colsS.heights = [0, 0, 0, 0, 0, 0, 0];
+ var data = this._els["dhx_cal_data"][0];
+
+ if (!this._table_view)
+ evs = this._pre_render_events_line(evs, hold); //ignore long events for now
+ else
+ evs = this._pre_render_events_table(evs, hold);
+
+ if (this._table_view) {
+ if (hold){
+ this._colsS.heights = h_old;
+ } else {
+ var evl = data.firstChild;
+ if (evl.rows) {
+ for (var i = 0; i < evl.rows.length; i++) {
+ h[i]++;
+ if ((h[i]) * hb > this._colsS.height - 22) { // 22 - height of cell's header
+ //we have overflow, update heights
+ var cells = evl.rows[i].cells;
+
+ var cHeight = this._colsS.height - 22;
+ if(this.config.max_month_events*1 !== this.config.max_month_events || h[i] <= this.config.max_month_events){
+ cHeight = h[i] * hb;
+ }else if( (this.config.max_month_events + 1) * hb > this._colsS.height - 22){
+ cHeight = (this.config.max_month_events + 1) * hb;
+ }
+
+ for (var j = 0; j < cells.length; j++) {
+ cells[j].childNodes[1].style.height = cHeight + "px";
+ }
+ h[i] = (h[i - 1] || 0) + cells[0].offsetHeight;
+ }
+ h[i] = (h[i - 1] || 0) + evl.rows[i].cells[0].offsetHeight;
+ }
+ h.unshift(0);
+ if (evl.parentNode.offsetHeight < evl.parentNode.scrollHeight && !evl._h_fix) {
+ //we have v-scroll, decrease last day cell
+
+ // NO CHECK SHOULD BE MADE ON VERTICAL SCROLL
+ }
+ } else {
+ if (!evs.length && this._els["dhx_multi_day"][0].style.visibility == "visible")
+ h[0] = -1;
+ if (evs.length || h[0] == -1) {
+ //shift days to have space for multiday events
+ var childs = evl.parentNode.childNodes;
+ var dh = ((h[0] + 1) * hb + 1) + "px"; // +1 so multiday events would have 2px from top and 2px from bottom by default
+ data.style.top = (this._els["dhx_cal_navline"][0].offsetHeight + this._els["dhx_cal_header"][0].offsetHeight + parseInt(dh, 10)) + 'px';
+ data.style.height = (this._obj.offsetHeight - parseInt(data.style.top, 10) - (this.xy.margin_top || 0)) + 'px';
+ var last = this._els["dhx_multi_day"][0];
+ last.style.height = dh;
+ last.style.visibility = (h[0] == -1 ? "hidden" : "visible");
+ last = this._els["dhx_multi_day"][1];
+ last.style.height = dh;
+ last.style.visibility = (h[0] == -1 ? "hidden" : "visible");
+ last.className = h[0] ? "dhx_multi_day_icon" : "dhx_multi_day_icon_small";
+ this._dy_shift = (h[0] + 1) * hb;
+ h[0] = 0;
+ }
+ }
+ }
+ }
+
+ return evs;
+ };
+
+ var checked_divs = ["dhx_cal_navline", "dhx_cal_header", "dhx_multi_day", "dhx_cal_data"];
+ var updateContainterHeight = function(is_repaint) {
+ var total_height = 0;
+ for (var i = 0; i < checked_divs.length; i++) {
+
+ var className = checked_divs[i];
+ var checked_div = (scheduler._els[className]) ? scheduler._els[className][0] : null;
+ var height = 0;
+ switch (className) {
+ case "dhx_cal_navline":
+ case "dhx_cal_header":
+ height = parseInt(checked_div.style.height, 10);
+ break;
+ case "dhx_multi_day":
+ height = (checked_div) ? checked_div.offsetHeight : 0;
+ if (height == 1)
+ height = 0;
+ break;
+ case "dhx_cal_data":
+ height = Math.max(checked_div.offsetHeight - 1, checked_div.scrollHeight);
+ var mode = scheduler.getState().mode;
+ if (mode == "month") {
+ if (scheduler.config.month_day_min_height && !is_repaint) {
+ var rows_length = checked_div.getElementsByTagName("tr").length;
+ height = rows_length * scheduler.config.month_day_min_height;
+ }
+ if (is_repaint) {
+ checked_div.style.height = height + "px";
+ }
+ }
+ if (scheduler.matrix && scheduler.matrix[mode]) {
+ if (is_repaint) {
+ height += 2;
+ checked_div.style.height = height + "px";
+ } else {
+ height = 2;
+ var cfg = scheduler.matrix[mode];
+ var rows = cfg.y_unit;
+ for(var r=0; r < rows.length; r++){
+ height += !rows[r].children ? cfg.dy : (cfg.folder_dy||cfg.dy);
+ }
+ }
+ }
+ if (mode == "day" || mode == "week") {
+ height += 2;
+ }
+ break;
+ }
+ total_height += height;
+ }
+ scheduler._obj.style.height = (total_height) + "px";
+
+ if (!is_repaint)
+ scheduler.updateView();
+ };
+
+ var conditionalUpdateContainerHeight = function() {
+ var mode = scheduler.getState().mode;
+
+ updateContainterHeight();
+ if ( (scheduler.matrix && scheduler.matrix[mode]) || mode == "month" ) {
+ window.setTimeout(function() {
+ updateContainterHeight(true);
+ }, 1);
+ }
+ };
+
+ scheduler.attachEvent("onViewChange", conditionalUpdateContainerHeight);
+ scheduler.attachEvent("onXLE", conditionalUpdateContainerHeight);
+ scheduler.attachEvent("onEventChanged", conditionalUpdateContainerHeight);
+ scheduler.attachEvent("onEventCreated", conditionalUpdateContainerHeight);
+ scheduler.attachEvent("onEventAdded", conditionalUpdateContainerHeight);
+ scheduler.attachEvent("onEventDeleted", conditionalUpdateContainerHeight);
+ scheduler.attachEvent("onAfterSchedulerResize", conditionalUpdateContainerHeight);
+ scheduler.attachEvent("onClearAll", conditionalUpdateContainerHeight);
+
+})(); \ No newline at end of file
diff --git a/sources/ext/dhtmlxscheduler_cookie.js b/sources/ext/dhtmlxscheduler_cookie.js
new file mode 100644
index 0000000..faf99b6
--- /dev/null
+++ b/sources/ext/dhtmlxscheduler_cookie.js
@@ -0,0 +1,70 @@
+/*
+This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
+to use it in non-GPL project. Please contact sales@dhtmlx.com for details
+*/
+(function(){
+ function setCookie(name,cookie_param,value) {
+ var str = name + "=" + value + (cookie_param?("; "+cookie_param):"");
+ document.cookie = str;
+ }
+ function getCookie(name) {
+ var search = name + "=";
+ if (document.cookie.length > 0) {
+ var offset = document.cookie.indexOf(search);
+ if (offset != -1) {
+ offset += search.length;
+ var end = document.cookie.indexOf(";", offset);
+ if (end == -1)
+ end = document.cookie.length;
+ return document.cookie.substring(offset, end);
+ }
+ }
+ return "";
+ }
+ var first = true;
+ scheduler.attachEvent("onBeforeViewChange",function(om,od,m,d){
+ if (first){
+ first = false;
+
+
+
+ var data=getCookie("scheduler_settings");
+ if (data){
+
+ if(!scheduler._min_date){
+ //otherwise scheduler will have incorrect date until timeout
+ //it can cause js error with 'onMouseMove' handler of key_nav.js
+ scheduler._min_date = d;
+ }
+
+ data = unescape(data).split("@");
+ data[0] = this.templates.xml_date(data[0]);
+ var view = this.isViewExists(data[1]) ? data[1] : m,
+ date = !isNaN(+data[0]) ? data[0] : d;
+
+ window.setTimeout(function(){
+ scheduler.setCurrentView(date,view);
+ },1);
+ return false;
+ }
+ }
+ var text = escape(this.templates.xml_format(d||od)+"@"+(m||om));
+ setCookie("scheduler_settings","expires=Sun, 31 Jan 9999 22:00:00 GMT",text);
+ return true;
+ });
+
+
+ // As we are blocking first render above there could be a problem in case of dynamic loading ('from' won't be defined)
+ var old_load = scheduler._load;
+ scheduler._load = function() {
+ var args = arguments;
+ if (!scheduler._date && scheduler._load_mode) {
+ var that = this;
+ window.setTimeout(function() {
+ old_load.apply(that, args);
+ },1);
+ } else {
+ old_load.apply(this, args);
+ }
+ }
+})(); \ No newline at end of file
diff --git a/sources/ext/dhtmlxscheduler_editors.js b/sources/ext/dhtmlxscheduler_editors.js
new file mode 100644
index 0000000..f68f2c1
--- /dev/null
+++ b/sources/ext/dhtmlxscheduler_editors.js
@@ -0,0 +1,158 @@
+/*
+This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
+to use it in non-GPL project. Please contact sales@dhtmlx.com for details
+*/
+scheduler.form_blocks['combo']={
+ render:function(sns) {
+ if (!sns.cached_options)
+ sns.cached_options = {};
+ var res = '';
+ res += "<div class='"+sns.type+"' style='height:"+(sns.height||20)+"px;' ></div>";
+ return res;
+ },
+ set_value:function(node,value,ev,config){
+ (function(){
+ resetCombo();
+ var id = scheduler.attachEvent("onAfterLightbox",function(){
+ // otherwise destructor will never be called after form reset(e.g. in readonly event mode)
+ resetCombo();
+ scheduler.detachEvent(id);
+ });
+ function resetCombo(){
+ if(node._combo && node._combo.DOMParent) {
+ node._combo.destructor();
+ }
+ }
+ })();
+ window.dhx_globalImgPath = config.image_path||'/';
+ node._combo = new dhtmlXCombo(node, config.name, node.offsetWidth-8);
+ if (config.onchange)
+ node._combo.attachEvent("onChange", config.onchange);
+
+ if (config.options_height)
+ node._combo.setOptionHeight(config.options_height);
+ var combo = node._combo;
+ combo.enableFilteringMode(config.filtering, config.script_path||null, !!config.cache);
+
+ if (!config.script_path) { // script-side filtration is used
+ var all_options = [];
+ for (var i = 0; i < config.options.length; i++) {
+ var option = config.options[i];
+ var single_option = [
+ option.key,
+ option.label,
+ option.css
+ ];
+ all_options.push(single_option);
+ }
+ combo.addOption(all_options);
+ if (ev[config.map_to]) {
+ var index = combo.getIndexByValue(ev[config.map_to]);
+ combo.selectOption(index);
+ }
+ } else { // server-side filtration is used
+ var selected_id = ev[config.map_to];
+ if (selected_id) {
+ if (config.cached_options[selected_id]) {
+ combo.addOption(selected_id, config.cached_options[selected_id]);
+ combo.disable(1);
+ combo.selectOption(0);
+ combo.disable(0);
+ } else {
+ dhtmlxAjax.get(config.script_path+"?id="+selected_id+"&uid="+scheduler.uid(), function(result){
+ var option = result.doXPath("//option")[0];
+ var label = option.childNodes[0].nodeValue;
+ config.cached_options[selected_id] = label;
+ combo.addOption(selected_id, label);
+ combo.disable(1);
+ combo.selectOption(0);
+ combo.disable(0);
+ });
+ }
+ } else {
+ combo.setComboValue("");
+ }
+ }
+ },
+ get_value:function(node,ev,config) {
+ var selected_id = node._combo.getSelectedValue(); // value = key
+ if (config.script_path) {
+ config.cached_options[selected_id] = node._combo.getSelectedText();
+ }
+ return selected_id;
+ },
+ focus:function(node){
+ }
+};
+
+scheduler.form_blocks['radio']={
+ render:function(sns) {
+ var res = '';
+ res += "<div class='dhx_cal_ltext dhx_cal_radio' style='height:"+sns.height+"px;' >";
+ for (var i=0; i<sns.options.length; i++) {
+ var id = scheduler.uid();
+ res += "<input id='"+id+"' type='radio' name='"+sns.name+"' value='"+sns.options[i].key+"'><label for='"+id+"'>"+" "+sns.options[i].label+"</label>";
+ if(sns.vertical)
+ res += "<br/>";
+ }
+ res += "</div>";
+
+ return res;
+ },
+ set_value:function(node,value,ev,config){
+ var radiobuttons = node.getElementsByTagName('input');
+ for (var i = 0; i < radiobuttons.length; i++) {
+ radiobuttons[i].checked = false;
+ var checked_value = ev[config.map_to]||value;
+ if (radiobuttons[i].value == checked_value) {
+ radiobuttons[i].checked = true;
+ }
+ }
+ },
+ get_value:function(node,ev,config){
+ var radiobuttons = node.getElementsByTagName('input');
+ for(var i=0; i<radiobuttons.length; i++) {
+ if(radiobuttons[i].checked) {
+ return radiobuttons[i].value;
+ }
+ }
+ },
+ focus:function(node){
+ }
+};
+
+scheduler.form_blocks['checkbox']={
+ render:function(sns) {
+ if (scheduler.config.wide_form)
+ return '<div class="dhx_cal_wide_checkbox" '+(sns.height?("style='height:"+sns.height+"px;'"):"")+'></div>';
+ else
+ return '';
+ },
+ set_value:function(node,value,ev,config){
+ node=document.getElementById(config.id);
+ var id = scheduler.uid();
+ var isChecked = (typeof config.checked_value != "undefined") ? ev[config.map_to] == config.checked_value : !!value;
+ node.className += " dhx_cal_checkbox";
+ var check_html = "<input id='"+id+"' type='checkbox' value='true' name='"+config.name+"'"+((isChecked)?"checked='true'":'')+"'>";
+ var label_html = "<label for='"+id+"'>"+(scheduler.locale.labels["section_"+config.name]||config.name)+"</label>";
+ if (scheduler.config.wide_form){
+ node.innerHTML = label_html;
+ node.nextSibling.innerHTML=check_html;
+ } else
+ node.innerHTML=check_html+label_html;
+
+ if (config.handler) {
+ var checkbox = node.getElementsByTagName('input')[0];
+ checkbox.onclick = config.handler;
+ }
+ },
+ get_value:function(node,ev,config){
+ node=document.getElementById(config.id);
+ var checkbox = node.getElementsByTagName('input')[0]; // moved to the header
+ if (!checkbox)
+ checkbox = node.nextSibling.getElementsByTagName('input')[0];
+ return (checkbox.checked)?(config.checked_value||true):(config.unchecked_value||false);
+ },
+ focus:function(node){
+ }
+};
diff --git a/sources/ext/dhtmlxscheduler_expand.js b/sources/ext/dhtmlxscheduler_expand.js
new file mode 100644
index 0000000..2dd9361
--- /dev/null
+++ b/sources/ext/dhtmlxscheduler_expand.js
@@ -0,0 +1,73 @@
+/*
+This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
+to use it in non-GPL project. Please contact sales@dhtmlx.com for details
+*/
+scheduler.expand = function() {
+ var t = scheduler._obj;
+ do {
+ t._position = t.style.position || "";
+ t.style.position = "static";
+ } while ((t = t.parentNode) && t.style);
+ t = scheduler._obj;
+ t.style.position = "absolute";
+ t._width = t.style.width;
+ t._height = t.style.height;
+ t.style.width = t.style.height = "100%";
+ t.style.top = t.style.left = "0px";
+
+ var top = document.body;
+ top.scrollTop = 0;
+
+ top = top.parentNode;
+ if (top)
+ top.scrollTop = 0;
+ document.body._overflow = document.body.style.overflow || "";
+ document.body.style.overflow = "hidden";
+ scheduler._maximize();
+};
+scheduler.collapse = function() {
+ var t = scheduler._obj;
+ do {
+ t.style.position = t._position;
+ } while ((t = t.parentNode) && t.style);
+ t = scheduler._obj;
+ t.style.width = t._width;
+ t.style.height = t._height;
+ document.body.style.overflow = document.body._overflow;
+ scheduler._maximize();
+};
+scheduler.attachEvent("onTemplatesReady", function() {
+ var t = document.createElement("DIV");
+ t.className = "dhx_expand_icon";
+ scheduler.toggleIcon = t;
+ scheduler._obj.appendChild(t);
+ t.onclick = function() {
+ if (!scheduler.expanded)
+ scheduler.expand(); else
+ scheduler.collapse();
+ }
+});
+scheduler._maximize = function() {
+ this.expanded = !this.expanded;
+ this.toggleIcon.style.backgroundPosition = "0 " + (this.expanded ? "0" : "18") + "px";
+
+ var directions = ['left', 'top'];
+ for (var i = 0; i < directions.length; i++) {
+ var margin = scheduler.xy['margin_' + directions[i]];
+ var prev_margin = scheduler['_prev_margin_' + directions[i]];
+ if (scheduler.xy['margin_' + directions[i]]) {
+ scheduler['_prev_margin_' + directions[i]] = scheduler.xy['margin_' + directions[i]];
+ scheduler.xy['margin_' + directions[i]] = 0;
+ } else {
+ if (prev_margin) {
+ scheduler.xy['margin_' + directions[i]] = scheduler['_prev_margin_' + directions[i]];
+ delete scheduler['_prev_margin_' + directions[i]];
+ }
+ }
+ }
+
+ if (scheduler.callEvent("onSchedulerResize", [])) {
+ scheduler.update_view();
+ scheduler.callEvent("onAfterSchedulerResize");
+ }
+};
diff --git a/sources/ext/dhtmlxscheduler_grid_view.js b/sources/ext/dhtmlxscheduler_grid_view.js
new file mode 100644
index 0000000..4ce1f5d
--- /dev/null
+++ b/sources/ext/dhtmlxscheduler_grid_view.js
@@ -0,0 +1,466 @@
+/*
+This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
+to use it in non-GPL project. Please contact sales@dhtmlx.com for details
+*/
+(function(){
+ scheduler._grid = {
+ sort_rules:{
+ "int":function(a,b, getVal){ return getVal(a)*1 < getVal(b)*1?1:-1},
+ "str":function(a,b, getVal){ return getVal(a) < getVal(b)?1:-1},
+ "date":function(a,b, getVal){ return new Date(getVal(a))< new Date(getVal(b))?1:-1}
+ },
+ _getObjName:function(name){
+ return "grid_"+name;
+ },
+ _getViewName:function(objName){
+ return objName.replace(/^grid_/,'');
+ }
+ };
+}
+)();
+/*
+obj={
+ name:'grid_name'
+ fields:[
+ { id:"id", label:"Id", width:80, sort:"int/date/str", template:function(start_date, end_date, ev){ return ""}, align:"right/left/center" },
+ { id:"text", label:"Text", width:'*', css:"class_name", sort:function(a,b){ return 1 or -1}, valign:'top/bottom/middle' }
+ ...
+ ],
+ from:new Date(0),
+ to:Date:new Date(9999,1,1),
+ rowHeight:int,
+ paging:true/false,
+ select:true/false
+}
+*/
+
+
+scheduler.createGridView=function(obj){
+
+ var name = obj.name || 'grid';
+ var objName = scheduler._grid._getObjName(name);
+
+ scheduler.config[name + '_start'] = obj.from ||(new Date(0));
+ scheduler.config[name + '_end'] = obj.to || (new Date(9999,1,1));
+
+ scheduler[objName] = obj;
+ scheduler[objName].defPadding = 8;
+ scheduler[objName].columns = scheduler[objName].fields;
+ delete scheduler[objName].fields;
+ function isValidSize(size){
+ return !(size !== undefined && (size*1 != size || size < 0));
+ }
+
+ var cols = scheduler[objName].columns;
+ for(var i=0; i < cols.length; i++){
+ if(isValidSize(cols[i].width))
+ cols[i].initialWidth = cols[i].width;
+ if(!isValidSize(cols[i].paddingLeft))
+ delete cols[i].paddingLeft;
+ if(!isValidSize(cols[i].paddingRight))
+ delete cols[i].paddingRight;
+ }
+
+ scheduler[objName].select = obj.select === undefined ? true : obj.select;
+ if(scheduler.locale.labels[name +'_tab'] === undefined)
+ scheduler.locale.labels[name +'_tab'] = scheduler[objName].label || scheduler.locale.labels.grid_tab;
+
+ scheduler[objName]._selected_divs = [];
+
+ scheduler.date[name+'_start']=function(d){ return d; };
+ scheduler.date['add_' + name] = function(date, inc){
+ var ndate = new Date(date);
+ ndate.setMonth(ndate.getMonth()+inc);
+ return ndate;
+ };
+
+ scheduler.templates[name+"_date"] = function(start, end){
+ return scheduler.templates.day_date(start)+" - "+scheduler.templates.day_date(end)
+ };
+ scheduler.templates[name + '_full_date'] = function(start,end,ev){
+ if (scheduler.isOneDayEvent(ev))
+ return this[name + '_single_date'](start);
+ else
+ return scheduler.templates.day_date(start)+" &ndash; "+scheduler.templates.day_date(end);
+ };
+ scheduler.templates[name + '_single_date'] = function(date){
+ return scheduler.templates.day_date(date)+" "+this.event_date(date);
+ };
+ scheduler.templates[name + '_field'] = function(field_name, event){
+ return event[field_name];
+ };
+
+ scheduler.attachEvent("onTemplatesReady",function(){
+
+ scheduler.attachEvent("onDblClick",function(event_id, native_event_object){
+ if(this._mode == name){
+ scheduler._click.buttons['details'](event_id)
+ return false;
+ }
+ return true;
+ });
+
+ scheduler.attachEvent("onClick",function(event_id, native_event_object){
+ if(this._mode == name && scheduler[objName].select ){
+ scheduler._grid.unselectEvent('', name);
+ scheduler._grid.selectEvent(event_id, name, native_event_object);
+ return false;
+ }
+ return true;
+ });
+
+ scheduler.attachEvent("onSchedulerResize", function() {
+ if (this._mode == name) {
+ this[name + '_view'](true);
+ // timeout used to run code after all onSchedulerResize handlers are finished
+ window.setTimeout(function(){
+ // we need to call event manually because handler return false, and blocks default logic
+ scheduler.callEvent("onAfterSchedulerResize", []);
+ },1);
+ return false;
+ }
+ return true;
+ });
+
+
+ var old = scheduler.render_data;
+ scheduler.render_data=function(evs){
+ if (this._mode == name)
+ scheduler._grid._fill_grid_tab(objName);
+ else
+ return old.apply(this,arguments);
+ };
+
+ var old_render_view_data = scheduler.render_view_data;
+ scheduler.render_view_data=function(){
+ if(this._mode == name) {
+ scheduler._grid._gridScrollTop = scheduler._els["dhx_cal_data"][0].childNodes[0].scrollTop;
+ scheduler._els["dhx_cal_data"][0].childNodes[0].scrollTop = 0;
+ scheduler._els["dhx_cal_data"][0].style.overflowY = 'auto';
+ }
+ else {
+ scheduler._els["dhx_cal_data"][0].style.overflowY = 'auto';
+ }
+ return old_render_view_data.apply(this,arguments);
+ }
+});
+
+
+ scheduler[name+'_view']=function(mode){
+ if (mode){
+ scheduler._min_date = scheduler[objName].paging ? scheduler.date[name+'_start'](new Date(scheduler._date)) : scheduler.config[name + '_start'];
+ scheduler._max_date = scheduler[objName].paging ? scheduler.date.add(scheduler._min_date, 1, name) : scheduler.config[name + '_end'];
+
+ scheduler._grid.set_full_view(objName);
+ if(scheduler._min_date > new Date(0) && scheduler._max_date < (new Date(9999,1,1)))
+ scheduler._els["dhx_cal_date"][0].innerHTML=scheduler.templates[name+"_date"](scheduler._min_date,scheduler._max_date);
+ else
+ scheduler._els["dhx_cal_date"][0].innerHTML="";
+
+ //grid tab activated
+ scheduler._grid._fill_grid_tab(objName);
+ scheduler._gridView = objName;
+ } else {
+ scheduler._grid._sort_marker = null;
+ delete scheduler._gridView;
+ scheduler._rendered=[];
+ scheduler[objName]._selected_divs = [];
+ //grid tab de-activated
+ }
+ };
+
+
+}
+
+
+scheduler.dblclick_dhx_grid_area=function(){
+ if (!this.config.readonly && this.config.dblclick_create)
+ this.addEventNow();
+};
+
+if(scheduler._click.dhx_cal_header){
+ scheduler._old_header_click = scheduler._click.dhx_cal_header;
+}
+scheduler._click.dhx_cal_header=function(e){
+ if(scheduler._gridView){
+ var event = e||window.event;
+ var params = scheduler._grid.get_sort_params(event, scheduler._gridView);
+
+ scheduler._grid.draw_sort_marker(event.originalTarget || event.srcElement, params.dir);
+
+ scheduler.clear_view();
+ scheduler._grid._fill_grid_tab(scheduler._gridView, params);
+ }
+ else if(scheduler._old_header_click)
+ return scheduler._old_header_click.apply(this,arguments);
+};
+
+scheduler._grid.selectEvent = function(id, view_name, native_event_object){
+ if(scheduler.callEvent("onBeforeRowSelect",[id,native_event_object])){
+ var objName = scheduler._grid._getObjName(view_name);
+
+ scheduler.for_rendered(id, function(event_div){
+ event_div.className += " dhx_grid_event_selected";
+ scheduler[objName]._selected_divs.push(event_div);
+ });
+ scheduler._select_id = id;
+ }
+};
+
+scheduler._grid._unselectDiv= function(div){
+ div.className = div.className.replace(/ dhx_grid_event_selected/,'');
+}
+scheduler._grid.unselectEvent = function(id, view_name){
+ var objName = scheduler._grid._getObjName(view_name);
+ if(!objName || !scheduler[objName]._selected_divs)
+ return;
+
+ if(!id){
+ for(var i=0; i<scheduler[objName]._selected_divs.length; i++)
+ scheduler._grid._unselectDiv(scheduler[objName]._selected_divs[i]);
+
+ scheduler[objName]._selected_divs = [];
+
+ }else{
+ for(var i=0; i<scheduler[objName]._selected_divs.length; i++)
+ if(scheduler[objName]._selected_divs[i].getAttribute('event_id') == id){
+ scheduler._grid._unselectDiv(scheduler[objName]._selected_divs[i]);
+ scheduler[objName]._selected_divs.slice(i,1);
+ break;
+ }
+
+ }
+};
+
+scheduler._grid.get_sort_params = function(event, objName){
+ var targ = event.originalTarget || event.srcElement;
+ if(targ.className == 'dhx_grid_view_sort')
+ targ = targ.parentNode;
+ if(!targ.className || targ.className.indexOf("dhx_grid_sort_asc") == -1)
+ var direction = 'asc';
+ else
+ var direction = 'desc';
+
+ var index = 0;
+ for(var i =0; i < targ.parentNode.childNodes.length; i++){
+ if(targ.parentNode.childNodes[i] == targ){
+ index = i;
+ break;
+ }
+ }
+
+
+
+ var value = null;
+ if(scheduler[objName].columns[index].template){
+ var template = scheduler[objName].columns[index].template
+ value = function(ev){
+ return template(ev.start_date, ev.end_date, ev);
+ };
+ }else{
+ var field = scheduler[objName].columns[index].id;
+ if(field == "date")
+ field = "start_date";
+ value = function(ev){ return ev[field];}
+ }
+
+ var rule = scheduler[objName].columns[index].sort;
+
+ if(typeof rule != 'function'){
+ rule = scheduler._grid.sort_rules[rule] || scheduler._grid.sort_rules['str'];
+ }
+
+ return {dir:direction, value:value, rule:rule};
+};
+
+scheduler._grid.draw_sort_marker = function(node, direction){
+ if(node.className == 'dhx_grid_view_sort')
+ node = node.parentNode;
+
+ if(scheduler._grid._sort_marker){
+ scheduler._grid._sort_marker.className = scheduler._grid._sort_marker.className.replace(/( )?dhx_grid_sort_(asc|desc)/, '');
+ scheduler._grid._sort_marker.removeChild(scheduler._grid._sort_marker.lastChild);
+ }
+
+ node.className += " dhx_grid_sort_"+direction;
+ scheduler._grid._sort_marker = node;
+ var html = "<div class='dhx_grid_view_sort' style='left:"+(+node.style.width.replace('px','') -15+node.offsetLeft)+"px'>&nbsp;</div>";
+ node.innerHTML += html;
+
+};
+
+scheduler._grid.sort_grid=function(sort){
+
+ var sort = sort || {dir:'desc', value:function(ev){return ev.start_date;}, rule:scheduler._grid.sort_rules['date']};
+
+ var events = scheduler.get_visible_events();
+
+ if(sort.dir == 'desc')
+ events.sort(function(a,b){return sort.rule(a,b,sort.value)});
+ else
+ events.sort(function(a,b){return -sort.rule(a,b, sort.value)});
+ return events;
+};
+
+
+
+scheduler._grid.set_full_view = function(mode){
+ if (mode){
+ var l = scheduler.locale.labels;
+ var html =scheduler._grid._print_grid_header(mode);
+
+ scheduler._els["dhx_cal_header"][0].innerHTML= html;
+ scheduler._table_view=true;
+ scheduler.set_sizes();
+ }
+};
+scheduler._grid._calcPadding = function(column, parent){
+ var padding = (column.paddingLeft !== undefined ? 1*column.paddingLeft : scheduler[parent].defPadding)
+ + (column.paddingRight !== undefined ? 1*column.paddingRight : scheduler[parent].defPadding);
+ return padding;
+};
+
+scheduler._grid._getStyles = function(column, items){
+ var cell_style = [], style = "";
+ for(var i=0; items[i]; i++ ){
+ style = items[i] + ":";
+ switch (items[i]){
+ case "text-align":
+ if(column.align)
+ cell_style.push(style+column.align);
+ break;
+ case "vertical-align":
+ if(column.valign)
+ cell_style.push(style+column.valign);
+ break;
+ case "padding-left":
+ if(column.paddingLeft != undefined)
+ cell_style.push(style+(column.paddingLeft||'0') + "px");
+ break;
+ case "padding-right":
+ if(column.paddingRight != undefined)
+ cell_style.push(style+(column.paddingRight||'0') + "px");
+ break;
+ }
+ }
+ return cell_style;
+};
+
+scheduler._grid._fill_grid_tab = function(objName, sort){
+ //get current date
+ var date = scheduler._date;
+ //select events for which data need to be printed
+ var events = scheduler._grid.sort_grid(sort)
+
+ //generate html for the view
+ var columns = scheduler[objName].columns;
+
+ var html = "<div>";
+ var left = -2;//column borders at the same pos as header borders...
+ for(var i=0; i < columns.length; i++){
+ var padding = scheduler._grid._calcPadding(columns[i], objName);
+ left +=columns[i].width + padding ;//
+ if(i < columns.length - 1)
+ html += "<div class='dhx_grid_v_border' style='left:"+(left)+"px'></div>";
+ }
+ html += "</div>"
+ html +="<div class='dhx_grid_area'><table>";
+
+ for (var i=0; i<events.length; i++){
+ html += scheduler._grid._print_event_row(events[i], objName);
+ }
+
+ html +="</table></div>";
+ //render html
+ scheduler._els["dhx_cal_data"][0].innerHTML = html;
+ scheduler._els["dhx_cal_data"][0].scrollTop = scheduler._grid._gridScrollTop||0;
+
+ var t=scheduler._els["dhx_cal_data"][0].getElementsByTagName("tr");
+
+ scheduler._rendered=[];
+ for (var i=0; i < t.length; i++){
+ scheduler._rendered[i]=t[i]
+ }
+
+};
+scheduler._grid._print_event_row = function(ev, objName){
+
+ var styles = [];
+ if(ev.color)
+ styles.push("background:"+ev.color);
+ if(ev.textColor)
+ styles.push("color:"+ev.textColor);
+ if(ev._text_style)
+ styles.push(ev._text_style);
+ if(scheduler[objName]['rowHeight'])
+ styles.push('height:'+scheduler[objName]['rowHeight'] + 'px');
+
+ var style = "";
+ if(styles.length){
+ style = "style='"+styles.join(";")+"'";
+ }
+
+ var columns = scheduler[objName].columns;
+ var ev_class = scheduler.templates.event_class(ev.start_date, ev.end_date, ev);
+
+ var html ="<tr class='dhx_grid_event"+(ev_class? ' '+ev_class:'')+"' event_id='"+ev.id+"' " + style + ">";
+
+ var name = scheduler._grid._getViewName(objName);
+ var availStyles = ["text-align", "vertical-align", "padding-left","padding-right"];
+ for(var i =0; i < columns.length; i++){
+ var value;
+ if(columns[i].template){
+ value = columns[i].template(ev.start_date, ev.end_date, ev);
+ }else if(columns[i].id == 'date') {
+ value = scheduler.templates[name + '_full_date'](ev.start_date, ev.end_date, ev);
+ }else if(columns[i].id == 'start_date' || columns[i].id == 'end_date' ){
+ value = scheduler.templates[name + '_single_date'](ev[columns[i].id]);
+ }else{
+ value = scheduler.templates[name + '_field'](columns[i].id, ev);
+ }
+
+ var cell_style = scheduler._grid._getStyles(columns[i], availStyles);
+
+ var className = columns[i].css ? (" class=\""+columns[i].css+"\"") : "";
+
+ html+= "<td style='width:"+ (columns[i].width )+"px;"+cell_style.join(";")+"' "+className+">"+value+"</td>";
+
+ }
+ html+="<td class='dhx_grid_dummy'></td></tr>";
+
+ return html;
+};
+
+scheduler._grid._print_grid_header = function(objName){
+ var head = "<div class='dhx_grid_line'>";
+
+ var columns = scheduler[objName].columns;
+ var widths = [];
+
+ var unsized_columns = columns.length;
+ var avail_width = scheduler._obj.clientWidth - 2*columns.length -20;//-20 for possible scrollbar, -length for borders
+ for(var ind=0; ind < columns.length; ind++){
+
+ var val = columns[ind].initialWidth*1;
+ if(!isNaN(val) && columns[ind].initialWidth != '' && columns[ind].initialWidth != null && typeof columns[ind].initialWidth != 'boolean'){
+
+ unsized_columns--;
+ avail_width -= val;
+ widths[ind] = val;
+ }else {
+ widths[ind] = null;
+ }
+ }
+
+ var unsized_width = Math.floor(avail_width / unsized_columns);
+ var availStyles = ["text-align", "padding-left","padding-right"];
+ for(var i=0; i < columns.length; i++){
+ var column_width = !widths[i] ? unsized_width : widths[i];
+ columns[i].width = column_width - scheduler._grid._calcPadding(columns[i], objName);
+ var cell_style = scheduler._grid._getStyles(columns[i], availStyles);
+ head += "<div style='width:"+(columns[i].width -1)+"px;"+cell_style.join(";")+"'>" + (columns[i].label === undefined ? columns[i].id : columns[i].label) + "</div>";
+ }
+ head +="</div>";
+
+ return head;
+};
diff --git a/sources/ext/dhtmlxscheduler_html_templates.js b/sources/ext/dhtmlxscheduler_html_templates.js
new file mode 100644
index 0000000..a7df078
--- /dev/null
+++ b/sources/ext/dhtmlxscheduler_html_templates.js
@@ -0,0 +1,19 @@
+/*
+This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
+to use it in non-GPL project. Please contact sales@dhtmlx.com for details
+*/
+scheduler.attachEvent("onTemplatesReady",function(){
+ var els = document.body.getElementsByTagName("DIV");
+ for (var i=0; i < els.length; i++) {
+ var cs = els[i].className||"";
+ cs = cs.split(":");
+ if (cs.length == 2 && cs[0] == "template"){
+ var code = "return \""+(els[i].innerHTML||"").replace(/\"/g,"\\\"").replace(/[\n\r]+/g,"")+"\";";
+ code = unescape(code).replace(/\{event\.([a-z]+)\}/g,function(all,mask){
+ return '"+ev.'+mask+'+"';
+ });
+ scheduler.templates[cs[1]]=Function("start","end","ev",code);
+ els[i].style.display='none';
+ }
+ };
+}) \ No newline at end of file
diff --git a/sources/ext/dhtmlxscheduler_key_nav.js b/sources/ext/dhtmlxscheduler_key_nav.js
new file mode 100644
index 0000000..253d0ae
--- /dev/null
+++ b/sources/ext/dhtmlxscheduler_key_nav.js
@@ -0,0 +1,91 @@
+/*
+This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
+to use it in non-GPL project. Please contact sales@dhtmlx.com for details
+*/
+//Initial idea and implementation by Steve MC
+(scheduler._temp_key_scope = function (){
+
+var isLightboxOpen = false;
+var date; // used for copy and paste operations
+var isCopy = null;
+
+scheduler.attachEvent("onBeforeLightbox",function(){ isLightboxOpen = true; return true; });
+scheduler.attachEvent("onAfterLightbox",function(){ isLightboxOpen = false; return true; });
+
+scheduler.attachEvent("onMouseMove", function(id,e){
+ date = scheduler.getActionData(e).date;
+});
+
+function clear_event_after(ev){
+ delete ev.rec_type; delete ev.rec_pattern;
+ delete ev.event_pid; delete ev.event_length;
+}
+
+dhtmlxEvent(document,(_isOpera?"keypress":"keydown"),function(e){
+ e=e||event;
+ if (!isLightboxOpen){
+
+ var scheduler = window.scheduler;
+
+ if (e.keyCode == 37 || e.keyCode == 39) { // Left, Right arrows
+ e.cancelBubble = true;
+
+ var next = scheduler.date.add(scheduler._date,(e.keyCode == 37 ? -1 : 1 ),scheduler._mode);
+ scheduler.setCurrentView(next);
+ return true;
+ }
+
+ var select_id = scheduler._select_id;
+ if (e.ctrlKey && e.keyCode == 67) { // CTRL+C
+ if (select_id) {
+ scheduler._buffer_id = select_id;
+ isCopy = true;
+ scheduler.callEvent("onEventCopied", [scheduler.getEvent(select_id)]);
+ }
+ return true;
+ }
+ if (e.ctrlKey && e.keyCode == 88) { // CTRL+X
+ if (select_id) {
+ isCopy = false;
+ scheduler._buffer_id = select_id;
+ var ev = scheduler.getEvent(select_id);
+ scheduler.updateEvent(ev.id);
+ scheduler.callEvent("onEventCut", [ev]);
+ }
+ }
+
+ if (e.ctrlKey && e.keyCode == 86) { // CTRL+V
+ var ev = scheduler.getEvent(scheduler._buffer_id);
+ if (ev) {
+ var event_duration = ev.end_date-ev.start_date;
+ if (isCopy) {
+ var new_ev = scheduler._lame_clone(ev);
+ clear_event_after(new_ev);
+ new_ev.id = scheduler.uid();
+ new_ev.start_date = new Date(date);
+ new_ev.end_date = new Date(new_ev.start_date.valueOf() + event_duration);
+ scheduler.addEvent(new_ev);
+ scheduler.callEvent("onEventPasted", [isCopy, new_ev, ev]);
+ }
+ else { // cut operation
+ var copy = scheduler._lame_copy({}, ev);
+ clear_event_after(copy);
+ copy.start_date = new Date(date);
+ copy.end_date = new Date(copy.start_date.valueOf() + event_duration);
+ var res = scheduler.callEvent("onBeforeEventChanged",[copy, e, false]);
+ if (res) {
+ ev.start_date = new Date(copy.start_date);
+ ev.end_date = new Date(copy.end_date);
+ scheduler.render_view_data(); // need to redraw all events
+
+ scheduler.callEvent("onEventPasted", [isCopy, ev, copy]);
+ isCopy = true; // switch to copy after first paste operation
+ }
+ }
+ }
+ return true;
+ }
+ }
+});
+
+})(); \ No newline at end of file
diff --git a/sources/ext/dhtmlxscheduler_limit.js b/sources/ext/dhtmlxscheduler_limit.js
new file mode 100644
index 0000000..304c344
--- /dev/null
+++ b/sources/ext/dhtmlxscheduler_limit.js
@@ -0,0 +1,941 @@
+/*
+This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
+to use it in non-GPL project. Please contact sales@dhtmlx.com for details
+*/
+scheduler.config.limit_start = null;
+scheduler.config.limit_end = null;
+scheduler.config.limit_view = false;
+scheduler.config.check_limits = true;
+scheduler.config.mark_now = true;
+scheduler.config.display_marked_timespans = true;
+
+(scheduler._temp_limit_scope = function(){
+ var before = null;
+ var dhx_time_block = "dhx_time_block";
+ var default_timespan_type = "default";
+ var fix_options = function(options, days, zones) {
+ if (days instanceof Date && zones instanceof Date) {
+ options.start_date = days;
+ options.end_date = zones
+ } else {
+ options.days = days;
+ options.zones = zones;
+ }
+ return options;
+ };
+ var get_resulting_options = function(days, zones, sections) {
+ var options = (typeof days == "object") ? days : { days: days };
+ options.type = dhx_time_block;
+ options.css = "";
+ if (zones) {
+ if (sections)
+ options.sections = sections;
+ options = fix_options(options, days, zones);
+ }
+ return options;
+ };
+ scheduler.blockTime = function(days, zones, sections){
+ var options = get_resulting_options(days, zones, sections);
+ return scheduler.addMarkedTimespan(options);
+ };
+ scheduler.unblockTime = function(days, zones, sections) {
+ zones = zones || "fullday";
+ var options = get_resulting_options(days, zones, sections);
+ return scheduler.deleteMarkedTimespan(options);
+ };
+ scheduler.attachEvent("onBeforeViewChange",function(om,od,nm,nd){
+ nd = nd||od; nm = nm||om;
+ if (scheduler.config.limit_view){
+ if (nd.valueOf()>scheduler.config.limit_end.valueOf() || this.date.add(nd,1,nm)<=scheduler.config.limit_start.valueOf()){
+ setTimeout(function(){
+ scheduler.setCurrentView(scheduler._date, nm);
+ },1);
+ return false;
+ }
+ }
+ return true;
+ });
+ scheduler.checkInMarkedTimespan = function(ev, timespan_type, on_overlap){
+ timespan_type = timespan_type || default_timespan_type;
+
+ var res = true;
+ var temp_start_date = new Date(ev.start_date.valueOf());
+ var temp_end_date = scheduler.date.add(temp_start_date, 1, "day");
+ var timespans = scheduler._marked_timespans;
+ for (; temp_start_date < ev.end_date; temp_start_date = scheduler.date.date_part(temp_end_date), temp_end_date = scheduler.date.add(temp_start_date, 1, "day") ) {
+ var day_value = +scheduler.date.date_part( new Date(temp_start_date) ); // the first part of event not necessarily contains only date part
+ var day_index = temp_start_date.getDay();
+
+ var zones = getZones(ev, timespans, day_index, day_value, timespan_type);
+ if (zones){
+ for (var i = 0; i < zones.length; i+=2) {
+
+ // they may change for new event if it passes limit zone
+ var sm = scheduler._get_zone_minutes(temp_start_date);
+ var em = ( ev.end_date>temp_end_date || ev.end_date.getDate() != temp_start_date.getDate() ) ? 1440 : scheduler._get_zone_minutes(ev.end_date);
+
+ var sz = zones[i];
+ var ez = zones[i+1];
+ if (sz<em && ez>sm) {
+ if(on_overlap == "function"){
+ //handler allows to cancel overlapping
+ //actually needed only to keep default behavior of limits
+ res = on_overlap(ev, sm, em, sz, ez);//event object, event start/end minutes in 'zones' format, zone start/end minutes
+ }else{
+ res = false;
+ }
+ if(!res)
+ break;
+ }
+ }
+ }
+ }
+ return !res;
+ };
+ var blocker = scheduler.checkLimitViolation = function(event){
+ if(!event)
+ return true;
+ if (!scheduler.config.check_limits)
+ return true;
+ var s = scheduler;
+ var c = s.config;
+ var evs = [];
+ if (event.rec_type) {
+ evs = scheduler.getRecDates(event);
+ } else {
+ evs = [event];
+ }
+
+ var complete_res = true;
+ for (var p=0; p<evs.length; p++) {
+ var res = true;
+ var ev = evs[p];
+ // Event could have old _timed property (e.g. we are creating event with DND on timeline view and crossed day)
+ ev._timed = scheduler.isOneDayEvent(ev);
+
+ res = (c.limit_start && c.limit_end) ? (ev.start_date.valueOf() >= c.limit_start.valueOf() && ev.end_date.valueOf() <= c.limit_end.valueOf()) : true;
+ if (res){
+ res = !scheduler.checkInMarkedTimespan(ev, dhx_time_block, function(ev, sm, em, sz, ez){
+ //try crop event to allow placing
+ var allow = true;
+ if (sm<=ez && sm >=sz){
+ if (ez == 24*60 || em<ez){
+ allow = false;
+ }
+ if(ev._timed && s._drag_id && s._drag_mode == "new-size"){
+ ev.start_date.setHours(0);
+ ev.start_date.setMinutes(ez);
+ }
+ else {
+ allow = false;
+ }
+ }
+ if ((em>=sz && em<ez) || (sm < sz && em > ez)){
+ if(ev._timed && s._drag_id && s._drag_mode == "new-size"){
+ ev.end_date.setHours(0);
+ ev.end_date.setMinutes(sz);
+ }
+ else {
+ allow = false;
+ }
+ }
+ return allow;
+ });
+ }
+ if (!res) {
+ s._drag_id = null;
+ s._drag_mode = null;
+ res = (s.checkEvent("onLimitViolation")) ? s.callEvent("onLimitViolation",[ev.id, ev]) : res;
+ }
+ complete_res = complete_res && res;
+ }
+ return complete_res;
+
+
+ };
+
+ function getZones(ev, timespans, day_index, day_value, timespan_type){
+ var s = scheduler;
+ //containers for 'unit' and 'timeline' views, and related 'section_id' properties
+ var zones = [];
+ var containers = {
+ '_props':'map_to',
+ 'matrix':'y_property'};
+ //check blocked sections in all units and timelines
+ for(var container in containers){
+ var property = containers[container];
+ if(s[container]){
+ for(var view in s[container]){
+ var view_config = s[container][view];
+ var linker = view_config[property];
+ if(!ev[linker]) continue;
+ zones = s._add_timespan_zones(zones,
+ getBlockedZones(timespans[view], ev[linker], day_index, day_value));
+ }
+ }
+ }
+ // now need to add day blocks
+ zones = s._add_timespan_zones(zones, getBlockedZones(timespans, 'global', day_index, day_value));
+ return zones;
+
+ function getBlockedZones(timespans, property, day_index, day_value){
+ var zones =[];
+ if (timespans && timespans[property]) {
+ var timeline_zones = timespans[property];
+ var blocked_timeline_zones = get_relevant_blocked_zones(day_index, day_value, timeline_zones);
+ for (var i=0; i<blocked_timeline_zones.length; i++) {
+ zones = s._add_timespan_zones(zones, blocked_timeline_zones[i].zones);
+ }
+ }
+ return zones;
+ }
+ function get_relevant_blocked_zones(day_index, day_value, zones) {
+ var relevant_zones = (zones[day_value] && zones[day_value][timespan_type]) ? zones[day_value][timespan_type] :
+ (zones[day_index] && zones[day_index][timespan_type]) ? zones[day_index][timespan_type] : [];
+ return relevant_zones;
+ };
+ }
+
+ scheduler.attachEvent("onMouseDown", function(classname) {
+ return !(classname = dhx_time_block);
+ });
+ scheduler.attachEvent("onBeforeDrag",function(id){
+ if (!id) return true;
+ return blocker(scheduler.getEvent(id));
+ });
+ scheduler.attachEvent("onClick", function (event_id, native_event_object){
+ return blocker(scheduler.getEvent(event_id));
+ });
+ scheduler.attachEvent("onBeforeLightbox",function(id){
+
+ var ev = scheduler.getEvent(id);
+ before = [ev.start_date, ev.end_date];
+ return blocker(ev);
+ });
+ scheduler.attachEvent("onEventSave", function(id, data, is_new_event) {
+
+ //lightbox may not have 'time' section
+ if(!(data.start_date && data.end_date)){
+ var ev = scheduler.getEvent(id);
+ data.start_date = new Date(ev.start_date);
+ data.end_date = new Date(ev.end_date);
+ }
+
+ if(data.rec_type){
+ //_roll_back_dates modifies start_date of recurring event, need to check limits after modification
+ // use a copy to keep original event unchanged
+ var data_copy = scheduler._lame_clone(data);
+ scheduler._roll_back_dates(data_copy);
+ return blocker(data_copy);
+ }
+ return blocker(data);
+ });
+ scheduler.attachEvent("onEventAdded",function(id){
+ if (!id) return true;
+ var ev = scheduler.getEvent(id);
+ if (!blocker(ev) && scheduler.config.limit_start && scheduler.config.limit_end) {
+ //if newly created event is outside of limited time - crop it, leaving only allowed time
+ if (ev.start_date < scheduler.config.limit_start) {
+ ev.start_date = new Date(scheduler.config.limit_start);
+ }
+ if (ev.start_date.valueOf() >= scheduler.config.limit_end.valueOf()) {
+ ev.start_date = this.date.add(scheduler.config.limit_end, -1, "day");
+ }
+ if (ev.end_date < scheduler.config.limit_start) {
+ ev.end_date = new Date(scheduler.config.limit_start);
+ }
+ if (ev.end_date.valueOf() >= scheduler.config.limit_end.valueOf()) {
+ ev.end_date = this.date.add(scheduler.config.limit_end, -1, "day");
+ }
+ if (ev.start_date.valueOf() >= ev.end_date.valueOf()) {
+ ev.end_date = this.date.add(ev.start_date, (this.config.event_duration||this.config.time_step), "minute");
+ }
+ ev._timed=this.isOneDayEvent(ev);
+ }
+ return true;
+ });
+ scheduler.attachEvent("onEventChanged",function(id){
+ if (!id) return true;
+ var ev = scheduler.getEvent(id);
+ if (!blocker(ev)){
+ if (!before) return false;
+ ev.start_date = before[0];
+ ev.end_date = before[1];
+ ev._timed=this.isOneDayEvent(ev);
+ }
+ return true;
+ });
+ scheduler.attachEvent("onBeforeEventChanged",function(ev, native_object, is_new){
+ return blocker(ev);
+ });
+ scheduler.attachEvent("onBeforeEventCreated", function(ev) { // native event
+ var start_date = scheduler.getActionData(ev).date;
+ var event = {
+ _timed: true,
+ start_date: start_date,
+ end_date: scheduler.date.add(start_date, scheduler.config.time_step, "minute")
+ };
+ return blocker(event);
+ });
+
+ scheduler.attachEvent("onViewChange", function(){
+ scheduler._mark_now();
+ });
+ scheduler.attachEvent("onSchedulerResize", function(){
+ window.setTimeout(function(){ scheduler._mark_now(); }, 1);
+ return true;
+ });
+ scheduler.attachEvent("onTemplatesReady", function() {
+ scheduler._mark_now_timer = window.setInterval(function() {
+ scheduler._mark_now();
+ }, 60000);
+ });
+ scheduler._mark_now = function(hide) {
+ // day, week, units views
+ var dhx_now_time = 'dhx_now_time';
+ if (!this._els[dhx_now_time]) {
+ this._els[dhx_now_time] = [];
+ }
+ var now = scheduler._currentDate();
+ var cfg = this.config;
+ scheduler._remove_mark_now(); // delete previous marks if they exist
+ if (!hide && cfg.mark_now && now < this._max_date && now > this._min_date && now.getHours() >= cfg.first_hour && now.getHours()<cfg.last_hour) {
+ var day_index = this.locate_holder_day(now);
+ this._els[dhx_now_time] = scheduler._append_mark_now(day_index, now);
+ }
+ };
+ scheduler._append_mark_now = function(day_index, now) {
+ var dhx_now_time = 'dhx_now_time';
+ var zone_start= scheduler._get_zone_minutes(now);
+ var options = {
+ zones: [zone_start, zone_start+1],
+ css: dhx_now_time,
+ type: dhx_now_time
+ };
+ if (!this._table_view) {
+ if (this._props && this._props[this._mode]) { // units view
+ var day_divs = this._els["dhx_cal_data"][0].childNodes;
+ var r_divs = [];
+
+ for (var i=0; i<day_divs.length-1; i++) {
+ var t_day = day_index+i; // as each unit is actually considered +1 day
+ options.days = t_day;
+ var t_div = scheduler._render_marked_timespan(options, null, t_day)[0];
+ r_divs.push(t_div)
+ }
+ return r_divs;
+ } else { // day/week views
+ options.days = day_index;
+ return scheduler._render_marked_timespan(options, null, day_index);
+ }
+ } else {
+ if (this._mode == "month") {
+ options.days = +scheduler.date.date_part(now);
+ return scheduler._render_marked_timespan(options, null, null);
+ }
+ }
+ };
+ scheduler._remove_mark_now = function() {
+ var dhx_now_time = 'dhx_now_time';
+ var els = this._els[dhx_now_time];
+ for (var i=0; i<els.length; i++) {
+ var div = els[i];
+ var parent = div.parentNode;
+ if (parent) {
+ parent.removeChild(div);
+ }
+ }
+ this._els[dhx_now_time] = [];
+ };
+
+ /*
+ scheduler._marked_timespans = {
+ "global": {
+ "0": {
+ "default": [
+ { // sunday
+ zones: [0, 100, 500, 600],
+ css: "yellow_box",
+ type: "default",
+ view: "global",
+ day: 0
+ }
+ ]
+ }
+ "112121312": {
+ "my_special_type": [
+ {
+ zones: [600, 900],
+ type: "block",
+ css: "some_class",
+ view: "global",
+ day: 112121312
+ },
+ {}
+ ]
+ }
+ },
+ "units": {
+ "5_id": {
+ "3": {
+ "special_type": [ {}, {}, {} ],
+ "another_type": [ {} ]
+ }
+ },
+ "6_id": {
+ "11212127": {
+ ...
+ }
+ }
+ }
+ }
+ */
+ scheduler._marked_timespans = { global: {} };
+
+ scheduler._get_zone_minutes = function(date) {
+ return date.getHours()*60 + date.getMinutes();
+ };
+ scheduler._prepare_timespan_options = function(config) { // receives 1 option, returns array of options
+ var r_configs = []; // resulting configs
+ var temp_configs = [];
+
+ if (config.days == "fullweek")
+ config.days = [0,1,2,3,4,5,6];
+
+ if (config.days instanceof Array) {
+ var t_days = config.days.slice();
+ for (var i=0; i<t_days.length; i++) {
+ var cloned_config = scheduler._lame_clone(config);
+ cloned_config.days = t_days[i];
+ r_configs.push.apply(r_configs, scheduler._prepare_timespan_options(cloned_config));
+ }
+ return r_configs;
+ }
+
+ if ( !config || !((config.start_date && config.end_date && config.end_date > config.start_date) || (config.days !== undefined && config.zones)) )
+ return r_configs; // incorrect config was provided
+
+ var min = 0;
+ var max = 24*60;
+ if (config.zones == "fullday")
+ config.zones = [min, max];
+ if (config.zones && config.invert_zones) {
+ config.zones = scheduler.invertZones(config.zones);
+ }
+
+ config.id = scheduler.uid();
+ config.css = config.css||"";
+ config.type = config.type||default_timespan_type;
+
+ var sections = config.sections;
+ if (sections) {
+ for (var view_key in sections) {
+ if (sections.hasOwnProperty(view_key)) {
+ var ids = sections[view_key];
+ if (!(ids instanceof Array))
+ ids = [ids];
+ for (var i=0; i<ids.length; i++) {
+ var t_config = scheduler._lame_copy({}, config);
+ t_config.sections = {};
+ t_config.sections[view_key] = ids[i];
+ temp_configs.push(t_config);
+ }
+ }
+ }
+ } else {
+ temp_configs.push(config);
+ }
+
+ for (var k=0; k<temp_configs.length; k++) {
+ var c_config = temp_configs[k]; // config to be checked
+
+ var start_date = c_config.start_date;
+ var end_date = c_config.end_date;
+
+ if (start_date && end_date) {
+ var t_sd = scheduler.date.date_part(new Date(start_date)); // e.g. 05 october
+ var t_ed= scheduler.date.add(t_sd, 1, "day"); // 06 october, will both be incremented in the loop
+
+ while (t_sd < end_date) {
+ var t_config = scheduler._lame_copy({}, c_config);
+ delete t_config.start_date;
+ delete t_config.end_date;
+ t_config.days = t_sd.valueOf();
+ var zone_start = (start_date > t_sd) ? scheduler._get_zone_minutes(start_date) : min;
+ var zone_end = ( end_date>t_ed || end_date.getDate() != t_sd.getDate() ) ? max : scheduler._get_zone_minutes(end_date);
+ t_config.zones = [zone_start, zone_end];
+ r_configs.push(t_config);
+
+ t_sd = t_ed;
+ t_ed = scheduler.date.add(t_ed, 1, "day");
+ }
+ } else {
+ if (c_config.days instanceof Date)
+ c_config.days = (scheduler.date.date_part(c_config.days)).valueOf();
+ c_config.zones = config.zones.slice();
+ r_configs.push(c_config);
+ }
+ }
+ return r_configs;
+ };
+ scheduler._get_dates_by_index = function(index, start, end) {
+ var dates = [];
+ start = scheduler.date.date_part(new Date(start||scheduler._min_date));
+ end = new Date(end||scheduler._max_date);
+ var start_day = start.getDay();
+ var delta = (index-start_day >= 0) ? (index-start_day) : (7-start.getDay()+index);
+ var t_date = scheduler.date.add(start, delta, "day");
+ for (; t_date < end; t_date = scheduler.date.add(t_date, 1, "week")) {
+ dates.push(t_date);
+ }
+ return dates;
+ };
+ scheduler._get_css_classes_by_config = function(config) {
+ var css_classes = [];
+ if (config.type == dhx_time_block) {
+ css_classes.push(dhx_time_block);
+ if (config.css)
+ css_classes.push(dhx_time_block+"_reset");
+ }
+ css_classes.push("dhx_marked_timespan", config.css);
+ return css_classes.join(" ");
+ };
+ scheduler._get_block_by_config = function(config) {
+ var block = document.createElement("DIV");
+ if (config.html) {
+ if (typeof config.html == "string")
+ block.innerHTML = config.html;
+ else
+ block.appendChild(config.html);
+ }
+ return block;
+ };
+ scheduler._render_marked_timespan = function(options, area, day) {
+ var blocks = []; // resulting block which will be rendered and returned
+ var c = scheduler.config;
+ var min_date = this._min_date;
+ var max_date = this._max_date;
+ var day_value = false; // if timespan for specific date should be displayed
+
+ if (!c.display_marked_timespans)
+ return blocks;
+
+ // in case of markTimespan
+ if (!day && day !== 0) {
+ if (options.days < 7)
+ day = options.days;
+ else {
+ var date_to_display = new Date(options.days);
+ day_value = +date_to_display;
+
+ // in case of markTimespan date could be not in the viewing range, need to return
+ if ( !(+max_date >= +date_to_display && +min_date <= +date_to_display) )
+ return blocks;
+
+ day = date_to_display.getDay();
+ }
+
+ // convert day default index (Sun - 0, Sat - 6) to index of hourscales (depends on week_start and config.start_on_monday)
+ var min_day = min_date.getDay();
+ if (min_day > day) {
+ day = 7 - (min_day-day);
+ } else {
+ day = day - min_day;
+ }
+ }
+ var zones = options.zones;
+ var css_classes = scheduler._get_css_classes_by_config(options);
+
+ if (scheduler._table_view && scheduler._mode == "month") {
+ var areas = [];
+ var days = [];
+
+
+ if (!area) {
+ days = (day_value) ? [day_value] : scheduler._get_dates_by_index(day);
+ for (var i=0; i < days.length; i++) {
+ areas.push( this._scales[days[i]] );
+ }
+ } else {
+ areas.push(area);
+ days.push(day);
+ }
+
+ for (var i=0; i < areas.length; i++) {
+ area = areas[i];
+ day = days[i];
+
+ for (var k=0; k < zones.length; k+=2) {
+ var start = zones[i];
+ var end = zones[i+1];
+ if (end <= start)
+ return [];
+
+ var block = scheduler._get_block_by_config(options);
+ block.className = css_classes;
+
+ var height = area.offsetHeight - 1; // 1 for bottom border
+ var width = area.offsetWidth - 1; // 1 for left border
+
+ var sweek = Math.floor((this._correct_shift(day,1)-min_date.valueOf())/(60*60*1000*24*this._cols.length));
+ var sday = this.locate_holder_day(day, false) % this._cols.length;
+
+ var left = this._colsS[sday];
+ var top = this._colsS.heights[sweek]+(this._colsS.height?(this.xy.month_scale_height+2):2)-1;
+
+ block.style.top = top + "px";
+ block.style.lineHeight = block.style.height = height + "px";
+
+ block.style.left = (left + Math.round( (start)/(24*60) * width)) + "px";
+ block.style.width = Math.round( (end-start)/(24*60) * width) + "px";
+
+ area.appendChild(block);
+ blocks.push(block);
+ }
+ }
+ } else {
+ var index = day;
+ if (this._props && this._props[this._mode] && options.sections && options.sections[this._mode]) {
+ var view = this._props[this._mode];
+ index = view.order[options.sections[this._mode]];
+ if (view.size && (index > view.position+view.size)) {
+ index = 0;
+ }
+ }
+ area = area ? area : scheduler.locate_holder(index);
+
+ for (var i = 0; i < zones.length; i+=2){
+ var start = Math.max(zones[i], c.first_hour*60);
+ var end = Math.min(zones[i+1], c.last_hour*60);
+ if (end <= start) {
+ if (i+2 < zones.length)
+ continue;
+ else
+ return [];
+ }
+
+ var block = scheduler._get_block_by_config(options);
+ block.className = css_classes;
+
+ // +1 for working with section which really takes up whole height (as % would be == 0)
+ var all_hours_height = this.config.hour_size_px*24 + 1;
+ var hour_ms = 60*60*1000;
+ block.style.top = (Math.round((start*60*1000-this.config.first_hour*hour_ms)*this.config.hour_size_px/hour_ms) % all_hours_height) + "px";
+ block.style.lineHeight = block.style.height = Math.max((Math.round(((end-start)*60*1000)*this.config.hour_size_px/hour_ms)) % all_hours_height, 1)+"px";
+
+ area.appendChild(block);
+ blocks.push(block);
+ }
+ }
+
+ return blocks;
+ };
+ // just marks timespan, will be cleaned after refresh
+ scheduler.markTimespan = function(configuration) {
+ var configs = scheduler._prepare_timespan_options(configuration);
+ if (!configs.length)
+ return;
+ var divs = [];
+ for (var i=0; i<configs.length; i++) {
+ var config = configs[i];
+ var blocks = scheduler._render_marked_timespan(config, null, null);
+ if(blocks.length)
+ divs.push.apply(divs, blocks);
+ }
+ return divs;
+ };
+ scheduler.unmarkTimespan = function(divs) {
+ if (!divs)
+ return;
+ for (var i=0; i<divs.length; i++) {
+ var div = divs[i];
+ // parent may no longer be present if we switched views, navigated
+ if (div.parentNode) {
+ div.parentNode.removeChild(div);
+ }
+ }
+ };
+
+ scheduler._marked_timespans_ids = {};
+ // adds marked timespan to collections, persistent
+ scheduler.addMarkedTimespan = function(configuration) {
+ var configs = scheduler._prepare_timespan_options(configuration);
+ var global = "global";
+
+ if (!configs.length)
+ return; // options are incorrect, nothing to mark
+
+ var id = configs[0].id;
+ var timespans = scheduler._marked_timespans;
+ var ids = scheduler._marked_timespans_ids;
+ if (!ids[id])
+ ids[id] = [];
+
+ for (var i=0; i<configs.length; i++) {
+ var config = configs[i];
+ var day = config.days;
+ var zones = config.zones;
+ var css = config.css;
+ var sections = config.sections;
+ var type = config.type; // default or specified
+ config.id = id;
+
+ if (sections) {
+ for (var view_key in sections) {
+ if (sections.hasOwnProperty(view_key)) {
+ if (!timespans[view_key])
+ timespans[view_key] = {};
+ var unit_id = sections[view_key];
+ var timespans_view = timespans[view_key];
+ if (!timespans_view[unit_id])
+ timespans_view[unit_id] = {};
+ if (!timespans_view[unit_id][day])
+ timespans_view[unit_id][day] = {};
+ if (!timespans_view[unit_id][day][type]){
+ timespans_view[unit_id][day][type] = [];
+ if(!scheduler._marked_timespans_types)
+ scheduler._marked_timespans_types = {}
+ if(!scheduler._marked_timespans_types[type])
+ scheduler._marked_timespans_types[type] = true;
+ }
+ var day_configs = timespans_view[unit_id][day][type];
+ config._array = day_configs;
+ day_configs.push(config);
+ ids[id].push(config);
+ }
+ }
+ } else {
+ if (!timespans[global][day])
+ timespans[global][day] = {};
+ if (!timespans[global][day][type])
+ timespans[global][day][type] = [];
+
+ if(!scheduler._marked_timespans_types)
+ scheduler._marked_timespans_types = {}
+ if(!scheduler._marked_timespans_types[type])
+ scheduler._marked_timespans_types[type] = true;
+
+
+ var day_configs = timespans[global][day][type];
+ config._array = day_configs;
+ day_configs.push(config);
+ ids[id].push(config);
+ }
+ }
+ return id;
+ };
+ // not used for now
+ scheduler._add_timespan_zones = function(current_zones, zones) {
+ var resulting_zones = current_zones.slice();
+ zones = zones.slice();
+
+ if (!resulting_zones.length)
+ return zones;
+
+ for (var i=0; i<resulting_zones.length; i+=2) {
+ var c_zone_start = resulting_zones[i];
+ var c_zone_end = resulting_zones[i+1];
+ var isLast = (i+2 == resulting_zones.length);
+
+ for (var k=0; k<zones.length; k+=2) {
+ var zone_start = zones[k];
+ var zone_end = zones[k+1];
+ if ((zone_end > c_zone_end && zone_start <= c_zone_end) || (zone_start < c_zone_start && zone_end >= c_zone_start)) {
+ resulting_zones[i] = Math.min(c_zone_start, zone_start);
+ resulting_zones[i+1] = Math.max(c_zone_end, zone_end);
+ i -= 2;
+ } else {
+ if (!isLast) // do nothing, maybe next current zone will match or will be last
+ continue;
+
+ var offset = (c_zone_start > zone_start)?0:2;
+ resulting_zones.splice(i+offset, 0, zone_start, zone_end); // last current zone, need to add another
+ }
+ zones.splice(k--,2); // zone was merged or added, need to exclude it
+ break;
+ }
+ }
+ return resulting_zones;
+ };
+ scheduler._subtract_timespan_zones = function(current_zones, zones) {
+ var resulting_zones = current_zones.slice();
+ for (var i=0; i<resulting_zones.length; i+=2 ) {
+ var c_zone_start = resulting_zones[i];// current_zone_start
+ var c_zone_end = resulting_zones[i+1];
+ for (var k=0; k<zones.length; k+=2) {
+ var zone_start = zones[k];
+ var zone_end = zones[k+1];
+ if (zone_end > c_zone_start && zone_start < c_zone_end) {
+ var is_modified = false;
+ if (c_zone_start >= zone_start && c_zone_end <= zone_end) {
+ resulting_zones.splice(i, 2);
+ }
+ if (c_zone_start < zone_start) {
+ resulting_zones.splice(i, 2, c_zone_start, zone_start);
+ is_modified = true;
+ }
+ if (c_zone_end > zone_end) {
+ resulting_zones.splice( (is_modified)?(i+2):i, (is_modified)?0:2, zone_end, c_zone_end);
+ }
+ i -= 2;
+ break;
+ } else {
+ continue;
+ }
+ }
+ }
+ return resulting_zones;
+ };
+ scheduler.invertZones = function(zones) {
+ return scheduler._subtract_timespan_zones([0, 1440], zones.slice());
+ };
+ scheduler._delete_marked_timespan_by_id = function(id) {
+ var configs = scheduler._marked_timespans_ids[id];
+ if (configs) {
+ for (var i=0; i<configs.length; i++) {
+ var config = configs[i];
+ var parent_array = config._array;
+ for (var k=0; k<parent_array.length; k++) {
+ if (parent_array[k] == config) {
+ parent_array.splice(k, 1);
+ break;
+ }
+ }
+ }
+ }
+ };
+ scheduler._delete_marked_timespan_by_config = function(config) {
+ var timespans = scheduler._marked_timespans;
+ var sections = config.sections;
+ var day = config.days;
+ var type = config.type||default_timespan_type;
+ var day_timespans = []; // array of timespans to subtract our config
+ if (sections) {
+ for (var view_key in sections) {
+ if (sections.hasOwnProperty(view_key) && timespans[view_key]) {
+ var unit_id = sections[view_key];
+ if (timespans[view_key][unit_id] && timespans[view_key][unit_id][day] && timespans[view_key][unit_id][day][type])
+ day_timespans = timespans[view_key][unit_id][day][type];
+ }
+ }
+ } else {
+ if (timespans.global[day] && timespans.global[day][type])
+ day_timespans = timespans.global[day][type];
+ }
+ for (var i=0; i<day_timespans.length; i++) {
+ var d_t = day_timespans[i];
+ var zones = scheduler._subtract_timespan_zones(d_t.zones, config.zones);
+ if (zones.length)
+ d_t.zones = zones;
+ else {
+ day_timespans.splice(i,1);
+ i--;
+ // need to update ids collection
+ var related_zones = scheduler._marked_timespans_ids[d_t.id];
+ for (var k=0; k<related_zones.length; k++) {
+ if (related_zones[k] == d_t) {
+ related_zones.splice(k, 1);
+ break;
+ }
+ }
+ }
+ }
+ };
+ scheduler.deleteMarkedTimespan = function(configuration) {
+ // delete everything
+ if (!arguments.length) {
+ scheduler._marked_timespans = { global: {} };
+ scheduler._marked_timespans_ids = {};
+ scheduler._marked_timespans_types = {};
+ }
+
+ if (typeof configuration != "object") { // id was passed
+ scheduler._delete_marked_timespan_by_id(configuration);
+ } else { // normal configuration was passed
+
+ if(!(configuration.start_date && configuration.end_date)){
+ if(!configuration.days)
+ configuration.days = "fullweek";
+ if(!configuration.zones)
+ configuration.zones = "fullday";
+ }
+
+ var types = [];
+ if(!configuration.type){
+ //if type not specified - delete timespans of all types
+ for(var type in scheduler._marked_timespans_types){
+ types.push(type);
+ }
+ }else{
+ types.push(configuration.type);
+ }
+
+
+ var configs = scheduler._prepare_timespan_options(configuration);
+
+ for (var i=0; i<configs.length; i++) {
+
+ var config = configs[i];
+ for( var t=0; t < types.length; t++){
+ var typedConfig = scheduler._lame_clone(config);
+ typedConfig.type = types[t];
+ scheduler._delete_marked_timespan_by_config(typedConfig);
+ }
+ }
+
+ }
+ };
+ scheduler._get_types_to_render = function(common, specific) {
+ var types_to_render = (common) ? common : {};
+ for (var type in specific||{} ) {
+ if (specific.hasOwnProperty(type)) {
+ types_to_render[type] = specific[type];
+ }
+ }
+ return types_to_render;
+ };
+ scheduler._get_configs_to_render = function(types) {
+ var configs = [];
+ for (var type in types) {
+ if (types.hasOwnProperty(type)) {
+ configs.push.apply(configs, types[type]);
+ }
+ }
+ return configs;
+ };
+ scheduler.attachEvent("onScaleAdd", function(area, day) {
+ if (scheduler._table_view && scheduler._mode != "month")
+ return;
+
+ var day_index = day.getDay();
+ var day_value = day.valueOf();
+ var mode = this._mode;
+ var timespans = scheduler._marked_timespans;
+ var r_configs = [];
+
+ if (this._props && this._props[mode]) { // we are in the units view and need to draw it's sections as well
+ var view = this._props[mode]; // units view object
+ var units = view.options;
+ var index = scheduler._get_unit_index(view, day);
+ var unit = units[index]; // key, label
+ day = scheduler.date.date_part(new Date(this._date)); // for units view actually only 1 day is displayed yet the day variable will change, need to use this._date for all calls
+ day_index = day.getDay();
+ day_value = day.valueOf();
+
+ if (timespans[mode] && timespans[mode][unit.key]) {
+ var unit_zones = timespans[mode][unit.key];
+ var unit_types = scheduler._get_types_to_render(unit_zones[day_index], unit_zones[day_value]);
+ r_configs.push.apply(r_configs, scheduler._get_configs_to_render(unit_types));
+ }
+ }
+
+ var global_data = timespans["global"];
+ var day_types = global_data[day_value]||global_data[day_index];
+ r_configs.push.apply(r_configs, scheduler._get_configs_to_render(day_types));
+
+ for (var i=0; i<r_configs.length; i++) {
+ scheduler._render_marked_timespan(r_configs[i], area, day);
+ }
+ });
+
+})();
diff --git a/sources/ext/dhtmlxscheduler_map_view.js b/sources/ext/dhtmlxscheduler_map_view.js
new file mode 100644
index 0000000..bec49c7
--- /dev/null
+++ b/sources/ext/dhtmlxscheduler_map_view.js
@@ -0,0 +1,488 @@
+/*
+This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
+to use it in non-GPL project. Please contact sales@dhtmlx.com for details
+*/
+scheduler.xy.map_date_width = 188; // date column width
+scheduler.xy.map_description_width = 400; // description column width
+
+scheduler.config.map_resolve_event_location = true; // if events in database doesn't have lat and lng values there will be an attempt to resolve them on event loading, useful for migration
+scheduler.config.map_resolve_user_location = true; // if user will be promted to share his location to display it on the map
+
+scheduler.config.map_initial_position = new google.maps.LatLng(48.724, 8.215); // inital position of the map
+scheduler.config.map_error_position = new google.maps.LatLng(15, 15); // this position will be displayed in case if event doesn't have corresponding coordinates
+
+scheduler.config.map_infowindow_max_width = 300;
+
+scheduler.config.map_type = google.maps.MapTypeId.ROADMAP;
+
+scheduler.config.map_zoom_after_resolve = 15;
+
+scheduler.locale.labels.marker_geo_success = "It seems you are here.";
+scheduler.locale.labels.marker_geo_fail = "Sorry, could not get your current position using geolocation.";
+
+scheduler.templates.marker_date = scheduler.date.date_to_str("%Y-%m-%d %H:%i"); // date for map's infowindow will be formated following way
+
+scheduler.templates.marker_text = function(start, end, ev) {
+ return "<div><b>" + ev.text + "</b><br/><br/>" + (ev.event_location || '') + "<br/><br/>" + scheduler.templates.marker_date(start) + " - " + scheduler.templates.marker_date(end) + "</div>";
+};
+scheduler.dblclick_dhx_map_area = function() {
+ if (!this.config.readonly && this.config.dblclick_create)
+ this.addEventNow({
+ start_date: scheduler._date,
+ end_date: scheduler.date.add(scheduler._date, scheduler.config.time_step, "minute")
+ });
+};
+scheduler.templates.map_time = function(start, end, ev) {
+ if (ev._timed)
+ return this.day_date(ev.start_date, ev.end_date, ev) + " " + this.event_date(start);
+ else
+ return scheduler.templates.day_date(start) + " &ndash; " + scheduler.templates.day_date(end);
+};
+scheduler.templates.map_text = function(start, end, ev) {
+ return ev.text;
+};
+
+scheduler.date.map_start = function(d) {
+ return d;
+};
+scheduler.date.add_map = function(date, inc, mode) {
+ return (new Date(date.valueOf()));
+};
+
+scheduler.templates.map_date = function(dd, ed, mode) {
+ return '';
+};
+
+scheduler._latLngUpdate = false; // flag for not displaying event second time in case of coordinates update
+
+scheduler.attachEvent("onSchedulerReady", function() {
+ scheduler._isMapPositionSet = false; // if user actual (geolocation) position was set on the map
+
+ var gmap = document.createElement('div');
+ gmap.className = 'dhx_map';
+ gmap.id = 'dhx_gmap';
+ gmap.style.dispay = "none";
+
+ var node = scheduler._obj;
+
+ node.appendChild(gmap);
+
+ scheduler._els.dhx_gmap = [];
+ scheduler._els.dhx_gmap.push(gmap);
+
+ _setMapSize('dhx_gmap');
+
+ var mapOptions = {
+ zoom: scheduler.config.map_inital_zoom || 10,
+ center: scheduler.config.map_initial_position,
+ mapTypeId: scheduler.config.map_type || google.maps.MapTypeId.ROADMAP
+ };
+ var map = new google.maps.Map(document.getElementById('dhx_gmap'), mapOptions);
+ map.disableDefaultUI = false;
+ map.disableDoubleClickZoom = !scheduler.config.readonly;
+
+ google.maps.event.addListener(map, "dblclick", function(event) {
+ if (!scheduler.config.readonly && scheduler.config.dblclick_create) {
+ var point = event.latLng;
+ geocoder.geocode(
+ { 'latLng': point },
+ function(results, status) {
+ if (status == google.maps.GeocoderStatus.OK) {
+ point = results[0].geometry.location;
+ scheduler.addEventNow({
+ lat: point.lat(),
+ lng: point.lng(),
+ event_location: results[0].formatted_address,
+ start_date: scheduler._date,
+ end_date: scheduler.date.add(scheduler._date, scheduler.config.time_step, "minute")
+ });
+ }
+ }
+ );
+ }
+ });
+
+ var infoWindowOptions = {
+ content: ''
+ };
+
+ if (scheduler.config.map_infowindow_max_width) {
+ infoWindowOptions.maxWidth = scheduler.config.map_infowindow_max_width;
+ }
+
+ scheduler.map = {
+ _points: [],
+ _markers: [],
+ _infowindow: new google.maps.InfoWindow(infoWindowOptions),
+ _infowindows_content: [],
+ _initialization_count: -1,
+ _obj: map
+ };
+
+ geocoder = new google.maps.Geocoder();
+
+ if (scheduler.config.map_resolve_user_location) {
+ if (navigator.geolocation) {
+ if (!scheduler._isMapPositionSet) {
+ navigator.geolocation.getCurrentPosition(function(position) {
+ var _userLocation = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);
+ map.setCenter(_userLocation);
+ map.setZoom(scheduler.config.map_zoom_after_resolve || 10);
+ scheduler.map._infowindow.setContent(scheduler.locale.labels.marker_geo_success);
+ scheduler.map._infowindow.position = map.getCenter();
+ scheduler.map._infowindow.open(map);
+
+ scheduler._isMapPositionSet = true;
+ },
+ function() {
+ scheduler.map._infowindow.setContent(scheduler.locale.labels.marker_geo_fail);
+ scheduler.map._infowindow.setPosition(map.getCenter());
+ scheduler.map._infowindow.open(map);
+ scheduler._isMapPositionSet = true;
+ });
+ }
+ }
+ }
+ google.maps.event.addListener(map, "resize", function(event) {
+ gmap.style.zIndex = '5';
+ map.setZoom(map.getZoom());
+
+ });
+ google.maps.event.addListener(map, "tilesloaded", function(event) {
+ gmap.style.zIndex = '5';
+ });
+
+ gmap.style.display = 'none'; // property was changed after attaching map
+
+
+ scheduler.attachEvent("onSchedulerResize", function() {
+ if (this._mode == "map") {
+ this.map_view(true);
+ return false
+ }
+ return true;
+ });
+
+ var old = scheduler.render_data;
+ scheduler.render_data = function(evs, hold) {
+ if (this._mode == "map") {
+ fill_map_tab();
+ var events = scheduler.get_visible_events();
+ for (var i = 0; i < events.length; i++) {
+ if (!scheduler.map._markers[events[i].id]) {
+ showAddress(events[i], false, false);
+ }
+ }
+ } else
+ return old.apply(this, arguments);
+ };
+
+ function set_full_view(mode) {
+ if (mode) {
+ var l = scheduler.locale.labels;
+ scheduler._els["dhx_cal_header"][0].innerHTML = "<div class='dhx_map_line' style='width: " + (scheduler.xy.map_date_width + scheduler.xy.map_description_width + 2) + "px;' ><div class='headline_date' style='width: " + scheduler.xy.map_date_width + "px;'>" + l.date + "</div><div class='headline_description' style='width: " + scheduler.xy.map_description_width + "px;'>" + l.description + "</div></div>";
+ scheduler._table_view = true;
+ scheduler.set_sizes();
+ }
+ }
+
+ function clear_map_tab() {
+ scheduler._selected_event_id = null;
+ scheduler.map._infowindow.close();
+ var markers = scheduler.map._markers;
+ for (var key in markers) {
+ if (markers.hasOwnProperty(key)) {
+ markers[key].setMap(null);
+ delete scheduler.map._markers[key];
+ if (scheduler.map._infowindows_content[key])
+ delete scheduler.map._infowindows_content[key];
+ }
+ }
+ }
+
+ function fill_map_tab() {
+ //select events for which data need to be printed
+ var events = scheduler.get_visible_events();
+ events.sort(function(a, b) {
+ if(a.start_date.valueOf()==b.start_date.valueOf())
+ return a.id>b.id?1:-1;
+ return a.start_date>b.start_date?1:-1;
+ });
+
+ //generate html for the view
+ var html = "<div class='dhx_map_area'>";
+ for (var i = 0; i < events.length; i++) {
+ var ev = events[i];
+ var event_class = (ev.id == scheduler._selected_event_id) ? 'dhx_map_line highlight' : 'dhx_map_line';
+ var bg_color = (ev.color ? ("background:" + ev.color + ";") : "");
+ var color = (ev.textColor ? ("color:" + ev.textColor + ";") : "");
+ html += "<div class='" + event_class + "' event_id='" + ev.id + "' style='" + bg_color + "" + color + "" + (ev._text_style || "") + " width: " + (scheduler.xy.map_date_width + scheduler.xy.map_description_width + 2) + "px;'><div style='width: " + scheduler.xy.map_date_width + "px;' >" + scheduler.templates.map_time(ev.start_date, ev.end_date, ev) + "</div>";
+ html += "<div class='dhx_event_icon icon_details'>&nbsp</div>";
+ html += "<div class='line_description' style='width:" + (scheduler.xy.map_description_width - 25) + "px;'>" + scheduler.templates.map_text(ev.start_date, ev.end_date, ev) + "</div></div>"; // -25 = icon size 20 and padding 5
+ }
+ html += "<div class='dhx_v_border' style='left: " + (scheduler.xy.map_date_width - 2) + "px;'></div><div class='dhx_v_border_description'></div></div>";
+
+ //render html
+ scheduler._els["dhx_cal_data"][0].scrollTop = 0; //fix flickering in FF
+ scheduler._els["dhx_cal_data"][0].innerHTML = html;
+ scheduler._els["dhx_cal_data"][0].style.width = (scheduler.xy.map_date_width + scheduler.xy.map_description_width + 1) + 'px';
+
+ var t = scheduler._els["dhx_cal_data"][0].firstChild.childNodes;
+ scheduler._els["dhx_cal_date"][0].innerHTML = scheduler.templates[scheduler._mode + "_date"](scheduler._min_date, scheduler._max_date, scheduler._mode);
+
+ scheduler._rendered = [];
+ for (var i = 0; i < t.length - 2; i++) {
+ scheduler._rendered[i] = t[i];
+ }
+ }
+
+ function _setMapSize(elem_id) { //input - map's div id
+ var map = document.getElementById(elem_id);
+ var height = scheduler._y - scheduler.xy.nav_height;
+ if (height < 0)
+ height = 0;
+ var width = scheduler._x - scheduler.xy.map_date_width - scheduler.xy.map_description_width - 1;
+ if (width < 0)
+ width = 0;
+ map.style.height = height + 'px';
+ map.style.width = width + 'px';
+ map.style.marginLeft = (scheduler.xy.map_date_width + scheduler.xy.map_description_width + 1) + 'px';
+ map.style.marginTop = (scheduler.xy.nav_height + 2) + 'px';
+ }
+
+ scheduler.map_view = function(mode) {
+ scheduler.map._initialization_count++;
+ var gmap = scheduler._els.dhx_gmap[0];
+ scheduler._els.dhx_cal_data[0].style.width = (scheduler.xy.map_date_width + scheduler.xy.map_description_width + 1) + 'px';
+
+ scheduler._min_date = scheduler.config.map_start || (scheduler._currentDate());
+ scheduler._max_date = scheduler.config.map_end || scheduler.date.add(scheduler._currentDate(), 1, "year");
+
+ scheduler._table_view = true;
+ set_full_view(mode);
+
+ if (mode) { //map tab activated
+ clear_map_tab();
+ fill_map_tab();
+ gmap.style.display = 'block';
+
+ // need to resize block everytime window is resized
+ _setMapSize('dhx_gmap');
+ var temp_center = scheduler.map._obj.getCenter();
+
+ var events = scheduler.get_visible_events();
+ for (var i = 0; i < events.length; i++) {
+ if (!scheduler.map._markers[events[i].id]) {
+ showAddress(events[i]);
+ }
+ }
+
+ } else { //map tab de-activated
+ gmap.style.display = 'none';
+ }
+ google.maps.event.trigger(scheduler.map._obj, 'resize');
+
+ if (scheduler.map._initialization_count === 0 && temp_center) { // if tab is activated for the first time need to fix position
+ scheduler.map._obj.setCenter(temp_center);
+ }
+
+ if (scheduler._selected_event_id) {
+ selectEvent(scheduler._selected_event_id);
+ }
+ };
+
+ var selectEvent = function(event_id) {
+ scheduler.map._obj.setCenter(scheduler.map._points[event_id]);
+ scheduler.callEvent("onClick", [event_id]);
+ };
+
+ var showAddress = function(event, setCenter, performClick) { // what if event have incorrect position from the start?
+ var point = scheduler.config.map_error_position;
+ if (event.lat && event.lng) {
+ point = new google.maps.LatLng(event.lat, event.lng);
+ }
+ var message = scheduler.templates.marker_text(event.start_date, event.end_date, event);
+ if (!scheduler._new_event) {
+
+ scheduler.map._infowindows_content[event.id] = message;
+
+ if (scheduler.map._markers[event.id])
+ scheduler.map._markers[event.id].setMap(null);
+
+ scheduler.map._markers[event.id] = new google.maps.Marker({
+ position: point,
+ map: scheduler.map._obj
+ });
+
+ google.maps.event.addListener(scheduler.map._markers[event.id], 'click', function() {
+ scheduler.map._infowindow.setContent(scheduler.map._infowindows_content[event.id]);
+ scheduler.map._infowindow.open(scheduler.map._obj, scheduler.map._markers[event.id]);
+ scheduler._selected_event_id = event.id;
+ scheduler.render_data();
+ });
+ scheduler.map._points[event.id] = point;
+
+ if (setCenter) scheduler.map._obj.setCenter(scheduler.map._points[event.id]);
+ if (performClick) scheduler.callEvent("onClick", [event.id]);
+ }
+ };
+
+ scheduler.attachEvent("onClick", function(event_id, native_event_object) {
+ if (this._mode == "map") {
+ scheduler._selected_event_id = event_id;
+ for (var i = 0; i < scheduler._rendered.length; i++) {
+ scheduler._rendered[i].className = 'dhx_map_line';
+ if (scheduler._rendered[i].getAttribute("event_id") == event_id) {
+ scheduler._rendered[i].className += " highlight";
+ }
+ }
+ if (scheduler.map._points[event_id] && scheduler.map._markers[event_id]) {
+ scheduler.map._obj.setCenter(scheduler.map._points[event_id]); // was panTo
+ google.maps.event.trigger(scheduler.map._markers[event_id], 'click');
+ }
+ }
+ return true;
+ });
+
+ var _displayEventOnMap = function(event) {
+ if (event.event_location && geocoder) {
+ geocoder.geocode(
+ {
+ 'address': event.event_location,
+ 'language': scheduler.uid().toString()
+ },
+ function(results, status) {
+ var point = {};
+ if (status != google.maps.GeocoderStatus.OK) {
+ point = scheduler.callEvent("onLocationError", [event.id]);
+ if (!point || point === true)
+ point = scheduler.config.map_error_position;
+ } else {
+ point = results[0].geometry.location;
+ }
+ event.lat = point.lat();
+ event.lng = point.lng();
+
+ scheduler._selected_event_id = event.id;
+
+ scheduler._latLngUpdate = true;
+ scheduler.callEvent("onEventChanged", [event.id, event]);
+ showAddress(event, true, true);
+ }
+ );
+ } else {
+ showAddress(event, true, true);
+ }
+ };
+
+ var _updateEventLocation = function(event) { // update lat and lng in database
+ if (event.event_location && geocoder) {
+ geocoder.geocode(
+ {
+ 'address': event.event_location,
+ 'language': scheduler.uid().toString()
+ },
+ function(results, status) {
+ var point = {};
+ if (status != google.maps.GeocoderStatus.OK) {
+ point = scheduler.callEvent("onLocationError", [event.id]);
+ if (!point || point === true)
+ point = scheduler.config.map_error_position;
+ } else {
+ point = results[0].geometry.location;
+ }
+ event.lat = point.lat();
+ event.lng = point.lng();
+ scheduler._latLngUpdate = true;
+ scheduler.callEvent("onEventChanged", [event.id, event]);
+ }
+ );
+ }
+ };
+
+ var _delay = function(method, object, params, delay) {
+ setTimeout(function() {
+ var ret = method.apply(object, params);
+ method = object = params = null;
+ return ret;
+ }, delay || 1);
+ };
+
+ scheduler.attachEvent("onEventChanged", function(event_id, event_object) {
+ if (!this._latLngUpdate) {
+ var event = scheduler.getEvent(event_id);
+ if ((event.start_date < scheduler._min_date && event.end_date > scheduler._min_date) || (event.start_date < scheduler._max_date && event.end_date > scheduler._max_date) || (event.start_date.valueOf() >= scheduler._min_date && event.end_date.valueOf() <= scheduler._max_date)) {
+ if (scheduler.map._markers[event_id])
+ scheduler.map._markers[event_id].setMap(null);
+ _displayEventOnMap(event);
+ } else { // event no longer should be displayed on the map view
+ scheduler._selected_event_id = null;
+ scheduler.map._infowindow.close();
+ if (scheduler.map._markers[event_id])
+ scheduler.map._markers[event_id].setMap(null);
+ }
+ }
+ else
+ this._latLngUpdate = false;
+ return true;
+ });
+
+
+ scheduler.attachEvent("onEventIdChange", function(old_event_id, new_event_id) {
+ var event = scheduler.getEvent(new_event_id);
+ if ((event.start_date < scheduler._min_date && event.end_date > scheduler._min_date) || (event.start_date < scheduler._max_date && event.end_date > scheduler._max_date) || (event.start_date.valueOf() >= scheduler._min_date && event.end_date.valueOf() <= scheduler._max_date)) {
+ if (scheduler.map._markers[old_event_id]) {
+ scheduler.map._markers[old_event_id].setMap(null);
+ delete scheduler.map._markers[old_event_id];
+ }
+ if (scheduler.map._infowindows_content[old_event_id])
+ delete scheduler.map._infowindows_content[old_event_id];
+ _displayEventOnMap(event);
+ }
+ return true;
+ });
+
+ scheduler.attachEvent("onEventAdded", function(event_id, event_object) {
+ if (!scheduler._dataprocessor) {
+ if ((event_object.start_date < scheduler._min_date && event_object.end_date > scheduler._min_date) || (event_object.start_date < scheduler._max_date && event_object.end_date > scheduler._max_date) || (event_object.start_date.valueOf() >= scheduler._min_date && event_object.end_date.valueOf() <= scheduler._max_date)) {
+ if (scheduler.map._markers[event_id])
+ scheduler.map._markers[event_id].setMap(null);
+ _displayEventOnMap(event_object);
+ }
+ }
+ return true;
+ });
+
+ /* Test/example
+ scheduler.attachEvent("onLocationError", function(event_id,event_object){
+ return new google.maps.LatLng(8, 8);
+ });
+ */
+
+ scheduler.attachEvent("onBeforeEventDelete", function(event_id, event_object) {
+ if (scheduler.map._markers[event_id]) {
+ scheduler.map._markers[event_id].setMap(null); // if new event is deleted tab != map then it doesn't have marker yet
+ }
+ scheduler._selected_event_id = null;
+ scheduler.map._infowindow.close();
+ return true;
+ });
+
+ scheduler._event_resolve_delay = 1500;
+ scheduler.attachEvent("onEventLoading", function(event) {
+ if (scheduler.config.map_resolve_event_location && event.event_location && !event.lat && !event.lng) { // don't delete !event.lat && !event.lng as location could change
+ scheduler._event_resolve_delay += 1500;
+ _delay(_updateEventLocation, this, [event], scheduler._event_resolve_delay);
+ }
+ return true;
+ });
+
+ scheduler.attachEvent("onEventCancel", function(event_id, is_new) {
+ if (is_new) {
+ if (scheduler.map._markers[event_id])
+ scheduler.map._markers[event_id].setMap(null);
+ scheduler.map._infowindow.close();
+ }
+ return true;
+ });
+});
diff --git a/sources/ext/dhtmlxscheduler_minical.js b/sources/ext/dhtmlxscheduler_minical.js
new file mode 100644
index 0000000..1fd41b3
--- /dev/null
+++ b/sources/ext/dhtmlxscheduler_minical.js
@@ -0,0 +1,455 @@
+/*
+This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
+to use it in non-GPL project. Please contact sales@dhtmlx.com for details
+*/
+scheduler.templates.calendar_month = scheduler.date.date_to_str("%F %Y");
+scheduler.templates.calendar_scale_date = scheduler.date.date_to_str("%D");
+scheduler.templates.calendar_date = scheduler.date.date_to_str("%d");
+scheduler.config.minicalendar = {
+ mark_events: true
+};
+scheduler._synced_minicalendars = [];
+scheduler.renderCalendar = function(obj, _prev, is_refresh) {
+ var cal = null;
+ var date = obj.date || (scheduler._currentDate());
+ if (typeof date == "string")
+ date = this.templates.api_date(date);
+
+ if (!_prev) {
+ var cont = obj.container;
+ var pos = obj.position;
+
+ if (typeof cont == "string")
+ cont = document.getElementById(cont);
+
+ if (typeof pos == "string")
+ pos = document.getElementById(pos);
+ if (pos && (typeof pos.left == "undefined")) {
+ var tpos = getOffset(pos);
+ pos = {
+ top: tpos.top + pos.offsetHeight,
+ left: tpos.left
+ };
+ }
+ if (!cont)
+ cont = scheduler._get_def_cont(pos);
+
+ cal = this._render_calendar(cont, date, obj);
+ cal.onclick = function(e) {
+ e = e || event;
+ var src = e.target || e.srcElement;
+
+ if (src.className.indexOf("dhx_month_head") != -1) {
+ var pname = src.parentNode.className;
+ if (pname.indexOf("dhx_after") == -1 && pname.indexOf("dhx_before") == -1) {
+ var newdate = scheduler.templates.xml_date(this.getAttribute("date"));
+ newdate.setDate(parseInt(src.innerHTML, 10));
+ scheduler.unmarkCalendar(this);
+ scheduler.markCalendar(this, newdate, "dhx_calendar_click");
+ this._last_date = newdate;
+ if (this.conf.handler) this.conf.handler.call(scheduler, newdate, this);
+ }
+ }
+ };
+ } else {
+ cal = this._render_calendar(_prev.parentNode, date, obj, _prev);
+ scheduler.unmarkCalendar(cal);
+ }
+
+ if (scheduler.config.minicalendar.mark_events) {
+ var start = scheduler.date.month_start(date);
+ var end = scheduler.date.add(start, 1, "month");
+ var evs = this.getEvents(start, end);
+ var filter = this["filter_" + this._mode];
+ for (var i = 0; i < evs.length; i++) {
+ var ev = evs[i];
+ if (filter && !filter(ev.id, ev))
+ continue;
+ var d = ev.start_date;
+ if (d.valueOf() < start.valueOf())
+ d = start;
+ d = scheduler.date.date_part(new Date(d.valueOf()));
+ while (d < ev.end_date) {
+ this.markCalendar(cal, d, "dhx_year_event");
+ d = this.date.add(d, 1, "day");
+ if (d.valueOf() >= end.valueOf())
+ break;
+ }
+ }
+ }
+
+ this._markCalendarCurrentDate(cal);
+
+ cal.conf = obj;
+ if (obj.sync && !is_refresh)
+ this._synced_minicalendars.push(cal);
+
+ return cal;
+};
+scheduler._get_def_cont = function(pos) {
+ if (!this._def_count) {
+ this._def_count = document.createElement("DIV");
+ this._def_count.className = "dhx_minical_popup";
+ this._def_count.onclick = function(e) { (e || event).cancelBubble = true; };
+ document.body.appendChild(this._def_count);
+ }
+
+ this._def_count.style.left = pos.left + "px";
+ this._def_count.style.top = pos.top + "px";
+ this._def_count._created = new Date();
+
+ return this._def_count;
+};
+scheduler._locateCalendar = function(cal, date) {
+ if (typeof date == "string")
+ date = scheduler.templates.api_date(date);
+
+ if(+date > +cal._max_date || +date < +cal._min_date)
+ return null;
+
+ var table = cal.childNodes[2].childNodes[0];
+
+ var weekNum = 0;
+ var dat = new Date(cal._min_date);
+ while(+this.date.add(dat, 1, "week") <= +date){
+ dat = this.date.add(dat, 1, "week");
+ weekNum++;
+ }
+
+ var sm = scheduler.config.start_on_monday;
+ var day = (date.getDay() || (sm ? 7 : 0)) - (sm ? 1 : 0);
+ return table.rows[weekNum].cells[day].firstChild;
+};
+scheduler.markCalendar = function(cal, date, css) {
+ var div = this._locateCalendar(cal, date);
+ if(!div)
+ return;
+
+ div.className += " " + css;
+};
+scheduler.unmarkCalendar = function(cal, date, css) {
+ date = date || cal._last_date;
+ css = css || "dhx_calendar_click";
+ if (!date) return;
+ var el = this._locateCalendar(cal, date);
+ if(!el)
+ return;
+ el.className = (el.className || "").replace(RegExp(css, "g"));
+};
+scheduler._week_template = function(width) {
+ var summ = (width || 250);
+ var left = 0;
+
+ var week_template = document.createElement("div");
+ var dummy_date = this.date.week_start(scheduler._currentDate());
+ for (var i = 0; i < 7; i++) {
+ this._cols[i] = Math.floor(summ / (7 - i));
+ this._render_x_header(i, left, dummy_date, week_template);
+ dummy_date = this.date.add(dummy_date, 1, "day");
+ summ -= this._cols[i];
+ left += this._cols[i];
+ }
+ week_template.lastChild.className += " dhx_scale_bar_last";
+ return week_template;
+};
+scheduler.updateCalendar = function(obj, sd) {
+ obj.conf.date = sd;
+ this.renderCalendar(obj.conf, obj, true);
+};
+scheduler._mini_cal_arrows = ["&nbsp", "&nbsp"];
+scheduler._render_calendar = function(obj, sd, conf, previous) {
+ /*store*/
+ var ts = scheduler.templates;
+ var temp = this._cols;
+ this._cols = [];
+ var temp2 = this._mode;
+ this._mode = "calendar";
+ var temp3 = this._colsS;
+ this._colsS = {height: 0};
+ var temp4 = new Date(this._min_date);
+ var temp5 = new Date(this._max_date);
+ var temp6 = new Date(scheduler._date);
+ var temp7 = ts.month_day;
+ ts.month_day = ts.calendar_date;
+
+ sd = this.date.month_start(sd);
+ var week_template = this._week_template(obj.offsetWidth - 1 - this.config.minicalendar.padding );
+
+ var d;
+ if (previous)
+ d = previous; else {
+ d = document.createElement("DIV");
+ d.className = "dhx_cal_container dhx_mini_calendar";
+ }
+ d.setAttribute("date", this.templates.xml_format(sd));
+ d.innerHTML = "<div class='dhx_year_month'></div><div class='dhx_year_week'>" + week_template.innerHTML + "</div><div class='dhx_year_body'></div>";
+
+ d.childNodes[0].innerHTML = this.templates.calendar_month(sd);
+ if (conf.navigation) {
+ var move_minicalendar_date = function(calendar, diff) {
+ var date = scheduler.date.add(calendar._date, diff, "month");
+ scheduler.updateCalendar(calendar, date);
+ if (scheduler._date.getMonth() == calendar._date.getMonth() && scheduler._date.getFullYear() == calendar._date.getFullYear()) {
+ scheduler._markCalendarCurrentDate(calendar);
+ }
+ };
+
+ var css_classnames = ["dhx_cal_prev_button", "dhx_cal_next_button"];
+ var css_texts = ["left:1px;top:2px;position:absolute;", "left:auto; right:1px;top:2px;position:absolute;"];
+ var diffs = [-1, 1];
+ var handler = function(diff) {
+ return function() {
+ if (conf.sync) {
+ var calendars = scheduler._synced_minicalendars;
+ for (var k = 0; k < calendars.length; k++) {
+ move_minicalendar_date(calendars[k], diff);
+ }
+ } else {
+ move_minicalendar_date(d, diff);
+ }
+ }
+ };
+ for (var j = 0; j < 2; j++) {
+ var arrow = document.createElement("DIV");
+ //var diff = diffs[j];
+ arrow.className = css_classnames[j];
+ arrow.style.cssText = css_texts[j];
+ arrow.innerHTML = this._mini_cal_arrows[j];
+ d.firstChild.appendChild(arrow);
+ arrow.onclick = handler(diffs[j])
+ }
+ }
+ d._date = new Date(sd);
+
+ d.week_start = (sd.getDay() - (this.config.start_on_monday ? 1 : 0) + 7) % 7;
+
+ var dd = d._min_date = this.date.week_start(sd);
+ d._max_date = this.date.add(d._min_date, 6, "week");
+
+ this._reset_month_scale(d.childNodes[2], sd, dd);
+
+ var r = d.childNodes[2].firstChild.rows;
+ for (var k = r.length; k < 6; k++) {
+ var last_row = r[r.length - 1];
+ r[0].parentNode.appendChild(last_row.cloneNode(true));
+ var last_day_number = parseInt(last_row.childNodes[last_row.childNodes.length - 1].childNodes[0].innerHTML);
+ last_day_number = (last_day_number < 10) ? last_day_number : 0; // previous week could end on 28-31, so we should start with 0
+ for (var ri = 0; ri < r[k].childNodes.length; ri++) {
+ r[k].childNodes[ri].className = "dhx_after";
+ r[k].childNodes[ri].childNodes[0].innerHTML = scheduler.date.to_fixed(++last_day_number);
+ }
+ }
+
+ if (!previous)
+ obj.appendChild(d);
+
+ d.childNodes[1].style.height = (d.childNodes[1].childNodes[0].offsetHeight - 1) + "px"; // dhx_year_week should have height property so that day dates would get correct position. dhx_year_week height = height of it's child (with the day name)
+
+ /*restore*/
+ this._cols = temp;
+ this._mode = temp2;
+ this._colsS = temp3;
+ this._min_date = temp4;
+ this._max_date = temp5;
+ scheduler._date = temp6;
+ ts.month_day = temp7;
+ return d;
+};
+scheduler.destroyCalendar = function(cal, force) {
+ if (!cal && this._def_count && this._def_count.firstChild) {
+ if (force || (new Date()).valueOf() - this._def_count._created.valueOf() > 500)
+ cal = this._def_count.firstChild;
+ }
+ if (!cal) return;
+ cal.onclick = null;
+ cal.innerHTML = "";
+ if (cal.parentNode)
+ cal.parentNode.removeChild(cal);
+ if (this._def_count)
+ this._def_count.style.top = "-1000px";
+};
+scheduler.isCalendarVisible = function() {
+ if (this._def_count && parseInt(this._def_count.style.top, 10) > 0)
+ return this._def_count;
+ return false;
+};
+scheduler.attachEvent("onTemplatesReady", function() {
+ dhtmlxEvent(document.body, "click", function() { scheduler.destroyCalendar(); });
+});
+
+scheduler.templates.calendar_time = scheduler.date.date_to_str("%d-%m-%Y");
+
+scheduler.form_blocks.calendar_time = {
+ render: function() {
+ var html = "<input class='dhx_readonly' type='text' readonly='true'>";
+
+ var cfg = scheduler.config;
+ var dt = this.date.date_part(scheduler._currentDate());
+
+ var last = 24 * 60, first = 0;
+ if (cfg.limit_time_select) {
+ first = 60 * cfg.first_hour;
+ last = 60 * cfg.last_hour + 1; // to include "17:00" option if time select is limited
+ }
+ dt.setHours(first / 60);
+
+ html += " <select>";
+ for (var i = first; i < last; i += this.config.time_step * 1) { // `<` to exclude last "00:00" option
+ var time = this.templates.time_picker(dt);
+ html += "<option value='" + i + "'>" + time + "</option>";
+ dt = this.date.add(dt, this.config.time_step, "minute");
+ }
+ html += "</select>";
+
+ var full_day = scheduler.config.full_day;
+
+ return "<div style='height:30px;padding-top:0; font-size:inherit;' class='dhx_section_time'>" + html + "<span style='font-weight:normal; font-size:10pt;'> &nbsp;&ndash;&nbsp; </span>" + html + "</div>";
+ },
+ set_value: function(node, value, ev) {
+
+ var inputs = node.getElementsByTagName("input");
+ var selects = node.getElementsByTagName("select");
+
+ var _init_once = function(inp, date, number) {
+ inp.onclick = function() {
+ scheduler.destroyCalendar(null, true);
+ scheduler.renderCalendar({
+ position: inp,
+ date: new Date(this._date),
+ navigation: true,
+ handler: function(new_date) {
+ inp.value = scheduler.templates.calendar_time(new_date);
+ inp._date = new Date(new_date);
+ scheduler.destroyCalendar();
+ if (scheduler.config.event_duration && scheduler.config.auto_end_date && number == 0) { //first element = start date
+ _update_minical_select();
+ }
+ }
+ });
+ };
+ };
+
+ if (scheduler.config.full_day) {
+ if (!node._full_day) {
+ var html = "<label class='dhx_fullday'><input type='checkbox' name='full_day' value='true'> " + scheduler.locale.labels.full_day + "&nbsp;</label></input>";
+ if (!scheduler.config.wide_form)
+ html = node.previousSibling.innerHTML + html;
+ node.previousSibling.innerHTML = html;
+ node._full_day = true;
+ }
+ var input = node.previousSibling.getElementsByTagName("input")[0];
+
+ var isFulldayEvent = (scheduler.date.time_part(ev.start_date) == 0 && scheduler.date.time_part(ev.end_date) == 0);
+ input.checked = isFulldayEvent;
+
+ selects[0].disabled = input.checked;
+ selects[1].disabled = input.checked;
+
+ input.onclick = function() {
+ if (input.checked == true) {
+ var obj = {};
+ scheduler.form_blocks.calendar_time.get_value(node, obj);
+
+ var start_date = scheduler.date.date_part(obj.start_date);
+ var end_date = scheduler.date.date_part(obj.end_date);
+
+ if (+end_date == +start_date || (+end_date >= +start_date && (ev.end_date.getHours() != 0 || ev.end_date.getMinutes() != 0)))
+ end_date = scheduler.date.add(end_date, 1, "day");
+ }
+
+ var start = start_date || ev.start_date;
+ var end = end_date || ev.end_date;
+ _attach_action(inputs[0], start);
+ _attach_action(inputs[1], end);
+ selects[0].value = start.getHours() * 60 + start.getMinutes();
+ selects[1].value = end.getHours() * 60 + end.getMinutes();
+
+ selects[0].disabled = input.checked;
+ selects[1].disabled = input.checked;
+
+ };
+ }
+
+ if (scheduler.config.event_duration && scheduler.config.auto_end_date) {
+
+ function _update_minical_select() {
+ start_date = scheduler.date.add(inputs[0]._date, selects[0].value, "minute");
+ end_date = new Date(start_date.getTime() + (scheduler.config.event_duration * 60 * 1000));
+
+ inputs[1].value = scheduler.templates.calendar_time(end_date);
+ inputs[1]._date = scheduler.date.date_part(new Date(end_date));
+
+ selects[1].value = end_date.getHours() * 60 + end_date.getMinutes();
+ }
+
+ selects[0].onchange = _update_minical_select; // only update on first select should trigger update so user could define other end date if he wishes too
+ }
+
+ function _attach_action(inp, date, number) {
+ _init_once(inp, date, number);
+ inp.value = scheduler.templates.calendar_time(date);
+ inp._date = scheduler.date.date_part(new Date(date));
+ }
+
+ _attach_action(inputs[0], ev.start_date, 0);
+ _attach_action(inputs[1], ev.end_date, 1);
+ _init_once = function() {};
+
+ selects[0].value = ev.start_date.getHours() * 60 + ev.start_date.getMinutes();
+ selects[1].value = ev.end_date.getHours() * 60 + ev.end_date.getMinutes();
+
+ },
+ get_value: function(node, ev) {
+ var inputs = node.getElementsByTagName("input");
+ var selects = node.getElementsByTagName("select");
+
+ ev.start_date = scheduler.date.add(inputs[0]._date, selects[0].value, "minute");
+ ev.end_date = scheduler.date.add(inputs[1]._date, selects[1].value, "minute");
+
+ if (ev.end_date <= ev.start_date)
+ ev.end_date = scheduler.date.add(ev.start_date, scheduler.config.time_step, "minute");
+ },
+ focus: function(node) {
+ }
+};
+scheduler.linkCalendar = function(calendar, datediff) {
+ var action = function() {
+ var date = scheduler._date;
+ var dateNew = new Date(date.valueOf());
+ if (datediff) dateNew = datediff(dateNew);
+ dateNew.setDate(1);
+ scheduler.updateCalendar(calendar, dateNew);
+ return true;
+ };
+
+ scheduler.attachEvent("onViewChange", action);
+ scheduler.attachEvent("onXLE", action);
+ scheduler.attachEvent("onEventAdded", action);
+ scheduler.attachEvent("onEventChanged", action);
+ scheduler.attachEvent("onAfterEventDelete", action);
+ action();
+};
+
+scheduler._markCalendarCurrentDate = function(calendar) {
+ var date = scheduler._date;
+ var mode = scheduler._mode;
+ var month_start = scheduler.date.month_start(new Date(calendar._date));
+ var month_end = scheduler.date.add(month_start, 1, "month");
+
+ if (mode == 'day' || (this._props && !!this._props[mode])) { // if day or units view
+ if (month_start.valueOf() <= date.valueOf() && month_end > date) {
+ scheduler.markCalendar(calendar, date, "dhx_calendar_click");
+ }
+ } else if (mode == 'week') {
+ var dateNew = scheduler.date.week_start(new Date(date.valueOf()));
+ for (var i = 0; i < 7; i++) {
+ if (month_start.valueOf() <= dateNew.valueOf() && month_end > dateNew) // >= would mean mark first day of the next month
+ scheduler.markCalendar(calendar, dateNew, "dhx_calendar_click");
+ dateNew = scheduler.date.add(dateNew, 1, "day");
+ }
+ }
+};
+
+scheduler.attachEvent("onEventCancel", function(){
+ scheduler.destroyCalendar(null, true);
+}); \ No newline at end of file
diff --git a/sources/ext/dhtmlxscheduler_multiselect.js b/sources/ext/dhtmlxscheduler_multiselect.js
new file mode 100644
index 0000000..4e39450
--- /dev/null
+++ b/sources/ext/dhtmlxscheduler_multiselect.js
@@ -0,0 +1,66 @@
+/*
+This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
+to use it in non-GPL project. Please contact sales@dhtmlx.com for details
+*/
+scheduler.form_blocks["multiselect"]={
+ render:function(sns) {
+ var _result = "<div class='dhx_multi_select_"+sns.name+"' style='overflow: auto; height: "+sns.height+"px; position: relative;' >";
+ for (var i=0; i<sns.options.length; i++) {
+ _result += "<label><input type='checkbox' value='"+sns.options[i].key+"'/>"+sns.options[i].label+"</label>";
+ if(convertStringToBoolean(sns.vertical)) _result += '<br/>';
+ }
+ _result += "</div>";
+ return _result;
+ },
+ set_value:function(node,value,ev,config){
+
+ var _children = node.getElementsByTagName('input');
+ for(var i=0;i<_children.length;i++) {
+ _children[i].checked = false; //unchecking all inputs on the form
+ }
+
+ function _mark_inputs(ids) { // ids = [ 0: undefined, 1: undefined, 2: true ... ]
+ var _children = node.getElementsByTagName('input');
+ for(var i=0;i<_children.length; i++) {
+ _children[i].checked = !! ids[_children[i].value];
+ }
+ }
+
+ var _ids = [];
+ if (ev[config.map_to]) {
+ var results = ev[config.map_to].split(',');
+ for (var i = 0; i < results.length; i++) {
+ _ids[results[i]] = true;
+ }
+ _mark_inputs(_ids);
+ } else {
+ if (scheduler._new_event || !config.script_url)
+ return;
+ var divLoading = document.createElement('div');
+ divLoading.className = 'dhx_loading';
+ divLoading.style.cssText = "position: absolute; top: 40%; left: 40%;";
+ node.appendChild(divLoading);
+ dhtmlxAjax.get(config.script_url + '?dhx_crosslink_' + config.map_to + '=' + ev.id + '&uid=' + scheduler.uid(), function(loader) {
+ var _result = loader.doXPath("//data/item");
+ var _ids = [];
+ for (var i = 0; i < _result.length; i++) {
+ _ids[_result[i].getAttribute(config.map_to)] = true;
+ }
+ _mark_inputs(_ids);
+ node.removeChild(divLoading);
+ });
+ }
+ },
+ get_value:function(node,ev,config){
+ var _result = [];
+ var _children = node.getElementsByTagName("input");
+ for(var i=0;i<_children.length;i++) {
+ if(_children[i].checked)
+ _result.push(_children[i].value);
+ }
+ return _result.join(',');
+ },
+
+ focus:function(node){
+ }
+}; \ No newline at end of file
diff --git a/sources/ext/dhtmlxscheduler_multisource.js b/sources/ext/dhtmlxscheduler_multisource.js
new file mode 100644
index 0000000..30829bc
--- /dev/null
+++ b/sources/ext/dhtmlxscheduler_multisource.js
@@ -0,0 +1,26 @@
+/*
+This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
+to use it in non-GPL project. Please contact sales@dhtmlx.com for details
+*/
+(function(){
+
+ function backup(obj){
+ var t = function(){};
+ t.prototype = obj;
+ return t;
+ }
+
+ var old = scheduler._load;
+ scheduler._load=function(url,from){
+ url=url||this._load_url;
+ if (typeof url == "object"){
+ var t = backup(this._loaded);
+ for (var i=0; i < url.length; i++) {
+ this._loaded=new t();
+ old.call(this,url[i],from);
+ }
+ } else
+ old.apply(this,arguments);
+ }
+
+})(); \ No newline at end of file
diff --git a/sources/ext/dhtmlxscheduler_mvc.js b/sources/ext/dhtmlxscheduler_mvc.js
new file mode 100644
index 0000000..91eb269
--- /dev/null
+++ b/sources/ext/dhtmlxscheduler_mvc.js
@@ -0,0 +1,82 @@
+/*
+This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
+to use it in non-GPL project. Please contact sales@dhtmlx.com for details
+*/
+(function(){
+
+ //remove private properties
+ function sanitize(ev){
+ var obj = {};
+ for (var key in ev)
+ if (key.indexOf("_") !== 0)
+ obj[key] = ev[key];
+
+ return obj;
+ }
+
+ var update_timer;
+ function update_view(){
+ clearTimeout(update_timer);
+ update_timer = setTimeout(function(){
+ scheduler.updateView();
+ },1);
+ };
+
+
+scheduler.backbone = function(collection){
+ events.bind("reset", function(){
+ scheduler.clearAll();
+ scheduler.parse(events.toJSON(), "json");
+ });
+ events.bind("change", function(model, info){
+ //special handling for id change
+ if (info.changes && info.changes.id){
+ var old_id = model.previous("id");
+ scheduler.changeEventId(old_id, model.id);
+ }
+
+ var id = model.id;
+ scheduler._init_event( scheduler._events[id] = model.toJSON() );
+ update_view();
+ });
+ events.bind("remove", function(model, changes){
+ if (scheduler._events[model.id])
+ scheduler.deleteEvent(model.id);
+ });
+ events.bind("add", function(model, changes){
+ if (!scheduler._events[model.id]){
+ var ev = model.toJSON();
+ scheduler._init_event(ev);
+ scheduler.addEvent(ev);
+ }
+ });
+
+
+ scheduler.attachEvent("onEventCreated", function(id){
+ var ev = new events.model(scheduler.getEvent(id));
+ scheduler._events[id] = ev.toJSON();
+
+ return true;
+ });
+
+ scheduler.attachEvent("onEventAdded", function(id){
+ if (!events.get(id))
+ events.add( new events.model(sanitize(scheduler.getEvent(id))) );
+
+ return true;
+ });
+ scheduler.attachEvent("onEventChanged", function(id){
+ var ev = events.get(id);
+ var upd = sanitize(scheduler.getEvent(id));
+ ev.set(upd);
+
+ return true;
+ });
+ scheduler.attachEvent("onEventDeleted", function(id){
+ if (events.get(id))
+ events.remove(id);
+ return true;
+ });
+}
+
+})(); \ No newline at end of file
diff --git a/sources/ext/dhtmlxscheduler_offline.js b/sources/ext/dhtmlxscheduler_offline.js
new file mode 100644
index 0000000..18d82fd
--- /dev/null
+++ b/sources/ext/dhtmlxscheduler_offline.js
@@ -0,0 +1,79 @@
+/*
+This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
+to use it in non-GPL project. Please contact sales@dhtmlx.com for details
+*/
+scheduler.load=function(url,call){
+ if (typeof call == "string"){
+ this._process=call;
+ var type = call;
+ call = arguments[2];
+ }
+
+ this._load_url=url;
+ this._after_call=call;
+ if (url.$proxy) {
+ url.load(this, typeof type == "string" ? type : null);
+ return;
+ }
+
+ this._load(url,this._date);
+};
+
+scheduler._dp_init_backup = scheduler._dp_init;
+scheduler._dp_init = function(dp) {
+ dp._sendData = function(a1,rowId){
+ if (!a1) return; //nothing to send
+ if (!this.callEvent("onBeforeDataSending",rowId?[rowId,this.getState(rowId),a1]:[null, null, a1])) return false;
+ if (rowId)
+ this._in_progress[rowId]=(new Date()).valueOf();
+ if (this.serverProcessor.$proxy) {
+ var mode = this._tMode!="POST" ? 'get' : 'post';
+ var to_send = [];
+ for (var i in a1)
+ to_send.push({ id: i, data: a1[i], operation: this.getState(i)});
+ this.serverProcessor._send(to_send, mode, this);
+ return;
+ }
+
+ var a2=new dtmlXMLLoaderObject(this.afterUpdate,this,true);
+ var a3 = this.serverProcessor+(this._user?(getUrlSymbol(this.serverProcessor)+["dhx_user="+this._user,"dhx_version="+this.obj.getUserData(0,"version")].join("&")):"");
+ if (this._tMode!="POST")
+ a2.loadXML(a3+((a3.indexOf("?")!=-1)?"&":"?")+this.serialize(a1,rowId));
+ else
+ a2.loadXML(a3,true,this.serialize(a1,rowId));
+ this._waitMode++;
+ };
+
+ dp._updatesToParams = function(items) {
+ var stack = {};
+ for (var i = 0; i < items.length; i++)
+ stack[items[i].id] = items[i].data;
+ return this.serialize(stack);
+ };
+
+ dp._processResult = function(text, xml, loader) {
+ if (loader.status != 200) {
+ for (var i in this._in_progress) {
+ var state = this.getState(i);
+ this.afterUpdateCallback(i, i, state, null);
+ }
+ return;
+ }
+ xml = new dtmlXMLLoaderObject(function() {},this,true);
+ xml.loadXMLString(text);
+ xml.xmlDoc = loader;
+
+ this.afterUpdate(this, null, null, null, xml);
+ };
+ this._dp_init_backup(dp);
+}
+
+if (window.dataProcessor)
+ dataProcessor.prototype.init=function(obj){
+ this.init_original(obj);
+ obj._dataprocessor=this;
+
+ this.setTransactionMode("POST",true);
+ if (!this.serverProcessor.$proxy)
+ this.serverProcessor+=(this.serverProcessor.indexOf("?")!=-1?"&":"?")+"editing=true";
+ }; \ No newline at end of file
diff --git a/sources/ext/dhtmlxscheduler_outerdrag.js b/sources/ext/dhtmlxscheduler_outerdrag.js
new file mode 100644
index 0000000..177e0fa
--- /dev/null
+++ b/sources/ext/dhtmlxscheduler_outerdrag.js
@@ -0,0 +1,57 @@
+/*
+This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
+to use it in non-GPL project. Please contact sales@dhtmlx.com for details
+*/
+// lame old code doesn't provide raw event object
+scheduler.attachEvent("onTemplatesReady", function() {
+ var dragger = (new dhtmlDragAndDropObject());
+ var old = dragger.stopDrag;
+ var last_event;
+ dragger.stopDrag = function(e) {
+ last_event = e || event;
+ return old.apply(this, arguments);
+ };
+ dragger.addDragLanding(scheduler._els["dhx_cal_data"][0], {
+ _drag: function(sourceHtmlObject, dhtmlObject, targetHtmlObject, targetHtml) {
+
+ if (scheduler.checkEvent("onBeforeExternalDragIn") && !scheduler.callEvent("onBeforeExternalDragIn", [sourceHtmlObject, dhtmlObject, targetHtmlObject, targetHtml, last_event]))
+ return;
+
+ var temp = scheduler.attachEvent("onEventCreated", function(id) {
+ if (!scheduler.callEvent("onExternalDragIn", [id, sourceHtmlObject, last_event])) {
+ this._drag_mode = this._drag_id = null;
+ this.deleteEvent(id);
+ }
+ });
+
+ var action_data = scheduler.getActionData(last_event);
+ var event_data = {
+ start_date: new Date(action_data.date)
+ };
+
+ // custom views, need to assign section id, fix dates
+ if (scheduler.matrix && scheduler.matrix[scheduler._mode]) {
+ var view_options = scheduler.matrix[scheduler._mode];
+ event_data[view_options.y_property] = action_data.section;
+
+ var pos = scheduler._locate_cell_timeline(last_event);
+ event_data.start_date = view_options._trace_x[pos.x];
+ event_data.end_date = scheduler.date.add(event_data.start_date, view_options.x_step, view_options.x_unit);
+ }
+ if (scheduler._props && scheduler._props[scheduler._mode]) {
+ event_data[scheduler._props[scheduler._mode].map_to] = action_data.section
+ }
+
+ scheduler.addEventNow(event_data);
+
+ scheduler.detachEvent(temp);
+
+ },
+ _dragIn: function(htmlObject, shtmlObject) {
+ return htmlObject;
+ },
+ _dragOut: function(htmlObject) {
+ return this;
+ }
+ });
+});
diff --git a/sources/ext/dhtmlxscheduler_pdf.js b/sources/ext/dhtmlxscheduler_pdf.js
new file mode 100644
index 0000000..b7fdb93
--- /dev/null
+++ b/sources/ext/dhtmlxscheduler_pdf.js
@@ -0,0 +1,354 @@
+/*
+This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
+to use it in non-GPL project. Please contact sales@dhtmlx.com for details
+*/
+(function() {
+ var dx, dy;
+ function clean_html(val) {
+ return val.replace(newline_regexp, "\n").replace(html_regexp, "");
+ }
+
+ function x_norm(x, offset) {
+ x = parseFloat(x);
+ offset = parseFloat(offset);
+ if (!isNaN(offset)) x -= offset;
+
+ var w = colsWidth(x);
+ x = x - w.width + w.cols*dx;
+ return isNaN(x)?"auto":(100*x/(dx));
+ }
+
+ function x_norm_event(x, offset, is_left) {
+ x = parseFloat(x);
+ offset = parseFloat(offset);
+ if (!isNaN(offset) && is_left) x -= offset;
+
+ var w = colsWidth(x);
+ x = x - w.width + w.cols*dx;
+ return isNaN(x)?"auto":(100*x/(dx-(!isNaN(offset)?offset:0)));
+ }
+ function colsWidth(width) {
+ var r = 0;
+ var header = scheduler._els.dhx_cal_header[0].childNodes;
+ var els = header[1] ? header[1].childNodes : header[0].childNodes;
+ for (var i = 0; i < els.length; i++) {
+ var el = els[i].style ? els[i] : els[i].parentNode;
+ var w = parseFloat(el.style.width);
+ if (width > w)
+ width -= (w+1),r+=(w+1);
+ else
+ break;
+ }
+ return { width: r, cols: i };
+ }
+
+ function y_norm(y) {
+ y = parseFloat(y);
+ if (isNaN(y)) return "auto";
+ return 100 * y / dy;
+ }
+
+ function get_style(node, style){
+ return (window.getComputedStyle?(window.getComputedStyle(node, null)[style]):(node.currentStyle?node.currentStyle[style]:null))||"";
+ }
+
+ function de_day(node, n) {
+ var x = parseInt(node.style.left, 10);
+
+ for (var dx = 0; dx < scheduler._cols.length; dx++) {
+ x -= scheduler._cols[dx];
+ if (x < 0) return dx;
+ }
+ return n;
+ }
+
+ function de_week(node, n) {
+ var y = parseInt(node.style.top, 10);
+ for (var dy = 0; dy < scheduler._colsS.heights.length; dy++)
+ if (scheduler._colsS.heights[dy] > y) return dy;
+ return n;
+ }
+
+ function xml_start(tag) {
+ return tag ? "<"+tag+">" : "";
+ }
+ function xml_end(tag) {
+ return tag ? "</"+tag+">" : "";
+ }
+
+ function xml_top(tag, profile, header, footer) {
+ var xml = "<"+tag+" profile='" + profile + "'";
+ if (header)
+ xml += " header='" + header + "'";
+ if (footer)
+ xml += " footer='" + footer + "'";
+ xml += ">";
+ return xml;
+ }
+
+ function xml_body_header() {
+ var xml = "";
+ // detects if current mode is timeline
+ var mode = scheduler._mode;
+ if (scheduler.matrix && scheduler.matrix[scheduler._mode])
+ mode = (scheduler.matrix[scheduler._mode].render == "cell") ? "matrix" : "timeline";
+ xml += "<scale mode='" + mode + "' today='" + scheduler._els.dhx_cal_date[0].innerHTML + "'>";
+
+ if (scheduler._mode == "week_agenda") {
+ var xh = scheduler._els.dhx_cal_data[0].getElementsByTagName("DIV");
+ for (var i = 0; i < xh.length; i++)
+ if (xh[i].className == "dhx_wa_scale_bar")
+ xml += "<column>" + clean_html(xh[i].innerHTML) + "</column>";
+ } else if (scheduler._mode == "agenda" || scheduler._mode == "map") {
+ var xh = scheduler._els.dhx_cal_header[0].childNodes[0].childNodes;
+
+ xml += "<column>" + clean_html(xh[0].innerHTML) + "</column><column>" + clean_html(xh[1].innerHTML) + "</column>";
+ } else if (scheduler._mode == "year") {
+ var xh = scheduler._els.dhx_cal_data[0].childNodes;
+ for (var i = 0; i < xh.length; i++) {
+ xml += "<month label='" + clean_html(xh[i].childNodes[0].innerHTML) + "'>";
+ xml += xml_month_scale(xh[i].childNodes[1].childNodes);
+ xml += xml_month(xh[i].childNodes[2]);
+ xml += "</month>";
+ }
+ } else {
+ xml += "<x>";
+ var xh = scheduler._els.dhx_cal_header[0].childNodes;
+ xml += xml_month_scale(xh);
+ xml += "</x>";
+
+ var yh = scheduler._els.dhx_cal_data[0];
+ if (scheduler.matrix && scheduler.matrix[scheduler._mode]) {
+ xml += "<y>";
+ for (var i = 0; i < yh.firstChild.rows.length; i++) {
+ var el = yh.firstChild.rows[i];
+ xml += "<row><![CDATA[" + clean_html(el.cells[0].innerHTML) + "]]></row>";
+ }
+ xml += "</y>";
+ dy = yh.firstChild.rows[0].cells[0].offsetHeight;
+ } else if (yh.firstChild.tagName == "TABLE") {
+ xml += xml_month(yh);
+ } else {
+ yh = yh.childNodes[yh.childNodes.length - 1];
+ while (yh.className.indexOf("dhx_scale_holder") == -1)
+ yh = yh.previousSibling;
+ yh = yh.childNodes;
+
+ xml += "<y>";
+ for (var i = 0; i < yh.length; i++)
+ xml += "\n<row><![CDATA[" + clean_html(yh[i].innerHTML) + "]]></row>";
+ xml += "</y>";
+ dy = yh[0].offsetHeight;
+ }
+ }
+ xml += "</scale>";
+ return xml;
+ }
+
+ function xml_month(yh) {
+ var xml = "";
+ var r = yh.firstChild.rows;
+ for (var i = 0; i < r.length; i++) {
+ var days = [];
+ for (var j = 0; j < r[i].cells.length; j++)
+ days.push(r[i].cells[j].firstChild.innerHTML);
+
+ xml += "\n<row height='" + yh.firstChild.rows[i].cells[0].offsetHeight + "'><![CDATA[" + clean_html(days.join("|")) + "]]></row>";
+ dy = yh.firstChild.rows[0].cells[0].offsetHeight;
+ }
+ return xml;
+ }
+
+ function xml_month_scale(xh) {
+ var xml = "";
+ if (scheduler.matrix && scheduler.matrix[scheduler._mode]) {
+ if (scheduler.matrix[scheduler._mode].second_scale)
+ var xhs = xh[1].childNodes;
+
+ xh = xh[0].childNodes;
+ }
+
+ for (var i = 0; i < xh.length; i++)
+ xml += "\n<column><![CDATA[" + clean_html(xh[i].innerHTML) + "]]></column>";
+ dx = xh[0].offsetWidth;
+
+ if (xhs) {
+ var width = 0;
+ var top_width = xh[0].offsetWidth;
+ var top_col = 1;
+ for (var i = 0; i < xhs.length; i++) {
+ xml += "\n<column second_scale='" + top_col + "'><![CDATA[" + clean_html(xhs[i].innerHTML) + "]]></column>";
+ width += xhs[i].offsetWidth;
+ if (width >= top_width) {
+ top_width += (xh[top_col] ? xh[top_col].offsetWidth : 0);
+ top_col++;
+ }
+ dx = xhs[0].offsetWidth;
+ }
+ }
+ return xml;
+ }
+
+ function xml_body(colors) {
+ var xml = "";
+ var evs = scheduler._rendered;
+ var matrix = scheduler.matrix && scheduler.matrix[scheduler._mode];
+
+ if (scheduler._mode == "agenda" || scheduler._mode == "map") {
+
+ for (var i = 0; i < evs.length; i++)
+ xml += "<event><head><![CDATA[" + clean_html(evs[i].childNodes[0].innerHTML) + "]]></head><body><![CDATA[" + clean_html(evs[i].childNodes[2].innerHTML) + "]]></body></event>";
+
+ } else if (scheduler._mode == "week_agenda") {
+
+ for (var i = 0; i < evs.length; i++)
+ xml += "<event day='" + evs[i].parentNode.getAttribute("day") + "'><body>" + clean_html(evs[i].innerHTML) + "</body></event>";
+
+ } else if (scheduler._mode == "year") {
+
+ var evs = scheduler.get_visible_events();
+ for (var i = 0; i < evs.length; i++) {
+ var d = evs[i].start_date;
+ if (d.valueOf() < scheduler._min_date.valueOf())
+ d = scheduler._min_date;
+
+ while (d < evs[i].end_date) {
+ var m = d.getMonth() + 12 * (d.getFullYear() - scheduler._min_date.getFullYear()) - scheduler.week_starts._month;
+ var day = scheduler.week_starts[m] + d.getDate() - 1;
+ var text_color = colors ? get_style(scheduler._get_year_cell(d), "color") : "";
+ var bg_color = colors ? get_style(scheduler._get_year_cell(d), "backgroundColor") : "";
+
+ xml += "<event day='" + (day % 7) + "' week='" + Math.floor(day / 7) + "' month='" + m + "' backgroundColor='" + bg_color + "' color='" + text_color + "'></event>";
+ d = scheduler.date.add(d, 1, "day");
+ if (d.valueOf() >= scheduler._max_date.valueOf())
+ break;
+ }
+ }
+ } else if (matrix && matrix.render == "cell") {
+ var evs = scheduler._els.dhx_cal_data[0].getElementsByTagName("TD");
+ for (var i = 0; i < evs.length; i++) {
+ var text_color = colors ? get_style(evs[i], "color") : "";
+ var bg_color = colors ? get_style(evs[i], "backgroundColor") : "";
+ xml += "\n<event><body backgroundColor='" + bg_color + "' color='" + text_color + "'><![CDATA[" + clean_html(evs[i].innerHTML) + "]]></body></event>";
+ }
+ } else {
+ for (var i = 0; i < evs.length; i++) {
+ var zx, zdx;
+ if (scheduler.matrix && scheduler.matrix[scheduler._mode]) {
+ // logic for timeline view
+ zx = x_norm(evs[i].style.left);
+ zdx = x_norm(evs[i].offsetWidth)-1;
+ } else {
+ // we should use specific logic for day/week/units view
+ var left_norm = scheduler.config.use_select_menu_space ? 0 : 26;
+ zx = x_norm_event(evs[i].style.left, left_norm, true);
+ zdx = x_norm_event(evs[i].style.width, left_norm)-1;
+ }
+ if (isNaN(zdx * 1)) continue;
+ var zy = y_norm(evs[i].style.top);
+ var zdy = y_norm(evs[i].style.height);
+
+ var e_type = evs[i].className.split(" ")[0].replace("dhx_cal_", "");
+ if (e_type === 'dhx_tooltip_line') continue;
+
+ var dets = scheduler.getEvent(evs[i].getAttribute("event_id"));
+ if (!dets) continue;
+ var day = dets._sday;
+ var week = dets._sweek;
+ var length = dets._length || 0;
+
+ if (scheduler._mode == "month") {
+ zdy = parseInt(evs[i].offsetHeight, 10);
+ zy = parseInt(evs[i].style.top, 10) - 22;
+
+ day = de_day(evs[i], day);
+ week = de_week(evs[i], week);
+ } else if (scheduler.matrix && scheduler.matrix[scheduler._mode]) {
+ day = 0;
+ var el = evs[i].parentNode.parentNode.parentNode;
+ week = el.rowIndex;
+ var dy_copy = dy;
+ dy = evs[i].parentNode.offsetHeight;
+ zy = y_norm(evs[i].style.top);
+ zy -= zy * 0.2;
+ dy = dy_copy;
+ } else {
+ if (evs[i].parentNode == scheduler._els.dhx_cal_data[0]) continue;
+ var parent = scheduler._els["dhx_cal_data"][0].childNodes[0];
+ var offset = parseFloat(parent.className.indexOf("dhx_scale_holder") != -1 ? parent.style.left : 0);
+ zx += x_norm(evs[i].parentNode.style.left, offset);
+ }
+
+ xml += "\n<event week='" + week + "' day='" + day + "' type='" + e_type + "' x='" + zx + "' y='" + zy + "' width='" + zdx + "' height='" + zdy + "' len='" + length + "'>";
+
+ if (e_type == "event") {
+ xml += "<header><![CDATA[" + clean_html(evs[i].childNodes[1].innerHTML) + "]]></header>";
+ var text_color = colors ? get_style(evs[i].childNodes[2], "color") : "";
+ var bg_color = colors ? get_style(evs[i].childNodes[2], "backgroundColor") : "";
+ xml += "<body backgroundColor='" + bg_color + "' color='" + text_color + "'><![CDATA[" + clean_html(evs[i].childNodes[2].innerHTML) + "]]></body>";
+ } else {
+ var text_color = colors ? get_style(evs[i], "color") : "";
+ var bg_color = colors ? get_style(evs[i], "backgroundColor") : "";
+ xml += "<body backgroundColor='" + bg_color + "' color='" + text_color + "'><![CDATA[" + clean_html(evs[i].innerHTML) + "]]></body>";
+ }
+ xml += "</event>";
+ }
+ }
+
+ return xml;
+ }
+
+ function to_pdf(start, end, view, url, mode, header, footer) {
+ var colors = false;
+ if (mode == "fullcolor") {
+ colors = true;
+ mode = "color";
+ }
+
+ mode = mode || "color";
+ html_regexp = new RegExp("<[^>]*>", "g");
+ newline_regexp = new RegExp("<br[^>]*>", "g");
+
+ var uid = scheduler.uid();
+ var d = document.createElement("div");
+ d.style.display = "none";
+ document.body.appendChild(d);
+
+ d.innerHTML = '<form id="' + uid + '" method="post" target="_blank" action="' + url + '" accept-charset="utf-8" enctype="application/x-www-form-urlencoded"><input type="hidden" name="mycoolxmlbody"/> </form>';
+
+
+ var xml = "";
+ if (start) {
+ var original_date = scheduler._date;
+ var original_mode = scheduler._mode;
+
+ xml = xml_top("pages", mode, header, footer);
+ for (var temp_date = new Date(start); +temp_date < +end; temp_date = scheduler.date.add(temp_date, 1, view)) {
+ scheduler.setCurrentView(temp_date, view);
+ xml += xml_start("page") + xml_body_header().replace("\u2013", "-") + xml_body(colors) + xml_end("page");
+ }
+ xml += xml_end("pages");
+
+ scheduler.setCurrentView(original_date, original_mode);
+ } else {
+ xml = xml_top("data", mode, header, footer) + xml_body_header().replace("\u2013", "-") + xml_body(colors) + xml_end("data");
+ }
+
+
+ document.getElementById(uid).firstChild.value = encodeURIComponent(xml);
+ document.getElementById(uid).submit();
+ d.parentNode.removeChild(d);
+ }
+
+ scheduler.toPDF = function(url, mode, header, footer) {
+ return to_pdf.apply(this, [null, null, null, url, mode, header, footer]);
+ };
+ scheduler.toPDFRange = function(start, end, view, url, mode, header, footer) {
+ if (typeof start == "string") {
+ start = scheduler.templates.api_date(start);
+ end = scheduler.templates.api_date(end);
+ }
+
+ return to_pdf.apply(this, arguments);
+ };
+})();
diff --git a/sources/ext/dhtmlxscheduler_quick_info.js b/sources/ext/dhtmlxscheduler_quick_info.js
new file mode 100644
index 0000000..425cee3
--- /dev/null
+++ b/sources/ext/dhtmlxscheduler_quick_info.js
@@ -0,0 +1,181 @@
+/*
+This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
+to use it in non-GPL project. Please contact sales@dhtmlx.com for details
+*/
+scheduler.config.icons_select = ["icon_details", "icon_delete"];
+scheduler.config.details_on_create = true;
+scheduler.xy.menu_width = 0;
+
+scheduler.attachEvent("onClick", function(id){
+ scheduler.showQuickInfo(id);
+ return true;
+});
+
+(function(){
+ var events = ["onEmptyClick", "onViewChange", "onLightbox", "onBeforeEventDelete", "onBeforeDrag"];
+ var hiding_function = function(){
+ scheduler._hideQuickInfo();
+ return true;
+ };
+ for (var i=0; i<events.length; i++)
+ scheduler.attachEvent(events[i], hiding_function);
+})();
+
+scheduler.templates.quick_info_title = function(start, end, ev){ return ev.text.substr(0,50); };
+scheduler.templates.quick_info_content = function(start, end, ev){ return ev.details || ev.text; };
+scheduler.templates.quick_info_date = function(start, end, ev){
+ if (scheduler.isOneDayEvent(ev))
+ return scheduler.templates.day_date(start, end, ev) + " " +scheduler.templates.event_header(start, end, ev);
+ else
+ return scheduler.templates.week_date(start, end, ev);
+};
+
+scheduler.showQuickInfo = function(id){
+ if (id == this._quick_info_box_id) return;
+ this.hideQuickInfo(true);
+
+ var pos = this._get_event_counter_part(id);
+
+ if (pos){
+ this._quick_info_box = this._init_quick_info(pos);
+ this._fill_quick_data(id);
+ this._show_quick_info(pos);
+ }
+};
+scheduler._hideQuickInfo = function(){
+ scheduler.hideQuickInfo();
+};
+scheduler.hideQuickInfo = function(forced){
+ var qi = this._quick_info_box;
+ this._quick_info_box_id = 0;
+
+ if (qi && qi.parentNode){
+ if (scheduler.config.quick_info_detached)
+ return qi.parentNode.removeChild(qi);
+
+ if (qi.style.right == "auto")
+ qi.style.left = "-350px";
+ else
+ qi.style.right = "-350px";
+
+ if (forced)
+ qi.parentNode.removeChild(qi);
+ }
+};
+dhtmlxEvent(window, "keydown", function(e){
+ if (e.keyCode == 27)
+ scheduler.hideQuickInfo();
+});
+
+scheduler._show_quick_info = function(pos){
+ var qi = scheduler._quick_info_box;
+
+ if (scheduler.config.quick_info_detached){
+ scheduler._obj.appendChild(qi);
+ var width = qi.offsetWidth;
+ var height = qi.offsetHeight;
+
+ qi.style.left = pos.left - pos.dx*(width - pos.width) + "px";
+ qi.style.top = pos.top - (pos.dy?height:-pos.height) + "px";
+ } else {
+ qi.style.top = this.xy.scale_height+this.xy.nav_height + 20 + "px";
+ if (pos.dx == 1){
+ qi.style.right = "auto";
+ qi.style.left = "-300px";
+
+ setTimeout(function(){
+ qi.style.left = "-10px";
+ },1);
+ } else {
+ qi.style.left = "auto";
+ qi.style.right = "-300px";
+
+ setTimeout(function(){
+ qi.style.right = "-10px";
+ },1);
+ }
+ qi.className = qi.className.replace("dhx_qi_left","").replace("dhx_qi_left","")+" dhx_qi_"+(pos==1?"left":"right");
+ scheduler._obj.appendChild(qi);
+ }
+};
+scheduler._init_quick_info = function(){
+ if (!this._quick_info_box){
+ var sizes = scheduler.xy;
+
+ var qi = this._quick_info_box = document.createElement("div");
+ qi.className = "dhx_cal_quick_info";
+ if (scheduler.$testmode)
+ qi.className += " dhx_no_animate";
+ //title
+ var html = "<div class=\"dhx_cal_qi_title\" style=\"height:"+sizes.quick_info_title+"px\">"
+ + "<div class=\"dhx_cal_qi_tcontent\"></div><div class=\"dhx_cal_qi_tdate\"></div>"
+ +"</div>"
+ +"<div class=\"dhx_cal_qi_content\"></div>";
+
+ //buttons
+ html += "<div class=\"dhx_cal_qi_controls\" style=\"height:"+sizes.quick_info_buttons+"px\">";
+ var buttons = scheduler.config.icons_select;
+ for (var i = 0; i < buttons.length; i++)
+ html += "<div class=\"dhx_qi_big_icon "+buttons[i]+"\" title=\""+scheduler.locale.labels[buttons[i]]+"\"><div class='dhx_menu_icon " + buttons[i] + "'></div><div>"+scheduler.locale.labels[buttons[i]]+"</div></div>";
+ html += "</div>";
+
+ qi.innerHTML = html;
+ dhtmlxEvent(qi, "click", function(ev){
+ ev = ev || event;
+ scheduler._qi_button_click(ev.target || ev.srcElement);
+ });
+ if (scheduler.config.quick_info_detached)
+ dhtmlxEvent(scheduler._els["dhx_cal_data"][0], "scroll", function(){ scheduler.hideQuickInfo(); });
+ }
+
+ return this._quick_info_box;
+};
+
+scheduler._qi_button_click = function(node){
+ var box = scheduler._quick_info_box;
+ if (!node || node == box) return;
+
+ var mask = node.className;
+ if (mask.indexOf("_icon")!=-1){
+ var id = scheduler._quick_info_box_id;
+ scheduler._click.buttons[mask.split(" ")[1].replace("icon_","")](id);
+ } else
+ scheduler._qi_button_click(node.parentNode);
+};
+scheduler._get_event_counter_part = function(id){
+ var domEv = scheduler.getRenderedEvent(id);
+ var left = 0;
+ var top = 0;
+
+ var node = domEv;
+ while (node && node != scheduler._obj){
+ left += node.offsetLeft;
+ top += node.offsetTop-node.scrollTop;
+ node = node.offsetParent;
+ }
+ if(node){
+ var dx = (left + domEv.offsetWidth/2) > (scheduler._x/2) ? 1 : 0;
+ var dy = (top + domEv.offsetHeight/2) > (scheduler._y/2) ? 1 : 0;
+
+ return { left:left, top:top, dx:dx, dy:dy,
+ width:domEv.offsetWidth, height:domEv.offsetHeight };
+ }
+ return 0;
+};
+
+scheduler._fill_quick_data = function(id){
+ var ev = scheduler.getEvent(id);
+ var qi = scheduler._quick_info_box;
+
+ scheduler._quick_info_box_id = id;
+
+//title content
+ var titleContent = qi.firstChild.firstChild;
+ titleContent.innerHTML = scheduler.templates.quick_info_title(ev.start_date, ev.end_date, ev);
+ var titleDate = titleContent.nextSibling;
+ titleDate.innerHTML = scheduler.templates.quick_info_date(ev.start_date, ev.end_date, ev);
+
+//main content
+ var main = qi.firstChild.nextSibling;
+ main.innerHTML = scheduler.templates.quick_info_content(ev.start_date, ev.end_date, ev);
+};
diff --git a/sources/ext/dhtmlxscheduler_readonly.js b/sources/ext/dhtmlxscheduler_readonly.js
new file mode 100644
index 0000000..0ead182
--- /dev/null
+++ b/sources/ext/dhtmlxscheduler_readonly.js
@@ -0,0 +1,163 @@
+/*
+This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
+to use it in non-GPL project. Please contact sales@dhtmlx.com for details
+*/
+scheduler.attachEvent("onTemplatesReady", function() {
+ var original_sns = scheduler.config.lightbox.sections;
+ var recurring_section = null;
+ var original_left_buttons = scheduler.config.buttons_left.slice();
+ var original_right_buttons = scheduler.config.buttons_right.slice();
+
+
+ scheduler.attachEvent("onBeforeLightbox", function(id) {
+ if (this.config.readonly_form || this.getEvent(id).readonly) {
+ this.config.readonly_active = true;
+
+ for (var i = 0; i < this.config.lightbox.sections.length; i++) {
+ this.config.lightbox.sections[i].focus = false;
+ }
+ }
+ else {
+ this.config.readonly_active = false;
+ scheduler.config.buttons_left = original_left_buttons.slice();
+ scheduler.config.buttons_right = original_right_buttons.slice();
+ }
+
+ var sns = this.config.lightbox.sections;
+ if (this.config.readonly_active) {
+ var is_rec_found = false;
+ for (var i = 0; i < sns.length; i++) {
+ if (sns[i].type == 'recurring') {
+ recurring_section = sns[i];
+ if (this.config.readonly_active) {
+ sns.splice(i, 1);
+ }
+ break;
+ }
+ }
+ if (!is_rec_found && !this.config.readonly_active && recurring_section) {
+ // need to restore restore section
+ sns.splice(sns.length-2,0,recurring_section);
+ }
+
+ var forbidden_buttons = ["dhx_delete_btn", "dhx_save_btn"];
+ var button_arrays = [scheduler.config.buttons_left, scheduler.config.buttons_right];
+ for (var i = 0; i < forbidden_buttons.length; i++) {
+ var forbidden_button = forbidden_buttons[i];
+ for (var k = 0; k < button_arrays.length; k++) {
+ var button_array = button_arrays[k];
+ var index = -1;
+ for (var p = 0; p < button_array.length; p++) {
+ if (button_array[p] == forbidden_button) {
+ index = p;
+ break;
+ }
+ }
+ if (index != -1) {
+ button_array.splice(index, 1);
+ }
+ }
+ }
+
+
+ }
+
+ this.resetLightbox();
+
+ return true;
+ });
+
+ function txt_replace(tag, d, n, text) {
+ var txts = d.getElementsByTagName(tag);
+ var txtt = n.getElementsByTagName(tag);
+ for (var i = txtt.length - 1; i >= 0; i--) {
+ var n = txtt[i];
+ if (!text){
+ n.disabled = true;
+ //radio and checkboxes loses state after .cloneNode in IE
+ if(d.checked)
+ n.checked = true;
+ }else {
+ var t = document.createElement("SPAN");
+ t.className = "dhx_text_disabled";
+ t.innerHTML = text(txts[i]);
+ n.parentNode.insertBefore(t, n);
+ n.parentNode.removeChild(n);
+ }
+ }
+ }
+
+ var old = scheduler._fill_lightbox;
+ scheduler._fill_lightbox = function() {
+
+ var lb = this.getLightbox();
+ if (this.config.readonly_active) {
+ lb.style.visibility = 'hidden';
+ // lightbox should have actual sizes before rendering controls
+ // currently only matters for dhtmlxCombo
+ lb.style.display = 'block';
+ }
+ var res = old.apply(this, arguments);
+ if (this.config.readonly_active) {
+ //reset visibility and display
+ lb.style.visibility = '';
+ lb.style.display = 'none';
+ }
+
+ if (this.config.readonly_active) {
+
+ var d = this.getLightbox();
+ var n = this._lightbox_r = d.cloneNode(true);
+ n.id = scheduler.uid();
+
+ txt_replace("textarea", d, n, function(a) {
+ return a.value;
+ });
+ txt_replace("input", d, n, false);
+ txt_replace("select", d, n, function(a) {
+ if(!a.options.length) return "";
+ return a.options[Math.max((a.selectedIndex || 0), 0)].text;
+ });
+
+ d.parentNode.insertBefore(n, d);
+
+ olds.call(this, n);
+ if (scheduler._lightbox)
+ scheduler._lightbox.parentNode.removeChild(scheduler._lightbox);
+ this._lightbox = n;
+
+ if (scheduler.config.drag_lightbox)
+ n.firstChild.onmousedown = scheduler._ready_to_dnd;
+ this.setLightboxSize();
+ n.onclick = function(e) {
+ var src = e ? e.target : event.srcElement;
+ if (!src.className) src = src.previousSibling;
+ if (src && src.className)
+ switch (src.className) {
+ case "dhx_cancel_btn":
+ scheduler.callEvent("onEventCancel", [scheduler._lightbox_id]);
+ scheduler._edit_stop_event(scheduler.getEvent(scheduler._lightbox_id), false);
+ scheduler.hide_lightbox();
+ break;
+ }
+ };
+ }
+ return res;
+ };
+
+ var olds = scheduler.showCover;
+ scheduler.showCover = function() {
+ if (!this.config.readonly_active)
+ olds.apply(this, arguments);
+ };
+
+ var hold = scheduler.hide_lightbox;
+ scheduler.hide_lightbox = function() {
+ if (this._lightbox_r) {
+ this._lightbox_r.parentNode.removeChild(this._lightbox_r);
+ this._lightbox_r = this._lightbox = null;
+ }
+
+ return hold.apply(this, arguments);
+ };
+});
diff --git a/sources/ext/dhtmlxscheduler_recurring.js b/sources/ext/dhtmlxscheduler_recurring.js
new file mode 100644
index 0000000..56eb265
--- /dev/null
+++ b/sources/ext/dhtmlxscheduler_recurring.js
@@ -0,0 +1,786 @@
+/*
+This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
+to use it in non-GPL project. Please contact sales@dhtmlx.com for details
+*/
+
+scheduler.config.occurrence_timestamp_in_utc = false;
+scheduler.form_blocks["recurring"] = {
+ render:function(sns) {
+ return scheduler.__recurring_template;
+ },
+ _ds: {},
+ _init_set_value:function(node, value, ev) {
+ scheduler.form_blocks["recurring"]._ds = {start:ev.start_date, end:ev._end_date};
+
+ var str_date_format = scheduler.date.str_to_date(scheduler.config.repeat_date);
+ var str_date = function(str_date) {
+ var date = str_date_format(str_date);
+ if (scheduler.config.include_end_by)
+ date = scheduler.date.add(date, 1, 'day');
+ return date;
+ };
+
+ var date_str = scheduler.date.date_to_str(scheduler.config.repeat_date);
+
+ var top = node.getElementsByTagName("FORM")[0];
+ var els = [];
+
+ function register_els(inps) {
+ for (var i = 0; i < inps.length; i++) {
+ var inp = inps[i];
+ if (inp.type == "checkbox" || inp.type == "radio") {
+ if (!els[inp.name])
+ els[inp.name] = [];
+ els[inp.name].push(inp);
+ } else
+ els[inp.name] = inp;
+ }
+ }
+
+ register_els(top.getElementsByTagName("INPUT"));
+ register_els(top.getElementsByTagName("SELECT"));
+
+ if (!scheduler.config.repeat_date_of_end) {
+ var formatter = scheduler.date.date_to_str(scheduler.config.repeat_date);
+ scheduler.config.repeat_date_of_end = formatter(scheduler.date.add(scheduler._currentDate(), 30, "day"));
+ }
+ els["date_of_end"].value = scheduler.config.repeat_date_of_end;
+
+ var $ = function(a) {
+ return document.getElementById(a);
+ };
+
+ function get_radio_value(name) {
+ var col = els[name];
+ for (var i = 0; i < col.length; i++)
+ if (col[i].checked) return col[i].value;
+ }
+
+ function change_current_view() {
+ $("dhx_repeat_day").style.display = "none";
+ $("dhx_repeat_week").style.display = "none";
+ $("dhx_repeat_month").style.display = "none";
+ $("dhx_repeat_year").style.display = "none";
+ $("dhx_repeat_" + this.value).style.display = "block";
+ }
+
+ function get_repeat_code(dates) {
+ var code = [get_radio_value("repeat")];
+ get_rcode[code[0]](code, dates);
+
+ while (code.length < 5) code.push("");
+ var repeat = "";
+ if (els["end"][0].checked) {
+ dates.end = new Date(9999, 1, 1);
+ repeat = "no";
+ }
+ else if (els["end"][2].checked) {
+ dates.end = str_date(els["date_of_end"].value);
+ }
+ else {
+ scheduler.transpose_type(code.join("_"));
+ repeat = Math.max(1, els["occurences_count"].value);
+ var transp = ((code[0] == "week" && code[4] && code[4].toString().indexOf(scheduler.config.start_on_monday ? 1 : 0) == -1) ? 1 : 0);
+ dates.end = scheduler.date.add(new Date(dates.start), repeat + transp, code.join("_"));
+ }
+
+ return code.join("_") + "#" + repeat;
+ }
+
+ scheduler.form_blocks["recurring"]._get_repeat_code = get_repeat_code;
+ var get_rcode = {
+ month:function(code, dates) {
+ if (get_radio_value("month_type") == "d") {
+ code.push(Math.max(1, els["month_count"].value));
+ dates.start.setDate(els["month_day"].value);
+ } else {
+ code.push(Math.max(1, els["month_count2"].value));
+ code.push(els["month_day2"].value);
+ code.push(Math.max(1, els["month_week2"].value));
+ dates.start.setDate(1);
+ }
+ dates._start = true;
+ },
+ week:function(code, dates) {
+ code.push(Math.max(1, els["week_count"].value));
+ code.push("");
+ code.push("");
+ var t = [];
+ var col = els["week_day"];
+ var day = dates.start.getDay();
+ var start_exists = false;
+
+ for (var i = 0; i < col.length; i++){
+ if (col[i].checked) {
+ t.push(col[i].value);
+ start_exists = start_exists || col[i].value == day;
+ }
+ }
+ if (!t.length){
+ t.push(day);
+ start_exists = true;
+ }
+ t.sort();
+
+
+ if (!scheduler.config.repeat_precise){
+ dates.start = scheduler.date.week_start(dates.start);
+ dates._start = true;
+ } else if (!start_exists){
+ scheduler.transpose_day_week(dates.start, t, 1, 7);
+ dates._start = true;
+ }
+
+ code.push(t.join(","));
+ },
+ day:function(code) {
+ if (get_radio_value("day_type") == "d") {
+ code.push(Math.max(1, els["day_count"].value));
+ }
+ else {
+ code.push("week");
+ code.push(1);
+ code.push("");
+ code.push("");
+ code.push("1,2,3,4,5");
+ code.splice(0, 1);
+ }
+ },
+ year:function(code, dates) {
+ if (get_radio_value("year_type") == "d") {
+ code.push("1");
+ dates.start.setMonth(0);
+ dates.start.setDate(els["year_day"].value);
+ dates.start.setMonth(els["year_month"].value);
+
+ } else {
+ code.push("1");
+ code.push(els["year_day2"].value);
+ code.push(els["year_week2"].value);
+ dates.start.setDate(1);
+ dates.start.setMonth(els["year_month2"].value);
+ }
+ dates._start = true;
+ }
+ };
+ var set_rcode = {
+ week:function(code, dates) {
+ els["week_count"].value = code[1];
+ var col = els["week_day"];
+ var t = code[4].split(",");
+ var d = {};
+ for (var i = 0; i < t.length; i++) d[t[i]] = true;
+ for (var i = 0; i < col.length; i++)
+ col[i].checked = (!!d[col[i].value]);
+ },
+ month:function(code, dates) {
+ if (code[2] == "") {
+ els["month_type"][0].checked = true;
+ els["month_count"].value = code[1];
+ els["month_day"].value = dates.start.getDate();
+ } else {
+ els["month_type"][1].checked = true;
+ els["month_count2"].value = code[1];
+ els["month_week2"].value = code[3];
+ els["month_day2"].value = code[2];
+ }
+ },
+ day:function(code, dates) {
+ els["day_type"][0].checked = true;
+ els["day_count"].value = code[1];
+ },
+ year:function(code, dates) {
+ if (code[2] == "") {
+ els["year_type"][0].checked = true;
+ els["year_day"].value = dates.start.getDate();
+ els["year_month"].value = dates.start.getMonth();
+ } else {
+ els["year_type"][1].checked = true;
+ els["year_week2"].value = code[3];
+ els["year_day2"].value = code[2];
+ els["year_month2"].value = dates.start.getMonth();
+ }
+ }
+ };
+
+ function set_repeat_code(code, dates) {
+ var data = code.split("#");
+ code = data[0].split("_");
+ set_rcode[code[0]](code, dates);
+ var e = els["repeat"][({day:0, week:1, month:2, year:3})[code[0]]];
+ switch (data[1]) {
+ case "no":
+ els["end"][0].checked = true;
+ break;
+ case "":
+ els["end"][2].checked = true;
+ els["date_of_end"].value = date_str(dates.end);
+ break;
+ default:
+ els["end"][1].checked = true;
+ els["occurences_count"].value = data[1];
+ break;
+ }
+
+ e.checked = true;
+ e.onclick();
+ }
+
+ scheduler.form_blocks["recurring"]._set_repeat_code = set_repeat_code;
+
+ for (var i = 0; i < top.elements.length; i++) {
+ var el = top.elements[i];
+ switch (el.name) {
+ case "repeat":
+ el.onclick = change_current_view;
+ break;
+ }
+ }
+ scheduler._lightbox._rec_init_done = true;
+ },
+ set_value:function(node, value, ev) {
+ var rf = scheduler.form_blocks["recurring"];
+ if (!scheduler._lightbox._rec_init_done)
+ rf._init_set_value(node, value, ev);
+ node.open = !ev.rec_type;
+ if (ev.event_pid && ev.event_pid != "0")
+ node.blocked = true;
+ else node.blocked = false;
+
+ var ds = rf._ds;
+ ds.start = ev.start_date;
+ ds.end = ev._end_date;
+
+ rf.button_click(0, node.previousSibling.firstChild.firstChild, node, node);
+ if (value)
+ rf._set_repeat_code(value, ds);
+ },
+ get_value:function(node, ev) {
+ if (node.open) {
+ var ds = scheduler.form_blocks["recurring"]._ds;
+ var actual_dates = {};
+ this.formSection('time').getValue(actual_dates);
+ ds.start = actual_dates.start_date;
+ ev.rec_type = scheduler.form_blocks["recurring"]._get_repeat_code(ds);
+ if (ds._start) {
+ ev.start_date = new Date(ds.start);
+ ev._start_date = new Date(ds.start);
+ ds._start = false;
+ } else
+ ev._start_date = null;
+
+ ev._end_date = ds.end;
+ ev.rec_pattern = ev.rec_type.split("#")[0];
+ } else {
+ ev.rec_type = ev.rec_pattern = "";
+ ev._end_date = ev.end_date;
+ }
+ return ev.rec_type;
+ },
+ focus:function(node) {
+ },
+ button_click:function(index, el, section, cont) {
+ if (!cont.open && !cont.blocked) {
+ cont.style.height = "115px";
+ el.style.backgroundPosition = "-5px 0px";
+ el.nextSibling.innerHTML = scheduler.locale.labels.button_recurring_open;
+ } else {
+ cont.style.height = "0px";
+ el.style.backgroundPosition = "-5px 20px";
+ el.nextSibling.innerHTML = scheduler.locale.labels.button_recurring;
+ }
+ cont.open = !cont.open;
+
+ scheduler.setLightboxSize();
+ }
+};
+
+
+//problem may occur if we will have two repeating events in the same moment of time
+scheduler._rec_markers = {};
+scheduler._rec_markers_pull = {};
+scheduler._add_rec_marker = function(ev, time) {
+ ev._pid_time = time;
+ this._rec_markers[ev.id] = ev;
+ if (!this._rec_markers_pull[ev.event_pid]) this._rec_markers_pull[ev.event_pid] = {};
+ this._rec_markers_pull[ev.event_pid][time] = ev;
+};
+scheduler._get_rec_marker = function(time, id) {
+ var ch = this._rec_markers_pull[id];
+ if (ch) return ch[time];
+ return null;
+};
+scheduler._get_rec_markers = function(id) {
+ return (this._rec_markers_pull[id] || []);
+};
+scheduler._rec_temp = [];
+(function() {
+ var old_add_event = scheduler.addEvent;
+ scheduler.addEvent = function(start_date, end_date, text, id, extra_data) {
+ var ev_id = old_add_event.apply(this, arguments);
+
+ if (ev_id) {
+ var ev = scheduler.getEvent(ev_id);
+ if (ev.event_pid != 0)
+ scheduler._add_rec_marker(ev, ev.event_length * 1000);
+ if (ev.rec_type)
+ ev.rec_pattern = ev.rec_type.split("#")[0];
+ }
+ return ev_id;
+ };
+})();
+scheduler.attachEvent("onEventIdChange", function(id, new_id) {
+ if (this._ignore_call) return;
+ this._ignore_call = true;
+
+ for (var i = 0; i < this._rec_temp.length; i++) {
+ var tev = this._rec_temp[i];
+ if (tev.event_pid == id) {
+ tev.event_pid = new_id;
+ this.changeEventId(tev.id, new_id + "#" + tev.id.split("#")[1]);
+ }
+ }
+
+ delete this._ignore_call;
+});
+scheduler.attachEvent("onConfirmedBeforeEventDelete", function(id) {
+ var ev = this.getEvent(id);
+ if (id.toString().indexOf("#") != -1 || (ev.event_pid && ev.event_pid != "0" && ev.rec_type && ev.rec_type != 'none')) {
+ id = id.split("#");
+ var nid = this.uid();
+ var tid = (id[1]) ? id[1] : (ev._pid_time / 1000);
+
+ var nev = this._copy_event(ev);
+ nev.id = nid;
+ nev.event_pid = ev.event_pid || id[0];
+ var timestamp = tid;
+ nev.event_length = timestamp;
+ nev.rec_type = nev.rec_pattern = "none";
+ this.addEvent(nev);
+
+ this._add_rec_marker(nev, timestamp * 1000);
+ } else {
+ if (ev.rec_type && this._lightbox_id)
+ this._roll_back_dates(ev);
+ var sub = this._get_rec_markers(id);
+ for (var i in sub) {
+ if (sub.hasOwnProperty(i)) {
+ id = sub[i].id;
+ if (this.getEvent(id))
+ this.deleteEvent(id, true);
+ }
+ }
+ }
+ return true;
+});
+
+scheduler.attachEvent("onEventChanged", function(id) {
+ if (this._loading) return true;
+
+ var ev = this.getEvent(id);
+
+ if (id.toString().indexOf("#") != -1) {
+ var id = id.split("#");
+ var nid = this.uid();
+ this._not_render = true;
+
+ var nev = this._copy_event(ev);
+ nev.id = nid;
+ nev.event_pid = id[0];
+ var timestamp = id[1];
+ nev.event_length = timestamp;
+ nev.rec_type = nev.rec_pattern = "";
+
+ this._add_rec_marker(nev, timestamp * 1000);
+ this.addEvent(nev);
+
+ this._not_render = false;
+
+ } else {
+ if (ev.rec_type && this._lightbox_id)
+ this._roll_back_dates(ev);
+ var sub = this._get_rec_markers(id);
+ for (var i in sub) {
+ if (sub.hasOwnProperty(i)) {
+ delete this._rec_markers[sub[i].id];
+ this.deleteEvent(sub[i].id, true);
+ }
+ }
+ delete this._rec_markers_pull[id];
+
+ // it's possible that after editing event is no longer exists, in such case we need to remove _select_id flag
+ var isEventFound = false;
+ for (var k = 0; k < this._rendered.length; k++) {
+ if (this._rendered[k].getAttribute('event_id') == id)
+ isEventFound = true;
+ }
+ if (!isEventFound)
+ this._select_id = null;
+ }
+ return true;
+});
+scheduler.attachEvent("onEventAdded", function(id) {
+ if (!this._loading) {
+ var ev = this.getEvent(id);
+ if (ev.rec_type && !ev.event_length)
+ this._roll_back_dates(ev);
+ }
+ return true;
+});
+scheduler.attachEvent("onEventSave", function(id, data, is_new_event) {
+ var ev = this.getEvent(id);
+ if (!ev.rec_type && data.rec_type && (id + '').indexOf('#') == -1)
+ this._select_id = null;
+ return true;
+});
+scheduler.attachEvent("onEventCreated", function(id) {
+ var ev = this.getEvent(id);
+ if (!ev.rec_type)
+ ev.rec_type = ev.rec_pattern = ev.event_length = ev.event_pid = "";
+ return true;
+});
+scheduler.attachEvent("onEventCancel", function(id) {
+ var ev = this.getEvent(id);
+ if (ev.rec_type) {
+ this._roll_back_dates(ev);
+ // a bit expensive, but we need to be sure that event re-rendered, because view can be corrupted by resize , during edit process
+ this.render_view_data();
+ }
+});
+scheduler._roll_back_dates = function(ev) {
+ ev.event_length = (ev.end_date.valueOf() - ev.start_date.valueOf()) / 1000;
+ ev.end_date = ev._end_date;
+ if (ev._start_date) {
+ ev.start_date.setMonth(0);
+ ev.start_date.setDate(ev._start_date.getDate());
+ ev.start_date.setMonth(ev._start_date.getMonth());
+ ev.start_date.setFullYear(ev._start_date.getFullYear());
+
+ }
+};
+
+scheduler._validId = function(id) {
+ return id.toString().indexOf("#") == -1;
+};
+
+scheduler.showLightbox_rec = scheduler.showLightbox;
+scheduler.showLightbox = function(id) {
+ var locale = this.locale;
+ var c = scheduler.config.lightbox_recurring;
+ var ev = this.getEvent(id);
+ var pid = ev.event_pid;
+ var isVirtual = (id.toString().indexOf("#") != -1);
+ if (isVirtual)
+ pid = id.split("#")[0];
+
+ // show series
+ var showSeries = function(id) {
+ var event = scheduler.getEvent(id);
+ event._end_date = event.end_date;
+ event.end_date = new Date(event.start_date.valueOf() + event.event_length * 1000);
+ return scheduler.showLightbox_rec(id); // editing series
+ };
+
+ if ( (pid || pid == 0) && ev.rec_type) {
+ // direct API call on series id
+ return showSeries(id);
+ }
+ if ( !pid || pid == 0 || ( (!locale.labels.confirm_recurring || c == 'instance') || (c == 'series' && !isVirtual)) ) {
+ // editing instance or non recurring event
+ return this.showLightbox_rec(id);
+ }
+ if (c == 'ask') {
+ var that = this;
+ dhtmlx.modalbox({
+ text: locale.labels.confirm_recurring,
+ title: locale.labels.title_confirm_recurring,
+ width: "500px",
+ position: "middle",
+ buttons:[locale.labels.button_edit_series, locale.labels.button_edit_occurrence, locale.labels.icon_cancel],
+ callback: function(index) {
+ switch(+index) {
+ case 0:
+ return showSeries(pid);
+ case 1:
+ return that.showLightbox_rec(id);
+ case 2:
+ return;
+ }
+ }
+ });
+ } else {
+ showSeries(pid);
+ }
+};
+
+
+scheduler.get_visible_events_rec = scheduler.get_visible_events;
+scheduler.get_visible_events = function(only_timed) {
+ for (var i = 0; i < this._rec_temp.length; i++)
+ delete this._events[this._rec_temp[i].id];
+ this._rec_temp = [];
+
+ var stack = this.get_visible_events_rec(only_timed);
+ var out = [];
+ for (var i = 0; i < stack.length; i++) {
+ if (stack[i].rec_type) {
+ //deleted element of serie
+ if (stack[i].rec_pattern != "none")
+ this.repeat_date(stack[i], out);
+ }
+ else out.push(stack[i]);
+ }
+ return out;
+};
+
+
+(function() {
+ var old = scheduler.isOneDayEvent;
+ scheduler.isOneDayEvent = function(ev) {
+ if (ev.rec_type) return true;
+ return old.call(this, ev);
+ };
+ var old_update_event = scheduler.updateEvent;
+ scheduler.updateEvent = function(id) {
+ var ev = scheduler.getEvent(id);
+ if(ev.rec_type){
+ //rec_type can be changed without the lightbox,
+ // make sure rec_pattern updated as well
+ ev.rec_pattern = (ev.rec_type || "").split("#")[0];
+ }
+ if (ev && ev.rec_type && id.toString().indexOf('#') === -1) {
+ scheduler.update_view();
+ } else {
+ old_update_event.call(this, id);
+ }
+ };
+})();
+
+scheduler.transponse_size = {
+ day:1, week:7, month:1, year:12
+};
+scheduler.date.day_week = function(sd, day, week) {
+ sd.setDate(1);
+ week = (week - 1) * 7;
+ var cday = sd.getDay();
+ var nday = day * 1 + week - cday + 1;
+ sd.setDate(nday <= week ? (nday + 7) : nday);
+};
+scheduler.transpose_day_week = function(sd, list, cor, size, cor2) {
+ var cday = (sd.getDay() || (scheduler.config.start_on_monday ? 7 : 0)) - cor;
+ for (var i = 0; i < list.length; i++) {
+ if (list[i] > cday)
+ return sd.setDate(sd.getDate() + list[i] * 1 - cday - (size ? cor : cor2));
+ }
+ this.transpose_day_week(sd, list, cor + size, null, cor);
+};
+scheduler.transpose_type = function(type) {
+ var f = "transpose_" + type;
+ if (!this.date[f]) {
+ var str = type.split("_");
+ var day = 60 * 60 * 24 * 1000;
+ var gf = "add_" + type;
+ var step = this.transponse_size[str[0]] * str[1];
+
+ if (str[0] == "day" || str[0] == "week") {
+ var days = null;
+ if (str[4]) {
+ days = str[4].split(",");
+ if (scheduler.config.start_on_monday) {
+ for (var i = 0; i < days.length; i++)
+ days[i] = (days[i] * 1) || 7;
+ days.sort();
+ }
+ }
+
+ this.date[f] = function(nd, td) {
+ var delta = Math.floor((td.valueOf() - nd.valueOf()) / (day * step));
+ if (delta > 0)
+ nd.setDate(nd.getDate() + delta * step);
+ if (days)
+ scheduler.transpose_day_week(nd, days, 1, step);
+ };
+ this.date[gf] = function(sd, inc) {
+ var nd = new Date(sd.valueOf());
+ if (days) {
+ for (var count = 0; count < inc; count++)
+ scheduler.transpose_day_week(nd, days, 0, step);
+ } else
+ nd.setDate(nd.getDate() + inc * step);
+
+ return nd;
+ };
+ }
+ else if (str[0] == "month" || str[0] == "year") {
+ this.date[f] = function(nd, td) {
+ var delta = Math.ceil(((td.getFullYear() * 12 + td.getMonth() * 1) - (nd.getFullYear() * 12 + nd.getMonth() * 1)) / (step));
+ if (delta >= 0)
+ nd.setMonth(nd.getMonth() + delta * step);
+ if (str[3])
+ scheduler.date.day_week(nd, str[2], str[3]);
+ };
+ this.date[gf] = function(sd, inc) {
+ var nd = new Date(sd.valueOf());
+ nd.setMonth(nd.getMonth() + inc * step);
+ if (str[3])
+ scheduler.date.day_week(nd, str[2], str[3]);
+ return nd;
+ };
+ }
+ }
+};
+scheduler.repeat_date = function(ev, stack, non_render, from, to) {
+
+ from = from || this._min_date;
+ to = to || this._max_date;
+
+ var td = new Date(ev.start_date.valueOf());
+
+ if (!ev.rec_pattern && ev.rec_type)
+ ev.rec_pattern = ev.rec_type.split("#")[0];
+
+ this.transpose_type(ev.rec_pattern);
+ scheduler.date["transpose_" + ev.rec_pattern](td, from);
+ while (td < ev.start_date || scheduler._fix_daylight_saving_date(td,from,ev,td,new Date(td.valueOf() + ev.event_length * 1000)).valueOf() <= from.valueOf() || td.valueOf() + ev.event_length * 1000 <= from.valueOf())
+ td = this.date.add(td, 1, ev.rec_pattern);
+ while (td < to && td < ev.end_date) {
+ var timestamp = (scheduler.config.occurrence_timestamp_in_utc) ? Date.UTC(td.getFullYear(), td.getMonth(), td.getDate(), td.getHours(), td.getMinutes(), td.getSeconds()) : td.valueOf();
+ var ch = this._get_rec_marker(timestamp, ev.id);
+ if (!ch) { // unmodified element of series
+ var ted = new Date(td.valueOf() + ev.event_length * 1000);
+ var copy = this._copy_event(ev);
+ //copy._timed = ev._timed;
+ copy.text = ev.text;
+ copy.start_date = td;
+ copy.event_pid = ev.id;
+ copy.id = ev.id + "#" + Math.ceil(timestamp / 1000);
+ copy.end_date = ted;
+
+ copy.end_date = scheduler._fix_daylight_saving_date(copy.start_date, copy.end_date, ev, td, copy.end_date);
+
+ copy._timed = this.isOneDayEvent(copy);
+
+ if (!copy._timed && !this._table_view && !this.config.multi_day) return;
+ stack.push(copy);
+
+ if (!non_render) {
+ this._events[copy.id] = copy;
+ this._rec_temp.push(copy);
+ }
+
+ } else
+ if (non_render) stack.push(ch);
+
+ td = this.date.add(td, 1, ev.rec_pattern);
+ }
+};
+scheduler._fix_daylight_saving_date = function(start_date, end_date, ev, counter, default_date) {
+ var shift = start_date.getTimezoneOffset() - end_date.getTimezoneOffset();
+ if (shift) {
+ if (shift > 0) {
+ // e.g. 24h -> 23h
+ return new Date(counter.valueOf() + ev.event_length * 1000 - shift * 60 * 1000);
+ }
+ else {
+ // e.g. 24h -> 25h
+ return new Date(end_date.valueOf() - shift * 60 * 1000);
+ }
+ }
+ return new Date(default_date.valueOf());
+};
+scheduler.getRecDates = function(id, max) {
+ var ev = typeof id == "object" ? id : scheduler.getEvent(id);
+ var count = 0;
+ var result = [];
+ max = max || 100;
+
+ var td = new Date(ev.start_date.valueOf());
+ var from = new Date(td.valueOf());
+
+ if (!ev.rec_type) {
+ return [
+ { start_date: ev.start_date, end_date: ev.end_date }
+ ];
+ }
+ if (ev.rec_type == "none") {
+ return [];
+ }
+ this.transpose_type(ev.rec_pattern);
+ scheduler.date["transpose_" + ev.rec_pattern](td, from);
+
+ while (td < ev.start_date || (td.valueOf() + ev.event_length * 1000) <= from.valueOf())
+ td = this.date.add(td, 1, ev.rec_pattern);
+ while (td < ev.end_date) {
+ var ch = this._get_rec_marker(td.valueOf(), ev.id);
+ var res = true;
+ if (!ch) { // unmodified element of series
+ var sed = new Date(td);
+ var ted = new Date(td.valueOf() + ev.event_length * 1000);
+
+ ted = scheduler._fix_daylight_saving_date(sed, ted, ev, td, ted);
+
+ result.push({start_date:sed, end_date:ted});
+ } else {
+ (ch.rec_type == "none") ?
+ (res = false) :
+ result.push({ start_date: ch.start_date, end_date: ch.end_date });
+ }
+ td = this.date.add(td, 1, ev.rec_pattern);
+ if (res) {
+ count++;
+ if (count == max)
+ break;
+ }
+ }
+ return result;
+};
+scheduler.getEvents = function(from, to) {
+ var result = [];
+ for (var a in this._events) {
+ var ev = this._events[a];
+ if (ev && ev.start_date < to && ev.end_date > from) {
+ if (ev.rec_pattern) {
+ if (ev.rec_pattern == "none") continue;
+ var sev = [];
+ this.repeat_date(ev, sev, true, from, to);
+ for (var i = 0; i < sev.length; i++) {
+ // if event is in rec_markers then it will be checked by himself, here need to skip it
+ if (!sev[i].rec_pattern && sev[i].start_date < to && sev[i].end_date > from && !this._rec_markers[sev[i].id]) {
+ result.push(sev[i]);
+ }
+ }
+ } else if (ev.id.toString().indexOf("#") == -1) { // if it's virtual event we can skip it
+ result.push(ev);
+ }
+ }
+ }
+ return result;
+};
+
+scheduler.config.repeat_date = "%m.%d.%Y";
+scheduler.config.lightbox.sections = [
+ {name:"description", height:130, map_to:"text", type:"textarea" , focus:true},
+ {name:"recurring", type:"recurring", map_to:"rec_type", button:"recurring"},
+ {name:"time", height:72, type:"time", map_to:"auto"}
+];
+
+
+//drop secondary attributes
+scheduler._copy_dummy = function(ev) {
+ var start_date = new Date(this.start_date);
+ var end_date = new Date(this.end_date);
+ this.start_date = start_date;
+ this.end_date = end_date;
+ this.event_length = this.event_pid = this.rec_pattern = this.rec_type = null;
+};
+
+scheduler.config.include_end_by = false;
+scheduler.config.lightbox_recurring = 'ask'; // series, instance
+
+scheduler.attachEvent("onClearAll", function(){
+ scheduler._rec_markers = {}; //clear recurring events data
+ scheduler._rec_markers_pull = {};
+ scheduler._rec_temp = [];
+});
+scheduler.__recurring_template='<div class="dhx_form_repeat"> <form> <div class="dhx_repeat_left"> <label><input class="dhx_repeat_radio" type="radio" name="repeat" value="day" />Daily</label><br /> <label><input class="dhx_repeat_radio" type="radio" name="repeat" value="week"/>Weekly</label><br /> <label><input class="dhx_repeat_radio" type="radio" name="repeat" value="month" checked />Monthly</label><br /> <label><input class="dhx_repeat_radio" type="radio" name="repeat" value="year" />Yearly</label> </div> <div class="dhx_repeat_divider"></div> <div class="dhx_repeat_center"> <div style="display:none;" id="dhx_repeat_day"> <label><input class="dhx_repeat_radio" type="radio" name="day_type" value="d"/>Every</label><input class="dhx_repeat_text" type="text" name="day_count" value="1" />day<br /> <label><input class="dhx_repeat_radio" type="radio" name="day_type" checked value="w"/>Every workday</label> </div> <div style="display:none;" id="dhx_repeat_week"> Repeat every<input class="dhx_repeat_text" type="text" name="week_count" value="1" />week next days:<br /> <table class="dhx_repeat_days"> <tr> <td> <label><input class="dhx_repeat_checkbox" type="checkbox" name="week_day" value="1" />Monday</label><br /> <label><input class="dhx_repeat_checkbox" type="checkbox" name="week_day" value="4" />Thursday</label> </td> <td> <label><input class="dhx_repeat_checkbox" type="checkbox" name="week_day" value="2" />Tuesday</label><br /> <label><input class="dhx_repeat_checkbox" type="checkbox" name="week_day" value="5" />Friday</label> </td> <td> <label><input class="dhx_repeat_checkbox" type="checkbox" name="week_day" value="3" />Wednesday</label><br /> <label><input class="dhx_repeat_checkbox" type="checkbox" name="week_day" value="6" />Saturday</label> </td> <td> <label><input class="dhx_repeat_checkbox" type="checkbox" name="week_day" value="0" />Sunday</label><br /><br /> </td> </tr> </table> </div> <div id="dhx_repeat_month"> <label><input class="dhx_repeat_radio" type="radio" name="month_type" value="d"/>Repeat</label><input class="dhx_repeat_text" type="text" name="month_day" value="1" />day every<input class="dhx_repeat_text" type="text" name="month_count" value="1" />month<br /> <label><input class="dhx_repeat_radio" type="radio" name="month_type" checked value="w"/>On</label><input class="dhx_repeat_text" type="text" name="month_week2" value="1" /><select name="month_day2"><option value="1" selected >Monday<option value="2">Tuesday<option value="3">Wednesday<option value="4">Thursday<option value="5">Friday<option value="6">Saturday<option value="0">Sunday</select>every<input class="dhx_repeat_text" type="text" name="month_count2" value="1" />month<br /> </div> <div style="display:none;" id="dhx_repeat_year"> <label><input class="dhx_repeat_radio" type="radio" name="year_type" value="d"/>Every</label><input class="dhx_repeat_text" type="text" name="year_day" value="1" />day<select name="year_month"><option value="0" selected >January<option value="1">February<option value="2">March<option value="3">April<option value="4">May<option value="5">June<option value="6">July<option value="7">August<option value="8">September<option value="9">October<option value="10">November<option value="11">December</select>month<br /> <label><input class="dhx_repeat_radio" type="radio" name="year_type" checked value="w"/>On</label><input class="dhx_repeat_text" type="text" name="year_week2" value="1" /><select name="year_day2"><option value="1" selected >Monday<option value="2">Tuesday<option value="3">Wednesday<option value="4">Thursday<option value="5">Friday<option value="6">Saturday<option value="7">Sunday</select>of<select name="year_month2"><option value="0" selected >January<option value="1">February<option value="2">March<option value="3">April<option value="4">May<option value="5">June<option value="6">July<option value="7">August<option value="8">September<option value="9">October<option value="10">November<option value="11">December</select><br /> </div> </div> <div class="dhx_repeat_divider"></div> <div class="dhx_repeat_right"> <label><input class="dhx_repeat_radio" type="radio" name="end" checked/>No end date</label><br /> <label><input class="dhx_repeat_radio" type="radio" name="end" />After</label><input class="dhx_repeat_text" type="text" name="occurences_count" value="1" />occurrences<br /> <label><input class="dhx_repeat_radio" type="radio" name="end" />End by</label><input class="dhx_repeat_date" type="text" name="date_of_end" value="'+scheduler.config.repeat_date_of_end+'" /><br /> </div> </form> </div> <div style="clear:both"> </div>';
+
diff --git a/sources/ext/dhtmlxscheduler_serialize.js b/sources/ext/dhtmlxscheduler_serialize.js
new file mode 100644
index 0000000..5a89e9d
--- /dev/null
+++ b/sources/ext/dhtmlxscheduler_serialize.js
@@ -0,0 +1,77 @@
+/*
+This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
+to use it in non-GPL project. Please contact sales@dhtmlx.com for details
+*/
+//redefine this method, if you want to provide a custom set of attributes for serialization
+scheduler.data_attributes=function(){
+ var attrs = [];
+ var format = scheduler.templates.xml_format;
+ for (var a in this._events){
+ var ev = this._events[a];
+ for (var name in ev)
+ if (name.substr(0,1) !="_")
+ attrs.push([name,((name == "start_date" || name == "end_date")?format:null)]);
+ break;
+ }
+ return attrs;
+}
+
+scheduler.toXML = function(header){
+ var xml = [];
+ var attrs = this.data_attributes();
+
+
+ for (var a in this._events){
+ var ev = this._events[a];
+ if (ev.id.toString().indexOf("#")!=-1) continue;
+ xml.push("<event>");
+ for (var i=0; i < attrs.length; i++)
+ xml.push("<"+attrs[i][0]+"><![CDATA["+(attrs[i][1]?attrs[i][1](ev[attrs[i][0]]):ev[attrs[i][0]])+"]]></"+attrs[i][0]+">");
+
+ xml.push("</event>");
+ }
+ return (header||"")+"<data>"+xml.join("\n")+"</data>";
+};
+
+scheduler.toJSON = function(){
+ var json = [];
+ var attrs = this.data_attributes();
+ for (var a in this._events){
+ var ev = this._events[a];
+ if (ev.id.toString().indexOf("#")!=-1) continue;
+ var ev = this._events[a];
+ var line =[];
+ for (var i=0; i < attrs.length; i++)
+ line.push(' "'+attrs[i][0]+'": "'+((attrs[i][1]?attrs[i][1](ev[attrs[i][0]]):ev[attrs[i][0]])||"").toString().replace(/\n/g,"")+'" ');
+ json.push("{"+line.join(",")+"}");
+ }
+ return "["+json.join(",\n")+"]";
+};
+
+
+scheduler.toICal = function(header){
+ var start = "BEGIN:VCALENDAR\nVERSION:2.0\nPRODID:-//dhtmlXScheduler//NONSGML v2.2//EN\nDESCRIPTION:";
+ var end = "END:VCALENDAR";
+ var format = scheduler.date.date_to_str("%Y%m%dT%H%i%s");
+ var full_day_format = scheduler.date.date_to_str("%Y%m%d");
+
+ var ical = [];
+ for (var a in this._events){
+ var ev = this._events[a];
+ if (ev.id.toString().indexOf("#")!=-1) continue;
+
+
+ ical.push("BEGIN:VEVENT");
+ if (!ev._timed || (!ev.start_date.getHours() && !ev.start_date.getMinutes()))
+ ical.push("DTSTART:"+full_day_format(ev.start_date));
+ else
+ ical.push("DTSTART:"+format(ev.start_date));
+ if (!ev._timed || (!ev.end_date.getHours() && !ev.end_date.getMinutes()))
+ ical.push("DTEND:"+full_day_format(ev.end_date));
+ else
+ ical.push("DTEND:"+format(ev.end_date));
+ ical.push("SUMMARY:"+ev.text);
+ ical.push("END:VEVENT");
+ }
+ return start+(header||"")+"\n"+ical.join("\n")+"\n"+end;
+}; \ No newline at end of file
diff --git a/sources/ext/dhtmlxscheduler_timeline.js b/sources/ext/dhtmlxscheduler_timeline.js
new file mode 100644
index 0000000..f27868a
--- /dev/null
+++ b/sources/ext/dhtmlxscheduler_timeline.js
@@ -0,0 +1,1195 @@
+/*
+This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
+to use it in non-GPL project. Please contact sales@dhtmlx.com for details
+*/
+(scheduler._temp_matrix_scope = function(){
+
+
+
+scheduler.matrix = {};
+scheduler._merge=function(a,b){
+ for (var c in b)
+ if (typeof a[c] == "undefined")
+ a[c]=b[c];
+};
+scheduler.createTimelineView=function(obj){
+ scheduler._skin_init();
+
+ scheduler._merge(obj,{
+ section_autoheight: true,
+ name:"matrix",
+ x:"time",
+ y:"time",
+ x_step:1,
+ x_unit:"hour",
+ y_unit:"day",
+ y_step:1,
+ x_start:0,
+ x_size:24,
+ y_start:0,
+ y_size: 7,
+ render:"cell",
+ dx:200,
+ dy:50,
+ event_dy: scheduler.xy.bar_height-5,
+ event_min_dy: scheduler.xy.bar_height-5,
+ resize_events: true,
+ fit_events: true,
+ show_unassigned: false,
+ second_scale: false,
+ round_position: false,
+ _logic: function(render_name, y_unit, timeline) {
+ var res = {};
+ if(scheduler.checkEvent("onBeforeSectionRender")) {
+ res = scheduler.callEvent("onBeforeSectionRender", [render_name, y_unit, timeline]);
+ }
+ return res;
+ }
+ });
+ obj._original_x_start = obj.x_start;
+
+ //first and last hours are applied only to day based timeline
+ if (obj.x_unit != "day") obj.first_hour = obj.last_hour = 0;
+ //correction for first and last hour
+ obj._start_correction = obj.first_hour?obj.first_hour*60*60*1000:0;
+ obj._end_correction = obj.last_hour?(24-obj.last_hour)*60*60*1000:0;
+
+ if (scheduler.checkEvent("onTimelineCreated")) {
+ scheduler.callEvent("onTimelineCreated", [obj]);
+ }
+
+ var old = scheduler.render_data;
+ scheduler.render_data = function(evs, mode) {
+ if (this._mode == obj.name) {
+ //repaint single event, precision is not necessary
+ if (mode && !obj.show_unassigned && obj.render != "cell") {
+ for (var i = 0; i < evs.length; i++) {
+ this.clear_event(evs[i]);
+ this.render_timeline_event.call(this.matrix[this._mode], evs[i], true);
+ }
+ } else {
+ scheduler._renderMatrix.call(obj, true, true);
+ }
+ } else
+ return old.apply(this, arguments);
+ };
+
+ scheduler.matrix[obj.name]=obj;
+ scheduler.templates[obj.name+"_cell_value"] = function(ar){ return ar?ar.length:""; };
+ scheduler.templates[obj.name+"_cell_class"] = function(arr){ return ""; };
+ scheduler.templates[obj.name+"_scalex_class"] = function(date){ return ""; };
+ scheduler.templates[obj.name+"_second_scalex_class"] = function(date){ return ""; };
+
+ scheduler.templates[obj.name+"_scaley_class"] = function(section_id, section_label, section_options){ return ""; };
+ scheduler.templates[obj.name+"_scale_label"] = function(section_id, section_label, section_options){ return section_label; };
+
+ scheduler.templates[obj.name+"_tooltip"] = function(a,b,e){ return e.text; };
+ scheduler.templates[obj.name+"_date"] = function(datea, dateb){
+ if ( (datea.getDay()==dateb.getDay() && dateb-datea < (24*60*60*1000))
+ || +datea == +scheduler.date.date_part(new Date(dateb))
+ || (+scheduler.date.add(datea, 1, "day") == +dateb && dateb.getHours() == 0 && dateb.getMinutes() == 0) )
+ return scheduler.templates.day_date(datea);
+ if ( (datea.getDay() != dateb.getDay() && dateb-datea < (24*60*60*1000)) ) {
+ return scheduler.templates.day_date(datea)+" &ndash; "+scheduler.templates.day_date(dateb);
+ }
+ return scheduler.templates.week_date(datea, dateb);
+ };
+
+ scheduler.templates[obj.name+"_scale_date"] = scheduler.date.date_to_str(obj.x_date||scheduler.config.hour_date);
+ scheduler.templates[obj.name+"_second_scale_date"] = scheduler.date.date_to_str((obj.second_scale && obj.second_scale.x_date)?obj.second_scale.x_date:scheduler.config.hour_date);
+
+ scheduler.date["add_" + obj.name] = function(date, step, c) {
+ var resulting_date = scheduler.date.add(date, (obj.x_length || obj.x_size) * step * obj.x_step, obj.x_unit);
+ if (obj.x_unit == "minute" || obj.x_unit == "hour") {
+ var size = (obj.x_length || obj.x_size);
+ if ( +scheduler.date.date_part(new Date(date)) == +scheduler.date.date_part(new Date(resulting_date )) ) {
+ obj.x_start += step*size;
+ } else {
+ var converted_step = (obj.x_unit == "hour") ? obj.x_step*60 : obj.x_step;
+ // total steps starting from 0
+ var total_steps = ( (24 * 60) / (size * converted_step) ) - 1;
+ var steps_offset = Math.round(total_steps * size);
+
+ if (step > 0) {
+ obj.x_start = obj.x_start - steps_offset;
+ } else {
+ obj.x_start = steps_offset + obj.x_start;
+ }
+ }
+ }
+ return resulting_date;
+ };
+
+ scheduler.attachEvent("onBeforeTodayDisplayed", function() {
+ obj.x_start = obj._original_x_start;
+ return true;
+ });
+ scheduler.date[obj.name+"_start"] = function(date) {
+ var func = scheduler.date[obj.x_unit+"_start"] || scheduler.date.day_start;
+ var start_date = func.call(scheduler.date, date);
+ start_date = scheduler.date.add(start_date, obj.x_step*obj.x_start, obj.x_unit);
+ return start_date;
+ };
+
+ scheduler.attachEvent("onSchedulerResize",function(){
+ if (this._mode == obj.name){
+ scheduler._renderMatrix.call(obj, true, true);
+ return false;
+ }
+ return true;
+ });
+
+ scheduler.attachEvent("onOptionsLoad",function(){
+ obj.order = {};
+ scheduler.callEvent('onOptionsLoadStart', []);
+ for(var i=0; i<obj.y_unit.length;i++)
+ obj.order[obj.y_unit[i].key]=i;
+ scheduler.callEvent('onOptionsLoadFinal', []);
+ if (scheduler._date && obj.name == scheduler._mode)
+ scheduler.setCurrentView(scheduler._date, scheduler._mode);
+ });
+ scheduler.callEvent("onOptionsLoad",[obj]);
+
+ //init custom wrappers
+ scheduler[obj.name+"_view"]=function(){
+ scheduler._renderMatrix.apply(obj, arguments);
+ };
+
+ //enable drag for non-cell modes
+ var temp_date = new Date();
+ var step_diff = (scheduler.date.add(temp_date, obj.x_step, obj.x_unit).valueOf() - temp_date.valueOf()); // "minute" + step in ms
+ scheduler["mouse_"+obj.name]=function(pos){ //mouse_coord handler
+ //get event object
+ var ev = this._drag_event;
+ if (this._drag_id){
+ ev = this.getEvent(this._drag_id);
+ this._drag_event._dhx_changed = true;
+ }
+
+ pos.x-=obj.dx;
+ var summ = 0, xind = 0, yind = 0;
+ for (xind; xind <= this._cols.length-1; xind++) {
+
+ var column_width = this._cols[xind];
+ summ += column_width;
+ if (summ>pos.x){ //index of section
+ var ratio = (pos.x-(summ-column_width))/column_width;
+ ratio = (ratio < 0) ? 0 : ratio;
+ break;
+ }
+ }
+ //border cases
+ if (xind == 0 && this._ignores[0]) {
+ xind = 1; ratio = 0;
+ while (this._ignores[xind]) xind++;
+ } else if ( xind == this._cols.length && this._ignores[xind-1]) {
+ xind = this._cols.length-1; ratio = 0;
+ while (this._ignores[xind]) xind--;
+ xind++;
+ }
+
+ summ = 0;
+ for (yind; yind < this._colsS.heights.length; yind++) {
+ summ += this._colsS.heights[yind];
+ if (summ > pos.y)
+ break;
+ }
+
+ pos.fields={};
+ if(!obj.y_unit[yind]) {
+ yind=obj.y_unit.length-1;
+ }
+
+ if (yind >= 0 && obj.y_unit[yind]) {
+ pos.section = pos.fields[obj.y_property] = obj.y_unit[yind].key;
+ if (ev) {
+ if(ev[obj.y_property] != pos.section){
+ var line_height = scheduler._get_timeline_event_height(ev, obj);
+ ev._sorder = scheduler._get_dnd_order(ev._sorder, line_height, obj._section_height[pos.section]);
+ }
+ ev[obj.y_property] = pos.section;
+ }
+ }
+
+ pos.x = 0;
+ pos.force_redraw = true;
+ pos.custom = true;
+
+ var end_date;
+ // if our event is at the end of the view
+ if(xind >= obj._trace_x.length) {
+ end_date = scheduler.date.add(obj._trace_x[obj._trace_x.length-1], obj.x_step, obj.x_unit);
+ if (obj._end_correction)
+ end_date = new Date(end_date-obj._end_correction);
+ } else {
+ var timestamp_diff = ratio * column_width * obj._step + obj._start_correction;
+ end_date = new Date(+obj._trace_x[xind]+timestamp_diff);
+ }
+
+ // as we can simply be calling _locate_cell_timeline
+ if (this._drag_mode == "move" && this._drag_id && this._drag_event) {
+ var ev = this.getEvent(this._drag_id);
+ var drag_event = this._drag_event;
+
+ pos._ignores = (this._ignores_detected || obj._start_correction || obj._end_correction);
+ if (!drag_event._move_delta) {
+ drag_event._move_delta = (ev.start_date-end_date)/60000;
+ if (this.config.preserve_length && pos._ignores){
+ drag_event._move_delta = this._get_real_event_length(ev.start_date,end_date, obj);
+ drag_event._event_length = this._get_real_event_length(ev.start_date,ev.end_date, obj);
+ }
+ }
+
+
+
+
+ //preserve visible size of event
+ if (this.config.preserve_length && pos._ignores){
+ var ev_length = drag_event._event_length;//this._get_real_event_length(ev.start_date, ev.end_date, obj);
+ var current_back_shift = this._get_fictional_event_length(end_date, drag_event._move_delta, obj, true);
+ end_date = new Date(end_date - current_back_shift);
+ } else {
+ // converting basically to start_date
+ end_date = scheduler.date.add(end_date, drag_event._move_delta, "minute");
+ }
+ }
+
+ if (this._drag_mode == "resize" && ev){
+ var pos_check = !!(Math.abs(ev.start_date-end_date) < Math.abs(ev.end_date-end_date));
+ if (obj._start_correction || obj._end_correction){
+ var first_check = (!this._drag_event || this._drag_event._resize_from_start == undefined);
+ if (first_check || Math.abs(ev.end_date - ev.start_date) <= (1000*60*this.config.time_step))
+ this._drag_event._resize_from_start = pos.resize_from_start = pos_check;
+ else
+ pos.resize_from_start = this._drag_event._resize_from_start;
+ } else
+ pos.resize_from_start = pos_check;
+ }
+
+ if (obj.round_position) {
+ switch(this._drag_mode) {
+ case "move":
+ if (!this.config.preserve_length){
+ end_date = get_rounded_date.call(obj, end_date, false);
+ // to preserve original start and end dates
+ if(obj.x_unit == "day")//only make sense for whole-day cells
+ pos.custom = false;
+ }
+ break;
+ case "resize":
+ if(this._drag_event){
+ // will save and use resize position only once
+ if (this._drag_event._resize_from_start == null) {
+ this._drag_event._resize_from_start = pos.resize_from_start;
+ }
+ pos.resize_from_start = this._drag_event._resize_from_start;
+ end_date = get_rounded_date.call(obj, end_date, !this._drag_event._resize_from_start);
+ }
+ break;
+ }
+ }
+
+ pos.y = Math.round((end_date-this._min_date)/(1000*60*this.config.time_step));
+ pos.shift = this.config.time_step; //step_diff;
+
+ return pos;
+ }
+};
+
+scheduler._get_timeline_event_height = function(ev, config){
+ var section = ev[config.y_property]; // section id
+ var event_height = config.event_dy;
+ if (config.event_dy == "full") {
+ if (config.section_autoheight) {
+ event_height = config._section_height[section] - 6;
+ } else {
+ event_height = config.dy - 3;
+ }
+ }
+
+ if (config.resize_events) {
+ event_height = Math.max(Math.floor(event_height / ev._count), config.event_min_dy);
+ }
+ return event_height;
+};
+scheduler._get_timeline_event_y = function(order, event_height){
+ var sorder = order;
+ var y = 2+sorder*event_height+(sorder?(sorder*2):0); // original top + number_of_events * event_dy + default event top/bottom borders
+ if (scheduler.config.cascade_event_display) {
+ y =2+sorder*scheduler.config.cascade_event_margin+(sorder?(sorder*2):0);
+ }
+ return y;
+};
+
+scheduler.render_timeline_event = function(ev, attach){
+ var section = ev[this.y_property]; // section id
+ if (!section)
+ return ""; // as we may await html
+
+ var sorder = ev._sorder;
+
+ var x_start = _getX(ev, false, this);
+ var x_end = _getX(ev, true, this);
+
+ var event_height = scheduler._get_timeline_event_height(ev, this);
+
+ var hb = event_height - 2;// takes into account css sizes (border/padding)
+ if (!ev._inner && this.event_dy == "full") {
+ hb=(hb+2)*(ev._count-sorder)-2;
+ }
+
+ var y = scheduler._get_timeline_event_y(ev._sorder, event_height);
+
+ var section_height = event_height+y+2;
+ if(!this._events_height[section] || (this._events_height[section] < section_height)){
+ this._events_height[section] = section_height;
+ }
+
+ var cs = scheduler.templates.event_class(ev.start_date,ev.end_date,ev);
+ cs = "dhx_cal_event_line "+(cs||"");
+
+ var bg_color = (ev.color?("background:"+ev.color+";"):"");
+ var color = (ev.textColor?("color:"+ev.textColor+";"):"");
+ var text = scheduler.templates.event_bar_text(ev.start_date,ev.end_date,ev);
+
+ var html='<div event_id="'+ev.id+'" class="'+cs+'" style="'+bg_color+''+color+'position:absolute; top:'+y+'px; height: '+hb+'px; left:'+x_start+'px; width:'+Math.max(0,x_end-x_start)+'px;'+(ev._text_style||"")+'">';
+ if (scheduler.config.drag_resize && !scheduler.config.readonly) {
+ var dhx_event_resize = 'dhx_event_resize';
+ html += ("<div class='"+dhx_event_resize+" "+dhx_event_resize+"_start' style='height: "+hb+"px;'></div><div class='"+dhx_event_resize+" "+dhx_event_resize+"_end' style='height: "+hb+"px;'></div>");
+ }
+ html += (text+'</div>');
+
+ if (!attach)
+ return html;
+ else {
+ var d = document.createElement("DIV");
+ d.innerHTML = html;
+ var ind = this.order[section];
+ var parent = scheduler._els["dhx_cal_data"][0].firstChild.rows[ind].cells[1].firstChild;
+
+ scheduler._rendered.push(d.firstChild);
+ parent.appendChild(d.firstChild);
+ }
+};
+function trace_events(){
+ //minimize event set
+ var evs = scheduler.get_visible_events();
+ var matrix =[];
+ for (var i=0; i < this.y_unit.length; i++)
+ matrix[i]=[];
+
+ //next code defines row for undefined key
+ //most possible it is an artifact of incorrect configuration
+ if (!matrix[y])
+ matrix[y]=[];
+
+ for (var i=0; i < evs.length; i++) {
+ var y = this.order[evs[i][this.y_property]];
+ var x = 0;
+ while (this._trace_x[x+1] && evs[i].start_date>=this._trace_x[x+1]) x++;
+ while (this._trace_x[x] && evs[i].end_date>this._trace_x[x]) {
+ if (!matrix[y][x]) matrix[y][x]=[];
+ matrix[y][x].push(evs[i]);
+ x++;
+ }
+ }
+ return matrix;
+}
+// function used to get X (both start and end) coordinates for timeline bar view
+function _getX(ev, isEndPoint, config) {
+ var x = 0;
+ var step = config._step;
+ var round_position = config.round_position;
+
+ var column_offset = 0;
+ var date = (isEndPoint) ? ev.end_date : ev.start_date;
+
+ if(date.valueOf()>scheduler._max_date.valueOf())
+ date = scheduler._max_date;
+ var delta = date - scheduler._min_date_timeline;
+
+ if (delta > 0){
+ var index = scheduler._get_date_index(config, date);
+ if (scheduler._ignores[index])
+ round_position=true;
+
+ for (var i = 0; i < index; i++) {
+ x += scheduler._cols[i];
+ }
+
+ var column_date = scheduler.date.add(scheduler._min_date_timeline, scheduler.matrix[scheduler._mode].x_step*index, scheduler.matrix[scheduler._mode].x_unit);
+ if (!round_position) {
+ delta = date - column_date;
+ if (config.first_hour || config.last_hour){
+ delta = delta - config._start_correction;
+ if (delta < 0) delta = 0;
+ column_offset = Math.round(delta/step);
+ if (column_offset > scheduler._cols[index])
+ column_offset = scheduler._cols[index];
+ } else {
+ column_offset = Math.round(delta/step);
+ }
+ } else {
+ if (+date > +column_date && isEndPoint) {
+ column_offset = scheduler._cols[index];
+ }
+ }
+ }
+ if (isEndPoint) {
+ // special handling for "round" dates which match columns and usual ones
+ if (delta != 0 && !round_position) {
+ x += column_offset-12;
+ } else {
+ x += column_offset-14;
+ }
+ } else {
+ x += column_offset+1;
+ }
+ return x;
+}
+function get_rounded_date(date, isEndDate) {
+ var index = scheduler._get_date_index(this, date);
+ var rounded_date = this._trace_x[index];
+ if (isEndDate && (+date != +this._trace_x[index])) {
+ rounded_date = (this._trace_x[index+1]) ? this._trace_x[index+1] : scheduler.date.add(this._trace_x[index], this.x_step, this.x_unit);
+ }
+ return new Date(rounded_date);
+}
+function get_events_html(evs) {
+ var html = "";
+ if (evs && this.render != "cell"){
+ evs.sort(this.sort || function(a,b){
+ if(a.start_date.valueOf()==b.start_date.valueOf())
+ return a.id>b.id?1:-1;
+ return a.start_date>b.start_date?1:-1;
+ });
+ var stack=[];
+ var evs_length = evs.length;
+ // prepare events for render
+ for (var j=0; j<evs_length; j++){
+ var ev = evs[j];
+ ev._inner = false;
+
+ var ev_start_date = (this.round_position) ? get_rounded_date.apply(this, [ev.start_date, false]) : ev.start_date;
+ var ev_end_date = (this.round_position) ? get_rounded_date.apply(this, [ev.end_date, true]) : ev.end_date;
+
+ // cutting stack from the last -> first event side
+ while (stack.length) {
+ var stack_ev = stack[stack.length-1];
+ if (stack_ev.end_date.valueOf() <= ev_start_date.valueOf()) {
+ stack.splice(stack.length-1,1);
+ } else {
+ break;
+ }
+ }
+
+ // cutting stack from the first -> last event side
+ var sorderSet = false;
+ for(var p=0; p<stack.length; p++){
+ var t_ev = stack[p];
+ if(t_ev.end_date.valueOf() <= ev_start_date.valueOf()){
+ sorderSet = true;
+ ev._sorder=t_ev._sorder;
+ stack.splice(p,1);
+ ev._inner=true;
+ break;
+ }
+ }
+
+
+ if (stack.length)
+ stack[stack.length-1]._inner=true;
+
+
+ if (!sorderSet) {
+ if (stack.length) {
+ if (stack.length <= stack[stack.length - 1]._sorder) {
+ if (!stack[stack.length - 1]._sorder)
+ ev._sorder = 0;
+ else
+ for (var h = 0; h < stack.length; h++) {
+ var _is_sorder = false;
+ for (var t = 0; t < stack.length; t++) {
+ if (stack[t]._sorder == h) {
+ _is_sorder = true;
+ break;
+ }
+ }
+ if (!_is_sorder) {
+ ev._sorder = h;
+ break;
+ }
+ }
+ ev._inner = true;
+ }
+ else {
+ var _max_sorder = stack[0]._sorder;
+ for (var w = 1; w < stack.length; w++)
+ if (stack[w]._sorder > _max_sorder)
+ _max_sorder = stack[w]._sorder;
+ ev._sorder = _max_sorder + 1;
+ ev._inner = false;
+ }
+ }
+ else
+ ev._sorder = 0;
+ }
+
+ stack.push(ev);
+
+ if (stack.length>(stack.max_count||0)) {
+ stack.max_count=stack.length;
+ ev._count=stack.length;
+ }
+ else {
+ ev._count=(ev._count)?ev._count:1;
+ }
+ }
+ // fix _count for every event
+ for (var m=0; m < evs.length; m++) {
+ evs[m]._count = stack.max_count;
+ }
+ // render events
+ for (var v=0; v<evs_length; v++) {
+ html+=scheduler.render_timeline_event.call(this, evs[v], false);
+ }
+ }
+ return html;
+}
+
+
+function y_scale(d) {
+ var html = "<table style='table-layout:fixed;' cellspacing='0' cellpadding='0'>";
+ var evs=[];
+ if(scheduler._load_mode)
+ scheduler._load();
+ if (this.render == "cell")
+ evs = trace_events.call(this);
+ else {
+ var tevs = scheduler.get_visible_events();
+ var order = this.order;
+
+ for (var j = 0; j < tevs.length; j++) {
+ var tev = tevs[j];
+ var tev_section = tev[this.y_property];
+ var index = this.order[ tev_section ];
+
+ if (this.show_unassigned && !tev_section) {
+ for (var key in order) {
+ if (order.hasOwnProperty(key)) {
+ index = order[key];
+ if (!evs[index]) evs[index] = [];
+ var clone = scheduler._lame_copy({}, tev);
+ clone[this.y_property] = key;
+ evs[index].push(clone);
+ }
+ }
+ } else {
+ // required as we could have index of not displayed section or "undefined"
+ if (!evs[index]) evs[index] = [];
+ evs[index].push(tev);
+ }
+ }
+ }
+
+ var summ = 0;
+ for (var i=0; i < scheduler._cols.length; i++)
+ summ+=scheduler._cols[i];
+
+ var step = new Date();
+ var realcount = scheduler._cols.length-scheduler._ignores_detected;
+ step = ((scheduler.date.add(step, this.x_step*realcount, this.x_unit)-step)-(this._start_correction + this._end_correction)*realcount)/summ;
+ this._step = step;
+ this._summ = summ;
+
+ var heights = scheduler._colsS.heights=[];
+
+ this._events_height = {};
+ this._section_height = {};
+ for (var i=0; i<this.y_unit.length; i++){
+
+ var stats = this._logic(this.render, this.y_unit[i], this); // obj with custom style
+
+ scheduler._merge(stats, {
+ height: this.dy
+ });
+
+ //autosize height, if we have a free space
+ if(this.section_autoheight) {
+ if (this.y_unit.length * stats.height < d.offsetHeight) {
+ stats.height = Math.max(stats.height, Math.floor((d.offsetHeight - 1) / this.y_unit.length));
+ }
+ this._section_height[this.y_unit[i].key] = stats.height;
+ }
+
+ scheduler._merge(stats, {
+ //section 1
+ tr_className: "",
+ style_height: "height:"+stats.height+"px;",
+ style_width: "width:"+(this.dx-1)+"px;",
+ td_className: "dhx_matrix_scell"+((scheduler.templates[this.name+"_scaley_class"](this.y_unit[i].key, this.y_unit[i].label, this.y_unit[i]))?" "+scheduler.templates[this.name+"_scaley_class"](this.y_unit[i].key, this.y_unit[i].label, this.y_unit[i]):''),
+ td_content: scheduler.templates[this.name+'_scale_label'](this.y_unit[i].key, this.y_unit[i].label, this.y_unit[i]),
+ //section 2
+ summ_width: "width:"+summ+"px;",
+ //section 3
+ table_className: ''
+ });
+
+ // generating events html in a temporary file, calculating their height
+ var events_html = get_events_html.call(this, evs[i]);
+
+ if(this.fit_events){
+ var rendered_height = this._events_height[this.y_unit[i].key]||0;
+ stats.height = (rendered_height>stats.height)?rendered_height:stats.height;
+ stats.style_height = "height:"+stats.height+"px;";
+ this._section_height[this.y_unit[i].key] = stats.height;
+ }
+
+ // section 1
+ html+="<tr class='"+stats.tr_className+"' style='"+stats.style_height+"'><td class='"+stats.td_className+"' style='"+stats.style_width+" height:"+(stats.height-1)+"px;'>"+stats.td_content+"</td>";
+
+ if (this.render == "cell"){
+ for (var j=0; j < scheduler._cols.length; j++) {
+ if (scheduler._ignores[j])
+ html+="<td></td>";
+ else
+ html+="<td class='dhx_matrix_cell "+scheduler.templates[this.name+"_cell_class"](evs[i][j],this._trace_x[j],this.y_unit[i])+"' style='width:"+(scheduler._cols[j]-1)+"px'><div style='width:"+(scheduler._cols[j]-1)+"px'>"+scheduler.templates[this.name+"_cell_value"](evs[i][j])+"</div></td>";
+ }
+ } else {
+ //section 2
+ html+="<td><div style='"+stats.summ_width+" "+stats.style_height+" position:relative;' class='dhx_matrix_line'>";
+
+ // adding events
+ html += events_html;
+
+ //section 3
+ html+="<table class='"+stats.table_className+"' cellpadding='0' cellspacing='0' style='"+stats.summ_width+" "+stats.style_height+"' >";
+ for (var j=0; j < scheduler._cols.length; j++){
+ if (scheduler._ignores[j])
+ html+="<td></td>";
+ else
+ html+="<td class='dhx_matrix_cell "+scheduler.templates[this.name+"_cell_class"](evs[i],this._trace_x[j],this.y_unit[i])+"' style='width:"+(scheduler._cols[j]-1)+"px'><div style='width:"+(scheduler._cols[j]-1)+"px'></div></td>";
+ }
+ html+="</table>";
+ html+="</div></td>";
+ }
+ html+="</tr>";
+ }
+ html += "</table>";
+ this._matrix = evs;
+ //d.scrollTop = 0; //fix flickering in FF; disabled as it was impossible to create dnd event if scroll was used (window jumped to the top)
+ d.innerHTML = html;
+
+ scheduler._rendered = [];
+ var divs = scheduler._obj.getElementsByTagName("DIV");
+ for (var i=0; i < divs.length; i++)
+ if (divs[i].getAttribute("event_id"))
+ scheduler._rendered.push(divs[i]);
+
+ this._scales = {};
+ for (var i=0; i < d.firstChild.rows.length; i++) {
+ heights.push(d.firstChild.rows[i].offsetHeight);
+ var unit_key = this.y_unit[i].key;
+ var scale = this._scales[unit_key] = (scheduler._isRender('cell')) ? d.firstChild.rows[i] : d.firstChild.rows[i].childNodes[1].getElementsByTagName('div')[0];
+ scheduler.callEvent("onScaleAdd", [scale, unit_key]);
+ }
+}
+function x_scale(h){
+ var current_sh = scheduler.xy.scale_height;
+ var original_sh = this._header_resized||scheduler.xy.scale_height;
+ scheduler._cols=[]; //store for data section, each column width
+ scheduler._colsS={height:0}; // heights of the y sections
+ this._trace_x =[]; // list of dates per cells
+ var summ = scheduler._x - this.dx - scheduler.xy.scroll_width; //border delta, whole width
+ var left = [this.dx]; // left margins, initial left margin
+ var header = scheduler._els['dhx_cal_header'][0];
+ header.style.width = (left[0]+summ)+'px';
+
+ scheduler._min_date_timeline = scheduler._min_date;
+
+ var preserve = scheduler.config.preserve_scale_length;
+ var start = scheduler._min_date;
+ scheduler._process_ignores(start, this.x_size, this.x_unit, this.x_step, preserve);
+
+ var size = this.x_size + (preserve ? scheduler._ignores_detected : 0);
+ if (size != this.x_size)
+ scheduler._max_date = scheduler.date.add(scheduler._min_date, size*this.x_step, this.x_unit);
+
+ var realcount = size - scheduler._ignores_detected;
+ for (var k=0; k<size; k++){
+ // dates calculation
+ this._trace_x[k]=new Date(start);
+ start = scheduler.date.add(start, this.x_step, this.x_unit);
+
+ // position calculation
+ if (scheduler._ignores[k]){
+ scheduler._cols[k]=0;
+ realcount++;
+ } else {
+ scheduler._cols[k]=Math.floor(summ/(realcount-k));
+ }
+
+ summ -= scheduler._cols[k];
+ left[k+1] = left[k] + scheduler._cols[k];
+ }
+ h.innerHTML = "<div></div>";
+
+ if(this.second_scale){
+ // additional calculations
+ var mode = this.second_scale.x_unit;
+ var control_dates = [this._trace_x[0]]; // first control date
+ var second_cols = []; // each column width of the secondary row
+ var second_left = [this.dx, this.dx]; // left margins of the secondary row
+ var t_index = 0; // temp index
+ for (var l = 0; l < this._trace_x.length; l++) {
+ var date = this._trace_x[l];
+ var res = is_new_interval(mode, date, control_dates[t_index]);
+
+ if(res) { // new interval
+ ++t_index; // starting new interval
+ control_dates[t_index] = date; // updating control date as we moved to the new interval
+ second_left[t_index+1] = second_left[t_index];
+ }
+ var t = t_index+1;
+ second_cols[t_index] = scheduler._cols[l] + (second_cols[t_index]||0);
+ second_left[t] += scheduler._cols[l];
+ }
+
+ h.innerHTML = "<div></div><div></div>";
+ var top = h.firstChild;
+ top.style.height = (original_sh)+'px'; // actually bottom header takes 21px
+ var bottom = h.lastChild;
+ bottom.style.position = "relative";
+
+ for (var m = 0; m < control_dates.length; m++) {
+ var tdate = control_dates[m];
+ var scs = scheduler.templates[this.name+"_second_scalex_class"](tdate);
+ var head=document.createElement("DIV"); head.className="dhx_scale_bar dhx_second_scale_bar"+((scs)?(" "+scs):"");
+ scheduler.set_xy(head,second_cols[m]-1,original_sh-3,second_left[m],0); //-1 for border, -3 = -2 padding -1 border bottom
+ head.innerHTML = scheduler.templates[this.name+"_second_scale_date"](tdate);
+ top.appendChild(head);
+ }
+ }
+
+ scheduler.xy.scale_height = original_sh; // fix for _render_x_header which uses current scale_height value
+ h = h.lastChild; // h - original scale
+ for (var i=0; i<this._trace_x.length; i++){
+ if (scheduler._ignores[i])
+ continue;
+
+ start = this._trace_x[i];
+ scheduler._render_x_header(i, left[i], start, h);
+ var cs = scheduler.templates[this.name+"_scalex_class"](start);
+ if (cs)
+ h.lastChild.className += " "+cs;
+ }
+ scheduler.xy.scale_height = current_sh; // restoring current value
+
+ var trace = this._trace_x;
+ h.onclick = function(e){
+ var pos = locate_hcell(e);
+ if (pos)
+ scheduler.callEvent("onXScaleClick",[pos.x, trace[pos.x], e||event]);
+ };
+ h.ondblclick = function(e){
+ var pos = locate_hcell(e);
+ if (pos)
+ scheduler.callEvent("onXScaleDblClick",[pos.x, trace[pos.x], e||event]);
+ };
+}
+function is_new_interval(mode, date, control_date){ // mode, date to check, control_date for which period should be checked
+ switch(mode) {
+ case "hour":
+ return ((date.getHours() != control_date.getHours()) || is_new_interval("day", date, control_date));
+ case "day":
+ return !(date.getDate() == control_date.getDate() && date.getMonth() == control_date.getMonth() && date.getFullYear() == control_date.getFullYear());
+ case "week":
+ return !(scheduler.date.getISOWeek(date) == scheduler.date.getISOWeek(control_date) && date.getFullYear() == control_date.getFullYear());
+ case "month":
+ return !(date.getMonth() == control_date.getMonth() && date.getFullYear() == control_date.getFullYear());
+ case "year":
+ return !(date.getFullYear() == control_date.getFullYear());
+ default:
+ return false; // same interval
+ }
+}
+function set_full_view(mode){
+ if (mode){
+ scheduler.set_sizes();
+ _init_matrix_tooltip()
+
+ //we need to have day-rounded scales for navigation
+ //in same time, during rendering scales may be shifted
+ var temp = scheduler._min_date;
+ x_scale.call(this,scheduler._els["dhx_cal_header"][0]);
+ y_scale.call(this,scheduler._els["dhx_cal_data"][0]);
+ scheduler._min_date = temp;
+ scheduler._els["dhx_cal_date"][0].innerHTML=scheduler.templates[this.name+"_date"](scheduler._min_date, scheduler._max_date);
+ if (scheduler._mark_now) {
+ scheduler._mark_now();
+ }
+ }
+
+ // hide tooltip if it is displayed
+ hideToolTip();
+}
+
+
+function hideToolTip(){
+ if (scheduler._tooltip){
+ scheduler._tooltip.style.display = "none";
+ scheduler._tooltip.date = "";
+ }
+}
+function showToolTip(obj,pos,offset){
+ if (obj.render != "cell") return;
+ var mark = pos.x+"_"+pos.y;
+ var evs = obj._matrix[pos.y][pos.x];
+
+ if (!evs) return hideToolTip();
+
+ evs.sort(function(a,b){ return a.start_date>b.start_date?1:-1; });
+
+ if (scheduler._tooltip){
+ if (scheduler._tooltip.date == mark) return;
+ scheduler._tooltip.innerHTML="";
+ } else {
+ var t = scheduler._tooltip = document.createElement("DIV");
+ t.className = "dhx_year_tooltip";
+ document.body.appendChild(t);
+ t.onclick = scheduler._click.dhx_cal_data;
+ }
+
+ var html = "";
+
+ for (var i=0; i<evs.length; i++){
+ var bg_color = (evs[i].color?("background-color:"+evs[i].color+";"):"");
+ var color = (evs[i].textColor?("color:"+evs[i].textColor+";"):"");
+ html+="<div class='dhx_tooltip_line' event_id='"+evs[i].id+"' style='"+bg_color+""+color+"'>";
+ html+="<div class='dhx_tooltip_date'>"+(evs[i]._timed?scheduler.templates.event_date(evs[i].start_date):"")+"</div>";
+ html+="<div class='dhx_event_icon icon_details'>&nbsp;</div>";
+ html+=scheduler.templates[obj.name+"_tooltip"](evs[i].start_date, evs[i].end_date,evs[i])+"</div>";
+ }
+
+ scheduler._tooltip.style.display="";
+ scheduler._tooltip.style.top = "0px";
+
+ if (document.body.offsetWidth-offset.left-scheduler._tooltip.offsetWidth < 0)
+ scheduler._tooltip.style.left = offset.left-scheduler._tooltip.offsetWidth+"px";
+ else
+ scheduler._tooltip.style.left = offset.left+pos.src.offsetWidth+"px";
+
+ scheduler._tooltip.date = mark;
+ scheduler._tooltip.innerHTML = html;
+
+ if (document.body.offsetHeight-offset.top-scheduler._tooltip.offsetHeight < 0)
+ scheduler._tooltip.style.top= offset.top-scheduler._tooltip.offsetHeight+pos.src.offsetHeight+"px";
+ else
+ scheduler._tooltip.style.top= offset.top+"px";
+}
+
+function _init_matrix_tooltip() {
+ dhtmlxEvent(scheduler._els["dhx_cal_data"][0], "mouseover", function(e){
+ var obj = scheduler.matrix[scheduler._mode];
+ if (!obj || obj.render != "cell")
+ return;
+ if (obj){
+ var pos = scheduler._locate_cell_timeline(e);
+ var e = e || event;
+ var src = e.target||e.srcElement;
+ if (pos)
+ return showToolTip(obj,pos,getOffset(pos.src));
+ }
+ hideToolTip();
+ });
+ _init_matrix_tooltip=function(){};
+}
+
+scheduler._renderMatrix = function(mode, refresh) {
+ if (!refresh)
+ scheduler._els['dhx_cal_data'][0].scrollTop=0;
+
+ scheduler._min_date = scheduler.date[this.name+"_start"](scheduler._date);
+ scheduler._max_date = scheduler.date.add(scheduler._min_date, this.x_size*this.x_step, this.x_unit);
+ scheduler._table_view = true;
+ if (this.second_scale) {
+ if (mode && !this._header_resized) {
+ this._header_resized = scheduler.xy.scale_height;
+ scheduler.xy.scale_height *= 2;
+ scheduler._els['dhx_cal_header'][0].className += " dhx_second_cal_header";
+ }
+ if (!mode && this._header_resized) {
+ scheduler.xy.scale_height /= 2;
+ this._header_resized = false;
+ var header = scheduler._els['dhx_cal_header'][0];
+ header.className = header.className.replace(/ dhx_second_cal_header/gi,"");
+ }
+ }
+ set_full_view.call(this,mode);
+};
+
+function html_index(el) {
+ var p = el.parentNode.childNodes;
+ for (var i=0; i < p.length; i++)
+ if (p[i] == el) return i;
+ return -1;
+}
+function locate_hcell(e){
+ e = e||event;
+ var trg = e.target?e.target:e.srcElement;
+ while (trg && trg.tagName != "DIV")
+ trg=trg.parentNode;
+ if (trg && trg.tagName == "DIV"){
+ var cs = trg.className.split(" ")[0];
+ if (cs == "dhx_scale_bar")
+ return { x:html_index(trg), y:-1, src:trg, scale:true };
+ }
+}
+scheduler._locate_cell_timeline = function(e){
+ e = e||event;
+ var trg = e.target?e.target:e.srcElement;
+
+ var res = {};
+ var view = scheduler.matrix[scheduler._mode];
+ var pos = scheduler.getActionData(e);
+
+ for (var xind = 0; xind < view._trace_x.length-1; xind++) {
+ // | 8:00, 8:30 | 8:15 should be checked against 8:30
+ // clicking at the most left part of the cell, say 8:30 should create event in that cell, not previous one
+ if (+pos.date < view._trace_x[xind+1])
+ break;
+ }
+
+ res.x = xind;
+ res.y = view.order[pos.section];
+ var diff = scheduler._isRender('cell') ? 1 : 0;
+ res.src = view._scales[pos.section] ? view._scales[pos.section].getElementsByTagName('td')[xind+diff] : null;
+
+ var isScale = false;
+ while (res.x == 0 && trg.className != "dhx_cal_data" && trg.parentNode) {
+ if (trg.className.split(" ")[0] == "dhx_matrix_scell") {
+ isScale = true;
+ break;
+ } else {
+ trg = trg.parentNode;
+ }
+ }
+ if (isScale) { // Y scale
+ res.x = -1;
+ res.src = trg;
+ res.scale = true;
+ }
+
+ return res;
+};
+
+var old_click = scheduler._click.dhx_cal_data;
+scheduler._click.dhx_marked_timespan = scheduler._click.dhx_cal_data = function(e){
+ var ret = old_click.apply(this,arguments);
+ var obj = scheduler.matrix[scheduler._mode];
+ if (obj){
+ var pos = scheduler._locate_cell_timeline(e);
+ if (pos){
+ if (pos.scale)
+ scheduler.callEvent("onYScaleClick",[pos.y, obj.y_unit[pos.y], e||event]);
+ else
+ scheduler.callEvent("onCellClick",[pos.x, pos.y, obj._trace_x[pos.x], (((obj._matrix[pos.y]||{})[pos.x])||[]), e||event]);
+ }
+ }
+ return ret;
+};
+
+scheduler.dblclick_dhx_marked_timespan = scheduler.dblclick_dhx_matrix_cell = function(e){
+ var obj = scheduler.matrix[scheduler._mode];
+ if (obj){
+ var pos = scheduler._locate_cell_timeline(e);
+ if (pos){
+ if (pos.scale)
+ scheduler.callEvent("onYScaleDblClick",[pos.y, obj.y_unit[pos.y], e||event]);
+ else
+ scheduler.callEvent("onCellDblClick",[pos.x, pos.y, obj._trace_x[pos.x], (((obj._matrix[pos.y]||{})[pos.x])||[]), e||event]);
+ }
+ }
+};
+scheduler.dblclick_dhx_matrix_scell = function(e){
+ return scheduler.dblclick_dhx_matrix_cell(e);
+};
+
+scheduler._isRender = function(mode){
+ return (scheduler.matrix[scheduler._mode] && scheduler.matrix[scheduler._mode].render == mode);
+};
+
+scheduler.attachEvent("onCellDblClick", function (x, y, a, b, event){
+ if (this.config.readonly|| (event.type == "dblclick" && !this.config.dblclick_create)) return;
+
+ var obj = scheduler.matrix[scheduler._mode];
+ var event_options = {};
+ event_options.start_date = obj._trace_x[x];
+ event_options.end_date = (obj._trace_x[x+1]) ? obj._trace_x[x+1] : scheduler.date.add(obj._trace_x[x], obj.x_step, obj.x_unit);
+
+ if (obj._start_correction)
+ event_options.start_date = new Date(event_options.start_date*1 + obj._start_correction);
+ if (obj._end_correction)
+ event_options.end_date = new Date(event_options.end_date - obj._end_correction);
+
+ event_options[obj.y_property] = obj.y_unit[y].key;
+ scheduler.addEventNow(event_options, null, event);
+});
+
+scheduler.attachEvent("onBeforeDrag", function (event_id, mode, native_event_object){
+ return !scheduler._isRender("cell");
+});
+scheduler.attachEvent("onEventChanged", function(id, ev) {
+ ev._timed = this.isOneDayEvent(ev);
+});
+var old_render_marked_timespan = scheduler._render_marked_timespan;
+scheduler._render_marked_timespan = function(options, area, unit_id, min_date, max_date) {
+ if (!scheduler.config.display_marked_timespans)
+ return [];
+
+ if (scheduler.matrix && scheduler.matrix[scheduler._mode]) {
+ if (scheduler._isRender('cell'))
+ return;
+
+ var view_opts = scheduler._lame_copy({}, scheduler.matrix[scheduler._mode]);
+ //timespans must always use actual position, not rounded
+ view_opts.round_position = false;
+ var blocks = [];
+
+ var units = [];
+ var areas = [];
+ if (!unit_id) { // should draw for every unit
+ var order = view_opts.order;
+ for (var key in order) {
+ if (order.hasOwnProperty(key)) {
+ units.push(key);
+ areas.push(view_opts._scales[key]);
+ }
+ }
+ } else {
+ areas = [area];
+ units = [unit_id]
+ }
+
+ var min_date = min_date ? new Date(min_date) : scheduler._min_date;
+ var max_date = max_date ? new Date(max_date) : scheduler._max_date;
+ var dates = [];
+
+ if (options.days > 6) {
+ var specific_date = new Date(options.days);
+ if (scheduler.date.date_part(new Date(min_date)) <= +specific_date && +max_date >= +specific_date)
+ dates.push(specific_date);
+ } else {
+ dates.push.apply(dates, scheduler._get_dates_by_index(options.days));
+ }
+
+ var zones = options.zones;
+ var css_classes = scheduler._get_css_classes_by_config(options);
+
+ for (var j=0; j<units.length; j++) {
+ area = areas[j];
+ unit_id = units[j];
+
+ for (var i=0; i<dates.length; i++) {
+ var date = dates[i];
+ for (var k=0; k<zones.length; k += 2) {
+ var zone_start = zones[k];
+ var zone_end = zones[k+1];
+ var start_date = new Date(+date + zone_start*60*1000);
+ var end_date = new Date(+date + zone_end*60*1000);
+
+ if (!(min_date < end_date && max_date > start_date))
+ continue;
+
+ var block = scheduler._get_block_by_config(options);
+ block.className = css_classes;
+
+ var start_pos = _getX({start_date: start_date}, false, view_opts)-1;
+ var end_pos = _getX({start_date: end_date}, false, view_opts)-1;
+ var width = Math.max(1, end_pos - start_pos - 1);
+ var height = view_opts._section_height[unit_id]-1;
+
+ block.style.cssText = "height: "+height+"px; left: "+start_pos+"px; width: "+width+"px; top: 0;";
+
+ area.insertBefore(block, area.firstChild);
+ blocks.push(block);
+ }
+ }
+ }
+
+ return blocks;
+
+ } else {
+ return old_render_marked_timespan.apply(scheduler, [options, area, unit_id]);
+ }
+};
+
+var old_append_mark_now = scheduler._append_mark_now;
+scheduler._append_mark_now = function(day_index, now) {
+ if (scheduler.matrix && scheduler.matrix[scheduler._mode]) {
+ var n_date = scheduler._currentDate();
+ var zone_start = scheduler._get_zone_minutes(n_date);
+ var options = {
+ days: +scheduler.date.date_part(n_date),
+ zones: [zone_start, zone_start+1],
+ css: "dhx_matrix_now_time",
+ type: "dhx_now_time"
+ };
+ return scheduler._render_marked_timespan(options);
+ } else {
+ return old_append_mark_now.apply(scheduler, [day_index, now]);
+ }
+};
+
+scheduler.attachEvent("onScaleAdd", function(scale, unit_key) {
+ var timespans = scheduler._marked_timespans;
+
+ if (timespans && scheduler.matrix && scheduler.matrix[scheduler._mode]) {
+ var mode = scheduler._mode;
+
+ var min_date = scheduler._min_date;
+ var max_date = scheduler._max_date;
+ var global_data = timespans["global"];
+
+ for (var t_date = scheduler.date.date_part(new Date(min_date)); t_date < max_date; t_date = scheduler.date.add(t_date, 1, "day")) {
+ var day_value = +t_date;
+ var day_index = t_date.getDay();
+ var r_configs = [];
+
+ var day_types = global_data[day_value]||global_data[day_index];
+ r_configs.push.apply(r_configs, scheduler._get_configs_to_render(day_types));
+
+ if (timespans[mode] && timespans[mode][unit_key]) {
+ var z_config = [];
+ var unit_types = scheduler._get_types_to_render(timespans[mode][unit_key][day_index], timespans[mode][unit_key][day_value]);
+ z_config.push.apply(z_config, scheduler._get_configs_to_render(unit_types));
+ if(z_config.length)
+ r_configs = z_config;
+ }
+
+ for (var i=0; i<r_configs.length; i++) {
+ var config = r_configs[i];
+ var day = config.days;
+ if (day < 7) {
+ day = day_value;
+ //specify min/max timespan dates, otherwise it can be rendered multiple times in some configurations
+ scheduler._render_marked_timespan(config, scale, unit_key, t_date, scheduler.date.add(t_date, 1, "day"));
+ day = day_index;
+ } else {
+ scheduler._render_marked_timespan(config, scale, unit_key, t_date, scheduler.date.add(t_date, 1, "day"));
+ }
+ }
+ }
+ }
+});
+
+scheduler._get_date_index=function(config, date) {
+ var index = 0;
+ var trace_x = config._trace_x;
+ while (index < trace_x.length-1 && +date >= +trace_x[index+1]) {
+ index++;
+ }
+ return index;
+};
+
+})();
diff --git a/sources/ext/dhtmlxscheduler_tooltip.js b/sources/ext/dhtmlxscheduler_tooltip.js
new file mode 100644
index 0000000..318139c
--- /dev/null
+++ b/sources/ext/dhtmlxscheduler_tooltip.js
@@ -0,0 +1,193 @@
+/*
+This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
+to use it in non-GPL project. Please contact sales@dhtmlx.com for details
+*/
+window.dhtmlXTooltip = scheduler.dhtmlXTooltip = window.dhtmlxTooltip = {};
+
+dhtmlXTooltip.config = {
+ className: 'dhtmlXTooltip tooltip',
+ timeout_to_display: 50,
+ timeout_to_hide: 50,
+ delta_x: 15,
+ delta_y: -20
+};
+
+dhtmlXTooltip.tooltip = document.createElement('div');
+dhtmlXTooltip.tooltip.className = dhtmlXTooltip.config.className;
+
+dhtmlXTooltip.show = function(event, text) { //browser event, text to display
+ if (scheduler.config.touch && !scheduler.config.touch_tooltip) return;
+
+ var dhxTooltip = dhtmlXTooltip;
+ var tooltip_div = this.tooltip;
+ var tooltip_div_style = tooltip_div.style;
+ dhxTooltip.tooltip.className = dhxTooltip.config.className;
+ var pos = this.position(event);
+
+ var target = event.target || event.srcElement;
+ // if we are over tooltip -- do nothing, just return (so tooltip won't move)
+ if (this.isTooltip(target)) {
+ return;
+ }
+
+ var actual_x = pos.x + (dhxTooltip.config.delta_x || 0);
+ var actual_y = pos.y - (dhxTooltip.config.delta_y || 0);
+
+ tooltip_div_style.visibility = "hidden";
+
+ if (tooltip_div_style.removeAttribute) {
+ tooltip_div_style.removeAttribute("right");
+ tooltip_div_style.removeAttribute("bottom");
+ } else {
+ tooltip_div_style.removeProperty("right");
+ tooltip_div_style.removeProperty("bottom");
+ }
+
+ tooltip_div_style.left = "0";
+ tooltip_div_style.top = "0";
+
+ this.tooltip.innerHTML = text;
+ document.body.appendChild(this.tooltip);
+
+ var tooltip_width = this.tooltip.offsetWidth;
+ var tooltip_height = this.tooltip.offsetHeight;
+
+ if ((document.body.offsetWidth - actual_x - tooltip_width) < 0) { // tooltip is out of the right page bound
+ if(tooltip_div_style.removeAttribute)
+ tooltip_div_style.removeAttribute("left");
+ else
+ tooltip_div_style.removeProperty("left");
+ tooltip_div_style.right = (document.body.offsetWidth - actual_x + 2 * (dhxTooltip.config.delta_x||0)) + "px";
+ } else {
+ if (actual_x < 0) {
+ // tooltips is out of the left page bound
+ tooltip_div_style.left = (pos.x + Math.abs(dhxTooltip.config.delta_x||0)) + "px";
+ } else {
+ // normal situation
+ tooltip_div_style.left = actual_x + "px";
+ }
+ }
+
+ if ((document.body.offsetHeight - actual_y - tooltip_height) < 0) { // tooltip is below bottom of the page
+ if(tooltip_div_style.removeAttribute)
+ tooltip_div_style.removeAttribute("top");
+ else
+ tooltip_div_style.removeProperty("top");
+ tooltip_div_style.bottom = (document.body.offsetHeight - actual_y - 2 * (dhxTooltip.config.delta_y||0)) + "px";
+ } else {
+ if (actual_y < 0) {
+ // tooltip is higher then top of the page
+ tooltip_div_style.top = (pos.y + Math.abs(dhxTooltip.config.delta_y||0)) + "px";
+ } else {
+ // normal situation
+ tooltip_div_style.top = actual_y + "px";
+ }
+ }
+
+ tooltip_div_style.visibility = "visible";
+
+ scheduler.callEvent("onTooltipDisplayed", [this.tooltip, this.tooltip.event_id]);
+};
+dhtmlXTooltip._clearTimeout = function(){
+ if(this.tooltip._timeout_id) {
+ window.clearTimeout(this.tooltip._timeout_id);
+ }
+};
+
+dhtmlXTooltip.hide = function() {
+ if (this.tooltip.parentNode) {
+ var event_id = this.tooltip.event_id;
+ this.tooltip.event_id = null;
+ this.tooltip.parentNode.removeChild(this.tooltip);
+ scheduler.callEvent("onAfterTooltip", [event_id]);
+ }
+ this._clearTimeout();
+};
+dhtmlXTooltip.delay = function(method, object, params, delay) {
+ this._clearTimeout();
+ this.tooltip._timeout_id = setTimeout(function() {
+ var ret = method.apply(object, params);
+ method = object = params = null;
+ return ret;
+ }, delay || this.config.timeout_to_display);
+};
+
+dhtmlXTooltip.isTooltip = function(node) {
+ var res = false;
+ if (node.className.split(" ")[0] == "dhtmlXTooltip") {
+ //debugger;
+ }
+ while (node && !res) {
+ res = (node.className == this.tooltip.className);
+ node = node.parentNode;
+ }
+ return res;
+};
+
+dhtmlXTooltip.position = function(ev) {
+ ev = ev || window.event;
+ if (ev.pageX || ev.pageY) //FF, KHTML
+ return {x:ev.pageX, y:ev.pageY};
+ //IE
+ var d = ((window._isIE) && (document.compatMode != "BackCompat")) ? document.documentElement : document.body;
+ return {
+ x:ev.clientX + d.scrollLeft - d.clientLeft,
+ y:ev.clientY + d.scrollTop - d.clientTop
+ };
+};
+
+scheduler.attachEvent("onMouseMove", function(event_id, e) { // (scheduler event_id, browser event)
+ var ev = window.event || e;
+ var target = ev.target || ev.srcElement;
+ var dhxTooltip = dhtmlXTooltip;
+
+ var is_tooltip = dhxTooltip.isTooltip(target);
+ var is_tooltip_target = (dhxTooltip.isTooltipTarget && dhxTooltip.isTooltipTarget(target));
+
+ // if we are over event or tooltip or custom target for tooltip
+ if (event_id || is_tooltip || is_tooltip_target) {
+ var text;
+
+ if (event_id || dhxTooltip.tooltip.event_id) {
+ var event = scheduler.getEvent(event_id) || scheduler.getEvent(dhxTooltip.tooltip.event_id);
+ if (!event)
+ return;
+
+ dhxTooltip.tooltip.event_id = event.id;
+ text = scheduler.templates.tooltip_text(event.start_date, event.end_date, event);
+ if (!text)
+ return dhxTooltip.hide();
+ }
+ if (is_tooltip_target) {
+ text = "";
+ }
+
+ var evt = undefined;
+ if (_isIE) {
+ //make a copy of event, will be used in timed call
+ evt = document.createEventObject(ev);
+ }
+
+ if (!scheduler.callEvent("onBeforeTooltip", [event_id]) || !text)
+ return;
+
+ dhxTooltip.delay(dhxTooltip.show, dhxTooltip, [(evt || ev), text]); // showing tooltip
+ } else {
+ dhxTooltip.delay(dhxTooltip.hide, dhxTooltip, [], dhxTooltip.config.timeout_to_hide);
+ }
+});
+scheduler.attachEvent("onBeforeDrag", function() {
+ dhtmlXTooltip.hide();
+ return true;
+});
+scheduler.attachEvent("onEventDeleted", function() {
+ dhtmlXTooltip.hide();
+ return true;
+});
+
+/* Could be redifined */
+scheduler.templates.tooltip_date_format = scheduler.date.date_to_str("%Y-%m-%d %H:%i");
+
+scheduler.templates.tooltip_text = function(start, end, event) {
+ return "<b>Event:</b> " + event.text + "<br/><b>Start date:</b> " + scheduler.templates.tooltip_date_format(start) + "<br/><b>End date:</b> " + scheduler.templates.tooltip_date_format(end);
+};
diff --git a/sources/ext/dhtmlxscheduler_treetimeline.js b/sources/ext/dhtmlxscheduler_treetimeline.js
new file mode 100644
index 0000000..51970a3
--- /dev/null
+++ b/sources/ext/dhtmlxscheduler_treetimeline.js
@@ -0,0 +1,302 @@
+/*
+This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
+to use it in non-GPL project. Please contact sales@dhtmlx.com for details
+*/
+scheduler.attachEvent("onTimelineCreated", function (obj){
+
+ if(obj.render == "tree") {
+ obj.y_unit_original = obj.y_unit;
+ obj.y_unit = scheduler._getArrayToDisplay(obj.y_unit_original);
+
+ scheduler.attachEvent('onOptionsLoadStart', function(){
+ obj.y_unit = scheduler._getArrayToDisplay(obj.y_unit_original);
+ });
+
+ scheduler.form_blocks[obj.name]={
+ render:function(sns) {
+ var _result = "<div class='dhx_section_timeline' style='overflow: hidden; height: "+sns.height+"px'></div>";
+ return _result;
+ },
+ set_value:function(node,value,ev,config){
+ var options = scheduler._getArrayForSelect(scheduler.matrix[config.type].y_unit_original, config.type);
+ node.innerHTML = '';
+ var temp_select = document.createElement('select');
+ node.appendChild(temp_select);
+
+ var select = node.getElementsByTagName('select')[0];
+
+ if (!select._dhx_onchange && config.onchange) {
+ select.onchange = config.onchange;
+ select._dhx_onchange = true;
+ }
+
+ for (var i = 0; i < options.length; i++) {
+ var temp_option = document.createElement('option');
+ temp_option.value = options[i].key;
+ if(temp_option.value == ev[scheduler.matrix[config.type].y_property])
+ temp_option.selected = true;
+ temp_option.innerHTML = options[i].label;
+ select.appendChild(temp_option);
+ }
+ },
+ get_value:function(node,ev,config){
+ return node.firstChild.value;
+ },
+ focus:function(node){
+ }
+ };
+
+
+ }
+});
+
+scheduler.attachEvent("onBeforeSectionRender", function (render_name, y_unit, timeline){
+ var res = {};
+ if(render_name == "tree"){
+ var height;
+ // section 1
+ var tr_className, style_height, td_className;
+ var div_expand;
+ // section 3
+ var table_className;
+ if(y_unit.children) {
+ height = timeline.folder_dy||timeline.dy;
+ if(timeline.folder_dy && !timeline.section_autoheight) {
+ style_height = "height:"+timeline.folder_dy+"px;";
+ }
+ tr_className = "dhx_row_folder";
+ td_className = "dhx_matrix_scell folder";
+ div_expand = "<div class='dhx_scell_expand'>"+((y_unit.open)?'-':'+')+"</div>";
+ table_className = (timeline.folder_events_available)?"dhx_data_table folder_events":"dhx_data_table folder";
+ } else {
+ height = timeline.dy;
+ tr_className = "dhx_row_item";
+ td_className = "dhx_matrix_scell item";
+ div_expand = '';
+ table_className = "dhx_data_table";
+ }
+ var td_content = "<div class='dhx_scell_level"+y_unit.level+"'>"+div_expand+"<div class='dhx_scell_name'>"+(scheduler.templates[timeline.name+'_scale_label'](y_unit.key, y_unit.label, y_unit)||y_unit.label)+"</div></div>";
+
+ res = {
+ height: height,
+ style_height: style_height,
+ //section 1
+ tr_className: tr_className,
+ td_className: td_className,
+ td_content: td_content,
+ //section 3
+ table_className: table_className
+ };
+ };
+ return res;
+});
+
+var section_id_before; // section id of the event before dragging (to bring it back if user drop's event on folder without folder_events_available)
+
+scheduler.attachEvent("onBeforeEventChanged", function(event_object, native_event, is_new) {
+ if (scheduler._isRender("tree")) { // if mode's render == tree
+ var section = scheduler.getSection(event_object[scheduler.matrix[scheduler._mode].y_property]);
+ if (section && typeof section.children != 'undefined' && !scheduler.matrix[scheduler._mode].folder_events_available) { // section itself could be not defined in case of new event (addEventNow)
+ if (!is_new) { //if old - move back
+ event_object[scheduler.matrix[scheduler._mode].y_property] = section_id_before;
+ }
+ return false;
+ }
+ }
+ return true;
+});
+
+scheduler.attachEvent("onBeforeDrag", function (event_id, mode, native_event_object){
+ if(scheduler._isRender("tree")) {
+ var cell = scheduler._locate_cell_timeline(native_event_object);
+ if(cell) {
+ var section_id = scheduler.matrix[scheduler._mode].y_unit[cell.y].key;
+ if(typeof scheduler.matrix[scheduler._mode].y_unit[cell.y].children != "undefined" && !scheduler.matrix[scheduler._mode].folder_events_available) {
+ return false;
+ }
+ }
+
+ var ev = scheduler.getEvent(event_id);
+ section_id_before = section_id||ev[scheduler.matrix[scheduler._mode].y_property]; // either event id or section_id will be available
+ }
+ return true;
+});
+
+scheduler._getArrayToDisplay = function(array){ // function to flatten out hierarhical array, used for tree view
+ var result = [];
+ var fillResultArray = function(array, lvl){
+ var level = lvl||0;
+ for(var i=0; i<array.length; i++) {
+ array[i].level = level;
+ if(typeof array[i].children != "undefined" && typeof array[i].key == "undefined")
+ array[i].key=scheduler.uid();
+ result.push(array[i]);
+ if(array[i].open && array[i].children) {
+ fillResultArray(array[i].children, level+1);
+ }
+ }
+ };
+ fillResultArray(array);
+ return result;
+};
+
+
+scheduler._getArrayForSelect = function(array, mode){ // function to flatten out hierarhical array, used for tree view
+ var result = [];
+ var fillResultArray = function(array){
+ for(var i=0; i<array.length; i++) {
+ if(scheduler.matrix[mode].folder_events_available) {
+ result.push(array[i]);
+ }
+ else {
+ if(typeof array[i].children == "undefined") {
+ result.push(array[i]);
+ }
+ }
+ if(array[i].children)
+ fillResultArray(array[i].children, mode);
+ }
+ };
+ fillResultArray(array);
+ return result;
+};
+
+
+/*
+scheduler._toggleFolderDisplay(4) -- toggle display of the section with key 4 (closed -> open)
+scheduler._toggleFolderDisplay(4, true) -- open section with the key 4 (doesn't matter what status was before). False - close.
+scheduler._toggleFolderDisplay(4, false, true) -- close ALL sections. Key is not used in such condition.
+*/
+scheduler._toggleFolderDisplay = function(key, status, all_sections){ // used for tree view
+ var marked;
+ var toggleElement = function(key, array, status, all_sections) {
+ for (var i=0; i<array.length; i++) {
+ if((array[i].key == key || all_sections) && array[i].children) {
+ array[i].open = (typeof status != "undefined") ? status : !array[i].open;
+ marked = true;
+ if(!all_sections && marked)
+ break;
+ }
+ if(array[i].children) {
+ toggleElement(key,array[i].children, status, all_sections);
+ }
+ }
+ };
+ var section = scheduler.getSection(key);
+ if (scheduler.callEvent("onBeforeFolderToggle", [section, status, all_sections])) {
+ toggleElement(key,scheduler.matrix[scheduler._mode].y_unit_original, status, all_sections);
+ scheduler.matrix[scheduler._mode].y_unit = scheduler._getArrayToDisplay(scheduler.matrix[scheduler._mode].y_unit_original);
+ scheduler.callEvent("onOptionsLoad",[]);
+ scheduler.callEvent("onAfterFolderToggle", [section, status, all_sections]);
+ }
+};
+
+scheduler.attachEvent("onCellClick", function (x, y, a, b, event){
+ if(scheduler._isRender("tree")) {
+ if(!scheduler.matrix[scheduler._mode].folder_events_available) {
+ if(typeof scheduler.matrix[scheduler._mode].y_unit[y].children != "undefined") {
+ scheduler._toggleFolderDisplay(scheduler.matrix[scheduler._mode].y_unit[y].key);
+ }
+ }
+ }
+});
+
+scheduler.attachEvent("onYScaleClick", function (index, value, event){
+ if(scheduler._isRender("tree")) {
+ if(typeof value.children != "undefined") {
+ scheduler._toggleFolderDisplay(value.key);
+ }
+ }
+});
+
+scheduler.getSection = function(id){
+ if(scheduler._isRender("tree")) {
+ var obj;
+ var findElement = function(key, array) {
+ for (var i=0; i<array.length; i++) {
+ if(array[i].key == key)
+ obj = array[i];
+ if(array[i].children)
+ findElement(key,array[i].children);
+ }
+ };
+ findElement(id, scheduler.matrix[scheduler._mode].y_unit_original);
+ return obj||null;
+ }
+};
+
+scheduler.deleteSection = function(id){
+ if(scheduler._isRender("tree")) {
+ var result = false;
+ var deleteElement = function(key, array) {
+ for (var i=0; i<array.length; i++) {
+ if(array[i].key == key) {
+ array.splice(i,1);
+ result = true;
+ }
+ if(result)
+ break;
+ if(array[i].children)
+ deleteElement(key,array[i].children);
+ }
+ };
+ deleteElement(id, scheduler.matrix[scheduler._mode].y_unit_original);
+ scheduler.matrix[scheduler._mode].y_unit = scheduler._getArrayToDisplay(scheduler.matrix[scheduler._mode].y_unit_original);
+ scheduler.callEvent("onOptionsLoad",[]);
+ return result;
+ }
+};
+
+scheduler.deleteAllSections = function(){
+ if(scheduler._isRender("tree")) {
+ scheduler.matrix[scheduler._mode].y_unit_original = [];
+ scheduler.matrix[scheduler._mode].y_unit = scheduler._getArrayToDisplay(scheduler.matrix[scheduler._mode].y_unit_original);
+ scheduler.callEvent("onOptionsLoad",[]);
+ }
+};
+
+scheduler.addSection = function(obj, parent_id){
+ if(scheduler._isRender("tree")) {
+ var result = false;
+ var addElement = function(obj, parent_key, array) {
+ if(!parent_id) {
+ array.push(obj);
+ result = true;
+ }
+ else {
+ for (var i=0; i<array.length; i++) {
+ if(array[i].key == parent_key && typeof array[i].children != "undefined") {
+ array[i].children.push(obj);
+ result = true;
+ }
+ if(result)
+ break;
+ if(array[i].children)
+ addElement(obj,parent_key,array[i].children);
+ }
+ }
+ };
+ addElement(obj, parent_id, scheduler.matrix[scheduler._mode].y_unit_original);
+ scheduler.matrix[scheduler._mode].y_unit = scheduler._getArrayToDisplay(scheduler.matrix[scheduler._mode].y_unit_original);
+ scheduler.callEvent("onOptionsLoad",[]);
+ return result;
+ }
+};
+
+
+scheduler.openAllSections = function() {
+ if(scheduler._isRender("tree"))
+ scheduler._toggleFolderDisplay(1, true, true);
+};
+scheduler.closeAllSections = function() {
+ if(scheduler._isRender("tree"))
+ scheduler._toggleFolderDisplay(1, false, true);
+};
+scheduler.openSection = function(section_id){
+ if(scheduler._isRender("tree"))
+ scheduler._toggleFolderDisplay(section_id, true);
+};
+scheduler.closeSection = function(section_id){
+ if(scheduler._isRender("tree"))
+ scheduler._toggleFolderDisplay(section_id, false);
+};
diff --git a/sources/ext/dhtmlxscheduler_units.js b/sources/ext/dhtmlxscheduler_units.js
new file mode 100644
index 0000000..bdd31cc
--- /dev/null
+++ b/sources/ext/dhtmlxscheduler_units.js
@@ -0,0 +1,238 @@
+/*
+This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
+to use it in non-GPL project. Please contact sales@dhtmlx.com for details
+*/
+scheduler._props = {};
+scheduler.createUnitsView=function(name,property,list,size,step,skip_incorrect){
+ if (typeof name == "object"){
+ list = name.list;
+ property = name.property;
+ size = name.size||0;
+ step = name.step||1;
+ skip_incorrect = name.skip_incorrect;
+ name = name.name;
+ }
+
+ scheduler._props[name]={map_to:property, options:list, step:step, position:0 };
+ if(size>scheduler._props[name].options.length){
+ scheduler._props[name]._original_size = size;
+ size = 0;
+ }
+ scheduler._props[name].size = size;
+ scheduler._props[name].skip_incorrect = skip_incorrect||false;
+
+ scheduler.date[name+"_start"]= scheduler.date.day_start;
+ scheduler.templates[name+"_date"] = function(date){
+ return scheduler.templates.day_date(date);
+ };
+
+ scheduler._get_unit_index = function(unit_view, date) {
+ var original_position = unit_view.position || 0;
+ var date_position = Math.floor((scheduler._correct_shift(+date, 1) - +scheduler._min_date) / (60 * 60 * 24 * 1000));
+ return original_position + date_position;
+ };
+ scheduler.templates[name + "_scale_text"] = function(id, label, option) {
+ if (option.css) {
+ return "<span class='" + option.css + "'>" + label + "</span>";
+ } else {
+ return label;
+ }
+ };
+ scheduler.templates[name+"_scale_date"] = function(date) {
+ var unit_view = scheduler._props[name];
+ var list = unit_view.options;
+ if (!list.length) return "";
+ var index = scheduler._get_unit_index(unit_view, date);
+ var option = list[index];
+ return scheduler.templates[name + "_scale_text"](option.key, option.label, option);
+ };
+
+ scheduler.date["add_"+name]=function(date,inc){ return scheduler.date.add(date,inc,"day"); };
+ scheduler.date["get_"+name+"_end"]=function(date){
+ return scheduler.date.add(date,scheduler._props[name].size||scheduler._props[name].options.length,"day");
+ };
+
+ scheduler.attachEvent("onOptionsLoad",function(){
+ var pr = scheduler._props[name];
+ var order = pr.order = {};
+ var list = pr.options;
+ for(var i=0; i<list.length;i++)
+ order[list[i].key]=i;
+ if(pr._original_size && pr.size==0){
+ pr.size = pr._original_size;
+ delete pr.original_size;
+ }
+ if(pr.size > list.length) {
+ pr._original_size = pr.size;
+ pr.size = 0;
+ }
+ else
+ pr.size = pr._original_size||pr.size;
+ if (scheduler._date && scheduler._mode == name)
+ scheduler.setCurrentView(scheduler._date, scheduler._mode);
+ });
+ scheduler.callEvent("onOptionsLoad",[]);
+};
+scheduler.scrollUnit=function(step){
+ var pr = scheduler._props[this._mode];
+ if (pr){
+ pr.position=Math.min(Math.max(0,pr.position+step),pr.options.length-pr.size);
+ this.update_view();
+ }
+};
+(function(){
+ var _removeIncorrectEvents = function(evs) {
+ var pr = scheduler._props[scheduler._mode];
+ if(pr && pr.order && pr.skip_incorrect) {
+ var correct_events = [];
+ for(var i=0; i<evs.length; i++) {
+ if(typeof pr.order[evs[i][pr.map_to]] != "undefined") {
+ correct_events.push(evs[i]);
+ }
+ }
+ evs.splice(0,evs.length);
+ evs.push.apply(evs,correct_events);
+ }
+ return evs;
+ };
+ var old_pre_render_events_table = scheduler._pre_render_events_table;
+ scheduler._pre_render_events_table=function(evs,hold) {
+ evs = _removeIncorrectEvents(evs);
+ return old_pre_render_events_table.apply(this, [evs, hold]);
+ };
+ var old_pre_render_events_line = scheduler._pre_render_events_line;
+ scheduler._pre_render_events_line = function(evs,hold){
+ evs = _removeIncorrectEvents(evs);
+ return old_pre_render_events_line.apply(this, [evs, hold]);
+ };
+ var fix_und=function(pr,ev){
+ if (pr && typeof pr.order[ev[pr.map_to]] == "undefined"){
+ var s = scheduler;
+ var dx = 24*60*60*1000;
+ var ind = Math.floor((ev.end_date - s._min_date)/dx);
+ //ev.end_date = new Date(s.date.time_part(ev.end_date)*1000+s._min_date.valueOf());
+ //ev.start_date = new Date(s.date.time_part(ev.start_date)*1000+s._min_date.valueOf());
+ ev[pr.map_to] = pr.options[Math.min(ind+pr.position,pr.options.length-1)].key;
+ return true;
+ }
+ };
+ var t = scheduler._reset_scale;
+
+ var oldive = scheduler.is_visible_events;
+ scheduler.is_visible_events = function(e){
+ var res = oldive.apply(this,arguments);
+ if (res){
+ var pr = scheduler._props[this._mode];
+ if (pr && pr.size){
+ var val = pr.order[e[pr.map_to]];
+ if (val < pr.position || val >= pr.size+pr.position )
+ return false;
+ }
+ }
+ return res;
+ };
+ scheduler._reset_scale = function(){
+ var pr = scheduler._props[this._mode];
+ var ret = t.apply(this,arguments);
+ if (pr){
+ this._max_date=this.date.add(this._min_date,1,"day");
+
+ var d = this._els["dhx_cal_data"][0].childNodes;
+ for (var i=0; i < d.length; i++)
+ d[i].className = d[i].className.replace("_now",""); //clear now class
+
+ if (pr.size && pr.size < pr.options.length){
+
+ var h = this._els["dhx_cal_header"][0];
+ var arrow = document.createElement("DIV");
+ if (pr.position){
+ arrow.className = "dhx_cal_prev_button";
+ arrow.style.cssText="left:1px;top:2px;position:absolute;"
+ arrow.innerHTML = "&nbsp;"
+ h.firstChild.appendChild(arrow);
+ arrow.onclick=function(){
+ scheduler.scrollUnit(pr.step*-1);
+ }
+ }
+ if (pr.position+pr.size<pr.options.length){
+ arrow = document.createElement("DIV");
+ arrow.className = "dhx_cal_next_button";
+ arrow.style.cssText="left:auto; right:0px;top:2px;position:absolute;"
+ arrow.innerHTML = "&nbsp;"
+ h.lastChild.appendChild(arrow);
+ arrow.onclick=function(){
+ scheduler.scrollUnit(pr.step);
+ }
+ }
+ }
+ }
+ return ret;
+
+ };
+ var r = scheduler._get_event_sday;
+ scheduler._get_event_sday=function(ev){
+ var pr = scheduler._props[this._mode];
+ if (pr){
+ fix_und(pr,ev);
+ return pr.order[ev[pr.map_to]]-pr.position;
+ }
+ return r.call(this,ev);
+ };
+ var l = scheduler.locate_holder_day;
+ scheduler.locate_holder_day=function(a,b,ev){
+ var pr = scheduler._props[this._mode];
+ if (pr && ev) {
+ fix_und(pr,ev);
+ return pr.order[ev[pr.map_to]]*1+(b?1:0)-pr.position;
+ }
+ return l.apply(this,arguments);
+ };
+ var p = scheduler._mouse_coords;
+ scheduler._mouse_coords=function(){
+ var pr = scheduler._props[this._mode];
+ var pos=p.apply(this,arguments);
+ if (pr){
+ if(!this._drag_event) this._drag_event = {};
+ var ev = this._drag_event;
+ if (this._drag_id && this._drag_mode){
+ ev = this.getEvent(this._drag_id);
+ this._drag_event._dhx_changed = true;
+ }
+ var unit_ind = Math.min(pos.x+pr.position,pr.options.length-1);
+ var key = pr.map_to;
+ pos.section = ev[key]=(pr.options[unit_ind]||{}).key;
+ pos.x = 0;
+ }
+ pos.force_redraw = true;
+ return pos;
+ };
+ var o = scheduler._time_order;
+ scheduler._time_order = function(evs){
+ var pr = scheduler._props[this._mode];
+ if (pr){
+ evs.sort(function(a,b){
+ return pr.order[a[pr.map_to]]>pr.order[b[pr.map_to]]?1:-1;
+ });
+ } else
+ o.apply(this,arguments);
+ };
+ scheduler.attachEvent("onEventAdded",function(id,ev){
+ if (this._loading) return true;
+ for (var a in scheduler._props){
+ var pr = scheduler._props[a];
+ if (typeof ev[pr.map_to] == "undefined")
+ ev[pr.map_to] = pr.options[0].key;
+ }
+ return true;
+ });
+ scheduler.attachEvent("onEventCreated",function(id,n_ev){
+ var pr = scheduler._props[this._mode];
+ if (pr && n_ev){
+ var ev = this.getEvent(id);
+ this._mouse_coords(n_ev);
+ fix_und(pr,ev);
+ this.event_updated(ev);
+ }
+ return true;
+ })
+})();
diff --git a/sources/ext/dhtmlxscheduler_url.js b/sources/ext/dhtmlxscheduler_url.js
new file mode 100644
index 0000000..2835341
--- /dev/null
+++ b/sources/ext/dhtmlxscheduler_url.js
@@ -0,0 +1,34 @@
+/*
+This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
+to use it in non-GPL project. Please contact sales@dhtmlx.com for details
+*/
+scheduler.attachEvent("onTemplatesReady",function(){
+ var first = true;
+ var s2d = scheduler.date.str_to_date("%Y-%m-%d");
+ var d2s = scheduler.date.date_to_str("%Y-%m-%d");
+ scheduler.attachEvent("onBeforeViewChange",function(om,od,m,d){
+ if (first){
+ first = false;
+ var p={};
+ var data=(document.location.hash||"").replace("#","").split(",");
+ for (var i=0; i < data.length; i++) {
+ var s = data[i].split("=");
+ if (s.length==2)
+ p[s[0]]=s[1];
+ }
+
+ if (p.date || p.mode){
+ try{
+ this.setCurrentView((p.date?s2d(p.date):null),(p.mode||null));
+ } catch(e){
+ //assuming that mode is not available anymore
+ this.setCurrentView((p.date?s2d(p.date):null),m);
+ }
+ return false;
+ }
+ }
+ var text = "#date="+d2s(d||od)+",mode="+(m||om);
+ document.location.hash = text;
+ return true;
+ });
+}); \ No newline at end of file
diff --git a/sources/ext/dhtmlxscheduler_week_agenda.js b/sources/ext/dhtmlxscheduler_week_agenda.js
new file mode 100644
index 0000000..cee9d7c
--- /dev/null
+++ b/sources/ext/dhtmlxscheduler_week_agenda.js
@@ -0,0 +1,256 @@
+/*
+This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
+to use it in non-GPL project. Please contact sales@dhtmlx.com for details
+*/
+scheduler._wa = {};
+scheduler.xy.week_agenda_scale_height = 20;
+scheduler.templates.week_agenda_event_text = function(start_date, end_date, event, date) {
+ return scheduler.templates.event_date(start_date) + " " + event.text;
+};
+scheduler.date.week_agenda_start = scheduler.date.week_start;
+scheduler.date.week_agenda_end = function(date) {
+ return scheduler.date.add(date, 7, "day");
+};
+scheduler.date.add_week_agenda = function(date, inc) {
+ return scheduler.date.add(date, inc * 7, "day");
+};
+
+scheduler.attachEvent("onSchedulerReady", function() {
+ var t = scheduler.templates;
+ if (!t.week_agenda_date)
+ t.week_agenda_date = t.week_date;
+});
+
+(function() {
+ var scale_date_format = scheduler.date.date_to_str("%l, %F %d");
+ scheduler.templates.week_agenda_scale_date = function(date) {
+ return scale_date_format(date);
+ };
+})();
+
+scheduler.attachEvent("onTemplatesReady", function() {
+
+ scheduler.attachEvent("onSchedulerResize", function() {
+ if (this._mode == "week_agenda") {
+ this.week_agenda_view(true);
+ return false;
+ }
+ return true;
+ });
+
+ var old = scheduler.render_data;
+ scheduler.render_data = function(evs) {
+ if (this._mode == "week_agenda") {
+ scheduler.week_agenda_view(true);
+ } else
+ return old.apply(this, arguments);
+ };
+
+ var getColumnSizes = function() {
+ // widths
+ scheduler._cols = [];
+ var twidth = parseInt(scheduler._els['dhx_cal_data'][0].style.width);
+ scheduler._cols.push(Math.floor(twidth / 2));
+ scheduler._cols.push(twidth - scheduler._cols[0] - 1); // To add border between columns
+
+ // heights
+ scheduler._colsS = {
+ 0: [],
+ 1: []
+ };
+ var theight = parseInt(scheduler._els['dhx_cal_data'][0].style.height);
+ for (var i = 0; i < 3; i++) {
+ scheduler._colsS[0].push(Math.floor(theight / (3 - scheduler._colsS[0].length)));
+ theight -= scheduler._colsS[0][i];
+ }
+ scheduler._colsS[1].push(scheduler._colsS[0][0]);
+ scheduler._colsS[1].push(scheduler._colsS[0][1]);
+ // last two days
+ theight = scheduler._colsS[0][scheduler._colsS[0].length - 1];
+ scheduler._colsS[1].push(Math.floor(theight / 2));
+ scheduler._colsS[1].push(theight - scheduler._colsS[1][scheduler._colsS[1].length - 1]);
+ };
+ var fillWeekAgendaTab = function() {
+ getColumnSizes();
+ scheduler._els["dhx_cal_data"][0].innerHTML = '';
+ scheduler._rendered = [];
+ var html = '';
+ for (var i = 0; i < 2; i++) {
+ var width = scheduler._cols[i];
+ var column_css = 'dhx_wa_column';
+ if (i == 1)
+ column_css += ' dhx_wa_column_last';
+ html += "<div class='" + column_css + "' style='width: " + width + "px;'>";
+ for (var k = 0; k < scheduler._colsS[i].length; k++) {
+ var scale_height = scheduler.xy.week_agenda_scale_height - 2;
+ var height = scheduler._colsS[i][k] - scale_height - 2;
+ var day = Math.min(6, k * 2 + i);
+ html += "<div class='dhx_wa_day_cont'><div style='height:" + scale_height + "px; line-height:" + scale_height + "px;' class='dhx_wa_scale_bar'></div><div style='height:" + height + "px;' class='dhx_wa_day_data' day='" + day + "'></div></div>";
+ }
+ html += "</div>";
+ }
+ scheduler._els["dhx_cal_date"][0].innerHTML = scheduler.templates[scheduler._mode + "_date"](scheduler._min_date, scheduler._max_date, scheduler._mode);
+ scheduler._els["dhx_cal_data"][0].innerHTML = html;
+ var all_divs = scheduler._els["dhx_cal_data"][0].getElementsByTagName('div');
+ var day_divs = [];
+ for (var i = 0; i < all_divs.length; i++) {
+ if (all_divs[i].className == 'dhx_wa_day_cont')
+ day_divs.push(all_divs[i]);
+ }
+ scheduler._wa._selected_divs = [];
+ var events = scheduler.get_visible_events(); // list of events to be displayed in current week
+ var tstart = scheduler.date.week_start(scheduler._date);
+ var tend = scheduler.date.add(tstart, 1, "day");
+ for (var i = 0; i < 7; i++) {
+ day_divs[i]._date = tstart;
+ var scale_bar = day_divs[i].childNodes[0];
+ var events_div = day_divs[i].childNodes[1];
+ scale_bar.innerHTML = scheduler.templates.week_agenda_scale_date(tstart);
+ var evs = []; // events which will be displayed in the current day
+ for (var j = 0; j < events.length; j++) {
+ var tev = events[j];
+ if (tev.start_date < tend && tev.end_date > tstart)
+ evs.push(tev);
+ }
+ evs.sort(function(a, b) {
+ if (a.start_date.valueOf() == b.start_date.valueOf())
+ return a.id > b.id ? 1 : -1;
+ return a.start_date > b.start_date ? 1 : -1;
+ });
+ for (var k = 0; k < evs.length; k++) {
+ var ev = evs[k];
+ var ev_div = document.createElement('div');
+ scheduler._rendered.push(ev_div);
+ var ev_class = scheduler.templates.event_class(ev.start_date, ev.end_date, ev);
+ ev_div.className = 'dhx_wa_ev_body' + (ev_class ? (' ' + ev_class) : '');
+ if (ev._text_style)
+ ev_div.style.cssText = ev._text_style;
+ if (ev.color)
+ ev_div.style.background = ev.color;
+ if (ev.textColor)
+ ev_div.style.color = ev.textColor;
+ if (scheduler._select_id && ev.id == scheduler._select_id && !(!scheduler.config.week_agenda_select && scheduler.config.week_agenda_select !== undefined)) {
+ ev_div.className += " dhx_cal_event_selected";
+ scheduler._wa._selected_divs.push(ev_div);
+ }
+ var position = "";
+ if (!ev._timed) {
+ position = "middle";
+ if (ev.start_date.valueOf() >= tstart.valueOf() && ev.start_date.valueOf() <= tend.valueOf())
+ position = "start";
+ if (ev.end_date.valueOf() >= tstart.valueOf() && ev.end_date.valueOf() <= tend.valueOf())
+ position = "end";
+ }
+ ev_div.innerHTML = scheduler.templates.week_agenda_event_text(ev.start_date, ev.end_date, ev, tstart, position);
+ ev_div.setAttribute('event_id', ev.id);
+ events_div.appendChild(ev_div);
+ }
+ tstart = scheduler.date.add(tstart, 1, "day");
+ tend = scheduler.date.add(tend, 1, "day");
+ }
+ };
+ scheduler.week_agenda_view = function(mode) {
+ scheduler._min_date = scheduler.date.week_start(scheduler._date);
+ scheduler._max_date = scheduler.date.add(scheduler._min_date, 1, "week");
+ scheduler.set_sizes();
+ if (mode) { // mode enabled
+ scheduler._table_view = scheduler._allow_dnd = true;
+
+ // hiding default top border from dhx_cal_data
+ scheduler._wa._prev_data_border = scheduler._els['dhx_cal_data'][0].style.borderTop;
+ scheduler._els['dhx_cal_data'][0].style.borderTop = 0;
+ scheduler._els['dhx_cal_data'][0].style.overflowY = 'hidden';
+
+ // cleaning dhx_cal_date from the previous date
+ scheduler._els['dhx_cal_date'][0].innerHTML = "";
+
+ // 1 to make navline to be over data
+ scheduler._els['dhx_cal_data'][0].style.top = (parseInt(scheduler._els['dhx_cal_data'][0].style.top) - 20 - 1) + 'px';
+ scheduler._els['dhx_cal_data'][0].style.height = (parseInt(scheduler._els['dhx_cal_data'][0].style.height) + 20 + 1) + 'px';
+
+ scheduler._els['dhx_cal_header'][0].style.display = 'none';
+ fillWeekAgendaTab();
+ } else { // leaving week_agenda mode
+ scheduler._table_view = scheduler._allow_dnd = false;
+
+ // restoring default top border to dhx_cal_data
+ if (scheduler._wa._prev_data_border)
+ scheduler._els['dhx_cal_data'][0].style.borderTop = scheduler._wa._prev_data_border;
+
+ scheduler._els['dhx_cal_data'][0].style.overflowY = 'auto';
+ scheduler._els['dhx_cal_data'][0].style.top = (parseInt(scheduler._els['dhx_cal_data'][0].style.top) + 20) + 'px';
+ scheduler._els['dhx_cal_data'][0].style.height = (parseInt(scheduler._els['dhx_cal_data'][0].style.height) - 20) + 'px';
+ scheduler._els['dhx_cal_header'][0].style.display = 'block';
+ }
+ };
+ scheduler.mouse_week_agenda = function(pos) {
+ var native_event = pos.ev;
+ var src = native_event.srcElement || native_event.target;
+ while (src.parentNode) {
+ if (src._date)
+ var date = src._date;
+ src = src.parentNode;
+ }
+ if (!date)
+ return pos;
+ pos.x = 0;
+ var diff = date.valueOf() - scheduler._min_date.valueOf();
+ pos.y = Math.ceil(( diff / (1000 * 60) ) / this.config.time_step);
+ if (this._drag_mode == 'move') {
+ this._drag_event._dhx_changed = true;
+ this._select_id = this._drag_id;
+ for (var i = 0; i < scheduler._rendered.length; i++) {
+ if (scheduler._drag_id == this._rendered[i].getAttribute('event_id'))
+ var event_div = this._rendered[i];
+ }
+ if (!scheduler._wa._dnd) {
+ var div = event_div.cloneNode(true);
+ this._wa._dnd = div;
+ div.className = event_div.className;
+ div.id = 'dhx_wa_dnd';
+ div.className += ' dhx_wa_dnd';
+ document.body.appendChild(div);
+ }
+ var dnd_div = document.getElementById('dhx_wa_dnd');
+ dnd_div.style.top = ((native_event.pageY || native_event.clientY) + 20) + "px";
+ dnd_div.style.left = ((native_event.pageX || native_event.clientX) + 20) + "px";
+ }
+ return pos;
+ };
+ scheduler.attachEvent('onBeforeEventChanged', function(event_object, native_event, is_new) {
+ if (this._mode == 'week_agenda') {
+ if (this._drag_mode == 'move') {
+ var dnd = document.getElementById('dhx_wa_dnd');
+ dnd.parentNode.removeChild(dnd);
+ scheduler._wa._dnd = false;
+ }
+ }
+ return true;
+ });
+
+ scheduler.attachEvent("onEventSave", function(id, data, is_new_event) {
+ if (is_new_event && this._mode == 'week_agenda')
+ this._select_id = id;
+ return true;
+ });
+
+ scheduler._wa._selected_divs = [];
+
+ scheduler.attachEvent("onClick", function(event_id, native_event_object) {
+ if (this._mode == 'week_agenda' && !(!scheduler.config.week_agenda_select && scheduler.config.week_agenda_select !== undefined)) {
+ if (scheduler._wa._selected_divs) {
+ for (var i = 0; i < this._wa._selected_divs.length; i++) {
+ var div = this._wa._selected_divs[i];
+ div.className = div.className.replace(/ dhx_cal_event_selected/, '');
+ }
+ }
+ this.for_rendered(event_id, function(event_div) {
+ event_div.className += " dhx_cal_event_selected";
+ scheduler._wa._selected_divs.push(event_div);
+ });
+ scheduler.select(event_id);
+ return false;
+ }
+ return true;
+ });
+});
diff --git a/sources/ext/dhtmlxscheduler_year_view.js b/sources/ext/dhtmlxscheduler_year_view.js
new file mode 100644
index 0000000..c0ffe74
--- /dev/null
+++ b/sources/ext/dhtmlxscheduler_year_view.js
@@ -0,0 +1,373 @@
+/*
+This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
+to use it in non-GPL project. Please contact sales@dhtmlx.com for details
+*/
+scheduler.config.year_x = 4;
+scheduler.config.year_y = 3;
+scheduler.xy.year_top = 0;
+
+scheduler.templates.year_date = function(date) {
+ return scheduler.date.date_to_str(scheduler.locale.labels.year_tab + " %Y")(date);
+};
+scheduler.templates.year_month = scheduler.date.date_to_str("%F");
+scheduler.templates.year_scale_date = scheduler.date.date_to_str("%D");
+scheduler.templates.year_tooltip = function(s, e, ev) {
+ return ev.text
+};
+
+(function() {
+ var is_year_mode = function() {
+ return scheduler._mode == "year";
+ };
+
+ scheduler.dblclick_dhx_month_head = function(e) {
+ if (is_year_mode()) {
+ var t = (e.target || e.srcElement);
+ if (t.parentNode.className.indexOf("dhx_before") != -1 || t.parentNode.className.indexOf("dhx_after") != -1) return false;
+ var start = this.templates.xml_date(t.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.getAttribute("date"));
+ start.setDate(parseInt(t.innerHTML, 10));
+ var end = this.date.add(start, 1, "day")
+ if (!this.config.readonly && this.config.dblclick_create)
+ this.addEventNow(start.valueOf(), end.valueOf(), e);
+ }
+ };
+
+ var chid = scheduler.changeEventId;
+ scheduler.changeEventId = function() {
+ chid.apply(this, arguments);
+ if (is_year_mode())
+ this.year_view(true);
+ };
+
+
+ var old = scheduler.render_data;
+ var to_attr = scheduler.date.date_to_str("%Y/%m/%d");
+ var from_attr = scheduler.date.str_to_date("%Y/%m/%d");
+ scheduler.render_data = function(evs) {
+ if (!is_year_mode()) return old.apply(this, arguments);
+ for (var i = 0; i < evs.length; i++)
+ this._year_render_event(evs[i]);
+ };
+
+ var clear = scheduler.clear_view;
+ scheduler.clear_view = function() {
+ if (!is_year_mode()) return clear.apply(this, arguments);
+ for (var date in marked) {
+ if (marked.hasOwnProperty(date)) {
+ var div = marked[date];
+ div.className = "dhx_month_head";
+ div.setAttribute("date", "")
+ }
+ }
+ marked = {};
+ };
+
+ scheduler._hideToolTip = function() {
+ if (this._tooltip) {
+ this._tooltip.style.display = "none";
+ this._tooltip.date = new Date(9999, 1, 1);
+ }
+ };
+
+ scheduler._showToolTip = function(date, pos, e, src) {
+ if (this._tooltip) {
+ if (this._tooltip.date.valueOf() == date.valueOf()) return;
+ this._tooltip.innerHTML = "";
+ } else {
+ var t = this._tooltip = document.createElement("DIV");
+ t.className = "dhx_year_tooltip";
+ document.body.appendChild(t);
+ t.onclick = scheduler._click.dhx_cal_data;
+ }
+ var evs = this.getEvents(date, this.date.add(date, 1, "day"));
+ var html = "";
+
+ for (var i = 0; i < evs.length; i++) {
+ var ev = evs[i];
+ var bg_color = (ev.color ? ("background:" + ev.color + ";") : "");
+ var color = (ev.textColor ? ("color:" + ev.textColor + ";") : "");
+
+ html += "<div class='dhx_tooltip_line' style='" + bg_color + "" + color + "' event_id='" + evs[i].id + "'>";
+ html += "<div class='dhx_tooltip_date' style='" + bg_color + "" + color + "'>" + (evs[i]._timed ? this.templates.event_date(evs[i].start_date) : "") + "</div>";
+ html += "<div class='dhx_event_icon icon_details'>&nbsp;</div>";
+ html += this.templates.year_tooltip(evs[i].start_date, evs[i].end_date, evs[i]) + "</div>";
+ }
+
+ this._tooltip.style.display = "";
+ this._tooltip.style.top = "0px";
+
+
+ if (document.body.offsetWidth - pos.left - this._tooltip.offsetWidth < 0)
+ this._tooltip.style.left = pos.left - this._tooltip.offsetWidth + "px";
+ else
+ this._tooltip.style.left = pos.left + src.offsetWidth + "px";
+
+ this._tooltip.date = date;
+ this._tooltip.innerHTML = html;
+
+ if (document.body.offsetHeight - pos.top - this._tooltip.offsetHeight < 0)
+ this._tooltip.style.top = pos.top - this._tooltip.offsetHeight + src.offsetHeight + "px";
+ else
+ this._tooltip.style.top = pos.top + "px";
+ };
+
+ scheduler._init_year_tooltip = function() {
+ dhtmlxEvent(scheduler._els["dhx_cal_data"][0], "mouseover", function(e) {
+ if (!is_year_mode()) return;
+
+ var e = e || event;
+ var src = e.target || e.srcElement;
+ if (src.tagName.toLowerCase() == 'a') // fix for active links extension (it adds links to the date in the cell)
+ src = src.parentNode;
+ if ((src.className || "").indexOf("dhx_year_event") != -1)
+ scheduler._showToolTip(from_attr(src.getAttribute("date")), getOffset(src), e, src);
+ else
+ scheduler._hideToolTip();
+ });
+ this._init_year_tooltip = function() {
+ };
+ };
+
+ scheduler.attachEvent("onSchedulerResize", function() {
+ if (is_year_mode()) {
+ this.year_view(true);
+ return false;
+ }
+ return true;
+ });
+ scheduler._get_year_cell = function(d) {
+ //there can be more than 1 year in view
+ //year can start not from January
+ var m = d.getMonth() + 12 * (d.getFullYear() - this._min_date.getFullYear()) - this.week_starts._month;
+ var t = this._els["dhx_cal_data"][0].childNodes[m];
+ var d = this.week_starts[m] + d.getDate() - 1;
+
+
+ return t.childNodes[2].firstChild.rows[Math.floor(d / 7)].cells[d % 7].firstChild;
+ };
+
+ var marked = {};
+ scheduler._mark_year_date = function(d, ev) {
+ var date = to_attr(d);
+ var c = this._get_year_cell(d);
+ var ev_class = this.templates.event_class(ev.start_date, ev.end_date, ev);
+ if (!marked[date]) {
+ c.className = "dhx_month_head dhx_year_event";
+ c.setAttribute("date", date);
+ marked[date] = c;
+ }
+ c.className += (ev_class) ? (" "+ev_class) : "";
+ };
+ scheduler._unmark_year_date = function(d) {
+ this._get_year_cell(d).className = "dhx_month_head";
+ };
+ scheduler._year_render_event = function(ev) {
+ var d = ev.start_date;
+ if (d.valueOf() < this._min_date.valueOf())
+ d = this._min_date;
+ else d = this.date.date_part(new Date(d));
+
+ while (d < ev.end_date) {
+ this._mark_year_date(d, ev);
+ d = this.date.add(d, 1, "day");
+ if (d.valueOf() >= this._max_date.valueOf())
+ return;
+ }
+ };
+
+ scheduler.year_view = function(mode) {
+ if (mode) {
+ var temp = scheduler.xy.scale_height;
+ scheduler.xy.scale_height = -1;
+ }
+
+ scheduler._els["dhx_cal_header"][0].style.display = mode ? "none" : "";
+ scheduler.set_sizes();
+
+ if (mode)
+ scheduler.xy.scale_height = temp;
+
+
+ scheduler._table_view = mode;
+ if (this._load_mode && this._load()) return;
+
+ if (mode) {
+ scheduler._init_year_tooltip();
+ scheduler._reset_year_scale();
+ if (scheduler._load_mode && scheduler._load()) return scheduler._render_wait = true;
+ scheduler.render_view_data();
+ } else {
+ scheduler._hideToolTip();
+ }
+ };
+ scheduler._reset_year_scale = function() {
+ this._cols = [];
+ this._colsS = {};
+ var week_starts = []; //start day of first week in each month
+ var b = this._els["dhx_cal_data"][0];
+
+ var c = this.config;
+ b.scrollTop = 0; //fix flickering in FF
+ b.innerHTML = "";
+
+ var dx = Math.floor(parseInt(b.style.width) / c.year_x);
+ var dy = Math.floor((parseInt(b.style.height) - scheduler.xy.year_top) / c.year_y);
+ if (dy < 190) {
+ dy = 190;
+ dx = Math.floor((parseInt(b.style.width) - scheduler.xy.scroll_width) / c.year_x);
+ }
+
+ var summ = dx - 11;
+ var left = 0;
+ var week_template = document.createElement("div");
+ var dummy_date = this.date.week_start(scheduler._currentDate());
+ for (var i = 0; i < 7; i++) {
+ this._cols[i] = Math.floor(summ / (7 - i));
+ this._render_x_header(i, left, dummy_date, week_template);
+ dummy_date = this.date.add(dummy_date, 1, "day");
+ summ -= this._cols[i];
+ left += this._cols[i];
+ }
+ week_template.lastChild.className += " dhx_scale_bar_last";
+
+ var sd = this.date[this._mode + "_start"](this.date.copy(this._date));
+ var ssd = sd;
+
+ for (var i = 0; i < c.year_y; i++)
+ for (var j = 0; j < c.year_x; j++) {
+ var d = document.createElement("DIV");
+ d.style.cssText = "position:absolute;";
+ d.setAttribute("date", this.templates.xml_format(sd));
+ d.innerHTML = "<div class='dhx_year_month'></div><div class='dhx_year_week'>" + week_template.innerHTML + "</div><div class='dhx_year_body'></div>";
+ d.childNodes[0].innerHTML = this.templates.year_month(sd);
+
+ var dd = this.date.week_start(sd);
+ var ed = this._reset_month_scale(d.childNodes[2], sd, dd);
+
+ var r = d.childNodes[2].firstChild.rows;
+ for (var k=r.length; k<6; k++) {
+ r[0].parentNode.appendChild(r[0].cloneNode(true));
+ for (var ri=0; ri < r[k].childNodes.length; ri++) {
+ r[k].childNodes[ri].className = "dhx_after";
+ r[k].childNodes[ri].firstChild.innerHTML = scheduler.templates.month_day(ed);
+ ed = scheduler.date.add(ed,1,"day");
+ }
+ }
+ b.appendChild(d);
+
+ d.childNodes[1].style.height = d.childNodes[1].childNodes[0].offsetHeight + "px"; // dhx_year_week should have height property so that day dates would get correct position. dhx_year_week height = height of it's child (with the day name)
+ var dt = Math.round((dy - 190) / 2);
+ d.style.marginTop = dt + "px";
+ this.set_xy(d, dx - 10, dy - dt - 10, dx * j + 5, dy * i + 5 + scheduler.xy.year_top);
+
+ week_starts[i * c.year_x + j] = (sd.getDay() - (this.config.start_on_monday ? 1 : 0) + 7) % 7;
+ sd = this.date.add(sd, 1, "month");
+
+ }
+ this._els["dhx_cal_date"][0].innerHTML = this.templates[this._mode + "_date"](ssd, sd, this._mode);
+ this.week_starts = week_starts;
+ week_starts._month = ssd.getMonth();
+ this._min_date = ssd;
+ this._max_date = sd;
+ };
+
+ var getActionData = scheduler.getActionData;
+ scheduler.getActionData = function(n_ev) {
+ if(!is_year_mode())
+ return getActionData.apply(scheduler, arguments);
+
+ var trg = n_ev?n_ev.target:event.srcElement;
+ var date = getMonthDate(trg);
+
+ var day = getMonthCell(trg);
+ var pos = getDayIndexes(day);
+
+ if(pos && date){
+ date = scheduler.date.add(date, pos.week, "week");
+ date = scheduler.date.add(date, pos.day, "day");
+ }else{
+ date = null;
+ }
+
+ return {
+ date:date,
+ section:null
+ };
+
+
+
+
+ function getMonthDate(node){
+ var node = getMonthRoot(node);
+ if(!node)
+ return null;
+
+ var date = node.getAttribute("date");
+ if(!date)
+ return null;
+
+ return scheduler.date.week_start(scheduler.templates.xml_date(date));
+ }
+ function getDayIndexes(targetCell){
+ var month = getMonthTable(targetCell);
+ if(!month)
+ return null;
+
+ var week = 0, day = 0;
+ for(var week = 0, weeks = month.rows.length; week < weeks;week ++){
+ var w = month.rows[week].getElementsByTagName("td");
+ for(var day = 0, days = w.length; day < days; day++){
+ if(w[day] == targetCell)
+ break;
+ }
+ if(day < days)
+ break;
+ }
+
+ if(week < weeks)
+ return {day:day, week:week};
+ else
+ return null;
+ }
+
+ };
+
+ var locateEvent = scheduler._locate_event;
+ scheduler._locate_event = function(node) {
+ if(!is_year_mode())
+ return locateEvent.apply(scheduler, arguments);
+
+ var day = getNode(node, function(n){
+ return n.className && n.className.indexOf("dhx_year_event") != -1 && n.hasAttribute && n.hasAttribute("date")
+ });
+
+ if(!day || !day.hasAttribute("date")) return null;
+
+ var dat = scheduler.templates.xml_date(day.getAttribute("date"));
+ var evs = scheduler.getEvents(dat, scheduler.date.add(dat, 1, "day"));
+ if(!evs.length) return null;
+
+ //can be multiple events in the cell, return any single one
+ return evs[0].id;
+ };
+
+
+ function getMonthCell(node){
+ return getNode(node, function(n){ return n.nodeName.toLowerCase() == "td" });
+ }
+
+ function getMonthTable(node){
+ return getNode(node, function(n){ return n.nodeName.toLowerCase() == "table" });
+ }
+ function getMonthRoot(node){
+ var node = getMonthTable(node);
+ return getNode(node, function(n){ return n.hasAttribute && n.hasAttribute("date")});
+ }
+ function getNode(node, condition){
+ while(node && !condition(node)){
+ node = node.parentNode;
+ }
+ return node;
+ }
+
+})();