2

I am trying to create a list item with JavaScript, but have been having problems.

To try to simplify things, I created a simple list called "Test List" with one column, "Title" and created a script manager web part on the DispForm.aspx as follows:

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" >
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<script src="/Style%20Library/scripts/SharepointJSOM.js" type="text/javascript" ></script>
<script src="/Style%20Library/scripts/AddFakeItem.js" type="text/javascript" ></script>
<div id="addFakeButton">
     <button type="button" class="btn btn-info btn-small" id="btnAddFake"
             onclick="AddFakeItem()">Add fake item</button>
</div>

which refers to AddFakeItem.js which is based on stock MS code:

var list;
var loops = new Date();
$(function () {
    $("button#btnAddFake").click(AddFakeItem());
});

function AddFakeItem(s, e) {
    ExecSPObjModel(AddFake, s, e);
}

function AddFake(s, e) {
    if (!SP.ClientContext)
        throw {
            name: 'Context Null Exception',
            message: 'SP.ClientContext has not loaded.'
        }
    var ctx = SP.ClientContext.get_current();
    //list = ctx.get_web().get_lists().getByTitle("Test List");
    list = ctx.get_web().get_lists().getById(_spPageContextInfo.pageListId);
    if (!list) {
        alert("List not found");
        return;
    }
    var itemCreateInfo = new SP.ListItemCreationInformation();
    this.newItem = list.addItem(itemCreateInfo);
    this.newItem.set_item("Title","Fake Item #" + ++loops);
    ctx.load(this.newItem);
    if ((1, eval)("this").newItem !== this.newItem)
        (1, eval)("this").newItem = this.newItem;
    ctx.executeQueryAsync(
        Function.createDelegate(this, this.onQuerySucceeded),
        Function.createDelegate(this, this.onQueryFailed)
    );
}
function onQuerySucceeded() {
    if (this.newItem)
        alert('Item created: ' + this.newItem.get_id());
    else if ((1, eval)("this").newItem)
        alert('Item created: ' + (1, eval)("this").newItem.get_id());
    else
        alert('No item returned');
}   
function onQueryFailed(sender, args) {
    alert('Request failed. ' + args.get_message() +
        '\n' + args.get_stackTrace());
}

when I click the button, the script executes, but when it hits .executeQueryAsync, I get

Uncaught TypeError: Cannot read property 'apply' of undefined

What am I doing wrong? Neither of the event handlers execute.

EDIT: Here is the working version of John-M's code from below:

function createListItem(listId) {
    // I'm going to keep all of the variables within this function's scope;
    // including the callback functions so that they have access to the calling
    // function's variables 
    var cc = SP.ClientContext;
    var clientContext = cc.get_current();
    var oList = clientContext.get_web().get_lists().getById(listId);
    var itemCreateInfo = new SP.ListItemCreationInformation();
    var oListItem = oList.addItem(itemCreateInfo);

    oListItem.set_item('Title', 'New item!' + ++loops);
    oListItem.update();

    clientContext.load(oListItem);
    clientContext.executeQueryAsync(onCreateListItemSuccess, onCreateListItemFailed);
    function onCreateListItemSuccess() {
        alert('Item created: ' + oListItem.get_id());
    }
    function onCreateListItemFailed(sender, args) {
        alert('createListItem request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
    }
}

var key = "sp.runtime.js";
var namespace = "SP.ClientContext";
SP.SOD.executeFunc(key, namespace, function () {
    console.log(key + ": Type available (" + namespace + ")");
}, false);
SP.SOD.executeOrDelayUntilScriptLoaded(function () {
    // then you can come wire up this function to your button   
    $("#btnAddNewCode").click(function () {
        createListItem(_spPageContextInfo.pageListId);
    });
}, key);

the last part is the details of was needed to get SP.SOD.executeOrDelayUntilScriptLoaded working so that I didn't have to rely on ExecSPObjModel.

  • 1
    Creative (ab)use of (1, eval)("this"). But you did not get that from the MSDN examples, Who taught you this? Because (in this code) is the same as window.newItem = – Danny '365CSI' Engelman Dec 22 '15 at 18:52
  • 1
    The answer almost assuredly lies within ExecSPObjModel, since it looks like you would have to use apply to call your AddFake function -- What are you trying to accomplish jumping through all the eval and apply indirection hoops anyway? In a function delegate (like you've created based on the MSDN example you can just use this.newItem since you already have the calling function for this – John-M Dec 22 '15 at 19:19
  • That is just some left-over cruft from debugging. Essentially, I was ruling out bad object references by using the global scope -- (1, eval)("this") Inside of ExecSPObjModel, this !=(1, eval)("this"). It is, however, unrelated to the issue at hand, as the error still happens identically with all of that stripped out. – SvdSinner Dec 22 '15 at 20:00
  • You know what I am going to double down on my bet it has something to do with how you're calling the function in ExecSPObjModel because Function.createDelegate also uses apply internally.. try calling AddFake directly from the onclick or from within AddFakeItem – John-M Dec 22 '15 at 20:02
  • I place my bet behind John; what is this inside your function? If it is not the global context then this.onQuerySucceeded is undefined. I say clean up your script, strip all that cruft. And I advice to make it work as a Chrome Snippet first; much easier to debug. And myfunc.bind(this) is modern ES5 code for oldschool Function.createDelegate(this, myfunc) – Danny '365CSI' Engelman Dec 22 '15 at 20:26
3

I would suggest ditching ExecSPObjModel and stick with the basics while you're getting up to speed with SharePoint's JavaScript Object Model.

Many of the MSDN examples make scoping more complicated than it needs to be... here is my suggestion to do the basic thing you're looking for:

function createListItem(listId) {
    // I'm going to keep all of the variables within this function's scope;
    // including the callback functions so that they have access to the calling
    // function's variables 
    var clientContext = SP.ClientContext.get_current();
    var oList = clientContext.get_web().get_lists().getById(listId);
    var itemCreateInfo = new SP.ListItemCreationInformation();
    var oListItem = oList.addItem(itemCreateInfo);

    oListItem.set_item('Title', 'New item!');
    oListItem.update();

    clientContext.load(oListItem);
    clientContext.executeQueryAsync(onCreateListItemSuccess, onCreateListItemFailed);

    function onCreateListItemSuccess() {
        alert('Item created: ' + oListItem.get_id());
    }
    function onCreateListItemFailed(sender, args) {
        alert('createListItem request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
    }
}
// then you can come wire up this function to your button   
SP.SOD.executeOrDelayUntilScriptLoaded(function() {
    $("#btnAddFake").click(createListItem( _spPageContextInfo.pageListId));
},"sp.js");

Using a construct like this, I don't need to worry about apply or Function.createDelegate or any of that nonsense -- and I don't create a bunch of globals because everything is inside the single function. For something like this I would use in production, I would just stick a function like this inside of a module (using whatever framework you want if necessary)

Other SharePoint stuff:

Don't use your own ctx variable, it is one that SharePoint defines within its own JavaScript execution environment and contains a bunch of useful properties. It may be annoying, but call it something else.

You can't check if a list exists with this:

...
list = ctx.get_web().get_lists().getById(_spPageContextInfo.pageListId);
if (!list) {
    alert("List not found");
    return;
} 
...

The call to ctx.get_web().get_lists().getById() will always return an SP.List object, even if a list with the given ID doesn't exist -- your later call to executeQueryAsync will let you know this, but then you'll wind up in an error callback anyway... SharePoint ClientObjects are always instantiated when you call the correct function, what they get "filled up" with later is entirely dependent on how they were created.

  • This doesn't work. If I don't call ExecSPObjModel, I don't have an SP.ClientContext object to get .get_current() from. What do I need to do to get a valid clientContext without ExecSPObjModel? – SvdSinner Dec 23 '15 at 15:13
  • @SvdSinner try wrapping the jQuery event binding in SP.SOD.executeOrDelayUntilScriptLoaded -- it should just be an issue with waiting for sp.js to load; I've updated my answer. And I realized you don't need new with SP.ClientContext.get_current – John-M Dec 23 '15 at 16:27
  • I am editing my question to include the version of your code that worked for me. Thank you! – SvdSinner Dec 23 '15 at 17:09
1

Can you give a try to this

ctx.executeQueryAsync(
        Function.createDelegate(this, this.onQuerySucceeded),
        Function.createDelegate(this, this.onQueryFailed)
    );

replace this block with

ctx.executeQueryAsync(onQuerySucceeded, onQueryFailed);

Let me know if it works for you.

  • This partly works. Switching to this syntax prevents the error, and causes the Success handler to run. However, this.newItem.get_id() comes back as 0, and no item actually gets created. – SvdSinner Dec 22 '15 at 20:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.