I have a Web Part that dynamically inserts div tags written in VS2010 using C#. I want to implement mouseover events for these div's. When the Web Part is deployed onto SP2010, my JavaScript is not able to find these div's when I just search for them with the control id that I have specified.
When I checked the page source, I found that some tags like ct100_m_g_ are prefixed to the control id that I have specified.
How can I guess these ids?
The ctlxxx stuff is automatically prepended to the control's ID by ASP.NET, to generate the client ID.
If you want to set a deterministic client ID, you can set the ClientID property instead of the ID property. See http://msdn.microsoft.com/en-us/library/system.web.ui.control.clientid.aspx
In webparts you can use the client ID to find your controls in javascript. Take a look at this example and it will become clear:
using System;
using System.Text;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
namespace MyDemo.WebParts
{
public class MyWebPart : WebPart
{
private TextBox txtInput;
private TextBox txtOutput;
private Button btnDoSomething;
protected override void CreateChildControls()
{
base.CreateChildControls();
txtInput = new TextBox();
Controls.Add(txtInput);
txtOutput = new TextBox();
txtOutput.ReadOnly = true;
Controls.Add(txtOutput);
btnDoSomething = new Button();
btnDoSomething.Text = "Do Something";
btnDoSomething.OnClientClick = string.Format("DoSomething('{0}', '{1}'); return false;", txtInput.ClientID, txtOutput.ClientID);
Controls.Add(btnDoSomething);
}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
// Check if the script is already registered, this is necessary because the webpart can be
// added multiple times to the same page
if (!Page.ClientScript.IsClientScriptBlockRegistered("MyWebPartScript"))
{
StringBuilder sb = new StringBuilder();
sb.Append("function DoSomething(inputControlID, outputControlID){");
sb.Append(" var inputControl = document.getElementById(inputControlID);");
sb.Append(" var outputControl = document.getElementById(outputControlID);");
sb.Append(" outputControl.value = inputControl.value;");
sb.Append("}");
Page.ClientScript.RegisterClientScriptBlock(GetType(), "MyWebPartScript", sb.ToString(), true);
}
}
protected override void RenderContents(System.Web.UI.HtmlTextWriter writer)
{
EnsureChildControls();
writer.Write("<table>");
writer.Write("<tr><td>");
txtInput.RenderControl(writer);
writer.Write("</td><td>");
txtOutput.RenderControl(writer);
writer.Write("</td></tr><tr><td colspan=\"2\">");
btnDoSomething.RenderControl(writer);
writer.Write("</td></tr></table>");
}
}
}
You could give each of your DIVs a class when generating them. THen, just use the class name to select them and add the event handler.
Related
Say I have an element in Blazor. I want to set the elements style based on the value of the input field in Blazor. I am aware that you can do this by using if else blocks in the html code however I want to avoid this as it grows the code base extremely quick. A function would be better which evaluates the incoming object and styles it.
for instance if I have the element below which passes a value the value is passed to a function.
<th><InputText placeholder="First Name" id=FirstName #bind-Value="#filterModel.FirstName">First Name</InputText></th>
Function
private async void UpdateVisibleData(object sender, FieldChangedEventArgs e){
Console.WriteLine(e.FieldIdentifier.FieldName + " Change Detected");
var fieldChanged = e.FieldIdentifier.FieldName;
var IsEmptyString = FindEmptyStringInField(filterModel, fieldChanged);
if(IsEmptyString){
element.style.setProperty('--my-variable-name', 'hotpink');
}
}
I havent been able to get the style.setproperty part to work. not sure what other methods there are. Open to JSInterop. preferrable though if I can target the style.setproperty function and get this working.
Revised Answer
Or you can use JSInterop like this:
/wwwroot/js/example.js
export function examplefunction (Id, Color) {
document.getElementById(Id).style.backgroundColor = Color;
}
razor page :
#inject IJSRuntime JS
#code {
private IJSObjectReference? module;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
module = await JS.InvokeAsync<IJSObjectReference>("import", "/js/example.js");
}
}
private async void UpdateVisibleData(object sender, FieldChangedEventArgs e){
var fieldChanged = e.FieldIdentifier.FieldName;
await module.InvokeVoidAsync("examplefunction", fieldChanged, "hotpink");
}
}
Original Answer
I would use ? operator to make simple HTML without additional code
<input type="text" placeholder="First Name" id=FirstName #bind=#textInput style='#(String.IsNullOrEmpty(textInput) ? "background-color: hotpink;" : "")' />
This isn't a exact answer by targeting the id. however I did find a workaround for this which isn't too bad for webassembly by passing a conditional function to the style tag on the element. It doesn't grow the code base as much as using if else blocks and declaring individual strings. although still targeting element id based on fieldeventchanges would be less computationally taxing as the conditional function is checked for every statechange on every field which uses it as opposed to only being called on the element id if the condition is met.
html
<th><InputText style="#StyleForEmptyString(filterModel.FirstName)" placeholder="First Name" id=FirstName #bind-Value="#filterModel.FirstName">First Name</InputText></th>
#code{
private string StyleForEmptyString(string fieldValue)
{
if (fieldValue == ""){
return "outline:none; box-shadow:none;";
}
return "";
}```
In a wicket application, I have two dependent dropdowns using Select2. Now, I want to set the default value of both dropdowns. The default value should be the first element of the choiceList.
Second dropdown's default value should be updated accordingly if I select other option from the choice in the first dropdown.
I have used Placeholder but didn't succeed. How can I update the placeholder value dynamically?
I tried the below code but the second dropdown's default value is not updating automatically.
private static class ValueEditor extends Select2Choice<String> {
private static final long serialVersionUID = SingleFiniteValueEditor.serialVersionUID;
ValueEditor(String id, EditableSingleFiniteModel<?> model, ChoiceProvider<String> provider) {
super(id, model.valueModel(), provider);
setOutputMarkupId(true);
getSettings().setDropdownAutoWidth(true);
getSettings().setCloseOnSelect(true);
getSettings().setPlaceholder("");
getSettings().setAllowClear(!model.mandatory());
if(!model.choices().getObject().isEmpty()) {
model.valueModel().setObject(model.choices().getObject().get(0));
}
}
#Override
protected void onInitialize() {
super.onInitialize();
add(new OnChangeAjaxBehavior() {
private static final long serialVersionUID = SingleFiniteValueEditor.serialVersionUID;
#Override
protected void onUpdate(AjaxRequestTarget target) {
target.add(getComponent().getParent());
target.prependJavaScript(JQuery.execute("$('#%s').select2('destroy');", getJquerySafeMarkupId()));
target.appendJavaScript(JQuery.execute("$('#%s').select2('focus');", getJquerySafeMarkupId()));
send(getComponent(), Broadcast.BUBBLE, new ValueChangedEvent());
}
});
// FIXME append a class and style in stylesheet?
add(AttributeModifier.append("style", "min-width:200px;"));
}
#Override
protected void onComponentTag(ComponentTag tag) {
tag.setName("select");
super.onComponentTag(tag);
}
}
I am new to the wicket framework so please help me with this problem.
First, two dropdowns are dependent dropdowns as shown in the fig enter image description here. Second dropdown's choice list is depend on first dropdown's selected value.
Other SO answers suggest overriding ApplicationServlet.writeAjaxPageHtmlHeader, but I cannot find those classes and methods in Vaadin 8.
I cannot find anything similar in com.vaadin.server.VaadinServlet or com.vaadin.ui.UI.
There is the #JavaScript annotation, but if I put that on my UI class, the script would be loaded for every page of my application. I need it only on one specific page.
The initial HTML page is called bootstrap page in Vaadin. There is some documentation that hints you to right direction in Book of Vaadin.
In Vaadin 8, you need to add BootstrapListener to session. You can get created sessions by adding SessionInitListener in VaadinServlet.
Register Session
This example is using Vaadin with Spring Boot but the same principle applies when not using Spring Boot.
#Component("vaadinServlet")
#WebServlet(urlPatterns = "/*", name = "BootstrapVaadinServlet", asyncSupported = true)
#VaadinServletConfiguration(ui = BoostrapUi.class, productionMode = false)
public class BootstrapVaadinServlet extends SpringVaadinServlet {
private static final Logger logger = LoggerFactory.getLogger(BootstrapVaadinServlet.class);
#Override
protected void servletInitialized() throws ServletException {
super.servletInitialized();
getService().addSessionInitListener(this::addBoostrapListenerOnSessionInit);
}
private void addBoostrapListenerOnSessionInit(SessionInitEvent sessionInitEvent) {
sessionInitEvent.getSession().addBootstrapListener(new AppBootstrapListener());
}
}
Implement html head tag modification
public class AppBootstrapListener implements BootstrapListener {
#Override
public void modifyBootstrapFragment(BootstrapFragmentResponse bootstrapFragmentResponse) {
}
#Override
public void modifyBootstrapPage(BootstrapPageResponse res) {
Elements headTags = res.getDocument().getElementsByTag("head");
Element head = headTags.get(0);
head.appendChild(metaExample(res.getDocument()));
}
private Node metaExample(Document document) {
Element meta = document.createElement("meta");
meta.attr("author", "Me");
return meta;
}
}
If using add-on is ok, try HeaderTags
Overview
Using this add-on, you can define tags to add to the host page by
adding annotation to your UI class.
Sample from add ons usage example
#MetaTags({
// Replaces the Vaadin X-UA-Compatible header
#Meta(httpEquiv = "X-UA-Compatible", content = "hello"),
#Meta(name = "test", content = "test") })
// And showing how to create a link tag as well
#Link(rel = "foobar", href = "about:blank")
public class DemoUI extends UI {
...
}
I have a view ( for creating a recipe ) to which I dynamically add a partial view ( representing products ). There can be several products added to recipe. Partial view is added on button click, using jQuery, and this works fine:
$('.loadPartial').on('click', function (evt) {
evt.preventDefault();
evt.stopPropagation();
var $productsDiv = $('#productsDiv'),
url = $(this).data('url');
$.get(url, function (data) {
$productsDiv.append(data);
});
});
Partial view has a combo. Name of this combo is generated dynamically using Html helper extension method 'GetIndexedName' which adds unique index to specified name. If I click button twice it should render two partial views, after first click combo name should be "ProductsCombo0", after the second "ProductsCombo1"
#Html.DropDownList(
#Html.GetIndexedName("ProductsCombo"),
null,
htmlAttributes: new { #class = "form-control col-md-3" }
)
The problem is that the method #Html.GetIndexedName fires only after the first button click ( checked in debugger ). Next clicks only render partial view, but do use the method to generate name. All combos have name "ProductsCombo0", "ProductsCombo0"
Do you know how to make it fire everytime partial view is rendered?
If it can`t be done this way could you recommend me some other solution for generating unique ids?
Because every time you make the ajax call for a new row, It is a totally separate http call and this call does not have any information whether you are making the call for the first or eighth row. You need to store this value(which row you are making this call for) and use that when you build the dropdown name.
So first update your action method to accept a row index value.
public ActionResult AddRow(int id)
{
var vm = new AddItemVm {RowIndex = id};
vm.Items = new List<SelectListItem>()
{
new SelectListItem {Value = "1", Text = "IPhone"}
};
return PartialView("_NewItem", vm);
}
I am having small view model to pass the data between my action method and the partial view, which looks like,
public class AddItemVm
{
public int RowIndex { set; get; }
public List<SelectListItem> Items { set; get; }
}
And in the partial view, which is strongly typed to our view model, we will read the Model.RowIndex property and use that to build the dropdown name/id.
#model YourNamespaceHere.AddItemVm
<div>
#Html.DropDownList("ProductsCombo"+Model.RowIndex, Model.Items)
</div>
Now we need to make sure that we are sending a unique row index value to our AddRow method every time we want to add a new row. You can keep a javascript variable and increment it's value everytime user tries to add a new row and use that value.
$(function () {
var rowIndex = 0;
$('.loadPartial').on('click', function (evt) {
evt.preventDefault();
evt.stopPropagation();
rowIndex++;
var $productsDiv = $('#productsDiv'),
url = $(this).data('url') + '/' + rowIndex;
$.get(url, function (data) {
$productsDiv.append(data);
});
});
});
I finally found a solution to the problem. Data from AJAX call was cached and that is why function was called only once. All I had to do was to disable it. So I changed jQuery 'get' mehtod to 'post' which ( what i know ony now ) is not cached.
Hey when I run my program, I want to dynamically add Panels to the mainpage and therfore work with "setVisible(boolean)".
But not I get the error:
ERROR: Cannot bind a listener for event "click" on element "institutCheck7" because the element is not in the DOM
In the Ajax Debugger.
I saw that it has something to do with the visibility in this
post, but I didn't get what I have to do, it says, that this is being resolved in Wicket 7, but I'm still running 6.20.
adminUIForm.add(myPanel= new MyPanel("myPanel", allThings));
myPanel.setOutputMarkupId(true);
myPanel.setVisible(false);
//Note: Setting the OutputMarkupId allows actions such as setting components in/visible
List<IColumn<ContactInfo, String>> columns = new ArrayList<IColumn<ContactInfo, String>>();
columns = initializeColumns(columns);
DataTable<ContactInfo, String> datatable = new DataTable<ContactInfo, String>("datatable", columns, provider, 10){
private static final long serialVersionUID = 1L;
#Override
protected Item<ContactInfo> newRowItem(final String id, final int index,
final IModel<ContactInfo> model) {
Item<ContactInfo> rowItem = new Item<ContactInfo>(id, index, model);
rowItem.add(new AjaxEventBehavior("onclick") {
private static final long serialVersionUID = 1L;
#Override
protected void onEvent(AjaxRequestTarget target) {
System.out.println("click");
myPanel.setSelectedKontakt(model);
myPanel.setVisible(true);
target.add(myPanel);
}
});
return rowItem;
}
};
setTableProperties(datatable);
adminUIForm.add(datatable);
}
I override the newRowItem method to add an onClick event. I get "click" in the console each time I hit a column, but I get this error log:
ERROR: Wicket.Ajax.Call.processComponent: Component with id [[institutTablePanel6]] was not found while trying to perform markup update. Make sure you called component.setOutputMarkupId(true) on the component whose markup you are trying to update.
ERROR: Cannot bind a listener for event "click" on element "institutCheck7" because the element is not in the DOM
ERROR: Cannot bind a listener for event "click" on element "cont8" because the element is not in the DOM
ERROR: Cannot bind a listener for event "click" on element "institutCheck9" because the element is not in the DOM
ERROR: Cannot bind a listener for event "click" on element "conta" because the element is not in the DOM
ERROR: Cannot bind a listener for event "click" on element "institutCheckb" because the element is not in the DOM
ERROR: Cannot bind a listener for event "click" on element "contc" because the element is not in the DOM
ERROR: Cannot bind a listener for event "click" on element "institutCheckd" because the element is not in the DOM
ERROR: Cannot bind a listener for event "click" on element "conte" because the element is not in the DOM
Those error all bind with components in "myPanel", I don't know how to resolve this. If I set it to visible earlier I get the same problem, including all elements, which will be hidden later in the workflow.
Edit:
public class MyPanel extends Panel {
private static final long serialVersionUID = 1L;
private Form<Void> institutForm = new Form<Void>("institutForm");
private ListView<Institut> listView;
private IModel<ContactInfo> selectedKontakt;
private Set<Institut> allInstitut;
#Override
protected void onModelChanged() {
//TODO maybe here
super.onModelChanged();
}
public InstitutTablePanel(String id, final Set<Institut> allInstitut) {
super(id);
this.allInstitut = allInstitut;
this.add(institutForm);
List<Institut> allInstitutList = new ArrayList<Institut>(allInstitut);
institutForm.add(listView = new ListView<Institut>("listView", allInstitutList) {
private static final long serialVersionUID = 1L;
protected void populateItem(final ListItem<Institut> item) {
final IModel<Boolean> checked = Model.of(Boolean.FALSE);
final IModel<String> expandState = Model.of("+");
#SuppressWarnings("unused")
final Label institutLabel;
final Institut institut = item.getModelObject();
// Define variables which concern only one item in populateItem
final WebMarkupContainer div = createMarkUpContainer("cont");
final WebMarkupContainer container = createMarkUpContainer("container");
container.setEnabled(false);
compareInstitute(checked, institut, container);
item.add(container);
div.add(new AjaxEventBehavior("onclick") {
private static final long serialVersionUID = 1L;
#Override
protected void onEvent(AjaxRequestTarget target) {
expandOrCollapse(expandState, container);
target.add(institutForm);
}
});
item.add(div);
div.add(new AjaxCheckBox("institutCheck", checked) {
private static final long serialVersionUID = 1L;
#Override
protected void onUpdate(AjaxRequestTarget target) {
//Not Important for this
target.add(institutForm);
}
});
container.add(new ErhebungMatrixPanel("erhebungMatrix", institut, selectedKontakt));
container.setVisible(false);
div.add(institutLabel = new Label("institutLabel", new PropertyModel<Institut>(institut, "langName")));
}
});
listView.setOutputMarkupId(true);
listView.setReuseItems(true);
institutForm.setOutputMarkupId(true);
}}
I edited my setSelectedKontakt method like this:
public void setSelectedKontakt(IModel<ContactInfo> model) {
this.selectedKontakt = model;
modelChanged();
}
to trigger the change if necessary.
The problem is target.add(myPanel); won't trigger populateItem again.
The error message says it pretty clearly and a look into the DOM tells that the markup for the panel is not there. Thats because setVisible(false) doens't even render the component with CSS display: none; as expected. It's just not there so it can't be modified afterwards. But you can tell Wicket to render a placeholder instead with setOutputMarkupPlaceholderTag(true). Wicket then creates an invisible markup e.g. <span style="display: none;" id="mypanel1a"></span>, which then is swaped with the real component when setting the visiblity to true later.
Just a tip to those that do get this error that a common mistake is to use:
<wicket:container wicket:id="panel"/>
...instead of...
<div wicket:id="panel"/>
...for where you put the panel. The method call setOutputMarkupId will not inform you that it is unable to add an id to a wicket:container, and hence your target.add(panel) will also fail.