0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041: package org.netbeans.modules.visualweb.designer.jsf;
0042:
0043: import org.netbeans.modules.visualweb.api.designer.markup.MarkupService;
0044: import org.netbeans.modules.visualweb.insync.CustomizerDisplayer;
0045: import org.netbeans.modules.visualweb.insync.InSyncServiceProvider;
0046: import org.netbeans.modules.visualweb.insync.faces.FacesPageUnit;
0047: import org.netbeans.modules.visualweb.insync.markup.MarkupUnit;
0048: import org.netbeans.modules.visualweb.insync.models.FacesModel;
0049: import javax.faces.component.UIComponent;
0050: import javax.swing.SwingUtilities;
0051: import org.apache.xerces.dom.events.MutationEventImpl;
0052: import org.openide.filesystems.FileObject;
0053:
0054: import org.w3c.dom.DocumentFragment;
0055: import org.w3c.dom.Element;
0056: import org.w3c.dom.Node;
0057: import org.w3c.dom.NodeList;
0058: import org.w3c.dom.events.EventTarget;
0059:
0060: import com.sun.rave.designtime.DesignBean;
0061: import com.sun.rave.designtime.DesignContext;
0062: import com.sun.rave.designtime.DesignEvent;
0063: import com.sun.rave.designtime.DesignProperty;
0064: import com.sun.rave.designtime.Position;
0065: import com.sun.rave.designtime.event.DesignContextListener;
0066: import com.sun.rave.designtime.markup.MarkupDesignBean;
0067: import java.util.ArrayList;
0068: import java.util.HashMap;
0069: import java.util.List;
0070: import java.util.Map;
0071: import org.netbeans.modules.visualweb.designer.html.HtmlTag;
0072: import org.openide.ErrorManager;
0073: import org.w3c.dom.Attr;
0074: import org.w3c.dom.Document;
0075: import org.w3c.dom.NamedNodeMap;
0076:
0077: /**
0078: * XXX Moved from designer.
0079: * <p>
0080: * This class is responsible for updating the HTML DOM when changes in the master JSP DOM, or
0081: * associated properties in Java files or JavaScript event occur.
0082: * </p>
0083: *
0084: * @author Tor Norbye
0085: */
0086: class DomSynchronizer implements /*DesignContextListener,*/Runnable,
0087: org.w3c.dom.events.EventListener,
0088: FacesDndSupport.UpdateSuspender {
0089:
0090: static {
0091: // XXX Very suspicious, why it is not more robust?
0092: CustomizerDisplayer.setBatchListener(new BeanModifyListener());
0093: }
0094:
0095: private static final boolean DEBUG = false;
0096: private static final int TYPE_NONE = 0;
0097: private static final int TYPE_INSERT = 1;
0098: private static final int TYPE_CHANGE = 2;
0099: private static final int TYPE_DELETE = 3;
0100: private static final int TYPE_REFRESH = 4;
0101:
0102: /** The currently planned bean to be changed, inserted or deleted (see pendingEventType) */
0103: private MarkupDesignBean pendingBean;
0104: // private WebForm webform;
0105: private JsfForm jsfForm;
0106: private org.w3c.dom.Document currentDOM;
0107: private Node deleteParent;
0108: // private DesignContext context;
0109:
0110: /** If true we're suspending all updates until setUpdatesSuspended is called to turn it off */
0111: private boolean suspendUpdates;
0112:
0113: /**
0114: * If true there's a timeout pending - we've enqueued a Runnable on the Swing event dispatch
0115: * thread
0116: */
0117: private boolean timeOutPending;
0118:
0119: /** If not TYPE_NONE, the type of pending update we're waiting for */
0120: private int pendingEventType = TYPE_NONE;
0121:
0122: // XXX #112216.
0123: private boolean inlineEditingUpdate;
0124:
0125: /**
0126: * Create a new DomSynchronizer for the given webform
0127: */
0128: public DomSynchronizer(JsfForm jsfForm) {
0129: this .jsfForm = jsfForm;
0130:
0131: currentDOM = jsfForm.getJspDom();
0132:
0133: if (currentDOM == null) {
0134: // jsfForm.getFacesModel().sync();
0135: jsfForm.syncModel();
0136: currentDOM = jsfForm.getJspDom();
0137:
0138: // XXX What was this good for?
0139: // if (currentDOM == null) {
0140: // currentDOM = MarkupUnit.createEmptyDocument(true);
0141: // }
0142: }
0143:
0144: if (currentDOM != null) {
0145: registerDomListeners();
0146: }
0147: }
0148:
0149: // /**
0150: // * Destroy this synchronizer - detach from listeners etc. when the object
0151: // * is no longer to be used
0152: // */
0153: // public void destroy() {
0154: // if (currentDOM != null) {
0155: // unregisterDomListeners();
0156: // }
0157: //
0158: //// detachContext();
0159: // jsfForm.detachContext();
0160: // }
0161:
0162: // /**
0163: // * Detach this synchronizer from the current DesignContext, if any
0164: // */
0165: // public void detachContext() {
0166: // if (context != null) {
0167: // context.removeDesignContextListener(this);
0168: // }
0169: //
0170: // context = null;
0171: // }
0172: //
0173: // /**
0174: // * Attach the synchronizer to the given DesignContext.
0175: // * The reason this is done "lazily", not part of the constructor,
0176: // * is that if we try to access a DesignContext after startup
0177: // * and the context has an error, the FacesModel will provide a null
0178: // * DesignContext. We don't obtain it until there has been a successful
0179: // * sync.
0180: // */
0181: // public void attachContext(DesignContext context) {
0182: // if (this.context == context) {
0183: // return;
0184: // }
0185: //
0186: // detachContext();
0187: // this.context = context;
0188: //
0189: // if (context != null) {
0190: // context.addDesignContextListener(this);
0191: // }
0192: // }
0193:
0194: // ----------- Implements DesignContextListener ---------------------------
0195: public void instanceNameChanged(DesignBean designBean,
0196: String oldInstanceName) {
0197: }
0198:
0199: /**
0200: * {@inheritDoc}
0201: */
0202: public void beanChanged(DesignBean designBean) {
0203: // TODO We need to find furthest ancestor that renders its own children.
0204: // It may be replicating this changed bean in multiple places, so needs
0205: // to be consulted.
0206: if (!(designBean instanceof MarkupDesignBean)) {
0207: // Could be something like a rowset or select items list.
0208: // We can't know visual beans are affected by this (at least we can't yet;
0209: // perhaps when we do a databinding view we'll know more about component
0210: // connections via value binding etc.) so we need to do a global re-render
0211: // Special case: when batch updates are suspended we know we can ignore
0212: // non markup beans are modified. Otherwise, dropping a listbox would
0213: // also have a select items object added and modified which would result
0214: // in a refresh if we didn't do this. (Similarly for DataTable; the non-markup
0215: // TableDataModel would also force a refresh). But in both cases we know
0216: // that the model itself is associated with the suspended/batched markup bean.
0217: if (suspendUpdates) {
0218: requestRefresh();
0219: }
0220:
0221: return;
0222: }
0223:
0224: // Insert new version of component
0225: MarkupDesignBean bean = (MarkupDesignBean) designBean;
0226: bean = getRenderTarget(bean);
0227: requestChange(bean);
0228: }
0229:
0230: public void beanCreated(DesignBean designBean) {
0231: // TODO We need to find furthest ancestor that renders its own children.
0232: // It may be replicating this changed bean in multiple places, so needs
0233: // to be consulted.
0234: if (!(designBean instanceof MarkupDesignBean)) {
0235: // We only care about markup beans when beans
0236: // are created (the same is not true for beanChanged
0237: // since an existing non-markup DesignBean can be
0238: // bound to a visual bean in some way and can affect
0239: // rendering - for example, the items property of
0240: // a SelectItems non-markup bean
0241: return;
0242: }
0243:
0244: MarkupDesignBean bean = (MarkupDesignBean) designBean;
0245: bean = getRenderTarget(bean);
0246:
0247: // Instead of inserting a new component directly we're re-rendering
0248: // a parent - this is a change, not an insert so we must delete the
0249: // old markup
0250: if (bean != designBean) {
0251: requestChange(bean);
0252:
0253: return;
0254: } else {
0255: requestInsert(bean);
0256: }
0257: }
0258:
0259: public void propertyChanged(DesignProperty prop, Object oldValue) {
0260: DesignBean designBean = prop.getDesignBean();
0261:
0262: if (!(designBean instanceof MarkupDesignBean)) {
0263: // Could be something like a rowset or select items list.
0264: // We can't know visual beans are affected by this (at least we can't yet;
0265: // perhaps when we do a databinding view we'll know more about component
0266: // connections via value binding etc.) so we need to do a global re-render
0267: requestRefresh();
0268:
0269: return;
0270: }
0271:
0272: MarkupDesignBean bean = (MarkupDesignBean) designBean;
0273:
0274: if (bean.getElement() == null) {
0275: // No element: the DesignBean does not correspond to a visual
0276: // component, or it might be one we're inserting; insync seems
0277: // to set some properties on these before actually inserting it
0278: // into the view hiearchy; we can safely ignore these requests
0279: // since the eventual DOM modification will cause a refresh.
0280: return;
0281: }
0282:
0283: bean = getRenderTarget(bean);
0284: requestChange(bean);
0285: }
0286:
0287: public void beanDeleted(DesignBean designBean) {
0288: if (!(designBean instanceof MarkupDesignBean)) {
0289: // Bean deletion of non-markup beans should have no effect on the visible
0290: // beans since if they are bound to this non-markup bean in some way,
0291: // insync should go and remove that relationship, and that will show up as
0292: // a property change on the markup bean which we will handle
0293: return;
0294: }
0295:
0296: MarkupDesignBean bean = (MarkupDesignBean) designBean;
0297: // XXX FIXME This can't work, the bean is supposed to be already removed from the tree.
0298: // How come it was working before and based on what hack? (see getRenderTarget).
0299: bean = getRenderTarget(bean);
0300:
0301: if (bean != designBean) {
0302: // If the bean to be deleted is inside a parent that must be
0303: // re-rendered, we should -change- the ancestor, not delete it.
0304: requestChange(bean);
0305: } else {
0306: requestDelete(bean);
0307: }
0308: }
0309:
0310: public void contextChanged(DesignContext context) {
0311: // The entire HTML needs to be replaced
0312: requestRefresh();
0313: }
0314:
0315: public void beanMoved(DesignBean designBean, DesignBean oldParent,
0316: Position pos) {
0317: if (!(designBean instanceof MarkupDesignBean)) {
0318: // Position on non-markup beans never matter to the HTML rendered
0319: return;
0320: } else if (!(oldParent instanceof MarkupDesignBean)) {
0321: // This should never be possible - and we should make sure this is
0322: // prevented in the UI to move beans around in e.g. the app outline and
0323: // the design surface
0324: return;
0325: }
0326:
0327: MarkupDesignBean bean = (MarkupDesignBean) designBean;
0328: bean = getRenderTarget(bean);
0329:
0330: if (bean == null) {
0331: requestRefresh();
0332: } else if (isBelow(oldParent, bean)) {
0333: // We've moving the bean from one position to another inside
0334: // the same encodes-children bean that we need to render
0335: // Nothing else to do
0336: } else if (isBelow(bean, oldParent)) {
0337: bean = (MarkupDesignBean) oldParent;
0338: } else {
0339: bean = (MarkupDesignBean) getCommonAncestor(bean, oldParent);
0340: }
0341:
0342: requestChange(bean);
0343: }
0344:
0345: public void contextActivated(DesignContext context) {
0346: // No-op: No impact on the rendered HTML DOM
0347: }
0348:
0349: public void contextDeactivated(DesignContext context) {
0350: // No-op: No impact on the rendered HTML DOM
0351: }
0352:
0353: public void eventChanged(DesignEvent event) {
0354: // No-op: No impact on the rendered HTML DOM
0355: }
0356:
0357: public void beanContextActivated(DesignBean designBean) {
0358: // No-op: No impact on the rendered HTML DOM
0359: }
0360:
0361: public void beanContextDeactivated(DesignBean designBean) {
0362: // No-op: No impact on the rendered HTML DOM
0363: }
0364:
0365: // ------- Coalesce Events --------------------------------
0366: // Queue up multiple simultaneous requests by tracking the "top level" bean that must
0367: // be inserted or replaced.
0368: // This is necessary because we often get "bursts" of activity; for example, when a component
0369: // is dragged from a gridpanel to a form, we have a deletion under the grid panel,
0370: // and addition under the form, and possibly multiple "set style property" calls.
0371: // We keep a list of changed beans and new requests are checked
0372: // for siblingness. Clients can also send hints to this code.
0373:
0374: /**
0375: * <p>
0376: * Add the ability to turn off updates for a while. When the suspend parameter is true, suspend
0377: * all updates to the DOM until the method is called again turning off the suspend.
0378: * </p>
0379: *
0380: * <p>
0381: * <b>NOTE:</b>: While updates are suspended, modifying any non-markup beans will NOT cause a
0382: * global refresh (which is usually the case since we don't know how a non-markup bean will
0383: * markup beans since there's no association map). When updates are suspended we're assuming
0384: * that any non-markup beans we are notified of are associated with the markup bean whose
0385: * updates are suspended.
0386: * </p>
0387: *
0388: * <p>
0389: * <b>NOTE:</b>: Calls to the batch facility CANNOT be nested!
0390: * </p>
0391: */
0392: public void setUpdatesSuspended(MarkupDesignBean bean,
0393: boolean suspend) {
0394: if (suspend) {
0395: assert !suspendUpdates;
0396: suspendUpdates = true;
0397: } else {
0398: suspendUpdates = false;
0399: bean = getRenderTarget(bean);
0400: requestChange(bean);
0401: }
0402: }
0403:
0404: /**
0405: * Dispatch the right type of event to the update manager only on the dispatch thread.
0406: * This is necessary because I don't do locking on the request variables like pendingEventType
0407: */
0408: private void requestUpdate(final int type,
0409: final MarkupDesignBean bean) {
0410: if (!SwingUtilities.isEventDispatchThread()) {
0411: inlineEditingUpdate = jsfForm.isInlineEditing();
0412: SwingUtilities.invokeLater(new Runnable() {
0413: public void run() {
0414: requestUpdate(type, bean);
0415: }
0416: });
0417:
0418: return;
0419: }
0420:
0421: switch (type) {
0422: case TYPE_INSERT:
0423: requestInsert(bean);
0424:
0425: break;
0426:
0427: case TYPE_DELETE:
0428: requestDelete(bean);
0429:
0430: break;
0431:
0432: case TYPE_CHANGE:
0433: requestChange(bean);
0434:
0435: break;
0436:
0437: case TYPE_REFRESH:
0438: requestRefresh();
0439:
0440: break;
0441:
0442: default:
0443: assert false;
0444: }
0445: }
0446:
0447: // /**
0448: // * Return true iff a refresh is pending
0449: // */
0450: // public boolean isRefreshPending() {
0451: // return pendingEventType == TYPE_REFRESH;
0452: // }
0453:
0454: /**
0455: * Return true iff any type of change is pending
0456: */
0457: public boolean isUpdatePending() {
0458: return pendingEventType != TYPE_NONE;
0459: }
0460:
0461: /**
0462: * Schedule a request to refresh the whole HTML DOM - this needs to be done after a synch() for
0463: * example. This will coalesce existing events if necessary.
0464: */
0465: public void requestRefresh() {
0466: if (!SwingUtilities.isEventDispatchThread()) {
0467: requestUpdate(TYPE_REFRESH, null);
0468:
0469: return;
0470: }
0471:
0472: if (!timeOutPending) { // XXX what about pending insert?
0473: pendingBean = null;
0474: pendingEventType = TYPE_REFRESH;
0475: timeOutPending = true;
0476: inlineEditingUpdate = jsfForm.isInlineEditing();
0477: SwingUtilities.invokeLater(this );
0478: } else {
0479: // A refresh supercedes all other types of events since if we're going
0480: // to do a global refresh we don't need to worry about individual inserts
0481: // or changes - their new content will be rendered by the global recompute
0482: pendingBean = null;
0483: pendingEventType = TYPE_REFRESH;
0484: }
0485: }
0486:
0487: /**
0488: * <p>
0489: * Plan to change the given bean. This schedules a request to change this bean, unless one is
0490: * already pending; if it is, and the pending request includes the given bean position do
0491: * nothing, if it does not but this request would cover the pending one, change the request,
0492: * and if they are independent, leave both requests in place.
0493: * </p>
0494: *
0495: * <p>
0496: * <b>NOTE:</b> This method must be run on the event dispatch thread!
0497: * </p>
0498: */
0499: public void requestChange(MarkupDesignBean bean) {
0500: if (bean == null) {
0501: requestRefresh();
0502:
0503: return;
0504: }
0505:
0506: if (!SwingUtilities.isEventDispatchThread()) {
0507: requestUpdate(TYPE_CHANGE, bean);
0508:
0509: return;
0510: }
0511:
0512: if (!timeOutPending) {
0513: pendingBean = bean;
0514: pendingEventType = TYPE_CHANGE;
0515: timeOutPending = true;
0516: adjustRequestIfRoot();
0517: inlineEditingUpdate = jsfForm.isInlineEditing();
0518: SwingUtilities.invokeLater(this );
0519: } else {
0520: if ((pendingEventType == TYPE_REFRESH)
0521: || (bean == pendingBean)
0522: || isBelow(bean, pendingBean)) {
0523: // The pending request will already take care of this bean, either because
0524: // it's going to render this or an ancestor node, or do a global refresh,
0525: // so we have nothing to worry about
0526: // In particular, don't change the type to TYPE_CHANGE even if bean==pendingBean;
0527: // if we have a pending insert then we still haven't inserted it so it's not
0528: // a change it's an insert; we'll just be inserting data that is now more
0529: // up to date!
0530: return;
0531: } else if (isBelow(pendingBean, bean)) {
0532: // We've now changed an anscestor of the already scheduled bean -
0533: // so just change the ancestor and the bean will be fine
0534: pendingBean = bean;
0535: pendingEventType = TYPE_CHANGE;
0536: adjustRequestIfRoot();
0537: } else {
0538: // They're in different parts of the tree. For now, just
0539: // re-render (change) their common ancestor. Later I can try to be smart and
0540: // look at their distances and perhaps just re-render each child -
0541: // this will be an advantage if you're just tweaking a couple of
0542: // leaves in a large tree. But it means I can't have a single
0543: // pendingBean field anymore, I need to maintain a list of pending nodes.
0544: pendingEventType = TYPE_CHANGE;
0545: pendingBean = (MarkupDesignBean) getCommonAncestor(
0546: bean, pendingBean);
0547: assert pendingBean != null;
0548: adjustRequestIfRoot();
0549: }
0550: }
0551: }
0552:
0553: /**
0554: * Check if the computed bean is at root level and if so change the
0555: * current request into a refresh
0556: */
0557: private void adjustRequestIfRoot() {
0558: // If the common ancestor has moved up to a fragment or document
0559: // root we need to refresh globally
0560: // refresh
0561: // RaveElement e = (RaveElement)pendingBean.getElement();
0562: // if (e.getRendered() != null) {
0563: // e = (RaveElement)e.getRendered();
0564: // }
0565: Element e = pendingBean.getElement();
0566: Element rendered = MarkupService
0567: .getRenderedElementForElement(e);
0568: if (rendered != null) {
0569: e = rendered;
0570: }
0571:
0572: if ((e == null)
0573: || (e.getParentNode() == null)
0574: || (e.getParentNode().getNodeType() != Node.ELEMENT_NODE)
0575: || e.getTagName().equals(HtmlTag.BODY.name)) {
0576: // We need to refresh the whole document
0577: pendingBean = null;
0578: pendingEventType = TYPE_REFRESH;
0579: }
0580: }
0581:
0582: /**
0583: * <p>
0584: * Plan to insert the given bean. This schedules a request to insert this bean, unless this
0585: * needs to be coalesced with an existing pending event.
0586: * </p>
0587: *
0588: * <p>
0589: * <b>NOTE:</b> This method must be run on the event dispatch thread!
0590: * </p>
0591: */
0592: public void requestInsert(MarkupDesignBean bean) {
0593: if (!SwingUtilities.isEventDispatchThread()) {
0594: requestUpdate(TYPE_INSERT, bean);
0595:
0596: return;
0597: }
0598:
0599: if (!timeOutPending) {
0600: pendingBean = bean;
0601: pendingEventType = TYPE_INSERT;
0602: timeOutPending = true;
0603: adjustRequestIfRoot();
0604: inlineEditingUpdate = jsfForm.isInlineEditing();
0605: SwingUtilities.invokeLater(this );
0606: } else {
0607: if ((pendingEventType == TYPE_REFRESH)
0608: || (bean == pendingBean) || // is bean==pendingBean even possible?
0609: isBelow(bean, pendingBean)) {
0610: // The pending request will already take care of this bean, either because
0611: // it's going to render this or an ancestor node, or do a global refresh,
0612: // so we have nothing to worry about
0613: return;
0614:
0615: /* This should not be possible -- you can't insert a bean
0616: where we already know its descendants!
0617: } else if (isBelow(pendingBean, bean)) {
0618: */
0619: } else {
0620: // They're in different parts of the tree. For now, just
0621: // re-render (change) their common ancestor. Later I can try to be smart and
0622: // look at their distances and perhaps just re-render each child -
0623: // this will be an advantage if you're just tweaking a couple of
0624: // leaves in a large tree. But it means I can't have a single
0625: // pendingBean field anymore, I need to maintain a list of pending nodes.
0626: pendingBean = (MarkupDesignBean) getCommonAncestor(
0627: bean, pendingBean);
0628: pendingEventType = TYPE_CHANGE;
0629: assert pendingBean != null;
0630: adjustRequestIfRoot();
0631: }
0632: }
0633: }
0634:
0635: /**
0636: * <p>
0637: * Plan to remove the given bean. This schedules a request to remove this bean.
0638: * </p>
0639: *
0640: * <p>
0641: * <b>NOTE:</b> This method must be run on the event dispatch thread!
0642: * </p>
0643: */
0644: public void requestDelete(MarkupDesignBean bean) {
0645: if (!SwingUtilities.isEventDispatchThread()) {
0646: requestUpdate(TYPE_DELETE, bean);
0647:
0648: return;
0649: }
0650:
0651: if (!timeOutPending) {
0652: pendingBean = bean;
0653: pendingEventType = TYPE_DELETE;
0654: timeOutPending = true;
0655: adjustRequestIfRoot();
0656: inlineEditingUpdate = jsfForm.isInlineEditing();
0657: SwingUtilities.invokeLater(this );
0658: } else {
0659: if (pendingEventType == TYPE_REFRESH) {
0660: return;
0661: } else if (bean == pendingBean) {
0662: pendingEventType = TYPE_DELETE; // no need to change something we're about to remove!
0663:
0664: return;
0665: } else if (isBelow(bean, pendingBean)) {
0666: // Will be handled by higher-up bean!
0667: return;
0668: } else if (isBelow(pendingBean, bean)) {
0669: pendingEventType = TYPE_DELETE;
0670: pendingBean = bean;
0671: adjustRequestIfRoot();
0672: } else {
0673: // They're in different parts of the tree. For now, just
0674: // re-render (change) their common ancestor. Later I can try to be smart and
0675: // look at their distances and perhaps just re-render each child -
0676: // this will be an advantage if you're just tweaking a couple of
0677: // leaves in a large tree. But it means I can't have a single
0678: // pendingBean field anymore, I need to maintain a list of pending nodes.
0679: pendingBean = (MarkupDesignBean) getCommonAncestor(
0680: bean, pendingBean);
0681: pendingEventType = TYPE_CHANGE;
0682: assert pendingBean != null;
0683: adjustRequestIfRoot();
0684: }
0685: }
0686: }
0687:
0688: /**
0689: * <p>
0690: * The given bean has either had text inserted as a child, or has had its existing text
0691: * changed. Either way the text needs to be reflown. This should be called when we listen for
0692: * the JSP DOM and see a change in nodes of the types Node.TEXT_NODE, Node.CDATA_SECTION_NODE
0693: * or Node.ENTITY_REFERENCE_NODE.
0694: * </p>
0695: *
0696: * @param bean The bean whose text child has been modified
0697: */
0698: public void requestTextUpdate(MarkupDesignBean bean) {
0699: beanChanged(bean);
0700: }
0701:
0702: /**
0703: * Process pending changes
0704: */
0705: public void run() {
0706: timeOutPending = false;
0707: processUpdates();
0708: }
0709:
0710: /**
0711: * Process all the changes that have been queued up
0712: */
0713: private void processUpdates() { // XXX Why is this called when we're editing the JSP doc?
0714:
0715: if ((pendingEventType == TYPE_NONE) || suspendUpdates) {
0716: return;
0717: }
0718:
0719: // XXX #114813 The model could get destroyed inbetween.
0720: if (!jsfForm.isModelValid()) {
0721: return;
0722: }
0723:
0724: MarkupDesignBean bean = pendingBean;
0725: pendingBean = null;
0726:
0727: int type = pendingEventType;
0728: pendingEventType = TYPE_NONE;
0729:
0730: if (DEBUG) {
0731: // DocumentFragment html = jsfForm.getDomProvider().getHtmlDocumentFragment();
0732: // DocumentFragment html = jsfForm.getHtmlDomFragment();
0733: Document html = jsfForm.getHtmlDom();
0734: if (html != null) {
0735: System.out.println("\nBefore delayed updates to bean "
0736: + bean
0737: + " the html is\n"
0738: + InSyncServiceProvider.get().getHtmlStream(
0739: html));
0740: }
0741: }
0742:
0743: if (type == TYPE_CHANGE) {
0744: // Delete old version of component
0745: // Node previouslyRendered = ((RaveElement)bean.getElement()).getRendered();
0746: Node previouslyRendered = MarkupService
0747: .getRenderedElementForElement(bean.getElement());
0748:
0749: // if (!processDelete(bean)) {
0750: // processRefresh();
0751: //// webform.getPane().getPaneUI().modelChanged();
0752: // jsfForm.modelChanged();
0753: //
0754: // return;
0755: // }
0756: //
0757: // // Insert new version of component
0758: // if (!processInsert(bean)) {
0759: // processRefresh();
0760: //// webform.getPane().getPaneUI().modelChanged();
0761: // jsfForm.modelChanged();
0762: //
0763: // return;
0764: // }
0765: List<Element> changedElements = new ArrayList<Element>();
0766: if (!processUpdate(bean, changedElements)) {
0767: processRefresh();
0768: jsfForm.modelChanged();
0769: return;
0770: }
0771:
0772: deleteParent = null;
0773:
0774: // Node rendered = ((RaveElement)bean.getElement()).getRendered();
0775: Node rendered = MarkupService
0776: .getRenderedElementForElement(bean.getElement());
0777:
0778: if (rendered != null) {
0779: // if (rendered != previouslyRendered) {
0780: // Node parent = rendered.getParentNode();
0781: //// PageBox pageBox = webform.getPane().getPaneUI().getPageBox();
0782: //// pageBox.changed(rendered, parent, false);
0783: // jsfForm.nodeChanged(rendered, parent, false);
0784: // } else {
0785: // // We've re-rendered but the bean-reference doesn't point
0786: // // to a new node. This means that the component must have
0787: // // supplied bogus component references when rendering.
0788: // // Note: With wrong element referenced we're not well off anyway -
0789: // // the component won't be selectable!
0790: //// PageBox pageBox = webform.getPane().getPaneUI().getPageBox();
0791: //// webform.getPane().getPaneUI().modelChanged();
0792: // // XXX Now it means, that the node was updated, see the tryUpdateOriginalNode.
0793: // jsfForm.modelChanged();
0794: // }
0795: // Now the original node could be reused (see the tryUpdateOriginalNode).
0796: Node parent = rendered.getParentNode();
0797: if (parent == null) {
0798: // XXX #117192 The node was most probably removed already (e.g. after undo).
0799: // Do nothing.
0800: } else {
0801: jsfForm
0802: .nodeChanged(
0803: rendered,
0804: parent,
0805: changedElements
0806: .toArray(new Element[changedElements
0807: .size()]));
0808: }
0809: } else if (previouslyRendered != null) {
0810: // It was just deleted - for example when you change a component by
0811: // switching off its "rendered" property
0812: //webform.getPane().getPaneUI().modelChanged();
0813: Node parent = previouslyRendered.getParentNode();
0814: // PageBox pageBox = webform.getPane().getPaneUI().getPageBox();
0815: // pageBox.removed(previouslyRendered, parent);
0816: jsfForm.nodeRemoved(previouslyRendered, parent);
0817: }
0818: } else if (type == TYPE_INSERT) {
0819: // Insert new version of component
0820: if (!processInsert(bean)) {
0821: processRefresh();
0822: // webform.getPane().getPaneUI().modelChanged();
0823: jsfForm.modelChanged();
0824:
0825: return;
0826: }
0827:
0828: // Node rendered = ((RaveElement)bean.getElement()).getRendered();
0829: Node rendered = MarkupService
0830: .getRenderedElementForElement(bean.getElement());
0831:
0832: if (rendered != null) {
0833: Node parent = rendered.getParentNode();
0834: // webform.getPane().getPaneUI().getPageBox().inserted(rendered, parent);
0835: jsfForm.nodeInserted(rendered, parent);
0836: }
0837: } else if (type == TYPE_REFRESH) {
0838: // The entire HTML needs to be replaced
0839: processRefresh();
0840:
0841: // if ((webform.getPane() != null) && (webform.getPane().getPaneUI() != null)) {
0842: // webform.getPane().getPaneUI().modelChanged();
0843: // }
0844: jsfForm.modelChanged();
0845: } else if (type == TYPE_DELETE) {
0846: // Delete old version of component
0847: if (!processDelete(bean)) {
0848: processRefresh();
0849: // webform.getPane().getPaneUI().modelChanged();
0850: jsfForm.modelChanged();
0851:
0852: return;
0853: }
0854:
0855: deleteParent = null;
0856:
0857: // Node rendered = ((RaveElement)bean.getElement()).getRendered();
0858: Node rendered = MarkupService
0859: .getRenderedElementForElement(bean.getElement());
0860:
0861: if (rendered != null) {
0862: Node parent = rendered.getParentNode();
0863: // webform.getPane().getPaneUI().getPageBox().removed(rendered, parent);
0864: jsfForm.nodeRemoved(rendered, parent);
0865: }
0866: }
0867:
0868: if (DEBUG) {
0869: // DocumentFragment html = jsfForm.getDomProvider().getHtmlDocumentFragment();
0870: // DocumentFragment html = jsfForm.getHtmlDomFragment();
0871: Document html = jsfForm.getHtmlDom();
0872: if (html != null) {
0873: System.out.println("\nAfter delayed updates to bean "
0874: + bean
0875: + " the html is\n"
0876: + InSyncServiceProvider.get().getHtmlStream(
0877: html));
0878: }
0879: }
0880: }
0881:
0882: private boolean processUpdate(MarkupDesignBean bean,
0883: List<Element> changedElements) {
0884: // Deleting
0885: // if (!processDelete(bean)) {
0886: // return false;
0887: // }
0888:
0889: Element element = bean.getElement();
0890: Node rendered = MarkupService
0891: .getRenderedElementForElement(element);
0892: if (rendered == null) {
0893: return false;
0894: }
0895:
0896: Node parent = rendered.getParentNode();
0897: // Find leftmost node
0898: Node curr = rendered.getPreviousSibling();
0899: while (curr != null) {
0900: if (curr.getNodeType() == Node.ELEMENT_NODE) {
0901: Element xel = (Element) curr;
0902: if (MarkupUnit.getMarkupDesignBeanForElement(xel) == bean) {
0903: rendered = curr;
0904: } else {
0905: break;
0906: }
0907: }
0908: curr = curr.getPreviousSibling();
0909: }
0910:
0911: deleteParent = parent;
0912:
0913: List<Node> originalNodes = new ArrayList<Node>();
0914: Node before = null;
0915: // TODO - I ought to assert here that the parent is in the
0916: // HTML DOM I'm attached to
0917: // Remove all the rendered nodes
0918: while (rendered != null) {
0919: // Node before = rendered.getNextSibling();
0920: before = rendered.getNextSibling();
0921: // parent.removeChild(rendered);
0922: originalNodes.add(rendered);
0923: if (before == null) {
0924: break;
0925: }
0926: // See if I need to delete additional siblings. This is slightly
0927: // tricky because I can't just look at the next sibling. Components
0928: // may separate tags with text nodes that are associated with the
0929: // parent rather than the component being rendered; for example,
0930: // a component renderer may do this:
0931: // writer.startElement(foo, component);
0932: // writer.endElement(foo);
0933: // writer.write("\n");
0934: // writer.startElement(bar, component);
0935: // ....
0936: // Note that we want to delete both foo and bar even though there's
0937: // a text node in between which is not associated with the component
0938: // since it's not within a startElement for "component", it's within
0939: // the element for the parent these children (foo and bar) are being
0940: // added to.
0941: // So, my strategy is to peek ahead and see if the next element
0942: // found is associated with the same DesignBean, and if so, delete
0943: // everything up to it.
0944: if (!moreElements(before, bean)) {
0945: break;
0946: }
0947: rendered = before;
0948: }
0949:
0950: // Inserting
0951: // if (!processInsert(bean)) {
0952: // return false;
0953: // }
0954:
0955: // XXX TODO There is not needed webform here.
0956: // Render the new element
0957: // TODO - this should not necessarily have to involve FacesBeans!
0958: // XXX Do not mark the new nodes as rendered for this case yet.
0959: DocumentFragment df = jsfForm.renderMarkupDesignBean(bean,
0960: false);
0961:
0962: // This seems to be not the correct place.
0963: // // XXX FIXME Is this correct here?
0964: // jsfForm.updateErrorsInComponent();
0965:
0966: if (DEBUG) {
0967: System.out.println("Got fragment from insert render html: "
0968: + InSyncServiceProvider.get().getHtmlStream(df));
0969: }
0970:
0971: List<Node> newNodes = new ArrayList<Node>();
0972:
0973: if (df != null) {
0974: // Insert nodes into the rendered dom
0975: NodeList nl = df.getChildNodes();
0976: int num = nl.getLength();
0977:
0978: if (num == 0) {
0979: // Rendered to nothing. This happens if you switch the Rendered attribute
0980: // to off for example - this comes in as a change request rather than
0981: // as a delete request - but we need to go and update the source element's
0982: // rendered reference since it would now be obsolete in the future
0983: // and we shouldn't attempt to use it
0984: // ((RaveElement)bean.getElement()).setRendered(null);
0985: // XXX FIXME Modifying the data structure!
0986: MarkupService.setRenderedElementForElement(bean
0987: .getElement(), null);
0988:
0989: // XXX #112220 Remove the original nodes (they are not to be rendered now.
0990: for (Node originalNode : originalNodes) {
0991: parent.removeChild(originalNode);
0992: }
0993:
0994: return true;
0995: }
0996:
0997: // Can't remove from a NodeList while iterating over it - causes
0998: // surprises like null siblings. So we need to copy first.
0999: int n = nl.getLength();
1000: Node[] nodes = new Node[n];
1001:
1002: for (int i = 0; i < n; i++) {
1003: nodes[i] = nl.item(i);
1004: }
1005:
1006: for (int i = 0; i < n; i++) {
1007: Node nn = nodes[i];
1008: if (DEBUG) {
1009: if (nn != null) {
1010: System.out.println("next node is: "
1011: + InSyncServiceProvider.get()
1012: .getHtmlStream(nn));
1013: System.out.println("Fragment is now: "
1014: + InSyncServiceProvider.get()
1015: .getHtmlStream(df));
1016: }
1017: }
1018:
1019: if (nn != null) {
1020: assert !isJspNode(nn);
1021: // parent.insertBefore(nn, before);
1022: newNodes.add(nn);
1023: }
1024: }
1025: } else {
1026: ErrorManager.getDefault().notify(
1027: ErrorManager.INFORMATIONAL,
1028: new NullPointerException(
1029: "Null DocumentFragment for JsfForm, jsfForm="
1030: + jsfForm)); // NOI18N
1031: return false;
1032: }
1033:
1034: if (originalNodes.size() == 1 && newNodes.size() == 1) {
1035: Node originalNode = originalNodes.get(0);
1036: Node newNode = newNodes.get(0);
1037: // XXX #110662 Doesn't work for inline editing (the newly created elements need to be used).
1038: // XXX #112216 Improved the check.
1039: try {
1040: if (!inlineEditingUpdate
1041: && tryUpdateOriginalNode(originalNode, newNode,
1042: changedElements)) {
1043: // XXX The original nodes are updated, do not mark the new as rendered, they are not used.
1044: return true;
1045: } else {
1046: // XXX Clear possibly added elements.
1047: changedElements.clear();
1048:
1049: // XXX Mark the newly used nodes as rendered.
1050: MarkupService.markRenderedNodes(df);
1051: parent.replaceChild(newNode, originalNode);
1052: }
1053: } finally {
1054: inlineEditingUpdate = false;
1055: }
1056: } else {
1057: // XXX Mark the newly used nodes as rendered.
1058: MarkupService.markRenderedNodes(df);
1059: for (Node toRemove : originalNodes) {
1060: parent.removeChild(toRemove);
1061: }
1062: for (Node toAdd : newNodes) {
1063: parent.insertBefore(toAdd, before);
1064: }
1065: }
1066:
1067: return true;
1068: }
1069:
1070: private static boolean tryUpdateOriginalNode(Node originalNode,
1071: Node newNode, List<Element> changedElements) {
1072: if (originalNode.getNodeType() != newNode.getNodeType()) {
1073: return false;
1074: }
1075:
1076: // XXX #110845 It doesn't work for elements pointing to external forms (fragments, frames, etc.).
1077: if (originalNode instanceof Element
1078: && isExternalElement((Element) originalNode)) {
1079: return false;
1080: }
1081:
1082: if (originalNode.isEqualNode(newNode)) {
1083: return true;
1084: }
1085:
1086: // Update children
1087: NodeList originalNodeChildren = originalNode.getChildNodes();
1088: NodeList newNodeChildren = newNode.getChildNodes();
1089: if (originalNodeChildren == null) {
1090: if (newNodeChildren != null) {
1091: return false;
1092: }
1093: } else {
1094: if (newNodeChildren == null) {
1095: return false;
1096: }
1097: int originalNodeChildrenSize = originalNodeChildren
1098: .getLength();
1099: int newNodeChildrenSize = newNodeChildren.getLength();
1100: if (originalNodeChildrenSize != newNodeChildrenSize) {
1101: return false;
1102: }
1103:
1104: for (int i = 0; i < originalNodeChildrenSize; i++) {
1105: boolean childOK = tryUpdateOriginalNode(
1106: originalNodeChildren.item(i), newNodeChildren
1107: .item(i), changedElements);
1108: if (!childOK) {
1109: return false;
1110: }
1111: }
1112: }
1113:
1114: // Update value.
1115: String originalValue = originalNode.getNodeValue();
1116: String newValue = newNode.getNodeValue();
1117: if ((originalValue == null && newValue != null)
1118: || (originalValue != null && !originalValue
1119: .equals(newValue))) {
1120: originalNode.setNodeValue(newValue);
1121: }
1122:
1123: // Update attributes.
1124: if (originalNode instanceof Element
1125: && newNode instanceof Element) {
1126: Element originalElement = (Element) originalNode;
1127: Element newElement = (Element) newNode;
1128:
1129: Map<String, Attr> originalMap = getAttributesMap(originalElement);
1130: Map<String, Attr> newMap = getAttributesMap(newElement);
1131:
1132: // Remove redundant attributes.
1133: for (String name : originalMap.keySet()) {
1134: if (newMap.containsKey(name)) {
1135: continue;
1136: }
1137: originalElement.removeAttribute(name);
1138: if (!changedElements.contains(originalElement)) {
1139: changedElements.add(originalElement);
1140: }
1141: }
1142: // Add/update the remaining attributes.
1143: for (String name : newMap.keySet()) {
1144: Attr newAttribute = newMap.get(name);
1145: Attr originalAttribute = originalMap.get(name);
1146:
1147: if (originalAttribute == null) {
1148: originalElement
1149: .setAttributeNode((Attr) newAttribute
1150: .cloneNode(false));
1151: if (!changedElements.contains(originalElement)) {
1152: changedElements.add(originalElement);
1153: }
1154: } else {
1155: String oldAttributeValue = originalAttribute
1156: .getValue();
1157: String newAttributeValue = newAttribute.getValue();
1158: if (newAttributeValue == null) {
1159: if (oldAttributeValue != null) {
1160: originalElement.removeAttribute(name);
1161: if (!changedElements
1162: .contains(originalElement)) {
1163: changedElements.add(originalElement);
1164: }
1165: }
1166: } else {
1167: if (!newAttributeValue
1168: .equals(oldAttributeValue)) {
1169: originalElement
1170: .setAttributeNode((Attr) newAttribute
1171: .cloneNode(false));
1172: if (!changedElements
1173: .contains(originalElement)) {
1174: changedElements.add(originalElement);
1175: }
1176: }
1177: }
1178: }
1179: }
1180: }
1181:
1182: return true;
1183: }
1184:
1185: /** Determines whether the element represents external form (fragment, frame etc.). */
1186: private static boolean isExternalElement(Element element) {
1187: String tagName = element.getTagName();
1188: if (HtmlTag.FSUBVIEW.name.equals(tagName)
1189: || HtmlTag.JSPINCLUDE.name.equals(tagName)
1190: || HtmlTag.JSPINCLUDEX.name.equals(tagName)
1191: || HtmlTag.IFRAME.name.equals(tagName)) {
1192: return true;
1193: }
1194: return false;
1195: }
1196:
1197: private static Map<String, Attr> getAttributesMap(Element element) {
1198: NamedNodeMap attributes = element.getAttributes();
1199: Map<String, Attr> attributesMap = new HashMap<String, Attr>();
1200: int size = attributes == null ? 0 : attributes.getLength();
1201: for (int i = 0; i < size; i++) {
1202: Node attr = attributes.item(i);
1203: if (attr instanceof Attr) {
1204: Attr attribute = (Attr) attr;
1205: attributesMap.put(attribute.getName(), attribute);
1206: }
1207: }
1208: return attributesMap;
1209: }
1210:
1211: /**
1212: * Check whether the given child is below the given parent.
1213: *
1214: * @param child The assumed child
1215: * @param parent The assumed parent
1216: *
1217: * @return true iff child is the same as parent or a descendant of the parent
1218: */
1219: public boolean isBelow(DesignBean child, DesignBean parent) {
1220: while (child != null) {
1221: if (child == parent) {
1222: return true;
1223: }
1224:
1225: child = child.getBeanParent();
1226: }
1227:
1228: return false;
1229: }
1230:
1231: /**
1232: * Given two DesignBeans known to not be different and known to not be ancestors or descendants
1233: * of each other, return a common ancestor DesignBean parent to both beans.
1234: *
1235: * @param a A DesignBean to locate a common ancestor with <code>b</code> for
1236: * @param b A DesignBean to locate a common ancestor with <code>a</code> for
1237: *
1238: * @return A common ancestor of <code>a</code> and <code>b</code>
1239: */
1240: public DesignBean getCommonAncestor(DesignBean a, DesignBean b) {
1241: a = a.getBeanParent();
1242:
1243: while (a != null) {
1244: if (isBelow(b, a)) {
1245: return a;
1246: }
1247:
1248: a = a.getBeanParent();
1249: }
1250:
1251: return null;
1252: }
1253:
1254: // ------------ Methods to actually modify the HTML DOM ---------------------------
1255:
1256: /**
1257: * Render the given source JSP element and insert its HTML content in the right place in the
1258: * HTML rendered DOM
1259: *
1260: * @param bean The bean to be rendered and inserted into the DOM
1261: *
1262: * @return true if everything works, otherwise false
1263: */
1264: private boolean processInsert(MarkupDesignBean bean) {
1265: // RaveElement element = (RaveElement)bean.getElement();
1266: Element element = bean.getElement();
1267:
1268: // Compute JSP DOM positions in the rendered markup
1269: Node parent = element.getParentNode();
1270: Node before = element.getNextSibling();
1271:
1272: // Compute HTML DOM corresponding positions
1273: if (before != null) {
1274: // if (before instanceof RaveRenderNode) {
1275: // before = ((RaveRenderNode)before).getRenderedNode();
1276: // } else {
1277: // before = null;
1278: //
1279: // // TODO - how do we handle this? There could be whitespace text nodes
1280: // // in the JSP source dom which do not get rendered in the HTML...
1281: // // XXX Should I modify XhtmlText to do source<->model mapping too?
1282: // // ... fall through ...
1283: // }
1284: before = MarkupService.getRenderedNodeForNode(before);
1285: }
1286:
1287: if (before != null) {
1288: parent = before.getParentNode();
1289: } else if (deleteParent != null) {
1290: parent = deleteParent;
1291: } else {
1292: // Finding the parent could be tricky - but this will work because we walk
1293: // up past encodes-children components. For example, let's say you added a
1294: // <h:commandButton> component to a <h:gridPanel>.
1295: // You probably expect this to be inserted inside a <td> in the grid panel -
1296: // but the HTML element for the <h:gridPanel> is going to be the topmost
1297: // <table> tag! But this is precisely why we skip up and re-render all
1298: // encodes-children components like gridpanel and data table. So you only
1299: // end up with cases where a parent is for example "<h:form>" and the
1300: // rendered dom parent insert position will be "<form>".
1301: // while (parent instanceof RaveRenderNode) {
1302: // Node renderedParent = ((RaveRenderNode)parent).getRenderedNode();
1303: while (parent != null) {
1304: Node renderedParent = MarkupService
1305: .getRenderedNodeForNode(parent);
1306:
1307: if (renderedParent != null) {
1308: parent = renderedParent;
1309:
1310: break;
1311: }
1312:
1313: parent = parent.getParentNode();
1314: }
1315:
1316: if (parent == null) {
1317: // You've inserted at the root of the document -- we should not allow that.
1318: // assert false : "Can't insert at document root";
1319: ErrorManager.getDefault().notify(
1320: ErrorManager.INFORMATIONAL,
1321: new NullPointerException(
1322: "There is no rendered parent for the bean, insert not possible, bean="
1323: + bean)); // NOI18N
1324: return false;
1325: }
1326: }
1327:
1328: // This should not be the case, but we REALLY have to make sure this does not happen
1329: // I can remove this check once I've fully debugged my new code
1330: if ((parent != null) && isJspNode(parent)) {
1331: // XXX What this check supposed to mean?
1332: ErrorManager.getDefault().notify(
1333: ErrorManager.INFORMATIONAL,
1334: new IllegalStateException(
1335: "Parent may not be a jsp node, parent="
1336: + parent)); // NOI18N
1337: return false;
1338: }
1339:
1340: // XXX TODO There is not needed webform here.
1341: // FileObject markupFile = jsfForm.getFacesModel().getMarkupFile();
1342: // Render the new element
1343: // TODO - this should not necessarily have to involve FacesBeans!
1344: // DocumentFragment df = FacesSupport.renderHtml(markupFile, bean, !CssBox.noBoxPersistence);
1345: // DocumentFragment df = InSyncServiceProvider.get().renderHtml(markupFile, bean);
1346: // FacesModel facesModel = jsfForm.getFacesModel();
1347: // DocumentFragment df = FacesPageUnit.renderHtml(facesModel, bean);
1348: DocumentFragment df = jsfForm.renderMarkupDesignBean(bean);
1349:
1350: // XXX FIXME Is this correct here?
1351: // webform.updateErrorsInComponent();
1352: jsfForm.updateErrorsInComponent();
1353:
1354: if (DEBUG) {
1355: System.out.println("Got fragment from insert render html: "
1356: + InSyncServiceProvider.get().getHtmlStream(df));
1357: }
1358:
1359: if (df != null) {
1360: // Insert nodes into the rendered dom
1361: NodeList nl = df.getChildNodes();
1362: int num = nl.getLength();
1363:
1364: if (num == 0) {
1365: // Rendered to nothing. This happens if you switch the Rendered attribute
1366: // to off for example - this comes in as a change request rather than
1367: // as a delete request - but we need to go and update the source element's
1368: // rendered reference since it would now be obsolete in the future
1369: // and we shouldn't attempt to use it
1370: // ((RaveElement)bean.getElement()).setRendered(null);
1371: // XXX FIXME Modifying the data structure!
1372: MarkupService.setRenderedElementForElement(bean
1373: .getElement(), null);
1374:
1375: return true;
1376: }
1377:
1378: // Can't remove from a NodeList while iterating over it - causes
1379: // surprises like null siblings. So we need to copy first.
1380: int n = nl.getLength();
1381: Node[] nodes = new Node[n];
1382:
1383: for (int i = 0; i < n; i++) {
1384: nodes[i] = nl.item(i);
1385: }
1386:
1387: for (int i = 0; i < n; i++) {
1388: Node nn = nodes[i];
1389:
1390: if (DEBUG) {
1391: if (nn != null) {
1392: System.out.println("next node is: "
1393: + InSyncServiceProvider.get()
1394: .getHtmlStream(nn));
1395: System.out.println("Fragment is now: "
1396: + InSyncServiceProvider.get()
1397: .getHtmlStream(df));
1398: }
1399: }
1400:
1401: if (nn != null) {
1402: assert !isJspNode(nn);
1403:
1404: parent.insertBefore(nn, before);
1405: }
1406: }
1407: } else {
1408: // assert false; // can this happen?
1409: ErrorManager.getDefault().notify(
1410: ErrorManager.INFORMATIONAL,
1411: // new NullPointerException("Null DocumentFragment for FacesModel, facesModel=" + facesModel)); // NOI18N
1412: new NullPointerException(
1413: "Null DocumentFragment for JsfForm, jsfForm="
1414: + jsfForm)); // NOI18N
1415: return false;
1416: }
1417:
1418: return true;
1419: }
1420:
1421: /**
1422: * Return true iff the given node is in the source JSP node
1423: */
1424: private boolean isJspNode(Node n) {
1425: // Determine if this node is in a DocumentFragment which means
1426: // it's read only
1427: while (n.getParentNode() != null) {
1428: n = n.getParentNode();
1429: }
1430:
1431: // We already have the isRendered property on XhtmlElements -- this should
1432: // agree with this actual parent-walking check. Assuming they are, I can
1433: // leave the faster isRendered check in the product once I feel confident
1434: // about this.
1435: // assert (!(n instanceof RaveElement)) ||
1436: // (((RaveElement)n).isRendered() == (n != webform.getDom()));
1437: // XXX One can't get out of this mess, what is actually supposed to be checked?
1438: // if (n instanceof Element
1439: // && (!MarkupService.isRenderedNode(n) != (n == webform.getDom()))) {
1440: // ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL,
1441: // new IllegalStateException(); // XXX What to log?
1442: // }
1443:
1444: return n == jsfForm.getJspDom();
1445: }
1446:
1447: /**
1448: * Remove the rendered HTML for the given bean from the HTML rendered DOM
1449: *
1450: * @param bean The bean whose markup should be deleted from the HTML DOM
1451: *
1452: * @return true if everything works, otherwise false
1453: */
1454: private boolean processDelete(MarkupDesignBean bean) {
1455: // RaveElement element = (RaveElement)bean.getElement();
1456: // Node rendered = element.getRendered();
1457: Element element = bean.getElement();
1458: Node rendered = MarkupService
1459: .getRenderedElementForElement(element);
1460:
1461: if (rendered == null) {
1462: return false;
1463: }
1464:
1465: Node parent = rendered.getParentNode();
1466:
1467: // Find leftmost node
1468: Node curr = rendered.getPreviousSibling();
1469:
1470: while (curr != null) {
1471: if (curr.getNodeType() == Node.ELEMENT_NODE) {
1472: // RaveElement xel = (RaveElement)curr;
1473: Element xel = (Element) curr;
1474:
1475: // if (xel.getDesignBean() == bean) {
1476: // if (InSyncServiceProvider.get().getMarkupDesignBeanForElement(xel) == bean) {
1477: if (MarkupUnit.getMarkupDesignBeanForElement(xel) == bean) {
1478: rendered = curr;
1479: } else {
1480: break;
1481: }
1482: }
1483:
1484: curr = curr.getPreviousSibling();
1485: }
1486:
1487: deleteParent = parent;
1488:
1489: // TODO - I ought to assert here that the parent is in the
1490: // HTML DOM I'm attached to
1491: // Remove all the rendered nodes
1492: while (rendered != null) {
1493: Node before = rendered.getNextSibling();
1494: parent.removeChild(rendered);
1495:
1496: //assert deleted != null;
1497: if (before == null) {
1498: break;
1499: }
1500:
1501: // See if I need to delete additional siblings. This is slightly
1502: // tricky because I can't just look at the next sibling. Components
1503: // may separate tags with text nodes that are associated with the
1504: // parent rather than the component being rendered; for example,
1505: // a component renderer may do this:
1506: // writer.startElement(foo, component);
1507: // writer.endElement(foo);
1508: // writer.write("\n");
1509: // writer.startElement(bar, component);
1510: // ....
1511: // Note that we want to delete both foo and bar even though there's
1512: // a text node in between which is not associated with the component
1513: // since it's not within a startElement for "component", it's within
1514: // the element for the parent these children (foo and bar) are being
1515: // added to.
1516: // So, my strategy is to peek ahead and see if the next element
1517: // found is associated with the same DesignBean, and if so, delete
1518: // everything up to it.
1519: if (!moreElements(before, bean)) {
1520: break;
1521: }
1522:
1523: rendered = before;
1524: }
1525:
1526: return true;
1527: }
1528:
1529: private boolean moreElements(Node node, MarkupDesignBean bean) {
1530: while (node != null) {
1531: // if (node instanceof RaveElement && (((RaveElement)node).getDesignBean() == bean)) {
1532: if ((node instanceof Element)
1533: // && (InSyncServiceProvider.get().getMarkupDesignBeanForElement((Element)node) == bean)) {
1534: && (MarkupUnit
1535: .getMarkupDesignBeanForElement((Element) node) == bean)) {
1536: return true;
1537: }
1538:
1539: node = node.getNextSibling();
1540: }
1541:
1542: return false;
1543: }
1544:
1545: private void processRefresh() {
1546: // XXX Revise this method, it might be meaningless here now.
1547: // webform.clearHtml();
1548: // jsfForm.getDomProvider().clearHtml();
1549: jsfForm.clearHtml();
1550:
1551: // Intentional side-effect -- cause re-generation of html dom
1552: // DocumentFragment html = jsfForm.getDomProvider().getHtmlDocumentFragment();
1553: // DocumentFragment html = jsfForm.getHtmlDomFragment();
1554: // XXX This is a very ugly hack.
1555: jsfForm.getHtmlBody();
1556:
1557: if (DEBUG) {
1558: System.err.println("Refresh: Got new HTML: " + // NOI18N
1559: InSyncServiceProvider.get().getHtmlStream(
1560: jsfForm.getHtmlDom()));
1561: }
1562:
1563: // webform.getPane().hideCaret();
1564: // webform.getSelection().syncCaret();
1565: // webform.setGridMode(webform.getDocument().isGridMode()); // XXX
1566: jsfForm.updateGridMode();
1567: }
1568:
1569: /**
1570: * Given a bean in the DOM, duplicate its associated node tree and return this in a new
1571: * DocumentFragment. Additionally, the returned fragment will be marked as the source nodes
1572: * for the render fragment.
1573: */
1574: public DocumentFragment createSourceFragment(MarkupDesignBean bean) {
1575: //return FacesSupport.renderHtml(webform, bean);
1576: // RaveElement element = (RaveElement)bean.getElement();
1577: // Node rendered = element.getRendered();
1578: Element element = bean.getElement();
1579: Node rendered = MarkupService
1580: .getRenderedElementForElement(element);
1581:
1582: if (rendered == null) {
1583: return null;
1584: }
1585:
1586: org.w3c.dom.Document doc = jsfForm.getJspDom();
1587: DocumentFragment fragment = doc.createDocumentFragment();
1588:
1589: // Duplicate and move all the nodes in the HTML DOM into the new fragment
1590: while (rendered != null) {
1591: Node before = rendered.getNextSibling();
1592: Node n = doc.importNode(rendered, true);
1593: fragment.appendChild(n);
1594: // ((RaveRenderNode)n).setJspx(false); // XXX Gotta do this recursively. How?
1595: MarkupService.setJspxNode(n, false);
1596:
1597: MarkupService.markRendered(n, rendered);
1598:
1599: if (before == null) {
1600: break;
1601: }
1602:
1603: // if (before instanceof RaveRenderNode) {
1604: // RaveRenderNode rn = (RaveRenderNode)before;
1605: //
1606: // if (rn.getSourceNode() != element) {
1607: // break;
1608: // }
1609: // } else {
1610: // break;
1611: // }
1612: Node sourceNode = MarkupService
1613: .getSourceNodeForNode(before);
1614: if (sourceNode != null && sourceNode != element) {
1615: break;
1616: }
1617:
1618: rendered = before;
1619: }
1620:
1621: return fragment;
1622: }
1623:
1624: /**
1625: * Locate the most distant renders-children ancestor of this bean (if any) and return it -
1626: * otherwise return the bean itself. This is used to compute the leaf-most node in the tree we
1627: * need to re-render the HTML for. For example, think of a datagrid containin a single command
1628: * button. When this button is updated, the parent will re-generate this button in multiple
1629: * table cells. Thus we need to find any containing datagrid (or more generally, any component
1630: * which has rendersChildren set, since these can potentially replicate the children.)
1631: */
1632: private MarkupDesignBean getRenderTarget(MarkupDesignBean bean) {
1633: DesignBean curr = bean;
1634:
1635: for (; curr != null; curr = curr.getBeanParent()) {
1636: if (!(curr instanceof MarkupDesignBean)) {
1637: break;
1638: }
1639:
1640: MarkupDesignBean b = (MarkupDesignBean) curr;
1641: Object instance = curr.getInstance();
1642:
1643: if (instance instanceof UIComponent) {
1644: // ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader();
1645: // try {
1646: // Thread.currentThread().setContextClassLoader(InSyncServiceProvider.get().getContextClassLoader(b));
1647: // See #6475512. Not needed anymore.
1648: // try {
1649: if (InSyncServiceProvider
1650: .isComponentRendersChildren(curr)) {
1651: bean = b;
1652:
1653: // Can't break here - there could be an outer
1654: // renders-children parent
1655: }
1656: // } catch (NullPointerException ex) { // XXX Catching runtime exception is a wrong solution.
1657: // // XXX #6465131 Temporary workaround to find out what is going on, see the bug.
1658: // IllegalStateException ise = new IllegalStateException(
1659: // "NPE occured when called UIComponent.getRendersChildren() on instance=" + instance, ex); // NOI18N
1660: // ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, ise);
1661: // // XXX #6475481 Force refresh in that case.
1662: // return null;
1663: // }
1664: // } finally {
1665: // Thread.currentThread().setContextClassLoader(oldContextClassLoader);
1666: // }
1667: }
1668: // Events in the <head> section typically implies a re-render
1669: // on the whole document - e.g. background color changes, or
1670: // style sheet changes, or character encoding changes...
1671: // RaveElement e = (RaveElement)b.getElement();
1672: // if (e.getRendered() != null) {
1673: // e = (RaveElement)e.getRendered();
1674: // }
1675: Element e = b.getElement();
1676: Element rendered = MarkupService
1677: .getRenderedElementForElement(e);
1678: if (rendered != null) {
1679: e = rendered;
1680: }
1681:
1682: String tag = e.getTagName();
1683:
1684: if (tag.equals(HtmlTag.HEAD.name)) {
1685: return null; // Force refresh
1686: } else if ((curr == bean) && tag.equals(HtmlTag.BODY.name)) {
1687: // Refresh document if we're trying to change the body itself, not some
1688: // child of it
1689: return null;
1690: } else if ((curr == bean)
1691: && ((e.getParentNode() == null) || (e
1692: .getParentNode().getNodeType() != Node.ELEMENT_NODE))) {
1693: return null;
1694: }
1695: }
1696:
1697: return bean;
1698: }
1699:
1700: // -------- Source DOM listening ---------------------------------------------------
1701: // We need to listen to the DOM for changes because updates like text modifications
1702: // aren't communicated to us!
1703: public void handleEvent(final org.w3c.dom.events.Event e) {
1704: // I seem to get lots of useless mutation events - old value = new value
1705: // if (e instanceof org.w3c.dom.events.MutationEvent) {
1706: // org.w3c.dom.events.MutationEvent me =
1707: // (org.w3c.dom.events.MutationEvent)e;
1708: // String old = me.getPrevValue();
1709: // String nw = me.getNewValue();
1710: // if (((old != null) && (nw != null) && (old.equals(nw)))) {
1711: // if (debugevents) {
1712: // Log.err.log("Event " + e + " on " + e.getTarget() + ": ignoring since prev-value==new-value");
1713: // }
1714: // return;
1715: // }
1716: // }
1717: // if (debugevents) {
1718: // Log.err.log("View.handleEvent(" + e + ")");
1719: // Log.err.log("type = " + e.getType());
1720: // Log.err.log("phase = " + e.getEventPhase());
1721: // Log.err.log("bubbles = " + e.getBubbles());
1722: // Log.err.log("target = " + e.getTarget());
1723: // if (e.getTarget() != null) {
1724: // Log.err.log("target.parent = " + ((org.w3c.dom.Node)e.getTarget()).getParentNode());
1725: // if (((org.w3c.dom.Node)e.getTarget()).getParentNode() != null) {
1726: // Log.err.log("target.parent.parent = " + ((org.w3c.dom.Node)e.getTarget()).getParentNode().getParentNode());
1727: // }
1728: // }
1729: // Log.err.log("target = " + e.getTarget().getClass().getName());
1730: // Log.err.log("currtarget = " + e.getCurrentTarget());
1731: // Log.err.log("currtarget = " + e.getCurrentTarget().getClass().getName());
1732: // if (e instanceof org.w3c.dom.events.MutationEvent) {
1733: // org.w3c.dom.events.MutationEvent me =
1734: // (org.w3c.dom.events.MutationEvent)e;
1735: // Log.err.log("getAttrName = " + me.getAttrName());
1736: // Log.err.log("attrchange = " + me.getAttrChange());
1737: // Log.err.log("newvalue = " + me.getNewValue());
1738: // Log.err.log("prevvalue = " + me.getPrevValue());
1739: // Log.err.log("relatednode = " + me.getRelatedNode());
1740: // if (me.getRelatedNode() != null) {
1741: // Log.err.log("relatednode.parent = " + me.getRelatedNode().getParentNode());
1742: // }
1743: // }
1744: // }
1745: if (e.getType().equals(MarkupUnit.DOM_DOCUMENT_REPLACED)) {
1746: updateDomListeners();
1747:
1748: // Ensure that the caret is in the new DOM
1749: // DesignerPane pane = webform.getPane();
1750: //
1751: // if (pane != null) {
1752: // if (pane.getCaret() != null) {
1753: // pane.getCaret().detachDom();
1754: //
1755: // //pane.setCaret(null);
1756: // }
1757: //
1758: // // pane.showCaretAtBeginning();
1759: // }
1760: jsfForm.documentReplaced();
1761:
1762: return;
1763: }
1764:
1765: Node node = (org.w3c.dom.Node) e.getTarget();
1766:
1767: // Text node or entity node changes should get translated
1768: // into a change event on their surrounding element...
1769: // XXX I could possibly handle to rebreak only
1770: // the LineBreakGroup.... That would save work -ESPECIALLY-
1771: // for text right within the <body> tag... but optimize that
1772: // later
1773: if (!(node instanceof Element)
1774: || ((Element) node).getTagName()
1775: .equals(HtmlTag.BR.name)) { // text, cdata, entity, ...
1776: node = node.getParentNode();
1777:
1778: if (node instanceof Element) {
1779: // MarkupDesignBean bean = ((RaveElement)node).getDesignBean();
1780: MarkupDesignBean bean = InSyncServiceProvider.get()
1781: .getMarkupDesignBeanForElement((Element) node);
1782:
1783: if (bean != null) {
1784: requestTextUpdate((MarkupDesignBean) bean);
1785:
1786: return;
1787: }
1788: }
1789: }
1790: }
1791:
1792: // -------- Listener Registration ---------------------------------------------------
1793: private void registerDomListeners() {
1794: if (currentDOM instanceof EventTarget) {
1795: EventTarget target = (org.w3c.dom.events.EventTarget) currentDOM;
1796: target.addEventListener(
1797: MutationEventImpl.DOM_ATTR_MODIFIED, this , false);
1798:
1799: /* This event seems to be redundant.
1800: target.addEventListener(MutationEventImpl.DOM_SUBTREE_MODIFIED, this, false);
1801: */
1802: target.addEventListener(
1803: MutationEventImpl.DOM_NODE_INSERTED, this , false);
1804: target.addEventListener(
1805: MutationEventImpl.DOM_NODE_INSERTED_INTO_DOCUMENT,
1806: this , false);
1807: target.addEventListener(MutationEventImpl.DOM_NODE_REMOVED,
1808: this , false);
1809: target.addEventListener(
1810: MutationEventImpl.DOM_NODE_REMOVED_FROM_DOCUMENT,
1811: this , false);
1812: target.addEventListener(
1813: MutationEventImpl.DOM_CHARACTER_DATA_MODIFIED,
1814: this , false);
1815:
1816: target.addEventListener(MarkupUnit.DOM_DOCUMENT_REPLACED,
1817: this , false);
1818: }
1819: }
1820:
1821: public void unregisterDomListeners() {
1822: if (currentDOM instanceof EventTarget) {
1823: EventTarget target = (org.w3c.dom.events.EventTarget) currentDOM;
1824: target.removeEventListener(
1825: MutationEventImpl.DOM_ATTR_MODIFIED, this , false);
1826:
1827: /* This event seems to be redundant.
1828: target.removeEventListener(MutationEventImpl.DOM_SUBTREE_MODIFIED, this, false);
1829: */
1830: target.removeEventListener(
1831: MutationEventImpl.DOM_NODE_INSERTED, this , false);
1832: target.removeEventListener(
1833: MutationEventImpl.DOM_NODE_INSERTED_INTO_DOCUMENT,
1834: this , false);
1835: target.removeEventListener(
1836: MutationEventImpl.DOM_NODE_REMOVED, this , false);
1837: target.removeEventListener(
1838: MutationEventImpl.DOM_NODE_REMOVED_FROM_DOCUMENT,
1839: this , false);
1840: target.removeEventListener(
1841: MutationEventImpl.DOM_CHARACTER_DATA_MODIFIED,
1842: this , false);
1843:
1844: target.removeEventListener(
1845: MarkupUnit.DOM_DOCUMENT_REPLACED, this , false);
1846: }
1847: }
1848:
1849: /**
1850: * The underlying DOM which manages the document content has changed. This might for example
1851: * happen if the document source is edited and reparsed.
1852: */
1853: public void updateDomListeners() {
1854: boolean wasDifferent = false;
1855:
1856: if (currentDOM != jsfForm.getJspDom()) {
1857: wasDifferent = true;
1858:
1859: if (currentDOM != null) {
1860: unregisterDomListeners();
1861: }
1862:
1863: currentDOM = jsfForm.getJspDom();
1864:
1865: if (currentDOM == null) {
1866: return;
1867: }
1868: }
1869:
1870: // fireDomChangedUpdate();
1871: if (wasDifferent) {
1872: registerDomListeners();
1873: }
1874:
1875: // webform.setGridMode(isGridMode());
1876: }
1877:
1878: /** Impl <code>UpdateSuspender</code>. */
1879: public void setSuspended(MarkupDesignBean markupDesignBean,
1880: boolean suspend) {
1881: setUpdatesSuspended(markupDesignBean, suspend);
1882: }
1883: }
|