diff --git a/web/pgadmin/browser/static/js/menu.js b/web/pgadmin/browser/static/js/menu.js
index e2f99ec69..26a48316e 100644
--- a/web/pgadmin/browser/static/js/menu.js
+++ b/web/pgadmin/browser/static/js/menu.js
@@ -4,7 +4,9 @@ function(_, pgAdmin, $) {
'use strict';
pgAdmin.Browser = pgAdmin.Browser || {};
- pgAdmin.Browser.MenuItem = function(opts) {
+
+ // Individual menu-item class
+ var MenuItem = pgAdmin.Browser.MenuItem = function(opts) {
var menu_opts = [
'name', 'label', 'priority', 'module', 'callback', 'data', 'enable',
'category', 'target', 'url', 'icon'
@@ -18,7 +20,32 @@ function(_, pgAdmin, $) {
};
_.extend(pgAdmin.Browser.MenuItem.prototype, {
+ /*
+ * Keeps track of the jQuery object representing this menu-item. This will
+ * be used by the update function to enable/disable the individual item.
+ */
+ $el: null,
+ /*
+ * Generate the UI for this menu-item. enable/disable, based on the
+ * currently selected object.
+ */
generate: function(node, item) {
+ this.create_el(node, item);
+
+ this.context = {
+ name: this.label,
+ icon: this.icon || this.module && (this.module.type),
+ disabled: this.is_disabled,
+ callback: this.context_menu_callback.bind(this, item)
+ };
+
+ return this.$el;
+ },
+
+ /*
+ * Create the jquery element for the menu-item.
+ */
+ create_el: function(node, item) {
var url = $('', {
'id': this.name,
'href': this.url,
@@ -29,15 +56,68 @@ function(_, pgAdmin, $) {
cb: this.callback,
data: this.data
}).addClass('menu-link');
+
if (this.icon) {
url.append($('', {'class': this.icon}));
}
url.append($('').text(' ' + this.label));
- return $('
')
- .addClass('menu-item' + (this.disabled(node, item) ? ' disabled' : ''))
+ this.is_disabled = this.disabled(node, item);
+ this.$el = $('')
+ .addClass('menu-item' + (this.is_disabled ? ' disabled' : ''))
.append(url);
},
+
+ /*
+ * Updates the enable/disable state of the menu-item based on the current
+ * selection using the disabled function. This also creates a object
+ * for this menu, which can be used in the context-menu.
+ */
+ update: function(node, item) {
+
+ if (!this.$el.hasClass('disabled')) {
+ this.$el.addClass('disabled');
+ }
+
+ this.is_disabled = this.disabled(node, item);
+ if (!this.is_disabled) {
+ this.$el.removeClass('disabled');
+ }
+
+ this.context = {
+ name: this.label,
+ icon: this.icon || (this.module && this.module.type),
+ disabled: this.is_disabled,
+ callback: this.context_menu_callback.bind(this, item)
+ };
+ },
+
+ /*
+ * This will be called when context-menu is clicked.
+ */
+ context_menu_callback: function(item) {
+ var o = this, cb;
+
+ if (o.module['callbacks'] && (
+ o.callback in o.module['callbacks']
+ )) {
+ cb = o.module['callbacks'][o.callback];
+ } else if (o.callback in o.module) {
+ cb = o.module[o.callback];
+ }
+ if (cb) {
+ cb.apply(o.module, [o.data, item]);
+ } else {
+ pgAdmin.Browser.report_error(
+ S('Developer Warning: Callback - "%s" not found!').
+ sprintf(o.cb).value()
+ );
+ }
+ },
+
+ /*
+ * Checks this menu enable/disable state based on the selection.
+ */
disabled: function(node, item) {
if (this.enable == undefined)
return false;
@@ -51,6 +131,179 @@ function(_, pgAdmin, $) {
}
});
+ /*
+ * This a class for creating a menu group, mainly used by the submenu
+ * creation logic.
+ *
+ * Arguments:
+ * 1. Options to render the submenu DOM Element.
+ * i.e. label, icon, above (separator), below (separator)
+ * 2. List of menu-items comes under this submenu.
+ * 3. Did we rendered separator after the menu-item/submenu?
+ * 4. A unique-id for this menu-item.
+ *
+ * Returns a object, similar to the menu-item, which has his own jQuery
+ * Element, context menu representing object, etc.
+ *
+ */
+
+ pgAdmin.Browser.MenuGroup = function(opts, items, prev, ctx) {
+ var template = _.template([
+ '<% if (above) { %>
<% } %>',
+ '',
+ '<% if (below) { %>
<% } %>',].join('\n')),
+ data = {
+ 'label': opts.label,
+ 'icon': opts.icon,
+ 'above': opts.above && !prev,
+ 'below': opts.below,
+ }, m,
+ $el = $(template(data)),
+ $menu = $el.find('.dropdown-menu'),
+ submenus = {},
+ ctxId = 1;
+
+ ctx = _.uniqueId(ctx + '_sub_');
+
+ // Sort by alphanumeric ordered first
+ items.sort(function(a, b) {return a.label.localeCompare(b.label);});
+ // Sort by priority
+ items.sort(function(a, b) {return a.priority - b.priority;});
+
+ for (var idx in items) {
+ m = items[idx];
+ $menu.append(m.$el);
+ if (!m.is_disabled) {
+ submenus[ctx + ctxId] = m.context
+ }
+ ctxId++;
+ }
+
+ var is_disabled = (_.size(submenus) == 0);
+
+ return {
+ $el: $el,
+ priority: opts.priority || 10,
+ label: opts.label,
+ above: data['above'],
+ below: opts.below,
+ is_disabled: is_disabled,
+ context: {
+ name: opts.label,
+ icon: opts.icon,
+ items: submenus,
+ disabled: is_disabled
+ }
+ };
+ };
+
+ /*
+ * A function to generate menus (submenus) based on the categories.
+ * Attach the current selected browser tree node to each of the generated
+ * menu-items.
+ *
+ * Arguments:
+ * 1. jQuery Element on which you may want to created the menus
+ * 2. list of menu-items
+ * 3. categories - metadata information about the categories, based on which
+ * the submenu (menu-group) will be created (if any).
+ * 4. d - Data object for the selected browser tree item.
+ * 5. item - The selected browser tree item
+ * 6. menu_items - A empty object on which the context menu for the given
+ * list of menu-items.
+ *
+ * Returns if any menu generated for the given input.
+ */
+ pgAdmin.Browser.MenuCreator = function($mnu, menus, categories, d, item, menu_items) {
+ var groups = {'common': []},
+ common, idx = 0, j, item,
+ ctxId = _.uniqueId('ctx_'),
+ update_menuitem = function(m) {
+ if (m instanceof MenuItem) {
+ if (m.$el) {
+ m.$el.remove();
+ delete m.$el;
+ }
+ m.generate(d, item);
+ var group = groups[m.category || 'common'] =
+ groups[m.category || 'common'] || [];
+ group.push(m);
+ } else {
+ for (var key in m) {
+ update_menuitem(m[key]);
+ }
+ }
+ }, ctxIdx = 1;
+
+ for (idx in menus) {
+ update_menuitem(menus[idx]);
+ }
+
+ // Not all menu creator requires the context menu structure.
+ menu_items = menu_items || {};
+
+ common = groups['common'];
+ delete groups['common'];
+
+ var prev = true;
+
+ for (name in groups) {
+ var g = groups[name],
+ c = categories[name] || {'label': name, single: false},
+ menu_group = pgAdmin.Browser.MenuGroup(c, g, prev, ctxId);
+
+ if (g.length <= 1 && !c.single) {
+ prev = false;
+ for (idx in g) {
+ common.push(g[idx]);
+ }
+ } else {
+ prev = g.below;
+ common.push(menu_group);
+ }
+ }
+
+ // The menus will be created based on the priority given.
+ // Menu with lowest value has the highest priority. If the priority is
+ // same, then - it will be ordered by label.
+ // Sort by alphanumeric ordered first
+ common.sort(function(a, b) {return a.label.localeCompare(b.label);});
+ // Sort by priority
+ common.sort(function(a, b) {return a.priority - b.priority;});
+ var len = _.size(common);
+
+ for (idx in common) {
+ item = common[idx];
+
+ item.priority = (item.priority || 10);
+ $mnu.append(item.$el);
+ var prefix = ctxId + '_' + item.priority + '_' + ctxIdx;
+
+ if (ctxIdx != 1 && item.above && !item.is_disabled) {
+ // For creatign the seprator any string will do.
+ menu_items[prefix + '_ma'] = '----';
+ }
+
+ if (!item.is_disabled) {
+ menu_items[prefix + '_ms'] = item.context;
+ }
+
+ if (ctxId != len && item.below && !item.is_disabled) {
+ menu_items[prefix + '_mz'] = '----';
+ }
+ ctxIdx++;
+ }
+
+ return (len > 0);
+ };
+
// MENU PUBLIC CLASS DEFINITION
// ==============================
diff --git a/web/pgadmin/browser/templates/browser/css/node.css b/web/pgadmin/browser/templates/browser/css/node.css
index 199423582..98895e5af 100644
--- a/web/pgadmin/browser/templates/browser/css/node.css
+++ b/web/pgadmin/browser/templates/browser/css/node.css
@@ -3,7 +3,7 @@
background-repeat: no-repeat;
align-content: center;
vertical-align: middle;
- height: 1.3em;
+ height: 20px;
}
.pgadmin-node-select option[node="{{node_type}}"] {
diff --git a/web/pgadmin/browser/templates/browser/index.html b/web/pgadmin/browser/templates/browser/index.html
index b43f102a2..89fe2a841 100644
--- a/web/pgadmin/browser/templates/browser/index.html
+++ b/web/pgadmin/browser/templates/browser/index.html
@@ -44,22 +44,9 @@ try {
{{ _('Edit') }}
-
+
{{ _('Object') }}
-
+
{{ _('Management') }}
diff --git a/web/pgadmin/browser/templates/browser/js/browser.js b/web/pgadmin/browser/templates/browser/js/browser.js
index 1463b923b..69dad684b 100644
--- a/web/pgadmin/browser/templates/browser/js/browser.js
+++ b/web/pgadmin/browser/templates/browser/js/browser.js
@@ -160,6 +160,18 @@ function(require, $, _, S, Bootstrap, pgAdmin, alertify, CodeMirror) {
// Help menus
help: {}
},
+ menu_categories: {
+ /* name, label (pair) */
+ 'create': {
+ label: '{{ _('Create')|safe }}',
+ priority: 1,
+ /* separator above this menu */
+ above: false,
+ below: true,
+ icon: 'fa fa-magic',
+ single: true
+ }
+ },
// A callback to load/fetch a script when a certain node is loaded
register_script: function(n, m, p) {
var scripts = this.scripts;
@@ -186,63 +198,57 @@ function(require, $, _, S, Bootstrap, pgAdmin, alertify, CodeMirror) {
enable_disable_menus: function(item) {
// Mechanism to enable/disable menus depending on the condition.
var obj = this, j, e,
- // menu navigation bar
- navbar = $('#navbar-menu > ul').first(),
- // Drop down menu for objects
- obj_mnu = navbar.find('li#mnu_obj > ul#mnu_dropdown_obj').first(),
- // Drop down menu for create object
- create_mnu = navbar.find("#mnu_create_obj").empty(),
- // data for current selected object
- d = this.tree.itemData(item);
+ // menu navigation bar
+ navbar = $('#navbar-menu > ul').first(),
+ // Drop down menu for objects
+ $obj_mnu = navbar.find('li#mnu_obj > ul.dropdown-menu').first(),
+ // data for current selected object
+ d = obj.tree.itemData(item),
+ update_menuitem = function(m) {
+ if (m instanceof pgAdmin.Browser.MenuItem) {
+ m.update(d, item);
+ } else {
+ for (var key in m) {
+ update_menuitem(m[key]);
+ }
+ }
+ };
+
// All menus from the object menus (except the create drop-down
// menu) needs to be removed.
- obj_mnu.children("li:not(:first-child)").remove();
- // Create a dummy 'no object seleted' menu
- create_mnu.html('\n');
+ $obj_mnu.empty();
// All menus (except for the object menus) are already present.
// They will just require to check, wheather they are
// enabled/disabled.
_.each([
- {m: 'file', id: '#mnu_file'},
- {m: 'edit', id: '#mnu_edit'},
- {m: 'management', id: '#mnu_management'},
- {m: 'tools', id: '#mnu_tools'},
- {m: 'help', id:'#mnu_help'}], function(o) {
- j = navbar.children(o.id).children('.dropdown-menu').first();
- _.each(obj.menus[o.m],
- function(v, k) {
- // Remove disabled class in any case first.
- e = j.find('#' + k).closest('.menu-item').removeClass('disabled');
- if (v.disabled(d, item)) {
- // Make this menu disabled
- e.addClass('disabled');
- }
- });
- });
+ {m: 'file', id: '#mnu_file'},
+ {m: 'edit', id: '#mnu_edit'},
+ {m: 'management', id: '#mnu_management'},
+ {m: 'tools', id: '#mnu_tools'},
+ {m: 'help', id:'#mnu_help'}], function(o) {
+ _.each(
+ obj.menus[o.m],
+ function(m, k) {
+ update_menuitem(m);
+ });
+ });
// Create the object menu dynamically
- if (item && this.menus['object'] && this.menus['object'][d._type]) {
- var create_items = [];
- // The menus will be created based on the priority given.
- // Menu with lowest value has the highest priority.
- _.each(_.sortBy(
- this.menus['object'][d._type],
- function(o) { return o.priority; }),
- function(m) {
- if (m.category && m.category == 'create') {
- create_items.push(m.generate(d, item));
- } else {
- obj_mnu.append(m.generate(d, item));
- }
- });
- // Create menus goes seperately
- if (create_items.length > 0) {
- create_mnu.empty();
- _.each(create_items, function(c) {
- create_mnu.append(c);
- });
- }
+ if (item && obj.menus['object'] && obj.menus['object'][d._type]) {
+ pgAdmin.Browser.MenuCreator(
+ $obj_mnu, obj.menus['object'][d._type], obj.menu_categories, d, item
+ )
+ } else {
+ // Create a dummy 'no object seleted' menu
+ create_submenu = pgAdmin.Browser.MenuGroup(
+ obj.menu_categories['create'], [{
+ $el: $(''),
+ priority: 1,
+ category: 'create',
+ update: function() {}
+ }], false);
+ $obj_mnu.append(create_submenu.$el);
}
},
init: function() {
@@ -347,66 +353,22 @@ function(require, $, _, S, Bootstrap, pgAdmin, alertify, CodeMirror) {
// Build the treeview context menu
$('#tree').contextMenu({
selector: '.aciTreeLine',
+ autoHide: false,
build: function(element) {
var item = obj.tree.itemFrom(element),
- menu = { },
- createMenu = { },
- d = obj.tree.itemData(item),
- menus = obj.menus['context'][d._type],
- cb = function(name) {
- var o = undefined;
+ d = obj.tree.itemData(item),
+ menus = obj.menus['context'][d._type],
+ $div = $(''),
+ context_menu = {};
- _.each(menus, function(m) {
- if (name == (m.module.type + '_' + m.name)) {
- o = m;
- }
- });
+ pgAdmin.Browser.MenuCreator(
+ $div, menus, obj.menu_categories, d, item, context_menu
+ );
- if (o) {
- var cb;
- if (o.module['callbacks'] && (
- o.callback in o.module['callbacks'])) {
- cb = o.module['callbacks'][o.callback];
- } else if (o.callback in o.module) {
- cb = o.module[o.callback];
- }
-
- if (cb) {
- cb.apply(o.module, [o.data, item]);
- } else {
- pgAdmin.Browser.report_error(
- S('Developer Warning: Callback - "%s" not found!').
- sprintf(o.cb).value());
- }
- }
+ return {
+ autoHide: false,
+ items: context_menu
};
-
- _.each(
- _.sortBy(menus, function(m) { return m.priority; }),
- function(m) {
- if (m.category == 'create' && !m.disabled(d, item)) {
- createMenu[m.module.type + '_' + m.name] = { name: m.label, icon: m.icon || m.module.type };
- }
- });
-
- if (_.size(createMenu)) {
- menu["create"] = { name: "{{ _('Create') }}", icon: 'fa fa-magic' };
- menu["create"]["items"] = createMenu;
- }
-
- _.each(
- _.sortBy(menus, function(m) { return m.priority; }),
- function(m) {
- if (m.category != 'create' && !m.disabled(d, item)) {
- menu[m.module.type + '_' + m.name] = { name: m.label, icon: m.icon };
- }
- });
-
- return _.size(menu) ? {
- autoHide: true,
- items: menu,
- callback: cb
- } : {};
}
});
@@ -529,6 +491,7 @@ function(require, $, _, S, Bootstrap, pgAdmin, alertify, CodeMirror) {
{% endif %}{% if item.url %}url: "{{ item.url }}",
{% endif %}{% if item.target %}target: "{{ item.target }}",
{% endif %}{% if item.callback %}callback: "{{ item.callback }}",
+ {% endif %}{% if item.category %}category: "{{ item.category }}",
{% endif %}{% if item.icon %}icon: '{{ item.icon }}',
{% endif %}{% if item.data %}data: {{ item.data }},
{% endif %}label: '{{ item.label }}', applies: ['{{ key.lower() }}'],
@@ -577,6 +540,18 @@ function(require, $, _, S, Bootstrap, pgAdmin, alertify, CodeMirror) {
'{{ _('Error loading script - ') }}' + path);
});
},
+ add_menu_category: function(
+ id, label, priority, icon, above_separator, below_separator, single
+ ) {
+ this.menu_categories[id] = {
+ label: label,
+ priority: priority,
+ icon: icon,
+ above: (above_separator === true),
+ below: (below_separator === true),
+ single: single
+ }
+ },
// Add menus of module/extension at appropriate menu
add_menus: function(menus) {
var pgMenu = this.menus;
@@ -591,7 +566,10 @@ function(require, $, _, S, Bootstrap, pgAdmin, alertify, CodeMirror) {
pgMenu[a] = pgMenu[a] || {};
if (_.isString(m.node)) {
menus = pgMenu[a][m.node] = pgMenu[a][m.node] || {};
- } else {
+ } else if (_.isString(m.category)) {
+ menus = pgMenu[a][m.category] = pgMenu[a][m.category] || {};
+ }
+ else {
menus = pgMenu[a];
}
@@ -623,26 +601,31 @@ function(require, $, _, S, Bootstrap, pgAdmin, alertify, CodeMirror) {
},
// Create the menus
create_menus: function() {
+
/* Create menus */
var navbar = $('#navbar-menu > ul').first();
var obj = this;
_.each([
- {menu: 'file', id: '#mnu_file'},
- {menu: 'edit', id: '#mnu_edit'},
- {menu: 'management', id: '#mnu_management'},
- {menu: 'tools', id: '#mnu_tools'},
- {menu: 'help', id:'#mnu_help'}], function(o) {
- var j = navbar.children(o.id).children('.dropdown-menu').first().empty();
- _.each(
- _.sortBy(obj.menus[o.menu],
- function(v, k) { return v.priority; }),
- function(v) {
- j.closest('.dropdown').removeClass('hide');
- j.append(v.generate());
- });
- navbar.children('#mnu_obj').removeClass('hide');
- });
+ {menu: 'file', id: '#mnu_file'},
+ {menu: 'edit', id: '#mnu_edit'},
+ {menu: 'management', id: '#mnu_management'},
+ {menu: 'tools', id: '#mnu_tools'},
+ {menu: 'help', id:'#mnu_help'}],
+ function(o) {
+ var $mnu = navbar.children(o.id).first(),
+ $dropdown = $mnu.children('.dropdown-menu').first();
+ $dropdown.empty();
+ var menus = {};
+
+ if (pgAdmin.Browser.MenuCreator(
+ $dropdown, obj.menus[o.menu], obj.menu_categories
+ )) {
+ $mnu.removeClass('hide');
+ }
+ });
+
+ navbar.children('#mnu_obj').removeClass('hide');
obj.enable_disable_menus();
},
messages: {
diff --git a/web/pgadmin/browser/templates/browser/js/collection.js b/web/pgadmin/browser/templates/browser/js/collection.js
index 77480b1a5..74f07222c 100644
--- a/web/pgadmin/browser/templates/browser/js/collection.js
+++ b/web/pgadmin/browser/templates/browser/js/collection.js
@@ -29,7 +29,7 @@ function($, _, S, pgAdmin, Backbone, Alertify, Backform) {
pgAdmin.Browser.add_menus([{
name: 'refresh', node: this.type, module: this,
applies: ['object', 'context'], callback: 'refresh',
- priority: 1, label: '{{ _("Refresh...") }}',
+ priority: 2, label: '{{ _("Refresh...") }}',
icon: 'fa fa-refresh'
}]);
},
diff --git a/web/pgadmin/test/__init__.py b/web/pgadmin/test/__init__.py
index c8ded795e..d131313ce 100644
--- a/web/pgadmin/test/__init__.py
+++ b/web/pgadmin/test/__init__.py
@@ -10,7 +10,7 @@
"""Browser integration functions for the Test module."""
MODULE_NAME = 'test'
from flask.ext.security import login_required
-from flask import render_template, url_for, current_app
+from flask import url_for, current_app
from flask.ext.babel import gettext
from pgadmin.utils import PgAdminModule
from pgadmin.utils.menu import MenuItem
@@ -22,32 +22,37 @@ class TestModule(PgAdminModule):
return {'file_items': [
MenuItem(name='mnu_generate_test_html',
label=gettext('Generated Test HTML'),
- priority=100,
+ priority=2,
url=url_for('test.generated')),
MenuItem(name='mnu_test_alert',
label=gettext('Test Alert'),
priority=200,
module='pgAdmin.Test',
+ category=gettext('alertify'),
callback='test_alert'),
MenuItem(name='mnu_test_confirm',
label=gettext('Test Confirm'),
priority=300,
module='pgAdmin.Test',
+ category=gettext('alertify'),
callback='test_confirm'),
MenuItem(name='mnu_test_dialog',
label=gettext('Test Dialog'),
priority=400,
module='pgAdmin.Test',
+ category=gettext('alertify'),
callback='test_dialog'),
MenuItem(name='mnu_test_prompt',
label=gettext('Test Prompt'),
priority=500,
module='pgAdmin.Test',
+ category=gettext('alertify'),
callback='test_prompt'),
MenuItem(name='mnu_test_notifier',
label=gettext('Test Notifier'),
priority=600,
module='pgAdmin.Test',
+ category=gettext('alertify'),
callback='test_notifier'),
MenuItem(name='mnu_test_disabled',
label=gettext('Test Disabled'),
diff --git a/web/pgadmin/test/static/js/test.js b/web/pgadmin/test/static/js/test.js
index c3527fd09..aa6ec997d 100644
--- a/web/pgadmin/test/static/js/test.js
+++ b/web/pgadmin/test/static/js/test.js
@@ -3,6 +3,9 @@ define(
function($, alertify, pgAdmin, pgServer, ServerGroup) {
pgAdmin = pgAdmin || window.pgAdmin || {};
+ if (pgAdmin.Test)
+ return pgAdmin.Test;
+
pgAdmin.Test = {
test_alert: function() {
alertify.alert(
@@ -66,19 +69,31 @@ define(
alertify.myAlert('Dialog Test 2',
'This is another test dialog from Alertify!', 'This is dialog 2');
+ },
+ init: function() {
+ if (this.initialized)
+ return;
+
+ this.initialized = true;
+
+ // Add the alertify category
+ pgAdmin.Browser.add_menu_category(
+ 'alertify', 'Alertify', 3, 'fa fa-film', true, true
+ );
+
+ pgServer.on(
+ 'server-connected', function() {
+ console.log(arguments);
+ console.log('Yay - we connected the server!');
+ },
+ {'a': 'test'});
+
+ ServerGroup.on('browser-node.loaded', function() {
+ console.log('I know that the server-group has been expanded!');
+ }, pgAdmin.Test);
+
}
};
- pgServer.on(
- 'server-connected', function() {
- console.log(arguments);
- console.log('Yay - we connected the server!');
- },
- {'a': 'test'});
-
- ServerGroup.on('browser-node.loaded', function() {
- console.log('I know that the server-group has been expanded!');
- }, pgAdmin.Test);
-
return pgAdmin.Test;
});