You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ja...@apache.org on 2021/05/12 08:47:28 UTC

[lucene-solr] branch branch_8x updated: SOLR-6152 Pre-populating values into search parameters on the query page of solr admin (#2489)

This is an automated email from the ASF dual-hosted git repository.

janhoy pushed a commit to branch branch_8x
in repository https://gitbox.apache.org/repos/asf/lucene-solr.git


The following commit(s) were added to refs/heads/branch_8x by this push:
     new 1ce087f  SOLR-6152  Pre-populating values into search parameters on the query page of solr admin (#2489)
1ce087f is described below

commit 1ce087f321780d16b199d8acbc10f6560dfa8fbf
Author: daniel6691 <da...@hotmail.com>
AuthorDate: Wed May 12 10:47:06 2021 +0200

    SOLR-6152  Pre-populating values into search parameters on the query page of solr admin (#2489)
    
    Co-authored-by: d.lechner <Fussball_3>
---
 solr/CHANGES.txt                                |   2 +
 solr/webapp/web/css/angular/query.css           |   9 +-
 solr/webapp/web/js/angular/controllers/query.js | 150 +++++++++++--
 solr/webapp/web/partials/query.html             | 284 ++++++++++++------------
 4 files changed, 275 insertions(+), 170 deletions(-)

diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 74c92b0..3e855a6 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -56,6 +56,8 @@ Improvements
 
 * SOLR-15337: Avoid XPath in solrconfig.xml parsing (noble)
 
+* SOLR-6152: Pre-populating values into search parameters on the query page of solr admin (jafurrer, daniel6691)
+
 * SOLR-15156: [child] doc transformer's childFilter param no longer applies query syntax escaping.
   It was inconsistent with other Solr options.  It did this escaping since 8.0 but not prior.  (David Smiley)
 
diff --git a/solr/webapp/web/css/angular/query.css b/solr/webapp/web/css/angular/query.css
index be264bf..dc0c457 100644
--- a/solr/webapp/web/css/angular/query.css
+++ b/solr/webapp/web/css/angular/query.css
@@ -128,16 +128,21 @@ limitations under the License.
   margin-top: 0;
 }
 
-#content #query #form fieldset .fieldset
+#content #query #form fieldset:not(.optional) .fieldset
 {
   border-bottom: 1px solid #f0f0f0;
   margin-bottom: 5px;
   padding-bottom: 10px;
 }
+#content #query #form fieldset.optional .fieldset{
+  margin-bottom: 5px;
+  padding-bottom: 10px;
+}
 
 #content #query #form .optional
 {
-  border: 0;
+  border-top-width: 0;
+  border-bottom: 1px solid #f0f0f0;
 }
 
 #content #query #form .optional legend
diff --git a/solr/webapp/web/js/angular/controllers/query.js b/solr/webapp/web/js/angular/controllers/query.js
index e8a3870..a1e8a38 100644
--- a/solr/webapp/web/js/angular/controllers/query.js
+++ b/solr/webapp/web/js/angular/controllers/query.js
@@ -19,17 +19,79 @@ solrAdminApp.controller('QueryController',
   function($scope, $routeParams, $location, Query, Constants){
     $scope.resetMenu("query", Constants.IS_COLLECTION_PAGE);
 
-    // @todo read URL parameters into scope
-    $scope.query = {q:'*:*'};
+    $scope._models = [];
     $scope.filters = [{fq:""}];
-    $scope.dismax = {defType: "dismax"};
-    $scope.edismax = {defType: "edismax", stopwords: true, lowercaseOperators: false};
-    $scope.hl = {hl:"on"};
-    $scope.facet = {facet: "on"};
-    $scope.spatial = {};
-    $scope.spellcheck = {spellcheck:"on"};
-    $scope.debugQuery = {debugQuery: "on"};
-    $scope.qt = "/select";
+    $scope.val = {};
+    $scope.val['q'] = "*:*";
+    $scope.val['q.op'] = "OR";
+    $scope.val['defType'] = "";
+
+    // get list of ng-models that have a form element
+    function setModels(argTagName){
+        var fields = document.getElementsByTagName(argTagName);
+        for( var i = 0, iLen = fields.length; i<iLen; i++ ){
+            var model = fields[i].getAttribute("ng-model");
+            if( model ){
+                $scope._models.push({modelName: model, element: fields[i]});
+            }
+        }
+    }
+    function setUrlParams(){
+      var urlParams = $location.search();
+      for( var p in urlParams ){
+        if( !urlParams.hasOwnProperty(p) ){
+          continue;
+        }
+        if( p === "fq" ) {
+            // filters are handled specially because of possible multiple values
+            addFilters(urlParams[p]);
+        } else {
+            setParam(p, urlParams[p]);
+        }
+      }
+    }
+    function setParam(argKey, argValue, argProperty){
+      if( argProperty ){
+        $scope[argProperty][argKey] = argValue;
+      } else if ( $scope._models.map(function(field){return field.modelName}).indexOf("val['" + argKey + "']") > -1 ) {
+        // parameters stored in "val" will be used in both links (admin link and REST-request)
+        var index = $scope._models.map(function(field){return field.modelName}).indexOf("val['" + argKey + "']");
+        var field = $scope._models[index].element;
+        if( argValue === "true" || argValue === "false"){
+          // use real booleans
+          argValue = argValue === "true";
+        } else if( field.tagName.toUpperCase() === "INPUT" && field.type === "checkbox" && (argValue === "on" || argValue === "off") ){
+          argValue = argValue === "on";
+        }
+        $scope.val[argKey] = argValue;
+      } else if( $scope._models.map(function(field){return field.modelName}).indexOf(argKey) > -1 ) {
+        // parameters that will only be used to generate the admin link
+        $scope[argKey] = argValue;
+      } else {
+        insertToRawParams(argKey, argValue);
+      }
+    }
+    // store not processed values to be displayed in a field
+    function insertToRawParams(argKey, argValue){
+      if( $scope.rawParams == null ){
+        $scope.rawParams = "";
+      } else {
+        $scope.rawParams += "&";
+      }
+      $scope.rawParams += argKey + "=" + argValue;
+    }
+    function addFilters(argObject){
+      if( argObject ){
+        $scope.filters = [];
+          if( Array.isArray(argObject) ){
+            for( var i = 0, iLen = argObject.length; i<iLen; i++ ){
+              $scope.filters.push({fq: argObject[i]});
+            }
+          } else {
+            $scope.filters.push({fq: argObject});
+          }
+      }
+    }
 
     $scope.doQuery = function() {
       var params = {};
@@ -45,21 +107,38 @@ solrAdminApp.controller('QueryController',
         for (var key in query) {
           terms = query[key];
           // Booleans have no length property - only set them if true
-          if (((typeof(terms) == typeof(true) && terms) || terms.length > 0) && key[0]!="$") {
+          if (typeof(terms) == typeof(true) || (terms.length && terms.length > 0 && key[0]!=="$") ) {
             set(key, terms);
           }
         }
-      };
+      }
+      // remove non-visible fields from the request-params
+      var purgeParams = function(params, fields, bRemove){
+        if( !bRemove ){
+            return;
+        }
+        for( var i = 0, iLen = fields.length; i<iLen; i++ ){
+            if( params.hasOwnProperty(fields[i]) ){
+                delete params[fields[i]];
+            }
+        }
+      }
+      var getDependentFields = function(argParam){
+          return Object.keys($scope.val)
+              .filter(function(param){
+                  return param.indexOf(argParam) === 0;
+              });
+      }
 
-      copy(params, $scope.query);
+      copy(params, $scope.val);
 
-      if ($scope.isDismax)     copy(params, $scope.dismax);
-      if ($scope.isEdismax)    copy(params, $scope.edismax);
-      if ($scope.isHighlight)  copy(params, $scope.hl);
-      if ($scope.isFacet)      copy(params, $scope.facet);
-      if ($scope.isSpatial)    copy(params, $scope.spatial);
-      if ($scope.isSpellcheck) copy(params, $scope.spellcheck);
-      if ($scope.isDebugQuery) copy(params, $scope.debugQuery);
+      purgeParams(params, ["q.alt", "qf", "mm", "pf", "ps", "qs", "tie", "bq", "bf"], $scope.val.defType !== "dismax" && $scope.val.defType !== "edismax");
+      purgeParams(params, ["uf", "pf2", "pf3", "ps2", "ps3", "boost", "stopwords", "lowercaseOperators"], $scope.val.defType !== "edismax");
+      purgeParams(params, getDependentFields("indent"), $scope.val.indent !== false);
+      purgeParams(params, getDependentFields("hl"), $scope.val.hl !== true);
+      purgeParams(params, getDependentFields("facet"), $scope.val.facet !== true);
+      purgeParams(params, getDependentFields("spatial"), $scope.val.spatial !== true);
+      purgeParams(params, getDependentFields("spellcheck"), $scope.val.spellcheck !== true);
 
       if ($scope.rawParams) {
         var rawParams = $scope.rawParams.split(/[&\n]/);
@@ -87,9 +166,19 @@ solrAdminApp.controller('QueryController',
         params.handler = "select";
         set("qt", qt);
       }
+      // create rest result url
       var url = Query.url(params);
+
+      // create admin page url
+      var adminParams = {...params};
+      delete adminParams.handler;
+      delete adminParams.core
+      if( $scope.qt != null ) {
+        adminParams.qt = [$scope.qt];
+      }
+
       Query.query(params, function(data) {
-        $scope.lang = $scope.query.wt;
+        $scope.lang = $scope.val['wt'];
         if ($scope.lang == undefined || $scope.lang == '') {
           $scope.lang = "json";
         }
@@ -97,14 +186,28 @@ solrAdminApp.controller('QueryController',
         // Use relative URL to make it also work through proxies that may have a different host/port/context
         $scope.url = url;
         $scope.hostPortContext = $location.absUrl().substr(0,$location.absUrl().indexOf("#")); // For display only
+        for( key in $location.search() ){
+            $location.search(key, null);
+        }
+        for( var key in adminParams ){
+          if( Array.isArray(adminParams[key]) && adminParams[key].length === 1 ){
+            adminParams[key] = adminParams[key][0];
+          }
+          if( typeof adminParams[key] === typeof true ){
+            adminParams[key] = adminParams[key].toString();
+          }
+          $location.search(key, adminParams[key]);
+        }
       });
     };
+    setModels("input");
+    setModels("textarea");
+    setModels("select");
+    setUrlParams();
 
     if ($location.search().q) {
-      $scope.query.q = $location.search()["q"];
       $scope.doQuery();
     }
-
     $scope.removeFilter = function(index) {
       if ($scope.filters.length === 1) {
         $scope.filters = [{fq: ""}];
@@ -112,7 +215,6 @@ solrAdminApp.controller('QueryController',
         $scope.filters.splice(index, 1);
       }
     };
-
     $scope.addFilter = function(index) {
       $scope.filters.splice(index+1, 0, {fq:""});
     };
diff --git a/solr/webapp/web/partials/query.html b/solr/webapp/web/partials/query.html
index 3d329ea..4c59865 100644
--- a/solr/webapp/web/partials/query.html
+++ b/solr/webapp/web/partials/query.html
@@ -21,7 +21,7 @@ limitations under the License.
       <label for="qt" title="Request handler in solrconfig.xml.">
         <a rel="help">Request-Handler (qt)</a>
       </label>
-      <input type="text" ng-model="qt" id="qt" value="/select" title="Request handler in solrconfig.xml.">
+      <input type="text" ng-model="qt" id="qt" placeholder="/select" value="/select" title="Request handler in solrconfig.xml.">
 
       <fieldset class="common">
         <legend>common</legend>
@@ -30,14 +30,22 @@ limitations under the License.
         <label for="q" title="The query string.">
           q
         </label>
-        <textarea name="q" ng-model="query.q" id="q" title="The query string.">*:*</textarea>
+        <textarea name="q" ng-model="val['q']" id="q" title="The query string.">*:*</textarea>
+
+        <label for="qOp" title="The query operator.">
+          q.op
+        </label>
+        <select ng-model="val['q.op']" id="qOp" name="q.op" title="The query operator.">
+          <option value="AND">AND</option>
+          <option value="OR">OR</option>
+        </select>
 
         <label for="fq" title="Filter query.">
           <a rel="help">fq</a>
         </label>
-        <div class="multiple">
+        <div class="multiple" id="fq">
           <div class="row clearfix" ng-repeat="filter in filters">
-            <input type="text" ng-model="filter.fq" id="fq" name="fq" title="Filter query.">
+            <input type="text" ng-model="filter.fq" name="fq" title="Filter query.">
             <div class="buttons">
               <a class="rem" ng-click="removeFilter($index)"><span></span></a>
               <a class="add" ng-click="addFilter($index)"><span></span></a>
@@ -48,36 +56,31 @@ limitations under the License.
         <label for="sort" title="Sort field or function with asc|desc.">
           <a rel="help">sort</a>
         </label>
-        <input type="text" ng-model="query.sort" id="sort" name="sort" title="Sort field or function with asc|desc.">
+        <input type="text" ng-model="val['sort']" id="sort" name="sort" title="Sort field or function with asc|desc.">
 
         <label for="start" title="Number of leading documents to skip and number of documents to return after 'start'. (Integers)">
           <a rel="help">start</a>,
           <a rel="help">rows</a>
         </label>
         <div class="clearfix">
-          <input type="text" name="start" ng-model="query.start" id="start" placeholder="0" pattern="[0-9]+" title="Number of leading documents to skip. (Integer)">
-          <input type="text" name="rows" ng-model="query.rows" id="rows" placeholder="10" pattern="[0-9]+" title="Number of documents to return after 'start'. (Integer)">
+          <input type="text" name="start" ng-model="val['start']" id="start" placeholder="0" pattern="[0-9]+" title="Number of leading documents to skip. (Integer)">
+          <input type="text" name="rows" ng-model="val['rows']" id="rows" placeholder="10" pattern="[0-9]+" title="Number of documents to return after 'start'. (Integer)">
         </div>
 
         <label for="fl" title="Field list, comma separated.">
           <a rel="help">fl</a>
         </label>
-        <input type="text" ng-model="query.fl" name="fl" id="fl" value="" title="Field list, comma separated.">
+        <input type="text" ng-model="val['fl']" name="fl" id="fl" value="" title="Field list, comma separated.">
 
         <label for="df" title="Default search field">
           <a rel="help">df</a>
         </label>
-        <input type="text" ng-model="query.df" name="df" id="df" value="" title="Default search field">
-
-        <label for="custom_parameters">
-          <a rel="help">Raw Query Parameters</a>
-        </label>
-        <input type="text" ng-model="rawParams" id="custom_parameters" value="" placeholder="key1=val1&amp;key2=val2">
+        <input type="text" ng-model="val['df']" name="df" id="df" value="" title="Default search field">
 
         <label for="wt" title="The writer type (response format).">
           <a rel="help">wt</a>
         </label>
-        <select name="wt" ng-model="query.wt" id="wt" title="The writer type (response format).">
+        <select name="wt" ng-model="val['wt']" id="wt" title="The writer type (response format).">
           <option ng-selected="selected" value=''>------</option>
           <option>json</option>
           <option>xml</option>
@@ -87,8 +90,8 @@ limitations under the License.
           <option>csv</option>
         </select>
 
-        <label for="indent off" class="checkbox" title="Do not indent results.">
-          <input type="checkbox" ng-model="query.indent" name="indent" id="indent" title="Do not indent results." ng-true-value="'off'" ng-false-value="''">
+        <label for="indent" class="checkbox" title="Do not indent results.">
+          <input type="checkbox" ng-model="val['indent']" name="indent" id="indent" title="Do not indent results." ng-true-value="false" ng-false-value="true">
           indent off
         </label>
 
@@ -98,14 +101,14 @@ limitations under the License.
       <fieldset class="debugQuery optional">
         <legend>
           <label for="debugQuery" class="checkbox" title="Show timing and diagnostics.">
-            <input type="checkbox" ng-model="isDebugQuery" name="debugQuery" id="debugQuery" value="true">
+            <input type="checkbox" ng-model="val['debugQuery']" name="debugQuery" id="debugQuery" value="true">
             debugQuery
           </label>
         </legend>
         <div class="fieldset" ng-show="isDebugQuery">
 
         <label for="debug_explain_structured" class="checkbox" title="Show Score explanations as nested structures.">
-          <input type="checkbox" ng-model="debugQuery['debug.explain.structured']" name="debug.explain.structured" id="debug_explain_structured" value="true">
+          <input type="checkbox" ng-model="val['debug.explain.structured']" name="debug.explain.structured" id="debug_explain_structured" value="true">
           debug.explain.structured
         </label>
 
@@ -114,140 +117,105 @@ limitations under the License.
 
       <fieldset class="dismax optional">
         <legend>
-          <label for="dismax" class="checkbox" title="Enable dismax.">
-            <input type="checkbox" ng-model="isDismax" name="defType" id="dismax" value="dismax">
-            dismax
-          </label>
-        </legend>
-        <div class="fieldset" ng-show="isDismax">
-
-        <label for="q_alt" title="Alternate query when 'q' is absent.">q.alt</label>
-        <input type="text" ng-model="dismax['q.alt']" name="q.alt" id="q_alt" title="Alternate query when 'q' is absent.">
-
-        <label for="qf" title="Query fields with optional boosts.">qf</label>
-        <input type="text" ng-model="dismax.qf" name="qf" id="qf" title="Query fields with optional boosts.">
-
-        <label for="mm" title="Min-should-match expression">mm</label>
-        <input type="text" ng-model="dismax.mm" name="mm" id="mm" title="Min-should-match expression.">
-
-        <label for="pf" title="Phrase boosted fields.">pf</label>
-        <input type="text" ng-model="dismax.pf" name="pf" id="pf" title="Phrase boosted fields.">
-
-        <label for="ps" title="Phrase boost slop.">ps</label>
-        <input type="text" ng-model="dismax.ps" name="ps" id="ps" title="Phrase boost slop.">
-
-        <label for="qs" title="Query string phrase slop.">qs</label>
-        <input type="text" ng-model="dismax.qs" name="qs" id="qs" title="Query string phrase slop.">
-
-        <label for="tie" title="Score tie-breaker. Try 0.1.">tie</label>
-        <input type="text" ng-model="dismax.tie" name="tie" id="tie" title="Score tie-breaker. Try 0.1.">
-
-        <label for="bq" title="Boost query.">bq</label>
-        <input type="text" ng-model="dismax.bq" name="bq" id="bq" title="Boost query.">
-
-        <label for="bf" title="Boost function (added).">bf</label>
-        <input type="text" ng-model="dismax.bf" name="bf" id="bf" title="Boost function (added).">
-
-        </div>
-      </fieldset>
-
-      <fieldset class="edismax optional">
-        <legend>
-          <label for="edismax" class="checkbox" title="Enable edismax.">
-            <input type="checkbox" ng-model="isEdismax" name="defType" id="edismax" value="edismax">
-            <strong>e</strong>dismax
+          <label for="defType" class="checkbox" title="Choose defType">
+            defType
+            <select ng-model="val['defType']" name="defType" id="defType">
+              <option ng-selected="selected" value=''>lucene</option>
+              <option>dismax</option>
+              <option>edismax</option>
+            </select>
           </label>
         </legend>
-        <div class="fieldset" ng-show="isEdismax">
 
-        <label for="edismax_q_alt" title="Alternate query when 'q' is absent.">q.alt</label>
-        <input type="text" ng-model="edismax['q.alt']" name="q.alt" id="edismax_q_alt"  title="Alternate query when 'q' is absent.">
+        <div class="fieldset" ng-show="val['defType']!=''">
+          <label for="q_alt" title="Alternate query when 'q' is absent.">q.alt</label>
+          <input type="text" ng-model="val['q.alt']" name="q.alt" id="q_alt"  title="Alternate query when 'q' is absent.">
 
-        <label for="edismax_qf" title="Query fields with optional boosts.">qf</label>
-        <input type="text" ng-model="edismax.qf" name="qf" id="edismax_qf" title="Query fields with optional boosts.">
+          <label for="qf" title="Query fields with optional boosts.">qf</label>
+          <input type="text" ng-model="val['qf']" name="qf" id="qf" title="Query fields with optional boosts.">
 
-        <label for="edismax_mm" title="Min-should-match expression.">mm</label>
-        <input type="text" ng-model="edismax.mm" name="mm" id="edismax_mm" title="Min-should-match expression.">
+          <label for="mm" title="Min-should-match expression.">mm</label>
+          <input type="text" ng-model="val['mm']" name="mm" id="mm" title="Min-should-match expression.">
 
-        <label for="edismax_pf" title="Phrase boosted fields.">pf</label>
-        <input type="text" ng-model="edismax.pf" name="pf" id="edismax_pf" title="Phrase boosted fields.">
+          <label for="pf" title="Phrase boosted fields.">pf</label>
+          <input type="text" ng-model="val['pf']" name="pf" id="pf" title="Phrase boosted fields.">
 
-        <label for="edismax_ps" title="Phrase boost slop.">ps</label>
-        <input type="text" ng-model="edismax.ps" name="ps" id="edismax_ps" title="Phrase boost slop.">
+          <label for="ps" title="Phrase boost slop.">ps</label>
+          <input type="text" ng-model="val['ps']" name="ps" id="ps" title="Phrase boost slop.">
 
-        <label for="edismax_qs" title="Query string phrase slop.">qs</label>
-        <input type="text" ng-model="edismax.qs" name="qs" id="edismax_qs" title="Query string phrase slop.">
+          <label for="qs" title="Query string phrase slop.">qs</label>
+          <input type="text" ng-model="val['qs']" name="qs" id="qs" title="Query string phrase slop.">
 
-        <label for="edismax_tie" title="Score tie-breaker. Try 0.1.">tie</label>
-        <input type="text" ng-model="edismax.tie" name="tie" id="edismax_tie" title="Score tie-breaker. Try 0.1.">
+          <label for="tie" title="Score tie-breaker. Try 0.1.">tie</label>
+          <input type="text" ng-model="val['tie']" name="tie" id="tie" title="Score tie-breaker. Try 0.1.">
 
-        <label for="edismax_bq" title="Boost query.">bq</label>
-        <input type="text" ng-model="edismax.bq" name="bq" id="edismax_bq" title="Boost query.">
+          <label for="bq" title="Boost query.">bq</label>
+          <input type="text" ng-model="val['bq']" name="bq" id="bq" title="Boost query.">
 
-        <label for="edismax_bf" title="Boost function (added).">bf</label>
-        <input type="text" ng-model="edismax.bf" name="bf" id="edismax_bf" title="Boost function (added).">
+          <label for="bf" title="Boost function (added).">bf</label>
+          <input type="text" ng-model="val['bf']" name="bf" id="bf" title="Boost function (added).">
+          <div ng-show="val['defType']=='edismax'">
+            <label for="uf" title="User Fields">uf</label>
+            <input type="text" ng-model="val['uf']" name="uf" id="uf" title="User Fields">
 
-        <label for="edismax_uf" title="User Fields">uf</label>
-        <input type="text" ng-model="edismax.uf" name="uf" id="edismax_uf" title="User Fields">
+            <label for="pf2" title="bigram phrase boost fields">pf2</label>
+            <input type="text" ng-model="val['pf2']" name="pf2" id="pf2" title="bigram phrase boost fields">
 
-        <label for="edismax_pf2" title="bigram phrase boost fields">pf2</label>
-        <input type="text" ng-model="edismax.pf2" name="pf2" id="edismax_pf2" title="bigram phrase boost fields">
+            <label for="pf3" title="trigram phrase boost fields">pf3</label>
+            <input type="text" ng-model="val['pf3']" name="pf3" id="pf3" title="trigram phrase boost fields">
 
-        <label for="edismax_pf3" title="trigram phrase boost fields">pf3</label>
-        <input type="text" ng-model="edismax.pf3" name="pf3" id="edismax_pf3" title="trigram phrase boost fields">
+            <label for="ps2" title="phrase slop for bigram phrases">ps2</label>
+            <input type="text" ng-model="val['ps2']" name="ps2" id="ps2" title="phrase slop for bigram phrases">
 
-        <label for="edismax_ps2" title="phrase slop for bigram phrases">ps2</label>
-        <input type="text" ng-model="edismax.ps2" name="ps2" id="edismax_ps2" title="phrase slop for bigram phrases">
+            <label for="ps3" title="phrase slop for trigram phrases">ps3</label>
+            <input type="text" ng-model="val['ps3']" name="ps3" id="ps3" title="phrase slop for trigram phrases">
 
-        <label for="edismax_ps3" title="phrase slop for trigram phrases">ps3</label>
-        <input type="text" ng-model="edismax.ps3" name="ps3" id="edismax_ps3" title="phrase slop for trigram phrases">
+            <label for="boost" title="multiplicative boost function">boost</label>
+            <input type="text" ng-model="val['boost']" name="boost" id="boost" title="multiplicative boost function">
 
-        <label for="edismax_boost" title="multiplicative boost function">boost</label>
-        <input type="text" ng-model="edismax.boost" name="boost" id="edismax_boost" title="multiplicative boost function">
-
-        <label for="edismax_stopwords" class="checkbox" title="remove stopwords from mandatory 'matching' component">
-          <input type="checkbox" ng-model="edismax.stopwords" name="stopwords" id="edismax_stopwords">
-          stopwords
-        </label>
-
-        <label for="edismax_lowercaseOperators" class="checkbox" title="Enable lower-case 'and' and 'or' as operators">
-          <input type="checkbox" ng-model="edismax.lowercaseOperators" name="lowercaseOperators" id="edismax_lowercaseOperators">
-          lowercaseOperators
-        </label>
+            <label for="stopwords" class="checkbox" title="remove stopwords from mandatory 'matching' component">
+              <input type="checkbox" ng-model="val['stopwords']" name="stopwords" id="stopwords">
+              stopwords
+            </label>
 
+            <label for="lowercaseOperators" class="checkbox" title="Enable lower-case 'and' and 'or' as operators">
+              <input type="checkbox" ng-model="val['lowercaseOperators']" name="lowercaseOperators" id="lowercaseOperators">
+              lowercaseOperators
+            </label>
+          </div>
         </div>
       </fieldset>
 
       <fieldset class="hl optional">
         <legend>
           <label for="hl" class="checkbox" title="Enable highlighting.">
-            <input type="checkbox" ng-model="isHighlight" name="hl" id="hl" value="true">
+            <input type="checkbox" ng-model="val['hl']" name="hl" id="hl" value="true">
             hl
           </label>
         </legend>
-        <div class="fieldset" ng-show="isHighlight">
+        <div class="fieldset" ng-show="val['hl']">
 
         <label for="hl_fl" title="Fields to highlight on.">hl.fl</label>
-        <input type="text" ng-model="hl['hl.fl']" name="hl.fl" id="hl_fl" value="" title="Fields to highlight on.">
+        <input type="text" ng-model="val['hl.fl']" name="hl.fl" id="hl_fl" value="" title="Fields to highlight on.">
 
         <label for="hl_simple_pre">hl.simple.pre</label>
-        <input type="text" ng-model="hl['hl.simple.pre']" name="hl.simple.pre" id="hl_simple_pre" value="<em>">
+        <input type="text" ng-model="val['hl.simple.pre']" name="hl.simple.pre" id="hl_simple_pre" value="<em>">
 
         <label for="hl_simple_post">hl.simple.post</label>
-        <input type="text" ng-model="hl['hl.simple.post']"  name="hl.simple.post" id="hl_simple_post" value="</em>">
+        <input type="text" ng-model="val['hl.simple.post']"  name="hl.simple.post" id="hl_simple_post" value="</em>">
 
         <label for="hl_requireFieldMatch" class="checkbox">
-          <input type="checkbox" ng-model="hl['hl.requireFieldMatch']" name="hl.requireFieldMatch" id="hl_requireFieldMatch" value="true">
+          <input type="checkbox" ng-model="val['hl.requireFieldMatch']" name="hl.requireFieldMatch" id="hl_requireFieldMatch" value="true">
           hl.requireFieldMatch
         </label>
 
         <label for="hl_usePhraseHighlighter" class="checkbox">
-          <input type="checkbox" ng-model="hl['usePhraseHighLighter']" name="hl.usePhraseHighlighter" id="hl_usePhraseHighlighter" value="true">
+          <input type="checkbox" ng-model="val['hl.usePhraseHighLighter']" name="hl.usePhraseHighlighter" id="hl_usePhraseHighlighter" value="true">
           hl.usePhraseHighlighter
         </label>
 
         <label for="hl_highlightMultiTerm" class="checkbox">
-          <input type="checkbox" ng-model="hl['highlightMultiTerm']" name="hl.highlightMultiTerm" id="hl_highlightMultiTerm" value="true">
+          <input type="checkbox" ng-model="val['hl.highlightMultiTerm']" name="hl.highlightMultiTerm" id="hl_highlightMultiTerm" value="true">
           hl.highlightMultiTerm
         </label>
 
@@ -257,41 +225,69 @@ limitations under the License.
       <fieldset class="facet optional">
         <legend>
           <label for="facet" class="checkbox" title="Enable faceting.">
-            <input type="checkbox" ng-model="isFacet" name="facet" id="facet">
+            <input type="checkbox" ng-model="val['facet']" name="facet" id="facet">
             facet
           </label>
         </legend>
-        <div class="fieldset" ng-show="isFacet">
+        <div class="fieldset" ng-show="val['facet']">
+
+          <label for="facet_query">facet.query</label>
+          <textarea ng-model="val['facet.query']" name="facet.query" id="facet_query"></textarea>
+
+          <label for="facet_field">facet.field</label>
+          <input type="text" ng-model="val['facet.field']" name="facet.field" id="facet_field">
+
+          <label for="facet_prefix">facet.prefix</label>
+          <input type="text" ng-model="val['facet.prefix']" name="facet.prefix" id="facet_prefix">
+
+          <label for="facet_contains">facet.contains</label>
+          <input type="text" ng-model="val['facet.contains']" name="facet.contains" id="facet_contains">
+
+          <label for="facet_contains_ignoreCase" class="checkbox">
+            <input type="checkbox" ng-model="val['facet.contains.ignoreCase']" name="facet.contains.ignoreCase" id="facet_contains_ignoreCase">
+            facet.contains.ignoreCase
+          </label>
+
+          <label for="facet_limit">facet.limit</label>
+          <input type="text" ng-model="val['facet.limit']" name="facet.limit" id="facet_limit">
 
-        <label for="facet_query">facet.query</label>
-        <textarea ng-model="facet['facet.query']" name="facet.query" id="facet_query"></textarea>
+          <label for="facet_matches">facet.matches</label>
+          <input type="text" ng-model="val['facet.matches']" name="facet.matches" id="facet_matches">
 
-        <label for="facet_field">facet.field</label>
-        <input type="text" ng-model="facet['facet.field']" name="facet.field" id="facet_field">
+          <label for="facet_sort">facet.sort</label>
+          <select ng-model="val['facet.sort']" id="facet_sort" name="facet.sort" title="Ordering of the facet field constraints">
+            <option ng-selected="selected" value="">------</option>
+            <option value="count">count</option>
+            <option value="index">index</option>
+          </select>
 
-        <label for="facet_prefix">facet.prefix</label>
-        <input type="text" ng-model="facet['facet.prefix']" name="facet.prefix" id="facet_prefix">
+          <label for="facet_mincount">facet.mincount</label>
+          <input type="text" ng-model="val['facet.mincount']" name="facet.mincount" id="facet_mincount">
 
+          <label for="facet_missing" class="checkbox">
+            <input type="checkbox" ng-model="val['facet.missing']" name="facet.missing" id="facet_missing">
+            facet.missing
+          </label>
         </div>
       </fieldset>
 
       <fieldset class="spatial optional">
         <legend>
           <label for="spatial" class="checkbox" title="Show spatial options">
-            <input type="checkbox" ng-model="isSpatial" name="spatial" id="spatial">
+            <input type="checkbox" ng-model="val['spatial']" name="spatial" id="spatial">
             spatial
           </label>
         </legend>
-        <div class="fieldset" ng-show="isSpatial">
+        <div class="fieldset" ng-show="val['spatial']">
 
         <label for="pt">pt</label>
-        <input type="text" ng-model="spatial.pt" name="pt" id="pt">
+        <input type="text" ng-model="val['spatial.pt']" name="pt" id="pt">
 
         <label for="sfield">sfield</label>
-        <input type="text" ng-model="spatial.sfield" name="sfield" id="sfield">
+        <input type="text" ng-model="val['spatial.sfield']" name="sfield" id="sfield">
 
         <label for="d">d</label>
-        <input type="text" ng-model="spatial.d" name="d" id="d">
+        <input type="text" ng-model="val['spatial.d']" name="d" id="d">
 
         </div>
       </fieldset>
@@ -299,71 +295,71 @@ limitations under the License.
       <fieldset class="spellcheck optional">
         <legend>
           <label for="spellcheck" class="checkbox" title="Enable spellchecking.">
-            <input type="checkbox" ng-model="isSpellcheck" name="spellcheck" id="spellcheck">
+            <input type="checkbox" ng-model="val['spellcheck']" name="spellcheck" id="spellcheck">
             spellcheck
           </label>
         </legend>
-        <div class="fieldset" ng-show="isSpellcheck">
+        <div class="fieldset" ng-show="val['spellcheck']">
 
         <label for="spellcheck_build" class="checkbox">
-          <input type="checkbox" ng-model="spellcheck['spellcheck.build']" name="spellcheck.build" id="spellcheck_build" value="true">
+          <input type="checkbox" ng-model="val['spellcheck.build']" name="spellcheck.build" id="spellcheck_build" value="true">
           spellcheck.build
         </label>
 
         <label for="spellcheck_reload" class="checkbox">
-          <input type="checkbox" ng-model="spellcheck['spellcheck.reload']" name="spellcheck.reload" id="spellcheck_reload" value="true">
+          <input type="checkbox" ng-model="val['spellcheck.reload']" name="spellcheck.reload" id="spellcheck_reload" value="true">
           spellcheck.reload
         </label>
 
         <label for="spellcheck_q">spellcheck.q</label>
-        <input type="text" ng-model="spellcheck['spellcheck.q']" name="spellcheck.q" id="spellcheck_q">
+        <input type="text" ng-model="val['spellcheck.q']" name="spellcheck.q" id="spellcheck_q">
 
         <label for="spellcheck_dictionary">spellcheck.dictionary</label>
-        <input type="text" ng-model="spellcheck['spellcheck.dictionary']" name="spellcheck.dictionary" id="spellcheck_dictionary">
+        <input type="text" ng-model="val['spellcheck.dictionary']" name="spellcheck.dictionary" id="spellcheck_dictionary">
 
         <label for="spellcheck_count">spellcheck.count</label>
-        <input type="text" ng-model="spellcheck['spellcheck.count']" name="spellcheck.count" id="spellcheck_count">
+        <input type="text" ng-model="val['spellcheck.count']" name="spellcheck.count" id="spellcheck_count">
 
         <label for="spellcheck_onlyMorePopular" class="checkbox">
-          <input type="checkbox" ng-model="spellcheck['spellcheck.onlyMorePopular']" name="spellcheck.onlyMorePopular" id="spellcheck_onlyMorePopular" value="true">
+          <input type="checkbox" ng-model="val['spellcheck.onlyMorePopular']" name="spellcheck.onlyMorePopular" id="spellcheck_onlyMorePopular" value="true">
           spellcheck.onlyMorePopular
         </label>
 
         <label for="spellcheck_extendedResults" class="checkbox">
-          <input type="checkbox" ng-model="spellcheck['spellcheck.extendedResults']" name="spellcheck.extendedResults" id="spellcheck_extendedResults" value="true">
+          <input type="checkbox" ng-model="val['spellcheck.extendedResults']" name="spellcheck.extendedResults" id="spellcheck_extendedResults" value="true">
           spellcheck.extendedResults
         </label>
 
         <label for="spellcheck_collate" class="checkbox">
-          <input type="checkbox" ng-model="spellcheck['spellcheck.collate']" name="spellcheck.collate" id="spellcheck_collate" value="true">
+          <input type="checkbox" ng-model="val['spellcheck.collate']" name="spellcheck.collate" id="spellcheck_collate" value="true">
           spellcheck.collate
         </label>
 
         <label for="spellcheck_maxCollations">spellcheck.maxCollations</label>
-        <input type="text" ng-model="spellcheck['spellcheck.maxCollations']" name="spellcheck.maxCollations" id="spellcheck_maxCollations">
+        <input type="text" ng-model="val['spellcheck.maxCollations']" name="spellcheck.maxCollations" id="spellcheck_maxCollations">
 
         <label for="spellcheck_maxCollationTries">spellcheck.maxCollationTries</label>
-        <input type="text" ng-model="spellcheck['spellcheck.maxCollationTries']" name="spellcheck.maxCollationTries" id="spellcheck_maxCollationTries">
+        <input type="text" ng-model="val['spellcheck.maxCollationTries']" name="spellcheck.maxCollationTries" id="spellcheck_maxCollationTries">
 
         <label for="spellcheck_accuracy">spellcheck.accuracy</label>
-        <input type="text" ng-model="spellcheck['spellcheck.accuracy']" name="spellcheck.accuracy" id="spellcheck_accuracy">
+        <input type="text" ng-model="val['spellcheck.accuracy']" name="spellcheck.accuracy" id="spellcheck_accuracy">
        </div>
-
       </fieldset>
-
+      <fieldset class="additional optional">
+        <label for="custom_parameters">
+          <a rel="help">Raw Query Parameters</a>
+        </label>
+        <input type="text" ng-model="rawParams" id="custom_parameters" value="" placeholder="key1=val1&amp;key2=val2">
+      </fieldset>
 
       <button type="submit" ng-click="doQuery()">Execute Query</button>
     </form>
   </div>
 
   <div id="result">
-
-    <a ng-show="response.data" id="url" class="address-bar" ng-href="{{url}}">{{hostPortContext}}{{url}}</a>
-
-    <div id="response">
+    <div ng-show="response.data" id="response">
+      <a id="url" class="address-bar" ng-href="{{url}}">{{hostPortContext}}{{url}}</a>
       <pre class="syntax language-{{lang}}"><code ng-bind-html="response.data | highlight:lang | unsafe"></code></pre>
     </div>
-
   </div>
-
 </div>