Vue Integration

Using the Notations library in Vue applications

This guide shows how to create Vue components for rendering Carnatic music notation.

Installation

npm install notations

Basic NotationViewer Component

Create a reusable Vue component (Composition API):

Basic NotationViewer Component
<!-- NotationViewer.vue -->
<template>
  <div ref="container" :class="className"></div>
</template>

<script setup>
import { ref, onMounted, watch } from 'vue';
import * as N from 'notations';

const props = defineProps({
  source: {
    type: String,
    required: true
  },
  className: {
    type: String,
    default: ''
  }
});

const emit = defineEmits(['error']);

const container = ref(null);
let view = null;

function renderNotation() {
  if (!container.value) return;

  if (!view) {
    const table = document.createElement('table');
    table.classList.add('notation-table');
    container.value.appendChild(table);
    view = new N.Carnatic.NotationView(table);
  }

  const [notation, beatLayout, errors] = N.load(props.source);

  if (errors.length === 0) {
    view.tableElement.innerHTML = '';
    view.renderNotation(notation, beatLayout);
  } else {
    emit('error', errors);
  }
}

onMounted(() => {
  renderNotation();
});

watch(() => props.source, () => {
  renderNotation();
});
</script>
\cycle("|4|2|2|") Sw: S R G M P D N S. Sh: sa ri ga ma pa dha ni sa

Component with Source Display and Editing

A more complete component that shows source code and allows editing:

Editable NotationBlock Component
<!-- NotationBlock.vue -->
<template>
  <div class="notation-block">
    <div v-if="showSource" class="notation-source">
      <div v-if="caption" class="notation-caption">{{ caption }}</div>

      <template v-if="isEditing">
        <textarea
          v-model="editedSource"
          class="notation-textarea"
        ></textarea>
        <div class="notation-actions">
          <button @click="applyEdit">Apply</button>
          <button @click="cancelEdit">Cancel</button>
        </div>
      </template>

      <template v-else>
        <pre><code>{{ currentSource }}</code></pre>
        <div class="notation-actions">
          <button @click="copyToClipboard">Copy</button>
          <button @click="startEdit">Edit</button>
        </div>
      </template>
    </div>

    <div class="notation-output">
      <strong>Output:</strong>
      <div ref="container"></div>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, watch } from 'vue';
import * as N from 'notations';

const props = defineProps({
  source: { type: String, required: true },
  caption: { type: String, default: '' },
  showSource: { type: Boolean, default: false }
});

const container = ref(null);
const isEditing = ref(false);
const currentSource = ref(props.source);
const editedSource = ref(props.source);
let view = null;

function renderNotation() {
  if (!container.value) return;

  if (!view) {
    const table = document.createElement('table');
    table.classList.add('notation-table');
    container.value.appendChild(table);
    view = new N.Carnatic.NotationView(table);
  }

  const [notation, beatLayout, errors] = N.load(currentSource.value);

  if (errors.length === 0) {
    view.tableElement.innerHTML = '';
    view.renderNotation(notation, beatLayout);
  }
}

function startEdit() {
  editedSource.value = currentSource.value;
  isEditing.value = true;
}

function applyEdit() {
  currentSource.value = editedSource.value;
  isEditing.value = false;
}

function cancelEdit() {
  editedSource.value = currentSource.value;
  isEditing.value = false;
}

async function copyToClipboard() {
  await navigator.clipboard.writeText(currentSource.value);
}

onMounted(() => renderNotation());
watch(currentSource, () => renderNotation());
</script>
\cycle("|4|2|2|") \beatDuration(4) \line("Pallavi") Sw: S , R , G , M , P , D , N , S. , Sh: ni , nnu , ko , ri , va , ra , nam , ma ,

Options API Version

<!-- NotationViewer.vue (Options API) -->
<template>
  <div ref="container" :class="className"></div>
</template>

<script>
import * as N from 'notations';

export default {
  name: 'NotationViewer',
  props: {
    source: {
      type: String,
      required: true
    },
    className: {
      type: String,
      default: ''
    }
  },
  emits: ['error'],
  data() {
    return {
      view: null
    };
  },
  mounted() {
    this.initView();
    this.renderNotation();
  },
  watch: {
    source() {
      this.renderNotation();
    }
  },
  methods: {
    initView() {
      const table = document.createElement('table');
      table.classList.add('notation-table');
      this.$refs.container.appendChild(table);
      this.view = new N.Carnatic.NotationView(table);
    },
    renderNotation() {
      const [notation, beatLayout, errors] = N.load(this.source);

      if (errors.length === 0) {
        this.view.tableElement.innerHTML = '';
        this.view.renderNotation(notation, beatLayout);
      } else {
        this.$emit('error', errors);
      }
    }
  }
};
</script>

TypeScript with Vue 3

<!-- NotationViewer.vue -->
<template>
  <div ref="container" :class="className"></div>
</template>

<script setup lang="ts">
import { ref, onMounted, watch } from 'vue';
import * as N from 'notations';

interface Props {
  source: string;
  className?: string;
}

const props = withDefaults(defineProps<Props>(), {
  className: ''
});

const emit = defineEmits<{
  (e: 'error', errors: N.ParseError[]): void;
}>();

const container = ref<HTMLDivElement | null>(null);
let view: N.Carnatic.NotationView | null = null;

function renderNotation(): void {
  if (!container.value) return;

  if (!view) {
    const table = document.createElement('table');
    table.classList.add('notation-table');
    container.value.appendChild(table);
    view = new N.Carnatic.NotationView(table);
  }

  const [notation, beatLayout, errors] = N.load(props.source);

  if (errors.length === 0) {
    view.tableElement.innerHTML = '';
    view.renderNotation(notation, beatLayout);
  } else {
    emit('error', errors);
  }
}

onMounted(() => renderNotation());
watch(() => props.source, () => renderNotation());
</script>

Error Handling

<template>
  <div>
    <NotationViewer :source="source" @error="handleErrors" />

    <div v-if="errors.length" class="error-display">
      <h4>Parsing Errors:</h4>
      <ul>
        <li v-for="(err, i) in errors" :key="i">
          Line {{ err.line }}, Col {{ err.column }}: {{ err.message }}
        </li>
      </ul>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import NotationViewer from './NotationViewer.vue';

const source = ref('...');
const errors = ref([]);

function handleErrors(errs) {
  errors.value = errs;
}
</script>

See Also