How to reference 'this' from a javascript call in a JSF component? - javascript

<h:commandLink id="#{id}" value="#{value}" action="#{actionBean.getAction}" onclick="alert(this);"/>
In the previous simplified example, the 'this' keyword used in the Javascript function won't reference the generated A HREF element, but will reference to the global window, because JSF generates the following (simplified) code:
<a onclick="var a=function(){alert(this)};var b=function(){if(typeof jsfcljs == 'function'){jsfcljs(document.forms['mainForm'],'generatedId,action,action','');}return false};return (a()==false) ? false : b();" href="#" id="generatedId">text</a>
So because JSF wraps the user defined onclick in a function, this will point to the global window. I don't have access to the id of the element because my code is used in a generic component that can be used in loops etc. and I don't use backing beans (Facelets + JSF).
So my question is, is there a way to access the A HREF element from within my onclick javascript function, without knowing the id?

EDIT:
Go here for a small library/sample code for getting the clientId from the id: JSF: working with component IDs
The HTML ID emitted by JSF controls is namespaced to avoid collisions (e.g. the control is a child of UIData and emitted multiple times, or the container is a portlet so the view can be rendered multiple times in one HTML page). This ID is known as the clientId, and is distinct from the ID set on the JSF control.
If you want to emit the clientId in the view, you can use a managed bean to do it.
public class JavaScriptBean {
public String getOnclickButton1() {
String id = "button1";
String clientId = getClientId(id);
return "alert('You clicked element id=" + clientId + "');";
}
public String getOnclickButton2() {
String id = "button2";
String clientId = getClientId(id);
return clientId;
}
private String getClientId(String id) {
FacesContext context = FacesContext.getCurrentInstance();
UIViewRoot view = context.getViewRoot();
UIComponent component = find(view, id);
return component.getClientId(context);
}
private UIComponent find(UIComponent component, String id) {
if (id.equals(component.getId())) {
return component;
}
Iterator<UIComponent> kids = component.getFacetsAndChildren();
while (kids.hasNext()) {
UIComponent kid = kids.next();
UIComponent found = find(kid, id);
if (found == null) {
continue;
}
return found;
}
return null;
}
}
This is configured in the application's faces-config.xml and bound to the view using the expression language:
<f:view>
<h:form>
<h:commandButton id="button1" value="b1"
onclick="#{javaScriptBean.onclickButton1}" />
<h:commandButton id="button2" value="b2"
onclick="alert('You clicked element id=#{javaScriptBean.onclickButton2}');" />
</h:form>
</f:view>

var a=function(){alert(this)};var b=function(){if(typeof jsfcljs == 'function'){jsfcljs(document.forms['mainForm'],'generatedId,action,action','');}return false};return (a()==false) ? false : b();
Oh dear lord! That's violently horrible.
And no, it appears to be throwing away 'this' before you get a chance to look at it - it should have said "return (a.call(this)==false)? false : b()".
On IE only you could read the srcElement from window.event, but that's pretty ugly.
How about avoiding JSF's weird event mangling entirely, by using unobtrusive scripting? eg. omit the onclick and say:
document.getElementById('generatedId').onclick= function() {
alert(this);
return false; // if you want to not follow the link
}
If you need the link to continue into JSF's event handling, you'd have to save the old onclick event handler and call it at the end of the function, or use Element.addEventListener (attachEvent on IE) to put your scripting hooks in at an earlier stage than the onclick.

<head>
..
<script type="text/javascript">
var myCommandLinkId = "undefined";
</script>
..
</head>
..
<h:commandLink id="myCommandLink" value="Click Me" onmousedown="myCommandLinkId=this.id;" onclick="alert(myCommandLinkId);" />

Related

Unable to read value from document

I'm a novice in MVC, Below is my code
I am unable to read the value of an ID and use that in an decision statement, I am getting "The name "Text" does not exist in current context", I need to work on the if statement based on the value I get from my document.getElementById
#{
var grid = new WebGrid(Model.Abc, canPage: true, canSort: true, rowsPerPage: 50);
}
#{
var gridColumnsNew = new List<WebGridColumn>();
gridColumnsNew.Add(grid.Column("Details", header: "Id"));
<text>
var obj = document.getElementById("NextAction").value;
</text>
if (#text.obj == "Start")
{
gridColumnsNew.Add(grid.Column("Temp"));
}
}
Try using
document.getElementsByName("NextAction").value;
I have seen in my case that Blazor changes Id to name.
Note: I am using DevexpressBlazor
Did you checked if you are able to see on the html generated that ID?
If yes, Did you have any JS error before?
Looks like the ID not was generated or the place where you are run the getElementById don't have visibility to your specific code.
You are mixing razor syntax and javascript. The line var obj = document.getElementById("NextAction").value; is javascript and should go inside <script> tag. You can't call javascript functions from razor code.
Solution:
Assuming you have a controller named GridController.cs and a view named Grid.cshtml. Inside your controller add a new HttpPost action:
[HttpPost]
public IActionResult NextAction(string nextAction)
{
ViewData["NextAction"] = nextAction;
return View("Grid");
}
Inside the view add a form that posts the nextAction value to the controller:
<form asp-action="NextAction" asp-controller="Grid">
<input type="hidden" value="Start" name="nextAction" />
<button type="submit">Start</button>
</form>
The controller added the NextAction value in the ViewData dictionary so now the view can access it:
#{
var gridColumnsNew = new List<WebGridColumn>();
gridColumnsNew.Add(grid.Column("Details", header: "Id"));
if (ViewData["NextAction"] == "Start")
{
gridColumnsNew.Add(grid.Column("Temp"));
}
}
You are getting that error because you are using #text.obj. In Razor, once you attached # before any identifier, it considers it a C# or VB variable.
Since we don't have your entire page, you may need to clarify where the source of the NextAction. It will be helpful. See a sample of something similar.
#if(item.Ward == "start")
{
gridColumnsNew.Add(grid.Column("Temp"));
}
The item is from the model I am iterating to form the grid.

Changing an Input value in Blazor by javascript doesn't change it's binded property value

I'm building a website using app.net core 3.1 with blazor.
In one of my components I have :
<input #bind="Message" type="text" id="input-message"/>
Message is just a string property.
and I have javascript:
document.getElementById('input-message').value = 'some text';
The problem is after running the above js, <input> value changes but Message value doesn't, and of course if I type or paste something inside <input> , Message value changes too.
Apparently changing <input> value or any other changes in DOM by javascript doesn't change State, so blazor won't re-render the component. Even calling StateHasChanged(); manually in your razor page won't work.
To get this done, you just have to trigger the same DOM events that occur if the user modifies the <input> normally, just like below:
var myElement = document.getElementById('input-message');
myElement.value = 'some text';
var event = new Event('change');
myElement.dispatchEvent(event);
You shouldn't change the input value directly in javascript, what you should do is call a c# function that updates the value and then it will update the javascript.
Instead of doing
document.getElementById('input-message').value = 'some text';
You should do something like
DotNet.invokeMethodAsync('UpdateMessageValue', 'some text');
Where you have
public void UpdateMessageValue(string value){
Message = value;
}
And because you are using bind in the input, the value of document.getElementById('input-message').value will be changed, and the value in the c# will also be changed.
This answer isn't complete, I'm passing you the idea on how to do it and not the correct code to solve your case, but if you want more information on how to do it, you can take a look at Call .NET methods from JavaScript functions in ASP.NET Core Blazor.
If you don't have control over the third-party lib script that is modifying your input field you can always use the following solution. The concept is the following:
After rendering the component we call JS to start intercepting the all input fields value setters, then we get our callback in Blazor from JS. Blazor then dispatches to appropriate field.
Fields example:
<div class="input-group input-daterange" data-date-format="dd.mm.yyyy">
<input type="text"
id="InputDateFrom"
#bind="InputDateFrom"
class="form-control text-center" placeholder="От">
<span class="input-group-addon"><i class="fa fa-angle-right"></i></span>
<input type="text"
id="InputDateTo"
#bind="InputDateTo"
class="form-control text-center" placeholder="До">
</div>
JS:
function WatchInputFields(callbackChangedName, dotnetRef) {
var descriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value");
var originalSet = descriptor.set;
// define our own setter
descriptor.set = function (val) {
console.log("Value set", this, val);
originalSet.apply(this, arguments);
dotnetRef.invokeMethodAsync(callbackChangedName, this.id, this.value);
}
Object.defineProperty(HTMLInputElement.prototype, "value", descriptor); }
BLAZOR: Inside OnAfterRenderAsync call InitInputFieldsToTrackOnce on first rendering (or second one in case of server with prerendering):
private async Task InitInputFieldsToTrackOnce()
{
_objRef = DotNetObjectReference.Create(this);
await JS.InvokeVoidAsync(
"WatchInputFields",
"OnInputFieldChanged",
_objRef);
WatchInputField("InputDateFrom", (value) => { _InputDateFrom = value; Console.WriteLine($"Setting FROM to {value}"); });
WatchInputField("InputDateTo", (value) => { _InputDateTo = value; ; Console.WriteLine($"Setting TO to {value}"); });
}
void WatchInputField(string id, Action<string> onChanged)
{
InputFieldChanges.TryAdd(id, onChanged);
}
private ConcurrentDictionary<string, Action<string>> InputFieldChanges { get; } = new ConcurrentDictionary<string, Action<string>>();
private DotNetObjectReference<ShopDesk> _objRef;
[JSInvokable]
public async Task OnInputFieldChanged(string id, string value)
{
var ok = InputFieldChanges.TryGetValue(id, out Action<string> action);
if (ok)
action(value);
}
And dispose _objRef when disposing your component.

jsf dynamically create SelectOneMenu [duplicate]

Can I add JSF components dynamically? I need to have a form with a button which should add one <h:inputText> to the form. Is this possible?
I know this should be possible in JavaScript somehow. Do anybody know how to do this in JSF? I think the major problem is how do I get or set values of new inputs via #{value}.
Use an iterating component like <h:dataTable> or <ui:repeat> to display a dynamically sized List of entities. Make the bean #ViewScoped to ensure that the list is remembered across postbacks on the same view instead of recreated over and over.
Kickoff example with <h:dataTable> (when using <ui:repeat> simply replace <h:dataTable> by <ui:repeat>, and <h:column> by e.g. <li> or <div>):
<h:form>
<h:dataTable value="#{bean.items}" var="item">
<h:column><h:inputText value="#{item.value}" /></h:column>
<h:column><h:commandButton value="remove" action="#{bean.remove(item)}" /></h:column>
</h:dataTable>
<h:commandButton value="add" action="#{bean.add}" />
<h:commandButton value="save" action="#{bean.save}" />
</h:form>
Managed bean:
#Named
#ViewScoped
public class Bean {
private List<Item> items;
#PostConstruct
public void init() {
items = new ArrayList<>();
}
public void add() {
items.add(new Item());
}
public void remove(Item item) {
items.remove(item);
}
public void save() {
System.out.println("items: " + items);
}
public List<Item> getItems() {
return items;
}
}
Model:
public class Item {
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String toString() {
return String.format("Item[value=%s]", value);
}
}
See also:
Recommended JSF 2.0 CRUD frameworks
How to create dynamic JSF form fields
How to implement a dynamic list with a JSF 2.0 Composite Component?
How to choose the right bean scope?
It's should be like that
Bind a form tag to the bean property
<form binding="#{myBean.myform}">...</form>
#ManagedBean("myBean")
public class Bean{
property HtmlForm myform;
}
on event, create a new instance of the input component
HtmlInputText input=new HtmlInputText();
and attach to the your form
myform.getChildren().add(input);
Use h:dataTable to add elements dynamically... Have a list of any type you want to provide values for dataTable...
In h:dataTable... you can include the element tag to create inside <h:column>
It will be used to generate elements you want to create dynamically.

Jquery doesn't find dynamicaly dom parent

I have to develop a very large platform and I need some improvements in some plugins.
Basically, I have a template which use smarty as engine (it doesn't matter this) and I have this code in that template:
<div class="imageLoader">
<div id="main_picture" data-instance="article" data-location="{$smarty.session.CONFIG.DIR.C_PHOTOS_DIR}">
{if $data.main_picture}
<input type='hidden' name='main_picture' value="{$data.main_picture}" />
<img src="{$smarty.session.CONFIG.DIR.C_PHOTOS_DIR}{$data.main_picture}" />
<span class="NTPDelete" onclick="javascript: ntpDeleteImage(this);">
{$smarty.session.language.ntp.delete}
</span>
{else}
<span class="NTPOpenLoader" onclick='javascript: ntpOpenLoader(this);'>
{$smarty.session.language.ntp.add}
</span>
{/if}
</div>
</div>
I have also a js script which contains this code:
function ntpDeleteImage(elem) {
var parent = $(elem).parent();
var szInstanceName = $(parent).attr('data-instance');
var params = {
'filename' : encodeURI($(parent).find('input').val()),
'instance': szInstanceName
};
$.post("./ajax/ntp.delete.php", params, function(data){
})
.done(function(data){
$(parent).find('img, span').remove();
$(parent).find('input').val('');
$("<span />", {
class: 'NTPOpenLoader'
}).html(ntpAddPictureText).appendTo(parent);
$(parent).on('click', 'span.NTPOpenLoader', function(){
ntpOpenLoader(elem);
});
});
}
function ntpOpenLoader(elem){
var parent = $(elem).parent();
var szInstanceName = $(parent).attr('data-instance');
console.log(parent);
window.open("ntp.loader.php?id=" + parent[0].id + "&instance=" + szInstanceName, "_blank", "width=400,height=170,top="+event.clientY+",left="+(event.clientX-150));
}
Method ntpOpenLoader() have two contexts: first is the method ntpDeleteImage and the second is directly from template code (see template code above).
When I run ntpOpenLoader() directly from template code it works fine.
When I run ntpOpenLoader() from ntpDeleteImage() context my span dom doesn't see the parent. Actually, I don't think that my span (with NTPOpenLoader class) retrieve correctly the parent.
Debugging this from Chrome I have in console as follow:
In (template context) console return.
[div#main_picture, prevObject: n.fn.init[1], context: span.NTPOpenLoader]
In context of running from ntpDeleteImage I have:
[prevObject: n.fn.init[1], context: span.NTPOpenLoader]
This means I have an object without parent.
Please help me find my error and also correct me where I'm wrong.
I'm was totally idiot to forgot the context of elem inside of onclick event binding. The correct call is ntpOpenLoader(this). Now it's work perfectly.
Thanks folks!

Form with JQuery Steps using aui:form tag in Liferay submits no data

I built a portlet and added a entity named Idea. There are two JSPs, one is the view and one the edit.
In the view there is only a button to create a new Idea and a table showing all ideas. Clicking on the button shows the edit jsp.
There is a form with two fieldsets and input stuff.
The "problem" is i cannot use the <aui:form ... stuff because it won't work with JQuery steps (or better, i cannot get it working). So i am using normal tag and also JQuery steps is providing the submit button which is only a <a href="#finish" ...>. So that wont bring the form to submit and the data being in the database.
So I tried to do it within the javascript code of the definition of jquery steps like here:
$(document).ready(function(){
var form = $("#wizard").show();
form.steps(
{
headerTag : "h3",
bodyTag : "fieldset",
transitionEffect : "slideLeft",
onFinishing: function (event, currentIndex) {
alert("Submitted!");
var data = jQuery("#wizard").serialize();
alert(data);
jQuery("#wizard").submit();
form.submit();[/b]
},
onFinished: function (event, currentIndex) {
//I tried also here..
},
});
});
But even if i declare the data explicitely it wont put it in the db.
So my idea was that the "controller" class which calls the "addIdea" function is never called.
How am I solving the problem?
Here is also my jsp code for the form part:
<aui:form id="wizard" class="wizard" action="<%= editIdeaURL %>" method="POST" name="fm">
<h3>Idea</h3>
<aui:fieldset>
<aui:input name="redirect" type="hidden" value="<%= redirect %>" />
<aui:input name="ideaId" type="hidden" value='<%= idea == null ? "" : idea.getIdeaId() %>'/>
<aui:input name="ideaName" />
</aui:fieldset>
<h3>Idea desc</h3>
<aui:fieldset>
<aui:input name="ideaDescription" />
</aui:fieldset>
<aui:button-row>
<aui:button type="submit" />
<aui:button onClick="<%= viewIdeaURL %>" type="cancel" />
</aui:button-row>
</aui:form>
Is there a way to "teach" JQuery Steps the <aui:*** tags? I tried it already while initializing the form but it won't work. To get it working using the aui tags would be great. Because otherwise the Liferay portal wont get the data or it would get it only with hacks right?
€dit: What I forgot, when I submit the form using javascript submit, it creates a new dataentry in the db but no actual data in it.
€dit2:
The editIdeaURL is referenced a bit over the form here:
<portlet:actionURL name='<%=idea == null ? "addIdea" : "updateIdea"%>'
var="editIdeaURL" windowState="normal" />
and the addIdea code looks as follows:
In the IdeaCreation class first this:
public void addIdea(ActionRequest request, ActionResponse response)
throws Exception {
_updateIdea(request);
sendRedirect(request, response);
}
Where _updateIdea() is:
private Idea _updateIdea(ActionRequest request)
throws PortalException, SystemException {
long ideaId = (ParamUtil.getLong(request, "ideaId"));
String ideaName = (ParamUtil.getString(request, "ideaName"));
String ideaDescription = (ParamUtil.getString(request, "ideaDescription"));
ServiceContext serviceContext = ServiceContextFactory.getInstance(
Idea.class.getName(), request);
Idea idea = null;
if (ideaId <= 0) {
idea = IdeaLocalServiceUtil.addIdea(
serviceContext.getUserId(),
serviceContext.getScopeGroupId(), ideaName, ideaDescription,
serviceContext);
} else {
idea = IdeaLocalServiceUtil.getIdea(ideaId);
idea = IdeaLocalServiceUtil.updateIdea(
serviceContext.getUserId(), ideaId, ideaName, ideaDescription,
serviceContext);
}
return idea;
}
And to finally put the data using IdeaLocalServiceImpl:
public Idea addIdea(
long userId, long groupId, String ideaName, String ideaDescription,
ServiceContext serviceContext)
throws PortalException, SystemException {
User user = userPersistence.findByPrimaryKey(userId);
Date now = new Date();
long ideaId =
counterLocalService.increment(Idea.class.getName());
Idea idea = ideaPersistence.create(ideaId);
idea.setIdeaName(ideaName);
idea.setIdeaDescription(ideaDescription);
idea.setGroupId(groupId);
idea.setCompanyId(user.getCompanyId());
idea.setUserId(user.getUserId());
idea.setCreateDate(serviceContext.getCreateDate(now));
idea.setModifiedDate(serviceContext.getModifiedDate(now));
super.addIdea(idea);
return idea;
}
Any ideas?

Categories