import { isUndefined, isObject } from '../../utils/isType';
$(() => {
// url for the api we will be querying
let url = '';
// key/value lookup for standards
const descriptions = {};
// cleaned up structure of the API results
const minTree = {};
// keeps track of how many waiting api-requests still need to run
let noWaiting = 0;
// prune the min_tree where there are no standards
// opporates on the principal that no two subjects have the same name
function removeUnused(name) {
const num = Object.keys(minTree).find(x => minTree[x].name === name);
// if not top level standard
if (isUndefined(num)) {
// for each top level standard
Object.keys(minTree).forEach((knum) => {
const child = Object.keys(minTree[knum].children)
.find(x => minTree[knum].children[x].name === name);
if (isObject(child)) {
delete minTree[knum].children[child];
$(`.rda_metadata .sub-subject select option[value="${name}"]`).remove();
}
});
} else {
delete minTree[num];
// remove min_tree[num] from top-level dropdowns
$(`.rda_metadata .subject select option[value="${name}"]`).remove();
}
}
function getDescription(id) {
$.ajax({
url: url + id.slice(4),
type: 'GET',
crossDomain: true,
dataType: 'json',
}).done((results) => {
descriptions[id] = {};
descriptions[id].title = results.title;
descriptions[id].description = results.description;
noWaiting -= 1;
});
}
// init descriptions lookup table based on passed ids
function initDescriptions(ids) {
ids.forEach((id) => {
if (!(id in descriptions)) {
noWaiting += 1;
getDescription(id);
}
});
}
// takes in a subset of the min_tree which has name and standards properties
// initializes the standards property to the result of an AJAX POST
function getStandards(name, num, child) {
// slice -4 from url to remove '/api/'
noWaiting += 1;
$.ajax({
url: `${url.slice(0, -4)}query/schemes`,
type: 'POST',
crossDomain: true,
data: `keyword=${name}`,
dataType: 'json',
}).done((result) => {
if (isUndefined(child)) {
minTree[num].standards = result.ids;
} else {
minTree[num].children[child].standards = result.ids;
}
if (result.ids.length < 1) {
removeUnused(name);
}
noWaiting -= 1;
initDescriptions(result.ids);
});
}
// clean up the data initially returned from the API
function cleanTree(apiTree) {
// iterate over api_tree
Object.keys(apiTree).forEach((num) => {
minTree[num] = {};
minTree[num].name = apiTree[num].name;
minTree[num].children = [];
if (apiTree[num].children !== undefined) {
Object.keys(apiTree[num].children).forEach((child) => {
minTree[num].children[child] = {};
minTree[num].children[child].name = apiTree[num].children[child].name;
minTree[num].children[child].standards = [];
getStandards(minTree[num].children[child].name, num, child);
});
}
// init a standards on top level
minTree[num].standards = [];
getStandards(minTree[num].name, num, undefined);
});
}
// create object for typeahead
function initTypeahead() {
const data = [];
const simpdat = [];
Object.keys(descriptions).forEach((id) => {
data.push({ value: descriptions[id].title, id });
simpdat.push(descriptions[id].title);
});
const typ = $('.standards-typeahead');
typ.typeahead({ source: simpdat });
}
function initStandards() {
// for each metadata question, init selected standards according to html
$('.rda_metadata').each(function () { // eslint-disable-line func-names
// list of selected standards
const selectedStandards = $(this).find('.selected_standards .list');
// form listing of standards
const formStandards = $(this).next('form').find('#standards');
// need to pull in the value from frm_stds
const standardsArray = JSON.parse(formStandards.val());
// init the data value
formStandards.data('standard', standardsArray);
Object.keys(standardsArray).forEach((key) => {
// add the standard to list
if (key === standardsArray[key]) {
selectedStandards.append(`<li class="${key}">${key}<button class="remove-standard"><i class="fa fa-times-circle"></i></button></li`);
} else {
selectedStandards.append(`<li class="${key}">${descriptions[key].title}<button class="remove-standard"><i class="fa fa-times-circle"></i></button></li>`);
}
});
});
}
function waitAndUpdate() {
if (noWaiting > 0) {
// if we are waiting on api responces, call this function in 1 seccond
setTimeout(waitAndUpdate, 1000);
} else {
// update all the dropdowns/ standards explore box (calling on subject
// will suffice since it will necisarily update sub-subject)
$('.rda_metadata .subject select').change();
initStandards();
initTypeahead();
}
}
// given a subject name, returns the portion of the min_tree applicable
function getSubject(subjectText) {
const num = Object.keys(minTree).find(x => minTree[x].name === subjectText);
return minTree[num];
}
// given a subsubject name and an array of children, data, return the
// applicable child
function getSubSubject(subsubjectText, data) {
const child = Object.keys(data).find(x => data[x].name === subsubjectText);
return data[child];
}
function updateSaveStatus(group) {
// update save/autosave status
group.next('form').find('fieldset input').change();
}
// change sub-subjects and standards based on selected subject
$('.rda_metadata').on('change', '.subject select', (e) => {
const target = $(e.currentTarget);
const group = target.closest('.rda_metadata');
const subSubject = group.find('.sub-subject select');
const subjectText = target.find(':selected').text();
// find subject in min_tree
const subject = getSubject(subjectText);
// check to see if this object has no children(and thus it's own standards)
if (subject.children.length === 0) {
// hide sub-subject since there's no data for it
subSubject.closest('div').hide();
// update the standards display selector
$('.rda_metadata .sub-subject select').change();
} else {
// show the sub-subject incase it was previously hidden
subSubject.closest('div').show();
// update the sub-subject display selector
subSubject.find('option').remove();
subject.children.forEach((child) => {
$('<option />', { value: child.name, text: child.name }).appendTo(subSubject);
});
// once we have updated the sub-standards, ensure the standards displayed
// get updated as well
$('.rda_metadata .sub-subject select').change();
}
});
// change standards based on selected sub-subject
$('.rda_metadata').on('change', '.sub-subject select', (e) => {
const target = $(e.currentTarget);
const group = target.closest('.rda_metadata');
const subject = group.find('.subject select');
const subSubject = group.find('.sub-subject select');
const subjectText = subject.find(':selected').text();
const subjectData = getSubject(subjectText);
const standards = group.find('.browse-standards-border');
let standardsData;
if (subjectData.children.length === 0) {
// update based on subject's standards
standardsData = subjectData.standards;
} else {
// update based on sub-subject's standards
const subsubjectText = subSubject.find(':selected').text();
standardsData = getSubSubject(subsubjectText, subjectData.children).standards;
}
// clear list of standards
standards.empty();
// update list of standards
Object.keys(standardsData).forEach((num) => {
const standard = descriptions[standardsData[num]];
standards.append(`<div style="background-color:#EAEAEA;border-radius:3px"><strong>${standard.title}</strong><div style="float:right"><button class="btn btn-primary select_standard" data-standard="${standardsData[num]}">Add Standard</button></br></div><p>${standard.description}</p></div>`);
});
});
// when 'Add Standard' button next to the search is clicked, we need to add
// this to the user's selected list of standards.
// update the data and val of hidden standards field in form
$('.rda_metadata').on('click', '.select_standard_typeahead', (e) => {
const target = $(e.currentTarget);
const group = target.closest('.rda_metadata');
const selected = group.find('ul.typeahead li.active');
const selectedStandards = group.find('.selected_standards .list');
// the title of the standard
const standardTitle = selected.data('value');
// need to find the standard
let standard;
Object.keys(descriptions).forEach((standardId) => {
if (descriptions[standardId].title === standardTitle) {
standard = standardId;
}
});
selectedStandards.append(`<li class="${standard}">${descriptions[standard].title}<button class="remove-standard"><i class="fa fa-times-circle"></i></button></li>`);
const formStandards = group.next('form').find('#standards');
// get the data for selected standards from the data attribute 'standard'
// of the hidden field #standards within the answer form
let frmStdsDat = formStandards.data('standard');
// need to init data object for first time
if (typeof frmStdsDat === 'undefined') {
frmStdsDat = {};
}
// init the key to standard id and value to standard.
// NOTE: is there any point in storing the title or description here?
// storing the title could make export easier as we wolnt need to query api
// but queries to the api would be 1 per-standard if we dont store these
frmStdsDat[standard] = descriptions[standard].title;
// update data value
formStandards.data('standard', frmStdsDat);
// update input value
formStandards.val(JSON.stringify(frmStdsDat));
updateSaveStatus(group);
});
// when a 'Add standard' button is clicked, we need to add this to the user's
// selected list of standards
// update the data and val of hidden standards field in form
$('.rda_metadata').on('click', '.select_standard', (e) => {
const target = $(e.currentTarget);
const group = target.closest('.rda_metadata');
const selectedStandards = group.find('.selected_standards .list');
// the identifier for the standard which was selected
const standard = target.data('standard');
// append the standard to the displayed list of selected standards
selectedStandards.append(`<li class="${standard}">${descriptions[standard].title}<button class="remove-standard"><i class="fa fa-times-circle"></i></button></li>`);
const formStandards = group.next('form').find('#standards');
// get the data for selected standards from the data attribute 'standard'
// of the hidden field #standards within the answer form
let frmStdsDat = formStandards.data('standard');
// need to init data object for first time
if (isUndefined(frmStdsDat)) {
frmStdsDat = {};
}
// init the key to standard id and value to standard.
frmStdsDat[standard] = descriptions[standard].title;
// update data value
formStandards.data('standard', frmStdsDat);
// update input value
formStandards.val(JSON.stringify(frmStdsDat));
updateSaveStatus(group);
});
// when a 'Remove Standard' button is clicked, we need to remove this from the
// user's selected list of standards. Additionally, we need to remove the
// standard from the data/val fields of standards in hidden form
$('.rda_metadata').on('click', '.remove-standard', (e) => {
const target = $(e.currentTarget);
const group = target.closest('.rda_metadata');
const listedStandard = target.closest('li');
const standardId = listedStandard.attr('class');
// remove the standard from the list
listedStandard.remove();
// update the data for the form
const formStandards = group.next('form').find('#standards');
const frmStdsDat = formStandards.data('standard');
delete frmStdsDat[standardId];
// update data value
formStandards.data('standard', frmStdsDat);
// update input value
formStandards.val(JSON.stringify(frmStdsDat));
updateSaveStatus(group);
});
// show the add custom standard div when standard not listed clicked
$('.rda_metadata').on('click', '.custom-standard', (e) => {
e.preventDefault();
const target = $(e.currentTarget);
const group = target.closest('.rda_metadata');
const addStandardDiv = $(group.find('.add-custom-standard'));
addStandardDiv.show();
});
// when this button is clicked, we add the typed standard to the list of
// selected standards
$('.rda_metadata').on('click', '.submit_custom_standard', (e) => {
e.preventDefault();
const target = $(e.currentTarget);
const group = target.closest('.rda_metadata');
const selectedStandards = group.find('.selected_standards .list');
const standardName = group.find('.custom-standard-name').val();
selectedStandards.append(`<li class="${standardName}">${standardName}<button class="remove-standard"><i class="fa fa-times-circle"></i></button></li>`);
const formStandards = group.next('form').find('#standards');
// get the data for selected standards from the data attribute 'standard'
// of the hidden field #standards within the answer form
let frmStdsDat = formStandards.data('standard');
// need to init data object for first time
if (typeof frmStdsDat === 'undefined') {
frmStdsDat = {};
}
// init the key to standard id and value to standard.
frmStdsDat[standardName] = standardName;
// update data value
formStandards.data('standard', frmStdsDat);
// update input value
formStandards.val(JSON.stringify(frmStdsDat));
updateSaveStatus(group);
});
function initMetadataQuestions() {
// find all elements with rda_metadata div
$('.rda_metadata').each((idx, el) => {
// $(this) is the element
const sub = $(el).find('.subject select');
// var sub_subject = $(this).find(".sub-subject select");
Object.keys(minTree).forEach((num) => {
$('<option />', { value: minTree[num].name, text: minTree[num].name }).appendTo(sub);
});
});
waitAndUpdate();// $(".rda_metadata .subject select").change();
}
// callback from url+subject-index
// define api_tree and call to initMetadataQuestions
function subjectCallback(data) {
// remove unused standards/substandards
cleanTree(data);
// initialize the dropdowns/selected standards for the page
initMetadataQuestions();
}
// callback from get request to rda_api_address
// define url and make a call to url+subject-index
function urlCallback(data) {
// init url
({ url } = data);
// get api_tree structure from api
$.ajax({
url: `${url}subject-index`,
type: 'GET',
crossDomain: true,
dataType: 'json',
}).done((result) => {
subjectCallback(result);
});
}
// get the url we will be using for the api
// only do this if page has an rda_metadata div
if ($('.rda_metadata').length) {
$.getJSON('/question_formats/rda_api_address', urlCallback);
}
// when the autosave or save action occurs, this clears out both the list of
// selected standards, and the selectors for new standards, as it re-renders
// the partial. This "autosave" event is triggered by that JS in order to
// allow us to know when the save has happened and re-init the question
$('.rda_metadata').on('autosave', (e) => {
e.preventDefault();
// re-initialize the metadata question
initMetadataQuestions();
});
});