├── demo └── example.gif ├── LICENSE ├── contextmenu.min.css ├── themes ├── contextmenu_light.min.css ├── contextmenu_dark.min.css ├── contextmenu_light.css └── contextmenu_dark.css ├── contextmenu.css ├── index.html ├── contextmenu.min.js ├── README.md └── contextmenu.js /demo/example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m-thalmann/contextmenujs/HEAD/demo/example.gif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Matthias Thalmann 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /contextmenu.min.css: -------------------------------------------------------------------------------- 1 | .cm_container{position:fixed;opacity:0;transform:scale(0);transition:transform 0.1s;transform-origin:top left;padding:0}.cm_container.display{opacity:1;transform:scale(1)}.cm_container,.cm_container *{box-sizing:border-box}.cm_container *{position:relative}.cm_container ul{list-style-type:none;padding:0;margin:0;background-color:#ccc;box-shadow:0 0 5px #333}.cm_container li{padding:5px 10px;padding-right:1.7em;cursor:pointer;white-space:nowrap}.cm_container li:hover{background-color:#bbb}.cm_container li .cm_icon_span{width:1.5em;height:1.2em;vertical-align:bottom;display:inline-block;border-right:1px solid #aaa;margin-right:5px;padding-right:5px;text-align:center}.cm_container li .cm_sub_span{width:1em;display:inline-block;text-align:center;position:absolute;top:50%;right:.5em;transform:translateY(-50%)}.cm_container li>ul{position:absolute;top:0;left:100%;opacity:0;transition:opacity 0.2s;visibility:hidden}.cm_container li:hover>ul{opacity:1;visibility:visible}.cm_container li.cm_divider{border-bottom:1px solid #aaa;margin:5px;padding:0;cursor:default}.cm_container li.cm_divider:hover{background-color:inherit}.cm_container.cm_border_right>ul ul{left:unset;right:100%}.cm_container.cm_border_bottom>ul ul{top:unset;bottom:0}.cm_container li[disabled=""]{color:#777;cursor:default}.cm_container li[disabled=""]:hover{background-color:inherit} 2 | -------------------------------------------------------------------------------- /themes/contextmenu_light.min.css: -------------------------------------------------------------------------------- 1 | .cm_container{position:fixed;opacity:0;transform:scale(0);transition:transform 0.1s;transform-origin:top left;padding:0}.cm_container.display{opacity:1;transform:scale(1)}.cm_container,.cm_container *{box-sizing:border-box}.cm_container *{position:relative}.cm_container ul{list-style-type:none;padding:0;margin:0;background-color:#dbdbdb;box-shadow:0 0 5px #626262;border-radius:1px}.cm_container li{padding:5px 10px;padding-right:1.7em;cursor:pointer;white-space:nowrap}.cm_container li:hover{background-color:#b0b0b0}.cm_container li .cm_icon_span{width:1.5em;height:1.2em;vertical-align:bottom;display:inline-block;margin-right:5px;padding-right:5px;text-align:center}.cm_container li .cm_sub_span{width:1em;display:inline-block;text-align:center;position:absolute;top:50%;right:.5em;transform:translateY(-50%)}.cm_container li>ul{position:absolute;top:0;left:100%;opacity:0;transition:opacity 0.2s;visibility:hidden}.cm_container li:hover>ul{opacity:1;visibility:visible}.cm_container li.cm_divider{border-bottom:1px solid #aaa;margin:5px;padding:0;cursor:default}.cm_container li.cm_divider:hover{background-color:inherit}.cm_container.cm_border_right>ul ul{left:unset;right:100%}.cm_container.cm_border_bottom>ul ul{top:unset;bottom:0}.cm_container li[disabled=""]{color:#777;cursor:default}.cm_container li[disabled=""]:hover{background-color:inherit} 2 | -------------------------------------------------------------------------------- /themes/contextmenu_dark.min.css: -------------------------------------------------------------------------------- 1 | .cm_container{position:fixed;opacity:0;transform:scale(0);transition:transform 0.1s;transform-origin:top left;padding:0}.cm_container.display{opacity:1;transform:scale(1)}.cm_container,.cm_container *{box-sizing:border-box}.cm_container *{position:relative}.cm_container ul{list-style-type:none;padding:0;margin:0;background-color:#2d2d2d;box-shadow:0 0 5px #000;border-radius:1px}.cm_container li{padding:5px 10px;padding-right:1.7em;cursor:pointer;white-space:nowrap;color:#ccc}.cm_container li:hover{background-color:#242424}.cm_container li .cm_icon_span{width:1.5em;height:1.2em;vertical-align:bottom;display:inline-block;margin-right:5px;padding-right:5px;text-align:center}.cm_container li .cm_sub_span{width:1em;display:inline-block;text-align:center;position:absolute;top:50%;right:.5em;transform:translateY(-50%)}.cm_container li>ul{position:absolute;top:0;left:100%;opacity:0;transition:opacity 0.2s;visibility:hidden}.cm_container li:hover>ul{opacity:1;visibility:visible}.cm_container li.cm_divider{border-bottom:1px solid #aaa;margin:5px;padding:0;cursor:default}.cm_container li.cm_divider:hover{background-color:inherit}.cm_container.cm_border_right>ul ul{left:unset;right:100%}.cm_container.cm_border_bottom>ul ul{top:unset;bottom:0}.cm_container li[disabled=""]{color:#777;cursor:default}.cm_container li[disabled=""]:hover{background-color:inherit} 2 | -------------------------------------------------------------------------------- /contextmenu.css: -------------------------------------------------------------------------------- 1 | .cm_container{ 2 | position: fixed; 3 | opacity: 0; 4 | transform: scale(0); 5 | transition: transform 0.1s; 6 | transform-origin: top left; 7 | padding: 0; 8 | } 9 | 10 | .cm_container.display{ 11 | opacity: 1; 12 | transform: scale(1); 13 | } 14 | 15 | .cm_container, .cm_container *{ 16 | box-sizing: border-box; 17 | } 18 | 19 | .cm_container *{ 20 | position: relative; 21 | } 22 | 23 | .cm_container ul{ 24 | list-style-type: none; 25 | padding: 0; 26 | margin: 0; 27 | background-color: #ccc; 28 | box-shadow: 0 0 5px #333; 29 | } 30 | 31 | .cm_container li{ 32 | padding: 5px 10px; 33 | padding-right: 1.7em; 34 | cursor: pointer; 35 | white-space: nowrap; 36 | } 37 | 38 | .cm_container li:hover{ 39 | background-color: #bbb; 40 | } 41 | 42 | .cm_container li .cm_icon_span{ 43 | width: 1.5em; 44 | height: 1.2em; 45 | vertical-align: bottom; 46 | display: inline-block; 47 | border-right: 1px solid #aaa; 48 | margin-right: 5px; 49 | padding-right: 5px; 50 | text-align: center; 51 | } 52 | 53 | .cm_container li .cm_sub_span{ 54 | width: 1em; 55 | display: inline-block; 56 | text-align: center; 57 | position: absolute; 58 | top: 50%; 59 | right: 0.5em; 60 | transform: translateY(-50%); 61 | } 62 | 63 | .cm_container li > ul{ 64 | position: absolute; 65 | top: 0; 66 | left: 100%; 67 | opacity: 0; 68 | transition: opacity 0.2s; 69 | visibility: hidden; 70 | } 71 | 72 | .cm_container li:hover > ul{ 73 | opacity: 1; 74 | visibility: visible; 75 | } 76 | 77 | .cm_container li.cm_divider{ 78 | border-bottom: 1px solid #aaa; 79 | margin: 5px; 80 | padding: 0; 81 | cursor: default; 82 | } 83 | 84 | .cm_container li.cm_divider:hover{ 85 | background-color: inherit; 86 | } 87 | 88 | .cm_container.cm_border_right > ul ul{ 89 | left: unset; 90 | right: 100%; 91 | } 92 | 93 | .cm_container.cm_border_bottom > ul ul{ 94 | top: unset; 95 | bottom: 0; 96 | } 97 | 98 | .cm_container li[disabled=""]{ 99 | color: #777; 100 | cursor: default; 101 | } 102 | 103 | .cm_container li[disabled=""]:hover{ 104 | background-color: inherit; 105 | } 106 | -------------------------------------------------------------------------------- /themes/contextmenu_light.css: -------------------------------------------------------------------------------- 1 | .cm_container{ 2 | position: fixed; 3 | opacity: 0; 4 | transform: scale(0); 5 | transition: transform 0.1s; 6 | transform-origin: top left; 7 | padding: 0; 8 | } 9 | 10 | .cm_container.display{ 11 | opacity: 1; 12 | transform: scale(1); 13 | } 14 | 15 | .cm_container, .cm_container *{ 16 | box-sizing: border-box; 17 | } 18 | 19 | .cm_container *{ 20 | position: relative; 21 | } 22 | 23 | .cm_container ul{ 24 | list-style-type: none; 25 | padding: 0; 26 | margin: 0; 27 | background-color: #dbdbdb; 28 | box-shadow: 0 0 5px #626262; 29 | border-radius: 1px; 30 | } 31 | 32 | .cm_container li{ 33 | padding: 5px 10px; 34 | padding-right: 1.7em; 35 | cursor: pointer; 36 | white-space: nowrap; 37 | } 38 | 39 | .cm_container li:hover{ 40 | background-color: #b0b0b0; 41 | } 42 | 43 | .cm_container li .cm_icon_span{ 44 | width: 1.5em; 45 | height: 1.2em; 46 | vertical-align: bottom; 47 | display: inline-block; 48 | margin-right: 5px; 49 | padding-right: 5px; 50 | text-align: center; 51 | } 52 | 53 | .cm_container li .cm_sub_span{ 54 | width: 1em; 55 | display: inline-block; 56 | text-align: center; 57 | position: absolute; 58 | top: 50%; 59 | right: 0.5em; 60 | transform: translateY(-50%); 61 | } 62 | 63 | .cm_container li > ul{ 64 | position: absolute; 65 | top: 0; 66 | left: 100%; 67 | opacity: 0; 68 | transition: opacity 0.2s; 69 | visibility: hidden; 70 | } 71 | 72 | .cm_container li:hover > ul{ 73 | opacity: 1; 74 | visibility: visible; 75 | } 76 | 77 | .cm_container li.cm_divider{ 78 | border-bottom: 1px solid #aaa; 79 | margin: 5px; 80 | padding: 0; 81 | cursor: default; 82 | } 83 | 84 | .cm_container li.cm_divider:hover{ 85 | background-color: inherit; 86 | } 87 | 88 | .cm_container.cm_border_right > ul ul{ 89 | left: unset; 90 | right: 100%; 91 | } 92 | 93 | .cm_container.cm_border_bottom > ul ul{ 94 | top: unset; 95 | bottom: 0; 96 | } 97 | 98 | .cm_container li[disabled=""]{ 99 | color: #777; 100 | cursor: default; 101 | } 102 | 103 | .cm_container li[disabled=""]:hover{ 104 | background-color: inherit; 105 | } 106 | -------------------------------------------------------------------------------- /themes/contextmenu_dark.css: -------------------------------------------------------------------------------- 1 | .cm_container{ 2 | position: fixed; 3 | opacity: 0; 4 | transform: scale(0); 5 | transition: transform 0.1s; 6 | transform-origin: top left; 7 | padding: 0; 8 | } 9 | 10 | .cm_container.display{ 11 | opacity: 1; 12 | transform: scale(1); 13 | } 14 | 15 | .cm_container, .cm_container *{ 16 | box-sizing: border-box; 17 | } 18 | 19 | .cm_container *{ 20 | position: relative; 21 | } 22 | 23 | .cm_container ul{ 24 | list-style-type: none; 25 | padding: 0; 26 | margin: 0; 27 | background-color: #2d2d2d; 28 | box-shadow: 0 0 5px #000000; 29 | border-radius: 1px; 30 | } 31 | 32 | .cm_container li{ 33 | padding: 5px 10px; 34 | padding-right: 1.7em; 35 | cursor: pointer; 36 | white-space: nowrap; 37 | color: #ccc; 38 | } 39 | 40 | .cm_container li:hover{ 41 | background-color: #242424; 42 | } 43 | 44 | .cm_container li .cm_icon_span{ 45 | width: 1.5em; 46 | height: 1.2em; 47 | vertical-align: bottom; 48 | display: inline-block; 49 | margin-right: 5px; 50 | padding-right: 5px; 51 | text-align: center; 52 | } 53 | 54 | .cm_container li .cm_sub_span{ 55 | width: 1em; 56 | display: inline-block; 57 | text-align: center; 58 | position: absolute; 59 | top: 50%; 60 | right: 0.5em; 61 | transform: translateY(-50%); 62 | } 63 | 64 | .cm_container li > ul{ 65 | position: absolute; 66 | top: 0; 67 | left: 100%; 68 | opacity: 0; 69 | transition: opacity 0.2s; 70 | visibility: hidden; 71 | } 72 | 73 | .cm_container li:hover > ul{ 74 | opacity: 1; 75 | visibility: visible; 76 | } 77 | 78 | .cm_container li.cm_divider{ 79 | border-bottom: 1px solid #aaa; 80 | margin: 5px; 81 | padding: 0; 82 | cursor: default; 83 | } 84 | 85 | .cm_container li.cm_divider:hover{ 86 | background-color: inherit; 87 | } 88 | 89 | .cm_container.cm_border_right > ul ul{ 90 | left: unset; 91 | right: 100%; 92 | } 93 | 94 | .cm_container.cm_border_bottom > ul ul{ 95 | top: unset; 96 | bottom: 0; 97 | } 98 | 99 | .cm_container li[disabled=""]{ 100 | color: #777; 101 | cursor: default; 102 | } 103 | 104 | .cm_container li[disabled=""]:hover{ 105 | background-color: inherit; 106 | } 107 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ContextmenuJS - Demo Page 7 | 8 | 9 | 10 | 11 | 12 | 70 | 71 | 118 | 119 | 120 | 121 |
122 |

ContextMenu Test

123 |
124 | 125 |
126 |

127 | Theme: 128 | 133 |

134 | 135 |
136 | Right-click this blue box 137 |
138 |
139 | 140 | 141 | -------------------------------------------------------------------------------- /contextmenu.min.js: -------------------------------------------------------------------------------- 1 | function ContextMenu(a,b){function c(h){var j=document.createElement("ul");return h.forEach(function(k){var l=document.createElement("li");if(l.menu=f,"undefined"==typeof k.type){var m=document.createElement("span");m.className="cm_icon_span",m.innerHTML=""==ContextUtil.getProperty(k,"icon","")?ContextUtil.getProperty(b,"default_icon",""):ContextUtil.getProperty(k,"icon","");var n=document.createElement("span");n.className="cm_text",n.innerHTML=""==ContextUtil.getProperty(k,"text","")?ContextUtil.getProperty(b,"default_text","item"):ContextUtil.getProperty(k,"text","");var o=document.createElement("span");if(o.className="cm_sub_span","undefined"!=typeof k.sub&&(""==ContextUtil.getProperty(b,"sub_icon","")?o.innerHTML="›":o.innerHTML=ContextUtil.getProperty(b,"sub_icon","")),l.appendChild(m),l.appendChild(n),l.appendChild(o),!ContextUtil.getProperty(k,"enabled",!0))l.setAttribute("disabled","");else{if("object"==typeof k.events)for(var p=Object.keys(k.events),q=0;qc&&(c=g.offsetWidth),g.offsetHeight>d&&(d=g.offsetHeight);for(var h=c,j=d,f=0;fh&&(h=c+l.width),d+l.height>j&&(j=d+l.height)}}return{width:h,height:j}}}; 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ContextmenuJS 2 | 3 | ContextmenuJS is a simple JavaScript library, to display custom contextmenus (right-click). 4 | 5 | **Demo:** https://m-thalmann.github.io/contextmenujs/ 6 | 7 | ## Navigation 8 | - [Installation](#installation) 9 | - [Usage](#usage) 10 | - [Documentation](#documentation) 11 | - [ContextMenu](#contextmenu) 12 | - [Menu structure](#menu-structure) 13 | - [ContextUtil](#contextutil) 14 | - [Options](#options) 15 | - [Example](#example) 16 | 17 | ## Installation 18 | 1. Download the .zip-File and put it in your project-folder. 19 | 20 | 2. Add this script-tag to the head of the file 21 | ```html 22 | 23 | ``` 24 | 25 | 3. Add this link-tag to the head of the file, to include the styles 26 | ```html 27 | 28 | ``` 29 | 30 | 4. Start using the library! 31 | 32 | ## Usage 33 | ### Create menu structure 34 | ```javascript 35 | var menuitems = [ 36 | { 37 | "text": "Item 1", 38 | "icon": "♛", 39 | "sub": [ 40 | { 41 | "text": "Item 1.1", 42 | "enabled": false 43 | } 44 | ] 45 | }, 46 | { 47 | "text": "Item 2" 48 | } 49 | ]; 50 | ``` 51 | 52 | ### Create new contextmenu 53 | ```javascript 54 | var menu = new ContextMenu(menuitems); 55 | ``` 56 | 57 | ### Append contextmenu to listener 58 | ```javascript 59 | document.addEventListener("contextmenu", function(e){ 60 | menu.display(e); 61 | }); 62 | ``` 63 | 64 | ### (Re-)load the contextmenu 65 | ```javascript 66 | menu.reload(); // Always use this, when you change the menu 67 | ``` 68 | ## Documentation 69 | ### Contextmenu 70 | It's the main object to display the contextmenu 71 | #### Instanciating 72 | ```javascript 73 | new ContextMenu(menu_structure, options); 74 | ``` 75 | - **menu_structure** (Array): This array contains all items of the menu (see [below](#menu-structure)) 76 | - **options** (object): A object with options for the contextmenu (see [below](#options)) **(optional)** 77 | 78 | After the instanciation, the contextmenu is reloaded/rendered, but not shown 79 | 80 | #### Methods 81 | ```javascript 82 | menu.reload(); // Reloads/Renders the contextmenu inside of a container (id: cm_) 83 | menu.display(e, target); // Displays the contextmenu on the location present in the contextmenu-event (e) 84 | // and executes e.preventDefault(); 85 | // if target is set, the contextTarget is set to that 86 | menu.hide(); // Hides the menu, if shown 87 | 88 | menu.setOptions(options); // Resets the options (object) 89 | menu.changeOption(option, value); // Changes one option (string, object) 90 | menu.getOptions(); // Returns the options 91 | ``` 92 | 93 | #### Properties (variables) 94 | ```javascript 95 | menu.menu // The menu structure; change this, or the parameter, to change the menu (no setter) 96 | menu.contextTarget // The target, where the contextmenu was last opened 97 | 98 | ContextMenu.count // Number of contextmenus created (for the id's) 99 | ContextMenu.DIVIDER // Constant, that is used to mark a item as a divider ("type": ContextMenu.DIVIDER) 100 | ``` 101 | 102 | ### Menu structure 103 | It's only a guideline, to define a menu (no object etc.) 104 | 105 | ```javascript 106 | var menustructure = [ 107 | { 108 | "text": "Item1", // Text to display 109 | "icon": '', // Icon to display next to the text 110 | "sub": [ // Item has nested items 111 | { 112 | "text": "Item1.1", 113 | "enabled": false // Item is disabled (if it has nested items, they won't show) 114 | } 115 | ] 116 | }, 117 | { 118 | "type": ContextMenu.DIVIDER // This item is a divider (shows only gray line, no text etc.) 119 | }, 120 | { 121 | "text": "Item2", 122 | "events": { // Adds eventlisteners to the item (you can use any event there is) 123 | "click": function(e){ 124 | console.log("clicked"); 125 | }, 126 | "mouseover": function(e){ 127 | console.log("mouse is over menuitem"); 128 | } 129 | } 130 | } 131 | ]; 132 | ``` 133 | **NOTE:** The braces after the = and after `"sub":` are square ones! 134 | 135 | **NOTE:** Every other property, not mentioned here, is skipped! 136 | 137 | ### ContextUtil 138 | A collection of helper methods. Can't be instanciated. 139 | #### Methods 140 | ```javascript 141 | ContextUtil.getProperty(opt, o, def); // Returns the value of 'o' in the array/object opt, if it is set; 142 | // else it returns def (object, string, object) 143 | 144 | ContextUtil.getSizes(obj); // Recursively gets the size of a DOM-List (ul), that has absolute positioned 145 | // children (dom-element) 146 | ``` 147 | 148 | ### Options 149 | 150 | | Option | Values | Definition | 151 | |:---------------:|:----------:|:---------------------------------------------------------------:| 152 | | close_on_resize | true/false | Sets if the contextmenu is closed, when the window is resized | 153 | | close_on_click | true/false | Sets if the contextmenu is closed, when the window is clicked | 154 | | default_icon | [string] | Sets the default icon for a menu item (is overridden, when set) | 155 | | default_text | [string] | Sets the default text for a menu item (is overridden, when set) | 156 | | sub_icon | [string] | Sets the arrow icon for sub menus | 157 | | mouse_offset | [integer] | Sets the offset to the mouse, when opened (in pixel) | 158 | 159 | ## Example 160 | ### Code: 161 | ```javascript 162 | var menu = new ContextMenu( 163 | [ 164 | { 165 | "text": "Item 1", 166 | "sub": [ 167 | { 168 | "text": "Item 11" 169 | }, 170 | { 171 | "text": "Item 12" 172 | }, 173 | { 174 | "type": ContextMenu.DIVIDER 175 | }, 176 | { 177 | "text": "Item 13", 178 | "enabled": false, 179 | "sub": [ 180 | { 181 | "text": "Item 131" 182 | } 183 | ] 184 | } 185 | ] 186 | }, 187 | { 188 | "text": "Item 2", 189 | "icon": '', 190 | "events": { 191 | "click": function(e){ 192 | alert(e); 193 | } 194 | } 195 | } 196 | ] 197 | ); 198 | ``` 199 | 200 | ### Output: 201 | 202 | ![contextmenuJs example](demo/example.gif) 203 | -------------------------------------------------------------------------------- /contextmenu.js: -------------------------------------------------------------------------------- 1 | function ContextMenu(menu, options){ 2 | var self = this; 3 | var num = ContextMenu.count++; 4 | 5 | this.menu = menu; 6 | this.contextTarget = null; 7 | 8 | if(!(menu instanceof Array)){ 9 | throw new Error("Parameter 1 must be of type Array"); 10 | } 11 | 12 | if(typeof options !== "undefined"){ 13 | if(typeof options !== "object"){ 14 | throw new Error("Parameter 2 must be of type object"); 15 | } 16 | }else{ 17 | options = {}; 18 | } 19 | 20 | window.addEventListener("resize", function(){ 21 | if(ContextUtil.getProperty(options, "close_on_resize", true)){ 22 | self.hide(); 23 | } 24 | }); 25 | 26 | this.setOptions = function(_options){ 27 | if(typeof _options === "object"){ 28 | options = _options; 29 | }else{ 30 | throw new Error("Parameter 1 must be of type object") 31 | } 32 | } 33 | 34 | this.changeOption = function(option, value){ 35 | if(typeof option === "string"){ 36 | if(typeof value !== "undefined"){ 37 | options[option] = value; 38 | }else{ 39 | throw new Error("Parameter 2 must be set"); 40 | } 41 | }else{ 42 | throw new Error("Parameter 1 must be of type string"); 43 | } 44 | } 45 | 46 | this.getOptions = function(){ 47 | return options; 48 | } 49 | 50 | this.reload = function(){ 51 | if(document.getElementById('cm_' + num) == null){ 52 | var cnt = document.createElement("div"); 53 | cnt.className = "cm_container"; 54 | cnt.id = "cm_" + num; 55 | 56 | document.body.appendChild(cnt); 57 | } 58 | 59 | var container = document.getElementById('cm_' + num); 60 | container.innerHTML = ""; 61 | 62 | container.appendChild(renderLevel(menu)); 63 | } 64 | 65 | function renderLevel(level){ 66 | var ul_outer = document.createElement("ul"); 67 | 68 | level.forEach(function(item){ 69 | var li = document.createElement("li"); 70 | li.menu = self; 71 | 72 | if(typeof item.type === "undefined"){ 73 | var icon_span = document.createElement("span"); 74 | icon_span.className = 'cm_icon_span'; 75 | 76 | if(ContextUtil.getProperty(item, "icon", "") != ""){ 77 | icon_span.innerHTML = ContextUtil.getProperty(item, "icon", ""); 78 | }else{ 79 | icon_span.innerHTML = ContextUtil.getProperty(options, "default_icon", ""); 80 | } 81 | 82 | var text_span = document.createElement("span"); 83 | text_span.className = 'cm_text'; 84 | 85 | if(ContextUtil.getProperty(item, "text", "") != ""){ 86 | text_span.innerHTML = ContextUtil.getProperty(item, "text", ""); 87 | }else{ 88 | text_span.innerHTML = ContextUtil.getProperty(options, "default_text", "item"); 89 | } 90 | 91 | var sub_span = document.createElement("span"); 92 | sub_span.className = 'cm_sub_span'; 93 | 94 | if(typeof item.sub !== "undefined"){ 95 | if(ContextUtil.getProperty(options, "sub_icon", "") != ""){ 96 | sub_span.innerHTML = ContextUtil.getProperty(options, "sub_icon", ""); 97 | }else{ 98 | sub_span.innerHTML = '›'; 99 | } 100 | } 101 | 102 | li.appendChild(icon_span); 103 | li.appendChild(text_span); 104 | li.appendChild(sub_span); 105 | 106 | if(!ContextUtil.getProperty(item, "enabled", true)){ 107 | li.setAttribute("disabled", ""); 108 | }else{ 109 | if(typeof item.events === "object"){ 110 | var keys = Object.keys(item.events); 111 | 112 | for(var i = 0; i < keys.length; i++){ 113 | li.addEventListener(keys[i], item.events[keys[i]]); 114 | } 115 | } 116 | 117 | if(typeof item.sub !== "undefined"){ 118 | li.appendChild(renderLevel(item.sub)); 119 | } 120 | } 121 | }else{ 122 | if(item.type == ContextMenu.DIVIDER){ 123 | li.className = "cm_divider"; 124 | } 125 | } 126 | 127 | ul_outer.appendChild(li); 128 | }); 129 | 130 | return ul_outer; 131 | } 132 | 133 | this.display = function(e, target){ 134 | if(typeof target !== "undefined"){ 135 | self.contextTarget = target; 136 | }else{ 137 | self.contextTarget = e.target; 138 | } 139 | 140 | var menu = document.getElementById('cm_' + num); 141 | 142 | var clickCoords = {x: e.clientX, y: e.clientY}; 143 | var clickCoordsX = clickCoords.x; 144 | var clickCoordsY = clickCoords.y; 145 | 146 | var menuWidth = menu.offsetWidth + 4; 147 | var menuHeight = menu.offsetHeight + 4; 148 | 149 | var windowWidth = window.innerWidth; 150 | var windowHeight = window.innerHeight; 151 | 152 | var mouseOffset = parseInt(ContextUtil.getProperty(options, "mouse_offset", 2)); 153 | 154 | if((windowWidth - clickCoordsX) < menuWidth){ 155 | menu.style.left = windowWidth - menuWidth + "px"; 156 | }else{ 157 | menu.style.left = (clickCoordsX + mouseOffset) + "px"; 158 | } 159 | 160 | if((windowHeight - clickCoordsY) < menuHeight){ 161 | menu.style.top = windowHeight - menuHeight + "px"; 162 | }else{ 163 | menu.style.top = (clickCoordsY + mouseOffset) + "px"; 164 | } 165 | 166 | var sizes = ContextUtil.getSizes(menu); 167 | 168 | if((windowWidth - clickCoordsX) < sizes.width){ 169 | menu.classList.add("cm_border_right"); 170 | }else{ 171 | menu.classList.remove("cm_border_right"); 172 | } 173 | 174 | if((windowHeight - clickCoordsY) < sizes.height){ 175 | menu.classList.add("cm_border_bottom"); 176 | }else{ 177 | menu.classList.remove("cm_border_bottom"); 178 | } 179 | 180 | menu.classList.add("display"); 181 | 182 | if(ContextUtil.getProperty(options, "close_on_click", true)){ 183 | window.addEventListener("click", documentClick); 184 | } 185 | 186 | e.preventDefault(); 187 | } 188 | 189 | this.hide = function(){ 190 | document.getElementById('cm_' + num).classList.remove("display"); 191 | window.removeEventListener("click", documentClick); 192 | } 193 | 194 | function documentClick(){ 195 | self.hide(); 196 | } 197 | 198 | this.reload(); 199 | } 200 | 201 | ContextMenu.count = 0; 202 | ContextMenu.DIVIDER = "cm_divider"; 203 | 204 | const ContextUtil = { 205 | getProperty: function(options, opt, def){ 206 | if(typeof options[opt] !== "undefined"){ 207 | return options[opt]; 208 | }else{ 209 | return def; 210 | } 211 | }, 212 | 213 | getSizes: function(obj){ 214 | var lis = obj.getElementsByTagName('li'); 215 | 216 | var width_def = 0; 217 | var height_def = 0; 218 | 219 | for(var i = 0; i < lis.length; i++){ 220 | var li = lis[i]; 221 | 222 | if(li.offsetWidth > width_def){ 223 | width_def = li.offsetWidth; 224 | } 225 | 226 | if(li.offsetHeight > height_def){ 227 | height_def = li.offsetHeight; 228 | } 229 | } 230 | 231 | var width = width_def; 232 | var height = height_def; 233 | 234 | for(var i = 0; i < lis.length; i++){ 235 | var li = lis[i]; 236 | 237 | var ul = li.getElementsByTagName('ul'); 238 | if(typeof ul[0] !== "undefined"){ 239 | var ul_size = ContextUtil.getSizes(ul[0]); 240 | 241 | if(width_def + ul_size.width > width){ 242 | width = width_def + ul_size.width; 243 | } 244 | 245 | if(height_def + ul_size.height > height){ 246 | height = height_def + ul_size.height; 247 | } 248 | } 249 | } 250 | 251 | return { 252 | "width": width, 253 | "height": height 254 | }; 255 | } 256 | }; 257 | --------------------------------------------------------------------------------