Hi,

PFA the revised patch.
Response is lnline.

Regards,
Sanket Mehta
Sr Software engineer
Enterprisedb

On Tue, May 17, 2016 at 12:54 PM, Harshal Dhumal <
harshal.dhu...@enterprisedb.com> wrote:

> Hi Sanket,
>
> Please find my review comments below:
>
> 1. In create mode it generates wrong sql.
>
> CREATE TEXT SEARCH CONFIGURATION test.asdf (
>     COPY=
> );
>
> steps to reproduce:
>     a] Fill up any necessary fields in general tab.
>     b] On definition tab set Copy config filed and navigate to SQL tab.
>     c] Come back again to definition tab clear Copy config field and set
> Parser field and again navigate to SQL tab
>
Fixed

>
> 2. Create new FTS configuration with parser throws below error.
>
> provide atleast copy config or parser.
>
> (this is because you have used key name for parser in js as "prsname" and
> in python you have applied validations on key "parser")
>
Fixed

>
> 3. When we clear dictionary for any token using cross (x) button in
> select2; JavaScript code for selecct2 fails. (see attached screenshot)
>
This occurs due to issue in select2 control. Its a generalized issue which
will be resolved later.

>
>
>
>
>
>
> --
> *Harshal Dhumal*
> *Software Engineer *
>
>
>
> EenterpriseDB <http://www.enterprisedb.com>
>
> On Mon, May 16, 2016 at 7:37 PM, Sanket Mehta <
> sanket.me...@enterprisedb.com> wrote:
>
>> Hi,
>>
>> Revised patch is attached with this mail.
>> My response is inline.
>>
>>
>> Regards,
>> Sanket Mehta
>> Sr Software engineer
>> Enterprisedb
>>
>> On Fri, May 13, 2016 at 6:20 PM, Akshay Joshi <
>> akshay.jo...@enterprisedb.com> wrote:
>>
>>> Hi Sanket
>>>
>>> Below are my review comments:
>>>
>>>
>>>    - Add button should be disabled in Tokens tab while creating.
>>>
>>> Done
>>
>>>
>>>    - Unable to click on down arrow for token select control.
>>>
>>> Done
>>
>>>
>>>    - Title should be change from "Create FTS-dictionaries" to "Create
>>>    FTS-Configuration".
>>>
>>> Done
>>
>>>
>>>    - Tree node is not getting refreshed on name change.
>>>
>>> Done
>>
>>>
>>>    - Unable to rename FTS Configuration when name contains any capital
>>>    letter.
>>>
>>> Done
>>
>>>
>>>    - If user tries to add already existing token then respective row in
>>>    the backgrid should be highlighted.
>>>
>>> Done
>>
>>>
>>>    - "URL not found" when click on SQL help icon from the properties
>>>    dialog.
>>>
>>> Done
>>
>>>
>>>    - SQL should not be generated when remove and add the same token
>>>    again. Currently it is creating two sql's one for remove and another
>>>    is for add.
>>>       - *Fix for the above issue is*: - Add "keys: ['token']," to
>>>       TokenModel in your js file.
>>>
>>> Done
>>
>>
>>>
>>> On Fri, May 13, 2016 at 2:58 PM, Sanket Mehta <
>>> sanket.me...@enterprisedb.com> wrote:
>>>
>>>> Hi All,
>>>>
>>>> Last patch for FTS configuration does not contain node.ui.js file
>>>> Kindly ignore it.
>>>>
>>>> Here is the new revised patch attached with this mail.
>>>> Please do review it and let me know if any changes required
>>>>
>>>> Regards,
>>>> Sanket Mehta
>>>> Sr Software engineer
>>>> Enterprisedb
>>>>
>>>> On Thu, May 12, 2016 at 4:38 PM, Sanket Mehta <
>>>> sanket.me...@enterprisedb.com> wrote:
>>>>
>>>>> Hi,
>>>>>
>>>>> PFA the revised patch.
>>>>> Please do review it and let me know if anything is not proper.
>>>>>
>>>>> Regards,
>>>>> Sanket Mehta
>>>>> Sr Software engineer
>>>>> Enterprisedb
>>>>>
>>>>> On Thu, May 5, 2016 at 8:19 PM, Harshal Dhumal <
>>>>> harshal.dhu...@enterprisedb.com> wrote:
>>>>>
>>>>>> + patch link
>>>>>>
>>>>>>
>>>>>> http://www.postgresql.org/message-id/CAFiP3vwkka+=1foj7kr2zbc4azecoca9eo9dz34-oyy_9ge...@mail.gmail.com
>>>>>>
>>>>>> --
>>>>>> *Harshal Dhumal*
>>>>>> *Software Engineer *
>>>>>>
>>>>>>
>>>>>>
>>>>>> EenterpriseDB <http://www.enterprisedb.com>
>>>>>>
>>>>>> On Thu, May 5, 2016 at 8:18 PM, Sanket Mehta <
>>>>>> sanket.me...@enterprisedb.com> wrote:
>>>>>>
>>>>>>> Hi,
>>>>>>>
>>>>>>> PFA first patch for FTS configuration node.
>>>>>>>
>>>>>>> It depends upon backgrid select2cell multi select control, for which
>>>>>>> Harshal has sent the patch recently.
>>>>>>> Please do apply his patch first and then apply this patch.
>>>>>>>
>>>>>>> Please do review it and let me know if any changes are required.
>>>>>>>
>>>>>>>
>>>>>>> Regards,
>>>>>>> Sanket Mehta
>>>>>>> Sr Software engineer
>>>>>>> Enterprisedb
>>>>>>>
>>>>>>>
>>>>>>> --
>>>>>>> Sent via pgadmin-hackers mailing list (
>>>>>>> pgadmin-hackers@postgresql.org)
>>>>>>> To make changes to your subscription:
>>>>>>> http://www.postgresql.org/mailpref/pgadmin-hackers
>>>>>>>
>>>>>>>
>>>>>>
>>>>>
>>>>
>>>>
>>>> --
>>>> Sent via pgadmin-hackers mailing list (pgadmin-hackers@postgresql.org)
>>>> To make changes to your subscription:
>>>> http://www.postgresql.org/mailpref/pgadmin-hackers
>>>>
>>>>
>>>
>>>
>>> --
>>> *Akshay Joshi*
>>> *Principal Software Engineer *
>>>
>>>
>>>
>>> *Phone: +91 20-3058-9517Mobile: +91 976-788-8246*
>>>
>>
>>
>>
>> --
>> Sent via pgadmin-hackers mailing list (pgadmin-hackers@postgresql.org)
>> To make changes to your subscription:
>> http://www.postgresql.org/mailpref/pgadmin-hackers
>>
>>
>
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/__init__.py
new file mode 100644
index 0000000..6b87740
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/__init__.py
@@ -0,0 +1,951 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2016, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+"""Defines views for management of Fts Configuration node"""
+
+import json
+from flask import render_template, make_response, current_app, request, jsonify
+from flask.ext.babel import gettext as _
+from pgadmin.utils.ajax import make_json_response, \
+    make_response as ajax_response, internal_server_error, gone
+from pgadmin.browser.utils import PGChildNodeView
+from pgadmin.browser.server_groups.servers.databases.schemas.utils \
+    import SchemaChildModule
+import pgadmin.browser.server_groups.servers.databases.schemas as schemas
+from pgadmin.utils.ajax import precondition_required
+from pgadmin.utils.driver import get_driver
+from config import PG_DEFAULT_DRIVER
+from functools import wraps
+
+
+class FtsConfigurationModule(SchemaChildModule):
+    """
+     class FtsConfigurationModule(SchemaChildModule)
+
+        A module class for FTS Configuration node derived from SchemaChildModule.
+
+    Methods:
+    -------
+    * __init__(*args, **kwargs)
+      - Method is used to initialize the FtsConfigurationModule and
+        it's base module.
+
+    * get_nodes(gid, sid, did, scid)
+      - Method is used to generate the browser collection node.
+
+    * node_inode()
+      - Method is overridden from its base class to make the node as leaf node
+
+    * script_load()
+      - Load the module script for FTS Configuration, when any of the schema
+      node is initialized.
+
+    * csssnippets()
+      - Load style sheets for this module
+    """
+    NODE_TYPE = 'fts_configuration'
+    COLLECTION_LABEL = _('FTS Configurations')
+
+    def __init__(self, *args, **kwargs):
+        self.min_ver = None
+        self.max_ver = None
+        self.manager = None
+        super(FtsConfigurationModule, self).__init__(*args, **kwargs)
+
+    def get_nodes(self, gid, sid, did, scid):
+        """
+        Generate the collection node
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        """
+        yield self.generate_browser_collection_node(scid)
+
+    @property
+    def node_inode(self):
+        """
+        Override the property to make the node as leaf node
+        """
+        return False
+
+    @property
+    def script_load(self):
+        """
+        Load the module script for fts template, when any of the schema
+        node is initialized.
+        """
+        return schemas.SchemaModule.NODE_TYPE
+
+    @property
+    def csssnippets(self):
+        snippets = [render_template(
+            'fts_configuration/css/fts_configuration.css',
+            node_type = self.node_type,
+            _=_
+        )]
+        snippets.extend(super(SchemaChildModule,self).csssnippets)
+        return snippets
+
+blueprint = FtsConfigurationModule(__name__)
+
+
+class FtsConfigurationView(PGChildNodeView):
+    """
+    class FtsConfigurationView(PGChildNodeView)
+
+        A view class for FTS Configuration node derived from PGChildNodeView.
+        This class is responsible for all the stuff related to view like
+        create/update/delete FTS Configuration,
+        showing properties of node, showing sql in sql pane.
+
+    Methods:
+    -------
+    * __init__(**kwargs)
+      - Method is used to initialize the FtsConfigurationView and it's base view.
+
+    * module_js()
+      - This property defines (if javascript) exists for this node.
+        Override this property for your own logic
+
+    * check_precondition()
+      - This function will behave as a decorator which will checks
+        database connection before running view, it will also attaches
+        manager,conn & template_path properties to self
+
+    * list()
+      - This function is used to list all the  nodes within that collection.
+
+    * nodes()
+      - This function will be used to create all the child node within collection.
+        Here it will create all the FTS Configuration nodes.
+
+    * node()
+      - This function will be used to create a node given its oid
+        Here it will create the FTS Template node based on its oid
+
+    * properties(gid, sid, did, scid, cfgid)
+      - This function will show the properties of the selected FTS Configuration node
+
+    * create(gid, sid, did, scid)
+      - This function will create the new FTS Configuration object
+
+    * update(gid, sid, did, scid, cfgid)
+      - This function will update the data for the selected FTS Configuration node
+
+    * delete(self, gid, sid, did, scid, cfgid):
+      - This function will drop the FTS Configuration object
+
+    * msql(gid, sid, did, scid, cfgid)
+      - This function is used to return modified SQL for the selected node
+
+    * get_sql(data, cfgid)
+      - This function will generate sql from model data
+
+    * sql(gid, sid, did, scid, cfgid):
+      - This function will generate sql to show in sql pane for node.
+
+    * parsers(gid, sid, did, scid):
+      - This function will fetch all ftp parsers from the same schema
+
+    * copyConfig():
+      - This function will fetch all existed fts configurations from same schema
+
+    * tokens():
+      - This function will fetch all tokens from fts parser related to node
+
+    * dictionaries():
+      - This function will fetch all dictionaries related to node
+
+    * dependents(gid, sid, did, scid, cfgid):
+      - This function get the dependents and return ajax response for the node.
+
+    * dependencies(self, gid, sid, did, scid, cfgid):
+      - This function get the dependencies and return ajax response for node.
+
+    """
+
+    node_type = blueprint.node_type
+
+    parent_ids = [
+        {'type': 'int', 'id': 'gid'},
+        {'type': 'int', 'id': 'sid'},
+        {'type': 'int', 'id': 'did'},
+        {'type': 'int', 'id': 'scid'}
+    ]
+    ids = [
+        {'type': 'int', 'id': 'cfgid'}
+    ]
+
+    operations = dict({
+        'obj': [
+            {'get': 'properties', 'delete': 'delete', 'put': 'update'},
+            {'get': 'list', 'post': 'create'}
+        ],
+        'children': [{
+            'get': 'children'
+        }],
+        'delete': [{'delete': 'delete'}],
+        'nodes': [{'get': 'node'}, {'get': 'nodes'}],
+        'sql': [{'get': 'sql'}],
+        'msql': [{'get': 'msql'}, {'get': 'msql'}],
+        'stats': [{'get': 'statistics'}],
+        'dependency': [{'get': 'dependencies'}],
+        'dependent': [{'get': 'dependents'}],
+        'module.js': [{}, {}, {'get': 'module_js'}],
+        'parsers': [{'get': 'parsers'},
+                          {'get': 'parsers'}],
+        'copyConfig': [{'get': 'copyConfig'},
+                              {'get': 'copyConfig'}],
+        'tokens': [{'get': 'tokens'}, {'get': 'tokens'}],
+        'dictionaries': [{}, {'get': 'dictionaries'}],
+    })
+
+    def _init_(self, **kwargs):
+        self.conn = None
+        self.template_path = None
+        self.manager = None
+        super(FtsConfigurationView, self).__init__(**kwargs)
+
+    def module_js(self):
+        """
+        Load JS file (fts_configuration.js) for this module.
+        """
+        return make_response(
+            render_template(
+                "fts_configuration/js/fts_configuration.js",
+                _=_
+            ),
+            200, {'Content-Type': 'application/x-javascript'}
+        )
+
+    def check_precondition(f):
+        """
+        This function will behave as a decorator which will checks
+        database connection before running view, it will also attaches
+        manager,conn & template_path properties to self
+        """
+
+        @wraps(f)
+        def wrap(*args, **kwargs):
+            # Here args[0] will hold self & kwargs will hold gid,sid,did
+            self = args[0]
+            self.manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(
+                kwargs['sid'])
+            self.conn = self.manager.connection(did=kwargs['did'])
+            # If DB not connected then return error to browser
+            if not self.conn.connected():
+                return precondition_required(
+                    _("Connection to the server has been lost!")
+                )
+            # we will set template path for sql scripts depending upon server version
+            ver = self.manager.version
+            if ver >= 90100:
+                self.template_path = 'fts_configuration/sql/9.1_plus'
+            return f(*args, **kwargs)
+
+        return wrap
+
+    @check_precondition
+    def list(self, gid, sid, did, scid):
+        """
+        List all FTS Configuration nodes.
+
+        Args:
+            gid: Server Group Id
+            sid: Server Id
+            did: Database Id
+            scid: Schema Id
+        """
+
+        sql = render_template(
+                "/".join([self.template_path, 'properties.sql']),
+                scid=scid
+             )
+        status, res = self.conn.execute_dict(sql)
+
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        return ajax_response(
+            response=res['rows'],
+            status=200
+        )
+
+    @check_precondition
+    def nodes(self, gid, sid, did, scid):
+        """
+        Return all FTS Configurations to generate nodes.
+
+        Args:
+            gid: Server Group Id
+            sid: Server Id
+            did: Database Id
+            scid: Schema Id
+        """
+
+        res = []
+        sql = render_template(
+            "/".join([self.template_path, 'nodes.sql']),
+            scid=scid
+        )
+        status, rset = self.conn.execute_2darray(sql)
+        if not status:
+            return internal_server_error(errormsg=rset)
+
+        for row in rset['rows']:
+            res.append(
+                self.blueprint.generate_browser_node(
+                    row['oid'],
+                    did,
+                    row['name'],
+                    icon="icon-fts_configuration"
+                ))
+
+        return make_json_response(
+            data=res,
+            status=200
+        )
+
+    @check_precondition
+    def node(self, gid, sid, did, scid, cfgid):
+        """
+        Return FTS Configuration node to generate node
+
+        Args:
+            gid: Server Group Id
+            sid: Server Id
+            did: Database Id
+            scid: Schema Id
+            cfgid: fts Configuration id
+        """
+
+        sql = render_template(
+            "/".join([self.template_path, 'nodes.sql']),
+            cfgid=cfgid
+        )
+        status, rset = self.conn.execute_2darray(sql)
+        if not status:
+            return internal_server_error(errormsg=rset)
+
+        if len(rset['rows']) == 0:
+            return gone(_("""
+                Could not find the FTS Configuration node.
+                """))
+
+        for row in rset['rows']:
+            return make_json_response(
+                data=self.blueprint.generate_browser_node(
+                    row['oid'],
+                    did,
+                    row['name'],
+                    icon="icon-fts_configuration"
+                ),
+                status=200
+            )
+
+    @check_precondition
+    def properties(self, gid, sid, did, scid, cfgid):
+        """
+        Show properties of FTS Configuration node
+
+        Args:
+            gid: Server Group Id
+            sid: Server Id
+            did: Database Id
+            scid: Schema Id
+            cfgid: fts Configuration id
+        """
+
+        sql = render_template(
+                "/".join([self.template_path, 'properties.sql']),
+                scid=scid,
+                cfgid=cfgid
+              )
+        status, res = self.conn.execute_dict(sql)
+
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        if len(res['rows']) == 0:
+            return gone(_("""
+                Could not find the FTS Configuration node.
+                """))
+
+        # In edit mode fetch token/dictionary list also
+        if cfgid:
+            sql = render_template("/".join([self.template_path,
+                                             'tokenDictList.sql']),
+                                cfgid=cfgid)
+
+            status, rset = self.conn.execute_dict(sql)
+
+            if not status:
+                return internal_server_error(errormsg=rset)
+
+            res['rows'][0]['tokens'] = rset['rows']
+
+        return ajax_response(
+            response=res['rows'][0],
+            status=200
+        )
+
+    @check_precondition
+    def create(self, gid, sid, did, scid):
+        """
+        This function will creates new the FTS Configuration object
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        """
+
+        # Mandatory fields to create a new FTS Configuration
+        required_args = [
+            'schema',
+            'name'
+        ]
+
+        data = request.form if request.form else json.loads(
+            request.data.decode())
+        for arg in required_args:
+            if arg not in data:
+                return make_json_response(
+                    status=410,
+                    success=0,
+                    errormsg=_(
+                        "Couldn't find the required parameter (%s)." % arg
+                    )
+                )
+
+        # Either copy config or parser must be present in data
+        if 'copy_config' not in data and 'prsname' not in data:
+            return make_json_response(
+                status=410,
+                success=0,
+                errormsg=_(
+                    "provide atleast copy config or parser"
+                )
+            )
+
+        try:
+            # Fetch schema name from schema oid
+            sql = render_template("/".join([self.template_path,
+                                            'schema.sql']),
+                                  data=data,
+                                  conn=self.conn,
+                                  )
+
+            status, schema = self.conn.execute_scalar(sql)
+            if not status:
+                return internal_server_error(errormsg=schema)
+
+            # Replace schema oid with schema name before passing to create.sql
+            # To generate proper sql query
+            new_data = data.copy()
+            new_data['schema'] = schema
+
+            sql = render_template(
+                "/".join([self.template_path, 'create.sql']),
+                data=new_data,
+                conn=self.conn,
+            )
+            status, res = self.conn.execute_scalar(sql)
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            # We need cfgid to add object in tree at browser,
+            # Below sql will give the same
+            sql = render_template(
+                "/".join([self.template_path, 'properties.sql']),
+                name=data['name']
+            )
+            status, cfgid = self.conn.execute_scalar(sql)
+            if not status:
+                return internal_server_error(errormsg=cfgid)
+
+            return jsonify(
+                node=self.blueprint.generate_browser_node(
+                    cfgid,
+                    did,
+                    data['name'],
+                    icon="icon-fts_configuration"
+                )
+            )
+        except Exception as e:
+            current_app.logger.exception(e)
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def update(self, gid, sid, did, scid, cfgid):
+        """
+        This function will update FTS Configuration node
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        :param cfgid: fts Configuration id
+        """
+        data = request.form if request.form else json.loads(
+            request.data.decode())
+
+        # Fetch sql query to update fts Configuration
+        sql = self.get_sql(gid, sid, did, scid, data, cfgid)
+        try:
+            if sql and sql.strip('\n') and sql.strip(' '):
+                status, res = self.conn.execute_scalar(sql)
+                if not status:
+                    return internal_server_error(errormsg=res)
+
+                if cfgid is not None:
+                    sql = render_template(
+                        "/".join([self.template_path, 'nodes.sql']),
+                        cfgid=cfgid,
+                        scid=scid
+                    )
+
+                status, res = self.conn.execute_dict(sql)
+                if not status:
+                    return internal_server_error(errormsg=res)
+
+                if len(res['rows']) == 0:
+                    return gone(_("""
+                        Could not find the FTS Configuration node to update.
+                    """))
+
+                data = res['rows'][0]
+                return make_json_response(
+                    success=1,
+                    info="FTS Configuration Updated.",
+                    data={
+                        'id': cfgid,
+                        'sid': sid,
+                        'gid': gid,
+                        'did': did,
+                        'scid': scid
+                    }
+                )
+            # In case FTS Configuration node is not present
+            else:
+                return make_json_response(
+                    success=1,
+                    info="Nothing to update",
+                    data={
+                        'id': cfgid,
+                        'sid': sid,
+                        'gid': gid,
+                        'did': did,
+                        'scid': scid
+                    }
+                )
+
+        except Exception as e:
+            current_app.logger.exception(e)
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def delete(self, gid, sid, did, scid, cfgid):
+        """
+        This function will drop the FTS Configuration object
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        :param cfgid: FTS Configuration id
+        """
+        # Below will decide if it's simple drop or drop with cascade call
+        if self.cmd == 'delete':
+            # This is a cascade operation
+            cascade = True
+        else:
+            cascade = False
+
+        try:
+            # Get name for FTS Configuration from cfgid
+            sql = render_template(
+                "/".join([self.template_path, 'get_name.sql']),
+                cfgid=cfgid
+                )
+            status, res = self.conn.execute_dict(sql)
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            if len(res['rows']) == 0:
+                return gone(_("""
+                        Could not find the FTS Configuration node to delete.
+                    """))
+
+            # Drop FTS Configuration
+            result = res['rows'][0]
+            sql = render_template(
+                "/".join([self.template_path, 'delete.sql']),
+                name=result['name'],
+                schema=result['schema'],
+                cascade=cascade
+                )
+
+            status, res = self.conn.execute_scalar(sql)
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            return make_json_response(
+                success=1,
+                info=_("FTS Configuration dropped"),
+                data={
+                    'id': cfgid,
+                    'sid': sid,
+                    'gid': gid,
+                    'did': did,
+                    'scid': scid
+                }
+            )
+
+        except Exception as e:
+            current_app.logger.exception(e)
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def msql(self, gid, sid, did, scid, cfgid=None):
+        """
+        This function returns modified SQL
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        :param cfgid: FTS Configuration id
+        """
+        data = {}
+        for k, v in request.args.items():
+            try:
+                data[k] = json.loads(v)
+            except ValueError:
+                data[k] = v
+
+        # Fetch sql query for modified data
+        sql = self.get_sql(gid, sid, did, scid, data, cfgid)
+
+        if isinstance(sql, str) and sql and sql.strip('\n') and sql.strip(' '):
+            return make_json_response(
+                data=sql,
+                status=200
+            )
+        else:
+            return make_json_response(
+                data="--modified SQL",
+                status=200
+            )
+
+    def get_sql(self, gid, sid, did, scid, data, cfgid=None):
+        """
+        This function will return SQL for model data
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        :param cfgid: fts Configuration id
+        """
+        try:
+            # Fetch sql for update
+            if cfgid is not None:
+                sql = render_template(
+                    "/".join([self.template_path, 'properties.sql']),
+                    cfgid=cfgid,
+                    scid=scid
+                )
+
+                status, res = self.conn.execute_dict(sql)
+                if not status:
+                    return internal_server_error(errormsg=res)
+
+                if len(res['rows']) == 0:
+                    return gone(_("""
+                        Could not find the FTS Configuration node.
+                    """))
+
+                old_data = res['rows'][0]
+
+                # If user has changed the schema then fetch new schema directly
+                # using its oid otherwise fetch old schema name using its oid
+                sql = render_template(
+                    "/".join([self.template_path, 'schema.sql']),
+                    data=data)
+
+                status, new_schema = self.conn.execute_scalar(sql)
+                if not status:
+                    return internal_server_error(errormsg=new_schema)
+
+                # Replace schema oid with schema name
+                new_data = data.copy()
+                if 'schema' in new_data:
+                    new_data['schema'] = new_schema
+
+                # Fetch old schema name using old schema oid
+                sql = render_template(
+                    "/".join([self.template_path, 'schema.sql']),
+                    data=old_data
+                    )
+
+                status, old_schema = self.conn.execute_scalar(sql)
+                if not status:
+                    return internal_server_error(errormsg=old_schema)
+
+                # Replace old schema oid with old schema name
+                old_data['schema'] = old_schema
+
+                sql = render_template(
+                    "/".join([self.template_path, 'update.sql']),
+                    data=new_data, o_data=old_data
+                    )
+                # Fetch sql query for modified data
+            else:
+                # Fetch schema name from schema oid
+                sql = render_template(
+                    "/".join([self.template_path, 'schema.sql']),
+                    data=data
+                    )
+
+                status, schema = self.conn.execute_scalar(sql)
+                if not status:
+                    return internal_server_error(errormsg=schema)
+
+                # Replace schema oid with schema name
+                new_data = data.copy()
+                new_data['schema'] = schema
+
+                if 'name' in new_data and \
+                        'schema' in new_data:
+                    sql = render_template("/".join([self.template_path,
+                                                    'create.sql']),
+                                          data=new_data,
+                                          conn=self.conn
+                                          )
+                else:
+                    sql = "-- incomplete definition"
+            return str(sql.strip('\n'))
+
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def parsers(self, gid, sid, did, scid):
+        """
+        This function will return fts parsers list for FTS Configuration
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        """
+        # Fetch last system oid
+        datlastsysoid = self.manager.db_info[did]['datlastsysoid']
+
+        sql = render_template(
+            "/".join([self.template_path, 'parser.sql']),
+            parser=True
+            )
+        status, rset = self.conn.execute_dict(sql)
+
+        if not status:
+            return internal_server_error(errormsg=rset)
+
+        # Empty set is added before actual list as initially it will be visible
+        # at parser control while creating a new FTS Configuration
+        res = [{'label':'', 'value':''}]
+        for row in rset['rows']:
+            if row['schemaoid'] > datlastsysoid:
+                row['prsname'] = row['nspname'] + '.' + row['prsname']
+
+            res.append({'label': row['prsname'],
+                        'value': row['prsname']})
+        return make_json_response(
+            data=res,
+            status=200
+        )
+
+    @check_precondition
+    def copyConfig(self, gid, sid, did, scid):
+        """
+        This function will return copy config list for FTS Configuration
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        """
+        # Fetch last system oid
+        datlastsysoid = self.manager.db_info[did]['datlastsysoid']
+
+        sql = render_template(
+            "/".join([self.template_path, 'copy_config.sql']),
+            copy_config=True
+            )
+        status, rset = self.conn.execute_dict(sql)
+
+        if not status:
+            return internal_server_error(errormsg=rset)
+
+        # Empty set is added before actual list as initially it will be visible
+        # at copy_config control while creating a new FTS Configuration
+        res = [{'label': '', 'value': ''}]
+        for row in rset['rows']:
+            if row['oid'] > datlastsysoid:
+                row['cfgname'] = row['nspname'] + '.' + row['cfgname']
+
+            res.append({'label': row['cfgname'],
+                        'value': row['cfgname']})
+        return make_json_response(
+            data=res,
+            status=200
+        )
+
+    @check_precondition
+    def tokens(self, gid, sid, did, scid, cfgid=None):
+        """
+        This function will return token list of fts parser node related to
+        current FTS Configuration node
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        :param cfgid: fts configuration id
+        """
+        try:
+            res = []
+            if cfgid is not None:
+                sql = render_template(
+                    "/".join([self.template_path, 'parser.sql']),
+                    cfgid=cfgid
+                    )
+                status, parseroid = self.conn.execute_scalar(sql)
+
+                if not status:
+                    return internal_server_error(errormsg=parseroid)
+
+                sql = render_template(
+                    "/".join([self.template_path, 'tokens.sql']),
+                    parseroid=parseroid
+                    )
+                status, rset = self.conn.execute_dict(sql)
+
+                for row in rset['rows']:
+                    res.append({'label': row['alias'],
+                                'value': row['alias']})
+
+            return make_json_response(
+                data=res,
+                status=200
+            )
+
+        except Exception as e:
+            current_app.logger.exception(e)
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def dictionaries(self, gid, sid, did, scid, cfgid=None):
+        """
+        This function will return dictionary list for FTS Configuration
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        """
+        sql = render_template(
+            "/".join([self.template_path,'dictionaries.sql'])
+            )
+        status, rset = self.conn.execute_dict(sql)
+
+        if not status:
+            return internal_server_error(errormsg=rset)
+
+        res = []
+        for row in rset['rows']:
+            res.append({'label': row['dictname'],
+                        'value': row['dictname']})
+        return make_json_response(
+            data=res,
+            status=200
+        )
+
+    @check_precondition
+    def sql(self, gid, sid, did, scid, cfgid):
+        """
+        This function will reverse generate sql for sql panel
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        :param cfgid: FTS Configuration id
+        """
+        try:
+            sql = render_template(
+                "/".join([self.template_path, 'sql.sql']),
+                cfgid=cfgid,
+                scid=scid,
+                conn=self.conn
+            )
+            status, res = self.conn.execute_scalar(sql)
+            if not status:
+                return internal_server_error(
+                    _(
+                    "ERROR: Couldn't generate reversed engineered query for the FTS Configuration!\n{0}"
+                    ).format(
+                        res
+                    )
+                )
+
+            if res is None:
+                return gone(
+                    _(
+                        "ERROR: Couldn't generate reversed engineered query for FTS Configuration node!")
+                )
+
+            return ajax_response(response=res)
+
+        except Exception as e:
+            current_app.logger.exception(e)
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def dependents(self, gid, sid, did, scid, cfgid):
+        """
+        This function get the dependents and return ajax response
+        for the FTS Configuration node.
+
+        Args:
+            gid: Server Group ID
+            sid: Server ID
+            did: Database ID
+            scid: Schema ID
+            cfgid: FTS Configuration ID
+        """
+        dependents_result = self.get_dependents(self.conn, cfgid)
+        return ajax_response(
+            response=dependents_result,
+            status=200
+        )
+
+    @check_precondition
+    def dependencies(self, gid, sid, did, scid, cfgid):
+        """
+        This function get the dependencies and return ajax response
+        for the FTS Configuration node.
+
+        Args:
+            gid: Server Group ID
+            sid: Server ID
+            did: Database ID
+            scid: Schema ID
+            cfgid: FTS Configuration ID
+        """
+        dependencies_result = self.get_dependencies(self.conn, cfgid)
+        return ajax_response(
+            response=dependencies_result,
+            status=200
+        )
+
+
+FtsConfigurationView.register_node_view(blueprint)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/static/img/coll-fts_configuration.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/static/img/coll-fts_configuration.png
new file mode 100644
index 0000000000000000000000000000000000000000..01bf4ee5bbcc3f7c8598a4fa9d9a76e061bf60e0
GIT binary patch
literal 369
zcmeAS@N?(olHy`uVBq!ia0vp^0wBx*Bp9q_EZ7UAm`Z~Df*BafCZDwc@shmVT^JZv
z^(q?yd7K3vk;OpT1B~5HX4?T7OFdm2LnJOIConW}78-sOmJ~?%c}Bax{<t#d71=#|
zHPqCWN$5@Z@lwFUm#62x_~-Sqk-qU~?)RU_`jzx=&r9~zKjry1W&57Ff8930^nd;L
z<IHbY?b^4AY0<OSr`yBd9c5tswJ<Ah{>^>As$>q&(poX=^>V-YH|>j=m=Dhq*)Xe{
zJ^#4MHC^^d=CI8letex_H{)<aR+wI0^MmFqG5h<@u)mICPGw@SSyf=o@G(9S=wQ_n
z*NBpo#FA92<f2p{#b9J$XrOCoq-$UpVq{=tVr*q%qHSPmWnf@2Q&kQ{LvDUbW?Cgg
ggMlSj14y-%ff+=@sp+9>fEpM)UHx3vIVCg!0N_P|FaQ7m

literal 0
HcmV?d00001

diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/static/img/fts_configuration.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/static/img/fts_configuration.png
new file mode 100644
index 0000000000000000000000000000000000000000..0a5caf5373670cc684605d42c9eb21724fd75523
GIT binary patch
literal 346
zcmeAS@N?(olHy`uVBq!ia0vp^0wBx*Bp9q_EZ7UAm`Z~Df*BafCZDwc@shmVT^JZv
z^(q?yd7K3vk;OpT1B~5HX4?T7eV#6kArhC96ArLWyRPcz_RN1y<+t?5ekwm_`v3pt
zpXShUW$y3)=KnrDaoe%($o&2Lz8?Sn->2o%@1(l@)iv**{hVA{@b9+t^71?jt76M3
z|6d>7dilAY%nVDxQ~!THIdrkwu(MR)r>SB6yt#Ml)tB+7IbQubmD}%*%Lhj$?|Vug
zC9j3V?(aLp-Wjqs@c=Kwu}j{!7{ZG80bQnA;u=wsl30>zm0Xkxq!^403=MP*jdTqR
zLyQcpOpL8eOtcM5tqcq-W~$1eXvob^$xN$+XfUt@YXGUXGBAT^I5j<V4NwDvr>mdK
II;Vst0AK%q4gdfE

literal 0
HcmV?d00001

diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/css/fts_configuration.css b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/css/fts_configuration.css
new file mode 100644
index 0000000..b8ef393
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/css/fts_configuration.css
@@ -0,0 +1,3 @@
+.pgadmin-controls {
+    min-width: 0px !important;
+}
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/js/fts_configuration.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/js/fts_configuration.js
new file mode 100644
index 0000000..b1d73e6
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/js/fts_configuration.js
@@ -0,0 +1,579 @@
+define(
+        ['jquery', 'underscore', 'underscore.string', 'pgadmin',
+         'pgadmin.browser', 'alertify', 'pgadmin.browser.collection'],
+function($, _, S, pgAdmin, pgBrowser, alertify) {
+
+
+// Model for tokens control
+  var TokenModel = pgAdmin.Browser.Node.Model.extend({
+        defaults: {
+          token: undefined,
+          dictname: undefined
+        },
+        keys: ['token'],
+        // Define the schema for the token/dictionary list
+        schema: [{
+          id: 'token', label:'Token', type:'text', group: null,
+          cellHeaderClasses:'width_percent_50', editable: true,
+          editable: false, cell: 'string', url: 'tokens'
+        },{
+          id: 'dictname', label: 'Dictionaries', type: 'text', group:null,
+          cellHeaderClasses:'width_percent_50', editable: true,
+          cell:Backgrid.Extension.MultiSelectAjaxCell, url: 'dictionaries'
+        }],
+        // Validation for token and dictionary list
+        validate: function() {
+            // Clear any existing errors.
+          var msg;
+          this.errorModel.clear();
+          var token = this.get('token');
+          var dictionary = this.get('dictname');
+
+          if (_.isNull(token) ||
+              _.isUndefined(token) ||
+              String(token).replace(/^\s+|\s+$/g, '') == '') {
+            msg = '{{ _('Token can not be empty!') }}';
+            this.errorModel.set('token',msg);
+            return msg;
+          }
+
+          if (_.isNull(dictionary) ||
+              _.isUndefined(dictionary) ||
+              String(dictionary).replace(/^\s+|\s+$/g, '') == '') {
+            msg = '{{ _('Dictionary name can not be empty!') }}';
+            this.errorModel.set('dictname',msg);
+            return msg;
+          }
+          return null;
+        }
+  });
+
+// Customized control for token control
+  var TokenControl =  Backform.TokenControl =
+    Backform.UniqueColCollectionControl.extend({
+
+    initialize: function(opts) {
+      Backform.UniqueColCollectionControl.prototype.initialize.apply(
+        this, arguments
+      );
+
+      var self = that = this,
+          node = 'fts_configuration',
+          headerSchema = [{
+            id: 'token', label:'', type:'text', url: 'tokens',
+            node:'fts_configuration', canAdd: true, 'url_with_id': true,
+
+            // Defining control for tokens dropdown control in header
+            control: Backform.NodeAjaxOptionsControl.extend({
+              formatter: Backform.NodeAjaxOptionsControl.prototype.formatter,
+              initialize: function() {
+                Backform.NodeAjaxOptionsControl.prototype.initialize.apply(
+                  this,
+                  arguments
+                 );
+                var self = this,
+                  url = self.field.get('url') || self.defaults.url,
+                  m = self.model.top || self.model;
+
+                /* Fetch the tokens/dict list from 'that' node.
+                 * Here 'that' refers to unique collection control where
+                 * 'self' refers to nodeAjaxOptions control for dictionary
+                 */
+                var cfgid = that.model.get('oid');
+                if (url) {
+                  var node = this.field.get('schema_node'),
+                    node_info = this.field.get('node_info'),
+                    full_url = node.generate_url.apply(
+                      node, [
+                        null, url, this.field.get('node_data'),
+                        this.field.get('url_with_id') || false,
+                        node_info
+                      ]),
+                    cache_level = this.field.get('cache_level') || node.type,
+                    cache_node = this.field.get('cache_node');
+
+                  cache_node = (cache_node &&
+                                    pgAdmin.Browser.Nodes['cache_node'])
+                               || node;
+
+                  /*
+                   * We needs to check, if we have already cached data
+                   * for this url. If yes - use it, and do not bother about
+                   * fetching it again.
+                   */
+                  var data = cache_node.cache(url, node_info, cache_level);
+
+                  // Fetch token/dictionary list
+                  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(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);
+                  }
+
+                  // It is feasible that the data may not have been fetched.
+                  data = (data && data.data) || [];
+
+                  /*
+                   * Transform the data
+                   */
+                  transform = (this.field.get('transform')
+                                || self.defaults.transform);
+                  if (transform && _.isFunction(transform)) {
+                    self.field.set('options', transform.bind(self, data));
+                  } else {
+                    self.field.set('options', data);
+                  }
+                }
+              }
+            }),
+            // Select2 control for adding new tokens
+            select2: {
+              allowClear: true, width: 'style',
+              placeholder: 'Select token'
+            },
+            first_empty: true,
+            disabled: function(m) {
+              return _.isUndefined(self.model.get('oid'));
+            }
+          }],
+          headerDefaults = {token: null},
+          // Grid columns backgrid
+          gridCols = ['token', 'dictname'];
+
+      // Creating model for header control which is used to add new tokens
+      self.headerData = new (Backbone.Model.extend({
+        defaults: headerDefaults,
+        schema: headerSchema
+      }))({});
+
+      // Creating view from header schema in tokens control
+      var headerGroups = Backform.generateViewSchema(
+          self.field.get('node_info'), self.headerData, 'create',
+          self.field.get('schema_node'), self.field.get('node_data')
+          ),
+          fields = [];
+
+      _.each(headerGroups, function(o) {
+        fields = fields.concat(o.fields);
+      });
+      self.headerFields = new Backform.Fields(fields);
+
+      // creating grid using grid columns
+      self.gridSchema = Backform.generateGridColumnsFromModel(
+          self.field.get('node_info'), self.field.get('model'),
+          'edit', gridCols, self.field.get('schema_node')
+      );
+
+      // Providing behaviour control functions to header and grid control
+      self.controls = [];
+      self.listenTo(self.headerData, "change", self.headerDataChanged);
+      self.listenTo(self.headerData, "select2", self.headerDataChanged);
+      self.listenTo(self.collection, "add", self.onAddorRemoveTokens);
+      self.listenTo(self.collection, "remove", self.onAddorRemoveTokens);
+    },
+
+    // Template for creating header view
+    generateHeader: function(data) {
+      var header = [
+        '<div class="subnode-header-form">',
+        ' <div class="container-fluid">',
+        '  <div class="row">',
+        '   <div class="col-xs-4">',
+        '    <label class="control-label"><%-token_label%></label>',
+        '   </div>',
+        '   <div class="col-xs-4" header="token"></div>',
+        '   <div class="col-xs-4">',
+        '     <button class="btn-sm btn-default add" <%=canAdd ? "" : "disabled=\'disabled\'"%> ><%-add_label%></buttton>',
+        '   </div>',
+        '  </div>',
+        ' </div>',
+        '</div>',].join("\n")
+
+      _.extend(data, {
+        token_label: '{{ _('Tokens')}}',
+        add_label: '{{ _('ADD')}}'
+      });
+
+      var self = this,
+          headerTmpl = _.template(header),
+          $header = $(headerTmpl(data)),
+          controls = this.controls;
+
+      self.headerFields.each(function(field) {
+        var control = new (field.get("control"))({
+          field: field,
+          model: self.headerData
+        });
+
+        $header.find('div[header="' + field.get('name') + '"]').append(
+          control.render().$el
+        );
+
+        control.$el.find('.control-label').remove();
+        controls.push(control);
+      });
+
+      // We should not show add button in properties mode
+      if (data.mode == 'properties') {
+        $header.find("button.add").remove();
+      }
+
+      // Disable add button in token control in create mode
+      if(data.mode == 'create') {
+        $header.find("button.add").attr('disabled', true);
+      }
+
+      self.$header = $header;
+      return $header;
+    },
+
+    // Providing event handler for add button in header
+    events: _.extend(
+              {}, Backform.UniqueColCollectionControl.prototype.events,
+              {'click button.add': 'addTokens'}
+            ),
+
+    // Show token/dictionary grid
+    showGridControl: function(data) {
+
+      var self = this,
+          titleTmpl = _.template("<div class='subnode-header'></div>"),
+          $gridBody = $("<div></div>", {
+            class:'pgadmin-control-group backgrid form-group col-xs-12 object subnode'
+          }).append(
+               titleTmpl({label: data.label})
+          );
+
+      $gridBody.append(self.generateHeader(data));
+
+      var gridColumns = _.clone(this.gridSchema.columns);
+
+      // Insert Delete Cell into Grid
+      if (data.disabled == false && data.canDelete) {
+          gridColumns.unshift({
+            name: "pg-backform-delete", label: "",
+            cell: Backgrid.Extension.DeleteCell,
+            editable: false, cell_priority: -1
+          });
+      }
+
+      if (self.grid) {
+        self.grid.remove();
+        self.grid.null;
+      }
+      // Initialize a new Grid instance
+      var grid = self.grid = new Backgrid.Grid({
+        columns: gridColumns,
+        collection: self.collection,
+        className: "backgrid table-bordered"
+      });
+      self.$grid = grid.render().$el;
+
+      $gridBody.append(self.$grid);
+
+      // Find selected dictionaries in grid and show it all together
+      setTimeout(function() {
+        self.headerData.set({
+          'token': self.$header.find(
+            'div[header="token"] select'
+            ).val()
+            }, {silent:true}
+          );
+      }, 10);
+
+      // Render node grid
+      return $gridBody;
+    },
+
+    // When user change the header control to add a new token
+    headerDataChanged: function() {
+      var self = this, val,
+          data = this.headerData.toJSON(),
+          inSelected = (_.isEmpty(data) || _.isUndefined(data)),
+          checkVars = ['token'];
+
+      if (!self.$header) {
+        return;
+      }
+
+      self.$header.find('button.add').prop('disabled', inSelected);
+    },
+
+    // Get called when user click on add button header
+    addTokens: function(ev) {
+      ev.preventDefault();
+      var self = this,
+          token = self.headerData.get('token');
+
+      if (!token || token == '') {
+        return false;
+      }
+
+      var coll = self.model.get(self.field.get('name')),
+          m = new (self.field.get('model'))(
+                self.headerData.toJSON(), {
+                  silent: true, top: self.model.top,
+                  collection: coll, handler: coll
+                }),
+          checkVars = ['token'],
+          idx = -1;
+
+      // Find if token exists in grid
+      self.collection.each(function(m) {
+        _.each(checkVars, function(v) {
+          val = m.get(v);
+          if(val == token) {
+            idx = coll.indexOf(m);
+          }
+        });
+      });
+
+
+
+      // remove 'm' if duplicate value found.
+      if (idx == -1) {
+        coll.add(m);
+        idx = coll.indexOf(m);
+      }
+      self.$grid.find('.new').removeClass('new');
+      var newRow = self.grid.body.rows[idx].$el;
+      newRow.addClass("new");
+      //$(newRow).pgMakeVisible('table-bordered');
+      $(newRow).pgMakeVisible('backform-tab');
+
+
+      return false;
+    },
+
+    // When user delete token/dictionary entry from grid
+    onAddorRemoveTokens: function() {
+      var self = this;
+
+      /*
+       * Wait for collection to be updated before checking for the button to
+       * be enabled, or not.
+       */
+      setTimeout(function() {
+          self.collection.trigger('pgadmin:tokens:updated', self.collection);
+        self.headerDataChanged();
+      }, 10);
+    },
+
+    // When control is about to destroy
+    remove: function() {
+      /*
+       * Stop listening the events registered by this control.
+       */
+      this.stopListening(this.headerData, "change", this.headerDataChanged);
+      this.listenTo(this.headerData, "select2", this.headerDataChanged);
+      this.listenTo(this.collection, "remove", this.onAddorRemoveTokens);
+
+      TokenControl.__super__.remove.apply(this, arguments);
+
+      // Remove the header model
+      delete (this.headerData);
+
+    }
+  });
+
+
+  // Extend the collection class for FTS Configuration
+  if (!pgBrowser.Nodes['coll-fts_configuration']) {
+    var fts_configurations = pgAdmin.Browser.Nodes['coll-fts_configuration'] =
+      pgAdmin.Browser.Collection.extend({
+        node: 'fts_configuration',
+        label: '{{ _('FTS Configurations') }}',
+        type: 'coll-fts_configuration',
+        columns: ['name', 'description']
+      });
+  };
+
+  // Extend the node class for FTS Configuration
+  if (!pgBrowser.Nodes['fts_configuration']) {
+    pgAdmin.Browser.Nodes['fts_configuration'] = pgAdmin.Browser.Node.extend({
+      parent_type: ['schema', 'catalog'],
+      type: 'fts_configuration',
+      sqlAlterHelp: 'sql-altertsconfig.html',
+      sqlCreateHelp: 'sql-createtsconfig.html',
+      canDrop: true,
+      canDropCascade: true,
+      label: '{{ _('FTS Configuration') }}',
+      hasSQL: true,
+      hasDepends: true,
+      Init: function() {
+
+        // Avoid multiple registration of menus
+        if (this.initialized)
+          return;
+
+        this.initialized = true;
+
+        // Add context menus for FTS Configuration
+        pgBrowser.add_menus([{
+          name: 'create_fts_configuration_on_schema', node: 'schema',
+          module: this, category: 'create', priority: 4,
+          applies: ['object', 'context'], callback: 'show_obj_properties',
+          label: '{{_('FTS Configuration...')}}',
+          icon: 'wcTabIcon icon-fts_configuration', data: {action: 'create'}
+          },{
+          name: 'create_fts_configuration_on_coll', module: this, priority: 4,
+          node: 'coll-fts_configuration', applies: ['object', 'context'],
+          callback: 'show_obj_properties', category: 'create',
+          label: '{{ _('FTS Configuration...') }}', data: {action: 'create'},
+          icon: 'wcTabIcon icon-fts_configuration'
+          },{
+          name: 'create_fts_configuration', node: 'fts_configuration',
+          module: this, applies: ['object', 'context'],
+          callback: 'show_obj_properties', category: 'create', priority: 4,
+          label: '{{_('FTS Configuration...')}}', data: {action: 'create'},
+          icon: 'wcTabIcon icon-fts_configuration'
+          }]);
+      },
+
+      // Defining model for FTS Configuration node
+      model: pgAdmin.Browser.Node.Model.extend({
+        defaults: {
+          name: undefined,        // FTS Configuration name
+          owner: undefined,       // FTS Configuration owner
+          description: undefined, // Comment on FTS Configuration
+          schema: undefined,      // Schema name FTS Configuration belongs to
+          prsname: undefined,    // FTS parser list for FTS Configuration node
+          copy_config: undefined, // FTS configuration list to copy from
+          tokens: undefined      // token/dictionary pair list for node
+        },
+        initialize: function(attrs, args) {
+          var isNew = (_.size(attrs) === 0);
+
+          if (isNew) {
+            var userInfo = pgBrowser.serverInfo[args.node_info.server._id].user;
+            this.set({'owner': userInfo.name}, {silent: true});
+          }
+          pgAdmin.Browser.Node.Model.prototype.initialize.apply(this, arguments);
+          if (_.isUndefined(this.get('schema'))) {
+              this.set('schema', this.node_info.schema._id);
+          }
+        },
+        // Defining schema for FTS Configuration
+        schema: [{
+          id: 'name', label: '{{ _('Name') }}', cell: 'string',
+          type: 'text', cellHeaderClasses: 'width_percent_50'
+        },{
+          id: 'oid', label:'{{ _('OID') }}', cell: 'string',
+          editable: false, type: 'text', disabled: true, mode:['properties']
+        },{
+          id: 'owner', label:'{{ _('Owner') }}', cell: 'string',
+          type: 'text', mode: ['properties', 'edit','create'], node: 'role',
+          control: Backform.NodeListByNameControl
+        },{
+          id: 'schema', label: '{{ _('Schema')}}', cell: 'string',
+          type: 'text', mode: ['create','edit'], node: 'schema',
+          control: 'node-list-by-id'
+        },{
+          id: 'description', label:'{{ _('Comment') }}', cell: 'string',
+          type: 'multiline', cellHeaderClasses: 'width_percent_50'
+        },{
+          id: 'prsname', label: '{{ _('Parser')}}',type: 'text',
+          url: 'parsers', first_empty: true,
+          group: '{{ _('Definition') }}', control: 'node-ajax-options',
+          deps: ['copy_config'],
+          //disable parser when user select copy_config manually and vica-versa
+          disabled: function(m) {
+            var copy_config = m.get('copy_config');
+            return m.isNew() &&
+                    (_.isNull(copy_config) ||
+                    _.isUndefined(copy_config) ||
+                    copy_config === '') ? false : true;
+          }
+        },{
+          id: 'copy_config', label: '{{ _('Copy Config')}}',type: 'text',
+          mode: ['create'], group: '{{ _('Definition') }}',
+          control: 'node-ajax-options', url: 'copyConfig', deps: ['prsname'],
+
+          //disable copy_config when user select parser manually and vica-versa
+          disabled: function(m) {
+            var parser = m.get('prsname');
+            return m.isNew() &&
+                    (_.isNull(parser) ||
+                    _.isUndefined(parser) ||
+                    parser === '') ? false : true;
+          }
+        },{
+          id: 'tokens', label: '{{ _('Tokens') }}', type: 'collection',
+          group: '{{ _('Tokens') }}', control: TokenControl,
+          model: TokenModel, columns: ['token', 'dictionary'],
+          uniqueCol : ['token'], mode: ['create','edit'],
+          canAdd: true, canEdit: false, canDelete: true
+         }],
+
+        /*
+         * Triggers control specific error messages for name,
+         * copy_config/parser and schema, if any one of them is not specified
+         * while creating new fts configuration
+         */
+        validate: function(keys){
+          var msg;
+          var name = this.get('name');
+          var parser = this.get('prsname');
+          var copy_config_or_parser = !(parser === '' ||
+                                        _.isUndefined(parser) ||
+                                        _.isNull(parser)) ?
+                                        this.get('prsname') : this.get('copy_config');
+          var schema = this.get('schema');
+
+          // Clear the existing error model
+          this.errorModel.clear();
+          this.trigger('on-status-clear');
+
+          // Validate the name
+          if (_.isUndefined(name) ||
+              _.isNull(name) ||
+              String(name).replace(/^\s+|\s+$/g, '') == '') {
+            msg = '{{ _('Name must be specified!') }}';
+            this.errorModel.set('name', msg);
+            return msg;
+          }
+
+          // Validate parser or copy_config
+          else if (_.isUndefined(copy_config_or_parser) ||
+                   _.isNull(copy_config_or_parser) ||
+                   String(copy_config_or_parser).replace(/^\s+|\s+$/g, '') == '') {
+            msg = '{{ _('Select parser or configuration to copy!') }}';
+            this.errorModel.set('parser', msg);
+            return msg;
+          }
+
+          // Validate schema
+          else if (_.isUndefined(schema) ||
+                   _.isNull(schema) ||
+                   String(schema).replace(/^\s+|\s+$/g, '') == '') {
+            msg = '{{ _('Schema must be selected!') }}';
+            this.errorModel.set('schema', msg);
+            return msg;
+          }
+
+          return null;
+        }
+      })
+    });
+  }
+
+return pgBrowser.Nodes['coll-fts_configuration'];
+});
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/copy_config.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/copy_config.sql
new file mode 100644
index 0000000..584fc00
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/copy_config.sql
@@ -0,0 +1,15 @@
+{# FETCH copy config for FTS CONFIGURATION #}
+{% if copy_config %}
+SELECT
+    cfg.oid,
+    cfgname,
+    nspname,
+    n.oid as schemaoid
+FROM
+    pg_ts_config cfg
+    JOIN pg_namespace n
+    ON n.oid=cfgnamespace
+ORDER BY
+    nspname,
+    cfgname
+{% endif %}
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/create.sql
new file mode 100644
index 0000000..88f428b
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/create.sql
@@ -0,0 +1,15 @@
+{# CREATE FTS CONFIGURATION Statement #}
+{% if data and data.schema and data.name %}
+CREATE TEXT SEARCH CONFIGURATION {{ conn|qtIdent(data.schema, data.name) }} (
+{% if 'copy_config' in data and data.copy_config != '' %}
+    COPY={{ data.copy_config }}
+{% elif 'prsname' in data and data.prsname != '' %}
+    PARSER = {{ data.prsname }}
+{% endif %}
+);
+{# Description for FTS_CONFIGURATION #}
+
+{% if data.description %}
+COMMENT ON TEXT SEARCH CONFIGURATION {{ conn|qtIdent(data.schema, data.name) }}
+    IS {{ data.description|qtLiteral }};
+{% endif %}{% endif %}
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/delete.sql
new file mode 100644
index 0000000..0052f61
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/delete.sql
@@ -0,0 +1,4 @@
+{# DROP FTS CONFIGURATION Statement #}
+{% if schema and name %}
+DROP TEXT SEARCH CONFIGURATION {{conn|qtIdent(schema)}}.{{conn|qtIdent(name)}} {% if cascade %}CASCADE{%endif%};
+{% endif %}
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/dictionaries.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/dictionaries.sql
new file mode 100644
index 0000000..f0e7b0f
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/dictionaries.sql
@@ -0,0 +1,7 @@
+{# FETCH DICTIONARIES statement #}
+SELECT
+    dictname
+FROM
+    pg_ts_dict
+ORDER BY
+    dictname
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/get_name.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/get_name.sql
new file mode 100644
index 0000000..7238b8c
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/get_name.sql
@@ -0,0 +1,17 @@
+{# GET FTS CONFIGURATION name #}
+{% if cfgid %}
+SELECT
+    cfg.cfgname as name,
+    (
+    SELECT
+        nspname
+    FROM
+        pg_namespace
+    WHERE
+        oid = cfg.cfgnamespace
+    ) as schema
+FROM
+    pg_ts_config cfg
+WHERE
+    cfg.oid = {{cfgid}}::OID;
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/nodes.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/nodes.sql
new file mode 100644
index 0000000..ec50acb
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/nodes.sql
@@ -0,0 +1,13 @@
+{# FETCH FTS CONFIGURATION NAME statement #}
+SELECT
+    oid, cfgname as name
+FROM
+    pg_ts_config cfg
+WHERE
+{% if scid %}
+    cfg.cfgnamespace = {{scid}}::OID
+{% elif cfgid %}
+    cfg.oid = {{cfgid}}::OID
+{% endif %}
+
+ORDER BY name
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/parser.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/parser.sql
new file mode 100644
index 0000000..cbca9c9
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/parser.sql
@@ -0,0 +1,24 @@
+{# PARSER name from FTS CONFIGURATION OID #}
+{% if cfgid %}
+SELECT
+    cfgparser
+FROM
+    pg_ts_config
+where
+    oid = {{cfgid}}::OID
+{% endif %}
+
+
+{# PARSER list #}
+{% if parser %}
+SELECT
+    prsname,
+    nspname,
+    n.oid as schemaoid
+FROM
+    pg_ts_parser
+    JOIN pg_namespace n
+    ON n.oid=prsnamespace
+ORDER BY
+    prsname;
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/properties.sql
new file mode 100644
index 0000000..a2f376e
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/properties.sql
@@ -0,0 +1,25 @@
+{# FETCH properties for FTS CONFIGURATION #}
+SELECT
+    cfg.oid,
+    cfg.cfgname as name,
+    pg_get_userbyid(cfg.cfgowner) as owner,
+    cfg.cfgparser as parser,
+    cfg.cfgnamespace as schema,
+    parser.prsname as prsname,
+    description
+FROM
+    pg_ts_config cfg
+    LEFT OUTER JOIN pg_ts_parser parser
+    ON parser.oid=cfg.cfgparser
+    LEFT OUTER JOIN pg_description des
+    ON (des.objoid=cfg.oid AND des.classoid='pg_ts_config'::regclass)
+WHERE
+{% if scid %}
+    cfg.cfgnamespace = {{scid}}::OID
+{% elif name %}
+    cfg.cfgname = {{name|qtLiteral}}
+{% endif %}
+{% if cfgid %}
+    AND cfg.oid = {{cfgid}}::OID
+{% endif %}
+ORDER BY cfg.cfgname
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/schema.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/schema.sql
new file mode 100644
index 0000000..b56ceb8
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/schema.sql
@@ -0,0 +1,19 @@
+{# FETCH statement for SCHEMA name #}
+{% if data.schema %}
+SELECT
+    nspname
+FROM
+    pg_namespace
+WHERE
+    oid = {{data.schema}}::OID
+
+{% elif data.id %}
+SELECT
+    nspname
+FROM
+    pg_namespace nsp
+    LEFT JOIN pg_ts_config cfg
+    ON cfg.cfgnamespace = nsp.oid
+WHERE
+    cfg.oid = {{data.id}}::OID
+{% endif %}
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/sql.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/sql.sql
new file mode 100644
index 0000000..e31307d
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/sql.sql
@@ -0,0 +1,75 @@
+{# REVERSED ENGINEERED SQL FOR FTS CONFIGURATION #}
+{% if cfgid and scid %}
+SELECT
+    array_to_string(array_agg(sql), E'\n\n') as sql
+FROM
+    (
+    SELECT
+        E'-- Text Search CONFIGURATION: ' || quote_ident(nspname) || E'.'
+        || quote_ident(cfg.cfgname) ||
+        E'\n\n-- DROP TEXT SEARCH CONFIGURATION ' || quote_ident(nspname) ||
+        E'.' || quote_ident(cfg.cfgname) ||
+        E'\n\nCREATE TEXT SEARCH CONFIGURATION ' || quote_ident(nspname) ||
+        E'.' ||  quote_ident(cfg.cfgname) || E' (\n' ||
+        E'\tPARSER = ' || parsername ||
+        E'\n);' ||
+        CASE
+            WHEN description IS NOT NULL THEN
+                E'\n\nCOMMENT ON TEXT SEARCH CONFIGURATION ' ||
+                quote_ident(nspname) || E'.' || quote_ident(cfg.cfgname) ||
+                E' IS ' || pg_catalog.quote_literal(description) || E';'
+            ELSE ''
+        END ||
+        (
+        SELECT array(
+	        SELECT
+	            'ALTER TEXT SEARCH CONFIGURATION ' || quote_ident(nspname) ||
+	            E'.' || quote_ident(cfg.cfgname) || ' ADD MAPPING FOR ' ||
+	            t.alias  || ' WITH ' ||
+	            array_to_string(array_agg(dict.dictname), ', ') || ';'
+            FROM
+                pg_ts_config_map map
+                LEFT JOIN (
+                          SELECT
+                              tokid,
+                              alias
+                          FROM
+                              pg_catalog.ts_token_type(cfg.cfgparser)
+                          ) t ON (t.tokid = map.maptokentype)
+                LEFT OUTER JOIN pg_ts_dict dict ON (map.mapdict = dict.oid)
+            WHERE
+                map.mapcfg = cfg.oid
+            GROUP BY t.alias
+            ORDER BY t.alias)
+        ) as sql
+    FROM
+        pg_ts_config cfg
+    LEFT JOIN (
+        SELECT
+            des.description as description,
+            des.objoid as descoid
+        FROM
+            pg_description des
+        WHERE
+            des.objoid={{cfgid}}::OID AND des.classoid='pg_ts_config'::regclass
+    ) a ON (a.descoid = cfg.oid)
+    LEFT JOIN (
+        SELECT
+            nspname,
+            nsp.oid as noid
+        FROM
+            pg_namespace nsp
+        WHERE
+            oid = {{scid}}::OID
+    ) b ON (b.noid = cfg.cfgnamespace)
+    LEFT JOIN(
+        SELECT
+            prs.prsname as parsername,
+            prs.oid as oid
+        FROM
+            pg_ts_parser prs
+    )c ON (c.oid = cfg.cfgparser)
+    WHERE
+       cfg.oid={{cfgid}}::OID
+    ) e;
+{% endif %}
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/tokenDictList.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/tokenDictList.sql
new file mode 100644
index 0000000..e8a2d5b
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/tokenDictList.sql
@@ -0,0 +1,23 @@
+{# Fetch token/dictionary list for FTS CONFIGURATION #}
+{% if cfgid %}
+SELECT
+    (
+    SELECT
+        t.alias
+    FROM
+        pg_catalog.ts_token_type(cfgparser) AS t
+    WHERE
+        t.tokid = maptokentype
+    ) AS token,
+    array_agg(dictname) AS dictname
+FROM
+    pg_ts_config_map
+    LEFT OUTER JOIN pg_ts_config ON mapcfg = pg_ts_config.oid
+    LEFT OUTER JOIN pg_ts_dict ON mapdict = pg_ts_dict.oid
+WHERE
+    mapcfg={{cfgid}}::OID
+GROUP BY
+    token
+ORDER BY
+    1
+{% endif %}
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/tokens.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/tokens.sql
new file mode 100644
index 0000000..ce0c5eb
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/tokens.sql
@@ -0,0 +1,10 @@
+{# Tokens for FTS CONFIGURATION node #}
+
+{% if parseroid %}
+SELECT
+    alias
+FROM
+    ts_token_type({{parseroid}}::OID)
+ORDER BY
+    alias
+{% endif %}
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/update.sql
new file mode 100644
index 0000000..77599f8
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/templates/fts_configuration/sql/9.1_plus/update.sql
@@ -0,0 +1,51 @@
+{# UPDATE statement for FTS CONFIGURATION #}
+{% if data %}
+{% set name = o_data.name %}
+{% set schema = o_data.schema %}
+{% if data.name and data.name != o_data.name %}
+{% set name = data.name %}
+ALTER TEXT SEARCH CONFIGURATION {{conn|qtIdent(o_data.schema)}}.{{conn|qtIdent(o_data.name)}}
+    RENAME TO {{conn|qtIdent(data.name)}};
+
+{% endif %}
+{% if 'tokens' in data %}
+{% if'changed' in data.tokens %}
+{% for tok in data.tokens.changed %}
+ALTER TEXT SEARCH CONFIGURATION {{conn|qtIdent(o_data.schema)}}.{{conn|qtIdent(name)}}
+    ALTER MAPPING FOR {{tok.token}}
+    WITH {% for dict in tok.dictname %}{{dict}}{% if not loop.last %}, {% endif %}{% endfor %};
+
+{% endfor %}
+{% endif %}
+{% if'added' in data.tokens %}
+{% for tok in data.tokens.added %}
+ALTER TEXT SEARCH CONFIGURATION {{conn|qtIdent(o_data.schema)}}.{{conn|qtIdent(name)}}
+    ADD MAPPING FOR {{tok.token}}
+    WITH {% for dict in tok.dictname %}{{dict}}{% if not loop.last %}, {% endif %}{% endfor %};
+
+{% endfor %}
+{% endif %}
+{% if'deleted' in data.tokens %}
+{% for tok in data.tokens.deleted %}
+ALTER TEXT SEARCH CONFIGURATION {{conn|qtIdent(o_data.schema)}}.{{conn|qtIdent(name)}}
+    DROP MAPPING FOR {{tok.token}};
+
+{% endfor %}
+{% endif %}
+{% endif %}
+{% if 'owner' in data and data.owner != o_data.owner %}
+ALTER TEXT SEARCH CONFIGURATION {{conn|qtIdent(o_data.schema)}}.{{conn|qtIdent(name)}}
+    OWNER TO {{data.owner}};
+
+{% endif %}
+{% if 'schema' in data and data.schema != o_data.schema %}
+{% set schema = data.schema%}
+ALTER TEXT SEARCH CONFIGURATION {{conn|qtIdent(o_data.schema)}}.{{conn|qtIdent(name)}}
+    SET SCHEMA {{data.schema}};
+
+{% endif %}
+{% if 'description' in data and data.description != o_data.description %}
+COMMENT ON TEXT SEARCH CONFIGURATION {{conn|qtIdent(schema)}}.{{conn|qtIdent(name)}}
+    IS {{ data.description|qtLiteral }};
+{% endif %}
+{% endif %}
\ No newline at end of file
diff --git a/web/pgadmin/browser/static/js/node.ui.js b/web/pgadmin/browser/static/js/node.ui.js
index 8bf71dc..7e84935 100644
--- a/web/pgadmin/browser/static/js/node.ui.js
+++ b/web/pgadmin/browser/static/js/node.ui.js
@@ -504,6 +504,35 @@ function($, _, pgAdmin, Backbone, Backform, Alertify, Node) {
     })
   });
 
+    // 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.
    */
-- 
Sent via pgadmin-hackers mailing list (pgadmin-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers

Reply via email to