Create edit link to document in sharepoint - c#

I have a document library in sharepoint storing a word document.
If I click on the link to the document I get a dialog box with "you want to open this file in readonly or editmode etc" and can open it in edit mode, change it, save it directly in word an the changes are saved in the document library.
The link to the file in the document library looks like this:
<a onfocus="OnLink(this)"
href="/test/DocLib2/wordtest.docx"
onmousedown="return VerifyHref(this,event,'1','SharePoint.OpenDocuments','')"
onclick="return DispEx(this,event,'TRUE','FALSE','FALSE',
'SharePoint.OpenDocuments.3','1', 'SharePoint.OpenDocuments',
'','','','1','0','0','0x7fffffffffffffff','','')"
>wordtest</a>
How do I create this link in my own web part where I have the name of the file and document library? Without just copying the above code, that wouldn't be a good idea...
Is there some "official" method to achieve this?

Unfortunately it doesn't seem like there is a better option. But at least you can sort of figure out what the function definition is. The DispEx function is defined in the core.js file (but it's easier to read in the core.debug.js). Both are in 14\Templates\Layouts\1033 directory.
Here is the function definition:
function DispEx(ele, objEvent, fTransformServiceOn, fShouldTransformExtension,
fTransformHandleUrl, strHtmlTrProgId, iDefaultItemOpen, strProgId, strHtmlType,
strServerFileRedirect, strCheckoutUser, strCurrentUser, strRequireCheckout,
strCheckedoutTolocal, strPermmask)
Here is my guess on what they mean. Please feel free to add comments to correct any mistakes or omissions:
ele - [obj] the element
objEvent - [obj] the event object
fTransformServiceOn - [bool] (unknown functionality) defaults to True
fShouldTransformExtension - [bool] (unknown functionality) defaults to False
fTransformHandleUrl - [bool] (unknown functionality) defaults to False
strHtmlTrProgId - [string] name of the ActiveXControl to try to load defaults to SharePoint.OpenDocuments.3
iDefaultItemOpen - [int] indicator of default to Edit or Read defaults to 1
strProgId - [string] name of the ActiveX Control
strHtmlType [string] (unknown functionality) defaults to empty
strServerFileRedirect - [string] (unknown functionality)
strCheckoutUser [string] the ID of the user who has checked out the document
strCurrentUser - [string] the ID of the current user
strRequireCheckout - [string] indicator whether to force a checkout
strCheckedoutTolocal - [string] indicator of whether to use the Local Drafts folder
strPermmask - [string] permissions mask for the current user defaults to 0x7fffffffffffffff
There are clearly some inconsistencies in terms of using strings and integers to represent boolean values. It's also strange that your code has 17 parameters but I can only find a function definition with 15 parameters, so I'm not sure what those last two empty strings are for. Some of that is the nature of JavaScript, but it also just looks sloppy on the part of Microsoft.
This doesn't really answer the question, hopefully it helps you or someone else.

Chad Schroeder made a blog post on how to construct the javascript function call in C#. Taking into account a couple of settings, like force checkout and open in browser or client for instance.
private string GetFileViewScript(SPFile file)
{
string text = SPUtility.MapToControl(SPContext.Current.Web, file.Name, string.Empty);
string text2 = (file.Item.ParentList.DefaultItemOpen == DefaultItemOpen.Browser) ? "1" : "0";
SPFieldLookupValue sPFieldLookupValue = file.Item["CheckedOutUserId"] as SPFieldLookupValue;
string scriptLiteralToEncode = (sPFieldLookupValue == null) ? string.Empty : sPFieldLookupValue.LookupValue;
string text3 = (SPContext.Current.Web.CurrentUser != null) ? SPContext.Current.Web.CurrentUser.ID.ToString(CultureInfo.InvariantCulture) : string.Empty;
string text4 = file.Item.ParentList.ForceCheckout ? "1" : "0";
return string.Format(CultureInfo.InvariantCulture, "return DispEx(this,event,'{0}','{1}','{2}','{3}','{4}','{5}','{6}','{7}','{8}','{9}','{10}','{11}','{12}')", new object[]
{
"TRUE",
"FALSE",
"FALSE",
text,
text2,
text,
string.Empty,
string.Empty,
SPHttpUtility.EcmaScriptStringLiteralEncode(scriptLiteralToEncode),
text3,
text4,
(string)file.Item["IsCheckedoutToLocal"],
(string)file.Item["PermMask"]
});
}
Using DispEx in a link to a SharePoint document

I end up with adding this code
return DispEx(this,event,'TRUE','FALSE','FALSE',
'SharePoint.OpenDocuments.3','1', 'SharePoint.OpenDocuments','','','',
'1','0','0','0x7fffffffffffffff','','')
to my link tag because I wasn't able to find a better solution.
If there is any, please let me know.

DispEx does not work in Chrome unless the link is within a div that contains the document type in an attribute called app:
<div class="ms-vb itx" ctxname="ctx19" id="2" app="ms-word">
<a onfocus="OnLink(this)"
href="/test/DocLib2/wordtest.docx"
onmousedown="return VerifyHref(this,event,'1','SharePoint.OpenDocuments','')"
onclick="return DispEx(this,event,'TRUE','FALSE','FALSE',
'SharePoint.OpenDocuments.3','1', 'SharePoint.OpenDocuments',
'','','','1','0','0','0x7fffffffffffffff','','')">wordtest</a>
<span class="ms-newdocument-iconouter">
<img class="ms-newdocument-icon" src="/_layouts/15/images/spcommon.png?rev=23" alt="new" title="new">
</span>
</div>
Either you need to wrap it in such a div, and be sure to insert the correct application that will open the file, or make your own list by looking at the file extension:
$('.test_links').click(function(e) {
e.preventDefault();
if (!!window.chrome) {
var extenstion = this.href.substr(this.href.lastIndexOf('.') + 1);
var prefix = '';
switch (extenstion) {
case 'doc':
case 'docx':
prefix = 'ms-word:ofv|u|';
break;
case 'xls':
case 'xlsx':
prefix = 'ms-excel:ofv|u|';
break;
}
window.location.href = prefix + this.href;
} else {
DispEx(this, e, 'TRUE', 'FALSE', 'FALSE', 'SharePoint.OpenDocuments.3', '0', 'SharePoint.OpenDocuments', '', '', '', _spPageContextInfo.userId + '', '0', '0', '0x7fffffffffffffff');
}
});

I don't remember if there is an official ability to do it with JavaScript COM, but you can use the ASP.NET HyperLink control to generate the similar link. For instance put in layout
<asp:HyperLink ID="EditHl" runat="server" Text="Edit document"/>
and in code-behind something like
EditHl.Attributes["attribute name"] = "attribute value";
just use the same values from OOTB link, but change
/test/DocLib2/wordtest.docx
to URL of your document.

Related

Is there a better way than AllowHtml attribute to allow user to input < followed by a letter in textbox ASP.NET MVC

I see this approach to allow html in a user input field:
https://www.codeproject.com/Articles/995931/Preventing-XSS-Attacks-in-ASP-NET-MVC-using-Valida
BUT what I need is not to allow HTML, I only need the user to be able to input < followed by some letters.
Is there not any solution better than [AllowHtml] for this case?
There is if security is your concern. Keep the [AllowHtml] attribute and then sanitize the particular property where the [AllowHtml] attribute was applied.
I personally use HtmlSanitizer which you can get from NuGet
pm > Install-Package HtmlSanitizer
The example usage below is referenced from their actual GitHub page (linked above).
var sanitizer = new HtmlSanitizer();
var html =
#"<script>alert('xss')</script><div onload=""alert('xss')""" +
#"style=""background-color: test"">Test<img src=""test.gif""" +
#"style=""background-image: url(javascript:alert('xss')); margin: 10px""></div>";
var sanitized = sanitizer.Sanitize(html, "http://www.example.com");
Assert.That(sanitized, Is.EqualTo(#"<div style=""background-color: test"">"
+ #"Test<img style=""margin: 10px"" src=""http://www.example.com/test.gif""></div>"));
If you are sanitizing the input on the server (usually by default in ASP.NET), you could use a little bit of Javascript in your view, before submitting the form, to encode the HTML characters.
function Encode(text)
{
var e = document.createElement("div");
e.innerText = e.textContent = text;
text = e.innerHTML;
return text;
}
console.log(Encode("<asd"));
You can use the onsubmit event to replace the element value before the submission of the form.
Here is an example of the code in action: https://jsfiddle.net/496zz5hv/3/.

Adding an image within C# code (MVC4)

I'm trying to load an image within a webgrid within some if/else logic. Essentially if the API returns true show one image if false show the other image however the code I'm using right now -
columns: grid.Columns(
grid.Column("PowerState", format: (vm) =>
{
if (vm.PowerState == "Stopped") //Choose font colour depending on the status of the VM
{
return new HtmlString("<img src ="~/Content/offstate.png">");
}
else
{
return new HtmlString("<img src =~/Content/onstate.png>");
}
}),
isn't working as intended as the URLs returned by each image are https://localhost:44300/~/Content/onstate.png and https://localhost:44300/"~/Content/offstate.png"
respectively when what I want is https://localhost:44300/Content/offstate.png
(tried removing the ~/ out of annoyance but this will return https://localhost:44300/Home/Content/onstate.png just in case somebody has that idea.)
EDIT: The solution is to use the Url.Content function as so
return new HtmlString("<img src = " + Url.Content("~/Content/offstate.png") + ">");
As suggested by Ashok!
You always need relative path to that image irrespective of current page. In such cases we need #Url.Content(...).
Look at why use #Url.Content topic and instead of providing path. Get path from #Url.Content and join to you html.

GeneralLink in Sitecore

I'm new to Sitecore.. I have created a Page template and add a field for a URL of type General Link. I have created another field for the text for the link (this is standard practice in this project).
I simply want to display the link in my user control but I just cant get it to work. This should be simple but Im going round in circles
Here's an example of the code I've tried ..
ascx :
<asp:HyperLink runat="server" ID="lnkMain"></asp:HyperLink>
ascx.cs:
lnkMain.NavigateUrl = SiteCore.Context.Item.GetGeneralLink("Link1");
lnkMain.Text = item.GetFieldValue("Link1Text");
You should be careful using linkField.Url since it it will incorrectly render internal links to Sitecore Items and Media. You should instead be using Sitecore.Links.LinkManager.GetItemUrl(item) and Sitecore.Resources.Media.MediaManager.GetMediaUrl(item) for those.
It would be better to have a helper (extension) method to return the correct url for you, based on the type of link. Take a look this Sitecore Links with LinkManager and MediaManager blog post which has the correct code you need for this.
For reference:
public static String LinkUrl(this Sitecore.Data.Fields.LinkField lf)
{
switch (lf.LinkType.ToLower())
{
  case "internal":
    // Use LinkMananger for internal links, if link is not empty
    return lf.TargetItem != null ? Sitecore.Links.LinkManager.GetItemUrl(lf.TargetItem) : string.Empty;
  case "media":
    // Use MediaManager for media links, if link is not empty
    return lf.TargetItem != null ? Sitecore.Resources.Media.MediaManager.GetMediaUrl(lf.TargetItem) : string.Empty;
  case "external":
    // Just return external links
    return lf.Url;
  case "anchor":
    // Prefix anchor link with # if link if not empty
    return !string.IsNullOrEmpty(lf.Anchor) ? "#" + lf.Anchor : string.Empty;
  case "mailto":
    // Just return mailto link
    return lf.Url;
  case "javascript":
    // Just return javascript
    return lf.Url;
  default:
    // Just please the compiler, this
    // condition will never be met
    return lf.Url;
}
}
Usage:
Sitecore.Data.Fields.LinkField linkField = item.Fields["Link1"];
lnkMain.NavigateUrl = linkField.LinkUrl();
It would be best of course to use <sc:FieldRender> control and let Sitecore handle it for you, but it looks like you do not have that option.
As of Sitecore 7.2 there is an alternative to linkField.Url:
Sitecore.Data.Fields.LinkField linkField = item.Fields["Link1"];
lnkMain.NavigateUrl = linkfield.GetFriendlyUrl();
A new LinkField.GetFriendlyUrl() method has been introduced. The method makes it easy to output a valid URL no matter what type of link the field contains. For internal links, the method returns a URL from LinkManager.GetItemUrl(). For media links, the method returns a URL from MediaManager.GetMediaUrl(). For external links, anchor links, e-mail links, and JavaScript links, the method returns the value of the LinkField.Url property. (400051)
http://techitpro.com/uncategorized/sitecore-7-2-changes/
It'd be easier if you use the Link control:
<sc:Link Field="Link1" runat="server" ID="link">
<sc:Text Field="Link1Text" runat="server" ID="linkText" />
</sc:Link>
That way, you don't have to do any code-behind stuff and you'll be able to use the Page Editor as well.
You can use below
<sc:Link ID="scLink" runat="server" Field="Your Link Field Name">
<sc:FieldRenderer ID="frTest" runat="server" FieldName="Your Text Field Name" />
</sc:Link>
It will work for you.
When you assign a value to a GeneralLink field of an item there is a field labeled "Link Description" in the Internal Link dialog that pops up. Fill in that value then use:
<sc:Link runat="server" Field="{YourFieldName}" />
That's it. Everything is "wired-up" for you, auto-magically.
You need to get the Linkfield value of the item and than get the LinkField type of that item. This will give you the type of link either an "Internal", "external", "mailto" and based on that can get the url of the link field as this is mentioned by #jammykam.
Same thing you can do to retrieve the LinkText as well.
For Reference :
public static string GetGeneralLinkText(LinkField link)
{
text = "";
if (link == null)
return false;
if (!string.IsNullOrEmpty(link.Text))
{
text = link.Text;
return true;
}
switch (link.LinkType)
{
case "internal":
if (link.TargetItem == null)
return false;
text = link["Text Field Name"];
break;
case "external":
case "mailto":
case "anchor":
case "javascript":
text = link.Text;
break;
case "media":
if (link.TargetItem == null)
return false;
Sitecore.Data.Items.MediaItem media = new Sitecore.Data.Items.MediaItem(link.TargetItem);
text = media.Name;
break;
case "":
break;
default:
return "";
}
return link["Text Field Name"];
}

mimic jQuery autocomplete with Selenium WebDriver ExecuteScript command

Note:
To answer this question, you shouldn't have to know anything about Selenium or WebDriver, just jQuery knowledge. That's where I don't have enough knowledge--precisely why I'm asking this question. :-) If you haven't heard of Selenium WebDriver, it's just a way to automate your website or web application from code (I'm using the C# client drivers in my example).
Also note, my FirefoxDriver object has native events turned on
Environment:
Below is a snippet of HTML and JavaScript to text you type in an input field autocomplete when you start typing. When you choose a value, it sets a hidden field with the id of the value chosen based on the name of the record entered into the input field. My goal is to mimic this autocomplete behavior in WebDriver by calling the ExecuteScript method to call some jQuery code. But since I know the exact value we're trying to match on in WebDriver, I want to mimic what the end-user would type into the field with this value. How can this be done?
If I can't get this working, my answer will be to just set the hidden field directly with the id. But I'd rather pass it the text so I can actually mimic what the end-user is doing. The WebDriver script will only have some or all of the text being typed (value being set in ac_variation_id), and will not have the record id being retrieved via AJAX (value being set in variation_id hidden field). Below, I'm setting both values. However, I just want a jQuery script that gets the id and sets the id, or mimics typing the value into the input.
So I have to solve it one of two ways:
- have WebDriver mimic autocomplete 100%
- have WebDriver call a JavaScript script (jQuery AJAX call) that does everything the page does except typing the value, so that the hidden field is set with the id returned for the chosen option
I just don't know how to do either.
Example jQuery script setting hidden field with id and input field with text:
Element.SetValueById(driver, "variation_id", discount.Variation); // set input field with text
Element.SetValueById(driver, "ac_variation_id", "123"); // set hidden field with id
public static void SetValueById(IWebDriver driver, string tagId, string newValue)
{
IJavaScriptExecutor js = driver as IJavaScriptExecutor;
js.ExecuteScript("$('#" + tagId + "').val('" + newValue + "')");
}
HTML code and JavaScript code for autocomplete functionality:
<link rel="stylesheet" href="http://localhost/admin/css/vanilla/jquery.ui.autocomplete.css" media="screen" type="text/css" />
<script type='text/javascript' src="http://localhost/admin/js/vanilla/jquery-ui-1.7.1.custom.min.js"></script>
<script type="text/javascript" src="http://localhost/admin/js/vanilla/jquery.ui.autocomplete.ext.js"></script>
<script type="text/javascript" src="http://localhost/admin/js/vanilla/jquery.ui.autocomplete.js"></script>
<input type="text" name="ac_variation_id" id="ac_variation_id" value="" class="autocomplete" autocomplete="off" />
<button type="button" value="clear" name="cl_variation_id" id="cl_variation_id" onclick="$('#variation_id').val('');$('#ac_variation_id').val('');" >clear</button>
<input type="hidden" name="variation_id" id="variation_id" value="" />
<script>
$('#ac_variation_id').autocomplete({
ajax: 'http://localhost/admin/discount/ajax-auto-complete/variation',
match: function(typed) {
return this.name;//.match(new RegExp("^"+typed, "i")); had to comment that out to be able to type integration_id and display name
},
insertText: function(entity) {
return entity.name +' '+ (( entity.integration_id == undefined ) ? '' : entity.integration_id);
}
}).bind("activate.autocomplete",function(e, entity){
var id = '#'+($(this).attr('id').substring(3));//remove ac_ prefix
$(id).val( entity.id );
});
</script>
Screen shot of autocomplete lookup values after typing text into the input field:
There are two things to test with the autocomplete widget: 1) typing into the text field and 2) selecting an item from the auto complete list.
These answers are in ruby, but I would suspect there is a corresponding C# version
Typing into the text field
search_term = "stackoverflow.com"
input = find('#q')
input.click
# this is the same as input.set(search_term[0..5]) I believe
search_term[0..5].chars.each do |key|
input.native.send_key(key)
end
Selecting an item from the autocomplete list (by the text of the item)
search_term = "stackoverflow.com"
selector = ".ui-menu-item a:contains(\"#{#search_term}\")"
page.execute_script " $('#{selector}').trigger(\"mouseenter\").click();"
# I have 4 auto completes on my page, so I just wait until they all gone
wait_until do
autocompletes = all(:css, '.ui-autocomplete')
autocompletes.inject(true) { |x,autocomplete| x && !autocomplete.visible? }
end
I couldn't mimic the auto-complete and selecting the option, so I'm calling the JSON GET request to get the id of the hidden field, and setting the hidden field on the match of the first that it finds (in case there are more than one).
Element.SetHiddenFieldIdViaAutoCompleteJSON(driver, "/admin/discount/ajax-auto-complete/variation", "val", discount.Variation, "id", "variation_id");
public static void SetHiddenFieldIdViaAutoCompleteJSON(IWebDriver driver, string requestPage, string queryParam, string queryParamValue, string jsonObjectProperty, string hiddenFieldId)
{
IJavaScriptExecutor js = driver as IJavaScriptExecutor;
js.ExecuteScript("$.ajax({ url: '" + Config.SITE_URL + requestPage + "',data:{'" + queryParam + "':'" + queryParamValue + "'},dataType:'json',type: 'GET',contentType: 'application/json',success: function(jsonObject) { $('#" + hiddenFieldId + "').val(jsonObject[0]." + jsonObjectProperty + "); } });");
}
Using the Selenuim IDE, and exporting to Java code, I adapted the results to following function so that I can choose which of my Autocomplete Comboboxes to change. ( This also in a 'Base' class, that all my PageObjets extend.
public BasicPage selectComboBox(int buttonIndex, String selection) {
driver.findElement(By.xpath("(//button[#type='button'])[" + buttonIndex + "]")).click();
driver.findElement(By.xpath("//html/body/ul/li/a[. = \"" + selection + "\"]")).click();
// delay till the selected element is visible
WebElement duh = (new WebDriverWait(driver, 10)).until( visibilityOfElementLocated(By.xpath("(//button[#type='button'])[" + buttonIndex +"]" )) ) ;
return this;
}

function in JSON format - possible?

I have the below JSON (been snipped for space), as you can see in the "test" and "tooltip" I have a property that needs to contain a function "formatter" (note this JSON is read in from an XML file and converted to JSON in .NET)
{
"test": {
"formatter": function(){return '<b>'+ this.point.name +'<\/b>: '+ this.y +' %';}
},
"title": {
"align": "center",
"text": "Your chart title here"
},
"tooltip": {
"formatter": function(){return '<b>'+ this.point.name +'<\/b>: '+ this.y +' %';}
}
}
Unfortunatly I'm getting an error on the ASPX page that produces the JSON file
There was an error parsing the JSON document. The document may not be well-formed.
This error is due to the fact that the bit after the "formatter" is not in quotation marks as it thinks it's a string. but If I put a string around it then the front end html page that uses the JSON doesn't see the function.
Is it possible to pass this as a function and not a string?
Many thanks.
Edit:
Thanks for the quick replys. As I said I know that the above isn't correct JSON due to the fact that the "function(){...}" part isn't in quote marks. The front end that reads the JSON file is 3rd party so I was wondering how I could pass the function through, I understand about the problems of injection (from a SQL point of view) and understand why it's not possible in JSON (not worked with JSON before).
If you passed it as a string you could use Javascripts EVAL function, but EVAL is EVIL.
What about meeting it half way and using Object Notation format ?
This is a template jquery plugin that I use at work, the $.fn.extend shows this notation format.
/*jslint browser: true */
/*global window: true, jQuery: true, $: true */
(function($) {
var MyPlugin = function(elem, options) {
// This lets us pass multiple optional parameters to your plugin
var defaults = {
'text' : '<b>Hello, World!</b>',
'anotherOption' : 'Test Plugin'
};
// This merges the passed options with the defaults
// so we always have a value
this.options = $.extend(defaults, options);
this.element = elem;
};
// Use function prototypes, it's a lot faster.
// Loads of sources as to why on the 'tinternet
MyPlugin.prototype.Setup = function()
{
// run Init code
$(this.element).html(this.options.text);
};
// This actually registers a plugin into jQuery
$.fn.extend({
// by adding a jquery.testPlugin function that takes a
// variable list of options
testPlugin: function(options) {
// and this handles that you may be running
// this over multiple elements
return this.each(function() {
var o = options;
// You can use element.Data to cache
// your plugin activation stopping
// running it again;
// this is probably the easiest way to
// check that your calls won't walk all
// over the dom.
var element = $(this);
if (element.data('someIdentifier'))
{
return;
}
// Initialise our plugin
var obj = new MyPlugin(element, o);
// Cache it to the DOM object
element.data('someIdentifier', obj);
// Call our Setup function as mentioned above.
obj.Setup();
});
}
});
})(jQuery);

Categories

Resources