Package er.extensions.components.javascript

Source Code of er.extensions.components.javascript.ERXJSPopUpRelationPicker

/*
* Copyright (C) Quetzal Consulting, Inc. All rights reserved.
*
*    This class was originally developed by Robert A. Decker
*    at Quetzal Consulting
*
* This software is published under the terms of the NetStruxr
* Public Software License version 0.5, a copy of which has been
* included with this distribution in the LICENSE.NPL file.  */
package er.extensions.components.javascript;

import java.util.Enumeration;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import com.webobjects.appserver.WOContext;
import com.webobjects.appserver.WORequest;
import com.webobjects.eocontrol.EOSortOrdering;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSKeyValueCodingAdditions;
import com.webobjects.foundation.NSMutableArray;

import er.extensions.components.ERXStatelessComponent;
import er.extensions.components._private.ERXWOForm;
import er.extensions.foundation.ERXArrayUtilities;
import er.extensions.foundation.ERXStringUtilities;

/**
* Very, very cool js component. Implements master-detail with js in two popups, ie the first popup could be say
* states and depending on which state is picked the second popup might reflect all of the cities of that state.<br />
* This WOComponent displays two pop-up buttons. One pop-up displays a list of what can be thought of as parent entities.
* The second pop-up displays a list of what can be thought of as children entities. When a user selects an entity in
* the parent list, the child list is instantly modified to reflect the children entities available to the user
* through that parent. This is done through client-side Javascript. Also handles to-many selections both on the
* parent and the children.<br />
* For example:
<pre><code>
parent1(child1,child2,child3)
parent2(child4,child5)
parent3(child2,child5)
</code></pre>
* When the user selects parent1, its appropriate children are displayed in the second popup.
* If the user selects child2 in the children pop-up this is the value that is returned to the
* user through the childrenSelection variable. This is either an NSArray if <code>multiple</code> is true
* or the single selected object.
* For the display of the parent popup, if we aren't passed in a parentSelection, then we default to
* parentPopUpStringForAll. If we aren't given that either, then we default to the first parent in the array.
* For the display of the child popup, if we aren't passed in a childrenSelection, then we default to childPopUpStringForAll.
* If we aren't given that either, then we default to the first child in the array.
*
* @binding multiple boolean the defines if there can multiple parents and children selected.
* @binding parentEntitiesList array of the parent objects that appear in the first pop-up.
* @binding parentToChildrenRelationshipName name of the relationship from the parent to its possible children. This is used to fill the values that appear in the children popup.
* @binding parentSelection currently selected parent(s) in the parent pop-up. This can be null, but will return the user-selected parent.
* @binding childrenSelection returns the user-selected child(ren).
* @binding parentDisplayValueName keypath of the parent displayed in the parent pop-up
* @binding parentLabel value displayed in the table interface for the parent popup.
* @binding childLabel value displayed in the table interface for the child popup.
* @binding defaultChildKey keypath of the parent for the default child (eg, largest city)
* @binding childrenSortKey keypath to sort the children on
* @binding childDisplayValueName keypath of the child displayed in the child pop-up
* @binding parentPopUpStringForAll  to display if no parent is chosen ("- all -")
* @binding childPopUpStringForAll to display if no child is chosen ("- all -")
* @binding size number of rows in select boxes
* @binding possibleChildren shows only these values for children
*/

public class ERXJSPopUpRelationPicker extends ERXStatelessComponent {
  /**
   * Do I need to update serialVersionUID?
   * See section 5.6 <cite>Type Changes Affecting Serialization</cite> on page 51 of the
   * <a href="http://java.sun.com/j2se/1.4/pdf/serial-spec.pdf">Java Object Serialization Spec</a>
   */
  private static final long serialVersionUID = 1L;

    public ERXJSPopUpRelationPicker(WOContext aContext) {
        super(aContext);
    }

    /** logging support */
    public final static Logger log = Logger.getLogger(ERXJSPopUpRelationPicker.class);
    public final static Logger jsLog = Logger.getLogger("er.extensions.ERXJSPopUpRelationPicker.script");

    protected Integer _size;
    protected String _childDisplayValueName;
    protected String _parentDisplayValueName;

    protected NSArray _parentEntitiesList;
    protected String _parentToChildrenRelationshipName;

    protected NSArray _parentSelection;
    protected NSArray _childrenSelection;

    protected String _parentPopUpStringForAll; // for example, '-- all --' or '-- none --'. sets selectedParent to null. All children displayed for all parents if picked.
    protected String _childPopUpStringForAll; // for example, '-- all --' or '-- none --'. sets selectedChild to null

    protected String _parentLabel;
    protected String _childLabel;
    protected String _childrenSortKey;
    protected String _defaultChildKey;
    protected Boolean _multiple;
    protected NSArray _possibleChildren;
   
    protected String parentSelectName;
    protected String childSelectName;
    protected String pickerName;

    protected String objectsArrayName;
   
    private static final int NOT_FOUND = -1;
    private static final NSArray UNSET = new NSArray();
   
    @Override
    public void awake() {
        super.awake();
        updateVarNames();
    }

    protected void updateVarNames() {
        String elementID = context().elementID();
        elementID = StringUtils.replace(elementID,  ".", "_");
        pickerName = "picker_"+ elementID;
        parentSelectName = "parent_" + elementID;
        childSelectName = "child_" + elementID;
        objectsArrayName = "parents_children_" + elementID;
    }

    protected int offsetForID(String id) {
        if(!(id == null || id.trim().length() == 0 || "WONoSelectionString".equals(id))) {
            try {
                return Integer.parseInt(id);
            } catch (Exception e) {
                log.info("Exception while parsing ID", e);
            }
        }
        return NOT_FOUND;
    }
   
    protected Object parentFromID(String id) {
        int offset = offsetForID(id);
        if(offset != NOT_FOUND) {
            return parentEntitiesList().objectAtIndex(offset);
        }
        return null;
    }
   
    protected Object idForParent(Object parent) {
        if(parent != null) {
            return Integer.valueOf(parentEntitiesList().indexOfObject(parent));
        }
        return null;
    }
   
    protected Object childFromID(Object parent, String id) {
        if(id != null) {
            NSArray ids = NSArray.componentsSeparatedByString(id, "|");
            if(ids.count() == 2) {
                if(parent == null) {
                    parent = parentFromID((String)ids.objectAtIndex(0));
                }
                int offset = offsetForID((String)ids.objectAtIndex(1));
                if(offset != NOT_FOUND && parent != null) {
                    return sortedChildren(parent).objectAtIndex(offset);
                }
            } else {
                log.info("Child ID not valid: " + id);
            }
        }
        return null;
    }
   
  protected int offsetForChild(Object parent, Object child) {
    return parent != null ? sortedChildren(parent).indexOfObject(child) : NOT_FOUND;
    }
 
    protected Object idForChild(Object parent, Object child) {
        if(parent != null) {
            int offset = sortedChildren(parent).indexOfObject(child);
            if(offset != NOT_FOUND) {
                return idForParent(parent) + "|" + offset;
            }
        } else {
            for (Enumeration parents = parentEntitiesList().objectEnumerator(); parents.hasMoreElements();) {
                Object aParent = parents.nextElement();
                int offset = sortedChildren(aParent).indexOfObject(child);
                if(offset != NOT_FOUND) {
                    return idForParent(aParent) + "|" + offset;
                }
           }
        }
        return null;
    }
   
  
    @Override
    public void takeValuesFromRequest(WORequest request, WOContext context) {
        NSArray parentFormValues = request.formValuesForKey(parentSelectName);
        NSArray childFormValues = request.formValuesForKey(childSelectName);
        if(parentFormValues != null && childFormValues != null) {
            if(parentFormValues.containsObject("WONoSelectionString")) {
                setSelectedParents(null);
            } else {
                NSMutableArray parents = new NSMutableArray();
                for (Enumeration ids = parentFormValues.objectEnumerator(); ids.hasMoreElements();) {
                    Object parent = parentFromID((String) ids.nextElement());
                    if(parent != null) {
                        parents.addObject(parent);
                    }
                }
                setSelectedParents(parents);
            }

            if(childFormValues.containsObject("WONoSelectionString")) {
                NSArray children = (NSArray)parentSelection().valueForKeyPath(parentToChildrenRelationshipName());
                if(parentFormValues.containsObject("WONoSelectionString")) {
                    setChildrenSelection(null);
                } else {
                    if(!multiple()) {
                        setChildrenSelection(null);
                    } else {
                        setChildrenSelection(ERXArrayUtilities.flatten(children));
                    }
                }
            } else {
                NSMutableArray children = new NSMutableArray();
                for (Enumeration ids = childFormValues.objectEnumerator(); ids.hasMoreElements();) {
                    Object child = childFromID(null, (String) ids.nextElement());
                    if(child != null) {
                        children.addObject(child);
                    }
                }
                setChildrenSelection(children);
            }
        }
        super.takeValuesFromRequest(request, context);
    }

    protected NSArray possibleChildren() {
        if(_possibleChildren != null) {
            _possibleChildren = (NSArray) valueForBinding("possibleChildren");
            if(_possibleChildren == null) {
                _possibleChildren = UNSET;
            }
        }
        return _possibleChildren == UNSET ? null : _possibleChildren;
    }
   
    protected NSArray unsortedChildren(Object parent) {
        NSArray result = (NSArray)NSKeyValueCodingAdditions.Utility.valueForKeyPath(parent, parentToChildrenRelationshipName()
                + ((parent instanceof NSArray) ? ".@flatten.@removeNullValues" : ""));
        NSArray restrictedChoices = possibleChildren();
        if(restrictedChoices != null) {
            result = ERXArrayUtilities.intersectingElements(result, restrictedChoices);
        }
        return result;
    }
   
    protected NSArray sortedChildren(Object parent) {
        EOSortOrdering sortOrdering=new EOSortOrdering(childrenSortKey(), EOSortOrdering.CompareAscending);
        NSMutableArray sortArray=new NSMutableArray(sortOrdering);
        NSArray result=EOSortOrdering.sortedArrayUsingKeyOrderArray(unsortedChildren(parent), sortArray);
        return result!=null ? result : NSArray.EmptyArray;
    }

    public String jsString() {
        // this method returns all the javascript we need to embed in the web page.
        StringBuilder returnString = new StringBuilder(2000);
        returnString.append('\n');

        // This Javascript string builds an array of Entity objects on the browser end.
        returnString.append(objectArrayCreationString());
        returnString.append('\n');
        if (jsLog.isDebugEnabled()) jsLog.debug("JSPopUpRelationPicker jsString  returnString is " + returnString);
        return returnString.toString();
    }

    public String hiddenFormElementStrings() {
        StringBuilder returnString  = new StringBuilder(500);
        returnString.append("\n<script language=\"JavaScript\">");
    String childToSelect = "";
    if (!multiple() &&
      childrenSelection()!=null && childrenSelection().count()==1 &&
      parentSelection()!=null && parentSelection().count()==1) {
      int childToSelectInt=offsetForChild(parentSelection().objectAtIndex(0),
                        childrenSelection().objectAtIndex(0));
      if (childToSelectInt!=NOT_FOUND) childToSelect=""+childToSelectInt;
    }
   
        returnString.append("\nvar " + pickerName +" = new ERXJSPopupRelationshipPicker("
        + objectsArrayName + ","
        + "window.document." + formName() + "." + parentSelectName + ","
        + (parentPopUpStringForAll() != null ? "\"" + parentPopUpStringForAll().replace('\n',' ') + "\"" : "null") + ","
        + "window.document." + formName() + "." + childSelectName + ","
        + (childPopUpStringForAll() != null ? "\"" + childPopUpStringForAll() + "\"" : "null")
        +");\n"
        + (parentPopUpStringForAll() == null ? pickerName + ".parentChanged("+childToSelect+");"  : "")
        +"\n</script>");
        log.debug(returnString);
    // trigger an update of the parent - this causes the child to be properly set to a sub selection (instead of listing all possible value) when
    // we are editing a new object
        return returnString.toString();
    }

    public String parentPopUpString() {
        StringBuffer returnString = selectHeader(parentSelectName, pickerName + ".parentChanged();");

        if (parentSelection().count() == 0 && parentPopUpStringForAll()==null)
            setSelectedParents(new NSArray(parentEntitiesList().objectAtIndex(0)));
        int iCount = parentEntitiesList().count();
        for (int i=0;i<iCount;i++) {
            Object aEntity = parentEntitiesList().objectAtIndex(i);
            returnString.append("\t<option ");
            if (isSelectedParent(aEntity)) {
                returnString.append("selected=\"selected\" ");
            }
            returnString.append("value=\"" + idForParent(aEntity) + "\">");
            returnString.append(NSKeyValueCodingAdditions.Utility.valueForKeyPath(aEntity, parentDisplayValueName()));
            returnString.append("</option>\n");
        }
        returnString.append("</select>\n");
        return returnString.toString();
    }

    public String formName() {
        return ERXWOForm.formName(context(), "forms[0]");
    }
   
 
    /**
     * @return the string to create the pop-up with the initial child values something like:
     <pre>&lt;select name="children_select"&gt;
     &lt;option value=4&gt;poodle
     &lt;option selected value=5&gt;puli
     &lt;option value=6&gt;greyhound
     &lt;/select&gt;</pre>
     */
    public String childPopUpString() {
        StringBuffer returnString = selectHeader(childSelectName, pickerName + ".childChanged();");

        if (parentSelection().count() != 0) {
            if (childrenSelection().count() == 0 && defaultChildKey() != null)
                setChildrenSelection((NSArray)NSKeyValueCodingAdditions.Utility.valueForKeyPath(parentSelection(), defaultChildKey() + ".@flatten"));
            appendChildPopupStringWithParent(returnString, parentSelection());
        } else {
            // nothing is selected in the parent, so set the children array to all possible child values
            // run through all parents, getting each child. However, if we don't have a parentPopUpStringForAll then we only do it for the last parent.
            if (parentPopUpStringForAll() != null) {
                appendChildPopupStringWithParent(returnString, parentEntitiesList());
            } else {
                // only do the last parent because we don't have a selected parent AND we don't have the possibility
                // of setting the parent to 'All'
                Object aParent = parentEntitiesList().objectAtIndex(0);
                setChildrenSelection(defaultChildKey()!=null ?
                        (NSArray)NSKeyValueCodingAdditions.Utility.valueForKeyPath(parentSelection(), defaultChildKey() + ".@flatten") : NSArray.EmptyArray);
                appendChildPopupStringWithParent(returnString, new NSArray(aParent));
            }
        }

        returnString.append("</select>\n");
        if (jsLog.isDebugEnabled()) jsLog.debug("JSPopUpRelationPicker childPopUpString  returnString is " + returnString);
        return returnString.toString();
    }
   
    private void appendChildPopupStringWithParent(StringBuffer returnString, NSArray aParents) {
        NSArray children = sortedChildren(aParents);
        // write out each of the values for the options tags. be sure to set the selected tag if necessary
        int iCount = children.count();
        for (int i=0;i<iCount;i++) {
            Object aChild = children.objectAtIndex(i);
            returnString.append("\t<option ");
            if (((i == iCount-1) && (childrenSelection().count() == 0) && (childPopUpStringForAll() == null))
                    || (isSelectedChild(aChild))) {
                returnString.append("selected=\"selected\" ");
            }
            returnString.append("value=\"" + idForChild(null, aChild) + "\">");
            returnString.append(NSKeyValueCodingAdditions.Utility.valueForKeyPath(aChild, childDisplayValueName()));
            returnString.append("</option>\n");
        }
    }

    protected StringBuffer selectHeader(String nm, String onChange) {
        StringBuffer returnString;
       
        returnString = new StringBuffer(1000);
        returnString.append("<select name=\"" + nm + "\"" + " size=\"" + size() + "\"");
        if (onChange != null) {
            returnString.append(" onChange=\"" + onChange + "\"");
        }
        if (multiple()) {
            returnString.append(" multiple=\"multiple\"");
        }
        returnString.append(">\n");
        return returnString;
    }
   
    public String objectArrayCreationString() {
        // here's an example of the string this method should return:
        //var parentschildren = new Array(new Entity("dogs","1",new Array(new Entity("poodle","4",null,false),new Entity("puli","5",null,true),new Entity("greyhound","5",null,false)),false), new Entity("fish","2",new Array(new Entity("trout","6",null,true),new Entity("mackerel","7",null,false),new Entity("bass","8",null,false)),true), new Entity("birds","3",new Array(new Entity("robin","9",null,false),new Entity("hummingbird","10",null,false),new Entity("crow","11",null,true)),false));

        StringBuilder returnString = new StringBuilder(1000);
        returnString.append("var ");
        returnString.append(objectsArrayName);
        returnString.append(" = [");

        int iCount = parentEntitiesList().count();
        for (int i=0;i<iCount;i++) {
            Object aParent = parentEntitiesList().objectAtIndex(i);
            returnString.append("\n\tnew Entity(");
            returnString.append(" \"" + NSKeyValueCodingAdditions.Utility.valueForKeyPath(aParent, parentDisplayValueName()) + "\",");
            returnString.append(" \"" + idForParent(aParent) + "\",");
            returnString.append(" \"" + System.identityHashCode(aParent) + "\",");

            // now do all the possible children of the parent. Each child should look like 'new Entity("poodle","4",null,false)'
            returnString.append(" [");
            NSArray childrenOfAParent = sortedChildren(aParent);

            int jCount = childrenOfAParent.count();
            Object defaultChild=defaultChildKey()!=null ? NSKeyValueCodingAdditions.Utility.valueForKeyPath(aParent, defaultChildKey()) : null;
            int defaultChildIndex=-1;

            for (int j=0;j<jCount;j++) {
                Object aChild = childrenOfAParent.objectAtIndex(j);
                returnString.append("\n\t\t new Entity(");
                returnString.append(" \"" + NSKeyValueCodingAdditions.Utility.valueForKeyPath(aChild, childDisplayValueName()) + "\","); // visible text of pop-up
                returnString.append(" \"" + idForChild(aParent, aChild) + "\","); // value text of pop-up
                returnString.append(" \"" + System.identityHashCode(aChild) + "\",");
                returnString.append(" null,");
                if (isSelectedChild(aChild)) {
                    returnString.append(" true");
                } else {
                    returnString.append(" false");
                }
                returnString.append(", null");
                returnString.append(')');
                if (j != jCount - 1) {
                    // append a comma and a space
                    returnString.append(", ");
                }
                if (aChild==defaultChild) defaultChildIndex=j;
            }
            returnString.append("],");
            if (isSelectedParent(aParent)) { // in the single case, the parent will be updated when we call parent changed
                returnString.append(" true");
            } else {
                returnString.append(" false");
            }
            returnString.append(", ");
            returnString.append(defaultChild!=null ? "\""+defaultChildIndex+"\"" : "-1");
            returnString.append(')');


            if (i != iCount - 1) {
                // append a comma and a space
                returnString.append(", ");
            }
        }
        returnString.append("];");
        return returnString.toString();
    }

    /**
     * @param aParent
     */
    private boolean isSelectedParent(Object aParent) {
        return parentSelection().containsObject(aParent);
    }

    /**
     * @param aChild
     */
    private boolean isSelectedChild(Object aChild) {
        return childrenSelection().containsObject(aChild);
    }

    public NSArray parentEntitiesList() {
        if(_parentEntitiesList == null) {
            _parentEntitiesList = (NSArray)valueForBinding("parentEntitiesList");
        }
        return _parentEntitiesList;
    }
    public NSArray parentSelection() {
        if(_parentSelection == null) {
            if(multiple()) {
                _parentSelection = (NSArray)valueForBinding("parentSelection");
            } else {
                Object parent = valueForBinding("parentSelection");
                if(parent != null) {
                    _parentSelection = new NSArray(parent);
                }
            }
            if(_parentSelection == null) {
                _parentSelection = NSArray.EmptyArray;                  
            }
        }
        return _parentSelection;
    }
    public void setSelectedParents(NSArray value) {
        if(!multiple() && canSetValueForBinding("parentSelection")) {
            setValueForBinding(value == null ? null : value.lastObject(), "parentSelection");
        } else if(multiple() && canSetValueForBinding("parentSelection")) {
            setValueForBinding(value, "parentSelection");
        }
        _parentSelection = value;
    }

    public NSArray childrenSelection() {
        if(_childrenSelection == null) {
            if(multiple()) {
                _childrenSelection = (NSArray)valueForBinding("childrenSelection");
            } else {
                Object child = valueForBinding("childrenSelection");
                if(child != null) {
                    _childrenSelection = new NSArray(child);
                }
            }
            if(_childrenSelection == null) {
                _childrenSelection = NSArray.EmptyArray;                  
            }
       }
        return _childrenSelection;
    }
    public void setChildrenSelection(NSArray value) {
        if(!multiple() && canSetValueForBinding("childrenSelection")) {
            setValueForBinding(value == null null: value.lastObject(), "childrenSelection");
        } else if(multiple() && canSetValueForBinding("childrenSelection")) {
            setValueForBinding(value, "childrenSelection");
        }
        _childrenSelection = value;
    }

    public String defaultChildKey() {
        if(_defaultChildKey == null) {
            _defaultChildKey = (String)valueForBinding("defaultChildKey");
        }
        return _defaultChildKey;
    }
    public String childrenSortKey() {
        if(_childrenSortKey == null) {
            _childrenSortKey = (String)valueForBinding("childrenSortKey");
        }
        return _childrenSortKey;
    }

    public String childLabel() {
        if(_childLabel == null) {
            _childLabel = (String)valueForBinding("childLabel");
            if(_childLabel == null)
                _childLabel = "Types";
        }
        return _childLabel;
    }

    public String parentLabel() {
        if(_parentLabel == null) {
            _parentLabel = (String)valueForBinding("parentLabel");
            if(_parentLabel == null)
                _parentLabel = "Categories";
        }
        return _parentLabel;
    }

    public String childDisplayValueName() {
        if(_childDisplayValueName == null) {
            _childDisplayValueName = (String)valueForBinding("childDisplayValueName");
        }
        return _childDisplayValueName;
    }
    public String parentDisplayValueName() {
        if(_parentDisplayValueName == null) {
            _parentDisplayValueName = (String)valueForBinding("parentDisplayValueName");
        }
        return _parentDisplayValueName;
    }
    public String parentToChildrenRelationshipName() {
        if(_parentToChildrenRelationshipName == null) {
            _parentToChildrenRelationshipName = (String)valueForBinding("parentToChildrenRelationshipName");
        }
        return _parentToChildrenRelationshipName;
    }
    public String parentPopUpStringForAll() {
        if(_parentPopUpStringForAll == null) {
            _parentPopUpStringForAll = (String)valueForBinding("parentPopUpStringForAll");
        }
        return _parentPopUpStringForAll;
    }
    public String childPopUpStringForAll() {
        if(_childPopUpStringForAll == null) {
            _childPopUpStringForAll = (String)valueForBinding("childPopUpStringForAll");
        }
        return _childPopUpStringForAll;
    }
    public int size() {
        if(_size == null) {
            _size = Integer.valueOf(intValueForBinding("size", multiple() ? 5 : 1));
        }
        return _size.intValue();
    }
    public boolean multiple() {
        if(_multiple == null) {
            _multiple = booleanValueForBinding("multiple") ? Boolean.TRUE : Boolean.FALSE;
        }
        return _multiple.booleanValue();
    }
   
    @Override
    public void reset() {
        super.reset();
        _childrenSelection = null;
        _parentSelection = null;
        _parentEntitiesList = null;
        _childLabel = null;
        _parentLabel = null;
        _defaultChildKey = null;
        _childrenSortKey = null;
        _multiple = null;

        _childDisplayValueName = null;
        _parentDisplayValueName = null;
        _parentToChildrenRelationshipName = null;
        _parentPopUpStringForAll = null;
        _childPopUpStringForAll = null;
        _possibleChildren = null;
        _size = null;
    }
}
TOP

Related Classes of er.extensions.components.javascript.ERXJSPopUpRelationPicker

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.