How to replace element with my GWT widget? - gwt

Is it possible to replace known html element with my widget component? (Emphasis on the word 'replace', I don't want to put the widget in that element. :)
<body>
<img />
<div />
<a id="tmpEl" />
...
<img />
</body>
would become
<body>
<img />
<div />
<div class="gwt-panel">...</div>
...
<img />
</body>
I tried something like this...
tmpEl.getParentElement().replaceChild(myPanel.getElement(), tmpEl);
...but the resulting DOM elements were 'deaf', i.e. they did not receive click events. (To make this work I would probably have to call RootPanel.get().adopt(widget), but that method is not accessible.)
For a second I thought HTMLPanel.addAndReplaceElement could be the answer, but that only works when your 'placeholder' element is (direct) child of HTMLPanel widget. Which is obviously not my case. :(
Note please that I only know id of that element, I'm not creating it. Simply put: I need exactly what the question says.
As for 'DOM manipulation at higher level': I will happily manipulate the DOM at highest possible level if it lets me place widget instead of that placeholder element.

It seems that calling widget.onAttach() after inserting widget into DOM does the trick.
class MyWidget extends Composite
{
...
public void attach()
{
/* Widget.onAttach() is protected
*/
onAttach();
/* mandatory for all widgets without parent widget
*/
RootPanel.detachOnWindowClose(this);
}
}
tmpEl.getParentElement().replaceChild(myWidget.getElement(), tmpEl);
myWidget.attach();
Credit goes to André at Google Web Toolkit group.
I still wonder though why there is no RootPanel.addAndReplaceElement(Widget, Element), similar to HTMLPanel.addAndReplaceElement(Widget, Element).

The solution is probably not so much different from what Igor suggested. I'd write something like this:
RootPanel rootPanel = RootPanel.get();
Element anchorElement = DOM.getElementById("tmpEl");
Anchor anchor = Anchor.wrap(anchorElement);
rootPanel.remove(anchor);
rootPanel.insert(new HTML("<div class='gwt-panel'>...</div>", 0);

I agree with markovuksanovic - you should consider DOM manipulation on a "higher level". For example, the functionality you need is provided via the InsertPanel interface, which FlowPanel implements:
FlowPanel mainPanel = new FlowPanel();
Hyperlink link = new Hyperlink("Something cool");
mainPanel.add(link);
mainPanel.add(new Label("Bla bla"));
// Now we want to replace the first element of the FlowPanel with a TextBox
// We can do that by its index...
mainPanel.remove(0);
// ...or Widget instance
mainPanel.remove(link);
// Now we add a TextBox at the beginning
mainPanel.add(new TextBox(), 0);
As you can see, this code is much more readable (at least to me), you don't manipulate the DOM directly (referencing via ids and such). Of course, there are places where direct DOM manipulation is beneficial (optimizing, most notably), but for the most part, I'd avoid juggling Elements, ids and such :) (at least in GWT)

I'm doing something pretty similar. I'm reading the contents of a <div>; using the elements found in the <div> to build an animated menu which then replaces the original content. The animated menu is a widget.
It seems to work...
// DisclosurePanel is a widget
DisclosurePanel accordion_panel = processAccordion(accordionElement);
// accordionElement is found in the DOM (it's a com.google.gwt.user.client.Element)
// clear what was there
accordionElement.setInnerText("");
// add the widget in
RootPanel.get(accordionElement.getId()).add(accordion_panel);

I would suggest you try something like this...
<body>
<a id="tmpEl" />
</body>
then in somewhere create a widget (e.g. textBox widget)
TextBox tb = new TextBox();
After you have created the widget you can get it's DOM object by using tb.getElement() (I'm not sure that's the correct method name, but it's definitely something like getElement)
After that you could use
Element parent = Document.getElementById('tmpEl').getParent();
parent.removeChild(Document.getElementById('tmpEl'));
parent.appendChild(tb.getElement());
One more thing that add() does is call Widget.onAttach() on the widget that is being added to the panel. onAttach does some work to register the widget to receive events.
You could try calling tb.onAttach(); It might also be necessary to call RootPanel.detachOnWindowClose(tb); (as mentioned in a previous post)

Related

TinyMCE - preserve style and function of selected element

I have decided to 'enhance' a textarea in a form with TinyMCE... however, doing so has interrupted the styling and jQuery functionality of the original element, as TinyMCE wraps that element in an iframe and a few divs. What I'd love to be able to do is to get the TinyMCE functionality (preserving text formatting, etc.) but not lose the styling and functions that I had associated with the original textarea. I looked through the TinyMCE documentation, but couldn't seem to find anything about this. Does anyone have any thoughts on how to accomplish this?
My form features the textarea like so:
<head>
<script>tinymce.init( { selector: 'textarea' } );</script>
</head>
<div class="form-element">
<div class="label-container">
<label for="body">Post</label><span class="warning">Please fill out this field</span>
</div>
<textarea id="body" class="input-field" name="body"></textarea>
</div>
but adding TinyMCE breaks the label/textarea relationship.
Also, jQuery functionality is broken, such as this validation script:
$("form").submit(function(e){
tinyMCE.triggerSave();
var inputFields = $(".input-field");
var proceed = true;
for(var i = 0; i < inputFields.length; i++){
if($(inputFields[i]).val() == ""){
$(inputFields[i]).css("border", "solid 3px #E86F3A");
$(inputFields[i]).prev().find(".warning").show();
var proceed = false;
e.preventDefault();
}
else{
$(inputFields[i]).css("border", "none");
$(inputFields[i]).prev().find(".warning").hide();
};
};
//OTHER STUFF...
});
since the textarea.input-field is no longer picked up in the inputFields variable.
So, in a nutshell, I'm looking for the TinyMCE wrapper to 'inherit' the styling and functionality of the element that it is attached to. Possible?
As you have correctly surmised when you invoke TinyMCE on a <textarea> the original <textarea> is no longer visible on the page and its replaced by an <iframe> and series of <div> elements.
If you want to keep the underlying <textarea> in sync you can use the tinymce.triggerSave() method. This will update all of the underlying <textarea> elements with the current value of the appropriate instance of TinyMCE.
You can do this when someone tries to save/submit the content or you can try to perform this when certain editor events happen (https://www.tinymce.com/docs/advanced/events/#editorevents). Unless you need real time accuracy of the contents of the <textarea> its far easier to call the triggerSave() method right before you perform you jQuery validation.
Note: Putting a border on the <textarea> won't have any impact on TinyMCE as you no longer see the underlying <textarea>. You can certainly try to add CSS to the editor's HTML in real time. The outer border of TinyMCE 4.4 has these classes attached:
class="mce-tinymce mce-container mce-panel"
...but do note that these classes could change over time so if you upgrade TinyMCE check to make sure your CSS still works before upgrading.

How can I create a contenteditable <div> in GWT on the client side?

I'm trying to create a custom widget in GWT that creates a div with contenteditable=true inside of it. The problem is that when I create this div and I initialize this widget, the div does not have the contenteditable property.
my code:
public MyWidget(){
FlowPanel panel = new FlowPanel();
initWidget(panel);
HTML div = new HTML("<div id=\"my-div\" contenteditable=true></div>");
div.setText("hello there");
div.addHandler(new MyWidgetKeyDownHandler(), KeyDownEvent.getType());
mDiv = div;
panel.add(div);
}
But when I run the code and I inspect the source, I see this:
<div>
<div class="gwt-HTML">hello there</div>
</div>
So it looks like it's missing the contenteditable tag as well as the id. What am I doing wrong here? How can I create this contenteditable div widget?
div.getElement().setAttribute('contenteditable', 'true');
I would work around your requirement for a specific id. You can set one with ensureDebugId but you might find it simpler to do without it.

How to wrap elements under a GWT HTMLPanel

Let's say I create an HTMLPanel with some HTML like:
<p>
Blah blah <span id='1'>more html... </span>
</p>
Now I want to attach an event handler to the span. I want to see it as an InlineHTML GWT widget. I tried:
HTMLPanel html = new HTMLPanel(stringOfHTML);
parentWidget.add(html);
String id = "1";
Element span = html.getElementById(id);
InlineHTML wid = InlineHTML.wrap(span); // -- error here
html.addAndReplaceElement(wid, id);
The second-to-last line dies with the AssertionError: A widget that has an existing parent widget may not be added to the detach list.
Is there a way to wrap sub elements in a HTMLPanel?
This is GWT 2.4.
Note
After a few comments and answers I realized that I forgot to mention: usually UiBinder is the answer here, but I'm not using it because the input is html text created in another context by non-programmers.
No, not really. You getting this exception because <span> element is already a part of some widget. wrap methods can be used only to create widgets on top of elements which are not part of some other widget. If you want to handle clicks on this span, you can add dom handler to the HTMLPanel, and then detect which element was clicked.
Convert your <span> into an InlineLabel inside your UiBinder template and attach anything you want to it :).

Prevent EPiServer from wrapping content in <p> tags

I'm working on a site in EPiServer, and whenever I create a page property with the type set to "XHTML string" (which uses the WYSIWYG content editor in Edit mode), it wraps all content in <p> tags.
Is there any way to prevent this from happening? I can't remove the paragraph margins universally through my CSS (e.g. p {margin: 0 !important;}) since I do need the margins for actual paragraphs of text. I've even tried going to the HTML source view in the editor and manually deleting the <p> tags that it generates, but it immediately adds them back in when I save!
It doesn't happen when the property type is either a long or short string, but that's not always an option since the content might contain images, dynamic controls, etc.
This is becoming a real nuisance since it's very hard to achieve the layout I need when basically every element on the page has extra margins applied to it.
As Johan is saying, they are there for a reason - see more info here. That being said, it's not impossible to remove them. It can be done in one of two ways (taken from world.episerver.com:
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
myEditor.InitOptions["force_p_newlines"] = "false";
}
or
<script type="text/javascript">
tinyMCE.init({
force_p_newlines: false
});
</script>
You can add your own custom TinyMCE-config that removes P-elements or strip them out using regular expressions either when saving the page or when rendering the property/page.
I think it's a bad idea though. P-elements are what the editors generate the most and in most cases their content is also semantically correct. Better to wrap your property in a div with a class and adjust margins using CSS like you mention.
If you're using a version of EPiServer with TinyMCE editors, you can insert <br /> elements instead of <p> elements if you type shift-enter instead of enter. This should eliminate your margin problems.
More info at the link below:
http://www.tinymce.com/wiki.php/TinyMCE_FAQ#TinyMCE_produce_P_elements_on_enter.2Freturn_instead_of_BR_elements.3F
EDIT: My comment below answers his question better.
I discovered that while I can't remove the <p> tags from the source view (because it adds them back in automatically), if I replace them with <div> tags, it'll leave things alone. It does mean that I've got an extra <div> wrapping some elements that I don't really need, but at least a <div> doesn't add margins like a <p> does, so...good enough!

Jquery Selector Multiple Classes (this)

I have a page that lists content that are contained in a div with a class ad-container. in that container there is a hidden div with the class ad-contact. on the hover of the ad class i want to animate the display of ad-info. since there are multiple ads on a paticular page, i want only the ad-info of the currently hovered ad-container to slide in. my problem is that since there are more than 10 ads a page when you hover over any of the ads, all of the ads-contact divs slideDown and not the one you are hovering over.
$(document).ready(function() {
$('.ad-container').hover(
function(){
$(".ad-contact").slideDown(1000);
},
function(){
$(".ad-contact").slideUp(1000);
});
});
i think, (this) is used here but im not sure. and this would really shed the light for me.
<div class="ad-container">
<div class="ad-title">title<span class="ad-title-img">(pic)</span></div>
<div class="ad-description">texttext</div>
<div class="ad-contact" style="display:none">contact poster</div>
<div class="ad-sellerinfo" style="display:none">* Verified ***-****<br />
Paid Member</div>
</div>
The jQuery constructor accepts a 2nd parameter which can be used to override the context of the selection. Something like this should work:
$(document).ready(function() {
$('.ad-container').hover(
function(){
$(".ad-contact", this).slideDown(1000);
},
function(){
$(".ad-contact", this).slideUp(1000);
});
});
Also, worth mentioning, $(".ad-contact", this) is internally converted into: $(this).find(".ad-contact") so you can use this one instead, it might be slightly faster.
You could use the .children() selector:
$(this).children(".ad-contact").slideDown(1000);
This way you will only act on the class ad-contact if its a child of the object in context (which is the object currently being hovered)
See a working demo here
You should use event to handle this,
First you need like
ad-container.hover(function(event){
event.target.children();
})
and then this.show().delay(1000).hide();
the code sample provide may not work when copy paste you have to write your own code in editor.