function updateDisclosureContent()

in modules/ui/raw_member_editor.js [94:366]


    function updateDisclosureContent(selection) {
        _contentSelection = selection;

        if (selection.empty()) return;

        var memberships = [];
        var entity = context.entity(_entityID);
        entity.members.slice(0, _maxMembers).forEach(function(member, index) {
            memberships.push({
                index: index,
                id: member.id,
                type: member.type,
                role: member.role,
                relation: entity,
                member: context.hasEntity(member.id)
            });
        });

        var list = selection.selectAll('.member-list')
            .data([0]);

        list = list.enter()
            .append('ul')
            .attr('class', 'member-list')
            .merge(list);


        var items = list.selectAll('li')
            .data(memberships, function(d) {
                return osmEntity.key(d.relation) + ',' + d.index + ',' +
                    (d.member ? osmEntity.key(d.member) : 'incomplete');
            });

        items.exit()
            .each(unbind)
            .remove();

        var itemsEnter = items.enter()
            .append('li')
            .attr('class', 'member-row form-field')
            .classed('member-incomplete', function(d) { return !d.member; });

        itemsEnter
            .each(function(d) {
                var item = d3_select(this);

                var label = item
                    .append('label')
                    .attr('class', 'field-label');

                if (d.member) {
                    // highlight the member feature in the map while hovering on the list item
                    item
                        .on('mouseover', function() {
                            utilHighlightEntities([d.id], true, context);
                        })
                        .on('mouseout', function() {
                            utilHighlightEntities([d.id], false, context);
                        });

                    var labelLink = label
                        .append('span')
                        .attr('class', 'label-text')
                        .append('a')
                        .attr('href', '#')
                        .on('click', selectMember);

                    labelLink
                        .append('span')
                        .attr('class', 'member-entity-type')
                        .text(function(d) {
                            var matched = context.presets().match(d.member, context.graph());
                            return (matched && matched.name()) || utilDisplayType(d.member.id);
                        });

                    labelLink
                        .append('span')
                        .attr('class', 'member-entity-name')
                        .text(function(d) { return utilDisplayName(d.member); });

                    label
                        .append('button')
                        .attr('class', 'member-zoom')
                        .attr('title', t('icons.zoom_to'))
                        .call(svgIcon('#iD-icon-geolocate'))
                        .on('click', zoomToMember);

                } else {
                    var labelText = label
                        .append('span')
                        .attr('class', 'label-text');

                    labelText
                        .append('span')
                        .attr('class', 'member-entity-type')
                        .text(t('inspector.' + d.type, { id: d.id }));

                    labelText
                        .append('span')
                        .attr('class', 'member-entity-name')
                        .text(t('inspector.incomplete', { id: d.id }));

                    label
                        .append('button')
                        .attr('class', 'member-download')
                        .attr('title', t('icons.download'))
                        .attr('tabindex', -1)
                        .call(svgIcon('#iD-icon-load'))
                        .on('click', downloadMember);
                }
            });

        var wrapEnter = itemsEnter
            .append('div')
            .attr('class', 'form-field-input-wrap form-field-input-member');

        wrapEnter
            .append('input')
            .attr('class', 'member-role')
            .property('type', 'text')
            .attr('maxlength', 255)
            .attr('placeholder', t('inspector.role'))
            .call(utilNoAuto);

        wrapEnter
            .append('button')
            .attr('tabindex', -1)
            .attr('title', t('icons.remove'))
            .attr('class', 'remove form-field-button member-delete')
            .call(svgIcon('#iD-operation-delete'));

        if (taginfo) {
            wrapEnter.each(bindTypeahead);
        }

        var dragOrigin, targetIndex;

        itemsEnter.call(d3_drag()
            .on('start', function() {
                dragOrigin = {
                    x: d3_event.x,
                    y: d3_event.y
                };
                targetIndex = null;
            })
            .on('drag', function(d, index) {
                var x = d3_event.x - dragOrigin.x,
                    y = d3_event.y - dragOrigin.y;

                if (!d3_select(this).classed('dragging') &&
                    // don't display drag until dragging beyond a distance threshold
                    Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)) <= 5) return;

                d3_select(this)
                    .classed('dragging', true);

                targetIndex = null;

                selection.selectAll('li.member-row')
                    .style('transform', function(d2, index2) {
                        var node = d3_select(this).node();
                        if (index === index2) {
                            return 'translate(' + x + 'px, ' + y + 'px)';
                        } else if (index2 > index && d3_event.y > node.offsetTop - node.offsetHeight) {
                            if (targetIndex === null || index2 > targetIndex) {
                                targetIndex = index2;
                            }
                            return 'translateY(-100%)';
                        } else if (index2 < index && d3_event.y < node.offsetTop) {
                            if (targetIndex === null || index2 < targetIndex) {
                                targetIndex = index2;
                            }
                            return 'translateY(100%)';
                        }
                        return null;
                    });
            })
            .on('end', function(d, index) {

                if (!d3_select(this).classed('dragging')) {
                    return;
                }

                d3_select(this)
                    .classed('dragging', false);

                selection.selectAll('li.member-row')
                    .style('transform', null);

                if (targetIndex !== null) {
                    // dragged to a new position, reorder
                    context.perform(
                        actionMoveMember(d.relation.id, index, targetIndex),
                        t('operations.reorder_members.annotation')
                    );
                }
            })
        );


        // update
        items = items
            .merge(itemsEnter);

        items.select('input.member-role')
            .property('value', function(d) { return d.role; })
            .on('blur', changeRole)
            .on('change', changeRole);

        items.select('button.member-delete')
            .on('click', deleteMember);



        function bindTypeahead(d) {
            var row = d3_select(this);
            var role = row.selectAll('input.member-role');
            var origValue = role.property('value');

            function sort(value, data) {
                var sameletter = [];
                var other = [];
                for (var i = 0; i < data.length; i++) {
                    if (data[i].value.substring(0, value.length) === value) {
                        sameletter.push(data[i]);
                    } else {
                        other.push(data[i]);
                    }
                }
                return sameletter.concat(other);
            }

            role.call(uiCombobox(context, 'member-role')
                .fetcher(function(role, callback) {
                    // The `geometry` param is used in the `taginfo.js` interface for
                    // filtering results, as a key into the `tag_members_fractions`
                    // object.  If we don't know the geometry because the member is
                    // not yet downloaded, it's ok to guess based on type.
                    var geometry;
                    if (d.member) {
                        geometry = context.geometry(d.member.id);
                    } else if (d.type === 'relation') {
                        geometry = 'relation';
                    } else if (d.type === 'way') {
                        geometry = 'line';
                    } else {
                        geometry = 'point';
                    }

                    var rtype = entity.tags.type;
                    taginfo.roles({
                        debounce: true,
                        rtype: rtype || '',
                        geometry: geometry,
                        query: role
                    }, function(err, data) {
                        if (!err) callback(sort(role, data));
                    });
                })
                .on('cancel', function() {
                    role.property('value', origValue);
                })
            );
        }


        function unbind() {
            var row = d3_select(this);

            row.selectAll('input.member-role')
                .call(uiCombobox.off);
        }
    }