This tutorial shows how to build a Collaborative Dashboard, which enables you to visualize and collaborate on data from multiple systems at once. If you haven't done so already, you should check out the video.
The dashboard has three parts:
Let's take a more in-depth look of the individual parts.
The top of the dashboard has the collaboration area. This area automatically includes the contents of a dedicated account page, which is used to share notes, files, and comments. Access to the page is provided by a link at the top of the area. If the page doesn't exist yet, the link will create the missing account page and pre-populate it with default content.
The middle of the dashboard is used for displaying information from various data sources. For this example, we combine data from customer maintenance records, past orders, and the payment history. This gives us a fairly complete idea of the quality of this customer, as well as a reminder of any upcoming maintenance renewals. Additional information, such as the order details, are displayed on demand when needed.
The bottom of the dashboard uses multiple web-services to create an always up-to-date report of online information. Using LinkedIn, it displays a list of contacts at the account's company. Using Google, it shows a web search results for the company's name, recent news about the company, and results from various blogs that discuss it. It also shows the current stock price, if the company is public. And finally, it displays the most recent buzz about the company on Twitter.
The Collaborative Dashboard tutorial requires the following components:
The complete experience requires 2 pages, 6 templates, and 5 extensions! However, each component of the dashboard is fairly independent from the other. So you can pick and chose the ones that fit your purpose best and leave the rest.
These are the dependencies:
This is the main page of the dashboard with all the components. Lines 2-3 show an input box that allows free entry of an account name. This is an optional component that is not required, but is useful for test driving the dashboard. In line 4 we use the if attribute to only render the <div> element and its contents if an account was specified in the URI of the page. The rest of the page invokes the templates corresponding to each section passing in the account name.
<h1>Collaborative Dashboard</h1>
<p>Enter an account name.<br />
{{ dhtml.inputbox("Lookup account information:", __request.args.account, "Show", 24, "account", wiki.uri('Collaborative Dashboard')) }}</p>
<div if="__request.args.account" block="var account = __request.args.account">
<p> </p>
<p style="text-align: center;"><span style="font-size: x-large;"><u><strong>{{ account }}</strong></u></span></p>
<h3>Account Notes</h3>
{{ template.collaborate(account)}}
<h3>Maintenance History</h3>
{{ template.Maintenance(account) }}
<h3>Order History</h3>
{{ template.Orders(account) }}
<h3>Online Information</h3>
{{ template.Dashboard(account) }}
</div>
This page shows a tree of immediate child pages that have been created to collaborate on accounts. Usually, users will not see this page, but if they do, it provides them with quick access to existing account collaboration pages.
<h1>Account Notes</h1>
<p>{{ wiki.tree(page.path, 1) }}</p>
The following templates are required to enable the collective intelligence component of the dashboard.
This template checks if a collaboration page already exists under "Account Notes" using the passed in account name as child page title. If no page exists, the template shows a corresponding message with a link to create a new page. If the page already exists, it provides a short summary of the number of files attached on the page, the number of comments left, who edited the collaboration page, a link to view the full page, and finally includes the "Notes" section from the page directly in place. This last step enables users of the dashboard to see content from the collaboration page in place without having to click on a link and lose their current context.
<h1>Template:Collaborate</h1>
<div block="var account = $0 ?? $account; var path = wiki.appendpath('Account Notes', account); var exists = wiki.pageexists(path)">
<div block="var collab = wiki.getpage(path)" if="exists"><span style="color: rgb(128, 128, 128);">There are {{ #collab.files }} file(s) attached and {{ #collab.comments }} comment(s). Last edited on {{ date.format(collab.date, 'MMM d, yyyy') }} by {{ collab.author.name }}.</span> {{ web.link(collab.uri, 'View full page.') }}<br />
{{ wiki.page(collab.path, 'Notes') }}</div>
<div if="!exists">No collaboration page exists for this account.<br />
{{wiki.create('Click here to create one.', 'Account Notes', 'NewAccountPage', false, account, { account: account })}}</div>
</div>
This template is used to populate a new account page with default content. The top of the page has class="comment" attribute, which will make it only show in edit mode, but not when the page is viewed. Below it, the page shows a link back to the dashboard for the current account. Then it shows the "Notes" section, which is automatically included on the dashboard page. Finally, it shows a summary of the top contributors for the accounts page.
<h1>Template:NewAccountPage</h1>
<div class="comment">
<table width="100%" cellspacing="0" cellpadding="1" border="1">
<tbody>
<tr>
<td style="background-color: rgb(255, 255, 153);"><strong>Note:</strong> use this page to capture notes and files for the <em>{{edit: __request.args.account}}</em> account.</td>
</tr>
</tbody>
</table>
</div>
<p>{{edit: web.link(uri.build(wiki.uri('Collaborative Dashboard'), _, { account: __request.args.account }), 'Click here to return to the Collaborative Dashboard.') }}</p>
<h2>Notes</h2>
<p>(replace this text with your notes)</p>
<h2>Page Summary</h2>
<p>Page created on {{edit: date.format(date.now, 'MMM d, yyyy') }}.</p>
<p><strong>Most active contributors</strong><br />
{{ wiki.contributors(page.path) }}</p>
The following templates are required to enable the enterprise intelligence component of the dashboard.
NOTE: these templates are designed for a specific setup that will not suite your needs out of the box. They are only provided for educational purposes.
This template fetches information about maintenance contracts for the current account. In line 3, we use the if attribute to check if any items where found. If not, we emit the <p> element stating that there is no information to display. Otherwise, we emit the <table> where we iterate over each entry in the returned list of maintenance records.
<h1>Template:Maintenance</h1>
<div block="var items = list.sort(snaplogic.json('Finance/MaintenanceHistory/MaintenanceHistory', { ACCOUNT: $0 ?? $account }), _, true, 'date.compare($left[3], $right[3])')">
<p if="#items == 0">No maintenance information found for this account.</p>
<table width="100%" cellspacing="0" cellpadding="1" border="1" if="#items != 0">
<tbody>
<tr valign="top" foreach="var item in items">
<td width="70%"><strong>{{ item[0] /* product_name */ }}</strong> <span style="color: rgb(255, 0, 0);"><span if="item[9] == 'Expired'">(Expired)</span></span><span style="color: rgb(51, 153, 102);"><span if="item[9] == 'Renewal'">(Upcoming renewal)</span></span><br />
<span style="font-size: small;"><span style="color: rgb(128, 128, 128);">{{ item[1] /* description */ }}</span></span></td>
<td width="15%">Qty: {{ item[6] }}<br />
Price: {{ item[7] }}</td>
<td width="15%"><strong>Start: {{ item[2] }}<br />
End: {{ item[3] }}<br />
</strong></td>
</tr>
</tbody>
</table>
</div>
This template fetches information about past order for the current account. Similarly to Template:Maintenance, we first check if any orders where found. If so, we emit the <table> element with a row for each order. Furthermore, we emit an inner <table> that is initially hidden that contains the information for each item in the order.
To make the inner <table> appear and disappear dynamically, we use the dhtml.link and dhtml.toggle functions. The dhtml.link function allows us to embed a link that when clicked sends a message to another component. In this case, the other component is dhtml.toggle, which toggles the display state of any HTML element with a matching id attribute.
<h1>Template:Orders</h1>
<div block="var account = $0 ?? $account; var orders = list.sort(snaplogic.json('Finance/Orders/Orders', { ACCOUNT: account }), _, true, 'date.compare($left[1], $right[1])')">
{{ dhtml.toggle(@toggle, _, 'fast') }}
<p if="#orders == 0">No orders found for this account.</p>
<table width="100%" cellspacing="0" cellpadding="1" border="1" if="#orders != 0">
<tbody>
<tr foreach="var order in orders" block="var items = snaplogic.json('Finance/OrderDetail/OrderDetail', { ORDER: order[0] })">
<td>
<table width="100%" cellspacing="0" cellpadding="1" border="0">
<tbody>
<tr valign="top">
<td width="50%"><strong>ID: {{ order[0] }}</strong><br />
Invoice Date: {{ order[1] }}<br />
<br />
{{ dhtml.link('Order Details (' .. #items .. ( #items == 1 ? ' item)' : ' items)' ), { id: 'order-details-' .. __count }, @toggle) }}</td>
<td width="50%"><strong>Total Invoice: {{ order[4] }}</strong> <span if="order[5] == '0%'" style="color: rgb(128, 128, 128); font-size: small;">(no discount)</span><span if="order[5] != '0%'" style="font-size: small;" class="warning">({{ order[5] }} discount)</span><br />
<span class="{{ order[10] != 'Paid in Full' && date.diffdays(date.now, order[1]) > 75 ? 'warning' : nil }}">Invoice Status: {{ order[10] }}</span><br />
Paid: {{ order[8] }} <span if="order[7] != ''" style="font-size: small;"><span if="num.cast(order[7]) <= 75" style="color: rgb(128, 128, 128);">(after {{ order[7] }} days)</span><span if="num.cast(order[7]) > 75" class="warning">(after {{ order[7] }} days)</span></span><br />
Method: {{ order[9] != '' ? order[9] : 'N/A' }}</td>
</tr>
</tbody>
</table>
<table width="90%" cellspacing="0" cellpadding="1" border="1" align="right" id="{{ 'order-details-' .. __count }}" style="display: none;">
<tbody>
<tr valign="top" foreach="var item in items">
<td width="70%"><strong>{{ item[8] /* product_name */ }}</strong><br />
<span style="font-size: small;"><span style="color: rgb(128, 128, 128);">{{ item[10] /* description */ }}</span></span></td>
<td width="15%">Qty: {{ item[6] }}<br />
Price: {{ item[9] }}</td>
<td width="15%" style="text-align: right;"><strong>{{ item[7] /* price */ }}</strong></td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</div>
The following templates are required to enable the web intelligence component of the dashboard.
This template embeds widgets from LinkedIn, Yahoo!, and Google. Each widget is parametrized with the name of the current account.
<h1>Template:Dashboard</h1>
<div block="var account = $0 ?? $account">
<div style="padding: 0px 20px 20px; width: 320px; float: left;">
<h4>Contacts</h4>
<p>{{ linkedin.CompanyInsider{company: account, style: "noborder"} }}</p>
<h4>Company Stock</h4>
<p>{{ template.StockQuote{company: account} }}</p>
<h4>Twitter Updates</h4>
<p>{{ google.feed(uri.build("http://search.twitter.com/search.atom", _, { q: string.replace(account, " ", "") })) }}</p>
</div>
<div style="padding: 0px 20px 20px; width: 320px; float: left;">
<h4>Web & Blog Search</h4>
<p>{{ google.Search{search: account, options: { web: 'partial', news: 'partial', blogs: 'partial' }} }}</p>
</div>
</div>
This template is used to convert the account name into a stock symbol. To accomplish this, we piggyback of the auto-complete web-service that exists on the Yahoo! Finance portal. This service returns a JSON response that is a list of possible matche for given account name. Using a bit of DekiScript, we remove the extra data, then convert the JSON response to a DekiScript value and use it to dynamically embed the Yahoo! Finance widget with the best-guess stock symbol. If we can't find a matching stock symbol, we show a message indicating so.
<h1>Template:StockQuote</h1>
<p>{{ var msg = web.text(uri.build("http://d.yimg.com/autoc.finance.yahoo.com/autoc?callback=YAHOO.Finance.SymbolSuggest.ssCallback", _, { query: $company ?? 'Google' }));<br />
let msg = string.deserialize(string.substr(msg, 39, -1));<br />
if(#msg.resultset.result > 0) { yahoo.stockchart(msg.resultset.result[0].symbol) } else { 'No Stock Data Available...' }<br />
}}</p>
wiki.uri('Account Information Dashboard' ...
should be
wiki.uri('Collaborative Dashboard'
2. The instructions include a warning that the templates for Enterprise Intelligence are not really suitable for most people's applications, but there's no indication of what specifically makes these unsuitable--in other words, how would a user go about creating these templates for their own application.
One major problem I have with this application's documentation (what limited amount of it there is) is that is seems to assume the reader pretty much already knows how to do whatever needs to be done. But you're marketing the product as being extremely user friendly, so you should be getting folks who aren't always tech savvy.