diff --git a/src/jquery.contextMenu.js b/src/jquery.contextMenu.js index e022dd1b..0a8fff8a 100755 --- a/src/jquery.contextMenu.js +++ b/src/jquery.contextMenu.js @@ -194,6 +194,11 @@ }, // position the sub-menu positionSubmenu: function ($menu) { + if ($menu === undefined) { + //When user hovers over item (which has sub items) handle.focusItem will call this. + //but the submenu does not exist yet if opt.items is a promise. just return, will call positionSubmenu after promise is completed. + return; + } if ($.ui && $.ui.position) { // .position() is provided as a jQuery UI utility // (...and it won't work on hidden elements) @@ -1159,6 +1164,8 @@ $t.addClass('context-menu-separator ' + root.classNames.notSelectable); } else if (item.type === 'html') { $t.addClass('context-menu-html ' + root.classNames.notSelectable); + } else if (item.type === 'sub') { + //we don't want to execute the next else-if if it is a sub. } else if (item.type) { $label = $('').appendTo($t); createNameNode(item).appendTo($label); @@ -1225,11 +1232,18 @@ case 'sub': createNameNode(item).appendTo($t); - item.appendTo = item.$node; - op.create(item, root); $t.data('contextMenu', item).addClass('context-menu-submenu'); item.callback = null; + //if item contains items, and this is a promise, we should create it later + //check if subitems is of type promise. If it is a promise we need to create it later, after promise has been resolved + if ('function' === typeof item.items.then) { + // probably a promise, process it, when completed it will create the sub menu's. + op.processPromises(item, root, item.items); + } else { + // normal submenu. + op.create(item, root); + } break; case 'html': @@ -1411,6 +1425,37 @@ } return $layer; + }, + processPromises: function (opt, root, promise) { + function completedPromise(opt,root,items) { + //completed promise (dev called promise.resolve) + //we now have a list of items which can be used to create the rest of the context menu. + if (items === undefined) { + //meh, null result, dev should have checked + errorPromise(undefined);//own error object + } + finishPromiseProcess(opt,root, items); + }; + function errorPromise(opt,root,errorItem) { + //user called promise.reject() with an error item, if not, provide own error item. + if (errorItem === undefined) { + errorItem = { "error": { name: "No items and no error item", icon: "context-menu-icon context-menu-icon-quit" } }; + if (window.console) { + (console.error || console.log).call(console, 'When you reject a promise, provide an "items" object, equal to normal sub-menu items'); + } + }else if(typeof errorItem === 'string'){ + errorItem = { "error": { name: errorItem } }; + } + finishPromiseProcess(opt,root,errorItem); + }; + function finishPromiseProcess(opt,root,items) { + opt.items = items;//override promise to items. + op.create(opt, root, true);//create submenu + op.update(opt, root);//correctly update position if user is already hovered over menu item + root.positionSubmenu.call(opt.$node, opt.$menu); //positionSubmenu, will only do anything if user already hovered over menu item that just got new subitems. + }; + //wait for promise completion. .then(success, error, notify) (we don't track notify). Bind the opt and root to avoid scope problems + promise.then(completedPromise.bind(this, opt, root), errorPromise.bind(this, opt, root)); } };