Skip to content

reference js controller

ax1 edited this page Nov 11, 2022 · 19 revisions

ir.controller()

Get a controller (MVC pattern). This object contains all the logic to retrieve data from the server, load the data into a model object, and paint the data in the related template. The controller also reacts to events generated in the template (if a input file has changed , it will update the model, and if a "save" button is pressed, it will send the updated model to the server).

Usage

ir.controller (name)

  • param name: the name of the controller = view (template) = model

  • return: a controller object, containing the model and the methods to perform REST calls against the server.

The controller object most useful methods and properties are:

  • add(key,value). [ONLY FOR ARRAY RESOURCES] Add an element (in the model and therefore in the view), BUT NOT in the server. If server must be finally updated, call update() method (or use autosave in the data-options attribute). Another option is to create a function to call controller.model.add(key,value).

  • configure(urlOrObject [,options] [,customMethods]): configure the controller with the url or service to synchronize data. When configuring, first retrieve data from the server and then call the Template to insert the data into the web page. The configure call is usually added in the same page containing the template. This configure call can be omitted if you configure the service by using the data-provider tag in the template.

    • param urlOrObject: the url address of a REST service. A javascript object or array can also be used as source of data. To allow offline mode and temporary data, the localStorage and sessionStorage can also be used as url addresses. valid examples are 'items/5791' or '{"name":"Jebediah"}' (JSON) or {name:'Jebediah'} (object).

    • (optional) param options: list of custom options for that controller. If two options or more, separate them with an space character. Allowed options are:

      • autorefresh: update automatically the content from the server every 1 second. This option is useful when not using webSockets and the content must be refreshing continuously.

      • autosave: save automatically the data (model) in the server when a change has occurred. For instance when typing in an input field, and two-way databinding is activated, the model is changing, so the updates are sent to the server. This option is really useful to emulate native apps behaviour, instead of adding a save button when the content changes.

    • (optional) param customMethods: an object containing a set of custom functions for the controller. See the controller.extend() method.

  • create(), read(), update(), delete(). Internal methods of the controller to perform REST calls. create = POST, read = GET, update = PUT, delete = DELETE.

  • extend(customMethods): increase the functionality of the controller by adding new methods. These methods can be called later from the controller or from the view (i.e: <button onclick='{{executeAll()}}'>execute</button>). Inside these methods, the implicit this object is referenced to the controller. The extend method should be invoked before the configure() method.

    • param customMethods: Object containing one or more functions.

      These functions are executed when called from the template-view or directly from your javascript code. The function can be a normal function, closure, callback, promise, async function... no restrictions on type. A minor note, if you declare the function as an ES6 fat arrow function, you lose the this === controller (but the controller could be get from ir.controller if needed!).

      To allow customize a REST method, two approaches can be implemented:

      • create a custom method and declare it in the template instead of the REST one. I.e.: <button onclick="{{customUpdate()}}">
      • override the REST method: this is is handy when preprocessing/postprocessing the REST call is needed. The original method is still available as _method(). I.e.: <button onclick="{{update()}}">.

      Use case, if before reading from backend and/or after reading we need to add our custom behavior, we just create a normal function, Inside that function we can call this.read() (or update delete...), so we could log data before reading or modify the model after reading from backend.

    • return: the controller object.

  • model. The container of the real data to be exchanged. This object is generally not useful since the library already process internally the changes in the model.

  • view. The HTML element container of the template. When the template is not initialized, view = template. This is useful when dealing with arrays, since the elements are recreated (yet) and sometimes we need to get a subelement by using selectors, retrieving the container element is faster than looking for it (by using querySelector(...controller.name)).

  • subscriptions. Useful only for debugging. External elements subscribed to another model. {{cars:name}}, this syntax asks for a variable from an (not the current) external model, so this would be displayed if inspecting the subscriptions object.

  • paint(). Call the view (only that view and related subscriptors) to be repainted again, even if no changes in the model. This method should not be called typically because the controller calls repaint when needed. But sometimes (i.e.: when changing data directly into the model._obj or when creating new html elements in the view, on custom events, etc) it is handy to repaint the view.

  • remove(element). [ONLY FOR ARRAY RESOURCES] Delete the element (in the model and therefore in the view), BUT NOT in the server. If server must be finally updated, call update() method (or use autosave in the data-options attribute). This method is intended to perform delete operations and discard them without side effects. The element parameter can be either the related object in the model <div>{{prop}}</div> , or a child tag <div>{{prop}} <button onclick="{{remove(this){{}}">remove</button></div>.

  • _destroy(). Delete the controller and all the related bindings (models, events...). This is a private method and in 99,99% of the cases, it should not be invoked. This method is automatically called when a new page is loading a template, in the same container that hosted a previous template, and therefore the template, controller, and model must be cleared properly.

  • {customMethod}(). Custom methods can also be added directly to the controller: ir.controller(name).customMethod = function(param){...} can be defined at any time (even if controller is not configured yet).The function has the this object assigned to the related controller object and it can be invoked either by using {{customMethod('hi')}} or directly in javascript code. Depending of your javascript style, you may want to add functions this way or by using the extend() method. The name of the function cannot be any, except the non-REST controller methods.

Important note

Most of the built-in methods in the controller returns a Promise

function refresh(name){
  ir.controller(name).update(controller=>alert(controller.isReady))
}

Important note

When developing an application, one controller can be called several times. Instead or repeating ir.controller(name)... you can alias the controller inside a module or a IIFE function.

// instead of ...
ir.controller('cars').customize =  function() {...}
ir.controller('cars').repair = function() {...}
ir.controller('cars').onNew = function(event) {...}

// too many functions, we can improve readability by aliasing the controller
// also we add it as a module to encapsulate all the variables
// no exports because other modules can always access the controller by using ir.controller('name')

const cars = ir.controller('cars')
cars.customize =  function() {...}
cars.repair = function() {...}
cars.onNew = function(event) {...}

Examples

Create a page to be processed in the client side. Note that only one ad-hoc function is implemented, because update() and delete() are the default ones. Note also that you can use a different. Depending on your javascript skills and flavours you can use normal functions, arrow functions or promises-async/await.

 <div data-model="car21" data-provider="services/cars/21">
   <p>change name: <input name="carName" value="{{name}}"/></p>
   <p>
     <button onclick="{{update()}}">save</button>
     <button onclick="if(checkBefore()){{delete()}}">delete</button>
   </p>
 </div>

 <script>
     ir.controller("car21").configure("services/cars/21")
     ir.controller("car21").checkBefore=function(){
       if (this.model.get("type")==="ferrari"){
         throw "you cannot delete this type of car"
       }
     }
 </script>

This new example shows real time reaction to changes. The user does not need to check for updates or click on a button to save new data. The data-options property improves the user experience, while keeping the code really clean and small for the developers.

<!--html only,
the controller object will take the options from the template-->
 <div data-model="car21" data-provider="services/cars/21"
 data-options="autosave autorefresh">
   <p>change name: <input name="carName" value="{{name}}"/></p>
   <p>current owner: {{owner}}</p>
   <p>bid timeout: {{time}}</p>
 </div>
<!--js only,
the controller object will configure the url and the options-->
 <div data-model="car21">
   <p>change name: <input name="carName" value="{{name}}"/></p>
   <p>current owner: {{owner}}</p>
   <p>bid timeout: {{time}}</p>
 </div>
 <script>
  ir.controller("car21").configure("services/cars/21","autosave autorefresh"));
 </script>

Add a javascript object as the data provider. Useful when testing or when a pure client-side data is needed.

var myCar={name:"mini clubman", color:"red"};
ir.controller("car21").configure(myCar));

Create an offline application. Data will be saved locally (in the user's device). This data could be saved later to the server on demand or periodically, but the user can play the application even if there is no internet connection.

ir.controller("tasks").configure('locaStorage/myTasks'));
//the tasks should be loaded in the template <div data-model='tasks'>

Add a custom method. This function can later be called by a button.

<!-- call a custom method-->
<div data-model="car21" data-provider="services/cars/21">
  <p>change name: <input name="carName" value="{{name}}"/></p>
  <p>
    <button onclick="{{general:check()}}">delete</button>
  </p>
</div>
<script>
  ir.controller('general').check = function(){/*do whatever*/}
</script>

Add several custom methods by using extend. They can also be declared by using several ir.controller.funcName, but this way is more elegant

<!-- call a custom function-->
<div data-model="car21" data-provider="services/cars/21">
  <p>change name: <input name="carName" value="{{name}}"/></p>
  <p>
    <button onclick="{{general:check()}}">delete</button>
  </p>
</div>
<script>
  ir.controller('general').extend({
    check() {...},
    uncheck() {...}
  })
</script>

This time, use only javascript to extend and configure controller (instead of data-provider attribute)

<!-- call a custom function-->
<div data-model="car21">
  <p>change name: <input name="carName" value="{{name}}"/></p>
  <p>
    <button onclick="{{general:check()}}">delete</button>
  </p>
</div>
<script>
  //controller cars
  ir.controller('car21').configure("services/cars/21")
  //controller general
  ir.controller('general')
  .extend({
    check(){...},
    uncheck(){...}
  })
  .configure('services/general')  
</script>

Override an existing method (delete in the backend) with your own functionality

<!-- call a custom function-->
<div data-model="friend" data-provider="people/friend/12">
  <p>{{name}} <button onclick="{{secureDelete()}}">delete</button></p>
</div>
<script>
  ir.controller('friend').secureDelete=function(){
    let ok=confirm('Are you sure')
    if(ok){
      this.delete().then (controller=>alert('deleted successfully'))
    }
  }
</script>

Override an existing method (delete in the backend), but this time, use the standard call name in the template. Note the {{delete()}}. Note that the default REST call is still available by using _delete().

<!-- call a custom function-->
<div data-model="friend" data-provider="people/friend/12">
  <p>{{name}} <button onclick="{{delete()}}">delete</button></p>
</div>
<script>
  ir.controller('friend').delete=function(){
    let ok=confirm('Are you sure')
    if(ok){
      this._delete().then (controller=>alert('deleted successfully'))
    }
  }
</script>

async/await version (instead of the promise.then()):

Override an existing method (delete in the backend). Try catch is also added to keep full control on exceptions. Note that the default REST call is still available by using _delete().

<!-- call a custom function-->
<div data-model="friend" data-provider="people/friend/12">
  <p>{{name}} <button onclick="{{delete()}}">delete</button></p>
</div>
<script>
  ir.controller('friend').delete = async function() {
    try{
      let ok = confirm('Are you sure')
      if(ok){
        await this._delete()
        alert('deleted successfully')
      }
    }catch(err){
      console.error(err)
    }
  }
</script>

Clone this wiki locally