define([ 'sources/gettext', 'jquery', 'underscore', 'sources/pgadmin', 'backbone', 'backform', 'alertify', 'backgrid', 'select2', 'pgadmin.browser.node' ], function(gettext, $, _, pgAdmin, Backbone, Backform, Alertify, Backgrid) { var pgBrowser = pgAdmin.Browser; /* * Define the selectAll adapter for select2. * * Reference: * https://github.com/select2/select2/issues/195#issuecomment-240130634 */ $.fn.select2.amd.define('select2/selectAllAdapter', [ 'select2/utils', 'select2/dropdown', 'select2/dropdown/attachBody' ], function (Utils, Dropdown, AttachBody) { function SelectAll() { } SelectAll.prototype.render = function (decorated) { var self = this, $rendered = decorated.call(this), $selectAll = $([ '' ].join('')), $unselectAll = $([ '' ].join('')), $btnContainer = $( '
' ).append($selectAll).append($unselectAll); if (!this.$element.prop("multiple")) { // this isn't a multi-select -> don't add the buttons! return $rendered; } $rendered.find('.select2-dropdown').prepend($btnContainer); $selectAll.on('click', function (e) { var $results = $rendered.find('.select2-results__option[aria-selected=false]'); $results.each(function () { self.trigger('select', { data: $(this).data('data') }); }); self.trigger('close'); }); $unselectAll.on('click', function (e) { var $results = $rendered.find('.select2-results__option[aria-selected=true]'); $results.each(function () { self.trigger('unselect', { data: $(this).data('data') }); }); self.trigger('close'); }); return $rendered; }; return Utils.Decorate( Utils.Decorate( Dropdown, AttachBody ), SelectAll ); }); /* * NodeAjaxOptionsControl * This control will fetch the options required to render the select * control, from the url specific to the pgAdmin.Browser node object. * * In order to use this properly, schema require to set the 'url' property, * which exposes the data for this node. * * In case the url is not providing the data in proper format, we can * specify the 'transform' function too, which will convert the fetched * data to proper 'label', 'value' format. */ var NodeAjaxOptionsControl = Backform.NodeAjaxOptionsControl = Backform.Select2Control.extend({ defaults: _.extend(Backform.Select2Control.prototype.defaults, { url: undefined, transform: undefined, url_with_id: false, select2: { allowClear: true, placeholder: 'Select from the list', width: 'style' } }), initialize: function() { /* * Initialization from the original control. */ Backform.Select2Control.prototype.initialize.apply(this, arguments); /* * We're about to fetch the options required for this control. */ var self = this, url = self.field.get('url') || self.defaults.url, m = self.model.top || self.model; // Hmm - we found the url option. // That means - we needs to fetch the options from that node. if (url) { var node = this.field.get('schema_node'), node_info = this.field.get('node_info'), with_id = this.field.get('url_with_id') || false, full_url = node.generate_url.apply( node, [ null, url, this.field.get('node_data'), with_id, node_info ]), cache_level, cache_node = this.field.get('cache_node'); cache_node = (cache_node && pgAdmin.Browser.Nodes[cache_node]) || node; if (this.field.has('cache_level')) { cache_level = this.field.get('cache_level'); } else { cache_level = cache_node.cache_level(node_info, with_id); } /* * We needs to check, if we have already cached data for this url. * If yes - use that, and do not bother about fetching it again, * and use it. */ var data = cache_node.cache(node.type + '#' + url, node_info, cache_level); if (this.field.get('version_compatible') && (_.isUndefined(data) || _.isNull(data))) { m.trigger('pgadmin:view:fetching', m, self.field); $.ajax({ async: false, url: full_url, success: function(res) { /* * We will cache this data for short period of time for avoiding * same calls. */ data = cache_node.cache(node.type + '#' + url, node_info, cache_level, res.data); }, error: function() { m.trigger('pgadmin:view:fetch:error', m, self.field); } }); m.trigger('pgadmin:view:fetched', m, self.field); } // To fetch only options from cache, we do not need time from 'at' // attribute but only options. // // It is feasible that the data may not have been fetched. data = (data && data.data) || []; /* * Transform the data */ var transform = this.field.get('transform') || self.defaults.transform; if (transform && _.isFunction(transform)) { // We will transform the data later, when rendering. // It will allow us to generate different data based on the // dependencies. self.field.set('options', transform.bind(self, data)); } else { self.field.set('options', data); } } } }); var formatNode = function(opt) { if (!opt.id) { return opt.text; } var optimage = $(opt.element).data('image'); if(!optimage){ return opt.text; } else { return $('').append( $('', {class: "wcTabIcon " + optimage}) ).append( $('').text(opt.text) ); } }; var NodeListByIdControl = Backform.NodeListByIdControl = NodeAjaxOptionsControl.extend({ controlClassName: 'pgadmin-node-select form-control', defaults: _.extend({}, NodeAjaxOptionsControl.prototype.defaults, { url: 'nodes', filter: undefined, transform: function(rows) { var self = this, node = self.field.get('schema_node'), res = [], filter = self.field.get('filter') || function() { return true; }; filter = filter.bind(self); _.each(rows, function(r) { if (filter(r)) { var l = (_.isFunction(node['node_label']) ? (node['node_label']).apply(node, [r, self.model, self]) : r.label), image= (_.isFunction(node['node_image']) ? (node['node_image']).apply( node, [r, self.model, self] ) : (node['node_image'] || ('icon-' + node.type))); res.push({ 'value': r._id, 'image': image, 'label': l }); } }); return res; }, select2: { allowClear: true, placeholder: 'Select from the list', width: 'style', templateResult: formatNode, templateSelection: formatNode } }) }); var NodeListByNameControl = Backform.NodeListByNameControl = NodeListByIdControl.extend({ defaults: _.extend({}, NodeListByIdControl.prototype.defaults, { transform: function(rows) { var self = this, node = self.field.get('schema_node'), res = [], filter = self.field.get('filter') || function() { return true; }; filter = filter.bind(self); _.each(rows, function(r) { if (filter(r)) { var l = (_.isFunction(node['node_label']) ? (node['node_label']).apply(node, [r, self.model, self]) : r.label), image = (_.isFunction(node['node_image']) ? (node['node_image']).apply( node, [r, self.model, self] ) : (node['node_image'] || ('icon-' + node.type))); res.push({ 'value': r.label, 'image': image, 'label': l }); } }); return res; } }) }); /* * Global function to make visible particular dom element in it's parent * with given class. */ $.fn.pgMakeVisible = function( cls ) { return this.each(function() { if (!this || !$(this.length)) return; var top, p = $(this), hasScrollbar = function(j) { if (j && j.length > 0) { return j.get(0).scrollHeight > j.height(); } return false; }; // check if p is not empty while(p && p.length > 0) { top = p.get(0).offsetTop + p.height(); p = p.parent(); if (hasScrollbar(p)) { p.scrollTop(top); } if (p.hasClass(cls)) //'backform-tab' return; } }); }; /* * NodeAjaxOptionsCell * This cell will fetch the options required to render the select * cell, from the url specific to the pgAdmin.Browser node object. * * In order to use this properly, schema require to set the 'url' property, * which exposes the data for this node. * * In case the url is not providing the data in proper format, we can * specify the 'transform' function too, which will convert the fetched * data to proper 'label', 'value' format. */ var NodeAjaxOptionsCell = Backgrid.Extension.NodeAjaxOptionsCell = Backgrid.Extension.Select2Cell.extend({ defaults: _.extend({}, Backgrid.Extension.Select2Cell.prototype.defaults, { url: undefined, transform: undefined, url_with_id: false, select2: { allowClear: true, placeholder: 'Select from the list', width: 'style' }, opt: { label: null, value: null, image: null, selected: false } }), template: _.template( '' ), initialize: function () { Backgrid.Extension.Select2Cell.prototype.initialize.apply(this, arguments); var url = this.column.get('url') || this.defaults.url, is_options_cached = _.has(this.column.attributes, 'options_cached'), options_cached = is_options_cached && this.column.get('options_cached'); // Hmm - we found the url option. // That means - we needs to fetch the options from that node. if (url && !options_cached) { var self = this, m = this.model, column = this.column, eventHandler = m.top || m, node = column.get('schema_node'), node_info = column.get('node_info'), with_id = column.get('url_with_id') || false, full_url = node.generate_url.apply( node, [ null, url, column.get('node_data'), with_id, node_info ]), cache_level, cache_node = column.get('cache_node'); cache_node = (cache_node && pgAdmin.Browser.Nodes[cache_node]) || node; if (column.has('cache_level')) { cache_level = column.get('cache_level'); } else { cache_level = cache_node.cache_level(node_info, with_id); } /* * We needs to check, if we have already cached data for this url. * If yes - use that, and do not bother about fetching it again, * and use it. */ var data = cache_node.cache(node.type + '#' + url, node_info, cache_level); if (column.get('version_compatible') && (_.isUndefined(data) || _.isNull(data))) { eventHandler.trigger('pgadmin:view:fetching', m, column); $.ajax({ async: false, url: full_url, success: function(res) { /* * We will cache this data for short period of time for avoiding * same calls. */ data = cache_node.cache(node.type + '#' + url, node_info, cache_level, res.data); }, error: function() { eventHandler.trigger('pgadmin:view:fetch:error', m, column); } }); eventHandler.trigger('pgadmin:view:fetched', m, column); } // To fetch only options from cache, we do not need time from 'at' // attribute but only options. // // It is feasible that the data may not have been fetched. data = (data && data.data) || []; /* * Transform the data */ var transform = column.get('transform') || self.defaults.transform; if (transform && _.isFunction(transform)) { // We will transform the data later, when rendering. // It will allow us to generate different data based on the // dependencies. column.set('options', transform.bind(column, data)); } else { column.set('options', data); } if(is_options_cached) { column.set('options_cached', true); } } } }); var NodeListByIdCell = Backgrid.Extension.NodeListByIdCell = NodeAjaxOptionsCell.extend({ controlClassName: 'pgadmin-node-select backgrid-cell', defaults: _.extend({}, NodeAjaxOptionsCell.prototype.defaults, { url: 'nodes', filter: undefined, transform: function(rows, control) { var self = control || this, node = self.column.get('schema_node'), res = [], filter = self.column.get('filter') || function() { return true; }; filter = filter.bind(self); _.each(rows, function(r) { if (filter(r)) { var l = (_.isFunction(node['node_label']) ? (node['node_label']).apply(node, [r, self.model, self]) : r.label), image= (_.isFunction(node['node_image']) ? (node['node_image']).apply( node, [r, self.model, self] ) : (node['node_image'] || ('icon-' + node.type))); res.push({ 'value': r._id, 'image': image, 'label': l }); } }); return res; }, select2: { placeholder: 'Select from the list', width: 'style', templateResult: formatNode, templateSelection: formatNode } }) }); var NodeListByNameCell = Backgrid.Extension.NodeListByNameCell = NodeAjaxOptionsCell.extend({ controlClassName: 'pgadmin-node-select backgrid-cell', defaults: _.extend({}, NodeAjaxOptionsCell.prototype.defaults, { url: 'nodes', filter: undefined, transform: function(rows, control) { var self = control || this, node = self.column.get('schema_node'), res = [], filter = self.column.get('filter') || function() { return true; }; filter = filter.bind(self); _.each(rows, function(r) { if (filter(r)) { var l = (_.isFunction(node['node_label']) ? (node['node_label']).apply(node, [r, self.model, self]) : r.label), image= (_.isFunction(node['node_image']) ? (node['node_image']).apply( node, [r, self.model, self] ) : (node['node_image'] || ('icon-' + node.type))); res.push({ 'value': r.label, 'image': image, 'label': l }); } }); return res; }, select2: { placeholder: 'Select from the list', width: 'style', templateResult: formatNode, templateSelection: formatNode } }) }); // Extend the browser's node model class to create a option/value pair var MultiSelectAjaxCell = Backgrid.Extension.MultiSelectAjaxCell = Backgrid.Extension.NodeAjaxOptionsCell.extend({ defaults: _.extend({}, NodeAjaxOptionsCell.prototype.defaults, { transform: undefined, url_with_id: false, select2: { allowClear: true, placeholder: 'Select from the list', width: 'style', multiple: true }, opt: { label: null, value: null, image: null, selected: false } }), getValueFromDOM: function() { var res = []; this.$el.find("select").find(':selected').each(function() { res.push($(this).attr('value')); }); return res; }, }); /* * Control to select multiple columns. */ var MultiSelectAjaxControl = Backform.MultiSelectAjaxControl = NodeAjaxOptionsControl.extend({ defaults: _.extend({}, NodeAjaxOptionsControl.prototype.defaults, { select2: { multiple: true, allowClear: true, width: 'style' } }) }); return Backform; });