Difference between revisions of "Widget:DCTList"

From LINKS Community Center
Jump to: navigation, search
 
(45 intermediate revisions by the same user not shown)
Line 1: Line 1:
<noinclude>Current version of the DCT List.<br><span style="color: red; font-weight: bold;">Not ready for production!</span></noinclude>
+
<noinclude>DCT list widget.<br><span style="color: red; font-weight: bold;">Currently in use &ndash; do not modify!</span></noinclude>
 
<includeonly>
 
<includeonly>
     <link href="/resources/assets/tabulator.min.css" rel="stylesheet">
+
     <link href="/resources/assets/tabulator/dist/css/tabulator.min.css" rel="stylesheet">
     <script type="text/javascript" src="/resources/assets/tabulator.min.js"></script>
+
     <script type="text/javascript" src="/resources/assets/tabulator/dist/js/tabulator.min.js"></script>
  
 
     <style>
 
     <style>
Line 261: Line 261:
 
             font-weight: 300;
 
             font-weight: 300;
 
         }
 
         }
 
 
     </style>
 
     </style>
  
Line 283: Line 282:
 
         * @property {FuncData} functions
 
         * @property {FuncData} functions
 
         * @property {string} usedByDmo
 
         * @property {string} usedByDmo
 +
        * @property {string} archived
 
         * @property {string} hasUC
 
         * @property {string} hasUC
 
         * @property {string} logo
 
         * @property {string} logo
Line 367: Line 367:
 
             const DMO_PROP = 'Used by Practitioners';
 
             const DMO_PROP = 'Used by Practitioners';
 
             const UC_PROP = 'Use Cases available';
 
             const UC_PROP = 'Use Cases available';
 +
            const ARCHIVED = 'Is Archived';
 
             const dctQuery = '[[Category:Disaster Community Technology]]'
 
             const dctQuery = '[[Category:Disaster Community Technology]]'
                 + '[[Is Archived::No]]'
+
                 // + '[[Is Archived::No]]'
 
                 + '|limit=500'
 
                 + '|limit=500'
 +
                + '|?' + ARCHIVED
 
                 + '|?' + IMG_PROP
 
                 + '|?' + IMG_PROP
 
                 + '|?' + DATASRC_PROP
 
                 + '|?' + DATASRC_PROP
Line 391: Line 393:
 
                 dct.usedByDmo = dctResult.printouts[DMO_PROP][0] === 't' ? 'yes' : 'no';    // not quite, but we only care about yes
 
                 dct.usedByDmo = dctResult.printouts[DMO_PROP][0] === 't' ? 'yes' : 'no';    // not quite, but we only care about yes
 
                 dct.hasUC = dctResult.printouts[UC_PROP][0] === 't' ? 'yes' : 'no';    // not quite, but we only care about yes
 
                 dct.hasUC = dctResult.printouts[UC_PROP][0] === 't' ? 'yes' : 'no';    // not quite, but we only care about yes
 +
                dct.archived = dctResult.printouts[ARCHIVED][0] === 't' ? 'yes' : 'no';    // not quite, but we only care about yes
  
 
                 dct.functions = new Map();
 
                 dct.functions = new Map();
Line 415: Line 418:
 
             // Passing an empty object (as with applyFilters(true)) should result in an unfiltered table.
 
             // Passing an empty object (as with applyFilters(true)) should result in an unfiltered table.
  
             let functionsCheck = true;
+
             let functionsCheck = true; // automatically pass functions check if the filter missing
 
             if (filterState.functions) {
 
             if (filterState.functions) {
 
                 // If filterState has a category but subfunctions array is empty, we only care about the category.
 
                 // If filterState has a category but subfunctions array is empty, we only care about the category.
Line 423: Line 426:
 
                 });
 
                 });
  
                 // Empty categories should only be checked by their name, nonempty - ONLY by subfunctions.
+
                 // Empty categories should only be checked by their name
                 const checkEmpty = emptyCategories.some(cat => dct.functions.has(cat));
+
                 const checkEmpty = emptyCategories.every(cat => dct.functions.has(cat));
                 const checkNonempty = nonemptyCategories.some(cat => {
+
 
 +
                // Nonempty categories should only be checked for presence of their subfunctions. Category itself is irrelevant.
 +
                 const checkNonempty = nonemptyCategories.every(cat => {
 
                     const selectedSubs = filterState.functions.get(cat);
 
                     const selectedSubs = filterState.functions.get(cat);
                     const dctCat = dct.functions.get(cat);
+
                     const dctSubs = dct.functions.get(cat);
                     return dctCat && dctCat[FUNC_KEY] && dctCat[FUNC_KEY].some(sub => selectedSubs.includes(sub));
+
                     return dctSubs && dctSubs[FUNC_KEY] && selectedSubs.every(sub => dctSubs[FUNC_KEY].includes(sub));
 
                 });
 
                 });
  
                 functionsCheck = checkEmpty || checkNonempty;
+
                 functionsCheck = checkEmpty && checkNonempty;
 +
                // TODO: Empty should no longer exist, once every category is fully listed in the filter.
 +
                // TODO: The filtering of functions therefore should be reduced to nonempty only.
 
             }
 
             }
  
            // const functionsCheck = filterState.functions
 
            //    ? dctFunctions.some(func => filterState.functions.includes(func))
 
            //    : true;
 
 
             const sourcesCheck = filterState.dataSources
 
             const sourcesCheck = filterState.dataSources
                 ? dct.dataSources.some(source => filterState.dataSources.includes(source))
+
                 ? filterState.dataSources.every(source => dct.dataSources.includes(source))
 
                 : true;
 
                 : true;
 
             const businessModelCheck = filterState.businessModel
 
             const businessModelCheck = filterState.businessModel
Line 449: Line 453:
 
                 ? filterState.hasUC === dct.hasUC
 
                 ? filterState.hasUC === dct.hasUC
 
                 : true;
 
                 : true;
             return sourcesCheck && functionsCheck && businessModelCheck && dmUseCheck && ucCheck;
+
            const archivedCheck = filterState.showArchived
 +
                ? true
 +
                : dct.archived !== 'yes';
 +
 
 +
             return sourcesCheck && functionsCheck && businessModelCheck && dmUseCheck && ucCheck && archivedCheck;
 
         }
 
         }
  
 
         function applyFilters(clear) {
 
         function applyFilters(clear) {
             if (!table) return;
+
             if (!table) return; // prevent filtering before the table is ready
  
 
             // If clear=true, pass empty object to the filter to disable it.
 
             // If clear=true, pass empty object to the filter to disable it.
Line 461: Line 469:
 
             }
 
             }
  
            /** @type {Partial<Omit<DCT, 'usedByDmo'> & { usedByDmo: string[]>}} */
 
 
             const filterState = {};
 
             const filterState = {};
  
             if (
+
             if (document.querySelectorAll('#functions-filter input[type="checkbox"]:checked').length > 0) {
                // Don't pass the filter on if everything is selected.
 
                document.querySelectorAll('#functions-filter input[type="checkbox"]').length !==
 
                document.querySelectorAll('#functions-filter input[type="checkbox"]:checked').length
 
            ) {
 
 
                 const functionFilterBlocks = document.querySelectorAll('#functions-filter .func-filter-block');
 
                 const functionFilterBlocks = document.querySelectorAll('#functions-filter .func-filter-block');
 
                 const funcOpts = new Map();
 
                 const funcOpts = new Map();
Line 482: Line 485:
 
                 });
 
                 });
 
                 filterState.functions = funcOpts;
 
                 filterState.functions = funcOpts;
 
                // TEMPORARY! Restores original functionality. Delete after fixing the 'dataFiltered' hook.
 
                // filterState.functions = Array.from(funcOpts).flat(2);
 
 
             }
 
             }
  
             const sourceOptions = Array.from(document.querySelectorAll('#data-source-filter input[type="checkbox"]'));
+
             const selectedSources = Array.from(document.querySelectorAll('#data-source-filter input[type="checkbox"]:checked'))
            const selectedSources = sourceOptions.filter(checkbox => checkbox.checked).map(checkbox => checkbox.value);
+
                .map(checkbox => checkbox.value);
             filterState.dataSources = selectedSources.length === sourceOptions.length ? undefined : selectedSources;
+
             if (selectedSources.length > 0) filterState.dataSources = selectedSources;
 
 
            const bmOptions = Array.from(document.querySelectorAll('#business-model-filter input[type="checkbox"]'));
 
            const selectedBModels = bmOptions.filter(checkbox => checkbox.checked).map(checkbox => checkbox.value);
 
            filterState.businessModel = selectedBModels.length === bmOptions.length ? undefined : selectedBModels;
 
 
 
            filterState.usedByDmo = document.getElementById('used-by-practitioners').checked ? 'yes' : undefined;
 
            filterState.hasUC = document.getElementById('has-use-case').checked ? 'yes' : undefined;
 
  
 +
            const selectedBModels = Array.from(document.querySelectorAll('#business-model-filter input[type="checkbox"]:checked'))
 +
                .map(checkbox => checkbox.value);
 +
            if (selectedBModels.length > 0) filterState.businessModel = selectedBModels;
  
             // const dmUseOptions = Array.from(document.querySelectorAll('#dm-use-filter input[type="checkbox"]'));
+
             if (document.getElementById('used-by-practitioners').checked) filterState.usedByDmo = 'yes';
             // const selectedDmUseOptions = dmUseOptions.filter(checkbox => checkbox.checked).map(checkbox => checkbox.value);
+
             if (document.getElementById('has-use-case').checked) filterState.hasUC = 'yes';
 
+
             if (document.getElementById('show-archived').checked) filterState.showArchived = 'yes';
             // filterState.usedByDmo = selectedDmUseOptions.length === dmUseOptions.length ? undefined : selectedDmUseOptions;
 
  
 
             table.setFilter(dctFilter, filterState);
 
             table.setFilter(dctFilter, filterState);
Line 515: Line 510:
 
             Array.from(functionsData).forEach(([fnCat, fnInfo], index) => {
 
             Array.from(functionsData).forEach(([fnCat, fnInfo], index) => {
 
                 const identifier = 'func-filter-' + escapeAttr(fnCat);
 
                 const identifier = 'func-filter-' + escapeAttr(fnCat);
                 funcFilterHtml +=
+
                 funcFilterHtml +=  
 
                     `<div class="func-filter-block">
 
                     `<div class="func-filter-block">
 
                         <div>
 
                         <div>
                             <input type="checkbox" checked id="${identifier}" value="${fnCat}" class="func-cat">
+
                             <input type="checkbox" id="${identifier}" value="${fnCat}" class="func-cat">
                             <label for="${identifier}"><img src="${fnImages[fnCat]}"> ${fnCat}</label>
+
                             <label for="${identifier}" title="${fnInfo[DESC_KEY]}"><img src="${fnImages[fnCat]}"> ${fnCat}</label>
 
                         </div>`;
 
                         </div>`;
  
 
                 // add subfunctions  
 
                 // add subfunctions  
                 if (index < 4) {
+
                 funcFilterHtml += '<div class="subfunc-filter-block">';
                    funcFilterHtml += '<div class="subfunc-filter-block">';
+
                for (const func of fnInfo.functions) {
                    for (const func of fnInfo.functions) {
+
                    const subfuncId = 'subfunc-filter-' + escapeAttr(func);
                        const subfuncId = 'subfunc-filter-' + escapeAttr(func);
+
                    funcFilterHtml +=
                        funcFilterHtml +=
+
                        `<div>
                            `<div>
+
                            <input type="checkbox" id="${subfuncId}" value="${func}">
                                <input type="checkbox" checked id="${subfuncId}" value="${func}">
+
                            <label for="${subfuncId}">${func}</label>
                                <label for="${subfuncId}">${func}</label>
+
                        </div>`;
                            </div>`;
 
                    }
 
                    funcFilterHtml += '</div>';
 
 
                 }
 
                 }
 +
                funcFilterHtml += '</div>';
 +
 
                 funcFilterHtml += '</div>';
 
                 funcFilterHtml += '</div>';
 
             });
 
             });
Line 559: Line 553:
 
                     return acc +
 
                     return acc +
 
                         '<div ' + (idx === 0 ? ' class="filter-group-start">' : '>') +
 
                         '<div ' + (idx === 0 ? ' class="filter-group-start">' : '>') +
                         '<input type="checkbox" id="filter-' + identifier + '" value="' + curr.name + '" checked>' +
+
                         '<input type="checkbox" id="filter-' + identifier + '" value="' + curr.name + '">' +
 
                         '<label for="filter-' + identifier + '"><img src="' + curr.image + '"> ' + curr.name + '</label></div>'
 
                         '<label for="filter-' + identifier + '"><img src="' + curr.image + '"> ' + curr.name + '</label></div>'
 
                 }, '');
 
                 }, '');
Line 571: Line 565:
 
                 const identifier = escapeAttr(curr);
 
                 const identifier = escapeAttr(curr);
 
                 return acc
 
                 return acc
                     + '<div><input type="checkbox" checked id="bm-filter-' + identifier
+
                     + '<div><input type="checkbox" id="bm-filter-' + identifier
 
                     + '" value="' + curr + '">'
 
                     + '" value="' + curr + '">'
 
                     + '<label for="bm-filter-' + identifier + '">' + curr
 
                     + '<label for="bm-filter-' + identifier + '">' + curr
Line 577: Line 571:
 
             }, '');
 
             }, '');
 
             document.getElementById('business-model-filter').innerHTML = pricingFilterHtml;
 
             document.getElementById('business-model-filter').innerHTML = pricingFilterHtml;
 
            // const dmUse = ['Yes', 'Use Case', 'Unknown'];
 
            // let dmUseFilterHtml = dmUse.reduce((acc, curr) => {
 
            //    const identifier = escapeAttr(curr);
 
            //    return acc
 
            //        + '<div><input type="checkbox" checked id="dm-use-filter-' + identifier
 
            //        + '" value="' + curr + '">'
 
            //        + '<label for="dm-use-filter-' + identifier + '">' + curr + '</label></div>'
 
            // }, '');
 
            // document.getElementById('dm-use-filter').innerHTML = dmUseFilterHtml;
 
  
 
             // Set up table.
 
             // Set up table.
Line 597: Line 581:
 
                         field: 'name',
 
                         field: 'name',
 
                         minWidth: 300, // required for responsiveness when using fitColumns
 
                         minWidth: 300, // required for responsiveness when using fitColumns
 +
                        widthGrow: 2,
 
                         formatter: function (cell) {
 
                         formatter: function (cell) {
 
                             /** @type {DCT} */
 
                             /** @type {DCT} */
 
                             const dct = cell.getData();
 
                             const dct = cell.getData();
                             let out = '<a href="' + dct.url + '">' + dct.name + '</a><br>';
+
                             let out = '<a href="' + dct.url + '" translate="no">' + dct.name + '</a><br>';
 +
                            if (dct.archived.toLowerCase() === 'yes') {
 +
                                out += '<small><span class="badge lcc-badge-grey">Archived</span></small> ';
 +
                            }
 
                             if (dct.businessModel.includes(FREE_KEY)) {
 
                             if (dct.businessModel.includes(FREE_KEY)) {
                                 out += '<small><span class="badge badge-success">' + FREE_KEY + '</span></small> ';
+
                                 out += '<small><span class="badge lcc-badge-green">' + FREE_KEY + '</span></small> ';
 
                             }
 
                             }
 
                             if (dct.businessModel.includes(FREE_PLAN_KEY)) {
 
                             if (dct.businessModel.includes(FREE_PLAN_KEY)) {
                                 out += '<small><span class="badge badge-success">' + FREE_PLAN_KEY + '</span></small> ';
+
                                 out += '<small><span class="badge lcc-badge-green">' + FREE_PLAN_KEY + '</span></small> ';
 
                             }
 
                             }
 
                             if (dct.usedByDmo.toLowerCase() === 'yes') {
 
                             if (dct.usedByDmo.toLowerCase() === 'yes') {
                                 out += '<small><span class="badge badge-danger">Used by practitioners</span></small> ';
+
                                 out += '<small><span class="badge lcc-badge-red">Used by practitioners</span></small> ';
 
                             }
 
                             }
 
                             if (dct.hasUC.toLowerCase() === 'yes') {
 
                             if (dct.hasUC.toLowerCase() === 'yes') {
                                 out += '<small><span class="badge badge-warning">Use case</span></small> ';
+
                                 out += '<small><span class="badge lcc-badge-purple">Use case available</span></small> ';
 
                             }
 
                             }
  
Line 620: Line 608:
 
                         title: 'Functions',
 
                         title: 'Functions',
 
                         field: 'functions',
 
                         field: 'functions',
                         minWidth: 300, // required for responsiveness when using fitColumns
+
                         minWidth: 200, // required for responsiveness when using fitColumns
 
                         cssClass: 'functions-cell',
 
                         cssClass: 'functions-cell',
 
                         formatter: function (cell) {
 
                         formatter: function (cell) {
 
                             return Array.from(cell.getValue().keys())
 
                             return Array.from(cell.getValue().keys())
                                 .map(fn => `<img class="func-img" src="${fnImages[fn]}" data-value="${fn}" alt="${fn}" title="${fn}">`)
+
                                 .map(fn => `<img class="func-img"  
 +
                                    src="${fnImages[fn]}"  
 +
                                    data-value="${fn}"  
 +
                                    alt="${fn}"  
 +
                                    title="${fn}\n\n${functionsData.get(fn)[DESC_KEY]}">`
 +
                                )
 
                                 .join('');
 
                                 .join('');
 
                         }
 
                         }
Line 672: Line 665:
  
 
                 if (encoded) {
 
                 if (encoded) {
                     const action = JSON.parse(decodeURIComponent(atob(encoded)));
+
                     const actions = JSON.parse(decodeURIComponent(atob(encoded)));
  
                     const filter = action.filter;
+
                     const filter = actions.filter;
 
                     if (filter) {
 
                     if (filter) {
 
                         // Functions filter
 
                         // Functions filter
 
                         const functions = filter.functions;
 
                         const functions = filter.functions;
 
                         if (functions) {
 
                         if (functions) {
                             Object.keys(functions).forEach(fnCat => {
+
                             Object.keys(functions).forEach(subfun => {
                                 document.getElementById('func-filter-' + escapeAttr(fnCat))
+
                                 const subfunEl = document.getElementById('subfunc-filter-' + escapeAttr(subfun));
                                    .closest('.func-filter-block')
+
                                subfunEl.checked = !!functions[subfun];
                                    .querySelectorAll('input[type="checkbox"]').forEach(box => box.checked = functions[fnCat]);
+
                                subfunEl.dispatchEvent(new Event('change', { bubbles: true }));
 
                             });
 
                             });
  
Line 698: Line 691:
 
                     // ...
 
                     // ...
 
                 }
 
                 }
 +
 +
                applyFilters();
 
             });
 
             });
  
Line 715: Line 710:
 
                     !filter.type.dataSources &&
 
                     !filter.type.dataSources &&
 
                     !filter.type.businessModel &&
 
                     !filter.type.businessModel &&
                     !filter.type.usedByDmo
+
                     !filter.type.usedByDmo &&
 +
                    !filter.type.hasUC
 
                 ) { summary.textContent = 'No filter. Showing all results.'; }
 
                 ) { summary.textContent = 'No filter. Showing all results.'; }
 
                 else {
 
                 else {
Line 745: Line 741:
 
                     if (filter.type.usedByDmo) {
 
                     if (filter.type.usedByDmo) {
 
                         summaryHtml += '<tr><td><strong>Used by practitioners</strong></td><td>Yes</td></tr>';
 
                         summaryHtml += '<tr><td><strong>Used by practitioners</strong></td><td>Yes</td></tr>';
 +
                    }
 +
                    if (filter.type.hasUC) {
 +
                        summaryHtml += '<tr><td><strong>Use case available</strong></td><td>Yes</td></tr>';
 
                     }
 
                     }
 
                     summaryHtml += '</table>';
 
                     summaryHtml += '</table>';
Line 784: Line 783:
 
                     // If no subfunctions are selected, deactivate the category. Activate otherwise.
 
                     // If no subfunctions are selected, deactivate the category. Activate otherwise.
 
                     const checkedSubs = Array.from(subfunctions).filter(sub => sub.checked).length;
 
                     const checkedSubs = Array.from(subfunctions).filter(sub => sub.checked).length;
                     if (checkedSubs > 0) { category.checked = true; } else { category.checked = false; }
+
                     category.checked = checkedSubs > 0;
 
                 }
 
                 }
  
Line 795: Line 794:
 
                 applyFilters();
 
                 applyFilters();
 
             }, { passive: true });
 
             }, { passive: true });
            // document.getElementById('dm-use-filter').addEventListener('change', event => {
 
            //    // hack to check "Yes" as well when "Yes with Use Case" is selected
 
            //    if (event.target.id == "dm-use-filter-Yes-with-Use-Case" && event.target.checked) {
 
            //        document.getElementById('dm-use-filter-Yes').checked = true
 
            //    }
 
 
            //    applyFilters();
 
            // }, { passive: true });
 
 
             document.getElementById('bool-filters').addEventListener('change', event => {
 
             document.getElementById('bool-filters').addEventListener('change', event => {
 
                 applyFilters();
 
                 applyFilters();
Line 902: Line 893:
 
         </h1>
 
         </h1>
 
         <div id="dct-intro">
 
         <div id="dct-intro">
 
+
             <p>The overall goal of the Social Media and Crowdsourcing (SMCS) Technologies Library is to face the growing
             <p>This page provides an&nbsp;overview of&nbsp;various Technologies related to Social Media and
+
                heterogeneous use of technologies in disasters and the overwhelming number of technologies on the
                 Crowdsourcing. You can use the&nbsp;filters to&nbsp;identify
+
                market. It
                relevant technologies according to your needs and then click on the name of a&nbsp;tool to&nbsp;get
+
                gathers and structures information about existing technologies to provide an up-to-date overview and
                further information.</p>
+
                 thus
 +
                support the selection of suitable technologies.
 +
            </p>
 +
            <p>
 +
                You can use the filters to identify relevant technologies according to your needs and then click on the
 +
                name of
 +
                the technology to get further information.
 +
            </p>
 
         </div>
 
         </div>
  
Line 921: Line 919:
 
             </div>
 
             </div>
  
             <h2 style="margin-top: 2.5rem; margin-bottom: 0;">Results: <span id="result-count"></span></h2>
+
             <div style="display: flex; justify-content: space-between; align-items: center; margin-top: 2.5rem;">
 +
                <h2 style="margin-bottom: 0;">Results: <span id="result-count"></span></h2>
 +
                <div><a href="/index.php/Form:Disaster_Community_Technology" style="color: var(--links-blue); font-size: 1.5em; font-variant:small-caps">Add new technology</a></div>
 +
            </div>
  
 
             <div id="dct-filters">
 
             <div id="dct-filters">
Line 943: Line 944:
 
                 </h2>
 
                 </h2>
 
                 <div style="text-align: center;">
 
                 <div style="text-align: center;">
                     <button class="large-button" type="button" onclick="clearFilters()">Show All</button>
+
                     <button class="large-button" type="button" onclick="clearFilters()">Clear Filters</button>
 
                 </div>
 
                 </div>
 
                 <div class="filter-wrapper">
 
                 <div class="filter-wrapper">
Line 989: Line 990:
 
                     </div>
 
                     </div>
 
                 </div> -->
 
                 </div> -->
                 <div id="bool-filters" style="border-top: 1px solid var(--links-blue); margin-top: 1em; padding-top: 1em;">
+
                 <div id="bool-filters"
 +
                    style="border-top: 1px solid var(--links-blue); margin-top: 1em; padding-top: 1em;">
 
                     <div>
 
                     <div>
 
                         <input type="checkbox" id="used-by-practitioners" value="yes">
 
                         <input type="checkbox" id="used-by-practitioners" value="yes">
Line 997: Line 999:
 
                         <input type="checkbox" id="has-use-case" value="yes">
 
                         <input type="checkbox" id="has-use-case" value="yes">
 
                         <label for="has-use-case">Use case available</label>
 
                         <label for="has-use-case">Use case available</label>
 +
                    </div>
 +
                    <div>
 +
                        <input type="checkbox" id="show-archived" value="yes">
 +
                        <label for="show-archived">Show archived</label>
 
                     </div>
 
                     </div>
 
                 </div>
 
                 </div>

Latest revision as of 15:30, 19 December 2023

DCT list widget.
Currently in use – do not modify!