diff options
Diffstat (limited to 'sources/ext')
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)+" – "+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'> </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)+" – "+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'> </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) + " – " + 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'> </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 = [" ", " "]; +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;'> – </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 + " </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)+" – "+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'> </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 = " " + 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 = " " + 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'> </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; + } + +})(); |