Add and Remove dom elements using alpine JS - alpine.js

I am trying to build a custom add and remove entire div element from an array using alpine JS, here is my code which is working but instead of removing from the exact remove button click it will remove the last one on the array.
HTML
<div x-data="addRemove()">
<template x-for="(field, index) in fields" :key="index">
<div>
<input type="text" name="txt1[]" class="form-input">
<button type="button" class="btn btn-danger btn-small" #click="removeField(index)">×</button>
</div>
</template>
<button type="button" #click="addNewField()">+ Add Row</button>
</div>
JAVASCRIPT
return {
fields: [],
addNewField() {
this.fields.push({});
},
removeField(index) {
this.fields.splice(index, 1);
}
}

Found a solution, this is what I did.
HTML
<div x-data="addRemove()">
<template x-for="(field, index) in fields" :key="field.id">
<div>
<input type="text" name="txt1[]" class="form-input">
<button type="button" class="btn btn-danger btn-small" #click="removeField(field)">×</button>
</div>
</template>
<button type="button" #click="addNewField()">+ Add Row</button>
</div>
JAVASCRIPT
function addRemove() {
return {
fields: [],
addNewField() {
this.fields.push({id: new Date().getTime() + this.fields.length});
},
removeField(field) {
this.fields.splice(this.fields.indexOf(field), 1);
}
}
}

You've to set the 'key' with a unique value in the looping.
<div x-data="{data: []}">
<div #click="data.push({ randomNumber: new Date().getTime()})">(Click Here To Add New Data)</div>
<template x-for="(item, index) in data" :key="index">
<div>
<span x-text="item.randomNumber"></span>
<span #click="data.splice(index, 1)">Remove</span>
</div>
</template>
</div>
online test: https://codepen.io/yuxufm/pen/YzLoxvE

Related

OnClick of Button , Need to Display the Modal using React Typescript

I have two Components, onClick on Button from "Button Component", I need to Perform Open & Close Operations of that Modal,
But my Modal is in "Dropdown component"... There is No Relation between Button and Dropdown Components, Now How Can I send Onclick event from "Button Component" to "Dropdown component" , and use there to Perform Open & Close Operations of that Modal...
My Button Component
const Button:React.FC = () => {
const [cancel, setcancel] = useState<boolean>(false)
const onCancel=(event: React.MouseEvent<HTMLButtonElement>)=>{
setcancel(true);
}
return (
<button className='btn btn-dark' onClick={onCancel}>Button</button>
)
}
export default Button
My Dropdown component
const Dropdown: React.FC = () => {
const data = inputs;
return (
<>
<div className='form-group'>
<div className="col-4">
<select className="form-select" aria-label="Disabled select example">
<option selected>Select an Option</option>
{data.purpose.map((items) => (
//console.log(items.value);
<option>{items.value}</option>
))}
</select>
</div>
</div>
<div className="modal">
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-header">
<h5 className="modal-title">Modal title</h5>
<button type="button" className="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div className="modal-body">
<p>Do YOu need any Changes</p>
</div>
<div className="modal-footer">
<button type="button" className="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" className="btn btn-primary">Save changes</button>
</div>
</div>
</div>
</div>
</>
)
}
export default Dropdown
Here is APP.tsx
function App() {
return (
<div className="App">
<h1>LordShiva</h1>
<Dropdown />
<Button/>
</div>
);
}
export default App;
Make states in App.js
const [isModal,setIsModal] = UseState(false);
Now pass setIsModal in props of Button Component in your app.js file
<Button passedFunc={()=>setIsModal(!isModal)}/>
Now get this passedFunc in Button Component parameters in props and call it on onClick of button like onClick={() => props.passedFunc()}
Now pass isModal state in your DropDown Component,
<Dropdown show={isModal} />
Now get this show property in your dropdown components props.And then wrap the modal code like that :
{props.show ? <div className="modal">
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-header">
<h5 className="modal-title">Modal title</h5>
<button type="button" className="btn-close" data-
bs-dismiss="modal" aria-label="Close"></button>
</div>
<div className="modal-body">
<p>Do YOu need any Changes</p>
</div>
<div className="modal-footer">
<button type="button" className="btn btn-secondary"
data-bs-dismiss="modal">Close</button>
<button type="button" className="btn btn-
primary">Save changes</button>
</div>
</div>
</div>
</div> : null}
Note: you can destructure props too.

Expand Collapse Icons with Vue Js and Bootstrap

I have a Bootstrap accordion with the expand collapse panels,which works fine.
But when expanded it should display minus icon which is not working well for me.
When I click to expand all the panel's icons are changed to minus instead of just changing the one which was expanded.
I have my code in the Vue JS template as below
Am calling the toggle function onclick to toggle the icons, but its doing it for all the other panels too.
How can I fix this? Thanks in advance.
Vue.component('accordion', {
data: function () {
return {
alerts: [],
sound: '',
collapsed:true
}
},
template: `
<div>
<div v-for="(alert, index ) in alerts" class="panel panel-default">
<div class="panel-heading" v-bind:style="'background-color:'+alert.color" role="tab" v-bind:id="'heading'+index" >
<a role="button" data-toggle="collapse" data-parent="#accordion"
v-bind:href="'#collapse'+index" aria-expanded="true" v-on:click="toggle">
<i id="collapseExpand" v-show="collapsed" class="more-less fa fa-plus"></i>
<i id="collapseExpand" v-show="!collapsed" class="more-less fa fa-minus"></i>
<h4 class="panel-title">#{{ alert.description }}</h4></a>
</div>
<div v-bind:id="'collapse'+index" class="panel-collapse collapse" role="tabpanel">
<div class="panel-body">
<div>#{{ alert.comment }}</div>
<div class="row">
<form v-bind:id="'form_'+index" v-bind:name="'form_'+index" v-bind:action="route" method="POST" style="display: inline;">
<input type="hidden" name="_token" :value="csrf">
<input type ="hidden" v-bind:id="'trigger_id_'+alert.triggerid" name = "trigger_id" v-bind:value="alert.triggerid">
<div class="col-lg-12">
<div class="input-group">
<input type="text" v-bind:id="'ack_msg_'+alert.triggerid" name="ack_msg" class="form-control"
placeholder="Acknowledge Message...">
<span class="input-group-btn">
<button class="btn btn-primary" type="submit">Save</button>
</span>
</div>
</div>
</form>
</div>
</div>
<div class="panel-footer">#{{ alert.timestamp }}</div>
</div>
</div>
<input type="hidden" id="audioFile" name="audioFile" v-bind:value="sound">
</div>`,
mounted: function () {
this.loadData();
setInterval(function () {
this.loadData();
}.bind(this), 1000);
},
methods: {
loadData: function () {
$.get('{{ route('getAlertsPersistent') }}', function (response) {
this.alerts = response;
this.sound = this.alerts[0].sound
}.bind(this));
},
toggle(){
this.collapsed = !this.collapsed ;
}
},
});
new Vue({el: '#accordion'});
You want to separate your accordion item that you want to loop. In that way you can have isolated states in each component.
<div>
<accordion-item
v-for="(alert, index) in alerts"
:alert="alert"
:key="index">
</accordion-item>
</div>
inside your <accordion-item/> you should have collapsed inside your data
The other way around is storing the toggled items in array.
export default {
data: () => ({
toggled: []
}),
methods: {
isActive (item) {
return this.toggled.indexOf(item) >= 0
},
toggleItem (item) {
const index = this.toggled.indexOf(item)
if (index >= 0) {
this.toggled.splice(index, 1)
return
}
this.toggled.push(item)
}
}
}
So you can use it now as follows
<a
role="button"
data-toggle="collapse"
data-parent="#accordion"
v-bind:href="'#collapse'+index"
aria-expanded="true"
v-on:click="toggleItem(index)">
<i
:class="[isActive(index) ? 'fa-minus' : 'fa-plus']"
class="more-less fa"></i>
<h4 class="panel-title">#{{ alert.description }}</h4>
</a>
btw you're looping an id=collapseExpand which will cause you problem. instead try :id="'collapseExpand' + index"

How do you use confirm dialogues in a custom Laravel Nova tool?

Is it possible to use the built in Laravel Nova confirm dialogue in your own tool? All I would like to use is interact with it how Nova does itself.
The docs are quite light on the JS topic, as the only built in UI you seem to be able to work with is the toasted plugin: https://nova.laravel.com/docs/1.0/customization/frontend.html#javascript
You can use <modal> component whenever you want.
Here is how it work internally in Nova:
<template>
<modal
data-testid="confirm-action-modal"
tabindex="-1"
role="dialog"
#modal-close="handleClose"
class-whitelist="flatpickr-calendar"
>
<form
autocomplete="off"
#keydown="handleKeydown"
#submit.prevent.stop="handleConfirm"
class="bg-white rounded-lg shadow-lg overflow-hidden"
:class="{
'w-action-fields': action.fields.length > 0,
'w-action': action.fields.length == 0,
}"
>
<div>
<heading :level="2" class="border-b border-40 py-8 px-8">{{ action.name }}</heading>
<p v-if="action.fields.length == 0" class="text-80 px-8 my-8">
{{ __('Are you sure you want to run this action?') }}
</p>
<div v-else>
<!-- Validation Errors -->
<validation-errors :errors="errors" />
<!-- Action Fields -->
<div class="action" v-for="field in action.fields" :key="field.attribute">
<component
:is="'form-' + field.component"
:errors="errors"
:resource-name="resourceName"
:field="field"
/>
</div>
</div>
</div>
<div class="bg-30 px-6 py-3 flex">
<div class="flex items-center ml-auto">
<button
dusk="cancel-action-button"
type="button"
#click.prevent="handleClose"
class="btn text-80 font-normal h-9 px-3 mr-3 btn-link"
>
{{ __('Cancel') }}
</button>
<button
ref="runButton"
dusk="confirm-action-button"
:disabled="working"
type="submit"
class="btn btn-default"
:class="{
'btn-primary': !action.destructive,
'btn-danger': action.destructive,
}"
>
<loader v-if="working" width="30"></loader>
<span v-else>{{ __('Run Action') }}</span>
</button>
</div>
</div>
</form>
</modal>
</template>
<script>
export default {
props: {
working: Boolean,
resourceName: { type: String, required: true },
action: { type: Object, required: true },
selectedResources: { type: [Array, String], required: true },
errors: { type: Object, required: true },
},
/**
* Mount the component.
*/
mounted() {
// If the modal has inputs, let's highlight the first one, otherwise
// let's highlight the submit button
if (document.querySelectorAll('.modal input').length) {
document.querySelectorAll('.modal input')[0].focus()
} else {
this.$refs.runButton.focus()
}
},
methods: {
/**
* Stop propogation of input events unless it's for an escape or enter keypress
*/
handleKeydown(e) {
if (['Escape', 'Enter'].indexOf(e.key) !== -1) {
return
}
e.stopPropagation()
},
/**
* Execute the selected action.
*/
handleConfirm() {
this.$emit('confirm')
},
/**
* Close the modal.
*/
handleClose() {
this.$emit('close')
},
},
}
</script>
Here is simplified example:
<modal>
<form
autocomplete="off"
class="bg-white rounded-lg shadow-lg overflow-hidden"
>
<div>
<heading :level="2" class="border-b border-40 py-8 px-8">test</heading>
test
</div>
<div class="bg-30 px-6 py-3 flex">
<div class="flex items-center ml-auto">
<button
type="button"
class="btn text-80 font-normal h-9 px-3 mr-3 btn-link"
>
{{ __('Cancel') }}
</button>
<button
ref="runButton"
type="submit"
class="btn-danger"
>
<span>{{ __('Run Action') }}</span>
</button>
</div>
</div>
</form>
</modal>
You need to create a new component in the same folder of Tool.vue
I'll attach the component I used here
Then in the "handleConfirm" method, you can add a Ajax call to API
You can add you logic in that API.
You can find the API file in path, ToolName/routes/api.php
/* CustomModal.vue */
<template>
<modal tabindex="-1" role="dialog" #modal-close="handleClose">
<form #keydown="handleKeydown" #submit.prevent.stop="handleConfirm" class="bg-white rounded-lg shadow-lg overflow-hidden w-action">
<div>
<heading :level="2" class="border-b border-40 py-8 px-8">Confirm action</heading>
<p class="text-80 px-8 my-8"> Are you sure you want to perform this action? </p>
</div>
<div class="bg-30 px-6 py-3 flex">
<div class="flex items-center ml-auto">
<button type="button" #click.prevent="handleClose" class="btn btn-link dim cursor-pointer text-80 ml-auto mr-6">
Cancel
</button>
<button :disabled="working" type="submit" class="btn btn-default btn-primary">
<loader v-if="working" width="30"></loader>
<span v-else>Confirm</span>
</button>
</div>
</div>
</form>
</modal>
</template>
<script>
export default {
methods: {
handleConfirm() {
// Here you can add an ajax call to API and you can add your logic there.
},
handleClose() {
// Logic to hide the component
},
},
}
</script>
For more detailed explanation : https://medium.com/vineeth-vijayan/how-to-use-confirm-dialogs-in-a-laravel-nova-tool-b16424ffee87

Can't pass Vue variable to hidden input v-model in view(v-for)

I'm new to Vue JS and I'm building an application with Laravel Spark and trying to utilize Vue as much as possible.
I have a form to simply add an 'Asset Type' with a component. Once the Asset Type is successfully created, a list of properties is grabbed from the database and set to a 'data' attribute. In my view(I'm using an inline template), I have a 'v-for' that creates a form for each property that has two hidden inputs for the property id and the type id, and one "Add" button that assigns the property to the newly created type.
THE PROBLEM:
I can't seem to assign the value of the hidden inputs within the view while using v-models. When I submit one of the forms, the form request data always returns the initial data value from the new SparkForm object.
In other words, I need to assign the hidden input values within the v-for loop in the view.
Here's my component:
Vue.component('new-asset-type', {
props: [],
data() {
return {
// type_id: this.type_id,
properties: this.properties,
newAssetType: new SparkForm({
label: null,
enabled: false,
}),
assignPropForm: new SparkForm({
prop_id: "",
type_id: "",
}),
};
},
methods: {
createType: function () {
Spark.post('/asset-types', this.newAssetType)
.then(response => {
this.type_id = response.type_id;
axios.get('/getTypeNotProps/' + this.type_id).then((response) => {
this.properties = response.data;
console.log(this.properties);
});
})
.catch(response => {
console.log("fail");
});
},
assignProp: function () {
Spark.post('/asset-properties/add', this.assignPropForm)
.then(response => {
console.log(response);
})
.catch(response => {
console.log("fail");
});
}
}
});
And here's my view:
#extends('spark::layouts.app')
#section('content')
<new-asset-type inline-template>
<div class="container">
<!-- Application Dashboard -->
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Add a New Asset Type</div>
<div class="panel-body" id="addTypeForm">
<div class="form-horizontal">
<div class="form-group" :class="{'has-error': newAssetType.errors.has('label')}">
{{ Form::label('label', 'Label', ['class' => 'col-md-4 control-label']) }}
<div class="col-md-6" >
<input type="test" name="label" v-model="newAssetType.label">
<span class="help-block" v-show="newAssetType.errors.has('label')">
#{{ newAssetType.errors.get('label') }}
</span>
</div>
</div>
<div class="form-group">
{{ Form::label('enabled', 'Enabled?', ['class' => 'col-md-4 control-label']) }}
<div class="col-md-6">
<input type="checkbox" name="enabled" v-model="newAssetType.enabled" >
</div>
</div>
<!-- Submit -->
<div class="form-group">
<div class="col-md-8 col-md-offset-4">
<button class="btn btn-primary" #click="createType" :disabled="newAssetType.busy">
Create Asset Type
</button>
</div>
</div>
<div id="assignProps" v-if="newAssetType.successful">
<!-- Button trigger modal -->
<button type="button" class="btn btn-primary btn-lg" data-toggle="modal" data-target="#myModal">
Add Property
</button>
<!-- Modal -->
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title" id="myModalLabel">Add New Property to Asset Type</h4>
</div>
<div class="modal-body">
<assign-asset-prop v-for="property in properties" class="panel panel-info property-item">
<div class="panel-heading">#{{ property.label }}</div>
<div class="panel-body"><strong>Input Type: </strong>#{{ property.input_type }}
<div class="pull-right">
<input type="hidden" name="prop_id" v-bind:value="property.p_id" v-model="assignPropForm.prop_id">
<input type="hidden" name="type_id" v-bind:value="property.p_id" v-model="assignPropForm.type_id">
<button class="btn btn-primary" #click="assignProp" :disabled="assignPropForm.busy">
Add
</button>
</div>
</div>
</assign-asset-prop>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save changes</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</new-asset-type>
#endsection
Thanks to the helpful comments, I learned that I shouldn't have been using hidden inputs at all. Instead, I simply passed the variables to the function on the click event.
<button class="btn btn-primary" #click="assignProp(type_id, property.p_id)" >
Add
</button>
Then in my component
methods: {
assignProp: function (type_id, property_id) {
var assignPropForm = new SparkForm({
propvalue: property_id,
typevalue: type_id,
});
Spark.post('/asset-properties/add', assignPropForm)
.then(response => {
console.log(response);
})
.catch(response => {
console.log("fail");
});
}
}
You need store variables at local data() dep., and geting it by getters function.

How to define extra button in fine uploader template that will trigger its own end action

I want to have
- a text field and a button. Upon click of button text data will need to posted to endpoint.
The text field and button will repeat for each file in the uploader.
Below my attempt, and it is not working properly in all the cases and want to get to better solution.
The text field: qq-edit-caption
The button: qq-upload-update-caption
The button acts as delete button with selector 'qq-upload-delete-selector' and to differentiate I set a flag true on the onclick action (crappy I know!).
I read lot about creating 'extraButton' and all it talks about(as per my understanding) creating additional upload button, buy I need button on each file like DELETE and CANCEL,and not sure how I can cleanly implement the solution. Any help is much appreciated.
My template
<div class='qq-uploader-selector qq-uploader' qq-drop-area-text='Drop files here'>
<div class='qq-total-progress-bar-container-selector qq-total-progress-bar-container'>
<div role='progressbar' aria-valuenow='0' aria-valuemin='0' aria-valuemax='100' class='qq-total-progress-bar-selector qq-progress-bar qq-total-progress-bar'></div>
</div>
<div class='qq-upload-drop-area-selector qq-upload-drop-area' qq-hide-dropzone>
<span class='qq-upload-drop-area-text-selector'></span>
</div>
<div class='buttons'>
<div class='qq-upload-button-selector qq-upload-button'>
<div>Select files</div>
</div>
<button type='button' id='trigger-upload-section<?php echo $ind; ?>' class='btn btn-primary file-section-button'>
<i class='icon-upload icon-white'></i> Upload
</button>
</div>
<span class='qq-drop-processing-selector qq-drop-processing'>
<span>Processing dropped files...</span>
<span class='qq-drop-processing-spinner-selector qq-drop-processing-spinner'></span>
</span>
<ul class='qq-upload-list-selector qq-upload-list' aria-live='polite' aria-relevant='additions removals'>
<li>
<div class='qq-progress-bar-container-selector'>
<div role='progressbar' aria-valuenow='0' aria-valuemin='0' aria-valuemax='100' class='qq-progress-bar-selector qq-progress-bar'></div>
</div>
<span class='qq-upload-spinner-selector qq-upload-spinner'></span>
<div class="filewrapper">
<div class="filecontainer">
<div class="filecontent">
<img class='qq-thumbnail-selector' qq-max-size='80' qq-server-scale>
</div>
<span class="qq-upload-file-selector qq-upload-file filename"></span>
</div>
<div class="filesidebar">
<!--span class='qq-upload-caption-selector qq-upload-caption'></span-->
<!--span class='qq-edit-caption-icon-selector qq-edit-caption-icon kk-editable' aria-label='Edit caption'></span-->
<input class='qq-edit-caption-selector qq-edit-caption kk-editing' placeholder='Enter Caption here ...' onchange='captionUpdate();'>
</div>
<div class="filesidebar2">
<button type='button' class='qq-btn qq-upload-delete-selector qq-upload-update-caption file-section-button btn-primary' onclick='captionUpdate(true);' title='Caption Update' disabled><i class='icon-pencil icon-white'></i></button>
<button type='button' class='qq-btn qq-upload-cancel-selector qq-upload-cancel'>Cancel</button>
<button type='button' class='qq-btn qq-upload-retry-selector qq-upload-retry'>Retry</button>
<button type='button' class='qq-btn qq-upload-delete-selector qq-upload-delete file-section-button btn-primary' onclick='captionUpdate(false);' title='Delete File'><i class='icon-trash icon-white'></i></button>
</div>
<span role='status' class='qq-upload-status-text-selector qq-upload-status-text'></span>
<div class="fileclear"></div>
</div>
</li>
</ul>
<dialog class='qq-alert-dialog-selector'>
<div class='qq-dialog-message-selector'></div>
<div class='qq-dialog-buttons'>
<button type='button' class='qq-cancel-button-selector'>Close</button>
</div>
</dialog>
<dialog class='qq-confirm-dialog-selector'>
<div class='qq-dialog-message-selector'></div>
<div class='qq-dialog-buttons'>
<button type='button' class='qq-cancel-button-selector'>No</button>
<button type='button' class='qq-ok-button-selector'>Yes</button>
</div>
</dialog>
<dialog class='qq-prompt-dialog-selector'>
<div class='qq-dialog-message-selector'></div>
<input type='text'>
<div class='qq-dialog-buttons'>
<button type='button' class='qq-cancel-button-selector'>Cancel</button>
<button type='button' class='qq-ok-button-selector'>Ok</button>
</div>
</dialog>
</div>
Caption Update Button Click:
uploaderContainer.addEventListener('click', function(event) {
isCaptionUpdateClick = false;
if (event.target.className.indexOf('icon-pencil') >= 0) {
isCaptionUpdateClick = true;
};
});
My Delete Parameter setup:
deleteFile: {
enabled: true,
method: "POST",
endpoint: "/ci/fine/endpoint",
params: {
qqcaptionupdate: function() {
if (isCaptionUpdateClick === true) {
return 'YES';
}
},
qqcaption: function() {
return captionValue;
},
bookid: function() {
return $("#book_id").val();
},
sectionid: function() {
return $('#'+uploaderInfo.sectionIdSelector+'').val();
},
}
}

Resources