/**
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.waveprotocol.wave.client.editor.content.paragraph;
import com.google.gwt.core.client.JavaScriptException;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Node;
import org.waveprotocol.wave.client.editor.content.ContentElement;
import org.waveprotocol.wave.client.editor.content.ContentView;
import org.waveprotocol.wave.model.document.util.Point;
/**
* IE specifics for {@link ParagraphHelper}
*
* Unlike Webkit and Gecko, IE does not add regular DOM to
* a paragraph to render it correctly in an editor, but rather
* sets some otherwise inaccessible flag on the <p> element.
* Consider this editable div:
*
* <div contentEditable><p></p></div>
*
* You can test that the <p> has the magic flag: if set, then
* the div's innerHTML is "<p> </p>". Otherwise it is "<p></p>".
* In both cases, the <p> element itself has an outerHTML of "<p></p>"
* and reports having no children. That is, we need to inspect
* the innerHTML of the paragraphs editable parent in order to
* check if the flag is set on the parent; the paragraph itself
* doesn't tell us the difference.
*
* Note that adding a text node with a single char is *not*
* the same as setting the magic flag. In particular adding a
* real makes it possible to move the caret before +
* after the char, which is not the case for an empty paragraph
* with the magic flag set.
*
* Deep cloning a <p> preserves the magic flag, even if the node
* being cloned is not attached to the document.
*
* The flag appears to work for all block elements, including
* custom ones like <title style="display:block">. (However
* the test above appears only to work for non-custom HTML block
* elements.)
*
* IE's native editor:
*
* -- Sets the flag when creating new, empty paragraphs.
* -- Sets the flag when the user deletes the last char in the paragraph.
* -- Unsets the flag when the user types in and empty paragraph (or at
* least we have found no evidence the flag is still there).
* -- Unsets the flag when our code appendChilds a node to an empty
* paragraph (or at least we have found no evidence the flag is still there).
*
* but IE:
*
* -- Does *not* set the flag when we removeChild the last node
* from a paragraph.
* -- Does *not* set the flag when we createElement an empty paragraph
* and insert it into the editor.
*
* Thus, we need only add helper code to set the flag on empty
* paragraphs our code creates.
*
*/
public class ParagraphHelperIE extends ParagraphHelper {
/**
* We need these only for the magic flag test in
* {@link #assertHealthy(ContentElement)}
*/
private static Element testMarker = null;
private static Element testDiv = null;
@Override
public Node getEndingNodelet(Element paragraph) {
return null;
}
@Override
public void onEmpty(Element nodelet) {
openEmptyParagraph(nodelet);
}
/**
* Conditionally check if we need to do the magic trick
*/
@Override
public void onRepair(ContentElement paragraph) {
if (paragraph.getFirstChild() == null) {
onEmpty(paragraph.getImplNodelet());
}
}
/**
* {@inheritDoc}
*/
@Override
public void assertHealthy(ContentElement paragraph) {
ContentView renderedContent = paragraph.getRenderedContentView();
if (renderedContent.getFirstChild(paragraph) == null) {
Element nodelet = paragraph.getImplNodelet();
assert 0 == nodelet.getChildCount() : "Empty Paragraph nodelet should have no chldren";
// Magic flag test
// TODO(user): can we find a test for this also for the <title> element?
// NOTE(user): temporarily disabling this, as it messes with setting
// carets in the paragraph
if (false) {
if (testDiv == null) {
// Construct test elements
testMarker = Document.get().createDivElement();
testDiv = Document.get().createDivElement();
testDiv.setAttribute("contentEditable", "true");
}
// Stick nodelet in test editable div, grab the div's inner html,
// and place nodelet back where it came from. We need this because
// only the innerHTML of an editable parent of the paragraph
// differs based on the flag we are testing.
nodelet.getParentNode().insertBefore(testMarker, nodelet);
testDiv.appendChild(nodelet);
String innerHTML = testDiv.getInnerHTML().toLowerCase();
testMarker.getParentNode().insertBefore(nodelet, testMarker);
testMarker.removeFromParent();
// Now test the innerHTML we got above
assert "<p> </p>".equals(innerHTML) :
"Empty Paragraph nodelet should have magic flag set";
}
}
super.assertHealthy(paragraph);
}
/**
* IE-specific trick to keep an empty paragraph "open" (i.e. prevent it from
* collapsing to zero height). See class javadoc for details.
*
* Since we know of no direct way to set the magic flag, we mimic the user
* deleting the last char.
*
* TODO(user): can we get away with only doing this when creating new, empty
* paragraphs? It seems our own emptying should already set the magic flag,
* but for some reason it doesn't seem to happen...
*
* NB(user): The flag only gets set when the nodelet is attached to the
* editor. See also ImplementorImpl.Helper#beforeImplementation(Element)
*/
public static void openEmptyParagraph(Element nodelet) {
// Add + delete an arbitrary char from the paragraph
// TODO(user): why does this throw exception in <caption> elements?
try {
Point<Node> point = IeNodeletHelper.beforeImplementation(nodelet);
nodelet.setInnerHTML("x");
nodelet.setInnerHTML("");
IeNodeletHelper.afterImplementation(nodelet, point);
} catch (JavaScriptException t) {}
}
}