another technical blog...technically

Showing posts with label Publishing. Show all posts
Showing posts with label Publishing. Show all posts

Thursday, August 6, 2015

SharePoint 2013 'Page not found (404)' with catalog item page and standard page

If you read these blog post:
SharePoint 2010 ‘Page not found (404)’ page the way it should be and this Inconvenient Catalog Item Page and ‘Page not found’ (404) experience from the master Waldek Mastykarz , and you're here, you're one step closer to the solution.
This blog post extends the Mastykartz strategy with just few lines of code.
 Let's assume you developed those components:
  1. PageNotFoundHttpModule from SharePoint 2010 ‘Page not found (404)’ page the way it should be
  2. PageNotFoundWebPart from SharePoint 2010 ‘Page not found (404)’ page the way it should be 
  3. Something like MaventionCatalogItemReuseWebPart from Inconvenient Catalog Item Page and ‘Page not found’ (404) experience
PageNotFoundHttpModule and PageNotFoundWebPart works also for SharePoint 2013.
MaventionCatalogItemReuseWebPart simply redirects to site collection page not found url, but, since this is a server side redirection, the HTTP status code is 302 and the redirect location it's also well known, so i just modified PageNotFoundHttpModule in this way.
public class PageNotFoundHttpModule : IHttpModule
    {
        #region Const
        const string URL_RELATIVE_PAGENOTFOUND = "/Pages/PageNotFoundError.aspx";
        #endregion

        #region Fields
        private HttpApplication webApplication;
        #endregion

        #region Public methods        
        public void Init(HttpApplication context)
        {
            webApplication = context;
            webApplication.PreSendRequestContent += new EventHandler(webApplication_PreSendRequestContent);
        }

        public void Dispose()
        {
        }
        #endregion

        #region Event handlers
        void webApplication_PreSendRequestContent(object sender, EventArgs e)
        {
            HttpResponse response = webApplication.Response;
            HttpRequest request = webApplication.Request;

            if ((response.StatusCode == 404 && string.Compare(request.Url.AbsolutePath, URL_RELATIVE_PAGENOTFOUND, StringComparison.InvariantCultureIgnoreCase) != 0) ||
               (response.StatusCode == 302 && string.Compare(response.RedirectLocation, URL_RELATIVE_PAGENOTFOUND, StringComparison.InvariantCultureIgnoreCase) == 0))
            {
                webApplication.Server.TransferRequest(URL_RELATIVE_PAGENOTFOUND);
            }
        }
        #endregion
So what i added is essentially a new or condition.
Share:

Wednesday, July 29, 2015

Publishing by search: what about video in HTML field?

I think the title it's someway self-explaing: we know that when you add a video to a HTML field, SharePoint doesn't use HTML5 video tag, but it installs a web part which show the video itself using a player.
A video, in SharePoint, it's not simply a file, but a folder which contains that video file, maybe its renditions and other stuff i'm not interested in.
Video and assets generally are in a Asset Site which could be accessed from catalog, public site and many others.
If we want to simply use HTML5 video tag, we first need to find the correct video file, then we have to create a reusable content with these features:
  • User can select the video using the OOB AssetPicker
  • Reusable content automatically finds the video file
  • Reusable content automatically generates HTML5 video tag
So, let's first declare a reusable content for video with this HTML code


Why in the world i'm using an img tag?
Because i want the user to have a placeholder like this


Then include a JS (in master page or page layout: up to you) which:
  1. Binds a click event to every asset_video class object 
  2. On click opens OOB Asset Picker and let user choose an asset
  3. On Asset Picker close event, finds the video URL using REST
  4. Writes the HTML5 tag using the URL
 Below some code you can use
jQuery(document).ready(function () {
    initAddVideoTemplate();
    SP.SOD.executeOrDelayUntilScriptLoaded(function () {
        SP.SOD.executeOrDelayUntilScriptLoaded(function () {
            var ctx = SP.ClientContext.get_current();
            var site = ctx.get_site();
            ctx.load(site);
        }, "cui.js");
    }, "sp.js");
});

// Adds a listener on every HTML element with class "asset_video"
// You can add your own classes in this obj array, lister for all elements will be created
// The listener will make asset picker to be opened when you click on a asset_video
function initAddVideoTemplate() {
    var count = 0;
    var obj = { 'asset_video': '' }
    jQuery.each(obj, function (index, value) {
        jQuery('body').on('click', 'img[class="' + index + '"]', function () {
            var className = this.className;
            var idOldVideo = "reusableContentInsertVideo-" + rc_generateUUID();
            this.setAttribute('id', idOldVideo);
            if (!count) {
                jQuery.getScript('/_layouts/15/AssetPickers.js', function () { insertVideo(idOldVideo, className, value); });
                count += 1;
            } else insertVideo(idOldVideo, className, value);
        });
    });
};

// Asset picker configuration
function insertVideo(idOldVideo, className, value) {
    var assetPicker = new AssetPickerConfig("");
    assetPicker.ClientID = "";
    assetPicker.DefaultAssetLocation = "";
    assetPicker.DefaultAssetImageLocation = "";
    assetPicker.CurrentWebBaseUrl = "";
    assetPicker.AllowExternalUrls = "";
    assetPicker.ManageHyperlink = false;
    assetPicker.AssetUrlClientID = idOldVideo;
    assetPicker.AssetType = "Media";
    assetPicker.ReturnItemFields = 'Title,Name,Label,SourceUrl,FileRef';
    assetPicker.ReturnCallback = function () {
  
  // Self explaining variable names
        var video_AbsoluteUrl = arguments[0];
        var video_FolderName = arguments[1];

        var pathArray = video_AbsoluteUrl.split('/');

        var assetSite_Url;
        var assetList_DocLib;

        if (pathArray.length >= 5) {
            assetSite_Url = pathArray[0] + "//" + pathArray[2];
            assetList_DocLib = pathArray[3];

   // Helps you to rebuild the folder relative URL
            for (i = 4; i < pathArray.length - 1; i++) {
                assetList_DocLib = assetList_DocLib + "/" + pathArray[i];
            }

            // Finds the HTML img tag to substitute
            var idCalled = jQuery(this).attr('AssetUrlClientID');

            if (undefined !== arguments &&
    arguments.length > 3 &&
    assetSite_Url !== undefined &&
    assetList_DocLib !== undefined) {

    // REST url generation
    // Using URL and folder, you can request the first file of the video folder, which is the default video rendition
                restUrl = assetSite_Url + "/_api/Web/GetFolderByServerRelativeUrl('/" + assetList_DocLib + "/" + video_FolderName + "')/Files?$select=ServerRelativeUrl,Name&top=1";

                jQuery.ajax({
                    url: restUrl,
                    type: "GET",
                    crossDomain: true,
                    dataType: "json",
                    headers: { "Accept": "application/json; odata=verbose" },
                    success: function (data) {
                        // If success, generate che HTML
                        var response = data.d.results;
                        if (response.length > 0) {
                            var videoUrl = assetSite_Url + response[0].ServerRelativeUrl;
                            var fileName = response[0].Name;
                            var extension = fileName.split('.').pop();

                            if (jQuery("#" + idCalled).length > 0)
                                jQuery("#" + idCalled).first().replaceWith("");
                        }
                    },
                    error: function (data) {
                        alert('Error');
                    }
                });
            }
        }
        else {
            alert("Error");
        }
    };

    var imageAsset = new LinkAsset("");
    imageAsset.LaunchModalAssetPicker(assetPicker);
};

// Generate a ID 
function rc_generateUUID() {
    var d = new Date().getTime();
    var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
        var r = (d + Math.random() * 16) % 16 | 0;
        d = Math.floor(d / 16);
        return (c == 'x' ? r : (r & 0x7 | 0x8)).toString(16);
    });
    return uuid;
};

Saturday, June 20, 2015

SharePoint 2013 Azure farm accessible from outside part 2: what about friendly URLs

Have you read this? SharePoint 2013 Azure farm accessible from outside
I discovered it doesn't work for friendly urls, it shows hyperlinks targeting site collection, so it's not possible to reach other pages. Moreover you'll get this error if you only create a outbuond rule for URL rewriting because of the gzip compression.
Outbound rewrite rules cannot be applied when the content of the HTTP response is encoded ("gzip").
What you have to do is something like this:
  • Introduce a outbound rule which rewritest some links
  • Introduce rewrite rules for gzip compression
The example below assumes:
  • Local site collection is: http://sitecollection.example.cloud
  • Public azure url is: http://publicsite.example.azure

    
        
            
                
                    
                    
                        
                        
                    
                    
                
            

            
                
                    
                    
                
                
                    
                    
                

                
                    
                        
                    
                    
                        
                    
                
                    
        
    

And that's all... for now!
Share:

Monday, June 8, 2015

SharePoint 2013 Azure farm accessible from outside

I gave you the possibility to create a SharePoint 2013 farm from scratch with ASPM... don't you know what it is? You can download it here
So, let's assume you have a site collection (http://sitecollection.example.cloud) on this SharePoint 2013 farm e you want to make it accessible to your customer, manager and so on...
Open IIS Manager and head to Default Web Site, then URL Rewrite (if you don't have this option on your IIS please downlad URL Rewrite module from Web Platform Installer).

Then, create a new blank rule

like this

And that's all.
I noticed you will have some problems with users adding, so it's possible you can experience some problems with other features.
Maybe further settings could be necessary.

Tuesday, September 23, 2014

Restrict content types and related page layouts on list pages new button

This is one of the most recurrent tasks... dealing with pages list.
You created your pages list content types, associated page layouts to those content types, and customers want to use only those content type and page layouts or simply.
Ok, let's assume you have Example 04 deployed (if  it's your first time on this blog, download VLibs here), this is what you see in Pages list.

The key to accomplish this task is divide-et-impera, so what about the new page menu?
Work on this property
IList<SPContentType> contentTypeInstances2Show = ...
pagesList.RootFolder.UniqueContentTypeOrder = contentTypeInstances2Show;
pagesList.RootFolder.Update();
Assigning a  SPContentType list to UniqueContentTypeOrder, we give the order of the content type available for creating, and we can also exclude some of them from the menu.

The second step is to make a selection of page layouts available, the method is:
PublishingWeb publishingWeb = PublishingWeb.GetPublishingWeb(web); 
PageLayout[] pageLayouts = publishingWeb.GetAvailablePageLayouts();

PageLayout[] pageLayouts2Show = ...
publishingWeb.SetAvailablePageLayouts(pageLayouts2Show .ToArray(), false);
So you have first to select content type related page layouts, just querying the publish website.
And this is what we will get
New document menu

Available page layouts

If you need the code, as usual this is the link. Go get it! and have a look at Example 06 ;) .

Monday, September 1, 2014

Device channel panels and web parts walktrough: the JS and content search query BUG

Let's continue from the first episode, haven't you read it? Check this link, download the code and load Example 04... Here we are and here is a smal recap from the last article...
A long time ago in a galaxy far, far away... A young SharePoint padawan was trying to use device channel panels to contitionally load web parts and app parts...
He discovers some unexpected behaviours and he found workarounds, and he was able to accomplish this task, but then he had to add javascript to the web parts and a new challenge begun...
/mode joke off
Why JS in web parts? Do you know content search query web part (we are dealing one of the new feature of SP2013) ? Well, it uses JS a lot. It means that, if you want to load content search query web part, choosing what to load according with the device channel, you are in a big trouble.

Problem repro steps
Let's use the DeviceChannelArticle_OOB page layouts i developed in Example 04 (i prefer the OOB workaround in order not to introduce unsupported code in this demo).
Let's simplify the problem and, starting from the Example 04, write a stupid alert JS script in every web part.
alert('This is the Internet Explorer web part script');
alert('This is the Mozilla Firefox web part script');
Now display the page (it's up to you to use Internet Exlorer or Mozilla Firefox).

What's happening? Even if you load the web part using device channel panels, you see alerts for both web parts... again, not a expected behaviour. The risk is to load extra data, extra scripts and extra stuffs... not so good.
Solutions
The first solution is to load JS detecting user agent from JS itself... yeah that's quite simple but... why use device channels? Rework web parts in order to use just server side code? It can be done, but you lose functionalities. I got an idea, why don't use a HttpModule to cut all the code of the wrong device channel panel? Since we are talking about a SP2013 recognized BUG (so it will be solved), we can think about HttpModule we can just plug'n'play until official fix will be realeased.

First, change page layout, switch to DeviceChannelArticle_JS, what's different from DeviceChannelArticle_OOB?

No more display/edit panel, just fields wrapped with HTML comment tags and no more device channel panels.

HttpModule will recognize user agent, understand what HTML code to cut according to tags above... yes i am reinventing the wheel.
Let's load the HttpModule using Example 05 feature to see the code in action, please be aware you need to run these powershell commands otherwise you have to edit web.config with your hands.
$contentService = [Microsoft.SharePoint.Administration.SPWebService]::ContentService
$contentService.RemoteAdministratorAccessDenied = $false
$contentService.Update()
You can see i created a abstract class named DeviceChannelPanelHttpModule in VLibs project i've realized with DeviceChannelArticleJSHttpModule in VLibs.Examples, in order to make the module aware about device channels, inclusion rules and wrapping tags.
Let's have a look to the astract class...
public abstract class DeviceChannelPanelHttpModule : IHttpModule
{
 #region Public methods
 public void Init(HttpApplication application)
 {
  application.PostRequestHandlerExecute += new EventHandler(ApplicationPostRequestHandlerExecute);
 }

 public void Dispose()
 {
 }
 #endregion 

 #region Protected methods
 protected void ApplicationPostRequestHandlerExecute(object sender, EventArgs e)
 {
  HttpContext httpContext = HttpContext.Current;

  if (httpContext.Response.ContentType == "text/html" && BrowserRequestNeedsToBeFiltered(httpContext.Request))
  {
   httpContext.Response.Filter = LoadFilter(httpContext);                
  }
 }

 protected abstract bool BrowserRequestNeedsToBeFiltered(HttpRequest httpRequest);

 protected abstract Stream LoadFilter(HttpContext httpContext);
 #endregion
}
  • Init: just attaches handler;
  • ApplicationPostRequestHandlerExecute: it decides if page has to be filtered, it calls BrowserRequestNeedsToBeFiltered to understand what to do, and if the request has to be filtered, instantiates the filter;
  • BrowserRequestNeedsToBeFiltered: abtract method, you implement this one in order to make the module aware of the filtering rules;
  • LoadFilter: abstract method, you implement this one to decide what filter to instantiate;
Follow the code example i developed and everything will become clear.
P.S.: May thanks to Sam Betts and Stefano Merotta, they helped me with this project of doom!

Monday, August 25, 2014

Device channel panels and web parts walktrough

Hi guys, this an article i wanted to write one year ago about device channel panels in SharePoint 2013. I think you know what a device channel is, don't you?
"NO"
It's a new "exciting" SP2013 feature which could be considered as responsive web design alternative (or complement). It offers the possibility of defining different master pages, page layouts and (more important) site contents for different devices, mantaining the same website URL.
"Cool"
Wanna read more? Read official doc
http://msdn.microsoft.com/en-us/library/office/jj862343%28v=office.15%29.aspx ...and why don't read also this?
http://blog.mastykarz.nl/device-channels-sharepoint-2013/ If you wanna know more Google/Bing is your friend. I'm here to solve problems my dear.
 Problems? Have you found a problematic case?
Yeah.
If we want to load web parts as site content for different devices we have little problems. Let me show you this little bug .

A pratical example
Let's head Example 04 on the usual project (download latest version clicking here or from Download page), deploy and activate the feature Example 04. I created three device channels, but we will use just two of them during this little demo, named IE and FF: the first is a device channel for Internet Explorer browser, the second one is a device channel for Firefox browser.
I have used those device channel in order to keep it simple the most, in the real world you can detect devices using lots of user agent string combinations.
Let's create a DeviceChannelsArticlePage page with DeviceChannelArticle_Demo layout.
The content type extends Article page and has two fields: Internet Explorer field and Mozilla Firefox field.
In order to show contents conditionally, it's necessary to create fields that will be conditionally loaded. Fields will be wrapped in device channel panels, so it's like to say: "When you intercept this device, load those fields" and the page layouts become something like this
Now if we try to add HTML content using page layout everything works fine, let's try to do the same thing adding content editor webparts... so follow these steps

  1. Edit page using Internet Explorer
  2. Add a content editor web part and Save
  3. Edit the same page using Mozilla Firefox
  4. Add another content editor web part and Save
  5. View page using Internet Explorer 
Step 1 & Step 2

Step 3 & Step 4

Step 5
And, even if Internet Explorer content is here with us, web part disappeared and... this is not properly the expected behaviour, yeah?
This is because even if you add several HTML fields in order to simulate web part zones, web parts are loaded in just one web part zone called WPZ. When you edit fields you are overwriting WPZ.
Let me explain the process better:
  1. Edit page using Internet Explorer
  2. Add a content editor web part and Save >> Web part is added to WPZ
  3. Edit the same page using Mozilla Firefox >> Device channel panel don't let you see the previously added web part, this is a correct behaviour, since you don't see the previously added web part, WPZ thinks you have removed that one
  4. Add another content editor web part and Save >> It's like adding a new web part to a empty WPZ
  5. View page using Internet Explorer >> I think it's clear why you don't see anything in Internet Explorer
Solution(s)
Oh oh, and now? You have two possible solutions, the first one use OOB controls, the second one is a custom one.
OOB approach
Simply create a new page layout with this schema


Wrap device channel panels in a display mode panel, and duplicate the same fields placeholder (without device channel panels) in a edit mode panel.
This will let device channels to be used only for conditional web part display, instead fields editing will be done at single time, so WPZ will be erased no more.
 VLibs approach (applause)
Why use OOB device channel panel when you can use my overriden VLibs device channel panel?
The advantage is to keep page layout simple.

How it works?
Simply the control understand if you are editing or displaying the page and, if in edit mode, it ignores device channel recognition and shows the entire content.

For more code please download the VLibs source here.

P.S.
I sense a disturbance in the force... a new question...
"What if content editor web part contains javascript?"
Well, stay tuned... this is the content of the next article.

Me, myself and I

My Photo
I'm just another IT guy sharing his knowledge with all of you out there.
Wanna know more?