Skip to content

Instantly share code, notes, and snippets.

@jacobslusser
Last active December 22, 2017 00:52
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jacobslusser/2d9210606a8ab64f3a4df7747ee40404 to your computer and use it in GitHub Desktop.
Save jacobslusser/2d9210606a8ab64f3a4df7747ee40404 to your computer and use it in GitHub Desktop.
A RequireJS plugin for loading and registering Knockout components/templates
// The component is required using the 'component!' plugin we created. This will load
// 'page1.html' and 'page1.js' and return 'page1.js' to us.
define(['knockout', 'component!page1'], function (ko, Page1) {
'use strict';
function App() {
var args = { name: 'Jacob' };
// Example of using the component binding with the automatically registered component
this.sampleComponent = ko.observable({
name: 'page1',
params: args
});
// Templates can also be used and are probably preferable because we have easy access
// to the view model if we want to programmatically call anything on the view model.
// i.e. sampleTemplate().data.yourFunction()
this.sampleTemplate = ko.observable({
name: 'page1',
data: new Page1(args)
});
this.init = function init() {
ko.applyBindings(this);
};
}
return new App();
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Knockout RequireJS Plugin</title>
</head>
<body>
<div data-bind="component: sampleComponent"></div>
<div data-bind="template: sampleTemplate"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.2/require.js" data-main="main.js" async></script>
</body>
</html>
(function () {
'use strict';
// A simple RequireJS plugin that will load a view and view model based on naming convention.
// Once loaded the view is added to the DOM so it can be used with KO templates and the view
// and view model are registered as a KO component.
define('component', ['jquery', 'knockout'], function ($, ko) {
return {
load: function (name, parentRequire, onload, config) {
parentRequire([name, 'text!' + name + '.html'], function (viewModel, view) {
// Add the view to the DOM
var template = '<script type="text/template" id="' + name + '">' + view + '</script>';
$('body').append(template);
// Register as a KO component
ko.components.register(name, {
viewModel: viewModel,
template: { element: name },
synchronous: true
});
onload(viewModel);
}, function (err) {
onload.error(err);
});
}
};
});
// Standard RequireJS stuff...
requirejs.config({
paths: {
'jquery': 'https://code.jquery.com/jquery-1.12.4',
'knockout': 'https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.1/knockout-debug',
'text': 'https://cdnjs.cloudflare.com/ajax/libs/require-text/2.0.12/text'
}
});
// Start the application
require(['app'], function (app) {
app.init();
});
}());
<div>
<h1>Welcome: <span data-bind="text: userName"></span></h1>
</div>
define(['knockout'], function (ko) {
'use strict';
function Page1(params) {
this.userName = ko.observable(params.name);
}
return Page1;
});
@jacobslusser
Copy link
Author

The 'component' plugin in the main.js file is the meat and potatoes, while app.js is an example of how to use it, and the rest of the files are just here for completeness.

Using a plugin to load/register components/templates has the following benefits over the recommend AMD module pattern in the Knockout documentation:

  1. Rather than require 'page1.js' which has a dependency on 'text!page1.html', simply require 'component!page1.js'. It's less to write and has better performance because both the view and view model will be requested in parallel rather than in serial.
  2. The plugin will automatically add the view (template) to the DOM and register it as a KO component before you ever even get your hands on it. This allows the synchronous = true flag to be used and avoid most of the problems of not knowing when a component has finished updating the DOM. See knockout/knockout#1475, specifically the workaround suggested by @jods4 (here).
  3. Also supports template scenarios -- which I'm beginning to feel are superior to components. The module returned from 'component!page1.js' is the Page1 constructor function which you can new-up and use with the template binding. See an example of that in app.js which makes it dead simple to call functions on your view model, i.e. sampleTemplate().data.yourFunction().

NOTE: I've used jQuery when adding the view to the DOM to simplify the example but that is not strictly necessary if you would prefer to use just vanilla JS.

@jacobslusser
Copy link
Author

NOTE: I've offer additional explanations and how this approach solves several issues in the following knockout/knockout threads:

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