Convert Single Column Text Input to CSS Grid Box

Hi Anki community!

The issue I faced was that I had multiple fields for a single purpose. Creating the individual notes was kind of cumbersome and it was easy to lose overview on each individual note.
My project was to consolidate these multiple fields and append their values to the card using javascript. In addition to that, I wanted a code that could handle more pieces of information (entities) in a dynamic manner. The output should be presented in a way that adjusts dynamically and has a nice structured panel look to it.

Narrow view:

Broad view:

In my case I have 3 different fields for the example sentence + 1 field for audio:
1x sentence in target language
1x phonetic transcription of the sentence
1x translation of the sentence.
1x audio

The javascript allows me to have multiple example sentences. Before that I was restricted to only one example sentence.
I chose a single column structure for the input value that uses double colons “::” as a delimiter for the headline. If the input should have nested objects then slash “/” is used for this purpose.
You can define your key values in the first line, separating the keys using double colons “::” as delimiter:

ids::target::phonetic::native::audio
(As an object with nested value, it could look like this:
ids::sentence/target::sentence/phonetic::sentence/native::audio )

The header defines the structure of your input. In this case I have 5 rows per entity.:
(1) ids, (2) target, (3) phonetic, (4) native, (5) audio
Newline (’\n’, break) is used as delimiter within the entity.
Numbers followed by period (1., 2., 3., aso.) are used as delimiter between the separate entities.
The complete input value looks like this in my case:

'ids::target::phonetic::native::audio

  1. example
    FIRST Target SNTNC
    FIRST Phonetic SNTNC
    FIRST Native SNTNC
    first audio file

  2. example
    SECOND Target SNTNC
    SECOND Phonetic SNTNC
    SECOND Native SNTNC

  3. example
    THIRD Target SNTNC
    THIRD Phonetic SNTNC
    THIRD Native SNTNC
    third audio file’

The issues that haven’t been solved yet:
I have not manged yet to include the audio files dynamically as Anki responds with error messages as soon as I try to create a code like: ‘[sound:’ + list[i][Object.keys(list[0])[4]] + ‘]’
I also haven’t figured out how to hide empty ‘undefined’ audio values.
Initially, I planned to have the code detect dynamically if there are nested objects and act accordingly but I can’t figure out how to make it work.
Instead of showing the audio symbol on each panel, I would prefer to have a different background color of the panel indicating that this example sentence provides audio on click or on hover.
I’m still researching on the topic of dynamic identifiers in HTML and CSS to make it easier to adjust the individual panels.

The solution so far:
The field name in my case is: {{example}}

HTML code (on front or back as needed):
<div class="wrapper" id="EXAMPLE"></div>

CSS code
You have to provide the CSS code for the snippets of information on each panel.
The key names defined in the header are the class names in CSS.

<style>
.wrapper {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
  grid-auto-rows: minmax(120px, auto);
  justify-content: center;
  grid-column-gap: 6px;
  grid-row-gap: 6px;
  padding: 2px 0;
  background-color: floralwhite;
}

.panel {
  background-color: snow;
	border: 2px solid teal;
  padding: 1rem;

}

.target {
color: green;
}

.phonetic {
color: Crimson;
}

.native {
color: SlateBlue;
}
</style>

Javascript code
I have very basic knowledge of javascript, so this is the best I could come up with:

<script src='_jquery.js'></script>

<script>

// convert an HTML fragment to plain text
    var tempDiv = document.createElement('div');
    tempDiv.innerHTML = '{{example}}';
    document.documentElement.appendChild(tempDiv);
    var singleColumnCSV = tempDiv.innerText;
    document.documentElement.removeChild(tempDiv);


var pipeSeparatedValues = singleColumnCSV.replace(/\s\n|\n/g, "|");
var removePipesTwoOrMore = pipeSeparatedValues.replace(/(\|)\1+/g, "|");
var removeSuperSpace = removePipesTwoOrMore.replace(/(?<=\|)\s(?=\d\.|$)/g,"");
var createNewArray = removeSuperSpace.match(/(^[^|]+)|(\b\d+\.+(.+?)(?=\s*\|\b\d+\.|\s*$))/g );

//source: 
//https://stackoverflow.com/questions/44178371/converting-csv-to-nested-json-in-javascript/44179597

// PSV = "Pipe Separated Values"
var PSV = createNewArray

var attrs = PSV.splice(0,1);

var result = PSV.map(function(row) {
  var obj = {};
  // use PIPE '|' as delimiter
  var rowData = row.split('|');
  // use 2 colons '::' as delimiter
  attrs[0].split('::').forEach(function(val, idx) {
    obj = constructObj(val, obj, rowData[idx]);
  });
  return obj;
})

function constructObj(str, parentObj, data) {
  if(str.split('/').length === 1) {
    parentObj[str] = data;
    return parentObj;
  }

  var curKey = str.split('/')[0];
  if(!parentObj[curKey])
    parentObj[curKey] = {};
  parentObj[curKey] = constructObj(str.split('/').slice(1).join('/'), 
                                   parentObj[curKey], data);
  return parentObj;
}

var list = result
var html = '';


// Get id and print
for (var i = 0, l = list.length; i < l; i++) {
	$('#EXAMPLE')
			.append('<div class="panel">'
+'<div>' 
			+	list[i][Object.keys(list[0])[0]] + '</div>'
+'<div class="'+ [Object.keys(list[0])[1]] + '">'
			+ list[i][Object.keys(list[0])[1]] + '</div>'
+'<div class="'+ [Object.keys(list[0])[2]] + '">'
			+ list[i][Object.keys(list[0])[2]] + '</div>'
+'<div class="'+ [Object.keys(list[0])[3]] + '">'
			+ list[i][Object.keys(list[0])[3]] + '</div>'

// audio file:
+'<div class="'+ [Object.keys(list[0])[4]] + '">'
			+ list[i][Object.keys(list[0])[4]] 
			+ '</div>'

			+'</div>');   

}

</script>

I’ve also posted this code on the reddit Anki community. It might be worth checking if someone came up with amendments.

Update:

Solved issues:

  • Including the audio files dynamically
  • Hiding empty ‘undefined’ audio values
  • Border and background color indicate if this example sentence provides audio.

Input value could look like this:

'ids::target::phonetic::native::audio

  1. example
    FIRST Target SNTNC
    FIRST Phonetic SNTNC
    FIRST Native SNTNC
    audio001.mp3

  2. example
    SECOND Target SNTNC
    SECOND Phonetic SNTNC
    audio002.mp3

  3. example
    THIRD Target SNTNC
    THIRD Phonetic SNTNC
    THIRD Native SNTNC
    audio003.mp3’

In case you want to transfer your deck to AnkiMobile make sure to include a field in the note where you add your sound files separately. Otherwise your media will not be transferred to your mobile device.

Narrow view:

Broad view:

CSS Code

    .wrapper {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
  grid-auto-rows: minmax(140px, auto);
  justify-content: center;
  grid-column-gap: 6px;
  grid-row-gap: 6px;
  padding: 2px 0;
  background-color: floralwhite;
}

.panel {
  background-color: SeaShell;
	border: 2px solid LightSteelBlue;
  padding: 1rem;

}

.panelwithaudio {
  background-color: Snow;
	border: 2px solid SteelBlue;
  padding: 1rem;
}

.target {
color: green;
}

.phonetic {
color: Crimson;
}

.native {
color: SlateBlue;
}

.audio {
		margin: 0;
		display: inline-block;
		margin: 1.25rem auto auto ;
}

HTML Code

<div class="wrapper" id="EXAMPLE">
</div>

Javascript

<script src='_jquery.js'></script>

<script>

// convert an HTML fragment to plain text
    var tempDiv = document.createElement('div');
    tempDiv.innerHTML = '{{example}}';
    document.documentElement.appendChild(tempDiv);
    var singleColumnCSV = tempDiv.innerText;
    document.documentElement.removeChild(tempDiv);


var pipeSeparatedValues = singleColumnCSV.replace(/\s\n|\n/g, "|");
var removePipesTwoOrMore = pipeSeparatedValues.replace(/(\|)\1+/g, "|");
var removeSuperSpace = removePipesTwoOrMore.replace(/(?<=\|)\s(?=\d\.|$)/g,"");
var createNewArray = removeSuperSpace.match(/(^[^|]+)|(\b\d+\.+(.+?)(?=\s*\|\b\d+\.|\s*$))/g );

//source: 
//https://stackoverflow.com/questions/44178371/converting-csv-to-nested-json-in-javascript/44179597

// PSV = "Pipe Separated Values"
var PSV = createNewArray

var attrs = PSV.splice(0,1);

var result = PSV.map(function(row) {
  var obj = {};
  // use PIPE '|' as delimiter
  var rowData = row.split('|');
  // use 2 colons '::' as delimiter
  attrs[0].split('::').forEach(function(val, idx) {
    obj = constructObj(val, obj, rowData[idx]);
  });
  return obj;
})

function constructObj(str, parentObj, data) {
  if(str.split('/').length === 1) {
    parentObj[str] = data;
    return parentObj;
  }

  var curKey = str.split('/')[0];
  if(!parentObj[curKey])
    parentObj[curKey] = {};
  parentObj[curKey] = constructObj(str.split('/').slice(1).join('/'), 
                                   parentObj[curKey], data);
  return parentObj;
}

var list = result


// Get id and print
for (var i = 0, l = list.length; i < l; i++) {
	$('#EXAMPLE')
			.append(
(list[i][Object.keys(list[0])[4]] != undefined ? '<div class="panelwithaudio">' : '<div class="panel">')
+'<div>' 
			+	list[i][Object.keys(list[0])[0]] + '</div>'
+'<div class="'+ [Object.keys(list[0])[1]] + '">'
			+ list[i][Object.keys(list[0])[1]] + '</div>'
+'<div class="'+ [Object.keys(list[0])[2]] + '">'
			+ list[i][Object.keys(list[0])[2]] + '</div>'
+'<div class="'+ [Object.keys(list[0])[3]] + '">'
			+ list[i][Object.keys(list[0])[3]] + '</div>'
// audio file:
		+ (list[i][Object.keys(list[0])[4]] != undefined ? 
			'<div class="'+ [Object.keys(list[0])[4]] + '">' 
			+ '<audio controls><source src="' + list[i][Object.keys(list[0])[4]] + '" type="audio/mp3"/></audio></div>' : '')

// end of panel:
			+'</div>');   
}

</script>