Using UpdateListItems to update a list item in Standard View

Feb 1, 2011 at 2:59 PM
Edited Feb 1, 2011 at 3:00 PM

Has anyone attempted to create a button on a list in standard view to invoke a SPService?

My need it to create a button or hyperlink for each item in the list that says "Mark Reviewed" - when a user clicks it, it will change a value in a choice column for that particular row.

I assume I can do this with one button above the list, and have them enter a value that I could pass to the caml query, but I am looking to do it a little more cleanly.

This is to allow a set of users who have 2-300 items to review to do so rapidly.  I am having a hard time wrapping my head around how to use a calculated column to invoke a SPServices action, or how to give the service a sense of "where it is".

Thanks!

w-

FYI - MOSS2007, WSS3, IE 8, no use of SharePoint Designer allowed

Feb 1, 2011 at 3:59 PM

It is sad you can't use designer, but I face that same issue a lot! With that said, you can do this, but you will need to use a content editor webpart. You can create a custom edit control block that could do it as well, but why not let the user just use the choice block? Am I missing something here?

Coordinator
Feb 1, 2011 at 4:03 PM
Edited Feb 1, 2011 at 4:03 PM

will266:

You can certainly do this. The one requirement will be to have the item IDs in the view. You can hide them in the view with the script, but they have to be there so that you can call UpdateListItems and update the right item each time. Your script can add the button or link per row, attaching a function to the click event that calls the jQuery to do the update per item. Does that make sense?

And as Daniel said, you can put your script into a CEWP.

M.

Feb 1, 2011 at 4:15 PM

I use CEWP's all the time to alter the presentation layer, as well to use SPServices, javascript etc.

I have taken this particular list, and used Quick Updates (2010 style updates from some JavaScript from Jan Tielens) but the client wants something even easier, since each reviewer has 2-300 to review each month, and this is not their primary job responsibility.  So speed is the key!

A grid view is out of the question, as the page they review from has lots of web=part-connected lists that give them the info they need to complete the review.

Basically, when looking at a list of "tasks"(really a custom list), they want a hyperlink on each line item that marks the item "Review Completed" for their particular area.  The list item actually has 16 groups reviewing it, each with their own choice block.

I am thinking I could put a CEWP on the page that would perform this, and then create a calculated column which would be a way to invoke the function in the cewp, and pass a unique identifier from the list item as a variable to be used in the CAMLQuery parameter.  Just not sure how to do it.

I can scrape the page URL to get the correct reviewer group to insure I update the correct reviewer block.  Just wondering how to pass the unique identifier and invoke the function.

Sorry if none of this makes sense!!

Coordinator
Feb 1, 2011 at 4:22 PM

At the core of this will be the UpdatelistItems operation in the Lists Web Service. If you have the ID for the item, you can do something like this (see: http://spservices.codeplex.com/wikipage?title=UpdateListItems):

    $().SPServices({
        operation: "UpdateListItems",
        async: false,
        batchCmd: "Update",
        ID: id,
        listName: "Tasks",
        valuepairs: [["Reviewed", "Yes"]],
        completefunc: function(xData, Status) {
          alert("completed");
        }
    });
M.
Feb 1, 2011 at 4:56 PM

Gotcha - I am using the updatelisttiems elsewhere with much success - usually I am scraping the URL of the DispForm.aspx to get the item ID or some other key value to use as a variable within the function.

I guess my real issue is a lack of understanding as to how to build a calc column to pass the id to the function, one - because you can't use ID in a calculation in SharePoint, and two - if I had the ID, how would I create a way to pass it to the CEWP. 

I do have another column that always has a unique value  (for argument sake, call it ASR).  Can I wrap the CEWP with the SPServices operation in another function - call it revComplete - and somehow create a column that would launch the function when clicked, and pass the ASR as a variable I would pick up in a CAMLQuery?

 

Maybe something like:

="<a onClick='revComplete(" & [ASR] & ")'>Mark as Reviewed</a>"

Sorry if that is lame - everything I know about html, SP, and JS I learned from Google!

 

Coordinator
Feb 1, 2011 at 5:07 PM
Edited Feb 2, 2011 at 3:07 AM

No calculated column required. You can do all of this in script.

Start out by displaying the ID in your LVWP. Then build a function that grabs the ID in each table row and adds the link for the action you want. (Make it a simple alert to start.)

Something like this (very rough):

$("table#some_id tr").each(function() {
  var thisID = $(this).find(":nth-child(1)").text();
  $(this).append("<td><a onclick='doSomething(" + thisID + ");'>Review Completed</a></td>");
});

M.


 

Feb 1, 2011 at 5:10 PM

ahhhh - a lightbulb is coming on...

I will play with this and report back!!

Bill

Coordinator
Feb 7, 2011 at 5:13 AM

Is that light bulb burning bright?

M.

Feb 7, 2011 at 2:51 PM

Still struggling - am trying to determine what to replace "some_ID" with.  Have used MouseOver to see every table ID, but when I try to use one of them, the page becomes covered in "Review Complete" statements.  Is there some easy way to determine what table ID should be populated there?

I have the ID in the first column of the LVWP - here is my code:

<SCRIPT type=text/javascript src="http://idnetmonitor/sites/rc/mastercode/jquery.current.min.js"></SCRIPT>

<SCRIPT type=text/javascript src="http://idnetmonitor/sites/rc/mastercode/jquery.SPServices.current.min.js"></SCRIPT>

<script>

$(document).ready(function() {

$("table#some_ID tr").each(function() {
  var thisID = $(this).find(":nth-child(1)").text();
  $(this).append("<td><a onclick='doSomething(" + thisID + ");'>Review Completed</a></td>");
});
});


function doSomething(){
alert(thisID)
}


</script>

 

Feb 7, 2011 at 3:21 PM

OK - learning as we go...

I realized the some_ID was a loop, but now the nth-child part is giving me problems - I have alerted the thisID value, and it comes back with the Title field of the list item in duplicate (i.e. the value is MIKE and the alert comes back MIKEMIKE).  I have tried changing the nth-child value to 2, 3, 4, etc, yet then my alert is blank.

What I end up with is a the words Review Complete in the Title field pushed to the far right (i.e. MIKE                                             Review Complete) that when I click on it, nothing occurs, and when I hover over each roww, the Review Complete words disappear.  They are also pretty large by comparison.

Thanks!

pertinent part of code:

var i=0;
for (i=0;i<=4;i++)
{
$("table#" + i + " tr").each(function() {
  var thisID = $(this).find(":nth-child(1)").text();
  $(this).append("<td><a onclick='doSomething(" + thisID + ");'>Review Completed</a></td>");
alert(thisID)

});
}

Feb 7, 2011 at 5:44 PM

Will,

     I think you are missing the important step that Marc was referring to. You need to add the ID field as a column. In Marc's example, I believe it was the first column. So that the code :

var thisID = $(this).find(":nth-child(1)").text();
will find the ID in the first column in this case.
Dan

 

Feb 7, 2011 at 7:09 PM

Sorry I didn't include in my previous comments - I have the ID field displayed, and it exists as the first column in my lvwp.  No matter what I do to reorganize the columns, it seems to follow the Title field, and drop the "Review Complete" reference there.  Plus, alerting on the value of this_ID always yields the Title value in duplicate (one alert, text repeated).

I looked a little bit more at the jQuery "nth-child", and that defines how often to grab the value (i.e. 1=every row) - not the placement of the value in the row. 

When this value is "1" I get an alert with the duplicated Title value for "alert(thisID)" in the looping function, but clicking on the Review Complete link yields nothing.

Ironically, when I set this value to "2", it alerts a blank value in the looping section, but clicking on the Review Complete link yields the alert "eek".

A tad confusing!

Bill

<SCRIPT type=text/javascript src="http://idnetmonitor/sites/rc/mastercode/jquery.current.min.js"></SCRIPT>

<SCRIPT type=text/javascript src="http://idnetmonitor/sites/rc/mastercode/jquery.SPServices.current.min.js"></SCRIPT>

<script>

var i=0;
for (i=0;i<=200;i++)
{
$("table#" + i + " tr").each(function() {
  var thisID = $(this).find(":nth-child(1)").text();
  $(this).append("<td><a onclick='doSomething(" + thisID + ");'>Review Completed</a></td>");
alert(thisID)
});
}

function doSomething(thisID){
alert("eek")
}


</script>

Feb 7, 2011 at 7:22 PM
Edited Feb 7, 2011 at 7:40 PM

OK - a lot closer now.

This code is able to get the item ID, even it with not displayed - my list has a lot of deleted items, but this grabbed and spit back out the ID everytime.

Only issues now are:

  • the Review Complete link only appears on the Title(link to edit menu) column.  No other Title columns work.
  • it is appending the "Review Complete" to the Title - pushed all the way to the right of that column
  • The actual Title is now just a link to the DispForm (where normally would be a dropdown menu)
  • when I hover over the "Review Complete" link, it enlarges and appears "chromed" - similar to a web part title chrome.
  • when I mouse away, the Review Complete, disappears.
  • hovering back over the item causes the Review Complete to re-appear, in a "chromed" appearance
  • Clicking "Review Complete", I get the ID in the alert, but then the "Review Complete" disappears

Can anyone recommend what class I should apply to the TD and how to split it off of the Title column?

Here is the code:

<script>

var i=0;
for (i=0;i<=200;i++)
{
$("table#" + i + " tr").each(function() {
  var thisID = i;
  $(this).append("<td><a onclick='doSomething(" + thisID + ");'>Review Completed</a></td>");
 });
}

function doSomething(thisID){
alert(thisID)
}


</script>

 

 

Feb 7, 2011 at 9:30 PM

oops my bad on nth child as I missed what it was doing, but I understood what I was trying to say :) However, I just do not get what this code is supposed to be doing. Do you have 200 tables? If you put an id on your table you could simplify this as it looks like you are trying to select 200 tables (table#0, table#1, table#2 etc) If your table has one id for example tblData you could then just use:

var i = 0;
$("#tblData tr td:eq(1)).each(function() {
$(this).append("<a onclick='doSomething(" + i + ");'>Review Completed</a>");
i++;
});

You can replace the 1 in eq(1) with the column you want to place the link in. This should do part of what you need. However, if I understand what you are trying to do, you want the id of the item to be used for the link correct?

Feb 7, 2011 at 9:44 PM

This kind of boils down what I am trying to accomplish, and am really close to achieving (the devil is in the details!!).

I have a list view web part where I want to inject a link ("Review Complete") on each list item that, when the user clicks on the link, it will change the Status to Complete for that list item.

The variable i is the ID of the row - somehow the code I have is gathering that.  For my example I crudely limited it to 200, but will later use GetListItems to get the highest ID value for the list.

At this point, I have it with the Review Complete link (with all its quirks) and when I click it, it correctly changes the Status for the record in Question.  At this point, I am just trying to nail it down to a location of my choosing, and remove the quirks in the bulleted list above.

Here is a more recent selection of the code, with the follow on action:

<SCRIPT type=text/javascript src="http://idnetmonitor/sites/rc/mastercode/jquery.current.min.js"></SCRIPT>

<SCRIPT type=text/javascript src="http://idnetmonitor/sites/rc/mastercode/jquery.SPServices.current.min.js"></SCRIPT>

<script>


var i=0;
for (i=0;i<=200;i++)
{
$("table#" + i + " tr").each(function() {
  var thisID = i;
  $(this).append("<td><a onclick='statComplete(" + thisID + ");'>Review Completed</a></td>");
  });
 }

function statComplete(thisID){
    $().SPServices({
        operation: "UpdateListItems",
        async: false,
        batchCmd: "Update",
        ID: thisID,
        listName: "Supervisor",
        valuepairs: [["Status", "Complete"]],
        completefunc: function(xData, Status) {
        }
    });
}


</script>

 

Feb 8, 2011 at 3:56 PM

Based on your code so far, I have to assume that you are not filtering the list at all and that your list is sorted by the ID column in ascending order. I say this because the code you are using still does not make sense to me, and might not work if the view is filtered or sorted as it will change the order of the rows in the table and the loop you are using does not seem to cover this.  The result of the selector you are using again appears to be selecting items like table#0 tr, table#1 tr, ...table#i tr which seems to be one of the main causes of the problems. I do not think you have those on the page, but jQuery is finding "something" and putting this link there. I might be able to find some code that does this, but I do not use a dvwp, I just use SPServices to create the table with the links so it might not be as helpful...

Feb 8, 2011 at 4:34 PM

Ironically, the list is not sorted by ID - it is sorted on a different field, and my list has deleted items, etc, yet the code always finds the correct ID without fail.  In my mind, it is nothing short of magic.

For example -  first item in my LVWP is ID=17, and the ID column is not even part of the view.  I click the Review Complete link, and it changes the Status for that record, and no others.  I skip down several rows to ID=6, click the link, and it updates that record.

I use MouseOver to view the structure of the page, and when my mouse is hovering over a the Title cell in the row, it shows:

   table id="9" class="ms-unselectedtitle"

Perhaps this is why:

  • it knows the ID (for the row in question the ID was indeed=9)
  • it is appending the Review Complete to the Title

Perhaps I should use the first part of the  code to get the thisID value, and a different bit of code to do the append into the main table...thoughts?

I had not considered using SPServices for the whole enchilada - using GetListItems to build my "LVWP" alternative.  I just felt I was really close with this - it certainly identifies the correct ID and allows me to take action on it, it is just not rendering very well...

Thanks for all the great feedback - I am a true novice, stumbling upon SPServices a few months back, and with no JS training, so I love any advice I get!

Bill

Feb 8, 2011 at 5:30 PM

Ahah! Your response enlightens me and now I get what your code is doing and why it behaves the way it does. You are actually hijacking the Edit Control Block title field! This is the reason your drop down goes away. This is actually rendered as a table element based on the id of the item so that the dropdown knows what item it affects. In short you are adding your link to this instead of the actual rows of the "real" table! This is why the code looked funny to me and I think I have a plan for you. Using "View Source" or whatever tool you are using to add code to the page, you should find the actual id of the table rendered by the LVWP. These webparts are actually very table driven themselves and it is this part that makes them harder to navigate through. Let me look at some code and I will post something later.

Feb 8, 2011 at 6:24 PM

Okay! I think I have something for you here that just might work. I tested this on my LVWP on a page and it worked great! The only caveat here is that if you mess with the CSS classes on the page, this might not work. IAW, if you change the class of certain items this might break otherwise this is what I have that works:

$(".ms-vb-title table").each(function() {
     var theId = $(this).attr("Id");
     $(this).parent().parent().prepend("<td><a onclick='alert(" + theId + ")'>Alert Me!</a></td>");
});

The ms-vb-title is the default class of the td element where the title field is. Inside this td is the table for the Edit Control Block stuff. It has the Id attribute and so I am looping the page getting this value and prepending the new td element to the row which you have to climb up twice with the parent selector! Whew!!

Feb 8, 2011 at 8:25 PM

Very nice!!

I tried the prepend, but my column headers were then off, so I stuck with append, which placed my link at the end of the displayed row.

Do you know of a way to make it so I can place the td at a place of my choosing, without skewing my column titles?

THANKS SO MUCH - this is awesome!!

Bill

Feb 8, 2011 at 9:29 PM

OK - I added a column title in the header row using this snippet, but it adds it twice.  I am sure there is something wrong with using the .each reference here...

$(".ms-vh2 table").each(function() {
     $(this).parent().parent().parent().append("<td class='ms-vh2'><a>My custom action</a></td>");
});

Thanks again!

Bill

Feb 8, 2011 at 9:59 PM

You could try to use this selector as I use it to add a wrapper to my list views so that the content inside scrolls:

$("TR.ms-viewheadertr:first").prepend(...)
This should be the header row for the first LVWP on a page. It should do what you need I think!

Feb 8, 2011 at 10:06 PM

I was able to break the .each loop after the first successful find by letting it only run if the index=0:

$(".ms-vh2 table").each(function(index) {
if(index==0){
     $(this).parent().parent().parent().append("<td class='ms-vh2'><a>My custom action</a></td>");
}
});

I will take a look at your suggestion, to see if it adds some flexibility for me!

Thanks so much for all the suggestions and guidance!

Bill