locked
Using AngularJS Wijmo Grid With LightSwitch OData RRS feed

All replies

  • Looks like another winner Michael.  May I ask where you found some good Angular tutorials?  You've been incorporating it into a number of your posts lately.  I've looked at it as well but it's got a bit of a learning curve.

    Is the ODataCollectionView mentioned in the article your customized one?  I saw in the Additional Resources section of your post the chat you'd had with Wijmo, and it sounded like you gave their component some error handling support.  Wasn't sure if they'd incorporated it into their version or not.


    Monday, October 27, 2014 2:02 AM
  • Thanks :)

    The ODataCollectionView in the article is customized but ComponentOne/Wijmo plans to incorporate most of the changes. This is not a LightSwitch issue, it is an issue with their sample code for OData support (they are concentrated on the controls so they are supporting many scenarios which include AngularJS and OData among other things such as TypeScript and pure JavaScript). 

    As far as learning Angular, I watched some Pluralsight videos and used this book: AngularJS: Up and Running. 


    Unleash the Power - Get the LightSwitch 2013 HTML Client / SharePoint 2013 book

    http://LightSwitchHelpWebsite.com

    Monday, October 27, 2014 4:36 AM
  • For something as simple as using a single wijGrid control for a LightSwitch collection as in this example, it is not necessary to create a separate ASP.NET MVC project.  The Angular WijGrid is very easily manufactured as a custom control.  Here's how:

    Using the same project, we'll add the necessary Wijmo scripts and CSS files to the default.htm in the LightSwitch HTML client as CDN files:

        <link href="http://cdn.wijmo.com/5.20142.15/styles/wijmo.min.css" rel="stylesheet" type="text/css" />
        <link href="http://cdn.wijmo.com/5.20142.15/styles/themes/wijmo.theme.modern.min.css" rel="stylesheet" type="text/css" />
    
        <script src="http://code.jquery.com/jquery-2.1.0.min.js" type="text/javascript"></script>
        <script src="http://cdn.wijmo.com/5.20142.15/controls/wijmo.min.js" type="text/javascript"></script>
        <script src="http://cdn.wijmo.com/5.20142.15/controls/wijmo.grid.min.js" type="text/javascript"></script>
        <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.1/angular.min.js" type="text/javascript"></script>
        <script src="http://cdn.wijmo.com/5.20142.15/interop/angular/wijmo.angular.min.js" type="text/javascript"></script>
      

    Although probably not necessary, jQuery is updated to 2.1 since that is what is on the Wijmo 5 demonstration documentation. 

    This example involves a simple implementation of the powerful WijGrid with only a few options and two columns, so adding it as a jQuery object in the usual fashion wouldn't be too cumbersome.  But since we're using Angular, we have the option of using the partial view directive ngInclude or a custom directive.  WijGrid already is a custom directive, so we'll go with ngInclude.  On a Browse screen of ToDoes that has its behavior set to Edit, we'll create a custom control and designate the Angular partial view file:

    myapp.BrowseToDoes.ToDoes_render = function(element, contentItem) {
        // Write code here.
        element.innerHTML = "<ng-include src=\"'./Partials/ToDoPartial.html'\"></ng-include>";
    
    


    In a folder named Partials in the HTML Client, the mark-up for the grid is entered in the file ToDoPartial:

    <div ng-controller='AppToDoCtrl'>
        <wj-flex-grid class="sGrid" allow-add-new="true" allow-delete="true" items-source="data.toDo">
            <wj-flex-grid-column binding="TaskName" width="300" header="Task Name"></wj-flex-grid-column>
            <wj-flex-grid-column binding="IsComplete" datatype="Boolean" width="200" header="Completed"></wj-flex-grid-column>
        </wj-flex-grid>
    </div>
    


    Now, we could create a separate JavaScript file for our controller AppToDoCtrl, which normally would be ideal.  However, in doing so, we'll lose the local scope containing the contentItem and as a result have to implement some REST services.  The ODataCollectionView library looks very interesting, although I haven't specifically experimented with it since it isn't yet available at C1 Wijmo site.  I have successfully used the vanilla AngularJS $http and ngResource services to read and write LightSwitch OData.

    Since this controller is incredibly simple, and to maintain the contentItem scope, we'll include it in the custom control method:

    myapp.BrowseToDoes.ToDoes_render = function(element, contentItem) {
        // Write code here.
        element.innerHTML = "<ng-include src=\"'./Partials/ToDoPartial.html'\"></ng-include>";
    
        contentItem.value.addChangeListener('isLoaded', function() {
            var app = angular.module('app', ['wj'])
                .controller('AppToDoCtrl', function($scope) {
                    $scope.data = {};
                    var cv = new wijmo.collections.CollectionView(contentItem.value.data);
                    cv.newItemCreator = function() {
                        var item = new myapp.ToDo();
                        return item;
                    };
                    $scope.data.toDo = cv;
    
                });
    

    The controller is wrapped in a function that ensures the visual collection is loaded before rendering the WijGrid.  WijGrid implements ICollectionView for exposing data in JavaScript arrays, in this case contentItem.value.data, the array of ToDoes in the Visual Collection.  The CollectionView API has a method newItemCreator() which is used to initialize a new To Do entity when a blank line on the grid is double-clicked.

    Now all that is left is for Angular to parse the HTML.  Normally, this occurs when you include ng-app in the DOM node containing your Angular mark-up.  As soon as Angular sees ng-app, it commences parsing the HTML for Angular directives.  Because the HTML containing this info is added only after the page is created and jQueryMobile finishes enhancing the page, you need to bootstrap Angular manually and not include the ng-app directive anywhere in your HTML pages.

    myapp.BrowseToDoes.ToDoes_render = function(element, contentItem) {
        // Write code here.
        element.innerHTML = "<ng-include src=\"'./Partials/ToDoPartial.html'\"></ng-include>";
    
        contentItem.value.addChangeListener('isLoaded', function() {
            var app = angular.module('app', ['wj'])
                .controller('AppToDoCtrl', function($scope) {
                    $scope.data = {};
                    var cv = new wijmo.collections.CollectionView(contentItem.value.data);
                    cv.newItemCreator = function() {
                        var item = new myapp.ToDo();
                        return item;
                    };
                    $scope.data.toDo = cv;
                });
            angular.bootstrap(element, ['app']);
        });
    };

    angular.bootstrap takes arguments for the DOM node, in this case the _postRender's element, and any module(s) that are needed to expose the controller (which can be optional).

    The final result:

    Monday, October 27, 2014 8:34 AM
  • @LittleBobbyTables.

    My Lord, I tried this so many times, bootstrapping AngularJS inside LightSwitch.

    angular.bootstrap... how could I miss this in the splendid Angular documentation.

    Thanks a lot !



    paul van bladel ==independent enterprise application architect== http://blog.pragmaswitch.com

    Monday, October 27, 2014 3:44 PM
  • Nice article Michael and I like your contribution too, Allen.  After a cursory read through of both, it seems perhaps a clarification or two may be helpful for readers of this thread. 

    First, the solution in Michael's blog does not require a separate MVC project.  The solution has 'MVC' in the name.  I believe is a remnant from an earlier blog about using MVC to authenticate an angular app.  Michael makes that clear in the article when he says "However, the code sample will work exactly the same in a normal non-MVC LightSwitch application."

    Second, Michael's scenario is exposing the grid in an angular HTML app connected to LS OData while LittleBobbyTable's scenario is rendering the grid in the LS HTML Client using VC data from the screen and angular to handle template html.

    Not having done either, I could be wrong, but rather than alternative methods toward to same solution, I see the two as separate use cases - the distinction boils down to "Are you in pure angular app or LS HTML Client?"

    That said, this is the first example I've seen of an angular template rendered inside an HTML Client custom control with data bound to VC.  Thanks Allen, please keep sharing.

    Cheers,

    Josh

    Monday, October 27, 2014 4:19 PM
  • Thanks for the explanation.  If I can followup that question with another one, it sounds like Wijmo has its own OData support libraries, while if you're rolling your own app without them and using Lightswitch as the OData source, something like Jaydata makes sense?
    Monday, October 27, 2014 5:25 PM
  • You are correct...the server project in a LightSwitch application is ASP.NET technology, and whether you use MVC or web forms or SharePoint, the techniques described in Michael's blog require the AngularJS to be part of the server project and some routing classes to shuttle between the LightSwitch HTML client and the ASP.NET page.  So in essence, two parallel projects in one solution that share a common LightSwitch model.  The example I showed above has AngularJS self-contained within the LightSwitch HTML Client.  I think that choosing either approach depends on the complexity of your project. 

    Currently, all of the major commercial vendors (Telerik, C1, Infragistics, DevExpress, SyncFusion) have created AngularJS custom directives of their JavaScript controls.  I would encourage anyone interested to experiment with this approach using any of these.  Inasmuch as it hasn't been blogged before and Paul feels chagrined he didn't come across angular.bootstrap(), well, we can blame the folks at Twitter for burying the technique on page 40 of a Google search for "Bootstrap Angular."

    Monday, October 27, 2014 5:32 PM
  • Thanks for the explanation.  If I can followup that question with another one, it sounds like Wijmo has its own OData support libraries, while if you're rolling your own app without them and using Lightswitch as the OData source, something like Jaydata makes sense?

    I asked basically that question of Wijmo and they basically said that 'at this time' you are suppose to 'roll your own ODataCollectionView' starting with the sample they provided (they intend to make it part of the 'core' at a later date).

    However, at this time, the example I have in my article (enhanced with help from Dale M. and Wijmo) seems to work as well as JayData so, no, I would not use JayData with Wijmo.


    Unleash the Power - Get the LightSwitch 2013 HTML Client / SharePoint 2013 book

    http://LightSwitchHelpWebsite.com

    Monday, October 27, 2014 6:00 PM
  • Yes, thanks for sharing both approaches.  

    Bootstrapping AngularJS inside a LS HTML client app is very interesting indeed. I wonder how this will work out for editable screens/controls when exposing the Entity to the AngularJS $scope and using AngularJS to do the binding on the edit controls? Although one would have to figure out how to declare those LS native controls inside the AngularJS partial...

    Very interesting indeed.


    Regards, Xander. My Blog

    Monday, October 27, 2014 10:25 PM
  • Previously I mentioned that defining a controller in a separate JavaScript file had the disadvantage of losing the local contentItem scope of the custom control. Although you could use OData methods or LightSwitch API query methods to reload the data from the server, another work-around involves using Angular services. For example, if we wanted to put this controller in a separate file AppToDoCtrl.js, we could declare a dependency on a service to inject the local contentItem:

    var app = angular.module('app', ['wj']);
    
    app.controller('AppToDoCtrl', function($scope, contentItemService) {
        $scope.data = {};
        var cv = new wijmo.collections.CollectionView(contentItemService.contentItem.value.data);
        cv.newItemCreator = function() {
            return new myapp.ToDo();
          };
        $scope.data.toDo = cv;
    
    });

    The custom control code then becomes:

    myapp.BrowseToDoes.ToDoes_render = function(element, contentItem) {
        // Write code here.
        element.innerHTML = "<ng-include src=\"'./Partials/ToDoPartial.html'\"></ng-include>";
    
        app.service('contentItemService', function() {
            this.contentItem = contentItem;
        });
    
        contentItem.value.addChangeListener('isLoaded', function() {
          angular.bootstrap(element, ['app']);
        });
    
    };
    ...and there's one step further for loose coupling and reusability achieved, although I am still experimenting with Angular factories and providers for more flexible dependency injection. 


    Thursday, October 30, 2014 2:02 AM