Skip to content

Commit

Permalink
Introduce new generic filter tracker
Browse files Browse the repository at this point in the history
The new filter tracker is a generic JS mechanism for tracking changes to the results of filters and changes to the tiddlers identified by those results.

The rationale for the new mechanism is that it is a generalisation of code that is already present in the keyboard manager. The intention is to refactor the keyboard manager to use the filter tracker in due course
  • Loading branch information
Jermolene committed Jan 5, 2025
1 parent ea2426e commit e78d3ae
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 22 deletions.
87 changes: 87 additions & 0 deletions core/modules/filter-tracker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*\
title: $:/core/modules/filter-tracker.js
type: application/javascript
module-type: global
Class to track the results of a filter string
\*/
(function(){

/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";

function FilterTracker(wiki) {
this.wiki = wiki;
this.trackers = [];
this.wiki.addEventListener("change",this.handleChangeEvent.bind(this));
}

FilterTracker.prototype.handleChangeEvent = function(changes) {
this.processTrackers();
this.processChanges(changes);
};

/*
Add a tracker to the filter tracker
filterString: the filter string to track
fnEnter: function to call when a title enters the filter results. Called even if the tiddler does not actually exist. Called as (title), and should return a truthy value that is stored in the tracker as the "enterValue"
fnLeave: function to call when a title leaves the filter results. Called as (title,enterValue)
fnChange: function to call when a tiddler changes in the filter results. Only called for filter results that identify a tiddler or shadow tiddler. Called as (title,enterValue), and may optionally return a replacement enterValue
*/
FilterTracker.prototype.track = function(filterString,fnEnter,fnLeave,fnChange) {
// Add the tracker details
var index = this.trackers.length;
this.trackers.push({
filterString: filterString,
fnEnter: fnEnter,
fnLeave: fnLeave,
fnChange: fnChange,
previousResults: [], // Results from the previous time the tracker was processed
resultValues: {} // Map by title to the value returned by fnEnter
});
// Process the tracker
this.processTracker(index);
};

FilterTracker.prototype.processTrackers = function() {
for(var t=0; t<this.trackers.length; t++) {
this.processTracker(t);
}
};

FilterTracker.prototype.processTracker = function(index) {
var tracker = this.trackers[index],
results = this.wiki.filterTiddlers(tracker.filterString);
// Process the results
$tw.utils.each(results,function(title) {
if(tracker.previousResults.indexOf(title) === -1 && !tracker.resultValues[title]) {
tracker.resultValues[title] = tracker.fnEnter(title) || true;
}
});
$tw.utils.each(tracker.previousResults,function(title) {
if(results.indexOf(title) === -1 && tracker.resultValues[title]) {
tracker.fnLeave(title,tracker.resultValues[title]);
delete tracker.resultValues[title];
}
});
// Update the previous results
tracker.previousResults = results;
};

FilterTracker.prototype.processChanges = function(changes) {
for(var t=0; t<this.trackers.length; t++) {
var tracker = this.trackers[t];
$tw.utils.each(changes,function(change,title) {
if(tracker.previousResults.indexOf(title) !== -1) {
// Call the change function and if it doesn't return a value then keep the old value
tracker.resultValues[title] = tracker.fnChange(title,tracker.resultValues[title]) || tracker.resultValues[title];
}
});
}
};

exports.FilterTracker = FilterTracker;

})();
45 changes: 32 additions & 13 deletions core/modules/info/mediaquerytracker.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,36 +13,55 @@ Initialise $:/info/ tiddlers derived from media queries via
"use strict";

exports.getInfoTiddlerFields = function(updateInfoTiddlersCallback) {
var infoTiddlerFields = [];
if($tw.browser) {
// Get the media query tracker tiddlers
var trackers = $tw.wiki.getTiddlersWithTag("$:/tags/MediaQueryTracker");
$tw.utils.each(trackers,function(title) {
var tiddler = $tw.wiki.getTiddler(title);
// Functions to start and stop tracking a particular media query tracker tiddler
function track(title) {
var result = {},
tiddler = $tw.wiki.getTiddler(title);
if(tiddler) {
var mediaQuery = tiddler.fields["media-query"],
infoTiddler = tiddler.fields["info-tiddler"],
infoTiddlerAlt = tiddler.fields["info-tiddler-alt"];
if(mediaQuery && infoTiddler) {
// Evaluate and track the media query
var mqList = window.matchMedia(mediaQuery);
result.mqList = window.matchMedia(mediaQuery);
function getResultTiddlers() {
var value = mqList.matches ? "yes" : "no",
tiddlers = [{title: infoTiddler, text: value}];
var value = result.mqList.matches ? "yes" : "no",
tiddlers = [];
tiddlers.push({title: infoTiddler, text: value});
if(infoTiddlerAlt) {
tiddlers.push({title: infoTiddlerAlt, text: value})
}
return tiddlers;
};
infoTiddlerFields.push.apply(infoTiddlerFields,getResultTiddlers());
mqList.addEventListener("change",function(event) {
updateInfoTiddlersCallback(getResultTiddlers());
result.handler = function(event) {
updateInfoTiddlersCallback(getResultTiddlers());
});
};
result.mqList.addEventListener("change",result.handler);
}
}
});
return result;
}
function untrack(enterValue) {
if(enterValue.mqList && enterValue.handler) {
enterValue.mqList.removeEventListener("change",enterValue.handler);
}
}
// Track media query tracker tiddlers
function fnEnter(title) {
return track(title);
}
function fnLeave(title,enterValue) {
untrack(enterValue);
}
function fnChange(title,enterValue) {
untrack(enterValue);
return track(title);
}
$tw.filterTracker.track("[all[tiddlers+shadows]tag[$:/tags/MediaQueryTracker]!is[draft]]",fnEnter,fnLeave,fnChange);
}
return infoTiddlerFields;
return [];
};

})();
11 changes: 11 additions & 0 deletions core/modules/startup/load-modules.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ Load core modules
exports.name = "load-modules";
exports.synchronous = true;

// Set to `true` to enable performance instrumentation
var PERFORMANCE_INSTRUMENTATION_CONFIG_TITLE = "$:/config/Performance/Instrumentation";

exports.startup = function() {
// Load modules
$tw.modules.applyMethods("utils",$tw.utils);
Expand All @@ -35,6 +38,14 @@ exports.startup = function() {
$tw.macros = $tw.modules.getModulesByTypeAsHashmap("macro");
$tw.wiki.initParsers();
$tw.Commander.initCommands();
// --------------------------
// The rest of the startup process here is not strictly to do with loading modules, but are needed before other startup
// modules are executed. It is easier to put them here than to introduce a new startup module
// --------------------------
// Set up the performance framework
$tw.perf = new $tw.Performance($tw.wiki.getTiddlerText(PERFORMANCE_INSTRUMENTATION_CONFIG_TITLE,"no") === "yes");
// Kick off the filter tracker
$tw.filterTracker = new $tw.FilterTracker($tw.wiki);
};

})();
5 changes: 0 additions & 5 deletions core/modules/startup/startup.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@ exports.name = "startup";
exports.after = ["load-modules"];
exports.synchronous = true;

// Set to `true` to enable performance instrumentation
var PERFORMANCE_INSTRUMENTATION_CONFIG_TITLE = "$:/config/Performance/Instrumentation";

var widget = require("$:/core/modules/widgets/widget.js");

exports.startup = function() {
Expand Down Expand Up @@ -57,8 +54,6 @@ exports.startup = function() {
}
// Initialise version
$tw.version = $tw.utils.extractVersionInfo();
// Set up the performance framework
$tw.perf = new $tw.Performance($tw.wiki.getTiddlerText(PERFORMANCE_INSTRUMENTATION_CONFIG_TITLE,"no") === "yes");
// Create a root widget for attaching event handlers. By using it as the parentWidget for another widget tree, one can reuse the event handlers
$tw.rootWidget = new widget.widget({
type: "widget",
Expand Down
4 changes: 2 additions & 2 deletions editions/tw5.com/tiddlers/mechanisms/InfoMechanism.tid
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ System tiddlers in the namespace `$:/info/` are used to expose information about
|[[$:/info/browser/language]] |<<.from-version "5.1.20">> Language as reported by browser (note that some browsers report two character codes such as `en` while others report full codes such as `en-GB`) |
|[[$:/info/browser/screen/width]] |Screen width in pixels |
|[[$:/info/browser/screen/height]] |Screen height in pixels |
|[[$:/info/browser/darkmode]] |<<.from-version "5.3.6">> Is dark mode preferred? ("yes" or "no") |
|[[$:/info/darkmode]] |<<.deprecated-since "5.3.6">> Alias for $:/info/browser/darkmode |
|[[$:/info/browser/darkmode]] |<<.from-version "5.3.7">> Is dark mode preferred? ("yes" or "no") |
|[[$:/info/darkmode]] |<<.deprecated-since "5.3.7">> Alias for $:/info/browser/darkmode |
|[[$:/info/node]] |Running under [[Node.js]]? ("yes" or "no") |
|[[$:/info/url/full]] |<<.from-version "5.1.14">> Full URL of wiki (eg, ''<<example full>>'') |
|[[$:/info/url/host]] |<<.from-version "5.1.14">> Host portion of URL of wiki (eg, ''<<example host>>'') |
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
title: MediaQueryTrackerMechanism
tags: Mechanisms

The media query tracker mechanism allows you to define [[custom CSS media queries|https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_media_queries/Using_media_queries]] to be bound to a specified [[info|InfoMechanism]] tiddler. The info tiddler will be dynamically update to reflect the current state of the media query.
<<.from-version "5.3.7">> The media query tracker mechanism allows you to define [[custom CSS media queries|https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_media_queries/Using_media_queries]] to be bound to a specified [[info|InfoMechanism]] tiddler. The info tiddler will be dynamically update to reflect the current state of the media query.

Adding or modifying a tiddler tagged $:/tags/MediaQueryTracker will only take effect when the wiki is reloaded
Adding or modifying a tiddler tagged $:/tags/MediaQueryTracker takes effect immediately.

The media queries are always applied against the main window. This is relevant for viewport related media queries such as `min-width` which will always respect the main window and ignore the sizes of any external windows.

0 comments on commit e78d3ae

Please sign in to comment.