diff --git a/README.md b/README.md
index e5a7dfd..3e2c305 100644
--- a/README.md
+++ b/README.md
@@ -48,6 +48,7 @@ Option | Description
`visible` | Show/hide item (result must be boolean).
`action` | Item method, mandatory if not url defined.
`disabled` | Disable menu item.
+`submenu` | Sub menu items.
Example:
@@ -58,6 +59,12 @@ Example:
"visible": someMethod() == '1',
"action": $root.someAction
},
+ "Sub menu": {
+ submenu: {
+ "Sub Option one": function() { alert("submenu,oh, you clicked me"); },
+ "Sub Option two": { action: function() { alert("submenu,you are on fire"); } }
+ }
+ },
"One method": $root.test
}
```
diff --git a/render/index.html b/render/index.html
index e363ca1..9a914a3 100644
--- a/render/index.html
+++ b/render/index.html
@@ -11,9 +11,16 @@
var menu = {
'Option one': function() { alert('oh, you clicked me'); },
'Option two': { action: function() { alert('you are on fire'); } },
+ 'Option 3': { action: function() { alert('you are on fire'); } },
'a separator': { separator: true },
+ 'Sub menu': { submenu: {
+ 'Sub Option one': function() { alert('submenu,oh, you clicked me'); },
+ 'Sub Option two': { action: function() { alert('submenu,you are on fire'); } }
+ } },
'Some boolean': ko.observable(true),
- 'Option three': { text: 'Final action', action: function() { /* nothing to do */ } }
+ '2ed separator': { separator: true },
+ 'Option 4': { action: function() { alert('you are on fire'); } },
+ 'Option three': { text: 'Final action', action: function() { /* nothing to do */ } },
};
ko.applyBindings({});
diff --git a/src/knockout.contextmenu.js b/src/knockout.contextmenu.js
index 38aa8c0..fafdc92 100644
--- a/src/knockout.contextmenu.js
+++ b/src/knockout.contextmenu.js
@@ -154,12 +154,15 @@ function bindContextMenu(ko) {
);
props.forEach(function (eventNameOutsideClosure) {
- pushItem(eventNameOutsideClosure);
+ pushItem(eventNameOutsideClosure, eventsToHandle, elements, items, actions);
});
if (elements.length) {
menu = document.createElement('div');
menu.className = defaultClass;
+ items.forEach(function (item) {
+ hasChecks = hasChecks || (item.isBoolean && item.isVisible);
+ });
// you may need padding to menus that has checks
menu.innerHTML = '
' +
@@ -174,8 +177,22 @@ function bindContextMenu(ko) {
if (!result && event) {
event.preventDefault();
}
- });
- });
+ }
+ );
+ if (items[index].hasSubmenu) {
+ var submenu = menu.children[0].children[index].children[1];
+ items[index].submenu.elements.forEach(function (item, subindex) {
+ registerEvent(submenu.children[subindex], 'click', function (event) {
+ var result = items[index].submenu.actions[subindex](viewModel, event);
+
+ if (!result && event) {
+ event.preventDefault();
+ }
+ });
+ });
+ }
+ }
+ );
}
return {
@@ -191,50 +208,52 @@ function bindContextMenu(ko) {
},
};
- function pushItem(eventName) {
- var item = getMenuProperties(eventName);
- var classes = [];
- var id = '';
- var liHtml;
-
- if (item.isVisible) {
- hasChecks = hasChecks || item.isBoolean;
+ }
- if (item.id) {
- id = item.id;
- }
+ function pushItem(eventName, eventsToHandle, elements, items, actions) {
+ var item = getMenuProperties(eventName, eventsToHandle);
+ var classes = [];
+ var id = '';
+ var liHtml;
- // set css classes
- if (item.isChecked) {
- classes.push('checked');
- }
+ if (item.isVisible) {
+ if (item.id) {
+ id = item.id;
+ }
- if (item.isDisabled) {
- classes.push('disabled');
- }
+ // set css classes
+ if (item.isChecked) {
+ classes.push('checked');
+ }
- if (item.isSeparator) {
- classes.push('separator');
- }
+ if (item.isDisabled) {
+ classes.push('disabled');
+ }
- if (item.url) {
- classes.push('with-url');
- }
+ if (item.isSeparator) {
+ classes.push('separator');
+ }
- liHtml = '- ' +
- item.html +
- '
';
+ if (item.hasSubmenu) {
+ classes.push('has-submenu');
+ }
- elements.push(liHtml);
- actions.push(item.action);
+ if (item.url) {
+ classes.push('with-url');
}
+ liHtml = '- ' +
+ item.html +
+ '
';
+
+ elements.push(liHtml);
+ actions.push(item.action);
items.push(item);
}
}
- function getMenuProperties(eventName) {
+ function getMenuProperties(eventName, eventsToHandle) {
var text = '';
var html = '';
var currentEvent = ko.isObservable(eventsToHandle) ?
@@ -254,7 +273,8 @@ function bindContextMenu(ko) {
var isBoolean = false;
var isDisabled = !isEnabled;
var isSeparator = !!currentEvent.separator;
-
+ var hasSubmenu = !!currentEvent.submenu;
+ var submenu = {};
if (!isSeparator) {
text = isObservable(item.text) ? item.text() : item.text;
@@ -262,7 +282,26 @@ function bindContextMenu(ko) {
text = eventName;
}
- if (url) {
+ if (hasSubmenu) {
+ var props = Object.keys(
+ ko.isObservable(currentEvent.submenu) ?
+ currentEvent.submenu() :
+ currentEvent.submenu
+ );
+ submenu.elements = [];
+ submenu.actions = [];
+ submenu.items = [];
+
+ props.forEach(function (eventNameOutsideClosure) {
+ pushItem(eventNameOutsideClosure,
+ currentEvent.submenu,
+ submenu.elements, submenu.items, submenu.actions);
+ });
+
+ html = '' + text + '';
+ } else if (url) {
html = '' + text + '';
} else {
html = text;
@@ -291,6 +330,8 @@ function bindContextMenu(ko) {
isBoolean: isBoolean,
isSeparator: isSeparator,
action: action,
+ hasSubmenu: hasSubmenu,
+ submenu: submenu,
};
function action(viewModel, event) {
@@ -308,7 +349,7 @@ function bindContextMenu(ko) {
// is an object? well, lets check it properties
else if (typeof item === 'object') {
// check if has an action or if its a separator
- if (!item.action && !url && !isSeparator) {
+ if (!item.action && !url && !isSeparator && !hasSubmenu) {
throw error;
}
diff --git a/src/knockout.contextmenu.less b/src/knockout.contextmenu.less
index 1883578..6277865 100644
--- a/src/knockout.contextmenu.less
+++ b/src/knockout.contextmenu.less
@@ -69,3 +69,29 @@
.context-menu ul > li.separator:hover {
background-color: @back-color;
}
+
+.context-menu ul .submenu {
+ display: none;
+ position: absolute;
+ left: 100%;
+ margin-left: 1px;
+ background: @back-color;
+ margin-top:-30px;
+}
+
+.context-menu ul .submenu li {
+ background: @back-color;
+}
+.context-menu ul .submenu > li:hover {
+ background-color: @hover-color;
+}
+
+
+.context-menu ul > .has-submenu:before {
+ position: absolute;
+ content: ">";
+ right: 7px;
+}
+.context-menu ul .has-submenu:hover .submenu {
+ display: block;
+}