Link to search page + cleanup client-side code
diff --git a/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/datafetchers/ArticlesBySectionFetcher.java b/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/datafetchers/ArticlesBySectionFetcher.java
index a466d53..0458736 100644
--- a/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/datafetchers/ArticlesBySectionFetcher.java
+++ b/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/datafetchers/ArticlesBySectionFetcher.java
@@ -46,8 +46,6 @@
         // TODO should paginate instead
         final int maxArticles = 42;
 
-        final Resource currentResource = FetcherUtil.getSourceResource(env, section);
-
         final List<Map<String, Object>> result = new ArrayList<>();
         final Iterable<Resource> it = () -> section.getResourceResolver().getChildren(section).iterator();
         StreamSupport.stream(it.spliterator(), false)
diff --git a/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/datafetchers/ArticlesWithTextFetcher.java b/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/datafetchers/ArticlesWithTextFetcher.java
index 065e111..dea5fcb 100644
--- a/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/datafetchers/ArticlesWithTextFetcher.java
+++ b/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/datafetchers/ArticlesWithTextFetcher.java
@@ -46,8 +46,8 @@
     public Object get(DataFetchingEnvironment environment) throws Exception {
         final String expectedText = environment.getArgument(P_WITH_TEXT);
         final String jcrQuery = String.format(
-            "/jcr:root/content/articles//*[jcr:contains(@text, '%s') or jcr:contains(@title, '%s')]",
-            expectedText, expectedText);
+            "/jcr:root%s//*[jcr:contains(@text, '%s') or jcr:contains(@title, '%s')]",
+            Constants.ARTICLES_ROOT, expectedText, expectedText);
 
         final List<Map<String, Object>> result = new ArrayList<>();
         final Iterator<Resource> it = resource.getResourceResolver().findResources(jcrQuery, "xpath");
diff --git a/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/datafetchers/Constants.java b/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/datafetchers/Constants.java
new file mode 100644
index 0000000..2dafd58
--- /dev/null
+++ b/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/datafetchers/Constants.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.graphql.samples.website.datafetchers;
+
+class Constants {
+    private Constants() {
+    }
+
+    public static final String ARTICLES_ROOT = "/content/articles";
+    public static final String ARTICLE_RESOURCE_SUPERTYPE = "samples/article";
+    public static final String SEARCH_PAGE_PATH = "/content/search";
+}
\ No newline at end of file
diff --git a/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/datafetchers/NavigationDataFetcher.java b/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/datafetchers/NavigationDataFetcher.java
index 9fe2dd1..a8613f1 100644
--- a/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/datafetchers/NavigationDataFetcher.java
+++ b/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/datafetchers/NavigationDataFetcher.java
@@ -39,8 +39,6 @@
 class NavigationDataFetcher implements DataFetcher<Object> {
 
     public static final String NAME = "navigation";
-    public static final String CONTENT_ROOT = "/content/articles";
-    public static final String ARTICLE_RESOURCE_SUPERTYPE = "samples/article";
 
     private final Resource resource;
 
@@ -50,7 +48,7 @@
 
     private Object[] getSections() {
         final List<Map<String, Object>> result = new ArrayList<>();
-        final Resource root = resource.getResourceResolver().getResource(CONTENT_ROOT);
+        final Resource root = resource.getResourceResolver().getResource(Constants.ARTICLES_ROOT);
         final Iterable<Resource> it = () -> root.getResourceResolver().getChildren(root).iterator();
         StreamSupport.stream(it.spliterator(), false)
             .forEach(child -> result.add(SlingWrappers.resourceWrapper(child)));
@@ -61,7 +59,8 @@
     String getNextOrPreviousPath(Resource r, String propertyName, String currentValue, boolean isNext) {
         String result = null;
         final String jcrQuery = String.format(
-            "/jcr:root/content/articles//*[%s %s '%s'] order by %s %s",
+            "/jcr:root%s//*[%s %s '%s'] order by %s %s",
+            Constants.ARTICLES_ROOT,
             propertyName,
             isNext ? ">" : "<",
             currentValue,
@@ -78,7 +77,7 @@
     /** If r is an article, add previous/next navigation based on article filenames */
     private void maybeAddPrevNext(Map<String, Object> result, Resource r) {
         final String propName = "filename";
-        if(ARTICLE_RESOURCE_SUPERTYPE.equals(r.getResourceSuperType())) {
+        if(Constants.ARTICLE_RESOURCE_SUPERTYPE.equals(r.getResourceSuperType())) {
             final String filename = r.adaptTo(ValueMap.class).get(propName, String.class);
             if(filename != null) {
                 result.put("previous", getNextOrPreviousPath(r, propName, filename, false));
@@ -90,8 +89,9 @@
     @Override
     public Object get(DataFetchingEnvironment env) throws Exception {
         final Map<String, Object> result = new HashMap<>();
-        result.put("root", CONTENT_ROOT);
+        result.put("root", Constants.ARTICLES_ROOT);
         result.put("sections", getSections());
+        result.put("search", Constants.SEARCH_PAGE_PATH);
         maybeAddPrevNext(result, FetcherUtil.getSourceResource(env, resource));
         return result;
     }
diff --git a/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/datafetchers/SeeAlsoDataFetcher.java b/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/datafetchers/SeeAlsoDataFetcher.java
index d9ea5c7..b07c051 100644
--- a/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/datafetchers/SeeAlsoDataFetcher.java
+++ b/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/datafetchers/SeeAlsoDataFetcher.java
@@ -47,7 +47,7 @@
      *  we can use the full path + title to render links.
      */
     private static Map<String, Object> toArticleRef(ResourceResolver resolver, String nodeName) {
-        final String jcrQuery = String.format("/jcr:root/content/articles//*[@filename='%s']", nodeName);
+        final String jcrQuery = String.format("/jcr:root%s//*[@filename='%s']", Constants.ARTICLES_ROOT, nodeName);
         final Iterator<Resource> it = resolver.findResources(jcrQuery, "xpath");
 
         // We want exactly one result
diff --git a/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/datafetchers/TagQueryDataFetcher.java b/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/datafetchers/TagQueryDataFetcher.java
index 8c7d54d..34cc0fc 100644
--- a/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/datafetchers/TagQueryDataFetcher.java
+++ b/org.apache.sling.graphql.samples.website/src/main/java/org/apache/sling/graphql/samples/website/datafetchers/TagQueryDataFetcher.java
@@ -46,7 +46,7 @@
     static String jcrQuery(String ... tags) {
         // Build a query like
         //  /jcr:root/content/articles//*[@tags = "panel" and @tags = "card"]
-        final StringBuilder sb = new StringBuilder("/jcr:root/content/articles//*[");
+        final StringBuilder sb = new StringBuilder("/jcr:root" + Constants.ARTICLES_ROOT + "//*[");
         for(int i=0 ; i < tags.length; i++) {
             if(i > 0) {
                 sb.append(" and ");
diff --git a/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/apps/samples/article/article.hbs b/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/apps/samples/article/article.hbs
index e33b48e..cdbffe6 100644
--- a/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/apps/samples/article/article.hbs
+++ b/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/apps/samples/article/article.hbs
@@ -38,6 +38,7 @@
             {{#if data.navigation.next}}
             <a href="{{data.navigation.next}}.html">Next article</a>
             {{/if}}
+            <a href="{{data.navigation.search}}.html">Search</a>
             <hr/>
         </div>
         
diff --git a/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/apps/samples/article/json.gql b/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/apps/samples/article/json.gql
index 8531cbd..58d0729 100644
--- a/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/apps/samples/article/json.gql
+++ b/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/apps/samples/article/json.gql
@@ -21,6 +21,7 @@
     root
     previous
     next
+    search
     sections {
       path
       name
diff --git a/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/apps/samples/common/GQLschema.jsp b/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/apps/samples/common/GQLschema.jsp
index abfeb49..512ef90 100644
--- a/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/apps/samples/common/GQLschema.jsp
+++ b/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/apps/samples/common/GQLschema.jsp
@@ -34,6 +34,8 @@
   previous: String
   # Next article, if application
   next: String
+  # Search page
+  search: String
 }
 
 # A content section with its name and path
diff --git a/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/apps/samples/section/json.gql b/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/apps/samples/section/json.gql
index 81c5e13..7c4ff63 100644
--- a/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/apps/samples/section/json.gql
+++ b/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/apps/samples/section/json.gql
@@ -19,6 +19,7 @@
 { 
   navigation {
     root
+    search
     sections {
       path
       name
diff --git a/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/apps/samples/section/section.hbs b/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/apps/samples/section/section.hbs
index 9c3199f..e28eeed 100644
--- a/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/apps/samples/section/section.hbs
+++ b/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/apps/samples/section/section.hbs
@@ -31,6 +31,8 @@
                 <a href="{{this.path}}.html">{{this.name}}</a>
               </span>
             {{/each}}
+            <br/>
+            <a href="{{data.navigation.search}}.html">Search</a>
             <hr/>
         </div>
         <div class="title">
diff --git a/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/apps/samples/tag/json.gql b/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/apps/samples/tag/json.gql
index 383d8a0..8e88e34 100644
--- a/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/apps/samples/tag/json.gql
+++ b/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/apps/samples/tag/json.gql
@@ -19,6 +19,7 @@
 { 
   navigation {
     root
+    search
     sections {
       path
       name
diff --git a/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/apps/samples/tag/tag.hbs b/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/apps/samples/tag/tag.hbs
index 7c04a0b..8d542ba 100644
--- a/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/apps/samples/tag/tag.hbs
+++ b/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/apps/samples/tag/tag.hbs
@@ -31,6 +31,8 @@
               <a href="{{this.path}}.html">{{this.name}}</a>
             </span>
           {{/each}}
+          <br/>
+          <a href="{{data.navigation.search}}.html">Search</a>
           <hr/>
       </div>
       
diff --git a/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/content/js/graphql.js b/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/content/js/graphql.js
new file mode 100644
index 0000000..31c66da
--- /dev/null
+++ b/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/content/js/graphql.js
@@ -0,0 +1,39 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+~ Licensed to the Apache Software Foundation (ASF) under one
+~ or more contributor license agreements.  See the NOTICE file
+~ distributed with this work for additional information
+~ regarding copyright ownership.  The ASF licenses this file
+~ to you under the Apache License, Version 2.0 (the
+~ "License"); you may not use this file except in compliance
+~ with the License.  You may obtain a copy of the License at
+~
+~   http://www.apache.org/licenses/LICENSE-2.0
+~
+~ Unless required by applicable law or agreed to in writing,
+~ software distributed under the License is distributed on an
+~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+~ KIND, either express or implied.  See the License for the
+~ specific language governing permissions and limitations
+~ under the License.
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+
+/** Minimal GraphQL client for our sample website */
+var graphQL = null;
+
+(function() {
+    graphQL = {
+        query : function(query, info, callback) {
+            fetch('/graphql.json', {
+                method: 'POST',
+                headers: {
+                'Content-Type': 'application/json',
+                'Accept': 'application/json',
+                },
+                body: JSON.stringify({query: `${query} `})
+            })
+            .then(r => r.json())
+            .then(json => callback ({ result: json.data, query: query, info: info}))
+            ;
+        }
+    }
+})();
\ No newline at end of file
diff --git a/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/content/search.html b/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/content/search.html
index 215d025..b97d7d3 100644
--- a/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/content/search.html
+++ b/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/content/search.html
@@ -33,25 +33,59 @@
         </script>
 
         <script
-            src="https://cdn.jsdelivr.net/npm/handlebars@latest/dist/handlebars.js">
+            src="https://cdn.jsdelivr.net/npm/handlebars@4.7.6/dist/handlebars.js"
+            integrity="sha256-ZafrO8ZXERYO794Tx1hPaAcdcXNZUNmXufXOSe0Hxj8="
+            crossorigin="anonymous">
         </script>
 
-        <script src="./search.js"></script>
+        <script src="./js/graphql.js"></script>
+
+        <script>
+            var template;
+
+            function queryAndRender(searchText) {
+                var query = `{
+                    article(withText: "${searchText}") {
+                        path
+                        title
+                        seeAlso {
+                        path
+                        title
+                        tags
+                        }
+                    }
+                }`;
+                console.log(`Querying:\n${query}`);
+                graphQL.query(query, { searchText: searchText}, function(data) {
+                    $("#results").html(template({data:data}));
+                });
+            }
+
+            $(document).ready(function() {
+                template = Handlebars.compile($("#template").html());
+                $("#search").submit(function() {
+                    queryAndRender($("#searchText").val());
+                    return false;
+                });
+            });
+        </script>
     </head>
     <body>
         <h1>Search in articles</h1>
         <hr/>
+
         <form id="search">
             <input id="searchText" type="text" width="40"/>
             <input type="submit" value="Search"/>
         </form>
+
         <div id="results"/>
 
         <div id="template" style="display:none">
-            <h2>Articles containing "{{data.query}}"</h2>
+            <h2>Found {{data.result.article.length}} articles containing "{{data.info.searchText}}"</h2>
             <ul>
             {{#each data.result.article}}
-                <li class="seeAlso">
+                <li class="articleLink">
                     <a href="{{this.path}}.html">{{this.title}}</a>
                 </li>
             {{/each}}
diff --git a/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/content/search.js b/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/content/search.js
deleted file mode 100644
index b3dccaa..0000000
--- a/org.apache.sling.graphql.samples.website/src/main/resources/SLING-INF/initial-content/content/search.js
+++ /dev/null
@@ -1,63 +0,0 @@
-/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-~ Licensed to the Apache Software Foundation (ASF) under one
-~ or more contributor license agreements.  See the NOTICE file
-~ distributed with this work for additional information
-~ regarding copyright ownership.  The ASF licenses this file
-~ to you under the Apache License, Version 2.0 (the
-~ "License"); you may not use this file except in compliance
-~ with the License.  You may obtain a copy of the License at
-~
-~   http://www.apache.org/licenses/LICENSE-2.0
-~
-~ Unless required by applicable law or agreed to in writing,
-~ software distributed under the License is distributed on an
-~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-~ KIND, either express or implied.  See the License for the
-~ specific language governing permissions and limitations
-~ under the License.
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
-
-var template;
-
-function render(resultElement, data) {
-    console.log(`Rendering ${data.result.article.length} articles`);
-    $(resultElement).html(template({ data : data }));
-}
-
-function queryAndRender(queryText) {
-    var query = `{
-        article(withText: "${queryText}") {
-            path
-            title
-            seeAlso {
-            path
-            title
-            tags
-            }
-        }
-        }`;
-
-    console.log(`Querying:\n${query}`);
-
-    fetch('/graphql.json', {
-        method: 'POST',
-        headers: {
-        'Content-Type': 'application/json',
-        'Accept': 'application/json',
-        },
-        body: JSON.stringify({query: `${query} `})
-    })
-    .then(r => r.json())
-    .then(json => render($("#results"), { result: json.data, query: queryText}))
-    ;
-}
-
-$(document).ready(function() {
-    template = Handlebars.compile($("#template").html());
-    $("#search").submit(function(e) {
-        var searchText = $("#searchText").val();
-        queryAndRender(searchText);
-        return false;
-    });
-});
-