diff --git a/pandora_console/images/background_grid.png b/pandora_console/images/background_grid.png new file mode 100644 index 0000000000..ad624e7761 Binary files /dev/null and b/pandora_console/images/background_grid.png differ diff --git a/pandora_console/include/javascript/functions_pandora_networkmap.js b/pandora_console/include/javascript/functions_pandora_networkmap.js new file mode 100644 index 0000000000..dbc9ee5f32 --- /dev/null +++ b/pandora_console/include/javascript/functions_pandora_networkmap.js @@ -0,0 +1,3124 @@ + +function draw_minimap() { + //Clean the canvas + context_minimap.clearRect(0, 0, minimap_w, minimap_h); + + context_minimap.beginPath(); + context_minimap.globalAlpha = 0.8; + context_minimap.fillStyle = "#ddd"; + context_minimap.fillRect(0, 0, minimap_w, minimap_h); + + + //Draw the items and lines + jQuery.each(graph.nodes, function (key, value) { + if (typeof(value) == 'undefined') return; + + context_minimap.beginPath(); + //Paint the item + center_orig_x = (value.x + value.image_width / 2) * minimap_relation; + center_orig_y = (value.y + value.image_height / 2) * minimap_relation; + + context_minimap.arc(center_orig_x, + center_orig_y, 2, 0, Math.PI * 2, false); + //Check if the pandora point + if (value.id_agent == -1) { + context_minimap.fillStyle = "#364D1F"; + } + else { + context_minimap.fillStyle = "#000"; + } + context_minimap.fill(); + }); + + + //Draw the rect of viewport + context_minimap.beginPath(); + context_minimap.strokeStyle = "#f00"; + context_minimap.strokeRect( + (-translation[0] / scale) * minimap_relation, + (-translation[1] / scale) * minimap_relation, + width_svg * minimap_relation / scale, + height_svg * minimap_relation / scale); + + context_minimap.beginPath(); + context_minimap.strokeStyle = "#0f0"; + context_minimap.strokeRect( + (networkmap_dimensions[0] + node_radius - holding_area_dimensions[0]) * minimap_relation, + (networkmap_dimensions[1] + node_radius - holding_area_dimensions[1]) * minimap_relation, + holding_area_dimensions[0] * minimap_relation, + holding_area_dimensions[1] * minimap_relation) + + context_minimap.globalAlpha = 1; +} + +function inner_minimap_box(param_x, param_y) { + if ((param_x + translation[0] * minimap_relation >= 0) + && (param_x + translation[0] * minimap_relation <= width_svg * minimap_relation) + && (param_y + translation[1] * minimap_relation >= 0) + && (param_y + translation[1] * minimap_relation <= height_svg * minimap_relation)) { + return true; + } + + return false; +} + +function set_center(id) { + pos_x = (width_svg / 2) - translation[0]; + pos_y = (height_svg / 2) - translation[1]; + + var params = []; + params.push("set_center=1"); + params.push("id=" + id); + params.push("x=" + pos_x); + params.push("y=" + pos_y); + params.push("page=operation/agentes/pandora_networkmap.view"); + jQuery.ajax ({ + data: params.join ("&"), + dataType: 'json', + type: 'POST', + url: action="ajax.php", + success: function (data) { + if (data['correct']) { + } + } + }); +} + +function get_relations(node_param) { + var return_links = []; + jQuery.each(graph.links, function(i, link_each) { + if (node_param.id == link_each.source.id) { + return_links.push(link_each); + } + else if (node_param.id == link_each.target.id) { + return_links.push(link_each); + } + }); + + return return_links; +} + +function delete_link(source_id, source_module_id, target_id, target_module_id, id_link) { + var params = []; + params.push("delete_link=1"); + params.push("networkmap_id=" + networkmap_id); + params.push("source_id=" + source_id); + params.push("source_module_id=" + source_module_id); + params.push("target_id=" + target_id); + params.push("target_module_id=" + target_module_id); + params.push("id_link=" + id_link); + params.push("page=operation/agentes/pandora_networkmap.view"); + jQuery.ajax ({ + data: params.join ("&"), + dataType: 'json', + type: 'POST', + url: action="ajax.php", + success: function (data) { + if (data['correct']) { + + do { + found = -1; + + jQuery.each(graph.links, function(i, element) { + if ((element.source.id_db == source_id) + && (element.target.id_db == target_id)) { + found = i; + } + }); + if (found != -1) + graph.links.splice(found, 1); + } + while (found != -1); + + draw_elements_graph(); + set_positions_graph(); + } + + $("#dialog_node_edit").dialog("close"); + } + }); +} + +function update_fictional_node(id_db_node) { + var name = $("input[name='edit_name_fictional_node']").val(); + var networkmap_to_link = $("#edit_networkmap_to_link").val(); + + var params = []; + params.push("update_fictional_node=1"); + params.push("networkmap_id=" + networkmap_id); + params.push("node_id=" + id_db_node); + params.push("name=" + name); + params.push("networkmap_to_link=" + networkmap_to_link); + params.push("page=operation/agentes/pandora_networkmap.view"); + + jQuery.ajax ({ + data: params.join ("&"), + dataType: 'json', + type: 'POST', + url: action="ajax.php", + success: function (data) { + if (data['correct']) { + $("#dialog_node_edit").dialog("close"); + + jQuery.each(graph.nodes, function(i, element) { + if (element.id_db == id_db_node) { + graph.nodes[i].text = name; + graph.nodes[i].networkmap_id = networkmap_to_link; + + $("#id_node_" + i + " title").html(name); + $("#id_node_" + i + " tspan").html(name); + } + }); + + draw_elements_graph(); + set_positions_graph(); + } + } + }); +} + +function change_shape(id_db_node) { + var shape = $("select[name='shape']").val(); + + var params = []; + params.push("change_shape=1"); + params.push("networkmap_id=" + networkmap_id); + params.push("id=" + id_db_node); + params.push("shape=" + shape); + params.push("page=operation/agentes/pandora_networkmap.view"); + + $("#shape_icon_correct").css("display", "none"); + $("#shape_icon_fail").css("display", "none"); + $("#shape_icon_in_progress").css("display", ""); + + jQuery.ajax ({ + data: params.join ("&"), + dataType: 'json', + type: 'POST', + url: action="ajax.php", + success: function (data) { + $("#shape_icon_in_progress").css("display", "none"); + if (data['correct']) { + $("#shape_icon_correct").css("display", ""); + + count = graph.nodes.length; + + jQuery.each(graph.nodes, function(i, element) { + if (element.id_db == id_db_node) { + graph.nodes[i].shape = shape; + + $("#id_node_" + i + " rect").remove(); + $("#id_node_" + i + " circle").remove(); + + if (shape == 'circle') { + d3.select("#id_node_" + element.id) + .insert("circle", "title") + .attr("r", node_radius) + .attr("class", "node_shape node_shape_circle") + .style("fill", function(d) { + return d.color; + }) + .classed('dragable_node', true) //own dragable + .on("mouseover", over_node) + .on("mouseout", over_node) + .on("click", selected_node) + .on("dblclick", show_details_agent) + .on("contextmenu", function(d) { show_menu("node", d);}); + + } + else if (shape == 'square') { + d3.select("#id_node_" + element.id) + .insert("rect", "title") + .attr("width", node_radius * 2) + .attr("height", node_radius * 2) + .attr("class", "node_shape node_shape_square") + .style("fill", function(d) { + return d.color; + }) + .classed('dragable_node', true) //own dragable + .on("mouseover", over_node) + .on("mouseout", over_node) + .on("click", selected_node) + .on("dblclick", show_details_agent) + .on("contextmenu", function(d) { show_menu("node", d);}); + + } + else if (shape == 'rhombus') { + d3.select("#id_node_" + element.id) + .insert("rect", "title") + .attr("transform", + "") + .attr("width", node_radius * 1.5) + .attr("height", node_radius * 1.5) + .attr("class", "node_shape node_shape_rhombus") + .style("fill", function(d) { + return d.color; + }) + .classed('dragable_node', true) //own dragable + .on("mouseover", over_node) + .on("mouseout", over_node) + .on("click", selected_node) + .on("dblclick", show_details_agent) + .on("contextmenu", function(d) { show_menu("node", d);}); + + } + + } + + count = count - 1; + if (count == 0) { + draw_elements_graph(); + set_positions_graph(); + } + }); + } + else { + $("#shape_icon_fail").css("display", ""); + } + } + }); +} + +function update_link(row_index, id_link) { + var interface_source = parseInt( + $("select[name='interface_source_" + row_index + "']") + .val() + ); + + var text_source_interface = ""; + if (interface_source != 0) { + text_source_interface = $("select[name='interface_source_" + + row_index + "'] option:selected").text(); + } + + var interface_target = parseInt( + $("select[name='interface_target_" + row_index + "']") + .val() + ); + + var text_target_interface = ""; + if (interface_source != 0) { + text_source_interface = $("select[name='interface_source_" + + row_index + "'] option:selected").text(); + } + + $(".edit_icon_progress_" + row_index).css("display", ""); + $(".edit_icon_" + row_index).css("display", "none"); + + var params = []; + params.push("update_link=1"); + params.push("networkmap_id=" + networkmap_id); + params.push("id_link=" + id_link); + params.push("interface_source=" + interface_source); + params.push("interface_target=" + interface_target); + params.push("page=operation/agentes/pandora_networkmap.view"); + + jQuery.ajax ({ + data: params.join ("&"), + dataType: 'json', + type: 'POST', + url: action="ajax.php", + success: function (data) { + $(".edit_icon_progress_" + row_index).css("display", "none"); + + if (data['correct']) { + $(".edit_icon_correct_" + row_index).css("display", ""); + + $("select[name='interface_source_" + row_index + "'] option[value='" + interface_source + "']") + .prop("selected", true); + $("select[name='interface_target_" + row_index + "'] option[value='" + interface_target + "']") + .prop("selected", true); + + + + if (interface_source == 0) { + jQuery.each(graph.links, function(i, link_each) { + if (link_each.id_db == id_link) { + //Found + + graph.links[i].arrow_start = ""; + graph.links[i].arrow_start = ""; + graph.links[i].text_start = text_source_interface; + + //Remove the arrow + $("#link_id_" + id_link) + .attr("marker-start", ""); + + $("tspan") + .filter(function() { + var textPath = $(this).parent(); + if ($(textPath).attr('href') == "#link_id_" + id_link) + return true; + else return false; + }) + .html(Array(25).join(" ") + text_source_interface); + } + }); + } + else { + jQuery.each(graph.links, function(i, link_each) { + if (link_each.id_db == id_link) { + //Found + + if (link_each.arrow_start == "") { + graph.links[i].id_db = data['id_link_change']; + } + + graph.links[i].arrow_start = "module"; + graph.links[i].id_module_start = interface_source; + graph.links[i].text_start = text_source_interface; + + //Added th arrow + $("#link_id_" + id_link) + .attr("marker-start", + "url(#interface_start_1)"); + + $("tspan") + .filter(function() { + var textPath = $(this).parent(); + + if ($(textPath).attr('href') == "#link_id_" + id_link) + return true; + else return false; + }) + .html(Array(25).join(" ") + text_source_interface); + } + }); + } + + + + + if (interface_target == 0) { + jQuery.each(graph.links, function(i, link_each) { + if (link_each.id_db == id_link) { + + //Found + + graph.links[i].arrow_end = ""; + graph.links[i].id_module_end = 0; + graph.links[i].text_end = text_target_interface; + + //Remove the arrow + $("#link_id_" + id_link) + .attr("marker-end", ""); + + + $("tspan") + .filter(function() { + var textPath = $(this).parent(); + + if ($(textPath).attr('href') == "#link_reverse_id_" + id_link) + return true; + else return false; + }) + .html(Array(25).join(" ") + text_target_interface); + } + }); + } + else { + jQuery.each(graph.links, function(i, link_each) { + if (link_each.id_db == id_link) { + + //Found + + if (link_each.arrow_end == "") { + graph.links[i].id_db = data['id_link_change']; + } + + graph.links[i].arrow_end = "module"; + graph.links[i].id_module_end = interface_target; + graph.links[i].text_end = text_target_interface; + + //Added th arrow + $("#link_id_" + id_link) + .attr("marker-end", + "url(#interface_end_1)"); + + $("tspan") + .filter(function() { + var textPath = $(this).parent(); + + if ($(textPath).attr('href') == "#link_reverse_id_" + id_link) + return true; + else return false; + }) + .html(Array(25).join(" ") + text_target_interface); + } + }); + } + + + + + //for to test + //jQuery.each(graph.links, function(i, link_each) {graph.links[i].arrow_start = ""; graph.links[i].arrow_end = "";}); + + // + + draw_elements_graph(); + set_positions_graph(); + } + else { + $(".edit_icon_fail_" + row_index).css("display", ""); + } + } + }); +} + +function edit_node(data) { + var flag_edit_node = true; + var edit_node = null + + //Only select one node + var selection = d3.selectAll('.node_selected'); + + if (selection[0].length == 1) { + edit_node = selection[0].pop(); + } + else if (selection[0].length > 1) { + edit_node = selection[0].pop(); + } + else { + flag_edit_node = false; + } + + if (flag_edit_node) { + d3.selectAll('.node_selected') + .classed("node_selected", false); + d3.select(edit_node) + .classed("node_selected", true); + + id = d3.select(edit_node).attr("id").replace("id_node_", ""); + node_selected = graph.nodes[id]; + + selected_links = get_relations(node_selected); + + $("select[name='shape'] option[value='" + data.shape + "']") + .prop("selected", true); + $("select[name='shape']").attr("onchange", + "javascript: change_shape(" + data.id_db + ");"); + $("#node_options-fictional_node_update_button-1 input") + .attr("onclick", "update_fictional_node(" + data.id_db + ");"); + + + $("#dialog_node_edit" ) + .dialog( "option", "title", + dialog_node_edit_title.replace("%s", data.text)); + $("#dialog_node_edit").dialog("open"); + + if (data.id_agent == -2) { + //Fictional node + $("#node_options-fictional_node_name") + .css("display", ""); + $("input[name='edit_name_fictional_node']") + .val(data.text); + $("#node_options-fictional_node_networkmap_link") + .css("display", ""); + $("#edit_networkmap_to_link") + .val(data.networkmap_id); + $("#node_options-fictional_node_update_button") + .css("display", ""); + } + else { + $("#node_options-fictional_node_name") + .css("display", "none"); + $("#node_options-fictional_node_networkmap_link") + .css("display", "none"); + $("#node_options-fictional_node_update_button") + .css("display", "none"); + } + + //Clean + $("#relations_table .relation_link_row").remove(); + //Show the no relations + $("#relations_table-loading").css('display', 'none'); + $("#relations_table-no_relations").css('display', ''); + + + jQuery.each(selected_links, function(i, link_each) { + + $("#relations_table-no_relations").css('display', 'none'); + $("#relations_table-loading").css('display', ''); + + var template_relation_row = $("#relations_table-template_row") + .clone(); + + $(template_relation_row).css('display', ''); + $(template_relation_row).attr('class', 'relation_link_row'); + + $("select[name='interface_source']", template_relation_row) + .attr('name', "interface_source_" + i) + .attr('id', "interface_source_" + i); + $("select[name='interface_target']", template_relation_row) + .attr('name', "interface_target_" + i) + .attr('id', "interface_target_" + i); + $(".edit_icon_progress", template_relation_row) + .attr('class', "edit_icon_progress_" + i); + $(".edit_icon", template_relation_row) + .attr('class', "edit_icon_" + i); + $(".edit_icon_correct", template_relation_row) + .attr('class', "edit_icon_correct_" + i); + $(".edit_icon_fail", template_relation_row) + .attr('class', "edit_icon_fail_" + i); + $(".edit_icon_link", template_relation_row) + .attr('class', "edit_icon_link_" + i) + .attr('href', 'javascript: update_link(' + i + "," + link_each.id_db + ');'); + + + var params = []; + params.push("get_intefaces=1"); + params.push("id_agent=" + link_each.source.id_agent); + params.push("page=operation/agentes/pandora_networkmap.view"); + + jQuery.ajax ({ + data: params.join ("&"), + dataType: 'json', + type: 'POST', + url: action="ajax.php", + async: false, + success: function (data) { + if (data['correct']) { + jQuery.each(data['interfaces'], function(j, interface) { + + $("select[name='interface_source_" + i + "']", template_relation_row) + .append($("'); + $("#spinner_group").css('display', 'none'); + } + else { + $("#group_for_show_agents").attr('disabled', true); + + var params = []; + params.push("get_agents_in_group=1"); + params.push("id=" + networkmap_id); + params.push("group=" + group); + params.push("page=operation/agentes/pandora_networkmap.view"); + jQuery.ajax ({ + data: params.join ("&"), + dataType: 'json', + type: 'POST', + url: action="ajax.php", + success: function (data) { + if (data['correct']) { + $("#agents_filter_group").html(''); + jQuery.each(data['agents'], function (id, name) { + if (typeof(name) == 'undefined') return; + + $("#agents_filter_group").append(''); + }); + + $("#agents_filter_group").removeAttr('disabled'); + $("#group_for_show_agents").removeAttr('disabled'); + $("#spinner_group").css('display', 'none'); + $("input[name=add_agent_group_button]").removeAttr('disabled'); + } + else { + $("#group_for_show_agents").removeAttr('disabled'); + $("#agents_filter_group").html(''); + $("#spinner_group").css('display', 'none');function show_networkmap_node(id_agent_param, refresh_state) { + id_agent = id_agent_param; + + canvas = $("#node_info"); + context_popup = canvas[0].getContext('2d'); + //Changed, now the popup is frozen as pussy nun. + //get_status_node(); + //get_status_module(); + + dirty_popup = true; + self.setInterval("check_popup_modification()", 1000/30); + //Changed, now the popup is frozen as pussy nun. + /* + self.setInterval("check_changes_num_modules()", refresh_state * 1000); + self.setInterval("get_status_node()", refresh_state * 1000); + self.setInterval("get_status_module()", refresh_state * 1000); + */ + + $("#node_info").mousemove(function(event) { + var x = event.pageX - $("#node_info").offset().left; + var y = event.pageY - $("#node_info").offset().top; + //var x = event.clientX; + //var y = event.clientY; + + //DISABLED THE DRAG AND DROP, NOW IT IS SCROLLBAR + /* + if (drag) { + drag_x_delta = drag_x - x; + drag_y_delta = drag_y - y; + + offset_x = offset_x - drag_x_delta; + offset_y = offset_y - drag_y_delta; + + drag_x = x; + drag_y = y; + + dirty_popup = true; + } + else { + */ + module_inner = inner_module(x, y); + + if (module_inner != null) { + document.body.style.cursor = "pointer"; + } + else { + document.body.style.cursor = "default"; + } + /* + } + */ + }); + + $("#node_info").mousedown(function(event) { + var x = event.pageX - $("#node_info").offset().left; + var y = event.pageY - $("#node_info").offset().top; + //var x = event.clientX; + //var y = event.clientY; + + if (module_inner != null) { + show_tooltip(module_inner, x, y); + } + //DISABLED THE DRAG AND DROP, NOW IT IS SCROLLBAR + /* + else { + drag = true; + drag_x = x; + drag_y = y; + + document.body.style.cursor = "pointer"; + } + */ + + event.stopPropagation(); + return false; + }); + + $("#node_info").mouseup(function(event) { + var x = event.pageX - $("#node_info").offset().left; + var y = event.pageY - $("#node_info").offset().top; + //var x = event.clientX; + //var y = event.clientY; + + drag = false; + drag_x = 0; + drag_y = 0; + dirty_popup = true; + + document.body.style.cursor = "default"; + + module_inner = null; + + event.stopPropagation(); + return false; + }); + + $("#node_info").mouseout(function(event) { + var x = event.pageX - $("#node_info").offset().left; + var y = event.pageY - $("#node_info").offset().top; + //var x = event.clientX; + //var y = event.clientY; + + drag = false; + drag_x = 0; + drag_y = 0; + dirty_popup = true; + + document.body.style.cursor = "default"; + + module_inner = null; + + event.stopPropagation(); + return false; + }); + + $(window).resize(function() { + //$("#node_info").attr('width', $(window).width()); + //node_info_height = $("#content_node_info").height(); + //node_info_width = $("#content_node_info").width(); + function show_networkmap_node(id_agent_param, refresh_state) { + id_agent = id_agent_param; + + canvas = $("#node_info"); + context_popup = canvas[0].getContext('2d'); + //Changed, now the popup is frozen as pussy nun. + //get_status_node(); + //get_status_module(); + + dirty_popup = true; + self.setInterval("check_popup_modification()", 1000/30); + //Changed, now the popup is frozen as pussy nun. + /* + self.setInterval("check_changes_num_modules()", refresh_state * 1000); + self.setInterval("get_status_node()", refresh_state * 1000); + self.setInterval("get_status_module()", refresh_state * 1000); + */ + + $("#node_info").mousemove(function(event) { + var x = event.pageX - $("#node_info").offset().left; + var y = event.pageY - $("#node_info").offset().top; + //var x = event.clientX; + //var y = event.clientY; + + //DISABLED THE DRAG AND DROP, NOW IT IS SCROLLBAR + /* + if (drag) { + drag_x_delta = drag_x - x; + drag_y_delta = drag_y - y; + + offset_x = offset_x - drag_x_delta; + offset_y = offset_y - drag_y_delta; + + drag_x = x; + drag_y = y; + + dirty_popup = true; + } + else { + */ + module_inner = inner_module(x, y); + + if (module_inner != null) { + document.body.style.cursor = "pointer"; + } + else { + document.body.style.cursor = "default"; + } + /* + } + */ + }); + + $("#node_info").mousedown(function(event) { + var x = event.pageX - $("#node_info").offset().left; + var y = event.pageY - $("#node_info").offset().top; + //var x = event.clientX; + //var y = event.clientY; + + if (module_inner != null) { + show_tooltip(module_inner, x, y); + } + //DISABLED THE DRAG AND DROP, NOW IT IS SCROLLBAR + /* + else { + drag = true; + drag_x = x; + drag_y = y; + + document.body.style.cursor = "pointer"; + } + */ + + event.stopPropagation(); + return false; + }); + + $("#node_info").mouseup(function(event) { + var x = event.pageX - $("#node_info").offset().left; + var y = event.pageY - $("#node_info").offset().top; + //var x = event.clientX; + //var y = event.clientY; + + drag = false; + drag_x = 0; + drag_y = 0; + dirty_popup = true; + + document.body.style.cursor = "default"; + + module_inner = null; + + event.stopPropagation(); + return false; + }); + + $("#node_info").mouseout(function(event) { + var x = event.pageX - $("#node_info").offset().left; + var y = event.pageY - $("#node_info").offset().top; + //var x = event.clientX; + //var y = event.clientY; + + drag = false; + drag_x = 0; + drag_y = 0; + dirty_popup = true; + + document.body.style.cursor = "default"; + + module_inner = null; + + event.stopPropagation(); + return false; + }); + + $(window).resize(function() { + //$("#node_info").attr('width', $(window).width()); + //node_info_height = $("#content_node_info").height(); + //node_info_width = $("#content_node_info").width(); + + pos_scroll = Math.floor($("#content_node_info").width() / 2); + + $("#content_node_info").scrollLeft(pos_scroll); + + dirty_popup = true; + check_popup_modification(); + }); +} + pos_scroll = Math.floor($("#content_node_info").width() / 2); + + $("#content_node_info").scrollLeft(pos_scroll); + + dirty_popup = true; + check_popup_modification(); + }); +} + } + } + }); + } +} + + +//////////////////////////////////////////////////////////////////////// +// Old code for the details node +//////////////////////////////////////////////////////////////////////// +//PSEUDO-CONSTANTS +var VERTICAL_SPACE_MODULES = 55; +var HORIZONTAL_SPACE_MODULES = 150; +var VERTICAL_SPACING_BETWEEN_MODULES = 10; +var BORDER_SIZE_AGENT_BOX = 5; +var SIZE_MODULE = 30; +var MARGIN_BETWEEN_AGENT_MODULE = 20; + + + +var context_popup = null; +var dirty_popup = false; +var id_agent = 0; +var pos_x = 0; +var box_height = 0; +var box_width = 0; +var count_no_snmp = 0; + +var drag = false; +var drag_x = 0; +var drag_y = 0; +var drag_x_delta = 0; +var drag_y_delta = 0; +var offset_x = 0; +var offset_y = 0; +var module_inner = null; + +function get_status_node() { + var params = []; + params.push("get_status_node=1"); + params.push("id=" + id_agent); + params.push("page=operation/agentes/pandora_networkmap.view"); + jQuery.ajax ({ + data: params.join ("&"), + dataType: 'json', + type: 'POST', + url: action="../../../ajax.php", + success: function (data) { + if (data['correct']) { + color_status_node = data['status_agent']; + dirty_popup = true; + } + } + }); +} + +function get_status_module() { + jQuery.each(modules, function (id, module) { + if (typeof(module) == 'undefined') return; + + + var params = []; + params.push("get_status_module=1"); + params.push("id=" + id); + params.push("page=operation/agentes/pandora_networkmap.view"); + jQuery.ajax ({ + data: params.join ("&"), + dataType: 'json', + type: 'POST', + url: action="../../../ajax.php", + success: function (data) { + if (data['correct']) { + modules[data['id']].status_color = data['status_color']; + dirty_popup = true; + } + } + }); + + + }); +} + +function check_changes_num_modules() { + var params = []; + params.push("check_changes_num_modules=1"); + params.push("id=" + id_agent); + params.push("page=operation/agentes/pandora_networkmap.view"); + jQuery.ajax ({ + data: params.join ("&"), + dataType: 'json', + type: 'POST', + url: action="../../../ajax.php", + success: function (data) { + if (data['correct']) { + if (module_count != data['count']) { + //location.reload(true); + } + } + } + }); +} + + +function show_networkmap_node(id_agent_param, refresh_state) { + id_agent = id_agent_param; + + canvas = $("#node_info"); + context_popup = canvas[0].getContext('2d'); + //Changed, now the popup is frozen as pussy nun. + //get_status_node(); + //get_status_module(); + + dirty_popup = true; + self.setInterval("check_popup_modification()", 1000/30); + //Changed, now the popup is frozen as pussy nun. + /* + self.setInterval("check_changes_num_modules()", refresh_state * 1000); + self.setInterval("get_status_node()", refresh_state * 1000); + self.setInterval("get_status_module()", refresh_state * 1000); + */ + + $("#node_info").mousemove(function(event) { + var x = event.pageX - $("#node_info").offset().left; + var y = event.pageY - $("#node_info").offset().top; + //var x = event.clientX; + //var y = event.clientY; + + //DISABLED THE DRAG AND DROP, NOW IT IS SCROLLBAR + /* + if (drag) { + drag_x_delta = drag_x - x; + drag_y_delta = drag_y - y; + + offset_x = offset_x - drag_x_delta; + offset_y = offset_y - drag_y_delta; + + drag_x = x; + drag_y = y; + + dirty_popup = true; + } + else { + */ + module_inner = inner_module(x, y); + + if (module_inner != null) { + document.body.style.cursor = "pointer"; + } + else { + document.body.style.cursor = "default"; + } + /* + } + */ + }); + + $("#node_info").mousedown(function(event) { + var x = event.pageX - $("#node_info").offset().left; + var y = event.pageY - $("#node_info").offset().top; + //var x = event.clientX; + //var y = event.clientY; + + if (module_inner != null) { + show_tooltip(module_inner, x, y); + } + //DISABLED THE DRAG AND DROP, NOW IT IS SCROLLBAR + /* + else { + drag = true; + drag_x = x; + drag_y = y; + + document.body.style.cursor = "pointer"; + } + */ + + event.stopPropagation(); + return false; + }); + + $("#node_info").mouseup(function(event) { + var x = event.pageX - $("#node_info").offset().left; + var y = event.pageY - $("#node_info").offset().top; + //var x = event.clientX; + //var y = event.clientY; + + drag = false; + drag_x = 0; + drag_y = 0; + dirty_popup = true; + + document.body.style.cursor = "default"; + + module_inner = null; + + event.stopPropagation(); + return false; + }); + + $("#node_info").mouseout(function(event) { + var x = event.pageX - $("#node_info").offset().left; + var y = event.pageY - $("#node_info").offset().top; + //var x = event.clientX; + //var y = event.clientY; + + drag = false; + drag_x = 0; + drag_y = 0; + dirty_popup = true; + + document.body.style.cursor = "default"; + + module_inner = null; + + event.stopPropagation(); + return false; + }); + + $(window).resize(function() { + //$("#node_info").attr('width', $(window).width()); + //node_info_height = $("#content_node_info").height(); + //node_info_width = $("#content_node_info").width(); + + pos_scroll = Math.floor($("#content_node_info").width() / 2); + + $("#content_node_info").scrollLeft(pos_scroll); + + dirty_popup = true; + check_popup_modification(); + }); +} + +function show_tooltip_content(id) { + var params = []; + params.push("get_tooltip_content=1"); + params.push("id=" + id); + params.push("page=operation/agentes/pandora_networkmap.view"); + jQuery.ajax ({ + data: params.join ("&"), + dataType: 'json', + type: 'POST', + url: action="../../../ajax.php", + success: function (data) { + if (data['correct']) { + $("#tooltip").html(data['content']); + } + } + }); +} + +function show_tooltip(id, x, y) { + $("#tooltip").css('top', y + 'px'); + $("#tooltip").css('left', x + 'px'); + + var params1 = []; + params1.push("get_image_path=1"); + params1.push("img_src=" + "images/spinner.gif"); + params1.push("page=include/ajax/skins.ajax"); + jQuery.ajax ({ + data: params1.join ("&"), + type: 'POST', + url: action="../../../ajax.php", + success: function (data) { + $("#tooltip").html(data); + $("#tooltip").css('display', ''); + + show_tooltip_content(id); + } + }); +} + +function hide_tooltip() { + $("#tooltip").css('display', 'none'); +} + +function inner_module(x, y) { + var return_var = null; + + jQuery.each(modules, function (key, module) { + if (typeof(module) == 'undefined') return; + + if ((x >= module.pos_x) && (x < (module.pos_x + SIZE_MODULE)) && + (y >= module.pos_y) && (y < (module.pos_y + SIZE_MODULE))) { + + return_var = key; + } + }); + + return return_var; +} + +function check_popup_modification() { + if (dirty_popup) { + draw_popup(); + dirty_popup = false; + } +} + +function draw_popup() { + //Calculate the size + count_no_snmp = module_count - count_snmp_modules; + + if (count_no_snmp > count_snmp_modules) { + box_height = Math.ceil(count_no_snmp / 2) * VERTICAL_SPACE_MODULES + + VERTICAL_SPACING_BETWEEN_MODULES; + } + else { + box_height = Math.ceil(count_snmp_modules / 2) * VERTICAL_SPACE_MODULES + + VERTICAL_SPACING_BETWEEN_MODULES; + } + + //Draw the agent box. + // 2 columns of HORIZONTAL_SPACE_MODULES px for each modules + // + 15 * 2 half each snmp module + box_width = HORIZONTAL_SPACE_MODULES * 2 + SIZE_MODULE; + + + //Resize the canvas if the box is bigger before of paint. + if ((box_height + 50) != $("#node_info").attr("height")) { + node_info_height = box_height + 50; + $("#node_info").attr("height", node_info_height); + //$("#node_info").attr("width", node_info_width); + } + + if ((box_width + 400) != $("#node_info").attr("width")) { + node_info_width = box_width + 400; + $("#node_info").attr("width", node_info_width); + } + + //Clean the canvas + context_popup.clearRect(0, 0, node_info_width, node_info_height); + context_popup.beginPath(); //Erase lines? + + + + pos_x = (node_info_width - box_width) / 2 + offset_x; + + context_popup.beginPath(); + context_popup.rect(pos_x, VERTICAL_SPACING_BETWEEN_MODULES + offset_y, box_width, box_height); + context_popup.fillStyle = "#ccc"; + context_popup.fill(); + + //Draw the global status of agent into the box's border color. + context_popup.lineWidth = BORDER_SIZE_AGENT_BOX; + context_popup.strokeStyle = color_status_node; + context_popup.stroke(); + + if (mode_show == 'all') { + draw_snmp_modules(); + draw_modules(); + } + else if (mode_show == 'status_module') { + draw_snmp_modules(); + } +} + +function draw_snmp_modules() { + module_pos_y = MARGIN_BETWEEN_AGENT_MODULE; + + count = 0; + reset_column = true; + + jQuery.each(modules, function (key, module) { + if (typeof(module) == 'undefined') return; + + if (module.type != 18) return; + + if (count < (count_snmp_modules / 2)) { + module_pos_x = pos_x - 15; + text_align = 'right'; + margin_text = 5; + } + else { + if (reset_column) { + module_pos_y = MARGIN_BETWEEN_AGENT_MODULE; + reset_column = false; + } + module_pos_x = pos_x + box_width - 15; + text_align = 'left'; + margin_text = SIZE_MODULE - 5; + } + count++; + + context_popup.beginPath(); + context_popup.rect(module_pos_x, module_pos_y + offset_y, + SIZE_MODULE, SIZE_MODULE); + context_popup.fillStyle = module.status_color; + context_popup.fill(); + context_popup.lineWidth = 1; + context_popup.strokeStyle = "#000"; + context_popup.stroke(); + + modules[key].pos_x = module_pos_x; + modules[key].pos_y = module_pos_y + offset_y; + + context_popup.fillStyle = "rgb(0,0,0)"; + context_popup.font = 'bold 10px sans-serif'; + context_popup.textBaseline = 'middle'; + context_popup.textAlign = text_align; + dimensions = context_popup.measureText(module.text); + text_pos_x = module_pos_x + margin_text; + text_pos_y = module_pos_y + 40 + offset_y; + context_popup.fillText(module.text, text_pos_x, text_pos_y); + + module_pos_y = module_pos_y + VERTICAL_SPACE_MODULES; + }); +} + +function draw_modules() { + module_pos_y = MARGIN_BETWEEN_AGENT_MODULE; + + count = 0; + reset_column = true; + + jQuery.each(modules, function (key, module) { + if (typeof(module) == 'undefined') return; + + if (module.type == 18) return; + + if (count < (count_no_snmp / 2)) { + module_pos_x = pos_x + (HORIZONTAL_SPACE_MODULES - SIZE_MODULE) / 2; + text_pos_x = pos_x + (HORIZONTAL_SPACE_MODULES / 2); + } + else { + if (reset_column) { + module_pos_y = MARGIN_BETWEEN_AGENT_MODULE; + reset_column = false; + } + module_pos_x = pos_x + (box_width - HORIZONTAL_SPACE_MODULES) + + (HORIZONTAL_SPACE_MODULES - SIZE_MODULE) / 2; + text_pos_x = pos_x + (box_width - HORIZONTAL_SPACE_MODULES) + + (HORIZONTAL_SPACE_MODULES / 2) + } + count++; + + context_popup.beginPath(); + center_orig_x = module_pos_x + (SIZE_MODULE / 2); + center_orig_y = module_pos_y + offset_y + (SIZE_MODULE / 2); + radius = SIZE_MODULE / 2; + context_popup.arc(center_orig_x, center_orig_y, radius, 0, Math.PI * 2, false); + //context_popup.rect(module_pos_x, module_pos_y + offset_y, SIZE_MODULE, SIZE_MODULE); + context_popup.fillStyle = module.status_color; + context_popup.fill(); + context_popup.lineWidth = 1; + context_popup.strokeStyle = "#000"; + context_popup.stroke(); + + + modules[key].pos_x = module_pos_x; + modules[key].pos_y = module_pos_y + offset_y; + + context_popup.fillStyle = "rgb(0,0,0)"; + context_popup.font = 'bold 10px sans-serif'; + context_popup.textBaseline = 'middle'; + context_popup.textAlign = 'center'; + dimensions = context_popup.measureText(module.short_text); + + text_pos_y = module_pos_y + 40 + offset_y; + context_popup.fillText(module.short_text, text_pos_x, text_pos_y); + + module_pos_y = module_pos_y + VERTICAL_SPACE_MODULES; + }); + + paint_tooltip_module_one_time = false; +} + +function update_fictional_node_popup(id) { + name = $("#text-fictional_name").val(); + shape = $("#fictional_shape option:selected").val(); + networmap = $("#networmaps_enterprise option:selected").val(); + radious = $("#fictional_radious").val(); + color = $("#fictional_color").val(); + + window.close(); + + window.opener.update_fictional_node(id, name, shape, networmap, radious, color); +} diff --git a/pandora_console/include/javascript/jquery.contextMenu.css b/pandora_console/include/javascript/jquery.contextMenu.css new file mode 100644 index 0000000000..ee2341948c --- /dev/null +++ b/pandora_console/include/javascript/jquery.contextMenu.css @@ -0,0 +1,158 @@ +/*! + * jQuery contextMenu - Plugin for simple contextMenu handling + * + * Version: 1.6.5 + * + * Authors: Rodney Rehm, Addy Osmani (patches for FF) + * Web: http://medialize.github.com/jQuery-contextMenu/ + * + * Licensed under + * MIT License http://www.opensource.org/licenses/mit-license + * GPL v3 http://opensource.org/licenses/GPL-3.0 + * + */ + +.context-menu-list { + margin:0; + padding:0; + + min-width: 120px; + max-width: 250px; + display: inline-block; + position: absolute; + list-style-type: none; + + border: 1px solid #DDD; + background: #EEE; + + -webkit-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5); + -moz-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5); + -ms-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5); + -o-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5); + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5); + + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: 11px; + text-align: left; +} + +.context-menu-item { + padding: 2px 2px 2px 24px; + background-color: #EEE; + position: relative; + -webkit-user-select: none; + -moz-user-select: -moz-none; + -ms-user-select: none; + user-select: none; +} + +.context-menu-item span { + padding-top: 10px; + padding-bottom: 10px; + padding-right: 10px; + padding-left: 10px; +} + +.context-menu-separator { + padding-bottom:0; + border-bottom: 1px solid #DDD; +} + +.context-menu-item > label > input, +.context-menu-item > label > textarea { + -webkit-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + user-select: text; +} + +.context-menu-item.hover { + cursor: pointer; + background-color: #39F; +} + +.context-menu-item.disabled { + color: #666 !important; +} + +.context-menu-input.hover, +.context-menu-item.disabled.hover { + cursor: default; + background-color: #EEE; +} + +.context-menu-submenu:after { + content: ">"; + color: #666; + position: absolute; + top: 0; + right: 3px; + z-index: 1; +} + +/* icons + #protip: + In case you want to use sprites for icons (which I would suggest you do) have a look at + http://css-tricks.com/13224-pseudo-spriting/ to get an idea of how to implement + .context-menu-item.icon:before {} + */ +.context-menu-item.icon { min-height: 18px; background-repeat: no-repeat; background-position: 4px 2px; } +.context-menu-item.icon-edit { background-image: url(images/page_white_edit.png); } +.context-menu-item.icon-cut { background-image: url(images/cut.png); } +.context-menu-item.icon-copy { background-image: url(images/page_white_copy.png); } +.context-menu-item.icon-paste { background-image: url(images/page_white_paste.png); } +.context-menu-item.icon-delete { background-image: url(images/page_white_delete.png); } +.context-menu-item.icon-add { background-image: url(images/page_white_add.png); } +.context-menu-item.icon-quit { background-image: url(images/door.png); } +.context-menu-item.icon-refresh { background-image: url(../../../images/refresh.png); } +.context-menu-item.icon-center { background-image: url(../../../images/star.png); } +.context-menu-item.icon-details { background-image: url(../../../images/application.png); } +.context-menu-item.icon-children { background-image: url(../../../images/link_add.png); } +.context-menu-item.icon-cancel_set_parent { background-image: url(../../../images/link_delete.png); } +.context-menu-item.icon-set_parent { background-image: url(../../../images/link_go.png); } +.context-menu-item.icon-add_node { background-image: url(../../../images/add.png); } +.context-menu-item.icon-refresh_holding_area { background-image: url(../../../images/category_col.png); } + +/* vertically align inside labels */ +.context-menu-input > label > * { vertical-align: top; } + +/* position checkboxes and radios as icons */ +.context-menu-input > label > input[type="checkbox"], +.context-menu-input > label > input[type="radio"] { + margin-left: -17px; +} +.context-menu-input > label > span { + margin-left: 5px; +} + +.context-menu-input > label, +.context-menu-input > label > input[type="text"], +.context-menu-input > label > textarea, +.context-menu-input > label > select { + display: block; + width: 100%; + + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + -ms-box-sizing: border-box; + -o-box-sizing: border-box; + box-sizing: border-box; +} + +.context-menu-input > label > textarea { + height: 100px; +} +.context-menu-item > .context-menu-list { + display: none; + /* re-positioned by js */ + right: -5px; + top: 5px; +} + +.context-menu-item.hover > .context-menu-list { + display: block; +} + +.context-menu-accesskey { + text-decoration: underline; +} diff --git a/pandora_console/include/javascript/jquery.contextMenu.js b/pandora_console/include/javascript/jquery.contextMenu.js new file mode 100644 index 0000000000..f591d98312 --- /dev/null +++ b/pandora_console/include/javascript/jquery.contextMenu.js @@ -0,0 +1,1686 @@ +/*! + * jQuery contextMenu - Plugin for simple contextMenu handling + * + * Version: 1.6.5 + * + * Authors: Rodney Rehm, Addy Osmani (patches for FF) + * Web: http://medialize.github.com/jQuery-contextMenu/ + * + * Licensed under + * MIT License http://www.opensource.org/licenses/mit-license + * GPL v3 http://opensource.org/licenses/GPL-3.0 + * + */ + +(function($, undefined){ + + // TODO: - + // ARIA stuff: menuitem, menuitemcheckbox und menuitemradio + // create structure if $.support[htmlCommand || htmlMenuitem] and !opt.disableNative + +// determine html5 compatibility +$.support.htmlMenuitem = ('HTMLMenuItemElement' in window); +$.support.htmlCommand = ('HTMLCommandElement' in window); +$.support.eventSelectstart = ("onselectstart" in document.documentElement); +/* // should the need arise, test for css user-select +$.support.cssUserSelect = (function(){ + var t = false, + e = document.createElement('div'); + + $.each('Moz|Webkit|Khtml|O|ms|Icab|'.split('|'), function(i, prefix) { + var propCC = prefix + (prefix ? 'U' : 'u') + 'serSelect', + prop = (prefix ? ('-' + prefix.toLowerCase() + '-') : '') + 'user-select'; + + e.style.cssText = prop + ': text;'; + if (e.style[propCC] == 'text') { + t = true; + return false; + } + + return true; + }); + + return t; +})(); +*/ + +if (!$.ui || !$.ui.widget) { + // duck punch $.cleanData like jQueryUI does to get that remove event + // https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.widget.js#L16-24 + var _cleanData = $.cleanData; + $.cleanData = function( elems ) { + for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { + try { + $( elem ).triggerHandler( "remove" ); + // http://bugs.jquery.com/ticket/8235 + } catch( e ) {} + } + _cleanData( elems ); + }; +} + +var // currently active contextMenu trigger + $currentTrigger = null, + // is contextMenu initialized with at least one menu? + initialized = false, + // window handle + $win = $(window), + // number of registered menus + counter = 0, + // mapping selector to namespace + namespaces = {}, + // mapping namespace to options + menus = {}, + // custom command type handlers + types = {}, + // default values + defaults = { + // selector of contextMenu trigger + selector: null, + // where to append the menu to + appendTo: null, + // method to trigger context menu ["right", "left", "hover"] + trigger: "right", + // hide menu when mouse leaves trigger / menu elements + autoHide: false, + // ms to wait before showing a hover-triggered context menu + delay: 200, + // flag denoting if a second trigger should simply move (true) or rebuild (false) an open menu + // as long as the trigger happened on one of the trigger-element's child nodes + reposition: true, + // determine position to show menu at + determinePosition: function($menu) { + // position to the lower middle of the trigger element + if ($.ui && $.ui.position) { + // .position() is provided as a jQuery UI utility + // (...and it won't work on hidden elements) + $menu.css('display', 'block').position({ + my: "center top", + at: "center bottom", + of: this, + offset: "0 5", + collision: "fit" + }).css('display', 'none'); + } else { + // determine contextMenu position + var offset = this.offset(); + offset.top += this.outerHeight(); + offset.left += this.outerWidth() / 2 - $menu.outerWidth() / 2; + $menu.css(offset); + } + }, + // position menu + position: function(opt, x, y) { + var $this = this, + offset; + // determine contextMenu position + if (!x && !y) { + opt.determinePosition.call(this, opt.$menu); + return; + } else if (x === "maintain" && y === "maintain") { + // x and y must not be changed (after re-show on command click) + offset = opt.$menu.position(); + } else { + // x and y are given (by mouse event) + offset = {top: y, left: x}; + } + + // correct offset if viewport demands it + var bottom = $win.scrollTop() + $win.height(), + right = $win.scrollLeft() + $win.width(), + height = opt.$menu.height(), + width = opt.$menu.width(); + + if (offset.top + height > bottom) { + offset.top -= height; + } + + if (offset.left + width > right) { + offset.left -= width; + } + + opt.$menu.css(offset); + }, + // position the sub-menu + positionSubmenu: function($menu) { + if ($.ui && $.ui.position) { + // .position() is provided as a jQuery UI utility + // (...and it won't work on hidden elements) + $menu.css('display', 'block').position({ + my: "left top", + at: "right top", + of: this, + collision: "flipfit fit" + }).css('display', ''); + } else { + // determine contextMenu position + var offset = { + top: 0, + left: this.outerWidth() + }; + $menu.css(offset); + } + }, + // offset to add to zIndex + zIndex: 1, + // show hide animation settings + animation: { + duration: 50, + show: 'slideDown', + hide: 'slideUp' + }, + // events + events: { + show: $.noop, + hide: $.noop + }, + // default callback + callback: null, + // list of contextMenu items + items: {} + }, + // mouse position for hover activation + hoveract = { + timer: null, + pageX: null, + pageY: null + }, + // determine zIndex + zindex = function($t) { + var zin = 0, + $tt = $t; + + while (true) { + zin = Math.max(zin, parseInt($tt.css('z-index'), 10) || 0); + $tt = $tt.parent(); + if (!$tt || !$tt.length || "html body".indexOf($tt.prop('nodeName').toLowerCase()) > -1 ) { + break; + } + } + + return zin; + }, + // event handlers + handle = { + // abort anything + abortevent: function(e){ + e.preventDefault(); + e.stopImmediatePropagation(); + }, + + // contextmenu show dispatcher + contextmenu: function(e) { + var $this = $(this); + + // disable actual context-menu + e.preventDefault(); + e.stopImmediatePropagation(); + + // abort native-triggered events unless we're triggering on right click + if (e.data.trigger != 'right' && e.originalEvent) { + return; + } + + // abort event if menu is visible for this trigger + if ($this.hasClass('context-menu-active')) { + return; + } + + if (!$this.hasClass('context-menu-disabled')) { + // theoretically need to fire a show event at + // http://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#context-menus + // var evt = jQuery.Event("show", { data: data, pageX: e.pageX, pageY: e.pageY, relatedTarget: this }); + // e.data.$menu.trigger(evt); + + $currentTrigger = $this; + if (e.data.build) { + var built = e.data.build($currentTrigger, e); + // abort if build() returned false + if (built === false) { + return; + } + + // dynamically build menu on invocation + e.data = $.extend(true, {}, defaults, e.data, built || {}); + + // abort if there are no items to display + if (!e.data.items || $.isEmptyObject(e.data.items)) { + // Note: jQuery captures and ignores errors from event handlers + if (window.console) { + (console.error || console.log)("No items specified to show in contextMenu"); + } + + throw new Error('No Items sepcified'); + } + + // backreference for custom command type creation + e.data.$trigger = $currentTrigger; + + op.create(e.data); + } + // show menu + op.show.call($this, e.data, e.pageX, e.pageY); + } + }, + // contextMenu left-click trigger + click: function(e) { + e.preventDefault(); + e.stopImmediatePropagation(); + $(this).trigger($.Event("contextmenu", { data: e.data, pageX: e.pageX, pageY: e.pageY })); + }, + // contextMenu right-click trigger + mousedown: function(e) { + // register mouse down + var $this = $(this); + + // hide any previous menus + if ($currentTrigger && $currentTrigger.length && !$currentTrigger.is($this)) { + $currentTrigger.data('contextMenu').$menu.trigger('contextmenu:hide'); + } + + // activate on right click + if (e.button == 2) { + $currentTrigger = $this.data('contextMenuActive', true); + } + }, + // contextMenu right-click trigger + mouseup: function(e) { + // show menu + var $this = $(this); + if ($this.data('contextMenuActive') && $currentTrigger && $currentTrigger.length && $currentTrigger.is($this) && !$this.hasClass('context-menu-disabled')) { + e.preventDefault(); + e.stopImmediatePropagation(); + $currentTrigger = $this; + $this.trigger($.Event("contextmenu", { data: e.data, pageX: e.pageX, pageY: e.pageY })); + } + + $this.removeData('contextMenuActive'); + }, + // contextMenu hover trigger + mouseenter: function(e) { + var $this = $(this), + $related = $(e.relatedTarget), + $document = $(document); + + // abort if we're coming from a menu + if ($related.is('.context-menu-list') || $related.closest('.context-menu-list').length) { + return; + } + + // abort if a menu is shown + if ($currentTrigger && $currentTrigger.length) { + return; + } + + hoveract.pageX = e.pageX; + hoveract.pageY = e.pageY; + hoveract.data = e.data; + $document.on('mousemove.contextMenuShow', handle.mousemove); + hoveract.timer = setTimeout(function() { + hoveract.timer = null; + $document.off('mousemove.contextMenuShow'); + $currentTrigger = $this; + $this.trigger($.Event("contextmenu", { data: hoveract.data, pageX: hoveract.pageX, pageY: hoveract.pageY })); + }, e.data.delay ); + }, + // contextMenu hover trigger + mousemove: function(e) { + hoveract.pageX = e.pageX; + hoveract.pageY = e.pageY; + }, + // contextMenu hover trigger + mouseleave: function(e) { + // abort if we're leaving for a menu + var $related = $(e.relatedTarget); + if ($related.is('.context-menu-list') || $related.closest('.context-menu-list').length) { + return; + } + + try { + clearTimeout(hoveract.timer); + } catch(e) {} + + hoveract.timer = null; + }, + + // click on layer to hide contextMenu + layerClick: function(e) { + var $this = $(this), + root = $this.data('contextMenuRoot'), + mouseup = false, + button = e.button, + x = e.pageX, + y = e.pageY, + target, + offset, + selectors; + + e.preventDefault(); + e.stopImmediatePropagation(); + + setTimeout(function() { + var $window, hideshow, possibleTarget; + var triggerAction = ((root.trigger == 'left' && button === 0) || (root.trigger == 'right' && button === 2)); + + // find the element that would've been clicked, wasn't the layer in the way + if (document.elementFromPoint) { + root.$layer.hide(); + target = document.elementFromPoint(x - $win.scrollLeft(), y - $win.scrollTop()); + root.$layer.show(); + } + + if (root.reposition && triggerAction) { + if (document.elementFromPoint) { + if (root.$trigger.is(target) || root.$trigger.has(target).length) { + root.position.call(root.$trigger, root, x, y); + return; + } + } else { + offset = root.$trigger.offset(); + $window = $(window); + // while this looks kinda awful, it's the best way to avoid + // unnecessarily calculating any positions + offset.top += $window.scrollTop(); + if (offset.top <= e.pageY) { + offset.left += $window.scrollLeft(); + if (offset.left <= e.pageX) { + offset.bottom = offset.top + root.$trigger.outerHeight(); + if (offset.bottom >= e.pageY) { + offset.right = offset.left + root.$trigger.outerWidth(); + if (offset.right >= e.pageX) { + // reposition + root.position.call(root.$trigger, root, x, y); + return; + } + } + } + } + } + } + + if (target && triggerAction) { + root.$trigger.one('contextmenu:hidden', function() { + $(target).contextMenu({x: x, y: y}); + }); + } + + root.$menu.trigger('contextmenu:hide'); + }, 50); + }, + // key handled :hover + keyStop: function(e, opt) { + if (!opt.isInput) { + e.preventDefault(); + } + + e.stopPropagation(); + }, + key: function(e) { + var opt = $currentTrigger.data('contextMenu') || {}; + + switch (e.keyCode) { + case 9: + case 38: // up + handle.keyStop(e, opt); + // if keyCode is [38 (up)] or [9 (tab) with shift] + if (opt.isInput) { + if (e.keyCode == 9 && e.shiftKey) { + e.preventDefault(); + opt.$selected && opt.$selected.find('input, textarea, select').blur(); + opt.$menu.trigger('prevcommand'); + return; + } else if (e.keyCode == 38 && opt.$selected.find('input, textarea, select').prop('type') == 'checkbox') { + // checkboxes don't capture this key + e.preventDefault(); + return; + } + } else if (e.keyCode != 9 || e.shiftKey) { + opt.$menu.trigger('prevcommand'); + return; + } + // omitting break; + + // case 9: // tab - reached through omitted break; + case 40: // down + handle.keyStop(e, opt); + if (opt.isInput) { + if (e.keyCode == 9) { + e.preventDefault(); + opt.$selected && opt.$selected.find('input, textarea, select').blur(); + opt.$menu.trigger('nextcommand'); + return; + } else if (e.keyCode == 40 && opt.$selected.find('input, textarea, select').prop('type') == 'checkbox') { + // checkboxes don't capture this key + e.preventDefault(); + return; + } + } else { + opt.$menu.trigger('nextcommand'); + return; + } + break; + + case 37: // left + handle.keyStop(e, opt); + if (opt.isInput || !opt.$selected || !opt.$selected.length) { + break; + } + + if (!opt.$selected.parent().hasClass('context-menu-root')) { + var $parent = opt.$selected.parent().parent(); + opt.$selected.trigger('contextmenu:blur'); + opt.$selected = $parent; + return; + } + break; + + case 39: // right + handle.keyStop(e, opt); + if (opt.isInput || !opt.$selected || !opt.$selected.length) { + break; + } + + var itemdata = opt.$selected.data('contextMenu') || {}; + if (itemdata.$menu && opt.$selected.hasClass('context-menu-submenu')) { + opt.$selected = null; + itemdata.$selected = null; + itemdata.$menu.trigger('nextcommand'); + return; + } + break; + + case 35: // end + case 36: // home + if (opt.$selected && opt.$selected.find('input, textarea, select').length) { + return; + } else { + (opt.$selected && opt.$selected.parent() || opt.$menu) + .children(':not(.disabled, .not-selectable)')[e.keyCode == 36 ? 'first' : 'last']() + .trigger('contextmenu:focus'); + e.preventDefault(); + return; + } + break; + + case 13: // enter + handle.keyStop(e, opt); + if (opt.isInput) { + if (opt.$selected && !opt.$selected.is('textarea, select')) { + e.preventDefault(); + return; + } + break; + } + opt.$selected && opt.$selected.trigger('mouseup'); + return; + + case 32: // space + case 33: // page up + case 34: // page down + // prevent browser from scrolling down while menu is visible + handle.keyStop(e, opt); + return; + + case 27: // esc + handle.keyStop(e, opt); + opt.$menu.trigger('contextmenu:hide'); + return; + + default: // 0-9, a-z + var k = (String.fromCharCode(e.keyCode)).toUpperCase(); + if (opt.accesskeys[k]) { + // according to the specs accesskeys must be invoked immediately + opt.accesskeys[k].$node.trigger(opt.accesskeys[k].$menu + ? 'contextmenu:focus' + : 'mouseup' + ); + return; + } + break; + } + // pass event to selected item, + // stop propagation to avoid endless recursion + e.stopPropagation(); + opt.$selected && opt.$selected.trigger(e); + }, + + // select previous possible command in menu + prevItem: function(e) { + e.stopPropagation(); + var opt = $(this).data('contextMenu') || {}; + + // obtain currently selected menu + if (opt.$selected) { + var $s = opt.$selected; + opt = opt.$selected.parent().data('contextMenu') || {}; + opt.$selected = $s; + } + + var $children = opt.$menu.children(), + $prev = !opt.$selected || !opt.$selected.prev().length ? $children.last() : opt.$selected.prev(), + $round = $prev; + + // skip disabled + while ($prev.hasClass('disabled') || $prev.hasClass('not-selectable')) { + if ($prev.prev().length) { + $prev = $prev.prev(); + } else { + $prev = $children.last(); + } + if ($prev.is($round)) { + // break endless loop + return; + } + } + + // leave current + if (opt.$selected) { + handle.itemMouseleave.call(opt.$selected.get(0), e); + } + + // activate next + handle.itemMouseenter.call($prev.get(0), e); + + // focus input + var $input = $prev.find('input, textarea, select'); + if ($input.length) { + $input.focus(); + } + }, + // select next possible command in menu + nextItem: function(e) { + e.stopPropagation(); + var opt = $(this).data('contextMenu') || {}; + + // obtain currently selected menu + if (opt.$selected) { + var $s = opt.$selected; + opt = opt.$selected.parent().data('contextMenu') || {}; + opt.$selected = $s; + } + + var $children = opt.$menu.children(), + $next = !opt.$selected || !opt.$selected.next().length ? $children.first() : opt.$selected.next(), + $round = $next; + + // skip disabled + while ($next.hasClass('disabled') || $next.hasClass('not-selectable')) { + if ($next.next().length) { + $next = $next.next(); + } else { + $next = $children.first(); + } + if ($next.is($round)) { + // break endless loop + return; + } + } + + // leave current + if (opt.$selected) { + handle.itemMouseleave.call(opt.$selected.get(0), e); + } + + // activate next + handle.itemMouseenter.call($next.get(0), e); + + // focus input + var $input = $next.find('input, textarea, select'); + if ($input.length) { + $input.focus(); + } + }, + + // flag that we're inside an input so the key handler can act accordingly + focusInput: function(e) { + var $this = $(this).closest('.context-menu-item'), + data = $this.data(), + opt = data.contextMenu, + root = data.contextMenuRoot; + + root.$selected = opt.$selected = $this; + root.isInput = opt.isInput = true; + }, + // flag that we're inside an input so the key handler can act accordingly + blurInput: function(e) { + var $this = $(this).closest('.context-menu-item'), + data = $this.data(), + opt = data.contextMenu, + root = data.contextMenuRoot; + + root.isInput = opt.isInput = false; + }, + + // :hover on menu + menuMouseenter: function(e) { + var root = $(this).data().contextMenuRoot; + root.hovering = true; + }, + // :hover on menu + menuMouseleave: function(e) { + var root = $(this).data().contextMenuRoot; + if (root.$layer && root.$layer.is(e.relatedTarget)) { + root.hovering = false; + } + }, + + // :hover done manually so key handling is possible + itemMouseenter: function(e) { + var $this = $(this), + data = $this.data(), + opt = data.contextMenu, + root = data.contextMenuRoot; + + root.hovering = true; + + // abort if we're re-entering + if (e && root.$layer && root.$layer.is(e.relatedTarget)) { + e.preventDefault(); + e.stopImmediatePropagation(); + } + + // make sure only one item is selected + (opt.$menu ? opt : root).$menu + .children('.hover').trigger('contextmenu:blur'); + + if ($this.hasClass('disabled') || $this.hasClass('not-selectable')) { + opt.$selected = null; + return; + } + + $this.trigger('contextmenu:focus'); + }, + // :hover done manually so key handling is possible + itemMouseleave: function(e) { + var $this = $(this), + data = $this.data(), + opt = data.contextMenu, + root = data.contextMenuRoot; + + if (root !== opt && root.$layer && root.$layer.is(e.relatedTarget)) { + root.$selected && root.$selected.trigger('contextmenu:blur'); + e.preventDefault(); + e.stopImmediatePropagation(); + root.$selected = opt.$selected = opt.$node; + return; + } + + $this.trigger('contextmenu:blur'); + }, + // contextMenu item click + itemClick: function(e) { + var $this = $(this), + data = $this.data(), + opt = data.contextMenu, + root = data.contextMenuRoot, + key = data.contextMenuKey, + callback; + + // abort if the key is unknown or disabled or is a menu + if (!opt.items[key] || $this.is('.disabled, .context-menu-submenu, .context-menu-separator, .not-selectable')) { + return; + } + + e.preventDefault(); + e.stopImmediatePropagation(); + + if ($.isFunction(root.callbacks[key]) && Object.prototype.hasOwnProperty.call(root.callbacks, key)) { + // item-specific callback + callback = root.callbacks[key]; + } else if ($.isFunction(root.callback)) { + // default callback + callback = root.callback; + } else { + // no callback, no action + return; + } + + // hide menu if callback doesn't stop that + if (callback.call(root.$trigger, key, root) !== false) { + root.$menu.trigger('contextmenu:hide'); + } else if (root.$menu.parent().length) { + op.update.call(root.$trigger, root); + } + }, + // ignore click events on input elements + inputClick: function(e) { + e.stopImmediatePropagation(); + }, + + // hide + hideMenu: function(e, data) { + var root = $(this).data('contextMenuRoot'); + op.hide.call(root.$trigger, root, data && data.force); + }, + // focus + focusItem: function(e) { + e.stopPropagation(); + var $this = $(this), + data = $this.data(), + opt = data.contextMenu, + root = data.contextMenuRoot; + + $this.addClass('hover') + .siblings('.hover').trigger('contextmenu:blur'); + + // remember selected + opt.$selected = root.$selected = $this; + + // position sub-menu - do after show so dumb $.ui.position can keep up + if (opt.$node) { + root.positionSubmenu.call(opt.$node, opt.$menu); + } + }, + // blur + blurItem: function(e) { + e.stopPropagation(); + var $this = $(this), + data = $this.data(), + opt = data.contextMenu, + root = data.contextMenuRoot; + + $this.removeClass('hover'); + opt.$selected = null; + } + }, + // operations + op = { + show: function(opt, x, y) { + var $trigger = $(this), + offset, + css = {}; + + // hide any open menus + $('#context-menu-layer').trigger('mousedown'); + + // backreference for callbacks + opt.$trigger = $trigger; + + // show event + if (opt.events.show.call($trigger, opt) === false) { + $currentTrigger = null; + return; + } + + // create or update context menu + op.update.call($trigger, opt); + + // position menu + opt.position.call($trigger, opt, x, y); + + // make sure we're in front + if (opt.zIndex) { + css.zIndex = zindex($trigger) + opt.zIndex; + } + + // add layer + op.layer.call(opt.$menu, opt, css.zIndex); + + // adjust sub-menu zIndexes + opt.$menu.find('ul').css('zIndex', css.zIndex + 1); + + // position and show context menu + opt.$menu.css( css )[opt.animation.show](opt.animation.duration, function() { + $trigger.trigger('contextmenu:visible'); + }); + // make options available and set state + $trigger + .data('contextMenu', opt) + .addClass("context-menu-active"); + + // register key handler + $(document).off('keydown.contextMenu').on('keydown.contextMenu', handle.key); + // register autoHide handler + if (opt.autoHide) { + // mouse position handler + $(document).on('mousemove.contextMenuAutoHide', function(e) { + // need to capture the offset on mousemove, + // since the page might've been scrolled since activation + var pos = $trigger.offset(); + pos.right = pos.left + $trigger.outerWidth(); + pos.bottom = pos.top + $trigger.outerHeight(); + + if (opt.$layer && !opt.hovering && (!(e.pageX >= pos.left && e.pageX <= pos.right) || !(e.pageY >= pos.top && e.pageY <= pos.bottom))) { + // if mouse in menu... + opt.$menu.trigger('contextmenu:hide'); + } + }); + } + }, + hide: function(opt, force) { + var $trigger = $(this); + if (!opt) { + opt = $trigger.data('contextMenu') || {}; + } + + // hide event + if (!force && opt.events && opt.events.hide.call($trigger, opt) === false) { + return; + } + + // remove options and revert state + $trigger + .removeData('contextMenu') + .removeClass("context-menu-active"); + + if (opt.$layer) { + // keep layer for a bit so the contextmenu event can be aborted properly by opera + setTimeout((function($layer) { + return function(){ + $layer.remove(); + }; + })(opt.$layer), 10); + + try { + delete opt.$layer; + } catch(e) { + opt.$layer = null; + } + } + + // remove handle + $currentTrigger = null; + // remove selected + opt.$menu.find('.hover').trigger('contextmenu:blur'); + opt.$selected = null; + // unregister key and mouse handlers + //$(document).off('.contextMenuAutoHide keydown.contextMenu'); // http://bugs.jquery.com/ticket/10705 + $(document).off('.contextMenuAutoHide').off('keydown.contextMenu'); + // hide menu + opt.$menu && opt.$menu[opt.animation.hide](opt.animation.duration, function (){ + // tear down dynamically built menu after animation is completed. + if (opt.build) { + opt.$menu.remove(); + $.each(opt, function(key, value) { + switch (key) { + case 'ns': + case 'selector': + case 'build': + case 'trigger': + return true; + + default: + opt[key] = undefined; + try { + delete opt[key]; + } catch (e) {} + return true; + } + }); + } + + setTimeout(function() { + $trigger.trigger('contextmenu:hidden'); + }, 10); + }); + }, + create: function(opt, root) { + if (root === undefined) { + root = opt; + } + // create contextMenu + opt.$menu = $('
    ').addClass(opt.className || "").data({ + 'contextMenu': opt, + 'contextMenuRoot': root + }); + + $.each(['callbacks', 'commands', 'inputs'], function(i,k){ + opt[k] = {}; + if (!root[k]) { + root[k] = {}; + } + }); + + root.accesskeys || (root.accesskeys = {}); + + // create contextMenu items + $.each(opt.items, function(key, item){ + var $t = $('
  • ').addClass(item.className || ""), + $label = null, + $input = null; + + // iOS needs to see a click-event bound to an element to actually + // have the TouchEvents infrastructure trigger the click event + $t.on('click', $.noop); + + item.$node = $t.data({ + 'contextMenu': opt, + 'contextMenuRoot': root, + 'contextMenuKey': key + }); + + // register accesskey + // NOTE: the accesskey attribute should be applicable to any element, but Safari5 and Chrome13 still can't do that + if (item.accesskey) { + var aks = splitAccesskey(item.accesskey); + for (var i=0, ak; ak = aks[i]; i++) { + if (!root.accesskeys[ak]) { + root.accesskeys[ak] = item; + item._name = item.name.replace(new RegExp('(' + ak + ')', 'i'), '$1'); + break; + } + } + } + + if (typeof item == "string") { + $t.addClass('context-menu-separator not-selectable'); + } else if (item.type && types[item.type]) { + // run custom type handler + types[item.type].call($t, item, opt, root); + // register commands + $.each([opt, root], function(i,k){ + k.commands[key] = item; + if ($.isFunction(item.callback)) { + k.callbacks[key] = item.callback; + } + }); + } else { + // add label for input + if (item.type == 'html') { + $t.addClass('context-menu-html not-selectable'); + } else if (item.type) { + $label = $('').appendTo($t); + $('').html(item._name || item.name).appendTo($label); + $t.addClass('context-menu-input'); + opt.hasTypes = true; + $.each([opt, root], function(i,k){ + k.commands[key] = item; + k.inputs[key] = item; + }); + } else if (item.items) { + item.type = 'sub'; + } + + switch (item.type) { + case 'text': + $input = $('') + .attr('name', 'context-menu-input-' + key) + .val(item.value || "") + .appendTo($label); + break; + + case 'textarea': + $input = $('') + .attr('name', 'context-menu-input-' + key) + .val(item.value || "") + .appendTo($label); + + if (item.height) { + $input.height(item.height); + } + break; + + case 'checkbox': + $input = $('') + .attr('name', 'context-menu-input-' + key) + .val(item.value || "") + .prop("checked", !!item.selected) + .prependTo($label); + break; + + case 'radio': + $input = $('') + .attr('name', 'context-menu-input-' + item.radio) + .val(item.value || "") + .prop("checked", !!item.selected) + .prependTo($label); + break; + + case 'select': + $input = $(' + if (item.type && item.type != 'sub' && item.type != 'html') { + $input + .on('focus', handle.focusInput) + .on('blur', handle.blurInput); + + if (item.events) { + $input.on(item.events, opt); + } + } + + // add icons + if (item.icon) { + $t.addClass("icon icon-" + item.icon); + } + } + + // cache contained elements + item.$input = $input; + item.$label = $label; + + // attach item to menu + $t.appendTo(opt.$menu); + + // Disable text selection + if (!opt.hasTypes && $.support.eventSelectstart) { + // browsers support user-select: none, + // IE has a special event for text-selection + // browsers supporting neither will not be preventing text-selection + $t.on('selectstart.disableTextSelect', handle.abortevent); + } + }); + // attach contextMenu to (to bypass any possible overflow:hidden issues on parents of the trigger element) + if (!opt.$node) { + opt.$menu.css('display', 'none').addClass('context-menu-root'); + } + opt.$menu.appendTo(opt.appendTo || document.body); + }, + resize: function($menu, nested) { + // determine widths of submenus, as CSS won't grow them automatically + // position:absolute within position:absolute; min-width:100; max-width:200; results in width: 100; + // kinda sucks hard... + + // determine width of absolutely positioned element + $menu.css({position: 'absolute', display: 'block'}); + // don't apply yet, because that would break nested elements' widths + // add a pixel to circumvent word-break issue in IE9 - #80 + $menu.data('width', Math.ceil($menu.width()) + 1); + // reset styles so they allow nested elements to grow/shrink naturally + $menu.css({ + position: 'static', + minWidth: '0px', + maxWidth: '100000px' + }); + // identify width of nested menus + $menu.find('> li > ul').each(function() { + op.resize($(this), true); + }); + // reset and apply changes in the end because nested + // elements' widths wouldn't be calculatable otherwise + if (!nested) { + $menu.find('ul').andSelf().css({ + position: '', + display: '', + minWidth: '', + maxWidth: '' + }).width(function() { + return $(this).data('width'); + }); + } + }, + update: function(opt, root) { + var $trigger = this; + if (root === undefined) { + root = opt; + op.resize(opt.$menu); + } + // re-check disabled for each item + opt.$menu.children().each(function(){ + var $item = $(this), + key = $item.data('contextMenuKey'), + item = opt.items[key], + disabled = ($.isFunction(item.disabled) && item.disabled.call($trigger, key, root)) || item.disabled === true; + + // dis- / enable item + $item[disabled ? 'addClass' : 'removeClass']('disabled'); + + if (item.type) { + // dis- / enable input elements + $item.find('input, select, textarea').prop('disabled', disabled); + + // update input states + switch (item.type) { + case 'text': + case 'textarea': + item.$input.val(item.value || ""); + break; + + case 'checkbox': + case 'radio': + item.$input.val(item.value || "").prop('checked', !!item.selected); + break; + + case 'select': + item.$input.val(item.selected || ""); + break; + } + } + + if (item.$menu) { + // update sub-menu + op.update.call($trigger, item, root); + } + }); + }, + layer: function(opt, zIndex) { + // add transparent layer for click area + // filter and background for Internet Explorer, Issue #23 + var $layer = opt.$layer = $('
    ') + .css({height: $win.height(), width: $win.width(), display: 'block'}) + .data('contextMenuRoot', opt) + .insertBefore(this) + .on('contextmenu', handle.abortevent) + .on('mousedown', handle.layerClick); + + // IE6 doesn't know position:fixed; + if (!$.support.fixedPosition) { + $layer.css({ + 'position' : 'absolute', + 'height' : $(document).height() + }); + } + + return $layer; + } + }; + +// split accesskey according to http://www.whatwg.org/specs/web-apps/current-work/multipage/editing.html#assigned-access-key +function splitAccesskey(val) { + var t = val.split(/\s+/), + keys = []; + + for (var i=0, k; k = t[i]; i++) { + k = k[0].toUpperCase(); // first character only + // theoretically non-accessible characters should be ignored, but different systems, different keyboard layouts, ... screw it. + // a map to look up already used access keys would be nice + keys.push(k); + } + + return keys; +} + +// handle contextMenu triggers +$.fn.contextMenu = function(operation) { + if (operation === undefined) { + this.first().trigger('contextmenu'); + } else if (operation.x && operation.y) { + this.first().trigger($.Event("contextmenu", {pageX: operation.x, pageY: operation.y})); + } else if (operation === "hide") { + var $menu = this.data('contextMenu').$menu; + $menu && $menu.trigger('contextmenu:hide'); + } else if (operation === "destroy") { + $.contextMenu("destroy", {context: this}); + } else if ($.isPlainObject(operation)) { + operation.context = this; + $.contextMenu("create", operation); + } else if (operation) { + this.removeClass('context-menu-disabled'); + } else if (!operation) { + this.addClass('context-menu-disabled'); + } + + return this; +}; + +// manage contextMenu instances +$.contextMenu = function(operation, options) { + if (typeof operation != 'string') { + options = operation; + operation = 'create'; + } + + if (typeof options == 'string') { + options = {selector: options}; + } else if (options === undefined) { + options = {}; + } + + // merge with default options + var o = $.extend(true, {}, defaults, options || {}); + var $document = $(document); + var $context = $document; + var _hasContext = false; + + if (!o.context || !o.context.length) { + o.context = document; + } else { + // you never know what they throw at you... + $context = $(o.context).first(); + o.context = $context.get(0); + _hasContext = o.context !== document; + } + + switch (operation) { + case 'create': + // no selector no joy + if (!o.selector) { + throw new Error('No selector specified'); + } + // make sure internal classes are not bound to + if (o.selector.match(/.context-menu-(list|item|input)($|\s)/)) { + throw new Error('Cannot bind to selector "' + o.selector + '" as it contains a reserved className'); + } + if (!o.build && (!o.items || $.isEmptyObject(o.items))) { + throw new Error('No Items sepcified'); + } + counter ++; + o.ns = '.contextMenu' + counter; + if (!_hasContext) { + namespaces[o.selector] = o.ns; + } + menus[o.ns] = o; + + // default to right click + if (!o.trigger) { + o.trigger = 'right'; + } + + if (!initialized) { + // make sure item click is registered first + $document + .on({ + 'contextmenu:hide.contextMenu': handle.hideMenu, + 'prevcommand.contextMenu': handle.prevItem, + 'nextcommand.contextMenu': handle.nextItem, + 'contextmenu.contextMenu': handle.abortevent, + 'mouseenter.contextMenu': handle.menuMouseenter, + 'mouseleave.contextMenu': handle.menuMouseleave + }, '.context-menu-list') + .on('mouseup.contextMenu', '.context-menu-input', handle.inputClick) + .on({ + 'mouseup.contextMenu': handle.itemClick, + 'contextmenu:focus.contextMenu': handle.focusItem, + 'contextmenu:blur.contextMenu': handle.blurItem, + 'contextmenu.contextMenu': handle.abortevent, + 'mouseenter.contextMenu': handle.itemMouseenter, + 'mouseleave.contextMenu': handle.itemMouseleave + }, '.context-menu-item'); + + initialized = true; + } + + // engage native contextmenu event + $context + .on('contextmenu' + o.ns, o.selector, o, handle.contextmenu); + + if (_hasContext) { + // add remove hook, just in case + $context.on('remove' + o.ns, function() { + $(this).contextMenu("destroy"); + }); + } + + switch (o.trigger) { + case 'hover': + $context + .on('mouseenter' + o.ns, o.selector, o, handle.mouseenter) + .on('mouseleave' + o.ns, o.selector, o, handle.mouseleave); + break; + + case 'left': + $context.on('click' + o.ns, o.selector, o, handle.click); + break; + /* + default: + // http://www.quirksmode.org/dom/events/contextmenu.html + $document + .on('mousedown' + o.ns, o.selector, o, handle.mousedown) + .on('mouseup' + o.ns, o.selector, o, handle.mouseup); + break; + */ + } + + // create menu + if (!o.build) { + op.create(o); + } + break; + + case 'destroy': + var $visibleMenu; + if (_hasContext) { + // get proper options + var context = o.context; + $.each(menus, function(ns, o) { + if (o.context !== context) { + return true; + } + + $visibleMenu = $('.context-menu-list').filter(':visible'); + if ($visibleMenu.length && $visibleMenu.data().contextMenuRoot.$trigger.is($(o.context).find(o.selector))) { + $visibleMenu.trigger('contextmenu:hide', {force: true}); + } + + try { + if (menus[o.ns].$menu) { + menus[o.ns].$menu.remove(); + } + + delete menus[o.ns]; + } catch(e) { + menus[o.ns] = null; + } + + $(o.context).off(o.ns); + + return true; + }); + } else if (!o.selector) { + $document.off('.contextMenu .contextMenuAutoHide'); + $.each(menus, function(ns, o) { + $(o.context).off(o.ns); + }); + + namespaces = {}; + menus = {}; + counter = 0; + initialized = false; + + $('#context-menu-layer, .context-menu-list').remove(); + } else if (namespaces[o.selector]) { + $visibleMenu = $('.context-menu-list').filter(':visible'); + if ($visibleMenu.length && $visibleMenu.data().contextMenuRoot.$trigger.is(o.selector)) { + $visibleMenu.trigger('contextmenu:hide', {force: true}); + } + + try { + if (menus[namespaces[o.selector]].$menu) { + menus[namespaces[o.selector]].$menu.remove(); + } + + delete menus[namespaces[o.selector]]; + } catch(e) { + menus[namespaces[o.selector]] = null; + } + + $document.off(namespaces[o.selector]); + } + break; + + case 'html5': + // if or are not handled by the browser, + // or options was a bool true, + // initialize $.contextMenu for them + if ((!$.support.htmlCommand && !$.support.htmlMenuitem) || (typeof options == "boolean" && options)) { + $('menu[type="context"]').each(function() { + if (this.id) { + $.contextMenu({ + selector: '[contextmenu=' + this.id +']', + items: $.contextMenu.fromMenu(this) + }); + } + }).css('display', 'none'); + } + break; + + default: + throw new Error('Unknown operation "' + operation + '"'); + } + + return this; +}; + +// import values into commands +$.contextMenu.setInputValues = function(opt, data) { + if (data === undefined) { + data = {}; + } + + $.each(opt.inputs, function(key, item) { + switch (item.type) { + case 'text': + case 'textarea': + item.value = data[key] || ""; + break; + + case 'checkbox': + item.selected = data[key] ? true : false; + break; + + case 'radio': + item.selected = (data[item.radio] || "") == item.value ? true : false; + break; + + case 'select': + item.selected = data[key] || ""; + break; + } + }); +}; + +// export values from commands +$.contextMenu.getInputValues = function(opt, data) { + if (data === undefined) { + data = {}; + } + + $.each(opt.inputs, function(key, item) { + switch (item.type) { + case 'text': + case 'textarea': + case 'select': + data[key] = item.$input.val(); + break; + + case 'checkbox': + data[key] = item.$input.prop('checked'); + break; + + case 'radio': + if (item.$input.prop('checked')) { + data[item.radio] = item.value; + } + break; + } + }); + + return data; +}; + +// find