Skip to content

Instantly share code, notes, and snippets.

@wuhaixing
Last active April 20, 2018 19:01
Show Gist options
  • Save wuhaixing/0cc7fcdaa1be3bc9f6d3 to your computer and use it in GitHub Desktop.
Save wuhaixing/0cc7fcdaa1be3bc9f6d3 to your computer and use it in GitHub Desktop.

#How does KeystoneJS render it's Admin UI

KeystoneJS is an open source framework for developing database-driven websites, applications and APIs in Node.js. It's built on Express and MongoDB.The easiest way to get started with KeystoneJS is to use Yeoman Generator.yo keystone will scaffold a new KeystoneJS project for you, and offer to set up blog, gallery, and enquiry (contact form) models + views.If you'd like to try the demo at first,here it is.

When I wrote this,keystone's stable version is 0.2.39,and 0.3.0 not published yet,so if you have a different version of KeystoneJS,the content maybe different.

##Generate a KeystoneJS Project

First up, you'll need Node.js >= 0.10.x and MongoDB >= 2.4.x installed. Then, install the Keystone generator:

$ npm install -g generator-keystone

With the generator installed, create an empty directory for your new KeystoneJS Project,I will call it nodecoffee, and run yo keystone in it:

$ mkdir nodecoffee
$ cd nodecoffee
$ yo keystone

The generator will ask you a few questions about which features to include, then prompt you for Cloudinary and Mandrill account details.

These accounts are optional, but Cloudinary is used to host the images for the blog and gallery templates. You can get a free account for each at:

When you've got your new project, check out the KeystoneJS Documentation to learn more about how to get started with KeystoneJS.

##Mysterious Admin UI

Besides the project's code yo generated for you as the following structure,Keystone gives you a beautiful, customisable Admin UI based on your models.To sign in to Keystone's Admin UI, run node web start your application and go to localhost:3000/keystone. Use the email and password you put in the update script, and you'll be redirected to Keystone's home page.

List 1: project structure

|--lib
|  Custom libraries and other code
|--models
|  Your application's database models
|--public
|  Static files (css, js, images, etc.) that are publicly available
|--routes
|  |--api
|  |  Your application's api controllers
|  |--views
|  |  Your application's view controllers
|  |--index.js
|  |  Initialises your application's routes and views
|  |--middleware.js
|  |  Custom middleware for your routes
|--templates
|  |--includes
|  |  Common .jade includes go in here
|  |--layouts
|  |  Base .jade layouts go in here
|  |--mixins
|  |  Common .jade mixins go in here
|  |--views
|  |  Your application's view templates
|--updates
|  Data population and migration scripts
|--package.json
|  Project configuration for npm
|--web.js
|  Main script that starts your application

Click Posts in the left Nav panel of home page,you will be redirected to the Posts Page.It's just like any list views you may see lots of times,the presentation maybe different,but if you draw boxes around every component (and subcomponent) in the page,and you will find they are all familiar components,such as Create button,Search box,Items table etc.There is also a Post Item page,when you click link on post title in list table,you will be redirected to it.

Post List View in Demo

But you cann't find anything about Admin UI in generated code,no templates,no routes,only models.So where is it come from?What happens when you access keystone's Admin UI?

##The common routes&templates of Admin UI

KeystoneJS is built on Express,so it's easy to find out how does KeystoneJS render the Admin UI from it's source code if you are familiar with Express(thanks all of the comment to the code).You can check out it's source code from KeystoneJS's Github repository:

git checkout https://github.com/keystonejs/keystone.git

First,there is a /lib/core/routes.js file,it has a routes(app) function called by function mount in /lib/core/mount.js file,adds bindings for the keystone routes,for example:

// List and Item Details Admin Routes
app.all('/keystone/:list/:page([0-9]{1,5})?', initList(true), require('../../routes/views/list'));
app.all('/keystone/:list/:item', initList(true), require('../../routes/views/item'));

The middleware initList(protected) in the route binding defined in this file too,it defines the req.list based on :list param in URL.In KeystoneJS, your data schema and models are controlled by Lists, and documents in your database are often called Items.To query data, you can use any of the mongoose query methods on the list.model.For example: to load the last 5 posts with the state published, populating the linked author, sorted by reverse published date:

List 2: Loading Posts

var keystone = require('keystone'),
    Post = keystone.list('Post');
 
Post.model.find()
    .where('state', 'published')
    .populate('author')
    .sort('-publishedAt')
    .limit(5)
    .exec(function(err, posts) {
        // do something with posts
    });

So before the handler process the request,KeystoneJS get the registed List instance based on the param of list's key in URL with list function,and put it into the request object for query data.And the binding lead us to the handler,or more specifically,Express middleware in file /routes/views/list.js,every list (and item) pages in Admin UI have same components but based on different models,so this is why KeystoneJS use one handler renders different views。The handler populate the pagination query of List based on the sort,search filters from req.query,execute it and pass the results to keystone.render in the callback,function render defined in file /lib/core/render.js,from the code and passed in arguments we can see the list template file is /templates/views/list.jade.

This lead us to another important component of KeystoneJS--Keystone Fields,and a Javascript library for building user interface--React.

Keystone Fields allow you to easily add rich, functional fields to your application's models. They are designed to describe not just the structure of your data, but also the intention of your data.In the /index.js file:

keystone.Field = require('./lib/field');
keystone.Field.Types = require('./lib/fieldTypes');

Field constructor defined in file /lib/field.js,all FieldTypes defined in directory /lib/fieldTypes inherit Field,and every FieldType has correspond React component files in /fields/types/。And then in /admin/src/ directory,there is a fields.js require all those fieldTypes React components and exposes them.And an app.js file exposes the Form components for Keystone Admin UI,and render it in /templates/views/item.jade:

List 3: Render form(from /templates/views/item.jade)

	script.
		Keystone.list = !{JSON.stringify(list.getOptions())};
		Keystone.item = !{JSON.stringify(list.getData(item))};
		Keystone.wysiwyg = { options: !{JSON.stringify(wysiwygOptions)} };
		
		App.Views.Item.renderForm(document.getElementById('item-form'), Keystone.list, Keystone.item);

But this app.js is not directly used,in the /templates/layout/base.jade:

script(src="/keystone/build/js/app.js")

The /keystone/build/js/app.js in is built from /admin/src/app.js by gulp,see gulpfile.js.

##Conclusion

So as the conclusion,KeystoneJS Admin UI's routes binding in the /lib/core/routes.js file,request handled by /routes/views/list.js and /routes/views/item.js.And then template files /templates/views/list.jade and /templates/views/item.jade render the list and item based on column's filedType.

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