Execute an action before saving a form but after the form validation [Sharepoint]

Let’s say you want to do an asynchronous request just before saving a Sharepoint form, but after Sharepoint verified all the fields in your form (for mandatory ones, or for format, …). Here is my solution to do it (using jQuery.Deferred):

// this function will do our asynchronous check
function beforeSaving() {
  var deferred=jQuery.Deferred();
  setTimeout(function() {
    alert("Test Complete!")
    deferred.reject(); // if the test failed
    // or deferred.resolve() to valid the test
  }, 2000)
  return deferred;
}
// force PostBackRequired to true in the context, otherwise it won't work if you open it into a modal
WPQ2FormCtx.PostBackRequired=true;
// we override SPClientForms.ClientFormManager.SubmitClientForm
SPClientForms.ClientFormManager.SubmitClientForm=function(b){
  var a=SPClientForms.ClientFormManager.GetClientForm(b);
  var res = (a!=null&&a.SubmitClientForm());
  // if the form is not valid, then res===true
  if (res === true) return true;
  else {
    // at this stage, all fields' value have been saved into a hidden input
    // e.g. document.querySelector('input[type="hidden"][id$="Field_x0020_Name"]')
    // all these saved values will be sent back to the server when "WebForm_DoPostBackWithOptions" is executed

    // if the form is valid we now want to do our asynchronous check
    beforeSaving().done(function() {
      // our test is valid too so we can send the form to the server
      WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions($get(WPQ2FormCtx.SubmitButtonID).name, "", true, "", "", false, true))
    }).fail(function() {
      // if it fails we just unblock the save button
      $get(WPQ2FormCtx.SubmitButtonID).disabled=false;
    })
  }
  // to make sure the form won't be submitted yet
  return true;
};

Disable X-Frame-Options on Sharepoint

I needed to communicate with an iframe on Sharepoint, however Sharepoint returned Load denied by X-Frame-Options … To override this limitation you have to create a Webpart page where you’ll add the code <WebPartPages:AllowFraming runat="server" /> into the <asp:Content> section (that might work somewhere else, but I did this way).

You can also add it into your masterpage to allow it everywhere on your website. Add it into your <head> block:

Now you can use postMessage() to communicate with your iframe.

Capture HTTP(S) traffic from Android using a sniffer

I wanted to debug an Android app that uses HTTPS requests with a JSON API. Thanks to this article I’ve been able to use my Windows 10 computer to get all the network from my Android phone thru my local network and decode the HTTPS requests !

I’m going to summarize the steps from the above article:

  1. Download and Install Fiddler on your computer
  2. Once Fiddler is installed, launch it and:
    • Click menu Tools > Options, then select the Connections tab
    • Make note of the “Fiddler listens on” port (normally it’s 8888)
    • Make sure the check box for “Allow remote computer to connect” is checked
    • Switch to the HTTPS tab
    • Make sure the check boxes for “Capture HTTPS Connects” and “Decrypt HTTPS traffic” are both checked
    • Restart Fiddler
  3. Go to your Android phone then:
    • Tap on Settings, then Wi-Fi
    • Find the network on which you’re connected (normally the first one listed), then tap and hold
    • Choose Modify network from the pop-up
    • Scroll down and enable “Show advanced options”
    • Change “Proxy settings” to Manual
    • Under “Proxy host name” enter the Windows PC IP address from above
    • Under “Proxy port” enter the Fiddler port from above (usually 8888)
    • Tap Save and wait a moment for the network to reconnect
  4. Now we need to add the certificate in Android to have the HTTPS working:
    • On Android start the Chrome browser
    • Navigate to http://IP_ADDRESS_WHERE_FIDDLER_IS:8888/ or http://ipv4.fiddler:8888
    • Tap on the link for the “Fiddler Root Certificate”
    • Name the certificate “Fiddler” and install it (entering your PIN or password if prompted)

You’re now ready to capture the traffic on Fiddler!

Once you’re done you can switch back to normal by following the below steps:

  1. Tap on Settings, then Wi-Fi
  2. Find the network on which you’re connected (should be the first one listed), then tap and hold
  3. Choose Modify network from the pop-up
  4. Scroll down and select (enable) “Show advanced options”
  5. Change “Proxy settings” to None
  6. Tap Save and wait a moment for the network to reconnect
  7. Go up a level in settings to Security
  8. Tap Trusted credentials, then select the User tab
  9. Tap on the Fiddler “Do not trust” certificate, then scroll down to remove it
  10. You may need to power cycle your device to get all apps to forget about the Fiddler certificate (e.g., the Chrome browser will continue to try to use it for a while)

Hide a field into a form with JSLink based on field’s description [Sharepoint]

On Sharepoint 2013 I was trying to hide a field (the complete row) based on some elements into its description.

To do so we will call our file using the JSLink (see my previous post about it).
The JS file looks like that:

(function() {
  // do some actions as soon as the fields are shown
  var loadAfterForm = {
    Templates: {
      OnPostRender:function(ctx) {
        // hide the field/row when "[INTERNAL USE - DO NOT CHANGE]" is detected in the field's description
        if (ctx.ListSchema.Field[0].Description.indexOf('[INTERNAL USE - DO NOT CHANGE]') > -1) {
          // get the element that represents the field
          var elem = document.querySelector('[id^="'+ctx.ListSchema.Field[0].Name +'_'+ ctx.ListSchema.Field[0].Id+'"]');
          while (elem) {
            elem = elem.parentNode;
            if (elem) {
              if (elem.tagName==="BODY") break;
              if (elem.tagName === "TD" && elem.className.indexOf("ms-formbody") > -1) {
                // hide the whole row
                elem.parentNode.style.display="none";
                break;
              }
            }
          }
        }
      }
    }
  }
  SPClientTemplates.TemplateManager.RegisterTemplateOverrides(loadAfterForm);
})();

Bypass a lookup field not displayed because of threshold on NewForm and EditForm [Sharepoint 2013]

EDIT 22/Dec/2016: there is s a new version of this article at https://blog.kodono.info/wordpress/2016/12/22/2-bypass-a-lookup-field-not-displayed-because-of-threshold-on-newform-and-editform-sharepoint-2013/

Sharepoint is really annoying to limit the access to the list with more than 5,000 items… It causes many troubles, and one of them is when you use a lookup field into a form that is tied to a big list. Sharepoint will not display the field/dropdown but will show the message:

This is a lookup column that displays data from another list that currently exceeds the List View Threshold defined by the administrator

There is a solution to bypass this problem and show our lookup dropdowns.

You need:

I’ll show how I did it for the EditForm.

First, copy/paste the below JavaScript code into a file that you’ll store somewhere on your Sharepoint:

// load jQuery, SharepointPlus and Chosen
var toLoad = '<script src="/Toolbox/Documents/js/jQuery/jquery-1.12.3.min.js"></script>'
           + '<script src="/Toolbox/Documents/js/SharepointPlus/3.13/sharepointplus-3.13.min.js"></script>'
           + '<link href="/Toolbox/Documents/js/Chosen/1.5.0/chosen.min.css" rel="stylesheet" type="text/css">'
           + '<script src="/Toolbox/Documents/js/Chosen/1.5.0/chosen.jquery.min.js"></script>'
document.write(toLoad);

(function() {
  var aDeferred=[];
  var modal;
  // list the fields we want to fix
  // /!\ Add here the Field ID of the fields that need to be fixed
  var lookupFieldsToFix = ["Voucher_x0020_Code", "User_x0020_Name"];
  // /!\ Specify the name of the related list, as well as the Column to retrieve
  var configurationsLookup = [{list:"Vouchers", field:"Title"}, {list:"Users", field:"Name"}];

  // this function is triggered once the form is loaded
  function allLoaded(ctx) {
    // delete all existing localStorage
    localStorage.removeItem('RequestsLookup')
    // check if our call to lists are done
    $.when.apply(this, aDeferred).done(function() {
      var save={}, count=0, i;
      for (i=arguments.length; i--;) {
        if (arguments[i]) {
          save[arguments[i].field] = arguments[i].choices; // save it as {fieldName:choices}
          count++;
        }
      }
      if (count > 0) {
        // we use localStorage
        localStorage.setItem('RequestsLookup', JSON.stringify(save));
        // now reload the page
        $SP().closeModalDialog();
        $('#aspnetForm').hide().after('<h1>Reloading...</h1>');
        window.location.reload();
      } else {
        // we use Chosen
        for (i=lookupFieldsToFix.length; i--;) {
          var e=WPQ2FormCtx.ListSchema[lookupFieldsToFix[i]];
          $(document.getElementById(e.Name+"_"+e.Id+"_$"+e.FieldType+"Field")).chosen({search_contains:true});
        }
        // and on Exam Name
        $SP().formfields("Exam Name").elem().chosen({search_contains:true});
      }
    })
  }
  
  /**
   * Fix the broken lookup fields
   * @param  {String} field Name of the field to fix
   * @return {Deferred}
   */  
  function fixLookup(field) {
    var deferred = jQuery.Deferred();
    var saved, choices=[], config;

    // we check if there is a Throttled
    if (WPQ2FormCtx.ListSchema[field].Throttled) {
      WPQ2FormCtx.ListSchema[field].Throttled=false;
      // check if we have a localStorage, if yes it means we are after the reload
      saved = localStorage.getItem("RequestsLookup");
      if (saved !== null) {
        saved = JSON.parse(saved);
        // we use the stored data to create our dropdown
        WPQ2FormCtx.ListSchema[field].ChoiceCount=saved[field].length;
        WPQ2FormCtx.ListSchema[field].Choices=saved[field];
        deferred.resolve();
      } else {
        WPQ2FormCtx.ListSchema[field].ChoiceCount=0;
        WPQ2FormCtx.ListSchema[field].Choices=[];
        // then we show a Waiting message
        if (!modal) {
          modal=true;
          $SP().waitModalDialog("Loading some data...");
        }
        // and we get data from the list
        config = configurationsLookup[SPArrayIndexOf(lookupFieldsToFix, field)];
        if (config.list) {
          $SP().list(config.list).get({fields:config.field, paging:true}, function(data) {
            var res=[];
            for (var i=data.length; i--;) {
              res.push({LookupId:data[i].getAttribute("ID"), LookupValue:data[i].getAttribute(config.field)});
            }
            deferred.resolve({field:field, choices:res});
          });
        } else deferred.resolve([]);
      }

    } else deferred.resolve();
    return deferred;
  }

  // do some actions as soon as the fields are shown
  var changeForm = {
    Templates: {
      OnPreRender:function(ctx) {
        // we want to show Voucher Code and User Name even if there are more than 5000 items in those lists
        if (SPArrayIndexOf(lookupFieldsToFix, ctx.ListSchema.Field[0].Name) > -1) {
          aDeferred.push(fixLookup(ctx.ListSchema.Field[0].Name));
        }
      },
      OnPostRender:function(ctx) {
        // only trigger when everything is loaded
        if (ctx.ListSchema.Field[0].Name === "Attachments") {
          allLoaded(ctx)
        }
      }
    }
  }
  // don't do it when editing the page
  if (GetUrlKeyValue("PageView") !== "Shared") SPClientTemplates.TemplateManager.RegisterTemplateOverrides(changeForm);
})();

Please refer to the /!\ symbol to configure two variables: only lookupFieldsToFix and configurationsLookup need to be set by you.
Note that the above code is compatible for IE8+, and all modern browsers.

Now go to the EditForm.aspx of your list. Then edit the page:
showing how to edit the EditForm thru Settings

Next, edit the webpart settings:
show how to edit webpart settings

Go to the Miscellaneous section and enter the path to your JavaScript file (created before) in the JS Link field – you can use ~site in the path:
show where to input the path to the JS file

Finally click ON, and then on Stop Editing in the ribbon.

You can now try to edit an item and the lookup fields should be fixed. Example:

NOTE

The document.write() is not really a good practice because it will delay the page load. It would be better to load asyn the JS using this tiny function. In that case you need to make sure to have only Vanilla JS in your changeForm.

Incruster des sous-titres dans une vidéo

Voilà une opération qui pourrait paraitre simple et qui pourtant est difficile à trouver sur le Net…

Pour cela on va utiliser le logiciel HandBrake. Commencer par le télécharger puis l’installer.

  1. On choisit notre fichier vidéo d’origine (ici un .mp4) en cliquant sur « Source » :
    step1
    step1_b
  2. On choisit la destination, c’est-à-dire là où notre nouveau fichier sera sauvegardé :
    step2
  3. On clique ensuite sur l’onglet « Subtitles » puis sur « Import SRT » et on choisit notre fichier avec l’extension « .srt » :
    step3
  4. On choisit la langue et on coche la case « Burn In » :
    step4
  5. Et enfin on clique sur le bouton « Start » dans la barre d’outil.

Send an email to several recipients from a String in a workflow [Sharepoint 2013]

I found the case in which I have to send 1 email to several recipients. I have the names stored into a Sharepoint list.

Using REST API and a call into my Workflow I’ve been able to get a list of login names (using $expand=MyUserField and $select=MyUserField/Name), then you just need to concatenate them separate by ;.

At the end you should have a string that looks like domain\login_name1;domain\login_name2;domain\login_name3; into yout To field for the email.

Load a script once a DispForm is fully shown in Sharepoint 2013

Sharepoint 2013 introduced the JSLink. This is very useful to play with forms and views.

My attempt here is to remove some rows from the DispForm. To do so I needed to trigger an action as soon as the fields rendering has been done. After different tries, I finally came up with the PostRender option.

// https://gist.github.com/Aymkdn/98acfbb46fbe7c1f00cdd3c753520ea8
function loadExt(e,t){var s=this;s.files=e,s.js=[],s.head=document.getElementsByTagName("head")[0],s.after=t||function(){},s.loadStyle=function(e){var t=document.createElement("link");t.rel="stylesheet",t.type="text/css",t.href=e,s.head.appendChild(t)},s.loadScript=function(e){var t=document.createElement("script");t.type="text/javascript",t.src=s.js[e];var a=function(){++e<s.js.length?s.loadScript(e):s.after()};t.onload=function(){a()},s.head.appendChild(t)};for(var a=0;a<s.files.length;a++)/\.js$|\.js\?/.test(s.files[a])&&s.js.push(s.files[a]),/\.css$|\.css\?/.test(s.files[a])&&s.loadStyle(s.files[a]);s.js.length>0?s.loadScript(0):s.after()}

// verify when all scripts have been loaded
var loadExtLoaded = false;
function checkExt(ctx) {
  if (loadExtLoaded) {
    // here you can call a function that is in one of the called script
  } else {
    setTimeout(function() { checkExt(ctx) }, 50);
  }
}
loadExt([
  'file_you_want_to_load.js',
  'style_you_want_to_load.css',
  'no_cache_file.js?timestamp='+(Date.now())
], function() {
  loadExtLoaded=true;
});

(function() {
  function onLoad(ctx) {
    checkExt(ctx);
  }

  // do some actions as soon as the fields are shown
  var loadAfterForm = {
    Templates: {
      OnPostRender:function(ctx) {
         // only trigger when everything is loaded
        // --> ctx.ListData.Items[0] all the fields
        if (ctx.ListSchema.Field[0].Name === "Attachments") {
          onLoad(ctx)
        }
      }
    }
  }
  SPClientTemplates.TemplateManager.RegisterTemplateOverrides(loadAfterForm);
})();

Add a SharePointWebControls into a HTML version of a MasterPage [Sharepoint 2013]

In Sharepoint 2013 it’s now possible to create the MasterPage based on an HTML file.

Here is the snippet you need to add into your HTML page to get a SharePointWebControls:

<!--SPM<%@ Register Tagprefix="SharePointWebControls" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>-->
<!--CS: Start Create Snippets From Custom ASP.NET Markup Snippet-->
<!--SPM:<SharePointWebControls:TextField runat="server" FieldName="Title" />-->
<!--CE: End Create Snippets From Custom ASP.NET Markup Snippet-->

Illegal use of $_SERVER. You must use the request class or request_var() to access input data. [phpBB]

When I want to update my phpBB forum I get this annoying message because I’m using a homemade auth file where $_SERVER is used.

I found the solution on the phpBB forum:

Modified /forums/config/parameters.yml. Set core.disable_super_globals to false and delete the cache