/* @license dhtmlxScheduler v.4.4.0 Stardard This software is covered by GPL license. You also can obtain Commercial or Enterprise license to use it in non-GPL project - please contact sales@dhtmlx.com. Usage without proper license is prohibited. (c) Dinamenta, UAB. */ (function(){ function setupKeyNav(scheduler){ scheduler.config.key_nav = true; scheduler.config.key_nav_step = 30; scheduler.addShortcut = function(shortcut, handler, scope){ var scopeObject = getScope(scope); if(scopeObject){ scopeObject.prototype.bind(shortcut, handler); } }; scheduler.removeShortcut = function(shortcut, scope){ var scopeObject = getScope(scope); if(scopeObject){ scopeObject.prototype.unbind(shortcut); } }; scheduler.focus = function(){ if(!scheduler.config.key_nav){ return; } var disp = scheduler.$keyboardNavigation.dispatcher; disp.enable(); var activeNode = disp.getActiveNode(); if(!activeNode || activeNode instanceof scheduler.$keyboardNavigation.MinicalButton || activeNode instanceof scheduler.$keyboardNavigation.MinicalCell){ disp.setDefaultNode(); }else{ disp.focusNode(disp.getActiveNode()); } }; function getScope(mode){ var scopes = { "minicalButton":scheduler.$keyboardNavigation.MinicalButton, "minicalDate":scheduler.$keyboardNavigation.MinicalCell, "scheduler":scheduler.$keyboardNavigation.SchedulerNode, "dataArea": scheduler.$keyboardNavigation.DataArea, "timeSlot": scheduler.$keyboardNavigation.TimeSlot, "event": scheduler.$keyboardNavigation.Event }; return scopes[mode] || scopes.scheduler; } scheduler.$keyboardNavigation = {}; scheduler._compose = function(){ var parts = Array.prototype.slice.call(arguments, 0); var res = {}; for(var i = 0; i < parts.length; i++){ var obj = parts[i]; if(typeof obj == "function"){ obj = new obj(); } for(var p in obj){ res[p] = obj[p]; } } return res; }; scheduler.$keyboardNavigation.shortcuts = { createCommand: function(){ return { modifiers:{ "shift": false, "alt": false, "ctrl": false, "meta": false }, keyCode: null }; }, parse: function(shortcut){ var commands = []; var expr = this.getExpressions(this.trim(shortcut)); for(var i = 0; i < expr.length; i++){ var words = this.getWords(expr[i]); var command = this.createCommand(); for(var j = 0; j < words.length; j++){ if(this.commandKeys[words[j]]){ command.modifiers[words[j]] = true; }else if(this.specialKeys[words[j]]){ command.keyCode = this.specialKeys[words[j]]; }else{ command.keyCode = words[j].charCodeAt(0); } } commands.push(command); } return commands; }, getCommandFromEvent: function(domEvent){ var command = this.createCommand(); command.modifiers.shift = !!domEvent.shiftKey; command.modifiers.alt = !!domEvent.altKey; command.modifiers.ctrl = !!domEvent.ctrlKey; command.modifiers.meta = !!domEvent.metaKey; command.keyCode = domEvent.which || domEvent.keyCode; var printableKey = String.fromCharCode(command.keyCode ); if(printableKey){ command.keyCode = printableKey.toLowerCase().charCodeAt(0); } return command; }, getHashFromEvent: function(domEvent){ return this.getHash(this.getCommandFromEvent(domEvent)); }, getHash: function(command){ var parts = []; for(var i in command.modifiers){ if(command.modifiers[i]){ parts.push(i); } } parts.push(command.keyCode); return parts.join(this.junctionChar); }, getExpressions: function(shortcut){ return shortcut.split(this.junctionChar); }, getWords: function(term){ return term.split(this.combinationChar); }, trim: function(shortcut){ return shortcut.replace(/\s/g, ""); }, junctionChar:",", combinationChar:"+", commandKeys:{ "shift": 16, "alt": 18, "ctrl": 17, "meta": true }, specialKeys:{ "backspace": 8, "tab": 9, "enter": 13, "esc": 27, "space": 32, "up": 38, "down": 40, "left": 37, "right": 39, "home": 36, "end": 35, "pageup": 33, "pagedown": 34, "delete": 46, "insert": 45, "plus":107, "f1": 112, "f2": 113, "f3": 114, "f4": 115, "f5": 116, "f6": 117, "f7": 118, "f8": 119, "f9": 120, "f10": 121, "f11": 122, "f12": 123 } }; scheduler.$keyboardNavigation.EventHandler = { _handlers: null, findHandler: function(command){ if(!this._handlers) this._handlers = {}; var shortcuts = scheduler.$keyboardNavigation.shortcuts; var hash = shortcuts.getHash(command); return this._handlers[hash]; }, doAction: function(command, e){ var handler = this.findHandler(command); if(handler){ handler.call(this, e); if (e.preventDefault) e.preventDefault(); else e.returnValue = false; } }, bind: function(shortcut, handler){ if(!this._handlers) this._handlers = {}; var shortcuts = scheduler.$keyboardNavigation.shortcuts; var commands = shortcuts.parse(shortcut); for(var i = 0; i < commands.length; i++){ this._handlers[shortcuts.getHash(commands[i])] = handler; } }, unbind: function(shortcut){ var shortcuts = scheduler.$keyboardNavigation.shortcuts; var commands = shortcuts.parse(shortcut); for(var i = 0; i < commands.length; i++){ if(this._handlers[shortcuts.getHash(commands[i])]){ delete this._handlers[shortcuts.getHash(commands[i])]; } } }, bindAll: function(map){ for(var i in map){ this.bind(i, map[i]); } }, initKeys: function(){ if(!this._handlers) this._handlers = {}; if(this.keys){ this.bindAll(this.keys); } } }; (function(){ scheduler.$keyboardNavigation.getFocusableNodes = scheduler._getFocusableNodes; scheduler.$keyboardNavigation.trapFocus = function trapFocus(root, e){ if(e.keyCode != 9) return false; var focusable = scheduler.$keyboardNavigation.getFocusableNodes(root); var currentFocus = document.activeElement; var currentIndex = -1; for(var i = 0; i < focusable.length; i++){ if(focusable[i] == currentFocus){ currentIndex = i; break; } } var nextIndex, nextItem; if(e.shiftKey){ // back tab // go to the last element if we focused on the first nextIndex = (currentIndex <= 0) ? (focusable[focusable.length - 1]) : (currentIndex - 1); nextItem = focusable[nextIndex]; if(nextItem){ nextItem.focus(); e.preventDefault(); return true; } }else{ // forward tab // forward tab from last element should go back to the first element nextIndex = (currentIndex >= focusable.length - 1) ? 0 : (currentIndex + 1); nextItem = focusable[nextIndex]; if(nextItem){ nextItem.focus(); e.preventDefault(); return true; } } return false; }; })(); scheduler.$keyboardNavigation.marker = { clear: function(){ var divs = scheduler.$container.querySelectorAll(".dhx_focus_slot"); for(var i = 0; i < divs.length; i++){ divs[i].parentNode.removeChild(divs[i]); } }, createElement: function(){ var element = document.createElement("DIV"); element.setAttribute("tabindex", -1); element.className = "dhx_focus_slot"; return element; }, renderMultiple: function(start, end, method){ var divs = []; var currentStart = new Date(start); var currentEnd = new Date(Math.min(end.valueOf(), scheduler.date.add(scheduler.date.day_start(new Date(start)), 1, "day").valueOf())); while(currentStart.valueOf() < end.valueOf()){ divs = divs.concat(method.call(this, currentStart, new Date(Math.min(currentEnd.valueOf(), end.valueOf())))); currentStart = scheduler.date.day_start(scheduler.date.add(currentStart, 1, "day")); currentEnd = scheduler.date.day_start(scheduler.date.add(currentStart, 1, "day")); currentEnd = new Date(Math.min(currentEnd.valueOf(), end.valueOf())); } return divs; }, render: function(start, end, section){ this.clear(); var divs = []; var modes = scheduler.$keyboardNavigation.TimeSlot.prototype._modes; var view = scheduler.$keyboardNavigation.TimeSlot.prototype._getMode(); switch (view){ case modes.units: divs = this.renderVerticalMarker(start, end, section); break; case modes.timeline: divs = this.renderTimelineMarker(start, end, section); break; case modes.year: divs = divs.concat(this.renderMultiple(start, end, this.renderYearMarker)); break; case modes.month: divs = this.renderMonthMarker(start, end); break; case modes.weekAgenda: divs = divs.concat(this.renderMultiple(start, end, this.renderWeekAgendaMarker)); break; case modes.list: divs = this.renderAgendaMarker(start, end); break; case modes.dayColumns: divs = divs.concat(this.renderMultiple(start, end, this.renderVerticalMarker)); break; } this.addWaiAriaLabel(divs, start, end, section); this.addDataAttributes(divs, start, end, section); for(var i = divs.length - 1; i >= 0; i--){ if(divs[i].offsetWidth){ return divs[i]; } } return null; }, addDataAttributes: function(divs, start, end, section){ var dateToStr = scheduler.date.date_to_str(scheduler.config.api_date); var from = dateToStr(start), to = dateToStr(end); for(var i = 0; i < divs.length; i++){ divs[i].setAttribute("data-start-date", from); divs[i].setAttribute("data-end-date", to); if(section){ divs[i].setAttribute("data-section", section); } } }, addWaiAriaLabel: function(divs, start, end, section){ var label = ""; var state = scheduler.getState(); var mode = state.mode; var dateTimeLabel = false; label += scheduler.templates.day_date(start); if((scheduler.date.day_start(new Date(start)).valueOf() != start.valueOf())){ label += " " + scheduler.templates.hour_scale(start); dateTimeLabel = true; } if((scheduler.date.day_start(new Date(start)).valueOf() != scheduler.date.day_start(new Date(end)).valueOf())){ label += " - " + scheduler.templates.day_date(end); if(dateTimeLabel || (scheduler.date.day_start(new Date(end)).valueOf() != end.valueOf())){ label += " " + scheduler.templates.hour_scale(end); } } if(section){ if(scheduler.matrix && scheduler.matrix[mode]){ label += ", " + scheduler.templates[mode + "_scale_label"](section.key, section.label, section); }else if(scheduler._props && scheduler._props[mode]){ label += ", " + scheduler.templates[mode + "_scale_text"](section.key, section.label, section); } } for(var i = 0; i < divs.length; i++){ scheduler._waiAria.setAttributes(divs[i], { "aria-label": label, "aria-live": "polite" }); } }, renderWeekAgendaMarker: function(start_date, end_date){ var divs = scheduler.$container.querySelectorAll(".dhx_wa_day_cont .dhx_wa_scale_bar"); var currDate = scheduler.date.week_start(new Date(scheduler.getState().min_date)); var index = -1; var markerDate = scheduler.date.day_start(new Date(start_date)); for(var i = 0; i < divs.length; i++){ index++; if(scheduler.date.day_start(new Date(currDate)).valueOf() == markerDate.valueOf()){ break; }else{ currDate = scheduler.date.add(currDate, 1, "day"); } } if(index != -1) return this._wrapDiv(divs[index]); return []; }, _wrapDiv: function(cell){ var marker = this.createElement(); marker.style.top = cell.offsetTop + "px"; marker.style.left = cell.offsetLeft + "px"; marker.style.width = cell.offsetWidth + "px"; marker.style.height = cell.offsetHeight + "px"; cell.appendChild(marker); return [marker]; }, renderYearMarker: function(start_date, end_date){ var cell = scheduler._get_year_cell(start_date); cell.style.position = "relative"; var marker = this.createElement(); marker.style.top = "0px"; marker.style.left = "0px"; marker.style.width = "100%"; marker.style.height = "100%"; cell.appendChild(marker); return [marker]; }, renderAgendaMarker: function(start_date, end_date){ var block = this.createElement(); block.style.height = "1px"; block.style.width = "100%"; block.style.opacity = 1; block.style.top = "0px"; block.style.left = "0px"; scheduler.$container.querySelector(".dhx_cal_data").appendChild(block); return [block]; }, renderTimelineMarker: function(start_date, end_date, section){ var view_opts = scheduler._lame_copy({}, scheduler.matrix[scheduler._mode]); var areas = view_opts._scales; //timespans must always use actual position, not rounded view_opts.round_position = false; var blocks = []; var min_date = start_date ? new Date(start_date) : scheduler._min_date; var max_date = end_date ? new Date(end_date) : scheduler._max_date; if(min_date.valueOf() < scheduler._min_date.valueOf()) min_date = new Date(scheduler._min_date); if(max_date.valueOf() > scheduler._max_date.valueOf()) max_date = new Date(scheduler._max_date); if(!view_opts._trace_x) return blocks; for(var i = 0; i < view_opts._trace_x.length; i++){ if(scheduler._is_column_visible(view_opts._trace_x[i])) break; } if(i == view_opts._trace_x.length) return blocks; var area = areas[section]; if (!(min_date < end_date && max_date > start_date)) return blocks; var block = this.createElement(); var start_pos = scheduler._timeline_getX({start_date: start_date}, false, view_opts)-1; var end_pos = scheduler._timeline_getX({start_date: end_date}, false, view_opts)-1; var height = ((view_opts._section_height[section]-1) || (view_opts.dy - 1)); var top = 0; if (scheduler._isRender('cell')){ top = area.offsetTop; start_pos += view_opts.dx; end_pos += view_opts.dx; area = scheduler.$container.querySelector(".dhx_cal_data"); }else{ } var width = Math.max(1, end_pos - start_pos - 1); block.style.cssText = "height: "+height+"px; left: "+start_pos+"px; width: "+width+"px; top: "+top+"px;"; area.insertBefore(block, area.firstChild); blocks.push(block); return blocks; }, renderMonthCell: function(date){ var cells = scheduler.$container.querySelectorAll(".dhx_month_head"); var divs = []; for(var i = 0; i < cells.length; i++){ divs.push(cells[i].parentNode); } var firstDate = scheduler.date.week_start(new Date(scheduler.getState().min_date)); var index = -1; var weekNumber = 0; var dayIndex = -1; var currDate = firstDate; var markerDate = scheduler.date.day_start(new Date(date)); for(var i = 0; i < divs.length; i++){ index++; if(dayIndex == 6){ weekNumber++; dayIndex = 0; }else{ dayIndex++; } if(scheduler.date.day_start(new Date(currDate)).valueOf() == markerDate.valueOf()){ break; }else{ currDate = scheduler.date.add(currDate, 1, "day"); } } if(index == -1){ return []; } var left = scheduler._colsS[dayIndex]; var top = scheduler._colsS.heights[weekNumber]; var div = this.createElement(); div.style.top = top + "px"; div.style.left = left + "px"; div.style.width = scheduler._cols[dayIndex] + "px"; div.style.height = ((scheduler._colsS.heights[weekNumber + 1] - top) || scheduler._colsS.height) + "px" ; var container = scheduler.$container.querySelector(".dhx_cal_data"); var datatable = container.querySelector("table"); if(datatable.nextSibling){ container.insertBefore(div, datatable.nextSibling); }else{ container.appendChild(div); } return div; }, renderMonthMarker: function(start_date, end_date){ var res = []; var currentDate = start_date; while(currentDate.valueOf() < end_date.valueOf()){ res.push(this.renderMonthCell(currentDate)); currentDate = scheduler.date.add(currentDate, 1, "day"); } return res; }, renderVerticalMarker: function(start_date, end_date, section){ var index = scheduler.locate_holder_day(start_date); var divs = []; var area = null; var c = scheduler.config; if(scheduler._ignores[index]) return divs; if (scheduler._props && scheduler._props[scheduler._mode] && section) { var view = scheduler._props[scheduler._mode]; index = view.order[section]; var inner_index = view.order[section]; if(!(view.days > 1)){ index = inner_index; if (view.size && (index > view.position+view.size)) { index = 0; } }else{ //var units_l = view.size || view.options.length; index = scheduler.locate_holder_day(start_date) + inner_index; //index = index*units_l + inner_index; } } area = scheduler.locate_holder(index); if(!area || area.querySelector(".dhx_scale_hour")){ // hour scale instead of date column return document.createElement("div"); } var start = Math.max((start_date.getHours()*60 + start_date.getMinutes()), c.first_hour*60); var end = Math.min((end_date.getHours()*60 + end_date.getMinutes()), c.last_hour*60); if(!end && (scheduler.date.day_start(new Date(end_date)).valueOf() > scheduler.date.day_start(new Date(start_date)).valueOf())){ end = c.last_hour*60; } if (end <= start) { return []; } var block = this.createElement(); // +1 for working with section which really takes up whole height (as % would be == 0) var all_hours_height = scheduler.config.hour_size_px*c.last_hour + 1; var hour_ms = 60*60*1000; block.style.top = (Math.round((start*60*1000-scheduler.config.first_hour*hour_ms)*scheduler.config.hour_size_px/hour_ms) % all_hours_height) + "px"; block.style.lineHeight = block.style.height = Math.max((Math.round(((end-start)*60*1000)*scheduler.config.hour_size_px/hour_ms)) % all_hours_height, 1)+"px"; block.style.width = "100%"; area.appendChild(block); divs.push(block); return divs[0]; } }; scheduler.$keyboardNavigation.SchedulerNode = function(){}; scheduler.$keyboardNavigation.SchedulerNode.prototype = scheduler._compose( scheduler.$keyboardNavigation.EventHandler, { getDefaultNode: function(){ var node = new scheduler.$keyboardNavigation.TimeSlot(); if(!node.isValid()){ node = node.fallback(); } return node; }, _modes:{ month: "month", year: "year", dayColumns: "dayColumns", timeline:"timeline", units:"units", weekAgenda: "weekAgenda", list: "list" }, getMode: function(){ var state = scheduler.getState(); var mode = state.mode; if((scheduler.matrix && scheduler.matrix[mode])){ return this._modes.timeline; } else if((scheduler._props && scheduler._props[mode])){ return this._modes.units; }else if(mode == "month"){ return this._modes.month; }else if(mode == "year"){ return this._modes.year; }else if(mode == "week_agenda"){ return this._modes.weekAgenda; }else if(mode == "map" || mode == "agenda" || (scheduler._grid && scheduler["grid_" + mode])){ return this._modes.list; }else{ return this._modes.dayColumns; } }, focus: function(){ scheduler.focus(); }, blur: function(){ }, disable: function(){ scheduler.$container.setAttribute("tabindex", "0"); }, enable: function(){ if(scheduler.$container) scheduler.$container.removeAttribute("tabindex"); }, isEnabled: function(){ return scheduler.$container.hasAttribute("tabindex"); }, _compareEvents: function(a, b){ if (a.start_date.valueOf() == b.start_date.valueOf()) return a.id > b.id ? 1 : -1; return a.start_date.valueOf() > b.start_date.valueOf() ? 1 : -1; }, _pickEvent: function(from, to, startId, reverse){ var range = scheduler.getState(); from = new Date(Math.max(range.min_date.valueOf(), from.valueOf())); to = new Date(Math.min(range.max_date.valueOf(), to.valueOf())); var evs = scheduler.getEvents(from, to); evs.sort(this._compareEvents); if(reverse){ evs = evs.reverse(); } var trim = !!startId; for(var i =0; i < evs.length && trim; i++){ if(evs[i].id == startId){ trim = false; } evs.splice(i, 1); i--; } return evs[0]; }, nextEventHandler: function(id){ var activeNode = scheduler.$keyboardNavigation.dispatcher.activeNode; var startId = id || (activeNode && activeNode.eventId); var nextEvent = null; if(startId && scheduler.getEvent(startId)){ var currEvent = scheduler.getEvent(startId); nextEvent = scheduler.$keyboardNavigation.SchedulerNode.prototype._pickEvent( currEvent.start_date, scheduler.date.add(currEvent.start_date, 1, "year"), currEvent.id, false ); } if(!nextEvent && !id){ var visibleDates = scheduler.getState(); nextEvent = scheduler.$keyboardNavigation.SchedulerNode.prototype._pickEvent( visibleDates.min_date, scheduler.date.add(visibleDates.min_date, 1, "year"), null, false ); } if(nextEvent){ var nextEv = new scheduler.$keyboardNavigation.Event(nextEvent.id); if(!nextEv.isValid()){// not visible event this.nextEventHandler(nextEvent.id); }else{ if(activeNode){activeNode.blur();} scheduler.$keyboardNavigation.dispatcher.setActiveNode(nextEv); } } }, prevEventHandler: function(id){ var activeNode = scheduler.$keyboardNavigation.dispatcher.activeNode; var startId = id || (activeNode && activeNode.eventId); var nextEvent = null; if(startId && scheduler.getEvent(startId)){ var currEvent = scheduler.getEvent(startId); nextEvent = scheduler.$keyboardNavigation.SchedulerNode.prototype._pickEvent( scheduler.date.add(currEvent.end_date, -1, "year"), currEvent.end_date, currEvent.id, true ); } if(!nextEvent && !id){ var visibleDates = scheduler.getState(); nextEvent = scheduler.$keyboardNavigation.SchedulerNode.prototype._pickEvent( scheduler.date.add(visibleDates.max_date, -1, "year"), visibleDates.max_date, null, true ); } if(nextEvent){ var nextEv = new scheduler.$keyboardNavigation.Event(nextEvent.id); if(!nextEv.isValid()){// not visible event this.prevEventHandler(nextEvent.id); }else{ if(activeNode){activeNode.blur();} scheduler.$keyboardNavigation.dispatcher.setActiveNode(nextEv); } } }, keys: { "alt+1, alt+2, alt+3, alt+4, alt+5, alt+6, alt+7, alt+8, alt+9": function(e){ var tabs = scheduler.$keyboardNavigation.HeaderCell.prototype.getNodes(".dhx_cal_navline .dhx_cal_tab"); var key = e.key; if(key === undefined){ key = e.keyCode - 48; } if(tabs[key*1 - 1]){ tabs[key*1 - 1].click(); } }, "ctrl+left,meta+left": function(e){ scheduler._click.dhx_cal_prev_button(); }, "ctrl+right,meta+right": function(e){ scheduler._click.dhx_cal_next_button(); }, "ctrl+up,meta+up":function(e){ var dataArea = scheduler.$container.querySelector(".dhx_cal_data"); dataArea.scrollTop -= 20; }, "ctrl+down,meta+down": function(e){ var dataArea = scheduler.$container.querySelector(".dhx_cal_data"); dataArea.scrollTop += 20; }, "e": function(){ this.nextEventHandler(); }, "home": function(){ scheduler.setCurrentView(new Date()); }, "shift+e": function(){ this.prevEventHandler(); }, "ctrl+enter,meta+enter": function(){ scheduler.addEventNow({start_date: new Date(scheduler.getState().date)}); }, "ctrl+c,meta+c": function(e){ scheduler._key_nav_copy_paste(e); }, "ctrl+v,meta+v": function(e){ scheduler._key_nav_copy_paste(e); }, "ctrl+x,meta+x": function(e){ scheduler._key_nav_copy_paste(e); } } } ); scheduler.$keyboardNavigation.SchedulerNode.prototype.bindAll(scheduler.$keyboardNavigation.SchedulerNode.prototype.keys); scheduler.$keyboardNavigation.KeyNavNode = function(){}; scheduler.$keyboardNavigation.KeyNavNode.prototype = scheduler._compose( scheduler.$keyboardNavigation.EventHandler, { isValid: function(){ return true; }, fallback: function(){ return null; }, moveTo: function (element) { scheduler.$keyboardNavigation.dispatcher.setActiveNode(element); }, compareTo: function(b){ // good enough comparison of two random objects if(!b) return false; for(var i in this){ if(!!this[i] != !!b[i]) return false; var canStringifyThis = !!(this[i] && this[i].toString); var canStringifyThat = !!(b[i] && b[i].toString); if(canStringifyThat != canStringifyThis) return false; if(!(canStringifyThat && canStringifyThis)) { if(b[i] != this[i]) return false; }else{ if(b[i].toString() != this[i].toString()) return false; } } return true; }, getNode: function(){}, focus: function(){ var node = this.getNode(); if(node){ node.setAttribute("tabindex", "-1"); //node.className += " scheduler_focused"; if(node.focus) node.focus(); } }, blur: function(){ var node = this.getNode(); if(node){ node.setAttribute("tabindex", "-1"); //node.className = (node.className || "").replace(/ ?scheduler_focused/g, ""); } } } ); scheduler.$keyboardNavigation.HeaderCell = function(index){ this.index = index || 0; }; scheduler.$keyboardNavigation.HeaderCell.prototype = scheduler._compose( scheduler.$keyboardNavigation.KeyNavNode, { getNode: function(index){ index = index || this.index || 0; var nodes = this.getNodes(); if(nodes[index]) return nodes[index]; }, getNodes: function(selector){ selector = selector || [ ".dhx_cal_navline .dhx_cal_prev_button", ".dhx_cal_navline .dhx_cal_next_button", ".dhx_cal_navline .dhx_cal_today_button", ".dhx_cal_navline .dhx_cal_tab" ].join(", "); var nodes = Array.prototype.slice.call(scheduler.$container.querySelectorAll(selector)); nodes.sort(function(a, b){ return a.offsetLeft - b.offsetLeft; }); return nodes; }, _handlers:null, isValid: function(){ return !!this.getNode(this.index); }, fallback:function(){ var defaultCell = this.getNode(0); if(!defaultCell){ defaultCell = new scheduler.$keyboardNavigation.TimeSlot(); } return defaultCell; }, keys: { "left": function(){ var newIndex = this.index - 1; if(newIndex < 0){ newIndex = this.getNodes().length - 1; } this.moveTo(new scheduler.$keyboardNavigation.HeaderCell(newIndex)); }, "right": function () { var newIndex = this.index + 1; if(newIndex >= this.getNodes().length){ newIndex = 0; } this.moveTo(new scheduler.$keyboardNavigation.HeaderCell(newIndex)); }, "down": function () { this.moveTo(new scheduler.$keyboardNavigation.TimeSlot()); }, "enter": function(){ var node = this.getNode(); if(node){ node.click(); } } } } ); scheduler.$keyboardNavigation.HeaderCell.prototype.bindAll(scheduler.$keyboardNavigation.HeaderCell.prototype.keys); scheduler.$keyboardNavigation.Event = function(id){ this.eventId = null; if(scheduler.getEvent(id)){ var ev = scheduler.getEvent(id); this.start = new Date(ev.start_date); this.end = new Date(ev.end_date); this.section = this._getSection(ev); this.eventId = id; } }; scheduler.$keyboardNavigation.Event.prototype = scheduler._compose( scheduler.$keyboardNavigation.KeyNavNode, { _getNodes: function(){ return Array.prototype.slice.call(scheduler.$container.querySelectorAll("[event_id]")); }, _modes: scheduler.$keyboardNavigation.SchedulerNode.prototype._modes, getMode: scheduler.$keyboardNavigation.SchedulerNode.prototype.getMode, _handlers: null, isValid: function(){ return !!(scheduler.getEvent(this.eventId) && this.getNode()); }, fallback: function(){ var eventNode = this._getNodes()[0]; var defaultElement = null; if(!eventNode || !(scheduler._locate_event(eventNode))){ defaultElement = new scheduler.$keyboardNavigation.TimeSlot(); }else{ var id = scheduler._locate_event(eventNode); defaultElement = new scheduler.$keyboardNavigation.Event(id); } return defaultElement; }, getNode: function(){ return scheduler.$container.querySelector("[event_id='"+this.eventId+"']"); }, focus: function(){ var event = scheduler.getEvent(this.eventId); var calendar = scheduler.getState(); if(event.start_date.valueOf() > calendar.max_date.valueOf() || event.end_date.valueOf() <= calendar.min_date.valueOf()){ scheduler.setCurrentView(event.start_date); } scheduler.$keyboardNavigation.KeyNavNode.prototype.focus.apply(this); }, blur: function(){ scheduler.$keyboardNavigation.KeyNavNode.prototype.blur.apply(this); }, _getSection: function(ev){ var section = null; var mode = scheduler.getState().mode; if(scheduler.matrix && scheduler.matrix[mode]){ var timeline = scheduler.matrix[scheduler.getState().mode]; section = ev[timeline.y_property]; }else if(scheduler._props && scheduler._props[mode]){ var unit = scheduler._props[mode]; section = ev[unit.map_to]; } return section; }, _moveToSlot: function(dir){ var ev = scheduler.getEvent(this.eventId); if(ev){ var section =this._getSection(ev); var slot = new scheduler.$keyboardNavigation.TimeSlot(ev.start_date, null, section); this.moveTo(slot.nextSlot(slot, dir)); }else{ this.moveTo(new scheduler.$keyboardNavigation.TimeSlot()); } }, keys: { "left": function(){ this._moveToSlot("left"); }, "right": function () { this._moveToSlot("right"); }, "down": function () { if(this.getMode() == this._modes.list){ scheduler.$keyboardNavigation.SchedulerNode.prototype.nextEventHandler(); }else { this._moveToSlot("down"); } }, "space": function(){ var node = this.getNode(); if(node && node.click){ node.click(); }else{ this.moveTo(new scheduler.$keyboardNavigation.TimeSlot()); } }, "up": function () { if(this.getMode() == this._modes.list){ scheduler.$keyboardNavigation.SchedulerNode.prototype.prevEventHandler(); }else { this._moveToSlot("up"); } }, "delete": function(){ if(scheduler.getEvent(this.eventId)) { scheduler._click.buttons["delete"](this.eventId); }else{ this.moveTo(new scheduler.$keyboardNavigation.TimeSlot()); } }, // open lightbox "enter": function () { if(scheduler.getEvent(this.eventId)) { scheduler.showLightbox(this.eventId); }else{ this.moveTo(new scheduler.$keyboardNavigation.TimeSlot()); } } } } ); scheduler.$keyboardNavigation.Event.prototype.bindAll(scheduler.$keyboardNavigation.Event.prototype.keys); scheduler.$keyboardNavigation.TimeSlot = function(from, to, section, movingDate){ var state = scheduler.getState(); var timeline = scheduler.matrix && scheduler.matrix[state.mode]; if(!from){ if(timeline){ from = scheduler.date[timeline.name + "_start"](new Date(state.date)); from = this.findVisibleColumn(from); }else{ from = new Date(scheduler.getState().min_date); from = this.findVisibleColumn(from); from.setHours(scheduler.config.first_hour); } } if(!to){ if(timeline){ to = scheduler.date.add(from, timeline.x_step, timeline.x_unit); }else{ to = scheduler.date.add(from, scheduler.config.key_nav_step, "minute"); } } this.section = section || this._getDefaultSection(); this.start_date = new Date(from); this.end_date = new Date(to); this.movingDate = movingDate || null; }; scheduler.$keyboardNavigation.TimeSlot.prototype = scheduler._compose( scheduler.$keyboardNavigation.KeyNavNode, { _handlers:null, clone: function(timeslot){ return new scheduler.$keyboardNavigation.TimeSlot(timeslot.start_date, timeslot.end_date, timeslot.section, timeslot.movingDate); }, _getMultisectionView: function(){ var state = scheduler.getState(); var view; if(scheduler._props && scheduler._props[state.mode]) { view = scheduler._props[state.mode]; }else if(scheduler.matrix && scheduler.matrix[state.mode]){ view = scheduler.matrix[state.mode]; } return view; }, _getDefaultSection: function(){ var section = null; var view = this._getMultisectionView(); if(view && !section){ section = this._getNextSection(); } return section; }, _getNextSection: function(sectionId, dir){ var view = this._getMultisectionView(); var currentIndex = view.order[sectionId]; var nextIndex = currentIndex; if(currentIndex !== undefined){ nextIndex = currentIndex + dir; }else{ nextIndex = (view.size && view.position) ? view.position : 0; } nextIndex = nextIndex < 0 ? nextIndex = (view.options || view.y_unit).length -1 : nextIndex; var options = view.options || view.y_unit; if(options[nextIndex]){ return options[nextIndex].key; }else{ return null; } }, isValid: function(){ var state = scheduler.getState(); var isInRange = !(this.start_date.valueOf() < state.min_date.valueOf() || this.start_date.valueOf() >= state.max_date.valueOf()); if(!isInRange) return false; if(!this.isVisible(this.start_date, this.end_date)) return false; var view = this._getMultisectionView(); if(view){ return (view.order[this.section] !== undefined); }else{ return true; } }, fallback:function(){ var defaultSlot = new scheduler.$keyboardNavigation.TimeSlot(); if(!defaultSlot.isValid()){ return new scheduler.$keyboardNavigation.DataArea(); }else{ return defaultSlot; } }, getNodes: function(){ return Array.prototype.slice.call(scheduler.$container.querySelectorAll(".dhx_focus_slot")); }, getNode: function(){ return this.getNodes()[0]; }, focus: function(){ scheduler.$keyboardNavigation.marker.render(this.start_date, this.end_date, this.section); scheduler.$keyboardNavigation.KeyNavNode.prototype.focus.apply(this); scheduler.$keyboardNavigation._pasteDate = this.start_date; scheduler.$keyboardNavigation._pasteSection = this.section; }, blur: function(){ scheduler.$keyboardNavigation.KeyNavNode.prototype.blur.apply(this); scheduler.$keyboardNavigation.marker.clear(); }, _modes: scheduler.$keyboardNavigation.SchedulerNode.prototype._modes, _getMode: scheduler.$keyboardNavigation.SchedulerNode.prototype.getMode, addMonthDate: function(date, dir, extend){ var res; switch (dir){ case "up": res = scheduler.date.add(date, -1, "week"); break; case "down": res = scheduler.date.add(date, 1, "week"); break; case "left": res = scheduler.date.day_start(scheduler.date.add(date, -1, "day")); res = this.findVisibleColumn(res, -1); break; case "right": res = scheduler.date.day_start(scheduler.date.add(date, 1, "day")); res = this.findVisibleColumn(res, 1); break; default: res = scheduler.date.day_start(new Date(date)); break; } var state = scheduler.getState(); if(date.valueOf() < state.min_date.valueOf() || (!extend && date.valueOf() >= state.max_date.valueOf())){ res = new Date(state.min_date); } return res; }, nextMonthSlot: function(slot, dir, extend){ var start, end; start = this.addMonthDate(slot.start_date, dir, extend); start.setHours(scheduler.config.first_hour); end = new Date(start); end.setHours(scheduler.config.last_hour); return {start_date: start, end_date: end}; }, _alignTimeSlot: function(date, minDate, unit, step){ var currentDate = new Date(minDate); while(currentDate.valueOf() < date.valueOf()){ currentDate = scheduler.date.add(currentDate, step, unit); } if(currentDate.valueOf() > date.valueOf()){ currentDate = scheduler.date.add(currentDate, -step, unit); } return currentDate; }, nextTimelineSlot: function(slot, dir, extend){ var state = scheduler.getState(); var view = scheduler.matrix[state.mode]; var startDate = this._alignTimeSlot(slot.start_date, scheduler.date[view.name + "_start"](new Date(slot.start_date)), view.x_unit, view.x_step); var endDate = this._alignTimeSlot(slot.end_date, scheduler.date[view.name + "_start"](new Date(slot.end_date)), view.x_unit, view.x_step); if(endDate.valueOf() <= startDate.valueOf()){ endDate = scheduler.date.add(startDate, view.x_step, view.x_unit); } var newPos = this.clone(slot); newPos.start_date = startDate; newPos.end_date = endDate; newPos.section = slot.section || this._getNextSection(); switch (dir){ case "up": newPos.section = this._getNextSection(slot.section, -1); break; case "down": newPos.section = this._getNextSection(slot.section, +1); break; case "left": newPos.start_date = this.findVisibleColumn(scheduler.date.add(newPos.start_date, -view.x_step, view.x_unit), -1); newPos.end_date = scheduler.date.add(newPos.start_date, view.x_step, view.x_unit); break; case "right": newPos.start_date = this.findVisibleColumn(scheduler.date.add(newPos.start_date, view.x_step, view.x_unit), 1); newPos.end_date = scheduler.date.add(newPos.start_date, view.x_step, view.x_unit); break; default: break; } if(newPos.start_date.valueOf() < state.min_date.valueOf() || newPos.start_date.valueOf() >= state.max_date.valueOf()){ if(extend && newPos.start_date.valueOf() >= state.max_date.valueOf()){ newPos.start_date = new Date(state.max_date); }else{ newPos.start_date = scheduler.date[state.mode + "_start"](scheduler.date.add(state.date, dir == "left" ? -1 : 1, state.mode)); newPos.end_date = scheduler.date.add(newPos.start_date, view.x_step, view.x_unit); } } return newPos; }, nextUnitsSlot: function(slot, dir, extend){ var newPos = this.clone(slot); newPos.section = slot.section || this._getNextSection(); var section = slot.section || this._getNextSection(); var state = scheduler.getState(); var view = scheduler._props[state.mode]; switch (dir){ case "left": section = this._getNextSection(slot.section, -1); var optionsCount = view.size ? (view.size - 1) : view.options.length; if(view.days > 1 && (view.order[section] == optionsCount - 1)){ if(scheduler.date.add(slot.start_date, -1, "day").valueOf() >= state.min_date.valueOf()){ newPos = this.nextDaySlot(slot, dir, extend); } } break; case "right": section = this._getNextSection(slot.section, 1); if(view.days > 1 && !view.order[section]){ if(scheduler.date.add(slot.start_date, 1, "day").valueOf() < state.max_date.valueOf()){ newPos = this.nextDaySlot(slot, dir, extend); } } break; default: newPos = this.nextDaySlot(slot, dir, extend); section = slot.section; break; } newPos.section = section; return newPos; }, _moveDate: function(oldDate, dir){ var newDate = this.findVisibleColumn(scheduler.date.add(oldDate, dir, "day"), dir); newDate.setHours(oldDate.getHours()); newDate.setMinutes(oldDate.getMinutes()); return newDate; }, isBeforeLastHour: function(date, isStartDate){ var minutes = date.getMinutes(), hours = date.getHours(), last_hour = scheduler.config.last_hour; return (hours < last_hour || (!isStartDate && ((last_hour == 24 || hours == last_hour) && !minutes))); }, isAfterFirstHour: function(date, isStartDate){ var minutes = date.getMinutes(), hours = date.getHours(), first_hour = scheduler.config.first_hour, last_hour = scheduler.config.last_hour; return (hours >= first_hour || (!isStartDate && (!minutes && ((!hours && last_hour == 24) || (hours == last_hour))))); }, isInVisibleDayTime: function(date, isStartDate){ return (this.isBeforeLastHour(date, isStartDate) && this.isAfterFirstHour(date, isStartDate)); }, nextDaySlot: function(slot, dir, extend){ var start, end; var key_nav_step = scheduler.config.key_nav_step; var date = this._alignTimeSlot(slot.start_date, scheduler.date.day_start(new Date(slot.start_date)), "minute", key_nav_step); var oldStart = slot.start_date; switch (dir){ case "up": start = scheduler.date.add(date, -key_nav_step, "minute"); if(!this.isInVisibleDayTime(start, true)){ if (!extend || this.isInVisibleDayTime(oldStart, true)) { var toNextDay = true; if(extend && scheduler.date.date_part(new Date(start)).valueOf() != scheduler.date.date_part(new Date(oldStart)).valueOf()) toNextDay = false; if(toNextDay) start = this.findVisibleColumn(scheduler.date.add(slot.start_date, -1, "day"), -1); start.setHours(scheduler.config.last_hour); start.setMinutes(0); start = scheduler.date.add(start, -key_nav_step, "minute"); } } end = scheduler.date.add(start, key_nav_step, "minute"); break; case "down": start = scheduler.date.add(date, key_nav_step, "minute"); var testEnd = extend ? start : scheduler.date.add(start, key_nav_step, "minute"); if(!this.isInVisibleDayTime(testEnd, false)){ if(!extend || this.isInVisibleDayTime(oldStart, false)) { if (!extend) { start = this.findVisibleColumn(scheduler.date.add(slot.start_date, 1, "day"), 1); start.setHours(scheduler.config.first_hour); start.setMinutes(0); } else { var toNextDay = true; if (scheduler.date.date_part(new Date(oldStart)).valueOf() == oldStart.valueOf()) { toNextDay = false; } if (toNextDay) { start = this.findVisibleColumn(scheduler.date.add(slot.start_date, 1, "day"), 1); } start.setHours(scheduler.config.first_hour); start.setMinutes(0); start = scheduler.date.add(start, key_nav_step, "minute"); } } } end = scheduler.date.add(start, key_nav_step, "minute"); break; case "left": start = this._moveDate(slot.start_date, -1); end = this._moveDate(slot.end_date, -1); break; case "right": start = this._moveDate(slot.start_date, 1); end = this._moveDate(slot.end_date, 1); break; default: start = date; end = scheduler.date.add(start, key_nav_step, "minute"); break; } return {start_date: start, end_date: end}; }, nextWeekAgendaSlot: function(slot, dir){ var start, end; var state = scheduler.getState(); switch (dir){ case "down": case "left": start = scheduler.date.day_start(scheduler.date.add(slot.start_date, -1, "day")); start = this.findVisibleColumn(start, -1); break; case "up": case "right": start = scheduler.date.day_start(scheduler.date.add(slot.start_date, 1, "day")); start = this.findVisibleColumn(start, 1); break; default: start = scheduler.date.day_start(slot.start_date); break; } if(slot.start_date.valueOf() < state.min_date.valueOf() || slot.start_date.valueOf() >= state.max_date.valueOf()){ start = new Date(state.min_date); } end = new Date(start); end.setHours(scheduler.config.last_hour); return {start_date: start, end_date: end}; }, nextAgendaSlot: function(slot, dir){ return {start_date: slot.start_date, end_date: slot.end_date}; }, isDateVisible: function(date){ if(!scheduler._ignores_detected) return true; var timeline = scheduler.matrix && scheduler.matrix[scheduler.getState().mode]; var index; if(timeline){ index = scheduler._get_date_index(timeline, date); }else{ index = scheduler.locate_holder_day(date); } return !scheduler._ignores[index]; }, findVisibleColumn: function(start, dir){ var date = start; dir = dir || 1; var range = scheduler.getState(); while(!this.isDateVisible(date) && ((dir > 0 && date.valueOf() <= range.max_date.valueOf()) || (dir < 0 && date.valueOf() >= range.min_date.valueOf()))){ date = this.nextDateColumn(date, dir); } return date; }, nextDateColumn: function(start, dir){ dir = dir || 1; var timeline = scheduler.matrix && scheduler.matrix[scheduler.getState().mode]; var date; if(timeline){ date = scheduler.date.add(start, dir * timeline.x_step, timeline.x_unit); }else{ date = scheduler.date.day_start(scheduler.date.add(start, dir, "day")); } return date; }, isVisible:function(from, to){ if(!scheduler._ignores_detected) return true; var current = new Date(from); while(current.valueOf() < to.valueOf()){ if(this.isDateVisible(current)) return true; current = this.nextDateColumn(current); } return false; }, nextSlot: function(slot, dir, view, extend){ var next; view = view || this._getMode(); var tempSlot = scheduler.$keyboardNavigation.TimeSlot.prototype.clone(slot); switch (view){ case this._modes.units: next = this.nextUnitsSlot(tempSlot, dir, extend); break; case this._modes.timeline: next = this.nextTimelineSlot(tempSlot, dir, extend); break; case this._modes.year: next = this.nextMonthSlot(tempSlot, dir, extend); break; case this._modes.month: next = this.nextMonthSlot(tempSlot, dir, extend); break; case this._modes.weekAgenda: next = this.nextWeekAgendaSlot(tempSlot, dir, extend); break; case this._modes.list: next = this.nextAgendaSlot(tempSlot, dir, extend); break; case this._modes.dayColumns: next = this.nextDaySlot(tempSlot, dir, extend); break; } if(next.start_date.valueOf() >= next.end_date.valueOf()){ next = this.nextSlot(next, dir, view); } return scheduler.$keyboardNavigation.TimeSlot.prototype.clone(next); }, extendSlot: function(slot, dir){ var view = this._getMode(); var next; switch (view){ case this._modes.units: if(dir == "left" || dir == "right"){ next = this.nextUnitsSlot(slot, dir); }else{ next = this.extendUnitsSlot(slot, dir); } break; case this._modes.timeline: if(dir == "down" || dir == "up"){ next = this.nextTimelineSlot(slot, dir); }else{ next = this.extendTimelineSlot(slot, dir); } break; case this._modes.year: next = this.extendMonthSlot(slot, dir); break; case this._modes.month: next = this.extendMonthSlot(slot, dir); break; case this._modes.dayColumns: next = this.extendDaySlot(slot, dir); break; case this._modes.weekAgenda: next = this.extendWeekAgendaSlot(slot, dir); break; default: next = slot; break; } var range = scheduler.getState(); if(next.start_date.valueOf() < range.min_date.valueOf()){ next.start_date = this.findVisibleColumn(range.min_date); next.start_date.setHours(scheduler.config.first_hour); } if(next.end_date.valueOf() > range.max_date.valueOf()){ // next.end_date = new Date(slot.end_date); next.end_date = this.findVisibleColumn(range.max_date, -1); } return scheduler.$keyboardNavigation.TimeSlot.prototype.clone(next); }, extendTimelineSlot: function(slot, direction){ return this.extendGenericSlot({ "left":"start_date", "right":"end_date" }, slot, direction, "timeline" ); }, extendWeekAgendaSlot: function(slot, direction){ return this.extendGenericSlot({ "left":"start_date", "right":"end_date" }, slot, direction, "weekAgenda" ); }, extendGenericSlot: function(allowedDirections, slot, direction, type){ var next; var moveDate = slot.movingDate; if(!moveDate){ moveDate = allowedDirections[direction]; } if(!moveDate || !allowedDirections[direction]){ return slot; } if(direction){ next = this.nextSlot({start_date: slot[moveDate], section: slot.section}, direction, type, true); if(next.start_date.valueOf() == slot.start_date.valueOf()){ next = this.nextSlot({start_date: next.start_date, section:next.section}, direction, type, true); } next.movingDate = moveDate; }else{ return scheduler.$keyboardNavigation.TimeSlot.prototype.clone(slot); } var newDates = this.extendSlotDates(slot, next, next.movingDate); if(newDates.end_date.valueOf() <= newDates.start_date.valueOf()){ next.movingDate = next.movingDate == "end_date" ? "start_date" : "end_date"; } newDates = this.extendSlotDates(slot, next, next.movingDate); next.start_date = newDates.start_date; next.end_date = newDates.end_date; return next; }, extendSlotDates: function(oldSlot, newSlot, dateDirection){ var res = {start_date:null, end_date:null}; if(dateDirection == "start_date"){ res.start_date = newSlot.start_date; res.end_date = oldSlot.end_date; }else{ res.start_date = oldSlot.start_date; res.end_date = newSlot.start_date; } return res; }, extendMonthSlot: function(slot, direction){ var slot = this.extendGenericSlot({ "up":"start_date", "down":"end_date", "left":"start_date", "right":"end_date" }, slot, direction, "month" ); slot.start_date.setHours(scheduler.config.first_hour); slot.end_date = scheduler.date.add(slot.end_date, -1, "day"); slot.end_date.setHours(scheduler.config.last_hour); return slot; }, extendUnitsSlot: function(slot, direction){ var next; switch (direction){ case "down": case "up": next = this.extendDaySlot(slot, direction); break; default: next = slot; break; } next.section = slot.section; return next; }, extendDaySlot: function(slot, direction){ return this.extendGenericSlot({ "up":"start_date", "down":"end_date", "left":"start_date", "right":"end_date" }, slot, direction, "dayColumns" ); }, scrollSlot: function(dir){ var state = scheduler.getState(); var slot = this.nextSlot(this, dir); if(slot.start_date.valueOf() < state.min_date.valueOf() || slot.start_date.valueOf() >= state.max_date.valueOf()){ scheduler.setCurrentView(new Date(slot.start_date)); } this.moveTo(slot); }, keys: { "left": function(){ this.scrollSlot("left"); }, "right": function () { this.scrollSlot("right"); }, "down": function () { var mode = this._getMode(); if(mode == this._modes.list){ scheduler.$keyboardNavigation.SchedulerNode.prototype.nextEventHandler(); }else{ this.scrollSlot("down"); } }, "up": function () { var mode = this._getMode(); if(mode == this._modes.list){ scheduler.$keyboardNavigation.SchedulerNode.prototype.prevEventHandler(); }else{ this.scrollSlot("up"); } }, "shift+down":function(){ this.moveTo(this.extendSlot(this, "down")); }, "shift+up":function(){ this.moveTo(this.extendSlot(this, "up")); }, "shift+right":function(){ this.moveTo(this.extendSlot(this, "right")); }, "shift+left":function(){ this.moveTo(this.extendSlot(this, "left")); }, "enter": function(){ var obj = {start_date: new Date(this.start_date), end_date: new Date(this.end_date)}; var mode = scheduler.getState().mode; if(scheduler.matrix && scheduler.matrix[mode]){ var timeline = scheduler.matrix[scheduler.getState().mode]; obj[timeline.y_property] = this.section; }else if(scheduler._props && scheduler._props[mode]){ var unit = scheduler._props[mode]; obj[unit.map_to] = this.section; } scheduler.addEventNow(obj); } } } ); scheduler.$keyboardNavigation.TimeSlot.prototype.bindAll(scheduler.$keyboardNavigation.TimeSlot.prototype.keys); scheduler.$keyboardNavigation.MinicalButton = function(div, index){ this.container = div; this.index = index || 0; }; scheduler.$keyboardNavigation.MinicalButton.prototype = scheduler._compose( scheduler.$keyboardNavigation.KeyNavNode, { isValid: function(){ return true; }, focus: function(){ scheduler.$keyboardNavigation.dispatcher.globalNode.disable(); this.container.removeAttribute("tabindex"); scheduler.$keyboardNavigation.KeyNavNode.prototype.focus.apply(this); }, blur: function(){ this.container.setAttribute("tabindex", "0"); scheduler.$keyboardNavigation.KeyNavNode.prototype.blur.apply(this); }, getNode: function(){ if(!this.index){ return this.container.querySelector(".dhx_cal_prev_button"); }else{ return this.container.querySelector(".dhx_cal_next_button"); } }, keys: { "right": function(e){ this.moveTo(new scheduler.$keyboardNavigation.MinicalButton(this.container, this.index ? 0 : 1)); }, "left": function(e){ this.moveTo(new scheduler.$keyboardNavigation.MinicalButton(this.container, this.index ? 0 : 1)); }, "down": function(){ var next = new scheduler.$keyboardNavigation.MinicalCell(this.container, 0, 0); if(next && !next.isValid()){ next = next.fallback(); } this.moveTo(next); }, "enter": function(e){ this.getNode().click(); } } } ); scheduler.$keyboardNavigation.MinicalButton.prototype.bindAll(scheduler.$keyboardNavigation.MinicalButton.prototype.keys); scheduler.$keyboardNavigation.MinicalCell = function(div, row, col){ this.container = div; this.row = row || 0; this.col = col || 0; }; scheduler.$keyboardNavigation.MinicalCell.prototype = scheduler._compose( scheduler.$keyboardNavigation.KeyNavNode, { isValid: function(){ var grid = this._getGrid(); return !!(grid[this.row] && grid[this.row][this.col]); }, fallback: function(){ var row = this.row; var col = this.col; var grid = this._getGrid(); if(!grid[row]){ row = 0; } var dir = true; if(row > grid.length / 2){ dir = false; } if(dir){ for(var c = col; c < grid[row].length; c++){ if(!grid[row][c] && c == grid[row].length - 1){ row++; col = 0; } if(grid[row][c]){ return new scheduler.$keyboardNavigation.MinicalCell(this.container, row, c); } } }else{ for(var c = col; c < grid[row].length; c--){ if(!grid[row][c] && !c){ row--; col = grid[row].length - 1; } if(grid[row][c]){ return new scheduler.$keyboardNavigation.MinicalCell(this.container, row, c); } } } return new scheduler.$keyboardNavigation.MinicalButton(this.container, 0); }, focus: function(){ scheduler.$keyboardNavigation.dispatcher.globalNode.disable(); this.container.removeAttribute("tabindex"); scheduler.$keyboardNavigation.KeyNavNode.prototype.focus.apply(this); }, blur: function(){ this.container.setAttribute("tabindex", "0"); scheduler.$keyboardNavigation.KeyNavNode.prototype.blur.apply(this); }, _getNode: function(row, col){ return this.container.querySelector(".dhx_year_body tr:nth-child("+(row + 1) + ") td:nth-child("+(col + 1)+")"); }, getNode: function(){ return this._getNode(this.row, this.col); }, _getGrid: function(){ var rows = this.container.querySelectorAll(".dhx_year_body tr"); var grid = []; for(var i = 0; i < rows.length; i++){ grid[i] = []; var row = rows[i]; var cells = row.querySelectorAll("td"); for(var c = 0; c < cells.length; c++){ var cell = cells[c]; var enabled = true; var css = scheduler._getClassName(cell); if(css.indexOf("dhx_after") > -1 || css.indexOf("dhx_before") > -1 || css.indexOf("dhx_scale_ignore") > -1){ enabled = false; } grid[i][c] = enabled; } } return grid; }, keys: { "right": function(e){ var grid = this._getGrid(); var newRow = this.row; var newCol = this.col + 1; if(!grid[newRow] || !grid[newRow][newCol]){ if(grid[newRow + 1]){ newRow = newRow + 1; newCol = 0; }else{ newCol = this.col; } } var next = new scheduler.$keyboardNavigation.MinicalCell(this.container, newRow, newCol); if(!next.isValid()){ next = next.fallback(); } this.moveTo(next); }, "left": function(e){ var grid = this._getGrid(); var newRow = this.row; var newCol = this.col - 1; if(!grid[newRow] || !grid[newRow][newCol]){ if(grid[newRow - 1]){ newRow = newRow - 1; newCol = grid[newRow].length - 1; }else{ newCol = this.col; } } var next = new scheduler.$keyboardNavigation.MinicalCell(this.container, newRow, newCol); if(!next.isValid()){ next = next.fallback(); } this.moveTo(next); }, "down": function(){ var grid = this._getGrid(); var newRow = this.row + 1; var newCol = this.col; if(!grid[newRow] || !grid[newRow][newCol]){ newRow = this.row; } var next = new scheduler.$keyboardNavigation.MinicalCell(this.container, newRow, newCol); if(!next.isValid()){ next = next.fallback(); } this.moveTo(next); }, "up": function(){ var grid = this._getGrid(); var newRow = this.row - 1; var newCol = this.col; if(!grid[newRow] || !grid[newRow][newCol]){ var index = 0; if(this.col > grid[this.row].length / 2){ index = 1; } this.moveTo(new scheduler.$keyboardNavigation.MinicalButton(this.container, index)); }else{ var next = new scheduler.$keyboardNavigation.MinicalCell(this.container, newRow, newCol); if(!next.isValid()){ next = next.fallback(); } this.moveTo(next); } }, "enter": function(e){ this.getNode().querySelector(".dhx_month_head").click(); } } } ); scheduler.$keyboardNavigation.MinicalCell.prototype.bindAll(scheduler.$keyboardNavigation.MinicalCell.prototype.keys); scheduler.$keyboardNavigation.DataArea = function(index){ this.index = index || 0; }; scheduler.$keyboardNavigation.DataArea.prototype = scheduler._compose( scheduler.$keyboardNavigation.KeyNavNode, { getNode: function(index){ return scheduler.$container.querySelector(".dhx_cal_data"); }, _handlers:null, isValid: function(){ return true; }, fallback:function(){ return this; }, keys: { "up,down,right,left":function(){ this.moveTo(new scheduler.$keyboardNavigation.TimeSlot()); } } } ); scheduler.$keyboardNavigation.DataArea.prototype.bindAll(scheduler.$keyboardNavigation.DataArea.prototype.keys); if(!dhtmlx._modalsStack){ dhtmlx._modalsStack = []; } (function(){ var modalsStack = []; function isModal(){ return !!(modalsStack.length || dhtmlx._modalsStack.length); } function isChildOf(child, parent){ while(child && child != parent){ child = child.parentNode; } return !!(child == parent); } function afterPopup(box){ setTimeout(function(){ if(!isModal() && !(isChildOf(document.activeElement, scheduler.$container))) { scheduler.focus(); } }, 1); } function startModal(box){ scheduler.eventRemove(box, "keydown", trapFocus); scheduler.event(box, "keydown", trapFocus); modalsStack.push(box); //scheduler.$keyboardNavigation.dispatcher.disable(); } function endModal(){ var box = modalsStack.pop(); if(box) { scheduler.eventRemove(box, "keydown", trapFocus); } afterPopup(box); } function isTopModal(box){ if(dhtmlx._modalsStack.length){ return box == dhtmlx._modalsStack[dhtmlx._modalsStack.length - 1]; }else{ return box == modalsStack[modalsStack.length - 1]; } } function trapFocus(event){ var event = event || window.event; var target = event.currentTarget; if(!isTopModal(target)) return; scheduler.$keyboardNavigation.trapFocus(target, event); } function traceLightbox(){ startModal(scheduler.getLightbox()); } scheduler.attachEvent("onLightbox", traceLightbox); scheduler.attachEvent("onAfterLightbox", endModal); scheduler.attachEvent("onAfterQuickInfo", function(){afterPopup();}); if(!dhtmlx._keyNavMessagePopup) { dhtmlx._keyNavMessagePopup = true; var focusElement = null; var backupFocus = null; dhtmlx.attachEvent("onMessagePopup", function(box){ focusElement = document.activeElement; backupFocus = focusElement; while(backupFocus && scheduler._getClassName(backupFocus).indexOf("dhx_cal_data") < 0){ backupFocus = backupFocus.parentNode; } if(backupFocus){ backupFocus = backupFocus.parentNode; } scheduler.eventRemove(box, "keydown", trapFocus); scheduler.event(box, "keydown", trapFocus); dhtmlx._modalsStack.push(box); }); dhtmlx.attachEvent("onAfterMessagePopup", function () { var box = dhtmlx._modalsStack.pop(); if(box) { scheduler.eventRemove(box, "keydown", trapFocus); } setTimeout(function(){ var currentTarget = document.activeElement; while(currentTarget && scheduler._getClassName(currentTarget).indexOf("dhx_cal_light") < 0){ currentTarget = currentTarget.parentNode; } if(currentTarget) return; if(focusElement && focusElement.parentNode){ focusElement.focus(); }else if(backupFocus && backupFocus.parentNode){ backupFocus.focus(); } focusElement = null; backupFocus = null; }, 1); }); } scheduler.$keyboardNavigation.isModal = isModal; })(); scheduler.$keyboardNavigation.dispatcher = { isActive: false, activeNode: null, globalNode: new scheduler.$keyboardNavigation.SchedulerNode(), enable: function(){ this.isActive = true; this.globalNode.enable(); this.setActiveNode(this.getActiveNode()); }, disable: function(){ this.isActive = false; this.globalNode.disable(); }, isEnabled: function(){ return !!this.isActive; }, getDefaultNode: function(){ return this.globalNode.getDefaultNode(); }, setDefaultNode: function() { this.setActiveNode(this.getDefaultNode()); }, getActiveNode: function(){ var node = this.activeNode; if(node && !node.isValid()){ node = node.fallback(); } return node; }, focusGlobalNode: function(){ this.blurNode(this.globalNode); this.focusNode(this.globalNode); }, setActiveNode: function(el){ if(!el || !el.isValid()) return; if(this.activeNode){ if(this.activeNode.compareTo(el)){ return; } } if(this.isEnabled()){ this.blurNode(this.activeNode); this.activeNode = el; this.focusNode(this.activeNode); } }, focusNode: function(el){ if(el && el.focus){ el.focus(); if(el.getNode && document.activeElement != el.getNode()){ this.setActiveNode(new scheduler.$keyboardNavigation.DataArea()); } } }, blurNode: function(el){ if(el && el.blur){ el.blur(); } }, keyDownHandler: function (e) { var activeElement = this.getActiveNode(); if(scheduler.$keyboardNavigation.isModal() && !(activeElement && activeElement.container && scheduler._locate_css({target:activeElement.container}, "dhx_minical_popup", false))) return; if (!this.isEnabled()) return; e = e || window.event; var schedulerNode = this.globalNode; var command = scheduler.$keyboardNavigation.shortcuts.getCommandFromEvent(e); if(!activeElement){ this.setDefaultNode(); }else if(activeElement.findHandler(command)){ activeElement.doAction(command, e); }else if(schedulerNode.findHandler(command)){ schedulerNode.doAction(command, e); } } }; //Initial idea and implementation by Steve MC scheduler._temp_key_scope = function (){ scheduler.config.key_nav = true; scheduler.$keyboardNavigation._pasteDate = null; // used for copy and paste operations scheduler.$keyboardNavigation._pasteSection = null; // used for copy and paste operations var isCopy = null; var pos = {}; if(!document.body){ dhtmlxEvent(window, "load", function(){ dhtmlxEvent(document.body, "mousemove", trackMousePosition); }); }else{ dhtmlxEvent(document.body, "mousemove", trackMousePosition); } function trackMousePosition(event){ event = event || window.event; pos.x = event.clientX; pos.y = event.clientY; } function currentTarget(){ var mousePointer = false; var keyNavPointer = false; var target = document.elementFromPoint(pos.x, pos.y); while(target && target != scheduler._obj){ target = target.parentNode; } mousePointer = !!(target == scheduler._obj); keyNavPointer = scheduler.$keyboardNavigation.dispatcher.isEnabled(); return mousePointer || keyNavPointer; } scheduler.attachEvent("onMouseMove", function(id,e){ var state = scheduler.getState(); // make sure scheduler is fully initialized before calling scheduler.getActionData if(!(state.mode && state.min_date)){ return; } var position = scheduler.getActionData(e); scheduler.$keyboardNavigation._pasteDate = position.date; scheduler.$keyboardNavigation._pasteSection = position.section; }); function clear_event_after(ev){ delete ev.rec_type; delete ev.rec_pattern; delete ev.event_pid; delete ev.event_length; } scheduler._make_pasted_event = function(ev){ var date = scheduler.$keyboardNavigation._pasteDate; var section = scheduler.$keyboardNavigation._pasteSection; var event_duration = ev.end_date-ev.start_date; 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); if(section){ var property = scheduler._get_section_property(); if(scheduler.config.multisection) copy[property] = ev[property]; // save initial set of resources for multisection view else copy[property] = section; } return copy; }; scheduler._do_paste = function(is_copy, modified_ev, original_ev){ scheduler.addEvent(modified_ev); scheduler.callEvent("onEventPasted", [is_copy, modified_ev, original_ev]); }; scheduler._is_key_nav_active = function(){ if(this._is_initialized() && !this._is_lightbox_open() && this.config.key_nav){ return true; } return false; }; function getSelectedEvent(){ var node = scheduler.$keyboardNavigation.dispatcher.getActiveNode(); if(node && node.eventId) return node.eventId; return scheduler._select_id; } scheduler._key_nav_copy_paste = function(e){ if(!scheduler._is_key_nav_active()) return true; e=e||event; 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 = getSelectedEvent(); if ((e.ctrlKey || e.metaKey) && 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.metaKey) && 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.metaKey) && e.keyCode == 86 && currentTarget(e)) { // CTRL+V var ev = scheduler.getEvent(scheduler._buffer_id); if (ev) { var new_ev = scheduler._make_pasted_event(ev); if (isCopy) { new_ev.id = scheduler.uid(); scheduler._do_paste(isCopy, new_ev, ev); } else { // cut operation var res = scheduler.callEvent("onBeforeEventChanged",[new_ev, e, false, ev]); if (res) { scheduler._do_paste(isCopy, new_ev, ev); isCopy = true; // switch to copy after first paste operation } } } return true; } }; }; scheduler._temp_key_scope(); (function(){ var dispatcher = scheduler.$keyboardNavigation.dispatcher; var keyDownHandler = function(e){ if(!scheduler.config.key_nav || scheduler._edit_id) return; return dispatcher.keyDownHandler(e); }; var focusHandler = function(){ dispatcher.focusGlobalNode(); }; scheduler.attachEvent("onDataRender", function(){ if(!scheduler.config.key_nav) return; if(dispatcher.isEnabled() && !scheduler.getState().editor_id){ var activeNode = dispatcher.getActiveNode(); if(activeNode instanceof scheduler.$keyboardNavigation.MinicalButton || activeNode instanceof scheduler.$keyboardNavigation.MinicalCell) return; if(!activeNode.isValid()){ dispatcher.setActiveNode(activeNode.fallback()); }else{ dispatcher.focusNode(activeNode); } dispatcher.focusNode(dispatcher.getActiveNode()); } }); scheduler.attachEvent("onSchedulerReady", function(){ var container = scheduler.$container; scheduler.eventRemove(document, "keydown", keyDownHandler); scheduler.eventRemove(container, "focus", focusHandler); if(scheduler.config.key_nav){ scheduler.event(document, "keydown", keyDownHandler); scheduler.event(container, "focus", focusHandler); container.setAttribute("tabindex", "0"); }else{ container.removeAttribute("tabindex"); } }); var timeout = null; function delay(callback){ clearTimeout(timeout); timeout = setTimeout(callback, 1); } function focusEvent(evNode){ if(!scheduler.config.key_nav) return; if(!dispatcher.isEnabled()) return; var prevState = evNode; var focusNode = new scheduler.$keyboardNavigation.Event(prevState.eventId); if(!focusNode.isValid()){ var lastStart = focusNode.start || prevState.start; var lastEnd = focusNode.end || prevState.end; var lastSection = focusNode.section || prevState.section; focusNode = new scheduler.$keyboardNavigation.TimeSlot(lastStart, lastEnd, lastSection); if(!focusNode.isValid()){ focusNode = new scheduler.$keyboardNavigation.TimeSlot(); } } dispatcher.setActiveNode(focusNode); var node = dispatcher.getActiveNode(); if(node && node.getNode && document.activeElement != node.getNode()){ dispatcher.focusNode(dispatcher.getActiveNode()); } } scheduler.attachEvent("onEventAdded", function(id,item){ if(!scheduler.config.key_nav) return true; if(dispatcher.isEnabled()){ var element = new scheduler.$keyboardNavigation.Event(id); delay(function(){ focusEvent(element);}); } }); var updateEvent = scheduler.updateEvent; scheduler.updateEvent = function(id){ var isInlineEdit = false; var activeElement = document.activeElement; if(activeElement && scheduler._getClassName(activeElement).indexOf("dhx_cal_editor") > -1){ isInlineEdit = true; } var res = updateEvent.apply(this, arguments); if(scheduler.config.key_nav && dispatcher.isEnabled()){ var activeNode = dispatcher.getActiveNode(); if(activeNode.eventId == id || isInlineEdit){ var element = new scheduler.$keyboardNavigation.Event(id); if(isInlineEdit){ dispatcher.disable(); delay(function(){ dispatcher.enable(); focusEvent(element); }); }else{ focusEvent(element); } } } return res; }; scheduler.attachEvent("onEventDeleted", function(id) { if(!scheduler.config.key_nav) return true; if(dispatcher.isEnabled()){ var activeNode = dispatcher.getActiveNode(); if(activeNode.eventId == id){ dispatcher.setActiveNode(new scheduler.$keyboardNavigation.TimeSlot()); } } return true; }); scheduler.attachEvent("onClearAll", function(){ if(!scheduler.config.key_nav) return true; if(dispatcher.isEnabled()){ if(dispatcher.getActiveNode() instanceof scheduler.$keyboardNavigation.Event){ dispatcher.setActiveNode(new scheduler.$keyboardNavigation.TimeSlot()); } } }); scheduler.attachEvent("onClick", function(id){ if(!scheduler.config.key_nav) return true; var element = new scheduler.$keyboardNavigation.Event(id); delay(function(){ if(scheduler.getEvent(id)){ dispatcher.enable(); focusEvent(element); } }); return true; }); scheduler.attachEvent("onEmptyClick", function(date, e){ if(!scheduler.config.key_nav) return true; if(!dispatcher.isEnabled()) { dispatcher.enable(); } if(dispatcher.isEnabled()) { var pos = scheduler.getActionData(e); if(pos.date){ var slot = scheduler.$keyboardNavigation.TimeSlot; dispatcher.setActiveNode(slot.prototype.nextSlot(new slot(pos.date, null, pos.section))); } } }); function isChildOf(child, parent){ while(child && child != parent){ child = child.parentNode; } return !!(child == parent); } function isMinical(focusElement){ for(var i = 0; i < minicalendars.length; i++){ if(isChildOf(focusElement, minicalendars[i])) return true; } return false; } function focusMinical(e){ var target = e.target; dispatcher.enable(); dispatcher.setActiveNode(new scheduler.$keyboardNavigation.MinicalButton(target, 0)); } var minicalendars = []; var renderMinical = scheduler.renderCalendar; function minicalClick(e){ var target = e.target || e.srcElement; var prev = scheduler._locate_css(e, "dhx_cal_prev_button", false); var next = scheduler._locate_css(e, "dhx_cal_next_button", false); var cell = scheduler._locate_css(e, "dhx_year_body", false); var rowIndex = 0; var cellIndex = 0; if(cell){ var tr; var td; var current = target; while(current && current.tagName.toLowerCase() != "td"){ current = current.parentNode; } if(current){ td = current; tr = td.parentNode; } if(tr && td){ var rows = tr.parentNode.querySelectorAll("tr"); for(var i = 0; i < rows.length; i++){ if(rows[i] == tr){ rowIndex = i; break; } } var cells = tr.querySelectorAll("td"); for(var i = 0; i < cells.length; i++){ if(cells[i] == td){ cellIndex = i; break; } } } } var root = e.currentTarget; delay(function(){ if(prev || next || cell){ dispatcher.enable(); dispatcher.activeNode = null; } if(prev){ dispatcher.setActiveNode(new scheduler.$keyboardNavigation.MinicalButton(root, 0)); }else if(next){ dispatcher.setActiveNode(new scheduler.$keyboardNavigation.MinicalButton(root, 1)); }else if(cell){ dispatcher.setActiveNode(new scheduler.$keyboardNavigation.MinicalCell(root, rowIndex, cellIndex)); } }); } scheduler.renderCalendar = function(){ var cal = renderMinical.apply(this, arguments); if(!cal._key_nav_click) { cal._key_nav_click = true; scheduler.event(cal, "click", minicalClick); } if(!cal._key_nav_focus) { cal._key_nav_focus = true; scheduler.event(cal, "focus", focusMinical); } var added = false; for(var i = 0; i < minicalendars.length; i++){ if(minicalendars[i] == cal){ added = true; break; } } if(!added) minicalendars.push(cal); if(dispatcher.isEnabled()){ var node = dispatcher.getActiveNode(); if(node.container == cal){ dispatcher.focusNode(node); }else{ cal.setAttribute("tabindex", "0"); } }else{ cal.setAttribute("tabindex", "0"); } return cal; }; var destroyMinical = scheduler.destroyCalendar; scheduler.destroyCalendar = function(cal){ for(var i = 0; i < minicalendars.length; i++){ if(minicalendars[i] == cal){ scheduler.eventRemove(minicalendars[i], "focus", focusMinical); minicalendars[i].splice(i, 1); i--; } } return destroyMinical.apply(this, arguments); }; function isSchedulerSelected(){ if(!scheduler.config.key_nav) return; var enable; var focusElement = document.activeElement; // halt key nav when focus is outside scheduler or in quick info popup if(!focusElement || scheduler._locate_css(focusElement, "dhx_cal_quick_info", false)){ enable = false; }else{ enable = isChildOf(focusElement, scheduler.$container) || isMinical(focusElement); } return enable; } function changeState(enable){ if(enable && !dispatcher.isEnabled()){ dispatcher.enable(); }else if(!enable && dispatcher.isEnabled()){ dispatcher.disable(); } } setInterval(function(){ var enable = isSchedulerSelected(); if(enable){ changeState(enable); }else if(!enable && dispatcher.isEnabled()){ setTimeout(function(){ // doublecheck in case checking is done in handler before focus element is repainted if(scheduler.config.key_nav){ changeState(isSchedulerSelected()); }else{ scheduler.$container.removeAttribute("tabindex"); } }, 20); } }, 500); })(); } if(window.Scheduler){ window.Scheduler.plugin(setupKeyNav); }else{ setupKeyNav(window.scheduler); } })();