Blog

May 19
LightSwitch Dynamic Connection Strings Now Supported

Yes that's right...no really...not kidding.

Visual Studio 2013 - Update 2 released last week and Eric Erhardt dropped a hint today here saying the following:

In the latest version of LightSwitch (VS 2013 Update 2) a new partial method was introduced on DataService classes that connect to a backing database. This method's signature is partial void <DataSourceName>_InitializingConnection(DatabaseConnectionState state). The state object has a ConnectionString property, that allows you to set the connection string to whatever you choose at runtime. This allows you to switch database instances, or username/password, etc.

This should be able to provide as much flexibility you need.  From this method, you can grab the current Application.User, and switch off of it.  Or you can call into SharePoint to get more information. 

That sounded specific to databases so I asked about sharepoint and he said this:

This method is only available for "Database" connections (either the ApplicationData or another attached Database data source). For OData service and SharePoint data sources, the new partial methods are:

partial void DeveloperData_SendingRequest(ODataSendingState state)

 

partial void DeveloperData_ReceivedResponse(ODataReceivedState state)

These methods allow you to intercept/inspect the outgoing request to the backend OData service, and inspect the incoming response from the backend service.

Just an FYI - none of these new methods show up in the Write Code drop down in the designers.  To get to them, you need to open the code file in the Server project (or generate it by selecting SaveChanges_CanExecute from the Write Code drop down, and then deleting the generated method stub).  Once in the xxxDataService class, type "partial " and from the auto-complete box, select the corresponding partial method.  In VB, you can select the partial method from the drop down at the top of the code file.

Wow, this is HUGE!

Look out for a Team blog about this and perhaps several from community members who'll prolly drop everything to try it out!

 Thank you LS Team, you're the best.

UPDATE:  2014-05 JUNE I still havn't had time to try this but Michael Washington has a nice post which includes this info here:

http://lightswitchhelpwebsite.com/Blog/tabid/61/EntryId/3264/Creating-A-LightSwitch-SharePoint-2013-Multi-Tenant-Provider-Hosted-Application.aspx

UPDATE:  2014-03 JULY I still havn't had time to blog about this, but OData URI cannot be changed in the new _Sending Request method.  This merans OData connections are not dynamic at runtime.

Eric's Comments in the above mentioned thread:

"Unfortunately changing the Request URI isn't something you can do during the OData service _SendingRequest method.  We need to expose another method - BuildingRequest - that would allow you to change the Request URI."

"The underlying design of the _SendingRequest method is that we use the OData Client's DataServiceContext.SendingRequest2 event, and whenever that is raised, we call the _SendingRequest in your code.  It was realized too late in the release cycle that the SendingRequest2 event didn't allow the URI to be changed. In order to do that in the OData client DataServiceContext, we need to subscribe to the BuildingRequest event."

IMPORTANT:  The popular long-standing Uservoice suggestion for this was incorrectly marked completed and unfoirtunatly  I was asked to create a new one specific to OData.  Your votes and sharing is very much appreciated:

PLEASE VOTE:

Allow Lightswitch OData connection URI to be changes dynamically at runtime 

May 06
msls Exposed: Declarative Binding

 

msls.js is amazing and we only see a fraction of it.  Recently I tried an experiment to see more at runtime by exposing everything in the msls namespace.  This is the first in perhaps several posts on what new morsels are found under the covers.

It's been enlightening to learn from some js masters in the LS forums.  Recently it's been around declarative data binding in the HTML Client.  I wrote a post with one way to do this and forums user LittleBobbyTables shared some great insights on how to do it better using WinJS binding with HTML templates and he said:

One goal of mine in posting these samples is to encourage developers smarter than me (read: all of you guys seeing this) into possibly experimenting on their own, then sharing their discoveries here on the forum. 

While I'm definitely not smarter, I have been experimenting so I'll share...

The ContentControl

There is a control in msls called ContentControl which does declarative binding.  msls uses it internally to render headers and commands bars and-the-like from html templates.  Some of the code for this control can be seen in my post from Jan 29th in the above referenced forum thread.

Two problems: 

1) You cannot create this type of control in the designer and

2) msls.ui.controls namespace is not 'exposed'.

Well as an experiment, I exposed everything, then tried this, in a custom control bound to screen.Customer:

(UPDATE: I'm having issues with posting this code on the blog, silly editor keeps altering my code  please get the code from here:  https://gist.github.com/joshbooker/ed30e98c62534a1529f6 )

 

myapp.AddEditCustomer.Customer2_render = function (element, contentItem) {
    // Write code here.
    thatTemplate = '
      
'; thatControl = new msls.ui.controls.ContentControl($(element)); thatControl.dataTemplate = thatTemplate; thatControl.data = contentItem.data; thatControl.render();

What do you know...it worked... I see Northwind contact name 'Maria Anders' with two-way binding!

It renders in the div.subControl a msls.ui.controls.TextBox with all the default props, bindings, etc.  Then it binds the text property to contentItem.data.ContactName. 

As you can see the .dataTemplate is declarative HTML. 
 

      
 
 
The ContentControl does the following:
  • Checks the template for elements having class="subControl" so that's required on everything that you want to bind declaratively
  • Checks for control attribute using the value to create & render a msls.ui.control[controlId] where controlId is the control type (Text, TextBox, ListView, etc.)
  • Looks for all attributes beginning with 'data-ls' using the value to set up binding (one-way binding, I think) relative to the .data property which is set to contentItem.data in this case. 

 

So the declarative binding here is between control.text and contentItem.data.ContactName ( or screen.Customer.ContactName - since the CustomControl is bound to screen.Customer.)      

 

 

I wish they'd expose more stuff, especially ui.controls.

Thanks to all communinty members for sharing your insights!

May 04
msls.js Exposed

The msls.js documentation By Soft Landing is such a great asset for the LS HTML Community.

Wouldn't it be nice to to see into internal msls objects at runtime?

Let's expose everthing with one line of code. 

1) Find this function 'addToNamespaceCore' in msls.js

2) add a line before the 'return' statement

msls_expose(path, ns);

That's all.  The function should look like this:

 

    function addToNamespaceCore(parent, path, members) {
        var standardMembers = processMembers(members),
            ns = WinJS.Namespace.defineWithParent(
                parent, path, standardMembers);
        defineExtendedMembers(ns, members);
        //CUSTOM: expose everything
        msls_expose(path, ns);
        return ns;
    }

 Now let's look at msls object in the watch pane. 

Interesting... among lots of other objects, we have all the msls.ui.controls exposed.

exposed1.JPG Hmm....I wonder if these could be used to render custom controls. (?)

Also check out msls.services.modelService.  This should be fun.

May 02
Declarative Binding

​​Html client: is it possible to use "declarative data binding" in an html custom control? 

Yes, and no.

Here's a function that enables declarative data binding.  

To try it out do this:

1) Create a LightSwitch HTML Project

2) Attach to Northwind OData Service

3) Create an Add\Edit screen on Customer entity

4) Add a Custom Control bound to screen.Customer

5) Click Edit Render Code and Paste this code in AddEditCustomer.lsml.js and F5

 

RenderBoundControl = function (template, element, contentItem, validate) {
    var thisControl = $(template);
    //get binding path
    var prop = thisControl.attr('data-ls-bind');

    //render
    thisControl.appendTo($(element));

    //add validate function
    thisControl.validate = validate;

    //validate and update contentItem on change
    thisControl.change(function () {
        if ($.isFunction(thisControl.validate)) {
            var isValid = thisControl.validate.call(this, $(this).val());
        }
        if (!isValid) {
            contentItem.validationResults = [
                new msls.ValidationResult(contentItem.value[prop],
                              "Invalid Entry.")];
        }
        else {    //bind thisControl change to contentItem
            if (contentItem.value[prop] != thisControl.val()) {
                contentItem.value[prop] = thisControl.val();
            }
        }
    });

    //bind contentItem change to thisControl
    contentItem.dataBind("value." + prop, function (newValue) { thisControl.val(newValue); });
    return thisControl;
};

myapp.AddEditCustomer.Customer_render = function (element, contentItem) {
    // Write code here.
    newControl = RenderBoundControl("", element, contentItem,
        function (newValue) { return newValue.length > 3 }
        );
};

The Custom Control should have two-way binding as well as validation.  Now you can add additional Custom Controls declaratively by calling the same function and including the 'data-ls-bind' attribute.  The last argument is a function that returns true if the data is valid.

 Plenty of room for improvement here, but it's a start. 

Mar 22
Please expose msls.sharepoint, please.

Interacting with SharePoint from javascript in HTML Client requires loading the SP CSOM scripts, referring to the AppWebUrl and HostWebUrl, and for HostWeb Calls - the cross-site request executor is needed.

All these operations are happening in msls.js and are nicely built into the msls.sharepoint object, however that object is contained in an anonymous self-executing function and it is not exposed via msls.expose().

Therefore the msls.sharepoint object cannot be used in custom code at runtime without altering msls.js

Like so:

    msls_sharepoint = {
        hostUrl: hostUrl,
        appWebUrl: appWebUrl,
        serverUrl: serverUrl,
        chromeBackgroundColor: chromeBackgroundColor,
        chromeLinkFontColor: chromeLinkFontColor,
        sharePointImagesUrl: sharePointImagesUrl,
        context: null,
        hostWeb: null,
        appWeb: null,
        executor: null,
        ready: promise.then,
        process: function () {
            var me = this, context = me.context, deferred;
            if (!queryPromise) {
                deferred = $.Deferred();
                queryPromise = deferred.promise();
                msls_dispatch(function () {
                    context.executeQueryAsync(
                        function () { deferred.resolve(); },
                        function (error) { deferred.reject(error); }
                    );
                    queryPromise = null;
                });
            }
            return queryPromise;
        },
        load: function () {
            var me = this, context = me.context, deferred = $.Deferred(),
                args = Array.prototype.slice.call(arguments, 0);
            args.forEach(function (o) {
                context.load(o);
            });
            return me.process();
        },
        openDocument: openDocument,
        getFileIconUrl: getFileIconUrl,
        getDefaultFileIcon: getDefaultFileIcon,
        getFileExtension: getFileExtension
    };
    //Custom: expose msls.sharepoint
    msls_expose('sharepoint',msls_sharepoint);

 

If it were exposed, then we wouldn't have to duplicate the above requirements when do scenarios such as:

Create or alter lists; Check for existence of an item; upload documents; check group membership; query items in order to... let's say, populate a choice list; anything else that requires a call to sharepoint.

CBA is THE RAD tool for sharepoint apps yet many LS devs haven't yet fully dug into the SP REST API.  It only makes sense to expose the msls.sharepoint REST executor so we don't have to deal with the requirements - we can just post a request using the familiar promise operation.

Another benefit is, if the object were exposed, we could extend it.  Devs could build all sorts of useful new reusable methods and append them to the msls.sharepoint object while avoiding any changes to msls.js. 

Buy the way, this goes for any of the msls objects and methods.  For example, anyone want to create controls in the msls.ui.controls namespace?  Can't - it's not exposed.

Discuss this topic here:

http://social.msdn.microsoft.com/Forums/vstudio/en-US/69b99f26-d59c-49c9-ad92-835cd8c57aea/please-expose-mslssharepoint-please?forum=lightswitch#1fe0528d-0842-45ed-a07d-40a8f832433a

Please vote for this suggestion on UserVoice here:

LightSwitch HTML Client (CBA) - Please expose msls.sharepoint object

Have a great day! 

Mar 18
HTML Client Upload Control

The March '14 Update added some nice features including a control to upload documents into a SharePoint Library.  It's really handy as long as your scenerio is exactly what it was desinged to handle and nothing less.  The control can be added to a LightSwitch screen containg a document library by choosing the createOrUploadDocument method for a command bar button.  Unfortuanately, the control only works if the document library is related to an Entity via a virtual relationship (Navagation Property) in LightSwitch.

This means:

1 )  it cannot be used if you simply throw a doc list on a browse screen and try to upload to the library and

2)  it cannot be used if your app has a SharePoint list as the parent entity in the relationship. 

The control checks for a navagation property then, if one is not found, it pukes "The Control cannot be used in this context'  error message and quits.

There are two ways to fix this:

1) Prevent the control from requiring a relationship and/or

2) Force the relationship to exist when Sharepoint List is the parent entity

Neither is 'supported' by MSFT.  To prevent the control from requiring a relationship you have to alter msls-2.5.0.js which I don't prefer, but I'll show you here:

        function setDocumentRelationship(control, itemId) {
        var deferred = $.Deferred();

        control._entitySet.filter("Id eq " + itemId)
            .merge(msls.MergeOption.unchangedOnly)
            .execute()
            .then(function onDocumentEntityRequestSuccess(response) {
                var documentEntity = response.results[0],
                    contentItem = control.data,
                    visualCollection = contentItem.value,
                    loader = visualCollection._loader,
                    collectionProperty,
                    data,
                    propertyValue,
                    propertyName;
                //custom: removed assignment of 4 vars above
                //custom: added if block with assignments below
                if (loader && loader._collectionProperty) {
                    collectionProperty = loader._collectionProperty,
                    data = collectionProperty._entry.data,
                    propertyValue = collectionProperty.owner,
                    propertyName = data.toPropertyName;
                }
                //custom: 'propertyName &&' added to condition below
                if (propertyName && documentEntity[propertyName] !== propertyValue) {
                    documentEntity[propertyName] = propertyValue;

                    window.msls.application.applyChanges()
                        .then(deferred.resolve, deferred.reject);
                } else {
                    visualCollection.refresh()
                        .then(deferred.resolve, deferred.reject);
                }
            }, deferred.reject);

        return deferred.promise();
    }

 Also edit this function:

      function _attachViewCore(templateData) {
        var me = this,
            view = me.getView(),
            entitySetModel,
            docLibNameAttribute,
            contentItem = me.data,
            valueModel = contentItem.valueModel,
            entityTypeModel,
            application, entityType, dataWorkspace;

        me._loaded = false;
        me._loadingElement = msls_getTemplateItem(view, templateData.loadingElementPath);
        me._menuContainerElement = msls_getTemplateItem(view, templateData.menuContainerElementPath);
        me._createDefaultListElement = msls_getTemplateItem(view, templateData.createDefaultListElementPath);
        me._titleContainerElement = msls_getTemplateItem(view, templateData.titleContainerElementPath);
        showSpinner(me);
        me._menuItemsRing = [];
        me._contentTypes = [];
        me._lastPopupPosition = null;

        //custom: below block remmed
        //if (!valueModel.query || !valueModel.query.source.links) {
        //    showError(me, msls_getResourceString("createOrUploadDocument_noNavigationPropertyError"));
        //    return;
        //}

        if (!msls_sharepoint) {
            showError(me, msls_getResourceString("createOrUploadDocument_siteError"));
            return;
        }

        msls_sharepoint.ready(function onSharePointLibrariesReady() {
            if (!msls_sharepoint.hostWeb) {
                showError(me, msls_getResourceString("createOrUploadDocument_siteError"));
                return;
            }

            me._canCreateDocuments = isCreateDocumentAvailable();

            application = window.msls.application;
            entityTypeModel = valueModel.elementType;
            entityType = application[entityTypeModel.name];
            dataWorkspace = application.activeDataWorkspace;
            me._entitySet = msls_EntitySet_getEntitySetForEntityType(dataWorkspace, entityType);
            entitySetModel = me._entitySet.getModel();
            docLibNameAttribute = msls_getAttribute(entitySetModel, msls_sharePointListModelAttribute);

            if (!docLibNameAttribute) {
                showError(me, msls_getResourceString("createOrUploadDocument_entityChangeError"));
                return;
            }
            me._docLibName = docLibNameAttribute.title;

            if (!me._canCreateDocuments) {
                me._createDefaultListElement.attr("style", "display: none;");
                me._titleContainerElement.attr("style", "display: none;");
            } else {
                attachCreateMenuItemsToView(me);
            }

            getDocumentLibraryInfo(me, true)
                .then(function addAllMenuItems() {
                    attachControlToView(me, templateData);

                    hideSpinner(me);
                    me._loaded = true;
                    repositionControl(me);
                }, function onGetDocumentLibraryInfoError() {
                    showError(me, msls_getResourceString("createOrUploadDocument_getListError"));
                })
                .always(function () {
                    msls_notify(loadedNotification, me);
                });
        }, function onSharePointLibrariesError() {
            showError(me, msls_getResourceString("createOrUploadDocument_siteError"));
        });
    }
   

To force the relationship to exist you can edit the entity .lsml file for the doc library like this:

 Related Sharepoint Lists Missing Navigation Properties - BUG?

More discussion here:

 Integrating document libraries in March 2014 with SP list as parent entity

Mar 12
Monkey Patched LightSwitch Screen Collection Queries

This code overrides the queries of all collections on the screen to inject a filter and sort. The solution combines a trick to override the default query of a screen collection, iterating over screen collections and introduces a Monkey Patch to modify the runtime code without altering the original source.

 

myapp.BrowseDocsByContact.created = function (screen) {
    // Write code here.
    //TO DO: test another way to get collections with less code 
    var model, collectionProperties, collectionName, property;
    model = screen.details.getModel();
    collectionProperties = msls.iterate(model.properties)
                    .where(function (p) {
                        return p.propertyType.kind == "CollectionView";
                    })
                    .array;
    collectionProperties.forEach(function (collectionValue, index) {
        collectionName = collectionProperties[index].name;
        property = screen.details.properties[collectionName];
        query = property._entry.simpleDescriptor.createQuery;
        //only overide once - have we done this already?
        if (!query.old) {
            //save the old query
            property._entry.simpleDescriptor.createQuery.old = query;
            //override the query
            property._entry.simpleDescriptor.createQuery =
                //TO DO: can we make these argument generic
                function (ContactId, filterString, sortString) {
                    //if these params don't exist in the query or both have no value
                    if (!filterString && !sortString) {
                        //do the default behavior
                        return query.old.apply(this, arguments);
                    } else {
                        // append filter and orderBy methods
                        return query.old.apply(this, arguments)
                            .filter(filterString)
                            .orderBy(sortString);
                    }
                };
        }
    });
};
Mar 11
LightSwitch HTML Client Collection Query Override

Queries in LightSwitch are implemented as functions in the OData DataService. For this reason overriding the .createQuery method on a screen collection is not as simple as seen in my previous post here:

 LightSwitch HTML Client Filter Hack

It is possible if you find the generated code in data.js and append the .filter and .orderBy methods to the returned DataServiceQuery object.

 

 myapp.BrowseCities.created = function (screen) {
    // Write code here.
    screen.details.properties["Cities"]._entry.simpleDescriptor.createQuery =
        function (filterString, sortString) {
            return new $DataServiceQuery({ _entitySet: this.Cities },
                                    lightSwitchApplication.rootUri + "/ApplicationData.svc" + "/CitiesByState()",
                                    {
                                        StateId: $toODataString(StateId, "Int32?"),
                                        filterString: $toODataString(filterString, "String?"),
                                        sortString: $toODataString(sortString, "String?")
                                    }).filter(filterString).orderBy(sortString);

        };
};

 

Mar 04
SharePoint Forms and LightSwitch

 

Couldn't make the SharePoint Conference, but I was a lurker in the session Update in InfoPath and SharePoint Forms thanks to a twitter feed by @jesscollicott.

The big take aways are:

  • The current roadmap has a focus on Information Workers and Developers are not discussed
  • Much of it is yet to be determined
  • MSFT want your feedback here: http://officeforms.uservoice.com

     

It was no surprise that Access took the prize for winner in category: 'App Forms Over Relational Data', but I must say it was a bit of a letdown.

Here are a couple slides from the session deck.

Having been an Access dev for over 16 years, I feels strange it's a letdown. I suppose the fact that all those years doing RAD over relational data while feeling like a second class citizen to two other classes of MSFT stakeholders drove me away from Access toward LightSwitch. Who are these stakeholders? 'Information Workers' (read: MS Office licensees) and 'Real Developers' (read: Visual Studio licensees). So I've moved into the Visual Studio crowd and learned a lot about a great RAD tool called LightSwitch which has been exciting.

Fast forward a couple years and here we are, Access gets first billing on SharePoint Forms over relational data and LightSwitch – nothing. I am happy for Access, but remain disappointed in how product roadmaps are envisioned at MSFT - everything is framed in terms of IW vs. DEV.

What now for LightSwitch? Let's hope for an upsize path from Access Web Apps to LightSwitch Cloud Business Apps.

You can make your voice heard here:

Roadmap needs: Upsize Wizard from Access Web App to LightSwitch

Collaborate with the LightSwitch team and enhance both products - LightSwitch is the right solution for vNext

Browser design basic --> Visual Studio LightSwitch advanced

Feb 27
Iterate over Screen Collections

This post is a work in progress …

    var model, collectionProperties, collectionName, property;
    model = screen.details.getModel();
    collectionProperties = msls.iterate(model.properties)
                    .where(function (p) {
                        return p.propertyType.kind == "CollectionView";
                    })
                    .array;
    collectionProperties.forEach(function (collectionValue, index) {
        collectionName = collectionProperties[index].name;
        property = screen.details.properties[collectionName];
        property._entry.simpleDescriptor.createQuery =
            function (filterString, sortString) {
                return this.dataWorkspace.ApplicationData[collectionName]
                    .filter(filterString)
                    .orderBy(sortString);
            };
    });
};

 

 
myapp.BrowseDocsByContact.created = function (screen) {
    // Write code here.
    var model, collectionProperties, collectionName, property;
    model = screen.details.getModel();
    collectionProperties = msls.iterate(model.properties)
                    .where(function (p) {
                        return p.propertyType.kind == "CollectionView";
                    })
                    .array;
    collectionProperties.forEach(function (collectionValue, index) {
        collectionName = collectionProperties[index].name;
        property = screen.details.properties[collectionName];
        query = property._entry.simpleDescriptor.createQuery;
        if (!query.old) {
            property._entry.simpleDescriptor.createQuery.old = query;
            property._entry.simpleDescriptor.createQuery =
                function (ContactId, filterString, sortString) {
                    if (!filterString && !sortString) {
                        //do the default behavior
                        return query.old.apply(this, arguments);
                    } else {
                        // append filter and orderBy methods
                        return query.old.apply(this, arguments)
                            .filter(filterString)
                            .orderBy(sortString);
                    }
                };
        }
    });
};
1 - 10Next