Difference between revisions of "Widget:DCTList"

From LINKS Community Center
Jump to: navigation, search
 
(43 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 = Array.from(document.querySelectorAll('#business-model-filter input[type="checkbox"]:checked'))
            const selectedBModels = bmOptions.filter(checkbox => checkbox.checked).map(checkbox => checkbox.value);
+
                .map(checkbox => checkbox.value);
             filterState.businessModel = selectedBModels.length === bmOptions.length ? undefined : selectedBModels;
+
             if (selectedBModels.length > 0) filterState.businessModel = selectedBModels;
  
             filterState.usedByDmo = document.getElementById('used-by-practitioners').checked ? 'yes' : undefined;
+
             if (document.getElementById('used-by-practitioners').checked) filterState.usedByDmo = 'yes';
             filterState.hasUC = document.getElementById('has-use-case').checked ? 'yes' : undefined;
+
             if (document.getElementById('has-use-case').checked) filterState.hasUC = 'yes';
 
+
             if (document.getElementById('show-archived').checked) filterState.showArchived = 'yes';
 
 
             // const dmUseOptions = Array.from(document.querySelectorAll('#dm-use-filter input[type="checkbox"]'));
 
            // const selectedDmUseOptions = dmUseOptions.filter(checkbox => checkbox.checked).map(checkbox => checkbox.value);
 
 
 
            // 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 788: 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 799: 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 907: Line 894:
 
         <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>The overall goal of the Social Media and Crowdsourcing (SMCS) Technologies Library is to face the growing
                 heterogeneous use of technologies in disasters and the overwhelming number of technologies on the market. It
+
                 heterogeneous use of technologies in disasters and the overwhelming number of technologies on the
                 gathers and structures information about existing technologies to provide an up-to-date overview and thus
+
                market. It
                 support the selection of suitable technologies.</p>
+
                 gathers and structures information about existing technologies to provide an up-to-date overview and
 +
                thus
 +
                 support the selection of suitable technologies.
 +
            </p>
 
             <p>
 
             <p>
                 You can use the filters to identify relevant technologies according to your needs and then click on the name of
+
                 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.
 
                 the technology to get further information.
 
             </p>
 
             </p>
Line 928: 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 950: 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 996: 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 1,004: 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!