Blog

Showing 10 post.

Front End Widgets - Part 5 - Password Protecting the Artificial Back End

Posted in KeystoneJS, JavaScript by Chris on November 13, 2015

Table of Contents:

 

The front end side of things is pretty straightforward. The code in part 3 can be copy and pasted into any of the *.hbs Handlebar template files in the templates/views/ directory. These are the front end templates that KeystoneJS serves up. For instance, templates/views/index.hbs is the template for the home page.

You could do the same thing with the Artificial Back End, but then anyone with the URL will be able to maliciously update your widgets. I haven't figured out a way to password protect the API yet, so hopefully someone in the KeystoneJS group will chime in with a good way to do that. However, you can at least protect the Artificial Back End.

As an aside, here is what I've tried to password protect the API, but it didn't work. I have tried to change this line in routes/index.js:

app.all('/api/frontendwidget/:id/update', keystone.middleware.api, routes.api.frontendwidget.update);

to this:

app.all('/api/frontendwidget/:id/update', middleware.requireUser, routes.api.frontendwidget.update);

which seems to be the obvious way to password protect the API, but it errors out. We can however, do the same thing to password protect the page. In order to do that, add this line to routes/index.js:

// Setup Route Bindings
exports = module.exports = function(app) {
...
app.get('/updatefrontend', middleware.requireUser, routes.views.updatefrontend); };

 

That will create a password protected route to our new page. Now, create the file routes/views/updatefrontend.js and paste this code into it:

var keystone = require('keystone');

exports = module.exports = function(req, res) {

        var view = new keystone.View(req, res);
        var locals = res.locals;

        // Set locals
        locals.section = 'updatefrontend';

        // Load the galleries by sortOrder
        view.query('frontendcollections', keystone.list('FrontendWidget').model.find().sort('sortOrder'));

        // Render the view
        view.render('updatefrontend');

};

 

Finally, paste the html from Part 4 into the file templates/views/updatefrontend.hbs to complete the process. This hbs file is the html that KeystoneJS will serve. The page can be accessed from http://<your ip>:3000/updatefrontend and the page will require that you be loggined in as an Admin user.

Read more...

Front End Widgets - Part 4 - Editing the Widget on an Artificial Back End

Posted in KeystoneJS, JavaScript by Chris on November 10, 2015

Table of Contents:

 

Updating widget data through the KeystoneJS API is very similar to retrieving it. Copy and paste the below code into an HTML file, update the server IP, and open it in a browser. It will generate a simple form that will allow you to update the URL of the first image in the first widget.

<!DOCTYPE html>

<!--
Note: You'll need the bootstrap css and js files and change the paths to them below.
-->

<html lang="en">
<head>
  <title>KeystoneJS API Example</title>

  <link href="css/bootstrap.min.css" media="all" rel="stylesheet" />
</head>
<body>


  <section>
    <div class='container'>
      <div class="row well well-lg">
        <div class="col-md-12">
          <p>Image from first widget:</p>
          <img id="image1" src="" alt="" />
          <br><br>
        </div>
      </div>
      
      <form action='' method="post" name='updateImgForm' id='updateImgForm' enctype='multipart/form-data'>
        <div class='form-group' >
          <div class='col-md-12'>
            <label for='widgetUrl'>URL:</label>
            <input class='form-control' type='text' id='widgetUrl' size="36">
          </div>
        </div>
        <div class='form-group' >
          <div class='col-md-4'>
              <button class="btn btn-default" type='button' id='oldUrlBtn' onclick='updateWidget()'>Update URL</button>
          </div>
          <div class='col-md-4'>
              <span id="checkMsg1">Click button to update widget URL.</span>
          </div>
          <div class='col-md-4'>

          </div>
        </div>
      </form>
      
    </div>
  </section>

  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
  <script src="js/bootstrap.min.js?body=1"></script>

  <script type="text/javascript">
    //Global Variables
    var serverIP = '<your ip here>';
    var JSONData;
    
    //First function that is called when the document finishes loading.
    $(document).ready( function() {
    
      //Call server to retrieve JSON Gallery data.
      $.getJSON('http://'+serverIP+':3000/api/frontendwidget/list', '', processJSON);
    });
    
    //Callback function executed when JSON data is returned.
    //Sorts the items in data.collections into different categories.
    function processJSON(data) {
      debugger;
      //Fill in the images with data from the KeystoneJS API
      $('#image1').attr('src', data.collections[0].url1);
      $('#image1').attr('alt', data.collections[0].alt1);
      $('#widgetUrl').val(data.collections[0].url1);
      
      JSONData = data; //Copy to global varible.
    }
    
    //This function is called when the 'Update URL' button is clicked.
    function updateWidget() {
      var collectionId = JSONData.collections[0]._id; //GUID of the first widget
      
      //Update the JSON data with the data from our form.
      JSONData.collections[0].url1 = $('#widgetUrl').val();
      
      //Send the JSON string to the server and log a copy on the console.
      console.log('JSON data sent: '+JSON.stringify(JSONData.collections[0])); //Used for debugging.
      $.getJSON('http://'+serverIP+':3000/api/frontendwidget/'+collectionId+'/update', JSONData.collections[0], validateChange);
    }
    
    //This function handles the AJAX callback from the server after a POST request has been sent to update the widget.
    //The purpose of this function is to validate that the data returned from the server matches the changes made.
    function validateChange(data) {
      
      //If the returned data matches the data in our form, then report a success.
      if( data.collection.url1 == $('#widgetUrl').val() ) {
        $('#checkMsg1').text('Widget successfully updated!');
      } else {
        $('#checkMsg1').text('Update failed!');
      }
    }
    
  </script>
  
</body>
</html>
 

 

Here's what the HTML page looks like:

To really study the code, I suggest you insert debugger statements at the top of each function and then run through the code line by line in Chrome Dev Tools. That way you can see the ways the API URL and other variables are getting manipulated.

The API is very picky about the format of the JSON, so it's best to simply manipulate the JSON returned by the server and then feed it back. If it doesn't like something, it won't complain, it simply won't update the widget. You can test if your changes were rejected by testing the returned JSON data against the manipulated JSON data. 

Read more...

Front End Widgets - Part 3 - Displaying the Widget on the Front End

Posted in JavaScript, KeystoneJS by Chris on November 08, 2015

Table of Contents:

 

The first step in displaying data on the front end is to open up Cross Origin Resouce-Sharing (CORS). Add the following lines to the file node_modules/keystone/lib/middleware/api.js

exports = module.exports = function(keystone) {
        return function initAPI(req, res, next) {

                //Add CORS
                res.header("Access-Control-Allow-Origin", "*");
                res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");


                res.apiResponse = function(data) {
...

 

There is a cors.js file in that directory that you could point the route to, but I had trouble in trying to execute it. In reality you only need to add the above two lines. Please note that there are security issues surrounding the concept of CORS. You should Google it and have a thorough understanding of the risks involved. 

Once again, you'll need to reboot KeystoneJS to make the changes go into affect.

 

Manipulating Front End Objects Through The API

Paste the following HTML into a file and open it in your browser. Note that you'll need to download the bootstrap css and js files to make it work correctly. This gives you an example using jQuery and the API to dynamically manipulate front end objects with data retrieved from the API.

 

<!DOCTYPE html>

<!--
Note: You'll need the bootstrap css and js files and change the paths to them below.
-->

<html lang="en">
<head>
  <title>KeystoneJS API Example</title>

  <link href="css/bootstrap.min.css" media="all" rel="stylesheet" />
</head>
<body>


  <section>
    <div class='container'>
      <div class="row well well-lg">
        <div class="col-md-12">
          <img id="image1" src="" alt="" width="600px" />
          <br><br>
          <img id="image2" src="" alt="" width="600px" />
          <br><br>
          <img id="image3" src="" alt="" width="600px" />
        </div>
      </div>
    </div>
  </section>

  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
  <script src="js/bootstrap.min.js?body=1"></script>

  <script type="text/javascript">
    //Global Variables
    var serverIP = '<your ip address>';
    
    //First function that is called when the document finishes loading.
    $(document).ready( function() {
    
      //Call server to retrieve JSON Gallery data.
      $.getJSON('http://'+serverIP+':3000/api/frontendwidget/list', '', processJSON);
    });
    
    //Callback function executed when JSON data is returned.
    //Sorts the items in data.collections into different categories.
    function processJSON(data) {
      //Fill in the images with data from the KeystoneJS API
      $('#image1').attr('src', data.collections[0].url1);
      $('#image1').attr('alt', data.collections[0].alt1);
      $('#image2').attr('src', data.collections[1].url1);
      $('#image2').attr('alt', data.collections[1].alt1);
      $('#image3').attr('src', data.collections[2].url1);
      $('#image3').attr('alt', data.collections[2].alt1);
    }
    
  </script>
  
</body>
</html>
 

Read more...

Front End Widgets - Part 2 - Opening the KeystoneJS API

Posted in KeystoneJS by Chris on November 06, 2015

Table of Contents:

 

This part of the tutorial is based on this GitHub Gist by Jed Watson. In it, he desribed how to open up an API to read and write KeystoneJS blog post data via an AJAX request. This post tweeks it slightly and instead opens up an API to our new front end widget.

Start by logging into the Admin UI of KeystoneJS and populate the database with three new widgets. In my case, I named them Widget 1, Widget 2, and Widget 3 and gave them an image URL and Alt tag description like so:

 

 

Add the following lines to routes/index.js. This handles the routing the API URL.

var routes = {
        views: importRoutes('./views'),
        api: importRoutes('./api')
};

// Setup Route Bindings
exports = module.exports = function(app) {
...
app.get('/api/frontendwidget/list', keystone.middleware.api, routes.api.frontendwidget.list); app.get('/api/frontendwidget/:id', keystone.middleware.api, routes.api.frontend
widget.get);
app.all('/api/frontendwidget/:id/update', keystone.middleware.api, routes.api.frontendwidget.update);

...
};

 

Now that the router is configured, you need create a view/handler for the API. Create the directory routes/api and create the following file routes/api/frontendwidget.js by pasting in the following code:

var async = require('async'),
keystone = require('keystone');

var ImgData = keystone.list('FrontendWidget');

/**
 * List Images
 */
exports.list = function(req, res) {
        ImgData.model.find(function(err, items) {

                if (err) return res.apiError('database error', err);

                res.apiResponse({
                        collections: items
                });

        });
}

/**
 * Get Image by ID
 */
exports.get = function(req, res) {

        ImgData.model.findById(req.params.id).exec(function(err, item) {

                if (err) return res.apiError('database error', err);
                if (!item) return res.apiError('not found');

                res.apiResponse({
                        collection: item
                });

        });
}


/**
 * Update Image by ID
 */
exports.update = function(req, res) {
        ImgData.model.findById(req.params.id).exec(function(err, item) {

                if (err) return res.apiError('database error', err);
                if (!item) return res.apiError('not found');

                var data = (req.method == 'POST') ? req.body : req.query;

                item.getUpdateHandler(req).process(data, function(err) {

                        if (err) return res.apiError('create error', err);

                        res.apiResponse({
                                collection: item
                        });

                });

        });
}

 

Restart KeystoneJS to make the changes go live. You can now 'list' the JSON data for the front end widgets in your browser by going to:

http://<your ip address>:3000/api/frontendwidget/list

 

 

Read more...

Front End Widgets - Part 1 - Creating the DB Model

Posted in JavaScript, KeystoneJS by Chris on November 05, 2015

After familiarizing myself with KeystoneJS I decided to use it as the CMS (content management system) of choice for my clients. This requires customizing a lot of features. After a couple false starts, I realized that the vast majority of the customization I needed to do could be accomplished by a generic front end 'widget'.

What I mean by a front end widget is a generic database model that will allow me store values for a couple images and paragraphs. I can then retrieve this wiget and display it on the front end. I realize this sounds archaic. Just bear with me, because if you are like me and need to customize KeystoneJS to create a CMS for your clients, you're going to want to see this. It's going to be a game changer for you, like it has been for me.

Here is how the tutorial breaks down:

Even if you don't like my idea of the wiget. This tutorial should provide a lot of value to developers aspiring to customize KeystoneJS for their own needs.

Note: I used Handlebars instead of the default of Jade as my template of choice. When using the yo keystone command during setup of KeystoneJS, it asks you which one you want to use. If you follow the tutorials on this blog, then make sure you choose Handlebars (hbs), otherwise they won't work.

Creating the Database Model

The nice thing about KeystoneJS is the simple format for creating new MongoDB database models. Create a file called FrontendWidget.js in the models/ directory and paste this code into it:

var keystone = require('keystone');
var Types = keystone.Field.Types;

/**
 * Frontend Widget Model
 * ===========
 * Frontend Widgets are simply links to image URLs that will be posted to the front-end.
 * This model intended can be updated with a custom UI or the KeystoneJS backend.
 */

var FrontendWidget = new keystone.List('FrontendWidget', {
        map: { name: 'title' }
});

FrontendWidget.add({
        title: { type: String, required: true },
        url1: { type: String },
        alt1: { type: String },
        attributes1: { type: String },
        category: { type: String },      //Used to categorize widgets.
priorityId: { type: String }, //Used to prioritize display order. url2: { type: String }, alt2: { type: String }, attributes2: { type: String }, content1: { type: String }, content2: { type: String }, content3: { type: String }, content4: { type: String }, trans1: { type: String }, //default transformaiton trans2: { type: String } }); FrontendWidget.defaultColumns = 'title';
FrontendWidget
.register();

 

You don't have to follow the exact field entries above. Some of them, like trans1 and trans2 may not be of any use to you. Once you run through this tutorial and understand what is being done and how it's being used, feel free to make changes to your model to make it suit your needs. 

After creating that file, restart KeystoneJS, log into the back end of the Admin UI and you will see an entry for your front end widget. Isn't that awesome!? KeystoneJS automtically creates the back end pages you need to add and edit widgets. Cool!

Still, if you're like me, you probably don't want to confuse your client by letting them loose in the Admin UI. Too many options, buttons, and things to mess up. Later on in this tutorial, I'll show you how to create a front end, which I like to call the Artificial Back End, to allow them to edit the widget through the API. This allows you to remove surperfluous entries and customize the widget on-the-fly so that clients don't run amok or get confused.

Read more...

Adding Dates to Blog Posts

Posted in KeystoneJS by Chris on November 04, 2015

I wanted to add dates to the blog post entries. I figure others might want to do to same, so here's how I did it:

 

In templates/views/blog.hbs, change the line that says:

{{#if author.name.first}}by {{author.name.first}}{{/if}}

so that is says this:

{{#if author.name.first}}by {{author.name.first}} on {{date publishedDate format='MMMM DD, YYYY'}}{{/if}

This uses the built in Handlebars date helper for format the existing date.

Read more...

Displaying Back End Data

Posted in KeystoneJS, JavaScript by Chris on November 03, 2015

I'm not sure what other developers use for debugging the back end of Node apps like KeystoneJS. I love the Dev Tools in Chrome for debugging front end JavaScript. Compared to that, using the Node inspector for debugging node applications is painful and slow. Be sure to drop me a line if you have a suggestion for a better back end debugging solution. 

So, I discovered a little hack that lets me retrieve back end JSON data and display it on the front end. Here's how:

Add this Handlebars helper to the end of the file templates/views/helpers, right above the line that says return _helpers;

// JSON.stringify helper.
// This is used to send JSON data to the front-end so that it can be handled
// by front-end JavaScript.
// 10/21/15 Chris Troutner
_helpers.JSON = function(obj, options) {
 debugger;
 return JSON.stringify(obj);
}

 

That will allow back end JSON data to be stringified for input into the Handlebars template. Now, whatever template page you want to display data on, you can with the little code snippet below. For instance, I added this code to the bottom of templates/views/blog.hbs:

{{#if data}}
  <script type="text/javascript">
    var blogdata = {{{JSON data}}}
  </script>
  <p>blog data loaded.</p>
{{else}}
  <h3 class="text-muted">Blog data not loaded.</h3>
{{/if}}

 

And now your back end JSON data is stored inside a front end javascript varible. You can use Chrome Dev Tools to play with the data on a JavaScript command line or simply view the souce code of the page to look at the raw data.

 

Read more...

Editing Front Page Nav Links

Posted in KeystoneJS by Chris on November 02, 2015

One of the slick things about KeystoneJS is it's automatic configuration of pages and navigation in the AdminUI based on the models that exist. Create a new model and the ability to interact with it in the Admin UI magically appears. Cool!

But after porting this site over to Keystone, I wanted to remove some of the default navigations like the Contact and Gallery pages. That left me scratching my head.

It turns out that the nav links are stored in routes/middleware.js in the variable locals.navLinks.

Read more...

Rolling Back Git Commits

by Chris on November 01, 2015

I'm still fairly new to using Git and GitHub. I've taken the Code School courses on Git, but my actual working knowledge is limited. Today I broke this website because my Production server is set to allow Node applications to run on Port 80, but my development server was not. I didn't realize that of course. All I knew is that I broke the build between commits.

 

I followed the directions on this GitHub tutorial and was able to revert a commit and figure out exactly what I did to break the build. 

I also found this great little tidbit for overwriting troublesome files that aren't allow ing me to revert or commit:

git checkout HEAD^ file/to/overwrite
git pull

Source

Read more...

Porting Website to KeystoneJS

Posted in KeystoneJS, JavaScript by Chris on October 27, 2015

One of my first roles at Skagit Publishing as a website designer was to find them a new Content Management System (CMS). As I have an affinitiy for JavaScript over PHP, I began focusing on JavaScript based CMS. With Node and the Express.js library only being a few years old, there aren't many well established CMSs out there. KeystoneJS seemed to be the most developed and best supported JavaScript-based CMS I could find.

 

I've begun learning the ins and outs of KeystoneJS as well as building and customizing the CMS for one of Skagit Publishing's clients. I decided I should drink my own koolaid, so I've ported ChrisTroutner.com over to KeystoneJS too. I had some trouble getting the Angular.js library to run correctly. In all honesty, I haven't been terribly impressed with Angular in my own personal experience using it. At first, I used it extensively on ChrisTroutner.com, but there was nothing I used it for that I couldn't have accomplished with jQuery. I've decided to refactor the Angular code into jQuery and drop the use of the Angular library for now. Based on what I've been reading in the KeystoneJS Google group, Angular.js is losing mindshare to the React.js library anyways. KeystoneJS is redoing their entire back-end in React.js, and so I'm motivated to focus on my energies on learning React.js as well. 

 

  • I captured the steps needed to get KeystoneJS up and running on a VPS in this blog post.
  • This tutorial showed me how to get this website up and running on Port 80
  • I'm working through this tutorial to allow me to point my domain name at a Droplet/VPS.

Note: I used Handlebars instead of the default of Jade as my template of choice. When using the yo keystone command during setup of KeystoneJS, it asks you which one you want to use. If you follow the tutorials on this blog, then make sure you choose Handlebars (hbs), otherwise they won't work.

Read more...

blog data loaded.