Blog

Dec 04
HTML Client Computed Properties

It would be nice and fairly simple for MS to add Computed Properties to the HTML Client.  In fact if they'd just include them in the model then we could do it ourselves. 

Rather than hold our breath, let's make it happen.

Here's one way to make Computed Properties easy - and it requires zero change to msls.js.

1) Add an optional property to your entity.  I have Customer and added a property called 'CSZ' - required=false.

2) In the entity designer select HTML perspective and choose Write Code | Created.  This adds the entity.js client side script. (customer.lsml.js in my case)

3) Now write _Compute methods in your entity.js file (Customer.lsml.js for example) like so:

 

 

myapp.Customer.created = function (entity) {
    // Write code here.

};

myapp.Customer.CSZ_Compute = function (entity, result) {
    // Write code here.
    var Me = entity;
    result = Me.City + ', ' + Me.State + "  " + Me.Zip;
    return result;
};

 

Notice how similar this is to SL compute methods.

4) Create a new script called OdataReadPatch.js in your scripts folder and paste in the this code. Also add a reference to this script in your default.htm after all other script refs.

 

/*
This script patches the OData read method 
enabling us to inject operations before the results return to the LS runtime
*/
var origJsonReadFunc = OData.jsonHandler.read;
OData.jsonHandler.read = function (response, context) {
    var result = origJsonReadFunc.call(OData.jsonHandler, response, context);
    var data = response.data, results = data.results;
    if (results) {
        results.forEach(function (entity) {
            //do stuff to each entity here

            //call function to add Computed properties
            myapp.addComputedProperties.call(this, entity);

        });
    }

    return result;
};

myapp.addComputedProperties = function (entity) {

    //get entity name  ie: "LightSwitch.Customer" = "Customer"
    var entityType = entity.__metadata.type.split(".").pop();

    //get the entity class - this object contains all methods defined in Customer.lsml.js
    var entityClass = myapp[entityType]

    //build an array of property names from '_Compute' methods
    var properties = [];
    for (var p in entityClass) {
        if (typeof entityClass[p] === "function" && p.indexOf("_Compute") > 0) {
            prop = { name: p.split("_")[0], type: String };
            properties.push(prop);
        }
    };
    //console.log(properties);

    //add the computed prop to this entity by calling the _Compute method
    if (properties) {
        properties.forEach(function (entry) {
            var entryName = entry.name;
            var computeMethod = entityClass[entryName + "_Compute"];
            entity[entryName] = computeMethod.call(this, entity);
        });
    }
};

or download OdataReadPatch.js from here:

https://gist.github.com/joshbooker/ddb4b07683e32a068d5d

Done!

 

Now just add the property to a browse screen and it works.

After this is setup, adding new computed props is as simple as adding a _Compute methods.

The trick is the Odata patch which allows us to inject an operation to add the properties to each entity before the results are returned to LS runtime.

More info:

The idea was to make HTML Client computed properties nearly the same as SL.  My understanding is that the _compute methods in SL is on the client - or at least both client and server.  The missing part on the HTML client was until they added the entity_created method, there was no client side table code to mirror that in the .net client.

LS defines entities using the generated model file data.js which cannot be changed and computed properties are not included.  As a result, we typically use existing entry points to add them such as _render & _postRender.  These are specific to code in a single screen and for a single contentItem on that screen.  Thus there is a ton of code duplication for a computed prop that appears on many screens or many times on a single screen.  Also, to keep the computed property current, dataBind code is needed.  For a simple computation like 'CSZ' above, which references 3 other properties (City, State & Zip),  at least 3 dataBinds are needed.  Need to change a calculation? - Make sure to do it here and here and here and there and there, you get the picture.  With this solution, the calculated property is added to the data - no the screen, computation code is written in one place and dataBind is automatic.

How does it work?

This solution adds properties to the entityData returned from each Odata Read call.  With these props present, LS builds the entities and the properties are majically added to the Entity Details just as though they were in the model to begin with.  So LS runtime doesn't appear to 'know' they are any different that another field in the data.

The collection of computed properties to be added is derived from looking directly at the client side entity code (<entity>.lsml.js) and finding all methods ending in '_Compute'.  It is assumed that those methods will accept entityData and return the computed result string.  The name of the property is the portion of the methid name before _Compute ( ie: 'myapp.Customer.CSZ_Compute(entity)' adds a computed property called 'CSZ' to all Customer entities).  Therefore all you have to do to add a computed prop is add another _Compute method to your entity code.  The property is added even if you don't add one of the same name in the entity designer and is therefore available for _render and dataBind manually.

Optionally, If you do add a property by the same name to the entity designer, you can use it on screens and LS will render and bind the computed value on screens automatically - no _render/dataBind necessary.  That part was a shocker to me.

Because this computes the props on every OData call, the values are recomputed quite often.  There's barely a need for dependency changes listeners to auto-recompute like SL does.

To summarize,

  • You create these computed props in one step,
  • The computation exists in one and only one place. 
  • By optionally adding a prop to the model and views LS will do the dataBinding for you.
  • They re-compute with every Odata call
  • Zero change to msls.js.

I think this hook may be the ticket to crack the data layer for a lot of other benefits.

Have a greeat day!

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

1 - 10Next