Create an Unpublished Content view for the masterpage galery [Sharepoint]

With Sharepoint 2013 I wanted an easy way to list of the pages in my masterpage galery that haven’t been published yet.

You first need to Create a new view named Unpublished Content. You can sort by Name, and scroll down to the Folders settings and choose Show all items without folders.

We now have all our files in the view.

Next, switch to the Library ribbon and choose Modify in SharePoint Designer (Advanced) from the Modify View dropdown.

Sharepoint Designer will open your page.

In Sharepoint Designer, switch to the Home tab and choose Advanced Mode. The file is reloaded.

Search into your code the tag ListViewXml. On this line you should have <Query;&>. Just after it, we need to add:
<Where><Or><Eq><FieldRef Name="_ModerationStatus" /><Value Type="ModStat">Draft</Value></Eq><IsNotNull><FieldRef Name="CheckoutUser" /></IsNotNull></Or></Where>

Before:

After:
(click to enlarge)

Save, and your view should only show the unpublished content!

(Article inspired by https://thechriskent.com/2013/02/14/unpublished-view/)

Trigger an event when an element is visible in a scrollable area [JavaScript]

EDIT in 2022

It might also be interesting to look at Intersection Observer that is now available in most browsers, and there is also a polyfill.

const observer = new IntersectionObserver((entries, observer) => {
  entries.forEach((entry) => {
    let isVisible=(entry.intersectionRatio===1);
  });
}, {threshold:1});
observer.observe(document.querySelector('#element');

END EDIT

I have this specific need that is to trigger an event when an element becomes visible/invisible into a scrollable area. Here is the code I came to:

/**
 * Create the handler
 * @param {NodeElement} holder The scrollable area to monitor (where the 'scroll' event will be apply)
 */
var VisibleEventListener = function(holder) {
  var _this=this;
  _this.started = false;
  _this.holder = holder;
  // detect if an element is visible
  _this.isScrolledIntoView=function(el) {
    var bndElem = el.getBoundingClientRect();
    var bndHolder = _this.holder.getBoundingClientRect();
    return bndElem.top <= bndHolder.top ? !(bndHolder.top - bndElem.top > bndElem.height) : !(bndElem.bottom - bndHolder.bottom > bndElem.height);
  }
  // permits to deal with the scroll
  _this.scrollHandler=function(e) {
    for (var i=0, len=_this.events.length; i<len; i++) {
      _this.events[i].check();
    }
  }
  _this.events=[];
}
/**
 * Add the visible/invisible event for an element into a scrollable area
 * @param {NodeElement}   element  The element to test
 * @param {String}   listener 'visible' or 'invisible'
 * @param {Function} callback The callback function to be called when the event is triggered
 */
VisibleEventListener.prototype.add = function(element, listener, callback) {
  var _this=this;
  var ElementToMonitor=function(element, listener, callback) {
    this._super=_this;
    this.element=element;
    this.isVisible=false;
    this.callback=callback;
    this.listener=listener;
  }
  ElementToMonitor.prototype.check = function() {
    var visible=this._super.isScrolledIntoView(this.element);
    if (visible && !this.isVisible) { // becomes visible
      this.isVisible=true;
      if (this.listener==='visible') this.callback.call(this.element)
    } else if (!visible && this.isVisible) { // becomes invisible
      this.isVisible=false;
      if (this.listener==='invisible') this.callback.call(this.element)
    }
  };
  var etm=new ElementToMonitor(element,listener,callback);
  _this.events.push(etm);
  // if we have started to monitor
  if (_this.started===true) etm.check();
}
VisibleEventListener.prototype.start = function() {
  this.holder.addEventListener('scroll', this.scrollHandler);
  this.started = true;
  // trigger the check to verify if the elements are already visible
  this.scrollHandler();
}
VisibleEventListener.prototype.stop = function() {
  this.holder.removeEventListener('scroll', this.scrollHandler);
  this.started = false;
}

And to use it:

// initiate our area with VisibleEventListener
var vel = new VisibleEventListener(document.querySelector('#s4-workspace'));
// add the elements to monitor
vel.add(document.querySelector('.slideshow'), 'invisible', function() {
  console.log(this,' is now invisible')
})
vel.add(document.querySelector('.slideshow'), 'visible', function() {
  console.log(this,' is now visible')
})
// start the monitoring
vel.start();

Check if an element is visible [JavaScript]

We can find plenty of answers about how to check if an element is visible.

In my case the one which works with opacity, and into a scrollable area, is this one (that I have optimized):

function isScrolledIntoView(el, holder) {
  var bndElem = el.getBoundingClientRect();
  var bndHolder = holder.getBoundingClientRect();
  return bndElem.top <= bndHolder.top ? !(bndHolder.top - bndElem.top > bndElem.height) : !(bndElem.bottom - bndHolder.bottom > bndElem.height);
}

Resolve Promise one after another, in sequence

Below is the code that is handy when you want to execute several Promise actions in sequence:

function PromiseChain(arr, fct) {
  var dfd = Promise.resolve();
  var res = arr.map(function(item,idx) {
    dfd = dfd.then(function() {
      return fct(item,idx)
    });
    return dfd
  });
  return Promise.all(res)
}

// for short version:
// function PromiseChain(n,r){var e=Promise.resolve(),i=n.map(function(n,i){return e=e.then(function(){return r(n,i)})});return Promise.all(i)}

And an example about how to use it:

function promFunc(key) {
  return new Promise(function(prom_res) {
    setTimeout(function() { prom_res(key) }, 1000);
  })
}

PromiseChain(["a", "b", "c"], function(val, idx) {
  console.log(idx, val);
  return promFunc(idx+"_"+val);
})
.then(function(res) {
  console.log(res);
})

// result:
// 0 a
// 1 b
// 2 c
// Array [ "0_a", "1_b", "2_c" ]

Deep clone an object in JavaScript

We can find many discussions and solutions about this issue.

In my case the one that worked is this code.

Below you’ll find a compressed version of the extend() function (with a fix):

function extend(){var r,t,n,o,e=arguments[0]||{},f=1,i=arguments.length,u=!1,y=function(r){if(null===r||"object"!=typeof r||r.nodeType||null!==r&&r===r.window)return!1;try{if(r.constructor&&!this.hasOwnProperty.call(r.constructor.prototype,"isPrototypeOf"))return!1}catch(t){return!1}return!0};for("boolean"==typeof e&&(u=e,e=arguments[f]||{},f++),"object"!=typeof e&&"function"!=typeof e&&(e={}),!1;i>f;f+=1)if(null!==(r=arguments[f]))for(t in r)e!==r[t]&&"undefined"==typeof e[t]&&(u&&r[t]&&(y(r[t])||(n=Array.isArray(r[t])))?(n?(n=!1,o=e[t]&&Array.isArray(e[t])?e[t]:[]):o=e[t]&&y(e[t])?e[t]:{},e[t]=extend(u,o,r[t])):void 0!==r[t]&&(e[t]=r[t]));return e}

And here an example:

var object_a = {
    value_1: 'a',
    value_2: true,
    value_3: 354,
    value_4: [
      'x',
      'y',
      'z'
    ],
    value_5: {
      I: 'I',
      II: 'II',
      III: 'III',
      IV: {
        a: 'A',
        b: 'B'
      },
      V: [
        'elm', {
          5: 'five'
        },
        ['my_array']
      ]
    },
    value_6: function() {
      return "pouet"
    },
    value_7: new Date(2017, 0, 1)
  };
var object_b = {};
// clone object_a into object_b
extend(true, object_b, object_a);
object_a.value_2 = false;
object_a.value_1 = "b";
console.log("A => ", object_a);
console.log("B => ", object_b);

Check permissions in Sharepoint based on a PermMask

In some cases you could get this kind of hexadecimal numbers from Sharepoint for the PermMask: 0xb008431061, or 0x1b03c4313ff, and so on.

To verify the PermMask against the different level of permissions you can proceed with the below method (using functions from core.js):

var permMask = '0x1b03c4313ff';
var permissions = new SP.BasePermissions();
permissions.initPropertiesFromJson({High:GetPermMaskH(permMask), Low:GetPermMaskL(permMask)});
// we can now check permissions using SP.BasePermissions.has()
// and we can compare with SP.PermissionKind — see https://msdn.microsoft.com/en-us/library/office/ee556747(v=office.14).aspx
var canEdit = permissions.has(SP.PermissionKind.editListItems);

On Sharepoint 2010 GetPermMaskH and GetPermMaskL are not defined, so here is their code:

function GetPermMaskH(b) {
    var a = b.length;
    return a <= 10 ? 0 : parseInt(b.substring(2, a - 8), 16)
}

function GetPermMaskL(b) {
    var a = b.length;
    return a <= 10 ? parseInt(b) : parseInt(b.substring(a - 8, a), 16)
}

It took me many hours to find how to proceed, so I hope it will help some others.

Redirect after Deleting [Sharepoint]

When I open a dialog for an EditFrom and use the « Delete Item » button from the ribbon, then the main page is redirected to the related Sharepoint list. This behavior is very bad for the user experience.

To change it I used the below Javascript code:

// replace the Delete Item default action when into a popup
$(document).ready(function() {
  // if it's in a popup
  if (window !== window.top) {
    $('a[id$="_toolBarTbl_RptControls_diidIODeleteItem"]').attr("href","javascript:commonDeleteItem(); return false")
  }
})
// now when clicking on the Delete item it will call my own function
// here I use http://aymkdn.github.io/SharepointPlus/ to show a waiting message and to delete the current item, and then close the modal
function commonDeleteItem() {
  $SP().waitModalDialog("Deleting...");
  $SP().list("{"+WPQ2FormCtx.ListAttributes.Id+"}").remove({ID:GetUrlKeyValue("ID")}, {
    after:function() {
      $SP().closeModalDialog();
      $SP().closeModalDialog(2);
    }
  })
}

How to open an Excel document from SharePoint into Chrome/Firefox in readonly/edit mode

With Sharepoint 2010, when you use Internet Explorer you can open an Office document in readonly or in edit mode. Also, when trying to open an Excel file in Firefox/Chrome it will probably open the ugly xslviewer and won’t let you deal with your file.

After searching a bit about it, I found a clue that helped me to find a good solution: on Firefox/Chrome when you click on an Excel file, you’ll now have a modal asking if you want to open it in edit or readonly mode!

To achieve this result you have to insert the below Javascript code somewhere into your masterpage:

/**
 * fix problem with Excel documents on Firefox/Chrome
 * @param  {HTMLElement} p the <A> element
 * @param  {HTMLEvent} a the click event
 * @param  {Boolean} h TRUE
 * @param  {Boolean} e FALSE
 * @param  {Boolean} g FALSE
 * @param  {Strin} k the ActiveX command (e.g. "SharePoint.OpenDocuments.3")
 * @param  {Number} c 0
 * @param  {String} o the activeX command, here we look at "SharePoint.OpenDocuments"
 * @param  {String} m
 * @param  {String} b the replacement URL to the xslviewer
 */
var bak_DispEx;
var modalOpenDocument; // it will be use with the modal
SP.SOD.executeOrDelayUntilEventNotified(function() {
  bak_DispEx = _DispEx;
  _DispEx=function(p, a, h, e, g, k, c, o, m, b, j, l, i, f, d) {
    // if o==="SharePoint.OpenDocuments" && !IsClientAppInstalled(o)
    // in that case we want to open ask the user if he/she wants to readonly or edit the file
    var fileURL = b.replace(/.*_layouts\/xlviewer\.aspx\?id=(.*)/, "$1");
    if (o === "SharePoint.OpenDocuments" && !IsClientAppInstalled(o) && /\.xlsx?$/.test(fileURL)) {
      // if the URL doesn't start with http
      if (!/^http/.test(fileURL)) {
        fileURL = window.location.protocol + "//" + window.location.host + fileURL;
      }
      var ohtml = document.createElement('div');
      ohtml.style.padding = "10px";
      ohtml.style.display = "inline-block";
      ohtml.style.width = "200px";
      ohtml.style.width = "200px";
      ohtml.innerHTML = '<style>'
                      + '.opendocument_button { background-color:#fdfdfd; border:1px solid #ababab; color:#444; display:inline-block; padding: 7px 10px; }'
                      + '.opendocument_button:hover { box-shadow: none }'
                      + '#opendocument_readonly,#opendocument_edit { float:none; font-size: 100%; line-height: 1.15; margin: 0; overflow: visible; box-sizing: border-box; padding: 0; height:auto }'
                      + '.opendocument_ul { list-style-type:none;margin-top:10px;margin-bottom:10px;padding-top:0;padding-bottom:0 }'
                      + '</style>'
                      + 'You are about to open:'
                      + '<ul class="opendocument_ul">'
                      + '  <li>Name: <b>'+fileURL.split("/").slice(-1)+'</b></li>'
                      + '  <li>From: <b>'+window.location.hostname+'</b></li>'
                      + '</ul>'
                      + 'How would like to open this file?'
                      + '<ul class="opendocument_ul">'
                      + '  <li><label><input type="radio" name="opendocument_choices" id="opendocument_readonly" checked> Read Only</label></li>'
                      + '  <li><label><input type="radio" name="opendocument_choices" id="opendocument_edit"> Edit</label></li>'
                      + '</ul>'
                      + '<div style="text-align: center;margin-top: 20px;"><button type="button" class="opendocument_button" style="background-color: #2d9f2d;color: #fff;" onclick="modalOpenDocument.close(document.getElementById(\'opendocument_edit\').checked)">Open</button> <button type="button" class="opendocument_button" style="margin-left:10px" onclick="modalOpenDocument.close(-1)">Cancel</button></div>';
      // show the modal
      modalOpenDocument=SP.UI.ModalDialog.showModalDialog({
        html:ohtml,
        dialogReturnValueCallback:function(ret) {
          if (ret!==-1) {
            if (ret === true) { // edit
              // reformat the fileURL
              var ext;
              if (/\.xlsx?$/.test(b)) ext = "ms-excel";
              if (/\.docx?$/.test(b)) ext = "ms-word"; // not currently supported
              fileURL = ext + ":ofe|u|" + fileURL;
            }
            window.location.href = fileURL; // open the file
          }
        }
      });
      a.preventDefault();
      a.stopImmediatePropagation()
      a.cancelBubble = true;
      a.returnValue = false;
      return false;
    }
    return bak_DispEx.apply(this, arguments);
  }
}, "sp.scriptloaded-core.js")