Unobtrusive JavaScript with server side values, best practice? - asp.net

I'm used to do doing things like this:
<a href="Javascript:void(0);" onclick="GetCommentForGeneralObservation(<%# Eval("ID") %>)">
That would be in a repeater or such like btw. However I an now trying to use unobtrusive JavaScript and hence get rid of any JS in the markup. I was trying to figure out what the best practice is in cases like this? I've used attributes and then got the value using JQuery to pass to the AJAX call but it seems a bit of a hack.
Edit in light of first reply:
I was thinking more of the
Separation of functionality (the "behavior layer") from a Web page's structure/content and presentation.
part of unobtrusive JS as I understand it.
This happens to be for an application where I don't have to worry about Javascript being turned off on the client, it's hosted internally at my company. What I was getting at was how would I get the value from Eval("ID") into the JS call if I were to attach the onclick event in a separate .js file via JQuery.
Sorry for not being clearer. I appreciate the need for progressive enhancement in public facing apps.

In HTML 5 you are allowed to define your own attributes prefixed with 'data-'
e.g.
<a
class="comment"
data-rendered-id="<%# Eval("ID") %>"
href="/getCommentForGeneralObservation/<%# Eval("ID") %>" >
And then use that attribute in jQuery in your click event handler.
$(".comment").click(function () {
var id = $(this).attr("data-rendered-id");
return GetCommentForGeneralObservation(id);
});
This will work in most pre-HTML5 browsers too as long as they support jQuery.
Note that in unobtrusive javascript you really should support non-javascript browsers, hence you need an href attribute that works as well.

I'd start with:
<a href="/getCommentForGeneralObservation/<%# Eval("ID") %>" class="getCommentForGeneralObservation">
and then attach an event handler that looked something like this:
function (event) {
var href = this.href;
var id = href.search(/\/(\d+)/);
return GetCommentForGeneralObservation(id);
};

Unobtrusive means you don't depend on Javascript for functionality and therefore your code is extended with Javascript rather than replaced by it.
In your case, you're embedding Javascript directly in the link, and worse off, the link won't work at all without Javascript enabled. Instead you'll want something like this, which is pure HTML without any reference to Javascript:
<a id="commentLink" href="comment.asp">Get Comment</a>
And your Javascript (assuming you're not using a framework) would be something like:
function GetCommentForGeneralObservation(evt) {
// Extra javascript functionality here
}
document.getElementById("commentLink").onclick = GetCommentForGeneralObservation;
With Jquery I believe you could just do:
$("#commentLink").click(GetCommentForGeneralObservation);

I'm going to re-answer this because I understand the question now and the approach I usually use is different from what has been suggested so far.
When there's no way to avoid having dynamic content, I will usually do the following:
<script type="text/javascript">
var myApp = {commentId:<%# Eval("ID") %>};
</script>
<script type="text/javascript" src="myAppScript.js"></script>
Now, in myAppScript.js, you can use myApp["commentId"] wherever you need that ID to be referenced. (I use a dictionary so as to not pollute the global namespace)
The advantage of this approach is that your myAppScript.js is still completely static and so you can serve it very fast. It also has the advantage of keeping the relevant information out of the URL and you can use Javascript objects, which can help a lot with complex and/or hard-to-parse data.
The disadvantage is that it requires inline Javascript, which isn't much of a disadvantage unless you're a web perfectionist.
I also really like DanSingerman's approach, which is more suited if your data is specific to a tag.

You might wish to use JQuery metadata plugin, or the core data function.
http://docs.jquery.com/Core/data
http://plugins.jquery.com/project/metadata

You could use the rel attribute of the anchor tag to store the value you want to associate with the link.
Click Here
Then in your jQuery code you can check the rel attribute to get the value you're looking for.
$(".comment").click(function () {
return GetCommentForGeneralObservation(this.rel);
});
(Pretty sure this is accurate for jQuery, but I'm more of a MooTools guy...)

How about making sure it is fully unobtrusive?
In the head of the HTML document
<script src="path/to/php/or/other/equivalent/server/side/file"></script>
In the path/to/php/or/other/equivalent/server/side/file:
var SiteServerVariablesEtc = {
someProperty: <?php echo "hello" ?>
}
In your normal JS file:
var myVar = SiteServerVariablesEtc.someProperty;

Related

Checkbox click event not firing

My ASP .Net page has a repeater that is loaded from AJAX. This AJAX page repeater has a column of checkboxes. The repeater is inside a html table.
<input id="chkOptionSelected" runat="server" enableviewstate="false"
type="checkbox" data-bind="click: Add" />
On the main page, I have a label whose value is computed by a JavaScript function. My view model:
function VM() {
var self = this;
self.totalSqft = ko.observable(TotalSqFt);
self.Add = function () {
self.totalSqft(TotalSqFt);
return true;
};
}
ko.applyBindings(new VM());
TotalSqFt is a global variable. The label is:
<asp:Label ID="lblTotalSqFt" runat="server" ClientIDMode="Static" data-bind="text: totalSqft"></asp:Label>
The click event computes the new total in a javascript function. All the view model needs to do is update the label with the new value.
What am I doing wrong? Is it because the checkbox is inside of AJAX content? I can see it all in the view source.
like #Jeroen said, asp:Lable will processed by server and render differently at client side.
so instead you can use normal html label and use runat="server" if you want to access it at server
check this working demo http://jsfiddle.net/91mek1tk/
The direct cause of your issue is most likely that data-bind on an asp:Label is not rendered. You would need to call Attributes.Add or something similar to add it.
Having said that, you're mixing Webforms and client-side heavy tech like KnockoutJS in a way that will probably negate the advantages you'd get from using KO, or worse cause lots of inconvenient cases like the one you're having now. I suggest either moving away from asp controls to more html-oriented controls (like you did with the first input tag), or dropping KO in favor of other, simpler client side technology (which seems more appropriate anyways, since you're currently using KO merely to handle clicks, whereas it excels mostly at two-way data-binding tasks).
Try this for your javascript function:
function VM() {
var self = this;
self.totalSqft = ko.observable("totalSqft");
self.Add = function () {
self.totalSqft("totalSqft");
return true;
};
}
ko.applyBindings(new VM());
Thank you, JAG. I tweaked the demo and got it working. I had val instead of text for my label in one of the lines and hence was not seeing the reflected value. It's working now. Thanks, everyone.

ClientId is different in localhost and the webserver

I am facing a new kind of problem.
I am using the jQuery to fill the state dropdown on the change of country dropdown and the code of the jquery is on a js file so i bind the static client id like ct100_ddlCountry, this is working properly on the localhost but when i host this website to web server it not working because the client generating on the server is _ct100_ddlCountry.
Please tell me something if anyone has an idea about this. I am new to this kind of problem.
Thanks to all.
If you can't upgrade to .NET 4.0 for clean id's, I wrote a small lib and shoved it on CodePlex to serialize controls to a JSON array on the client.
http://awesomeclientid.codeplex.com/
http://www.philliphaydon.com/2010/12/i-love-clean-client-ids-especially-with-net-2-0/
It serializes the controls and outputs some JavaScript like:
<script type=”text/javascript”>
//<![CDATA[
var controls = {
"txtUserName": "ctl00_ContentPlaceHolder1_txtUserName",
"txtEmail": "ctl00_ContentPlaceHolder1_txtEmail",
"btnSubmit": "ctl00_ContentPlaceHolder1_btnSubmit"
};
//]]>
</script>
Which then allows you to access controls like:
<script type=”text/javascript”>
//<![CDATA[
var element = document.getElementById(controls.btnSubmit);
//]]>
</script>
No need to write spaghetti code :)
Edit: Alternatively, you can use jQuery selectors to do something like:
var control = $('[id*=txtEmail]');
It is not normally good practice to hard code control ids in your js script includes or html source.
Try using something like this:
JS
function DoChange(controlid) {
$("#"+controlid);
}
HTML
<select onchange='DoChange("<%= ddlCountry.ClientID %>");' />
It means if you move your control around in your control tree, then you dont break your code, and it should work on your localhost and IIS
UPDATE
Or like this
JS
function DoChange(control) {
$(control);
}
HTML
<select onchange="DoChange(this);" />
If it is ASP.Net 4.0 then you can use ClientIDMode="Static" to make sure that only IDs provided by you are there on final markup.
Or you can use something like $('id$=country'). $ is used to match the end of Id, but I am sure that is something not optimal.

ASP.Net Ajax $find() Jquery Equivalent

Is there a JQuery equivalent of ASP.Net Ajax's $find() function?
$() != $find()
There is not since $find returns the AJAX component related to the DIV element, and not the DOM element. You could build your own plugin that shortcuts the find method.
Microsoft created $find as a way to link their ASP.NET AJAX components to the DOM.
There is not a 1to1 equivalent but what you want is $('selector')
Check out the docs on the different selectors
$find('MyComponent') would be $('#MyComponent')
$find('MyComponent',div) would be $(div).find('#MyComponent')
I'd just do the following, no muss, no fuss, straight to the point.
$('#' + <%=myControl.ClientID%>)
If you want to find an element by its ASP.NET code ID rather than the generated ClientID (ctl00_RealId) then you can use this function. It just looks for elements that have an ID that ends with _{the real ID here}:
var $$ = function (id, context) {
var $ = (jQuery) ? jQuery : return ;
var el = $("#" + id, context);
if (el.length < 1)
el = $("[id$=_" + id + "]", context);
return el;
}
For example, say your ID in your code is pnlSuccess, say a panel:
<asp:Panel ID="pnlSuccess" runat="server"></asp:Panel>
But in the rendered code it comes out as: ctl00_content_ctl00_pnlSuccess
calling $$("pnlSuccess") will find that rendered panel.
I know it's a LOOOOOOOONG time overdue, but I think I have the kind of solution you're looking for. If I'm correct, you're looking for a $find jQuery substitute because you don't know the ID of the element (which $find doesn't have selectors as far as I know but jQuery is awesome with). I just ran into this issue using Telerik controls on a SharePoint page, so my object ID is some long crazy mess, and since Sharepoint 2010 is on .NET 3.5, I can't use a static ID.
The solution is simple, but it racked my brain for a while. $find() is searching by ID, which luckily we can return as a string through jQuery: $("elem").attr("id"). So basically what we do is use jQuery inside the $find function and it works. Here's a sample from my project:
var contextMenu = $find($("[id*=mnuContext]").attr("id"));
This worked for me, and is going to help me out a lot with the rest of my SharePoint solution.

jQuery and ASP.NET names

What is the correct way to get a control that was rendered by ASP.NET with jQuery?
Example: I have a checkbox that was generated like this:
<input id="ctl00_Content_chkOk" type="checkbox" name="ctl00$Content$chkOk" />
If I want to select it to get if it's checked, I tried this:
$('form input[name=chkOk]').attr("checked")
and
$('chkOk').attr("checked")
But it didn't work, I had to do it like this
$('form input[name=ctl00$Content$chkOk]').attr("checked")
And I guess this would've worked too:
$('ctl00$Content$chkOk').attr("checked")
Is this the correct way to do it? Is there another selector I can use?
You can use the server side control ClientID property:
var isChecked = $('#<%=YourCheckBox.ClientID%>').attr("checked");
or you could use the "ends with" selector: attribute$=value
var isChecked = $('form input[name$=chkOk]').attr("checked");
I always used this notation
$('#ctl00_Content_chkOk:checked').length; // will evaluate as true when checked
You can do
$("input[id*=_chkOk]").attr('checked');
$('#ctl00_Content_chkOk').attr('checked');
$('form #ctl00_Content_chkOk').attr('checked');
$('form input#ctl00_Content_chkOk').attr('checked');
$('form input#ctl00_Content_chkOk[type="checkbox"]').attr('checked');
Pick one ;)
Yeah, JQuery only cares about what is rendered on the page, whether through the ASP.NET engine or through simple HTML, so you'll have to access it via the id or name. The two bottom versions you used would work as well as $('#ct100_Content_chk0k').attr("checked").
Adam
I tend to go back and forth between two ways. I either use the control's clientID property as mentioned by CMS, or I generate the javascript code in my code behind and use the page's ClientScriptManager to write it to the browser.

How can I use a traditional HTML id attribute with an ASP.net runat='server' tag?

I am refactoring some CSS on a website. I have been working on, and noticed the absence of traditional HTML IDs in the code.
There is heavy use of CssClass='…', or sometimes just class='…', but I can't seem to find a way to say id='…' and not have it swapped out by the server.
Here is an example:
<span id='position_title' runat='server'>Manager</span>
When the response comes back from the server, I get:
<span id='$aspnet$crap$here$position_title'>Manager</span>
Any help here?
Use jQuery to select the element:
$("span[id$='position_title']")....
jQuery's flexible selectors, especially its 'begins with'/'ends with selectors' (the 'end with' selector is shown above, provide a great way around ASP.NET's dom id munge.
rp
The 'crap' placed in front of the id is related to the container(s) of the control and there is no way (as far as I know) to prevent this behavior, other than not putting it in any container.
If you need to refer to the id in script, you can use the ClientID of the control, like so:
<script type="text/javascript">
var theSpan = document.getElementById('<%= position_title.ClientID %>');
</script>
Most of the fixes suggested her are overkill for a very simple problem. Just have separate divs and spans that you target with CSS. Don't target the ASP.NET controls directly if you want to use IDs.
<span id="FooContainer">
<span runat="server" id="Foo" >
......
<span>
</span>
You can embed your CSS within the page, sprinkled with some server tags to overcome the problem. At runtime the code blocks will be replaced with the ASP.NET generated IDs.
For example:
[style type="text/css"]
#<%= AspNetId.ClientID %> {
... styles go here...
}
[/style]
[script type="text/javascript"]
document.getElementById("<%= AspNetId.ClientID %>");
[/script]
You could go a bit further and have some code files that generate CSS too, if you wanted to have your CSS contained within a separate file.
Also, I may be jumping the gun a bit here, but you could use the ASP.NET MVC stuff (not yet officially released as of this writing) which gets away from the Web Forms and gives you total control over the markup generated.
Ok, I guess the jury is out on this one.
#leddt, I already knew that the 'crap' was the containers surrounding it, but I thought maybe Microsoft would have left a backdoor to leave the ID alone. Regenerating CSS files on every use by including ClientIDs would be a horrible idea.
I'm either left with using classes everywhere, or some garbled looking IDs hardcoded in the css.
#Matt Dawdy: There are some great uses for IDs in CSS, primarily when you want to style an element that you know only appears once in either the website or a page, such as a logout button or masthead.
The best thing to do here is give it a unique class name.
You're likely going to have to remove the runat="server" from the span and then place a within the span so you can stylize the span and still have the dynamic internal content.
Not an elegant or easy solution (and it requires a recompile), but it works.
.Net will always replace your id values with some mangled (every so slightly predictable, but still don't count on it) value. Do you really NEED to have that id runat=server? If you don't put in runat=server, then it won't mangle it...
ADDED:
Like leddt said, you can reference the span (or any runat=server with an id) by using ClientID, but I don't think that works in CSS.
But I think that you have a larger problem if your CSS is using ID based selectors. You can't re-use an ID. You can't have multiple items on the same page with the same ID. .Net will complain about that.
So, with that in mind, is your job of refactoring the CSS getting to be a bit larger in scope?
I don't know of a way to stop .NET from mangling the ID, but I can think of a couple ways to work around it:
1 - Nest spans, one with runat="server", one without:
<style type="text/css">
#position_title { // Whatever
}
<span id="position_titleserver" runat="server"><span id="position_title">Manager</span></span>
2 - As Joel Coehoorn suggested, use a unique class name instead. Already using the class for something? Doesn't matter, you can use more than 1! This...
<style type="text/css">
.position_title { font-weight: bold; }
.foo { color: red; }
.bar { font-style: italic; }
</style>
<span id="thiswillbemangled" class="foo bar position_title" runat="server">Manager</span>
...will display this:
Manager
3 - Write a Javascript function to fix the IDs after the page loads
function fixIds()
{
var tagList = document.getElementsByTagName("*");
for(var i=0;i<tagList.length;i++)
{
if(tagList[i].id)
{
if(tagList[i].id.indexOf('$') > -1)
{
var tempArray = tagList[i].id.split("$");
tagList[i].id = tempArray[tempArray.length - 1];
}
}
}
}
If you're fearing classitus, try using an id on a parent or child selector that contains the element that you wish to style. This parent element should NOT have the runat server applied. Simply put, it's a good idea to plan your structural containers to not run code behind (ie. no runat), that way you can access major portions of your application/site using non-altered IDs. If it's too late to do so, add a wrapper div/span or use the class solution as mentioned.
Is there a particular reason that you want the controls to be runat="server"?
If so, I second the use of < asp : Literal > . . .
It should do the job for you as you will still be able to edit the data in code behind.
I usually make my own control that extends WebControl or HtmlGenericControl, and I override ClientID - returning the ID property instead of the generated ClientID. This will cause any transformation that .NET does to the ClientID because of naming containers to be reverted back to the original id that you specified in tag markup. This is great if you are using client side libraries like jQuery and need predictable unique ids, but tough if you rely on viewstate for anything server-side.
If you are accessing the span or whatever tag is giving you problems from the C# or VB code behind, then the runat="server" has to remain and you should use instead <span class="some_class" id="someID">. If you are not accessing the tag in the code behind, then remove the runat="server".

Resources