Get folder from document library as SP.ListItem

One of the recent requests was to get folder from document library and read additional properties added to folder content type on that list (only JavaScript is implied). I was starting to work on it, believing this is an easy task. But surprise, all I could find were methods to get folder but as SP.Folder, but nothing to access it as SP.ListItem. Also, no cast between these objects exists, or, if exists, I am not aware about it.

Being very clear I need different approach, I started to build my own code for this. Below I will explain it.

I always prefer to store information about SP.Context , site and web in a single place, to avoid initialization of the objects every time I need to perform a simple operation. So I created a class to store all these properties.

/// <Reference Name="MicrosoftAjax.js" />
/// <Reference Name="MicrosoftAjaxWebForms" />

Type.registerNamespace("Shp");

Shp.Info = function () {
    /// <summary>Static class which contains information about web and lists.</summary>
}

Shp.Info.context = null;
Shp.Info.web = null;
Shp.Info.lists = null;
Shp.Info.propertyBags = null;

Shp.Info.isInitialized = function () {
    ///<summary>Determine if Info class was initialized.</summary>
    return !((Shp.Info.context === null) || (Shp.Info.web === null) || (Shp.Info.lists === null) || (Shp.Info.propertyBags = null));
}

Shp.Info.getPropertyBag = function (propName) {
    ///<summary>Get site property bag.</summary>
    ///<param name="propName">Property bag</param>
    var e = Function._validateParams(arguments, [{ name: 'propName', type: String, mayBeNull: false, optional: false }], false);
    if (e) throw Error.create("Shp.Info.getPropertyBag was called with invalid parameter.");

    // Determint is Shp.Info was initialized on site level
    if (Shp.Info.propertyBags === null) {
        throw Error.create("Shp.Info was not initialized as root.");
    }

    for (_propertyBag in Shp.Info.propertyBags.get_fieldValues()) {
        if (_propertyBag === propName) {
            return Shp.Info.propertyBags.get_fieldValues()[_propertyBag];
        }
    }

    return "";
}


Shp.Info.Initialize = function (callback, args) {
    /// <summary>Initialize Shp.Info class for current web</summary>
    /// <param name="callback">Function to be passed a success callback operation</param>
    /// <param name="args">Array of additional for callback.</param>
    var e = Function._validateParams(arguments, [{ name: 'callback', type: Function, mayBeNull: false, optional: false },
                                                 { name: 'args', type: Array, mayBeNull: true, optional: true }], false);
    if (e) throw Error.create("Shp.Info.Initialize was called with invalid parameters.");

    var _cArgs = (typeof (arguments) === "undefined") ? ([]) : (args);

    Shp.Info.propertyBags = null;
    Shp.Info.context = SP.ClientContext.get_current();
    Shp.Info.web = Shp.Info.context.get_web();
    var webLists = Shp.Info.web.get_lists();
    Shp.Info.context.load(webLists);

    var fail = function (args, message) {
        alert('Cannot get web information: ' + args.get_message() + '\n' + args.get_stackTrace());
    };

    var success = function () {
        Shp.Info.lists = webLists.$2_1;
        if ((typeof (callback) !== "undefined") && (callback !== null)) callback.apply(null, _cArgs);
    };

    Shp.Info.context.executeQueryAsync(success, fail);
}


Shp.Info.InitializeAsRoot = function (callback) {
    /// <summary>Initialize Shp.Info class for top level site</summary>
    /// <param name="callback">Function to be passed a success callback operation</param>
    var e = Function._validateParams(arguments, [{ name: 'callback', type: Function, mayBeNull: false, optional: false }], false);
    if (e) throw Error.create("Shp.Info.Initialize was called with invalid parameter.");

    Shp.Info.context = SP.ClientContext.get_current();
    Shp.Info.web = Shp.Info.context.get_site().get_rootWeb();
    var webLists = Shp.Info.web.get_lists();
    var webProperties = Shp.Info.web.get_allProperties();
    Shp.Info.context.load(webLists);
    Shp.Info.context.load(webProperties);

    Shp.Info.context.executeQueryAsync(function () {
        Shp.Info.lists = webLists.$2_1;
        Shp.Info.propertyBags = webProperties;
        callback();
    },
        function (source, args) {
            alert('Cannot initialize root web: ' + args.get_message() + '\n' + args.get_stackTrace());
        }
    );
}


Shp.Info._get_documentLibrary = function (docLibName) {
    /// <summary>Get document library from initalized web (internal use).</summary>
    /// <param name="docLibName">Document library title</param>
    var e = Function._validateParams(arguments, [{ name: 'docLibName', type: String, mayBeNull: false, optional: false }], false);
    if (e) throw Error.create("Shp.Info._get_documentLibrary was called with invalid parameter.");

    for (var i = 0; i < Shp.Info.lists.length; i++) {
        if ((Shp.Info.lists[i].get_baseType() === 1) && (Shp.Info.lists[i].get_title() === docLibName)) {
            return Shp.Info.lists[i];
        }
    }
    return null;
}

Shp.Info.registerClass("Shp.Info");

I could probably improve the code, but you got the idea. I can initialize properties for current web or for site collection web, store them in the properties of the class and keep them for later use. I do not insist on this, as this is not the scope of this article.

I also created a class to include built-in SP.ListItem and refine it. Is an optional choice, but I am not 100% happy with SP.ListItem class. For example I prefer to have an empty string returned by get_item method if field is not found in the collection or is null.


Shp.ListItem = function (listItem) {
    this.baseItem = listItem;
}

Shp.ListItem.prototype = {

    check_field: function (fldName) {
        var _fields = this.baseItem.get_fieldValues();
        return !(typeof (_fields[fldName]) === 'undefined');
    },

    get_item: function (fldName) {
        var _fldExist = this.check_field(fldName);
        var _fieldValue = ((_fldExist === false) || (this.baseItem.get_item(fldName) === null)) ? ('') : (this.baseItem.get_item(fldName));
        return _fieldValue;
    },

    get_baseItem: function () {
        return this.baseItem;
    }
}

Shp.ListItem.registerClass("Shp.ListItem");

And here is the code for getting the folder as SP.ListItem.

Shp.ListItemsOps = function () {
    /// <summary>SharePoint ListItemsOps custom static class.</summary>
}


Shp.ListItemsOps.get_folder = function (documentLibrary, folderUrl, callbackSuccess, callbackFailed) {
    /// <summary>Get parent folder</summary>
    /// <param name="callbackSuccess" type="Function" mayBeNull="false" optional="false"></param>
    /// <param name="callbackFailed" type="String" mayBeNull="true" optional="true"></param>

    var e = Function._validateParams(arguments, [{ name: 'documentLibrary', type: String, mayBeNull: false, optional: false },
                                                 { name: 'folderUrl', type: String, mayBeNull: false, optional: false },
                                                 { name: 'callbackSuccess', type: Function, mayBeNull: false, optional: false },
                                                 { name: 'callbackFailed', type: Function, mayBeNull: true, optional: true }], false);
    if (e) throw Error.create("Shp.ListItemsOps.get_folder was called with invalid parameters.");



    // Define get  folder operation (closure)
    var _getFolder = function () {

        // Get the document library
        var _docLib = Shp.Info._get_documentLibrary(documentLibrary);
        if (_docLib === null) throw Error.create(documentLibrary + ' could not be found in the site document libraries collection');

        // Build CAML query 
        var query = new SP.CamlQuery();
        query.set_viewXml("<View Scope='RecursiveAll'> ><Query><Where><And><Eq><FieldRef Name='FileRef'/><Value Type='URL'>" + folderUrl + "</Value></Eq><Eq><FieldRef Name='FSObjType'/><Value Type='Integer'>1</Value></Eq></And></Where></Query></View>");

        // Get items based on CAML
        var _results = _docLib.getItems(query);
        Shp.Info.context.load(_results);


        // Execute async
        Shp.Info.context.executeQueryAsync(function () {
            if (_results.get_count() > 0) {
                callbackSuccess(new Shp.ListItem(_results.itemAt(0)));
            }
            else {
                callbackSuccess(null);
            }
        }, function (source, args) {
            if (typeof (callbackFailed) === 'undefined') {
                alert('Cannot get folder information: ' + args.get_message());
            }
            else {
                callbackFailed(source, args);
            }
        });
    };

    // If Info class is not initialized, then initialize first and execute the operation, if not execute operation directly
    if (Shp.Info.isInitialized() === false) {
        Shp.Info.Initialize(_getFolder);
    }
    else {
        _getFolder();
    };
}
Shp.ListItemsOps.registerClass("Shp.ListItemsOps");

The CAML below did the entire trick. 🙂

<View Scope='RecursiveAll'> ><Query><Where><And><Eq><FieldRef Name='FileRef'/><Value Type='URL'>" + folderUrl + "</Value></Eq><Eq><FieldRef Name='FSObjType'/><Value Type='Integer'>1</Value></Eq></And></Where></Query></View>

Try to adapt and apply the code to your situation. In case you have any ideas about how to improve it, please leave a comment.

Advertisements

“DataSource failed to execute insert command” when adding new record to a calendar list

Reproduce the error

  1. Create a content type called “Training” or choose different name if you want, which inherits from “Event” content type.
  2. From new content type remove the column called “Start Time”. Internal name of the field is “StartDate”.
  3. Create a list called “Trainings”, attach “Training” content type on it and remove the default one (“Event” content type).
  4. Try to add a new record

You will discover you won’t be able to add a new record. The error will contain this message: “The data source control failed to execute the insert command.” or/and “Invalid data has been used to update the list item. The field you are trying to update may be read only.”

Solution

Go to site content type, locate “Training” content type and add to it site column “Start Date”. It is the same column as the one you just removed (note: the internal field the same).

Cause

This is cause by the fact you are trying to set an end date (“End Time” column was not removed from the content type or the list) without having a start date. If you need an end date for an event, please keep both columns.

Event columns