- Build a Trello-like board in MDriven Turnkey
You can build a board that displays cards in lists, moves a card between lists, and opens a modal editor for a card; this guide is for MDriven Turnkey developers creating a custom AngularJS view override.
A board is a custom presentation of your domain data. In this example, a Board contains BoardLists, and each BoardList contains BoardCards. The custom client code handles pointer interaction, while MDriven actions on the server perform the model changes.
- Example: A project board can have the lists
To do,In progress, andDone. Each card has aNameandText. DraggingWrite release notesfromTo dotoIn progresschanges the card's associated BoardList.
For a ready-to-apply sample model, see Documentation:Example Gist. For general Turnkey documentation, see Documentation:Turnkey.
What you will create
The implementation has four parts:
| Part | Purpose | Example |
|---|---|---|
| Domain model | Stores boards, lists, and cards. | A BoardCard belongs to one BoardList. |
| ViewModels | Expose the board data and editing operations to Turnkey. | DemoBoard exposes BoardLists, and each list exposes BoardCards.
|
| View override | Replaces the standard Turnkey presentation with board markup. | BoardDemo.cshtml renders one table column per BoardList.
|
| AngularJS directive | Adds drag, drop, and double-click behavior. | ph-card tracks a drag and triggers a server-side move action.
|
Model the board data
Create the smallest domain model that represents a board:
1. Create a Board class.
2. Create a BoardList class and associate it with Board.
3. Create a BoardCard class and associate it with BoardList.
4. Add Name and Text attributes to BoardCard.
The video demonstration uses this minimum structure: a board has lists, each list has cards, and each card has a name and text.
- Example data
- - Board:
Release planning - - BoardList:
To do - - BoardCard: Name =
Write release notes; Text =Summarize changes for version 1.0.
The move operation changes the BoardCard-to-BoardList association. It does not need to recreate the card or copy its text.
Create the ViewModels and actions
Create a ViewModel named DemoBoard that presents:
- the board name or presentation on root
- the board's lists as root.BoardLists
- each list's name as boardlist.Name
- each list's cards as boardlist.BoardCards
- each card's name and text as card.Name and card.Text
Also create a ViewModel for editing one BoardCard. The board's double-click behavior opens this editor as a modal view.
Add server-side actions
The client should set ViewModel attributes that identify the card to move and the list to move it to, then execute a move action. The move action assigns the card's BoardList association to the selected target list.
The demonstrated implementation also has an action that opens the current card in the card-editing ViewModel as a modal view.
| User interaction | Server-side responsibility |
|---|---|
| Drop a card on a list | Assign the card's BoardList to the target BoardList. |
| Double-click a card | Set the card as current and execute an action that opens the edit-card ViewModel as a modal view. |
| Save in the editor | Persist the changed card name or text and return the updated presentation. |
Keep the domain mutation in the action. The client identifies the card and destination, but the server updates the model and returns the updated screen state.
Set up a local Turnkey development site
A local Turnkey site lets you edit overrides and refresh the browser to inspect the result. You can point the local web application at the MDrivenServer of a cloud Turnkey application when you need to use cloud data during development.
Configure the MDrivenServer override
1. Download and import the MDriven Turnkey site into local IIS, or run it with IIS Express.
2. Open the web site in Visual Studio.
3. Configure SSL for the local host name or address that you use, and enable SSL for the site.
4. In the web application's App_Data folder, create MDrivenServerOverride.xml.
5. Set MDrivenServerPWD to the MDrivenServer password and set the element value to the cloud MDrivenServer URL.
<?xml version="1.0" encoding="utf-8"?>
<root>
<MDrivenServerOverride MDrivenServerPWD="----yoursecretpwd---">https://YourMDrivenTurnkeyApp.azurewebsites.net/__MDrivenServer</MDrivenServerOverride>
</root>
Normally, MDrivenServer is a sub-application of the Turnkey application in __MDrivenServer (two underscore characters). With this override, the locally running Turnkey web application uses the MDrivenServer at the specified cloud URL instead.
- Gotcha: Treat
MDrivenServerPWDas a secret. Do not commit a real password to source control.
Register the board script
Put custom scripts in the Turnkey web application's EXT_Scripts folder.
Turnkey includes files with - NotInEffect in the name. To activate one of these extension points, copy the file and remove - NotInEffect from the copy's name. Read the copied file for its own instructions.
To load the board script application-wide:
1. Locate AppWideAngularScriptIncludes â NotInEffect.html.
2. Copy it and remove - NotInEffect from the new file name.
3. Add the Board script include to the active file.
<script src="/EXT_Scripts/Board.js"></script>|
Turnkey will then look for Board.js in EXT_Scripts.
Add the view override
Create an override named for the ViewModel presentation. For the DemoBoard ViewModel, the demonstrated override file is BoardDemo.cshtml.
The copy command in the original implementation places this file in:
Views\EXT_OverridePages\
Use the override location used by your local Turnkey web application. The important point is that BoardDemo.cshtml replaces the standard presentation for this view.
Board markup
The following markup renders each BoardList as a column and each BoardCard as a card. It also adds directive markers that Board.js uses for interaction.
@{ Layout = null; }
<h3>{{root._ViewModelPresentation}}</h3>
<style>
.boardlist {
vertical-align: top;
}
.boardheader {
font-weight: bold;
font-size: 1.20rem;
background-color: seagreen;
text-align: center;
color: white;
}
.card {
vertical-align: top;
background-color: mediumpurple;
margin: 2px;
border-radius: 12px;
border: 2px solid purple;
padding: 6px;
}
.cardname {
font-weight: bold;
}
.cardtext {
font-size: 0.90rem;
word-wrap: break-word;
}
.rotateWhileMove {
-webkit-transform: rotate(7deg);
-moz-transform: rotate(7deg);
-ms-transform: rotate(7deg);
-o-transform: rotate(7deg);
z-index: 1000;
}
.myHorizontalbut {
height: 24px;
width: 90px;
position: relative;
padding: 2px;
display: inline-block;
margin: 2px;
}
</style>
<div id="theDivForTheBoard" style="height:300px;" ph-board vmclassid="{{root.VMClassId}}">
<table border="1" align="center" style="height:100%; width:100%;">
<tr>
<td ng-repeat="boardlist in root.BoardLists" height="20px" width="200px">
<div class="boardheader">{{boardlist.Name}}</div>
</td>
</tr>
<tr>
<td ng-repeat="boardlist in root.BoardLists" class="boardlist" ph-boardlist vmclassid="{{boardlist.VMClassId}}">
<div ng-repeat="card in boardlist.BoardCards" class="card" ph-card>
<div class="cardname">{{card.Name}}</div>
<div class="cardtext">{{card.Text}}</div>
</div>
</td>
</tr>
</table>
</div>
<button ng-repeat="oneaction in root.VM.StateActions() | orderBy:'+SortKey'"
ng-class="oneaction.Class"
ng-click="oneaction.Execute(); hidemenu();"
ng-disabled="!oneaction.Enable"
class="myHorizontalbut">
{{oneaction.Presentation}}
</button>
<img id="loadingAnimation"
src="/Content/loadingAnimation.gif"
scroll-position="scroll"
style="margin-top: 0px;"
ng-show="root.VM.Loading()"
class="ng-hide">How the bindings work
AngularJS ng-repeat repeats markup for each item in a collection.
| Binding | Result |
|---|---|
ng-repeat="boardlist in root.BoardLists"
|
Creates one board column for every exposed list. |
Template:Boardlist.Name
|
Shows the list name in the column header. |
ng-repeat="card in boardlist.BoardCards"
|
Creates one card element for every card in that list. |
Template:Card.Name and Template:Card.Text
|
Show the card content. |
vmclassid="Template:...VMClassId"
|
Supplies the ViewModel class identifier used by the directive behavior. |
- Example: If you add a fourth BoardList in the standard form and save it,
root.BoardListscontains the new list. Theng-repeatbinding creates a fourth board column when Turnkey updates the view.
Implement drag, drop, and double-click behavior
Board.js installs AngularJS directives for the board, lists, and cards. The card directive sets the card element's vmclassid, makes the element relatively positioned, and listens for pointer events.
The original implementation uses TypeScript as the source and generates Board.js from it. Keep the TypeScript source in your development project and deploy the generated JavaScript to EXT_Scripts.
Required interaction flow
Implement the following flow in your directives:
1. On card mouse down, start tracking the pointer position.
2. While the pointer moves, move the card element visually and apply the rotateWhileMove class.
3. On mouse up, inspect the board-list elements to find whether the drop point is inside a list.
4. If the pointer is inside a list, identify the dragged card and target list.
5. Set the corresponding ViewModel attributes for the move action.
6. Execute the move action.
7. Let MDrivenServer update the BoardCard-to-BoardList association and return the refreshed board.
8. On a double-click, set the selected card as current and execute the action that opens the modal card editor.
The client-side code should not duplicate the association-update logic. Its role is limited to detecting the interaction and invoking the appropriate ViewModel action.
- Example: When the user drops
Write release notesinside theIn progresscolumn, the directive sets the move target card and target list, then executes the move action. The action assignsWrite release notestoIn progress; Turnkey then receives the updated board state.
Keep custom files separate from the Turnkey web project
You can develop the override and TypeScript in a separate Visual Studio project, then copy the generated files to the local Turnkey site after a build. This keeps custom control code separate and suitable for its own source-control repository.
The demonstrated post-build commands copy the override and scripts to the running web application:
xcopy /Y "$(ProjectDir)BoardDemo.cshtml" "C:\CapableObjectsWush\source\StreamingApp\WebApplication2\Views\EXT_OverridePages\"
xcopy /E /Y /I "$(ProjectDir)EXT_Scripts" "C:\CapableObjectsWush\source\StreamingApp\WebApplication2\EXT_Scripts"
Replace the example destination paths with paths for your local Turnkey web application.
Development loop
1. Run the local Turnkey site from Visual Studio.
2. Open the board in a browser.
3. Edit BoardDemo.cshtml or the TypeScript source in the separate project.
4. Build the separate project so its post-build step copies the files.
5. Refresh the browser to inspect the changed override.
Inspect the data and standard presentation
Before writing an override, inspect the existing Turnkey output and the ViewModel data it exposes.
- Open <your-site>/MDriven/Development and select the ViewModel to inspect the standard UI documentation and available bindings.
- Append debug to the board page URL to inspect a JSON representation of the current ViewModel data.
- Use the browser debugger to inspect the live board object, lists, and cards.
These tools let you verify that the bindings in the override match the data exposed by your ViewModel.
- Example: If
root.BoardListsis absent from the debug output, the markup cannot create columns. Update theDemoBoardViewModel before troubleshooting the HTML.
Verify the board
Test the feature in this order:
1. Open a board with at least three lists and one card.
2. Confirm that every list appears as a column.
3. Add a list through the available form and save; confirm that a new column appears after the update.
4. Confirm that each card displays its Name and Text.
5. Drag a card to another list; confirm that the association changes and the card appears in the destination column.
6. Double-click a card; confirm that the modal card editor opens.
7. Edit the card name or text, save, and confirm that the board shows the updated value.
Limitations and implementation notes
- This example uses a table layout with a fixed board height of 300px; adjust the presentation only after the data bindings work.
- The board depends on the ViewModel names used in the markup: root.BoardLists, boardlist.BoardCards, Name, Text, and VMClassId.
- The complete directive source is not included in the available page content. The essential behavior is the interaction flow described above: identify card and target list on mouse up, set ViewModel targets, and execute the server-side action.
- Use the debug view and /MDriven/Development to confirm the actual properties and actions exposed by your implementation before changing the override.
Watch this video demonstrating the implementation:
<iframe src="https://www.youtube.com/embed/jfmASHr_IXM?rel=0&autoplay=0" frameborder="0" allowfullscreen></iframe>
Installation
The MDrivenServerOverride.xml file in App_Data configures your server connection: The file MDrivenServerOverride.xml is placed in your App_Data folder and has this format:

<?xml version="1.0" encoding="utf-8"?> <root> <MDrivenServerOverride MDrivenServerPWD="----yoursecretpwd---">https://YourMDrivenTurnkeyApp.azurewebsites.net/__MDrivenServer</MDrivenServerOverride> </root>
Adding a file like this allows you to move where MDriven Turnkey should go to find its MDriven Server. Normally, the MDriven Server is placed in a sub-application to the Turnkey-application in a folder named __MDrivenServer (double underscores). But this way, I can use a local Turnkey app â and point out the MDrivenServer for the cloud app.
Running the MDriven Turnkey in Visual Studio
Download and set up the Turnkey application in IIS Express or local IIS.

Download and import the MDriven Turnkey in your local IIS â or use IIS Express. Then, open the website in Visual Studio.
- Make sure you have the hang of the SSL certificate for your local host or whatever address you will be using.
- Enable SSL on site.
- Add the MDrivenServerOverride file to your App_Data.
On the website, you will find a folder named EXT_Scripts. This is where we want to keep our custom scripts.
Create a BoardDemo.cshtml view file in the EXT_View folder to override the standard UI.
We will also need some markup to replace the Turnkey standard UI. We put this override markup in the EXT_View Folder.

For things to work in my view that I will name BoardDemo, I will need to add a BoardDemo.cshtml in that folder.
TypeScript is used for the board's client-side functionality.

Configure a post-build event to copy the generated files to the Turnkey web application. In this project, I will set up a post-build event that copies the resulting BoardDemo.cshtml and Board.js files to the correct places in my Turnkey web app:
xcopy /Y â$(ProjectDir)BoardDemo.cshtmlâ âC:\CapableObjectsWush\source\StreamingApp\WebApplication2\Views\EXT_OverridePages\â
xcopy /E /Y /I â$(ProjectDir)EXT_Scriptsâ âC:\CapableObjectsWush\source\StreamingApp\WebApplication2\EXT_Scriptsâ
This way, you can separate your control development and have it in SVN or Git on its own.
The Model
The domain model defines the structure for the board application.
The BoardList entity manages individual lists on the board.
The BoardCard entity represents individual cards within lists.
The Markup Override
The custom markup produces the following user interface from the model data. You can look at the page source before overriding it â or use <mysite>/MDriven/Development to get documentation on how we do the standard UI:

The markup gives this UI on data from my model:

