CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-vue-select

Everything you wish the HTML select element could do, wrapped up into a lightweight, extensible Vue component.

Pending
Overview
Eval results
Files

tagging.mddocs/

Tagging and Creation

Dynamic option creation functionality allowing users to create new options from search input, with customizable creation logic and tag management.

Capabilities

Tagging Configuration

Properties that enable and control option creation from user input.

/**
 * Enable/disable creating options from search input
 * When true, allows users to create new options by typing
 */
taggable: Boolean  // default: false

/**
 * When true, newly created tags will be added to the options list
 * Useful for persistent tag creation across sessions
 */
pushTags: Boolean  // default: false

/**
 * User-defined function for creating new options
 * Determines format of created options
 * @param option - The search text to convert to an option
 * @returns Created option object or string
 */
createOption: Function  // default: creates object with label key or returns string

Tag State Management

Data properties that track created tags and their state.

// Data properties
data: {
  /**
   * Array of tags that have been created when pushTags is enabled
   */
  pushedTags: Array  // default: []
}

// Computed properties
computed: {
  /**
   * Available options including pushed tags
   * Combines original options with user-created tags
   */
  optionList: Array
}

Tag Creation Methods

Methods for creating, managing, and validating new tags.

/**
 * Add a created tag to the pushed tags list
 * Called automatically when pushTags is true
 * @param option - The tag option to add
 */
pushTag(option: Object | String): void

/**
 * Check if an option already exists in the option list
 * Prevents duplicate tag creation
 * @param option - Option to check for existence
 * @returns Whether the option exists
 */
optionExists(option: Object | String): Boolean

/**
 * Delete value on Delete keypress when no text in search input
 * Handles tag removal via keyboard in multiple selection mode
 * @returns Deleted value or undefined
 */
maybeDeleteValue(): any

Tag Events

Events emitted during tag creation and management.

/**
 * Emitted when a new tag/option is created from user input
 * @param option - The newly created option
 */
'option:created': (option: Object | String) => void

Usage Examples

Basic Tagging

<template>
  <v-select 
    v-model="selectedTags"
    :options="availableTags"
    taggable
    multiple
    placeholder="Type to create new tags..."
  />
</template>

<script>
export default {
  data() {
    return {
      selectedTags: [],
      availableTags: ['JavaScript', 'Vue.js', 'React', 'Angular']
    };
  }
};
</script>

Persistent Tag Creation

<template>
  <v-select 
    v-model="selectedCategories"
    :options="categories"
    taggable
    pushTags
    multiple
    @option:created="onTagCreated"
    placeholder="Add or create categories..."
  />
</template>

<script>
export default {
  data() {
    return {
      selectedCategories: [],
      categories: ['Electronics', 'Clothing', 'Books', 'Home & Garden']
    };
  },
  methods: {
    onTagCreated(newTag) {
      console.log('New category created:', newTag);
      // Could save to backend, analytics, etc.
    }
  }
};
</script>

Custom Tag Creation Logic

<template>
  <v-select 
    v-model="selectedUsers"
    :options="users"
    :createOption="createUserOption"
    taggable
    multiple
    label="name"
    placeholder="Add existing users or create new ones..."
  />
</template>

<script>
export default {
  data() {
    return {
      selectedUsers: [],
      users: [
        { id: 1, name: 'John Doe', email: 'john@example.com', type: 'existing' },
        { id: 2, name: 'Jane Smith', email: 'jane@example.com', type: 'existing' }
      ]
    };
  },
  methods: {
    createUserOption(inputText) {
      // Create a user object from input text
      const email = inputText.includes('@') ? inputText : `${inputText}@example.com`;
      return {
        id: Date.now(), // Temporary ID
        name: inputText,
        email: email,
        type: 'new'
      };
    }
  }
};
</script>

Tag Validation and Formatting

<template>
  <v-select 
    v-model="selectedSkills"
    :options="skills"
    :createOption="createSkillTag"
    taggable
    pushTags
    multiple
    @option:created="validateAndSaveSkill"
    placeholder="Add skills (will be formatted)..."
  />
</template>

<script>
export default {
  data() {
    return {
      selectedSkills: [],
      skills: [
        'JavaScript', 'Python', 'Java', 'C++', 'Go',
        'React', 'Vue.js', 'Angular', 'Node.js'
      ]
    };
  },
  methods: {
    createSkillTag(skillText) {
      // Format and validate skill name
      const formattedSkill = skillText
        .split(' ')
        .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
        .join(' ');
      
      return formattedSkill;
    },
    validateAndSaveSkill(skill) {
      // Validate skill length
      if (skill.length < 2) {
        this.$nextTick(() => {
          // Remove invalid skill
          this.selectedSkills = this.selectedSkills.filter(s => s !== skill);
        });
        alert('Skill name must be at least 2 characters long');
        return;
      }
      
      console.log('Valid skill created:', skill);
      // Could save to backend
    }
  }
};
</script>

Email Tag Creation

<template>
  <v-select 
    v-model="selectedEmails"
    :options="commonEmails"
    :createOption="createEmailOption"
    taggable
    multiple
    placeholder="Enter email addresses..."
  />
</template>

<script>
export default {
  data() {
    return {
      selectedEmails: [],
      commonEmails: [
        'admin@example.com',
        'support@example.com',
        'info@example.com'
      ]
    };
  },
  methods: {
    createEmailOption(emailText) {
      // Validate email format
      const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
      
      if (emailRegex.test(emailText)) {
        return emailText.toLowerCase();
      } else {
        // Return invalid marker or throw error
        return null; // This will prevent tag creation
      }
    }
  }
};
</script>

Complex Object Tag Creation

<template>
  <v-select 
    v-model="selectedTasks"
    :options="existingTasks"
    :createOption="createTaskOption"
    taggable
    pushTags
    multiple
    label="title"
    :reduce="task => task.id"
    placeholder="Add existing tasks or create new ones..."
  />
</template>

<script>
export default {
  data() {
    return {
      selectedTasks: [],
      existingTasks: [
        { id: 1, title: 'Review PR #123', priority: 'high', status: 'pending' },
        { id: 2, title: 'Update documentation', priority: 'medium', status: 'in-progress' }
      ]
    };
  },
  methods: {
    createTaskOption(taskTitle) {
      // Create a task object with default values
      return {
        id: `temp-${Date.now()}`, // Temporary ID
        title: taskTitle,
        priority: 'medium',
        status: 'pending',
        createdAt: new Date().toISOString(),
        createdBy: 'user'
      };
    }
  }
};
</script>

Conditional Tagging

<template>
  <div>
    <label>
      <input type="checkbox" v-model="allowNewTags"> Allow creating new tags
    </label>
    
    <v-select 
      v-model="selectedTags"
      :options="predefinedTags"
      :taggable="allowNewTags"
      multiple
      :placeholder="allowNewTags ? 'Type to create tags...' : 'Choose from existing tags...'"
    />
  </div>
</template>

<script>
export default {
  data() {
    return {
      allowNewTags: false,
      selectedTags: [],
      predefinedTags: ['Tag 1', 'Tag 2', 'Tag 3', 'Tag 4']
    };
  }
};
</script>

Tag Creation with Backend Integration

<template>
  <v-select 
    v-model="selectedTopics"
    :options="topics"
    :createOption="createTopicOption"
    taggable
    pushTags
    multiple
    :loading="isCreatingTopic"
    @option:created="saveNewTopic"
    placeholder="Add or create discussion topics..."
  />
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      selectedTopics: [],
      topics: [],
      isCreatingTopic: false
    };
  },
  async created() {
    // Load existing topics
    try {
      const response = await axios.get('/api/topics');
      this.topics = response.data;
    } catch (error) {
      console.error('Failed to load topics:', error);
    }
  },
  methods: {
    createTopicOption(topicName) {
      return {
        id: null, // Will be set by backend
        name: topicName,
        isNew: true
      };
    },
    async saveNewTopic(topic) {
      if (!topic.isNew) return;
      
      this.isCreatingTopic = true;
      
      try {
        const response = await axios.post('/api/topics', {
          name: topic.name
        });
        
        // Update the topic with real ID from backend
        const savedTopic = response.data;
        const index = this.topics.findIndex(t => t === topic);
        if (index > -1) {
          this.$set(this.topics, index, savedTopic);
        }
        
        // Update selection if this topic was selected
        const selectedIndex = this.selectedTopics.findIndex(t => t === topic);
        if (selectedIndex > -1) {
          this.$set(this.selectedTopics, selectedIndex, savedTopic);
        }
        
      } catch (error) {
        console.error('Failed to save new topic:', error);
        // Remove the failed topic
        this.topics = this.topics.filter(t => t !== topic);
        this.selectedTopics = this.selectedTopics.filter(t => t !== topic);
      } finally {
        this.isCreatingTopic = false;
      }
    }
  }
};
</script>

Install with Tessl CLI

npx tessl i tessl/npm-vue-select

docs

ajax-loading.md

customization.md

index.md

keyboard-navigation.md

search-filtering.md

selection.md

tagging.md

tile.json