Category: Technical

Technical articles about programming, shell scripts, and computer hints ‘n’ tips.

Google Maps Javascript API and reactive Vue.js 3

I’ve been updating MVOW to use Vue.js 3, and it’s been a bit of a chore. The application is pretty large, so there have been a lot of changes. I ran into one problem that had me stumped for a while. A brief description of the problem is that I would turn some overlays on, and they showed up. For example, a colour-coded map of the length of time since the last sale. Then I turned them off, and they disappeared. Then I zoomed out, and they reappeared.

That was pretty perplexing.

I checked the code for turning the overlays off, and sure enough, I was using setMap(null) for each of the thematic overlays. I set the entry in my map of overlays to null for good measure, and then deleted the entry from the map. That didn’t help, and still the overlays came back. In fact, if I showed them again, and then turned them off, and then changed the zoom again, they came back twice (they’re semi-transparent, and they came back less transparent).

So, I figured, somewhere there are copies of the overlays that aren’t being deleted.

Clutching at straws, I changed the definition of the overlay map from:

const thematicOverlays = ref({})

to:

const thematicOverlays = {}

… and the problem went away.

Of course, I had to change a bunch of thematicOverlays.value into thematicOverlays, but that was pretty simple.

So it seems like there is something about the implementation of the reactivity in Vue.js 3 that is keeping extra copies of references to the elements in objects that have been declared as reactive.

Traps for new players.

Bootstrap list views with badges as links

I’m using a Bootstrap list-group containing recently-used search criteria. The user can click a search criteria set in order to re-run the search. However, this wasn’t good enough for the users, because they wanted (reasonably enough) to tweak their search criteria, so I figured that the smart thing to do would be to add a badge to the list-group-items with the caption “edit”. Clicking this would bring up the search criteria editor, and they could tweak to their hearts’ content. Something that looks like this:

searching

Problem #1: the list-group-item was an anchor element, and these can’t be nested, so I couldn’t put the new “edit” anchor inside the badge.

Problem #2: I didn’t want to make the human-readable version of the search into a link because that way, the link wouldn’t cover the whole width of the list-group-item.

What I ended up doing in the PHP was this (I’m using Yii2):

<div class="list-group">
  <?php
  foreach ($recentSearches as $search)
  {
    $edit = Html::a('edit', $search->editUrl);
    $badge = Html::tag('span', $edit, [
      'class' => 'badge',
    ]);

    $clickableCond = Html::tag('div', $search->hrConditions, [
      'condhash' => $search->cond_hash,
    ]);

    echo Html::tag('span', $badge . $clickableCond, [
      'class' => 'list-group-item',
    ]);
  }
  ?>
</div>

This produced a structure that looks like this (roughly):

<div class="list-group">
  <span class="list-group-item">
    <span class="badge">
      <a href="/properties/search?condhash=9fe8">edit</a>
    </span>
    <div condhash="9fe8">
      District EQUALS DISTNAME<br>
      AND Street name CONTAINS STREETNAME<br>
      AND Suburb EQUALS SUBURBNAME<br>
      AND Cancelled = NO
    </div>
  </span>
</div>

With the judicious application of the following styles and script, I achieved what I was looking for.

<style>
span.list-group-item span.badge a {
    color: white;
}
span.list-group-item span.badge a:hover {
    text-decoration: none;
}
span.list-group-item {
    color: #555;
}
span.list-group-item:hover {
    background-color: #f5f5f5;
    cursor: pointer;
}
</style>

<script>
$(document).ready(function() {
  var sel = 'span.list-group-item div[condhash]';
  $(sel).unbind().bind('click', function(evt) {
    var url1 = "/properties/index?condhash=";
    var url2 = $(this).attr('condhash');
    window.location.href = url1 + url2;
    return false;
  });
});
</script>

Annoying display before styles applied

Sometimes, you’ll write a piece of markup for a web page, and surprisingly, it works. Well, almost. If you close your eyes, hit F5, wait a second or so, and then look … it’s perfect! However, if you watch the page load, you can find things being rendered before final styling is applied.

The worst culprit for this sort of thing is when tabs are being used. jQuery or Bootstrap tabs, in particular. You see, your tab set is really a collection of lists and/or divs that are rendered within the page initially without styling. So everything is visible first up. To be blatantly obvious, all of your tab content flashes on to the page, and then, when the styling is applied, all except the active tab disappear.

In case you’re wondering … yes, people do care about this. So how to get around it? Well, I’ve created a CSS class called “initially-hidden”. It looks like this:

.initially-hidden {
  display: none;
}

Then you can apply this class to anything that you want to be hidden while the page is loaded. For instance (using Bootstrap’s tab code):

<div role="tabpanel" class="initially-hidden">
  <!-- Nav tabs -->
  <ul class="nav nav-tabs" role="tablist">
    <li role="presentation" class="active"><a href="#home" aria-controls="home" role="tab" data-toggle="tab">Home</a></li>
    <li role="presentation"><a href="#profile" aria-controls="profile" role="tab" data-toggle="tab">Profile</a></li>
    <li role="presentation"><a href="#messages" aria-controls="messages" role="tab" data-toggle="tab">Messages</a></li>
    <li role="presentation"><a href="#settings" aria-controls="settings" role="tab" data-toggle="tab">Settings</a></li>
  </ul>

  <!-- Tab panes -->
  <div class="tab-content">
    <div role="tabpanel" class="tab-pane active" id="home">...</div>
    <div role="tabpanel" class="tab-pane" id="profile">...</div>
    <div role="tabpanel" class="tab-pane" id="messages">...</div>
    <div role="tabpanel" class="tab-pane" id="settings">...</div>
  </div>

</div>

If you leave things as they are, you’ll never see the tab, so what you have so far is (at best) a sub-optimal solution.

You also need this bit of code at the end of your page:

jQuery(document).ready(function() {
  $('.initially-hidden').show();
});

So at the end, when the page is fully loaded (including the style sheets), your component will be shown in it’s desired form.

Automatic position when inserting rows

I have a need to make sure that there is always a position value when adding a row to a table. In this case, it’s an attachment position. So this query creates a trigger function (PostgreSQL) that will set the ‘posn’ value appropriately whenever I insert a row.

CREATE OR REPLACE FUNCTION tf_auto_position()
      RETURNS trigger AS
$BODY$
    DECLARE
        nextpos INTEGER;
    BEGIN
        EXECUTE 'SELECT COALESCE(MAX(posn), 0) + 1 FROM ' || TG_TABLE_NAME
            INTO nextpos
        NEW.posn = nextpos;
        RETURN NEW;
    END;
$BODY$
    LANGUAGE plpgsql VOLATILE;

And this is how the trigger is applied to a table:

CREATE TRIGGER ins_attachments
    BEFORE INSERT ON attachments FOR EACH ROW
    EXECUTE PROCEDURE tf_auto_position();

Note that the table must have a ‘posn’ column.

I’ve also had the situation where I’ve had to set the position for rows that are children of another table’s rows. In this case, the EXECUTE statement changes to something like this:

EXECUTE 'SELECT COALESCE(MAX(posn), 0) + 1 FROM ' || TG_TABLE_NAME || ' WHERE foreign_id=$1'
    INTO nextpos
    USING NEW.foreign_id;