KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > alfresco > web > ui > repo > component > property > BaseAssociationEditor


1 /*
2  * Copyright (C) 2005 Alfresco, Inc.
3  *
4  * Licensed under the GNU Lesser General Public License as
5  * published by the Free Software Foundation; either version
6  * 2.1 of the License, or (at your option) any later version.
7  * You may obtain a copy of the License at
8  *
9  * http://www.gnu.org/licenses/lgpl.txt
10  *
11  * Unless required by applicable law or agreed to in writing,
12  * software distributed under the License is distributed on an
13  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
14  * either express or implied. See the License for the specific
15  * language governing permissions and limitations under the
16  * License.
17  */

18 package org.alfresco.web.ui.repo.component.property;
19
20 import java.io.IOException JavaDoc;
21 import java.text.MessageFormat JavaDoc;
22 import java.util.ArrayList JavaDoc;
23 import java.util.List JavaDoc;
24 import java.util.Map JavaDoc;
25
26 import javax.faces.component.UIComponent;
27 import javax.faces.component.UIInput;
28 import javax.faces.context.FacesContext;
29 import javax.faces.context.ResponseWriter;
30 import javax.faces.el.ValueBinding;
31 import javax.faces.event.AbortProcessingException;
32 import javax.faces.event.ActionEvent;
33 import javax.faces.event.FacesEvent;
34
35 import org.alfresco.service.cmr.dictionary.AssociationDefinition;
36 import org.alfresco.service.cmr.repository.NodeRef;
37 import org.alfresco.service.cmr.repository.NodeService;
38 import org.alfresco.service.cmr.search.ResultSet;
39 import org.alfresco.service.cmr.search.SearchService;
40 import org.alfresco.service.namespace.NamespaceService;
41 import org.alfresco.service.namespace.QName;
42 import org.alfresco.web.app.Application;
43 import org.alfresco.web.bean.repository.DataDictionary;
44 import org.alfresco.web.bean.repository.Node;
45 import org.alfresco.web.bean.repository.Repository;
46 import org.alfresco.web.ui.common.Utils;
47 import org.apache.commons.logging.Log;
48 import org.apache.commons.logging.LogFactory;
49 import org.springframework.web.jsf.FacesContextUtils;
50
51 /**
52  * Base class for all association editor components
53  *
54  * @author gavinc
55  */

56 public abstract class BaseAssociationEditor extends UIInput
57 {
58    private final static Log logger = LogFactory.getLog(BaseAssociationEditor.class);
59    
60    private final static String JavaDoc ACTION_SEPARATOR = ";";
61    private final static int ACTION_NONE = -1;
62    private final static int ACTION_REMOVE = 0;
63    private final static int ACTION_SELECT = 1;
64    private final static int ACTION_ADD = 2;
65    private final static int ACTION_CHANGE = 3;
66    private final static int ACTION_CANCEL = 4;
67    private final static int ACTION_SEARCH = 5;
68    private final static int ACTION_SET = 6;
69    
70    private final static String JavaDoc MSG_ERROR_ASSOC = "error_association";
71    private final static String JavaDoc FIELD_CONTAINS = "_contains";
72    private final static String JavaDoc FIELD_AVAILABLE = "_available";
73    
74    /** I18N message strings */
75    private final static String JavaDoc MSG_ADD_TO_LIST_BUTTON = "add_to_list_button";
76    private final static String JavaDoc MSG_SELECT_BUTTON = "select_button";
77    private static final String JavaDoc MSG_NO_SELECTED_ITEMS = "no_selected_items";
78    private final static String JavaDoc MSG_SEARCH_SELECT_ITEMS = "search_select_items";
79    private final static String JavaDoc MSG_SEARCH_SELECT_ITEM = "search_select_item";
80    private final static String JavaDoc MSG_SELECTED_ITEMS = "selected_items";
81    private final static String JavaDoc MSG_REMOVE = "remove";
82    private final static String JavaDoc MSG_ADD = "add";
83    private final static String JavaDoc MSG_OK = "ok";
84    private final static String JavaDoc MSG_CANCEL = "cancel";
85    private final static String JavaDoc MSG_SEARCH = "search";
86    private final static String JavaDoc MSG_CHANGE = "change";
87    
88    protected String JavaDoc associationName;
89    protected String JavaDoc availableOptionsSize;
90    protected String JavaDoc selectItemMsg;
91    protected String JavaDoc selectItemsMsg;
92    protected String JavaDoc selectedItemsMsg;
93    protected String JavaDoc noSelectedItemsMsg;
94    protected Boolean JavaDoc disabled;
95    
96    protected boolean showAvailable = false;
97    
98    /** Map of the original associations keyed by the id of the child */
99    protected Map JavaDoc<String JavaDoc, Object JavaDoc> originalAssocs;
100    protected Map JavaDoc<String JavaDoc, Object JavaDoc> added;
101    protected Map JavaDoc<String JavaDoc, Object JavaDoc> removed;
102    
103    /** List containing the currently available options */
104    protected List JavaDoc<NodeRef> availableOptions;
105    
106    protected String JavaDoc changingAssociation;
107    protected boolean highlightedRow;
108    
109    // ------------------------------------------------------------------------------
110
// Component implementation
111

112    /**
113     * Default constructor
114     */

115    public BaseAssociationEditor()
116    {
117       setRendererType(null);
118    }
119    
120    /**
121     * @see javax.faces.component.StateHolder#restoreState(javax.faces.context.FacesContext, java.lang.Object)
122     */

123    @SuppressWarnings JavaDoc("unchecked")
124    public void restoreState(FacesContext context, Object JavaDoc state)
125    {
126       Object JavaDoc values[] = (Object JavaDoc[])state;
127       // standard component attributes are restored by the super class
128
super.restoreState(context, values[0]);
129       this.associationName = (String JavaDoc)values[1];
130       this.originalAssocs = (Map JavaDoc<String JavaDoc, Object JavaDoc>)values[2];
131       this.availableOptions = (List JavaDoc<NodeRef>)values[3];
132       this.availableOptionsSize = (String JavaDoc)values[4];
133       this.selectItemMsg = (String JavaDoc)values[5];
134       this.selectItemsMsg = (String JavaDoc)values[6];
135       this.selectedItemsMsg = (String JavaDoc)values[7];
136       this.changingAssociation = (String JavaDoc)values[8];
137       this.disabled = (Boolean JavaDoc)values[9];
138    }
139    
140    /**
141     * @see javax.faces.component.StateHolder#saveState(javax.faces.context.FacesContext)
142     */

143    public Object JavaDoc saveState(FacesContext context)
144    {
145       Object JavaDoc values[] = new Object JavaDoc[10];
146       // standard component attributes are saved by the super class
147
values[0] = super.saveState(context);
148       values[1] = this.associationName;
149       values[2] = this.originalAssocs;
150       values[3] = this.availableOptions;
151       values[4] = this.availableOptionsSize;
152       values[5] = this.selectItemMsg;
153       values[6] = this.selectItemsMsg;
154       values[7] = this.selectedItemsMsg;
155       values[8] = this.changingAssociation;
156       values[9] = this.disabled;
157       
158       // NOTE: we don't save the state of the added and removed maps as these
159
// need to be rebuilt everytime
160

161       return (values);
162    }
163
164    /**
165     * @see javax.faces.component.UIComponent#decode(javax.faces.context.FacesContext)
166     */

167    public void decode(FacesContext context)
168    {
169       Map JavaDoc requestMap = context.getExternalContext().getRequestParameterMap();
170       Map JavaDoc valuesMap = context.getExternalContext().getRequestParameterValuesMap();
171       String JavaDoc fieldId = getHiddenFieldName();
172       String JavaDoc value = (String JavaDoc)requestMap.get(fieldId);
173       
174       int action = ACTION_NONE;
175       String JavaDoc removeId = null;
176       if (value != null && value.length() != 0)
177       {
178          // break up the action into it's parts
179
int sepIdx = value.indexOf(ACTION_SEPARATOR);
180          if (sepIdx != -1)
181          {
182             action = Integer.parseInt(value.substring(0, sepIdx));
183             removeId = value.substring(sepIdx+1);
184          }
185          else
186          {
187             action = Integer.parseInt(value);
188          }
189       }
190       
191       // gather the current state and queue an event
192
String JavaDoc[] addedItems = (String JavaDoc[])valuesMap.get(fieldId + FIELD_AVAILABLE);
193       String JavaDoc contains = (String JavaDoc)requestMap.get(fieldId + FIELD_CONTAINS);
194       
195       AssocEditorEvent event = new AssocEditorEvent(this, action, addedItems, removeId, contains);
196       queueEvent(event);
197       
198       super.decode(context);
199    }
200
201    /**
202     * @see javax.faces.component.UIComponent#broadcast(javax.faces.event.FacesEvent)
203     */

204    public void broadcast(FacesEvent event) throws AbortProcessingException
205    {
206       if (event instanceof AssocEditorEvent)
207       {
208          AssocEditorEvent assocEvent = (AssocEditorEvent)event;
209          Node node = (Node)getValue();
210          
211          switch (assocEvent.Action)
212          {
213             case ACTION_SEARCH:
214             {
215                this.showAvailable = true;
216                this.availableOptions = new ArrayList JavaDoc<NodeRef>();
217                getAvailableOptions(FacesContext.getCurrentInstance(), assocEvent.Contains);
218                break;
219             }
220             case ACTION_SELECT:
221             {
222                this.showAvailable = true;
223                break;
224             }
225             case ACTION_ADD:
226             {
227                addTarget(node, assocEvent.ToAdd);
228                break;
229             }
230             case ACTION_REMOVE:
231             {
232                removeTarget(node, assocEvent.RemoveId);
233                break;
234             }
235             case ACTION_CHANGE:
236             {
237                this.changingAssociation = assocEvent.RemoveId;
238                this.showAvailable = true;
239                break;
240             }
241             case ACTION_CANCEL:
242             {
243                this.showAvailable = false;
244                break;
245             }
246             case ACTION_SET:
247             {
248                if (assocEvent.ToAdd != null && assocEvent.ToAdd.length > 0)
249                {
250                   removeTarget(node, this.changingAssociation);
251                   addTarget(node, assocEvent.ToAdd);
252                }
253                break;
254             }
255          }
256       }
257       else
258       {
259          super.broadcast(event);
260       }
261    }
262    
263    /**
264     * @see javax.faces.component.UIComponent#encodeBegin(javax.faces.context.FacesContext)
265     */

266    public void encodeBegin(FacesContext context) throws IOException JavaDoc
267    {
268       if (isRendered() == false)
269       {
270          return;
271       }
272       
273       // reset the highlighted row flag
274
this.highlightedRow = false;
275       
276       ResponseWriter out = context.getResponseWriter();
277       String JavaDoc clientId = getClientId(context);
278
279       // get the child associations currently on the node and any that have been added
280
NodeService nodeService = Repository.getServiceRegistry(context).getNodeService();
281       
282       // show the editable association component
283
AssociationDefinition assocDef = getAssociationDefinition(context);
284       if (assocDef == null)
285       {
286          logger.warn("Failed to find association definition for association '" + associationName + "'");
287          
288          // add an error message as the property is not defined in the data dictionary
289
String JavaDoc msg = MessageFormat.format(Application.getMessage(context, MSG_ERROR_ASSOC), new Object JavaDoc[] {this.associationName});
290          Utils.addErrorMessage(msg);
291       }
292       else
293       {
294          String JavaDoc targetType = assocDef.getTargetClass().getName().toString();
295          boolean allowMany = assocDef.isTargetMany();
296          
297          populateAssocationMaps((Node)getValue());
298          
299          if (isDisabled())
300          {
301             // show the current list of associations in a read-only form
302
renderReadOnlyAssociations(context, out, nodeService);
303          }
304          else
305          {
306             // start outer table
307
out.write("<table border='0' cellspacing='4' cellpadding='0' class='selector'>");
308             
309             if (allowMany)
310             {
311                out.write("<tr><td colspan='2'>1.&nbsp;");
312                out.write(getSelectItemsMsg());
313                out.write("</td></tr>");
314                
315                // show the search field
316
renderSearchField(context, out);
317                
318                // show available options for this association
319
renderAvailableOptions(context, out, nodeService, targetType, allowMany);
320                
321                // add the Add to List button
322
out.write("<tr><td colspan='2'>2.&nbsp;<input type='submit' value='");
323                out.write(Application.getMessage(context, MSG_ADD_TO_LIST_BUTTON));
324                out.write("' onclick=\"");
325                out.write(generateFormSubmit(context, Integer.toString(ACTION_ADD)));
326                out.write("\"/>");
327                
328                // add some padding
329
out.write("<tr><td height='6'></td></tr>");
330                
331                out.write("<tr><td colspan='2'>");
332                out.write(getSelectedItemsMsg());
333                out.write("</td></tr>");
334                
335                // show all the current associations
336
out.write("<tr><td colspan='2'><table cellspacing='0' cellpadding='2' border='0' class='selectedItems'>");
337                out.write("<tr><td colspan='2' class='selectedItemsHeader'>");
338                out.write(Application.getMessage(context, "name"));
339                out.write("</td></tr>");
340                renderExistingAssociations(context, out, nodeService, allowMany);
341                out.write("</table></td></tr>");
342             }
343             else
344             {
345                if (this.showAvailable)
346                {
347                   out.write("<tr><td colspan='2'>1.&nbsp;");
348                   out.write(getSelectItemMsg());
349                   out.write("</td></tr>");
350                
351                   // show the search field
352
renderSearchField(context, out);
353                   
354                   // show available options for this association
355
renderAvailableOptions(context, out, nodeService, targetType, allowMany);
356                   
357                   // add the ok and cancel buttons
358
out.write("<tr><td colspan='2' align='right'><input type='submit' value='");
359                   out.write(Application.getMessage(context, MSG_OK));
360                   out.write("' onclick=\"");
361                   out.write(generateFormSubmit(context, Integer.toString(ACTION_SET)));
362                   out.write("\"/>&nbsp;&nbsp;<input type='submit' value='");
363                   out.write(Application.getMessage(context, MSG_CANCEL));
364                   out.write("' onclick=\"");
365                   out.write(generateFormSubmit(context, Integer.toString(ACTION_CANCEL)));
366                   out.write("\"/></td></tr>");
367                }
368                else
369                {
370                   // show the select button if required
371
if ((allowMany == false && this.originalAssocs.size() == 0 && this.added.size() == 0) ||
372                       (allowMany == false && this.originalAssocs.size() == 1 && this.removed.size() == 1 && this.added.size() == 0) )
373                   {
374                      out.write("<tr><td><input type='submit' value='");
375                      out.write(Application.getMessage(context, MSG_SELECT_BUTTON));
376                      out.write("' onclick=\"");
377                      out.write(generateFormSubmit(context, Integer.toString(ACTION_SELECT)));
378                      out.write("\"/></td></tr>");
379                   }
380                   else
381                   {
382                      // show the current association
383
renderExistingAssociations(context, out, nodeService, allowMany);
384                   }
385                }
386             }
387             
388             if (logger.isDebugEnabled())
389             {
390                logger.debug("number original = " + this.originalAssocs.size());
391                logger.debug("number added = " + this.added.size());
392                logger.debug("number removed = " + this.removed.size());
393             }
394             
395             // close table
396
out.write("</table>");
397          }
398       }
399    }
400    
401    /**
402     * Returns the name of the association this component is editing
403     *
404     * @return Association name
405     */

406    public String JavaDoc getAssociationName()
407    {
408       ValueBinding vb = getValueBinding("associationName");
409       if (vb != null)
410       {
411          this.associationName = (String JavaDoc)vb.getValue(getFacesContext());
412       }
413       
414       return this.associationName;
415    }
416
417    /**
418     * Sets the name of the association this component will edit
419     *
420     * @param associationName Name of the association to edit
421     */

422    public void setAssociationName(String JavaDoc associationName)
423    {
424       this.associationName = associationName;
425    }
426    
427    /**
428     * Determines whether the component should be rendered in a disabled state
429     *
430     * @return Returns whether the component is disabled
431     */

432    public boolean isDisabled()
433    {
434       if (this.disabled == null)
435       {
436          ValueBinding vb = getValueBinding("disabled");
437          if (vb != null)
438          {
439             this.disabled = (Boolean JavaDoc)vb.getValue(getFacesContext());
440          }
441       }
442       
443       if (this.disabled == null)
444       {
445          this.disabled = Boolean.FALSE;
446       }
447       
448       return this.disabled;
449    }
450
451    /**
452     * Determines whether the component should be rendered in a disabled state
453     *
454     * @param disabled true to disable the component
455     */

456    public void setDisabled(boolean disabled)
457    {
458       this.disabled = disabled;
459    }
460    
461    /**
462     * Returns the size of the select control when multiple items
463     * can be selected
464     *
465     * @return The size of the select control
466     */

467    public String JavaDoc getAvailableOptionsSize()
468    {
469       if (this.availableOptionsSize == null)
470       {
471          this.availableOptionsSize = "4";
472       }
473       
474       return this.availableOptionsSize;
475    }
476    
477    /**
478     * Sets the size of the select control used when multiple items can
479     * be selected
480     *
481     * @param availableOptionsSize The size
482     */

483    public void setAvailableOptionsSize(String JavaDoc availableOptionsSize)
484    {
485       this.availableOptionsSize = availableOptionsSize;
486    }
487    
488    /**
489     * Returns the message to display when no items have been selected, if one hasn't been
490     * set it defaults to the message in the bundle under key 'no_selected_items'.
491     *
492     * @return The message
493     */

494    public String JavaDoc getNoSelectedItemsMsg()
495    {
496       ValueBinding vb = getValueBinding("noSelectedItemsMsg");
497       if (vb != null)
498       {
499          this.noSelectedItemsMsg = (String JavaDoc)vb.getValue(getFacesContext());
500       }
501       
502       if (this.noSelectedItemsMsg == null)
503       {
504          this.noSelectedItemsMsg = Application.getMessage(getFacesContext(), MSG_NO_SELECTED_ITEMS);
505       }
506       
507       return this.noSelectedItemsMsg;
508    }
509
510    /**
511     * Sets the no selected items message to display in the UI
512     *
513     * @param noSelectedItemsMsg The message
514     */

515    public void setNoSelectedItemsMsg(String JavaDoc noSelectedItemsMsg)
516    {
517       this.noSelectedItemsMsg = noSelectedItemsMsg;
518    }
519    
520    /**
521     * Returns the message to display for the selected items, if one hasn't been
522     * set it defaults to the message in the bundle under key 'selected_items'.
523     *
524     * @return The message
525     */

526    public String JavaDoc getSelectedItemsMsg()
527    {
528       ValueBinding vb = getValueBinding("selectedItemsMsg");
529       if (vb != null)
530       {
531          this.selectedItemsMsg = (String JavaDoc)vb.getValue(getFacesContext());
532       }
533       
534       if (this.selectedItemsMsg == null)
535       {
536          this.selectedItemsMsg = Application.getMessage(getFacesContext(), MSG_SELECTED_ITEMS);
537       }
538       
539       return this.selectedItemsMsg;
540    }
541
542    /**
543     * Sets the selected items message to display in the UI
544     *
545     * @param selectedItemsMsg The message
546     */

547    public void setSelectedItemsMsg(String JavaDoc selectedItemsMsg)
548    {
549       this.selectedItemsMsg = selectedItemsMsg;
550    }
551    
552    /**
553     * Returns the message to display for select an item, if one hasn't been
554     * set it defaults to the message in the bundle under key 'search_select_item'.
555     *
556     * @return The message
557     */

558    public String JavaDoc getSelectItemMsg()
559    {
560       ValueBinding vb = getValueBinding("selectItemMsg");
561       if (vb != null)
562       {
563          this.selectItemMsg = (String JavaDoc)vb.getValue(getFacesContext());
564       }
565       
566       if (this.selectItemMsg == null)
567       {
568          this.selectItemMsg = Application.getMessage(getFacesContext(), MSG_SEARCH_SELECT_ITEM);
569       }
570       
571       return this.selectItemMsg;
572    }
573
574    /**
575     * Sets the select an item message to display in the UI
576     *
577     * @param selectedItemMsg The message
578     */

579    public void setSelectItemMsg(String JavaDoc selectItemMsg)
580    {
581       this.selectItemMsg = selectItemMsg;
582    }
583    
584    /**
585     * Returns the message to display for select items, if one hasn't been
586     * set it defaults to the message in the bundle under key 'search_select_items'.
587     *
588     * @return The message
589     */

590    public String JavaDoc getSelectItemsMsg()
591    {
592       ValueBinding vb = getValueBinding("selectItemsMsg");
593       if (vb != null)
594       {
595          this.selectItemsMsg = (String JavaDoc)vb.getValue(getFacesContext());
596       }
597       
598       if (this.selectItemsMsg == null)
599       {
600          this.selectItemsMsg = Application.getMessage(getFacesContext(), MSG_SEARCH_SELECT_ITEMS);
601       }
602       
603       return this.selectItemsMsg;
604    }
605
606    /**
607     * Sets the select items message to display in the UI
608     *
609     * @param selectedItemsMsg The message
610     */

611    public void setSelectItemsMsg(String JavaDoc selectItemsMsg)
612    {
613       this.selectItemsMsg = selectItemsMsg;
614    }
615    
616    /**
617     * Populates all the internal Maps with the appropriate association reference objects
618     *
619     * @param node The Node we are dealing with
620     */

621    protected abstract void populateAssocationMaps(Node node);
622    
623    /**
624     * Renders the existing associations in a read-only form
625     *
626     * @param context FacesContext
627     * @param out ResponseWriter
628     * @param nodeService The NodeService
629     * @throws IOException
630     */

631    protected abstract void renderReadOnlyAssociations(FacesContext context, ResponseWriter out,
632          NodeService nodeService) throws IOException JavaDoc;
633    
634    /**
635     * Renders the existing associations in an editable form
636     *
637     * @param context FacesContext
638     * @param out ResponseWriter
639     * @param nodeService The NodeService
640     * @param allowMany Whether multiple associations are allowed
641     * @throws IOException
642     */

643    protected abstract void renderExistingAssociations(FacesContext context, ResponseWriter out,
644          NodeService nodeService, boolean allowMany) throws IOException JavaDoc;
645    
646    /**
647     * Updates the component and node state to reflect an association being removed
648     *
649     * @param node The node we are dealing with
650     * @param targetId The id of the child to remove
651     */

652    protected abstract void removeTarget(Node node, String JavaDoc targetId);
653
654    /**
655     * Updates the component and node state to reflect an association being added
656     *
657     * @param node The node we are dealing with
658     * @param childId The id of the child to add
659     */

660    protected abstract void addTarget(Node node, String JavaDoc[] toAdd);
661    
662    /**
663     * Renders an existing association with the appropriate options
664     *
665     * @param context FacesContext
666     * @param out Writer to write output to
667     * @param nodeService The NodeService
668     * @param targetRef The node at the end of the association being rendered
669     * @param allowMany Whether the current association allows multiple children
670     * @throws IOException
671     */

672    protected void renderExistingAssociation(FacesContext context, ResponseWriter out, NodeService nodeService,
673          NodeRef targetRef, boolean allowMany) throws IOException JavaDoc
674    {
675       out.write("<tr><td class='");
676       if (this.highlightedRow)
677       {
678          out.write("selectedItemsRowAlt");
679       }
680       else
681       {
682          out.write("selectedItemsRow");
683       }
684       out.write("'>");
685
686       out.write(Repository.getDisplayPath(nodeService.getPath(targetRef)));
687       out.write("/");
688       out.write(Repository.getNameForNode(nodeService, targetRef));
689       out.write("</td><td class='");
690       if (this.highlightedRow)
691       {
692          out.write("selectedItemsRowAlt");
693       }
694       else
695       {
696          out.write("selectedItemsRow");
697       }
698       out.write("'><a HREF='#' title='");
699       out.write(Application.getMessage(context, MSG_REMOVE));
700       out.write("' onclick=\"");
701       out.write(generateFormSubmit(context, ACTION_REMOVE + ACTION_SEPARATOR + targetRef.getId()));
702       out.write("\"><img SRC='");
703       out.write(context.getExternalContext().getRequestContextPath());
704       out.write("/images/icons/delete.gif' border='0' width='13' height='16'/></a>");
705       
706       if (allowMany == false)
707       {
708          out.write("&nbsp;<a HREF='#' title='");
709          out.write(Application.getMessage(context, MSG_CHANGE));
710          out.write("' onclick=\"");
711          out.write(generateFormSubmit(context, ACTION_CHANGE + ACTION_SEPARATOR + targetRef.getId()));
712          out.write("\"><img SRC='");
713          out.write(context.getExternalContext().getRequestContextPath());
714          out.write("/images/icons/edit_icon.gif' border='0' width='12' height='16'/></a>");
715       }
716       
717       out.write("</td></tr>");
718       
719       this.highlightedRow = !this.highlightedRow;
720    }
721    
722    /**
723     * Renders the search fields
724     *
725     * @param context Faces Context
726     * @param out The Response Writer
727     * @throws IOException
728     */

729    protected void renderSearchField(FacesContext context, ResponseWriter out) throws IOException JavaDoc
730    {
731       // TODO: externalise the max and size attributes
732
out.write("<tr><td colspan='2'><input type='text' maxlength='1024' size='32' name='");
733       out.write(getClientId(context) + FIELD_CONTAINS);
734       out.write("'/>&nbsp;&nbsp;<input type='submit' value='");
735       out.write(Application.getMessage(context, MSG_SEARCH));
736       out.write("' onclick=\"");
737       out.write(generateFormSubmit(context, Integer.toString(ACTION_SEARCH)));
738       out.write("\"/></td></tr>");
739    }
740    
741    /**
742     * Renders the <None> message
743     *
744     * @param context Faces Context
745     * @param out Response Writer
746     * @throws IOException
747     */

748    protected void renderNone(FacesContext context, ResponseWriter out) throws IOException JavaDoc
749    {
750       out.write("<tr><td class='selectedItemsRow'>");
751       out.write(getNoSelectedItemsMsg());
752       out.write("</td></tr>");
753    }
754    
755    /**
756     * Renders the list of available options for a new association
757     *
758     * @param context FacesContext
759     * @param out Writer to write output to
760     * @param nodeService The NodeService
761     * @param targetType The type of the child at the end of the association
762     * @param allowMany Whether the current association allows multiple children
763     * @throws IOException
764     */

765    protected void renderAvailableOptions(FacesContext context, ResponseWriter out, NodeService nodeService,
766          String JavaDoc targetType, boolean allowMany) throws IOException JavaDoc
767    {
768       boolean itemsPresent = (this.availableOptions != null && this.availableOptions.size() > 0);
769       
770       out.write("<tr><td colspan='2'><select ");
771       if (itemsPresent == false)
772       {
773          // rather than having a very slim select box set the width if there are no results
774
out.write("style='width:240px;' ");
775       }
776       out.write("name='");
777       out.write(getClientId(context) + FIELD_AVAILABLE);
778       out.write("' size='");
779       if (allowMany)
780       {
781          out.write(getAvailableOptionsSize());
782          out.write("' multiple");
783       }
784       else
785       {
786          out.write("1'");
787       }
788       out.write(">");
789       
790       if (itemsPresent)
791       {
792          Node currentNode = (Node)getValue();
793          for (NodeRef item : this.availableOptions)
794          {
795             // NOTE: only show the items that are not already associated to and don't show the current node
796
if ((this.originalAssocs.containsKey(item.getId()) == false && this.added.containsKey(item.getId()) == false &&
797                 item.getId().equals(currentNode.getId()) == false) ||
798                 this.removed.containsKey(item.getId()))
799             {
800                out.write("<option value='");
801                out.write(item.getId());
802                out.write("'>");
803                out.write(Repository.getDisplayPath(nodeService.getPath(item)));
804                out.write("/");
805                out.write(Repository.getNameForNode(nodeService, item));
806                out.write("</option>");
807             }
808          }
809       }
810       
811       out.write("</select></td></tr>");
812    }
813    
814    /**
815     * Retrieves the AssociationDefinition for the association we are representing
816     *
817     * @param context Faces Context
818     * @return The AssociationDefinition for the association, null if a definition does not exist
819     */

820    protected AssociationDefinition getAssociationDefinition(FacesContext context)
821    {
822       // get some metadata about the association from the data dictionary
823
DataDictionary dd = (DataDictionary)FacesContextUtils.getRequiredWebApplicationContext(
824                context).getBean(Application.BEAN_DATA_DICTIONARY);
825       return dd.getAssociationDefinition((Node)getValue(), this.associationName);
826    }
827    
828    /**
829     * Retrieves the available options for the current association
830     *
831     * @param context Faces Context
832     * @param contains The contains part of the query
833     */

834    protected void getAvailableOptions(FacesContext context, String JavaDoc contains)
835    {
836       AssociationDefinition assocDef = getAssociationDefinition(context);
837       if (assocDef != null)
838       {
839          // find and show all the available options for the current association
840
StringBuilder JavaDoc query = new StringBuilder JavaDoc("+TYPE:\"");
841          query.append(assocDef.getTargetClass().getName().toString());
842          query.append("\"");
843          
844          if (contains != null && contains.length() > 0)
845          {
846             String JavaDoc safeContains = Utils.remove(contains.trim(), "\"");
847             String JavaDoc nameAttr = Repository.escapeQName(QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "name"));
848             
849             query.append(" AND +@");
850             query.append(nameAttr);
851             query.append(":*" + safeContains + "*");
852          }
853          
854          if (logger.isDebugEnabled())
855             logger.debug("Query: " + query.toString());
856          
857          ResultSet results = null;
858          try
859          {
860             results = Repository.getServiceRegistry(context).getSearchService().query(
861                   Repository.getStoreRef(), SearchService.LANGUAGE_LUCENE, query.toString());
862             this.availableOptions = results.getNodeRefs();
863          }
864          finally
865          {
866             if (results != null)
867             {
868                results.close();
869             }
870          }
871          
872          if (logger.isDebugEnabled())
873             logger.debug("Found " + this.availableOptions.size() + " available options");
874       }
875    }
876    
877    /**
878     * We use a hidden field per picker instance on the page.
879     *
880     * @return hidden field name
881     */

882    private String JavaDoc getHiddenFieldName()
883    {
884       return getClientId(getFacesContext());
885    }
886    
887    /**
888     * Generate FORM submit JavaScript for the specified action
889     *
890     * @param context FacesContext
891     * @param action Action string
892     *
893     * @return FORM submit JavaScript
894     */

895    private String JavaDoc generateFormSubmit(FacesContext context, String JavaDoc action)
896    {
897       return Utils.generateFormSubmit(context, this, getHiddenFieldName(), action);
898    }
899    
900    // ------------------------------------------------------------------------------
901
// Inner classes
902

903    /**
904     * Class representing an action relevant to the AssociationEditor component.
905     */

906    public static class AssocEditorEvent extends ActionEvent
907    {
908       private static final long serialVersionUID = 7346758616063937703L;
909
910       public int Action;
911       public String JavaDoc[] ToAdd;
912       public String JavaDoc RemoveId;
913       public String JavaDoc Contains;
914       
915       public AssocEditorEvent(UIComponent component, int action, String JavaDoc[] toAdd, String JavaDoc removeId, String JavaDoc contains)
916       {
917          super(component);
918          this.Action = action;
919          this.ToAdd = toAdd;
920          this.RemoveId = removeId;
921          this.Contains = contains;
922       }
923    }
924 }
925
Popular Tags