Reference ASP.NET control by ID in JavaScript? - javascript

When ASP.NET controls are rendered their ids sometimes change, like if they are in a naming container. Button1 may actually have an id of ctl00_ContentMain_Button1 when it is rendered, for example.
I know that you can write your JavaScript as strings in your .cs file, get the control's clientID and inject the script into your page using clientscript, but is there a way that you can reference a control directly from JavaScript using ASP.NET Ajax?
I have found that writing a function to parse the dom recursively and find a control that CONTAINS the id that I want is unreliable, so I was looking for a best practice rather than a work-around.

This post by Dave Ward might have what you're looking for:
http://encosia.com/2007/08/08/robust-aspnet-control-referencing-in-javascript/
Excerpt from article:
Indeed there is. The better solution
is to use inline ASP.NET code to
inject the control’s ClientID
property:
$get('<%= TextBox1.ClientID %>')
Now the correct client element ID is
referenced, regardless of the
structure of the page and the nesting
level of the control. In my opinion,
the very slight performance cost of
this method is well worth it to make
your client scripting more resilient
to change.
And some sample code by Dave from the comment thread of that post:
<script>
alert('TextBox1 has a value of: ' + $get('<%= TextBox1.ClientID %>').value);
</script>
The comment thread to the article I linked above has some good discussion as well.

You can change to ClientIDMode property of the control to 'Static' that will result the same ID that you give the control in the .NET code.
<asp:TextBox ID="TextBox1" ClientIDMode="Static" runat="server"></asp:TextBox>
will result:
<input name="ctl00$MainContent$TextBox1" type="text" id="TextBox1">
so you have the same ID.

Couple of thoughts on this:
1) I've had a lot of luck getting elements by css class instead of id because asp.net ids are not reliable as you stated. I use this function and it performs reasonably well:
function getElementsByClass(searchClass,node,tag) {
var classElements = new Array();
if ( node == null )
{
node = document;
}
if ( tag == null )
{
tag = '*';
}
var els = node.getElementsByTagName(tag);
var elsLen = els.length;
var pattern = new RegExp("(^|\\s)"+searchClass+"(\\s|$)");
for (i = 0, j = 0; i < elsLen; i++)
{
if ( pattern.test(els[i].className) )
{
classElements[j] = els[i];
j++;
}
}
return classElements;
}
2) jQuery helps here alot. Using jQuery you can reliably get elements where the id ends with a certain string. While this is not "the" reason to use jQuery it's definitely a plus.
3) This will be fixed in asp.net 4.0 so hang in there :-) http://weblogs.asp.net/asptest/archive/2009/01/06/asp-net-4-0-clientid-overview.aspx

I prefer data bound tags in the markup document.getElementById('<%#TextBox1.ClientID %>').value, over the use of the server side tag implementation <% = TextBox1.ClientID %>.
Server side tags prohibit you from adding controls to the dom in the code behind. This need commonly arises as you build out your application and the databound approach may save you from major rewrites.
When using server side tags also know as 'code blocks' performing this common operation
this.Form.Controls.Add(myContorl);
generates this error at run time:
The Controls collection cannot be modified because the control
contains code blocks (i.e. <% ... %>).
Unfortunately this often only become inherently obvious after you have built out your web site.
When implementing data bound control '<%#TextBox1.ClientID %>' resolve the value of control properties referenced in the markup, in the appropriate place such as the end of Page_Load data bind like this:
Page.DataBind()
Keep in mind Page.DataBind() causes child controls on the page to also DataBind, this may be an unwanted side effect if the page handles the data binding of certain child controls separately. If this is the case, data binding can be performed on the individual control like this:
TextBox1.DataBind()
An applications evolution eventually leads to some sort of base site wide functionality where you may want to add base controls, once you've peppered you website application with server side tags replacing them with databinds becomes problematic, especially when pages have been coded to handle databinding on their own.

I don't think there's a single "best practice" for doing this. There's plenty of different pretty good practices. Here's ours:
Every control which has client-side functionality renders a script block inline, directly below the markup for the control:
<span id="something_crazy_long">
control markup
</span>
<script type="text/javascript">new CustomControl('something_crazy_long');</script>
Each control has an accompanying JS like:
var CustomControl = function(id) {
this.control = document.getElementByID(id);
this.init();
};
CustomControl.prototype.init = function() {
//do stuff to wire up relevant events
};
In the codebehind, we do something like:
class CustomControl : Control
override void Render(HtmlTextWriter writer)
{
writer.WriteBeginTag("span");
writer.WriteAttribute("id", this.ClientID);
writer.Write(HtmlTextWriter.TagRightChar);
//write control markup
writer.WriteEndTag("span");
writer.WriteBeginTag("script");
writer.WriteAttribute("type", "text/javascript");
writer.Write(HtmlTextWriter.TagRightChar);
writer.Write(
string.Format("new CustomControl('{0}');", this.ClientID)
);
writer.WriteEndTag("script");
}

I do something similar to Rex M except to avoid multiple script tags I use a function in my page base class to register the controls I am going to use clientside, then spit them out to html inside 1 script tag.
You could even subclass your controls to automatically register with the function or use a boolean property to set whether you are going to use them clientside.

For 'ctl00_ContentMain_Button1' - In asp.net when page renders in the browser, first part remains same 'ctl00'. Second part is ID of ContentPlaceHolder used 'ContentMain'. Third is ID of a control 'Button1'
I liked this http://codedotnets.blogspot.in/2012/01/how-get-id-server-control-javascript.html

Oh, and I also found this, in case anyone else is having this problem.
Use a custom jQuery selector for asp.net controls:
http://john-sheehan.com/blog/custom-jquery-selector-for-aspnet-webforms/

You can get the ID by using document.getElementById method as well.

Related

how to check if html or script tags exist in input in javascript using RegEx

we want to prevent user to enter scrips or html tags input to avoid cross site script attack
for this i am writing this code but its seems not working
var preventScriptsRegEx = new RegExp("[^<>]*");
function getValue() {
return document.getElementById("myinput").value;
}
function test() {
alert(preventScriptsRegEx.test(getValue()));
}
this is inspired from this post : Prevent html tags entries in mvc textbox using regular expression
You can try creating a temporary element, set the input's value to the element's innerHTML property, and check the element's childElementCount:
function checkForHTML(text){
var elem = document.createElement('div')
elem.innerHTML = text;
return !!elem.childElementCount;
}
button.addEventListener('click', function(){
console.log(checkForHTML(input.value))
})
<input id="input">
<button id="button">Check</button>
Please don't do this. You can't just use some nifty RegExp to check for script injection. There are plenty of attack vectors where you can trick injections where RegExp simply cannot match well. This involves for example, using \u0001 UTF8 encodings or HTML entity encoding (< becomes & lt;, or & # 60; or & # x003C;) (lol in original post it even worked here...) which will pass your validation but automatically transformed so that execution is possible. I've been writing such exploits for fun, so I can guarantee you that there are almost as many ways to exploit such algorithms as there is creativity in a hackers/crackers mind.
The right way to protect yourself from such script injections/XSS is, to not trust user generated content in the first place. Do not trust "validation logic" as well. You shouldn't just accept HTML, JS or CSS code when it is somehow generated on the client side. Never. You should never save such content in a database, or transfer it by any other means and render it again. User generated content that is or could be in form of CSS, HTML or JS is evil and should be treated like a ticking nuclear bomb.
Every content that the client is sending to the server and that is re-rendered on client side in some way must not be sanitized but explicitly rendered via (htmlElement).innerText = user content (pseudo code); innerText is guaranteed to not create DOM nodes than TextNodes which is the only way to be sure that you're safe Never ever in-place render into HTML or CSS. Remark: I can also make CSS code XSS e.g. using vendor-specific CSS addons.
Example: behavior:url(script.htc); -moz-binding: url(script.xml#mycode);
Just never use .innerHTML = as well. Never let user generated code directly affect the DOM at all, never do < div > render($content) </ div > or anything like that.
For content that should be styled, use a DSL. It could be a JSON or any other DSL like Markdown etc. if you need a simple one, that splits text content from context information. Then, by code you trust, loop thru that data structure and render the HTML / DOM elements and always use .innerText or guaranteed .innerText use to render the user generated content (React for example is guaranteed to use that API except you're explicitly using innerHTML or dangerouslySetInnerHTML which is just sabotage). Also don't allow user generated content to set HTML element attributes. I can XSS that too.
Example: < a href="javascript:alert('XSS!')" / >

Recommended method to locate the current script?

I am writing a script that needs to add DOM elements to the page, at the place where the script is located (widget-like approach).
What is the best way to do this?
Here are the techniques I am considering:
Include an element with an id="Locator" right above the script. Issues:
I don't like the extra markup
If I reuse the widget in the page, several elements will have the same "Locator" id. I was thinking about adding a line in the script to remove the id once used, but still...
Add an id to the script. Issues:
even though it seems to work, the id attribute is not valid for the script element
same issue as above, several elements will have the same id if I reuse the script in the page.
Use getElementsByTagName("script") and pick the last element. This has worked for me so far, it just seems a little heavy and I am not sure if it is reliable (thinking about deferred scripts)
document.write: not elegant, but seems to do the job.
[Edit] Based on the reply from idealmachine, I am thinking about one more option:
Include in the script tag an attribute, for example goal="tabify".
Use getElementsByTagName("script") to get all the scripts.
Loop through the scripts and check the goal="tabify" attribute to find my script.
Remove the goal attribute in case there's another widget in the page.
[Edit] Another idea, also inspired by the replies so far:
Use getElementsByTagName("script") to get all the scripts.
Loop through the scripts and check innerHTML to find my script.
At the end of the script, remove the script tag in case there's another widget in the page.
Out of the box : document.currentScript (not supported by IE)
I've worked for OnlyWire which provides, as their main service, a widget to put on your site.
We use the var scripts = document.getElementsByTagName("script"); var thisScript = scripts[scripts.length - 1]; trick and it seems to work pretty well. Then we use thisScript.parentNode.insertBefore(ga, thisScript); to insert whatever we want before it, in the DOM tree.
I'm not sure I understand why you consider this a "heavy" solution... it doesn't involve iteration, it's a pure cross-browser solution which integrates perfectly.
This works with multiple copies of same code on page as well as with dynamically inserted code:
<script type="text/javascript" class="to-run">
(function(self){
if (self == window) {
var script = document.querySelector('script.to-run');
script.className = '';
Function(script.innerHTML).call(script);
} else {
// Do real stuff here. self refers to current script element.
console.log(1, self);
}
})(this);
</script>
Either document.write or picking the last script element will work for synchronously loaded scripts in the majority of web pages. However, there are some options I can think of that you did not consider to allow for async loading:
Adding a div with class="Locator" before the script. HTML classes has the advantage that duplicates are not invalid. Of course, to handle the multiple widget case, you will want to change the element's class name when done adding the HTML elements so you do not add them twice. (Note that it is also possible for an element to be a member of multiple classes; it is a space-separated list.)
Checking the src of each script element can ensure that tracking code (e.g. Google Analytics legacy tracking code) and other scripts loaded at the very end of the page will not prevent your script from working properly when async loading is used. Again, to handle the multiple widget case, you may need to remove the script elements when done with them (i.e. when the desired code has been added to the page).
One final comment I will make (although you may already be aware of this) is that when coding a widget, you need to declare all your variables using var and enclose all your code within: (JSLint can help check this)
(function(){
...
})();
This has been called a "self-executing function" and will ensure that variables used in your script do not interfere with the rest of the Web page.
Whether you drop a <script> tag in or a <div class="mywidget">, you're adding something to the markup. Personally, I prefer the latter as the script itself is only added once. Too many scripts in the page body can slow down the page load time.
But if you need to add the script tag where the widget is going to be, I don't see what's wrong with using document.write() to place a div.
I just found another method that seems to answer my question:
How to access parent Iframe from javascript
Embedding the script in an iframe allows to locate it anytime, as the script always keeps a reference to its own window.
I vote this the best approach, as it'll always work no matter how many times you add the script to the page (think widget). You're welcome to comment.
What pushed me to consider iframes in the first place was an experiment I did to build a Google gadget.
In many cases this work well (hud.js is the name of the scipt):
var jsscript = document.getElementsByTagName("script");
for (var i = 0; i < jsscript.length; i++) {
var pattern = /hud.js/i;
if ( pattern.test( jsscript[i].getAttribute("src") ) )
{
var parser = document.createElement('a');
parser.href = jsscript[i].getAttribute("src");
host = parser.host;
}
}
Also you can add individual script's name inside them.
either inside some js-script
dataset['my_prefix_name'] = 'someScriptName'
or inside HTML - in the <script> tag
data-my_prefix_name='someScriptName'
and next search appropriate one by looping over document.scripts array:
... function(){
for (var i = 0, n = document.scripts.length; i < n; i++) {
var prefix = document.scripts[i].dataset['my_prefix_name']
if (prefix == 'whatYouNeed')
return prefix
}
}
I haven't had access to internet explorer since forever, but this should work pretty much everywhere:
<script src="script.js"
data-count="30"
data-headline="My headline"
onload="uniqueFunctionName(this)"
defer
></script>
and inside script.js:
window.uniqueFunctionName = function (currentScript) {
var dataset = currentScript.dataset
console.log(dataset['count'])
console.log(dataset['headline'])
}

Can you mix ASP.Net and Unobtrusive JavaScript

Is it possible to mix the concept of Unobtrusive JavaScript with the event model of ASP.Net?
ASP.NET makes it very difficult, as every server side control requires a postback via the __doPostback javascript function.
You can make sure you are not using any server side controls, but this means you loose most of the benefits of ASP.NET.
Another option is to override the OnRender event and output different controls/javascript, but this is also quite a lot of work and defeats the purpose of ASP.NET.
You have much greater control when using ASP.NET-MVC.
yes, to a point. discounting standard controls and how they are built, there's not much stopping your from offloading all your own javascript to a separate file. the big hangup that comes to mind is referencing myControl.ClientID to render an element id in the middle of a script block. with a little planning, you can still minimize the amount of script you have to render in the page to work around this.
I realize that this question has already been answered but for anybody surfing in, I somewhat disagree with the accepted answer.
It depends on what controls you are using.
Not all the controls require JavaScript. The biggest culprit for me was always LinkButton. A regular button control does not use JavaScript at all however. On my pages, I actually use regular buttons and use CSS and JavaScript to make them into LinkButtons. The first step is to use CSS to make them look like links. If you really want to get fancy, you can detach the button, add an HTML anchor, and associate all the event handlers for the button with the anchor. This means that a user without JavaScript sees a regular button (HTML input) that is styled with CSS. Anybody using JavaScript will see an HTML link (
Also, if you use JQuery, it is very easy to select ASP.NET elements without worrying about all the extra mumbo-jumbo that ASP.NET adds to the IDs.
Example:
<asp:Button id='theButton' text='Click here' cssclass='linkbutton' runat='server' />
You can select this individual button using JQuery:
var theButton = $("input[name$='theButton']");
You can also replace everything of class 'linkbutton' with HTML anchors:
$(function() {
var buttons = $(".linkbutton");
buttons.each(function() {
var button = $(this);
var id = button.attr('id');
/*
* If a link button is not working
* it is likely because it does not
* have an ID attribute - check that
*/
if (id)
{
var text = button.attr('value');
var cssclass = button.attr('class');
button
.before("<a id='"+id+"' class='"+cssclass+"' href=''>"+text+"</a>")
.hide()
.detach();
$("a[id='"+id+"']").live('click', function(event) {
$(this).after(button);
button.click();
event.preventDefault();
});
}
});
});
Things like GridViews are a bit more work but also doable. I found that, after the initial honeymoon, I avoided those kinds of controls and just used repeaters anyway. Repeaters do not impose any nasty JavaScript either.
Anyway, it is certainly possible to do unobtrusive JavaScript with ASP.NET WebForms.

javascript in MVC Application?

I have written javascript function in my view page but it is giving me an error.I wnat to set the selected value of dropdown in the label.Please tell me where am I going wrong??
function OnSelect()
{
var label = document.getElementById("<%= lblSelection.ClientID %>");
label .Text= "You selected <b>";
}
Above is the script function
There is no text property on an element. Assuming the element refered to using label is something like a div or span, use:
function OnSelect()
{
var label = document.getElementById("<%= lblSelection.ClientID %>");
label.innerHTML = "You selected <b>";
}
Mixing Server and Client
Property Text is only available on the server Label control, but not on the client DOM element. You've mixed this a bit.
Avoid server controls in MVC
Even though you shouldn't use Asp.net web server controls with a MVC application. It's not recommended. They can be used but with caution. In your case where you don't distinguish between server and client controls I assume you're rather a beginner. No offence but I suggest you rather start and learn pure and clean MVC applications first before doing this kind of mix.
You seem to be confusing an asp:Label control on the server-side with a DOMElement on the client side. Take some time to learn the JavaScript environment and the DOM API. Then learn jQuery, which wraps the different DOM implementations with a very nice and consistent API.
Also When using MVC you shouldn't use WebForms controls unless you need to. This makes it much easier to understand what's going on in the client-side of things when your JavaScript is running.
As others have pointed out, Text is not a valid DOM property. You may be thinking of the text method that jQuery provides to set the innerHTML of an element as plain text.
However, you also end your string in with the bold tag, which leads me to believe that you plan on building out an HTML string with more content. If this is the case, you should use the following approach and build out the entire HTML string, and then update the DOM element.
var OnSelect = function() {
var html = "You selected <b>";
if ( someCondition ) {
html += "some value";
}
html += "</b>";
$("<%= lblSelection.ClientID %>").html(html);
};
On the other hand, if you mean to literally display You have selected <b>, and <b> is not supposed to be an HTML tag, then you'll need to do the following:
var OnSelect = function() {
$("<%= lblSelection.ClientID %>").text("You selected <b>");
};
Notice the use of text rather than html.

How to handle localization in JavaScript files?

I want JavaScript code to be separated from views.
I got the requirement to implement localization for a simple image button generated by JavaScript:
<img src="..." onclick="..." title="Close" />
What's the best technique to localize the title of it?
PS: I found a solution by Ayende. This is the right direction.
Edit:
I got Localization helper class which provides the Controller.Resource('foo') extension method.
I am thinking about to extend it (helper) so it could return all JavaScript resources (from "ClientSideResources" subfolder in App_LocalResources) for the specified controller by its name. Then - call it in BaseController, add it to ViewData and render it in Layout.
Would that be a good idea?
EDIT
Consider writing the necessary localized resources to a JavaScript object (hash) and then using it for lookup for your dynamically created objects. I think this is better than going back to the server for translations. This is similar to adding it via viewdata, but may be a little more flexible. FWIW, I could consider the localization resources to be part of the View, not part of the controller.
In the View:
<script type="text/javascript"
src='<%= Url.Content( "~/Resources/Load?translate=Close,Open" %>'></script>
which would output something like:
var local = {};
local.Close = "Close";
local.Open = "Open";
Without arguments it would output the entire translation hash. Using arguments gives you the ability to customize it per view.
You would then use it in your JavaScript files like:
$(function(){
$('#button').click( function() {
$("<img src=... title='" + local.Close + "' />")
.appendTo("#someDiv")
.click( function() { ... } );
});
});
Actually, I'm not too fussed about keeping my JavaScript code out of my views as long as the JavaScript code is localized in a container. Typically I'll set my master page up with 4 content area: title, header, main, and scripts. Title, header, and main go where you would expect and the scripts area goes at the bottom of the body.
I put all my JavaScript includes, including any for viewusercontrols, into the scripts container. View-specific JavaScript code comes after the includes. I refactor shared code back to scripts as needed. I've thought about using a controller method to collate script includes, that is, include multiple scripts using a single request, but haven't gotten around to that, yet.
This has the advantage of keeping the JavaScript code separate for readability, but also allows me to easily inject model or view data into the JavaScript code as needed.
Actually ASP.NET Ajax has a built-in localization mechanism: Understanding ASP.NET AJAX Localization
If you insist on keeping it separate, you could do something like:
//keep all of your localised vars somewhere
var title = '{title_from_server}';
document.getElementById('someImage').title = title;
Remember, if you use JavaScript code to initialize any text of elements, your site will degrade horribly where JavaScript isn't available.

Categories

Resources