Node.js Domestic MVC Framework ThinkJS Development controller (continued)

Keywords: Javascript REST node.js Database ECMAScript

Original: Jingxiu Web page instantly pushes https://xxuyou.com Reprinted please indicate the source
Links: https://blog.xxuyou.com/nodejs-thinkjs-study-controller-2/

This series of tutorials is in the version of ThinkJS v2.x. Official website (For example, the course is mainly about practical operation.

This article continues to explain the use of Controller.

Construction method

If you want to do something with object instantiation, the construction method is the best choice. The constructor provided by ES6 is constructor.

The constructor method is the default method of the class, which is called automatically when the object instance is generated by the new command. A class must have constructor methods, and if there is no explicit definition, an empty constructor method will be added by default.
Http://es6.ruanyifeng.com/ docs/class constructor-method
ECMAScript 6 Introduction Author: Ruan Yifeng

init and constructor

thinkjs is powerful in that we can not only declare Class by ourselves with a regular export default class, but also provide a way to create Class dynamically: think.controller.

However, thinkjs dynamically created lass does not have a constructor, but instead provides an init as an alternative to the construction method, which is used in the same way as the constructor.

Last article ( Node.js Domestic MVC Framework ThinkJS Development controller There are also examples of the use of init methods in the base class and inheritance chain section. Look at the code:

// src/home/controller/base.js
'use strict';
export default class extends think.controller.base {
  init(...args) {
    super.init(...args);
    // Require all URLs to carry auth parameters
    let auth = this.get('auth');
    if (think.isEmpty(auth)) {
      return this.error(500, 'whole url Must carry auth parameter');
    }
  }
}

Of course, this does not mean that you can't use the constructor method. If you are as accustomed as I am to using export default class to declare your own classes, you can still use the standard constructor method.

thinkjs creates Class dynamically. See the official documentation for more details.

Magic Methods

thinkjs implements several useful magic methods, which provide great convenience for development.

_u before pre-operation

As the name implies, the pre-operation will be executed before the specific Action in Controller is executed, which means "executed before xxx". Look at the code:

// src/home/controller/user.js
'use strict';
export default class extends think.controller.base {
  __before() {
    console.log('this is __before().');
  }
  indexAction() {
    console.log('this is indexAction().');
    return this.end();
  }
}
// The results of visiting/home/user/index are as follows:
// this is __before().
// this is indexAction().

Then someone might say, uuuuuuuuuuuuuuuuu The old rule, look at the code:

// src/home/controller/user.js
'use strict';
export default class extends think.controller.base {
  init(...args) {
    super.init(...args);
    console.log('this is init().');
  }
  __before() {
    console.log('this is __before().');
  }
  indexAction() {
    console.log('this is indexAction().');
    return this.end();
  }
}
// The results of visiting/home/user/index are as follows:
// this is init().
// this is __before().
// this is indexAction().

Did you see? There is still a sequence of execution, and a more complex one:

// src/home/controller/base.js
'use strict';
export default class extends think.controller.base {
  init(...args) {
    super.init(...args);
    console.log('this is base.init().');
  }
}
// src/home/controller/user.js
'use strict';
export default class extends think.controller.base {
  init(...args) {
    super.init(...args);
    console.log('this is user.init().');
  }
  __before() {
    console.log('this is user.__before().');
  }
  indexAction() {
    console.log('this is user.indexAction().');
    return this.end();
  }
}
// The results of visiting/home/user/index are as follows:
// this is base.init().
// this is user.init().
// this is user.__before().
// this is user.indexAction().

Well, you'll say "unexpected"-

_u after post-operation

Understanding the pre-operation, post-operation is not difficult to understand, see the code:

// src/home/controller/user.js
'use strict';
export default class extends think.controller.base {
  init(...args) {
    super.init(...args);
    console.log('this is init().');
  }
  __before() {
    console.log('this is __before().');
  }
  __after() {
    console.log('this is __after().');
  }
  indexAction() {
    console.log('this is indexAction().');
    return this.end();
  }
}
// The results of visiting/home/user/index are as follows:
// this is init().
// this is __before().
// this is indexAction().

Why? There seems to be something wrong... _u after did not execute.

This is certainly not the result of u after written on indexAction! Modify the code:

// src/home/controller/user.js
'use strict';
export default class extends think.controller.base {
  init(...args) {
    super.init(...args);
    console.log('this is init().');
  }
  __before() {
    console.log('this is __before().');
  }
  __after() {
    console.log('this is __after().');
    return this.end();
  }
  indexAction() {
    console.log('this is indexAction().');
  }
}
// The results of visiting/home/user/index are as follows:
// this is init().
// this is __before().
// this is indexAction().
// this is __after().

OK this time, and the expected results are in line.

I know you've noticed carefully that the code return this.end() moved from indexAction to u after.

this.end() is executed internally Node.js HTTP response.end() Operations indicate that the entire response flow is over, so if you want to enable u after, this code will run in u after.

_u call empty operation

This magic method is a bit special. It is not used to run in a process node as the first two magic methods, but to share part of the responsibility of init: to detect that u call takes over when an Action accessed by a Controller is not defined.

// src/home/controller/user.js
'use strict';
export default class extends think.controller.base {
  init(...args) {
    super.init(...args);
    console.log('this is init().');
  }
  __call() {
    console.log(this.http.action + 'Action is not exists.');
    return this.end();
  }
  indexAction() {
    console.log('this is indexAction().');
    return this.end();
  }
}
// The results of visiting/home/user/test are as follows:
// this is init().
// testAction is not exists.

You can see that when the accessed testAction does not exist, the framework runs u call for processing, and our processing is to record errors and end the response output.

The sample code is to place u call in a secondary subclass, usually in a base class, which can control the illegal access processing of all subclasses.

Tip: This method can only be used to capture situations where Action does not exist, but if Controller does not exist, it will directly trigger 404 errors (taken over by the framework) without interference.
To capture the absence of Controller, we need to extend the framework's error classes, which are described in another article.

External invocation

The thinkjs official API has an interface to instantiate another Controller, but does not specify the specific purpose of this:

//user controller under instantiated home module
let instance = think.controller('user', http, 'home');

Usually this method can be used to instantiate sibling level Controller, or to get data, or trigger a business process, etc. to see the code:

// Increased src/home/controller/user.js
_getPoints() {
  return 8000;
}

// src/home/controller/index.js
let instance = think.controller('user', this.http, 'home');
let points = instance._getPoints();
console.log(points); // Printing: 8000
instance.indexAction(); // The same effect as direct execution of / home/user/index
instance.testAction(); // Error [Error] TypeError: instance.testAction is not a function

So thinkjs provides a way to instantiate a Controller on demand and run its methods.

At first glance, this approach seems very close to the result of this.redirect (except for the magic method that does not trigger u call), so what's the use of thinkjs providing this approach? Look at the code:

// src/home/controller/util.js
'use strict';
export default class extends think.controller.base {
  calcGPSDistance(lat, lng){
    // Calculating the Linear Distance of GPS Two Points
    return distance;
  }
  calcBaiduDistance(lat, lng){
    // Calculating the Linear Distance of Two Points in Baidu Geodetic Coordinates
    return distance;
  }
  calcSosoDistance(lat, lng){
    // Calculating the Linear Distance between Two Points in Soso Coordinates
    return distance;
  }
}

This is an assistant Controller, a "invisible" Controller that cannot be accessed directly from the url because all its method names have no Action suffix.

In this scenario, the way the runtime instantiates Controller and manipulates its methods comes in handy.

Built-in http objects

When the controller is instantiated, http is passed in. This http object is an object that ThinkJS repackages req and res, not the built-in http object of Node.js.
If you want to get the object in Action, you can get it through this.http.
https://thinkjs.org/zh-cn/doc/2.2/controller.html#toc-efc
thinkjs official website

Extended application: add a transition page function that automatically jumps after n seconds

thinkjs framework does not provide us with the function of such a transition page, so we can implement one by ourselves to practice and code it:

// src/common/controller/complete.js
'use strict';
export default class extends think.controller.base {
  /**
   * Display transit pages
   *
   * Call method:
   * let complete = think.controller('complete', this.http, 'common');
   * return complete.display('Apply new success! ',' / ', 5);
   *
   * @param msg Prompt text, supporting HTML
   * @param url Target address for subsequent automatic jumps
   * @param delay Seconds of stay
   * @returns {think.Promise}
   */
  display(msg, url='', delay=3) {
    let tpl = 'common/complete/200';
    let opt = think.extend({}, {type: 'base', file_depr: '_', content_type: 'text/html'});
    this.fetch(tpl, {}, opt).then(content => {
      content = content.replace(/COMPLETE_MESSAGE/g, msg);
      if (url) {
        content = content.replace(/TARGET_URL/g, url);
        content = content.replace(/WAIT_SECONDS/g, delay);
      };
      this.type(opt['content_type']);
      return this.end(content);
    }).catch(function(err){
      return this.end('');
    });
  }
}
<!-- view/common/complete_200.html -->
<!DOCTYPE html>
<html>
<head>
    <title>Jumping - Jingxiu net</title>
</head>
<body>
<div class="header">
    <div class="wrap">
        <div class="logo"><a href="/"><img src="/static/img/logo.png" alt="XxuYou" width="60"></a></div>
        <div class="headr">&nbsp;</div>
    </div>
</div>
<div class="wrap">
    <div style="margin-top:20px;height:100px;background:url(/static/img/200.gif) top center no-repeat;"></div>
    <h1>COMPLETE_MESSAGE</h1>
    <div class="error-msg"><pre>Tip: The page will be <span id="_count">WAIT_SECONDS</span> Redirect to <a href="TARGET_URL">TARGET_URL</a></pre></div>
    <input type="hidden" id="_target_url" value="TARGET_URL" />
    <input type="hidden" id="_wait_seconds" value="WAIT_SECONDS" />
</div>
<script type="text/javascript">
    var thisLoad = function () {
        var _target_url = document.getElementById('_target_url').value;
        var _wait_seconds = document.getElementById('_wait_seconds').value;
        if (_target_url == '') return false;
        if (/^\d+$/.test(_wait_seconds) == false || _wait_seconds < 1 || _wait_seconds >= 3600) {
            try {
                document.location.replace(_target_url);
            } catch(e) {};
        } else {
            thisCount(_wait_seconds);
            window.setTimeout(function () {
                try {
                    document.location.replace(_target_url);
                } catch(e) {};
            }, _wait_seconds*1000);
        };
        return true;
    };
    var thisCount = function (cnt) {
        if (cnt < 0) return false;
        document.getElementById('_count').innerHTML = cnt;
        window.setTimeout(function () {
            thisCount(--cnt);
        }, 1000);
    };
    window.attachEvent ? window.attachEvent('onload', thisLoad) : window.addEventListener('load', thisLoad);
</script>
</body>
</html>
// Controller internal invocation
indexAction() {
  // Business processes...
  let complete = think.controller('complete', this.http, 'common');
  return complete.display('Successful operation!', '/', 5);
}

The newly added src/common/controller/complete.js is a non-intrusive and successful business processing page, and its internal operation is similar to that of brother Controller src/common/controller/error.js.

The view/common/complete_200.html added above is a template for related transition pages. There are three placeholders (corresponding to the entry of display ing method, respectively):

  • COMPLETE_MESSAGE Used to Operate Successful Text Tips
  • TARGET_URL is used for the target url that will automatically enter later
  • WAIT_SECONDS is used for page transition time in seconds

The principle of implementation is very simple, you can understand it by reading two new codes.

Extended Application: Fast Building REST API

Actually, this part is too brief, I didn't want to write it. However, considering the integrity of the tutorial, it is better to write it down.

The concept of REST is introduced here. Interested users can search by themselves.

thinkjs's official website says:

Automatically generate REST API without writing any code.

This is true. When you create Controller, just add a parameter (thinkjs controller [name] --rest), you can generate a REST API that can manipulate database tables.

Of course, there are also operational agreements:

  • GET/ticket# Get ticket list
  • GET/ticket/12# View a specific ticket
  • POST/ticket # Create a new ticket
  • PUT/ticket/12# Update ticket 12
  • DELETE/ticket/12 # Delete ticekt 12

Following the above operation convention, it is possible to directly manipulate the data in the database tables.

However, such an API is just an API in a "naked run" state, or it can not be directly put into use. Because it requires the requester not only to be familiar with all the data table structures, but also to rely on the requester to maintain the correlation between multi-table data, not to mention the mapping of operation path, field mapping, mapping of returned data and so on.

Even though thinkjs provides field filtering, custom GET/POST/PUT/DELETE methods for more customization, the end result is probably to wrap another layer outside the current API to provide operational path mapping, authentication token issuance and recognition, field mapping, associated data maintenance, and so on.

Of course, as a development framework, thinkjs has done enough and excellent, so we still need to build a complete production model of REST API that can be implemented as an application system.

Extended application: controller hierarchy (multilevel)

At the beginning of Controller, we talked about the use of multilevel controllers. Now it's time to practice.

thinkjs controller home/group/article
# Print results
#  create : src/home/controller/group
#  create : src/home/controller/group/article.js
#  create : src/home/logic/group
#  create : src/home/logic/group/article.js

You can see that the hierarchical folder group is automatically created under the controller, and the new controller file is also placed under the group.

Unfortunately, thinkjs ignores that the inheritance path of the base class Base is not modified within the new controller when hierarchy exists.

Let's modify the src/home/controller/group/article.js code:

import Base from './base.js';
// Modified to read as follows: uuuuuuuuuuu
import Base from './../base.js';

After dealing with the path problem of base class inheritance, nothing else. It's just one more layer of folders, and the url of access needs to be adjusted accordingly:

domain.com/home/controller/group/article

Just adjust your Nginx Rewrite rule or thinkjs route rule according to the actual situation. Is that simple ~?

done~

Last article: Node.js Domestic MVC Framework ThinkJS Development controller
Next article: Node.js Domestic MVC Framework ThinkJS Development logic

Original: Jingxiu Web page instantly pushes https://xxuyou.com Reprinted please indicate the source
Links: https://blog.xxuyou.com/nodejs-thinkjs-study-controller-2/

Posted by lesolemph on Mon, 24 Dec 2018 14:24:06 -0800