summaryrefslogtreecommitdiffstats
path: root/codebase/sources/ext/dhtmlxscheduler_key_nav.js
diff options
context:
space:
mode:
authorAlexKlimenkov <shurick.klimenkov@gmail.com>2017-01-31 18:38:36 +0300
committerAlexKlimenkov <shurick.klimenkov@gmail.com>2017-01-31 18:38:36 +0300
commitae92cf850550a9be965db867ba4bfb5651a18e5f (patch)
treebbf70ce45cc6e608def6e9a81570febe4528208b /codebase/sources/ext/dhtmlxscheduler_key_nav.js
parent2e509c1f562c4f471d766c9b3532370f847f0839 (diff)
downloadscheduler-ae92cf850550a9be965db867ba4bfb5651a18e5f.zip
scheduler-ae92cf850550a9be965db867ba4bfb5651a18e5f.tar.gz
scheduler-ae92cf850550a9be965db867ba4bfb5651a18e5f.tar.bz2
[update] version 4.4.0
Diffstat (limited to 'codebase/sources/ext/dhtmlxscheduler_key_nav.js')
-rw-r--r--codebase/sources/ext/dhtmlxscheduler_key_nav.js2888
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