I am working on a React-based Google Docs clone as a side project. I'm using TipTap as the base.
I wanted to try to mimic the functionality in Google Docs where the select field would update with the appropriate value based on whatever text the user selects (ex., "heading 1" would show up on
the select field if a user highlights text with <h1></h1> styling).
Here's a small video of the functionality I want to mimic: https://i.gyazo.com/18cc57f0285c8c5cd80d5dd6051f0ee7.mp4
This is the code I have so far with the React Hook used for the select fields:
const [font, setFont] = useState('sans-serif')
const handleFontChange = (e) => {
setFont(e.target.value)
editor.chain().focus().setFontFamily(e.target.value).run()
}
return (
<select value={font} onChange={handleFontChange}>
{fontFamilies.map((item) => <option key={item.value} value={item.value}>{item.label}</option>)}
</select>
)
I've tried to wrap my head around React Hooks and the Selection API, but I'm stuck. Is this at all possible in Javascript/React?
EDIT:
I found a function from Tiptap's API that does exactly what I'm looking for. I guess now the problem is how do I update that value into the select? Here's my new code:
const font = [
{ value: 'sans-serif', label: 'Sans-serif' }, ...
]
const [selectedFont, setSelectedFont] = useState([])
const handleFontChange = (obj) => {
setSelectedFont(obj)
editor.chain().focus().setFontFamily(obj.value).run()
console.log(editor.isActive('textStyle', {fontFamily: selectedFont.value})) // prints true if the user clicked on text with the property active, otherwise prints false
}
return (
<Select
name="fontFamily"
options={font}
isClearable="true"
isSearchable="true"
value={selectedFont}
onChange={(option) => handleFontChange(option)}
/>
)
It feels like the solution is really simple but I'm just overthinking it.
So I finally figured out what to do. I needed to pass a function in value that iterated over the array:
const fontList = [
{ label: 'Sans-serif', value: 'sans-serif' },
...
]
<Select
name="fontFamily"
placeholder="Select font..."
value={
editor.isActive('textStyle', {fontFamily: font}) ? (
selectedFont.find(index => fontList[index])
) : ''
}
options={fontList}
onChange={({value: fontSel}) => {
setFont(fontSel)
editor.chain().focus().setFontFamily(fontSel).run()
}}
/>
It only took me a whole week of tearing my hair out... Glad to finally get this done.
Just to add to this, I was able to do the following in Vue to select headings with a dropdown.
<select
:value="editor.isActive('heading') ? editor.getAttributes('heading').level : 0"
#input="setSelectedHeading"
>
<option
v-for="heading in headings"
:key="heading.value"
:label="heading.label"
:value="heading.value"
/>
</select>
headings: [
{ value: 0, label: 'Normal text' },
{ value: 1, label: 'Heading 1' },
{ value: 2, label: 'Heading 2' },
{ value: 3, label: 'Heading 3' },
{ value: 4, label: 'Heading 4' },
{ value: 5, label: 'Heading 5' },
{ value: 6, label: 'Heading 6' }
]
setSelectedHeading (selected) {
if (selected === 0) {
const selectedLevel = this.editor.getAttributes('heading').level
return this.editor.chain().focus().toggleHeading({ level: selectedLevel }).run()
}
return this.editor.chain().focus().setHeading({ level: selected }).run()
}
In case someone wants to create a simple dropdown menu with vue and tiptap, since this doesn't seem to work:
<select class="font-type">
<option #click="editor.chain().focus().setParagraph().run()" selected>Paragraph</option>
<option #click="editor.chain().focus().toggleHeading({level: 1}).run()">Heading 1</option>
<option #click="editor.chain().focus().toggleHeading({level: 2}).run()">Heading 2</option>
<option #click="editor.chain().focus().toggleHeading({level: 3}).run()">Heading 3</option>
</select>
you can do this:
<select class="font-type" #change="toggleType(editor, $event)">
<option value="p" selected>Paragraph</option>
<option value="1">Heading 1</option>
<option value="2">Heading 2</option>
<option value="3">Heading 3</option>
</select>
and add this to your <script>:
const toggleType = (editor, event) => {
if (event.target.value == 'p') {
editor.chain().focus().setParagraph().run()
} else {
editor.chain().focus().toggleHeading({level: Number(event.target.value)}).run()
}
}
Related
In case of multi-select , can someone help me with the onChange function ?
What can I pass inside updateFormState( ) to make the code work. Thanks.
const formState = {
fruits: "",
};
function updateFormState(key, value) {
formState[key] = value;
}
const [fruits] = useState([]);
<Form.Label>fruits</Form.Label>
<MultiSelect
options={[
{ label: "Grapes 🍇", value: "grapes" },
{ label: "Mango 🥭", value: "mango" },
{ label: "Strawberry 🍓", value: "strawberry", disabled: true },
]}
id="fruits"
labelledBy="Select"
value={fruits}
onChange={(e) => updateFormState( )}
/>
</Form.Group>
hi try this i hope it usefull
const [fruits, setFruits] = useState();
console.log("fruits",fruits);
return (
<div>
<select onChange={(e) => setFruits(e.target.value)} value={fruits}>
<option value='' selected disabled hidden>
select a fruits
</option>
<option value='apple'>apple</option>
<option value='banana'>banana</option>
<option value='orange'>orange</option>
</select>
</div>
);
I have a search feature using Vue.Js. The code below works when clicking the search button. My concern is, all data does not appear at first, it appears only when I click search. What I want, data appears at the start. Is there anything missing in the code I created? please help me solving this problem
<template>
<div>
<select v-model="selectedLevel">
<option value="" disabled selected hidden>Level</option>
<option value="beginner">Beginner</option>
<option value="intermediate">Intermediate</option>
<option value="advanced">Advanced</option>
</select>
<select v-model="selectedTime">
<option value="" disabled selected hidden>Time</option>
<option value="30">30 Min</option>
<option value="60">60 Min</option>
</select>
<select v-model="selectedType">
<option value="" disabled selected hidden>Type</option>
<option value="cycling">Cycling</option>
<option value="boxing">Boxing</option>
</select>
<button #click="search">Search</button>
<div class="list-item" v-for="item in searchResult">
<div class="card">
<p>Class Name: {{ item.type }}</p>
<p>Level: {{ item.level }}</p>
<p>Time: {{ item.time }}</p>
</div>
</div>
</div>
</template>
export default {
data() {
return {
selectedType: '',
selectedTime: '',
selectedLevel: '',
items: [{
type: 'cycling',
time: '30',
level: 'beginner'
},
{
type: 'boxing',
time: '60',
level: 'beginner'
},
{
type: 'cycling',
time: '60',
level: 'advanced'
},
{
type: 'boxing',
time: '30',
level: 'advanced'
}
],
searchResult: [],
}
},
methods: {
search() {
let filterType = this.selectedType,
filterTime = this.selectedTime,
filterLevel = this.selectedLevel
this.searchResult = this.items.filter(function(item) {
let filtered = true
if (filterType && filterType.length > 0) {
filtered = item.type == filterType
}
if (filtered) {
if (filterTime && filterTime.length > 0) {
filtered = item.time == filterTime
}
}
if (filtered) {
if (filterLevel && filterLevel.length > 0) {
filtered = item.level == filterLevel
}
}
return filtered
})
}
}
}
You need to run the search method once the component is mounted because now runs only when you click the button, try the below:
inside export default add the below:
data() {
return {
selectedType: '',
selectedTime: '',
selectedLevel: '',
items: [{
type: 'cycling',
time: '30',
level: 'beginner'
},
{
type: 'boxing',
time: '60',
level: 'beginner'
},
{
type: 'cycling',
time: '60',
level: 'advanced'
},
{
type: 'boxing',
time: '30',
level: 'advanced'
}
],
searchResult: [],
}
},
mounted(){
this.search();
}
Just add the mounted part
When the page loads this.searchResult is empty array.
You search method is called only when you click search button. and then filtered items are assigned to this.searchResult
Simple solution would be to manually call the search method on component mounted or created hook.
Better solution would be to rewrite your search logic using pipe and computed propeties
I have tricky challenge with vuejs, I want to have two select fields. the first one should select fruits for example, and the second should list all fruits. If I select vegetable from the first select field, the second select field should list all vegetable.
I stumble and find similar stuff online but I don't know how to make first item in the second select field selected.
anytime I select fruits, the first item on the list in second select first should be selected as default, and if I select vegetable, the first item in the second select field should be selected as default.
pls help me check the code here: https://jsfiddle.net/aj6g87dh/1/
new Vue({
el: '#test',
data: {
category: 'fruits',
list: '',
optionsData: {
fruits: [
{ text: 'Orange', value: 'orange' },
{ text: 'Banane', value: 'banana' },
],
vegetables: [
{ text: 'Brocolis', value: 'brocolis' },
{ text: 'Radish', value: 'radish' },
]
}
},
computed: {
options: function() {
let options = ''
switch (this.category) {
case 'fruits':
options = this.optionsData.fruits
break;
case 'vegetables':
options = this.optionsData.vegetables
break;
default:
options = this.optionsData.fruits
}
return options
}
},
methods: {
onChange: function() {
this.options = this.options
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.5/vue.js"></script>
<div id="test">
<select v-model="category" v-on:change="onChange" id="select1">
<option value="fruits">Fruits</option>
<option value="vegetables">Vegetables</option>
</select>
<select id="select2" v-model="list">
<option v-for="option in options" v-bind:value="option.value"> {{ option.text }} </option>
</select>
<span>{{ }}</span>
</div>
You can remove onChange method and add a watch property. This way you can handle changing logic there.
Also, you can simplify options retrieval to one line.
new Vue({
el: '#test',
data: {
category: 'fruits',
list: '',
optionsData: {
fruits: [{
text: 'Orange',
value: 'orange'
},
{
text: 'Banane',
value: 'banana'
},
],
vegetables: [{
text: 'Brocolis',
value: 'brocolis'
},
{
text: 'Radish',
value: 'radish'
},
]
}
},
computed: {
options: function() {
return this.optionsData[this.category]
}
},
watch: {
category: {
handler: function(newVal) {
this.list = this.optionsData[newVal][0].value;
},
immediate: true
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="test">
<select v-model="category" id="select1">
<option value="fruits">Fruits</option>
<option value="vegetables">Vegetables</option>
</select>
<select id="select2" v-model="list">
<option v-for="(option, i) in options" v-bind:value="option.value"> {{ option.text }} </option>
</select>
<span>{{ }}</span>
</div>
I have a basic Easyui combobox where i need to add dynamic options based on some condition.
My Html :
<input id="org_type" name="org_type" class="easyui-combobox" data-options="required: true,valueField:'value',textField:'text',prompt:'Select Org Type'" style="width:100%;" >
Now i need to load some options based on some conditions.
for example :
if(level == 1){
<option value="volvo">Volvo</option>
<option value="saab">Saab</option>
}else{
<option value="vw">Volvo</option>
<option value="audi">Saab</option>
}
Though that's not the right approach i know.
I need actually something like this.
Finally i got my own solution.
Simply i added these JavaScript code when i need to add options dynamically.
let options = [];
if(level == "1"){
options = [
{
text: 'Volvo',
value: 'volvo'
},
{
text: 'Saab',
value: 'saab'
}
];
}else if(org_level == "2"){
options = [
{
text: 'Marcedes',
value: 'marcedes'
},
{
text: 'Scania',
value: 'scania'
},
{
text: 'BMW',
value: 'bmw'
}
];
}
$('#org_type').combobox({
data: options
})
And it work's fine for me.
Thanks.
I am trying to add limit to iView ui Multiple select. Here is the code
<Select
v-model="data.category"
:multiple="true"
filterable
remote
:remote-method="remoteMethod2"
:loading="loading2">
<Option v-for="(option, index) in options2" :value="option.value" :key="index">{{option.label}}</Option>
</Select>
I want to add something like this max="3" to limit the selected items
Couldn't find anything in api doc.
There's no property with that functionality, but we could do it ourselves by watching the length of our model that contains the selected items and if it's equal to the fixed max in data object properties we change the disabled property state to true and if remove an item from the selected ones we could also enable the options drop down, check th following example that explains itself :
var Main = {
data() {
return {
disable:false,
max: 2,
cityList: [{
value: 'New York',
label: 'New York'
},
{
value: 'London',
label: 'London'
},
{
value: 'Sydney',
label: 'Sydney'
},
{
value: 'Ottawa',
label: 'Ottawa'
},
{
value: 'Paris',
label: 'Paris'
},
{
value: 'Canberra',
label: 'Canberra'
}
],
model10: []
}
},
watch: {
model10(val) {
if (val.length == this.max) this.disable=true
else this.disable=false
},
}
}
var Component = Vue.extend(Main)
new Component().$mount('#app')
#import url("//unpkg.com/iview/dist/styles/iview.css");
#app {
padding: 32px;
}
<script src="//unpkg.com/vue/dist/vue.js"></script>
<script src="//unpkg.com/iview/dist/iview.min.js"></script>
<div id="app">
<i-select v-model="model10" multiple style="width:260px">
<i-option :disabled="disable" v-for="item in cityList" :value="item.value" :key="item.value">{{ item.label }}</i-option>
</i-select>
</div>