Skip to content

[Draft] Infowindow API Proposal

Pablo Alonso edited this page Jun 10, 2015 · 43 revisions

Intro

Current Infowindow API is okay, but users are asking for more. Let's see can we can improve that! 😄

What we have today (06/2015)

Loading infowindows

Infowindows are part of the viz.json.

{
  ...
  "layers": [
    {
      ...
      "type": "layergroup",
      "options": {
        "layer_definition": {
          "layers": [
            {
              ...
              "infowindow": {
                "fields": [],
                "template_name": "table/views/infowindow_light",
                "template": "<div class=\"cartodb-popup v2\">\n  <a href=\"#close\" class=\"cartodb-popup-close-button close\">x</a>\n  <div class=\"cartodb-popup-content-wrapper\">\n    <div class=\"cartodb-popup-content\">\n      {{#content.fields}}\n        {{#title}}<h4>{{title}}</h4>{{/title}}\n        {{#value}}\n          <p {{#type}}class=\"{{ type }}\"{{/type}}>{{{ value }}}</p>\n        {{/value}}\n        {{^value}}\n          <p class=\"empty\">null</p>\n        {{/value}}\n      {{/content.fields}}\n    </div>\n  </div>\n  <div class=\"cartodb-popup-tip-container\"></div>\n</div>\n",
                "alternative_names": {},
                "width": 226,
                "maxHeight": 180
              },
              ...
            }
          ]
        }
      }
    }
  ]
}

There are a couple of classes that handle infowindows: cdb.geo.ui.InfowindowModel and cdb.geo.ui.Infowindow.

Adding infowindows dynamically

cdb.vis.Vis.addInfowindow(map, layer, fields [, options]) allows us to setup an infowindow for a layer. We need to pass a native map, the sublayer and an array of strings with the names of the fields that will be displayed. The following options could be specified: templateType, triggerEvent, templateName, extraFields, cursorInteraction (but they are not documented in the docs).

Here's an example (using cartodb.createLayer):

var map = L.map('map', { ... });

cartodb.createLayer(map, {
  ...
  })
  .addTo(map)
  .done(function(layer) {
    var sublayer = layer.getSubLayer(0);
    sublayer.setInteraction(true); // It looks like this is not necessary

    cdb.vis.Vis.addInfowindow(map, sublayer, ['cartodb_id','name']);
  });

And here's another example (using cartodb.createVis):

cartodb.createVis('map', 'https://wadus.cartodb.com/api/v2/viz/48b32c96-ffce-11e4-96bd-0e4fddd5de28/viz.json', {...})
  .done(function(vis, layers) {
    var sublayer = layers[1].getSubLayer(0);

    // you can get the native map to work with it
    var map = vis.getNativeMap();

    // It doesn't work if we don't set this
    map.viz = vis;

    cdb.vis.Vis.addInfowindow(map, sublayer, ['cartodb_id','name']);

    // Since we have the `vis` here, it would probably be easier to do something like:
    // vis.addInfowindow(sublayer, ['cartodb_id','name']);
  })

More info here.

Customizing infowindows

Infowindows can be customized via sublayer.infowindow. This is a Backbone.Model with the following attributes: template, sanitizeTemplate, width, maxHeight. This model exposes some more attributes like: fields, etc, but they are not documented.

Here's an example:

  ...
  sublayer.infowindow.set({
    template: $('#infowindow_template').html(),
    width: 218,
    maxHeight: 100
  });
  ...

More info here.

Hacking a custom infowindow

Someone could hack a custom infowindow by listening and taking advantage of the featureClick event on a sublayer.

Here's an example:

  sublayer.setInteraction(true);
  sublayer.setInteractivity('place');

  sublayer.on('featureClick', function(e, latlng, pos, data, layerNumber) {
    cartodb.log.log(e, latlng, pos, data, layerNumber);
    $("#myInfowindow").css({'display':'block','left':pos.x-75,'bottom':($(window).height()-pos.y+20), 'cursor': 'pointer'});
    $("#myInfowindow").find('p').text(data.place);
  });

More info here.

Facts about infowindows in CartoDB

  • A visualization/map can only have one opened infowindow at a time.
  • The information (fields/attributes) that is displayed in the infowindow is always linked to a feature that belongs to a mapnik/cartodb sublayer.
  • The attributes that are displayed in the infowindow are retrieved using the Maps API (attributes endpoint).

What users are asking for

  1. Open an infowindow from javascript and listen to events to know when this happens (example).
  2. Close an infowindow.
  3. Change data provider for infowindows. For example: many users want to show data from a custom sql based on cartodb_id and other stuff (example).

Other requests:

  • Create infowindows form scratch (without the need of a viz.json).
  • DOMready events to do things after infowindow it's rendered.
  • Show custom content, like graphs generated using Google Charts (example).
  • Extend template formatting options (to format numbers and so on).
  • Manage layer order interaction properly.
  • Customize infowindow size from CartoDB. Currently this can be done using cartodb.js using a hack (so an API endpoint should be provided for that (example).

New API

Infowindows will now always be accessed through sublayers and the cartodb.Infowindow object, instead of relying on cdb.vis.Vis.addInfowindow.

// Accessing infowindows
sublayer.infowindow // Returns a cartodb.Infowindow

// Updating infowindows
sublayer.infowindow.set({
  template: '<p>wadus: {{ wadus }}</p>', // string, mustache, function(data){},
  fields: ['wadus'],
  sanitizeTemplate: true|false,
  width: 400,
  maxHeight: 300
});

// Opening an infowindow on a sublayer feature
sublayer.infowindow.open('23234');

// Setting a dynamic function as a template for the infowindow. Function
// can return a string with the template or a Promise.
sublayer.infowindow.set('template', function(data) {
  var promise = new cartodb.Promise();

  $.get('http://javi.cartodb.com/api/v1/sql?q=select * from generate_series(1, 3) AS wadus', function(data) {
    var rows = _.pluck(data.rows, 'generate_series');
    var template = "<p>{{ rows[0].wadus }}</p>"
    promise.trigger('done', template);
  })

  return promise;
});

sublayer.infowindow.isOpened(); // Returns true if there's an opened infowindow
sublayer.infowindow.close();  // Closes the infowindow (if opened)
sublayer.infowindow.enable(); // Enables the infowindow for the layer
sublayer.infowindow.disable(); // Disables the infowindow for the layer

// Events
sublayer.infowindow.on('open', function() { ... });
sublayer.infowindow.on('close', function() { ... });
sublayer.infowindow.on('load', function(featureId, data) { ... }); // Data has been rendered

Existing JS APIs for infowindows/popups:

  • GMaps InfoWindow API. Features:
  • Customize the infowindow via options.
  • Getting/setting the content of an infowindow (also an option).
  • Getting/setting the position (latlng) of the infowindow (also an option).
  • Open an infowindow in a map (in the given position or in the position of an associated marker).
  • Listen to events that are triggered when attributes change, DOM inside of the infowindow is ready, etc.
  • Leaflet Popup API. Features:
  • Customize the infowindow via options.
  • Add a popup to a map.
  • Open a popup on a map.
  • Getting/setting the content of the popup.
  • Getting/setting the position (latlng) of the popup.
Clone this wiki locally