This one is going to be about creating a knockout.js project on Codepen. Knockout.js has long fallen from the internet’s good graces but it’s still a very useful library that is much easier to grasp, write, and publish than React.js.
First, the Demo
Apparently, you can’t embed Codepen projects… so here’s the link: Knockout.js Template Project (codepen.io)
What I’ve done is setup an environment that uses a few different tools to make life easy. I’m using require.js, less.js, pug.js, and knockout.js. Codepen automatically handles the less and pug conversions for you.
Why less and pug?
Less is a preprocessor for CSS. One big reason to use a preprocessor is that you can separate your styles into separate files. For example, any theme-related color styles can be stored in a colors.less file and any typography related styles can be stored in a typography.less file. For larger projects, having this separation is extremely helpful. There are many other reasons to use a preprocessor but native CSS is starting to catch up and make them irrelevant. We aren’t there yet though.
Pug is also a preprocessor, but for HTML. I like it because the syntax is much quicker to write than HTML. See below:
Pug:
.card img(data-bind="attr:{src:img}") .name(data-bind="text:name") .email(data-bind="text:email") button(data-bind="click:updateData") Reload Data
Processed HTML:
<div class="card"><img data-bind="attr:{src:img}"/> <div class="name" data-bind="text:name"></div> <div class="email" data-bind="text:email"></div> </div> <button data-bind="click:updateData">Reload Data</button>
So, Knockout and Require?
Knockout.js is strictly a data-binding library. I set a variable in javascript and then I tell a DOM element to display the value of that variable. That element will update any time that variable updates. On its own, it can do a lot of neat things but when you couple it with require.js, it feels a lot more like working with true components.
Require.js is an asynchronous module loader. With some configuration, you can dynamically load javascript files when and if you need them. In terms of what it actually does, it appends script elements to the head element in your app. Using the require-text.js library, you can include HTML files in a similar fashion. Combined, these two features help modularize Knockout.js.
Setting up Require
To start, you need a config file, the require.js library, and an app.js file. Put them in the body of your index page like so (reminder, this is pug):
script(type="text/javascript",src="./js/config.js") script(type="text/javascript",data-main="app.js",src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js")
This is the config.js file:
var require = { baseUrl: '/js', paths: { ko: 'https://cdnjs.cloudflare.com/ajax/libs/knockout/3.5.0/knockout-min', text: 'https://cdnjs.cloudflare.com/ajax/libs/require-text/2.0.12/text.min' } };
It simply contains an object with a few properties. baseUrl is the relative path to all of your javascript files. The paths property tells us where to find the knockout and require-text script files when you need them. I’m using CDNs here but you can use local files as well.
app.js:
const app = {}; require(['ko'], function (ko) { app.model = function(){ } ko.components.register('home', { viewModel: { require: "js/home.js" }, template: { require: "text!../templates/home.html" } }); ko.applyBindings(); });
This file gets called in the require.js setup. This is the entry point into your application. I’ve registered a component called ‘home’. the viewModel property grabs the javascript file and the template property grabs the HTML template for that viewModel. Now, anytime I want the home component to show up, I call it like this:
In the index file:
div(data-bind="component: 'home'")
To recap, this is what the full index.pug file looks like (the fontawesome cdn is just something I like to use in pretty much everything):
doctype html html head link(rel='stylesheet',type="text/css", href='css/main.processed.css') script(src="https://kit.fontawesome.com/ec16337fc0.js", crossorigin="anonymous") body script(type="text/javascript",src="./js/config.js") script(type="text/javascript",data-main="app.js",src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js") div(data-bind="component: 'home'")
The Components
In the project, I’ve created a home component. It requires a little bit of funky require.js syntax, but the short version is that I’m creating an object that handles default values related to parameters, calling a data API, and assigning observables.
define(['ko'], function (ko) { app.home = function (params) { var self = this; self.defaults = new defaults(params); self.getData = ()=>{ fetch("https://randomuser.me/api/") .then(response => response.json()) .then(json => { console.log(json) self.img(json.results[0].picture.large); self.name(json.results[0].name.first+" "+json.results[0].name.last); self.email(json.results[0].email); }) }; self.img = self.defaults.img; self.name = self.defaults.name; self.email = self.defaults.email; self.updateData = ()=>{ self.getData(); } self.getData(); }; var defaults = function (params) { var self = this; if(params==null) params = {}; self.img = (params.img!=null)?params.img:ko.observable(""); self.name = (params.name!=null)?params.name:ko.observable("Empty"); self.email = (params.email!=null)?params.email:ko.observable("empty"); return self; } return app.home; });
Using the data-bind attribute, I can tell certain HTML elements what I want them to display. This is the home.pug file that is tied to the home knockout model:
.card img(data-bind="attr:{src:img}") .name(data-bind="text:name") .email(data-bind="text:email") button(data-bind="click:updateData") Reload Data
There is a less file involved as well, but I just made it to make the demo look pretty. You can use any CSS preprocessor or vanilla CSS.
Final Folder Structure
- css
- main.less
- imgs
- backgrounds.jpg
- js
- app.js
- config.js
- home.js
- templates
- home.pug
- index.pug
In the end, your folder layout can be whatever you want it to be, as long as it makes sense without thinking about it too much. I’ve seen hundreds of different ways of organizing projects across many different languages and frameworks. This is the one that speaks to me the most.
Good to know!
thank you very much
Good luck!