cancel
Showing results for 
Search instead for 
Did you mean: 

Advanced search: search in location

jonash
Champ in-the-making
Champ in-the-making
In the old Alfresco Explorer interface it is possible to specify a path in the advanced search screen (Look in location) to only find files located in a specific folder or its subfolders. Is it possible to add a similar path field to the advanced search form in Share?

I tried using the confguration below but it generates an error:


<config evaluator="model-type" condition="cm:content">
      <forms>
         <form id="search">
            <field-visibility>
               <!– … (other fields here)  –>
               <show id="path"/>
            </field-visibility>
            <appearance>
               <!– … (other fields here)  –>
               <field id="path" label="Path">
                  <control template="/org/alfresco/components/form/controls/association.ftl" />
               </field>
            </appearance>
         </form>
      </forms>
   </config>

This is the error I see in the log:


ERROR [freemarker.runtime] [http-8080-36] Template processing error: "Expression field.endpointType is undefined on line 22, column 20 in org/alfresco/components/form/controls/association.ftl."

Expression field.endpointType is undefined on line 22, column 20 in org/alfresco/components/form/controls/association.ftl.
The problematic instruction:
———-
==> ${field.endpointType} [on line 22, column 18 in org/alfresco/components/form/controls/association.ftl]
in include "${field.control.template}" [on line 90, column 7 in org/alfresco/components/form/form.lib.ftl]
in user-directive renderField [on line 121, column 13 in org/alfresco/components/form/form.lib.ftl]
in user-directive formLib.renderSet [on line 23, column 16 in org/alfresco/components/form/form.get.html.ftl]
in user-directive formLib.renderFormContainer [on line 20, column 7 in org/alfresco/components/form/form.get.html.ftl]
———-
6 REPLIES 6

afaust
Legendary Innovator
Legendary Innovator
Hello,

I'm not sure that what you are trying to do will work, but in order to fix the error, you would need to implement a form filter or form field processor on the repository tier that generates the property or association field definition for the pseudo-field "Path" when a form for the model type "cm:content" is requested. Forms can only display fields whose definition is being returned by the repository when Share asks for a content types form definition. As "Path" is not a regular property or association, it is not being returned without a filter / processor that generates it.

Regards
Axel

jonash
Champ in-the-making
Champ in-the-making
Thanks for the reply!

Are there examples of form filters and field processors available somewhere? I looked at the documentation in the wiki (http://wiki.alfresco.com/wiki/Forms_Developer_Guide) but an example of adding a field would make things much easier.

A second customization that's needed is extending the search repository web script to handle the PATH field. The data from the form is converted to a search query in WEB-INF/classes/alfresco/templates/webscripts/org/alfresco/slingshot/search/search.lib.js. The getSearchResults(params) function only handles PATH queries for sites, I want to use this for searches in the repository.

Maybe it would be easier to modify the Alfresco.AdvancedSearch YUI component instead of adding a field to the advanced search form using the Forms API? This way I could just pass the PATH: term via the terms param (used for simple search) instead of the query param (used to pass the advanced search form data). Anybody any thoughts on this?

florent
Champ in-the-making
Champ in-the-making
Hi,

Did you find answers for your question because I've got the same problem.

Regards.
Florent

boumbh
Champ in-the-making
Champ in-the-making
<strong>tl;dr:</strong> I solved the problem using the <em>association.ftl</em>, a custom
FormFilter
and a few dirty tricks. I tried and put all the info in this post.

<strong>Grumbling:</strong> I'm so tired of incomplete answers… and since I spent two days on the problem, I will explain here how I got it to work. I want to get my answer as comprehensive as it can: no Java snippets where you end up searching for what classes to import / libraries to include for hours, no XML file parts that you don't know where to put. I want this answer to be useful, not a riddle. Excuse in advance for the length…

<strong>Context:</strong> Alfresco 4.2.f, project Maven from the <em>org.alfresco.maven.archetype:alfresco-amp-archetype:1.1.1 archetype</em>, I put everything in the embedded JAR when possible.

I assume there is a module on the project:

<em>src/main/resources/alfresco/site-data/extensions/my-custom-extension.xml</em>


<extension>
  <modules>
    <module>
      <id>Main module of my custom extension</id>
      <version>${project.version}</version>
      <auto-deploy>true</auto-deploy>
      <customizations>
        <customization>
          <!– Order matters here! target before source, always! –>
          <targetPackageRoot>org.alfresco</targetPackageRoot>
          <sourcePackageRoot>my-module</sourcePackageRoot>
        </customization>
      </customizations>
    </module>
  </modules>
</extension>


See this blog <a>http://blogs.alfresco.com/wp/developer/2013/09/04/customizing-the-share-header-menu-part-1/</a> for more details about creating modules.

<strong>Let's get started:</strong> First, in <em>share-config-custom.xml</em>, declare a new field in the search form.

<em>share-amp-project:src/main/resources/alfresco/web-extension/share-config-custom.xml</em>


<alfresco-config>
 
  <!– Search form
   | OVERRIDES: webapps/share/WEB-INF/classes/alfresco/share-form-config.xml
   | OVERRIDES: webapps/share/WEB-INF/classes/alfresco/team-config.xml
  –>
  <config evaluator="model-type" condition="cm:content">
    <forms>
      <!– Search form –>
      <form id="search">
        <field-visibility>
          <show id="searchInFolder" force="true" />
        </field-visibility>
        <appearance>
          <field id="searchInFolder" label="Search in">
            <control template="/org/alfresco/components/form/controls/association.ftl" />
          </field>
        </appearance>
      </form>
    </forms>
  </config>
 
</alfresco-config>


It won't work as is because the <em>association.ftl</em> template uses data from the field definition that is provided by Alfresco based on the model (definition of properties and associations in aspects and types…). In order to trick the <em>association.ftl</em> into believing that there is actually an association (called
searchInFolder
) in the content model, you could create a type or an aspect with such association (modulo the namespace), or create a form filter like I did.

<em>alfresco-amp-project:src/main/java/my/project/forms/SearchInFolderFilter.java</em>


package my.project.forms;

import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.alfresco.repo.forms.AssociationFieldDefinition;
import org.alfresco.repo.forms.Field;
import org.alfresco.repo.forms.Form;
import org.alfresco.repo.forms.FormData;
import org.alfresco.repo.forms.processor.AbstractFilter;
import org.alfresco.repo.forms.processor.node.ContentModelField;
import org.alfresco.service.cmr.repository.NodeRef;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class SearchInFolderFilter extends AbstractFilter<Object, NodeRef> {
  private static final Log logger = LogFactory.getLog(SearchInFolderFilter.class);
 
  @Override
  public void beforeGenerate(Object item, List<String> fieldStringList,
      List<String> forcedFieldStringList, Form form,
      Map<String, Object> contextMap) {
    logger.debug("beforeGenerate");
  }
 
  @Override
  public void afterGenerate(Object item, List<String> fieldStringList,
      List<String> forcedFieldStringList, Form form,
      Map<String, Object> contextMap) {
    logger.debug("afterGenerate");
    try {
      if (fieldStringList.contains("searchInFolder")) {
        AssociationFieldDefinition associationFieldDefinition = new AssociationFieldDefinition("searchInFolder", "cm:folder", AssociationFieldDefinition.Direction.TARGET);
        associationFieldDefinition.setDataKeyName("searchInFolder");
        associationFieldDefinition.setLabel("searchInFolder");
        Field field = new ContentModelField(associationFieldDefinition, Collections.EMPTY_LIST);
        form.addField(field);
      }
    } catch (Exception e) {
      logger.error("There was a problem creating the searchInFolder field", e);
    }
   
  }
 
  @Override
  public void beforePersist(Object item, FormData formData) {
    logger.debug("beforePersist");
  }
 
  @Override
  public void afterPersist(Object item, FormData formData,
      NodeRef persistedObjectNodeRef) {
    logger.debug("afterPersist");
  }
 
}


This form filter must be declared as a bean.

<em>alfresco-amp-project:src/main/amp/config/alfresco/extension/custom-form-context.xml</em>


<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>
<beans>
   <bean id="searchInFolderFilterNode"
         class="my.project.forms.SearchInFolderFilter"
         parent="baseFormFilter">
      <property name="filterRegistry" ref="nodeFilterRegistry" />
   </bean>
   <bean id="searchInFolderFilterType"
         class="my.project.forms.SearchInFolderFilter"
         parent="baseFormFilter">
      <property name="filterRegistry" ref="typeFilterRegistry" />
   </bean>
</beans>


Now the form should display the association field correctly, but the field one won't be taken into account. Obviously,
searchInFolder
is not interpreted by Alfresco in the search. In order to get it to work, I will put
searchInFolder
(which is a node reference of the folder that was selected by the user) in
rootNode
(which is the root node from where the search query is applied).

In Share, on the client side, the form data is "JSONified" and it will stay as is until the search query is run by Alfresco. I chose to handle the
searchInFolder
before the "serialization".

<em>share-amp-project:src/main/resources/META-INF/my-module/components/search/advsearch.js</em>


/**
* Advanced Search component.
*
* @namespace Alfresco
* @class Alfresco.AdvancedSearch
*
* CALLED AFTER: webapps/share/components/search/advsearch.js
*/
(function()
{
   /**
    * YUI Library aliases
    */
   var Dom = YAHOO.util.Dom;
  
   YAHOO.lang.augmentObject(Alfresco.AdvancedSearch.prototype,
   {
      /**
       * Event handler that gets fired when user clicks the Search button.
       *
       * @method onSearchClick
       * @param e {object} DomEvent
       * @param obj {object} Object passed back from addListener method
       */
      onSearchClick: function ADVSearch_onSearchClick(e, obj)
      {
         // retrieve form data structure directly from the runtime
         var formData = this.currentForm.runtime.getFormData();
        
         // add DD type to form data structure
         formData.datatype = this.currentForm.type;
        
         // Clean association.ftl data from the formData, it is useless for our purpose
         delete formData.assoc_searchInFolder_added;
         delete formData.assoc_searchInFolder_removed;
        
         // Retrieve the node ref of the selected "search in" folder, it is in
         // the hidden input of "association.ftl" that has no name. In order to
         // find it, use one of the two useless (in our case) fields:
         // "assoc_searchInFolder_added" and "assoc_searchInFolder_removed" in
         // order to deduce the id of our useful field.
         var searchInFolder_value = null;
         try {
            var searchInFolder_added_id = document.getElementsByName("assoc_searchInFolder_added")[0].id;
            var searchInFolder_id = searchInFolder_added_id.substring(0, searchInFolder_added_id.length - "-cntrl-added".length);
            searchInFolder_value = document.getElementById(searchInFolder_id).value;
         } catch (err) {
            // Something is probably undefined…
            alert("Something is probably undefined - " + err.message)
         }
        
         // build and execute url for search page
         var url = YAHOO.lang.substitute(Alfresco.constants.URL_PAGECONTEXT + "{site}search?t={terms}&q={query}&r={repo}&searchInFolder={searchInFolder}",
         {
            site: (this.options.siteId.length !== 0 ? ("site/" + this.options.siteId + "/") : ""),
            terms: encodeURIComponent(Dom.get(this.id + "-search-text").value),
            query: encodeURIComponent(YAHOO.lang.JSON.stringify(formData)),
            repo: this.options.searchRepo.toString(),
            searchInFolder : searchInFolder_value == null?"":searchInFolder_value
         });
        
         window.location.href = url;
      }
   }, true);
})();


The <em>advsearch.js</em> needs to be declared in the HTML. For that purpose, I override the corresponding ftl.

<em>share-amp-project:src/main/resources/alfresco/site-webscripts/my-module/components/search/advsearch.get.html.ftl</em>


<#– OVERRIDES: webapps/share/WEB-INF/classes/alfresco/site-webscripts/org/alfresco/components/search/advsearch.get.html.ftl
–>
<@markup id="custom-share-js"  target="js" action="after">
  <@script src="${url.context}/res/my-module/components/search/advsearch.js" group="search"/>
</@>


Finally, in Share, on the server side, we need to redefine the
rootNode
argument before calling the Alfresco search web script.

<em>share-amp-project:src/main/resources/alfresco/site-webscripts/my-module/components/search/search.get.js</em>


/**
* Search component GET method
*/

function main()
{
   var search = widgetUtils.findObject(model.widgets, "id", "Search");
   if (page.url.args["searchInFolder"] != null && page.url.args["searchInFolder"] != "") {
      search.options.searchRootNode = page.url.args["searchInFolder"];
   }
}

main();


<strong>Yet to do…</strong> There is a bad conception (in my opinion, and I really mean: it was a big mistake, in my opinion: bad) in Share advanced search: when you don't fill any field, no result is returned. It should actually return all the results since an empty field is usually interpreted as indifferent. First, I thought it was a behaviour caused by the way Lucene works, but then I realized that it was deliberately done in the search web script on Alfresco side.

In our case, the <em>Search in</em> field does not count as a property field: if you select a <em>Search in</em> folder without filling any other field, it will return 0 element. This needs to be worked around…

I updated <em>advsearch.js</em> so it will return all the results. I'm not satisfied with this workaround (it is dirty, we can see the "*" in the search field on the result page). I should update the search web script in Alfresco but it is not well customizable (would need to redefine
getSearchResults
from <em>search.lib.js</em>, a 200 lines js function inside a 1000 lines script that is imported as a resource, so you can't override it without taking the whole lot in your project… it is about choosing between two evils for working this side effect around)…

<em>share-amp-project:src/main/resources/META-INF/my-module/components/search/advsearch.js</em>


         // When "seach in" folder is selected and "key word" field is empty, we set it to "*" so it will return all the results
         var searchText = encodeURIComponent(Dom.get(this.id + "-search-text").value);
         if (searchInFolder_value != null && searchInFolder_value != "" && searchText == "") {
            searchText = "*";
         }
        
         // build and execute url for search page
         var url = YAHOO.lang.substitute(Alfresco.constants.URL_PAGECONTEXT + "{site}search?t={terms}&q={query}&r={repo}&searchInFolder={searchInFolder}",
         {
            site: (this.options.siteId.length !== 0 ? ("site/" + this.options.siteId + "/") : ""),
            terms: searchText,
            query: encodeURIComponent(YAHOO.lang.JSON.stringify(formData)),
            repo: this.options.searchRepo.toString(),
            searchInFolder : searchInFolder_value == null?"":searchInFolder_value
         });

does this work for alfresco CE 5.2?

sharifu
Confirmed Champ
Confirmed Champ