MediaWiki:PolicyOverview.js: Difference between revisions
No edit summary  | 
				No edit summary  | 
				||
| Line 165: | Line 165: | ||
             var group = actions[year]  |              var group = actions[year]  | ||
             var year_container = $('<div>', {id: 'year' + year, class: 'year-container'})  |              var year_container = $('<div>', {id: 'year' + year, class: 'year-container'})  | ||
             var year_header_container = $('<div>', {class: 'sticky-top', style:'z-index: 5; background-color:   |              var year_header_container = $('<div>', {class: 'sticky-top', style:'z-index: 5; background-color: #00426b'})  | ||
             var year_header = $('<div>', {class: 'year-header '}).html('<h1 class="year">'+year+'</h1>')  |              var year_header = $('<div>', {class: 'year-header '}).html('<h1 class="year">'+year+'</h1>')  | ||
Revision as of 18:32, 11 December 2022
ORIGIN = 'http://localhost:8000'
API_URL = 'https://164-92-142-113.nip.io/api.php'
var actions = [];
var progressItems = {};
var orgs = {}
var org_name = null
var lang = 'fi'
var highlightedTerm = null
var highlights = null
var hightlightIndex = 0
class_map = {
    'Tutkimusjulkaisut': 'field-border-1',
    'Toimintakulttuuri': 'field-border-2',
    'Oppiminen' : 'field-border-3',
    'Data' : 'field-border-4'
}
color_map = {
    'Tutkimusjulkaisut': '#007ECA',
    'Toimintakulttuuri': '#53BD9D',
    'Oppiminen' : '#93A0FF',
    'Data' : '#003EAD'
}
const observer = new IntersectionObserver( 
    ([e]) => e.target.classList.toggle('stuck', e.intersectionRatio < 1),
    {threshold: [1]}
  );
  
//observer.observe(document.querySelector('.rowgroup-header'));
function prevMatch() {
    var searchword = $("#searchtxt").val();
    if(searchword != '') {
        $('#overview_actions').addClass('search')
        if( hightlightIndex > 0) {
            hightlightIndex--
        }
        replaceText()
    
    }
}
function nextMatch() {
    var searchword = $("#searchtxt").val();
    if(searchword != '') {
        $('#overview_actions').addClass('search')
        replaceText()
        if(hightlightIndex < highlights.length -1) {
            hightlightIndex++
        }
    }
}
function replaceText() {
    var searchword = $("#searchtxt").val();
    console.log(hightlightIndex)
    if(searchword != highlightedTerm) {
        highlightedTerm = searchword
        hightlightIndex = 0
        $(".card-body").find(".highlight").removeClass("highlight");
        var custfilter = new RegExp(searchword, "ig");
        var repstr = "<span class='highlight'>" + searchword + "</span>";
        if (searchword != "") {
            $('.card-body').each(function() {
                $(this).html($(this).html().replace(custfilter, repstr));
            })
        }
        highlights = $('.highlight')
        if(highlights.length >0) {
            highlights[hightlightIndex].scrollIntoView()
            $('html, body').animate({scrollTop: '-=70px'}, 0);
    
        }
    }
    else {        
        highlights[hightlightIndex].scrollIntoView()
        $('html, body').animate({scrollTop: '-=70px'}, 0);        
    }
    $('#numOfMatches').text((hightlightIndex + 1) + '/' + highlights.length)
}
function clearSearch() {
    $("#searchtxt").val('');
    $('#overview_actions').removeClass('search')
    $(".card-body").find(".highlight").removeClass("highlight");
    hightlightIndex = 0
    $('#numOfMatches').text('')
}
function getSelectedCheckboxes(selector) {
    var selected = new Set()
    $(selector + ' input:checked').each(function () {
        selected.add($(this).val());
    });
    return selected
}
function getSelectedRadio(selector) {
    return $(selector + ' input:checked').val()
}
function getSelectedOptions(selector) {
    var selected = new Set();
    $(selector + ' option:selected').each(function () {
        selected.add($(this).val());
    });
    return selected
}
function updateActionView() {
    $('#overview_actions').text('')
    var domains = getSelectedOptions('#domains')
    var years = getSelectedOptions('#years')
    var actors = getSelectedOptions('#actors')
    var orgs = getSelectedOptions('#orgs')
    console.log(domains)
    console.log(years)
    console.log(orgs)
    console.log(actors)
    if(actors.size > 0) {
        $('#intro').hide()
    }
    else {
        $('#intro').show()
    }
    // filter actions based on selections 
    filtered_actions = filterActions(domains, years, actors)
    console.log(filtered_actions)
    // render 
    var column_grouping = 'none' //getSelectedOptions('#col_grouping').values().next().value
    var row_grouping = getSelectedOptions('#row_grouping').values().next().value
    console.log(row_grouping)
    console.log(column_grouping)
    grouped_actions = groupActions(filtered_actions, column_grouping, row_grouping)
    renderActions(grouped_actions, column_grouping, row_grouping, actors)
    document.querySelectorAll('.rowgroup-header').forEach(function(e) {        
        observer.observe(e)
    });
    
}
function getSortedKeys(dictonary) {
    var sorted = [];
    for(var key in dictonary) {
        sorted[sorted.length] = key;
    }
    sorted.sort();    
    return sorted
}
function renderActions(actions, column_grouping, row_grouping, actors)  {
    for(var year = 2020; year < 2026; year++)  {
        if((''+year in actions)) {
            var group = actions[year]
            var year_container = $('<div>', {id: 'year' + year, class: 'year-container'})
            var year_header_container = $('<div>', {class: 'sticky-top', style:'z-index: 5; background-color: #00426b'})
            var year_header = $('<div>', {class: 'year-header '}).html('<h1 class="year">'+year+'</h1>')
            year_container.append(year_header_container)
            year_header_container.append(year_header)
    
            $('#nav-item-year' + year).show()
            var group = actions[year]
            if(Array.isArray(group)) {
                var header_count = $('<span>', {class: 'action_count'}).text(group.length + ' toimenpidettä')
                year_header.append(header_count)
                group.forEach(obj => {
                    action_card = renderAction(obj)
                    year_container.append(action_card)                
                })    
            }
            else {
                if (row_grouping != 'none') {
                    var rowKeys = getSortedKeys(group)
                    var rowZ = 10;
                    rowKeys.forEach(rowTitle => {
                        if(row_grouping === 'action_responsible_actor' &&  !actors.has(rowTitle)) {
                            // skip
                        }  
                        else {
                            var rowgroup_header = $('<div>', {id: 'year-title-' + year, class: 'rowgroup-header sticky-top', style:'z-index:' + rowZ}).html('<h2>'+rowTitle+'</h2>')
                            rowZ = rowZ + 1
                            year_container.append(rowgroup_header)
                            if(column_grouping != 'none') {
                                var colKeys = getSortedKeys(group[rowTitle])
                                var colContainer = $('<div>', {class:'container-fluid'})
                                var colRow = $('<div>', {class: 'row'})
                                colKeys.forEach(colTitle => {
                                    if(column_grouping === 'action_responsible_actor' &&  !actors.has(colTitle)) {
                                        // skip
                                    }else {
                                        var colCol = $('<div>', {class: 'col'})
                                        if(Array.isArray(group[rowTitle][colTitle])) {
                                            var colgroup_header = $('<div>', {class: 'colgroup-header'}).html('<h2>'+colTitle+'</h2>')
                                            colCol.append(colgroup_header)
                                            group[rowTitle][colTitle].forEach(obj => {
                                                action_card = renderAction(obj)
                                                colCol.append(action_card)                
                                            })    
                                        }
                                        colRow.append(colCol)
    
                                    }                                    
                                })
                                
                                colContainer.append(colRow)
                                year_container.append(colContainer)                            
                            }   
                            else {
                                var header_count = $('<span>', {class: 'action_count'}).text(group[rowTitle].length + ' toimenpidettä')
                                rowgroup_header.append(header_count)
                                group[rowTitle].forEach(obj => {
                                    action_card = renderAction(obj)
                                    year_container.append(action_card)                            
                                })
                            }    
                        }       
                        
                 
    
                    })
                }
                else {
                    var colKeys = getSortedKeys(group)
                    var colContainer = $('<div>', {class:'container-fluid'})
                    var colRow = $('<div>', {class: 'row'})
                    colKeys.forEach(colTitle => {
                        if(column_grouping === 'action_responsible_actor' &&  !actors.has(colTitle)) {
                            // skip
                        }          
                        else {
                            var colCol = $('<div>', {class: 'col'})
                            if(Array.isArray(group[colTitle])) {
                                var colgroup_header = $('<div>', {class: 'colgroup-header'}).html('<h2>'+colTitle+'</h2>')
                                colCol.append(colgroup_header)
                                group[colTitle].forEach(obj => {
                                    action_card = renderAction(obj)
                                    colCol.append(action_card)                
                                })    
                            }
                            colRow.append(colCol)
    
                        }              
                    })
                    
                    colContainer.append(colRow)
                    year_container.append(colContainer)
                }
            }
        }
        else {
            $('#nav-item-year' + year).hide()
        }
        $('#overview_actions').append(year_container)
    }
}
function renderAction(obj) {
    var policy_page = obj['action_id_raw'].substring(0, obj['action_id_raw'].indexOf('/'))    
    var policy_doc_link = '/index.php/' + policy_page + '#' + obj['section_type_name'] + '_' + obj['section_ordinal'] + ':_' + obj['section_name'].replaceAll(' ', '_')
    var action_card = $('<div>', {class: 'card mb-4 ' + class_map[obj.action_field]})
    var action_header = $('<div>', {class: 'card-header', style:'color: #ffffff;  background-color:' + color_map[obj.action_field]}).html(obj.action_field + ' - ' + obj['section_type_name'] + '  ' + obj.section_ordinal + '<a style="color:white" data-toggle="collapse" href="#collapse' + obj.action_id +'"><i  class="bi bi-chevron-down float-right"></i></a>')
    var action_body = $('<div>', {class: 'card-body'})
    var field_badge = $('<span>', {class: 'badge float-right', style:'background-color:' + color_map[obj.action_field]}).text(obj.action_field)
    var action_section = $('<div>', {class:'collapse', id: 'collapse' + obj.action_id }).html(
        '<h5 style="margin-top: 1em">'+ obj.section_name + '<a style="color: white" href="' + policy_doc_link + '"><i title="Avaa linjausteksti" style="margin-left: 1em;" class="bi bi-box-arrow-up-right"></i></a></h5><p> ' + obj.section_desc + '</p>'
    )
    var action_title = $('<h5>', {class: 'card-title'}).text(obj.action_name)
    //var action_title = $('<h5>', {class: 'card-title'}).html(obj.action_name+'<i class="bi bi-question-octagon-fill float-right" style="font-size: 2rem; color:'+ color_map[obj.action_field] +'" data-trigger"focus" data-container="body" data-toggle="popover" data-placement="top" data-content="' + obj.section_desc + ' "></i>')
    var action_text = $('<p>', {class: 'card-text'}).text(obj.action_description)
    var action_footer = $('<div>', {class: 'card-footer', style:'background-color: ' + color_map[obj.action_field]})
    var progressCount = 0
    if(obj.action_id in progressItems) {
        var p_items = progressItems[obj.action_id]
        var progressCount = p_items.length
    }
    if(progressCount > 0) {
        console.log(progressCount)
        var progress_button = $('<button>', {class: 'btn btn-secondary float-right', type:'button', onclick:'$("#' + obj.action_id + '").modal("show")'}).html('Edistysaskeleita<span style="margin-left: 5px" class="badge badge-dark">' + progressCount +'</span>')
        action_body.append(progress_button)
        console.log(obj)
    }
    var progress_add = $('<a>', {class:'new-progress btn btn-outline-primary float-right', style: 'color: white; border-color: white;', href:'https://164-92-142-113.nip.io/index.php/Special:FormEdit/Progress?Progress[Action]='+encodeURIComponent(obj.action_id_raw)  }).text('Add new')         
    action_card.append(action_header)
    action_card.append(action_body)
    
    action_header.append(action_section)
    action_body.append(action_title)
    action_body.append(action_text)
    //action_body.append(action_link)
    addProgressModal(obj)
    return action_card
}
function addProgressModal(action) {    
    if(action.action_id in progressItems) {
        console.log(action)
        var _progressItems = progressItems[action.action_id]
        var $modalContainer = $('<div>', {class:'modal', tabindex:'-1', role:'dialog', id:action.action_id})
        var $modal = $('<div>', {class:'modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable', role:'document'})
        var $modalContent = $('<div>', {class:'modal-content'})
        var $modalHeader = $('<div>', {class:'modal-header'})
        var $title = $('<h5>', {class:'modal-title'}).text(action.section_type_name)
        var $titleClose = $('<button>', {type:'button', class:'close', 'data-dismiss':'modal'})
        var $close = $('<span>').html('×')
        $titleClose.append($close)
        var $actionText = $('<p>', {class:'card-text'}).text((action.action_name != '' ? action.action_name + '. ' : '') + action.action_description)
        var $actionRole = $('<h6>', {class:'card-subtitle mb-2 text-muted'}).text(action.action_responsible_actor.join(','))
        var $objectiveName = $('<h5>', {class:'card-title'}).text(action.section_name)
        var $policyLink = $('<a>', {href:'#', class:'card-link'}).text(action.policy_name)
        var $modalBody = $('<div>', {class:'modal-body'})
        var $sectionType = $('<p>').text(action.section_type_name)
        //$modalBody.append($sectionType)
        $modalBody.append($objectiveName)
        //$modalBody.append($actionRole)
        //$modalBody.append($actionText)
        _progressItems.forEach(function(progressItem) {
            $modalBody.append(createProgressCard(progressItem))
        })
        $modalHeader.append($title)
        $modalHeader.append($titleClose)
        $modalContent.append($modalHeader)
        $modalContent.append($modalBody)
        $modal.append($modalContent)
        $modalContainer.append($modal)
        $('#modals').append($modalContainer)
    }
}
function createProgressCard(data) {
    var $card = $('<div>', {class:'card', style: 'margin-bottom: 1em;'})
    var $cardBody = $('<div>', {class:'card-body'})
    var $cardTitle = $('<h5>', {class: 'card-title'}).text(data.name)
    var $cardSubTitle = $('<h6>', {class:'card-subtitle mb-2 text-muted'}).text(data.date)
    var $cardSubTitle2 = $('<h6>', {class:'card-subtitle mb-2 text-muted'}).text(data.org)
    $cardText = $('<p>', {class: 'card-text'}).text(data.desc)
    $cardBody.append($cardTitle)
    $cardBody.append($cardSubTitle)
    $cardBody.append($cardSubTitle2)
    $cardBody.append($cardText)
    $card.append($cardBody)
    return $card
}
function groupActions(actions, column_grouping, row_grouping) {
    // group first by year
    groups = group_actions_by_year(actions)
    // then by row if selected
    if(row_grouping != 'none') {
        var nestedProp = null
        if (row_grouping != 'action_field') {
            nestedProp = 'fulltext'
        }
        second_level_grouping(groups, row_grouping, nestedProp)
        nestedProp = null
        if(column_grouping != 'none') {
            if (column_grouping != 'action_field') {
                nestedProp = 'fulltext'
            }
            console.log(nestedProp)
            third_level_grouping(groups, column_grouping, nestedProp)
        }
    }
    // and last by col if selected
    if(column_grouping != 'none' && row_grouping == 'none') {
        var nestedProp = null
        if (column_grouping != 'action_field') {
            nestedProp = 'fulltext'
        }
        second_level_grouping(groups, column_grouping, nestedProp)
    
    }
    console.log(groups)
    return groups
}
function group_actions_by_year(actions) {    
    return actions.reduce((groups, item) => {
        year = getActionYear(item)
        const group = (groups[year] || []);
        group.push(item);
        groups[year] = group;
        return groups;
      }, {});
}
function second_level_grouping(groups, prop, nestedProp = null) {
    console.log(nestedProp)
    Object.keys(groups).forEach(function(key) {
        var data = groups[key]
        const parents = data.reduce((parents, item) => {
            if (nestedProp != null) {
                for (const _item of item[prop]) {
                    var value = _item[nestedProp]                    
                    const c = parents[value] || []
                    c.push(item)
                    parents[value] = c                    
                } 
            }
            else {
                const parent = (parents[item[prop]] || []);
                parent.push(item);
                parents[item[prop]] = parent;
            }
            return parents               
        }, {});
        groups[key] = parents
    })
    return groups
}
function third_level_grouping(groups, prop, nestedProp = null) {
    console.log(nestedProp)
    Object.keys(groups).forEach(function(_key) {
        var second_group = groups[_key]
        Object.keys(second_group).forEach(function(key) {
            data = second_group[key]
            const parents = data.reduce((parents, item) => {
                if (nestedProp != null) {
                    for (const _item of item[prop]) {
                        var value = _item[nestedProp]                    
                        const c = parents[value] || []
                        c.push(item)
                        parents[value] = c                    
                    } 
                }
                else {
                    const parent = (parents[item[prop]] || []);
                    parent.push(item);
                    parents[item[prop]] = parent;
                }
                return parents               
    
            }, {});
            second_group[key] = parents
    
        })
    })
    return groups
}
function intersection(setA, setB) {
    const _intersection = new Set();
    for (const elem of setB) {
        if (setA.has(elem)) {
            _intersection.add(elem);
        }
    }
    return _intersection;
}
function getActionYear(action) {
    if(action.action_deadline) {
        return ''+action.action_deadline
    }
    if(action.section_deadline) {
        return ''+action.section_deadline
    }
    return ''+action.policy_endyear
}
function filterActions(domains, years, actors) {
    // actors   
    result = actions.filter(action => {
        return intersection(
            new Set(action.action_responsible_actor.map(a=>a.fulltext)), 
            actors).size > 0
    })    
    // years
    result = result.filter(action => {        
        return years.has(getActionYear(action))
    })
    // domains
    result = result.filter(action => {
        return domains.has(action.action_domain)
    })
    return result
}
/* shared */
async function updateProgressData(data) {
    console.log(data)
    items = {}
    data.forEach(function (r) {
        progressID = Object.keys(r)[0]
        obj = r[progressID]
        console.log(obj)
        date = ''
        if (obj.printouts['Date'].length > 0 && obj.printouts['Date'][0].timestamp) {
            date = new Date(parseInt(obj.printouts['Date'][0].timestamp) * 1000)
        }
        actionID = obj.printouts.Action[0].fulltext.replace('#', '').replace(/\s/g, '').replace(':', '').replace('/', '')
        progressUrl = obj.printouts.Action[0].fullurl
        name = obj.printouts.Name || ''
        desc = obj.printouts.Description || ''
        orgName = obj.printouts.OrgName || ''
        if (!(actionID in items)) {
            items[actionID] = []
        }
        items[actionID].push({
            url: obj.fullurl,
            fulltext: obj.fulltext,
            actionID: actionID,
            url: progressUrl,
            date: date,
            name: name,
            desc: desc,
            org: orgName
        })
    })
    progressItems = items
}
function updateActions(data) {
    console.log(data)
    actions = []
    data.forEach(function (r) {
        progressID = Object.keys(r)[0]
        obj = r[progressID]
        
        var section_desc = obj.printouts['sectionDesc'][0] || '' // footnotes!
        section_desc = section_desc.replace(/\[\[.*\]\]/g, "");
        var action_description = obj.printouts['Description'][0] || ''
        //action_description = action_description.replace(/<\/?[^>]+(>|$)/g, "")
        actions.push(
            {
                'policy_name': obj.printouts['policyName'][0] || '',
                'policy_endyear': obj.printouts['policyEndYear'][0] || null,
                'section_name': obj.printouts['sectionName'][0] || '',
                'section_desc': section_desc,
                'section_deadline': obj.printouts['Section deadline'][0] || null,
                'section_ordinal': obj.printouts['sectionOrdinal'][0] || null,
                'section_type': obj.printouts['sectionType'][0].fulltext || '',
                'section_type_name': obj.printouts['sectionTypeName'][0] || '',
                'action_deadline': obj.printouts['Action deadline'][0] || null,
                'action_description': action_description,
                'action_field': obj.printouts['Field'][0] || '',
                'action_domain': obj.printouts['Domain'][0].fulltext || '',
                'action_name': obj.printouts['Name'][0] || '',
                'action_responsible_actor': obj.printouts['Responsible actor'],
                'action_id': obj.fulltext.replace('#', '').replace(/\s/g, '').replace(':', '').replace('/', ''),
                'action_id_raw': obj.fulltext
            }
        )
    })
}
function updateOrgs(data) {
    console.log(data)
    actions = []
    data.forEach(function (r) {
        progressID = Object.keys(r)[0]
        obj = r[progressID]
        console.log(obj)
        var businessID = obj.printouts['BusinessID'][0] || ''
        // handle localized string queries and separate properties -  todo refactor this out
        var actors = []
        var actor_ids = obj.printouts['Responsible actor']
        var actors_fi = obj.printouts['actors_fi']
        var actors_sv = obj.printouts['actors_sv']
        var actors_en = obj.printouts['actors_en']
        for (let i = 0; i < actor_ids.length; i++) {
            actors.push({
                id: actor_ids[i].fulltext,
                fi: actors_fi[i],
                sv: actors_sv[i],
                en: actors_en[i]
            })
        }
        orgs[businessID] = {
            'name': obj.printouts['Name'][0] || '',
            'businessID': businessID,
            'responsible_actors': actors,
        }
    })
    console.log(orgs)
}
function updateActors(data) {
    console.log(data)
    data.forEach(function (r) {
        id = Object.keys(r)[0]
        
        var name = r[id].printouts['Name'][0]
        $('#actors').append('<option value="'+id+'">'+name+'</option>')    
        console.log('test')
    })  
    
}
async function fetch_data(lang) {
    // get all Actions
console.log(lang)
    const actionQuery = '[[Category:Action]]|?Part of document.Name ' + lang + '=sectionName|?Part of document.Type=sectionType|?Part of document.Type.Name ' + lang + '=sectionTypeName|?Part of document.Description ' + lang + '=sectionDesc|?Part of document.Ordinal=sectionOrdinal|?Part of document.Document.Name ' + lang + '=policyName|?Part of document.Document.Validity end=policyEndYear|?Part of document.Document.Field.Name ' + lang + '=Field|?Part of document.Document.Field=Domain|?Name ' + lang + '=Name|?Description ' + lang + '=Description|?Responsible actor|?Deadline year=Action deadline|?Part of document.Name ' + lang + '=Section name|?Part of document.Deadline year=Section deadline|limit=50'
    const progressQuery = '[[Category:Progress]]|?Name|?Description|?Date|?Action|?Organization.Name=OrgName'
    const orgQuery = '[[Category:Organization]]|?Name|?BusinessID|?Responsible actor|?Responsible actor.Name fi=actors_fi|?Responsible actor.Name sv=actors_sv|?Responsible actor.Name en=actors_en|limit=500'
    const actorQuery = '[[Category:Actor]]|?Name ' + lang + '=Name|limit=500'
    $('#spinner').show()
    $('#error').hide()
    return Promise.all([
        executeAsk(progressQuery)
            .then(updateProgressData),
        executeAsk(actionQuery)
            .then(updateActions),
        executeAsk(actorQuery)
            .then(updateActors)
    ])
        .then(() => {
            console.log('data loaded')
            $('#spinner').hide()
            console.log(actions)
            console.log(progressItems)
            $('#actors').selectpicker({
                actionsBox: true,
                selectedTextFormat: 'count',
                liveSearch: true,
                multiple: true
            })
            //$('#actors').selectpicker('selectAll');
            $('#filters').collapse('show')
        })
}
async function executeAsk(query) {
    var results = [];
    var res = null;
    var offset = 0
    try {
        while (res == null || res.query.results.length == 50) {
            _query = query + '|offset=' + offset
            console.log(_query)
            res = await $.ajax({
                url: API_URL,
                data: {
                    action: 'ask',
                    format: 'json',
                    api_version: 3,
                    query: _query
                    
                },
                xhrFields: {
                    withCredentials: false
                },
                dataType: 'json'
            })
            offset = offset + 50
            results = results.concat(res.query.results)
        }
        return results
    } catch (error) {
        $('#spinner').hide()
        $('#error').text(error)
        $('#error').show()
        console.log(error)
    }
}