diff options
Diffstat (limited to 'codebase/sources/ext/dhtmlxscheduler_key_nav.js')
-rw-r--r-- | codebase/sources/ext/dhtmlxscheduler_key_nav.js | 2888 |
1 files changed, 2816 insertions, 72 deletions
diff --git a/codebase/sources/ext/dhtmlxscheduler_key_nav.js b/codebase/sources/ext/dhtmlxscheduler_key_nav.js index a7bf989..6f16ff8 100644 --- a/codebase/sources/ext/dhtmlxscheduler_key_nav.js +++ b/codebase/sources/ext/dhtmlxscheduler_key_nav.js @@ -1,113 +1,2857 @@ /* @license -dhtmlxScheduler v.4.3.1 +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. */ -//Initial idea and implementation by Steve MC -scheduler._temp_key_scope = function (){ +(function(){ + function setupKeyNav(scheduler){ + scheduler.config.key_nav = true; + scheduler.config.key_nav_step = 30; -scheduler.config.key_nav = true; + 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); + } + }; -var date; // used for copy and paste operations -var section; // used for copy and paste operations -var isCopy = null; + 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 + }; -scheduler.attachEvent("onMouseMove", function(id,e){ - date = scheduler.getActionData(e).date; - section = scheduler.getActionData(e).section; -}); + return scopes[mode] || scopes.scheduler; + } -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 event_duration = ev.end_date-ev.start_date; + scheduler.$keyboardNavigation = {}; - 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); + 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(); + } - 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; + 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); + } } - 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]); +(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._is_key_nav_active = function(){ - if(this._is_initialized() && !this._is_lightbox_open() && this.config.key_nav){ - return true; +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; } - return false; }; -dhtmlxEvent(document,(_isOpera?"keypress":"keydown"),function(e){ - if(!scheduler._is_key_nav_active()) return true; +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()); + } + }, - e=e||event; + keys: { + "left": function(){ + this._moveToSlot("left"); + }, - if (e.keyCode == 37 || e.keyCode == 39) { // Left, Right arrows - e.cancelBubble = true; + "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"); + } + }, - var next = scheduler.date.add(scheduler._date,(e.keyCode == 37 ? -1 : 1 ),scheduler._mode); - scheduler.setCurrentView(next); - return true; + "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){ - 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)]); + 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); } - 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(!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"); } + } - if (e.ctrlKey && e.keyCode == 86) { // 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); + 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]; } - 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 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(); } - return true; + }, + + 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); + } +})();
\ No newline at end of file |