Skip to content

Instantly share code, notes, and snippets.

@RainerAtSpirit
Created August 2, 2012 15:14
Show Gist options
  • Save RainerAtSpirit/3237820 to your computer and use it in GitHub Desktop.
Save RainerAtSpirit/3237820 to your computer and use it in GitHub Desktop.
KnockoutJS, RequireJS and the revealing module pattern
define (['jquery', 'knockout', 'LogonVM', 'TileVM', 'ListingVM', 'postbox', 'path', 'appData', 'kocBH' ],
function ( $, ko, LogonVM, TileVM, ListingVM, postbox ) {
"use strict";
var init = function () {
// Exposing ko as global
window.ko = window.ko || ko;
var $tileContainer = $ ('#tileVM');
// Configuring JayData to use current site's Odata service
app.context = new app.MetroStyleDataContext ({ name : 'oData', oDataServiceHost : '../_vti_bin/listdata.svc' });
// binding ko to the appropriate container
ko.applyBindings (new TileVM (), document.getElementById ('tileVM'));
ko.applyBindings (new LogonVM (), document.getElementById ('logonVM'));
ko.applyBindings (new ListingVM (), document.getElementById ('listingVM'));
// Client-side routes. Path exposed as global via shim configuration
function toggleTiles() {
var isVisible = $tileContainer.css ('display') === 'block';
isVisible ? $tileContainer.slideUp (300) : $tileContainer.slideDown (300);
}
Path.map ("#/view/:list").to (function () {
postbox.publish ('selectedList', this.params.list);
}).enter (toggleTiles);
Path.map ("(#)").to (function () {
postbox.publish ('selectedList', '');
}).enter (toggleTiles);
Path.listen ();
};
return {
init : init
}
});
define (['knockout', 'jquery', 'prettyDate', 'metrojs'], function ( ko, $ ) {
"use strict";
// See https://github.com/SteveSanderson/knockout/wiki/Bindings---class
ko.bindingHandlers['class'] = {
'update' : function ( element, valueAccessor ) {
if ( element['__ko__previousClassValue__'] ) {
ko.utils.toggleDomNodeCssClass (element, element['__ko__previousClassValue__'], false);
}
var value = ko.utils.unwrapObservable (valueAccessor ());
ko.utils.toggleDomNodeCssClass (element, value, true);
element['__ko__previousClassValue__'] = value;
}
};
// Based on ideas in http://stackoverflow.com/questions/10231347/knockout-afterrender-but-just-once
ko.bindingHandlers.updateTilesOnce = {
init : {
},
update : function ( element, valueAccessor, allBindingsAccessor, viewModel ) {
// This will be called once when the binding is first applied to an element,
// and again whenever the associated observable changes value.
// Update the DOM element based on the supplied values here.
var tileOptions = valueAccessor ().liveTile || {};
if ( $ (element).find ('.live-tile').length > 0 ) {
$ (element).find ('.live-tile')
.liveTile (tileOptions)
.click (function () {
$ (this).liveTile ('play');
})
.find ('.prettyDate').prettyDate ({ isUTC : true });
}
}
};
// No return as we only extend the knockout
});
define (['knockout', 'postbox', 'underscore', 'jd2ko', 'appData'], function ( ko, postbox ) {
"use strict";
return function () {
// ko observables
var allItems = ko.observableArray ([]),
itemDetail = ko.observableArray ([]),
selectedList = ko.observable ('').syncWith ('selectedList'),
userId = ko.observable ().subscribeTo ('userId'),
// defaults for OData requests as ko observables
includeArray = ko.observableArray (['CreatedBy', 'ModifiedBy']),
orderAsc = ko.observable (false),
orderBy = ko.observable ('Modified'),
take = ko.observable (10),
takeValues = ko.observableArray ([10, 20, 50]),
// functions and ko.compute
handleAfterRender,
showTable,
showDetails,
chooseMap,
getDetails;
// end of var declaration
handleAfterRender = function ( elements, data ) {
// Enabling prettyDate
$ (elements).find ('.prettyDate').prettyDate ({ isUTC : true });
// Enabling live tiles
var x = data;
};
showTable = ko.computed (function () {
return selectedList () !== '';
});
showDetails = ko.computed (function () {
return itemDetail ().length > 0;
});
chooseMap = function ( list ) {
var defaultMap, convertMap;
defaultMap = {
Id : 'it.Id',
Title : 'it.Title',
Modified : 'it.Modified',
Created : 'it.Created',
CreatedBy : 'it.CreatedBy.Name'
};
convertMap = function ( map ) {
var values = [];
_.each (map, function ( val, key ) {
values.push (key + ': ' + val)
});
return '{' + values.join (', ') + '}'
};
if ( app.context[list].elementType.memberDefinitions.getMember ('Title') && app.context[list].elementType.memberDefinitions.getMember ('Name') ) {
return convertMap (_.extend (defaultMap, {Title : 'it.Name'}));
}
else if ( app.context[list].elementType.memberDefinitions.getMember ('Title') ) {
return convertMap (defaultMap);
}
else if ( app.context[list].elementType.memberDefinitions.getMember ('URL') ) {
return convertMap (_.extend (defaultMap, {Title : 'it.URL'}));
}
else {
return convertMap (_.extend (defaultMap, {Title : 'it.ContentType'}));
}
};
postbox.subscribe ("selectedList", function ( newValue ) {
if ( app.configMap.userId === 'anonymous' ) {
return alert ('Make sure to log on.');
}
if ( newValue !== '' ) {
// Clean out existing itemDetail
itemDetail ([]);
var base = app.context[newValue],
myBase = _.extend ({}, base),
sortExp = 'it.' + orderBy ();
if ( orderAsc () ) {
_.extend (myBase, myBase.orderBy (sortExp));
}
else {
_.extend (myBase, myBase.orderByDescending (sortExp));
}
_.each (includeArray (), function ( inc ) {
_.extend (myBase, myBase.include (inc));
});
myBase.map (chooseMap (newValue))
.take (take ())
.toArray (allItems);
}
else {
// Clean out existing allItems
allItems ([]);
// Clean out existing itemDetail
itemDetail ([]);
}
});
getDetails = function ( currItem ) {
var currentList = selectedList ();
app.context[currentList]
.single (function ( item ) {
return item.Id == this.Id
},
{Id : currItem.Id},
function ( item ) {
var keyValue = [];
_.each (item.toJSON (), function ( val, key ) {
keyValue.push ({"key" : key, "val" : val});
});
itemDetail ([]);
ko.utils.arrayPushAll (itemDetail (), keyValue);
itemDetail.valueHasMutated ();
});
};
// Return public methods
return {
selectedList : selectedList,
take : take,
takeValues : takeValues,
allItems : allItems,
itemDetail : itemDetail,
handleAfterRender : handleAfterRender,
showTable : showTable,
showDetails : showDetails,
getDetails : getDetails
}
};
});
<div class="row" id="listingVM">
<div class="nine columns" style="display:none"
data-bind="visible: showTable()">
<div class="twelve columns">
<div class="two columns">
<a href="#" title="Go back"><img src="images/48/arrow_left.png" style="border: none"/></a>
</div>
<div class="ten columns">
<h3 data-bind="text: $root.selectedList"></h3>
</div>
</div>
<div class="twelve columns"
data-bind="foreach: allItems,
updateTilesOnce: {liveTile: { repeatCount : 0, delay : 0 }}" >
<div class="live-tile blue" data-bind="click: $root.getDetails" data-mode="flip">
<div>
<h3 data-bind="text: Title"></h3>
<span class="tile-title prettyDate"
data-bind=" text: Modified,
attr: {title: new Date(Modified).toISOString().substring(0,19) + 'Z'}"></span>
<span class="badge" data-bind="text: Id"></span>
</div>
<div>
<p>Here would be room for more information, but for this demo we simply show the infos on the
right.</p>
</div>
</div>
</div>
</div>
<div class="three columns" style="display:none"
data-bind="visible: showDetails">
<h3>Detail info</h3>
<ul class="disc" data-bind="foreach: { data: itemDetail}">
<li>
<span data-bind="text: key"></span>
<span class="secondary label" data-bind="text: val"></span>
</li>
</ul>
</div>
</div>
define (['knockout'], function ( ko ) {
"use strict";
return function () {
var userId = ko.observable (app.configMap.userId).publishOn ('userId'),
loginURL;
loginURL = ko.computed (function () {
return '../_layouts/Authenticate.aspx?Source=' + encodeURIComponent (location.pathname) + location.hash;
});
// Return public methods
return {
userId : userId,
loginURL : loginURL
}
}
});
<div id="logonVM">
<div data-bind="visible: userId() !== 'anonymous' " style="display: none;">
You're logged on as: <span class="success label" data-bind="text: userId"></span>
</div>
<div data-bind="visible: userId() === 'anonymous' " style="display: none;">
<a href="#" data-bind="attr: {href: loginURL}" class="button"> Sign
in</a>
with username: <span class="secondary label">ODataDemo</span> password: <span class="secondary label">OData!Demo</span>
</div>
</div>
<div class="row" id="tileVM">
<!--<h3 class="subheader">Debugging info:</h3>
<div class="debug" data-bind="text: ko.toJSON($root)"></div>-->
<div class="twelve columns tiles"
data-bind="foreach: TileData, updateTilesOnce: true">
<div class="live-tile" data-stops="100%" data-speed="750" data-delay="-1"
data-bind="class: color + ' ' + size,
attr: {'data-mode': mode ? mode : '', 'data-direction': direction} ,
click: $root.goToDetail">
<span class="tile-title" data-bind="text: title"></span>
<span class="badge" data-bind="text: count"></span>
<div>
<img class="micon" data-bind="attr: {src: icon}"/>
</div>
<div>
<h3 data-bind="text: backTitle"></h3>
<span class="prettyDate" data-bind="attr: {title: prettyDate}"></span>
</div>
</div>
</div>
</div>
define (['knockout', 'helper'], function ( ko, fn ) {
"use strict";
return function () {
// ko observables
var userId = ko.observable ().subscribeTo ('userId'),
selectedList = ko.observable ().subscribeTo ('selectedList'),
TileData = ko.observableArray ([]),
// functions
goToDetail;
// end of var declaration
goToDetail = function ( tile, event ) {
if ( userId () !== 'anonymous' ) {
selectedList (fn.cleanup (tile.title));
location.hash = '/view/' + selectedList ();
}
else {
alert (' Make sure to log on.');
}
};
// Bootstrap
TileData (app.tilesData.tiles.tile);
// Return public methods
return{
userId : userId,
TileData : TileData,
goToDetail : goToDetail
};
};
});
@kylos101
Copy link

kylos101 commented Dec 6, 2015

Hi, thank you very much for sharing this work.
I was having trouble exposing my viewModel in a side project, and bumped into this via a Google search.
It turns out I wasn't providing a public return object! Take care. :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment