I recently ran into an issue when building a Laravel Livewire component where the javascript portion wouldn't update when a select input changed. The component is a chart from the Chartist.js library and it displays on load but when I change the select input the chart disappears. I came up with a solution but it feels dirty, anyone have a better solution to this.
line-chart.blade.php
<div class="mt-6">
<h2 class="text-xl text-gray-600 py-3 font-bold">Location Views</h2>
<div class="bg-white rounded-lg shadow overflow-hidden overflow-y-scroll">
<div class="flex justify-end px-10 py-6">
<select wire:model="days" class="block w-40 pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-yellow-500 focus:border-yellow-500 sm:text-sm rounded-md">
<option value="30">Last 30 Days</option>
<option value="365">Last 12 Months</option>
</select>
</div>
<div id="line-chart" class="relative ct-chart">
<div class="hidden absolute inline-block chartist-tooltip bg-white text-xs shadow text-center px-3 py-1 rounded-md w-36">
<span class="chartist-tooltip-key"></span><br>
<span class="chartist-tooltip-date"></span><br>
<span class="chartist-tooltip-value"></span>
</div>
</div>
</div>
</div>
#push('styles')
<link rel="stylesheet" href="//cdn.jsdelivr.net/chartist.js/latest/chartist.min.css">
#endpush
#push('scripts')
<script src="//cdn.jsdelivr.net/chartist.js/latest/chartist.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartist-plugin-tooltips#0.0.17/dist/chartist-plugin-tooltip.min.js"></script>
#endpush
#push('js')
<script>
document.addEventListener('livewire:load', function () {
setTimeout(() => {
Livewire.emit('updateJS')
})
Livewire.on('updateJS', function () {
var data = {
labels: #this.labels,
// Our series array that contains series objects or in this case series data arrays
series: #this.data,
};
var options = {
height: 300,
fullWidth: true,
chartPadding: 40,
axisX: {
offset: 12,
showGrid: true,
showLabel: true,
},
axisY: {
offset: 0,
showGrid: true,
showLabel: true,
onlyInteger: true,
},
}
new Chartist.Line('#line-chart', data, options).on("draw", function (data) {
if (data.type === "point") {
data.element._node.addEventListener('mouseover', e => {
const tooltip = document.getElementsByClassName('chartist-tooltip')
tooltip[0].style.top = data.y - 75 + 'px'
tooltip[0].style.left = data.x > 200 ? data.x - 150 + 'px' : data.x + 'px'
tooltip[0].classList.remove('hidden')
const key = document.getElementsByClassName('chartist-tooltip-key')
key[0].innerHTML = data.meta[1]
const meta = document.getElementsByClassName('chartist-tooltip-date')
meta[0].innerHTML = data.meta[0]
const value = document.getElementsByClassName('chartist-tooltip-value')
value[0].innerHTML = data.value.y === 1 ? data.value.y + ' view' : data.value.y + ' views'
})
data.element._node.addEventListener('mouseleave', e => {
const tooltip = document.getElementsByClassName('chartist-tooltip')
tooltip[0].classList.add('hidden')
})
}
})
})
})
</script>
#endpush
LineChart.php
<?php
namespace App\Http\Livewire\Components;
use Carbon\Carbon;
use Carbon\CarbonPeriod;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\View\Factory;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Collection;
use Livewire\Component;
class LineChart extends Component
{
/**
* #var Collection
*/
public Collection $data;
/**
* #var array
*/
public array $labels;
/**
* #var int
*/
public int $days = 30;
/**
*
*/
public function mount()
{
$this->data();
$this->labels = $this->labels();
}
/**
* Trigger mount when days is updated.
*/
public function updatedDays()
{
$this->mount();
$this->emit('updateJS');
}
/**
* #return Application|Factory|View
*/
public function render()
{
return view('livewire.components.line-chart');
}
/**
* Generates the chart data.
*/
public function data()
{
$locations = request()->user()->locations;
if ($this->days === 30) {
$this->data = $locations->map(function ($location) {
return $this->getDatesForPeriod()->map(function ($date) use ($location) {
return [
'meta' => [
Carbon::parse($date)->format('M j'),
$location->name
],
'value' => $location->views->filter(function ($view) use ($date) {
return $view->viewed_on->toDateString() === $date;
})->count()
];
})->toArray();
});
}
if ($this->days === 365) {
$this->data = $locations->map(function ($location) {
return $this->getDatesForPeriod()->map(function ($date) use ($location) {
return [
'meta' => [
Carbon::parse($date)->format('M'),
$location->name
],
'value' => $location->views->filter(function ($view) use ($date) {
return $view->viewed_on->month === Carbon::parse($date)->month;
})->count()
];
})->toArray();
});
}
}
/**
* Creates the labels for the chart.
*
* #return array
*/
protected function labels()
{
return $this->getDatesForPeriod()->map(function ($date) {
if ($this->days === 30) {
return Carbon::parse($date)->format('M j');
} else {
return Carbon::parse($date)->format('M');
}
})->toArray();
}
/**
* Gets the dates for the specified period.
*
* #return Collection
*/
protected function getDatesForPeriod()
{
if ($this->days === 30) {
return collect(CarbonPeriod::create(now()->subDays($this->days)->toDateString(), now()->toDateString()))
->map(function ($date) {
return $date->toDateString();
});
}
if ($this->days === 365) {
return collect(now()->startOfMonth()->subMonths(11)->monthsUntil(now()))
->map(function ($date) {
return $date->toDateString();
});
}
}
}
If I change document.addEventListener('livewire:load', function () {} to livewire:update then the chart works as expected when I use the select input, but then the chart doesn't display on load. So to get around this I had to set a timeout and trigger an event that displays the chart on load but will also display the chart on update. I feel like there is a better way to do this, I'm just missing something.
To get this to work I removed the setTimeout() and the Livewire.On('updateJS') and added another event listener inside of livewire:load that listens for livewire:update and in here we're updating the chart with
chart.update({labels: #this.labels, series: #this.data})
Here is the entire code snippet. I feel like this is a much cleaner solution and doesn't feel dirty lol.
<script>
document.addEventListener('livewire:load', function () {
var data = {
labels: #this.labels,
// Our series array that contains series objects or in this case series data arrays
series: #this.data,
};
var options = {
height: 300,
fullWidth: true,
chartPadding: 40,
axisX: {
offset: 12,
showGrid: true,
showLabel: true,
},
axisY: {
offset: 0,
showGrid: true,
showLabel: true,
onlyInteger: true,
},
}
const chart = new Chartist.Line('#line-chart', data, options).on("draw", function (data) {
if (data.type === "point") {
data.element._node.addEventListener('mouseover', e => {
const tooltip = document.getElementsByClassName('chartist-tooltip')
tooltip[0].style.top = data.y - 75 + 'px'
tooltip[0].style.left = data.x > 200 ? data.x - 150 + 'px' : data.x + 'px'
tooltip[0].classList.remove('hidden')
const key = document.getElementsByClassName('chartist-tooltip-key')
key[0].innerHTML = data.meta[1]
const meta = document.getElementsByClassName('chartist-tooltip-date')
meta[0].innerHTML = data.meta[0]
const value = document.getElementsByClassName('chartist-tooltip-value')
value[0].innerHTML = data.value.y === 1 ? data.value.y + ' view' : data.value.y + ' views'
})
data.element._node.addEventListener('mouseleave', e => {
const tooltip = document.getElementsByClassName('chartist-tooltip')
tooltip[0].classList.add('hidden')
})
}
})
document.addEventListener('livewire:update', function () {
chart.update({labels: #this.labels, series: #this.data})
})
})
</script>
I never used the chartist, but I usually use this approach (let me know if the code make sense to you).
<div class="mt-6" x-data="{
labels: #entangle('labels'),
series: #entangle('data'),
chart: null
}"
x-init="() => {
const options = {}; // I'll omit part of the code here
chart = new Chartist.Line('#line-chart', data, options);
},
$watch('series', (dataChart) => {
// I usually put both, the series data and the labels in an associative array on the livewire component back-end
const { series, labels } = dataChart;
chart.update(dataChart);
"
>
<h2 class="text-xl text-gray-600 py-3 font-bold">Location Views</h2>
<div class="bg-white rounded-lg shadow overflow-hidden overflow-y-scroll">
<div class="flex justify-end px-10 py-6">
<select wire:model="days" class="block w-40 pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-yellow-500 focus:border-yellow-500 sm:text-sm rounded-md">
<option value="30">Last 30 Days</option>
<option value="365">Last 12 Months</option>
</select>
</div>
<div id="line-chart" class="relative ct-chart">
<div class="hidden absolute inline-block chartist-tooltip bg-white text-xs shadow text-center px-3 py-1 rounded-md w-36">
<span class="chartist-tooltip-key"></span><br>
<span class="chartist-tooltip-date"></span><br>
<span class="chartist-tooltip-value"></span>
</div>
</div>
</div>
</div>
I think that watching a value from the livewire component it's easier to debug. The use of events to make the communication between back and front-end can make debug challenge when the page gets bigger (and with a lot of events).
With this code your char will be loaded after livewire initial load (you don't need to check the 'livewire:load' event). The chart instance will not be mounted every time you dispatch the updateJS event, only the data will be update.
Ps: I don't know how Chartist works, so I didn't put the code on chart.update(...), but since it's a chart lib it should have an options to update the data. I just want to show you my approach, hope it helps.
Related
I want to make tab switcher auto decide the slot for the switcher but when I am trying to make it dynamic with the help of observable no data is showing the tab content area until I write the slot area statically. With observable variable, the slot is not getting the selected Slot value.
Please check how I can do this.
slot = [[selectedSlot]] //using for the slot value in html
this.selectedSlot = ko.observable('settings');
<div id="tabbardemo">
<oj-dialog class="tab-dialog hidden" id="tabDialog" dialog-title="Tab data">
<div slot="body">
<oj-form-layout>
<oj-input-text id="t1" value="{{newTabTitle}}" label-hint="Title"></oj-input-text>
</oj-form-layout>
</div>
<div slot="footer">
<oj-button id="idOK" on-oj-action="[[addTab]]">OK</oj-button>
<oj-button id="idCancel" on-oj-action="[[closeDialog]]">Cancel</oj-button>
</div>
</oj-dialog>
<oj-button id="addTab" on-oj-action="[[openDialog]]">Add Tab</oj-button>
<br/>
<br/>
<oj-tab-bar contextmenu="tabmenu" id="hnavlist" selection="{{selectedItem}}" current-item="{{currentItem}}" edge="top" data="[[dataProvider]]"
on-oj-remove="[[onRemove]]">
<template slot="itemTemplate" data-oj-as="item">
<li class="oj-removable" :class="[[{'oj-disabled' : item.data.disabled}]]">
<a href="#">
<oj-bind-text value="[[item.data.name]]"></oj-bind-text>
</a>
</li>
</template>
<oj-menu slot="contextMenu" class="hidden" aria-label="Actions">
<oj-option data-oj-command="oj-tabbar-remove">
Removable
</oj-option>
</oj-menu>
</oj-tab-bar>
<oj-switcher value="[[selectedItem]]">
<div slot="[[selectedSlot]]"
id="home-tab-panel"
role="tabpanel"
aria-labelledby="home-tab">
<div class="demo-tab-content-style">
<h2>Home page content area</h2>
</div>
</div>
<div slot="tools"
id="tools-tab-panel"
role="tabpanel"
aria-labelledby="tools-tab">
<div class="demo-tab-content-style">
<h1>Tools Area</h1>
</div>
</div>
<div slot="base"
id="base-tab-panel"
role="tabpanel"
aria-labelledby="ba`enter code here`se-tab">
<div class="demo-tab-content-style">
<h1>Base Tab</h1>
</div>
</div>
</oj-switcher>
<br>
<div>
<p class="bold">Last selected list item:
<span id="results">
<oj-bind-text value="[[selectedItem]]"></oj-bind-text>
</span>
</p>
</div>
</div>
JS code below
require(['ojs/ojcontext',
'knockout',
'ojs/ojbootstrap',
'ojs/ojarraydataprovider',
'ojs/ojknockout',
'ojs/ojnavigationlist',
'ojs/ojconveyorbelt',
'ojs/ojdialog',
'ojs/ojbutton',
'ojs/ojinputtext',
'ojs/ojformlayout',
'ojs/ojswitcher',
],
function (Context, ko, Bootstrap, ArrayDataProvider) { // this callback gets executed when all required modules are loaded
function ViewModel() {
this.data = ko.observableArray([{
name: 'Settings',
id: 'settings'
},
{
name: 'Tools',
id: 'tools'
},
{
name: 'Base',
id: 'base'
}
]);
this.selectedSlot = ko.observable('settings'); //Sepecifically mentioned to show what it is the objective
this.dataProvider = new ArrayDataProvider(this.data, { keyAttributes: 'id' });
this.selectedItem = ko.observable('settings');
this.currentItem = ko.observable();
this.tabCount = 0;
this.newTabTitle = ko.observable();
this.delete = (function (id) {
var hnavlist = document.getElementById('hnavlist');
var items = this.data();
for (var i = 0; i < items.length; i++) {
if (items[i].id === id) {
this.data.splice(i, 1);
Context.getContext(hnavlist)
.getBusyContext()
.whenReady()
.then(function () {
hnavlist.focus();
});
break;
}
}
}).bind(this);
this.onRemove = (function (event) {
this.delete(event.detail.key);
event.preventDefault();
event.stopPropagation();
}).bind(this);
this.openDialog = (function () {
this.tabCount += 1;
this.newTabTitle('Tab ' + this.tabCount);
document.getElementById('tabDialog').open();
}).bind(this);
this.closeDialog = function () {
document.getElementById('tabDialog').close();
};
this.addTab = (function () {
var title = this.newTabTitle();
var tabid = 'tid' + this.tabCount;
this.data.push({
name: title,
id: tabid
});
this.closeDialog();
}).bind(this);
}
Bootstrap.whenDocumentReady().then(function () {
ko.applyBindings(new ViewModel(), document.getElementById('tabbardemo'));
});
}
);
It is a bit complex to understand when you copy from JET cookbook. You have done almost everything right. Just make the following changes:
1) Remove this:
Bootstrap.whenDocumentReady().then(function () {
ko.applyBindings(new ViewModel(), document.getElementById('tabbardemo'));
});
Why? The bootstrapping is required once per application, which is done inside your main.js file.
2) Replace require by define
Why? Require block is again maintained in main.js, where your required modules are pre-loaded. All subsequent viewModels have define block
3) Return an instance of your ViewModel
define([
... Your imports
],
function (Context, ko, Bootstrap, ArrayDataProvider) { // this callback gets executed when all required modules are loaded
function ViewModel() {
// Your code
}
return ViewModel;
});
I have chart bar and I am trying to show data by date wise when I filter by date but unfortunately data does not show on chart bar. I want that when I select a start date and end date record should be shown on chart bar by date wise.
Controller
public function status(Request $request)
{
$date = explode(' - ', $request->date);
$manager_hourlog = Hourlog::with('project', "user")->whereBetween('date', $date)
->get()
->groupBy('project.name');
$projects = [];
$totals = [];
foreach ($manager_hourlog as $key => $val) {
$projects[] = $key;
}
foreach ($manager_hourlog as $key2 => $val) {
$minutes = $val->sum('hour_work');
$totals[] = round($minutes / 60, 1);
}
$users = User::where("status", 1)->get();
$data = [
// manager report
'manager_projects' => $projects,
'totals' => $totals,
"manager_hourlog" => $manager_hourlog,
];
return $data;
return response()->json($data, 200);
}
html view
<form action="{{route('dashboard')}}" method="post" >
<div class="form-group">
<div class="input-group theme-input-group select-max-h-200">
<div class="input-group-prepend width-160px">
<div class="input-group-text">Date
</div>
</div>
<input parsley-trigger="change" required type="text" id="date" class="form-control" name="date" autocomplete="off">
<div class="input-group-append">
<span class="input-group-text">
<i class="md md-event-note">
</i>
</span>
</div>
</div>
<!-- input-group -->
</div>
</div>
<div class="col-lg-1">
<div class="text-right">
<button class="btn btn-light-theme waves-effect waves-light" id="search" name="search" type="submit">
<i class="fa fa-search pr-1">
</i> Search
</button>
</div>
</div>
</form>
script
<script type="text/javascript">
$(function () {
$('#search').on('click', function () {
var date = $("#date").val();
$.ajax({
type: 'GET',
url: '{{Route("dashboard.status")}}',
data: {
date: date
},
dataType: "JSon",
success: function(response){
// Employee report script
var colors = ["#1abc9c", "#2ecc71", "#3498db", "#9b59b6", "#34495e", "#16a085", "#27ae60", "#2980b9", "#8e44ad", "#2c3e50", "#f1c40f", "#e67e22", "#e74c3c", "#ecf0f1", "#95a5a6", "#f39c12", "#d35400", "#c0392b", "#bdc3c7", "#7f8c8d"];
#if ($auth->user_type != 1)
// manager report script
var managerchartbar = {
labels: {!! json_encode($manager_projects) !!},
datasets: [
#foreach($users as $user)
{
label: {!! json_encode($user->name) !!},
backgroundColor: colors[Math.floor(Math.random() * colors.length)],
data: [
#foreach($manager_hourlog as $hourlog)
{{$hourlog->where("user_id", $user->id)->sum("hour_work") / 60}},
#endforeach
]
},
#endforeach
]
};
var ctx = document.getElementById('manager').getContext('2d');
window.myBar = new Chart(ctx, {
type: 'bar',
data: managerchartbar,
options: {
title: {
display: true,
text: 'Project Report chart'
},
tooltips: {
mode: 'index',
intersect: false
},
responsive: true,
scales: {
xAxes: [{
stacked: true,
}],
yAxes: [{
stacked: true
}]
}
}
});
#endif
console.log(response);
},
error: function(xhr){
console.log(xhr.responseText);
}});
});
});
</script>
The Problem
I have a search component and component which implements the search component. When I type something in the search bar after 1/2 second of not typing (debounce) the server should be hit and the results should be returned.
The solution i am trying to implement comes from this post on Stackoverflow
The code
This leads me to the following code.
I have search.vue
<template>
<label for="search">
<input
id="search"
class="w-full py-2 px-1 border-gray-900 border"
type="text"
name=":searchTitle"
v-model="searchFilter"
:placeholder="searchPlaceholder"
autocomplete="off"
v-on:keydown="filteredDataset"
/>
</label>
</template>
<script>
import {debounce} from 'lodash';
export default {
props: {
searchPlaceholder: {
type: String,
required: false,
default: ''
},
searchName: {
type: String,
required: false,
default: 'search'
}
},
data() {
return {
searchFilter: '',
}
},
methods: {
filteredDataset() {
console.log('event fired');
this.$emit('searchValue', this.searchFilter);
}
},
}
</script>
And product.vue
<template>
<div>
<div class="my-4">
<search
search-placeholder=""
search-name=""
v-on:searchValue="filterValue = $event"
v-model="productsFiltered"
>
</search>
<div class="flex w-full py-1 border px-2 my-2" v-for="product in productsFiltered"> (...)
</div>
</div>
</div>
</div>
</template>
<script>
import {debounce} from 'lodash';
export default {
data() {
return {
products: [],
filterValue: '',
filteredProducts: ''
}
},
computed: {
productsFiltered: {
get(){
console.log('getter called');
return this.filteredProducts;
},
set: _.debounce(function(){
console.log('setter called');
if (this.filterValue.length < 1) {
this.filteredProducts = [];
}
axios.get(`${apiUrl}search/` + this.filterValue)
.then(response => {
this.products = response.data.products;
const filtered = [];
const regOption = new RegExp(this.filterValue, 'ig');
for (const product of this.products) {
if (this.filterValue.length < 1 || product.productname.match(regOption)) {
filtered.push(product);
}
}
this.filteredProducts = filtered;
});
}, 500)
}
},
}
</script>
The result
The result is that the setter in the computed property in product.vue does not get called and no data is fetched from the server. Any ideas on how to solve this?
Your first code block imports debounce but does not use it. It also declares a prop, searchName, that isn't used. These aren't central issues, but clutter makes it harder to figure out what's going on.
Your second code block uses v-model but does not follow the required conventions for getting v-model to work with components:
the component must take a prop named value
the component must emit input events to signal changes to value
You have the component emit searchValue events, and handle them with a v-on that sets a data item. You seem to expect the v-model to call the setter, but as I noted, you haven't hooked it up to do so.
From what's here, you don't even really need to store the input value. You just want to emit it when it changes. Here's a demo:
const searchComponent = {
template: '#search-template',
props: {
searchPlaceholder: {
type: String,
required: false,
default: ''
}
},
methods: {
filteredDataset(searchFilter) {
console.log('event fired');
this.$emit('input', searchFilter);
}
}
};
new Vue({
el: '#app',
data() {
return {
products: [],
filterValue: '',
filteredProducts: ''
}
},
components: {
searchComponent
},
computed: {
productsFiltered: {
get() {
console.log('getter called');
return this.filteredProducts;
},
set: _.debounce(function() {
console.log('setter called');
if (this.filterValue.length < 1) {
this.filteredProducts = [];
}
setTimeout(() => {
console.log("This is the axios call");
this.filteredProducts = ['one','two','three'];
}, 200);
}, 500)
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
<template id="search-template">
<label for="search">
<input
id="search"
class="w-full py-2 px-1 border-gray-900 border"
type="text"
name=":searchTitle"
:placeholder="searchPlaceholder"
autocomplete="off"
#input="filteredDataset"
/>
</label>
</template>
<div id="app">
<div class="my-4">
<search-component search-placeholder="enter something" v-model="productsFiltered">
</search-component>
<div class="flex w-full py-1 border px-2 my-2" v-for="product in productsFiltered"> (...)
</div>
</div>
</div>
I'm coding a to-do list using React hooks.
Every added item has two dropdown list where the user can decide how urgent the task (urgency value) is and how long the thing to do will take (speed value).
Updating either list will add their value into a 'score' property.
By clicking a "Sort" button I can sort the entries based on the score.
Right now the problem is that if I have more then one to-do item with different urgency and speed value, the score will always be the same for both components.
Can somebody help? Thanks
function ToDo(){
const [ input, setInput ] = React.useState('')
const [ toDo, setToDo ] = React.useState([])
const [ score, setScore ] = React.useState(0)
const [ speed, setSpeed ] = React.useState(0)
const [ urgency, setUrgency ] = React.useState(0)
return(
<div>
<h2>List of things to do</h2>
<input
value={ input }
onChange={ (e) => setInput( e.target.value ) }/>
<button
onClick={ () => {
setToDo( toDo.concat(input))
setInput('')
}}>Add
</button>
<ul>
{ toDo.map(( task, idTask ) => {
return (
<li
key={idTask}
score={ speed + urgency }>
{task}<br/>
<select onChange={(e) => { setSpeed(Number(e.target.value)) }}>
<option value={1}>slow</option>
<option value={2}>medium</option>
<option value={3}>fast</option>
</select><br/>
<select onChange={(e) => { setUrgency(Number(e.target.value)) }}>
<option value={1}>non-urgent</option>
<option value={3}>urgent</option>
</select>
<span
onClick={
(index) => {
const newTodos = [...toDo]
newTodos.splice(index, 1);
setToDo( newTodos)
}}>
[-------]
</span>
</li>
)
})
}
</ul>
<button onClick={
() => {
const sortMe = [...toDo].sort((a, b) => b - a)
setToDo( sortMe )
}}>Sort!</button>
</div>
)
}
ReactDOM.render(<ToDo/>, document.getElementById('app'));
You should implement a different data model to achieve that. You should hold an array of objects for your todos (each todo will be an object) and each object should have an urgency property so you can set that individually.
Something like this:
function App() {
const [todos,setTodos] = React.useState([
{ id: 'todo1', text: 'This is todo1', urgency: 0 },
{ id: 'todo2', text: 'This is todo2', urgency: 1 }
]);
function handleClick(id) {
setTodos((prevState) => {
let aux = Array.from(prevState);
aux = aux.map((todo) => {
if (todo.id === id) {
todo.urgency === 0 ? todo.urgency = 1 : todo.urgency = 0;
}
return todo;
});
return aux;
});
}
const todoItems = todos.map((todo) =>
<li
key={todo.id}
className={todo.urgency === 1 ? 'urgent' : 'normal'}
onClick={()=>handleClick(todo.id)}
>
{todo.text}
{!!todo.urgency && '<--- This is urgent'}
</li>
);
return(
<React.Fragment>
<div>
Click on the todos!
</div>
<ul>
{todoItems}
</ul>
</React.Fragment>
);
}
ReactDOM.render(<App/>, document.getElementById('root'));
li {
cursor: pointer;
}
.urgent {
color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>
I found a ajax auto complete and I wan't it to integrate to my form but I can't make it work. Please advise thank you!
[controller]
<?php
namespace App\Http\Controllers;
use App\Purchasetransactions;
use App\AjaxAutocompleteController;
use App\Products;
use App\Categories;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Session;
class PurchasetransactionsController extends Controller
{
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index()
{
$purchasetransactions = Purchasetransactions::all();
return view('orders.index', compact('purchasetransactions'));
}
/**
* Show the form for creating a new resource.
*
* #return \Illuminate\Http\Response
*/
public function create()
{
return view('orders.create');
}
/**
* Store a newly created resource in storage.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$purchasetransactions = Purchasetransactions::create($request->only('products_code','name'));
return redirect(route('orders.index'));
}
/** Auto Complete */
public function productSearch(Request $request){
$query = $request->get('term','');
$products=\DB::table('products');
if($request->type=='product_code'){
$products->where('product_code','LIKE','%'.$query.'%');
}
if($request->type=='product_name'){
$products->where('name','LIKE','%'.$query.'%');
}
$products=$products->get();
$data=array();
foreach ($products as $product) {
$data[]=array('product_code'=>$product->product_code,'name'=>$product->name);
}
if(count($data))
return $data;
else
return ['product_code'=>'','name'=>''];
}
}
[create.blade]
<div class="container">
{!! Form::open(array('route'=>'orders.store')) !!}
<table class="table table-bordered">
<tr>
<th><input class='check_all' type='checkbox' onclick="select_all()"/></th>
<th>S. No</th>
<th>Product Code</th>
<th>product name</th>
</tr>
<tr>
<td><input type='checkbox' class='chkbox'/></td>
<td><span id='sn'>1.</span></td>
<td><input class="form-control autocomplete_txt" type='text' data-type="product_code" id='product_code_1' name='product_code[]'/></td>
<td><input class="form-control autocomplete_txt" type='text' data-type="product_name" id='product_name_1' name='product_name[]'/> </td>
</tr>
</table>
<button type="button" class='btn btn-danger delete'>- Delete</button>
<button type="button" class='btn btn-success addbtn'>+ Add More</button>
{!! Form::close() !!}
</div>
<script type="text/javascript">
$(".delete").on('click', function() {
$('.chkbox:checkbox:checked').parents("tr").remove();
$('.check_all').prop("checked", false);
updateSerialNo();
});
var i=$('table tr').length;
$(".addbtn").on('click',function(){
count=$('table tr').length;
var data="<tr><td><input type='checkbox' class='chkbox'/></td>";
data+="<td><span id='sn"+i+"'>"+count+".</span></td>";
data+="<td><input class='form-control autocomplete_txt' type='text' data-type='product_code' id='product_code_"+i+"' name='product_code[]'/></td>";
data+="<td><input class='form-control autocomplete_txt' type='text' data-type='product_name' id='product_name_"+i+"' name='product_name[]'/></td></tr>";
$('table').append(data);
i++;
});
function select_all() {
$('input[class=chkbox]:checkbox').each(function(){
if($('input[class=check_all]:checkbox:checked').length == 0){
$(this).prop("checked", false);
} else {
$(this).prop("checked", true);
}
});
}
function updateSerialNo(){
obj=$('table tr').find('span');
$.each( obj, function( key, value ) {
id=value.id;
$('#'+id).html(key+1);
});
}
//autocomplete script
$(document).on('focus','.autocomplete_txt',function(){
type = $(this).data('type');
if(type =='product_code' )autoType='product_code';
if(type =='product_name' )autoType='name';
$(this).autocomplete({
minLength: 0,
source: function( request, response ) {
$.ajax({
url: "{{ route('productsearch') }}",
dataType: "json",
data: {
term : request.term,
type : type,
},
success: function(data) {
var array = $.map(data, function (item) {
return {
label: item[autoType],
value: item[autoType],
data : item
}
});
response(array)
}
});
},
select: function( event, ui ) {
var data = ui.item.data;
id_arr = $(this).attr('id');
id = id_arr.split("_");
elementId = id[id.length-1];
$('#product_code_'+elementId).val(data.product_code);
$('#product_name_'+elementId).val(data.name);
}
});
});
</script>
[route]
Route::get('/orders/create','PurchasetransactionsController#create')->name('orders.create');
Route::get('productsearch', ['as'=>'productsearch','uses'=>'PurchasetransactionsController#productsearch']);
I've added multiple autocomplete searches to input fields in laravel, and i've always had to look it back up each time i've implemented it. This is working for me. Here is the input which i'm searching for matching abbreviations in my search
<div class="input-group">
#if(isset($_GET['variable_name']))
<input value="{{$_GET['variable_name']}}" type="search" name="variable_name" class="form-control" id="variable_name" autocomplete="off">
#else
<input type="search" name="variable_name" class="form-control" id="variable_name" placeholder="Search" autocomplete="off">
#endif
</div>
Script to control ajax return:
$(document).ready(function($) {
// Set the Options for "Bloodhound" suggestion engine
var engine = new Bloodhound({
remote: {
url: '/find?variable_name=%QUERY%',
wildcard: '%QUERY%'
},
datumTokenizer: Bloodhound.tokenizers.whitespace('variable_name'),
queryTokenizer: Bloodhound.tokenizers.whitespace
});
$('#variable_name').typeahead({
hint: true,
highlight: true,
minLength: 1
}, {
name: 'abbreviations',
source: engine,
display: function(data) {
console.log(data);
return data.abbreviation //Input value to be set when you select a suggestion.
},
templates: {
empty: [
'<div class="list-group search-results-dropdown"><div class="list-group-item">Nothing found.</div></div>'
],
header: [
'<div class="list-group search-results-dropdown">'
],
suggestion: function(data) {
return '<div style="font-weight:normal; margin-top:-10px ! important;" class="list-group-item">' + data.abbreviation + ' ' + data.table + '</div></div>'
}
}
});
});
Controller function
public function find(Request $request) {
$result=Abbreviation::where('abbreviation', 'LIKE', "%{$request->input('variable_name')}%")
->orWhere('name', 'LIKE', "%{$request->input('variable_name')}%")->get();
return response()->json($result);
}
My Route:
Route::get('/find', 'PagesController#find')->name('typeahead.search');
libraries:
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Also need the bloodhound.js and tyepahead.jquery