Week 12: c.server.update(): Calling the Server from a Widget

Week 8 built the exact mental model for this: send a request, get a response asynchronously, update the UI in a callback. This week wires that pattern into the widget framework directly — the one call that quietly does everything GlideAjax and a client-callable Script Include did together, minus almost all the ceremony.

Module 12 of 25 Week 12 of 26 ~3–4 Hours Hands-on Exercise Included

By the end of this week, you'll be able to

  • Trigger a server-script re-run from the client with c.server.update()
  • Build loading and error states around an async widget action
  • Embed one widget inside another's server script with $sp.getWidget()

1. c.server.update()

Calling c.server.update() re-runs the widget's server script from the client, sending along whatever is currently on c.data, and returns a promise that resolves once the server script finishes — at which point c.data is replaced with whatever the server script produced this time.

Client Controller — search action
function() {
  var c = this;
  c.data.searchTerm = '';

  c.runSearch = function() {
    c.data.action = 'search'; // the server script reads this to decide what to do
    c.server.update();        // re-runs the server script; c.data gets replaced on resolve
  };
}
Server Script — reacting to the action
(function() {
  if (input && input.action === 'search') {
    data.results = [];
    var gr = new GlideRecord('incident');
    gr.addQuery('short_description', 'CONTAINS', data.searchTerm || '');
    gr.setLimit(20);
    gr.query();
    while (gr.next()) {
      data.results.push({
        number: gr.getValue('number'),
        shortDescription: gr.getValue('short_description')
      });
    }
  }
})();

2. The input Object

Notice the server script reads from input, not data, to check what the client sent — input is the server script's view of the client's c.data at the moment update() was called, distinct from the data object the script is about to populate for the response. Keeping this distinction straight (client sends via input, server responds via data) avoids a lot of confusion once a widget has several different actions flowing through the same update() call.

3. Loading & Error States

c.server.update() returns a standard promise — use .then() and a boolean flag to drive a loading indicator, exactly like you would around any async call in a modern front end:

loading/error state around c.server.update()
c.runSearch = function() {
  c.loading = true;
  c.error = null;
  c.data.action = 'search';

  c.server.update().then(function() {
    c.loading = false;
  }, function() {
    c.loading = false;
    c.error = 'Search failed. Please try again.';
  });
};
HTML Template — reflecting the state

{{c.error}}

4. Embedding One Widget Inside Another

A server script can render a completely separate widget and hand its markup to the current widget's data — useful for composing a page out of smaller, reusable widgets from within a parent widget's own server script, rather than only via the page designer.

Server Script — embedding a widget
(function() {
  data.headerWidget = $sp.getWidget('page-header', {
    title: 'Search Results'
  }).html;
})();
HTML Template — rendering the embedded widget's markup

5. Hands-on Exercise

Hands-on

Build a live incident search widget

Requirements:

  1. Build the search widget shown above, with an ng-model-bound search box and a "Search" button calling c.runSearch().
  2. Add the loading/error state pattern, including a disabled button state while a search is in flight.
  3. Deliberately break the server script (e.g. reference an undefined variable) and confirm your error state renders correctly when the promise rejects.
  4. Fix the server script, then confirm the happy path renders a result list correctly.
Hint

c.server.update() rejects its promise when the server script throws an unhandled exception — that's the mechanism your error-state test in step 3 is exercising, not something you need to manually simulate.

6. Knowledge Check

Four quick questions. Expand each to check your answer.

Q1

What does c.server.update() actually do?

It re-runs the widget's server script from the client, sending along the current contents of c.data, and returns a promise that resolves once the server script finishes — at which point c.data on the client is replaced with whatever the server script produced during that run.

Q2

What's the difference between input and data inside a widget's server script during an update() call?

input is the server script's read-only view of what the client's c.data looked like at the moment update() was called — how the script knows what the client is asking for. data is the object the server script populates to send back as the new c.data on the client. Client-to-server flows through input; server-to-client flows through data.

Q3

How do you build a loading indicator around a c.server.update() call?

Set a boolean flag (e.g. c.loading = true) before calling update(), then use .then() with success and failure callbacks to reset it to false in both cases — success clears it normally, failure clears it and also sets an error message. The template reads that flag with ng-if/ng-disabled to reflect the in-flight state.

Q4

What does $sp.getWidget() let you do that the page designer alone can't?

It lets a widget's own server script render a separate widget programmatically and use its output HTML as part of the current widget's data — composing widgets together from within server script logic, rather than only by dragging widgets into containers via the Service Portal Designer.