Displaying list forms in the same div

Oct 10, 2014 at 7:31 PM
First, I want to thank you for the very hepful SPServices library. The search services has been especially useful to me.

I'm trying to create an SPA application in SP2007 using SPServices and SharePoint Designer. I want to have a left pane and a right pane. The left pane will have a bunch of links to items from different lists. The right pane will display the list forms (DispForm, NewForm, EditForm) every time a link is clicked from the left pane OR the right pane (clicking a lookup link in a DispForm rendered on the right pane should open up the target DispForm in the right pane, not in a separate window).

What is the best approach to implement this? I tried the code below to prevent a new window from opening and load the onetIDListForm div in the right pane. It works for all links except links in the right pane (from the loaded DispForm). Is there a better way?
                $("[href*='Form']").click(function( event ) {
                //prevent display page from appearing
                event.preventDefault();
                
    
                //get url
                var thisURL = $(this).attr('href') + ' #onetIDListForm';
                var thisTitle = $(this).html();             
                    $('#itemPage').load(thisURL);
                    $('#itemPageHeading').html(thisTitle);
                    
                                    
                });
Coordinator
Oct 15, 2014 at 1:36 PM
I'd consider using a framework to make things easier on yourself (after the initial ramp up). I'm using KnockoutJS a lot with SharePoint 2007 and 2010 these days and building some very cool SPAs.

M.
Oct 15, 2014 at 1:44 PM
Have you tried working with frames? Your basic page could include all of the links you build in the left pane and an iframe element in the right pane to render whatever you need. Your links on the left can be set to just change the source of the iframe element, then process the contents of the element on the fly to hide everything you don't want displayed. The links internal to the iframe will still work as they normally do.

The other option is to create your own version of the DispForm for the right pane and populate it via script as the links on the left are clicked. That way, you can build the lookup links as you see fit and direct pop-ups, same window, message boxes, or whatever you want. I've done something like this and it is quite simple and surprisingly efficient. You can borrrow the basic architecture from any existing DispForm.aspx page to just design the form area and then populate the data elements with an SPServices call. Each link on the left could run a sub-routine in the page that just reloads the content on the right--doesn't require a page refresh each time since you're doing an SPServices call for the data and manipulating the existing DOM to display it.

Geoff
Oct 15, 2014 at 3:21 PM
Thanks, Marc. I was actually considering AngularJS since I'm somewhat familiar with it, but I can look into Knockout. Do you have a working example that I can use for inspiration?
Coordinator
Oct 15, 2014 at 3:24 PM
If you're familiar with AngularJS, then go with that. Depending on your requirements, either framework can do the job.

M.
Oct 15, 2014 at 3:29 PM
Geoff,

The first method seems like a great idea that doesn't require much coding. The only issue is that the page would have only one URL and it wouldn't change with each click. How do I build unique urls so that if the URL is shared, it goes to that specific item instead of the base SPA page.

Do you have an example of the second method? Could that work for multiple lists?
Coordinator
Oct 15, 2014 at 3:37 PM
Given your question about URLs and your familiarity with AngularJS, go that way. You get the routing capability there so that the URLs are unique.

M.
Oct 15, 2014 at 6:22 PM
I was avoiding Angular because I want the server to do the heavy lifting with the listforms instead of building my own on the client side. I also want to accommodate links users would get automatically (e.g., links from alerts). In addition, I also have several DispForms with mashups (DVWP's filtered by QueryStrings). Given all this, is Angular the right way to go?

I remember seeing you demo a Task list SPA you built in Knockout during your SP24 conference session. Is that publicly available?
Coordinator
Oct 15, 2014 at 6:32 PM
That Tasks thing I showed isn't available because it's got a bunch of client stuff embedded in it.

You can AJAX in the DispForm.aspx page, parse out the actual form, and drop it into the page. You'd need to get the digest right, etc., but it's possible.

Unless you need this to be general purpose, I think you'd end up with a better result if you built is with AngularJS. The underlying DVWPs don't have legs.

M.
Oct 20, 2014 at 5:09 PM
I started building the AngularJS app in SP2007. The index page works fine but as soon as I reference an app.js file that defines a controller, it doesn't work (expressions don't evaluate). Do you know what may be causing this issue?
Oct 20, 2014 at 8:26 PM
myahia wrote:
I started building the AngularJS app in SP2007. The index page works fine but as soon as I reference an app.js file that defines a controller, it doesn't work (expressions don't evaluate). Do you know what may be causing this issue?
...what are you getting in the console?
Oct 20, 2014 at 9:51 PM
I get all the objects int he console, but they don't evaluate in the expression. My code is below. SPGetCurrentSite(); binds to the view, but the objects from SPGetListItemsJson don't.
//controller

app.controller('TermsCtrl', function ($scope) {
    
    $scope.SiteData = $().SPServices.SPGetCurrentSite();
        
    var p = $().SPServices.SPGetListItemsJson({
        listName: "Terms"    
    });

    $.when(p).done(function() {
    
    $scope.Terms = this.data;
    console.log($scope.Terms);
    
    });

});
//view
    <div ng-app="myApp">
      <div ng-controller="TermsCtrl">
        <h1>{{SiteData}}</h1>
        <ul>
            <li ng-repeat="term in TermsCtrl.Terms">
              {{term.Title}}
            </li>
        </ul>
      </div>
    </div>
Coordinator
Oct 21, 2014 at 12:22 PM
I have no idea about the AngularJS side of this. Is the column in the Terms list called "term"? Might it be Title?

M.
Oct 21, 2014 at 12:46 PM
term in the ng-repeat is an index to iterate over the Terms objects. I see all the objects in the console, but the expression doesn't evaluate on the DOM.
Coordinator
Oct 21, 2014 at 1:23 PM
Right, sorry. I didn't read your code carefully enough.

M.
Oct 21, 2014 at 2:41 PM
Edited Oct 21, 2014 at 2:42 PM
You don't name the controller in the ng-repeat or anywhere else on the page other than the very top or the isolated area you're trying to apply angular to. Right now you're doing TermsCtrl.Terms. That's not going to work or I've never seen it done like that before.

This is your mistake:
<li ng-repeat="term in TermsCtrl.Terms">
              {{term.Title}}
</li>
Change it to
<li ng-repeat="term in Terms">
              {{term.Title}}
</li>
Oct 21, 2014 at 4:52 PM
Edited Oct 21, 2014 at 4:54 PM
SimK,

Really appreciate your taking the time to help me! Much appreciated. I tried removing the TermsCtrl and it still doesn't work. I think my issue is with the promise callback, not the Angular expressions.

Here's the new code. I put the call to SPGetListItemsJson in a service called "TermData". The "SiteData" service works fine and displays on the page, so means the problem is with the promise. Also tried calling SPGetListItemsJson from controller itself - same results.

The console.log output from inside the .done method lists all the objects fine, but the console.log output outside of that returns "object: {state: function, always: function...}

Again, it's the promise I think, but I can't put my finger on the precise cause.
//index.html
<!DOCTYPE html>
<html>
 <head>
   <title>dsApp</title>
 </head>
 <body>
    <div ng-app="myApp">
      <div ng-controller="TermsCtrl">
        <h1>{{site}}</h1>
        <ul>
            <li ng-repeat="term in terms">
              <h2>{{term.Title}}</h2>
            </li>
        </ul>
      </div>
    </div>

    <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
    <script  type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular.min.js"></script>
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular-route.min.js"></script>
    <script type="text/javascript" src="main.js"></script>
    <script type="text/javascript" language="javascript" src="//cdnjs.cloudflare.com/ajax/libs/jquery.SPServices/2014.01/jquery.SPServices.min.js"></script>


  </body>
</html>
//main.js

var app = angular.module('myApp', ['ngRoute']);

app.controller('TermsCtrl', function ($scope, SiteData, TermData) {
    console.log(TermData);
    $scope.site = SiteData;
    $scope.terms = TermData;
});

app.factory('SiteData', function() {
    return $().SPServices.SPGetCurrentSite();
});

app.factory('TermData', function () {
    return $().SPServices.SPGetListItemsJson({
        listName: "Terms" 
    }).done(function() {
        console.log(this.data);
        return this.data;
    }); 
    
});
Oct 21, 2014 at 8:04 PM
Does {{site}} work? Get rid of the other stuff and just test the basics. Is your scope connected to your view at all. From there if that works then you know definitely it's spgetlistitemsjson (which I never got to work myself - didn't try that hard).
Oct 21, 2014 at 9:13 PM
Yes {{site}} does evaluate correctly.
Oct 22, 2014 at 5:39 AM
Edited Oct 22, 2014 at 5:05 PM
Try using the get list item without the json, like i said, I wasn't able to get it working with JSON myself. Also try calling it from the controller instead of the factory just to see if it works. If it does, figure out how to move it to a factory properly. You really just need to troubleshoot it yourself and try different things do, make sure your spservice is returning something, make sure what it's returning is properly assigned to the scope, make sure you view is correctly setup, put logs everywhere.

This is the example on how to do it in the documentation, follow that:
var traineePromise = $().SPServices.SPGetListItemsJson({
    listName: "Trainees",
    CAMLQuery: "<Query><Where><Eq><FieldRef Name='accountName' LookupId='TRUE'/><Value Type='Integer'>" + opt.traineeId + "</Value></Eq></Where></Query>",
    changeToken: opt.changeToken,
    mappingOverrides: {
        ows_Scores: {
            mappedName: "Scores",
            objectType: "JSON"
        },
        ows_Attendance: {
            mappedName: "Attendance",
            objectType: "JSON"
        }
    }
});

$.when(traineePromise).done(function() {

    thisTraineeUser = this.data;

});
Oct 22, 2014 at 12:59 PM
Got it! I need to propagate the data to outside of the then method. Used $scope.$apply(); for that.

//new controller
app.controller('TermsCtrl', function ($scope, $q, SiteData) {
    
    $scope.myTerms = [];

    $scope.site = SiteData;
        
    $().SPServices.SPGetListItemsJson({
        listName: "Terms" 
    }).then(function() {
        $scope.myTerms = this.data;
        $scope.$apply();
        console.log($scope.myTerms);
    });     
});
May 1, 2015 at 2:36 AM
Edited May 1, 2015 at 2:39 AM
Realize this is an older post – but while I was searching for a solution on calling multiple "getlistitems" I ran across this issue and noticed you said you were trying to prevent new windows from opening. Not sure if this will help on this project, but it's handy to include both "preventD" & "returnV false" when you are calling preventDefault.
event.preventDefault ? event.preventDefault() : event.returnValue = false; /* IE 8 */
Hope this is helpful!

Also thanks for the lead on the AngularJS and SPServices. Really cool.