Instant search external resource with params - stimulusjs

What does the code for loading query results from text search look like? There’s an example in handbook that loads external resource, is it as simple as that but appending query string to resource and capturing form data? I’m looking to make instant search where results are updated with every input event. For some reason google search for instant search yield examples that also use web socket part of hot wired, is that necessary and does it improve rendering performance? I’m hoping that turbo doesn’t simply tear up dom when replacing external resource.

I've written a Stimulus controller before that does something similar.
You would attach a controller to your input element (or form), and the controller would have an event handler for the change event. Something like this:
// your.html
// <input type='text' data-controller="your_controller"/>
// your_controller.js
import { Controller } from "stimulus"
export default class extends Controller {
connect() {
this.element.addEventListener('change', (e) => {
// execute a fetch request or something here
})
}
}

Related

Best practice to write minimal code when using vuejs vee-validate with composition api in vuejs3

I have gone through the composition api docs for vee-validate, and I can definitely get my validation working on my forms if I follow the pattern described in their docs.
I don't feel comfortable however doing it as documented there, as I just feel I'm writing too much code for each form, and I do not want to repeat myself.
So I've been experimeting a bit with how I can optimise this, and this is what we currently came up with, but I'm a bit stuck now.
You can see the code example on https://codesandbox.io/s/restless-star-1qwgb, but I'll walk you through.
Consider we want to create a form for creating a new Invoice.
An invoice is typically composed of the Invoice model with a number of InvoiceElements attached to it. (the invoice lines)
In our vueJs codebase, we have javascript classes representing each model that we need to work with, so you'll find a class Invoice and a class InvoiceElement, both extending BaseModel which already providers some basic functionality.
On each mode, we have defined a static method returning a yup validation schema, e.g.:
import * as yup from "yup";
import BaseModel from "./BaseModel";
import InvoiceElement from "./InvoiceElement";
export default class Invoice extends BaseModel {
static get validationSchema() {
return yup.object().shape({
due_date: yup.date().min(new Date()).default("2021-09-30"),
reference: yup.string(),
elements: yup
.array()
.of(InvoiceElement.validationSchema)
.default([InvoiceElement.validationSchema.getDefault()])
});
}
}
As you can see, we also define default values for each of the schema fields.
This allows us to do the following in our form component:
setup() {
let { handleSubmit, errors, values } = useForm({
validationSchema: Invoice.validationSchema,
initialValues: Invoice.validationSchema.getDefault(),
validateOnMount: false,
});
let addLine = () => {
values.elements.push(InvoiceElement.validationSchema.getDefault());
};
let submitForm = handleSubmit((values) => {
alert("form was valid and we submit data here");
});
return {
values,
errors,
addLine,
submitForm,
};
}
Okay - I'm very happy with this as we now have:
values which is reactive and I can just bind to my inputs using ```
errors which has my errors
When I submit the form, it correctly triggers vee-validate's handleSubmit() method and my errors get correctly updated.
My main issue with this approach now is how to trigger the validation of the fields when they get updated. The main goal is to avoid having to write too much code using the useField() composable.
I know I'm not following the proposed pattern, but it just kept feeling as if we were writing too much code, and we seem quite close to a good pattern, but I just don't get the last bits...
Maybe someone on here does though :-)
Generally, the docs are only meant to show you "how it works", but I do agree that useField is very verbose if used incorrectly. Using it for models isn't the intended use-case.
For example:
// Very verbose and not the intended use-case
const { value: email } = useField(...);
const { value: password } = useField(...);
The main purpose of useField is to help create input components that can be validated, that if you are willing to couple your form components with vee-validate which I find reasonable in 80% of situations.
To try to answer your issue, since you want to avoid using useField you could actually make use of the other useXXX functions called composition helpers. Like useValidateField which creates a function for you that can validate any field given it was created in a child of a useForm component.
const validate = useValidateField('fieldName');
validate(); // triggers validation on the field

Not reloading the page after delete in Vue component

I have a vue component to delete items. function working fine.but that deleted items is remain the page until I press the reload in web browser. Following is the delete method in vue component.
deletedItem: function () {
let data = {
"_method" : 'delete',
}
axios.post('/delete-item' +this.id, data);
this.isDelete =false;
location.reload();
}
-Delete button
<button v-on:click="deletedItem" class="bg-red-500 text-gray-200 rounded hover:bg-red-400 px-6 py-2 focus:outline-none mx-1"> Delete</button>
Is there are anyway to remove deleted item once I click delete button.
Once I reload the page after the response came then it reload the page & remove deleted item. solution is as follows.
axios.post('/delete-item' +this.id, data).then( response => {
this.isDelete =false
location.reload()
});
As i understand it you have all the items stored remotely on a server somewhere else, and the way you fetch the items is by some other request. And what you are doing here is deleting the item on the server without doing anything else.
I don't see how the web-application would know that the item got deleted. Normally I solve this situation with deleting the object locally after i receive a 200 from the api.
If you want something even more simple you could just refresh the list of items after you receive a response of any kind. It all depends on what kind of features you want to have.
Please confirm the points below:
Did your api responded with a successful response?
Did you declare isDelete property in data function?
You should change the isDelete property after the ajax finished using promise or async/await because ajax is asynchoronous at the most time.And you should declare the isDelete property in data function like this:
data () {
return { isDelete: true }
}
because it makes Vue be able to collect the dependency relation and make it responsive.

Prevent DOM reuse within lit-html/lit-element

I am looking for a way to NOT reuse DOM elements within lit-html/lit-element (yes, I know, I'm turning off one of the prime features). The particular scenario is moving an existing system to lit-element/lit-html that at certain points embeds the trumbowyg WYSIWYG editor. This editor attaches itself to a <div> tag made within lit-element and modifies its own internal DOM, but of course lit-html does not know that this has happened, so it will often reuse the same <div> tag instead of creating a new one. I am looking for something similar to the vue.js key attribute (e.g., preventing Vue from aggresively reusing dom-elements)
I feel like the live() directive in lit-html should be useful for this, but that guards against reuse based on a given attribute, and I want to prevent reuse even if all attributes are identical. Thanks!
I have had similar issues with rich text editors and contenteditable - due to how templates update the DOM you don't want that to be part of a template.
You do this by adding a new element with the non-Lit DOM and then adding that to the DOM that Lit does manage:
class TrumbowygEditor
extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({mode: 'open'});
const div = document.createElement('div');
shadow.appendChild(div);
const style = document.createElement('style');
// Add CSS required
shadow.appendChild(style);
$(div).trumbowyg(); //init
}
}
customElements.define('trumbowyg-editor', TrumbowygEditor);
As this is running in a custom element's shadow DOM Lit won't touch it, you can do:
html`
<div>Lit managed DOM</div>
<trumbowyg-editor></trumbowyg-editor>`;
However, you will have to implement properties and events on TrumbowygEditor to add everything you want to pass to or get from the nested jQuery component.
You can add the scripts with import if you can get module versions of jQuery/Trumbowyg (or your build tools support it) or you can add <script> tags to your component, add fallback loading DOM content in the constructor, and then on the load event of the <script> call the $(div).trumbowyg() to init the component.
While messier and more work I'd recommend the latter as both components are large and (thanks to jQuery being built on assumptions that are now 15 years old) need to load synchronously (<script async or <script defer don't work). Especially on slower connections Lit will be ready long before jQuery/Trumbowyg have loaded in, so you want <trumbowyg-editor> to look good (show spinner, layout in the right amount of space etc) while that's happening.
You write that you attach the external library directly to an element managed by lit-html. It sounds like you're doing essentially this:
render(html`<section><div id=target></div></section>`, document.body)
external_lib.render_to(document.querySelector("#target"))
If this is what you do instead try to create your own div, let the external lib render to that div, and finally attach that div to lit-html:
let target_div = document.createElement('div')
render(html`<section>${div}</section>`, document.body)
external_lib.render_to(target_div)
The most up-to-date answer to this problem is to use Lit's built-in keyed directive. This scenario is exactly what it's for:
https://lit.dev/docs/templates/directives/#keyed
Associates a renderable value with a unique key. When the key changes, the previous DOM is removed and disposed before rendering the next value, even if the value—such as a template—is the same.
#customElement('my-element')
class MyElement extends LitElement {
#property()
userId: string = '';
render() {
return html`
<div>
${keyed(this.userId, html`<user-card .userId=${this.userId}></user-card>`)}
</div>`;
}
}

Trying to get vue.js to render something conditionally based on a method in created()

I have a call in my created method which has an await.
I want to know that the results of that call are loaded so that i can conditionally show/hide things in the DOM.
Right now it looks like the DOM is being rendered before that method has completed. But I though that methods in created were called before the DOM rendered?
You're correct in assuming that the created hook runs before the component mounts. However, the lifecycle hooks are not waiting for async calls to complete. If you want to wait for that call to be completed and data to load, you can do so by using a Boolean that you set to true when your data has loaded.
Your template:
<div v-if='dataLoaded'>Now you can see me.</div>
in your vue instace
export default {
data () {
return {
dataLoaded: false
}
},
created () {
loadMyData().then(data => {
// do awesome things with data
this.dataLoaded = true
})
}
}
This way you can keep your content hidden until that call has resolved. Take care with the context when you handle the ajax response. You will want to keep this as a reference to the original vue instance, so that you can set your data correctly. Arrow functions work well for that.

Vuetify and require.js: How do I show a dynamic component?

I am creating a tab component that loads its v-tab-item components dynamically, given an array of configuration objects that consist of tabName, id, and tabContent which is a resource location for the component. I have it successfully loading the components. However, they don't actually initialize (or run their created() methods) until I switch tabs. I just get empty tabs with the correct labels. Using the DOM inspector initially shows just <componentId></componentId>, and then when I switch tabs, those tags are replaced with all of the component's content.
How do I get the dynamic components to initialize as soon as they are loaded?
EDIT: I created a CodePen here:
https://codepen.io/sgarfio/project/editor/DKgQON
But as this is my first CodePen, I haven't yet figured out how to reference other files in the project (i.e. what to set tabContent to so that require.js can load them up). I'm seeing "Access is denied" in the console, which makes it sound like it found the files but isn't allowed to access them, which is weird because all the files belong to the same project. So my CodePen doesn't even work as well as my actual project. But maybe it will help someone understand what I'm trying to do.
Also, after poking around a bit more, I found this:
http://michaelnthiessen.com/force-re-render/
that says I should change the key on the component and that will force the component to re-render. I also found this:
https://v2.vuejs.org/v2/guide/components-dynamic-async.html
Which has a pretty good example of what I'm trying to do, but it doesn't force the async component to initialize in the first place. That's what I need the async components to do - they don't initialize until I switch tabs. In fact they don't even show up in the network calls. Vue is simply generating a placeholder for them.
I got it working! What I ended up doing was to emit an event from the code that loads the async components to indicate that that component was loaded. The listener for that event keeps a count of how many components have been loaded (it already knows how many there should be), and as soon as it receives the right number of these events, it changes the value of this.active (v-model value for the v-tabs component, which indicates which tab is currently active) to "0". I tried this because as I noted before, the async components were loading/rendering whenever I switched tabs. I also have prev/next buttons to set this.active, and today I noticed that if I used the "next" button instead of clicking on a tab, it would load the async components but not advance the tab. I had already figured out how to emit an event from the loading code, so all I had to do at that point was capture the number of loaded components and then manipulate this.active.
I might try to update my CodePen to reflect this, and if I do I'll come back and comment accordingly. For now, here's a sample of what I ended up with. I'm still adding things to make it more robust (e.g. in case the configuration object contains a non-existent component URL), but this is the basic gist of it.
created: function() {
this.$on("componentLoaded", () => {
this.numTabsInitialized++;
if(this.numTabsInitialized == this.numTabs) {
// All tabs loaded; update active to force them to load
this.active = "0";
}
})
},
methods: {
loadComponent: function(config) {
var id = config.id;
var compPath = config.tabContent;
var self = this;
require([compPath], function(comp) {
Vue.component(id, comp);
self.$emit("componentLoaded");
});
}
}